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,456 @@
|
|
|
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
|
+
id="report-container"
|
|
7
|
+
>
|
|
8
|
+
<!-- 导出按钮 (右上角) -->
|
|
9
|
+
<div class="absolute top-6 right-8 flex gap-4 no-print">
|
|
10
|
+
<el-button
|
|
11
|
+
@click="exportToPDF('report-container', '图文报告_房地产整合')"
|
|
12
|
+
size="small"
|
|
13
|
+
plain
|
|
14
|
+
type="danger"
|
|
15
|
+
>
|
|
16
|
+
匯出為PDF
|
|
17
|
+
</el-button>
|
|
18
|
+
<el-button
|
|
19
|
+
@click="exportToWord('report-container', '图文报告_房地产整合')"
|
|
20
|
+
size="small"
|
|
21
|
+
plain
|
|
22
|
+
type="danger"
|
|
23
|
+
>
|
|
24
|
+
匯出為Word
|
|
25
|
+
</el-button>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="prose prose-slate max-w-none">
|
|
29
|
+
<div class="font-bold flex items-center mb-5 text-lg border-t pt-6">
|
|
30
|
+
正文:
|
|
31
|
+
<Edit3Icon :size="18" class="ml-2 text-[#C54E5E] cursor-pointer" />
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="content-wrapper space-y-4">
|
|
35
|
+
<template v-for="(item, idx) in displaySequence" :key="idx">
|
|
36
|
+
<!-- 文本段落:带打字机效果 -->
|
|
37
|
+
<TypewriterText
|
|
38
|
+
v-if="item.type === 'text'"
|
|
39
|
+
:text="item.content!"
|
|
40
|
+
:auto-start="idx <= currentTypingIndex"
|
|
41
|
+
@completed="onTextCompleted(idx)"
|
|
42
|
+
class="text-paragraph"
|
|
43
|
+
/>
|
|
44
|
+
<!-- 普通组件:直接渲染,使用 resolveComponent 或直接引用 -->
|
|
45
|
+
<component
|
|
46
|
+
v-else-if="item.type === 'component'"
|
|
47
|
+
:is="item.component"
|
|
48
|
+
class="my-4"
|
|
49
|
+
/>
|
|
50
|
+
</template>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<span
|
|
54
|
+
v-if="isTypingActive"
|
|
55
|
+
class="inline-block w-[2px] h-[18px] bg-[#C54E5E] animate-pulse ml-1 align-middle"
|
|
56
|
+
></span>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</main>
|
|
60
|
+
|
|
61
|
+
<aside class="w-80 bg-white border-l border-gray-200 p-6 no-print">
|
|
62
|
+
<h3 class="font-bold text-gray-700 mb-6">生成報告設置</h3>
|
|
63
|
+
<p class="text-sm text-gray-500">✨ 整合 Element Plus 组件</p>
|
|
64
|
+
<p class="text-xs text-gray-400 mt-2">
|
|
65
|
+
包含:带状态表格 / 按钮组 / 图标集 / 断轴柱状图<br />
|
|
66
|
+
房地产报告全文 + 打字机效果
|
|
67
|
+
</p>
|
|
68
|
+
<el-button @click="restartDemo" type="danger" plain class="w-full mt-6">
|
|
69
|
+
重新播放
|
|
70
|
+
</el-button>
|
|
71
|
+
<div class="mt-6 text-xs text-gray-400 border-t pt-4">
|
|
72
|
+
💡 提示:<br />
|
|
73
|
+
• 表格行按状态高亮(成功/信息/警告/危险)<br />
|
|
74
|
+
• 柱状图区域点击可展开详细数据<br />
|
|
75
|
+
• 图标展示 Element Plus 常用图标
|
|
76
|
+
</div>
|
|
77
|
+
</aside>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
import {
|
|
83
|
+
ref,
|
|
84
|
+
onMounted,
|
|
85
|
+
defineComponent,
|
|
86
|
+
h,
|
|
87
|
+
markRaw,
|
|
88
|
+
type Component,
|
|
89
|
+
} from "vue";
|
|
90
|
+
import { Edit3Icon } from "lucide-vue-next";
|
|
91
|
+
import { ElButton, ElTable, ElTableColumn, ElIcon } from "element-plus";
|
|
92
|
+
// 注意:图标已在 main.ts 中全局注册,但为了 h 函数中使用,需要导入或使用 resolveComponent
|
|
93
|
+
// 这里直接导入以确保类型和可用性
|
|
94
|
+
import { Platform, Eleme, DeleteFilled, Delete } from "@element-plus/icons-vue";
|
|
95
|
+
|
|
96
|
+
// 模拟导出函数(请替换为实际实现)
|
|
97
|
+
const exportToPDF = (containerId: string, filename: string) => {
|
|
98
|
+
console.log(`导出 PDF: ${filename}`, containerId);
|
|
99
|
+
alert("PDF 导出功能演示(需接入实际库)");
|
|
100
|
+
};
|
|
101
|
+
const exportToWord = (containerId: string, filename: string) => {
|
|
102
|
+
console.log(`导出 Word: ${filename}`, containerId);
|
|
103
|
+
alert("Word 导出功能演示(需接入实际库)");
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ---------- 1. 报告文本 ----------
|
|
107
|
+
const rawReport = `2024年香港房地產市場整體呈現「價跌量穩、租金偏強、商業不振」的分化局面,全年各類物業價格普遍下調,但成交及租賃需求在政策放寬和人口流入支持下較前兩年有所改善。住宅方面,撤銷所有樓市「辣招」、放寬按揭成數及利率見頂後回落,帶動一手主導的成交回升,但無礙樓價全年再跌約中個百分比,反映經濟及供應壓力仍然主導價格走勢。
|
|
108
|
+
|
|
109
|
+
在住宅市場,本年特點包括:
|
|
110
|
+
一、銷售量回升、價格續調:2024年在全面撤辣及按揭寬鬆後,全年住宅買賣宗數按年錄得逾兩成增長,但全年樓價仍錄得約6%至7%的跌幅,延續過去兩年的調整趨勢。
|
|
111
|
+
二、供應顯著增加:私樓落成量約2.4萬個單位,較2023年大幅增加約七成,主要為中小型單位,為後市價格帶來持續壓力。
|
|
112
|
+
三、租金走勢優於樓價:在人口及人才計劃帶動下,住宅租金全年錄得中低個位升幅,租金指數距歷史高位僅約數個百分點,令小型單位的平均回報率升至近十多年高位水平。
|
|
113
|
+
|
|
114
|
+
在非住宅方面,2024年為商廈及舖位市場的「谷底年」,寫字樓空置率創逾十年新高,商業成交金額及宗數均跌至有紀錄以來低位。`;
|
|
115
|
+
|
|
116
|
+
const rawParagraphs = rawReport
|
|
117
|
+
.split(/\n\s*\n/)
|
|
118
|
+
.filter((p) => p.trim().length > 0);
|
|
119
|
+
const para1 = rawParagraphs[0] ?? "";
|
|
120
|
+
const para2 = rawParagraphs[1] ?? "";
|
|
121
|
+
const para3 = rawParagraphs[2] ?? "";
|
|
122
|
+
|
|
123
|
+
// ---------- 2. 组件定义(使用 markRaw 避免响应式包裹)----------
|
|
124
|
+
const ElStatusTable = markRaw(
|
|
125
|
+
defineComponent({
|
|
126
|
+
name: "ElStatusTable",
|
|
127
|
+
setup() {
|
|
128
|
+
const tableData = [
|
|
129
|
+
{
|
|
130
|
+
date: "2016-05-02",
|
|
131
|
+
name: "王小虎",
|
|
132
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
133
|
+
status: "success",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
date: "2016-05-04",
|
|
137
|
+
name: "王小虎",
|
|
138
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
139
|
+
status: "info",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
date: "2016-05-01",
|
|
143
|
+
name: "王小虎",
|
|
144
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
145
|
+
status: "warning",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
date: "2016-05-03",
|
|
149
|
+
name: "王小虎",
|
|
150
|
+
address: "上海市普陀区金沙江路1518弄",
|
|
151
|
+
status: "danger",
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
const rowClassName = ({ row }: { row: any }) => {
|
|
155
|
+
if (row.status === "success") return "success-row";
|
|
156
|
+
if (row.status === "info") return "info-row";
|
|
157
|
+
if (row.status === "warning") return "warning-row";
|
|
158
|
+
if (row.status === "danger") return "danger-row";
|
|
159
|
+
return "";
|
|
160
|
+
};
|
|
161
|
+
return () =>
|
|
162
|
+
h(
|
|
163
|
+
ElTable,
|
|
164
|
+
{
|
|
165
|
+
data: tableData,
|
|
166
|
+
style: { width: "100%" },
|
|
167
|
+
rowClassName,
|
|
168
|
+
border: true,
|
|
169
|
+
},
|
|
170
|
+
() => [
|
|
171
|
+
h(ElTableColumn, { prop: "date", label: "日期", width: "120" }),
|
|
172
|
+
h(ElTableColumn, { prop: "name", label: "姓名", width: "100" }),
|
|
173
|
+
h(ElTableColumn, { prop: "address", label: "地址" }),
|
|
174
|
+
h(
|
|
175
|
+
ElTableColumn,
|
|
176
|
+
{ label: "状态", width: "100" },
|
|
177
|
+
{
|
|
178
|
+
default: ({ row }: any) => {
|
|
179
|
+
let type: "success" | "info" | "warning" | "danger" = "success";
|
|
180
|
+
if (row.status === "info") type = "info";
|
|
181
|
+
if (row.status === "warning") type = "warning";
|
|
182
|
+
if (row.status === "danger") type = "danger";
|
|
183
|
+
return h(
|
|
184
|
+
ElButton,
|
|
185
|
+
{ size: "small", type, plain: true },
|
|
186
|
+
() => row.status
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
),
|
|
191
|
+
]
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const ButtonGroup = markRaw(
|
|
198
|
+
defineComponent({
|
|
199
|
+
setup() {
|
|
200
|
+
return () =>
|
|
201
|
+
h("div", { class: "flex flex-wrap gap-3 my-2" }, [
|
|
202
|
+
h(ElButton, { type: "primary" }, () => "主要按钮"),
|
|
203
|
+
h(ElButton, { type: "success" }, () => "成功按钮"),
|
|
204
|
+
h(ElButton, { type: "info" }, () => "信息按钮"),
|
|
205
|
+
h(ElButton, { type: "warning" }, () => "警告按钮"),
|
|
206
|
+
h(ElButton, { type: "danger" }, () => "危险按钮"),
|
|
207
|
+
h(ElButton, {}, () => "默认按钮"),
|
|
208
|
+
h(ElButton, { round: true }, () => "圆角按钮"),
|
|
209
|
+
]);
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const IconShowcase = markRaw(
|
|
215
|
+
defineComponent({
|
|
216
|
+
setup() {
|
|
217
|
+
return () =>
|
|
218
|
+
h(
|
|
219
|
+
"div",
|
|
220
|
+
{ class: "flex gap-4 items-center py-3 border-y border-gray-100" },
|
|
221
|
+
[
|
|
222
|
+
h(ElIcon, { size: 24, color: "#409EFF" }, () => h(Platform)),
|
|
223
|
+
h(ElIcon, { size: 24, color: "#67C23A" }, () => h(Eleme)),
|
|
224
|
+
h(ElIcon, { size: 24, color: "#F56C6C" }, () => h(DeleteFilled)),
|
|
225
|
+
h(ElIcon, { size: 24, color: "#E6A23C" }, () => h(Delete)),
|
|
226
|
+
h(
|
|
227
|
+
"span",
|
|
228
|
+
{ class: "text-sm text-gray-500 ml-2" },
|
|
229
|
+
"el-icon-platform-eleme / eleme / delete-filled / delete"
|
|
230
|
+
),
|
|
231
|
+
]
|
|
232
|
+
);
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const AxisBreakChart = markRaw(
|
|
238
|
+
defineComponent({
|
|
239
|
+
name: "AxisBreakChart",
|
|
240
|
+
setup() {
|
|
241
|
+
const createChartHtml = () => {
|
|
242
|
+
const wrapper = document.createElement("div");
|
|
243
|
+
wrapper.className =
|
|
244
|
+
"my-2 p-4 bg-gray-50 rounded-xl border border-gray-200";
|
|
245
|
+
const days = ["周一", "周二", "周三", "周四"];
|
|
246
|
+
const values = [45, 62, 28, 78];
|
|
247
|
+
const maxValue = 80;
|
|
248
|
+
let barsHtml = "";
|
|
249
|
+
for (let i = 0; i < days.length; i++) {
|
|
250
|
+
const value = values[i] ?? 0;
|
|
251
|
+
const heightPercent = (value / maxValue) * 100;
|
|
252
|
+
barsHtml += `
|
|
253
|
+
<div class="flex flex-col items-center w-16 group">
|
|
254
|
+
<div class="relative w-10 bg-teal-500 rounded-t-md hover:bg-teal-600 transition-all cursor-pointer" style="height: ${heightPercent}px; min-height: 8px;">
|
|
255
|
+
<span class="absolute -top-5 left-1/2 transform -translate-x-1/2 text-xs font-medium text-gray-700 opacity-0 group-hover:opacity-100 transition-opacity">${value}</span>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="text-xs mt-2 font-medium text-gray-600">${days[i]}</div>
|
|
258
|
+
<div class="text-xs text-gray-500 mt-0.5">${value}</div>
|
|
259
|
+
</div>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
wrapper.innerHTML = `
|
|
263
|
+
<div class="flex items-center justify-between mb-3">
|
|
264
|
+
<h4 class="font-bold text-gray-800">📊 断轴柱状图(点击展开)</h4>
|
|
265
|
+
<span class="text-xs text-gray-400 bg-white px-2 py-0.5 rounded-full border">断轴标记 ⚡</span>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="relative ml-8 mb-2">
|
|
268
|
+
<div class="absolute left-0 top-0 bottom-0 w-8 flex flex-col items-center justify-between text-xs text-gray-400">
|
|
269
|
+
<span>80</span><span>60</span>
|
|
270
|
+
<span class="relative">40<span class="absolute left-4 top-1/2 w-6 h-px bg-gray-400 transform -rotate-12"></span><span class="absolute left-4 top-1/2 w-6 h-px bg-gray-400 transform rotate-12"></span></span>
|
|
271
|
+
<span>20</span><span>0</span>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="flex justify-around items-end pt-2 pl-8 border-l-2 border-b-2 border-gray-300">
|
|
274
|
+
${barsHtml}
|
|
275
|
+
</div>
|
|
276
|
+
<div class="text-center text-xs text-gray-400 mt-3 italic">💡 点击柱状图或区域展开详细数据</div>
|
|
277
|
+
</div>
|
|
278
|
+
`;
|
|
279
|
+
const chartArea = wrapper.querySelector(".flex.justify-around");
|
|
280
|
+
if (chartArea) {
|
|
281
|
+
chartArea.addEventListener("click", (e) => {
|
|
282
|
+
const target = e.target as HTMLElement;
|
|
283
|
+
const barDiv = target.closest(".group");
|
|
284
|
+
if (barDiv) {
|
|
285
|
+
const day = barDiv.querySelector(
|
|
286
|
+
".text-xs.font-medium"
|
|
287
|
+
)?.textContent;
|
|
288
|
+
const value = barDiv.querySelector(
|
|
289
|
+
".text-xs.text-gray-500"
|
|
290
|
+
)?.textContent;
|
|
291
|
+
alert(
|
|
292
|
+
`📈 ${day} 详细数据:销售额 ${value} 万元\n点击展开更多趋势信息。`
|
|
293
|
+
);
|
|
294
|
+
} else {
|
|
295
|
+
const existingPanel = wrapper.querySelector(".expand-panel");
|
|
296
|
+
if (existingPanel) existingPanel.remove();
|
|
297
|
+
else {
|
|
298
|
+
const panel = document.createElement("div");
|
|
299
|
+
panel.className =
|
|
300
|
+
"expand-panel mt-4 p-3 bg-white rounded-lg border border-teal-200 shadow-sm";
|
|
301
|
+
panel.innerHTML = `<div class="font-medium text-gray-700 mb-2">📋 本周销售明细(展开数据)</div>
|
|
302
|
+
<div class="grid grid-cols-2 gap-2 text-sm">
|
|
303
|
+
<div>周一:45 万元</div><div>环比 +2%</div>
|
|
304
|
+
<div>周二:62 万元</div><div>环比 +38%</div>
|
|
305
|
+
<div>周三:28 万元</div><div>环比 -55%</div>
|
|
306
|
+
<div>周四:78 万元</div><div>环比 +179%</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="text-xs text-gray-500 mt-2">✨ 断轴区域已展开,数据来自实时模拟</div>`;
|
|
309
|
+
wrapper.appendChild(panel);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return wrapper;
|
|
315
|
+
};
|
|
316
|
+
return () =>
|
|
317
|
+
h("div", {
|
|
318
|
+
ref: (el) => {
|
|
319
|
+
if (el instanceof HTMLElement) el.appendChild(createChartHtml());
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// ---------- 3. 构建显示序列(使用 markRaw 包装的组件)----------
|
|
327
|
+
type DisplayItem =
|
|
328
|
+
| { type: "text"; content: string }
|
|
329
|
+
| { type: "component"; component: Component };
|
|
330
|
+
|
|
331
|
+
// 使用 shallowRef 存储序列,避免深度响应式
|
|
332
|
+
const displaySequence = ref<DisplayItem[]>([
|
|
333
|
+
{ type: "text", content: para1 },
|
|
334
|
+
{ type: "component", component: ElStatusTable },
|
|
335
|
+
{ type: "text", content: "📌 带状态表格(高亮区分成功/信息/警告/危险)" },
|
|
336
|
+
{ type: "text", content: para2 },
|
|
337
|
+
{ type: "component", component: ButtonGroup },
|
|
338
|
+
{ type: "component", component: IconShowcase },
|
|
339
|
+
{ type: "text", content: para3 },
|
|
340
|
+
{ type: "component", component: AxisBreakChart },
|
|
341
|
+
{
|
|
342
|
+
type: "text",
|
|
343
|
+
content: "✅ 以上为整合 Element Plus 组件与完整房地产报告的图文混排效果。",
|
|
344
|
+
},
|
|
345
|
+
]);
|
|
346
|
+
|
|
347
|
+
// ---------- 4. 打字机组件(内联定义)----------
|
|
348
|
+
const TypewriterText = defineComponent({
|
|
349
|
+
name: "TypewriterText",
|
|
350
|
+
props: {
|
|
351
|
+
text: { type: String, required: true },
|
|
352
|
+
autoStart: { type: Boolean, default: false },
|
|
353
|
+
},
|
|
354
|
+
emits: ["completed"],
|
|
355
|
+
setup(props, { emit }) {
|
|
356
|
+
const displayed = ref("");
|
|
357
|
+
let interval: number | null = null;
|
|
358
|
+
|
|
359
|
+
const startTyping = () => {
|
|
360
|
+
if (interval) return;
|
|
361
|
+
displayed.value = "";
|
|
362
|
+
let i = 0;
|
|
363
|
+
interval = window.setInterval(() => {
|
|
364
|
+
if (i < props.text.length) {
|
|
365
|
+
displayed.value += props.text[i];
|
|
366
|
+
i++;
|
|
367
|
+
} else {
|
|
368
|
+
if (interval) clearInterval(interval);
|
|
369
|
+
interval = null;
|
|
370
|
+
emit("completed");
|
|
371
|
+
}
|
|
372
|
+
}, 28);
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (props.autoStart) {
|
|
376
|
+
startTyping();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return () => h("div", { class: "typewriter-text" }, displayed.value);
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// 控制打字机顺序
|
|
384
|
+
const currentTypingIndex = ref(-1);
|
|
385
|
+
const isTypingActive = ref(false);
|
|
386
|
+
|
|
387
|
+
const onTextCompleted = (idx: number) => {
|
|
388
|
+
let nextTextIdx = -1;
|
|
389
|
+
for (let i = idx + 1; i < displaySequence.value.length; i++) {
|
|
390
|
+
const seqItem = displaySequence.value[i];
|
|
391
|
+
if (seqItem?.type === "text") {
|
|
392
|
+
nextTextIdx = i;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (nextTextIdx !== -1) {
|
|
397
|
+
currentTypingIndex.value = nextTextIdx;
|
|
398
|
+
} else {
|
|
399
|
+
isTypingActive.value = false;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const startSequence = () => {
|
|
404
|
+
const firstTextIdx = displaySequence.value.findIndex(
|
|
405
|
+
(item) => item.type === "text"
|
|
406
|
+
);
|
|
407
|
+
if (firstTextIdx !== -1) {
|
|
408
|
+
currentTypingIndex.value = firstTextIdx;
|
|
409
|
+
isTypingActive.value = true;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const restartDemo = () => {
|
|
414
|
+
// 重置所有状态
|
|
415
|
+
currentTypingIndex.value = -1;
|
|
416
|
+
isTypingActive.value = false;
|
|
417
|
+
// 强制重新渲染 displaySequence(触发重新开始)
|
|
418
|
+
// 简单方法:重新赋值序列引用
|
|
419
|
+
displaySequence.value = [...displaySequence.value];
|
|
420
|
+
setTimeout(() => startSequence(), 50);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
onMounted(() => {
|
|
424
|
+
startSequence();
|
|
425
|
+
});
|
|
426
|
+
</script>
|
|
427
|
+
|
|
428
|
+
<style scoped>
|
|
429
|
+
:deep(.el-table .success-row) {
|
|
430
|
+
background-color: #f0f9eb;
|
|
431
|
+
}
|
|
432
|
+
:deep(.el-table .info-row) {
|
|
433
|
+
background-color: #e6f7ff;
|
|
434
|
+
}
|
|
435
|
+
:deep(.el-table .warning-row) {
|
|
436
|
+
background-color: #fdf6ec;
|
|
437
|
+
}
|
|
438
|
+
:deep(.el-table .danger-row) {
|
|
439
|
+
background-color: #fef0f0;
|
|
440
|
+
}
|
|
441
|
+
.text-paragraph {
|
|
442
|
+
line-height: 1.8;
|
|
443
|
+
font-size: 15px;
|
|
444
|
+
letter-spacing: 0.01em;
|
|
445
|
+
color: #374151;
|
|
446
|
+
margin-bottom: 1rem;
|
|
447
|
+
}
|
|
448
|
+
.typewriter-text {
|
|
449
|
+
white-space: pre-wrap;
|
|
450
|
+
}
|
|
451
|
+
@media print {
|
|
452
|
+
.no-print {
|
|
453
|
+
display: none !important;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
</style>
|