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,69 @@
|
|
|
1
|
+
import { buildAuthHeaders } from '../../api/request'
|
|
2
|
+
import { HEDERA_SYSTEM_PROMPT } from './Knowledge'
|
|
3
|
+
|
|
4
|
+
/** Browser calls dev proxy; keys stay in .env on the server. */
|
|
5
|
+
export const CHAT_API_URL = '/api/chat/completions'
|
|
6
|
+
|
|
7
|
+
export const CHAT_MODEL =
|
|
8
|
+
import.meta.env.VITE_CHAT_MODEL ?? 'qwen/qwen-2.5-7b-instruct:free'
|
|
9
|
+
|
|
10
|
+
export const CHAT_PROVIDER_LABEL =
|
|
11
|
+
import.meta.env.VITE_CHAT_PROVIDER_LABEL ?? 'OpenRouter · Qwen 2.5'
|
|
12
|
+
|
|
13
|
+
export function buildChatMessages(userContent: string) {
|
|
14
|
+
return [
|
|
15
|
+
{ role: 'system' as const, content: HEDERA_SYSTEM_PROMPT },
|
|
16
|
+
{ role: 'user' as const, content: userContent },
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function* streamChatCompletion(
|
|
21
|
+
messages: Array<{ role: string; content: string }>,
|
|
22
|
+
) {
|
|
23
|
+
const response = await fetch(CHAT_API_URL, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: buildAuthHeaders(),
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
model: CHAT_MODEL,
|
|
28
|
+
messages,
|
|
29
|
+
stream: true,
|
|
30
|
+
temperature: 0.7,
|
|
31
|
+
max_tokens: 2048,
|
|
32
|
+
}),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`HTTP ${response.status}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const reader = response.body?.getReader()
|
|
40
|
+
if (!reader) {
|
|
41
|
+
throw new Error('No reader')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const decoder = new TextDecoder()
|
|
45
|
+
let buffer = ''
|
|
46
|
+
|
|
47
|
+
while (true) {
|
|
48
|
+
const { done, value } = await reader.read()
|
|
49
|
+
if (done) break
|
|
50
|
+
|
|
51
|
+
buffer += decoder.decode(value, { stream: true })
|
|
52
|
+
const lines = buffer.split('\n')
|
|
53
|
+
buffer = lines.pop() || ''
|
|
54
|
+
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
const trimmed = line.trim()
|
|
57
|
+
if (!trimmed || trimmed === 'data: [DONE]') continue
|
|
58
|
+
if (!trimmed.startsWith('data: ')) continue
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const json = JSON.parse(trimmed.slice(6))
|
|
62
|
+
const delta = json.choices?.[0]?.delta?.content || ''
|
|
63
|
+
if (delta) yield delta
|
|
64
|
+
} catch {
|
|
65
|
+
/* ignore malformed SSE chunks */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-table ref="tableRef" :data="reports" :fit="true" table-layout="fixed" class="recent-reports-table">
|
|
3
|
+
<el-table-column prop="name" label="報告名稱" min-width="240" align="left" header-align="left"
|
|
4
|
+
class-name="industry-col-name">
|
|
5
|
+
<template #default="{ row }">
|
|
6
|
+
<a v-if="row.canOpen" href="#" class="industry-report-name industry-report-name__pill industry-report-name--link"
|
|
7
|
+
@click.prevent="$emit('open', row)">{{ row.name }}</a>
|
|
8
|
+
<span v-else class="industry-report-name industry-report-name__pill">{{ row.name }}</span>
|
|
9
|
+
</template>
|
|
10
|
+
</el-table-column>
|
|
11
|
+
<el-table-column prop="industry" label="行業" min-width="88" align="left" header-align="left" />
|
|
12
|
+
<el-table-column prop="year" label="年份" min-width="80" align="left" header-align="left" />
|
|
13
|
+
<el-table-column prop="status" label="狀態" min-width="96" align="left" header-align="left" />
|
|
14
|
+
<el-table-column prop="initiator" label="發起人" min-width="220" class-name="industry-col-initiator" align="left"
|
|
15
|
+
header-align="left" />
|
|
16
|
+
<el-table-column prop="editor" label="編輯" min-width="360" class-name="industry-col-editor" align="left"
|
|
17
|
+
header-align="left" />
|
|
18
|
+
<el-table-column prop="permission" label="我的權限" min-width="112" align="left" header-align="left" />
|
|
19
|
+
<el-table-column prop="updatedAt" label="最近更新" min-width="112" align="left" header-align="left" />
|
|
20
|
+
</el-table>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import type { RecentReport } from '@/types/gen-ai/gen-entry/index'
|
|
25
|
+
import type { TableInstance } from 'element-plus'
|
|
26
|
+
import { ref } from 'vue'
|
|
27
|
+
|
|
28
|
+
defineProps<{
|
|
29
|
+
reports: RecentReport[]
|
|
30
|
+
}>()
|
|
31
|
+
|
|
32
|
+
defineEmits<{
|
|
33
|
+
open: [report: RecentReport]
|
|
34
|
+
}>()
|
|
35
|
+
|
|
36
|
+
const tableRef = ref<TableInstance>()
|
|
37
|
+
|
|
38
|
+
function doLayout() {
|
|
39
|
+
tableRef.value?.doLayout()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
defineExpose({ doLayout, tableRef })
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<style scoped>
|
|
46
|
+
.recent-reports-table {
|
|
47
|
+
width: 100%;
|
|
48
|
+
margin: 0;
|
|
49
|
+
padding: 0;
|
|
50
|
+
--el-table-border: none;
|
|
51
|
+
--el-table-border-color: transparent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.recent-reports-table :deep(.el-table) {
|
|
55
|
+
--el-table-header-text-color: #1a1a1a;
|
|
56
|
+
width: 100% !important;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.recent-reports-table :deep(.el-table__inner-wrapper),
|
|
60
|
+
.recent-reports-table :deep(.el-table__header-wrapper),
|
|
61
|
+
.recent-reports-table :deep(.el-table__body-wrapper) {
|
|
62
|
+
margin-left: 0;
|
|
63
|
+
padding-left: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.recent-reports-table :deep(.el-table__header table),
|
|
67
|
+
.recent-reports-table :deep(.el-table__body table) {
|
|
68
|
+
width: 100% !important;
|
|
69
|
+
table-layout: fixed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.recent-reports-table :deep(.el-table__header .cell),
|
|
73
|
+
.recent-reports-table :deep(.el-table__body .cell) {
|
|
74
|
+
padding: 0;
|
|
75
|
+
text-align: left;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.recent-reports-table :deep(.el-table__body-wrapper .cell) {
|
|
79
|
+
padding: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.recent-reports-table :deep(.el-table__header th.el-table__cell .cell) {
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
overflow: visible;
|
|
85
|
+
text-overflow: clip;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.recent-reports-table :deep(.el-table__header th.el-table__cell:first-child .cell) {
|
|
89
|
+
padding-left: 10px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.recent-reports-table :deep(.industry-col-name .cell) {
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.recent-reports-table :deep(.industry-col-initiator .cell),
|
|
98
|
+
.recent-reports-table :deep(.industry-col-editor .cell) {
|
|
99
|
+
white-space: normal;
|
|
100
|
+
word-break: break-word;
|
|
101
|
+
line-height: 1.5;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.recent-reports-table :deep(.el-table__inner-wrapper::before),
|
|
105
|
+
.recent-reports-table :deep(.el-table__inner-wrapper::after) {
|
|
106
|
+
display: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.recent-reports-table :deep(.el-table__body-wrapper .el-table__body) {
|
|
110
|
+
border-collapse: separate;
|
|
111
|
+
border-spacing: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.recent-reports-table :deep(.el-table__body tr) {
|
|
115
|
+
background: #fff;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.recent-reports-table :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell) {
|
|
119
|
+
background-color: var(--el-table-row-hover-bg-color, #f5f7fa) !important;
|
|
120
|
+
box-shadow: none !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.recent-reports-table :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell:first-child) {
|
|
124
|
+
border-radius: 4px 0 0 4px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.recent-reports-table :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell:last-child) {
|
|
128
|
+
border-radius: 0 4px 4px 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.recent-reports-table :deep(.el-table__header-wrapper) {
|
|
132
|
+
border-radius: 4px;
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.recent-reports-table :deep(.el-table__header th.el-table__cell) {
|
|
137
|
+
font-weight: 600;
|
|
138
|
+
border-bottom: none;
|
|
139
|
+
background: #f5f7fa;
|
|
140
|
+
color: #1a1a1a;
|
|
141
|
+
padding: 12px 16px;
|
|
142
|
+
vertical-align: bottom;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.recent-reports-table :deep(.el-table__header th.el-table__cell:first-child) {
|
|
146
|
+
border-radius: 4px 0 0 4px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.recent-reports-table :deep(.el-table__header th.el-table__cell:last-child) {
|
|
150
|
+
border-radius: 0 4px 4px 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.recent-reports-table :deep(.el-table__body td.el-table__cell) {
|
|
154
|
+
border-bottom: 1px solid #f0f2f5;
|
|
155
|
+
padding: 12px 16px;
|
|
156
|
+
vertical-align: middle;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.recent-reports-table :deep(.el-table__body td.el-table__cell:not(:first-child) .cell) {
|
|
160
|
+
color: #2d3139;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.industry-report-name__pill {
|
|
165
|
+
display: block;
|
|
166
|
+
width: 100%;
|
|
167
|
+
max-width: 100%;
|
|
168
|
+
padding: 6px 10px;
|
|
169
|
+
border-radius: 4px;
|
|
170
|
+
background: transparent;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
text-overflow: ellipsis;
|
|
173
|
+
white-space: nowrap;
|
|
174
|
+
box-sizing: border-box;
|
|
175
|
+
line-height: 1.4;
|
|
176
|
+
font-size: 13px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.industry-report-name {
|
|
180
|
+
color: #606266;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.industry-report-name--link {
|
|
184
|
+
color: #409eff;
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.industry-report-name--link:hover {
|
|
190
|
+
color: #66b1ff;
|
|
191
|
+
text-decoration: none;
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="industry-page">
|
|
3
|
+
<PageHeader title="IR-DEMO" />
|
|
4
|
+
|
|
5
|
+
<main ref="pageBodyRef" class="industry-page__body" :class="{ 'is-scrollable': needsPageScroll }">
|
|
6
|
+
<section class="industry-hero">
|
|
7
|
+
<h2 class="industry-hero__title">AI產業分析報告</h2>
|
|
8
|
+
<p class="industry-hero__desc">
|
|
9
|
+
基於你選擇的模型、行業與年份,並結合上傳或雲端引用的資料,自動生成結構化的風控分析報告。
|
|
10
|
+
</p>
|
|
11
|
+
<p class="industry-hero__desc">
|
|
12
|
+
你可以從標準模板開始,逐章生成與調整內容,最終輸出可複用、可審閱的完整報告。
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<div class="industry-hero__actions">
|
|
16
|
+
<template v-for="action in heroActions" :key="action.id">
|
|
17
|
+
<el-badge v-if="action.id === 'admin-sim'" :value="200" :max="99" type="danger"
|
|
18
|
+
:badge-style="{ marginRight: '5px', marginTop: '-5px' }" class="industry-hero__action-badge">
|
|
19
|
+
<button type="button" class="app-btn" @click="handleHeroAction(action)">
|
|
20
|
+
{{ action.label }}
|
|
21
|
+
</button>
|
|
22
|
+
</el-badge>
|
|
23
|
+
<button v-else type="button" class="app-btn" @click="handleHeroAction(action)">
|
|
24
|
+
{{ action.label }}
|
|
25
|
+
</button>
|
|
26
|
+
</template>
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
29
|
+
|
|
30
|
+
<section ref="reportsSectionRef" class="industry-reports">
|
|
31
|
+
<h3 class="industry-reports__title">
|
|
32
|
+
<el-icon class="industry-reports__title-icon">
|
|
33
|
+
<Document />
|
|
34
|
+
</el-icon>
|
|
35
|
+
最近的報告
|
|
36
|
+
</h3>
|
|
37
|
+
<RecentReportsTable ref="tableRef" :reports="displayedReports" @open="openReport" />
|
|
38
|
+
<a v-if="showMoreLink" href="#" class="industry-reports__more" @click.prevent="handleMoreReports">
|
|
39
|
+
更多報告 >>
|
|
40
|
+
</a>
|
|
41
|
+
</section>
|
|
42
|
+
</main>
|
|
43
|
+
|
|
44
|
+
<CapsuleScrollbar
|
|
45
|
+
ref="capsuleScrollbarRef"
|
|
46
|
+
:scroll-el="pageBodyRef"
|
|
47
|
+
:anchor-el="reportsSectionRef"
|
|
48
|
+
:active="needsPageScroll"
|
|
49
|
+
:visible-ratio="2 / 3"
|
|
50
|
+
:right-offset-px="1"
|
|
51
|
+
:auto-hide-after-ms="3000"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import genEntryApi from '@/api/modules/gen-ai/gen-entry/index'
|
|
58
|
+
import type { RecentReport } from '@/types/gen-ai/gen-entry/index'
|
|
59
|
+
import {
|
|
60
|
+
bindViewportBodyHeight,
|
|
61
|
+
lockDocumentScroll,
|
|
62
|
+
} from '@/utils/gen-ai-scroll'
|
|
63
|
+
import {
|
|
64
|
+
navigateToReportCenter,
|
|
65
|
+
openManagementCenter,
|
|
66
|
+
openModelManager,
|
|
67
|
+
openReportCenter,
|
|
68
|
+
openTemplateCenter,
|
|
69
|
+
} from '@/utils/gen-ai-navigation'
|
|
70
|
+
import { Document } from '@element-plus/icons-vue'
|
|
71
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
72
|
+
import { useRouter } from 'vue-router'
|
|
73
|
+
import PageHeader from '@/components/PageHeader.vue'
|
|
74
|
+
import CapsuleScrollbar from '@/components/CapsuleScrollbar.vue'
|
|
75
|
+
import RecentReportsTable from '../components/RecentReportsTable.vue'
|
|
76
|
+
import { MOCK_RECENT_REPORTS } from './mockData'
|
|
77
|
+
|
|
78
|
+
const DEFAULT_VISIBLE_ROWS = 10
|
|
79
|
+
|
|
80
|
+
const router = useRouter()
|
|
81
|
+
const recentReports = ref<RecentReport[]>([...MOCK_RECENT_REPORTS])
|
|
82
|
+
|
|
83
|
+
function isRecentReportList(data: unknown): data is RecentReport[] {
|
|
84
|
+
return Array.isArray(data) && data.length > 0 && typeof data[0]?.id === 'string'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function loadRecentReports() {
|
|
88
|
+
try {
|
|
89
|
+
const data = await genEntryApi.fetchGenAiReportListData({})
|
|
90
|
+
if (isRecentReportList(data)) {
|
|
91
|
+
recentReports.value = data
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// 介面不可用時保留 mock 資料
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const pageBodyRef = ref<HTMLElement | null>(null)
|
|
99
|
+
const reportsSectionRef = ref<HTMLElement | null>(null)
|
|
100
|
+
const tableRef = ref<InstanceType<typeof RecentReportsTable> | null>(null)
|
|
101
|
+
const needsPageScroll = ref(false)
|
|
102
|
+
const capsuleScrollbarRef = ref<InstanceType<typeof CapsuleScrollbar> | null>(null)
|
|
103
|
+
|
|
104
|
+
const displayedReports = computed(() => recentReports.value.slice(0, DEFAULT_VISIBLE_ROWS))
|
|
105
|
+
|
|
106
|
+
const showMoreLink = computed(() => recentReports.value.length > DEFAULT_VISIBLE_ROWS)
|
|
107
|
+
|
|
108
|
+
let cleanupBodyHeight: (() => void) | undefined
|
|
109
|
+
let cleanupLock: (() => void) | undefined
|
|
110
|
+
let resizeObserver: ResizeObserver | undefined
|
|
111
|
+
let measureRafId = 0
|
|
112
|
+
|
|
113
|
+
async function detectPageScroll() {
|
|
114
|
+
await nextTick()
|
|
115
|
+
await new Promise<void>((resolve) => {
|
|
116
|
+
cancelAnimationFrame(measureRafId)
|
|
117
|
+
measureRafId = requestAnimationFrame(() => {
|
|
118
|
+
measureRafId = requestAnimationFrame(() => resolve())
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const body = pageBodyRef.value
|
|
123
|
+
if (!body) return
|
|
124
|
+
|
|
125
|
+
const prevScrollable = body.classList.contains('is-scrollable')
|
|
126
|
+
if (!prevScrollable) {
|
|
127
|
+
body.classList.add('is-scrollable')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const overflows = body.scrollHeight > body.clientHeight + 1
|
|
131
|
+
needsPageScroll.value = overflows
|
|
132
|
+
|
|
133
|
+
if (!overflows) {
|
|
134
|
+
body.classList.remove('is-scrollable')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
tableRef.value?.doLayout()
|
|
138
|
+
capsuleScrollbarRef.value?.refresh()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function scheduleDetectPageScroll() {
|
|
142
|
+
cancelAnimationFrame(measureRafId)
|
|
143
|
+
measureRafId = requestAnimationFrame(() => {
|
|
144
|
+
void detectPageScroll()
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function handleMoreReports() {
|
|
149
|
+
navigateToReportCenter(router)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
onMounted(() => {
|
|
153
|
+
void loadRecentReports().then(() => scheduleDetectPageScroll())
|
|
154
|
+
if (pageBodyRef.value) {
|
|
155
|
+
cleanupBodyHeight = bindViewportBodyHeight(pageBodyRef.value, {
|
|
156
|
+
subtractSelector: '.page-header',
|
|
157
|
+
subtractRoot: pageBodyRef.value.parentElement ?? undefined,
|
|
158
|
+
})
|
|
159
|
+
resizeObserver = new ResizeObserver(() => scheduleDetectPageScroll())
|
|
160
|
+
resizeObserver.observe(pageBodyRef.value)
|
|
161
|
+
}
|
|
162
|
+
cleanupLock = lockDocumentScroll()
|
|
163
|
+
window.addEventListener('resize', scheduleDetectPageScroll)
|
|
164
|
+
scheduleDetectPageScroll()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
onUnmounted(() => {
|
|
168
|
+
cancelAnimationFrame(measureRafId)
|
|
169
|
+
resizeObserver?.disconnect()
|
|
170
|
+
window.removeEventListener('resize', scheduleDetectPageScroll)
|
|
171
|
+
cleanupBodyHeight?.()
|
|
172
|
+
cleanupLock?.()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
watch(displayedReports, () => {
|
|
176
|
+
scheduleDetectPageScroll()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const heroActions = [
|
|
180
|
+
{ id: 'new-report', label: '新建報告' },
|
|
181
|
+
{ id: 'report-sim', label: '報告中心' },
|
|
182
|
+
{ id: 'template-sim', label: '模板中心' },
|
|
183
|
+
{ id: 'admin-sim', label: '管理中心' },
|
|
184
|
+
] as const
|
|
185
|
+
|
|
186
|
+
function handleHeroAction(action: (typeof heroActions)[number]) {
|
|
187
|
+
if (action.id === 'new-report') {
|
|
188
|
+
openTemplateCenter(router)
|
|
189
|
+
}
|
|
190
|
+
if (action.id === 'report-sim') {
|
|
191
|
+
openReportCenter(router)
|
|
192
|
+
}
|
|
193
|
+
if (action.id === 'template-sim') {
|
|
194
|
+
openModelManager(router)
|
|
195
|
+
}
|
|
196
|
+
if (action.id === 'admin-sim') {
|
|
197
|
+
openManagementCenter(router)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function openReport(report: RecentReport) {
|
|
202
|
+
openModelManager(router, { id: report.id })
|
|
203
|
+
}
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<style scoped>
|
|
207
|
+
.industry-page {
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-direction: column;
|
|
210
|
+
height: 100vh;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
background: #fff;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.industry-page__body {
|
|
216
|
+
flex: 1;
|
|
217
|
+
min-height: 0;
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
width: 100%;
|
|
222
|
+
padding: 48px 24px 40px;
|
|
223
|
+
box-sizing: border-box;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.industry-page__body.is-scrollable {
|
|
227
|
+
overflow-y: auto;
|
|
228
|
+
overflow-x: hidden;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.industry-hero {
|
|
232
|
+
flex-shrink: 0;
|
|
233
|
+
text-align: center;
|
|
234
|
+
margin-bottom: 48px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.industry-hero__title {
|
|
238
|
+
margin: 0 0 20px;
|
|
239
|
+
font-size: 28px;
|
|
240
|
+
font-weight: 700;
|
|
241
|
+
color: #1a1a1a;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.industry-hero__desc {
|
|
245
|
+
margin: 0 0 8px;
|
|
246
|
+
font-size: 14px;
|
|
247
|
+
line-height: 1.7;
|
|
248
|
+
color: #666;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.industry-hero__actions {
|
|
252
|
+
display: flex;
|
|
253
|
+
flex-wrap: wrap;
|
|
254
|
+
align-items: center;
|
|
255
|
+
justify-content: center;
|
|
256
|
+
gap: 16px;
|
|
257
|
+
margin-top: 28px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.industry-reports {
|
|
261
|
+
width: 80%;
|
|
262
|
+
max-width: 80%;
|
|
263
|
+
margin: 0 auto;
|
|
264
|
+
padding: 0;
|
|
265
|
+
box-sizing: border-box;
|
|
266
|
+
flex: 0 0 auto;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.industry-reports__title {
|
|
270
|
+
display: inline-flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 8px;
|
|
273
|
+
margin: 0 0 16px;
|
|
274
|
+
padding: 0;
|
|
275
|
+
font-size: 16px;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
color: #1a1a1a;
|
|
278
|
+
text-align: left;
|
|
279
|
+
width: 100%;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.industry-reports__title-icon {
|
|
283
|
+
font-size: 18px;
|
|
284
|
+
color: #606266;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.industry-reports__more {
|
|
288
|
+
display: inline-block;
|
|
289
|
+
margin: 12px 0 0;
|
|
290
|
+
padding: 0;
|
|
291
|
+
font-size: 14px;
|
|
292
|
+
color: #409eff;
|
|
293
|
+
text-decoration: none;
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
text-align: left;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.industry-reports__more:hover {
|
|
299
|
+
color: #66b1ff;
|
|
300
|
+
text-decoration: none;
|
|
301
|
+
}
|
|
302
|
+
</style>
|
|
303
|
+
|
|
304
|
+
<style>
|
|
305
|
+
.industry-hero__action-badge .el-badge__content.el-badge__content--danger.is-fixed {
|
|
306
|
+
margin-right: 10px !important;
|
|
307
|
+
margin-top: -5px !important;
|
|
308
|
+
}
|
|
309
|
+
</style>
|