@wycm9527/morganite 0.0.0-stage → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/LICENSE +33 -0
  3. package/README.md +62 -2
  4. package/bridge/base.css +138 -0
  5. package/bridge/echarts.d.ts +44 -0
  6. package/bridge/echarts.js +217 -0
  7. package/bridge/element-plus.css +785 -0
  8. package/bridge/fonts.css +124 -0
  9. package/bridge/icon-merge.d.ts +5 -0
  10. package/bridge/icon-merge.js +103 -0
  11. package/bridge/iconpark.config.ts +75 -0
  12. package/bridge/iconpark.css +29 -0
  13. package/bridge/react/ConfirmBar.tsx +43 -0
  14. package/bridge/react/EChart.tsx +45 -0
  15. package/bridge/react/PageHeader.tsx +24 -0
  16. package/bridge/react/SectionHeader.tsx +17 -0
  17. package/bridge/react/StatCard.tsx +33 -0
  18. package/bridge/react/TableSkeleton.tsx +11 -0
  19. package/bridge/react/TrendChart.tsx +8 -0
  20. package/bridge/recipes.css +444 -0
  21. package/bridge/shadcn-globals.css +6 -0
  22. package/bridge/shadcn.css +238 -0
  23. package/bridge/vue/ConfirmBar.vue +23 -0
  24. package/bridge/vue/EChart.vue +43 -0
  25. package/bridge/vue/KitchenSink.vue +507 -0
  26. package/bridge/vue/PageHeader.vue +23 -0
  27. package/bridge/vue/SectionHeader.vue +15 -0
  28. package/bridge/vue/StatCard.vue +33 -0
  29. package/bridge/vue/TableSkeleton.vue +14 -0
  30. package/bridge/vue/TrendChart.vue +11 -0
  31. package/bridge/vue/confirm.js +18 -0
  32. package/bridge/vue/inlineConfirm.js +21 -0
  33. package/design-system/AUDIT.md +277 -0
  34. package/design-system/DESIGN.md +176 -0
  35. package/design-system/contrast.md +71 -0
  36. package/design-system/preview/drawer.html +38 -0
  37. package/design-system/preview/form.html +40 -0
  38. package/design-system/preview/index.html +21 -0
  39. package/design-system/preview/preview.css +190 -0
  40. package/design-system/preview/preview.local.css +17 -0
  41. package/design-system/preview/shell.html +50 -0
  42. package/design-system/scope-map.json +4 -0
  43. package/design-system/style-dictionary.config.mjs +172 -0
  44. package/design-system/theme-map.json +16 -0
  45. package/design-system/themes/dark/THEME.md +29 -0
  46. package/design-system/themes/dark/tokens/semantic.tokens.json +354 -0
  47. package/design-system/token-board.html +137 -0
  48. package/design-system/tokens/primitives.tokens.json +1898 -0
  49. package/design-system/tokens/semantic.tokens.json +1080 -0
  50. package/design-system.json +217 -0
  51. package/migration/roles.json +743 -0
  52. package/package.json +82 -4
  53. package/templates/AGENTS.md +13 -0
  54. package/templates/KitchenSink.tsx +179 -0
  55. package/templates/accept.config.mjs +28 -0
  56. package/templates/entry-css.css +6 -0
  57. package/templates/entry-element-plus.css +9 -0
  58. package/templates/entry-shadcn.css +7 -0
  59. package/templates/notes-css.md +4 -0
  60. package/templates/notes-element-plus.md +7 -0
  61. package/templates/notes-shadcn.md +7 -0
