diff --git a/frontend/README.md b/frontend/README.md index e186291..a65afbf 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -79,7 +79,7 @@ pnpm build # 类型检查 + 生产构建 ## 视觉设计 -**设计立意**:公司名「衍水」是水,而中文「流水账」字面就是**流水之账**——系统的全部工作正是记录回款怎么流、绩效怎么算。视觉方向由此而生:中性灰白的水面纸感 + 靛蓝点缀,安静、克制,长时间核数不累眼。完整规范见 `docs/design/视觉方向预览.html`(2026-10-12 起为 v3「质感版」)。 +**设计立意**:公司名「衍水」是水,而中文「流水账」字面就是**流水之账**——系统的全部工作正是记录回款怎么流、绩效怎么算。视觉方向由此而生:中性灰白的水面纸感 + 靛蓝点缀,安静、克制,长时间核数不累眼。完整规范见 `docs/design/视觉方向预览.html`(2026-10-12 起为 v3「质感版」;2026-10-22 升 v4「同步版」——设计稿与已交付的月结 / 审批 / 报表 / 工作台对齐,色板、字体与组件基调未变)。 **配色**(靛蓝只作重点点缀——主按钮 / 链接 / 焦点 / 当前菜单,大面积用中性灰白) @@ -100,17 +100,41 @@ pnpm build # 类型检查 + 生产构建 **落地方式**:品牌色已提升为全局 CSS 变量(`src/assets/styles/index.css` 的 `:root`,`--ys-*` 前缀),登录页、布局与各业务页面共用;全局把 `--el-color-primary` 指向靛蓝,并按 Element Plus 公式预计算了 light-3/5/7/8/9 与 dark-2 色阶(不用 `color-mix()`,避免依赖较新浏览器特性)。旧 token 名(深水青 / 金色系列)在 `:root` 中保留映射,存量页面迁移完成后移除。 -**签名元素**:**渐隐靛蓝水线**——仪表盘等页面标题下的细线(56px,左实右虚)与登录页品牌名下的短线(40px),「衍水」之水的提纯,全站唯一的装饰性元素;登录页右上角另有涟漪同心圆与顶部靛蓝天光作背景(低透明度)。与早期深色面板 + 水纹层理同一意象,品牌连续而非推倒重来。 +**语义三色**(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`。 + +**页面版式:只用全局类**(新页面不再各写一套) + +| 类 | 用途 | +| --- | --- | +| `.page-head` / `.page-title` / `.page-desc` / `.waterline` | 页面头:18px 标题 + 一句 12px 描述 + 水线(**每页一条,不重复**) | +| `.card` / `.card-head` / `.card-title` / `.card-body` / `.card-foot` | 内容卡片;卡脚右对齐,承载分页或合计条 | +| `.filter-bar` / `.filter-bar__actions` | 筛选条(横向 flex);按钮组靠右 | +| `.toolbar` / `.toolbar-note` | 主操作行 / 右侧提示文字 | +| `.section-title` | 区块小标题(13px/600) | +| `.cell-code` / `.cell-num` / `.cell-muted` | 表格列语义:等宽编号 / 数字右对齐 + 等宽数字 / 弱化文本 | +| `.ys-pill` + `--ok` `--wait` `--lock` `--reject` | 状态胶囊(浅底 + 6px 圆点),四档语义与月结生命周期一致 | +| `.ys-rise` | 入场上浮动画(卡片默认已带) | + +`.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`**,否则菜单项只有文字。 + +**签名元素**:**渐隐靛蓝水线**——各页面标题下的细线(56px,左实右虚)与登录页品牌名下的短线(40px),「衍水」之水的提纯,全站唯一的装饰性元素;登录页右上角另有涟漪同心圆与顶部靛蓝天光作背景(低透明度)。与早期深色面板 + 水纹层理同一意象,品牌连续而非推倒重来。 + +**打印视图**(`views/reports/print/`):纸面产物与屏幕稿不同源——独立顶层路由、不挂侧栏顶栏,白灰画布上一张定宽白稿纸,表格 11px 细实线、表头浅底左对齐(数值列表头右对齐)、章节编号、三栏签字(制表 / 复核 / 审批),无圆角无阴影。屏幕上的操作条带 `.no-print`,打印时整块消失。超出打印行数上限时只打前 N 行并在稿面明确提示,不静默截断。 **构图**:登录页品牌内容(名称 / 标语 / 水线)**垂直居中聚成一块**,配一层柔和径向光晕作落点。 早期版本用 `space-between` 把品牌与标语顶到两端,中间空出大半屏,两处都成了孤岛——超宽屏会把这种疏离放大得更明显。**内容块要限宽**(`max-width`),否则宽屏下文字会被拉散。 -### 写代码时的四条约定 +### 写代码时的五条约定 - 覆盖 Element Plus 主题请改 **CSS 变量**(`--el-button-bg-color` 等),不要直接写 `background-color` —— 后者与 `.el-button--primary` 同权重,会受样式注入顺序影响而不稳定 - 中文可在任意字间断行,**关键文案的断行位置要显式控制**(如主标语拆成两个 ``),交给浏览器自动换行会出现单字独占一行的难看结果 - 新页面需响应式到移动端、保留键盘焦点可见、并尊重 `prefers-reduced-motion` - **确认框统一加 `customClass: 'ys-confirm-box'`,不要再传 `type: 'warning'`** —— Element Plus 的默认 warning 图标会渲染在正文左侧独立成列(多行正文时图标悬在文字块中间),与 v3 靛蓝体系也冲突。全局样式(`src/assets/styles/index.css`)已把图标改为 18px 描边样式并放在标题行左侧,正文与标题文字对齐 +- **页面 scoped 里只写本页特有的东西**:表格 / 按钮 / 输入框 / 表头 / 分页的皮肤已经全局覆盖,页面再写一遍与组件库同权重,会受样式注入顺序影响而不稳定;结构用全局类(见上一节表格),样式只补本页独有的块(如展开区、抽屉内的键值表) ## 验证码图片 diff --git a/frontend/package.json b/frontend/package.json index 5d1bc27..e964288 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "type-check": "vue-tsc --noEmit" }, "dependencies": { + "@element-plus/icons-vue": "^2.3.2", "@vue/devtools-api": "^8.1.5", "axios": "^1.20.0", "echarts": "^5.6.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 995c85e..b62c47e 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.2 + version: 2.3.2(vue@3.5.43(typescript@5.9.3)) '@vue/devtools-api': specifier: ^8.1.5 version: 8.2.1 diff --git a/frontend/src/assets/styles/index.css b/frontend/src/assets/styles/index.css index f9a1265..02eef75 100644 --- a/frontend/src/assets/styles/index.css +++ b/frontend/src/assets/styles/index.css @@ -1,24 +1,60 @@ -/* 全局样式:重置 + 品牌 token + 基础版式 */ +/* + * 全局样式:品牌 token + Element Plus 组件基调 + 全站版式工具类 + * + * 设计来源:docs/design/视觉方向预览.html(v4,2026-10-22)。 + * 落实口径:token 与设计稿 :root 同名同值;组件基调按设计稿 §03; + * 版式工具类(.page-head / .card / .filter-bar / .data-table 等)按 §04、§09, + * 各页面只写业务差异,不再重复定义这些类。 + * + * 加载顺序:main.ts 里排在 element-plus/dist/index.css 之后, + * 同权重选择器靠后者生效 —— 覆盖组件库样式必须与此保持一致。 + */ :root { - /* - * 品牌色(2026-10-12 视觉方向:企业级扁平 · 轻质感 · 靛蓝点缀)。 - * 靛蓝只作重点点缀(主按钮/链接/焦点/当前菜单),大面积用中性灰白。 - * 设计预览与规范:docs/design/视觉方向预览.html - */ + /* ── 品牌色 ─────────────────────────────────────────────────────────── */ + /* 靛蓝只作重点点缀(主按钮/链接/焦点/当前菜单),大面积用中性灰白。 */ --ys-indigo: #2e4ba8; + --ys-indigo-hi: #3a58bc; /* 主按钮渐变亮端 */ --ys-indigo-deep: #223a85; --ys-indigo-wash: #edf1fa; + --ys-paper: #ffffff; --ys-mist: #f4f5f8; - --ys-line: rgba(28, 35, 51, 0.09); + --ys-aside-1: #f2f4f8; + --ys-aside-2: #ecedf3; + --ys-ink: #1c2333; --ys-ink-2: #5b6577; --ys-ink-3: #9aa3b2; + --ys-line: rgba(28, 35, 51, 0.09); + --ys-line-soft: rgba(28, 35, 51, 0.065); + + /* 语义三色:饱和度压到与靛蓝同级,保证灰底卡片上不跳色 */ + --ys-ok: #1e9e6a; + --ys-ok-wash: #ecf8f2; + --ys-ok-wash-2: #e4f3ec; + --ys-warn: #c98a1b; + --ys-warn-wash: #fbf4e4; + --ys-warn-wash-2: #f8efd9; + --ys-danger: #c94f4f; + --ys-danger-wash: #fbeded; + --ys-danger-wash-2: #f6e3e3; + + /* 形体与材质 */ + --ys-radius: 10px; + --ys-radius-sm: 7px; + --ys-ease: cubic-bezier(0.25, 0.1, 0.25, 1); + --ys-shadow-card: + 0 1px 2px rgba(28, 35, 51, 0.04), + 0 3px 10px rgba(28, 35, 51, 0.03); + --ys-shadow-raise: + 0 1px 2px rgba(28, 35, 51, 0.05), + 0 6px 18px rgba(28, 35, 51, 0.07); + /* - * 旧 token 名保留映射(BasicLayout / 登录页存量引用,值迁移到新体系: - * 深水青 → 冷墨系、金色 → 靛蓝系),存量页面迁移完成后移除。 + * 旧 token 名保留映射(存量页面引用,值已迁移到新体系): + * 深水青 → 冷墨系、金色 → 靛蓝系。存量页面迁移完成后移除。 */ --ys-ink-900: #16203a; --ys-ink-800: #1c2333; @@ -26,7 +62,6 @@ --ys-ink-600: #3a4a6e; --ys-gold-bright: #6d81c2; --ys-gold-deep: #2e4ba8; - --ys-text: #1c2333; --ys-muted: #5b6577; @@ -36,11 +71,12 @@ */ --ys-font-brand: 'Noto Sans SC', 'Source Han Sans SC', 'HarmonyOS Sans SC', 'PingFang SC', sans-serif; + --ys-font-mono: 'SF Mono', 'Cascadia Mono', Consolas, 'Courier New', monospace; + /* ── Element Plus 主色 ──────────────────────────────────────────────── */ /* - * Element Plus 主色 = 靛蓝(白字对比约 7.5:1)。 - * 色阶为按 Element Plus 公式「主色与白色混合」预先算好的静态值 —— - * 不用 color-mix(),避免依赖较新的浏览器特性。 + * 白字对比约 7.5:1。色阶为按 Element Plus 公式「主色与白色混合」预先算好的 + * 静态值 —— 不用 color-mix(),避免依赖较新的浏览器特性。 */ --el-color-primary: #2e4ba8; --el-color-primary-light-3: #6d81c2; @@ -49,6 +85,72 @@ --el-color-primary-light-8: #d5dbee; --el-color-primary-light-9: #eaedf6; --el-color-primary-dark-2: #253c86; + + /* + * 语义三色(设计稿规范值):成功 #1E9E6A / 警示 #C98A1B / 危险 #C94F4F。 + * + * Element Plus 默认是绿 #67C23A / 黄 #E6A23C / 红 #F56C6C,饱和度明显高于 + * 本系统「中性灰白 + 靛蓝点缀」基调,状态标签放在灰底卡片上会跳色。 + * 色阶同样按 Element Plus 公式预计算(light-N = N×10% 白 + 其余主色, + * dark-2 = 20% 黑 + 80% 主色),不用 color-mix(),与主色色阶口径一致。 + * -rgb 变体一并覆盖:组件库的半透明底色(rgba(var(--el-color-success-rgb), .1)) + * 依赖它,漏改会出现「文字是新的、底色还是旧的」。 + * error 与 danger 同值:表单校验错误与危险操作在本系统语义一致。 + */ + --el-color-success: #1e9e6a; + --el-color-success-rgb: 30, 158, 106; + --el-color-success-light-3: #62bb97; + --el-color-success-light-5: #8eceb4; + --el-color-success-light-7: #bce2d2; + --el-color-success-light-8: #d2ece1; + --el-color-success-light-9: #e8f5f0; + --el-color-success-dark-2: #187e55; + + --el-color-warning: #c98a1b; + --el-color-warning-rgb: 201, 138, 27; + --el-color-warning-light-3: #d9ad5f; + --el-color-warning-light-5: #e4c48d; + --el-color-warning-light-7: #efdcbb; + --el-color-warning-light-8: #f4e8d1; + --el-color-warning-light-9: #faf3e8; + --el-color-warning-dark-2: #a16e16; + + --el-color-danger: #c94f4f; + --el-color-danger-rgb: 201, 79, 79; + --el-color-danger-light-3: #d98484; + --el-color-danger-light-5: #e4a7a7; + --el-color-danger-light-7: #efcaca; + --el-color-danger-light-8: #f4dcdc; + --el-color-danger-light-9: #faeded; + --el-color-danger-dark-2: #a13f3f; + + --el-color-error: #c94f4f; + --el-color-error-rgb: 201, 79, 79; + --el-color-error-light-3: #d98484; + --el-color-error-light-5: #e4a7a7; + --el-color-error-light-7: #efcaca; + --el-color-error-light-8: #f4dcdc; + --el-color-error-light-9: #faeded; + --el-color-error-dark-2: #a13f3f; + + /* ── Element Plus 形体基准(设计稿 §03:控件高 34px、圆角 7px)─────── */ + --el-component-size: 34px; + --el-component-size-small: 28px; + --el-component-size-large: 38px; + --el-border-radius-base: 7px; + --el-border-radius-small: 6px; + + /* ── Element Plus 表格 ──────────────────────────────────────────────── */ + --el-table-border-color: var(--ys-line-soft); + --el-table-text-color: var(--ys-ink); + --el-table-header-text-color: var(--ys-ink-3); + --el-table-row-hover-bg-color: #f7f9fd; + /* 斑马纹压到几乎不可见:设计稿列表只有行悬停,没有斑马带 */ + --el-fill-color-lighter: #fafbfd; + + /* ── 字体 ───────────────────────────────────────────────────────────── */ + --el-font-family: 'Noto Sans SC', 'Source Han Sans SC', 'HarmonyOS Sans SC', 'PingFang SC', + 'Helvetica Neue', Arial, sans-serif; } * { @@ -73,15 +175,528 @@ body { -webkit-font-smoothing: antialiased; } -/* 系统管理列表页共用的版式(原在三个页面 scoped 中各自重复) */ -.filter-form { - margin-bottom: 4px; +/* 滚动条:细、低对比,避免在密集表格页抢视线 */ +*::-webkit-scrollbar { + width: 8px; + height: 8px; } +*::-webkit-scrollbar-thumb { + background-color: rgba(28, 35, 51, 0.16); + border: 2px solid transparent; + border-radius: 6px; + background-clip: padding-box; +} + +*::-webkit-scrollbar-thumb:hover { + background-color: rgba(28, 35, 51, 0.28); +} + +*::-webkit-scrollbar-track { + background: transparent; +} + +/* ══════════════════════════════════════════════════════════════════════ + * 全站版式工具类(设计稿 §04 / §09):页面不再各自重复定义 + * ══════════════════════════════════════════════════════════════════════ */ + +/* 页面头:标题 + 描述 + 签名水线,每页出现一次 */ +.page-head { + margin-bottom: 18px; +} + +.page-title { + margin: 0; + font-size: 18px; + font-weight: 600; + letter-spacing: -0.01em; + color: var(--ys-ink); +} + +.page-desc { + margin: 6px 0 0; + font-size: 12px; + letter-spacing: 0.01em; + color: var(--ys-ink-3); +} + +/* + * 签名元素:渐隐靛蓝水线。 + * 全站唯一的装饰性元素 —— 其余层次靠底色、描边与阴影区分, + * 所以每页只允许出现一次(页面标题下,或登录卡品牌区)。 + */ +.waterline { + display: block; + width: 56px; + height: 2px; + margin-top: 10px; + border-radius: 1px; + background: linear-gradient(90deg, var(--ys-indigo), rgba(46, 75, 168, 0)); +} + +/* 区块小标题(抽屉内、卡片内分组) */ +.section-title { + margin: 0 0 10px; + font-size: 13px; + font-weight: 600; + letter-spacing: 0.02em; + color: var(--ys-ink-2); +} + +/* 卡片:纸白微渐变 + 半透明描边 + 双层柔影 */ +.card { + background: linear-gradient(180deg, var(--ys-paper), #fcfcfe); + border: 1px solid var(--ys-line); + border-radius: var(--ys-radius); + box-shadow: var(--ys-shadow-card); +} + +.card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 13px 16px; + border-bottom: 1px solid var(--ys-line-soft); +} + +.card-title { + font-size: 15px; + font-weight: 600; + color: var(--ys-ink); +} + +.card-body { + padding: 16px; +} + +.card-foot { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 10px 16px; + background: linear-gradient(180deg, #fdfdfe, #fbfcfe); + border-top: 1px solid var(--ys-line-soft); + border-radius: 0 0 var(--ys-radius) var(--ys-radius); +} + +/* 筛选条:与卡片同材质,比表格低一档体量 */ +.filter-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + padding: 14px 16px; + margin-bottom: 14px; + background: linear-gradient(180deg, var(--ys-paper), #fcfcfe); + border: 1px solid var(--ys-line); + border-radius: var(--ys-radius); + box-shadow: var(--ys-shadow-card); +} + +.filter-bar .el-form-item { + margin-right: 0; + margin-bottom: 0; +} + +.filter-bar__actions { + display: flex; + gap: 10px; + margin-left: auto; +} + +/* 工具栏:主操作行,与筛选条/表格区分(无底色,只占位) */ .toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; margin-bottom: 12px; } +.toolbar-note { + font-size: 12px; + color: var(--ys-ink-3); +} + +/* 编号/代码类单元格:等宽字体,弱一档颜色 */ +.cell-code { + font-family: var(--ys-font-mono); + font-size: 12px; + color: var(--ys-ink-2); +} + +/* + * 金额/数量类单元格:等宽数字右对齐(对齐是工资表的可读性前提)。 + * + * 两种用法都要成立: + * 1) 直接挂在 td 上 —— 单元格本身就是右对齐的容器; + * 2) 包在单元格里当 —— 一行里既有文本又有数字时用。 + * 行内元素的 text-align 无效,所以要给 span 单独撑满一行再右对齐; + * 反过来不能给 .cell-num 统一加 display,那会把 td 从 table-cell 变成 + * inline-block,整个表格的列宽算法随即失效。 + */ +.cell-num { + font-variant-numeric: tabular-nums; +} + +td.cell-num, +th.cell-num { + text-align: right; +} + +span.cell-num { + display: block; + text-align: right; +} + +.cell-muted { + color: var(--ys-ink-3); +} + +/* + * 状态 pill(设计稿 §03):浅底 + 细边 + 6px 圆点。 + * 四档语义与月结生命周期一致:草稿/待处理(中性)、待办(琥珀)、 + * 已锁定/完成(靛蓝)、驳回(砖红)。 + */ +.ys-pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 2px 10px; + font-size: 12px; + line-height: 18px; + white-space: nowrap; + color: var(--ys-ink-2); + background: linear-gradient(180deg, #f4f5f8, #eff1f5); + border: 1px solid var(--ys-line); + border-radius: 999px; +} + +.ys-pill::before { + content: ''; + flex: none; + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + opacity: 0.65; +} + +.ys-pill--ok { + color: var(--ys-ok); + background: linear-gradient(180deg, var(--ys-ok-wash), var(--ys-ok-wash-2)); + border-color: rgba(30, 158, 106, 0.2); +} + +.ys-pill--wait { + color: var(--ys-warn); + background: linear-gradient(180deg, var(--ys-warn-wash), var(--ys-warn-wash-2)); + border-color: rgba(201, 138, 27, 0.22); +} + +.ys-pill--lock { + color: var(--ys-indigo); + background: linear-gradient(180deg, #eef2fc, #e5eaf8); + border-color: rgba(46, 75, 168, 0.2); +} + +.ys-pill--reject { + color: var(--ys-danger); + background: linear-gradient(180deg, var(--ys-danger-wash), var(--ys-danger-wash-2)); + border-color: rgba(201, 79, 79, 0.2); +} + +/* ══════════════════════════════════════════════════════════════════════ + * Element Plus 组件基调(设计稿 §02 字体与数字 / §03 组件 / §04 表格) + * ══════════════════════════════════════════════════════════════════════ */ + +/* 按钮:高 34px、圆角 7px、字号 13px、150ms 过渡 */ +.el-button { + height: var(--el-component-size); + padding: 0 15px; + font-size: 13px; + font-weight: 400; + border-radius: var(--ys-radius-sm); + transition: + background-color 150ms var(--ys-ease), + background-image 150ms var(--ys-ease), + border-color 150ms var(--ys-ease), + color 150ms var(--ys-ease), + box-shadow 150ms var(--ys-ease), + transform 150ms var(--ys-ease); +} + +.el-button--large { + font-size: 14px; +} + +.el-button--small { + height: var(--el-component-size-small); + padding: 0 11px; + font-size: 12px; +} + +.el-button + .el-button { + margin-left: 0; +} + +.el-button.is-text, +.el-button.is-link { + height: auto; + padding: 2px 0; + font-size: 13px; +} + +/* 主按钮:靛蓝微渐变 + 顶部内高光,悬停抬高 1px 并扩散柔影 */ +.el-button--primary:not(.is-plain):not(.is-text):not(.is-link):not(.is-disabled) { + background-color: var(--ys-indigo); + background-image: linear-gradient(180deg, var(--ys-indigo-hi), var(--ys-indigo)); + border-color: var(--ys-indigo); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.18), + 0 1px 2px rgba(46, 75, 168, 0.22); +} + +.el-button--primary:not(.is-plain):not(.is-text):not(.is-link):not(.is-disabled):hover { + background-image: linear-gradient(180deg, #31509f, var(--ys-indigo-deep)); + border-color: var(--ys-indigo-deep); + transform: translateY(-1px); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.2), + 0 4px 12px rgba(46, 75, 168, 0.3); +} + +.el-button--primary:not(.is-plain):not(.is-text):not(.is-link):not(.is-disabled):active { + transform: translateY(0); + background-image: linear-gradient(180deg, #2b4699, #1f3478); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.18); +} + +.el-button--primary.is-disabled, +.el-button--primary.is-loading { + background-image: none; + box-shadow: none; +} + +/* + * 默认按钮:纸白微渐变 + 细描边,悬停只加靛蓝描边与文字色,不做位移 + * —— 筛选条里可能并排四五个默认按钮,集体位移会让整行抖动。 + */ +.el-button:not( + .el-button--primary, + .el-button--success, + .el-button--warning, + .el-button--danger, + .el-button--info, + .is-link, + .is-text, + .is-plain + ) { + background-image: linear-gradient(180deg, var(--ys-paper), #fbfcfe); + border-color: var(--ys-line); + box-shadow: 0 1px 2px rgba(28, 35, 51, 0.03); +} + +.el-button:not( + .el-button--primary, + .el-button--success, + .el-button--warning, + .el-button--danger, + .el-button--info, + .is-link, + .is-text, + .is-plain + ):hover { + color: var(--ys-indigo); + background-image: linear-gradient(180deg, var(--ys-paper), #f8fafd); + border-color: rgba(46, 75, 168, 0.45); +} + +/* 危险按钮:白底 + 砖红文字与描边(红色只提示边界,不做大面积填充) */ +.el-button--danger:not(.is-link):not(.is-text):not(.is-plain):not(.is-disabled) { + color: var(--ys-danger); + background-color: var(--ys-paper); + background-image: linear-gradient(180deg, var(--ys-paper), #fdfbfb); + border-color: rgba(201, 79, 79, 0.28); + box-shadow: 0 1px 2px rgba(28, 35, 51, 0.03); +} + +.el-button--danger:not(.is-link):not(.is-text):not(.is-plain):not(.is-disabled):hover { + color: #b23f3f; + background-color: var(--ys-danger-wash); + border-color: rgba(201, 79, 79, 0.5); +} + +/* 输入框 / 选择器 / 文本域:微内阴影 + 靛蓝焦点环 */ +.el-input__wrapper, +.el-select__wrapper, +.el-textarea__inner { + border-radius: var(--ys-radius-sm); + box-shadow: + 0 0 0 1px rgba(28, 35, 51, 0.13) inset, + inset 0 1px 2px rgba(28, 35, 51, 0.035); + transition: box-shadow 150ms var(--ys-ease); +} + +.el-input__wrapper:hover, +.el-select__wrapper:hover, +.el-textarea__inner:hover { + box-shadow: + 0 0 0 1px rgba(28, 35, 51, 0.24) inset, + inset 0 1px 2px rgba(28, 35, 51, 0.035); +} + +.el-input__wrapper.is-focus, +.el-select__wrapper.is-focused, +.el-textarea__inner:focus { + box-shadow: + 0 0 0 1px var(--ys-indigo) inset, + inset 0 1px 2px rgba(28, 35, 51, 0.03), + 0 0 0 3px rgba(46, 75, 168, 0.13); +} + +.el-input__inner, +.el-textarea__inner { + font-size: 13px; +} + +/* 表单标签:13px 次墨,不抢输入框的注意力 */ +.el-form-item__label { + font-size: 13px; + color: var(--ys-ink-2); +} + +/* 表格容器:与卡片同材质;裸表格(未套 .card)也不会贴在雾灰底上 */ +.el-table { + border-radius: var(--ys-radius); + background-color: transparent; +} + +.el-table th.el-table__cell { + padding: 10px 0; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.04em; + background-color: transparent; + background-image: linear-gradient(180deg, #fafbfc, #f5f6f9); +} + +.el-table td.el-table__cell { + padding: 12px 0; + font-size: 13px; +} + +.el-table .cell { + padding: 0 16px; +} + +.el-table__body tr:hover > td.el-table__cell { + transition: background-color 120ms var(--ys-ease); +} + +/* 分页:当前页与主按钮同材质(靛蓝渐变 + 内高光) */ +.el-pagination button, +.el-pagination .el-pager li { + border-radius: 6px; + font-variant-numeric: tabular-nums; +} + +.el-pagination.is-background .el-pager li.is-active { + color: #fff; + background-color: var(--ys-indigo); + background-image: linear-gradient(180deg, var(--ys-indigo-hi), var(--ys-indigo)); + border: 1px solid var(--ys-indigo); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.18), + 0 1px 2px rgba(46, 75, 168, 0.22); +} + +/* 标签:默认压成 11px 方角小签(状态列请改用 .ys-pill) */ +.el-tag { + font-size: 11px; + border-radius: 4px; +} + +.el-tag--small { + height: 20px; + padding: 0 7px; + font-size: 11px; +} + +/* 弹层:淡入 + 上移 4px(160ms),与设计稿 §08 一致 */ +.el-popper.is-light { + border-radius: var(--ys-radius-sm); + box-shadow: var(--ys-shadow-raise); +} + +.el-drawer__header { + margin-bottom: 0; + padding: 16px 20px; + font-size: 15px; + font-weight: 600; + color: var(--ys-ink); + border-bottom: 1px solid var(--ys-line-soft); +} + +.el-drawer__body { + padding: 18px 20px; +} + +/* 空状态:弱墨、留白,不喊叫 */ +.el-table__empty-text { + font-size: 13px; + color: var(--ys-ink-3); +} + +/* 入场上浮:卡片/面板通用(设计稿 §08 320ms,可被页面用 animation-delay 交错) */ +@keyframes ys-rise { + from { + opacity: 0; + transform: translateY(6px); + } +} + +.ys-rise { + animation: ys-rise 320ms var(--ys-ease) backwards; +} + +/* 不可再补装饰的场合(打印)统一去动效 */ +@media print { + .ys-rise { + animation: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .el-button, + .el-input__wrapper, + .el-select__wrapper, + .el-textarea__inner, + .el-table__body tr:hover > td.el-table__cell { + transition: none; + } + + .el-button--primary:not(.is-disabled):hover, + .el-button--primary:not(.is-disabled):active { + transform: none; + } + + .ys-rise { + animation: none; + } +} + +/* + * 分页放进卡片页脚时去掉自身的上外边距 —— 页脚已有 padding, + * 再加 14px 会让卡片底部比顶部厚一档(各页原本各写一遍 :deep 覆盖)。 + * 连 .app-pager 一起写:AppPagination 组件自己的 scoped 样式里有 + * margin-top,同权重下后注入的 scoped 会赢,这里多一级类压住它。 + */ +.card-foot .el-pagination.app-pager { + margin-top: 0; +} + /* * 确认框(ElMessageBox)统一视觉 * diff --git a/frontend/src/components/layout/AppMenu.vue b/frontend/src/components/layout/AppMenu.vue index c850200..5ed73ea 100644 --- a/frontend/src/components/layout/AppMenu.vue +++ b/frontend/src/components/layout/AppMenu.vue @@ -11,11 +11,19 @@ const { topLevelMenus, groupedMenus } = useAppMenu()