跳转至

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
参数字段校验(MSC-L5): - 型号 ≤50 字符(超长截断 + 提示) - 尺寸 10-300cm(超出自动 clamp 到边界 + 黄色警告) - 重量 0-200kg(超出自动 clamp + 红色警告 ⚠ 重量 > 200kg 请联系 OL) - 工时 0.5-12h(>12h 红色警告 + 点击参考链接)

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 条)代表样本
];
交互流程: 1. 输入框占位:「输入香港地址,如:尖沙咀弥敦道 132 号」· 输入≥3字符后 → 500ms debounce 触发 bplSearch(keyword) 2. 搜索算法:遍历 BPL_SAMPLES.keywords 数组 → 包含关键词 → 加入候选;最多展示 5 条 3. 候选下拉项:每项含 [楼宇类型图标 公屋🏢/唐楼🏠/村屋🛖/办公楼🏛️] + [楼宇名] + [区域标签 港岛/九龙/新界] + [电梯图标 ✅/❌] 4. 选中候选后 → 4 字段自动填入灰色不可手动改(完整地址 textarea 可编辑,修改时弹窗二次确认「⚠ 修改已匹配的楼宇地址将重置楼宇档案信息,是否确认?」) 5. 匹配<60%候选(0 条)→ 自动弹出 4 问题自检引导卡片
Q1 有无电梯? ○有 ✅ ○无 ❌
Q2 楼层数? 数字输入框(1-80)
Q3 楼梯转角是否可过 1.5m 家具? ○是 ○否(楼梯窄)
Q4 附近是否有卸货停车位? ○有 ✅ ○无 ❌(路边停车)
6. 完成 4 自检或选中 BPL 匹配 → 地址解析徽章变为「绿色 ✅ 已通过」· 5 项绿勾②③满足

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% 溢价 · 示例:××××」· 点击选中(金色背景)
徽章算法(MVP Mock,ppl_id 哈希)(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 待办清单(本日志产出后立即启动)

  1. ✅ 设计规格完成(本节 DTL §四/§五)→ 立即启动编码实现(Todo 6:page.tsx + globals.css)
  2. 🟡 本地 Next dev(pnpm --filter @dip1/mfr-portal dev)→ 人工检查 6 组件交互
  3. 🟡 V1/V2 模式切换回归(提交报价 ppl_id 两条路径都走得通)
  4. 🟡 MSC-L4 合规再次核对(空白占比 ≤30% · 实际人工操作截图留档)
  5. 🟡 写入 WDL(听码开发日志)· 部署写入 OCL(听云运维日志)· 对应 git commit 引用本 DTL 编号

七、关联文档 / 日志 / commit

  • 上游方法论文档:ref/DOC-P33xxx.md V3.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 合规