DTL L-20260824-03:MFR 开放报价 V2 前端全面优化设计(MSC-FE 原则 #4 核心落地)¶
日志编号:L-20260824-03(DTL · 听写任务日志 + WDE 编码衔接说明) 任务类型:前端交互设计(DT)· 代码实现建议(供听码 WDE 衔接执行) 起止时间:2026-08-24 13:30 — 16:00(设计 · 编码同步进行) 执行人:听写 DT(设计规格)· 设计落地后移交 @听码 WDE 编码实现 & 部署验证 用户重点指令(原文 verbatim):「3.例如:面向生产商的开放报价功能,要参照(1.3 前端功能设计六条核心原则,尤其是其中第4条)进行全面优化设计」
一、输入产物¶
| 序号 | 输入项 | 来源 | 说明 |
|---|---|---|---|
| 1 | ref/DOC-P33前端设计方法和部分关键需求.md §1.3 六条原则(尤其 #4 主动引导优于被动填空) |
DTL L-20260824-01 产物 | MSC-L1~L6 验收红线,特别 MSC-L4 空白占比≤30% |
| 2 | docs/需求分析/业务级需求.md §10.3 MFR 开放报价 V2 6 组件规格表(用户重点) |
DTL L-20260824-02 产物 | 每组件的交互规格、参数字段、校验规则、需求编号完整定义 |
| 3 | dip1/frontend/apps/mfr-portal/app/quote/page.tsx V1 纯填空实现 + lib/api.ts + globals.css + package.json |
代码现状(见 Summary §5 · §9 文件读取) | V1 8 字段:产品类型下拉/工时 number/型号 text/地址 text/楼宇类型下拉/区域下拉/品牌档位下拉;固定绕过参数 weight_kg=50、volume_m3=1、mode=M1;零依赖(仅 next/react) |
| 4 | shared 类型:packages/shared/src/types/qsv.ts(SclCode 模板字面量 8 品类) |
共享包 | 品类枚举/参数结构 |
二、输出产物清单¶
| # | 产物名 | 路径 | 版本 | 状态 | 说明 |
|---|---|---|---|---|---|
| ✅ D1 | MFR 开放报价 V2 6 组件设计规格文档(本节) | 本 DTL §三/§四 | V2.0 | ✅ 已完成设计 | 对齐 §10.3 规格表 + MSC-L4 30% 阈值验证 + 无新增依赖方案 |
| ⏳ D2 | page.tsx V2 代码重写(6 组件内联实现 · 保留 V1 高级模式切换) |
dip1/frontend/apps/mfr-portal/app/quote/page.tsx |
V2 | 🟡 编码执行中 | @听码 WDE 按本 DTL 规格实现;DT 本会话同步完成 TSX 初稿(见 §五 代码结构) |
| ⏳ D3 | globals.css V2 追加样式(品类卡片/滑动条/柱状图 SVG/绿勾清单/徽章样式) |
同路径追加类 | V2 | 🟡 待编码 | 增量追加,不覆写 V1 样式 |
三、技术约束与设计决策(独立列出)¶
3.1 硬性技术约束(不可突破)¶
| # | 约束 | 原因 | 设计应对 |
|---|---|---|---|
| C1 | 禁止新增 npm 依赖 | package.json 仅 next/react/@dip1/shared;pnpm install 可能破坏 monorepo 锁文件一致性(用户偏好:最小学习成本 + 避免重复沟通) | 柱状图 → 纯 SVG 手写高斯分布;二维码 → 纯 CSS 点阵方块 QR 示意(无真实纠错码,MVP 期点击复制短链接到剪贴板);品类图标 → emoji 系统图标(家具🛋️/灯具💡/卫浴🛁/厨房🍳/家电📺/智能🤖/门类🚪/软装🧵) |
| C2 | 必须保留 V1 纯填空「高级模式」切换 | 老用户习惯 + MSC-L4 不强制所有用户走引导路径,提供选择权 | 页面右上角加「⚙ 模式切换」按钮:「🔵 引导模式 V2(推荐)」/「⚪ 高级模式 V1(纯填空)」· 默认 V2 |
| C3 | 提交 API 契约与 V1 完全兼容 | 后端 mfr-open 路由 32 因子 DTO 不变,避免 ppl_id hotfix 后再次变更后端接口风险 |
V2 前端在提交前将 6 组件的结构化参数翻译回与 V1 完全一致的 P0~P8 DTO(见 §四 翻译映射表)· ppl_id Header 逻辑与 V1 完全复用 |
| C4 | 后端未提供接口一律 MVP-Lite Mock | BPL 地址解析接口未开发 / PPL 档案接口 ppl_id 仅 JWT claim,无完整品牌名徽章字段 / CPT L1 转化接口未开发 / QR 短链接服务未开发 | BPL:内置香港 30 常用楼宇样本 JSON;工时柱状图:内置 8 品类各 100 单高斯分布模拟数据;PPL 徽章:ppl_id 非空→按前缀哈希(033%铜/3466%银/67~100%金)MVP 模拟;L1 转化:写入 localStorage["mfr_l1_leads"](键 quoteId);分享二维码:点击后生成 location.origin + "/quote?share=q_{quoteId}" + prompt 复制 |
3.2 设计决策(每项附原因)¶
| # | 决策点 | 方案 A | 方案 B | 最终选择 | 原因 |
|---|---|---|---|---|---|
| D1 | 6 组件代码结构 | 拆 6 个独立组件文件(components/ 目录下 CategoryCard.tsx 等) |
全部内联为 page.tsx 内部函数组件(6 个 function XxxComp) |
✅ B | mfr-portal 目前无 components/ 目录(仅 opr-admin 有)· monorepo 目录结构最小变更 · MVP 快速交付 · 后续 V3 升级再拆 |
| D2 | 工时柱状图实现 | wait 装 recharts + 调整 pnpm | 纯 SVG 实现 50 柱高斯分布(柱高 = Math.round(100 * exp(-(x-μ)²/(2σ²))) + 绿柱 μ±1σ / 黄柱 μ±2σ / 红柱 >2σ 三色)· μ/σ 取 DOC-P33 §4.2.2 推荐值(家具 μ=4.2h σ=0.8h 等) |
✅ B | 约束 C1(不新增依赖)· SVG 可直接嵌入 TSX return 无需额外 DOM 计算 |
| D3 | 8 品类卡片交互 | 点击后 router 跳转 + 状态存 URL | 点击卡片后 setCategory useState + 蓝色边框 2px + 背景淡蓝色 + 自动滚动下一组参数组件(useRef<HTMLDivElement> + scrollIntoView({behavior:"smooth"})) |
✅ B | 单页应用 + 引导流畅度(避免刷新)· 与 V1 useState 架构一致 |
| D4 | 地址解析防抖 | 200ms / 500ms / 1000ms | 500ms debounce(useEffect + setTimeout + cleanup) |
✅ 500ms | 香港屋苑名普遍 3-4 字,输入速率适中,500ms 兼顾响应速度与 BPL Mock 查询频率(不过热) |
| D5 | 5 项绿勾校验与提交按钮联动 | 缺项时提交按钮 display:none | 缺项时按钮灰色 disabled + 悬浮 tooltip「请完成 {N} 项缺项:xxx」 · 点击缺项行 → scrollIntoView + 黄色闪烁 3 次(CSS animation 3× 0.5s) |
✅ 灰色 disabled | MSC-L5 异常前置:用户能看到提交按钮位置,减少挫败感;黄色闪烁 3 次视觉反馈强 |
| D6 | 结果页 QSV-09 灵活部分展示 | 真实调用后端灵活部分接口 | 前端内置 QSV-09 规则展示:等待费 HKD 50/15min(封顶 200)· 拆旧(待定)· 高空作业(待定)· 配说明文字「灵活部分按实际发生结算,封顶已纳入总报价保护」 | ✅ 前端内置 | 原 V1 完全未展示 QSV-09,V2 先展示规则 → 后续后端补 flex_items 字段再对接 |
四、6 组件交互设计规格(对应 RQD-B §10.3 6 行)¶
4.0 整体 V2 页面结构(从上到下)¶
[Topbar(织布鸟 MFR)]
[模式切换按钮 · V2引导/V1高级]
[页面标题 · 开放报价 V2 · ppl_id绑定徽章]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[组件1:8 品类彩色卡片选择器]
[组件2:品类参数结构化引导(选中品类后展开 · 3滑动条+工时柱状图)]
[组件3:地址智能解析器]
[组件4:品牌档位自动加载 + 信任徽章]
[组件5:提交前 5 项绿勾校验清单 + 提交按钮]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[组件6:结果页 V2(报价成功后渲染 · 6区块)]
4.1 组件 1:8 品类彩色卡片选择器(对应 RQD-B §10.3 第 1 行)¶
视觉:横排 8 卡片,grid-cols-4(移动端 2 列),每卡 120px×100px。
卡片结构(每张):
- 顶部大 emoji(28px):家具🛋️ / 灯具💡 / 卫浴🛁 / 厨房🍳 / 家电📺 / 智能🤖 / 门类🚪 / 软装🧵
- 中间中文品类名(13px 粗体)
- 底部 SCL 编码(10px 灰色)
状态:未选中(白底+浅灰边)· 选中(#004046 蓝边 2px + #f2f0eb 浅蓝背景 + 右上角✅角标)
校验:isCategorySelected → 5 项绿勾①
MSC-L4 合规检查:✅ 非空白输入(100% 选择)· ✅ 图文并茂(emoji 图 + 文字)· ✅ 默认值:家具选中(第 1 张默认选中,减少空白点击)
4.2 组件 2:品类参数结构化引导(对应 §10.3 第 2 行 · MSC-L4 核心)¶
展开条件:组件 1 选中任意卡片后,通过 useState<selectedCategory !== null> 控制 CSS max-height 动画展开(300ms)。
子区块顺序:
子区2.1 型号输入框:
label: 型号(SKU/产品编号)
placeholder: 如 ACME-WARD-240(可从产品标签扫描)
悬浮气泡(title 属性): 填写后可在 MFR 产品目录中一键复用
子区2.2 尺寸滑动条(3 条横排):
每条:[label H×W×D + 当前数值 cm] | [slider range min=10 max=300 step=1] | [徽章 G1/G2/G3/G4]
G 徽章计算(对齐 LQM §3 件型):max(H,W,D) ≤60cm → G1(绿色 · 小); ≤120 → G2(蓝 · 中); ≤200 → G3(橙 · 大); >200 → G4(红 · 超大)
子区2.3 重量 + 易碎品:
重量滑块 range 0-200kg step=1 · 默认 50kg · 右侧「G 件型徽章自动更新」
易碎品 checkbox(玻璃/岩板/大理石产品勾选)· 勾选后右侧红色横幅「⚠ 易碎品强制 SV3 送装一体保障档」
子区2.4 工时推荐 + 柱状图(★ MSC-L4 核心主动引导):
左列卡片(120px):
顶部绿色徽章「推荐工时 {μ}h(同类工单均值 ±σh)」
下方两个微调按钮 < >,步进 0.5h,限制 [0.5h, 12h]
超出 [μ-2σ, μ+2σ] → 工时红色 + 右侧链接气泡「点击查看同类工单参考工时分布」
右列 SVG 柱状图(280px × 120px):
X 轴 0.5h-12h,50 柱 · Y 轴频次 0-100(归一化)
柱颜色:[μ-1σ, μ+1σ] 绿色 / [μ-2σ, μ-1σ] ∪ [μ+1σ, μ+2σ] 黄色 / 其余红色
红色虚线标记用户当前 chosen 工时位置
★ 8 品类 μ/σ 默认值(DOC-P33 参考值):
家具 FURN: μ=4.2 σ=0.8 | 灯具 LAMP: μ=1.5 σ=0.4 | 卫浴 BATH: μ=2.5 σ=0.6
厨房 KITC: μ=3.5 σ=0.7 | 家电 APPL: μ=2.0 σ=0.5 | 智能 SMART: μ=1.8 σ=0.4
门类 DOOR: μ=3.0 σ=0.6 | 软装 SOFT: μ=1.0 σ=0.3
4.3 组件 3:地址智能解析器(对应 §10.3 第 3 行 · 闭环 1 信息流)¶
解析 MVP-Lite Mock 数据(内置香港 30 常用楼宇样本 JSON):
// 内嵌常量,存于 component closure 内
const BPL_SAMPLES = [
{ bpl_id: "BPL-HKI-0001", name: "尖沙咀中心", district: "KOWLOON", building_type: "PRIVATE_RESIDENCE", has_elevator: true, floor_num: 16, has_parking: true, stairs_width_cm: 120, keywords: ["尖沙咀中心", "弥敦道132号", "132 Nathan Rd"] },
{ bpl_id: "BPL-HKI-0002", name: "中环国际金融中心", district: "HK_ISLAND", building_type: "OFFICE_BUILDING", has_elevator: true, floor_num: 88, has_parking: true, stairs_width_cm: 180, keywords: ["IFC", "国际金融中心", "中环金融街8号"] },
// ... 另 + 28 条(公屋/村屋/唐楼各覆盖至少 3 条)代表样本
];
bplSearch(keyword)
2. 搜索算法:遍历 BPL_SAMPLES.keywords 数组 → 包含关键词 → 加入候选;最多展示 5 条
3. 候选下拉项:每项含 [楼宇类型图标 公屋🏢/唐楼🏠/村屋🛖/办公楼🏛️] + [楼宇名] + [区域标签 港岛/九龙/新界] + [电梯图标 ✅/❌]
4. 选中候选后 → 4 字段自动填入灰色不可手动改(完整地址 textarea 可编辑,修改时弹窗二次确认「⚠ 修改已匹配的楼宇地址将重置楼宇档案信息,是否确认?」)
5. 匹配<60%候选(0 条)→ 自动弹出 4 问题自检引导卡片:
Q1 有无电梯? ○有 ✅ ○无 ❌
Q2 楼层数? 数字输入框(1-80)
Q3 楼梯转角是否可过 1.5m 家具? ○是 ○否(楼梯窄)
Q4 附近是否有卸货停车位? ○有 ✅ ○无 ❌(路边停车)
4.4 组件 4:品牌档位自动加载 + 信任徽章(对应 §10.3 第 4 行 · 闭环 3 口碑反馈)¶
两条分支路径:
路径 A(已登录 ppl_id 非空,JWT claim.ppl_id 存在):
顶部 1 张浅蓝背景卡片:
左:MFR 信息「🏭 PPL-{ppl_id}」(ppl_id 后 4 位脱敏展示,合规)
中:信任徽章(铜🟫/银⬜/金🟨 大图标 36px)· 徽章下方文字「基于历史 NPS/差评率自动评定」· 点击查看达成条件弹窗(历史订单 N 笔 / NPS ≥ x.x / 差评率 ≤ y%)
右:品牌档位(灰色不可选)· 大字体「PREMIUM 1.2×」· 下方小字「如需调整品牌档位 → 联系平台 PPL 评级」
路径 B(未绑定 ppl_id,开放报价场景):
顶部 3 张彩色卡片横排(grid cols-3):
卡 1(左 灰色 普通):[无溢价徽章 灰] · 大字「STANDARD 1.0×」 · 文字「普通品牌 无溢价」· 点击选中(边框变灰粗)
卡 2(中 蓝色 默认选中):[推荐徽章 蓝 ✨] · 大字「PREMIUM 1.2×」 · 文字「中高端品牌 +20% 溢价 · 示例:××××(悬浮气泡 3 张示例品牌图)」· 默认选中(蓝色背景)
卡 3(右 金色):[顶级徽章 金 👑] · 大字「SUPER_PREMIUM 1.4×」 · 文字「顶级品牌 +40% 溢价 · 示例:××××」· 点击选中(金色背景)
(hash(ppl_id) % 100) → 0-33=铜/34-66=银/67-100=金 · 后续对接真实 PPL badge_level 接口后替换(注释标注 TODO 切换)
4.5 组件 5:提交前 5 项绿勾校验(对应 §10.3 第 5 行 · MSC-L5 异常前置)¶
5 行清单(每行 40px 高,左图标 + 右文字):
行① 🟩✅ 产品参数齐全(品类 + 尺寸 + 重量 + 工时)· 缺项 → 🟥✗ · 点击 → 滚动到组件2 + 黄色闪烁
行② 🟩✅ 地址解析成功(BPL 匹配)· 缺项 → 🟥✗ · 点击 → 滚动到组件3
行③ 🟩✅ 楼宇条件完整(电梯 + 楼层数 + 停车条件)· 缺项 → 🟥✗ · 同上
行④ 🟩✅ 品牌档位已选择 · 缺项 → 🟥✗ · 点击 → 滚动到组件4
行⑤ ☐ 我已查看报价预览明细(checkbox 必须勾选 · 勾选中后变为 🟩✅)
canSubmit = (①②③④⑤全绿) → canSubmit 为 true → 按钮蓝色(.btn 类)可点击;false → 按钮灰色(.btn:disabled)· 悬浮 tooltip「请完成以下 {N} 项:{缺项文案, 逗号分隔}」
黄色闪烁实现(点击缺项行时):目标组件外层 div 的 CSS class animate-pulse-yellow(3× 0.5s background-color: #fff59d → transparent,animation-iteration-count: 3)
4.6 组件 6:结果页 V2 升级(对应 §10.3 第 6 行 · 信息流闭环 L1 转化 + 分享)¶
区块顺序(6 区块):
区块① 最终金额卡(大号展示):
H1「HKD {final_amount.toFixed(2)}」· 字号 42px 颜色 #004046
右上角 2 徽章:[📅 报价有效期 14 天 · 徽章蓝] · [💱 货币 HKD · 灰色]
下方小字:报价单号 {quote_id} · 计算时间 {calculation_at 格式化}
区块② 9 步报价明细树(★ 原 V1 仅金额,V2 新增公式说明 + 溯源):
每步一行(可点击展开/收起,默认全部展开):
左:步骤标签(P1 基础人工费 / P2 产品难度 / P3 楼宇系数 / P4 区域系数 / P5 物流运输 / P6 时间附加 / P7 折扣 / P8 师傅/品牌溢价 / CAP 封顶)
中:公式说明列(前端 mock step_formula_desc:P1 = 4.2h × HKD 450/h = 1890 · P3 = ×1.05 楼宇系数 · P5 = LQM 本地模式 M1 · P8 = PREMIUM 1.2× 品牌溢价 · ... )
右:金额列(对齐 V1 subtotal_steps)
最终金额行(行背景 #d4af78 金色)
区块③ QSV-09 灵活部分独立区块(★ V1 完全缺失,V2 补齐规则):
标题「💡 灵活部分(按实际发生结算 · 封顶保护已纳入)」
3 行规则:
行1:等待费 HKD 50 / 15min · 封顶 HKD 200(师傅到达后等待超时计算)
行2:拆旧附加费(衣柜/空调等,按实际拆旧类型报价)· 封顶 HKD 800
行3:高空作业附加(≥10楼/外墙作业)· 封顶 HKD 1500
底部小字提示:「以上灵活部分仅在实际发生时收取,报价已设置单项封顶与总封顶双重保护」
区块④ 主操作按钮组(3 大按钮横排 · grid cols-3):
按钮1(左 📄):「下载报价单 PDF」· 复用 V1 onDownloadPdf 逻辑(✅ 直接迁移)
按钮2(中 🔗 推荐蓝 btn-accent):「转为 CPT L1 线索」· onClick:
· payload = { quote_id, source_type: "MFR_OPEN_QUOTE", mfr_ppl_id: ppl_id, created_at: new Date().toISOString() }
· 写入 localStorage["mfr_l1_leads"](键 quoteId,值 payload JSON)· 后续 OL 工作台可批量同步读取
· 成功 toast「✅ 已转为 L1 线索!(仅本地存储,后续 OL 端同步 CPT)」
· (真实接口上线 TODO:POST /api/v2/cpt/l1/from-quote)
按钮3(右 📤):「分享给客户」· onClick:
· 生成 shareUrl = `${location.origin}/quote?share=q_${quoteId}`
· 展示模态框:左「二维码(纯 CSS 点阵示意 5×5 方块交替色 100×100px)」· 右「分享链接文本框 + 复制按钮」
· 复制按钮 onClick → navigator.clipboard.writeText(shareUrl) → toast「✅ 链接已复制!客户点击后可查看完整报价单」
· (真实 qrcode + 短链接服务 TODO:后续对接 qrcode API)
区块⑤ 服务档位 SV 结果展示:
3 档横排卡片:SV1(灰色 未选)· SV2(灰色 未选)· SV3(若勾选易碎品 → 红色强制选中)
V1 档位固定 STANDARD → V2 卡片显示「当前档位:STANDARD(升级档,请联系平台开通 SV2/SV3)」
区块⑥ 微调返回:
底部链接按钮「💡 如报价金额不符,点击微调参数」· onClick → window.scrollTo({top: 0, behavior:"smooth"}) · 所有已填 useState 保留(✅ 无需重填)
4.7 V1 高级模式(兼容保留)¶
- 右上角模式切换按钮 →
useState<advancedMode>切换 - 为 true 时:渲染原 V1 纯填空表单(代码原封不动保留在同一文件条件分支)
- 提交逻辑、结果页逻辑完全复用(✅ 保证老用户零学习成本)
4.8 API DTO 翻译映射表(V2 6 组件 → 32 因子,约束 C3)¶
后端 mfr-open 路由所需 P0~P8 字段与 V1 完全一致,V2 前端提交前 buildPayloadV2() 做如下翻译:
function buildPayloadV2(s: V2FormState): QuoteParams32(同 V1 payload 结构) {
return {
P0: { project_id: genProjectId(), include_c7_insurance: s.fragile_yn, service_tier: s.fragile_yn ? "SV3" : "STANDARD" },
P1: { scl_code: CATEGORY_SCL_MAP[s.category], estimated_hours: s.labor_hours },
P2: { product_code: s.model_no || `${s.category}-GENERIC`, quantity: 1 },
P3: { building_type_coef: BUILDING_COEF_MAP[s.building_type] },
P4: { area_code: s.district_code },
P5: {
pickup_addr: { lat: 22.2793, lng: 114.1628, addr_text: "仓库" },
delivery_addr: { lat: s.bpl_match?.lat ?? 22.3193, lng: s.bpl_match?.lng ?? 114.1694, addr_text: s.install_addr },
weight_kg: s.weight_kg, volume_m3: calcVolumeM3(s.h_cm, s.w_cm, s.d_cm), preferred_mode: "M1"
},
P8: { ppl_brand_tier: s.brand_tier }
};
}
五、代码结构建议(供 @听码 WDE 衔接 · 含组件拆分)¶
5.1 page.tsx V2 文件结构(单文件 6 组件内联,决策 D1)¶
文件总行数预计:~850 行(原 V1 384 行 → + 6 组件 + 2 Mock 数据)
├── import 区(新增 useRef 用于 scrollIntoView)
├── 常量区(CATEGORIES_8 / BPL_SAMPLES 30 / LABOR_μσ_8 品类 / STEP_FORMULA_DESC 9 步公式)
├── 主函数 QuotePage():
│ ├── useState<V2FormState>(category/hcm/wcm/dcm/weightkg/laborhrs/... · advancedModeFlag)
│ ├── useEffect:登录检查 + ppl_id claim(复用 V1)
│ ├── return:
│ ├── 模式切换按钮
│ ├── if (advancedModeFlag) → 渲染原 V1 表单(原代码完整保留)
│ ├── else → 渲染 V2:组件1~6(条件渲染)
│ ├── onSubmitV2():buildPayloadV2 → 复用 V1 fetch 逻辑(ppl_id Header 完全一致)
│ └── onDownloadPdf():完全复用 V1
├── 6 组件子函数:
│ ├── CategoryCardComp() · SlidersWithLaborChartComp() · AddressBplResolverComp()
│ ├── BrandTierBadgeComp() · FiveGreenChecklistComp() · ResultPageV2Comp()
├── Helper 函数:
│ ├── bplSearch(keyword) · hashPplBadgeLevel(ppl_id) · calcGGauge(h,w,d)
│ ├── genProjectId() · buildPayloadV2() · generateGaussData(μ,σ,n=50)
└── 末尾:style 标签追加(黄色闪烁动画 / 卡片选中边框 / 柱状图 SVG 样式)
5.2 globals.css 追加类(增量 ~60 行,不覆写 V1)¶
/* ===== MFR V2 新增类(增量追加)===== */
.card-selected { border: 2px solid var(--brand-primary); background-color: var(--bg); }
.badge-green { background: #27ae60; color: #fff; padding: 2px 8px; border-radius: 12px; font-size: 11px; }
.badge-gold { background: var(--brand-accent); color: var(--brand-primary); padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 700; }
.badge-red { background: var(--danger); color: #fff; padding: 2px 8px; border-radius: 12px; font-size: 11px; }
.row4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.cat-card { text-align: center; padding: 14px 8px; border-radius: 8px; border: 1px solid #e4e0d8; cursor: pointer; transition: all 0.2s; }
.cat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,64,70,0.1); }
.cat-card .emoji { font-size: 28px; display: block; margin-bottom: 6px; }
@keyframes flashYellow3x { 0%,100% { background: transparent; } 50% { background: #fff59d; } }
.flash-yellow { animation: flashYellow3x 0.5s ease 3; }
.checklist-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 6px; cursor: pointer; }
.checklist-row:hover { background: var(--bg); }
.check-icon-ok { color: #27ae60; font-weight: 700; }
.check-icon-fail { color: var(--danger); font-weight: 700; }
.step-tree-row { display: grid; grid-template-columns: 1fr 2fr 120px; gap: 10px; padding: 8px 12px; border-bottom: 1px solid #f2f0eb; align-items: center; }
.qr-code-demo { display: grid; grid-template-columns: repeat(5, 16px); gap: 2px; padding: 12px; background: #fff; border: 1px solid #000; }
.qr-code-demo div { width: 16px; height: 16px; }
.qr-black { background: #000; }
.qr-white { background: #fff; }
5.3 MSC-L4 合规性验证(空白输入占比 ≤30% · 红线)¶
统计 V2 表单所有 <input> 字段(按 RQD-B §10.1 定义:空白 = 无引导的纯 text/number):
| 字段 | 类型 | 是否空白(30%判定) | 主动引导机制 |
|-----|------|-------------------|-----------|
| 型号 | text | ⚠ 算空白 · 但仅 1 项 | placeholder + 悬浮气泡说明 |
| h_cm | slider | ✅ 非空白 | 滑动条 + G 件型徽章实时反馈 |
| w_cm | slider | ✅ 非空白 | 同上 |
| d_cm | slider | ✅ 非空白 | 同上 |
| weight_kg | slider | ✅ 非空白 | 滑块 + 默认 50kg |
| fragile | checkbox | ✅ 非空白 | 勾选后强制 SV3 横幅 |
| 安装地址 | text | ✅ 非空白 | BPL 解析 autocomplete + 4 问题自检兜底 |
| 楼层数(自检时才出现)| number | ✅ 非空白 | 4 问题引导出现场景 |
空白占比 = 1(型号)/ 8 总字段 = 12.5% ≤ 30% ✅ MSC-L4 合规通过(远超红线要求)
六、阻塞点 / 后续移交 / 待办¶
6.1 当前阻塞点(需 @听云 OCM / @听码 WDE 协作)¶
| # | 阻塞点 | 解决依赖方 | 建议时间 |
|---|---|---|---|
| B1 | 后端 /mds/bpl/search?keyword= 接口未开发 → V2 用 BPL_SAMPLES 30 条 Mock |
@听码 WDE(后端 FastAPI + BPL 档案 SQLite 建表) | 9 月底前(WP-01 智能下单组件补) |
| B2 | 后端 /cpt/l1/from-quote(报价→L1 转化)接口未开发 → V2 用 localStorage Mock |
@听码 WDE(CPT 路由 + L1 来源 source_type 字段) | 10 月中(OPR MVP-Lite 上线前) |
| B3 | 二维码真实生成 + 短链接服务未开发 → V2 用 5×5 点阵示意 + 复制链接 | @听码 WDE(可选引入 qrcode npm 包 + 短链接 Redis 存储)· 或 @听云 OCM(Cloudflare Worker 短链接 KV) | 10 月底(CST 端分享功能上线前) |
| B4 | 真实 PPL 徽章 + NPS 数据接口(/mds/supply/ppl/{id}/badge)未开发 → V2 用哈希 Mock |
@听码 WDE(PPL 档案表 badge_level 字段) | 9 月底(MFR 看板 NPS 模块上线前) |
6.2 待办清单(本日志产出后立即启动)¶
- ✅ 设计规格完成(本节 DTL §四/§五)→ 立即启动编码实现(Todo 6:page.tsx + globals.css)
- 🟡 本地 Next dev(pnpm --filter @dip1/mfr-portal dev)→ 人工检查 6 组件交互
- 🟡 V1/V2 模式切换回归(提交报价 ppl_id 两条路径都走得通)
- 🟡 MSC-L4 合规再次核对(空白占比 ≤30% · 实际人工操作截图留档)
- 🟡 写入 WDL(听码开发日志)· 部署写入 OCL(听云运维日志)· 对应 git commit 引用本 DTL 编号
七、关联文档 / 日志 / commit¶
- 上游方法论文档:
ref/DOC-P33xxx.mdV3.0 · DTL L-20260824-01 - 上游需求规格:
docs/需求分析/业务级需求.md§10.3 · DTL L-20260824-02 - 代码路径:
dip1/frontend/apps/mfr-portal/app/quote/page.tsx - 部署路径:听云 OCL L-20260823-02/04(112.74.108.189:3000 systemd mfr-portal)
- 代码实现完成后追加:WDL L-20260824-NN(MFR V2 编码)· OCL L-20260824-NN(部署)
日志编号:DTL L-20260824-03 · 维护人:听写 DT · 设计规格已完成 · 编码移交 @听码 WDE(本会话 DT 同步输出 TSX 初稿见 Todo 6 执行)· AGENTS V8.3 合规