@lemonppt/renderer 0.1.8 → 0.2.1
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/dist/client/client-render.js +136 -0
- package/dist/client/editor-script.js +3355 -0
- package/dist/client/theme-echarts.js +122 -0
- package/dist/client-render.d.ts +39 -0
- package/dist/client-render.d.ts.map +1 -0
- package/dist/client-render.js +65 -0
- package/dist/client-render.js.map +1 -0
- package/dist/editor-script.d.ts +1 -1
- package/dist/editor-script.d.ts.map +1 -1
- package/dist/editor-script.js +2791 -310
- package/dist/editor-script.js.map +1 -1
- package/dist/export-pdf.d.ts +5 -0
- package/dist/export-pdf.d.ts.map +1 -1
- package/dist/export-pdf.js +22 -2
- package/dist/export-pdf.js.map +1 -1
- package/dist/export-pptx.d.ts +1157 -4
- package/dist/export-pptx.d.ts.map +1 -1
- package/dist/export-pptx.js +2436 -1356
- package/dist/export-pptx.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/render-editor-data.d.ts +21 -0
- package/dist/render-editor-data.d.ts.map +1 -0
- package/dist/render-editor-data.js +1068 -0
- package/dist/render-editor-data.js.map +1 -0
- package/dist/render.d.ts +0 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +946 -160
- package/dist/render.js.map +1 -1
- package/dist/theme-echarts.d.ts +9 -0
- package/dist/theme-echarts.d.ts.map +1 -0
- package/dist/theme-echarts.js +14 -0
- package/dist/theme-echarts.js.map +1 -0
- package/dist/theme01-pptx.d.ts +12 -0
- package/dist/theme01-pptx.d.ts.map +1 -0
- package/dist/theme01-pptx.js +2954 -0
- package/dist/theme01-pptx.js.map +1 -0
- package/dist/theme02-pptx.d.ts +5 -0
- package/dist/theme02-pptx.d.ts.map +1 -0
- package/dist/theme02-pptx.js +1063 -0
- package/dist/theme02-pptx.js.map +1 -0
- package/dist/theme03-pptx.d.ts +5 -0
- package/dist/theme03-pptx.d.ts.map +1 -0
- package/dist/theme03-pptx.js +3279 -0
- package/dist/theme03-pptx.js.map +1 -0
- package/dist/theme04-pptx.d.ts +6 -0
- package/dist/theme04-pptx.d.ts.map +1 -0
- package/dist/theme04-pptx.js +5496 -0
- package/dist/theme04-pptx.js.map +1 -0
- package/dist/theme05-pptx.d.ts +5 -0
- package/dist/theme05-pptx.d.ts.map +1 -0
- package/dist/theme05-pptx.js +3152 -0
- package/dist/theme05-pptx.js.map +1 -0
- package/dist/theme06-pptx.d.ts +124 -0
- package/dist/theme06-pptx.d.ts.map +1 -0
- package/dist/theme06-pptx.js +8154 -0
- package/dist/theme06-pptx.js.map +1 -0
- package/dist/theme07-pptx.d.ts +49 -0
- package/dist/theme07-pptx.d.ts.map +1 -0
- package/dist/theme07-pptx.js +2579 -0
- package/dist/theme07-pptx.js.map +1 -0
- package/dist/theme08-pptx.d.ts +19 -0
- package/dist/theme08-pptx.d.ts.map +1 -0
- package/dist/theme08-pptx.js +1411 -0
- package/dist/theme08-pptx.js.map +1 -0
- package/dist/theme09-pptx.d.ts +18 -0
- package/dist/theme09-pptx.d.ts.map +1 -0
- package/dist/theme09-pptx.js +1513 -0
- package/dist/theme09-pptx.js.map +1 -0
- package/dist/theme10-pptx.d.ts +18 -0
- package/dist/theme10-pptx.d.ts.map +1 -0
- package/dist/theme10-pptx.js +2889 -0
- package/dist/theme10-pptx.js.map +1 -0
- package/package.json +8 -6
- package/templates/editor.html +580 -0
- package/templates/editor.js +222 -0
package/dist/export-pptx.js
CHANGED
|
@@ -3,9 +3,188 @@
|
|
|
3
3
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
4
|
import { normalizeDeckGoal } from '@lemonppt/core';
|
|
5
5
|
import PptxGenJS from 'pptxgenjs';
|
|
6
|
+
import { mkdtempSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import crypto from 'node:crypto';
|
|
11
|
+
import { configureTheme08, registerTheme08Renderers } from './theme08-pptx.js';
|
|
12
|
+
import { configureTheme09, registerTheme09Renderers } from './theme09-pptx.js';
|
|
13
|
+
import { configureTheme10, registerTheme10Renderers } from './theme10-pptx.js';
|
|
14
|
+
import { registerTheme01Renderers, cleanupTheme01TempImages } from './theme01-pptx.js';
|
|
15
|
+
// 重新导出 theme01 通用渲染器,供 theme02-pptx.ts 等复用,避免 theme02→theme01→export-pptx 的循环依赖。
|
|
16
|
+
export { renderChartV1, renderContentV1, renderCoverV1, renderClosing, renderComparisonV1 } from './theme01-pptx.js';
|
|
17
|
+
import { registerTheme02Renderers } from './theme02-pptx.js';
|
|
18
|
+
import { registerTheme03Renderers } from './theme03-pptx.js';
|
|
19
|
+
import { registerTheme04Renderers } from './theme04-pptx.js';
|
|
20
|
+
import { registerTheme05Renderers } from './theme05-pptx.js';
|
|
21
|
+
import { registerTheme06Renderers } from './theme06-pptx.js';
|
|
22
|
+
import { registerTheme07Renderers } from './theme07-pptx.js';
|
|
23
|
+
import { renderTheme07SectorV1, renderTheme07TableV1 } from './theme07-pptx.js';
|
|
24
|
+
import { renderTheme06Bento, renderTheme06BigNumber, renderTheme06Case, renderTheme06ChainFlow, renderTheme06Chapter, renderTheme06Closing, renderTheme06CompanyProfile, renderTheme06CompanySpotlight, renderTheme06Comparison, renderTheme06ContentNumbered, renderTheme06Cover, renderTheme06CoverVariant, renderTheme06DealMap, renderTheme06ListBased, renderTheme06Matrix, renderTheme06MetricShowcase, renderTheme06Milestone, renderTheme06Process, renderTheme06Quote, renderTheme06Rank, renderTheme06RiskMatrix, renderTheme06SectorSpotlight, renderTheme06Sources, renderTheme06Statement, renderTheme06Summary, renderTheme06TechLandscape, renderTheme06Timeline, renderTheme06Toc, renderTheme06Triad } from './theme06-pptx.js';
|
|
25
|
+
// Load theme snapshot for PPTX export colors and gradients
|
|
26
|
+
// Use ESM-compatible path resolution
|
|
27
|
+
const _currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
function loadThemeSnapshot() {
|
|
29
|
+
const possiblePaths = [
|
|
30
|
+
path.resolve(_currentDir, 'theme-snapshot.json'),
|
|
31
|
+
path.resolve(_currentDir, '../src/theme-snapshot.json'),
|
|
32
|
+
path.resolve(_currentDir, '../theme-snapshot.json'),
|
|
33
|
+
];
|
|
34
|
+
for (const snapshotPath of possiblePaths) {
|
|
35
|
+
try {
|
|
36
|
+
const content = readFileSync(snapshotPath, 'utf-8');
|
|
37
|
+
console.log(`Loading theme snapshot from: ${snapshotPath}`);
|
|
38
|
+
return JSON.parse(content);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Continue to next path
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.warn('Warning: theme-snapshot.json not found in any location, using default theme configs');
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
const THEME_SNAPSHOT = loadThemeSnapshot();
|
|
48
|
+
// Load layout coordinates for PPTX export
|
|
49
|
+
function loadLayoutCoordinates() {
|
|
50
|
+
const possiblePaths = [
|
|
51
|
+
path.resolve(_currentDir, 'pptx-layout-coordinates.json'),
|
|
52
|
+
path.resolve(_currentDir, '../src/pptx-layout-coordinates.json'),
|
|
53
|
+
path.resolve(_currentDir, '../pptx-layout-coordinates.json'),
|
|
54
|
+
];
|
|
55
|
+
for (const coordPath of possiblePaths) {
|
|
56
|
+
try {
|
|
57
|
+
const content = readFileSync(coordPath, 'utf-8');
|
|
58
|
+
console.log(`Loading layout coordinates from: ${coordPath}`);
|
|
59
|
+
return JSON.parse(content);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Continue to next path
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
console.warn('Warning: pptx-layout-coordinates.json not found, using default coordinates');
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
const LAYOUT_COORDINATES = loadLayoutCoordinates();
|
|
69
|
+
// Load theme06-specific layout coordinates
|
|
70
|
+
function loadTheme06LayoutCoordinates() {
|
|
71
|
+
const possiblePaths = [
|
|
72
|
+
path.resolve(_currentDir, 'theme06-layout-coordinates.json'),
|
|
73
|
+
path.resolve(_currentDir, '../src/theme06-layout-coordinates.json'),
|
|
74
|
+
path.resolve(_currentDir, '../theme06-layout-coordinates.json'),
|
|
75
|
+
];
|
|
76
|
+
for (const coordPath of possiblePaths) {
|
|
77
|
+
try {
|
|
78
|
+
const content = readFileSync(coordPath, 'utf-8');
|
|
79
|
+
console.log(`Loading theme06 layout coordinates from: ${coordPath}`);
|
|
80
|
+
return JSON.parse(content);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Continue to next path
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.warn('Warning: theme06-layout-coordinates.json not found');
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const THEME06_LAYOUT_COORDINATES = loadTheme06LayoutCoordinates();
|
|
90
|
+
// Load theme07-specific layout coordinates (v2 coordinate system)
|
|
91
|
+
function loadTheme07LayoutCoordinates() {
|
|
92
|
+
const possiblePaths = [
|
|
93
|
+
path.resolve(_currentDir, 'theme07-layout-coordinates.json'),
|
|
94
|
+
path.resolve(_currentDir, '../src/theme07-layout-coordinates.json'),
|
|
95
|
+
path.resolve(_currentDir, '../theme07-layout-coordinates.json'),
|
|
96
|
+
];
|
|
97
|
+
for (const coordPath of possiblePaths) {
|
|
98
|
+
try {
|
|
99
|
+
const content = readFileSync(coordPath, 'utf-8');
|
|
100
|
+
console.log(`Loading theme07 layout coordinates from: ${coordPath}`);
|
|
101
|
+
return JSON.parse(content);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Continue to next path
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.warn('Warning: theme07-layout-coordinates.json not found');
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
const THEME07_LAYOUT_COORDINATES = loadTheme07LayoutCoordinates();
|
|
111
|
+
// Load theme08-specific layout coordinates
|
|
112
|
+
function loadTheme08LayoutCoordinates() {
|
|
113
|
+
const possiblePaths = [
|
|
114
|
+
path.resolve(_currentDir, 'theme08-layout-coordinates.json'),
|
|
115
|
+
path.resolve(_currentDir, '../src/theme08-layout-coordinates.json'),
|
|
116
|
+
path.resolve(_currentDir, '../theme08-layout-coordinates.json'),
|
|
117
|
+
];
|
|
118
|
+
for (const coordPath of possiblePaths) {
|
|
119
|
+
try {
|
|
120
|
+
const content = readFileSync(coordPath, 'utf-8');
|
|
121
|
+
console.log(`Loading theme08 layout coordinates from: ${coordPath}`);
|
|
122
|
+
return JSON.parse(content);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Continue to next path
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.warn('Warning: theme08-layout-coordinates.json not found');
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
const THEME08_LAYOUT_COORDINATES = loadTheme08LayoutCoordinates();
|
|
132
|
+
/** Convert CSS layout ID to coordinate config key */
|
|
133
|
+
function normalizeLayoutId(layoutId) {
|
|
134
|
+
// theme01_cover_v1 -> cover_v1
|
|
135
|
+
const parts = layoutId.split('_');
|
|
136
|
+
// Find the layout role (cover, toc, metric, stats, chart, content, comparison, timeline, table)
|
|
137
|
+
const roles = ['cover', 'toc', 'metric', 'stats', 'chart', 'content', 'comparison', 'timeline', 'table', 'chapter', 'closing', 'quote', 'feature', 'gallery', 'image', 'team', 'pricing', 'faq', 'roadmap', 'process', 'quadrant', 'matrix', 'bento', 'risk', 'trend', 'scorecard', 'outlook', 'statement', 'conclusion'];
|
|
138
|
+
for (const role of roles) {
|
|
139
|
+
if (layoutId.includes(role)) {
|
|
140
|
+
const version = parts[parts.length - 1] || 'v1';
|
|
141
|
+
return `${role}_${version}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return 'default';
|
|
145
|
+
}
|
|
146
|
+
/** Get layout coordinates by layout ID */
|
|
147
|
+
export function getLayoutCoordinates(layoutId) {
|
|
148
|
+
// First check theme-specific coordinates (e.g., theme06_cover_v1)
|
|
149
|
+
const themeId = layoutId.split('_')[0] + '_' + layoutId.split('_')[1]; // e.g., "theme06"
|
|
150
|
+
if (themeId === 'theme06') {
|
|
151
|
+
const theme06Layouts = THEME06_LAYOUT_COORDINATES.layouts;
|
|
152
|
+
if (theme06Layouts && theme06Layouts[layoutId]) {
|
|
153
|
+
return { ...theme06Layouts[layoutId], _themeSpecific: true };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (themeId === 'theme07') {
|
|
157
|
+
const theme07Layouts = THEME07_LAYOUT_COORDINATES.layouts;
|
|
158
|
+
if (theme07Layouts && theme07Layouts[layoutId]) {
|
|
159
|
+
return { ...theme07Layouts[layoutId], _themeSpecific: true };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (themeId === 'theme08') {
|
|
163
|
+
const theme08Layouts = THEME08_LAYOUT_COORDINATES.layouts;
|
|
164
|
+
if (theme08Layouts && theme08Layouts[layoutId]) {
|
|
165
|
+
return { ...theme08Layouts[layoutId], _themeSpecific: true };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Then check generic coordinates
|
|
169
|
+
const normalizedId = normalizeLayoutId(layoutId);
|
|
170
|
+
const layouts = LAYOUT_COORDINATES.layouts;
|
|
171
|
+
if (layouts && layouts[normalizedId]) {
|
|
172
|
+
return layouts[normalizedId];
|
|
173
|
+
}
|
|
174
|
+
// Fallback: try broader match (just the role without version)
|
|
175
|
+
if (layouts) {
|
|
176
|
+
for (const [key, value] of Object.entries(layouts)) {
|
|
177
|
+
const roleKey = key.split('_')[0];
|
|
178
|
+
if (normalizedId.startsWith(roleKey)) {
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
6
185
|
const THEME_CONFIGS = {
|
|
7
|
-
|
|
8
|
-
|
|
186
|
+
theme01: {
|
|
187
|
+
colorsLight: {
|
|
9
188
|
primary: '1D1D1F',
|
|
10
189
|
secondary: '6E6E73',
|
|
11
190
|
accent: '2563EB',
|
|
@@ -15,63 +194,604 @@ const THEME_CONFIGS = {
|
|
|
15
194
|
surface: 'FFFFFF',
|
|
16
195
|
surfaceElevated: 'F9FAFB',
|
|
17
196
|
},
|
|
197
|
+
colorsDark: {
|
|
198
|
+
primary: 'F0F0F5',
|
|
199
|
+
secondary: 'C0C0CC',
|
|
200
|
+
accent: '3B82F6',
|
|
201
|
+
white: '1A1A20',
|
|
202
|
+
light: '25252C',
|
|
203
|
+
border: '3A3A44',
|
|
204
|
+
surface: '1A1A20',
|
|
205
|
+
surfaceElevated: '25252C',
|
|
206
|
+
},
|
|
18
207
|
fonts: { heading: 'Inter', body: 'Inter', mono: 'JetBrains Mono' },
|
|
19
208
|
chartColors: ['2563EB', '10B981', 'F59E0B', 'EF4444', '8B5CF6', 'EC4899'],
|
|
209
|
+
chartColorsDark: ['3B82F6', '34D399', 'FBBF24', 'F87171', 'A78BFA', 'F472B6'],
|
|
20
210
|
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
primary: '
|
|
24
|
-
secondary: '
|
|
25
|
-
accent: '
|
|
26
|
-
white: '
|
|
27
|
-
light: '
|
|
28
|
-
border: '
|
|
29
|
-
surface: '
|
|
30
|
-
surfaceElevated: '
|
|
211
|
+
theme02: {
|
|
212
|
+
colorsLight: {
|
|
213
|
+
primary: 'F0F4F8',
|
|
214
|
+
secondary: 'A0A8B0',
|
|
215
|
+
accent: '00E5B0',
|
|
216
|
+
white: '080A0E',
|
|
217
|
+
light: '0F1218',
|
|
218
|
+
border: '2A2E36',
|
|
219
|
+
surface: '0F1218',
|
|
220
|
+
surfaceElevated: '161A22',
|
|
221
|
+
},
|
|
222
|
+
colorsDark: {
|
|
223
|
+
primary: 'F0F4F8',
|
|
224
|
+
secondary: 'A0A8B0',
|
|
225
|
+
accent: '00E5B0',
|
|
226
|
+
white: '080A0E',
|
|
227
|
+
light: '0F1218',
|
|
228
|
+
border: '2A2E36',
|
|
229
|
+
surface: '0F1218',
|
|
230
|
+
surfaceElevated: '161A22',
|
|
231
|
+
},
|
|
232
|
+
fonts: { heading: 'Space Grotesk', body: 'Noto Sans SC', mono: 'Space Mono' },
|
|
233
|
+
chartColors: ['00E5B0', 'FFD166', '00B4FF', 'FF6B6B', 'A78BFA', 'F472B6'],
|
|
234
|
+
},
|
|
235
|
+
theme03: {
|
|
236
|
+
colorsLight: {
|
|
237
|
+
primary: '05080D',
|
|
238
|
+
secondary: '5A5E68',
|
|
239
|
+
accent: '0077B6',
|
|
240
|
+
white: 'F1F3F5',
|
|
241
|
+
light: 'E5E8EB',
|
|
242
|
+
border: 'D0D2D6',
|
|
243
|
+
surface: 'FFFFFF',
|
|
244
|
+
surfaceElevated: 'F1F3F5',
|
|
245
|
+
},
|
|
246
|
+
colorsDark: {
|
|
247
|
+
primary: 'E8EDF3',
|
|
248
|
+
secondary: '8A8F99',
|
|
249
|
+
accent: '00B4FF',
|
|
250
|
+
white: '05080D',
|
|
251
|
+
light: '0D1118',
|
|
252
|
+
border: '22262D',
|
|
253
|
+
surface: '05080D',
|
|
254
|
+
surfaceElevated: '0D1118',
|
|
255
|
+
},
|
|
256
|
+
fonts: { heading: 'Space Grotesk', body: 'Noto Sans SC', mono: 'Space Mono' },
|
|
257
|
+
chartColors: ['00B4FF', 'FF2A6D', '8B5CF6', 'FF7B54', 'FF9F1C', '22D3EE'],
|
|
258
|
+
},
|
|
259
|
+
theme04: {
|
|
260
|
+
colorsLight: {
|
|
261
|
+
primary: '1A1A1A',
|
|
262
|
+
secondary: '6B6B73',
|
|
263
|
+
accent: '22A55C',
|
|
264
|
+
white: 'FAFAF8',
|
|
265
|
+
light: 'F1F1ED',
|
|
266
|
+
border: 'E5E5E0',
|
|
267
|
+
surface: 'FFFFFF',
|
|
268
|
+
surfaceElevated: 'FAFAF8',
|
|
269
|
+
},
|
|
270
|
+
colorsDark: {
|
|
271
|
+
primary: 'F4F4F5',
|
|
272
|
+
secondary: 'A0A0A8',
|
|
273
|
+
accent: '3ADE80',
|
|
274
|
+
white: '0A0A0A',
|
|
275
|
+
light: '121212',
|
|
276
|
+
border: '2A2A2E',
|
|
277
|
+
surface: '0A0A0A',
|
|
278
|
+
surfaceElevated: '121212',
|
|
31
279
|
},
|
|
32
280
|
fonts: { heading: 'Space Grotesk', body: 'Inter', mono: 'Space Mono' },
|
|
33
|
-
chartColors: ['
|
|
281
|
+
chartColors: ['3ADE80', 'FF6B9D', '4ECDC4', 'FFD166', 'A78BFA', 'FF8A5B'],
|
|
282
|
+
},
|
|
283
|
+
theme05: {
|
|
284
|
+
colorsLight: {
|
|
285
|
+
primary: '1A1A1A',
|
|
286
|
+
secondary: '6B6B73',
|
|
287
|
+
accent: 'C73E2F',
|
|
288
|
+
white: 'F7F4EF',
|
|
289
|
+
light: 'EDE9E2',
|
|
290
|
+
border: 'DCD6CC',
|
|
291
|
+
surface: 'FFFFFF',
|
|
292
|
+
surfaceElevated: 'F7F4EF',
|
|
293
|
+
},
|
|
294
|
+
colorsDark: {
|
|
295
|
+
primary: 'F4F4F5',
|
|
296
|
+
secondary: 'A0A0A8',
|
|
297
|
+
accent: 'E85D4E',
|
|
298
|
+
white: '15151A',
|
|
299
|
+
light: '1E1E24',
|
|
300
|
+
border: '2A2A30',
|
|
301
|
+
surface: '15151A',
|
|
302
|
+
surfaceElevated: '1E1E24',
|
|
303
|
+
},
|
|
304
|
+
fonts: { heading: 'Inter', body: 'Inter', mono: 'Space Mono' },
|
|
305
|
+
chartColors: ['E85D4E', 'F5A623', '0FA3B1', '4A58D9', '7C3AED', '22C55E'],
|
|
306
|
+
},
|
|
307
|
+
theme06: {
|
|
308
|
+
colorsLight: {
|
|
309
|
+
primary: '0F172A',
|
|
310
|
+
secondary: '5A5E68',
|
|
311
|
+
accent: '7BC800',
|
|
312
|
+
white: 'F7F8FA',
|
|
313
|
+
light: 'ECEFF3',
|
|
314
|
+
border: 'D0D2D6',
|
|
315
|
+
surface: 'FFFFFF',
|
|
316
|
+
surfaceElevated: 'F1F3F5',
|
|
317
|
+
},
|
|
318
|
+
colorsDark: {
|
|
319
|
+
primary: 'F2F4F7',
|
|
320
|
+
secondary: 'A0A0A8',
|
|
321
|
+
accent: 'B6FF2B',
|
|
322
|
+
white: '0B0F17',
|
|
323
|
+
light: '121926',
|
|
324
|
+
border: '2A2E36',
|
|
325
|
+
surface: '0B0F17',
|
|
326
|
+
surfaceElevated: '151C27',
|
|
327
|
+
},
|
|
328
|
+
fonts: { heading: 'Inter', body: 'Inter', mono: 'Space Mono' },
|
|
329
|
+
chartColors: ['B6FF2B', '00E5B0', '2E9FFF', 'FF6B45', 'A855F7', 'FFD100'],
|
|
34
330
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
primary: '
|
|
38
|
-
secondary: '
|
|
39
|
-
accent: '
|
|
331
|
+
theme07: {
|
|
332
|
+
colorsLight: {
|
|
333
|
+
primary: '1A1D23',
|
|
334
|
+
secondary: '5A5E68',
|
|
335
|
+
accent: '0E86E6',
|
|
336
|
+
accent2: '12B886',
|
|
337
|
+
accentCool: 'FF7A33',
|
|
40
338
|
white: 'FFFFFF',
|
|
41
|
-
light: '
|
|
42
|
-
border: '
|
|
43
|
-
surface: '
|
|
339
|
+
light: 'F2F5F8',
|
|
340
|
+
border: 'E2E5EA',
|
|
341
|
+
surface: 'FFFFFF',
|
|
342
|
+
surfaceElevated: 'F4F6F8',
|
|
343
|
+
},
|
|
344
|
+
colorsDark: {
|
|
345
|
+
primary: 'F2F4F7',
|
|
346
|
+
secondary: 'A0A0A8',
|
|
347
|
+
accent: '5B9BD5',
|
|
348
|
+
white: '0D100F',
|
|
349
|
+
light: '151C27',
|
|
350
|
+
border: '2A2E36',
|
|
351
|
+
surface: '151C27',
|
|
352
|
+
surfaceElevated: '1A2330',
|
|
353
|
+
},
|
|
354
|
+
fonts: { heading: 'Source Serif Pro', body: 'Inter', mono: 'JetBrains Mono' },
|
|
355
|
+
chartColors: ['0E86E6', '12B886', 'FF7A33', '7C5CFC', '16BFD9', 'F24E7D'],
|
|
356
|
+
},
|
|
357
|
+
theme08: {
|
|
358
|
+
colorsLight: {
|
|
359
|
+
primary: '1A1812',
|
|
360
|
+
secondary: '524C3D',
|
|
361
|
+
accent: '8F6608',
|
|
362
|
+
white: 'FDFBF4',
|
|
363
|
+
light: 'FAF7EE',
|
|
364
|
+
border: 'D9CFB8',
|
|
365
|
+
surface: 'FFFFFF',
|
|
366
|
+
surfaceElevated: 'FFFFFF',
|
|
367
|
+
},
|
|
368
|
+
colorsDark: {
|
|
369
|
+
primary: 'F7F4EA',
|
|
370
|
+
secondary: 'B8AE98',
|
|
371
|
+
accent: 'FFD23F',
|
|
372
|
+
white: '14130D',
|
|
373
|
+
light: '1C1A12',
|
|
374
|
+
border: '512810',
|
|
375
|
+
surface: '211F17',
|
|
376
|
+
surfaceElevated: '2C281E',
|
|
377
|
+
},
|
|
378
|
+
fonts: { heading: 'Noto Sans SC', body: 'Noto Sans SC', mono: 'JetBrains Mono' },
|
|
379
|
+
chartColors: ['FFD23F', 'FF4D4D', '2BD2FF', 'C44DFF', '3DDC84', 'FF9F1C'],
|
|
380
|
+
},
|
|
381
|
+
theme09: {
|
|
382
|
+
// 墨韵专色 · 杂志印刷风:双基底(纸底/墨底)按版式预分配,
|
|
383
|
+
// appearance(primary/muted) 仅调专色浓度,不翻转明暗。
|
|
384
|
+
colorsLight: {
|
|
385
|
+
primary: '14161C',
|
|
386
|
+
secondary: '3A3F4A',
|
|
387
|
+
accent: 'C8102E',
|
|
388
|
+
white: 'F4F1EA',
|
|
389
|
+
light: 'EDE9DE',
|
|
390
|
+
border: 'D5CFC0',
|
|
391
|
+
surface: 'FDFCF8',
|
|
44
392
|
surfaceElevated: 'FFFFFF',
|
|
45
393
|
},
|
|
46
|
-
|
|
47
|
-
|
|
394
|
+
colorsDark: {
|
|
395
|
+
primary: 'F4F1EA',
|
|
396
|
+
secondary: 'C9C6BE',
|
|
397
|
+
accent: 'F04A62',
|
|
398
|
+
white: '14161C',
|
|
399
|
+
light: '0D0F14',
|
|
400
|
+
border: '2E323C',
|
|
401
|
+
surface: '1C2027',
|
|
402
|
+
surfaceElevated: '242932',
|
|
403
|
+
},
|
|
404
|
+
fonts: { heading: 'Noto Serif SC', body: 'Noto Sans SC', mono: 'Space Mono' },
|
|
405
|
+
chartColors: ['C8102E', '2B4A8B', '8F6410', '3F6B5C', '6D4C7D', 'A8562A'],
|
|
406
|
+
},
|
|
407
|
+
theme10: {
|
|
408
|
+
// 金色指数 · 金融编辑风:墨黑金线底(始终深色),冰蓝主强调 + 香槟金/铜点睛。
|
|
409
|
+
// 与 theme09 双基底不同,theme10 仅一套深色基底;具体强调色由专属渲染器按 SUBSTRATE 取色。
|
|
410
|
+
colorsLight: {
|
|
411
|
+
primary: 'EEF1F6',
|
|
412
|
+
secondary: 'A9B2C0',
|
|
413
|
+
accent: '4A7FD4',
|
|
414
|
+
white: '0A0E14',
|
|
415
|
+
light: '121823',
|
|
416
|
+
border: '2A3340',
|
|
417
|
+
surface: '121823',
|
|
418
|
+
surfaceElevated: '1A2230',
|
|
419
|
+
},
|
|
420
|
+
colorsDark: {
|
|
421
|
+
primary: 'EEF1F6',
|
|
422
|
+
secondary: 'A9B2C0',
|
|
423
|
+
accent: '4A7FD4',
|
|
424
|
+
white: '0A0E14',
|
|
425
|
+
light: '121823',
|
|
426
|
+
border: '2A3340',
|
|
427
|
+
surface: '121823',
|
|
428
|
+
surfaceElevated: '1A2230',
|
|
429
|
+
},
|
|
430
|
+
fonts: { heading: 'Noto Sans SC', body: 'Noto Sans SC', mono: 'Space Mono' },
|
|
431
|
+
chartColors: ['4A7FD4', 'D9B977', 'E8A23A', 'C97A52', '9A82DC', '6F9BD8'],
|
|
48
432
|
},
|
|
49
433
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
434
|
+
// Helper to load theme colors from snapshot
|
|
435
|
+
function getSnapshotThemeColors(themeId, appearance) {
|
|
436
|
+
const snapshotData = THEME_SNAPSHOT[themeId];
|
|
437
|
+
if (!snapshotData)
|
|
438
|
+
return null;
|
|
439
|
+
const mode = appearance === 'light' ? 'light' : 'dark';
|
|
440
|
+
const data = snapshotData[mode];
|
|
441
|
+
if (!data)
|
|
442
|
+
return null;
|
|
443
|
+
return {
|
|
444
|
+
colors: data.colors,
|
|
445
|
+
fonts: data.fonts,
|
|
446
|
+
chartColors: data.chartColors,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
// Helper to get gradient from snapshot
|
|
450
|
+
function getSnapshotGradient(themeId) {
|
|
451
|
+
const snapshotData = THEME_SNAPSHOT[themeId];
|
|
452
|
+
if (!snapshotData || !snapshotData.gradients)
|
|
453
|
+
return null;
|
|
454
|
+
// Return the first gradient available, or the theme-specific one
|
|
455
|
+
const gradientKeys = Object.keys(snapshotData.gradients);
|
|
456
|
+
if (gradientKeys.length === 0)
|
|
457
|
+
return null;
|
|
458
|
+
// Try to find a gradient specific to this theme
|
|
459
|
+
if (snapshotData.gradients[themeId]) {
|
|
460
|
+
return snapshotData.gradients[themeId];
|
|
461
|
+
}
|
|
462
|
+
// Return first available
|
|
463
|
+
return snapshotData.gradients[gradientKeys[0]];
|
|
464
|
+
}
|
|
465
|
+
function resolveThemeConfig(theme, language, colorScheme = 'light', appearance = 'dark') {
|
|
466
|
+
const themeId = theme ?? 'theme01';
|
|
467
|
+
const snapshotColors = getSnapshotThemeColors(themeId, appearance);
|
|
468
|
+
// Fallback to hardcoded config if snapshot is not available
|
|
469
|
+
const config = THEME_CONFIGS[themeId] ?? THEME_CONFIGS.theme01;
|
|
470
|
+
const effectiveColorScheme = colorScheme || (theme === 'theme02' || theme === 'theme03' ? 'scheme-a' : theme === 'theme04' ? 'green' : 'light');
|
|
471
|
+
let isDark = false;
|
|
472
|
+
let colors;
|
|
473
|
+
let chartColors;
|
|
474
|
+
let resolved;
|
|
475
|
+
if (theme === 'theme02') {
|
|
476
|
+
isDark = true;
|
|
477
|
+
colors = config.colorsDark;
|
|
478
|
+
chartColors = config.chartColors;
|
|
479
|
+
}
|
|
480
|
+
else if (theme === 'theme03') {
|
|
481
|
+
isDark = appearance !== 'light';
|
|
482
|
+
colors = isDark ? config.colorsDark : config.colorsLight;
|
|
483
|
+
const isSchemeB = effectiveColorScheme === 'scheme-b';
|
|
484
|
+
const isLight03 = appearance === 'light';
|
|
485
|
+
const accent = isSchemeB
|
|
486
|
+
? (isLight03 ? 'B45309' : 'FF9F1C')
|
|
487
|
+
: (isLight03 ? '0077B6' : '00B4FF');
|
|
488
|
+
const accent2 = isSchemeB
|
|
489
|
+
? (isLight03 ? '0077B6' : '00B4FF')
|
|
490
|
+
: (isLight03 ? 'C2185B' : 'FF2A6D');
|
|
491
|
+
const accentCool = isSchemeB
|
|
492
|
+
? (isLight03 ? '6B21A8' : '8B5CF6')
|
|
493
|
+
: (isLight03 ? '6B21A8' : '8B5CF6');
|
|
494
|
+
colors = {
|
|
495
|
+
...colors,
|
|
496
|
+
accent,
|
|
497
|
+
secondary: accent2,
|
|
498
|
+
};
|
|
499
|
+
chartColors = [accent, accent2, accentCool, isLight03 ? 'C2410C' : 'FF7B54', isLight03 ? 'B45309' : 'FF9F1C', isLight03 ? '0077B6' : '22D3EE'];
|
|
500
|
+
}
|
|
501
|
+
else if (theme === 'theme04') {
|
|
502
|
+
isDark = appearance !== 'light';
|
|
503
|
+
colors = isDark ? config.colorsDark : config.colorsLight;
|
|
504
|
+
const isLight04 = appearance === 'light';
|
|
505
|
+
const tone = ['green', 'yellow', 'blue', 'pink'].includes(colorScheme) ? colorScheme : 'green';
|
|
506
|
+
const toneAccents = {
|
|
507
|
+
green: { dark: '3ADE80', light: '22A55C' },
|
|
508
|
+
yellow: { dark: 'FFD166', light: 'D97706' },
|
|
509
|
+
blue: { dark: '4ECDC4', light: '0D9488' },
|
|
510
|
+
pink: { dark: 'FF6B9D', light: 'DB2777' },
|
|
511
|
+
};
|
|
512
|
+
const accent = toneAccents[tone][isLight04 ? 'light' : 'dark'];
|
|
513
|
+
const accent2 = isLight04 ? 'DB2777' : 'FF6B9D';
|
|
514
|
+
const accentCool = isLight04 ? '0D9488' : '4ECDC4';
|
|
515
|
+
colors = {
|
|
516
|
+
...colors,
|
|
517
|
+
accent,
|
|
518
|
+
secondary: accent2,
|
|
519
|
+
};
|
|
520
|
+
chartColors = [accent, accent2, accentCool, isLight04 ? 'D97706' : 'FFD166', isLight04 ? '7C3AED' : 'A78BFA', isLight04 ? 'EA580C' : 'FF8A5B'];
|
|
521
|
+
}
|
|
522
|
+
else if (theme === 'theme05') {
|
|
523
|
+
isDark = appearance !== 'light';
|
|
524
|
+
colors = isDark ? config.colorsDark : config.colorsLight;
|
|
525
|
+
const isLight05 = appearance === 'light';
|
|
526
|
+
const scheme = ['coral', 'amber', 'teal', 'indigo', 'violet'].includes(colorScheme) ? colorScheme : 'coral';
|
|
527
|
+
const schemeAccents = {
|
|
528
|
+
coral: { dark: 'E85D4E', light: 'C73E2F', accent2: { dark: 'F5A623', light: 'C47B08' }, accentCool: { dark: '4A58D9', light: '3A46B0' } },
|
|
529
|
+
amber: { dark: 'F5A623', light: 'C47B08', accent2: { dark: 'E85D4E', light: 'C73E2F' }, accentCool: { dark: '0FA3B1', light: '0B7A85' } },
|
|
530
|
+
teal: { dark: '0FA3B1', light: '0B7A85', accent2: { dark: '4A58D9', light: '3A46B0' }, accentCool: { dark: 'F5A623', light: 'C47B08' } },
|
|
531
|
+
indigo: { dark: '4A58D9', light: '3A46B0', accent2: { dark: '0FA3B1', light: '0B7A85' }, accentCool: { dark: 'F5A623', light: 'C47B08' } },
|
|
532
|
+
violet: { dark: '7C3AED', light: '5B25C1', accent2: { dark: 'F5A623', light: 'C47B08' }, accentCool: { dark: '0FA3B1', light: '0B7A85' } },
|
|
533
|
+
};
|
|
534
|
+
const accent = schemeAccents[scheme][isLight05 ? 'light' : 'dark'];
|
|
535
|
+
const accent2 = schemeAccents[scheme].accent2[isLight05 ? 'light' : 'dark'];
|
|
536
|
+
const accentCool = schemeAccents[scheme].accentCool[isLight05 ? 'light' : 'dark'];
|
|
537
|
+
colors = {
|
|
538
|
+
...colors,
|
|
539
|
+
accent,
|
|
540
|
+
secondary: accent2,
|
|
541
|
+
};
|
|
542
|
+
chartColors = [accent, accent2, accentCool, isLight05 ? 'C73E2F' : 'E85D4E', isLight05 ? '0B7A85' : '0FA3B1', isLight05 ? '5B25C1' : '7C3AED'];
|
|
543
|
+
}
|
|
544
|
+
else if (theme === 'theme06') {
|
|
545
|
+
isDark = appearance !== 'light';
|
|
546
|
+
colors = isDark ? config.colorsDark : config.colorsLight;
|
|
547
|
+
const isLight06 = appearance === 'light';
|
|
548
|
+
const scheme = ['volt', 'magma', 'nebula', 'nova'].includes(colorScheme) ? colorScheme : 'volt';
|
|
549
|
+
const schemeAccents = {
|
|
550
|
+
volt: { dark: 'B6FF2B', light: '7BC800', accent2: { dark: '00E5B0', light: '009E7A' }, accentCool: { dark: '2E9FFF', light: '1A6FD6' } },
|
|
551
|
+
magma: { dark: 'FF6B45', light: 'D94E2B', accent2: { dark: 'FFC700', light: 'C78F00' }, accentCool: { dark: '00C2FF', light: '008FBF' } },
|
|
552
|
+
nebula: { dark: '2E9FFF', light: '1A6FD6', accent2: { dark: 'A855F7', light: '7C2ED3' }, accentCool: { dark: '22D3EE', light: '0B9FB8' } },
|
|
553
|
+
nova: { dark: 'FFD100', light: 'C78F00', accent2: { dark: 'FF6B45', light: 'D94E2B' }, accentCool: { dark: '00E5B0', light: '009E7A' } },
|
|
554
|
+
};
|
|
555
|
+
const accent = schemeAccents[scheme][isLight06 ? 'light' : 'dark'];
|
|
556
|
+
const accent2 = schemeAccents[scheme].accent2[isLight06 ? 'light' : 'dark'];
|
|
557
|
+
const accentCool = schemeAccents[scheme].accentCool[isLight06 ? 'light' : 'dark'];
|
|
558
|
+
colors = {
|
|
559
|
+
...colors,
|
|
560
|
+
accent,
|
|
561
|
+
secondary: accent2,
|
|
562
|
+
accent2,
|
|
563
|
+
accentCool,
|
|
564
|
+
borderStrong: isLight06 ? 'D0D2D6' : '2A2E36',
|
|
565
|
+
surfaceSolid: isLight06 ? 'FFFFFF' : '151C27',
|
|
566
|
+
edge: isLight06 ? 'C8CDD4' : '2A2E36',
|
|
567
|
+
};
|
|
568
|
+
chartColors = [accent, accent2, accentCool, isLight06 ? 'D94E2B' : 'FF6B45', isLight06 ? '009E7A' : '00E5B0', isLight06 ? '7C2ED3' : 'A855F7'];
|
|
569
|
+
}
|
|
570
|
+
else if (theme === 'theme07') {
|
|
571
|
+
// theme07 冷白调研风:单一 light 外观,电光蓝主色 + 翠绿辅色 + 暖橙警示(明亮鲜艳)
|
|
572
|
+
isDark = false;
|
|
573
|
+
const accent2 = '12B886';
|
|
574
|
+
const accentCool = 'FF7A33';
|
|
575
|
+
colors = {
|
|
576
|
+
...config.colorsLight,
|
|
577
|
+
accent2,
|
|
578
|
+
accentCool,
|
|
579
|
+
borderStrong: 'D0D2D6',
|
|
580
|
+
surfaceSolid: 'FFFFFF',
|
|
581
|
+
};
|
|
582
|
+
chartColors = config.chartColors;
|
|
583
|
+
}
|
|
584
|
+
else if (theme === 'theme08') {
|
|
585
|
+
// theme08 黑金实验风:primary(深黑/荧光金) / muted(象牙暖白/古铜金)
|
|
586
|
+
const isMuted = appearance === 'muted' || appearance === 'light';
|
|
587
|
+
isDark = !isMuted;
|
|
588
|
+
colors = isMuted ? config.colorsLight : config.colorsDark;
|
|
589
|
+
chartColors = config.chartColors;
|
|
590
|
+
}
|
|
591
|
+
else if (theme === 'theme09') {
|
|
592
|
+
// theme09 墨韵专色:双基底按版式预分配,全局仅给默认纸底;
|
|
593
|
+
// 墨底由专属渲染器在每一页内部按 layout 覆盖背景。
|
|
594
|
+
const isMuted = appearance === 'muted' || appearance === 'light';
|
|
595
|
+
isDark = false; // 默认纸底;墨底页由 theme09 渲染器覆盖
|
|
596
|
+
colors = isMuted ? config.colorsLight : config.colorsLight;
|
|
597
|
+
chartColors = config.chartColors;
|
|
598
|
+
}
|
|
599
|
+
else if (theme === 'theme10') {
|
|
600
|
+
// theme10 金色指数:始终深色墨黑金线底,专属渲染器内部按 layout 取强调色/基底。
|
|
601
|
+
isDark = true;
|
|
602
|
+
colors = config.colorsDark;
|
|
603
|
+
chartColors = config.chartColors;
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
isDark = effectiveColorScheme === 'dark';
|
|
607
|
+
colors = isDark ? config.colorsDark : config.colorsLight;
|
|
608
|
+
chartColors = isDark ? (config.chartColorsDark ?? config.chartColors) : config.chartColors;
|
|
609
|
+
}
|
|
610
|
+
// If snapshot has data, use it as the base and overlay scheme-specific colors
|
|
611
|
+
if (snapshotColors) {
|
|
612
|
+
// Use snapshot colors as base
|
|
613
|
+
const snapshotColorBase = snapshotColors.colors;
|
|
614
|
+
// For themes with scheme/variant support, apply the accent overrides
|
|
615
|
+
if (themeId === 'theme03') {
|
|
616
|
+
const isSchemeB = effectiveColorScheme === 'scheme-b';
|
|
617
|
+
const isLight03 = appearance === 'light';
|
|
618
|
+
const accent = isSchemeB
|
|
619
|
+
? (isLight03 ? 'B45309' : 'FF9F1C')
|
|
620
|
+
: (isLight03 ? '0077B6' : '00B4FF');
|
|
621
|
+
const accent2 = isSchemeB
|
|
622
|
+
? (isLight03 ? '0077B6' : '00B4FF')
|
|
623
|
+
: (isLight03 ? 'C2185B' : 'FF2A6D');
|
|
624
|
+
colors = {
|
|
625
|
+
...snapshotColorBase,
|
|
626
|
+
accent,
|
|
627
|
+
secondary: accent2,
|
|
628
|
+
};
|
|
629
|
+
chartColors = snapshotColors.chartColors;
|
|
630
|
+
}
|
|
631
|
+
else if (themeId === 'theme04') {
|
|
632
|
+
const isLight04 = appearance === 'light';
|
|
633
|
+
const tone = ['green', 'yellow', 'blue', 'pink'].includes(colorScheme) ? colorScheme : 'green';
|
|
634
|
+
const toneAccents = {
|
|
635
|
+
green: { dark: '3ADE80', light: '22A55C' },
|
|
636
|
+
yellow: { dark: 'FFD166', light: 'D97706' },
|
|
637
|
+
blue: { dark: '4ECDC4', light: '0D9488' },
|
|
638
|
+
pink: { dark: 'FF6B9D', light: 'DB2777' },
|
|
639
|
+
};
|
|
640
|
+
const accent = toneAccents[tone][isLight04 ? 'light' : 'dark'];
|
|
641
|
+
const accent2 = isLight04 ? 'DB2777' : 'FF6B9D';
|
|
642
|
+
colors = {
|
|
643
|
+
...snapshotColorBase,
|
|
644
|
+
accent,
|
|
645
|
+
secondary: accent2,
|
|
646
|
+
};
|
|
647
|
+
chartColors = snapshotColors.chartColors;
|
|
648
|
+
}
|
|
649
|
+
else if (themeId === 'theme05') {
|
|
650
|
+
const isLight05 = appearance === 'light';
|
|
651
|
+
const scheme = ['coral', 'amber', 'teal', 'indigo', 'violet'].includes(colorScheme) ? colorScheme : 'coral';
|
|
652
|
+
const schemeAccents = {
|
|
653
|
+
coral: { dark: 'E85D4E', light: 'C73E2F', accent2: { dark: 'F5A623', light: 'C47B08' } },
|
|
654
|
+
amber: { dark: 'F5A623', light: 'C47B08', accent2: { dark: 'E85D4E', light: 'C73E2F' } },
|
|
655
|
+
teal: { dark: '0FA3B1', light: '0B7A85', accent2: { dark: '4A58D9', light: '3A46B0' } },
|
|
656
|
+
indigo: { dark: '4A58D9', light: '3A46B0', accent2: { dark: '0FA3B1', light: '0B7A85' } },
|
|
657
|
+
violet: { dark: '7C3AED', light: '5B25C1', accent2: { dark: 'F5A623', light: 'C47B08' } },
|
|
658
|
+
};
|
|
659
|
+
const accent = schemeAccents[scheme][isLight05 ? 'light' : 'dark'];
|
|
660
|
+
const accent2 = schemeAccents[scheme].accent2[isLight05 ? 'light' : 'dark'];
|
|
661
|
+
colors = {
|
|
662
|
+
...snapshotColorBase,
|
|
663
|
+
accent,
|
|
664
|
+
secondary: accent2,
|
|
665
|
+
};
|
|
666
|
+
chartColors = snapshotColors.chartColors;
|
|
667
|
+
}
|
|
668
|
+
else if (themeId === 'theme06') {
|
|
669
|
+
const isLight06 = appearance === 'light';
|
|
670
|
+
const scheme = ['volt', 'magma', 'nebula', 'nova'].includes(colorScheme) ? colorScheme : 'volt';
|
|
671
|
+
const schemeAccents = {
|
|
672
|
+
volt: { dark: 'B6FF2B', light: '7BC800', accent2: { dark: '00E5B0', light: '009E7A' }, accentCool: { dark: '2E9FFF', light: '1A6FD6' } },
|
|
673
|
+
magma: { dark: 'FF6B45', light: 'D94E2B', accent2: { dark: 'FFC700', light: 'C78F00' }, accentCool: { dark: '00C2FF', light: '008FBF' } },
|
|
674
|
+
nebula: { dark: '2E9FFF', light: '1A6FD6', accent2: { dark: 'A855F7', light: '7C2ED3' }, accentCool: { dark: '22D3EE', light: '0B9FB8' } },
|
|
675
|
+
nova: { dark: 'FFD100', light: 'C78F00', accent2: { dark: 'FF6B45', light: 'D94E2B' }, accentCool: { dark: '00E5B0', light: '009E7A' } },
|
|
676
|
+
};
|
|
677
|
+
const accent = schemeAccents[scheme][isLight06 ? 'light' : 'dark'];
|
|
678
|
+
const accent2 = schemeAccents[scheme].accent2[isLight06 ? 'light' : 'dark'];
|
|
679
|
+
const accentCool = schemeAccents[scheme].accentCool[isLight06 ? 'light' : 'dark'];
|
|
680
|
+
colors = {
|
|
681
|
+
...snapshotColorBase,
|
|
682
|
+
accent,
|
|
683
|
+
secondary: accent2,
|
|
684
|
+
accent2,
|
|
685
|
+
accentCool,
|
|
686
|
+
};
|
|
687
|
+
chartColors = snapshotColors.chartColors;
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
// Simple themes (theme01) - just use snapshot colors directly
|
|
691
|
+
colors = snapshotColorBase;
|
|
692
|
+
chartColors = snapshotColors.chartColors;
|
|
693
|
+
}
|
|
694
|
+
resolved = {
|
|
695
|
+
colors,
|
|
696
|
+
fonts: snapshotColors.fonts,
|
|
697
|
+
chartColors,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// Fallback to the original logic
|
|
702
|
+
resolved = {
|
|
703
|
+
colors,
|
|
704
|
+
fonts: config.fonts,
|
|
705
|
+
chartColors,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
// 中文场景优先使用 Noto 中文字体
|
|
53
709
|
if (language === 'zh') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
body: 'Noto Sans SC',
|
|
59
|
-
mono: 'Noto Sans SC',
|
|
60
|
-
},
|
|
710
|
+
resolved.fonts = {
|
|
711
|
+
heading: 'Noto Sans SC',
|
|
712
|
+
body: 'Noto Sans SC',
|
|
713
|
+
mono: 'Noto Sans SC',
|
|
61
714
|
};
|
|
62
715
|
}
|
|
63
|
-
return
|
|
716
|
+
return resolved;
|
|
717
|
+
}
|
|
718
|
+
export let COLORS = THEME_CONFIGS.theme01.colorsLight;
|
|
719
|
+
export let FONTS = THEME_CONFIGS.theme01.fonts;
|
|
720
|
+
export let CHART_COLORS = THEME_CONFIGS.theme01.chartColors;
|
|
721
|
+
// Store current gradient for background functions
|
|
722
|
+
let CURRENT_GRADIENT = null;
|
|
723
|
+
/** 调试辅助:拦截 slide 的绘制调用,检测空颜色 */
|
|
724
|
+
function wrapSlideForColorDebug(slide, index) {
|
|
725
|
+
const methodsToWrap = ['addShape', 'addText', 'addChart', 'addImage'];
|
|
726
|
+
return new Proxy(slide, {
|
|
727
|
+
get(target, prop) {
|
|
728
|
+
const value = target[prop];
|
|
729
|
+
if (methodsToWrap.includes(prop) && typeof value === 'function') {
|
|
730
|
+
return function (...args) {
|
|
731
|
+
// args[0] is shapeType/text/chartType; args[1] is options for shape/chart/image, text options is args[1] for addText
|
|
732
|
+
const optionsArg = prop === 'addText' ? args[1] : args[1];
|
|
733
|
+
if (optionsArg && typeof optionsArg === 'object') {
|
|
734
|
+
findEmptyColor(optionsArg, `${String(prop)}(slide ${index + 1})`);
|
|
735
|
+
}
|
|
736
|
+
return value.apply(this === undefined ? target : this, args);
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
return value;
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
function findEmptyColor(obj, path) {
|
|
744
|
+
if (!obj || typeof obj !== 'object')
|
|
745
|
+
return;
|
|
746
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
747
|
+
const currentPath = `${path}.${key}`;
|
|
748
|
+
if ((key === 'color' || key === 'fill' || key === 'line' || key === 'chartColors' || key === 'dataLabelColor') && (val === '' || val === undefined || val === null)) {
|
|
749
|
+
console.warn(`[PPTX color debug] 空颜色 @ ${currentPath}`, val, new Error().stack?.split('\n').slice(2, 6).join('\n'));
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (key === 'color' && typeof val === 'string' && val.startsWith('var(--')) {
|
|
753
|
+
console.warn(`[PPTX color debug] CSS 变量颜色未解析 @ ${currentPath}`, val, new Error().stack?.split('\n').slice(2, 6).join('\n'));
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (typeof val === 'object') {
|
|
757
|
+
findEmptyColor(val, currentPath);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
64
760
|
}
|
|
65
|
-
let COLORS = THEME_CONFIGS.base.colors;
|
|
66
|
-
let FONTS = THEME_CONFIGS.base.fonts;
|
|
67
|
-
let CHART_COLORS = THEME_CONFIGS.base.chartColors;
|
|
68
761
|
export async function exportDeckToPptx(goal, options) {
|
|
69
762
|
goal = normalizeDeckGoal(goal);
|
|
70
763
|
const { outFile, title = goal.title, subject, author } = options;
|
|
71
|
-
const theme = resolveThemeConfig(goal.theme, goal.language);
|
|
764
|
+
const theme = resolveThemeConfig(goal.theme, goal.language, goal.colorScheme, goal.appearance);
|
|
72
765
|
COLORS = theme.colors;
|
|
73
766
|
FONTS = theme.fonts;
|
|
74
767
|
CHART_COLORS = theme.chartColors;
|
|
768
|
+
// 注入 theme08 专属渲染器所需的状态(深浅模式随 appearance 切换)
|
|
769
|
+
if (goal.theme === 'theme08') {
|
|
770
|
+
configureTheme08({
|
|
771
|
+
colors: COLORS,
|
|
772
|
+
fonts: FONTS,
|
|
773
|
+
chartColors: CHART_COLORS,
|
|
774
|
+
muted: goal.appearance === 'muted' || goal.appearance === 'light',
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// 注入 theme09 专属渲染器所需的状态(appearance 决定专色浓度)
|
|
778
|
+
if (goal.theme === 'theme09') {
|
|
779
|
+
configureTheme09({
|
|
780
|
+
appearance: goal.appearance === 'muted' || goal.appearance === 'light' ? 'muted' : 'primary',
|
|
781
|
+
fonts: FONTS,
|
|
782
|
+
chartColors: CHART_COLORS,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
// 注入 theme10 专属渲染器所需的状态(appearance 决定强调色浓度)
|
|
786
|
+
if (goal.theme === 'theme10') {
|
|
787
|
+
configureTheme10({
|
|
788
|
+
appearance: goal.appearance === 'muted' || goal.appearance === 'light' ? 'muted' : 'primary',
|
|
789
|
+
fonts: FONTS,
|
|
790
|
+
chartColors: CHART_COLORS,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
// Load gradient from snapshot
|
|
794
|
+
CURRENT_GRADIENT = getSnapshotGradient(goal.theme ?? 'theme01');
|
|
75
795
|
const pptx = new PptxGenJS();
|
|
76
796
|
pptx.layout = 'LAYOUT_16x9';
|
|
77
797
|
pptx.title = title;
|
|
@@ -79,415 +799,1245 @@ export async function exportDeckToPptx(goal, options) {
|
|
|
79
799
|
pptx.subject = subject;
|
|
80
800
|
if (author)
|
|
81
801
|
pptx.author = author;
|
|
82
|
-
for (const slide of goal.slides) {
|
|
83
|
-
|
|
802
|
+
for (const [slideIndex, slide] of goal.slides.entries()) {
|
|
803
|
+
let pptxSlide = pptx.addSlide();
|
|
804
|
+
if (process.env.DEBUG_PPTX_COLOR === '1') {
|
|
805
|
+
pptxSlide = wrapSlideForColorDebug(pptxSlide, slideIndex);
|
|
806
|
+
}
|
|
84
807
|
pptxSlide.background = { color: COLORS.surface };
|
|
85
|
-
|
|
808
|
+
// theme07 冷白调研风:纯白底 + 亮色弥散光晕(优先,覆盖快照渐变)
|
|
809
|
+
if (goal.theme === 'theme07') {
|
|
810
|
+
addTheme07Background(pptxSlide);
|
|
811
|
+
}
|
|
812
|
+
else if (CURRENT_GRADIENT) {
|
|
813
|
+
addGradientBackground(pptxSlide, CURRENT_GRADIENT);
|
|
814
|
+
}
|
|
815
|
+
else if (goal.theme === 'theme02' || goal.theme === 'theme03') {
|
|
816
|
+
addTheme02Background(pptxSlide);
|
|
817
|
+
}
|
|
818
|
+
else if (goal.theme === 'theme04') {
|
|
819
|
+
addTheme04Background(pptxSlide);
|
|
820
|
+
}
|
|
821
|
+
else if (goal.theme === 'theme05') {
|
|
822
|
+
addTheme05Background(pptxSlide);
|
|
823
|
+
}
|
|
824
|
+
else if (goal.theme === 'theme06') {
|
|
825
|
+
addTheme06Background(pptxSlide);
|
|
826
|
+
}
|
|
827
|
+
renderSlideToPptx(pptxSlide, slide);
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
await pptx.writeFile({ fileName: outFile });
|
|
831
|
+
}
|
|
832
|
+
finally {
|
|
833
|
+
cleanupTempImages();
|
|
834
|
+
cleanupTheme01TempImages();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/** 按 layoutId 索引的 PPTX 渲染器注册表。 */
|
|
838
|
+
const pptxRenderersByLayout = new Map();
|
|
839
|
+
function registerPptxLayoutRenderer(layoutId, renderFn) {
|
|
840
|
+
pptxRenderersByLayout.set(layoutId, renderFn);
|
|
841
|
+
}
|
|
842
|
+
function resolvePptxRenderer(slide) {
|
|
843
|
+
return pptxRenderersByLayout.get(slide.layout);
|
|
844
|
+
}
|
|
845
|
+
function renderSlideToPptx(pptxSlide, slide) {
|
|
846
|
+
// theme08 拥有专属 PPTX 渲染器,优先按 layoutId 精确分发,避免被下方 theme06 的 includes() 兜底误路由。
|
|
847
|
+
if (slide.layout.startsWith('theme08_')) {
|
|
848
|
+
const t08 = resolvePptxRenderer(slide);
|
|
849
|
+
if (t08) {
|
|
850
|
+
t08(pptxSlide, slide.props);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
// theme09 墨韵专色:双基底(纸/墨)按版式预分配,专属渲染器内部按 layout 取基底。
|
|
855
|
+
if (slide.layout.startsWith('theme09_')) {
|
|
856
|
+
const t09 = resolvePptxRenderer(slide);
|
|
857
|
+
if (t09) {
|
|
858
|
+
t09(pptxSlide, slide.props);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// theme10 金色指数:墨黑金线底,专属渲染器内部按 layout 取强调色/基底。
|
|
863
|
+
if (slide.layout.startsWith('theme10_')) {
|
|
864
|
+
const t10 = resolvePptxRenderer(slide);
|
|
865
|
+
if (t10) {
|
|
866
|
+
t10(pptxSlide, slide.props);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
// theme01 浅色玻璃:双基底(light / tint)按版式预分配,专属渲染器内部按 layout 取基底。
|
|
871
|
+
if (slide.layout.startsWith('theme01_')) {
|
|
872
|
+
const t01 = resolvePptxRenderer(slide);
|
|
873
|
+
if (t01) {
|
|
874
|
+
t01(pptxSlide, slide.props);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// theme06 所有子页面统一支持可选背景图(已有专属图片处理的版式除外)
|
|
879
|
+
const theme06ExplicitImageLayouts = new Set([
|
|
880
|
+
'theme06_cover_v1',
|
|
881
|
+
'theme06_chapter_v1',
|
|
882
|
+
'theme06_closing_v1',
|
|
883
|
+
'theme06_chapter_image_v1',
|
|
884
|
+
]);
|
|
885
|
+
if (slide.layout.startsWith('theme06_') &&
|
|
886
|
+
slide.props?.imageUrl &&
|
|
887
|
+
!theme06ExplicitImageLayouts.has(slide.layout)) {
|
|
888
|
+
addImageMaybe(pptxSlide, slide.props.imageUrl, 0, 0, 10, 5.625);
|
|
889
|
+
}
|
|
890
|
+
// 检查是否有坐标配置 - 如果有,使用 v2 坐标系统
|
|
891
|
+
// 支持 theme06 专属版式坐标(带 _themeSpecific 标记)
|
|
892
|
+
const coords = getLayoutCoordinates(slide.layout);
|
|
893
|
+
const hasCoords = Object.keys(coords).length > 0;
|
|
894
|
+
const isTheme06Specific = coords._themeSpecific === true;
|
|
895
|
+
const layoutId = slide.layout;
|
|
896
|
+
// 仅对"标准 layout ID"或"theme06 专属版式"启用 v2 渲染
|
|
897
|
+
const standardLayoutPattern = /^theme\d+_(cover|content|metric|table_of_contents)_v\d+$/;
|
|
898
|
+
const isStandardLayout = standardLayoutPattern.test(layoutId);
|
|
899
|
+
const shouldUseV2 = (hasCoords && isStandardLayout) || (hasCoords && isTheme06Specific);
|
|
900
|
+
if (shouldUseV2) {
|
|
901
|
+
// 根据布局类型选择对应的 v2 渲染函数
|
|
902
|
+
if (layoutId.includes('cover_product') || layoutId.includes('cover_business') || layoutId.includes('cover_manufacturing')) {
|
|
903
|
+
renderTheme06CoverVariant(pptxSlide, slide.props, layoutId);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (layoutId.includes('cover')) {
|
|
907
|
+
// Theme06 cover layouts use their own renderer with different prop structure
|
|
908
|
+
if (layoutId.startsWith('theme06_')) {
|
|
909
|
+
renderTheme06Cover(pptxSlide, slide.props, layoutId);
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
renderCoverV2(pptxSlide, slide.props, layoutId);
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
else if (layoutId.includes('content_numbered')) {
|
|
917
|
+
renderTheme06ContentNumbered(pptxSlide, slide.props, layoutId);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
else if (layoutId.includes('content')) {
|
|
921
|
+
renderContentWithCoords(pptxSlide, slide.props, layoutId);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
else if (layoutId.includes('metric_showcase')) {
|
|
925
|
+
renderTheme06MetricShowcase(pptxSlide, slide.props, layoutId);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
else if (layoutId.includes('metric')) {
|
|
929
|
+
renderMetricWithCoords(pptxSlide, slide.props, layoutId);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
else if (layoutId.includes('table_of_contents')) {
|
|
933
|
+
renderTheme06Toc(pptxSlide, slide.props, layoutId);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
else if (layoutId.includes('toc')) {
|
|
937
|
+
renderTheme06Toc(pptxSlide, slide.props, layoutId);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
else if (layoutId.includes('big_number')) {
|
|
941
|
+
renderTheme06BigNumber(pptxSlide, slide.props, layoutId);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
else if (layoutId.includes('chapter_numbered') || layoutId.includes('chapter_split') || layoutId.includes('chapter_focus') || layoutId.includes('chapter_image') || layoutId.includes('chapter_minimal')) {
|
|
945
|
+
renderTheme06Chapter(pptxSlide, slide.props, layoutId);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
else if (layoutId.includes('chapter')) {
|
|
949
|
+
renderTheme06Chapter(pptxSlide, slide.props, layoutId);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
else if (layoutId.includes('closing')) {
|
|
953
|
+
renderTheme06Closing(pptxSlide, slide.props, layoutId);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
else if (layoutId.includes('timeline')) {
|
|
957
|
+
renderTheme06Timeline(pptxSlide, slide.props, layoutId);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
else if (layoutId.includes('milestone')) {
|
|
961
|
+
renderTheme06Milestone(pptxSlide, slide.props, layoutId);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
else if (layoutId.includes('risk_matrix')) {
|
|
965
|
+
renderTheme06RiskMatrix(pptxSlide, slide.props, layoutId);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
else if (layoutId.includes('summary')) {
|
|
969
|
+
renderTheme06Summary(pptxSlide, slide.props, layoutId);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
else if (layoutId.includes('quote')) {
|
|
973
|
+
renderTheme06Quote(pptxSlide, slide.props, layoutId);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
else if (layoutId.includes('process')) {
|
|
977
|
+
renderTheme06Process(pptxSlide, slide.props, layoutId);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
else if (layoutId.includes('method')) {
|
|
981
|
+
renderTheme06Process(pptxSlide, slide.props, layoutId);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
else if (layoutId.includes('outlook')) {
|
|
985
|
+
renderTheme06ListBased(pptxSlide, slide.props, layoutId);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
else if (layoutId.includes('recap')) {
|
|
989
|
+
renderTheme06ListBased(pptxSlide, slide.props, layoutId);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
else if (layoutId.includes('statement')) {
|
|
993
|
+
renderTheme06Statement(pptxSlide, slide.props, layoutId);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
else if (layoutId.includes('sources')) {
|
|
997
|
+
renderTheme06Sources(pptxSlide, slide.props, layoutId);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
else if (layoutId.includes('matrix')) {
|
|
1001
|
+
renderTheme06Matrix(pptxSlide, slide.props, layoutId);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
else if (layoutId.includes('rank')) {
|
|
1005
|
+
renderTheme06Rank(pptxSlide, slide.props, layoutId);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
else if (layoutId.includes('comparison')) {
|
|
1009
|
+
renderTheme06Comparison(pptxSlide, slide.props, layoutId);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
else if (layoutId.includes('bento')) {
|
|
1013
|
+
renderTheme06Bento(pptxSlide, slide.props, layoutId);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
else if (layoutId.includes('triad')) {
|
|
1017
|
+
renderTheme06Triad(pptxSlide, slide.props, layoutId);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
else if (layoutId.includes('tech_landscape')) {
|
|
1021
|
+
renderTheme06TechLandscape(pptxSlide, slide.props, layoutId);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
else if (layoutId.includes('chain_flow')) {
|
|
1025
|
+
renderTheme06ChainFlow(pptxSlide, slide.props, layoutId);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
else if (layoutId.includes('deal_map')) {
|
|
1029
|
+
renderTheme06DealMap(pptxSlide, slide.props, layoutId);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
else if (layoutId.includes('sector_spotlight')) {
|
|
1033
|
+
renderTheme06SectorSpotlight(pptxSlide, slide.props, layoutId);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
else if (layoutId.includes('company_profile')) {
|
|
1037
|
+
renderTheme06CompanyProfile(pptxSlide, slide.props, layoutId);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
else if (layoutId.includes('company_spotlight')) {
|
|
1041
|
+
renderTheme06CompanySpotlight(pptxSlide, slide.props, layoutId);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
else if (layoutId.includes('case')) {
|
|
1045
|
+
renderTheme06Case(pptxSlide, slide.props, layoutId);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
// 否则使用原有渲染器
|
|
1050
|
+
const renderer = resolvePptxRenderer(slide);
|
|
1051
|
+
if (renderer) {
|
|
1052
|
+
renderer(pptxSlide, slide.props);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
pptxSlide.addText(`Unknown layout: ${slide.layout}`, {
|
|
1056
|
+
x: 0.5, y: 3.5, w: 9, h: 1,
|
|
1057
|
+
fontSize: 18, color: 'EF4444', align: 'center',
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
// ---- Layout Coordinate System (v2) ---------------------------------------
|
|
1061
|
+
/**
|
|
1062
|
+
* 通用元素渲染函数 - 从坐标配置读取位置和样式
|
|
1063
|
+
* 这是解决版式错位问题的核心:所有布局坐标都从配置文件读取,
|
|
1064
|
+
* 而不是在渲染函数中硬编码。
|
|
1065
|
+
*/
|
|
1066
|
+
export function renderElementFromConfig(slide, text, config, options) {
|
|
1067
|
+
if (!text)
|
|
1068
|
+
return false;
|
|
1069
|
+
const textOptions = {
|
|
1070
|
+
x: config.x,
|
|
1071
|
+
y: config.y,
|
|
1072
|
+
w: config.w || 8,
|
|
1073
|
+
h: config.h || 0.4,
|
|
1074
|
+
fontSize: config.fontSize || 18,
|
|
1075
|
+
align: config.align || 'left',
|
|
1076
|
+
bold: config.bold || false,
|
|
1077
|
+
color: options?.color || COLORS.primary,
|
|
1078
|
+
fontFace: options?.fontFace || FONTS.body,
|
|
1079
|
+
};
|
|
1080
|
+
slide.addText(text, textOptions);
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* 使用坐标配置渲染封面布局
|
|
1085
|
+
* 这是新系统的示例实现
|
|
1086
|
+
*/
|
|
1087
|
+
function renderCoverV2(slide, props, layoutId) {
|
|
1088
|
+
const coords = getLayoutCoordinates(layoutId);
|
|
1089
|
+
// 背景图
|
|
1090
|
+
if (props.image) {
|
|
1091
|
+
const imgConfig = coords.image || { x: 0, y: 0, w: 10, h: 5.625 };
|
|
1092
|
+
addImageMaybe(slide, props.image, imgConfig.x, imgConfig.y, imgConfig.w, imgConfig.h);
|
|
1093
|
+
}
|
|
1094
|
+
// Kicker
|
|
1095
|
+
if (props.kicker && coords.kicker) {
|
|
1096
|
+
renderElementFromConfig(slide, props.kicker, coords.kicker, {
|
|
1097
|
+
color: COLORS.accent,
|
|
1098
|
+
fontFace: FONTS.mono,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
// Title
|
|
1102
|
+
if (coords.title) {
|
|
1103
|
+
renderElementFromConfig(slide, props.title, coords.title, {
|
|
1104
|
+
color: COLORS.primary,
|
|
1105
|
+
fontFace: FONTS.heading,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
// Subtitle
|
|
1109
|
+
if (props.subtitle && coords.subtitle) {
|
|
1110
|
+
renderElementFromConfig(slide, props.subtitle, coords.subtitle, {
|
|
1111
|
+
color: COLORS.secondary,
|
|
1112
|
+
fontFace: FONTS.body,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
// Date
|
|
1116
|
+
if (props.date && coords.date) {
|
|
1117
|
+
renderElementFromConfig(slide, props.date, coords.date, {
|
|
1118
|
+
color: COLORS.secondary,
|
|
1119
|
+
fontFace: FONTS.mono,
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
/** 使用坐标配置渲染内容布局 (v2 坐标系统) */
|
|
1124
|
+
function renderContentWithCoords(slide, props, layoutId) {
|
|
1125
|
+
const coords = getLayoutCoordinates(layoutId);
|
|
1126
|
+
// Kicker
|
|
1127
|
+
if (props.kicker && coords.kicker) {
|
|
1128
|
+
renderElementFromConfig(slide, props.kicker, coords.kicker, {
|
|
1129
|
+
color: COLORS.accent,
|
|
1130
|
+
fontFace: FONTS.mono,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
// Title
|
|
1134
|
+
if (coords.title) {
|
|
1135
|
+
renderElementFromConfig(slide, props.title, coords.title, {
|
|
1136
|
+
color: COLORS.primary,
|
|
1137
|
+
fontFace: FONTS.heading,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
// Body text
|
|
1141
|
+
if (props.body && coords.body) {
|
|
1142
|
+
renderElementFromConfig(slide, props.body, coords.body, {
|
|
1143
|
+
color: COLORS.primary,
|
|
1144
|
+
fontFace: FONTS.body,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
// Bullets
|
|
1148
|
+
if (props.bullets && props.bullets.length > 0 && coords.bulletItem) {
|
|
1149
|
+
const bulletConfig = coords.bulletItem;
|
|
1150
|
+
let y = (coords.body?.y || 2.5) + (coords.body?.h || 2.5) + 0.3;
|
|
1151
|
+
props.bullets.forEach((bullet) => {
|
|
1152
|
+
// Bullet number/marker
|
|
1153
|
+
slide.addText('•', {
|
|
1154
|
+
x: bulletConfig.indentX,
|
|
1155
|
+
y,
|
|
1156
|
+
w: 0.3,
|
|
1157
|
+
h: bulletConfig.height,
|
|
1158
|
+
fontSize: bulletConfig.fontSize,
|
|
1159
|
+
color: COLORS.accent,
|
|
1160
|
+
align: 'left',
|
|
1161
|
+
});
|
|
1162
|
+
// Bullet text
|
|
1163
|
+
slide.addText(bullet, {
|
|
1164
|
+
x: bulletConfig.textStartX,
|
|
1165
|
+
y,
|
|
1166
|
+
w: 8,
|
|
1167
|
+
h: bulletConfig.height,
|
|
1168
|
+
fontSize: bulletConfig.fontSize,
|
|
1169
|
+
color: COLORS.primary,
|
|
1170
|
+
align: 'left',
|
|
1171
|
+
fontFace: FONTS.body,
|
|
1172
|
+
});
|
|
1173
|
+
y += bulletConfig.height + 0.1;
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
/** 使用坐标配置渲染指标布局 (v2 坐标系统) */
|
|
1178
|
+
function renderMetricWithCoords(slide, props, layoutId) {
|
|
1179
|
+
const coords = getLayoutCoordinates(layoutId);
|
|
1180
|
+
// Label/Kicker
|
|
1181
|
+
if (props.kicker && coords.label) {
|
|
1182
|
+
renderElementFromConfig(slide, props.kicker, coords.label, {
|
|
1183
|
+
color: COLORS.accent,
|
|
1184
|
+
fontFace: FONTS.mono,
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
// Title
|
|
1188
|
+
if (props.title && coords.title) {
|
|
1189
|
+
renderElementFromConfig(slide, props.title, coords.title, {
|
|
1190
|
+
color: COLORS.primary,
|
|
1191
|
+
fontFace: FONTS.heading,
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
// Main value
|
|
1195
|
+
if (props.value && coords.value) {
|
|
1196
|
+
const valueText = props.unit ? `${props.value}${props.unit}` : props.value;
|
|
1197
|
+
renderElementFromConfig(slide, valueText, coords.value, {
|
|
1198
|
+
color: COLORS.accent,
|
|
1199
|
+
fontFace: FONTS.heading,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
// Description
|
|
1203
|
+
if (props.description && coords.description) {
|
|
1204
|
+
renderElementFromConfig(slide, props.description, coords.description, {
|
|
1205
|
+
color: COLORS.secondary,
|
|
1206
|
+
fontFace: FONTS.body,
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// ---- Shared helpers --------------------------------------------------------
|
|
1211
|
+
export function addKicker(slide, kicker, x = 0.8, y = 0.8) {
|
|
1212
|
+
if (!kicker)
|
|
1213
|
+
return;
|
|
1214
|
+
slide.addText(kicker, {
|
|
1215
|
+
x, y, w: 8.4, h: 0.4,
|
|
1216
|
+
fontSize: 14, color: COLORS.accent, align: 'left',
|
|
1217
|
+
fontFace: FONTS.mono,
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
/** 通用渐变背景函数:从快照配置中读取渐变参数 */
|
|
1221
|
+
function addGradientBackground(slide, gradient) {
|
|
1222
|
+
slide.addShape('rect', {
|
|
1223
|
+
x: 0, y: 0, w: 10, h: 5.625,
|
|
1224
|
+
fill: {
|
|
1225
|
+
color: COLORS.surface,
|
|
1226
|
+
gradient: {
|
|
1227
|
+
type: 'linear',
|
|
1228
|
+
angle: gradient.angle,
|
|
1229
|
+
stops: gradient.stops,
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
/** 为 theme02 幻灯片添加深色霓虹渐变背景。 */
|
|
1235
|
+
function addTheme02Background(slide) {
|
|
1236
|
+
slide.addShape('rect', {
|
|
1237
|
+
x: 0, y: 0, w: 10, h: 5.625,
|
|
1238
|
+
fill: {
|
|
1239
|
+
color: COLORS.surface,
|
|
1240
|
+
gradient: {
|
|
1241
|
+
type: 'linear',
|
|
1242
|
+
angle: 160,
|
|
1243
|
+
stops: [
|
|
1244
|
+
{ position: 0, color: COLORS.white },
|
|
1245
|
+
{ position: 1, color: COLORS.light },
|
|
1246
|
+
],
|
|
1247
|
+
},
|
|
1248
|
+
},
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
/** 为 theme04 幻灯片添加玻璃糖果渐变背景。 */
|
|
1252
|
+
export function addTheme04Background(slide) {
|
|
1253
|
+
slide.addShape('rect', {
|
|
1254
|
+
x: 0, y: 0, w: 10, h: 5.625,
|
|
1255
|
+
fill: {
|
|
1256
|
+
color: COLORS.surface,
|
|
1257
|
+
gradient: {
|
|
1258
|
+
type: 'linear',
|
|
1259
|
+
angle: 155,
|
|
1260
|
+
stops: [
|
|
1261
|
+
{ position: 0, color: COLORS.white },
|
|
1262
|
+
{ position: 1, color: COLORS.light },
|
|
1263
|
+
],
|
|
1264
|
+
},
|
|
1265
|
+
},
|
|
1266
|
+
});
|
|
1267
|
+
// 右上角糖果色弥散光晕
|
|
1268
|
+
slide.addShape('ellipse', {
|
|
1269
|
+
x: 7.2, y: -0.8, w: 3.8, h: 2.6,
|
|
1270
|
+
fill: {
|
|
1271
|
+
color: COLORS.accent,
|
|
1272
|
+
transparency: 88,
|
|
1273
|
+
},
|
|
1274
|
+
});
|
|
1275
|
+
// 左下角冷色光晕
|
|
1276
|
+
slide.addShape('ellipse', {
|
|
1277
|
+
x: -1.2, y: 4.0, w: 3.4, h: 2.2,
|
|
1278
|
+
fill: {
|
|
1279
|
+
color: '4ECDC4',
|
|
1280
|
+
transparency: 90,
|
|
1281
|
+
},
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
/** 为 theme05 幻灯片添加纸白/深色渐变背景与光谱色带。 */
|
|
1285
|
+
function addTheme05Background(slide) {
|
|
1286
|
+
slide.addShape('rect', {
|
|
1287
|
+
x: 0, y: 0, w: 10, h: 5.625,
|
|
1288
|
+
fill: {
|
|
1289
|
+
color: COLORS.surface,
|
|
1290
|
+
gradient: {
|
|
1291
|
+
type: 'linear',
|
|
1292
|
+
angle: 170,
|
|
1293
|
+
stops: [
|
|
1294
|
+
{ position: 0, color: COLORS.white },
|
|
1295
|
+
{ position: 1, color: COLORS.light },
|
|
1296
|
+
],
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
/** 在 theme05 幻灯片底部绘制光谱色带。 */
|
|
1302
|
+
export function addTheme05SpectrumBar(slide) {
|
|
1303
|
+
const colors = ['E85D4E', 'F5A623', '0FA3B1', '4A58D9', '7C3AED'];
|
|
1304
|
+
const barY = 5.18;
|
|
1305
|
+
const barH = 0.06;
|
|
1306
|
+
const segmentW = 10 / colors.length;
|
|
1307
|
+
colors.forEach((color, idx) => {
|
|
1308
|
+
slide.addShape('rect', {
|
|
1309
|
+
x: idx * segmentW, y: barY, w: segmentW, h: barH,
|
|
1310
|
+
fill: { color },
|
|
1311
|
+
});
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
/** 为 theme06 幻灯片添加深色图谱渐变背景。 */
|
|
1315
|
+
function addTheme06Background(slide) {
|
|
1316
|
+
slide.addShape('rect', {
|
|
1317
|
+
x: 0, y: 0, w: 10, h: 5.625,
|
|
1318
|
+
fill: {
|
|
1319
|
+
color: COLORS.surface,
|
|
1320
|
+
gradient: {
|
|
1321
|
+
type: 'linear',
|
|
1322
|
+
angle: 160,
|
|
1323
|
+
stops: [
|
|
1324
|
+
{ position: 0, color: COLORS.white },
|
|
1325
|
+
{ position: 1, color: COLORS.light },
|
|
1326
|
+
],
|
|
1327
|
+
},
|
|
1328
|
+
},
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
/** theme07 冷白调研风:纯白底 + 亮色弥散光晕背景。 */
|
|
1332
|
+
function addTheme07Background(slide) {
|
|
1333
|
+
const W = 10;
|
|
1334
|
+
const H = 5.625;
|
|
1335
|
+
// 纯白底(覆盖默认 surface,确保无灰偏)
|
|
1336
|
+
slide.addShape('rect', { x: 0, y: 0, w: W, h: H, fill: { color: 'FFFFFF' }, line: { type: 'none' } });
|
|
1337
|
+
// 右上角蓝色弥散光晕
|
|
1338
|
+
slide.addShape('ellipse', {
|
|
1339
|
+
x: 7.2, y: -1.7, w: 5.0, h: 3.9,
|
|
1340
|
+
fill: { color: COLORS.accent, transparency: 82 },
|
|
1341
|
+
line: { type: 'none' },
|
|
1342
|
+
});
|
|
1343
|
+
// 右侧绿色弥散光晕
|
|
1344
|
+
slide.addShape('ellipse', {
|
|
1345
|
+
x: 6.4, y: 1.0, w: 3.6, h: 3.1,
|
|
1346
|
+
fill: { color: COLORS.accent2, transparency: 86 },
|
|
1347
|
+
line: { type: 'none' },
|
|
1348
|
+
});
|
|
1349
|
+
// 暖橙色小光晕
|
|
1350
|
+
slide.addShape('ellipse', {
|
|
1351
|
+
x: 5.4, y: 2.6, w: 2.9, h: 2.5,
|
|
1352
|
+
fill: { color: COLORS.accentCool, transparency: 88 },
|
|
1353
|
+
line: { type: 'none' },
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
/** 在 theme06 幻灯片底部绘制霓虹装饰线。 */
|
|
1357
|
+
export function addTheme06GlowLine(slide) {
|
|
1358
|
+
const barY = 5.18;
|
|
1359
|
+
const barH = 0.04;
|
|
1360
|
+
const stops = [
|
|
1361
|
+
{ position: 0, color: COLORS.white },
|
|
1362
|
+
{ position: 0.35, color: COLORS.accent },
|
|
1363
|
+
{ position: 0.65, color: COLORS.secondary },
|
|
1364
|
+
{ position: 1, color: COLORS.white },
|
|
1365
|
+
];
|
|
1366
|
+
slide.addShape('rect', {
|
|
1367
|
+
x: 0, y: barY, w: 10, h: barH,
|
|
1368
|
+
fill: {
|
|
1369
|
+
color: COLORS.accent,
|
|
1370
|
+
gradient: {
|
|
1371
|
+
type: 'linear',
|
|
1372
|
+
angle: 90,
|
|
1373
|
+
stops,
|
|
1374
|
+
},
|
|
1375
|
+
},
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
function splitBilingual(text) {
|
|
1379
|
+
if (!text)
|
|
1380
|
+
return {};
|
|
1381
|
+
const parts = text.split(' / ');
|
|
1382
|
+
if (parts.length >= 2)
|
|
1383
|
+
return { cn: parts[0], en: parts.slice(1).join(' / ') };
|
|
1384
|
+
return { cn: text };
|
|
1385
|
+
}
|
|
1386
|
+
export function addTheme06FooterBilingual(slide, footnote) {
|
|
1387
|
+
const parts = splitBilingual(footnote);
|
|
1388
|
+
if (parts.cn) {
|
|
1389
|
+
slide.addText(parts.cn, {
|
|
1390
|
+
x: 0.65, y: 5.08, w: 4.5, h: 0.22,
|
|
1391
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.heading, bold: true,
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
if (parts.en) {
|
|
1395
|
+
slide.addText(parts.en, {
|
|
1396
|
+
x: 5.15, y: 5.08, w: 4.2, h: 0.22,
|
|
1397
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, bold: true, align: 'right',
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
/** theme06 标准数据卡片:实色表面 + 细边框。 */
|
|
1402
|
+
export function addTheme06Card(slide, x, y, w, h, accent = false) {
|
|
1403
|
+
slide.addShape('roundRect', {
|
|
1404
|
+
x, y, w, h,
|
|
1405
|
+
fill: { color: accent ? COLORS.accent : COLORS.surfaceElevated },
|
|
1406
|
+
line: { color: accent ? COLORS.accent : COLORS.border, width: 1 },
|
|
1407
|
+
rectRadius: 0.08,
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
/** theme06 霓虹徽章(圆角胶囊)。 */
|
|
1411
|
+
export function addTheme06Badge(slide, x, y, w, h, text) {
|
|
1412
|
+
slide.addShape('roundRect', {
|
|
1413
|
+
x, y, w, h,
|
|
1414
|
+
fill: { color: COLORS.accent },
|
|
1415
|
+
rectRadius: h / 2,
|
|
1416
|
+
});
|
|
1417
|
+
slide.addText(text, {
|
|
1418
|
+
x, y, w, h,
|
|
1419
|
+
fontSize: 10, color: COLORS.white, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.mono,
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
/** theme06 进度条卡片。 */
|
|
1423
|
+
export function addTheme06ProgressCard(slide, x, y, w, h, label, value, progress) {
|
|
1424
|
+
addTheme06Card(slide, x, y, w, h);
|
|
1425
|
+
slide.addText(label, {
|
|
1426
|
+
x: x + 0.12, y: y + 0.1, w: w - 0.24, h: 0.24,
|
|
1427
|
+
fontSize: 11, color: COLORS.primary, bold: true, fontFace: FONTS.body,
|
|
1428
|
+
});
|
|
1429
|
+
slide.addText(value, {
|
|
1430
|
+
x: x + 0.12, y: y + 0.32, w: w - 0.24, h: 0.32,
|
|
1431
|
+
fontSize: 20, color: COLORS.accent, bold: true, align: 'right', fontFace: FONTS.heading,
|
|
1432
|
+
});
|
|
1433
|
+
const trackY = y + h - 0.18;
|
|
1434
|
+
slide.addShape('roundRect', {
|
|
1435
|
+
x: x + 0.12, y: trackY, w: w - 0.24, h: 0.06,
|
|
1436
|
+
fill: { color: COLORS.white },
|
|
1437
|
+
rectRadius: 0.03,
|
|
1438
|
+
});
|
|
1439
|
+
const fillW = Math.max(0, Math.min(1, progress)) * (w - 0.24);
|
|
1440
|
+
if (fillW > 0) {
|
|
1441
|
+
slide.addShape('roundRect', {
|
|
1442
|
+
x: x + 0.12, y: trackY, w: fillW, h: 0.06,
|
|
1443
|
+
fill: { color: COLORS.accent },
|
|
1444
|
+
rectRadius: 0.03,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
/** theme06 时间轴节点与连线。 */
|
|
1449
|
+
export function addTheme06TimelineNode(slide, cx, cy, label, date, focus = false) {
|
|
1450
|
+
slide.addShape('ellipse', {
|
|
1451
|
+
x: cx - 0.15, y: cy - 0.15, w: 0.3, h: 0.3,
|
|
1452
|
+
fill: { color: focus ? COLORS.accent : COLORS.surfaceElevated },
|
|
1453
|
+
line: { color: COLORS.accent, width: 2 },
|
|
1454
|
+
});
|
|
1455
|
+
slide.addText(date, {
|
|
1456
|
+
x: cx - 0.5, y: cy - 0.55, w: 1.0, h: 0.22,
|
|
1457
|
+
fontSize: 9, color: COLORS.accent, bold: true, align: 'center', fontFace: FONTS.mono,
|
|
1458
|
+
});
|
|
1459
|
+
slide.addText(label, {
|
|
1460
|
+
x: cx - 0.55, y: cy + 0.22, w: 1.1, h: 0.22,
|
|
1461
|
+
fontSize: 10, color: COLORS.primary, bold: true, align: 'center', fontFace: FONTS.body,
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
/** theme06 2×2 象限网格(虚线十字)。 */
|
|
1465
|
+
export function addTheme06QuadrantGrid(slide, x, y, w, h) {
|
|
1466
|
+
addTheme06Card(slide, x, y, w, h);
|
|
1467
|
+
const midX = x + w / 2;
|
|
1468
|
+
const midY = y + h / 2;
|
|
1469
|
+
const dash = 0.06;
|
|
1470
|
+
const gap = 0.04;
|
|
1471
|
+
for (let cy = y + 0.1; cy < y + h - 0.1; cy += dash + gap) {
|
|
1472
|
+
slide.addShape('rect', {
|
|
1473
|
+
x: midX - 0.01, y: cy, w: 0.02, h: Math.min(dash, y + h - 0.1 - cy),
|
|
1474
|
+
fill: { color: COLORS.border },
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
for (let cx = x + 0.1; cx < x + w - 0.1; cx += dash + gap) {
|
|
1478
|
+
slide.addShape('rect', {
|
|
1479
|
+
x: cx, y: midY - 0.01, w: Math.min(dash, x + w - 0.1 - cx), h: 0.02,
|
|
1480
|
+
fill: { color: COLORS.border },
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/** theme06 气泡节点。 */
|
|
1485
|
+
export function addTheme06Bubble(slide, x, y, size, label, color) {
|
|
1486
|
+
slide.addShape('ellipse', {
|
|
1487
|
+
x: x - size / 2, y: y - size / 2, w: size, h: size,
|
|
1488
|
+
fill: { color, transparency: 84 },
|
|
1489
|
+
line: { color, width: 1 },
|
|
1490
|
+
});
|
|
1491
|
+
slide.addText(label, {
|
|
1492
|
+
x: x - size / 2, y: y - 0.15, w: size, h: 0.3,
|
|
1493
|
+
fontSize: 8, color: COLORS.primary, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.body,
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
/** theme06 流程步骤卡片(带输入/输出标签)。 */
|
|
1497
|
+
export function addTheme06StepCard(slide, x, y, w, h, step, accent = false) {
|
|
1498
|
+
addTheme06Card(slide, x, y, w, h, accent);
|
|
1499
|
+
slide.addText(step.title ?? '', {
|
|
1500
|
+
x: x + 0.1, y: y + 0.1, w: w - 0.2, h: 0.26,
|
|
1501
|
+
fontSize: 11, color: accent ? COLORS.white : COLORS.primary, bold: true, align: 'center', fontFace: FONTS.body,
|
|
1502
|
+
});
|
|
1503
|
+
if (step.description) {
|
|
1504
|
+
slide.addText(step.description, {
|
|
1505
|
+
x: x + 0.1, y: y + 0.36, w: w - 0.2, h: 0.5,
|
|
1506
|
+
fontSize: 9, color: accent ? 'FFFFFF' : COLORS.secondary, align: 'center', valign: 'top', fontFace: FONTS.body,
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
if (step.input || step.output) {
|
|
1510
|
+
const ioText = [step.input ? `IN: ${step.input}` : '', step.output ? `OUT: ${step.output}` : ''].filter(Boolean).join(' / ');
|
|
1511
|
+
slide.addText(ioText, {
|
|
1512
|
+
x: x + 0.1, y: y + h - 0.32, w: w - 0.2, h: 0.22,
|
|
1513
|
+
fontSize: 8, color: accent ? 'FFFFFF' : COLORS.secondary, align: 'center', valign: 'middle', fontFace: FONTS.mono,
|
|
1514
|
+
});
|
|
86
1515
|
}
|
|
87
|
-
await pptx.writeFile({ fileName: outFile });
|
|
88
1516
|
}
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1517
|
+
/** theme05 标准数据卡片:实色表面 + 细边框。 */
|
|
1518
|
+
export function addTheme05Card(slide, x, y, w, h, accent = false) {
|
|
1519
|
+
slide.addShape('roundRect', {
|
|
1520
|
+
x, y, w, h,
|
|
1521
|
+
fill: { color: accent ? COLORS.accent : COLORS.surfaceElevated },
|
|
1522
|
+
line: { color: accent ? COLORS.accent : COLORS.border, width: 1 },
|
|
1523
|
+
rectRadius: 0.08,
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
/** theme05 scheme 颜色到固定 hex 的映射(与运行时 CSS 变量对应)。 */
|
|
1527
|
+
const THEME05_SCHEME_COLORS = {
|
|
1528
|
+
coral: 'E85D4E',
|
|
1529
|
+
amber: 'F5A623',
|
|
1530
|
+
teal: '0FA3B1',
|
|
1531
|
+
indigo: '4A58D9',
|
|
1532
|
+
violet: '7C3AED',
|
|
1533
|
+
};
|
|
1534
|
+
export function theme05SchemeColor(scheme) {
|
|
1535
|
+
return THEME05_SCHEME_COLORS[scheme || 'coral'] || COLORS.accent;
|
|
95
1536
|
}
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1537
|
+
/** theme05 小胶囊(用于高亮标签)。 */
|
|
1538
|
+
export function addTheme05Pill(slide, x, y, w, h, text, color) {
|
|
1539
|
+
slide.addShape('roundRect', {
|
|
1540
|
+
x, y, w, h,
|
|
1541
|
+
fill: { color },
|
|
1542
|
+
rectRadius: h / 2,
|
|
1543
|
+
});
|
|
1544
|
+
slide.addText(text, {
|
|
1545
|
+
x, y: y + 0.02, w, h: h - 0.02,
|
|
1546
|
+
fontSize: 11, color: 'FFFFFF', bold: true, align: 'center', valign: 'middle', fontFace: FONTS.body,
|
|
1547
|
+
});
|
|
102
1548
|
}
|
|
103
|
-
/**
|
|
104
|
-
export function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
1549
|
+
/** theme05 结论卡片(右侧)。 */
|
|
1550
|
+
export function addTheme05ConclusionCard(slide, x, y, w, h, conclusion) {
|
|
1551
|
+
slide.addShape('roundRect', {
|
|
1552
|
+
x, y, w, h,
|
|
1553
|
+
fill: { color: COLORS.surfaceElevated },
|
|
1554
|
+
line: { color: COLORS.border, width: 1 },
|
|
1555
|
+
rectRadius: 0.12,
|
|
1556
|
+
});
|
|
1557
|
+
let cursorY = y + 0.18;
|
|
1558
|
+
if (conclusion?.value) {
|
|
1559
|
+
slide.addText(conclusion.value, {
|
|
1560
|
+
x: x + 0.16, y: cursorY, w: w - 0.32, h: 0.55,
|
|
1561
|
+
fontSize: 30, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
|
|
1562
|
+
});
|
|
1563
|
+
cursorY += 0.55;
|
|
108
1564
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const normalized = normalizeUniversalTheme(theme);
|
|
116
|
-
const exact = byTheme?.get(normalized);
|
|
117
|
-
if (exact) {
|
|
118
|
-
return exact;
|
|
119
|
-
}
|
|
1565
|
+
if (conclusion?.label) {
|
|
1566
|
+
slide.addText(conclusion.label, {
|
|
1567
|
+
x: x + 0.16, y: cursorY, w: w - 0.32, h: 0.22,
|
|
1568
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body,
|
|
1569
|
+
});
|
|
1570
|
+
cursorY += 0.26;
|
|
120
1571
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (renderer) {
|
|
127
|
-
renderer(pptxSlide, slide.props);
|
|
128
|
-
return;
|
|
1572
|
+
if (conclusion?.description) {
|
|
1573
|
+
slide.addText(conclusion.description, {
|
|
1574
|
+
x: x + 0.16, y: cursorY, w: w - 0.32, h: h - (cursorY - y) - 0.2,
|
|
1575
|
+
fontSize: 10, color: COLORS.secondary, valign: 'top', fontFace: FONTS.body,
|
|
1576
|
+
});
|
|
129
1577
|
}
|
|
130
|
-
pptxSlide.addText(`Unknown layout: ${slide.layout}`, {
|
|
131
|
-
x: 0.5, y: 3.5, w: 9, h: 1,
|
|
132
|
-
fontSize: 18, color: 'EF4444', align: 'center',
|
|
133
|
-
});
|
|
134
1578
|
}
|
|
135
|
-
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1579
|
+
/** theme02 玻璃卡片:深色表面 + 细边框 + 柔和阴影。 */
|
|
1580
|
+
export function addTheme02Card(slide, x, y, w, h, radius = 0.15) {
|
|
1581
|
+
slide.addShape('roundRect', {
|
|
1582
|
+
x, y, w, h,
|
|
1583
|
+
fill: { color: COLORS.surfaceElevated },
|
|
1584
|
+
line: { color: COLORS.border, width: 1 },
|
|
1585
|
+
rectRadius: radius,
|
|
1586
|
+
shadow: {
|
|
1587
|
+
type: 'outer',
|
|
1588
|
+
blur: 8,
|
|
1589
|
+
offset: 4,
|
|
1590
|
+
angle: 45,
|
|
1591
|
+
color: '000000',
|
|
1592
|
+
opacity: 0.35,
|
|
1593
|
+
},
|
|
143
1594
|
});
|
|
144
1595
|
}
|
|
145
|
-
function addTitle(slide, title, x = 0.8, y = 1.3, w = 8.4, h = 0.9, fontSize = 44) {
|
|
1596
|
+
export function addTitle(slide, title, x = 0.8, y = 1.3, w = 8.4, h = 0.9, fontSize = 44) {
|
|
146
1597
|
slide.addText(title, {
|
|
147
1598
|
x, y, w, h,
|
|
148
1599
|
fontSize, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
149
1600
|
fontFace: FONTS.heading,
|
|
150
1601
|
});
|
|
151
1602
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1603
|
+
export function renderInsightPanel(slide, insight, x, y, w, h) {
|
|
1604
|
+
if (!insight || (!insight.value && !insight.label && !insight.description))
|
|
1605
|
+
return;
|
|
1606
|
+
slide.addShape('roundRect', {
|
|
1607
|
+
x, y, w, h,
|
|
1608
|
+
fill: { color: COLORS.surfaceElevated },
|
|
1609
|
+
line: { color: COLORS.border, width: 1 },
|
|
1610
|
+
rectRadius: 0.15,
|
|
1611
|
+
});
|
|
1612
|
+
let cursorY = y + 0.25;
|
|
1613
|
+
if (insight.value) {
|
|
1614
|
+
slide.addText(insight.value, {
|
|
1615
|
+
x: x + 0.2, y: cursorY, w: w - 0.4, h: 0.65,
|
|
1616
|
+
fontSize: 36, color: COLORS.accent, bold: true, align: 'left', valign: 'top',
|
|
1617
|
+
fontFace: FONTS.heading,
|
|
159
1618
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
1619
|
+
cursorY += 0.62;
|
|
1620
|
+
}
|
|
1621
|
+
if (insight.label) {
|
|
1622
|
+
slide.addText(insight.label, {
|
|
1623
|
+
x: x + 0.2, y: cursorY, w: w - 0.4, h: 0.3,
|
|
1624
|
+
fontSize: 11, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
1625
|
+
fontFace: FONTS.body,
|
|
1626
|
+
});
|
|
1627
|
+
cursorY += 0.38;
|
|
1628
|
+
}
|
|
1629
|
+
if (insight.description) {
|
|
1630
|
+
slide.addText(insight.description, {
|
|
1631
|
+
x: x + 0.2, y: cursorY, w: w - 0.4, h: h - (cursorY - y) - 0.25,
|
|
1632
|
+
fontSize: 12, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
163
1633
|
fontFace: FONTS.body,
|
|
164
1634
|
});
|
|
165
|
-
cy += 0.7;
|
|
166
1635
|
}
|
|
167
|
-
return cy;
|
|
168
1636
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1637
|
+
let exportTmpDir = null;
|
|
1638
|
+
const dataUriToTempPath = new Map();
|
|
1639
|
+
function getExportTmpDir() {
|
|
1640
|
+
if (!exportTmpDir) {
|
|
1641
|
+
try {
|
|
1642
|
+
exportTmpDir = mkdtempSync(path.join(tmpdir(), 'lemonppt-'));
|
|
1643
|
+
}
|
|
1644
|
+
catch {
|
|
1645
|
+
return null;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return exportTmpDir;
|
|
1649
|
+
}
|
|
1650
|
+
export function dataUriToTempFile(url) {
|
|
1651
|
+
const cached = dataUriToTempPath.get(url);
|
|
1652
|
+
if (cached)
|
|
1653
|
+
return cached;
|
|
1654
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
1655
|
+
if (!match)
|
|
1656
|
+
return undefined;
|
|
1657
|
+
const mime = match[1];
|
|
1658
|
+
const base64 = match[2];
|
|
1659
|
+
const ext = mime === 'image/svg+xml' ? 'svg' :
|
|
1660
|
+
mime === 'image/png' ? 'png' :
|
|
1661
|
+
mime === 'image/jpeg' ? 'jpg' :
|
|
1662
|
+
mime === 'image/webp' ? 'webp' : 'bin';
|
|
173
1663
|
try {
|
|
174
|
-
|
|
1664
|
+
const buffer = Buffer.from(base64, 'base64');
|
|
1665
|
+
const tmpDir = getExportTmpDir();
|
|
1666
|
+
if (!tmpDir)
|
|
1667
|
+
return undefined;
|
|
1668
|
+
const fileName = `img-${crypto.createHash('sha256').update(buffer).digest('hex').slice(0, 16)}.${ext}`;
|
|
1669
|
+
const filePath = path.join(tmpDir, fileName);
|
|
1670
|
+
writeFileSync(filePath, buffer);
|
|
1671
|
+
dataUriToTempPath.set(url, filePath);
|
|
1672
|
+
return filePath;
|
|
175
1673
|
}
|
|
176
1674
|
catch {
|
|
177
|
-
|
|
1675
|
+
return undefined;
|
|
178
1676
|
}
|
|
179
1677
|
}
|
|
180
|
-
function
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
1678
|
+
export function addImageMaybe(slide, url, x, y, w, h) {
|
|
1679
|
+
if (!url)
|
|
1680
|
+
return;
|
|
1681
|
+
let pathOrUrl = url;
|
|
1682
|
+
if (url.startsWith('data:')) {
|
|
1683
|
+
const tempPath = dataUriToTempFile(url);
|
|
1684
|
+
if (!tempPath) {
|
|
1685
|
+
slide.addText('[图片]', { x, y, w, h, fontSize: 14, color: COLORS.secondary, align: 'center', valign: 'middle' });
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
pathOrUrl = tempPath;
|
|
189
1689
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
fontFace: FONTS.heading,
|
|
194
|
-
});
|
|
195
|
-
if (props.subtitle) {
|
|
196
|
-
slide.addText(props.subtitle, {
|
|
197
|
-
x: 1.5, y: 3.8, w: 7, h: 0.8,
|
|
198
|
-
fontSize: 22, color: COLORS.secondary, align: 'center',
|
|
199
|
-
fontFace: FONTS.body,
|
|
200
|
-
});
|
|
1690
|
+
// pptxgenjs 在 Node 环境无法直接下载远程 URL,仅当为本地文件或 base64 时可用。
|
|
1691
|
+
try {
|
|
1692
|
+
slide.addImage({ path: pathOrUrl, x, y, w, h, sizing: { type: 'crop', w, h } });
|
|
201
1693
|
}
|
|
202
|
-
|
|
203
|
-
slide.addText(
|
|
204
|
-
x: 1, y: 5.0, w: 8, h: 0.3,
|
|
205
|
-
fontSize: 14, color: COLORS.secondary, align: 'center',
|
|
206
|
-
fontFace: FONTS.mono,
|
|
207
|
-
});
|
|
1694
|
+
catch {
|
|
1695
|
+
slide.addText('[图片]', { x, y, w, h, fontSize: 14, color: COLORS.secondary, align: 'center', valign: 'middle' });
|
|
208
1696
|
}
|
|
209
1697
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
slide.addShape('rect', {
|
|
214
|
-
x: 0, y: 0, w: 10, h: 5.625,
|
|
215
|
-
fill: { color: '0A0F1C' },
|
|
216
|
-
});
|
|
217
|
-
slide.addShape('ellipse', {
|
|
218
|
-
x: 2.5, y: 0.5, w: 5, h: 4.5,
|
|
219
|
-
fill: { color: '06B6D4' },
|
|
220
|
-
});
|
|
221
|
-
if (props.image) {
|
|
1698
|
+
function cleanupTempImages() {
|
|
1699
|
+
dataUriToTempPath.clear();
|
|
1700
|
+
if (exportTmpDir) {
|
|
222
1701
|
try {
|
|
223
|
-
|
|
1702
|
+
rmSync(exportTmpDir, { recursive: true, force: true });
|
|
224
1703
|
}
|
|
225
1704
|
catch {
|
|
226
|
-
// ignore
|
|
1705
|
+
// ignore cleanup errors
|
|
227
1706
|
}
|
|
228
|
-
|
|
229
|
-
if (props.kicker) {
|
|
230
|
-
slide.addText(props.kicker, {
|
|
231
|
-
x: 1, y: 1.8, w: 8, h: 0.35,
|
|
232
|
-
fontSize: 13, color: '06B6D4', align: 'center',
|
|
233
|
-
fontFace: FONTS.mono,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
slide.addText(props.title, {
|
|
237
|
-
x: 1, y: 2.2, w: 8, h: 1.2,
|
|
238
|
-
fontSize: 56, color: 'FFFFFF', bold: true, align: 'center',
|
|
239
|
-
fontFace: FONTS.heading,
|
|
240
|
-
});
|
|
241
|
-
if (props.subtitle) {
|
|
242
|
-
slide.addText(props.subtitle, {
|
|
243
|
-
x: 1.5, y: 3.5, w: 7, h: 0.7,
|
|
244
|
-
fontSize: 22, color: '94A3B8', align: 'center',
|
|
245
|
-
fontFace: FONTS.body,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
if (props.date) {
|
|
249
|
-
slide.addText(props.date, {
|
|
250
|
-
x: 1, y: 4.6, w: 8, h: 0.3,
|
|
251
|
-
fontSize: 14, color: '94A3B8', align: 'center',
|
|
252
|
-
fontFace: FONTS.mono,
|
|
253
|
-
});
|
|
1707
|
+
exportTmpDir = null;
|
|
254
1708
|
}
|
|
255
1709
|
}
|
|
256
|
-
function renderTableOfContentsV1(slide, props) {
|
|
1710
|
+
export function renderTableOfContentsV1(slide, props) {
|
|
257
1711
|
addKicker(slide, props.kicker);
|
|
258
1712
|
addTitle(slide, props.title);
|
|
259
|
-
const items = props.items || [];
|
|
260
|
-
let y = 2.
|
|
1713
|
+
const items = (props.items || []).slice(0, 4);
|
|
1714
|
+
let y = 2.4;
|
|
261
1715
|
items.forEach((item, index) => {
|
|
262
1716
|
const number = String(index + 1).padStart(2, '0');
|
|
263
1717
|
slide.addText(number, {
|
|
264
|
-
x: 0.8, y, w: 0.8, h: 0.
|
|
1718
|
+
x: 0.8, y, w: 0.8, h: 0.45,
|
|
265
1719
|
fontSize: 18, color: COLORS.accent, align: 'left', valign: 'top',
|
|
266
1720
|
fontFace: FONTS.mono,
|
|
267
1721
|
});
|
|
268
1722
|
slide.addText(item, {
|
|
269
|
-
x: 1.7, y, w: 7.8, h: 0.
|
|
1723
|
+
x: 1.7, y, w: 7.8, h: 0.45,
|
|
270
1724
|
fontSize: 26, color: COLORS.primary, align: 'left', valign: 'top',
|
|
271
1725
|
fontFace: FONTS.body,
|
|
272
1726
|
});
|
|
273
|
-
y += 0.
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
function renderMetricV1(slide, props) {
|
|
277
|
-
if (props.label) {
|
|
278
|
-
slide.addText(props.label, {
|
|
279
|
-
x: 1, y: 1.8, w: 8, h: 0.4,
|
|
280
|
-
fontSize: 16, color: COLORS.secondary, align: 'center',
|
|
281
|
-
fontFace: FONTS.body,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
const metricText = props.unit ? `${props.value} ${props.unit}` : props.value;
|
|
285
|
-
slide.addText(metricText, {
|
|
286
|
-
x: 1, y: 2.4, w: 8, h: 1.4,
|
|
287
|
-
fontSize: 90, color: COLORS.primary, bold: true, align: 'center',
|
|
288
|
-
fontFace: FONTS.heading,
|
|
289
|
-
});
|
|
290
|
-
if (props.description) {
|
|
291
|
-
slide.addText(props.description, {
|
|
292
|
-
x: 1.5, y: 4.1, w: 7, h: 0.8,
|
|
293
|
-
fontSize: 20, color: COLORS.secondary, align: 'center',
|
|
294
|
-
fontFace: FONTS.body,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function renderDarkTechMetricV1(slide, props) {
|
|
299
|
-
slide.background = { color: '0A0F1C' };
|
|
300
|
-
slide.addShape('ellipse', {
|
|
301
|
-
x: 2.5, y: 0.5, w: 5, h: 4.5,
|
|
302
|
-
fill: { color: '06B6D4' },
|
|
303
|
-
});
|
|
304
|
-
if (props.label) {
|
|
305
|
-
slide.addText(props.label, {
|
|
306
|
-
x: 1, y: 1.6, w: 8, h: 0.4,
|
|
307
|
-
fontSize: 15, color: '06B6D4', align: 'center',
|
|
308
|
-
fontFace: FONTS.mono,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
const metricText = props.unit ? `${props.value} ${props.unit}` : props.value;
|
|
312
|
-
slide.addText(metricText, {
|
|
313
|
-
x: 1, y: 2.2, w: 8, h: 1.5,
|
|
314
|
-
fontSize: 96, color: 'FFFFFF', bold: true, align: 'center',
|
|
315
|
-
fontFace: FONTS.heading,
|
|
316
|
-
});
|
|
317
|
-
if (props.description) {
|
|
318
|
-
slide.addText(props.description, {
|
|
319
|
-
x: 1.5, y: 4.0, w: 7, h: 0.8,
|
|
320
|
-
fontSize: 20, color: '94A3B8', align: 'center',
|
|
321
|
-
fontFace: FONTS.body,
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function renderMetricV2(slide, props) {
|
|
326
|
-
if (props.kicker) {
|
|
327
|
-
slide.addText(props.kicker, {
|
|
328
|
-
x: 1, y: 1.8, w: 8, h: 0.4,
|
|
329
|
-
fontSize: 14, color: COLORS.accent, align: 'center',
|
|
330
|
-
fontFace: FONTS.mono,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
const metricText = props.unit ? `${props.value} ${props.unit}` : props.value;
|
|
334
|
-
slide.addText(metricText, {
|
|
335
|
-
x: 1, y: 2.5, w: 8, h: 1.4,
|
|
336
|
-
fontSize: 80, color: COLORS.primary, bold: true, align: 'center',
|
|
337
|
-
fontFace: FONTS.heading,
|
|
1727
|
+
y += 0.75;
|
|
338
1728
|
});
|
|
339
|
-
if (props.description) {
|
|
340
|
-
slide.addText(props.description, {
|
|
341
|
-
x: 1.5, y: 4.1, w: 7, h: 0.8,
|
|
342
|
-
fontSize: 20, color: COLORS.secondary, align: 'center',
|
|
343
|
-
fontFace: FONTS.body,
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
1729
|
}
|
|
347
|
-
function renderStatsV1(slide, props) {
|
|
1730
|
+
export function renderStatsV1(slide, props) {
|
|
1731
|
+
const hasInsight = props.showInsight !== false && !!props.insight && (!!props.insight.value || !!props.insight.label || !!props.insight.description);
|
|
1732
|
+
const mainW = hasInsight ? 6.8 : 8.4;
|
|
1733
|
+
const cardW = (mainW - 0.4) / 2;
|
|
1734
|
+
const startX = 0.8;
|
|
348
1735
|
addKicker(slide, props.kicker);
|
|
349
|
-
addTitle(slide, props.title);
|
|
1736
|
+
addTitle(slide, props.title, startX, 1.3, mainW);
|
|
350
1737
|
const stats = (props.stats || []).slice(0, 4);
|
|
351
1738
|
const positions = [
|
|
352
|
-
{ x:
|
|
353
|
-
{ x:
|
|
354
|
-
{ x:
|
|
355
|
-
{ x:
|
|
1739
|
+
{ x: startX, y: 2.5 },
|
|
1740
|
+
{ x: startX + cardW + 0.2, y: 2.5 },
|
|
1741
|
+
{ x: startX, y: 4.0 },
|
|
1742
|
+
{ x: startX + cardW + 0.2, y: 4.0 },
|
|
356
1743
|
];
|
|
357
1744
|
stats.forEach((stat, index) => {
|
|
358
1745
|
const pos = positions[index];
|
|
359
1746
|
if (!pos)
|
|
360
1747
|
return;
|
|
361
1748
|
slide.addShape('rect', {
|
|
362
|
-
x: pos.x, y: pos.y, w:
|
|
1749
|
+
x: pos.x, y: pos.y, w: cardW, h: 1.3,
|
|
363
1750
|
fill: { color: COLORS.light },
|
|
364
1751
|
line: { color: COLORS.border, width: 1 },
|
|
365
1752
|
});
|
|
366
1753
|
const valueText = [stat.value, stat.unit].filter(Boolean).join(' ');
|
|
367
1754
|
slide.addText(valueText || '-', {
|
|
368
|
-
x: pos.x + 0.2, y: pos.y + 0.15, w:
|
|
1755
|
+
x: pos.x + 0.2, y: pos.y + 0.15, w: cardW - 0.4, h: 0.55,
|
|
369
1756
|
fontSize: 30, color: COLORS.primary, bold: true, align: 'left',
|
|
370
1757
|
fontFace: FONTS.heading,
|
|
371
1758
|
});
|
|
372
1759
|
slide.addText(stat.label || '', {
|
|
373
|
-
x: pos.x + 0.2, y: pos.y + 0.75, w:
|
|
1760
|
+
x: pos.x + 0.2, y: pos.y + 0.75, w: cardW - 1.0, h: 0.35,
|
|
374
1761
|
fontSize: 14, color: COLORS.secondary, align: 'left',
|
|
375
1762
|
fontFace: FONTS.body,
|
|
376
1763
|
});
|
|
377
1764
|
if (stat.change) {
|
|
378
1765
|
slide.addText(stat.change, {
|
|
379
|
-
x: pos.x +
|
|
1766
|
+
x: pos.x + cardW - 1.0, y: pos.y + 0.75, w: 0.8, h: 0.35,
|
|
380
1767
|
fontSize: 14, color: COLORS.accent, align: 'right',
|
|
381
1768
|
fontFace: FONTS.mono,
|
|
382
1769
|
});
|
|
383
1770
|
}
|
|
384
1771
|
});
|
|
1772
|
+
if (hasInsight) {
|
|
1773
|
+
renderInsightPanel(slide, props.insight, 8.0, 2.3, 2.4, 3.2);
|
|
1774
|
+
}
|
|
385
1775
|
}
|
|
386
|
-
function
|
|
1776
|
+
export function renderChartGauge(slide, props) {
|
|
387
1777
|
addKicker(slide, props.kicker);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
1778
|
+
if (props.title)
|
|
1779
|
+
addTitle(slide, props.title);
|
|
1780
|
+
const value = props.value ?? 0;
|
|
1781
|
+
const min = props.min ?? 0;
|
|
1782
|
+
const max = Math.max(props.max ?? 100, min + 1);
|
|
1783
|
+
const unit = props.unit || '';
|
|
1784
|
+
const percentage = Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100));
|
|
1785
|
+
slide.addText(`${value}${unit}`, {
|
|
1786
|
+
x: 0, y: 2.4, w: 10, h: 1.0,
|
|
1787
|
+
fontSize: 48, bold: true, color: COLORS.accent,
|
|
1788
|
+
align: 'center', valign: 'middle', fontFace: FONTS.heading,
|
|
1789
|
+
});
|
|
1790
|
+
slide.addText(`${percentage.toFixed(1)}%`, {
|
|
1791
|
+
x: 0, y: 3.4, w: 10, h: 0.5,
|
|
1792
|
+
fontSize: 18, color: COLORS.secondary,
|
|
1793
|
+
align: 'center', valign: 'middle', fontFace: FONTS.body,
|
|
1794
|
+
});
|
|
1795
|
+
// pptxgenjs 3.12 运行时支持 doughnut,但当前类型定义较旧,使用 as any 绕过。
|
|
1796
|
+
slide.addChart('doughnut', [
|
|
1797
|
+
{ name: '已完成', labels: ['已完成', '未完成'], values: [percentage, 100 - percentage] },
|
|
1798
|
+
], {
|
|
1799
|
+
x: 3, y: 4.0, w: 4, h: 1.5,
|
|
1800
|
+
chartColors: [COLORS.accent, COLORS.border],
|
|
1801
|
+
showValue: false,
|
|
1802
|
+
holeSize: 60,
|
|
1803
|
+
});
|
|
1804
|
+
const insight = props.insight;
|
|
1805
|
+
const hasInsight = !!insight && (!!insight.value || !!insight.label || !!insight.description);
|
|
1806
|
+
if (hasInsight) {
|
|
1807
|
+
slide.addShape('roundRect', {
|
|
1808
|
+
x: 6.5, y: 2.3, w: 3.0, h: 3.2,
|
|
1809
|
+
fill: { color: COLORS.surfaceElevated },
|
|
1810
|
+
line: { color: COLORS.border, width: 1 },
|
|
1811
|
+
rectRadius: 0.15,
|
|
1812
|
+
});
|
|
1813
|
+
let cursorY = 2.6;
|
|
1814
|
+
if (insight.value) {
|
|
1815
|
+
slide.addText(insight.value, {
|
|
1816
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.6,
|
|
1817
|
+
fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.accent,
|
|
1818
|
+
});
|
|
1819
|
+
cursorY += 0.6;
|
|
1820
|
+
}
|
|
1821
|
+
if (insight.label) {
|
|
1822
|
+
slide.addText(insight.label, {
|
|
1823
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.3,
|
|
1824
|
+
fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1825
|
+
});
|
|
1826
|
+
cursorY += 0.45;
|
|
1827
|
+
}
|
|
1828
|
+
if (insight.description) {
|
|
1829
|
+
cursorY += 0.15;
|
|
1830
|
+
slide.addText(insight.description, {
|
|
1831
|
+
x: 6.7, y: cursorY, w: 2.6, h: 1.6,
|
|
1832
|
+
fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1833
|
+
valign: 'top',
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
export function renderChartHeatmap(slide, props) {
|
|
1839
|
+
addKicker(slide, props.kicker);
|
|
1840
|
+
if (props.title)
|
|
1841
|
+
addTitle(slide, props.title);
|
|
1842
|
+
const raw = props.data || [];
|
|
1843
|
+
const flat = raw.map((cell) => ({ name: `${cell[0]} / ${cell[1]}`, value: cell[2] }));
|
|
1844
|
+
const sorted = flat.sort((a, b) => b.value - a.value).slice(0, 12);
|
|
1845
|
+
const insight = props.insight;
|
|
1846
|
+
const hasInsight = !!insight && (!!insight.value || !!insight.label || !!insight.description);
|
|
1847
|
+
if (sorted.length === 0) {
|
|
1848
|
+
slide.addText('(暂无图表数据)', {
|
|
1849
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
1850
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
1851
|
+
fontFace: FONTS.body,
|
|
1852
|
+
});
|
|
392
1853
|
return;
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
slide.addChart(chartType, chartData, {
|
|
402
|
-
x: 0.8, y: 2.3, w: 8.4, h: 3.2,
|
|
403
|
-
chartColors: [COLORS.accent],
|
|
1854
|
+
}
|
|
1855
|
+
const labels = sorted.map((d) => d.name);
|
|
1856
|
+
const values = sorted.map((d) => d.value);
|
|
1857
|
+
const chartColors = sorted.map((_, i) => CHART_COLORS[i % CHART_COLORS.length]);
|
|
1858
|
+
const chartW = hasInsight ? 5.6 : 8.4;
|
|
1859
|
+
slide.addChart('bar', [{ name: props.title || '热度', labels, values }], {
|
|
1860
|
+
x: 0.8, y: 2.3, w: chartW, h: 3.2,
|
|
1861
|
+
chartColors,
|
|
404
1862
|
showValue: true,
|
|
405
1863
|
dataLabelColor: COLORS.primary,
|
|
406
1864
|
dataLabelFontSize: 10,
|
|
407
1865
|
});
|
|
1866
|
+
if (hasInsight) {
|
|
1867
|
+
slide.addShape('roundRect', {
|
|
1868
|
+
x: 6.5, y: 2.3, w: 3.0, h: 3.2,
|
|
1869
|
+
fill: { color: COLORS.surfaceElevated },
|
|
1870
|
+
line: { color: COLORS.border, width: 1 },
|
|
1871
|
+
rectRadius: 0.15,
|
|
1872
|
+
});
|
|
1873
|
+
let cursorY = 2.6;
|
|
1874
|
+
if (insight.value) {
|
|
1875
|
+
slide.addText(insight.value, {
|
|
1876
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.6,
|
|
1877
|
+
fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.accent,
|
|
1878
|
+
});
|
|
1879
|
+
cursorY += 0.6;
|
|
1880
|
+
}
|
|
1881
|
+
if (insight.label) {
|
|
1882
|
+
slide.addText(insight.label, {
|
|
1883
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.3,
|
|
1884
|
+
fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1885
|
+
});
|
|
1886
|
+
cursorY += 0.45;
|
|
1887
|
+
}
|
|
1888
|
+
if (insight.description) {
|
|
1889
|
+
cursorY += 0.15;
|
|
1890
|
+
slide.addText(insight.description, {
|
|
1891
|
+
x: 6.7, y: cursorY, w: 2.6, h: 1.6,
|
|
1892
|
+
fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1893
|
+
valign: 'top',
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
408
1897
|
}
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
1898
|
+
export function renderChartFunnel(slide, props) {
|
|
1899
|
+
if (props.kicker)
|
|
1900
|
+
addKicker(slide, props.kicker);
|
|
1901
|
+
if (props.title)
|
|
1902
|
+
addTitle(slide, props.title);
|
|
1903
|
+
const raw = props.data || [];
|
|
1904
|
+
const insight = props.insight || {};
|
|
1905
|
+
const hasInsight = !!insight.value || !!insight.label || !!insight.description;
|
|
1906
|
+
if (raw.length === 0) {
|
|
1907
|
+
slide.addText('(暂无图表数据)', {
|
|
1908
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
1909
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
1910
|
+
fontFace: FONTS.body,
|
|
1911
|
+
});
|
|
415
1912
|
return;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
slide.addChart('bar',
|
|
423
|
-
x: 0.
|
|
1913
|
+
}
|
|
1914
|
+
const labels = raw.map((d) => d.name);
|
|
1915
|
+
const values = raw.map((d) => d.value);
|
|
1916
|
+
const chartColors = raw.map((_, i) => CHART_COLORS[i % CHART_COLORS.length]);
|
|
1917
|
+
const chartW = hasInsight ? 5.6 : 8.4;
|
|
1918
|
+
// PptxGenJS 没有真正的漏斗图,使用横向柱状图模拟漏斗层级
|
|
1919
|
+
slide.addChart('bar', [{ name: props.title || '漏斗', labels, values }], {
|
|
1920
|
+
x: 0.6, y: 2.3, w: chartW, h: 3.2,
|
|
424
1921
|
chartColors,
|
|
425
1922
|
showValue: true,
|
|
426
1923
|
dataLabelColor: COLORS.primary,
|
|
427
1924
|
dataLabelFontSize: 10,
|
|
428
1925
|
});
|
|
1926
|
+
if (hasInsight) {
|
|
1927
|
+
slide.addShape('roundRect', {
|
|
1928
|
+
x: 6.5, y: 2.3, w: 3.0, h: 3.2,
|
|
1929
|
+
fill: { color: 'FFFFFF', transparency: 40 },
|
|
1930
|
+
line: { color: 'E5E7EB', width: 1 },
|
|
1931
|
+
rectRadius: 0.15,
|
|
1932
|
+
});
|
|
1933
|
+
let cursorY = 2.6;
|
|
1934
|
+
if (insight.value) {
|
|
1935
|
+
slide.addText(insight.value, {
|
|
1936
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.6,
|
|
1937
|
+
fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.primary,
|
|
1938
|
+
});
|
|
1939
|
+
cursorY += 0.6;
|
|
1940
|
+
}
|
|
1941
|
+
if (insight.label) {
|
|
1942
|
+
slide.addText(insight.label, {
|
|
1943
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.3,
|
|
1944
|
+
fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1945
|
+
});
|
|
1946
|
+
cursorY += 0.45;
|
|
1947
|
+
}
|
|
1948
|
+
if (insight.description) {
|
|
1949
|
+
cursorY += 0.15;
|
|
1950
|
+
slide.addText(insight.description, {
|
|
1951
|
+
x: 6.7, y: cursorY, w: 2.6, h: 1.6,
|
|
1952
|
+
fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1953
|
+
valign: 'top',
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
if (props.footnote) {
|
|
1958
|
+
slide.addText(props.footnote, {
|
|
1959
|
+
x: 0.6, y: 5.75, w: 8.9, h: 0.3,
|
|
1960
|
+
fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary,
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
429
1963
|
}
|
|
430
|
-
function
|
|
431
|
-
addKicker(slide, props.kicker);
|
|
432
|
-
addTitle(slide, props.title);
|
|
433
|
-
addBulletList(slide, props.points || [], 0.8, 2.8, 8.4);
|
|
434
|
-
}
|
|
435
|
-
function renderContentV2(slide, props) {
|
|
436
|
-
addKicker(slide, props.kicker);
|
|
437
|
-
addTitle(slide, props.title);
|
|
438
|
-
slide.addText('左栏', {
|
|
439
|
-
x: 0.8, y: 2.4, w: 4.0, h: 0.4,
|
|
440
|
-
fontSize: 18, color: COLORS.accent, bold: true, align: 'left',
|
|
441
|
-
fontFace: FONTS.heading,
|
|
442
|
-
});
|
|
443
|
-
slide.addText('右栏', {
|
|
444
|
-
x: 5.2, y: 2.4, w: 4.0, h: 0.4,
|
|
445
|
-
fontSize: 18, color: COLORS.accent, bold: true, align: 'left',
|
|
446
|
-
fontFace: FONTS.heading,
|
|
447
|
-
});
|
|
448
|
-
addBulletList(slide, props.leftPoints || [], 0.8, 2.9, 4.0);
|
|
449
|
-
addBulletList(slide, props.rightPoints || [], 5.2, 2.9, 4.0);
|
|
450
|
-
}
|
|
451
|
-
function renderContentV3(slide, props) {
|
|
452
|
-
addKicker(slide, props.kicker);
|
|
453
|
-
addTitle(slide, props.title, 0.8, 1.3, 4.5);
|
|
454
|
-
addBulletList(slide, props.points || [], 0.8, 2.5, 4.2);
|
|
455
|
-
addImageMaybe(slide, props.imageUrl, 5.3, 1.5, 4.0, 3.5);
|
|
456
|
-
}
|
|
457
|
-
function renderSplitV1(slide, props) {
|
|
458
|
-
renderContentV3(slide, props);
|
|
459
|
-
}
|
|
460
|
-
function renderComparisonV1(slide, props) {
|
|
461
|
-
addKicker(slide, props.kicker);
|
|
462
|
-
addTitle(slide, props.title);
|
|
463
|
-
renderComparisonColumns(slide, props.leftTitle || '方案 A', props.leftPoints || [], props.rightTitle || '方案 B', props.rightPoints || []);
|
|
464
|
-
}
|
|
465
|
-
function renderComparisonV2(slide, props) {
|
|
1964
|
+
export function renderChartRadar(slide, props) {
|
|
466
1965
|
addKicker(slide, props.kicker);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
1966
|
+
if (props.title)
|
|
1967
|
+
addTitle(slide, props.title);
|
|
1968
|
+
const indicators = props.indicators || [];
|
|
1969
|
+
const datasets = props.data || [];
|
|
1970
|
+
const insight = props.insight;
|
|
1971
|
+
const hasInsight = !!insight && (!!insight.value || !!insight.label || !!insight.description);
|
|
1972
|
+
if (indicators.length === 0 || datasets.length === 0) {
|
|
1973
|
+
slide.addText('(暂无图表数据)', {
|
|
1974
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
1975
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
1976
|
+
fontFace: FONTS.body,
|
|
1977
|
+
});
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const labels = indicators.map((i) => i.name);
|
|
1981
|
+
const chartData = datasets.map((d) => ({
|
|
1982
|
+
name: d.name,
|
|
1983
|
+
labels,
|
|
1984
|
+
values: d.value,
|
|
1985
|
+
}));
|
|
1986
|
+
const chartColors = datasets.map((_, i) => CHART_COLORS[i % CHART_COLORS.length]);
|
|
1987
|
+
const chartW = hasInsight ? 5.6 : 8.4;
|
|
1988
|
+
// pptxgenjs 3.12 运行时支持 radar,但当前类型定义较旧,使用 as any 绕过。
|
|
1989
|
+
slide.addChart('radar', chartData, {
|
|
1990
|
+
x: 0.8, y: 2.3, w: chartW, h: 3.2,
|
|
1991
|
+
chartColors,
|
|
1992
|
+
showValue: true,
|
|
1993
|
+
dataLabelColor: COLORS.primary,
|
|
1994
|
+
dataLabelFontSize: 10,
|
|
480
1995
|
});
|
|
481
|
-
|
|
482
|
-
|
|
1996
|
+
if (hasInsight) {
|
|
1997
|
+
slide.addShape('roundRect', {
|
|
1998
|
+
x: 6.5, y: 2.3, w: 3.0, h: 3.2,
|
|
1999
|
+
fill: { color: COLORS.surfaceElevated },
|
|
2000
|
+
line: { color: COLORS.border, width: 1 },
|
|
2001
|
+
rectRadius: 0.15,
|
|
2002
|
+
});
|
|
2003
|
+
let cursorY = 2.6;
|
|
2004
|
+
if (insight.value) {
|
|
2005
|
+
slide.addText(insight.value, {
|
|
2006
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.6,
|
|
2007
|
+
fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.accent,
|
|
2008
|
+
});
|
|
2009
|
+
cursorY += 0.6;
|
|
2010
|
+
}
|
|
2011
|
+
if (insight.label) {
|
|
2012
|
+
slide.addText(insight.label, {
|
|
2013
|
+
x: 6.7, y: cursorY, w: 2.6, h: 0.3,
|
|
2014
|
+
fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
|
|
2015
|
+
});
|
|
2016
|
+
cursorY += 0.45;
|
|
2017
|
+
}
|
|
2018
|
+
if (insight.description) {
|
|
2019
|
+
cursorY += 0.15;
|
|
2020
|
+
slide.addText(insight.description, {
|
|
2021
|
+
x: 6.7, y: cursorY, w: 2.6, h: 1.6,
|
|
2022
|
+
fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary,
|
|
2023
|
+
valign: 'top',
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
483
2027
|
}
|
|
484
|
-
function renderProcessV1(slide, props) {
|
|
2028
|
+
export function renderProcessV1(slide, props) {
|
|
485
2029
|
addKicker(slide, props.kicker);
|
|
486
2030
|
addTitle(slide, props.title);
|
|
487
2031
|
const steps = props.steps || [];
|
|
488
2032
|
const count = steps.length;
|
|
489
|
-
if (count === 0)
|
|
2033
|
+
if (count === 0) {
|
|
2034
|
+
slide.addText('(暂无流程步骤)', {
|
|
2035
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
2036
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
2037
|
+
fontFace: FONTS.body,
|
|
2038
|
+
});
|
|
490
2039
|
return;
|
|
2040
|
+
}
|
|
491
2041
|
const stepWidth = 8.4 / count;
|
|
492
2042
|
steps.forEach((step, index) => {
|
|
493
2043
|
const x = 0.8 + index * stepWidth;
|
|
@@ -502,40 +2052,12 @@ function renderProcessV1(slide, props) {
|
|
|
502
2052
|
});
|
|
503
2053
|
slide.addText(step, {
|
|
504
2054
|
x: x + 0.1, y: 3.5, w: stepWidth - 0.2, h: 1.2,
|
|
505
|
-
fontSize: 18, color: COLORS.primary, align: 'center', valign: 'top',
|
|
506
|
-
fontFace: FONTS.body,
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
function renderProcessV2(slide, props) {
|
|
511
|
-
addKicker(slide, props.kicker);
|
|
512
|
-
addTitle(slide, props.title);
|
|
513
|
-
const steps = (props.steps || []).slice(0, 4);
|
|
514
|
-
let y = 2.5;
|
|
515
|
-
steps.forEach((step, index) => {
|
|
516
|
-
slide.addShape('ellipse', {
|
|
517
|
-
x: 0.9, y: y + 0.12, w: 0.4, h: 0.4,
|
|
518
|
-
fill: { color: COLORS.accent },
|
|
519
|
-
});
|
|
520
|
-
slide.addText(String(index + 1), {
|
|
521
|
-
x: 0.9, y: y + 0.12, w: 0.4, h: 0.4,
|
|
522
|
-
fontSize: 16, color: COLORS.white, bold: true, align: 'center', valign: 'middle',
|
|
523
|
-
fontFace: FONTS.heading,
|
|
524
|
-
});
|
|
525
|
-
slide.addText(step.title || '', {
|
|
526
|
-
x: 1.6, y, w: 7.6, h: 0.35,
|
|
527
|
-
fontSize: 20, color: COLORS.primary, bold: true, align: 'left',
|
|
528
|
-
fontFace: FONTS.heading,
|
|
529
|
-
});
|
|
530
|
-
slide.addText(step.description || '', {
|
|
531
|
-
x: 1.6, y: y + 0.38, w: 7.6, h: 0.5,
|
|
532
|
-
fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
2055
|
+
fontSize: 18, color: COLORS.primary, align: 'center', valign: 'top',
|
|
533
2056
|
fontFace: FONTS.body,
|
|
534
2057
|
});
|
|
535
|
-
y += 1.0;
|
|
536
2058
|
});
|
|
537
2059
|
}
|
|
538
|
-
function renderTimelineV1(slide, props) {
|
|
2060
|
+
export function renderTimelineV1(slide, props) {
|
|
539
2061
|
addKicker(slide, props.kicker);
|
|
540
2062
|
addTitle(slide, props.title);
|
|
541
2063
|
const milestones = (props.milestones || []).slice(0, 4);
|
|
@@ -568,65 +2090,11 @@ function renderTimelineV1(slide, props) {
|
|
|
568
2090
|
});
|
|
569
2091
|
});
|
|
570
2092
|
}
|
|
571
|
-
function renderWarmBusinessTimelineV1(slide, props) {
|
|
572
|
-
addKicker(slide, props.kicker);
|
|
573
|
-
addTitle(slide, props.title);
|
|
574
|
-
const milestones = (props.milestones || []).slice(0, 4);
|
|
575
|
-
const count = milestones.length || 1;
|
|
576
|
-
const gap = 0.25;
|
|
577
|
-
const cardW = (8.4 - gap * (count - 1)) / count;
|
|
578
|
-
const startX = 0.8;
|
|
579
|
-
const y = 2.7;
|
|
580
|
-
const h = 3.2;
|
|
581
|
-
const warmAccent = 'D97706';
|
|
582
|
-
const warmText = '3E2723';
|
|
583
|
-
const warmSecondary = '6D4C41';
|
|
584
|
-
milestones.forEach((m, index) => {
|
|
585
|
-
const x = startX + index * (cardW + gap);
|
|
586
|
-
slide.addShape('rect', {
|
|
587
|
-
x, y, w: cardW, h,
|
|
588
|
-
fill: { color: 'FFF8F0' },
|
|
589
|
-
line: { color: 'F3E5D8', width: 1 },
|
|
590
|
-
rectRadius: 0.12,
|
|
591
|
-
});
|
|
592
|
-
if (index < count - 1) {
|
|
593
|
-
slide.addShape('rect', {
|
|
594
|
-
x: x + cardW, y: y + 0.35, w: gap, h: 0.04,
|
|
595
|
-
fill: { color: warmAccent },
|
|
596
|
-
});
|
|
597
|
-
slide.addShape('ellipse', {
|
|
598
|
-
x: x + cardW + gap - 0.06, y: y + 0.30, w: 0.12, h: 0.12,
|
|
599
|
-
fill: { color: warmAccent },
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
if (m.date) {
|
|
603
|
-
slide.addText(m.date, {
|
|
604
|
-
x: x + 0.16, y: y + 0.22, w: cardW - 0.32, h: 0.3,
|
|
605
|
-
fontSize: 12, color: warmAccent, align: 'left', valign: 'top',
|
|
606
|
-
fontFace: FONTS.mono,
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
if (m.title) {
|
|
610
|
-
slide.addText(m.title, {
|
|
611
|
-
x: x + 0.16, y: y + 0.6, w: cardW - 0.32, h: 0.45,
|
|
612
|
-
fontSize: 17, color: warmText, bold: true, align: 'left', valign: 'top',
|
|
613
|
-
fontFace: FONTS.heading,
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
if (m.description) {
|
|
617
|
-
slide.addText(m.description, {
|
|
618
|
-
x: x + 0.16, y: y + 1.1, w: cardW - 0.32, h: 1.6,
|
|
619
|
-
fontSize: 13, color: warmSecondary, align: 'left', valign: 'top',
|
|
620
|
-
fontFace: FONTS.body,
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
2093
|
function renderRoadmapV1(slide, props) {
|
|
626
2094
|
addKicker(slide, props.kicker);
|
|
627
2095
|
addTitle(slide, props.title);
|
|
628
2096
|
const phases = (props.phases || []).slice(0, 4);
|
|
629
|
-
let y = 2.
|
|
2097
|
+
let y = 2.1;
|
|
630
2098
|
phases.forEach((phase, index) => {
|
|
631
2099
|
const statusColor = phase.status === '已完成' ? COLORS.accent : COLORS.secondary;
|
|
632
2100
|
slide.addShape('rect', {
|
|
@@ -639,48 +2107,14 @@ function renderRoadmapV1(slide, props) {
|
|
|
639
2107
|
fontFace: FONTS.heading,
|
|
640
2108
|
});
|
|
641
2109
|
slide.addText(phase.description || '', {
|
|
642
|
-
x: 1.1, y: y + 0.38, w: 7.8, h: 0.
|
|
2110
|
+
x: 1.1, y: y + 0.38, w: 7.8, h: 0.45,
|
|
643
2111
|
fontSize: 14, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
644
2112
|
fontFace: FONTS.body,
|
|
645
2113
|
});
|
|
646
|
-
y +=
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
function renderQuoteV1(slide, props) {
|
|
650
|
-
slide.addText(`“${props.quote}”`, {
|
|
651
|
-
x: 1, y: 2.0, w: 8, h: 2.2,
|
|
652
|
-
fontSize: 36, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
|
|
653
|
-
fontFace: FONTS.heading,
|
|
654
|
-
});
|
|
655
|
-
const attribution = [props.author, props.source].filter(Boolean).join(' · ');
|
|
656
|
-
if (attribution) {
|
|
657
|
-
slide.addText(attribution, {
|
|
658
|
-
x: 1, y: 4.4, w: 8, h: 0.4,
|
|
659
|
-
fontSize: 18, color: COLORS.secondary, align: 'center',
|
|
660
|
-
fontFace: FONTS.mono,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
function renderQuoteV2(slide, props) {
|
|
665
|
-
slide.addShape('rect', {
|
|
666
|
-
x: 0.6, y: 1.5, w: 0.15, h: 2.8,
|
|
667
|
-
fill: { color: COLORS.accent },
|
|
668
|
-
});
|
|
669
|
-
slide.addText(`“${props.quote}”`, {
|
|
670
|
-
x: 1.0, y: 1.8, w: 8.0, h: 2.0,
|
|
671
|
-
fontSize: 34, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
672
|
-
fontFace: FONTS.heading,
|
|
2114
|
+
y += 0.8;
|
|
673
2115
|
});
|
|
674
|
-
const attribution = [props.author, props.role].filter(Boolean).join(' · ');
|
|
675
|
-
if (attribution) {
|
|
676
|
-
slide.addText(attribution, {
|
|
677
|
-
x: 1.0, y: 4.0, w: 8.0, h: 0.4,
|
|
678
|
-
fontSize: 18, color: COLORS.secondary, align: 'left',
|
|
679
|
-
fontFace: FONTS.mono,
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
2116
|
}
|
|
683
|
-
function renderTestimonialV1(slide, props) {
|
|
2117
|
+
export function renderTestimonialV1(slide, props) {
|
|
684
2118
|
slide.addText(`“${props.quote}”`, {
|
|
685
2119
|
x: 1, y: 1.8, w: 8, h: 2.2,
|
|
686
2120
|
fontSize: 34, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
|
|
@@ -697,107 +2131,26 @@ function renderTestimonialV1(slide, props) {
|
|
|
697
2131
|
});
|
|
698
2132
|
}
|
|
699
2133
|
}
|
|
700
|
-
function
|
|
701
|
-
const warmAccent = 'D97706';
|
|
702
|
-
const warmText = '3E2723';
|
|
703
|
-
const warmSecondary = '6D4C41';
|
|
704
|
-
const warmSurface = 'FFF8F0';
|
|
705
|
-
slide.addShape('rect', {
|
|
706
|
-
x: 0.8, y: 1.4, w: 8.4, h: 4.2,
|
|
707
|
-
fill: { color: warmSurface },
|
|
708
|
-
line: { color: 'F3E5D8', width: 1 },
|
|
709
|
-
rectRadius: 0.12,
|
|
710
|
-
});
|
|
711
|
-
slide.addText('“', {
|
|
712
|
-
x: 1.0, y: 1.5, w: 0.8, h: 0.8,
|
|
713
|
-
fontSize: 64, color: warmAccent, align: 'left', valign: 'top',
|
|
714
|
-
fontFace: FONTS.heading,
|
|
715
|
-
});
|
|
716
|
-
slide.addText(props.quote, {
|
|
717
|
-
x: 1.2, y: 2.0, w: 7.6, h: 2.0,
|
|
718
|
-
fontSize: 32, color: warmText, bold: true, align: 'center', valign: 'middle',
|
|
719
|
-
fontFace: FONTS.heading,
|
|
720
|
-
});
|
|
721
|
-
if (props.avatarUrl)
|
|
722
|
-
addImageMaybe(slide, props.avatarUrl, 1.4, 4.15, 0.7, 0.7);
|
|
723
|
-
const attribution = [props.author, props.role, props.company].filter(Boolean).join(' · ');
|
|
724
|
-
if (attribution) {
|
|
725
|
-
slide.addText(attribution, {
|
|
726
|
-
x: 2.3, y: 4.3, w: 6.5, h: 0.4,
|
|
727
|
-
fontSize: 15, color: warmSecondary, align: 'left', valign: 'middle',
|
|
728
|
-
fontFace: FONTS.mono,
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
function renderTestimonialV2(slide, props) {
|
|
733
|
-
// 卡片背景
|
|
734
|
-
slide.addShape('rect', {
|
|
735
|
-
x: 1, y: 1.2, w: 8, h: 4.6,
|
|
736
|
-
fill: { color: COLORS.light },
|
|
737
|
-
line: { color: COLORS.border, width: 1 },
|
|
738
|
-
});
|
|
739
|
-
// 公司/Logo
|
|
740
|
-
const header = props.company || '';
|
|
741
|
-
if (header) {
|
|
742
|
-
slide.addText(header, {
|
|
743
|
-
x: 1.4, y: 1.5, w: 7.2, h: 0.4,
|
|
744
|
-
fontSize: 14, color: COLORS.secondary, bold: true,
|
|
745
|
-
fontFace: FONTS.mono,
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
if (props.logoUrl)
|
|
749
|
-
addImageMaybe(slide, props.logoUrl, 7.2, 1.4, 1.2, 0.6);
|
|
750
|
-
// 引用
|
|
751
|
-
slide.addText(`“${props.quote}”`, {
|
|
752
|
-
x: 1.4, y: 2.1, w: 7.2, h: 2.2,
|
|
753
|
-
fontSize: 28, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
754
|
-
fontFace: FONTS.heading,
|
|
755
|
-
});
|
|
756
|
-
// 作者信息
|
|
757
|
-
const attribution = [props.author, props.role].filter(Boolean).join(' · ');
|
|
758
|
-
if (attribution) {
|
|
759
|
-
slide.addText(attribution, {
|
|
760
|
-
x: 1.4, y: 4.5, w: 4.5, h: 0.4,
|
|
761
|
-
fontSize: 16, color: COLORS.secondary, align: 'left',
|
|
762
|
-
fontFace: FONTS.body,
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
// 指标
|
|
766
|
-
if (props.metric) {
|
|
767
|
-
slide.addText(props.metric, {
|
|
768
|
-
x: 6.2, y: 4.3, w: 2, h: 0.6,
|
|
769
|
-
fontSize: 32, color: COLORS.accent, bold: true, align: 'right',
|
|
770
|
-
fontFace: FONTS.heading,
|
|
771
|
-
});
|
|
772
|
-
if (props.metricLabel) {
|
|
773
|
-
slide.addText(props.metricLabel, {
|
|
774
|
-
x: 6.2, y: 4.85, w: 2, h: 0.3,
|
|
775
|
-
fontSize: 12, color: COLORS.secondary, align: 'right',
|
|
776
|
-
fontFace: FONTS.mono,
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
function renderFaqV1(slide, props) {
|
|
2134
|
+
export function renderFaqV1(slide, props) {
|
|
782
2135
|
addKicker(slide, props.kicker);
|
|
783
2136
|
addTitle(slide, props.title);
|
|
784
2137
|
const items = (props.items || []).slice(0, 4);
|
|
785
|
-
let y = 2.
|
|
2138
|
+
let y = 2.0;
|
|
786
2139
|
items.forEach((item) => {
|
|
787
2140
|
slide.addText(`Q: ${item.q || ''}`, {
|
|
788
|
-
x: 0.8, y, w: 8.4, h: 0.
|
|
2141
|
+
x: 0.8, y, w: 8.4, h: 0.32,
|
|
789
2142
|
fontSize: 18, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
790
2143
|
fontFace: FONTS.heading,
|
|
791
2144
|
});
|
|
792
2145
|
slide.addText(`A: ${item.a || ''}`, {
|
|
793
|
-
x: 0.8, y: y + 0.
|
|
2146
|
+
x: 0.8, y: y + 0.37, w: 8.4, h: 0.43,
|
|
794
2147
|
fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
795
2148
|
fontFace: FONTS.body,
|
|
796
2149
|
});
|
|
797
|
-
y +=
|
|
2150
|
+
y += 0.85;
|
|
798
2151
|
});
|
|
799
2152
|
}
|
|
800
|
-
function renderFeatureV1(slide, props) {
|
|
2153
|
+
export function renderFeatureV1(slide, props) {
|
|
801
2154
|
addKicker(slide, props.kicker);
|
|
802
2155
|
addTitle(slide, props.title);
|
|
803
2156
|
const features = (props.features || []).slice(0, 3);
|
|
@@ -823,39 +2176,7 @@ function renderFeatureV1(slide, props) {
|
|
|
823
2176
|
});
|
|
824
2177
|
});
|
|
825
2178
|
}
|
|
826
|
-
function
|
|
827
|
-
addKicker(slide, props.kicker);
|
|
828
|
-
addTitle(slide, props.title);
|
|
829
|
-
const features = (props.features || []).slice(0, 3);
|
|
830
|
-
const cardWidth = 2.6;
|
|
831
|
-
const gap = 0.3;
|
|
832
|
-
const startX = (10 - (features.length * cardWidth + (features.length - 1) * gap)) / 2;
|
|
833
|
-
features.forEach((feature, index) => {
|
|
834
|
-
const x = startX + index * (cardWidth + gap);
|
|
835
|
-
slide.addShape('rect', {
|
|
836
|
-
x, y: 2.8, w: cardWidth, h: 2.2,
|
|
837
|
-
fill: { color: '111827' },
|
|
838
|
-
line: { color: '06B6D4', width: 1 },
|
|
839
|
-
rectRadius: 0.1,
|
|
840
|
-
});
|
|
841
|
-
slide.addText(String(index + 1).padStart(2, '0'), {
|
|
842
|
-
x: x + 0.15, y: 2.95, w: cardWidth - 0.3, h: 0.35,
|
|
843
|
-
fontSize: 14, color: '06B6D4', align: 'left', valign: 'top',
|
|
844
|
-
fontFace: FONTS.mono,
|
|
845
|
-
});
|
|
846
|
-
slide.addText(feature.title || '', {
|
|
847
|
-
x: x + 0.15, y: 3.35, w: cardWidth - 0.3, h: 0.45,
|
|
848
|
-
fontSize: 20, color: 'FFFFFF', bold: true, align: 'left', valign: 'top',
|
|
849
|
-
fontFace: FONTS.heading,
|
|
850
|
-
});
|
|
851
|
-
slide.addText(feature.description || '', {
|
|
852
|
-
x: x + 0.15, y: 3.85, w: cardWidth - 0.3, h: 0.95,
|
|
853
|
-
fontSize: 14, color: '94A3B8', align: 'left', valign: 'top',
|
|
854
|
-
fontFace: FONTS.body,
|
|
855
|
-
});
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
function renderTeamV1(slide, props) {
|
|
2179
|
+
export function renderTeamV1(slide, props) {
|
|
859
2180
|
addKicker(slide, props.kicker);
|
|
860
2181
|
addTitle(slide, props.title);
|
|
861
2182
|
const members = (props.members || []).slice(0, 4);
|
|
@@ -883,7 +2204,7 @@ function renderTeamV1(slide, props) {
|
|
|
883
2204
|
});
|
|
884
2205
|
});
|
|
885
2206
|
}
|
|
886
|
-
function renderPartnersV1(slide, props) {
|
|
2207
|
+
export function renderPartnersV1(slide, props) {
|
|
887
2208
|
addKicker(slide, props.kicker);
|
|
888
2209
|
addTitle(slide, props.title);
|
|
889
2210
|
const partners = (props.partners || []).slice(0, 8);
|
|
@@ -912,7 +2233,7 @@ function renderPartnersV1(slide, props) {
|
|
|
912
2233
|
});
|
|
913
2234
|
});
|
|
914
2235
|
}
|
|
915
|
-
function renderPricingV1(slide, props) {
|
|
2236
|
+
export function renderPricingV1(slide, props) {
|
|
916
2237
|
addKicker(slide, props.kicker);
|
|
917
2238
|
addTitle(slide, props.title);
|
|
918
2239
|
const tiers = (props.tiers || []).slice(0, 3);
|
|
@@ -958,58 +2279,7 @@ function renderPricingV1(slide, props) {
|
|
|
958
2279
|
}
|
|
959
2280
|
});
|
|
960
2281
|
}
|
|
961
|
-
function
|
|
962
|
-
addKicker(slide, props.kicker);
|
|
963
|
-
addTitle(slide, props.title);
|
|
964
|
-
const images = (props.images || []).slice(0, 4);
|
|
965
|
-
const positions = [
|
|
966
|
-
{ x: 0.8, y: 2.5 },
|
|
967
|
-
{ x: 5.2, y: 2.5 },
|
|
968
|
-
{ x: 0.8, y: 4.0 },
|
|
969
|
-
{ x: 5.2, y: 4.0 },
|
|
970
|
-
];
|
|
971
|
-
images.forEach((image, index) => {
|
|
972
|
-
const pos = positions[index];
|
|
973
|
-
if (!pos)
|
|
974
|
-
return;
|
|
975
|
-
if (image.url)
|
|
976
|
-
addImageMaybe(slide, image.url, pos.x, pos.y, 4.0, 1.3);
|
|
977
|
-
if (image.caption) {
|
|
978
|
-
slide.addText(image.caption, {
|
|
979
|
-
x: pos.x, y: pos.y + 1.35, w: 4.0, h: 0.25,
|
|
980
|
-
fontSize: 12, color: COLORS.secondary, align: 'center',
|
|
981
|
-
fontFace: FONTS.body,
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
function renderGalleryV2(slide, props) {
|
|
987
|
-
addKicker(slide, props.kicker);
|
|
988
|
-
addTitle(slide, props.title);
|
|
989
|
-
const images = (props.images || []).slice(0, 6);
|
|
990
|
-
const cols = 3;
|
|
991
|
-
const itemWidth = 2.6;
|
|
992
|
-
const itemHeight = 1.5;
|
|
993
|
-
const gapX = 0.33;
|
|
994
|
-
const startX = 0.8;
|
|
995
|
-
const startY = 2.4;
|
|
996
|
-
images.forEach((image, index) => {
|
|
997
|
-
const col = index % cols;
|
|
998
|
-
const row = Math.floor(index / cols);
|
|
999
|
-
const x = startX + col * (itemWidth + gapX);
|
|
1000
|
-
const y = startY + row * (itemHeight + 0.45);
|
|
1001
|
-
if (image.url)
|
|
1002
|
-
addImageMaybe(slide, image.url, x, y, itemWidth, itemHeight);
|
|
1003
|
-
if (image.caption) {
|
|
1004
|
-
slide.addText(image.caption, {
|
|
1005
|
-
x, y: y + itemHeight + 0.05, w: itemWidth, h: 0.25,
|
|
1006
|
-
fontSize: 12, color: COLORS.secondary, align: 'center',
|
|
1007
|
-
fontFace: FONTS.body,
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
function renderImageV1(slide, props) {
|
|
2282
|
+
export function renderImageV1(slide, props) {
|
|
1013
2283
|
if (props.imageUrl) {
|
|
1014
2284
|
addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
|
|
1015
2285
|
slide.addShape('rect', {
|
|
@@ -1030,7 +2300,7 @@ function renderImageV1(slide, props) {
|
|
|
1030
2300
|
});
|
|
1031
2301
|
}
|
|
1032
2302
|
}
|
|
1033
|
-
function renderSwotV1(slide, props) {
|
|
2303
|
+
export function renderSwotV1(slide, props) {
|
|
1034
2304
|
addKicker(slide, props.kicker);
|
|
1035
2305
|
addTitle(slide, props.title);
|
|
1036
2306
|
render2x2Grid(slide, [
|
|
@@ -1040,7 +2310,7 @@ function renderSwotV1(slide, props) {
|
|
|
1040
2310
|
{ title: '威胁 Threats', text: props.threat || '' },
|
|
1041
2311
|
]);
|
|
1042
2312
|
}
|
|
1043
|
-
function renderPestV1(slide, props) {
|
|
2313
|
+
export function renderPestV1(slide, props) {
|
|
1044
2314
|
addKicker(slide, props.kicker);
|
|
1045
2315
|
addTitle(slide, props.title);
|
|
1046
2316
|
render2x2Grid(slide, [
|
|
@@ -1078,800 +2348,610 @@ function render2x2Grid(slide, cells) {
|
|
|
1078
2348
|
});
|
|
1079
2349
|
});
|
|
1080
2350
|
}
|
|
1081
|
-
function
|
|
1082
|
-
if (props.kicker) {
|
|
1083
|
-
slide.addText(props.kicker, {
|
|
1084
|
-
x: 1, y: 2.0, w: 8, h: 0.4,
|
|
1085
|
-
fontSize: 14, color: COLORS.accent, align: 'center',
|
|
1086
|
-
fontFace: FONTS.mono,
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
slide.addText(props.title, {
|
|
1090
|
-
x: 1, y: 2.5, w: 8, h: 1.0,
|
|
1091
|
-
fontSize: 48, color: COLORS.primary, bold: true, align: 'center',
|
|
1092
|
-
fontFace: FONTS.heading,
|
|
1093
|
-
});
|
|
1094
|
-
if (props.subtitle) {
|
|
1095
|
-
slide.addText(props.subtitle, {
|
|
1096
|
-
x: 1.5, y: 3.6, w: 7, h: 0.7,
|
|
1097
|
-
fontSize: 22, color: COLORS.secondary, align: 'center',
|
|
1098
|
-
fontFace: FONTS.body,
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
if (props.cta) {
|
|
1102
|
-
slide.addShape('roundRect', {
|
|
1103
|
-
x: 3.5, y: 4.6, w: 3, h: 0.6,
|
|
1104
|
-
fill: { color: COLORS.accent },
|
|
1105
|
-
rectRadius: 0.3,
|
|
1106
|
-
});
|
|
1107
|
-
slide.addText(props.cta, {
|
|
1108
|
-
x: 3.5, y: 4.6, w: 3, h: 0.6,
|
|
1109
|
-
fontSize: 16, color: COLORS.white, bold: true, align: 'center', valign: 'middle',
|
|
1110
|
-
fontFace: FONTS.body,
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
function renderClosingV2(slide, props) {
|
|
1115
|
-
slide.addText(props.title, {
|
|
1116
|
-
x: 1, y: 2.2, w: 8, h: 1.0,
|
|
1117
|
-
fontSize: 48, color: COLORS.primary, bold: true, align: 'center',
|
|
1118
|
-
fontFace: FONTS.heading,
|
|
1119
|
-
});
|
|
1120
|
-
if (props.subtitle) {
|
|
1121
|
-
slide.addText(props.subtitle, {
|
|
1122
|
-
x: 1.5, y: 3.3, w: 7, h: 0.6,
|
|
1123
|
-
fontSize: 22, color: COLORS.secondary, align: 'center',
|
|
1124
|
-
fontFace: FONTS.body,
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
const lines = [props.contact, props.email, props.link].filter(Boolean);
|
|
1128
|
-
let y = 4.2;
|
|
1129
|
-
lines.forEach((line) => {
|
|
1130
|
-
slide.addText(line, {
|
|
1131
|
-
x: 1, y, w: 8, h: 0.35,
|
|
1132
|
-
fontSize: 16, color: COLORS.accent, align: 'center',
|
|
1133
|
-
fontFace: FONTS.mono,
|
|
1134
|
-
});
|
|
1135
|
-
y += 0.45;
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
function renderTimelineV2(slide, props) {
|
|
1139
|
-
addKicker(slide, props.kicker);
|
|
1140
|
-
addTitle(slide, props.title ?? 'Timeline');
|
|
1141
|
-
const milestones = props.milestones ?? [];
|
|
1142
|
-
const maxItems = 5;
|
|
1143
|
-
let y = 2.6;
|
|
1144
|
-
milestones.slice(0, maxItems).forEach((milestone, index) => {
|
|
1145
|
-
const isLast = index === Math.min(milestones.length, maxItems) - 1;
|
|
1146
|
-
// marker dot
|
|
1147
|
-
slide.addShape('ellipse', {
|
|
1148
|
-
x: 0.9, y: y + 0.16, w: 0.16, h: 0.16,
|
|
1149
|
-
fill: { color: COLORS.accent },
|
|
1150
|
-
});
|
|
1151
|
-
if (!isLast) {
|
|
1152
|
-
slide.addShape('rect', {
|
|
1153
|
-
x: 0.97, y: y + 0.42, w: 0.02, h: 1.0,
|
|
1154
|
-
fill: { color: COLORS.border },
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
const dateText = milestone.date ?? '';
|
|
1158
|
-
if (dateText) {
|
|
1159
|
-
slide.addText(dateText, {
|
|
1160
|
-
x: 1.3, y, w: 2.0, h: 0.35,
|
|
1161
|
-
fontSize: 14, color: COLORS.accent, align: 'left', valign: 'top',
|
|
1162
|
-
fontFace: FONTS.mono,
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
if (milestone.title) {
|
|
1166
|
-
slide.addText(milestone.title, {
|
|
1167
|
-
x: 1.3, y: y + 0.35, w: 7.8, h: 0.4,
|
|
1168
|
-
fontSize: 20, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
1169
|
-
fontFace: FONTS.heading,
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
if (milestone.description) {
|
|
1173
|
-
slide.addText(milestone.description, {
|
|
1174
|
-
x: 1.3, y: y + 0.75, w: 7.8, h: 0.55,
|
|
1175
|
-
fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
1176
|
-
fontFace: FONTS.body,
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
y += 1.35;
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
function renderRoadmapV2(slide, props) {
|
|
1183
|
-
addKicker(slide, props.kicker);
|
|
1184
|
-
addTitle(slide, props.title ?? 'Roadmap');
|
|
1185
|
-
const phases = props.phases ?? [];
|
|
1186
|
-
const maxPhases = 4;
|
|
1187
|
-
const cardW = 2.1;
|
|
1188
|
-
const startX = 0.7;
|
|
1189
|
-
const y = 2.6;
|
|
1190
|
-
const h = 3.4;
|
|
1191
|
-
phases.slice(0, maxPhases).forEach((phase, index) => {
|
|
1192
|
-
const x = startX + index * (cardW + 0.25);
|
|
1193
|
-
slide.addShape('rect', {
|
|
1194
|
-
x, y, w: cardW, h,
|
|
1195
|
-
fill: { color: COLORS.surfaceElevated },
|
|
1196
|
-
line: { color: COLORS.border, width: 1 },
|
|
1197
|
-
rectRadius: 0.08,
|
|
1198
|
-
});
|
|
1199
|
-
slide.addText(phase.phase ?? `Phase ${index + 1}`, {
|
|
1200
|
-
x: x + 0.12, y: y + 0.2, w: cardW - 0.24, h: 0.4,
|
|
1201
|
-
fontSize: 18, color: COLORS.accent, bold: true, align: 'left', valign: 'top',
|
|
1202
|
-
fontFace: FONTS.heading,
|
|
1203
|
-
});
|
|
1204
|
-
const goals = (phase.goals ?? []).slice(0, 5);
|
|
1205
|
-
let goalY = y + 0.7;
|
|
1206
|
-
goals.forEach((goal) => {
|
|
1207
|
-
slide.addText(`• ${goal}`, {
|
|
1208
|
-
x: x + 0.16, y: goalY, w: cardW - 0.32, h: 0.45,
|
|
1209
|
-
fontSize: 13, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
1210
|
-
fontFace: FONTS.body,
|
|
1211
|
-
});
|
|
1212
|
-
goalY += 0.45;
|
|
1213
|
-
});
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
|
-
function renderPricingV2(slide, props) {
|
|
1217
|
-
addKicker(slide, props.kicker);
|
|
1218
|
-
addTitle(slide, props.title ?? 'Pricing');
|
|
1219
|
-
const plans = props.plans ?? [];
|
|
1220
|
-
const maxPlans = 3;
|
|
1221
|
-
const cardW = 2.6;
|
|
1222
|
-
const startX = 0.9;
|
|
1223
|
-
const y = 2.6;
|
|
1224
|
-
const h = 3.8;
|
|
1225
|
-
plans.slice(0, maxPlans).forEach((plan, index) => {
|
|
1226
|
-
const x = startX + index * (cardW + 0.25);
|
|
1227
|
-
const isHighlighted = plan.highlighted;
|
|
1228
|
-
slide.addShape('rect', {
|
|
1229
|
-
x, y, w: cardW, h,
|
|
1230
|
-
fill: { color: isHighlighted ? COLORS.accent : COLORS.surfaceElevated },
|
|
1231
|
-
line: { color: isHighlighted ? COLORS.accent : COLORS.border, width: 1 },
|
|
1232
|
-
rectRadius: 0.08,
|
|
1233
|
-
});
|
|
1234
|
-
slide.addText(plan.name ?? `Plan ${index + 1}`, {
|
|
1235
|
-
x: x + 0.15, y: y + 0.25, w: cardW - 0.3, h: 0.4,
|
|
1236
|
-
fontSize: 20, color: isHighlighted ? 'FFFFFF' : COLORS.primary, bold: true, align: 'center', valign: 'top',
|
|
1237
|
-
fontFace: FONTS.heading,
|
|
1238
|
-
});
|
|
1239
|
-
slide.addText(`${plan.price ?? ''} ${plan.period ?? ''}`, {
|
|
1240
|
-
x: x + 0.15, y: y + 0.75, w: cardW - 0.3, h: 0.5,
|
|
1241
|
-
fontSize: 24, color: isHighlighted ? 'FFFFFF' : COLORS.accent, bold: true, align: 'center', valign: 'top',
|
|
1242
|
-
fontFace: FONTS.heading,
|
|
1243
|
-
});
|
|
1244
|
-
const features = (plan.features ?? []).slice(0, 5);
|
|
1245
|
-
let featY = y + 1.4;
|
|
1246
|
-
features.forEach((feature) => {
|
|
1247
|
-
slide.addText(`• ${feature}`, {
|
|
1248
|
-
x: x + 0.2, y: featY, w: cardW - 0.4, h: 0.4,
|
|
1249
|
-
fontSize: 13, color: isHighlighted ? 'FFFFFF' : COLORS.secondary, align: 'left', valign: 'top',
|
|
1250
|
-
fontFace: FONTS.body,
|
|
1251
|
-
});
|
|
1252
|
-
featY += 0.4;
|
|
1253
|
-
});
|
|
1254
|
-
});
|
|
1255
|
-
}
|
|
1256
|
-
function renderFeatureV2(slide, props) {
|
|
1257
|
-
addKicker(slide, props.kicker);
|
|
1258
|
-
addTitle(slide, props.title ?? 'Features');
|
|
1259
|
-
const features = props.features ?? [];
|
|
1260
|
-
const maxFeatures = 3;
|
|
1261
|
-
const cardW = 2.8;
|
|
1262
|
-
const startX = 0.7;
|
|
1263
|
-
const y = 2.6;
|
|
1264
|
-
const h = 3.4;
|
|
1265
|
-
features.slice(0, maxFeatures).forEach((feature, index) => {
|
|
1266
|
-
const x = startX + index * (cardW + 0.25);
|
|
1267
|
-
slide.addShape('rect', {
|
|
1268
|
-
x, y, w: cardW, h,
|
|
1269
|
-
fill: { color: COLORS.surfaceElevated },
|
|
1270
|
-
line: { color: COLORS.border, width: 1 },
|
|
1271
|
-
rectRadius: 0.08,
|
|
1272
|
-
});
|
|
1273
|
-
slide.addText(feature.icon ?? '◆', {
|
|
1274
|
-
x: x + 0.2, y: y + 0.25, w: cardW - 0.4, h: 0.5,
|
|
1275
|
-
fontSize: 28, color: COLORS.accent, align: 'left', valign: 'top',
|
|
1276
|
-
fontFace: FONTS.body,
|
|
1277
|
-
});
|
|
1278
|
-
slide.addText(feature.title ?? `Feature ${index + 1}`, {
|
|
1279
|
-
x: x + 0.2, y: y + 0.85, w: cardW - 0.4, h: 0.4,
|
|
1280
|
-
fontSize: 18, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
1281
|
-
fontFace: FONTS.heading,
|
|
1282
|
-
});
|
|
1283
|
-
slide.addText(feature.description ?? '', {
|
|
1284
|
-
x: x + 0.2, y: y + 1.35, w: cardW - 0.4, h: 1.8,
|
|
1285
|
-
fontSize: 14, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
1286
|
-
fontFace: FONTS.body,
|
|
1287
|
-
});
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1290
|
-
function renderTeamV2(slide, props) {
|
|
1291
|
-
addKicker(slide, props.kicker);
|
|
1292
|
-
addTitle(slide, props.title ?? 'Team');
|
|
1293
|
-
const members = props.members ?? [];
|
|
1294
|
-
const maxMembers = 4;
|
|
1295
|
-
const cardW = 2.2;
|
|
1296
|
-
const startX = 0.6;
|
|
1297
|
-
const y = 2.5;
|
|
1298
|
-
const h = 3.8;
|
|
1299
|
-
members.slice(0, maxMembers).forEach((member, index) => {
|
|
1300
|
-
const x = startX + index * (cardW + 0.25);
|
|
1301
|
-
slide.addShape('rect', {
|
|
1302
|
-
x, y, w: cardW, h,
|
|
1303
|
-
fill: { color: COLORS.surfaceElevated },
|
|
1304
|
-
line: { color: COLORS.border, width: 1 },
|
|
1305
|
-
rectRadius: 0.08,
|
|
1306
|
-
});
|
|
1307
|
-
slide.addShape('ellipse', {
|
|
1308
|
-
x: x + cardW / 2 - 0.35, y: y + 0.25, w: 0.7, h: 0.7,
|
|
1309
|
-
fill: { color: COLORS.border },
|
|
1310
|
-
});
|
|
1311
|
-
slide.addText(member.name ?? `Member ${index + 1}`, {
|
|
1312
|
-
x: x + 0.15, y: y + 1.05, w: cardW - 0.3, h: 0.35,
|
|
1313
|
-
fontSize: 17, color: COLORS.primary, bold: true, align: 'center', valign: 'top',
|
|
1314
|
-
fontFace: FONTS.heading,
|
|
1315
|
-
});
|
|
1316
|
-
slide.addText(member.role ?? '', {
|
|
1317
|
-
x: x + 0.15, y: y + 1.45, w: cardW - 0.3, h: 0.3,
|
|
1318
|
-
fontSize: 13, color: COLORS.accent, align: 'center', valign: 'top',
|
|
1319
|
-
fontFace: FONTS.body,
|
|
1320
|
-
});
|
|
1321
|
-
slide.addText(member.bio ?? '', {
|
|
1322
|
-
x: x + 0.15, y: y + 1.85, w: cardW - 0.3, h: 1.7,
|
|
1323
|
-
fontSize: 12, color: COLORS.secondary, align: 'center', valign: 'top',
|
|
1324
|
-
fontFace: FONTS.body,
|
|
1325
|
-
});
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
function renderMetricV3(slide, props) {
|
|
2351
|
+
export function renderTheme01GalleryV1(slide, props) {
|
|
1329
2352
|
addKicker(slide, props.kicker);
|
|
1330
|
-
addTitle(slide, props.title ?? '
|
|
1331
|
-
const
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
2353
|
+
addTitle(slide, props.title ?? 'Gallery');
|
|
2354
|
+
const images = (props.images || []).slice(0, 4);
|
|
2355
|
+
const positions = [
|
|
2356
|
+
{ x: 0.8, y: 2.5 },
|
|
2357
|
+
{ x: 5.2, y: 2.5 },
|
|
2358
|
+
{ x: 0.8, y: 4.0 },
|
|
2359
|
+
{ x: 5.2, y: 4.0 },
|
|
2360
|
+
];
|
|
2361
|
+
images.forEach((image, index) => {
|
|
2362
|
+
const pos = positions[index];
|
|
2363
|
+
if (!pos)
|
|
2364
|
+
return;
|
|
1339
2365
|
slide.addShape('rect', {
|
|
1340
|
-
x, y, w:
|
|
2366
|
+
x: pos.x, y: pos.y, w: 4.0, h: 1.3,
|
|
1341
2367
|
fill: { color: COLORS.surfaceElevated },
|
|
1342
2368
|
line: { color: COLORS.border, width: 1 },
|
|
1343
2369
|
rectRadius: 0.08,
|
|
1344
2370
|
});
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
x: x + 0.25, y: y + 0.85, w: cardW - 0.5, h: 0.8,
|
|
1352
|
-
fontSize: 48, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
1353
|
-
fontFace: FONTS.heading,
|
|
1354
|
-
});
|
|
1355
|
-
if (metric.change) {
|
|
1356
|
-
slide.addText(metric.change, {
|
|
1357
|
-
x: x + 0.25, y: y + 1.85, w: cardW - 0.5, h: 0.4,
|
|
1358
|
-
fontSize: 16, color: COLORS.accent, bold: true, align: 'left', valign: 'top',
|
|
2371
|
+
if (image.url)
|
|
2372
|
+
addImageMaybe(slide, image.url, pos.x + 0.08, pos.y + 0.08, 3.84, 0.9);
|
|
2373
|
+
if (image.caption) {
|
|
2374
|
+
slide.addText(image.caption, {
|
|
2375
|
+
x: pos.x, y: pos.y + 1.05, w: 4.0, h: 0.25,
|
|
2376
|
+
fontSize: 12, color: COLORS.secondary, align: 'center',
|
|
1359
2377
|
fontFace: FONTS.body,
|
|
1360
2378
|
});
|
|
1361
2379
|
}
|
|
1362
2380
|
});
|
|
1363
2381
|
}
|
|
1364
|
-
function
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
if (props.kicker) {
|
|
1376
|
-
slide.addText(props.kicker, {
|
|
1377
|
-
x: 0.8, y: 1.6, w: 4.0, h: 0.4,
|
|
1378
|
-
fontSize: 14, color: COLORS.accent, align: 'left',
|
|
1379
|
-
fontFace: FONTS.mono,
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
slide.addText(props.title, {
|
|
1383
|
-
x: 0.8, y: 2.1, w: 4.2, h: 1.2,
|
|
1384
|
-
fontSize: 44, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
1385
|
-
fontFace: FONTS.heading,
|
|
1386
|
-
});
|
|
1387
|
-
if (props.subtitle) {
|
|
1388
|
-
slide.addText(props.subtitle, {
|
|
1389
|
-
x: 0.8, y: 3.4, w: 4.2, h: 0.8,
|
|
1390
|
-
fontSize: 18, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
2382
|
+
export function renderTheme01TableV1(slide, props) {
|
|
2383
|
+
addKicker(slide, props.kicker);
|
|
2384
|
+
addTitle(slide, props.title ?? 'Table');
|
|
2385
|
+
const headers = props.headers || [];
|
|
2386
|
+
const rows = (props.rows || []).slice(0, 6);
|
|
2387
|
+
if (rows.length === 0) {
|
|
2388
|
+
slide.addText('(暂无表格数据)', {
|
|
2389
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
2390
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
1391
2391
|
fontFace: FONTS.body,
|
|
1392
2392
|
});
|
|
1393
|
-
}
|
|
1394
|
-
if (props.date) {
|
|
1395
|
-
slide.addText(props.date, {
|
|
1396
|
-
x: 0.8, y: 4.5, w: 4.0, h: 0.3,
|
|
1397
|
-
fontSize: 13, color: COLORS.secondary, align: 'left',
|
|
1398
|
-
fontFace: FONTS.mono,
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
function renderContentV4(slide, props) {
|
|
1403
|
-
addKicker(slide, props.kicker);
|
|
1404
|
-
addTitle(slide, props.title);
|
|
1405
|
-
const cards = (props.cards || []).slice(0, 3);
|
|
1406
|
-
if (cards.length === 0)
|
|
1407
2393
|
return;
|
|
1408
|
-
|
|
1409
|
-
const
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
2394
|
+
}
|
|
2395
|
+
const colCount = Math.max(headers.length, rows[0]?.length || 1);
|
|
2396
|
+
const tableW = 8.4;
|
|
2397
|
+
const startX = 0.8;
|
|
2398
|
+
const startY = 2.5;
|
|
2399
|
+
const rowH = 0.45;
|
|
2400
|
+
const colW = tableW / colCount;
|
|
2401
|
+
// Header
|
|
2402
|
+
headers.forEach((header, index) => {
|
|
2403
|
+
const x = startX + index * colW;
|
|
1413
2404
|
slide.addShape('rect', {
|
|
1414
|
-
x, y:
|
|
2405
|
+
x, y: startY, w: colW, h: rowH,
|
|
1415
2406
|
fill: { color: COLORS.surfaceElevated },
|
|
1416
2407
|
line: { color: COLORS.border, width: 1 },
|
|
1417
2408
|
});
|
|
1418
|
-
slide.addText(
|
|
1419
|
-
x
|
|
1420
|
-
fontSize:
|
|
2409
|
+
slide.addText(header, {
|
|
2410
|
+
x, y: startY, w: colW, h: rowH,
|
|
2411
|
+
fontSize: 12, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
|
|
1421
2412
|
fontFace: FONTS.heading,
|
|
1422
2413
|
});
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
2414
|
+
});
|
|
2415
|
+
// Rows
|
|
2416
|
+
rows.forEach((row, rowIndex) => {
|
|
2417
|
+
const y = startY + rowH * (rowIndex + 1);
|
|
2418
|
+
const bg = rowIndex % 2 === 0 ? COLORS.surface : COLORS.surfaceElevated;
|
|
2419
|
+
row.forEach((cell, colIndex) => {
|
|
2420
|
+
const x = startX + colIndex * colW;
|
|
2421
|
+
slide.addShape('rect', {
|
|
2422
|
+
x, y, w: colW, h: rowH,
|
|
2423
|
+
fill: { color: bg },
|
|
2424
|
+
line: { color: COLORS.border, width: 1 },
|
|
2425
|
+
});
|
|
2426
|
+
slide.addText(cell ?? '', {
|
|
2427
|
+
x: x + 0.06, y, w: colW - 0.12, h: rowH,
|
|
2428
|
+
fontSize: 11, color: props.highlightFirstColumn && colIndex === 0 ? COLORS.primary : COLORS.secondary,
|
|
2429
|
+
bold: props.highlightFirstColumn && colIndex === 0,
|
|
2430
|
+
align: colIndex === 0 ? 'left' : 'center', valign: 'middle',
|
|
2431
|
+
fontFace: FONTS.body,
|
|
2432
|
+
});
|
|
2433
|
+
});
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
export function renderTheme01TagsV1(slide, props) {
|
|
2437
|
+
addKicker(slide, props.kicker);
|
|
2438
|
+
addTitle(slide, props.title ?? 'Tags');
|
|
2439
|
+
const tags = (props.tags || []).slice(0, 18);
|
|
2440
|
+
if (tags.length === 0) {
|
|
2441
|
+
slide.addText('(暂无标签)', {
|
|
2442
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
2443
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
2444
|
+
fontFace: FONTS.body,
|
|
2445
|
+
});
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
const toneColors = {
|
|
2449
|
+
positive: { text: '10B981', bg: 'D1FAE5' },
|
|
2450
|
+
negative: { text: 'EF4444', bg: 'FEE2E2' },
|
|
2451
|
+
accent: { text: COLORS.accent, bg: 'DBEAFE' },
|
|
2452
|
+
};
|
|
2453
|
+
let x = 0.8;
|
|
2454
|
+
let y = 2.5;
|
|
2455
|
+
const maxW = 8.4;
|
|
2456
|
+
const gap = 0.15;
|
|
2457
|
+
const tagH = 0.42;
|
|
2458
|
+
tags.forEach((tag) => {
|
|
2459
|
+
const label = tag.label ?? '';
|
|
2460
|
+
const valueText = tag.value !== undefined && tag.value > 0 ? ` ${tag.value}` : '';
|
|
2461
|
+
const text = `${label}${valueText}`;
|
|
2462
|
+
const textW = Math.min(Math.max(text.length * 0.09 + 0.3, 0.7), 2.2);
|
|
2463
|
+
const tone = toneColors[tag.tone ?? ''] ?? { text: COLORS.secondary, bg: COLORS.surfaceElevated };
|
|
2464
|
+
const color = tone.text;
|
|
2465
|
+
const bgColor = tone.bg;
|
|
2466
|
+
if (x + textW > 0.8 + maxW) {
|
|
2467
|
+
x = 0.8;
|
|
2468
|
+
y += tagH + gap;
|
|
2469
|
+
}
|
|
2470
|
+
slide.addShape('roundRect', {
|
|
2471
|
+
x, y, w: textW, h: tagH,
|
|
2472
|
+
fill: { color: bgColor },
|
|
2473
|
+
line: { color: tag.tone ? color : COLORS.border, width: 1 },
|
|
2474
|
+
rectRadius: 0.21,
|
|
2475
|
+
});
|
|
2476
|
+
slide.addText(text, {
|
|
2477
|
+
x, y, w: textW, h: tagH,
|
|
2478
|
+
fontSize: 11, color, bold: true, align: 'center', valign: 'middle',
|
|
1426
2479
|
fontFace: FONTS.body,
|
|
1427
2480
|
});
|
|
2481
|
+
x += textW + gap;
|
|
1428
2482
|
});
|
|
1429
2483
|
}
|
|
1430
|
-
function
|
|
2484
|
+
export function renderTheme01FilmstripV1(slide, props) {
|
|
1431
2485
|
addKicker(slide, props.kicker);
|
|
1432
|
-
addTitle(slide, props.title);
|
|
1433
|
-
const
|
|
1434
|
-
if (
|
|
2486
|
+
addTitle(slide, props.title ?? 'Filmstrip');
|
|
2487
|
+
const images = (props.images || []).slice(0, 5);
|
|
2488
|
+
if (images.length === 0) {
|
|
2489
|
+
slide.addText('(暂无影像)', {
|
|
2490
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
2491
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
2492
|
+
fontFace: FONTS.body,
|
|
2493
|
+
});
|
|
1435
2494
|
return;
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
2495
|
+
}
|
|
2496
|
+
const gap = 0.2;
|
|
2497
|
+
const frameW = (8.4 - gap * (images.length - 1)) / images.length;
|
|
2498
|
+
const startX = 0.8;
|
|
2499
|
+
const y = 2.5;
|
|
2500
|
+
const imgH = 2.2;
|
|
2501
|
+
images.forEach((image, index) => {
|
|
2502
|
+
const x = startX + index * (frameW + gap);
|
|
1439
2503
|
slide.addShape('rect', {
|
|
1440
|
-
x
|
|
2504
|
+
x, y, w: frameW, h: imgH + 0.5,
|
|
1441
2505
|
fill: { color: COLORS.surfaceElevated },
|
|
1442
2506
|
line: { color: COLORS.border, width: 1 },
|
|
2507
|
+
rectRadius: 0.08,
|
|
1443
2508
|
});
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
x: x + 0.2, y: 3.9, w: itemW - 0.4, h: 0.4,
|
|
1452
|
-
fontSize: 14, color: COLORS.secondary, align: 'center', valign: 'top',
|
|
1453
|
-
fontFace: FONTS.body,
|
|
1454
|
-
});
|
|
1455
|
-
if (stat.change) {
|
|
1456
|
-
slide.addText(stat.change, {
|
|
1457
|
-
x: x + 0.2, y: 4.35, w: itemW - 0.4, h: 0.35,
|
|
1458
|
-
fontSize: 13, color: COLORS.accent, align: 'center',
|
|
1459
|
-
fontFace: FONTS.mono,
|
|
2509
|
+
if (image.url)
|
|
2510
|
+
addImageMaybe(slide, image.url, x + 0.06, y + 0.06, frameW - 0.12, imgH);
|
|
2511
|
+
if (image.caption) {
|
|
2512
|
+
slide.addText(image.caption, {
|
|
2513
|
+
x, y: y + imgH + 0.08, w: frameW, h: 0.35,
|
|
2514
|
+
fontSize: 11, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
2515
|
+
fontFace: FONTS.body,
|
|
1460
2516
|
});
|
|
1461
2517
|
}
|
|
1462
2518
|
});
|
|
1463
2519
|
}
|
|
1464
|
-
function
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
2520
|
+
export function renderTheme01RoadmapV1(slide, props) {
|
|
2521
|
+
const phases = (props.phases || []).map((p) => ({
|
|
2522
|
+
title: p.phase || '',
|
|
2523
|
+
description: (p.items || []).join('\n'),
|
|
2524
|
+
}));
|
|
2525
|
+
renderRoadmapV1(slide, { kicker: props.kicker, title: props.title ?? '路线图', phases });
|
|
2526
|
+
}
|
|
2527
|
+
export function renderTheme01MetricBig(slide, props) {
|
|
2528
|
+
const hasInsight = props.showInsight !== false && !!props.insight && (!!props.insight.value || !!props.insight.label || !!props.insight.description);
|
|
2529
|
+
const mainW = hasInsight ? 6.8 : 8.4;
|
|
2530
|
+
const mainX = 0.8;
|
|
2531
|
+
addKicker(slide, props.kicker);
|
|
2532
|
+
addTitle(slide, props.title ?? '核心指标', mainX, 1.3, mainW);
|
|
2533
|
+
if (props.subtitle) {
|
|
2534
|
+
slide.addText(props.subtitle, {
|
|
2535
|
+
x: mainX, y: 2.05, w: mainW, h: 0.35,
|
|
2536
|
+
fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
2537
|
+
fontFace: FONTS.body,
|
|
1480
2538
|
});
|
|
1481
2539
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
2540
|
+
const valueText = [props.value, props.unit].filter(Boolean).join(' ') || '0';
|
|
2541
|
+
slide.addText(valueText, {
|
|
2542
|
+
x: mainX, y: 2.7, w: mainW, h: 1.2,
|
|
2543
|
+
fontSize: hasInsight ? 60 : 72, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
|
|
1485
2544
|
fontFace: FONTS.heading,
|
|
1486
2545
|
});
|
|
1487
|
-
if (props.
|
|
1488
|
-
slide.addText(props.
|
|
1489
|
-
x: 0.
|
|
1490
|
-
fontSize:
|
|
2546
|
+
if (props.context) {
|
|
2547
|
+
slide.addText(props.context, {
|
|
2548
|
+
x: mainX + (hasInsight ? 0.7 : 0.7), y: 4.0, w: hasInsight ? mainW - 1.4 : 7.0, h: 0.6,
|
|
2549
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'top',
|
|
1491
2550
|
fontFace: FONTS.body,
|
|
1492
2551
|
});
|
|
1493
2552
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
2553
|
+
const metrics = (props.metrics || []).filter((m) => m != null && (m.value || m.label)).slice(0, 4);
|
|
2554
|
+
if (metrics.length > 0) {
|
|
2555
|
+
const count = metrics.length;
|
|
2556
|
+
const gap = 0.25;
|
|
2557
|
+
const cardW = (mainW - gap * (count - 1)) / count;
|
|
2558
|
+
const startX = mainX;
|
|
2559
|
+
const y = 4.85;
|
|
2560
|
+
const h = 0.65;
|
|
2561
|
+
metrics.forEach((metric, index) => {
|
|
2562
|
+
const x = startX + index * (cardW + gap);
|
|
2563
|
+
const bg = metric.accent ? COLORS.accent : COLORS.surfaceElevated;
|
|
2564
|
+
const textColor = metric.accent ? COLORS.white : COLORS.primary;
|
|
2565
|
+
slide.addShape('rect', {
|
|
2566
|
+
x, y, w: cardW, h,
|
|
2567
|
+
fill: { color: bg },
|
|
2568
|
+
line: { color: COLORS.border, width: 1 },
|
|
2569
|
+
rectRadius: 0.1,
|
|
2570
|
+
});
|
|
2571
|
+
slide.addText(metric.value || '', {
|
|
2572
|
+
x: x + 0.1, y: y + 0.08, w: cardW - 0.2, h: 0.32,
|
|
2573
|
+
fontSize: 18, color: textColor, bold: true, align: 'center', valign: 'top',
|
|
2574
|
+
fontFace: FONTS.heading,
|
|
2575
|
+
});
|
|
2576
|
+
slide.addText(metric.label || '', {
|
|
2577
|
+
x: x + 0.1, y: y + 0.36, w: cardW - 0.2, h: 0.24,
|
|
2578
|
+
fontSize: 11, color: metric.accent ? COLORS.white : COLORS.secondary, align: 'center', valign: 'top',
|
|
2579
|
+
fontFace: FONTS.body,
|
|
2580
|
+
});
|
|
1512
2581
|
});
|
|
1513
2582
|
}
|
|
1514
|
-
if (props.
|
|
1515
|
-
slide.addText(props.
|
|
1516
|
-
x:
|
|
1517
|
-
fontSize:
|
|
2583
|
+
if (props.footnote) {
|
|
2584
|
+
slide.addText(props.footnote, {
|
|
2585
|
+
x: mainX, y: 5.7, w: mainW, h: 0.25,
|
|
2586
|
+
fontSize: 11, color: COLORS.secondary, align: 'left',
|
|
1518
2587
|
fontFace: FONTS.mono,
|
|
1519
2588
|
});
|
|
1520
2589
|
}
|
|
2590
|
+
if (hasInsight) {
|
|
2591
|
+
renderInsightPanel(slide, props.insight, 8.0, 2.3, 2.4, 3.2);
|
|
2592
|
+
}
|
|
1521
2593
|
}
|
|
1522
|
-
function
|
|
2594
|
+
export function renderTheme01ChartDonut(slide, props) {
|
|
1523
2595
|
addKicker(slide, props.kicker);
|
|
1524
|
-
addTitle(slide, props.title);
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
const gap = 0.18;
|
|
1530
|
-
const startY = 2.6;
|
|
1531
|
-
steps.forEach((step, index) => {
|
|
1532
|
-
const y = startY + index * (stepH + gap);
|
|
1533
|
-
slide.addShape('rect', {
|
|
1534
|
-
x: 0.8, y, w: 8.4, h: stepH,
|
|
1535
|
-
fill: { color: COLORS.surfaceElevated },
|
|
1536
|
-
line: { color: COLORS.border, width: 1 },
|
|
1537
|
-
});
|
|
1538
|
-
slide.addShape('rect', {
|
|
1539
|
-
x: 0.8, y, w: 0.6, h: stepH,
|
|
1540
|
-
fill: { color: COLORS.accent },
|
|
1541
|
-
});
|
|
1542
|
-
slide.addText(String(index + 1).padStart(2, '0'), {
|
|
1543
|
-
x: 0.8, y, w: 0.6, h: stepH,
|
|
1544
|
-
fontSize: 14, color: 'FFFFFF', bold: true, align: 'center', valign: 'middle',
|
|
1545
|
-
fontFace: FONTS.mono,
|
|
1546
|
-
});
|
|
1547
|
-
slide.addText(step.title || '', {
|
|
1548
|
-
x: 1.6, y: y + 0.12, w: 7.4, h: 0.3,
|
|
1549
|
-
fontSize: 16, color: COLORS.primary, bold: true, align: 'left',
|
|
1550
|
-
fontFace: FONTS.heading,
|
|
1551
|
-
});
|
|
1552
|
-
slide.addText(step.description || '', {
|
|
1553
|
-
x: 1.6, y: y + 0.42, w: 7.4, h: 0.35,
|
|
1554
|
-
fontSize: 12, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
2596
|
+
addTitle(slide, props.title ?? '环形图');
|
|
2597
|
+
if (props.subtitle) {
|
|
2598
|
+
slide.addText(props.subtitle, {
|
|
2599
|
+
x: 0.8, y: 2.05, w: 8.4, h: 0.35,
|
|
2600
|
+
fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
1555
2601
|
fontFace: FONTS.body,
|
|
1556
2602
|
});
|
|
1557
|
-
});
|
|
1558
|
-
}
|
|
1559
|
-
function renderFeatureV3(slide, props) {
|
|
1560
|
-
if (props.image) {
|
|
1561
|
-
addImageMaybe(slide, props.image, 0, 0, 4.5, 5.625);
|
|
1562
2603
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
2604
|
+
const rawSegments = (props.segments || [])
|
|
2605
|
+
.filter((s) => s != null && !!(s.label || s.value))
|
|
2606
|
+
.map((s) => ({ ...s }))
|
|
2607
|
+
.sort((a, b) => {
|
|
2608
|
+
const na = parseFloat(String(a.value || '0').replace(/,/g, ''));
|
|
2609
|
+
const nb = parseFloat(String(b.value || '0').replace(/,/g, ''));
|
|
2610
|
+
return nb - na;
|
|
2611
|
+
});
|
|
2612
|
+
if (rawSegments.length === 0) {
|
|
2613
|
+
slide.addText('(暂无环形图数据)', {
|
|
2614
|
+
x: 2, y: 3.2, w: 6, h: 0.8,
|
|
2615
|
+
fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
|
|
2616
|
+
fontFace: FONTS.body,
|
|
1568
2617
|
});
|
|
2618
|
+
return;
|
|
1569
2619
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
2620
|
+
const totalValue = rawSegments.reduce((sum, s) => {
|
|
2621
|
+
return sum + parseFloat(String(s.value || '0').replace(/,/g, ''));
|
|
2622
|
+
}, 0);
|
|
2623
|
+
const segments = rawSegments.map((s, i) => {
|
|
2624
|
+
const n = parseFloat(String(s.value || '0').replace(/,/g, ''));
|
|
2625
|
+
const pct = totalValue > 0 ? Math.round((n / totalValue) * 1000) / 10 : 0;
|
|
2626
|
+
return {
|
|
2627
|
+
...s,
|
|
2628
|
+
percent: s.percent || `${pct}%`,
|
|
2629
|
+
numericValue: n,
|
|
2630
|
+
color: s.color || `var(--lp-${['blue', 'green', 'amber', 'red', 'violet'][i % 5]})`,
|
|
2631
|
+
};
|
|
2632
|
+
});
|
|
2633
|
+
const labels = segments.map((s) => s.label || '');
|
|
2634
|
+
const values = segments.map((s) => s.numericValue);
|
|
2635
|
+
const chartColors = segments.map((_, i) => CHART_COLORS[i % CHART_COLORS.length]);
|
|
2636
|
+
const insight = props.insight;
|
|
2637
|
+
const hasInsight = !!insight && (!!insight.value || !!insight.label || !!insight.description);
|
|
2638
|
+
const chartW = hasInsight ? 4.2 : 4.8;
|
|
2639
|
+
slide.addChart('doughnut', [
|
|
2640
|
+
{ name: props.title || '', labels, values },
|
|
2641
|
+
], {
|
|
2642
|
+
x: 0.6, y: 2.4, w: chartW, h: 3.0,
|
|
2643
|
+
chartColors,
|
|
2644
|
+
showValue: false,
|
|
2645
|
+
holeSize: 60,
|
|
2646
|
+
});
|
|
2647
|
+
const total = props.total || { value: String(totalValue), label: '合计' };
|
|
2648
|
+
const centerX = 0.6 + chartW / 2;
|
|
2649
|
+
slide.addText(total.value || String(totalValue), {
|
|
2650
|
+
x: centerX - 1.8, y: 3.4, w: 3.6, h: 0.6,
|
|
2651
|
+
fontSize: 28, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
|
|
1573
2652
|
fontFace: FONTS.heading,
|
|
1574
2653
|
});
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
2654
|
+
slide.addText(total.label || '合计', {
|
|
2655
|
+
x: centerX - 1.8, y: 4.0, w: 3.6, h: 0.3,
|
|
2656
|
+
fontSize: 12, color: COLORS.secondary, align: 'center', valign: 'top',
|
|
2657
|
+
fontFace: FONTS.body,
|
|
2658
|
+
});
|
|
2659
|
+
// 右侧图例列表
|
|
2660
|
+
const legendX = hasInsight ? 5.0 : 5.7;
|
|
2661
|
+
const legendW = hasInsight ? 2.6 : 3.3;
|
|
2662
|
+
let legendY = 2.5;
|
|
2663
|
+
segments.forEach((segment, index) => {
|
|
2664
|
+
const color = chartColors[index % chartColors.length];
|
|
1578
2665
|
slide.addShape('ellipse', {
|
|
1579
|
-
x:
|
|
1580
|
-
fill: { color
|
|
2666
|
+
x: legendX, y: legendY + 0.08, w: 0.18, h: 0.18,
|
|
2667
|
+
fill: { color },
|
|
1581
2668
|
});
|
|
1582
|
-
slide.addText(
|
|
1583
|
-
x:
|
|
1584
|
-
fontSize:
|
|
2669
|
+
slide.addText(segment.label || '', {
|
|
2670
|
+
x: legendX + 0.3, y: legendY, w: legendW - 1.4, h: 0.28,
|
|
2671
|
+
fontSize: 13, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
|
|
1585
2672
|
fontFace: FONTS.heading,
|
|
1586
2673
|
});
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2674
|
+
if (segment.labelEn) {
|
|
2675
|
+
slide.addText(segment.labelEn, {
|
|
2676
|
+
x: legendX + 0.3, y: legendY + 0.26, w: legendW - 1.4, h: 0.22,
|
|
2677
|
+
fontSize: 10, color: COLORS.secondary, align: 'left', valign: 'top',
|
|
2678
|
+
fontFace: FONTS.body,
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
slide.addText(segment.percent || '', {
|
|
2682
|
+
x: legendX + legendW - 1.0, y: legendY, w: 0.9, h: 0.28,
|
|
2683
|
+
fontSize: 13, color: COLORS.accent, bold: true, align: 'right', valign: 'top',
|
|
2684
|
+
fontFace: FONTS.heading,
|
|
1591
2685
|
});
|
|
2686
|
+
slide.addText(segment.value || '', {
|
|
2687
|
+
x: legendX + legendW - 1.0, y: legendY + 0.26, w: 0.9, h: 0.22,
|
|
2688
|
+
fontSize: 10, color: COLORS.secondary, align: 'right', valign: 'top',
|
|
2689
|
+
fontFace: FONTS.mono,
|
|
2690
|
+
});
|
|
2691
|
+
legendY += segment.labelEn ? 0.65 : 0.5;
|
|
1592
2692
|
});
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
addTitle(slide, props.title ?? 'GalleryV3');
|
|
1597
|
-
const images = (props.images ?? []).slice(0, 3);
|
|
1598
|
-
const gap = 0.35;
|
|
1599
|
-
const cardW = (8.4 - gap * 2) / 3;
|
|
1600
|
-
const startX = 0.8;
|
|
1601
|
-
const y = 2.6;
|
|
1602
|
-
const imgH = 2.8;
|
|
1603
|
-
images.forEach((image, index) => {
|
|
1604
|
-
const x = startX + index * (cardW + gap);
|
|
1605
|
-
slide.addShape('rect', {
|
|
1606
|
-
x,
|
|
1607
|
-
y,
|
|
1608
|
-
w: cardW,
|
|
1609
|
-
h: imgH,
|
|
2693
|
+
if (hasInsight) {
|
|
2694
|
+
slide.addShape('roundRect', {
|
|
2695
|
+
x: 8.0, y: 2.3, w: 2.4, h: 3.2,
|
|
1610
2696
|
fill: { color: COLORS.surfaceElevated },
|
|
1611
2697
|
line: { color: COLORS.border, width: 1 },
|
|
1612
|
-
rectRadius: 0.
|
|
2698
|
+
rectRadius: 0.15,
|
|
1613
2699
|
});
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
fill: { color: COLORS.accent },
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
2700
|
+
let cursorY = 2.6;
|
|
2701
|
+
if (insight.value) {
|
|
2702
|
+
slide.addText(insight.value, {
|
|
2703
|
+
x: 8.2, y: cursorY, w: 2.0, h: 0.6,
|
|
2704
|
+
fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.accent,
|
|
2705
|
+
});
|
|
2706
|
+
cursorY += 0.6;
|
|
1624
2707
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
color: COLORS.secondary,
|
|
1632
|
-
align: 'center',
|
|
1633
|
-
valign: 'middle',
|
|
1634
|
-
fontFace: FONTS.body,
|
|
1635
|
-
});
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
|
-
function renderTestimonialV3(slide, props) {
|
|
1639
|
-
const leftX = 0.8;
|
|
1640
|
-
const leftW = 5.0;
|
|
1641
|
-
const rightX = 6.2;
|
|
1642
|
-
const rightW = 3.0;
|
|
1643
|
-
slide.addText('"', {
|
|
1644
|
-
x: leftX, y: 1.2, w: 1.0, h: 1.0,
|
|
1645
|
-
fontSize: 120, color: COLORS.accent, align: 'left', valign: 'top',
|
|
1646
|
-
fontFace: FONTS.heading,
|
|
1647
|
-
});
|
|
1648
|
-
slide.addText(props.quote ?? '', {
|
|
1649
|
-
x: leftX, y: 2.2, w: leftW, h: 3.2,
|
|
1650
|
-
fontSize: 28, color: COLORS.primary, align: 'left', valign: 'top',
|
|
1651
|
-
fontFace: FONTS.heading,
|
|
1652
|
-
});
|
|
1653
|
-
slide.addShape('rect', {
|
|
1654
|
-
x: rightX, y: 1.4, w: rightW, h: 4.4,
|
|
1655
|
-
fill: { color: COLORS.surfaceElevated },
|
|
1656
|
-
line: { color: COLORS.border, width: 1 },
|
|
1657
|
-
rectRadius: 0.1,
|
|
1658
|
-
});
|
|
1659
|
-
if (props.avatar) {
|
|
1660
|
-
try {
|
|
1661
|
-
slide.addImage({ path: props.avatar, x: rightX + 0.8, y: 1.8, w: 1.4, h: 1.4 });
|
|
2708
|
+
if (insight.label) {
|
|
2709
|
+
slide.addText(insight.label, {
|
|
2710
|
+
x: 8.2, y: cursorY, w: 2.0, h: 0.3,
|
|
2711
|
+
fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
|
|
2712
|
+
});
|
|
2713
|
+
cursorY += 0.45;
|
|
1662
2714
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
2715
|
+
if (insight.description) {
|
|
2716
|
+
cursorY += 0.15;
|
|
2717
|
+
slide.addText(insight.description, {
|
|
2718
|
+
x: 8.2, y: cursorY, w: 2.0, h: 1.6,
|
|
2719
|
+
fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary,
|
|
2720
|
+
valign: 'top',
|
|
1667
2721
|
});
|
|
1668
2722
|
}
|
|
1669
2723
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
2724
|
+
if (props.footnote) {
|
|
2725
|
+
slide.addText(props.footnote, {
|
|
2726
|
+
x: 0.6, y: 5.7, w: 8.8, h: 0.25,
|
|
2727
|
+
fontSize: 11, color: COLORS.secondary, align: 'left',
|
|
2728
|
+
fontFace: FONTS.mono,
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
// Theme02 版式复用 theme01 的渲染函数,视觉差异由主题色板控制
|
|
2733
|
+
// ---- Theme02 Batch B/C/D adapters ------------------------------------------
|
|
2734
|
+
export const T2_CHART = ['00E5B0', 'FFD166', '00B4FF', 'FF6B6B', 'A78BFA', 'F472B6'];
|
|
2735
|
+
export function theme03TitleText(title) {
|
|
2736
|
+
const parts = title.split(/(\{\{[^}]+\}\})/g);
|
|
2737
|
+
return parts.map((part) => {
|
|
2738
|
+
const match = part.match(/^\{\{(.+)\}\}$/);
|
|
2739
|
+
if (match) {
|
|
2740
|
+
return { text: match[1], options: { color: COLORS.accent, bold: true } };
|
|
2741
|
+
}
|
|
2742
|
+
return { text: part, options: { color: COLORS.primary, bold: true } };
|
|
1684
2743
|
});
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
2744
|
+
}
|
|
2745
|
+
export function addTheme03Topbar(slide, props) {
|
|
2746
|
+
const tagText = [props.tag, props.tagLabel].filter(Boolean).join(' · ');
|
|
2747
|
+
if (tagText) {
|
|
2748
|
+
slide.addText(tagText, {
|
|
2749
|
+
x: 0.84375, y: 0.71875, w: 4, h: 0.25,
|
|
2750
|
+
fontSize: 12, color: COLORS.accent, fontFace: FONTS.mono,
|
|
2751
|
+
valign: 'middle',
|
|
1690
2752
|
});
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
2753
|
+
}
|
|
2754
|
+
if (props.topRightMeta) {
|
|
2755
|
+
slide.addText(props.topRightMeta, {
|
|
2756
|
+
x: 5.15625, y: 0.71875, w: 4, h: 0.25,
|
|
2757
|
+
fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono,
|
|
2758
|
+
align: 'right', valign: 'middle',
|
|
1695
2759
|
});
|
|
1696
2760
|
}
|
|
1697
2761
|
}
|
|
1698
|
-
function
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
const startY = 2.6;
|
|
1703
|
-
const rowH = 0.8;
|
|
1704
|
-
const col1W = 3.2;
|
|
1705
|
-
const col2W = 2.8;
|
|
1706
|
-
const col3W = 2.8;
|
|
1707
|
-
const gap = 0.1;
|
|
1708
|
-
const startX = 0.8;
|
|
1709
|
-
slide.addShape('rect', {
|
|
1710
|
-
x: startX, y: startY, w: col1W, h: 0.5,
|
|
1711
|
-
fill: { color: COLORS.surfaceElevated },
|
|
1712
|
-
line: { color: COLORS.border, width: 1 },
|
|
1713
|
-
});
|
|
1714
|
-
slide.addShape('rect', {
|
|
1715
|
-
x: startX + col1W + gap, y: startY, w: col2W, h: 0.5,
|
|
1716
|
-
fill: { color: COLORS.accent },
|
|
1717
|
-
line: { color: COLORS.accent, width: 1 },
|
|
1718
|
-
});
|
|
1719
|
-
slide.addShape('rect', {
|
|
1720
|
-
x: startX + col1W + gap + col2W + gap, y: startY, w: col3W, h: 0.5,
|
|
1721
|
-
fill: { color: COLORS.surfaceElevated },
|
|
2762
|
+
export function addTheme03Footer(slide, props) {
|
|
2763
|
+
const y = 5.625 - 0.65625;
|
|
2764
|
+
slide.addShape('line', {
|
|
2765
|
+
x1: 0.84375, y1: y - 0.1, x2: 9.15625, y2: y - 0.1,
|
|
1722
2766
|
line: { color: COLORS.border, width: 1 },
|
|
1723
2767
|
});
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
slide.addText(props.leftTitle ?? '方案 A', {
|
|
1730
|
-
x: startX + col1W + gap, y: startY, w: col2W, h: 0.5,
|
|
1731
|
-
fontSize: 12, color: '#ffffff', bold: true, align: 'center', valign: 'middle',
|
|
1732
|
-
fontFace: FONTS.heading,
|
|
1733
|
-
});
|
|
1734
|
-
slide.addText(props.rightTitle ?? '方案 B', {
|
|
1735
|
-
x: startX + col1W + gap + col2W + gap, y: startY, w: col3W, h: 0.5,
|
|
1736
|
-
fontSize: 12, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
|
|
1737
|
-
fontFace: FONTS.heading,
|
|
1738
|
-
});
|
|
1739
|
-
rows.forEach((row, index) => {
|
|
1740
|
-
const y = startY + 0.6 + index * rowH;
|
|
1741
|
-
const bg = index % 2 === 0 ? COLORS.surface : COLORS.surfaceElevated;
|
|
1742
|
-
slide.addShape('rect', {
|
|
1743
|
-
x: startX, y, w: col1W, h: rowH,
|
|
1744
|
-
fill: { color: bg },
|
|
1745
|
-
line: { color: COLORS.border, width: 1 },
|
|
1746
|
-
});
|
|
1747
|
-
slide.addShape('rect', {
|
|
1748
|
-
x: startX + col1W + gap, y, w: col2W, h: rowH,
|
|
1749
|
-
fill: { color: bg },
|
|
1750
|
-
line: { color: COLORS.border, width: 1 },
|
|
1751
|
-
});
|
|
1752
|
-
slide.addShape('rect', {
|
|
1753
|
-
x: startX + col1W + gap + col2W + gap, y, w: col3W, h: rowH,
|
|
1754
|
-
fill: { color: bg },
|
|
1755
|
-
line: { color: COLORS.border, width: 1 },
|
|
2768
|
+
if (props.footnoteLeft) {
|
|
2769
|
+
slide.addText(props.footnoteLeft, {
|
|
2770
|
+
x: 0.84375, y: y, w: 4, h: 0.25,
|
|
2771
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
|
|
2772
|
+
valign: 'top',
|
|
1756
2773
|
});
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
x: startX + col1W + gap + 0.1, y, w: col2W - 0.2, h: rowH,
|
|
1764
|
-
fontSize: 13, color: COLORS.primary, align: 'center', valign: 'middle',
|
|
1765
|
-
fontFace: FONTS.body,
|
|
1766
|
-
});
|
|
1767
|
-
slide.addText(row.right ?? '', {
|
|
1768
|
-
x: startX + col1W + gap + col2W + gap + 0.1, y, w: col3W - 0.2, h: rowH,
|
|
1769
|
-
fontSize: 13, color: COLORS.primary, align: 'center', valign: 'middle',
|
|
1770
|
-
fontFace: FONTS.body,
|
|
2774
|
+
}
|
|
2775
|
+
if (props.footnoteRight) {
|
|
2776
|
+
slide.addText(props.footnoteRight, {
|
|
2777
|
+
x: 5.15625, y: y, w: 4, h: 0.25,
|
|
2778
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
|
|
2779
|
+
align: 'right', valign: 'top',
|
|
1771
2780
|
});
|
|
1772
|
-
}
|
|
2781
|
+
}
|
|
1773
2782
|
}
|
|
1774
|
-
function
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2783
|
+
export function getTheme03PricingFeatureValue(feature) {
|
|
2784
|
+
if (feature == null)
|
|
2785
|
+
return undefined;
|
|
2786
|
+
if (typeof feature === 'string')
|
|
2787
|
+
return feature;
|
|
2788
|
+
return feature.value;
|
|
2789
|
+
}
|
|
2790
|
+
export function getTheme03ComparisonValue(item) {
|
|
2791
|
+
if (item == null)
|
|
2792
|
+
return undefined;
|
|
2793
|
+
if (typeof item === 'string')
|
|
2794
|
+
return item;
|
|
2795
|
+
return item.value;
|
|
2796
|
+
}
|
|
2797
|
+
// ---- Theme04 renderers -----------------------------------------------------
|
|
2798
|
+
export function totalForSegments(segments, current) {
|
|
2799
|
+
const total = segments.reduce((sum, s) => sum + (Number(String(s.value || '0').replace(/,/g, '')) || 0), 0);
|
|
2800
|
+
const value = Number(String(current.value || '0').replace(/,/g, '')) || 0;
|
|
2801
|
+
if (total <= 0)
|
|
2802
|
+
return '0';
|
|
2803
|
+
return String(Math.round((value / total) * 100));
|
|
2804
|
+
}
|
|
2805
|
+
export function layoutTreemap(items, x, y, w, h) {
|
|
2806
|
+
const validItems = (items || []).filter((it) => (it.value ?? 0) > 0);
|
|
2807
|
+
if (validItems.length === 0)
|
|
2808
|
+
return [];
|
|
2809
|
+
if (validItems.length === 1) {
|
|
2810
|
+
return [{ x, y, w, h, item: validItems[0] }];
|
|
2811
|
+
}
|
|
2812
|
+
const total = validItems.reduce((sum, it) => sum + (it.value ?? 0), 0);
|
|
2813
|
+
const sorted = [...validItems].sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
|
|
2814
|
+
// Split direction: favor the longer dimension
|
|
2815
|
+
const vertical = w >= h;
|
|
2816
|
+
const results = [];
|
|
2817
|
+
let currentPos = vertical ? x : y;
|
|
2818
|
+
const primary = vertical ? w : h;
|
|
2819
|
+
sorted.forEach((item, idx) => {
|
|
2820
|
+
const ratio = (item.value ?? 0) / total;
|
|
2821
|
+
const size = idx === sorted.length - 1 ? (vertical ? x + w - currentPos : y + h - currentPos) : primary * ratio;
|
|
2822
|
+
if (vertical) {
|
|
2823
|
+
results.push({ x: currentPos, y, w: size, h, item });
|
|
2824
|
+
currentPos += size;
|
|
1795
2825
|
}
|
|
1796
|
-
|
|
1797
|
-
x
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2826
|
+
else {
|
|
2827
|
+
results.push({ x, y: currentPos, w, h: size, item });
|
|
2828
|
+
currentPos += size;
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
return results;
|
|
2832
|
+
}
|
|
2833
|
+
// ---- Theme06 深色图谱风版式渲染器 -----------------------------------------
|
|
2834
|
+
// ---- Theme06 Phase 3/4 新版式渲染器 ---------------------------------------
|
|
2835
|
+
// ---- Theme06 Phase 1 + Phase 2 新版式渲染器 --------------------------------
|
|
2836
|
+
// ---- Theme05 光谱报告风版式渲染器 -----------------------------------------
|
|
2837
|
+
// ---- Theme05 Phase A / Phase B 新增版式渲染器 -----------------------------
|
|
2838
|
+
// ---- Theme05 Phase B 新版式渲染器 -----------------------------------------
|
|
2839
|
+
// ---- Theme05 Phase C 版式渲染函数 -----------------------------------------
|
|
2840
|
+
// Theme05 光谱报告风版式注册
|
|
2841
|
+
// Phase A 回填
|
|
2842
|
+
// Phase B 新版式
|
|
2843
|
+
// Phase C 新版式
|
|
2844
|
+
// Theme06 深色图谱风版式注册
|
|
2845
|
+
// ==================== Theme07 冷白调研风 PPTX 渲染器 ====================
|
|
2846
|
+
// 坐标系:10" × 5.625",页面内边距 0.65",圆角 0.08"
|
|
2847
|
+
// 设计:冷白底 + 深墨文字 + 深靛蓝主色 + 衬线标题
|
|
2848
|
+
/** theme07 通用装饰线(底部渐变线) */
|
|
2849
|
+
export function addTheme07GlowLine(slide) {
|
|
2850
|
+
slide.addShape('line', {
|
|
2851
|
+
x1: 0.65, y1: 5.05, x2: 9.35, y2: 5.05,
|
|
2852
|
+
line: { color: COLORS.accent, width: 2, transparency: 60 },
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
/** theme07 通用卡片(冷白底 + 细边框 + 圆角) */
|
|
2856
|
+
export function addTheme07Card(slide, x, y, w, h, radius = 0.08, fill = COLORS.surface) {
|
|
2857
|
+
slide.addShape('roundRect', {
|
|
2858
|
+
x, y, w, h,
|
|
2859
|
+
fill: { color: fill },
|
|
2860
|
+
line: { color: COLORS.border, width: 1 },
|
|
2861
|
+
rectRadius: radius,
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
/** theme07 通用页脚 */
|
|
2865
|
+
export function addTheme07Footer(slide, props) {
|
|
2866
|
+
if (props.footnoteLeft) {
|
|
2867
|
+
slide.addText(props.footnoteLeft, {
|
|
2868
|
+
x: 0.65, y: 5.2, w: 4, h: 0.25,
|
|
2869
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
|
|
1811
2870
|
});
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2871
|
+
}
|
|
2872
|
+
if (props.footnoteRight) {
|
|
2873
|
+
slide.addText(props.footnoteRight, {
|
|
2874
|
+
x: 8.7, y: 5.2, w: 0.65, h: 0.25,
|
|
2875
|
+
fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right',
|
|
1816
2876
|
});
|
|
1817
|
-
}
|
|
2877
|
+
}
|
|
1818
2878
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2879
|
+
/** theme07 通用顶部标签(kicker) */
|
|
2880
|
+
export function addTheme07Kicker(slide, text, x = 0.65, y = 0.55) {
|
|
2881
|
+
slide.addText(text, {
|
|
2882
|
+
x, y, w: 8, h: 0.3,
|
|
2883
|
+
fontSize: 11, color: COLORS.accent, bold: true, fontFace: FONTS.mono,
|
|
2884
|
+
charSpacing: 2,
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
/** 将 theme07 CSS 变量名解析为 PPTX 可用 HEX(去掉 var(--lp-...) 包装) */
|
|
2888
|
+
export function resolveTheme07CssColor(value, fallback = COLORS.accent) {
|
|
2889
|
+
if (!value)
|
|
2890
|
+
return fallback;
|
|
2891
|
+
const inner = value.replace('var(--lp-', '').replace(')', '').trim();
|
|
2892
|
+
const map = {
|
|
2893
|
+
accent: '2E5C8A',
|
|
2894
|
+
'accent-2': '7B9E7B',
|
|
2895
|
+
'accent-cool': 'B8623E',
|
|
2896
|
+
blue: '5B9BD5',
|
|
2897
|
+
ink: '1A1D23',
|
|
2898
|
+
ink2: '1A1D23',
|
|
2899
|
+
ink3: '6B7280',
|
|
2900
|
+
red: 'C73E2F',
|
|
2901
|
+
orange: 'C25E00',
|
|
2902
|
+
amber: 'B45309',
|
|
2903
|
+
green: '15803D',
|
|
2904
|
+
teal: '0B7A85',
|
|
2905
|
+
cyan: '0B7A85',
|
|
2906
|
+
violet: '7C2ED3',
|
|
2907
|
+
purple: '7C2ED3',
|
|
2908
|
+
};
|
|
2909
|
+
const hex = map[inner];
|
|
2910
|
+
if (hex)
|
|
2911
|
+
return hex;
|
|
2912
|
+
// 若已经是 6 位 HEX 则直接返回
|
|
2913
|
+
if (/^[0-9A-Fa-f]{6}$/.test(inner))
|
|
2914
|
+
return inner;
|
|
2915
|
+
return fallback;
|
|
2916
|
+
}
|
|
2917
|
+
// ============ theme07 Phase 3/4 通用渲染器 ============
|
|
2918
|
+
/** theme07 通用赛道/垂直页渲染器(sector-layout) */
|
|
2919
|
+
/** theme07 通用表格/结构化数据页渲染器(table-layout) */
|
|
2920
|
+
/** theme07 通用公司案例页渲染器(company-layout) */
|
|
2921
|
+
/** theme07 通用地理分布页渲染器(geo-layout) */
|
|
2922
|
+
/** theme07 通用结尾/金句页渲染器(closing-layout) */
|
|
2923
|
+
// 收尾页(引语变体):居中大引语 + 关键词高亮(支持 [[关键词]] 语法)
|
|
2924
|
+
// ---- Theme07 大数字版式渲染器(stat 系列,新增 4 个) ------------------
|
|
2925
|
+
/** 单数字 Hero:巨号结论数字 + 单位 + 说明 */
|
|
2926
|
+
/** 多数字并列:2–4 组大数字横向并列 */
|
|
2927
|
+
/** 数字 + 图表:左侧巨号结论,右侧支撑柱状序列 */
|
|
2928
|
+
/** 数字对比:左右巨号对峙 + 中间差值徽标 */
|
|
2929
|
+
// Theme07 PPTX 渲染器注册
|
|
2930
|
+
// theme07 Phase 3/4 通用渲染器注册
|
|
2931
|
+
const THEME07_SECTOR_IDS = [
|
|
2932
|
+
'theme07_knowledge_v1', 'theme07_legal_v1', 'theme07_healthcare_v1', 'theme07_finance_v1',
|
|
2933
|
+
'theme07_compute_v1', 'theme07_chip_v1', 'theme07_robotics_v1', 'theme07_autonomy_v1',
|
|
2934
|
+
'theme07_safety_v1', 'theme07_content_gen_v1', 'theme07_education_v1', 'theme07_support_v1',
|
|
2935
|
+
'theme07_sales_v1', 'theme07_low_code_v1', 'theme07_open_source_v1', 'theme07_alignment_v1',
|
|
2936
|
+
];
|
|
2937
|
+
THEME07_SECTOR_IDS.forEach((id) => registerPptxLayoutRenderer(id, renderTheme07SectorV1));
|
|
2938
|
+
const THEME07_TABLE_IDS = [
|
|
2939
|
+
'theme07_early_stage_v1', 'theme07_deal_structure_v1', 'theme07_investor_mix_v1', 'theme07_resource_v1',
|
|
2940
|
+
'theme07_alliance_v1', 'theme07_ecosystem_v1', 'theme07_revenue_v1', 'theme07_compliance_v1',
|
|
2941
|
+
'theme07_margin_v1', 'theme07_moat_v1', 'theme07_strategy_infra_v1', 'theme07_strategy_vertical_v1',
|
|
2942
|
+
];
|
|
2943
|
+
THEME07_TABLE_IDS.forEach((id) => registerPptxLayoutRenderer(id, renderTheme07TableV1));
|
|
2944
|
+
// theme08 黑金实验风:注册全部 39 个版式专属 PPTX 渲染器
|
|
2945
|
+
registerTheme08Renderers(registerPptxLayoutRenderer);
|
|
2946
|
+
// theme09 墨韵专色 · 杂志印刷风:注册 P0 全部 12 个版式专属 PPTX 渲染器
|
|
2947
|
+
registerTheme09Renderers(registerPptxLayoutRenderer);
|
|
2948
|
+
// theme10 金色指数 · 金融编辑风:注册 P0 全部 12 个版式专属 PPTX 渲染器
|
|
2949
|
+
registerTheme10Renderers(registerPptxLayoutRenderer);
|
|
2950
|
+
registerTheme01Renderers(registerPptxLayoutRenderer);
|
|
2951
|
+
registerTheme02Renderers(registerPptxLayoutRenderer);
|
|
2952
|
+
registerTheme03Renderers(registerPptxLayoutRenderer);
|
|
2953
|
+
registerTheme04Renderers(registerPptxLayoutRenderer);
|
|
2954
|
+
registerTheme05Renderers(registerPptxLayoutRenderer);
|
|
2955
|
+
registerTheme06Renderers(registerPptxLayoutRenderer);
|
|
2956
|
+
registerTheme07Renderers(registerPptxLayoutRenderer);
|
|
1877
2957
|
//# sourceMappingURL=export-pptx.js.map
|