一人公司/超级个体产业社区公共服务基础设施 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>
295 lines
15 KiB
HTML
295 lines
15 KiB
HTML
<!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="institution">
|
||
<header class="topnav" id="topnav"></header>
|
||
|
||
<div class="container">
|
||
<div class="page-title">培训机构管理</div>
|
||
<div class="page-sub" id="ins-sub"></div>
|
||
|
||
<!-- REQ-099 入驻资质 -->
|
||
<div class="card">
|
||
<h3>机构资质 <span class="req">REQ-099</span></h3>
|
||
<div id="ins-steps"></div>
|
||
<table style="margin-top:12px" id="qual-tbl"></table>
|
||
</div>
|
||
|
||
<div class="section grid grid-4" id="ins-stats"></div>
|
||
|
||
<!-- REQ-100/101 课程发布 -->
|
||
<div class="section">
|
||
<h2>课程与实训项目 <span class="req">REQ-100/101 · 上架审核 + 收费交易</span></h2>
|
||
<div class="card">
|
||
<table id="course-tbl"></table>
|
||
<p class="muted" style="margin-top:8px">收费项目经第三方支付通道结算,明示退费规则;财政补贴项目单独核算绩效(REQ-101/109/110)。
|
||
<button class="btn sm" style="float:right" onclick="newCourse()">+ 发布课程/实训项目</button></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- REQ-104 认证项目 -->
|
||
<div class="section">
|
||
<div class="two-col">
|
||
<div class="card">
|
||
<h3>认证项目 <span class="req">REQ-104</span></h3>
|
||
<table id="program-tbl"></table>
|
||
<p class="muted" style="margin-top:8px">认证标准经运营商审核,与平台能力标签体系关联;通过结果计入 OPC 能力档案。
|
||
<button class="btn sm" onclick="newCertProgram()">+ 新增认证项目</button></p>
|
||
</div>
|
||
<div class="card">
|
||
<h3>教学质量与退出机制 <span class="req">REQ-102</span></h3>
|
||
<ul class="list" id="ins-quality"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- REQ-105/108 证书颁发与查验 -->
|
||
<div class="section">
|
||
<h2>证书颁发与查验 <span class="req">REQ-105/108</span></h2>
|
||
<div class="card">
|
||
<table id="cert-tbl"></table>
|
||
<p class="muted" style="margin-top:8px">对外查验页:输入证书编号可验证颁发主体、等级、能力标签、有效期与防伪标识;吊销/复评同步能力档案。
|
||
<button class="btn sm" style="float:right" onclick="issueCert()">+ 颁发证书</button>
|
||
<button class="btn sm ghost" style="float:right;margin-right:6px" onclick="verifyPortal()">查验页演示</button></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- REQ-103 共建 + REQ-109 绩效 -->
|
||
<div class="section">
|
||
<div class="two-col">
|
||
<div class="card">
|
||
<h3>共建实训基地 <span class="req">REQ-103 · 公共服务轨</span></h3>
|
||
<ul class="list" id="cobuild-list"></ul>
|
||
</div>
|
||
<div class="card">
|
||
<h3>财政补贴绩效 <span class="req">REQ-109</span></h3>
|
||
<ul class="list" id="subsidy-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>
|
||
let certSeq = 43;
|
||
|
||
function renderPage(DB) {
|
||
const I = DB.institution;
|
||
const role = currentRole();
|
||
document.getElementById('ins-sub').textContent = role === 'operator'
|
||
? I.code + ' ' + I.name + ' · 运营商管理视角(机构入驻审核与运营监管) · 市场化 + 公共服务双轨'
|
||
: I.code + ' ' + I.name + ' · 入驻机构工作台 · 市场化 + 公共服务双轨';
|
||
document.getElementById('ins-steps').innerHTML = STEPS(I.steps);
|
||
|
||
const qa = {
|
||
license: `<button class="btn sm ghost" onclick="toast('办学许可证 No.教民xxxx · 有效期至 2028-12(演示)')">查看证照</button>`,
|
||
teacher: `<button class="btn sm ghost" onclick="addTeacher()">新增讲师</button>`,
|
||
dirs: `<button class="btn sm ghost" onclick="toast('已审方向:电商视觉 / AI 漫剧 / 设计基础')">方向明细</button>`,
|
||
credit: `<button class="btn sm ghost" onclick="toast('近 12 个月无行政处罚与投诉成立记录')">信用报告</button>`,
|
||
};
|
||
document.getElementById('qual-tbl').innerHTML =
|
||
`<tr><th>核验项</th><th>状态</th><th>操作</th></tr>` +
|
||
I.quals.map(q => `<tr><td>${q.item}</td><td>${B(q.status)}</td><td>${qa[q.action]}</td></tr>`).join('');
|
||
|
||
document.getElementById('ins-stats').innerHTML = I.stats.map(s =>
|
||
`<div class="card"><h3>${s.label}</h3><div class="num">${s.num}</div><p class="muted">${s.sub}</p></div>`).join('');
|
||
|
||
renderCourses(); renderPrograms(); renderCerts();
|
||
|
||
document.getElementById('ins-quality').innerHTML = I.quality.map(q =>
|
||
LI(q[0], Array.isArray(q[1]) ? B(q[1]) : q[1])).join('');
|
||
document.getElementById('cobuild-list').innerHTML = I.cobuild.map(c => LI(c[0], B(c[1]))).join('');
|
||
document.getElementById('subsidy-list').innerHTML = I.subsidy.map(s =>
|
||
LI(s[0], Array.isArray(s[1]) ? B(s[1]) : s[1])).join('');
|
||
}
|
||
|
||
function renderCourses() {
|
||
const acts = {
|
||
edit: c => `<button class="btn sm ghost" onclick="editCourse('${c.name}')">编辑</button> <button class="btn sm ghost" onclick="offShelf(this)">下架</button>`,
|
||
cobuild: c => `<button class="btn sm ghost" onclick="toast('共建课程由平台统一审核管理(REQ-103),修改需运营商复核')">共建管理</button>`,
|
||
withdraw: c => `<button class="btn sm ghost" onclick="withdraw(this)">撤回</button>`,
|
||
};
|
||
document.getElementById('course-tbl').innerHTML =
|
||
`<tr><th>项目</th><th>类型</th><th>属性</th><th>价格</th><th>报名</th><th>上架状态</th><th>操作</th></tr>` +
|
||
DB.institution.courses.map(c => `<tr><td>${c.name} <span class="muted">${c.code}</span><br>${c.tags.map(TAG).join('')}</td><td>${c.type}</td><td>${B(c.attr)}</td><td>${c.price}</td><td>${c.enroll}</td><td>${B(c.status)}</td><td>${acts[c.action](c)}</td></tr>`).join('');
|
||
}
|
||
|
||
function renderPrograms() {
|
||
const pa = {
|
||
exam: () => `<button class="btn sm ghost" onclick="toast('本期报名 42 人 · 已考 30 人 · 通过 23 人')">考情</button>`,
|
||
review: () => `<button class="btn sm ghost" onclick="toast('评审中 8 人 · 待发证 5 人')">评审</button>`,
|
||
};
|
||
document.getElementById('program-tbl').innerHTML =
|
||
`<tr><th>认证项目</th><th>方式</th><th>通过条件</th><th>关联标签</th><th>状态</th><th>操作</th></tr>` +
|
||
DB.institution.certPrograms.map(p => `<tr><td>${p.name} <span class="muted">${p.code}</span></td><td>${p.method}</td><td>${p.cond}</td><td>${TAG(p.tag)}</td><td>${B(p.status)}</td><td>${pa[p.action]()}</td></tr>`).join('');
|
||
}
|
||
|
||
function renderCerts() {
|
||
const ca = {
|
||
revoke: c => `<button class="btn sm ghost" onclick="revoke(this,'${c.no}')">吊销</button>`,
|
||
reassess: c => `<button class="btn sm" onclick="reassess(this,'${c.no}')">安排复评</button>`,
|
||
};
|
||
document.getElementById('cert-tbl').innerHTML =
|
||
`<tr><th>证书编号</th><th>持证人</th><th>认证项目</th><th>颁发日</th><th>有效期至</th><th>状态</th><th>操作</th></tr>` +
|
||
DB.institution.certs.map(c => `<tr><td>${c.no}</td><td>${c.holder}</td><td>${c.program}</td><td>${c.issued}</td><td>${c.expire}</td><td>${B(c.status)}</td><td>${c.action ? ca[c.action](c) : '—'}</td></tr>`).join('');
|
||
}
|
||
|
||
function newCourse() {
|
||
openModal({
|
||
title: '发布课程/实训项目(REQ-100)',
|
||
body: `
|
||
<div class="field"><label>项目名称</label><input id="nc-name" placeholder="如:AIGC 短视频实战班"></div>
|
||
<div class="field-row">
|
||
<div class="field"><label>类型</label><select id="nc-type"><option>课程</option><option>实训项目</option><option>认证项目</option></select></div>
|
||
<div class="field"><label>属性</label><select id="nc-attr"><option>收费</option><option>公益·财政补贴</option><option>共建</option></select></div>
|
||
</div>
|
||
<div class="field-row">
|
||
<div class="field"><label>价格(公益课填 0)</label><input id="nc-price" value="980"></div>
|
||
<div class="field"><label>目标等级</label><select><option>→L1</option><option>→L2</option><option>→L3</option><option>→L4</option></select></div>
|
||
</div>
|
||
<div class="field"><label>退费规则(收费必填)</label><input placeholder="如:开课前全退,开课后按课时比例"></div>`,
|
||
actions: [
|
||
{ label: '取消', cls: 'ghost' },
|
||
{ label: '提交审核', onClick: m => {
|
||
const n = document.getElementById('nc-name').value.trim();
|
||
if (!n) { toast('请填写项目名称', 'warn'); return; }
|
||
const type = document.getElementById('nc-type').value;
|
||
const attr = document.getElementById('nc-attr').value;
|
||
const price = document.getElementById('nc-price').value;
|
||
m.remove();
|
||
DB.institution.courses.push({ name: n, tags: ['待标注'], type, attr: [attr, 'pri'], price: '¥' + price, enroll: '—', status: ['运营商审核中', 'info'], action: 'withdraw' });
|
||
renderCourses();
|
||
toast('已提交,运营商审核通过后上架(REQ-100)');
|
||
} },
|
||
],
|
||
});
|
||
}
|
||
function editCourse(name) {
|
||
openModal({
|
||
title: '编辑 · ' + name,
|
||
body: `<div class="field"><label>价格</label><input value="¥1,280"></div>
|
||
<div class="field"><label>退费规则</label><input value="开课前全退,开课后按课时比例退"></div>
|
||
<p class="muted">价格/退费规则变更需运营商复核后生效。</p>`,
|
||
actions: [
|
||
{ label: '取消', cls: 'ghost' },
|
||
{ label: '保存并送审', onClick: m => { m.remove(); toast('修改已提交复核'); } },
|
||
],
|
||
});
|
||
}
|
||
function offShelf(btn) {
|
||
confirmDlg('下架确认', '下架后停止新报名,在读学员不受影响。确认下架?', '确认下架', () => {
|
||
const row = btn.closest('tr');
|
||
setBadge(row.cells[5], '已下架', 'gray');
|
||
btn.remove();
|
||
toast('课程已下架');
|
||
});
|
||
}
|
||
function withdraw(btn) {
|
||
confirmDlg('撤回送审', '撤回后可修改内容重新提交审核。', '确认撤回', () => {
|
||
const name = btn.closest('tr').cells[0].textContent.trim();
|
||
DB.institution.courses = DB.institution.courses.filter(c => !name.startsWith(c.name));
|
||
renderCourses();
|
||
toast('已撤回送审项目');
|
||
});
|
||
}
|
||
function addTeacher() {
|
||
openModal({
|
||
title: '新增讲师备案',
|
||
body: `<div class="field"><label>姓名</label><input></div>
|
||
<div class="field"><label>资质证明(职称/作品/行业认证)</label><input type="file"></div>`,
|
||
actions: [
|
||
{ label: '取消', cls: 'ghost' },
|
||
{ label: '提交备案', onClick: m => { m.remove(); toast('讲师备案已提交,计入师资力量核验'); } },
|
||
],
|
||
});
|
||
}
|
||
function newCertProgram() {
|
||
openModal({
|
||
title: '新增认证项目(REQ-104)',
|
||
body: `
|
||
<div class="field"><label>认证名称</label><input id="np-name" placeholder="如:AI 视觉质检师认证"></div>
|
||
<div class="field"><label>认证方式</label><select id="np-method"><option>考试+作品评审</option><option>实训评审</option><option>仅考试</option></select></div>
|
||
<div class="field"><label>通过条件</label><input id="np-cond" placeholder="如:笔试 ≥80 + 作品评审通过"></div>
|
||
<div class="field"><label>关联能力标签</label><input id="np-tag" placeholder="行业 / 服务 / 工具"></div>`,
|
||
actions: [
|
||
{ label: '取消', cls: 'ghost' },
|
||
{ label: '提交审核', onClick: m => {
|
||
const n = document.getElementById('np-name').value.trim();
|
||
if (!n) { toast('请填写认证名称', 'warn'); return; }
|
||
DB.institution.certPrograms.push({ name: n, method: document.getElementById('np-method').value, cond: document.getElementById('np-cond').value, tag: document.getElementById('np-tag').value || '待关联', status: ['运营商审核中', 'info'], action: 'review' });
|
||
m.remove(); renderPrograms();
|
||
toast('认证项目已提交运营商审核,通过后与能力标签体系关联');
|
||
} },
|
||
],
|
||
});
|
||
}
|
||
function issueCert() {
|
||
certSeq++;
|
||
const no = 'CERT_2026' + String(certSeq).padStart(4, '0');
|
||
openModal({
|
||
title: '颁发证书(REQ-105)',
|
||
body: `
|
||
<div class="field"><label>持证人</label><input placeholder="OPC_编号"></div>
|
||
<div class="field"><label>认证项目</label><select><option>电商视觉设计师认证(中级)</option><option>AI 漫剧制作认证</option></select></div>
|
||
<div class="field"><label>有效期</label><select><option>2 年</option><option>3 年</option></select></div>
|
||
<p class="muted">将生成证书编号 ${no},附防伪标识并开放对外查验。</p>`,
|
||
actions: [
|
||
{ label: '取消', cls: 'ghost' },
|
||
{ label: '确认颁发', onClick: m => {
|
||
m.remove();
|
||
DB.institution.certs.push({ no, holder: 'OPC_新学员', program: '电商视觉中级', issued: '2026-09-12', expire: '2028-09-12', status: ['有效 · 可对外查验', 'ok'], action: 'revoke' });
|
||
renderCerts();
|
||
toast(`证书 ${no} 已颁发并计入持证人能力档案`);
|
||
} },
|
||
],
|
||
});
|
||
}
|
||
function revoke(btn, no) {
|
||
confirmDlg('吊销证书', `确认吊销 ${no}?吊销原因将留痕并同步持证人能力档案(REQ-108)。`, '确认吊销', () => {
|
||
const c = DB.institution.certs.find(x => x.no === no);
|
||
if (c) { c.status = ['已吊销', 'err']; c.action = null; }
|
||
renderCerts();
|
||
toast(`${no} 已吊销,能力档案同步更新`, 'warn');
|
||
});
|
||
}
|
||
function reassess(btn, no) {
|
||
confirmDlg('安排复评', `为 ${no} 持证人安排到期复评?复评不通过将降级或吊销。`, '确认安排', () => {
|
||
const c = DB.institution.certs.find(x => x.no === no);
|
||
if (c) { c.status = ['复评安排中', 'info']; c.action = null; }
|
||
renderCerts();
|
||
toast('复评已排期,通知已发送持证人');
|
||
});
|
||
}
|
||
function verifyPortal() {
|
||
openModal({
|
||
title: '证书查验页(对外公开演示)',
|
||
body: `<div class="field"><label>证书编号</label><input id="vp-no" value="CERT_20260042"></div>`,
|
||
actions: [
|
||
{ label: '取消', cls: 'ghost' },
|
||
{ label: '查验', onClick: m => {
|
||
const no = document.getElementById('vp-no').value.trim();
|
||
m.remove();
|
||
const c = DB.institution.certs.find(x => x.no === no);
|
||
if (!c) { toast('未查询到该证书,请核对编号(REQ-105)', 'err'); return; }
|
||
openModal({ title: '查验结果 · ' + no, body: `<table>
|
||
<tr><th>颁发主体</th><td>${DB.institution.code} ${DB.institution.name}(平台入驻机构)</td></tr>
|
||
<tr><th>持证人</th><td>${c.holder}(脱敏)</td></tr>
|
||
<tr><th>认证项目</th><td>${c.program}</td></tr>
|
||
<tr><th>有效期</th><td>${c.issued} ~ ${c.expire}</td></tr>
|
||
<tr><th>状态</th><td>${B(c.status)}</td></tr>
|
||
</table>` });
|
||
} },
|
||
],
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|