Files
OPC/Demo/opc.html
T
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

258 lines
12 KiB
HTML
Raw 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 工作台 · OPC 公共服务平台 Demo</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body data-page="opc">
<header class="topnav" id="topnav"></header>
<div class="container">
<div class="page-title">OPC 工作台</div>
<div class="page-sub" id="opc-sub"></div>
<!-- REQ-013 五态入驻状态 -->
<div class="card">
<h3>入驻状态 <span class="req">REQ-013</span></h3>
<div id="opc-steps"></div>
<p class="muted" style="margin-top:10px">距离"优质供应商":复购率需 ≥50%(当前 62% ✓)、累计交付 ≥50 单(当前 38 单)、连续 90 天零投诉 ✓</p>
<p class="muted">支持自然人/个体户/一人公司/机构等主体类型(REQ-011);注销保留法定审计数据并匿名化个人信息(REQ-018)。</p>
</div>
<div class="section grid grid-4" id="opc-stats"></div>
<!-- REQ-019~021 能力档案与认证 -->
<div class="section">
<div class="two-col">
<div class="card">
<h3>能力档案 <span class="req">REQ-019/020</span></h3>
<div id="opc-profile"></div>
<button class="btn sm" style="margin-top:10px" onclick="uploadWork()">上传作品</button>
<button class="btn sm ghost" style="margin-top:10px" onclick="toast('能力名片已生成: opc.card/OPC_00017 (演示链接)')">生成对外能力名片</button>
<button class="btn sm ghost" style="margin-top:10px" onclick="identity()">实名认证信息</button>
<p class="muted" style="margin-top:6px">REQ-006 对外能力名片 · REQ-012/016/017 实名核验</p>
</div>
<div class="card">
<h3>能力认证 <span class="req">REQ-021/022/024/025</span></h3>
<div id="opc-cert"></div>
<a class="btn ghost sm" href="training.html" style="margin-top:10px">进入培训认证</a>
<button class="btn sm ghost" style="margin-top:10px" onclick="verifyCert()">查验证书</button>
<button class="btn sm ghost" style="margin-top:10px" onclick="scoreBasis()">评分依据</button>
<button class="btn sm ghost" style="margin-top:10px" onclick="toast('复评申请已登记,运营商将在到期前 30 天安排复评')">预约复评</button>
</div>
</div>
</div>
<!-- REQ-031 订单池与智能推荐 -->
<div class="section">
<h2>订单池 · 推荐任务 <span class="req">REQ-026/031 · 能力档案作为派单/资源券/信用输入,按标签/等级/表现匹配</span></h2>
<div class="card"><table id="pool-tbl"></table></div>
</div>
<!-- REQ-043/046 收入与结算 -->
<div class="section">
<h2>收入与结算 <span class="req">REQ-043/046 · 分账核算 + 保证金托管</span></h2>
<div class="two-col">
<div class="card">
<h3>结算总览</h3>
<ul class="list" id="stl-summary"></ul>
<button class="btn sm" style="margin-top:10px" onclick="withdraw()">申请提现</button>
</div>
<div class="card">
<h3>结算单 <span class="req">SETL_ 编号</span></h3>
<table id="stl-tbl"></table>
</div>
</div>
</div>
<!-- REQ-023 质量预警 + REQ-044 评价 -->
<div class="section">
<div class="two-col">
<div class="card">
<h3>质量与信用 <span class="req">REQ-023/050</span></h3>
<ul class="list" id="opc-quality"></ul>
</div>
<div class="card">
<h3>最新客户评价 <span class="req">REQ-044</span></h3>
<ul class="list" id="opc-reviews"></ul>
</div>
</div>
</div>
</div>
<div class="footer">OPC 公共服务平台 Demo · OPC 工作台</div>
<script src="assets/app.js"></script>
<script src="assets/store.js"></script>
<script>
let avail = { h: '40h', cap: 25 };
function renderPage(DB) {
const O = DB.opc;
const role = currentRole();
document.getElementById('opc-sub').textContent = role === 'operator'
? `${O.id} · ${O.name} · 运营商管理视角(查看 OPC 档案与运营状态)`
: `${O.id} · ${O.name} · 入驻五态与能力档案一览`;
document.getElementById('opc-steps').innerHTML = STEPS(O.steps);
document.getElementById('opc-stats').innerHTML = O.stats.map(s =>
`<div class="card"><h3>${s.label}</h3><div class="num">${s.num}</div><p class="muted">${s.sub}</p></div>`).join('');
const p = O.profile;
document.getElementById('opc-profile').innerHTML = `
<p class="muted">五层标签:${p.tags}</p>
<div>${p.skills.map(TAG).join('')}</div>
<table style="margin-top:12px">
<tr><th>档案项</th><th>内容</th></tr>
${p.rows.map(r => `<tr><td>${r[0]}</td><td>${r[1]}</td></tr>`).join('')}
<tr><td>可接单时间</td><td id="avail-cell"></td></tr>
<tr><td>保密/合规</td><td><span class="badge ok">NDA 已签</span> <span class="badge ok">合规培训通过</span></td></tr>
</table>`;
renderAvail();
const c = O.cert;
document.getElementById('opc-cert').innerHTML = `
<div class="num">${c.level} <small>${c.desc}</small></div>
<p class="muted" style="margin:8px 0">证书 ${c.no} · 有效期至 ${c.expire}</p>
${BAR(c.progress, 'ok')}
<p class="muted" style="margin-top:6px">${c.next}</p>`;
renderPool();
document.getElementById('stl-summary').innerHTML = O.settlement.summary.map(s => LI(s[0], s[1])).join('');
document.getElementById('stl-tbl').innerHTML =
`<tr><th>结算单</th><th>关联订单</th><th>金额</th><th>状态</th></tr>` +
O.settlement.rows.map(r => `<tr><td>${r.code}</td><td>${r.order}</td><td>${r.amount}</td><td>${B(r.status)}</td></tr>`).join('');
document.getElementById('opc-quality').innerHTML = O.quality.map(q => LI(q[0], B(q[1]))).join('');
document.getElementById('opc-reviews').innerHTML = O.reviews.map(r =>
`<li><span>${r[0]}</span><span class="badge ok">${r[1]}</span></li>`).join('');
}
function renderAvail() {
document.getElementById('avail-cell').innerHTML =
`每周 ${avail.h} · 月产能约 ${avail.cap} 任务件 <button class="btn sm ghost" onclick="editAvail()">调整</button>`;
}
function renderPool() {
const acts = {
quote: o => `<button class="btn sm" onclick="quote(this,'${o.id}')">参与报价</button>`,
claim: o => `<button class="btn sm" onclick="claim(this,'${o.id}')">领取任务</button>`,
view: o => `<a class="btn sm ghost" href="order.html">查看</a>`,
};
document.getElementById('pool-tbl').innerHTML =
`<tr><th>订单</th><th>类型</th><th>交付物</th><th>预算</th><th>截止</th><th>匹配度</th><th>操作</th></tr>` +
DB.opc.orderPool.map(o => `<tr><td>${o.id}</td><td>${B(o.type)}</td><td>${o.items}</td><td>${o.budget}</td><td>${o.ddl}</td><td>${B(o.match)}</td><td>${acts[o.action](o)}</td></tr>`).join('');
}
function claim(btn, id) {
const o = DB.opc.orderPool.find(x => x.id === id);
confirmDlg('领取派单任务', `${o.items} · ${o.budget} · 截止 ${o.ddl}。领取后将按 SOP 提交清单执行并计入产能。`, '确认领取', () => {
btn.outerHTML = '<span class="badge ok">已领取 · 进入任务协同</span>';
toast('任务已领取,可在「订单与协同」查看 SOP 与提交清单');
});
}
function quote(btn, id) {
const o = DB.opc.orderPool.find(x => x.id === id);
openModal({
title: '参与询价报价 · ' + id,
body: `
<div class="field"><label>报价金额(客户预算 ${o.budget})</label><input id="q-price" value="22500"></div>
<div class="field"><label>交付周期</label><select><option>10 天</option><option>14 天</option><option>20 天</option></select></div>
<div class="field"><label>方案说明</label><textarea placeholder="交付物拆分、样例、排产说明"></textarea></div>
<p class="muted">小样试做:本单要求小样,报价后进入小样环节(REQ-032)。</p>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '提交报价', onClick: m => {
m.remove();
btn.outerHTML = '<span class="badge info">已报价 ¥22,500</span>';
toast('报价已提交,等待运营商组织评审');
} },
],
});
}
function uploadWork() {
openModal({
title: '上传作品',
body: `
<div class="field"><label>作品名称</label><input placeholder="如:夏季新品主图系列"></div>
<div class="field"><label>关联能力标签</label><input value="跨境电商 / 视觉设计 / 商品场景图"></div>
<div class="field"><label>客户授权说明(REQ-004)</label><select><option>已获客户授权展示</option><option>仅展示脱敏能力描述</option></select></div>
<div class="field"><label>文件</label><input type="file"></div>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '提交审核', onClick: m => { m.remove(); toast('作品已提交,运营商审核通过后进入对外名片'); } },
],
});
}
function editAvail() {
openModal({
title: '调整可接单时间',
body: `<div class="field-row">
<div class="field"><label>每周可投入</label><select id="a-h"><option>20h</option><option selected>40h</option><option>60h</option></select></div>
<div class="field"><label>月产能(任务件)</label><input id="a-cap" value="${avail.cap}"></div></div>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '保存', onClick: m => {
avail.h = document.getElementById('a-h').value;
avail.cap = document.getElementById('a-cap').value;
m.remove(); renderAvail();
toast('可接单时间已更新,派单匹配将按新产能计算');
} },
],
});
}
function identity() {
openModal({
title: '实名认证信息(REQ-012/017)',
body: `<table>
<tr><th>主体类型</th><td>个体工商户(个人通道已完成实名+支付验证)</td></tr>
<tr><th>手机核验</th><td><span class="badge ok">已通过</span> 138****6688</td></tr>
<tr><th>身份证核验</th><td><span class="badge ok">已通过</span> 3301**********1234</td></tr>
<tr><th>营业执照</th><td><span class="badge ok">已核验</span> 统一社会信用代码 92****XU</td></tr>
<tr><th>法人/操作人/收款主体</th><td>三者一致 · 已关联</td></tr>
<tr><th>变更触发</th><td>法人、收款账户变更将自动触发重新核验(REQ-016)</td></tr>
</table>`,
actions: [
{ label: '关闭', cls: 'ghost' },
{ label: '模拟收款账户变更', cls: 'danger', onClick: m => { m.remove(); toast('检测到收款主体变更:已触发重新核验流程,期间接单权限临时锁定(REQ-016)', 'warn'); } },
],
});
}
function scoreBasis() {
openModal({
title: '认证评分依据 · L3(REQ-022/024)',
body: `<p class="muted">评分由系统自动计算,全部依据可追溯至业务单据;不以缴费培训作为认证条件。</p>
<table>
<tr><th>依据来源</th><th>指标</th><th>得分贡献</th></tr>
<tr><td>ORDER_2026071501 验收记录</td><td>一次验收通过率 95%</td><td>+28</td></tr>
<tr><td>DLV_ 质检批次 ×14</td><td>三级质检通过率 / 返工率 4%</td><td>+24</td></tr>
<tr><td>TRP_20260802 实训评审</td><td>真实单实训 95 分</td><td>+18</td></tr>
<tr><td>EVAL_ 客户评价 ×23</td><td>均分 4.9 / 复购率 62%</td><td>+20</td></tr>
<tr><td>CERT_20260042 机构认证</td><td>中级能力附加</td><td>+10</td></tr>
<tr><th>综合</th><th>L3 阈值 ≥85</th><th>96 · 达标</th></tr>
</table>`,
});
}
function withdraw() {
confirmDlg('申请提现', '可提现余额 ¥46,800,提至已核验收款账户(REQ-012 实名关联)。预计 T+1 到账。', '确认提现', () => {
toast('提现申请已提交财务通道,到账后将生成结算回单');
});
}
function verifyCert() {
const c = DB.opc.cert;
openModal({
title: '证书查验 · ' + c.no,
body: `<table>
<tr><th>证书编号</th><td>${c.no}</td></tr>
<tr><th>颁发主体</th><td>OPC 公共服务平台</td></tr>
<tr><th>认证等级</th><td>${c.level} · ${c.desc}</td></tr>
<tr><th>能力标签</th><td>跨境电商 / 视觉设计 / 商品场景图</td></tr>
<tr><th>有效期</th><td>2026-04-01 ~ ${c.expire}</td></tr>
<tr><th>状态</th><td><span class="badge ok">有效 · 防伪校验通过</span></td></tr>
</table>`,
});
}
</script>
</body>
</html>