aegon-lv82 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 (148) hide show
  1. package/package.json +12 -0
  2. package/src/App.vue +44 -0
  3. package/src/api/index.ts +19 -0
  4. package/src/api/modules/build-report/reportEditor.ts +526 -0
  5. package/src/api/modules/build-report/reportPermission.ts +64 -0
  6. package/src/api/modules/index.ts +69 -0
  7. package/src/api/modules/industry/green_mortgage.ts +42 -0
  8. package/src/api/modules/user/index.ts +4 -0
  9. package/src/api/request.ts +124 -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 +7 -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 +140 -0
  21. package/src/components/GEN-AI/index1.vue +422 -0
  22. package/src/components/GEN-AI/index10.vue +5 -0
  23. package/src/components/GEN-AI/index2.vue +548 -0
  24. package/src/components/GEN-AI/index3.vue +605 -0
  25. package/src/components/GEN-AI/index4.vue +609 -0
  26. package/src/components/GEN-AI/index5.vue +558 -0
  27. package/src/components/GEN-AI/index6.vue +634 -0
  28. package/src/components/GEN-AI/index7.vue +688 -0
  29. package/src/components/GEN-AI/index8.vue +458 -0
  30. package/src/components/GEN-AI/index9.vue +1217 -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 +124 -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/directives/btnLoading.ts +455 -0
  42. package/src/directives/debounce copy.ts +670 -0
  43. package/src/directives/debounce.ts +98 -0
  44. package/src/directives/index.ts +25 -0
  45. package/src/layouts/MainLayout.vue +129 -0
  46. package/src/main.ts +76 -0
  47. package/src/router/index.ts +142 -0
  48. package/src/router/menus.ts +28 -0
  49. package/src/store/modules/gen-ai/index.ts +1 -0
  50. package/src/store/modules/gen-ai/reportBuild.ts +37 -0
  51. package/src/style.css +79 -0
  52. package/src/styles/report-config-popover.css +39 -0
  53. package/src/styles/report-icon-btn.css +19 -0
  54. package/src/test/mock.ts +101 -0
  55. package/src/test/test1.vue +402 -0
  56. package/src/test/test2.vue +1689 -0
  57. package/src/types/gen-ai/build-report/reportEditor.ts +684 -0
  58. package/src/utils/function.ts +46 -0
  59. package/src/utils/gen-ai-navigation.ts +1 -0
  60. package/src/utils/gen-ai-scroll.ts +1 -0
  61. package/src/utils/openDataLoaderWordExport.ts +33 -0
  62. package/src/utils/pageScrollbar.ts +115 -0
  63. package/src/utils/randomTranscode.ts +87 -0
  64. package/src/utils/reportPdfExport.ts +44 -0
  65. package/src/views/Blank.vue +68 -0
  66. package/src/views/Home.vue +29 -0
  67. package/src/views/TemplateCenter/Knowledge.ts +83 -0
  68. package/src/views/TemplateCenter/data.d.ts +10 -0
  69. package/src/views/TemplateCenter/index.vue +1128 -0
  70. package/src/views/TemplateCenter/service.ts +69 -0
  71. package/src/views/gen-ai/build-report/chartStreamRefs.ts +174 -0
  72. package/src/views/gen-ai/build-report/coherence-check/index.vue +559 -0
  73. package/src/views/gen-ai/build-report/components/ReportChapterWorkspace.vue +4258 -0
  74. package/src/views/gen-ai/build-report/components/ReportConfigPanel.vue +727 -0
  75. package/src/views/gen-ai/build-report/components/ReportGenerateSettingsPanel.vue +2361 -0
  76. package/src/views/gen-ai/build-report/components/ReportGeneratingPanel.vue +1765 -0
  77. package/src/views/gen-ai/build-report/components/ReportPromptPanel.vue +614 -0
  78. package/src/views/gen-ai/build-report/components/ReportSourceFileSummary.vue +656 -0
  79. package/src/views/gen-ai/build-report/components/ReportSourcesPanel.vue +1327 -0
  80. package/src/views/gen-ai/build-report/components/ReportTemplateDetail.vue +191 -0
  81. package/src/views/gen-ai/build-report/components/ReportTemplateEditorFormPanel.vue +667 -0
  82. package/src/views/gen-ai/build-report/components/ReportTemplatePicker.vue +302 -0
  83. package/src/views/gen-ai/build-report/components/ReportWorkflowSidebar.vue +148 -0
  84. package/src/views/gen-ai/build-report/components/paramsSetting.vue +886 -0
  85. package/src/views/gen-ai/build-report/components/reportEdit.vue +2283 -0
  86. package/src/views/gen-ai/build-report/index.vue +856 -0
  87. package/src/views/gen-ai/build-report/report-domain.ts +1133 -0
  88. package/src/views/gen-ai/build-report/report-template-data.ts +295 -0
  89. package/src/views/gen-ai/build-report/useReportWorkflow.ts +1408 -0
  90. package/src/views/gen-ai/common/assets/NotoSans-SC.woff2 +0 -0
  91. package/src/views/gen-ai/common/assets/NotoSans-TC.woff2 +0 -0
  92. package/src/views/gen-ai/common/assets/NotoSans.woff2 +0 -0
  93. package/src/views/gen-ai/common/assets/fonts.css +278 -0
  94. package/src/views/gen-ai/common/assets/pagination.css +163 -0
  95. package/src/views/gen-ai/common/directives/btnLoading.ts +455 -0
  96. package/src/views/gen-ai/common/directives/debounce.ts +98 -0
  97. package/src/views/gen-ai/common/directives/index.ts +30 -0
  98. package/src/views/gen-ai/common/en.md +417 -0
  99. package/src/views/gen-ai/common/index.ts +36 -0
  100. package/src/views/gen-ai/common/types/component-expose.ts +9 -0
  101. package/src/views/gen-ai/common/utils/api-res-status.ts +7 -0
  102. package/src/views/gen-ai/common/utils/navigation.ts +59 -0
  103. package/src/views/gen-ai/common/utils/remote-request.ts +45 -0
  104. package/src/views/gen-ai/common/utils/scroll.ts +577 -0
  105. package/src/views/gen-ai/components/AIExport/docx-export.ts +950 -0
  106. package/src/views/gen-ai/components/AIExport/docxExport.ts +2566 -0
  107. package/src/views/gen-ai/components/AIExport/index.ts +52 -0
  108. package/src/views/gen-ai/components/AIExport/parseMarkdownHtml.ts +649 -0
  109. package/src/views/gen-ai/components/CapsuleScrollbar.vue +149 -0
  110. package/src/views/gen-ai/components/ChapterTitleScroll.vue +468 -0
  111. package/src/views/gen-ai/components/EntryHeroNavCards.vue +379 -0
  112. package/src/views/gen-ai/components/GenAiPagination.vue +52 -0
  113. package/src/views/gen-ai/components/ModelCard.vue +1114 -0
  114. package/src/views/gen-ai/components/Preview/FilePreviewPage.vue +947 -0
  115. package/src/views/gen-ai/components/Preview/PreviewCapsuleScroll.vue +205 -0
  116. package/src/views/gen-ai/components/Preview/PreviewOutlineTree.vue +148 -0
  117. package/src/views/gen-ai/components/Preview/index.ts +11 -0
  118. package/src/views/gen-ai/components/Preview/preview.ts +722 -0
  119. package/src/views/gen-ai/components/PromptField.vue +505 -0
  120. package/src/views/gen-ai/components/RecentReportsTable.vue +974 -0
  121. package/src/views/gen-ai/components/ReportCollapsibleSection.vue +172 -0
  122. package/src/views/gen-ai/components/ReportHistoryList.vue +637 -0
  123. package/src/views/gen-ai/components/ReportSectionConfirmPopover.vue +251 -0
  124. package/src/views/gen-ai/components/editor/ChapterTiptapEditor.vue +3139 -0
  125. package/src/views/gen-ai/components/editor/StructureBlockView.vue +441 -0
  126. package/src/views/gen-ai/components/editor/TiptapToolbar.vue +1036 -0
  127. package/src/views/gen-ai/components/editor/TypewriterDocEditor.vue +398 -0
  128. package/src/views/gen-ai/components/editor/_StructureOrgNode.vue +326 -0
  129. package/src/views/gen-ai/components/editor/index.ts +12 -0
  130. package/src/views/gen-ai/components/editor/parseMarkdownHtml.ts +667 -0
  131. package/src/views/gen-ai/gen-entry/api/index.ts +32 -0
  132. package/src/views/gen-ai/gen-entry/index.vue +287 -0
  133. package/src/views/gen-ai/gen-entry/types/index.ts +4 -0
  134. package/src/views/gen-ai/i.mark +2 -0
  135. package/src/views/gen-ai/management-center/index.vue +1815 -0
  136. package/src/views/gen-ai/management-center/mockData.ts +248 -0
  137. package/src/views/gen-ai/management-center/types/index.ts +37 -0
  138. package/src/views/gen-ai/model-manager/TemplateEditorFormPanel.vue +661 -0
  139. package/src/views/gen-ai/model-manager/api/index.ts +32 -0
  140. package/src/views/gen-ai/model-manager/api/mockApi.ts +33 -0
  141. package/src/views/gen-ai/model-manager/index.vue +1583 -0
  142. package/src/views/gen-ai/model-manager/mockData.ts +177 -0
  143. package/src/views/gen-ai/model-manager/template-detail-shared.ts +154 -0
  144. package/src/views/gen-ai/model-manager/template-editor.vue +345 -0
  145. package/src/views/gen-ai/model-manager/types/index.ts +40 -0
  146. package/src/views/gen-ai/model-manager/utils.ts +25 -0
  147. package/src/views/greenMortgage.vue +1834 -0
  148. package/src/vite-env.d.ts +46 -0