@@ -0,0 +1,17 @@
1
+ // 板块标题(种子配方组件 · React 版,与 bridge/vue/SectionHeader.vue 同形):看板页在 H1 下分板块的二级标题,actions 放只作用于这一板块的筛选与操作。
2
+ // 样式来自 recipes.css 的 .section-head。页面级操作放 PageHeader 的 actions;不值一个标题的次要分组用配方类 .section-label。
3
+ import * as React from 'react'
4
+
5
+ export interface SectionHeaderProps {
6
+ title: React.ReactNode
7
+ actions?: React.ReactNode
8
+ }
9
+
10
+ export function SectionHeader({ title, actions }: SectionHeaderProps) {
11
+ return (
12
+ <div className="section-head">
13
+ <h2>{title}</h2>
14
+ {actions ? <div className="right">{actions}</div> : null}
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,33 @@
1
+ // 统计卡(种子配方组件 · React 版,与 bridge/vue/StatCard.vue 同形):数据大字 + 涨跌 + 口径说明。样式来自 recipes.css 的 .stat-card。
2
+ // up 是箭头方向,positive 是颜色(好坏);卡片与普通卡片同一形态,不加品牌指示条。
3
+ import * as React from 'react'
4
+
5
+ export interface StatCardProps {
6
+ label: string
7
+ value: string | number
8
+ delta?: string
9
+ up?: boolean
10
+ positive?: boolean
11
+ note?: string
12
+ hint?: string
13
+ labelExtra?: React.ReactNode
14
+ }
15
+
16
+ export function StatCard({ label, value, delta, up = true, positive, note, hint, labelExtra }: StatCardProps) {
17
+ const good = positive === undefined ? up : positive
18
+ return (
19
+ <div className="stat-card">
20
+ <div className="stat-card__label">
21
+ <span>{label}</span>
22
+ {hint && <button type="button" className="stat-card__hint" title={hint} aria-label={`${label}的统计口径说明`}>
23
+ <svg className="lucide lucide-info" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4M12 8h.01" /></svg>
24
+ </button>}
25
+ {labelExtra}
26
+ </div>
27
+ <div className="stat-card__value num">{value}</div>
28
+ {delta ? (
29
+ <div className={`stat-card__delta num ${good ? 'up' : 'down'}`}>{up ? '▲' : '▼'} {delta}{note && <span className="stat-card__note"> · {note}</span>}</div>
30
+ ) : note ? <div className="stat-card__note">{note}</div> : null}
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,11 @@
1
+ // 表格骨架(种子配方组件 · React 版):按目标表格的形状占位(表头 + 等高行 + 列宽),加载完成后不发生版面跳动。样式来自 recipes.css 的 .tskel。
2
+ export function TableSkeleton({ rows = 6, cols = [200, 160, 120, 120, 100] }: { rows?: number; cols?: number[] }) {
3
+ return (
4
+ <div className="tskel" role="status" aria-live="polite" aria-label="正在加载">
5
+ <div className="tskel-head">{cols.map((w, i) => <span key={i} className="cell" style={{ flex: `0 0 ${w}px` }}><i className="bar head" /></span>)}</div>
6
+ {Array.from({ length: rows }, (_, r) => (
7
+ <div key={r} className="tskel-row">{cols.map((w, i) => <span key={i} className="cell" style={{ flex: `0 0 ${w}px` }}><i className={`bar${(r + i) % 3 === 0 ? ' short' : ''}`} /></span>)}</div>
8
+ ))}
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,8 @@
1
+ // 趋势折线(种子配方组件 · React 版):桥接 trendLine 配方——计数型自动整数刻度、单序列渐变面积、多序列不填面积。
2
+ import * as React from 'react'
3
+ import { EChart } from './EChart'
4
+ import { trendLine, areaGradient } from '../echarts.js'
5
+
6
+ export function TrendChart({ days, series, count }: { days: string[]; series: { name: string; data: number[] }[]; count?: boolean }) {
7
+ return <EChart deps={[days, series, count]} option={(t, ec) => trendLine({ days, series, count, area: series.length === 1 ? areaGradient(ec) : undefined })} />
8
+ }
@@ -0,0 +1,444 @@
1
+ /* 配方层(recipes):DESIGN.md「页面骨架 / 组件配方」的可复制实现。
2
+ 与组件库无关,只引用 design-system/dist 的 CSS 变量;引入顺序在桥接之后、项目自己的 app.css 之前。
3
+ 项目只写业务页面,不重写这些类;需要新配方时先向种子提提案,批准后改这里,各项目升级后拿到。
4
+ 效果预览(视觉基准)直接引用这份文件:预览页里出现的页面形态都应在这里有对应的类,预览自己的样式表只放静态组件与示意图表。
5
+ 元素外边距写全:Element 的样式入口不引 base.css,h1 / p / ul 带着浏览器默认外边距。 */
6
+
7
+ /* ── 基础 ── */
8
+ *, *::before, *::after { box-sizing: border-box; }
9
+ [hidden] { display: none !important; } /* 模板项目用 hidden 属性切换空态 / 表格时,配方类的 display 不得把它压掉 */
10
+ /* 根字号保持浏览器默认 16px:Tailwind 的 rem 刻度(size-4 = 1rem)依赖它,改在 html 上会让 shadcn 的每个尺寸缩 12.5%;正文字号落在 body */
11
+ html { font-family: var(--font-family-body); line-height: var(--text-body-line-height); }
12
+ body { margin: 0; font-size: var(--text-body-size); background: var(--color-bg-page); color: var(--color-text-primary); }
13
+ a { color: inherit; text-decoration: none; }
14
+ .num { font-family: var(--text-numeric-family); font-variant-numeric: var(--text-numeric-variant); } /* 数字数据:正文字体 + 等宽数字(text.numeric.*;数据大字才用 D-DIN 系字体);单号、代码用下面的 .mono */
15
+ .mono { font-family: var(--font-family-code); font-variant-numeric: var(--text-numeric-variant); }
16
+
17
+ /* ── 图标尺寸六档:IconPark 走 font-size(见 iconpark.css);lucide 这类 SVG 图标库走 width / height,同一组类名 ── */
18
+ svg.i-icon--xs { width: var(--size-icon-xs); height: var(--size-icon-xs); flex: none; }
19
+ svg.i-icon--sm { width: var(--size-icon-sm); height: var(--size-icon-sm); flex: none; }
20
+ svg.i-icon--md { width: var(--size-icon-md); height: var(--size-icon-md); flex: none; }
21
+ svg.i-icon--lg { width: var(--size-icon-lg); height: var(--size-icon-lg); flex: none; }
22
+ svg.i-icon--xl { width: var(--size-icon-xl); height: var(--size-icon-xl); flex: none; }
23
+ svg.i-icon--2xl { width: var(--size-icon-2xl); height: var(--size-icon-2xl); flex: none; }
24
+ svg.i-icon--muted { color: var(--color-icon-muted); }
25
+ /* 面包屑(没有组件库时):父级 text.secondary、当前页 text.primary,只有真实路由是 <a>。
26
+ 分隔符是细箭头:标记里仍写 <span class="sep">/</span>,span 本身 visibility: hidden(「/」从视觉与无障碍树里消失,文字扫描也不再把它当小字),箭头在 ::before 上设回 visible、用遮罩画在 text.muted 上,
27
+ icon.size.sm 见方,描边 1.4 与 icon.stroke.width 同值(遮罩里写不了 var());Element 的 .el-breadcrumb__separator 在桥接里同一画法 */
28
+ .crumbs { display: flex; align-items: center; gap: var(--spacing-2); font-size: var(--text-body-sm-size); color: var(--color-text-secondary); }
29
+ .crumbs .sep { display: inline-flex; flex: none; width: var(--size-icon-sm); height: var(--size-icon-sm); font-size: 0; color: var(--color-text-muted); visibility: hidden; }
30
+ .crumbs .sep::before { content: ""; visibility: visible; display: block; width: 100%; height: 100%; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M4.5 2.5 8 6l-3.5 3.5' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M4.5 2.5 8 6l-3.5 3.5' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; }
31
+ .crumbs span:last-child { color: var(--color-text-primary); }
32
+ .crumbs a:hover { color: var(--color-text-link-hover); }
33
+ .secondary { color: var(--color-text-secondary); }
34
+
35
+ /* ── 链接四层(DESIGN 视觉语言·链接) ── */
36
+ /* 内容型:订单号、名称——下划线 + 斜体;等宽数字型不斜体 */
37
+ /* 配方类一律单类选择器:不加 td / .topbar 等后代前缀抬特异性(2.11.7 汉堡按钮事故的根因),后面的状态 / 三端规则靠书写顺序覆盖 */
38
+ .link { color: var(--color-text-link); text-decoration: var(--text-link-decoration); font-style: var(--text-link-style); cursor: pointer; }
39
+ .link:hover { color: var(--color-text-link-hover); }
40
+ .link.danger { color: var(--color-text-danger); }
41
+ .link.danger:hover { color: var(--color-text-danger); }
42
+ .link.mono, .mono.link { font-style: normal; }
43
+ /* 操作型:动词(编辑 / 详情 / 改派 / 催单…)——同色胶囊描边,行内胶囊档 control.height.xs、text.body-sm、400;<a> 与 <button> 同形 */
44
+ .act { color: var(--color-text-link); font-family: inherit; font-size: var(--text-body-sm-size); font-weight: var(--font-weight-regular); text-decoration: none; font-style: normal; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-1); height: var(--control-height-xs); min-height: 0; padding: 0 var(--spacing-2); border: var(--border-width-default) solid currentColor; border-radius: var(--radius-full); line-height: 1; white-space: nowrap; }
45
+ .act:hover { color: var(--color-text-link-hover); background: var(--color-bg-hover); }
46
+ .act.danger, .act.danger:hover { color: var(--color-text-danger); }
47
+ .act.danger:hover { background: var(--color-status-error-bg); }
48
+ .act + .act { margin-left: var(--space-inline); }
49
+ /* 操作组:一组 .act 由容器 gap 留间距——不可用的动作要外包一层带 title 的 span 说明原因,相邻选择器 .act + .act 就断了 */
50
+ .act-group { display: flex; align-items: center; gap: var(--space-inline); }
51
+ .act-group .act + .act { margin-left: 0; }
52
+ button.act { background: transparent; margin: 0; } /* 字体三项由 .act 声明;不要写 font: inherit,简写会把字重行高一并重置 */
53
+ .act:disabled, .act.is-disabled { opacity: var(--opacity-disabled); cursor: not-allowed; } /* 不可用的动作保留位置、压淡——列宽不跳;button 靠原生 disabled 挡点击,title 仍能悬停出原因 */
54
+ .act.is-disabled { pointer-events: none; } /* <a> 没有原生禁用,只能切断事件(要给原因时外包一层带 title 的 span) */
55
+ /* 标题型:列表项标题本身可点(待办、公告、供应商名)——近黑 + 500,hover 出下划线;<a> / <button> 同形 */
56
+ .title-link { color: var(--color-text-primary); font-weight: var(--text-weight-label); text-decoration: none; cursor: pointer; font-family: inherit; font-size: inherit; }
57
+ .title-link:hover { color: var(--color-text-link-hover); text-decoration: underline; }
58
+ button.title-link, button.link { background: transparent; border: 0; padding: 0; margin: 0; text-align: left; } /* 用 button 承载时去掉默认外观;不用 font 简写 */
59
+ /* 导览型:查看全部 / 查看列表——文字 + 右箭头图标,不描边不斜体 */
60
+ .go { display: inline-flex; align-items: center; gap: var(--spacing-1); color: var(--color-text-link); font-weight: var(--text-weight-label); text-decoration: none; font-style: normal; cursor: pointer; min-height: var(--control-hit-min); }
61
+ .go:hover { color: var(--color-text-link-hover); }
62
+ .go:hover .i-icon { transform: translateX(var(--spacing-0-5)); }
63
+ .go .i-icon { transition: transform var(--motion-duration-fast) var(--motion-easing-standard); }
64
+ /* 表格、卡片页脚、批量条、空态里的链接不是句中链接:命中区不低于 control.hit-min,视觉字号不变 */
65
+ td .link, .card-head .link, .batch .link, .empty .link, .more-link { display: inline-flex; align-items: center; min-height: var(--control-hit-min); }
66
+ .more-link { min-width: var(--control-hit-min); justify-content: center; }
67
+
68
+ /* ── 壳层:侧栏 + 顶栏 + 内容区(DESIGN 页面骨架·页面框架) ── */
69
+ .app { display: grid; grid-template-columns: var(--layout-sidebar-width) 1fr; min-height: 100vh; transition: grid-template-columns var(--motion-duration-normal) var(--motion-easing-standard); }
70
+ .app.is-collapsed { grid-template-columns: var(--layout-sidebar-collapsed-width) 1fr; }
71
+ .sidebar { background: var(--color-bg-sidebar); border-right: var(--border-width-default) solid var(--color-border-sidebar); position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
72
+ .app-brand { display: flex; align-items: center; gap: var(--spacing-2-5); height: var(--layout-topbar-height); padding: 0 var(--spacing-4); font-weight: var(--text-weight-brand); font-size: var(--text-title-size); color: var(--color-text-sidebar-strong); white-space: nowrap; }
73
+ .app-brand .logo { width: var(--avatar-size-sm); height: var(--avatar-size-sm); border-radius: var(--radius-md); background: var(--color-action-primary); color: var(--color-text-on-primary); display: grid; place-items: center; font-weight: var(--text-weight-logo); font-size: var(--text-title-sm-size); flex: none; overflow: hidden; }
74
+ .app-brand .logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
75
+ .is-collapsed .app-brand { padding: 0; justify-content: center; }
76
+ .is-collapsed .app-brand > :not(.logo) { display: none; } /* 纯 CSS 壳层(无框架 v-if)折叠时隐藏品牌文字 */
77
+ .sidebar .el-menu { border-right: 0; flex: 1; padding-top: var(--spacing-2); overflow: hidden auto; } /* 侧栏是 100vh 且裁切溢出:窗口不够高时菜单区自己滚动,否则底部的项被裁掉、点不到 */
78
+ /* 不用 Element 菜单时的侧栏导航(React / 静态页):分组标题 + 链接,当前项亮色主色底块 + 白字白图标、暗色金色暗底 + 亮金字,不加指示条,与 el-menu 桥接同一形态 */
79
+ .nav { flex: 1; padding: var(--spacing-2); display: flex; flex-direction: column; overflow: hidden auto; }
80
+ .nav :is(a, button) + :is(a, button) { margin-top: var(--layout-sidebar-item-gap); } /* 间距只给同组相邻项,分组标题靠自己的上下留白,与 el-menu 一致 */
81
+ .nav-group { font-size: var(--text-caption-size); color: var(--color-text-sidebar-muted); padding: var(--spacing-3-5) var(--spacing-3) var(--spacing-1-5); white-space: nowrap; }
82
+ .nav a, .nav button { display: flex; align-items: center; gap: var(--spacing-2-5); min-height: var(--layout-sidebar-item-height); padding: 0 var(--spacing-3); border-radius: var(--radius-md); color: var(--color-text-sidebar); position: relative; text-decoration: none; background: transparent; border: 0; font: inherit; font-weight: var(--text-weight-nav); width: 100%; text-align: left; cursor: pointer; white-space: nowrap; }
83
+ .nav a:hover, .nav button:hover { background: var(--color-bg-sidebar-hover); }
84
+ .nav a.active, .nav button.active { background: var(--color-bg-sidebar-selected); color: var(--color-text-sidebar-selected); font-weight: var(--text-weight-nav); } /* 图标随文字色;与其他项同字重、不加指示条 */
85
+ .is-collapsed .nav-group { display: none; }
86
+ .is-collapsed .nav .nav-group:not(:first-child) + :is(a, button) { margin-top: var(--layout-sidebar-item-gap); } /* 标题隐藏后相邻两组的项会贴在一起:组与组之间也留同样的项间距 */
87
+ .is-collapsed .nav a, .is-collapsed .nav button { justify-content: center; padding: 0; }
88
+ .is-collapsed .nav .nav-label { display: none; }
89
+ .sidebar-foot { padding: var(--spacing-3) var(--spacing-4); border-top: var(--border-width-default) solid var(--color-border-sidebar); font-size: var(--text-small-size); color: var(--color-text-sidebar-muted); white-space: nowrap; overflow: hidden; }
90
+ .main { min-width: 0; display: flex; flex-direction: column; }
91
+ .topbar { height: var(--layout-topbar-height); display: flex; align-items: center; gap: var(--spacing-3); padding: 0 var(--space-gutter); background: var(--color-bg-surface); border-bottom: var(--border-width-default) solid var(--color-border-default); position: sticky; top: 0; z-index: var(--layer-sticky); }
92
+ .topbar .search { width: var(--layout-search-width); margin-left: auto; font-size: var(--text-body-sm-size); --el-input-border-radius: var(--radius-full); }
93
+ .topbar .search input { border-radius: var(--radius-full); } /* 顶栏搜索是胶囊;表单里的输入框保持控件圆角 */
94
+ /* 纯 CSS / shadcn 项目的顶栏搜索:<label class="search"> + 图标 + <input>,填充式胶囊(Element 用 el-input.search,走上面两条) */
95
+ .topbar label.search { display: flex; align-items: center; gap: var(--spacing-2); height: var(--control-height-md); padding: 0 var(--spacing-3); background: var(--color-bg-input); border: var(--border-width-default) solid var(--color-border-input); border-radius: var(--radius-full); color: var(--color-icon-muted); }
96
+ .topbar label.search:hover { border-color: var(--color-border-strong); }
97
+ .topbar label.search:focus-within { border-color: var(--color-border-focus); }
98
+ .topbar label.search input { flex: 1; min-width: 0; height: 100%; border: 0; border-radius: 0; background: transparent; outline: none; color: var(--color-text-primary); font-size: var(--text-body-sm-size); }
99
+ .topbar .spacer { margin-left: auto; }
100
+ .iconbtn { position: relative; width: var(--control-height-md); height: var(--control-height-md); border-radius: var(--radius-md); display: grid; place-items: center; color: var(--color-icon-default); cursor: pointer; background: transparent; border: 0; padding: 0; }
101
+ .iconbtn:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
102
+ .iconbtn.is-on { background: var(--color-bg-selected); color: var(--color-text-selected); }
103
+ /* 未读计数:贴图标右上角;底色 action.danger(亮暗都是深红,白字够对比),不用 status.error */
104
+ .iconbtn .unread { position: absolute; top: 0; right: 0; transform: translate(25%, -15%); min-width: var(--size-icon-md); height: var(--size-icon-md); padding: 0 var(--spacing-1); border-radius: var(--radius-full); background: var(--color-action-danger); color: var(--color-text-on-danger); font-size: var(--text-caption-size); line-height: var(--size-icon-md); font-weight: var(--text-weight-strong); text-align: center; pointer-events: none; }
105
+ /* 头像:不透明的奶金浅底 bg.selected-subtle + 深金字 text.selected(亮 4.8:1、暗 6.6:1),放在页面底、卡片、悬停底上都过 AA */
106
+ .avatar { width: var(--avatar-size-md); height: var(--avatar-size-md); border-radius: var(--radius-full); background: var(--color-bg-selected-subtle); color: var(--color-text-selected); display: grid; place-items: center; font-weight: var(--text-weight-brand); font-size: var(--text-body-sm-size); cursor: pointer; flex: none; }
107
+ .topbar .user { display: inline-flex; align-items: center; gap: var(--spacing-2); cursor: pointer; padding: var(--spacing-1); border-radius: var(--radius-md); }
108
+ .topbar .user:hover { background: var(--color-bg-hover); }
109
+ button.user { background: var(--color-action-quiet); border: 0; font: inherit; color: inherit; } /* 头像区用 <button> 承载时去掉默认外观 */
110
+ .topbar .user-name { font-size: var(--text-body-sm-size); color: var(--color-text-primary); white-space: nowrap; }
111
+ .user-caret { color: var(--color-icon-muted); }
112
+ /* 顶栏左侧统一是面包屑(.crumbs / el-breadcrumb),工作台也不例外;问候语放进工作台的页头副标题(DESIGN「页面骨架」) */
113
+ .content { padding: var(--space-gutter); display: flex; flex-direction: column; gap: var(--space-stack); container-type: inline-size; } /* 看板 / 两栏按内容区宽度(容器查询)切单栏,侧栏宽度变了也成立 */
114
+
115
+ /* ── 页头(bridge/vue/PageHeader.vue 与 bridge/react/PageHeader.tsx 输出这组类):每个一级页面一个 H1 text.heading × strong;副标题 text.body × text.secondary,与标题相距 spacing.1-5;
116
+ 右侧是页面级操作(主操作金底),与标题顶部对齐——表格、图表自己的筛选留在各自的卡片里 ── */
117
+ .page-head { display: flex; align-items: flex-start; gap: var(--space-stack); }
118
+ .page-head h1 { margin: 0; font-size: var(--text-heading-size); line-height: var(--text-heading-line-height); font-weight: var(--text-weight-strong); }
119
+ .page-head p { margin: var(--spacing-1-5) 0 0; color: var(--color-text-secondary); }
120
+ .page-head .actions { margin-left: auto; display: flex; gap: var(--space-inline); align-items: center; flex: none; }
121
+ /* 详情页标题行:标题 + 状态胶囊同一行 */
122
+ .page-head .title-row { display: flex; align-items: center; gap: var(--spacing-3); flex-wrap: wrap; }
123
+ /* 板块标题(bridge/vue/SectionHeader.vue 与 bridge/react/SectionHeader.tsx):看板页在 H1 下分板块——二级标题 text.title-sm × strong × text.primary,
124
+ 右侧放只作用于这一板块的筛选与操作(周期切换、导出日报),与标题垂直居中;页面级操作不放这里 */
125
+ .section-head { display: flex; align-items: center; gap: var(--space-stack); }
126
+ .section-head h2 { margin: 0; font-size: var(--text-title-sm-size); font-weight: var(--text-weight-strong); color: var(--color-text-primary); }
127
+ .section-head .right { margin-left: auto; display: flex; align-items: center; gap: var(--space-inline); }
128
+ /* 次要分组的小标签(小号、半粗、次要色、大写字距):表单分组、侧栏区块这类不值一个标题的地方;板块标题用上面的 .section-head */
129
+ .section-label { font-size: var(--text-small-size); font-weight: var(--text-weight-strong); color: var(--color-text-secondary); letter-spacing: var(--text-tracking-caps); text-transform: uppercase; }
130
+
131
+ /* ── 内容区网格:看板(主栏 : 侧栏 = 1.8 : 1)、两栏详情(1.6 : 1)、纵向堆叠;内容区窄于 880px 收成单栏(见文件末的容器查询) ── */
132
+ .board { display: grid; grid-template-columns: minmax(0, 1.8fr) minmax(0, 1fr); gap: var(--space-stack); align-items: stretch; }
133
+ .two-col { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: var(--space-stack); align-items: start; }
134
+ .stack { display: flex; flex-direction: column; gap: var(--space-stack); min-width: 0; }
135
+
136
+ /* ── 卡片区块 ── */
137
+ .card-head { display: flex; align-items: center; gap: var(--spacing-3); padding: var(--spacing-4) var(--space-card); border-bottom: var(--border-width-default) solid var(--color-border-default); }
138
+ .card-head h2 { margin: 0; font-size: var(--text-body-size); font-weight: var(--text-weight-strong); color: var(--color-text-card-title); } /* 不设行高:沿用 h2 的标题比例(14px 时约 18.7px),与同行 12px 说明的行高相当,带不带说明的卡片头一样高 */
139
+ .card-head .right { margin-left: auto; display: flex; gap: var(--space-inline); align-items: center; }
140
+ .card-body { padding: var(--space-card); } /* flat 卡里直接放图表 / 自由内容时的留白(表格、筛选栏等配方自带内边距,不需要它) */
141
+ .el-card.flat { --el-card-padding: 0; }
142
+ /* 看板卡:卡片标题下不画分隔线、说明与标题同行;正文纵向排布、块间距 spacing.4;卡里的表格首尾列与标题对齐。
143
+ 用 el-card 承载(class="flat panel")时卡片外观来自桥接,这里的外观声明值相同 */
144
+ .panel { background: var(--color-bg-surface); border: var(--border-width-default) solid var(--color-border-default); border-radius: var(--radius-lg); box-shadow: 0 var(--elevation-card-y) var(--elevation-card-blur) var(--elevation-card-color); min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
145
+ .panel .card-head { border-bottom: 0; padding-bottom: var(--spacing-1); }
146
+ .panel .card-head .sub { display: inline; margin-left: var(--spacing-2); }
147
+ .panel .card-body { flex: 1; display: flex; flex-direction: column; gap: var(--spacing-4); }
148
+ .panel .table-wrap { margin-top: var(--spacing-2); }
149
+ .panel .table th:first-child, .panel .table td:first-child { padding-left: var(--space-card); }
150
+ .panel .table th:last-child, .panel .table td:last-child { padding-right: var(--space-card); }
151
+ /* 卡片栅格(商品 / 活动 / 优惠券这类卡片视图):列宽下限 layout.search-width、自动填充,放在 flat 卡里四周留 space.card */
152
+ .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--layout-search-width), 1fr)); gap: var(--space-stack); padding: var(--space-card); }
153
+
154
+ /* ── 状态胶囊:文字 + 同色浅底,400 字重;.status 与 .badge 同物,两个名字只为语义 ── */
155
+ .status, .badge { display: inline-flex; align-items: center; gap: var(--spacing-1-5); padding: var(--spacing-0-5) var(--spacing-2); border-radius: var(--radius-full); font-size: var(--text-small-size); font-weight: var(--font-weight-regular); line-height: var(--text-body-line-height); white-space: nowrap; }
156
+ .status.success, .badge.success { color: var(--color-status-success); background: var(--color-status-success-bg); }
157
+ .status.warning, .badge.warning { color: var(--color-status-warning); background: var(--color-status-warning-bg); }
158
+ .status.error, .badge.error { color: var(--color-status-error); background: var(--color-status-error-bg); }
159
+ .status.info, .badge.info { color: var(--color-status-info); background: var(--color-status-info-bg); }
160
+ .status.neutral, .badge.neutral { color: var(--color-status-neutral); background: var(--color-status-neutral-bg); }
161
+ .badge.brand { color: var(--color-text-selected); background: var(--color-bg-selected-subtle); } /* 强调徽标(置顶、本机、管理员):奶金浅底 + 深金字,与表格选中行同一套;action.selected 在本系统是白块,不能当底色 */
162
+ /* 标签(类别 / 属性,中性):radius.sm 的小方标签,不做胶囊(DESIGN「圆角」「徽标 / 提示条」);表达状态用上面的 .status */
163
+ .tag { display: inline-flex; align-items: center; height: var(--control-height-xs); padding: 0 var(--spacing-2); border-radius: var(--radius-sm); background: var(--color-status-neutral-bg); color: var(--color-status-neutral); font-size: var(--text-small-size); white-space: nowrap; }
164
+ .sub { display: block; font-size: var(--text-small-size); color: var(--color-text-muted); }
165
+ .up { color: var(--color-data-increase); } .down { color: var(--color-data-decrease); }
166
+
167
+ /* ── 列表页:筛选栏 / 条件标签 / 批量条 / 分页 ── */
168
+ .filter { display: flex; flex-wrap: wrap; gap: var(--spacing-3); padding: var(--spacing-4) var(--space-card); border-bottom: var(--border-width-default) solid var(--color-border-default); align-items: center; }
169
+ .filter .conds { display: flex; flex-wrap: wrap; gap: var(--spacing-3); align-items: center; flex: 1 1 auto; min-width: 0; }
170
+ .filter .acts { display: flex; gap: var(--spacing-3); align-items: center; margin-left: auto; flex: none; }
171
+ .filter.solo { border-bottom: 0; } /* 筛选栏单独成一张卡(报表页)时不要再画底线,否则与卡片边线叠成双线 */
172
+ /* 控件宽度档(筛选栏、设置行):搜索框 layout.search-width,选择器 0.6 倍,日期范围 1.15 倍。Element 的选择器默认撑满父容器,要给档位 */
173
+ .w-search { width: var(--layout-search-width); }
174
+ .w-select { width: calc(var(--layout-search-width) * 0.6); }
175
+ .w-date { width: calc(var(--layout-search-width) * 1.15); }
176
+ .nowrap { white-space: nowrap; }
177
+ .chips { display: flex; gap: var(--spacing-2); flex-wrap: wrap; padding: var(--spacing-3) var(--space-card) 0; }
178
+ .chips:last-child, .chips + .el-table, .chips + .tskel { padding-bottom: var(--spacing-3); } /* 后面没有批量条时也与表头留出间距 */
179
+ .chip { display: inline-flex; align-items: center; gap: var(--spacing-1-5); padding: 0 var(--spacing-2-5); height: var(--control-height-sm); border-radius: var(--radius-full); background: var(--color-bg-chip); color: var(--color-text-selected); font-size: var(--text-small-size); } /* 筹码:字色同色 10% 浅底(color.bg.chip) */
180
+ .chip .x { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; opacity: var(--opacity-on-primary-muted); }
181
+ /* × 用 <button class="x" aria-label="移除条件 …">:命中区拉到 control.hit-min,负外边距抵消,胶囊视觉宽度不变 */
182
+ .chip button.x { background: transparent; border: 0; padding: 0; color: inherit; min-width: var(--control-hit-min); height: var(--control-height-sm); margin: 0 calc(var(--spacing-2-5) * -1) 0 calc(var(--spacing-1) * -1); border-radius: var(--radius-full); }
183
+ .chip button.x:hover { opacity: 1; }
184
+ .batch { display: flex; align-items: center; gap: var(--spacing-3); padding: var(--spacing-2-5) var(--space-card); margin-top: var(--spacing-3); background: var(--color-bg-selected); border-top: var(--border-width-default) solid var(--color-border-default); border-bottom: var(--border-width-default) solid var(--color-border-default); color: var(--color-text-primary); font-size: var(--text-body-sm-size); }
185
+ .batch .right { margin-left: auto; display: flex; gap: var(--space-inline); }
186
+ .pager { display: flex; justify-content: flex-end; padding: var(--spacing-3) var(--space-card); }
187
+ /* 分页(纯 CSS / shadcn 项目;Element 用 el-pagination,桥接已对齐同一套):页码透明、悬停浅底,当前页金底白字 */
188
+ .pagination { display: flex; align-items: center; gap: var(--spacing-1); flex-wrap: wrap; }
189
+ .pagination .total { margin-right: var(--spacing-3); color: var(--color-text-secondary); font-size: var(--text-body-sm-size); }
190
+ .page { display: inline-grid; place-items: center; min-width: var(--control-height-md); height: var(--control-height-md); padding: 0 var(--spacing-2); border: var(--border-width-default) solid var(--color-action-quiet); border-radius: var(--radius-md); background: var(--color-action-quiet); color: var(--color-text-primary); font-size: var(--text-body-sm-size); font-variant-numeric: var(--text-numeric-variant); cursor: pointer; }
191
+ .page:hover:not(:disabled):not([aria-current="page"]):not(.ellipsis) { background: var(--color-bg-hover); }
192
+ .page[aria-current="page"] { background: var(--color-action-primary); border-color: var(--color-action-primary); color: var(--color-text-on-primary); font-weight: var(--text-weight-strong); }
193
+ .page:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
194
+ .page.ellipsis { color: var(--color-text-muted); cursor: default; }
195
+ .page svg { width: var(--size-icon-md); height: var(--size-icon-md); }
196
+
197
+ /* ── 表单页 ── */
198
+ .form-page { max-width: var(--layout-form-max-width); }
199
+ .form-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 var(--spacing-6); padding: var(--space-card) var(--space-card) 0; }
200
+ .form-grid .span2 { grid-column: 1 / -1; }
201
+ .form-foot { display: flex; justify-content: flex-end; gap: var(--space-inline); padding: var(--spacing-3-5) var(--space-card); border-top: var(--border-width-default) solid var(--color-border-default); }
202
+ /* 长表单的操作条贴底跟随:浮在内容上时要有边界(发丝边 + 向上的卡片阴影) */
203
+ .form-foot.is-sticky { position: sticky; bottom: 0; z-index: var(--layer-sticky); background: var(--color-bg-surface); border: var(--border-width-default) solid var(--color-border-default); border-radius: var(--radius-lg); box-shadow: 0 calc(var(--elevation-card-y) * -1) var(--elevation-card-blur) var(--elevation-card-color); }
204
+ .el-form--label-right .el-form-item__label { width: var(--layout-form-label-width); }
205
+ .help { display: block; margin-top: var(--spacing-1); color: var(--color-text-muted); font-size: var(--text-small-size); line-height: var(--text-body-line-height); }
206
+ .el-form-item.is-error .help { display: none; } /* 出错时错误文案占同一位置,不与帮助文字叠加(DESIGN 表单页) */
207
+ /* 只读字段:不可聚焦的文本块,不是 readonly 输入框(后者会进 Tab 序列却不可改) */
208
+ .ro { display: flex; align-items: center; min-height: var(--control-height-md); padding: 0 var(--spacing-3); border-radius: var(--radius-md); background: var(--color-bg-readonly); color: var(--color-text-secondary); }
209
+ /* 抽屉 / 弹窗内的分组小标题 */
210
+ .drawer-h { margin: var(--spacing-5) 0 var(--spacing-3); font-size: var(--text-title-sm-size); font-weight: var(--text-weight-label); color: var(--color-text-primary); }
211
+ .drawer-h:first-child { margin-top: 0; }
212
+ /* 表单内的可编辑表格(采购明细这类行内录入):单元格内边距收到 spacing.3 / spacing.2,控件 small,行内错误静态渲染在控件下,删除胶囊与 small 控件对齐 */
213
+ .table-editable .el-table__cell { padding: var(--spacing-2) var(--spacing-3); vertical-align: top; }
214
+ .table-editable .el-form-item { margin-bottom: 0; }
215
+ .table-editable .el-form-item__error { position: static; padding-top: var(--spacing-0-5); }
216
+ .table-editable th.el-table__cell .req::before { content: "*"; color: var(--color-status-error); margin-right: var(--spacing-1); }
217
+ /* 上传区:虚线框 + 浅底,hover 换底,拖入时边线换焦点色,键盘焦点画在整块上 */
218
+ .upload-zone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--spacing-1); min-height: calc(var(--control-height-md) * 3); padding: var(--spacing-4); border: var(--border-width-default) dashed var(--color-border-strong); border-radius: var(--radius-md); background: var(--color-bg-subtle); color: var(--color-text-secondary); font-size: var(--text-body-size); cursor: pointer; text-align: center; }
219
+ .upload-zone:hover { background: var(--color-bg-hover); }
220
+ .upload-zone.is-over { border-color: var(--color-border-focus); background: var(--color-bg-selected); }
221
+ .upload-zone:has(:focus-visible) { outline: var(--border-width-active) solid var(--color-border-focus); outline-offset: var(--spacing-0-5); }
222
+ .upload-zone.is-full { opacity: var(--opacity-disabled); cursor: not-allowed; }
223
+ .upload-zone input[type="file"] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; } /* 透明铺满:点 label 触发选择,Tab 仍可到达 */
224
+ .upload-zone { position: relative; }
225
+ .upload-zone b { color: var(--color-text-primary); font-weight: var(--text-weight-label); }
226
+ /* 附件行:类型标签 / 图标 + 文件名与说明 + 操作,行间发丝线;紧跟在上传区后面时首行不画线 */
227
+ .file-row { display: flex; align-items: center; gap: var(--spacing-3); padding: var(--spacing-3) 0; border-top: var(--border-width-default) solid var(--color-border-default); }
228
+ .file-row:first-child { border-top: 0; padding-top: 0; }
229
+ .file-row .text { flex: 1; min-width: 0; }
230
+ .upload-zone + .file-row { margin-top: var(--spacing-3); border-top: 0; }
231
+
232
+ /* ── 设置页:横向表单 + 设置行 ── */
233
+ /* 横向表单:标签列 layout.form.label-width、标签右对齐;Element 用 el-form label-position="right",下面的 .field 规则给纯 CSS 表单 */
234
+ .hform { max-width: calc(var(--layout-form-label-width) + var(--layout-form-max-width)); padding: var(--space-card); }
235
+ .hform .field { display: grid; grid-template-columns: var(--layout-form-label-width) minmax(0, 1fr); column-gap: var(--spacing-4); row-gap: var(--spacing-1-5); align-items: start; }
236
+ .hform .field > label, .hform .field > .label { display: flex; align-items: center; justify-content: flex-end; min-height: var(--control-height-md); text-align: right; }
237
+ .hform .field > :not(label):not(.label) { grid-column: 2; }
238
+ .hform .foot { display: flex; gap: var(--space-inline); padding-left: calc(var(--layout-form-label-width) + var(--spacing-4)); }
239
+ .avatar-row { display: flex; align-items: center; gap: var(--spacing-3); min-height: var(--control-height-md); }
240
+ /* 设置行:标题 + 说明(.sub)在左,开关 / 选择 / 操作在右;行间发丝线 */
241
+ .setting { display: flex; align-items: center; gap: var(--spacing-4); padding: var(--spacing-4) var(--space-card); border-top: var(--border-width-default) solid var(--color-border-default); }
242
+ .setting:first-child { border-top: 0; }
243
+ .setting .text { flex: 1; min-width: 0; }
244
+ .setting .text b { display: block; font-weight: var(--text-weight-label); }
245
+
246
+ /* ── 浮层内原位确认条(bridge/vue/ConfirmBar.vue):替换抽屉 / 弹窗页脚,不叠第二层弹窗;底色中性、只有确认按钮带颜色(彩色文字不落灰底) ── */
247
+ .confirm-bar { display: flex; align-items: center; gap: var(--space-inline); padding: var(--spacing-3) var(--space-card); background: var(--color-bg-subtle); border-top: var(--border-width-default) solid var(--color-border-default); }
248
+ .confirm-bar .msg { flex: 1; min-width: 0; color: var(--color-text-primary); font-size: var(--text-body-sm-size); line-height: var(--text-body-line-height); }
249
+ /* 没有组件库按钮时(React 版未传 renderButton)的兜底按钮:次要 = 描边、主 = 金底白字、危险 = 红底白字 */
250
+ .confirm-bar__btn { height: var(--control-height-md); padding: 0 var(--spacing-4); border-radius: var(--radius-md); font: inherit; font-weight: var(--text-weight-label); cursor: pointer; border: var(--border-width-control) solid var(--color-border-strong); background: var(--color-bg-surface); color: var(--color-text-primary); }
251
+ .confirm-bar__btn.is-primary { background: var(--color-action-primary); border-color: var(--color-action-primary); color: var(--color-text-on-primary); }
252
+ .confirm-bar__btn.is-danger { background: var(--color-action-danger); border-color: var(--color-action-danger); color: var(--color-text-on-danger); }
253
+ .el-drawer__footer .confirm-bar { margin: calc(var(--spacing-3) * -1) calc(var(--space-card) * -1); border-top: 0; } /* 撑满 Element 抽屉页脚(spacing.3 × space.card)的内边距,页脚自带的上边线保留 */
254
+ .el-dialog__footer .confirm-bar { margin: calc(var(--space-card) * -1); margin-top: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); } /* 弹窗页脚是 space.card 内边距、无边线:成为带圆角的完整底栏 */
255
+
256
+ /* ── 结果页(403 / 404 / 500):内容区垂直居中,插图 two-tone ── */
257
+ .result { text-align: center; padding: var(--spacing-10) var(--spacing-5); max-width: var(--layout-form-max-width); margin: auto; min-height: calc(100vh - var(--layout-topbar-height) - var(--space-gutter) * 2); display: flex; flex-direction: column; justify-content: center; align-items: center; }
258
+ .result .illu { width: var(--illustration-size-md); height: calc(var(--illustration-size-md) * 0.6); border-radius: var(--radius-lg); background: var(--color-bg-subtle); border: var(--border-width-default) dashed var(--color-border-strong); display: grid; place-items: center; margin-bottom: var(--spacing-5); }
259
+ .result .code { font-size: var(--text-hero-size); line-height: var(--text-hero-line-height); font-weight: var(--text-weight-brand); color: var(--color-text-selected); } /* 主色不当文字色(DESIGN Don't):深一档的 text.selected,页面底上 4.8:1 */
260
+ .result h3 { margin: var(--spacing-2) 0 var(--spacing-1); font-size: var(--text-title-size); font-weight: var(--text-weight-strong); }
261
+ .result p { margin: 0 0 var(--spacing-5); color: var(--color-text-secondary); line-height: var(--text-paragraph-line-height); }
262
+ .result .acts { display: flex; justify-content: center; gap: var(--space-inline); }
263
+
264
+ /* ── 空态 / 加载失败 ── */
265
+ .empty { display: flex; flex-direction: column; align-items: center; gap: var(--spacing-2); padding: var(--spacing-10) var(--space-card); color: var(--color-text-muted); text-align: center; }
266
+ .empty .illu { width: var(--illustration-size-md); height: calc(var(--illustration-size-md) * 0.6); border-radius: var(--radius-md); background: var(--color-bg-subtle); border: var(--border-width-default) dashed var(--color-border-strong); display: grid; place-items: center; margin-bottom: var(--spacing-2); }
267
+ .empty b { color: var(--color-text-secondary); font-weight: var(--text-weight-label); }
268
+ .error-box { display: flex; align-items: center; gap: var(--spacing-3); margin: var(--space-card); padding: var(--spacing-3) var(--spacing-4); border-radius: var(--radius-lg); background: var(--color-status-error-bg); color: var(--color-status-error); font-size: var(--text-body-sm-size); }
269
+ .error-box .el-button { margin-left: auto; }
270
+
271
+ /* ── 登录页:左侧品牌面 bg.brand + text.on-brand(正文用 ≥ 24px 的大字),右侧表单卡 ── */
272
+ .login { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); min-height: 100vh; background: var(--color-bg-page); }
273
+ .login-brand { display: flex; flex-direction: column; justify-content: space-between; gap: var(--space-stack); padding: var(--space-gutter); background: var(--color-bg-brand); color: var(--color-text-on-brand); }
274
+ .login-brand .app-brand { padding: 0; color: inherit; }
275
+ .login-brand .app-brand .logo { background: var(--color-bg-surface); color: var(--color-text-selected); } /* 金底上的 logo 块:表面色底 + 深一档的 text.selected(主色不当文字色),亮暗都过 AA */
276
+ .login-brand .slogan { font-size: var(--text-hero-size); line-height: var(--text-hero-line-height); font-weight: var(--text-weight-strong); }
277
+ .login-brand .lead { margin: var(--spacing-3) 0 0; font-size: var(--text-heading-size); line-height: var(--text-heading-line-height); }
278
+ .login-panel { display: grid; place-items: center; padding: var(--space-gutter); }
279
+ .login-card { width: min(100%, var(--layout-modal-width-sm)); }
280
+ .login-card h1 { margin: 0; font-size: var(--text-heading-size); line-height: var(--text-heading-line-height); }
281
+ .login-card .secondary { margin: 0; }
282
+ .login-card .tabs, .login-card .el-tabs { margin: var(--spacing-5) 0 var(--spacing-6); }
283
+ .login-card .el-tabs > .el-tabs__header { margin-bottom: 0; }
284
+ .login-card .alert, .login-card .el-alert { margin-top: var(--spacing-5); }
285
+ .login-row { display: flex; align-items: center; justify-content: space-between; margin: calc(var(--spacing-2) * -1) 0 var(--spacing-5); }
286
+ .divider-text { display: flex; align-items: center; gap: var(--spacing-3); margin: var(--spacing-6) 0 var(--spacing-4); color: var(--color-text-muted); font-size: var(--text-small-size); }
287
+ .divider-text::before, .divider-text::after { content: ""; flex: 1; height: var(--border-width-default); background: var(--color-border-default); }
288
+ .login-foot { margin: var(--spacing-6) 0 0; color: var(--color-text-muted); font-size: var(--text-small-size); text-align: center; }
289
+ .login-alt { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-inline); }
290
+ .code-row { display: flex; gap: var(--space-inline); width: 100%; }
291
+ .code-row .input, .code-row .el-input { flex: 1; }
292
+
293
+ /* ── 优惠券券面:左侧面额块是品牌面 bg.brand + text.on-brand(面额用展示字;门槛小字属「金底白字」例外),右侧放名称、状态与操作;已结束 / 过期 / 草稿的券加 .is-off,面额块变灰 ── */
294
+ .coupon { display: grid; grid-template-columns: var(--illustration-size-md) minmax(0, 1fr); border: var(--border-width-default) solid var(--color-border-default); border-radius: var(--radius-lg); overflow: hidden; }
295
+ .coupon-value { display: grid; place-content: center; gap: var(--spacing-1); text-align: center; padding: var(--spacing-3); background: var(--color-bg-brand); color: var(--color-text-on-brand); font-size: var(--text-small-size); }
296
+ .coupon-value b { font-family: var(--text-display-family); font-size: var(--text-heading-size); font-weight: var(--text-display-weight); line-height: var(--text-heading-line-height); }
297
+ .coupon.is-off .coupon-value { background: var(--color-bg-subtle); color: var(--color-text-muted); }
298
+ .coupon-body { display: grid; gap: var(--spacing-2); padding: var(--spacing-3) var(--spacing-4); min-width: 0; }
299
+ .coupon-foot { display: flex; align-items: center; justify-content: space-between; }
300
+
301
+ /* ── 图表容器:高度用 token 表达(控件高度 × 7 / × 9) ── */
302
+ .chart { height: calc(var(--control-height-md) * 7); width: 100%; }
303
+ .chart.tall { height: calc(var(--control-height-md) * 9); }
304
+ /* 分类色:.c1–.c6 只设 --c(chart.1–6),图例色块与占比列表的色块读它 */
305
+ .c1 { --c: var(--color-chart-1); } .c2 { --c: var(--color-chart-2); } .c3 { --c: var(--color-chart-3); }
306
+ .c4 { --c: var(--color-chart-4); } .c5 { --c: var(--color-chart-5); } .c6 { --c: var(--color-chart-6); }
307
+ /* 图例:spacing.2.5 见方的色块 + text.small × text.secondary */
308
+ .legend { display: flex; flex-wrap: wrap; gap: var(--spacing-2) var(--spacing-4); font-size: var(--text-small-size); color: var(--color-text-secondary); }
309
+ .legend i { display: inline-block; width: var(--spacing-2-5); height: var(--spacing-2-5); border-radius: var(--radius-xs); margin-right: var(--spacing-1-5); vertical-align: -1px; background: var(--c); }
310
+ .legend i.is-bench { background: var(--color-chart-sequential-1); box-shadow: inset 0 0 0 var(--border-width-default) var(--color-chart-4); }
311
+ /* 占比列表(环形图旁):色块 + 名称 + 占比(strong)+ 金额(muted,可省);列按子元素个数自动排 */
312
+ .alloc-list { display: grid; gap: var(--spacing-3); margin: 0; padding: 0; list-style: none; }
313
+ .alloc-list li { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-auto-flow: column; grid-auto-columns: auto; gap: var(--spacing-2-5); align-items: center; list-style: none; font-size: var(--text-body-sm-size); }
314
+ .alloc-list i { display: inline-block; width: var(--spacing-2-5); height: var(--spacing-2-5); border-radius: var(--radius-xs); background: var(--c); }
315
+ .alloc-list .pct { font-family: var(--text-numeric-family); font-weight: var(--text-weight-strong); font-variant-numeric: var(--text-numeric-variant); color: var(--color-text-primary); }
316
+ .alloc-list .amt { font-family: var(--text-numeric-family); color: var(--color-text-muted); font-variant-numeric: var(--text-numeric-variant); min-width: 6em; text-align: right; }
317
+ /* 目标进度列表:名称 + 数值一行,下面是进度条(纯 CSS 用 .track / .fill,Element 用 el-progress);填充同进度条:左深右浅,品牌金用 chart.gradient.start → end,中性序列是同色饱和度渐变 */
318
+ .goals { display: grid; gap: var(--spacing-5); padding: 0; margin: 0; }
319
+ .goals li { list-style: none; display: grid; gap: var(--spacing-2); }
320
+ .goals .top { display: flex; align-items: baseline; justify-content: space-between; font-size: var(--text-body-sm-size); }
321
+ .goals .top b { font-family: var(--text-numeric-family); font-weight: var(--text-weight-strong); color: var(--color-text-secondary); font-variant-numeric: var(--text-numeric-variant); }
322
+ .goals .track { height: var(--spacing-1-5); border-radius: var(--radius-full); background: var(--color-bg-selected); overflow: hidden; }
323
+ .goals .fill { height: 100%; border-radius: var(--radius-full); background: var(--color-chart-gradient-start); background: linear-gradient(to right, var(--color-chart-gradient-start), var(--color-chart-gradient-end)); }
324
+ .goals .fill.is-neutral { --fill: var(--color-chart-4); background: var(--fill); background: linear-gradient(to right, var(--fill), hwb(from var(--fill) h calc(w + (100 - w - b) * (1 - var(--chart-gradient-saturation))) b)); }
325
+
326
+ /* ── 纯 CSS 表格 `.table`(没有组件库的模板项目用;Element / shadcn 走各自桥接):表头 bg.subtle + text.secondary,单元格 table.cell.padding,行 hover bg.hover,发丝线 ── */
327
+ .table-wrap { overflow-x: auto; } /* 手机上表格内部横向滚动,文档级不溢出 */
328
+ .table { width: 100%; border-collapse: collapse; font-size: var(--text-body-sm-size); }
329
+ .table th, .table td { padding: var(--table-cell-padding-y) var(--table-cell-padding-x); border-bottom: var(--border-width-default) solid var(--color-border-default); text-align: left; vertical-align: middle; white-space: nowrap; }
330
+ .table th { background: var(--color-bg-subtle); color: var(--color-text-secondary); font-weight: var(--text-weight-label); font-size: var(--text-small-size); }
331
+ .table tbody tr:hover { background: var(--color-bg-hover); }
332
+ .table tbody tr.is-selected { background: var(--color-bg-hover); } /* 勾选行与悬停同色(DESIGN 表格「选中行」),再悬停深一档 */
333
+ .table tbody tr.is-selected:hover { background: var(--color-bg-selected-hover); }
334
+ .table tbody tr:last-child td { border-bottom: 0; }
335
+ .table .num-col { text-align: right; font-family: var(--text-numeric-family); }
336
+
337
+ /* ── 统计卡(bridge/vue/StatCard.vue 与 bridge/react/StatCard.tsx 共用这组类):数据大字 + 涨跌 + 口径说明;与普通卡片同一形态(细边线 + 柔阴影),不加品牌指示条 ── */
338
+ .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-stack); }
339
+ .stat-card { background: var(--color-bg-surface); border: var(--border-width-default) solid var(--color-border-default); border-radius: var(--radius-lg); box-shadow: 0 var(--elevation-card-y) var(--elevation-card-blur) var(--elevation-card-color); padding: var(--spacing-5) var(--space-card); min-width: 0; }
340
+ .stat-card__label { display: flex; align-items: center; gap: var(--spacing-1); color: var(--color-text-secondary); font-size: var(--text-body-sm-size); }
341
+ .stat-card__hint { display: inline-grid; place-items: center; width: var(--control-hit-min); height: var(--control-hit-min); margin: calc((var(--control-hit-min) - var(--size-icon-sm)) / -2) 0; padding: 0; border: 0; border-radius: var(--radius-full); background: transparent; color: var(--color-icon-muted); cursor: help; font: inherit; }
342
+ .stat-card__hint:hover { color: var(--color-text-primary); background: var(--color-bg-hover); }
343
+ .stat-card__value { font-family: var(--text-display-family); font-size: var(--text-display-size); font-weight: var(--text-display-weight); line-height: var(--text-display-line-height); margin: var(--spacing-1-5) 0 var(--spacing-1); letter-spacing: var(--text-display-tracking); color: var(--color-text-primary); }
344
+ .stat-card__delta { font-family: var(--text-numeric-family); font-size: var(--text-small-size); } /* 颜色交给 .up / .down */
345
+ .stat-card__note { color: var(--color-text-muted); font-size: var(--text-small-size); }
346
+
347
+ /* ── 表格骨架(bridge/vue/TableSkeleton.vue 与 bridge/react/TableSkeleton.tsx 共用):表头 + 等高行 + 列宽,加载完成不跳版 ── */
348
+ .tskel { overflow: hidden; }
349
+ .tskel-head, .tskel-row { display: flex; align-items: center; border-bottom: var(--border-width-default) solid var(--color-border-default); }
350
+ .tskel-head { background: var(--color-bg-subtle); height: calc(var(--table-cell-padding-y) * 2 + var(--text-body-line-height) * var(--text-small-size)); }
351
+ .tskel-row { height: calc(var(--table-cell-padding-y) * 2 + var(--text-body-line-height) * var(--text-body-size) + var(--text-body-line-height) * var(--text-small-size)); }
352
+ .tskel .cell { padding: 0 var(--table-cell-padding-x); min-width: 0; }
353
+ /* 贴边放在卡片里时(同 el-table 与 .panel .table 的首尾列),首尾列让出卡片内边距,加载完成不跳版 */
354
+ :is(.el-card.flat > .el-card__body, .el-card.flat .el-tab-pane, .panel, .panel > .table-wrap) > .tskel .cell:first-child { padding-left: var(--space-card); }
355
+ :is(.el-card.flat > .el-card__body, .el-card.flat .el-tab-pane, .panel, .panel > .table-wrap) > .tskel .cell:last-child { padding-right: var(--space-card); }
356
+ .tskel .bar { display: block; height: var(--text-small-size); border-radius: var(--radius-xs); width: 70%; background: linear-gradient(90deg, var(--color-bg-skeleton) 25%, var(--color-bg-skeleton-highlight) 50%, var(--color-bg-skeleton) 75%); background-size: 400% 100%; animation: tskel-shimmer 1.4s var(--motion-easing-standard) infinite; }
357
+ .tskel .bar.head { width: 45%; }
358
+ .tskel .bar.short { width: 40%; }
359
+ @keyframes tskel-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
360
+
361
+ /* ── 状态条 ── */
362
+ .stat-strip { display: inline-flex; flex-wrap: wrap; gap: var(--space-inline); margin-bottom: var(--space-stack); }
363
+ .content > .stat-strip { margin-bottom: 0; } /* 直接放在内容区时间距交给内容区的 gap */
364
+ .stat-strip .cell { display: inline-flex; align-items: center; gap: var(--spacing-2); height: var(--control-height-lg); padding: 0 var(--spacing-4); background: var(--color-bg-surface); border: var(--border-width-default) solid var(--color-border-default); border-radius: var(--radius-md); color: var(--color-text-secondary); font-size: var(--text-body-sm-size); }
365
+ .stat-strip .cell.is-clickable { cursor: pointer; }
366
+ .stat-strip .cell.is-clickable:hover { background: var(--color-bg-hover); }
367
+ .stat-strip .cell.is-active { border-color: var(--color-border-focus); color: var(--color-text-primary); }
368
+ .stat-strip .cell .value { font-family: var(--text-display-family); font-size: var(--text-title-size); font-weight: var(--text-weight-strong); color: var(--color-text-primary); font-variant-numeric: var(--text-numeric-variant); }
369
+
370
+ /* ── 打印:隐藏导航与按钮,色底转边线 ── */
371
+ /* ── 键盘焦点兜底与减少动态效果:与 element-plus.css / shadcn.css 同一份约定。有桥接时规则重复无害;纯 CSS 渠道(Flask 等无组件库)靠这里拿到焦点色(border.focus)2px 焦点环而不是浏览器默认蓝环。
372
+ shadcn 组件(data-slot)走桥接的 ring 模型,这里不叠。 ── */
373
+ :where(a, button, [role="button"], [role="menuitem"], [role="tab"], summary, [tabindex]:not([tabindex="-1"]):not(input, textarea, select)):not([data-slot]):focus-visible { outline: var(--border-width-active) solid var(--color-border-focus); outline-offset: 1px; }
374
+ @media (prefers-reduced-motion: reduce) {
375
+ :root { --motion-duration-fast: 0ms; --motion-duration-normal: 0ms; --motion-duration-slow: 0ms; }
376
+ *, *::before, *::after { transition-duration: 0s !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
377
+ }
378
+
379
+ /* ── 内容区窄于 880px(右栏放得下 KPI 大字约 300px,按 1.8 : 1 折算)时看板与两栏收成单栏;按内容区而不是视口判断,侧栏折叠与否都成立 ── */
380
+ @container (max-width: 880px) {
381
+ .board, .two-col { grid-template-columns: minmax(0, 1fr); }
382
+ }
383
+
384
+ /* ── 三端(值是 layout.breakpoint.narrow / mobile = 992 / 768 的字面镜像,见 DESIGN「布局与响应式」;CSS 媒体查询不能用 var(),改 token 必须同步) ── */
385
+ /* 手机(≤ 768):侧栏离屏抽屉。项目 JS 只需维护 .is-nav-open(汉堡开 / 遮罩与选中菜单关) */
386
+ .menu-btn { display: none; }
387
+ .sidebar-mask { display: none; position: fixed; inset: 0; background: var(--color-bg-overlay); z-index: calc(var(--layer-sticky) + 1); border: 0; padding: 0; }
388
+ @media (max-width: 992px) {
389
+ .stats { grid-template-columns: repeat(2, 1fr); }
390
+ }
391
+ @media (max-width: 768px) {
392
+ .app, .app.is-collapsed { display: block; }
393
+ .sidebar { position: fixed; left: 0; top: 0; bottom: 0; height: 100dvh; width: var(--layout-sidebar-width); z-index: calc(var(--layer-sticky) + 2); transform: translateX(-100%); transition: transform var(--motion-duration-normal) var(--motion-easing-standard); box-shadow: none; }
394
+ .is-nav-open .sidebar { transform: none; box-shadow: 0 var(--elevation-modal-y) var(--elevation-modal-blur) var(--elevation-modal-color); }
395
+ .is-nav-open .sidebar-mask { display: block; }
396
+ .is-collapsed .app-brand { padding: 0 var(--spacing-4); justify-content: flex-start; } /* 抽屉里永远是展开形态:折叠态样式全部还原 */
397
+ .is-collapsed .app-brand > :not(.logo) { display: inline; }
398
+ .is-collapsed .nav-group { display: block; }
399
+ .is-collapsed .nav .nav-group:not(:first-child) + :is(a, button) { margin-top: 0; }
400
+ .is-collapsed .nav a, .is-collapsed .nav button { justify-content: flex-start; padding: 0 var(--spacing-3); }
401
+ .is-collapsed .nav .nav-label { display: inline; }
402
+ .menu-btn { display: inline-grid; }
403
+ .collapse-btn { display: none; } /* 折叠切换在手机上无意义(侧栏是抽屉) */
404
+ .content { padding: var(--space-stack); gap: var(--spacing-3); }
405
+ .topbar { padding: 0 var(--space-stack); }
406
+ .topbar .search, .topbar label.search { display: none; } /* 顶栏全局搜索在手机上收起(页面内搜索仍在筛选栏);label 写法的特异性更高,要单独列出 */
407
+ .page-head { flex-wrap: wrap; }
408
+ .page-head .actions { flex: 1 1 100%; flex-wrap: wrap; margin-left: 0; justify-content: flex-start; } /* 覆盖桌面的 flex:none:动作区整行换下、内部可换行 */
409
+ .card-head { flex-wrap: wrap; }
410
+ .card-head .right, .batch .right { flex-wrap: wrap; min-width: 0; justify-content: flex-start; }
411
+ .batch { flex-wrap: wrap; }
412
+ .batch > * { white-space: nowrap; } /* 「已选 N 项」不竖排 */
413
+ .batch .right { flex: 1 1 100%; } /* 批量动作整行换下 */
414
+ .crumbs span:not(.sep):not(:last-child), .crumbs .sep { display: none; } /* 面包屑只留当前页 */
415
+ .stats { grid-template-columns: 1fr; }
416
+ .filter .conds { flex-wrap: wrap; width: 100%; }
417
+ .filter .conds > * { flex: 1 1 auto; min-width: 0; }
418
+ .filter .acts { width: 100%; justify-content: flex-end; }
419
+ .form-grid { grid-template-columns: minmax(0, 1fr); } /* minmax(0,…):行内录入表格等宽内容不得撑破网格 */
420
+ .form-grid .span2 { grid-column: auto; }
421
+ .two-col { grid-template-columns: 1fr; }
422
+ .pager { flex-wrap: wrap; justify-content: center; }
423
+ .topbar .user-name, .topbar .user-caret { display: none; } /* 顶栏只留当前页面包屑与头像 */
424
+ .section-head { flex-wrap: wrap; }
425
+ .section-head .right { flex: 1 1 100%; flex-wrap: wrap; margin-left: 0; } /* 板块的筛选整行换下 */
426
+ .setting { flex-wrap: wrap; }
427
+ .login { grid-template-columns: minmax(0, 1fr); }
428
+ .login-brand { padding: var(--spacing-6) var(--spacing-4); }
429
+ .login-brand .slogan { font-size: var(--text-heading-size); line-height: var(--text-heading-line-height); }
430
+ .login-brand .lead { font-size: var(--text-title-size); }
431
+ .login-panel { padding: var(--spacing-6) var(--spacing-4); }
432
+ }
433
+ /* 触屏:命中区升到 control.hit-touch,视觉不变(min-height 拉高由 flex 居中吸收) */
434
+ @media (pointer: coarse) {
435
+ .act, .go, .link, .more-link, .iconbtn, .title-link { min-height: var(--control-hit-touch); }
436
+ .chip button.x { min-width: var(--control-hit-touch); height: var(--control-hit-touch); }
437
+ }
438
+
439
+ @media print {
440
+ .sidebar, .topbar, .el-button, .no-print { display: none !important; }
441
+ .app { grid-template-columns: 1fr; }
442
+ .el-card, .badge, .status { box-shadow: none !important; }
443
+ .badge, .chip, .batch { background: transparent !important; border: var(--border-width-default) solid currentColor; }
444
+ }
@@ -0,0 +1,6 @@
1
+ /* shadcn/ui 项目的 globals.css 模板(复制式接入用;从 npm 包接入时项目自己写这三行即可)。
2
+ 顺序:Tailwind → 设计系统构建产物 → 桥接本体 → 配方层。路径按项目实际调整;由 skill 的 integrate 写入。 */
3
+ @import "tailwindcss";
4
+ @import "../design-system/dist/index.css";
5
+ @import "./shadcn.css";
6
+ @import "./recipes.css";