feat(定时任务): 新增第六个 HTML 模板 no.6(只展示)
- no.6.html(266KB)复制进 templates/no6/;数据用 `const DATA = {…}`(= 前有空格),
当前引擎正则要求 const DATA=,不匹配,故先注册为 convertible=False 只展示
- 前端下拉/参考列表自动出现 no.6
- 测试覆盖 no.6 存在/只展示/文件可读
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c0af23aefe
commit
4f62703477
2 files changed
+335
No files matched your search
+9
@@ -166,6 +166,15 @@ TEMPLATES: tuple[TemplateSpec, ...] = (
|
||||
# 故暂注册为只展示;接转换需扩展行注入正则。
|
||||
convertible=False,
|
||||
),
|
||||
TemplateSpec(
|
||||
id="no6",
|
||||
name="no.6",
|
||||
description="",
|
||||
file="no6/no6.html",
|
||||
# 数据用 `const DATA = {…}`(= 前有空格,当前引擎正则要求 const DATA=),不匹配,
|
||||
# 故暂注册为只展示;接转换需放宽行注入正则。
|
||||
convertible=False,
|
||||
),
|
||||
)
|
||||
|
||||
_TEMPLATES_BY_ID: dict[str, TemplateSpec] = {t.id: t for t in TEMPLATES}
|
||||
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>台湾后备力量与全社会防卫韧性专题库 v2</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg0:#4a1013;
|
||||
--bg1:#6b181c;
|
||||
--bg2:#8a2428;
|
||||
--card:#fffaf7;
|
||||
--card2:#fff5ee;
|
||||
--ink:#211617;
|
||||
--muted:#6f5959;
|
||||
--line:#ead5cc;
|
||||
--line2:#d6b8ac;
|
||||
--accent:#a83b3f;
|
||||
--accent2:#f3d3c5;
|
||||
--gold:#b88a45;
|
||||
--green:#617a51;
|
||||
--blue:#496f8f;
|
||||
--shadow:0 18px 50px rgba(40,0,0,.22);
|
||||
--radius:18px;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;font-family:"Microsoft YaHei","PingFang SC",Arial,sans-serif;color:var(--ink);background:linear-gradient(145deg,var(--bg0),var(--bg1) 45%,#3b0d10);}
|
||||
body{min-height:100vh;}
|
||||
.header{position:relative;overflow:hidden;padding:34px 34px 26px;color:#fff;border-bottom:1px solid rgba(255,255,255,.18);}
|
||||
.header:before{content:"";position:absolute;inset:-80px -80px auto auto;width:300px;height:300px;border-radius:50%;background:rgba(255,255,255,.08)}
|
||||
.header:after{content:"";position:absolute;left:35px;bottom:-65px;width:180px;height:180px;border:1px solid rgba(255,255,255,.18);border-radius:50%;}
|
||||
.header-inner{position:relative;z-index:2;max-width:1440px;margin:0 auto;}
|
||||
.kicker{display:inline-flex;align-items:center;gap:8px;border:1px solid rgba(255,255,255,.32);border-radius:999px;padding:7px 12px;font-size:12px;letter-spacing:.08em;background:rgba(255,255,255,.08)}
|
||||
.kicker .dot{width:8px;height:8px;background:#f7d4c3;border-radius:50%;display:inline-block;}
|
||||
h1{margin:18px 0 8px;font-size:34px;letter-spacing:.04em;line-height:1.25}
|
||||
.subtitle{max-width:1060px;color:#f7ddd3;font-size:15px;line-height:1.75}
|
||||
.boundary{margin-top:16px;display:flex;flex-wrap:wrap;gap:10px;}
|
||||
.boundary span{background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.18);padding:7px 10px;border-radius:12px;font-size:12px;color:#fff1eb}
|
||||
.shell{max-width:1440px;margin:0 auto;padding:20px 24px 42px;}
|
||||
.panel{background:rgba(255,250,247,.96);border:1px solid rgba(255,255,255,.45);box-shadow:var(--shadow);border-radius:24px;overflow:hidden;}
|
||||
.controls{padding:18px;display:grid;grid-template-columns:2.1fr 1fr 1fr 1fr auto;gap:12px;border-bottom:1px solid var(--line);background:linear-gradient(180deg,#fffaf8,#fff3ec);}
|
||||
.input,select{width:100%;border:1px solid var(--line2);border-radius:14px;background:#fff;color:var(--ink);font-size:14px;padding:12px 13px;outline:none;}
|
||||
.input:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(168,59,63,.12)}
|
||||
.btn{border:1px solid var(--accent);background:var(--accent);color:#fff;border-radius:14px;padding:11px 14px;font-weight:700;cursor:pointer;white-space:nowrap}
|
||||
.btn.secondary{background:#fff;color:var(--accent)}
|
||||
.btn:hover{filter:brightness(.98)}
|
||||
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;padding:14px 18px;border-bottom:1px solid var(--line);background:#fffaf7;}
|
||||
.stat{border:1px solid var(--line);border-radius:16px;padding:12px;background:#fff;}
|
||||
.stat b{display:block;font-size:22px;color:var(--accent);}
|
||||
.stat span{font-size:12px;color:var(--muted)}
|
||||
.tabs{display:flex;gap:8px;overflow-x:auto;padding:12px 14px;border-bottom:1px solid var(--line);background:#fbefe7;}
|
||||
.tab{border:1px solid var(--line2);background:#fff;border-radius:999px;padding:10px 14px;cursor:pointer;white-space:nowrap;font-weight:700;color:#5a3d3e;}
|
||||
.tab.active{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 8px 18px rgba(168,59,63,.22)}
|
||||
.tab small{display:block;font-weight:500;opacity:.75;font-size:10px;margin-top:2px}
|
||||
.tab-head{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;padding:18px 18px 10px;}
|
||||
.tab-title h2{margin:0;font-size:24px;color:#321b1d}
|
||||
.tab-title p{margin:6px 0 0;color:var(--muted);line-height:1.7}
|
||||
.tab-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
|
||||
.toolbar{padding:10px 18px 16px;display:grid;grid-template-columns:2fr repeat(3,1fr);gap:10px;border-bottom:1px solid var(--line);}
|
||||
.subfilters{display:flex;flex-wrap:wrap;gap:9px;padding:13px 18px;background:#fffaf7;border-bottom:1px solid var(--line);}
|
||||
.chip{border:1px solid var(--line2);background:#fff;border-radius:999px;padding:8px 12px;cursor:pointer;font-size:13px;color:#5d4444;}
|
||||
.chip.active{background:#3f2225;color:#fff;border-color:#3f2225}
|
||||
.content{padding:18px;}
|
||||
.flow{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:18px;}
|
||||
.flow-node{background:linear-gradient(180deg,#fff,#fff5ee);border:1px solid var(--line);border-radius:18px;padding:14px;position:relative;min-height:92px;}
|
||||
.flow-node:after{content:"→";position:absolute;right:-13px;top:34px;color:var(--accent);font-weight:900;}
|
||||
.flow-node:last-child:after{content:""}
|
||||
.flow-node b{display:block;color:var(--accent);font-size:15px;margin-bottom:6px}
|
||||
.flow-node span{font-size:12px;color:var(--muted);line-height:1.5}
|
||||
.cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;}
|
||||
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:16px;box-shadow:0 8px 20px rgba(76,25,18,.06);display:flex;flex-direction:column;gap:12px;min-height:230px;}
|
||||
.card:hover{border-color:#c69d8d;transform:translateY(-1px);transition:.15s ease}
|
||||
.card h3{margin:0;font-size:18px;line-height:1.35;color:#2b1719}
|
||||
.meta-row{display:flex;flex-wrap:wrap;gap:7px;}
|
||||
.badge{font-size:12px;border:1px solid var(--line2);background:#fff;border-radius:999px;padding:5px 8px;color:#684d4d;}
|
||||
.badge.red{background:#fff0ee;border-color:#e5b6ae;color:#913135}
|
||||
.badge.green{background:#f1f6ed;border-color:#c6d5bf;color:#516e3d}
|
||||
.badge.blue{background:#eff6fb;border-color:#bfd2df;color:#426886}
|
||||
.summary{color:#604a4a;font-size:13px;line-height:1.68;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;}
|
||||
.section-mini{background:#fff;border:1px solid var(--line);border-radius:14px;padding:10px;}
|
||||
.section-mini b{font-size:12px;color:#8c3337;display:block;margin-bottom:6px}
|
||||
.section-mini ul{margin:0;padding-left:18px;color:#5e4a4a;font-size:12px;line-height:1.55}
|
||||
.card-footer{margin-top:auto;display:flex;justify-content:space-between;gap:10px;align-items:center}
|
||||
.link-btn{border:0;background:transparent;color:var(--accent);font-weight:800;cursor:pointer;padding:6px 0}
|
||||
.warning{font-size:12px;color:#805f40}
|
||||
.table-wrap{overflow:auto;border:1px solid var(--line);border-radius:18px;background:#fff}
|
||||
table{width:100%;border-collapse:collapse;font-size:13px;}
|
||||
th{position:sticky;top:0;background:#f5e3d9;color:#4b292a;text-align:left;padding:12px;border-bottom:1px solid var(--line2);white-space:nowrap}
|
||||
td{padding:11px 12px;border-bottom:1px solid var(--line);vertical-align:top}
|
||||
tr:hover td{background:#fff8f4}
|
||||
.timeline{position:relative;padding-left:26px;display:grid;gap:14px}
|
||||
.timeline:before{content:"";position:absolute;left:9px;top:5px;bottom:5px;width:2px;background:#d9b8ac}
|
||||
.titem{position:relative;background:#fff;border:1px solid var(--line);border-radius:18px;padding:15px;}
|
||||
.titem:before{content:"";position:absolute;left:-24px;top:18px;width:12px;height:12px;background:var(--accent);border-radius:50%;border:3px solid #fff}
|
||||
.empty{text-align:center;color:var(--muted);padding:48px;border:1px dashed var(--line2);border-radius:18px;background:#fffaf7}
|
||||
.modal-mask{position:fixed;inset:0;background:rgba(32,10,10,.62);display:none;align-items:center;justify-content:center;z-index:20;padding:26px;}
|
||||
.modal{width:min(1060px,96vw);max-height:90vh;overflow:auto;background:#fffaf7;border-radius:24px;box-shadow:0 30px 90px rgba(0,0,0,.45);border:1px solid #fff;}
|
||||
.modal-head{position:sticky;top:0;background:linear-gradient(180deg,#fffaf7,#fff2ea);border-bottom:1px solid var(--line);padding:18px;z-index:2;display:flex;justify-content:space-between;gap:14px;align-items:flex-start}
|
||||
.modal-head h2{margin:0;font-size:24px}
|
||||
.close{border:1px solid var(--line2);background:#fff;border-radius:12px;font-size:20px;width:40px;height:40px;cursor:pointer}
|
||||
.modal-body{padding:18px;display:grid;gap:14px}
|
||||
.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
|
||||
.detail-block{background:#fff;border:1px solid var(--line);border-radius:16px;padding:14px}
|
||||
.detail-block h4{margin:0 0 10px;color:#8b3035;font-size:15px}
|
||||
.detail-block p{margin:0;color:#4f3b3b;line-height:1.75}
|
||||
.detail-block ul{margin:0;padding-left:20px;color:#4f3b3b;line-height:1.65}
|
||||
.source-list a{color:#8b3035;text-decoration:none;border-bottom:1px dotted #8b3035}
|
||||
.source-item{padding:9px 0;border-bottom:1px solid var(--line)}
|
||||
.source-item:last-child{border-bottom:0}
|
||||
.notice{border:1px solid #e2bd9a;background:#fff6e8;color:#74512b;border-radius:14px;padding:12px;line-height:1.6}
|
||||
@media(max-width:1100px){.controls{grid-template-columns:1fr 1fr}.toolbar{grid-template-columns:1fr 1fr}.cards{grid-template-columns:repeat(2,1fr)}.stats{grid-template-columns:repeat(3,1fr)}.flow{grid-template-columns:repeat(2,1fr)}.flow-node:after{display:none}}
|
||||
@media(max-width:720px){.header{padding:26px 18px}h1{font-size:26px}.shell{padding:14px}.controls,.toolbar{grid-template-columns:1fr}.cards{grid-template-columns:1fr}.stats{grid-template-columns:repeat(2,1fr)}.detail-grid{grid-template-columns:1fr}.flow{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header-inner">
|
||||
<div class="kicker"><span class="dot"></span>公开资料专题库 · v2 综合重构版</div>
|
||||
<h1>台湾后备力量与全社会防卫韧性专题库</h1>
|
||||
<div class="subtitle">本版根据最新栏目思路重新生成:不新增栏目12,而是将“军种官科、装备类别、晋任制度、教召权益、退辅福利、制度短板”全部补充进「军方后备体系」栏目,形成更完整的军方后备主体数据库。</div>
|
||||
<div class="boundary"><span>仅展示公开资料</span><span>不展示营区坐标、集结点、动员路线</span><span>不展示具体装备数量与仓储位置</span><span>不展示个人资料</span></div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="shell">
|
||||
<div class="panel">
|
||||
<div class="controls">
|
||||
<input id="globalSearch" class="input" placeholder="全局搜索:概念、组织、法规、教召、装备、福利、争议……" />
|
||||
<select id="circleFilter"><option value="">全部圈层</option></select>
|
||||
<select id="reliabilityFilter"><option value="">全部可信度</option></select>
|
||||
<select id="displayFilter"><option value="">全部展示等级</option></select>
|
||||
<button id="resetBtn" class="btn secondary">重置</button>
|
||||
</div>
|
||||
<div class="stats" id="stats"></div>
|
||||
<nav class="tabs" id="tabs"></nav>
|
||||
<div class="tab-head">
|
||||
<div class="tab-title"><h2 id="tabTitle"></h2><p id="tabSubtitle"></p></div>
|
||||
<div class="tab-actions"><button class="btn secondary" id="exportBtn">导出当前筛选 JSON</button><button class="btn" id="topBtn">回到顶部</button></div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input id="localSearch" class="input" placeholder="当前栏目内搜索……" />
|
||||
<select id="typeFilter"><option value="">全部类型</option></select>
|
||||
<select id="agencyFilter"><option value="">全部机关/机构</option></select>
|
||||
<select id="yearFilter"><option value="">全部年份</option></select>
|
||||
</div>
|
||||
<div class="subfilters" id="subfilters"></div>
|
||||
<main class="content" id="content"></main>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-mask" id="modalMask">
|
||||
<div class="modal">
|
||||
<div class="modal-head"><div><h2 id="modalTitle"></h2><div class="meta-row" id="modalBadges"></div></div><button class="close" id="closeModal">×</button></div>
|
||||
<div class="modal-body" id="modalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const DATA = {"meta": {"title": "台湾后备力量与全社会防卫韧性专题库", "version": "v2.0-integrated", "language": "zh-CN", "lastUpdated": "2026-06-24", "description": "围绕台湾后备军人、后备部队、教召制度、全民防卫动员、民防体系、社会韧性、地方执行、研究成果、舆情争议、装备类别、军种官科、晋任制度与退辅福利建立的公开资料专题库。", "displayBoundary": ["仅展示公开资料、制度信息、政策分析、社会影响与来源审查。", "不展示具体营区坐标、集结地点、动员路线、个人名单、具体战术部署、关键设施脆弱点。", "社交平台资料仅作为舆情样本,不代表整体民意。"]}, "tabs": [{"id": "conceptDefinitions", "name": "概念界定", "subtitle": "官方定义、研究边界与三圈模型", "viewType": "cards", "filters": ["概念层级", "对应圈层", "是否官方定义", "可信度"]}, {"id": "militaryReserveSystem", "name": "军方后备体系", "subtitle": "组织体系、后备人员、军种官科、装备类别、晋任制度、权益福利、退辅制度与制度短板", "viewType": "military-integrated", "filters": ["二级分类", "对象类型", "层级", "可信度", "展示等级"], "searchFields": ["名称", "类别", "摘要", "标签", "对象类型", "二级分类", "军种", "官科兵科", "公开数据", "制度短板", "社会争议"]}, {"id": "trainingAndMobilization", "name": "教召训练", "subtitle": "教召制度、14天新制、训练流程、权益与争议", "viewType": "timeline-cards", "filters": ["制度类型", "适用对象", "年份", "争议等级", "可信度"]}, {"id": "policyEvolution", "name": "政策沿革", "subtitle": "后备改革时间线、改革逻辑与政策关键词", "viewType": "timeline", "filters": ["年份", "政策类型", "政策主体", "核心关键词", "可信度"]}, {"id": "legalFramework", "name": "法规图谱", "subtitle": "兵役、后备管理、召集、动员、民防、灾防法规", "viewType": "law-table", "filters": ["法规类型", "主管机关", "对应圈层", "涉及地方政府", "可信度"]}, {"id": "civilDefensePublicSafety", "name": "民防公共安全", "subtitle": "民防、警消、替代役、医疗、工程与公用事业支援", "viewType": "matrix-cards", "filters": ["对象类型", "主管机关", "功能类型", "对应圈层", "可信度"]}, {"id": "societalResilienceNetwork", "name": "社会韧性网络", "subtitle": "民间组织、社区、学校、企业、宗教慈善、媒体与资安网络", "viewType": "network-cards", "filters": ["组织类型", "是否官方组织", "政治标签", "可信度"]}, {"id": "localImplementation", "name": "地方执行", "subtitle": "县市差异、地方动员、民防、灾防与城镇韧性演习", "viewType": "region-cards", "filters": ["区域类型", "县市", "演习年份", "主要议题", "可信度"]}, {"id": "researchExpertViews", "name": "研究成果", "subtitle": "官方报告、智库研究、学术论文、媒体深度报道与国际比较", "viewType": "research-list", "filters": ["资料类型", "机构", "年份", "主题", "可信度"]}, {"id": "publicOpinionControversies", "name": "舆情争议", "subtitle": "教召、女性后备、民防政治化、企业成本、社交平台反应", "viewType": "issue-cards", "filters": ["议题类型", "平台", "情绪标签", "影响人群", "可信度"]}, {"id": "sourceAudit", "name": "来源审查", "subtitle": "来源分级、可信度、交叉验证与展示限制", "viewType": "source-table", "filters": ["来源类型", "可信度评级", "是否原始来源", "是否交叉验证", "可公开展示等级"]}], "sharedDictionaries": {"circles": ["第一圈:军方后备力量", "第二圈:守备民防与公共安全支援", "第三圈:社会韧性网络", "跨圈"], "reliabilityLevels": ["高", "中高", "中", "中低", "低"], "sourceRatings": ["A1", "A2", "B1", "B2", "C1", "C2", "D"], "displayLevels": ["可公开", "摘要展示", "不展示细节"], "controversyTypes": ["制度公平", "民生成本", "训练成效", "政治标签", "社会军事化", "女性后备", "企业负担", "地方执行", "假讯息"], "militarySubcategories": ["全部", "组织体系", "后备人员", "军种官科", "装备类别", "晋任制度", "权益福利", "退辅制度", "制度短板"]}, "conceptDefinitions": [{"ID": "concept_001", "名称": "后备军人", "类别": "官方概念", "摘要": "依法转入后备役并接受后备管理的人员,是后备力量专题的狭义核心。", "标签": ["兵役", "后备役", "后备管理"], "对应圈层": "第一圈:军方后备力量", "可信度": "高", "展示等级": "可公开", "资料来源": [{"标题": "《兵役法》", "类型": "法规", "URL": "https://law.moj.gov.tw/LawClass/LawAll.aspx?PCode=F0040001", "可信度": "A1"}, {"标题": "《后备军人管理规则》", "类型": "法规", "URL": "httpLine truncated
|
||||
const TAB_MAP = {
|
||||
conceptDefinitions:'conceptDefinitions', militaryReserveSystem:'militaryReserveSystem', trainingAndMobilization:'trainingAndMobilization',
|
||||
policyEvolution:'policyEvolution', legalFramework:'legalFramework', civilDefensePublicSafety:'civilDefensePublicSafety', societalResilienceNetwork:'societalResilienceNetwork',
|
||||
localImplementation:'localImplementation', researchExpertViews:'researchExpertViews', publicOpinionControversies:'publicOpinionControversies', sourceAudit:'sourceAudit'
|
||||
};
|
||||
const state = { tab:'conceptDefinitions', global:'', local:'', circle:'', reliability:'', display:'', type:'', agency:'', year:'', sub:'全部' };
|
||||
const militarySubs = ['全部','组织体系','后备人员','军种官科','装备类别','晋任制度','权益福利','退辅制度','制度短板'];
|
||||
function $(id){return document.getElementById(id)}
|
||||
function arr(v){ if(!v) return []; if(Array.isArray(v)) return v; if(typeof v==='object') return Object.values(v).flatMap(arr); return [String(v)]; }
|
||||
function txt(v){ return arr(v).join(' '); }
|
||||
function titleOf(item){ return item['名称']||item['标题']||item['法规名称']||item['组织名称']||item['县市']||item['议题名称']||item['资料标题']||item['sourceTitle']||item.name||item.title||'未命名条目'; }
|
||||
function summaryOf(item){ return item['摘要']||item['定义或说明']||item['核心目的']||item['触发原因']||item['核心观点']?.[0]||item['description']||item['研究价值']||item['制度短板']?.[0]||'点击查看详情。'; }
|
||||
function categoryOf(item){ return item['二级分类']||item['对象类型']||item['类别']||item['资料类型']||item['事件类型']||item['组织类型']||item['法规类型']||item['区域类型']||''; }
|
||||
function circleOf(item){ return item['对应圈层']||item['相关圈层']||item['relatedCircle']||item['circle']||''; }
|
||||
function reliabilityOf(item){ return item['可信度']||item['可信度评级']||item['credibilityRating']||item['reliability']||''; }
|
||||
function displayOf(item){ return item['展示等级']||item['可公开展示等级']||item['displayLevel']||''; }
|
||||
function yearOf(item){ const s=txt([item['发布时间'],item['发表时间'],item['时间'],item['年份'],item['事件时间'],item['更新时间'],item['publishYear'],item['year']]); const m=s.match(/20\d{2}|19\d{2}|11[0-9]年|民国\d+年/); return m?m[0]:''; }
|
||||
function agencyOf(item){ return txt(item['主管机关']||item['competentAgency']||item['主管或关联机关']||item['机构']||item['institution']||item['执行机关']||item['权责机关']).split(' / ')[0]; }
|
||||
function itemText(item){ return JSON.stringify(item).toLowerCase(); }
|
||||
function isMilitaryOrg(item){ const t=item['对象类型']||item['类别']||''; return ['中央主管机关','动员整合机关','业务处室','后备监督机关','地区管理机关','地方执行机关','后备部队','训练支撑单位','服务协力组织'].some(x=>t.includes(x)); }
|
||||
function militarySubOf(item){ if(item['二级分类']) return item['二级分类']; if(isMilitaryOrg(item)) return '组织体系'; return '组织体系'; }
|
||||
function tabConfig(){ return DATA.tabs.find(t=>t.id===state.tab)||DATA.tabs[0]; }
|
||||
function currentData(){ return DATA[TAB_MAP[state.tab]]||[]; }
|
||||
function initSelects(){
|
||||
const circles = new Set(), rels = new Set(), displays = new Set();
|
||||
Object.keys(TAB_MAP).forEach(k=> (DATA[TAB_MAP[k]]||[]).forEach(item=>{ if(circleOf(item)) circles.add(circleOf(item)); if(reliabilityOf(item)) rels.add(reliabilityOf(item)); if(displayOf(item)) displays.add(displayOf(item)); }));
|
||||
fillSelect('circleFilter', circles); fillSelect('reliabilityFilter', rels); fillSelect('displayFilter', displays);
|
||||
}
|
||||
function fillSelect(id, values){ const el=$(id); const first=el.options[0].outerHTML; el.innerHTML=first; [...values].filter(Boolean).sort().forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; el.appendChild(o); }); }
|
||||
function renderStats(){
|
||||
const total = Object.keys(TAB_MAP).reduce((s,k)=>s+(DATA[TAB_MAP[k]]||[]).length,0);
|
||||
const mil = (DATA.militaryReserveSystem||[]).length;
|
||||
const laws = (DATA.legalFramework||[]).length;
|
||||
const local = (DATA.localImplementation||[]).length;
|
||||
const issues = (DATA.publicOpinionControversies||[]).length;
|
||||
const sources = (DATA.sourceAudit||[]).length;
|
||||
$('stats').innerHTML = [ ['总条目',total], ['军方后备体系',mil], ['法规资料',laws], ['县市数据',local], ['争议议题',issues], ['来源审查',sources] ].map(x=>`<div class="stat"><b>${x[1]}</b><span>${x[0]}</span></div>`).join('');
|
||||
}
|
||||
function renderTabs(){
|
||||
$('tabs').innerHTML = DATA.tabs.map(t=>`<button class="tab ${t.id===state.tab?'active':''}" data-tab="${t.id}">${t.name}<small>${(DATA[TAB_MAP[t.id]]||[]).length}条</small></button>`).join('');
|
||||
document.querySelectorAll('.tab').forEach(b=>b.onclick=()=>{state.tab=b.dataset.tab; state.local=''; state.type=''; state.agency=''; state.year=''; state.sub='全部'; $('localSearch').value=''; render();})
|
||||
}
|
||||
function refreshDynamicFilters(data){
|
||||
const types=new Set(), agencies=new Set(), years=new Set();
|
||||
data.forEach(item=>{ const c=categoryOf(item); if(c) types.add(Array.isArray(c)?c.join('、'):c); const a=agencyOf(item); if(a) agencies.add(a); const y=yearOf(item); if(y) years.add(y); });
|
||||
fillSelect('typeFilter', types); fillSelect('agencyFilter', agencies); fillSelect('yearFilter', years);
|
||||
$('typeFilter').value=state.type; $('agencyFilter').value=state.agency; $('yearFilter').value=state.year;
|
||||
}
|
||||
function filteredData(){
|
||||
const g=state.global.trim().toLowerCase(), l=state.local.trim().toLowerCase();
|
||||
return currentData().filter(item=>{
|
||||
const hay=itemText(item);
|
||||
if(g && !hay.includes(g)) return false;
|
||||
if(l && !hay.includes(l)) return false;
|
||||
if(state.circle && !txt(circleOf(item)).includes(state.circle)) return false;
|
||||
if(state.reliability && reliabilityOf(item)!==state.reliability) return false;
|
||||
if(state.display && displayOf(item)!==state.display) return false;
|
||||
if(state.type && !txt(categoryOf(item)).includes(state.type)) return false;
|
||||
if(state.agency && !txt(agencyOf(item)).includes(state.agency)) return false;
|
||||
if(state.year && yearOf(item)!==state.year) return false;
|
||||
if(state.tab==='militaryReserveSystem' && state.sub && state.sub!=='全部' && militarySubOf(item)!==state.sub) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
function renderSubfilters(){
|
||||
const box=$('subfilters');
|
||||
if(state.tab==='militaryReserveSystem'){
|
||||
const all=DATA.militaryReserveSystem||[];
|
||||
box.innerHTML=militarySubs.map(s=>{ const count=s==='全部'?all.length:all.filter(i=>militarySubOf(i)===s).length; return `<button class="chip ${state.sub===s?'active':''}" data-sub="${s}">${s} · ${count}</button>`; }).join('');
|
||||
box.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{state.sub=ch.dataset.sub; renderContent(); renderSubfilters();});
|
||||
} else { box.innerHTML='<span class="badge red">当前栏目支持全局搜索、栏目搜索、类型/机关/年份筛选,点击卡片可打开详情。</span>'; }
|
||||
}
|
||||
function renderFlow(){
|
||||
if(state.tab!=='militaryReserveSystem') return '';
|
||||
const nodes=[['谁在管','国防部、全动署、后备指挥部、地区/县市后备系统'],['管哪些人','后备军官、后备士官、后备士兵、教召人员'],['用什么支撑','军种官科、公开装备类别、晋任制度、权益福利'],['看哪些风险','装备整备、干部储备、专长相符、企业家庭成本']];
|
||||
return `<div class="flow">${nodes.map(n=>`<div class="flow-node"><b>${n[0]}</b><span>${n[1]}</span></div>`).join('')}</div>`;
|
||||
}
|
||||
function renderContent(){
|
||||
const data=filteredData();
|
||||
$('content').innerHTML = renderFlow() + (data.length? renderByView(data): `<div class="empty">没有找到匹配条目。请调整搜索词或筛选条件。</div>`);
|
||||
}
|
||||
function renderByView(data){
|
||||
const cfg=tabConfig();
|
||||
if(['law-table','source-table'].includes(cfg.viewType)) return renderTable(data);
|
||||
if(cfg.viewType==='timeline') return renderTimeline(data);
|
||||
return `<div class="cards">${data.map(renderCard).join('')}</div>`;
|
||||
}
|
||||
function renderCard(item, idx){
|
||||
const id = encodeURIComponent(item.ID||item.id||titleOf(item));
|
||||
const tags=arr(item['标签']||item.tags).slice(0,5);
|
||||
const rel=reliabilityOf(item), disp=displayOf(item), cat=categoryOf(item), cir=circleOf(item);
|
||||
const minis=[];
|
||||
const fields=['主要功能','公开数据','训练模块','法律依据','制度短板','社会争议','研究问题','涉及权益'];
|
||||
fields.forEach(f=>{ if(item[f]) minis.push(`<div class="section-mini"><b>${f}</b><ul>${arr(item[f]).slice(0,3).map(v=>`<li>${escapeHTML(v)}</li>`).join('')}</ul></div>`); });
|
||||
return `<article class="card">
|
||||
<div><h3>${escapeHTML(titleOf(item))}</h3><div class="meta-row">${cat?`<span class="badge red">${escapeHTML(cat)}</span>`:''}${cir?`<span class="badge blue">${escapeHTML(cir)}</span>`:''}${rel?`<span class="badge green">可信度:${escapeHTML(rel)}</span>`:''}</div></div>
|
||||
<div class="meta-row">${tags.map(t=>`<span class="badge">${escapeHTML(t)}</span>`).join('')}</div>
|
||||
<div class="summary">${escapeHTML(txt(summaryOf(item)))}</div>
|
||||
${minis.slice(0,2).join('')}
|
||||
<div class="card-footer"><span class="warning">${disp?escapeHTML(disp):'可公开'}</span><button class="link-btn" onclick="openModalByKey('${id}')">查看详情 →</button></div>
|
||||
</article>`;
|
||||
}
|
||||
function renderTable(data){
|
||||
const rows=data.map(item=>`<tr onclick="openModalByKey('${encodeURIComponent(item.ID||item.id||titleOf(item))}')"><td><b>${escapeHTML(titleOf(item))}</b></td><td>${escapeHTML(categoryOf(item))}</td><td>${escapeHTML(agencyOf(item))}</td><td>${escapeHTML(reliabilityOf(item))}</td><td>${escapeHTML(txt(summaryOf(item)).slice(0,160))}</td></tr>`).join('');
|
||||
return `<div class="table-wrap"><table><thead><tr><th>名称</th><th>类型</th><th>机关/机构</th><th>可信度</th><th>摘要</th></tr></thead><tbody>${rows}</tbody></table></div>`;
|
||||
}
|
||||
function renderTimeline(data){
|
||||
return `<div class="timeline">${data.map(item=>`<div class="titem"><div class="meta-row"><span class="badge red">${escapeHTML(yearOf(item)||'时间未标注')}</span><span class="badge">${escapeHTML(categoryOf(item))}</span></div><h3>${escapeHTML(titleOf(item))}</h3><p class="summary">${escapeHTML(txt(summaryOf(item)))}</p><button class="link-btn" onclick="openModalByKey('${encodeURIComponent(item.ID||item.id||titleOf(item))}')">查看详情 →</button></div>`).join('')}</div>`;
|
||||
}
|
||||
function openModalByKey(key){
|
||||
const decoded=decodeURIComponent(key);
|
||||
let found=null;
|
||||
Object.keys(TAB_MAP).some(k=> (DATA[TAB_MAP[k]]||[]).some(item=>{ const itemKey=item.ID||item.id||titleOf(item); if(itemKey===decoded){found=item; return true} return false; }));
|
||||
if(found) openModal(found);
|
||||
}
|
||||
function openModal(item){
|
||||
$('modalTitle').textContent=titleOf(item);
|
||||
const badges=[categoryOf(item), circleOf(item), reliabilityOf(item)?'可信度:'+reliabilityOf(item):'', displayOf(item)].filter(Boolean);
|
||||
$('modalBadges').innerHTML=badges.map((b,i)=>`<span class="badge ${i===0?'red':i===1?'blue':'green'}">${escapeHTML(b)}</span>`).join('');
|
||||
$('modalBody').innerHTML=renderDetail(item);
|
||||
$('modalMask').style.display='flex';
|
||||
}
|
||||
function renderDetail(item){
|
||||
const priority=['摘要','定义或说明','官方定义','核心目的','触发原因','主管机关','上级单位','下级单位','层级','对象类型','二级分类','适用对象','军种','官科兵科','专业方向','装备类别','取得方式','训练关联','主要功能','公开数据','法律依据','关联制度','评选条件','作业流程','权责机关','涉及权益','福利边界','社会争议','制度短板','治理风险','研究问题','展示限制'];
|
||||
const blocks=[];
|
||||
priority.forEach(k=>{ if(item[k]) blocks.push(detailBlock(k,item[k])); });
|
||||
const rest=Object.keys(item).filter(k=>!priority.includes(k)&&!['ID','id','名称','标题','标签','资料来源','publicSources','更新时间','可信度','展示等级','对应圈层','类别'].includes(k)&&item[k]);
|
||||
rest.slice(0,8).forEach(k=>blocks.push(detailBlock(k,item[k])));
|
||||
const sources=item['资料来源']||item.publicSources||item['来源']||[];
|
||||
if(sources.length) blocks.push(sourceBlock(sources));
|
||||
blocks.push(`<div class="notice"><b>展示边界:</b>本页面仅展示公开制度、公开类别和政策研究信息;涉及具体营区、集结点、动员路线、个人资料、具体装备数量、仓储位置、单位配属和可操作性脆弱信息的内容不予展示。</div>`);
|
||||
return `<div class="detail-grid">${blocks.join('')}</div>`;
|
||||
}
|
||||
function detailBlock(k,v){
|
||||
let content='';
|
||||
if(Array.isArray(v)) content=`<ul>${v.map(x=>`<li>${escapeHTML(txt(x))}</li>`).join('')}</ul>`;
|
||||
else if(typeof v==='object') content=objectToHTML(v);
|
||||
else content=`<p>${escapeHTML(String(v))}</p>`;
|
||||
return `<section class="detail-block"><h4>${escapeHTML(k)}</h4>${content}</section>`;
|
||||
}
|
||||
function objectToHTML(obj){
|
||||
return `<ul>${Object.entries(obj).map(([k,v])=>`<li><b>${escapeHTML(k)}:</b>${Array.isArray(v)?v.map(x=>escapeHTML(txt(x))).join('、'):escapeHTML(txt(v))}</li>`).join('')}</ul>`;
|
||||
}
|
||||
function sourceBlock(sources){
|
||||
const html=sources.map(s=>{ if(typeof s==='string') return `<div class="source-item">${escapeHTML(s)}</div>`; const url=s.URL||s.url||''; const name=s.标题||s.title||s.sourceTitle||s.来源名称||'来源'; return `<div class="source-item"><b>${escapeHTML(name)}</b> <span class="badge">${escapeHTML(s.类型||s.type||'')}</span> <span class="badge green">${escapeHTML(s.可信度||s.rating||'')}</span>${url?`<br><a href="${escapeAttr(url)}" target="_blank" rel="noopener">打开来源</a>`:''}</div>`; }).join('');
|
||||
return `<section class="detail-block source-list" style="grid-column:1/-1"><h4>资料来源</h4>${html}</section>`;
|
||||
}
|
||||
function escapeHTML(s){ return String(s??'').replace(/[&<>"]/g, c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
|
||||
function escapeAttr(s){ return escapeHTML(s).replace(/'/g,'''); }
|
||||
function render(){
|
||||
renderTabs();
|
||||
const cfg=tabConfig(); $('tabTitle').textContent=cfg.name; $('tabSubtitle').textContent=cfg.subtitle||'';
|
||||
refreshDynamicFilters(currentData()); renderSubfilters(); renderContent();
|
||||
}
|
||||
['globalSearch','localSearch'].forEach(id=>$(id).addEventListener('input',e=>{ if(id==='globalSearch') state.global=e.target.value; else state.local=e.target.value; renderContent(); }));
|
||||
$('circleFilter').onchange=e=>{state.circle=e.target.value; renderContent();};
|
||||
$('reliabilityFilter').onchange=e=>{state.reliability=e.target.value; renderContent();};
|
||||
$('displayFilter').onchange=e=>{state.display=e.target.value; renderContent();};
|
||||
$('typeFilter').onchange=e=>{state.type=e.target.value; renderContent();};
|
||||
$('agencyFilter').onchange=e=>{state.agency=e.target.value; renderContent();};
|
||||
$('yearFilter').onchange=e=>{state.year=e.target.value; renderContent();};
|
||||
$('resetBtn').onclick=()=>{Object.assign(state,{global:'',local:'',circle:'',reliability:'',display:'',type:'',agency:'',year:'',sub:'全部'}); ['globalSearch','localSearch','circleFilter','reliabilityFilter','displayFilter','typeFilter','agencyFilter','yearFilter'].forEach(id=>$(id).value=''); render();};
|
||||
$('closeModal').onclick=()=>$('modalMask').style.display='none';
|
||||
$('modalMask').onclick=e=>{ if(e.target.id==='modalMask') $('modalMask').style.display='none'; };
|
||||
$('topBtn').onclick=()=>window.scrollTo({top:0,behavior:'smooth'});
|
||||
$('exportBtn').onclick=()=>{ const blob=new Blob([JSON.stringify(filteredData(),null,2)],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=`${state.tab}_filtered.json`; a.click(); URL.revokeObjectURL(a.href); };
|
||||
initSelects(); renderStats(); render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user