// 甄房业务系统 · UI 规范与标准(定稿 v1.2) // 品牌资产(LOGO 定稿)+ SAP Fiori Morning Horizon(官方实测色值)+ 组件规范 + ElementUI 落地清单 // // ⚠ 数据来源纪律:本文件所有 Fiori 色值均取自官方主题包实测,不做记忆引用。 // npm @sap-theming/theming-base-content content/Base/baseLib/sap_horizon/css_variables.css // npm element-ui@2.15.14 packages/theme-chalk/src/common/var.scss // CSS 变量、令牌文件、规范表格三处均由下方 T 表统一生成,杜绝三处不一致。 // // 用法: node _design-test/spec/build-spec.js const fs = require('fs'); const path = require('path'); const HERE = __dirname; const LOGO = path.join(HERE, '..', 'logo'); const FZ = JSON.parse(fs.readFileSync(path.join(LOGO, 'final-z.json'), 'utf8')); const MS = JSON.parse(fs.readFileSync(path.join(LOGO, 'mark-scales.json'), 'utf8')); const PCT = {}; MS.stages.forEach(s => { PCT[s.pct] = s; }); const MAIN = PCT[80]; const H04 = 'M3 11.99v2.51c0 3.3 0 4.95 1.025 5.975S6.7 21.5 10 21.5h4c3.3 0 4.95 0 5.975-1.025S21 17.8 21 14.5v-2.51c0-1.682 0-2.522-.356-3.25s-1.02-1.244-2.346-2.276l-2-1.555C14.233 3.303 13.2 2.5 12 2.5s-2.233.803-4.298 2.409l-2 1.555C4.375 7.496 3.712 8.012 3.356 8.74S3 10.308 3 11.99'; const FG = 'translate(5.3304 5.3294) scale(2.22222)'; const zScale = s => 'translate(' + MS.center[0] + ' ' + MS.center[1] + ') scale(' + s.toFixed(4) + ') translate(' + (-MS.center[0]) + ' ' + (-MS.center[1]) + ')'; const mark = (pct, ink, size) => '' + '' + ''; // ═══════════════════════════════════════════════════════════════ // T:全部设计令牌(唯一数据源) // v = 取值 src = 官方来源变量 / 「派生」 // use = 用途 dev = 与官方的偏离说明(可选) // c = [底色, 门槛] 对比度检查(可选) // ═══════════════════════════════════════════════════════════════ const GROUP = [ ['品牌层', '甄房自有色彩,用于标识与导航。绝不进入交互色 —— 界面交互一律用主色,标识一律用墨蓝', [ ['--zf-brand-ink', '#0C2340', '甄房自有', '品牌主色 · LOGO / 侧栏底 / 登录页底', ['#FFFFFF', 4.5]], ['--zf-brand-ink-deep', '#071527', '甄房自有', '最深底 · 应用图标、启动图', null], ['--zf-brand-gold', '#C6A15B', '甄房自有', '品牌点缀 · 选中指示条、关键数字,占比 ≤1%', null], ['--zf-brand-gold-soft', '#E3CB99', '甄房自有', '金色浅阶 · 深底上的次级强调', null] ]], ['交互层', '主色取官方强调按钮底色;悬停加深一档;按下态官方是「白底蓝字蓝框」,不是更深的蓝', [ ['--zf-primary', '#0070F2', 'sapButton_Emphasized_Background', '主按钮底 · 关键图标', ['#FFFFFF', 4.5]], ['--zf-primary-hover', '#0064D9', 'sapButton_Emphasized_Hover_Background', '主按钮悬停底', ['#FFFFFF', 4.5]], ['--zf-primary-active-bg', '#FFFFFF', 'sapButton_Emphasized_Active_Background', '主按钮按下底(官方为白)', null], ['--zf-primary-active-ink', '#0064D9', 'sapButton_Emphasized_Active_TextColor', '主按钮按下时的字与框色', ['#FFFFFF', 4.5]], ['--zf-link', '#0064D9', 'sapLinkColor', '链接色(官方链接比主色深一档,两者不同色)', ['#FFFFFF', 4.5]] ]], ['表面层', '列表/表格悬停是中性灰,选中才是浅蓝 —— 与「悬停也用蓝」的常见做法不同', [ ['--zf-bg-base', '#FFFFFF', 'sapList_Background', '卡片、表格、内容容器底', null], ['--zf-bg-sub', '#F5F6F7', 'sapBackgroundColor', '页面底 · 表头 · 斑马纹(sapList_AlternatingBackground 同值)', null], ['--zf-bg-hover', '#EAECEE', 'sapList_Hover_Background', '行/项悬停底(中性灰)', null], ['--zf-bg-active', '#DEE2E5', 'sapList_Active_Background', '行/项按下底', null], ['--zf-bg-selected', '#EBF8FF', 'sapList_SelectionBackgroundColor', '行/项选中底(极浅蓝)', null] ]], ['边框', '分两类:功能性描边(输入框,深色保证可辨识)与分隔线(表格卡片,浅色)', [ ['--zf-border', '#E5E5E5', 'sapList_BorderColor', '分隔线 · 表格线 · 卡片边框', null], ['--zf-border-field', '#556B81', 'sapField_BorderColor', '输入框/下拉/日期等控件描边(功能性,需 ≥3:1)', ['#FFFFFF', 3]], ['--zf-border-strong', '#758CA4', 'sapContent_ForegroundBorderColor', '强分隔 · 未选中控件 · 分组边界', ['#FFFFFF', 3]] ]], ['文字', '白底上能过 AA 4.5:1 的灰最深只到 #5F7590,所以二级与三级灰必然接近 —— 拉开层级要靠字号字重,不是继续变浅', [ ['--zf-text-1', '#131E29', 'sapTextColor', '一级文字 · 标题、正文、数字(同时也是 sapField_TextColor)', ['#FFFFFF', 4.5]], ['--zf-text-2', '#556B82', 'sapContent_LabelColor', '二级文字 · 表单标签、表头、说明', ['#FFFFFF', 4.5]], ['--zf-text-3', '#5F7590', '派生', '三级文字 · 单位、时间戳、辅助信息', ['#FFFFFF', 4.5], '官方 Neutral #788FA6 白底仅 3.34:1,不能用于正文级小字;此值按 4.5:1 上限反推'], ['--zf-text-placeholder', '#5F7590', '派生(官方为 #556B82)', '占位符 · 禁用态文字', ['#FFFFFF', 4.5], '官方占位符与二级文字同色,会让人误以为已填内容;这里刻意浅一档以示区别'], ['--zf-text-inverse', '#FFFFFF', 'sapContent_ContrastTextColor', '深底上的文字', ['#0C2340', 4.5]], ['--zf-text-inverse-2', '#BBC1CA', '派生(rgba(255,255,255,.72) 在 #0C2340 上的合成值)', '深底上的二级文字 · 侧栏未选中项', ['#0C2340', 4.5]] ]], ['语义色', '一律「描边 + 浅底」,不用实心高饱和色块。只有成功/错误能直接当正文色,警告与中性只能作图形', [ ['--zf-success', '#256F3A', 'sapPositiveColor', '成功 · 有效 · 已完成(正文可用)', ['#FFFFFF', 4.5]], ['--zf-error', '#AA0808', 'sapNegativeColor', '错误 · 已下架 · 危险操作(正文可用)', ['#FFFFFF', 4.5]], ['--zf-warning', '#E76500', 'sapCriticalColor', '警告 · 维护中 · 待处理(仅描边/底色,正文需配深色字)', ['#FFFFFF', 3]], ['--zf-info', '#0070F2', 'sapInformativeColor', '信息提示(同主色)', ['#FFFFFF', 4.5]], ['--zf-neutral', '#788FA6', 'sapNeutralColor', '中性 · 仅图形与 ≥18px 大字,禁用于正文', ['#FFFFFF', 3]] ]], ['形状与层级', '圆角全部来自官方 rem 值(根字号 16px);阴影官方未提供 UI 级令牌,为派生值', [ ['--zf-radius-s', '4px', 'sapField_BorderCornerRadius(.25rem)', '输入类控件 · 标签 · 复选框', null], ['--zf-radius', '8px', 'sapButton_BorderCornerRadius(.5rem)', '按钮 · 卡片(基准)', null], ['--zf-radius-l', '12px', 'sapElement_BorderCornerRadius(.75rem)', '弹窗 · 抽屉 · 大面积容器', null], ['--zf-radius-tile', '16px', 'sapTile_BorderCornerRadius(1rem)', '大卡片 · 磁贴', null], ['--zf-radius-pill', '999px', '派生', '状态标签 · 头像', null], ['--zf-shadow-1', '0 1px 2px rgba(34,53,72,.08)', '派生', '卡片默认(官方无 UI 阴影令牌)', null], ['--zf-shadow-2', '0 2px 8px rgba(34,53,72,.12)', '派生', '下拉 · 弹窗 · 浮层', null] ]] ]; // 展开成便于生成 CSS 的扁平表 const TOKENS = []; GROUP.forEach(([g, desc, list]) => list.forEach(([n, v, src, use, c, dev]) => TOKENS.push({ g, gdesc: desc, n, v, src, use, c, dev }))); // 字号阶梯(除 caption 外均来自官方 rem 值,根字号 16px) const TYPE = [ ['display', 24, 600, 1.35, 'sapFontHeader3Size(1.5rem)', '页面主标题'], ['title', 20, 600, 1.4, 'sapFontHeader4Size(1.25rem)', '模块标题 · 抽屉标题'], ['title-s', 16, 600, 1.45, 'sapFontHeader5Size / sapFontLargeSize(1rem)', '卡片标题 · 分区小标题'], ['body', 14, 400, 1.6, 'sapFontSize(.875rem)', '正文基准 · 表格内容 · 表单输入值'], ['body-s', 12, 400, 1.6, 'sapFontSmallSize(.75rem)', '次级说明 · 表格辅助行'], ['caption', 11, 400, 1.5, '派生', '标签 · 单位 · 时间戳(不要再小)'] ]; const SPACE = [4, 8, 12, 16, 20, 24, 32, 40]; // 无障碍检查清单 const A11Y = [ ['一级文字 / 白底', '#131E29', '#FFFFFF', 4.5], ['二级文字 / 白底', '#556B82', '#FFFFFF', 4.5], ['三级文字 / 白底', '#5F7590', '#FFFFFF', 4.5], ['一级文字 / 次级底', '#131E29', '#F5F6F7', 4.5], ['二级文字 / 次级底', '#556B82', '#F5F6F7', 4.5], ['一级文字 / 选中底', '#131E29', '#EBF8FF', 4.5], ['白字 / 交互主色(按钮)', '#FFFFFF', '#0070F2', 4.5], ['白字 / 主色悬停', '#FFFFFF', '#0064D9', 4.5], ['链接色 / 白底', '#0064D9', '#FFFFFF', 4.5], ['成功 / 白底(正文)', '#256F3A', '#FFFFFF', 4.5], ['错误 / 白底(正文)', '#AA0808', '#FFFFFF', 4.5], ['警告 / 白底(仅图形)', '#E76500', '#FFFFFF', 3], ['中性 / 白底(仅图形)', '#788FA6', '#FFFFFF', 3], ['控件描边 / 白底', '#556B81', '#FFFFFF', 3], ['白字 / 品牌墨蓝(侧栏)', '#FFFFFF', '#0C2340', 4.5], ['侧栏二级字 / 品牌墨蓝', '#BBC1CA', '#0C2340', 4.5] ]; // ═══════════════════════════════════════════════════════════════ // ElementUI 2.15.14 落地清单 // m = 必改(不改主题就是半成品) a = 自动派生(无需覆盖) d = 刻意偏离官方 // ═══════════════════════════════════════════════════════════════ const ELMAP = [ ['$--color-primary', '--zf-primary', '#0070F2', 'm', '主色,ElementUI 全组件取此值'], ['$--color-success', '--zf-success', '#256F3A', 'm', ''], ['$--color-warning', '--zf-warning', '#E76500', 'm', '同时须改白字,见下方风险项'], ['$--color-danger', '--zf-error', '#AA0808', 'm', '官方 Negative 色'], ['$--color-info', '--zf-text-3', '#5F7590', 'd', '官方 Informative 与主色同值,若照搬会让「信息按钮」与「主按钮」无法区分,故退为中性灰'], ['$--color-text-primary', '--zf-text-1', '#131E29', 'm', ''], ['$--color-text-regular', '--zf-text-1', '#131E29', 'd', '官方正文只有一档 sapTextColor;ElementUI 分 primary/regular 两级,这里刻意拉平'], ['$--color-text-secondary', '--zf-text-3', '#5F7590', 'm', '同时决定表头文字色'], ['$--color-text-placeholder', '--zf-text-placeholder', '#5F7590', 'd', '官方与二级文字同色,会与已填内容混淆'], ['$--border-color-base', '--zf-border-field', '#556B81', 'm', '功能性描边。官方输入框描边本就深,这是 Horizon 的高辨识度来源'], ['$--border-color-light', '--zf-border', '#E5E5E5', 'm', ''], ['$--border-color-lighter', '--zf-border', '#E5E5E5', 'm', '同时决定表格线'], ['$--border-color-extra-light', '--zf-bg-active', '#DEE2E5', 'm', ''], ['$--border-color-hover', '--zf-border-strong', '#758CA4', 'm', ''], ['$--background-color-base', '--zf-bg-sub', '#F5F6F7', 'm', '同时是表格悬停的默认值,必须连带覆盖下一行'], ['$--border-radius-base', '--zf-radius', '8px', 'm', '官方按钮圆角 .5rem'], ['$--border-radius-small', '--zf-radius-s', '4px', 'm', '官方输入类圆角 .25rem'], ['$--button-border-radius', '--zf-radius', '8px', 'm', '自动继承 base,写出只为明确'], ['$--font-size-base', '--zf-fs-body', '14px', 'm', '官方 sapFontSize = .875rem,与 ElementUI 默认一致,无需改动'], ['$--font-size-small', '--zf-fs-body-s', '12px', 'd', '官方小号 = .75rem。由 ElementUI 默认的 13px 降到 12px,会影响部分组件,需目测回归'], ['$--font-size-extra-small', '--zf-fs-body-s', '12px', 'm', ''], ['$--font-size-medium', '--zf-fs-title-s', '16px', 'm', ''], ['$--font-size-large', '--zf-fs-title', '20px', 'd', '官方 Header4 = 1.25rem。由 ElementUI 默认 18px 提到 20px,会改变标题观感'], ['$--font-size-extra-large', '--zf-fs-display', '24px', 'm', '官方 Header3 = 1.5rem'], ['$--table-border-color', '--zf-border', '#E5E5E5', 'm', ''], ['$--table-font-color', '--zf-text-1', '#131E29', 'm', '默认继承 text-regular,已同上'], ['$--table-header-font-color', '--zf-text-3', '#5F7590', 'm', '默认继承 text-secondary,已同上'], ['$--table-header-background-color', '--zf-bg-sub', '#F5F6F7', 'm', '必改:默认是纯白,不改则表头与内容无法区分'], ['$--table-row-hover-background-color', '--zf-bg-hover', '#EAECEE', 'm', '必改:默认继承 background-color-base,不改则悬停变灰底而非中性灰阶'], ['$--table-current-row-background-color', '--zf-bg-selected', '#EBF8FF', 'm', '默认由 color-primary-light-9 派生,显式写死更可控'], ['$--box-shadow-base', '--zf-shadow-2', '0 2px 8px rgba(34,53,72,.12)', 'm', ''], ['$--menu-background-color', '--zf-brand-ink', '#0C2340', 'm', '深色侧栏底色'], ['$--color-primary-light-1 … -9', '—', '自动派生', 'a', '由 mix($--color-white, $--color-primary, N*10%) 基于覆盖后的主色计算,顺序正确即无需覆盖:必须在 @import theme-chalk 之前定义 $--color-primary'], ['$--color-success-light / -lighter 等 8 个', '—', '自动派生', 'a', '同上,按语义色 mix 得到'], ['$--table-border', '—', '自动派生', 'a', '= 1px solid $--table-border-color'], ['$--button-small-border-radius / -mini-border-radius', '—', '自动派生', 'a', '= $--border-radius-base - 1 = 7px,官方无对应值,如需一致请显式设为 6px'] ]; // 落地风险项(必须人工处理,光改变量解决不了) const RISKS = [ ['警告按钮白字不达标', '#E76500 底 + 白字实测只有 3.36:1,连正文 4.5:1 都不到(官方 Fiori 也没有橙底白字的实心按钮)。两条出路:① 橙色只用于描边/文字按钮;② 保留实心橙底但把 $--button-warning-font-color 改成深色 #131E29(对 #E76500 为 4.6:1)'], ['按钮悬停方向与官方相反', 'ElementUI 的主按钮悬停 = mix(白, 主色, 20%) 即变浅,按下 = mix(黑, 主色, 10%) 变深;而 Fiori 是悬停 #0064D9 变深、按下变白底蓝框。二者不可兼得:
· 若照 ElementUI 走,只需改色,但规范里「悬停 #0064D9」不成立,按钮观感偏「淡」;
· 若照 Fiori 走,必须给 .el-button--primary 写自定义 hover/active 样式(变量层面做不到)。
建议按 Fiori 走并写自定义样式,否则主按钮的悬停反馈会与全站其余悬停(深一档)不一致'], ['深色侧栏变量不够用', 'ElementUI 只暴露 4 个菜单变量($--menu-background-color、$--menu-item-font-color、$--menu-item-hover-fill、$--menu-item-font-size)。改成墨蓝底后至少还要把 $--menu-item-font-color 设为白色(否则默认 #131E29 在墨蓝上几乎看不见),且子菜单底色、选中态仍无变量可用,必须补自定义 CSS'], ['表格只有一种边框色', 'ElementUI 全表共用 $--table-border-color,做不出「外框深、内线浅」的两级分隔。这不是配置问题,要两级就必须写自定义样式'] ]; // 项目现有主题文件里已经失效的变量(实测 ElementUI 2.15.14 中不存在) const DEADVARS = [ ['$--color-primary-dark-2', 'Element Plus 才有此变量;ElementUI 的按下态由 mix(黑, 主色, 10%) 算出'], ['$--border-radius-round', '不存在;ElementUI 只有 $--border-radius-circle: 100%'], ['$--table-header-bg', '不存在;正确名为 $--table-header-background-color'], ['$--table-row-hover-bg', '不存在;正确名为 $--table-row-hover-background-color'], ['$--table-current-row-bg', '不存在;正确名为 $--table-current-row-background-color'] ]; // 图表分类色板(v1.2 新增)—— ECharts 多序列用,色相拉开 + 明度错位,相邻序差异最大 const CHART = [ ['#0057D2', '蓝', 'sapShell_Category_1(主色系,与品牌呼应)'], ['#E76500', '橙', 'sapCriticalColor'], ['#188918', '绿', 'sapShell_Category_8'], ['#DF1278', '洋红', 'sapShell_Category_2'], ['#07838F', '青', 'sapShell_Category_6'], ['#D27700', '琥珀', 'sapAccentColor1'], ['#7858FF', '紫', 'sapShell_Category_12'], ['#D20A0A', '红', 'sapShell_Category_11'], ['#5B738B', '蓝灰', 'sapShell_Category_10(中性序列)'], ['#C6A15B', '品牌金', '甄房品牌色'] ]; // 业务语义色(v1.2 定稿) const BIZ = [ ['房源等级 A(优质)', '#C6A15B', '品牌金 —— 金色表达「优质/尊贵」,比红色更贴合甄房调性'], ['房源等级 B(一般)', '#E76500', 'sapCriticalColor'], ['房源等级 C(低质)', '#788FA6', 'sapNeutralColor'], ['客源热度 H(热)', '#AA0808', 'sapNegativeColor —— 温度用红,与等级的质量语义区分开'], ['客源热度 M(温)', '#E76500', 'sapCriticalColor'], ['客源热度 L(冷)', '#788FA6', 'sapNeutralColor'], ['跟进 · 3 天内', '#256F3A', 'sapPositiveColor'], ['跟进 · 7 天内', '#0070F2', '品牌交互蓝'], ['跟进 · 15 天内', '#E76500', 'sapCriticalColor'], ['跟进 · 超 15 天', '#AA0808', 'sapNegativeColor'] ]; const esc = s => String(s).replace(/&/g, '&').replace(//g, '>'); // ── 由 TOKENS 生成 CSS :root ──────────────────────────────────── const cssRoot = () => GROUP.map(([g, , list], i) => ' /* ── ' + g + ' ── */\n' + list.map(t => ' ' + t[0] + ': ' + t[1] + ';').join('\n') ).join('\n\n'); const FS = [['caption', 11], ['body-s', 12], ['body', 14], ['title-s', 16], ['title', 20], ['display', 24]]; const cssFontSizes = () => FS.map(([k, v]) => ' --zf-fs-' + k + ': ' + v + 'px;').join('\n'); const cssSpace = () => SPACE.map((v, i) => ' --zf-sp-' + (i + 1) + ': ' + v + 'px;').join(' '); // ═══════════════════════════════════════════════════════════════ // 页面样式 // ═══════════════════════════════════════════════════════════════ const CSS = ` *{box-sizing:border-box;margin:0;padding:0} :root{ ${cssRoot()} ${cssFontSizes()} ${cssSpace()} --zf-font:"72","PingFang SC","Microsoft YaHei","Helvetica Neue",Arial,sans-serif; } body{background:#0a0d13;color:#e9edf5;font-family:var(--zf-font);padding:40px 24px 90px;line-height:1.6} .wrap{max-width:1420px;margin:0 auto} h1{font-size:26px;letter-spacing:.2px}h1 b{color:#E3CB99} .hdr{border-bottom:1px solid rgba(255,255,255,.12);padding-bottom:18px;margin-bottom:6px} .meta{display:flex;gap:22px;flex-wrap:wrap;font-size:12px;color:#8b94a7;margin-top:12px} .meta b{color:#cfd6e4;font-weight:500} h2{font-size:19px;margin:48px 0 8px;padding-left:11px;border-left:4px solid #C6A15B} h2 em{font-size:11px;font-weight:400;color:#8b94a7;font-style:normal;margin-left:10px} h3{font-size:15px;margin:26px 0 10px;color:#E3CB99} p.note{font-size:12.5px;color:#8b94a7;margin:8px 0 16px;line-height:1.9} p.note b{color:#cfd6e4} code{font-family:Menlo,Consolas,monospace;font-size:11.5px} table.doc{width:100%;border-collapse:collapse;font-size:12.5px;margin-top:12px} table.doc th,table.doc td{border:1px solid rgba(255,255,255,.09);padding:8px 11px;text-align:left;vertical-align:top} table.doc th{background:rgba(255,255,255,.045);font-weight:600;color:#cfd6e4;font-size:11.5px;white-space:nowrap} table.doc td.k{color:#E3CB99;font-family:Menlo,Consolas,monospace;font-size:11.5px} table.doc td.v{font-family:Menlo,Consolas,monospace;font-size:11.5px;white-space:nowrap} table.doc td.s{font-family:Menlo,Consolas,monospace;font-size:10.5px;color:#8b94a7} table.doc td.u{color:#98a1b3} table.doc tr.hi td{background:rgba(94,224,138,.10)} table.doc tr.hi td:first-child{box-shadow:inset 3px 0 0 #5ee08a} .chip{display:inline-block;width:13px;height:13px;border-radius:4px;vertical-align:-2px;margin-right:7px; border:1px solid rgba(255,255,255,.22)} .pass,.fail{font-size:10px;padding:1px 7px;border-radius:20px;border:1px solid;white-space:nowrap} .pass{color:#5ee08a;border-color:rgba(94,224,138,.42)} .fail{color:#f0a55c;border-color:rgba(240,165,92,.45)} .tag2{display:inline-block;font-size:10px;padding:1px 7px;border-radius:20px;border:1px solid;white-space:nowrap} .tag2.m{color:#f0a55c;border-color:rgba(240,165,92,.5)} .tag2.d{color:#8ab4f8;border-color:rgba(138,180,248,.5)} .tag2.a{color:#8b94a7;border-color:rgba(139,148,167,.4)} .stage{background:var(--zf-bg-base);color:var(--zf-text-1);border-radius:var(--zf-radius-l); border:1px solid rgba(255,255,255,.13);padding:22px;margin-top:14px;font-family:var(--zf-font)} .stage.dark{background:var(--zf-brand-ink);color:#fff} .stage.plain{background:var(--zf-bg-sub)} .row{display:flex;gap:12px;flex-wrap:wrap;align-items:center} .btn{font-family:inherit;font-size:var(--zf-fs-body);border-radius:var(--zf-radius);padding:7px 16px; border:1px solid var(--zf-border);background:var(--zf-bg-base);color:var(--zf-text-1);cursor:default;white-space:nowrap} .btn.pri{background:var(--zf-primary);border-color:var(--zf-primary);color:#fff} .btn.pri.h{background:var(--zf-primary-hover);border-color:var(--zf-primary-hover)} .btn.pri.a{background:var(--zf-primary-active-bg);border-color:var(--zf-primary-active-ink);color:var(--zf-primary-active-ink)} .btn.gho{border-color:var(--zf-primary);color:var(--zf-primary);background:transparent} .btn.tx{border-color:transparent;background:transparent;color:var(--zf-link);padding:7px 6px} .btn.dan{background:var(--zf-error);border-color:var(--zf-error);color:#fff} .btn.dis{background:var(--zf-bg-sub);border-color:var(--zf-border);color:var(--zf-text-placeholder)} .btn.sm{font-size:var(--zf-fs-body-s);padding:4px 11px} .fld{display:flex;flex-direction:column;gap:5px} .fld label{font-size:var(--zf-fs-body-s);color:var(--zf-text-2)} .fld label .req{color:var(--zf-error);margin-left:3px} .fld input,.fld select{font-family:inherit;font-size:var(--zf-fs-body);color:var(--zf-text-1); background:var(--zf-bg-base);border:1px solid var(--zf-border-field);border-radius:var(--zf-radius-s); padding:8px 11px;min-width:190px} .fld input::placeholder{color:var(--zf-text-placeholder)} .fld input:focus,.fld select:focus{outline:none;border-color:var(--zf-primary);box-shadow:0 0 0 3px var(--zf-bg-selected)} .fld .hint{font-size:var(--zf-fs-caption);color:var(--zf-text-3)} .fld.err input{border-color:var(--zf-error)} .fld.err .hint{color:var(--zf-error)} .fld.dis input{background:var(--zf-bg-sub);color:var(--zf-text-placeholder);border-color:var(--zf-border)} .tbl{width:100%;border-collapse:collapse;font-size:var(--zf-fs-body);font-variant-numeric:tabular-nums;color:var(--zf-text-1)} .tbl th{text-align:left;font-weight:600;font-size:var(--zf-fs-body-s);color:var(--zf-text-3); background:var(--zf-bg-sub);padding:9px 12px;border-bottom:1px solid var(--zf-border);white-space:nowrap} .tbl td{padding:9px 12px;border-bottom:1px solid var(--zf-border)} .tbl tbody tr:nth-child(even){background:var(--zf-bg-sub)} .tbl tbody tr.hov{background:var(--zf-bg-hover)} .tbl tbody tr.sel{background:var(--zf-bg-selected)} .tbl tbody tr.sel td:first-child{box-shadow:inset 3px 0 0 var(--zf-primary)} .tbl .lk{color:var(--zf-link)} .tbl .sub{display:block;font-size:var(--zf-fs-caption);color:var(--zf-text-3);margin-top:2px} .tbl .num{text-align:right} .tbl .act a{color:var(--zf-link);text-decoration:none;margin-right:10px;font-size:var(--zf-fs-body-s)} .tbl .act a.dan{color:var(--zf-error)} .tg{display:inline-block;font-size:var(--zf-fs-caption);padding:2px 9px;border-radius:var(--zf-radius-pill); border:1px solid;white-space:nowrap;background:var(--zf-bg-base)} .tg.ok{color:var(--zf-success);border-color:var(--zf-success);background:#F1F7F3} .tg.wn{color:#B44F00;border-color:var(--zf-warning);background:#FDF1E8} .tg.er{color:var(--zf-error);border-color:var(--zf-error);background:#FBF0F0} .tg.pr{color:var(--zf-primary);border-color:var(--zf-primary);background:var(--zf-bg-selected)} .tg.nu{color:var(--zf-text-2);border-color:var(--zf-border-strong);background:var(--zf-bg-base)} .tg.gd{color:#8a6a2f;border-color:var(--zf-brand-gold);background:#FAF6EE} .tabs{display:flex;gap:2px;border-bottom:1px solid var(--zf-border)} .tabs a{font-size:var(--zf-fs-body);padding:9px 14px;color:var(--zf-text-2);text-decoration:none;border-bottom:2px solid transparent} .tabs a.on{color:var(--zf-primary);border-bottom-color:var(--zf-primary);font-weight:600} .pg{min-width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center; border:1px solid var(--zf-border);border-radius:var(--zf-radius-s);background:var(--zf-bg-base); color:var(--zf-text-1);font-size:var(--zf-fs-body-s);margin-right:6px} .pg.on{background:var(--zf-primary);border-color:var(--zf-primary);color:#fff;font-weight:600} .al{display:flex;gap:9px;align-items:flex-start;font-size:var(--zf-fs-body);padding:11px 14px; border-radius:var(--zf-radius);border:1px solid;border-left-width:3px;color:var(--zf-text-1);margin-bottom:9px} .al b{font-weight:600} .al.i{border-color:var(--zf-primary);background:var(--zf-bg-selected)} .al.s{border-color:var(--zf-success);background:#F1F7F3} .al.w{border-color:var(--zf-warning);background:#FDF1E8} .al.e{border-color:var(--zf-error);background:#FBF0F0} .card{background:var(--zf-bg-base);border:1px solid var(--zf-border);border-radius:var(--zf-radius); box-shadow:var(--zf-shadow-1);padding:16px} .kpi{background:var(--zf-bg-base);border:1px solid var(--zf-border);border-radius:var(--zf-radius); padding:13px 15px;box-shadow:var(--zf-shadow-1)} .kpi .t{font-size:var(--zf-fs-body-s);color:var(--zf-text-3)} .kpi .v{font-size:var(--zf-fs-display);font-weight:600;margin-top:4px;font-variant-numeric:tabular-nums} .kpi .v em{font-size:var(--zf-fs-body-s);font-style:normal;font-weight:400;color:var(--zf-text-3);margin-left:4px} .kpi .d{font-size:var(--zf-fs-caption);margin-top:3px;color:var(--zf-success)} .kpi .d.dn{color:var(--zf-error)} .kpi.gold .v{color:#8a6a2f} .app{display:grid;grid-template-columns:200px 1fr;border:1px solid var(--zf-border); border-radius:var(--zf-radius-l);overflow:hidden;background:var(--zf-bg-base)} .app .side{background:var(--zf-brand-ink);padding:14px 0} .app .side .brand{display:flex;align-items:center;gap:10px;padding:2px 15px 16px;color:#fff} .app .side .brand b{font-size:var(--zf-fs-title-s);letter-spacing:1px} .app .side .brand em{display:block;font-size:8px;letter-spacing:1.4px;opacity:.6;font-style:normal} .app .side a{display:flex;align-items:center;gap:9px;color:var(--zf-text-inverse-2);text-decoration:none; font-size:var(--zf-fs-body);padding:9px 15px;border-left:3px solid transparent} .app .side a.on{background:rgba(255,255,255,.10);color:#fff;border-left-color:var(--zf-brand-gold);font-weight:600} .app .side a .dot{width:6px;height:6px;border-radius:2px;background:currentColor;opacity:.55} .app .main{min-width:0} .app .head{display:flex;align-items:center;gap:14px;padding:11px 18px;border-bottom:1px solid var(--zf-border)} .app .head .cb{font-size:var(--zf-fs-body);color:var(--zf-text-2)} .app .head .cb b{color:var(--zf-text-1)} .app .head .usr{margin-left:auto;display:flex;align-items:center;gap:9px;font-size:var(--zf-fs-body-s);color:var(--zf-text-2)} .app .head .av{width:26px;height:26px;border-radius:50%;background:var(--zf-bg-selected); color:var(--zf-primary);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600} .app .bd{padding:16px 18px;background:var(--zf-bg-sub)} .st{border:1px dashed var(--zf-border-strong);border-radius:var(--zf-radius);padding:26px; text-align:center;color:var(--zf-text-3);font-size:var(--zf-fs-body)} .st .ic{font-size:26px;display:block;margin-bottom:9px;opacity:.55} .st b{display:block;color:var(--zf-text-2);font-size:var(--zf-fs-body);margin-bottom:5px;font-weight:600} .sk{background:var(--zf-bg-sub);border-radius:var(--zf-radius-s);height:13px;margin:7px 0} .dont{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:12px;margin-top:12px} .dont div{background:rgba(240,92,92,.06);border:1px solid rgba(240,92,92,.3);border-radius:10px; padding:12px;text-align:center;font-size:11px;color:#f0a5a5} .dont div .pv{height:78px;display:flex;align-items:center;justify-content:center;background:#fff; border-radius:7px;margin-bottom:9px;overflow:hidden} .risk{background:rgba(240,165,92,.08);border:1px solid rgba(240,165,92,.32);border-radius:12px; padding:14px 16px;font-size:12.5px;line-height:1.95;color:#f2c48a;margin-top:10px} .risk b{color:#ffd9a8} .risk code{background:rgba(255,255,255,.07);padding:1px 5px;border-radius:4px} pre{background:#0d1220;border:1px solid rgba(255,255,255,.09);border-radius:11px;padding:14px 16px; font-size:11.5px;line-height:1.85;color:#cfd6e4;overflow-x:auto;margin-top:12px;font-family:Menlo,Consolas,monospace} a.dl{color:#8ab4f8} `; // ── 令牌表 ── const tokenTables = GROUP.map(([g, desc, list]) => `

${g}

${desc}

${list.map(([n, v, src, use, c, dev]) => ``).join('')}
令牌取值官方来源用途对比度
${n} ${v} ${src} ${use}${dev ? '
⚠ 偏离官方:' + dev + '' : ''}
${c ? '' : '—'}
`).join(''); // ═══════════════════════════════════════════════════════════════ const html = ` 甄房业务系统 · UI 规范与标准(定稿 v1.2)

甄房业务系统 · UI 规范与标准

版本 v1.2(定稿) 色彩体系 SAP Fiori Morning Horizon LOGO Forte Z + 甄房墨蓝 · Z 占比 80% UI 库 element-ui 2.15.14(Vue 2.6) 日期 2026-09-19

本页是该项目的唯一权威 UI 标准。任何界面实现以本页的令牌名与取值为准;不要直接写十六进制色值,不要引入第二套色彩。
色值来源纪律:全部 Fiori 色值取自官方主题包 @sap-theming/theming-base-content → sap_horizon/css_variables.css 实测,ElementUI 变量名与默认值取自 element-ui@2.15.14 → theme-chalk/src/common/var.scss 实测,逐条标注官方来源变量名,无记忆引用。 对比度由页面运行时真实计算(WCAG 2.1)。

一、品牌资产 · LOGO已定稿

主 logo 采用 Z 在房框内 80% 档;房框与描边粗细恒定,四档只缩放框内 Z。全部资产为矢量转曲路径。

${mark(80, '#0C2340', 88)}
主 logo · 浅底
${mark(80, '#FFFFFF', 88)}
主 logo · 深底反白
${mark(80, '#C6A15B', 88)}
金色点缀版(限定场景)

四档占比

${MS.stages.map(s => { const use = { 90: '需要更强视觉重量时:小图标位、深色底上的白标(不要用于 32px 以下)', 80: '主 logo,一律默认用这一档', 70: '大幅面(≥1200px)与印刷物料', 60: '名片、信封、大留白版式' }[s.pct]; return ` `; }).join('')}
档位Z 外接框(64 网格)相对主 logo用途
${s.pct}%${s.pct === 80 ? ' ★' : ''} ${s.size[0].toFixed(1)} × ${s.size[1].toFixed(1)} ×${s.vsMain.toFixed(2)}${use}

规则

项标准
配色标识只用 #0C2340(浅底)或 #FFFFFF(深底)。严禁使用交互蓝 #0070F2 作为标识色 —— 否则用户无法区分「品牌标记」与「可点元素」。
注意:v1.1 起链接色已改为 #0064D9,标识色也不要用它
最小尺寸图标主形 ≥ 24px;横版锁定标 ≥ 120px 宽。16px 场景用 favicon 专用版,不要硬缩主形
安全留白四周留白 ≥ 图标高度的 25%(64 网格里留 16)
深色场景用反白版;不要在深底上用墨蓝正片
禁止拉伸变形 · 改比例 · 加描边/阴影/发光 · 换非品牌色 · 改动框与 Z 的相对大小 · 在花哨照片上直接压标
${mark(80, '#0C2340', 62)}
拉伸变形
${mark(80, '#0C2340', 62)}
旋转
${mark(80, '#0070F2', 62)}
用交互蓝作标识色
${mark(80, '#0C2340', 62)}
加阴影
${mark(90, '#0C2340', 62)}
在 24px 以下用 90% 档(易糊线)
${mark(80, '#0C2340', 62)}
在中间调灰底上用墨蓝

二、色彩系统SAP Fiori Morning Horizon · 官方实测值

核心策略:品牌色与交互色分离。甄房墨蓝 #0C2340 承担品牌与导航,Fiori 的 #0070F2 承担交互反馈。 两者的明度差足够大 —— 墨蓝在界面里读作「近黑的中性色」,不会与亮蓝争夺注意力,也不会被误读为可点击元素。

${tokenTables}

用色配比

层占比说明
中性(白 / 灰)≈ 75%页面基底与分隔靠灰阶承担,这是数据密集型界面不累的前提
品牌墨蓝≈ 15%侧栏与页头;形成稳定视觉锚点,让导航边界一眼可辨
交互蓝≈ 7%主操作、链接、选中态。一屏内主操作建议 ≤ 1 个
语义色≈ 2%状态标签、提示条。一律描边+浅底,不用实心块
品牌金≤ 1%只放在「点」上:选中指示条、关键数字。铺面即俗

三、字体与文字层级官方 rem 值,根字号 16px

字族:--zf-font = "72", "PingFang SC", "Microsoft YaHei", …("72" 为 Fiori 官方字族,未安装时回落系统中文黑体)。 数字一律加 font-variant-numeric: tabular-nums。

${TYPE.map(([n, sz, w, lh, src, u]) => `
--zf-fs-${n} 甄房 · 二手房房源 1,284 套 ${sz}px / ${w} / 行高 ${lh} · ${u}
${src}
`).join('')}

只有 6 档,不要再加。中文最小 11px(caption)—— 再小在 Windows 上会糊。

四、间距 · 圆角 · 阴影

间距(4pt 基数)

${SPACE.map(v => `
${v}
`).join('')}

常用:控件内边距 8 / 11 / 16;表单行间距 12;卡片内边距 16;区块间距 20 / 24;页面左右边距 18 / 24。 不要出现 5、7、9、13 这类非 4 倍数。

圆角与阴影

4px · 输入类
输入框、下拉、标签、复选框
8px · 基准
按钮、卡片
12px
弹窗、抽屉、大容器
16px · 磁贴
大卡片、看板磁贴
pill
状态标签、头像

阴影只用两级:--zf-shadow-1 卡片默认、--zf-shadow-2 浮层。 分隔优先用边框而不是阴影 —— 一屏几十张卡片都带阴影会整体发糊。

五、组件规范

按钮

「按下」是白底蓝字蓝框 —— 这是 Fiori 官方定义(sapButton_Emphasized_Active_Background: #fff + 蓝字蓝框),不是做错了。 其余状态:悬停 #0064D9 加深一档、禁用灰底。

类型用途一屏数量
主操作 pri推动流程前进的唯一动作:查询、提交、保存、确认≤ 1
次要 gho辅助动作:高级筛选、批量导入≤ 2
普通 / 文字重置、取消、查看详情不限
危险 dan删除、作废、强制回收;必须二次确认≤ 1

表单

支持模糊匹配
必填
请输入完整姓名(至少 2 个字)
不可编辑

标签在上、控件在下的纵向排布。标签 12px 不加重。错误态只改边框与提示文字色,不要给整个字段加红底。
控件描边用 --zf-border-field #556B81(深色)—— 这是 Horizon 的特色,输入框「有框」才看得清;分隔线才用浅色 #E5E5E5。两者不可混用。

数据表格

房源编码楼盘 / 房号面积售价状态维护人操作
SH-ES-202609-00037滨江壹号 3幢 1802滨江区 · 钱塘江畔128.5 ㎡1,285 万有效张伟查看编辑
SH-ES-202609-00041钱江新城 7幢 2201上城区 · 万象城156.0 ㎡1,860 万待检核李敏查看编辑
SH-ES-202609-00052滨江壹号 5幢 901滨江区 · 钱塘江畔98.2 ㎡920 万独家王强查看编辑
SH-ES-202609-00058绿城桂语 2幢 1503西湖区 · 文三路142.6 ㎡1,420 万维护中张伟查看编辑
SH-ES-202609-00063万科未来 1幢 608余杭区 · 未来科技城89.0 ㎡610 万已下架赵磊查看删除
共 1,284 条123…65 每页 20 条
项标准
斑马纹偶数行 --zf-bg-sub #F5F6F7,与白底差值仅 2–3%。这是数据表最高频的疲劳源,不要加大对比
悬停 / 选中悬停是中性灰 #EAECEE,选中才是浅蓝 #EBF8FF —— 官方如此,两者不要都做成蓝。选中行左侧加 3px 主色竖条,长表格滚动后仍可辨认
数字列右对齐 + tabular-nums;金额带千分位,单位写表头(「售价(万)」)不逐行重复
操作列固定最后一列,文字链 ≤ 3 个;破坏性动作放最后并标红
边框全表只有一种线色 #E5E5E5(ElementUI 也只暴露一个变量)。想要「外框深、内线浅」必须写自定义样式

状态标签

有效维护中已下架 待检核草稿独家

一律 描边 + 浅底,圆角 pill,字号 11。
注意警告标签的文字色是 #B44F00 而不是 #E76500 —— 官方 Critical 色在白底只有 3.36:1,直接当文字色不达标; 底色仍用 #E76500 描边,文字压深一档。不要用白字压在 #E76500 上(3.36:1,同样不达标)。

页签 · 分页

123…65

提示条

ℹ提示:该房源委托期将于 7 天后到期,请及时续签。
✓成功:房源 SH-ES-202609-00037 已提交检核。
⚠警告:钥匙编号与系统记录不一致,请核对后重试。
✕错误:售价不能低于政府指导价,请修改后重试。

卡片 · KPI · 页头导航

在售房源
1,284套
↑ 3.2% 较上周
本周新增客源
96位
↑ 12 位
待跟进
23条
↓ 5 条
本月预估佣金
86.4万
↑ 8.6%
房源管理 / 二手房房源
张张伟 · 滨江门店
内容区底色 --zf-bg-sub,卡片用白底与 1px 边框区分层级

侧栏固定用 墨蓝 #0C2340。这是本项目刻意偏离 Fiori 官方 Shell 的地方(官方 sapShell_Background 是浅色 #EFF1F2)—— 为了减少整屏亮度、形成稳定视觉锚点。浅灰侧栏在长时间使用中会与内容区「糊在一起」。 选中项用金色 3px 左指示条 + 10% 白底。代价是 ElementUI 的菜单变量不够用,需补自定义样式(见第九章)。

六、页面状态

⏳加载中用骨架屏(灰条),不要整页转圈
📭没有数据「还没有房源,点右上角新增」+ 主操作按钮
🔍筛选无结果「没有符合条件的房源,试试放宽条件」+ 重置按钮
⚠加载失败「数据加载失败」+ 重试按钮 + 错误码
🔒无权限「你没有查看该门店房源的权限」+ 申请入口
骨架屏示例

五种状态都必须单独设计。「没有数据」与「筛选无结果」文案必须不同 —— 否则用户会以为自己的数据丢了。

七、长时使用约束针对经纪人每天连续使用 6–8 小时

约束做法解决什么
深色品牌侧栏侧栏统一 #0C2340,不用浅灰(偏离官方 Shell 浅色)形成稳定视觉锚点;浅灰侧栏会与内容区糊在一起
斑马纹极低对比条纹 #F5F6F7,与白底差值 2–3%帮助横向追行不串行,又不产生条纹眩光
语义色降饱和描边+浅底,不用实心块;警告文字用深一档的 #B44F00一屏几十个状态标签时的最大刺眼源
悬停与选中分工悬停中性灰 #EAECEE、选中浅蓝 #EBF8FF长时间划动列表时,中性灰比蓝底更不累眼;蓝色只留给「已选定」这一个强语义
金色只做点占比 ≤1%,只用于指示条与关键数字保住品牌质感,避免大面积金色显廉价
正文不用纯黑正文 #131E29 而非 #000降低硬反差带来的持续眼疲劳

八、无障碍标准

门槛:正文文字 ≥ 4.5:1,大字与图形/控件边界 ≥ 3:1。下表全部由运行时真实计算。

${A11Y.map(([n, f, b, r]) => ``).join('')}
检查项前景 / 背景实测门槛结论
${n}${f} / ${b} ≥${r}:1

其他无障碍要求

项标准
焦点态所有可交互元素必须有可见焦点环:border-color: --zf-primary + box-shadow: 0 0 0 3px --zf-bg-selected。禁止 outline:none 而不给替代
触达尺寸可点区域 ≥ 32×32px(H5 ≥ 44×44px)
不靠颜色单独表意状态标签必须带文字,不能只有色点;图表必须带数据标签或图例
键盘可达Tab 顺序符合视觉顺序;弹窗打开后焦点移入并锁定,Esc 关闭后归还触发元素
动效过渡 ≤ 200ms;尊重 prefers-reduced-motion

九、落地清单element-ui 2.15.14 变量映射 · 已逐条核对官方源码

项目前端为 RuoYi-Vue + element-ui 2.15.14 + Vue 2.6 + sass,变量覆盖写在 ruoyi-ui/src/assets/styles/element-variables.scss(须在 @import "~element-ui/packages/theme-chalk/src/index" 之前定义)。
下表 必改 = 不改主题就是半成品 · 刻意偏离 = 与官方取值不同,已在行内写明原因 · 自动派生 = ElementUI 会基于主色自动算出,只要定义顺序正确就无需覆盖。

${ELMAP.map(([e, t, v, k, note]) => ``).join('')}
ElementUI 变量本项目令牌取值类别说明
${e}${t} ${v} ${k === 'm' ? '必改' : k === 'd' ? '刻意偏离' : '自动派生'} ${note || '—'}

光改变量解决不了的风险项

${RISKS.map(([t, d]) => `
${t}
${d}
`).join('')}

项目现有主题文件中的失效变量

现有 element-variables.scss 里下面这些变量在 element-ui 2.15.14 中并不存在, Sass 不会报错、但也不会生效 —— 属于「以为改了其实没改」。同样问题也存在于另一个主题体系(橙色那套)里。

${DEADVARS.map(([v, d]) => ``).join('')}
写法实际情况
${v}${d}

图表分类色板(ECharts)v1.2 新增 · 最多 10 序列

多序列图表禁止把单色映射逐色套用 —— 旧色板里 #ff7a1a 映射成主色蓝后会与原有的 #3b82f6 撞色, #ffb31a/#d9481c 也会与 #ef4444 挤在橙红区,10 条序列将失去区分度。
下表是重配后的分类色板:色相均匀拉开、相邻序差异最大,全部取自 Fiori 官方 Category / Accent 色(最后一色为品牌金)。

${CHART.map(([c, n, s], i) => `
${i + 1} · ${n}
${c}
`).join('')}
const COLORS = ['#0057D2', '#E76500', '#188918', '#DF1278', '#07838F', '#D27700', '#7858FF', '#D20A0A', '#5B738B', '#C6A15B']

序列少于 10 条时按顺序取前 N 个,不要跳着取 —— 相邻色的差异是按顺序优化过的。分类色只用于「无固有顺序」的维度;有顺序的量(如时间)用单色深浅渐变。

业务语义色v1.2 定稿

「等级」是质量语义、「热度」是温度语义,两者刻意分到不同色相族,避免同屏混淆。A 级用品牌金而非红色 —— 金色表达「优质/尊贵」,与甄房墨蓝+金的调性一致。

${BIZ.map(([u, v, s]) => ``).join('')}
用途色值来源与理由
${u}${v}${s}

令牌文件

可直接下载 zhenfang-tokens.css(下列全部令牌的 CSS 变量定义),引入到项目根样式之前。

${esc(':root {\n' + cssRoot() + '\n\n' + cssFontSizes() + '\n' + cssSpace() + '\n  --zf-font: "72", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;\n}')}

十、变更记录

版本日期变更
v1.22026-09-19 新增图表分类色板与业务语义色,并在 ZhenOS-ZF 落地。
· 图表:旧 ECharts 色板逐色映射会与 #3b82f6/#ef4444 撞色,改为重配的 10 色分类色板(全部 Fiori 官方 Category/Accent 色 + 品牌金);
· 业务色:房源等级 A 由红色改为品牌金(金色表达优质,贴合甄房调性);热度 H 保留红色,与等级的质量语义区分;
· 落地范围:ZhenOS-ZF 的 WEB(ruoyi-ui)+ H5(zhenfang-uni)双端全量换肤,含主题层 9 个 SCSS、业务页面 100+ 文件、H5 tabbar 图标重着色、logo/应用图标资源替换。
v1.12026-09-19 修正 v1.0 的色值错误。v1.0 的 Fiori 色值来自二手记忆,逐条核对官方主题包后发现 16 个色值里有 10 个不准(例如把 #0064D9 写成 #0057D2、把插图/分类色 #223548 / #d5dadd / #a9b4be 当成 UI 文字与边框令牌、链接色漏了官方单独定义的 #0064D9)。
本次全部改为官方实测值并逐条标注来源变量名;文字只用两级(官方 Neutral 白底 3.34:1 不能用于正文,三级灰改为按 4.5:1 上限反推的 #5F7590);
字号阶梯改为官方 rem 值(正文 14px 而非 13px、标题 20/24px);
ElementUI 映射从 22 条补到 37 条,区分「必改 / 刻意偏离 / 自动派生」,并新增 4 条光改变量解决不了的风险项(警告按钮白字不达标、按钮悬停方向与官方相反、深色侧栏变量不够用、表格只有一种边框色);
新增「项目现有主题文件中的失效变量」一节,列出 5 个在当前 ElementUI 版本中不存在的变量写法;
CSS 变量、令牌文件、规范表格改由同一份数据生成,杜绝三处不一致。
v1.02026-09-19 色彩体系定稿为 SAP Fiori Morning Horizon;LOGO 定稿 Z 在房框内 80% 档为主 logo,派生 90/70/60 三档; 确立「品牌色与交互色分离」;首次发布组件规范、页面状态规范、长时使用约束与 ElementUI 变量映射。

本页由 _design-test/spec/build-spec.js 生成。LOGO 数据源自 mark-scales.json 与 final-z.json; Fiori 色值源自 @sap-theming/theming-base-content 的 sap_horizon/css_variables.css; ElementUI 变量名与默认值源自 element-ui@2.15.14 的 theme-chalk/src/common/var.scss。

`; fs.writeFileSync(path.join(HERE, 'index.html'), html); // ── 令牌文件(与页面同源生成)──────────────────────────────────── const tokensCss = `/* 甄房业务系统 · 设计令牌 v1.1(定稿 2026-09-19) * 色彩体系:SAP Fiori Morning Horizon(官方实测值) * 引入顺序:本文件应在项目根样式之前引入 * 规则:界面实现一律使用 var(--zf-*) ,禁止直接写十六进制色值 * 来源:@sap-theming/theming-base-content → sap_horizon/css_variables.css */ :root { ${cssRoot()} /* ── 字号(官方 rem 值,根字号 16px)── */ ${cssFontSizes()} /* ── 间距(4pt 基数)── */ ${cssSpace()} /* ── 字族 ── */ --zf-font: "72", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; } `; fs.writeFileSync(path.join(HERE, 'zhenfang-tokens.css'), tokensCss); console.log('已生成 spec/index.html 与 spec/zhenfang-tokens.css'); console.log(' 令牌组', GROUP.length, '| 令牌总数', TOKENS.length, '| 字号档', TYPE.length, '| ElementUI 映射', ELMAP.length, '| 风险项', RISKS.length, '| 失效变量', DEADVARS.length);