@topmindspace/tms-skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/bin/tms-skills.js +160 -0
- package/package.json +48 -0
- package/top-ppt-html/README.md +254 -0
- package/top-ppt-html/SKILL.md +111 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +168 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +168 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +321 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +321 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +321 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +914 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +914 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +914 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +914 -0
- package/top-ppt-html/assets/pptx-export.js +1944 -0
- package/top-ppt-html/assets/style-gallery.html +589 -0
- package/top-ppt-html/assets/templates/architecture.html +3728 -0
- package/top-ppt-html/assets/templates/engine.css +840 -0
- package/top-ppt-html/assets/templates/presentation.html +3738 -0
- package/top-ppt-html/assets/templates/research.html +4017 -0
- package/top-ppt-html/assets/templates/ui.js +520 -0
- package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
- package/top-ppt-html/assets/theme-overview-presentation.png +0 -0
- package/top-ppt-html/assets/theme-overview-research.png +0 -0
- package/top-ppt-html/assets/theme-overview.png +0 -0
- package/top-ppt-html/evals/prompts.csv +15 -0
- package/top-ppt-html/evals/rubric.schema.json +25 -0
- package/top-ppt-html/evals/run_evals.py +220 -0
- package/top-ppt-html/evals/trace.example.json +16 -0
- package/top-ppt-html/package.json +35 -0
- package/top-ppt-html/references/charts-basic.md +624 -0
- package/top-ppt-html/references/charts-discipline.md +108 -0
- package/top-ppt-html/references/charts-extended.md +482 -0
- package/top-ppt-html/references/charts.md +28 -0
- package/top-ppt-html/references/components-atoms.md +624 -0
- package/top-ppt-html/references/components.md +30 -0
- package/top-ppt-html/references/content-rules.md +490 -0
- package/top-ppt-html/references/design-system-engine.md +235 -0
- package/top-ppt-html/references/design-system.md +478 -0
- package/top-ppt-html/references/failure-modes.md +214 -0
- package/top-ppt-html/references/high-fidelity.md +127 -0
- package/top-ppt-html/references/icons.md +397 -0
- package/top-ppt-html/references/industry-benchmark.md +105 -0
- package/top-ppt-html/references/infographics-stats.md +308 -0
- package/top-ppt-html/references/infographics-structure.md +226 -0
- package/top-ppt-html/references/infographics.md +43 -0
- package/top-ppt-html/references/layout-grammar.md +315 -0
- package/top-ppt-html/references/layouts-architecture.md +108 -0
- package/top-ppt-html/references/layouts-combo.md +600 -0
- package/top-ppt-html/references/layouts-research.md +160 -0
- package/top-ppt-html/references/modes.md +254 -0
- package/top-ppt-html/references/outline-design.md +275 -0
- package/top-ppt-html/references/playbook.md +266 -0
- package/top-ppt-html/references/pptx-export.md +209 -0
- package/top-ppt-html/references/reform-plan.md +252 -0
- package/top-ppt-html/references/styles.md +370 -0
- package/top-ppt-html/references/tech-design.md +138 -0
- package/top-ppt-html/scripts/audit_css.py +109 -0
- package/top-ppt-html/scripts/audit_docs.py +176 -0
- package/top-ppt-html/scripts/audit_skill.py +220 -0
- package/top-ppt-html/scripts/audit_styles.py +351 -0
- package/top-ppt-html/scripts/build_examples.py +2276 -0
- package/top-ppt-html/scripts/build_pptx.js +2380 -0
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -0
- package/top-ppt-html/scripts/checks_html.py +127 -0
- package/top-ppt-html/scripts/cross_verify.py +294 -0
- package/top-ppt-html/scripts/env_probe.py +158 -0
- package/top-ppt-html/scripts/extract_model.py +210 -0
- package/top-ppt-html/scripts/extract_snippet.py +374 -0
- package/top-ppt-html/scripts/gen_channel_a.js +214 -0
- package/top-ppt-html/scripts/layout-constants.json +3377 -0
- package/top-ppt-html/scripts/layout_slots.json +830 -0
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -0
- package/top-ppt-html/scripts/measure_height.py +178 -0
- package/top-ppt-html/scripts/model-schema.json +547 -0
- package/top-ppt-html/scripts/negative_tests.py +307 -0
- package/top-ppt-html/scripts/package_skill.py +291 -0
- package/top-ppt-html/scripts/prepare_images.py +341 -0
- package/top-ppt-html/scripts/probe_image_export.py +188 -0
- package/top-ppt-html/scripts/quality_gate.py +301 -0
- package/top-ppt-html/scripts/regression.py +308 -0
- package/top-ppt-html/scripts/render_compare.py +275 -0
- package/top-ppt-html/scripts/render_from_model.py +698 -0
- package/top-ppt-html/scripts/scaffold_report.py +1054 -0
- package/top-ppt-html/scripts/section-file-map.json +105 -0
- package/top-ppt-html/scripts/sync_runtime.py +662 -0
- package/top-ppt-html/scripts/validate_pptx.py +1510 -0
- package/top-ppt-html/scripts/validate_report.py +1456 -0
|
@@ -0,0 +1,3738 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!--
|
|
3
|
+
TopPPT HTML· 演示汇报模式模板(presentation)
|
|
4
|
+
═══════════════════════════════════════════════════════════════════════
|
|
5
|
+
单文件 / 零外链 / 亮暗双主题 / 宽屏 16:9 / 9 风格实时切换。
|
|
6
|
+
形态定位:台上讲给人听——大字号、每屏一主张、图表大而克制。本模板即演示形态本体,
|
|
7
|
+
与 research / architecture 是三种独立输出体系(生成时确定,页面无模式切换)。
|
|
8
|
+
|
|
9
|
+
用法:复制本文件 → 选风格(data-style + header 下拉可实时预览 9 套)→ 按本骨架填内容
|
|
10
|
+
→ 同步填写 window.REPORT_MODEL(mode 固定为 presentation)→ 跑 validate_report.py 校验
|
|
11
|
+
→ 交付到 <输出目录>/YYYY-MM-DD-主题.html(输出目录由用户指定,默认当前工作目录)
|
|
12
|
+
模式规则:references/modes.md §A 版式组件:references/components.md
|
|
13
|
+
导出:header「预览 PPTX」+「?」帮助(精导通道指引);正式交付走智能体精导通道。
|
|
14
|
+
公共引擎/UI/运行时由 scripts/sync_runtime.py 注入(标记块内禁止手改)。
|
|
15
|
+
-->
|
|
16
|
+
<html lang="zh-CN" data-mode="presentation" data-style="business-blue" data-theme="light">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="UTF-8">
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
20
|
+
<meta name="color-scheme" content="light dark">
|
|
21
|
+
<title>报告标题</title>
|
|
22
|
+
<style>
|
|
23
|
+
/* __TOPPPT_ENGINE_START__ */
|
|
24
|
+
/* ══ TopPPT HTML 公共样式引擎(assets/templates/engine.css 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
|
|
25
|
+
/* ══ TopPPT HTML 公共样式引擎(assets/templates/engine.css)══
|
|
26
|
+
由 scripts/sync_runtime.py 注入到 templates/*.html 的 __TOPPPT_ENGINE__ 标记块 · 禁止在各模板手改。
|
|
27
|
+
含:结构 token / 9 套风格 token / 排版类 / 栅格(含密度版式扩展 g-hero--rev/g-side--rev/g-31/g-13、
|
|
28
|
+
拼贴栅格 g-bento + span 工具类、交错版式 .stagger、键值 .kv)/
|
|
29
|
+
强调带(band--accent / band--accent--solid / band--deep)/
|
|
30
|
+
顶栏+工具组(含全屏按钮与折叠态)/ 组件 / SVG 语义类 /
|
|
31
|
+
锁定版式组件(steps/heatmap/bullet/pyramid)/ 待核实标注(.tbd/.tbd-legend/.flagbar)/
|
|
32
|
+
素材图片族(.media 比例/裁切/蒙版 · --ph 配图占位 · .media-grid 多图网格 ·
|
|
33
|
+
.media-compare 双图对比 · .media-wall Logo 墙)/ Agenda / 页码指示 / PPTX 预览与帮助模态。
|
|
34
|
+
不含:模式密度层与模式专属组件(各模板自带,置于引擎块之后覆盖)。 */
|
|
35
|
+
|
|
36
|
+
/* ── 结构 token(版式/间距/字号,风格无关;presentation 基准值,模式模板在其后覆盖密度) ── */
|
|
37
|
+
:root{color-scheme:light;
|
|
38
|
+
--font-body:"Google Sans","Roboto",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",
|
|
39
|
+
"Hiragino Sans GB","Microsoft YaHei","Noto Sans SC",sans-serif;
|
|
40
|
+
--font-display:var(--font-body);
|
|
41
|
+
--fw-display:700;--fw-title:600;
|
|
42
|
+
--fs-display:clamp(2.75rem,4.6vw,4.25rem);--fs-h1:clamp(2rem,3.2vw,3rem);
|
|
43
|
+
--fs-h2:clamp(1.5rem,2.1vw,2.125rem);--fs-h3:clamp(1.125rem,1.35vw,1.375rem);
|
|
44
|
+
--fs-lead:clamp(1.0625rem,1.25vw,1.3125rem);--fs-body:clamp(.9375rem,1.05vw,1.0625rem);
|
|
45
|
+
--fs-sm:clamp(.8125rem,.92vw,.9375rem);--fs-xs:clamp(.75rem,.82vw,.8125rem);
|
|
46
|
+
--fs-metric:clamp(2.25rem,3.4vw,3.25rem);
|
|
47
|
+
/* 风格语汇基线(每套风格可覆盖:字距 / 行高 / 链接色 / 字重 / 字阶) */
|
|
48
|
+
--letter-display:-.03em;--letter-eyebrow:.11em;
|
|
49
|
+
--lh-body:1.68;--lh-lead:1.62;
|
|
50
|
+
--link:var(--accent-text);
|
|
51
|
+
--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:24px;--sp-6:32px;--sp-7:48px;--sp-8:64px;
|
|
52
|
+
--radius:16px;--r-sm:8px;--r-md:12px;--r-lg:var(--radius);--r-xl:calc(var(--radius) + 8px);--r-full:999px;
|
|
53
|
+
/* 动效标度(MD3:short≤200 / medium 250–400 / long 450–600 / extra-long 700–1000)——
|
|
54
|
+
design-system.md §Motion 早有规定,此前散落成 8 个离散值,这里收敛成 4 档 + 2 条缓动 */
|
|
55
|
+
--dur-short:.18s;--dur-medium:.28s;--dur-long:.6s;--dur-xlong:.7s;
|
|
56
|
+
--ease-emphasized:cubic-bezier(.05,.7,.1,1);--ease-standard:cubic-bezier(.2,0,0,1);
|
|
57
|
+
--wrap:1400px;--gap:clamp(16px,1.6vw,28px);--bar-h:68px;
|
|
58
|
+
/* 页面高度模型(每页高度稳定):--band-pad 页内上下留白;--band-min 单页最小高度(一屏)。
|
|
59
|
+
模式模板只覆盖这两个 token,不重写 .band 规则。
|
|
60
|
+
--band-min 是唯一生效的页高开关:默认 100vh,支持 svh 的浏览器升级为 100svh(移动端工具栏友好)。 */
|
|
61
|
+
--band-pad:clamp(40px,6vh,88px);--band-min:calc(100vh - var(--bar-h));
|
|
62
|
+
--deep-bg:#1a1d21;--deep-fg:#e8eaed;--deep-fg-2:#b9c2cd;--deep-line:#2b3540;
|
|
63
|
+
--shadow-1:0 1px 2px rgba(60,64,67,.08),0 1px 3px 1px rgba(60,64,67,.06);
|
|
64
|
+
--shadow-2:0 1px 3px rgba(60,64,67,.10),0 4px 8px 3px rgba(60,64,67,.06)}
|
|
65
|
+
@supports (height:100svh){:root{--band-min:calc(100svh - var(--bar-h))}}
|
|
66
|
+
|
|
67
|
+
/* ── 风格 token · 9 套全量内嵌(header 风格下拉实时切换;生成时选定 data-style 默认值) ── */
|
|
68
|
+
:root{
|
|
69
|
+
--bg:#fff;--surface:#fff;--surface-1:#f8f9fa;--surface-2:#f1f3f4;--surface-3:#e8eaed;
|
|
70
|
+
--surface-inv:#202124;--text:#202124;--text-2:#5f6368;--text-3:#80868b;--text-inv:#fff;
|
|
71
|
+
--border:#dadce0;--border-soft:#e8eaed;
|
|
72
|
+
--accent:#1a73e8;--accent-soft:#e8f0fe;--accent-soft-2:#d2e3fc;--accent-text:#1b66c9;--accent-on:#fff}
|
|
73
|
+
/* business-blue 显式块(Google Material 语汇:中性灰阶 + 单一蓝 · 圆角 16 · 同字族层级靠字重字号) */
|
|
74
|
+
html[data-style="business-blue"]{
|
|
75
|
+
--font-body:"Google Sans","Roboto",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans SC",sans-serif;
|
|
76
|
+
--font-display:var(--font-body);
|
|
77
|
+
--radius:16px;--fw-display:700;--fw-title:600;
|
|
78
|
+
--letter-display:-.03em;--letter-eyebrow:.11em;--lh-body:1.68;--lh-lead:1.62;--link:#1b66c9;
|
|
79
|
+
--bg:#fff;--surface:#fff;--surface-1:#f8f9fa;--surface-2:#f1f3f4;--surface-3:#e8eaed;
|
|
80
|
+
--surface-inv:#202124;--text:#202124;--text-2:#5f6368;--text-3:#80868b;--text-inv:#fff;
|
|
81
|
+
--border:#dadce0;--border-soft:#e8eaed;
|
|
82
|
+
--accent:#1a73e8;--accent-soft:#e8f0fe;--accent-soft-2:#d2e3fc;--accent-text:#1b66c9;--accent-on:#fff}
|
|
83
|
+
html[data-style="business-blue"][data-theme="dark"]{
|
|
84
|
+
--bg:#0d0f13;--surface:#14171c;--surface-1:#191d23;--surface-2:#20252c;--surface-3:#2a3038;
|
|
85
|
+
--surface-inv:#e8eaed;--text:#e8eaed;--text-2:#adb4bd;--text-3:#868d96;--text-inv:#14171c;
|
|
86
|
+
--border:#2f353d;--border-soft:#23282f;
|
|
87
|
+
--accent:#8ab4f8;--accent-soft:#16243a;--accent-soft-2:#1d3557;--accent-text:#8ab4f8;--accent-on:#0d1b2e;--link:#8ab4f8}
|
|
88
|
+
html[data-theme="dark"]{color-scheme:dark;
|
|
89
|
+
--bg:#0d0f13;--surface:#14171c;--surface-1:#191d23;--surface-2:#20252c;--surface-3:#2a3038;
|
|
90
|
+
--surface-inv:#e8eaed;--text:#e8eaed;--text-2:#adb4bd;--text-3:#868d96;--text-inv:#14171c;
|
|
91
|
+
--border:#2f353d;--border-soft:#23282f;
|
|
92
|
+
--accent:#8ab4f8;--accent-soft:#16243a;--accent-soft-2:#1d3557;--accent-text:#8ab4f8;--accent-on:#0d1b2e;
|
|
93
|
+
--shadow-1:0 1px 2px rgba(0,0,0,.4),0 1px 3px 1px rgba(0,0,0,.24);
|
|
94
|
+
--shadow-2:0 1px 3px rgba(0,0,0,.5),0 4px 8px 3px rgba(0,0,0,.28)}
|
|
95
|
+
/* apple-mono 极简语汇:大字 · 大留白 · 近无阴影 · 圆角 20 · 系统蓝仅链接/CTA */
|
|
96
|
+
html[data-style="apple-mono"]{
|
|
97
|
+
--font-body:-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
|
|
98
|
+
--font-display:-apple-system,BlinkMacSystemFont,"SF Pro Display","Helvetica Neue","PingFang SC",sans-serif;
|
|
99
|
+
--radius:20px;--fw-display:600;--fw-title:500;
|
|
100
|
+
--fs-display:clamp(3rem,5.4vw,4.75rem);--fs-h1:clamp(2.125rem,3.6vw,3.25rem);
|
|
101
|
+
--letter-display:-.04em;--letter-eyebrow:.12em;--lh-body:1.75;--lh-lead:1.7;
|
|
102
|
+
--gap:clamp(22px,2.2vw,36px);--link:#0071e3;
|
|
103
|
+
--bg:#fff;--surface:#fff;--surface-1:#f5f5f7;--surface-2:#f5f5f7;--surface-3:#e8e8ed;
|
|
104
|
+
--surface-inv:#1d1d1f;--text:#1d1d1f;--text-2:#6e6e73;--text-3:#86868b;--text-inv:#f5f5f7;
|
|
105
|
+
--border:#d2d2d7;--border-soft:#e8e8ed;
|
|
106
|
+
--accent:#1d1d1f;--accent-soft:#f5f5f7;--accent-soft-2:#e8e8ed;--accent-text:#1d1d1f;--accent-on:#fff;
|
|
107
|
+
--shadow-1:none;--shadow-2:0 4px 24px rgba(0,0,0,.06)}
|
|
108
|
+
html[data-style="apple-mono"][data-theme="dark"]{
|
|
109
|
+
--bg:#000;--surface:#000;--surface-1:#161617;--surface-2:#1d1d1f;--surface-3:#2a2a2d;
|
|
110
|
+
--surface-inv:#f5f5f7;--text:#f5f5f7;--text-2:#a1a1a6;--text-3:#86868b;--text-inv:#1d1d1f;
|
|
111
|
+
--border:#38383d;--border-soft:#2a2a2d;
|
|
112
|
+
--accent:#f5f5f7;--accent-soft:#1d1d1f;--accent-soft-2:#2a2a2d;--accent-text:#f5f5f7;--accent-on:#000;
|
|
113
|
+
--link:#2997ff}
|
|
114
|
+
/* mckinsey 咨询语汇:衬线标题 · 小圆角 6 · hairline · 无阴影 · eyebrow 拉开字距 · 密排 */
|
|
115
|
+
html[data-style="mckinsey"]{
|
|
116
|
+
--font-body:"Inter","IBM Plex Sans",-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
|
117
|
+
--font-display:"Georgia","Times New Roman","Songti SC","STSong","SimSun",serif;
|
|
118
|
+
--radius:6px;--fw-display:600;--fw-title:600;
|
|
119
|
+
--letter-display:-.015em;--letter-eyebrow:.14em;--lh-body:1.55;--lh-lead:1.5;
|
|
120
|
+
--gap:clamp(14px,1.3vw,22px);
|
|
121
|
+
--bg:#fff;--surface:#fff;--surface-1:#f2f4f7;--surface-2:#f2f4f7;--surface-3:#e4e7ec;
|
|
122
|
+
--surface-inv:#101828;--text:#101828;--text-2:#475467;--text-3:#667085;--text-inv:#fff;
|
|
123
|
+
--border:#d0d5dd;--border-soft:#e4e7ec;
|
|
124
|
+
--accent:#003a70;--accent-soft:#eef4fb;--accent-soft-2:#d9e8f6;--accent-text:#003a70;--accent-on:#fff;
|
|
125
|
+
--shadow-1:none;--shadow-2:0 1px 2px rgba(16,24,40,.06)}
|
|
126
|
+
html[data-style="mckinsey"][data-theme="dark"]{
|
|
127
|
+
--bg:#0c1116;--surface:#0c1116;--surface-1:#12181f;--surface-2:#182030;--surface-3:#21293a;
|
|
128
|
+
--surface-inv:#e4e7ec;--text:#e4e7ec;--text-2:#aab3c2;--text-3:#8a94a6;--text-inv:#101828;
|
|
129
|
+
--border:#2a3342;--border-soft:#1f2733;
|
|
130
|
+
--accent:#7fb2e5;--accent-soft:#12233a;--accent-soft-2:#1b3358;--accent-text:#7fb2e5;--accent-on:#0c1a2e}
|
|
131
|
+
/* brand-red 正式品牌语汇:HarmonyOS/雅黑栈 · 圆角 14 · 品牌红强调 */
|
|
132
|
+
html[data-style="brand-red"]{
|
|
133
|
+
--font-body:"HarmonyOS Sans","PingFang SC","Microsoft YaHei","Noto Sans SC",-apple-system,"Segoe UI",sans-serif;
|
|
134
|
+
--font-display:var(--font-body);
|
|
135
|
+
--radius:14px;--fw-display:700;--fw-title:600;
|
|
136
|
+
--letter-display:-.025em;--letter-eyebrow:.12em;--lh-body:1.66;--lh-lead:1.6;
|
|
137
|
+
--bg:#fff;--surface:#fff;--surface-1:#faf7f7;--surface-2:#f5f0f0;--surface-3:#ece5e5;
|
|
138
|
+
--surface-inv:#1f1a1b;--text:#1f1a1b;--text-2:#5d5456;--text-3:#8a8083;--text-inv:#fff;
|
|
139
|
+
--border:#e2d9da;--border-soft:#ece5e5;
|
|
140
|
+
--accent:#d0021b;--accent-soft:#fbecec;--accent-soft-2:#f6d5d7;--accent-text:#b00217;--accent-on:#fff}
|
|
141
|
+
html[data-style="brand-red"][data-theme="dark"]{
|
|
142
|
+
--bg:#130d0e;--surface:#130d0e;--surface-1:#1a1213;--surface-2:#231718;--surface-3:#2e1e20;
|
|
143
|
+
--surface-inv:#f3ecec;--text:#f3ecec;--text-2:#c4b6b7;--text-3:#9a8d8e;--text-inv:#1f1a1b;
|
|
144
|
+
--border:#37272a;--border-soft:#2a1e20;
|
|
145
|
+
--accent:#ff5a66;--accent-soft:#331518;--accent-soft-2:#471c21;--accent-text:#ff7a83;--accent-on:#2a0d10}
|
|
146
|
+
/* warm-sand 编辑/年报语汇:暖米底 · 衬线标题 · 圆角 12 · 宽松行高 */
|
|
147
|
+
html[data-style="warm-sand"]{
|
|
148
|
+
--font-body:"Inter","Source Han Sans","PingFang SC","Microsoft YaHei",-apple-system,sans-serif;
|
|
149
|
+
--font-display:"Georgia","Playfair Display","Songti SC","STSong","SimSun",serif;
|
|
150
|
+
--radius:12px;--fw-display:600;--fw-title:600;
|
|
151
|
+
--letter-display:-.01em;--letter-eyebrow:.13em;--lh-body:1.72;--lh-lead:1.65;
|
|
152
|
+
--gap:clamp(18px,1.8vw,30px);
|
|
153
|
+
--bg:#fbf9f6;--surface:#fff;--surface-1:#f6f2ec;--surface-2:#efe9df;--surface-3:#e6ded2;
|
|
154
|
+
--surface-inv:#2a2521;--text:#2a2521;--text-2:#6b5f52;--text-3:#93836f;--text-inv:#f6f2ec;
|
|
155
|
+
--border:#e0d7c9;--border-soft:#eae3d6;
|
|
156
|
+
--accent:#96681f;--accent-soft:#f3ecdd;--accent-soft-2:#eaddc4;--accent-text:#8f6117;--accent-on:#fff;
|
|
157
|
+
--shadow-1:none;--shadow-2:0 1px 3px rgba(42,37,33,.08)}
|
|
158
|
+
html[data-style="warm-sand"][data-theme="dark"]{
|
|
159
|
+
--bg:#15120e;--surface:#15120e;--surface-1:#1d1913;--surface-2:#26201a;--surface-3:#312920;
|
|
160
|
+
--surface-inv:#ece5db;--text:#ece5db;--text-2:#c4b8a8;--text-3:#9b8d7a;--text-inv:#2a2521;
|
|
161
|
+
--border:#3a3128;--border-soft:#2c261e;
|
|
162
|
+
--accent:#d9b36a;--accent-soft:#2e2417;--accent-soft-2:#3e3019;--accent-text:#e0c184;--accent-on:#2a1f10}
|
|
163
|
+
/* deep-teal 沉稳语汇:冷灰底 · 深青强调 · 圆角 12 · 克制阴影 */
|
|
164
|
+
html[data-style="deep-teal"]{
|
|
165
|
+
--font-body:"Inter","Source Han Sans","PingFang SC","Microsoft YaHei",-apple-system,sans-serif;
|
|
166
|
+
--font-display:var(--font-body);
|
|
167
|
+
--radius:12px;--fw-display:700;--fw-title:600;
|
|
168
|
+
--letter-display:-.028em;--letter-eyebrow:.11em;--lh-body:1.66;--lh-lead:1.6;
|
|
169
|
+
--bg:#fff;--surface:#fff;--surface-1:#f2f6f5;--surface-2:#e8f0ef;--surface-3:#dce7e5;
|
|
170
|
+
--surface-inv:#14201e;--text:#14201e;--text-2:#4a5a57;--text-3:#73837f;--text-inv:#fff;
|
|
171
|
+
--border:#cfdedc;--border-soft:#e0eae9;
|
|
172
|
+
--accent:#0f6b5c;--accent-soft:#e4f1ee;--accent-soft-2:#cfe5e0;--accent-text:#0d5a4e;--accent-on:#fff}
|
|
173
|
+
html[data-style="deep-teal"][data-theme="dark"]{
|
|
174
|
+
--bg:#0d1312;--surface:#0d1312;--surface-1:#131b1a;--surface-2:#1a2523;--surface-3:#22302d;
|
|
175
|
+
--surface-inv:#e2ecea;--text:#e2ecea;--text-2:#b3c2bf;--text-3:#86948f;--text-inv:#14201e;
|
|
176
|
+
--border:#293834;--border-soft:#1e2a27;
|
|
177
|
+
--accent:#4fc3b0;--accent-soft:#12312c;--accent-soft-2:#1a423b;--accent-text:#66cfbd;--accent-on:#0c2420}
|
|
178
|
+
/* graphite-dark 石墨语汇:深色优先 · 亮蓝青 · 圆角 14 · 深色阴影更沉 */
|
|
179
|
+
html[data-style="graphite-dark"]{
|
|
180
|
+
--font-body:"Inter","SF Pro Text","PingFang SC","Microsoft YaHei",-apple-system,sans-serif;
|
|
181
|
+
--font-display:"Inter","SF Pro Display","PingFang SC",sans-serif;
|
|
182
|
+
--radius:14px;--fw-display:700;--fw-title:600;
|
|
183
|
+
--letter-display:-.03em;--letter-eyebrow:.1em;--lh-body:1.62;--lh-lead:1.55;
|
|
184
|
+
--gap:clamp(14px,1.4vw,24px);
|
|
185
|
+
--bg:#f4f5f7;--surface:#fff;--surface-1:#eceef1;--surface-2:#e3e6ea;--surface-3:#d8dbdf;
|
|
186
|
+
--surface-inv:#14171b;--text:#1a1d21;--text-2:#4b5158;--text-3:#767c84;--text-inv:#eef2f6;
|
|
187
|
+
--border:#ced1d6;--border-soft:#e3e6ea;
|
|
188
|
+
--accent:#0369a1;--accent-soft:#e2eff7;--accent-soft-2:#c8e1f2;--accent-text:#075985;--accent-on:#fff}
|
|
189
|
+
html[data-style="graphite-dark"][data-theme="dark"]{
|
|
190
|
+
--bg:#0b0e12;--surface:#12161b;--surface-1:#171d24;--surface-2:#1f2732;--surface-3:#28323f;
|
|
191
|
+
--surface-inv:#eef2f6;--text:#eef2f6;--text-2:#b9c2cd;--text-3:#85909d;--text-inv:#12161b;
|
|
192
|
+
--border:#2b3540;--border-soft:#212932;
|
|
193
|
+
--accent:#4da3ff;--accent-soft:#132841;--accent-soft-2:#1a3a5e;--accent-text:#6fb2ff;--accent-on:#07182b;
|
|
194
|
+
--shadow-1:0 1px 2px rgba(0,0,0,.5);--shadow-2:0 4px 12px rgba(0,0,0,.45)}
|
|
195
|
+
/* indigo-violet 创新语汇:冷白 + 靛紫 · 圆角 16 · 轻快字距 */
|
|
196
|
+
html[data-style="indigo-violet"]{
|
|
197
|
+
--font-body:"Inter","SF Pro Text","PingFang SC","Microsoft YaHei",-apple-system,sans-serif;
|
|
198
|
+
--font-display:var(--font-body);
|
|
199
|
+
--radius:16px;--fw-display:700;--fw-title:600;
|
|
200
|
+
--letter-display:-.032em;--letter-eyebrow:.12em;--lh-body:1.68;--lh-lead:1.62;
|
|
201
|
+
--bg:#fff;--surface:#fff;--surface-1:#f4f4fb;--surface-2:#ececf7;--surface-3:#e0e0f0;
|
|
202
|
+
--surface-inv:#1b1a2e;--text:#1b1a2e;--text-2:#52506b;--text-3:#7a779a;--text-inv:#fff;
|
|
203
|
+
--border:#d7d7e8;--border-soft:#e6e6f2;
|
|
204
|
+
--accent:#5b5bd6;--accent-soft:#ececf9;--accent-soft-2:#dcdcf3;--accent-text:#4848c0;--accent-on:#fff}
|
|
205
|
+
html[data-style="indigo-violet"][data-theme="dark"]{
|
|
206
|
+
--bg:#100f1c;--surface:#100f1c;--surface-1:#171629;--surface-2:#1f1e36;--surface-3:#292845;
|
|
207
|
+
--surface-inv:#e6e5f5;--text:#e6e5f5;--text-2:#b6b4d4;--text-3:#8b89b0;--text-inv:#1b1a2e;
|
|
208
|
+
--border:#322f52;--border-soft:#262441;
|
|
209
|
+
--accent:#a5a4f0;--accent-soft:#232146;--accent-soft-2:#2e2c58;--accent-text:#b6b5f5;--accent-on:#171631}
|
|
210
|
+
/* spectrum 数据语汇:中性骨架 + 彩色只进数据 · 圆角 14 · 紧凑栅格 */
|
|
211
|
+
html[data-style="spectrum"]{
|
|
212
|
+
--font-body:"Inter","SF Pro Text","PingFang SC","Microsoft YaHei",-apple-system,sans-serif;
|
|
213
|
+
--font-display:var(--font-body);
|
|
214
|
+
--radius:14px;--fw-display:700;--fw-title:600;
|
|
215
|
+
--letter-display:-.03em;--letter-eyebrow:.1em;--lh-body:1.6;--lh-lead:1.55;
|
|
216
|
+
--gap:clamp(12px,1.2vw,20px);
|
|
217
|
+
--bg:#fbfcfe;--surface:#fff;--surface-1:#f4f6fa;--surface-2:#eceff5;--surface-3:#dfe4ec;
|
|
218
|
+
--surface-inv:#171a21;--text:#171a21;--text-2:#4d5566;--text-3:#7b8494;--text-inv:#fff;
|
|
219
|
+
--border:#d6dce6;--border-soft:#e6eaf1;
|
|
220
|
+
--accent:#4563ef;--accent-soft:#eceeff;--accent-soft-2:#d8defb;--accent-text:#3d5bef;--accent-on:#fff}
|
|
221
|
+
html[data-style="spectrum"][data-theme="dark"]{
|
|
222
|
+
--bg:#0e1116;--surface:#131720;--surface-1:#171c26;--surface-2:#1d2330;--surface-3:#252c3b;
|
|
223
|
+
--surface-inv:#e8ebf2;--text:#e8ebf2;--text-2:#a7afbf;--text-3:#7e8798;--text-inv:#171a21;
|
|
224
|
+
--border:#2b3342;--border-soft:#212839;
|
|
225
|
+
--accent:#8ba0f8;--accent-soft:#1b2242;--accent-soft-2:#26305c;--accent-text:#93a7f9;--accent-on:#141a38}
|
|
226
|
+
|
|
227
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
228
|
+
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;scroll-snap-type:y proximity}
|
|
229
|
+
body{margin:0;background:var(--bg);color:var(--text);
|
|
230
|
+
font-family:var(--font-body);
|
|
231
|
+
font-size:var(--fs-body);line-height:1.65;letter-spacing:.01em;-webkit-font-smoothing:antialiased;
|
|
232
|
+
transition:background-color var(--dur-medium) var(--ease-standard),color var(--dur-medium) var(--ease-standard)}
|
|
233
|
+
h1,h2,h3,h4,p{margin:0}
|
|
234
|
+
h1,h2,h3,h4{font-family:var(--font-display);font-weight:var(--fw-title);color:var(--text)}
|
|
235
|
+
.t-display{font-size:var(--fs-display);line-height:1.1;letter-spacing:var(--letter-display);font-weight:var(--fw-display)}
|
|
236
|
+
.t-h1{font-size:var(--fs-h1);line-height:1.18;letter-spacing:var(--letter-display);font-weight:var(--fw-title)}
|
|
237
|
+
.t-h2{font-size:var(--fs-h2);line-height:1.25;letter-spacing:var(--letter-display);font-weight:var(--fw-title)}
|
|
238
|
+
.t-h3{font-size:var(--fs-h3);line-height:1.35;letter-spacing:var(--letter-display);font-weight:var(--fw-title)}
|
|
239
|
+
.t-lead{font-size:var(--fs-lead);line-height:var(--lh-lead);color:var(--text-2)}
|
|
240
|
+
.t-body{font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-2)}
|
|
241
|
+
.t-sm{font-size:var(--fs-sm);line-height:1.6;color:var(--text-3)}
|
|
242
|
+
.t-xs{font-size:var(--fs-xs);line-height:1.55;color:var(--text-3)}
|
|
243
|
+
.t-metric{font-size:var(--fs-metric);line-height:1;letter-spacing:var(--letter-display);font-weight:500;
|
|
244
|
+
font-variant-numeric:tabular-nums}
|
|
245
|
+
.t-eyebrow{font-size:var(--fs-xs);font-weight:600;letter-spacing:var(--letter-eyebrow);text-transform:uppercase;
|
|
246
|
+
color:var(--accent-text)}
|
|
247
|
+
.cite{font-size:.62em;vertical-align:super;color:var(--accent-text);font-weight:600;
|
|
248
|
+
margin-left:2px;text-decoration:none}
|
|
249
|
+
.cite:hover{text-decoration:underline}
|
|
250
|
+
.ref-link{color:var(--link);text-decoration:none;font-weight:500;
|
|
251
|
+
border-bottom:1px solid var(--accent-soft-2);padding-bottom:1px;
|
|
252
|
+
transition:border-color var(--dur-short),color var(--dur-short);white-space:nowrap}
|
|
253
|
+
.ref-link:hover{color:var(--accent);border-bottom-color:var(--accent)}
|
|
254
|
+
.ref-link svg{width:.82em;height:.82em;vertical-align:-.08em;margin-left:2px;opacity:.75}
|
|
255
|
+
.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:clamp(20px,3.2vw,56px)}
|
|
256
|
+
section{scroll-margin-top:calc(var(--bar-h) + 16px)}
|
|
257
|
+
section.band{scroll-snap-align:start}
|
|
258
|
+
/* ── 页面高度模型:默认「一页 = 一屏」——
|
|
259
|
+
所有 .band 至少一屏高并垂直居中,短内容页不再出现"高矮不齐";
|
|
260
|
+
内容超一屏时 min-height 不截断、自动退化为自然流(安全)。
|
|
261
|
+
svh 兼容移动端浏览器工具栏;旧浏览器落到 100vh。 */
|
|
262
|
+
.band{padding-block:var(--band-pad);min-height:var(--band-min);
|
|
263
|
+
display:flex;flex-direction:column;justify-content:center}
|
|
264
|
+
.band>.wrap{width:100%}
|
|
265
|
+
.band--top{justify-content:flex-start} /* 内容约六七成屏:贴顶 + 底部自然留白 */
|
|
266
|
+
.band--fit{justify-content:center} /* 内容接近一屏:垂直居中(默认行为,保留以兼容旧报告) */
|
|
267
|
+
.band--flow{min-height:0;justify-content:flex-start} /* 长内容页(附录/全量参考)显式退出固定页高 */
|
|
268
|
+
.band--tint{background:var(--surface-1)}
|
|
269
|
+
/* ── 强调带(主题一致):浅色/深色同向,不再出现"浅色模式末尾深色页" ──
|
|
270
|
+
band--accent accent-soft 软强调(收尾 / CTA / 小结)
|
|
271
|
+
band--accent--solid accent 实底强调(引言 / 金句 / 关键判断,文字走 --accent-on)
|
|
272
|
+
band--deep 固定深色反相页(**两主题一致**:始终深底浅字,不再随主题翻转)
|
|
273
|
+
——仅限显式选择,全文 ≤1 且不得作末页(校验器 emphasis 约束) */
|
|
274
|
+
.band--accent{background:var(--accent-soft)}
|
|
275
|
+
.band--accent .t-eyebrow{color:var(--accent-text)}
|
|
276
|
+
.band--accent .card{background:var(--surface);border-color:var(--border-soft)}
|
|
277
|
+
.band--accent--solid{background:var(--accent);color:var(--accent-on)}
|
|
278
|
+
.band--accent--solid .t-h1,.band--accent--solid .t-h2,.band--accent--solid .t-h3,
|
|
279
|
+
.band--accent--solid .t-display{color:var(--accent-on)}
|
|
280
|
+
.band--accent--solid .t-lead,.band--accent--solid .t-body{color:color-mix(in srgb,var(--accent-on) 80%,transparent)}
|
|
281
|
+
.band--accent--solid .t-eyebrow{color:color-mix(in srgb,var(--accent-on) 84%,transparent)}
|
|
282
|
+
.band--accent--solid .card{background:color-mix(in srgb,var(--accent-on) 12%,transparent);
|
|
283
|
+
border-color:color-mix(in srgb,var(--accent-on) 20%,transparent);color:var(--accent-on)}
|
|
284
|
+
.band--accent--solid .card .t-h3,.band--accent--solid .card .t-body{color:inherit}
|
|
285
|
+
.band--accent--solid .ul li{color:color-mix(in srgb,var(--accent-on) 84%,transparent)}
|
|
286
|
+
.band--accent--solid .ul li::before{background:var(--accent-on)}
|
|
287
|
+
.band--deep{background:var(--deep-bg);color:var(--deep-fg)}
|
|
288
|
+
.band--deep .t-lead,.band--deep .t-body{color:var(--deep-fg-2)}
|
|
289
|
+
.band--deep .t-eyebrow{color:color-mix(in srgb,var(--accent) 40%,#fff)}
|
|
290
|
+
.band--deep .card{background:color-mix(in srgb,var(--deep-fg) 6%,var(--deep-bg));border-color:var(--deep-line);color:var(--deep-fg)}
|
|
291
|
+
.band--deep .card .t-h3,.band--deep .card .t-body{color:inherit}
|
|
292
|
+
.band--deep .ul li{color:var(--deep-fg-2)}
|
|
293
|
+
.band--deep .ul li::before{background:color-mix(in srgb,var(--accent) 40%,#fff)}
|
|
294
|
+
.band--deep .tbl-wrap table thead th{color:var(--deep-fg)}
|
|
295
|
+
.band--deep .tbl-wrap table tbody td{color:var(--deep-fg-2)}
|
|
296
|
+
.a-c{align-items:center}.a-start{align-items:start}.a-end{align-items:end}
|
|
297
|
+
.j-c{justify-content:center}.j-between{justify-content:space-between}
|
|
298
|
+
.grow{flex:1;min-width:0}
|
|
299
|
+
.grid{display:grid;gap:var(--gap)}
|
|
300
|
+
.g-2{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
301
|
+
.g-3{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
302
|
+
.g-4{grid-template-columns:repeat(4,minmax(0,1fr))}
|
|
303
|
+
.g-5{grid-template-columns:repeat(5,minmax(0,1fr))}
|
|
304
|
+
.g-6{grid-template-columns:repeat(6,minmax(0,1fr))}
|
|
305
|
+
.g-auto{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
|
|
306
|
+
.g-hero{grid-template-columns:minmax(0,1.08fr) minmax(0,1fr);align-items:center}
|
|
307
|
+
.g-hero__visual{min-width:0}
|
|
308
|
+
.g-side{grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);align-items:start}
|
|
309
|
+
.stack{display:flex;flex-direction:column}
|
|
310
|
+
.gap-3{gap:var(--sp-3)}.gap-4{gap:var(--sp-4)}.gap-5{gap:var(--sp-5)}.gap-6{gap:var(--sp-6)}
|
|
311
|
+
.row{display:flex;align-items:center;gap:var(--sp-3)}.row-wrap{flex-wrap:wrap}
|
|
312
|
+
/* ── 密度版式扩展(内容丰富度分级选版式 · 见 content-rules.md 四-d / `components.md` §46)──
|
|
313
|
+
g-hero--rev:Hero 视觉居左(大图开场,文字收在右侧)
|
|
314
|
+
g-side--rev:图右文左(与 g-side 镜像,用于"结论先行、证据靠右")
|
|
315
|
+
g-31 / g-13:3:1 / 1:3 非对称双栏(主次分明的中等密度页)
|
|
316
|
+
g-41 / g-14:4:1 / 1:4 强主次双栏(主件 + 窄侧栏注解/指标条) */
|
|
317
|
+
.g-hero--rev{grid-template-columns:minmax(0,1fr) minmax(0,1.08fr);align-items:center}
|
|
318
|
+
.g-side--rev{grid-template-columns:minmax(0,1fr) minmax(0,1.35fr);align-items:start}
|
|
319
|
+
.g-31{grid-template-columns:minmax(0,3fr) minmax(0,1fr)}
|
|
320
|
+
.g-13{grid-template-columns:minmax(0,1fr) minmax(0,3fr)}
|
|
321
|
+
.g-41{grid-template-columns:minmax(0,4fr) minmax(0,1fr)}
|
|
322
|
+
.g-14{grid-template-columns:minmax(0,1fr) minmax(0,4fr)}
|
|
323
|
+
/* ── 版式 pattern 扩展(非对称多栏 / 象限 / 侧栏 / 马赛克 / 全幅主件)──
|
|
324
|
+
g-211 / g-121:三栏非对称(主栏 + 两个辅助栏;主次分明且不呆板)
|
|
325
|
+
g-quad:2×2 象限(四宫格,每格等权,适合四象限分析 / 四类并列)
|
|
326
|
+
g-aside:主内容 + 粘性侧栏(长文或大图 + 常驻注解/指标栏)
|
|
327
|
+
g-mosaic:命名区马赛克(hero 通栏 + 三卡,一屏一重心)
|
|
328
|
+
g-hero-full:全幅主件 + 底部三等分(大图/大表 + 支撑卡)
|
|
329
|
+
rows-2 / rows-3:等高行分区(纵向节奏,替代等高卡片堆叠) */
|
|
330
|
+
.g-211{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr)}
|
|
331
|
+
.g-121{grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr)}
|
|
332
|
+
.g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);align-items:stretch}
|
|
333
|
+
.g-aside{grid-template-columns:minmax(0,1fr) minmax(200px,.36fr);align-items:start}
|
|
334
|
+
.g-aside__side{position:sticky;top:calc(var(--bar-h) + 16px)}
|
|
335
|
+
.g-mosaic{grid-template-columns:repeat(3,minmax(0,1fr));
|
|
336
|
+
grid-template-areas:"hero hero hero" "a b c";grid-auto-rows:auto}
|
|
337
|
+
.g-mosaic__hero{grid-area:hero}.g-mosaic__a{grid-area:a}
|
|
338
|
+
.g-mosaic__b{grid-area:b}.g-mosaic__c{grid-area:c}
|
|
339
|
+
.g-hero-full{grid-template-columns:repeat(3,minmax(0,1fr));
|
|
340
|
+
grid-template-areas:"main main main" "s1 s2 s3"}
|
|
341
|
+
.g-hero-full__main{grid-area:main}
|
|
342
|
+
.g-hero-full__s1{grid-area:s1}.g-hero-full__s2{grid-area:s2}.g-hero-full__s3{grid-area:s3}
|
|
343
|
+
.rows-2{grid-template-rows:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,1fr)}
|
|
344
|
+
.rows-3{grid-template-rows:repeat(3,minmax(0,1fr));grid-auto-rows:minmax(0,1fr)}
|
|
345
|
+
.kv--2col{grid-template-columns:auto minmax(0,1fr) auto minmax(0,1fr)}
|
|
346
|
+
/* ── 拼贴栅格 g-bento(Bento / 马赛克):4 列基准 + 跨列/跨行工具类 ──
|
|
347
|
+
.sp-2/.sp-3/.sp-4 跨列(span),.rw-2 跨行;窄屏自动降列并收敛 span。 */
|
|
348
|
+
.g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(118px,auto)}
|
|
349
|
+
.sp-2{grid-column:span 2}.sp-3{grid-column:span 3}.sp-4{grid-column:span 4}
|
|
350
|
+
.rw-2{grid-row:span 2}
|
|
351
|
+
@media (max-width:1180px){.g-bento{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
352
|
+
.sp-3,.sp-4{grid-column:span 2}}
|
|
353
|
+
@media (max-width:620px){.g-bento{grid-template-columns:1fr}
|
|
354
|
+
.sp-2,.sp-3,.sp-4{grid-column:span 1}.rw-2{grid-row:auto}}
|
|
355
|
+
/* ── 交错图文版式 .stagger(左右交替:一行图左文右、一行文左图右) ── */
|
|
356
|
+
.stagger{display:flex;flex-direction:column;gap:clamp(20px,2.8vh,40px)}
|
|
357
|
+
.stagger__row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
|
|
358
|
+
gap:clamp(20px,3vw,52px);align-items:center}
|
|
359
|
+
.stagger__row--rev .stagger__media{order:2}
|
|
360
|
+
.stagger__row--rev .stagger__txt{order:1}
|
|
361
|
+
@media (max-width:960px){.stagger__row{grid-template-columns:1fr}
|
|
362
|
+
.stagger__row--rev .stagger__media,.stagger__row--rev .stagger__txt{order:0}}
|
|
363
|
+
/* ── 键值对照 .kv(定义列表:指标口径 / 参数 / 对照项) ── */
|
|
364
|
+
.kv{display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--sp-3) var(--sp-5);
|
|
365
|
+
align-items:baseline;margin:0}
|
|
366
|
+
.kv dt{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap}
|
|
367
|
+
.kv dd{margin:0;font-size:var(--fs-sm);color:var(--text-2);line-height:1.6}
|
|
368
|
+
@media (max-width:1180px){.g-4{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
369
|
+
.g-5{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
370
|
+
.g-6{grid-template-columns:repeat(3,minmax(0,1fr))}.g-side,.g-side--rev{grid-template-columns:1fr}}
|
|
371
|
+
@media (max-width:960px){.g-3,.g-hero,.g-hero--rev,.g-31,.g-13,.g-41,.g-14,.g-211,.g-121,.g-quad,.g-aside{grid-template-columns:1fr}
|
|
372
|
+
.g-aside__side{position:static}
|
|
373
|
+
.g-mosaic,.g-hero-full{grid-template-columns:1fr;grid-template-areas:none}
|
|
374
|
+
.g-hero__visual{order:-1;max-width:460px;margin-inline:auto}}
|
|
375
|
+
@media (max-width:620px){.g-2,.g-4,.g-5,.g-6{grid-template-columns:1fr}}
|
|
376
|
+
|
|
377
|
+
/* ── 顶栏 + header 工具组(主题/风格/预览/帮助/全屏/折叠;无模式切换) ── */
|
|
378
|
+
.bar{position:sticky;top:0;z-index:50;height:var(--bar-h);
|
|
379
|
+
background:color-mix(in srgb,var(--bg) 86%,transparent);
|
|
380
|
+
backdrop-filter:saturate(160%) blur(14px);-webkit-backdrop-filter:saturate(160%) blur(14px);
|
|
381
|
+
border-bottom:1px solid var(--border-soft);
|
|
382
|
+
transition:height var(--dur-medium) var(--ease-standard)}
|
|
383
|
+
.bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4)}
|
|
384
|
+
.brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
|
|
385
|
+
.brand__mark{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent);
|
|
386
|
+
color:var(--accent-on);display:grid;place-items:center;font-size:13px;font-weight:700}
|
|
387
|
+
.brand__title{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap;
|
|
388
|
+
overflow:hidden;text-overflow:ellipsis}
|
|
389
|
+
.brand__sub{font-size:var(--fs-xs);color:var(--text-3);white-space:nowrap;overflow:hidden;
|
|
390
|
+
text-overflow:ellipsis}
|
|
391
|
+
.nav{display:flex;align-items:center;gap:var(--sp-1)}
|
|
392
|
+
.nav a{padding:8px 14px;border-radius:var(--r-full);color:var(--text-2);text-decoration:none;
|
|
393
|
+
font-size:var(--fs-sm);font-weight:500;white-space:nowrap;transition:background-color var(--dur-short),color var(--dur-short)}
|
|
394
|
+
.nav a:hover{background:var(--surface-2);color:var(--text)}
|
|
395
|
+
@media (max-width:1180px){.nav{display:none}}
|
|
396
|
+
.iconbtn{width:40px;height:40px;flex:none;display:grid;place-items:center;
|
|
397
|
+
border:1px solid var(--border);border-radius:var(--r-full);background:transparent;
|
|
398
|
+
color:var(--text-2);cursor:pointer;transition:background-color var(--dur-short),color var(--dur-short)}
|
|
399
|
+
.iconbtn:hover{background:var(--surface-2);color:var(--text)}
|
|
400
|
+
.iconbtn svg{width:19px;height:19px}
|
|
401
|
+
.icon-sun,.icon-moon{display:none}
|
|
402
|
+
html[data-theme="light"] .icon-moon{display:block}
|
|
403
|
+
html[data-theme="dark"] .icon-sun{display:block}
|
|
404
|
+
.toolsbar{display:flex;align-items:center;gap:6px;flex:none}
|
|
405
|
+
@media (max-width:620px){.toolsbar .iconbtn{width:36px;height:36px}
|
|
406
|
+
.toolsbar .iconbtn svg{width:17px;height:17px}}
|
|
407
|
+
/* ── header 折叠态(B 键收起成迷你工具条:只留品牌标 + 展开钮;--bar-h 联动收紧) ── */
|
|
408
|
+
html.bar--fold{--bar-h:48px}
|
|
409
|
+
html.bar--fold .bar{border-bottom-color:transparent}
|
|
410
|
+
html.bar--fold .brand__txt,html.bar--fold .nav{display:none}
|
|
411
|
+
html.bar--fold .brand{gap:0}
|
|
412
|
+
html.bar--fold .brand__mark{width:28px;height:28px;border-radius:8px;font-size:11px}
|
|
413
|
+
html.bar--fold .toolsbar>*{display:none}
|
|
414
|
+
html.bar--fold .toolsbar #barFold{display:grid}
|
|
415
|
+
.icon-fold{display:block}.icon-unfold{display:none}
|
|
416
|
+
html.bar--fold .icon-fold{display:none}
|
|
417
|
+
html.bar--fold .icon-unfold{display:block}
|
|
418
|
+
/* ── 全屏按钮图标态(F 键同效) ── */
|
|
419
|
+
.icon-fsx{display:block}.icon-fsx-exit{display:none}
|
|
420
|
+
html.is-fs .icon-fsx{display:none}html.is-fs .icon-fsx-exit{display:block}
|
|
421
|
+
/* 风格下拉(header 内,克制弹出面板) */
|
|
422
|
+
.seld{position:relative}
|
|
423
|
+
.seld__pop{position:absolute;top:calc(100% + 10px);right:0;width:238px;background:var(--surface);
|
|
424
|
+
border:1px solid var(--border);border-radius:var(--r-lg);box-shadow:var(--shadow-2);
|
|
425
|
+
padding:var(--sp-3);display:none;z-index:80}
|
|
426
|
+
.seld.open .seld__pop{display:block}
|
|
427
|
+
.seld__lab{font-size:var(--fs-xs);font-weight:700;letter-spacing:.08em;text-transform:uppercase;
|
|
428
|
+
color:var(--text-3);padding:2px 6px 8px}
|
|
429
|
+
.seld__list{display:flex;flex-direction:column;gap:2px;max-height:330px;overflow:auto}
|
|
430
|
+
.sitem{display:flex;align-items:center;gap:10px;padding:7px 10px;border:none;background:transparent;
|
|
431
|
+
border-radius:var(--r-sm);color:var(--text-2);font-size:var(--fs-sm);font-weight:500;cursor:pointer;
|
|
432
|
+
text-align:left;font-family:inherit;transition:background-color var(--dur-short),color var(--dur-short)}
|
|
433
|
+
.sitem:hover{background:var(--surface-2);color:var(--text)}
|
|
434
|
+
.sitem.on{background:var(--accent-soft);color:var(--accent-text);font-weight:600}
|
|
435
|
+
.sitem .swd{width:12px;height:12px;border-radius:50%;flex:none;
|
|
436
|
+
box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--text) 12%,transparent)}
|
|
437
|
+
@media print{.toolsbar{display:none!important}.seld__pop{display:none!important}}
|
|
438
|
+
|
|
439
|
+
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:48px;
|
|
440
|
+
padding-inline:26px;border-radius:var(--r-full);font-size:var(--fs-sm);font-weight:500;
|
|
441
|
+
text-decoration:none;cursor:pointer;white-space:nowrap;border:1px solid transparent;
|
|
442
|
+
transition:background-color var(--dur-short),box-shadow var(--dur-short),transform var(--dur-short)}
|
|
443
|
+
.btn:hover{transform:translateY(-1px)}
|
|
444
|
+
.btn--primary{background:var(--accent);color:var(--accent-on)}
|
|
445
|
+
.btn--primary:hover{box-shadow:var(--shadow-2)}
|
|
446
|
+
.btn--soft{background:var(--accent-soft);color:var(--accent-text)}
|
|
447
|
+
.btn--ghost{background:transparent;color:var(--text);border-color:var(--border)}
|
|
448
|
+
.btn--onDark{background:var(--surface);color:var(--text)}
|
|
449
|
+
.card{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-lg);
|
|
450
|
+
padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium)}
|
|
451
|
+
.card:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
|
|
452
|
+
.card--flat{background:var(--surface-1);border-color:transparent}
|
|
453
|
+
.card--flat:hover{background:var(--surface-2)}
|
|
454
|
+
.card--accent{background:var(--accent-soft);border-color:transparent}
|
|
455
|
+
.card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)}
|
|
456
|
+
.card__ico{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent-soft);
|
|
457
|
+
color:var(--accent-text);display:grid;place-items:center;font-size:15px;font-weight:600}
|
|
458
|
+
.card__ico svg{width:18px;height:18px}
|
|
459
|
+
.chip{display:inline-flex;align-items:center;gap:6px;height:30px;padding-inline:13px;
|
|
460
|
+
border-radius:var(--r-full);background:var(--surface-2);color:var(--text-2);
|
|
461
|
+
font-size:var(--fs-xs);font-weight:500;white-space:nowrap}
|
|
462
|
+
.chip--accent{background:var(--accent-soft);color:var(--accent-text)}
|
|
463
|
+
.chip--line{background:transparent;border:1px solid var(--border);color:var(--text-2)}
|
|
464
|
+
.metric{position:relative;background:var(--surface);border:1px solid var(--border-soft);
|
|
465
|
+
border-radius:var(--r-lg);
|
|
466
|
+
padding:clamp(18px,1.7vw,26px);display:flex;flex-direction:column;gap:var(--sp-2)}
|
|
467
|
+
.metric:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
|
|
468
|
+
.metric__v{display:flex;align-items:baseline;gap:4px;color:var(--text)}
|
|
469
|
+
.metric__v small{font-size:.42em;font-weight:500;letter-spacing:0;color:var(--text-3)}
|
|
470
|
+
.metric__k{font-size:var(--fs-sm);color:var(--text-2);font-weight:500}
|
|
471
|
+
.metric__n{font-size:var(--fs-xs);color:var(--text-3);line-height:1.5}
|
|
472
|
+
.metric--warn .metric__v{color:var(--accent)}
|
|
473
|
+
.metric__ico{position:absolute;top:clamp(14px,1.4vw,20px);right:clamp(14px,1.4vw,20px);
|
|
474
|
+
width:20px;height:20px;color:var(--text-3)}
|
|
475
|
+
.ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-4)}
|
|
476
|
+
.ul li{position:relative;padding-left:30px;color:var(--text-2);font-size:var(--fs-body);
|
|
477
|
+
line-height:1.65}
|
|
478
|
+
.ul li::before{content:"";position:absolute;left:0;top:.52em;width:7px;height:7px;
|
|
479
|
+
border-radius:50%;background:var(--accent)}
|
|
480
|
+
.ul li strong{color:var(--text);font-weight:600}
|
|
481
|
+
.ul--flow li::before{content:"→";width:auto;height:auto;background:none;color:var(--accent);
|
|
482
|
+
font-weight:700;top:0;left:0}
|
|
483
|
+
.ul--check li::before{width:18px;height:18px;top:.18em;background:var(--accent-soft);
|
|
484
|
+
color:var(--accent-text);content:"✓";font-size:11px;font-weight:700;display:grid;place-items:center}
|
|
485
|
+
.ul--num{counter-reset:n}.ul--num li{padding-left:34px;counter-increment:n}
|
|
486
|
+
.ul--num li::before{content:counter(n);width:22px;height:22px;top:.06em;border-radius:50%;
|
|
487
|
+
background:var(--accent-soft);color:var(--accent-text);font-size:11px;font-weight:700;
|
|
488
|
+
display:grid;place-items:center}
|
|
489
|
+
.ul--ico li{padding-left:34px}
|
|
490
|
+
.ul--ico li>svg{position:absolute;left:0;top:.14em;width:17px;height:17px;color:var(--accent-text)}
|
|
491
|
+
.tbl-wrap{border:1px solid var(--border-soft);border-radius:var(--r-lg);overflow:hidden;
|
|
492
|
+
background:var(--surface);overflow-x:auto}
|
|
493
|
+
/* min-width 防列挤,但绝不能超过容器:半幅/双区版式里写死 720px 会把列裁掉(裁切比压缩更糟) */
|
|
494
|
+
table{width:100%;border-collapse:collapse;min-width:min(720px,100%)}
|
|
495
|
+
thead th{background:var(--surface-1);color:var(--text-2);font-size:var(--fs-xs);font-weight:600;
|
|
496
|
+
letter-spacing:.04em;text-transform:uppercase;text-align:left;padding:15px 20px;
|
|
497
|
+
border-bottom:1px solid var(--border);white-space:nowrap}
|
|
498
|
+
tbody td{padding:17px 20px;border-bottom:1px solid var(--border-soft);font-size:var(--fs-sm);
|
|
499
|
+
color:var(--text-2);vertical-align:top;line-height:1.6}
|
|
500
|
+
tbody tr:last-child td{border-bottom:none}
|
|
501
|
+
tbody tr:hover{background:var(--surface-1)}
|
|
502
|
+
td strong{color:var(--text);font-weight:600}
|
|
503
|
+
td.k{color:var(--text);font-weight:500;white-space:nowrap}
|
|
504
|
+
td.num{font-variant-numeric:tabular-nums}
|
|
505
|
+
.note{display:flex;gap:var(--sp-4);padding:clamp(20px,2vw,28px);border-radius:var(--r-lg);
|
|
506
|
+
background:var(--accent-soft);border-left:3px solid var(--accent)}
|
|
507
|
+
.note__ico{width:26px;height:26px;flex:none;border-radius:50%;background:var(--accent);
|
|
508
|
+
color:var(--accent-on);display:grid;place-items:center;font-size:13px;font-weight:700;font-style:normal}
|
|
509
|
+
.note__t{font-size:var(--fs-h3);font-weight:600;color:var(--text);margin-bottom:6px}
|
|
510
|
+
.tl{position:relative;padding-left:30px}
|
|
511
|
+
.tl::before{content:"";position:absolute;left:7px;top:10px;bottom:10px;width:2px;
|
|
512
|
+
background:var(--border);border-radius:2px}
|
|
513
|
+
.tl__i{position:relative;padding-bottom:var(--sp-6)}
|
|
514
|
+
.tl__i:last-child{padding-bottom:0}
|
|
515
|
+
.tl__d{position:absolute;left:-30px;top:5px;width:16px;height:16px;border-radius:50%;
|
|
516
|
+
background:var(--surface);border:3px solid var(--border)}
|
|
517
|
+
.tl__i--now .tl__d{border-color:var(--accent);background:var(--accent)}
|
|
518
|
+
.tl__i--done .tl__d{border-color:var(--accent)}
|
|
519
|
+
.tl__t{font-size:var(--fs-h3);font-weight:600;color:var(--text);margin-bottom:6px}
|
|
520
|
+
.tl__l{font-size:var(--fs-xs);font-weight:700;letter-spacing:.08em;text-transform:uppercase;
|
|
521
|
+
color:var(--accent-text);margin-bottom:2px}
|
|
522
|
+
.fig{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-xl);
|
|
523
|
+
padding:clamp(20px,2.2vw,36px);overflow:hidden}
|
|
524
|
+
|
|
525
|
+
/* ── 锁定版式组件(新页型 steps / heatmap / bullet / pyramid)──
|
|
526
|
+
几何与 PPTX 同源页型一致(layout-constants.json 的 pageTypes.steps/heatmap/bullet/pyramid)。 */
|
|
527
|
+
|
|
528
|
+
/* 步骤条:N 步横排(序号 + 标题 + 说明)+ 步间箭头;可分组(.steps__phase 阶段标签) */
|
|
529
|
+
.steps{display:flex;align-items:stretch}
|
|
530
|
+
.step{flex:1;min-width:0;position:relative;background:var(--surface);border:1px solid var(--border-soft);
|
|
531
|
+
border-radius:var(--r-md);padding:var(--sp-4) var(--sp-4) var(--sp-5)}
|
|
532
|
+
.step+.step{margin-left:calc(var(--sp-4) + 14px)}
|
|
533
|
+
.step+.step::before{content:"";position:absolute;left:calc(-1 * (var(--sp-4) + 14px));top:50%;
|
|
534
|
+
width:calc(var(--sp-4) + 14px);height:2px;background:var(--border);transform:translateY(-50%)}
|
|
535
|
+
.step+.step::after{content:"";position:absolute;left:-11px;top:50%;width:8px;height:8px;
|
|
536
|
+
border-top:2px solid var(--border);border-right:2px solid var(--border);
|
|
537
|
+
transform:translateY(-50%) rotate(45deg)}
|
|
538
|
+
.step__n{font-size:var(--fs-xs);font-weight:700;letter-spacing:.08em;color:var(--accent-text);
|
|
539
|
+
font-variant-numeric:tabular-nums;margin-bottom:6px}
|
|
540
|
+
.step__t{font-size:var(--fs-h3);font-weight:600;color:var(--text);margin-bottom:4px}
|
|
541
|
+
.step__d{font-size:var(--fs-sm);color:var(--text-3);line-height:1.55}
|
|
542
|
+
.step--a{background:var(--accent-soft);border-color:transparent}
|
|
543
|
+
.step--a .step__t{color:var(--accent-text)}
|
|
544
|
+
.steps__phase{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3);
|
|
545
|
+
font-size:var(--fs-xs);font-weight:700;letter-spacing:.06em;color:var(--text-3);text-transform:uppercase}
|
|
546
|
+
.steps__phase::after{content:"";flex:1;height:1px;background:var(--border-soft)}
|
|
547
|
+
@media (max-width:960px){.steps{flex-direction:column;gap:var(--sp-3)}
|
|
548
|
+
.step+.step{margin-left:0}.step+.step::before,.step+.step::after{display:none}}
|
|
549
|
+
|
|
550
|
+
/* 热力矩阵:行标签 × 列标签 + 强度色阶(accent 明度阶梯,不引入第二色相) */
|
|
551
|
+
.heat{border:1px solid var(--border-soft);border-radius:var(--r-md);background:var(--surface);
|
|
552
|
+
padding:clamp(14px,1.4vw,22px)}
|
|
553
|
+
.heat__grid{display:grid;grid-template-columns:var(--heat-label,150px) repeat(var(--heat-cols,4),minmax(0,1fr));
|
|
554
|
+
gap:6px;align-items:stretch}
|
|
555
|
+
.heat__h{font-size:var(--fs-xs);font-weight:700;letter-spacing:.05em;color:var(--text-2);
|
|
556
|
+
text-align:center;padding:6px 8px;border-bottom:2px solid var(--border)}
|
|
557
|
+
.heat__rh{font-size:var(--fs-xs);font-weight:700;color:var(--text-2);display:flex;align-items:center;
|
|
558
|
+
justify-content:flex-end;text-align:right;padding:6px 12px 6px 0;border-right:2px solid var(--border)}
|
|
559
|
+
.heat__c{background:var(--surface-1);border-radius:var(--r-sm);padding:10px 12px;text-align:center;
|
|
560
|
+
font-size:var(--fs-sm);font-weight:600;color:var(--text-2);font-variant-numeric:tabular-nums;
|
|
561
|
+
line-height:1.4;display:flex;flex-direction:column;justify-content:center;gap:2px}
|
|
562
|
+
.heat__c small{display:block;font-size:var(--fs-xs);font-weight:500;color:var(--text-3)}
|
|
563
|
+
.heat__c--1{background:var(--accent-soft);color:var(--accent-text)}
|
|
564
|
+
.heat__c--2{background:var(--accent-soft-2);color:var(--accent-text)}
|
|
565
|
+
.heat__c--3{background:var(--accent);color:var(--accent-on)}
|
|
566
|
+
.heat__c--3 small{color:color-mix(in srgb,var(--accent-on) 70%,transparent)}
|
|
567
|
+
.heat__legend{display:flex;align-items:center;gap:var(--sp-2);margin-top:var(--sp-3);
|
|
568
|
+
font-size:var(--fs-xs);color:var(--text-3)}
|
|
569
|
+
.heat__sw{width:18px;height:10px;border-radius:3px}
|
|
570
|
+
|
|
571
|
+
/* 达成对比:每行 = 标签 + 底槽 + 实际条 + 目标刻度 + 数值(实际/目标) */
|
|
572
|
+
.bul{display:flex;flex-direction:column;gap:var(--sp-4)}
|
|
573
|
+
.bul__row{display:grid;grid-template-columns:minmax(0,2.2fr) minmax(0,5fr) minmax(0,1.6fr);
|
|
574
|
+
align-items:center;gap:var(--sp-4)}
|
|
575
|
+
.bul__k{font-size:var(--fs-sm);color:var(--text);font-weight:600}
|
|
576
|
+
.bul__track{position:relative;height:10px;border-radius:999px;background:var(--surface-2)}
|
|
577
|
+
.bul__fill{position:absolute;top:0;bottom:0;left:0;border-radius:999px;background:var(--accent)}
|
|
578
|
+
.bul__tgt{position:absolute;top:-5px;bottom:-5px;width:2px;background:var(--text-3)}
|
|
579
|
+
.bul__tgt::after{content:"";position:absolute;top:-3px;left:-3px;width:8px;height:8px;
|
|
580
|
+
border-radius:2px;background:var(--text-3);transform:rotate(45deg)}
|
|
581
|
+
.bul__v{font-size:var(--fs-sm);color:var(--text-3);text-align:right;font-variant-numeric:tabular-nums}
|
|
582
|
+
.bul__v b{color:var(--text)}
|
|
583
|
+
.bul__row--warn .bul__fill{background:var(--text-3)}
|
|
584
|
+
|
|
585
|
+
/* 金字塔:自上而下逐层加宽(--w 控制该层宽度占比) */
|
|
586
|
+
.pyr{display:flex;flex-direction:column;gap:var(--sp-2);align-items:center}
|
|
587
|
+
.pyr__lvl{width:var(--w,100%);max-width:100%;background:var(--surface);border:1px solid var(--border-soft);
|
|
588
|
+
border-radius:var(--r-md);padding:var(--sp-3) var(--sp-5);display:grid;
|
|
589
|
+
grid-template-columns:minmax(0,auto) minmax(0,1fr);gap:var(--sp-4);align-items:center}
|
|
590
|
+
.pyr__lvl--a{background:var(--accent-soft);border-color:transparent}
|
|
591
|
+
.pyr__t{font-size:var(--fs-h3);font-weight:600;color:var(--text);white-space:nowrap}
|
|
592
|
+
.pyr__lvl--a .pyr__t{color:var(--accent-text)}
|
|
593
|
+
.pyr__d{font-size:var(--fs-sm);color:var(--text-3);line-height:1.5}
|
|
594
|
+
@media (max-width:960px){.pyr__lvl{width:100%!important;grid-template-columns:1fr;gap:var(--sp-2)}}
|
|
595
|
+
|
|
596
|
+
/* ── 结构图(分层 arch / 泳道 lane / 正交流)──
|
|
597
|
+
布局语法:节点等距、连接件正交带箭头、图例右下;三模式共用(v9 迁入引擎)。 */
|
|
598
|
+
.arch{display:flex;flex-direction:column;gap:0}
|
|
599
|
+
.arch__layer{display:grid;grid-template-columns:120px 1fr;gap:var(--sp-4);align-items:stretch}
|
|
600
|
+
.arch__lname{font-size:var(--fs-xs);font-weight:700;letter-spacing:.06em;color:var(--text-3);
|
|
601
|
+
display:flex;align-items:center;padding-left:12px;border-left:3px solid var(--border-soft)}
|
|
602
|
+
.arch__layer--focus .arch__lname{color:var(--accent-text);border-left-color:var(--accent)}
|
|
603
|
+
.arch__nodes{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--sp-3)}
|
|
604
|
+
.arch__node{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-md);
|
|
605
|
+
padding:var(--sp-3) var(--sp-4);transition:border-color .18s,box-shadow .18s}
|
|
606
|
+
.arch__node:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
|
|
607
|
+
.arch__node--accent{background:var(--accent-soft);border-color:transparent}
|
|
608
|
+
.arch__nt{font-size:var(--fs-sm);font-weight:600;color:var(--text);margin-bottom:2px}
|
|
609
|
+
.arch__nd{font-size:var(--fs-xs);color:var(--text-3);line-height:1.45}
|
|
610
|
+
.arch__legend{display:flex;flex-wrap:wrap;gap:var(--sp-3);justify-content:flex-end;margin-top:var(--sp-4)}
|
|
611
|
+
.arch__conn{display:grid;place-items:center;height:28px;color:var(--text-3);margin-left:120px}
|
|
612
|
+
.arch__conn svg{display:block}
|
|
613
|
+
.lane{display:grid;grid-template-columns:110px 1fr;border:1px solid var(--border-soft);
|
|
614
|
+
border-radius:var(--r-md);overflow:hidden;background:var(--surface)}
|
|
615
|
+
.lane+.lane{margin-top:var(--sp-3)}
|
|
616
|
+
.lane__hd{background:var(--surface-1);padding:var(--sp-3) var(--sp-2);font-size:var(--fs-xs);
|
|
617
|
+
font-weight:700;letter-spacing:.04em;color:var(--text-2);display:flex;align-items:center;
|
|
618
|
+
justify-content:center;text-align:center;border-right:1px solid var(--border-soft)}
|
|
619
|
+
.lane__body{display:flex;align-items:center;gap:0;padding:var(--sp-3);flex-wrap:wrap;row-gap:var(--sp-2)}
|
|
620
|
+
.lane__step{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-sm);
|
|
621
|
+
padding:8px 14px;font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap}
|
|
622
|
+
.lane__step--a{background:var(--accent-soft);border-color:transparent;color:var(--accent-text)}
|
|
623
|
+
/* 步骤间正交箭头(CSS 线段+箭头,替代裸文字 →) */
|
|
624
|
+
.lane__arr{position:relative;flex:none;width:28px;height:12px;color:var(--text-3);margin:0 2px}
|
|
625
|
+
.lane__arr::before{content:"";position:absolute;left:2px;right:8px;top:50%;height:2px;
|
|
626
|
+
background:currentColor;transform:translateY(-50%)}
|
|
627
|
+
.lane__arr::after{content:"";position:absolute;right:2px;top:50%;width:7px;height:7px;
|
|
628
|
+
border-top:2px solid currentColor;border-right:2px solid currentColor;
|
|
629
|
+
transform:translateY(-50%) rotate(45deg)}
|
|
630
|
+
/* 正交流(简单流程骨架):动作盒 + 判断菱形占位 + 主线箭头 */
|
|
631
|
+
.flow-rail{display:flex;align-items:center;gap:0;flex-wrap:wrap;row-gap:var(--sp-3)}
|
|
632
|
+
.flow-box{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-md);
|
|
633
|
+
padding:12px 18px;font-size:var(--fs-sm);font-weight:600;color:var(--text);min-width:100px;text-align:center}
|
|
634
|
+
.flow-box--a{background:var(--accent);border-color:transparent;color:var(--accent-on)}
|
|
635
|
+
.flow-box--end{border-radius:999px}
|
|
636
|
+
.flow-box--ex{background:var(--surface-2);border-style:dashed;color:var(--text-2)}
|
|
637
|
+
.flow-dia{width:72px;height:72px;display:grid;place-items:center;flex:none;position:relative}
|
|
638
|
+
.flow-dia svg{position:absolute;inset:0;width:100%;height:100%}
|
|
639
|
+
.flow-dia span{position:relative;z-index:1;font-size:var(--fs-xs);font-weight:600;color:var(--accent-text);
|
|
640
|
+
text-align:center;line-height:1.25;padding:0 6px}
|
|
641
|
+
@media (max-width:960px){.arch__layer{grid-template-columns:1fr}
|
|
642
|
+
.arch__conn{margin-left:0}.lane{grid-template-columns:1fr}
|
|
643
|
+
.lane__hd{border-right:none;border-bottom:1px solid var(--border-soft)}}
|
|
644
|
+
|
|
645
|
+
/* ── 待核实标注(二次修改强调色体系)──
|
|
646
|
+
.tbd 内联标注:accent 色 + 虚线底(无法核实的数字 / xx% / 待补口径)
|
|
647
|
+
.tbd-legend 页级图例:说明"标色项待核实"(内联标注页必须配一处)
|
|
648
|
+
.flagbar 块级清单条:逐条列出待核实项(数据页/收尾页收口) */
|
|
649
|
+
.tbd{color:var(--accent-text);font-weight:600;border-bottom:1px dashed var(--accent);
|
|
650
|
+
padding-bottom:1px;white-space:nowrap}
|
|
651
|
+
.tbd-legend{display:flex;align-items:flex-start;gap:var(--sp-2);margin-top:var(--sp-4);
|
|
652
|
+
font-size:var(--fs-xs);color:var(--text-3);line-height:1.55}
|
|
653
|
+
.tbd-legend svg{width:14px;height:14px;flex:none;margin-top:.18em;color:var(--accent-text)}
|
|
654
|
+
.tbd-legend b{color:var(--accent-text);font-weight:600}
|
|
655
|
+
.flagbar{margin-top:var(--sp-4);border:1px solid var(--border-soft);border-left:3px solid var(--accent);
|
|
656
|
+
border-radius:var(--r-md);background:var(--accent-soft);padding:var(--sp-3) var(--sp-4)}
|
|
657
|
+
.flagbar__hd{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-xs);
|
|
658
|
+
font-weight:700;letter-spacing:.04em;color:var(--accent-text);margin-bottom:var(--sp-2)}
|
|
659
|
+
.flagbar__hd svg{width:15px;height:15px;flex:none}
|
|
660
|
+
.flagbar ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
|
|
661
|
+
.flagbar li{position:relative;padding-left:16px;font-size:var(--fs-xs);color:var(--text-2);line-height:1.55}
|
|
662
|
+
.flagbar li::before{content:"";position:absolute;left:0;top:.52em;width:6px;height:6px;
|
|
663
|
+
border-radius:2px;background:var(--accent)}
|
|
664
|
+
|
|
665
|
+
/* ── 素材图片 .media(效果与位置锁定:比例 / 裁切 / 圆角 / 蒙版图注 / 全幅)──
|
|
666
|
+
图片源只允许 data: 内联或相对路径(零外链铁律,禁 http(s) 外链图)。
|
|
667
|
+
比例:--r16-9 / --r4-3 / --r3-2 / --r1-1 / --r21-9;--contain 完整显示不裁切;
|
|
668
|
+
--plain 去圆角去边框;--mask 底部蒙版 + 图上白字图注;--full 通栏出血。 */
|
|
669
|
+
.media{position:relative;display:block;border-radius:var(--r-lg);overflow:hidden;
|
|
670
|
+
background:var(--surface-1);border:1px solid var(--border-soft)}
|
|
671
|
+
.media img{display:block;width:100%;height:100%;object-fit:cover}
|
|
672
|
+
.media--r16-9{aspect-ratio:16/9}.media--r4-3{aspect-ratio:4/3}.media--r3-2{aspect-ratio:3/2}
|
|
673
|
+
.media--r1-1{aspect-ratio:1/1}.media--r21-9{aspect-ratio:21/9}.media--r3-1{aspect-ratio:3/1}
|
|
674
|
+
.media--contain img{object-fit:contain}
|
|
675
|
+
.media--plain{border:none;border-radius:0}
|
|
676
|
+
.media--mask::after{content:"";position:absolute;inset:0;z-index:1;
|
|
677
|
+
background:linear-gradient(180deg,transparent 42%,color-mix(in srgb,var(--surface-inv) 64%,transparent))}
|
|
678
|
+
.media__cap{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:var(--sp-4) var(--sp-5);
|
|
679
|
+
color:var(--text-inv);font-size:var(--fs-sm);line-height:1.5}
|
|
680
|
+
.media__cap--below{position:static;color:var(--text-3);padding:var(--sp-3) 0 0;font-size:var(--fs-xs)}
|
|
681
|
+
.media--full{margin-inline:calc(-1 * clamp(20px,3.2vw,56px));border-radius:0;
|
|
682
|
+
border-left:none;border-right:none}
|
|
683
|
+
/* ── 图片版式族(与 PPTX image.layout 一一对应:full/half/bleed/grid/compare/wall)──
|
|
684
|
+
full = 版心全宽(默认,配 --r16-9 / --r4-3 锁比例)
|
|
685
|
+
half = 左图右注(.grid.g-side + .media + 右栏 .stack,见 `components.md` §11c)
|
|
686
|
+
bleed = 通栏出血(--bleed,等价 --full;用于章节大图)
|
|
687
|
+
grid = 多图网格(.media-grid--2/3/4,图下小图注)
|
|
688
|
+
compare = 双图 A/B 对比(.media-compare + .media__tag 角标)
|
|
689
|
+
wall = Logo 墙(.media-wall,等距小图、灰阶弱化)
|
|
690
|
+
占位符 .media--ph = 无图时的原生占位(虚线框 + 图标 + 尺寸提示),零外链、随主题变色。 */
|
|
691
|
+
.media--bleed{margin-inline:calc(-1 * clamp(20px,3.2vw,56px));border-radius:0;
|
|
692
|
+
border-left:none;border-right:none}
|
|
693
|
+
.media--ph{border:1px dashed var(--border);background:var(--surface-1);
|
|
694
|
+
display:grid;place-items:center;text-align:center;gap:var(--sp-2)}
|
|
695
|
+
.media--ph::before{content:"";position:absolute;inset:0;opacity:.5;
|
|
696
|
+
background-image:repeating-linear-gradient(135deg,transparent 0 9px,var(--border-soft) 9px 10px)}
|
|
697
|
+
.media__ph{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;
|
|
698
|
+
gap:6px;padding:var(--sp-4)}
|
|
699
|
+
.media__ph svg{width:28px;height:28px;stroke:var(--accent);fill:none;stroke-width:1.6}
|
|
700
|
+
.media__ph b{font-size:var(--fs-sm);font-weight:600;color:var(--text-2)}
|
|
701
|
+
.media__ph span{font-size:var(--fs-xs);color:var(--text-3)}
|
|
702
|
+
.media-grid{display:grid;gap:clamp(12px,1.6vw,22px);align-items:start}
|
|
703
|
+
.media-grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
704
|
+
.media-grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
705
|
+
.media-grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}
|
|
706
|
+
.media-grid .media__cap--below{text-align:center}
|
|
707
|
+
.media-compare{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
|
|
708
|
+
gap:clamp(16px,2.4vw,40px);align-items:start}
|
|
709
|
+
.media__tag{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:22px;
|
|
710
|
+
padding-inline:8px;border-radius:var(--r-full);background:var(--accent-soft);color:var(--accent-text);
|
|
711
|
+
font-size:var(--fs-xs);font-weight:700;letter-spacing:.04em}
|
|
712
|
+
.media-wall{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));
|
|
713
|
+
gap:clamp(10px,1.4vw,20px);align-items:center}
|
|
714
|
+
.media-wall .media{background:var(--surface-2);filter:grayscale(1);opacity:.82;
|
|
715
|
+
transition:filter var(--dur-short),opacity var(--dur-short)}
|
|
716
|
+
.media-wall .media:hover{filter:none;opacity:1}
|
|
717
|
+
.media__src{margin-top:var(--sp-2);font-size:var(--fs-xs);color:var(--text-3)}
|
|
718
|
+
@media (max-width:1180px){.media-grid--4{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
719
|
+
@media (max-width:760px){.media-grid--3,.media-grid--4,.media-compare{grid-template-columns:1fr}
|
|
720
|
+
.media--bleed{margin-inline:calc(-1 * clamp(16px,4vw,28px))}}
|
|
721
|
+
.fig__cap{text-align:center;margin-bottom:var(--sp-4);font-size:var(--fs-sm);
|
|
722
|
+
color:var(--text-3);font-weight:500}
|
|
723
|
+
.fig svg{display:block;width:100%;height:auto}
|
|
724
|
+
.f-acc{fill:var(--accent)}.f-accs{fill:var(--accent-soft)}.f-accs2{fill:var(--accent-soft-2)}
|
|
725
|
+
.f-s1{fill:var(--surface-1)}.f-s2{fill:var(--surface-2)}.f-s3{fill:var(--surface-3)}
|
|
726
|
+
.f-inv{fill:var(--surface-inv)}
|
|
727
|
+
.f-txt{fill:var(--text)}.f-txt2{fill:var(--text-2)}.f-txt3{fill:var(--text-3)}.f-none{fill:none}
|
|
728
|
+
.s-bd{stroke:var(--border)}.s-bds{stroke:var(--border-soft)}
|
|
729
|
+
.s-acc{stroke:var(--accent)}.s-txt3{stroke:var(--text-3)}
|
|
730
|
+
.t-on-inv{fill:var(--text-inv)}
|
|
731
|
+
/* ── 编码色板(数据系列 / 图例色点 / 小段标签)──
|
|
732
|
+
与结构色纪律正交:结构色(标题/正文/边框/按钮/大面积底色)恒为中性 + 单一强调色;
|
|
733
|
+
编码色只出现在数据本体。**9 套风格各有自己的 c1–c5**(单源 = scripts/layout-constants.json
|
|
734
|
+
的 styleDataColors / styleDataColorsDark;改色值改 JSON,再跑 sync_runtime.py)。
|
|
735
|
+
无 data-style 时回落中性,不串色。 */
|
|
736
|
+
:root{--c1:var(--accent);--c2:var(--surface-3);--c3:var(--border);
|
|
737
|
+
--c4:var(--accent-soft-2);--c5:var(--text-3)}
|
|
738
|
+
html[data-style="business-blue"]{--c1:#1a73e8;--c2:#3526bb;--c3:#21a199;--c4:#b77148;--c5:#99983d}
|
|
739
|
+
html[data-style="business-blue"][data-theme="dark"]{--c1:#8ab4f8;--c2:#7966e0;--c3:#88e7e5;--c4:#d4ad91;--c5:#cbce83}
|
|
740
|
+
html[data-style="apple-mono"]{--c1:#1d1d1f;--c2:#0071e3;--c3:#0f7b6c;--c4:#c2410c;--c5:#7c3aed}
|
|
741
|
+
html[data-style="apple-mono"][data-theme="dark"]{--c1:#f5f5f7;--c2:#2997ff;--c3:#2dd4bf;--c4:#ff9f0a;--c5:#bf5af2}
|
|
742
|
+
html[data-style="mckinsey"]{--c1:#003a70;--c2:#2826bb;--c3:#21a18e;--c4:#b76848;--c5:#99903d}
|
|
743
|
+
html[data-style="mckinsey"][data-theme="dark"]{--c1:#7fb2e5;--c2:#6f6bdb;--c3:#8ce3d8;--c4:#d1a694;--c5:#cbc586}
|
|
744
|
+
html[data-style="brand-red"]{--c1:#d0021b;--c2:#bb6326;--c3:#d634ae;--c4:#40a182;--c5:#438aa8}
|
|
745
|
+
html[data-style="brand-red"][data-theme="dark"]{--c1:#ff5a66;--c2:#e09e66;--c3:#e788cb;--c4:#91d4c2;--c5:#83b4ce}
|
|
746
|
+
html[data-style="warm-sand"]{--c1:#96681f;--c2:#839324;--c3:#cf3b3e;--c4:#4d88b2;--c5:#4a47a4}
|
|
747
|
+
html[data-style="warm-sand"][data-theme="dark"]{--c1:#d9b36a;--c2:#c2d571;--c3:#df9391;--c4:#97b5ce;--c5:#8e89c7}
|
|
748
|
+
html[data-style="deep-teal"]{--c1:#0f6b5c;--c2:#2683bb;--c3:#22aa3e;--c4:#b74870;--c5:#a85c43}
|
|
749
|
+
html[data-style="deep-teal"][data-theme="dark"]{--c1:#4fc3b0;--c2:#7aaecd;--c3:#97d8a4;--c4:#c99cac;--c5:#c29c8f}
|
|
750
|
+
html[data-style="graphite-dark"]{--c1:#0369a1;--c2:#2637bb;--c3:#1f9977;--c4:#b75948;--c5:#a18a40}
|
|
751
|
+
html[data-style="graphite-dark"][data-theme="dark"]{--c1:#4da3ff;--c2:#6c66e0;--c3:#88e7dc;--c4:#d4a691;--c5:#cec983}
|
|
752
|
+
html[data-style="indigo-violet"]{--c1:#5b5bd6;--c2:#7432ae;--c3:#4197c8;--c4:#a08f4b;--c5:#7ca04b}
|
|
753
|
+
html[data-style="indigo-violet"][data-theme="dark"]{--c1:#a5a4f0;--c2:#a967e0;--c3:#89c3e7;--c4:#d3c792;--c5:#aece83}
|
|
754
|
+
html[data-style="spectrum"]{--c1:#4563ef;--c2:#5b26bb;--c3:#269ebb;--c4:#af8746;--c5:#83993d}
|
|
755
|
+
html[data-style="spectrum"][data-theme="dark"]{--c1:#8ba0f8;--c2:#9066e0;--c3:#88d6e7;--c4:#d4ba91;--c5:#bdce83}
|
|
756
|
+
.f-c1{fill:var(--c1)}.f-c2{fill:var(--c2)}.f-c3{fill:var(--c3)}.f-c4{fill:var(--c4)}.f-c5{fill:var(--c5)}
|
|
757
|
+
.s-c1{stroke:var(--c1)}.s-c2{stroke:var(--c2)}.s-c3{stroke:var(--c3)}.s-c4{stroke:var(--c4)}.s-c5{stroke:var(--c5)}
|
|
758
|
+
.shead{margin-bottom:clamp(28px,3.4vh,48px);max-width:940px}
|
|
759
|
+
.shead__title{margin-block:var(--sp-3) var(--sp-4)}
|
|
760
|
+
.shead__desc{max-width:900px}
|
|
761
|
+
.shead--center{margin-inline:auto;text-align:center}
|
|
762
|
+
.shead--center .shead__desc{margin-inline:auto}
|
|
763
|
+
.foot{padding-block:clamp(40px,5vh,64px);border-top:1px solid var(--border-soft);
|
|
764
|
+
background:var(--surface-1)}
|
|
765
|
+
.foot__in{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-5);flex-wrap:wrap}
|
|
766
|
+
@media (prefers-reduced-motion:no-preference){.rv{opacity:0;transform:translateY(14px)}
|
|
767
|
+
.rv.in{opacity:1;transform:none;transition:opacity var(--dur-long) var(--ease-emphasized),
|
|
768
|
+
transform var(--dur-long) var(--ease-emphasized)}}
|
|
769
|
+
@media (prefers-reduced-motion:reduce){.rv{opacity:1;transform:none}html{scroll-behavior:auto}}
|
|
770
|
+
/* ── 图表轻量动效(进入视口触发;JS 加 .in) ── */
|
|
771
|
+
[data-anim]{transform-box:fill-box}
|
|
772
|
+
.chart rect[data-anim="grow"]{transform:scaleY(0);transform-origin:center bottom;
|
|
773
|
+
transition:transform var(--dur-xlong) var(--ease-emphasized)}
|
|
774
|
+
.chart.in rect[data-anim="grow"]{transform:scaleY(1)}
|
|
775
|
+
.chart--x rect[data-anim="grow"]{transform:scaleX(0);transform-origin:left center}
|
|
776
|
+
.chart--x.in rect[data-anim="grow"]{transform:scaleX(1)}
|
|
777
|
+
.chart [data-anim="fade"]{opacity:0;transition:opacity var(--dur-long) ease var(--dur-short)}
|
|
778
|
+
.chart.in [data-anim="fade"]{opacity:1}
|
|
779
|
+
@media (prefers-reduced-motion:reduce){[data-anim]{transition:none!important;transform:none!important;opacity:1!important}}
|
|
780
|
+
/* 降级总闸:前面的 reduce 块只管入场动效,hover 的位移/缩放仍会触发前庭变换 */
|
|
781
|
+
@media (prefers-reduced-motion:reduce){
|
|
782
|
+
*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important}
|
|
783
|
+
.btn:hover,.pager__dot:hover,.card:hover,.metric:hover{transform:none!important}
|
|
784
|
+
}
|
|
785
|
+
/* ── Agenda 大纲页 ── */
|
|
786
|
+
.agenda{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
|
|
787
|
+
.agenda__i{border-top:1px solid var(--border-soft)}
|
|
788
|
+
.agenda__i:last-child{border-bottom:1px solid var(--border-soft)}
|
|
789
|
+
.agenda__a{display:grid;grid-template-columns:auto 1fr auto;align-items:center;
|
|
790
|
+
gap:clamp(16px,2.4vw,36px);padding:clamp(18px,2.2vh,26px) clamp(4px,.6vw,12px);
|
|
791
|
+
text-decoration:none;color:inherit;transition:background-color var(--dur-short)}
|
|
792
|
+
.agenda__a:hover{background:var(--surface-1)}
|
|
793
|
+
.agenda__n{font-size:clamp(1.4rem,2vw,1.9rem);font-weight:var(--fw-display);
|
|
794
|
+
color:var(--accent);font-variant-numeric:tabular-nums;line-height:1;min-width:2.2em}
|
|
795
|
+
.agenda__t{font-size:var(--fs-h3);font-weight:var(--fw-title);color:var(--text);margin-bottom:3px}
|
|
796
|
+
.agenda__d{font-size:var(--fs-sm);color:var(--text-3)}
|
|
797
|
+
.agenda__go{color:var(--text-3);transition:color var(--dur-short),transform var(--dur-short)}
|
|
798
|
+
.agenda__a:hover .agenda__go{color:var(--accent);transform:translateX(4px)}
|
|
799
|
+
.agenda__go svg{width:20px;height:20px;display:block}
|
|
800
|
+
@media (min-width:900px){
|
|
801
|
+
.agenda--2col{display:grid;grid-template-columns:1fr 1fr;column-gap:clamp(20px,3vw,48px)}
|
|
802
|
+
.agenda--2col .agenda__i{border-top:1px solid var(--border-soft)}}
|
|
803
|
+
/* ── 页码指示(翻页导航) ── */
|
|
804
|
+
.pager{position:fixed;right:clamp(10px,1.6vw,26px);top:50%;transform:translateY(-50%);
|
|
805
|
+
z-index:60;display:flex;flex-direction:column;align-items:center;gap:10px}
|
|
806
|
+
.pager__count{font-size:var(--fs-xs);font-weight:600;color:var(--text-3);
|
|
807
|
+
font-variant-numeric:tabular-nums;letter-spacing:.04em}
|
|
808
|
+
.pager__dots{display:flex;flex-direction:column;gap:8px}
|
|
809
|
+
.pager__dot{width:9px;height:9px;border-radius:50%;border:1px solid var(--border);
|
|
810
|
+
background:transparent;cursor:pointer;padding:0;transition:background-color var(--dur-short),transform var(--dur-short),border-color var(--dur-short)}
|
|
811
|
+
.pager__dot:hover{transform:scale(1.3);border-color:var(--accent)}
|
|
812
|
+
.pager__dot.on{background:var(--accent);border-color:var(--accent);transform:scale(1.25)}
|
|
813
|
+
@media (max-width:760px){.pager{right:6px;gap:6px}.pager__dot{width:7px;height:7px}}
|
|
814
|
+
@media (prefers-reduced-motion:reduce){.pager__dot{transition:none}}
|
|
815
|
+
/* ── PPTX 预览/导出/帮助 模态(预览与导出同一引擎,所见即所得;帮助为双通道指引) ── */
|
|
816
|
+
.pmodal{position:fixed;inset:0;z-index:90;display:none;align-items:center;justify-content:center;
|
|
817
|
+
background:color-mix(in srgb,var(--surface-inv) 55%,transparent);
|
|
818
|
+
backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
|
|
819
|
+
.pmodal.open{display:flex}
|
|
820
|
+
.pmodal__panel{width:min(1060px,94vw);max-height:90vh;display:flex;flex-direction:column;
|
|
821
|
+
background:var(--surface);border:1px solid var(--border);border-radius:var(--r-xl);
|
|
822
|
+
box-shadow:var(--shadow-2);overflow:hidden}
|
|
823
|
+
.pmodal__hd{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);
|
|
824
|
+
padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--border-soft)}
|
|
825
|
+
.pmodal__t{font-size:var(--fs-h3);font-weight:600;color:var(--text)}
|
|
826
|
+
.pmodal__meta{font-size:var(--fs-xs);color:var(--text-3);margin-top:2px}
|
|
827
|
+
.pmodal__body{overflow:auto;padding:var(--sp-5);display:grid;
|
|
828
|
+
grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:var(--sp-4)}
|
|
829
|
+
.pslide{position:relative;width:100%;aspect-ratio:16/9;border:1px solid var(--border-soft);
|
|
830
|
+
border-radius:var(--r-sm);background:var(--bg);overflow:hidden;font-size:7px; /* JS 按宽度重设:1em=1pt;底色随主题(与导出的亮/暗版一致) */ }
|
|
831
|
+
.pslide__no{position:absolute;left:8px;bottom:5px;z-index:5;font-size:1.4em;font-weight:600;
|
|
832
|
+
color:var(--text-3);letter-spacing:.04em}
|
|
833
|
+
.pslide [data-el]{position:absolute;overflow:hidden}
|
|
834
|
+
.pslide [data-el] p{margin:0}
|
|
835
|
+
.pmodal__ft{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-4);
|
|
836
|
+
padding:var(--sp-4) var(--sp-5);border-top:1px solid var(--border-soft);flex-wrap:wrap}
|
|
837
|
+
.pmodal__ft .row{flex:none}
|
|
838
|
+
.pmodal__issues{flex:1;min-width:280px;font-size:var(--fs-xs);color:var(--text-2);line-height:1.6}
|
|
839
|
+
.pmodal__issues b{color:var(--accent-text)}
|
|
840
|
+
.pmodal__issues .btn{height:40px;padding-inline:18px}
|
|
841
|
+
.pmodal__prompt{width:100%;margin-top:var(--sp-3);padding:var(--sp-3);border:1px dashed var(--border);
|
|
842
|
+
border-radius:var(--r-md);background:var(--surface-1);
|
|
843
|
+
font-family:ui-monospace,Consolas,"Courier New",monospace;font-size:11px;line-height:1.65;
|
|
844
|
+
color:var(--text-2);white-space:pre-wrap;max-height:150px;overflow:auto}
|
|
845
|
+
/* 帮助模态(复用 pmodal 骨架,文档式单列) */
|
|
846
|
+
.pmodal__panel--doc{width:min(860px,94vw)}
|
|
847
|
+
.pmodal__panel--doc .pmodal__body{display:block}
|
|
848
|
+
.hdoc{font-size:var(--fs-sm);color:var(--text-2);line-height:1.75}
|
|
849
|
+
.hdoc h4{font-size:var(--fs-h3);color:var(--text);margin:var(--sp-5) 0 var(--sp-2)}
|
|
850
|
+
.hdoc h4:first-child{margin-top:0}
|
|
851
|
+
.hdoc p{margin-bottom:var(--sp-3)}
|
|
852
|
+
.hdoc b{color:var(--text)}
|
|
853
|
+
.hdoc .hch{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--sp-4);
|
|
854
|
+
margin:var(--sp-4) 0}
|
|
855
|
+
.hdoc .hch>div{background:var(--surface-1);border:1px solid var(--border-soft);
|
|
856
|
+
border-radius:var(--r-md);padding:var(--sp-4)}
|
|
857
|
+
.hdoc .hch h5{font-size:var(--fs-sm);font-weight:700;color:var(--accent-text);margin:0 0 var(--sp-2)}
|
|
858
|
+
.hdoc .hch p{font-size:var(--fs-xs);margin:0}
|
|
859
|
+
.hdoc code{font-family:ui-monospace,Consolas,"Courier New",monospace;font-size:var(--fs-xs);
|
|
860
|
+
background:var(--surface-2);border-radius:6px;padding:2px 7px;color:var(--text)}
|
|
861
|
+
@media print{.pmodal{display:none!important}}
|
|
862
|
+
@media print{.rv{opacity:1!important;transform:none!important}
|
|
863
|
+
.band{padding-block:24px;break-inside:avoid}body{background:#fff;color:#000}
|
|
864
|
+
.pager{display:none!important}}
|
|
865
|
+
/* __TOPPPT_ENGINE_END__ */
|
|
866
|
+
|
|
867
|
+
/* ── 演示模式专属层(本模板即 presentation 形态:版心 1400 / 大字号 / 大留白;无模式切换) ── */
|
|
868
|
+
:root{--band-pad:clamp(56px,8.5vh,116px)}
|
|
869
|
+
</style>
|
|
870
|
+
</head>
|
|
871
|
+
<body>
|
|
872
|
+
|
|
873
|
+
<!-- ══ 顶栏 + header 工具组(风格 / 预览 / 帮助 / 全屏 / 主题 / 折叠) ══ -->
|
|
874
|
+
<header class="bar">
|
|
875
|
+
<div class="wrap bar__in">
|
|
876
|
+
<div class="brand">
|
|
877
|
+
<div class="brand__mark">R</div>
|
|
878
|
+
<div class="brand__txt">
|
|
879
|
+
<div class="brand__title">主标题</div>
|
|
880
|
+
<div class="brand__sub">副标题 / 专题名</div>
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
<nav class="nav">
|
|
884
|
+
<a href="#s1">章节一</a>
|
|
885
|
+
<a href="#s2">章节二</a>
|
|
886
|
+
<a href="#refs">参考资料</a>
|
|
887
|
+
</nav>
|
|
888
|
+
<div class="toolsbar">
|
|
889
|
+
<div class="seld" id="styleSel">
|
|
890
|
+
<button class="iconbtn" id="styleBtn" type="button" aria-label="切换风格" title="风格(9 套实时切换 · 纯视觉皮肤不动内容)">
|
|
891
|
+
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
892
|
+
<circle cx="13.5" cy="6.5" r="1.2"/><circle cx="17.5" cy="10.5" r="1.2"/>
|
|
893
|
+
<circle cx="8.5" cy="7.5" r="1.2"/><circle cx="6.5" cy="12.5" r="1.2"/>
|
|
894
|
+
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.93 0 1.65-.75 1.65-1.69 0-.44-.18-.84-.44-1.13-.29-.29-.44-.65-.44-1.12a1.64 1.64 0 0 1 1.67-1.67h2c3.05 0 5.56-2.5 5.56-5.55C22 6.01 17.46 2 12 2z" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
|
|
895
|
+
</button>
|
|
896
|
+
<div class="seld__pop" id="stylePop" role="menu" aria-label="风格选择">
|
|
897
|
+
<div class="seld__lab">风格 · 9 套</div>
|
|
898
|
+
<div class="seld__list" id="styleList"></div>
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
<button class="iconbtn" id="pptPreviewBtn" type="button" aria-label="预览 PPTX" title="预览 PPTX(P)· 与智能体精导同一引擎,所见即所得">
|
|
902
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
903
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
904
|
+
</button>
|
|
905
|
+
<button class="iconbtn" id="helpBtn" type="button" aria-label="PPT 生成指引" title="PPT 生成指引(H)· 精导通道说明与提示词">
|
|
906
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
907
|
+
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
|
|
908
|
+
</button>
|
|
909
|
+
<button class="iconbtn" id="fsBtn" type="button" aria-label="全屏" title="全屏(F)">
|
|
910
|
+
<svg class="icon-fsx" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
|
|
911
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
912
|
+
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
|
|
913
|
+
<svg class="icon-fsx-exit" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
|
|
914
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
915
|
+
<path d="M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7"/></svg>
|
|
916
|
+
</button>
|
|
917
|
+
<button class="iconbtn" id="themeBtn" type="button" aria-label="切换亮/暗主题" title="切换亮/暗主题(T)">
|
|
918
|
+
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
|
|
919
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
920
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
|
|
921
|
+
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
|
|
922
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
923
|
+
<circle cx="12" cy="12" r="4.2"/>
|
|
924
|
+
<path d="M12 2.4v2.2M12 19.4v2.2M4.2 12H2M22 12h-2.2M6.3 6.3 4.8 4.8
|
|
925
|
+
M19.2 19.2l-1.5-1.5M17.7 6.3l1.5-1.5M4.8 19.2l1.5-1.5"/></svg>
|
|
926
|
+
</button>
|
|
927
|
+
<button class="iconbtn" id="barFold" type="button" aria-label="收起/展开工具栏" title="收起/展开工具栏(B)">
|
|
928
|
+
<svg class="icon-fold" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
|
|
929
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
930
|
+
<path d="M18 15l-6-6-6 6"/></svg>
|
|
931
|
+
<svg class="icon-unfold" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
|
|
932
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
933
|
+
<path d="M6 9l6 6 6-6"/></svg>
|
|
934
|
+
</button>
|
|
935
|
+
</div>
|
|
936
|
+
</div>
|
|
937
|
+
</header>
|
|
938
|
+
|
|
939
|
+
<!-- __TOPPPT_CONTENT_START__ -->
|
|
940
|
+
<!-- ═══════════════════════════ 正文区(演示模式骨架 · build_examples.py 整体替换此区域) ═══════════════════════════
|
|
941
|
+
可用页型(HTML 版式 ↔ PPTX 页型同源,完整代码见 references/`components.md` §46/§46b):
|
|
942
|
+
封面 / Agenda / points 要点卡 / metrics 指标带 / kpi 大数 / table 表 / timeline 时间线 /
|
|
943
|
+
steps 步骤条 / bar·donut 图表 / heatmap 热力矩阵 / bullet 达成对比 / pyramid 金字塔 /
|
|
944
|
+
cards 卡片网格 / split 左文右图 / comparison 对比 / quote 金句 / diagram 分层架构 / 收尾 / 参考资料 -->
|
|
945
|
+
|
|
946
|
+
<!-- ══ Hero(16:9 满屏页 band--fit) ══ -->
|
|
947
|
+
<section class="band band--fit">
|
|
948
|
+
<div class="wrap">
|
|
949
|
+
<div class="grid g-hero" style="gap:clamp(32px,4vw,72px)">
|
|
950
|
+
<div class="stack gap-5 rv">
|
|
951
|
+
<div class="row row-wrap">
|
|
952
|
+
<span class="chip chip--accent">标签</span>
|
|
953
|
+
<span class="chip chip--line">标签二</span>
|
|
954
|
+
</div>
|
|
955
|
+
<h1 class="t-display">主标题<br>第二行</h1>
|
|
956
|
+
<p class="t-lead" style="max-width:620px">一句话主张 + 补充说明。</p>
|
|
957
|
+
<div class="row row-wrap" style="gap:var(--sp-3)">
|
|
958
|
+
<a class="btn btn--primary" href="#agenda">开始阅读</a>
|
|
959
|
+
<a class="btn btn--ghost" href="#refs">参考资料</a>
|
|
960
|
+
</div>
|
|
961
|
+
<div class="row row-wrap" style="gap:var(--sp-6);margin-top:var(--sp-3)">
|
|
962
|
+
<div>
|
|
963
|
+
<div class="t-metric" style="color:var(--accent)">50%</div>
|
|
964
|
+
<div class="t-xs" style="margin-top:4px">数字一注解</div>
|
|
965
|
+
</div>
|
|
966
|
+
<div>
|
|
967
|
+
<div class="t-metric">62<span style="font-size:.45em"> TB</span></div>
|
|
968
|
+
<div class="t-xs" style="margin-top:4px">数字二注解</div>
|
|
969
|
+
</div>
|
|
970
|
+
</div>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="g-hero__visual rv"><!-- 内联 SVG 主视觉 --></div>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
</section>
|
|
976
|
+
|
|
977
|
+
<!-- ══ Agenda 大纲页(必为第二页;演示模式 8–15 页) ══ -->
|
|
978
|
+
<section class="band band--tint" id="agenda">
|
|
979
|
+
<div class="wrap">
|
|
980
|
+
<div class="shead rv">
|
|
981
|
+
<div class="t-eyebrow">Agenda</div>
|
|
982
|
+
<h2 class="t-h1 shead__title">报告大纲</h2>
|
|
983
|
+
<p class="t-lead shead__desc">点击任意条目跳转对应章节。</p>
|
|
984
|
+
</div>
|
|
985
|
+
<ol class="agenda rv">
|
|
986
|
+
<li class="agenda__i"><a class="agenda__a" href="#s1">
|
|
987
|
+
<span class="agenda__n">01</span>
|
|
988
|
+
<span><span class="agenda__t">章节一标题</span>
|
|
989
|
+
<div class="agenda__d">本章一句话说明</div></span>
|
|
990
|
+
<span class="agenda__go"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
|
991
|
+
</a></li>
|
|
992
|
+
<li class="agenda__i"><a class="agenda__a" href="#s2">
|
|
993
|
+
<span class="agenda__n">02</span>
|
|
994
|
+
<span><span class="agenda__t">章节二标题</span>
|
|
995
|
+
<div class="agenda__d">本章一句话说明</div></span>
|
|
996
|
+
<span class="agenda__go"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
|
997
|
+
</a></li>
|
|
998
|
+
<li class="agenda__i"><a class="agenda__a" href="#s3">
|
|
999
|
+
<span class="agenda__n">03</span>
|
|
1000
|
+
<span><span class="agenda__t">章节三标题</span>
|
|
1001
|
+
<div class="agenda__d">本章一句话说明</div></span>
|
|
1002
|
+
<span class="agenda__go"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
|
1003
|
+
</a></li>
|
|
1004
|
+
<li class="agenda__i"><a class="agenda__a" href="#refs">
|
|
1005
|
+
<span class="agenda__n">04</span>
|
|
1006
|
+
<span><span class="agenda__t">参考资料</span>
|
|
1007
|
+
<div class="agenda__d">数据来源与口径说明</div></span>
|
|
1008
|
+
<span class="agenda__go"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
|
1009
|
+
</a></li>
|
|
1010
|
+
</ol>
|
|
1011
|
+
</div>
|
|
1012
|
+
</section>
|
|
1013
|
+
|
|
1014
|
+
<!-- ══ 章节一(要点卡 + 指标行) ══ -->
|
|
1015
|
+
<section class="band" id="s1">
|
|
1016
|
+
<div class="wrap">
|
|
1017
|
+
<div class="shead rv">
|
|
1018
|
+
<div class="t-eyebrow">01 · 关键词</div>
|
|
1019
|
+
<h2 class="t-h1 shead__title">一句主张</h2>
|
|
1020
|
+
<p class="t-lead shead__desc">本章回答什么问题。</p>
|
|
1021
|
+
</div>
|
|
1022
|
+
<div class="grid g-3 rv">
|
|
1023
|
+
<div class="card">
|
|
1024
|
+
<div class="card__hd"><div class="card__ico">01</div><h3 class="t-h3">要点</h3></div>
|
|
1025
|
+
<ul class="ul">
|
|
1026
|
+
<li><strong>小标题。</strong>一句话说明。</li>
|
|
1027
|
+
</ul>
|
|
1028
|
+
</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
</div>
|
|
1031
|
+
</section>
|
|
1032
|
+
|
|
1033
|
+
<!-- ══ 章节二(表格) ══ -->
|
|
1034
|
+
<section class="band" id="s2">
|
|
1035
|
+
<div class="wrap">
|
|
1036
|
+
<div class="shead rv">
|
|
1037
|
+
<div class="t-eyebrow">02 · 关键词</div>
|
|
1038
|
+
<h2 class="t-h1 shead__title">一句主张</h2>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="tbl-wrap rv">
|
|
1041
|
+
<table>
|
|
1042
|
+
<thead><tr><th style="width:20%">维度</th><th style="width:40%">现状</th><th>目标</th></tr></thead>
|
|
1043
|
+
<tbody>
|
|
1044
|
+
<tr><td class="k">示例</td><td>现状描述</td>
|
|
1045
|
+
<td><strong>目标值</strong><a class="cite" href="#ref-1">[1]</a></td></tr>
|
|
1046
|
+
<tr><td class="k">示例二</td><td>现状描述</td>
|
|
1047
|
+
<td><strong>目标值</strong><a class="cite" href="#ref-2">[2]</a></td></tr>
|
|
1048
|
+
</tbody>
|
|
1049
|
+
</table>
|
|
1050
|
+
</div>
|
|
1051
|
+
</div>
|
|
1052
|
+
</section>
|
|
1053
|
+
|
|
1054
|
+
<!-- ══ 章节三(图表示例:柱状 + 动效 + data-chart 标记) ══ -->
|
|
1055
|
+
<section class="band" id="s3">
|
|
1056
|
+
<div class="wrap">
|
|
1057
|
+
<div class="shead rv">
|
|
1058
|
+
<div class="t-eyebrow">03 · 关键词</div>
|
|
1059
|
+
<h2 class="t-h1 shead__title">一句主张</h2>
|
|
1060
|
+
<p class="t-lead shead__desc">本章回答什么问题。</p>
|
|
1061
|
+
</div>
|
|
1062
|
+
<div class="grid g-side rv" style="align-items:center">
|
|
1063
|
+
<div class="fig">
|
|
1064
|
+
<div class="fig__cap">图表示例(更多图表见 references/components.md)</div>
|
|
1065
|
+
<svg class="chart" data-chart="bar" viewBox="0 0 560 220">
|
|
1066
|
+
<line x1="46" y1="170" x2="540" y2="170" class="s-bds" stroke-width="1"/>
|
|
1067
|
+
<rect data-anim="grow" x="60" y="131" width="58" height="39" rx="6" class="f-s3"/>
|
|
1068
|
+
<rect data-anim="grow" x="188" y="92" width="58" height="78" rx="6" class="f-s3"/>
|
|
1069
|
+
<rect data-anim="grow" x="316" y="60" width="58" height="110" rx="6" class="f-acc"/>
|
|
1070
|
+
<rect data-anim="grow" x="444" y="53" width="58" height="117" rx="6" class="f-acc"/>
|
|
1071
|
+
<text data-anim="fade" x="89" y="192" text-anchor="middle" class="f-txt3" font-size="11">Q3</text>
|
|
1072
|
+
<text data-anim="fade" x="217" y="192" text-anchor="middle" class="f-txt3" font-size="11">Q4</text>
|
|
1073
|
+
<text data-anim="fade" x="345" y="192" text-anchor="middle" class="f-txt3" font-size="11">2027Q1</text>
|
|
1074
|
+
<text data-anim="fade" x="473" y="192" text-anchor="middle" class="f-txt3" font-size="11">Q2</text>
|
|
1075
|
+
</svg>
|
|
1076
|
+
</div>
|
|
1077
|
+
<div class="stack gap-4">
|
|
1078
|
+
<h3 class="t-h3">怎么读这张图</h3>
|
|
1079
|
+
<ul class="ul">
|
|
1080
|
+
<li><strong>结论一。</strong>一句话解读。</li>
|
|
1081
|
+
<li><strong>结论二。</strong>一句话解读。</li>
|
|
1082
|
+
</ul>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
</section>
|
|
1087
|
+
|
|
1088
|
+
<!-- ══ 金句 / 大引言页(节奏休止 · 全文 1–2 处即可 · 主题一致强调带) ══
|
|
1089
|
+
默认 band--accent(accent-soft 底,亮/暗同向);要更强的强调可改 band--accent--solid
|
|
1090
|
+
(风格强调色实底,文字走 --accent-on);确需反相深色页才用 band--deep——浅色模式下会渲染为
|
|
1091
|
+
深色,全文限 1 处且不得作末页(校验器检查)。
|
|
1092
|
+
<section class="band band--accent band--fit" id="quote-1">
|
|
1093
|
+
<div class="wrap">
|
|
1094
|
+
<div class="shead shead--center rv">
|
|
1095
|
+
<div class="t-eyebrow">0X · 关键判断</div>
|
|
1096
|
+
<h2 class="t-h1 shead__title">一句金句主张</h2>
|
|
1097
|
+
<p class="t-lead shead__desc">本章结论的一句话浓缩</p>
|
|
1098
|
+
</div>
|
|
1099
|
+
</div>
|
|
1100
|
+
</section>
|
|
1101
|
+
-->
|
|
1102
|
+
|
|
1103
|
+
<!-- ══ 收尾章(band--accent 主题一致强调带——浅色模式不出现深色页) ══ -->
|
|
1104
|
+
<section class="band band--accent band--fit" id="next">
|
|
1105
|
+
<div class="wrap">
|
|
1106
|
+
<div class="shead shead--center rv">
|
|
1107
|
+
<div class="t-eyebrow">下一步</div>
|
|
1108
|
+
<h2 class="t-h1 shead__title">主张</h2>
|
|
1109
|
+
<p class="t-lead shead__desc">说明。</p>
|
|
1110
|
+
</div>
|
|
1111
|
+
<div class="grid g-3 rv">
|
|
1112
|
+
<div class="card">
|
|
1113
|
+
<h3 class="t-h3" style="margin-bottom:var(--sp-2)">要点</h3>
|
|
1114
|
+
<p class="t-body">一句话。</p>
|
|
1115
|
+
</div>
|
|
1116
|
+
</div>
|
|
1117
|
+
<div class="row" style="justify-content:center;gap:var(--sp-3);
|
|
1118
|
+
margin-top:clamp(28px,3.4vh,44px);flex-wrap:wrap">
|
|
1119
|
+
<a class="btn btn--primary" href="#s1">返回查看</a>
|
|
1120
|
+
<a class="btn btn--ghost" href="#refs">参考资料</a>
|
|
1121
|
+
</div>
|
|
1122
|
+
</div>
|
|
1123
|
+
</section>
|
|
1124
|
+
|
|
1125
|
+
<!-- ══ 参考资料(外部数据必标 [n],与正文 cite 双向对齐) ══ -->
|
|
1126
|
+
<section class="band band--tint" id="refs">
|
|
1127
|
+
<div class="wrap">
|
|
1128
|
+
<div class="shead rv">
|
|
1129
|
+
<div class="t-eyebrow">参考资料</div>
|
|
1130
|
+
<h2 class="t-h1 shead__title">数据来源与口径说明</h2>
|
|
1131
|
+
</div>
|
|
1132
|
+
<div class="tbl-wrap rv">
|
|
1133
|
+
<table>
|
|
1134
|
+
<thead><tr><th style="width:6%">编号</th><th style="width:26%">来源</th>
|
|
1135
|
+
<th style="width:14%">时间</th><th>口径说明</th></tr></thead>
|
|
1136
|
+
<tbody>
|
|
1137
|
+
<tr id="ref-1"><td class="k">[1]</td>
|
|
1138
|
+
<td><a class="ref-link" href="https://example.com" target="_blank" rel="noopener">来源名称
|
|
1139
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></a></td>
|
|
1140
|
+
<td>YYYY-MM</td><td>用于哪些数据;口径说明</td></tr>
|
|
1141
|
+
<tr id="ref-2"><td class="k">[2]</td><td>无链接的内部来源(纯文本)</td>
|
|
1142
|
+
<td>YYYY-MM</td><td>内部实测口径</td></tr>
|
|
1143
|
+
</tbody>
|
|
1144
|
+
</table>
|
|
1145
|
+
</div>
|
|
1146
|
+
</div>
|
|
1147
|
+
</section>
|
|
1148
|
+
|
|
1149
|
+
<!-- ══ 页脚 ══ -->
|
|
1150
|
+
<footer class="foot">
|
|
1151
|
+
<div class="wrap foot__in">
|
|
1152
|
+
<div class="brand">
|
|
1153
|
+
<div class="brand__mark">R</div>
|
|
1154
|
+
<div class="brand__txt">
|
|
1155
|
+
<div class="brand__title">报告名</div>
|
|
1156
|
+
<div class="brand__sub">副标题 · YYYY-MM-DD</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
<div class="t-xs">密级说明</div>
|
|
1160
|
+
</div>
|
|
1161
|
+
</footer>
|
|
1162
|
+
<!-- __TOPPPT_CONTENT_END__ -->
|
|
1163
|
+
|
|
1164
|
+
<!-- ══ 页码指示(翻页导航) ══ -->
|
|
1165
|
+
<aside class="pager" id="pager" aria-label="页码导航">
|
|
1166
|
+
<div class="pager__count" id="pagerCount">1 / 1</div>
|
|
1167
|
+
<div class="pager__dots" id="pagerDots"></div>
|
|
1168
|
+
</aside>
|
|
1169
|
+
|
|
1170
|
+
<!-- ══ PPTX 预览模态(与智能体精导同一引擎,所见即所得 + 可复制 AI 提示词) ══ -->
|
|
1171
|
+
<div class="pmodal" id="pptModal" role="dialog" aria-modal="true" aria-label="PPTX 预览">
|
|
1172
|
+
<div class="pmodal__panel">
|
|
1173
|
+
<div class="pmodal__hd">
|
|
1174
|
+
<div>
|
|
1175
|
+
<div class="pmodal__t">PPTX 预览</div>
|
|
1176
|
+
<div class="pmodal__meta" id="pptMeta">—</div>
|
|
1177
|
+
</div>
|
|
1178
|
+
<button class="iconbtn" id="pptClose" type="button" aria-label="关闭预览" title="关闭(Esc)">
|
|
1179
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
1180
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
1181
|
+
<path d="M18 6 6 18M6 6l12 12"/></svg>
|
|
1182
|
+
</button>
|
|
1183
|
+
</div>
|
|
1184
|
+
<div class="pmodal__body" id="pptBody"><!-- 页序列缩略(JS 渲染,同导出引擎) --></div>
|
|
1185
|
+
<div class="pmodal__ft">
|
|
1186
|
+
<div class="pmodal__issues" id="pptIssues">—</div>
|
|
1187
|
+
<div class="row" style="gap:var(--sp-3)">
|
|
1188
|
+
<button class="btn btn--primary" id="pptCopy" type="button" style="height:40px;padding-inline:18px">复制 AI 提示词</button>
|
|
1189
|
+
</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
</div>
|
|
1192
|
+
</div>
|
|
1193
|
+
|
|
1194
|
+
<!-- ══ PPT 生成指引(帮助模态:精导通道说明 + 可复制提示词) ══ -->
|
|
1195
|
+
<div class="pmodal" id="helpModal" role="dialog" aria-modal="true" aria-label="PPT 生成指引">
|
|
1196
|
+
<div class="pmodal__panel pmodal__panel--doc">
|
|
1197
|
+
<div class="pmodal__hd">
|
|
1198
|
+
<div>
|
|
1199
|
+
<div class="pmodal__t">PPT 生成指引</div>
|
|
1200
|
+
<div class="pmodal__meta">智能体精导 · 与页面同源 · 16:9 可编辑原生形状</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
<button class="iconbtn" id="helpClose" type="button" aria-label="关闭指引" title="关闭(Esc)">
|
|
1203
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
1204
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
1205
|
+
<path d="M18 6 6 18M6 6l12 12"/></svg>
|
|
1206
|
+
</button>
|
|
1207
|
+
</div>
|
|
1208
|
+
<div class="pmodal__body">
|
|
1209
|
+
<div class="hdoc">
|
|
1210
|
+
<h4>HTML 与 PPTX 的分工</h4>
|
|
1211
|
+
<p>HTML 是阅读与演示界面,PPTX 是协作与修改格式——两者来自同一内容模型(window.REPORT_MODEL),PPTX 内全部是原生形状、文本框、表格(<code>pictures=0</code>),可在 PowerPoint / WPS 中继续编辑;<b>带数据的图表为原生数据图表</b>(chart 对象 + 内嵌 Excel,双击即可"编辑数据");口径、来源与 so-what 自动写入<b>演讲者备注</b>,不占版面。</p>
|
|
1212
|
+
<h4>怎么获得 PPTX</h4>
|
|
1213
|
+
<div class="hch">
|
|
1214
|
+
<div>
|
|
1215
|
+
<h5>页面预览(P)· 所见即所得</h5>
|
|
1216
|
+
<p>点 header 的「预览」按钮查看 WYSIWYG 页序列——预览与智能体精导使用同一序列化引擎,看到的即交付所得。页面不直接导出文件(浏览器端达不到 strict 质量硬门禁)。</p>
|
|
1217
|
+
</div>
|
|
1218
|
+
<div>
|
|
1219
|
+
<h5>智能体精导 · 唯一交付通道</h5>
|
|
1220
|
+
<p>复制下方提示词回到 AI 对话,由本地智能体执行 <code>extract_model.py → build_pptx.js --model → validate_pptx.py --strict</code>,0 错误 0 警告才交付(含模型往返保真检查)。</p>
|
|
1221
|
+
</div>
|
|
1222
|
+
</div>
|
|
1223
|
+
<h4>命令序列(智能体精导)</h4>
|
|
1224
|
+
<p><code>python scripts/extract_model.py 报告.html</code><br>
|
|
1225
|
+
<code>node scripts/build_pptx.js 报告.pptx --model=报告.model.json</code><br>
|
|
1226
|
+
<code>python scripts/validate_pptx.py 报告.pptx --strict --model=报告.model.json</code></p>
|
|
1227
|
+
<h4>可复制提示词(交给 AI 生成 PPTX)</h4>
|
|
1228
|
+
<div class="pmodal__prompt" id="helpPrompt"></div>
|
|
1229
|
+
</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
<div class="pmodal__ft">
|
|
1232
|
+
<div class="pmodal__issues">预览(P)与智能体精导使用同一序列化引擎——所见即所得;交付的 PPTX 为全原生形状,可在 PowerPoint / WPS 中继续编辑。</div>
|
|
1233
|
+
<div class="row" style="gap:var(--sp-3)">
|
|
1234
|
+
<button class="btn btn--primary" id="helpCopy" type="button" style="height:40px;padding-inline:18px">复制 AI 提示词</button>
|
|
1235
|
+
</div>
|
|
1236
|
+
</div>
|
|
1237
|
+
</div>
|
|
1238
|
+
</div>
|
|
1239
|
+
|
|
1240
|
+
<script>
|
|
1241
|
+
/* __TOPPPT_UI_START__ */
|
|
1242
|
+
/* ══ TopPPT HTML 公共 UI 脚本(assets/templates/ui.js 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
|
|
1243
|
+
/* ══ TopPPT HTML 公共 UI 脚本(assets/templates/ui.js)══
|
|
1244
|
+
由 scripts/sync_runtime.py 注入到 templates/*.html 的 __TOPPPT_UI__ 标记块 · 禁止在各模板手改。
|
|
1245
|
+
含:主题切换 / header 工具组(风格下拉·预览·帮助·全屏·折叠)/ 滚动显现 / PPT 翻页页码 /
|
|
1246
|
+
图表轻量动效 / PPTX 预览模态(WYSIWYG + 可复制 AI 提示词)/
|
|
1247
|
+
快捷键(T 主题 · P 预览 · H 帮助 · F 全屏 · B 收起/展开工具栏)。
|
|
1248
|
+
注:三模式为生成时确定的独立形态——此处无模式切换;风格为纯视觉皮肤可实时切换。
|
|
1249
|
+
页面不提供 PPTX 导出(浏览器端达不到 strict 硬门禁质量);正式 PPTX 由
|
|
1250
|
+
本地智能体走精导通道生成,预览即"交付所见"(同一序列化引擎)+ 提示词引导后续补生成。
|
|
1251
|
+
主题记忆按文件隔离(report-theme:<pathname>)且系统偏好不覆盖模板出厂
|
|
1252
|
+
data-theme;
|
|
1253
|
+
翻页引擎:飞行期禁 scroll-snap / 连按直跳 / 滚轮打断 / 模态打开不翻页;
|
|
1254
|
+
header 全屏按钮(F)与折叠(B,收起成迷你工具条)。 */
|
|
1255
|
+
|
|
1256
|
+
/* ── 主题切换(记忆按文件隔离;出厂 data-theme 优先于系统偏好) ── */
|
|
1257
|
+
(function(){var r=document.documentElement,b=document.getElementById('themeBtn'),
|
|
1258
|
+
K='report-theme:'+(location.pathname||'index');
|
|
1259
|
+
if(!b)return;
|
|
1260
|
+
function a(t){r.setAttribute('data-theme',t);try{localStorage.setItem(K,t)}catch(e){}
|
|
1261
|
+
/* 主题切换实时同步模型(预览与导出按当前主题出亮/暗版) */
|
|
1262
|
+
if(window.REPORT_MODEL)window.REPORT_MODEL.theme=t;}
|
|
1263
|
+
var s=null;try{s=localStorage.getItem(K)}catch(e){}
|
|
1264
|
+
if(s==='light'||s==='dark'){a(s)}
|
|
1265
|
+
else if(!r.getAttribute('data-theme')&&window.matchMedia&&
|
|
1266
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches){a('dark')}
|
|
1267
|
+
b.addEventListener('click',function(){a(r.getAttribute('data-theme')==='dark'?'light':'dark')})})();
|
|
1268
|
+
|
|
1269
|
+
/* ── header 全屏按钮(F 键同效) ── */
|
|
1270
|
+
(function(){
|
|
1271
|
+
var b=document.getElementById('fsBtn');if(!b)return;
|
|
1272
|
+
function sync(){var on=!!(document.fullscreenElement||document.webkitFullscreenElement);
|
|
1273
|
+
document.documentElement.classList.toggle('is-fs',on);
|
|
1274
|
+
b.title=(on?'退出全屏':'全屏(F)')}
|
|
1275
|
+
b.addEventListener('click',function(){
|
|
1276
|
+
var d=document,el=d.documentElement;
|
|
1277
|
+
if(d.fullscreenElement||d.webkitFullscreenElement){
|
|
1278
|
+
var ex=d.exitFullscreen||d.webkitExitFullscreen;
|
|
1279
|
+
if(ex)ex.call(d)}
|
|
1280
|
+
else{var fn=el.requestFullscreen||el.webkitRequestFullscreen;
|
|
1281
|
+
if(fn){var p=fn.call(el);if(p&&p.catch)p.catch(function(){})}}
|
|
1282
|
+
});
|
|
1283
|
+
['fullscreenchange','webkitfullscreenchange'].forEach(function(ev){
|
|
1284
|
+
document.addEventListener(ev,sync)});
|
|
1285
|
+
sync();
|
|
1286
|
+
})();
|
|
1287
|
+
|
|
1288
|
+
/* ── header 折叠/展开(收起成迷你工具条 · B 键同效 · 记忆按文件隔离) ── */
|
|
1289
|
+
(function(){
|
|
1290
|
+
var b=document.getElementById('barFold');if(!b)return;
|
|
1291
|
+
var K='report-bar-fold:'+(location.pathname||'index');
|
|
1292
|
+
function folded(){return document.documentElement.classList.contains('bar--fold')}
|
|
1293
|
+
function set(v){
|
|
1294
|
+
document.documentElement.classList.toggle('bar--fold',v);
|
|
1295
|
+
try{localStorage.setItem(K,v?'1':'0')}catch(e){}
|
|
1296
|
+
/* --bar-h 收紧会改变 band--fit 高度与锚点,动画结束后重算当前页 */
|
|
1297
|
+
setTimeout(function(){try{window.dispatchEvent(new Event('resize'))}catch(e){}},320);
|
|
1298
|
+
}
|
|
1299
|
+
b.addEventListener('click',function(){set(!folded())});
|
|
1300
|
+
try{if(localStorage.getItem(K)==='1')set(true)}catch(e){}
|
|
1301
|
+
})();
|
|
1302
|
+
|
|
1303
|
+
/* ── header 工具组:风格下拉 + 帮助模态 ── */
|
|
1304
|
+
(function(){
|
|
1305
|
+
var STYLES=[
|
|
1306
|
+
['business-blue','商务蓝','#1a73e8'],['apple-mono','优雅黑白','#1d1d1f'],
|
|
1307
|
+
['mckinsey','麦肯锡','#003a70'],['brand-red','品牌红','#d0021b'],
|
|
1308
|
+
['warm-sand','暖沙金','#96681f'],['deep-teal','墨绿','#0f6b5c'],
|
|
1309
|
+
['graphite-dark','石墨','#0369a1'],['indigo-violet','靛紫','#5b5bd6'],
|
|
1310
|
+
['spectrum','彩色','#4563ef']];
|
|
1311
|
+
var r=document.documentElement;
|
|
1312
|
+
function syncModel(){if(window.REPORT_MODEL){
|
|
1313
|
+
window.REPORT_MODEL.style=r.getAttribute('data-style')}}
|
|
1314
|
+
var seld=document.getElementById('styleSel');
|
|
1315
|
+
if(seld){
|
|
1316
|
+
var btn=document.getElementById('styleBtn'),list=document.getElementById('styleList');
|
|
1317
|
+
STYLES.forEach(function(s){
|
|
1318
|
+
var b=document.createElement('button');b.type='button';b.className='sitem';
|
|
1319
|
+
b.setAttribute('data-style',s[0]);b.title=s[1];
|
|
1320
|
+
b.innerHTML='<span class="swd" style="background:'+s[2]+'"></span>'+s[1];
|
|
1321
|
+
if(s[0]===r.getAttribute('data-style'))b.classList.add('on');
|
|
1322
|
+
b.addEventListener('click',function(){
|
|
1323
|
+
r.setAttribute('data-style',s[0]);
|
|
1324
|
+
list.querySelectorAll('.sitem').forEach(function(x){x.classList.remove('on')});
|
|
1325
|
+
b.classList.add('on');syncModel();
|
|
1326
|
+
seld.classList.remove('open')}); /* MD3 menu 惯例:选中即收起 */
|
|
1327
|
+
list.appendChild(b)});
|
|
1328
|
+
btn.addEventListener('click',function(e){e.stopPropagation();
|
|
1329
|
+
seld.classList.toggle('open')});
|
|
1330
|
+
document.addEventListener('click',function(e){
|
|
1331
|
+
if(!seld.contains(e.target))seld.classList.remove('open')});
|
|
1332
|
+
}
|
|
1333
|
+
/* 帮助模态(PPT 生成指引:双通道说明 + 可复制提示词) */
|
|
1334
|
+
var hmodal=document.getElementById('helpModal');
|
|
1335
|
+
if(hmodal){
|
|
1336
|
+
var hb=document.getElementById('helpBtn');
|
|
1337
|
+
var hclose=document.getElementById('helpClose');
|
|
1338
|
+
var hcopy=document.getElementById('helpCopy');
|
|
1339
|
+
if(hb)hb.addEventListener('click',function(){hmodal.classList.add('open')});
|
|
1340
|
+
if(hclose)hclose.addEventListener('click',function(){hmodal.classList.remove('open')});
|
|
1341
|
+
hmodal.addEventListener('click',function(e){if(e.target===hmodal)hmodal.classList.remove('open')});
|
|
1342
|
+
if(hcopy)hcopy.addEventListener('click',function(){
|
|
1343
|
+
var tx=document.getElementById('helpPrompt');
|
|
1344
|
+
var text=tx?tx.textContent:'';
|
|
1345
|
+
function done(){hcopy.textContent='已复制 ✓';
|
|
1346
|
+
setTimeout(function(){hcopy.textContent='复制 AI 提示词'},1600)}
|
|
1347
|
+
function fallback(){
|
|
1348
|
+
var ta=document.createElement('textarea');ta.value=text;
|
|
1349
|
+
document.body.appendChild(ta);ta.select();
|
|
1350
|
+
try{document.execCommand('copy');done()}catch(e){}ta.remove();}
|
|
1351
|
+
if(navigator.clipboard&&navigator.clipboard.writeText){
|
|
1352
|
+
navigator.clipboard.writeText(text).then(done,fallback)}
|
|
1353
|
+
else fallback();
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
})();
|
|
1357
|
+
|
|
1358
|
+
/* ── 滚动显现 ── */
|
|
1359
|
+
(function(){var i=document.querySelectorAll('.rv');
|
|
1360
|
+
if(!('IntersectionObserver' in window)){i.forEach(function(e){e.classList.add('in')});return}
|
|
1361
|
+
var o=new IntersectionObserver(function(es){es.forEach(function(e){
|
|
1362
|
+
if(e.isIntersecting){e.target.classList.add('in');o.unobserve(e.target)}})},
|
|
1363
|
+
{threshold:.08,rootMargin:'0px 0px -48px 0px'});i.forEach(function(e){o.observe(e)})})();
|
|
1364
|
+
|
|
1365
|
+
/* ── PPT 翻页:方向键整屏翻页 + 页码指示
|
|
1366
|
+
翻页要点:① 飞行期临时禁用 scroll-snap(平滑滚动与吸附互相拉扯是卡顿/回弹根因);
|
|
1367
|
+
② 450ms 内连按直跳落位,不反复重启平滑滚动;③ 滚轮/触摸即打断飞行并恢复吸附;
|
|
1368
|
+
④ 预览/帮助模态打开时不翻页;⑤ 空格不劫持聚焦中的按钮/链接。 ── */
|
|
1369
|
+
(function(){
|
|
1370
|
+
var pages=[].slice.call(document.querySelectorAll('section.band'));
|
|
1371
|
+
var dots=document.getElementById('pagerDots');
|
|
1372
|
+
var count=document.getElementById('pagerCount');
|
|
1373
|
+
if(!pages.length||!dots)return;
|
|
1374
|
+
var flying=false,pending=-1,lastGo=0,restoreTO=null;
|
|
1375
|
+
var dotEls=pages.map(function(sec,i){
|
|
1376
|
+
var d=document.createElement('button');
|
|
1377
|
+
d.className='pager__dot';d.type='button';
|
|
1378
|
+
d.setAttribute('aria-label','第 '+(i+1)+' 页');
|
|
1379
|
+
d.addEventListener('click',function(){go(i)});
|
|
1380
|
+
dots.appendChild(d);return d;
|
|
1381
|
+
});
|
|
1382
|
+
function modalOpen(){return !!document.querySelector('.pmodal.open')}
|
|
1383
|
+
function pageTop(i){return pages[i].getBoundingClientRect().top+window.scrollY}
|
|
1384
|
+
function idx(){
|
|
1385
|
+
var y=window.scrollY+window.innerHeight*0.4,best=0,bd=1e9;
|
|
1386
|
+
pages.forEach(function(sec,i){
|
|
1387
|
+
var d=Math.abs(pageTop(i)-y);
|
|
1388
|
+
if(d<bd){bd=d;best=i}
|
|
1389
|
+
});
|
|
1390
|
+
return best;
|
|
1391
|
+
}
|
|
1392
|
+
function mark(c){
|
|
1393
|
+
dotEls.forEach(function(d,i){d.classList.toggle('on',i===c)});
|
|
1394
|
+
if(count)count.textContent=(c+1)+' / '+pages.length;
|
|
1395
|
+
}
|
|
1396
|
+
function snap(on){document.documentElement.style.scrollSnapType=on?'':'none'}
|
|
1397
|
+
function land(){flying=false;pending=-1;snap(true);mark(idx())}
|
|
1398
|
+
function interrupt(){if(flying){flying=false;pending=-1;clearTimeout(restoreTO);snap(true)}}
|
|
1399
|
+
function go(i){
|
|
1400
|
+
i=Math.max(0,Math.min(pages.length-1,i));
|
|
1401
|
+
if(flying&&i===pending)return;
|
|
1402
|
+
var now=Date.now(),instant=(now-lastGo)<450; /* 连按直跳:防平滑滚动反复重启打架 */
|
|
1403
|
+
lastGo=now;pending=i;flying=true;
|
|
1404
|
+
mark(i);snap(false);clearTimeout(restoreTO);
|
|
1405
|
+
try{pages[i].scrollIntoView({behavior:instant?'auto':'smooth',block:'start'})}
|
|
1406
|
+
catch(e){pages[i].scrollIntoView()}
|
|
1407
|
+
restoreTO=setTimeout(land,instant?150:900);
|
|
1408
|
+
}
|
|
1409
|
+
function paint(){if(flying)return;mark(idx())}
|
|
1410
|
+
var KEY_NEXT=['ArrowRight','ArrowDown','PageDown',' '];
|
|
1411
|
+
var KEY_PREV=['ArrowLeft','ArrowUp','PageUp'];
|
|
1412
|
+
document.addEventListener('keydown',function(e){
|
|
1413
|
+
var tag=(e.target&&e.target.tagName)||'';
|
|
1414
|
+
if(/INPUT|TEXTAREA|SELECT/.test(tag))return;
|
|
1415
|
+
if(e.key===' '&&tag!=='BODY')return; /* 空格留给聚焦中的按钮/链接 */
|
|
1416
|
+
if(modalOpen())return; /* 预览/帮助模态打开时不翻页 */
|
|
1417
|
+
var base=(flying&&pending>-1)?pending:idx(); /* 飞行中按基准页继续翻,不回跳 */
|
|
1418
|
+
if(KEY_NEXT.indexOf(e.key)>-1){e.preventDefault();go(base+1)}
|
|
1419
|
+
else if(KEY_PREV.indexOf(e.key)>-1){e.preventDefault();go(base-1)}
|
|
1420
|
+
else if(e.key==='Home'){e.preventDefault();go(0)}
|
|
1421
|
+
else if(e.key==='End'){e.preventDefault();go(pages.length-1)}
|
|
1422
|
+
});
|
|
1423
|
+
/* 用户主动滚动(滚轮/触摸)→ 立即打断飞行并恢复吸附 */
|
|
1424
|
+
['wheel','touchstart'].forEach(function(ev){
|
|
1425
|
+
window.addEventListener(ev,function(){interrupt();paint()},{passive:true});
|
|
1426
|
+
});
|
|
1427
|
+
if('onscrollend' in window)window.addEventListener('scrollend',function(){
|
|
1428
|
+
if(flying)land()});
|
|
1429
|
+
var raf=null;
|
|
1430
|
+
window.addEventListener('scroll',function(){
|
|
1431
|
+
if(raf)return;
|
|
1432
|
+
raf=requestAnimationFrame(function(){paint();raf=null});
|
|
1433
|
+
},{passive:true});
|
|
1434
|
+
window.addEventListener('resize',paint);
|
|
1435
|
+
paint();
|
|
1436
|
+
})();
|
|
1437
|
+
|
|
1438
|
+
/* ── 图表轻量动效:进入视口触发 生长/描边/扫入/计数 ── */
|
|
1439
|
+
(function(){
|
|
1440
|
+
if(!('IntersectionObserver' in window))return;
|
|
1441
|
+
function countUp(el){var t=parseFloat(el.dataset.count),s=el.dataset.suffix||'',
|
|
1442
|
+
d=(el.dataset.count.split('.')[1]||'').length,t0=null;
|
|
1443
|
+
(function st(ts){if(!t0)t0=ts;var p=Math.min(1,(ts-t0)/900);
|
|
1444
|
+
el.textContent=(t*p).toFixed(d)+s;if(p<1)requestAnimationFrame(st)})(performance.now());}
|
|
1445
|
+
var EASE='stroke-dashoffset 1s cubic-bezier(.05,.7,.1,1)';
|
|
1446
|
+
function all(g,sel){return [].slice.call(g.querySelectorAll(sel))}
|
|
1447
|
+
var io=new IntersectionObserver(function(es){
|
|
1448
|
+
var hit=[],draws=[],sweeps=[],counts=[];
|
|
1449
|
+
es.forEach(function(e){if(!e.isIntersecting)return;
|
|
1450
|
+
var g=e.target;io.unobserve(g);hit.push(g);
|
|
1451
|
+
draws=draws.concat(all(g,'[data-draw]'));
|
|
1452
|
+
sweeps=sweeps.concat(all(g,'[data-sweep]'));
|
|
1453
|
+
counts=counts.concat(all(g,'[data-count]'));});
|
|
1454
|
+
if(!hit.length)return;
|
|
1455
|
+
/* 先集中量(读),再集中写:逐条 getTotalLength 与改样式交替会反复触发强制回流 */
|
|
1456
|
+
var lens=draws.map(function(p){return p.getTotalLength()});
|
|
1457
|
+
hit.forEach(function(g){g.classList.add('in')});
|
|
1458
|
+
draws.forEach(function(p,i){p.style.willChange='stroke-dashoffset';
|
|
1459
|
+
p.style.strokeDasharray=lens[i];p.style.strokeDashoffset=lens[i]});
|
|
1460
|
+
sweeps.forEach(function(c){var L=+c.dataset.circ;c.style.willChange='stroke-dashoffset';
|
|
1461
|
+
c.style.strokeDasharray=L;c.style.strokeDashoffset=L});
|
|
1462
|
+
counts.forEach(countUp);
|
|
1463
|
+
requestAnimationFrame(function(){
|
|
1464
|
+
draws.forEach(function(p){p.style.transition=EASE;p.style.strokeDashoffset=0});
|
|
1465
|
+
sweeps.forEach(function(c){c.style.transition=EASE;
|
|
1466
|
+
c.style.strokeDashoffset=(+c.dataset.circ)*(1-(+c.dataset.p))});
|
|
1467
|
+
});
|
|
1468
|
+
/* will-change 长期驻留会占用合成层内存,动画结束即撤下 */
|
|
1469
|
+
setTimeout(function(){draws.concat(sweeps).forEach(function(el){el.style.willChange=''})},1200);
|
|
1470
|
+
},{threshold:.3});
|
|
1471
|
+
document.querySelectorAll('.chart,[data-chart]').forEach(function(el){io.observe(el)});
|
|
1472
|
+
})();
|
|
1473
|
+
|
|
1474
|
+
/* ── research 导航轨高亮(滚动跟随;rail 默认注释不启用,启用后自动生效) ── */
|
|
1475
|
+
(function(){
|
|
1476
|
+
var rail=document.getElementById('rail');if(!rail)return;
|
|
1477
|
+
var as=[].slice.call(rail.querySelectorAll('.rail__a'));
|
|
1478
|
+
if(!as.length)return;
|
|
1479
|
+
var raf=null,tops=[];
|
|
1480
|
+
/* 章节位置缓存:长报告里每帧对每个锚点做 querySelector + 读 offsetTop 会反复触发同步布局 */
|
|
1481
|
+
function measure(){tops=as.map(function(a){
|
|
1482
|
+
var s=document.querySelector(a.getAttribute('href'));return s?s.offsetTop:null})}
|
|
1483
|
+
function paint(){var y=window.scrollY+window.innerHeight*0.4,best=0,bd=1e9;
|
|
1484
|
+
tops.forEach(function(t,i){if(t==null)return;
|
|
1485
|
+
var d=Math.abs(t-y);if(d<bd){bd=d;best=i}});
|
|
1486
|
+
as.forEach(function(a,i){a.classList.toggle('on',i===best)});raf=null}
|
|
1487
|
+
window.addEventListener('scroll',function(){if(raf)return;
|
|
1488
|
+
raf=requestAnimationFrame(paint)},{passive:true});
|
|
1489
|
+
window.addEventListener('resize',function(){measure();paint()});
|
|
1490
|
+
measure();paint();
|
|
1491
|
+
})();
|
|
1492
|
+
|
|
1493
|
+
/* ══ PPTX 预览 / 提示词复制 / 快捷键(预览通道 UI 层;与智能体精导同一序列化引擎)
|
|
1494
|
+
版式/文本所见即所得;带数据的原生图表在交付 PPTX 中为真 chart part(双击可编辑数据),
|
|
1495
|
+
预览侧为形状近似——形态以精导产物为准。 ══ */
|
|
1496
|
+
(function(){
|
|
1497
|
+
var PW=13.333,PH=7.5,EMU=914400;
|
|
1498
|
+
var modal=document.getElementById('pptModal');
|
|
1499
|
+
var body=document.getElementById('pptBody'),meta=document.getElementById('pptMeta'),
|
|
1500
|
+
issues=document.getElementById('pptIssues'),
|
|
1501
|
+
copy=document.getElementById('pptCopy'),closeBtn=document.getElementById('pptClose');
|
|
1502
|
+
var helpModal=document.getElementById('helpModal');
|
|
1503
|
+
|
|
1504
|
+
var PROMPT=[
|
|
1505
|
+
'请为这份 HTML 报告生成可编辑 PPTX 并交付:',
|
|
1506
|
+
'1) 阅读 references/pptx-export.md 与 references/modes.md(三模式独立规则);',
|
|
1507
|
+
'2) 核对/补全 window.REPORT_MODEL 为与正文严格一致的内容(含 mode/style/theme/title/agenda/sections/closing;theme 为 light 或 dark、与页面 data-theme 一致——PPTX 按此导出亮色版或深色版;29 种页型字段与 30 类 chart.type 见 scripts/model-schema.json;research 关键图表页用 type:"exhibit" 并填 exhibitNo/soWhat/footnote);',
|
|
1508
|
+
'3) 运行 python scripts/validate_report.py <报告.html> 直到全 PASS(含模型一致性检查);',
|
|
1509
|
+
'4) 智能体精导并质检(0/0 才交付):python scripts/extract_model.py <报告.html> → NODE_PATH=<pptxgenjs 所在 node_modules> node scripts/build_pptx.js <报告.pptx> --model=<报告.model.json>(如需覆盖模型主题可加 --theme=light|dark) → python scripts/validate_pptx.py <报告.pptx> --strict --model=<报告.model.json>。',
|
|
1510
|
+
'报告文件:<请填写报告.html 的路径>'
|
|
1511
|
+
].join('\n');
|
|
1512
|
+
var promptEl=document.getElementById('helpPrompt');
|
|
1513
|
+
if(promptEl)promptEl.textContent=PROMPT;
|
|
1514
|
+
|
|
1515
|
+
function escHtml(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
1516
|
+
|
|
1517
|
+
/* ── slide XML → DOM 缩略(与导出同一序列化输出,所见即所得) ── */
|
|
1518
|
+
function renderSlide(xml,i,model){
|
|
1519
|
+
var doc=new DOMParser().parseFromString(xml,'application/xml');
|
|
1520
|
+
var slide=document.createElement('div');slide.className='pslide';
|
|
1521
|
+
var no=document.createElement('span');no.className='pslide__no';no.textContent=(i+1);
|
|
1522
|
+
slide.appendChild(no);
|
|
1523
|
+
var tree=doc.getElementsByTagName('p:spTree')[0];
|
|
1524
|
+
if(tree){[].forEach.call(tree.children||tree.childNodes,function(n){
|
|
1525
|
+
if(n.nodeType!==1)return;
|
|
1526
|
+
if(n.tagName==='p:sp')renderSp(slide,n);
|
|
1527
|
+
else if(n.tagName==='p:graphicFrame')renderTable(slide,n);});}
|
|
1528
|
+
paintImages(slide,i,model);
|
|
1529
|
+
return slide;
|
|
1530
|
+
}
|
|
1531
|
+
/* 把模型里的素材图片填进图位块(预览保真)。
|
|
1532
|
+
页序 = 封面 + 大纲(可选) + sections + 收尾 —— 与 slidesOf 同规则。 */
|
|
1533
|
+
function imageSrcsFor(model,idx){
|
|
1534
|
+
try{
|
|
1535
|
+
var secs=(model&&model.sections)||[];
|
|
1536
|
+
var off=1+((model&&model.agenda&&model.agenda.length)?1:0);
|
|
1537
|
+
var si=idx-off;
|
|
1538
|
+
if(si<0||si>=secs.length)return null;
|
|
1539
|
+
var sec=secs[si]||{},srcs=[],ph=false;
|
|
1540
|
+
var holders=[sec.image,sec.right&&sec.right.image];
|
|
1541
|
+
holders.forEach(function(img){
|
|
1542
|
+
if(!img||typeof img!=='object')return;
|
|
1543
|
+
if(img.placeholder){ph=true;return}
|
|
1544
|
+
if(typeof img.src==='string'&&img.src)srcs.push(img.src);
|
|
1545
|
+
(img.items||[]).forEach(function(it){
|
|
1546
|
+
if(typeof it==='string'&&it)srcs.push(it);
|
|
1547
|
+
else if(it&&typeof it.src==='string'&&it.src)srcs.push(it.src);});
|
|
1548
|
+
});
|
|
1549
|
+
return {ph:ph,srcs:srcs};
|
|
1550
|
+
}catch(e){return null}
|
|
1551
|
+
}
|
|
1552
|
+
function paintImages(slide,idx,model){
|
|
1553
|
+
var info=imageSrcsFor(model,idx);if(!info)return;
|
|
1554
|
+
var slots=[].slice.call(slide.querySelectorAll('[data-el="img"],[data-el="imgph"]'));
|
|
1555
|
+
if(info.ph&&!info.srcs.length)return; /* 纯占位:保留虚线占位块与标签 */
|
|
1556
|
+
slots.forEach(function(el,k){
|
|
1557
|
+
var src=info.srcs[k]||info.srcs[0];if(!src)return;
|
|
1558
|
+
el.style.backgroundImage='url("'+String(src).replace(/"/g,'%22')+'")';
|
|
1559
|
+
el.style.backgroundSize='cover';el.style.backgroundPosition='center';
|
|
1560
|
+
el.style.border='none';el.style.borderRadius='0.14em';
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
function geomOf(scope){
|
|
1564
|
+
var xf=scope.getElementsByTagName('a:xfrm')[0];if(!xf)return null;
|
|
1565
|
+
var off=xf.getElementsByTagName('a:off')[0],ext=xf.getElementsByTagName('a:ext')[0];
|
|
1566
|
+
if(!off||!ext)return null;
|
|
1567
|
+
return{x:+off.getAttribute('x')/EMU,y:+off.getAttribute('y')/EMU,
|
|
1568
|
+
w:+ext.getAttribute('cx')/EMU,h:+ext.getAttribute('cy')/EMU};
|
|
1569
|
+
}
|
|
1570
|
+
function place(el,g){
|
|
1571
|
+
el.style.left=(g.x/PW*100)+'%';el.style.top=(g.y/PH*100)+'%';
|
|
1572
|
+
el.style.width=(g.w/PW*100)+'%';el.style.height=(g.h/PH*100)+'%';
|
|
1573
|
+
}
|
|
1574
|
+
function fillOf(node){
|
|
1575
|
+
if(!node)return null;
|
|
1576
|
+
var f=node.getElementsByTagName('a:solidFill')[0];if(!f)return null;
|
|
1577
|
+
var c=f.getElementsByTagName('a:srgbClr')[0];
|
|
1578
|
+
return c?'#'+c.getAttribute('val'):null;
|
|
1579
|
+
}
|
|
1580
|
+
function renderSp(slide,sp){
|
|
1581
|
+
var spPr=sp.getElementsByTagName('p:spPr')[0];if(!spPr)return;
|
|
1582
|
+
var g=geomOf(spPr);if(!g)return;
|
|
1583
|
+
var el=document.createElement('div');
|
|
1584
|
+
/* 图位块标记(img 真实图片 / imgph 配图占位)——预览时用真实图片填充图位,
|
|
1585
|
+
让"有配图的页面"在预览里就是交付所见(占位块保持虚线 + 标签)。 */
|
|
1586
|
+
var nv=sp.getElementsByTagName('p:cNvPr')[0];
|
|
1587
|
+
var nm=nv?nv.getAttribute('name'):'';
|
|
1588
|
+
el.setAttribute('data-el',(nm==='img'||nm==='imgph')?nm:'');
|
|
1589
|
+
place(el,g);
|
|
1590
|
+
var fill=fillOf(spPr);
|
|
1591
|
+
if(fill)el.style.background=fill;
|
|
1592
|
+
var prst=spPr.getElementsByTagName('a:prstGeom')[0];
|
|
1593
|
+
if(prst){var t=prst.getAttribute('prst');
|
|
1594
|
+
if(t==='roundRect')el.style.borderRadius='0.14em';
|
|
1595
|
+
else if(t==='ellipse')el.style.borderRadius='50%';}
|
|
1596
|
+
var ln=spPr.getElementsByTagName('a:ln')[0];
|
|
1597
|
+
if(ln&&!fill){var lc=fillOf(ln);if(lc)el.style.border='1px solid '+lc;}
|
|
1598
|
+
var tx=sp.getElementsByTagName('p:txBody')[0];
|
|
1599
|
+
if(tx){
|
|
1600
|
+
var bodyPr=tx.getElementsByTagName('a:bodyPr')[0];
|
|
1601
|
+
var anchor=(bodyPr&&bodyPr.getAttribute('anchor'))||'t';
|
|
1602
|
+
el.style.display='flex';el.style.flexDirection='column';
|
|
1603
|
+
el.style.justifyContent=anchor==='ctr'?'center':(anchor==='b'?'flex-end':'flex-start');
|
|
1604
|
+
el.style.padding='0.06em 0.08em';el.style.boxSizing='border-box';
|
|
1605
|
+
[].forEach.call(tx.getElementsByTagName('a:p'),function(p){
|
|
1606
|
+
var pe=document.createElement('p');
|
|
1607
|
+
var pPr=p.getElementsByTagName('a:pPr')[0];
|
|
1608
|
+
var algn=pPr?pPr.getAttribute('algn'):null;
|
|
1609
|
+
if(algn==='ctr')pe.style.textAlign='center';
|
|
1610
|
+
else if(algn==='r'||algn==='just')pe.style.textAlign='right';
|
|
1611
|
+
[].forEach.call(p.getElementsByTagName('a:r'),function(r){
|
|
1612
|
+
var rPr=r.getElementsByTagName('a:rPr')[0];
|
|
1613
|
+
var run=document.createElement('span');
|
|
1614
|
+
run.style.fontSize=(((rPr&&rPr.getAttribute('sz'))||1000)/100)+'em';
|
|
1615
|
+
if(rPr&&rPr.getAttribute('b'))run.style.fontWeight='700';
|
|
1616
|
+
var col=rPr?fillOf(rPr):null;if(col)run.style.color=col;
|
|
1617
|
+
var lat=r.getElementsByTagName('a:latin')[0];
|
|
1618
|
+
if(lat)run.style.fontFamily=lat.getAttribute('typeface');
|
|
1619
|
+
var tNode=r.getElementsByTagName('a:t')[0];
|
|
1620
|
+
run.textContent=tNode?tNode.textContent:'';
|
|
1621
|
+
pe.appendChild(run);
|
|
1622
|
+
});
|
|
1623
|
+
el.appendChild(pe);
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
slide.appendChild(el);
|
|
1627
|
+
}
|
|
1628
|
+
function renderTable(slide,gf){
|
|
1629
|
+
var g=geomOf(gf);if(!g)return;
|
|
1630
|
+
var el=document.createElement('div');el.setAttribute('data-el','');
|
|
1631
|
+
place(el,g);
|
|
1632
|
+
var tbl=gf.getElementsByTagName('a:tbl')[0];
|
|
1633
|
+
if(tbl){
|
|
1634
|
+
var cols=[].map.call(tbl.getElementsByTagName('a:gridCol'),function(c){return +c.getAttribute('w')});
|
|
1635
|
+
var tot=cols.reduce(function(a,b){return a+b},0)||1;
|
|
1636
|
+
var t=document.createElement('table');
|
|
1637
|
+
t.style.width='100%';t.style.height='100%';t.style.borderCollapse='collapse';
|
|
1638
|
+
[].forEach.call(tbl.getElementsByTagName('a:tr'),function(tr){
|
|
1639
|
+
var re=document.createElement('tr');
|
|
1640
|
+
var tcs=tr.getElementsByTagName('a:tc');
|
|
1641
|
+
for(var ci=0;ci<cols.length;ci++){
|
|
1642
|
+
var td=document.createElement('td');
|
|
1643
|
+
td.style.width=(cols[ci]/tot*100)+'%';td.style.padding='0.08em 0.1em';
|
|
1644
|
+
var tc=tcs[ci];
|
|
1645
|
+
if(tc){
|
|
1646
|
+
var f=fillOf(tc);if(f)td.style.background=f;
|
|
1647
|
+
var r=tc.getElementsByTagName('a:r')[0];
|
|
1648
|
+
if(r){
|
|
1649
|
+
var rPr=r.getElementsByTagName('a:rPr')[0];
|
|
1650
|
+
var run=document.createElement('span');
|
|
1651
|
+
run.style.fontSize=(((rPr&&rPr.getAttribute('sz'))||1000)/100)+'em';
|
|
1652
|
+
if(rPr&&rPr.getAttribute('b'))run.style.fontWeight='700';
|
|
1653
|
+
var col=rPr?fillOf(rPr):null;if(col)run.style.color=col;
|
|
1654
|
+
var tNode=r.getElementsByTagName('a:t')[0];
|
|
1655
|
+
run.textContent=tNode?tNode.textContent:'';
|
|
1656
|
+
td.appendChild(run);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
re.appendChild(td);
|
|
1660
|
+
}
|
|
1661
|
+
t.appendChild(re);
|
|
1662
|
+
});
|
|
1663
|
+
el.appendChild(t);
|
|
1664
|
+
}
|
|
1665
|
+
slide.appendChild(el);
|
|
1666
|
+
}
|
|
1667
|
+
function fitSlides(){
|
|
1668
|
+
[].forEach.call(body.querySelectorAll('.pslide'),function(s){
|
|
1669
|
+
s.style.fontSize=(s.clientWidth/PW/72)+'px'; /* 1em = 1pt(按当前缩略宽度) */
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
function currentModel(){
|
|
1673
|
+
var m=window.REPORT_MODEL;
|
|
1674
|
+
if(m){var r=document.documentElement;
|
|
1675
|
+
m.style=r.getAttribute('data-style')||m.style;
|
|
1676
|
+
m.theme=r.getAttribute('data-theme')||m.theme||'light';}
|
|
1677
|
+
return m;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/* ── 打开模态:WYSIWYG 预览(与通道 B 同一序列化输出)+ 自检结果 + 常显可复制提示词 ── */
|
|
1681
|
+
function open(){
|
|
1682
|
+
if(!modal||!body||!meta||!issues)return; /* 预览骨架缺失时优雅降级(不影响其它快捷键) */
|
|
1683
|
+
var m=currentModel();
|
|
1684
|
+
var chk=(window.TopPptHtml&&window.TopPptHtml.validateModel)
|
|
1685
|
+
?window.TopPptHtml.validateModel(m)
|
|
1686
|
+
:{ok:false,missing:['预览运行时缺失'],warnings:[],pages:0};
|
|
1687
|
+
meta.textContent=(m&&m.title?m.title:'未命名报告')+' · '+(m&&m.style||'business-blue')
|
|
1688
|
+
+' · '+((m&&m.theme)==='dark'?'深色':(m&&m.theme)==='light'?'浅色':'浅色')
|
|
1689
|
+
+' · '+(m&&m.mode||'presentation')+' · '+(chk.pages||0)+' 页';
|
|
1690
|
+
body.innerHTML='';
|
|
1691
|
+
if(window.TopPptHtml&&m&&m.sections){
|
|
1692
|
+
try{
|
|
1693
|
+
window.TopPptHtml.slidesXml(m).forEach(function(x,i){body.appendChild(renderSlide(x,i,m))});
|
|
1694
|
+
}catch(e){body.innerHTML='<div class="t-sm">预览渲染失败:'+escHtml(e.message)+'</div>'}
|
|
1695
|
+
}else if(!m){
|
|
1696
|
+
body.innerHTML='<div class="t-sm">报告未内嵌 window.REPORT_MODEL 内容模型,无法预览。请按下方提示词回到 AI 对话补全。</div>';
|
|
1697
|
+
}
|
|
1698
|
+
if(chk.ok){
|
|
1699
|
+
issues.innerHTML='<b>模型自检通过</b>:内容完整('+chk.pages+' 页)。版式与文本与精导一致;'
|
|
1700
|
+
+'带数据的原生图表在交付 PPTX 中为可编辑数据图表(chart part),预览为形状近似。'
|
|
1701
|
+
+(chk.warnings.length?'<br>· '+chk.warnings.map(escHtml).join('<br>· '):'')
|
|
1702
|
+
+'<br>本页面不直接导出 PPTX——复制下方提示词交给 AI,由智能体精导通道生成并过 strict 校验(0/0)后交付。'
|
|
1703
|
+
+'<div class="pmodal__prompt">'+escHtml(PROMPT)+'</div>';
|
|
1704
|
+
}else{
|
|
1705
|
+
issues.innerHTML='<b>模型不完整</b>:'
|
|
1706
|
+
+chk.missing.map(escHtml).join('、')
|
|
1707
|
+
+'<br>预览为尽力渲染;复制下方提示词回到 AI 对话补全模型并由智能体精导交付。'
|
|
1708
|
+
+'<div class="pmodal__prompt">'+escHtml(PROMPT)+'</div>';
|
|
1709
|
+
}
|
|
1710
|
+
if(copy)copy.style.display='';
|
|
1711
|
+
modal.classList.add('open');
|
|
1712
|
+
requestAnimationFrame(fitSlides);
|
|
1713
|
+
}
|
|
1714
|
+
function close(){if(modal)modal.classList.remove('open')}
|
|
1715
|
+
|
|
1716
|
+
/* 按钮 wiring(header 预览 / 帮助) */
|
|
1717
|
+
var previewBtn=document.getElementById('pptPreviewBtn');
|
|
1718
|
+
if(previewBtn)previewBtn.addEventListener('click',open);
|
|
1719
|
+
if(closeBtn)closeBtn.addEventListener('click',close);
|
|
1720
|
+
if(modal)modal.addEventListener('click',function(e){if(e.target===modal)close()});
|
|
1721
|
+
if(copy)copy.addEventListener('click',function(){
|
|
1722
|
+
function done(){copy.textContent='已复制 ✓';
|
|
1723
|
+
setTimeout(function(){copy.textContent='复制 AI 提示词'},1600)}
|
|
1724
|
+
function fallback(){
|
|
1725
|
+
var ta=document.createElement('textarea');ta.value=PROMPT;
|
|
1726
|
+
document.body.appendChild(ta);ta.select();
|
|
1727
|
+
try{document.execCommand('copy');done()}catch(e){}ta.remove();}
|
|
1728
|
+
if(navigator.clipboard&&navigator.clipboard.writeText){
|
|
1729
|
+
navigator.clipboard.writeText(PROMPT).then(done,fallback)}
|
|
1730
|
+
else fallback();
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
/* 快捷键:T 主题 / F 全屏 / B 收起工具栏 / P 预览 / H 帮助 / Esc 关闭 */
|
|
1734
|
+
document.addEventListener('keydown',function(e){
|
|
1735
|
+
var tag=(e.target&&e.target.tagName)||'';
|
|
1736
|
+
if(/INPUT|TEXTAREA|SELECT/.test(tag))return;
|
|
1737
|
+
if(e.key==='Escape'){
|
|
1738
|
+
close();
|
|
1739
|
+
if(helpModal)helpModal.classList.remove('open');
|
|
1740
|
+
var seld=document.getElementById('styleSel');
|
|
1741
|
+
if(seld)seld.classList.remove('open');
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
if(e.altKey||e.ctrlKey||e.metaKey)return;
|
|
1745
|
+
/* 模态打开时屏蔽内容快捷键:预览态整页锁定;帮助态仅 Esc(上方已处理)可用 */
|
|
1746
|
+
if((modal&&modal.classList.contains('open'))||
|
|
1747
|
+
(helpModal&&helpModal.classList.contains('open')))return;
|
|
1748
|
+
var k=e.key.toLowerCase();
|
|
1749
|
+
if(k==='t'){e.preventDefault();
|
|
1750
|
+
var tb=document.getElementById('themeBtn');if(tb)tb.click()}
|
|
1751
|
+
else if(k==='f'){e.preventDefault(); /* 全屏 */
|
|
1752
|
+
var fb=document.getElementById('fsBtn');if(fb)fb.click()}
|
|
1753
|
+
else if(k==='b'){e.preventDefault(); /* 收起/展开工具栏 */
|
|
1754
|
+
var bb=document.getElementById('barFold');if(bb)bb.click()}
|
|
1755
|
+
else if(k==='p'){e.preventDefault();open()}
|
|
1756
|
+
else if(k==='h'){e.preventDefault();
|
|
1757
|
+
if(helpModal)helpModal.classList.add('open')}
|
|
1758
|
+
});
|
|
1759
|
+
|
|
1760
|
+
window.addEventListener('resize',function(){
|
|
1761
|
+
if(modal&&modal.classList.contains('open'))fitSlides()},{passive:true});
|
|
1762
|
+
})();
|
|
1763
|
+
/* __TOPPPT_UI_END__ */
|
|
1764
|
+
|
|
1765
|
+
/* ══ PPTX 导出内容模型(与页面正文同源,生成报告时同步填写;填法见 references/pptx-export.md) ══
|
|
1766
|
+
两通道共用:① header"预览/导出 PPTX"(页面直导,导出前自检)② extract_model.py → build_pptx.js --model(智能体精导)
|
|
1767
|
+
必须是严格 JSON(双引号、无尾逗号、无注释)。mode 固定为 presentation(与 data-mode 一致,生成时锁定)。
|
|
1768
|
+
页型:points / metrics / table / timeline / bar / cards / split / diagram。 */
|
|
1769
|
+
window.REPORT_MODEL = {
|
|
1770
|
+
"mode": "presentation",
|
|
1771
|
+
"style": "business-blue",
|
|
1772
|
+
"title": "主标题",
|
|
1773
|
+
"subtitle": "副标题",
|
|
1774
|
+
"meta": "密级 · YYYY-MM-DD",
|
|
1775
|
+
"agenda": [
|
|
1776
|
+
["01", "章节一标题", "本章一句话说明"],
|
|
1777
|
+
["02", "章节二标题", "本章一句话说明"],
|
|
1778
|
+
["03", "章节三标题", "本章一句话说明"],
|
|
1779
|
+
["04", "参考资料", "数据来源与口径说明"]
|
|
1780
|
+
],
|
|
1781
|
+
"sections": [
|
|
1782
|
+
{ "type": "points", "eyebrow": "01 · 关键词", "title": "一句主张",
|
|
1783
|
+
"points": [["小标题", "一句话说明"]],
|
|
1784
|
+
"metrics": [["62TB", "指标注解"]] }
|
|
1785
|
+
],
|
|
1786
|
+
"closing": { "title": "下一步主张", "points": [["要点一", "一句话"], ["要点二", "一句话"], ["要点三", "一句话"]] }
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
/* __TOPPPT_RUNTIME_START__ */
|
|
1790
|
+
/* ══ PPTX 导出运行时(assets/pptx-export.js 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
|
|
1791
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
1792
|
+
* TopPPT HTML· 浏览器端 PPTX 预览运行时
|
|
1793
|
+
* 零依赖、随报告单文件分发;OOXML 序列化器(仅供预览,页面不导出文件)。
|
|
1794
|
+
*
|
|
1795
|
+
* 定位:HTML 是阅读与演示界面,PPTX 是协作与修改格式——两者来自同一内容模型
|
|
1796
|
+
* (window.REPORT_MODEL)。页面只提供「预览 PPTX」(WYSIWYG 模态,与智能体精导通道
|
|
1797
|
+
* 同一序列化引擎)与可复制 AI 提示词;正式 PPTX 一律由本地智能体走精导通道
|
|
1798
|
+
* (build_pptx.js)生成并过 strict 硬门禁,浏览器端导出达不到该质量标准。
|
|
1799
|
+
*
|
|
1800
|
+
* API:
|
|
1801
|
+
* TopPptHtml.slidesXml(model) → string[](每页 slide XML,预览模态渲染用)
|
|
1802
|
+
* TopPptHtml.validateModel(model) → {ok, missing[], warnings[], pages}(schema 单源驱动)
|
|
1803
|
+
* TopPptHtml.slidesOf / slideXml / relsXml / NS / PRESETS
|
|
1804
|
+
* → 供 scripts/gen_channel_a.js 在 Node 侧组装完整
|
|
1805
|
+
* PPTX 包(回归双裁判内部质量路径,页面不使用)
|
|
1806
|
+
*
|
|
1807
|
+
* model = window.REPORT_MODEL(JSON,与页面正文同源):
|
|
1808
|
+
* { mode, style, theme(light|dark:PPTX 按此选亮/暗 token,默认 light),
|
|
1809
|
+
* title, subtitle, meta, agenda:[[num,标题,说明]…](architecture 可省),
|
|
1810
|
+
* sections:[{type: points|metrics|kpi|table|timeline|steps|bar|donut|heatmap|bullet|pyramid|
|
|
1811
|
+
* image|cards|split|comparison|quote|diagram|exhibit|twocol|threecol|halftable|
|
|
1812
|
+
* matrix|lane, soWhat?, footnote?, flags?, image?, …}],
|
|
1813
|
+
* closing:{title,points} }
|
|
1814
|
+
* 通用可选字段:soWhat(结论条)/ footnote(来源行)/ flags(待核实清单 → accent 强调色条)
|
|
1815
|
+
* / image(素材图片或配图占位,{src|items|placeholder, layout, fit, caption, alt, hint},
|
|
1816
|
+
* src 仅 data: 或相对路径;layout ∈ full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec)。
|
|
1817
|
+
* 页型字段定义(29 种)与 chart.type 白名单(30 类)的唯一事实源:scripts/model-schema.json。
|
|
1818
|
+
*
|
|
1819
|
+
* ★ 单源机制(scripts/sync_runtime.py 注入,标记块内禁止手改):
|
|
1820
|
+
* - 版式常量:scripts/layout-constants.json → __TOPPPT_CONSTANTS__ 块;
|
|
1821
|
+
* 通道 B(build_pptx.js)require 同一份 JSON,两通道产出一致。
|
|
1822
|
+
* - DSL schema:scripts/model-schema.json → __TOPPPT_SCHEMA__ 块;
|
|
1823
|
+
* scripts/extract_model.py 读同一份文件,双端校验同源。
|
|
1824
|
+
* ★ 三模式独立排版比例尺(MTS)——research 咨询密排 / architecture 全幅图页型;
|
|
1825
|
+
* modeSize() 把调用点的 presentation 基准 pt 映射到当前模式比例尺。
|
|
1826
|
+
* ★ 本运行时只做「预览与回归裁判」:图表为形状近似渲染(交付通道 B 用原生数据图表,
|
|
1827
|
+
* 可在 PowerPoint 中"编辑数据");文本与版式几何两通道同源,cross_verify 逐页比对。
|
|
1828
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
1829
|
+
(function (g) {
|
|
1830
|
+
'use strict';
|
|
1831
|
+
/* 双形态载荷判定:[[t,d]…] 数组 vs [{t,d,accent}…] 记录。typeof [] === 'object'。 */
|
|
1832
|
+
function isRec(v) { return !!v && typeof v === 'object' && !Array.isArray(v); }
|
|
1833
|
+
|
|
1834
|
+
/* __TOPPPT_CONSTANTS_START__ */
|
|
1835
|
+
/* ── 由 scripts/sync_runtime.py 从 scripts/layout-constants.json 注入 · 禁止手改 ── */
|
|
1836
|
+
/* MTS = 三模式独立排版比例尺(按模式取 pt;基准 TS_BASE = presentation 档) */
|
|
1837
|
+
/* PRESETS_DARK = 风格 dark token(REPORT_MODEL.theme="dark" 时导出深色版)*/
|
|
1838
|
+
/* GRID = 12 列网格;TYPO = 语义字阶 C0–T14;CONT = 容器内边距;REG = 图表登记四元组;DEEP = 深度模式配置 */
|
|
1839
|
+
/* IMG = 素材图片规格与配图占位约定(版式/裁切/建议尺寸/占位标签,双引擎同源) */
|
|
1840
|
+
/* STYLE_DATA_COLORS(_DARK) = 9 套风格各自的编码色板 c1–c5(数据系列;结构色纪律不变) */
|
|
1841
|
+
/* LAYOUT_SLOTS + regionOf = 页型布局区域 IR(与 scripts/lib_layout_regions.js 同源) */
|
|
1842
|
+
var PW = 13.333, PH = 7.5, MX = 0.6, CW = PW - 2 * MX, EMU = 914400;
|
|
1843
|
+
var TS_BASE = {"coverTitle":44,"coverSub":18,"h1":30,"h2":17,"lead":14,"body":13,"caption":11,"micro":10};
|
|
1844
|
+
var MTS = {"presentation":{"coverTitle":44,"coverSub":18,"h1":30,"h2":17,"lead":14,"body":13,"caption":11,"micro":10},"research":{"coverTitle":36,"coverSub":14,"h1":22,"h2":13.5,"lead":11.5,"body":10.5,"caption":9,"micro":8.5},"architecture":{"coverTitle":44,"coverSub":19,"h1":30,"h2":17,"lead":14,"body":13,"caption":11,"micro":10}};
|
|
1845
|
+
var PT = {"layout":{"contentTop":2.35,"contentBottom":6.9,"contentBottomWithNote":6.4,"pageNumY":7.0},"common":{"headEyebrowY":0.48,"headTitleY":0.82,"headRuleY":1.72,"leadY":1.88,"bodyY":2.35,"bodyYWithLead":2.55,"pagerY":7.0},"cover":{"metaY":0.7,"titleY":2.4,"titleH":2.0,"subtitleY":4.5,"subtitleH":0.8},"closing":{"eyebrowY":0.7,"titleY":1.2,"titleH":1.2,"pointsY":3.0,"pointTitleH":0.6,"pointBodyH":1.2},"points":{"listY":2.5,"rowH":0.72,"rowHMin":0.5,"markSize":0.14,"metricY":4.7,"metricW":2.6,"metricValH":0.9,"metricCapY":5.6},"metrics":{"bandY":3.1,"cardH":2.3,"valY":3.45,"valH":1.1,"capY":4.55,"capH":0.7,"gap":0.3,"maxW":2.9},"table":{"y":2.5,"rowH":0.55,"rowHMin":0.32,"headRowH":0.5},"bar":{"hbarY0":2.6,"hbarY1":6.25,"chartX":0.6,"chartY":2.95,"chartW":1.2,"chartH":3.35,"noteY":6.55},"note":{"y":6.55,"h":0.4},"exhibit":{"badgeY":2.35,"chartY":2.75,"chartH":3.25,"soWhatY":6.05,"footnoteY":6.72},"twocol":{"colGap":0.5,"bodyH":3.6,"rowHMin":0.5},"hbar":{"labelW":1.9,"valW":1.0,"rowH":0.55,"barRatio":0.5},"diagram":{"layerBarW":1.35,"layerGap":0.15,"nodeGap":0.15,"bodyStartY":2.9,"maxLayerH":1.15,"nodeMaxW":2.2},"split":{"leftW":0.55,"rightW":0.415,"rightX":0.585,"pointsY":2.7,"rowH":0.78,"capY":2.55,"chartY":3.0,"chartH":3.7,"tableY":2.7,"tableRowH":0.5},"cards":{"startY":3.0,"gap":0.25,"maxH":2.0,"titleH":0.4},"timeline":{"labelY":2.95,"axisY":3.58,"dotY":3.51,"dotR":0.18,"nameY":3.9,"descY":4.45,"descH":1.2},"research":{"col3Gap":0.38,"matrixLabelW":1.5,"matrixCellH":0.85,"matrixY":2.2,"denseTableY":2.2,"denseRowH":0.42,"halfTableW":0.52,"halfChartX":0.57,"halfChartW":0.43},"arch":{"fullStartY":2.1,"fullEndY":6.9,"laneH":0.72,"laneHMin":0.5,"laneGap":0.14,"laneHeadW":1.3,"stepGap":0.12,"stepMaxW":1.9},"kpi":{"heroY":2.9,"heroW":0.52,"heroH":1.6,"dividerX":0.56,"metricY0":2.95,"metricRowH":0.85},"comparison":{"panelGap":0.4,"panelY":2.55,"panelH":3.4,"titleH":0.45,"rowH":0.62},"quote":{"markY":1.75,"textY":2.7,"textH":2.3,"authorY":5.25,"contextY":5.8},"donut":{"centerX":3.05,"centerY":4.6,"r":1.6,"holeR":0.95,"legendX":5.6,"legendRowH":0.55,"legendLabelW":3.4,"legendValW":1.3,"legendPctX":5.3,"legendPctW":1.3},"steps":{"y":3.05,"barH":1.35,"gap":0.12,"arrowW":0.3,"numH":0.3,"titleH":0.34,"pad":0.08,"maxPerRow":6},"heatmap":{"y":2.6,"labelW":1.7,"headH":0.42,"cellH":0.6,"cellGap":0.06,"legendW":1.6},"pyramid":{"y":2.35,"rowH":0.72,"gap":0.08,"minW":0.34,"labelW":3.2},"bullet":{"y":2.6,"rowH":0.8,"labelW":2.4,"valW":1.7,"barH":0.26},"image":{"y":2.3,"h":4.2,"halfW":0.56,"halfGap":0.06,"capY":6.6,"capH":0.3,"radius":0.09,"noteRowH":0.62,"gridGap":0.16,"gridCols":{"2":2,"3":3,"4":4,"6":3},"gridCapH":0.28,"compareGap":0.3,"compareTagH":0.26,"wallCellH":0.92,"wallGap":0.18,"wallMaxRows":2},"flagbar":{"hdH":0.26,"rowH":0.24,"maxRows":3,"gap":0.12},"sankey":{"y":2.45,"h":4.1,"nodeW":0.32,"nodeGap":0.14,"labelW":1.55,"minBandH":0.22,"maxNodes":12,"maxFlows":24},"treemap":{"y":2.45,"h":4.1,"gap":0.05,"labelMinH":0.34,"maxLeaves":16,"maxDepth":3},"boxplot":{"y":2.7,"h":3.7,"labelW":1.5,"groupGap":0.3,"boxMaxW":0.9,"whiskerCapW":0.34,"maxGroups":8},"network":{"y":2.4,"h":4.2,"nodeR":0.28,"nodeRMin":0.18,"labelMaxW":1.4,"maxNodes":18,"maxEdges":30},"marimekko":{"y":2.6,"h":3.75,"labelW":1.55,"colGap":0.06,"legendH":0.42,"maxCols":6,"maxSegs":4},"streamgraph":{"y":2.6,"h":3.75,"labelW":1.4,"bandMinH":0.12,"maxSeries":6}};
|
|
1846
|
+
var PRESETS = {
|
|
1847
|
+
'business-blue': { accent:'1A73E8', ink:'202124', body:'5F6368', faint:'80868B', bg:'FFFFFF', surface:'F8F9FA', line:'DADCE0', soft:'E8F0FE', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1848
|
+
'apple-mono': { accent:'1D1D1F', ink:'1D1D1F', body:'6E6E73', faint:'86868B', bg:'FFFFFF', surface:'F5F5F7', line:'D2D2D7', soft:'F5F5F7', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1849
|
+
'mckinsey': { accent:'003A70', ink:'101828', body:'475467', faint:'667085', bg:'FFFFFF', surface:'F2F4F7', line:'D0D5DD', soft:'EEF4FB', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1850
|
+
'brand-red': { accent:'D0021B', ink:'1F1A1B', body:'5D5456', faint:'8A8083', bg:'FFFFFF', surface:'FAF7F7', line:'E2D9DA', soft:'FBECEC', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1851
|
+
'warm-sand': { accent:'96681F', ink:'2A2521', body:'6B5F52', faint:'93836F', bg:'FBF9F6', surface:'F6F2EC', line:'E0D7C9', soft:'F3ECDD', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1852
|
+
'deep-teal': { accent:'0F6B5C', ink:'14201E', body:'4A5A57', faint:'73837F', bg:'FFFFFF', surface:'F2F6F5', line:'CFDEDC', soft:'E4F1EE', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1853
|
+
'graphite-dark': { accent:'0369A1', ink:'1A1D21', body:'4B5158', faint:'767C84', bg:'F4F5F7', surface:'ECEEF1', line:'CED1D6', soft:'E2EFF7', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1854
|
+
'indigo-violet': { accent:'5B5BD6', ink:'1B1A2E', body:'52506B', faint:'7A779A', bg:'FFFFFF', surface:'F4F4FB', line:'D7D7E8', soft:'ECECF9', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1855
|
+
'spectrum': { accent:'4563EF', ink:'171A21', body:'4D5566', faint:'7B8494', bg:'FBFCFE', surface:'F4F6FA', line:'D6DCE6', soft:'ECEEFF', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' }
|
|
1856
|
+
};
|
|
1857
|
+
var PRESETS_DARK = {
|
|
1858
|
+
'business-blue': { accent:'8AB4F8', ink:'E8EAED', body:'ADB4BD', faint:'868D96', bg:'0D0F13', surface:'191D23', line:'2F353D', soft:'16243A', onAccent:'0D1B2E', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1859
|
+
'apple-mono': { accent:'F5F5F7', ink:'F5F5F7', body:'A1A1A6', faint:'86868B', bg:'000000', surface:'161617', line:'38383D', soft:'1D1D1F', onAccent:'000000', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1860
|
+
'mckinsey': { accent:'7FB2E5', ink:'E4E7EC', body:'AAB3C2', faint:'8A94A6', bg:'0C1116', surface:'12181F', line:'2A3342', soft:'12233A', onAccent:'0C1A2E', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1861
|
+
'brand-red': { accent:'FF5A66', ink:'F3ECEC', body:'C4B6B7', faint:'9A8D8E', bg:'130D0E', surface:'1A1213', line:'37272A', soft:'331518', onAccent:'2A0D10', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1862
|
+
'warm-sand': { accent:'D9B36A', ink:'ECE5DB', body:'C4B8A8', faint:'9B8D7A', bg:'15120E', surface:'1D1913', line:'3A3128', soft:'2E2417', onAccent:'2A1F10', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1863
|
+
'deep-teal': { accent:'4FC3B0', ink:'E2ECEA', body:'B3C2BF', faint:'86948F', bg:'0D1312', surface:'131B1A', line:'293834', soft:'12312C', onAccent:'0C2420', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1864
|
+
'graphite-dark': { accent:'4DA3FF', ink:'EEF2F6', body:'B9C2CD', faint:'85909D', bg:'0B0E12', surface:'171D24', line:'2B3540', soft:'132841', onAccent:'07182B', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1865
|
+
'indigo-violet': { accent:'A5A4F0', ink:'E6E5F5', body:'B6B4D4', faint:'8B89B0', bg:'100F1C', surface:'171629', line:'322F52', soft:'232146', onAccent:'171631', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
1866
|
+
'spectrum': { accent:'8BA0F8', ink:'E8EBF2', body:'A7AFBF', faint:'7E8798', bg:'0E1116', surface:'171C26', line:'2B3342', soft:'1B2242', onAccent:'141A38', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' }
|
|
1867
|
+
};
|
|
1868
|
+
var STYLE_DATA_COLORS = {"business-blue":["1a73e8","3526bb","21a199","b77148","99983d"],"apple-mono":["1d1d1f","0071e3","0f7b6c","c2410c","7c3aed"],"mckinsey":["003a70","2826bb","21a18e","b76848","99903d"],"brand-red":["d0021b","bb6326","d634ae","40a182","438aa8"],"warm-sand":["96681f","839324","cf3b3e","4d88b2","4a47a4"],"deep-teal":["0f6b5c","2683bb","22aa3e","b74870","a85c43"],"graphite-dark":["0369a1","2637bb","1f9977","b75948","a18a40"],"indigo-violet":["5b5bd6","7432ae","4197c8","a08f4b","7ca04b"],"spectrum":["4563ef","5b26bb","269ebb","af8746","83993d"]};
|
|
1869
|
+
var STYLE_DATA_COLORS_DARK = {"business-blue":["8ab4f8","7966e0","88e7e5","d4ad91","cbce83"],"apple-mono":["f5f5f7","2997ff","2dd4bf","ff9f0a","bf5af2"],"mckinsey":["7fb2e5","6f6bdb","8ce3d8","d1a694","cbc586"],"brand-red":["ff5a66","e09e66","e788cb","91d4c2","83b4ce"],"warm-sand":["d9b36a","c2d571","df9391","97b5ce","8e89c7"],"deep-teal":["4fc3b0","7aaecd","97d8a4","c99cac","c29c8f"],"graphite-dark":["4da3ff","6c66e0","88e7dc","d4a691","cec983"],"indigo-violet":["a5a4f0","a967e0","89c3e7","d3c792","aece83"],"spectrum":["8ba0f8","9066e0","88d6e7","d4ba91","bdce83"]};
|
|
1870
|
+
var GRID = {"columns":12,"gutter":0.16,"colW":0.8644,"x":[0.6,1.6244,2.6488,3.6732,4.6976,5.722,6.7464,7.7708,8.7952,9.8196,10.844,11.8684,12.733],"y":[2.35,3.27,4.19,5.11,6.03,6.9],"safe":{"top":0.4,"bottom":0.4,"left":0.6,"right":0.6}};
|
|
1871
|
+
var TYPO = {"levels":[{"id":"C0","name":"封面/章节幕标题","role":"coverTitle","usage":"封面主标题、章节幕标题"},{"id":"T1","name":"页码/章节徽章","role":"caption","usage":"页码徽章、章节编号"},{"id":"T2","name":"页面主标题/结论标题","role":"h1","usage":"每页顶部结论句(research 行动标题)"},{"id":"T3","name":"副标题/语境说明","role":"lead","usage":"主标题下方导语"},{"id":"T4","name":"模块标题/图表标题","role":"h2","usage":"卡片标题、图表标题、面板标题"},{"id":"T5","name":"证据编号/轻量标签","role":"micro","usage":"Exhibit 编号、状态标签"},{"id":"T6","name":"证据块标题/小节标题","role":"h2","usage":"分栏小标题、证据块标题"},{"id":"T7","name":"正文解释段落","role":"body","usage":"证据解释、管理解读正文、表格正文"},{"id":"T8","name":"结论条文字","role":"body","usage":"so-what 结论条、核心结论框"},{"id":"T9","name":"SO WHAT 标签","role":"caption","usage":"so-what 标签行"},{"id":"T10","name":"SO WHAT 正文/业务含义","role":"body","usage":"so-what 正文、行动含义、表格行动项"},{"id":"T11","name":"图表轴/图例/刻度/微标签","role":"micro","usage":"坐标轴、图例、刻度、单位(禁用于表格正文与完整短句)"},{"id":"T12","name":"图表数据标签","role":"caption","usage":"折线点值、柱形标签、百分比标注"},{"id":"T13","name":"关键 KPI 大数字","role":"h1","usage":"KPI 大数字、hero 数值"},{"id":"T14","name":"注释/口径/来源/页脚","role":"micro","usage":"footnote、来源行、页脚、小页码"}],"tableSemanticRule":{"body":["T7","T10"],"microAllowed":["T11"],"microForbiddenFor":["表格正文","行动项","风险项","解释句","建议句","长项目符号","完整短句"]}};
|
|
1872
|
+
var CONT = {"pad":{"card":0.18,"panel":0.2,"cell":0.08,"band":0.16,"soWhat":0.16,"kpi":0.18,"chart":0.1,"head":0.05,"footer":0.05,"node":0.06},"minPad":0.06,"textMetrics":{"lineFactor":1.45,"emAsciiRatio":0.52},"overflowRule":"溢出处置优先级:① 优化内容(列表化/精炼/合并/拆页/换组合版式)→ ② 调整容器尺寸与行宽 → ③ 有限度缩小字号(见 fontShrink)→ ④ 仍装不下则拆页。禁止无限缩字号硬塞,也禁止把「精简」当第一手段砍掉决策必需信息。","fontShrink":{"ladder":[15,14,13.5,13,12.5,12,11.5,11,10.5,10,9.5,9,8.5],"maxShrinkSteps":4,"floorPt":{"presentation":10,"research":9,"architecture":10},"preferContentFirst":true,"warnBelow":9.5},"continuousTextRule":"语义连续的一句话 / so-what 主句 / 结论句 / 表格单元格正文不得拆成多个独立文本框(避免异常空格、断句、基线漂移);局部加粗高亮必须用同一文本框内富文本。"};
|
|
1873
|
+
var REG = {"bar":{"html":"svg-bars","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":160}},"hbar":{"html":"svg-hbars","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":100}},"stack":{"html":"svg-stack","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":160},"nativeOpt":{"barGrouping":"stacked"}},"stackline":{"html":"svg-stackline","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":40},"nativeOpt":{"barGrouping":"percentStacked"}},"line":{"html":"svg-line","pptx":"native","nativeType":"line","dataTable":"notes","min":{"vbHeightMin":160}},"dualline":{"html":"svg-dualline","pptx":"native","nativeType":"line","dataTable":"notes","min":{"vbHeightMin":160}},"area":{"html":"svg-area","pptx":"native","nativeType":"area","dataTable":"notes","min":{"vbHeightMin":160}},"donut":{"html":"svg-donut","pptx":"native","nativeType":"doughnut","dataTable":"notes","min":{"pxWidthMin":140}},"multidonut":{"html":"svg-mdonut","pptx":"native","nativeType":"doughnut","dataTable":"notes","min":{"pxWidthMin":140}},"pie":{"html":"svg-pie","pptx":"native","nativeType":"pie","dataTable":"notes","min":{"pxWidthMin":140}},"radar":{"html":"svg-radar","pptx":"native","nativeType":"radar","dataTable":"notes","min":{"pxWidthMin":220}},"scatter":{"html":"svg-scatter","pptx":"native","nativeType":"scatter","dataTable":"notes","min":{"vbHeightMin":180}},"bubble":{"html":"svg-bubble","pptx":"native","nativeType":"bubble","dataTable":"notes","min":{"vbHeightMin":180}},"waterfall":{"html":"svg-wf","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":170},"nativeTrick":"stacked+hiddenBase+connector"},"gauge":{"html":"svg-gauge","pptx":"native","nativeType":"doughnut","dataTable":"notes","min":{"pxWidthMin":180},"nativeTrick":"doughnut+firstSliceAng+hiddenRemainder"},"pareto":{"html":"svg-pareto","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":180},"nativeTrick":"multiType+bar+line"},"gantt":{"html":"svg-gantt","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":130},"path":{"forbidPresetShapes":true}},"vsbar":{"html":"svg-vsbar","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":100},"path":{"forbidPresetShapes":true}},"progress":{"html":"svg-progress","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":100},"path":{"forbidPresetShapes":true}},"sparkline":{"html":"svg-spark","pptx":"shape","dataTable":"off","min":{"vbHeightMin":20},"path":{"forbidPresetShapes":true}},"funnel":{"html":"svg-funnel","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":160},"path":{"minSamplePoints":8,"forbidPresetShapes":true}},"treemap":{"html":"svg-treemap","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":170},"path":{"forbidPresetShapes":true}},"marimekko":{"html":"svg-marimekko","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":160},"path":{"forbidPresetShapes":true}},"boxplot":{"html":"svg-boxplot","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":180},"path":{"forbidPresetShapes":true}},"network":{"html":"svg-network","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":200},"path":{"forbidPresetShapes":true}},"sankey":{"html":"svg-sankey","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":200},"path":{"minSamplePoints":16,"trackBothEdges":true,"forbidPresetShapes":true}},"streamgraph":{"html":"svg-stream","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":160},"path":{"minSamplePoints":24,"trackBothEdges":true,"forbidPresetShapes":true}},"slope":{"html":"svg-slope","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":180},"path":{"forbidPresetShapes":true}},"dumbbell":{"html":"svg-dumbbell","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":160},"path":{"forbidPresetShapes":true}},"lollipop":{"html":"svg-lollipop","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":140},"path":{"forbidPresetShapes":true}},"dotplot":{"html":"svg-dotplot","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":120},"path":{"forbidPresetShapes":true}},"bulletchart":{"html":"svg-bullet","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":140},"path":{"forbidPresetShapes":true}},"waffle":{"html":"svg-waffle","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":160},"path":{"forbidPresetShapes":true}},"radialbar":{"html":"svg-radialbar","pptx":"shape","dataTable":"notes","min":{"pxWidthMin":200},"path":{"minSamplePoints":16,"forbidPresetShapes":true}},"rose":{"html":"svg-rose","pptx":"shape","dataTable":"notes","min":{"pxWidthMin":180},"path":{"minSamplePoints":16,"forbidPresetShapes":true}},"candlestick":{"html":"svg-candle","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":180},"path":{"forbidPresetShapes":true}}};
|
|
1874
|
+
var DEEP = {"triggers":["高保真","1:1","一比一","精确还原","按图还原","正式交付","不能偏移","像素级","严格对照","逐页验收"],"complexCharts":["sankey","streamgraph","treemap","marimekko","boxplot","network","rose","radialbar","candlestick","funnel"],"checks":{"anchors":true,"containerOverflow":true,"renderCompare":true,"manifest":true},"renderCompare":{"engine":"soffice","fallback":"powerpoint-com","skipIfMissing":true,"dpi":96}};
|
|
1875
|
+
var IMG = {"layouts":["full","half","bleed","grid","compare","wall"],"multiLayouts":["grid","compare","wall"],"fitEnum":["cover","contain"],"fitDefault":"cover","formats":["png","jpeg","jpg","webp","svg"],"ratioDefault":{"full":"3:1","half":"4:3","bleed":"21:9","grid":"4:3","compare":"4:3","wall":"1:1"},"ratioCssClass":{"full":"media--r3-1","half":"media--r4-3","bleed":"media--r21-9","grid":"media--r4-3","compare":"media--r4-3","wall":"media--r1-1"},"recommendedSizePx":{"full":"2400×800","half":"1400×1050","bleed":"2560×1100","grid":"900×675","compare":"1400×1050","wall":"480×480"},"maxInlineBytes":1572864,"maxTotalInlineBytes":8388608,"maxPerPage":6,"placeholderLabel":"配图占位","placeholderHintPrefix":"建议 ","placeholderHintSuffix":"px","zeroExternal":true,"qualityRule":"位图优先提供 ≥2× 显示尺寸;截图用 16:9 或 4:3 锁定比例;数据图不要用截图(应用内联 SVG 图表,可随主题变色)。"};
|
|
1876
|
+
var LAYOUT_SLOTS = {"version":"0.1","pageTypes":{"points":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".grid.g-2 / .card / .ul","pptx":"points columns","minUnits":1},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar / footnoteLine"}]},"metrics":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".metric","pptx":"metric row","minUnits":4},{"id":"annotation","role":"annotation","required":false,"html":"口径行 / .sowhat","pptx":"footnoteLine / soWhatBar"}]},"kpi":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"hero","role":"primary","required":true,"html":".t-metric hero","pptx":"hero number"},{"id":"metrics","role":"secondary","required":false,"html":".metric","pptx":"support metrics"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar"}]},"exhibit":{"mode":["research","presentation"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead / .exhibit__hd","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart] / .exhibit","pptx":"nativeChart | shapeChart","channel":"registry"},{"id":"secondary","role":"secondary","required":false,"html":"要点列表","pptx":"side bullets"},{"id":"annotation","role":"annotation","required":true,"html":".exhibit__src / .sowhat","pptx":"footnoteLine / soWhatBar / notes"}]},"split":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"left","role":"primary","required":true,"html":"左区(points/table/image/chart)","pptx":"left zone"},{"id":"right","role":"secondary","required":true,"html":"右区(points/table/image/chart)","pptx":"right zone"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar"}]},"table":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".tbl-wrap table","pptx":"addTable","maxRows":{"presentation":8,"research":16}},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar / footnoteLine"}]},"bar":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart]","pptx":"nativeChart | shapeChart","channel":"registry"},{"id":"annotation","role":"annotation","required":true,"html":"note / dataTable","pptx":"footnoteLine / notes / inline table"}]},"donut":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=donut]","pptx":"addChart doughnut"},{"id":"annotation","role":"annotation","required":false,"html":"中心合计 / 图例 / .note","pptx":"center label / legend / notes"}]},"twocol":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".cols-2","pptx":"two text columns"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"threecol":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".cols-3","pptx":"three text columns"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"halftable":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"左表","pptx":"addTable left"},{"id":"secondary","role":"secondary","required":true,"html":"右图","pptx":"chart right"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"matrix":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".matrix","pptx":"matrix cells"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"comparison":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"left","role":"primary","required":true,"html":"左面板","pptx":"left panel"},{"id":"right","role":"secondary","required":true,"html":"右面板","pptx":"right panel"},{"id":"annotation","role":"annotation","required":false,"html":"verdict / .sowhat","pptx":"verdict bar"}]},"quote":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"金句正文","pptx":"quote text"}]},"timeline":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".tl","pptx":"timeline axis"}]},"steps":{"mode":["presentation","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".steps","pptx":"step row"}]},"cards":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".card grid","pptx":"card grid"}]},"heatmap":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".heat","pptx":"heat cells"}]},"bullet":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".bul","pptx":"bullet rows"}]},"pyramid":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".pyr","pptx":"pyramid layers"}]},"image":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".media*","pptx":"imageLayoutShapes"}]},"diagram":{"mode":["architecture","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".arch / layers","pptx":"layered nodes + edges","maxNodes":24,"maxLayers":4},{"id":"annotation","role":"annotation","required":false,"html":".arch__legend / .note","pptx":"legend chips / notes"}]},"lane":{"mode":["architecture","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".lane","pptx":"lanes + steps"}]},"sankey":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=sankey]","pptx":"infoSankey","maxNodes":12},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"treemap":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=treemap]","pptx":"infoTreemap"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"boxplot":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=boxplot]","pptx":"infoBoxplot"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"network":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=network]","pptx":"infoNetwork"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"marimekko":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=marimekko]","pptx":"infoMarimekko"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"streamgraph":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=streamgraph]","pptx":"infoStreamgraph"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]}}};
|
|
1877
|
+
|
|
1878
|
+
/* regionOf(pageType, slotId, opts) — 页型布局区域 IR(与 scripts/lib_layout_regions.js 同源) */
|
|
1879
|
+
function regionOf(pageType, slotId, opts) {
|
|
1880
|
+
var type = String(pageType || 'points'), slot = String(slotId || '');
|
|
1881
|
+
opts = opts || {};
|
|
1882
|
+
var L = PT.layout || {}, C = PT.common || {};
|
|
1883
|
+
var topDefault = C.bodyY || 2.5;
|
|
1884
|
+
var bottomDefault = opts.withNote ? (L.contentBottomWithNote || 6.4) : (L.contentBottom || 6.9);
|
|
1885
|
+
if (slot === 'head' || slot === 'chrome') {
|
|
1886
|
+
return { x: MX, y: C.headEyebrowY || 0.55, w: CW,
|
|
1887
|
+
h: Math.max(0.4, (C.headRuleY || 1.95) - (C.headEyebrowY || 0.55)),
|
|
1888
|
+
titleY: C.headTitleY, ruleY: C.headRuleY, leadY: C.leadY, bodyY: C.bodyY };
|
|
1889
|
+
}
|
|
1890
|
+
if (slot === 'annotation' || slot === 'soWhat') {
|
|
1891
|
+
var sy = (PT.exhibit && PT.exhibit.soWhatY != null) ? PT.exhibit.soWhatY : 6.05;
|
|
1892
|
+
return { x: MX, y: sy, w: CW, h: 0.62 };
|
|
1893
|
+
}
|
|
1894
|
+
if (slot === 'footnote') {
|
|
1895
|
+
var fy = (PT.exhibit && PT.exhibit.footnoteY != null) ? PT.exhibit.footnoteY : ((PT.note && PT.note.y) || 6.55);
|
|
1896
|
+
return { x: MX, y: fy, w: CW - 1.2, h: (PT.note && PT.note.h) || 0.32 };
|
|
1897
|
+
}
|
|
1898
|
+
var P = PT[type] || {};
|
|
1899
|
+
if (type === 'exhibit' && slot === 'primary') {
|
|
1900
|
+
var ey = P.chartY || 2.95;
|
|
1901
|
+
return { x: MX, y: ey, w: CW, h: Math.max(1.2, (L.contentBottomWithNote || 6.4) - ey), badgeY: P.badgeY };
|
|
1902
|
+
}
|
|
1903
|
+
if (type === 'metrics' && slot === 'primary') {
|
|
1904
|
+
return { x: MX, y: P.bandY || 3.1, w: CW, h: P.cardH || 2.3, gap: P.gap, maxW: P.maxW,
|
|
1905
|
+
valY: P.valY, valH: P.valH, capY: P.capY, capH: P.capH };
|
|
1906
|
+
}
|
|
1907
|
+
if (type === 'table' && slot === 'primary') {
|
|
1908
|
+
var ty = P.y || topDefault;
|
|
1909
|
+
return { x: MX, y: ty, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - ty) };
|
|
1910
|
+
}
|
|
1911
|
+
if (type === 'bar' && slot === 'primary') {
|
|
1912
|
+
var by = P.chartY || 2.95;
|
|
1913
|
+
var bb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
1914
|
+
return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
|
|
1915
|
+
}
|
|
1916
|
+
if (type === 'bar' && slot === 'hbar') {
|
|
1917
|
+
var y0 = P.hbarY0 || 2.6;
|
|
1918
|
+
var y1 = opts.bottom != null ? opts.bottom : (P.hbarY1 || 6.25);
|
|
1919
|
+
return { x: MX, y: y0, w: CW, h: Math.max(1.2, y1 - y0) };
|
|
1920
|
+
}
|
|
1921
|
+
if (type === 'points' && (slot === 'primary' || slot === 'list')) {
|
|
1922
|
+
var ly = P.listY || topDefault;
|
|
1923
|
+
return { x: MX, y: ly, w: CW, h: Math.max(0.8, (opts.bottom != null ? opts.bottom : bottomDefault) - ly) };
|
|
1924
|
+
}
|
|
1925
|
+
if (type === 'split' && (slot === 'left' || slot === 'right')) {
|
|
1926
|
+
var st = P.chartY || topDefault;
|
|
1927
|
+
var sb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
1928
|
+
if (slot === 'left') return { x: MX, y: st, w: CW * (P.leftW || 0.55), h: Math.max(1.2, sb - st) };
|
|
1929
|
+
return { x: MX + CW * (P.rightX != null ? P.rightX : 0.585), y: st, w: CW * (P.rightW || 0.415), h: Math.max(1.2, sb - st) };
|
|
1930
|
+
}
|
|
1931
|
+
if (type === 'twocol' && (slot === 'primary' || slot === 'left' || slot === 'right')) {
|
|
1932
|
+
var gap = P.colGap || 0.5, colW = (CW - gap) / 2;
|
|
1933
|
+
var tt = opts.top != null ? opts.top : topDefault;
|
|
1934
|
+
var tb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
1935
|
+
if (slot === 'right') return { x: MX + colW + gap, y: tt, w: colW, h: Math.max(1, tb - tt) };
|
|
1936
|
+
return { x: MX, y: tt, w: colW, h: Math.max(1, tb - tt) };
|
|
1937
|
+
}
|
|
1938
|
+
if ((type === 'diagram' || type === 'lane') && slot === 'primary') {
|
|
1939
|
+
var arch = PT.arch || {};
|
|
1940
|
+
var dy = (opts.mode === 'architecture' && arch.fullStartY != null) ? arch.fullStartY : (P.bodyStartY || 2.9);
|
|
1941
|
+
var db = (opts.mode === 'architecture' && arch.fullEndY != null) ? arch.fullEndY
|
|
1942
|
+
: (opts.bottom != null ? opts.bottom : bottomDefault);
|
|
1943
|
+
return { x: MX, y: dy, w: CW, h: Math.max(1.2, db - dy) };
|
|
1944
|
+
}
|
|
1945
|
+
if (type === 'kpi' && (slot === 'hero' || slot === 'primary')) {
|
|
1946
|
+
return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
|
|
1947
|
+
dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
|
|
1948
|
+
}
|
|
1949
|
+
if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
|
|
1950
|
+
var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
|
|
1951
|
+
if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
|
|
1952
|
+
return { x: MX, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
|
|
1953
|
+
}
|
|
1954
|
+
if (type === 'quote' && slot === 'primary') {
|
|
1955
|
+
return { x: MX + 1.05, y: P.textY || 2.7, w: CW - 2.1, h: P.textH || 2.3 };
|
|
1956
|
+
}
|
|
1957
|
+
if (type === 'heatmap' && slot === 'primary') {
|
|
1958
|
+
var hy = P.y || 2.6;
|
|
1959
|
+
return { x: MX, y: hy, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - hy) };
|
|
1960
|
+
}
|
|
1961
|
+
if (type === 'bullet' && slot === 'primary') {
|
|
1962
|
+
var uy = P.y || 2.6;
|
|
1963
|
+
return { x: MX, y: uy, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - uy) };
|
|
1964
|
+
}
|
|
1965
|
+
if (type === 'pyramid' && slot === 'primary') {
|
|
1966
|
+
var py2 = P.y || 2.35;
|
|
1967
|
+
return { x: MX, y: py2, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - py2) };
|
|
1968
|
+
}
|
|
1969
|
+
if (type === 'steps' && slot === 'primary') {
|
|
1970
|
+
return { x: MX, y: P.y || 3.05, w: CW, h: P.barH || 1.35 };
|
|
1971
|
+
}
|
|
1972
|
+
if (type === 'cards' && slot === 'primary') {
|
|
1973
|
+
var cy2 = P.startY || 3.0;
|
|
1974
|
+
return { x: MX, y: cy2, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - cy2) };
|
|
1975
|
+
}
|
|
1976
|
+
if (type === 'timeline' && slot === 'primary') {
|
|
1977
|
+
return { x: MX, y: P.labelY || 2.95, w: CW, h: Math.max(1, bottomDefault - (P.labelY || 2.95)) };
|
|
1978
|
+
}
|
|
1979
|
+
if (type === 'image' && slot === 'primary') {
|
|
1980
|
+
var iy2 = Math.max(P.y || 2.3, opts.top || 0);
|
|
1981
|
+
return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, (opts.bottom != null ? opts.bottom : bottomDefault) - iy2)) };
|
|
1982
|
+
}
|
|
1983
|
+
if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
|
|
1984
|
+
var rs = PT.research || {};
|
|
1985
|
+
return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
|
|
1986
|
+
}
|
|
1987
|
+
if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
|
|
1988
|
+
var rs2 = PT.research || {};
|
|
1989
|
+
return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
|
|
1990
|
+
}
|
|
1991
|
+
if (type === 'matrix' && slot === 'primary') {
|
|
1992
|
+
var rm = PT.research || {};
|
|
1993
|
+
var my = rm.matrixY || 2.3;
|
|
1994
|
+
return { x: MX, y: my, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - my) };
|
|
1995
|
+
}
|
|
1996
|
+
var INFO = { sankey:1, treemap:1, boxplot:1, network:1, marimekko:1, streamgraph:1 };
|
|
1997
|
+
if (INFO[type] && slot === 'primary') {
|
|
1998
|
+
var iy3 = P.y || topDefault;
|
|
1999
|
+
var ih3 = Math.max(1.2, Math.min(P.h || 4.1, (opts.bottom != null ? opts.bottom : bottomDefault) - iy3));
|
|
2000
|
+
return { x: MX, y: iy3, w: CW, h: ih3 };
|
|
2001
|
+
}
|
|
2002
|
+
return { x: MX, y: topDefault, w: CW, h: Math.max(0.8, bottomDefault - topDefault) };
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
/* __TOPPPT_CONSTANTS_END__ */
|
|
2006
|
+
|
|
2007
|
+
/* __TOPPPT_SCHEMA_START__ */
|
|
2008
|
+
/* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
|
|
2009
|
+
/* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
|
|
2010
|
+
var MODEL_SCHEMA = {"version":"0.1","modes":["presentation","research","architecture"],"model":{"required":["title","sections:array","closing.title","closing.points:array"],"optional":["mode","style","theme","subtitle","meta","agenda:array"],"agendaMin":{"presentation":3,"research":3,"architecture":0},"sectionsMin":1,"sectionsRecommended":3,"agendaComfortMax":16},"commonSectionFields":["eyebrow","title","lead","soWhat","footnote","flags","image","exhibitNo","layoutPreset"],"flagsHint":"flags = 待核实/待二次修改条目字符串数组(如 '2027 增速 xx%(口径未定)');渲染为 accent 强调色标注行,提示用户核对。","imageHint":"image = 素材图片/配图占位对象。三选一必填:src(用户图,data: 内联或相对路径,零外链铁律禁 http(s))|items(多图版式:grid/compare/wall,元素 {src?, alt?, caption?, placeholder?})|placeholder:true(无图时出配图占位,原生形状渲染、pictures 不增)。其余可选:layout(full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec.layouts)、fit(cover 默认 / contain)、caption、alt、hint、ratio。","imageLayoutHint":"layout 与 HTML 版式对应:full=版心全宽图|half=左图右注(points 为右栏注解)|bleed=通栏出血|grid=多图网格(items 2/3/4/6 张)|compare=双图 A/B 对比(items 2 张)|wall=Logo 墙(items 3–12 张小图)。占位符标签文本由双引擎按 imageSpec 拼同一串,保证 A/B 通道逐页文本一致。","chartTypes":["bar","hbar","stack","stackline","line","dualline","area","donut","multidonut","pie","radar","scatter","bubble","waterfall","gauge","pareto","funnel","gantt","vsbar","progress","sparkline","slope","dumbbell","lollipop","dotplot","bulletchart","waffle","radialbar","rose","candlestick"],"chartTypesHint":"chart.type 可取值 = 本表(30 类:16 原生 + 14 形状还原)。其余 6 类复杂信息图(sankey/treemap/boxplot/network/marimekko/streamgraph)是**专属页型**而非 chart.type——它们要按列/段/节点等结构化载荷表达,故走 sections[].type。不变量:charts.registry.types == chartTypes ∪ 上述 6 类页型(sync_runtime.py 校验)。","chartDataTable":{"enum":["notes","inline","appendix","off"],"default":"notes","rule":"非原生图表(registry.pptx=shape)的 dataTable 不得为 off;缺失时按 registry 默认值补齐。"},"pageTypes":{"points":{"label":"要点列表页","modes":["presentation","research","architecture"],"required":["points:array"],"optional":["metrics:array"]},"metrics":{"label":"指标带页(4–6 个核心数字)","modes":["presentation","research"],"required":["metrics:array"]},"kpi":{"label":"大数指标页(hero 大数字 + 支撑指标行)","modes":["presentation","research"],"required":["hero:array"],"optional":["metrics:array"]},"table":{"label":"对比表页","modes":["presentation","research","architecture"],"required":["table.head:array","table.rows:array"],"optional":["table.colW:array"]},"timeline":{"label":"时间线 / 路线页","modes":["presentation","research"],"required":["phases:array"]},"steps":{"label":"步骤条页(N 步横排 + 箭头,可分组)","modes":["presentation","research","architecture"],"required":["steps:array"],"optional":["groups:array","note"]},"bar":{"label":"图表页(chart.type 见 chartTypes;native 通道走原生数据图表,shape 通道走高保真形状还原 + 数据表)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.start:array","chart.target:array","chart.dataTable","note"]},"donut":{"label":"环形图页(原生 pie 楔形 + 图例)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.unit","chart.centerLabel","chart.colors:array","chart.dataTable","note"]},"heatmap":{"label":"热力矩阵页(行 × 列 + 强度色阶)","modes":["presentation","research"],"required":["rowHeads:array","colHeads:array","cells:array"],"optional":["unit","scaleLabel:array","note"]},"bullet":{"label":"达成对比页(实际 vs 目标条)","modes":["presentation","research"],"required":["items:array"],"optional":["unit","max","note"]},"pyramid":{"label":"金字塔页(层级递进,顶层最窄)","modes":["presentation","research","architecture"],"required":["levels:array"],"optional":["note"]},"image":{"label":"素材图片页(full/half/bleed/grid/compare/wall,效果与位置锁定;支持配图占位)","modes":["presentation","research","architecture"],"required":["anyof:image.src:str|image.items:array|image.placeholder"],"optional":["image.caption","image.alt","image.layout","image.fit","image.ratio","image.hint","points:array","note"]},"cards":{"label":"卡片网格页(cards 元素须为 {title, points:[[k,v]…]},不接受元组)","modes":["presentation","research"],"required":["cards:array"],"optional":["columns"]},"split":{"label":"双区组合页(left / right 各可为 要点|图表|表格|图片:type = points(默认)| table | image | 图表类型名;图表/表格字段直接挂在对应侧)","modes":["presentation","research"],"required":["anyof:left.points:array|left.type:str"],"optional":["left.cap","left.image","left.labels:array","left.values:array","left.series:array","left.points:array","left.max","left.unit","left.colors:array","left.dataTable","left.head:array","left.rows:array","left.colW:array","right.type","right.cap","right.image","right.labels:array","right.values:array","right.series:array","right.points:array","right.max","right.unit","right.colors:array","right.dataTable","right.head:array","right.rows:array","right.colW:array"]},"comparison":{"label":"对比页(左右双栏 + 可选结论条)","modes":["presentation","research"],"required":["left.title","left.points:array","right.title","right.points:array"],"optional":["verdict"]},"quote":{"label":"引用 / 金句页(深色全幅)","modes":["presentation","research"],"required":["quote","author"],"optional":["context"]},"diagram":{"label":"分层架构页(architecture 模式走全幅几何)","modes":["presentation","research","architecture"],"required":["layers:array"],"optional":["legend:array"]},"exhibit":{"label":"Exhibit 编号图表页(research R2)","modes":["research"],"required":["chart.labels:array","chart.values:array"],"optional":["exhibitNo","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"twocol":{"label":"双栏论证页(research R1)","modes":["research"],"required":["paragraphs:array"]},"threecol":{"label":"三栏证据页(research R6)","modes":["research"],"required":["paragraphs:array"]},"halftable":{"label":"半表半图页(research R7)","modes":["research"],"required":["table.head:array","table.rows:array","chart.labels:array","chart.values:array"],"optional":["table.colW:array","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"matrix":{"label":"矩阵图页(research R8)","modes":["research"],"required":["rowHeads:array","colHeads:array","cells:array"]},"lane":{"label":"泳道页(architecture A2)","modes":["architecture"],"required":["lanes:array"]},"sankey":{"label":"桑基图页(节点-流带,流向与流量)","modes":["research","architecture"],"required":["flows:array"],"optional":["unit","chart.dataTable","note"]},"treemap":{"label":"树图页(面积编码的层级构成)","modes":["research","architecture"],"required":["items:array"],"optional":["unit","chart.dataTable","note"]},"boxplot":{"label":"箱线图页(分布对比:min/q1/median/q3/max)","modes":["research","architecture"],"required":["groups:array"],"optional":["unit","chart.dataTable","note"]},"network":{"label":"关系网络页(节点-边拓扑)","modes":["research","architecture"],"required":["nodes:array","edges:array"],"optional":["chart.dataTable","note"]},"marimekko":{"label":"马赛克图页(列宽 × 列高双重编码)","modes":["research","architecture"],"required":["cols:array","cells:array"],"optional":["unit","chart.dataTable","note"]},"streamgraph":{"label":"流带图页(时间上的构成演变)","modes":["research","architecture"],"required":["series:array"],"optional":["labels:array","chart.dataTable","note"]}}};
|
|
2011
|
+
/* __TOPPPT_SCHEMA_END__ */
|
|
2012
|
+
|
|
2013
|
+
var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
|
|
2014
|
+
/* 三模式独立排版比例尺:调用点给 presentation 基准 pt → 取最接近的 TS_BASE 角色 →
|
|
2015
|
+
查 MTS[mode][role]。research 是咨询密排比例尺(正文 10.5pt),不再是简单 ×0.8。
|
|
2016
|
+
未知模式回落 presentation 档(MTS 与 mode 取值同源于 model-schema.json 的 modes)。 */
|
|
2017
|
+
function modeSize(base) {
|
|
2018
|
+
var mts = (typeof MTS !== 'undefined' && MTS) ? (MTS[_mode] || MTS.presentation) : null;
|
|
2019
|
+
if (!mts) return base;
|
|
2020
|
+
var best = null, bd = 1e9;
|
|
2021
|
+
for (var k in TS_BASE) {
|
|
2022
|
+
if (!TS_BASE.hasOwnProperty(k)) continue;
|
|
2023
|
+
var d = Math.abs(TS_BASE[k] - base);
|
|
2024
|
+
if (d < bd) { bd = d; best = mts[k]; }
|
|
2025
|
+
}
|
|
2026
|
+
/* 钳制与取整必须与 build_pptx.js 的 sz() 逐字一致——同一张表配不同后处理 = 两通道字号漂移 */
|
|
2027
|
+
return Math.max(8.5, Math.round((best == null ? base : best) * 2) / 2);
|
|
2028
|
+
}
|
|
2029
|
+
/* 编码色板:9 套风格**各有自己的** c1–c5(单源 styleDataColors / styleDataColorsDark)。
|
|
2030
|
+
与 HTML 侧的 .f-c1~c5 语义类同源;结构色纪律不受影响——本表只用于数据系列。 */
|
|
2031
|
+
function dataColors(style, theme) {
|
|
2032
|
+
var dark = (theme === 'dark');
|
|
2033
|
+
var map = (dark && typeof STYLE_DATA_COLORS_DARK !== 'undefined' && STYLE_DATA_COLORS_DARK)
|
|
2034
|
+
? STYLE_DATA_COLORS_DARK
|
|
2035
|
+
: (typeof STYLE_DATA_COLORS !== 'undefined' ? STYLE_DATA_COLORS : null);
|
|
2036
|
+
if (!map) return null;
|
|
2037
|
+
var c = map[style];
|
|
2038
|
+
return (c && c.length) ? c : null; // null = 用 S 的中性色(在调用处映射)
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
/* ── 版式安全边界(与 build_pptx.js 同一常量单源) ── */
|
|
2042
|
+
var LAY = PT.layout || {};
|
|
2043
|
+
var CONTENT_TOP = (LAY.contentTop != null) ? LAY.contentTop : 2.5;
|
|
2044
|
+
var CONTENT_BOTTOM = (LAY.contentBottom != null) ? LAY.contentBottom : 6.9;
|
|
2045
|
+
|
|
2046
|
+
/* ── 自适应排版工具(与 build_pptx.js 同算法,保证两通道字号/换行一致) ── */
|
|
2047
|
+
function estLines(text, widthIn, fontPt) {
|
|
2048
|
+
var usable = Math.max(24, widthIn * 72 - 8), w = 0, lines = 1;
|
|
2049
|
+
var s = String(text == null ? '' : text);
|
|
2050
|
+
for (var i = 0; i < s.length; i++) {
|
|
2051
|
+
if (s[i] === '\n') { lines++; w = 0; continue; }
|
|
2052
|
+
var cw = /[\x00-\xff]/.test(s[i]) ? fontPt * 0.52 : fontPt;
|
|
2053
|
+
if (w + cw > usable) { lines++; w = cw; } else { w += cw; }
|
|
2054
|
+
}
|
|
2055
|
+
return lines;
|
|
2056
|
+
}
|
|
2057
|
+
function estTextH(text, widthIn, fontPt, lineFactor) {
|
|
2058
|
+
return estLines(text, widthIn, fontPt) * fontPt * (lineFactor || 1.45) / 72;
|
|
2059
|
+
}
|
|
2060
|
+
var FONT_LADDER = [15, 14, 13.5, 13, 12.5, 12, 11.5, 11, 10.5, 10, 9.5, 9, 8.5];
|
|
2061
|
+
/* 有限缩字号(v8.2,与 build_pptx.js 同策略):先优化内容/组合/拆页,最后才缩字号。
|
|
2062
|
+
maxShrinkSteps 限制下探步数;floorPt 按模式绝对下限。 */
|
|
2063
|
+
function modeFloorPtA(mode) {
|
|
2064
|
+
var floors = (typeof CONT !== 'undefined' && CONT && CONT.fontShrink && CONT.fontShrink.floorPt) ||
|
|
2065
|
+
{ presentation: 10, research: 9, architecture: 10 };
|
|
2066
|
+
var m = String(mode || 'presentation');
|
|
2067
|
+
return (floors[m] != null) ? floors[m] : 10;
|
|
2068
|
+
}
|
|
2069
|
+
function fitFont(items, widthIn, availIn, opts) {
|
|
2070
|
+
opts = opts || {};
|
|
2071
|
+
var max = (opts.max == null) ? 99 : opts.max, lf = opts.lineFactor || 1.45;
|
|
2072
|
+
var gapF = (opts.gapFactor == null) ? 0.5 : opts.gapFactor;
|
|
2073
|
+
var mode = (typeof CONTENT !== 'undefined' && CONTENT && CONTENT.mode) ||
|
|
2074
|
+
(typeof TOPPPT_MODE !== 'undefined' ? TOPPPT_MODE : 'presentation');
|
|
2075
|
+
var floor = (opts.floor != null) ? opts.floor : modeFloorPtA(mode);
|
|
2076
|
+
var maxSteps = (opts.maxShrinkSteps != null) ? opts.maxShrinkSteps
|
|
2077
|
+
: ((typeof CONT !== 'undefined' && CONT && CONT.fontShrink && CONT.fontShrink.maxShrinkSteps != null)
|
|
2078
|
+
? CONT.fontShrink.maxShrinkSteps : 4);
|
|
2079
|
+
var baseLadder = (typeof CONT !== 'undefined' && CONT && CONT.fontShrink && CONT.fontShrink.ladder) || FONT_LADDER;
|
|
2080
|
+
var raw = baseLadder.filter(function (v) { return v <= max && v >= floor; });
|
|
2081
|
+
var needH = function (f) {
|
|
2082
|
+
var h = 0;
|
|
2083
|
+
for (var i = 0; i < items.length; i++) h += estTextH(items[i], widthIn, f, lf) + f * gapF / 72;
|
|
2084
|
+
return h + (opts.pad || 0);
|
|
2085
|
+
};
|
|
2086
|
+
for (var li = 0; li < raw.length; li++) {
|
|
2087
|
+
if (needH(raw[li]) <= availIn) return raw[li];
|
|
2088
|
+
}
|
|
2089
|
+
var preferred = raw.length ? raw[0] : floor;
|
|
2090
|
+
var prefIdx = baseLadder.indexOf(preferred);
|
|
2091
|
+
var limited = baseLadder.slice(prefIdx, prefIdx + 1 + maxSteps)
|
|
2092
|
+
.filter(function (v) { return v >= floor && v <= max; });
|
|
2093
|
+
for (var lj = 0; lj < limited.length; lj++) {
|
|
2094
|
+
if (needH(limited[lj]) <= availIn) return limited[lj];
|
|
2095
|
+
}
|
|
2096
|
+
if (limited.length) return limited[limited.length - 1];
|
|
2097
|
+
return floor;
|
|
2098
|
+
}
|
|
2099
|
+
function cols(total, n, gap) {
|
|
2100
|
+
var w = (total - (n - 1) * (gap || 0)) / Math.max(1, n);
|
|
2101
|
+
return { w: w, step: w + (gap || 0) };
|
|
2102
|
+
}
|
|
2103
|
+
/* 行高收敛:与 build_pptx.js 同算法(宁可行高更小也不越界) */
|
|
2104
|
+
function fitRowH(avail, n, pref, floor) {
|
|
2105
|
+
var lo = (floor == null) ? 0.22 : floor;
|
|
2106
|
+
return Math.max(lo, Math.min(pref, avail / Math.max(1, n)));
|
|
2107
|
+
}
|
|
2108
|
+
function chartBottom(hasSoWhat, hasFootnote) {
|
|
2109
|
+
if (hasFootnote) return PT.exhibit.footnoteY - 0.12;
|
|
2110
|
+
if (hasSoWhat) return PT.exhibit.soWhatY - 0.12;
|
|
2111
|
+
return CONTENT_BOTTOM;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/* ── XML 工具 ── */
|
|
2115
|
+
function esc(s) {
|
|
2116
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
2117
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
2118
|
+
}
|
|
2119
|
+
function E(v) { return Math.round(v * EMU); }
|
|
2120
|
+
function solid(hex) { return '<a:solidFill><a:srgbClr val="' + hex + '"/></a:solidFill>'; }
|
|
2121
|
+
|
|
2122
|
+
var _id = 10;
|
|
2123
|
+
function nid() { return ++_id; }
|
|
2124
|
+
|
|
2125
|
+
/* 文本框:paras = [[{t,b,sz,col,font,spc}…]…](每个内层数组=一段,可混排多 run)
|
|
2126
|
+
opts.lineSpacing / opts.spaceBefore 为基准 pt(与 build_pptx.js 的 lineSpacing/paraSpaceBefore 同源),
|
|
2127
|
+
序列化时统一过 modeSize 映射,保证两通道行距一致。 */
|
|
2128
|
+
function txSp(x, y, w, h, paras, opts) {
|
|
2129
|
+
opts = opts || {};
|
|
2130
|
+
var pPr = '';
|
|
2131
|
+
if (opts.lineSpacing) pPr += '<a:lnSpc><a:spcPts val="' + Math.round(modeSize(opts.lineSpacing) * 100) + '"/></a:lnSpc>';
|
|
2132
|
+
if (opts.spaceBefore) pPr += '<a:spcBef><a:spcPts val="' + Math.round(modeSize(opts.spaceBefore) * 100) + '"/></a:spcBef>';
|
|
2133
|
+
var pPrXml = (opts.align || pPr)
|
|
2134
|
+
? '<a:pPr' + (opts.align ? ' algn="' + opts.align + '"' : '') + '>' + pPr + '</a:pPr>'
|
|
2135
|
+
: '';
|
|
2136
|
+
var body = paras.map(function (runs) {
|
|
2137
|
+
var rs = runs.map(function (r) {
|
|
2138
|
+
var pr = '<a:rPr lang="zh-CN" sz="' + Math.round(modeSize(r.sz) * 100) + '"' +
|
|
2139
|
+
(r.b ? ' b="1"' : '') + (r.spc ? ' spc="' + r.spc + '"' : '') + '>' +
|
|
2140
|
+
solid(r.col) +
|
|
2141
|
+
'<a:latin typeface="' + esc(r.font) + '"/><a:ea typeface="' + esc(r.font) + '"/></a:rPr>';
|
|
2142
|
+
return '<a:r>' + pr + '<a:t>' + esc(r.t) + '</a:t></a:r>';
|
|
2143
|
+
}).join('');
|
|
2144
|
+
return '<a:p>' + pPrXml + rs + '</a:p>';
|
|
2145
|
+
}).join('');
|
|
2146
|
+
var fill = opts.fill ? solid(opts.fill) : '<a:noFill/>';
|
|
2147
|
+
return '<p:sp><p:nvSpPr><p:cNvPr id="' + nid() + '" name="tx"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>' +
|
|
2148
|
+
'<p:spPr><a:xfrm><a:off x="' + E(x) + '" y="' + E(y) + '"/><a:ext cx="' + E(w) + '" cy="' + E(h) + '"/></a:xfrm>' +
|
|
2149
|
+
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>' + fill + '<a:ln><a:noFill/></a:ln></p:spPr>' +
|
|
2150
|
+
'<p:txBody><a:bodyPr wrap="square" anchor="' + (opts.anchor || 't') + '"><a:normAutofit/></a:bodyPr><a:lstStyle/>' +
|
|
2151
|
+
body + '</p:txBody></p:sp>';
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
/* 形状:rect / roundRect / ellipse / pie(opts.av = avLst 原始内联 XML,供 donut 楔形角度) */
|
|
2155
|
+
function shape(geom, x, y, w, h, fillHex, opts) {
|
|
2156
|
+
opts = opts || {};
|
|
2157
|
+
var av;
|
|
2158
|
+
if (opts.av) av = '<a:avLst>' + opts.av + '</a:avLst>';
|
|
2159
|
+
else if (geom === 'roundRect') av = '<a:avLst><a:gd name="adj" fmla="val ' + (opts.adj || 8000) + '"/></a:avLst>';
|
|
2160
|
+
else av = '<a:avLst/>';
|
|
2161
|
+
var ln = opts.line
|
|
2162
|
+
? '<a:ln w="9525">' + (opts.dash ? '<a:prstDash val="dash"/>' : '') + solid(opts.line) + '</a:ln>'
|
|
2163
|
+
: '<a:ln><a:noFill/></a:ln>';
|
|
2164
|
+
return '<p:sp><p:nvSpPr><p:cNvPr id="' + nid() + '" name="' + (opts.name || 'sh') + '"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>' +
|
|
2165
|
+
'<p:spPr><a:xfrm><a:off x="' + E(x) + '" y="' + E(y) + '"/><a:ext cx="' + E(Math.max(w, 0.01)) + '" cy="' + E(Math.max(h, 0.01)) + '"/></a:xfrm>' +
|
|
2166
|
+
'<a:prstGeom prst="' + geom + '">' + av + '</a:prstGeom>' + solid(fillHex) + ln + '</p:spPr>' +
|
|
2167
|
+
'<p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="zh-CN"/></a:p></p:txBody></p:sp>';
|
|
2168
|
+
}
|
|
2169
|
+
/* pie 楔形 avLst:角度(度)× 60000;donut 页型两通道共用同一角度拆分规则 */
|
|
2170
|
+
function pieAv(a1, a2) {
|
|
2171
|
+
return '<a:gd name="adj1" fmla="val ' + Math.round(a1 * 60000) + '"/>' +
|
|
2172
|
+
'<a:gd name="adj2" fmla="val ' + Math.round(a2 * 60000) + '"/>';
|
|
2173
|
+
}
|
|
2174
|
+
/* 环形图(donut 页型):原生 pie 楔形 + 背景色圆(挖孔)+ 中心文字 + 右侧图例。
|
|
2175
|
+
角度约定:12 点钟(270°)起顺时针;跨 360° 的楔形拆成两段(≤359.9 封顶)。 */
|
|
2176
|
+
function donutWedges(labels, values) {
|
|
2177
|
+
var total = 0;
|
|
2178
|
+
(values || []).forEach(function (v) { total += Math.max(0, v); });
|
|
2179
|
+
var segs = [], a = 270;
|
|
2180
|
+
(values || []).forEach(function (v, i) {
|
|
2181
|
+
var frac = total > 0 ? Math.max(0, v) / total : 0;
|
|
2182
|
+
var b = a + frac * 360;
|
|
2183
|
+
var s = a % 360, e = b % 360;
|
|
2184
|
+
if (frac <= 0) { a = b; return; }
|
|
2185
|
+
if (b - a >= 359.9) { segs.push([0, 359.9, i]); }
|
|
2186
|
+
else if (b <= 360) { segs.push([a, Math.min(b, 359.9), i]); }
|
|
2187
|
+
else { segs.push([a, 359.9, i]); if (e > 0.1) segs.push([0, e, i]); }
|
|
2188
|
+
a = b;
|
|
2189
|
+
});
|
|
2190
|
+
return segs;
|
|
2191
|
+
}
|
|
2192
|
+
function donutChart(sh, ec, S, dcols) {
|
|
2193
|
+
var d = PT.donut, labels = ec.labels || [], values = ec.values || [];
|
|
2194
|
+
var segs = donutWedges(labels, values);
|
|
2195
|
+
var bx = d.centerX - d.r, by = d.centerY - d.r, bw = d.r * 2;
|
|
2196
|
+
segs.forEach(function (sg) {
|
|
2197
|
+
sh.push(shape('pie', bx, by, bw, bw, dcols[sg[2] % dcols.length], { av: pieAv(sg[0], sg[1]) }));
|
|
2198
|
+
});
|
|
2199
|
+
var hb = d.holeR * 2;
|
|
2200
|
+
sh.push(shape('ellipse', d.centerX - d.holeR, d.centerY - d.holeR, hb, hb, S.bg));
|
|
2201
|
+
var total = 0; values.forEach(function (v) { total += Math.max(0, v); });
|
|
2202
|
+
var totalStr = (Math.round(total * 10) / 10) + (ec.unit || '');
|
|
2203
|
+
sh.push(txSp(d.centerX - d.holeR, d.centerY - 0.42, hb, 0.5,
|
|
2204
|
+
[[{ t: totalStr, sz: 20, b: 1, col: S.ink, font: S.fontDisplay }]], { align: 'ctr', anchor: 'ctr' }));
|
|
2205
|
+
sh.push(txSp(d.centerX - d.holeR, d.centerY + 0.08, hb, 0.35,
|
|
2206
|
+
[[{ t: ec.centerLabel || '合计', sz: 9.5, col: S.faint, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
2207
|
+
var n = Math.max(1, labels.length);
|
|
2208
|
+
var rowH = Math.min(d.legendRowH, 3.4 / n);
|
|
2209
|
+
var ly0 = d.centerY - (n * rowH) / 2 + 0.1;
|
|
2210
|
+
var sum2 = total || 1;
|
|
2211
|
+
labels.forEach(function (lb, i) {
|
|
2212
|
+
var v = values[i] || 0;
|
|
2213
|
+
var y = ly0 + i * rowH;
|
|
2214
|
+
sh.push(shape('ellipse', d.legendX, y + 0.08, 0.16, 0.16, dcols[i % dcols.length]));
|
|
2215
|
+
sh.push(txSp(d.legendX + 0.3, y, d.legendLabelW, rowH,
|
|
2216
|
+
[[{ t: lb, sz: 12, b: 1, col: S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
2217
|
+
sh.push(txSp(d.legendX + 3.8, y, d.legendValW, rowH,
|
|
2218
|
+
[[{ t: String(v) + (ec.unit || ''), sz: 12, b: 1, col: S.body, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
2219
|
+
sh.push(txSp(d.legendPctX, y, d.legendPctW, rowH,
|
|
2220
|
+
[[{ t: (Math.round(v / sum2 * 1000) / 10) + '%', sz: 10.5, col: S.faint, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
function donutColors(style, S, theme) {
|
|
2224
|
+
var d = dataColors(style, theme);
|
|
2225
|
+
return d || [S.accent, S.faint, S.body, S.line];
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
/* 表格(graphicFrame + a:tbl);rows[0]=表头;行高按可用高度自适应(与 B 通道同规则) */
|
|
2229
|
+
function table(x, y, w, colW, rows, S, opts) {
|
|
2230
|
+
opts = opts || {};
|
|
2231
|
+
var n = rows.length;
|
|
2232
|
+
var maxRowH = (opts.maxRowH == null) ? PT.table.rowH : opts.maxRowH;
|
|
2233
|
+
var minRowH = (opts.minRowH == null) ? PT.table.rowHMin : opts.minRowH;
|
|
2234
|
+
var availH = (opts.availH == null) ? (CONTENT_BOTTOM - y) : opts.availH;
|
|
2235
|
+
var rowH = Math.max(minRowH, Math.min(maxRowH, availH / Math.max(1, n)));
|
|
2236
|
+
var dense = rowH < 0.42;
|
|
2237
|
+
var total = colW.reduce(function (a, b) { return a + b; }, 0);
|
|
2238
|
+
var grid = colW.map(function (cw) { return '<a:gridCol w="' + E(cw) + '"/>'; }).join('');
|
|
2239
|
+
var trs = rows.map(function (row, ri) {
|
|
2240
|
+
var tcs = row.map(function (cell) {
|
|
2241
|
+
var isHead = ri === 0;
|
|
2242
|
+
var fz = isHead ? (dense ? 11 : 13) : (dense ? 10 : 12.5);
|
|
2243
|
+
return '<a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r>' +
|
|
2244
|
+
'<a:rPr lang="zh-CN" sz="' + Math.round(modeSize(fz) * 100) + '"' + (isHead ? ' b="1"' : '') + '>' +
|
|
2245
|
+
solid(isHead ? S.onAccent : S.body) +
|
|
2246
|
+
'<a:latin typeface="' + S.font + '"/><a:ea typeface="' + S.font + '"/></a:rPr>' +
|
|
2247
|
+
'<a:t>' + esc(cell) + '</a:t></a:r></a:p></a:txBody>' +
|
|
2248
|
+
'<a:tcPr marL="91440" marR="91440" marT="45720" marB="45720" anchor="ctr">' +
|
|
2249
|
+
solid(isHead ? S.accent : S.bg) + '</a:tcPr></a:tc>';
|
|
2250
|
+
}).join('');
|
|
2251
|
+
return '<a:tr h="' + E(rowH) + '">' + tcs + '</a:tr>';
|
|
2252
|
+
}).join('');
|
|
2253
|
+
/* OOXML 规范:p:graphicFrame 的 graphic 子元素属 DrawingML 命名空间(a:graphic,与 pptxgenjs 一致) */
|
|
2254
|
+
return '<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="' + nid() + '" name="tbl"/>' +
|
|
2255
|
+
'<p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr><p:nvPr/></p:nvGraphicFramePr>' +
|
|
2256
|
+
'<p:xfrm><a:off x="' + E(x) + '" y="' + E(y) + '"/><a:ext cx="' + E(w) + '" cy="' + E(rowH * rows.length) + '"/></p:xfrm>' +
|
|
2257
|
+
'<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">' +
|
|
2258
|
+
'<a:tbl><a:tblPr firstRow="1" bandRow="0"/><a:tblGrid>' + grid + '</a:tblGrid>' + trs + '</a:tbl>' +
|
|
2259
|
+
'</a:graphicData></a:graphic></p:graphicFrame>';
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
/* so-what 结论条 / 页脚来源行(research 页通用可选件,与 HTML 同源版式;区域走布局 IR) */
|
|
2263
|
+
function soWhatBar(sh, text, S) {
|
|
2264
|
+
var R = (typeof regionOf === 'function') ? regionOf('exhibit', 'annotation')
|
|
2265
|
+
: { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
|
|
2266
|
+
/* 与 B 通道同算法:长结论条有限缩字号,优先保容器不溢出 */
|
|
2267
|
+
var bodyW = Math.max(2.5, R.w - 0.44 - 1.35);
|
|
2268
|
+
var fzB = fitFont([String(text || '')], bodyW, R.h - 0.14, { max: 12.5, gapFactor: 0.2, maxShrinkSteps: 3 });
|
|
2269
|
+
sh.push(shape('rect', R.x, R.y, R.w, R.h, S.soft));
|
|
2270
|
+
sh.push(shape('rect', R.x, R.y, 0.06, R.h, S.accent));
|
|
2271
|
+
sh.push(txSp(R.x + 0.22, R.y + 0.06, R.w - 0.44, R.h - 0.12,
|
|
2272
|
+
[[{ t: 'SO WHAT ', sz: 11, b: 1, col: S.accent, font: S.font, spc: 150 },
|
|
2273
|
+
{ t: text, sz: fzB, col: S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
2274
|
+
}
|
|
2275
|
+
function footnoteLine(sh, text, S) {
|
|
2276
|
+
var R = (typeof regionOf === 'function') ? regionOf('exhibit', 'footnote')
|
|
2277
|
+
: { x: MX, y: PT.exhibit.footnoteY, w: CW - 1.2, h: 0.32 };
|
|
2278
|
+
sh.push(txSp(R.x, R.y, R.w, R.h,
|
|
2279
|
+
[[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
|
|
2280
|
+
}
|
|
2281
|
+
/* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
|
|
2282
|
+
function flagBar(sh, items, S, yTop) {
|
|
2283
|
+
var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
|
|
2284
|
+
var list = (items || []).slice(0, F.maxRows).map(String);
|
|
2285
|
+
if (!list.length) return 0;
|
|
2286
|
+
var h = F.hdH + list.length * F.rowH + 0.06;
|
|
2287
|
+
var y = (yTop != null) ? yTop : (CONTENT_BOTTOM - h);
|
|
2288
|
+
sh.push(shape('rect', MX, y, CW, h, S.soft));
|
|
2289
|
+
sh.push(shape('rect', MX, y, 0.055, h, S.accent));
|
|
2290
|
+
sh.push(txSp(MX + 0.22, y + 0.02, CW - 0.44, F.hdH,
|
|
2291
|
+
[[{ t: '待核实 · 需二次确认', sz: 9.5, b: 1, col: S.accent, font: S.font, spc: 100 }]]));
|
|
2292
|
+
list.forEach(function (t, i) {
|
|
2293
|
+
sh.push(txSp(MX + 0.24, y + F.hdH + i * F.rowH, CW - 0.5, F.rowH,
|
|
2294
|
+
[[{ t: '· ' + t, sz: 9.5, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2295
|
+
});
|
|
2296
|
+
return h;
|
|
2297
|
+
}
|
|
2298
|
+
/* 素材图片:预览引擎只产出 slide XML(无 media part)→ 以中性图位块表示;
|
|
2299
|
+
配图占位(placeholder)额外发射标签文本——标签串由 imageSpec 常量拼接,与 B 通道**同串**
|
|
2300
|
+
(cross_verify 逐页比对)。真实图片不发射任何文本,保证 A/B 文本集合一致。 */
|
|
2301
|
+
function imgPlaceholderLabel(layout, multi) {
|
|
2302
|
+
var IS = (typeof IMG !== 'undefined' && IMG) || {};
|
|
2303
|
+
var label = IS.placeholderLabel || '配图占位';
|
|
2304
|
+
if (multi) return label;
|
|
2305
|
+
var size = (IS.recommendedSizePx || {})[layout];
|
|
2306
|
+
if (!size) return label;
|
|
2307
|
+
return label + ' · ' + (IS.placeholderHintPrefix || '建议 ') + size + (IS.placeholderHintSuffix || 'px');
|
|
2308
|
+
}
|
|
2309
|
+
function addImageEl(sh, src, S, x, y, w, h, opts) {
|
|
2310
|
+
opts = opts || {};
|
|
2311
|
+
var ph = !!opts.placeholder || !src;
|
|
2312
|
+
/* name="img"/"imgph":预览模态据此把图位替换为真实图片(ui.js renderSlide),
|
|
2313
|
+
不影响文本集合(cross_verify 只比文本)。 */
|
|
2314
|
+
sh.push(shape('roundRect', x, y, w, h, S.surface,
|
|
2315
|
+
{ line: S.line, dash: ph, adj: 4000, name: ph ? 'imgph' : 'img' }));
|
|
2316
|
+
if (ph) {
|
|
2317
|
+
sh.push(txSp(x + 0.08, y + h / 2 - 0.22, Math.max(0.4, w - 0.16), 0.44,
|
|
2318
|
+
[[{ t: imgPlaceholderLabel(opts.layout || 'full', !!opts.multi), sz: 11, b: 1, col: S.faint, font: S.font }]],
|
|
2319
|
+
{ align: 'ctr', anchor: 'ctr' }));
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
/* 版式比例(imageSpec.ratioDefault 或 image.ratio,形如 "3:1")→ 数值宽高比。
|
|
2323
|
+
双引擎同源:HTML 用同比例的 .media--r* 锁定类,PPTX 用本函数算高度,版式才一致。 */
|
|
2324
|
+
function imgRatio(layout, img) {
|
|
2325
|
+
var IS = (typeof IMG !== 'undefined' && IMG) || {};
|
|
2326
|
+
var raw = String((img && img.ratio) || (IS.ratioDefault || {})[layout] || '3:1');
|
|
2327
|
+
var m = raw.match(/^\s*(\d+(?:\.\d+)?)\s*[:/×x]\s*(\d+(?:\.\d+)?)/);
|
|
2328
|
+
if (!m) return 3;
|
|
2329
|
+
var a = parseFloat(m[1]), b = parseFloat(m[2]);
|
|
2330
|
+
return (a > 0 && b > 0) ? a / b : 3;
|
|
2331
|
+
}
|
|
2332
|
+
/* 图片版式族(与 HTML .media / .media-grid / .media-compare / .media-wall 同源):
|
|
2333
|
+
full 版心全宽|half 左图右注|bleed 通栏出血|grid 多图网格|compare 双图对比|wall Logo 墙。
|
|
2334
|
+
每版式按 imageSpec.ratioDefault 锁定比例;空间不足时按可用高度收敛并**垂直居中**
|
|
2335
|
+
(避免"图贴顶、下方大片留白")。返回图注基准 Y。占位符走原生形状,pictures 不增。 */
|
|
2336
|
+
function imageLayoutShapes(sh, S, layout, img, items, points, isPh, fit, y0, y1) {
|
|
2337
|
+
var IM = PT.image || {};
|
|
2338
|
+
var gap = IM.gridGap || 0.16;
|
|
2339
|
+
var iy = y0;
|
|
2340
|
+
var ih = Math.max(1.0, y1 - iy);
|
|
2341
|
+
var capY = iy + ih;
|
|
2342
|
+
var ratio = imgRatio(layout, img);
|
|
2343
|
+
function one(x, y, w, h, it, multi) {
|
|
2344
|
+
var ph = isPh || !(it && it.src);
|
|
2345
|
+
addImageEl(sh, (it && it.src) || '', S, x, y, w, h,
|
|
2346
|
+
{ placeholder: ph, layout: layout, multi: multi, fit: fit });
|
|
2347
|
+
}
|
|
2348
|
+
if (layout === 'grid' && items.length) {
|
|
2349
|
+
var n = Math.min(items.length, (IMG && IMG.maxPerPage) || 6);
|
|
2350
|
+
var colsN = (IM.gridCols || {})[String(n)] || Math.min(3, n);
|
|
2351
|
+
var rowsN = Math.max(1, Math.ceil(n / colsN));
|
|
2352
|
+
var capH = IM.gridCapH || 0.28;
|
|
2353
|
+
var cellW = (CW - (colsN - 1) * gap) / colsN;
|
|
2354
|
+
var capRows = rowsN * (capH + 0.04);
|
|
2355
|
+
var cellH = Math.min(cellW / ratio, Math.max(0.5, (ih - (rowsN - 1) * gap - capRows) / rowsN));
|
|
2356
|
+
var blockH = rowsN * cellH + (rowsN - 1) * gap + capRows;
|
|
2357
|
+
var gy = iy + Math.max(0, (ih - blockH) / 2);
|
|
2358
|
+
items.slice(0, n).forEach(function (it, i) {
|
|
2359
|
+
var cx = MX + (i % colsN) * (cellW + gap);
|
|
2360
|
+
var cy = gy + Math.floor(i / colsN) * (cellH + gap + capH + 0.04);
|
|
2361
|
+
var cap = (it && it.caption) || '';
|
|
2362
|
+
one(cx, cy, cellW, cellH, it, true);
|
|
2363
|
+
if (cap) sh.push(txSp(cx, cy + cellH + 0.02, cellW, capH,
|
|
2364
|
+
[[{ t: cap, sz: 10, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2365
|
+
});
|
|
2366
|
+
capY = gy + blockH;
|
|
2367
|
+
} else if (layout === 'compare' && items.length >= 2) {
|
|
2368
|
+
var cgap = IM.compareGap || 0.3;
|
|
2369
|
+
var cw2 = (CW - cgap) / 2;
|
|
2370
|
+
var capH2 = 0.34;
|
|
2371
|
+
var ih2 = Math.min(cw2 / ratio, Math.max(0.5, ih - capH2));
|
|
2372
|
+
var cy2 = iy + Math.max(0, (ih - ih2 - capH2) / 2);
|
|
2373
|
+
[0, 1].forEach(function (i) {
|
|
2374
|
+
var it = items[i] || {};
|
|
2375
|
+
var cx2 = MX + i * (cw2 + cgap);
|
|
2376
|
+
one(cx2, cy2, cw2, ih2, it, true);
|
|
2377
|
+
var cap2 = it.caption || '';
|
|
2378
|
+
if (cap2) sh.push(txSp(cx2, cy2 + ih2 + 0.02, cw2, 0.3,
|
|
2379
|
+
[[{ t: cap2, sz: 10, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2380
|
+
});
|
|
2381
|
+
capY = cy2 + ih2 + capH2;
|
|
2382
|
+
} else if (layout === 'wall' && items.length) {
|
|
2383
|
+
var wgap = IM.wallGap || 0.18;
|
|
2384
|
+
var wn = Math.min(items.length, 12);
|
|
2385
|
+
var rowsW = Math.max(1, Math.min(IM.wallMaxRows || 2, Math.ceil(wn / 6)));
|
|
2386
|
+
var perRow = Math.max(1, Math.ceil(wn / rowsW));
|
|
2387
|
+
var cellW2 = (CW - (perRow - 1) * wgap) / perRow;
|
|
2388
|
+
var cellH2 = Math.min(IM.wallCellH || 0.92, cellW2 / ratio,
|
|
2389
|
+
Math.max(0.4, (ih - (rowsW - 1) * wgap) / rowsW));
|
|
2390
|
+
var blockH2 = rowsW * cellH2 + (rowsW - 1) * wgap;
|
|
2391
|
+
var wy = iy + Math.max(0, (ih - blockH2) / 2);
|
|
2392
|
+
items.slice(0, wn).forEach(function (it, i) {
|
|
2393
|
+
var cx3 = MX + (i % perRow) * (cellW2 + wgap);
|
|
2394
|
+
var cy3 = wy + Math.floor(i / perRow) * (cellH2 + wgap);
|
|
2395
|
+
one(cx3, cy3, cellW2, cellH2, it, true);
|
|
2396
|
+
});
|
|
2397
|
+
capY = wy + blockH2;
|
|
2398
|
+
} else if (layout === 'half') {
|
|
2399
|
+
var iw = CW * (IM.halfW || 0.56);
|
|
2400
|
+
var ihh = Math.min(iw / ratio, ih);
|
|
2401
|
+
one(MX, iy, iw, ihh, img, false);
|
|
2402
|
+
var rxI = MX + iw + CW * (IM.halfGap || 0.06);
|
|
2403
|
+
var rwI = CW - iw - CW * (IM.halfGap || 0.06);
|
|
2404
|
+
var ptsI = points || [];
|
|
2405
|
+
var rowHI = fitRowH(ih - 0.1, ptsI.length, IM.noteRowH || 0.62, 0.3);
|
|
2406
|
+
var fzI = fitFont(ptsI.map(function (p) { return (p[0] || '') + (p[1] || ''); }), rwI - 0.3, ih,
|
|
2407
|
+
{ max: 14, gapFactor: 0.6 });
|
|
2408
|
+
ptsI.forEach(function (pt, i) {
|
|
2409
|
+
var lyI = iy + i * rowHI;
|
|
2410
|
+
sh.push(shape('rect', rxI, lyI + rowHI / 2 - 0.06, 0.12, 0.12, S.accent));
|
|
2411
|
+
sh.push(txSp(rxI + 0.26, lyI, rwI - 0.3, rowHI - 0.04,
|
|
2412
|
+
[[{ t: (pt[0] || '') + ' ', sz: fzI, b: 1, col: S.ink, font: S.font },
|
|
2413
|
+
{ t: pt[1] || '', sz: fzI - 1, col: S.body, font: S.font }]], { anchor: 't' }));
|
|
2414
|
+
});
|
|
2415
|
+
capY = iy + Math.max(ihh, ih * 0.5);
|
|
2416
|
+
} else if (layout === 'bleed') {
|
|
2417
|
+
var bh = Math.min(ih, PW / ratio);
|
|
2418
|
+
var by = iy + Math.max(0, (ih - bh) / 2);
|
|
2419
|
+
one(0, by, PW, bh, img, false);
|
|
2420
|
+
capY = by + bh;
|
|
2421
|
+
} else {
|
|
2422
|
+
var fh = Math.min(ih, CW / ratio);
|
|
2423
|
+
var fy = iy + Math.max(0, (ih - fh) / 2);
|
|
2424
|
+
one(MX, fy, CW, fh, img, false);
|
|
2425
|
+
capY = fy + fh;
|
|
2426
|
+
}
|
|
2427
|
+
return capY;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
/* ══ 图表形状渲染(预览/回归用近似版;交付通道 B 用原生可编辑数据图表) ══
|
|
2431
|
+
A/B 两通道的「文本集合」必须一致(cross_verify 逐页比对):
|
|
2432
|
+
类别标签 + 数值标签都渲染为文本,数值标签由 cross_verify 的数值 token 规则过滤。 */
|
|
2433
|
+
function valText(v, unit) { return String(v) + (unit || ''); }
|
|
2434
|
+
/* 横向条形(hbar / halftable / exhibit hbar) */
|
|
2435
|
+
function hbarRows(sh, ec, S, y0, y1, dcols, x0, w) {
|
|
2436
|
+
x0 = (x0 == null) ? MX : x0;
|
|
2437
|
+
w = (w == null) ? CW : w;
|
|
2438
|
+
var labels = ec.labels || [], values = ec.values || [];
|
|
2439
|
+
var n = Math.max(1, labels.length);
|
|
2440
|
+
var rowH = Math.min(PT.hbar.rowH, (y1 - y0) / n);
|
|
2441
|
+
var barH = Math.max(0.16, rowH * (PT.hbar.barRatio || 0.5));
|
|
2442
|
+
var labelW = PT.hbar.labelW, valW = PT.hbar.valW;
|
|
2443
|
+
var trackX = x0 + labelW + 0.1, trackW = w - labelW - valW - 0.2;
|
|
2444
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
2445
|
+
var top = values.length ? Math.max.apply(null, values) : 0;
|
|
2446
|
+
labels.forEach(function (lb, i) {
|
|
2447
|
+
var v = values[i] || 0;
|
|
2448
|
+
var y = y0 + i * rowH + (rowH - barH) / 2;
|
|
2449
|
+
var hot = v === top;
|
|
2450
|
+
var dc = dcols ? dcols[i % 5] : null;
|
|
2451
|
+
sh.push(txSp(x0, y - 0.06, labelW - 0.05, barH + 0.12,
|
|
2452
|
+
[[{ t: lb, sz: 11, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2453
|
+
sh.push(shape('rect', trackX, y, trackW, barH, S.surface, { line: S.line }));
|
|
2454
|
+
var bw2 = Math.max(0.02, v / vmax * trackW);
|
|
2455
|
+
sh.push(shape('rect', trackX, y, bw2, barH, dc || (hot ? S.accent : S.faint)));
|
|
2456
|
+
sh.push(txSp(trackX + trackW + 0.08, y - 0.06, valW, barH + 0.12,
|
|
2457
|
+
[[{ t: valText(v, ec.unit), sz: 11.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
/* 竖柱 / 堆叠柱 */
|
|
2461
|
+
function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
2462
|
+
var labels = ec.labels || [], values = ec.values || [];
|
|
2463
|
+
var nb = Math.max(1, labels.length);
|
|
2464
|
+
var baseY = y + h, plotW = w, step = plotW / nb;
|
|
2465
|
+
var bw = Math.min(1.1, step * 0.55);
|
|
2466
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
2467
|
+
var top = values.length ? Math.max.apply(null, values) : 0;
|
|
2468
|
+
sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
|
|
2469
|
+
values.forEach(function (v, i) {
|
|
2470
|
+
var hh = Math.max(0.05, v / vmax * (h - 0.5));
|
|
2471
|
+
var bx = x + i * step + (step - bw) / 2;
|
|
2472
|
+
var hot = v === top;
|
|
2473
|
+
var dc = dcols ? dcols[i % 5] : null;
|
|
2474
|
+
sh.push(shape('roundRect', bx, baseY - hh, bw, hh, dc || (hot ? S.accent : S.faint), { adj: 6000 }));
|
|
2475
|
+
sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
|
|
2476
|
+
[[{ t: valText(v, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
|
|
2477
|
+
sh.push(txSp(bx - 0.3, baseY + 0.08, bw + 0.6, 0.32,
|
|
2478
|
+
[[{ t: labels[i], sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
/* 折线 / 双折线 / 面积 */
|
|
2482
|
+
function lineShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
2483
|
+
var labels = ec.labels || [];
|
|
2484
|
+
var series = (ec.series && ec.series.length) ? ec.series : [{ name: '数值', values: ec.values || [] }];
|
|
2485
|
+
var n = Math.max(1, labels.length);
|
|
2486
|
+
var all = [];
|
|
2487
|
+
series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
|
|
2488
|
+
var vmax = ec.max || Math.max.apply(null, all.concat([1]));
|
|
2489
|
+
var plotH = h - 0.5, baseY = y + h;
|
|
2490
|
+
sh.push(shape('rect', x, baseY, w, 0.02, S.line));
|
|
2491
|
+
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
2492
|
+
series.forEach(function (se, si) {
|
|
2493
|
+
var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
|
|
2494
|
+
var pts = (se.values || []).map(function (v, i) {
|
|
2495
|
+
var px = x + (n > 1 ? i * stepX : w / 2);
|
|
2496
|
+
var py = baseY - Math.max(0.02, (Number(v) || 0) / vmax * plotH);
|
|
2497
|
+
return [px, py];
|
|
2498
|
+
});
|
|
2499
|
+
if (!pts.length) return;
|
|
2500
|
+
for (var i = 0; i < pts.length - 1; i++) {
|
|
2501
|
+
sh.push(lineSeg(pts[i][0], pts[i][1], pts[i + 1][0], pts[i + 1][1], col, 2.5));
|
|
2502
|
+
}
|
|
2503
|
+
pts.forEach(function (p) {
|
|
2504
|
+
sh.push(shape('ellipse', p[0] - 0.055, p[1] - 0.055, 0.11, 0.11, col));
|
|
2505
|
+
});
|
|
2506
|
+
});
|
|
2507
|
+
labels.forEach(function (lb, i) {
|
|
2508
|
+
var px = x + (n > 1 ? i * stepX : w / 2);
|
|
2509
|
+
sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
|
|
2510
|
+
[[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
/* 直线段(用细长矩形近似,避免自定义几何——两通道渲染一致) */
|
|
2514
|
+
function lineSeg(x1, y1, x2, y2, hex, thick) {
|
|
2515
|
+
var dx = x2 - x1, dy = y2 - y1;
|
|
2516
|
+
var len = Math.sqrt(dx * dx + dy * dy);
|
|
2517
|
+
var ang = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
2518
|
+
var cx = (x1 + x2) / 2, cy = (y1 + y2) / 2;
|
|
2519
|
+
var t = thick / 72;
|
|
2520
|
+
var rot = '<a:xfrm rot="' + Math.round(ang * 60000) + '">';
|
|
2521
|
+
return '<p:sp><p:nvSpPr><p:cNvPr id="' + nid() + '" name="ln"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>' +
|
|
2522
|
+
'<p:spPr>' + rot + '<a:off x="' + E(cx - len / 2) + '" y="' + E(cy - t / 2) + '"/>' +
|
|
2523
|
+
'<a:ext cx="' + E(len) + '" cy="' + E(t) + '"/></a:xfrm>' +
|
|
2524
|
+
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>' + solid(hex) + '<a:ln><a:noFill/></a:ln></p:spPr>' +
|
|
2525
|
+
'<p:txBody><a:bodyPr/><a:lstStyle/><a:p/></p:txBody></p:sp>';
|
|
2526
|
+
}
|
|
2527
|
+
/* 散点 / 气泡(A 通道近似):只画点、不产文本——与 B 通道 XY 图表(类别为数值,过滤后为空)一致 */
|
|
2528
|
+
function xyDots(sh, ec, S, x, y, w, h) {
|
|
2529
|
+
var values = (ec.values || []).map(Number);
|
|
2530
|
+
var pts = (ec.points && ec.points.length) ? ec.points : null;
|
|
2531
|
+
var n = pts ? pts.length : Math.max(1, values.length);
|
|
2532
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
2533
|
+
var baseY = y + h, plotH = Math.max(0.3, h - 0.3);
|
|
2534
|
+
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
2535
|
+
sh.push(shape('rect', x, baseY, w, 0.02, S.line));
|
|
2536
|
+
for (var i = 0; i < n; i++) {
|
|
2537
|
+
var px = pts ? x + (Number(pts[i][0]) || 0) / vmax * w : x + i * stepX;
|
|
2538
|
+
var py = pts ? baseY - (Number(pts[i][1]) || 0) / vmax * plotH : baseY - (values[i] || 0) / vmax * plotH;
|
|
2539
|
+
sh.push(shape('ellipse', px - 0.07, py - 0.07, 0.14, 0.14, S.accent));
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
/* 仪表盘(A 通道近似):270° 弧段 + 数值(B 通道 gauge 的类别为数值,过滤后两端一致,故此处不产标签文本) */
|
|
2543
|
+
function gaugeApprox(sh, ec, S, x, y, w, h) {
|
|
2544
|
+
var v = Number((ec.values || [0])[0]) || 0;
|
|
2545
|
+
var max = Number(ec.max) || 100;
|
|
2546
|
+
var frac = Math.max(0, Math.min(1, v / max));
|
|
2547
|
+
var r = Math.min(w, h) / 2 - 0.12;
|
|
2548
|
+
var cx = x + w / 2, cy = y + h / 2;
|
|
2549
|
+
var steps = 40, startA = Math.PI * 0.75, sweep = Math.PI * 1.5;
|
|
2550
|
+
var px = null, py = null;
|
|
2551
|
+
for (var i = 0; i <= steps; i++) {
|
|
2552
|
+
var a = startA + sweep * (i / steps);
|
|
2553
|
+
var qx = cx + r * Math.cos(a), qy = cy + r * Math.sin(a);
|
|
2554
|
+
if (px !== null) sh.push(lineSeg(px, py, qx, qy, (i / steps) <= frac ? S.accent : S.line, 5));
|
|
2555
|
+
px = qx; py = qy;
|
|
2556
|
+
}
|
|
2557
|
+
sh.push(txSp(x, cy - 0.2, w, 0.42,
|
|
2558
|
+
[[{ t: String(v) + (ec.unit || ''), sz: 18, b: 1, col: S.accent, font: S.fontDisplay }]], { align: 'ctr', anchor: 'ctr' }));
|
|
2559
|
+
}
|
|
2560
|
+
/* 饼 / 环(A 通道近似):横向占比条 + 图例(类别标签为文本,数值由 cross_verify 过滤) */
|
|
2561
|
+
function pieApprox(sh, ec, S, x, y, w, h, dcols) {
|
|
2562
|
+
var labels = ec.labels || [], values = (ec.values || []).map(Number);
|
|
2563
|
+
var total = values.reduce(function (a, b) { return a + b; }, 0) || 1;
|
|
2564
|
+
var barH = Math.max(0.32, Math.min(0.62, h * 0.22));
|
|
2565
|
+
var barY = y + h / 2 - barH / 2 - 0.2;
|
|
2566
|
+
var cx0 = x;
|
|
2567
|
+
labels.forEach(function (lb, i) {
|
|
2568
|
+
var bw = (values[i] || 0) / total * w;
|
|
2569
|
+
sh.push(shape('rect', cx0, barY, bw, barH,
|
|
2570
|
+
dcols ? dcols[i % dcols.length] : (i === 0 ? S.accent : S.faint)));
|
|
2571
|
+
cx0 += bw;
|
|
2572
|
+
});
|
|
2573
|
+
var lx = x;
|
|
2574
|
+
labels.forEach(function (lb, i) {
|
|
2575
|
+
sh.push(shape('ellipse', lx, barY + barH + 0.16, 0.1, 0.1,
|
|
2576
|
+
dcols ? dcols[i % dcols.length] : (i === 0 ? S.accent : S.faint)));
|
|
2577
|
+
sh.push(txSp(lx + 0.14, barY + barH + 0.12, 1.2, 0.24, [[{ t: lb, sz: 10.5, col: S.body, font: S.font }]]));
|
|
2578
|
+
sh.push(txSp(lx + 1.36, barY + barH + 0.12, 0.8, 0.24,
|
|
2579
|
+
[[{ t: String(values[i] != null ? values[i] : '') + (ec.unit || ''), sz: 10.5, b: 1, col: S.body, font: S.font }]]));
|
|
2580
|
+
lx += 2.3;
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
/* 迷你折线(A 近似):无轴无标签,仅趋势线 + 末值(与 B 的 shapeSpark 同文本集) */
|
|
2584
|
+
function sparkApprox(sh, ec, S, x, y, w, h) {
|
|
2585
|
+
var vals = (ec.values || []).map(Number);
|
|
2586
|
+
var vmax = ec.max || Math.max.apply(null, vals.concat([1]));
|
|
2587
|
+
var n = vals.length;
|
|
2588
|
+
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
2589
|
+
var baseY = y + h - 0.06, plotH = Math.max(0.2, h - 0.18);
|
|
2590
|
+
var px = 0, py = 0;
|
|
2591
|
+
vals.forEach(function (v, i) {
|
|
2592
|
+
var cx = x + i * stepX, cy = baseY - Math.max(0.02, (v / vmax) * plotH);
|
|
2593
|
+
if (i > 0) sh.push(lineSeg(px, py, cx, cy, S.accent, 2));
|
|
2594
|
+
px = cx; py = cy;
|
|
2595
|
+
});
|
|
2596
|
+
sh.push(txSp(x, y, w, 0.3, [[{ t: String(vals[n - 1] != null ? vals[n - 1] : '') + (ec.unit || ''),
|
|
2597
|
+
sz: 11.5, b: 1, col: S.accent, font: S.font }]], { align: 'r' }));
|
|
2598
|
+
}
|
|
2599
|
+
/* K 线(A 近似):实体 + 须,标签 + 收盘值(与 B 的 shapeCandlestick 同文本集) */
|
|
2600
|
+
function candleApprox(sh, ec, S, x, y, w, h) {
|
|
2601
|
+
var labels = ec.labels || [];
|
|
2602
|
+
var rows = (ec.values || []).filter(function (r) { return r && r.length >= 4; })
|
|
2603
|
+
.map(function (r) { return r.slice(0, 4).map(Number); });
|
|
2604
|
+
var n = Math.max(1, rows.length);
|
|
2605
|
+
var flat = [];
|
|
2606
|
+
rows.forEach(function (r) { r.forEach(function (v) { if (!isNaN(v)) flat.push(v); }); });
|
|
2607
|
+
var lo = flat.length ? Math.min.apply(null, flat) : 0;
|
|
2608
|
+
var hi = flat.length ? Math.max.apply(null, flat) : 1;
|
|
2609
|
+
var span = (hi - lo) || 1;
|
|
2610
|
+
var axisH = 0.26, plotH = Math.max(0.5, h - axisH - 0.1);
|
|
2611
|
+
var yFor = function (v) { return y + 0.06 + (1 - (v - lo) / span) * plotH; };
|
|
2612
|
+
var step = w / n, bw = Math.min(0.42, step * 0.5);
|
|
2613
|
+
sh.push(shape('rect', x, y + 0.06 + plotH, w, 0.015, S.line));
|
|
2614
|
+
rows.forEach(function (r, i) {
|
|
2615
|
+
var o = r[0], hiP = r[1], loP = r[2], cl = r[3];
|
|
2616
|
+
var cxp = x + step * i + step / 2;
|
|
2617
|
+
var up = cl >= o;
|
|
2618
|
+
sh.push(shape('rect', cxp - 0.011, yFor(hiP), 0.022, Math.max(0.02, yFor(loP) - yFor(hiP)), S.faint));
|
|
2619
|
+
sh.push(shape('rect', cxp - bw / 2, yFor(Math.max(o, cl)), bw,
|
|
2620
|
+
Math.max(0.04, Math.abs(yFor(cl) - yFor(o))), up ? S.accent : S.line));
|
|
2621
|
+
if (labels[i]) {
|
|
2622
|
+
sh.push(txSp(cxp - step / 2, y + plotH + axisH - 0.06, step, 0.24,
|
|
2623
|
+
[[{ t: String(labels[i]), sz: 10, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2624
|
+
}
|
|
2625
|
+
sh.push(txSp(cxp - step / 2, yFor(hiP) - 0.26, step, 0.24,
|
|
2626
|
+
[[{ t: String(cl) + (ec.unit || ''), sz: 11.5, b: 1, col: up ? S.accent : S.body, font: S.font }]],
|
|
2627
|
+
{ align: 'r' }));
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
/* 通用形状近似(与 build_pptx.js 的 shapeProportion 同文本:标签 + 数值) */
|
|
2631
|
+
function shapeProportion(sh, ec, S, x, y, w, h, dcols) {
|
|
2632
|
+
var labels = ec.labels || [], values = (ec.values || []).map(Number);
|
|
2633
|
+
var n = Math.max(1, labels.length);
|
|
2634
|
+
var labelW = Math.min(2.4, w * 0.24), valW = 0.9;
|
|
2635
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
2636
|
+
var top = values.length ? Math.max.apply(null, values) : 0;
|
|
2637
|
+
var rowH = Math.max(0.22, Math.min(0.52, h / n));
|
|
2638
|
+
var trackX = x + labelW + 0.08, trackW = w - labelW - valW - 0.2;
|
|
2639
|
+
var barH = Math.max(0.12, rowH * 0.52);
|
|
2640
|
+
labels.forEach(function (lb, i) {
|
|
2641
|
+
var v = values[i] || 0, ry = y + i * rowH, hot = v === top;
|
|
2642
|
+
sh.push(txSp(x, ry, labelW - 0.06, rowH, [[{ t: lb, sz: 11, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2643
|
+
sh.push(shape('rect', trackX, ry + (rowH - barH) / 2, trackW, barH, S.surface, { line: S.line }));
|
|
2644
|
+
sh.push(shape('rect', trackX, ry + (rowH - barH) / 2, Math.max(0.02, Math.abs(v) / vmax * trackW), barH,
|
|
2645
|
+
dcols ? dcols[i % dcols.length] : (hot ? S.accent : S.faint)));
|
|
2646
|
+
sh.push(txSp(trackX + trackW + 0.06, ry, valW, rowH,
|
|
2647
|
+
[[{ t: String(v) + (ec.unit || ''), sz: 11.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]],
|
|
2648
|
+
{ align: 'right', anchor: 'ctr' }));
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
/* 数据表行(与 build_pptx.js 的 dataTableRows 同规则) */
|
|
2652
|
+
function dataTableRows(ec) {
|
|
2653
|
+
var labels = (ec.labels || []).map(String);
|
|
2654
|
+
var unit = ec.unit ? '(' + ec.unit + ')' : '';
|
|
2655
|
+
if (ec.series && ec.series.length) {
|
|
2656
|
+
var out = [['项目'].concat(ec.series.map(function (se, i) { return se.name || ('系列' + (i + 1)); }))];
|
|
2657
|
+
labels.forEach(function (lb, i) {
|
|
2658
|
+
out.push([lb].concat(ec.series.map(function (se) {
|
|
2659
|
+
var v = (se.values || [])[i]; return v == null ? '' : String(v);
|
|
2660
|
+
})));
|
|
2661
|
+
});
|
|
2662
|
+
return out;
|
|
2663
|
+
}
|
|
2664
|
+
var out2 = [['项目', '数值' + unit]];
|
|
2665
|
+
labels.forEach(function (lb, i) {
|
|
2666
|
+
var v = (ec.values || [])[i];
|
|
2667
|
+
out2.push([lb, v == null ? '' : String(v)]);
|
|
2668
|
+
});
|
|
2669
|
+
return out2;
|
|
2670
|
+
}
|
|
2671
|
+
/* 数据表策略(与 build_pptx.js 的 dataTableMode 同规则) */
|
|
2672
|
+
function dataTableModeOf(ec) {
|
|
2673
|
+
var t = (ec.type || 'bar').toLowerCase();
|
|
2674
|
+
var reg = (typeof REG !== 'undefined' && REG && REG[t]) ? REG[t] : null;
|
|
2675
|
+
var m = ec.dataTable || (reg && reg.dataTable) || 'notes';
|
|
2676
|
+
return m === 'appendix' ? 'inline' : m;
|
|
2677
|
+
}
|
|
2678
|
+
/* 图表 + 数据表组合(与 build_pptx.js 的 chartBlock 同策略:inline 在图表下方附原生表格) */
|
|
2679
|
+
function chartBlockShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
2680
|
+
var dm = dataTableModeOf(ec);
|
|
2681
|
+
var chH = h;
|
|
2682
|
+
if (dm === 'inline' && (ec.labels || []).length) {
|
|
2683
|
+
var rowsData = dataTableRows(ec);
|
|
2684
|
+
var n = rowsData.length;
|
|
2685
|
+
var tH = Math.min(h * 0.44, Math.max(0.44, n * 0.22 + 0.06));
|
|
2686
|
+
chH = Math.max(0.85, h - tH - 0.1);
|
|
2687
|
+
var colW = rowsData[0].map(function () { return w / rowsData[0].length; });
|
|
2688
|
+
sh.push(table(x, y + chH + 0.1, w, colW, rowsData, S, { availH: tH, maxRowH: 0.24, minRowH: 0.14 }));
|
|
2689
|
+
}
|
|
2690
|
+
chartShapes(sh, ec, S, x, y, w, chH, dcols);
|
|
2691
|
+
}
|
|
2692
|
+
/* ══ 复杂信息图页型(A 通道形状近似:文本集合与 B 通道严格一致,cross_verify 逐页比对)══ */
|
|
2693
|
+
function infoTableRows(sec) {
|
|
2694
|
+
var t = sec.type || '';
|
|
2695
|
+
var u = sec.unit ? '(' + sec.unit + ')' : '';
|
|
2696
|
+
if (t === 'sankey') return [['源', '汇', '流量' + u]].concat((sec.flows || []).map(function (f) { return [String(f[0]), String(f[1]), String(f[2])]; }));
|
|
2697
|
+
if (t === 'treemap') return [['项目', '数值' + u]].concat((sec.items || []).map(function (it) { return [String(it[0]), String(it[1])]; }));
|
|
2698
|
+
if (t === 'boxplot') return [['分组', '最小', 'Q1', '中位', 'Q3', '最大']].concat((sec.groups || []).map(function (g) { return g.map(String); }));
|
|
2699
|
+
if (t === 'network') return [['节点', '名称']].concat((sec.nodes || []).map(function (n) { return [String(n[0]), String(n[1])]; }));
|
|
2700
|
+
if (t === 'marimekko') {
|
|
2701
|
+
var head = ['列', '总量' + u].concat((sec.legend || []).map(String));
|
|
2702
|
+
return [head].concat((sec.cols || []).map(function (c, i) {
|
|
2703
|
+
return [String(c[0]), String(c[1])].concat(((sec.cells || [])[i] || []).map(String));
|
|
2704
|
+
}));
|
|
2705
|
+
}
|
|
2706
|
+
if (t === 'streamgraph') {
|
|
2707
|
+
return [['系列'].concat((sec.labels || []).map(String))]
|
|
2708
|
+
.concat((sec.series || []).map(function (se) {
|
|
2709
|
+
return [String(se.name || '')].concat((se.values || []).map(String));
|
|
2710
|
+
}));
|
|
2711
|
+
}
|
|
2712
|
+
return null;
|
|
2713
|
+
}
|
|
2714
|
+
function infoDataTableMode(sec) {
|
|
2715
|
+
var m = String(((sec.chart || {}).dataTable) || 'notes').toLowerCase();
|
|
2716
|
+
return m === 'appendix' ? 'inline' : m;
|
|
2717
|
+
}
|
|
2718
|
+
function infoPlotA(sec, y0, y1) {
|
|
2719
|
+
var rows = infoTableRows(sec);
|
|
2720
|
+
var inline = (infoDataTableMode(sec) === 'inline') && rows && rows.length;
|
|
2721
|
+
if (!inline) return { y0: y0, y1: y1, rows: rows };
|
|
2722
|
+
var tH = Math.min((y1 - y0) * 0.4, Math.max(0.44, rows.length * 0.22 + 0.06));
|
|
2723
|
+
return { y0: y0, y1: y1 - tH - 0.1, rows: rows, table: { y: y1 - tH, h: tH } };
|
|
2724
|
+
}
|
|
2725
|
+
function tmSplitA(items, x, y, w, h, out) {
|
|
2726
|
+
if (!items.length) return;
|
|
2727
|
+
if (items.length === 1) { out.push({ it: items[0], x: x, y: y, w: w, h: h }); return; }
|
|
2728
|
+
var total = items.reduce(function (a, i) { return a + i.v; }, 0) || 1;
|
|
2729
|
+
var acc = 0, k = 0;
|
|
2730
|
+
for (; k < items.length - 1; k++) { acc += items[k].v; if (acc >= total / 2) break; }
|
|
2731
|
+
var first = items.slice(0, k + 1), second = items.slice(k + 1);
|
|
2732
|
+
var r = first.reduce(function (a, i) { return a + i.v; }, 0) / total;
|
|
2733
|
+
if (w >= h) {
|
|
2734
|
+
tmSplitA(first, x, y, w * r, h, out);
|
|
2735
|
+
tmSplitA(second, x + w * r, y, w * (1 - r), h, out);
|
|
2736
|
+
} else {
|
|
2737
|
+
tmSplitA(first, x, y, w, h * r, out);
|
|
2738
|
+
tmSplitA(second, x, y + h * r, w, h * (1 - r), out);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
/* 信息图近似渲染:按页型画形状 + 落同样的标签文本 */
|
|
2742
|
+
function infoApprox(sh, sec, S, y0, y1) {
|
|
2743
|
+
var t = sec.type || '';
|
|
2744
|
+
var P = infoPlotA(sec, y0, y1);
|
|
2745
|
+
if (P.table) {
|
|
2746
|
+
var colW = P.rows[0].map(function () { return CW / P.rows[0].length; });
|
|
2747
|
+
sh.push(table(MX, P.table.y, CW, colW, P.rows, S, { availH: P.table.h, maxRowH: 0.24, minRowH: 0.15 }));
|
|
2748
|
+
}
|
|
2749
|
+
var palette = [S.accent, S.faint, S.body, S.line];
|
|
2750
|
+
if (t === 'sankey') {
|
|
2751
|
+
var flows = (sec.flows || []).filter(function (f) { return f && f.length >= 3; });
|
|
2752
|
+
var nodes = [], idx = {};
|
|
2753
|
+
flows.forEach(function (f) {
|
|
2754
|
+
[f[0], f[1]].forEach(function (n) { if (idx[n] == null) { idx[n] = nodes.length; nodes.push(String(n)); } });
|
|
2755
|
+
});
|
|
2756
|
+
var level = nodes.map(function () { return 0; });
|
|
2757
|
+
for (var it2 = 0; it2 < nodes.length + 1; it2++) {
|
|
2758
|
+
var ch = false;
|
|
2759
|
+
flows.forEach(function (f) {
|
|
2760
|
+
var a = idx[f[0]], b = idx[f[1]];
|
|
2761
|
+
if (level[b] < level[a] + 1) { level[b] = level[a] + 1; ch = true; }
|
|
2762
|
+
});
|
|
2763
|
+
if (!ch) break;
|
|
2764
|
+
}
|
|
2765
|
+
var maxLv = Math.max.apply(null, level.concat([0]));
|
|
2766
|
+
var colW2 = CW / (maxLv + 1);
|
|
2767
|
+
var geo = {};
|
|
2768
|
+
for (var l = 0; l <= maxLv; l++) {
|
|
2769
|
+
var lv = nodes.filter(function (n, i) { return level[i] === l; });
|
|
2770
|
+
var rowH2 = Math.max(0.24, (P.y1 - P.y0) / Math.max(1, lv.length));
|
|
2771
|
+
lv.forEach(function (n, k2) { geo[n] = { x: MX + l * colW2 + colW2 * 0.5 - 0.16, y: P.y0 + k2 * rowH2, h: rowH2 * 0.7 }; });
|
|
2772
|
+
}
|
|
2773
|
+
flows.forEach(function (f) {
|
|
2774
|
+
var a = geo[f[0]], b = geo[f[1]];
|
|
2775
|
+
if (!a || !b) return;
|
|
2776
|
+
sh.push(lineSeg(a.x + 0.32, a.y + a.h / 2, b.x, b.y + b.h / 2, S.soft, 8));
|
|
2777
|
+
/* 流量标签:与 B 通道同文本(数值 + 单位,由 cross_verify 过滤) */
|
|
2778
|
+
sh.push(txSp((a.x + b.x) / 2 - 0.5, (a.y + b.y) / 2 - 0.14, 1.0, 0.28,
|
|
2779
|
+
[[{ t: String(f[2]) + (sec.unit || ''), sz: 9.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2780
|
+
});
|
|
2781
|
+
nodes.forEach(function (n) {
|
|
2782
|
+
var g = geo[n];
|
|
2783
|
+
sh.push(shape('rect', g.x, g.y, 0.32, g.h, S.accent));
|
|
2784
|
+
sh.push(txSp(g.x + 0.38, g.y, Math.min(1.4, colW2 * 0.5), g.h,
|
|
2785
|
+
[[{ t: n, sz: 10.5, b: 1, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2786
|
+
});
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
if (t === 'treemap') {
|
|
2790
|
+
var items = (sec.items || []).map(function (it) { return { label: String(it[0]), v: Math.max(0, Number(it[1]) || 0) }; })
|
|
2791
|
+
.filter(function (i) { return i.v > 0; }).sort(function (a, b) { return b.v - a.v; });
|
|
2792
|
+
var out = [];
|
|
2793
|
+
tmSplitA(items, MX, P.y0, CW, P.y1 - P.y0, out);
|
|
2794
|
+
out.forEach(function (o) {
|
|
2795
|
+
sh.push(shape('rect', o.x + 0.03, o.y + 0.03, Math.max(0.05, o.w - 0.06), Math.max(0.05, o.h - 0.06), S.surface, { line: S.line }));
|
|
2796
|
+
sh.push(txSp(o.x + 0.1, o.y + 0.06, Math.max(0.2, o.w - 0.2), 0.28, [[{ t: o.it.label, sz: 10.5, b: 1, col: S.ink, font: S.font }]]));
|
|
2797
|
+
sh.push(txSp(o.x + 0.1, o.y + 0.32, Math.max(0.2, o.w - 0.2), 0.26,
|
|
2798
|
+
[[{ t: String(o.it.v) + (sec.unit || ''), sz: 10, col: S.body, font: S.font }]]));
|
|
2799
|
+
});
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
if (t === 'boxplot') {
|
|
2803
|
+
var groups = (sec.groups || []).filter(function (g) { return g && g.length >= 6; });
|
|
2804
|
+
var step = CW / Math.max(1, groups.length);
|
|
2805
|
+
var plotH = (P.y1 - P.y0) - 0.4;
|
|
2806
|
+
groups.forEach(function (g, i) {
|
|
2807
|
+
var cx = MX + step * i + step / 2;
|
|
2808
|
+
var bw = Math.min(0.9, step * 0.5);
|
|
2809
|
+
sh.push(shape('rect', cx - 0.012, P.y0, 0.024, plotH, S.faint));
|
|
2810
|
+
sh.push(shape('rect', cx - bw / 2, P.y0 + plotH * 0.2, bw, plotH * 0.6, S.soft, { line: S.accent }));
|
|
2811
|
+
sh.push(shape('rect', cx - bw / 2, P.y0 + plotH * 0.48, bw, 0.03, S.accent));
|
|
2812
|
+
sh.push(txSp(cx - step / 2, P.y1 - 0.34, step, 0.28,
|
|
2813
|
+
[[{ t: String(g[0]), sz: 10.5, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
2814
|
+
});
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
if (t === 'network') {
|
|
2818
|
+
var nds = sec.nodes || [];
|
|
2819
|
+
var n2 = Math.max(1, nds.length);
|
|
2820
|
+
var cx0 = MX + CW / 2, cy0 = (P.y0 + P.y1) / 2;
|
|
2821
|
+
var r = Math.min(CW / 2, (P.y1 - P.y0) / 2) - 1.0;
|
|
2822
|
+
var pos = {};
|
|
2823
|
+
nds.forEach(function (nd, i) {
|
|
2824
|
+
var a = -Math.PI / 2 + i * 2 * Math.PI / n2;
|
|
2825
|
+
pos[String(nd[0])] = { x: cx0 + r * Math.cos(a), y: cy0 + r * Math.sin(a) };
|
|
2826
|
+
});
|
|
2827
|
+
(sec.edges || []).forEach(function (e) {
|
|
2828
|
+
var a = pos[String(e[0])], b = pos[String(e[1])];
|
|
2829
|
+
if (!a || !b) return;
|
|
2830
|
+
sh.push(lineSeg(a.x, a.y, b.x, b.y, S.line, 1.5));
|
|
2831
|
+
});
|
|
2832
|
+
nds.forEach(function (nd) {
|
|
2833
|
+
var p = pos[String(nd[0])];
|
|
2834
|
+
sh.push(shape('ellipse', p.x - 0.26, p.y - 0.26, 0.52, 0.52, S.accent));
|
|
2835
|
+
var outward = p.x >= cx0;
|
|
2836
|
+
sh.push(txSp(outward ? p.x + 0.32 : p.x - 0.32 - 1.4, p.y - 0.14, 1.4, 0.28,
|
|
2837
|
+
[[{ t: String(nd[1] != null ? nd[1] : nd[0]), sz: 10, col: S.body, font: S.font }]],
|
|
2838
|
+
{ align: outward ? 'l' : 'r', anchor: 'ctr' }));
|
|
2839
|
+
});
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
if (t === 'marimekko') {
|
|
2843
|
+
var cols = sec.cols || [];
|
|
2844
|
+
var legend = (sec.legend || []).map(String);
|
|
2845
|
+
var total = cols.reduce(function (a, c) { return a + (Number(c[1]) || 0); }, 0) || 1;
|
|
2846
|
+
var plotH2 = (P.y1 - P.y0) - 0.6;
|
|
2847
|
+
var availW = CW - 0.06 * Math.max(0, cols.length - 1);
|
|
2848
|
+
var cx1 = MX;
|
|
2849
|
+
cols.forEach(function (c, i) {
|
|
2850
|
+
var cw = Math.max(0.2, (Number(c[1]) || 0) / total * availW);
|
|
2851
|
+
var shares = ((sec.cells || [])[i] || []).map(Number);
|
|
2852
|
+
var sum = shares.reduce(function (a, b) { return a + b; }, 0) || 1;
|
|
2853
|
+
var cy = P.y0;
|
|
2854
|
+
shares.forEach(function (v, k) {
|
|
2855
|
+
var shh = Math.max(0.02, v / sum * plotH2);
|
|
2856
|
+
sh.push(shape('rect', cx1, cy, cw, shh, palette[k % palette.length]));
|
|
2857
|
+
cy += shh;
|
|
2858
|
+
});
|
|
2859
|
+
sh.push(txSp(cx1, P.y0 + plotH2 + 0.06, cw, 0.28,
|
|
2860
|
+
[[{ t: String(c[0]), sz: 10.5, b: 1, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
2861
|
+
cx1 += cw + 0.06;
|
|
2862
|
+
});
|
|
2863
|
+
var lx = MX;
|
|
2864
|
+
legend.forEach(function (lg, k) {
|
|
2865
|
+
sh.push(shape('rect', lx, P.y1 - 0.2, 0.12, 0.12, palette[k % palette.length]));
|
|
2866
|
+
sh.push(txSp(lx + 0.16, P.y1 - 0.26, 1.5, 0.26, [[{ t: lg, sz: 10, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2867
|
+
lx += 1.75;
|
|
2868
|
+
});
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
if (t === 'streamgraph') {
|
|
2872
|
+
var series = sec.series || [];
|
|
2873
|
+
var labels = sec.labels || [];
|
|
2874
|
+
var nPts = labels.length || ((series[0] && series[0].values || []).length);
|
|
2875
|
+
if (nPts >= 2) {
|
|
2876
|
+
var values = series.map(function (se) {
|
|
2877
|
+
return (se.values || []).map(function (v) { return Math.max(0, Number(v) || 0); });
|
|
2878
|
+
});
|
|
2879
|
+
var totals = [];
|
|
2880
|
+
for (var i2 = 0; i2 < nPts; i2++) {
|
|
2881
|
+
var tt = 0;
|
|
2882
|
+
values.forEach(function (v) { tt += (v[i2] || 0); });
|
|
2883
|
+
totals.push(tt);
|
|
2884
|
+
}
|
|
2885
|
+
var maxTot = Math.max.apply(null, totals.concat([1]));
|
|
2886
|
+
var plotH3 = P.y1 - P.y0;
|
|
2887
|
+
var cyMid = (P.y0 + P.y1) / 2;
|
|
2888
|
+
var base = totals.map(function (tv) { return cyMid - tv / maxTot * plotH3 / 2; });
|
|
2889
|
+
series.forEach(function (se, si) {
|
|
2890
|
+
var upper = base.map(function (bv, i3) { return bv + (values[si][i3] || 0) / maxTot * plotH3 / 2; });
|
|
2891
|
+
var stepX = CW / (nPts - 1);
|
|
2892
|
+
for (var i4 = 0; i4 < nPts - 1; i4++) {
|
|
2893
|
+
var hgt = Math.max(0.04, ((upper[i4] - base[i4]) + (upper[i4 + 1] - base[i4 + 1])) / 2);
|
|
2894
|
+
sh.push(shape('rect', MX + i4 * stepX, (base[i4] + base[i4 + 1]) / 2, stepX, hgt, palette[si % palette.length]));
|
|
2895
|
+
}
|
|
2896
|
+
base = upper;
|
|
2897
|
+
sh.push(shape('rect', MX, P.y1 + 0.06 + si * 0.26, 0.12, 0.12, palette[si % palette.length]));
|
|
2898
|
+
sh.push(txSp(MX + 0.16, P.y1 + si * 0.26, 1.5, 0.26,
|
|
2899
|
+
[[{ t: String(se.name || ('系列' + (si + 1))), sz: 10, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
2900
|
+
});
|
|
2901
|
+
labels.forEach(function (lb, i5) {
|
|
2902
|
+
sh.push(txSp(MX + (nPts > 1 ? i5 / (nPts - 1) : 0.5) * CW - 0.4, P.y1 - 0.02, 0.8, 0.24,
|
|
2903
|
+
[[{ t: String(lb), sz: 9.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
/* 统一图表分发(与 scripts/layout-constants.json 的 charts.registry 同分派规则)
|
|
2910
|
+
* A 通道为形状近似:类别标签与数值都落为文本,数值 token 由 cross_verify 过滤;
|
|
2911
|
+
* 复杂信息图(sankey / treemap / boxplot / network / marimekko / streamgraph)请用专属页型承载。 */
|
|
2912
|
+
function chartShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
2913
|
+
var t = (ec.type || 'bar').toLowerCase();
|
|
2914
|
+
var reg = (typeof REG !== 'undefined' && REG && REG[t]) ? REG[t] : null;
|
|
2915
|
+
if (t === 'hbar') { hbarRows(sh, ec, S, y, y + h, dcols, x, w); return; }
|
|
2916
|
+
if (t === 'line' || t === 'dualline' || t === 'area') { lineShapes(sh, ec, S, x, y, w, h, dcols); return; }
|
|
2917
|
+
if (t === 'scatter' || t === 'bubble') { xyDots(sh, ec, S, x, y, w, h); return; }
|
|
2918
|
+
if (t === 'gauge') { gaugeApprox(sh, ec, S, x, y, w, h); return; }
|
|
2919
|
+
if (t === 'pie' || t === 'donut' || t === 'multidonut') { pieApprox(sh, ec, S, x, y, w, h, dcols); return; }
|
|
2920
|
+
/* sparkline / candlestick 的文本集须与 B 通道专属渲染器一致(否则 cross_verify 误报) */
|
|
2921
|
+
if (t === 'sparkline') { sparkApprox(sh, ec, S, x, y, w, h); return; }
|
|
2922
|
+
if (t === 'candlestick') { candleApprox(sh, ec, S, x, y, w, h); return; }
|
|
2923
|
+
if (reg && reg.pptx === 'shape') { shapeProportion(sh, ec, S, x, y, w, h, dcols); return; }
|
|
2924
|
+
vbarShapes(sh, ec, S, x, y, w, h, dcols);
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
/* 区域 IR 取用(与 scripts/lib_layout_regions.js 同源;未注入时回落 null) */
|
|
2928
|
+
function regOf(type, slot, opts) {
|
|
2929
|
+
return (typeof regionOf === 'function') ? regionOf(type, slot, opts) : null;
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
/* ── 幻灯片生成(版式常量同 build_pptx.js · 同一 layout-constants.json) ── */
|
|
2933
|
+
function slidesOf(model, S) {
|
|
2934
|
+
var slides = [];
|
|
2935
|
+
_mode = model.mode || 'presentation';
|
|
2936
|
+
function head(sh, eyebrow, title, lead) {
|
|
2937
|
+
var H = (typeof regionOf === 'function') ? regionOf('head', 'head') : {
|
|
2938
|
+
x: MX, y: PT.common.headEyebrowY, w: CW,
|
|
2939
|
+
titleY: PT.common.headTitleY, ruleY: PT.common.headRuleY, leadY: PT.common.leadY,
|
|
2940
|
+
};
|
|
2941
|
+
if (eyebrow) sh.push(txSp(H.x, H.y, H.w, 0.4, [[{ t: eyebrow, sz: 13, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
2942
|
+
var titleH = eyebrow ? (H.ruleY - H.titleY - 0.06) : 1.0;
|
|
2943
|
+
var tSize = estTextH(title, CW, 30, 1.25) > titleH ? 24 : 30;
|
|
2944
|
+
sh.push(txSp(H.x, eyebrow ? H.titleY : 0.6, H.w, Math.max(0.6, titleH), [[{ t: title, sz: tSize, b: 1, col: S.ink, font: S.fontDisplay }]]));
|
|
2945
|
+
sh.push(shape('rect', H.x, eyebrow ? H.ruleY : 1.6, 0.9, 0.045, S.accent));
|
|
2946
|
+
if (lead) sh.push(txSp(H.x, H.leadY || PT.common.leadY, H.w, 0.5, [[{ t: lead, sz: 14, col: S.body, font: S.font }]]));
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
/* 封面 */
|
|
2950
|
+
var CV = PT.cover;
|
|
2951
|
+
var c = [shape('rect', 0, 0, PW, PH, S.accent)];
|
|
2952
|
+
c.push(txSp(MX, CV.metaY, CW, 0.4, [[{ t: model.meta || '', sz: 12, col: S.soft, font: S.font }]]));
|
|
2953
|
+
var cTSize = estTextH(model.title, CW - 1, 44, 1.25) > CV.titleH ? 36 : 44;
|
|
2954
|
+
c.push(txSp(MX, CV.titleY, CW - 1, CV.titleH, [[{ t: model.title, sz: cTSize, b: 1, col: S.onAccent, font: S.fontDisplay }]]));
|
|
2955
|
+
c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
|
|
2956
|
+
slides.push(c);
|
|
2957
|
+
|
|
2958
|
+
/* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
|
|
2959
|
+
var items = model.agenda || [];
|
|
2960
|
+
if (items.length) {
|
|
2961
|
+
var ag = [];
|
|
2962
|
+
head(ag, 'AGENDA', '报告大纲');
|
|
2963
|
+
var colN = items.length > 8 ? 2 : 1;
|
|
2964
|
+
var perCol = Math.ceil(items.length / colN) || 1;
|
|
2965
|
+
var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP) / perCol);
|
|
2966
|
+
var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
|
|
2967
|
+
var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
|
|
2968
|
+
items.forEach(function (it, i) {
|
|
2969
|
+
var col = Math.floor(i / perCol), row = i % perCol;
|
|
2970
|
+
var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
|
|
2971
|
+
ag.push(txSp(x, y, 0.9, Math.min(0.9, rowH), [[{ t: it[0], sz: numSz, b: 1, col: S.accent, font: S.font }]]));
|
|
2972
|
+
ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
|
|
2973
|
+
[[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
|
|
2974
|
+
[{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
|
|
2975
|
+
});
|
|
2976
|
+
slides.push(ag);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
/* 章节页 */
|
|
2980
|
+
(model.sections || []).forEach(function (sec) {
|
|
2981
|
+
var type = sec.type || 'points';
|
|
2982
|
+
var sh = [];
|
|
2983
|
+
if (type !== 'quote') head(sh, sec.eyebrow, sec.title, sec.lead);
|
|
2984
|
+
/* 非 exhibit 页型也可带 Exhibit 编号(帧头徽标;正文区下移让位,与 B 通道同规则) */
|
|
2985
|
+
var exBadge = (type !== 'exhibit' && sec.exhibitNo) ? String(sec.exhibitNo).toUpperCase() : null;
|
|
2986
|
+
var bodyY0 = sec.lead ? PT.common.bodyYWithLead : CONTENT_TOP;
|
|
2987
|
+
var bodyY = exBadge ? bodyY0 + 0.42 : bodyY0;
|
|
2988
|
+
if (exBadge) {
|
|
2989
|
+
sh.push(txSp(MX, CONTENT_TOP - 0.06, CW, 0.3,
|
|
2990
|
+
[[{ t: exBadge, sz: 11.5, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
2991
|
+
}
|
|
2992
|
+
var flagItems = Array.isArray(sec.flags) ? sec.flags.filter(Boolean) : [];
|
|
2993
|
+
var _F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
|
|
2994
|
+
var flagH = flagItems.length
|
|
2995
|
+
? (_F.hdH + Math.min(_F.maxRows, flagItems.length) * _F.rowH + 0.06) : 0;
|
|
2996
|
+
var bodyBottom = chartBottom(!!sec.soWhat, !!sec.footnote);
|
|
2997
|
+
var flagY = 0;
|
|
2998
|
+
if (flagH) {
|
|
2999
|
+
flagY = CONTENT_BOTTOM - flagH;
|
|
3000
|
+
if (sec.footnote) flagY = Math.min(flagY, PT.exhibit.footnoteY - flagH - 0.06);
|
|
3001
|
+
if (sec.soWhat) flagY = Math.min(flagY, PT.exhibit.soWhatY - flagH - 0.06);
|
|
3002
|
+
bodyBottom = Math.min(bodyBottom, flagY - (_F.gap || 0.12));
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
if (type === 'metrics') {
|
|
3006
|
+
var M = PT.metrics, ms = sec.metrics || [], n = Math.max(1, ms.length);
|
|
3007
|
+
var mReg = regOf('metrics', 'primary') || { x: MX, y: M.bandY, w: CW, h: M.cardH };
|
|
3008
|
+
var g = cols(mReg.w, n, M.gap);
|
|
3009
|
+
var mw = Math.min(M.maxW, g.w);
|
|
3010
|
+
var gx = mReg.x + (mReg.w - n * mw - (n - 1) * M.gap) / 2;
|
|
3011
|
+
var cardH = Math.min(mReg.h, CONTENT_BOTTOM - mReg.y);
|
|
3012
|
+
ms.forEach(function (m, i) {
|
|
3013
|
+
var x = gx + i * (mw + M.gap);
|
|
3014
|
+
sh.push(shape('roundRect', x, mReg.y, mw, cardH, S.surface, { line: S.line, adj: 4000 }));
|
|
3015
|
+
var vSize = estTextH(m[0], mw - 0.2, 40, 1.1) > cardH * 0.5 ? 30 : 40;
|
|
3016
|
+
sh.push(txSp(x, M.valY, mw, M.valH, [[{ t: m[0], sz: vSize, b: 1, col: S.accent, font: S.fontDisplay }]], { align: 'ctr' }));
|
|
3017
|
+
sh.push(txSp(x + 0.15, M.capY, mw - 0.3, M.capH, [[{ t: m[1], sz: 11.5, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
3018
|
+
});
|
|
3019
|
+
} else if (type === 'kpi') {
|
|
3020
|
+
var K = PT.kpi, hro = sec.hero || [];
|
|
3021
|
+
var hReg = regOf('kpi', 'hero') || { x: MX, y: K.heroY, w: CW * K.heroW, h: K.heroH };
|
|
3022
|
+
sh.push(txSp(hReg.x, hReg.y, hReg.w, hReg.h, [[{ t: String(hro[0] || ''), sz: 64, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
3023
|
+
sh.push(txSp(hReg.x + 0.05, hReg.y + 1.5, hReg.w - 0.4, 0.55, [[{ t: String(hro[1] || ''), sz: 15, b: 1, col: S.ink, font: S.font }]]));
|
|
3024
|
+
if (hro[2]) sh.push(txSp(hReg.x + 0.05, hReg.y + 2.1, hReg.w - 0.4, 0.4, [[{ t: '▲ ' + hro[2], sz: 12, b: 1, col: S.accent, font: S.font }]]));
|
|
3025
|
+
var kdivX = MX + CW * K.dividerX;
|
|
3026
|
+
sh.push(shape('rect', kdivX, K.heroY + 0.15, 0.025, 3.1, S.line));
|
|
3027
|
+
var mRegK = regOf('kpi', 'metrics') || { x: kdivX + 0.35, y: K.metricY0, w: CW * (1 - K.dividerX) - 0.5 };
|
|
3028
|
+
var kmets = sec.metrics || [];
|
|
3029
|
+
var kCap = Math.max(1, Math.floor((CONTENT_BOTTOM - mRegK.y) / K.metricRowH));
|
|
3030
|
+
var kRows = Math.min(kmets.length, kCap);
|
|
3031
|
+
var kRowH = fitRowH(CONTENT_BOTTOM - mRegK.y, kRows, K.metricRowH, 0.42);
|
|
3032
|
+
var kTopH = Math.min(0.55, kRowH * 0.62);
|
|
3033
|
+
kmets.slice(0, kRows).forEach(function (m, i) {
|
|
3034
|
+
var ky = mRegK.y + i * kRowH;
|
|
3035
|
+
sh.push(txSp(mRegK.x, ky, mRegK.w, kTopH, [[{ t: m[0], sz: 22, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
3036
|
+
sh.push(txSp(mRegK.x, ky + kTopH, mRegK.w, Math.max(0.24, kRowH - kTopH), [[{ t: m[1], sz: 10.5, col: S.faint, font: S.font }]]));
|
|
3037
|
+
});
|
|
3038
|
+
} else if (type === 'comparison') {
|
|
3039
|
+
var C2 = PT.comparison;
|
|
3040
|
+
var lR = regOf('comparison', 'left') || { x: MX, y: C2.panelY, w: (CW - C2.panelGap) / 2, h: C2.panelH };
|
|
3041
|
+
var rR = regOf('comparison', 'right') || { x: MX + (CW + C2.panelGap) / 2, y: C2.panelY, w: (CW - C2.panelGap) / 2, h: C2.panelH };
|
|
3042
|
+
var cw2 = lR.w, cpy = lR.y, cph = lR.h, rx2 = rR.x;
|
|
3043
|
+
sh.push(shape('roundRect', lR.x, cpy, cw2, cph, S.surface, { adj: 4000, line: S.line }));
|
|
3044
|
+
sh.push(shape('roundRect', rx2, cpy, cw2, cph, S.soft, { adj: 4000 }));
|
|
3045
|
+
[{ x: lR.x, d: (sec.left || {}) }, { x: rx2, d: (sec.right || {}) }].forEach(function (sd) {
|
|
3046
|
+
var isRight = sd.x === rx2;
|
|
3047
|
+
var pts = sd.d.points || [];
|
|
3048
|
+
var avail = cph - C2.titleH - 0.34;
|
|
3049
|
+
var rowH2 = fitRowH(avail, pts.length, C2.rowH, 0.34);
|
|
3050
|
+
var fz = rowH2 < 0.5 ? 11.5 : 13;
|
|
3051
|
+
sh.push(txSp(sd.x + 0.22, cpy + 0.18, cw2 - 0.44, C2.titleH,
|
|
3052
|
+
[[{ t: sd.d.title || '', sz: 15, b: 1, col: isRight ? S.accent : S.ink, font: S.font }]]));
|
|
3053
|
+
pts.forEach(function (p, pi) {
|
|
3054
|
+
var py = cpy + 0.18 + C2.titleH + 0.16 + pi * rowH2;
|
|
3055
|
+
sh.push(shape('rect', sd.x + 0.24, py + rowH2 / 2 - 0.06, 0.12, 0.12, isRight ? S.accent : S.faint));
|
|
3056
|
+
sh.push(txSp(sd.x + 0.5, py, cw2 - 0.75, rowH2 - 0.04,
|
|
3057
|
+
[[{ t: p[0] + ' ', sz: fz, b: 1, col: S.ink, font: S.font },
|
|
3058
|
+
{ t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
|
|
3059
|
+
});
|
|
3060
|
+
});
|
|
3061
|
+
if (sec.verdict) {
|
|
3062
|
+
var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
|
|
3063
|
+
sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
|
|
3064
|
+
sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
|
|
3065
|
+
[[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
|
|
3066
|
+
{ t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
|
|
3067
|
+
}
|
|
3068
|
+
} else if (type === 'quote') {
|
|
3069
|
+
var Q = PT.quote;
|
|
3070
|
+
var qR = regOf('quote', 'primary') || { x: MX + 1.05, y: Q.textY, w: CW - 2.1, h: Q.textH };
|
|
3071
|
+
/* 金句页用主题一致强调带(accent-soft 底)——浅色主题下不再出现深色页 */
|
|
3072
|
+
sh.push(shape('rect', 0, 0, PW, PH, S.soft));
|
|
3073
|
+
if (sec.eyebrow) sh.push(txSp(MX, PT.common.headEyebrowY, CW, 0.4, [[{ t: sec.eyebrow, sz: 13, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
3074
|
+
sh.push(txSp(MX, PT.common.headTitleY, CW, 1.0, [[{ t: sec.title, sz: 30, b: 1, col: S.ink, font: S.fontDisplay }]]));
|
|
3075
|
+
sh.push(shape('rect', MX, PT.common.headRuleY, 0.9, 0.045, S.accent));
|
|
3076
|
+
sh.push(txSp(MX + 0.05, Q.markY, 1.2, 1.1, [[{ t: '「', sz: 60, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
3077
|
+
var qSize = estTextH(sec.quote, qR.w, 20, 1.4) > qR.h ? 16 : 20;
|
|
3078
|
+
sh.push(txSp(qR.x, qR.y, qR.w, qR.h, [[{ t: sec.quote, sz: qSize, b: 1, col: S.ink, font: S.fontDisplay }]], { anchor: 'ctr' }));
|
|
3079
|
+
sh.push(txSp(qR.x, Q.authorY, qR.w, 0.45, [[{ t: '—— ' + sec.author, sz: 13, b: 1, col: S.accent, font: S.font }]], { align: 'r' }));
|
|
3080
|
+
if (sec.context) sh.push(txSp(qR.x, Q.contextY, qR.w, 0.4, [[{ t: sec.context, sz: 11, col: S.faint, font: S.font }]], { align: 'r' }));
|
|
3081
|
+
} else if (type === 'image') {
|
|
3082
|
+
/* 素材图片页:full 版心全宽 / half 左图右注 / bleed 通栏出血 / grid 多图网格 /
|
|
3083
|
+
compare 双图对比 / wall Logo 墙;无图(image.placeholder)走原生占位块 + 标签文本 */
|
|
3084
|
+
var IM = PT.image, img = sec.image || {};
|
|
3085
|
+
var items = Array.isArray(img.items) ? img.items.filter(Boolean) : [];
|
|
3086
|
+
var layout = String(img.layout || (items.length > 1 ? 'grid' : 'full')).toLowerCase();
|
|
3087
|
+
var fit = String(img.fit || ((typeof IMG !== 'undefined' && IMG && IMG.fitDefault) || 'cover')).toLowerCase();
|
|
3088
|
+
var isPh = !!img.placeholder;
|
|
3089
|
+
var iR = regOf('image', 'primary', { top: bodyY, caption: !!img.caption }) ||
|
|
3090
|
+
{ x: MX, y: Math.max(IM.y, bodyY), w: CW, h: IM.h };
|
|
3091
|
+
var iy = iR.y;
|
|
3092
|
+
var capH2 = img.caption ? 0.32 : 0;
|
|
3093
|
+
var ih = Math.max(1.2, iR.h);
|
|
3094
|
+
imageLayoutShapes(sh, S, layout, img, items, sec.points || [], isPh, fit, iy, iy + ih);
|
|
3095
|
+
if (img.caption) sh.push(txSp(MX, iy + ih + 0.04, CW, capH2, [[{ t: img.caption, sz: 10.5, col: S.faint, font: S.font }]]));
|
|
3096
|
+
} else if (type === 'donut') {
|
|
3097
|
+
var dcn = sec.chart || {};
|
|
3098
|
+
if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme));
|
|
3099
|
+
if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
|
|
3100
|
+
} else if (type === 'heatmap') {
|
|
3101
|
+
var H = PT.heatmap;
|
|
3102
|
+
var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
|
|
3103
|
+
var rowsH = sec.rowHeads || [], colsH = sec.colHeads || [], cells = sec.cells || [];
|
|
3104
|
+
var nR = Math.max(1, rowsH.length), nC = Math.max(1, colsH.length);
|
|
3105
|
+
var availH = hR.h;
|
|
3106
|
+
var rowH3 = fitRowH(availH - H.headH, nR, H.cellH, 0.24);
|
|
3107
|
+
var gridW = hR.w - H.labelW;
|
|
3108
|
+
var cw3 = (gridW - (nC - 1) * H.cellGap) / nC;
|
|
3109
|
+
var flat = [], k2;
|
|
3110
|
+
for (k2 = 0; k2 < cells.length; k2++) for (var j2 = 0; j2 < (cells[k2] || []).length; j2++) { var nv = Number(cells[k2][j2]); if (!isNaN(nv)) flat.push(nv); }
|
|
3111
|
+
var vmax3 = flat.length ? Math.max.apply(null, flat) : 1;
|
|
3112
|
+
var vmin3 = flat.length ? Math.min.apply(null, flat) : 0;
|
|
3113
|
+
var levels = [S.surface, S.soft, S.line, S.accent];
|
|
3114
|
+
colsH.forEach(function (ch2, ci) {
|
|
3115
|
+
sh.push(txSp(hR.x + H.labelW + ci * (cw3 + H.cellGap), hR.y, cw3, H.headH,
|
|
3116
|
+
[[{ t: ch2, sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3117
|
+
});
|
|
3118
|
+
var rowsUse = Math.min(nR, cells.length);
|
|
3119
|
+
for (var ri = 0; ri < rowsUse; ri++) {
|
|
3120
|
+
var yy = hR.y + H.headH + 0.08 + ri * (rowH3 + H.cellGap);
|
|
3121
|
+
sh.push(txSp(hR.x, yy, H.labelW - 0.15, rowH3, [[{ t: rowsH[ri] || '', sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
3122
|
+
for (var ci2 = 0; ci2 < nC; ci2++) {
|
|
3123
|
+
var v2 = Number((cells[ri] || [])[ci2]);
|
|
3124
|
+
var has2 = !isNaN(v2);
|
|
3125
|
+
var p2 = (has2 && vmax3 > vmin3) ? (v2 - vmin3) / (vmax3 - vmin3) : 0.5;
|
|
3126
|
+
var lvl = p2 >= 0.86 ? 3 : (p2 >= 0.55 ? 2 : (p2 >= 0.25 ? 1 : 0));
|
|
3127
|
+
var cx2 = hR.x + H.labelW + ci2 * (cw3 + H.cellGap);
|
|
3128
|
+
sh.push(shape('roundRect', cx2, yy, cw3, rowH3, levels[lvl],
|
|
3129
|
+
{ adj: 3000, line: lvl === 0 ? S.line : undefined }));
|
|
3130
|
+
sh.push(txSp(cx2, yy, cw3, rowH3,
|
|
3131
|
+
[[{ t: has2 ? valText(v2, sec.unit) : '—', sz: 11.5, b: lvl >= 2 ? 1 : 0,
|
|
3132
|
+
col: lvl >= 3 ? S.onAccent : S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
var lg = sec.scaleLabel || ['低', '高'];
|
|
3136
|
+
var lgy = hR.y + H.headH + 0.08 + rowsUse * (rowH3 + H.cellGap) + 0.06;
|
|
3137
|
+
sh.push(txSp(hR.x, lgy, 0.6, 0.3, [[{ t: lg[0] || '低', sz: 9.5, col: S.faint, font: S.font }]]));
|
|
3138
|
+
for (var li2 = 0; li2 < 4; li2++) {
|
|
3139
|
+
sh.push(shape('rect', hR.x + 0.45 + li2 * 0.3, lgy + 0.04, 0.28, 0.16, levels[li2],
|
|
3140
|
+
{ line: li2 === 0 ? S.line : undefined }));
|
|
3141
|
+
}
|
|
3142
|
+
sh.push(txSp(hR.x + 1.75, lgy, 0.6, 0.3, [[{ t: lg[1] || '高', sz: 9.5, col: S.faint, font: S.font }]]));
|
|
3143
|
+
} else if (type === 'bullet') {
|
|
3144
|
+
var B = PT.bullet, its = sec.items || [], nB = Math.max(1, its.length);
|
|
3145
|
+
var bR = regOf('bullet', 'primary', { bottom: bodyBottom }) || { x: MX, y: B.y, w: CW, h: bodyBottom - B.y };
|
|
3146
|
+
var availB = bR.h;
|
|
3147
|
+
var rowHB = fitRowH(availB, nB, B.rowH, 0.26);
|
|
3148
|
+
var trackX = bR.x + B.labelW + 0.15;
|
|
3149
|
+
var trackW = bR.w - B.labelW - B.valW - 0.3;
|
|
3150
|
+
var nums = [], q2;
|
|
3151
|
+
for (q2 = 0; q2 < its.length; q2++) { var nq = Number(its[q2][1]); if (!isNaN(nq)) nums.push(nq); }
|
|
3152
|
+
var vmaxB = sec.max || (nums.length ? Math.max.apply(null, nums) : 1);
|
|
3153
|
+
its.forEach(function (it, i) {
|
|
3154
|
+
var yy2 = bR.y + i * rowHB + rowHB / 2 - B.barH / 2;
|
|
3155
|
+
var v = Number(it[1]) || 0, tgt = Number(it[2]);
|
|
3156
|
+
var hit = !isNaN(tgt) && v >= tgt;
|
|
3157
|
+
sh.push(txSp(bR.x, bR.y + i * rowHB, B.labelW, rowHB, [[{ t: String(it[0]), sz: 12, b: 1, col: S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
3158
|
+
sh.push(shape('roundRect', trackX, yy2, trackW, B.barH, S.surface, { adj: 50000, line: S.line }));
|
|
3159
|
+
var fw = Math.max(0.02, Math.min(1, v / (vmaxB || 1)) * trackW);
|
|
3160
|
+
sh.push(shape('roundRect', trackX, yy2, fw, B.barH, hit ? S.accent : S.faint, { adj: 50000 }));
|
|
3161
|
+
if (!isNaN(tgt)) {
|
|
3162
|
+
var tx2 = trackX + Math.min(1, tgt / (vmaxB || 1)) * trackW;
|
|
3163
|
+
sh.push(shape('rect', tx2 - 0.01, yy2 - 0.07, 0.02, B.barH + 0.14, S.body));
|
|
3164
|
+
}
|
|
3165
|
+
sh.push(txSp(trackX + trackW + 0.12, bR.y + i * rowHB, B.valW, rowHB,
|
|
3166
|
+
[[{ t: String(it[1]) + (sec.unit || ''), sz: 12, b: 1, col: hit ? S.accent : S.body, font: S.font },
|
|
3167
|
+
{ t: isNaN(tgt) ? '' : ' / ' + tgt + (sec.unit || ''), sz: 10, col: S.faint, font: S.font }]],
|
|
3168
|
+
{ align: 'r', anchor: 'ctr' }));
|
|
3169
|
+
});
|
|
3170
|
+
} else if (type === 'pyramid') {
|
|
3171
|
+
var P = PT.pyramid, lv = sec.levels || [], nP = Math.max(1, lv.length);
|
|
3172
|
+
var pR = regOf('pyramid', 'primary', { bottom: bodyBottom }) || { x: MX, y: P.y, w: CW, h: bodyBottom - P.y };
|
|
3173
|
+
var availP = pR.h;
|
|
3174
|
+
var rowHP = fitRowH(availP - (nP - 1) * P.gap, nP, P.rowH, 0.26);
|
|
3175
|
+
lv.forEach(function (l, i) {
|
|
3176
|
+
var frac = P.minW + (1 - P.minW) * (nP === 1 ? 1 : i / (nP - 1));
|
|
3177
|
+
var w2 = pR.w * frac, x2 = pR.x + (pR.w - w2) / 2, y2 = pR.y + i * (rowHP + P.gap);
|
|
3178
|
+
var acc = isRec(l) && l.accent;
|
|
3179
|
+
var lt = isRec(l) ? (l.t || '') : String(l[0] || '');
|
|
3180
|
+
var ld = isRec(l) ? (l.d || '') : String(l[1] || '');
|
|
3181
|
+
sh.push(shape('roundRect', x2, y2, w2, rowHP, acc ? S.soft : S.surface, { adj: 5000, line: acc ? undefined : S.line }));
|
|
3182
|
+
var lwP = Math.min(P.labelW, w2 - 0.44);
|
|
3183
|
+
sh.push(txSp(x2 + 0.22, y2, lwP, rowHP, [[{ t: lt, sz: 13.5, b: 1, col: acc ? S.accent : S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
3184
|
+
if (ld) sh.push(txSp(x2 + 0.22 + lwP, y2, w2 - 0.44 - lwP, rowHP, [[{ t: ld, sz: 11, col: S.faint, font: S.font }]], { anchor: 'ctr' }));
|
|
3185
|
+
});
|
|
3186
|
+
} else if (type === 'steps') {
|
|
3187
|
+
var S2 = PT.steps, st = sec.steps || [], maxPer = S2.maxPerRow || 6, groups = sec.groups || [];
|
|
3188
|
+
var sR = regOf('steps', 'primary') || { x: MX, y: S2.y, w: CW, h: S2.barH };
|
|
3189
|
+
var chunks = [];
|
|
3190
|
+
if (groups.length) {
|
|
3191
|
+
var kk = 0;
|
|
3192
|
+
groups.forEach(function (gp) { var cnt = Number(gp[1]) || 0; chunks.push({ label: gp[0], items: st.slice(kk, kk + cnt) }); kk += cnt; });
|
|
3193
|
+
if (kk < st.length) chunks.push({ label: '', items: st.slice(kk) });
|
|
3194
|
+
} else {
|
|
3195
|
+
for (var i3 = 0; i3 < st.length; i3 += maxPer) chunks.push({ label: '', items: st.slice(i3, i3 + maxPer) });
|
|
3196
|
+
}
|
|
3197
|
+
var rowsN = Math.max(1, chunks.length);
|
|
3198
|
+
var availS = bodyBottom - sR.y;
|
|
3199
|
+
var rowHS = fitRowH(availS - (rowsN - 1) * 0.3, rowsN, sR.h || S2.barH, 0.3);
|
|
3200
|
+
var cyS = sR.y;
|
|
3201
|
+
chunks.forEach(function (ck) {
|
|
3202
|
+
if (ck.label) {
|
|
3203
|
+
sh.push(txSp(sR.x, cyS - 0.02, sR.w, 0.26, [[{ t: ck.label, sz: 10, b: 1, col: S.faint, font: S.font, spc: 100 }]]));
|
|
3204
|
+
cyS += 0.28;
|
|
3205
|
+
}
|
|
3206
|
+
var n4 = Math.max(1, ck.items.length);
|
|
3207
|
+
var g4 = cols(sR.w, n4, S2.gap + S2.arrowW);
|
|
3208
|
+
ck.items.forEach(function (it, i) {
|
|
3209
|
+
var acc = isRec(it) && it.accent;
|
|
3210
|
+
var t4 = isRec(it) ? (it.t || '') : String(it[0] || '');
|
|
3211
|
+
var d4 = isRec(it) ? (it.d || '') : String(it[1] || '');
|
|
3212
|
+
var x4 = sR.x + i * g4.step;
|
|
3213
|
+
sh.push(shape('roundRect', x4, cyS, g4.w, rowHS, acc ? S.soft : S.surface, { adj: 5000, line: acc ? undefined : S.line }));
|
|
3214
|
+
var num = String(i + 1); if (num.length < 2) num = '0' + num;
|
|
3215
|
+
var pd = S2.pad, numH = S2.numH, ttlH = S2.titleH;
|
|
3216
|
+
sh.push(txSp(x4 + 0.14, cyS + pd, g4.w - 0.28, numH, [[{ t: num, sz: 10, b: 1, col: S.accent, font: S.font }]]));
|
|
3217
|
+
sh.push(txSp(x4 + 0.14, cyS + pd + numH, g4.w - 0.28, ttlH, [[{ t: t4, sz: 13, b: 1, col: acc ? S.accent : S.ink, font: S.font }]]));
|
|
3218
|
+
if (d4) sh.push(txSp(x4 + 0.14, cyS + pd + numH + ttlH, g4.w - 0.28, Math.max(0.2, rowHS - pd * 2 - numH - ttlH), [[{ t: d4, sz: 10, col: S.faint, font: S.font }]]));
|
|
3219
|
+
if (i < n4 - 1) {
|
|
3220
|
+
sh.push(txSp(x4 + g4.w, cyS, S2.arrowW + S2.gap, rowHS, [[{ t: '→', sz: 12, col: S.faint, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3221
|
+
}
|
|
3222
|
+
});
|
|
3223
|
+
cyS += rowHS + 0.3;
|
|
3224
|
+
});
|
|
3225
|
+
} else if (type === 'table') {
|
|
3226
|
+
var tR = regOf('table', 'primary', { bottom: bodyBottom }) || { x: MX, y: CONTENT_TOP, w: CW, h: bodyBottom - CONTENT_TOP };
|
|
3227
|
+
var headRow = sec.table.head;
|
|
3228
|
+
var colW = sec.table.colW || headRow.map(function () { return tR.w / headRow.length; });
|
|
3229
|
+
sh.push(table(tR.x, tR.y, tR.w, colW, [headRow].concat(sec.table.rows), S, { availH: tR.h }));
|
|
3230
|
+
} else if (type === 'timeline') {
|
|
3231
|
+
var T = PT.timeline, ps = sec.phases || [], np = Math.max(1, ps.length);
|
|
3232
|
+
var tLR = regOf('timeline', 'primary') || { x: MX, y: T.labelY, w: CW };
|
|
3233
|
+
var stepW = (tLR.w - 1.0) / np;
|
|
3234
|
+
sh.push(shape('rect', tLR.x + 0.2, T.axisY, tLR.w - 0.4, 0.02, S.line));
|
|
3235
|
+
ps.forEach(function (p, i) {
|
|
3236
|
+
var x5 = tLR.x + 0.2 + i * stepW;
|
|
3237
|
+
var on = p[3] === 'done' || p[3] === 'now';
|
|
3238
|
+
sh.push(shape('ellipse', x5 - T.dotR / 2, T.dotY, T.dotR, T.dotR, on ? S.accent : S.line));
|
|
3239
|
+
sh.push(txSp(x5 - 0.1, T.labelY, stepW - 0.2, 0.4, [[{ t: p[0], sz: 12, b: 1, col: S.accent, font: S.font, spc: 100 }]]));
|
|
3240
|
+
sh.push(txSp(x5 - 0.1, T.nameY, stepW - 0.2, 0.5, [[{ t: p[1], sz: 17, b: 1, col: p[3] === 'now' ? S.accent : S.ink, font: S.fontDisplay }]]));
|
|
3241
|
+
sh.push(txSp(x5 - 0.1, T.descY, stepW - 0.25, T.descH, [[{ t: p[2] || '', sz: 12, col: S.body, font: S.font }]], { lineSpacing: 18 }));
|
|
3242
|
+
});
|
|
3243
|
+
} else if (type === 'bar') {
|
|
3244
|
+
var ch = sec.chart || {};
|
|
3245
|
+
var dcols2 = (ch.colors || dataColors(model.style, model.theme));
|
|
3246
|
+
var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
|
|
3247
|
+
var isHBar = ch.type === 'hbar';
|
|
3248
|
+
var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
|
|
3249
|
+
{ x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
|
|
3250
|
+
w: isHBar ? CW : CW - PT.bar.chartW, h: 3 };
|
|
3251
|
+
if (isHBar) {
|
|
3252
|
+
chartBlockShapes(sh, ch, S, bRR.x, bRR.y, bRR.w, bRR.h, dcols2);
|
|
3253
|
+
} else {
|
|
3254
|
+
chartBlockShapes(sh, ch, S, bRR.x, bRR.y, bRR.w, Math.max(1.5, bRR.h), dcols2);
|
|
3255
|
+
}
|
|
3256
|
+
if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
|
|
3257
|
+
} else if (type === 'twocol' || type === 'threecol') {
|
|
3258
|
+
var ps2 = sec.paragraphs || [];
|
|
3259
|
+
var nCol = (type === 'threecol') ? 3 : 2;
|
|
3260
|
+
var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
|
|
3261
|
+
var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
|
|
3262
|
+
{ x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
3263
|
+
var gC = cols(tcR.w, nCol, gapC);
|
|
3264
|
+
var perC = Math.ceil(ps2.length / nCol);
|
|
3265
|
+
var availC = tcR.h;
|
|
3266
|
+
/* 探针与 build_pptx.js 同算法:按「各栏字数之和」的最大值估算(保证两通道字号一致) */
|
|
3267
|
+
var longest = 0, gi, pi2;
|
|
3268
|
+
for (gi = 0; gi < nCol; gi++) {
|
|
3269
|
+
var colG = ps2.slice(gi * perC, (gi + 1) * perC), ssum = 0;
|
|
3270
|
+
for (pi2 = 0; pi2 < colG.length; pi2++) ssum += String(colG[pi2][1] || '').length;
|
|
3271
|
+
if (ssum > longest) longest = ssum;
|
|
3272
|
+
}
|
|
3273
|
+
var probe = [], w4;
|
|
3274
|
+
for (w4 = 0; w4 < longest / 120; w4++) probe.push(new Array(121).join('x'));
|
|
3275
|
+
if (!probe.length) probe.push(new Array(1 + longest).join('x'));
|
|
3276
|
+
var fzC = fitFont(probe, gC.w, availC, { max: 13.5, gapFactor: 1.2 });
|
|
3277
|
+
for (var ci3 = 0; ci3 < nCol; ci3++) {
|
|
3278
|
+
var colC = ps2.slice(ci3 * perC, (ci3 + 1) * perC);
|
|
3279
|
+
if (!colC.length) continue;
|
|
3280
|
+
var runsC = [];
|
|
3281
|
+
colC.forEach(function (p) {
|
|
3282
|
+
runsC.push([{ t: p[0] + ' ', sz: fzC, b: 1, col: S.ink, font: S.font },
|
|
3283
|
+
{ t: p[1], sz: fzC - 1, col: S.body, font: S.font }]);
|
|
3284
|
+
});
|
|
3285
|
+
sh.push(txSp(tcR.x + ci3 * gC.step, tcR.y, gC.w, availC, runsC,
|
|
3286
|
+
{ lineSpacing: fzC * 1.5, spaceBefore: 7 }));
|
|
3287
|
+
}
|
|
3288
|
+
} else if (type === 'halftable') {
|
|
3289
|
+
var yH = sec.lead ? PT.common.bodyYWithLead : PT.research.denseTableY;
|
|
3290
|
+
var hLR = regOf('halftable', 'left', { top: yH, bottom: bodyBottom }) ||
|
|
3291
|
+
{ x: MX, y: yH, w: CW * PT.research.halfTableW, h: bodyBottom - yH };
|
|
3292
|
+
var hRR = regOf('halftable', 'right', { top: yH, bottom: bodyBottom }) ||
|
|
3293
|
+
{ x: MX + CW * PT.research.halfChartX, y: yH, w: CW * PT.research.halfChartW, h: bodyBottom - yH };
|
|
3294
|
+
var lwH = hLR.w;
|
|
3295
|
+
if (sec.table && sec.table.head) {
|
|
3296
|
+
var colWH = sec.table.colW || sec.table.head.map(function () { return lwH / sec.table.head.length; });
|
|
3297
|
+
sh.push(table(hLR.x, hLR.y, lwH, colWH, [sec.table.head].concat(sec.table.rows || []), S,
|
|
3298
|
+
{ availH: hLR.h, maxRowH: PT.research.denseRowH + 0.1, minRowH: 0.28 }));
|
|
3299
|
+
}
|
|
3300
|
+
var ecH = sec.chart || {};
|
|
3301
|
+
if (ecH.labels && ecH.values) {
|
|
3302
|
+
chartBlockShapes(sh, ecH, S, hRR.x, hRR.y, hRR.w, Math.max(1.2, hRR.h),
|
|
3303
|
+
(ecH.colors || dataColors(model.style, model.theme)));
|
|
3304
|
+
}
|
|
3305
|
+
} else if (type === 'matrix') {
|
|
3306
|
+
var rhH = sec.rowHeads || [], chH = sec.colHeads || [];
|
|
3307
|
+
var mxR = regOf('matrix', 'primary', { bottom: bodyBottom }) ||
|
|
3308
|
+
{ x: MX, y: PT.research.matrixY, w: CW, h: bodyBottom - PT.research.matrixY };
|
|
3309
|
+
var mY = mxR.y, mLW = PT.research.matrixLabelW;
|
|
3310
|
+
var rowsC = sec.cells || [], nR2 = Math.max(1, rowsC.length);
|
|
3311
|
+
var availM = mxR.h - 0.42;
|
|
3312
|
+
var mCH = fitRowH(availM, nR2, PT.research.matrixCellH, 0.3);
|
|
3313
|
+
var mW = (mxR.w - mLW) / Math.max(1, chH.length);
|
|
3314
|
+
chH.forEach(function (ch3, ci) {
|
|
3315
|
+
sh.push(txSp(mxR.x + mLW + ci * mW, mY, mW, 0.34, [[{ t: ch3, sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3316
|
+
});
|
|
3317
|
+
rowsC.forEach(function (rowC, ri) {
|
|
3318
|
+
var y6 = mY + 0.42 + ri * mCH;
|
|
3319
|
+
sh.push(txSp(mxR.x, y6 + (mCH - 0.5) / 2, mLW - 0.15, 0.5, [[{ t: rhH[ri] || '', sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
3320
|
+
rowC.forEach(function (cell, ci) {
|
|
3321
|
+
var acc = isRec(cell) && cell.accent;
|
|
3322
|
+
var txt = isRec(cell) ? (cell.t || '') : String(cell);
|
|
3323
|
+
var cx4 = mxR.x + mLW + ci * mW;
|
|
3324
|
+
sh.push(shape('roundRect', cx4 + 0.04, y6, mW - 0.08, mCH - 0.1, acc ? S.soft : S.surface,
|
|
3325
|
+
{ adj: 3000, line: acc ? undefined : S.line }));
|
|
3326
|
+
sh.push(txSp(cx4 + 0.14, y6 + 0.08, mW - 0.28, mCH - 0.26, [[{ t: txt, sz: 11, col: acc ? S.accent : S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
3327
|
+
});
|
|
3328
|
+
});
|
|
3329
|
+
} else if (type === 'lane') {
|
|
3330
|
+
var lnS = sec.lanes || [], nL = Math.max(1, lnS.length);
|
|
3331
|
+
var startL = sec.lead ? PT.common.bodyYWithLead : PT.arch.fullStartY;
|
|
3332
|
+
var lnR = regOf('lane', 'primary', { mode: model.mode, bottom: bodyBottom }) ||
|
|
3333
|
+
{ x: MX, y: startL, w: CW, h: bodyBottom - startL };
|
|
3334
|
+
var availL = lnR.h;
|
|
3335
|
+
var lnH = fitRowH(availL - (nL - 1) * PT.arch.laneGap, nL, PT.arch.laneH, 0.3);
|
|
3336
|
+
var lnGap = PT.arch.laneGap, lnHW = PT.arch.laneHeadW;
|
|
3337
|
+
lnS.forEach(function (ln, li) {
|
|
3338
|
+
var y7 = lnR.y + li * (lnH + lnGap);
|
|
3339
|
+
sh.push(shape('roundRect', lnR.x, y7, lnR.w, lnH, S.surface, { adj: 2500, line: S.line }));
|
|
3340
|
+
sh.push(shape('rect', lnR.x, y7, lnHW, lnH, S.surface, { line: S.line }));
|
|
3341
|
+
sh.push(txSp(lnR.x + 0.08, y7, lnHW - 0.16, lnH, [[{ t: ln[0], sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3342
|
+
var steps = ln[1] || [];
|
|
3343
|
+
var stepW2 = Math.min(PT.arch.stepMaxW, (lnR.w - lnHW - 0.4 - (steps.length - 1) * PT.arch.stepGap) / Math.max(1, steps.length));
|
|
3344
|
+
steps.forEach(function (st, si) {
|
|
3345
|
+
var stAcc = isRec(st) && st.accent;
|
|
3346
|
+
var stT = isRec(st) ? st.t : st;
|
|
3347
|
+
var sx2 = lnR.x + lnHW + 0.2 + si * (stepW2 + PT.arch.stepGap);
|
|
3348
|
+
sh.push(shape('roundRect', sx2, y7 + 0.13, stepW2, lnH - 0.26, stAcc ? S.soft : S.bg,
|
|
3349
|
+
{ adj: 4000, line: stAcc ? undefined : S.line }));
|
|
3350
|
+
sh.push(txSp(sx2 + 0.06, y7 + 0.13, stepW2 - 0.12, lnH - 0.26,
|
|
3351
|
+
[[{ t: stT, sz: 11, b: stAcc ? 1 : 0, col: stAcc ? S.accent : S.ink, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3352
|
+
if (si < steps.length - 1) {
|
|
3353
|
+
sh.push(txSp(sx2 + stepW2, y7 + 0.13, PT.arch.stepGap, lnH - 0.26, [[{ t: '→', sz: 11, col: S.faint, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3354
|
+
}
|
|
3355
|
+
});
|
|
3356
|
+
});
|
|
3357
|
+
} else if (type === 'cards') {
|
|
3358
|
+
var cdC = PT.cards, cds = sec.cards || [];
|
|
3359
|
+
var cR = regOf('cards', 'primary', { bottom: bodyBottom }) ||
|
|
3360
|
+
{ x: MX, y: cdC.startY, w: CW, h: bodyBottom - cdC.startY };
|
|
3361
|
+
var cN = Math.min(sec.columns || 3, cds.length || 1);
|
|
3362
|
+
var rN = Math.ceil((cds.length || 1) / cN);
|
|
3363
|
+
var gw2 = (cR.w - (cN - 1) * cdC.gap) / cN;
|
|
3364
|
+
var gh2 = fitRowH(cR.h - (rN - 1) * cdC.gap, rN, cdC.maxH, 0.6);
|
|
3365
|
+
cds.forEach(function (cd, i) {
|
|
3366
|
+
var col2 = i % cN, row2 = Math.floor(i / cN);
|
|
3367
|
+
var x8 = cR.x + col2 * (gw2 + cdC.gap), y8 = cR.y + row2 * (gh2 + cdC.gap);
|
|
3368
|
+
sh.push(shape('roundRect', x8, y8, gw2, gh2, S.surface, { line: S.line, adj: 5000 }));
|
|
3369
|
+
/* 卡片头路标(与 B 通道同源:accent 小方块 = PPTX 侧图标等价物) */
|
|
3370
|
+
var ico2 = 0.18;
|
|
3371
|
+
sh.push(shape('roundRect', x8 + 0.18, y8 + 0.2, ico2, ico2, S.accent, { adj: 2500 }));
|
|
3372
|
+
sh.push(txSp(x8 + 0.18 + ico2 + 0.1, y8 + 0.14, gw2 - 0.36 - ico2 - 0.1, cdC.titleH,
|
|
3373
|
+
[[{ t: cd.title, sz: 15, b: 1, col: S.ink, font: S.font }]]));
|
|
3374
|
+
var paras2 = (cd.points || []).map(function (pt) {
|
|
3375
|
+
var line;
|
|
3376
|
+
if (pt && pt.length != null && typeof pt !== 'string') {
|
|
3377
|
+
var k = pt[0] == null ? '' : String(pt[0]);
|
|
3378
|
+
var v = pt[1] == null ? '' : String(pt[1]);
|
|
3379
|
+
line = (k && v) ? ('· ' + k + ' ' + v) : ('· ' + (k || v));
|
|
3380
|
+
} else if (pt && typeof pt === 'object') {
|
|
3381
|
+
line = '· ' + String(pt.t || '') + (pt.d ? ' ' + String(pt.d) : '');
|
|
3382
|
+
} else {
|
|
3383
|
+
line = '· ' + String(pt == null ? '' : pt);
|
|
3384
|
+
}
|
|
3385
|
+
return [{ t: line, sz: 12, col: S.body, font: S.font }];
|
|
3386
|
+
});
|
|
3387
|
+
sh.push(txSp(x8 + 0.18, y8 + 0.14 + cdC.titleH + 0.06, gw2 - 0.36, Math.max(0.4, gh2 - cdC.titleH - 0.34), paras2,
|
|
3388
|
+
{ lineSpacing: 18 }));
|
|
3389
|
+
});
|
|
3390
|
+
} else if (type === 'split') {
|
|
3391
|
+
/* 双区自由组合页:左区与右区各可为 要点 / 图表 / 表格 / 图片(与 B 通道同语义)。 */
|
|
3392
|
+
var SP = PT.split;
|
|
3393
|
+
var lRs = regOf('split', 'left', { bottom: bodyBottom }) ||
|
|
3394
|
+
{ x: MX, y: SP.chartY, w: CW * SP.leftW, h: bodyBottom - SP.chartY };
|
|
3395
|
+
var rRs = regOf('split', 'right', { bottom: bodyBottom }) ||
|
|
3396
|
+
{ x: MX + CW * SP.rightX, y: SP.chartY, w: CW * SP.rightW, h: bodyBottom - SP.chartY };
|
|
3397
|
+
var lw2 = lRs.w, rw2 = rRs.w, rxL = rRs.x;
|
|
3398
|
+
var lt2 = (sec.left && sec.left.type) || 'points';
|
|
3399
|
+
/* 要点区渲染(左右两区共用;与 B 通道同算法同文本) */
|
|
3400
|
+
var drawPointsL = function (el, x, w) {
|
|
3401
|
+
var ptsP = (el && el.points) || [];
|
|
3402
|
+
var lyP = bodyY, availP = bodyBottom - lyP;
|
|
3403
|
+
var rowHP = fitRowH(availP, ptsP.length, SP.rowH, 0.3);
|
|
3404
|
+
var probeP = [], q5;
|
|
3405
|
+
for (q5 = 0; q5 < ptsP.length; q5++) probeP.push(String(ptsP[q5][0] || '') + String(ptsP[q5][1] || ''));
|
|
3406
|
+
var fzP = fitFont(probeP, w - 0.4, availP, { max: 14, gapFactor: 0.6 });
|
|
3407
|
+
ptsP.forEach(function (pt, i) {
|
|
3408
|
+
var ly2 = lyP + i * rowHP;
|
|
3409
|
+
sh.push(shape('rect', x, ly2 + rowHP / 2 - 0.06, 0.12, 0.12, S.accent));
|
|
3410
|
+
sh.push(txSp(x + 0.28, ly2, w - 0.4, rowHP - 0.04,
|
|
3411
|
+
[[{ t: pt[0] + ' ', sz: fzP, b: 1, col: S.ink, font: S.font },
|
|
3412
|
+
{ t: pt[1], sz: fzP - 1, col: S.body, font: S.font }]]));
|
|
3413
|
+
});
|
|
3414
|
+
};
|
|
3415
|
+
if (lt2 === 'table') {
|
|
3416
|
+
var lhL = [sec.left.head].concat(sec.left.rows);
|
|
3417
|
+
var lcwL = sec.left.colW || sec.left.head.map(function () { return lw2 / sec.left.head.length; });
|
|
3418
|
+
sh.push(table(lRs.x, SP.tableY, lw2, lcwL, lhL, S, { availH: bodyBottom - SP.tableY, maxRowH: SP.tableRowH, minRowH: 0.3 }));
|
|
3419
|
+
} else if (lt2 === 'image') {
|
|
3420
|
+
var imL = (sec.left && sec.left.image) || {};
|
|
3421
|
+
addImageEl(sh, imL.src, S, lRs.x, SP.chartY, lw2, Math.max(1.4, bodyBottom - SP.chartY),
|
|
3422
|
+
{ placeholder: !!imL.placeholder || !imL.src, layout: 'half', multi: false, fit: imL.fit });
|
|
3423
|
+
if (imL.caption) sh.push(txSp(lRs.x, SP.capY, lw2, 0.35, [[{ t: imL.caption, sz: 11, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
3424
|
+
} else if (lt2 !== 'points') {
|
|
3425
|
+
var lc2 = sec.left;
|
|
3426
|
+
chartBlockShapes(sh, lc2, S, lRs.x, SP.chartY, lw2,
|
|
3427
|
+
Math.max(1.4, bodyBottom - SP.chartY - (lc2.cap ? 0.1 : 0)),
|
|
3428
|
+
lc2.colors || dataColors(model.style, model.theme));
|
|
3429
|
+
if (lc2.cap) sh.push(txSp(lRs.x, SP.capY, lw2, 0.35, [[{ t: lc2.cap, sz: 12, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
3430
|
+
} else {
|
|
3431
|
+
drawPointsL(sec.left, lRs.x, lw2);
|
|
3432
|
+
}
|
|
3433
|
+
var rt = (sec.right && sec.right.type) || 'bar';
|
|
3434
|
+
if (rt === 'table') {
|
|
3435
|
+
var rrL = [sec.right.head].concat(sec.right.rows);
|
|
3436
|
+
var rcwL = sec.right.colW || sec.right.head.map(function () { return rw2 / sec.right.head.length; });
|
|
3437
|
+
sh.push(table(rxL, SP.tableY, rw2, rcwL, rrL, S, { availH: bodyBottom - SP.tableY, maxRowH: SP.tableRowH, minRowH: 0.3 }));
|
|
3438
|
+
} else if (rt === 'image') {
|
|
3439
|
+
var im2 = (sec.right && sec.right.image) || {};
|
|
3440
|
+
addImageEl(sh, im2.src, S, rxL, SP.chartY, rw2, Math.max(1.4, bodyBottom - SP.chartY),
|
|
3441
|
+
{ placeholder: !!im2.placeholder || !im2.src, layout: 'half', multi: false });
|
|
3442
|
+
if (im2.caption) sh.push(txSp(rxL, SP.capY, rw2, 0.35, [[{ t: im2.caption, sz: 11, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
3443
|
+
} else if (rt === 'points') {
|
|
3444
|
+
drawPointsL(sec.right, rxL, rw2);
|
|
3445
|
+
} else {
|
|
3446
|
+
var bc2 = sec.right;
|
|
3447
|
+
var dcols3 = bc2.colors || dataColors(model.style, model.theme);
|
|
3448
|
+
chartBlockShapes(sh, bc2, S, rxL, SP.chartY, rw2, Math.max(1.4, bodyBottom - SP.chartY - (bc2.cap ? 0.1 : 0)), dcols3);
|
|
3449
|
+
if (bc2.cap) sh.push(txSp(rxL, SP.capY, rw2, 0.35, [[{ t: bc2.cap, sz: 12, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
3450
|
+
}
|
|
3451
|
+
} else if (type === 'diagram') {
|
|
3452
|
+
var D = PT.diagram, lys = sec.layers || [];
|
|
3453
|
+
var dgR = regOf('diagram', 'primary', { mode: _mode, bottom: bodyBottom }) ||
|
|
3454
|
+
{ x: MX, y: D.bodyStartY, w: CW, h: bodyBottom - D.bodyStartY };
|
|
3455
|
+
var dStart = dgR.y;
|
|
3456
|
+
var dEnd = dgR.y + dgR.h;
|
|
3457
|
+
var nLay = Math.max(1, lys.length);
|
|
3458
|
+
var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
|
|
3459
|
+
lys.forEach(function (lay, li) {
|
|
3460
|
+
var y9 = dStart + li * (lh2 + D.layerGap);
|
|
3461
|
+
var focus = lay[2] === 'focus';
|
|
3462
|
+
sh.push(shape('roundRect', dgR.x, y9, D.layerBarW, lh2, focus ? S.accent : S.surface,
|
|
3463
|
+
{ adj: 3000, line: focus ? undefined : S.line }));
|
|
3464
|
+
sh.push(txSp(dgR.x + 0.1, y9 + 0.1, D.layerBarW - 0.2, lh2 - 0.2, [[{ t: lay[0], sz: 12, b: 1, col: focus ? S.onAccent : S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
3465
|
+
var nodes = lay[1] || [], nn2 = Math.max(1, nodes.length);
|
|
3466
|
+
var availW = dgR.w - D.layerBarW - 0.2;
|
|
3467
|
+
var nw2 = Math.min(D.nodeMaxW, (availW - (nn2 - 1) * D.nodeGap) / nn2);
|
|
3468
|
+
nodes.forEach(function (nd, ni) {
|
|
3469
|
+
var x10 = dgR.x + D.layerBarW + 0.2 + ni * (nw2 + D.nodeGap);
|
|
3470
|
+
var ndacc = isRec(nd) ? nd.accent : false;
|
|
3471
|
+
var ntt = isRec(nd) ? nd.t : nd;
|
|
3472
|
+
var nds = isRec(nd) ? (nd.d || '') : '';
|
|
3473
|
+
sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
|
|
3474
|
+
sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
|
|
3475
|
+
[[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
|
|
3476
|
+
if (nds) sh.push(txSp(x10 + 0.12, y9 + lh2 - 0.5, nw2 - 0.24, 0.4, [[{ t: nds, sz: 10, col: S.faint, font: S.font }]]));
|
|
3477
|
+
});
|
|
3478
|
+
if (li < lys.length - 1) {
|
|
3479
|
+
var cy2 = y9 + lh2 + D.layerGap / 2;
|
|
3480
|
+
var cxm2 = dgR.x + D.layerBarW / 2;
|
|
3481
|
+
sh.push(shape('rect', cxm2 - 0.01, cy2 - 0.1, 0.02, 0.14, focus ? S.accent : S.line));
|
|
3482
|
+
sh.push(shape('rect', cxm2 - 0.06, cy2 + 0.04, 0.12, 0.02, focus ? S.accent : S.line));
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3485
|
+
if (sec.legend) {
|
|
3486
|
+
var lx2 = MX;
|
|
3487
|
+
sec.legend.forEach(function (lg2) {
|
|
3488
|
+
sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
|
|
3489
|
+
lx2 += 2.0;
|
|
3490
|
+
});
|
|
3491
|
+
}
|
|
3492
|
+
} else if (type === 'exhibit') {
|
|
3493
|
+
var eReg = regOf('exhibit', 'primary')
|
|
3494
|
+
|| { x: MX, y: PT.exhibit.chartY, w: CW, badgeY: PT.exhibit.badgeY };
|
|
3495
|
+
if (sec.exhibitNo) {
|
|
3496
|
+
sh.push(txSp(MX, eReg.badgeY != null ? eReg.badgeY : PT.exhibit.badgeY, CW, 0.35, [[{ t: String(sec.exhibitNo).toUpperCase(), sz: 11.5, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
3497
|
+
}
|
|
3498
|
+
var ec2 = sec.chart || {};
|
|
3499
|
+
if (ec2.labels && ec2.values) {
|
|
3500
|
+
var edcols2 = (ec2.colors || dataColors(model.style, model.theme));
|
|
3501
|
+
var hh2 = Math.max(1.2, chartBottom(!!sec.soWhat, !!sec.footnote) - eReg.y - 0.05);
|
|
3502
|
+
if (ec2.type === 'hbar') {
|
|
3503
|
+
chartBlockShapes(sh, ec2, S, eReg.x, eReg.y, eReg.w, hh2, edcols2);
|
|
3504
|
+
} else {
|
|
3505
|
+
chartBlockShapes(sh, ec2, S, eReg.x + 0.4, eReg.y - 0.1, eReg.w - 0.8, hh2 + 0.15, edcols2);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
} else if (type === 'sankey' || type === 'treemap' || type === 'boxplot'
|
|
3509
|
+
|| type === 'network' || type === 'marimekko' || type === 'streamgraph') {
|
|
3510
|
+
var infoR = regOf(type, 'primary', { bottom: bodyBottom }) ||
|
|
3511
|
+
{ x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
3512
|
+
infoApprox(sh, sec, S, infoR.y, infoR.y + infoR.h);
|
|
3513
|
+
} else {
|
|
3514
|
+
/* points(默认):要点列表 + 可选右侧指标列 */
|
|
3515
|
+
var P2 = PT.points, pts3 = sec.points || [];
|
|
3516
|
+
var hasM = !!(sec.metrics && sec.metrics.length);
|
|
3517
|
+
var ptsR = regOf('points', 'primary', { top: bodyY, bottom: bodyBottom }) ||
|
|
3518
|
+
{ x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
3519
|
+
/* 指标列最多 3 个 + 文本区严格分区(与 build_pptx.js 同算法) */
|
|
3520
|
+
var nm2 = hasM ? Math.min(sec.metrics.length, 3) : 0;
|
|
3521
|
+
var mw2 = hasM ? Math.min(P2.metricW, Math.max(1.6, (CW - 4.8) / nm2)) : 0;
|
|
3522
|
+
var textW = hasM ? Math.max(4.2, (PW - MX - nm2 * mw2) - MX - 0.35) : CW - 0.4;
|
|
3523
|
+
var availPt = ptsR.h;
|
|
3524
|
+
var rowHPt = fitRowH(availPt, pts3.length, P2.rowH, 0.3);
|
|
3525
|
+
var probeP = [], q5;
|
|
3526
|
+
for (q5 = 0; q5 < pts3.length; q5++) probeP.push(String(pts3[q5][0] || '') + String(pts3[q5][1] || ''));
|
|
3527
|
+
var fzP = fitFont(probeP, textW - 0.32, availPt, { max: 15, gapFactor: 0.55 });
|
|
3528
|
+
pts3.forEach(function (p, i) {
|
|
3529
|
+
var y11 = ptsR.y + i * rowHPt;
|
|
3530
|
+
sh.push(shape('rect', ptsR.x, y11 + rowHPt / 2 - P2.markSize / 2, P2.markSize, P2.markSize, S.accent));
|
|
3531
|
+
sh.push(txSp(ptsR.x + 0.32, y11, textW, rowHPt - 0.04,
|
|
3532
|
+
[[{ t: p[0] + ' ', sz: fzP, b: 1, col: S.ink, font: S.font },
|
|
3533
|
+
{ t: p[1], sz: fzP - 1, col: S.body, font: S.font }]]));
|
|
3534
|
+
});
|
|
3535
|
+
if (hasM) {
|
|
3536
|
+
var gx2 = PW - MX - nm2 * mw2;
|
|
3537
|
+
var vSz = nm2 >= 3 ? 28 : 34;
|
|
3538
|
+
sec.metrics.slice(0, nm2).forEach(function (m, i) {
|
|
3539
|
+
var x12 = gx2 + i * mw2;
|
|
3540
|
+
sh.push(txSp(x12, P2.metricY, mw2 - 0.2, P2.metricValH, [[{ t: m[0], sz: vSz, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
3541
|
+
sh.push(txSp(x12, P2.metricCapY, mw2 - 0.2, 0.9, [[{ t: m[1], sz: 11, col: S.faint, font: S.font }]]));
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
/* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
|
|
3546
|
+
if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
|
|
3547
|
+
if (sec.footnote) footnoteLine(sh, sec.footnote, S);
|
|
3548
|
+
if (flagH) flagBar(sh, flagItems, S, flagY);
|
|
3549
|
+
slides.push(sh);
|
|
3550
|
+
});
|
|
3551
|
+
|
|
3552
|
+
/* 收尾(主题一致强调带——accent-soft 底 + 常规文字,浅色模式不再出深色页) */
|
|
3553
|
+
var CL = PT.closing;
|
|
3554
|
+
var cl = [shape('rect', 0, 0, PW, PH, S.soft)];
|
|
3555
|
+
cl.push(shape('rect', MX, CL.eyebrowY, 0.9, 0.045, S.accent));
|
|
3556
|
+
cl.push(txSp(MX, CL.eyebrowY + 0.16, CW, 0.4, [[{ t: '下一步', sz: 13, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
3557
|
+
cl.push(txSp(MX, CL.titleY, CW - 1, CL.titleH, [[{ t: model.closing.title, sz: 30, b: 1, col: S.ink, font: S.fontDisplay }]]));
|
|
3558
|
+
var clPts = model.closing.points || [], clN = Math.max(1, clPts.length);
|
|
3559
|
+
var clW = (CW - (clN - 1) * 0.3) / clN;
|
|
3560
|
+
clPts.forEach(function (p, i) {
|
|
3561
|
+
var x13 = MX + i * (clW + 0.3);
|
|
3562
|
+
cl.push(shape('roundRect', x13, CL.pointsY - 0.2, clW, CL.pointTitleH + CL.pointBodyH + 0.32, S.surface, { line: S.line, adj: 4000 }));
|
|
3563
|
+
cl.push(txSp(x13 + 0.18, CL.pointsY, clW - 0.36, CL.pointTitleH, [[{ t: p[0], sz: 17, b: 1, col: S.accent, font: S.font }]]));
|
|
3564
|
+
cl.push(txSp(x13 + 0.18, CL.pointsY + CL.pointTitleH, clW - 0.36, CL.pointBodyH, [[{ t: p[1], sz: 12.5, col: S.body, font: S.font }]]));
|
|
3565
|
+
});
|
|
3566
|
+
slides.push(cl);
|
|
3567
|
+
|
|
3568
|
+
/* 页码 */
|
|
3569
|
+
var total = slides.length;
|
|
3570
|
+
slides.forEach(function (sh, i) {
|
|
3571
|
+
sh.push(txSp(PW - 1.4, (LAY.pageNumY != null ? LAY.pageNumY : PH - 0.5), 0.9, 0.3,
|
|
3572
|
+
[[{ t: (i + 1) + ' / ' + total, sz: 10, col: S.faint, font: S.font }]], { align: 'r' }));
|
|
3573
|
+
});
|
|
3574
|
+
return slides;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
/* ── 打包骨架 ── */
|
|
3578
|
+
var NS = {
|
|
3579
|
+
a: 'http://schemas.openxmlformats.org/drawingml/2006/main',
|
|
3580
|
+
p: 'http://schemas.openxmlformats.org/presentationml/2006/main',
|
|
3581
|
+
r: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
|
3582
|
+
};
|
|
3583
|
+
function slideXml(shapes) {
|
|
3584
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
|
3585
|
+
'<p:sld xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '">' +
|
|
3586
|
+
'<p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
|
|
3587
|
+
'<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>' +
|
|
3588
|
+
shapes.join('') + '</p:spTree></p:cSld><p:clrMapOvr><a:overrideClrMapping bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/></p:clrMapOvr></p:sld>';
|
|
3589
|
+
}
|
|
3590
|
+
function relsXml(pairs) {
|
|
3591
|
+
var rs = pairs.map(function (p) {
|
|
3592
|
+
return '<Relationship Id="' + p[0] + '" Type="' + p[1] + '" Target="' + p[2] + '"/>';
|
|
3593
|
+
}).join('');
|
|
3594
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
|
3595
|
+
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rs + '</Relationships>';
|
|
3596
|
+
}
|
|
3597
|
+
var RT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
|
|
3598
|
+
|
|
3599
|
+
/* 每页 slide XML(预览模态用:与导出同一引擎,所见即所得)
|
|
3600
|
+
theme="dark" 时用 PRESETS_DARK(与页面深色主题同源),默认 light */
|
|
3601
|
+
function themePresets(theme) {
|
|
3602
|
+
return (theme === 'dark' && typeof PRESETS_DARK !== 'undefined' &&
|
|
3603
|
+
PRESETS_DARK && Object.keys(PRESETS_DARK).length) ? PRESETS_DARK : PRESETS;
|
|
3604
|
+
}
|
|
3605
|
+
function slidesXml(model) {
|
|
3606
|
+
var S = themePresets(model && model.theme)[model.style] || PRESETS['business-blue'];
|
|
3607
|
+
return slidesOf(model, S).map(slideXml);
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
/* ── 模型自检(schema 单源驱动:scripts/model-schema.json 经 sync_runtime 注入的 MODEL_SCHEMA;
|
|
3611
|
+
与 scripts/extract_model.py 本地校验同一份定义,双端不漂移) ── */
|
|
3612
|
+
function schemaGet(obj, path) {
|
|
3613
|
+
var cur = obj, ks = path.split('.');
|
|
3614
|
+
for (var i = 0; i < ks.length; i++) {
|
|
3615
|
+
if (cur == null || typeof cur !== 'object') return undefined;
|
|
3616
|
+
cur = cur[ks[i]];
|
|
3617
|
+
}
|
|
3618
|
+
return cur;
|
|
3619
|
+
}
|
|
3620
|
+
function schemaFieldOk(sec, spec) {
|
|
3621
|
+
/* 'anyof:a|b:c'(任一满足,用于 image 页型 src/items/placeholder 三选一);
|
|
3622
|
+
'a.b:array' / 'a.b:str' / 'a.b'(真值检查)——与 scripts/extract_model.py 同语义。 */
|
|
3623
|
+
if (spec.indexOf('anyof:') === 0) {
|
|
3624
|
+
return spec.slice(6).split('|').some(function (alt) { return alt && schemaFieldOk(sec, alt); });
|
|
3625
|
+
}
|
|
3626
|
+
var m = spec.match(/^(.*?)(?::(array|str))?$/), path = m[1], kind = m[2];
|
|
3627
|
+
var v = schemaGet(sec, path);
|
|
3628
|
+
if (kind === 'array') return Array.isArray(v) && v.length > 0;
|
|
3629
|
+
if (kind === 'str') return typeof v === 'string' && v.trim().length > 0;
|
|
3630
|
+
return !!v;
|
|
3631
|
+
}
|
|
3632
|
+
/* 图片模型软校验:版式 / 裁切策略 / 多图数量 / 外链 src——与 validate_pptx.py 硬门禁同口径,
|
|
3633
|
+
浏览器预览与 extract_model.py 都能提前看到问题。 */
|
|
3634
|
+
function imageModelWarnings(sec, where, warnings) {
|
|
3635
|
+
var IS = (typeof IMG !== 'undefined' && IMG) || {};
|
|
3636
|
+
var holders = [{ img: sec.image, at: where + '.image' }];
|
|
3637
|
+
if (sec.right && sec.right.image) holders.push({ img: sec.right.image, at: where + '.right.image' });
|
|
3638
|
+
holders.forEach(function (h) {
|
|
3639
|
+
var img = h.img;
|
|
3640
|
+
if (!img || typeof img !== 'object') return;
|
|
3641
|
+
var layouts = IS.layouts || ['full', 'half', 'bleed', 'grid', 'compare', 'wall'];
|
|
3642
|
+
var fitEnum = IS.fitEnum || ['cover', 'contain'];
|
|
3643
|
+
var items = Array.isArray(img.items) ? img.items.filter(Boolean) : [];
|
|
3644
|
+
var layout = String(img.layout || (items.length > 1 ? 'grid' : 'full')).toLowerCase();
|
|
3645
|
+
if (layouts.indexOf(layout) < 0) {
|
|
3646
|
+
warnings.push(h.at + '.layout="' + layout + '" 未知(应为 ' + layouts.join('/') + ')');
|
|
3647
|
+
}
|
|
3648
|
+
if (img.fit && fitEnum.indexOf(String(img.fit).toLowerCase()) < 0) {
|
|
3649
|
+
warnings.push(h.at + '.fit="' + img.fit + '" 未知(应为 ' + fitEnum.join('/') + ')');
|
|
3650
|
+
}
|
|
3651
|
+
if (img.placeholder && (img.src || items.length)) {
|
|
3652
|
+
warnings.push(h.at + ' 同时声明 placeholder 与图片源(placeholder 优先,图片将被忽略)');
|
|
3653
|
+
}
|
|
3654
|
+
if (items.length > (IS.maxPerPage || 6)) {
|
|
3655
|
+
warnings.push(h.at + '.items 共 ' + items.length + ' 张,超出单页上限 ' + (IS.maxPerPage || 6));
|
|
3656
|
+
}
|
|
3657
|
+
if (layout === 'compare' && items.length && items.length !== 2) {
|
|
3658
|
+
warnings.push(h.at + '.items 为 compare 版式时应为 2 张(当前 ' + items.length + ')');
|
|
3659
|
+
}
|
|
3660
|
+
[img].concat(items).forEach(function (it) {
|
|
3661
|
+
if (it && typeof it === 'object' && typeof it.src === 'string' &&
|
|
3662
|
+
/^\s*(?:https?:)?\/\//.test(it.src)) {
|
|
3663
|
+
warnings.push(h.at + ' 含外链 src(只允许 data: 内联或相对路径)');
|
|
3664
|
+
}
|
|
3665
|
+
});
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
function validateModel(model) {
|
|
3670
|
+
var missing = [], warnings = [];
|
|
3671
|
+
var M = (typeof MODEL_SCHEMA !== 'undefined' && MODEL_SCHEMA) || {};
|
|
3672
|
+
var pageTypes = M.pageTypes || {};
|
|
3673
|
+
if (!model || typeof model !== 'object') {
|
|
3674
|
+
return { ok: false, missing: ['window.REPORT_MODEL(内容模型缺失)'], warnings: warnings, pages: 0 };
|
|
3675
|
+
}
|
|
3676
|
+
(M.model && M.model.required || ['title', 'sections:array', 'closing.title', 'closing.points:array'])
|
|
3677
|
+
.forEach(function (spec) {
|
|
3678
|
+
if (!schemaFieldOk(model, spec)) missing.push(spec.split(':')[0] + '(顶层必填)');
|
|
3679
|
+
});
|
|
3680
|
+
var mode = model.mode || 'presentation';
|
|
3681
|
+
var hasAgenda = Array.isArray(model.agenda) && model.agenda.length > 0;
|
|
3682
|
+
var agMin = (M.model && M.model.agendaMin && M.model.agendaMin[mode]) || 0;
|
|
3683
|
+
if (agMin > 0 && (!hasAgenda || model.agenda.length < agMin)) {
|
|
3684
|
+
missing.push('agenda(大纲 ≥' + agMin + ' 条)');
|
|
3685
|
+
}
|
|
3686
|
+
var secs = model.sections || [];
|
|
3687
|
+
if (!Array.isArray(secs) || secs.length < ((M.model && M.model.sectionsMin) || 1)) {
|
|
3688
|
+
missing.push('sections(章节页 ≥1)');
|
|
3689
|
+
}
|
|
3690
|
+
if (!model.closing || !model.closing.title) missing.push('closing(收尾页)');
|
|
3691
|
+
secs.forEach(function (sec, i) {
|
|
3692
|
+
var t = sec.type || 'points';
|
|
3693
|
+
var def = pageTypes[t];
|
|
3694
|
+
var where = 'sections[' + i + ']';
|
|
3695
|
+
if (!def) { missing.push(where + '.type="' + t + '"(未知页型)'); return; }
|
|
3696
|
+
if (!sec.title) missing.push(where + '.title(第 ' + (i + 1) + ' 章标题)');
|
|
3697
|
+
(def.required || []).forEach(function (spec) {
|
|
3698
|
+
if (!schemaFieldOk(sec, spec)) missing.push(where + '.' + spec + '(' + (def.label || t) + '必填)');
|
|
3699
|
+
});
|
|
3700
|
+
if (def.modes && def.modes.indexOf(mode) < 0) {
|
|
3701
|
+
warnings.push('sections[' + i + '] 页型 "' + t + '" 文档口径适用于 ' + def.modes.join('/') + ',当前 mode="' + mode + '"');
|
|
3702
|
+
}
|
|
3703
|
+
imageModelWarnings(sec, where, warnings);
|
|
3704
|
+
});
|
|
3705
|
+
if (M.modes && model.mode && M.modes.indexOf(model.mode) < 0) {
|
|
3706
|
+
warnings.push('mode="' + model.mode + '" 未知,按 presentation 处理');
|
|
3707
|
+
}
|
|
3708
|
+
if (model.style && !PRESETS[model.style]) warnings.push('style="' + model.style + '" 未知,回落商务蓝');
|
|
3709
|
+
if (model.theme && model.theme !== 'light' && model.theme !== 'dark') {
|
|
3710
|
+
warnings.push('theme="' + model.theme + '" 未知(应为 light/dark),按浅色导出');
|
|
3711
|
+
}
|
|
3712
|
+
if (secs.length < ((M.model && M.model.sectionsRecommended) || 3)) {
|
|
3713
|
+
warnings.push('章节页仅 ' + secs.length + ' 页,正式报告建议 ≥3');
|
|
3714
|
+
}
|
|
3715
|
+
var agMax = (M.model && M.model.agendaComfortMax) || 16;
|
|
3716
|
+
if ((model.agenda || []).length > agMax) {
|
|
3717
|
+
warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
|
|
3718
|
+
}
|
|
3719
|
+
var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
|
|
3720
|
+
return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
var api = {
|
|
3724
|
+
slidesXml: slidesXml,
|
|
3725
|
+
validateModel: validateModel,
|
|
3726
|
+
/* 以下供 scripts/gen_channel_a.js 在 Node 侧组装完整 PPTX 包(回归双裁判内部质量路径) */
|
|
3727
|
+
slidesOf: slidesOf, slideXml: slideXml, relsXml: relsXml, NS: NS, RT: RT, PRESETS: PRESETS,
|
|
3728
|
+
PRESETS_DARK: (typeof PRESETS_DARK !== 'undefined' ? PRESETS_DARK : null),
|
|
3729
|
+
dataColors: dataColors,
|
|
3730
|
+
esc: esc, solid: solid, E: E
|
|
3731
|
+
};
|
|
3732
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api; /* Node:gen_channel_a.js 用 */
|
|
3733
|
+
g.TopPptHtml = api;
|
|
3734
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
3735
|
+
/* __TOPPPT_RUNTIME_END__ */
|
|
3736
|
+
</script>
|
|
3737
|
+
</body>
|
|
3738
|
+
</html>
|