diff --git a/docs/design/视觉方向预览.html b/docs/design/视觉方向预览.html
index 7282ef5..1fa43a1 100644
--- a/docs/design/视觉方向预览.html
+++ b/docs/design/视觉方向预览.html
@@ -508,12 +508,15 @@
.flow-step:last-child { border-radius: 0 var(--ys-radius-sm) var(--ys-radius-sm) 0; }
.flow-step + .flow-step { border-left: 0; }
.flow-step .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ys-ink-3); }
- .flow-step.done { color: var(--ys-ink); }
- .flow-step.done .dot { background: var(--ys-ok); }
+ /* 圆点按「阶段」着色,与选中与否无关 —— 未选中时也能看出四段各自代表什么;
+ 颜色之外每段仍有文字,不靠颜色单独表意 */
+ .flow-step.tone-wait .dot { background: var(--ys-warn); }
+ .flow-step.tone-lock .dot { background: var(--ys-indigo); }
+ .flow-step.tone-reject .dot { background: var(--ys-danger); }
.flow-step.cur { color: var(--ys-indigo); font-weight: 500; background: var(--ys-indigo-wash); border-color: rgba(46, 75, 168, 0.3); }
.flow-step.cur .dot { background: var(--ys-indigo); }
- .flow-step.rejected .dot { background: var(--ys-danger); }
- .flow-step.rejected { color: var(--ys-danger); }
+ /* 流程条右侧挂一行操作提示:没有提示时,四段读起来像四颗孤立的小按钮 */
+ .flow-hint { margin-left: 10px; font-size: 12px; color: var(--ys-ink-3); }
/* 打印稿(A4):独立顶层路由,浏览器打印只出报表本身 */
.sheet {
@@ -1204,10 +1207,11 @@
月结生命周期
-
草稿
-
待审批
-
已审定(锁定)
-
已驳回 → 回到草稿
+
草稿
+
待审批
+
已审定(锁定)
+
已驳回 → 回到草稿
+
点状态只看该阶段,再点一次取消
diff --git a/frontend/README.md b/frontend/README.md
index a65afbf..871c28e 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -102,7 +102,9 @@ pnpm build # 类型检查 + 生产构建
**语义三色**(2026-10-22 落地):成功 `#1e9e6a` / 警示 `#c98a1b` / 危险 `#c94f4f`,已覆盖 `--el-color-success/warning/danger/error` 及其 `-rgb`、`light-3/5/7/8/9`、`dark-2` 全阶。Element Plus 默认的绿黄红饱和度过高,放在灰底卡片上会跳色。**`-rgb` 变体必须一并覆盖**——组件库的半透明底色用 `rgba(var(--el-color-success-rgb), .1)` 取色,漏改会出现「文字是新的、底色还是旧的」。色阶按 Element Plus 公式预计算,不用 `color-mix()`。
-**组件基调**(同样写在 `src/assets/styles/index.css`,页面不要再重复):按钮高 34px / 圆角 7px / 字号 13px,主按钮是 `#3a58bc → #2e4ba8` 微渐变 + 内高光 + 悬停上抬 1px 并加深阴影;输入框高 34px,焦点为靛蓝描边加 3px 柔光外环;表头 12px/500/字距 `0.04em` + 极浅渐变底,单元格内距 12px 16px,行悬停 120ms;分页需 `background` 模式,当前页与主按钮同材质;`el-tag` 压到 11px 方角。动效统一 120–180ms,只做颜色、阴影与 1px 位移,并尊重 `prefers-reduced-motion`。
+**组件基调**(同样写在 `src/assets/styles/index.css`,页面不要再重复):按钮高 34px / 圆角 7px / 字号 13px,主按钮是 `#3a58bc → #2e4ba8` 微渐变 + 内高光 + 悬停上抬 1px 并加深阴影;输入框高 34px,焦点为靛蓝描边加 3px 柔光外环;表头 12px/500/字距 `0.04em` + 极浅渐变底,单元格内距 12px 16px、行高 1.5,行悬停 120ms;分页需 `background` 模式,当前页与主按钮同材质;`el-tag` 压到 11px 方角。动效统一 120–180ms,只做颜色、阴影与 1px 位移,并尊重 `prefers-reduced-motion`。
+
+**表格行高必须是 1.5**(全局 `.el-table .cell`):Element Plus 默认给单元格 23px 行高(≈1.77 倍),加上 12px 上下内距,单行数据行会到 51px——一屏少看两行。收到 1.5 后单行数据行 44px、纯文本行 44px,与设计稿「12px 内距 + 13px 字」对齐;多行单元格仍按内容撑开。
**页面版式:只用全局类**(新页面不再各写一套)
@@ -119,7 +121,25 @@ pnpm build # 类型检查 + 生产构建
`.cell-num` 有两种用法:直接挂在 `td` 上,或包一层 `` 处理「一行里既文本又数字」。给后者单独撑满一行是必要的——行内元素的 `text-align` 无效;但**不能**给 `.cell-num` 统一加 `display`,那会把 `td` 从 `table-cell` 变成 `inline-block`,表格列宽算法随即失效。
-**菜单图标**(2026-10-22 落地,M1 遗留项):路由 `meta.icon` 只写**语义 key**,图标实现集中在 `src/components/layout/menuIcons.ts` 的注册表(`@element-plus/icons-vue` 具名导入,可摇树)。换图标库或换风格只改注册表,路由表不动。视觉口径沿用设计稿:16px、透明度 0.7(激活项 1)、右间距 9px;分组标题**不加图标**,只有菜单项有。**新增页面必须补 `meta.icon`**,否则菜单项只有文字。
+**表单页版式(公司信息页为样板)**
+
+设置类表单页不要把表单缩在左上角一小块——内容区再宽也只占三分之一,页面看着像没做完。`views/system/settings/index.vue` 是基准:
+
+| 做法 | 口径 |
+| --- | --- |
+| 网格 | 左列 `minmax(0, 1fr)` + 右列固定 480px(实景预览),`align-items: start`;≤1200px 折成单列 |
+| 控件行 | 主控件与说明同一行(`.field-control`):控件 ≤420px,说明跟在后面 20px;≥1600px 说明改右对齐贴卡片右缘,整行铺满而不是缩在左上角 |
+| 实景预览 | 右侧「登录页预览」按**真实登录页 1:1 复刻**(控件 38px 高、品牌名 17px / 0.34em 字距、logo ≤56×36、三项输入含验证码位、登录按钮、版权行),不是缩比示意——控件高度对不上就等于没有所见即所得 |
+| 生效范围 | 独立一张卡列出这份设置会出现在哪(登录页品牌位与版权行 / Excel 导出页眉 / 打印稿页眉),用户不用存完再去别处找 |
+| 预览态 | 预览卡 `aria-hidden` + 标注「示意,非交互」,避免被当成可点的登录框 |
+
+**状态与流程表达(2026-10-23 实测补)**
+
+- 月结生命周期条四段圆点按阶段着色:草稿中性 / 待审批琥珀 / 已审定靛蓝 / 已驳回砖红。颜色之外每段仍有文字,不靠颜色单独表意。
+- 流程条这类分段控件旁要挂一行操作提示(如「点状态只看该阶段,再点一次取消」)。没有提示时,未选中态的四段读起来像四颗孤立的小按钮,不像一条流程。
+- 卡片内空态(工作台 `.b-empty-mini`)在卡片剩余高度内居中,不要全挤在顶部留一整块空白。
+
+**菜单图标**(2026-10-22 落地,M1 遗留项):路由 `meta.icon` 只写**语义 key**,图标实现集中在 `src/components/layout/menuIcons.ts` 的注册表(`@element-plus/icons-vue` 具名导入,可摇树)。换图标库或换风格只改注册表,路由表不动。视觉口径沿用设计稿:16px、透明度 0.7(激活项 1)、右间距 9px;分组标题**不加图标**,只有菜单项有。**新增页面必须补 `meta.icon`**,否则菜单项只有文字。注册表是 **UTF-8** 文件——批量转码脚本会把中文注释转成乱码(2026-10-23 已在该文件踩过一次,已修回),改这个文件不要过任何编码转换。
**签名元素**:**渐隐靛蓝水线**——各页面标题下的细线(56px,左实右虚)与登录页品牌名下的短线(40px),「衍水」之水的提纯,全站唯一的装饰性元素;登录页右上角另有涟漪同心圆与顶部靛蓝天光作背景(低透明度)。与早期深色面板 + 水纹层理同一意象,品牌连续而非推倒重来。
@@ -135,6 +155,7 @@ pnpm build # 类型检查 + 生产构建
- 新页面需响应式到移动端、保留键盘焦点可见、并尊重 `prefers-reduced-motion`
- **确认框统一加 `customClass: 'ys-confirm-box'`,不要再传 `type: 'warning'`** —— Element Plus 的默认 warning 图标会渲染在正文左侧独立成列(多行正文时图标悬在文字块中间),与 v3 靛蓝体系也冲突。全局样式(`src/assets/styles/index.css`)已把图标改为 18px 描边样式并放在标题行左侧,正文与标题文字对齐
- **页面 scoped 里只写本页特有的东西**:表格 / 按钮 / 输入框 / 表头 / 分页的皮肤已经全局覆盖,页面再写一遍与组件库同权重,会受样式注入顺序影响而不稳定;结构用全局类(见上一节表格),样式只补本页独有的块(如展开区、抽屉内的键值表)
+- **状态 / 结果列的固定宽度要按胶囊内容给足**:`.cell` 左右各 16px 内距(共 32px),2 字胶囊约 55px、3 字约 67px、4 字约 79px,宽度不够时 Element Plus 会在胶囊右侧画一个省略号(看起来像多了个小点)。口径:2 字列 ≥96px(如 启用/禁用、成功/失败)、3 字列 ≥108px(如 已核算/已替代/执行中)、4 字列 ≥120px(如 以物抵顶)
## 验证码图片
diff --git a/frontend/src/assets/styles/index.css b/frontend/src/assets/styles/index.css
index 02eef75..10cc895 100644
--- a/frontend/src/assets/styles/index.css
+++ b/frontend/src/assets/styles/index.css
@@ -589,6 +589,12 @@ span.cell-num {
.el-table .cell {
padding: 0 16px;
+ /*
+ * Element Plus 默认给单元格 23px 行高(≈1.77 倍),加上 12px 上下内距,
+ * 单行数据行会到 51px —— 一屏少看两行,也与设计稿的「12px 内距 + 13px 字」
+ * 不符。收到 1.5(中文正文常用行高)后单行约 45px;多行单元格仍按内容撑开。
+ */
+ line-height: 1.5;
}
.el-table__body tr:hover > td.el-table__cell {
diff --git a/frontend/src/components/layout/menuIcons.ts b/frontend/src/components/layout/menuIcons.ts
index 0225399..fbf7b62 100644
--- a/frontend/src/components/layout/menuIcons.ts
+++ b/frontend/src/components/layout/menuIcons.ts
@@ -25,19 +25,19 @@ import {
} from '@element-plus/icons-vue'
/**
- * 鑿滃崟鍥炬爣娉ㄥ唽琛ㄣ??
+ * 菜单图标注册表。
*
- * **璺敱鍙啓璇箟 key锛坢eta.icon锛夛紝涓嶇洿鎺ュ紩鐢ㄥ浘鏍囩粍浠?**锛氳矾鐢辫〃淇濇寔杞婚噺鍙?
- * 搴忓垪鍖栵紝鍥炬爣瀹炵幇鎹㈠簱鎴栨崲椋庢牸鏃跺彧鏀规湰鏂囦欢锛岃矾鐢变笌鑿滃崟缁勪欢涓嶅姩銆?
+ * **路由只写语义 key(meta.icon),不直接引用图标组件**:路由表保持轻量、
+ * 序列化,图标实现换库或换风格时只改本文件,路由与菜单组件不动。
*
- * 鍏峰悕瀵煎叆锛堜笉鐢? `import * as`锛変互淇濊瘉鎽囨爲 鈥斺?? 鏁村寘寮曞叆浼氭妸 200+ 鍥炬爣鎵撹繘
- * 浜х墿銆傛敞鍐岃〃閲屽浣欑殑鍥炬爣鍚屾牱浼氳鎽囨帀锛屼絾浼氳銆岀敤浜嗗摢浜涖?嶉毦浠ユ牳瀵癸紝鏁?
- * 鍙繚鐣欏疄闄呯敤鍒扮殑銆?
+ * 具名导入(不用 `import * as`)以保证摇树 —— 整包引入会把 200+ 图标打进
+ * 产物。注册表里多余的图标同样会被摇掉,但会让「用了哪些」难以核对,
+ * 只保留实际用到的。
*
- * **鍏ㄩ儴鍙栫嚎鎬э紙outline锛夊浘鏍囷紝涓嶇敤 *-filled 鍙樹綋**锛氫晶鏍忓悓涓?灞傜骇娣风敤瀹炲績涓?
- * 绾挎?у浘鏍囦細璁╄彍鍗曠湅鐫?涓嶆槸涓?濂楄瑷?锛堣璁$ǹ 搂04 鐨勫浘鏍囨棌鍗? 16px 绾挎?э級銆?
- * 棣栭〉鐢? House锛堢嚎鎬ф埧灞嬶級鑰岄潪 HomeFilled锛屼汉鍛樼鐞嗙敤 Postcard锛堜汉鍛樻。妗堝崱锛?
- * 鑰岄潪 UserFilled 鈥斺?? 鐢ㄦ埛绠$悊宸插崰 User锛屼汉鍛樻敼鐢ㄦ。妗堝崱浠ュ厤涓や釜銆屼汉銆嶆挒褰€??
+ * **全部取线性(outline)图标,不用 *-filled 变体**:侧栏同一层级混用实心与
+ * 线性图标会让菜单看着不是一套语汇(设计稿 §04 的图标族:16px 线性)。
+ * 首页用 House(线性房屋)而非 HomeFilled,人员管理用 Postcard(人员档案卡)
+ * 而非 UserFilled —— 用户管理已占 User,人员改用档案卡以免两个「人」撞形。
*/
const ICONS = {
home: House,
@@ -63,10 +63,10 @@ const ICONS = {
'report-payment': PieChart,
} satisfies Record
-/** 鑿滃崟鍥炬爣鐨勮涔? key锛堣矾鐢? meta.icon 鐨勫彇鍊艰寖鍥达級銆? */
+/** 菜单图标的语义 key(路由 meta.icon 的取值范围)。 */
export type MenuIconName = keyof typeof ICONS
-/** 鎸夎涔? key 鍙栧浘鏍囷紱鏈厤缃椂杩斿洖 undefined锛堣彍鍗曢」鍙樉绀烘枃瀛楋級銆? */
+/** 按语义 key 取图标;未配置时返回 undefined(菜单项只显示文字)。 */
export function menuIcon(name?: string): Component | undefined {
return name ? (ICONS[name as MenuIconName] ?? undefined) : undefined
}
diff --git a/frontend/src/views/base/personnel/index.vue b/frontend/src/views/base/personnel/index.vue
index 28b716b..de4453c 100644
--- a/frontend/src/views/base/personnel/index.vue
+++ b/frontend/src/views/base/personnel/index.vue
@@ -460,7 +460,7 @@ onMounted(() => {
{{ row.salary_standard ?? '—' }}
-
+
diff --git a/frontend/src/views/base/products/index.vue b/frontend/src/views/base/products/index.vue
index ce55dbd..2064fd1 100644
--- a/frontend/src/views/base/products/index.vue
+++ b/frontend/src/views/base/products/index.vue
@@ -283,7 +283,7 @@ onMounted(() => {
-
+
diff --git a/frontend/src/views/business/businesses/index.vue b/frontend/src/views/business/businesses/index.vue
index 137707d..ffa270c 100644
--- a/frontend/src/views/business/businesses/index.vue
+++ b/frontend/src/views/business/businesses/index.vue
@@ -692,7 +692,7 @@ onMounted(() => {
{{ row.business_date ?? '—' }}
-
+
diff --git a/frontend/src/views/business/receipts/index.vue b/frontend/src/views/business/receipts/index.vue
index 3f0f69a..fb6caf9 100644
--- a/frontend/src/views/business/receipts/index.vue
+++ b/frontend/src/views/business/receipts/index.vue
@@ -348,7 +348,7 @@ onMounted(() => {
{{ row.amount }}
-
+
以物抵顶
@@ -361,7 +361,7 @@ onMounted(() => {
未指定批次
-
+
{
{{ row.payable_amount }}
-
+
v{{ row.calc_version }}
diff --git a/frontend/src/views/dashboard/index.vue b/frontend/src/views/dashboard/index.vue
index b2f669a..c621421 100644
--- a/frontend/src/views/dashboard/index.vue
+++ b/frontend/src/views/dashboard/index.vue
@@ -411,6 +411,8 @@ function formatTime(value: string): string {
.bento-card {
position: relative;
+ display: flex;
+ flex-direction: column;
padding: 16px 18px;
overflow: hidden;
background: linear-gradient(180deg, var(--ys-paper), #fcfcfe);
@@ -511,10 +513,19 @@ function formatTime(value: string): string {
color: var(--ys-ink-3);
}
+/*
+ * 空态在卡片剩余高度里居中:待办/预警是 2×2 的卡,只有一行提示时
+ * 内容全挤在顶部、下方留一大片空白,看起来像没加载完。
+ */
.b-empty-mini {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ justify-content: center;
margin-top: 8px;
font-size: 12px;
color: var(--ys-ink-3);
+ text-align: center;
}
.chart {
diff --git a/frontend/src/views/settlements/index.vue b/frontend/src/views/settlements/index.vue
index 7f7334e..c762d15 100644
--- a/frontend/src/views/settlements/index.vue
+++ b/frontend/src/views/settlements/index.vue
@@ -45,10 +45,10 @@ function resetFilters(): void {
* 复用 search() 意味着**不新增任何后端请求口径**。
*/
const FLOW_STEPS = [
- { value: 1, label: '草稿' },
- { value: 2, label: '待审批' },
- { value: 3, label: '已审定(锁定)' },
- { value: 4, label: '已驳回' },
+ { value: 1, label: '草稿', tone: 'draft' },
+ { value: 2, label: '待审批', tone: 'wait' },
+ { value: 3, label: '已审定(锁定)', tone: 'lock' },
+ { value: 4, label: '已驳回', tone: 'reject' },
] as const
/** 点当前段再点一次即取消筛选(idle 态),避免「点了没反应」的死角 */
@@ -149,8 +149,10 @@ async function handleSubmit(row: Settlement): Promise {
:class="{ 'flow-step__item--cur': filters.status === step.value }"
@click="pickStatus(step.value)"
>
- {{ step.label }}
+
+ {{ step.label }}
+ 点状态只看该阶段,再点一次取消
@@ -328,6 +330,7 @@ async function handleSubmit(row: Settlement): Promise {
.flow-step {
display: flex;
flex-wrap: wrap;
+ align-items: center;
margin-bottom: 14px;
}
@@ -350,6 +353,14 @@ async function handleSubmit(row: Settlement): Promise {
border-color 150ms var(--ys-ease);
}
+/* 未选中时四段必须读起来仍是「一条流程」:给右侧挂一行说明,
+ 高度与分段齐平,避免出现四颗孤立小按钮的观感 */
+.flow-step__hint {
+ margin-left: 10px;
+ font-size: 12px;
+ color: var(--ys-ink-3);
+}
+
.flow-step__item:first-child {
border-radius: var(--ys-radius-sm) 0 0 var(--ys-radius-sm);
}
@@ -378,6 +389,19 @@ async function handleSubmit(row: Settlement): Promise {
background: var(--ys-ink-3);
}
+/* 圆点按阶段着色:未选中时也能看出四段各自代表什么(颜色之外仍有文字) */
+.flow-step__dot--wait {
+ background: var(--ys-warn);
+}
+
+.flow-step__dot--lock {
+ background: var(--ys-indigo);
+}
+
+.flow-step__dot--reject {
+ background: var(--ys-danger);
+}
+
.flow-step__item--cur {
font-weight: 500;
color: var(--ys-indigo);
diff --git a/frontend/src/views/system/business-roles/index.vue b/frontend/src/views/system/business-roles/index.vue
index 6d46f8c..94b048b 100644
--- a/frontend/src/views/system/business-roles/index.vue
+++ b/frontend/src/views/system/business-roles/index.vue
@@ -276,7 +276,7 @@ onMounted(() => {
{{ row.sort_order }}
-
+
diff --git a/frontend/src/views/system/logs/index.vue b/frontend/src/views/system/logs/index.vue
index e192c4c..28077ed 100644
--- a/frontend/src/views/system/logs/index.vue
+++ b/frontend/src/views/system/logs/index.vue
@@ -265,7 +265,7 @@ watch(activeTab, (tab) => {
-
+
{
{{ formatDateTime(row.create_time) }}
-
+
{{ row.sort_order }}
-
+
diff --git a/frontend/src/views/system/settings/index.vue b/frontend/src/views/system/settings/index.vue
index 9332dfe..c04927a 100644
--- a/frontend/src/views/system/settings/index.vue
+++ b/frontend/src/views/system/settings/index.vue
@@ -10,7 +10,10 @@ import { handledError } from '@/utils/error'
* 公司信息设置(2026-10-15 补充需求:公司名称、logo)。
*
* 签名元素是右侧「登录页实景预览」:品牌名与 logo 改动即时反映在一张
- * 缩比登录卡上 —— 所见即所得,避免「保存后去登录页核对」的往返。
+ * **1:1 等比**的登录卡上 —— 所见即所得,避免「保存后去登录页核对」的往返。
+ * 预览按登录页的真实版式复刻(品牌区 / 欢迎回来 / 三项输入 / 登录按钮 /
+ * 版权行),控件尺寸与登录页一致,logo 展示位也按登录页的 ≤56×36 约束。
+ *
* logo 校验(png/jpg ≤2MB)与后端三重校验同口径,前端先拦一道。
*
* 进入页面即加载当前设置(设置页即所见即所得,无需手动刷新)。
@@ -99,79 +102,131 @@ const previewName = computed(() => form.company_name.trim() || '衍水集团')
-
-
-
-
基础信息
+
+
+
+
+
+ 基础信息
+
+
+
+
+
+
+
+
+
最多 64 个字符;保存后对所有在线用户立即生效。
+
+
+
+
+
+
+
+
![公司 logo]()
+
+
+
+
+
+ png / jpg,不超过 2MB;仅展示于登录页品牌位,按 ≤56×36 显示。
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
![公司 logo]()
-
+
-
-
-
png / jpg,不超过 2MB;
展示于登录页品牌位与导出页眉。
+
+
+ 生效范围
+ 保存后对所有人生效
+
+
+
+
+
- 公司名称
+ - 登录页品牌位与版权行 · Excel 导出页眉 · 打印稿页眉
-
-
-
-
-
-
-
+
登录页预览
- 保存后对所有人生效
+ 1:1 等比
-
-
-
![]()
-
{{ previewName }}
-
把每一笔回款,算成一份踏实。
-
+
+
+
+
![]()
+
{{ previewName }}
+
把每一笔回款,算成一份踏实。
+
+
+
+
+
+
+ 用户名
+
+
+
+ 密码
+
+
+
+ 验证码
+
+
+
+
+
+
+
登 录
-
-
-
登 录
+
-
登录页品牌位实景(示意,非交互)
+
登录页品牌位实景(示意,非交互)——显示的是当前编辑值
@@ -181,11 +236,18 @@ const previewName = computed(() => form.company_name.trim() || '衍水集团')