feat(定时任务): 新增 9 个 HTML 模板,下拉补齐为 no.1~no.18(只展示)

- 从 zt-0625 加入 no.8/no.9/no.10/no.11/no.14/no.15/no.16/no.17/no.18
- 新 no.8/no.9 的 id 用 no8b/no9b(no8/no9 已被显示名 no.12/no.13 的模板占用)
- 重排 TEMPLATES 使下拉按显示名升序 no.1→no.18;均 convertible=False 只展示
- 测试覆盖全部 id 存在、显示名覆盖 1~18、各模板文件可读为完整 HTML

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-26 09:55:34 +08:00
1 parent 38f98cdf5c
commit a400989f64
10 files changed
+12662

No files matched your search

@@ -184,6 +184,36 @@ TEMPLATES: tuple[TemplateSpec, ...] = (
# 当前引擎只支持 const VAR= 行注入,故暂注册为只展示;接转换需扩展元素注入。
convertible=False,
),
TemplateSpec(
# 显示 no.8;id 用 no8b(no8 已被显示名 no.12 的模板占用)。
id="no8b",
name="no.8",
description="",
file="no8b/no8b.html",
convertible=False,
),
TemplateSpec(
# 显示 no.9;id 用 no9b(no9 已被显示名 no.13 的模板占用)。
id="no9b",
name="no.9",
description="",
file="no9b/no9b.html",
convertible=False,
),
TemplateSpec(
id="no10",
name="no.10",
description="",
file="no10/no10.html",
convertible=False,
),
TemplateSpec(
id="no11",
name="no.11",
description="",
file="no11/no11.html",
convertible=False,
),
TemplateSpec(
id="no8",
name="no.12",
@@ -202,6 +232,41 @@ TEMPLATES: tuple[TemplateSpec, ...] = (
# 故暂注册为只展示;接转换需放宽行注入正则。
convertible=False,
),
TemplateSpec(
id="no14",
name="no.14",
description="",
file="no14/no14.html",
convertible=False,
),
TemplateSpec(
id="no15",
name="no.15",
description="",
file="no15/no15.html",
convertible=False,
),
TemplateSpec(
id="no16",
name="no.16",
description="",
file="no16/no16.html",
convertible=False,
),
TemplateSpec(
id="no17",
name="no.17",
description="",
file="no17/no17.html",
convertible=False,
),
TemplateSpec(
id="no18",
name="no.18",
description="",
file="no18/no18.html",
convertible=False,
),
)
_TEMPLATES_BY_ID: dict[str, TemplateSpec] = {t.id: t for t in TEMPLATES}
@@ -0,0 +1,61 @@
<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>台方向“解放军无法战胜”叙事专题数据库</title><style>
:root{
--bg:#fbf3ef;--paper:#fffdfb;--paper2:#fff7f2;--ink:#271312;--muted:#7b6760;
--red:#b92e27;--red2:#8e211c;--red3:#d35142;--line:#ecd6cc;--soft:#f6e6df;
--shadow:0 14px 35px rgba(78,30,18,.12);--radius:22px;
}
*{box-sizing:border-box}html,body{margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif;background:linear-gradient(180deg,#fff 0,#fbf1ec 240px,#fffaf7 100%);color:var(--ink);}
.hero{background:linear-gradient(135deg,var(--red2),var(--red3));color:#fff;padding:30px 40px 34px;box-shadow:0 8px 30px rgba(104,23,17,.22)}
.hero h1{margin:0 0 14px;font-size:34px;line-height:1.22;letter-spacing:.5px}.hero p{margin:0;max-width:1280px;font-size:18px;line-height:1.75;opacity:.96}.meta-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:18px}.meta-pill{border:1px solid rgba(255,255,255,.42);background:rgba(255,255,255,.12);padding:8px 14px;border-radius:999px;font-weight:700}
main{max-width:1520px;margin:0 auto;padding:22px 24px 50px}.tabs-wrap{position:sticky;top:0;z-index:20;background:rgba(251,243,239,.94);border-bottom:1px solid var(--line);backdrop-filter:blur(10px);margin:0 -24px 20px;padding:14px 24px 12px;overflow:auto}.tabs{display:flex;gap:10px;min-width:max-content}.tab-btn{appearance:none;border:1px solid var(--line);background:white;color:var(--red2);border-radius:999px;padding:11px 16px;font-weight:900;cursor:pointer;box-shadow:0 6px 14px rgba(102,38,26,.06);white-space:nowrap}.tab-btn small{color:inherit;opacity:.72;margin-left:5px}.tab-btn.active{background:var(--red2);color:#fff;border-color:var(--red2)}
.toolbar{display:grid;grid-template-columns:1fr 220px 210px 170px;gap:14px;align-items:center;background:rgba(255,255,255,.88);border:1px solid var(--line);border-radius:20px;padding:14px;margin-bottom:22px;box-shadow:var(--shadow)}.searchbox{height:54px;border:1px solid var(--line);border-radius:16px;background:white;display:flex;align-items:center;overflow:hidden}.searchbox span{width:78px;text-align:center;color:var(--red);font-weight:900;font-size:22px;letter-spacing:4px}.searchbox input{border:0;outline:0;flex:1;font-size:17px;padding:0 18px;background:transparent;color:var(--ink)}select,.btn{height:54px;border:1px solid var(--line);border-radius:16px;background:white;padding:0 16px;font-size:16px;color:var(--ink)}.btn{font-weight:900;cursor:pointer}.btn.primary{background:var(--red);color:#fff;border-color:var(--red)}
.section-intro,.panel{background:rgba(255,255,255,.9);border:1px solid var(--line);border-radius:var(--radius);padding:22px;margin-bottom:20px;box-shadow:var(--shadow)}.intro-head{display:flex;gap:22px;justify-content:space-between}.intro-head h2{margin:0 0 8px;color:var(--red2);font-size:26px}.intro-head p{margin:0 0 8px;color:var(--muted);line-height:1.7}.stats{display:flex;gap:10px;min-width:270px}.stat{flex:1;background:var(--paper2);border:1px solid var(--line);border-radius:16px;padding:12px;text-align:center}.stat b{display:block;font-size:26px;color:var(--red)}.stat span{font-size:13px;color:var(--muted)}.profile-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:18px}.profile-card{background:#fff;border:1px solid var(--line);border-radius:16px;padding:16px}.profile-card b{display:block;color:var(--red2);margin-bottom:8px}.profile-card p,.profile-card li{color:var(--muted);line-height:1.65;margin:0}.profile-card ul{margin:0;padding-left:18px}.chipbar{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 18px}.chip{border:1px solid var(--line);background:white;border-radius:999px;padding:7px 12px;cursor:pointer;color:var(--red2);font-weight:800}.chip.active{background:var(--red2);color:white;border-color:var(--red2)}.result-note,.footer-note{background:#fff8f4;border:1px solid var(--line);border-radius:16px;padding:12px 16px;color:var(--muted);line-height:1.7;margin-bottom:18px}.footer-note{margin-top:20px}
.card-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}.record-card,.chain-card{background:#fff;border:1px solid var(--line);border-radius:22px;padding:18px;box-shadow:0 10px 25px rgba(78,30,18,.09);display:flex;flex-direction:column;gap:12px}.record-card h3,.chain-card h3{margin:0;color:var(--red2);font-size:21px}.record-card p,.chain-card p{margin:0;color:var(--muted);line-height:1.7}.badge{display:inline-block;background:#f8e4dc;color:var(--red2);border:1px solid var(--line);border-radius:999px;padding:5px 10px;font-weight:900;font-size:13px}.field-list{display:grid;gap:8px}.mini-kv{border-left:4px solid var(--red3);background:#fff8f4;border-radius:10px;padding:9px 10px}.mini-kv b{display:block;color:var(--red2);margin-bottom:4px}.mini-kv span{color:var(--muted);line-height:1.55}.tags{display:flex;gap:7px;flex-wrap:wrap}.tag,.source-pill{font-size:12px;border-radius:999px;padding:5px 9px;background:#f5eee9;color:#61423b;border:1px solid var(--line)}.source-pill{background:#fff2e8;color:#9a3a2f}.card-actions{margin-top:auto;display:flex;justify-content:flex-end}.open-btn{border:0;background:var(--red);color:white;border-radius:12px;padding:10px 14px;font-weight:900;cursor:pointer}.empty{padding:40px;text-align:center;background:#fff;border:1px dashed var(--line);border-radius:20px;color:var(--muted)}
.timeline{position:relative;margin-left:16px}.timeline:before{content:"";position:absolute;left:14px;top:0;bottom:0;width:3px;background:var(--line)}.timeline-item{position:relative;margin:0 0 16px 42px}.timeline-item:before{content:"";position:absolute;left:-34px;top:26px;width:15px;height:15px;border-radius:50%;background:var(--red);box-shadow:0 0 0 6px #f9e7df}.chain-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:18px}.chain-steps{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.step{background:#fff5ef;border:1px solid var(--line);border-radius:12px;padding:9px 10px;color:var(--red2);font-weight:800}.arrow{color:var(--red);font-weight:900}.table-wrap{overflow:auto;border:1px solid var(--line);border-radius:18px;background:#fff}table{width:100%;border-collapse:collapse;min-width:900px}th,td{padding:13px 14px;border-bottom:1px solid var(--line);vertical-align:top;text-align:left;line-height:1.55}th{background:#fff1eb;color:var(--red2);position:sticky;top:0;z-index:1}tr:hover td{background:#fffaf7}.cell-title{font-weight:900;color:var(--red2)}
.model-grid,.source-grid,.detail-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.model-card,.source-card,.kv{background:#fff;border:1px solid var(--line);border-radius:16px;padding:15px}.model-card b,.source-card b,.kv b{display:block;color:var(--red2);margin-bottom:7px}.model-card li,.source-card p,.source-card span,.kv span{color:var(--muted);line-height:1.65}.source-card a,.source-line a{color:var(--red);font-weight:900;text-decoration:none}.modal-backdrop{display:none;position:fixed;inset:0;background:rgba(30,15,13,.52);z-index:99;align-items:center;justify-content:center;padding:24px}.modal{width:min(1120px,96vw);max-height:90vh;overflow:hidden;background:var(--paper);border-radius:26px;box-shadow:0 20px 70px rgba(0,0,0,.32);border:1px solid var(--line);display:flex;flex-direction:column}.modal-head{background:linear-gradient(135deg,var(--red2),var(--red3));color:#fff;padding:22px 26px;display:flex;justify-content:space-between;gap:18px}.modal-head h2{margin:0 0 8px;font-size:24px}.modal-head p{margin:0;line-height:1.65;opacity:.95}.close{width:42px;height:42px;border-radius:50%;border:1px solid rgba(255,255,255,.4);background:rgba(255,255,255,.16);color:#fff;font-size:28px;cursor:pointer}.modal-body{overflow:auto;padding:22px}.detail-grid{grid-template-columns:repeat(2,1fr);margin:16px 0}.detail-section{background:#fff;border:1px solid var(--line);border-radius:18px;padding:18px;margin:14px 0}.detail-section h3{margin:0 0 10px;color:var(--red2)}.detail-section p,.detail-section li{line-height:1.75;color:var(--muted)}.source-list{display:grid;gap:10px}.source-line{background:#fff8f4;border:1px solid var(--line);border-radius:14px;padding:13px}.source-line p{margin:4px 0;color:var(--muted)}
@media(max-width:1180px){.toolbar{grid-template-columns:1fr 1fr}.card-grid,.profile-grid,.model-grid,.source-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:760px){.hero{padding:24px 18px}.hero h1{font-size:24px}main{padding:14px}.tabs-wrap{margin:0 -14px 14px;padding:12px 14px}.toolbar,.card-grid,.profile-grid,.chain-grid,.model-grid,.source-grid,.detail-grid{grid-template-columns:1fr}.intro-head{flex-direction:column}.stats{min-width:0}.modal-backdrop{padding:8px}}
</style></head><body>
<header class="hero"><h1>台方向“解放军无法战胜”叙事专题数据库</h1><p>围绕“台湾难以抗衡 / 抵抗代价过高 / 外援不确定”的安全叙事,展示事件、口径、党派转译、平台链路、受众画像、青年兵役与疑美叙事。</p><div class="meta-row"><span class="meta-pill">版本:纯中文展示版 v5.0</span><span class="meta-pill">横向 Tab 导航</span><span class="meta-pill">无可见 JSON 区块</span><span class="meta-pill">本地单文件可运行</span></div></header>
<main><div class="tabs-wrap"><nav class="tabs" id="tabs"><button class="tab-btn active" data-tab="overview">专题总览 <small>(4)</small></button><button class="tab-btn " data-tab="core_narratives">核心叙事库 <small>(12)</small></button><button class="tab-btn " data-tab="narrative_chains">叙事链条拆解 <small>(6)</small></button><button class="tab-btn " data-tab="timeline">事件时间线 <small>(12)</small></button><button class="tab-btn " data-tab="official_positions">官方口径对照 <small>(9)</small></button><button class="tab-btn " data-tab="party_translation">台湾党派转译 <small>(8)</small></button><button class="tab-btn " data-tab="platform_chains">平台传播链路 <small>(9)</small></button><button class="tab-btn " data-tab="audience_profiles">受众群体画像 <small>(10)</small></button><button class="tab-btn " data-tab="youth_conscription">青年与兵役专题 <small>(9)</small></button><button class="tab-btn " data-tab="us_skepticism">疑美叙事专题 <small>(7)</small></button></nav></div><section class="toolbar"><label class="searchbox"><span>搜索</span><input id="searchInput" placeholder="输入关键词:军演、兵役、疑美、Dcard、封锁、青年……"></label><select id="tagSelect"><option value="">全部标签</option></select><select id="viewSelect"><option value="rich">丰富卡片</option><option value="table">表格视图</option></select><button class="btn primary" id="exportBtn">导出当前表格</button></section><section id="content"><section class="section-intro"><div class="intro-head"><div><h2>专题总览</h2><p>定义研究对象、时间范围、边界、资料来源和系统结构。</p><p class="result-note">本页将底层数据转为中文卡片、时间线、链条图、表格和详情面板,不显示 JSON 原文。</p></div><div class="stats"><div class="stat"><b>4</b><span>条目</span></div><div class="stat"><b>8</b><span>标签</span></div><div class="stat"><b>3</b><span>来源引用</span></div></div></div><div class="profile-grid"><div class="profile-card"><b>研究重点</b><p>给出专题边界、研究对象、数据层构成、平台环境基线与安全边界。</p></div><div class="profile-card"><b>展示方式</b><p>总览卡片、统计面板、叙事—心理—行为模型、来源索引。</p></div><div class="profile-card"><b>适合回答</b><ul><li>这个专题研究什么?</li><li>数据有哪些层次?</li><li>哪些内容可以直接入库?</li><li>有哪些伦理边界?</li></ul></div><div class="profile-card"><b>核心字段</b><ul><li>研究对象</li><li>时间范围</li><li>研究边界</li><li>主要社群平台</li><li>数据结构</li></ul></div></div></section><div class="result-note">当前显示 <b>4</b> / 4 条记录。点击“查看完整详情”可打开中文详情面板。</div><div class="card-grid"><article class="record-card"><div><span class="badge">OV001 · 专题总览</span></div><h3>专题定义</h3><p>研究台湾方向围绕“解放军无法战胜 / 台湾难以抗衡 / 抵抗代价过高”的安全叙事体系。</p><div class="field-list"><div class="mini-kv"><b>研究对象</b><span>台湾舆论场中的安全叙事、党派转译、平台传播、受众心理与社会反应</span></div><div class="mini-kv"><b>时间范围</b><span>1995—1996;2022—2026为重点</span></div><div class="mini-kv"><b>研究边界</b><span>公开资料研究,不做隐蔽影响、定向操控、恐吓传播或虚假信息设计</span></div></div><div class="tags"><span class="tag">研究对象</span><span class="tag">范围</span></div><div class="card-actions"><button class="open-btn primary" data-open="OV001">查看完整详情</button></div></article><article class="record-card"><div><span class="badge">OV002 · 专题总览</span></div><h3>数据层说明</h3><p>本库包括10个栏目、12类核心叙事、6条叙事链、12个事件/战术动作条目、9类平台链路和10类受众画像。</p><div class="field-list"><div class="mini-kv"><b>栏目数量</b><span>10</span></div><div class="mini-kv"><b>核心叙事</b><span>12</span></div><div class="mini-kv"><b>叙事链</b><span>6</span></div><div class="mini-kv"><b>事件条目</b><span>12</span></div></div><div class="tags"><span class="tag">数据结构</span><span class="tag">知识库</span></div><div class="card-actions"><button class="open-btn primary" data-open="OV002">查看完整详情</button></div></article><article class="record-card"><div><span class="badge">OV003 · 专题总览</span></div><h3>安全与伦理边界</h3><p>本系统定位为研究型工具,输出为公开舆情研究、风险沟通和信息韧性分析。</p><div class="field-list"><div class="mini-kv"><b>允许方向</b><span>公开舆情研究、平台机制分析、风险沟通、政策研究</span></div><div class="mini-kv"><b>禁止方向</b><span>隐蔽投放、心理操控、恐吓传播、谣言制造、群体污名化、军事攻击Line truncated
<div class="modal-backdrop" id="modalBackdrop"><div class="modal"><div class="modal-head"><div><h2 id="modalTitle"></h2><p id="modalSummary"></p></div><button class="close" id="closeBtn">×</button></div><div class="modal-body" id="modalBody"></div></div></div>
<script type="application/json" id="app-data">{"metadata":{"title":"台方向“解放军无法战胜”叙事专题数据库","subtitle":"围绕“台湾难以抗衡 / 抵抗代价过高 / 外援不确定”的安全叙事,展示事件、口径、党派转译、平台链路、受众画像、青年兵役与疑美叙事。","version":"1.0.0","language":"zh-CN","created_at":"2026-06-25T11:56:25","data_date":"2026-06-25","scope":"公开资料研究、叙事结构分析、平台链路与受众心理研究","ethics_boundary":"仅用于透明、非操纵、非煽动的研究型分析;不用于隐蔽影响、恐吓传播、群体污名化或虚假信息设计。","display_version":"Rich UI v3.1","ui_note":"横向Tab、中文卡片、字段网格、时间线、链条图、详情面板、来源说明。"},"stats":{"tab_count":10,"record_count":86,"source_count":16,"last_updated":"2026-06-25","current_timezone":"Asia/Tokyo","data_status":{"framework":"complete","event_cards":"initial_completed","official_raw_quotes":"needs_more_original_text","platform_samples":"schema_ready_needs_collection","poll_data":"partial","front_end":"single_page_offline"}},"tabs":[{"id":"overview","name":"专题总览","description":"定义研究对象、时间范围、边界、资料来源和系统结构。","records":[{"id":"OV001","title":"专题定义","summary":"研究台湾方向围绕“解放军无法战胜 / 台湾难以抗衡 / 抵抗代价过高”的安全叙事体系。","category":"专题总览","tags":["研究对象","范围"],"level":"研究条目","fields":{"研究对象":"台湾舆论场中的安全叙事、党派转译、平台传播、受众心理与社会反应","时间范围":"1995—1996;2022—2026为重点","研究边界":"公开资料研究,不做隐蔽影响、定向操控、恐吓传播或虚假信息设计"},"detail_sections":[{"title":"核心问题","bullets":["该叙事如何由军演、军售、领导人讲话等事件激活?","它如何被台湾内部政党、媒体、政论节目和社群平台转译?","它如何与兵役焦虑、疑美论、棋子论、乌克兰化和避战论结合?","哪些受众更易将其转化为政治态度、家庭风险或个人规划问题?"]},{"title":"入库原则","bullets":["事实、观点、推测分开记录。","官方来源、民调、智库、媒体、社群样本分级标注。","平台语料只做公开研究与机制分析,不制作操控性传播内容。"]}],"source_refs":[],"display_type":"overview","中文展示摘要":"专题定义:研究台湾方向围绕“解放军无法战胜 / 台湾难以抗衡 / 抵抗代价过高”的安全叙事体系。"},{"id":"OV002","title":"数据层说明","summary":"本库包括10个栏目、12类核心叙事、6条叙事链、12个事件/战术动作条目、9类平台链路和10类受众画像。","category":"专题总览","tags":["数据结构","知识库"],"level":"研究条目","fields":{"栏目数量":10,"核心叙事":12,"叙事链":6,"事件条目":12,"平台类型":9,"受众类型":10},"detail_sections":[{"title":"可前端展示功能","bullets":["Tab切换","全库检索","栏目内筛选","详情弹框","来源查看","结构化详情查看","导出当前栏目表格"]}],"source_refs":[],"display_type":"overview","中文展示摘要":"数据层说明:本库包括10个栏目、12类核心叙事、6条叙事链、12个事件/战术动作条目、9类平台链路和10类受众画像。"},{"id":"OV003","title":"安全与伦理边界","summary":"本系统定位为研究型工具,输出为公开舆情研究、风险沟通和信息韧性分析。","category":"专题总览","tags":["安全边界","研究用途"],"level":"研究条目","fields":{"允许方向":"公开舆情研究、平台机制分析、风险沟通、政策研究","禁止方向":"隐蔽投放、心理操控、恐吓传播、谣言制造、群体污名化、军事攻击细节"},"detail_sections":[{"title":"研究注意事项","bullets":["不得把单一平台声量直接等同于台湾社会整体民意。","不得把青年对兵役不满直接等同于没有防卫意志。","不得把政党表态当成群体共识。","涉及社群样本时应保留来源、时间和上下文。"]}],"source_refs":[],"display_type":"overview","中文展示摘要":"安全与伦理边界:本系统定位为研究型工具,输出为公开舆情研究、风险沟通和信息韧性分析。"},{"id":"OV004","title":"数字与平台环境基线","summary":"台湾互联网和社群普及率很高,YouTube、Facebook、Instagram、Dcard、Threads、PTT 等平台承担不同传播功能。","category":"专题总览","tags":["平台环境","基线数据"],"level":"研究条目","fields":{"2025社群用户身份":"约1840万,占总人口约79.4%","2025年底互联网用户":"约2230万,普及率约96.7%","主要社群平台":"YouTube、Facebook、Instagram、Dcard、Threads、PTT、LINE"},"detail_sections":[{"title":"平台环境解释","bullets":["高互Line truncated
<script>
'use strict';
const DATA = JSON.parse(document.getElementById('app-data').textContent);
const $ = s => document.querySelector(s);
const arr = x => Array.isArray(x) ? x : (x ? [x] : []);
const state = { tab: DATA.tabs?.[0]?.id || '', q: '', tag: '', view: 'rich' };
function esc(v) { return String(v ?? '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }
function textOf(v) { if (v === null || v === undefined) return ''; if (Array.isArray(v)) return v.map(textOf).join('、'); if (typeof v === 'object') return Object.entries(v).map(([k,val]) => k + ':' + textOf(val)).join(';'); return String(v); }
function allText(obj) { return textOf(obj).toLowerCase(); }
function getTab() { return DATA.tabs.find(t => t.id === state.tab) || DATA.tabs[0]; }
function getSource(id) { return (DATA.sources || []).find(s => s.id === id); }
function tagHtml(tags) { return arr(tags).map(t => `<span class="tag">${esc(t)}</span>`).join(''); }
function sourceHtml(ids) { return arr(ids).map(id => `<span class="source-pill">来源 ${esc(id)}</span>`).join(''); }
function list(items) { return arr(items).map(x => `<li>${esc(x)}</li>`).join(''); }
function sourceDetail(ids) { const sources = arr(ids).map(getSource).filter(Boolean); if (!sources.length) return ''; return `<section class="detail-section"><h3>来源说明</h3><div class="source-list">${sources.map(s => `<div class="source-line"><b>${esc(s.id)} · ${esc(s.title)}</b><p>${esc(s.publisher)}|${esc(s.date)}|可信度:${esc(s.reliability)}</p><p>${esc(s.note || '')}</p><p><a href="${esc(s.url)}" target="_blank" rel="noopener">打开来源</a></p></div>`).join('')}</div></section>`; }
function renderIntro(tab) { const p = tab.profile || {}; return `<section class="section-intro"><div class="intro-head"><div><h2>${esc(tab.name)}</h2><p>${esc(tab.description || tab.richness_note || '')}</p><p class="result-note">本页将底层数据转为中文卡片、时间线、链条图、表格和详情面板,不显示 JSON 原文。</p></div><div class="stats"><div class="stat"><b>${tab.records.length}</b><span>条目</span></div><div class="stat"><b>${tab.tag_count || 0}</b><span>标签</span></div><div class="stat"><b>${tab.source_ref_count || 0}</b><span>来源引用</span></div></div></div><div class="profile-grid"><div class="profile-card"><b>研究重点</b><p>${esc(p['研究重点'] || '本栏目提供可结构化入库的数据条目。')}</p></div><div class="profile-card"><b>展示方式</b><p>${esc(p['展示方式'] || '中文字段、详情面板、来源说明。')}</p></div><div class="profile-card"><b>适合回答</b><ul>${list(p['适合回答'] || [])}</ul></div><div class="profile-card"><b>核心字段</b><ul>${list(p['核心字段'] || [])}</ul></div></div></section>`; }
function fieldRows(r, max=4) { const entries = Object.entries(r.fields || {}).slice(0, max); return entries.length ? entries.map(([k,v]) => `<div class="mini-kv"><b>${esc(k)}</b><span>${esc(textOf(v))}</span></div>`).join('') : '<div class="mini-kv"><b>说明</b><span>该条目暂无额外字段,完整信息见详情面板。</span></div>'; }
function cardHtml(r) { return `<article class="record-card"><div><span class="badge">${esc(r.id)} · ${esc(r.category || r.level || '条目')}</span></div><h3>${esc(r.title)}</h3><p>${esc(r.summary || '')}</p><div class="field-list">${fieldRows(r,4)}</div><div class="tags">${tagHtml(r.tags)}${sourceHtml(r.source_refs)}</div><div class="card-actions"><button class="open-btn primary" data-open="${esc(r.id)}">查看完整详情</button></div></article>`; }
function renderCards(records) { return records.length ? `<div class="card-grid">${records.map(cardHtml).join('')}</div>` : '<div class="empty">没有匹配的记录。请调整搜索词或标签。</div>'; }
function renderTimeline(records) { return records.length ? `<div class="timeline">${records.map(r => `<div class="timeline-item">${cardHtml(r)}</div>`).join('')}</div>` : '<div class="empty">没有匹配的时间线记录。</div>'; }
function renderChains(records) { if (!records.length) return '<div class="empty">没有匹配的叙事链条。</div>'; return `<div class="chain-grid">${records.map(r => { const chain = (r.fields && r.fields['叙事链条']) ? String(r.fields['叙事链条']).split('→').map(s => s.trim()).filter(Boolean) : []; return `<article class="chain-card"><span class="badge">${esc(r.id)}</span><h3>${esc(r.title)}</h3><p>${esc(r.summary || '')}</p><div class="chain-steps">${chain.map((s,i) => `<span class="step">${esc(s)}</span>${i < chain.length - 1 ? '<span class="arrow">→</span>' : ''}`).join('')}</div><div class="field-list">${fieldRows(r,4)}</div><div class="tags">${tagHtml(r.tags)}${sourceHtml(r.source_refs)}</div><div class="card-actions"><button class="open-btn primary" data-open="${esc(r.id)}">查看完整详情</button></div></article>`; }).join('')}</div>`; }
function renderTable(records) { if (!records.length) return '<div class="empty">没有匹配记录。</div>'; return `<div class="table-wrap"><table><thead><tr><th>ID</th><th>标题</th><th>摘要</th><th>主要字段</th><th>标签/来源</th><th>操作</th></tr></thead><tbody>${records.map(r => `<tr><td><span class="badge">${esc(r.id)}</span></td><td class="cell-title">${esc(r.title)}</td><td>${esc(r.summary || '')}</td><td>${Object.entries(r.fields || {}).slice(0,6).map(([k,v]) => `<b>${esc(k)}:</b>${esc(textOf(v))}`).join('<br>')}</td><td>${tagHtml(r.tags)}<br>${sourceHtml(r.source_refs)}</td><td><button class="open-btn primary" data-open="${esc(r.id)}">详情</button></td></tr>`).join('')}</tbody></table></div>`; }
function renderModels() { if (state.tab !== 'overview') return ''; const m = DATA.models || {}; return `<section class="panel"><h3>叙事 → 心理 → 行为模型</h3><div class="model-grid"><div class="model-card"><b>叙事到心理</b><ul>${arr(m.narrative_to_psychology).map(x => `<li>${esc(x.narrative)} → ${esc(x.psychology)}</li>`).join('')}</ul></div><div class="model-card"><b>心理到行为</b><ul>${arr(m.psychology_to_behavior).map(x => `<li>${esc(x.psychology)} → ${esc(x.behavior)}</li>`).join('')}</ul></div><div class="model-card"><b>可观测行为指标</b><ul>${arr(m.observable_behavior_indicators).map(x => `<li>${esc(x)}</li>`).join('')}</ul></div></div></section>`; }
function renderSourcesPanel(tab) { const ids = [...new Set(tab.records.flatMap(r => arr(r.source_refs)))]; const sources = (ids.length ? ids.map(getSource).filter(Boolean) : (DATA.sources || []).slice(0,8)); if (!sources.length) return ''; return `<section class="panel"><h3>来源索引</h3><div class="source-grid">${sources.map(s => `<div class="source-card"><b>${esc(s.id)} · ${esc(s.title)}</b><span>${esc(s.publisher)}|${esc(s.date)}|可信度:${esc(s.reliability)}</span><p>${esc(s.note || '')}</p><p><a href="${esc(s.url)}" target="_blank" rel="noopener">打开来源</a></p></div>`).join('')}</div></section>`; }
function filterRecords(tab) { return tab.records.filter(r => (!state.q || allText(r).includes(state.q)) && (!state.tag || arr(r.tags).includes(state.tag))); }
function buildTagOptions(tab) { const tags = [...new Set(tab.records.flatMap(r => arr(r.tags)))].sort(); $('#tagSelect').innerHTML = '<option value="">全部标签</option>' + tags.map(t => `<option value="${esc(t)}">${esc(t)}</option>`).join(''); }
function renderChips(tab) { const tags = [...new Set(tab.records.flatMap(r => arr(r.tags)))].slice(0, 24); $('#chips').innerHTML = `<button class="chip ${state.tag === '' ? 'active' : ''}" data-tag="">全部</button>` + tags.map(t => `<button class="chip ${state.tag === t ? 'active' : ''}" data-tag="${esc(t)}">${esc(t)}</button>`).join(''); }
function renderRecordsOnly() { const tab = getTab(); const records = filterRecords(tab); let body = ''; if (state.view === 'table') body = renderTable(records); else if (tab.id === 'timeline') body = renderTimeline(records); else if (tab.id === 'narrative_chains') body = renderChains(records); else body = renderCards(records); $('#recordsHost').innerHTML = `<div class="result-note">当前显示 <b>${records.length}</b> / ${tab.records.length} 条记录。点击“查看完整详情”可打开中文详情面板。</div>` + body + renderModels() + renderSourcesPanel(tab) + `<div class="footer-note">后续可继续补充:${esc(arr(DATA.schema_note?.recommended_next_collection).slice(0,7).join(';'))}</div>`; }
function render() { const tab = getTab(); document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === state.tab)); buildTagOptions(tab); $('#tagSelect').value = state.tag; $('#content').innerHTML = renderIntro(tab) + '<div class="chipbar" id="chips"></div><div id="recordsHost"></div>'; renderChips(tab); renderRecordsOnly(); }
function findRecord(id) { for (const t of DATA.tabs) for (const r of t.records) if (r.id === id) return r; }
function sectionHtml(sec) { let h = `<section class="detail-section"><h3>${esc(sec.title || '详情')}</h3>`; if (sec.text) h += `<p>${esc(sec.text)}</p>`; if (sec.bullets) h += `<ul>${arr(sec.bullets).map(b => `<li>${esc(b)}</li>`).join('')}</ul>`; if (sec.table) { const rows = arr(sec.table); const keys = [...new Set(rows.flatMap(o => Object.keys(o)))]; h += `<div class="table-wrap"><table><thead><tr>${keys.map(k => `<th>${esc(k)}</th>`).join('')}</tr></thead><tbody>${rows.map(row => `<tr>${keys.map(k => `<td>${esc(textOf(row[k]))}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`; } return h + '</section>'; }
function openModal(id) { const r = findRecord(id); if (!r) return; $('#modalTitle').textContent = `${r.id} · ${r.title}`; $('#modalSummary').textContent = r.summary || ''; const fields = Object.entries(r.fields || {}).map(([k,v]) => `<div class="kv"><b>${esc(k)}</b><span>${esc(textOf(v))}</span></div>`).join(''); const sections = arr(r.detail_sections).map(sectionHtml).join(''); $('#modalBody').innerHTML = `<div class="tags">${tagHtml(r.tags)}${sourceHtml(r.source_refs)}</div><div class="detail-grid">${fields || '<div class="kv"><b>说明</b><span>该条目暂无额外字段,主要内容已在摘要和下方说明中展示。</span></div>'}</div>${sections || '<section class="detail-section"><h3>说明</h3><p>该条目暂无二级详情,已展示全部可读字段。</p></section>'}${sourceDetail(r.source_refs)}`; $('#modalBackdrop').style.display = 'flex'; }
function closeModal() { $('#modalBackdrop').style.display = 'none'; }
function csvEscape(v) { return '"' + textOf(v).replace(/"/g, '""') + '"'; }
function exportCurrentTabCsv() { const tab = getTab(); const rows = filterRecords(tab); const baseCols = ['id','title','summary','category','tags','source_refs']; const dyn = [...new Set(rows.flatMap(r => Object.keys(r.fields || {})))]; const header = baseCols.concat(dyn); const lines = [header.map(csvEscape).join(',')]; rows.forEach(r => { lines.push(header.map(c => csvEscape(c in r ? (Array.isArray(r[c]) ? r[c].join('、') : r[c]) : (r.fields || {})[c])).join(',')); }); const blob = new Blob(['' + lines.join('\n')], {type:'text/csv;charset=utf-8'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = tab.id + '_records.csv'; a.click(); URL.revokeObjectURL(a.href); }
function init() { $('#tabs').innerHTML = DATA.tabs.map(t => `<button class="tab-btn" data-tab="${esc(t.id)}">${esc(t.name)} <small>(${t.records.length})</small></button>`).join(''); $('#tabs').addEventListener('click', e => { const btn = e.target.closest('.tab-btn'); if (!btn) return; state.tab = btn.dataset.tab; state.tag = ''; state.q = $('#searchInput').value.trim().toLowerCase(); render(); window.scrollTo({top:0,behavior:'smooth'}); }); $('#searchInput').addEventListener('input', e => { state.q = e.target.value.trim().toLowerCase(); renderRecordsOnly(); }); $('#tagSelect').addEventListener('change', e => { state.tag = e.target.value; renderChips(getTab()); renderRecordsOnly(); }); $('#viewSelect').addEventListener('change', e => { state.view = e.target.value; renderRecordsOnly(); }); $('#content').addEventListener('click', e => { const btn = e.target.closest('[data-open]'); if (btn) openModal(btn.dataset.open); const chip = e.target.closest('[data-tag]'); if (chip) { state.tag = chip.dataset.tag; $('#tagSelect').value = state.tag; renderChips(getTab()); renderRecordsOnly(); } }); $('#exportBtn').addEventListener('click', exportCurrentTabCsv); $('#closeBtn').addEventListener('click', closeModal); $('#modalBackdrop').addEventListener('click', e => { if (e.target.id === 'modalBackdrop') closeModal(); }); window.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); render(); }
try { init(); } catch (err) { console.error(err); $('#content').innerHTML = `<div class="empty">页面加载出错:${esc(err.message)}。请使用新版 v5 文件或更换 Chrome / Edge 打开。</div>`; }
</script></body></html>
@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>驻印太美国军事群体专题数据库</title>
<style>
:root {
--bg0:#5b0d14;
--bg1:#7f1620;
--bg2:#2b080b;
--card:#ffffff;
--ink:#241015;
--muted:#6f5960;
--line:#ead9dc;
--soft:#fff7f8;
--accent:#b6202d;
--accent2:#f2d7dc;
--ok:#1f7a4d;
--warn:#a96b00;
--danger:#b6202d;
--shadow:0 18px 45px rgba(27,5,9,.18);
}
*{box-sizing:border-box}
body{margin:0;font-family:"Microsoft YaHei", "PingFang SC", Arial, sans-serif;background:radial-gradient(circle at 20% 0%, #9e2430 0, var(--bg1) 22%, var(--bg0) 50%, var(--bg2) 100%);color:var(--ink);}
.hero{max-width:1320px;margin:0 auto;padding:34px 24px 18px;color:#fff;}
.hero-top{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;}
.title-block h1{font-size:34px;margin:0 0 10px;letter-spacing:.5px}
.title-block p{margin:0;max-width:900px;line-height:1.75;color:#ffe9ed;font-size:14px}
.version{border:1px solid rgba(255,255,255,.35);padding:10px 14px;border-radius:999px;background:rgba(255,255,255,.1);white-space:nowrap;font-weight:700}
.shell{max-width:1320px;margin:0 auto 40px;padding:0 24px 28px;}
.panel{background:rgba(255,255,255,.96);border:1px solid rgba(255,255,255,.45);border-radius:26px;box-shadow:var(--shadow);overflow:hidden;}
.tabs{display:flex;gap:8px;overflow-x:auto;padding:14px 14px 0;background:linear-gradient(180deg,#fff,#fff8f9);border-bottom:1px solid var(--line);}
.tab-btn{border:1px solid var(--line);background:#fff;color:var(--muted);padding:10px 16px;border-radius:999px;font-weight:700;cursor:pointer;white-space:nowrap;transition:.18s;}
.tab-btn:hover{transform:translateY(-1px);border-color:#d9aab1;color:var(--accent)}
.tab-btn.active{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 8px 18px rgba(182,32,45,.22)}
.content{padding:22px;}
.tab-section{display:none}
.tab-section.active{display:block}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:16px;}
.section-head h2{margin:0;font-size:24px;color:#2b0c10}
.section-head p{margin:6px 0 0;color:var(--muted);line-height:1.65}
.toolbar{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0 18px;}
.input,.select{height:42px;border:1px solid var(--line);border-radius:14px;padding:0 12px;background:#fff;outline:none;color:var(--ink);}
.input{min-width:260px;flex:1}
.select{min-width:160px}
.stats-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-bottom:20px;}
.stat{background:linear-gradient(180deg,#fff,#fff6f7);border:1px solid var(--line);border-radius:20px;padding:16px;}
.stat .value{font-size:26px;font-weight:900;color:var(--accent);margin-bottom:6px}
.stat .label{font-weight:800;margin-bottom:8px}
.stat .note{font-size:12px;color:var(--muted);line-height:1.5}
.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;}
.card{background:#fff;border:1px solid var(--line);border-radius:22px;padding:16px;box-shadow:0 10px 26px rgba(58,16,23,.07);position:relative;overflow:hidden;}
.card::before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,var(--accent),#e8a1ab)}
.card h3{margin:4px 0 6px;font-size:19px;color:#2b0c10}
.sub{font-size:13px;color:var(--muted);line-height:1.55;margin-bottom:12px}
.meta-row{display:grid;grid-template-columns:90px 1fr;gap:8px;font-size:13px;margin:8px 0;line-height:1.5}
.meta-row b{color:#5a1b22}
.tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:12px}
.tag{font-size:12px;padding:5px 8px;border-radius:999px;background:var(--soft);border:1px solid var(--accent2);color:#7b2430;font-weight:700}
.risk{position:absolute;right:14px;top:13px;font-size:12px;padding:5px 9px;border-radius:999px;color:#fff;background:var(--warn);font-weight:800}
.risk.high{background:var(--danger)}
.risk.medium{background:var(--warn)}
.risk.low{background:var(--ok)}
.btn{border:0;background:var(--accent);color:#fff;border-radius:12px;padding:10px 12px;font-weight:800;cursor:pointer;margin-top:12px;}
.btn.secondary{background:#fff;color:var(--accent);border:1px solid var(--accent2)}
.table-wrap{overflow:auto;border:1px solid var(--line);border-radius:18px;background:#fff;}
table{border-collapse:collapse;width:100%;min-width:980px}
th,td{border-bottom:1px solid var(--line);padding:12px 12px;text-align:left;vertical-align:top;line-height:1.55;font-size:13px}
th{background:#fff3f5;color:#5b1118;position:sticky;top:0;z-index:1}
tr:hover td{background:#fffafa}
.timeline{position:relative;margin:8px 0 0;padding-left:24px}
.timeline::before{content:"";position:absolute;left:8px;top:0;bottom:0;width:2px;background:#e7b5bd}
.event{position:relative;margin:0 0 14px;padding:14px 14px 14px 18px;background:#fff;border:1px solid var(--line);border-radius:18px;}
.event::before{content:"";position:absolute;left:-22px;top:20px;width:12px;height:12px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 4px #f7dce1}
.event .year{font-weight:900;color:var(--accent);font-size:14px}
.event h3{margin:4px 0 8px}
.columns{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.list-card{border:1px solid var(--line);border-radius:18px;padding:14px;background:#fff}
.list-card h3{margin:0 0 10px;color:#391016}
.list-card ul{margin:0;padding-left:18px;line-height:1.8;color:#443238}
.schema-box{background:#fff;border:1px solid var(--line);border-radius:18px;padding:14px;margin-bottom:12px}
.schema-title{font-weight:900;color:#5b1118;margin-bottom:10px}
.field-list{display:flex;flex-wrap:wrap;gap:8px}
.field{padding:7px 9px;border:1px solid var(--accent2);background:#fff7f8;border-radius:10px;font-size:12px;font-weight:700;color:#6d222b}
.note-box{background:#fff7f8;border:1px solid var(--accent2);border-radius:18px;padding:14px;color:#5d4047;line-height:1.7;margin:14px 0}
.modal-backdrop{display:none;position:fixed;inset:0;background:rgba(25,0,5,.55);z-index:20;padding:28px;overflow:auto}
.modal{max-width:980px;margin:20px auto;background:#fff;border-radius:24px;box-shadow:0 25px 70px rgba(0,0,0,.35);overflow:hidden}
.modal-head{padding:18px 22px;background:linear-gradient(90deg,#7f1620,#b6202d);color:#fff;display:flex;justify-content:space-between;gap:14px;align-items:center}
.modal-head h2{margin:0;font-size:22px}
.close{background:rgba(255,255,255,.16);color:#fff;border:1px solid rgba(255,255,255,.35);border-radius:12px;padding:8px 10px;cursor:pointer;font-weight:900}
.modal-body{padding:20px}
.detail-grid{display:grid;grid-template-columns:160px 1fr;gap:10px;border:1px solid var(--line);border-radius:18px;overflow:hidden}
.detail-k{background:#fff6f7;color:#6d222b;font-weight:900;padding:11px;border-bottom:1px solid var(--line)}
.detail-v{padding:11px;border-bottom:1px solid var(--line);line-height:1.65}
.footer{padding:16px 22px;background:#fff8f9;border-top:1px solid var(--line);color:var(--muted);font-size:12px;line-height:1.6}
.empty{text-align:center;padding:28px;color:var(--muted);background:#fff;border:1px dashed var(--line);border-radius:18px}
@media(max-width:1000px){.stats-grid{grid-template-columns:repeat(2,1fr)}.grid{grid-template-columns:repeat(2,1fr)}.columns{grid-template-columns:1fr}}
@media(max-width:680px){.hero-top{flex-direction:column}.stats-grid,.grid{grid-template-columns:1fr}.content{padding:14px}.detail-grid{grid-template-columns:1fr}.detail-k{border-bottom:0;padding-bottom:0}.input{min-width:100%}}
</style>
</head>
<body>
<header class="hero">
<div class="hero-top">
<div class="title-block">
<h1>驻印太美国军事群体专题库</h1>
<p>围绕日本、韩国、关岛等地区的美国军事群体、基地网络、地方舆情、SOFA争议、环境健康、NEO撤离与信息安全风险进行结构化展示。</p>
</div>
<div class="version">V1.2</div>
</div>
</header>
<main class="shell">
<section class="panel">
<nav class="tabs" id="tabs"></nav>
<div class="content" id="content"></div>
<div class="footer">资料边界:本页面仅展示公开资料研究数据,避免包含非公开部署细节、敏感坐标、警备路线、具体集合点或可操作化攻击信息。建议在正式发布前继续核验年度人员、预算、地方投诉与民调数据。</div>
</section>
</main>
<div class="modal-backdrop" id="modalBackdrop" onclick="if(event.target.id==='modalBackdrop') closeModal()">
<div class="modal">
<div class="modal-head"><h2 id="modalTitle">详情</h2><button class="close" onclick="closeModal()">关闭</button></div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
<script>
const DB = {"metadata": {"title": "驻印太美国军事群体专题数据库", "version": "V1.2", "language": "zh-CN", "generated_at": "2026-06-25", "scope": "公开资料研究型专题,覆盖驻日本、韩国、关岛等印太地区美国军事群体、基地社区、地方舆情、法律制度、环境健康、NEO与信息安全风险。", "safety_note": "本数据库只用于公开资料研究与专题展示,不包含非公开部署细节、敏感坐标、警备路线、具体集合点或可操作化攻击信息。", "primary_modules": ["基地档案", "地方舆情与民调", "历史事件", "人群画像", "SOFA与预算", "环境健康", "NEO与信息安全", "涉华涉台立场"]}, "overview_metrics": [{"label": "印太司令部全区军事人员", "value": "约36万", "note": "skill速查口径,需按DMDC或DoD最新口径周期更新"}, {"label": "驻日美军", "value": "约53,500", "note": "日本全境现役规模概数"}, {"label": "驻韩美军", "value": "约23,600—28,500", "note": "不同统计口径包括实际现役与政策上限口径"}, {"label": "关岛美军", "value": "约7,000+", "note": "不含全部家属、文职和承包商扩展口径"}, {"label": "驻日美军家属", "value": "约35,000", "note": "USFJ公开口径"}, {"label": "日本当地雇员", "value": "约25,000", "note": "驻日基地经济与同盟分担重要变量"}, {"label": "DoDEA太平洋学校", "value": "46所", "note": "服务太平洋美军与合格DoD文职子女"}, {"label": "核心矛盾", "value": "威慑 / 负担 / 法律 / 环境", "note": "专题分析四条主线"}], "tabs": [{"id": "overview", "name": "总览"}, {"id": "bases", "name": "基地档案"}, {"id": "opinion", "name": "地方舆情"}, {"id": "events", "name": "事件时间线"}, {"id": "groups", "name": "人群画像"}, {"id": "legal_budget", "name": "SOFA与预算"}, {"id": "environment", "name": "环境健康"}, {"id": "neo_opsec", "name": "NEO与信息安全"}, {"id": "geopolitics", "name": "涉华涉台"}, {"id": "schemas", "name": "数据字段"}], "bases": [{"id": "JP-YOKOTA-001", "name_cn": "横田空军基地", "name_en": "Yokota Air Base", "country_area": "日本", "region": "日本本土", "location": "东京都多摩地区,东京西北方向", "service": "空军/太空军/联合指挥", "command": "USFJ、第五空军、374th Airlift Wing", "core_position": "驻日美军司令部所在地;印太方向空运、后勤、联络和危机协调枢纽", "units": ["374th Airlift Wing", "第五空军相关机构", "USFJ总部", "多个任务伙伴"], "missions": ["战区空运", "人员与物资输送", "危机支援", "日美联合协调", "首都圈军事联络"], "population": "374th Airlift Wing支撑约11,500人的基地人口;USFJ整体包含约60,000名军人、35,000名家属、7,000名DoD文职/承包商与约25,000名日本雇员。", "community": "首都圈型基地,家属、文职、日方雇员、任务伙伴较多,具有总部—后勤—家庭社区复合属性。", "facilities": ["空运设施", "指挥办公设施", "住房", "学校", "家庭支援", "医疗与生活服务"], "controversies": ["噪音", "空域管制", "首都圈军事存在", "基地开放与周边秩序"], "opinion_tags": ["首都圈基地", "指挥中枢", "空运枢纽", "噪音", "空域"], "visual_hint": "作为驻日美军总部节点展示,并与横须贺、嘉手纳、岩国形成指挥—海军—空军—陆战队网络。", "risk_level": "中", "source_note": "USFJ与Yokota官方公开资料"}, {"id": "JP-YOKOSUKA-002", "name_cn": "横须贺海军基地", "name_en": "Commander Fleet Activities Yokosuka", "country_area": "日本", "region": "日本本土", "location": "神奈川县横须贺市", "service": "海军", "command": "Commander, Navy Region Japan;第七舰队支撑体系", "core_position": "第七舰队前沿部署核心母港之一,支撑航母打击群及前沿部署海军力量。", "units": ["第七舰队相关前沿部署单位", "舰艇维修与后勤单位", "港口保障单位"], "missions": ["舰艇母港", "舰队后勤", "维修", "家庭支援", "港口保障", "同盟联络"], "population": "港口城市型基地,军人、家属、文职、承包商与当地商业区互动频繁。", "community": "基地与横须贺城市经济、旅游、港口身份高度绑定,同时舰艇可见性强。", "facilities": ["港口", "舰艇支援设施", "家庭服务", "宗教服务", "住房", "生活配套", "紧急管理设施"], "controversies": ["核动力舰艇停靠", "港口安全", "夜间秩序", "地方商业依赖", "军人外出行为"], "opinion_tags": ["第七舰队", "航母母港", "港口城市", "地方商业", "核动力舰艇"], "visual_hint": "适合做港口—舰艇—城市经济三层图,不展示具体舰艇行动细节。", "risk_level": "中高", "source_note": "CFA Yokosuka官方公开资料与日媒公开报道"}, {"id": "JP-KADENA-003", "name_cn": "嘉手纳空军基Line truncated
const state = { active:'overview' };
const $ = sel => document.querySelector(sel);
const content = $('#content');
function esc(v){
if(v === null || v === undefined) return '';
return String(v).replace(/[&<>"']/g, s=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[s]));
}
function arr(v){ return Array.isArray(v) ? v : (v ? [v] : []); }
function tags(list){ return arr(list).map(t=>`<span class="tag">${esc(t)}</span>`).join(''); }
function riskClass(r){ if(!r) return 'medium'; if(r.includes('高')) return 'high'; if(r.includes('低')) return 'low'; return 'medium'; }
function includesText(obj, q){ return JSON.stringify(obj).toLowerCase().includes(q.toLowerCase()); }
function filterByRegion(list, region){ return !region || region==='全部' ? list : list.filter(x => (x.region||x.country_area||'').includes(region) || (x.area||'').includes(region)); }
function initTabs(){
$('#tabs').innerHTML = DB.tabs.map(t=>`<button class="tab-btn ${t.id===state.active?'active':''}" onclick="switchTab('${t.id}')">${esc(t.name)}</button>`).join('');
}
function switchTab(id){ state.active=id; initTabs(); render(); window.scrollTo({top:0,behavior:'smooth'}); }
function head(title, desc){ return `<div class="section-head"><div><h2>${esc(title)}</h2><p>${esc(desc)}</p></div></div>`; }
function renderOverview(){
const stats = DB.overview_metrics.map(m=>`<div class="stat"><div class="value">${esc(m.value)}</div><div class="label">${esc(m.label)}</div><div class="note">${esc(m.note)}</div></div>`).join('');
const baseCount = DB.bases.length, opinionCount = DB.local_opinion.length, eventCount = DB.events.length, groupCount = DB.groups.length;
return `${head('专题总览','以“前沿威慑—基地社会—同盟成本—地方冲突—家庭压力—信息安全—台海/朝鲜半岛风险”为主线。')}
<div class="stats-grid">${stats}</div>
<div class="columns">
<div class="list-card"><h3>数据库构成</h3><ul>
<li>基地档案:${baseCount} 条</li><li>地方舆情/民调:${opinionCount} 条</li><li>历史事件:${eventCount} 条</li><li>人群画像:${groupCount} 类</li>
<li>SOFA制度:${DB.legal_sofa.length} 条</li><li>预算条目:${DB.budget.length} 条</li><li>环境健康:${DB.environment.length} 条</li>
</ul></div>
<div class="list-card"><h3>专题主线</h3><ul>
<li>基地网络:日本本土、冲绳、韩国、关岛三层结构。</li>
<li>地方舆情:冲绳反基地、韩国全国/地方分裂、关岛原住民与住房压力。</li>
<li>制度争议:SOFA、刑事管辖、环境进入权、承包商身份。</li>
<li>风险维度:NEO家庭压力、OPSEC、台海和朝鲜半岛危机暴露。</li>
</ul></div>
</div>
<div class="note-box"><b>使用建议:</b>先用“基地档案”建立核心对象,再用“事件时间线”和“地方舆情”解释社会反应,最后用“SOFA与预算”“环境健康”“NEO与信息安全”补足专题深度。</div>`;
}
function baseCard(b){
return `<article class="card"><span class="risk ${riskClass(b.risk_level)}">风险${esc(b.risk_level||'中')}</span><h3>${esc(b.name_cn)}</h3><div class="sub">${esc(b.name_en)} · ${esc(b.country_area)} / ${esc(b.region)}</div>
<div class="meta-row"><b>军种</b><span>${esc(b.service)}</span></div>
<div class="meta-row"><b>上级</b><span>${esc(b.command)}</span></div>
<div class="meta-row"><b>定位</b><span>${esc(b.core_position)}</span></div>
<div class="tags">${tags(b.opinion_tags)}</div><button class="btn" onclick="openBase('${b.id}')">查看详情</button></article>`;
}
function renderBases(){
const regions = ['全部', ...new Set(DB.bases.map(b=>b.region))];
return `${head('基地详细档案','按地区、军种和关键词检索基地,点击卡片可查看完整画像。')}
<div class="toolbar"><input class="input" id="baseSearch" placeholder="搜索:基地名、军种、争议、任务、标签..." oninput="updateBases()"><select class="select" id="baseRegion" onchange="updateBases()">${regions.map(r=>`<option>${esc(r)}</option>`).join('')}</select></div><div class="grid" id="baseGrid"></div>`;
}
function updateBases(){
const q = ($('#baseSearch')?.value || '').trim(); const region = $('#baseRegion')?.value || '全部';
let list = filterByRegion(DB.bases, region); if(q) list = list.filter(b=>includesText(b,q));
$('#baseGrid').innerHTML = list.length ? list.map(baseCard).join('') : '<div class="empty">没有找到匹配基地。</div>';
}
function openBase(id){
const b = DB.bases.find(x=>x.id===id); if(!b) return;
const rows = [
['基地ID', b.id], ['中文名', b.name_cn], ['英文名', b.name_en], ['国家/地区', b.country_area], ['区域', b.region], ['所在地', b.location], ['所属军种', b.service], ['上级体系', b.command], ['核心定位', b.core_position], ['主要单位', arr(b.units).join('、')], ['主要任务', arr(b.missions).join('、')], ['人员/社区', b.population], ['社区特征', b.community], ['主要设施', arr(b.facilities).join('、')], ['争议点', arr(b.controversies).join('、')], ['舆情标签', arr(b.opinion_tags).join('、')], ['可视化建议', b.visual_hint], ['风险等级', b.risk_level], ['资料来源说明', b.source_note]
];
openModal(b.name_cn, detailRows(rows));
}
function renderOpinion(){
const regions = ['全部', ...new Set(DB.local_opinion.map(o=>o.region))];
return `${head('地方舆情与民调数据库','重点呈现冲绳、韩国、关岛三个典型场景:地方负担、全国/地方分裂、原住民与住房压力。')}
<div class="toolbar"><input class="input" id="opSearch" placeholder="搜索:边野古、THAAD、关岛、住房、SOFA..." oninput="updateOpinion()"><select class="select" id="opRegion" onchange="updateOpinion()">${regions.map(r=>`<option>${esc(r)}</option>`).join('')}</select></div>
<div class="table-wrap"><table><thead><tr><th>时间</th><th>地区</th><th>议题</th><th>核心数据</th><th>支持/反对</th><th>深层矛盾</th><th>标签</th></tr></thead><tbody id="opRows"></tbody></table></div>`;
}
function updateOpinion(){
const q = ($('#opSearch')?.value || '').trim(); const region = $('#opRegion')?.value || '全部';
let list = filterByRegion(DB.local_opinion, region); if(q) list = list.filter(o=>includesText(o,q));
$('#opRows').innerHTML = list.map(o=>`<tr><td>${esc(o.date)}</td><td>${esc(o.area)}</td><td><b>${esc(o.issue)}</b><br><span class="sub">${esc(o.source_type)} / 可信度:${esc(o.confidence)}</span></td><td>${esc(o.core_data)}</td><td>支持:${esc(o.support)}<br>反对:${esc(o.oppose)}<br>中立:${esc(o.neutral)}</td><td>${esc(o.deep_conflict)}</td><td>${tags(o.tags)}</td></tr>`).join('') || '<tr><td colspan="7">没有匹配结果。</td></tr>';
}
function renderEvents(){
return `${head('历史事件时间线','将个案事件与SOFA、地方自治、环境健康、基地扩张和大国博弈连接。')}
<div class="toolbar"><input class="input" id="eventSearch" placeholder="搜索:冲绳、SOFA、THAAD、PFAS、关岛..." oninput="updateEvents()"></div>
<div class="timeline" id="eventTimeline"></div>`;
}
function updateEvents(){
const q = ($('#eventSearch')?.value || '').trim(); let list = DB.events; if(q) list = list.filter(e=>includesText(e,q));
$('#eventTimeline').innerHTML = list.map(e=>`<div class="event"><div class="year">${esc(e.date)} · ${esc(e.region)} · ${esc(e.type)}</div><h3>${esc(e.title)}</h3><p>${esc(e.summary)}</p><p><b>影响:</b>${esc(e.impact)}</p><p><b>深层矛盾:</b>${esc(e.deep_conflict)}</p><div class="tags">${tags(e.tags)}</div></div>`).join('') || '<div class="empty">没有匹配事件。</div>';
}
function renderGroups(){
return `${head('人群画像','把“美军群体”拆成军人、家属、文职、承包商、当地雇员和混合国籍家庭。')}
<div class="toolbar"><input class="input" id="groupSearch" placeholder="搜索:配偶、子女、承包商、NEO、就业..." oninput="updateGroups()"></div><div class="grid" id="groupGrid"></div>`;
}
function updateGroups(){
const q = ($('#groupSearch')?.value || '').trim(); let list = DB.groups; if(q) list = list.filter(g=>includesText(g,q));
$('#groupGrid').innerHTML = list.map(g=>`<article class="card"><h3>${esc(g.name)}</h3><div class="sub">${esc(g.category)} · ${esc(g.display)}</div><p>${esc(g.profile)}</p><div class="meta-row"><b>主要需求</b><span>${esc(arr(g.needs).join('、'))}</span></div><div class="meta-row"><b>核心压力</b><span>${esc(arr(g.pressures).join('、'))}</span></div><div class="tags">${tags(g.risks)}</div></article>`).join('') || '<div class="empty">没有匹配画像。</div>';
}
function renderLegalBudget(){
return `${head('SOFA与预算','把法律身份、刑事管辖、环境进入权、同盟分担和基地建设放在同一制度框架下观察。')}
<h3>SOFA制度要点</h3><div class="table-wrap"><table><thead><tr><th>议题</th><th>日本方向</th><th>韩国方向</th><th>专题解释</th></tr></thead><tbody>${DB.legal_sofa.map(x=>`<tr><td><b>${esc(x.topic)}</b></td><td>${esc(x.japan)}</td><td>${esc(x.korea)}</td><td>${esc(x.meaning)}</td></tr>`).join('')}</tbody></table></div>
<h3 style="margin-top:20px">预算与同盟分担</h3><div class="table-wrap"><table><thead><tr><th>地区</th><th>时期</th><th>项目</th><th>金额/口径</th><th>说明</th><th>标签</th></tr></thead><tbody>${DB.budget.map(x=>`<tr><td>${esc(x.area)}</td><td>${esc(x.period)}</td><td><b>${esc(x.item)}</b></td><td>${esc(x.amount)}</td><td>${esc(x.explain)}</td><td>${tags(x.tags)}</td></tr>`).join('')}</tbody></table></div>`;
}
function renderEnvironment(){
return `${head('环境与公共健康','重点覆盖PFAS、噪音、土地占用、海洋生态等与基地社会关系最密切的议题。')}
<div class="grid">${DB.environment.map(e=>`<article class="card"><h3>${esc(e.type)}</h3><div class="sub">涉及地点:${esc(arr(e.locations).join('、'))}</div><p><b>已知问题:</b>${esc(e.known_issue)}</p><p><b>社会影响:</b>${esc(e.social_impact)}</p><div class="meta-row"><b>字段</b><span>${esc(arr(e.fields).join('、'))}</span></div><div class="tags">${tags(e.tags)}</div></article>`).join('')}</div>`;
}
function renderNeoOpsec(){
return `${head('NEO与信息安全','以家庭风险、撤离资格、信息安全治理为主题,不展示具体路线、集合点或可操作细节。')}
<div class="columns"><div><h3>NEO风险场景</h3>${DB.neo.map(n=>`<div class="list-card" style="margin-bottom:12px"><h3>${esc(n.region)}</h3><p><b>触发:</b>${esc(n.risk_trigger)}</p><p><b>对象:</b>${esc(n.objects)}</p><p><b>压力:</b>${esc(n.pressure)}</p><div class="tags">${tags(n.tags)}</div></div>`).join('')}</div>
<div><h3>OPSEC风险类型</h3>${DB.opsec.map(o=>`<div class="list-card" style="margin-bottom:12px"><h3>${esc(o.risk_type)}</h3><p><b>案例:</b>${esc(o.case)}</p><p><b>对象:</b>${esc(o.objects)}</p><p><b>机制:</b>${esc(o.mechanism)}</p><p><b>展示口径:</b>${esc(o.display)}</p><div class="tags">${tags(o.tags)}</div></div>`).join('')}</div></div>`;
}
function renderGeopolitics(){
return `${head('涉华涉台与地缘风险','从第一岛链、台湾问题、朝鲜半岛、关岛角色与中国叙事五个角度呈现。')}
<div class="grid">${DB.geopolitics.map(g=>`<article class="card"><h3>${esc(g.issue)}</h3><p><b>美国官方口径:</b>${esc(g.official_line)}</p><p><b>基地/群体关联:</b>${esc(g.base_link)}</p><p><b>社会争议:</b>${esc(g.social_conflict)}</p><div class="tags">${tags(g.tags)}</div></article>`).join('')}</div>`;
}
function renderSchemas(){
const boxes = Object.entries(DB.schemas).map(([k, fields])=>`<div class="schema-box"><div class="schema-title">${esc(k)}</div><div class="field-list">${fields.map(f=>`<span class="field">${esc(f)}</span>`).join('')}</div></div>`).join('');
const gaps = DB.data_gaps.map(g=>`<tr><td><b>${esc(g.gap)}</b></td><td>${esc(g.priority)}</td><td>${esc(g.explain)}</td></tr>`).join('');
return `${head('数据字段与后续缺口','用于后续扩展JSON、接入外部数据源和长期更新。')}${boxes}<h3>当前数据缺口</h3><div class="table-wrap"><table><thead><tr><th>缺口</th><th>优先级</th><th>说明</th></tr></thead><tbody>${gaps}</tbody></table></div>`;
}
function detailRows(rows){
return `<div class="detail-grid">${rows.map(([k,v])=>`<div class="detail-k">${esc(k)}</div><div class="detail-v">${esc(v)}</div>`).join('')}</div>`;
}
function openModal(title, body){ $('#modalTitle').textContent = title; $('#modalBody').innerHTML = body; $('#modalBackdrop').style.display='block'; document.body.style.overflow='hidden'; }
function closeModal(){ $('#modalBackdrop').style.display='none'; document.body.style.overflow=''; }
function render(){
let html = '';
if(state.active==='overview') html = renderOverview();
if(state.active==='bases') html = renderBases();
if(state.active==='opinion') html = renderOpinion();
if(state.active==='events') html = renderEvents();
if(state.active==='groups') html = renderGroups();
if(state.active==='legal_budget') html = renderLegalBudget();
if(state.active==='environment') html = renderEnvironment();
if(state.active==='neo_opsec') html = renderNeoOpsec();
if(state.active==='geopolitics') html = renderGeopolitics();
if(state.active==='schemas') html = renderSchemas();
content.innerHTML = `<section class="tab-section active">${html}</section>`;
if(state.active==='bases') updateBases();
if(state.active==='opinion') updateOpinion();
if(state.active==='events') updateEvents();
if(state.active==='groups') updateGroups();
}
initTabs(); render();
</script>
</body>
</html>
@@ -0,0 +1,204 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>美国涉华涉台政策权力网络专题平台</title>
<style>
:root {
--bg1:#7a0d18; --bg2:#3d0710; --card:#fffaf7; --card2:#fff4ec; --ink:#241314; --muted:#6f5452;
--line:#e8cfc9; --accent:#b21f2d; --accent2:#d88a5b; --deep:#520812; --good:#1f7a5b; --warn:#bd6b00; --danger:#a41422;
}
* { box-sizing: border-box; }
body { margin:0; font-family: "Microsoft YaHei", "PingFang SC", system-ui, -apple-system, Segoe UI, sans-serif; color:var(--ink); background: radial-gradient(circle at 20% 0%, #a12b38 0, var(--bg1) 30%, var(--bg2) 100%); }
header { padding: 34px 42px 22px; color:#fff; position:relative; overflow:hidden; }
header:after { content:""; position:absolute; right:-130px; top:-120px; width:360px; height:360px; border:1px solid rgba(255,255,255,.16); border-radius:50%; }
h1 { margin:0; font-size:32px; letter-spacing:.04em; }
.subtitle { margin-top:10px; max-width:1120px; opacity:.88; line-height:1.7; font-size:14px; }
.topbar { display:flex; gap:12px; margin-top:20px; align-items:center; flex-wrap:wrap; }
.search { flex:1; min-width:280px; background:rgba(255,255,255,.14); border:1px solid rgba(255,255,255,.38); color:#fff; border-radius:999px; padding:12px 18px; outline:none; }
.search::placeholder { color:rgba(255,255,255,.72); }
.btn { border:none; background:#fff; color:var(--deep); border-radius:999px; padding:10px 16px; cursor:pointer; font-weight:700; box-shadow:0 10px 24px rgba(0,0,0,.12); }
.container { padding:0 28px 42px; }
.shell { background:rgba(255,255,255,.92); border:1px solid rgba(255,255,255,.55); border-radius:28px; min-height:680px; box-shadow:0 28px 70px rgba(0,0,0,.25); overflow:hidden; }
.tabs { display:flex; gap:8px; overflow:auto; padding:14px 18px; background:#fff8f4; border-bottom:1px solid var(--line); position:sticky; top:0; z-index:5; }
.tab { white-space:nowrap; padding:10px 15px; border:1px solid var(--line); background:white; color:var(--muted); border-radius:999px; cursor:pointer; font-weight:700; }
.tab.active { background:var(--accent); color:#fff; border-color:var(--accent); }
.panel { display:none; padding:24px; }
.panel.active { display:block; }
.grid { display:grid; grid-template-columns: repeat(auto-fill, minmax(280px,1fr)); gap:16px; }
.card { background:var(--card); border:1px solid var(--line); border-radius:20px; padding:18px; box-shadow:0 10px 20px rgba(80,20,20,.06); cursor:pointer; transition:.18s; }
.card:hover { transform:translateY(-2px); box-shadow:0 16px 30px rgba(80,20,20,.12); }
.card h3 { margin:0 0 8px; font-size:18px; color:var(--deep); }
.meta { color:var(--muted); font-size:13px; line-height:1.6; }
.tags { display:flex; flex-wrap:wrap; gap:6px; margin:10px 0; }
.tag { border:1px solid #e0b8ad; color:#7a1a24; background:#fff; border-radius:999px; padding:4px 9px; font-size:12px; }
.kpis { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px,1fr)); gap:14px; margin-bottom:18px; }
.kpi { background:linear-gradient(135deg,#fff,#fff0e7); border:1px solid var(--line); border-radius:18px; padding:18px; }
.kpi .num { font-size:32px; font-weight:800; color:var(--accent); }
.kpi .label { color:var(--muted); margin-top:4px; }
.section-title { display:flex; align-items:center; justify-content:space-between; gap:14px; margin:0 0 14px; }
.section-title h2 { margin:0; font-size:22px; color:var(--deep); }
.filters { display:flex; gap:8px; flex-wrap:wrap; margin:12px 0 18px; }
.filter { padding:8px 12px; border-radius:999px; border:1px solid var(--line); background:#fff; cursor:pointer; color:var(--muted); }
.filter.active { background:var(--deep); color:#fff; }
.table-wrap { overflow:auto; border:1px solid var(--line); border-radius:18px; background:#fff; }
table { border-collapse:collapse; width:100%; min-width:900px; }
th,td { padding:12px 13px; border-bottom:1px solid #f1ded9; text-align:left; vertical-align:top; font-size:13px; line-height:1.55; }
th { background:#fff0e9; color:#5a1520; position:sticky; top:0; z-index:1; }
tr:hover td { background:#fffaf7; }
.chain { display:grid; gap:10px; }
.step { display:flex; gap:12px; align-items:flex-start; background:#fff; border:1px solid var(--line); border-radius:16px; padding:12px; }
.step .dot { width:26px; height:26px; border-radius:50%; background:var(--accent); color:#fff; display:grid; place-items:center; flex:0 0 auto; font-size:12px; font-weight:800; }
.risk { display:grid; grid-template-columns:repeat(auto-fit, minmax(230px,1fr)); gap:12px; }
.riskbox { border:1px solid var(--line); border-radius:16px; padding:14px; background:#fff; }
.riskbox strong { color:var(--deep); }
.badge { padding:3px 8px; border-radius:999px; font-size:12px; background:#fff0e9; color:#7a1a24; }
.graphbar { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-bottom:12px; }
.graph { background:#fff; border:1px solid var(--line); border-radius:20px; height:660px; overflow:hidden; position:relative; }
svg { width:100%; height:100%; }
.node-label { font-size:12px; fill:#3d0710; font-weight:700; pointer-events:none; }
.edge { stroke:#a4786d; stroke-opacity:.35; }
.edge.strong { stroke:#a41422; stroke-opacity:.75; }
.node { cursor:pointer; }
.legend { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; color:var(--muted); font-size:13px; }
.legend span { background:#fff; border:1px solid var(--line); border-radius:999px; padding:5px 9px; }
.modal { position:fixed; inset:0; background:rgba(0,0,0,.44); display:none; align-items:center; justify-content:center; z-index:20; padding:20px; }
.modal.show { display:flex; }
.modal-card { width:min(860px, 94vw); max-height:86vh; overflow:auto; background:#fff; border-radius:24px; padding:24px; box-shadow:0 30px 100px rgba(0,0,0,.4); }
.modal-head { display:flex; justify-content:space-between; gap:14px; align-items:flex-start; border-bottom:1px solid var(--line); padding-bottom:12px; margin-bottom:14px; }
.close { border:none; border-radius:50%; width:36px; height:36px; background:#fff0e9; color:var(--deep); cursor:pointer; font-size:20px; }
.detail-grid { display:grid; grid-template-columns:180px 1fr; gap:8px 14px; font-size:14px; line-height:1.7; }
.detail-grid b { color:#6f242c; }
.small { font-size:12px; color:var(--muted); }
.source-link { color:var(--accent); text-decoration:none; }
.empty { padding:24px; color:var(--muted); text-align:center; }
@media (max-width:760px) { header { padding:24px 18px; } .container { padding:0 12px 28px; } .panel { padding:16px; } .detail-grid { grid-template-columns:1fr; } }
</style>
</head>
<body>
<header>
<h1>美国涉华涉台政策权力网络专题平台</h1>
<div class="subtitle">基于公开资料与 Skill 框架整理的结构化研究平台:覆盖白宫、行政部门、国会委员会、利益集团、智库媒体、摩擦事件、风险指标与关系图谱。研究边界:公开资料、结构性分析、风险预警;不包含私人信息或定向影响操作。</div>
<div class="topbar"><input id="globalSearch" class="search" placeholder="搜索节点、人物、委员会、政策工具、事件、风险指标……"><button class="btn" onclick="downloadJSON()">下载当前 JSON</button><button class="btn" onclick="resetSearch()">重置搜索</button></div>
</header>
<div class="container">
<div class="shell">
<nav class="tabs" id="tabs"></nav>
<section id="overview" class="panel active"></section>
<section id="nodes" class="panel"></section>
<section id="tools" class="panel"></section>
<section id="congress" class="panel"></section>
<section id="graph" class="panel"></section>
<section id="chains" class="panel"></section>
<section id="interest" class="panel"></section>
<section id="thinktank" class="panel"></section>
<section id="events" class="panel"></section>
<section id="risks" class="panel"></section>
<section id="sources" class="panel"></section>
</div>
</div>
<div id="modal" class="modal"><div class="modal-card"><div class="modal-head"><div><h2 id="modalTitle" style="margin:0;color:var(--deep)"></h2><div id="modalSub" class="meta"></div></div><button class="close" onclick="closeModal()">×</button></div><div id="modalBody"></div></div></div>
<script>
const DATA = {"metadata": {"标题": "美国涉华涉台政策权力网络专题数据库", "英文标题": "US-China Policy Power Network Dataset", "版本": "v1.0", "生成日期": "2026-06-25", "时间口径": "截至2026年6月25日的公开资料与本专题已补充数据。", "研究边界": "仅使用公开资料进行结构性政策研究、风险预警和认知防护分析;不包含私人信息、定向影响操作或作战敏感细节。", "证据等级说明": {"A": "官方网页、国会官网、政府文件直接证明", "B": "权威媒体、主流智库、权威数据库支持", "C": "基于公开结构的合理推断,需后续核验"}}, "dashboard": {"栏目": ["总览仪表盘", "白宫与NSC", "行政工具系统", "国会网络", "利益集团", "智库媒体", "摩擦事件", "影响链条", "风险预警", "关系图谱", "来源审查"], "统计": {"节点数量": 79, "边数量": 101, "政策工具数量": 10, "事件数量": 6, "风险指标数量": 8, "影响链条数量": 5}, "五层结构": ["第一层:总统 / 白宫 / NSC", "第二层:行政部门工具系统", "第三层:国会监督与立法系统", "第四层:利益攸关群体", "第五层:舆论与认知场"], "核心判断": "白宫定调,NSC协调,行政部门掌握工具,国会负责立法和施压,利益集团提供资源、数据和游说,智库媒体塑造议题,民意决定政策成本。"}, "nodes": [{"id": "P001", "名称": "Donald J. Trump / 白宫", "英文名称": "Donald J. Trump / The White House", "节点类型": "人物/机构", "所属层级": "第一层:总统 / 白宫 / NSC", "政策领域": ["总体战略", "关税", "台湾", "峰会外交"], "权力来源": "总统正式决策权;行政令、峰会、关税与危机反应定调。", "政策工具": ["行政令", "峰会声明", "关税安排", "军售节奏"], "核心诉求": ["政策交易空间", "国内政治成本控制", "美国优先议程"], "涉华立场": "竞争性、交易型、工具化强。", "涉台立场": "维持美台关系工具性与危机控制并行。", "近期动作": ["白宫发布内阁与政策事实清单。"], "风险信号": ["总统直接点名台湾或关税;中美峰会取消;军售节奏异常。"], "证据等级": "A", "来源": ["S001"]}, {"id": "A001", "名称": "Marco Rubio", "英文名称": "Marco Rubio", "节点类型": "人物", "所属层级": "第二层:行政部门工具系统", "政策领域": ["外交", "台湾", "人权", "盟友协调"], "权力来源": "国务卿,掌握外交声明、国务院政策口径与盟友协调。", "政策工具": ["外交声明", "签证限制", "盟友联合声明", "台湾交往指南"], "核心诉求": ["强化美国外交优势", "价值观议题与安全议题联动"], "涉华立场": "强硬竞争与价值观议题结合。", "涉台立场": "支持台湾国际空间与非官方关系强化。", "近期动作": ["白宫列名其为国务卿。"], "风险信号": ["国务院升级台湾表述;联合盟友点名中国行动。"], "证据等级": "A", "来源": ["S001"]}, {"id": "A002", "名称": "Pete Hegseth", "英文名称": "Pete Hegseth", "节点类型": "人物", "所属层级": "第二层:行政部门工具系统", "政策领域": ["军事安全", "台海", "印太", "军售"], "权力来源": "白宫页面列为Secretary of War;负责军事工具、国防预算与威慑态势。", "政策工具": ["军售", "印太部署", "国防预算", "军事演训"], "核心诉求": ["提升战备", "威慑对手", "强化军工与前沿部署"], "涉华立场": "以军事威慑和战备为主。", "涉台立场": "支持强化台海威慑与区域协同。", "近期动作": ["白宫页面列其为Secretary of War。"], "风险信号": ["印太军力前推;NDAA台湾条款加码;军售加速。"], "证据等级": "A", "来源": ["S001"]}, {"id": "A003", "名称": "Scott Bessent", "英文名称": "Scott Bessent", "节点类型": "人物", "所属层级": "第二层:行政部门工具系统", "政策领域": ["金融", "制裁", "投资审查"], "权力来源": "财政部长,影响OFAC、CFIUS、对外投资限制和金融风险边界。", "政策工具": ["OFAC制裁", "CFIUS", "对外投资安全规则", "金融沟通"], "核心诉求": ["金融稳定", "资本安全", "敏感技术投资边界"], "涉华立场": "通过金融和资本工具塑造竞争边界。", "涉台立场": "可通过制裁或投资限制影响涉台军工与技术网络。", "近期动作": ["白宫列名其为财政部长。"], "风险信号": ["制裁大型机构;CFIUS扩张;对外投资规则加码。"], "证据等级": "A", "来源": ["S001", "S007"]}, {"id": "A004", "名称": "Howard Lutnick", "英文名称": "Howard Lutnick", "节点类型": "人物", "所属层级": "第二层:行政部门工具系统", "政策领域": ["出口管制", "半导体", "产业安全"], "权力来源": "商务部长,影响BIS、实体清单、许可证和产Line truncated
const tabs = [
["overview","总览仪表盘"],["nodes","权力节点"],["tools","行政工具"],["congress","国会网络"],["graph","关系图谱"],["chains","影响链条"],["interest","利益集团"],["thinktank","智库媒体"],["events","摩擦事件"],["risks","风险预警"],["sources","来源审查"]
];
let query = "";
let graphFilter = "全部";
const $ = id => document.getElementById(id);
function esc(s) { return String(s ?? '').replace(/[&<>"]/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m])); }
function arr(v) { return Array.isArray(v) ? v : (v ? [v] : []); }
function tags(list) { return `<div class="tags">${arr(list).map(x=>`<span class="tag">${esc(x)}</span>`).join('')}</div>`; }
function match(obj) { if(!query) return true; return JSON.stringify(obj).toLowerCase().includes(query.toLowerCase()); }
function short(text, n=140) { text = String(text||''); return text.length>n ? text.slice(0,n)+'……' : text; }
function initTabs() {
$('tabs').innerHTML = tabs.map((t,i)=>`<button class="tab ${i==0?'active':''}" onclick="showTab('${t[0]}', this)">${t[1]}</button>`).join('');
}
function showTab(id, btn) { document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active')); $(id).classList.add('active'); document.querySelectorAll('.tab').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); if(id==='graph') renderGraph(); }
function card(obj, title, sub, body, click=true) { return `<div class="card" ${click?`onclick='openDetail(${JSON.stringify(obj).replace(/'/g,"&apos;")})'`:''}><h3>${esc(title)}</h3><div class="meta">${esc(sub||'')}</div>${body||''}</div>`; }
function openDetail(obj) {
$('modalTitle').textContent = obj.名称 || obj.工具名称 || obj.事件名称 || obj.指标名称 || obj.名称 || obj.edge_id || obj.id || '详情';
$('modalSub').textContent = obj.英文名称 || obj.节点类型 || obj.类型 || obj.政策性质 || obj.时间 || '';
let rows = Object.entries(obj).filter(([k])=>!['可视化样式'].includes(k)).map(([k,v])=>`<b>${esc(k)}</b><div>${Array.isArray(v)? v.map(esc).join('、') : typeof v === 'object' && v ? `<pre>${esc(JSON.stringify(v,null,2))}</pre>` : esc(v)}</div>`).join('');
$('modalBody').innerHTML = `<div class="detail-grid">${rows}</div>`;
$('modal').classList.add('show');
}
function closeModal() { $('modal').classList.remove('show'); }
function resetSearch() { query=''; $('globalSearch').value=''; renderAll(); }
$('globalSearch').addEventListener('input', e=>{ query=e.target.value.trim(); renderAll(); });
function renderOverview() {
const s=DATA.dashboard.统计;
$('overview').innerHTML = `<div class="section-title"><h2>总览仪表盘</h2><span class="badge">${esc(DATA.metadata.版本)} · ${esc(DATA.metadata.生成日期)}</span></div>
<div class="kpis">${Object.entries(s).map(([k,v])=>`<div class="kpi"><div class="num">${v}</div><div class="label">${k}</div></div>`).join('')}</div>
<div class="grid">
${card(DATA.dashboard, '五层权力结构', DATA.dashboard.核心判断, tags(DATA.dashboard.五层结构), false)}
${card(DATA.metadata, '研究边界', DATA.metadata.时间口径, `<p class="meta">${esc(DATA.metadata.研究边界)}</p>`, false)}
${card(DATA.metadata.证据等级说明, '证据等级', 'A/B/C 分级用于页面筛选和来源审查', `<div class="detail-grid"><b>A</b><div>${DATA.metadata.证据等级说明.A}</div><b>B</b><div>${DATA.metadata.证据等级说明.B}</div><b>C</b><div>${DATA.metadata.证据等级说明.C}</div></div>`, false)}
</div>`;
}
const layers = ["全部","第一层:总统 / 白宫 / NSC","第二层:行政部门工具系统","第三层:国会监督与立法系统","第四层:利益攸关群体","第五层:舆论与认知场"];
let nodeLayer='全部';
function renderNodes() {
const filtered = DATA.nodes.filter(x=>match(x) && (nodeLayer==='全部'||x.所属层级===nodeLayer));
$('nodes').innerHTML = `<div class="section-title"><h2>权力节点库</h2><span class="badge">${filtered.length} / ${DATA.nodes.length}</span></div>
<div class="filters">${layers.map(l=>`<button class="filter ${nodeLayer===l?'active':''}" onclick="nodeLayer='${l}';renderNodes()">${l.replace(':',':<br>')}</button>`).join('')}</div>
<div class="grid">${filtered.map(x=>card(x,x.名称,x.所属层级,`${tags(x.政策领域)}<p class="meta">${esc(short(x.权力来源 || x.核心诉求?.[0]))}</p>`)).join('') || '<div class="empty">没有匹配结果</div>'}</div>`;
}
function renderTools() {
const rows = DATA.policy_tools.filter(match).map(x=>`<tr onclick='openDetail(${JSON.stringify(x).replace(/'/g,"&apos;")})'><td>${esc(x.工具名称)}</td><td>${esc(x.主责机构)}</td><td>${esc(x.政策性质)}</td><td>${arr(x.政策领域).map(esc).join('、')}</td><td>${esc(x.功能说明)}</td><td>${esc(x.建议字段)}</td></tr>`).join('');
$('tools').innerHTML = `<div class="section-title"><h2>行政政策工具系统</h2><span class="badge">${DATA.policy_tools.filter(match).length} 项</span></div><div class="table-wrap"><table><thead><tr><th>工具名称</th><th>主责机构</th><th>政策性质</th><th>政策领域</th><th>功能说明</th><th>建议字段</th></tr></thead><tbody>${rows}</tbody></table></div>`;
}
function renderCongress() {
const congress = DATA.nodes.filter(x=>match(x) && (x.所属层级||'').includes('国会'));
const legs = DATA.legislation.filter(match);
$('congress').innerHTML = `<div class="section-title"><h2>国会委员会、人物与法案库</h2><span class="badge">${congress.length} 个节点 · ${legs.length} 项法案/议题</span></div>
<div class="grid">${congress.map(x=>card(x,x.名称,x.权力来源,tags(x.政策领域))).join('')}</div>
<h2 style="margin-top:24px;color:var(--deep)">法案与立法议题</h2><div class="table-wrap"><table><thead><tr><th>名称</th><th>状态</th><th>牵引节点</th><th>意义</th><th>证据</th></tr></thead><tbody>${legs.map(x=>`<tr onclick='openDetail(${JSON.stringify(x).replace(/'/g,"&apos;")})'><td>${esc(x.名称)}</td><td>${esc(x.状态)}</td><td>${arr(x.牵引节点).map(esc).join('、')}</td><td>${esc(x.涉华涉台意义)}</td><td>${esc(x.证据等级)}</td></tr>`).join('')}</tbody></table></div>`;
}
function renderInterest() {
const list = DATA.nodes.filter(x=>match(x) && x.所属层级==='第四层:利益攸关群体');
$('interest').innerHTML = `<div class="section-title"><h2>利益集团画像</h2><span class="badge">${list.length} 个节点</span></div><div class="grid">${list.map(x=>card(x,x.名称,x.节点类型,`${tags(x.政策领域)}<p class="meta">${esc(short(x.核心诉求?.[0],180))}</p>`)).join('')}</div>`;
}
function renderThinktank() {
const list = DATA.nodes.filter(x=>match(x) && x.所属层级==='第五层:舆论与认知场');
$('thinktank').innerHTML = `<div class="section-title"><h2>智库媒体与叙事生态</h2><span class="badge">${list.length} 个节点</span></div><div class="grid">${list.map(x=>card(x,x.名称,x.英文名称,`${tags(x.政策领域)}<p class="meta">${esc(short(x.核心诉求?.[0],180))}</p>`)).join('')}</div>`;
}
function renderEvents() {
const list=DATA.friction_events.filter(match);
$('events').innerHTML=`<div class="section-title"><h2>摩擦事件库</h2><span class="badge">${list.length} 件</span></div><div class="grid">${list.map(x=>card(x,x.事件名称,`${x.时间} · ${x.类型} · 风险:${x.风险等级}`,`${tags(x.涉及主体)}<p class="meta"><b>美国动作:</b>${esc(short(x.美国动作))}</p><p class="meta"><b>中方可能反应:</b>${esc(short(x.中方可能反应))}</p>`)).join('')}</div>`;
}
function renderRisks() {
const list=DATA.risk_indicators.filter(match);
$('risks').innerHTML=`<div class="section-title"><h2>风险预警指标体系</h2><span class="badge">${list.length} 个指标</span></div><div class="grid">${list.map(x=>`<div class="card" onclick='openDetail(${JSON.stringify(x).replace(/'/g,"&apos;")})'><h3>${esc(x.指标名称)}</h3>${tags(x.关联领域)}<div class="risk"><div class="riskbox"><strong>低</strong><br>${esc(x.低风险)}</div><div class="riskbox"><strong>中</strong><br>${esc(x.中风险)}</div><div class="riskbox"><strong>高</strong><br>${esc(x.高风险)}</div><div class="riskbox"><strong>极高</strong><br>${esc(x.极高风险)}</div></div></div>`).join('')}</div>`;
}
function renderChains() {
const list=DATA.influence_chains.filter(match);
$('chains').innerHTML=`<div class="section-title"><h2>关键影响链条</h2><span class="badge">${list.length} 条</span></div><div class="grid">${list.map(c=>`<div class="card" onclick='openDetail(${JSON.stringify(c).replace(/'/g,"&apos;")})'><h3>${esc(c.名称)}</h3><span class="badge">风险:${esc(c.风险)}</span><div class="chain" style="margin-top:12px">${c.路径.map((s,i)=>`<div class="step"><div class="dot">${i+1}</div><div>${esc(s)}</div></div>`).join('')}</div></div>`).join('')}</div>`;
}
function renderSources() {
const list=DATA.sources.filter(match);
$('sources').innerHTML=`<div class="section-title"><h2>来源审查</h2><span class="badge">${list.length} 个来源</span></div><div class="table-wrap"><table><thead><tr><th>ID</th><th>名称</th><th>类型</th><th>等级</th><th>说明</th><th>链接</th></tr></thead><tbody>${list.map(s=>`<tr><td>${s.id}</td><td>${esc(s.名称)}</td><td>${esc(s.类型)}</td><td>${esc(s.证据等级)}</td><td>${esc(s.说明)}</td><td><a class="source-link" href="${esc(s.url)}" target="_blank">打开来源</a></td></tr>`).join('')}</tbody></table></div>`;
}
function renderGraph() {
const fields=["全部",...Array.from(new Set(DATA.edges.flatMap(e=>e.政策领域||[]))).slice(0,18)];
const edges=DATA.edges.filter(e=>match(e) && (graphFilter==='全部'||(e.政策领域||[]).includes(graphFilter))).slice(0,120);
const ids=new Set(edges.flatMap(e=>[e.source,e.target]));
const nodes=DATA.nodes.filter(n=>ids.has(n.id) || ids.has(n.名称)).slice(0,80);
const fallbackIds=[...ids].filter(id=>!nodes.some(n=>n.id===id||n.名称===id)).map(id=>({id,名称:id,所属层级:'外部概念',节点类型:'概念',政策领域:[]}));
const all=[...nodes,...fallbackIds];
const layersOrder=["第一层:总统 / 白宫 / NSC","第二层:行政部门工具系统","第三层:国会监督与立法系统","第四层:利益攸关群体","第五层:舆论与认知场","外部概念"];
const width=1200, height=640, margin=70;
const pos={};
layersOrder.forEach((ly,li)=>{ const group=all.filter(n=>(n.所属层级||'外部概念')===ly); group.forEach((node,i)=>{ pos[node.id]=pos[node.名称]={x: margin + li*(width-2*margin)/(layersOrder.length-1), y: 55 + (i+1)*(height-110)/(group.length+1)}; }); });
const color=(ly)=>({"第一层:总统 / 白宫 / NSC":"#a41422","第二层:行政部门工具系统":"#d45a35","第三层:国会监督与立法系统":"#7a1f77","第四层:利益攸关群体":"#276f55","第五层:舆论与认知场":"#2f68a8","外部概念":"#999"}[ly]||'#777');
const lineSvg=edges.map(ed=>{ const a=pos[ed.source], b=pos[ed.target]; if(!a||!b) return ''; return `<line class="edge ${ed.强度>=5?'strong':''}" x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" stroke-width="${Math.max(1,ed.强度/1.3)}"><title>${esc(ed.关系类型)}</title></line>`; }).join('');
const nodeSvg=all.map(nd=>{ const p=pos[nd.id]||pos[nd.名称]; if(!p) return ''; const r = nd.节点类型==='人物'?12: nd.节点类型==='委员会'?15: 13; return `<g class="node" onclick='openDetail(${JSON.stringify(nd).replace(/'/g,"&apos;")})'><circle cx="${p.x}" cy="${p.y}" r="${r}" fill="${color(nd.所属层级||'外部概念')}" stroke="#fff" stroke-width="2"><title>${esc(nd.名称)}</title></circle><text class="node-label" x="${p.x+16}" y="${p.y+4}">${esc(nd.名称).slice(0,18)}</text></g>`; }).join('');
$('graph').innerHTML=`<div class="section-title"><h2>关系图谱</h2><span class="badge">${edges.length} 条边 · ${all.length} 个节点</span></div><div class="graphbar">${fields.map(f=>`<button class="filter ${graphFilter===f?'active':''}" onclick="graphFilter='${f}';renderGraph()">${esc(f)}</button>`).join('')}</div><div class="graph"><svg viewBox="0 0 ${width} ${height}">${lineSvg}${nodeSvg}</svg></div><div class="legend"><span>红:白宫</span><span>橙:行政部门/工具</span><span>紫:国会</span><span>绿:利益集团</span><span>蓝:智库媒体</span><span>线越粗关系越强</span></div>`;
}
function renderAll() { renderOverview(); renderNodes(); renderTools(); renderCongress(); renderGraph(); renderChains(); renderInterest(); renderThinktank(); renderEvents(); renderRisks(); renderSources(); }
function downloadJSON() { const blob=new Blob([JSON.stringify(DATA,null,2)],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='us_china_policy_power_network_data.json'; a.click(); URL.revokeObjectURL(a.href); }
initTabs(); renderAll();
</script>
</body>
</html>
@@ -0,0 +1,273 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>美国左翼/进步群体涉华涉台专题研究平台(增强版)</title>
<style>
:root{
--bg:#781d22;--bg2:#4d1014;--card:#fffaf5;--card2:#fff3eb;--soft:#f8e8de;--line:rgba(95,27,31,.22);
--text:#261313;--muted:#755e5e;--accent:#b33a3f;--accent2:#8e2f34;--gold:#b98b54;--ok:#236e56;--warn:#a05a16;
--shadow:0 18px 55px rgba(45,10,12,.22);--radius:22px;--radius2:14px;
}
*{box-sizing:border-box} body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif;background:radial-gradient(circle at 20% 0%,#9b3035 0,#781d22 36%,#4d1014 100%);color:var(--text)}
a{color:var(--accent2);text-decoration:none} a:hover{text-decoration:underline}.wrap{max-width:1500px;margin:0 auto;padding:28px 22px 48px}.hero{background:linear-gradient(135deg,rgba(255,250,245,.96),rgba(247,232,222,.92));border:1px solid rgba(255,255,255,.52);border-radius:30px;box-shadow:var(--shadow);padding:28px;position:relative;overflow:hidden}.hero:after{content:"";position:absolute;right:-80px;top:-80px;width:300px;height:300px;border-radius:50%;background:rgba(179,58,63,.12)}.eyebrow{font-size:13px;color:var(--accent2);font-weight:800;letter-spacing:.12em;text-transform:uppercase}.hero h1{margin:10px 0 8px;font-size:34px;line-height:1.2;color:#2b1113}.subtitle{color:var(--muted);font-size:16px;max-width:940px;line-height:1.75}.pillbar{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.pill,.tag{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:#fff;border:1px solid var(--line);font-size:12px;color:#553333}.pill.strong{background:#792026;color:#fff;border-color:#792026}.toolbar{margin:18px 0;background:rgba(255,250,245,.94);border:1px solid rgba(255,255,255,.45);border-radius:22px;box-shadow:0 10px 32px rgba(39,7,9,.14);padding:14px;display:grid;grid-template-columns:minmax(260px,1.6fr) repeat(4,minmax(130px,.7fr)) auto;gap:10px;align-items:center}.toolbar input,.toolbar select,.toolbar button{height:42px;border-radius:14px;border:1px solid var(--line);background:#fff;color:var(--text);padding:0 12px;font-size:14px}.toolbar button{background:var(--accent);color:#fff;border-color:var(--accent);font-weight:700;cursor:pointer}.tabs{display:flex;gap:8px;overflow-x:auto;padding:4px 0 12px}.tab{border:1px solid rgba(255,255,255,.45);background:rgba(255,250,245,.88);color:#5b2a2d;border-radius:999px;padding:10px 16px;white-space:nowrap;font-weight:800;cursor:pointer}.tab.active{background:#fff;color:#781d22;box-shadow:0 8px 22px rgba(55,8,10,.2)}.panel{background:rgba(255,250,245,.96);border:1px solid rgba(255,255,255,.52);border-radius:28px;box-shadow:var(--shadow);padding:22px;min-height:560px}.panel-head{display:flex;justify-content:space-between;gap:16px;align-items:flex-end;margin-bottom:18px}.panel-title{font-size:25px;font-weight:900;margin:0;color:#2d1214}.panel-desc{color:var(--muted);line-height:1.65;margin:8px 0 0}.count-badge{background:#5a1519;color:#fff;border-radius:999px;padding:7px 12px;font-size:13px;font-weight:800}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.grid.two{grid-template-columns:repeat(2,minmax(0,1fr))}.card{background:#fff;border:1px solid var(--line);border-radius:20px;padding:16px;box-shadow:0 7px 18px rgba(51,16,15,.07);cursor:pointer;transition:.15s}.card:hover{transform:translateY(-2px);box-shadow:0 14px 32px rgba(51,16,15,.14);border-color:rgba(179,58,63,.45)}.card h3{margin:0 0 8px;font-size:18px;color:#2a1012;line-height:1.35}.card .meta{color:var(--muted);font-size:13px;line-height:1.6;margin-bottom:10px}.summary{font-size:14px;line-height:1.72;color:#3b2222;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.kv{display:grid;grid-template-columns:150px 1fr;gap:8px;border-top:1px dashed var(--line);padding-top:10px;margin-top:10px;font-size:14px}.kv b{color:#5b2225}.list{display:flex;flex-wrap:wrap;gap:7px;margin-top:8px}.chip{display:inline-flex;padding:5px 9px;border-radius:999px;background:var(--soft);border:1px solid var(--line);font-size:12px;color:#653234}.chip.red{background:#ffe3de;color:#8b2327}.chip.green{background:#e9f5ef;color:#236e56}.chip.gold{background:#fff0d9;color:#8d5816}.stat-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:18px}.stat{background:#fff;border:1px solid var(--line);border-radius:20px;padding:16px}.stat .num{font-size:28px;font-weight:950;color:#8c2429}.stat .lab{color:var(--muted);font-size:13px;margin-top:4px}.section-grid{display:grid;grid-template-columns:1.2fr .8fr;gap:16px}.info-box{background:#fff;border:1px solid var(--line);border-radius:20px;padding:18px;margin-bottom:14px}.info-box h3{margin:0 0 12px;color:#3a1517}.info-box p,.info-box li{line-height:1.75;color:#442626}.timeline{position:relative;padding-left:22px}.timeline:before{content:"";position:absolute;left:8px;top:4px;bottom:4px;width:2px;background:#e2c6bd}.titem{position:relative;background:#fff;border:1px solid var(--line);border-radius:18px;padding:14px 16px;margin-bottom:12px;cursor:pointer}.titem:before{content:"";position:absolute;left:-20px;top:20px;width:12px;height:12px;border-radius:50%;background:var(--accent);border:3px solid #fff;box-shadow:0 0 0 1px var(--accent)}.tdate{font-weight:900;color:#8c2429;font-size:13px}.matrix{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.matrix-card{background:#fff;border:1px solid var(--line);border-radius:20px;padding:16px;cursor:pointer}.split{display:grid;grid-template-columns:1fr 1fr;gap:16px}.table-wrap{overflow:auto;background:#fff;border:1px solid var(Line truncated
@media(max-width:1100px){.toolbar{grid-template-columns:1fr 1fr}.grid,.grid.two,.matrix,.split,.section-grid,.stat-grid{grid-template-columns:1fr 1fr}.doc-layout{grid-template-columns:1fr}.doc-nav{position:relative;max-height:none}.field{grid-template-columns:1fr}.subcards{grid-template-columns:1fr}}
@media(max-width:720px){.wrap{padding:14px 10px}.hero h1{font-size:25px}.toolbar,.grid,.grid.two,.matrix,.split,.section-grid,.stat-grid{grid-template-columns:1fr}.modal{padding:8px}.modal-head{padding:16px}.modal-body{padding:16px}.panel{padding:14px}.tabs{gap:6px}.tab{padding:9px 12px;font-size:13px}}
</style>
</head>
<body>
<div class="wrap">
<header class="hero">
<div class="eyebrow">Research Dashboard · Enhanced Display</div>
<h1 id="pageTitle">美国左翼/进步群体涉华涉台专题研究平台(增强版)</h1>
<div class="subtitle" id="pageSubtitle"></div>
<div class="pillbar" id="boundaryPills"></div>
</header>
<section class="toolbar">
<input id="q" placeholder="搜索组织、事件、叙事、关系、来源……" />
<select id="factionFilter"><option value="">全部阵营</option></select>
<select id="relevanceFilter"><option value="">全部相关度</option></select>
<select id="confidenceFilter"><option value="">全部可信度</option></select>
<select id="yearFilter"><option value="">全部年份</option></select>
<button id="resetBtn">重置</button>
</section>
<nav class="tabs" id="tabs"></nav>
<main class="panel" id="panel"></main>
<div class="footer-note">本页面仅用于公开资料研究与来源核验,不包含普通成员隐私、个人弱点、线下行动操作指南或定向操纵建议。</div>
</div>
<div class="modal" id="modal" onclick="if(event.target.id==='modal') closeModal()">
<div class="modal-card">
<div class="modal-head">
<div><div class="modal-title" id="modalTitle"></div><div class="modal-sub" id="modalSub"></div></div>
<button class="close" onclick="closeModal()">×</button>
</div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
<script id="data-json" type="application/json">{"meta": {"title": "美国左翼/进步群体涉华涉台专题研究平台(增强版)", "subtitle": "公开资料研究 · 组织深描 · 事件时间线 · 叙事语料 · 关系图谱 · 争议与来源", "version": "v2-enhanced-display", "updated_at": "2026-06-25T18:20:00+09:00", "language": "zh-CN", "scope_note": "本数据包基于公开网页、组织自述、媒体报道和政策/倡议文本,服务学术研究和公开资料分析。组织立场不等于普通成员个人立场;间接相关组织不得被强行解释为涉台组织。", "safety_boundary": {"allowed_scope": ["公开资料研究", "组织层级生态分析", "非定向叙事风险评估", "来源可信度审查", "公共沟通误读预警"], "excluded": ["个人脆弱点", "住址/行程/家庭/普通成员账号", "线下行动操作指南", "规避执法或平台审核", "欺骗性影响行动", "冒充身份或假草根动员"], "terminology_rule": "将“脆弱点”统一改写为“内部矛盾、传播风险、误读点、韧性需求”。"}, "data_note": "展示层 JSON 由 v2 原始数据转换而成,供单文件 HTML 离线检索、筛选、弹框展示使用。", "enhancement_note": "增强版将弹框从原始 JSON 改为分区式详情页,并增加 Markdown 原始研究摘编、组织深描、事件研究价值、争议处理和来源可信度说明。"}, "tabs": [{"id": "overview", "label": "专题总览", "dataset": "dashboard", "layout": "dashboard"}, {"id": "factions", "label": "阵营分类", "dataset": "factions", "layout": "cards", "modal": "factions"}, {"id": "organizations", "label": "组织画像", "dataset": "organizations", "layout": "cards", "modal": "organizations"}, {"id": "events", "label": "事件时间线", "dataset": "events", "layout": "timeline", "modal": "events"}, {"id": "narratives", "label": "叙事矩阵", "dataset": "narratives", "layout": "matrix", "modal": "narratives"}, {"id": "corpus", "label": "叙事语料", "dataset": "corpus", "layout": "cards", "modal": "corpus"}, {"id": "relationships", "label": "关系图谱", "dataset": "relationships", "layout": "network", "modal": "relationships"}, {"id": "platforms", "label": "平台传播", "dataset": "platforms", "layout": "cards", "modal": "platforms"}, {"id": "public_opinion", "label": "民意基础", "dataset": "public_opinion", "layout": "evidence", "modal": "public_opinion"}, {"id": "finance_resources", "label": "资源能力", "dataset": "finance_resources", "layout": "cards", "modal": "finance_resources"}, {"id": "disputes_sources", "label": "争议与来源", "dataset": "disputes", "layout": "split", "modal": "disputes"}, {"id": "research_doc", "label": "研究文档摘编", "dataset": "research_sections", "layout": "document", "modal": null}], "global_filters": [{"id": "faction", "label": "阵营", "type": "multi_select", "options": ["亚裔/华裔反新冷战派", "人权与台湾主体性左翼/反威权左翼", "制度内民主社会主义派", "反战反帝国主义派", "无政府主义、Antifa与黑块式网络", "校园亲巴勒斯坦与反殖民左翼", "气候正义与环境直接行动派", "种族正义与废除主义派", "选举进步派与民主党左翼外围", "革命社会主义/共产主义组织派"]}, {"id": "organization_type", "label": "组织类型", "type": "select", "options": ["abolitionist organization", "anti-Zionist Jewish advocacy organization", "anti-new-cold-war advocacy network", "antiwar NGO / global peace network", "antiwar coalition", "antiwar feminist grassroots organization", "candidate recruitment / progressive PAC-style organization", "communist party", "diasporic Chinese left media/education collective", "global climate movement organization", "left movement space / education venue", "membership organization / democratic socialist", "nonviolent direct action climate movement", "online racial justice advocacy organization", "peace organization / veterans network", "progressive advocacy / petition platform", "progressive electoral party / ballot line organization", "racial justice coalition", "revolutionary socialist party / party media network", "student Palestine solidarity network", "student/youth network", "youth climate movement"]}, {"id": "relevance", "label": "涉华涉台相关度", "type": "select", "options": ["中等:通过反新冷战、反军工、台湾主体性内部讨论进入", "中高:反战动员中出现No war with China和亚太基地议题", "中高:有台湾与南海军事对抗、冲绳基地等文章", "中高:设有China Working Group并提到佩洛西访台抗议", "中:不一定频繁涉台,但在中国/关税/新冷战议题上有公开文章", "中:曾有请愿要求佩洛西重新考虑访台风险", "中:通过中国社会主义、反帝论坛和左翼教育议题关联", "低到中:以校园政治为通道,涉台多为间接", "低到中:通过国会进步派候选人和外交预Line truncated
<script>
const DATA = JSON.parse(document.getElementById('data-json').textContent);
const D = DATA.datasets;
const sourceIndex = Object.fromEntries((D.sources||[]).map(s=>[s.id,s]));
const orgIndex = Object.fromEntries((D.organizations||[]).map(o=>[o.id,o]));
const state = {tab:'overview', q:'', faction:'', relevance:'', confidence:'', year:''};
const tabDesc = {
overview:'总览数据规模、核心发现、研究边界、补充摘要和仍需补充的数据缺口。',
factions:'展示美国左翼/进步派生态的阵营分类、社会基础、组织能力和涉华涉台立场。',
organizations:'展示组织画像、公开结构、涉华涉台活动、组织深描、争议入口和资料来源。',
events:'按时间线展示涉华涉台及关联事件,突出触发因素、组织反应、研究价值和争议。',
narratives:'展示叙事框架、道德入口、接受与反弹结构、证据要求和安全边界。',
corpus:'展示公开声明、文章、活动页和短摘录语料,用于支撑叙事分析。',
relationships:'展示组织—组织、组织—议题之间的关系边、证据基础和简易网络图。',
platforms:'展示社交平台、组织官网和独立媒体的传播适配、内容形式和研究指标。',
public_opinion:'展示民意、社会基础和受众背景,用来解释叙事为什么有进入空间或反弹风险。',
finance_resources:'展示公开财务与资源能力信息,同时明确禁止用途。',
disputes_sources:'展示争议、反驳、研究处理方式和来源可信度注册表。',
research_doc:'尽量保留 v2 Markdown 的原始研究知识,以文档摘编形式完整展示。'
};
const titleFields = {
factions:'阵营名称', organizations:'显示标题', events:'事件标题', narratives:'叙事名称', corpus:'中文摘要', relationships:'关系类型', platforms:'平台名称', public_opinion:'主题', finance_resources:'组织名称', disputes:'争议标题', sources:'标题'
};
function esc(x){return String(x??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function arr(v){ if(!v) return []; if(Array.isArray(v)) return v.filter(x=>x!==''&&x!=null); return [v]; }
function isEmpty(v){return v==null || v==='' || (Array.isArray(v)&&v.length===0);}
function textOf(v){ if(Array.isArray(v)) return v.map(textOf).join(' '); if(v&&typeof v==='object') return Object.values(v).map(textOf).join(' '); return String(v??''); }
function orgName(id){ return orgIndex[id]?.['组织名称'] || orgIndex[id]?.['显示标题'] || id; }
function tagList(items, cls=''){ const a=arr(items); if(!a.length) return ''; return `<div class="list">${a.map(x=>`<span class="chip ${cls}">${esc(formatToken(x))}</span>`).join('')}</div>`; }
function formatToken(x){ if(orgIndex[x]) return orgName(x); return String(x??''); }
function fieldValue(v, key=''){
if(isEmpty(v)) return '<span style="color:#967979">暂无记录</span>';
if(Array.isArray(v)){
if(key.includes('来源')) return v.map(sourceCard).join('') || '<span style="color:#967979">暂无来源</span>';
return '<ul>'+v.map(x=>`<li>${esc(formatToken(x))}</li>`).join('')+'</ul>';
}
const s=String(v);
if(/^https?:\/\//.test(s)) return `<a href="${esc(s)}" target="_blank" rel="noopener">${esc(s)}</a>`;
return `<p>${esc(s).replace(/\n/g,'<br>')}</p>`;
}
function sourceCard(idOrObj){
const sid = typeof idOrObj==='string'?idOrObj:(idOrObj?.id||idOrObj?.source_id||'');
const s = sourceIndex[sid];
if(!s) return `<div class="source-card"><div class="source-title">${esc(sid || textOf(idOrObj))}</div><div class="source-meta">未在来源表中展开,建议后续补齐。</div></div>`;
const link = s['链接'] ? `<a href="${esc(s['链接'])}" target="_blank" rel="noopener">打开来源</a>` : '无链接';
return `<div class="source-card"><div class="source-title">${esc(s['标题']||s.id)}</div><div class="source-meta">${esc(s['来源类型']||'')} · 可信度:${esc(s['可信度']||'')} · ${link}</div>${s['说明']?`<div class="source-meta">${esc(s['说明'])}</div>`:''}</div>`;
}
function init(){
document.getElementById('pageTitle').textContent = DATA.meta.title;
document.getElementById('pageSubtitle').textContent = DATA.meta.subtitle + '。' + (DATA.meta.enhancement_note||'');
const b = D.safety_boundary || {};
document.getElementById('boundaryPills').innerHTML = ['公开资料研究','分区式详情页','完整文档摘编','可检索数据'].map((x,i)=>`<span class="pill ${i===0?'strong':''}">${x}</span>`).join('') + (b.allowed_scope? b.allowed_scope.slice(0,4).map(x=>`<span class="pill">${esc(x)}</span>`).join(''):'');
document.getElementById('tabs').innerHTML = DATA.tabs.map(t=>`<button class="tab ${t.id===state.tab?'active':''}" onclick="switchTab('${t.id}')">${esc(t.label)}</button>`).join('');
populateFilters(); bind(); render();
}
function bind(){
document.getElementById('q').addEventListener('input',e=>{state.q=e.target.value.trim(); render();});
['factionFilter','relevanceFilter','confidenceFilter','yearFilter'].forEach(id=>document.getElementById(id).addEventListener('change',e=>{state[id.replace('Filter','')]=e.target.value; render();}));
document.getElementById('resetBtn').onclick=()=>{state.q='';state.faction='';state.relevance='';state.confidence='';state.year='';document.querySelectorAll('.toolbar input,.toolbar select').forEach(el=>el.value='');render();};
}
function populateFilters(){
const factions=[...new Set((D.organizations||[]).flatMap(o=>arr(o['所属阵营'])).filter(Boolean))].sort();
const relevance=[...new Set((D.organizations||[]).map(o=>o['涉华涉台相关度']).filter(Boolean))].sort();
const conf=[...new Set(Object.values(D).flatMap(ds=>Array.isArray(ds)?ds.map(x=>x['来源可信度']||x['可信度']||x['补充来源可信度']).filter(Boolean):[]))].sort();
const years=[...new Set((D.events||[]).concat(D.corpus||[]).map(x=>x['年份']).filter(Boolean))].sort().reverse();
fillSelect('factionFilter',factions); fillSelect('relevanceFilter',relevance); fillSelect('confidenceFilter',conf); fillSelect('yearFilter',years);
}
function fillSelect(id, vals){ const el=document.getElementById(id); const first=el.options[0].outerHTML; el.innerHTML=first+vals.map(v=>`<option value="${esc(v)}">${esc(v)}</option>`).join('');}
function switchTab(id){state.tab=id;document.querySelectorAll('.tab').forEach(b=>b.classList.toggle('active',b.textContent===DATA.tabs.find(t=>t.id===id).label));render();}
function datasetForTab(){ const t=DATA.tabs.find(x=>x.id===state.tab); return t?.dataset; }
function filtered(items){
return items.filter(item=>{
const txt=textOf(item).toLowerCase();
if(state.q && !txt.includes(state.q.toLowerCase())) return false;
if(state.faction && !arr(item['所属阵营']).includes(state.faction) && !arr(item['易接受阵营']).includes(state.faction) && !arr(item['易反弹阵营']).includes(state.faction)) return false;
if(state.relevance && item['涉华涉台相关度']!==state.relevance) return false;
if(state.confidence && ![item['来源可信度'],item['可信度'],item['补充来源可信度']].includes(state.confidence)) return false;
if(state.year && item['年份']!==state.year) return false;
return true;
});
}
function panelHead(label, desc, count){return `<div class="panel-head"><div><h2 class="panel-title">${esc(label)}</h2><p class="panel-desc">${esc(desc||'')}</p></div>${count!=null?`<div class="count-badge">${count} 条</div>`:''}</div>`;}
function render(){
document.getElementById('tabs').innerHTML = DATA.tabs.map(t=>`<button class="tab ${t.id===state.tab?'active':''}" onclick="switchTab('${t.id}')">${esc(t.label)}</button>`).join('');
const t=DATA.tabs.find(x=>x.id===state.tab); const panel=document.getElementById('panel');
if(state.tab==='overview'){ panel.innerHTML=renderOverview(); return; }
if(state.tab==='research_doc'){ panel.innerHTML=renderDocument(); return; }
if(state.tab==='disputes_sources'){ panel.innerHTML=renderSplit(t); return; }
const data=filtered(D[t.dataset]||[]);
let body='';
if(t.layout==='timeline') body=renderTimeline(data,t.modal);
else if(t.layout==='matrix') body=renderMatrix(data,t.modal);
else if(t.layout==='network') body=renderNetwork(data,t.modal);
else body=renderCards(data,t.dataset,t.modal);
panel.innerHTML=panelHead(t.label,tabDesc[t.id],data.length)+body;
}
function renderOverview(){
const counts=DATA.dashboard.counts||{}; const summary=DATA.dashboard.completion_summary||{}; const safety=DATA.dashboard.safety_boundary||{};
const stats=[['阵营',counts.factions],['组织',counts.organizations],['事件',counts.events],['叙事框架',counts.narratives],['语料',counts.corpus],['关系边',counts.relationships],['来源',counts.sources],['研究文档节',counts.research_sections]];
return panelHead('专题总览',tabDesc.overview,null)+`
<div class="stat-grid">${stats.map(([k,v])=>`<div class="stat"><div class="num">${esc(v??0)}</div><div class="lab">${esc(k)}</div></div>`).join('')}</div>
<div class="section-grid">
<div>
<div class="info-box"><h3>核心发现</h3><ul>${(DATA.dashboard.core_findings||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
<div class="info-box"><h3>v2 补充摘要</h3><div class="grid two">${Object.entries(summary).map(([k,v])=>`<div class="card" style="cursor:default"><h3>${esc(k)}</h3><div class="summary">新增/补充 ${esc(v)} 条</div></div>`).join('')}</div></div>
</div>
<div>
<div class="info-box"><h3>研究边界</h3><p><b>允许范围:</b></p>${tagList(safety.allowed_scope,'green')}<p><b>排除内容:</b></p>${tagList(safety.excluded,'red')}<p>${esc(safety.terminology_rule||'')}</p></div>
<div class="info-box"><h3>后续数据缺口</h3><ul>${(DATA.dashboard.remaining_gaps_v2||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
</div>
</div>`;
}
function cardTitle(item, ds){return item[titleFields[ds]] || item['标题'] || item['id'];}
function cardSummary(item, ds){
const keys={factions:['涉华涉台立场','组织能力'], organizations:['公开自我描述','涉华涉台活动','组织能力判断'], events:['触发因素','组织反应','研究价值','结果'], narratives:['核心主张','风险说明'], corpus:['中文摘要','原文短摘'], platforms:['适配场景','涉华涉台用途'], public_opinion:['最新观察','研究相关性'], finance_resources:['已知公开信息','研究用途'], disputes:['研究处理方式','主张A','主张B'], sources:['说明']};
return (keys[ds]||[]).map(k=>item[k]).filter(Boolean).join(' ');
}
function renderCards(items, ds, modal){
if(!items.length) return '<div class="empty">没有匹配结果</div>';
return `<div class="grid">${items.map(item=>`<article class="card" onclick='openModal("${modal||ds}",${JSON.stringify(item.id)})'><h3>${esc(cardTitle(item,ds))}</h3><div class="meta">${esc(item['组织类型']||item['阵营类型']||item['事件类型']||item['平台名称']||item['来源类型']||'')}</div><div class="summary">${esc(cardSummary(item,ds))}</div>${renderCardChips(item)}</article>`).join('')}</div>`;
}
function renderCardChips(item){
const chips=[]; ['所属阵营','涉华涉台相关度','来源可信度','可信度','争议等级','年份'].forEach(k=>{if(item[k]) chips.push(...arr(item[k]).slice(0,3));});
if(item['议题焦点']) chips.push(...arr(item['议题焦点']).slice(0,3));
if(item['叙事标签']) chips.push(...arr(item['叙事标签']).slice(0,3));
return tagList(chips.slice(0,8));
}
function renderTimeline(items, modal){
if(!items.length) return '<div class="empty">没有匹配事件</div>';
return `<div class="timeline">${items.map(item=>`<div class="titem" onclick='openModal("${modal}",${JSON.stringify(item.id)})'><div class="tdate">${esc(item['日期']||'未注明日期')}</div><h3>${esc(item['事件标题'])}</h3><div class="summary">${esc(cardSummary(item,'events'))}</div>${tagList([item['事件类型'],...(arr(item['叙事标签']).slice(0,4)), item['来源可信度']].filter(Boolean))}</div>`).join('')}</div>`;
}
function renderMatrix(items, modal){
if(!items.length) return '<div class="empty">没有匹配叙事</div>';
return `<div class="matrix">${items.map(item=>`<div class="matrix-card" onclick='openModal("${modal}",${JSON.stringify(item.id)})'><h3>${esc(item['叙事名称'])}</h3><div class="summary">${esc(item['核心主张'])}</div><div class="kv"><b>道德入口</b><div>${tagList(item['道德入口'])}</div><b>争议等级</b><div>${esc(item['争议等级']||'')}</div></div></div>`).join('')}</div>`;
}
function renderNetwork(items, modal){
const top=items.slice(0,50); const nodes=[]; const seen=new Set(); top.forEach(e=>{[e['源节点'],e['目标节点']].forEach(n=>{if(n&&!seen.has(n)){seen.add(n);nodes.push(n)}})});
const N=Math.min(nodes.length,24); const cx=520,cy=160,R=125; const pos={}; nodes.slice(0,N).forEach((n,i)=>{pos[n]=[cx+R*Math.cos(2*Math.PI*i/N),cy+R*Math.sin(2*Math.PI*i/N)]});
const svg=`<div class="network"><svg viewBox="0 0 1040 320">${top.map(e=>pos[e['源节点']]&&pos[e['目标节点']]?`<line class="edge" x1="${pos[e['源节点']][0]}" y1="${pos[e['源节点']][1]}" x2="${pos[e['目标节点']][0]}" y2="${pos[e['目标节点']][1]}"/>`:'' ).join('')}${nodes.slice(0,N).map(n=>`<g><circle class="node" cx="${pos[n][0]}" cy="${pos[n][1]}" r="24"/><text class="nodeText" text-anchor="middle" x="${pos[n][0]}" y="${pos[n][1]+4}">${esc(orgName(n)).slice(0,14)}</text></g>`).join('')}</svg></div>`;
const table=`<div class="table-wrap"><table><thead><tr><th>源节点</th><th>目标节点</th><th>关系类型</th><th>议题语境</th><th>强度</th><th>可信度</th></tr></thead><tbody>${items.map(item=>`<tr onclick='openModal("${modal}",${JSON.stringify(item.id)})'><td>${esc(item['源节点名称'])}</td><td>${esc(item['目标节点名称'])}</td><td>${esc(item['关系类型'])}</td><td>${esc(item['议题语境'])}</td><td>${esc(item['强度']||'')}</td><td>${esc(item['来源可信度']||'')}</td></tr>`).join('')}</tbody></table></div>`;
return svg+table;
}
function renderSplit(t){
const disputes=filtered(D.disputes||[]); const sources=filtered(D.sources||[]);
return panelHead('争议与来源',tabDesc.disputes_sources,disputes.length+sources.length)+`<div class="split"><div><h3>争议与反驳记录</h3>${renderCards(disputes,'disputes','disputes')}</div><div><h3>来源注册表</h3><div class="table-wrap"><table><thead><tr><th>标题</th><th>类型</th><th>可信度</th><th>说明</th></tr></thead><tbody>${sources.map(s=>`<tr onclick='openModal("sources",${JSON.stringify(s.id)})'><td>${esc(s['标题'])}</td><td>${esc(s['来源类型'])}</td><td>${esc(s['可信度'])}</td><td>${esc(s['说明']||'')}</td></tr>`).join('')}</tbody></table></div></div></div>`;
}
function renderDocument(){
const sections=D.research_sections||[];
return panelHead('研究文档摘编',tabDesc.research_doc,sections.length)+`<div class="doc-layout"><div class="doc-nav">${sections.map(s=>`<button onclick="document.getElementById('${s.id}').scrollIntoView({behavior:'smooth',block:'start'})">${esc(s.title)}</button>`).join('')}</div><div class="doc-body">${sections.map(s=>`<section class="md-section" id="${s.id}"><h2>${esc(s.title)}</h2>${mdToHtml(s.markdown)}</section>`).join('')}</div></div>`;
}
function mdToHtml(md){
const lines=(md||'').split(/\n/); let out='', i=0;
while(i<lines.length){
const line=lines[i];
if(!line.trim()){i++; continue;}
if(line.startsWith('|')){
let rows=[]; while(i<lines.length && lines[i].startsWith('|')){rows.push(lines[i]); i++;}
const clean=rows.filter(r=>!/^[|\s:\-]+$/.test(r));
if(clean.length){ out+='<div class="md-table"><table>'+clean.map((r,idx)=>'<tr>'+r.split('|').slice(1,-1).map(c=>idx===0?`<th>${esc(c.trim())}</th>`:`<td>${esc(c.trim())}</td>`).join('')+'</tr>').join('')+'</table></div>'; }
continue;
}
if(/^###\s+/.test(line)){out+=`<h3>${esc(line.replace(/^###\s+/,''))}</h3>`;i++;continue;}
if(/^[-*]\s+/.test(line)){let items=[]; while(i<lines.length && /^[-*]\s+/.test(lines[i])){items.push(lines[i].replace(/^[-*]\s+/,''));i++;} out+='<ul>'+items.map(x=>`<li>${esc(x)}</li>`).join('')+'</ul>'; continue;}
out+=`<p>${esc(line)}</p>`; i++;
}
return out;
}
function openModal(ds, id){
let item=(D[ds]||[]).find(x=>x.id===id);
if(!item && ds==='sources') item=(D.sources||[]).find(x=>x.id===id);
if(!item) return;
const title=cardTitle(item,ds)||id; document.getElementById('modalTitle').textContent=title;
document.getElementById('modalSub').innerHTML=modalSubtitle(item,ds);
document.getElementById('modalBody').innerHTML=renderDetail(item,ds)+renderRelated(item,ds);
document.getElementById('modal').classList.add('show');
}
function modalSubtitle(item,ds){
const vals=[item['组织类型'],item['阵营类型'],item['事件类型'],item['平台名称'],item['来源类型'],item['日期'],item['来源可信度']||item['可信度']].filter(Boolean);
return vals.map(esc).join(' · ');
}
function renderDetail(item,ds){
const schema=DATA.rich_modal_schemas[ds]||[{title:'详情字段',fields:Object.keys(item).filter(k=>k!=='搜索文本')}];
let used=new Set();
let html=schema.map(sec=>{
const fields=sec.fields.filter(k=>!isEmpty(item[k])); fields.forEach(k=>used.add(k));
if(!fields.length) return '';
return `<section class="detail-section"><h3>${esc(sec.title)}</h3>${fields.map(k=>`<div class="field"><div class="field-label">${esc(k)}</div><div class="field-value">${fieldValue(item[k],k)}</div></div>`).join('')}</section>`;
}).join('');
const rest=Object.keys(item).filter(k=>!used.has(k)&&!['id','搜索文本','显示标题'].includes(k)&&!isEmpty(item[k]));
if(rest.length){ html+=`<section class="detail-section"><h3>其他补充字段</h3>${rest.map(k=>`<div class="field"><div class="field-label">${esc(k)}</div><div class="field-value">${fieldValue(item[k],k)}</div></div>`).join('')}</section>`; }
return html;
}
function renderRelated(item,ds){
let blocks=[];
if(ds==='organizations'){
const oid=item.id;
const ev=(D.events||[]).filter(e=>arr(e['涉及组织']).includes(oid)).slice(0,6);
const rel=(D.relationships||[]).filter(r=>r['源节点']===oid||r['目标节点']===oid).slice(0,8);
const corp=(D.corpus||[]).filter(c=>c['来源组织']===oid).slice(0,6);
blocks.push(relatedCards('关联事件',ev,'events','事件标题',['日期','研究价值','触发因素']));
blocks.push(relatedCards('关系边',rel,'relationships','关系类型',['源节点名称','目标节点名称','议题语境']));
blocks.push(relatedCards('相关语料',corp,'corpus','中文摘要',['日期','平台','反弹风险']));
}
if(ds==='events'){
const tags=arr(item['叙事标签']).concat(arr(item['议题框架']));
const ns=(D.narratives||[]).filter(n=>tags.some(t=>textOf(n).includes(t))).slice(0,6);
blocks.push(relatedCards('相关叙事框架',ns,'narratives','叙事名称',['核心主张','风险说明']));
}
if(ds==='narratives'){
const name=item['叙事名称'];
const corp=(D.corpus||[]).filter(c=>arr(c['叙事标签']).some(t=>name.includes(t)||t.includes(name)||item['核心主张']?.includes(t))).slice(0,8);
blocks.push(relatedCards('支撑语料样本',corp,'corpus','中文摘要',['来源组织名称','日期','反弹风险']));
}
return blocks.filter(Boolean).join('');
}
function relatedCards(title,items,ds,titleKey,fields){
if(!items.length) return '';
return `<section class="detail-section related-block"><h3>${esc(title)}</h3><div class="subcards">${items.map(it=>`<div class="subcard" onclick='openModal("${ds}",${JSON.stringify(it.id)})'><h4>${esc(it[titleKey]||it.id)}</h4><p>${esc(fields.map(f=>it[f]).filter(Boolean).join(' · ')).slice(0,220)}</p></div>`).join('')}</div></section>`;
}
function closeModal(){document.getElementById('modal').classList.remove('show');}
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeModal()});
init();
</script>
</body>
</html>
@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>美方新右翼与极右翼群体研究专题库 v3</title>
<style>
:root{
--bg:#f4f5f7; --card:#fff; --ink:#1f2933; --muted:#657282; --line:#e1e4e8;
--red:#7a1f1f; --red2:#9f2b2b; --softred:#fff4f4; --accent:#b84632;
--green:#2e7d32; --yellow:#b7791f; --orange:#c05621; --high:#c62828; --severe:#5f0f0f;
--shadow:0 14px 36px rgba(31,41,51,.12);
}
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",Arial,sans-serif;line-height:1.6}
.header{background:linear-gradient(135deg,#6b1717,#9c2f2f 64%,#c25a42);color:#fff;padding:32px 28px 26px;border-bottom:6px solid rgba(255,255,255,.18)}
.header-inner{max-width:1440px;margin:auto}.header h1{margin:0 0 8px;font-size:30px;letter-spacing:.5px}.header p{margin:0;color:rgba(255,255,255,.88);font-size:15px;max-width:1100px}.top-pills{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.app{max-width:1440px;margin:0 auto;padding:20px 24px 38px}.toolbar{background:#fff;border:1px solid var(--line);border-radius:18px;padding:14px;box-shadow:0 8px 22px rgba(31,41,51,.06);position:sticky;top:0;z-index:20}
.search-row{display:grid;grid-template-columns:minmax(280px,1fr) 170px 190px 140px 96px;gap:10px}.input,.select{width:100%;border:1px solid #d7dce2;border-radius:12px;padding:11px 12px;background:#fff;color:var(--ink);outline:none}.input:focus,.select:focus{border-color:#a93a31;box-shadow:0 0 0 3px rgba(169,58,49,.12)}
.btn{border:0;border-radius:12px;padding:10px 14px;background:var(--red);color:#fff;cursor:pointer;font-weight:700}.btn.secondary{background:#f1f3f5;color:#334155;border:1px solid #dbe1e8}.btn:hover{filter:brightness(.96)}
.tabs{display:flex;gap:8px;overflow-x:auto;padding:14px 0 4px}.tab{white-space:nowrap;border:1px solid #e1d0d0;background:#fff;color:#5f1d1d;border-radius:999px;padding:9px 13px;cursor:pointer;font-weight:700}.tab.active{background:var(--red);color:#fff;border-color:var(--red)}
.notice{border-radius:16px;padding:14px 16px;margin:14px 0;border:1px solid #ead7d7;background:#fffafa}.notice b{color:var(--red)}.notice.safe{background:#fbfff7;border-color:#d9eac8}.notice.warn{background:#fffaf0;border-color:#f0dfb8}.notice.danger{background:#fff4f4;border-color:#eac2c2}
.section-title{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin:22px 0 12px}.section-title h2{font-size:20px;margin:0;color:#5c1717}.section-title small{color:var(--muted)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}.grid.two{grid-template-columns:repeat(auto-fit,minmax(400px,1fr))}.grid.three{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.overview-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-bottom:14px}.stat,.card{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:16px;box-shadow:0 8px 22px rgba(31,41,51,.05);position:relative}.stat .label{color:var(--muted);font-size:13px}.stat .value{font-size:24px;font-weight:900;color:#7a1f1f;margin:6px 0}.stat .desc,.sub,.mini{color:var(--muted);font-size:13px}.card h3{margin:0 0 6px;font-size:18px;color:#253041}.card .kv{color:#4b5563;font-size:14px;margin:10px 0}.card-actions{display:flex;justify-content:space-between;align-items:center;margin-top:12px;gap:10px}.linkbtn{border:1px solid #d8caca;background:#fff;color:#7a1f1f;border-radius:10px;padding:7px 10px;cursor:pointer;font-weight:800}.linkbtn:hover{background:#fff4f4}
.tags{display:flex;flex-wrap:wrap;gap:7px}.pill{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:4px 9px;background:#f1f3f5;color:#344054;font-size:12px;max-width:100%;word-break:break-word}.pill.red{background:#fff1f1;color:#7a1f1f}.pill.orange{background:#fff7ed;color:#9a3412}.pill.blue{background:#eef6ff;color:#075985}.pill.light{background:rgba(255,255,255,.16);color:#fff;border:1px solid rgba(255,255,255,.26)}
.risk{position:absolute;top:14px;right:14px;color:#fff;border-radius:999px;padding:4px 10px;font-size:12px;font-weight:900}.risk.low{background:var(--green)}.risk.mid{background:var(--yellow)}.risk.midhi{background:var(--orange)}.risk.high{background:var(--high)}.risk.severe{background:var(--severe)}
.chain{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.step{border:1px solid #eadede;background:#fffafa;border-radius:12px;padding:7px 9px;font-size:13px}.arrow{color:#a33;font-weight:900}
.table-wrap{background:#fff;border:1px solid var(--line);border-radius:16px;overflow:auto;margin:10px 0}table{width:100%;border-collapse:collapse;font-size:14px}th{background:#faf3f3;color:#6b1717;text-align:left;position:sticky;top:0;z-index:1}th,td{padding:10px 12px;border-bottom:1px solid #edf0f3;vertical-align:top}td ul{margin:0;padding-left:18px}.timeline{border-left:3px solid #b84632;margin-left:12px}.timeline-item{margin:0 0 16px 18px;position:relative}.timeline-item:before{content:"";width:12px;height:12px;background:#b84632;border-radius:50%;position:absolute;left:-26px;top:22px;box-shadow:0 0 0 5px #f7dada}
.search-results{display:none;background:#fff;border:1px solid var(--line);border-radius:16px;margin-top:12px;max-height:330px;overflow:auto}.result-row{display:flex;justify-content:space-between;gap:12px;align-items:center;border-top:1px solid #eef0f2;padding:10px 12px}.result-row:first-child{border-top:0}.result-title{font-weight:900}.result-source{font-size:12px;color:var(--muted)}.empty{background:#fff;border:1px dashed #d2d8e0;border-radius:16px;padding:22px;text-align:center;color:var(--muted)}
.modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;z-index:100;padding:18px}.modal{width:min(1180px,96vw);max-height:92vh;background:#fff;border-radius:22px;box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden}.modal-head{padding:18px 20px;background:linear-gradient(135deg,#711818,#a5362f);color:#fff;display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.modal-head h2{margin:0;font-size:22px}.modal-sub{font-size:13px;color:rgba(255,255,255,.82);margin-top:4px}.modal-close{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.14);color:#fff;border-radius:10px;padding:8px 10px;cursor:pointer;font-weight:800;white-space:nowrap}.modal-tabs{display:flex;gap:8px;overflow-x:auto;padding:12px 16px;border-bottom:1px solid var(--line);background:#fff7f7}.modal-tab{border:1px solid #e2cfcf;background:#fff;color:#6b1717;border-radius:999px;padding:8px 12px;cursor:pointer;white-space:nowrap;font-weight:800}.modal-tab.active{background:#7a1f1f;color:#fff;border-color:#7a1f1f}.modal-body{padding:18px;overflow:auto}.detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:14px}.detail-section{border:1px solid #e5e7eb;border-radius:16px;background:#fff;padding:14px}.detail-section h4{margin:0 0 8px;color:#6b1717}.detail-section p{margin:0;white-space:pre-line}.field-list{margin:0;padding-left:18px}.object-card{border:1px solid #e7eaf0;background:#fbfcfe;border-radius:14px;padding:10px;margin:8px 0}.object-card h5{margin:0 0 6px;color:#354052}.modal-grid-large{display:grid;grid-template-columns:1.2fr .8fr;gap:14px}.json-note{font-size:12px;color:rgba(255,255,255,.86);margin-top:6px}
@media(max-width:900px){.search-row{grid-template-columns:1fr}.grid.two,.grid.three,.modal-grid-large{grid-template-columns:1fr}.header h1{font-size:24px}.app{padding:14px}.modal{width:100vw;height:100vh;max-height:100vh;border-radius:0}.modal-backdrop{padding:0}.modal-head{flex-direction:column}.risk{position:static;display:inline-block;margin-bottom:8px}}
</style>
</head>
<body>
<header class="header"><div class="header-inner"><h1 id="pageTitle"></h1><p id="pageSubtitle"></p><div class="top-pills" id="metaPills"></div></div></header>
<main class="app">
<section class="toolbar">
<div class="search-row">
<input class="input" id="globalSearch" placeholder="全局搜索:组织、人物、平台、事件、叙事、涉华涉台议题……" />
<select class="select" id="riskFilter"><option value="">全部风险等级</option></select>
<select class="select" id="typeFilter"><option value="">全部类型/节点</option></select>
<select class="select" id="sourceFilter"><option value="">全部来源</option></select>
<button class="btn secondary" id="resetBtn">重置</button>
</div>
<div id="searchResults" class="search-results"></div>
<nav class="tabs" id="tabs"></nav>
</section>
<div class="notice" id="tabDesc"></div>
<section id="content"></section>
</main>
<div class="modal-backdrop" id="modalBackdrop">
<div class="modal" role="dialog" aria-modal="true">
<div class="modal-head">
<div><h2 id="modalTitle"></h2><div class="modal-sub" id="modalSubtitle"></div><div class="json-note">详情页为结构化展示;原始 JSON 仅可通过按钮复制。</div></div>
<div style="display:flex;gap:8px;align-items:center"><button class="modal-close" id="copyJsonBtn">复制本条JSON</button><button class="modal-close" id="closeModal">关闭</button></div>
</div>
<div class="modal-tabs" id="modalTabs"></div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
<script>
const DATA = {"meta": {"title": "美方新右翼与极右翼群体研究专题库", "subtitle": "公开资料研究|组织谱系|叙事结构|平台生态|地方嵌入|涉华涉台议题", "version": "v3.0-full-structured-no-blank", "language": "zh-CN", "lastUpdated": "2026-06-25", "researchScope": ["美国新右翼政策网络", "极右翼街头组织", "反政府民兵", "白人民族主义组织", "阴谋论运动", "地方文化战生态", "涉华涉台右翼叙事"], "researchBoundary": "本专题库仅用于公开资料研究、政策分析、舆情观察和风险防护,不用于渗透、动员、骚扰、目标定位、动员招募、规避平台治理或暴力指导。", "methodNote": "所有条目均以公开资料研究为定位;风险等级是研究性提示,不等同于法律定性。新右翼、传统保守主义、宗教保守主义、共和党支持者与暴力极端主义必须严格区分。", "dataDesignNote": "v3 修复详情页空白问题:弹框改为按字段动态分组,只有存在内容的二级页才显示;同时增加“全部字段”结构化页,避免知识删减或字段遗漏。", "allowedUses": ["公开资料研究", "非操控性风险监测", "舆情趋势分析", "反极端主义预防", "政策建议", "公共沟通建议", "族群安全风险提示", "来源可信度审查"], "prohibitedUses": ["渗透、诱导、策反或操控任何群体", "制造、放大或投放欺骗性信息", "对特定个人进行画像、骚扰、跟踪、威胁或 doxxing", "规避平台治理、执法监测或安全机制", "提供暴力、破坏、骚乱组织、逃避执法或现场对抗的操作性指导", "针对族裔、宗教、政治立场、国籍、性别或性取向群体进行仇恨动员"], "coreResearchChain": ["概念区分", "历史演化", "主要群体与组织谱系", "人员结构与社会基础", "叙事结构与受众心理", "社交媒体与平台生态", "生态网络与影响通道", "涉华涉台议题嵌入路径", "风险行为链条", "地方社会与经济利益", "资源、法律与治理反应"], "interpretationPrinciple": "保守主义立场、共和党支持、反移民政策主张、反中共言论、支持台湾或反对美国介入台湾,本身均不等于极端主义;只有出现去人化、暴力正当化、具体目标指向、威胁或行动化迹象,才进入高风险研判。"}, "dictionaries": {"riskLevels": {"低": "普通政治表达、政策争议或低风险研究对象", "中": "存在议题极化、阴谋论倾向或平台扩散风险", "中-高": "存在群体泛化、目标个人化、地方冲突或跨平台扩散风险", "高": "存在反政府动员、仇恨叙事、线下行动化或暴力正当化风险", "严重": "存在现实暴力、模仿袭击、攻击者崇拜或明确威胁风险"}, "riskColors": {"低": "green", "中": "yellow", "中-高": "orange", "高": "red", "严重": "darkred"}, "sourceLevels": {"A": "法院、政府、国会、执法机构等官方公开文件", "B": "主流媒体或可靠地方媒体", "C": "研究机构、智库、倡议组织", "D": "学术论文、预印本、数据集", "E": "公开社交媒体样本,仅作为舆情线索", "F": "匿名论坛、截图、二手转述,只能作为待核查线索"}, "organizationTypes": ["新右翼政策网络", "极右翼街头组织", "反政府民兵", "白人民族主义组织", "阴谋论运动", "主权公民运动", "宗教民族主义网络", "男性至上主义生态", "反移民组织", "去中心化训练网络"], "platformTypes": ["主流社交平台", "替代社交平台", "匿名论坛", "长视频平台", "播客生态", "众筹平台", "地方社群平台", "邮件/通讯平台", "独立网站"], "researchModules": {"concepts_typology": "概念与谱系:区分新右翼、极右翼和暴力极端主义", "history_evolution": "历史演化:从白人至上、民兵、Tea Party、Alt-Right 到 MAGA 与新右翼制度化", "groups_organizations": "群体与组织:组织资料卡、行动形态与风险边界", "personnel_social_base": "人员与社会基础:受众分层、人员生态与地方嵌入", "narratives_psychology": "叙事与心理:解释世界、塑造敌人、吸引受众的叙事结构", "social_media_ecology": "社交媒体生态:引流、沉淀、强化、迁移和线下转化", "influence_networks": "生态网络与影响通道:思想、媒体、平台、地方、资金如何形成影响", "china_taiwan_issues": "涉华涉台议题:TikTok、芯片、疫情、留学生、芬太尼、台湾和台海", "local_society_economics": "地方社会与经济利益:学校、教会、县级政治、注意力经济与法律基金"}, "riskBehaviorSignals": ["明确敌人 + 末日紧迫感", "去人化语言 + 行动号召", "目标地点 + 时间窗口", "个人 grievance + 武器文化 + 社群鼓励", "过去袭击者崇拜 + 模仿语言", "执法/法院不合法叙事 + 被捕者殉道化"], "sourLine truncated
let activeTab = 'dashboard';
let currentModalItem = null; let currentModalKey = null; let currentModalGroup = null;
const KEY_TO_TITLE = Object.fromEntries(DATA.tabs.map(t=>[t.dataKey,t.name]));
const SEARCH_KEYS = (DATA.queryConfig && DATA.queryConfig.searchKeys) || DATA.tabs.map(t=>t.dataKey).filter(k=>Array.isArray(DATA[k]));
const riskClass = r => ({'低':'low','中':'mid','中-高':'midhi','高':'high','严重':'severe'}[r] || '');
function esc(s){ return String(s ?? '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }
function arr(v){ return Array.isArray(v) ? v : (v === undefined || v === null || v === '' ? [] : [v]); }
function isEmpty(v){ return v === undefined || v === null || v === '' || (Array.isArray(v)&&v.length===0) || (typeof v==='object'&&!Array.isArray(v)&&Object.keys(v).length===0); }
function titleOf(item){ return item['名称'] || item['事件'] || item['议题'] || item['平台'] || item['场景'] || item['受众类型'] || item['阶段'] || item['概念'] || item['类型'] || item.label || item.id || '条目'; }
function subOf(item){ return item['中文名称'] || item['英文'] || item['事件类型'] || item['议题类型'] || item['平台类型'] || item['节点类型'] || item['时间'] || item['基本定位'] || item.description || ''; }
function riskBadge(r){ return r ? `<span class="risk ${riskClass(r)}">${esc(r)}</span>` : ''; }
function tags(list, cls=''){ return arr(list).slice(0,18).map(x=>`<span class="pill ${cls}">${esc(typeof x==='object' ? objectSummary(x) : x)}</span>`).join(''); }
function objectSummary(o){ return Object.entries(o).map(([k,v])=>`${k}:${Array.isArray(v)?v.join('、'):v}`).join(';'); }
function init(){
document.getElementById('pageTitle').textContent = DATA.meta.title || '专题库';
document.getElementById('pageSubtitle').textContent = (DATA.meta.subtitle||'') + '。' + (DATA.meta.interpretationPrinciple||'');
document.getElementById('metaPills').innerHTML = [`版本:${DATA.meta.version}`,`更新:${DATA.meta.lastUpdated}`,'11个研究栏目','结构化详情页','无空白二级页'].map(x=>`<span class="pill light">${esc(x)}</span>`).join('');
renderFilters(); renderTabs(); bindEvents(); render();
}
function renderFilters(){
const rf=document.getElementById('riskFilter'); Object.keys(DATA.dictionaries.riskLevels||{}).forEach(r=>rf.insertAdjacentHTML('beforeend',`<option>${esc(r)}</option>`));
const sf=document.getElementById('sourceFilter'); Object.keys(DATA.dictionaries.sourceLevels||{}).forEach(s=>sf.insertAdjacentHTML('beforeend',`<option>${esc(s)}</option>`));
updateTypeFilter();
}
function updateTypeFilter(){
const sel=document.getElementById('typeFilter'); const old=sel.value; sel.innerHTML='<option value="">全部类型/节点</option>';
const tab=DATA.tabs.find(t=>t.id===activeTab); const d=DATA[tab.dataKey]; const set=new Set();
if(Array.isArray(d)) d.forEach(it=>['类型','节点类型','平台类型','事件类型','议题类型','受众类型','场景','概念'].forEach(k=>{if(it[k]) set.add(it[k]);}));
[...set].sort().forEach(v=>sel.insertAdjacentHTML('beforeend',`<option>${esc(v)}</option>`));
if([...set].includes(old)) sel.value=old;
}
function renderTabs(){ document.getElementById('tabs').innerHTML = DATA.tabs.map(t=>`<button class="tab ${t.id===activeTab?'active':''}" data-tab="${t.id}">${esc(t.name)}</button>`).join(''); }
function bindEvents(){
document.getElementById('tabs').addEventListener('click', e=>{if(e.target.dataset.tab){activeTab=e.target.dataset.tab; renderTabs(); updateTypeFilter(); render();}});
['globalSearch','riskFilter','typeFilter','sourceFilter'].forEach(id=>document.getElementById(id).addEventListener('input',()=>{ renderSearch(); render(); }));
document.getElementById('resetBtn').onclick=()=>{['globalSearch','riskFilter','typeFilter','sourceFilter'].forEach(id=>document.getElementById(id).value=''); document.getElementById('searchResults').style.display='none'; render();};
document.getElementById('closeModal').onclick=closeModal;
document.getElementById('modalBackdrop').addEventListener('click', e=>{if(e.target.id==='modalBackdrop') closeModal();});
document.getElementById('copyJsonBtn').onclick=()=>{ if(currentModalItem) navigator.clipboard.writeText(JSON.stringify(currentModalItem,null,2)); };
document.getElementById('modalTabs').addEventListener('click', e=>{ if(e.target.dataset.group){currentModalGroup=e.target.dataset.group; renderModalBody();} });
}
function itemText(it){ return JSON.stringify(it).toLowerCase(); }
function filterData(items){
if(!Array.isArray(items)) return items;
const q=document.getElementById('globalSearch').value.trim().toLowerCase(); const risk=document.getElementById('riskFilter').value; const type=document.getElementById('typeFilter').value; const source=document.getElementById('sourceFilter').value;
return items.filter(it=>{
if(q && !itemText(it).includes(q)) return false;
if(risk && it['风险等级']!==risk) return false;
if(source && !arr(it['来源等级']).includes(source)) return false;
if(type){ const vals=['类型','节点类型','平台类型','事件类型','议题类型','受众类型','场景','概念'].map(k=>it[k]).filter(Boolean); if(!vals.includes(type)) return false; }
return true;
});
}
function render(){
const tab=DATA.tabs.find(t=>t.id===activeTab); document.getElementById('tabDesc').innerHTML = `<b>${esc(tab.name)}</b>:${esc(tab.description||'')}`;
const raw=DATA[tab.dataKey]; const filtered=filterData(raw);
if(tab.dataKey==='dashboard') return renderDashboard(raw);
if(tab.layout==='timeline' || tab.layout==='timelineTable') return renderTimeline(filtered, tab.dataKey);
if(tab.layout==='tableCard') return renderTableCards(filtered, tab.dataKey);
return renderCards(filtered, tab.dataKey, tab.layout);
}
function renderDashboard(d){
const html = [];
html.push(`<div class="overview-grid">${d.summaryCards.map(c=>`<div class="stat"><div class="label">${esc(c.label)}</div><div class="value">${esc(c.value)}${c.unit?`<span style="font-size:14px">${esc(c.unit)}</span>`:''}</div><div class="desc">${esc(c.description||'')}</div></div>`).join('')}</div>`);
html.push(`<div class="notice safe"><b>研究边界:</b>${esc(DATA.meta.researchBoundary)}<br><b>判读原则:</b>${esc(DATA.meta.interpretationPrinciple)}</div>`);
html.push(`<div class="grid two"><div class="card"><h3>热点议题</h3><div class="tags">${tags(d.hotIssues,'red')}</div></div><div class="card"><h3>重点风险关注</h3>${listBlock(d.riskFocus)}</div></div>`);
html.push(section('核心研究问题','来自原 Skill 的研究框架'));
html.push(`<div class="grid three">${d.coreQuestions.map(q=>`<div class="card"><h3>${esc(q.title)}</h3><p class="sub">${esc(q.content)}</p></div>`).join('')}</div>`);
html.push(section('风险行为链条模型','结构化展示,不提供操作性行动方案'));
html.push(`<div class="grid">${d.riskBehaviorModels.map(m=>`<div class="card"><h3>${esc(m.name)}</h3><div class="chain">${m.steps.map((s,i)=>`${i?'<span class="arrow">→</span>':''}<span class="step">${esc(s)}</span>`).join('')}</div></div>`).join('')}</div>`);
html.push(section('八类核心叙事模块','叙事-受众-风险点'));
html.push(simpleTable(d.narrativeModules,['叙事','核心内容','主要受众','风险点']));
html.push(section('六层生态网络','思想、媒体、平台、地方、街头、资源'));
html.push(simpleTable(d.ecosystemLayers,['层级','功能','典型节点']));
html.push(section('来源材料地图','对应原 Skill references/source_materials_map.md'));
html.push(renderObjectCards(d.sourceMaterialsMap,'来源类型'));
html.push(section('公开资料研究检查清单','对应原 Skill references/open_source_research_checklist.md'));
html.push(`<div class="grid three">${Object.entries(d.openSourceResearchChecklist||{}).map(([k,v])=>`<div class="card"><h3>${esc(k)}</h3>${listBlock(v)}</div>`).join('')}</div>`);
html.push(section('标准报告模板','对应原 Skill 的标准输出结构与模板'));
html.push(renderObjectCards(d.standardTemplates,'模板'));
html.push(section('未来趋势判断框架','对应原 Skill 的未来十年趋势判断框架'));
html.push(`<div class="card">${listBlock(d.futureTrendFramework)}</div>`);
html.push(`<div class="grid two" style="margin-top:14px"><div class="card"><h3>最小可用研判公式</h3><p class="sub" style="font-weight:900;color:#7a1f1f">${esc(d.minimumAssessmentFormula)}</p>${listBlock(d.formulaInterpretation)}</div><div class="card"><h3>事件触发覆盖范围</h3><div class="tags">${tags(d.triggerEventCoverage,'orange')}</div></div></div>`);
document.getElementById('content').innerHTML = html.join('');
}
function section(h,small=''){return `<div class="section-title"><h2>${esc(h)}</h2><small>${esc(small)}</small></div>`;}
function renderCards(items,key,layout){
const content=document.getElementById('content'); if(!items.length){content.innerHTML='<div class="empty">没有匹配的数据。请调整搜索或筛选条件。</div>';return;}
content.innerHTML = `<div class="grid">${items.map(it=>cardHTML(it,key)).join('')}</div>`; bindOpenButtons();
}
function pickTags(it){ return it['核心叙事'] || it['核心特征'] || it['主要议题'] || it['关联议题'] || it['易触发议题'] || it['典型内容'] || it['主要形式'] || it['外溢风险'] || it['代表现象'] || []; }
function cardHTML(it,key){
const title=titleOf(it), sub=subOf(it), risk=it['风险等级'];
const line1 = it['基本定位'] || it['公开身份'] || it['功能定位'] || it['研究意义'] || it['事件概况'] || it['研究价值'] || it['风险关注']?.[0] || '';
return `<article class="card">${riskBadge(risk)}<h3>${esc(title)}</h3><div class="sub">${esc(sub)}</div><div class="kv">${esc(String(line1)).slice(0,220)}</div><div class="tags">${tags(pickTags(it))}</div><div class="card-actions"><button class="linkbtn open-detail" data-id="${esc(it.id||title)}" data-key="${key}">查看结构化详情</button>${it['来源等级']?`<span class="mini">来源:${arr(it['来源等级']).join('/')}</span>`:''}</div></article>`;
}
function renderTimeline(items,key){
if(!items.length){document.getElementById('content').innerHTML='<div class="empty">没有匹配的数据。</div>';return;}
const rows=items.map(it=>`<div class="timeline-item"><div class="card">${riskBadge(it['风险等级'])}<h3>${esc(titleOf(it))}</h3><div class="sub">${esc(it['时间']||'')} ${it['地点']?'|'+esc(it['地点']):''}</div><div class="kv">${esc(it['研究意义']||it['事件类型']||it['事件概况']||'')}</div><div class="tags">${tags(it['主要特征']||it['核心叙事']||it['代表现象'])}</div><button class="linkbtn open-detail" data-id="${esc(it.id||titleOf(it))}" data-key="${key}">查看详情</button></div></div>`).join('');
document.getElementById('content').innerHTML=`<div class="timeline">${rows}</div>`; bindOpenButtons();
}
function renderTableCards(items,key){
if(!items.length){document.getElementById('content').innerHTML='<div class="empty">没有匹配的数据。</div>';return;}
document.getElementById('content').innerHTML = `<div class="table-wrap"><table><thead><tr><th>类型</th><th>对应生态</th><th>主要形式</th><th>相关议题</th><th>风险</th><th>操作</th></tr></thead><tbody>${items.map(it=>`<tr><td><b>${esc(it['类型'])}</b></td><td>${renderSimple(it['对应生态'])}</td><td>${renderSimple(it['主要形式'])}</td><td>${renderSimple(it['相关议题'])}</td><td>${it['风险等级']?`<span class="pill red">${esc(it['风险等级'])}</span>`:''}</td><td><button class="linkbtn open-detail" data-id="${esc(it.id||it['类型'])}" data-key="${key}">详情</button></td></tr>`).join('')}</tbody></table></div>`; bindOpenButtons();
}
function renderSimple(v){ return arr(v).map(x=>esc(typeof x==='object'?objectSummary(x):x)).join('、'); }
function simpleTable(rows, cols){ return `<div class="table-wrap"><table><thead><tr>${cols.map(c=>`<th>${esc(c)}</th>`).join('')}</tr></thead><tbody>${(rows||[]).map(r=>`<tr>${cols.map(c=>`<td>${renderValue(r[c])}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`; }
function renderObjectCards(rows,titleKey){ return `<div class="grid">${(rows||[]).map((r,i)=>`<div class="card"><h3>${esc(r[titleKey] || titleOf(r) || ('条目 '+(i+1)))}</h3>${Object.entries(r).filter(([k])=>k!==titleKey).map(([k,v])=>`<div style="margin-top:9px"><b style="color:#6b1717">${esc(k)}:</b>${renderValue(v)}</div>`).join('')}</div>`).join('')}</div>`; }
function listBlock(list){ return `<ul>${arr(list).map(x=>`<li>${esc(typeof x==='object'?objectSummary(x):x)}</li>`).join('')}</ul>`; }
function bindOpenButtons(){ document.querySelectorAll('.open-detail').forEach(btn=>btn.onclick=()=>openDetail(btn.dataset.key, btn.dataset.id)); }
function openDetail(key,id){
const arrData=DATA[key]||[]; currentModalItem=arrData.find(x=>(x.id||titleOf(x))===id) || arrData.find(x=>titleOf(x)===id); currentModalKey=key;
if(!currentModalItem) return; const groups=availableGroups(currentModalItem); currentModalGroup=groups[0]?.id || 'all';
document.getElementById('modalTitle').textContent=titleOf(currentModalItem); document.getElementById('modalSubtitle').textContent=(KEY_TO_TITLE[key]||key) + (subOf(currentModalItem)?'|'+subOf(currentModalItem):'');
renderModalTabs(); renderModalBody(); document.getElementById('modalBackdrop').style.display='flex';
}
function closeModal(){ document.getElementById('modalBackdrop').style.display='none'; }
function groupDefs(){ return (DATA.detailConfig && DATA.detailConfig.groups) || []; }
function groupFields(item, group){
const keys = group.keys === '*' ? Object.keys(item) : group.keys;
return keys.filter(k=>!isEmpty(item[k]));
}
function assignedKeys(){
const s=new Set(); groupDefs().forEach(g=>{ if(g.keys !== '*') g.keys.forEach(k=>s.add(k)); }); return s;
}
function availableGroups(item){
const defs=groupDefs();
const groups=[];
defs.forEach(g=>{
if(g.keys==='*') return;
if(groupFields(item,g).length) groups.push(g);
});
// Always include all fields as a structured page, never blank, but not JSON raw.
groups.push(defs.find(g=>g.id==='all') || {id:'all',name:'全部字段',keys:'*'});
return groups;
}
function renderModalTabs(){
const groups=availableGroups(currentModalItem);
if(!groups.find(g=>g.id===currentModalGroup)) currentModalGroup=groups[0].id;
document.getElementById('modalTabs').innerHTML=groups.map(g=>`<button class="modal-tab ${g.id===currentModalGroup?'active':''}" data-group="${esc(g.id)}">${esc(g.name)}</button>`).join('');
}
function renderModalBody(){
renderModalTabs(); const group=availableGroups(currentModalItem).find(g=>g.id===currentModalGroup); let html='';
if(currentModalGroup==='boundary') html += `<div class="notice danger"><b>安全提醒:</b>${esc(DATA.meta.researchBoundary)}</div>`;
if(currentModalGroup==='all') {
const entries=Object.entries(currentModalItem).filter(([k,v])=>!isEmpty(v));
html += `<div class="notice warn"><b>全部字段结构化展示:</b>这一页不是 JSON 原文,而是将该条目的所有非空字段按字段卡片呈现,避免详情内容遗漏。</div>`;
html += `<div class="detail-grid">${entries.map(([k,v])=>fieldHTML(k,v)).join('')}</div>`;
} else {
const fields=groupFields(currentModalItem, group);
if(!fields.length) html += '<div class="empty">该分类没有可展示字段,已自动隐藏;请查看“全部字段”。</div>';
else html += `<div class="detail-grid">${fields.map(k=>fieldHTML(k,currentModalItem[k])).join('')}</div>`;
}
document.getElementById('modalBody').innerHTML=html;
}
function fieldHTML(k,v){ return `<section class="detail-section"><h4>${esc(k)}</h4><div class="field">${renderValue(v)}</div></section>`; }
function renderValue(v){
if(isEmpty(v)) return '<span class="mini">暂无数据</span>';
if(Array.isArray(v)){
if(v.every(x=>typeof x !== 'object')){
if(v.length<=12 && v.every(x=>String(x).length<60)) return `<div class="tags">${tags(v)}</div>`;
return `<ul class="field-list">${v.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`;
}
return v.map((obj,i)=>`<div class="object-card"><h5>条目 ${i+1}</h5>${objectTable(obj)}</div>`).join('');
}
if(typeof v === 'object') return objectTable(v);
const str=String(v);
if(str.length>180) return `<p>${esc(str)}</p>`;
return `<p>${esc(str)}</p>`;
}
function objectTable(obj){ return `<div class="table-wrap"><table><tbody>${Object.entries(obj).filter(([k,v])=>!isEmpty(v)).map(([k,v])=>`<tr><th>${esc(k)}</th><td>${renderValue(v)}</td></tr>`).join('')}</tbody></table></div>`; }
function renderSearch(){
const q=document.getElementById('globalSearch').value.trim().toLowerCase(); const box=document.getElementById('searchResults');
if(!q){box.style.display='none';return;}
const res=[]; SEARCH_KEYS.forEach(k=>(DATA[k]||[]).forEach(it=>{ if(itemText(it).includes(q)) res.push({key:k,item:it}); }));
box.style.display='block'; box.innerHTML = `<div style="padding:10px 12px"><b>全局搜索结果:${res.length} 条</b></div>` + (res.length?res.slice(0,50).map(r=>`<div class="result-row"><div><div class="result-title">${esc(titleOf(r.item))}</div><div class="result-source">来源栏目:${esc(KEY_TO_TITLE[r.key]||r.key)} ${r.item['风险等级']?'|风险:'+esc(r.item['风险等级']):''}</div></div><button class="linkbtn" onclick="openDetail('${r.key}','${esc(r.item.id||titleOf(r.item))}')">查看</button></div>`).join(''):'<div class="empty">无匹配结果</div>');
}
init();
</script>
</body>
</html>
@@ -0,0 +1,220 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>台海经济民生叙事监测专题平台</title>
<style>
:root {
--bg:#7f1d1d;
--bg2:#991b1b;
--panel:#fff7ed;
--card:#ffffff;
--text:#1f2937;
--muted:#6b7280;
--line:#e5e7eb;
--primary:#b91c1c;
--primary2:#ef4444;
--amber:#f59e0b;
--green:#16a34a;
--blue:#2563eb;
--shadow: 0 18px 45px rgba(30, 41, 59, .18);
--radius: 22px;
}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',Arial,sans-serif;background:linear-gradient(135deg,var(--bg),#b91c1c 45%,#f97316);color:var(--text);}
a{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
.shell{max-width:1440px;margin:0 auto;padding:28px 24px 60px;}
.hero{background:linear-gradient(135deg,rgba(255,255,255,.94),rgba(255,247,237,.88));border:1px solid rgba(255,255,255,.8);border-radius:32px;padding:28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
.hero:after{content:'';position:absolute;right:-80px;top:-120px;width:300px;height:300px;border:42px solid rgba(185,28,28,.08);border-radius:50%;}
.hero h1{margin:0;font-size:34px;letter-spacing:.5px;color:#7f1d1d}
.hero p{max-width:980px;line-height:1.75;color:#4b5563;font-size:15px;margin:14px 0 0}
.hero-meta{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}
.badge{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:7px 12px;font-size:12px;background:#fff;border:1px solid #fecaca;color:#7f1d1d;font-weight:700}
.toolbar{margin:18px 0;background:rgba(255,255,255,.92);border:1px solid rgba(255,255,255,.8);border-radius:22px;padding:14px;display:grid;grid-template-columns:1fr 180px 180px 180px;gap:12px;box-shadow:0 8px 24px rgba(0,0,0,.08)}
.toolbar input,.toolbar select{width:100%;border:1px solid #e5e7eb;border-radius:14px;padding:12px 14px;font-size:14px;background:#fff;color:var(--text);outline:none}
.tabs{position:sticky;top:0;z-index:10;margin:16px 0 18px;background:rgba(255,255,255,.9);backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.8);border-radius:24px;padding:8px;display:flex;gap:8px;overflow-x:auto;box-shadow:0 10px 24px rgba(0,0,0,.12)}
.tab-btn{border:0;background:transparent;white-space:nowrap;padding:11px 15px;border-radius:18px;color:#6b7280;font-weight:800;cursor:pointer}
.tab-btn.active{background:linear-gradient(135deg,#b91c1c,#ef4444);color:white;box-shadow:0 8px 18px rgba(185,28,28,.25)}
.main{background:rgba(255,255,255,.93);border:1px solid rgba(255,255,255,.8);border-radius:30px;padding:24px;box-shadow:var(--shadow);min-height:580px}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:18px;border-bottom:1px solid var(--line);padding-bottom:14px}
.section-head h2{margin:0;color:#7f1d1d;font-size:24px}
.section-head p{margin:6px 0 0;color:var(--muted);line-height:1.65}
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
.grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:18px;box-shadow:0 8px 20px rgba(31,41,55,.06);position:relative;overflow:hidden}
.card h3{margin:0 0 10px;color:#111827;font-size:17px}
.card p{margin:8px 0;color:#4b5563;line-height:1.68;font-size:14px}
.kpi .value{font-size:28px;font-weight:900;color:#991b1b;margin-top:8px}
.kpi .sub{font-size:13px;color:#6b7280;margin-top:6px}
.tag-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}
.tag{display:inline-flex;border-radius:999px;background:#fff7ed;border:1px solid #fed7aa;color:#9a3412;padding:4px 8px;font-size:12px;font-weight:700}
.tag.high,.risk-high{background:#fee2e2;border-color:#fecaca;color:#991b1b}
.tag.medium,.risk-medium{background:#fef3c7;border-color:#fde68a;color:#92400e}
.tag.low,.risk-low{background:#dcfce7;border-color:#bbf7d0;color:#166534}
.temp-wrap{display:grid;grid-template-columns: 1fr 1fr;gap:16px;margin-bottom:18px}
.temp-card{background:linear-gradient(135deg,#fff,#fff7ed);border:1px solid #fed7aa;border-radius:24px;padding:18px}
.bars{display:flex;gap:8px;margin-top:12px;align-items:end;height:74px}
.bar{flex:1;background:linear-gradient(180deg,#ef4444,#991b1b);border-radius:10px 10px 4px 4px;min-height:8px;position:relative}
.bar span{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);font-size:12px;font-weight:900;color:#7f1d1d;margin-bottom:4px}
.table-wrap{overflow:auto;border:1px solid var(--line);border-radius:18px;background:#fff}
table{border-collapse:collapse;width:100%;font-size:14px;min-width:780px}
th,td{border-bottom:1px solid var(--line);padding:12px 14px;text-align:left;vertical-align:top;line-height:1.55}
th{background:#fff7ed;color:#7f1d1d;font-weight:900;position:sticky;top:0}
tr:hover td{background:#fffafa}
.timeline{position:relative;margin-left:8px}
.timeline:before{content:'';position:absolute;left:18px;top:4px;bottom:4px;width:3px;background:#fecaca;border-radius:99px}
.tl-item{position:relative;margin:0 0 14px 0;padding-left:54px}
.tl-dot{position:absolute;left:8px;top:18px;width:23px;height:23px;background:#fff;border:5px solid #b91c1c;border-radius:50%;box-shadow:0 0 0 5px #fee2e2}
.tl-card{cursor:pointer;transition:.2s}
.tl-card:hover{transform:translateY(-2px);box-shadow:0 12px 28px rgba(185,28,28,.16)}
.tl-date{font-weight:900;color:#991b1b;font-size:13px;margin-bottom:6px}
.pill{display:inline-block;padding:5px 9px;border-radius:999px;background:#f3f4f6;color:#4b5563;font-size:12px;font-weight:800;margin-right:6px;margin-top:6px}
.source-list{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}
.source-item{border:1px solid var(--line);border-radius:16px;padding:14px;background:#fff}
.source-item strong{color:#7f1d1d}
.muted{color:var(--muted)}
.empty{padding:40px;border:1px dashed #fecaca;border-radius:20px;text-align:center;color:#9ca3af;background:#fffafa}
.modal-backdrop{position:fixed;inset:0;background:rgba(17,24,39,.55);display:none;align-items:center;justify-content:center;z-index:50;padding:20px}
.modal{max-width:900px;width:100%;max-height:86vh;overflow:auto;background:#fff;border-radius:28px;box-shadow:0 32px 80px rgba(0,0,0,.35);padding:24px;border:1px solid #fee2e2}
.modal h2{color:#7f1d1d;margin:0 0 10px}
.modal-close{float:right;border:0;background:#fee2e2;color:#991b1b;border-radius:12px;padding:8px 12px;font-weight:900;cursor:pointer}
.footer{margin-top:18px;color:#fff7ed;text-align:center;font-size:13px;line-height:1.7}
@media (max-width:1100px){.grid{grid-template-columns:repeat(2,1fr)}.grid3{grid-template-columns:repeat(2,1fr)}.toolbar{grid-template-columns:1fr 1fr}.source-list{grid-template-columns:1fr}}
@media (max-width:720px){.shell{padding:16px 12px 42px}.hero h1{font-size:25px}.main{padding:16px}.grid,.grid2,.grid3,.temp-wrap{grid-template-columns:1fr}.toolbar{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="shell">
<header class="hero">
<h1>台海经济民生叙事监测专题平台</h1>
<p>围绕“军事介入台海经济民生必下降”这一叙事,建立公开资料研究型数据库。页面用于拆解事件、预算、军售、半导体、航运、贸易、受众和平台传播之间的关系,同时展示竞争叙事与反弹逻辑。</p>
<div class="hero-meta">
<span class="badge">版本 1.0</span>
<span class="badge">生成日期:2026-06-25</span>
<span class="badge">公开资料研究</span>
<span class="badge">非操纵性传播方案</span>
</div>
</header>
<div class="toolbar">
<input id="searchBox" placeholder="搜索事件、叙事、受众、关键词……" />
<select id="categoryFilter"><option value="all">全部类别</option></select>
<select id="riskFilter"><option value="all">全部风险</option><option value="high">高风险</option><option value="medium">中风险</option><option value="low">低风险</option></select>
<select id="regionFilter"><option value="all">全部地区</option></select>
</div>
<nav class="tabs" id="tabs"></nav>
<main class="main" id="content"></main>
<div class="footer">边界说明:本页面仅用于公开资料研究、政策风险监测与舆论观察;不提供虚假信息、隐蔽影响、假账号或定向操纵建议。</div>
</div>
<div class="modal-backdrop" id="modalBackdrop"><div class="modal" id="modal"></div></div>
<script id="dataset" type="application/json">{"metadata": {"title": "台海经济民生叙事监测专题数据库", "version": "1.0", "generated_at": "2026-06-25", "language": "zh-CN", "scope": "公开资料研究、舆论观察、风险评估、政策过程分析。", "safety_boundary": "不包含虚假信息制造、假账号操作、定向骚扰、隐蔽影响或操纵性传播方案。", "core_question": "美国对台军售、印太军事部署与台海战备如何被叙事化为经济民生风险,以及该叙事如何与威慑避战、供应链韧性等反叙事竞争。"}, "tabs": [{"id": "overview", "name": "总览仪表盘", "description": "展示当前叙事温度、核心 KPI、重大事件和风险等级。"}, {"id": "events", "name": "关键事件时间线", "description": "汇总军售、军演、预算、半导体、贸易和航运事件。"}, {"id": "budget", "name": "防务预算与民生成本", "description": "分析国防预算、特别预算、财政压力和民生争议。"}, {"id": "arms", "name": "美国军售与军工链", "description": "展示军售项目、金额、后勤、供应商核查和交付争议。"}, {"id": "semi", "name": "半导体与硅盾变化", "description": "展示台积电、ICT 出口、半导体外移和供应链韧性。"}, {"id": "maritime", "name": "海峡航运与贸易通道", "description": "展示封锁、检疫、海警、保险和海峡贸易风险。"}, {"id": "trade", "name": "美台贸易与美国成本", "description": "分析美台贸易逆差、关税、产业回流和纳税人成本叙事。"}, {"id": "tw_audience", "name": "台湾内部受众", "description": "分析 DPP、KMT、中间选民、青年、工商业界和疑美群体。"}, {"id": "us_audience", "name": "美国国内受众", "description": "分析鹰派、MAGA 成本派、财政保守派和科技安全派。"}, {"id": "allies", "name": "亚太盟友与产业界", "description": "分析日本、韩国、菲律宾、澳大利亚、航运业和投资者。"}, {"id": "platforms", "name": "平台传播与关键词", "description": "展示平台观察矩阵和中英文关键词库。"}, {"id": "frames", "name": "叙事框架与反叙事", "description": "展示核心叙事框架、脆弱点和反叙事。"}], "sources": [{"id": "SRC-TW-MND", "name": "台湾国防部 National Defense Budgeting", "type": "官方", "url": "https://www.mnd.gov.tw/en/publication/129", "credibility": "A", "notes": "2026 防务预算、NATO 口径、GDP 占比、国防部预算增幅。"}, {"id": "SRC-TSMC", "name": "TSMC 官方新闻稿:扩大美国投资", "type": "企业官方", "url": "https://pr.tsmc.com/english/news/3210", "credibility": "A", "notes": "台积电美国投资增至 1650 亿美元、三座晶圆厂、两座先进封装设施、研发中心。"}, {"id": "SRC-USTR", "name": "美国贸易代表办公室:Taiwan", "type": "官方", "url": "https://ustr.gov/countries-regions/china/taiwan", "credibility": "A", "notes": "2025 年美台货物贸易额、美国对台出口、进口、逆差。"}, {"id": "SRC-TW-MOF-2025", "name": "台湾财政部 / Taiwan Today:2025 外贸创新高", "type": "官方/官方媒体", "url": "https://www.taiwantoday.tw/print/Economics/Top-News/280295/Taiwan-reports-record-high-foreign-trade-in-2025", "credibility": "A-", "notes": "2025 年台湾出口、进口、贸易顺差数据。"}, {"id": "SRC-CSIS-STRAIT", "name": "CSIS:Taiwan Strait trade disruption analysis", "type": "智库", "url": "https://www.csis.org/analysis/disruptions-trade-taiwan-strait-would-severely-impact-chinas-economy", "credibility": "A-", "notes": "台湾海峡贸易规模、区域经济体依赖估算。"}, {"id": "SRC-CHICAGO-2025", "name": "Chicago Council:Americans Grow More Supportive of Aiding Taiwan", "type": "民调/研究机构", "url": "https://globalaffairs.org/research/public-opinion-survey/americans-grow-more-supportive-aiding-taiwan-china-crisis", "credibility": "A-", "notes": "美国公众在台湾危机中的援助、制裁、军援、海军打破封锁、派兵支持率。"}, {"id": "SRC-BROOKINGS-2026", "name": "Brookings:How partisan is Taiwan's security debate?", "type": "智库/民调分析", "url": "https://www.brookings.edu/articles/how-partisan-is-taiwans-security-debate/", "credibility": "A-", "notes": "台湾特别防务预算支持率及党派差异。"}, {"id": "SRC-DSCA-TECO", "name": "DSCA:TECRO major arms sales archive", "type": "美国官方", "url": "https://www.dsca.mil/Press-Media/Major-Arms-Sales/Tag/295270/taipei-economic-and-cultural-representative-office-in-the-united-states-tecro", "credibility": "A", "notes": "2025 年 12 月及 2025 年 11 月对台军售通知。"}, {"id": "SRC-USTBC-2025-ARMS", "name": "USTBC:Eight arms sales to Taiwan valued at over US$11.1bn", "type": "行业组织/汇总", "url": "https://www.us-taiwan.org/resources/ustbc-comments-on-eight-arms-sales-to-taiwan-valued-at-over-us-11-1-billion/", "credibility": "A-", "Line truncated
<script>
const DATA = JSON.parse(document.getElementById('dataset').textContent);
let activeTab = 'overview';
const $ = sel => document.querySelector(sel);
const content = $('#content');
function sourceLinks(ids=[]){
return ids.map(id=>DATA.sources.find(s=>s.id===id)).filter(Boolean)
.map(s=>`<a href="${s.url}" target="_blank">${s.name}</a>`).join(';') || '<span class="muted">未标注来源</span>';
}
function riskClass(r){ return r==='high'||r==='高'?'high':r==='medium'||r==='中'?'medium':'low'; }
function esc(v){return String(v??'').replace(/[&<>"]/g, m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));}
function matchFilter(obj){
const q = $('#searchBox').value.trim().toLowerCase();
const cat = $('#categoryFilter').value;
const risk = $('#riskFilter').value;
const region = $('#regionFilter').value;
const text = JSON.stringify(obj).toLowerCase();
if(q && !text.includes(q)) return false;
if(cat !== 'all' && (obj.category||obj.type) !== cat) return false;
if(risk !== 'all' && riskClass(obj.risk_level||obj.risk) !== risk) return false;
if(region !== 'all' && (obj.region||'') !== region) return false;
return true;
}
function initFilters(){
const cats = [...new Set([...DATA.events.map(e=>e.category), ...DATA.indicators.map(i=>i.category)])].filter(Boolean);
$('#categoryFilter').innerHTML = '<option value="all">全部类别</option>' + cats.map(c=>`<option value="${esc(c)}">${esc(c)}</option>`).join('');
const regions = [...new Set([...DATA.events.map(e=>e.region), ...DATA.audiences.map(a=>a.region)])].filter(Boolean);
$('#regionFilter').innerHTML = '<option value="all">全部地区</option>' + regions.map(r=>`<option value="${esc(r)}">${esc(r)}</option>`).join('');
}
function initTabs(){
$('#tabs').innerHTML = DATA.tabs.map(t=>`<button class="tab-btn ${t.id===activeTab?'active':''}" data-tab="${t.id}">${t.name}</button>`).join('');
document.querySelectorAll('.tab-btn').forEach(btn=>btn.onclick=()=>{activeTab=btn.dataset.tab; render(); initTabs();});
}
function head(title, desc){ return `<div class="section-head"><div><h2>${title}</h2><p>${desc||''}</p></div></div>`; }
function kpiCard(k){ return `<div class="card kpi"><h3>${k.name}</h3><div class="value">${k.value}</div><div class="sub">${k.sub_value||''}</div><p>${k.interpretation||''}</p><div class="tag-row"><span class="tag ${riskClass(k.risk_level)}">${k.risk_level}风险</span><span class="tag">${k.category}</span></div><p class="muted">来源:${sourceLinks(k.source_ids)}</p></div>`; }
function card(title, body, tags=[], sources=[]){ return `<div class="card"><h3>${title}</h3><p>${body}</p><div class="tag-row">${tags.map(t=>`<span class="tag">${esc(t)}</span>`).join('')}</div>${sources.length?`<p class="muted">来源:${sourceLinks(sources)}</p>`:''}</div>`; }
function renderOverview(){
const temp = DATA.scoring_rules.current_temperature_sample;
content.innerHTML = head('总览仪表盘', DATA.tabs.find(t=>t.id==='overview').description) +
`<div class="temp-wrap"><div class="temp-card"><h3>叙事温度样本</h3><p>${temp.explanation}</p><div class="bars">${
[['原叙事',temp.original_narrative_heat],['竞争',temp.competing_narrative_heat],['反弹',temp.backlash_intensity],['政策',temp.policy_impact_likelihood]].map(([n,v])=>`<div class="bar" style="height:${v*14}px"><span>${v}</span></div>`).join('')
}</div><div class="tag-row">${temp.key_drivers.map(x=>`<span class="tag">${x}</span>`).join('')}</div></div><div class="temp-card"><h3>专题解释边界</h3><p>${DATA.metadata.safety_boundary}</p><p>核心问题:${DATA.metadata.core_question}</p></div></div>`+
`<div class="grid">${DATA.dashboard_kpis.map(kpiCard).join('')}</div>`+
`<div style="margin-top:18px">${head('数据缺口与后续采集', '这些字段已在页面预留,但需要后续日度或周度采样。')}<div class="grid3">${DATA.data_gaps.map(g=>card(g.item, g.reason, [g.status])).join('')}</div></div>`;
}
function openEvent(e){
$('#modal').innerHTML = `<button class="modal-close" onclick="closeModal()">关闭</button><h2>${e.title}</h2><p><strong>时间:</strong>${e.date} <strong>类别:</strong>${e.category} <strong>风险:</strong>${e.risk_level}</p><p>${e.event_summary}</p><h3>经济传导</h3><p>${(e.economic_link||[]).join('、')}</p><h3>叙事影响</h3><p>${e.narrative_effect}</p><h3>涉及行为体</h3><p>${(e.involved_actors||[]).join('、')}</p><h3>受影响受众</h3><p>${(e.affected_audiences||[]).join('、')}</p><div class="tag-row">${(e.tags||[]).map(t=>`<span class="tag">${t}</span>`).join('')}</div><p class="muted">来源:${sourceLinks(e.source_ids)}</p>`;
$('#modalBackdrop').style.display='flex';
}
function closeModal(){$('#modalBackdrop').style.display='none'}
window.closeModal = closeModal;
$('#modalBackdrop').onclick = e => { if(e.target.id==='modalBackdrop') closeModal(); };
function renderEvents(){
const arr = DATA.events.filter(matchFilter).sort((a,b)=>a.date.localeCompare(b.date));
content.innerHTML = head('关键事件时间线', DATA.tabs.find(t=>t.id==='events').description) + (arr.length?`<div class="timeline">${arr.map(e=>`<div class="tl-item"><div class="tl-dot"></div><div class="card tl-card" onclick='openEvent(${JSON.stringify(e).replace(/'/g,"&#39;")})'><div class="tl-date">${e.date}</div><h3>${e.title}</h3><p>${e.event_summary}</p><span class="pill">${e.category}</span><span class="pill">${e.region}</span><span class="pill">${e.risk_level}</span></div></div>`).join('')}</div>`:'<div class="empty">没有匹配事件</div>');
}
function renderBudget(){
content.innerHTML = head('防务预算与民生成本', DATA.tabs.find(t=>t.id==='budget').description) + `<div class="grid">${DATA.budget.cards.map(c=>`<div class="card kpi"><h3>${c.label}</h3><div class="value">${c.value}</div><p>${c.note}</p><span class="tag ${riskClass(c.risk)}">${c.risk}风险</span></div>`).join('')}</div><div style="margin-top:18px" class="grid2">${DATA.budget.debate_points.map(d=>card(d.title,d.content,d.tags)).join('')}</div><p class="muted">来源:${sourceLinks(DATA.budget.source_ids)}</p>`;
}
function renderArms(){
const rows = DATA.arms_sales.map(a=>`<tr><td><strong>${a.project}</strong><br><span class="muted">${a.id}</span></td><td>${a.amount_usd}</td><td>${a.type}</td><td>${a.narrative}</td><td>${sourceLinks(a.source_ids)}</td></tr>`).join('');
content.innerHTML = head('美国军售与军工链', DATA.tabs.find(t=>t.id==='arms').description) + `<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 renderSemi(){
content.innerHTML = head('半导体与“硅盾”变化', DATA.tabs.find(t=>t.id==='semi').description) + `<div class="grid">${DATA.semiconductor.tsmc_investment.map(x=>`<div class="card kpi"><h3>${x.label}</h3><div class="value">${x.value}</div></div>`).join('')}</div><div style="margin-top:18px" class="table-wrap"><table><thead><tr><th>出口类别</th><th>金额</th><th>同比</th><th>叙事意义</th></tr></thead><tbody>${DATA.semiconductor.export_structure.map(x=>`<tr><td>${x.category}</td><td>${x.amount}</td><td>${x.yoy}</td><td>${x.meaning}</td></tr>`).join('')}</tbody></table></div><div style="margin-top:18px" class="grid2">${DATA.semiconductor.frames.map(f=>card(f.title,f.content)).join('')}</div><p class="muted">来源:${sourceLinks(DATA.semiconductor.source_ids)}</p>`;
}
function renderMaritime(){
content.innerHTML = head('台湾海峡航运与贸易通道', DATA.tabs.find(t=>t.id==='maritime').description) + `<div class="grid">${DATA.maritime.metrics.map(m=>`<div class="card kpi"><h3>${m.label}</h3><div class="value">${m.value}</div><p>${m.note}</p></div>`).join('')}</div><div style="margin-top:18px" class="grid2">${DATA.maritime.risk_types.map(r=>card(r.type,r.transmission)).join('')}</div><p class="muted">来源:${sourceLinks(DATA.maritime.source_ids)}</p>`;
}
function renderTrade(){
content.innerHTML = head('美台贸易与美国成本叙事', DATA.tabs.find(t=>t.id==='trade').description) + `<div class="grid">${DATA.trade.us_taiwan.map(x=>`<div class="card kpi"><h3>${x.label}</h3><div class="value">${x.value}</div><p>${x.note||''}</p></div>`).join('')}</div><div style="margin-top:18px" class="grid2">${DATA.trade.frames.map(f=>card(f.title,f.content)).join('')}</div><p class="muted">来源:${sourceLinks(DATA.trade.source_ids)}</p>`;
}
function audienceCard(a){
return `<div class="card"><h3>${a.audience_group}</h3><p><strong>反应:</strong>${a.current_reaction} <strong>类别:</strong>${a.category}</p><p><strong>关注点:</strong>${a.concerns.join('、')}</p><p><strong>可能行为:</strong>${a.likely_behavior.join('、')}</p><p><strong>竞争叙事:</strong>${a.competing_narratives.join('、')}</p><div class="tag-row"><span class="tag">${a.region}</span><span class="tag">置信度:${a.confidence}</span></div><p class="muted">${a.caveat}</p></div>`;
}
function renderAudience(regionSet, title, desc){
const arr = DATA.audiences.filter(a=>regionSet.includes(a.region)).filter(matchFilter);
content.innerHTML = head(title, desc) + (arr.length?`<div class="grid3">${arr.map(audienceCard).join('')}</div>`:'<div class="empty">没有匹配受众</div>');
}
function renderPlatforms(){
const platCards = DATA.platform_observations.map(p=>`<div class="card"><h3>${p.platform}</h3><p>${p.narrative_variant}</p><p><strong>受众反应:</strong>${p.audience_reaction}</p><div class="tag-row">${p.example_keywords.map(k=>`<span class="tag">${k}</span>`).join('')}<span class="tag ${p.backlash_level==='high'?'high':p.backlash_level==='medium'?'medium':'low'}">反弹:${p.backlash_level}</span></div><p class="muted">${p.caveat}</p></div>`).join('');
const kw = Object.entries(DATA.keywords).map(([lang,groups])=>`<div class="card"><h3>${lang}</h3>${groups.map(g=>`<p><strong>${g.category}:</strong>${g.terms.map(t=>`<span class="pill">${t}</span>`).join('')}</p>`).join('')}</div>`).join('');
content.innerHTML = head('平台传播与关键词监测', DATA.tabs.find(t=>t.id==='platforms').description) + `<div class="grid2">${platCards}</div><div style="margin-top:18px" class="grid2">${kw}</div>`;
}
function renderFrames(){
const arr = DATA.narrative_frames.filter(matchFilter);
content.innerHTML = head('叙事框架与反叙事库', DATA.tabs.find(t=>t.id==='frames').description) + `<div class="grid2">${arr.map(f=>`<div class="card"><h3>${f.frame_name}</h3><p><strong>核心主张:</strong>${f.core_claim}</p><p><strong>支撑证据:</strong>${f.supporting_evidence.join(';')}</p><p><strong>脆弱点:</strong>${f.vulnerability}</p><p><strong>反叙事:</strong>${f.counter_frame}</p><div class="tag-row">${f.typical_trigger.map(t=>`<span class="tag">${t}</span>`).join('')}<span class="tag ${riskClass(f.risk_level)}">${f.risk_level}</span></div><p class="muted">来源:${sourceLinks(f.source_ids)}</p></div>`).join('')}</div><div style="margin-top:18px">${head('来源库', '核心事实来源与可信度。')}<div class="source-list">${DATA.sources.map(s=>`<div class="source-item"><strong>${s.name}</strong><p>${s.notes}</p><p><span class="pill">${s.type}</span><span class="pill">可信度 ${s.credibility}</span></p><a href="${s.url}" target="_blank">打开来源</a></div>`).join('')}</div></div>`;
}
function render(){
const t = DATA.tabs.find(x=>x.id===activeTab);
if(activeTab==='overview') return renderOverview();
if(activeTab==='events') return renderEvents();
if(activeTab==='budget') return renderBudget();
if(activeTab==='arms') return renderArms();
if(activeTab==='semi') return renderSemi();
if(activeTab==='maritime') return renderMaritime();
if(activeTab==='trade') return renderTrade();
if(activeTab==='tw_audience') return renderAudience(['Taiwan'], '台湾内部受众反应', t.description);
if(activeTab==='us_audience') return renderAudience(['United States'], '美国国内受众反应', t.description);
if(activeTab==='allies') return renderAudience(['Japan','South Korea','Philippines','Australia','Global'], '亚太盟友与产业界反应', t.description);
if(activeTab==='platforms') return renderPlatforms();
if(activeTab==='frames') return renderFrames();
}
['searchBox','categoryFilter','riskFilter','regionFilter'].forEach(id=>document.getElementById(id).addEventListener('input', render));
initFilters(); initTabs(); render();
</script>
</body>
</html>
@@ -0,0 +1,302 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>台湾涉两岸政治叙事监测专题</title>
<style>
:root {
--bg: #f6f2ee;
--panel: #fffaf6;
--panel2: #ffffff;
--ink: #201815;
--muted: #6f625c;
--line: #dbcfc8;
--line2: #bdaaa0;
--accent: #8f1f1b;
--accent2: #b63a2f;
--soft: #f1e2dc;
--soft2: #ead6cf;
--warn: #8c5a13;
--shadow: 0 16px 42px rgba(74, 37, 29, 0.12);
--radius: 18px;
}
* { box-sizing: border-box; }
html { scroll-behavior:smooth; }
body {
margin:0; font-family: "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", Arial, sans-serif;
color:var(--ink); background:
radial-gradient(circle at 12% -8%, rgba(143,31,27,.20), transparent 28%),
radial-gradient(circle at 95% 2%, rgba(182,58,47,.11), transparent 24%),
linear-gradient(180deg, #fff8f3 0%, var(--bg) 42%, #f4ece8 100%);
}
header.hero {
padding: 34px 34px 18px;
border-bottom: 1px solid var(--line);
}
.hero-inner { max-width: 1500px; margin:0 auto; }
.eyebrow {
display:inline-flex; gap:8px; align-items:center; padding:7px 12px; border:1px solid var(--line2);
border-radius:999px; background:rgba(255,255,255,.72); color:var(--accent); font-weight:700; font-size:13px;
}
h1 { margin:14px 0 10px; font-size:34px; letter-spacing:.5px; line-height:1.16; }
.subtitle { max-width:1050px; color:var(--muted); line-height:1.75; font-size:15px; }
.boundary {
margin-top:14px; padding:14px 16px; border-left:5px solid var(--accent); background:rgba(255,255,255,.72);
border-radius:14px; color:#4a332d; line-height:1.7;
}
.top-meta { display:flex; flex-wrap:wrap; gap:10px; margin-top:18px; }
.chip {
display:inline-flex; align-items:center; gap:6px; border:1px solid var(--line); background:var(--panel2);
color:#4b3933; border-radius:999px; padding:7px 11px; font-size:12px; font-weight:650;
}
.toolbar-wrap { position:sticky; top:0; z-index:20; background:rgba(246,242,238,.92); backdrop-filter: blur(10px); border-bottom:1px solid var(--line); }
.toolbar { max-width:1500px; margin:0 auto; padding:12px 34px; display:flex; gap:12px; align-items:center; flex-wrap:wrap; }
.tabs { display:flex; gap:8px; overflow:auto; flex:1; padding-bottom:2px; }
.tab-btn {
border:1px solid var(--line2); background:var(--panel); color:#3a2a25; border-radius:999px; padding:10px 15px;
cursor:pointer; white-space:nowrap; font-weight:750; font-size:13px; transition:.18s ease;
}
.tab-btn.active, .tab-btn:hover { background:var(--accent); color:white; border-color:var(--accent); }
.searchbar { display:flex; gap:8px; align-items:center; min-width:290px; }
.searchbar input, select {
width:100%; border:1px solid var(--line2); border-radius:12px; padding:10px 12px; background:#fff; color:var(--ink);
}
main { max-width:1500px; margin:0 auto; padding:24px 34px 54px; }
.tab-pane { display:none; animation: fadeIn .18s ease; }
.tab-pane.active { display:block; }
@keyframes fadeIn { from {opacity:.35; transform:translateY(4px)} to {opacity:1; transform:none} }
.section-title { display:flex; align-items:flex-end; justify-content:space-between; gap:18px; margin:0 0 15px; }
.section-title h2 { margin:0; font-size:24px; }
.section-title p { margin:6px 0 0; color:var(--muted); line-height:1.7; }
.grid { display:grid; gap:16px; }
.grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.card {
background:rgba(255,255,255,.86); border:1px solid var(--line); border-radius:var(--radius); padding:18px; box-shadow: var(--shadow);
}
.card.plain { box-shadow:none; background:var(--panel); }
.card h3 { margin:0 0 10px; font-size:18px; }
.card h4 { margin:0 0 8px; font-size:15px; }
.card p { color:#4e3e38; line-height:1.75; margin:8px 0; }
.kpi { min-height:120px; position:relative; overflow:hidden; }
.kpi::after { content:""; position:absolute; width:110px; height:110px; right:-40px; top:-45px; border-radius:50%; background:rgba(143,31,27,.08); }
.kpi .num { font-size:32px; font-weight:900; color:var(--accent); margin-top:8px; }
.kpi .label { color:var(--muted); font-size:13px; line-height:1.5; }
.tagrow { display:flex; flex-wrap:wrap; gap:7px; margin-top:10px; }
.tag { display:inline-flex; padding:5px 9px; border-radius:999px; background:var(--soft); border:1px solid #e3cbc2; color:#5a2b25; font-size:12px; font-weight:650; }
.tag.risk5 { background:#8f1f1b; color:#fff; border-color:#8f1f1b; }
.tag.risk4 { background:#c46845; color:#fff; border-color:#c46845; }
.tag.risk3 { background:#e5bd76; color:#3c2a14; border-color:#e5bd76; }
.tag.risk2 { background:#ede0bd; color:#3c2a14; }
.tag.risk1 { background:#e5efe1; color:#21401d; }
.filters { display:flex; gap:10px; flex-wrap:wrap; margin:0 0 16px; }
.filters select, .filters input { width:auto; min-width:190px; }
.timeline { position:relative; margin-left:8px; }
.timeline::before { content:""; position:absolute; left:15px; top:0; bottom:0; width:2px; background:var(--line2); }
.event-card { display:grid; grid-template-columns:42px 1fr; gap:13px; margin-bottom:14px; }
.event-dot { width:32px; height:32px; border-radius:50%; background:var(--accent); color:white; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:900; position:relative; z-index:1; box-shadow:0 6px 18px rgba(143,31,27,.22); }
.event-body { background:#fff; border:1px solid var(--line); border-radius:17px; padding:16px; box-shadow: 0 10px 26px rgba(74, 37, 29, 0.08); }
.event-head { display:flex; gap:10px; justify-content:space-between; align-items:flex-start; }
.event-date { color:var(--accent); font-weight:850; font-size:13px; white-space:nowrap; }
.btn {
border:1px solid var(--accent); background:var(--accent); color:#fff; border-radius:11px; padding:8px 11px; cursor:pointer; font-weight:750;
}
.btn.ghost { background:#fff; color:var(--accent); }
.detail-grid { display:grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap:12px; margin-top:12px; }
.detail-box { border:1px solid var(--line); border-radius:14px; padding:12px; background:#fffaf7; }
.detail-box b { color:var(--accent); display:block; margin-bottom:6px; }
ul.clean { margin:8px 0 0 18px; padding:0; }
ul.clean li { margin:5px 0; line-height:1.65; }
.matrix-table, .data-table { width:100%; border-collapse:separate; border-spacing:0; overflow:hidden; border:1px solid var(--line); border-radius:16px; background:#fff; }
.matrix-table th, .matrix-table td, .data-table th, .data-table td { border-bottom:1px solid var(--line); border-right:1px solid var(--line); padding:12px; vertical-align:top; line-height:1.65; }
.matrix-table th, .data-table th { background:#6f1714; color:#fff; text-align:left; }
.matrix-table tr:last-child td, .data-table tr:last-child td { border-bottom:none; }
.matrix-table td:last-child, .matrix-table th:last-child, .data-table td:last-child, .data-table th:last-child { border-right:none; }
.level-cell { font-weight:800; color:var(--accent); white-space:nowrap; }
.narrative-card { min-height: 330px; display:flex; flex-direction:column; }
.narrative-card .grow { flex:1; }
.two-col-list { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.mini-title { font-weight:850; color:var(--accent); margin-bottom:5px; }
.source-level { width:46px; height:46px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:var(--accent); color:#fff; font-weight:900; }
.actor-icon { width:44px; height:44px; border-radius:14px; background:var(--soft); color:var(--accent); display:flex; align-items:center; justify-content:center; font-weight:900; border:1px solid #dfc7bf; }
.actor-row { display:flex; gap:12px; align-items:flex-start; }
.modal-backdrop { position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(30,18,14,.56); z-index:50; padding:24px; }
.modal-backdrop.active { display:flex; }
.modal { width:min(980px, 96vw); max-height:88vh; overflow:auto; background:#fffdfa; border-radius:22px; border:1px solid var(--line); box-shadow:0 30px 90px rgba(0,0,0,.32); }
.modal-head { position:sticky; top:0; background:#fffdfa; border-bottom:1px solid var(--line); padding:18px 20px; display:flex; justify-content:space-between; gap:12px; align-items:flex-start; }
.modal-head h3 { margin:0; font-size:21px; }
.modal-content { padding:20px; }
.close { border:0; background:var(--soft); color:var(--accent); border-radius:999px; padding:8px 12px; font-weight:900; cursor:pointer; }
.empty { padding:28px; border:1px dashed var(--line2); border-radius:18px; background:#fff; color:var(--muted); text-align:center; }
.schema-code { white-space:pre-wrap; word-break:break-word; background:#231b18; color:#fff8ef; border-radius:16px; padding:16px; line-height:1.65; font-size:13px; max-height:460px; overflow:auto; }
.badge-line { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.notice { border:1px solid #e6c7aa; background:#fff7e9; color:#5a371d; border-radius:15px; padding:14px 16px; line-height:1.7; }
footer { max-width:1500px; margin:0 auto; padding:0 34px 40px; color:var(--muted); font-size:12px; line-height:1.8; }
@media (max-width: 980px) {
header.hero, .toolbar, main, footer { padding-left:18px; padding-right:18px; }
.grid.cols-2, .grid.cols-3, .grid.cols-4, .detail-grid, .two-col-list { grid-template-columns:1fr; }
.searchbar { min-width:100%; }
.event-head { flex-direction:column; }
}
</style>
</head>
<body>
<header class="hero">
<div class="hero-inner">
<div class="eyebrow">专题研究平台 · 精简栏目版 v0.3</div>
<h1>台湾涉两岸政治叙事监测专题</h1>
<div class="subtitle">公开资料监测、叙事结构拆解、平台传播观察、群体匿名反应、竞争叙事识别与主体节点观察</div>
<div class="boundary"><b>研究边界:</b>仅用于公开资料研究和舆情态势理解;不生成定向影响、群体操控、个人画像或宣传执行方案。</div>
<div class="top-meta" id="topMeta"></div>
</div>
</header>
<div class="toolbar-wrap">
<div class="toolbar">
<div class="tabs" id="tabs"></div>
<div class="searchbar"><input id="globalSearch" placeholder="全局搜索:事件、叙事、平台、主体、关键词" /></div>
</div>
</div>
<main>
<section class="tab-pane active" id="pane-overview"></section>
<section class="tab-pane" id="pane-events"></section>
<section class="tab-pane" id="pane-narratives"></section>
<section class="tab-pane" id="pane-structures"></section>
<section class="tab-pane" id="pane-platforms"></section>
<section class="tab-pane" id="pane-audiences"></section>
<section class="tab-pane" id="pane-competing"></section>
<section class="tab-pane" id="pane-actors"></section>
</main>
<div class="modal-backdrop" id="modalBackdrop">
<div class="modal">
<div class="modal-head"><div><h3 id="modalTitle"></h3><div id="modalSub" class="subtitle"></div></div><button class="close" onclick="closeModal()">关闭</button></div>
<div class="modal-content" id="modalContent"></div>
</div>
</div>
<footer>
本页面为单文件离线版,已移除来源审查、风险评估与 Schema 栏目。建议正式研究时继续补充每条事件的原始链接、样本截图、平台互动量和跨平台传播链。
</footer>
<script id="DATA" type="application/json">{"metadata":{"title":"台湾涉两岸政治叙事监测专题","version":"v0.3-content-only","generated_at":"2026-06-25 16:10 JST","time_range":"2024-01-13 至 2026-06-25","timezone":"Asia/Tokyo","orientation":"公开资料监测、叙事结构拆解、平台传播观察、群体匿名反应、竞争叙事识别与主体节点观察","safety_boundary":"仅用于公开资料研究和舆情态势理解;不生成定向影响、群体操控、个人画像或宣传执行方案。","recommended_columns":["专题总览","核心叙事库","叙事结构拆解","触发事件时间线","平台传播生态","受众反应观察","竞争叙事分析","反弹机制观察","关键主体与传播节点"],"update_cycle":"建议日报记录事件与样本,周报比较平台与叙事变化,月报归纳趋势与栏目数据缺口。","display_principles":["字段中文化","信息卡片化","长字段弹窗化","研究边界显性化","数据缺口可追踪"]},"dashboard":{"summary_cards":[{"label":"事件样本","value":"16","note":"覆盖选举、演训、国会、国安、国防预算等触发点"},{"label":"核心叙事","value":"10","note":"含疑美、战争风险、绿色威权、维持现状等"},{"label":"观察平台","value":"9","note":"覆盖公开平台、私域转传样本与跨境平台"},{"label":"受众群体","value":"12","note":"以匿名群体层级呈现关切、共鸣与反弹逻辑"},{"label":"主体节点","value":"16","note":"覆盖官方机构、政党、媒体、研究与事实查核组织"}],"current_focus":["赖清德政府两岸与国安表述变化","台海军事演训与灰色地带活动对战争风险叙事的激活","国会改革、罢免、司法案件带来的绿色威权/程序正义叙事对撞","国防预算、民防韧性和兵役议题对青年与风险家庭的影响","事实查核、AI假信息和社交平台再框架化机制"]},"events":[{"id":"E001","date":"2024-01-13","title":"台湾总统与立委选举","type":"选举政治","risk_level":4,"confidence":"高","source_status":"需在正式版回填原始链接","triggered_narratives":["民主韧性","疑美论","战争风险","选举干预"],"platforms":["YouTube","Facebook","LINE","PTT","Dcard","Threads"],"actors":["总统府","民进党","国民党","民众党","事实查核机构","主流媒体"],"summary":"赖清德胜选,民进党保住总统职位,但立法院形成朝小野大格局。选举期间战争风险、外部因素、假信息与选举干预等叙事被持续激活。","observation_points":["总统胜选与国会失去多数同时发生,形成后续国会叙事冲突的制度背景","选举信息战议题强化事实查核与平台治理的重要性","战争风险与疑美论在选前选后均有延续性"],"risk_dimensions":{"content":4,"spread":4,"audience":4,"political":5},"risk_label":"高风险","research_value":["可作为叙事触发点观察不同平台的框架转换","可用于比对官方定性、媒体再框架和社群反应之间的差异","可继续补充原始链接、热度曲线、事实查核与跨平台传播链"],"data_to_add":["一手公开来源链接","主流媒体报道链接","平台样本截图或标题","热度指标","事实查核记录","后续政策或舆论影响"],"risk_dimension_text":["content:4/5","spread:4/5","audience:4/5","political:5/5"]},{"id":"E002","date":"2024-01","title":"选举期间 AI 与假信息争议","type":"信息战/假信息","risk_level":4,"confidence":"中","source_status":"需回填查核报告、研究报告与平台样本链接","triggered_narratives":["AI假信息","认知战","选举干预","境外信息操纵"],"platforms":["LINE","YouTube","TikTok/Reels/Shorts","Facebook","PTT","X"],"actors":["事实查核机构","研究机构","社交平台","媒体"],"summary":"围绕2024年台湾选举,AI生成内容、深伪、断章取义视频、图卡和私域转传内容成为重要监测对象。","observation_points":["应区分真实政治表达、本地党派攻防和外部信息操纵","LINE私域样本需要依赖查核机构和用户提交样本","短影音平台更容易出现低上下文、强情绪内容"],"risk_dimensions":{"content":5,"spread":4,"audience":4,"operation":5},"risk_label":"高风险","research_value":["可作为叙事触发点观察不同平台的框架转换","可用于比对官方定性、媒体再框架和社群反应之间的差异","可继续补充原始链接、热度曲线、事实查核与跨平台传播链"],"data_to_add":["一手公开来源链接","主流媒体报道链接","平台样本截图或标题","热度指标","事实查核记录","后续政策或舆论影响"],"risk_dimension_text":["content:5/5","spread:4/5","audience:4/5","operation:5/5"]},{"id":"E003","date":"2024-05-20","title":"赖清德就职演说","type":"两岸政治","risk_level":5,"confidence":"高","source_status":"需回填总统府原文、大陆回应与媒体报道链接","triggLine truncated
<script>
const db = JSON.parse(document.getElementById('DATA').textContent);
const panes = [
['overview','总览仪表盘'], ['events','事件时间线'], ['narratives','核心叙事库'], ['structures','叙事结构拆解'],
['platforms','平台传播生态'], ['audiences','受众反应观察'], ['competing','竞争叙事'], ['actors','主体与传播节点']
];
let state = { search:'', eventRisk:'all', eventType:'all' };
const $ = id => document.getElementById(id);
const esc = s => String(s ?? '').replace(/[&<>'"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]));
const list = arr => Array.isArray(arr) && arr.length ? '<ul class="clean">'+arr.map(x=>'<li>'+esc(x)+'</li>').join('')+'</ul>' : '<span class="muted">暂无</span>';
const tags = (arr, cls='') => Array.isArray(arr) && arr.length ? '<div class="tagrow">'+arr.map(x=>'<span class="tag '+cls+'">'+esc(x)+'</span>').join('')+'</div>' : '';
const riskTag = lv => '<span class="tag risk'+esc(lv)+'">风险 '+esc(lv)+' / 5</span>';
function contains(obj, q) { return !q || JSON.stringify(obj).toLowerCase().includes(q.toLowerCase()); }
function sectionTitle(title, desc) { return `<div class="section-title"><div><h2>${esc(title)}</h2><p>${esc(desc)}</p></div></div>`; }
function card(title, body, extra='') { return `<div class="card"><h3>${esc(title)}</h3>${body}${extra}</div>`; }
function initTabs() {
$('tabs').innerHTML = panes.map(([id,name]) => `<button class="tab-btn ${id==='overview'?'active':''}" data-tab="${id}">${name}</button>`).join('');
document.querySelectorAll('.tab-btn').forEach(b => b.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(x=>x.classList.remove('active'));
b.classList.add('active');
document.querySelectorAll('.tab-pane').forEach(x=>x.classList.remove('active'));
$('pane-'+b.dataset.tab).classList.add('active');
}));
}
function initMeta() {
$('topMeta').innerHTML = [
['时间范围', db.metadata.time_range], ['时区', db.metadata.timezone || 'Asia/Tokyo'], ['版本', db.metadata.version], ['生成时间', db.metadata.generated_at]
].map(([k,v])=>`<span class="chip"><b>${esc(k)}:</b>${esc(v)}</span>`).join('');
}
function renderOverview() {
const counts = db.module_counts || [];
const kpis = counts.map(m => `<div class="card kpi"><div class="label">${esc(m.module)}</div><div class="num">${esc(m.count)}</div><div class="label">${esc(m.status)}</div></div>`).join('');
const focus = (db.dashboard?.current_focus || []).map(f => `<div class="card plain"><h4>${esc(f.title || f.name || '重点')}</h4><p>${esc(f.desc || f.description || f.summary || f)}</p></div>`).join('');
const map = (db.research_map || []).map(m => `<div class="card plain"><h3>${esc(m.name)}</h3><p>${esc(m.purpose)}</p>${tags(m.core_fields)}</div>`).join('');
$('pane-overview').innerHTML = sectionTitle('总览仪表盘','集中展示专题边界、数据模块、栏目结构和当前可继续补充的数据方向。') +
`<div class="grid cols-4">${kpis}</div>`+
`<div style="height:18px"></div><div class="notice"><b>更新机制:</b>${esc(db.metadata.update_cycle)}<br><b>页面设计:</b>${esc((db.metadata.display_principles||[]).join('、'))}</div>`+
`<div style="height:18px"></div>`+
sectionTitle('专题栏目结构','每个栏目都对应一个可维护的数据模块,避免页面只展示标题而缺少内容。')+
`<div class="grid cols-4">${map}</div>`+
(focus ? `<div style="height:18px"></div>${sectionTitle('当前重点','从专题数据中抽取的近期观察方向。')}<div class="grid cols-3">${focus}</div>` : '');
}
function renderEvents() {
const types = [...new Set(db.events.map(e=>e.type))];
let events = db.events.filter(e => contains(e, state.search));
if (state.eventRisk !== 'all') events = events.filter(e => String(e.risk_level) === state.eventRisk);
if (state.eventType !== 'all') events = events.filter(e => e.type === state.eventType);
events.sort((a,b)=>String(b.date).localeCompare(String(a.date)));
const controls = `<div class="filters"><select id="eventRisk"><option value="all">全部风险等级</option>${[5,4,3,2,1].map(x=>`<option value="${x}" ${state.eventRisk==String(x)?'selected':''}>风险 ${x} / 5</option>`).join('')}</select><select id="eventType"><option value="all">全部事件类型</option>${types.map(t=>`<option value="${esc(t)}" ${state.eventType===t?'selected':''}>${esc(t)}</option>`).join('')}</select></div>`;
const rows = events.length ? `<div class="timeline">${events.map(e=>`<div class="event-card"><div class="event-dot">${esc(e.risk_level)}</div><div class="event-body"><div class="event-head"><div><div class="event-date">${esc(e.date)} · ${esc(e.type)}</div><h3>${esc(e.title)}</h3></div><div>${riskTag(e.risk_level)}</div></div><p>${esc(e.summary)}</p>${tags(e.triggered_narratives)}<div class="detail-grid"><div class="detail-box"><b>观察要点</b>${list(e.observation_points)}</div><div class="detail-box"><b>风险维度</b>${tags(e.risk_dimension_text)}</div></div><div class="tagrow"><button class="btn ghost" onclick="openEvent('${esc(e.id)}')">查看完整事件卡</button></div></div></div>`).join('')}</div>` : '<div class="empty">没有匹配事件。</div>';
$('pane-events').innerHTML = sectionTitle('事件时间线','按时间顺序展示叙事触发事件,每张事件卡都保留摘要、观察要点、触发叙事、平台和风险维度。') + controls + rows;
$('eventRisk').onchange = e => { state.eventRisk=e.target.value; renderEvents(); };
$('eventType').onchange = e => { state.eventType=e.target.value; renderEvents(); };
}
function renderNarratives() {
const items = db.narratives.filter(n=>contains(n,state.search));
const html = items.length ? `<div class="grid cols-3">${items.map(n=>`<div class="card narrative-card"><div class="badge-line"><h3 style="margin-right:auto">${esc(n.name)}</h3>${riskTag(n.risk_level)}</div><p><b>核心命题:</b>${esc(n.core_claim)}</p>${tags(n.keywords)}<div class="grow"><div class="detail-grid"><div class="detail-box"><b>常见表达</b>${list(n.common_forms)}</div><div class="detail-box"><b>触发事件</b>${list(n.trigger_events)}</div></div></div><div class="tagrow"><button class="btn ghost" onclick="openNarrative('${esc(n.id)}')">查看叙事详情</button></div></div>`).join('')}</div>` : '<div class="empty">没有匹配叙事。</div>';
$('pane-narratives').innerHTML = sectionTitle('核心叙事库','展示每类叙事的核心命题、关键词、常见表达、触发事件、反叙事、反弹风险和数据缺口。') + html;
}
function renderStructures() {
const items = db.narratives.filter(n=>contains(n,state.search));
$('pane-structures').innerHTML = sectionTitle('叙事结构拆解','将叙事拆成问题定义、责任归因、情绪机制、道德框架、反叙事与数据需求,便于研究而非只看口号。')+
(items.length ? `<div class="grid cols-2">${items.map(n=>`<div class="card"><div class="badge-line"><h3 style="margin-right:auto">${esc(n.name)}</h3>${riskTag(n.risk_level)}</div><p><b>核心命题:</b>${esc(n.core_claim)}</p><div class="detail-grid">${(n.structure_list||[]).map(s=>`<div class="detail-box"><b>${esc(s.label)}</b><p>${esc(s.value)}</p></div>`).join('')}</div><div class="detail-grid"><div class="detail-box"><b>反叙事</b>${list(n.counter_narratives)}</div><div class="detail-box"><b>反弹风险</b>${list(n.backlash_risks)}</div><div class="detail-box"><b>数据缺口</b>${list(n.data_needs)}</div><div class="detail-box"><b>样本采集字段</b>${list(n.sample_collection_fields)}</div></div></div>`).join('')}</div>` : '<div class="empty">没有匹配结构。</div>');
}
function renderPlatforms() {
const items = db.platforms.filter(p=>contains(p,state.search));
$('pane-platforms').innerHTML = sectionTitle('平台传播生态','将平台差异转化为可采集字段:内容形态、叙事类型、监测字段、风险点和分析注意事项。')+
(items.length ? `<div class="grid cols-3">${items.map(p=>`<div class="card"><h3>${esc(p.name)}</h3><div class="detail-box"><b>内容形态</b>${list(p.content_forms)}</div><div class="detail-box"><b>高频叙事</b>${list(p.high_frequency_narratives)}</div><div class="detail-box"><b>监测字段</b>${list(p.monitor_fields)}</div><div class="detail-box"><b>风险点</b>${list(p.risk_points)}</div><div class="detail-box"><b>样本字段</b>${list(p.sample_fields)}</div><div class="detail-box"><b>分析注意</b>${list(p.analysis_notes)}</div></div>`).join('')}</div>` : '<div class="empty">没有匹配平台。</div>');
}
function renderAudiences() {
const items = db.audiences.filter(a=>contains(a,state.search));
$('pane-audiences').innerHTML = sectionTitle('受众反应观察','只做匿名群体层级观察:主要关切、共鸣模块、反感模块、平台分布与伦理边界。')+
(items.length ? `<div class="grid cols-3">${items.map(a=>`<div class="card"><h3>${esc(a.group)}</h3><p><b>反应类型:</b>${esc(a.reaction_type)}</p><div class="detail-box"><b>主要关切</b>${list(a.concerns)}</div><div class="detail-grid"><div class="detail-box"><b>容易共鸣</b>${list(a.accepted_modules)}</div><div class="detail-box"><b>容易反感</b>${list(a.rejected_modules)}</div></div><div class="detail-box"><b>观察平台</b>${tags(a.platforms)}</div><div class="detail-box"><b>伦理边界</b><p>${esc(a.ethics || a.observation_rule)}</p></div></div>`).join('')}</div>` : '<div class="empty">没有匹配群体。</div>');
}
function renderCompeting() {
const items = db.competing_narratives.filter(c=>contains(c,state.search));
$('pane-competing').innerHTML = sectionTitle('竞争叙事分析','呈现不同叙事之间如何互相对抗、校正或引发反弹。')+
(items.length ? `<div class="grid cols-2">${items.map(c=>`<div class="card"><h3>${esc(c.name)}</h3><p><b>核心主张:</b>${esc(c.core_claim)}</p><div class="detail-grid"><div class="detail-box"><b>主要对抗对象</b>${list(c.against)}</div><div class="detail-box"><b>典型证据来源</b>${list(c.evidence_sources)}</div><div class="detail-box"><b>反弹风险</b><p>${esc(c.risk)}</p></div><div class="detail-box"><b>待补数据</b>${list(c.data_needs)}</div></div></div>`).join('')}</div>` : '<div class="empty">没有匹配竞争叙事。</div>');
}
function renderActors() {
const items = db.actors.filter(a=>contains(a,state.search));
$('pane-actors').innerHTML = sectionTitle('主体与传播节点','把公开机构、政党、媒体、事实查核机构、研究机构与国际媒体作为可观察节点管理。')+
(items.length ? `<div class="grid cols-3">${items.map(a=>`<div class="card"><div class="actor-row"><div class="actor-icon">${esc((a.name||'?').slice(0,1))}</div><div><h3>${esc(a.name)}</h3><div class="tagrow"><span class="tag">${esc(a.type)}</span></div></div></div><p><b>主要角色:</b>${esc(a.role)}</p><div class="detail-box"><b>议题范围</b>${list(a.issues)}</div><div class="detail-box"><b>采集字段</b>${list(a.collect_fields)}</div><div class="detail-box"><b>监测维度</b>${list(a.monitor_dimensions)}</div><div class="detail-box"><b>边界提示</b><p>${esc(a.boundary_note)}</p></div></div>`).join('')}</div>` : '<div class="empty">没有匹配主体。</div>');
}
function openModal(title, sub, content) {
$('modalTitle').innerHTML = esc(title); $('modalSub').innerHTML = esc(sub || ''); $('modalContent').innerHTML = content; $('modalBackdrop').classList.add('active');
}
function closeModal() { $('modalBackdrop').classList.remove('active'); }
$('modalBackdrop').addEventListener('click', e => { if(e.target.id === 'modalBackdrop') closeModal(); });
function openEvent(id) {
const e = db.events.find(x=>x.id===id); if(!e) return;
openModal(e.title, `${e.date} · ${e.type} · 风险 ${e.risk_level}/5 · 信心:${e.confidence}`,
`<p>${esc(e.summary)}</p><div class="detail-grid"><div class="detail-box"><b>触发叙事</b>${list(e.triggered_narratives)}</div><div class="detail-box"><b>传播平台</b>${list(e.platforms)}</div><div class="detail-box"><b>相关主体</b>${list(e.actors)}</div><div class="detail-box"><b>观察要点</b>${list(e.observation_points)}</div><div class="detail-box"><b>风险维度</b>${list(e.risk_dimension_text)}</div><div class="detail-box"><b>研究价值</b>${list(e.research_value)}</div><div class="detail-box"><b>后续需补数据</b>${list(e.data_to_add)}</div><div class="detail-box"><b>来源状态</b><p>${esc(e.source_status)}</p></div></div>`);
}
function openNarrative(id) {
const n = db.narratives.find(x=>x.id===id); if(!n) return;
openModal(n.name, `风险 ${n.risk_level}/5 · ${n.id}`,
`<p><b>核心命题:</b>${esc(n.core_claim)}</p>${tags(n.keywords)}<div class="detail-grid"><div class="detail-box"><b>常见表达</b>${list(n.common_forms)}</div><div class="detail-box"><b>触发事件</b>${list(n.trigger_events)}</div>${(n.structure_list||[]).map(s=>`<div class="detail-box"><b>${esc(s.label)}</b><p>${esc(s.value)}</p></div>`).join('')}<div class="detail-box"><b>竞争/反叙事</b>${list(n.counter_narratives)}</div><div class="detail-box"><b>反弹风险</b>${list(n.backlash_risks)}</div><div class="detail-box"><b>数据缺口</b>${list(n.data_needs)}</div><div class="detail-box"><b>字段说明</b>${Object.entries(n.field_notes||{}).map(([k,v])=>`<p><b>${esc(k)}:</b>${esc(v)}</p>`).join('')}</div></div>`);
}
function renderAll() { renderOverview(); renderEvents(); renderNarratives(); renderStructures(); renderPlatforms(); renderAudiences(); renderCompeting(); renderActors(); }
$('globalSearch').addEventListener('input', e => { state.search = e.target.value.trim(); renderAll(); });
initTabs(); initMeta(); renderAll();
</script>
</body>
</html>
@@ -0,0 +1,255 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>台湾经济安全与公共叙事研究专题数据库</title>
<style>
:root {
--bg:#7f171d;
--bg2:#b7423d;
--paper:#fffaf2;
--card:#fffdf8;
--line:#ead9c8;
--text:#2a1b1b;
--muted:#7c6860;
--red:#9b1c24;
--red2:#c64d45;
--gold:#d8a24a;
--shadow:0 18px 45px rgba(50,16,18,.18);
--radius:22px;
}
* { box-sizing:border-box; }
body { margin:0; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',Arial,sans-serif; color:var(--text); background:linear-gradient(135deg,var(--bg),var(--bg2)); min-height:100vh; }
a { color:var(--red); }
.app { max-width:1480px; margin:0 auto; padding:28px 26px 48px; }
.hero { background:rgba(255,250,242,.96); border:1px solid rgba(255,255,255,.45); border-radius:30px; padding:26px; box-shadow:var(--shadow); position:relative; overflow:hidden; }
.hero:before { content:''; position:absolute; right:-120px; top:-160px; width:420px; height:420px; background:radial-gradient(circle,rgba(216,162,74,.42),rgba(216,162,74,0) 65%); }
.kicker { display:inline-flex; gap:8px; align-items:center; padding:6px 12px; border:1px solid #e4c3b0; background:#fff3e6; border-radius:999px; color:#8d211f; font-size:13px; font-weight:700; }
h1 { margin:14px 0 8px; font-size:34px; line-height:1.2; letter-spacing:.2px; }
.subtitle { max-width:980px; line-height:1.75; color:#614c45; font-size:15px; }
.toolbar { margin-top:20px; display:flex; gap:12px; flex-wrap:wrap; align-items:center; }
.search { flex:1; min-width:260px; position:relative; }
.search input { width:100%; padding:13px 16px 13px 42px; border:1px solid #e3cbbb; background:#fffdfa; border-radius:15px; outline:none; font-size:14px; }
.search span { position:absolute; left:15px; top:12px; opacity:.55; }
select, button { border:1px solid #e3cbbb; background:#fffdfa; border-radius:14px; padding:12px 14px; font-size:14px; color:var(--text); }
button { cursor:pointer; font-weight:700; }
button.primary { background:linear-gradient(135deg,var(--red),var(--red2)); color:white; border:none; }
.stats { display:grid; grid-template-columns:repeat(6,1fr); gap:14px; margin:18px 0; }
.stat { background:rgba(255,250,242,.94); border:1px solid rgba(255,255,255,.5); border-radius:18px; padding:15px; box-shadow:0 8px 24px rgba(50,16,18,.1); }
.stat b { display:block; font-size:28px; color:#8c191e; }
.stat span { color:#725f59; font-size:13px; }
.tabs { display:flex; gap:10px; overflow-x:auto; padding:10px 0 16px; scrollbar-width:thin; }
.tab { white-space:nowrap; border:1px solid rgba(255,255,255,.52); background:rgba(255,250,242,.92); padding:12px 16px; border-radius:999px; font-weight:800; color:#6c2928; cursor:pointer; box-shadow:0 8px 18px rgba(0,0,0,.08); }
.tab.active { background:#2b1616; color:#fffaf2; border-color:#2b1616; }
.panel { display:none; background:var(--paper); border-radius:28px; padding:22px; box-shadow:var(--shadow); border:1px solid rgba(255,255,255,.55); }
.panel.active { display:block; }
.panel-title { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; margin-bottom:18px; border-bottom:1px solid var(--line); padding-bottom:16px; }
.panel-title h2 { margin:0; font-size:24px; }
.panel-title p { margin:7px 0 0; color:var(--muted); line-height:1.7; }
.grid { display:grid; gap:16px; }
.grid.cols-2 { grid-template-columns:repeat(2, minmax(0,1fr)); }
.grid.cols-3 { grid-template-columns:repeat(3, minmax(0,1fr)); }
.grid.cols-4 { grid-template-columns:repeat(4, minmax(0,1fr)); }
.card { background:var(--card); border:1px solid var(--line); border-radius:20px; padding:17px; box-shadow:0 10px 28px rgba(90,40,30,.07); }
.card h3 { margin:0 0 10px; font-size:18px; color:#451b1b; }
.card p { line-height:1.72; color:#604e48; font-size:14px; margin:8px 0; }
.meta { display:flex; flex-wrap:wrap; gap:7px; margin:10px 0; }
.badge { display:inline-flex; align-items:center; gap:4px; padding:5px 9px; border-radius:999px; background:#f7eadc; color:#7a2725; border:1px solid #ead2be; font-size:12px; font-weight:700; }
.badge.dark { background:#2d1919; color:white; border-color:#2d1919; }
.badge.gold { background:#fff1cf; color:#835513; border-color:#f1d99b; }
.table-wrap { overflow:auto; border:1px solid var(--line); border-radius:18px; background:white; }
table { border-collapse:collapse; width:100%; min-width:850px; }
th,td { padding:12px 13px; border-bottom:1px solid #f0e1d4; text-align:left; vertical-align:top; font-size:13px; line-height:1.55; }
th { position:sticky; top:0; background:#fff2e5; color:#5d201f; z-index:1; }
tr:hover td { background:#fffaf4; }
.event-list { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; }
.event-card { cursor:pointer; transition:.18s transform,.18s box-shadow; }
.event-card:hover { transform:translateY(-2px); box-shadow:0 15px 32px rgba(90,30,30,.12); }
.event-head { display:flex; justify-content:space-between; gap:10px; align-items:flex-start; }
.event-id { font-weight:900; color:#981b20; }
.time { color:#80645a; font-size:13px; font-weight:700; }
.timeline { position:relative; padding-left:20px; }
.timeline:before { content:''; position:absolute; left:6px; top:8px; bottom:8px; width:2px; background:#e4c6b1; }
.timeline-item { position:relative; padding:0 0 15px 18px; }
.timeline-item:before { content:''; position:absolute; left:-19px; top:5px; width:12px; height:12px; border-radius:50%; background:#9b1c24; border:3px solid #f8e7d5; }
.timeline-item b { color:#4a1d1d; }
.notice { background:#fff4dc; border:1px solid #efd49d; border-radius:18px; padding:14px 16px; color:#6b4b18; line-height:1.7; }
.modal-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.5); display:none; align-items:center; justify-content:center; padding:20px; z-index:20; }
.modal { max-width:840px; width:100%; max-height:86vh; overflow:auto; background:var(--paper); border-radius:24px; padding:22px; box-shadow:0 35px 90px rgba(0,0,0,.35); }
.modal h2 { margin:0 0 8px; }
.modal-close { float:right; border:none; background:#2d1919; color:white; padding:8px 12px; border-radius:999px; }
.detail-grid { display:grid; grid-template-columns:130px 1fr; gap:10px; margin-top:15px; }
.detail-grid div { border-bottom:1px solid #ead9c8; padding:9px 0; line-height:1.65; }
.detail-grid .label { color:#7a5148; font-weight:900; }
.footer { color:#fff4e8; opacity:.92; text-align:center; margin-top:24px; font-size:13px; line-height:1.8; }
.empty { padding:32px; text-align:center; color:#795e55; }
@media(max-width:1100px) { .stats { grid-template-columns:repeat(3,1fr); } .grid.cols-3,.grid.cols-4 { grid-template-columns:repeat(2,1fr); } .event-list { grid-template-columns:1fr; } }
@media(max-width:720px) { .app { padding:16px; } h1 { font-size:26px; } .stats { grid-template-columns:repeat(2,1fr); } .grid.cols-2,.grid.cols-3,.grid.cols-4 { grid-template-columns:1fr; } .panel { padding:16px; } .detail-grid { grid-template-columns:1fr; } }
</style>
</head>
<body>
<div class="app">
<section class="hero">
<span class="kicker">专题数据库 · JSON + HTML 离线版</span>
<h1>台湾经济安全与公共叙事研究专题</h1>
<div class="subtitle">围绕台积电赴美、CHIPS Act、军费军购、台美贸易倡议、CPTPP/RCEP、ECFA、两岸经贸、台商供应链与平台舆情进行结构化整理。页面强调事实边界,避免把事件直接写成单向结论。</div>
<div class="toolbar">
<div class="search"><span>⌕</span><input id="globalSearch" placeholder="搜索事件、叙事、企业、人物、平台关键词……" /></div>
<select id="categoryFilter"><option value="">全部事件分类</option></select>
<button class="primary" onclick="downloadJSON()">导出当前 JSON</button>
<button onclick="resetFilters()">重置筛选</button>
</div>
</section>
<section class="stats" id="stats"></section>
<nav class="tabs" id="tabs"></nav>
<main id="panels"></main>
<div class="footer">生成日期:2026-06-25 · 使用边界:公开资料研究、事实核查、政策与产业分析,不用于欺骗性影响行动或定向操控。</div>
</div>
<div class="modal-backdrop" id="modalBackdrop" onclick="closeModal(event)">
<div class="modal" id="modal"></div>
</div>
<script>
const DATA = {"metadata": {"title": "台湾经济安全与公共叙事研究专题数据库", "version": "1.0", "generated_date": "2026-06-25", "language": "zh-CN", "scope": "公开资料研究、事实核查、产业与经贸安全、军费军购、两岸经贸、平台生态与竞争叙事分析", "safety_boundary": "研究型、分析型、非欺骗、非煽动、非操控;区分事实、观点、归因、推论与未确认信息。", "source_note": "本库由用户提供的 skill 框架与本轮补充数据整理而成;部分事件为待持续更新项,页面以“事实边界”提示不可过度推论。"}, "modules": [{"id": "M01", "name": "专题总览与议题地图", "desc": "说明台湾经济安全叙事的整体结构,呈现产业、军费、经贸、财政、台美关系和两岸关系等主线。"}, {"id": "M02", "name": "核心叙事结构库", "desc": "拆解台积电赴美、硅盾、军购延迟、军费挤压、台美贸易、RCEP/CPTPP、台商两难、前线化等叙事。"}, {"id": "M03", "name": "关键事件与证据库", "desc": "建立事件卡、时间线和事实边界,标注事件能支撑什么、不能证明什么。"}, {"id": "M04", "name": "半导体与产业安全", "desc": "研究台积电赴美、半导体供应链、人才外流、硅盾争议和台湾产业根基。"}, {"id": "M05", "name": "军购、军费与财政压力", "desc": "研究对美军购、国防预算、交付延迟、战力形成与财政排挤争议。"}, {"id": "M06", "name": "区域经贸与两岸经济", "desc": "研究台美贸易倡议、CPTPP、RCEP、ECFA、两岸贸易和供应链转移。"}, {"id": "M07", "name": "企业、台商与产业组织", "desc": "研究重点企业、台商、工商团体、产业协会的利益和结构性压力。"}, {"id": "M08", "name": "人物、政党、媒体与组织网络", "desc": "研究台湾政党人物、官员、媒体、智库、事实核查组织与美国相关人物。"}, {"id": "M09", "name": "受众群体与平台生态", "desc": "研究不同群体如何接收经济安全叙事,以及不同平台上的表达方式。"}, {"id": "M10", "name": "竞争叙事、事实核查与风险边界", "desc": "比较不同阵营对同一事件的解释,标注事实、推论、过度归因和稳健表达。"}], "issue_map": [{"id": "I01", "name": "台积电赴美与硅盾变化", "desc": "研究台积电海外先进制程布局是否影响台湾产业安全与硅盾叙事。", "actors": ["台积电", "美国商务部", "台湾经济部门", "半导体供应链"], "indicators": ["海外厂制程", "台湾本土先进制程", "研发留台程度", "补贴条件"]}, {"id": "I02", "name": "半导体人才与技术外流", "desc": "研究工程师外派、美国本地招聘和制造经验转移是否削弱台湾本土人才密度。", "actors": ["台积电", "工程师群体", "美国亚利桑那地方政府"], "indicators": ["外派人数", "招聘岗位", "训练计划", "薪资结构"]}, {"id": "I03", "name": "CHIPS Act 与美国产业政策", "desc": "研究美国补贴条件、供应链回流政策对台湾企业的约束与机会。", "actors": ["美国商务部", "TSMC Arizona", "美国客户"], "indicators": ["补贴金额", "拨付条件", "限制条款", "项目里程碑"]}, {"id": "I04", "name": "对美军购与交付延迟", "desc": "研究台湾采购美国武器是否存在价格、交付和战力形成争议。", "actors": ["台湾防务部门", "美国军工企业", "DSCA", "AIT"], "indicators": ["军售金额", "交付时间", "延迟原因", "训练完成情况"]}, {"id": "I05", "name": "军费上升与民生财政竞争", "desc": "研究国防支出上升是否挤压社会福利、教育、医疗、产业升级预算。", "actors": ["台湾财政部门", "防务部门", "立法机构"], "indicators": ["GDP占比", "特别预算", "举债", "民生预算变化"]}, {"id": "I06", "name": "台美贸易倡议是否对等", "desc": "研究台美21世纪贸易倡议是否带来关税红利或只是制度对接。", "actors": ["USTR", "TECRO", "AIT", "台湾经贸部门"], "indicators": ["协议章节", "关税减免", "企业受益", "规则成本"]}, {"id": "I07", "name": "CPTPP/RCEP 与区域整合缺口", "desc": "研究台湾未加入RCEP、CPTPP迟迟未入会对传统产业的影响。", "actors": ["台湾经济部门", "传统产业", "CPTPP成员"], "indicators": ["关税差距", "订单转移", "传统产业出口表现"]}, {"id": "I08", "name": "两岸贸易依赖变化", "desc": "研究台湾对大陆、香港、美国、东盟出口结构变化。", "actors": ["出口商", "台商", "传统制造业", "高科技产业"], "indicators": ["出口占比", "贸易顺差", "产业类别"]}, {"id": "I09", "name": "台商供应链转移与选边压力", "desc": "研究台商在大陆、美国、东南亚之间的市场和政治压力。", "actors": ["台商协会", "电子代工", "传统产业"], "indicators": ["投资流向", "迁厂Line truncated
const TAB_CONFIG = [
['overview','总览'], ['issues','议题地图'], ['narratives','叙事结构'], ['events','关键事件'], ['semiconductor','半导体产业'], ['defense','军购预算'], ['trade','经贸区域'], ['orgs','企业组织'], ['actors','人物网络'], ['audience','受众平台'], ['facts','事实核查']
];
let activeTab = 'overview';
let searchTerm = '';
let categoryFilter = '';
function esc(s) { return String(s ?? '').replace(/[&<>'"]/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[ch])); }
function arr(a) { return Array.isArray(a) ? a : (a ? [a] : []); }
function badges(items, cls='') { return arr(items).map(x => `<span class="badge ${cls}">${esc(x)}</span>`).join(''); }
function contains(obj, q) { if(!q) return true; return JSON.stringify(obj).toLowerCase().includes(q.toLowerCase()); }
function filteredEvents() { return DATA.events.filter(e => (!categoryFilter || e.category === categoryFilter) && contains(e, searchTerm)); }
function filtered(list) { return list.filter(x => contains(x, searchTerm)); }
function init() {
renderStats(); renderTabs(); fillFilter(); renderPanels(); bind();
}
function bind() {
document.getElementById('globalSearch').addEventListener('input', e => { searchTerm = e.target.value.trim(); renderPanels(); });
document.getElementById('categoryFilter').addEventListener('change', e => { categoryFilter = e.target.value; renderPanels(); });
}
function renderStats() {
const s = [
['研究栏目', DATA.modules.length], ['核心叙事', DATA.narratives.length], ['关键事件', DATA.events.length], ['企业样本', DATA.companies.length], ['组织样本', DATA.organizations.length], ['人物样本', DATA.actors.length]
];
document.getElementById('stats').innerHTML = s.map(([k,v]) => `<div class="stat"><b>${v}</b><span>${k}</span></div>`).join('');
}
function renderTabs() {
document.getElementById('tabs').innerHTML = TAB_CONFIG.map(([id,name]) => `<button class="tab ${id===activeTab?'active':''}" onclick="setTab('${id}')">${name}</button>`).join('');
}
function setTab(id) { activeTab = id; renderTabs(); renderPanels(); window.scrollTo({top:190,behavior:'smooth'}); }
function fillFilter() {
const cats = [...new Set(DATA.events.map(e=>e.category))].sort();
document.getElementById('categoryFilter').innerHTML = '<option value="">全部事件分类</option>' + cats.map(c=>`<option value="${esc(c)}">${esc(c)}</option>`).join('');
}
function resetFilters() { searchTerm=''; categoryFilter=''; document.getElementById('globalSearch').value=''; document.getElementById('categoryFilter').value=''; renderPanels(); }
function panel(title, desc, body) { return `<section class="panel active"><div class="panel-title"><div><h2>${title}</h2><p>${desc}</p></div></div>${body}</section>`; }
function renderPanels() {
const root = document.getElementById('panels');
if(activeTab==='overview') root.innerHTML = renderOverview();
if(activeTab==='issues') root.innerHTML = renderIssues();
if(activeTab==='narratives') root.innerHTML = renderNarratives();
if(activeTab==='events') root.innerHTML = renderEvents();
if(activeTab==='semiconductor') root.innerHTML = renderSemiconductor();
if(activeTab==='defense') root.innerHTML = renderDefense();
if(activeTab==='trade') root.innerHTML = renderTrade();
if(activeTab==='orgs') root.innerHTML = renderOrgs();
if(activeTab==='actors') root.innerHTML = renderActors();
if(activeTab==='audience') root.innerHTML = renderAudience();
if(activeTab==='facts') root.innerHTML = renderFacts();
}
function renderOverview() {
const body = `<div class="notice"><b>资料边界:</b>${esc(DATA.metadata.safety_boundary)} 本页面保留来源链接和事实边界字段,适合作为专题研究初版数据库,不应替代逐条原始资料核验。</div>
<div class="grid cols-2" style="margin-top:16px">${DATA.modules.map(m=>`<div class="card"><div class="meta"><span class="badge dark">${m.id}</span></div><h3>${esc(m.name)}</h3><p>${esc(m.desc)}</p></div>`).join('')}</div>`;
return panel('专题总览', '10个一级栏目与数据库结构,可作为后续JSON扩展和HTML页面长期更新的基础。', body);
}
function renderIssues() {
const list = filtered(DATA.issue_map);
const body = `<div class="grid cols-2">${list.map(i=>`<div class="card"><div class="meta"><span class="badge dark">${i.id}</span>${badges(i.indicators,'gold')}</div><h3>${esc(i.name)}</h3><p>${esc(i.desc)}</p><p><b>关联对象:</b>${esc(i.actors.join('、'))}</p></div>`).join('') || '<div class="empty">没有匹配结果</div>'}</div>`;
return panel('议题地图', '把产业、经贸、军费、两岸关系和安全焦虑拆成可观察的研究议题。', body);
}
function renderNarratives() {
const list = filtered(DATA.narratives);
const body = `<div class="grid cols-2">${list.map(n=>`<div class="card"><div class="meta"><span class="badge dark">${n.id}</span>${badges(n.keywords)}</div><h3>${esc(n.name)}</h3><p><b>核心主张:</b>${esc(n.claim)}</p><p><b>情绪基础:</b>${esc(n.emotion.join('、'))}</p><p><b>反方解释:</b>${esc(n.counter)}</p><p><b>事实边界:</b>${esc(n.boundary)}</p></div>`).join('') || '<div class="empty">没有匹配结果</div>'}</div>`;
return panel('核心叙事结构库', '每条叙事均包含核心主张、关键词、情绪基础、支撑事实、反方解释和事实边界。', body);
}
function renderEvents() {
const list = filteredEvents();
const body = `<div class="notice">当前显示 <b>${list.length}</b> 条事件。点击任一事件卡可查看完整字段、关联叙事、来源链接与事实边界。</div><div class="event-list" style="margin-top:15px">${list.map(e=>eventCard(e)).join('') || '<div class="empty">没有匹配结果</div>'}</div>`;
return panel('关键事件与证据库', 'E01—E80事件库,覆盖半导体、台美经贸、区域协定、ECFA、军费军购与安全环境。', body);
}
function eventCard(e) {
return `<div class="card event-card" onclick="showEvent('${e.id}')"><div class="event-head"><span class="event-id">${e.id}</span><span class="time">${esc(e.time)}</span></div><div class="meta"><span class="badge gold">${esc(e.category)}</span>${badges(e.supports_narratives,'dark')}</div><h3>${esc(e.name)}</h3><p>${esc(e.summary)}</p><p><b>事实边界:</b>${esc(e.fact_boundary)}</p></div>`;
}
function showEvent(id) {
const e = DATA.events.find(x=>x.id===id); if(!e) return;
const src = arr(e.source_urls).map(u=>`<a href="${esc(u)}" target="_blank">${esc(u)}</a>`).join('<br>') || '待补充';
const html = `<button class="modal-close" onclick="closeModal()">关闭</button><h2>${esc(e.id)} · ${esc(e.name)}</h2><div class="meta"><span class="badge gold">${esc(e.category)}</span>${badges(e.supports_narratives,'dark')}</div><div class="detail-grid"><div class="label">时间</div><div>${esc(e.time)}</div><div class="label">事件摘要</div><div>${esc(e.summary)}</div><div class="label">可支撑叙事</div><div>${esc(e.supports_narratives.join('、'))}</div><div class="label">事实边界</div><div>${esc(e.fact_boundary)}</div><div class="label">来源链接</div><div>${src}</div><div class="label">可信度</div><div>${esc(e.credibility)}</div></div>`;
document.getElementById('modal').innerHTML = html;
document.getElementById('modalBackdrop').style.display='flex';
}
function closeModal(ev) { if(!ev || ev.target.id==='modalBackdrop' || ev.target.className==='modal-close') document.getElementById('modalBackdrop').style.display='none'; }
function renderSemiconductor() {
const list = filtered(DATA.companies.filter(c=>/晶圆|IC|封测|硅|半导体|台积电|联发科|日月光|环球/.test(c.sector+c.name)));
const body = `<div class="grid cols-3">${list.map(c=>`<div class="card"><div class="meta"><span class="badge dark">${c.id}</span>${badges(c.narratives)}</div><h3>${esc(c.name)}</h3><p><b>产业位置:</b>${esc(c.sector)}</p><p>${esc(c.focus)}</p></div>`).join('')}</div>`;
return panel('半导体与产业安全', '聚焦台积电、先进制程、半导体供应链、人才外流和硅盾争议。', body);
}
function renderDefense() {
const list = filteredEvents().filter(e=>e.category==='军费军购与预算');
const body = `<div class="event-list">${list.map(e=>eventCard(e)).join('') || '<div class="empty">没有匹配结果</div>'}</div>`;
return panel('军购、军费与财政压力', '集中展示防务特别预算、对美军购、交付延迟、后勤依赖和战力形成边界。', body);
}
function renderTrade() {
const indicators = filtered(DATA.trade_indicators);
const events = filteredEvents().filter(e=>/经贸|区域|贸易|ECFA|协定/.test(e.category+e.name));
const table = `<div class="table-wrap"><table><thead><tr><th>编号</th><th>指标</th><th>年份</th><th>数值</th><th>研究意义</th></tr></thead><tbody>${indicators.map(t=>`<tr><td>${t.id}</td><td>${esc(t.name)}</td><td>${esc(t.year)}</td><td>${esc(t.value)}</td><td>${esc(t.meaning)}</td></tr>`).join('')}</tbody></table></div>`;
const cards = `<div class="event-list" style="margin-top:16px">${events.map(e=>eventCard(e)).join('')}</div>`;
return panel('区域经贸与两岸经济', '包含贸易指标、台美贸易倡议、RCEP/CPTPP、ECFA与两岸贸易结构变化。', table + cards);
}
function renderOrgs() {
const companies = filtered(DATA.companies);
const orgs = filtered(DATA.organizations);
const comp = `<h3>企业样本</h3><div class="grid cols-3">${companies.map(c=>`<div class="card"><div class="meta"><span class="badge dark">${c.id}</span>${badges(c.narratives)}</div><h3>${esc(c.name)}</h3><p><b>产业位置:</b>${esc(c.sector)}</p><p>${esc(c.focus)}</p></div>`).join('')}</div>`;
const orgHtml = `<h3 style="margin-top:24px">组织样本</h3><div class="grid cols-3">${orgs.map(o=>`<div class="card"><div class="meta"><span class="badge gold">${o.id}</span><span class="badge">${esc(o.type)}</span></div><h3>${esc(o.name)}</h3><p>${esc(o.focus)}</p></div>`).join('')}</div>`;
return panel('企业、台商与产业组织', '企业与产业组织是叙事落地的重要利益主体,需要继续补公开表态和政策诉求。', comp + orgHtml);
}
function renderActors() {
const list = filtered(DATA.actors);
const body = `<div class="grid cols-3">${list.map(p=>`<div class="card"><div class="meta"><span class="badge dark">${p.id}</span>${badges(p.issues,'gold')}</div><h3>${esc(p.name)}</h3><p><b>身份:</b>${esc(p.role)}</p><p>${esc(p.note)}</p></div>`).join('') || '<div class="empty">没有匹配结果</div>'}</div>`;
return panel('人物、政党、媒体与组织网络', '当前先放官方人物样本;后续应扩充政党、立法机构、政论人物、财经媒体、智库和事实查核组织。', body);
}
function renderAudience() {
const audiences = filtered(DATA.audiences);
const platforms = filtered(DATA.platforms);
const ahtml = `<h3>受众群体</h3><div class="grid cols-2">${audiences.map(g=>`<div class="card"><div class="meta"><span class="badge dark">${g.id}</span>${badges(g.concerns)}</div><h3>${esc(g.name)}</h3><p><b>容易共鸣:</b>${esc(g.resonant_issues.join('、'))}</p><p><b>反弹风险:</b>${esc(g.risk)}</p></div>`).join('')}</div>`;
const phtml = `<h3 style="margin-top:24px">平台采样计划</h3><div class="grid cols-4">${platforms.map(p=>`<div class="card"><div class="meta"><span class="badge gold">目标 ${p.sample_target} 条</span></div><h3>${esc(p.name)}</h3><p>${esc(p.focus)}</p><p><b>字段:</b>${esc(p.fields.join('、'))}</p></div>`).join('')}</div>`;
return panel('受众群体与平台生态', '受众画像用于理解议题接收方式;平台数据只应作为舆情样本,不能代表整体民意。', ahtml + phtml);
}
function renderFacts() {
const checks = filtered(DATA.fact_checks);
const gaps = DATA.data_gaps;
const table = `<div class="table-wrap"><table><thead><tr><th>争议说法</th><th>事实状态</th><th>建议稳健表达</th><th>高风险表达</th></tr></thead><tbody>${checks.map(f=>`<tr><td>${esc(f.claim)}</td><td>${esc(f.status)}</td><td>${esc(f.safe_expression)}</td><td>${esc(f.risky_expression)}</td></tr>`).join('')}</tbody></table></div>`;
const gapHtml = `<h3 style="margin-top:24px">后续数据缺口</h3><div class="grid cols-3">${gaps.map(g=>`<div class="card"><div class="meta"><span class="badge ${g.priority==='高'?'dark':'gold'}">${esc(g.priority)}优先级</span></div><h3>${esc(g.name)}</h3><p>${esc(g.target)}</p></div>`).join('')}</div>`;
return panel('竞争叙事、事实核查与风险边界', '该栏目用于把绝对化、阴谋化或证据不足的表达改写成研究型表述。', table + gapHtml);
}
function downloadJSON() {
const blob = new Blob([JSON.stringify(DATA,null,2)], {type:'application/json;charset=utf-8'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href=url; a.download='taiwan_economic_security_narrative_database.json'; a.click(); URL.revokeObjectURL(url);
}
init();
</script>
</body>
</html>