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,177 @@
1
+ import type { TemplateItem } from './types'
2
+
3
+ const INDUSTRY_RISK_ANNUAL_NAME = '行業風險年報測試擴展內容用於省略展示測試擴展內容用於省略展示'
4
+ const INDUSTRY_RISK_ANNUAL_INDUSTRY = '新能源汽車測試擴展內容用於省略展示測試擴展內容用於省略展示測'
5
+
6
+ const INDUSTRY_RISK_ANNUAL_DESC =
7
+ '本模板用於生成年度行業風險分析報告,覆蓋宏觀環境、行業運行、風險識別、授信結構、資產質素等模塊配置。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預警規則與附件數據清單,確保報告內容一致、可追溯且具備決策參考價值。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預警規則與附件數據清單,確保報告內容一致、可追溯且具備決策參考價值。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預警規則與附件數據清單,確保報告內容一致、可追溯且具備決策參考價值。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預警規則與附件數據清單,確保報告內容一致、可追溯且具備決策參考價值。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預警規則與附件數據清單,確保報告內容一致、可追溯且具備決策參考價值。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預警規則與附件數據清單,確保報告內容一致、可追溯且具備決策參考價值。適用於風險管理、授信審批及合規條線年度復盤,可按產業視角擴展指標口徑、預'
8
+
9
+ const INDUSTRY_RISK_ANNUAL_CHAPTERS = [
10
+ '1. 摘要與本年總結',
11
+ '2. 宏觀經濟與政策背景',
12
+ '3. 行業運行情況與核心指標',
13
+ '4. 風險識別與評級',
14
+ '5. 授信與客戶結構分析',
15
+ '6. 資產質素與逾期/不良變化資產質素與逾期/不良變化資產質素與逾期/不良變化資產質素與逾期/不良變化資產質素與逾期/不良變化資產質素與逾期/不良變化',
16
+ '7. 重點客戶/項目風險',
17
+ '8. 新發生風險與預警',
18
+ '9. 風險管控措施與成效',
19
+ '10. 下年度風險研判與策略',
20
+ '11. 附件與數據',
21
+ ]
22
+
23
+ const QUARTERLY_BRIEF_DESC =
24
+ '面向季度復盤的輕量模板,聚焦行業景氣、核心指標波動、重點風險事件與管控建議,適用於管理層例會與條線月報。'
25
+
26
+ const QUARTERLY_BRIEF_CHAPTERS = [
27
+ '1. 季度摘要',
28
+ '2. 行業景氣與政策動態',
29
+ '3. 核心指標走勢',
30
+ '4. 重點風險事件',
31
+ '5. 管控措施與下季關注',
32
+ ]
33
+
34
+ const FULL_RISK_DESC =
35
+ '完整版風險報告模板,章節可擴展至 20+,覆蓋母行、子公司、區域及產品線多維度風險拆解,支援附錄與數據口徑說明。'
36
+
37
+ const FULL_RISK_CHAPTERS = [
38
+ ...INDUSTRY_RISK_ANNUAL_CHAPTERS,
39
+ '12. 區域風險分佈',
40
+ '13. 產品線風險拆解',
41
+ '14. 關聯方與集中度',
42
+ '15. 市場與流動性風險',
43
+ '16. 操作與合規風險',
44
+ '17. 聲譽與環境風險',
45
+ '18. 壓力測試結果',
46
+ '19. 整改跟進清單',
47
+ '20. 監管關注重點',
48
+ '21. 術語與指標口徑',
49
+ '22. 修訂記錄',
50
+ ]
51
+
52
+ export const TEMPLATE_CATALOG: TemplateItem[] = [
53
+ {
54
+ id: '1',
55
+ name: INDUSTRY_RISK_ANNUAL_NAME,
56
+ description: INDUSTRY_RISK_ANNUAL_DESC,
57
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
58
+ author: '王明輝(風險管理部)',
59
+ approver: '李思琪(合規部)',
60
+ updatedAt: '2025/11/7 15:21',
61
+ unit: '風險管理部',
62
+ industry: INDUSTRY_RISK_ANNUAL_INDUSTRY,
63
+ locked: false,
64
+ locker: '陳大文(授信審批部)',
65
+ approvalStatus: '待審批',
66
+ },
67
+ {
68
+ id: '2',
69
+ name: INDUSTRY_RISK_ANNUAL_NAME,
70
+ description: INDUSTRY_RISK_ANNUAL_DESC,
71
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
72
+ author: '張偉(風險管理部)',
73
+ approver: '劉芳(合規部)',
74
+ updatedAt: '2025/10/28 09:15',
75
+ unit: '授信審批部',
76
+ industry: INDUSTRY_RISK_ANNUAL_INDUSTRY,
77
+ locked: true,
78
+ locker: '陳大文(授信審批部)',
79
+ },
80
+ {
81
+ id: '3',
82
+ name: INDUSTRY_RISK_ANNUAL_NAME,
83
+ description: INDUSTRY_RISK_ANNUAL_DESC,
84
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
85
+ author: '趙敏(公司金融部)',
86
+ approver: '周傑(合規部)',
87
+ updatedAt: '2025/9/12 11:40',
88
+ unit: '風險管理部',
89
+ industry: INDUSTRY_RISK_ANNUAL_INDUSTRY,
90
+ locked: false,
91
+ locker: '陳大文(授信審批部)',
92
+ },
93
+ {
94
+ id: '4',
95
+ name: '季度行業風險簡報',
96
+ description: QUARTERLY_BRIEF_DESC,
97
+ chapters: QUARTERLY_BRIEF_CHAPTERS,
98
+ author: '陳曉東(風險管理部)',
99
+ approver: '何靜(合規部)',
100
+ updatedAt: '2025/8/3 16:05',
101
+ unit: '合規部',
102
+ industry: '零售銀行',
103
+ locked: false,
104
+ locker: '陳大文(授信審批部)',
105
+ },
106
+ {
107
+ id: '5',
108
+ name: '全面風險分析模板',
109
+ description: FULL_RISK_DESC,
110
+ chapters: FULL_RISK_CHAPTERS,
111
+ author: '孫麗(風險管理部)',
112
+ approver: '馬強(合規部)',
113
+ updatedAt: '2025/7/19 14:32',
114
+ unit: '公司金融部',
115
+ industry: '房地產',
116
+ locked: false,
117
+ locker: '陳大文(授信審批部)',
118
+ },
119
+ {
120
+ id: '6',
121
+ name: INDUSTRY_RISK_ANNUAL_NAME,
122
+ description: INDUSTRY_RISK_ANNUAL_DESC,
123
+ chapters: INDUSTRY_RISK_ANNUAL_CHAPTERS,
124
+ author: '黃磊(授信審批部)',
125
+ approver: '吳婷(合規部)',
126
+ updatedAt: '2025/6/25 10:18',
127
+ unit: '授信審批部',
128
+ industry: INDUSTRY_RISK_ANNUAL_INDUSTRY,
129
+ locked: true,
130
+ locker: '陳大文(授信審批部)',
131
+ },
132
+ ]
133
+
134
+ let chapterSeed = 0
135
+
136
+ export function createEmptyChapter(expanded = false) {
137
+ chapterSeed += 1
138
+ return {
139
+ id: `chapter-${Date.now()}-${chapterSeed}`,
140
+ title: '',
141
+ description: '',
142
+ prompt: '',
143
+ expanded,
144
+ }
145
+ }
146
+
147
+ function stripChapterOrder(title: string) {
148
+ return title.replace(/^\d+\.\s*/, '')
149
+ }
150
+
151
+ export function buildEditorFormFromTemplate(template: TemplateItem, copy = false) {
152
+ return {
153
+ language: 'zh',
154
+ title: copy ? `${template.name}(副本)` : template.name,
155
+ description: template.description,
156
+ chapters: template.chapters.map((chapterTitle, index) => ({
157
+ id: `chapter-${template.id}-${index}`,
158
+ title: stripChapterOrder(chapterTitle),
159
+ description: index === 0 ? '本章節概述報告核心結論與年度重點。' : '',
160
+ prompt: index === 0 ? '請根據行業數據與風控政策,生成本章節分析內容。' : '',
161
+ expanded: index === 0,
162
+ })),
163
+ }
164
+ }
165
+
166
+ export function createEmptyEditorForm() {
167
+ return {
168
+ language: 'zh',
169
+ title: '',
170
+ description: '',
171
+ chapters: [createEmptyChapter(true)],
172
+ }
173
+ }
174
+
175
+ export function findTemplateById(id: string) {
176
+ return TEMPLATE_CATALOG.find((item) => item.id === id)
177
+ }
@@ -0,0 +1,154 @@
1
+ import { buildEditorFormFromTemplate, findTemplateById } from './mockData'
2
+ import type { TemplateChapterForm } from './types'
3
+
4
+ export interface TemplateApprovalRecord {
5
+ id: string
6
+ operator: string
7
+ status: string
8
+ completedAt: string
9
+ detail: string
10
+ }
11
+
12
+ export interface TemplateDetailView {
13
+ sourceReport: string
14
+ language: string
15
+ title: string
16
+ description: string
17
+ chapters: TemplateChapterForm[]
18
+ approvalRecords: TemplateApprovalRecord[]
19
+ }
20
+
21
+ export type TemplateDetailMode = 'view' | 'process'
22
+
23
+ export type TemplateProcessAction = 'reject' | 'approve'
24
+
25
+ export interface TemplateProcessSubmitPayload {
26
+ templateId: string
27
+ action: TemplateProcessAction
28
+ title: string
29
+ description: string
30
+ opinion: string
31
+ }
32
+
33
+ export const TEMPLATE_DETAIL_MSG = {
34
+ dialogTitleView: '查看詳情',
35
+ dialogTitleProcess: '處理',
36
+ closeAriaLabel: '關閉',
37
+ sourceReport: '來源報告',
38
+ primaryLanguage: '主要語言',
39
+ templateTitle: '模板標題',
40
+ templateDescription: '模板描述',
41
+ chapterTitleFallback: '章節標題',
42
+ chapterPrompt: '章節Prompt',
43
+ chapterDescription: '章節描述',
44
+ processAction: '處理事項',
45
+ reject: '拒絕',
46
+ approve: '同意',
47
+ opinion: '意見',
48
+ cancel: '取消',
49
+ submit: '提交',
50
+ approvalSectionTitle: '審批明細',
51
+ approvalOperator: '操作人員',
52
+ approvalStatus: '處理狀態',
53
+ approvalCompletedAt: '處理完成時間',
54
+ approvalDetail: '詳情',
55
+ languageEnSuffix: 'English[原模板所選語言]',
56
+ languageZhSuffix: '中文[原模板所選語言]',
57
+ warnTitleRequired: '請輸入模板標題',
58
+ warnDescriptionRequired: '請輸入模板描述',
59
+ warnActionRequired: '請選擇處理事項',
60
+ warnOpinionRequired: '拒絕時請填寫意見',
61
+ confirmRejectMessage: '確定拒絕嗎?',
62
+ confirmApproveMessage: '確定通過嗎?',
63
+ confirmRejectTitle: '拒絕',
64
+ confirmApproveTitle: '通過',
65
+ confirmButton: '確定',
66
+ cancelButton: '取消',
67
+ } as const
68
+
69
+ export const TEMPLATE_DETAIL_LIMITS = {
70
+ titleMax: 100,
71
+ descMax: 3000,
72
+ chapterDescMax: 1000,
73
+ chapterPromptMax: 1300,
74
+ chapterPromptRows: 8,
75
+ opinionMax: 250,
76
+ } as const
77
+
78
+ const TEMPLATE_SOURCE_REPORTS: Record<string, string> = {
79
+ '1': '20XX年房地產調研報告 V3',
80
+ }
81
+
82
+ const TEMPLATE_APPROVAL_RECORDS: Record<string, TemplateApprovalRecord[]> = {
83
+ '1': [
84
+ {
85
+ id: 'apr-1',
86
+ operator: '陳大文 (8808808)',
87
+ status: '待審批',
88
+ completedAt: '2025-11-07 15:21:11',
89
+ detail: '-',
90
+ },
91
+ {
92
+ id: 'apr-2',
93
+ operator: '陳大文 (8808808)',
94
+ status: '已拒絕',
95
+ completedAt: '2025-11-07 15:21:11',
96
+ detail: '模板描述有錯別字',
97
+ },
98
+ ],
99
+ }
100
+
101
+ function enrichChapterForms(chapters: TemplateChapterForm[]) {
102
+ return chapters.map((chapter, index) => {
103
+ if (index === 0) {
104
+ return {
105
+ ...chapter,
106
+ title: chapter.title || '摘要與本年總結',
107
+ prompt:
108
+ chapter.prompt ||
109
+ '請根據行業數據與風控政策,生成本章節分析內容。[定稿時所使用的章節Prompt][不可修改]',
110
+ description:
111
+ chapter.description ||
112
+ '本章節概述報告核心結論與年度重點。[新增章節時所錄入的章節描述][不可修改]',
113
+ }
114
+ }
115
+ if (index === 1) {
116
+ return {
117
+ ...chapter,
118
+ title: chapter.title || '宏觀經濟與政策背景',
119
+ prompt: '請結合宏觀政策與行業背景撰寫本章節。[定稿時所使用的章節Prompt][不可修改]',
120
+ description: '說明宏觀經濟與政策變化對行業的影響。[新增章節時所錄入的章節描述][不可修改]',
121
+ }
122
+ }
123
+ return chapter
124
+ })
125
+ }
126
+
127
+ export function getTemplateDetail(id: string): TemplateDetailView | null {
128
+ const template = findTemplateById(id)
129
+ if (!template) return null
130
+
131
+ const form = buildEditorFormFromTemplate(template)
132
+
133
+ return {
134
+ sourceReport: TEMPLATE_SOURCE_REPORTS[id] ?? '—',
135
+ language: form.language,
136
+ title: form.title,
137
+ description: form.description,
138
+ chapters: enrichChapterForms(form.chapters),
139
+ approvalRecords: TEMPLATE_APPROVAL_RECORDS[id] ?? [],
140
+ }
141
+ }
142
+
143
+ export function getDetailLanguageDisplay(language: string) {
144
+ return language === 'en'
145
+ ? TEMPLATE_DETAIL_MSG.languageEnSuffix
146
+ : TEMPLATE_DETAIL_MSG.languageZhSuffix
147
+ }
148
+
149
+ export function getDetailDisplayChapters(detail: TemplateDetailView | null) {
150
+ if (!detail) return []
151
+ return detail.chapters.filter(
152
+ (chapter, index) => index < 2 || Boolean(chapter.prompt.trim() || chapter.description.trim())
153
+ )
154
+ }
@@ -0,0 +1,345 @@
1
+ <template>
2
+ <div class="template-editor-page">
3
+ <main class="template-editor-page__body">
4
+ <section class="template-editor-hero">
5
+ <h2 class="template-editor-hero__title">{{ pageTitle }}</h2>
6
+ <p class="template-editor-hero__desc gen-ai-page-hero-desc">
7
+ 配置報告生成的基礎參數,確定分析視角
8
+ </p>
9
+ <p v-if="lockedNoticeText" class="template-editor-hero__locked-notice">
10
+ {{ lockedNoticeText }}
11
+ </p>
12
+ </section>
13
+
14
+ <TemplateEditorFormPanel ref="formPanelRef" v-model="form" :editor-mode="editorMode" />
15
+
16
+ <div class="template-editor-form__footer">
17
+ <button
18
+ v-if="isCreateMode"
19
+ type="button"
20
+ class="template-editor-save-btn app-btn app-btn--wide gen-ai-action-btn"
21
+ @click="handleSave('saveAsNew')"
22
+ >
23
+ 保存模板
24
+ </button>
25
+ <template v-else>
26
+ <button
27
+ v-if="showOverwriteSave"
28
+ type="button"
29
+ class="template-editor-save-btn app-btn app-btn--wide gen-ai-action-btn"
30
+ @click="handleSave('overwrite')"
31
+ >
32
+ 保存模板(覆蓋)
33
+ </button>
34
+ <button
35
+ type="button"
36
+ class="template-editor-save-btn app-btn app-btn--wide gen-ai-action-btn"
37
+ @click="handleSave('saveAsNew')"
38
+ >
39
+ 另存為新模板
40
+ </button>
41
+ </template>
42
+ </div>
43
+ </main>
44
+ </div>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import '../common/assets/fonts.css'
49
+ import { ElMessage } from 'element-plus'
50
+ import { computed, ref, watch } from 'vue'
51
+ import { useRoute } from 'vue-router'
52
+ import TemplateEditorFormPanel from './TemplateEditorFormPanel.vue'
53
+ import { buildEditorFormFromTemplate, createEmptyEditorForm, findTemplateById } from './mockData'
54
+ import type {
55
+ TemplateChapterForm,
56
+ TemplateEditorForm,
57
+ TemplateEditorMode,
58
+ TemplateItem,
59
+ } from './types'
60
+
61
+ const route = useRoute()
62
+
63
+ const editorMode = computed(
64
+ () => (route.query.mode as TemplateEditorMode) || (route.query.id ? 'edit' : 'create')
65
+ )
66
+ const templateId = computed(() => String(route.query.id || ''))
67
+
68
+ const pageTitle = computed(() => {
69
+ if (editorMode.value === 'create') return '創建報告模板'
70
+ if (editorMode.value === 'copy') return '創建報告模板'
71
+ return '編輯報告模板'
72
+ })
73
+
74
+ const form = ref<TemplateEditorForm>(createEmptyEditorForm())
75
+ const sourceTemplate = ref<TemplateItem | null>(null)
76
+ const formPanelRef = ref<InstanceType<typeof TemplateEditorFormPanel> | null>(null)
77
+
78
+ const isCreateMode = computed(() => editorMode.value === 'create')
79
+
80
+ const showOverwriteSave = computed(
81
+ () => editorMode.value === 'edit' && !sourceTemplate.value?.locked
82
+ )
83
+
84
+ const lockedNoticeText = computed(() => {
85
+ const template = sourceTemplate.value
86
+ if (!template?.locked) return ''
87
+ return '(此模板已被鎖定,如需修改,僅支援另存為新模板)'
88
+ })
89
+
90
+ function initForm() {
91
+ formPanelRef.value?.resetEditingState()
92
+
93
+ if (editorMode.value === 'create') {
94
+ form.value = createEmptyEditorForm()
95
+ sourceTemplate.value = null
96
+ return
97
+ }
98
+
99
+ const template = findTemplateById(templateId.value)
100
+ if (!template) {
101
+ form.value = createEmptyEditorForm()
102
+ sourceTemplate.value = null
103
+ ElMessage.warning('未找到模板,已進入新建模式')
104
+ return
105
+ }
106
+
107
+ sourceTemplate.value = template
108
+ form.value = buildEditorFormFromTemplate(template, editorMode.value === 'copy')
109
+ }
110
+
111
+ watch([editorMode, templateId], initForm, { immediate: true })
112
+
113
+ function validateChapter(chapter: TemplateChapterForm) {
114
+ return Boolean(chapter.prompt.trim())
115
+ }
116
+
117
+ function validateForm() {
118
+ if (!form.value.language) {
119
+ ElMessage.warning('請選擇主要語言')
120
+ return false
121
+ }
122
+ if (!form.value.title.trim()) {
123
+ ElMessage.warning('請輸入模板標題')
124
+ return false
125
+ }
126
+ if (!form.value.description.trim()) {
127
+ ElMessage.warning('請輸入模板描述')
128
+ return false
129
+ }
130
+ if (form.value.chapters.length === 0) {
131
+ ElMessage.warning('請至少新增一個章節')
132
+ return false
133
+ }
134
+ if (!form.value.chapters.every(validateChapter)) {
135
+ ElMessage.warning('請完善所有章節的 Prompt')
136
+ return false
137
+ }
138
+ return true
139
+ }
140
+
141
+ type TemplateSaveType = 'overwrite' | 'saveAsNew'
142
+
143
+ function handleSave(saveType: TemplateSaveType) {
144
+ if (!validateForm()) return
145
+
146
+ if (saveType === 'overwrite') {
147
+ ElMessage.success('模板已覆蓋保存')
148
+ } else {
149
+ ElMessage.success(
150
+ editorMode.value === 'create' || editorMode.value === 'copy' ? '模板已創建' : '已另存為新模板'
151
+ )
152
+ }
153
+ formPanelRef.value?.resetEditingState()
154
+ }
155
+ </script>
156
+
157
+ <style scoped>
158
+ .template-editor-page {
159
+ --gen-ai-design-w: 1920;
160
+ --gen-ai-fs-base: clamp(13px, calc(14 * 100vw / 1920), 14px);
161
+ --gen-ai-fs-hero: clamp(26px, calc(32 * 100vw / 1920), 32px);
162
+ --gen-ai-lh-tight: 1.4;
163
+ --gen-ai-fw-bold: 700;
164
+ --app-btn-color: #fff;
165
+ --app-btn-color-hover: #c53355;
166
+ --app-btn-bg: #c53355;
167
+ --app-btn-bg-hover: #fff;
168
+ --app-btn-border: #c53355;
169
+ --app-btn-border-hover: #c53355;
170
+ --app-btn-radius: 5px;
171
+ --app-btn-font-size: clamp(14px, calc(16 * 100vw / 1920), 16px);
172
+ --app-btn-font-weight: 600;
173
+ --app-btn-padding-y: 8px;
174
+ --app-btn-padding-x: 15px;
175
+ --app-btn-min-width: 120px;
176
+ --report-accent: #c53355;
177
+ --report-accent-soft: #fdf0f2;
178
+ font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
179
+ font-size: var(--gen-ai-fs-base);
180
+ line-height: 1.6;
181
+ color: #2d3139;
182
+ min-height: 100vh;
183
+ background: #fff;
184
+ }
185
+
186
+ .template-editor-page :deep(.el-button) {
187
+ font-size: 18px;
188
+ font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
189
+ }
190
+
191
+ .gen-ai-page-hero-desc {
192
+ font-size: 16px;
193
+ font-weight: 600;
194
+ line-height: 1.7;
195
+ color: #666;
196
+ }
197
+
198
+ .app-btn {
199
+ min-width: var(--app-btn-min-width);
200
+ padding: var(--app-btn-padding-y) var(--app-btn-padding-x);
201
+ border: 1px solid var(--app-btn-border);
202
+ border-radius: var(--app-btn-radius);
203
+ background: var(--app-btn-bg);
204
+ color: var(--app-btn-color);
205
+ font-size: var(--app-btn-font-size);
206
+ font-weight: var(--app-btn-font-weight);
207
+ line-height: var(--gen-ai-lh-tight);
208
+ font-family: inherit;
209
+ cursor: pointer;
210
+ transition: all 0.2s ease;
211
+ }
212
+
213
+ .app-btn:hover,
214
+ .app-btn.is-active,
215
+ .app-btn--active {
216
+ background: var(--app-btn-bg-hover);
217
+ border-color: var(--app-btn-border-hover);
218
+ color: var(--app-btn-color-hover);
219
+ }
220
+
221
+ .app-btn--wide {
222
+ min-width: 148px;
223
+ }
224
+
225
+ .gen-ai-action-btn,
226
+ .template-center-toolbar-btn {
227
+ font-size: 15px;
228
+ font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
229
+ font-weight: 400;
230
+ }
231
+
232
+ .template-center-toolbar-btn {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ justify-content: center;
236
+ gap: 6px;
237
+ box-sizing: border-box;
238
+ height: 32px;
239
+ min-width: 120px;
240
+ padding: 0 14px;
241
+ border-radius: 4px;
242
+ line-height: 1;
243
+ white-space: nowrap;
244
+ cursor: pointer;
245
+ transition: all 0.2s ease;
246
+ }
247
+
248
+ .template-center-toolbar-btn:disabled {
249
+ opacity: 0.45;
250
+ cursor: not-allowed;
251
+ }
252
+
253
+ .template-center-toolbar-btn--primary {
254
+ border: 1px solid var(--app-btn-border);
255
+ background: var(--app-btn-bg);
256
+ color: var(--app-btn-color);
257
+ }
258
+
259
+ .template-center-toolbar-btn--primary:hover:not(:disabled) {
260
+ background: var(--app-btn-bg-hover);
261
+ border-color: var(--app-btn-border-hover);
262
+ color: var(--app-btn-color-hover);
263
+ }
264
+
265
+ .template-center-toolbar-btn--outline {
266
+ border: 1px solid var(--report-accent);
267
+ color: var(--report-accent);
268
+ background: #fff;
269
+ }
270
+
271
+ .template-center-toolbar-btn--outline:hover:not(:disabled) {
272
+ border-color: var(--report-accent);
273
+ color: var(--report-accent);
274
+ background: var(--report-accent-soft);
275
+ }
276
+
277
+ .template-center-toolbar-btn--delete {
278
+ border-color: var(--report-accent);
279
+ color: var(--report-accent);
280
+ background: #fff;
281
+ }
282
+
283
+ .template-center-toolbar-btn--delete:hover:not(:disabled) {
284
+ border-color: var(--report-accent);
285
+ color: var(--report-accent);
286
+ background: var(--report-accent-soft);
287
+ }
288
+
289
+ .template-center-toolbar-btn--ghost {
290
+ border: 1px solid #dcdfe6;
291
+ background: #fff;
292
+ color: #606266;
293
+ }
294
+
295
+ .template-center-toolbar-btn--ghost:hover:not(:disabled) {
296
+ border-color: var(--report-accent);
297
+ color: var(--report-accent);
298
+ background: var(--report-accent-soft);
299
+ }
300
+
301
+ .template-editor-page__body {
302
+ width: 80%;
303
+ max-width: 1920px;
304
+ margin: 0 auto;
305
+ padding: 32px 24px 48px;
306
+ box-sizing: border-box;
307
+ }
308
+
309
+ .template-editor-hero {
310
+ text-align: center;
311
+ margin-bottom: 28px;
312
+ }
313
+
314
+ .template-editor-hero__title {
315
+ margin: 0 0 12px;
316
+ font-size: var(--gen-ai-fs-hero);
317
+ font-weight: var(--gen-ai-fw-bold);
318
+ color: #1a1a1a;
319
+ }
320
+
321
+ .template-editor-hero__desc {
322
+ margin: 0;
323
+ }
324
+
325
+ .template-editor-hero__locked-notice {
326
+ margin: 10px 0 0;
327
+ font-size: 16px;
328
+ font-weight: 500;
329
+ line-height: 1.7;
330
+ color: #f56c6c;
331
+ }
332
+
333
+ .template-editor-form__footer {
334
+ display: flex;
335
+ justify-content: center;
336
+ align-items: center;
337
+ flex-wrap: wrap;
338
+ gap: 16px;
339
+ margin-top: 8px;
340
+ }
341
+
342
+ .template-editor-save-btn {
343
+ min-width: 160px;
344
+ }
345
+ </style>