运营中心 · 小改版设计规范
基于 master 分支,新增「商品」和「计价」两个原型模块(共 9 个页面), 主题色由橙 #FF921C 切换为蓝 #4362FF(与其他蓝色产品对齐), 统一侧栏图标风格(codesign → 恐龙图标库 duotone), 对齐公司设计规范的色彩 Token 体系。本页用于前端交接,记录所有改动点。
色彩规范
全局色彩通过 config/theme.ts 注入 AntD ConfigProvider,
以 CSS 变量形式输出(前缀 --alaya-*),全项目共享。
主题色 · Primary
品牌主色 #4362FF,按钮、链接、选中态、菜单激活态。本次改版从橙 #FF921C 切换到蓝色,与其他蓝色产品对齐。
中性色 · Gray
10 阶灰色,全部文字色、边框色、填充色、背景色都从此推导。
辅助色 · Orange 原主色 · 保留供 warning 状态色使用
原主色橙色降级为辅助色。状态色(待审核、待支付等)若需暖色调从此选取,不要新增其他色族。
文字层级
文字色严格使用 语义层变量(如 colorText、colorTextSecondary),
而不是直接引用 gray-10、gray-8 等调色板层级。
语义变量
边框、填充、背景的语义层级映射,确保改版时能从单一来源调整。
边框 · Border
填充 · Fill
背景 · Background
间距 Spacing
全部使用 AntD 标准 token(与 Figma 设计稿一致)。
基础尺寸 sizeUnit: 4 + sizeStep: 4,输出 11 档 Size.Base 阶梯。
Padding / Margin 都是 Base 的别名引用,外加 Content Padding / Control Padding 两组语义专用。
不要自己造 spacing* 自定义 token,组件直接消费 AntD CSS 变量。
Size.Base · 基础阶梯(11 档)
所有 Padding/Margin 都引用此层数值。CSS 变量 --alaya-size-*。
--alaya-size-xxs--alaya-size-xs--alaya-size-sm--alaya-size--alaya-size-md--alaya-size-lg--alaya-size-xl--alaya-size-xxl--alaya-size-xxxl--alaya-size-xxxxl--alaya-size-xxxxxlPadding / Margin(Base 别名)
| Token | 值 | 引用 | 用途 |
|---|---|---|---|
paddingXXS / marginXXS | 4 | sizeXXS | 极小间距 |
paddingXS / marginXS | 8 | sizeXS | 按钮 icon-文字、Space size 默认 |
paddingSM / marginSM | 12 | sizeSM | 表格单元格垂直、小尺寸 padding |
padding / margin | 16 | size | 卡片内边距、表单项间距、模块之间 |
paddingMD / marginMD | 20 | sizeMD | — |
paddingLG / marginLG | 24 | sizeLG | 大尺寸内边距 |
paddingXL / marginXL | 32 | sizeXL | — |
paddingXL2 / marginXXL | 40 | sizeXXL | 大块内容间距 |
paddingXL3 / marginXXXL | 80 | sizeXXXXL | 章节级间距 |
paddingXL4 | 120 | sizeXXXXXL | — |
Content Padding(内容专用)
内容区域专用 padding,页面水平内边距用 paddingContentHorizontalLG,标题区垂直用 paddingContentVertical。
| Token | 值 | 用途 |
|---|---|---|
paddingContentHorizontal | 16 | 内容水平 padding |
paddingContentHorizontalLG | 24 | 页面水平内边距 |
paddingContentHorizontalSM | 16 | 内容水平 SM |
paddingContentVertical | 12 | 标题区垂直 padding |
paddingContentVerticalLG | 16 | 内容垂直 LG |
paddingContentVerticalSM | 8 | 内容垂直 SM |
Control Padding(控件专用)
| Token | 值 | 用途 |
|---|---|---|
controlPaddingHorizontal | 12 | 输入框/按钮内水平 padding |
controlPaddingHorizontalSM | 8 | 小尺寸控件水平 |
使用示例
// DO:直接用 AntD 标准 CSS 变量(与 Figma 一致) .my-page { padding: 0 var(--alaya-padding-content-horizontal-lg); /* 24 */ } .my-section + .my-section { margin-top: var(--alaya-margin); /* 16 */ } .my-table-cell { padding: var(--alaya-padding-sm) var(--alaya-padding); /* 12 16 */ } // ❌ 禁止:硬编码像素 .my-page { padding: 0 24px; } // ❌ 禁止:自造 spacing* 语义 token // 不要在 theme.ts 里加 spacingPagePaddingX 之类的,AntD 标准命名已经有对应 token
paddingContentHorizontalLG(24)
③ 标题区垂直 → paddingContentVertical(12)
④ 模块之间垂直间距 → margin(16)
⑤ 永远不要硬编码像素
DinoIcons · 恐龙图标库
24 个 duotone(双色)风格 SVG 图标,统一替换原 codesign iconfont 实现侧栏图标风格。
通过 { ReactComponent } inline SVG 方式渲染,颜色跟随 currentColor,
默认 colorTextTertiary,菜单选中/hover 跟随 colorPrimary。
默认状态 · gray-6
使用方式
// 1. 在 route config 中使用 dino-* 字符串 { name: '商品', icon: 'dino-bag-shopping', path: '/ops/misc/commodity', } // 2. app.tsx 的 menuDataRender 自动转换为 React 组件 menuDataRender(menuData) { const patchDinoIcons = (items) => items.map((item) => { if (typeof item.icon === 'string' && item.icon.startsWith('dino-')) { item.icon = React.createElement(dinoIconMap[item.icon]); } return item; }); return patchDinoIcons(menuData); }
Button 内图标对齐(全局强约束)
任何 SVG 图标跟文字共存(Button icon prop / flex 容器 / inline span)必须 inline-flex + alignItems center + lineHeight 0,否则 RemixIcon / DinoIcons SVG 默认 baseline 对齐会比中文字偏下 1-2px。
src/global.ui.css):
.ant-btn .ant-btn-icon {
display: inline-flex !important;
align-items: center !important;
line-height: 0 !important;
}
.ant-btn .ant-btn-icon > svg {
display: block !important;
}
直接用 <Button icon={<RiXxxLine size={14} />}>文字</Button> 即可,不用每处再 hack。
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> <span style={{ display: 'inline-flex', alignItems: 'center', lineHeight: 0 }}> {icon} </span> <span>文字</span> </div>三件套缺一不可(漏 lineHeight: 0 还是会下沿偏移)。
详见 feedback_inline_icon_alignment.md(项目记忆,强约束 — 写代码时主动应用,不等用户指出再修)。
顶部导航栏
基于 ProLayout 顶部 Header,layout: 'mix' + splitMenus: true 模式:
一级菜单在 header(横向)、子菜单在 sidebar(纵向)。
左侧 Logo + 平台名,中部一级菜单,右侧操作区(Locale / Avatar)。
本次改版按 Figma 把所有硬编码值改成 AntD token 引用。
完整效果
开启右侧「Inspect」浮层按钮后 hover 任意元素查看具体 token / 尺寸。
规格(全部走 AntD token,禁止硬编码)
controlHeightLG = 40 · 原 48pxcolorBgHeader · #FFFFFFfontSizeLG = 16colorTextHeading · 原 #333 硬编码display: flex + alignItems: centermarginXS = 8 · 原 10 非 AntD scalepadding = 16 · 每个菜单项左右colorPrimary + 底部 2px 主色横线colorPrimary,无背景变化margin = 16 · 操作项之间config/defaultSettings.ts 设置 layout: 'mix' + splitMenus: true →
路由配置中第一层菜单(如「费用中心」「综合中心」)渲染在 header 顶部,
点击后该模块的子菜单(如「账户管理」「订单管理」)才出现在左侧 sidebar。
实际代码(src/app.tsx)
logo: ( <div style={{ display: 'flex', alignItems: 'center' }}> <img src={AlayaNewLogo} style={{ height: 'var(--alaya-control-height-lg)', /* 40 */ }} /> <p style={{ fontSize: 'var(--alaya-font-size-lg)', /* 16 */ marginLeft: 'var(--alaya-margin-xs)', /* 8 */ color: 'var(--alaya-color-text-heading)', marginBottom: 0, fontWeight: 500, }}> 运营平台 </p> </div> )
fontSize: 16 / marginLeft: 10 / color: '#333' / fontWeight: 450 都是硬编码或非 AntD 标准值,本次全部改为 token 引用。
内容区标题 + Alert
基于自定义 PageContainer(包裹 ProLayout PageContainer),
提供主标题(来自路由 name)、副标题、右侧操作区、可选 Alert 警告。
本次改版去除所有内联硬编码像素,全部走 AntD CSS 变量,样式抽到 pageContainer/index.less。
一级页面(列表/概览)
backIcon={false},无返回按钮。标题区是独立白底块,下方 children 容器承载筛选/表格。按业务需要分三种形态:
一级页面标题
一级页面标题
产品管理
· 必含「全部」作为默认 / 兜底 Tab(key=
all),避免用户被强制选某状态· columns 同步移除:相应字段加
hideInSearch: true,避免 Tab 和 search 双控制冲突· request 读 tabActiveKey:通过
useEffect(() => reload(), [tab]) 联动,'all' 不过滤· 底层多态可走聚合 Tab:4 态生命周期(内测/公测/运营/已下架)→ 3 项 Tab「全部 / 在售 / 已下架」,其中「在售」过滤 =
status !== 'offline'(聚合 internal/beta/active 三态)。这种做法让 Tab 数量稳定在 ≤ 4,符合规约,且对运营更友好(无需在三个发布态间频繁切换)· ⏸ 不要拿来做 view 切换(table/card):那是 viewMode 职责,用 Segmented,不走 tabList
· CustomPageContainer footer 字段防覆盖:原代码
header={{ footer: alertNode || header?.footer }} 即使值为 undefined 也会被 ProLayout 的 {...defaultProps, ...header} spread 覆盖掉 renderFooter(tabList) 算出的 Tabs,导致 tabList 不生效。修复:footer 字段只在真有值时挂上 header 对象。· 有 Tab 时白底块 padding 改 8/0:
:has(.ant-pro-page-container-tabs) 时 padding-block: 8px 0,比无 Tab 形态的 12/12 紧凑 4px,让 Tab 紧贴底部。· 标题底贴 Tab 顶 (间距 0):
.ant-page-header-footer:has(.ant-pro-page-container-tabs) { margin-top: 0 },覆盖 AntD PageHeader 默认 footer margin-top 8px,让标题/Tab 节奏更紧凑(总高 90px = 8 + 36 + 0 + 46)。· 砍 AntD Tabs nav 默认 16 下边距:PageHeader 内 Tab 下方无内容,
.ant-tabs-nav { margin-bottom: 0 !important }。· 透明化 AntD Tabs 灰色基线:
.ant-tabs-nav::before 默认带 1px #E1E7EF border-bottom 跟白底块视觉冲突,设 border-bottom-color: transparent。保留 .ant-tabs-ink-bar(选中态 2px 蓝条)正常显示。
二级页面(详情/编辑)
默认带返回按钮(不传 backIcon 即生效),点击返回上一级路由。
二级页面标题
可选元素
在一级或二级形态上叠加,按业务需要选用。
页面标题
副标题描述文字页面标题
规格
paddingContentHorizontalLG = 24paddingContentVertical = 12(上下对称,global 强制)margin = 16controlHeightSM = 24×24paddingXS paddingSM = 8 12marginXXS = 4margin = 16Alert 4 种类型(设计规范定义)
底色 / 边框 / 图标走 设计规范(公司网站设计规范.txt)里 colorXxxBg / colorXxxBorder / colorXxx 三层 token。Info 跟随我们自定义的主色蓝;Success/Warning/Error 复用规范里的 Green/Gold/Red 色板。
Alert 用法
// 直接用 AntD 官方 Alert,不要包装 import { Alert } from 'antd'; <Alert type="info" showIcon message="提示信息" /> <Alert type="success" showIcon message="操作成功" /> <Alert type="warning" showIcon message="请注意" /> <Alert type="error" showIcon message="操作失败" /> // PageContainer.alertProps 仅承载页面级注意事项,type 在组件层硬编码 info(不可改) <PageContainer alertProps={{ description: '状态流转:内测 → 公测 → 运营 → 已下线' }} />
使用方式
// PageContainer 自定义封装:src/components/pageContainer/index.tsx import PageContainer from '@/components/pageContainer'; <PageContainer subTitle="管理客户透支额度规则" extra={[ <Button>导出</Button>, <Button type="primary">新建规则</Button>, ]} alertProps={{ /* type 在 pageContainer 层固定 info,不暴露;这里只给文案 */ description: '设置了透支规则的客户...', }} > <AlayaProTable ... /> </PageContainer>
· 外层白底块兜底:
:global(.ant-pro-page-container-warp-page-header) 强制 background: var(--alaya-color-bg-container),避免云端 mfCloud 主题没设白底导致与下方 layout 灰底糊一起。· 白底块下方 16px 间距:同选择器加
margin-block-end: var(--alaya-margin)(必须放外层 wrapper 上,内层 .ant-page-header 撑不开)。· 标题栏 padding 12 上下对称(2026-05-11 修复):ProLayout 通过 cssinjs 在
.ant-pro-page-container .ant-pro-page-container-warp-page-header 上注入 padding-block: 8px 16px(specificity 0,2,0),把 .pcHeader 的 12 0 压掉造成上下不对称。修复方案:在 src/global.ui.css 用同 specificity selector + !important 强制 padding-block: 12px。总高 = 12 + 28(标题 line-height) + 12 = 52px。· 主标题字号 18/28/500(2026-05 更新):覆盖 AntD PageHeader 默认 24,对齐 Figma。
· 返回按钮 size 走 token:
var(--alaya-control-height-sm) = 24。· ⏸ Alert 渲染逻辑保持 master 不变:图标 14、padding 8 12、margin-bottom 16 全部内联 style(必须 inline 才能压过 AntD
.ant-alert-with-description 默认值),type="info" 硬编码。Alert 应用层规则见下一节《Alert 应用规约》。
Alert 应用规约
列表页标题下「那一行字」到底该不该写、写什么,不只是前端样式问题,更是产品文案规约。
现状是同一种"页面级文案"被两个 API(alertProps 和 header.subTitle)混用,
且 PM 写文案时**没有区分模块描述和 Alert 提示的语义**。本节给 PM 一套判定规则,给前端一份待清洗清单。
统一规则:所有页面级 Alert(不论是规则说明 info 还是动态告警 warning)一律走 PageContainer.alertProps,渲染到标题白底块内。type 由业务传值,颜色和图标自动跟随。
核心判定:模块描述 vs Alert 提示
| 维度 | 模块描述 | Alert 提示 |
|---|---|---|
| 回答的问题 | 这个页面是什么 | 用这个页面要知道什么 |
| 内容性质 | 介绍功能(CRUD 同义反复) | 注意事项 / 业务规则 / 数据特性 / 警告 / 操作前提 |
| 与标题关系 | 同义反复(标题已说明) | 标题不能传达 |
| 用户价值 | 看完 = 没看 | 影响判断 / 决策 |
| 例子 | "查看和管理客户账户算力包" "记录用户在系统中的所有操作过程和结果" |
"账单明细数据相对于实际费用消耗会有延迟" "状态流转:内测 → 公测 → 运营 → 已下线" |
| 处理建议 | 不写(标题已经够了) | 用 PageContainer.alertProps |
type · 颜色 · 场景
Alert 不论何种类型,位置统一(标题白底块内)。颜色 / 图标按 type 自动匹配(图标在 pageContainer 里按 type 映射)。
| type | 颜色 token | 使用场景 / 例子 |
|---|---|---|
| info(默认) | colorInfo(蓝) | 静态规则说明、数据特性、操作前提 例:「状态流转:内测 → 公测 → 运营 → 已下线」「分层定价规则」 |
| warning | colorWarning(Gold) | 动态业务告警 / 风险提醒 / 仅异常时显示 例:「底层资源告警:1 个算力中心已下线」「3 张账单超期未支付」 |
| success | colorSuccess(Green) | 不在 Alert 范畴;操作成功用 message.success() |
| error | colorError(Red) | 不在 Alert 范畴;操作失败用 message.error() 或 Result |
代码实现
// 静态 info 规则说明(默认 type) <PageContainer alertProps={{ description: '状态流转:内测 → 公测 → 运营 → 已下线' }} /> // 动态 warning 业务告警(条件渲染:异常时才有 description) <PageContainer alertProps={hasResourceAlert ? { type: 'warning', description: `底层资源告警:${...}`, action: <a>查看影响明细</a>, {/* 右侧操作(可选) */} } : undefined} />
全项目盘点(15 处)
截至 2026-05-08,所有列表页头部含文案的 15 处实例分类如下(不含商品管理 warning 告警 1 处)。全部走统一 alertProps。
留 /
删 /
待 PM 复核
。每条都标注了菜单位置,方便对照页面查看。
| 菜单位置 | 组件 | 当前 API | 文案 / 性质 | 动作 |
|---|---|---|---|---|
| A. ALERT — 含信息量,应保留 | ||||
| 综合中心 → 计价 → AIDC 定价 | pricing/AidcList | alertProps | 状态流转:内测 → 公测 → 运营 → 已下线 业务规则,标题不能传达 | 保留 |
| 综合中心 → 商品 → 套餐包管理 | commodity/BundleList | alertProps | DEDUCTION 抵扣包 vs DCU_PACK 算力包… 枚举类型区别 | 保留 |
| 综合中心 → 计价 → 计量项管理 | pricing/MeterList | alertProps | 分层定价:基准单价 + DCU + 系数 计费逻辑 | 保留 |
| 费用中心 → 账单管理 → 账单明细 | bill/BillList | alertProps | 账单明细数据相对于实际费用消耗会有延迟 数据延迟提示 | 保留 |
| 综合中心 → 营销管理 → 算力兑换 | voucher/VoucherList | subTitle | 新建和管理兑换码…兑换后转为算力包 业务流程 | 转 alertProps |
| 费用中心 → 账单管理 → 手工账单 (hideInMenu,仅 bsmAdmin) | bill/HandmadeBill | alertProps | 请下载《手工计量》模板… 含交互按钮 | 保留 |
| B. 待 PM 复核 — 文案语义未审 | ||||
| 费用中心 → 账单管理 → 月度账单 | bill/BillMonth | alertProps | 多段条件渲染(含 i18n) | PM 复核 |
| 费用中心 → 账单管理 → 月结算单 | bill/BillSettlement | alertProps | i18n 短句 | PM 复核 |
| 费用中心 → 导出记录 | bill/Export | alertProps | i18n 短句 | PM 复核 |
| 综合中心 → 营销管理 → 算力透支 | overdraft | alertProps | i18n 短句 | PM 复核 |
| C. 模块描述 — CRUD 同义反复,建议删除 | ||||
| 综合中心 → 客户管理 → 企业管理 | org/OrgList | subTitle | 本平台支持多客户管理。可以创建多个客户…(60+ 字 CRUD 废话) | 删除 |
| 综合中心 → 系统设置 → 操作日志 | log/LogList | subTitle | 记录用户在系统中的所有操作过程和结果 | 删除 |
| 费用中心 → 收支管理 → 现金收支 | account/CashTrans | subTitle | 查看和管理客户账户现金收支明细 | 删除 |
| 费用中心 → 收支管理 → 算力收支 | account/DcuTransList | subTitle | 查看和管理客户账户DCU收支明细 | 删除 |
| 费用中心 → 算力包管理 | package/List | subTitle | 查看和管理客户账户算力包 | 删除 |
文案标点:句末统一不带句号
不论文案是完整陈述句、祈使句,还是图示型规则——所有 alert 末尾一律不加句号。
1. 一致性优先:图示型规则(如 AidcList "状态流转:内测 → 公测 → 运营 → 已下线")末尾加 "。" 视觉别扭。 若用"完整句加 / 图示型不加"的条件规则,相当于让 PM 每写一条都要做一次判断,**规则成本高且容易破例**。 统一不加 → 规则只有一条,机械执行不出错。
2. 业界惯例:Material Design / Apple HIG / AntD 官方文档示例中,UI 提示类(alert / tooltip / hint / 表单帮助文字)普遍不带终结标点。 Alert 不是正文段落,本身就是一个有边界的视觉块(icon + 蓝底),不需要标点收口。
3. 视觉重量:Alert 高度只有 30 px,单行短句加句号会让句尾"压"住一个小黑点,与轻量提示气质不符。
4. i18n 友好:英文环境下 "..." 和 "...." 句末标点逻辑与中文不一致。统一不加,多语言切换时不需要为标点再做适配。
下一步动作
· 视觉方案确定 —— Alert 移入 PageHeader.footer(与标题同处白底块)+ 去除描边。本节展示的就是新视觉。
· 组件层规则收口 ——
type 硬编码 info,不暴露给业务(避免 PM 滥用 warning/error)。后续小迭代待办(需 PM 协同):
1. PM 侧 —— 拿上面【全项目盘点】这张表逐条 review,确认每条是删除、保留、还是补新的。
2. 前端侧 —— 删除 5 条「模块描述」性质的
subTitle;把 1 条 subTitle(VoucherList)转成 alertProps。3. 文案标点统一 —— 把 9 条现存 alert 末尾的"。"全部去掉(PM 可以一次性提 issue 给翻译/i18n 团队)。
4. 组件侧(可选) —— 评估是否在
pageContainer 里彻底**移除 subTitle API 入口**,让 PM 想填废话也没地方填,靠组件强制收口语义。
FAQ
都不用
PageContainer.alertProps。· 成功 →
message.success() toast· 失败 →
message.error() 或 Result 组件· 风险确认(删除不可恢复等)→
Modal.confirm· 表单字段校验 → Form 自带 errorMessage
PageContainer.alertProps 只承载页面级、静态、规则性的提示。Q: 我的 alert 文案超过 2 行了能写吗?
说明这条提示已经超出"一句话注意事项"的范畴,应该:
· 拆分成多条短句的列表
· 或改成「使用说明」按钮,点开 Drawer/Modal 看详细规则
不要在 alert 里堆长文。
Q: PageContainer.alertProps 的 type 可以传哪些值?
'info'(默认)/ 'warning'。· info → 静态规则说明(蓝)
· warning → 动态业务告警(黄),通常配合条件渲染(仅异常时传 description)
success / error 不在 Alert 范畴 —— 用
message.success() / Modal.error() / Result。Q: 业务告警(如"3 张账单超期")也走 alertProps 吗?
是。统一规则:所有页面级 Alert 都走
alertProps,传 type: 'warning' 即可。条件渲染 `description` 即可控制"仅异常时显示"。Q: 告警 Alert 右侧能放"查看明细"按钮吗?
可以,传
action 给 alertProps:alertProps={{ type: 'warning', description: '...', action: <a>查看影响明细</a> }}AntD Alert 的
action 槽位会渲染在右侧。
表格列规范
所有 ProTable / Table 共用一套操作列宽度档位和数值列对齐规则, 避免每个页面各算一套尺寸。新页面强约束,存量页面随 bug fix / 改版逐步迁移。
操作列宽度档位
档位 = 最小可读宽度。视觉舒适宽度 ≈ 档位 × 1.25(觉得局促时按 1.25 加宽)。
// 公式 列宽 = 单元格左右 padding (32px) + Σ 操作文字宽度 + (操作数 - 1) × 8px 间距 + 16px 安全余量 // 参数依据 AntD Table padding-inline : 16px (默认) OperationRender / Space gap : 8px (默认) 中文 14px 正文 : 2 字 ≈ 28px · 3 字 ≈ 42px
| 操作数量 | 典型示例 | 最小宽度 | 舒适宽度 | 说明 |
|---|---|---|---|---|
| 1 个 | 编辑 / 查看 |
80px |
100px |
单操作页常用 |
| 2 个 短词(2 字) | 编辑 + 删除 |
120px |
140px |
最常见 |
| 2 个 含长词(≥3 字) | 编辑 + SKU 管理 |
160px |
180px |
长操作 ≥3 字 |
| 3 个 短词 | 编辑 + 删除 + 启用 |
180px |
200px |
— |
| 3 个 含长词 | SKU 管理 + 编辑 + 停售 |
200px |
220px |
— |
| 4+ 个 | 编辑 + 删除 + 复制 + 更多▾ |
220px |
— | 第 4 个起收进 Dropdown「更多 ▾」 |
OperationRender space(AntD <Space> 默认 8px)。
不要用 | / <Divider /> 分隔操作 — 项目里已统一靠间距区分。
删除操作的 friction
删除走 Modal.confirm 二次确认即可(已是项目惯例)— 这就是 friction,不要再嵌进 Dropdown 隐藏,
否则用户找不到。隐藏式的「更多 ▾」只用于操作数量 ≥ 4 时压缩视觉。
数值列右对齐
价格、金额、单价、数量、配额、百分比等数值类列必须 align: 'right',
小数点视觉自然对齐,扫读时易做大小比较。配 D-DIN 数字字体(global.css 已注册)。
| 列类型 | 对齐 | 示例 |
|---|---|---|
| 价格 / 金额 / 单价 | align: 'right' | ¥35.20/h · ¥1,000.00 · ¥0.50 |
| 数量 / 计数 / 配额 | align: 'right' | 128 · 3 · 1,024 |
| 百分比 / 比率 | align: 'right' | 92.5% · 0.85x |
| 文本(名称 / 描述) | left(默认) | 商品名称 / 介绍 |
| 时间戳 | left(默认) | 2026-05-09 18:40:20 |
| 状态 Tag | center 或 left | ● 运营 / Tag 胶囊 |
| 操作 | left(与首列一致) | 编辑 删除 |
代码示例
// ✅ 价格列:右对齐 + D-DIN 字体 span 包裹 { title: '价格', dataIndex: 'price', width: 140, align: 'right', render: (_, r) => <span className={styles.numericValue}>{r.price}</span>, } // ✅ 操作列:固定宽度 + OperationRender { title: '操作', valueType: 'option', width: 120, // 2 个短词最小档 render: (_, r) => ( <OperationRender space> <Typography.Link onClick={() => openEdit(r)}>编辑</Typography.Link> <Typography.Link type="danger" onClick={() => handleDelete(r)}>删除</Typography.Link> </OperationRender> ), } // .ui.less 里数字字体(global.css 已注册 D-DIN @font-face) .numericValue { font-family: 'D-DIN', sans-serif; font-weight: 500; letter-spacing: 0.2px; }
反例
| ❌ 错 | ✅ 对 |
|---|---|
width: 'auto' 让 AntD 自动算 — 行宽不一致 | 显式按档位定 width |
| 同表两行操作数量不同导致换行 | 取最长那行的档位 |
用 | / <Divider /> 分隔 | <Space> 8px 间距 |
| 价格列默认左对齐 | align: 'right' + D-DIN 字体 |
| 把删除按钮藏进 Dropdown「更多」 | 保留可见 + Modal.confirm 即可 |
ui/doc/tableColumnSpec.md。新页面落实,存量页跟着 bug fix 迁移,不一次性扫。
编码字段(code / key / id)展示规约
运营中心所有编码字段(商品编码 / 套餐 code / 产品 code / 区域 code 等)统一展示样式。
核心规则:主题色 = 可下钻。所有蓝色 Typography.Link 必须有真实跳转/Modal,不能是死链接。
| 场景 | 写法 | 视觉 |
|---|---|---|
| 卡片内(标题旁紧挨) | <span className={styles.cardCode}>{code}</span>+ .cardCode { font-size:12px; color:var(--alaya-color-text-quaternary); } |
灰色 12px 小字,inline 紧挨标题 |
| 表格独立列 有详情页 | <Typography.Link onClick={跳详情}>{code}</Typography.Link> |
蓝色链接(点击跳详情/弹 Modal) |
| 表格独立列 无详情页 | <Typography.Text>{code}</Typography.Text> |
普通黑字(不要用主题色,避免"死链接") |
<Typography.Text code>(白底 + 灰边框 + 等宽 SF Mono)—— 这套是"嵌入代码片段"样式,不适合业务编码。<Tag> 包编码 —— Tag 是"分类/状态标签"语义,不是 ID 展示。主题色无跳转 = 死链接 —— 跟"主题色 = 可下钻"规约冲突,用户会困惑"为什么点击没反应"。
详见
feedback_code_field_display.md。
列表筛选区
ProTable 内置 search 筛选区位于表格上方、工具栏下方。一排槽位密度直接影响运营查找效率:太挤(4 字段一排)下拉截断难读;太松(2 字段一排)多字段时翻折页不连贯。本节定下默认密度 + 高频字段抽 Tab 快筛规则。
默认密度:3 字段 + 1 操作 = 一排 4 列
每个 grid col 占 span: 6(24 / 6 = 4 列)。AntD ProTable 默认即此密度,无需额外配置。
search={{ labelWidth: 'auto' }}高频枚举抽 Tab 快筛
当某筛选字段同时满足以下条件时,从 search 区抽出来做 PageContainer.tabList 快筛(参见 内容区标题 · 形态 C):
· 互斥单选:只能选一个值,不存在多选场景
· 枚举值 ≤ 4 个(含「全部」),超过用 Select 更合适
· columns 同步:相应字段加
hideInSearch: true,避免双控制冲突· ⏸ 不是所有 select 都该升级 Tab:低频字段(如「创建人」「产品线」选项 ≥ 5)留在 search 区,别污染 Tab 槽位
实际示例(ProductList)
原 4 字段「产品编码 / 产品名称 / 产品线 / 状态」→ 状态抽到 Tab 快筛,剩 3 个常驻 search 区刚好 3+1 一排放满。
// columns 配置 — status 加 hideInSearch { title: '状态', dataIndex: 'status', hideInSearch: true, // 抽到 PageContainer.tabList render: (_, r) => <Badge status=...> } // 页面层 — PageContainer.tabList + state 联动 const [statusTab, setStatusTab] = useState('all'); useEffect(() => { actionRef.current?.reload(); }, [statusTab]); <PageContainer tabList={[ { tab: '全部', key: 'all' }, { tab: '在售', key: 'active' }, { tab: '已下架', key: 'offline' }, ]} tabActiveKey={statusTab} onTabChange={setStatusTab} > <AlayaProTable request={async (params) => { const filtered = statusTab === 'all' ? data : data.filter((i) => i.status === statusTab); return { data: filtered, total: filtered.length }; }} /> </PageContainer>
· ❌ 不要 2+1 一排(每字段 ~380px):浪费横向空间,多字段时被迫多次「展开/收起」
· ❌ 不要 4+1 一排(每字段 ~210px):Select 选项一长就截断,运营要 hover 看 tooltip
· ❌ 不要把状态 Tab 做成 Segmented:那是 view 切换(table/card)的视觉,跟状态筛选语义不同
· ❌ 不要 Tab + status select 双控:必须二选一,columns.status 加
hideInSearch: true
表单选择器组件
Form 互斥单选 / 多选场景下我们有 6 种组件可选:AntD Segmented、AntD Radio.Group(button)、3 个自定义 Alaya* 卡片组件(AlayaTabs / AlayaCheckboxCards / AlayaRadioStackCards)、Select 下拉。每种视觉重量 / 适用场景不同,避免随意混用,按下方决策树选。
决策树
┌─ 视图 / 模式切换(table↔card · tab 导航)? ──→ Segmented(滑块) │ │ ┌─ 想要 均分铺满? ─→ + .alaya-radio-block │── Form 短词 ladder / 等价单选 ──┤ │ └─ 紧凑跟随内容? ─→ 默认 outline │ (以上两种都用 Radio.Group(button)) 单选 ── 2-4 项 ───────────────────┤ │── 重要决策 · 含图标 · 仅标题 ────────────────→ <AlayaTabs> │ └── 重要决策 · 含 标题 + 描述 ──────────────────→ <AlayaRadioStackCards> 多选 ── 2-N 项 · 需 status 配色(运营/公测/内测/下线)─────────────→ <AlayaCheckboxCards> ≥ 5 项(单选)─────────────────────────────────────────────────→ Select ≥ 8 项 + 长选项 ──────────────────────────────────────────────→ Select showSearch
6 种组件对比
| 组件 | 视觉特征 | 数据语义 | 典型场景 |
|---|---|---|---|
Segmented |
共享灰底容器 + 滑块指示器 | 视图 / 模式切换 | List/Kanban 切换、列表 toolbar 视图切换、tab 式导航 |
Radio.Group(button) + .alaya-radio-block |
连接的按钮(共享边框)· .alaya-radio-block 加上后均分宽度 |
Form 字段:ladder 进度 / 短词等价单选 | 新建商品 Drawer:状态(内测/公测/运营)、区域筛选、period 选择 |
<AlayaTabs> 自定义组件 |
水平独立卡片 + 8px gap + 蓝边/浅蓝底选中 | Form 单选:重要决策(仅标题,含图标) | 新建商品 Drawer:销售渠道 / 计费形态 / 计价策略 |
<AlayaRadioStackCards> 自定义组件 |
垂直堆叠卡片 + 圆形 radio + icon + 标题 + 描述 | Form 单选:重要决策(含 1~2 行描述帮助理解) | 新建商品 Drawer Step 2:默认可见人群(公开 / 限定客户群) |
<AlayaCheckboxCards> 自定义组件 |
水平独立卡片 + 5 态配色(active/beta/internal/offline/unsupported)+ 右侧 tag 小标 | Form 多选:需要 status 区分语义的多选场景 | 新建商品 Drawer Step 2:可售区域(蓝运营 / 黄公测 / 浅灰内测 / 灰底划线已下线) |
Select |
下拉框 | 选项数多 / 节省垂直空间 / 需搜索 | 所属产品(8 项)、城市选择、用户列表 |
代码示例
// ✅ Segmented(视图/模式切换 — table↔card 类) <Segmented options=[ { label: '表格', value: 'table', icon: <RiTableLine /> }, { label: '卡片', value: 'card', icon: <RiLayoutGridLine /> }, ] /> // ✅ Radio.Group(button) + alaya-radio-block(Form ladder · 均分铺满) <Radio.Group optionType="button" className="alaya-radio-block" options=[ { label: '内测', value: 'BETA' }, { label: '公测', value: 'PUBLIC_BETA' }, { label: '运营', value: 'ACTIVE' }, ] /> // ✅ Radio.Group(button) 默认(短词等价 · 紧凑跟随内容) <Radio.Group optionType="button"> <Radio.Button value="hangzhou">杭州</Radio.Button> <Radio.Button value="shanghai">上海</Radio.Button> <Radio.Button value="beijing">北京</Radio.Button> </Radio.Group> // ✅ AlayaTabs(水平卡片单选 · 仅标题 + 图标) <AlayaTabs options=[ { label: '线上', value: 'online', icon: <RiGlobalLine size=20 /> }, { label: '线下', value: 'offline', icon: <RiFileTextLine size=20 /> }, ] /> // ✅ AlayaRadioStackCards(垂直堆叠单选 · 含描述) <AlayaRadioStackCards options=[ { value: 'public', title: '公开', description: '全部客户可见 · 控制台自助下单', icon: <RiGlobalLine size=16 />, }, { value: 'group', title: '限定客户群', description: '仅勾选的客户群可见 · 多选取并集', icon: <RiTeamLine size=16 />, }, ] /> // ✅ AlayaCheckboxCards(多选 · status 5 态配色) <AlayaCheckboxCards options=[ { value: 'cn-hangzhou', label: '华东 1(杭州)', status: 'active' }, { value: 'cn-shenzhen-gpu', label: '深圳 GPU', status: 'beta', tag: '公测' }, { value: 'ap-singapore', label: '新加坡', status: 'internal', tag: '内测' }, { value: 'cn-qingdao', label: '青岛', status: 'offline', tag: '已下线' }, ] /> // ✅ Select(≥ 5 项) <Select options=[ { label: '弹性容器集群 VKS', value: 'VKS' }, { label: '云容器实例 CCI-INSTANCE', value: 'CCI_INSTANCE' }, { label: '带宽 BANDWIDTH', value: 'BANDWIDTH' }, { label: '弹性公网 IP EIP', value: 'EIP' }, { label: '大模型 API LLM', value: 'LLM' }, { label: '对象存储 OSS', value: 'OSS' }, { label: '文件存储 NAS', value: 'NAS' }, { label: '块存储 EBS', value: 'EBS' }, ] />
<AlayaTabs> 规格(自定义组件)
Figma 设计稿 27148:18292 / 26978:48187 对齐。源码 src/components/AlayaTabs/。每个 item 独立边框 + 圆角 + 8px gap + 三态主题色,自适应宽度跟随内容。
| 维度 | 值 | Token |
|---|---|---|
| 高度(default) | 32px | 1+4+22+4+1 = 32(项目默认 control 高) |
| 高度(large) | 40px | size="large" · 1+8+22+8+1 = 40 |
| 圆角 | 6px | --alaya-border-radius |
| 边框 | 1px solid | 默认 --alaya-color-border · 选中 --alaya-color-primary |
| Padding (default) | 4px 24px | 4 / --alaya-padding-lg |
| Padding (large) | 8px 24px | --alaya-padding-xs / --alaya-padding-lg |
| icon ↔ text gap | 8px | --alaya-padding-xs |
| item 之间 gap | 8px | --alaya-padding-xs |
| 图标尺寸 | 16px / 20px | RemixIcon size 由调用方传 |
| 选中态 bg | blue1 浅蓝 | --alaya-color-primary-bg |
| 右上角 ✓ 角标 | opt-in | showBadge 默认 false,需要再开 |
<AlayaCheckboxCards> 规格(多选 · 5 态配色)
源码 src/components/AlayaCheckboxCards/。AlayaTabs 的多选扩展,每项含内置 16×16 勾选框 + label + 可选 tag 角标。status 决定卡片配色与可勾选性。
| status | 语义 | 配色 | 可勾选 |
|---|---|---|---|
active | 运营 | 蓝边 + 浅蓝底(--alaya-color-primary / -primary-bg) | 是(默认勾选目标) |
beta | 公测 | 黄边 + 浅黄底(--alaya-color-warning / -warning-bg)+ 右侧黄 tag | 是 |
internal | 内测 | 浅灰文字 + 灰底(--alaya-color-text-secondary)+ 灰 tag | 是(语义降权但仍可勾) |
offline | 已下线 | 灰底删除线(--alaya-color-fill-quaternary + line-through) | 否(组件自动 disabled) |
unsupported | 当前规格族不支持 | 同 offline | 否(组件自动 disabled) |
| 维度 | 值 | Token |
|---|---|---|
| 高度(default / large) | 32px / 40px | 同 AlayaTabs |
| 勾选框 | 16×16 圆角 3px,选中态填 status 主色 | 内置,无需外部传图标 |
| item gap | 8px | --alaya-padding-xs |
| tag 小标 | 11px / radius 4px / 跟随 status 配色 | — |
<AlayaRadioStackCards> 规格(垂直堆叠单选 · 含描述)
源码 src/components/AlayaRadioStackCards/。每项占满父容器宽度,左侧圆形 radio + 右侧 icon + 标题 + 描述。适用于「重要决策且需要 1~2 行描述帮助用户理解」的场景,AlayaTabs 撑不下描述时用这个。
| 维度 | 值 | Token |
|---|---|---|
| 布局 | 垂直堆叠 · 占满父宽 | flex-direction: column |
| Padding | 12px 16px | --alaya-padding-sm / --alaya-padding |
| 左侧 radio | 16×16 圆形 · 选中态填主色 + 内圆白 | --alaya-color-primary |
| 选中态卡片 | 蓝边 + 浅蓝底 | --alaya-color-primary / -primary-bg |
| icon ↔ title 间距 | 8px | --alaya-padding-xs |
| title | 14px / 22 line-height · font-weight 500 | — |
| description | 12px / 20 line-height · tertiary 灰 | --alaya-color-text-tertiary |
| item 间距 | 8px | --alaya-padding-xs |
.alaya-radio-block 规格(Radio.Group 均分修饰)
AntD 5.18 的 Radio.Group 还没有原生 block prop(5.21+ 才加)。给 Radio.Group 加 className="alaya-radio-block",每个按钮 flex:1 平分容器宽度。源码 src/global.ui.css。
| 用法 | 说明 |
|---|---|
<Radio.Group className="alaya-radio-block" /> | 占满父容器宽度,每个 button 等分 |
| 不加该 class | 跟随内容宽度(紧凑) |
反例
| ❌ 错 | ✅ 对 |
|---|---|
| 2 个等价短词用 Select 下拉 | 用 Radio.Group(button) |
| 状态 ladder 用 AlayaTabs(独立卡) | 用 Radio.Group(button) + alaya-radio-block(短词均分更克制) |
| 表头 table↔card 切换用 Radio.Group(button) | 用 Segmented(视图切换语义更准) |
选项 label 用 emoji 🌐 线上 | 用 RemixIcon 走 icon 字段 |
| 所属产品 8 项摆一排做 AlayaTabs | ≥5 项一律 Select |
用 <Segmented className="alaya-tabs">(旧用法) | 统一用 <AlayaTabs> 组件,旧 className hack 已废弃 |
| 需要描述的单选用 AlayaTabs(只放标题撑不下) | 用 <AlayaRadioStackCards>(垂直堆叠 + 描述) |
多选 + 需要 status 配色用 AntD Checkbox.Group 自定义 render | 用 <AlayaCheckboxCards>(status 5 态内置) |
| 已下线/不支持的 region 还放成可勾的项 | option 加 status: 'offline' 或 'unsupported',组件自动 disabled + 划线 |
Form.Item.tooltip 或字段级 label 副标题。需要描述的场景请改用 <AlayaRadioStackCards>(垂直堆叠 + 描述是它的核心定位)。
列表展现模式选型(运营后台)
运营后台列表按性质二选一,不做视图切换:业务对象类用卡片(卡片 12/页 + 翻页器),流水 / 数据类用表格。哪种用哪种由列表性质决定。
两类列表分工
| 类型 | 特征 | 展示模式 | 代表页 |
|---|---|---|---|
| 业务对象类 | 每条有"产品形象"(图标 + 名称 + 关键指标 + 价格区间) | 纯卡片 · 12/页 + 翻页器 | 商品 / 产品 / 套餐 / 计费模式 |
| 流水 / 数据类 | 每条以数据为主(金额 / 日期 / 状态 / ID) | 纯表格 · 默认 20/页 | 账单 / 月结 / 订单 / 客户管理 / 客户群成员 / 计量项 / AIDC 定价 |
规约要点
- 不做视图切换器 — 哪种用哪种由列表性质决定,给业务对象强加表格 / 给数据类强加卡片都是反例。"切换器"曾被考虑但最终撤回(用户原话:「商品管理这个模块我不是说没有表格视图了吗」)
- 卡片视图:12/页 + 翻页器 — 不做无限滚动。运营场景是「翻到第 N 页定位某条 → 跳详情 → 回来」,URL 友好分页比无限滚动顺手;24 太多 / 6 太少,12 = 3 列 × 4 行刚好一屏
- 表格视图:20/页起步,密度高,AntD 内置排序 / 筛选 / 行选直接用
- URL 带分页位置 —
?page=N&pageSize=12(或 20),刷新 / 跳详情回来都不丢位置,可分享链接 - 判断技巧 — 看每条数据是否有"图标 + 名称 + 关键指标"的"形象感"。有 → 卡片;只是金额/日期/状态/ID → 表格
代码示例
// 业务对象类:纯卡片 + 12/页 + 翻页器 <Row gutter={[16, 16]}> {items.slice((page - 1) * 12, page * 12).map(renderCard)} </Row> {items.length > 12 && ( <Pagination current={page} pageSize={12} total={items.length} showSizeChanger={false} onChange={setPage} /> )} // 流水/数据类:纯表格 <Table columns={columns} dataSource={items} pagination={{ pageSize: 20, showSizeChanger: true }} />
反例
| ❌ 错 | ✅ 对 |
|---|---|
| 商品 / 产品 / 套餐 做表格视图(或给卡片加切换器) | 纯卡片 · 商品本身就是"产品目录"形象 |
| 账单 / 月结 / 订单 做卡片网格 | 纯表格 · 数据扫读密度高 |
| 卡片视图做无限滚动 | 分页器 12/页 · URL 友好 / 跳详情回来不丢位置 |
| 给卡片加 Segmented 切表格 | 不做切换 · 哪种用哪种由列表性质决定,给用户选择反而徒增决策成本 |
Drawer 统一规范
所有运营中心新建/编辑 Drawer 一律按本节规约重做。规约沉淀自 CommodityList 4 步 Steps 向导 Drawer,2026-05-11 同步到 ProductList / BundleList / AidcList / MeterList / BillingMode 五个 Drawer。
硬性规约表
800 全部统一(原 520 / 560 / 600 / 640 一律收口)footer={<div flex space-between>} · 取消左 / 主操作右(详见 Drawer footer 操作按钮)Form.Item(禁止 Row+Col 两列;Form.List 行内字段例外)<AlayaCheckboxCards> 共享组件<AlayaRadioStackCards direction="horizontal"> 共享组件status 用 default/warning/success,禁用 processing 蓝硬性禁止(4 条)
Form.Item rules,UI 只保留 placeholder + 必要的 extra· 不做 🔒 LockTag「发布后锁定」备注:disabled 输入框的灰色态本身就能传达不可编辑
· 不做概念解释 Alert:DCU/RESERVED/Region 等业务概念说明、类型对比说明 不放进 Drawer
· 不用 emoji 图标:🌐 🎯 ⚡ 🔋 🔒 等一律用 RemixIcon / DinoIcons 替代
2026-05-11 落地清单(5 Drawer)
| 页面 | 原宽 | 新宽 | 关键改动 |
|---|---|---|---|
commodity/ProductList | 520 | 800 | 4 态升级 / AlayaCheckboxCards 可售区域 / 删 dev-spec + LockTag + Alert + emoji |
commodity/BundleList | 560 | 800 | 新增 RESERVED 类型 / AlayaRadioStackCards 3 选 / 保留 LockOutlined 作为 RESERVED 功能图标(不是备注 LockTag) |
pricing/AidcList | 600 | 800 | 单列垂直 / footer / 4 态颜色 |
pricing/MeterList | 640 | 800 | meter 编辑 + 价格编辑 两个 Drawer / footer / 单列 |
pricing/BillingMode | 640 | 800 | 单列垂直 / footer / 4 态颜色 |
代码模板
import { Drawer, Form, Button, Space } from 'antd'; import AlayaCheckboxCards from '@/components/AlayaCheckboxCards'; <Drawer title="新建商品" width={800} // 统一 800 open={open} onClose={onClose} footer={ <div style={{ display: 'flex', justifyContent: 'space-between' }}> <Button onClick={onClose}>取消</Button> <Button type="primary" onClick={onSubmit}>提交</Button> </div> } > <Form layout="vertical"> // 单列垂直,禁止 Row+Col 两列 <Form.Item name="code" label="商品编码" rules={[...]} /> <Form.Item name="name" label="商品名称" rules={[...]} /> <Form.Item name="regions" label="可售区域"> <AlayaCheckboxCards options={regionOptions} /> </Form.Item> </Form> </Drawer>
新增路由
所有页面均使用 mock 数据,前端接入时需替换为真实 API。
商品模块 · /ops/misc/commodity
| 路由 | 页面组件 | 说明 |
|---|---|---|
/ops/misc/commodity | — | 一级菜单,icon: dino-bag-shopping |
/ops/misc/commodity/product | commodity/ProductList | 产品管理列表(Drawer 600px 新建/编辑) |
/ops/misc/commodity/list | commodity/CommodityList | 商品管理列表(卡片+表格双视图) |
/ops/misc/commodity/list/create | commodity/CommodityCreate | 新建商品(4 步 Steps 独立页面) |
/ops/misc/commodity/list/:action/:id | commodity/CommodityCreate | 编辑商品 |
/ops/misc/commodity/bundle | commodity/BundleList | 套餐包管理(Drawer 640px 新建/编辑) |
计价模块 · /ops/misc/pricing
| 路由 | 页面组件 | 说明 |
|---|---|---|
/ops/misc/pricing | — | 一级菜单,icon: dino-coins |
/ops/misc/pricing/aidc | pricing/AidcList | AIDC 定价(Drawer 520px) |
/ops/misc/pricing/meter | pricing/MeterList | 计量项管理(Drawer 640px 编辑 + 定价) |
/ops/misc/pricing/billing | pricing/BillingMode | 计费模式(Drawer 560px + 绑定商品 Modal) |
侧栏图标替换 · 14 项
| 菜单 | 旧 icon (codesign) | 新 icon (恐龙) | |
|---|---|---|---|
| 账户管理 | icon-yy-zhanghuguanli | → | dino-wallet |
| 收支管理 | icon-yy-DCUshouzhimingxi | → | dino-invoice-dollar |
| 订单管理 | icon-yy-dingdanguanli | → | dino-receipt |
| 合同管理 | icon-yy-hetongguanli | → | dino-file-check |
| 账单管理 | icon-yy-zhangdanguanli | → | dino-bank-statement |
| 算力包管理 | icon-yy-suanlibaoguanli | → | dino-box |
| 导出记录 | icon-yy-daochujilu | → | dino-file-download |
| 会话监控 | icon-yy-mingchengziyuanjianqiang | → | dino-clipboard-list |
| 客户管理 | icon-yy-yonghuguanli | → | dino-users |
| 商品管理 | icon-yy-shangpinguanli | → | dino-box-check |
| 营销管理 | icon-yy-duihuanmaguanli | → | dino-marketing-target |
| 权限管理 | icon-yy-renyuanquanxianguanli | → | dino-shield-check |
| 消息管理 | icon-yy-gonggaotongzhi | → | dino-notification |
| 系统设置 | icon-yy-biaoqian | → | dino-gear |
icon-yy-xxx 格式并删除 DinoIcons 组件 + SVG 文件。
改动文件清单
合并前请重点 review 以下文件。
新增文件
| 文件路径 | 说明 |
|---|---|
src/pages/commodity/ProductList/index.tsx | 产品管理列表 |
src/pages/commodity/CommodityList/index.tsx | 商品管理列表(双视图) |
src/pages/commodity/CommodityCreate/index.tsx | 新建/编辑商品 |
src/pages/commodity/CommodityCreate/index.less | 商品创建页样式 |
src/pages/commodity/BundleList/index.tsx | 套餐包管理 |
src/pages/pricing/AidcList/index.tsx | AIDC 定价 |
src/pages/pricing/MeterList/index.tsx | 计量项管理 |
src/pages/pricing/BillingMode/index.tsx | 计费模式 |
src/components/DinoIcons/index.tsx | 恐龙图标组件(24 个) |
src/components/DinoIcons/index.global.less | 图标颜色 + 菜单状态样式 |
src/assets/icons/icon*.svg × 24 | duotone 风格 SVG 资源 |
已有文件修改
| 文件 | 改动说明 |
|---|---|
config/route/platformRoutes/misc.ts | 新增商品、计价模块路由 + 14 项图标替换 |
config/route/platformRoutes/cost.ts | 费用中心 8 项图标替换 |
src/app.tsx | 导入 dinoIconMap 并在 menuDataRender 递归替换;底部折叠按钮(menuFooterRender);隐藏默认折叠按钮;顶部 logo 高度 48px → 40px(对齐 Figma),logo 容器加 alignItems: 'center' 让图片和文字水平居中 |
config/theme.ts | 主题色切换:colorPrimary #FF921C 橙 → #4362FF 蓝(与其他蓝色产品对齐);菜单选中背景 secondaryOrange1 → blue1;对齐公司设计规范:gray/blue/orange 色板、文本色、边框色、填充色、背景色 |
src/components/DinoIcons/index.global.less | 图标默认色 colorTextTertiary;选中/hover 跟随主色;下拉箭头 colorTextQuaternary;菜单去过渡 + 子菜单展开动画 0.15s |
已知遗留问题(不在本次改版范围)
主题色已通过 config/theme.ts 切换到蓝色,AntD 内置组件 + 用 var(--alaya-*) 的自定义样式都自动跟随。
但项目中仍有 十几处硬编码 #FF921C 橙色,未走主题系统:
| 文件 | 用途 | 建议 |
|---|---|---|
public/scripts/loading.js | 加载页背景色 | 改为 --alaya-color-primary |
src/constants/order.ts | 订单状态文字色 | 改用 colorWarning 语义 token |
src/constants/notice.ts | "待发布" 状态文字色 | 改用 colorWarning |
src/constants/payOrder.ts | 支付状态文字色 | 改用 colorWarning |
src/components/ChangeLan/index.less | 语言切换器 hover 色 | 改为 --alaya-color-primary |
src/components/CustomPayModal/index.tsx | 弹窗强调色 | 改为 --alaya-color-primary |
var(--alaya-color-primary) 或对应语义 token,不在本次小改版范围。
下架 / 停售 Modal 统一规范
覆盖项目里 6 个破坏性操作 Modal — 结构、尺寸、组件用法、文案全部对齐,让运营 / PM / 测试在不同页面看到的"下架 / 停售"操作有完全一致的体验。
1. 宽度统一 width=560
width={560}例外:含复杂表格 / Form.List 多列(如 SKU 价格合成追溯)可放大到 760
2. 标题规范
| 场景 | 标题 |
|---|---|
| 产品下架 | 下架产品「{record.name}」 |
| 商品状态变更 | 商品状态变更 |
| 套餐包下架 | 下架套餐包「{record.name}」 |
| 计量项下架 | 计量项下架 |
| SKU 停售 | SKU 停售 |
| SKU 停售停续 | 停售停续「{record.code}」 |
❌ 标题禁带 emoji(⏸ / 🔄 / 🛑 等)
✅ 涉及目标对象时带「目标名」让运营确认操作哪一条
3. 内容区结构
① 橙色 / 红色 Alert — 说明此操作的影响("新客户买不到、老客户可续费"等)
② 影响范围 div / Alert — 用
· N 个 X 列出受影响的关联数据(可选,仅复杂破坏性操作显示)③ Form 收集原因 — 必填 textarea + 可选「下架后处理」Select
❌ 禁止:"彻底删除"FAQ 提示 / 6 态流转图等开发文档式内容(这些是 ui/doc 范畴)
3.1. 影响范围数字 — 中性黑色 strong,不要橙/红
<strong>(中性黑色),不要加 color: warning/error/orange。原因:警示信号集中在顶部 Alert(橙/红背景)+ 底部 danger 按钮(红色)。影响范围是「陈述事实」不是「警告」,加颜色反而稀释顶部 Alert 的警示意图,让弹窗颜色过载。
❌ 错:
<strong style={{ color: 'var(--alaya-color-warning)' }}>1 个在售商品</strong>✅ 对:
<strong>1 个在售商品</strong>
4. Form 字段规范
| 字段 | 组件 | 规则 |
|---|---|---|
| 下架原因 / 变更原因 | Input.TextArea rows=3 maxLength=200 showCount | 必填 · placeholder 用 / 分隔示例 |
| 下架后处理 | Select + getPopupContainer={() => document.body} | 避免 dropdown 被 Modal overflow 裁剪 |
| 目标状态(仅商品状态变更) | Radio.Group 全宽分块 | 3 选项卡片式,每项含主标题+二级描述 |
| 客户群多选(仅"客户群专属在售") | Checkbox.Group grid 2 列 | 条件渲染 — Radio.Group 外层用 shouldUpdate 控制 |
5. Footer 按钮规范
okText / okButtonProps / cancelText / onOk / onCancel❌ 不要自定义
footer={ReactNode} JSX(除非有特殊间距需求且全局 CSS 无法解决)✅
okButtonProps={{ danger: true }} 让确认按钮红色(破坏性操作)✅
okText 用「确认下架」/「确认停售」/「确认变更」/「确认彻底下线」明确动作
6. 全局 Modal 间距 — 跟 stop modal 共享
已在 src/global.ui.css 全局调整(影响所有 Modal):
.ant-modal-root .ant-modal .ant-modal-content > .ant-modal-header {
padding-bottom: 24px !important;
margin-bottom: 0 !important;
}
.ant-modal-root .ant-modal .ant-modal-content > .ant-modal-footer {
padding-top: 24px !important;
margin-top: 0 !important;
}
.ant-modal-root .ant-modal .ant-modal-content {
overflow: visible !important; /* 让 Select dropdown 不被裁 */
}
7. 6 个 stop Modal 实现一览
| 页面 | 触发 | 状态 |
|---|---|---|
| ProductList | 列表行「下架」link · danger | ✅ 对齐 |
| CommodityList | 列表行「停售」link · danger | ✅ 对齐(6 态变更 Modal) |
| BundleList | 列表行「下架」link · danger | ✅ 对齐 |
| MeterList | 列表行「下架」link · danger | ✅ 对齐 |
| CommoditySku | SKU 表「停售」link · danger | ✅ 对齐 |
| CommoditySku | SKU 表「停售停续」link · danger(红 Alert + 必勾 checkbox) | ✅ 对齐 |
文字提示词规范
覆盖 Input / TextArea / Select placeholder、Label 后缀(如「(必填)」)、Alert / Modal 内部文案。统一才能让运营 / PM / 测试一眼读懂,不被工程师术语劝退。
1. Placeholder 示例分隔符 — 统一 / (带空格)
placeholder="例:被新版商品替代 / 销售持续下滑 / 业务策略调整 / 合规清退"❌ 错:
placeholder="例:被新版商品替代 · 销售持续下滑 · 业务策略调整"(用了 · 中点)❌ 错:
placeholder="例:替代 ; 下滑 ; 调整"(用了 ; 分号)
为什么 / 比 · 好:斜杠在中文 placeholder 里更易识别为"或者"的语义;中点 · 容易跟正文里的中点冲突,视觉上是"和并列"而不是"或多选"。
2. 词汇选择 — 运营 / 业务语言,禁工程师术语
| ❌ 工程师术语 | ✅ 运营友好 | 说明 |
|---|---|---|
| 硬件 EOL | 业务线下线 / 合规清退 | EOL = End of Life,运营人员不知道 |
| 被 g8 替代 | 被新版商品替代 | g8 是 GPU 代号,普通运营不熟 |
| API 重构 | 计费模型重构 | API 是后端实现术语 |
| line item 拆分 | 账单条目拆分 | line item 是英文行业黑话 |
| 规格族 EOL | 规格族迭代 | EOL → 迭代/替代更清楚 |
3. Label 后缀 — 禁冗余"(必填)"
rules=[{ required: true }] 时,label 自动渲染红色 * 星号。❌ 错:label="变更原因(必填 · 留痕用)" — "必填" 跟 * 重复,"留痕" 是后端实现说明
✅ 对:label="变更原因" — 简洁
4. Modal / Drawer 内文案 — 禁"开发文档式"
→ 这是开发说明 / FAQ,不是操作流程,运营点不到,是文档
✅ 对:直接在 Alert / 标题里说清"会发生什么"+ 二次确认即可,FAQ 单独写文档
5. 5 个下架 / 停售 Modal placeholder 现状
| 页面 | 统一后的 placeholder |
|---|---|
| ProductList(产品下架) | 例:产品迭代替代 / 业务线下线 / 合规清退 / 客户反馈整改 |
| CommodityList(商品状态变更) | 例:被新版商品替代 / 销售持续下滑 / 业务策略调整 / 合规清退 |
| BundleList(套餐包下架) | 例:被新版包替代 / 业务线下线 / 优惠期结束 / 销售下滑 |
| MeterList(计量项下架) | 例:被新计量项替代 / 计费模型重构 / 业务线下线 |
| CommoditySku(SKU 停售) | 例:被新档位替代 / 业务线下线 / 优惠期结束 / 销售下滑 |
6. 通用模板
例:被新版 XX 替代 / 业务线下线 / 销售下滑 / 合规清退
新建 / 编辑类描述:
placeholder="一句话说明 XX 用法"(不要 "客户感知"、"留给前端" 之类的工程师说明)
表格分页规约
解决"小数据表格挂废分页器 1/1 页"的视觉噪音问题。按表格性质分三档,不一刀切「全部分页」也不一刀切「全部不分页」。
| 表格性质 | 分页规约 | 典型场景 |
|---|---|---|
| 业务对象列表 | 必须分页pageSize: 10 + showSizeChanger |
ProductList / CommodityList / BundleList / CommoditySku / OrderList / OrgList / MeterList / BillingMode / AidcList |
| 详情页关联资源 | 数据 ≥ 10 条 才分页; < 10 直接全列展示 |
ProductDetail 计量项 / AidcDetail 计费模型·产品·商品 / 引用范围 SKU / 关联套餐 |
| 固定枚举列表 | 永不分页pagination={false} |
MeterList Drawer 算力中心系数 8 行 / 计费模式枚举 4 行 / 状态枚举 / 货币 / 时区 |
三个判断维度
会(业务对象类)→ 必须分页 · 不会(固定枚举)→ 永不分页 · 会但天花板低(关联资源)→ 看当前条数
2. 用户主要任务是浏览还是聚焦?
浏览全集(列表页)→ 分页帮助分批消化 · 看完所有项的关联(详情页)→ 全列展示更高效
3. 视觉噪音容忍度?
「1/1 页」「共 3 条」对所有数据 < 10 的表格都是浪费
业务对象列表 — 必须分页
数据天花板高 / 用户主要任务是筛选 + 浏览 + 操作。
<AlayaProTable
pagination={{
pageSize: 10, // 默认 10/页
showSizeChanger: true, // 允许切 20 / 50 / 100
showQuickJumper: true, // 大数据集允许跳页
showTotal: (total) => `共 ${total} 条`,
}}
/>
详情页关联资源 — 条件分页
数据天花板低(一般 1-30) / 用户已经聚焦某个主对象 / 需要看到所有关联。
// 数据 < 10:不分页
<Table pagination={false} ... />
// 数据 >= 10:分页(同业务列表但 showSizeChanger 可关)
<Table
pagination={{
pageSize: 10,
showSizeChanger: false, // 详情页关联资源不需要 100/页
showTotal: (total) => `共 ${total} 条`,
}}
/>
固定枚举 — 永不分页
数据规模业务上固定,加分页 = 反模式,违背"固定数据"语义。
<Table pagination={false} ... />
边界场景 FAQ
A: 是。规约是 ≥ 10 才分页。8 条全列展示 (~ 320px 表高) 没问题。
Q2: 当前 6 条但 API 上限 100?
A: 走分页(保守)。规约判断"业务天花板"优先于"当前数量"。
Q3: SKU 列表算业务对象还是关联资源?
A: 看页面性质。CommoditySku 详情页里的 SKU = 关联资源(按条件分页);单独 SKU 列表页 = 业务对象(必须分页)。
Q4: 表格在 Modal / Drawer 里要分页吗?
A: 看数据性质,跟容器无关。Modal 里的引用范围表(一般 < 10)不分页;Modal 里的批量选 SKU(可能上百)必分页。
Q5: 已经有「展开 / 收起」交互的怎么办?
A: 走「折叠」交互 + 不分页。例如:可售区域勾选了 8 个里的 6 个 → 展示成 "杭州 / 上海 / + 4 个" 折叠 + 点击展开。
落地清单
1. 这个表格的数据是 业务对象 / 关联资源 / 固定枚举?
2. 业务对象 →
pagination={{ pageSize: 10, showSizeChanger: true, showTotal: ... }}3. 关联资源 → 看数据数:< 10 →
pagination={false};≥ 10 → pagination={{ pageSize: 10, showSizeChanger: false }}4. 固定枚举 →
pagination={false}5. 不确定?默认走分页(保守)
详细规约文档:ui/doc/tablePagination.md
状态 vs 类型 — 双轴视觉规约
跨列表 / 详情页统一两类语义的视觉表达。状态用小圆点(Badge dot),类型用色块标签(Tag color),跨页面用户秒识别"圆点 = 状态属性 / 色块 = 分类身份"。
| 语义 | 形态 | AntD 组件 | 例子 |
|---|---|---|---|
| 状态 | 小圆点 + 文字 | <Badge status="..." color="..." /> |
在售 / 已下架 / 内测 / 公测 / 运营中 / 应用中 / 已启用 |
| 类型 | 边框 + 底色块 | <Tag color="..." /> |
PAYG / SUBSCRIPTION / SPOT / TIERED / 计算 / 网络 / 存储 |
Badge dot 颜色规约(状态用)
| 状态 key | Badge prop | 视觉 |
|---|---|---|
| internal 内测 | color="gray" | 灰点 |
| beta 公测 | color="orange" | 橙点 |
| active 运营中 / 在售 / 应用中 / 已启用 | status="success" | 绿点 |
| offline 已下架 / 已下线 | status="default" | 灰点 |
Tag color 规约(类型用)
类型 Tag 加颜色区分,对齐原型设计。
| 类型 | Tag color | 来源 |
|---|---|---|
| PAYG(按量付费) | blue | 原型 计价.html tag-blue |
| SUBSCRIPTION(订阅) | green | 原型 tag-green |
| SPOT(抢占式) | orange | 原型 自定义 #fed7aa |
| TIERED(阶梯) | purple | 原型 tag-purple |
| 产品线(计算 / 网络 / 存储 / AI) | processing 等 | 按业务约定 |
详细规约:ui/doc/statusVsTypeDisplay.md
列表页 Tab 切换规约
业务对象类列表都用 PageContainer.tabList 状态切换。不加 tab 仅 2 类例外:已用段头分类(如 CommodityList IaaS / LLM)/ 不存在状态字段。
| 页面 | tab 项 | 态数 |
|---|---|---|
| ProductList | 全部 / 在售 / 已下架 | 2 态 |
| MeterList | 全部 / 应用中 / 已下架 | 2 态 |
| BundleList | 全部 / 内测 / 公测 / 运营中 / 已下架 | 4 态 |
| AidcList | 全部 / 内测 / 公测 / 运营中 / 已下线 | 4 态 |
| BillingMode | 全部 / 内测 / 已启用 | 2 态(原型只 2 态) |
| CommodityList | (段头分 IaaS / LLM,不加 tab) | 例外 |
主轴:状态而非类型
① 业务上"按状态找模式"频率 > "按类型找模式"
② 类型信号通过 KPI 卡 + 表格类型列已表达
③ 跨页面 tab 都按状态切换,用户认知模型稳定
数据分布不均的处理
某态当前 mock 数据为 0 时(如 BillingMode 默认全 active),补 1-2 条 mock 让 tab 不空,业务上真实环境会有这些态。
连带改动:删除 subTitle
加 tabList 时顺手删开发文档式 subTitle(如「基础设施单元 · 所有原价和计费模式的'地理坐标'」)。PM 视角的描述应该放在帮助 / 文档,不挂页面顶部。
详细规约:ui/doc/listFilterTab.md
active 状态文案规约
status: 'active' 状态文案带「中」字表达"进行时"语义,避免名词歧义("运营"会被理解为名词"运营部门")。
| 页面 | active 文案 | 业务语义 |
|---|---|---|
| ProductList | 在售 | 商品对客户开放销售中 |
| MeterList | 应用中 | 计量项被 SKU / 计费模式引用使用中 |
| BillingMode | 已启用 | 计费模式启用中(原型 计价.html line 537) |
| AidcList | 运营中 | 算力中心机房运营中 |
| BundleList | 运营中 | 套餐包销售运营中 |
| CommodityList | 运营中 | IaaS / LLM 商品运营中 |
| CommoditySku 详情 | 运营中 | 商品在售状态 |
命名风格
| 风格 | 适用 | 例子 |
|---|---|---|
X 中 | 持续进行的状态 | 应用中 / 运营中 |
已 X | 已完成的状态变更 | 已启用 / 已下架 / 已下线 |
| 业务专属 | 已含"进行态"语义 | 在售(在 + 售 已含进行) |
同步 toast 文案:列表显示「已启用」时,操作 toast 也用「已启用 xxx」,不要漂移成「已上线 xxx」。
详细规约:ui/doc/activeStatusWording.md
业务模型 4 层 · 产品 / 商品 / 规格族 / SKU + 计量项
Alaya 运营中心标准业务模型层级 · 跨页面认知基础。注意正确顺序是「产品 → 商品 → 规格族 → SKU」,商品在规格族上面(之前曾讲反过)。
产品(Product · 品类)
└── 商品(Commodity · 售卖配置)
└── 规格族(SPU · Standard Product Unit · 配置族)
└── SKU(Stock Keeping Unit · 最终售卖单元)
× 计量项(meter · 横切计费字段)
× 算力中心系数
× 计费模式系数
= 客户账单
各层定义(以云容器实例为例)
| 层 | 定义 | 例子 |
|---|---|---|
| 产品 | 业务品类目录 | 云容器实例 / VKS / 带宽 / EIP / LLM / OSS / NAS / EBS |
| 商品 | 按计费形态 / 资源类型切的售卖单元 | 云容器实例-GPU / 云容器实例-CPU |
| 规格族 | 同商品内的硬件 / 配置变种 | NVIDIA-H800A-NV-80G / NVIDIA-L40S-PCIE-48G |
| SKU | 最终带价格的具体配置 | gpu:1 cpu:2 mem:4 disk:50 |
| 计量项(横切) | 所有 SKU 共享的"按啥算钱"字段 | cci.gpu.hours / cci.cpu.hours / cci.memory.gb.hours |
直观记法
- 产品 = 卖什么类型(业务目录视角)
- 商品 = 怎么切售卖(销售视角,按计费形态 + 资源类型切)
- 规格族 = 同商品下的硬件变种(工程视角,卡型 / 档位族)
- SKU = 最终具体配置(财务视角,挂价格的最小单元)
- 计量项 = 计费的最小度量字段(数据视角,量纲)
价格计算链路
客户实际用量(如 100 GPU·h + 200 CPU·h)
× 计量项基准单价(gpu.hours = ¥4.40 / cpu.hours = ¥0.12)
× 算力中心系数(杭州 ×1.0 / 香港 ×1.42)
× 计费模式系数(按量 ×1.0 / 包年 ×0.85)
= 账单金额
详细规约:ui/doc/billingHierarchy.md
已下架行不整体置灰
表格里 status: 'offline' 的行不要 opacity-60 整行透明、不要单元格文字灰。状态信号交给状态 Badge + 副信息(下架日期),整行置灰会让"启用 / 下架详情"等操作链接看起来不可点。
禁止做的
rowClassName={(r) => r.status === 'offline' ? 'opacity-60' : ''}— 整行透明<span className={r.status === 'offline' ? styles.textOffline : ''}>— 单元格灰- 价格 / 数字列根据 status 切换灰色
正确做法
- 状态列:
<Badge status="default" text="已下架" />灰圆点 + 文字 - 状态列下方副信息:小灰字下架日期(如 "2026-02-15")
- 其他列:保持默认色,不区分 status
- 操作列:Typography.Link 保持默认蓝色(运营仍可点"下架详情 / 启用")
详细规约:memory feedback_offline_row_no_dim.md
详情页 4 Tab 规范
所有详情页统一形态:PageContainer + ProCard「基本信息」+ ProCard「关联资源 Tab」。对齐 Figma node 27615:41659。
基本信息卡(顶部)
- 左 88×88 大图标(产品 icon / AIDC icon)
- 右标题:名称 18px / 500 + 状态 Badge dot
- Descriptions
column={3}colon size="small" — 必含新建/编辑表单的所有用户编辑字段(标题视觉重复不是删字段的理由) - 备注 / 描述独立一行:
<span 灰 label:><span 默认色 value>,不加灰底块
关联资源 Tab(下方)
- 用 AntD Tabs + HTML table(不用 antd Table,性能更轻)
- tab 数 ≤ 4 个:基础 / 计费模型 / 产品 / 商品 之类
- 每个 tab 数据 < 10 条不分页(按 表格分页规约)
告警走 alertProps(不挂 body)
详情页告警(如「底层计量项已下架影响计费」)走 PageContainer.alertProps 放标题栏区域,不散装到 body 顶部,避免抢主内容视觉焦点。
header.extra 操作按钮
- 主操作右:编辑(primary + RiEditLine icon)
- 次操作左:商品 selector / 查看商品 / 导出 等
- onBack:history.back()
详细规约:ui/doc/detailPageStandard.md + ui/doc/detailPageBasicInfo.md
Drawer 表单单列垂直
运营中心所有 Drawer 表单 Form.Item 一行一个字段,禁止用 Row + Col 做两列布局。Form.List 行内字段(如 SKU 表格、阶梯表格)例外。
<Row gutter={12}><Col span={12}><Form.Item ... /></Col><Col span={12}><Form.Item ... /></Col></Row>
正确做法
<Form layout="vertical">
<Form.Item name="code" label="编码" rules={[...]}><Input /></Form.Item>
<Form.Item name="name" label="名称" rules={[...]}><Input /></Form.Item>
<Form.Item name="line" label="产品线"><Select ... /></Form.Item>
</Form>
为什么
- Drawer 一般 800-960 宽,两列字段挤变形(label 截断 / placeholder 没空间)
- 单列上下扫读符合表单填写心理流
- 跨 Drawer 统一形态,用户认知模型稳定
原型 2 列 grid 怎么办
原型 HTML 经常用 grid-cols-2 写两列字段,不要按原型布局照抄,强约束改单列。原型是 PM 视角的"字段都列了"草图,不是布局规约。
详细规约:memory feedback_drawer_form_vertical.md
Modal 标题不带 icon
AntD <Modal title="..." /> 用纯文字,不带 icon。「禁 emoji」≠「必须 RemixIcon」—— 默认是没 icon。
正确
<Modal title="计量项下架" ... />
<Modal title="商品状态变更" ... />
禁止
<Modal title={<><RiPauseCircleLine /> 计量项下架</>} ... />
<Modal title="⏸ 计量项下架" ... />
例外
仅 Modal.confirm 系列允许带语义 icon(success / warning / error / info),那是 AntD 内置交互模式,不需要自定义。
详细规约:memory feedback_modal_title_no_icon.md
不擅自改 PM 产品文案 + 业务模型
UI Developer 的边界是「样式 + 布局 + 交互」,不包括字面文字 + 不包括"加几个状态枚举"。即便发现规约不一致也要先问用户拍板。
不许擅自改的
| 类 | 具体内容 |
|---|---|
| 文案类 | defaultMessage 内容 / Modal title / 字段 label / placeholder / extra / 按钮文字("停售"/"下架"/"保存"/"发布" 等) |
| 业务模型类 | 状态枚举数量 / 状态枚举 key 值 / 字段必填校验 / 工作流步骤数 / 数据 schema 字段 |
允许动的(UI 规约层)
- 视觉组件选择(如 Tag → Badge 切换形态,字面值不变)
- 颜色映射 / 主题 token
- 布局 / 间距 / 字号
- 交互形态(卡片 vs 表格 / Drawer vs 独立页面)
发现页面间文案不一致怎么办
- 不要"统一"
- 列差异给用户看("ProductList 用 X / CommodityList 用 Y")
- 问用户:A 保留各自 / B 统一到 X / C 统一到 Y
- 等用户拍板再改
❌ 把原型 HTML 里英文
Code 当成 PM 文案搬到表头 → 应中文化「编码」❌ MeterList 把「保存」改成「发布」(凭语义合理性自换)
❌ ProductList 自加 4 态 internal/beta(PM 实际只 2 态)
❌ 详情页 Descriptions 漏字段(觉得跟标题重复就删 — 不行,字段是表单值展示,标题是视觉锚点)
详细规约:memory feedback_no_product_copy_edit.md
列表 / 详情 mock 必须同步
同一对象在列表页和详情页的 mock 数据(名称 / 编码 / 状态 / 字段)必须保持一致,改一边要同步另一边。
典型 List ↔ Detail 对
- ProductList ↔ ProductDetail
- CommodityList ↔ CommoditySku
- AidcList ↔ AidcDetail
- BillingMode ↔ BillingModeDetail(如有)
- BundleList ↔ BundleDetail(如有)
改 mock 的 checklist
2. 改状态:列表 status 字段 + 详情 status 字段 + 4 态颜色映射
3. 改字段值(goodsSku 数 / regions 数):列表字段 + 详情 Descriptions 同字段 + 关联资源 Tab 条数要对得上
反例
ProductList: name: '云容器实例(固定规格)'
ProductDetail: name: '云容器实例'
→ 用户点进详情看到名字不一样,怀疑系统错误
详细规约:memory feedback_list_detail_mock_sync.md
编码字段统一展示
编码字段(code / spuCode / meterCode 等)跨场景统一形态。禁止 <Typography.Text code> 带边框等宽。
| 场景 | 形态 | 原因 |
|---|---|---|
| 列表表格编码列(有详情页) | <Typography.Link onClick=> history.push(detail)> 蓝色 | 点击下钻语义 |
| 列表表格编码列(无详情页) | <Typography.Text> 默认黑色 | 普通信息 |
| 详情页 Descriptions 编码值 | 纯字符串,不加任何样式 | 跟其他字段视觉重量一致 |
| 卡片副信息编码(产品名后) | 灰色小字 <span className="cardCode"> | 次要标识 |
禁止
<Typography.Text code>VKS</Typography.Text>(自带灰底框 + 等宽字体 → 抢视觉)- 自加 mono 字体 className 让编码"看起来更技术"(除非原型明确要求 font-mono,且只用于专业开发场景如计量项详情)
详细规约:memory feedback_code_field_display.md
原型规范三合一
Alaya 原型 HTML(/Users/dang/Desktop/计费-v1.7/*.html)是 PM 规约源头。3 条解读规则:
① 可点击都要落地
原型里所有 onclick 标记的元素(蓝色 link / 按钮 / 卡片 / Tag)都要有真实交互,不能做死按钮 / 死链接。点击 → 跳转 / 打开 Drawer / 弹 Modal / showToast 都行,但不能没反应。
② dev-spec / 字段规则块 不上 UI
原型里 <div class="dev-spec"> 灰底块 + 「📐 字段规则 / 正则 / 长度 / 锁定」这类是开发文档给前端看的,不做到 UI 上。校验落到 rules,UI 保留 placeholder + 关键 LockTag 就够了。
③ 原型既是布局也是内容规约
文字 / 数值 / 占位顺序全是规约。动手前先复制原型对应段到对话逐字段核对;不写 fallback 写 catalog(产品名 / 状态名 / 字段名都按原型来)。
3 条规则的边界
- 「dev-spec 不上 UI」解决:开发草稿别误读成 UI 规约
- 「布局即内容规约」解决:原型字面值就是 PM 拍板的文案,不要按"语义合理性"自换
三条一起用,少犯 90% 的原型误读
英文字段名是开发草稿
原型 HTML 里 <th>Code</th> / <th>Status</th> 这种英文是开发草稿记号,不是 PM 拍板的产品文案。运营控制台所有表头 / Tab / 按钮 / 字段 label 都要中文化。
详细规约:memory feedback_prototype_clickables.md + feedback_prototype_dev_spec.md + feedback_prototype_content_spec.md
Drawer 单选 / 多选组件选择
Drawer 表单单选 / 多选不能"看着像 Select 就用 Select",要按选项数和视觉权重选组件。
| 类型 | 选项数 | 组件 |
|---|---|---|
| 单选 | 2-5 项 | <Radio.Group className="alaya-radio-block"> + flex 全宽分块 |
| 单选 | 6+ 项 | <Select /> 下拉 |
| 多选 | 少量 + 平展 | <AlayaCheckboxCards /> 卡片网格 |
| 多选 | 大量 / 需搜索 | <Select mode="multiple" /> 下拉 |
为什么
- 2-5 项 Radio 全展开扫读最快,省一次点击
- 6+ 项 Radio 会撑爆 Drawer 横向空间
- 多选需要"看到全部已选" → 卡片网格 / Select tags 两种
详细规约:memory feedback_drawer_singleselect.md
运营后台列表 — 卡片 vs 表格 二选一
运营后台所有列表页按数据性质二选一,不做视图切换器。
| 数据性质 | 视图 | 例子 |
|---|---|---|
| 业务对象类 | 纯卡片网格 12 / 页带翻页器 | CommodityList 平台商品 / LLM 推理服务(产品形象强,价格 / 计费模式可视化好) |
| 流水 / 数据类 | 纯表格 | ProductList / MeterList / BundleList / AidcList / BillingMode(数字 + 文本扫读为主) |
判断口诀
- 是 → 卡片(图标 + 配色 + 重点价格醒目展示)
- 否 → 表格(多列扫读 + 排序 + 筛选效率优先)
详细规约:memory feedback_ops_table_first.md + ui/doc/listViewMode.md
视图切换需事先审核
任何页面加 Segmented / Tabs 切换不同呈现前必须先问用户,不限于运营后台。
需要审核的场景
- 加「卡片 / 表格」切换器(Segmented)
- 加「按状态 / 按类型」切换器
- 加 Tabs 让一个页面承担多种数据呈现
- 详情页加 4 Tab 以外的非标准切换
不需要审核的场景
- PageContainer.tabList(标题栏带 Tab)的状态筛选 — 已是全局规约
- 详情页的 4 Tab 关联资源 — 已是详情页规范
- Drawer 内 view 切换(如 SPU 子视图)— 已写过
ui/doc/drawerViewSwitch.md
为什么
视图切换会让用户认知模型不稳定,是重设计决策,应该 PM 拍板而不是开发凭"我觉得这样更好"加。
详细规约:memory feedback_view_toggle_approval.md
PageContainer 标题栏带 Tab 是固定形态
用 ProLayout 原生 tabList + tabActiveKey + onTabChange 实现标题栏 Tab,已有 4 条 :has CSS 覆盖把布局对齐到总高 90px。
标准实现
<PageContainer
backIcon={false}
tabList={[
{ tab: '全部', key: 'all' },
{ tab: '在售', key: 'active' },
{ tab: '已下架', key: 'offline' },
]}
tabActiveKey={statusTab}
onTabChange={(key) => setStatusTab(key)}
>
<AlayaProTable ... />
</PageContainer>
不要这样做
- ❌ 在 body 里自加
<Tabs>模拟标题栏 Tab — 会破坏 PageContainer 总高布局 - ❌ 用 Segmented 替代 tabList — Segmented 是 mini 切换器,不是页面级筛选
详细规约:memory feedback_pagecontainer_tabs.md + ui/doc/pageContainerWithTabs.md
inline icon 跟文字水平居中
SVG icon + 文字共存时必须 inline-flex + alignItems: 'center' + lineHeight: 0 三件套,否则视觉总会差几像素。
标准写法
<span style={{
display: 'inline-flex',
alignItems: 'center',
gap: 4,
lineHeight: 0,
}}>
<RiCheckLine size={14} />
<span style={{ lineHeight: 1.5 }}>运营中</span>
</span>
为什么
- SVG 默认
vertical-align: baseline,跟文字基线对齐而非中线 line-height影响 SVG 容器高度,造成"图标被文字行高拉低"- 三件套:父级 inline-flex + alignItems center 强制中线对齐 + 父级 lineHeight 0 消除文字行高影响 + 子级 lineHeight 恢复
老踩坑
每次都"看上去差一点点"调半天,最后回归三件套。已经被坑过 N 次,记到 memory 了。
详细规约:memory feedback_inline_icon_alignment.md
切图比例不准擅自 stretch
切图(设计师给的 4x PNG 资源)禁 background-size: 100% 100%。用 background-size: auto 100%(或 contain),或调容器尺寸 = 切图原比例。
为什么
- 切图里有端头装饰 / 曲率 / 高光等按原比例画的视觉细节
100% 100%强制拉伸 → 圆角变椭圆 / 曲线变形 / 高光位置漂移- 设计师切图时已经按真实容器尺寸做了像素级调整,前端只需配合不要破坏
正确做法
/* 容器尺寸保持切图原比例 */
.banner {
width: 1280px; /* 切图原始宽 ÷ scale */
height: 320px; /* 切图原始高 ÷ scale */
background-image: url(/banner.png);
background-size: auto 100%; /* 高占满,宽按比例 */
background-repeat: no-repeat;
}
老踩坑
长江小学安防大屏 / 大巴山中药材驾驶舱多个 demo 项目都踩过这个坑 — 切图拉伸后设计师不认。
详细规约:memory feedback_preserve_cut_aspect.md
Drawer view 内部切换(不叠层)
Drawer 内出现"子流程"(如新建商品流程里要新建规格族)时,用同 Drawer 内 view 切换,不要叠层第二个 Drawer。
实现思路
- 父 Drawer 一直 open,加
spuViewOpen等 state 控制内部 view - 父 Drawer 的
title/footer/body/closeIcon都按 view state 条件渲染 - 子 view 的 closeIcon 改成
<RiArrowLeftLine />返回箭头 - onClose:子 view 状态 → 返回父 view,不是关 Drawer
为什么不叠层
- 叠层 Drawer 视觉层级混乱(哪个是当前?)
- 背景遮罩重叠像素加深问题
- 用户关闭子 Drawer 容易误关父 Drawer
已实现案例
CommodityList 新建商品 Drawer → 「+ 新建规格族」子 view(task #103)
详细规约:ui/doc/drawerViewSwitch.md
Snapshot demo 模式
ops-center-web 的 demo 模式(部署到 alaya-ops-snapshot.pages.dev)通过 window.__SNAPSHOT__ 全局标记激活。
关键能力
- Mock 拦截:window.fetch 重写,所有
/api/请求返回 mockData 不真打后端 - access 全开:access Proxy 任何 key 返回 true,所有菜单可见
- 构建信息注入:
window.__SNAPSHOT_BUILD__ = { hash, time }供 UI 版本 tag 显示 - UI 版本标识:右上角橙色 tag「UI 版本」+ tooltip 显示 commit hash + build 时间
- 原型对比 FAB:右下角浮动按钮,点击叠层显示原型 HTML iframe,URL hash 联动子页
代码标记
所有 demo 模式相关代码用 @ui-change-snapshot 注释标记,方便合代码时整段删除恢复生产模式。
部署流程
# Node 20 切换
PATH="/Users/dang/.local/share/fnm/node-versions/v20.20.2/installation/bin:$PATH"
# 构建(输出到 dist/)
npm run build:snapshot
# 重命名
rm -rf dist-snapshot && mv dist dist-snapshot
# 部署到 CF Pages
npx wrangler pages deploy dist-snapshot \
--project-name alaya-ops-snapshot \
--branch main \
--commit-dirty=true \
--commit-message "<英文消息>"
详细规约:ui/doc/snapshotDemoMode.md + ui/doc/prototypeCompareFab.md
改动记录
本节按日期顺序记录 design-guo-minor 分支所有改动,**给前端同事 / PM 接手时快速对照"改了哪、为什么改、有什么风险"**。 每条改动包含:背景 / 涉及文件 / 风险点。
2026-05-01 ~ 02 · 接入 mfCloud 云端共享主题
前端架构师告知:Alaya 系产品(bp + ops + 其他)的主题切换标准做法是接入云端共享 ThemeProvider,CDN 加载,多产品同源。本地 config/theme.ts 改为 fallback。
| 文件 | 改动 |
|---|---|
src/app.tsx | 引入 ThemeProvider, { themeConfig } from 'mfCloud/ThemeProvider';memo.theme = themeConfig(远程加载);rootContainer 用 <ThemeProvider> 包裹;本地保留 colorBgMenuItemSelected: blue1 一项 override 兜底 |
config/theme.ts | 改为构建期 fallback:gray 色板 / 文本色 / 边框色 / 填充色 / 背景色全部对齐公司设计规范;主色 #FF921C 橙 → #4362FF 蓝;间距 spacing 统一走 AntD 标准 token,不自造 spacing* |
config/env.js | envName: 'dev02' → 'unite'(dev02 环境无法连接,统一走 unite 测试环境 https://catalog.unite.zetyun.cn) |
mfCloud/ThemeProvider 是远程模块,TS 类型缺失,需 @ts-ignore。云端 mfCloud 暂未配置 colorBgMenuItemSelected,本地 override 兜底;云端补齐后删。
2026-05-02 · 顶部导航栏全部走 AntD token
src/app.tsx 顶部导航栏原有大量硬编码(logo 高度 48 / 标题色 #333 / marginLeft 10 / fontWeight 450 等非 AntD scale),全部 token 化。
| 字段 | 改前 | 改后 |
|---|---|---|
| logo 高度 | 48 硬编码 | var(--alaya-control-height-lg) = 40 |
| 标题字号 | 字面量 | var(--alaya-font-size-lg) = 16 |
| 标题颜色 | #333 | var(--alaya-color-text-heading) |
| logo 与标题间距 | 10px(非 AntD scale) | var(--alaya-margin-xs) = 8 |
| 标题 font-weight | 450(非标准) | 500 |
| logo 容器对齐 | 默认 | display: flex; alignItems: center 让图片与文字水平居中 |
2026-05-02 ~ 03 · 侧栏图标全局替换(codesign iconfont → 恐龙图标库)
原侧栏菜单用 codesign 上传的 iconfont(icon-yy-xxx)。前端要求改用恐龙图标库(duotone 风格),通过本地 SVG + DinoIcons 组件承载。后期再统一上传 codesign 生成 iconfont 替换回。
| 文件 | 改动 |
|---|---|
新建 src/components/DinoIcons/index.tsx | 组件本体:根据 name prop 渲染对应 SVG(用 <use href="#i-xxx"/> 引用 sprite) |
新建 src/components/DinoIcons/index.global.less | 默认色 colorTextTertiary(gray6);选中/hover 跟随菜单文字色;下拉箭头色 colorTextQuaternary(gray5) |
新建 src/assets/icons/ 30+ SVG 切图 | 恐龙库 duotone 风格本地 SVG(iconWallet / iconBox / iconCoins / iconBagShopping 等),全部用 currentColor,受组件层主题色控制 |
替换 src/assets/icons/iconNotification.svg | 用恐龙库 communication/duotone/envelope-notification.svg 替换原图,并把 #fff 全替换为 currentColor |
config/route/platformRoutes/cost.ts | 费用中心 8 个菜单 icon 字段全替(账户/收支/订单/合同/账单/算力包/导出记录/会话监控) |
config/route/platformRoutes/misc.ts | 综合中心 8 个菜单 icon 字段全替(客户/商品/营销/权限/消息/系统设置 + 新增商品/计价) |
src/app.tsx | 导入 dinoIconMap,menuDataRender 中递归把 dino-* 字符串替换为 <DinoIcon /> 组件 |
src/assets/icons/ 下 SVG 上传 CoDesign 生成新 iconfont,届时改回 icon-yy-xxx 格式并删除 DinoIcons 组件 + 本地 SVG。
2026-05-03 ~ 05 · 商品 / 计价模块原型页面(4 步 Steps + 路由改造)
综合中心新增「商品」「计价」两个一级菜单,需要原型页面给业务方走查。商品管理是 4 步向导(15+ 字段 + 条件联动),Drawer 承载不下,改为独立页面 + Steps。
新增路由
| 路由 | 组件 | 说明 |
|---|---|---|
| 商品(icon: dino-bag-shopping) | ||
/ops/misc/commodity/product | commodity/ProductList | 产品管理列表 |
/ops/misc/commodity/list | commodity/CommodityList | 商品管理(卡片+表格双视图) |
/ops/misc/commodity/list/create | commodity/CommodityCreate | 新建(4 步 Steps 独立页) |
/ops/misc/commodity/list/:action/:id | commodity/CommodityCreate | 编辑商品 |
/ops/misc/commodity/bundle | commodity/BundleList | 套餐包管理 |
| 计价(icon: dino-coins) | ||
/ops/misc/pricing/aidc | pricing/AidcList | AIDC 定价列表 |
/ops/misc/pricing/meter | pricing/MeterList | 计量项管理 |
/ops/misc/pricing/billing | pricing/BillingMode | 计费模式 |
CRUD 模式
- ProductList · Drawer(600px) 新建/编辑 + Modal 下线
- CommodityList · 卡片/表格双视图 + 跳转独立页新建/编辑 + Modal 停售;含 Tier 2 资源告警 banner(详见下文 Alert 应用规约)
- CommodityCreate · 4 步 Steps 独立页面(ProForm + ProCard 水平布局)
- BundleList · Drawer(640px) 新建/编辑 + Modal 下线
- AidcList · Drawer(520px) 新建/编辑
- MeterList · Drawer(640px) 编辑 + 定价 Drawer + Modal 下线
- BillingMode · Drawer(560px) 新建/编辑 + Modal 绑定商品
2026-05-06 ~ 08 · PageContainer 多次迭代(核心改动)
PageContainer 是全局列表页容器,原有 3 处内联常量 + 硬编码像素,且 mfCloud 主题接入后外层白底块没了背景色(标题与下方 layout 灰底糊一起),需要重构 + 修复。
最终状态(建议前端只看这部分)
| 改动点 | 具体内容 |
|---|---|
| 新增文件 | src/components/pageContainer/index.less 承载非内联样式 |
| Alert 渲染位置 | renderAlert() 改到 header.footer(与标题同处白底块内);与标题间距 16px 走 token |
| Alert 样式 | 去掉 marginBottom(白底块自身 padding 已撑开下方)+ 去 border(白底块本身已与下方灰底有视觉边界) |
| Alert 其他 | 保持 master:图标 14/22/mr4 内联,padding 8 12 内联,type="info" 硬编码(业务侧不暴露 type 写口) |
| 主标题字号 | var(--alaya-font-size-lg) = 16,行高 24,weight 500(覆盖 AntD PageHeader 默认 fontSizeHeading4=24 偏大) |
| 外层白底兜底 | :global(.ant-pro-page-container-warp-page-header) 强制 background: var(--alaya-color-bg-container) + margin-block-end: var(--alaya-margin) (16) |
| 返回按钮 | var(--alaya-control-height-sm) = 24×24;font-size 12 字面量保留(AntD 没暴露 fontSizeSM=12 token) |
兼容性(9 个使用 alertProps 的页面)
BillMonth / HandmadeBill / BillSettlement / BillList / Export / overdraft / BundleList / AidcList / MeterList —— 业务渲染逻辑零变化,只是渲染位置从 children 区移入白底块 footer。
- HandmadeBill 内含
<DownloadBlobBtn>—— 测试通过,按钮在白底块内可正常点击下载 - BillMonth 多段条件渲染 —— 实际只渲染一行短句,无溢出风险
2026-05-07 ~ 08 · 设计规范页 design-system.html(本页)
前端 + 设计 + PM 缺一份共同对齐的"组件视觉规范 + Token 字典"。把所有 token、颜色、间距、组件状态、视觉规约整合成一个独立 HTML 页面,部署 CF Pages 给团队访问。
| 条目 | 位置 / 链接 |
|---|---|
| 源码(单文件) | /Users/dang/ops-center-web/design-system.html |
| 部署地址 | https://alaya-design-spec.pages.dev/ |
| CF Pages 项目 | alaya-design-spec |
| 部署命令 | npx wrangler pages deploy .deploy-spec --project-name alaya-design-spec --branch main --commit-dirty=true(先把 design-system.html 复制到 .deploy-spec/index.html) |
· 右侧浮层「Inspect 模式 ON/OFF」按钮
· 开启后 hover 任意
[data-spec] 元素 → 显示 spec tooltip(黑底)· 点击锚定(蓝色 outline)→ 按住 Alt + hover 另一个元素 → 画虚线测距(支持相邻和包含两种关系)
· 无锚定时 Alt + hover → 跟随光标显示
width × height
2026-05-08 · Alert 应用规约(产品 × 前端共同对齐)
Alert 在项目里有多种语义被混用:① PM 写文案时不区分"模块描述"和"alert 提示";② 同一个文案位(PageContainer)被两个 API 混用;③ 商品管理的业务告警一开始我用了独立 <Alert type="warning">,跟其他页面不统一。统一收口:所有页面级 Alert 都走 PageContainer.alertProps,type 由业务传值(info / warning),位置统一进白底块。
· 新增 section "Alert 应用规约"(核心判定 + type/颜色/场景 + 全项目盘点 + 文案标点 + FAQ)
· 盘点 15 处 info 现有文案,分 A 留 / B 待 PM 复核 / C 删 三类,含菜单位置
· 统一规则:所有 alert 走
alertProps,type 可传 info | warning,组件内按 type 自动选图标和颜色· 商品管理 warning 告警迁移:从独立
<Alert banner> → alertProps={{ type: 'warning', description: ... }},渲染到白底块内,支持右侧 action 链接("查看影响明细")· 文案标点统一不带句号,含 4 条 WHY
本节配套产出
| 文档 | 受众 / 用途 |
|---|---|
| 本设计规范页(design-system.html) | 全团队(前端 / 设计 / PM)—— 视觉规范 + 组件状态 + Token 字典 |
CHANGELOG-design-guo-minor.md(项目根) | 前端 —— 开发视角详细记录,git 提交时会跟代码一起留底 |
| Obsidian 笔记(个人) | 个人 —— 跨产品对比 / 决策回溯 / 跟其他 Alaya 产品的横向参考 |
2026-05-09 · 商品 / SKU 详情页 + 全局规范扩充
商品管理列表 / LLM 推理服务 / SKU 详情页迭代过程中沉淀的全局规则,全部进 src/global.ui.css + ui/doc/ 留底。
· PageContainer 主标题字号 16 → 18px(行高 24 → 28px),主副标题 flex 居中 — 见上方《内容区标题 + Alert》节
· 表格列规范新增独立章节 — 操作列宽度 6 档位 / 数值列右对齐 / D-DIN 数字字体 / 删除 friction 规则
· D-DIN 字体启用 — global.css 早就 @font-face 注册了但项目没用,本次首次落地到商品卡指标 / 价格 / SKU 列表
· 操作列档位"最小可读 vs 视觉舒适"区分 — 之前只给最小档位被反映"局促",文档补充舒适宽度 ≈ 档位 × 1.25
本轮配套产出
| 文档 | 主题 |
|---|---|
ui/doc/pageHeaderTitleSize.md | PageContainer 主标题 18px + flex 居中 |
ui/doc/tableColumnSpec.md | 表格操作列宽度档位 + 价格数值列右对齐 |
ui/doc/customSegmentedTabs.md | alaya-tabs 自定义选项卡(独立卡片样式 Segmented) |
ui/doc/noEmojiIcons.md | 禁 emoji 作为图标(用 RemixIcon / DinoIcons) |
2026-05-09 · 表单选择器规范(4 种组件取舍)
迭代过程中反复在 Segmented / Radio.Group(button) / 自定义 alaya-tabs / Select 之间切换,沉淀决策树。
· 2-4 选 + 渐进/视图切换 → AntD
Segmented 默认· 2-4 选 + 含图标 + 重要决策 → 自定义
alaya-tabs(独立卡片)· 2-4 选 + 短词无图标 → AntD
Radio.Group(button)· ≥ 5 选 →
Select(≥ 8 加 showSearch)· 选项卡只要标题 — 不带描述,详细解释走
Form.Item.tooltip
详见上方《表单选择器组件》整章。
2026-05-11 · ProTable 表格操作区 Toolbar 右侧 padding 对齐
修正 .ant-pro-table-list-toolbar-container 与下方表格单元格右沿错位 16px 的视觉 bug。
padding-inline 必须与表格单元格 cellPaddingInline 对齐,不能再叠加上层容器的 padding。· 层级累积:
.ant-pro-card-body(24) + .ant-pro-table-list-toolbar-container(原 24) = 右侧 48px· 单元格:
.ant-pro-card-body(24) + cellPaddingInline(8) = 右侧 32px· 两者相差 16px,导致 toolbar 右侧按钮(齿轮/刷新)凭空浮在表格列右沿之外
· 修法:把 toolbar 的
padding-inline 改成跟 cellPaddingInline 一样(8px),让 toolbar 内容右沿与表格内容右沿对齐
修改文件:src/global.ui.css(!important 规则 24 → 8)+ src/components/AlayaProTable/index.global.less(注释同步)+ ui/doc/toolbarPaddingFix.md(文档)
通用启示:覆盖 AntD ProTable 组件样式时,必须先确认有几层嵌套容器都加了 padding——任何"右侧多出一截"的视觉感往往来自两层 padding 叠加,不是 AntD 默认偏大。
2026-05-11 · 5 个 Drawer 统一规范重做
把 ProductList / BundleList / AidcList / MeterList / BillingMode 5 个新建/编辑 Drawer 全部对齐 CommodityList 4 步 Steps 抽屉的规范。新增 2 个共享卡片组件,统一 4 态生命周期颜色映射。
· Footer 用
footer prop 取消左 / 主操作右(不再用 extra)· 表单单列垂直(删 Row+Col 两列,Form.List 行内字段例外)
· 新建共享组件:
src/components/AlayaCheckboxCards/(多选卡)+ src/components/AlayaRadioStackCards/(3-4 选 1 卡)· BundleList 新增 RESERVED 包月预留(DEDUCTION / DCU_PACK / RESERVED 三类)
· 4 态颜色统一:内测灰 / 公测橙 / 运营绿 / 已下架灰(Badge status 用 default/warning/success,禁用 processing 蓝)
· 全部删除:📐 字段规则灰底块 / 🔒 LockTag「发布后锁定」/ DCU 概念 Alert / RESERVED 警告 Alert / 所有 emoji 图标
详见 Drawer 统一规范 节、ui/doc/drawerStandard.md、CHANGELOG-design-guo-minor.md「2026-05-11 · 5 个 Drawer 统一规范重做」。
LockOutlined 是 RESERVED 类型功能图标 + Form.List 内 row-level 锁定状态指示,不是「发布后锁定」备注 LockTag,接手时不要误删。
2026-05-12 · 卡片视图规约定型 + 状态/分类 Tag 统一 + Loading 改造
围绕"业务对象类列表卡片"的视觉规约定型,覆盖状态显示、分类显示、操作位置、卡片样式、首屏 loading 五大维度。
· 分类用文字描述(不用 Tag color):产品线 / 类型 等分类信息改 plain 灰字描述,跟状态 Tag 视觉拉开
· 卡片操作位置规约:危险操作左下(danger 红 + 物理分离防误触),主操作右下(Z 型扫读落点),操作行
margin-top: auto 锁底跨卡水平对齐· 全局 Card box-shadow 压平:默认 none,hoverable hover 时
0 4px 16px rgba(15,23,42,.08) 干净微投影· 全局 Button 内图标对齐:
.ant-btn .ant-btn-icon 强制 inline-flex 居中,防 RemixIcon baseline 偏下· 首屏 Loading 改造:旧 AntD 4 方块橙 → 云朵 logo 双层描边循环 + 内联骨架屏接管(详见 Loading 规范)
· 编码字段统一展示:禁用
<Typography.Text code> 带边框等宽样式,卡片用灰色小字 span,表格用 Typography.Link· 不擅自改 PM 文案 + 业务模型:CommodityList 2 态 active/offline = 运营/停售 PM 原文案保留,不擅自升级 4 态
详见 卡片列表规约 + 首屏 Loading 规范 节,ui/doc/cardListStandard.md + ui/doc/loadingSpinner.md,CHANGELOG-design-guo-minor.md「2026-05-12」。
2026-05-12 收口 · 列表视图模式最终规约 + 下架按钮颜色统一 + 编码 Link 规约
当天晚些时候用户三次拍板后的最终规约。
· 下架按钮颜色统一红色:表格行
<Typography.Link type="warning">(橘色) → <Typography.Link type="danger">(红色),跟卡片 Button danger 视觉一致。下架是破坏性操作(改变发布状态),用 danger 红 跟"删除"同语义,warning 橘是"待处理/风险提示"不适合。· 主题色 = 可下钻规约:表格 code 列只在有详情页时用
<Typography.Link> 蓝色(+ onClick 跳详情),无详情页必须用 <Typography.Text> 普通黑字。死链接(蓝色但无跳转)跟"主题色 = 可下钻"潜规约冲突,用户会困惑。
详见 表格列规范 · 编码字段展示 节。
2026-05-13 · BillingMode / MeterList 对齐原型 + 6 模块 toast 文案统一 + 价格颜色规约
围绕计价模块两张表单页 + 全局 toast 文案 + 数值色规约的三大整改。
· 计费大类 AlayaRadioStackCards:5 卡片 3+2 grid,emoji 全删用 RemixIcon(PAYG=Timer / TIERED=BarChart2 / SUBSCRIPTION=Calendar / SPOT=Flashlight / QUEUE=OrderPlay),description 3 行(title + 主描述 + 二级描述)
· 模式系数特征盒子:灰底盒子「全局系数 = [InputNumber] × 基准单价」+ 蓝 Alert 业务说明 + 演示行(vks.cpu.hours / cci.disk.gb.hours 实时演算)— 严格按原型 modeNew/modeEditPAYG ②
· PAYG 计价参数 4 字段:付款时机 / 计量粒度 / 计费粒度 / 金额取整,全 2 项 Radio,选项数 + 文案严格按原型 modeNew 行 2379-2382(不擅自增加项)
· MeterList Drawer 5 section:基础信息单列 + 「高级 · LLM 父子关系」Collapse 仅 LLM 出现 + 基准定价(紫 Alert)+ DCU 速率(黄 Alert)+ 算力中心系数(蓝 Alert,表 6 列含实际DCU/说明 + 杭州行紫底「基准」Tag)+ 生效时间(蓝 Alert 版本规则)
· MeterList 列表表格:删「产品」列;单价/DCU 与单位并排展示(
inline-flex; align-items: baseline),不堆两行;列宽加宽· 价格色规约:
--alaya-color-warning 橘是"告警"语义,价格不用。统一改 --alaya-color-text 默认黑 + font-weight: 600;已下架走 text-quaternary 灰。详见 价格 / 数值类显示规约· 6 模块 toast 文案严格按原型
showToast 原文:禁用 operateSuccess 泛文案,下架类用 message.warning 不用 success。详见 Toast 文案规约· 启用状态字段 label:用
pages.pricing.mode.status id 避免被全局 status 翻译表覆盖成「状态」· 绑定 Modal 居中:
centered prop + 标题对齐原型「绑定适用商品」+ 外框 320px 高纵向滚动 + webkit-scrollbar 主题 token· BillingMode KPI 卡
bordered={false}:跟内容区背景无缝过渡("内容区大背景不带边框"惯例)· TOU 分时计价折叠删除:本期不做,恢复参考 git history 5/13 之前
详见 Toast 文案规约 + 价格 / 数值类显示规约,ui/doc/toastWording.md + ui/doc/priceDisplayRule.md,CHANGELOG-design-guo-minor.md「2026-05-13」。
Toast 文案规约
2026-05-13 由 PM 反馈定型:"6 模块新建/编辑/下架的 toast 全部用『操作成功』,原型已经写好了专属文案,为什么不照着用?"
硬规
intl.formatMessage({ id: 'operateSuccess', defaultMessage: '操作成功' }) 泛文案· 必须查原型 HTML 里对应操作的
showToast(...) 字符串照抄(~/Desktop/v2/商品.html / 计价.html)· toast 类型:创建/编辑/启用 →
message.success;下架/停售/状态变更 → message.warning(destructive 不是 success);删除/失败 → message.error
6 模块落地清单
| 模块 | 新建 | 编辑 | 下架(warning) |
|---|---|---|---|
| ProductList | 产品已发布 | 产品配置已保存 | 产品已下架 · 切换列表「已下架」筛选可查看记录 |
| CommodityCreate | 商品已创建 · 批量生成 ${n} 个 SKU | 商品配置已保存 | — |
| BundleList | 套餐包已发布 | 套餐包配置已保存 | 套餐包已下架 · 已购余量到期清零 |
| AidcList | 算力中心已发布 | 算力中心配置已保存 | — |
| MeterList | 计量项已发布 | 已保存 · 价格变化生成新版本 · SKU 价格已重算 | 计量项已下架 · 切换列表「已下架」筛选可查看记录 |
| BillingMode | 新模式已创建并启用(内测) | 配置已保存 | — |
| BillingMode 绑定 Modal | 已绑定到选定商品 | — | — |
ui/doc/toastWording.md。同主旨规约:feedback_no_product_copy_edit.md。
价格 / 数值类显示规约
2026-05-13 PM 反馈:"计量项列表的单价为什么用橘色?我们没这种用法。" 起源 MeterList 旧版用 var(--alaya-color-warning) 给单价上橘色 — warning 是"告警"语义,价格不是告警。
硬规
var(--alaya-color-text) 黑色 + font-weight: 600 bold 给视觉权重· 已下架 / disabled 行用
var(--alaya-color-text-quaternary) 灰· 价格 + 单位并排(
inline-flex + align-items: baseline + gap: 4px),不堆两行。单位 12px text-quaternary· 例外:演示行 / 公式结果可用
--alaya-color-primary 蓝高亮(如 BillingMode 系数演算);大屏 KPI 数字趋势色(success 绿 / danger 红)与计费单价场景脱钩
颜色语义对照
| Token | 设计意图 | 用在哪 |
|---|---|---|
--alaya-color-text | 默认黑 | 价格、DCU、系数、数量、文字 |
--alaya-color-text-quaternary | 浅灰 | 单位、说明、已下架、placeholder |
--alaya-color-primary | 主蓝 | 链接、操作、Tag processing、公式结果 |
--alaya-color-success | 绿 | 状态 Tag「运营」、成功 Alert |
--alaya-color-warning | 橘 | 状态 Tag「公测」、告警 Alert ⚠️ 不要用在价格 |
--alaya-color-error | 红 | 危险操作、destructive Tag |
代码模板
// MeterList 标杆 (.less) .priceInline { display: inline-flex; align-items: baseline; gap: 4px; } .priceMain { font-weight: 600; color: var(--alaya-color-text); /* ✅ 默认黑 */ } .priceOffline { font-weight: 600; color: var(--alaya-color-text-quaternary); /* 已下架灰 */ } // .tsx 渲染 <span className={styles.priceInline}> <span className={record.status === 'offline' ? styles.priceOffline : styles.priceMain}> {record.basePrice} </span> <span className={styles.priceUnit}>{record.basePriceUnit}</span> </span>
ui/doc/priceDisplayRule.md。
业务对象类列表卡片规约
所有"业务对象类"列表卡片(ProductList / CommodityList / BundleList 等)按本规约定型。涵盖容器 / 卡片头 / 状态显示 / 分类显示 / 操作位置 / 视图切换 6 个维度。
硬性规约
height: 100% + ant-card-body display: flex; flex-direction: columnRow gutter [16, 16] + Col span={8}<Typography.Text code>)margin-top: auto 锁底 / 危险左 / 主操作右 / type="link"卡片操作位置规则(核心)
┌──────────────────────────────┐ │ 卡片信息... │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ [危险] ·· [次] [主] │ │ 左 右 │ └──────────────────────────────┘按钮规约:全部
<Button type="link" size="small">,危险操作加 danger 但不加图标(danger 红色已区分破坏性,加图标视觉重量超过主操作)。
状态 Tag vs 分类 Tag — 视觉重量必须拉开
| 维度 | 状态(生命周期) | 分类(产品线/类型) |
|---|---|---|
| 组件 | <Tag color="success"> 彩色背景 | <div className=styles.cardLine> 文字描述(推荐)/ 或 <Tag> plain 灰边框 |
| 视觉重量 | 突出(彩色块),运营关心 | 低调(灰字),辅助信息 |
| 颜色 | 4 态规约:success 绿 / warning 橙 / default 灰 | 无 color,灰字或灰边框 |
| 禁用 | processing 蓝(跟主题色冲突) | hue color(blue/cyan/gold/purple)— 跟状态 Tag 同款视觉 |
代码模板
// cardHead:图标 + 标题块 + 状态 Tag <div className={styles.cardHead}> <img src={iconMap[item.code]} width=64 height=64 /> <div className={styles.cardTitleWrap}> <h3 className={styles.cardTitle}> {item.name} <span className={styles.cardCode}>{item.code}</span> </h3> {/* 分类用文字描述 */} <div className={styles.cardLine}>{item.line}</div> </div> {/* 状态用彩色 Tag */} <Tag color={statusTagColor[item.status]}>{statusLabelMap[item.status]}</Tag> </div> // actionRow:危险左 / 主操作右 / 锁底 <div className={styles.actionRow}> {item.status !== 'offline' && ( <Button type="link" size="small" danger onClick={handleOffline}>下架</Button> )} <div className={styles.actionSpacer} /> <Button type="link" size="small" onClick={handleEdit}>编辑</Button> <Button type="link" size="small" onClick={handleView}>查看商品</Button> </div>
ui/doc/cardListStandard.md。memory 沉淀:
feedback_card_actions_position.md /
feedback_card_action_lock_bottom.md /
feedback_status_4state_colors.md /
feedback_tag_plain_classification.md /
feedback_code_field_display.md /
feedback_no_product_copy_edit.md
首屏 Loading 规范
首屏 loading 由三层视觉接力,HTML 静态层做,不依赖 React。文件位置:public/scripts/loading.js(首屏预加载脚本)+ src/components/loading-ui/(应用内 React 组件库)。
三层接力
LogoSpinner 关键技术点
· SVG
pathLength="100" 属性:必须写在 path 元素上(不是 CSS),让 stroke-dasharray: 100 标准化所有 path 描边时长一致· stroke-linecap: butt:端点截断(不圆头),起笔/终笔轨迹更明显
· 瞬间复位避免呼吸闪烁:keyframes 85%→86% 间
stroke-dashoffset: 0 → 100 瞬间跳变(1% 时长),CSS animation 不插值,视觉上是"消失重画"而非"渐隐"
内联骨架屏(关键设计决策)
React registerLoaded 函数(src/app.tsx)会物理 removeChild(#globalLoading) 删 logo DOM 节点,CSS opacity transition 无效。所以骨架屏必须从启动就在 DOM 底层 opacity:1,logo 上层覆盖 — React 删 logo 瞬间骨架屏自动暴露,零间隙。
src/loading.tsx + Suspense fallback)— 会引入延迟:骨架屏组件本身是 React chunk 需要加载,反而拖慢首屏。正例:骨架屏 HTML 内联到
public/scripts/loading.js 里,跟 logo 同 DOM,0 chunk 加载延迟。
应用内 Loading 组件库
| 组件 | 视觉 | 用法 |
|---|---|---|
<CometSpinner /> |
彗星拖尾环形 spinner(conic-gradient + radial mask) | <CometSpinner className="size-12" /> |
<TextDots /> |
文字 + 跳动省略号(3 dot opacity 错开波浪) | <TextDots>Thinking</TextDots> |
<TextShimmer /> |
文字扫光(亮带从左到右掠过) | <TextShimmer>Processing</TextShimmer> |
已知局限(不再尝试解决)
真正能根治的方案需要 React app 自己做:ProLayout
loading={!initialState} 配置 / PageContainer Suspense fallback / ProTable 数据加载 Skeleton — 属业务层范畴,UI Developer 不动 src/app.tsx。
详细规约 + 关键代码 + 移除时机逻辑 + 反例清单,见 ui/doc/loadingSpinner.md。