aegon-gen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/package.json +12 -0
  2. package/src/App.vue +3 -0
  3. package/src/api/index.ts +19 -0
  4. package/src/api/modules/gen-ai/gen-entry/index.ts +30 -0
  5. package/src/api/modules/gen-ai/model-manager/index.ts +42 -0
  6. package/src/api/modules/gen-ai/model-manager/mockApi.ts +33 -0
  7. package/src/api/modules/index.ts +98 -0
  8. package/src/api/modules/user/index.ts +4 -0
  9. package/src/api/request.ts +102 -0
  10. package/src/assets/sample-access-icon.png +0 -0
  11. package/src/assets/sample-pie-chart.png +0 -0
  12. package/src/assets/vue.svg +1 -0
  13. package/src/components/CapsuleScrollbar.vue +93 -0
  14. package/src/components/Export/ExcelExport.vue +592 -0
  15. package/src/components/Export/ExcelExport2.vue +494 -0
  16. package/src/components/Export/ExcelExport3.vue +342 -0
  17. package/src/components/Export/ExcelExport4.vue +665 -0
  18. package/src/components/Export/excelExport.js +547 -0
  19. package/src/components/Export/excelExport.ts +551 -0
  20. package/src/components/GEN-AI/index.vue +142 -0
  21. package/src/components/GEN-AI/index1.vue +456 -0
  22. package/src/components/GEN-AI/index10.vue +5 -0
  23. package/src/components/GEN-AI/index2.vue +568 -0
  24. package/src/components/GEN-AI/index3.vue +623 -0
  25. package/src/components/GEN-AI/index4.vue +629 -0
  26. package/src/components/GEN-AI/index5.vue +578 -0
  27. package/src/components/GEN-AI/index6.vue +656 -0
  28. package/src/components/GEN-AI/index7.vue +717 -0
  29. package/src/components/GEN-AI/index8.vue +405 -0
  30. package/src/components/GEN-AI/index9.vue +1065 -0
  31. package/src/components/GEN-AI/types.ts +12 -0
  32. package/src/components/GEN-AI/utils.ts +42 -0
  33. package/src/components/HelloWorld.vue +41 -0
  34. package/src/components/PageCard.vue +7 -0
  35. package/src/components/PageHeader.vue +32 -0
  36. package/src/components/backup/index5 copy.vue +556 -0
  37. package/src/components/backup/index5.vue +620 -0
  38. package/src/components/backup/index9 copy.vue +1029 -0
  39. package/src/components/backup/index9-pro.vue +1065 -0
  40. package/src/components/backup/index9.vue +1057 -0
  41. package/src/components/el-date-picker.vue +64 -0
  42. package/src/directives/btnLoading.ts +427 -0
  43. package/src/directives/debounce copy.ts +670 -0
  44. package/src/directives/debounce.ts +98 -0
  45. package/src/directives/index.ts +25 -0
  46. package/src/layouts/MainLayout.vue +101 -0
  47. package/src/main.ts +85 -0
  48. package/src/router/index.ts +76 -0
  49. package/src/router/menus.ts +28 -0
  50. package/src/style.css +79 -0
  51. package/src/styles/_variables.scss +24 -0
  52. package/src/styles/app-button.css +26 -0
  53. package/src/styles/element-overrides.css +23 -0
  54. package/src/styles/global.css +44 -0
  55. package/src/styles/index.scss +1 -0
  56. package/src/styles/page-card.css +21 -0
  57. package/src/styles/variables.css +26 -0
  58. package/src/test/mock.ts +101 -0
  59. package/src/test/test1.vue +402 -0
  60. package/src/test/test2.vue +1689 -0
  61. package/src/types/gen-ai/gen-entry/index.ts +17 -0
  62. package/src/types/gen-ai/model-manager/index.ts +19 -0
  63. package/src/utils/docxExport.ts +1610 -0
  64. package/src/utils/gen-ai-navigation.ts +37 -0
  65. package/src/utils/gen-ai-scroll.ts +455 -0
  66. package/src/utils/openDataLoaderWordExport.ts +33 -0
  67. package/src/utils/pageScrollbar.ts +115 -0
  68. package/src/utils/randomTranscode.ts +87 -0
  69. package/src/utils/reportPdfExport.ts +44 -0
  70. package/src/views/AdminCenter/index.vue +817 -0
  71. package/src/views/Blank.vue +68 -0
  72. package/src/views/Home.vue +29 -0
  73. package/src/views/ReportCenter/index.vue +1380 -0
  74. package/src/views/TemplateCenter/Knowledge.ts +83 -0
  75. package/src/views/TemplateCenter/data.d.ts +10 -0
  76. package/src/views/TemplateCenter/index.vue +1205 -0
  77. package/src/views/TemplateCenter/service.ts +69 -0
  78. package/src/views/gen-ai/components/RecentReportsTable.vue +193 -0
  79. package/src/views/gen-ai/gen-entry/index.vue +309 -0
  80. package/src/views/gen-ai/gen-entry/mockData.ts +160 -0
  81. package/src/views/gen-ai/management-center/index.vue +53 -0
  82. package/src/views/gen-ai/model-manager/ChapterTitleScroll.vue +275 -0
  83. package/src/views/gen-ai/model-manager/index.vue +1205 -0
  84. package/src/views/gen-ai/model-manager/mockData.ts +122 -0
  85. package/src/views/gen-ai/report-center/index.vue +158 -0
  86. package/src/vite-env.d.ts +38 -0
