850 lines
63 KiB
JavaScript
850 lines
63 KiB
JavaScript
// 甄房业务系统 · 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) =>
|
||
'<svg viewBox="0 0 64 64" width="' + size + '" height="' + size + '">' +
|
||
'<g transform="' + FG + '"><path d="' + H04 + '" fill="none" stroke="' + ink +
|
||
'" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></g>' +
|
||
'<g transform="' + zScale(PCT[pct].scale) + '"><path d="' + FZ.final + '" fill="' + ink + '"/></g></svg>';
|
||
|
||
// ═══════════════════════════════════════════════════════════════
|
||
// 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]
|
||
]],
|
||
['交互层', '主色取官方强调按钮底色;悬停加深一档;<b>按下态官方是「白底蓝字蓝框」</b>,不是更深的蓝', [
|
||
['--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]]
|
||
]],
|
||
['表面层', '列表/表格悬停是<b>中性灰</b>,选中才是浅蓝 —— 与「悬停也用蓝」的常见做法不同', [
|
||
['--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]
|
||
]],
|
||
['边框', '分两类:<b>功能性描边</b>(输入框,深色保证可辨识)与<b>分隔线</b>(表格卡片,浅色)', [
|
||
['--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]]
|
||
]],
|
||
['语义色', '一律「描边 + 浅底」,不用实心高饱和色块。<b>只有成功/错误能直接当正文色</b>,警告与中性只能作图形', [
|
||
['--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 默认一致,<b>无需改动</b>'],
|
||
['$--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', '<b>必改</b>:默认是纯白,不改则表头与内容无法区分'],
|
||
['$--table-row-hover-background-color', '--zf-bg-hover', '#EAECEE', 'm', '<b>必改</b>:默认继承 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', '由 <code>mix($--color-white, $--color-primary, N*10%)</code> 基于覆盖后的主色计算,<b>顺序正确即无需覆盖</b>:必须在 @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 也没有橙底白字的实心按钮)。两条出路:① 橙色只用于描边/文字按钮;② 保留实心橙底但把 <code>$--button-warning-font-color</code> 改成深色 <code>#131E29</code>(对 #E76500 为 4.6:1)'],
|
||
['按钮悬停方向与官方相反', 'ElementUI 的主按钮悬停 = <code>mix(白, 主色, 20%)</code> 即<b>变浅</b>,按下 = <code>mix(黑, 主色, 10%)</code> 变深;而 Fiori 是悬停 <b>#0064D9 变深</b>、按下变白底蓝框。二者不可兼得:<br>· 若照 ElementUI 走,只需改色,但规范里「悬停 #0064D9」不成立,按钮观感偏「淡」;<br>· 若照 Fiori 走,必须给 <code>.el-button--primary</code> 写自定义 hover/active 样式(变量层面做不到)。<br><b>建议按 Fiori 走并写自定义样式</b>,否则主按钮的悬停反馈会与全站其余悬停(深一档)不一致'],
|
||
['深色侧栏变量不够用', 'ElementUI 只暴露 4 个菜单变量(<code>$--menu-background-color</code>、<code>$--menu-item-font-color</code>、<code>$--menu-item-hover-fill</code>、<code>$--menu-item-font-size</code>)。改成墨蓝底后至少还要把 <code>$--menu-item-font-color</code> 设为白色(否则默认 #131E29 在墨蓝上几乎看不见),且子菜单底色、选中态仍无变量可用,必须补自定义 CSS'],
|
||
['表格只有一种边框色', 'ElementUI 全表共用 <code>$--table-border-color</code>,做不出「外框深、内线浅」的两级分隔。这不是配置问题,要两级就必须写自定义样式']
|
||
];
|
||
|
||
// 项目现有主题文件里已经失效的变量(实测 ElementUI 2.15.14 中不存在)
|
||
const DEADVARS = [
|
||
['$--color-primary-dark-2', 'Element Plus 才有此变量;ElementUI 的按下态由 <code>mix(黑, 主色, 10%)</code> 算出'],
|
||
['$--border-radius-round', '不存在;ElementUI 只有 <code>$--border-radius-circle: 100%</code>'],
|
||
['$--table-header-bg', '不存在;正确名为 <code>$--table-header-background-color</code>'],
|
||
['$--table-row-hover-bg', '不存在;正确名为 <code>$--table-row-hover-background-color</code>'],
|
||
['$--table-current-row-bg', '不存在;正确名为 <code>$--table-current-row-background-color</code>']
|
||
];
|
||
|
||
// 图表分类色板(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, '<').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]) => `
|
||
<h3>${g}</h3>
|
||
<p class="note" style="margin:0 0 4px">${desc}</p>
|
||
<table class="doc"><thead><tr><th>令牌</th><th>取值</th><th>官方来源</th><th>用途</th><th>对比度</th></tr></thead><tbody>
|
||
${list.map(([n, v, src, use, c, dev]) => `<tr>
|
||
<td class="k">${n}</td>
|
||
<td class="v"><span class="chip" style="background:${v}"></span>${v}</td>
|
||
<td class="s">${src}</td>
|
||
<td class="u">${use}${dev ? '<br><span style="color:#f0a55c">⚠ 偏离官方:' + dev + '</span>' : ''}</td>
|
||
<td>${c ? '<span class="cc" data-fg="' + v + '" data-bg="' + c[0] + '" data-req="' + c[1] + '"></span>' : '—'}</td>
|
||
</tr>`).join('')}
|
||
</tbody></table>`).join('');
|
||
|
||
// ═══════════════════════════════════════════════════════════════
|
||
const html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
|
||
<title>甄房业务系统 · UI 规范与标准(定稿 v1.2)</title>
|
||
<style>${CSS}</style></head><body><div class="wrap">
|
||
|
||
<div class="hdr">
|
||
<h1>甄房业务系统 · <b>UI 规范与标准</b></h1>
|
||
<div class="meta">
|
||
<span>版本 <b>v1.2(定稿)</b></span>
|
||
<span>色彩体系 <b>SAP Fiori Morning Horizon</b></span>
|
||
<span>LOGO <b>Forte Z + 甄房墨蓝 · Z 占比 80%</b></span>
|
||
<span>UI 库 <b>element-ui 2.15.14(Vue 2.6)</b></span>
|
||
<span>日期 <b>2026-09-19</b></span>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="note" style="margin-top:18px">
|
||
<b>本页是该项目的唯一权威 UI 标准。</b>任何界面实现以本页的令牌名与取值为准;不要直接写十六进制色值,不要引入第二套色彩。<br>
|
||
<b>色值来源纪律</b>:全部 Fiori 色值取自官方主题包
|
||
<code>@sap-theming/theming-base-content → sap_horizon/css_variables.css</code> 实测,ElementUI 变量名与默认值取自
|
||
<code>element-ui@2.15.14 → theme-chalk/src/common/var.scss</code> 实测,逐条标注官方来源变量名,<b>无记忆引用</b>。
|
||
对比度由页面运行时真实计算(WCAG 2.1)。
|
||
</p>
|
||
|
||
<h2>一、品牌资产 · LOGO<em>已定稿</em></h2>
|
||
<p class="note">主 logo 采用 <b>Z 在房框内 80% 档</b>;房框与描边粗细恒定,四档只缩放框内 Z。全部资产为矢量转曲路径。</p>
|
||
<div class="stage">
|
||
<div class="row" style="gap:26px">
|
||
<div style="text-align:center">${mark(80, '#0C2340', 88)}<div style="font-size:11px;color:#8b94a7;margin-top:8px">主 logo · 浅底</div></div>
|
||
<div style="text-align:center;background:#0C2340;padding:14px;border-radius:10px">${mark(80, '#FFFFFF', 88)}<div style="font-size:11px;color:rgba(255,255,255,.6);margin-top:8px">主 logo · 深底反白</div></div>
|
||
<div style="text-align:center">${mark(80, '#C6A15B', 88)}<div style="font-size:11px;color:#8b94a7;margin-top:8px">金色点缀版(限定场景)</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>四档占比</h3>
|
||
<table class="doc"><thead><tr><th>档位</th><th>Z 外接框(64 网格)</th><th>相对主 logo</th><th>用途</th></tr></thead><tbody>
|
||
${MS.stages.map(s => {
|
||
const use = { 90: '需要更强视觉重量时:小图标位、深色底上的白标(不要用于 32px 以下)',
|
||
80: '<b>主 logo,一律默认用这一档</b>', 70: '大幅面(≥1200px)与印刷物料',
|
||
60: '名片、信封、大留白版式' }[s.pct];
|
||
return `<tr${s.pct === 80 ? ' class="hi"' : ''}>
|
||
<td class="k">${s.pct}%${s.pct === 80 ? ' ★' : ''}</td>
|
||
<td class="v">${s.size[0].toFixed(1)} × ${s.size[1].toFixed(1)}</td>
|
||
<td class="v">×${s.vsMain.toFixed(2)}</td><td class="u">${use}</td></tr>`;
|
||
}).join('')}
|
||
</tbody></table>
|
||
|
||
<h3>规则</h3>
|
||
<table class="doc"><thead><tr><th>项</th><th>标准</th></tr></thead><tbody>
|
||
<tr><td class="k">配色</td><td class="u">标识只用 <b>#0C2340</b>(浅底)或 <b>#FFFFFF</b>(深底)。<b style="color:#f0a55c">严禁使用交互蓝 #0070F2 作为标识色</b> —— 否则用户无法区分「品牌标记」与「可点元素」。<br>注意:v1.1 起链接色已改为 #0064D9,标识色也<b>不要</b>用它</td></tr>
|
||
<tr><td class="k">最小尺寸</td><td class="u">图标主形 ≥ 24px;横版锁定标 ≥ 120px 宽。16px 场景用 favicon 专用版,不要硬缩主形</td></tr>
|
||
<tr><td class="k">安全留白</td><td class="u">四周留白 ≥ 图标高度的 25%(64 网格里留 16)</td></tr>
|
||
<tr><td class="k">深色场景</td><td class="u">用反白版;不要在深底上用墨蓝正片</td></tr>
|
||
<tr><td class="k">禁止</td><td class="u">拉伸变形 · 改比例 · 加描边/阴影/发光 · 换非品牌色 · 改动框与 Z 的相对大小 · 在花哨照片上直接压标</td></tr>
|
||
</tbody></table>
|
||
|
||
<div class="dont">
|
||
<div><div class="pv"><div style="transform:scaleX(1.5)">${mark(80, '#0C2340', 62)}</div></div>拉伸变形</div>
|
||
<div><div class="pv"><div style="transform:rotate(-12deg)">${mark(80, '#0C2340', 62)}</div></div>旋转</div>
|
||
<div><div class="pv">${mark(80, '#0070F2', 62)}</div>用交互蓝作标识色</div>
|
||
<div><div class="pv"><div style="filter:drop-shadow(2px 2px 3px rgba(0,0,0,.55))">${mark(80, '#0C2340', 62)}</div></div>加阴影</div>
|
||
<div><div class="pv">${mark(90, '#0C2340', 62)}</div>在 24px 以下用 90% 档(易糊线)</div>
|
||
<div><div class="pv" style="background:#9aa4b8">${mark(80, '#0C2340', 62)}</div>在中间调灰底上用墨蓝</div>
|
||
</div>
|
||
|
||
<h2>二、色彩系统<em>SAP Fiori Morning Horizon · 官方实测值</em></h2>
|
||
<p class="note">
|
||
核心策略:<b>品牌色与交互色分离</b>。甄房墨蓝 <b>#0C2340</b> 承担品牌与导航,Fiori 的 <b>#0070F2</b> 承担交互反馈。
|
||
两者的明度差足够大 —— 墨蓝在界面里读作「近黑的中性色」,不会与亮蓝争夺注意力,也不会被误读为可点击元素。
|
||
</p>
|
||
${tokenTables}
|
||
|
||
<h3>用色配比</h3>
|
||
<table class="doc"><thead><tr><th>层</th><th>占比</th><th>说明</th></tr></thead><tbody>
|
||
<tr><td class="k">中性(白 / 灰)</td><td class="v">≈ 75%</td><td class="u">页面基底与分隔靠灰阶承担,这是数据密集型界面不累的前提</td></tr>
|
||
<tr><td class="k">品牌墨蓝</td><td class="v">≈ 15%</td><td class="u">侧栏与页头;形成稳定视觉锚点,让导航边界一眼可辨</td></tr>
|
||
<tr><td class="k">交互蓝</td><td class="v">≈ 7%</td><td class="u">主操作、链接、选中态。一屏内主操作建议 ≤ 1 个</td></tr>
|
||
<tr><td class="k">语义色</td><td class="v">≈ 2%</td><td class="u">状态标签、提示条。一律描边+浅底,不用实心块</td></tr>
|
||
<tr><td class="k">品牌金</td><td class="v">≤ 1%</td><td class="u">只放在「点」上:选中指示条、关键数字。铺面即俗</td></tr>
|
||
</tbody></table>
|
||
|
||
<h2>三、字体与文字层级<em>官方 rem 值,根字号 16px</em></h2>
|
||
<p class="note">
|
||
字族:<code>--zf-font</code> = "72", "PingFang SC", "Microsoft YaHei", …("72" 为 Fiori 官方字族,未安装时回落系统中文黑体)。
|
||
数字一律加 <code>font-variant-numeric: tabular-nums</code>。
|
||
</p>
|
||
<div class="stage">
|
||
${TYPE.map(([n, sz, w, lh, src, u]) => `
|
||
<div style="display:flex;align-items:baseline;gap:14px;padding:9px 0;border-bottom:1px solid #E5E5E5">
|
||
<code style="width:104px;color:#5F7590;font-size:11px;flex:none">--zf-fs-${n}</code>
|
||
<span style="font-size:${sz}px;font-weight:${w};line-height:${lh};flex:1;color:#131E29">甄房 · 二手房房源 1,284 套</span>
|
||
<span style="font-size:11px;color:#5F7590;width:250px;text-align:right;flex:none">${sz}px / ${w} / 行高 ${lh} · ${u}</span>
|
||
</div>
|
||
<div style="font-size:10.5px;color:#8b94a7;padding:0 0 7px 104px;font-family:Menlo,Consolas,monospace">${src}</div>`).join('')}
|
||
</div>
|
||
<p class="note">
|
||
<b>只有 6 档,不要再加。</b>中文最小 11px(caption)—— 再小在 Windows 上会糊。
|
||
</p>
|
||
|
||
<h2>四、间距 · 圆角 · 阴影</h2>
|
||
<h3>间距(4pt 基数)</h3>
|
||
<div class="stage plain">
|
||
<div class="row" style="align-items:flex-end;gap:14px">
|
||
${SPACE.map(v => `<div style="text-align:center">
|
||
<div style="width:${v}px;height:${v}px;background:#0070F2;border-radius:2px;margin:0 auto 6px"></div>
|
||
<code style="font-size:10.5px;color:#5F7590">${v}</code></div>`).join('')}
|
||
</div>
|
||
<p class="note" style="margin:14px 0 0">
|
||
常用:控件内边距 <b>8 / 11 / 16</b>;表单行间距 <b>12</b>;卡片内边距 <b>16</b>;区块间距 <b>20 / 24</b>;页面左右边距 <b>18 / 24</b>。
|
||
不要出现 5、7、9、13 这类非 4 倍数。
|
||
</p>
|
||
</div>
|
||
|
||
<h3>圆角与阴影</h3>
|
||
<div class="stage">
|
||
<div class="row" style="align-items:stretch">
|
||
<div class="card" style="flex:1;border-radius:4px"><code style="font-size:11px;color:#5F7590">4px · 输入类</code><div style="font-size:12px;color:#556B82;margin-top:6px">输入框、下拉、标签、复选框</div></div>
|
||
<div class="card" style="flex:1"><code style="font-size:11px;color:#5F7590">8px · 基准</code><div style="font-size:12px;color:#556B82;margin-top:6px">按钮、卡片</div></div>
|
||
<div class="card" style="flex:1;border-radius:12px"><code style="font-size:11px;color:#5F7590">12px</code><div style="font-size:12px;color:#556B82;margin-top:6px">弹窗、抽屉、大容器</div></div>
|
||
<div class="card" style="flex:1;border-radius:16px"><code style="font-size:11px;color:#5F7590">16px · 磁贴</code><div style="font-size:12px;color:#556B82;margin-top:6px">大卡片、看板磁贴</div></div>
|
||
<div class="card" style="flex:1;border-radius:999px"><code style="font-size:11px;color:#5F7590">pill</code><div style="font-size:12px;color:#556B82;margin-top:6px">状态标签、头像</div></div>
|
||
</div>
|
||
<p class="note" style="margin:14px 0 0">
|
||
阴影只用两级:<code>--zf-shadow-1</code> 卡片默认、<code>--zf-shadow-2</code> 浮层。
|
||
<b>分隔优先用边框而不是阴影</b> —— 一屏几十张卡片都带阴影会整体发糊。
|
||
</p>
|
||
</div>
|
||
|
||
<h2>五、组件规范</h2>
|
||
|
||
<h3>按钮</h3>
|
||
<div class="stage">
|
||
<div class="row">
|
||
<button class="btn pri">主操作</button>
|
||
<button class="btn pri h">悬停</button>
|
||
<button class="btn pri a">按下</button>
|
||
<button class="btn gho">次要操作</button>
|
||
<button class="btn">普通按钮</button>
|
||
<button class="btn tx">文字按钮</button>
|
||
<button class="btn dan">危险操作</button>
|
||
<button class="btn dis">禁用</button>
|
||
<button class="btn pri sm">小号主操作</button>
|
||
</div>
|
||
<p class="note" style="margin:16px 0 0">
|
||
<b>「按下」是白底蓝字蓝框</b> —— 这是 Fiori 官方定义(<code>sapButton_Emphasized_Active_Background: #fff</code> + 蓝字蓝框),不是做错了。
|
||
其余状态:悬停 <code>#0064D9</code> 加深一档、禁用灰底。
|
||
</p>
|
||
<table class="doc" style="margin-top:14px"><thead><tr><th>类型</th><th>用途</th><th>一屏数量</th></tr></thead><tbody>
|
||
<tr><td class="k">主操作 pri</td><td class="u">推动流程前进的唯一动作:查询、提交、保存、确认</td><td class="v">≤ 1</td></tr>
|
||
<tr><td class="k">次要 gho</td><td class="u">辅助动作:高级筛选、批量导入</td><td class="v">≤ 2</td></tr>
|
||
<tr><td class="k">普通 / 文字</td><td class="u">重置、取消、查看详情</td><td class="v">不限</td></tr>
|
||
<tr><td class="k">危险 dan</td><td class="u">删除、作废、强制回收;必须二次确认</td><td class="v">≤ 1</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
|
||
<h3>表单</h3>
|
||
<div class="stage">
|
||
<div class="row" style="align-items:flex-start;gap:18px">
|
||
<div class="fld" style="width:200px"><label>房源编码</label><input placeholder="SH-ES-202609-…"><span class="hint">支持模糊匹配</span></div>
|
||
<div class="fld" style="width:200px"><label>所属楼盘<span class="req">*</span></label><select><option>滨江壹号</option></select><span class="hint">必填</span></div>
|
||
<div class="fld err" style="width:200px"><label>维护人<span class="req">*</span></label><input value="张"><span class="hint">请输入完整姓名(至少 2 个字)</span></div>
|
||
<div class="fld dis" style="width:200px"><label>房源编号(系统生成)</label><input value="SH-ES-202609-00037" disabled><span class="hint">不可编辑</span></div>
|
||
</div>
|
||
<p class="note" style="margin:16px 0 0">
|
||
标签在上、控件在下的纵向排布。标签 12px 不加重。错误态只改边框与提示文字色,<b>不要给整个字段加红底</b>。<br>
|
||
控件描边用 <code>--zf-border-field #556B81</code>(深色)—— 这是 Horizon 的特色,输入框「有框」才看得清;分隔线才用浅色 <code>#E5E5E5</code>。两者不可混用。
|
||
</p>
|
||
</div>
|
||
|
||
<h3>数据表格</h3>
|
||
<div class="stage" style="padding:0;overflow:hidden">
|
||
<table class="tbl">
|
||
<thead><tr><th>房源编码</th><th>楼盘 / 房号</th><th class="num">面积</th><th class="num">售价</th><th>状态</th><th>维护人</th><th>操作</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="lk">SH-ES-202609-00037</td><td>滨江壹号 3幢 1802<span class="sub">滨江区 · 钱塘江畔</span></td><td class="num">128.5 ㎡</td><td class="num">1,285 万</td><td><span class="tg ok">有效</span></td><td>张伟</td><td class="act"><a>查看</a><a>编辑</a></td></tr>
|
||
<tr class="hov"><td class="lk">SH-ES-202609-00041</td><td>钱江新城 7幢 2201<span class="sub">上城区 · 万象城</span></td><td class="num">156.0 ㎡</td><td class="num">1,860 万</td><td><span class="tg pr">待检核</span></td><td>李敏</td><td class="act"><a>查看</a><a>编辑</a></td></tr>
|
||
<tr class="sel"><td class="lk">SH-ES-202609-00052</td><td>滨江壹号 5幢 901<span class="sub">滨江区 · 钱塘江畔</span></td><td class="num">98.2 ㎡</td><td class="num">920 万</td><td><span class="tg gd">独家</span></td><td>王强</td><td class="act"><a>查看</a><a>编辑</a></td></tr>
|
||
<tr><td class="lk">SH-ES-202609-00058</td><td>绿城桂语 2幢 1503<span class="sub">西湖区 · 文三路</span></td><td class="num">142.6 ㎡</td><td class="num">1,420 万</td><td><span class="tg wn">维护中</span></td><td>张伟</td><td class="act"><a>查看</a><a>编辑</a></td></tr>
|
||
<tr><td class="lk">SH-ES-202609-00063</td><td>万科未来 1幢 608<span class="sub">余杭区 · 未来科技城</span></td><td class="num">89.0 ㎡</td><td class="num">610 万</td><td><span class="tg er">已下架</span></td><td>赵磊</td><td class="act"><a>查看</a><a class="dan">删除</a></td></tr>
|
||
</tbody></table>
|
||
<div style="padding:13px 16px;font-size:12px;color:#556B82;display:flex;align-items:center">
|
||
共 1,284 条<span class="pg on" style="margin-left:16px">1</span><span class="pg">2</span><span class="pg">3</span><span class="pg">…</span><span class="pg">65</span>
|
||
<span style="margin-left:auto;font-size:11.5px">每页 20 条</span></div>
|
||
</div>
|
||
<table class="doc" style="margin-top:14px"><thead><tr><th>项</th><th>标准</th></tr></thead><tbody>
|
||
<tr><td class="k">斑马纹</td><td class="u">偶数行 <code>--zf-bg-sub #F5F6F7</code>,与白底差值仅 2–3%。<b>这是数据表最高频的疲劳源</b>,不要加大对比</td></tr>
|
||
<tr><td class="k">悬停 / 选中</td><td class="u">悬停是<b>中性灰</b> <code>#EAECEE</code>,选中才是<b>浅蓝</b> <code>#EBF8FF</code> —— 官方如此,两者不要都做成蓝。选中行左侧加 3px 主色竖条,长表格滚动后仍可辨认</td></tr>
|
||
<tr><td class="k">数字列</td><td class="u">右对齐 + tabular-nums;金额带千分位,单位写表头(「售价(万)」)不逐行重复</td></tr>
|
||
<tr><td class="k">操作列</td><td class="u">固定最后一列,文字链 ≤ 3 个;破坏性动作放最后并标红</td></tr>
|
||
<tr><td class="k">边框</td><td class="u">全表只有一种线色 <code>#E5E5E5</code>(ElementUI 也只暴露一个变量)。想要「外框深、内线浅」必须写自定义样式</td></tr>
|
||
</tbody></table>
|
||
|
||
<h3>状态标签</h3>
|
||
<div class="stage">
|
||
<div class="row">
|
||
<span class="tg ok">有效</span><span class="tg wn">维护中</span><span class="tg er">已下架</span>
|
||
<span class="tg pr">待检核</span><span class="tg nu">草稿</span><span class="tg gd">独家</span>
|
||
</div>
|
||
<p class="note" style="margin:14px 0 0">
|
||
一律 <b>描边 + 浅底</b>,圆角 pill,字号 11。<br>
|
||
<b>注意警告标签的文字色是 #B44F00 而不是 #E76500</b> —— 官方 Critical 色在白底只有 3.36:1,直接当文字色不达标;
|
||
底色仍用 <code>#E76500</code> 描边,文字压深一档。<b>不要</b>用白字压在 #E76500 上(3.36:1,同样不达标)。
|
||
</p>
|
||
</div>
|
||
|
||
<h3>页签 · 分页</h3>
|
||
<div class="stage">
|
||
<div class="tabs"><a class="on">全部 1284</a><a>有效 892</a><a>维护中 236</a><a>已成交 121</a><a>暂缓 35</a></div>
|
||
<div style="margin-top:14px">
|
||
<span class="pg on">1</span><span class="pg">2</span><span class="pg">3</span><span class="pg">…</span><span class="pg">65</span>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>提示条</h3>
|
||
<div class="stage">
|
||
<div class="al i"><span>ℹ</span><span><b>提示:</b>该房源委托期将于 7 天后到期,请及时续签。</span></div>
|
||
<div class="al s"><span>✓</span><span><b>成功:</b>房源 SH-ES-202609-00037 已提交检核。</span></div>
|
||
<div class="al w"><span>⚠</span><span><b>警告:</b>钥匙编号与系统记录不一致,请核对后重试。</span></div>
|
||
<div class="al e"><span>✕</span><span><b>错误:</b>售价不能低于政府指导价,请修改后重试。</span></div>
|
||
</div>
|
||
|
||
<h3>卡片 · KPI · 页头导航</h3>
|
||
<div class="stage plain">
|
||
<div class="row" style="align-items:stretch;margin-bottom:16px">
|
||
<div class="kpi" style="flex:1"><div class="t">在售房源</div><div class="v">1,284<em>套</em></div><div class="d">↑ 3.2% 较上周</div></div>
|
||
<div class="kpi" style="flex:1"><div class="t">本周新增客源</div><div class="v">96<em>位</em></div><div class="d">↑ 12 位</div></div>
|
||
<div class="kpi" style="flex:1"><div class="t">待跟进</div><div class="v">23<em>条</em></div><div class="d dn">↓ 5 条</div></div>
|
||
<div class="kpi gold" style="flex:1"><div class="t">本月预估佣金</div><div class="v">86.4<em>万</em></div><div class="d">↑ 8.6%</div></div>
|
||
</div>
|
||
<div class="app">
|
||
<aside class="side">
|
||
<div class="brand">${mark(80, '#FFFFFF', 30)}<span><b>甄房</b><em>ZHENFANG</em></span></div>
|
||
<a class="on"><span class="dot"></span>工作台</a>
|
||
<a><span class="dot"></span>房源管理</a>
|
||
<a><span class="dot"></span>客源管理</a>
|
||
<a><span class="dot"></span>交易管理</a>
|
||
<a><span class="dot"></span>钥匙管理</a>
|
||
<a><span class="dot"></span>我的佣金</a>
|
||
</aside>
|
||
<div class="main">
|
||
<div class="head">
|
||
<div class="cb">房源管理 / <b>二手房房源</b></div>
|
||
<div class="usr"><span class="av">张</span>张伟 · 滨江门店</div>
|
||
</div>
|
||
<div class="bd" style="font-size:12px;color:#5F7590">内容区底色 <code>--zf-bg-sub</code>,卡片用白底与 1px 边框区分层级</div>
|
||
</div>
|
||
</div>
|
||
<p class="note" style="margin:14px 0 0">
|
||
侧栏固定用 <b>墨蓝 #0C2340</b>。这是本项目<b>刻意偏离 Fiori 官方 Shell</b> 的地方(官方 <code>sapShell_Background</code> 是浅色 <code>#EFF1F2</code>)——
|
||
为了减少整屏亮度、形成稳定视觉锚点。浅灰侧栏在长时间使用中会与内容区「糊在一起」。
|
||
选中项用金色 3px 左指示条 + 10% 白底。<b>代价是 ElementUI 的菜单变量不够用,需补自定义样式(见第九章)。</b>
|
||
</p>
|
||
</div>
|
||
|
||
<h2>六、页面状态</h2>
|
||
<div class="stage plain">
|
||
<div class="row" style="align-items:stretch;gap:14px">
|
||
<div class="st" style="flex:1;background:#fff"><span class="ic">⏳</span><b>加载中</b>用骨架屏(灰条),不要整页转圈</div>
|
||
<div class="st" style="flex:1;background:#fff"><span class="ic">📭</span><b>没有数据</b>「还没有房源,点右上角新增」+ 主操作按钮</div>
|
||
<div class="st" style="flex:1;background:#fff"><span class="ic">🔍</span><b>筛选无结果</b>「没有符合条件的房源,试试放宽条件」+ 重置按钮</div>
|
||
<div class="st" style="flex:1;background:#fff"><span class="ic">⚠</span><b>加载失败</b>「数据加载失败」+ 重试按钮 + 错误码</div>
|
||
<div class="st" style="flex:1;background:#fff"><span class="ic">🔒</span><b>无权限</b>「你没有查看该门店房源的权限」+ 申请入口</div>
|
||
</div>
|
||
<div class="card" style="margin-top:14px">
|
||
<div style="font-size:12px;color:#5F7590;margin-bottom:9px">骨架屏示例</div>
|
||
<div class="sk" style="width:38%"></div>
|
||
<div class="sk" style="width:72%"></div>
|
||
<div class="sk" style="width:56%"></div>
|
||
</div>
|
||
<p class="note" style="margin:14px 0 0">
|
||
五种状态都必须单独设计。「没有数据」与「筛选无结果」文案必须不同 —— 否则用户会以为自己的数据丢了。
|
||
</p>
|
||
</div>
|
||
|
||
<h2>七、长时使用约束<em>针对经纪人每天连续使用 6–8 小时</em></h2>
|
||
<table class="doc"><thead><tr><th>约束</th><th>做法</th><th>解决什么</th></tr></thead><tbody>
|
||
<tr><td class="k">深色品牌侧栏</td><td class="u">侧栏统一 <code>#0C2340</code>,不用浅灰(偏离官方 Shell 浅色)</td><td class="u">形成稳定视觉锚点;浅灰侧栏会与内容区糊在一起</td></tr>
|
||
<tr><td class="k">斑马纹极低对比</td><td class="u">条纹 <code>#F5F6F7</code>,与白底差值 2–3%</td><td class="u">帮助横向追行不串行,又不产生条纹眩光</td></tr>
|
||
<tr><td class="k">语义色降饱和</td><td class="u">描边+浅底,不用实心块;警告文字用深一档的 #B44F00</td><td class="u">一屏几十个状态标签时的最大刺眼源</td></tr>
|
||
<tr><td class="k">悬停与选中分工</td><td class="u">悬停中性灰 <code>#EAECEE</code>、选中浅蓝 <code>#EBF8FF</code></td><td class="u">长时间划动列表时,中性灰比蓝底更不累眼;蓝色只留给「已选定」这一个强语义</td></tr>
|
||
<tr><td class="k">金色只做点</td><td class="u">占比 ≤1%,只用于指示条与关键数字</td><td class="u">保住品牌质感,避免大面积金色显廉价</td></tr>
|
||
<tr><td class="k">正文不用纯黑</td><td class="u">正文 <code>#131E29</code> 而非 <code>#000</code></td><td class="u">降低硬反差带来的持续眼疲劳</td></tr>
|
||
</tbody></table>
|
||
|
||
<h2>八、无障碍标准</h2>
|
||
<p class="note">门槛:正文文字 ≥ 4.5:1,大字与图形/控件边界 ≥ 3:1。下表全部由运行时真实计算。</p>
|
||
<table class="doc"><thead><tr><th>检查项</th><th>前景 / 背景</th><th>实测</th><th>门槛</th><th>结论</th></tr></thead><tbody>
|
||
${A11Y.map(([n, f, b, r]) => `<tr><td class="u">${n}</td><td class="v"><span class="chip" style="background:${f}"></span>${f} / <span class="chip" style="background:${b}"></span>${b}</td>
|
||
<td class="cc" data-fg="${f}" data-bg="${b}" data-req="${r}"></td><td class="v">≥${r}:1</td>
|
||
<td><span class="vc" data-fg="${f}" data-bg="${b}" data-req="${r}"></span></td></tr>`).join('')}
|
||
</tbody></table>
|
||
|
||
<h3>其他无障碍要求</h3>
|
||
<table class="doc"><thead><tr><th>项</th><th>标准</th></tr></thead><tbody>
|
||
<tr><td class="k">焦点态</td><td class="u">所有可交互元素必须有可见焦点环:<code>border-color: --zf-primary</code> + <code>box-shadow: 0 0 0 3px --zf-bg-selected</code>。禁止 <code>outline:none</code> 而不给替代</td></tr>
|
||
<tr><td class="k">触达尺寸</td><td class="u">可点区域 ≥ 32×32px(H5 ≥ 44×44px)</td></tr>
|
||
<tr><td class="k">不靠颜色单独表意</td><td class="u">状态标签必须带文字,不能只有色点;图表必须带数据标签或图例</td></tr>
|
||
<tr><td class="k">键盘可达</td><td class="u">Tab 顺序符合视觉顺序;弹窗打开后焦点移入并锁定,Esc 关闭后归还触发元素</td></tr>
|
||
<tr><td class="k">动效</td><td class="u">过渡 ≤ 200ms;尊重 <code>prefers-reduced-motion</code></td></tr>
|
||
</tbody></table>
|
||
|
||
<h2>九、落地清单<em>element-ui 2.15.14 变量映射 · 已逐条核对官方源码</em></h2>
|
||
<p class="note">
|
||
项目前端为 <b>RuoYi-Vue + element-ui 2.15.14 + Vue 2.6 + sass</b>,变量覆盖写在
|
||
<code>ruoyi-ui/src/assets/styles/element-variables.scss</code>(须在 <code>@import "~element-ui/packages/theme-chalk/src/index"</code> <b>之前</b>定义)。<br>
|
||
下表 <span class="tag2 m">必改</span> = 不改主题就是半成品 ·
|
||
<span class="tag2 d">刻意偏离</span> = 与官方取值不同,已在行内写明原因 ·
|
||
<span class="tag2 a">自动派生</span> = ElementUI 会基于主色自动算出,只要定义顺序正确就无需覆盖。
|
||
</p>
|
||
<table class="doc"><thead><tr><th>ElementUI 变量</th><th>本项目令牌</th><th>取值</th><th>类别</th><th>说明</th></tr></thead><tbody>
|
||
${ELMAP.map(([e, t, v, k, note]) => `<tr>
|
||
<td class="k">${e}</td><td class="k">${t}</td>
|
||
<td class="v"><span class="chip" style="background:${v.indexOf('#') === 0 ? v : 'transparent'}"></span>${v}</td>
|
||
<td><span class="tag2 ${k}">${k === 'm' ? '必改' : k === 'd' ? '刻意偏离' : '自动派生'}</span></td>
|
||
<td class="u">${note || '—'}</td></tr>`).join('')}
|
||
</tbody></table>
|
||
|
||
<h3>光改变量解决不了的风险项</h3>
|
||
${RISKS.map(([t, d]) => `<div class="risk"><b>${t}</b><br>${d}</div>`).join('')}
|
||
|
||
<h3>项目现有主题文件中的失效变量</h3>
|
||
<p class="note">
|
||
现有 <a class="dl" href="../../ruoyi-ui/src/assets/styles/element-variables.scss">element-variables.scss</a> 里下面这些变量在 element-ui 2.15.14 中<b>并不存在</b>,
|
||
Sass 不会报错、但也不会生效 —— 属于「以为改了其实没改」。同样问题也存在于另一个主题体系(橙色那套)里。
|
||
</p>
|
||
<table class="doc"><thead><tr><th>写法</th><th>实际情况</th></tr></thead><tbody>
|
||
${DEADVARS.map(([v, d]) => `<tr><td class="k" style="color:#f0a55c">${v}</td><td class="u">${d}</td></tr>`).join('')}
|
||
</tbody></table>
|
||
|
||
<h3>图表分类色板(ECharts)<span class="pill">v1.2 新增 · 最多 10 序列</span></h3>
|
||
<p class="note">
|
||
多序列图表<b>禁止把单色映射逐色套用</b> —— 旧色板里 <code>#ff7a1a</code> 映射成主色蓝后会与原有的 <code>#3b82f6</code> 撞色,
|
||
<code>#ffb31a</code>/<code>#d9481c</code> 也会与 <code>#ef4444</code> 挤在橙红区,10 条序列将失去区分度。<br>
|
||
下表是重配后的分类色板:色相均匀拉开、相邻序差异最大,全部取自 Fiori 官方 Category / Accent 色(最后一色为品牌金)。
|
||
</p>
|
||
<div class="stage" style="padding:16px 18px">
|
||
<div style="display:flex;gap:6px">
|
||
${CHART.map(([c, n, s], i) => `<div style="flex:1;text-align:center" title="${s}">
|
||
<div style="height:46px;border-radius:6px;background:${c}"></div>
|
||
<div style="font-size:10.5px;color:var(--zf-text-3);margin-top:6px">${i + 1} · ${n}</div>
|
||
<div style="font-size:9.5px;color:var(--zf-text-3);opacity:.75">${c}</div>
|
||
</div>`).join('')}
|
||
</div>
|
||
</div>
|
||
<pre>const COLORS = ['#0057D2', '#E76500', '#188918', '#DF1278', '#07838F', '#D27700', '#7858FF', '#D20A0A', '#5B738B', '#C6A15B']</pre>
|
||
<p class="note">序列少于 10 条时<b>按顺序取前 N 个</b>,不要跳着取 —— 相邻色的差异是按顺序优化过的。分类色只用于「无固有顺序」的维度;有顺序的量(如时间)用单色深浅渐变。</p>
|
||
|
||
<h3>业务语义色<span class="pill">v1.2 定稿</span></h3>
|
||
<p class="note">「等级」是质量语义、「热度」是温度语义,两者刻意分到不同色相族,避免同屏混淆。A 级用品牌金而非红色 —— 金色表达「优质/尊贵」,与甄房墨蓝+金的调性一致。</p>
|
||
<table class="doc"><thead><tr><th>用途</th><th>色值</th><th>来源与理由</th></tr></thead><tbody>
|
||
${BIZ.map(([u, v, s]) => `<tr><td class="u">${u}</td><td class="v"><span class="chip" style="background:${v}"></span>${v}</td><td class="u">${s}</td></tr>`).join('')}
|
||
</tbody></table>
|
||
|
||
<h3>令牌文件</h3>
|
||
<p class="note">可直接下载 <a class="dl" href="zhenfang-tokens.css" download>zhenfang-tokens.css</a>(下列全部令牌的 CSS 变量定义),引入到项目根样式之前。</p>
|
||
<pre>${esc(':root {\n' + cssRoot() + '\n\n' + cssFontSizes() + '\n' + cssSpace() + '\n --zf-font: "72", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;\n}')}</pre>
|
||
|
||
<h2>十、变更记录</h2>
|
||
<table class="doc"><thead><tr><th>版本</th><th>日期</th><th>变更</th></tr></thead><tbody>
|
||
<tr class="hi"><td class="k">v1.2</td><td class="v">2026-09-19</td><td class="u">
|
||
<b>新增图表分类色板与业务语义色,并在 ZhenOS-ZF 落地。</b><br>
|
||
· 图表:旧 ECharts 色板逐色映射会与 <code>#3b82f6</code>/<code>#ef4444</code> 撞色,改为重配的 10 色分类色板(全部 Fiori 官方 Category/Accent 色 + 品牌金);<br>
|
||
· 业务色:<b>房源等级 A 由红色改为品牌金</b>(金色表达优质,贴合甄房调性);热度 H 保留红色,与等级的质量语义区分;<br>
|
||
· 落地范围:ZhenOS-ZF 的 WEB(ruoyi-ui)+ H5(zhenfang-uni)双端全量换肤,含主题层 9 个 SCSS、业务页面 100+ 文件、H5 tabbar 图标重着色、logo/应用图标资源替换。
|
||
</td></tr>
|
||
<tr><td class="k">v1.1</td><td class="v">2026-09-19</td><td class="u">
|
||
<b>修正 v1.0 的色值错误。</b>v1.0 的 Fiori 色值来自二手记忆,逐条核对官方主题包后发现 16 个色值里有 10 个不准(例如把
|
||
<code>#0064D9</code> 写成 <code>#0057D2</code>、把插图/分类色 <code>#223548</code> / <code>#d5dadd</code> / <code>#a9b4be</code> 当成 UI 文字与边框令牌、链接色漏了官方单独定义的 <code>#0064D9</code>)。<br>
|
||
本次全部改为官方实测值并逐条标注来源变量名;文字只用两级(官方 Neutral 白底 3.34:1 不能用于正文,三级灰改为按 4.5:1 上限反推的 <code>#5F7590</code>);<br>
|
||
字号阶梯改为官方 rem 值(正文 14px 而非 13px、标题 20/24px);<br>
|
||
ElementUI 映射从 22 条补到 37 条,区分「必改 / 刻意偏离 / 自动派生」,并新增 4 条光改变量解决不了的风险项(警告按钮白字不达标、按钮悬停方向与官方相反、深色侧栏变量不够用、表格只有一种边框色);<br>
|
||
新增「项目现有主题文件中的失效变量」一节,列出 5 个在当前 ElementUI 版本中不存在的变量写法;<br>
|
||
CSS 变量、令牌文件、规范表格改由同一份数据生成,杜绝三处不一致。
|
||
</td></tr>
|
||
<tr><td class="k">v1.0</td><td class="v">2026-09-19</td><td class="u">
|
||
色彩体系定稿为 SAP Fiori Morning Horizon;LOGO 定稿 Z 在房框内 80% 档为主 logo,派生 90/70/60 三档;
|
||
确立「品牌色与交互色分离」;首次发布组件规范、页面状态规范、长时使用约束与 ElementUI 变量映射。
|
||
</td></tr>
|
||
</tbody></table>
|
||
|
||
<p class="note" style="margin-top:34px;color:#6d768a">
|
||
本页由 <code>_design-test/spec/build-spec.js</code> 生成。LOGO 数据源自 <code>mark-scales.json</code> 与 <code>final-z.json</code>;
|
||
Fiori 色值源自 <code>@sap-theming/theming-base-content</code> 的 <code>sap_horizon/css_variables.css</code>;
|
||
ElementUI 变量名与默认值源自 <code>element-ui@2.15.14</code> 的 <code>theme-chalk/src/common/var.scss</code>。
|
||
</p>
|
||
|
||
</div>
|
||
<script>
|
||
function toRgb(c){
|
||
if (c.indexOf('rgba') === 0) return c.slice(5, c.length - 1).split(',').slice(0, 3).map(Number);
|
||
var h = c.replace('#', '');
|
||
if (h.length === 3) h = h.split('').map(function(x){ return x + x; }).join('');
|
||
return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];
|
||
}
|
||
function lum(c){
|
||
var v = toRgb(c).map(function(x){ x /= 255; return x <= .03928 ? x/12.92 : Math.pow((x+.055)/1.055, 2.4); });
|
||
return .2126*v[0] + .7152*v[1] + .0722*v[2];
|
||
}
|
||
function ratio(a, b){
|
||
var l1 = lum(a), l2 = lum(b);
|
||
return (Math.max(l1,l2) + .05) / (Math.min(l1,l2) + .05);
|
||
}
|
||
var ok = 0, bad = 0;
|
||
Array.prototype.forEach.call(document.querySelectorAll('.cc'), function(el){
|
||
var r = ratio(el.dataset.fg, el.dataset.bg), req = parseFloat(el.dataset.req);
|
||
if (r >= req) { ok++; } else { bad++; }
|
||
el.innerHTML = '<span class="' + (r >= req ? 'pass' : 'fail') + '">' + r.toFixed(2) + ':1</span>';
|
||
});
|
||
Array.prototype.forEach.call(document.querySelectorAll('.vc'), function(el){
|
||
var r = ratio(el.dataset.fg, el.dataset.bg), req = parseFloat(el.dataset.req);
|
||
el.innerHTML = '<span class="' + (r >= req ? 'pass' : 'fail') + '">' + (r >= req ? '通过' : '未达标') + '</span>';
|
||
});
|
||
document.title = document.title + '(对比度 ' + ok + '/' + (ok + bad) + ' 通过)';
|
||
</script>
|
||
</body></html>`;
|
||
|
||
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);
|