Files
freedakandDevin f307e877c9 feat: OPC 社区运营公共服务平台 Demo v1.3
一人公司/超级个体产业社区公共服务基础设施 Demo,包含:
- 18 个角色页面(运营商/OPC/企业/个人/学员/培训机构/服务机构/政府/审计/金融)
- 暖橙活力主题 UI(渐变毛玻璃 + 暖色侧栏)
- 五步运营闭环(选方向→育人才→建平台→立SOP→聚产业)
- 华强北硬件供应链接入
- 蜂群式交付 + 产业集聚效应展示
- 需求文档(REQ-001~118 全覆盖)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-13 09:07:23 +08:00

286 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订单协同与质检 · OPC 公共服务平台 Demo</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body data-page="order">
<header class="topnav" id="topnav"></header>
<div class="container">
<div class="page-title" id="od-title"></div>
<div class="page-sub" id="od-sub"></div>
<!-- REQ-030 订单状态机 -->
<div class="card">
<h3>订单状态 <span class="req">REQ-030</span>
<span style="margin-left:auto">
<button class="btn sm" onclick="submitDeliver()">提交交付物</button>
<button class="btn sm ghost" onclick="toast('流转留痕:09-01 签约 → 09-03 开工 → 09-08 里程碑1验收通过(演示)')">流转留痕</button>
</span>
</h3>
<div id="od-steps"></div>
</div>
<!-- REQ-033/034 任务拆解与协作组 -->
<div class="section">
<h2>任务拆解 <span class="req">REQ-033/034 · 协作组 3 人</span></h2>
<div class="card">
<table id="task-tbl"></table>
<div class="notice" style="margin-top:12px" id="risk-notice"></div>
<p class="muted" style="margin-top:8px">订单保证金 ¥12,000 已托管 · 延期预警与替补机制生效中(REQ-046);任务支持模板/依赖/暂停/转派(REQ-034)。</p>
</div>
</div>
<!-- 蜂群式交付(§1.5 步骤④) -->
<div class="section">
<h2>蜂群式交付 <span class="req">§1.5 ④ · SOP 标准体系 · 单兵可替 · 产能可扩</span></h2>
<div class="card">
<p style="margin-bottom:12px">基于 <code>SOP_电商视觉_V3.2</code> 标准体系,多个 OPC 按统一标准并行交付同一项目;每个交付单元独立可替换,单个 OPC 退出不影响整体产能。</p>
<table id="swarm-tbl"></table>
<p class="muted" style="margin-top:10px">蜂群交付核心:① 统一 SOP 标准保证质量一致性 ② 任务粒度可拆可替 ③ 产能随 OPC 数量线性扩展 ④ 项目负责人复检 + 运营商终检兜底</p>
</div>
</div>
<!-- REQ-036 进度甘特图 -->
<div class="section">
<div class="card">
<h3>进度甘特图 <span class="req">REQ-036 · 红线为今日 09-11</span></h3>
<div class="gantt" id="gantt"></div>
</div>
</div>
<!-- REQ-049 三级质检 -->
<div class="section">
<div class="two-col">
<div class="card">
<h3>三级质检 <span class="req">REQ-049/039/054/055</span></h3>
<table id="qc-tbl"></table>
<p class="muted" style="margin-top:8px">版权与合规审查为内容类订单强制终检项(REQ-054);AI 生成内容须经事实/肖像/商标/有害信息审核(REQ-055)。</p>
<p class="muted">返工归因(REQ-057):详情页批次 A → 需求变更(客户新增合规文案要求),不计入 OPC 返工率。</p>
</div>
<div class="card">
<h3>质量指标 · 跨境电商视觉 <span class="req">REQ-051/056/058/059</span>
<span style="margin-left:auto"><button class="btn sm ghost" onclick="qcRules()">抽检规则</button></span>
</h3>
<ul class="list" id="qm-list"></ul>
<p class="muted" style="margin-top:8px">REQ-052:按时交付率接近阈值,已触发预警观察;质量数据脱敏同步政府驾驶舱与 OPC 能力档案(REQ-058),指标集版本 QI_0002/V3 可扩展(REQ-059)。</p>
</div>
</div>
</div>
<!-- REQ-035/038 协作与安全 -->
<div class="section">
<div class="two-col">
<div class="card">
<h3>文件与沟通 <span class="req">REQ-035</span></h3>
<ul class="list" id="file-list"></ul>
</div>
<div class="card">
<h3>数据安全 <span class="req">REQ-038 · L3 敏感</span></h3>
<ul class="list" id="sec-list"></ul>
</div>
</div>
</div>
</div>
<div class="footer">OPC 公共服务平台 Demo · 订单协同与质检</div>
<script src="assets/app.js"></script>
<script src="assets/store.js"></script>
<script>
function renderPage(DB) {
const D = DB.orderDetail;
const role = currentRole();
const subMap = {
opc: 'OPC 交付方视角 · 提交交付物、三级质检、替补协作',
enterprise: '企业发单方视角 · 跟踪进度、验收与争议',
personal: '个人发单方视角 · 跟踪进度、验收与争议',
operator: '运营商管理视角 · 全链路协同与质检监管',
};
document.getElementById('od-title').textContent = '订单协同 · ' + D.id;
document.getElementById('od-sub').textContent = D.title + (subMap[role] ? ' · ' + subMap[role] : '');
document.getElementById('od-steps').innerHTML = STEPS(D.steps);
renderTasks();
renderSwarm();
renderQc();
document.getElementById('qm-list').innerHTML = D.qualityMetrics.map(q => LI(q[0], B(q[1]))).join('');
renderGantt();
document.getElementById('file-list').innerHTML = D.files.map(f =>
`<li><span>${f[0]}</span><span class="muted">${f[1]}</span></li>`).join('');
document.getElementById('sec-list').innerHTML =
D.security.map(s => LI(s[0], B(s[1]))).join('') +
`<li><span>下载审计日志</span><span><span class="muted">近 7 天 14 次</span> <button class="btn sm ghost" onclick="auditLog()">查看</button></span></li>`;
}
const DAYS = 20, TODAY = 11;
function renderGantt() {
const D = DB.orderDetail;
const ticks = [1, 5, 10, 15, 20].map(d =>
`<span style="left:${(d - 0.5) / DAYS * 100}%">09-${String(d).padStart(2, '0')}</span>`).join('');
document.getElementById('gantt').innerHTML =
`<div class="gantt-row"><span class="gantt-name"></span><div class="gantt-axis">${ticks}</div></div>` +
D.tasks.map(t => `
<div class="gantt-row">
<span class="gantt-name">${t.name}</span>
<div class="gantt-track">
<div class="gantt-bar ${t.risk ? 'err' : (t.cls || '')}" style="left:${(t.s - 1) / DAYS * 100}%;width:${(t.e - t.s + 1) / DAYS * 100}%">${t.ddl} 止 · ${t.pct}%</div>
<div class="gantt-today" style="left:${(TODAY - 0.5) / DAYS * 100}%"></div>
</div>
</div>`).join('');
}
function qcRules() {
openModal({
title: '抽检规则配置(REQ-056)',
body: `
<div class="field-row">
<div class="field"><label>抽检比例</label><select><option>10%</option><option selected>30%</option><option>100%(高敏项目默认)</option></select></div>
<div class="field"><label>合格阈值</label><select><option>≥95%</option><option selected>≥98%</option><option>≥99%</option></select></div>
</div>
<div class="field"><label>抽检人</label><select><option>运营商质检组</option><option>交叉质检(其他 L3+ OPC)</option></select></div>
<p class="muted">本项目数据等级 L3 敏感,建议全检;规则变更留痕并影响后续批次。</p>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '保存规则', onClick: m => { m.remove(); toast('抽检规则已更新:30% → 全检(L3 高敏默认),新批次生效'); } },
],
});
}
function renderSwarm() {
const S = DB.orderDetail.swarm || [];
document.getElementById('swarm-tbl').innerHTML =
`<tr><th>交付单元</th><th>承接 OPC</th><th>等级</th><th>SOP 标准</th><th>状态</th><th>可替换</th></tr>` +
S.map(s => `<tr>
<td>${s.unit}</td>
<td>${s.opc}</td>
<td><span class="badge pri">${s.level}</span></td>
<td><code>${s.sop}</code></td>
<td>${B(s.status)}</td>
<td>${s.replaceable ? '<span class="badge ok">可替</span>' : '<span class="badge gray">—</span>'}</td>
</tr>`).join('');
}
function renderTasks() {
const D = DB.orderDetail;
document.getElementById('task-tbl').innerHTML =
`<tr><th>任务</th><th>负责人</th><th>前置依赖</th><th>截止</th><th>状态</th><th>进度</th></tr>` +
D.tasks.map((t, i) => `<tr${t.risk ? ' id="task-risk"' : ''}><td>${t.name}</td><td>${t.owner}</td><td>${t.dep}</td><td>${t.ddl}</td><td>${B(t.status)}</td><td>${BAR(t.pct, t.cls)}</td></tr>`).join('');
renderRiskNotice();
}
function renderRiskNotice() {
const t = DB.orderDetail.tasks.find(x => x.risk);
const n = document.getElementById('risk-notice');
if (!t) { n.style.display = 'none'; return; }
n.style.display = '';
n.innerHTML = t.taken
? `替补已接管:OPC_00061 刘工 接手商品场景图任务,新交期 09-21,已同步客户与项目负责人。`
: `REQ-037 预警:${t.name}任务预计逾期 2 天,已通知项目负责人与运营商,替补 OPC_00061 刘工 可接管。
<button class="btn sm" style="margin-left:8px" onclick="takeover()">启用替补接管</button>
<button class="btn sm ghost" onclick="reassign()">转派</button>`;
}
function renderQc() {
const D = DB.orderDetail;
document.getElementById('qc-tbl').innerHTML =
`<tr><th>交付批次</th><th>① OPC 自检</th><th>② 项目复检</th><th>③ 交付终检</th><th>操作</th></tr>` +
D.qc.map((q, i) => `<tr${q.id ? ` id="${q.id}"` : ''}><td>${q.batch}</td><td>${B(q.s1)}</td><td>${B(q.s2)}</td><td>${q.s3 ? B(q.s3) : '—'}</td><td>${qcAction(q, i)}</td></tr>`).join('');
}
function qcAction(q, i) {
if (q.action === 'rework') return `<button class="btn sm ghost" onclick="reworkDetail()">归因</button>`;
if (q.action === 'review') return `<button class="btn sm" onclick="reviewPass(${i})">复检通过</button> <button class="btn sm ghost" onclick="reviewFail(${i})">退回</button>`;
if (q.action === 'final') return `<button class="btn sm" onclick="finalPass(${i})">终检通过</button>`;
return '—';
}
function submitDeliver() {
openModal({
title: '提交交付物 · SOP 提交清单校验(REQ-039)',
body: `
<div class="field"><label>所属任务</label><select><option>首页+详情页改版 ×8</option><option>活动 Banner ×12</option></select></div>
<div class="checks">
<label><input type="checkbox" id="d1" checked> 交付文件齐全(源文件+导出图)</label>
<label><input type="checkbox" id="d2" checked> 命名规范符合 SOP_电商视觉_V3.2</label>
<label><input type="checkbox" id="d3"> 素材来源记录已填写(版权审查必备)</label>
<label><input type="checkbox" id="d4" checked> 自检问题清单已附</label>
</div>
<div class="field"><label>文件</label><input type="file" multiple></div>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '提交质检', onClick: m => {
if (!document.getElementById('d3').checked) { toast('清单不完整:素材来源记录为强制项,无法进入质检', 'err'); return; }
m.remove();
DB.orderDetail.qc.push({ batch: 'DLV_2026091101 · 详情页批次 B(新提交)', s1: ['自检通过', 'ok'], s2: ['待复检', 'info'], s3: null, action: null });
renderQc();
toast('清单校验通过,交付物进入①自检→②复检队列');
} },
],
});
}
function takeover() {
confirmDlg('启用替补接管', '将「商品场景图 ×40」移交替补 OPC_00061 刘工?已交付部分与文件版本一并移交,逾期责任按 REQ-057 归因。', '确认接管', () => {
const t = DB.orderDetail.tasks.find(x => x.risk);
t.owner = 'OPC_00061 刘工(替补)'; t.status = ['已接管 · 排产中', 'info']; t.taken = true;
renderTasks();
toast('替补接管完成,任务与权限已转移');
});
}
function reassign() {
openModal({
title: '任务转派',
body: `<div class="field"><label>转派给</label><select><option>OPC_00061 刘工(替补 · L2 · 匹配 88%)</option><option>OPC_00045 陈视觉(L2 · 当前负载 80%)</option></select></div>
<div class="field"><label>转派原因</label><textarea placeholder="将记录于任务流转留痕"></textarea></div>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '确认转派', onClick: m => { m.remove(); toast('转派申请已提交项目负责人审批'); } },
],
});
}
function reviewPass(i) {
const q = DB.orderDetail.qc[i];
q.s2 = ['通过', 'ok']; q.s3 = ['待终检', 'info']; q.action = 'final';
renderQc();
toast('②复检通过,进入③交付终检');
}
function reviewFail(i) {
const q = DB.orderDetail.qc[i];
q.s2 = ['返工', 'err']; q.action = 'rework';
renderQc();
toast('已退回返工,请填写责任归因(REQ-057)', 'warn');
}
function finalPass(i) {
const q = DB.orderDetail.qc[i];
q.s3 = ['通过', 'ok']; q.action = null;
renderQc();
toast('③终检通过,批次可提交客户验收');
}
function reworkDetail() {
openModal({
title: '返工归因 · 详情页批次 A(REQ-057)',
body: `<div class="checks">
<label><input type="radio" name="rw" checked> 需求变更(客户新增合规文案)</label>
<label><input type="radio" name="rw"> OPC 执行问题</label>
<label><input type="radio" name="rw"> 质检遗漏</label>
<label><input type="radio" name="rw"> 客户其他原因</label>
</div><p class="muted">归因结果用于费用与信用结算:本次不计入 OPC 返工率。</p>`,
actions: [{ label: '确认归因', onClick: m => { m.remove(); toast('归因已记录:需求变更 · 客户承担返工费用'); } }],
});
}
function auditLog() {
openModal({
title: '下载审计日志(近 7 天)',
body: `<table>
<tr><th>时间</th><th>操作人</th><th>动作</th></tr>
<tr><td>09-11 14:02</td><td>OPC_00017 张设计</td><td>下载 首页改版_v2.psd</td></tr>
<tr><td>09-10 09:31</td><td>项目负责人</td><td>导出 交付清单</td></tr>
<tr><td>09-09 16:47</td><td>运营商质检员</td><td>下载 DLV_2026090901 源文件</td></tr>
</table>`,
});
}
</script>
</body>
</html>