#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""《客户拉通大表 · 销售员使用说明》生成器（自包含单页 · 零外链 · 可投屏/打印）。

面向一线销售员（约 30 人），七块固定顺序：
  1 为什么要用（3 句话）· 2 你要做的事（就三件）· 3 一步步怎么做 · 4 背后的逻辑 ·
  5 谁能看到什么（权限）· 6 填写质量要求（会被拦的情形）· 7 你从中能得到什么。

铁律：
  · 匿名化：示例一律泛化（某三甲医院 / A 客户 / 王工），禁真实客户名 / 订单号 / 姓名 / 货号金额。
  · 不得写成已上线：页首/页脚标「DEMO 原型 · 后续上线后再发正式版使用手册」。
  · 功能名逐字一致：周会决议 / 三轨落盘 / 资源协作 / 插入模板 / 待关联。
  · 零外链：src="http / href="http / <script src / <link 计数均 = 0；Logo base64 内嵌。
  · 内联 SVG 禁 <title> / <br>；方框等宽等间距。
  · 沙盒纯内存：不写 localStorage/sessionStorage/cookie，不发 fetch/XHR/sendBeacon，不用 confirm/alert。

产物：知识沉淀/客户拉通大表_销售员使用说明_20261011.html
验收：脚本/verify_customer_thread_training.py
"""
import os
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, '脚本'))
OUT = os.path.join(ROOT, '知识沉淀', '客户拉通大表_销售员使用说明_20261011.html')

import brand_kit as BK  # noqa: E402

DEMO_NOTICE = 'DEMO 原型 · 后续上线后再发正式版使用手册'

CSS = """
:root{
  --blue:#1852B6; --deep:#0d3a85; --ink:#1f2937; --sub:#5b6472; --line:#e3e8f0;
  --bg:#f5f7fb; --card:#ffffff; --green:#16A34A; --red:#C0392B; --amber:#F59E0B;
  --gap:14px; --pad:18px; --rad:12px;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{background:var(--bg);color:var(--ink);
  font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
  font-size:14px;line-height:1.72}
.wrap{max-width:1180px;margin:0 auto;padding:0 18px 40px}
.demo-bar{margin:14px 0 0;background:#fff7e6;border:1px solid #f2d49b;color:#8a5a00;
  border-radius:10px;padding:9px 14px;font-size:12.5px}
.demo-bar .dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--amber);
  margin-right:7px;vertical-align:middle}
.hero{margin:16px 0 4px}
.hero h1{margin:0;font-size:25px;letter-spacing:.5px;color:var(--deep)}
.hero .sub{display:block;margin-top:6px;color:var(--sub);font-size:13.5px}
.hero .who{margin-top:10px;font-size:13px;color:var(--sub)}
.toc{margin:18px 0 8px;background:var(--card);border:1px solid var(--line);border-radius:var(--rad);
  padding:14px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:8px 18px}
