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,886 @@
1
+ <template>
2
+ <div class="admin-center-page">
3
+ <header class="admin-center-hero">
4
+ <h1 class="admin-center-hero__title">AI行業分析報告助手</h1>
5
+ <p class="admin-center-hero__subtitle">選擇報告生成參數,確定分析視角</p>
6
+ </header>
7
+
8
+ <div class="admin-center-board">
9
+ <!-- 左:參數 -->
10
+ <section class="admin-center-panel admin-center-panel--params">
11
+ <h2 class="admin-center-panel__title">基本參數</h2>
12
+
13
+ <div class="admin-form">
14
+ <div class="admin-form__row">
15
+ <label class="admin-form__label">行業</label>
16
+ <div class="admin-form__control">
17
+ <input
18
+ v-model="form.industry"
19
+ type="text"
20
+ class="admin-input admin-input--required"
21
+ />
22
+ </div>
23
+ </div>
24
+ <div class="admin-form__row">
25
+ <label class="admin-form__label">調研主要出發點</label>
26
+ <div class="admin-form__control">
27
+ <input
28
+ v-model="form.researchFocus"
29
+ type="text"
30
+ class="admin-input admin-input--required"
31
+ />
32
+ </div>
33
+ </div>
34
+ <div class="admin-form__row">
35
+ <label class="admin-form__label">年份</label>
36
+ <div class="admin-form__control">
37
+ <input v-model="form.year" type="text" class="admin-input admin-input--required" />
38
+ </div>
39
+ </div>
40
+ <div class="admin-form__row">
41
+ <label class="admin-form__label">報告名稱</label>
42
+ <div class="admin-form__control">
43
+ <input
44
+ v-model="form.reportName"
45
+ type="text"
46
+ class="admin-input admin-input--required"
47
+ />
48
+ </div>
49
+ </div>
50
+ <div class="admin-form__row">
51
+ <label class="admin-form__label">報告語言</label>
52
+ <div class="admin-form__control admin-form__control--radio">
53
+ <label v-for="lang in languageOptions" :key="lang.value" class="admin-radio">
54
+ <input v-model="form.language" type="radio" :value="lang.value" />
55
+ <span>{{ lang.label }}</span>
56
+ </label>
57
+ </div>
58
+ </div>
59
+ <div class="admin-form__row">
60
+ <label class="admin-form__label">每章節字數</label>
61
+ <div class="admin-form__control">
62
+ <input
63
+ v-model="form.wordsPerChapter"
64
+ type="number"
65
+ min="0"
66
+ class="admin-input admin-input--required"
67
+ />
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ <button type="button" class="admin-advanced-toggle" @click="advancedOpen = !advancedOpen">
73
+ <span
74
+ class="admin-advanced-toggle__arrow"
75
+ :class="{ 'is-open': advancedOpen }"
76
+ aria-hidden="true"
77
+ />
78
+ <span>進階參數</span>
79
+ </button>
80
+
81
+ <div v-show="advancedOpen" class="admin-form admin-form--advanced">
82
+ <div class="admin-form__row">
83
+ <label class="admin-form__label">模型</label>
84
+ <div class="admin-form__control">
85
+ <input v-model="form.model" type="text" class="admin-input admin-input--required" />
86
+ </div>
87
+ </div>
88
+ <div class="admin-form__row">
89
+ <label class="admin-form__label">搜尋模式</label>
90
+ <div class="admin-form__control">
91
+ <select v-model="form.searchMode" class="admin-input admin-input--select">
92
+ <option v-for="mode in searchModeOptions" :key="mode" :value="mode">
93
+ {{ mode }}
94
+ </option>
95
+ </select>
96
+ </div>
97
+ </div>
98
+ <div class="admin-form__row admin-form__row--slider">
99
+ <label class="admin-form__label">權重</label>
100
+ <div class="admin-form__control admin-form__control--slider">
101
+ <el-slider
102
+ v-model="form.weight"
103
+ :min="0"
104
+ :max="1"
105
+ :step="0.1"
106
+ :show-tooltip="false"
107
+ />
108
+ <input
109
+ v-model.number="form.weight"
110
+ type="number"
111
+ min="0"
112
+ max="1"
113
+ step="0.1"
114
+ class="admin-slider-num"
115
+ />
116
+ </div>
117
+ </div>
118
+ <div class="admin-form__row">
119
+ <label class="admin-form__label">Rerank</label>
120
+ <div class="admin-form__control">
121
+ <input v-model="form.rerank" type="text" class="admin-input" />
122
+ </div>
123
+ </div>
124
+ <div class="admin-form__row admin-form__row--slider">
125
+ <label class="admin-form__label">Rerank top-N</label>
126
+ <div class="admin-form__control admin-form__control--slider">
127
+ <el-slider
128
+ v-model="form.rerankTopN"
129
+ :min="1"
130
+ :max="20"
131
+ :step="1"
132
+ :show-tooltip="false"
133
+ />
134
+ <input
135
+ v-model.number="form.rerankTopN"
136
+ type="number"
137
+ min="1"
138
+ max="20"
139
+ class="admin-slider-num"
140
+ />
141
+ </div>
142
+ </div>
143
+ <div class="admin-form__row admin-form__row--slider">
144
+ <label class="admin-form__label">Rerank threshold</label>
145
+ <div class="admin-form__control admin-form__control--slider">
146
+ <el-slider
147
+ v-model="form.rerankThreshold"
148
+ :min="0"
149
+ :max="1"
150
+ :step="0.1"
151
+ :show-tooltip="false"
152
+ />
153
+ <input
154
+ v-model.number="form.rerankThreshold"
155
+ type="number"
156
+ min="0"
157
+ max="1"
158
+ step="0.1"
159
+ class="admin-slider-num"
160
+ />
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <p v-if="form.searchMode === 'Vector Search'" class="admin-form__hint">
166
+ 搜尋模式為「Vector Search」時,權重與 Rerank 相關參數將被忽略。
167
+ </p>
168
+ </section>
169
+
170
+ <!-- 右:資訊源 -->
171
+ <section class="admin-center-panel admin-center-panel--sources">
172
+ <h2 class="admin-center-panel__title">資訊源檔案</h2>
173
+
174
+ <div
175
+ class="admin-upload-zone"
176
+ role="button"
177
+ tabindex="0"
178
+ @click="triggerFilePick"
179
+ @keydown.enter="triggerFilePick"
180
+ >
181
+ <span class="admin-upload-zone__plus">+</span>
182
+ <span class="admin-upload-zone__label">添加資訊源</span>
183
+ <p class="admin-upload-zone__hint">
184
+ 可一次選擇多個檔案,每個檔案大小不超過 100MB;支援
185
+ txt、doc、docx、pdf、xls、xlsx、json、csv、pptx、PNG、JPG、JPEG、GIF
186
+ </p>
187
+ <input
188
+ ref="fileInputRef"
189
+ type="file"
190
+ class="admin-upload-zone__input"
191
+ multiple
192
+ accept=".txt,.doc,.docx,.pdf,.xls,.xlsx,.json,.csv,.pptx,.png,.jpg,.jpeg,.gif"
193
+ @change="onFilesPicked"
194
+ />
195
+ </div>
196
+
197
+ <div v-if="pendingFiles.length" class="admin-pending">
198
+ <div v-for="file in pendingFiles" :key="file.id" class="admin-pending__row">
199
+ <span class="admin-pending__name">{{ file.name }}</span>
200
+ <span
201
+ class="admin-pending__size"
202
+ :class="{ 'is-over-limit': file.sizeKb > FILE_SIZE_LIMIT_KB }"
203
+ >
204
+ {{ file.sizeKb }}KB
205
+ </span>
206
+ <button
207
+ type="button"
208
+ class="admin-pending__remove"
209
+ aria-label="刪除"
210
+ @click="removePending(file.id)"
211
+ >
212
+ ×
213
+ </button>
214
+ </div>
215
+ <div class="admin-pending__actions">
216
+ <button type="button" class="admin-btn admin-btn--primary" @click="submitPending">
217
+ 提交
218
+ </button>
219
+ <button type="button" class="admin-btn admin-btn--outline" @click="clearPending">
220
+ 清空
221
+ </button>
222
+ </div>
223
+ </div>
224
+
225
+ <div class="admin-source-toolbar">
226
+ <label class="admin-source-toolbar__all">
227
+ <input v-model="allSourcesSelected" type="checkbox" @change="toggleAllSources" />
228
+ <span>全選</span>
229
+ </label>
230
+ <div class="admin-source-toolbar__search">
231
+ <span class="admin-source-toolbar__search-label">Search:</span>
232
+ <input v-model="sourceSearch" type="text" class="admin-source-toolbar__search-input" />
233
+ <button
234
+ type="button"
235
+ class="admin-btn admin-btn--outline admin-btn--sm"
236
+ @click="refreshSources"
237
+ >
238
+ Refresh
239
+ </button>
240
+ </div>
241
+ </div>
242
+
243
+ <div class="admin-source-table-wrap">
244
+ <table class="admin-source-table">
245
+ <thead>
246
+ <tr>
247
+ <th class="admin-source-table__check" />
248
+ <th>檔案名稱</th>
249
+ <th class="admin-source-table__action">下載</th>
250
+ <th class="admin-source-table__action">刪除</th>
251
+ </tr>
252
+ </thead>
253
+ <tbody>
254
+ <tr v-for="file in filteredSources" :key="file.id">
255
+ <td class="admin-source-table__check">
256
+ <input v-model="file.selected" type="checkbox" />
257
+ </td>
258
+ <td class="admin-source-table__name">{{ file.name }}</td>
259
+ <td class="admin-source-table__action">
260
+ <button
261
+ type="button"
262
+ class="admin-icon-btn"
263
+ aria-label="下載"
264
+ @click="downloadSource(file)"
265
+ >
266
+ <el-icon><Download /></el-icon>
267
+ </button>
268
+ </td>
269
+ <td class="admin-source-table__action">
270
+ <button
271
+ type="button"
272
+ class="admin-icon-btn"
273
+ aria-label="刪除"
274
+ @click="deleteSource(file.id)"
275
+ >
276
+ <el-icon><Close /></el-icon>
277
+ </button>
278
+ </td>
279
+ </tr>
280
+ <tr v-if="!filteredSources.length">
281
+ <td colspan="4" class="admin-source-table__empty">暫無匹配檔案</td>
282
+ </tr>
283
+ </tbody>
284
+ </table>
285
+ </div>
286
+ </section>
287
+ </div>
288
+
289
+ <footer class="admin-center-footer">
290
+ <button type="button" class="app-btn app-btn--wide" @click="selectTemplate">選擇模板</button>
291
+ </footer>
292
+ </div>
293
+ </template>
294
+
295
+ <script setup lang="ts">
296
+ import { Close, Download } from '@element-plus/icons-vue'
297
+ import { ElIcon, ElSlider } from 'element-plus'
298
+ import { computed, reactive, ref } from 'vue'
299
+ import { useRouter } from 'vue-router'
300
+ import { openTemplateCenter } from '@/utils/gen-ai-navigation'
301
+
302
+ const FILE_SIZE_LIMIT_KB = 100 * 1024
303
+
304
+ const router = useRouter()
305
+ const fileInputRef = ref<HTMLInputElement | null>(null)
306
+ const advancedOpen = ref(true)
307
+ const sourceSearch = ref('')
308
+ const allSourcesSelected = ref(false)
309
+
310
+ const languageOptions = [
311
+ { value: 'zh-Hant', label: '繁體中文' },
312
+ { value: 'zh-Hans', label: '簡體中文' },
313
+ { value: 'en', label: 'English' },
314
+ ] as const
315
+
316
+ const searchModeOptions = ['Hybrid Search', 'Vector Search', 'Keyword Search'] as const
317
+
318
+ const form = reactive({
319
+ industry: '',
320
+ researchFocus: '',
321
+ year: '',
322
+ reportName: '',
323
+ language: 'zh-Hant',
324
+ wordsPerChapter: '',
325
+ model: '',
326
+ searchMode: 'Hybrid Search',
327
+ weight: 0.5,
328
+ rerank: '',
329
+ rerankTopN: 5,
330
+ rerankThreshold: 0.5,
331
+ })
332
+
333
+ interface PendingFile {
334
+ id: string
335
+ name: string
336
+ sizeKb: number
337
+ }
338
+
339
+ interface SourceFile {
340
+ id: string
341
+ name: string
342
+ selected: boolean
343
+ }
344
+
345
+ const pendingFiles = ref<PendingFile[]>([
346
+ { id: 'p1', name: 'file_4.docx', sizeKb: 107782 },
347
+ { id: 'p2', name: 'file_3.xlsx', sizeKb: 530 },
348
+ { id: 'p3', name: 'file_1.pdf', sizeKb: 325 },
349
+ ])
350
+
351
+ const sourceFiles = ref<SourceFile[]>([
352
+ { id: 's1', name: '香港物業市場回顧及前瞻2024.pdf', selected: false },
353
+ { id: 's2', name: '2024年香港住宅物業價格與租金走勢分析.pdf', selected: false },
354
+ { id: 's3', name: '香港商業地產市場年度觀察報告2024.pdf', selected: false },
355
+ { id: 's4', name: '香港房地產市場2024年回顧與展望.docx', selected: false },
356
+ { id: 's5', name: '香港住宅租金指數2024年數據匯總.xlsx', selected: false },
357
+ ])
358
+
359
+ const filteredSources = computed(() => {
360
+ const q = sourceSearch.value.trim().toLowerCase()
361
+ if (!q) return sourceFiles.value
362
+ return sourceFiles.value.filter((f) => f.name.toLowerCase().includes(q))
363
+ })
364
+
365
+ function triggerFilePick() {
366
+ fileInputRef.value?.click()
367
+ }
368
+
369
+ function onFilesPicked(event: Event) {
370
+ const input = event.target as HTMLInputElement
371
+ const files = input.files
372
+ if (!files?.length) return
373
+
374
+ for (const file of files) {
375
+ pendingFiles.value.push({
376
+ id: `pending-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
377
+ name: file.name,
378
+ sizeKb: Math.ceil(file.size / 1024),
379
+ })
380
+ }
381
+ input.value = ''
382
+ }
383
+
384
+ function removePending(id: string) {
385
+ pendingFiles.value = pendingFiles.value.filter((f) => f.id !== id)
386
+ }
387
+
388
+ function clearPending() {
389
+ pendingFiles.value = []
390
+ }
391
+
392
+ function submitPending() {
393
+ const valid = pendingFiles.value.filter((f) => f.sizeKb <= FILE_SIZE_LIMIT_KB)
394
+ valid.forEach((f) => {
395
+ sourceFiles.value.push({ id: f.id, name: f.name, selected: false })
396
+ })
397
+ pendingFiles.value = pendingFiles.value.filter((f) => f.sizeKb > FILE_SIZE_LIMIT_KB)
398
+ }
399
+
400
+ function toggleAllSources() {
401
+ sourceFiles.value.forEach((f) => {
402
+ f.selected = allSourcesSelected.value
403
+ })
404
+ }
405
+
406
+ function refreshSources() {
407
+ sourceSearch.value = ''
408
+ }
409
+
410
+ function downloadSource(file: SourceFile) {
411
+ console.log('download', file.name)
412
+ }
413
+
414
+ function deleteSource(id: string) {
415
+ sourceFiles.value = sourceFiles.value.filter((f) => f.id !== id)
416
+ }
417
+
418
+ function selectTemplate() {
419
+ openTemplateCenter(router)
420
+ }
421
+ </script>
422
+
423
+ <style scoped>
424
+ .admin-center-page {
425
+ display: flex;
426
+ flex-direction: column;
427
+ gap: 20px;
428
+ min-height: 100%;
429
+ color: #1a1a1a;
430
+ }
431
+
432
+ .admin-center-hero {
433
+ text-align: center;
434
+ padding: 8px 0 4px;
435
+ }
436
+
437
+ .admin-center-hero__title {
438
+ margin: 0 0 8px;
439
+ font-size: 22px;
440
+ font-weight: 700;
441
+ color: #1a1a1a;
442
+ letter-spacing: 0.02em;
443
+ }
444
+
445
+ .admin-center-hero__subtitle {
446
+ margin: 0;
447
+ font-size: 14px;
448
+ color: #4e5969;
449
+ }
450
+
451
+ .admin-center-board {
452
+ display: grid;
453
+ grid-template-columns: 1fr 1fr;
454
+ gap: 0;
455
+ border: 1px solid #1a1a1a;
456
+ min-height: 520px;
457
+ }
458
+
459
+ .admin-center-panel {
460
+ padding: 20px 24px 24px;
461
+ min-width: 0;
462
+ }
463
+
464
+ .admin-center-panel--params {
465
+ border-right: 1px solid #1a1a1a;
466
+ }
467
+
468
+ .admin-center-panel__title {
469
+ margin: 0 0 16px;
470
+ font-size: 15px;
471
+ font-weight: 700;
472
+ color: #1a1a1a;
473
+ }
474
+
475
+ /* —— 表單 —— */
476
+ .admin-form {
477
+ display: flex;
478
+ flex-direction: column;
479
+ gap: 10px;
480
+ }
481
+
482
+ .admin-form--advanced {
483
+ margin-top: 4px;
484
+ }
485
+
486
+ .admin-form__row {
487
+ display: grid;
488
+ grid-template-columns: 120px 1fr;
489
+ align-items: center;
490
+ gap: 12px;
491
+ }
492
+
493
+ .admin-form__row--slider {
494
+ align-items: center;
495
+ }
496
+
497
+ .admin-form__label {
498
+ font-size: 13px;
499
+ color: #1a1a1a;
500
+ white-space: nowrap;
501
+ }
502
+
503
+ .admin-form__control--radio {
504
+ display: flex;
505
+ flex-wrap: wrap;
506
+ gap: 16px;
507
+ }
508
+
509
+ .admin-form__control--slider {
510
+ display: flex;
511
+ align-items: center;
512
+ gap: 12px;
513
+ }
514
+
515
+ .admin-form__control--slider :deep(.el-slider) {
516
+ flex: 1;
517
+ min-width: 0;
518
+ --el-slider-main-bg-color: #c53355;
519
+ --el-slider-runway-bg-color: #e8eaec;
520
+ --el-slider-button-size: 14px;
521
+ }
522
+
523
+ .admin-radio {
524
+ display: inline-flex;
525
+ align-items: center;
526
+ gap: 6px;
527
+ font-size: 13px;
528
+ cursor: pointer;
529
+ }
530
+
531
+ .admin-radio input {
532
+ accent-color: #c53355;
533
+ }
534
+
535
+ .admin-input {
536
+ width: 100%;
537
+ height: 32px;
538
+ padding: 4px 10px;
539
+ border: 1px solid #c9cdd4;
540
+ border-radius: 2px;
541
+ font-size: 13px;
542
+ color: #1a1a1a;
543
+ background: #fff;
544
+ box-sizing: border-box;
545
+ outline: none;
546
+ }
547
+
548
+ .admin-input:focus {
549
+ border-color: #c53355;
550
+ }
551
+
552
+ .admin-input--select {
553
+ appearance: auto;
554
+ cursor: pointer;
555
+ }
556
+
557
+ .admin-input--required {
558
+ position: relative;
559
+ background-image: linear-gradient(225deg, #c53355 8px, transparent 8px);
560
+ background-repeat: no-repeat;
561
+ background-position: top right;
562
+ }
563
+
564
+ .admin-slider-num {
565
+ width: 52px;
566
+ height: 28px;
567
+ padding: 2px 6px;
568
+ border: 1px solid #c9cdd4;
569
+ border-radius: 2px;
570
+ font-size: 13px;
571
+ text-align: center;
572
+ flex-shrink: 0;
573
+ }
574
+
575
+ .admin-advanced-toggle {
576
+ display: inline-flex;
577
+ align-items: center;
578
+ gap: 6px;
579
+ margin: 16px 0 8px;
580
+ padding: 0;
581
+ border: 0;
582
+ background: none;
583
+ font-size: 14px;
584
+ font-weight: 700;
585
+ color: #1a1a1a;
586
+ cursor: pointer;
587
+ }
588
+
589
+ .admin-advanced-toggle__arrow {
590
+ display: inline-block;
591
+ width: 0;
592
+ height: 0;
593
+ border-left: 5px solid transparent;
594
+ border-right: 5px solid transparent;
595
+ border-top: 7px solid #c53355;
596
+ border-bottom: none;
597
+ transition: transform 0.2s ease;
598
+ }
599
+
600
+ .admin-advanced-toggle__arrow.is-open {
601
+ transform: rotate(180deg);
602
+ }
603
+
604
+ .admin-form__hint {
605
+ margin: 12px 0 0;
606
+ font-size: 12px;
607
+ color: #86909c;
608
+ }
609
+
610
+ /* —— 上傳區 —— */
611
+ .admin-upload-zone {
612
+ position: relative;
613
+ display: flex;
614
+ flex-direction: column;
615
+ align-items: center;
616
+ justify-content: center;
617
+ gap: 6px;
618
+ min-height: 120px;
619
+ padding: 16px;
620
+ border: 1px dashed #c9cdd4;
621
+ border-radius: 2px;
622
+ background: #fafafa;
623
+ cursor: pointer;
624
+ text-align: center;
625
+ transition:
626
+ background 0.2s ease,
627
+ border-color 0.2s ease;
628
+ }
629
+
630
+ .admin-upload-zone:hover {
631
+ background: #f5f5f5;
632
+ border-color: #c53355;
633
+ }
634
+
635
+ .admin-upload-zone__plus {
636
+ font-size: 28px;
637
+ line-height: 1;
638
+ color: #86909c;
639
+ font-weight: 300;
640
+ }
641
+
642
+ .admin-upload-zone__label {
643
+ font-size: 13px;
644
+ color: #4e5969;
645
+ }
646
+
647
+ .admin-upload-zone__hint {
648
+ margin: 4px 0 0;
649
+ max-width: 100%;
650
+ font-size: 11px;
651
+ line-height: 1.5;
652
+ color: #86909c;
653
+ }
654
+
655
+ .admin-upload-zone__input {
656
+ position: absolute;
657
+ width: 0;
658
+ height: 0;
659
+ opacity: 0;
660
+ pointer-events: none;
661
+ }
662
+
663
+ /* —— 待提交列表 —— */
664
+ .admin-pending {
665
+ margin-top: 12px;
666
+ border: 1px solid #e8eaec;
667
+ }
668
+
669
+ .admin-pending__row {
670
+ display: grid;
671
+ grid-template-columns: 1fr auto 28px;
672
+ align-items: center;
673
+ gap: 8px;
674
+ padding: 6px 10px;
675
+ border-bottom: 1px solid #f0f2f5;
676
+ font-size: 13px;
677
+ }
678
+
679
+ .admin-pending__name {
680
+ overflow: hidden;
681
+ text-overflow: ellipsis;
682
+ white-space: nowrap;
683
+ }
684
+
685
+ .admin-pending__size {
686
+ color: #4e5969;
687
+ white-space: nowrap;
688
+ }
689
+
690
+ .admin-pending__size.is-over-limit {
691
+ color: #c53355;
692
+ font-weight: 600;
693
+ }
694
+
695
+ .admin-pending__remove {
696
+ border: 0;
697
+ background: none;
698
+ font-size: 18px;
699
+ line-height: 1;
700
+ color: #86909c;
701
+ cursor: pointer;
702
+ padding: 0;
703
+ }
704
+
705
+ .admin-pending__remove:hover {
706
+ color: #c53355;
707
+ }
708
+
709
+ .admin-pending__actions {
710
+ display: flex;
711
+ gap: 10px;
712
+ padding: 10px;
713
+ }
714
+
715
+ /* —— 按鈕 —— */
716
+ .admin-btn {
717
+ min-width: 72px;
718
+ padding: 6px 16px;
719
+ border-radius: 3px;
720
+ font-size: 13px;
721
+ font-weight: 600;
722
+ cursor: pointer;
723
+ transition: all 0.2s ease;
724
+ }
725
+
726
+ .admin-btn--primary {
727
+ border: 1px solid #c53355;
728
+ background: #c53355;
729
+ color: #fff;
730
+ }
731
+
732
+ .admin-btn--primary:hover {
733
+ background: #a82b48;
734
+ border-color: #a82b48;
735
+ }
736
+
737
+ .admin-btn--outline {
738
+ border: 1px solid #c53355;
739
+ background: #fff;
740
+ color: #c53355;
741
+ }
742
+
743
+ .admin-btn--outline:hover {
744
+ background: #fff5f6;
745
+ }
746
+
747
+ .admin-btn--sm {
748
+ min-width: auto;
749
+ padding: 4px 12px;
750
+ font-size: 12px;
751
+ }
752
+
753
+ /* —— 檔案表 —— */
754
+ .admin-source-toolbar {
755
+ display: flex;
756
+ align-items: center;
757
+ justify-content: space-between;
758
+ flex-wrap: wrap;
759
+ gap: 10px;
760
+ margin-top: 16px;
761
+ margin-bottom: 8px;
762
+ }
763
+
764
+ .admin-source-toolbar__all {
765
+ display: inline-flex;
766
+ align-items: center;
767
+ gap: 6px;
768
+ font-size: 13px;
769
+ cursor: pointer;
770
+ }
771
+
772
+ .admin-source-toolbar__all input {
773
+ accent-color: #c53355;
774
+ }
775
+
776
+ .admin-source-toolbar__search {
777
+ display: flex;
778
+ align-items: center;
779
+ gap: 8px;
780
+ flex-wrap: wrap;
781
+ }
782
+
783
+ .admin-source-toolbar__search-label {
784
+ font-size: 13px;
785
+ color: #4e5969;
786
+ }
787
+
788
+ .admin-source-toolbar__search-input {
789
+ width: 140px;
790
+ height: 28px;
791
+ padding: 2px 8px;
792
+ border: 1px solid #c9cdd4;
793
+ border-radius: 2px;
794
+ font-size: 13px;
795
+ }
796
+
797
+ .admin-source-table-wrap {
798
+ border: 1px solid #e8eaec;
799
+ overflow: auto;
800
+ max-height: 220px;
801
+ }
802
+
803
+ .admin-source-table {
804
+ width: 100%;
805
+ border-collapse: collapse;
806
+ font-size: 13px;
807
+ }
808
+
809
+ .admin-source-table th,
810
+ .admin-source-table td {
811
+ padding: 8px 10px;
812
+ border-bottom: 1px solid #f0f2f5;
813
+ text-align: left;
814
+ }
815
+
816
+ .admin-source-table th {
817
+ background: #fafafa;
818
+ font-weight: 600;
819
+ color: #1a1a1a;
820
+ }
821
+
822
+ .admin-source-table__check {
823
+ width: 36px;
824
+ text-align: center;
825
+ }
826
+
827
+ .admin-source-table__check input {
828
+ accent-color: #c53355;
829
+ }
830
+
831
+ .admin-source-table__action {
832
+ width: 56px;
833
+ text-align: center;
834
+ }
835
+
836
+ .admin-source-table__name {
837
+ word-break: break-all;
838
+ }
839
+
840
+ .admin-source-table__empty {
841
+ text-align: center;
842
+ color: #86909c;
843
+ padding: 24px;
844
+ }
845
+
846
+ .admin-icon-btn {
847
+ display: inline-flex;
848
+ align-items: center;
849
+ justify-content: center;
850
+ width: 28px;
851
+ height: 28px;
852
+ padding: 0;
853
+ border: 0;
854
+ background: none;
855
+ color: #4e5969;
856
+ cursor: pointer;
857
+ border-radius: 4px;
858
+ }
859
+
860
+ .admin-icon-btn:hover {
861
+ color: #c53355;
862
+ background: #fff5f6;
863
+ }
864
+
865
+ .admin-center-footer {
866
+ display: flex;
867
+ justify-content: center;
868
+ padding: 8px 0 4px;
869
+ }
870
+
871
+ @media (max-width: 960px) {
872
+ .admin-center-board {
873
+ grid-template-columns: 1fr;
874
+ }
875
+
876
+ .admin-center-panel--params {
877
+ border-right: 0;
878
+ border-bottom: 1px solid #1a1a1a;
879
+ }
880
+
881
+ .admin-form__row {
882
+ grid-template-columns: 1fr;
883
+ gap: 4px;
884
+ }
885
+ }
886
+ </style>