Files
OPC/Demo/opc.html
T
freedakandDevin 13cad398dc feat: OPC 平台功能增强 v1.4 · 8 项需求变更实施
CHG-006 抢单订单池:OPC 工作台加抢单池(REQ-128/129)
CHG-004 政策落地:OPC 加算力券申领/核销+贷款+租金,运营商加政策跟踪(REQ-124/125/126)
CHG-001 组队模块:新增 team.html 组队协同页(REQ-119/120/121)
CHG-003 活动管理:运营工作台加活动创建/发布/报名管理(REQ-122/123)
CHG-005 OPC 财务分析:OPC 工作台加财务看板(REQ-127)
CHG-008 平台撮合归集:运营工作台加录入入口(REQ-130)
CHG-002 入口跳转:确认已正确(OPC→opc.html)
CHG-007 服务中心定位:明确为运营商服务资源管理

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

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

377 lines
18 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-128 抢单订单池 -->
<div class="section">
<h2>抢单订单池 <span class="req">REQ-128/129 · 标准化任务公开抢单 · 先到先得</span></h2>
<div class="card"><table id="grab-tbl"></table></div>
<p class="muted" style="margin-top:8px">运营商将标准化任务放入抢单池,OPC 按 SOP 标准自主抢单;抢单后锁定 30 分钟内确认接单,超时释放回池。</p>
</div>
<!-- REQ-124/125 政策落地:算力券 + 贷款 + 租金 -->
<div class="section">
<h2>政策落地 · 算力券 / 贷款 / 租金 <span class="req">REQ-124/125 · 全链路可见</span></h2>
<div class="three-col" style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">
<div class="card">
<h3>算力券申领与核销 <span class="req">REQ-124</span></h3>
<p class="muted">余额 <b id="vc-bal">¥8,000</b> / ¥10,000</p>
<table id="vc-tbl" style="margin-top:8px"></table>
<button class="btn sm" style="margin-top:8px" onclick="applyCompute()">申领算力券</button>
</div>
<div class="card">
<h3>贷款支持 <span class="req">REQ-125</span></h3>
<table id="loan-tbl"></table>
<button class="btn sm" style="margin-top:8px" onclick="applyLoan()">申请贷款</button>
</div>
<div class="card">
<h3>租金补贴</h3>
<table id="rent-tbl"></table>
</div>
</div>
</div>
<!-- REQ-127 OPC 财务分析 -->
<div class="section">
<h2>财务分析 <span class="req">REQ-127 · 接单收入 - 算力 - 社保 = 净收益</span></h2>
<div class="two-col">
<div class="card">
<h3>本月收支概览</h3>
<table id="fin-tbl"></table>
</div>
<div class="card">
<h3>财务建议</h3>
<ul class="list" id="fin-suggest"></ul>
</div>
</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();
renderGrab();
renderPolicySupport();
renderFinance();
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>`,
});
}
/* === 抢单订单池 === */
function renderGrab() {
const pool = DB.opc.grabPool || [];
document.getElementById('grab-tbl').innerHTML =
`<tr><th>订单号</th><th>类型</th><th>交付物</th><th>预算</th><th>截止</th><th>SOP</th><th>已抢单/上限</th><th>状态</th><th>操作</th></tr>` +
pool.map((o, i) => `<tr>
<td>${o.id}</td><td>${B(o.type)}</td><td>${o.items}</td><td>${o.budget}</td><td>${o.ddl}</td>
<td><code>${o.sop}</code></td><td>${o.count}</td><td>${B(o.status)}</td>
<td>${o.status[1] === 'ok' ? `<button class="btn sm" onclick="grabOrder(${i})">立即抢单</button>` : '<span class="muted">已满</span>'}</td>
</tr>`).join('');
}
function grabOrder(i) {
const o = DB.opc.grabPool[i];
confirmDlg('抢单确认', `${o.items} · ${o.budget} · 截止 ${o.ddl}。抢单后 30 分钟内确认接单,超时释放。`, '确认抢单', () => {
toast('抢单成功!请在 30 分钟内确认接单,否则任务释放回池');
o.count = o.count.split('/')[0] + '/' + o.count.split('/')[1];
const cur = parseInt(o.count) + 1;
o.count = cur + '/' + o.count.split('/')[1];
if (cur >= parseInt(o.count.split('/')[1])) o.status = ['已满', 'gray'];
renderGrab();
});
}
/* === 政策落地 === */
function renderPolicySupport() {
const P = DB.opc.policySupport;
if (!P) return;
// 算力券
document.getElementById('vc-bal').textContent = `¥${P.compute.balance.toLocaleString()}`;
document.getElementById('vc-tbl').innerHTML =
`<tr><th>券号</th><th>金额</th><th>关联</th><th>状态</th></tr>` +
P.compute.records.map(r => `<tr><td>${r.code}</td><td>${r.amount}</td><td class="muted">${r.target}</td><td>${B(r.status)}</td></tr>`).join('');
// 贷款
document.getElementById('loan-tbl').innerHTML =
`<tr><th>编号</th><th>类型</th><th>金额</th><th>利率</th><th>状态</th></tr>` +
P.loan.records.map(r => `<tr><td>${r.code}</td><td>${r.type}</td><td>${r.amount}</td><td>${r.rate}</td><td>${B(r.status)}</td></tr>`).join('');
// 租金
document.getElementById('rent-tbl').innerHTML =
`<tr><th>编号</th><th>类型</th><th>金额</th><th>状态</th></tr>` +
P.rent.records.map(r => `<tr><td>${r.code}</td><td>${r.type}</td><td>${r.amount}</td><td>${B(r.status)}</td></tr>`).join('');
}
function applyCompute() {
confirmDlg('申领算力券', '按细则可申领 ≤¥10,000/年。当前余额 ¥8,000,可追加申领 ¥2,000。', '确认申领', () => {
toast('算力券申领已提交,经信局审核后发放至券台账');
});
}
function applyLoan() {
openModal({
title: '申请贷款支持',
body: `<div class="field"><label>贷款类型</label><select><option>创业担保贷款(政府贴息 2%)</option><option>算力信用贷(年化 3.85%)</option><option>经营性贷款(年化 4.5%)</option></select></div>
<div class="field-row"><div class="field"><label>申请额度</label><input placeholder="如:¥20 万"></div>
<div class="field"><label>期限</label><select><option>6 个月</option><option selected>12 个月</option><option>24 个月</option></select></div></div>
<p class="muted">贷款申请将关联 OPC 信用档案与订单收入流水,运营商初审后推送合作金融机构。</p>`,
actions: [
{ label: '取消', cls: 'ghost' },
{ label: '提交申请', onClick: m => { m.remove(); toast('贷款申请已提交,运营商初审中'); } },
],
});
}
/* === 财务分析 === */
function renderFinance() {
const F = DB.opc.finance;
if (!F) return;
document.getElementById('fin-tbl').innerHTML =
`<tr><th>项目</th><th>金额</th><th>占比</th><th>说明</th></tr>` +
`<tr><td><b>接单收入</b></td><td><b>${F.income.total}</b></td><td>—</td><td>${F.income.orders} 单 · 均 ${F.income.avg}</td></tr>` +
F.costs.map(c => `<tr><td>${c.item}</td><td style="color:var(--danger)">-${c.amount}</td><td>${c.pct || '—'}%</td><td class="muted">${c.detail}</td></tr>`).join('') +
`<tr style="border-top:2px solid var(--primary)"><td><b>净收益</b></td><td><b style="color:var(--success)">${F.net}</b></td><td>—</td><td>税后</td></tr>`;
document.getElementById('fin-suggest').innerHTML = F.suggestions.map(s => `<li><span>${s}</span></li>`).join('');
}
</script>
</body>
</html>