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.
- package/package.json +12 -0
- package/src/App.vue +3 -0
- package/src/api/index.ts +19 -0
- package/src/api/modules/gen-ai/gen-entry/index.ts +30 -0
- package/src/api/modules/gen-ai/model-manager/index.ts +42 -0
- package/src/api/modules/gen-ai/model-manager/mockApi.ts +33 -0
- package/src/api/modules/index.ts +98 -0
- package/src/api/modules/user/index.ts +4 -0
- package/src/api/request.ts +102 -0
- package/src/assets/sample-access-icon.png +0 -0
- package/src/assets/sample-pie-chart.png +0 -0
- package/src/assets/vue.svg +1 -0
- package/src/components/CapsuleScrollbar.vue +93 -0
- package/src/components/Export/ExcelExport.vue +592 -0
- package/src/components/Export/ExcelExport2.vue +494 -0
- package/src/components/Export/ExcelExport3.vue +342 -0
- package/src/components/Export/ExcelExport4.vue +665 -0
- package/src/components/Export/excelExport.js +547 -0
- package/src/components/Export/excelExport.ts +551 -0
- package/src/components/GEN-AI/index.vue +142 -0
- package/src/components/GEN-AI/index1.vue +456 -0
- package/src/components/GEN-AI/index10.vue +5 -0
- package/src/components/GEN-AI/index2.vue +568 -0
- package/src/components/GEN-AI/index3.vue +623 -0
- package/src/components/GEN-AI/index4.vue +629 -0
- package/src/components/GEN-AI/index5.vue +578 -0
- package/src/components/GEN-AI/index6.vue +656 -0
- package/src/components/GEN-AI/index7.vue +717 -0
- package/src/components/GEN-AI/index8.vue +405 -0
- package/src/components/GEN-AI/index9.vue +1065 -0
- package/src/components/GEN-AI/types.ts +12 -0
- package/src/components/GEN-AI/utils.ts +42 -0
- package/src/components/HelloWorld.vue +41 -0
- package/src/components/PageCard.vue +7 -0
- package/src/components/PageHeader.vue +32 -0
- package/src/components/backup/index5 copy.vue +556 -0
- package/src/components/backup/index5.vue +620 -0
- package/src/components/backup/index9 copy.vue +1029 -0
- package/src/components/backup/index9-pro.vue +1065 -0
- package/src/components/backup/index9.vue +1057 -0
- package/src/components/el-date-picker.vue +64 -0
- package/src/directives/btnLoading.ts +427 -0
- package/src/directives/debounce copy.ts +670 -0
- package/src/directives/debounce.ts +98 -0
- package/src/directives/index.ts +25 -0
- package/src/layouts/MainLayout.vue +101 -0
- package/src/main.ts +85 -0
- package/src/router/index.ts +76 -0
- package/src/router/menus.ts +28 -0
- package/src/style.css +79 -0
- package/src/styles/_variables.scss +24 -0
- package/src/styles/app-button.css +26 -0
- package/src/styles/element-overrides.css +23 -0
- package/src/styles/global.css +44 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/page-card.css +21 -0
- package/src/styles/variables.css +26 -0
- package/src/test/mock.ts +101 -0
- package/src/test/test1.vue +402 -0
- package/src/test/test2.vue +1689 -0
- package/src/types/gen-ai/gen-entry/index.ts +17 -0
- package/src/types/gen-ai/model-manager/index.ts +19 -0
- package/src/utils/docxExport.ts +1610 -0
- package/src/utils/gen-ai-navigation.ts +37 -0
- package/src/utils/gen-ai-scroll.ts +455 -0
- package/src/utils/openDataLoaderWordExport.ts +33 -0
- package/src/utils/pageScrollbar.ts +115 -0
- package/src/utils/randomTranscode.ts +87 -0
- package/src/utils/reportPdfExport.ts +44 -0
- package/src/views/AdminCenter/index.vue +817 -0
- package/src/views/Blank.vue +68 -0
- package/src/views/Home.vue +29 -0
- package/src/views/ReportCenter/index.vue +1380 -0
- package/src/views/TemplateCenter/Knowledge.ts +83 -0
- package/src/views/TemplateCenter/data.d.ts +10 -0
- package/src/views/TemplateCenter/index.vue +1205 -0
- package/src/views/TemplateCenter/service.ts +69 -0
- package/src/views/gen-ai/components/RecentReportsTable.vue +193 -0
- package/src/views/gen-ai/gen-entry/index.vue +309 -0
- package/src/views/gen-ai/gen-entry/mockData.ts +160 -0
- package/src/views/gen-ai/management-center/index.vue +53 -0
- package/src/views/gen-ai/model-manager/ChapterTitleScroll.vue +275 -0
- package/src/views/gen-ai/model-manager/index.vue +1205 -0
- package/src/views/gen-ai/model-manager/mockData.ts +122 -0
- package/src/views/gen-ai/report-center/index.vue +158 -0
- package/src/vite-env.d.ts +38 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/components/GEN-AI/utils.ts
|
|
2
|
+
import html2pdf from 'html2pdf.js';
|
|
3
|
+
import { asBlob } from 'html-docx-js-typescript';
|
|
4
|
+
import { saveAs } from 'file-saver';
|
|
5
|
+
|
|
6
|
+
export const exportToPDF = (elementId: string, filename: string) => {
|
|
7
|
+
const element = document.getElementById(elementId);
|
|
8
|
+
|
|
9
|
+
if (!element) return;
|
|
10
|
+
|
|
11
|
+
const opt = {
|
|
12
|
+
margin: 10,
|
|
13
|
+
filename: `${filename}.pdf`,
|
|
14
|
+
image: { type: 'jpeg' as const, quality: 0.98 },
|
|
15
|
+
html2canvas: { scale: 2, useCORS: true },
|
|
16
|
+
jsPDF: { unit: 'mm' as const, format: 'a4' as const, orientation: 'portrait' as const }
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
html2pdf().set(opt).from(element).save();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const exportToWord = async (elementId: string, filename: string) => {
|
|
23
|
+
const element = document.getElementById(elementId);
|
|
24
|
+
|
|
25
|
+
if (!element) return;
|
|
26
|
+
|
|
27
|
+
// 包装标准的 HTML 文档结构,否则 Word 打开可能乱码
|
|
28
|
+
const htmlString = `
|
|
29
|
+
<!DOCTYPE html>
|
|
30
|
+
<html lang="zh">
|
|
31
|
+
<head><meta charset="utf-8"></head>
|
|
32
|
+
<body>${element.innerHTML}</body>
|
|
33
|
+
</html>
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const blob = await asBlob(htmlString);
|
|
38
|
+
saveAs(blob as Blob, `${filename}.docx`);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Word 匯出失敗:', error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
|
|
4
|
+
defineProps<{ msg: string }>();
|
|
5
|
+
|
|
6
|
+
const count = ref(0);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<h1>{{ msg }}</h1>
|
|
11
|
+
|
|
12
|
+
<div class="card">
|
|
13
|
+
<button type="button" @click="count++">count is {{ count }}</button>
|
|
14
|
+
<p>
|
|
15
|
+
// Edit
|
|
16
|
+
<code>components/HelloWorld.vue</code> to test HMR
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<p>
|
|
21
|
+
Check out
|
|
22
|
+
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
|
23
|
+
>create-vue</a
|
|
24
|
+
>, the official Vue + Vite starter
|
|
25
|
+
</p>
|
|
26
|
+
<p>
|
|
27
|
+
Learn more about IDE Support for Vue in the
|
|
28
|
+
<a
|
|
29
|
+
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
|
30
|
+
target="_blank"
|
|
31
|
+
>Vue Docs Scaling up Guide</a
|
|
32
|
+
>.
|
|
33
|
+
</p>
|
|
34
|
+
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<style scoped>
|
|
38
|
+
.read-the-docs {
|
|
39
|
+
color: #888;
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<header class="page-header">
|
|
3
|
+
<h1 class="page-header__title">{{ title }}</h1>
|
|
4
|
+
</header>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
defineProps<{
|
|
9
|
+
title: string
|
|
10
|
+
}>()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<style scoped>
|
|
14
|
+
.page-header {
|
|
15
|
+
flex-shrink: 0;
|
|
16
|
+
height: 62px;
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
padding: 0 24px;
|
|
20
|
+
background: #fff;
|
|
21
|
+
border-bottom: 1px solid #e8eaec;
|
|
22
|
+
box-shadow: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.page-header__title {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-size: 16px;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
color: #2d3139;
|
|
30
|
+
line-height: 1;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex h-screen bg-[#F3F4F6] text-[#333]">
|
|
3
|
+
<main class="flex-1 overflow-y-auto p-12">
|
|
4
|
+
<div
|
|
5
|
+
class="max-w-5xl mx-auto bg-white shadow-sm border border-gray-100 rounded-sm p-10 min-h-screen relative"
|
|
6
|
+
>
|
|
7
|
+
<!-- 导出按钮组(不参与 Word/PDF 导出) -->
|
|
8
|
+
<div
|
|
9
|
+
class="absolute top-6 right-8 flex gap-4 no-print"
|
|
10
|
+
style="margin: 10px 0; text-align: end"
|
|
11
|
+
>
|
|
12
|
+
<el-button
|
|
13
|
+
@click="handleExportPDF"
|
|
14
|
+
size="small"
|
|
15
|
+
plain
|
|
16
|
+
type="danger"
|
|
17
|
+
:loading="isExporting"
|
|
18
|
+
>
|
|
19
|
+
匯出為PDF
|
|
20
|
+
</el-button>
|
|
21
|
+
<el-button
|
|
22
|
+
@click="handleExportWord"
|
|
23
|
+
size="small"
|
|
24
|
+
plain
|
|
25
|
+
type="danger"
|
|
26
|
+
:loading="isExporting"
|
|
27
|
+
>
|
|
28
|
+
匯出為Word
|
|
29
|
+
</el-button>
|
|
30
|
+
<el-button @click="handlePrint" size="small" plain type="success">
|
|
31
|
+
打印 / 另存為PDF
|
|
32
|
+
</el-button>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
id="report-container"
|
|
37
|
+
class="prose prose-slate max-w-none"
|
|
38
|
+
style="
|
|
39
|
+
margin: 0px;
|
|
40
|
+
padding: 10px;
|
|
41
|
+
border: 1px solid #ccc;
|
|
42
|
+
min-height: 600px;
|
|
43
|
+
"
|
|
44
|
+
>
|
|
45
|
+
<div class="section-title font-bold flex items-center mb-5 text-lg border-t pt-6">
|
|
46
|
+
正文:
|
|
47
|
+
<Edit3Icon :size="18" class="ml-2 text-[#C54E5E] cursor-pointer no-export" />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="content-wrapper space-y-4">
|
|
51
|
+
<template v-for="(item, idx) in displaySequence" :key="idx">
|
|
52
|
+
<TypewriterText
|
|
53
|
+
v-if="item.type === 'text' && idx < activeIndex"
|
|
54
|
+
:text="item.content!"
|
|
55
|
+
:auto-start="
|
|
56
|
+
idx === activeIndex - 1 && idx === lastStartedTextIndex
|
|
57
|
+
"
|
|
58
|
+
@completed="onTextCompleted"
|
|
59
|
+
class="text-paragraph"
|
|
60
|
+
/>
|
|
61
|
+
<component
|
|
62
|
+
v-else-if="item.type === 'component' && idx < activeIndex"
|
|
63
|
+
:is="item.component"
|
|
64
|
+
class="my-4"
|
|
65
|
+
@ready="onComponentReady"
|
|
66
|
+
/>
|
|
67
|
+
</template>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<span
|
|
71
|
+
v-if="activeIndex < displaySequence.length"
|
|
72
|
+
class="inline-block w-[2px] h-[18px] bg-[#C54E5E] animate-pulse ml-1 align-middle"
|
|
73
|
+
></span>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</main>
|
|
77
|
+
|
|
78
|
+
<aside class="w-80 bg-white border-l border-gray-200 p-6 no-print">
|
|
79
|
+
<h3 class="font-bold text-gray-700 mb-6">生成報告設置</h3>
|
|
80
|
+
<p class="text-sm text-gray-500">✨ 完整图文混排 + 打字机效果</p>
|
|
81
|
+
<p class="text-xs text-gray-400 mt-2">
|
|
82
|
+
包含:带状态表格 / 按钮组 / 图标集 / 完整文本段落 / ECharts混合图表<br />
|
|
83
|
+
上半部分+下半部分全部按顺序逐字/逐组件展示
|
|
84
|
+
</p>
|
|
85
|
+
<el-button @click="restartDemo" type="danger" plain class="w-full mt-6">
|
|
86
|
+
重新播放
|
|
87
|
+
</el-button>
|
|
88
|
+
<div class="mt-6 text-xs text-gray-400 border-t pt-4">
|
|
89
|
+
💡 提示:<br />
|
|
90
|
+
• 表格、按钮、图标、图表均会逐步出现<br />
|
|
91
|
+
• 文本逐字显示,完成后自动展示下一项<br />
|
|
92
|
+
• PDF/Word导出前自动等待所有内容渲染完成
|
|
93
|
+
</div>
|
|
94
|
+
</aside>
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<script setup lang="ts">
|
|
99
|
+
import {
|
|
100
|
+
ref,
|
|
101
|
+
onMounted,
|
|
102
|
+
onUnmounted,
|
|
103
|
+
defineComponent,
|
|
104
|
+
h,
|
|
105
|
+
markRaw,
|
|
106
|
+
type Component,
|
|
107
|
+
nextTick,
|
|
108
|
+
} from "vue";
|
|
109
|
+
import { Edit3Icon } from "lucide-vue-next";
|
|
110
|
+
import { ElButton, ElTable, ElTableColumn, ElIcon } from "element-plus";
|
|
111
|
+
import { Platform, Eleme, DeleteFilled, Delete } from "@element-plus/icons-vue";
|
|
112
|
+
import * as echarts from "echarts";
|
|
113
|
+
import html2canvas from "html2canvas";
|
|
114
|
+
import jsPDF from "jspdf";
|
|
115
|
+
import { saveAs } from "file-saver";
|
|
116
|
+
import {
|
|
117
|
+
buildWordDocumentHtml,
|
|
118
|
+
exportHtmlToDocx,
|
|
119
|
+
prepareDomForDocx,
|
|
120
|
+
rasterizeMediaForDocx,
|
|
121
|
+
} from "../../utils/docxExport";
|
|
122
|
+
|
|
123
|
+
// ---------- 导出工具函数 ----------
|
|
124
|
+
const isExporting = ref(false);
|
|
125
|
+
|
|
126
|
+
const waitForFullDisplay = async () => {
|
|
127
|
+
if (activeIndex.value < displaySequence.value.length) {
|
|
128
|
+
activeIndex.value = displaySequence.value.length;
|
|
129
|
+
await nextTick();
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// PDF 导出(保持不变)
|
|
135
|
+
const exportToPDF = async (containerId: string, filename: string) => {
|
|
136
|
+
const element = document.getElementById(containerId);
|
|
137
|
+
if (!element) return;
|
|
138
|
+
try {
|
|
139
|
+
isExporting.value = true;
|
|
140
|
+
await waitForFullDisplay();
|
|
141
|
+
const cursor = document.querySelector(".animate-pulse");
|
|
142
|
+
if (cursor) (cursor as HTMLElement).style.display = "none";
|
|
143
|
+
|
|
144
|
+
const canvas = await html2canvas(element, {
|
|
145
|
+
scale: 3,
|
|
146
|
+
backgroundColor: "#ffffff",
|
|
147
|
+
useCORS: true,
|
|
148
|
+
logging: false,
|
|
149
|
+
windowWidth: element.scrollWidth,
|
|
150
|
+
windowHeight: element.scrollHeight,
|
|
151
|
+
onclone: (clonedDoc, element) => {
|
|
152
|
+
const charts = clonedDoc.querySelectorAll(".echarts-container");
|
|
153
|
+
charts.forEach((chart: any) => {
|
|
154
|
+
chart.style.height = "400px";
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const imgData = canvas.toDataURL("image/png");
|
|
159
|
+
const pdf = new jsPDF("p", "mm", "a4");
|
|
160
|
+
const imgWidth = 210;
|
|
161
|
+
const pageHeight = 297;
|
|
162
|
+
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
163
|
+
let heightLeft = imgHeight;
|
|
164
|
+
let position = 0;
|
|
165
|
+
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
|
|
166
|
+
heightLeft -= pageHeight;
|
|
167
|
+
while (heightLeft > 0) {
|
|
168
|
+
position = heightLeft - imgHeight;
|
|
169
|
+
pdf.addPage();
|
|
170
|
+
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
|
|
171
|
+
heightLeft -= pageHeight;
|
|
172
|
+
}
|
|
173
|
+
pdf.save(`${filename}.pdf`);
|
|
174
|
+
if (cursor) (cursor as HTMLElement).style.display = "";
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("PDF导出失败:", error);
|
|
177
|
+
alert("PDF导出失败,请重试");
|
|
178
|
+
} finally {
|
|
179
|
+
isExporting.value = false;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const exportToWord = async (containerId: string, filename: string) => {
|
|
184
|
+
const element = document.getElementById(containerId);
|
|
185
|
+
if (!element) return;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
isExporting.value = true;
|
|
189
|
+
await waitForFullDisplay();
|
|
190
|
+
await nextTick();
|
|
191
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
192
|
+
|
|
193
|
+
const cloneContainer = element.cloneNode(true) as HTMLElement;
|
|
194
|
+
await rasterizeMediaForDocx(element, cloneContainer, echarts);
|
|
195
|
+
const prepared = prepareDomForDocx(cloneContainer, true);
|
|
196
|
+
const fullHtml = buildWordDocumentHtml(prepared.innerHTML);
|
|
197
|
+
const blob = await exportHtmlToDocx(fullHtml, {
|
|
198
|
+
orientation: "portrait",
|
|
199
|
+
margins: { top: 720, right: 720, bottom: 720, left: 720 },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
saveAs(blob, `${filename}.docx`);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error("Word 导出失败:", error);
|
|
205
|
+
alert("Word 导出失败,请检查浏览器控制台");
|
|
206
|
+
} finally {
|
|
207
|
+
isExporting.value = false;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const handleExportPDF = () =>
|
|
212
|
+
exportToPDF("report-container", "图文报告_房地产整合");
|
|
213
|
+
const handleExportWord = () =>
|
|
214
|
+
exportToWord("report-container", "图文报告_房地产整合");
|
|
215
|
+
const handlePrint = () => window.print();
|
|
216
|
+
|
|
217
|
+
// ---------- 报告文本 ----------
|
|
218
|
+
const rawReport = `2024年香港房地產市場整體呈現「價跌量穩、租金偏強、商業不振」的分化局面,全年各類物業價格普遍下調,但成交及租賃需求在政策放寬和人口流入支持下較前兩年有所改善。住宅方面,撤銷所有樓市「辣招」、放寬按揭成數及利率見頂後回落,帶動一手主導的成交回升,但無礙樓價全年再跌約中個百分比,反映經濟及供應壓力仍然主導價格走勢。
|
|
219
|
+
|
|
220
|
+
在住宅市場,本年特點包括:
|
|
221
|
+
一、銷售量回升、價格續調:2024年在全面撤辣及按揭寬鬆後,全年住宅買賣宗數按年錄得逾兩成增長,但全年樓價仍錄得約6%至7%的跌幅,延續過去兩年的調整趨勢。
|
|
222
|
+
二、供應顯著增加:私樓落成量約2.4萬個單位,較2023年大幅增加約七成,主要為中小型單位,為後市價格帶來持續壓力。
|
|
223
|
+
三、租金走勢優於樓價:在人口及人才計劃帶動下,住宅租金全年錄得中低個位升幅,租金指數距歷史高位僅約數個百分點,令小型單位的平均回報率升至近十多年高位水平。
|
|
224
|
+
|
|
225
|
+
在非住宅方面,2024年為商廈及舖位市場的「谷底年」,寫字樓空置率創逾十年新高,商業成交金額及宗數均跌至有紀錄以來低位。
|
|
226
|
+
|
|
227
|
+
下半部分额外内容:以下是按钮组与折柱图展示区。`;
|
|
228
|
+
|
|
229
|
+
const rawParagraphs = rawReport
|
|
230
|
+
.split(/\n\s*\n/)
|
|
231
|
+
.filter((p) => p.trim().length > 0);
|
|
232
|
+
const [para1, para2, para3, paraExtra] = rawParagraphs;
|
|
233
|
+
|
|
234
|
+
// ---------- 组件定义(markRaw) ----------
|
|
235
|
+
const ElStatusTable = markRaw(
|
|
236
|
+
defineComponent({
|
|
237
|
+
name: "ElStatusTable",
|
|
238
|
+
setup(_, { emit }) {
|
|
239
|
+
const tableData = [
|
|
240
|
+
{
|
|
241
|
+
date: "2016-05-02",
|
|
242
|
+
name: "王小虎",
|
|
243
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
244
|
+
status: "success",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
date: "2016-05-04",
|
|
248
|
+
name: "王小虎",
|
|
249
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
250
|
+
status: "info",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
date: "2016-05-01",
|
|
254
|
+
name: "王小虎",
|
|
255
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
256
|
+
status: "warning",
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
date: "2016-05-03",
|
|
260
|
+
name: "王小虎",
|
|
261
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
262
|
+
status: "danger",
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
const rowClassName = ({ row }: { row: any }) => {
|
|
266
|
+
if (row.status === "success") return "success-row";
|
|
267
|
+
if (row.status === "info") return "info-row";
|
|
268
|
+
if (row.status === "warning") return "warning-row";
|
|
269
|
+
if (row.status === "danger") return "danger-row";
|
|
270
|
+
return "";
|
|
271
|
+
};
|
|
272
|
+
onMounted(() => emit("ready"));
|
|
273
|
+
return () =>
|
|
274
|
+
h(
|
|
275
|
+
ElTable,
|
|
276
|
+
{
|
|
277
|
+
data: tableData,
|
|
278
|
+
style: { width: "100%" },
|
|
279
|
+
rowClassName,
|
|
280
|
+
border: true,
|
|
281
|
+
},
|
|
282
|
+
() => [
|
|
283
|
+
h(ElTableColumn, { prop: "date", label: "日期", width: "120" }),
|
|
284
|
+
h(ElTableColumn, { prop: "name", label: "姓名", width: "100" }),
|
|
285
|
+
h(ElTableColumn, { prop: "address", label: "地址" }),
|
|
286
|
+
h(
|
|
287
|
+
ElTableColumn,
|
|
288
|
+
{ label: "状态", width: "100" },
|
|
289
|
+
{
|
|
290
|
+
default: ({ row }: any) => {
|
|
291
|
+
let type = "success";
|
|
292
|
+
if (row.status === "info") type = "info";
|
|
293
|
+
if (row.status === "warning") type = "warning";
|
|
294
|
+
if (row.status === "danger") type = "danger";
|
|
295
|
+
return h(
|
|
296
|
+
ElButton,
|
|
297
|
+
{ size: "small", type, plain: true },
|
|
298
|
+
() => row.status
|
|
299
|
+
);
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
),
|
|
303
|
+
]
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const ButtonGroup = markRaw(
|
|
310
|
+
defineComponent({
|
|
311
|
+
setup(_, { emit }) {
|
|
312
|
+
onMounted(() => emit("ready"));
|
|
313
|
+
return () =>
|
|
314
|
+
h("div", { class: "flex flex-wrap gap-3 my-2" }, [
|
|
315
|
+
h(ElButton, { type: "primary" }, () => "主要按钮"),
|
|
316
|
+
h(ElButton, { type: "success" }, () => "成功按钮"),
|
|
317
|
+
h(ElButton, { type: "info" }, () => "信息按钮"),
|
|
318
|
+
h(ElButton, { type: "warning" }, () => "警告按钮"),
|
|
319
|
+
h(ElButton, { type: "danger" }, () => "危险按钮"),
|
|
320
|
+
h(ElButton, {}, () => "默认按钮"),
|
|
321
|
+
h(ElButton, { round: true }, () => "圆角按钮"),
|
|
322
|
+
]);
|
|
323
|
+
},
|
|
324
|
+
})
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const IconShowcase = markRaw(
|
|
328
|
+
defineComponent({
|
|
329
|
+
setup(_, { emit }) {
|
|
330
|
+
onMounted(() => emit("ready"));
|
|
331
|
+
return () =>
|
|
332
|
+
h(
|
|
333
|
+
"div",
|
|
334
|
+
{ class: "flex gap-4 items-center py-3 border-y border-gray-100" },
|
|
335
|
+
[
|
|
336
|
+
h(ElIcon, { size: 24, color: "#409EFF" }, () => h(Platform)),
|
|
337
|
+
h(ElIcon, { size: 24, color: "#67C23A" }, () => h(Eleme)),
|
|
338
|
+
h(ElIcon, { size: 24, color: "#F56C6C" }, () => h(DeleteFilled)),
|
|
339
|
+
h(ElIcon, { size: 24, color: "#E6A23C" }, () => h(Delete)),
|
|
340
|
+
h(
|
|
341
|
+
"span",
|
|
342
|
+
{ class: "text-sm text-gray-500 ml-2" },
|
|
343
|
+
"el-icon-platform-eleme / eleme / delete-filled / delete"
|
|
344
|
+
),
|
|
345
|
+
]
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const EChartsMixed = markRaw(
|
|
352
|
+
defineComponent({
|
|
353
|
+
name: "EChartsMixed",
|
|
354
|
+
setup(_, { emit }) {
|
|
355
|
+
const chartRef = ref<HTMLDivElement | null>(null);
|
|
356
|
+
let chartInstance: echarts.ECharts | null = null;
|
|
357
|
+
const initChart = () => {
|
|
358
|
+
if (!chartRef.value) return;
|
|
359
|
+
chartInstance = echarts.init(chartRef.value);
|
|
360
|
+
const option = {
|
|
361
|
+
tooltip: {
|
|
362
|
+
trigger: "axis",
|
|
363
|
+
axisPointer: { type: "cross", crossStyle: { color: "#999" } },
|
|
364
|
+
},
|
|
365
|
+
toolbox: {
|
|
366
|
+
feature: {
|
|
367
|
+
dataView: { show: true, readOnly: false },
|
|
368
|
+
magicType: { show: true, type: ["line", "bar"] },
|
|
369
|
+
restore: { show: true },
|
|
370
|
+
saveAsImage: { show: true },
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
legend: { data: ["Evaporation", "Precipitation", "Temperature"] },
|
|
374
|
+
xAxis: [
|
|
375
|
+
{
|
|
376
|
+
type: "category",
|
|
377
|
+
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
378
|
+
axisPointer: { type: "shadow" },
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
yAxis: [
|
|
382
|
+
{
|
|
383
|
+
type: "value",
|
|
384
|
+
name: "Precipitation",
|
|
385
|
+
min: 0,
|
|
386
|
+
max: 250,
|
|
387
|
+
interval: 50,
|
|
388
|
+
axisLabel: { formatter: "{value} ml" },
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
type: "value",
|
|
392
|
+
name: "Temperature",
|
|
393
|
+
min: 0,
|
|
394
|
+
max: 25,
|
|
395
|
+
interval: 5,
|
|
396
|
+
axisLabel: { formatter: "{value} °C" },
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
series: [
|
|
400
|
+
{
|
|
401
|
+
name: "Evaporation",
|
|
402
|
+
type: "bar",
|
|
403
|
+
tooltip: { valueFormatter: (value: any) => value + " ml" },
|
|
404
|
+
data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6],
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "Precipitation",
|
|
408
|
+
type: "bar",
|
|
409
|
+
tooltip: { valueFormatter: (value: any) => value + " ml" },
|
|
410
|
+
data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6],
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "Temperature",
|
|
414
|
+
type: "line",
|
|
415
|
+
yAxisIndex: 1,
|
|
416
|
+
tooltip: { valueFormatter: (value: any) => value + " °C" },
|
|
417
|
+
data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3],
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
};
|
|
421
|
+
chartInstance.setOption(option);
|
|
422
|
+
window.addEventListener("resize", () => chartInstance?.resize());
|
|
423
|
+
emit("ready");
|
|
424
|
+
};
|
|
425
|
+
onMounted(() => {
|
|
426
|
+
nextTick(() => initChart());
|
|
427
|
+
});
|
|
428
|
+
onUnmounted(() => {
|
|
429
|
+
chartInstance?.dispose();
|
|
430
|
+
});
|
|
431
|
+
return () =>
|
|
432
|
+
h("div", {
|
|
433
|
+
ref: chartRef,
|
|
434
|
+
style: { width: "100%", height: "400px" },
|
|
435
|
+
class: "echarts-container",
|
|
436
|
+
});
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// ---------- 显示序列 ----------
|
|
442
|
+
type DisplayItem =
|
|
443
|
+
| { type: "text"; content: string }
|
|
444
|
+
| { type: "component"; component: Component };
|
|
445
|
+
|
|
446
|
+
const displaySequence = ref<DisplayItem[]>([
|
|
447
|
+
{ type: "text", content: para1 },
|
|
448
|
+
{ type: "component", component: ElStatusTable },
|
|
449
|
+
{ type: "text", content: "📌 带状态表格(高亮区分成功/信息/警告/危险)" },
|
|
450
|
+
{ type: "text", content: para2 },
|
|
451
|
+
{ type: "component", component: ButtonGroup },
|
|
452
|
+
{ type: "component", component: IconShowcase },
|
|
453
|
+
{ type: "text", content: para3 },
|
|
454
|
+
{
|
|
455
|
+
type: "text",
|
|
456
|
+
content: paraExtra || "📊 下半部分:按钮组与折柱图详细数据展示",
|
|
457
|
+
},
|
|
458
|
+
{ type: "component", component: ButtonGroup },
|
|
459
|
+
{ type: "component", component: EChartsMixed },
|
|
460
|
+
{ type: "text", content: "✅ 以上为完整报告,包含上下两部分全部内容。" },
|
|
461
|
+
]);
|
|
462
|
+
|
|
463
|
+
// ---------- 顺序控制 ----------
|
|
464
|
+
const activeIndex = ref(0);
|
|
465
|
+
const lastStartedTextIndex = ref(-1);
|
|
466
|
+
|
|
467
|
+
const nextItem = () => {
|
|
468
|
+
if (activeIndex.value < displaySequence.value.length) {
|
|
469
|
+
activeIndex.value++;
|
|
470
|
+
const newItem = displaySequence.value[activeIndex.value - 1];
|
|
471
|
+
if (newItem.type === "text") {
|
|
472
|
+
lastStartedTextIndex.value = activeIndex.value - 1;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const onTextCompleted = () => nextItem();
|
|
478
|
+
const onComponentReady = () => nextItem();
|
|
479
|
+
|
|
480
|
+
const startSequence = () => {
|
|
481
|
+
if (displaySequence.value.length === 0) return;
|
|
482
|
+
activeIndex.value = 1;
|
|
483
|
+
const firstItem = displaySequence.value[0];
|
|
484
|
+
if (firstItem.type === "text") lastStartedTextIndex.value = 0;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const restartDemo = () => {
|
|
488
|
+
activeIndex.value = 0;
|
|
489
|
+
lastStartedTextIndex.value = -1;
|
|
490
|
+
nextTick(() => startSequence());
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
onMounted(() => startSequence());
|
|
494
|
+
|
|
495
|
+
// ---------- 打字机组件 ----------
|
|
496
|
+
const TypewriterText = defineComponent({
|
|
497
|
+
name: "TypewriterText",
|
|
498
|
+
props: {
|
|
499
|
+
text: { type: String, required: true },
|
|
500
|
+
autoStart: { type: Boolean, default: false },
|
|
501
|
+
},
|
|
502
|
+
emits: ["completed"],
|
|
503
|
+
setup(props, { emit }) {
|
|
504
|
+
const displayed = ref("");
|
|
505
|
+
let interval: number | null = null;
|
|
506
|
+
const startTyping = () => {
|
|
507
|
+
if (interval) return;
|
|
508
|
+
displayed.value = "";
|
|
509
|
+
let i = 0;
|
|
510
|
+
interval = window.setInterval(() => {
|
|
511
|
+
if (i < props.text.length) {
|
|
512
|
+
displayed.value += props.text[i];
|
|
513
|
+
i++;
|
|
514
|
+
} else {
|
|
515
|
+
if (interval) clearInterval(interval);
|
|
516
|
+
interval = null;
|
|
517
|
+
emit("completed");
|
|
518
|
+
}
|
|
519
|
+
}, 28);
|
|
520
|
+
};
|
|
521
|
+
if (props.autoStart) startTyping();
|
|
522
|
+
return () => h("div", { class: "typewriter-text" }, displayed.value);
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
</script>
|
|
526
|
+
|
|
527
|
+
<style scoped>
|
|
528
|
+
:deep(.el-table .success-row) {
|
|
529
|
+
background-color: #f0f9eb;
|
|
530
|
+
}
|
|
531
|
+
:deep(.el-table .info-row) {
|
|
532
|
+
background-color: #e6f7ff;
|
|
533
|
+
}
|
|
534
|
+
:deep(.el-table .warning-row) {
|
|
535
|
+
background-color: #fdf6ec;
|
|
536
|
+
}
|
|
537
|
+
:deep(.el-table .danger-row) {
|
|
538
|
+
background-color: #fef0f0;
|
|
539
|
+
}
|
|
540
|
+
.text-paragraph {
|
|
541
|
+
line-height: 1.8;
|
|
542
|
+
font-size: 15px;
|
|
543
|
+
letter-spacing: 0.01em;
|
|
544
|
+
color: #374151;
|
|
545
|
+
margin-bottom: 1rem;
|
|
546
|
+
white-space: pre-wrap;
|
|
547
|
+
}
|
|
548
|
+
.typewriter-text {
|
|
549
|
+
white-space: pre-wrap;
|
|
550
|
+
}
|
|
551
|
+
@media print {
|
|
552
|
+
.no-print {
|
|
553
|
+
display: none !important;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
</style>
|