<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/styles.css">
<script src="_ds/industry-22c874fd-9888-4e99-8234-d60d39b3d719/_ds_bundle.js"></script>
<style>
body{margin:0;background:#f2f2f3;min-width:1280px}
*{scrollbar-width:thin;scrollbar-color:var(--color-accent-300) transparent}
a{color:#5980a6}a:hover{color:#416180}
@keyframes omIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes omScan{to{background-position:28px 0}}
@keyframes omDraw{to{stroke-dashoffset:0}}
@keyframes omGrow{from{width:0}}
</style>
</helmet>
<dc-import name="ConsoleNav" active="workflows" hint-size="100%,92px"></dc-import>
<div data-screen-label="工作流模板" style="max-width:1400px;margin:0 auto;padding:22px 24px 64px;font-family:var(--font-body);animation:omIn .5s cubic-bezier(.2,.7,.2,1) both">
  <div style="display:flex;align-items:flex-end;gap:16px;margin-bottom:18px">
    <div>
      <div style="font-size:10px;letter-spacing:.18em;color:var(--color-accent-700);font-weight:500">08 · WORKFLOWS</div>
      <h2 style="margin:2px 0 0;font-size:30px">工作流模板</h2>
      <div class="text-muted" style="font-size:12.5px;margin-top:2px">SOP 结构化 · 版本化 · 审批与签名 — 运行时禁止拼接未审核的危险命令</div>
    </div>
    <span style="margin-left:auto"></span>
    <button class="btn btn-secondary" style="font-size:12.5px">从 SOP 文档草拟(AI)</button>
    <button class="btn btn-primary" style="font-size:13px">新建模板</button>
  </div>

  <div style="display:grid;grid-template-columns:330px 1fr;gap:18px;align-items:start">
    <div style="display:flex;flex-direction:column;gap:10px">
      <sc-for list="{{ list }}" as="tp" hint-placeholder-count="4">
        <div onClick="{{ tp.pick }}" tabindex="0" style="{{ tp.itemStyle }}">
          <div style="display:flex;align-items:center;gap:8px">
            <span style="font-family:ui-monospace,Menlo,monospace;font-size:12.5px;font-weight:700">{{ tp.name }}</span>
            <span style="margin-left:auto;font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500)">{{ tp.ver }}</span>
          </div>
          <div style="font-size:12px;color:var(--color-neutral-700);margin:4px 0 6px">{{ tp.desc }}</div>
          <div style="display:flex;gap:6px;align-items:center">
            <span style="{{ tp.stStyle }}">{{ tp.status }}</span>
            <sc-for list="{{ tp.risks }}" as="rk" hint-placeholder-count="2"><span style="font-family:ui-monospace,Menlo,monospace;font-size:9px;background:var(--color-accent-800);color:#f2f2f3;padding:1px 6px">{{ rk }}</span></sc-for>
            <span style="margin-left:auto;font-size:10px;color:var(--color-neutral-500)">{{ tp.updated }}</span>
          </div>
        </div>
      </sc-for>
      <div style="border:1px dashed var(--color-divider);padding:11px 12px;font-size:11.5px;color:var(--color-neutral-600);line-height:1.6">每接入一套真实 SOP,就沉淀为可复用模板与连接器 · 模板参数化,严格控制定制边界</div>
    </div>

    <div style="display:flex;flex-direction:column;gap:16px">
      <div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 18px;gap:10px">
        <i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
        <div style="display:flex;align-items:center;gap:10px">
          <h4 style="margin:0;letter-spacing:.01em">{{ selName }}</h4>
          <span style="{{ selStStyle }}">{{ selStatus }}</span>
          <span style="margin-left:auto"></span>
          <button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">复制为新版本</button>
          <button class="btn btn-secondary" style="font-size:12px;padding:5px 12px">导出 YAML</button>
          <button class="btn btn-primary" disabled="{{ selPublished }}" style="font-size:12px;padding:5px 14px">提交审批</button>
        </div>
        <div style="display:flex;gap:8px;flex-wrap:wrap;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--color-neutral-700)">
          <span style="border:1px solid var(--color-divider);padding:2px 8px">{{ selVer }} · workflow_version 1</span>
          <span style="border:1px solid var(--color-divider);padding:2px 8px">签名 {{ selSig }}</span>
          <span style="border:1px solid var(--color-divider);padding:2px 8px">来源 {{ selSource }}</span>
          <span style="border:1px solid var(--color-divider);padding:2px 8px">检查点:每循环</span>
          <span style="border:1px solid var(--color-divider);padding:2px 8px">恢复策略 esc-default-v2</span>
        </div>
      </div>

      <div style="display:grid;grid-template-columns:1fr 340px;gap:16px;align-items:start">
        <div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:14px 16px;gap:4px">
          <i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
          <h6 style="margin:0 0 8px;color:var(--color-neutral-700)">步骤定义 · STEPS</h6>
          <sc-for list="{{ steps }}" as="st" hint-placeholder-count="10">
            <div style="{{ st.rowStyle }}">
              <span style="{{ st.numStyle }}">{{ st.num }}</span>
              <div style="flex:1">
                <div style="display:flex;align-items:center;gap:8px">
                  <span style="font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:600">{{ st.name }}</span>
                  <sc-if value="{{ st.risk }}" hint-placeholder-val="{{ false }}"><span style="font-family:ui-monospace,Menlo,monospace;font-size:9px;background:var(--color-accent-800);color:#f2f2f3;padding:1px 6px">{{ st.risk }}</span></sc-if>
                </div>
                <div style="font-size:11.5px;color:var(--color-neutral-600);margin-top:1px">{{ st.note }}</div>
              </div>
              <span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500);white-space:nowrap">{{ st.meta }}</span>
            </div>
          </sc-for>
          <div style="border:1px dashed #b03d2e;padding:10px 12px;margin-top:10px">
            <div style="font-size:10px;letter-spacing:.12em;color:#8a2e22;margin-bottom:6px">ON_FAILURE · 失败处理管道</div>
            <div style="display:flex;gap:8px;align-items:center;font-family:ui-monospace,Menlo,monospace;font-size:11px;color:var(--color-neutral-800);flex-wrap:wrap">
              <span style="border:1px solid var(--color-divider);padding:3px 9px;background:var(--color-bg)">evidence.capture_all</span>
              <span style="color:var(--color-neutral-400)">→</span>
              <span style="border:1px solid var(--color-divider);padding:3px 9px;background:var(--color-bg)">recovery.escalate</span>
              <span style="color:var(--color-neutral-400)">→</span>
              <span style="border:1px solid var(--color-divider);padding:3px 9px;background:var(--color-bg)">failure.classify</span>
              <span style="font-size:10px;color:var(--color-neutral-600);margin-left:4px">取证先于恢复 · 破坏性步骤不自动重试</span>
            </div>
          </div>
        </div>

        <div style="display:flex;flex-direction:column;gap:16px">
          <div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:8px;border-color:var(--color-accent-600)">
            <i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
            <h6 style="margin:0;color:var(--color-neutral-700)">安全声明 · SAFETY</h6>
            <div style="font-family:ui-monospace,Menlo,monospace;font-size:10.5px;line-height:1.9;background:var(--color-neutral-100);border:1px solid var(--color-divider);padding:9px 11px;color:var(--color-neutral-800)">safety:<br>&nbsp;&nbsp;allowed_dut_serials: {{ selSerials }}<br>&nbsp;&nbsp;destructive_write: {{ selDestr }}<br>&nbsp;&nbsp;require_approval: true<br>&nbsp;&nbsp;system_disk_guard: enforce<br>variables:<br>&nbsp;&nbsp;cycles: {{ selCycles }}<br>&nbsp;&nbsp;firmware: "${firmware}"</div>
            <div style="font-size:11px;color:var(--color-neutral-600)">Format / Sanitize / 刷写只能来自签名模板,参数受 Schema 限制</div>
          </div>
          <div class="card blueprint" style-hover="border-color:var(--color-accent-400);box-shadow:var(--shadow-sm)" style="transition:border-color .15s ease,box-shadow .15s ease;padding:12px 14px;gap:8px">
            <i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
            <h6 style="margin:0;color:var(--color-neutral-700)">审批记录 · AUDIT</h6>
            <div style="display:flex;flex-direction:column">
              <sc-for list="{{ approvals }}" as="ap" hint-placeholder-count="4">
                <div style="display:flex;gap:10px;padding:5px 0;border-bottom:1px solid rgba(29,31,32,.06);font-size:11.5px">
                  <span style="font-family:ui-monospace,Menlo,monospace;font-size:10px;color:var(--color-neutral-500);min-width:44px">{{ ap.t }}</span>
                  <span style="{{ ap.dotStyle }}"></span>
                  <span style="flex:1">{{ ap.what }}</span>
                  <span style="color:var(--color-neutral-600)">{{ ap.who }}</span>
                </div>
              </sc-for>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1440,&quot;height&quot;:880}}">