.toc a{color:var(--blue);text-decoration:none;font-size:13px;border-bottom:1px dashed #cdd8ea;padding:2px 0}
.toc a:hover{color:var(--deep)}
.sec{background:var(--card);border:1px solid var(--line);border-radius:var(--rad);
  padding:var(--pad) 20px 20px;margin:16px 0;break-inside:avoid}
.sec > h2{margin:0 0 12px;font-size:18px;color:var(--deep);display:flex;align-items:baseline;gap:10px}
.sec > h2 .no{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;
  border-radius:8px;background:var(--blue);color:#fff;font-size:14px;flex:none;
  position:relative;top:1px}
.sec h3{font-size:14.5px;margin:16px 0 8px;color:var(--deep)}
.sec p{margin:8px 0}
.lead{font-size:14.5px}
.lead li{margin:6px 0}
ul.plain{list-style:none;padding-left:0;margin:6px 0}
ul.plain li{padding-left:22px;position:relative;margin:7px 0}
ul.plain li::before{content:"▸";position:absolute;left:4px;color:var(--blue)}
ol.steps{counter-reset:s;list-style:none;padding-left:0;margin:10px 0}
ol.steps > li{counter-increment:s;position:relative;padding:10px 12px 10px 46px;margin:9px 0;
  border:1px solid var(--line);border-radius:10px;background:#fbfcfe}
ol.steps > li::before{content:counter(s);position:absolute;left:12px;top:11px;width:24px;height:24px;
  border-radius:50%;background:var(--blue);color:#fff;font-size:13px;display:flex;align-items:center;
  justify-content:center;font-weight:700}
ol.steps b{color:var(--deep)}
.kbd{display:inline-block;padding:1px 7px;border:1px solid #c9d4e6;border-bottom-width:2px;
  border-radius:6px;background:#fff;font-size:12.5px;color:var(--deep);font-family:inherit}
.pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:11.5px;font-weight:600;
  border:1px solid transparent}
.pill.blue{background:#e8f0fe;color:var(--deep);border-color:#c6dbfd}
.pill.green{background:#e7f7ee;color:#0f7a3d;border-color:#b9e6cd}
.pill.red{background:#fdeaea;color:#a5281c;border-color:#f3c2bc}
.pill.amber{background:#fff4e0;color:#8a5a00;border-color:#f2d49b}
.pill.gray{background:#eef1f5;color:#5b6472;border-color:#dfe4ec}
table.t{width:100%;border-collapse:collapse;margin:10px 0;font-size:13px}
table.t th,table.t td{border:1px solid var(--line);padding:8px 10px;text-align:left;vertical-align:top}
table.t thead th{background:#eef3fb;color:var(--deep);font-weight:700}
table.t tbody tr:nth-child(even){background:#fafbfe}
.role{font-weight:700;color:var(--deep);white-space:nowrap}
.fig{margin:12px 0;border:1px solid var(--line);border-radius:10px;background:#fbfcfe;padding:10px}
.fig .cap{font-size:12px;color:var(--sub);margin:4px 2px 8px}
.scroll{overflow-x:auto}
.scroll svg{min-width:760px;display:block}
.note{background:#f4f8ff;border-left:4px solid var(--blue);border-radius:8px;padding:10px 12px;
  font-size:13px;color:#31415a;margin:10px 0}
.warn{background:#fff8f0;border-left:4px solid var(--amber);border-radius:8px;padding:10px 12px;
  font-size:13px;color:#7a4c00;margin:10px 0}
.cmp{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:10px 0}
.cmp .col{border:1px solid var(--line);border-radius:10px;overflow:hidden}
.cmp .col .hd{padding:7px 12px;font-weight:700;font-size:13px;color:#fff}
.cmp .ok .hd{background:var(--green)}
.cmp .no .hd{background:var(--red)}
.cmp .col .bd{padding:10px 12px;font-size:13px}
.cmp .col .bd p{margin:6px 0}
code.mono{background:#f1f4f9;border:1px solid #e0e6f0;border-radius:6px;padding:1px 6px;
  font-size:12.5px;color:#243b5e}
.sandbox{border:1px dashed #b9c9e3;border-radius:12px;background:#fbfdff;padding:14px 16px;margin:12px 0}
.sandbox .sb-hd{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.sandbox .sb-hd .t{font-weight:700;color:var(--deep);font-size:14px}
.sandbox .sb-hd .badge{font-size:11.5px;color:#7a4c00;background:#fff4e0;border:1px solid #f2d49b;
  border-radius:999px;padding:2px 9px}
.sandbox textarea{width:100%;min-height:150px;border:1px solid #cfd9e8;border-radius:9px;padding:10px 12px;
  font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;line-height:1.6;resize:vertical;
  background:#fff;color:#22303f}
.sandbox textarea.bad{border-color:var(--red);box-shadow:0 0 0 2px rgba(192,57,43,.12)}
.sb-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:9px}
.btn{appearance:none;border:1px solid var(--blue);background:var(--blue);color:#fff;border-radius:9px;
  padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit}
.btn:hover{background:var(--deep);border-color:var(--deep)}
.btn.ghost{background:#fff;color:var(--blue)}
.btn.ghost:hover{background:#eef3fb}
.btn.gray{background:#fff;color:var(--sub);border-color:#cfd9e8}
.btn.gray:hover{background:#f1f4f9}
.sb-status{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:10px;font-size:12.5px}
.sb-status .it{display:inline-flex;align-items:center;gap:5px}
.sb-status .it.fill{color:#0f7a3d}
.sb-status .it.miss{color:#a5281c}
.sb-msg{margin-top:9px;font-size:13px;border-radius:8px;padding:9px 12px;display:none}
.sb-msg.err{display:block;background:#fdeaea;border:1px solid #f3c2bc;color:#8f2317}
.sb-msg.ok{display:block;background:#e7f7ee;border:1px solid #b9e6cd;color:#0f7a3d}
.sb-note{font-size:11.5px;color:var(--sub);margin-top:8px}
.foot-note{margin:22px 0 8px;background:#fff7e6;border:1px solid #f2d49b;color:#8a5a00;
  border-radius:10px;padding:10px 14px;font-size:12.5px;text-align:center}
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media(max-width:820px){.two,.cmp{grid-template-columns:1fr}.hero h1{font-size:21px}}
@media print{
  body{background:#fff}
  .sec,.fig,.sandbox,table.t{break-inside:avoid}
  .toc{break-inside:avoid}
  .demo-bar,.foot-note{background:#fff}
}
"""

# ── 内联 SVG 示意图（禁 <title> / <br>）─────────────────────────────
SVG_A = """
<svg viewBox="0 0 900 250" width="900" height="250" role="img" aria-label="两层结构示意图">
  <defs>
    <marker id="arA" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
      <path d="M0,0 L8,3 L0,6 z" fill="#1852B6"/>
    </marker>
  </defs>
  <rect x="30" y="40" width="350" height="170" rx="12" fill="#e8f0fe" stroke="#1852B6" stroke-width="1.6"/>
  <text x="205" y="70" text-anchor="middle" font-size="16" font-weight="700" fill="#0d3a85">第一层 ｜ 项目推移表</text>
  <text x="205" y="97" text-anchor="middle" font-size="12.5" fill="#31415a">一行一商机 · 8 个关键节点</text>
  <text x="205" y="120" text-anchor="middle" font-size="12.5" fill="#31415a">按 4 个部门分组</text>
  <text x="205" y="143" text-anchor="middle" font-size="12.5" fill="#31415a">资源协助可按需加列</text>
  <text x="205" y="175" text-anchor="middle" font-size="12" fill="#5b6472">你在这里看：我的商机走到哪一步</text>
  <rect x="520" y="40" width="350" height="170" rx="12" fill="#e7f7ee" stroke="#16A34A" stroke-width="1.6"/>
  <text x="695" y="70" text-anchor="middle" font-size="16" font-weight="700" fill="#0f7a3d">第二层 ｜ 活动录入与待办</text>
  <text x="695" y="97" text-anchor="middle" font-size="12.5" fill="#31415a">一行一活动 · 每次沟通/拜访填一条</text>
  <text x="695" y="120" text-anchor="middle" font-size="12.5" fill="#31415a">活动描述按模板填写</text>
  <text x="695" y="143" text-anchor="middle" font-size="12.5" fill="#31415a">周会决议三岗各填一栏</text>
  <text x="695" y="175" text-anchor="middle" font-size="12" fill="#5b6472">你在这里做：填活动 + 提资源需求</text>
  <line x1="522" y1="108" x2="388" y2="108" stroke="#1852B6" stroke-width="2" marker-end="url(#arA)"/>
  <text x="455" y="88" text-anchor="middle" font-size="11.5" fill="#1852B6">抓取（客户 / 商机）</text>
  <text x="455" y="132" text-anchor="middle" font-size="11.5" fill="#1852B6">回写（节点推进 / 资源协作加列）</text>
  <text x="450" y="228" text-anchor="middle" font-size="12" fill="#5b6472">你只填第二层，第一层由系统按「客户名称 + 商机名称」自动抓取与回写</text>
</svg>
"""

SVG_B = """
<svg viewBox="0 0 900 260" width="900" height="260" role="img" aria-label="第二层录入区结构示意图">
  <rect x="24" y="30" width="852" height="200" rx="12" fill="#ffffff" stroke="#e3e8f0" stroke-width="1.4"/>
  <text x="46" y="57" font-size="13.5" font-weight="700" fill="#0d3a85">第二层 · 录入区（4 块 + 1 独立模块）</text>
  <rect x="46" y="74" width="150" height="86" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="121" y="106" text-anchor="middle" font-size="13" font-weight="700" fill="#0d3a85">① 活动类型</text>
  <text x="121" y="130" text-anchor="middle" font-size="11.5" fill="#31415a">三选一</text>
  <text x="121" y="149" text-anchor="middle" font-size="11.5" fill="#31415a">下拉选择</text>
  <rect x="214" y="74" width="150" height="86" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="289" y="106" text-anchor="middle" font-size="13" font-weight="700" fill="#0d3a85">② 活动描述</text>
  <text x="289" y="130" text-anchor="middle" font-size="11.5" fill="#31415a">插入模板后填写</text>
  <text x="289" y="149" text-anchor="middle" font-size="11.5" fill="#31415a">按段校验</text>
  <rect x="382" y="74" width="150" height="86" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="457" y="106" text-anchor="middle" font-size="13" font-weight="700" fill="#0d3a85">③ 关联订单 / 测试单</text>
  <text x="457" y="130" text-anchor="middle" font-size="11.5" fill="#31415a">可选 · 可留空</text>
  <text x="457" y="149" text-anchor="middle" font-size="11.5" fill="#31415a">留空标「待关联」</text>
  <rect x="550" y="74" width="150" height="86" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="625" y="106" text-anchor="middle" font-size="13" font-weight="700" fill="#0d3a85">④ 待办事项</text>
  <text x="625" y="130" text-anchor="middle" font-size="11.5" fill="#31415a">可多条</text>
  <text x="625" y="149" text-anchor="middle" font-size="11.5" fill="#31415a">含资源协作多选</text>
  <rect x="726" y="74" width="136" height="86" rx="9" fill="#e7f7ee" stroke="#16A34A"/>
  <text x="794" y="104" text-anchor="middle" font-size="12.5" font-weight="700" fill="#0f7a3d">周会决议</text>
  <text x="794" y="127" text-anchor="middle" font-size="11.5" fill="#31415a">三岗各填一栏</text>
  <text x="794" y="146" text-anchor="middle" font-size="11.5" fill="#31415a">独立模块</text>
  <text x="121" y="192" text-anchor="middle" font-size="11.5" fill="#5b6472">合同号 / 货号并进描述</text>
  <text x="457" y="192" text-anchor="middle" font-size="11.5" fill="#5b6472">部门 / 客户编码 / 商机规模 / 销售员 / 技术支持 = 只读继承</text>
  <text x="794" y="192" text-anchor="middle" font-size="11.5" fill="#5b6472">本岗栏解锁后才可填</text>
  <text x="46" y="219" font-size="11.5" fill="#5b6472">提交按钮在末尾：一键完成「三轨落盘 + 回写第一层」</text>
</svg>
"""

SVG_C = """
<svg viewBox="0 0 900 240" width="900" height="240" role="img" aria-label="闭环逻辑示意图">
  <defs>
    <marker id="arC" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
      <path d="M0,0 L8,3 L0,6 z" fill="#1852B6"/>
    </marker>
  </defs>
  <text x="24" y="30" font-size="13.5" font-weight="700" fill="#0d3a85">你填一条活动之后，系统自动走完这条闭环</text>
  <rect x="24" y="56" width="128" height="76" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="88" y="88" text-anchor="middle" font-size="12" font-weight="700" fill="#0d3a85">你填一条活动</text>
  <text x="88" y="108" text-anchor="middle" font-size="11" fill="#31415a">勾选资源协作</text>
  <line x1="154" y1="94" x2="176" y2="94" stroke="#1852B6" stroke-width="2" marker-end="url(#arC)"/>
  <rect x="180" y="56" width="128" height="76" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="244" y="88" text-anchor="middle" font-size="12" font-weight="700" fill="#0d3a85">生成跨部门待办</text>
  <text x="244" y="108" text-anchor="middle" font-size="11" fill="#31415a">技术支持 / 研发 / 售后</text>
  <line x1="310" y1="94" x2="332" y2="94" stroke="#1852B6" stroke-width="2" marker-end="url(#arC)"/>
  <rect x="336" y="56" width="128" height="76" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="400" y="88" text-anchor="middle" font-size="12" font-weight="700" fill="#0d3a85">部门认领</text>
  <text x="400" y="108" text-anchor="middle" font-size="11" fill="#31415a">状态 → 已认领</text>
  <line x1="466" y1="94" x2="488" y2="94" stroke="#1852B6" stroke-width="2" marker-end="url(#arC)"/>
  <rect x="492" y="56" width="128" height="76" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="556" y="88" text-anchor="middle" font-size="12" font-weight="700" fill="#0d3a85">部门回写反馈</text>
  <text x="556" y="108" text-anchor="middle" font-size="11" fill="#31415a">进展 / 结论 / 附件</text>
  <line x1="622" y1="94" x2="644" y2="94" stroke="#1852B6" stroke-width="2" marker-end="url(#arC)"/>
  <rect x="648" y="56" width="128" height="76" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
  <text x="712" y="88" text-anchor="middle" font-size="12" font-weight="700" fill="#0d3a85">承接人确认闭环</text>
  <text x="712" y="108" text-anchor="middle" font-size="11" fill="#31415a">状态 → 已闭环</text>
  <line x1="778" y1="94" x2="800" y2="94" stroke="#1852B6" stroke-width="2" marker-end="url(#arC)"/>
  <rect x="804" y="56" width="72" height="76" rx="9" fill="#e7f7ee" stroke="#16A34A"/>
  <text x="840" y="90" text-anchor="middle" font-size="11" font-weight="700" fill="#0f7a3d">商机</text>
  <text x="840" y="108" text-anchor="middle" font-size="11" font-weight="700" fill="#0f7a3d">节点推进</text>
  <rect x="24" y="152" width="852" height="62" rx="10" fill="#fff8f0" stroke="#F59E0B"/>
  <text x="46" y="178" font-size="12.5" font-weight="700" fill="#8a5a00">成交门禁：该商机下所有资源请求都闭环后，商机才允许标为成交 / 闭环。</text>
  <text x="46" y="200" font-size="12" fill="#7a4c00">全过程只追加不覆盖：每一步都留痕，谁在什么时候做了什么，可回看。</text>
</svg>
"""

SVG_D = """
<svg viewBox="0 0 900 250" width="900" height="250" role="img" aria-label="权限示意图">
  <text x="24" y="30" font-size="13.5" font-weight="700" fill="#0d3a85">按角色分字段：你只能改自己那一栏</text>
  <rect x="24" y="46" width="200" height="42" rx="8" fill="#eef3fb" stroke="#1852B6"/>
  <text x="124" y="72" text-anchor="middle" font-size="12.5" font-weight="700" fill="#0d3a85">角色</text>
  <rect x="234" y="46" width="200" height="42" rx="8" fill="#eef3fb" stroke="#1852B6"/>
  <text x="334" y="72" text-anchor="middle" font-size="12.5" font-weight="700" fill="#0d3a85">可编辑范围</text>
  <rect x="444" y="46" width="432" height="42" rx="8" fill="#eef3fb" stroke="#1852B6"/>
  <text x="660" y="72" text-anchor="middle" font-size="12.5" font-weight="700" fill="#0d3a85">说明</text>
  <rect x="24" y="92" width="200" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="124" y="114" text-anchor="middle" font-size="12.5" fill="#1f2937">销售员</text>
  <rect x="234" y="92" width="200" height="34" rx="8" fill="#e7f7ee" stroke="#b9e6cd"/>
  <text x="334" y="114" text-anchor="middle" font-size="12.5" fill="#0f7a3d">本人填写的活动 / 待办</text>
  <rect x="444" y="92" width="432" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="456" y="114" font-size="12" fill="#31415a">只能改自己提交的记录；别人那栏改不了</text>
  <rect x="24" y="130" width="200" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="124" y="152" text-anchor="middle" font-size="12.5" fill="#1f2937">部门承接人</text>
  <rect x="234" y="130" width="200" height="34" rx="8" fill="#e7f7ee" stroke="#b9e6cd"/>
  <text x="334" y="152" text-anchor="middle" font-size="12.5" fill="#0f7a3d">本部门承接的待办 / 反馈</text>
  <rect x="444" y="130" width="432" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="456" y="152" font-size="12" fill="#31415a">只改自己承接的：认领 / 处理 / 写结论 / 闭环</text>
  <rect x="24" y="168" width="200" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="124" y="190" text-anchor="middle" font-size="12.5" fill="#1f2937">销售支持</text>
  <rect x="234" y="168" width="200" height="34" rx="8" fill="#e7f7ee" stroke="#b9e6cd"/>
  <text x="334" y="190" text-anchor="middle" font-size="12.5" fill="#0f7a3d">指派 / 商机状态</text>
  <rect x="444" y="168" width="432" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="456" y="190" font-size="12" fill="#31415a">可标成交 / 闭环（须满足成交门禁）</text>
  <rect x="24" y="206" width="200" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="124" y="228" text-anchor="middle" font-size="12.5" fill="#1f2937">管理员</text>
  <rect x="234" y="206" width="200" height="34" rx="8" fill="#fff4e0" stroke="#f2d49b"/>
  <text x="334" y="228" text-anchor="middle" font-size="12.5" fill="#8a5a00">不受字段限制</text>
  <rect x="444" y="206" width="432" height="34" rx="8" fill="#ffffff" stroke="#e3e8f0"/>
  <text x="456" y="228" font-size="12" fill="#31415a">读 + 写全字段（含三岗周会决议栏）</text>
</svg>
"""

SVG_E = """
<svg viewBox="0 0 900 230" width="900" height="230" role="img" aria-label="填写质量对照示意图">
  <text x="24" y="30" font-size="13.5" font-weight="700" fill="#0d3a85">同一件事，两种写法</text>
  <rect x="24" y="46" width="420" height="164" rx="10" fill="#e7f7ee" stroke="#16A34A" stroke-width="1.5"/>
  <text x="44" y="74" font-size="13" font-weight="700" fill="#0f7a3d">合格版（可提交）</text>
  <text x="44" y="102" font-size="12" fill="#1f2937">· 应用场景：冻干体系放大</text>
  <text x="44" y="124" font-size="12" fill="#1f2937">· 参数要求：37℃ 加速 7 天 Ct 漂移 ≤1</text>
  <text x="44" y="146" font-size="12" fill="#1f2937">· 结论：超阈（方向：Ct 漂移偏大 +0.4）</text>
  <text x="44" y="168" font-size="12" fill="#1f2937">· 问题现象：客户原话「怀疑保护剂析晶」</text>
  <text x="44" y="190" font-size="12" fill="#1f2937">· 待办：补做 1 批 ｜ 王工 ｜ 2026-10-20</text>
  <rect x="456" y="46" width="420" height="164" rx="10" fill="#fdeaea" stroke="#C0392B" stroke-width="1.5"/>
  <text x="476" y="74" font-size="13" font-weight="700" fill="#a5281c">不合格版（会被拦下）</text>
  <text x="476" y="102" font-size="12" fill="#1f2937">· 已沟通</text>
  <text x="476" y="124" font-size="12" fill="#1f2937">· 客户说再看看</text>
  <text x="476" y="146" font-size="12" fill="#1f2937">· 缺【测试结果】【客户反馈】等段</text>
  <text x="476" y="168" font-size="12" fill="#1f2937">· 选「其他」资源协作但未填说明</text>
  <text x="476" y="190" font-size="12" fill="#1f2937">· 关联单号留空（标「待关联」）</text>
</svg>
"""

SANDBOX_JS = r"""
(function(){
  var REQ = ["客户需求","宝锐方案","测试结果","问题现象","客户反馈","待办事项"];
  var ta  = document.getElementById('sbDesc');
  var seg = document.getElementById('sbSeg');
  var msg = document.getElementById('sbMsg');
  var composing = false;

  var VALID = [
    "【客户需求】",
    "· 应用场景：冻干体系放大    · 技术平台：冻干/RT-qPCR",
    "· 参数要求：37℃ 加速 7 天 Ct 漂移 ≤1；批内 CV ≤3%",
    "【宝锐方案】",
    "· 货号+名称：某冻干保护剂（示例）    · 关联订单/测试单：TS-示例-00X",
    "【测试结果】",
    "· 1. A 组 Ct 漂移 0.6 → 通过    · 2. C 组 Ct 漂移 1.4 → 超阈",
    "· 结论：超阈（偏差方向：Ct 漂移偏大 +0.4）",
    "【问题现象】客户原话“C 组第 6 天曲线抬不起来，怀疑保护剂析晶”",
    "【客户反馈 / 下一步诉求】认可 A 组，要求补做 1 批后复测",
    "【待办事项】研发补做 1 批并附加速数据；负责人 王工；2026-10-20"
  ].join("\n");

  var BROKEN = [
    "【客户需求】",
    "· 应用场景：冻干体系放大    · 技术平台：冻干",
    "【宝锐方案】",
    "· 货号+名称：某冻干保护剂（示例）",
    "【测试结果】",
    "（此处故意留空，模拟漏填）",
    "【问题现象】客户原话“C 组第 6 天曲线抬不起来，怀疑保护剂析晶”",
    "【客户反馈 / 下一步诉求】认可 A 组，要求补做 1 批后复测",
    "【待办事项】研发补做 1 批并附加速数据；负责人 王工；2026-10-20"
  ].join("\n");

  function parse(text){
    var segs = {};
    var parts = text.split("\u3010");
    for (var i=1;i<parts.length;i++){
      var head = parts[i].split("\u3011")[0];
      var body = parts[i].indexOf("\u3011") >= 0 ? parts[i].slice(parts[i].indexOf("\u3011")+1) : "";
      var key = null;
      for (var j=0;j<REQ.length;j++){
        if (head.indexOf(REQ[j]) >= 0){ key = REQ[j]; break; }
      }
      if (key === null) continue;
      var clean = body.replace(/[\s\u3000]+/g,"").replace(/[（(].*?[)）]/g,"");
      segs[key] = (segs[key] || "") + clean;
    }
    return segs;
  }
  function missing(text){
    var segs = parse(text), out = [];
    for (var i=0;i<REQ.length;i++){
      if (!segs[REQ[i]] || segs[REQ[i]].length === 0) out.push(REQ[i]);
    }
    return out;
  }
  function render(){
    var segs = parse(ta.value);
    var html = "";
    for (var i=0;i<REQ.length;i++){
      var ok = segs[REQ[i]] && segs[REQ[i]].length > 0;
      html += '<span class="it '+(ok?'fill':'miss')+'">'+(ok?'\u25CF 已填':'\u25CB 缺')+' 【'+REQ[i]+'】</span>';
    }
    seg.innerHTML = html;
    var miss = missing(ta.value);
    ta.classList.toggle('bad', miss.length > 0);
    if (miss.length > 0){
      msg.className = 'sb-msg err';
      msg.textContent = '当前缺 '+miss.length+' 段 —— 缺段时点提交会被拦下：'+miss.map(function(x){return '【'+x+'】';}).join('、');
    } else {
      msg.className = 'sb-msg ok';
      msg.textContent = '六段齐全，可提交（本沙盒为教学示意，提交不落盘）。';
    }
  }
  function setText(t){ ta.value = t; render(); }

  document.getElementById('sbFillOk').addEventListener('click', function(){ setText(VALID); });
  document.getElementById('sbFillBad').addEventListener('click', function(){ setText(BROKEN); });
  document.getElementById('sbReset').addEventListener('click', function(){
    ta.value = ""; ta.classList.remove('bad'); msg.className='sb-msg'; msg.textContent='';
    render();
  });
  document.getElementById('sbSubmit').addEventListener('click', function(){
    var miss = missing(ta.value);
    if (miss.length > 0){
      msg.className = 'sb-msg err';
      msg.textContent = '提交被拦截：活动描述缺少必填段 —— ' + miss.map(function(x){return '【'+x+'】';}).join('、') + '。请补齐后再提交。';
    } else {
      msg.className = 'sb-msg ok';
      msg.textContent = '校验通过：六段齐全。真实页提交后会弹出「三轨落盘」面板（本地记录 / CloudBase / 飞书描述）。';
    }
  });
  ta.addEventListener('compositionstart', function(){ composing = true; });
  ta.addEventListener('compositionend', function(){ composing = false; render(); });
  ta.addEventListener('input', function(){ if (!composing) render(); });
  render();
})();
"""

HTML = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>客户拉通大表 · 销售员使用说明（DEMO）</title>
<style>%s</style>
</head>
<body>
<div class="wrap">
  <div class="demo-bar"><span class="dot"></span>%s</div>

  <div class="hero">
    <h1>客户拉通大表 · 销售员使用说明</h1>
    <span class="sub">两层设计 DEMO ｜ 你要做的事，就三件。照着做即可。</span>
    <div class="who">面向：一线销售员（约 30 人）　｜　配套页面：客户拉通大表（第一层｜项目推移表 · 第二层｜活动录入与待办）</div>
  </div>

  <nav class="toc">
    <a href="#b1">1 为什么要用（3 句话）</a>
    <a href="#b2">2 你要做的事（就三件）</a>
    <a href="#b3">3 一步步怎么做</a>
    <a href="#b4">4 背后的逻辑（一张简图）</a>
    <a href="#b5">5 谁能看到什么（权限）</a>
    <a href="#b6">6 填写质量要求（会被拦的情形）</a>
    <a href="#b7">7 你从中能得到什么</a>
  </nav>

  <!-- ===== 1 ===== -->
  <section class="sec" id="b1">
    <h2><span class="no">1</span>为什么要用（3 句话）</h2>
    <ul class="plain lead">
      <li>拜访完不用再靠口头交接——你填的活动就是交接本身，谁都能在同一张表上看到。</li>
      <li>你提的资源需求有人接、有回话、有闭环——技术支持 / 研发 / 售后认领前，你能看到它卡在哪。</li>
      <li>商机推进到哪一步随时可查——第一层一行一商机，8 个关键节点一眼看到走到哪儿。</li>
    </ul>
    <div class="note">一句话：<b>把你嘴上的事，落到表里；把你要的资源，走进正式入口。</b></div>
  </section>

  <!-- ===== 2 ===== -->
  <section class="sec" id="b2">
    <h2><span class="no">2</span>你要做的事（就三件）</h2>
    <ol class="steps">
      <li><b>每次沟通 / 拜访后 → 在第二层填一条活动。</b><br>
        选活动类型 → 插入模板 → 按模板写活动描述 → 该关联的订单/测试单填上 → 勾选触发的资源协作 → 写待办事项 → 提交。</li>
      <li><b>每周周会 → 在「周会决议」里填自己那一栏。</b><br>
        销售 / 技术支持 / 研发三岗各填各的，一行一条，格式：<code class="mono">决议事项 ｜ 负责人 ｜ 时间节点</code>。别人那栏你改不了。</li>
      <li><b>随时 → 看第一层自己的商机在哪一步，点行看全部跟进。</b><br>
        轨道视图一行一商机；点任意一行，下方展开该商机的资源泳道矩阵（跟进内容按时间向右延展）。</li>
    </ol>
    <div class="warn">三件事之外不用你做：第一层的项目基本信息、节点回写、资源协助加列，都由系统按「客户名称 + 商机名称」自动抓取与回写。</div>
  </section>

  <!-- ===== 3 ===== -->
  <section class="sec" id="b3">
    <h2><span class="no">3</span>一步步怎么做</h2>

    <div class="fig">
      <div class="cap">图 1 · 两层结构与数据流（你只填第二层）</div>
      <div class="scroll">%s</div>
    </div>

    <ol class="steps">
      <li><b>进入第二层</b>：点顶部页签「② 第二层｜活动录入与待办」。顶部「关联对象与继承字段」条里，客户名称、商机名称先选好；
        部门 / 客户编码 / 商机规模 / 销售员 / 技术支持是<b>只读继承</b>，不用你填（客户编码未回填会显示「待关联」）。</li>
      <li><b>选活动类型</b>：① 活动类型 下拉，三选一——
        <span class="pill blue">测试结果跟进</span>
        <span class="pill blue">采购跟进</span>
        <span class="pill blue">客户档案完善</span>。选完描述框会自动套用对应模板。</li>
      <li><b>点「插入模板」→ 按模板填活动描述</b>：② 活动描述 右上角有
        <span class="kbd">插入模板</span> 按钮，可随时重插。
        <span class="pill red">选「测试结果跟进」时六段必填</span>
        ——【客户需求】【宝锐方案】【测试结果】【问题现象】【客户反馈】【待办事项】，
        缺哪段会被标红拦下，提交不了；描述框下方有实时的段位状态（● 已填 / ○ 缺）可自查。</li>
      <li><b>填关联订单 / 测试单</b>：③ 关联订单 / 测试单（可选 · 单字段）。
        填不了就留空，表格里会标
        <span class="pill amber">待关联</span>，能补尽快补上。</li>
      <li><b>勾选触发的资源协作</b>：在 ④ 待办事项 下方「资源协作（多选）」里勾——
        <span class="pill blue">技术支持测试项目单</span>
        <span class="pill blue">研发项目单</span>
        <span class="pill blue">售后</span>
        <span class="pill blue">其他</span>，
        可多选，也可以一个都不选。每选一类 → 第一层对应商机行「资源协助」加一列；
        选<span class="pill amber">其他</span>必须写说明，未填会被拦下。</li>
      <li><b>写待办事项</b>：④ 待办事项可多条，点「＋ 添加一条待办」。写法：
        <b>做什么 / 怎么做 / 做到什么程度 / 关联哪些资源</b>；负责人默认=技术支持协同人，可改可加人；时间节点写到具体日期。</li>
      <li><b>点提交</b>：底部「提交 · 三轨落盘 + 回写第一层」。
        <span class="pill green">三轨落盘</span>
        面板会弹出：① 本地记录（含活动编号）② CloudBase 写入 JSON ③ 飞书描述（一键复制）。同时自动回写第一层对应商机行。</li>
      <li><b>每周在「周会决议」填自己那一栏</b>：先点「以本岗身份填写」解锁本栏（状态由 🔒 本岗未解锁 → 🔓 本岗填写中），
        再按「一行一条」写；未写时表格显示「本岗未提交」。三岗各填各的，别人那栏你改不了。</li>
    </ol>

    <div class="fig">
      <div class="cap">图 2 · 第二层录入区结构（4 块 + 1 独立模块）</div>
      <div class="scroll">%s</div>
    </div>
  </section>

  <!-- ===== 4 ===== -->
  <section class="sec" id="b4">
    <h2><span class="no">4</span>背后的逻辑（一张简图）</h2>
    <div class="fig">
      <div class="cap">图 3 · 你填的活动 → 跨部门待办 → 闭环 → 商机节点推进</div>
      <div class="scroll">%s</div>
    </div>
    <p>这条链路的关键是<b>成交门禁</b>：<b>该商机下所有资源请求都闭环后，商机才能标成交。</b>
      所以你的资源协作不是「提了就完」，它会在第一层挂成待办，直到对应部门认领、回写反馈、承接人确认闭环，才被允许推进成交。</p>
    <div class="note">全过程<b>只追加不覆盖</b>：每一步都留痕（谁 / 何时 / 做了什么 / 附件），可随时回看。</div>
  </section>

  <!-- ===== 5 ===== -->
  <section class="sec" id="b5">
    <h2><span class="no">5</span>谁能看到什么（权限）</h2>
    <p>按角色分字段：<b>你只能改自己那栏；部门只改自己承接的；管理员不受限。</b></p>
    <div class="fig">
      <div class="cap">图 4 · 角色 × 可编辑范围</div>
      <div class="scroll">%s</div>
    </div>
    <table class="t">
      <thead><tr><th>角色</th><th>可读</th><th>可改</th></tr></thead>
      <tbody>
        <tr><td class="role">销售员</td><td>本人发起的活动 / 待办，及本人客户相关记录</td><td>本人填写的活动、待办、资源协作勾选</td></tr>
        <tr><td class="role">部门承接人</td><td>本部门认领的待办与反馈流水（跨商机同行记录只读）</td><td>仅自己承接待办的认领 / 处理 / 结论 / 闭环</td></tr>
        <tr><td class="role">销售支持</td><td>全量</td><td>指派承接人、商机状态（标成交须过成交门禁）</td></tr>
        <tr><td class="role">管理员</td><td>全量</td><td>读 + 写全字段（含三岗周会决议栏）</td></tr>
      </tbody>
    </table>
    <div class="warn">周会决议按岗位分栏：销售 / 技术支持 / 研发各只能写本岗栏；管理员（勾选「管理员模式」）三栏不受限。
      未填栏一律显示「本岗未提交」，不会静默空白。</div>
  </section>

  <!-- ===== 6 ===== -->
  <section class="sec" id="b6">
    <h2><span class="no">6</span>填写质量要求（会被拦的情形）</h2>
    <div class="fig">
      <div class="cap">图 5 · 同一件事：合格版 vs 不合格版</div>
      <div class="scroll">%s</div>
    </div>

    <h3>会被拦下的情形</h3>
    <ul class="plain">
      <li>只写「已沟通」「再看看」这类空话——活动描述没有实质内容，等于没写。</li>
      <li>活动类型选「测试结果跟进」时<b>六段缺一</b>——被拦下，并提示缺哪段。</li>
      <li>勾选资源协作的「其他」却<b>未填说明</b>——被拦下，提示必填说明。</li>
      <li>关联订单 / 测试单留空——不拦，但表格标
        <span class="pill amber">待关联</span>，能补尽快补（占比过高会被重点关注）。</li>
    </ul>

    <div class="fig">
      <div class="cap">图 6 · 六段必填（测试结果跟进）</div>
      <div class="scroll">
        <svg viewBox="0 0 900 96" width="900" height="96" role="img" aria-label="六段必填示意">
          <rect x="20" y="22" width="132" height="52" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
          <text x="86" y="53" text-anchor="middle" font-size="12.5" fill="#0d3a85">客户需求</text>
          <rect x="164" y="22" width="132" height="52" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
          <text x="230" y="53" text-anchor="middle" font-size="12.5" fill="#0d3a85">宝锐方案</text>
          <rect x="308" y="22" width="132" height="52" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
          <text x="374" y="53" text-anchor="middle" font-size="12.5" fill="#0d3a85">测试结果</text>
          <rect x="452" y="22" width="132" height="52" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
          <text x="518" y="53" text-anchor="middle" font-size="12.5" fill="#0d3a85">问题现象</text>
          <rect x="596" y="22" width="132" height="52" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
          <text x="662" y="53" text-anchor="middle" font-size="12.5" fill="#0d3a85">客户反馈</text>
          <rect x="740" y="22" width="132" height="52" rx="9" fill="#e8f0fe" stroke="#1852B6"/>
          <text x="806" y="53" text-anchor="middle" font-size="12.5" fill="#0d3a85">待办事项</text>
        </svg>
      </div>
    </div>

    <div class="sandbox">
      <div class="sb-hd">
        <span class="t">🧪 试试看：段级校验沙盒</span>
        <span class="badge">教学示意 · 纯内存不落盘</span>
      </div>
      <div class="sb-note">把活动描述贴进来，看六段是否齐。真实页在<b>提交时</b>做段级校验并拦截；本沙盒同步演示状态与拦截效果。</div>
      <textarea id="sbDesc" placeholder="点下面「一键填充合格示例 / 一键填充反例」，或自己按【段名】格式粘贴。"></textarea>
      <div class="sb-status" id="sbSeg"></div>
      <div class="sb-msg" id="sbMsg"></div>
      <div class="sb-row">
        <button class="btn" id="sbFillOk">一键填充合格示例</button>
        <button class="btn gray" id="sbFillBad">一键填充反例（看黄灯）</button>
        <button class="btn ghost" id="sbReset">重置</button>
        <button class="btn ghost" id="sbSubmit">提交（看是否被拦）</button>
      </div>
    </div>

    <h3>自查清单（提交前打勾）</h3>
    <ul class="plain">
      <li>活动类型选对了吗？（测试结果跟进 / 采购跟进 / 客户档案完善）</li>
      <li>测试结果跟进：六段齐了吗？问题现象里有没有客户原话？结论写了超阈方向与幅度吗？</li>
      <li>资源协作勾对了吗？选「其他」写说明了吗？</li>
      <li>待办写了「做什么 / 怎么做 / 做到什么程度 / 关联哪些资源 + 负责人 + 日期」吗？</li>
      <li>关联订单/测试单能填就填，填不了留空（会标「待关联」）。</li>
    </ul>
  </section>

  <!-- ===== 7 ===== -->
  <section class="sec" id="b7">
    <h2><span class="no">7</span>你从中能得到什么</h2>
    <ul class="plain lead">
      <li><b>周会不用重复口头汇报</b>——你的活动、待办、周会决议都在表里，会上直接看进展。</li>
      <li><b>年底复盘有据可查</b>——每次沟通、每条资源请求、每次闭环都留痕，成果可追溯。</li>
      <li><b>要资源有正式入口</b>——勾选资源协作即生成跨部门待办，部门认领、回话、闭环，全程可见，不用私下催。</li>
    </ul>
    <div class="note">记住三件事：<b>填活动</b>（第二层）· <b>填周会决议</b>（本岗栏）· <b>看第一层</b>（商机走到哪一步）。</div>
  </section>

  <div class="foot-note">%s</div>
</div>
<script>%s</script>
</body>
</html>
""" % (CSS, DEMO_NOTICE, SVG_A, SVG_B, SVG_C, SVG_D, SVG_E, DEMO_NOTICE, SANDBOX_JS)


def build():
    html = BK.inject(HTML, theme='dark', tag='销售培训',
                     footer_extra='本页为 DEMO 原型使用说明 · ' + DEMO_NOTICE)
    os.makedirs(os.path.dirname(OUT), exist_ok=True)
    with open(OUT, 'w', encoding='utf-8') as f:
        f.write(html)
    return OUT


def selfcheck(path):
    raw = open(path, encoding='utf-8').read()
    import re
    bad = {
        'src="http': raw.count('src="http'),
        "href=\"http": raw.count('href="http'),
        '<script src': raw.count('<script src'),
        '<link': raw.count('<link'),
        'localStorage': raw.count('localStorage'),
        'sessionStorage': raw.count('sessionStorage'),
        'cookie': raw.count('cookie'),
        'fetch(': raw.count('fetch('),
        'XMLHttpRequest': raw.count('XMLHttpRequest'),
        'sendBeacon': raw.count('sendBeacon'),
        'confirm(': raw.count('confirm('),
        'alert(': raw.count('alert('),
    }
    bad = {k: v for k, v in bad.items() if v}
    assert not bad, '自检未过：%s' % bad
    # SVG 禁 <title>/<br>
    svg_bad = len(re.findall(r'<svg[\s\S]*?</svg>', raw))
    assert svg_bad >= 5, 'SVG 数量不足：%d' % svg_bad
    assert not re.search(r'<svg[\s\S]*?<title[\s>]', raw), 'SVG 内出现 <title>'
    for kw in ['周会决议', '三轨落盘', '资源协作', '插入模板', '待关联']:
        assert kw in raw, '功能名缺失：%s' % kw
    assert '已上线' not in raw, '出现「已上线」（不得写成已上线）'
    v = BK.verify(raw)
    badv = [k for k, ok in v.items() if not ok]
    assert not badv, '品牌自检未过：%s' % badv
    return {'SVG 张数': svg_bad, '字符数': len(raw)}


if __name__ == '__main__':
    p = build()
    info = selfcheck(p)
    print('✅ 生成：%s' % p)
    print('   %s' % info)
