PRD-项目创建与管理阅读预览 · 表格与标题已排版
打开交互原型 下载 Markdown 打开源文件 .md

PRD:项目创建与管理(价目表 · 商户端)

字段 内容
文档版本 v1.33
日期 2026-09-24
交互原型(唯一可视依据) 同目录 index.html(价目表独立原型,390 × 844;深链清单见 README.md);线上预览 https://xdean-designer.github.io/menu/(GitHub Pages,main 分支根目录);大原型镜像 ../card/demo.html(中文镜像:剑琅联盟-RTB重构.html;功能链路「项目创建与管理」)
画布 390 × 844
目的 供前端 / 后端 / 测试及 AI 工具落地「价目表(项目·产品)」;只需本文 + 交互原型
关联(非必读) 同目录 PRD-会员卡管理.md(办卡权益选品依赖本文价目数据;会员卡流程以该文档为准);PRD-迁移方案-大类转主分组.md(本模块替换线上价目时的数据迁移与提成口径方案,上线前必读)

修订记录

版本 日期 内容
v1.33 2026-09-24 补产品维度的「分组管理·行展开」演示节点与 Figma 帧(原型仅新增一条深链,无功能与视觉变更)。① 背景:既有演示节点 price-group-menu(行展开态)只在项目维度,产品维度缺失 —— 左侧导航「分组行展开」、Figma 第 18 帧均只有项目版。② 新增节点 price-group-menu-product:与 price-group-menu 完全对称,仅把桶从 project 换成 product —— 先 state.priceCatalogTab = 'product' + syncPriceCatalogTabs()(入口文案与计数随之变 「包含产品 / 个产品」、分组数据取 seedCatalogGroups().product),再 openCatalogGroupManage(),最后显式展开首个自定义产品组「洗护」(state.catalogGroupExpandedId = g.id)。进页默认仍收起,仅该深链显式展开 —— 与 v1.20「进页一律收起」不冲突。③ 左侧导航新增「分组行展开 · 产品」(_build-index.js 的 navSheets → 重新生成 index.html)。④ Figma「价目表-开发🔥」补第 19 帧 价目表-19 分组管理·行展开(产品):原 价目表-18 分组管理·行展开 补 「(项目)」后缀、与 05 分组管理(项目) / 06 分组管理(产品) 的命名口径对齐;新帧紧接 18 之后插入,其后 10 帧(原 19–28)顺移为 20–29 并整体重排 x。⑤ 验证:_tmp_verify_groupmenu_prod.mjs 14 PASS / 0 FAIL(含「产品维度落「包含产品」且不含「包含项目」」「有且仅有一行展开(手风琴)」「系统『隐藏』组不可拖拽」「项目版 18 未受影响」等;均为实测断言)。改动同步 §4(深链清单)/ §6.4(深链补充说明)。
v1.32 2026-09-24 修掉分组操作「写副本」缺陷(同一根因 4 处):删除 / 新建 / 拖拽排序对分组数据的写入全部落空,且 toast 谎报成功。① 根因:getActiveCatalogGroups() 每次调用都新建一个数组(groups.filter(g => !g.system) 之后再包一层 [...custom, sys]),它只是渲染用的只读视图;而四处结构写操作误把它当成数据本体 —— confirmCatalogGroupDelete 的 splice、commitCatalogGroupName 新建分支的 push、commitCatalogGroupOrderFromDom 的顺序回写 splice、moveCatalogGroup 的槽位交换,全部作用在副本上,state.catalogGroups[bucket] 从未被改动;副本一被丢弃改动即消失,于是表现为「弹窗关掉了、toast 报了成功,界面却什么都没变」。② 三个用户可见症状:删组(本次上报)—— 确认后被删组仍留在分组管理列表与分组轨上(实测删除前 9 组 → 删除后仍 9 组,「洗吹」未消失);新建 —— toast「已新建分组」但列表 / 分组轨 / 「设置分组」Sheet 里都不出现新组;拖拽排序 —— 松手后顺序回弹(顺序只是 DOM 呈现,重渲染即按 state 恢复)。moveCatalogGroup 当前无调用方(死代码),同根因一并修正。③ 修法(只改写入端,视图语义与展示顺序不变):四处结构写操作一律改落到 getCatalogGroupsForBucket() 返回的活数组(即 state.catalogGroups[bucket] 本身);删除时若 state.catalogGroupExpandedId 指向被删组则一并清空(避免留悬空展开态)。getActiveCatalogGroups() 继续只作只读 —— 供渲染、重名校验,以及「上 / 下移」按视觉邻居定位真实槽位(indexOf)之用;moveCatalogGroup 因「视觉顺序 = 自定义组… + 系统组」而改用 indexOf 映射,不再假设两者下标相同。④ 行为定稿:删除确认后该组立即从分组管理列表与分组轨消失,组内条目本身不受影响(实测成员 p1,p3,p19 均仍在价目表);若删的正是当前筛选组,筛选回落「全部」;系统「隐藏」组仍不可删、不可改名、仍固定末尾;新建后立即出现在列表 / 分组轨 / Sheet(在空态 Sheet 走「去新建分组」时回填并默认勾选新组,仍须 Sheet「确定」才写入成员);拖拽顺序落库并跨重渲染保持。⑤ 验证:_tmp_verify_groupcrud.mjs 29 PASS / 0 FAIL(全程真实点击 / 真实指针拖拽驱动,并对 window.state 做独立断言;覆盖项目 / 产品两个 Tab 的删组、系统组拦截、删除后重渲染持久、新建后回填勾选、拖拽持久等)。改动同步 §6.4 / §6.6 / §7.3 / §10.2(+84~86)。
v1.31 2026-09-24 四处视觉调整:选中态红改橙 + 展开区右侧数值收敛为单档黑 + 保存按钮改浅底(tinted)+ 保存图标换线描「存入托盘」。① 选中态改橙(新增色 --brand-orange: #FF7A45):主 Tab(项目 / 产品)选中文字与指示条、分组胶囊选中文字,四处声明由 var(--brand)(#F32F41)改 var(--brand-orange)。严格最小范围 —— 其余红一律不动:标题栏「+添加」文字链、虚线添加行、空态 CTA、分组管理「新建分组」、「已绑卡」行名 #333333、必填星号、排序箭头、switch.on、勾选框、弹窗「确定/删除」、左滑删除底,均仍为品牌红 #F32F41(实测已断言)。分组胶囊选中仍为白底,只换字色。② 展开区右侧数值改主文字档 #1A1A1A,时长控件除外:#fProjName(原 #929292 灰)与 #fPrice(原 #FE8D4B 橙)统一为 #1A1A1A,与同区标签 / 时长数字框 / 规格输入框同档,同屏只有一档黑;字重仍 Medium(500)、仍右对齐。这是产品决定,明确偏离 Figma 人工稿 1258:616 / 1258:624(该稿指定「灰字 + 品牌橙」),非实现偏差。时长控件(数字框 #1A1A1A / 未选单位 #929292 / 选中单位 #FF8A4C / 滑块底 #FE8B4B@10%)保持原样。③ 折叠行:价格 .price-num 由主红 #F32F41 改 #1A1A1A(红色让位给「选中态」);名称 .name-text 由「继承初始黑 #000」显式锁到 #1A1A1A,与同行价格同档。两个语义例外用 :not() 排除、原声明不动:.is-bound 绑卡名 #333333、.is-off-sale 下架名/价格 #929292。④ 展开态标题行项目名转灰 #929292:名称已在展开区字段里以可编辑值呈现,标题行降级为「这是哪一行」的定位线索,避免同屏两个等重黑体项目名;仅作用于 .has-inline-panel,折叠行不受影响。⑤ 保存按钮改 tinted:底由实心 #F32F41 改 --brand-soft #FFF2F2、字由白改 品牌红 #F32F41、按下 #FEEAEC;满宽 / 高 48 / 圆角 12 / 页脚发丝线一概不变(只降对比度、不减触摸区)。存盘图标由强制 #fff 改 currentColor 随之转红(图形本身在 ⑥ 换稿)。弹窗「确定 / 删除」仍实心品牌红不动(强打断场景,实心红是合理的)。⑥ 保存图标换稿:实心软盘 → 线描「存入托盘」(arrow → line)。换稿三依据:① 本模块 19 个图标里 17 个是线描(Stratis 族),实心软盘是其中唯一的普通控件类实心图标(另一个实心是左滑「删除」上的锁角标,徽标类实心合理);② 原图标 13.5px 框里塞 3 段子路径(外框 + 快门 + 标签块),16px 下过密,还要靠 stroke-width="0.2" 反向补线;③ 按钮转 tinted 淡底后,那块实心红方块成了按钮上最重的东西,比 16px 的「保存」二字更抢眼。新图标规格:3 段线(箭杆 M12 3.4v11.2 / 箭头 M6.6 9.6l5.4 5.4 5.4-5.4 / 底线 M3.4 21h17.2)、viewBox 0 0 24 24、stroke-width 2.3、圆头圆角收笔 —— 渲染 16px 时有效线宽 1.53px,与图标族 1.56px 同档;墨迹 13.0 × 13.27px(≈ 原软盘 13.5px,换图标不改变视觉重量);墨迹在 16px 框内水平居中 8.0 / 垂直 8.13(偏差 ≤0.3px),与文字的盒中心 y 偏差 ≤0.6px。⚠️ 一并删除的 CSS:base.css 里 .catalog-inline-save__icon path { fill: currentColor; stroke: currentColor } 是为实心图标写的,留着会把线描图形整体填实,已删除;fill / stroke / stroke-width 一律由 SVG 自带属性给出,与 PROJ_CHEV_ICON / CATALOG_DUR_CLOCK_ICON 等其余线描图标同口径。按钮尺寸 / tinted 配色 / 与文字 gap 6 一概不变。⑦ 验证:_tmp_verify_v131.mjs 28 PASS / 0 FAIL(含「未波及项仍为 #F32F41」的反向断言);换图标项另有专项 _tmp_verify_saveicon.mjs 18 PASS / 0 FAIL(含「落到 DOM 里仍是线描、fill 为 none、未被填实」的断言);全量回归 _tmp_verify_catalog.mjs 100 PASS / 0 FAIL、_tmp_verify_hidden_toggle.mjs parity 六项全 true。改动同步 §6.1(主 Tab 规格 / 分组轨 / 行)/ §6.1.1(字段区视觉规范 / 页脚 / 保存按钮)/ §6.2 / §6.3 / §10.2(验收 47 / 53 / 55 改写,+80~83)。
v1.30 2026-09-23 「已隐藏」折叠条改为「已下架」同款版式(仅底色与文字色保留差异)+ 修掉两分区交界处叠线(承接 v1.29 的分区标题改造)。① 版式统一:.catalog-hidden-toggle 原为 space-between、min-height:37px、padding:8px 12px、15px/400、眼睛图标在左 + 折叠箭头在右;现与 .catalog-offsale-head 完全同款 —— 同宽 388 / 同高 26(写死 height+min/max-height)/ 13px / 400 / justify-content:center / padding:0 16px / line-height:18px,实测 parity heightSame / fontSizeSame / weightSame / centerSame / padSame 全为真。② 只保留两处刻意差异:底色 #EEEEEE(vs #fff)与文字色 --text #333333(vs --text-sec #929292)=「同款但文字颜色区分」;块底 #EEEEEE 维持 v1.19 规则不变(实测 hiddenBlockBg 仍为 rgb(238,238,238))。③ 移除眼睛图标、保留折叠箭头:删掉 label 内的 ${CATALOG_HIDE_ICON}(常量本身保留,仍供「隐藏」Tab 使用,实测该 Tab hasIco:true 未受影响);箭头作为唯一「可展开」线索保留,并由 stroke="#929292" 改 stroke="currentColor"(随文字色 → #333,非文字对比度 2.59:1 → 10.6:1,越过 WCAG 3:1 建议线),尺寸 16→14px(按 v1.22「图标/文字 ≈ 1.07」比例折算);实测 14×14 / color: rgb(51,51,51) / stroke="currentColor"。④ 热区取舍:触控高度 26px(全宽 388)。放弃伪元素外扩至 44px —— 实测上方 1px 即为「已下架」区块内的可点行(ROW:接发)、下方 12px 后即为底部「添加项目」行,外扩会直接抢相邻元素点击,而 26+12=38px 仍不足 44。⑤ 修掉交界处叠线(真 bug):#projProjectTable .table-row-swipe:has(+ .catalog-hidden-block) .table-row::after 原为 display:block,而 .catalog-hidden-block::before 在 top:0 无条件画线 —— 当有隐藏、无已下架时二者落在同一 y、叠成一条比其它发丝线更重的线(已用左滑「上架」把演示数据构造成该状态复现:matchedByHasRule:['卸甲重做']、该行 ::after 曾被设为 block)。现改为 display:none,该线由隐藏块自己提供 —— 与 §6.1.1 对「已下架」的处理对称。实测三态 lineDrawers 均为单条:有已下架 ['HIDDEN_BLOCK::before']、无已下架同样 ['HIDDEN_BLOCK::before']、展开态同样 1 条。⑥ 验证:全量回归 _tmp_verify_catalog.mjs 100 PASS / 0 FAIL;_tmp_verify_hidden_toggle.mjs 已同步改写检查项(由「查眼睛图标」改为「查箭头 + 与已下架几何 parity」)。改动同步 §6.1(列表结构第 3 项)/ §6.1.1(分区标题与分区线)/ §10.2(验收 24 改写)。
v1.29 2026-09-23 评审反馈落地:分区标题补副标题 + 分区线方向统一(承接同事对原型的 5 条意见,仅第 2 条部分采纳,其余判定驳回/维持,理由见下)。① 分区标题补足区分度:已下架 / 已隐藏 两个分区标题原只有 4 字 / 3 字,评审者第一眼即误读为同一事物(原话「都是已下架的意思」),故补副标题点出业务差异 —— 已下架 · 停售(catalog.js L2420)/ 已隐藏 · 仅顾客端不可见(L2433)。「仅」字是关键:下架同样是「顾客端不可见」,若无「仅」则副标题反而无法与「已下架」区分(下架=顾客端不可见 且 开单 / 权益选品均不可选;隐藏=仅顾客端不可见、开单仍可选)—— 这也说明该混淆是语义问题而非样式问题:实测两标题本就不同规格(已下架 13px/400/--text-sec/居中;已隐藏 15px/400/--text/左对齐 + 折叠箭头)。② 分区线方向统一(反向是评审「感觉被分隔符全部分开了」的真实成因):.catalog-offsale-head 的分区线画在自己下方(background-position: 16px 100%),而 .catalog-hidden-block::before 画在自己上方(top: 0)—— 两处方向相反,导致「已下架」标题被两条线夹住(上=在售末行 .table-row::after、下=自己的线),再加「已隐藏」上方那条,同一处 78px 竖向区间内出现 3 条线。现将 .catalog-offsale-head 改为 background-position: 16px 0(线移到自己上方),并把早已存在却因特异性不足而失效的「在售末行让位」规则复活 —— 原规则 .table-row-swipe:has(+ .catalog-offsale-block) .table-row::after{display:none} 无 ID 前缀,被 #projProjectTable .table-row-swipe .table-row::after{display:block} 覆盖(v1.28 已记为死代码),现补 ID 前缀;两处线同 y、同内缩(16/16),若同时绘制会叠成 1px 重线,故必须让位。修正后层级为:在售末行无线 → 「已下架 · 停售」上方 1 条 → 「已隐藏 · 仅顾客端不可见」上方 1 条,两个分区的线与方向完全一致。③ 其余 4 条判定(均不采纳 / 维持现状):时长 分 / 时 双单位保留(服务时长跨度 5 分~8 小时,单用「分」需输 480、单用「时」需输 0.75,双单位是防单位错误而非多给一个选择;且控件是"数值 + 单位"同一字段,成本仅一次点选);两个添加入口保留(列表 27 行而可视区仅 9 行,标题栏「+添加」保证任意滚动位置都可达,底部虚线行对应「内容末尾就近」并承担「列表到此结束、可继续添加」语义,两个空态/长列表场景各有覆盖;三入口共用同一提交流程且已明确取消右下悬浮球,故非冗余);展开态行内隐藏时长 / 价格保留(数据就在同屏展开区表单里,非信息丢失;PRD 原意即「避免与展开区字段重复」,改回会出现同屏两处价格);绑卡行无可见标识维持现状(现实中绝大多数项目终将绑卡,为常态加标识=无信息量;原型中未渲染的 .bound-tag 死 CSS 留待后续清理,本次不动)。④ 改动性质:仅文案 + 一处 CSS 数值,无逻辑变更 —— 全量回归 _tmp_verify_catalog.mjs 100 PASS / 0 FAIL,_tmp_verify_hidden_toggle.mjs / _tmp_verify_final.mjs 均通过;新文案实测不换行、不溢出(分区头 h=26 未变、折叠条 h=39 未变、标签宽 189.1 < 可用宽,项目 Tab / 产品 Tab / 折叠展开态三种情形一致)。改动同步 §1.3 / §2(结构树)/ §6.1(列表结构)/ §6.1.1(分区标题与分区线规范)/ §10.2(验收 24 改写)。⑤ 其余章节仍以短名「已下架」/「已隐藏」指代该分区(属结构性称谓,如 §5 状态表、§6.1 拖拽拦截、§10.2 其它条目),显示文案一律以 §6.1.1 为准,不要按短名改回 UI。
v1.28 2026-09-23 对照原型实测校正 7 处 + 发丝线等距修正(本次不改原型逻辑,仅 ① 一处 CSS 数值修正,其余均为 PRD 与实测对齐)。① 发丝线左右等距:.table-row::after(base.css L1421)与 .catalog-hidden-block::before(L1327)原为 left:16px / right:13px,与 §6.1.1「行分隔线左右各内缩 16」不符;全仓 10 处发丝线中仅此 2 处为 13px,已统一为 right: 16px。② 展开区高度按实测校正:「约 308」→ 详情态 312(§6.1.1 规格表 + 验收 56);高度预算里「新增 638→312 / 新增产品 563→312」→ 均为 352(=312 + v1.20 新增态题头 40;实测 ?flow=price-add 与产品 Tab 新增态结构完全一致,均为 head 40 + body 240 + foot 72)。③ 修正 #catalogInlineHead 过时描述:v1.15 起「不再有」仅对详情态成立,v1.20 起新增态题头回归并复用该 id(40px)。④ 删除「时长至少 5 分钟」规则:openDurPicker(catalog.js L2148)全仓无任何调用方,#durPickerMask 为死 UI,该下限只存在于不可达的 confirmDurPicker(L2163)—— 删除 §5.2 校验行与 §9.1 toast 清单该句,§5.1 改述为「无独立选择器入口、校验仅未填/为 0」,验收 22 改写为「未填或为 0 → 请填写项目时长」。⑤ 清理「未上传,不影响保存」残留:§5.1 封面行与 §9.1 Dialog/Sheet 清单仍写该旧文案,与 §6.1.1「v1.16 起已删」自相矛盾(catalog.js 无此串),已删除。⑥ 补 §7.1 boundTemplateId 字段:单模板引用 = boundTemplateIds[0],boundToCard 由其派生(原型已有 boundTemplateIds / Names,缺单数项)。⑦ 修正 v1.26 / v1.27 修订条笔误:「验收 M1–M16」→ M1–M22(迁移方案已扩至 M22);「invalidateCommLineCache() 全仓调用 30 余处」→ 17 处(实测 employee.js 16 / comm2.js 1);帮助弹窗「L478」→ L477。
v1.27 2026-09-23 分组变更对提成的影响:不弹窗、自动生效、仅 toast;并统一「类 → 组」术语(承接 v1.26 的迁移口径,仍属"替换线上"范畴、原型本期不改码)。① 生效口径改为 forward 单一结果:原「复用两枚按钮(本期全部重算 / 从现在起用新方案)」作废 —— 分组归属变更(成员增删 / 删组 / 改主分组)即时生效、无弹窗、无二选一,分界时刻之后按新分组算、之前按旧分组算(按单据日期,非"按期")。② 必须落「分界记录」:薪资侧 employee.js 是重算(finalizeSeedCommLines() → calcStaffTrial(staffId, trialPayload, state.salaryMonth))而非读存量,且 invalidateCommLineCache() 在代码里共 17 处调用(employee.js 16 / comm2.js 1) —— 只加 toast 不落分界会导致当期金额被静默重算、文案失真。实现复用方案侧既有 _prev 模式:分组加 _prevItemIds + effectiveAt,lineInGroup() 按 line.ymd(已在试算载荷里)取值,零新增 UI。③ toast 文案定稿:成员 / 主分组变更 → 分组已更新 · 之后的提成按新分组计算;删组 → 「X」已删除 · N 条提成规则不再生效;仅当该分组被 ≥1 条提成规则引用时才弹,重命名 / 新建 / 未被引用的分组改动完全静默。④ 与方案侧重算弹窗互不干扰:方案规则(defaults / overrides 等)改动仍走既有 #comm2RecalcMask 两枚按钮,本次不动;两条链各按行日期解析后叠加。⑤ 术语统一(D9,范围已收窄):仅针对「通过价目表编辑的项目 / 产品」及「价目表功能所产生的分类 / 分组行为」这一条轴,不涉及其他任何含义的「类」—— 大类 → 主分组、项目分类/产品分类 → 项目组/产品组、整类 → 整组(提成设置/comm2.js L1176 / L1181 / L2600);排除(属别的轴,一处不得误改):#comm2CatSheetTitle 的「分类提成」(L425)、帮助弹窗的「大类 / 大类卡」(index.html L471 / L481 / L574 —— 由同弹窗 L470「每张卡管一类业务」与 L477「这单属于哪一类」自证为业务类型轴)、支出管理「分类管理」;字段名 / 接口键本次不动。完整边界、判定准则、对照表与歧义项见 PRD-迁移方案-大类转主分组.md §11.0–§11.4(验收项 M20 改名命中 / M21 排除项未被误改 / M22 歧义项)。
v1.26 2026-09-23 与线上价目替换相关的两项口径变更:①大类升格为「主分组」 —— §7.1/§7.2 的 category 字段(原型现状为写死常量:项目「其他」/ 产品「产品」,表单无控件)在替换线上时必须落成真实的 primaryGroupId(非空、1:1、可改,∈ groupIds),另设 source:'category' 追溯标记与 systemKind:'fallback' 兜底组(项目「其他」/ 产品「产品」,不可删 / 不可改名);照原型直上会让存量分类整体塌缩成「其他」/「产品」。②提成重复命中口径修正 —— 原型现状(comm2.js L1620 resolveLineBlock 顺序遍历、第一条命中即 return,用户新增规则项走 push 追加末尾)经实测为「先写先赢」且随书写顺序翻转(同项目两条覆盖 5%/20%:539 顺序不同 → 13.4 / 53.6;分组 40% vs 具体项目 5% → 107.2 / 13.4),与本文档及提成帮助弹窗写的「挑钱多的那张算」「单独加的规则项更优先」均不符;改为显式优先级链:具体项目/产品/卡 > 分组 > 默认卡,同层取高、并列按规则项顺序(跨方案取高不变)。以上均属"替换线上"的迁移范畴,原型本期不改码,完整方案(映射表、优先级算法、护栏、上线预案、验收 M1–M22)见 PRD-迁移方案-大类转主分组.md。
v1.25 2026-09-23 图标换 Stratis 稿 + 包含项目/产品页改通栏白列表 + 取消「功能入口」演示页:①「包含项目 / 包含产品」入口图标换成 Figma 图标库 Stratis UI Icons folder-minus-01(节点 101:3599,24×24 / stroke 2):保留 d 路径不变,仅把 stroke 折算为 1.7(渲染 22×22 → 1.7 × 22/24 ≈ 1.56px,与同族重命名 / 删除完全一致;照搬 2 会得到 1.83px,比同族重 17%)。实测三枚墨迹:包含项目 19.16 × 16.12 / 1.56px(viewBox 24)、重命名 18.06 × 17.25 / 1.56px、删除 18.06 × 17.14 / 1.56px —— 新图标线宽与同族一致,且高度比原「文件夹+长短两行」(13.66px)更贴近同族(17.2px)。②该页去卡片壳改通栏白色列表:删 .catalog-member-card 的 border-radius:12px / margin:0 16px 16px / overflow:hidden,#screen-p-group-members 及其 .page-body 底色由 #F7F7F7 改 var(--card) —— 整页(标题栏 + 选择条 + 列表)纯白,与列表页 .page-body 白底同规格;选择条补一条 1px #EBEBEB 底边(白对白需要分界,取本页行分隔线同色)。列表保持左右 16px 内边距、行高 52 / padding:14px 0、行分隔线 #EBEBEB、末行无线;实测桌面 / 手机 / 产品 Tab 三种情形页面灰采样点均为 0。③取消「功能入口」演示页:删掉左侧导航「入口」组、首屏 screen-hub、hub-* 样式与 app.js 的 openHub / hub 卡片绑定;原型首页 = price-list-filled(价目表 · 项目),导航高亮直接打在该项(生成时即带 on,不依赖启动期 JS);列表页标题栏返回按钮随之下线(已无上级页),projBackFromCatalog 兜底改为回列表页、openWorkbench 兜底改为 runPriceFlow('price-list-filled');?flow=hub 不再存在,访问会走既有兜底 toast「未接入: hub」。④顺带修正 _tmp_verify_catalog.mjs 一条过时断言(E 组 deep link 落地页):原断言把「可见 screen 恰好等于目标页」当判据,与 v1.21 起的推栈转场天然冲突 —— 推栈完成后底层分组管理会有意保留 is-stack-back(x −85.36 / z 1,供右滑退出)。改为排除 is-stack-back 垫底页后,全量回归 100 PASS / 0 FAIL(改动前该套件亦为 99/1,同一条,非本次引入)。改动同步 §3.0 / §6.5 / §10.2(验收 +76~79)。
v1.24 2026-09-22 包含项目 / 包含产品页顶部对齐修正(假状态栏高度不再写死):①原 .phone-inner.is-group-pushing > #screen-p-groups, #screen-p-group-members { top: 53px /* .status-bar */ } 把假状态栏高度写死在推栈层上;手机模式(真机)隐藏假状态栏后,这 53px 就成了顶部空带,把该页标题栏整体压低一条状态栏高度(430 视口实测多 58.5px),且退出动画时背景层也会闪同一条空带。现引入单一来源 --statusbar-h: 53px:.status-bar 高度与推栈层 top 共用它,shell.css 在 html.view-mobile 下覆写为 0px。②修正后实测(每行都是 list / groups / members 三页对照):距画布顶 桌面 55 / 55 / 55(保留状态栏偏移)、手机 0 / 0 / 0、Figma 抓取 53(状态栏可见,行为不变);推入动画中 / 退出动画中 / 退出后均为 0;标题栏高 44 / 44 / 44(手机 48.5,按 scale 1.103 折算)、标题中轴距栏顶 桌面 22 / 22 / 22、手机 24.3 / 24.3 / 24.3 —— 三者完全一致。③顺带修正该页选择条(已选 N · 反选 · 全选)底色失效:.catalog-members-bar { background: var(--card) } 被其后同特异性的 .pick-select-bar { background: transparent } 覆盖,实际露成页面灰 #F7F7F7;现以 .pick-select-bar.catalog-members-bar 提高特异性,恢复白底,与列表页 Tab 条 .page-tabs 同规格(白标题栏 + 白选择条 + 灰正文)。改动同步 §6.5 / §10.2(验收 +73~75)。
v1.23 2026-09-22 手机预览(真机可用)+ 原型上线:①原型发布到 GitHub Pages —— https://xdean-designer.github.io/menu/(仓库 XDean-Designer/menu,main 分支根目录即本模块;index.html 为首页,_tmp_* 校验脚本 / _shots/ 截图不入库)。②新增 mobile-preview.js(替代原先从「提成设置」壳复制来的内联脚本,改由 _build-index.js 注入):逻辑画布固定 390(--phone-w),整体 transform: scale(视口宽 / 390) 铺满视口 —— 不再用 width:100% 拉伸布局,故分组行删除钮、0.5px 发丝线、按文字宽算出的 Tab 指示条都不会被撑歪;transform-origin: top center + 台面居中,横屏等极矮视口走兜底等比并自动居中。③不再以视口宽度判定真机(max-width 窄窗、DevTools 设备模式一律保持桌面壳),只认 UA / iPadOS 触屏 Mac;参数改为 ?mobile=1|0(?view=mobile|desktop 保留兼容)。④系统键盘避让:visualViewport 与 innerHeight 差值(< 80px 视为地址栏抖动)按缩放折算成逻辑 px 写入 --kb-h,底部 Sheet 用 padding-bottom 抬起、居中 Dialog 用 translateY 上移;台面高度改用 innerHeight(真机上已排除 iOS 工具栏、但不随键盘变化),与 --kb-h 分工不重复扣。⑤手机模式隐藏假状态栏(9:41),顶栏按 env(safe-area-inset-top) 避让刘海、并按缩放折算;--safe-bottom 同理。⑥左缘滑动唤出左侧导航抽屉(跟手拖拽,抽屉 z-index 400 > 蒙层 350)。⑦补站内 favicon(内联 SVG),消除线上 favicon.ico 404。改动同步 §10.2(验收 +67~72)。
v1.22 2026-09-22 分组入口改名「包含项目 / 包含产品」+ 该页选中态改字感 + 标题栏右侧入口同轴 + 「隐藏」图标重绘:①展开条入口与页面标题由「编辑成员」改为按当前 Tab 动态命名:包含项目(项目 Tab)/ 包含产品(产品 Tab),标题形如 包含项目 · {组名}(系统「隐藏」组直接叫 隐藏项目 / 隐藏产品);左侧导航与术语表同步。②入口图标由双人换为文件夹内条目线描(22×22 / stroke 1.56,与重命名、删除同族)。③该页选中态不再靠底色,改为字重 + 明暗:未选名称 15px/400/#8A8A8A、价格 13px/400/#B9B9B9;选中名称 15px/600/#1A1A1A、价格 13px/500/#1A1A1A(勾选框保持 18×18 原样,仅作控件确认符)。④标题栏右侧入口(分组管理 +新建 / 列表 +添加)图标与文字统一 14px 盒,加号重绘为 14 单位 / stroke 1.5(原 12 单位 / 1.8 在 14px 下描边达 2.1px,比 15px 文字重约 40%,视觉不成对),两枚均 flex 居中即同轴;实测墨迹中轴差 ≤ 0.5px。⑤「隐藏」图标重绘为 16×16 / stroke 1.5 的眼 + 斜线(原 19.5×16.5 内嵌实心 + 细斜线,线宽不成族),Tab 内 14×14。改动同步 §3.0 / §6.1 / §6.4 / §6.5 / §10.2(验收 +63~66)。
v1.21 2026-09-22 分组管理展开条版式对齐人工稿 + 编辑成员改右侧推栈(设计源:Figma 1381:110 展开块 1397:632):①展开整块底色 #F7F7F7 → #E7E7E7;去掉顶沿内凹阴影,改为行下 0.5px rgba(0,0,0,.08) 发丝线分隔。②操作条改为「编辑成员 + 重命名」整组水平居中、间距 16(单格 68.33×66),删除收成 32×32、圆角 8、#BEBEBE 灰底 + 白垃圾桶、无文案,钉在右内边距 16(图标 x 310–342);操作条高 82(上 4 / 下 12,行高 69)。③图标换为稿件线描(22×22);文案与图标色 #7D7D7D(删除钮内为白)。④编辑成员页改为右侧推入 / 推出(300ms cubic-bezier(.32,.72,0,1)):进入时成员页自右滑入覆盖,分组管理左移 22% + rgba(0,0,0,.1) 蒙层;返回 / 确定后成员页自右滑出、蒙层淡出;prefers-reduced-motion 与抓取模式降级为即时切换。改动同步 §6.4 / §6.5 / §10.2(验收 +60~62)。
v1.20 2026-09-22 新增展开区挂在售区末 + 标题;分组管理行内展开三钮:①新增表单插在在售最后一行之下 /「已下架」之上(无下架则在「已隐藏」之上),并显示标题「添加项目/添加产品」。②分组管理:点行或 ⋯ → 行内展开「编辑成员 / 重命名 / 删除」(上图标下文案;删除仅垃圾桶);废止分组菜单 Sheet;编辑成员/重命名/删除仍走原页面与 Dialog。③「+新建」在标题栏右侧(无底栏)。④进页一律收起;展开项行+操作条同底 #F7F7F7、操作条左缩进对齐组名(视觉一体)。改动同步 §6.1.1 / §6.4 / §10.2。
v1.19 2026-09-22 标题栏添加改文字链 + 展开隐藏价格 + 保存绿减淡 + 已隐藏灰区分 + 价格键盘不退出编辑:①标题栏「+添加」去掉红底白字胶囊,改为品牌红加号图标 + 红色「添加」文字(无按钮壳)。②展开后标题行隐藏价格/时长(强化选择器,避免与展开区字段重复)。③保存成功高亮减淡:底 #F3FAF5、描边 rgba(52,199,89,.28)。④「已隐藏」块底改 #EEEEEE,与展开区 #F7F7F7 区分。⑤点价格打开金额键盘时 stopPropagation、缓冲去 ¥、键盘打开期间不因外部点击收起展开区。⑥删除 PRD 精简版(仅保留完整稿)。改动同步 §6.1 / §6.1.1 / §10.2。
v1.18 2026-09-22 产品行对齐修复 + 展开标题收敛 + 保存改通栏实心:①产品 Tab 隐藏空的 .dur 占位,名称/价格落回 2 列网格并垂直居中。②展开后标题行只保留拖拽图标 + 名称(隐藏时长/价格)。③保存区上方加 0.5px #EBEBEB 分隔线;「保存」改为通栏实心主按钮(左右 16、高 48、圆角 12、品牌红 #F32F41 底白字 + 白存盘图标)。改动同步 §6.1.1 / §10.2。
v1.17 2026-09-22 展开区去掉白卡 + 阴影;列表行垂直居中(对齐 Figma 1258:605 最新人工修改):①字段区 .proj-form-section 删除白底与圆角,容器左右 16px 边距一并去掉(padding: 0 0 12px)——字段直接铺在 #F7F7F7 灰底上,标签仍由行内 padding: 0 16px 距屏边 16;顶沿 0.5px #EBEBEB 发丝线保留。②删除 .catalog-inline-panel--joined::before 顶部 8px 凹陷渐变 —— 展开区彻底无阴影。③列表行显式锁定 align-items: center,名称/时长/价格 line-height: 19px + align-self: center,消除字下留白多于字上的视觉偏上。改动同步 §6.1.1 / §10.2。
v1.16 2026-09-21 展开区字段区对齐 Figma 人工修改稿(设计源:RTB老版本优化 → 「价目表-项目详情·普通」1258:605;已把该稿的改动回灌原型):①白卡与展开区顶沿齐平 —— 容器 padding 由 12 16 12 改 0 16 12,展开区高度 324 → 312(产品 / 下架 / 新增各形态同为 312、绑卡态 410 = 原 40%~53%);白卡顶沿补一条 0.5px #EBEBEB 发丝线(左右内缩 16,与行间分隔线同色同内缩)。②标签改短文案:项目名称/项目价格/项目时长/项目封面图(可选) → 名称/价格/时长/封面图(可选)(68px 固定标签列不变;无障碍名仍走 aria-label)。③值色改版:名称值 #929292 Medium(500)、价格值 #FE8D4B Medium(500)(原统一 #1A1A1A Regular)。④名称 / 价格行右端新增 16×16 铅笔图标(描边 #C7C7CC、线宽 1.05;图形=10×10 铅笔 + 右下 5.25 横线;与值间距由 8px 收到 4px;pointer-events:none,只作「可点编辑」线索)。⑤时长分段选中态由蓝改橙:指示器 rgba(74,123,244,.1) → #FE8B4B@10%(渲染为 #FEF3ED)、选中字 #4A7BF4 → #FF8A4C(未选中字仍 #929292)。⑥封面行去掉副标题「未上传,不影响保存」,只剩单行「封面图(可选)」+ chevron。⑦「保存」由通栏实心改药丸文字钮:358×48 实心红底白字 → 172×48、圆角 39、无底色无描边、居中,红字 #F42F41 16px Medium + 红存盘图标(13.5×13.5 置于 16×16 框内)。改动同步 §6.1.1(新增「字段区视觉规范」)/ §6.2 / §6.3 / §10.2(验收 +53~56)。核对方式:把原型与设计稿按展开区顶对齐后逐像素比对,差异由 22491px 降到 3841px(余量全部是字体栅格化边——浏览器次像素抗锯齿 vs Figma 灰度抗锯齿)。
v1.15 2026-09-21 行内展开区=选中行的「向下延伸」+ 保存反馈提速 + 表头固定(原型 价目表/index.html):①形态——展开区两侧贴边(margin:0)、底色与选中行同为 #F7F7F7、去掉圆角/描边/外阴影;.catalog-inline-panel--joined 顶部加 8px 向下渐隐内阴影做凹陷感,承载行同步 #F7F7F7 + 隐藏行分隔线,与展开区连成一块(读作「整块被按进列表」);内容改为灰底上的一张白卡(左右各内缩 16,灰底从两侧露出)。②删除题头与提示条(#catalogInlineHead/#catalogInlineTitle/#catalogInlineNotice 一律不再生成):标题退化为容器 aria-label,限制信息改由 「点锁定字段 → 弹原因 toast」 承担(文案表见 §6.2,data-lock-toast 统一承载)。③紧凑到 ≤ 原高度的 2/3(实测:项目详情 686→324、绑卡态 784→422,即 47%~54%;上限 457/523):删题头(46)与提示条(34)、不再渲染三段小标题、「在售 + 可预约」并为一行(switch-pair)、撤掉「可预约」副标题(其信息改由点击开关的短 toast 承担,见 §6.2 开关文案)。④保存反馈提速:收起 240→120ms、FLIP 归位 420→260ms、保存高亮 1200→320ms(--cip-close/--cip-flip/--cip-glow 单一来源;高亮只有 320ms,测试必须在动效中采样)。⑤表头固定:滚动容器从 .page-body 下移到 #projProjectTable(flex:1; overflow-y:auto、隐藏滚动条),Tab 条 / 分组轨 / 表头为固定层 —— 滚动时位移 Δ=0,表头下沿即列表可视区顶边。⑥最佳操作位补口径:落点 clamp(0, maxTop),被夹住时(首行 / 空态无可滚内容 / 新增区在内容末尾)只保证整块可见。⑦原型修复:独立原型无会员卡模板,syncProjectCatalogBindings() 会把演示绑卡项 p4 冲成未绑卡 → 每次重算后重新落回演示绑定,price-edit-bound 才真的能看到「名称/预约锁定 + 原因 toast」。改动同步 §6.1 / §6.1.1 / §6.2 / §6.3 / §10.2(验收 +41b/48/48b/51/52)。
v1.14 2026-09-21 新增 / 详情改为「行内展开区」(原型 价目表/index.html):①废止全屏页 screen-p-add / screen-p-edit(HTML、CSS 作用域、按钮与返回逻辑全部移除)——点行 → 该行正下方就地展开详情;标题栏「+添加」/ 空态 CTA / 底部添加行 → 内容区就地展开新增(空态时替代空态块);②新增 §6.1.1「行内展开区」(形态、内部版式左基准、最佳操作位滚动算法、动效时长与缓动、保存反馈、FLIP 归位、脏值确认、手风琴、DOM 钩子);③最佳操作位:≤可视区则垂直居中,超出则顶边贴 Tab 下沿 +12;动画期间逐帧锁死面板顶边,抵消 scrollTop 被夹回(不修则「已下架」区块内条目偏差达 378px);④保存反馈:收起 → 重绘 → 高亮闪一次(1200ms 淡出);新增另加 FLIP 归位(ghost 高变矮 + 宽变宽 + 淡出,420ms);⑤脏值确认「放弃修改?」;⑥展开区工整化:标签改固定 68px 列宽(原内容宽导致控件左沿参差 593.5/565.5/537.5)、分区标题与通知条与标签同列;⑦标题栏「+」改 12×12 SVG 修对齐;⑧产品 Tab 表头删除「时长(分钟)」(3 格塞 2 列导致错行);⑨7 个新增/详情深链改为「进列表 + 自动展开」。改动同步 §3 / §5.6 / §6.1 / §6.1.1 / §6.2 / §6.3 / §10.2。
v1.13 2026-09-21 修复主 Tab 选中态不可辨(原型 价目表/index.html):base.css 里 .page-tabs__item.on::after 只写了 width/height/border-radius/bottom,漏了 content / position / background → 伪元素从未生成,实测 content: "none"、position: "static"、background: rgba(0,0,0,0);导致选中态只剩「文字色 #1A1A1A + 字重 600」一处差别。改为单一滑动指示条(品牌红 #F32F41,宽 = 文字宽 + 20、高 3、圆角 2,贴容器下沿)+ 选中文字改品牌红 600;切换时指示条在两项间滑动 160ms 缓入缓出;prefers-reduced-motion 降级为直接切换。新增 §6.1「主 Tab 规格」(含实现口径与实测数据)。
v1.12 2026-09-21 空态与拖拽排序修复(原型 价目表/index.html):①空态进入即重绘 —— 整表无数据时不再残留上一节点的表头 / 分组轨 / 列表行,正确呈现空态块(插画 + 标题 + 说明 + CTA);②空态 CTA 统一 —— 项目与产品同用「虚线品牌红」样式,取代原「项目灰描边方块 vs 产品虚线」两套;③空态隐藏拖拽提示条(无内容可拖);④价格回填修复 —— 金额键盘确定后回填表单价格值,否则保存被「请填写有效价格」拦截(新增 / 编辑、项目 / 产品均受影响);⑤列表底部虚线添加行补样式(此前缺 CSS,渲染为浏览器默认按钮);⑥行拖拽排序修复 —— 复位拖拽所需的挂载点与缩放计算,长按行或按把手可正常换序并写回 sortOrder;⑦导航 / 深链切换先关闭遗留 Sheet / Dialog。改动同步 §5.6 / §6.1 / §6.2 / §6.3 / §6.7 / §10.2。
v1.11 2026-08-06 口径基线:拖拽拦截矩阵、空态矩阵、列表底部虚线添加行、无搜索、无右下悬浮球。

怎么读这份 PRD

正文按模块编排。不用通读全文,按角色跳章即可。

# 模块 前端 后端 测试 说明
1 背景 / 目标 / 范围 / 能力映射 ✓ ✓ ✓ 重构叙事、防范围蔓延
2 用户场景与成功标准 ✓ 了解 ✓ 测「业务对不对」
3 信息架构与页面清单 ✓ 了解 ✓ 做哪些页
4 状态机 ✓ 必看 必看 在售/下架/隐藏、绑卡锁、分组
5 交互细则 / 异常流 ✓ 了解 ✓ 校验、toast、拖拽拦截
6 功能规格 ✓ 了解 ✓ 逐屏字段与跳转
7 数据模型与接口约定 ✓ 必看 ✓ 字段、能力清单、时序
8 业务规则 ✓ 必看 ✓ 排序桶、多分组、可见性、8.1 产品库存、改价支付拦截
9 文案清单与权限 ✓ 部分 ✓ 文案;店主/店员权限
10 验收 P0 + 造数场景 了解 了解 必看 Given→When→Then
11 非功能(极简) ✓ ✓ ✓
12 交付物 ✓ — ✓ 仅 PRD + 原型

先读这几条(贯穿全文)

  1. 本期是对 旧价目表 / 旧项目管理 的 整体重构并覆盖,不是长期并行两套。新版强调 B 端 工具属性:列表 纯信息、无列表配图装饰,一屏信息密度更高,操作更紧凑。
  2. 价目分 项目 与 产品 两个 Tab;均可 分组筛选、多分组归属、行内 拖拽排序(在约束内)、左滑/长按快捷操作。
  3. 项目可被会员卡权益 按名称绑定(项目桶、产品桶内 名称唯一);绑卡后 锁定名称、可预约、删除(优惠券/商城关联亦会锁定删除);在售/下架仍可改。列表绑卡行以名称样式区分;删除锁定在左滑按钮与行操作 Sheet 以锁标呈现。boundToCard 随模板引用 自动派生并刷新。
  4. B 端改价、上下架、时长、隐藏、封面等变更需 同步客户端展示(开单侧为 实时目录/价格;推送细节【待联调确认】);顾客端具体 UI 不在本文展开。价目产品 本期不维护库存字段(见 8.1)。
  5. 会员卡建卡时「选项目 / 选产品 / 选折扣适用项目」及标题栏「管理价目表」依赖本价目数据;从权益页进入价目再返回时 刷新权益选品分组 Tab(重置为「全部」并重绘);会员卡三步流程见 PRD-会员卡管理.md,本文只写联结面。
  6. 开单选品:仅排除已下架(须 onSale);隐藏项可选;建卡权益选品仍为 onSale && !hidden。价目产品 不限购(见 8.1)。开单过程中若价目改价导致应付变化,支付链路拦截提示(见 6.8)。
  7. 原型工具栏「无数据 / 有数据」造数 仅演示,不进正式产品。接口路径标 【待联调确认】,正式接口文档 暂无交付时间;不臆造现网 URL。
  8. 本期仅单店;价目管理权限:店主可编辑,店员不可编辑。

交互行为与文案以 同目录 index.html(价目表独立原型)当前表现 为准。


模块 1 背景 / 目标 / 范围 / 能力映射

读者:前端 ✓ · 后端 ✓ · 测试 ✓

1.1 背景

旧价目表 / 旧项目管理在列表中带有图片与较多装饰元素,内容不够紧凑,一屏信息量少,操作分散,不符合 B 端管理软件「找得到、改得快」的诉求。

本期在整体产品(含会员卡权益组合)对齐前提下重构价目能力:

1.2 目标

目标 说明
覆盖旧能力 新价目成为项目管理的唯一体系
高密度列表 项目/产品双清单,信息行可扫可读
分组与排序 自定义分组 + 系统「隐藏」组 + 在售/下架桶内拖拽 sortOrder
售卖可控 在售/下架/隐藏;项目可配可预约;价目产品本期不维护库存
可见性清晰 顾客端 / 开单 / 建卡选品范围分明(见模块 4.6;隐藏项开单可选,权益选品仍须未隐藏)
与卡对齐 绑卡锁定规则清晰;权益选品只取在售且未隐藏

1.3 本期做(In)

1.4 本期不做(Out)

1.5 旧 → 新能力映射(不写废弃产品名)

旧体系常见能力(语义) 新体系如何配置
维护服务项目价格/时长 项目 Tab:名称、价格、时长、在售、预约、可选封面图
维护可售卖货品 产品 Tab:名称、规格、价格、在售、可选封面图(无库存字段;见 8.1)
列表浏览与查找 列排序 + 分组筛选;信息行无配图、无状态列;无关键字搜索
分类/归类 自定义分组(可多组、可空组、删组保留条目)+ 系统 隐藏 组
调整展示顺序 桶内拖拽 sortOrder(在售 / 下架两桶)
停售 下架(落入列表「已下架」分区;顾客端 / 开单 / 权益选品均不可选)
对 C 端隐藏但 B 端保留 隐藏(hidden=true / 系统「隐藏」组;「全部」底部「已隐藏」折叠;开单可选)
被卡引用后的保护 绑卡锁定(名称/预约/删除等受限;在售仍可改)

映射不足时以原型字段与行为为准。


模块 2 用户场景与成功标准

读者:前端 ✓ · 后端 了解 · 测试 ✓

2.1 角色

角色 说明
店主(默认) 可进行本文全部价目操作(增删改查、分组、排序等)
店员 不可编辑/修改价目表(无价目管理写入权限;本期原型按店主路径演示)

2.2 核心场景

场景 用户期望 成功标准
新建项目 填名称价格时长等后出现在项目列表在售区 列表可见;可被会员卡选项目/折扣(在售且未隐藏)
新建产品 填名称/规格(选填)/价格后出现在产品列表 列表「名称(规格)」+ 价格(无库存列);在售时可开单(含隐藏);权益选用须未隐藏
改价 绑卡或未绑卡均可改价(规则内) toast 成功;客户端同步语义成立;开单中改价触发支付拦截(见 6.8)
下架 停售但仍可在价目表看到 落入「已下架」分区;顾客端 / 开单 / 权益选品均不可见
隐藏 对顾客端隐藏但 B 端保留 hidden=true;「全部」主列表不显示、底部「已隐藏」折叠可展开;「隐藏」Tab 可见;开单可选(「全部」与「隐藏」Tab);权益选品仍不可选;项目隐藏时关闭「可预约」,取消隐藏后仍关,须手动再开
产品库存 价目侧不维护库存 见 8.1:表单/列表无库存;开单对价目产品不限购;库存管理另模块
分组运营 建组、把条目加入多组、按组筛选 筛选正确;删组不丢条目;系统「隐藏」组不可删/改名
拖拽排序 调整同售卖态桶内顺序 刷新后顺序保持;自定义分组内可拖;「隐藏」Tab / 「全部」已隐藏折叠 / 列排序激活时不可拖
绑卡项目 被卡引用后不能乱改关键属性 锁定名称/预约/删除有 toast 与锁标;可改在售/下架
开单选品 开单可选在售货(含对 C 端隐藏项) 仅排除已下架:onSale;隐藏项可选;价目产品不限购(8.1)
从建卡管价目 权益页点「管理价目表」补货后返回继续选 返回原权益页、列表刷新,分组 Tab 刷新为「全部」

模块 3 信息架构与页面清单

读者:前端 ✓ · 后端 了解 · 测试 ✓

价目表
 ├─ 列表(项目 Tab / 产品 Tab)
 │    ├─ 标题栏右侧品牌红「+添加」文字链(跟当前 Tab,不弹窗;无悬浮球)
 │    ├─ 分组轨(灰底白胶囊 · 可横滑 · 全部/自定义/隐藏)· 深灰齿轮
 │    ├─ 在售区 →「已下架 · 停售」分区(线在标题上方)→(全部)「已隐藏 · 仅顾客端不可见」折叠 → 底部虚线添加行
 │    ├─ 行:**点行就地向下展开**(详情即展开 · 不再跳页)· 拖拽排序 · 左滑/长按操作(产品:名称规格+价格)
 │    ├─ **行内展开区**:新增 / 详情表单(字段同三段、**不渲染小标题** + 底部「保存」),见 §6.1.1
 │    └─ 空态(保留空态 CTA;点 CTA 在**内容区就地展开**新增表单)
 ├─ 分组管理
 │    └─ 包含项目 / 包含产品(含系统「隐藏」组)
 └─ Sheets / 弹层
      ├─ 行操作 Sheet(单层贴底菜单)
      ├─ 设置分组(多选,含系统「隐藏」组)
      ├─ 分组行内展开操作条(包含项目·包含产品 / 重命名 / 删除)
      ├─ 新建/重命名分组 Dialog
      ├─ 删除分组确认
      ├─ 删除条目确认
      ├─ 放弃修改确认(展开区有未保存改动时收起)
      └─ 金额键盘(价格)

3.0 页面流(示意)

flowchart LR L[价目表列表] -->|标题栏+添加 / 空态 CTA| Add[行内展开区 · 新增] L -->|点行| Edit[行内展开区 · 详情] L -->|齿轮| G[分组管理] G -->|包含项目 / 包含产品| M[包含项目 / 包含产品] G -->|新建或重命名| D[[分组名称 Dialog]] L -->|长按或左滑| A[[行操作 Sheet]] A -->|设置分组| IG[[多选分组 Sheet]] Edit -->|保存| L Add -->|保存| L

从会员卡权益页进入:

flowchart LR P[权益选品页] -->|管理价目表| L[价目表列表] L -->|返回| P

关键口径:本模块只有「价目表列表」一个页面主体 —— 新增与详情不再是独立页面,而是在列表内就地向下展开的一块区域(行内展开区,§6.1.1)。除了分组管理的两级页面(分组管理 / 包含项目·包含产品),其余全部是列表页上的层。

原型首页(v1.25):index.html 不带参数打开 = price-list-filled(价目表 · 项目)。原先的「功能入口」演示页(?flow=hub + screen-hub)已取消 —— 它只是独立原型的入口索引,不属于产品功能;左侧导航现在只剩「价目表 / 浮层·叠加态 / 文档」三组。相应地列表页标题栏不再有返回按钮(已无上级页;从会员卡权益页进入的返回语义见 §6.8)。

页面/层 导航标题(原型) 说明
列表 价目表 Tab 项目/产品;标题栏品牌红「+添加」文字链(跟当前 Tab,不弹窗;无悬浮球)
行内展开区 · 新增 添加项目 / 添加产品 空态时替代空态块;有数据时挂在在售末行之下、已下架/已隐藏之上;顶部可见标题;底部「保存」
行内展开区 · 详情 项目详情 / 产品详情 展开在该行正下方;底部「保存」。无「删除」(删除走左滑 → 行操作 → 删除)
分组管理 分组管理 标题栏右侧加号+「新建」;点行/⋯ 行内展开三钮(包含项目·包含产品 + 重命名居中、删除灰钮钉右 16)
包含项目 / 包含产品 包含项目 · {组名}(产品 Tab:包含产品 · {组名});系统「隐藏」组为 隐藏项目 / 隐藏产品 整页纯白(白标题栏 + 白选择条 + 通栏白列表,无卡片壳);底栏「确定」;系统「隐藏」组可编辑成员;选中态靠字重+明暗区分
行操作 条目标题(无副标题) 设置分组/上下架/隐藏或取消隐藏/删除;单层贴底,取消在 foot
设置分组 条目标题 可多选(含系统「隐藏」);无自定义组时「去新建分组」→ 新建 Dialog

功能链路演示节点(FLOW_MAP「项目创建与管理」):price-list-empty / price-list-filled / price-list-product / price-list-action / price-list-swipe / price-list-swipe-locked / price-list-hidden-open / price-groups / price-group-members / price-group-menu / price-group-menu-product / price-group-create / price-group-rename / price-group-delete / price-item-group / price-item-group-empty / price-item-delete / price-add / price-add-product / price-edit-normal / price-edit-bound / price-edit-off-sale / price-edit-product / price-edit-product-off-sale。

深链:index.html?flow=<节点 id>(可选 &capture=1)。开单改价拦截节点 bill-pay-price-changed 归属「开单记账」分组,规格见 §6.8。

新增 / 详情类节点的落地口径(7 个节点):price-add / price-add-product / price-edit-normal / price-edit-bound / price-edit-off-sale / price-edit-product / price-edit-product-off-sale —— 一律先落到列表页,再自动展开对应位置(新增 → 内容区末尾 / 空态位置;详情 → 该条目的行正下方),不再有独立全屏页。深链切换时会先收掉上一轮遗留的展开区,避免两条动画打架。

实现路由建议(名称可调整):price-list(含 ?panel=add|edit&id=<条目 id> 表达行内展开区)/ price-groups / price-group-members + sheets。不再需要 price-add / price-edit 独立路由。


模块 4 状态机

读者:前端 ✓ · 后端 必看 · 测试 必看

4.1 售卖状态 onSale

状态 产品表现 含义
在售 true 主列表在售区信息行(无状态胶囊) 对外可售(仍须未隐藏才顾客端可见)
已下架 false 「已下架」分区内弱化行 停售;价目表仍保留
在售 ──下架──► 已下架 ──上架──► 在售

4.2 隐藏状态 hidden

字段 说明
hidden: boolean 条目是否对顾客端隐藏
系统组 每桶固定 g_sys_hidden_project / g_sys_hidden_product,名称「隐藏」,system: true
双向同步 加入/移出系统「隐藏」组 ↔ 切换 hidden

4.3 绑卡锁定(项目为主)

绑定依据:项目/产品 名称(同桶内 trim 后唯一)出现在会员卡模板权益引用中(项目次数、折扣键、产品权益等)时,同步 boundToCard 及模板名列表。

自动刷新:boundToCard(及关联模板列表)由当前模板引用 自动派生;模板解除对某名称的引用后,下次同步/进入价目相关页即更新,无需商户手动刷新。是否落库由联调确认(见模块 7)。

条件 结果(项目)
未绑卡 表单字段均可按规则编辑;可删;可上下架
已绑卡 不可改:名称、可预约;不可删除
已绑卡仍可改 价格、时长、封面图(演示)、在售/下架

产品:绑卡名称样式同项目(列表名称 is-bound 样式区分,无独立卡面图标);在售/下架不受绑卡限制;删除若同时关联优惠券/商城则 toast「已关联会员卡/优惠券/商城,不可删除」(按实际关联动态拼接)。产品详情无预约开关,同样不展示项目类限制提示(限制一律走「点锁定字段 → toast」)。

删除锁定视觉:左滑删除钮角标小锁;行操作 Sheet 删除项旁锁标。

4.4 表单模式

模式 入口 保存结果
新增 标题栏「+添加」/ 空态 CTA / 底部虚线添加行(均跟当前项目或产品 Tab,不弹选择层;无悬浮球) 在列表内容区就地展开表单(§6.1.1);写入对应 catalog;toast「项目已创建」/「产品已创建」;展开区收起 + 新行归位高亮
编辑 点行 → 该行正下方就地展开(不跳页) 更新原条目;toast「保存成功」;展开区收起 + 该行高亮闪一次

4.5 分组生命周期

无组 ──新建──► 有组(可空成员)
有组 ──包含项目 / 包含产品──► 更新 itemIds(系统「隐藏」组同步 hidden)
有组 ──重命名──► 同 id 改名(桶内唯一;系统组不可)
有组 ──删除──► 组消失;条目仍在价目表;若当前筛选该组则回「全部」

条目可同时属于 多个 自定义分组;「全部」「隐藏」不是可编辑的自定义分组实体。

4.6 可见性矩阵(跨端)

状态 顾客端 开单选品 建卡权益选品(项目/产品/折扣)
在售且未隐藏 可见(项目预约另需 bookable) 可选(价目侧不限购;见 8.1) 可选
已下架(含绑卡下架) 不可见 不可选 不可选
隐藏(hidden=true / 系统「隐藏」组) 不可见 可选(须仍在售) 不可选

顾客端可见(项目/产品):onSale && !hidden。
开单选品(项目/产品):onSale(含 hidden;价目产品 不限购,见 8.1);分组轨含「全部 / 自定义 / 隐藏」。
建卡权益选品:onSale && !hidden(与开单口径不同)。
顾客端可预约(仅项目):onSale && !hidden && bookable。

4.7 列表排序相关状态


模块 5 交互细则 / 异常流

读者:前端 ✓ · 后端 了解 · 测试 ✓

5.1 通用

5.2 校验失败(保存)

条件 提示
项目名称为空 请填写项目名称
产品名称为空 请填写产品名称
同桶名称已存在(trim 后全等,排除自身) 名称已存在
价格无效 请填写有效价格
价格 > 999,999.99 金额不能超过 999,999.99
项目时长无效或不足 1 分钟 请填写项目时长

时长以 行内时长输入(分/时切换)为主路径;上限按最多 480 分钟(8 小时) 约束;无独立时长选择器入口(#durPickerMask 为死 UI,「不足 5 分钟」校验随 v1.28 一并删除),校验仅「未填 / 为 0 → 请填写项目时长」。
产品库存:当前原型价目表单 无库存字段,不做上表库存校验(见 8.1)。

5.3 分组异常

条件 提示
名称为空 请输入分组名称
桶内重名 分组名称已存在
名称长度 最多 20 字

5.4 拖拽拦截

条件 提示
当前为「隐藏」Tab 「隐藏」分组内不支持拖拽排序,请切换到「全部」或其它分组
「全部」底部「已隐藏」折叠区内的条目 已隐藏的项目不支持拖拽排序
列排序激活 请先取消列排序后再手动排序

5.5 绑卡 / 下架 / 隐藏拦截(节选)

时机 提示
改绑卡项目名称 该项目已绑定会员卡,项目名称不可修改
改绑卡预约 该项目已绑定会员卡,预约设置不可修改
下架时点预约 项目已下架,请先开启「在售」后再设置可预约
隐藏时点预约 项目已隐藏,取消隐藏后需手动开启可预约
删绑卡项 该项目已绑定会员卡,不可删除
删关联优惠券/商城项 已关联优惠券/商城,不可删除(可组合「会员卡」来源)
隐藏条目 已隐藏
取消隐藏 已取消隐藏
封面超限 仅可上传 1 张封面
添加演示图 已添加演示图片

5.6 空态

见模块 6.1。「隐藏」Tab 空态引导左滑将条目移入隐藏。

进入空态即重绘:整表无数据时,空态块(插画 + 标题 + 说明 + CTA)必须取代列表主体出现 —— 表头、分组轨、列表行、拖拽提示条一律不出现(不允许残留进入前的列表)。

CTA 样式统一:空态 CTA 项目 / 产品同款「虚线品牌红」按钮(见 6.1 空态样式),不因 Tab 切换而改变形状、颜色、圆角、字号。

CTA 行为(v1.14):点击空态 CTA 不跳页 —— 在内容区就地展开「新增」展开区(§6.1.1),替代空态块(表头、分组轨一并收起,列表外壳保留)。新增成功保存后:展开区收起 → 新行入列 → 由归位动效落到它该在的位置 → 高亮闪一次;此时列表已有数据,底部虚线添加行随之出现。


模块 6 功能规格

读者:前端 ✓ · 后端 了解 · 测试 ✓

下列字段、按钮、跳转均须与原型一致;视觉以原型为准。

本模块的两色分工(v1.31 起,前端必须分清):

色 值 语义与用途
品牌红 --brand #F32F41 动作 / 强调:实心主按钮(弹窗「确定 / 删除」)、文字链(「+添加」)、虚线添加行与空态 CTA、「新建分组」、必填星号、左滑「删除」、switch.on、勾选框、排序激活箭头。展开区「保存」的底色改为浅品牌底、字色仍是本红
选中橙 --brand-orange #FF7A45 只表示「选中」:主 Tab(项目 / 产品)选中文字 + 指示条、分组胶囊选中文字。范围严格限定,不要外溢到动作类元素
浅品牌底 --brand-soft #FFF2F2 浅底态底 + 各虚线按钮按下态(展开区「保存」底、按下 #FEEAEC)

文字档:#1A1A1A = 主文字(标签、展开区字段值、折叠行名称与价格、时长数字框);#333333 = --text(「已绑卡」行名、折叠条文字);#929292 = 次级(时长、下架行、展开态标题行名、未选中单位);#6C7279 = 未选中 Tab。

6.1 价目表列表

结构

主 Tab 规格(项目 / 产品)

项 规格
容器 白底 #fff、高 45、padding-top: 12、上圆角 16 16 0 0、position: relative(承载指示条)
项 两项 flex: 1 等宽(390 画布下各 194、高 33);单项为按钮,aria-selected 随态同步
未选 文字 #6C7279、字重 400、字号 16、letter-spacing: 0.32px、无下划线、透明底
选中 文字 选中橙 #FF7A45、字重 600;选中橙下划线(见下)
指示条(下划线) 单一元素 .price-catalog-tabs__ink,非每项各画一条 —— 切换时在两项间滑动
· 颜色 var(--brand-orange)(#FF7A45,v1.31 起;原 var(--brand));高 3;圆角 2;贴容器下沿 bottom: 0、bottomGap = 0
· 宽 = 文字宽 + 20(最短 40);本项目/产品同为 2 字(实测文字宽 32.64)→ 实测 53
· 水平中心 = 所在项中心(两项等宽 → 第 1 项 25%、第 2 项 75%);transform: translateX(-50%) 居中
· pointer-events: none(不拦截点击)
位置实现 纯 CSS 驱动:容器 data-active="project"|"product" → 指示条 left 在 25% / 75% 间切换。不做 JS 几何测量,因此屏幕处于 display:none 时同样正确
宽度实现 JS syncPriceCatalogTabInk() 用 Range 量标签文字宽,写入 CSS 变量 --pct-ink-w;量不到(隐藏态)时保留 CSS 兜底 52px。projShowScreen('screen-p-list') 与 syncPriceCatalogTabs() 都会重试,可见后即落到真实宽度
切换动效 left 与 width 过渡 160ms,缓动 cubic-bezier(.22,.82,.24,1)。实测 24 帧采样:409 → 483 → 539 → 569 → 585 → 593 → 598 → 601 → 602 → 603(136ms 内到位,落点误差 <1px)—— 是滑动而非瞬移
降级 prefers-reduced-motion: reduce → transition: none(直接切换)
不改变布局 指示条绝对定位,不参与流:Tab 条高仍 45、两项仍各 194(实测 A…/D1/D2)
- 可关闭提示条:长按项目可拖动排序,左滑快捷操作;分组可左右滑动(文案固定含「项目」;关闭状态以 session 记忆);整表无数据时整个提示条隐藏(无内容可拖)
- 分组轨:灰底 #F2F2F2 圆角轨道;全部 + 自定义组 + 系统 隐藏(隐藏 Tab 带 hide 图标:内联 16×16 / stroke 1.5 的眼 + 斜线,胶囊内 14×14,色随 Tab 文字 —— 未选 #6C7279、选中 #FF7A45);未选 灰字纯文字;选中 白胶囊 + 选中橙 #FF7A45字(v1.31 起;原品牌红);可左右横滑,右侧渐隐;右侧 深灰 18px 齿轮进「分组管理」;整表无数据时分组轨一并隐藏
- 「全部」Tab 列表结构(自上而下):
1. 在售且未隐藏条目
2. 「已下架 · 停售」分区标题(仅当存在未隐藏的下架条目时出现;分区线在其上方,不在标题与内容之间)
3. 底部折叠条「已隐藏 · 仅顾客端不可见」(无数量;块底 #EEEEEE,与展开区 #F7F7F7 区分;分区线在其上方;版式与「已下架」标题同款 —— 同高 26 / 13px / 居中,仅底色与文字色不同,另仅多一个 14px 折叠箭头,见 §6.1.1);展开后可查看/操作隐藏条目
4. (有数据时)底部虚线 添加项目 / 添加产品 行:等高 44、左右各留 16、1px 虚线品牌红、圆角 12、无底色;左侧 + 约 18px;文案跟当前 Tab(「添加项目」/「添加产品」);按下底色 --brand-soft
- 表头可点循环排序:升序 → 降序 → 取消
- 项目列:项目名称 / 时长(分钟) / 价格(¥)(无状态列)
- 产品列:产品名称(规格) / 价格(¥)(规格并入名称;无库存列)
- 行:拖拽把手、名称(产品为「名称(规格)」;绑卡时名称 is-bound 样式,无卡面图标)、时长(仅项目)、价格;无行内状态胶囊;展开后标题行只留把手+名称(时长/价格隐藏,避免与展开区字段重复)
- 行内字色(v1.31 收敛,前端须照此实现):
元素 常态 绑卡(语义例外) 下架(语义例外) 展开态
名称 .name-text #1A1A1A(显式声明;v1.31 前未声明 color、实际继承初始黑 #000) #333333(var(--text)) #929292(var(--text-sec)) #929292(标题行转灰,见 §6.1.1)
时长 .dur-text #929292 #929292 #929292 隐藏
价格 .price-num #1A1A1A(v1.31 起;原主红 #F32F41) #1A1A1A #929292 隐藏

① 红色不再承担「价格」语义,交给「选中态」独占(--brand-orange #FF7A45,见主 Tab 规格)。 ② 绑卡 / 下架是语义例外,实现上用 :not(.is-bound) / :not(.is-off-sale) 把它们排除在统一规则之外,各自的原声明一字不改 —— 不要用「统一规则 + !important 覆盖」的写法。 ③ 同一行内不得出现两档黑(v1.31 前名称 #000 与价格 #F32F41、名称 #000 与值 #1A1A1A 并存属历史遗留)。 - 默认排序:在售优先 → 桶内 sortOrder → 名称

空态

条件 标题 说明 CTA
整表无数据 暂无项目 / 暂无产品 项目:添加后可在选择项目与办卡中使用;产品:添加后可在开单记账中使用 添加项目 / 添加产品(标题栏「+添加」仍可用)
某自定义组无成员 本组暂无… 可将价目表中的条目加入本组 添加…到本组 → 包含项目 / 包含产品
「隐藏」Tab 无成员 暂无隐藏… 左滑条目可将项目/产品移入隐藏 —

空态块规格(整表无数据;三类空态共用同一骨架)

元素 规格
容器 占据列表主体(flex:1)、白底、水平居中、整表无数据时表头与分组轨不出现
插画 assets/catalog/empty-box.svg,144 × 144,距标题 20
标题 / 说明 14px #B2B2B2;说明距 CTA 28
CTA 项目 / 产品同款:宽 168 × 高 36、1px 虚线品牌红 #F32F41、圆角 12、透明底、品牌红字、按下底色 --brand-soft;文案「添加项目」/「添加产品」跟当前 Tab

行进入:点击主区域 → 就地向下展开该行的详情(不跳页,见 §6.1.1);再次点击同一行 / 点展开区之外(表头、列表空白、别的浮层以外的地方)→ 收起。
左滑:分组 · 下架/上架 · 隐藏/取消隐藏 · 删除(锁定时角标锁)。
长按:无垂直拖动则打开行操作 Sheet(仅条目标题,无副标题;单层 picker-sheet--menu,取消在 foot)。

6.1.1 行内展开区(新增 / 编辑 / 详情)

本模块的唯一交互主体。新增与详情都不再是独立页面(v1.14 起废止全屏页),而是在列表内容区就地向下展开的一块区域。

位置

模式 出现位置
详情(编辑) 目标行的正下方(DOM:<tr 该行的 swipe 容器> 的下一兄弟)
新增(有数据) 列表内容区末尾(底部虚线「添加项目/添加产品」行之后)
新增(空态) 列表内容区顶部,替代空态块

视觉形态:选中行的「向下延伸」——不是一张独立卡片,也不是全屏页、不是 Sheet。

项 规格
两侧 贴边:margin: 0,左右与列表同沿(画布 390 时左 0 / 右 0);新增态仅底部留 12px
底色 与选中行同为 #F7F7F7(--page 灰),不是白底
描边 / 圆角 / 阴影 全部去掉(border:none、border-radius:0、box-shadow:none;无顶部凹陷渐变)——任何阴影或圆角都会立刻读成「另一张卡」
承载行 展开时挂 .has-inline-panel:行底色同为 #F7F7F7、行分隔线隐藏(.table-row::after { opacity: 0 }),与展开区连成一块,交界处无接缝
字段区 不再套白卡(v1.17):.proj-form-section 透明、无圆角,与展开区同宽贴边;容器 padding: 0 0 12px(左右无内缩)。字段直接铺在灰底上;顶沿仍有一条 0.5px #EBEBEB 发丝线(左右各内缩 16、与行间分隔线同色同内缩);行内标签仍 padding: 0 16px
题头 / 通知条 详情态无题头(v1.15)。新增态有可见标题「添加项目 / 添加产品」(v1.20);详情限制原因改由「点锁定字段 → toast」承担(见 §6.2)
页脚 padding: 12 16,上方 0.5px #EBEBEB 分隔线(左右内缩 16);通栏「保存」主按钮(高 48、圆角 12、底 #FFF2F2 --brand-soft + 品牌红 #F32F41 字 16/500 + 品牌红线描「存入托盘」图标、按下 #FEEAEC;v1.31 起由实心红底白字改浅底,满宽 / 高 / 圆角一律不变)。详情态无「删除」(删除走左滑 → 行操作 → 删除)
空态新增 表头一并收起(.table-card.is-add-panel);列表外壳保持可见;底部虚线添加行隐藏

高度预算(v1.15 新增,v1.16 收紧):展开区高度不得超过原全屏表单的 2/3(实测已压到 40%~53%:项目详情 686→312、绑卡态 784→410、产品详情 559→312、下架态 686→312、新增 638→352、新增产品 563→352;上限 457/523)。压缩手段只有三条,且都不许丢信息:

  1. 删掉题头(46px)与通知条(34px);
  2. 三个分组小标题「基本信息 / 售卖设置 / 高级设置」不再渲染(字段直接铺在灰底上);
  3. 「在售」与「可预约」由两行并为一行(proj-form-row--switches + .switch-pair);
  4. 「可预约」的副标题「打开后项目会在小程序-预约中显示」不再渲染(它原本独占一行);这句话的信息改由点击开关后的短 toast 承担(见 §6.2 开关文案),因此不会丢信息。
  5. (v1.16)封面行的副标题「未上传,不影响保存」不再渲染——它不承载限制信息、也不承担状态说明(封面本来就是选填),删掉不丢信息。
  6. (v1.16)字段区顶部那 12px 留白取消(字段区贴展开区顶沿),标签改短文案、值色与图标按设计稿重排,行高与字段一个不少。
  7. (v1.17)字段区去掉白底 / 圆角 / 左右 16px 边距,并删除顶部凹陷阴影 —— 展开区彻底是选中行的灰底延伸。

字段区视觉规范(v1.16 起,v1.17 修订;对齐 Figma「价目表-项目详情·普通」1258:605 人工修改稿)

项 规格
展开区高度 详情态 312 = 字段区 240 + 页脚(顶 12 + 保存 48 + 底 12);新增态 352 = 312 + 题头 40
字段区 与展开区同宽贴边、无白底无圆角;顶沿 0.5px #EBEBEB 发丝线(内缩 16)
行 5 行 × 48px:名称 / 价格 / 时长 / 在售·可预约 / 封面图;行间分隔线 1px #EBEBEB、scaleY(.5)、内缩 16
标签 名称 / 价格 / 时长 / 封面图(可选)(去掉「项目 / 产品」前缀);固定 68px 列宽不变;无障碍名称仍由 aria-label 承担
名称值 #1A1A1A、Medium(500)、右对齐(v1.31 起;原 #929292 灰 —— 与同区标签 / 时长数字框 / 规格输入框同档)
价格值 #1A1A1A、Medium(500)、右对齐(v1.31 起;原 #FE8D4B 橙)。两处均为产品决定,明确偏离 Figma 人工稿 1258:616 / 1258:624
展开态标题行名称 #929292(仅 .has-inline-panel 时生效):名称已在字段区以可编辑值(#1A1A1A)呈现,标题行降级为「这是哪一行」的定位线索,避免同屏两个等重黑体项目名;折叠行不受影响
行右铅笔图标 名称 / 价格行右端 16×16(描边 #C7C7CC、线宽 1.05;图形=10×10 铅笔 + 右下 5.25 长横线),与值间距 4px(其余行仍 8px);pointer-events:none,只作「可点编辑」的视觉线索
时长分段 数字框 68×32 + 分/时 90×32(白底、#EDEDED 1px、圆角 8);选中底色 #FE8B4B@10%(原蓝 #4A7BF4@10%)、选中字 #FF8A4C、未选中字 #929292。v1.31 明确:时长控件不受「右侧数值改单档黑」影响,配色保持本行原样
开关 40×22、圆角 11、白点 18×18;开启轨道渐变 135deg #FF974B → #FE7A4C(与设计稿逐色一致)
封面行 单行「封面图(可选)」+ 右侧 chevron(16×16 框内 8×4、#929292);无副标题
保存按钮 通栏主按钮(浅底 tinted,v1.31):宽 = 展开区 − 左右 16、高 48、圆角 12;底 --brand-soft #FFF2F2、字品牌红 #F32F41 16 Medium、图标随字色(currentColor)、按下 #FEEAEC、无边框无阴影;页脚上方 0.5px #EBEBEB 分隔线(左右内缩 16)。只降对比度,不减触摸区(高仍 48 ≥ 44)。
· 图标为「存入托盘」线描稿(viewBox 24 / stroke-width 2.3 / 圆头圆角 / 3 段线):渲染 16px 时有效线宽 1.53px(与模块图标族 1.56px 同档)、墨迹 13.0 × 13.27px(≈ 原实心软盘 13.5px,换图标不改视觉重量)。fill 必须为 none —— 线描图形一旦被 fill: currentColor 命中就会整体填实。
· 弹窗「确定 / 删除」不适用本条 —— 仍为实心品牌红 #F32F41 + 白字(强打断场景,实心红合理)

表头固定与滚动容器(v1.15 新增)

滚动容器从 .page-body 下移到 #projProjectTable:.table-card 为 flex column,Tab 条 / 分组轨 / 表头都是 flex-shrink:0 的固定层,#projProjectTable 为 flex:1; overflow-y:auto(隐藏滚动条)。

内部版式(工整基准,前端须照此实现)

展开区只有 两级左基准,所有元素必须落在其中一条线上(实测值,画布 390):

层级 左沿 命中元素
展开区贴边 0(与列表同沿) 灰底展开区本身
列表内容级 16px 表单标签、控件、行分隔线(左右同为 16);页脚「保存」居中

「最佳操作位」(滚动定位)

展开时必须滚动列表,把展开区送到手指最舒服的位置。规则(滚动容器是 #projProjectTable,见上「表头固定与滚动容器」):

面板高度 落点
≤ 可视区高 垂直居中:上下留白相等
> 可视区高 顶边锚定:顶边贴表头下沿 +12px(CIN_FOCUS_GAP),其余部分靠滚动查看

落点会被物理夹住:目标 scrollTop = clamp(0, maxTop)。下面三种情况滚不动,此时只保证「整块可见」,不再追求严格居中(前端不要为此改成别的算法):

情况 为什么滚不动
选中/新增的是列表首行 已经在顶部,再往上滚没有内容
空态新增 列表没有可滚内容(maxTop = 0)
新增区挂在内容末尾 已经在底部,再往下滚没有内容

实现要点(易错,务必照做):展开动画是把 height 从 0 长起来,scrollHeight 会先变矮再变高,浏览器会把 scrollTop 夹回 → 面板再也回不到焦点线(实测「已下架」区块内条目偏差可达 378px)。因此:

  1. 先以自然高度(未收起)量出落点并立即设 scrollTop;
  2. 动画期间逐帧锁死面板顶边(每帧比对顶边偏差并补回 scrollTop);
  3. 新增态需先让新行入列并可见,再量落点。

动效(统一缓动 cubic-bezier(.22,.82,.24,1),变量 --cip-ease)

节奏口径(v1.15):保存反馈必须快、干净利落——三个时长整组压短(收起 240→120、归位 420→260、高亮 1200→320)。原先 1.2s 的高亮显得拖沓,用户会以为界面卡住。

动效 时长 说明
展开 320ms(--cip-open) height 0 → 目标 + margin-bottom + opacity(透明度约 75% 时长);同帧起飞,落定后清掉内联样式
收起 120ms(--cip-close) 反向:height → 0 + opacity → 0(透明度约 80% 时长)
新增归位 260ms(--cip-flip) 收起后 ghost 从展开区尺寸长成目标行(见下)
保存高亮 320ms(--cip-glow) 该行 catalogRowSavedGlow 一次:浅绿底 #F3FAF5 + 淡绿内描边 rgba(52,199,89,.28),70% 处开始淡出;结束后移除类,行上不留痕

保存反馈

场景 反馈序列
详情保存 收起(120ms)→ 重绘该行(名称/价格/时长可能已变)→ 滚动保证该行在可视区 → 该行高亮闪一次(320ms 后自动淡出)+ toast「保存成功」
新增保存 收起(120ms)→ 重绘(新行入列)→ 滚动到新行 → 完整 FLIP 归位:一个 ghost 从原展开区的位置和尺寸,同时把高度压到行高、把宽度长到通栏行宽、并淡出(260ms),与下方真实行无缝交接 → 新行高亮闪一次 + toast「项目已创建 / 产品已创建」

FLIP 口径:展开区是贴边灰底、目标是通栏行 → ghost 必须两边同时变(高变矮 + 宽变宽),只做位移会看出穿帮。末帧 ghost 淡出(内部内容在 42% 时长处先退场,避免被压成一条),由真实行接管。

收起时机(手风琴)

状态与数据口径

关注点 口径
当前编辑项 state.projEditingId(详情态 = 该条目 id;新增态 = null),禁用独立的全屏页状态
展开区状态 catalogInline = { mode: 'add'|'edit'|null, id, dirty, busy, base }
脏值判定 进入时快照表单可编辑字段(base),输入 / 开关 / 时长 / 图片变更时与快照比对置 dirty
保存 复用同一份提交流程(校验 → 写库 → 返回 {ok,id,isNew,isProduct}),与「底部添加行」「标题栏 +添加」「空态 CTA」入口共用,不得各写一套
长按拖拽热区 展开区占位期间,该行的长按拖拽与左滑按 §6.7 规则继续生效

DOM / 类名钩子(给前端对齐用;实现风格可换,语义要留)

#catalogInlinePanel(外壳;新增态 aria-label + .catalog-inline-panel__head 可见标题为「添加项目 / 添加产品」;详情态仅 aria-label)· .catalog-inline-panel--joined(详情态,紧接选中行)· .catalog-inline-panel--add(新增态,挂在售末行下)· .catalog-inline-panel__foot(页脚)· #catalogInlineBody(表单容器)· #catalogInlineSave(保存)· .catalog-flip-ghost(新增归位 ghost)· .table-row-swipe.has-inline-panel(承载行)· .table-row-swipe.is-saved-glow(保存高亮)· .table-card.is-add-panel(空态新增)。

v1.15 起详情态不再有题头与通知条;v1.20 起新增态题头回归,并复用 #catalogInlineHead(40px,catalog.js 新增态渲染处),#catalogInlineTitle / #catalogInlineNotice 仍不渲染。

6.2 新增 / 编辑 · 项目

表单字段按下表分为三段(基本信息 → 售卖设置 → 高级设置)——v1.15 起不再渲染三段小标题,全部字段并入展开区里的一张白卡;下表的分段只表示字段归属与顺序。

分区 字段 必填 默认(新增) 说明
基本信息 名称 ✓ 空 最多 30 字;同桶唯一(trim 后);绑卡后锁定;placeholder「请输入」;值 #1A1A1A Medium、行右带铅笔图标(v1.16 标签由「项目名称」改「名称」;值色 v1.31 由 #929292 灰改单档黑)
基本信息 价格 ✓ 空 元;金额键盘;≥0;上限 999,999.99;键盘「确定」后回填表单价格值(保存校验以此为准,禁止只写入输入框而不回填);值 #1A1A1A Medium、行右带铅笔图标(v1.16 标签由「项目价格」改「价格」;值色 v1.31 由 #FE8D4B 橙改单档黑);点价格不得收起展开区(键盘打开期间外部收起手势暂停;输入缓冲去掉展示前缀 ¥)
基本信息 时长 ✓ 60 分钟 行内分/时输入;≥1 分钟;选中态底色 #FE8B4B@10%、选中字 #FF8A4C(v1.16 标签由「项目时长」改「时长」)
售卖设置 在售 — 开 绑卡 不锁定;点击开关后短 toast:开 已上架,顾客端可见 / 关 已下架,顾客端不可见、不可预约(关在售会连带关掉可预约并锁定它)
售卖设置 可预约 — 开 表单标签为「可预约」;下架、隐藏或绑卡时锁定(压暗 + 点它弹原因);隐藏解除后仍为关,须手动开。v1.15 起没有副标题,信息改为点击开关时的短 toast:开 已开启,项目会在小程序「预约」中显示 / 关 已关闭,小程序「预约」中不再显示
高级设置 封面图(可选) — 未上传 折叠轨 +「添加图片」;最多 1 张演示图;v1.16 起无副标题(原「未上传,不影响保存」已删)、标签去掉「项目」前缀

分类写入默认「其他」(对用户可无独立控件)。

限制信息的展示口径(v1.15):展开区没有提示条了。字段级的限制改为「锁定 + 点它说原因」:

状态 锁定表现 点它时(toast 文案)
绑卡 名称输入框 disabled、行挂 .is-locked 该项目已绑定会员卡,项目名称不可修改
绑卡 可预约开关 disabled、opacity .45、pointer-events:none(锁定只挂在这一格上,「在售」永远可点) 该项目已绑定会员卡,预约设置不可修改
已下架 可预约开关锁定 项目已下架,请先开启「在售」后再设置可预约
已隐藏 可预约开关锁定 项目已隐藏,取消隐藏后需手动开启可预约
绑卡 / 关联卡券 / 商城 删除入口锁定(左滑角标锁) 该项目已绑定会员卡,不可删除 / 已关联会员卡/优惠券/商城,不可删除

展开区页脚:仅「保存」(通栏主按钮,浅品牌底 #FFF2F2 + 品牌红字,v1.31 起;原实心红底白字)。删除不在展开区内 —— 走行左滑 → 行操作 Sheet → 删除,确认文案「确认删除?」「删除后不可恢复,是否继续?」。

6.3 新增 / 编辑 · 产品

与项目共用表单骨架(字段归属同三段,同样不渲染小标题)。展开区 aria-label 为「新增产品 / 产品详情」。

分区 字段 必填 默认(新增) 说明
基本信息 名称 ✓ 空 最多 30 字;表单标签「名称」(v1.16);同桶唯一(trim 后);校验空名时 toast「请填写产品名称」;重名 toast「名称已存在」
基本信息 规格 — 空 placeholder「选填」;最多 20;空则列表仅显示名称;无铅笔图标(保持 #1A1A1A Regular,设计稿未定义该行)
基本信息 价格 ✓ 空 同价格规则(≥0;上限 999,999.99);键盘「确定」后回填表单价格值,保存校验以此为准;值 #1A1A1A Medium、行右带铅笔图标(值色 v1.31 由 #FE8D4B 橙改单档黑)
售卖设置 在售 — 开
高级设置 封面图(可选) — 未上传 同项目演示图规则;v1.16 起无副标题、标签去掉「产品」前缀

无时长、无预约开关、无库存字段(见 8.1)。分类默认「产品」。产品详情无项目类限制提示条(v1.15 起两者都没有提示条)。

6.4 分组管理

6.5 包含项目 / 包含产品

6.6 设置分组(条目)

6.7 拖拽排序(列表)

仅在同一 sort-bucket 内重排并写回 sortOrder:

桶 条件
sale 在售
off 已下架

不可跨桶;自定义分组 Tab 内可拖(仍限同售卖态桶)。「隐藏」Tab / 「全部」已隐藏折叠内条目 / 列排序激活时拦截(文案见模块 5;无搜索)。

进入手势(两条等价入口,都要能拖)

入口 行为
长按行主体 按住 400ms 后进入待拖态(轻微振动反馈;此时松手不移动 = 打开行操作 Sheet,原语义不变)
按住行左侧拖拽把手 立即进入待拖态,无需长按

进入待拖态后,垂直位移超过 8px 且不小于水平位移即正式起拖(水平位移超过 10px 则判为左滑,让位给快捷操作)。

拖拽中

落位与写回

  1. 松手 → 被拖行插入占位块位置,占位块移除;
  2. 按该桶最终 DOM 顺序把 sortOrder 写回 0…n-1;
  3. 重绘列表(在售优先 → 桶内 sortOrder → 名称)。

写回口径:只覆盖同桶可见项的相对次序;跨桶条目与当前筛除在外的条目保持原有相对顺序(仅「列排序」激活时拦截拖拽、不写回;「分组筛选」下不拦截,可在自定义分组 Tab 内正常拖拽并写回,见 §6.5)。

原型范围说明:独立原型为内存演示数据,整页刷新会重置为演示造数(见「先读这几条」第 7 条)。因此验收口径为 「同会话重绘后顺序保持」(重绘列表、切 Tab 往返);「刷新后顺序保持」由服务端持久化 sortOrder 保证,属接口约定(模块 7)。

6.8 与会员卡 / 开单联结

入口 行为
添加项目权益 / 添加产品权益 / 添加折扣权益 标题「管理价目表」 openPriceCatalog,记录返回页;返回后刷新对应选择列表,并 刷新分组 Tab(重置为「全部」)
权益选择列表 仅 onSale && !hidden;可按项目或产品 自定义 分组 Tab 筛选(不展示 系统「隐藏」Tab)
分组空 本组暂无…;请在「管理价目表」中将项目/产品加入本组
开单选品 通过 CardCatalogBridge.getBillProjects / getBillProducts 读取价目实时目录,再过滤为 仅排除已下架(onSale;含 hidden);分组轨含系统「隐藏」Tab;价目产品 不限购(catalogStockLimit 恒返回 null,见 8.1);改价后开单侧可见最新价;缓存/推送实现【待联调确认】
开单支付·价目改价 进入结账时快照应付;打开支付 / 确认支付前按价目重算;若与快照不一致 → Dialog「支付金额已变化」:「价目表已更新,当前应付与开单时不一致,请返回核对后再支付。」按钮「知道了」→ 关闭支付层,留在开单详情。范围:账单 payMask(非办卡/充值支付)

会员卡权益组合、办卡支付等 不在本文展开(见 PRD-会员卡管理.md)。


模块 7 数据模型与接口约定

读者:前端 ✓ · 后端 必看 · 测试 ✓

7.1 项目 Project

字段 类型 说明
id string 唯一
name string 展示与绑卡匹配键;同桶唯一(trim 后)
price number 金额,建议存分或两位小数【待联调确认】
duration number 分钟
onSale boolean
hidden boolean 对顾客端隐藏;与系统「隐藏」组成员同步
bookable boolean 可预约
images string[] 封面图本地/演示路径;表单规范化最多 1 张
hasImage boolean 是否有图(可由 images.length 派生;正式环境应为图片 URL 列表【待联调确认】)
category string 默认「其他」
boundToCard boolean 按模板引用自动派生并刷新;是否另落库【待联调确认】
boundToCoupon / boundToMall boolean 删除锁定来源【待联调确认】
boundTemplateIds / Names string[] 引用模板
boundTemplateId string / null 单模板引用(= boundTemplateIds[0];无则 null);boundToCard 由此派生
sortOrder number 桶内序

7.2 产品 Product

字段 类型 说明
id string
name string 同桶唯一(trim 后)
spec string 可空;列表展示拼入名称
price number
stock number | null 本期价目表单不读写;保存不写入;开单限购忽略(见 8.1)。字段可保留兼容,语义不与本期价目 UI 绑定
onSale boolean
hidden boolean 同项目
category string 默认「产品」
images / hasImage 同项目
bound* 同项目
sortOrder number

7.3 分组 CatalogGroup

字段 类型 说明
id string 系统隐藏组:g_sys_hidden_project / g_sys_hidden_product
name string 桶内唯一,≤20 字
system boolean 系统固定组(「隐藏」);不可重命名/删除
itemIds string[] 成员 id;允许空

桶:project | product 两套分组互不影响。

写入口径(v1.32):桶数组(state.catalogGroups[bucket])是分组的唯一真源 —— 新增 / 删除 / 排序一律写该数组;getActiveCatalogGroups() 返回的只是只读视图(自定义组… + 系统「隐藏」),不得在其返回值上做结构变更(push / splice / 交换),否则改动会落空而 UI 貌似成功。系统「隐藏」组在桶内固定末尾,不可删除、不可重命名。

7.4 能力清单(路径【待联调确认】;正式文档暂无交付时间)

能力 说明
列表查询 按类型、分组、排序;分页见下
创建/更新/删除项目或产品 校验(含同桶名称唯一);删前校验绑卡/优惠券/商城
上下架 / 隐藏
更新 sortOrder(批量) 桶内重排
分组 CRUD 删组不删货;系统组只读除成员
设置组成员 / 条目多分组 含系统「隐藏」同步
封面图上传 本期不做正式 OSS;原型最多 1 张演示
绑卡状态查询或同步 与会员卡模板引用对齐;自动刷新
开单价目桥接 实时目录供开单消费;开单侧过滤仅排除已下架(含 hidden);价目产品不限购
库存数量读写 不在本期价目表;由后续「库存管理」模块负责【待联调确认】
变更后通知顾客端缓存/展示 同步策略【待联调确认】

列表分页(常规方案)

7.5 时序(示意)

商户保存项目
  → API 更新价目
  → 写库成功
  → 异步/同步刷新顾客端可见价目【待联调确认】
  → 开单模块读取最新目录【待联调确认】
  → 客户端 toast 成功并回列表

商户从权益页打开价目表
  → 带 returnContext
  → 编辑保存
  → 返回权益页
  → 重新拉取在售目录(开单含 hidden;顾客端 / 权益仍为 onSale&&!hidden)与分组

模块 8 业务规则

读者:前端 ✓ · 后端 必看 · 测试 ✓

  1. 权益选品范围:会员卡/折扣/产品权益选择器只展示 onSale=true && hidden=false。
  2. 开单选品范围:仅 onSale=true(含 hidden;已下架不可选;与权益口径不同)。
  3. 顾客端可见:onSale && !hidden;项目预约另需 bookable。
  4. 产品库存:价目侧本期不维护;开单对价目产品不限购(见 8.1)。
  5. 开单支付·价目改价:结账快照应付;打开/确认支付前重算;不一致则 Dialog「支付金额已变化」→「知道了」关支付层,留在开单详情(见 6.8)。
  6. 绑卡匹配:以 名称 与模板权益引用对齐;同桶名称唯一;改名锁定避免孤儿引用;解除引用后 boundToCard 自动刷新。
  7. 绑卡上下架:绑卡 不 阻止改 onSale。
  8. 删组:只移除分组实体与成员关系,不删除项目/产品。
  9. 多分组:同一条目可出现在多个 itemIds 中;加入系统「隐藏」组即 hidden=true。
  10. 排序桶:在售 / 下架 两桶隔离;sortOrder 仅在桶内生效。
  11. 下架与预约:项目下架 ⇒ bookable=false 且不可开,直到重新在售。
  12. 隐藏与预约:项目隐藏 ⇒ bookable=false 且隐藏期间不可开;取消隐藏后仍为关,须手动再开「可预约」。
  13. B→C / 开单同步:价目变更后客户端与开单侧按 实时目录/价格 语义生效;推送与缓存细节【待联调确认】;顾客端页面结构不在范围。
  14. 列排序 vs 手动排序:列排序为临时视图;手动拖拽前须取消列排序;「隐藏」Tab 与「全部」已隐藏折叠内不可拖。
  15. 产品无预约字段;项目才有「可预约」。
  16. 权益选品分组 Tab:仅自定义组 +「全部」;不展示 系统「隐藏」Tab;从「管理价目表」返回后 刷新分组 Tab(重置为「全部」)。
  17. 封面图:选填;演示最多 1 张;正式 OSS 本期不做。
  18. 权限:店主可编辑价目;店员不可编辑。
  19. 门店范围:本期仅单店。

8.1 产品库存规则(以当前原型为准)

适用范围说明:价目表产品侧本期不维护库存字段。

项 当前原型表现
产品表单 无「库存」输入;保存时不写入价目 stock
产品列表 列仅为 名称(规格)+ 价格,无库存列
开单限购 catalogStockLimit() 恒返回不限购;不按价目 stock 封顶
与「库存管理」 后续库存能力走独立模块 / StockStore 等路径;不与本期价目表单字段绑定

开单选品范围仍为 onSale(含 hidden)。支付金额因价目改价不一致时,仍走 Dialog「支付金额已变化」(FLOW:bill-pay-price-changed,归属开单分组)。

若后续产品再次在价目表恢复「选填库存」UI,须同步改本节与 §6.1 / §6.3 / §5.2 / §9.1 / 验收用例。


模块 9 文案清单与权限

读者:前端 ✓ · 后端 部分 · 测试 ✓

9.1 文案清单(须与原型一致)

导航/按钮:价目表、项目、产品、分组管理、包含项目、包含产品、新增项目、新增产品、项目详情、产品详情、保存、删除、新建分组、确定、取消、全选、反选、设置分组、下架、上架、隐藏、取消隐藏、分组、重命名、包含项目、包含产品、去新建分组、+添加、添加项目、添加产品、封面图(可选)、添加图片、在售、已下架、已隐藏、可预约、规格、时长(分钟)、价格(¥)、全部、添加项目、添加产品、知道了。 表单标签(v1.16 改短):名称、价格、时长、规格、在售、可预约、封面图(可选)。

Toast:请输入分组名称、分组名称已存在、名称已存在、已重命名、已新建分组、已删除分组、已更新成员、已更新分组、已隐藏、已取消隐藏、已上架、已下架、保存成功、项目已创建、产品已创建、已删除、请填写项目名称、请填写产品名称、请填写有效价格、请填写项目时长、「隐藏」分组内不支持拖拽排序,请切换到「全部」或其它分组、已隐藏的项目不支持拖拽排序、请先取消列排序后再手动排序、已添加演示图片、已移除图片、仅可上传 1 张封面、项目已隐藏,取消隐藏后需手动开启可预约、项目已下架,请先开启「在售」后再设置可预约、系统分组不可重命名或删除;以及模块 5.5 绑卡/预约/删除拦截句(删除可为「已关联会员卡/优惠券/商城,不可删除」动态拼接)。

Dialog(开单支付):标题「支付金额已变化」;正文「价目表已更新,当前应付与开单时不一致,请返回核对后再支付。」;按钮「知道了」。

Dialog/Sheet:确认删除?、删除后不可恢复,是否继续?、删除分组?、仅删除分组,不会删除项目/产品。、最多 20 字,不可与已有分组重名、可同时加入多个分组、去新建分组、长按项目可拖动排序,左滑快捷操作;分组可左右滑动。

Toast(开关与锁定):已上架,顾客端可见 / 已下架,顾客端不可见、不可预约 / 已开启,项目会在小程序「预约」中显示 / 已关闭,小程序「预约」中不再显示 / 该项目已绑定会员卡,项目名称不可修改 / 该项目已绑定会员卡,预约设置不可修改 / 项目已下架,请先开启「在售」后再设置可预约 / 项目已隐藏,取消隐藏后需手动开启可预约。

空态:暂无项目/产品、暂无隐藏项目/产品、本组暂无项目/产品、暂无分组、创建分组后可快速筛选价目表、暂无可选项目/产品、可将价目表中的条目加入本组、左滑条目可将项目/产品移入隐藏、添加后可在选择项目与办卡中使用、添加后可在开单记账中使用、请在「管理价目表」中将项目/产品加入本组。

9.2 权限

角色 价目表
店主 增删改查、分组、排序、上下架/隐藏等全文操作
店员 不可编辑/修改价目表(无写入权限)

本期交互原型按 店主 路径演示,不单独做店员只读壳。


模块 10 验收 P0 + 造数场景

读者:测试 必看

10.1 造数

场景 说明
空数据 项目/产品/分组皆空(原型「无数据」)
有数据 演示多项项目与产品、多分组(含空组)、含下架/隐藏/绑卡/优惠券或商城锁样例(原型「有数据」)

正式环境自备等价数据;演示开关不进产品。

10.2 Given → When → Then(P0)

  1. Given 项目 Tab 为空,When 保存合法新项目,Then 列表在售区出现,可被选项目(未隐藏时)。
  2. Given 产品 Tab,When 保存带规格产品,Then 列表名称列展示「名称(规格)」;无库存列。
  3. Given 产品表单,When 查看基本信息,Then 有规格(选填)、无库存输入。
  4. Given 在售未隐藏产品,When 开单对该产品加购任意数量,Then 不以价目 stock 限购(catalogStockLimit 不限购)。
  5. Given 列表有数据,When 滚到底部,Then 可见虚线「添加项目」或「添加产品」行(跟当前 Tab)。
  6. Given 未绑卡在售项目,When 下架,Then 落入「已下架」分区;顾客端不可见;开单与权益选品均不可见。
  7. Given 绑卡项目,When 尝试删除或改名称/预约,Then toast 拦截且数据不变;删除控件可见锁标。
  8. Given 绑卡项目,When 改在售/下架并保存或左滑,Then 操作成功。
  9. Given 绑卡项目,When 改价格并保存,Then 保存成功。
  10. Given 已下架项目详情,When 查看预约开关,Then 关闭且不可开,直到重新在售。
  11. Given 在售且可预约的项目,When 左滑隐藏或加入系统「隐藏」组,Then 「全部」主列表不显示但底部「已隐藏」折叠可展开;「隐藏」Tab 显示;顾客端不可见;开单仍可选;权益选品不可见;bookable=false。
  12. Given 隐藏前可预约的项目,When 取消隐藏,Then 回到「全部」主列表可见(在售时顾客端可见);「可预约」仍为关,须手动再开。
  13. Given 新建分组并在包含项目页勾选多项,When 确定,Then 按组筛选只显示非 hidden 成员。
  14. Given 条目属于两组,When 删除其中一组,Then 条目仍在,另一组关系保留。
  15. Given 在售区两行,When 长按行主体(或按住行左侧拖拽把手)上下拖动并松手,Then 被拖行落到占位块位置;sortOrder 按桶内最终顺序写回;同会话重绘(重绘列表 / 切 Tab 往返)后顺序保持;接口侧刷新后顺序保持。
  16. Given 当前「隐藏」Tab,When 拖拽,Then toast「「隐藏」分组内不支持拖拽排序,请切换到「全部」或其它分组」。
    16b. Given 「全部」Tab 已展开「已隐藏」折叠,When 拖拽折叠内条目,Then toast「已隐藏的项目不支持拖拽排序」。
  17. Given 列排序激活,When 拖拽,Then toast「请先取消列排序后再手动排序」。
  18. Given 从添加项目权益进「管理价目表」并新增项目,When 返回,Then 回到权益页且可选到新在售未隐藏项目,分组 Tab 刷新为「全部」。
  19. Given 已下架产品,When 开单选品,Then 列表中不出现该产品。
    19b. Given 在售且已隐藏产品,When 开单选品,Then 「全部」与「隐藏」Tab 均可见且可加购;权益选品仍不可见。
  20. Given 开单已选品并进入支付前,When 价目表改价导致应付与快照不一致,Then Dialog「支付金额已变化」;点「知道了」关闭支付层并留在开单详情。
  21. Given 开单已选价目产品且支付成功,When 支付完成,Then 不因价目表 stock 扣减(库存管理另模块)。
  22. Given 展开区时长字段未填或为 0,When 保存,Then toast「请填写项目时长」,保存被拦截。
  23. Given 项目详情高级设置,When 添加第 2 张封面,Then toast「仅可上传 1 张封面」。
  24. Given 「全部」列表有下架与隐藏条目,When 浏览,Then 无状态列;可见「已下架 · 停售」分区标题与「已隐藏 · 仅顾客端不可见」折叠条(无数量),二者同高 26 / 同字号 13 / 同字重 400 / 同居中 / 同内边距 0 16px、仅底色(#fff vs #EEEEEE)与文字色(#929292 vs #333333)不同;「已隐藏」只多一个 14px 折叠箭头、无眼睛图标;两条分区线各自在其标题上方(左右内缩 16),且行不再重复画线 —— 有已下架时该区段共 2 条线,无已下架条目(隐藏块与末行直接相邻)时交界处同样只有 1 条;任何情况下同一 y 上不得有 2 个画线者(否则叠成一条更重的线)。
  25. Given 当前为项目 Tab 且列表有数据,When 点标题栏「+添加」,Then 不弹窗,在列表内容区就地展开「新增项目」(§6.1.1);标题栏入口为品牌红图标+文字链(无红底胶囊按钮壳)。
  26. Given 当前为产品 Tab 且列表有数据,When 点标题栏「+添加」,Then 不弹窗,在列表内容区就地展开「新增产品」。
  27. Given 任意价目表列表态,When 查看界面,Then 无右下悬浮球「+」;整表无数据时可用空态 CTA 添加,标题栏「+添加」始终可用。
  28. Given 同桶已存在名称「精致剪发」,When 再保存同名项目(trim 后相同),Then toast「名称已存在」且不写入。
  29. Given 模板已解除对某项目名称的引用,When 进入价目列表或触发绑卡同步,Then 该条目 boundToCard 自动为 false,无需手动刷新。
  30. Given 产品表单,When 查看规格输入框,Then placeholder 为「选填」;无库存输入框。
  31. Given 项目 Tab 整表无数据,When 从导航 / 深链进入空态,Then 只出现空态块(插画 + 标题「暂无项目」+ 说明 + 虚线 CTA);不出现表头、分组轨、列表行与拖拽提示条(不得残留进入前的列表)。
  32. Given 空态,When 看 CTA(项目 / 产品分别查看),Then 两者为同款虚线品牌红按钮(宽 168 × 高 36、1px 虚线 #F32F41、圆角 12、透明底、品牌红字),仅文案随 Tab 变(「添加项目」/「添加产品」)。
  33. Given 空态,When 点 CTA → 填名称 → 金额键盘输入并「确定」→「保存」,Then 键盘值回填表单价格;保存成功回列表并在售区出现该行;toast「项目已创建」(产品为「产品已创建」)。
  34. Given 列表有数据,When 滚到底部,Then 底部虚线添加行为 1px 虚线品牌红、圆角 12、高 44、左侧 + 18px(不是浏览器默认按钮外观)。
  35. Given 处于「行操作 Sheet」等自带浮层的节点,When 切换导航 / 深链到另一节点,Then 上一节点的 Sheet / Dialog 不残留;目标节点自身该开的浮层照常打开。
  36. Given 任意 Tab 有数据,When 长按行主体并垂直拖动,Then 该行跟手抬起、原位留等高占位块,松手后落到占位块位置(不得出现拖拽无响应或未捕获异常)。
  37. Given 价目表列表,When 看「项目 / 产品」Tab,Then 选中项同时有三重差异:文字选中橙 #FF7A45(v1.31 起;原品牌红 #F32F41)、字重 600、下方橙下划线(高 3 / 圆角 2 / 贴 Tab 条下沿 / 宽 = 文字宽 + 20,色同为 #FF7A45);未选项为 #6C7279 400 且无下划线。仅靠文字变化区分视为不通过。
  38. Given 项目 Tab 选中,When 点「产品」,Then 下划线滑动(有中间帧)到第 2 项中心并在 160ms 内到位,落点误差 <1px;不出现瞬移,也不出现两条下划线同时存在。
  39. Given 产品 Tab 选中,When 刷新页面 / 从导航重新进入列表,Then Tab 条高度仍为 45、两项仍各 194 等宽(下划线绝对定位,不参与流、不撑开布局)。
  40. Given 系统设定「减少动态效果」,When 切换 Tab,Then 下划线直接切换位置(无过渡),选中态仍然可辨。
  41. Given 列表有数据,When 点某行主区域,Then 该行正下方就地展开详情区(不跳页、不出现新页面);再点同一行 / 点展开区之外,Then 收起。展开区两侧贴边、灰底 #F7F7F7 与选中行同色、无圆角无描边无阴影,字段直接铺在灰底上——不得读成「另一张浮起来的白卡」,也不得有顶部凹陷阴影。
  42. Given 已展开某行的详情,When 点另一行,Then 旧展开区先收起、新展开区再展开;任意时刻只有一个展开区。
  43. Given 展开区高度 ≤ 可视区,When 展开,Then 上下留白相等(垂直居中);Given 展开区高于可视区,Then 顶边贴表头下沿 +12px。Given 落点被物理夹住(选中首行 / 空态无可滚内容 / 新增区在内容末尾),Then 保证展开区整块可见即可。三种情况下均不得出现顶边被顶出可视区、或展开后位置明显偏离(含「已下架」区块内的条目)。
  44. Given 展开区内改了名称/价格但未保存,When 点行头 / 点外部 / 点另一行收起,Then 弹「放弃修改?」确认;点「继续编辑」保留改动,点「放弃」丢弃。
  45. Given 展开区内保存成功(详情),When 保存,Then 展开区收起(120ms)→ 列表该行立即显示新名称/新价格 → 该行高亮闪一次(320ms 内淡出;底 #F3FAF5、描边约 28% 绿,不得用过重的实心 #34C759)→ toast「保存成功」。不得出现 1s 级别的拖沓高亮。
    45b. Given 详情或新增展开区已打开,When 点价格字段,Then 打开金额键盘且展开区保持打开;输入并「确定」后价格回填;不得因点价格而退出编辑。
    45c. Given 某行已展开,When 看该行标题区,Then 不显示时长与价格(只留把手+名称);「已隐藏」折叠块底色为 #EEEEEE,与展开区 #F7F7F7 可区分。
  46. Given 空态点 CTA 打开新增展开区(表头与分组轨收起、列表外壳保留、底部虚线添加行隐藏),When 填名称 + 金额键盘「确定」+ 保存,Then 展开区收起(120ms)→ 新行入列 → 归位动效(ghost 高变矮 + 宽变宽 + 淡出,260ms)→ 新行高亮闪一次(320ms)→ toast「项目已创建」(产品「产品已创建」);底部虚线添加行出现。
  47. Given 展开区(新增或详情),When 检查内部版式,Then 表单标签为固定 68px 列宽、所有控件左沿落在同一条竖线上、行分隔线左右各内缩 16;不得出现控件左沿参差(如 4 字标签与 2 字标签各行其是);不再有三段分组小标题。v1.16 起标签文案为 名称 / 价格 / 时长 / 封面图(可选);v1.18 起页脚「保存」为通栏主按钮(高 48,v1.31 起为浅品牌底 #FFF2F2 + 品牌红字)。
  48. Given 展开区,When 检查触控热区与高度,Then 每个表单行高 ≥ 48、「保存」按钮 ≥ 44、展开区无横向溢出;整体高度 ≤ 原全屏表单的 2/3;不再有题头、提示条与「可预约」副标题。
    48b. Given 展开区的「在售 / 可预约」开关,When 点击,Then 弹出短 toast 说明后果(已上架,顾客端可见 / 已下架,顾客端不可见、不可预约 / 已开启,项目会在小程序「预约」中显示 / 已关闭,小程序「预约」中不再显示),且状态真的翻转;Given 关掉「在售」,Then 「可预约」被连带置为关并锁定;Given 锁定态(绑卡/下架/隐藏)点开关,Then 弹的是锁定原因而不是开关文案。
  49. Given 展开区已展开,When 长按承载行或按住其拖拽把手上下拖动,Then 拖拽排序照常可用(§6.7 规则不变)。
  50. Given 深链 price-edit-normal / price-edit-product 等 7 个新增/详情类节点,When 进入,Then 落在列表页并自动展开对应位置(新增在内容区、详情在目标行下方)且位于最佳操作位;不得出现独立全屏页,也不得残留上一轮的展开区。
  51. Given 价目表列表(有数据),When 上下滚动内容区,Then Tab 条、分组轨、表头一律不动(位移 Δ = 0),只有列表行在动;表头下沿即列表可视区顶边,行不得钻到表头底下。
  52. Given 绑卡项目(原型深链 price-edit-bound 演示项),When 查看展开区,Then 名称输入框不可编辑、可预约开关压暗且不可开;点它们分别弹 toast「该项目已绑定会员卡,项目名称不可修改」/「该项目已绑定会员卡,预约设置不可修改」;点「在售」不弹锁定 toast(锁定只挂在该格上)。
  53. Given 展开区(项目 / 产品的详情与新增),When 检查字段区视觉,Then 名称值 #1A1A1A Medium、价格值 #1A1A1A Medium(v1.31 起统一为单档黑;原为 #929292 灰 / #FE8D4B 橙),两行右端各有 16×16 铅笔图标(#C7C7CC、线宽 1.05、与值间距 4px),时长选中态为 #FE8B4B@10% 淡底 + #FF8A4C 字(未选中字 #929292、时长控件是唯一不受本条约束的例外);不得再出现蓝色选中态。
  54. Given 展开区,When 展开,Then 字段区与展开区顶沿齐平、同宽贴边(无白底、无圆角、无左右灰缝),顶沿有一条 0.5px #EBEBEB 发丝线(左右内缩 16,与行间分隔线同色同内缩);不得再出现白卡或顶部凹陷阴影。
  55. Given 展开区,When 查看底部「保存」,Then 是通栏主按钮(左右各 16、高 48、圆角 12)、底 #FFF2F2、字品牌红 #F32F41、图标同色(currentColor)(v1.31 起为浅底 tinted;原为实心 #F32F41 底 + 白字 + 白图标);页脚上方有 0.5px #EBEBEB 分隔线;不得再是透明药丸红字,也不得是实心红底。Given 打开任一弹窗,Then 其「确定 / 删除」仍为实心品牌红——本条的浅底只适用于展开区保存按钮。
  56. Given 项目详情展开区,When 量高度,Then 312px(字段区 240 + 页脚 12+48+12;新增态 352px = 312 + 题头 40);产品详情 / 下架态 / 新增形态同量级、绑卡态更高;封面行只有一行「封面图(可选)」,不得再有「未上传,不影响保存」。
  57. Given 价目表列表行,When 检查垂直对齐,Then 名称 / 时长 / 价格在 52px 行高内垂直居中(align-items: center + line-height: 19px);不得出现字下留白明显多于字上的视觉偏上。
  58. Given 产品 Tab,When 查看列表行,Then 名称与价格在同一行、垂直居中、与表头对齐;不得因空时长格把价格挤到下一行。
  59. Given 任意行展开,When 查看该行标题,Then 只显示拖拽图标 + 名称;时长与价格隐藏(已在展开区字段里);且该名称转灰 #929292(v1.31;折叠行仍为 #1A1A1A)—— 避免与展开区字段里的项目名形成同屏两个等重黑体。
  60. Given 分组管理某行已展开,When 量测展开条,Then 整块底色为 #E7E7E7、行下沿有 0.5px rgba(0,0,0,.08) 发丝线(左右内缩 16)、操作条高 82;「包含项目·包含产品 + 重命名」整组水平居中、间距 16(单格 68.33 × 66);删除为 32×32、圆角 8、#BEBEBE 灰底 + 白垃圾桶、无文案,距卡片右沿 16;不得再是「左缩进 44 对齐组名」或红色纯图标。
  61. Given 分组管理页,When 点展开条「包含项目 / 包含产品」,Then 对应页自右侧滑入覆盖分组管理(300ms),底层分组管理左移 22% 并叠 rgba(0,0,0,.1) 蒙层;不得是瞬时硬切。
  62. Given 包含项目 / 包含产品页,When 点返回或「确定」,Then 该页自右侧滑出、蒙层淡出、回到分组管理且展开态已收起;系统「减少动态效果」或 Figma 抓取模式下动效降级为即时切换(功能不变)。
  63. Given 分组管理某行已展开,When 读入口文案,Then 项目 Tab 为 「包含项目」、产品 Tab 为 「包含产品」(图标为 Stratis folder-minus-01 线描,22×22 渲染、stroke 折算 1.7 → 1.56px,见验收 77);不得再出现「编辑成员」字样与双人图标。
  64. Given 点该入口进入,Then 页面标题为 包含项目 · {组名}(产品 Tab 为 包含产品 · {组名};系统「隐藏」组为 隐藏项目 / 隐藏产品);左侧导航同样为「分组 · 包含项目」。
  65. Given 包含项目 / 包含产品页同时存在已选与未选条目,When 对比二者,Then 未选名称 15px/400/#8A8A8A、价格 13px/400/#B9B9B9;选中名称 15px/600/#1A1A1A、价格 13px/500/#1A1A1A;不得再依赖整行底色/描边区分,勾选框仍为 18×18 原样。
  66. Given 分组管理页与价目表列表页标题栏右侧入口,When 量测加号与文字,Then 二者同为 14px 盒、flex 垂直居中,墨迹中轴差 ≤ 0.5px;加号为 14 单位 / stroke 1.5(不得再用 12 单位 / stroke 1.8,那会让描边比 15px 文字重约 40%)。「隐藏」Tab 图标为 16×16 / stroke 1.5 的眼 + 斜线(Tab 内 14×14),线宽与重命名/删除图标成族。
  67. Given 手机(iOS / Android),When 打开原型地址,Then 按 UA 自动进入全屏手机模式(无需参数);Given 电脑浏览器,When 不加参数打开,Then 保持「左侧导航 + 390×844 手机框」桌面壳;Given 真机 UA 加 ?mobile=0(或桌面加 ?mobile=1),Then 按参数强制切换。
  68. Given 手机模式,When 量测画布,Then #frame 布局宽仍为 390(--phone-w),视觉宽 = 视口宽(transform: scale(视口宽 / 390),如 430 视口 → --mobile-scale 1.103、视觉 430.2),不得把布局改成 width:100% 拉伸;绝对定位元素(分组行删除钮、0.5px 发丝线、Tab 指示条)位置与桌面一致。
  69. Given 桌面窄窗(如 390×700)或 DevTools 设备模式(不伪造真机 UA),When 打开原型,Then 不得自动进入手机模式(只认 UA / iPadOS 触屏 Mac);横屏等极矮视口(如 844×390)走兜底等比(--mobile-scale 0.462、--mobile-h 844)居中留边且不得出现 NaN。
  70. Given 手机模式聚焦输入框(如分组名称 Dialog),When 系统键盘弹出,Then --kb-h = (innerHeight − visualViewport.height)/ --mobile-scale(差值 < 80px 视为地址栏抖动、记 0);底部 Sheet 用 padding-bottom: var(--kb-h) 抬起、居中 Dialog 用 translateY(calc(-1 * var(--kb-h))) 上移,均不得被键盘遮住;台面高用 innerHeight 而非 visualViewport.height,不得与 --kb-h 重复扣一次。
  71. Given 手机模式,When 查看顶栏与底栏,Then 假状态栏(9:41)隐藏,顶部按 env(safe-area-inset-top) / --mobile-scale 避让刘海(--safe-bottom 同理按缩放折算);When 从屏幕左缘向右滑,Then 唤出左侧导航抽屉(跟手拖拽),抽屉 z-index 400 > 蒙层 350,点蒙层或点导航项后收起。
  72. Given 线上站点 https://xdean-designer.github.io/menu/,When 打开控制台,Then 无 404 / 无脚本报错(站内已有内联 SVG favicon);index.html / base.css / shell.css / mobile-preview.js / catalog.js / app.js / amount-keypad.js / assets/** 全部 200。
  73. Given 手机模式(假状态栏已隐藏),When 从分组管理点「包含项目 / 包含产品」推入,Then 该页距画布顶为 0、标题栏 top 与列表页 / 分组管理完全一致(430 视口实测三者均为 0);不得多出一条状态栏高度的空带(旧版为 53 × scale = 58.5px)。
  74. Given 桌面壳或 Figma 抓取模式(?capture=1,假状态栏可见),When 推入同一页,Then 顶部偏移仍为 53px(三种模式实测:桌面 55 / 手机 0 / 抓取 53 距画布顶);推入动画中、退出动画中、退出后三个时点的偏移均为 0(手机)/ 53(桌面),不得闪出空带。实现须走单一来源 --statusbar-h,不得在推栈层再写死 53px。
  75. Given 包含项目 / 包含产品页,When 量测选择条(已选 N · 反选 · 全选),Then 底色为白 #FFFFFF(与列表页 Tab 条 .page-tabs 同规格),不得露成页面灰 #F7F7F7;内边距 14px 16px 10px、标题 13px/600、按钮 14px/500 品牌红 #F32F41;选择条下沿有 1px #EBEBEB 分界线(v1.25 起列表改纯白,白对白需要一条发丝线);底栏沿用通用 .bottom-bar(56px、按钮 48px)。
  76. Given 不带参数打开 index.html,When 看首屏,Then 落地 price-list-filled(价目表 · 项目)、左侧导航高亮该项;不得再出现「功能入口」导航组、screen-hub 页、hub-* 样式与 openHub;?flow=hub 节点已取消(访问走既有兜底 toast「未接入: hub」);列表页标题栏不再有返回按钮(已无上级页)。
  77. Given 分组管理某行已展开,When 量测三枚操作图标(22×22 渲染)的描边与墨迹,Then 描边三者一致为 1.56px(包含项目 viewBox 24 / stroke 1.7:1.7 × 22/24 ≈ 1.56);墨迹分别 19.16 × 16.12(包含项目)/ 18.06 × 17.25(重命名)/ 18.06 × 17.14(删除);不得照搬 Stratis 原始 stroke: 2(渲染 1.83px,比同族重 17%)。
  78. Given 包含项目 / 包含产品页(桌面 / 手机 / 产品 Tab 三种情形),When 从画布顶逐 3px 采样整页底色,Then 页面灰 #F7F7F7 采样点为 0 —— 整页只剩白 #FFFFFF 与品牌红 #F32F41(勾选框 / 底栏保存主按钮);#screen-p-group-members 及其 .page-body 底色为 var(--card)。
  79. Given 包含项目 / 包含产品页,When 量测列表,Then 列表壳通栏(相对正文内容区左右偏移 0、宽 = 内容宽;桌面经典滚动条吃掉 15px 属正常,真机为覆盖式滚动条)、无圆角(border-radius: 0)、无外边距(margin: 0);行左右内边距 16、行高 52、padding: 14px 0、行分隔线 1px #EBEBEB(左右内缩 16)、末行无分隔线;首行顶边紧贴选择条下沿。
  80. Given 价目表列表,When 检查「选中态」用色,Then 只有两处用选中橙 #FF7A45 —— ① 主 Tab(项目 / 产品)的选中文字与指示条;② 分组胶囊的选中文字(胶囊底仍为白 #FFFFFF,未选字 #6C7279);Given「设置分组」Sheet 里的分组列表,Then 同一 .catalog-group-tab 组件、同样为 #FF7A45(两处复用,改一处两处同步)。
  81. Given 价目表列表的折叠行,When 采样名称 / 时长 / 价格字色,Then 常态行:名称 #1A1A1A、价格 #1A1A1A、时长 #929292(同一行内不得出现两档黑);Given 该行已绑卡,Then 名称仍为 #333333(语义例外);Given 该行已下架,Then 名称与价格均为 #929292(语义例外)。Given 该行已展开,Then 标题行名称转 #929292、时长与价格隐藏。
  82. Given 本次(v1.31)只改「选中态 + 展开区数值 + 保存按钮 + 保存图标」四处,When 反向断言未波及项,Then 下列元素仍为品牌红 #F32F41、不得被改成橙色:标题栏「+添加」文字链与加号图标、底部虚线「添加项目 / 添加产品」行、空态虚线 CTA、分组管理「新建分组」、必填星号 ::after、列排序激活箭头、switch.on 轨道、勾选框选中底、弹窗「确定 / 删除」实心底、左滑「删除」底。Given 展开区「保存」,Then 其字色仍是品牌红 #F32F41(底为浅底 #FFF2F2)—— 与上列同色,只有底不同。
  83. Given 展开区页脚的「保存」按钮,When 检查其图标,Then 是线描「存入托盘」(arrow → line):3 段路径、viewBox="0 0 24 24"、stroke-width="2.3"、stroke-linecap/linejoin="round";Given 渲染尺寸,Then 图标框 16×16,有效线宽 ≈ 1.53px(与模块图标族 1.56px 同档)、墨迹 ≈ 13.0 × 13.27px(≈ 原实心软盘 13.5px,换稿不得改变视觉重量)。Given 取 getComputedStyle,Then 每条 path 的 fill 必须为 none —— 线描图形若被 fill: currentColor 命中会整体填实,故 base.css 里不得存在 .catalog-inline-save__icon path { fill: … } 这类规则;stroke 为 #F32F41(随字色)。Given 图标与文字同处 flex 行,Then 两者盒中心 y 偏差 ≤0.6px;按钮满宽 356 / 高 48 / 圆角 12 / gap 6 / 无边框无阴影均不得因换图标而变。
  84. Given 分组管理页,When 点某自定义组行内展开的「删除」并在确认弹窗点「确定」,Then 该组立即从分组管理列表与分组轨上消失(state.catalogGroups[bucket] 中已移除),且 toast「已删除分组」与事实一致;Given 被删组内有成员条目,Then 这些条目本身不得被删除(仍在价目表中,只是不再属于该组);Given 删的正是当前筛选组,Then 筛选回落「全部」;Given 系统「隐藏」组,Then 删除被拦截(toast「系统分组不可删除」)、该组仍在且仍固定末尾。项目 Tab 与产品 Tab 均须如此。
  85. Given 分组管理页标题栏「新建」,When 输入名称并确定,Then 新组立即出现在列表与分组轨上、并成为当前筛选组;Given 在「设置分组」Sheet 空态点「去新建分组」新建,Then 回到 Sheet 后新组出现在勾选列表且默认勾选(仍须点 Sheet「确定」才写入成员);Given 重命名某组,Then 组名就地更新、分组轨同步。三者均不得只出 toast 而数据未落。
  86. Given 分组管理页(项目或产品 Tab),When 拖拽某自定义组到新位置后松手,Then 顺序写入分组数据并跨重渲染保持(列表 DOM 顺序与 state.catalogGroups[bucket] 顺序一致);系统「隐藏」组仍固定末尾且不可拖拽。Given 上述删除 / 新建 / 拖拽三处实现,When 检查代码,Then 不得在 getActiveCatalogGroups() 的返回值上做结构写操作(该函数返回只读视图,每次调用都是新数组;写入一律落 state.catalogGroups[bucket])—— 这正是 v1.32 修的缺陷模式。

模块 11 非功能(极简)

读者:前端 ✓ · 后端 ✓ · 测试 ✓


模块 12 交付物

交付 说明
本文 PRD-项目创建与管理.md 开发与测试准稿
交互原型 index.html 唯一可视依据(价目表独立原型;深链见 §3);大原型镜像 ../card/demo.html

开发与测试只需 PRD + 原型。与会员卡交叉时,会员卡流程以 PRD-会员卡管理.md 为准,价目数据与锁定规则以本文为准。冲突时以原型界面为准修正 PRD。

给 AI / 研发请使用同目录 PRD-项目创建与管理.md;本页仅作阅读预览。交互以本目录 index.html 为准。