@@ -0,0 +1,974 @@
1
+ <template>
2
+ <div ref="rootRef" class="recent-reports-table">
3
+ <el-table
4
+ ref="tableRef"
5
+ :data="reports"
6
+ :fit="true"
7
+ :max-height="tableMaxHeight"
8
+ table-layout="fixed"
9
+ >
10
+ <el-table-column
11
+ prop="name"
12
+ label="報告名稱"
13
+ min-width="240"
14
+ align="left"
15
+ header-align="left"
16
+ class-name="industry-col-name"
17
+ >
18
+ <template #default="{ row }">
19
+ <a
20
+ v-if="row.canOpen"
21
+ href="#"
22
+ class="industry-report-name industry-report-name__pill industry-report-name--link"
23
+ @click.prevent="$emit('open', row)"
24
+ >{{ row.name }}</a
25
+ >
26
+ <span v-else class="industry-report-name industry-report-name__pill">{{ row.name }}</span>
27
+ </template>
28
+ </el-table-column>
29
+ <el-table-column
30
+ prop="industry"
31
+ label="行業"
32
+ min-width="88"
33
+ align="left"
34
+ header-align="left"
35
+ />
36
+ <el-table-column prop="year" label="年份" min-width="80" align="left" header-align="left" />
37
+ <el-table-column prop="status" label="狀態" min-width="96" align="left" header-align="left" />
38
+ <el-table-column
39
+ prop="initiator"
40
+ label="發起人"
41
+ min-width="220"
42
+ class-name="industry-col-initiator"
43
+ align="left"
44
+ header-align="left"
45
+ />
46
+ <el-table-column
47
+ label="編輯"
48
+ min-width="360"
49
+ class-name="industry-col-editor"
50
+ align="left"
51
+ header-align="left"
52
+ >
53
+ <template #default="{ row }">
54
+ <div class="industry-editors">
55
+ <span class="industry-editors__text">{{ formatVisibleEditors(row) }}</span>
56
+ <el-popover
57
+ v-if="resolveEditors(row).length > EDITOR_PREVIEW_COUNT"
58
+ placement="bottom-start"
59
+ :width="320"
60
+ trigger="click"
61
+ :visible="isEditorsPopoverOpen(row.id)"
62
+ popper-class="recent-editors-popover"
63
+ @update:visible="(open: boolean) => setEditorsPopoverOpen(row.id, open)"
64
+ >
65
+ <template #reference>
66
+ <button
67
+ type="button"
68
+ class="industry-editors__more"
69
+ title="查看全部編輯者"
70
+ @click.stop
71
+ >
72
+ ...
73
+ </button>
74
+ </template>
75
+ <div class="recent-editors-popover__panel">
76
+ <div class="recent-editors-popover__title">全部編輯者</div>
77
+ <table class="recent-editors-popover__table">
78
+ <thead>
79
+ <tr>
80
+ <th>用戶名</th>
81
+ <th>所屬部門</th>
82
+ </tr>
83
+ </thead>
84
+ <tbody>
85
+ <tr v-for="(item, index) in toEditorRows(row)" :key="`${row.id}-${index}`">
86
+ <td>{{ item.name }}</td>
87
+ <td>{{ item.department }}</td>
88
+ </tr>
89
+ </tbody>
90
+ </table>
91
+ </div>
92
+ </el-popover>
93
+ </div>
94
+ </template>
95
+ </el-table-column>
96
+ <el-table-column
97
+ prop="permission"
98
+ label="我的權限"
99
+ min-width="112"
100
+ align="left"
101
+ header-align="left"
102
+ />
103
+ <el-table-column
104
+ prop="updatedAt"
105
+ label="最近更新"
106
+ min-width="112"
107
+ align="left"
108
+ header-align="left"
109
+ />
110
+ </el-table>
111
+
112
+ <!-- 表格外側滑塊(樣式對齊 el-scrollbar__bar.is-vertical),不改動 table 內部 overflow -->
113
+ <div
114
+ v-show="scrollCan"
115
+ class="recent-reports-table__vbar"
116
+ :style="vbarTrackStyle"
117
+ aria-hidden="true"
118
+ >
119
+ <div
120
+ class="recent-reports-table__vthumb"
121
+ :style="vbarThumbStyle"
122
+ @mousedown.prevent="onThumbMouseDown"
123
+ />
124
+ </div>
125
+ </div>
126
+ </template>
127
+
128
+ <script lang="ts">
129
+ export interface RecentReport {
130
+ id: string
131
+ name: string
132
+ industry: string
133
+ year: string
134
+ status: string
135
+ initiator: string
136
+ /** 編輯者列表(優先) */
137
+ editors?: string[]
138
+ /** 兼容舊字段:逗號分隔字串 */
139
+ editor?: string
140
+ permission: string
141
+ updatedAt: string
142
+ canOpen: boolean
143
+ }
144
+
145
+ export const EDITOR_PREVIEW_COUNT = 2
146
+
147
+ export function resolveEditors(row: RecentReport): string[] {
148
+ if (row.editors?.length) return row.editors
149
+ if (!row.editor?.trim()) return []
150
+ return row.editor
151
+ .replace(/\s*\.\.\.\s*$/u, '')
152
+ .split(/\s*,\s*/u)
153
+ .map((item) => item.trim())
154
+ .filter(Boolean)
155
+ }
156
+
157
+ export function formatVisibleEditors(row: RecentReport): string {
158
+ const list = resolveEditors(row)
159
+ if (!list.length) return '—'
160
+ return list.slice(0, EDITOR_PREVIEW_COUNT).join(' , ')
161
+ }
162
+
163
+ export interface EditorRow {
164
+ name: string
165
+ department: string
166
+ }
167
+
168
+ /** 解析「姓名 (部門)」為表格行 */
169
+ export function parseEditorLabel(label: string): EditorRow {
170
+ const matched = label.match(/^(.+?)\s*[((]\s*(.+?)\s*[))]\s*$/u)
171
+ if (!matched) {
172
+ return { name: label.trim() || '—', department: '—' }
173
+ }
174
+ const department = matched[2]!.replace(/^單位\s*/u, '').trim() || '—'
175
+ return {
176
+ name: matched[1]!.trim() || '—',
177
+ department,
178
+ }
179
+ }
180
+
181
+ export function toEditorRows(row: RecentReport): EditorRow[] {
182
+ return resolveEditors(row).map(parseEditorLabel)
183
+ }
184
+
185
+ export const RECENT_REPORTS: RecentReport[] = [
186
+ {
187
+ id: '1',
188
+ name: '深圳房地產風控年報 - 2024',
189
+ industry: '房地產',
190
+ year: '2025',
191
+ status: '草稿',
192
+ initiator: '陳大文 (單位 xxxx)',
193
+ editors: ['陳1文 (行業研究部)', '陳2文 (投行業務部)', '錢七 (行業研究部)', '王五 (投行業務部)'],
194
+ permission: '可編輯',
195
+ updatedAt: '3天前',
196
+ canOpen: true,
197
+ },
198
+ {
199
+ id: '2',
200
+ name: '深圳房地產風控年報 - 2024',
201
+ industry: '房地產',
202
+ year: '2024',
203
+ status: '已產生',
204
+ initiator: '陳中文 (單位 yyyy)',
205
+ editors: ['陳4文 (行業研究部)', '陳5文 (投行業務部)', '錢七 (行業研究部)', '王五 (投行業務部)'],
206
+ permission: '無權限',
207
+ updatedAt: '3天前',
208
+ canOpen: false,
209
+ },
210
+ {
211
+ id: '3',
212
+ name: '深圳房地產風控年報 - 2024',
213
+ industry: '房地產',
214
+ year: '2023',
215
+ status: '已導出',
216
+ initiator: '陳小文 (單位 zzzz)',
217
+ editors: ['陳6文 (單位 zzzz)'],
218
+ permission: '可編輯',
219
+ updatedAt: '3天前',
220
+ canOpen: true,
221
+ },
222
+ {
223
+ id: '4',
224
+ name: '深圳房地產風控年報 - 2024',
225
+ industry: '房地產',
226
+ year: '2023',
227
+ status: '已導出',
228
+ initiator: '陳小文 (單位 zzzz)',
229
+ editors: ['陳6文 (單位 zzzz)', '陳3文 (單位 xxxx)', '陳11文 (單位 zzzz)'],
230
+ permission: '可編輯',
231
+ updatedAt: '3天前',
232
+ canOpen: true,
233
+ },
234
+ {
235
+ id: '5',
236
+ name: '深圳房地產風控年報 - 2024',
237
+ industry: '房地產',
238
+ year: '2023',
239
+ status: '已導出',
240
+ initiator: '陳小文 (單位 zzzz)',
241
+ editors: ['陳6文 (單位 zzzz)'],
242
+ permission: '可編輯',
243
+ updatedAt: '3天前',
244
+ canOpen: true,
245
+ },
246
+ {
247
+ id: '6',
248
+ name: '深圳房地產風控年報 - 2024',
249
+ industry: '房地產',
250
+ year: '2023',
251
+ status: '已導出',
252
+ initiator: '陳小文 (單位 zzzz)',
253
+ editors: ['陳6文 (單位 rrrr)', '陳3文 (單位 xxxx)'],
254
+ permission: '可編輯',
255
+ updatedAt: '3天前',
256
+ canOpen: true,
257
+ },
258
+ {
259
+ id: '7',
260
+ name: '深圳房地產風控年報 - 2024',
261
+ industry: '房地產',
262
+ year: '2023',
263
+ status: '已導出',
264
+ initiator: '陳小文 (單位 zzzz)',
265
+ editors: ['陳6文 (單位 zzzz)'],
266
+ permission: '可編輯',
267
+ updatedAt: '3天前',
268
+ canOpen: true,
269
+ },
270
+ {
271
+ id: '8',
272
+ name: '深圳房地產風控年報 - 2024',
273
+ industry: '房地產',
274
+ year: '2023',
275
+ status: '已導出',
276
+ initiator: '陳小文 (單位 zzzz)',
277
+ editors: ['陳6文 (單位 zzzz)'],
278
+ permission: '可編輯',
279
+ updatedAt: '3天前',
280
+ canOpen: true,
281
+ },
282
+ {
283
+ id: '9',
284
+ name: '深圳房地產風控年報 - 2024',
285
+ industry: '房地產',
286
+ year: '2023',
287
+ status: '已導出',
288
+ initiator: '陳小文 (單位 zzzz)',
289
+ editors: ['陳6文 (單位 zzzz)'],
290
+ permission: '可編輯',
291
+ updatedAt: '3天前',
292
+ canOpen: true,
293
+ },
294
+ {
295
+ id: '10',
296
+ name: '深圳房地產風控年報 - 2024',
297
+ industry: '房地產',
298
+ year: '2023',
299
+ status: '已導出',
300
+ initiator: '陳小文 (單位 zzzz)',
301
+ editors: ['陳6文 (單位 zzzz)'],
302
+ permission: '可編輯',
303
+ updatedAt: '3天前',
304
+ canOpen: true,
305
+ },
306
+ {
307
+ id: '11',
308
+ name: '上海商業地產風控季報 - 2024',
309
+ industry: '商業地產',
310
+ year: '2024',
311
+ status: '草稿',
312
+ initiator: '王小明 (單位 aaaa)',
313
+ editors: ['王2文 (單位 aaaa)'],
314
+ permission: '可編輯',
315
+ updatedAt: '5天前',
316
+ canOpen: true,
317
+ },
318
+ {
319
+ id: '12',
320
+ name: '廣州基建項目風控月報 - 2024',
321
+ industry: '基建',
322
+ year: '2024',
323
+ status: '已產生',
324
+ initiator: '李雷 (單位 bbbb)',
325
+ editors: ['李3文 (單位 bbbb)'],
326
+ permission: '可編輯',
327
+ updatedAt: '1周前',
328
+ canOpen: true,
329
+ },
330
+ {
331
+ id: '13',
332
+ name: '北京科技園區風控年報 - 2023',
333
+ industry: '科技園區',
334
+ year: '2023',
335
+ status: '已導出',
336
+ initiator: '韓梅梅 (單位 cccc)',
337
+ editors: ['韓4文 (單位 cccc)'],
338
+ permission: '無權限',
339
+ updatedAt: '2周前',
340
+ canOpen: false,
341
+ },
342
+ {
343
+ id: '14',
344
+ name: '北京科技園區風控年報 - 2023',
345
+ industry: '科技園區',
346
+ year: '2023',
347
+ status: '已導出',
348
+ initiator: '韓梅梅 (單位 cccc)',
349
+ editors: ['韓4文 (單位 cccc)'],
350
+ permission: '無權限',
351
+ updatedAt: '2周前',
352
+ canOpen: false,
353
+ },
354
+ {
355
+ id: '15',
356
+ name: '北京科技園區風控年報 - 2023',
357
+ industry: '科技園區',
358
+ year: '2023',
359
+ status: '已導出',
360
+ initiator: '韓梅梅 (單位 cccc)',
361
+ editors: ['韓4文 (單位 cccc)'],
362
+ permission: '無權限',
363
+ updatedAt: '2周前',
364
+ canOpen: false,
365
+ },
366
+ {
367
+ id: '16',
368
+ name: '北京科技園區風控年報 - 2023',
369
+ industry: '科技園區',
370
+ year: '2023',
371
+ status: '已導出',
372
+ initiator: '韓梅梅 (單位 cccc)',
373
+ editors: ['韓4文 (單位 cccc)'],
374
+ permission: '無權限',
375
+ updatedAt: '2周前',
376
+ canOpen: false,
377
+ },
378
+ {
379
+ id: '17',
380
+ name: '北京科技園區風控年報 - 2023',
381
+ industry: '科技園區',
382
+ year: '2023',
383
+ status: '已導出',
384
+ initiator: '韓梅梅 (單位 cccc)',
385
+ editors: ['韓4文 (單位 cccc)'],
386
+ permission: '無權限',
387
+ updatedAt: '2周前',
388
+ canOpen: false,
389
+ },
390
+ ]
391
+ </script>
392
+
393
+ <script setup lang="ts">
394
+ import type { TableInstance } from 'element-plus'
395
+ import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
396
+
397
+ const props = defineProps<{
398
+ reports: RecentReport[]
399
+ /**
400
+ * 父級傳入的表格最大高度(出現滾動條的上限)。
401
+ * number 視為 px;也可傳 '400px' / '50vh' / '100%' 等。
402
+ * 未傳時按父容器剩餘高度自動測量。
403
+ */
404
+ maxHeight?: number | string
405
+ }>()
406
+
407
+ defineEmits<{
408
+ open: [report: RecentReport]
409
+ }>()
410
+
411
+ const rootRef = ref<HTMLElement>()
412
+ const tableRef = ref<TableInstance>()
413
+ const measuredMaxHeight = ref<number | undefined>(undefined)
414
+ const editorsPopoverOpenMap = reactive<Record<string, boolean>>({})
415
+
416
+ const hasExternalMaxHeight = computed(() => {
417
+ const v = props.maxHeight
418
+ return v != null && v !== ''
419
+ })
420
+
421
+ /** 實際交給 el-table 的 max-height */
422
+ const tableMaxHeight = computed(() => {
423
+ if (!hasExternalMaxHeight.value) return measuredMaxHeight.value
424
+ return props.maxHeight
425
+ })
426
+
427
+ function isEditorsPopoverOpen(id: string) {
428
+ return !!editorsPopoverOpenMap[id]
429
+ }
430
+
431
+ function setEditorsPopoverOpen(id: string, open: boolean) {
432
+ editorsPopoverOpenMap[id] = open
433
+ }
434
+
435
+ const THUMB_PX = 50
436
+ const scrollCan = ref(false)
437
+ const thumbHeight = ref(THUMB_PX)
438
+ const thumbOffset = ref(0)
439
+ const trackTop = ref(0)
440
+ const trackHeight = ref(0)
441
+
442
+ const vbarTrackStyle = computed(() => ({
443
+ top: `${trackTop.value}px`,
444
+ height: `${trackHeight.value}px`,
445
+ }))
446
+
447
+ const vbarThumbStyle = computed(() => ({
448
+ height: `${thumbHeight.value}px`,
449
+ transform: `translateY(${thumbOffset.value}px)`,
450
+ }))
451
+
452
+ function getScrollWrap(): HTMLElement | null {
453
+ return (
454
+ (rootRef.value?.querySelector(
455
+ '.el-table__body-wrapper .el-scrollbar__wrap'
456
+ ) as HTMLElement | null) ?? null
457
+ )
458
+ }
459
+
460
+ function refreshOuterBar() {
461
+ const root = rootRef.value
462
+ const wrap = getScrollWrap()
463
+ if (!root || !wrap) {
464
+ scrollCan.value = false
465
+ return
466
+ }
467
+
468
+ const { scrollTop, scrollHeight, clientHeight } = wrap
469
+ const can = scrollHeight > clientHeight + 1
470
+ scrollCan.value = can
471
+ if (!can) return
472
+
473
+ const header = root.querySelector('.el-table__header-wrapper') as HTMLElement | null
474
+ const headerH = header?.offsetHeight ?? 0
475
+ trackTop.value = headerH + 2
476
+ trackHeight.value = Math.max(0, clientHeight - 4)
477
+
478
+ const trackH = trackHeight.value
479
+ // 與編輯器同款:滑塊固定 50px
480
+ thumbHeight.value = Math.min(trackH, THUMB_PX)
481
+ const maxTravel = Math.max(0, trackH - thumbHeight.value)
482
+ const scrollMax = scrollHeight - clientHeight
483
+ thumbOffset.value = scrollMax > 0 ? Math.round((scrollTop / scrollMax) * maxTravel) : 0
484
+ }
485
+
486
+ function onWrapScroll() {
487
+ refreshOuterBar()
488
+ }
489
+
490
+ let dragCleanup: (() => void) | undefined
491
+
492
+ function onThumbMouseDown(event: MouseEvent) {
493
+ const wrapEl = getScrollWrap()
494
+ if (!wrapEl || !scrollCan.value) return
495
+ const scrollEl: HTMLElement = wrapEl
496
+
497
+ const startY = event.clientY
498
+ const startTop = thumbOffset.value
499
+ const trackH = trackHeight.value
500
+ const maxTravel = Math.max(0, trackH - thumbHeight.value)
501
+ const scrollMax = scrollEl.scrollHeight - scrollEl.clientHeight
502
+
503
+ function onMove(e: MouseEvent) {
504
+ if (maxTravel <= 0 || scrollMax <= 0) return
505
+ const delta = e.clientY - startY
506
+ const nextOffset = Math.min(maxTravel, Math.max(0, startTop + delta))
507
+ thumbOffset.value = nextOffset
508
+ scrollEl.scrollTop = (nextOffset / maxTravel) * scrollMax
509
+ }
510
+
511
+ function onUp() {
512
+ dragCleanup?.()
513
+ dragCleanup = undefined
514
+ }
515
+
516
+ window.addEventListener('mousemove', onMove)
517
+ window.addEventListener('mouseup', onUp, { once: true })
518
+ dragCleanup = () => {
519
+ window.removeEventListener('mousemove', onMove)
520
+ window.removeEventListener('mouseup', onUp)
521
+ }
522
+ }
523
+
524
+ function bindWrapScroll() {
525
+ unbindWrapScroll()
526
+ const wrap = getScrollWrap()
527
+ if (!wrap) return
528
+ wrap.addEventListener('scroll', onWrapScroll, { passive: true })
529
+ boundWrap = wrap
530
+ }
531
+
532
+ let boundWrap: HTMLElement | null = null
533
+
534
+ function unbindWrapScroll() {
535
+ boundWrap?.removeEventListener('scroll', onWrapScroll)
536
+ boundWrap = null
537
+ }
538
+
539
+ function measureTableHeight() {
540
+ if (hasExternalMaxHeight.value) {
541
+ tableRef.value?.doLayout()
542
+ void nextTick(() => refreshOuterBar())
543
+ return
544
+ }
545
+
546
+ const root = rootRef.value
547
+ const parent = root?.parentElement
548
+ if (!root || !parent) return
549
+
550
+ let used = 0
551
+ for (const child of Array.from(parent.children)) {
552
+ if (child === root) continue
553
+ used += (child as HTMLElement).offsetHeight
554
+ }
555
+ const style = getComputedStyle(parent)
556
+ const gap = Number.parseFloat(style.rowGap || style.gap || '0') || 0
557
+ const siblingCount = Math.max(0, parent.children.length - 1)
558
+ const available = Math.floor(parent.clientHeight - used - gap * siblingCount)
559
+ if (available > 0) {
560
+ measuredMaxHeight.value = available
561
+ }
562
+ tableRef.value?.doLayout()
563
+ void nextTick(() => refreshOuterBar())
564
+ }
565
+
566
+ function doLayout() {
567
+ measureTableHeight()
568
+ }
569
+
570
+ let resizeObserver: ResizeObserver | undefined
571
+
572
+ onMounted(() => {
573
+ measureTableHeight()
574
+ void nextTick(() => {
575
+ bindWrapScroll()
576
+ refreshOuterBar()
577
+ })
578
+ const root = rootRef.value
579
+ const parent = root?.parentElement
580
+ if (root || parent) {
581
+ resizeObserver = new ResizeObserver(() => {
582
+ void nextTick(() => {
583
+ measureTableHeight()
584
+ bindWrapScroll()
585
+ refreshOuterBar()
586
+ })
587
+ })
588
+ if (parent) resizeObserver.observe(parent)
589
+ if (root) resizeObserver.observe(root)
590
+ }
591
+ })
592
+
593
+ onUnmounted(() => {
594
+ resizeObserver?.disconnect()
595
+ unbindWrapScroll()
596
+ dragCleanup?.()
597
+ })
598
+
599
+ watch(
600
+ () => props.reports,
601
+ () => {
602
+ void nextTick(() => {
603
+ measureTableHeight()
604
+ bindWrapScroll()
605
+ refreshOuterBar()
606
+ })
607
+ }
608
+ )
609
+
610
+ watch(
611
+ () => props.maxHeight,
612
+ () => {
613
+ void nextTick(() => {
614
+ if (hasExternalMaxHeight.value) {
615
+ measuredMaxHeight.value = undefined
616
+ }
617
+ measureTableHeight()
618
+ bindWrapScroll()
619
+ refreshOuterBar()
620
+ })
621
+ }
622
+ )
623
+
624
+ defineExpose({ doLayout })
625
+ </script>
626
+
627
+ <style scoped>
628
+ .recent-reports-table {
629
+ --rr-table-row-h: 40px;
630
+ --rr-table-header-bg: #f9fafb;
631
+ --rr-table-header-fs: 14px;
632
+ --rr-table-header-color: #666;
633
+ --rr-table-header-fw: 500;
634
+ --rr-table-row-fs: 14px;
635
+ --rr-table-row-color: #111827;
636
+ --rr-table-border: 1px solid rgb(235, 238, 245);
637
+ --rr-font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
638
+ --rr-scroll-thumb: #999;
639
+ --rr-scroll-gutter: 16px;
640
+ position: relative;
641
+ width: 100%;
642
+ min-height: 0;
643
+ margin: 0;
644
+ padding: 0 var(--rr-scroll-gutter) 0 0;
645
+ box-sizing: border-box;
646
+ overflow: visible;
647
+ --el-table-border: none;
648
+ --el-table-border-color: transparent;
649
+ font-family: var(--rr-font-family);
650
+ }
651
+
652
+ /* 隱藏 table 內建豎向滑塊(外側自繪);底部橫向滑塊寬 50px */
653
+ .recent-reports-table :deep(.el-scrollbar__bar.is-vertical) {
654
+ display: none !important;
655
+ }
656
+
657
+ .recent-reports-table :deep(.el-scrollbar__wrap) {
658
+ scrollbar-width: none !important;
659
+ -ms-overflow-style: none !important;
660
+ }
661
+
662
+ .recent-reports-table :deep(.el-scrollbar__wrap::-webkit-scrollbar) {
663
+ width: 0 !important;
664
+ height: 0 !important;
665
+ display: none !important;
666
+ }
667
+
668
+ .recent-reports-table :deep(.el-scrollbar__bar.is-horizontal) {
669
+ display: block !important;
670
+ height: 8px !important;
671
+ left: 2px !important;
672
+ right: calc(var(--rr-scroll-gutter) + 2px) !important;
673
+ /* 與表格底邊預留 2px,避免橫向滑塊貼死最後一行 */
674
+ bottom: 2px !important;
675
+ opacity: 1 !important;
676
+ z-index: 10;
677
+ }
678
+
679
+ .recent-reports-table :deep(.el-scrollbar__bar.is-horizontal .el-scrollbar__thumb) {
680
+ display: block !important;
681
+ height: 8px !important;
682
+ width: 50px !important;
683
+ min-width: 50px !important;
684
+ border-radius: 4px !important;
685
+ background-color: var(--rr-scroll-thumb, #999) !important;
686
+ opacity: 1 !important;
687
+ }
688
+
689
+ .recent-reports-table__vbar {
690
+ position: absolute;
691
+ right: 4px;
692
+ width: 8px;
693
+ z-index: 10;
694
+ pointer-events: none;
695
+ }
696
+
697
+ .recent-reports-table__vthumb {
698
+ width: 8px;
699
+ height: 50px;
700
+ min-height: 50px;
701
+ border-radius: 4px;
702
+ background-color: var(--rr-scroll-thumb);
703
+ cursor: grab;
704
+ pointer-events: auto;
705
+ will-change: transform;
706
+ }
707
+
708
+ .recent-reports-table__vthumb:active {
709
+ cursor: grabbing;
710
+ background-color: #777;
711
+ }
712
+
713
+ .recent-reports-table :deep(.el-table) {
714
+ --el-table-header-text-color: var(--rr-table-header-color);
715
+ --el-table-text-color: var(--rr-table-row-color);
716
+ --el-table-row-hover-bg-color: #f5f7fa;
717
+ width: 100% !important;
718
+ font-family: var(--rr-font-family);
719
+ }
720
+
721
+ .recent-reports-table :deep(.el-table__inner-wrapper),
722
+ .recent-reports-table :deep(.el-table__header-wrapper),
723
+ .recent-reports-table :deep(.el-table__body-wrapper) {
724
+ margin-left: 0;
725
+ padding-left: 0;
726
+ }
727
+
728
+ .recent-reports-table :deep(.el-table__header table),
729
+ .recent-reports-table :deep(.el-table__body table) {
730
+ width: 100% !important;
731
+ table-layout: fixed;
732
+ }
733
+
734
+ .recent-reports-table :deep(.el-table__header .cell),
735
+ .recent-reports-table :deep(.el-table__body .cell) {
736
+ padding: 0;
737
+ text-align: left;
738
+ font-family: var(--rr-font-family);
739
+ }
740
+
741
+ .recent-reports-table :deep(.el-table__body-wrapper .cell) {
742
+ padding: 0;
743
+ }
744
+
745
+ .recent-reports-table :deep(.el-table__header th.el-table__cell),
746
+ .recent-reports-table :deep(.el-table__header th.el-table__cell .cell) {
747
+ font-size: var(--rr-table-header-fs);
748
+ font-weight: var(--rr-table-header-fw);
749
+ font-family: var(--rr-font-family);
750
+ color: var(--rr-table-header-color);
751
+ }
752
+
753
+ .recent-reports-table :deep(.el-table__header th.el-table__cell .cell) {
754
+ white-space: nowrap;
755
+ overflow: visible;
756
+ text-overflow: clip;
757
+ }
758
+
759
+ .recent-reports-table :deep(.el-table__header th.el-table__cell:first-child .cell) {
760
+ padding-left: 10px;
761
+ }
762
+
763
+ .recent-reports-table :deep(.industry-col-name .cell) {
764
+ overflow: hidden;
765
+ white-space: nowrap;
766
+ }
767
+
768
+ .recent-reports-table :deep(.industry-col-initiator .cell),
769
+ .recent-reports-table :deep(.industry-col-editor .cell) {
770
+ white-space: normal;
771
+ word-break: break-word;
772
+ line-height: 1.6;
773
+ overflow: visible;
774
+ }
775
+
776
+ .recent-reports-table :deep(.industry-col-editor.el-table__cell) {
777
+ overflow: visible;
778
+ }
779
+
780
+ .industry-editors {
781
+ display: inline-flex;
782
+ align-items: center;
783
+ flex-wrap: wrap;
784
+ gap: 0;
785
+ min-width: 0;
786
+ max-width: 100%;
787
+ }
788
+
789
+ .industry-editors__text {
790
+ min-width: 0;
791
+ }
792
+
793
+ .industry-editors__more {
794
+ margin: 0 0 0 4px;
795
+ padding: 0;
796
+ border: none;
797
+ background: none;
798
+ color: #c53355;
799
+ font-family: inherit;
800
+ font-size: inherit;
801
+ font-weight: 900;
802
+ letter-spacing: 0.08em;
803
+ line-height: inherit;
804
+ cursor: pointer;
805
+ }
806
+
807
+ .industry-editors__more:hover {
808
+ color: #a82b48;
809
+ }
810
+
811
+ .recent-reports-table :deep(.el-table__inner-wrapper::before),
812
+ .recent-reports-table :deep(.el-table__inner-wrapper::after) {
813
+ display: none;
814
+ }
815
+
816
+ .recent-reports-table :deep(.el-table__body-wrapper .el-table__body) {
817
+ border-collapse: separate;
818
+ border-spacing: 0;
819
+ }
820
+
821
+ .recent-reports-table :deep(.el-table__body tr.el-table__row),
822
+ .recent-reports-table :deep(.el-table__body td.el-table__cell),
823
+ .recent-reports-table :deep(.el-table__body td.el-table__cell .cell) {
824
+ font-size: var(--rr-table-row-fs);
825
+ font-family: var(--rr-font-family);
826
+ color: var(--rr-table-row-color);
827
+ }
828
+
829
+ .recent-reports-table :deep(.industry-report-name:not(.industry-report-name--link)) {
830
+ color: var(--rr-table-row-color);
831
+ }
832
+
833
+ .recent-reports-table :deep(.el-table__body tr.el-table__row) {
834
+ height: var(--rr-table-row-h);
835
+ background: #fff;
836
+ cursor: pointer;
837
+ }
838
+
839
+ .recent-reports-table :deep(.el-table__body tr) {
840
+ background: #fff;
841
+ }
842
+
843
+ .recent-reports-table :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell) {
844
+ background-color: #f5f7fa !important;
845
+ box-shadow: none !important;
846
+ }
847
+
848
+ .recent-reports-table
849
+ :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell:first-child) {
850
+ border-radius: 4px 0 0 4px;
851
+ }
852
+
853
+ .recent-reports-table :deep(.el-table__body tr.el-table__row:hover > td.el-table__cell:last-child) {
854
+ border-radius: 0 4px 4px 0;
855
+ }
856
+
857
+ .recent-reports-table :deep(.el-table__header-wrapper) {
858
+ border-radius: 4px;
859
+ overflow: hidden;
860
+ }
861
+
862
+ .recent-reports-table :deep(.el-table__header th.el-table__cell) {
863
+ font-weight: var(--rr-table-header-fw);
864
+ border-bottom: var(--rr-table-border);
865
+ background: var(--rr-table-header-bg);
866
+ height: var(--rr-table-row-h);
867
+ padding: 0 16px;
868
+ vertical-align: middle;
869
+ box-sizing: border-box;
870
+ }
871
+
872
+ .recent-reports-table :deep(.el-table__header th.el-table__cell:first-child) {
873
+ border-radius: 4px 0 0 4px;
874
+ }
875
+
876
+ .recent-reports-table :deep(.el-table__header th.el-table__cell:last-child) {
877
+ border-radius: 0 4px 4px 0;
878
+ }
879
+
880
+ .recent-reports-table :deep(.el-table__body td.el-table__cell) {
881
+ height: var(--rr-table-row-h);
882
+ border-bottom: var(--rr-table-border);
883
+ padding: 0 16px;
884
+ vertical-align: middle;
885
+ box-sizing: border-box;
886
+ }
887
+
888
+ .recent-reports-table :deep(.el-table__body td.el-table__cell .cell) {
889
+ display: flex;
890
+ align-items: center;
891
+ min-height: var(--rr-table-row-h);
892
+ height: 100%;
893
+ box-sizing: border-box;
894
+ }
895
+
896
+ .recent-reports-table :deep(.el-table__body td.el-table__cell:not(:first-child) .cell) {
897
+ white-space: nowrap;
898
+ }
899
+
900
+ .industry-report-name__pill {
901
+ display: block;
902
+ width: 100%;
903
+ max-width: 100%;
904
+ padding: 6px 10px;
905
+ border-radius: 4px;
906
+ background: transparent;
907
+ overflow: hidden;
908
+ text-overflow: ellipsis;
909
+ white-space: nowrap;
910
+ box-sizing: border-box;
911
+ line-height: 1.4;
912
+ }
913
+
914
+ .industry-report-name {
915
+ color: inherit;
916
+ }
917
+
918
+ .industry-report-name--link {
919
+ color: #409eff;
920
+ text-decoration: none;
921
+ cursor: pointer;
922
+ }
923
+
924
+ .industry-report-name--link:hover {
925
+ color: #409eff;
926
+ text-decoration: underline;
927
+ }
928
+ </style>
929
+
930
+ <style>
931
+ .recent-editors-popover.el-popover.el-popper {
932
+ --rr-editors-header-bg: #f9fafb;
933
+ padding: 12px 14px;
934
+ border: 1px solid #ebeef5;
935
+ border-radius: 6px;
936
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
937
+ }
938
+
939
+ .recent-editors-popover__title {
940
+ margin: -12px -14px 10px;
941
+ padding: 10px 14px;
942
+ background: var(--rr-editors-header-bg);
943
+ color: #303133;
944
+ font-size: 14px;
945
+ font-weight: 600;
946
+ line-height: 1.4;
947
+ }
948
+
949
+ .recent-editors-popover__table {
950
+ width: 100%;
951
+ border-collapse: collapse;
952
+ table-layout: fixed;
953
+ font-size: 13px;
954
+ color: #303133;
955
+ }
956
+
957
+ .recent-editors-popover__table th,
958
+ .recent-editors-popover__table td {
959
+ padding: 8px 10px;
960
+ text-align: left;
961
+ border-bottom: 1px solid #ebeef5;
962
+ word-break: break-all;
963
+ }
964
+
965
+ .recent-editors-popover__table th {
966
+ background: var(--rr-editors-header-bg);
967
+ font-weight: 600;
968
+ color: #606266;
969
+ }
970
+
971
+ .recent-editors-popover__table tbody tr:last-child td {
972
+ border-bottom: none;
973
+ }
974
+ </style>