class Component extends DCLogic {
  state = { sel: 0 };
  renderVals() {
    const mono = { fontFamily: 'ui-monospace,Menlo,monospace' };
    const stOf = st => {
      const b = { fontSize: '10px', padding: '2px 9px', letterSpacing: '.04em' };
      if (st === '已发布') return { ...b, background: 'var(--color-accent-100)', color: 'var(--color-accent-800)' };
      if (st === '审批中') return { ...b, border: '1px solid var(--color-accent-600)', color: 'var(--color-accent-700)' };
      return { ...b, border: '1px dashed var(--color-neutral-500)', color: 'var(--color-neutral-700)' };
    };
    const T = [
      { name: 'fw_ab_powerstate_regression', ver: 'v3', status: '已发布', risks: ['破坏写', '刷写'], desc: '固件 A/B 休眠/唤醒 100 循环回归 — 首个落地 SOP', updated: '07-15', sig: '8e02…c4', source: 'SOP-FW-012(客户脱敏版)', serials: '["SAMPLE-001","SAMPLE-002"]', destr: 'true', cycles: '100',
        steps: [
          { num: '01', name: 'lab.precheck', note: '安全预检 · 环境指纹 · 序列号/系统盘校验', meta: 'timeout 120s', risk: null, lvl: 0 },
          { num: '02', name: 'vendor.flash_firmware', note: 'image: ${firmware} · 哈希校验 · slot 激活', meta: 'timeout 300s · retry 1', risk: '固件更新', lvl: 0 },
          { num: '03', name: 'host.reboot', note: 'wait_for agent_online · 版本确认', meta: 'timeout 600s', risk: null, lvl: 0 },
          { num: 'L', name: 'loop · repeat ${cycles}', note: '检查点:每循环写入,失败从安全点续跑', meta: 'cycles 100', risk: null, lvl: -1 },
          { num: '·1', name: 'nvme.collect_baseline', note: 'SMART / Error Log / Telemetry 基线', meta: '30s', risk: null, lvl: 1 },
          { num: '·2', name: 'io.run_profile', note: 'profile 4k_mixed · duration 300s · QD32', meta: '写入', risk: '破坏写', lvl: 1 },
          { num: '·3', name: 'host.hibernate', note: 'S4 · 断言电源状态达成', meta: 'timeout 180s', risk: null, lvl: 1 },
          { num: '·4', name: 'host.wait_resume', note: 'RTC 唤醒 · Agent 上线', meta: 'timeout 300s', risk: null, lvl: 1 },
          { num: '·5', name: 'dut.verify_enumeration', note: '设备节点 · 链路宽度/速率断言', meta: 'retry 3', risk: null, lvl: 1 },
          { num: '·6', name: 'data.verify_hash', note: '校验集 sha256 对比 · 失败范围定位', meta: '120s', risk: null, lvl: 1 }
        ],
        approvals: [
          { t: '07-15', what: 'v3 审核通过并发布 · 签名 8e02…c4', who: '李工 · 验证组长', kind: 'ok' },
          { t: '07-12', what: 'v3 提交审批:唤醒断言加严(+链路速率)', who: 'quan.zhang', kind: 'n' },
          { t: '07-08', what: 'v2 修订:循环 50→100 · 检查点改每循环', who: 'quan.zhang', kind: 'n' },
          { t: '06-30', what: 'v1 由 SOP-FW-012 草拟(AI 初稿+人工修订)', who: 'quan.zhang', kind: 'n' }
        ] },
      { name: 'fw_retention_72h', ver: 'v1', status: '审批中', risks: ['破坏写'], desc: '高温 72h 数据保持,分段校验', updated: '07-24', sig: '待签名', source: 'SOP-FW-019', serials: '["SAMPLE-004"]', destr: 'true', cycles: '3 段 ×24h',
        steps: [
          { num: '01', name: 'lab.precheck', note: '含温箱通道检查', meta: 'timeout 120s', risk: null, lvl: 0 },
          { num: '02', name: 'io.write_retention_set', note: '写入 1.2TB 校验集 · 记录 LBA 映射', meta: '≈70min', risk: '破坏写', lvl: 0 },
          { num: 'L', name: 'loop · repeat 3', note: '每 24h 一段,段间校验', meta: '72h', risk: null, lvl: -1 },
          { num: '·1', name: 'chamber.hold_temp', note: '55°C 恒温 24h · 温度证据采集', meta: '24h', risk: null, lvl: 1 },
          { num: '·2', name: 'data.verify_hash', note: '分段读校验 · 错误率统计', meta: '90min', risk: null, lvl: 1 }
        ],
        approvals: [
          { t: '07-24', what: 'v1 提交审批 · 等待验证组长', who: 'quan.zhang', kind: 'n' },
          { t: '07-22', what: 'v1 由 SOP-FW-019 草拟', who: 'quan.zhang', kind: 'n' }
        ] },
      { name: 'fw_coldboot_matrix', ver: 'v2', status: '已发布', risks: ['刷写'], desc: '500 次冷启动 · 枚举与启动时序断言', updated: '07-10', sig: '5fd0…9a', source: 'SOP-FW-007', serials: '["SAMPLE-006"]', destr: 'false', cycles: '500',
        steps: [
          { num: '01', name: 'lab.precheck', note: '安全预检 · OOB AC 通道确认', meta: 'timeout 120s', risk: null, lvl: 0 },
          { num: 'L', name: 'loop · repeat 500', note: '冷启动矩阵', meta: 'cycles 500', risk: null, lvl: -1 },
          { num: '·1', name: 'oob.power_cycle', note: '整机 AC 断电 · 安全间隔 30s', meta: 'OOB 动作', risk: null, lvl: 1 },
          { num: '·2', name: 'host.wait_boot', note: 'POST → OS → Agent 上线计时', meta: 'timeout 600s', risk: null, lvl: 1 },
          { num: '·3', name: 'dut.verify_enumeration', note: '枚举耗时断言 <5s', meta: 'retry 0', risk: null, lvl: 1 }
        ],
        approvals: [
          { t: '07-10', what: 'v2 审核通过并发布', who: '李工 · 验证组长', kind: 'ok' },
          { t: '07-06', what: 'v2 修订:加启动时序断言', who: 'quan.zhang', kind: 'n' }
        ] },
      { name: 'incoming_qc_quick', ver: 'v5', status: '草稿', risks: [], desc: '来料抽检 30min 快检(只读+短写)', updated: '今日', sig: '未签名', source: '内部模板', serials: '["批量·入库检标签"]', destr: 'false', cycles: '1',
        steps: [
          { num: '01', name: 'lab.precheck', note: '批量扫码 · 序列号登记比对', meta: 'timeout 120s', risk: null, lvl: 0 },
          { num: '02', name: 'nvme.identify_baseline', note: 'Identify / SMART / 固件版本核对', meta: '60s', risk: null, lvl: 0 },
          { num: '03', name: 'io.short_stress', note: '5min 混合负载 · 温升记录', meta: '5min', risk: '破坏写', lvl: 0 },
          { num: '04', name: 'report.batch_summary', note: '批次基线对比 · 标签打印', meta: '30s', risk: null, lvl: 0 }
        ],
        approvals: [
          { t: '今日', what: 'v5 草稿修订中(批次基线阈值)', who: 'quan.zhang', kind: 'n' }
        ] }
    ];
    const sel = T[this.state.sel];
    const list = T.map((tp, i) => ({
      ...tp,
      pick: () => this.setState({ sel: i }),
      stStyle: stOf(tp.status),
      itemStyle: {
        border: i === this.state.sel ? '1px solid var(--color-accent-600)' : '1px solid var(--color-divider)',
        background: i === this.state.sel ? 'var(--color-accent-100)' : 'transparent',
        padding: '11px 13px', cursor: 'pointer', transition: 'border-color .15s ease, background .15s ease'
      }
    }));
    const steps = sel.steps.map((st, i) => {
      const isLoop = st.lvl === -1;
      return {
        ...st,
        rowStyle: {
          animation: 'omIn .4s cubic-bezier(.2,.7,.2,1) both', animationDelay: (i * 45) + 'ms',
          display: 'flex', gap: '12px', alignItems: 'flex-start', padding: '8px 6px',
          borderBottom: '1px solid rgba(29,31,32,.07)',
          marginLeft: st.lvl === 1 ? '26px' : 0,
          borderLeft: st.lvl === 1 ? '2px solid var(--color-accent-300)' : isLoop ? '2px solid var(--color-accent)' : '2px solid transparent',
          paddingLeft: '12px',
          background: isLoop ? 'var(--color-accent-100)' : 'transparent'
        },
        numStyle: { ...mono, minWidth: '24px', fontSize: '10.5px', color: isLoop ? 'var(--color-accent-800)' : 'var(--color-neutral-500)', paddingTop: '2px', fontWeight: isLoop ? 700 : 400 }
      };
    });
    const approvals = sel.approvals.map(ap => ({
      ...ap,
      dotStyle: { width: '8px', height: '8px', borderRadius: '50%', flex: 'none', marginTop: '4px', background: ap.kind === 'ok' ? 'var(--color-accent)' : 'var(--color-bg)', border: ap.kind === 'ok' ? 'none' : '1.5px solid var(--color-neutral-400)' }
    }));
    return {
      list, steps, approvals,
      selName: sel.name, selVer: sel.ver, selStatus: sel.status, selStStyle: stOf(sel.status),
      selSig: sel.sig, selSource: sel.source, selSerials: sel.serials, selDestr: sel.destr, selCycles: sel.cycles,
      selPublished: sel.status === '已发布'
    };
  }
}
</script>
</body>
</html>
