aegon-dangerious 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 (32) hide show
  1. package/build-report/components/AiPromptGenerateDialog.vue +349 -0
  2. package/build-report/components/CapsuleScrollbar.vue +145 -0
  3. package/build-report/components/ChapterTitleScroll.vue +292 -0
  4. package/build-report/components/PromptField.vue +314 -0
  5. package/build-report/components/RecentReportsTable.vue +408 -0
  6. package/build-report/components/ReportChapterPreview.vue +437 -0
  7. package/build-report/components/ReportChapterWorkspace.vue +923 -0
  8. package/build-report/components/ReportCollapsibleSection.vue +132 -0
  9. package/build-report/components/ReportConfigPanel.vue +609 -0
  10. package/build-report/components/ReportGenerateSettingsPanel.vue +403 -0
  11. package/build-report/components/ReportGeneratingPanel.vue +908 -0
  12. package/build-report/components/ReportHistoryList.vue +157 -0
  13. package/build-report/components/ReportHistoryPanel.vue +140 -0
  14. package/build-report/components/ReportPromptPanel.vue +445 -0
  15. package/build-report/components/ReportSectionConfirmPopover.vue +232 -0
  16. package/build-report/components/ReportSourceFileSummary.vue +544 -0
  17. package/build-report/components/ReportSourcesMoreOverlay.vue +279 -0
  18. package/build-report/components/ReportSourcesPanel.vue +1055 -0
  19. package/build-report/components/ReportTemplateDetail.vue +176 -0
  20. package/build-report/components/ReportTemplateEditorFormPanel.vue +602 -0
  21. package/build-report/components/ReportTemplatePicker.vue +802 -0
  22. package/build-report/components/ReportTemplatePromptField.vue +314 -0
  23. package/build-report/components/ReportWorkflowSidebar.vue +104 -0
  24. package/build-report/components/reportEdit.vue +334 -0
  25. package/build-report/index.vue +646 -0
  26. package/build-report/report-list.vue +407 -0
  27. package/build-report/report-template-data.ts +248 -0
  28. package/build-report/styles/report-workflow-dialog.css +33 -0
  29. package/build-report/useReportWorkflow.ts +343 -0
  30. package/build-report/utils/report-edit-route.ts +109 -0
  31. package/build-report/utils/scroll.ts +361 -0
  32. package/package.json +12 -0
