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