@@ -0,0 +1,1065 @@
1
+ <template>
2
+ <div
3
+ class="text-[#333] select-none"
4
+ :class="embedded ? 'gen-ai-index9-embedded' : 'min-h-screen bg-[#F3F4F6]'"
5
+ >
6
+ <main :class="embedded ? 'gen-ai-index9-embedded__main' : 'overflow-y-auto p-8'">
7
+ <div :class="embedded ? 'gen-ai-index9-embedded__inner' : 'max-w-[842px] mx-auto'">
8
+ <div class="typewriter-card" :class="{ 'typewriter-card--embedded': embedded }">
9
+ <div v-if="!embedded" class="typewriter-card__toolbar no-print no-export">
10
+ <el-button @click="restartDemo" size="small" plain type="danger">
11
+ 重新播放
12
+ </el-button>
13
+ <div class="typewriter-card__actions">
14
+ <el-button
15
+ @click="handleExportPDF"
16
+ size="small"
17
+ plain
18
+ type="danger"
19
+ :loading="isExporting"
20
+ >
21
+ 匯出為PDF
22
+ </el-button>
23
+ <el-button
24
+ @click="handleExportWord"
25
+ size="small"
26
+ plain
27
+ type="danger"
28
+ :loading="isExporting"
29
+ >
30
+ 匯出為Word
31
+ </el-button>
32
+ <el-button @click="handlePrint" size="small" plain type="success">
33
+ 打印
34
+ </el-button>
35
+ </div>
36
+ </div>
37
+
38
+ <div
39
+ id="report-container"
40
+ class="typewriter-card__body doc-sample-root"
41
+ >
42
+ <div class="content-wrapper doc-sample-body">
43
+ <template v-for="(item, idx) in displaySequence" :key="idx">
44
+ <TypewriterText
45
+ v-if="item.type === 'text' && idx < activeIndex"
46
+ :text="item.content!"
47
+ :customClass="item.styleClass"
48
+ :auto-start="idx === activeIndex - 1 && idx === lastStartedTextIndex"
49
+ @completed="onTextCompleted"
50
+ />
51
+
52
+ <span
53
+ v-else-if="item.type === 'page-break' && idx < activeIndex"
54
+ class="page-break-marker"
55
+ data-export-page-break
56
+ :class="triggerComponentReady(idx)"
57
+ ></span>
58
+
59
+ <component
60
+ v-else-if="item.type === 'component' && idx < activeIndex"
61
+ :is="item.component"
62
+ @ready="onComponentReady"
63
+ />
64
+ </template>
65
+ </div>
66
+
67
+ <span
68
+ v-if="showGlobalCursor"
69
+ class="typewriter-cursor typewriter-cursor--global"
70
+ >|</span>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </main>
75
+ </div>
76
+ </template>
77
+
78
+ <script setup lang="ts">
79
+ import {
80
+ ref,
81
+ computed,
82
+ onMounted,
83
+ defineComponent,
84
+ h,
85
+ markRaw,
86
+ type Component,
87
+ nextTick,
88
+ watch,
89
+ } from "vue";
90
+ import { ElButton } from "element-plus";
91
+
92
+ withDefaults(
93
+ defineProps<{
94
+ embedded?: boolean
95
+ }>(),
96
+ {
97
+ embedded: false,
98
+ },
99
+ )
100
+ import { saveAs } from "file-saver";
101
+ import { renderReportPdfBlob } from "../../utils/reportPdfExport";
102
+ import {
103
+ buildWordDocumentHtml,
104
+ DOCX_FLOAT_ICON_SIZE_PX,
105
+ DOCX_PIE_CHART_MAX_HEIGHT_PX,
106
+ DOCX_PIE_CHART_MAX_WIDTH_PX,
107
+ exportHtmlToDocx,
108
+ stripWordPageBreaks,
109
+ prepareDomForDocx,
110
+ rasterizeImagesForDocx,
111
+ } from "../../utils/docxExport";
112
+ import accessIconImg from "../../assets/sample-access-icon.png";
113
+ import pieChartImg from "../../assets/sample-pie-chart.png";
114
+
115
+ const TH_GREEN_STYLE = {
116
+ backgroundColor: "#c6efce",
117
+ color: "#000000",
118
+ };
119
+
120
+ const TH_RED_STYLE = {
121
+ backgroundColor: "#ffc7ce",
122
+ color: "#000000",
123
+ };
124
+
125
+ // ---------- 状态及导出逻辑控制器 ----------
126
+ const isExporting = ref(false);
127
+ const activeIndex = ref(0);
128
+ const lastStartedTextIndex = ref(-1);
129
+
130
+ const showGlobalCursor = computed(() => {
131
+ if (activeIndex.value >= displaySequence.value.length) return false;
132
+ const pending = displaySequence.value[activeIndex.value];
133
+ return pending?.type !== "text";
134
+ });
135
+
136
+ const waitForFullDisplay = async () => {
137
+ if (activeIndex.value < displaySequence.value.length) {
138
+ activeIndex.value = displaySequence.value.length;
139
+ await nextTick();
140
+ await new Promise((resolve) => setTimeout(resolve, 600));
141
+ }
142
+ };
143
+
144
+ // 强制令无内容实体(如 Page Break)快速推入下一生命周期周期
145
+ const triggerComponentReady = (idx: number) => {
146
+ if (idx === activeIndex.value - 1) {
147
+ nextTick(() => { nextItem(); });
148
+ }
149
+ return "";
150
+ };
151
+
152
+ // --- PDF 深度渲染并注入物理 A4 切割机制 ---
153
+ const handleExportPDF = async () => {
154
+ const element = document.getElementById("report-container");
155
+ if (!element) return;
156
+ try {
157
+ isExporting.value = true;
158
+ await waitForFullDisplay();
159
+
160
+ const pulseCursor = document.querySelector(".animate-pulse");
161
+ if (pulseCursor) (pulseCursor as HTMLElement).style.display = "none";
162
+
163
+ const pdfBlob = await renderReportPdfBlob(element);
164
+ saveAs(pdfBlob, "無障礙標準圖文報告_sample3.pdf");
165
+ if (pulseCursor) (pulseCursor as HTMLElement).style.display = "";
166
+ } catch (error) {
167
+ console.error("PDF導出失敗:", error);
168
+ } finally {
169
+ isExporting.value = false;
170
+ }
171
+ };
172
+
173
+ // --- Word:DOM 结构化 HTML → 可编辑 .docx(非 PDF 截图转档)---
174
+ const handleExportWord = async () => {
175
+ const element = document.getElementById("report-container");
176
+ if (!element) return;
177
+ try {
178
+ isExporting.value = true;
179
+ await waitForFullDisplay();
180
+ await nextTick();
181
+
182
+ const pulseCursor = document.querySelector(".animate-pulse");
183
+ if (pulseCursor) (pulseCursor as HTMLElement).style.display = "none";
184
+
185
+ const cloneContainer = element.cloneNode(true) as HTMLElement;
186
+ stripWordPageBreaks(cloneContainer);
187
+ await rasterizeImagesForDocx(cloneContainer, element);
188
+ await nextTick();
189
+ await new Promise((resolve) => setTimeout(resolve, 200));
190
+
191
+ const prepared = prepareDomForDocx(cloneContainer, true);
192
+ const fullHtml = buildWordDocumentHtml(prepared.innerHTML);
193
+ const blob = await exportHtmlToDocx(fullHtml, {
194
+ headerFooter: { headerText: "商业机密", showPageNumbers: true },
195
+ });
196
+
197
+ saveAs(blob, "無障礙標準圖文報告_sample3.docx");
198
+ if (pulseCursor) (pulseCursor as HTMLElement).style.display = "";
199
+ } catch (error) {
200
+ console.error("Word 導出失敗:", error);
201
+ window.alert("Word 導出失敗,請查看瀏覽器控制台");
202
+ } finally {
203
+ isExporting.value = false;
204
+ }
205
+ };
206
+
207
+ const handlePrint = () => window.print();
208
+
209
+ // ---------- 1:1 静态抽取分流组件群 (markRaw 防底层死循环) ----------
210
+
211
+ // 1. 文档提纲有序及嵌套无序列表组件
212
+ const DocumentOutlineList = markRaw(
213
+ defineComponent({
214
+ name: "DocumentOutlineList",
215
+ setup(_, { emit }) {
216
+ onMounted(() => emit("ready"));
217
+ return () =>
218
+ h("ol", { class: "doc-ol" }, [
219
+ h("li", {}, "Headings"),
220
+ h("li", {}, "Lists"),
221
+ h("li", {}, "Links"),
222
+ h("li", {}, "Images"),
223
+ h("li", {}, [
224
+ h("span", { class: "doc-ol-label" }, "Tables"),
225
+ h("ul", { class: "doc-ul" }, [
226
+ h("li", {}, "Simple Tables"),
227
+ h("li", {}, "Complex Tables"),
228
+ ]),
229
+ ]),
230
+ h("li", {}, "Columns"),
231
+ ]);
232
+ },
233
+ })
234
+ );
235
+
236
+ const TABLE_CELL_STYLE = {
237
+ border: "1px solid #000000",
238
+ padding: "4pt 6pt",
239
+ fontSize: "11pt",
240
+ color: "#000000",
241
+ };
242
+
243
+ // 2. Images 章节:左浮动图标 + 两段正文环绕排版
244
+ const ImagesFloatSection = markRaw(
245
+ defineComponent({
246
+ name: "ImagesFloatSection",
247
+ setup(_, { emit }) {
248
+ const p1 = ref("");
249
+ const p2 = ref("");
250
+ const typingPhase = ref<0 | 1 | 2>(0);
251
+ const text1 =
252
+ 'Documents may contain images. For example, there is an image of the web accessibility symbol to the left of this paragraph. Its alternate text is "Web Access Symbol".';
253
+ const text2 =
254
+ "Alt text should communicate what an image means, not how it looks.";
255
+
256
+ const typeInto = (target: typeof p1, text: string) =>
257
+ new Promise<void>((resolve) => {
258
+ let i = 0;
259
+ const timer = window.setInterval(() => {
260
+ if (i < text.length) {
261
+ target.value += text[i];
262
+ i++;
263
+ } else {
264
+ clearInterval(timer);
265
+ resolve();
266
+ }
267
+ }, 15);
268
+ });
269
+
270
+ onMounted(async () => {
271
+ typingPhase.value = 1;
272
+ await typeInto(p1, text1);
273
+ typingPhase.value = 2;
274
+ await typeInto(p2, text2);
275
+ typingPhase.value = 0;
276
+ emit("ready");
277
+ });
278
+
279
+ return () =>
280
+ h("div", { class: "doc-images-block" }, [
281
+ h("img", {
282
+ class: "doc-float-icon",
283
+ src: accessIconImg,
284
+ alt: "Web Access Symbol",
285
+ width: DOCX_FLOAT_ICON_SIZE_PX,
286
+ height: DOCX_FLOAT_ICON_SIZE_PX,
287
+ }),
288
+ h("p", { class: "doc-p doc-images-p" }, [
289
+ p1.value,
290
+ typingPhase.value === 1
291
+ ? h("span", { class: "typewriter-cursor" }, "|")
292
+ : null,
293
+ ]),
294
+ h("p", { class: "doc-p doc-images-p" }, [
295
+ p2.value,
296
+ typingPhase.value === 2
297
+ ? h("span", { class: "typewriter-cursor" }, "|")
298
+ : null,
299
+ ]),
300
+ ]);
301
+ },
302
+ })
303
+ );
304
+
305
+ const CHART_INTRO_TEXT =
306
+ "Some images, such as charts or graphs, require long descriptions, but not all document types allow that. In web pages, long descriptions may be provided in several ways: on the page below the image, via a link below the image, or via a link on the image.";
307
+
308
+ // 3. 饼图(左)+ 说明文字(右)左右布局
309
+ const PieChartIntro = markRaw(
310
+ defineComponent({
311
+ name: "PieChartIntro",
312
+ setup(_, { emit }) {
313
+ const displayed = ref("");
314
+ const isTyping = ref(false);
315
+ let timer: number | null = null;
316
+
317
+ onMounted(() => {
318
+ isTyping.value = true;
319
+ let i = 0;
320
+ timer = window.setInterval(() => {
321
+ if (i < CHART_INTRO_TEXT.length) {
322
+ displayed.value += CHART_INTRO_TEXT[i];
323
+ i++;
324
+ } else {
325
+ if (timer) clearInterval(timer);
326
+ timer = null;
327
+ isTyping.value = false;
328
+ emit("ready");
329
+ }
330
+ }, 15);
331
+ });
332
+
333
+ return () =>
334
+ h("div", { class: "doc-chart-intro" }, [
335
+ h("div", { class: "doc-chart-intro__left" }, [
336
+ h("img", {
337
+ class: "doc-pie-chart-img",
338
+ src: pieChartImg,
339
+ alt: "Screen reader market share chart",
340
+ width: DOCX_PIE_CHART_MAX_WIDTH_PX,
341
+ height: DOCX_PIE_CHART_MAX_HEIGHT_PX,
342
+ }),
343
+ ]),
344
+ h("div", { class: "doc-chart-intro__right" }, [
345
+ h("p", { class: "doc-p doc-chart-intro-text" }, [
346
+ displayed.value,
347
+ isTyping.value
348
+ ? h("span", { class: "typewriter-cursor" }, "|")
349
+ : null,
350
+ ]),
351
+ ]),
352
+ ]);
353
+ },
354
+ })
355
+ );
356
+
357
+ // 4. 双栏排版(Columns 章节)
358
+ const ColumnsTypewriter = markRaw(
359
+ defineComponent({
360
+ name: "ColumnsTypewriter",
361
+ setup(_, { emit }) {
362
+ const col1 = ref("");
363
+ const col2 = ref("");
364
+ const typingCol = ref<1 | 2 | 0>(0);
365
+ const text1 =
366
+ "This is an example of columns. With columns, the page is split into two or more horizontal sections. Unlike tables, in which you usually read across a row and then down to the next, in columns, you read down a column and then across to the next.";
367
+ const text2 =
368
+ "When columns are not created correctly, screen readers may run lines together, reading the first line of the first column, then the first line of the second column, then the second line of the first column, and so on. Obviously, that is not accessible.";
369
+
370
+ const typeInto = (target: typeof col1, text: string) =>
371
+ new Promise<void>((resolve) => {
372
+ let i = 0;
373
+ const timer = window.setInterval(() => {
374
+ if (i < text.length) {
375
+ target.value += text[i];
376
+ i++;
377
+ } else {
378
+ clearInterval(timer);
379
+ resolve();
380
+ }
381
+ }, 15);
382
+ });
383
+
384
+ onMounted(async () => {
385
+ typingCol.value = 1;
386
+ await typeInto(col1, text1);
387
+ typingCol.value = 2;
388
+ await typeInto(col2, text2);
389
+ typingCol.value = 0;
390
+ emit("ready");
391
+ });
392
+
393
+ return () =>
394
+ h("div", { class: "doc-columns" }, [
395
+ h("div", { class: "doc-col" }, [
396
+ col1.value,
397
+ typingCol.value === 1 ? h("span", { class: "typewriter-cursor" }, "|") : null,
398
+ ]),
399
+ h("div", { class: "doc-col" }, [
400
+ col2.value,
401
+ typingCol.value === 2 ? h("span", { class: "typewriter-cursor" }, "|") : null,
402
+ ]),
403
+ ]);
404
+ },
405
+ })
406
+ );
407
+
408
+ const tableAttrs = () => ({
409
+ class: "doc-table",
410
+ style: {
411
+ width: "100%",
412
+ borderCollapse: "collapse" as const,
413
+ border: "1px solid #000000",
414
+ },
415
+ cellspacing: "0",
416
+ cellpadding: "0",
417
+ border: "1",
418
+ });
419
+
420
+ const thCell = (
421
+ text: string,
422
+ extra: { style?: Record<string, string>; [key: string]: unknown } = {}
423
+ ) => {
424
+ const { style: extraStyle, ...rest } = extra;
425
+ return h(
426
+ "th",
427
+ {
428
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", ...extraStyle },
429
+ ...rest,
430
+ },
431
+ text
432
+ );
433
+ };
434
+
435
+ const tdCell = (text: string, alignRight = false) =>
436
+ h(
437
+ "td",
438
+ {
439
+ style: {
440
+ ...TABLE_CELL_STYLE,
441
+ textAlign: alignRight ? "right" : "left",
442
+ },
443
+ },
444
+ text
445
+ );
446
+
447
+ // 5. 简单数据表格(Simple Table: 统一行列且不包含合并单元格)
448
+ const SimpleDataTable = markRaw(
449
+ defineComponent({
450
+ name: "SimpleDataTable",
451
+ setup(_, { emit }) {
452
+ const rows = [
453
+ ["JAWS", "853", "49%"],
454
+ ["NVDA", "238", "14%"],
455
+ ["Window-Eyes", "214", "12%"],
456
+ ["System Access", "181", "10%"],
457
+ ["VoiceOver", "159", "9%"],
458
+ ];
459
+ onMounted(() => emit("ready"));
460
+ return () =>
461
+ h("table", tableAttrs(), [
462
+ h("thead", {}, [
463
+ h("tr", { class: "doc-table-head-green" }, [
464
+ thCell("Screen Reader", {
465
+ class: "doc-th-green",
466
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", ...TH_GREEN_STYLE },
467
+ }),
468
+ thCell("Responses", {
469
+ class: "doc-th-green",
470
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "right", ...TH_GREEN_STYLE },
471
+ }),
472
+ thCell("Share", {
473
+ class: "doc-th-green",
474
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "right", ...TH_GREEN_STYLE },
475
+ }),
476
+ ]),
477
+ ]),
478
+ h(
479
+ "tbody",
480
+ {},
481
+ rows.map((row) =>
482
+ h("tr", {}, [
483
+ tdCell(row[0]!),
484
+ tdCell(row[1]!, true),
485
+ tdCell(row[2]!, true),
486
+ ])
487
+ )
488
+ ),
489
+ ]);
490
+ },
491
+ })
492
+ );
493
+
494
+ // 6. 复杂合并表头数据表格(Complex Table: 包含顶层跨列合并单元格字段)
495
+ const ComplexDataTable = markRaw(
496
+ defineComponent({
497
+ name: "ComplexDataTable",
498
+ setup(_, { emit }) {
499
+ onMounted(() => emit("ready"));
500
+ return () =>
501
+ h("table", { ...tableAttrs(), class: "doc-table doc-table-complex" }, [
502
+ h("thead", {}, [
503
+ h("tr", { class: "doc-table-head-red" }, [
504
+ thCell("Screen Reader", {
505
+ rowspan: 2,
506
+ class: "doc-th-red",
507
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", ...TH_RED_STYLE },
508
+ }),
509
+ thCell("May 2012", {
510
+ colspan: 2,
511
+ class: "doc-th-red",
512
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "center", ...TH_RED_STYLE },
513
+ }),
514
+ thCell("September 2010", {
515
+ colspan: 2,
516
+ class: "doc-th-red",
517
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "center", ...TH_RED_STYLE },
518
+ }),
519
+ ]),
520
+ h("tr", { class: "doc-table-head-red" }, [
521
+ thCell("Responses", {
522
+ class: "doc-th-red",
523
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "right", ...TH_RED_STYLE },
524
+ }),
525
+ thCell("Share", {
526
+ class: "doc-th-red",
527
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "right", ...TH_RED_STYLE },
528
+ }),
529
+ thCell("Responses", {
530
+ class: "doc-th-red",
531
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "right", ...TH_RED_STYLE },
532
+ }),
533
+ thCell("Share", {
534
+ class: "doc-th-red",
535
+ style: { ...TABLE_CELL_STYLE, fontWeight: "bold", textAlign: "right", ...TH_RED_STYLE },
536
+ }),
537
+ ]),
538
+ ]),
539
+ h("tbody", {}, [
540
+ ["JAWS", "853", "49%", "727", "59%"],
541
+ ["NVDA", "238", "14%", "105", "9%"],
542
+ ["Window-Eyes", "214", "12%", "138", "11%"],
543
+ ["System Access", "181", "10%", "58", "5%"],
544
+ ["VoiceOver", "159", "9%", "120", "10%"],
545
+ ].map((row) =>
546
+ h(
547
+ "tr",
548
+ {},
549
+ row.map((cell, i) => tdCell(cell, i > 0))
550
+ )
551
+ )),
552
+ ]);
553
+ },
554
+ })
555
+ );
556
+
557
+ // ---------- 基于 docx 原始拓扑构造的核心流水展示序列 ----------
558
+ interface DisplayItem {
559
+ type: "text" | "component" | "page-break";
560
+ content?: string;
561
+ styleClass?: string;
562
+ component?: Component;
563
+ }
564
+
565
+ const displaySequence = ref<DisplayItem[]>([
566
+ // PAGE 1: 导言与排版大纲
567
+ { type: "text", content: "Sample Document", styleClass: "doc-h1" },
568
+ { type: "text", content: "This document was created using accessibility techniques for headings, lists, image alternate text, tables, and columns. It should be completely accessible using assistive technologies such as screen readers.", styleClass: "doc-p" },
569
+
570
+ { type: "text", content: "Headings", styleClass: "doc-h2" },
571
+ { type: "text", content: "There are eight section headings in this document. At the beginning, \"Sample Document\" is a level 1 heading. The main section headings, such as \"Headings\" and \"Lists\" are level 2 headings. The Tables section contains two sub-headings, \"Simple Table\" and \"Complex Table,\" which are both level 3 headings.", styleClass: "doc-p" },
572
+
573
+ { type: "text", content: "Lists", styleClass: "doc-h2" },
574
+ { type: "text", content: "The following outline of the sections of this document is an ordered (numbered) list with six items. The fifth item, \"Tables,\" contains a nested unordered (bulleted) list with two items.", styleClass: "doc-p" },
575
+ { type: "component", component: DocumentOutlineList },
576
+
577
+ // 插入物理分页符点:拦截断层
578
+ { type: "page-break" },
579
+
580
+ // PAGE 2: 链接与多媒体无障碍设计说明
581
+ { type: "text", content: "Links", styleClass: "doc-h2" },
582
+ { type: "text", content: "In web documents, links can point different locations on the page, different pages, or even downloadable documents, such as Word documents or PDFs:", styleClass: "doc-p" },
583
+ { type: "text", content: "Top of this Page", styleClass: "doc-link-line" },
584
+ { type: "text", content: "Sample Document", styleClass: "doc-link-line" },
585
+ { type: "text", content: "Sample Document (docx)", styleClass: "doc-link-line" },
586
+
587
+ { type: "text", content: "Images", styleClass: "doc-h2" },
588
+ { type: "component", component: ImagesFloatSection },
589
+ { type: "text", content: "Some images, such as charts or graphs, require long descriptions, but not all document types allow that. In web pages, long descriptions may be provided in several ways: on the page below the image, via a link below the image, or via a link on the image.", styleClass: "doc-p" },
590
+
591
+ { type: "page-break" },
592
+
593
+ // PAGE 3: 饼图说明(左右布局)+ 简单/复杂表格
594
+ { type: "component", component: PieChartIntro },
595
+ { type: "text", content: "Tables", styleClass: "doc-h2" },
596
+ { type: "text", content: "Simple Tables", styleClass: "doc-h3" },
597
+ { type: "text", content: "Simple tables have a uniform number of columns and rows, without any merged cells:", styleClass: "doc-p" },
598
+ { type: "component", component: SimpleDataTable },
599
+
600
+ { type: "text", content: "Complex Tables", styleClass: "doc-h3" },
601
+ { type: "text", content: "The following is a complex table, using merged cells as headers for sections within the table. This can't be made accessible in all types of documents:", styleClass: "doc-p" },
602
+ { type: "component", component: ComplexDataTable },
603
+
604
+ { type: "page-break" },
605
+
606
+ // PAGE 4: 分栏排版特征说明
607
+ { type: "text", content: "Columns", styleClass: "doc-h2" },
608
+ { type: "component", component: ColumnsTypewriter },
609
+ ]);
610
+
611
+ // ---------- 线性打字机驱动器进程控制 ----------
612
+ const nextItem = () => {
613
+ if (activeIndex.value < displaySequence.value.length) {
614
+ activeIndex.value++;
615
+ const newItem = displaySequence.value[activeIndex.value - 1];
616
+ if (!newItem) return;
617
+ if (newItem.type === "text") {
618
+ lastStartedTextIndex.value = activeIndex.value - 1;
619
+ }
620
+ }
621
+ };
622
+
623
+ const onTextCompleted = () => nextItem();
624
+ const onComponentReady = () => nextItem();
625
+
626
+ const startSequence = () => {
627
+ if (displaySequence.value.length === 0) return;
628
+ activeIndex.value = 1;
629
+ const firstItem = displaySequence.value[0];
630
+ if (firstItem?.type === "text") lastStartedTextIndex.value = 0;
631
+ };
632
+
633
+ const restartDemo = () => {
634
+ activeIndex.value = 0;
635
+ lastStartedTextIndex.value = -1;
636
+ nextTick(() => startSequence());
637
+ };
638
+
639
+ onMounted(() => startSequence());
640
+
641
+ defineExpose({
642
+ handleExportPDF,
643
+ handleExportWord,
644
+ restartDemo,
645
+ });
646
+
647
+ const STYLE_TAG_MAP: Record<string, string> = {
648
+ "doc-h1": "h1",
649
+ "doc-h2": "h2",
650
+ "doc-h3": "h3",
651
+ "doc-p": "p",
652
+ "doc-link-line": "p",
653
+ };
654
+
655
+ // ---------- 打字机文本流(1:1 复现 Word 样式 + 竖线光标) ----------
656
+ const TypewriterText = defineComponent({
657
+ name: "TypewriterText",
658
+ props: {
659
+ text: { type: String, required: true },
660
+ autoStart: { type: Boolean, default: false },
661
+ customClass: { type: String, default: "" },
662
+ },
663
+ emits: ["completed"],
664
+ setup(props, { emit }) {
665
+ const displayed = ref("");
666
+ const isTyping = ref(false);
667
+ let timer: number | null = null;
668
+
669
+ const classList = () =>
670
+ props.customClass
671
+ .split(/\s+/)
672
+ .filter(Boolean)
673
+ .concat("typewriter-paragraph");
674
+
675
+ const resolveTag = () => {
676
+ const primary = props.customClass.split(/\s+/).find((c) => STYLE_TAG_MAP[c]);
677
+ return primary ? (STYLE_TAG_MAP[primary] ?? "p") : "p";
678
+ };
679
+
680
+ const startTyping = () => {
681
+ if (timer) return;
682
+ displayed.value = "";
683
+ isTyping.value = true;
684
+ let i = 0;
685
+ timer = window.setInterval(() => {
686
+ if (i < props.text.length) {
687
+ displayed.value += props.text[i];
688
+ i++;
689
+ } else {
690
+ if (timer) clearInterval(timer);
691
+ timer = null;
692
+ isTyping.value = false;
693
+ emit("completed");
694
+ }
695
+ }, 15);
696
+ };
697
+
698
+ watch(
699
+ () => props.autoStart,
700
+ (val) => {
701
+ if (val) startTyping();
702
+ },
703
+ { immediate: true }
704
+ );
705
+
706
+ return () => {
707
+ const tag = resolveTag();
708
+ const linkText =
709
+ tag === "p" && props.customClass.includes("doc-link-line")
710
+ ? h("a", { class: "doc-link-anchor", href: "#" }, displayed.value)
711
+ : displayed.value;
712
+
713
+ return h(tag as keyof HTMLElementTagNameMap, { class: classList() }, [
714
+ linkText,
715
+ isTyping.value ? h("span", { class: "typewriter-cursor" }, "|") : null,
716
+ ]);
717
+ };
718
+ },
719
+ });
720
+ </script>
721
+
722
+ <style scoped>
723
+ .typewriter-card {
724
+ background: #ffffff;
725
+ border: 1px solid #e8eaec;
726
+ border-radius: 8px;
727
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
728
+ overflow: hidden;
729
+ }
730
+
731
+ .typewriter-card__toolbar {
732
+ display: flex;
733
+ align-items: center;
734
+ justify-content: space-between;
735
+ gap: 12px;
736
+ padding: 12px 16px;
737
+ border-bottom: 1px solid #e8eaec;
738
+ background: #fafafa;
739
+ }
740
+
741
+ .typewriter-card__actions {
742
+ display: flex;
743
+ align-items: center;
744
+ gap: 8px;
745
+ flex-wrap: wrap;
746
+ }
747
+
748
+ .typewriter-card__body {
749
+ padding: 32px 40px;
750
+ min-height: 600px;
751
+ overflow-x: hidden;
752
+ box-sizing: border-box;
753
+ }
754
+
755
+ /* ---------- Sample Document 1:1 样式(对齐 Word / 参考图) ---------- */
756
+ .doc-sample-root,
757
+ .doc-sample-body {
758
+ font-family: Calibri, "Segoe UI", Arial, Helvetica, sans-serif;
759
+ font-size: 11pt;
760
+ color: #000000;
761
+ line-height: 1.15;
762
+ text-align: left;
763
+ }
764
+
765
+ .doc-h1 {
766
+ font-size: 20pt;
767
+ font-weight: bold;
768
+ color: #c00000;
769
+ text-align: left;
770
+ margin: 0 0 12pt;
771
+ padding: 0;
772
+ border: none;
773
+ }
774
+
775
+ .doc-h2 {
776
+ font-size: 14pt;
777
+ font-weight: bold;
778
+ color: #000000;
779
+ margin: 12pt 0 6pt;
780
+ padding: 0;
781
+ border: none;
782
+ }
783
+
784
+ .doc-h3 {
785
+ font-size: 12pt;
786
+ font-weight: bold;
787
+ color: #000000;
788
+ margin: 10pt 0 4pt;
789
+ }
790
+
791
+ .doc-p {
792
+ font-size: 11pt;
793
+ line-height: 1.15;
794
+ color: #000000;
795
+ margin: 0 0 10pt;
796
+ text-align: left;
797
+ }
798
+
799
+ .doc-images-block {
800
+ overflow: hidden;
801
+ margin: 0 0 10pt;
802
+ max-width: 100%;
803
+ box-sizing: border-box;
804
+ }
805
+
806
+ .doc-images-block .doc-images-p {
807
+ margin: 0 0 10pt;
808
+ text-align: left;
809
+ }
810
+
811
+ .doc-images-block .doc-images-p:last-child {
812
+ margin-bottom: 0;
813
+ }
814
+
815
+ .doc-link-line {
816
+ font-size: 11pt;
817
+ margin: 0 0 2pt;
818
+ padding: 0;
819
+ }
820
+
821
+ .doc-link-line :deep(.doc-link-anchor) {
822
+ color: #0563c1;
823
+ text-decoration: underline;
824
+ cursor: pointer;
825
+ }
826
+
827
+ .doc-ol {
828
+ list-style-type: decimal;
829
+ padding-left: 0.5in;
830
+ margin: 6pt 0 10pt;
831
+ font-size: 11pt;
832
+ color: #000000;
833
+ }
834
+
835
+ .doc-ol > li {
836
+ margin-bottom: 2pt;
837
+ line-height: 1.15;
838
+ }
839
+
840
+ .doc-ul {
841
+ list-style-type: disc;
842
+ padding-left: 0.25in;
843
+ margin: 2pt 0 0;
844
+ }
845
+
846
+ .doc-ul > li {
847
+ margin-bottom: 2pt;
848
+ }
849
+
850
+ .doc-float-icon,
851
+ :deep(.doc-float-icon) {
852
+ float: left;
853
+ width: 72px;
854
+ height: 72px;
855
+ max-width: 72px;
856
+ max-height: 72px;
857
+ margin: 0 12pt 8pt 0;
858
+ box-sizing: border-box;
859
+ }
860
+
861
+ .doc-chart-intro {
862
+ display: table;
863
+ width: 100%;
864
+ max-width: 100%;
865
+ table-layout: fixed;
866
+ margin: 0 0 10pt;
867
+ overflow: hidden;
868
+ box-sizing: border-box;
869
+ }
870
+
871
+ .doc-chart-intro__left,
872
+ .doc-chart-intro__right {
873
+ display: table-cell;
874
+ vertical-align: top;
875
+ }
876
+
877
+ .doc-chart-intro__left {
878
+ width: 42%;
879
+ max-width: 220px;
880
+ padding-right: 12pt;
881
+ box-sizing: border-box;
882
+ }
883
+
884
+ .doc-chart-intro__right {
885
+ width: 58%;
886
+ }
887
+
888
+ .doc-pie-chart-img,
889
+ :deep(.doc-pie-chart-img) {
890
+ display: block;
891
+ width: 100%;
892
+ max-width: 220px;
893
+ max-height: 120px;
894
+ height: auto;
895
+ object-fit: contain;
896
+ box-sizing: border-box;
897
+ }
898
+
899
+ .doc-chart-intro-text {
900
+ margin: 0;
901
+ text-align: left;
902
+ }
903
+
904
+ :deep(.doc-table) {
905
+ width: 100%;
906
+ max-width: 100%;
907
+ table-layout: fixed;
908
+ border-collapse: collapse;
909
+ margin: 6pt 0 12pt;
910
+ font-size: 11pt;
911
+ color: #000000;
912
+ border: 1px solid #000000;
913
+ box-sizing: border-box;
914
+ word-wrap: break-word;
915
+ }
916
+
917
+ :deep(.doc-table th),
918
+ :deep(.doc-table td) {
919
+ border: 1px solid #000000 !important;
920
+ padding: 4pt 6pt;
921
+ vertical-align: top;
922
+ word-wrap: break-word;
923
+ overflow-wrap: break-word;
924
+ box-sizing: border-box;
925
+ }
926
+
927
+ :deep(.doc-table th) {
928
+ font-weight: bold;
929
+ text-align: left;
930
+ }
931
+
932
+ :deep(.doc-th-green),
933
+ :deep(th.doc-th-green) {
934
+ background-color: #c6efce !important;
935
+ color: #000000 !important;
936
+ }
937
+
938
+ :deep(.doc-th-red),
939
+ :deep(th.doc-th-red) {
940
+ background-color: #ffc7ce !important;
941
+ color: #000000 !important;
942
+ }
943
+
944
+ :deep(.doc-table-num) {
945
+ text-align: right;
946
+ }
947
+
948
+ :deep(.doc-table-center) {
949
+ text-align: center;
950
+ }
951
+
952
+ .doc-columns {
953
+ display: table;
954
+ width: 100%;
955
+ table-layout: fixed;
956
+ margin: 0 0 10pt;
957
+ font-size: 11pt;
958
+ line-height: 1.15;
959
+ color: #000000;
960
+ }
961
+
962
+ .doc-col {
963
+ display: table-cell;
964
+ width: 50%;
965
+ vertical-align: top;
966
+ padding-right: 12pt;
967
+ text-align: left;
968
+ }
969
+
970
+ .doc-col:last-child {
971
+ padding-right: 0;
972
+ padding-left: 6pt;
973
+ }
974
+
975
+ .typewriter-paragraph {
976
+ white-space: pre-wrap;
977
+ word-break: break-word;
978
+ }
979
+
980
+ .typewriter-cursor {
981
+ display: inline-block;
982
+ color: inherit;
983
+ font-weight: normal;
984
+ margin-left: 1px;
985
+ animation: typewriter-blink 1s step-end infinite;
986
+ }
987
+
988
+ .typewriter-cursor--global {
989
+ font-size: 11pt;
990
+ color: #000000;
991
+ margin-top: 4pt;
992
+ }
993
+
994
+ @keyframes typewriter-blink {
995
+ 50% { opacity: 0; }
996
+ }
997
+
998
+ /* 打字机预览:分页标记不可见,仅用于导出 Word 时定位 */
999
+ .page-break-marker {
1000
+ display: none !important;
1001
+ width: 0;
1002
+ height: 0;
1003
+ margin: 0;
1004
+ padding: 0;
1005
+ border: 0;
1006
+ overflow: hidden;
1007
+ }
1008
+
1009
+ .gen-ai-index9-embedded {
1010
+ flex: 1;
1011
+ min-height: 0;
1012
+ height: 100%;
1013
+ display: flex;
1014
+ flex-direction: column;
1015
+ overflow: hidden;
1016
+ background: #fff;
1017
+ }
1018
+
1019
+ .gen-ai-index9-embedded__main {
1020
+ flex: 1;
1021
+ min-height: 0;
1022
+ height: 100%;
1023
+ overflow-y: auto;
1024
+ padding: 0;
1025
+ }
1026
+
1027
+ .gen-ai-index9-embedded__inner {
1028
+ width: 100%;
1029
+ max-width: none;
1030
+ min-height: 100%;
1031
+ margin: 0;
1032
+ display: flex;
1033
+ flex-direction: column;
1034
+ }
1035
+
1036
+ .typewriter-card--embedded {
1037
+ width: 100%;
1038
+ flex: 1;
1039
+ min-height: 100%;
1040
+ display: flex;
1041
+ flex-direction: column;
1042
+ border: none;
1043
+ border-radius: 0;
1044
+ box-shadow: none;
1045
+ background: #fff;
1046
+ }
1047
+
1048
+ .typewriter-card--embedded .typewriter-card__body {
1049
+ flex: 1;
1050
+ width: 100%;
1051
+ min-height: 100%;
1052
+ padding: 28px 32px 36px;
1053
+ box-sizing: border-box;
1054
+ background: #fff;
1055
+ overflow-x: hidden;
1056
+ overflow-y: auto;
1057
+ }
1058
+
1059
+ @media print {
1060
+ .no-print,
1061
+ .no-export {
1062
+ display: none !important;
1063
+ }
1064
+ }
1065
+ </style>