aegon-lv97 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 (78) hide show
  1. package/gen-ai/build-report/chartStreamRefs.ts +227 -0
  2. package/gen-ai/build-report/coherence-check/index.vue +559 -0
  3. package/gen-ai/build-report/components/ReportChapterWorkspace.vue +4571 -0
  4. package/gen-ai/build-report/components/ReportConfigPanel.vue +727 -0
  5. package/gen-ai/build-report/components/ReportGenerateSettingsPanel.vue +2361 -0
  6. package/gen-ai/build-report/components/ReportGeneratingPanel.vue +1765 -0
  7. package/gen-ai/build-report/components/ReportPromptPanel.vue +625 -0
  8. package/gen-ai/build-report/components/ReportSourceFileSummary.vue +665 -0
  9. package/gen-ai/build-report/components/ReportSourcesPanel.vue +1327 -0
  10. package/gen-ai/build-report/components/ReportTemplateDetail.vue +191 -0
  11. package/gen-ai/build-report/components/ReportTemplateEditorFormPanel.vue +667 -0
  12. package/gen-ai/build-report/components/ReportTemplatePicker.vue +302 -0
  13. package/gen-ai/build-report/components/ReportWorkflowSidebar.vue +148 -0
  14. package/gen-ai/build-report/components/paramsSetting.vue +886 -0
  15. package/gen-ai/build-report/components/reportEdit.vue +2283 -0
  16. package/gen-ai/build-report/index.vue +856 -0
  17. package/gen-ai/build-report/report-domain.ts +1133 -0
  18. package/gen-ai/build-report/report-template-data.ts +295 -0
  19. package/gen-ai/build-report/useReportWorkflow.ts +1386 -0
  20. package/gen-ai/common/assets/NotoSans-SC.woff2 +0 -0
  21. package/gen-ai/common/assets/NotoSans-TC.woff2 +0 -0
  22. package/gen-ai/common/assets/NotoSans.woff2 +0 -0
  23. package/gen-ai/common/assets/fonts.css +278 -0
  24. package/gen-ai/common/assets/pagination.css +163 -0
  25. package/gen-ai/common/directives/btnLoading.ts +455 -0
  26. package/gen-ai/common/directives/debounce.ts +98 -0
  27. package/gen-ai/common/directives/index.ts +30 -0
  28. package/gen-ai/common/en.md +417 -0
  29. package/gen-ai/common/index.ts +36 -0
  30. package/gen-ai/common/types/component-expose.ts +9 -0
  31. package/gen-ai/common/utils/api-res-status.ts +7 -0
  32. package/gen-ai/common/utils/navigation.ts +59 -0
  33. package/gen-ai/common/utils/remote-request.ts +45 -0
  34. package/gen-ai/common/utils/scroll.ts +577 -0
  35. package/gen-ai/components/AIExport/docx-export.ts +950 -0
  36. package/gen-ai/components/AIExport/docxExport.ts +2566 -0
  37. package/gen-ai/components/AIExport/index.ts +52 -0
  38. package/gen-ai/components/AIExport/parseMarkdownHtml.ts +653 -0
  39. package/gen-ai/components/CapsuleScrollbar.vue +149 -0
  40. package/gen-ai/components/ChapterTitleScroll.vue +468 -0
  41. package/gen-ai/components/EntryHeroNavCards.vue +379 -0
  42. package/gen-ai/components/GenAiPagination.vue +52 -0
  43. package/gen-ai/components/ModelCard.vue +1114 -0
  44. package/gen-ai/components/Preview/FilePreviewPage.vue +949 -0
  45. package/gen-ai/components/Preview/PreviewCapsuleScroll.vue +205 -0
  46. package/gen-ai/components/Preview/PreviewOutlineTree.vue +148 -0
  47. package/gen-ai/components/Preview/index.ts +11 -0
  48. package/gen-ai/components/Preview/preview.ts +724 -0
  49. package/gen-ai/components/PromptField.vue +505 -0
  50. package/gen-ai/components/RecentReportsTable.vue +974 -0
  51. package/gen-ai/components/ReportCollapsibleSection.vue +172 -0
  52. package/gen-ai/components/ReportHistoryList.vue +637 -0
  53. package/gen-ai/components/ReportSectionConfirmPopover.vue +251 -0
  54. package/gen-ai/components/editor/ChapterTiptapEditor.vue +3718 -0
  55. package/gen-ai/components/editor/StructureBlockView.vue +441 -0
  56. package/gen-ai/components/editor/TiptapToolbar.vue +1036 -0
  57. package/gen-ai/components/editor/TypewriterDocEditor.vue +422 -0
  58. package/gen-ai/components/editor/_StructureOrgNode.vue +326 -0
  59. package/gen-ai/components/editor/index.ts +28 -0
  60. package/gen-ai/components/editor/parseMarkdownHtml.ts +754 -0
  61. package/gen-ai/components/editor/reviewCitationTip.ts +163 -0
  62. package/gen-ai/gen-entry/api/index.ts +32 -0
  63. package/gen-ai/gen-entry/index.vue +287 -0
  64. package/gen-ai/gen-entry/types/index.ts +4 -0
  65. package/gen-ai/i.mark +2 -0
  66. package/gen-ai/management-center/index.vue +1815 -0
  67. package/gen-ai/management-center/mockData.ts +248 -0
  68. package/gen-ai/management-center/types/index.ts +37 -0
  69. package/gen-ai/model-manager/TemplateEditorFormPanel.vue +661 -0
  70. package/gen-ai/model-manager/api/index.ts +32 -0
  71. package/gen-ai/model-manager/api/mockApi.ts +33 -0
  72. package/gen-ai/model-manager/index.vue +1583 -0
  73. package/gen-ai/model-manager/mockData.ts +177 -0
  74. package/gen-ai/model-manager/template-detail-shared.ts +154 -0
  75. package/gen-ai/model-manager/template-editor.vue +345 -0
  76. package/gen-ai/model-manager/types/index.ts +40 -0
  77. package/gen-ai/model-manager/utils.ts +25 -0
  78. package/package.json +12 -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>