@@ -0,0 +1,407 @@
1
+ <template>
2
+ <div class="report-center-page">
3
+ <main ref="pageBodyRef" class="report-center-page__body is-scrollable">
4
+ <section class="report-center-hero">
5
+ <h2 class="report-center-hero__title">報告中心</h2>
6
+ <p class="report-center-hero__desc">查看與管理全部報告</p>
7
+ </section>
8
+
9
+ <section ref="reportsSectionRef" class="report-center-reports">
10
+ <h3 class="report-center-reports__title">
11
+ <el-icon class="report-center-reports__title-icon">
12
+ <Document />
13
+ </el-icon>
14
+ 全部報告
15
+ </h3>
16
+ <RecentReportsTable ref="tableRef" :reports="recentReports" @open="openReport" />
17
+ </section>
18
+ </main>
19
+
20
+ <CapsuleScrollbar ref="capsuleScrollbarRef" :scroll-el="pageBodyRef" :anchor-el="reportsSectionRef" :active="true"
21
+ :visible-ratio="2 / 3" :right-offset-px="1" :auto-hide-after-ms="3000" />
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { Document } from '@element-plus/icons-vue'
27
+ import { onMounted, onUnmounted, ref, watch } from 'vue'
28
+ import { useRouter, type Router } from 'vue-router'
29
+ import CapsuleScrollbar from './components/CapsuleScrollbar.vue'
30
+ import RecentReportsTable from './components/RecentReportsTable.vue'
31
+ import { bindViewportBodyHeight, lockDocumentScroll } from './utils/scroll'
32
+
33
+ interface RecentReport {
34
+ id: string
35
+ name: string
36
+ industry: string
37
+ year: string
38
+ status: string
39
+ initiator: string
40
+ editor: string
41
+ permission: string
42
+ updatedAt: string
43
+ canOpen: boolean
44
+ }
45
+
46
+ const GEN_AI_REQUEST_TIMEOUT_MS = 8000
47
+
48
+ const MOCK_RECENT_REPORTS: RecentReport[] = [
49
+ {
50
+ id: '1',
51
+ name: '深圳房地產風控年報 - 2024',
52
+ industry: '房地產',
53
+ year: '2025',
54
+ status: '草稿',
55
+ initiator: '陳大文 (單位 xxxx)',
56
+ editor: '陳1文 (單位 xxxx) , 陳2文 (單位 xxxx) , 陳3文 (單位 xxxx) ...',
57
+ permission: '可編輯',
58
+ updatedAt: '3天前',
59
+ canOpen: true,
60
+ },
61
+ {
62
+ id: '2',
63
+ name: '深圳房地產風控年報 - 2024',
64
+ industry: '房地產',
65
+ year: '2024',
66
+ status: '已產生',
67
+ initiator: '陳中文 (單位 yyyy)',
68
+ editor: '陳4文 (單位 yyyy) , 陳5文 (單位 yyyy) ...',
69
+ permission: '無權限',
70
+ updatedAt: '3天前',
71
+ canOpen: false,
72
+ },
73
+ {
74
+ id: '3',
75
+ name: '深圳房地產風控年報 - 2024',
76
+ industry: '房地產',
77
+ year: '2023',
78
+ status: '已導出',
79
+ initiator: '陳小文 (單位 zzzz)',
80
+ editor: '陳6文 (單位 zzzz)',
81
+ permission: '可編輯',
82
+ updatedAt: '3天前',
83
+ canOpen: true,
84
+ },
85
+ {
86
+ id: '4',
87
+ name: '深圳房地產風控年報 - 2024',
88
+ industry: '房地產',
89
+ year: '2023',
90
+ status: '已導出',
91
+ initiator: '陳小文 (單位 zzzz)',
92
+ editor: '陳6文 (單位 zzzz)',
93
+ permission: '可編輯',
94
+ updatedAt: '3天前',
95
+ canOpen: true,
96
+ },
97
+ {
98
+ id: '5',
99
+ name: '深圳房地產風控年報 - 2024',
100
+ industry: '房地產',
101
+ year: '2023',
102
+ status: '已導出',
103
+ initiator: '陳小文 (單位 zzzz)',
104
+ editor: '陳6文 (單位 zzzz)',
105
+ permission: '可編輯',
106
+ updatedAt: '3天前',
107
+ canOpen: true,
108
+ },
109
+ {
110
+ id: '6',
111
+ name: '深圳房地產風控年報 - 2024',
112
+ industry: '房地產',
113
+ year: '2023',
114
+ status: '已導出',
115
+ initiator: '陳小文 (單位 zzzz)',
116
+ editor: '陳6文 (單位 zzzz)',
117
+ permission: '可編輯',
118
+ updatedAt: '3天前',
119
+ canOpen: true,
120
+ },
121
+ {
122
+ id: '7',
123
+ name: '深圳房地產風控年報 - 2024',
124
+ industry: '房地產',
125
+ year: '2023',
126
+ status: '已導出',
127
+ initiator: '陳小文 (單位 zzzz)',
128
+ editor: '陳6文 (單位 zzzz)',
129
+ permission: '可編輯',
130
+ updatedAt: '3天前',
131
+ canOpen: true,
132
+ },
133
+ {
134
+ id: '8',
135
+ name: '深圳房地產風控年報 - 2024',
136
+ industry: '房地產',
137
+ year: '2023',
138
+ status: '已導出',
139
+ initiator: '陳小文 (單位 zzzz)',
140
+ editor: '陳6文 (單位 zzzz)',
141
+ permission: '可編輯',
142
+ updatedAt: '3天前',
143
+ canOpen: true,
144
+ },
145
+ {
146
+ id: '9',
147
+ name: '深圳房地產風控年報 - 2024',
148
+ industry: '房地產',
149
+ year: '2023',
150
+ status: '已導出',
151
+ initiator: '陳小文 (單位 zzzz)',
152
+ editor: '陳6文 (單位 zzzz)',
153
+ permission: '可編輯',
154
+ updatedAt: '3天前',
155
+ canOpen: true,
156
+ },
157
+ {
158
+ id: '10',
159
+ name: '深圳房地產風控年報 - 2024',
160
+ industry: '房地產',
161
+ year: '2023',
162
+ status: '已導出',
163
+ initiator: '陳小文 (單位 zzzz)',
164
+ editor: '陳6文 (單位 zzzz)',
165
+ permission: '可編輯',
166
+ updatedAt: '3天前',
167
+ canOpen: true,
168
+ },
169
+ {
170
+ id: '11',
171
+ name: '上海商業地產風控季報 - 2024',
172
+ industry: '商業地產',
173
+ year: '2024',
174
+ status: '草稿',
175
+ initiator: '王小明 (單位 aaaa)',
176
+ editor: '王2文 (單位 aaaa)',
177
+ permission: '可編輯',
178
+ updatedAt: '5天前',
179
+ canOpen: true,
180
+ },
181
+ {
182
+ id: '12',
183
+ name: '廣州基建項目風控月報 - 2024',
184
+ industry: '基建',
185
+ year: '2024',
186
+ status: '已產生',
187
+ initiator: '李雷 (單位 bbbb)',
188
+ editor: '李3文 (單位 bbbb)',
189
+ permission: '可編輯',
190
+ updatedAt: '1周前',
191
+ canOpen: true,
192
+ },
193
+ {
194
+ id: '13',
195
+ name: '北京科技園區風控年報 - 2023',
196
+ industry: '科技園區',
197
+ year: '2023',
198
+ status: '已導出',
199
+ initiator: '韓梅梅 (單位 cccc)',
200
+ editor: '韓4文 (單位 cccc)',
201
+ permission: '無權限',
202
+ updatedAt: '2周前',
203
+ canOpen: false,
204
+ },
205
+ ]
206
+
207
+ function getToken(): string {
208
+ return localStorage.getItem('token') || ''
209
+ }
210
+
211
+ function buildAuthHeaders(): HeadersInit {
212
+ const token = getToken()
213
+ return {
214
+ 'Content-Type': 'application/json',
215
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
216
+ }
217
+ }
218
+
219
+ async function postRequest<T = unknown>(options: {
220
+ url: string
221
+ data?: unknown
222
+ signal?: AbortSignal
223
+ }): Promise<T> {
224
+ const response = await fetch(options.url, {
225
+ method: 'POST',
226
+ headers: buildAuthHeaders(),
227
+ body: JSON.stringify(options.data ?? {}),
228
+ signal: options.signal,
229
+ })
230
+
231
+ if (!response.ok) {
232
+ throw new Error(`請求失敗 ${response.status}`)
233
+ }
234
+
235
+ return response.json() as Promise<T>
236
+ }
237
+
238
+ function isRecentReportList(data: unknown): data is RecentReport[] {
239
+ return Array.isArray(data) && data.length > 0 && typeof data[0]?.id === 'string'
240
+ }
241
+
242
+ async function fetchGenAiReportListData(data: { usmRight?: string } = {}) {
243
+ const controller = new AbortController()
244
+ const timer = window.setTimeout(() => controller.abort(), GEN_AI_REQUEST_TIMEOUT_MS)
245
+ try {
246
+ const response = await postRequest({
247
+ url: '/aiapi/v1/reportLatest',
248
+ data,
249
+ signal: controller.signal,
250
+ })
251
+ if (response == null) throw new Error('暫無數據')
252
+ if (isRecentReportList(response)) return response
253
+ } catch (error) {
254
+ console.error('獲取 fetchGenAiReportListData 失敗', error)
255
+ } finally {
256
+ window.clearTimeout(timer)
257
+ }
258
+ return [...MOCK_RECENT_REPORTS]
259
+ }
260
+
261
+ type RouteQuery = { [key: string]: string }
262
+
263
+ function openGenAiPage(
264
+ router: Router,
265
+ envUrl: string | undefined,
266
+ routeName: string,
267
+ query?: RouteQuery,
268
+ ) {
269
+ const configured = envUrl?.trim()
270
+ const url = configured || router.resolve({ name: routeName, query }).href
271
+ window.open(url, '_blank')
272
+ }
273
+
274
+ function openModelManager(router: Router, query?: RouteQuery) {
275
+ openGenAiPage(router, import.meta.env.VITE_MODEL_MANAGER_URL, 'model-manager', query)
276
+ }
277
+
278
+ const router = useRouter()
279
+ const recentReports = ref([...MOCK_RECENT_REPORTS])
280
+ const pageBodyRef = ref<HTMLElement | null>(null)
281
+ const reportsSectionRef = ref<HTMLElement | null>(null)
282
+ const tableRef = ref<InstanceType<typeof RecentReportsTable> | null>(null)
283
+ const capsuleScrollbarRef = ref<InstanceType<typeof CapsuleScrollbar> | null>(null)
284
+
285
+ async function loadRecentReports() {
286
+ recentReports.value = await fetchGenAiReportListData({})
287
+ }
288
+
289
+ function openReport(report: RecentReport) {
290
+ openModelManager(router, { id: report.id })
291
+ }
292
+
293
+ let cleanupBodyHeight: (() => void) | undefined
294
+ let cleanupLock: (() => void) | undefined
295
+
296
+ onMounted(() => {
297
+ void loadRecentReports()
298
+ if (pageBodyRef.value) {
299
+ cleanupBodyHeight = bindViewportBodyHeight(pageBodyRef.value)
300
+ }
301
+ cleanupLock = lockDocumentScroll()
302
+ tableRef.value?.doLayout()
303
+ capsuleScrollbarRef.value?.refresh()
304
+ })
305
+
306
+ watch(recentReports, () => {
307
+ tableRef.value?.doLayout()
308
+ capsuleScrollbarRef.value?.refresh()
309
+ })
310
+
311
+ onUnmounted(() => {
312
+ cleanupBodyHeight?.()
313
+ cleanupLock?.()
314
+ })
315
+ </script>
316
+
317
+ <style scoped>
318
+ .report-center-page {
319
+ --gen-ai-design-w: 1920;
320
+ --gen-ai-fs-base: clamp(13px, calc(14 * 100vw / 1920), 14px);
321
+ --gen-ai-fs-md: clamp(14px, calc(16 * 100vw / 1920), 16px);
322
+ --gen-ai-fs-lg: clamp(16px, calc(18 * 100vw / 1920), 18px);
323
+ --gen-ai-fs-hero: clamp(26px, calc(32 * 100vw / 1920), 32px);
324
+ --gen-ai-lh-normal: 1.6;
325
+ --gen-ai-lh-relaxed: 1.7;
326
+ --gen-ai-fw-semibold: 600;
327
+ --gen-ai-fw-bold: 700;
328
+ font-family: 'PingFang TC', 'SF Pro TC', -apple-system, BlinkMacSystemFont, 'Helvetica Neue',
329
+ 'Microsoft JhengHei', 'Noto Sans TC', sans-serif;
330
+ font-size: var(--gen-ai-fs-base);
331
+ line-height: var(--gen-ai-lh-normal);
332
+ color: #2d3139;
333
+ display: flex;
334
+ flex-direction: column;
335
+ height: 100vh;
336
+ overflow: hidden;
337
+ background: #fff;
338
+ }
339
+
340
+ .report-center-page :deep(.el-table),
341
+ .report-center-page :deep(.el-input),
342
+ .report-center-page :deep(.el-select),
343
+ .report-center-page :deep(.el-pagination) {
344
+ font-size: inherit;
345
+ }
346
+
347
+ .report-center-page :deep(.el-button) {
348
+ font-size: 18px;
349
+ font-family: Arial, sans-serif;
350
+ }
351
+
352
+ .report-center-page__body {
353
+ flex: 1;
354
+ min-height: 0;
355
+ overflow-y: auto;
356
+ overflow-x: hidden;
357
+ -webkit-overflow-scrolling: touch;
358
+ width: 100%;
359
+ padding: 48px 24px 40px;
360
+ box-sizing: border-box;
361
+ }
362
+
363
+ .report-center-hero {
364
+ flex-shrink: 0;
365
+ text-align: center;
366
+ margin-bottom: 48px;
367
+ }
368
+
369
+ .report-center-hero__title {
370
+ margin: 0 0 12px;
371
+ font-size: var(--gen-ai-fs-hero);
372
+ font-weight: var(--gen-ai-fw-bold);
373
+ color: #1a1a1a;
374
+ }
375
+
376
+ .report-center-hero__desc {
377
+ margin: 0;
378
+ font-size: var(--gen-ai-fs-base);
379
+ line-height: var(--gen-ai-lh-relaxed);
380
+ color: #666;
381
+ }
382
+
383
+ .report-center-reports {
384
+ width: 80%;
385
+ max-width: 80%;
386
+ margin: 0 auto;
387
+ padding: 0;
388
+ box-sizing: border-box;
389
+ }
390
+
391
+ .report-center-reports__title {
392
+ display: inline-flex;
393
+ align-items: center;
394
+ gap: 8px;
395
+ margin: 0 0 16px;
396
+ padding: 0;
397
+ font-size: var(--gen-ai-fs-md);
398
+ font-weight: var(--gen-ai-fw-semibold);
399
+ color: #1a1a1a;
400
+ width: 100%;
401
+ }
402
+
403
+ .report-center-reports__title-icon {
404
+ font-size: var(--gen-ai-fs-lg);
405
+ color: #606266;
406
+ }
407
+ </style>
@@ -0,0 +1,248 @@
1
+ /** build-report 模板數據與 API */
2
+
3
+ export interface TemplateListParams {
4
+ unit?: string
5
+ keyword?: string
6
+ language?: string
7
+ }
8
+
9
+ export interface TemplateItem {
10
+ id: string
11
+ name: string
12
+ description: string
13
+ chapters: string[]
14
+ author: string
15
+ approver: string
16
+ updatedAt: string
17
+ unit: string
18
+ locked: boolean
19
+ locker: string
20
+ approvalStatus?: '待審批' | '已通過' | '已拒絕'
21
+ }
22
+
23
+ export interface TemplateChapterForm {
24
+ id: string
25
+ title: string
26
+ description: string
27
+ prompt: string
28
+ expanded: boolean
29
+ }
30
+
31
+ export interface TemplateEditorForm {
32
+ language: string
33
+ title: string
34
+ description: string
35
+ chapters: TemplateChapterForm[]
36
+ }
37
+
38
+ export type TemplateEditorMode = 'create' | 'edit' | 'copy'
39
+
40
+ const INDUSTRY_RISK_ANNUAL_DESC =
41
+ '本模板用於生成年度行業風險分析報告,覆蓋宏觀環境、行業運行、風險識別、授信結構、資產質素、重點客戶/項目、新發生風險、管控措施及下年度策略等模塊,支援按章節配置與版本管理。'
42
+
43
+ const INDUSTRY_RISK_ANNUAL_CHAPTERS = [
44
+ '1. 摘要與本年總結',
45
+ '2. 宏觀經濟與政策背景',
46
+ '3. 行業運行情況與核心指標',
47
+ '4. 風險識別與評級',
48
+ '5. 授信與客戶結構分析',
49
+ '6. 資產質素與逾期/不良變化6. 資產質素與逾期/不良變化6. 資產質素與逾期/不良變化6. 資產質素與逾期/不良變化6. 資產質素與逾期/不良變化',
50
+ '7. 重點客戶/項目風險',
51
+ '8. 新發生風險與預警',
52
+ '9. 風險管控措施與成效',
53
+ '10. 下年度風險研判與策略',
54
+ '11. 附件與數據',
55
+ ]
56
+
57
+ const QUARTERLY_BRIEF_DESC =
58
+ '面向季度復盤的輕量模板,聚焦行業景氣、核心指標波動、重點風險事件與管控建議,適用於管理層例會與條線月報。'
59
+
60
+ const QUARTERLY_BRIEF_CHAPTERS = [
61
+ '1. 季度摘要',
62
+ '2. 行業景氣與政策動態',
63
+ '3. 核心指標走勢',
64
+ '4. 重點風險事件',
65
+ '5. 管控措施與下季關注',
66
+ ]
67
+
68
+ const FULL_RISK_DESC =
69
+ '完整版風險報告模板,章節可擴展至 20+,覆蓋母行、子公司、區域及產品線多維度風險拆解,支援附錄與數據口徑說明。'
70
+
71
+ const FULL_RISK_CHAPTERS = [
72
+ ...INDUSTRY_RISK_ANNUAL_CHAPTERS,
73
+ '12. 區域風險分佈',
74
+ '13. 產品線風險拆解',
75
+ '14. 關聯方與集中度',
76
+ '15. 市場與流動性風險',
77
+ '16. 操作與合規風險',
78
+ '17. 聲譽與環境風險',
79
+ '18. 壓力測試結果',
80
+ '19. 整改跟進清單',
81
+ '20. 監管關注重點',
82
+ '21. 術語與指標口徑',
83
+ '22. 修訂記錄',
84
+ ]
85
+
86
+ export const MOCK_TEMPLATES: TemplateItem[] = [
87
+ {
88
+ id: '1',
89
+ name: '行業風險年報',
90
+ description: INDUSTRY_RISK_ANNUAL_DESC,
91
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
92
+ author: '王明輝(風險管理部)',
93
+ approver: '李思琪(合規部)',
94
+ updatedAt: '2025/11/7 15:21',
95
+ unit: '風險管理部',
96
+ locked: false,
97
+ locker: '陳大文(授信審批部)',
98
+ approvalStatus: '待審批',
99
+ },
100
+ {
101
+ id: '2',
102
+ name: '行業風險年報',
103
+ description: INDUSTRY_RISK_ANNUAL_DESC,
104
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
105
+ author: '張偉(風險管理部)',
106
+ approver: '劉芳(合規部)',
107
+ updatedAt: '2025/10/28 09:15',
108
+ unit: '授信審批部',
109
+ locked: true,
110
+ locker: '陳大文(授信審批部)',
111
+ },
112
+ {
113
+ id: '3',
114
+ name: '行業風險年報',
115
+ description: INDUSTRY_RISK_ANNUAL_DESC,
116
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
117
+ author: '趙敏(公司金融部)',
118
+ approver: '周傑(合規部)',
119
+ updatedAt: '2025/9/12 11:40',
120
+ unit: '風險管理部',
121
+ locked: false,
122
+ locker: '陳大文(授信審批部)',
123
+ },
124
+ {
125
+ id: '4',
126
+ name: '季度行業風險簡報',
127
+ description: QUARTERLY_BRIEF_DESC,
128
+ chapters: QUARTERLY_BRIEF_CHAPTERS,
129
+ author: '陳曉東(風險管理部)',
130
+ approver: '何靜(合規部)',
131
+ updatedAt: '2025/8/3 16:05',
132
+ unit: '合規部',
133
+ locked: false,
134
+ locker: '陳大文(授信審批部)',
135
+ },
136
+ {
137
+ id: '5',
138
+ name: '全面風險分析模板',
139
+ description: FULL_RISK_DESC,
140
+ chapters: FULL_RISK_CHAPTERS,
141
+ author: '孫麗(風險管理部)',
142
+ approver: '馬強(合規部)',
143
+ updatedAt: '2025/7/19 14:32',
144
+ unit: '公司金融部',
145
+ locked: false,
146
+ locker: '陳大文(授信審批部)',
147
+ },
148
+ {
149
+ id: '6',
150
+ name: '行業風險年報',
151
+ description: INDUSTRY_RISK_ANNUAL_DESC,
152
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
153
+ author: '黃磊(授信審批部)',
154
+ approver: '吳婷(合規部)',
155
+ updatedAt: '2025/6/25 10:18',
156
+ unit: '授信審批部',
157
+ locked: true,
158
+ locker: '陳大文(授信審批部)',
159
+ },
160
+ ]
161
+
162
+ let chapterSeed = 0
163
+
164
+ export function createEmptyChapter(expanded = false): TemplateChapterForm {
165
+ chapterSeed += 1
166
+ return {
167
+ id: `chapter-${Date.now()}-${chapterSeed}`,
168
+ title: '',
169
+ description: '',
170
+ prompt: '',
171
+ expanded,
172
+ }
173
+ }
174
+
175
+ function stripChapterOrder(title: string) {
176
+ return title.replace(/^\d+\.\s*/, '')
177
+ }
178
+
179
+ export function buildEditorFormFromTemplate(template: TemplateItem, copy = false): TemplateEditorForm {
180
+ return {
181
+ language: 'zh',
182
+ title: copy ? `${template.name}(副本)` : template.name,
183
+ description: template.description,
184
+ chapters: template.chapters.map((chapterTitle, index) => ({
185
+ id: `chapter-${template.id}-${index}`,
186
+ title: stripChapterOrder(chapterTitle),
187
+ description: index === 0 ? '本章節概述報告核心結論與年度重點。' : '',
188
+ prompt: index === 0 ? '請根據行業數據與風控政策,生成本章節分析內容。' : '',
189
+ expanded: index === 0,
190
+ })),
191
+ }
192
+ }
193
+
194
+ export function findTemplateById(id: string) {
195
+ return MOCK_TEMPLATES.find((item) => item.id === id)
196
+ }
197
+
198
+ function filterTemplates(list: TemplateItem[], params: TemplateListParams) {
199
+ const kw = params.keyword?.trim().toLowerCase() ?? ''
200
+ return list.filter((item) => {
201
+ const unitOk = !params.unit || item.unit === params.unit
202
+ const keywordOk =
203
+ !kw ||
204
+ item.name.toLowerCase().includes(kw) ||
205
+ item.description.toLowerCase().includes(kw) ||
206
+ item.author.toLowerCase().includes(kw)
207
+ return unitOk && keywordOk
208
+ })
209
+ }
210
+
211
+ const MOCK_DELAY_MS = 200
212
+
213
+ export async function fetchReportTemplateList(params: TemplateListParams = {}) {
214
+ await new Promise<void>((resolve) => {
215
+ window.setTimeout(resolve, MOCK_DELAY_MS)
216
+ })
217
+ return filterTemplates([...MOCK_TEMPLATES], params)
218
+ }
219
+
220
+ export interface ReportTemplateCard {
221
+ id: string
222
+ name: string
223
+ description: string
224
+ chapters: string[]
225
+ author: string
226
+ updatedAt: string
227
+ }
228
+
229
+ export function templateItemToReportCard(item: TemplateItem): ReportTemplateCard {
230
+ const dateOnly = item.updatedAt.includes(' ')
231
+ ? item.updatedAt.slice(0, item.updatedAt.indexOf(' '))
232
+ : item.updatedAt
233
+
234
+ return {
235
+ id: item.id,
236
+ name: item.name,
237
+ description: item.description,
238
+ chapters: [...item.chapters],
239
+ author: item.author,
240
+ updatedAt: dateOnly,
241
+ }
242
+ }
243
+
244
+ export const MOCK_REPORT_TEMPLATES: ReportTemplateCard[] = MOCK_TEMPLATES.map(templateItemToReportCard)
245
+
246
+ export function resolveReportTemplateById(templateId: string): ReportTemplateCard | null {
247
+ return MOCK_REPORT_TEMPLATES.find((item) => item.id === templateId) ?? null
248
+ }
@@ -0,0 +1,33 @@
1
+ /* 與 management-center「待處理選項」彈窗尺寸一致 */
2
+ .report-workflow-dialog-overlay {
3
+ overflow: auto;
4
+ }
5
+
6
+ .report-workflow-dialog.el-dialog {
7
+ --el-dialog-padding-primary: 0;
8
+ width: 60% !important;
9
+ max-width: 80%;
10
+ height: 70vh;
11
+ max-height: 70vh;
12
+ margin-top: 5% !important;
13
+ margin-left: 21% !important;
14
+ margin-right: auto !important;
15
+ border-radius: 4px;
16
+ overflow: hidden;
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ .report-workflow-dialog .el-dialog__header {
22
+ flex-shrink: 0;
23
+ }
24
+
25
+ .report-workflow-dialog .el-dialog__body {
26
+ flex: 1;
27
+ min-height: 0;
28
+ overflow-y: auto;
29
+ }
30
+
31
+ .report-workflow-dialog .el-dialog__footer {
32
+ flex-shrink: 0;
33
+ }