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,1327 @@
1
+ <template>
2
+ <section
3
+ class="report-sources-panel"
4
+ :class="{
5
+ 'is-embedded': embedded,
6
+ 'is-sources-finalize': isFinalizeView,
7
+ }"
8
+ >
9
+ <h2 v-if="!embedded" class="report-sources-panel__title">資訊源檔案</h2>
10
+
11
+ <div v-if="!isFinalizeView" class="report-sources-stage-wrapper">
12
+ <div class="report-sources-stage">
13
+ <div
14
+ class="report-upload-zone"
15
+ role="button"
16
+ tabindex="0"
17
+ @click="triggerFilePick"
18
+ @keydown.enter="triggerFilePick"
19
+ >
20
+ <span class="report-upload-zone__plus">+</span>
21
+ <span class="report-upload-zone__label">添加資訊源</span>
22
+ <p class="report-upload-zone__hint">
23
+ (可一次多選,單個檔案上限100 MB,接受格式
24
+ txt、doc、docx、pdf、xls、xlsx、json、csv、pptx、PNG、JPG、JPEG、GIF,任何外部檔案需從可掃描病毒的方法接收,如內部電郵系統。)
25
+ </p>
26
+ <input
27
+ ref="fileInputRef"
28
+ type="file"
29
+ class="report-upload-zone__input"
30
+ multiple
31
+ accept=".txt,.doc,.docx,.pdf,.xls,.xlsx,.json,.csv,.pptx,.png,.jpg,.jpeg,.gif"
32
+ @change="onFilesPicked"
33
+ />
34
+ </div>
35
+
36
+ <div v-if="pendingFiles.length" class="report-pending">
37
+ <div class="report-pending__header">
38
+ <span class="report-pending__name-col">檔案名稱</span>
39
+ <span class="report-pending__size-col">大小</span>
40
+ <span class="report-pending__delete-col">刪除</span>
41
+ </div>
42
+ <div class="report-pending__body">
43
+ <div v-for="file in pendingFiles" :key="file.id" class="report-pending__row">
44
+ <span class="report-pending__name">{{ file.name }}</span>
45
+ <span
46
+ class="report-pending__size"
47
+ :class="{ 'is-over-limit': file.sizeKb > FILE_SIZE_LIMIT_KB }"
48
+ >
49
+ {{ file.sizeKb }}KB
50
+ </span>
51
+ <span class="report-pending__delete-col">
52
+ <button
53
+ type="button"
54
+ class="report-icon-btn report-pending__remove"
55
+ aria-label="刪除"
56
+ @click="requestRemovePending(file.id, $event)"
57
+ >
58
+ ×
59
+ </button>
60
+ </span>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ <div
65
+ class="report-pending__actions report-pending__actions--center"
66
+ :class="{ 'report-pending__actions--standalone': !pendingFiles.length }"
67
+ >
68
+ <button
69
+ type="button"
70
+ class="report-btn report-btn--primary"
71
+ :disabled="!pendingFiles.length || submitLoading"
72
+ @click="requestSubmitPending($event)"
73
+ >
74
+ 提交
75
+ </button>
76
+ <button
77
+ type="button"
78
+ class="report-btn report-btn--outline"
79
+ :disabled="!pendingFiles.length"
80
+ @click="clearPending"
81
+ >
82
+ 清空
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="report-sources-library">
89
+ <div class="report-source-toolbar">
90
+ <div class="report-source-toolbar__tabs">
91
+ <button
92
+ type="button"
93
+ class="report-source-tab"
94
+ :class="{ 'report-source-tab--active': sourceFilterTab === 'selected' }"
95
+ @click="setSourceFilterTab('selected')"
96
+ >
97
+ 已選中 ({{ selectedSourceCount }})
98
+ </button>
99
+ <button
100
+ type="button"
101
+ class="report-source-tab"
102
+ :class="{ 'report-source-tab--active': sourceFilterTab === 'unselected' }"
103
+ @click="setSourceFilterTab('unselected')"
104
+ >
105
+ 未選中 ({{ unselectedSourceCount }})
106
+ </button>
107
+ </div>
108
+ <div class="report-source-toolbar__search">
109
+ <span class="report-source-toolbar__search-label">Search:</span>
110
+ <input v-model="sourceSearch" type="text" class="report-source-toolbar__search-input" />
111
+ <button
112
+ type="button"
113
+ class="report-btn report-btn--outline report-btn--sm"
114
+ @click="refreshSources"
115
+ >
116
+ Refresh
117
+ </button>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="report-source-table-wrap">
122
+ <div class="report-source-table__bulk">
123
+ <!-- // 0914:資訊源「全選/全不選」checkbox -->
124
+ <label
125
+ class="report-source-table__select-all"
126
+ :class="{ 'is-disabled': !isSourcesEditable }"
127
+ >
128
+ <span class="report-source-table__bulk-check">
129
+ <input
130
+ type="checkbox"
131
+ :checked="allSourcesSelected"
132
+ :disabled="!isSourcesEditable"
133
+ @change="onToggleAllSources"
134
+ />
135
+ </span>
136
+ <span class="report-source-table__bulk-label">全選/全不選</span>
137
+ </label>
138
+ <!-- // 0914 -->
139
+ <button
140
+ v-if="showMoreSourcesLink && !isFinalizeView"
141
+ type="button"
142
+ class="report-sources-more report-source-table__bulk-more"
143
+ @click="handleShowMore"
144
+ >
145
+ 顯示更多 &gt;&gt;
146
+ </button>
147
+ </div>
148
+ <div class="report-source-table-scroll">
149
+ <table class="report-source-table" :class="{ 'is-finalize': isFinalizeView }">
150
+ <colgroup>
151
+ <col class="report-source-table__col-check" />
152
+ <col class="report-source-table__col-name" />
153
+ <col class="report-source-table__col-action" />
154
+ <col class="report-source-table__col-action" />
155
+ <col v-if="!isFinalizeView" class="report-source-table__col-action" />
156
+ </colgroup>
157
+ <thead>
158
+ <tr>
159
+ <th class="report-source-table__check" />
160
+ <th>檔案名稱</th>
161
+ <th class="report-source-table__action">預覽</th>
162
+ <th class="report-source-table__action">下載</th>
163
+ <th v-if="!isFinalizeView" class="report-source-table__action">刪除</th>
164
+ </tr>
165
+ </thead>
166
+ <tbody>
167
+ <tr v-for="file in visibleSources" :key="file.id">
168
+ <td class="report-source-table__check">
169
+ <!-- finalize(報告歷史環節):僅反顯 selected,不可主動勾選;可預覽/下載 -->
170
+ <input
171
+ type="checkbox"
172
+ :checked="file.selected"
173
+ :disabled="!isSourcesEditable"
174
+ @change="onSourceSelectedChange(file, $event)"
175
+ />
176
+ </td>
177
+ <td class="report-source-table__name">{{ file.name }}</td>
178
+ <td class="report-source-table__action">
179
+ <button
180
+ type="button"
181
+ class="report-icon-btn"
182
+ aria-label="預覽"
183
+ @click="previewSource(file)"
184
+ >
185
+ <el-icon>
186
+ <View />
187
+ </el-icon>
188
+ </button>
189
+ </td>
190
+ <td class="report-source-table__action">
191
+ <button
192
+ type="button"
193
+ class="report-icon-btn"
194
+ aria-label="下載"
195
+ @click="downloadSource(file)"
196
+ >
197
+ <el-icon>
198
+ <Download />
199
+ </el-icon>
200
+ </button>
201
+ </td>
202
+ <td v-if="!isFinalizeView" class="report-source-table__action">
203
+ <button
204
+ type="button"
205
+ class="report-icon-btn"
206
+ aria-label="刪除"
207
+ @click="requestDeleteSource(file.id, $event)"
208
+ >
209
+ <el-icon>
210
+ <Close />
211
+ </el-icon>
212
+ </button>
213
+ </td>
214
+ </tr>
215
+ <tr v-if="!visibleSources.length">
216
+ <td :colspan="isFinalizeView ? 4 : 5" class="report-source-table__empty">
217
+ 暫無匹配檔案
218
+ </td>
219
+ </tr>
220
+ </tbody>
221
+ </table>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </section>
226
+
227
+ <ReportSectionConfirmPopover
228
+ v-if="embedded"
229
+ :visible="deleteConfirmOpen"
230
+ :virtual-ref="deleteConfirmVirtualRef"
231
+ :boundary-el="sectionBoundaryEl ?? null"
232
+ :trigger-btn="deleteConfirmTriggerBtn"
233
+ :message="deleteConfirmMessage"
234
+ :instant="popoverInstant"
235
+ @confirm="confirmDelete"
236
+ @cancel="closeDeleteConfirm"
237
+ />
238
+ <ReportSectionConfirmPopover
239
+ v-if="embedded"
240
+ :visible="submitConfirmOpen"
241
+ :virtual-ref="submitConfirmVirtualRef"
242
+ :boundary-el="sectionBoundaryEl ?? null"
243
+ :trigger-btn="submitConfirmTriggerBtn"
244
+ title="確認提交"
245
+ :message="submitConfirmMessage"
246
+ :instant="popoverInstant"
247
+ @confirm="confirmSubmit"
248
+ @cancel="closeSubmitConfirm"
249
+ />
250
+ </template>
251
+
252
+ <script setup lang="ts">
253
+ import { Close, Download, View } from '@element-plus/icons-vue'
254
+ import { ElIcon, ElMessage, ElMessageBox } from 'element-plus'
255
+ import { computed, ref, watch } from 'vue'
256
+ import { ReportSectionConfirmPopover } from '../../common'
257
+ import {
258
+ deleteSourceFileBlob,
259
+ downloadBlobAsFile,
260
+ fetchPreviewFileBlob,
261
+ isPreviewableFileName,
262
+ openSourceFilePreview,
263
+ putSourceFileBlob,
264
+ } from '../../components/Preview'
265
+ import {
266
+ FILE_SIZE_LIMIT_KB,
267
+ SOURCE_VISIBLE_LIMIT,
268
+ useReportWorkflow,
269
+ type PendingFile,
270
+ type SourceFile,
271
+ type SourceFilterTab,
272
+ } from '../useReportWorkflow'
273
+ const props = withDefaults(
274
+ defineProps<{
275
+ syncWorkflow?: boolean
276
+ embedded?: boolean
277
+ sectionBoundaryEl?: HTMLElement | null
278
+ panelOpen?: boolean
279
+ sourcesViewMode?: 'edit' | 'finalize'
280
+ }>(),
281
+ {
282
+ syncWorkflow: false,
283
+ embedded: false,
284
+ sectionBoundaryEl: null,
285
+ panelOpen: true,
286
+ sourcesViewMode: 'edit',
287
+ }
288
+ )
289
+
290
+ const emit = defineEmits<{
291
+ submitted: []
292
+ }>()
293
+
294
+ const workflow = useReportWorkflow()
295
+
296
+ const isFinalizeView = computed(() => props.sourcesViewMode === 'finalize')
297
+ const isSourcesEditable = computed(() => !isFinalizeView.value)
298
+
299
+ const fileInputRef = ref<HTMLInputElement | null>(null)
300
+ const sourceSearch = ref('')
301
+ const sourceFilterTab = ref<SourceFilterTab>('all')
302
+ const showAllSources = ref(false)
303
+ // 0914
304
+ const allSourcesSelected = ref(false)
305
+ // 0914
306
+
307
+ const deleteConfirmOpen = ref(false)
308
+ const submitConfirmOpen = ref(false)
309
+ const submitLoading = ref(false)
310
+ const popoverInstant = ref(false)
311
+ const deleteConfirmMessage = ref('')
312
+ const submitConfirmMessage = ref('')
313
+ const deleteConfirmVirtualRef = ref<HTMLElement | null>(null)
314
+ const submitConfirmVirtualRef = ref<HTMLElement | null>(null)
315
+ const deleteConfirmTriggerBtn = ref<HTMLElement | null>(null)
316
+ const submitConfirmTriggerBtn = ref<HTMLElement | null>(null)
317
+ const deleteTarget = ref<{ type: 'source' | 'pending'; id: string } | null>(null)
318
+
319
+ const localPendingFiles = ref<PendingFile[]>([])
320
+ const localSourceFiles = ref<SourceFile[]>([])
321
+
322
+ const pendingFiles = computed({
323
+ get: () => (props.syncWorkflow ? workflow.pendingFiles.value : localPendingFiles.value),
324
+ set: (value) => {
325
+ if (props.syncWorkflow) workflow.pendingFiles.value = value
326
+ else localPendingFiles.value = value
327
+ },
328
+ })
329
+
330
+ const sourceFiles = computed({
331
+ get: () => (props.syncWorkflow ? workflow.sourceFiles.value : localSourceFiles.value),
332
+ set: (value) => {
333
+ if (props.syncWorkflow) workflow.sourceFiles.value = value
334
+ else localSourceFiles.value = value
335
+ },
336
+ })
337
+
338
+ const selectedSourceCount = computed(() => sourceFiles.value.filter((f) => f.selected).length)
339
+ const unselectedSourceCount = computed(() => sourceFiles.value.filter((f) => !f.selected).length)
340
+
341
+ const tabFilteredSources = computed(() => {
342
+ if (sourceFilterTab.value === 'selected') {
343
+ return sourceFiles.value.filter((file) => file.selected)
344
+ }
345
+ if (sourceFilterTab.value === 'unselected') {
346
+ return sourceFiles.value.filter((file) => !file.selected)
347
+ }
348
+ return sourceFiles.value
349
+ })
350
+
351
+ const filteredSources = computed(() => {
352
+ const q = sourceSearch.value.trim().toLowerCase()
353
+ const base = tabFilteredSources.value
354
+ if (!q) return base
355
+ return base.filter((file) => file.name.toLowerCase().includes(q))
356
+ })
357
+
358
+ const visibleSources = computed(() => {
359
+ if (showAllSources.value) return filteredSources.value
360
+ return filteredSources.value.slice(0, SOURCE_VISIBLE_LIMIT)
361
+ })
362
+
363
+ const showMoreSourcesLink = computed(
364
+ () => !showAllSources.value && filteredSources.value.length > SOURCE_VISIBLE_LIMIT
365
+ )
366
+
367
+ function handleShowMore() {
368
+ if (isFinalizeView.value) return
369
+ showAllSources.value = true
370
+ }
371
+
372
+ // 0914:全選 checkbox 與可見列表同步
373
+ watch(sourceFilterTab, () => {
374
+ showAllSources.value = false
375
+ const list = visibleSources.value
376
+ allSourcesSelected.value = list.length > 0 && list.every((f) => f.selected)
377
+ })
378
+
379
+ watch(
380
+ () => visibleSources.value.map((file) => file.selected),
381
+ () => {
382
+ const list = visibleSources.value
383
+ allSourcesSelected.value = list.length > 0 && list.every((file) => file.selected)
384
+ }
385
+ )
386
+ // 0914
387
+
388
+ function triggerFilePick() {
389
+ if (!isSourcesEditable.value) return
390
+ fileInputRef.value?.click()
391
+ }
392
+
393
+ function onFilesPicked(event: Event) {
394
+ if (!isSourcesEditable.value) return
395
+ const input = event.target as HTMLInputElement
396
+ const files = input.files
397
+ if (!files?.length) return
398
+
399
+ for (const file of files) {
400
+ const id = `pending-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
401
+ void putSourceFileBlob(id, file, file.name)
402
+ pendingFiles.value = [
403
+ ...pendingFiles.value,
404
+ {
405
+ id,
406
+ name: file.name,
407
+ sizeKb: Math.ceil(file.size / 1024),
408
+ },
409
+ ]
410
+ }
411
+ input.value = ''
412
+ }
413
+
414
+ function removePending(id: string) {
415
+ pendingFiles.value = pendingFiles.value.filter((f) => f.id !== id)
416
+ void deleteSourceFileBlob(id)
417
+ }
418
+
419
+ function resolveDeletePopoverAnchors(event: MouseEvent) {
420
+ const btn = event.currentTarget as HTMLElement
421
+ const row = btn.closest('tr, .report-pending__row') as HTMLElement | null
422
+ return {
423
+ virtualRef: row ?? btn,
424
+ triggerBtn: btn,
425
+ }
426
+ }
427
+
428
+ function requestRemovePending(id: string, event: MouseEvent) {
429
+ if (!isSourcesEditable.value) return
430
+ if (!props.embedded || !props.sectionBoundaryEl) {
431
+ removePending(id)
432
+ return
433
+ }
434
+ const file = pendingFiles.value.find((item) => item.id === id)
435
+ if (!file) return
436
+ const anchors = resolveDeletePopoverAnchors(event)
437
+ deleteConfirmVirtualRef.value = anchors.virtualRef
438
+ deleteConfirmTriggerBtn.value = anchors.triggerBtn
439
+ deleteTarget.value = { type: 'pending', id }
440
+ deleteConfirmMessage.value = `確定刪除「${file.name}」?`
441
+ deleteConfirmOpen.value = true
442
+ }
443
+
444
+ function clearPending() {
445
+ if (!isSourcesEditable.value) return
446
+ if (!pendingFiles.value.length) return
447
+
448
+ if (props.syncWorkflow) {
449
+ workflow.pendingFiles.value = []
450
+ } else {
451
+ localPendingFiles.value = []
452
+ }
453
+
454
+ if (fileInputRef.value) {
455
+ fileInputRef.value.value = ''
456
+ }
457
+ }
458
+
459
+ function hasOverLimitPendingFiles() {
460
+ return pendingFiles.value.some((file) => file.sizeKb > FILE_SIZE_LIMIT_KB)
461
+ }
462
+
463
+ function getSubmitConfirmMessage() {
464
+ return hasOverLimitPendingFiles() ? '是否跳過超出限制的檔案,上傳列表中的檔案?' : '是否提交?'
465
+ }
466
+
467
+ function requestSubmitPending(event: MouseEvent) {
468
+ if (!isSourcesEditable.value) return
469
+ if (!pendingFiles.value.length || submitLoading.value) return
470
+
471
+ const message = getSubmitConfirmMessage()
472
+ if (props.embedded && props.sectionBoundaryEl) {
473
+ const btn = event.currentTarget as HTMLElement
474
+ submitConfirmVirtualRef.value = btn
475
+ submitConfirmTriggerBtn.value = btn
476
+ submitConfirmMessage.value = message
477
+ submitConfirmOpen.value = true
478
+ return
479
+ }
480
+
481
+ void ElMessageBox.confirm(message, '確認提交', {
482
+ confirmButtonText: '確認',
483
+ cancelButtonText: '取消',
484
+ type: 'warning',
485
+ })
486
+ .then(() => performSubmit())
487
+ .catch(() => undefined)
488
+ }
489
+
490
+ function confirmSubmit() {
491
+ closeSubmitConfirm()
492
+ void performSubmit()
493
+ }
494
+
495
+ function closeSubmitConfirm() {
496
+ submitConfirmOpen.value = false
497
+ }
498
+
499
+ async function uploadPendingFiles(files: PendingFile[]) {
500
+ void files
501
+ await new Promise<void>((resolve) => {
502
+ window.setTimeout(resolve, 300)
503
+ })
504
+ }
505
+
506
+ async function performSubmit() {
507
+ if (!pendingFiles.value.length || submitLoading.value) return
508
+
509
+ const valid = pendingFiles.value.filter((file) => file.sizeKb <= FILE_SIZE_LIMIT_KB)
510
+ submitLoading.value = true
511
+ try {
512
+ await uploadPendingFiles(valid)
513
+ valid.forEach((file) => {
514
+ sourceFiles.value.push({ id: file.id, name: file.name, selected: false })
515
+ })
516
+ pendingFiles.value = pendingFiles.value.filter((file) => file.sizeKb > FILE_SIZE_LIMIT_KB)
517
+ ElMessage.success('提交成功')
518
+ emit('submitted')
519
+ } catch {
520
+ ElMessage.warning('請稍後重試')
521
+ } finally {
522
+ submitLoading.value = false
523
+ }
524
+ }
525
+
526
+ function setSourceFilterTab(tab: 'selected' | 'unselected') {
527
+ sourceFilterTab.value = sourceFilterTab.value === tab ? 'all' : tab
528
+ }
529
+
530
+ // 0914
531
+ function onToggleAllSources(event: Event) {
532
+ if (!isSourcesEditable.value) {
533
+ // 報告歷史環節:強制回顯當前狀態,阻止主動勾選
534
+ const input = event.target as HTMLInputElement
535
+ input.checked = allSourcesSelected.value
536
+ return
537
+ }
538
+ const next = (event.target as HTMLInputElement).checked
539
+ allSourcesSelected.value = next
540
+ visibleSources.value.forEach((file) => {
541
+ file.selected = next
542
+ })
543
+ }
544
+ // 0914
545
+
546
+ /** 單行勾選;finalize 僅反顯接口 selected,不可改 */
547
+ function onSourceSelectedChange(file: SourceFile, event: Event) {
548
+ const input = event.target as HTMLInputElement
549
+ if (!isSourcesEditable.value) {
550
+ input.checked = !!file.selected
551
+ return
552
+ }
553
+ file.selected = input.checked
554
+ }
555
+
556
+ function refreshSources() {
557
+ sourceSearch.value = ''
558
+ sourceFilterTab.value = 'all'
559
+ showAllSources.value = false
560
+ }
561
+
562
+ async function previewSource(file: SourceFile) {
563
+ if (!isPreviewableFileName(file.name)) {
564
+ ElMessage.warning('暫僅支持預覽 PDF / DOCX / DOC / 圖片')
565
+ return
566
+ }
567
+ try {
568
+ await openSourceFilePreview({
569
+ id: file.id,
570
+ name: file.name,
571
+ url: file.url,
572
+ })
573
+ } catch (err) {
574
+ ElMessage.warning(
575
+ err instanceof Error && err.message.trim()
576
+ ? `${err.message}(可嘗試重新上傳該檔後再預覽)`
577
+ : '無法打開預覽頁,請重新上傳後再試'
578
+ )
579
+ }
580
+ }
581
+
582
+ async function downloadSource(file: SourceFile) {
583
+ try {
584
+ const blob = await fetchPreviewFileBlob({
585
+ id: file.id,
586
+ name: file.name,
587
+ url: file.url,
588
+ })
589
+ downloadBlobAsFile(blob, file.name)
590
+ } catch (err) {
591
+ console.error(err)
592
+ ElMessage.warning(err instanceof Error && err.message.trim() ? err.message : '下載失敗')
593
+ }
594
+ }
595
+
596
+ function deleteSource(id: string) {
597
+ sourceFiles.value = sourceFiles.value.filter((f) => f.id !== id)
598
+ void deleteSourceFileBlob(id)
599
+ }
600
+
601
+ function requestDeleteSource(id: string, event: MouseEvent) {
602
+ if (!isSourcesEditable.value) return
603
+ if (!props.embedded || !props.sectionBoundaryEl) {
604
+ deleteSource(id)
605
+ return
606
+ }
607
+ const file = sourceFiles.value.find((item) => item.id === id)
608
+ if (!file) return
609
+ const anchors = resolveDeletePopoverAnchors(event)
610
+ deleteConfirmVirtualRef.value = anchors.virtualRef
611
+ deleteConfirmTriggerBtn.value = anchors.triggerBtn
612
+ deleteTarget.value = { type: 'source', id }
613
+ deleteConfirmMessage.value = `確定刪除「${file.name}」?`
614
+ deleteConfirmOpen.value = true
615
+ }
616
+
617
+ function confirmDelete() {
618
+ if (!deleteTarget.value) {
619
+ closeDeleteConfirm()
620
+ return
621
+ }
622
+ if (deleteTarget.value.type === 'source') {
623
+ deleteSource(deleteTarget.value.id)
624
+ } else {
625
+ removePending(deleteTarget.value.id)
626
+ }
627
+ closeDeleteConfirm()
628
+ }
629
+
630
+ function closeDeleteConfirm() {
631
+ deleteConfirmOpen.value = false
632
+ deleteTarget.value = null
633
+ }
634
+
635
+ function hasOpenOverlays() {
636
+ return deleteConfirmOpen.value || submitConfirmOpen.value
637
+ }
638
+
639
+ function closeAllOverlays(instant = false) {
640
+ if (!deleteConfirmOpen.value && !submitConfirmOpen.value) return
641
+ if (instant) popoverInstant.value = true
642
+ closeDeleteConfirm()
643
+ closeSubmitConfirm()
644
+ if (instant) {
645
+ window.setTimeout(() => {
646
+ popoverInstant.value = false
647
+ }, 320)
648
+ }
649
+ }
650
+
651
+ watch(
652
+ () => props.panelOpen,
653
+ (open) => {
654
+ if (!open) closeAllOverlays()
655
+ }
656
+ )
657
+
658
+ defineExpose({
659
+ sourceFiles: props.syncWorkflow ? workflow.sourceFiles : localSourceFiles,
660
+ pendingFiles: props.syncWorkflow ? workflow.pendingFiles : localPendingFiles,
661
+ hasOpenOverlays,
662
+ closeAllOverlays,
663
+ })
664
+ </script>
665
+
666
+ <style scoped>
667
+ .report-sources-panel {
668
+ display: none;
669
+ flex-direction: column;
670
+ height: 100%;
671
+ padding: 20px 24px 24px;
672
+ min-width: 0;
673
+ border: 1px solid #ebeef5;
674
+ border-radius: 8px;
675
+ background: #fff;
676
+ box-sizing: border-box;
677
+ transition: box-shadow var(--el-transition-duration, 0.3s);
678
+ --report-source-check-col: 36px;
679
+ --report-source-action-col: 36px;
680
+ --report-source-grid-cols: var(--report-source-check-col) 1fr
681
+ repeat(3, var(--report-source-action-col));
682
+ --report-source-row-h: 36px;
683
+ }
684
+
685
+ .report-sources-panel:not(.is-embedded):hover {
686
+ box-shadow: var(--el-box-shadow-light, 0 0 12px rgba(0, 0, 0, 0.12));
687
+ }
688
+
689
+ .report-sources-panel__title {
690
+ margin: 0 0 16px;
691
+ font-size: 15px;
692
+ font-weight: 700;
693
+ color: #1a1a1a;
694
+ flex-shrink: 0;
695
+ }
696
+
697
+ .report-sources-panel.is-embedded {
698
+ display: flex;
699
+ flex-direction: column;
700
+ flex: 1 1 auto;
701
+ height: 100%;
702
+ min-height: 0;
703
+ padding: 0;
704
+ border: 0;
705
+ border-radius: 0;
706
+ background: transparent;
707
+ }
708
+
709
+ .report-sources-panel.is-embedded.is-section-collapsed {
710
+ flex: 0 0 auto;
711
+ }
712
+
713
+ .report-sources-panel.is-embedded .report-sources-library {
714
+ flex: 0 0 auto;
715
+ overflow: visible;
716
+ }
717
+
718
+ .report-sources-panel.is-embedded .report-source-toolbar__search-input {
719
+ min-width: 0;
720
+ }
721
+
722
+ .report-sources-panel.is-embedded .report-source-table-wrap {
723
+ overflow-x: auto;
724
+ }
725
+
726
+ .report-sources-panel.is-sources-finalize {
727
+ --report-source-grid-cols: var(--report-source-check-col) 1fr
728
+ repeat(2, var(--report-source-action-col));
729
+ }
730
+
731
+ /* // 0914 */
732
+ .report-sources-panel.is-sources-finalize .report-source-table__select-all.is-disabled {
733
+ cursor: not-allowed;
734
+ }
735
+ /* // 0914 */
736
+
737
+ .report-sources-panel.is-sources-finalize .report-source-table__check input:disabled {
738
+ cursor: not-allowed;
739
+ }
740
+
741
+ .report-sources-panel.is-embedded.is-sources-finalize .report-sources-library {
742
+ border: 1px solid #ebeef5;
743
+ border-radius: 8px;
744
+ }
745
+
746
+ .report-sources-panel.is-sources-finalize:not(.is-embedded) .report-sources-library {
747
+ border-top: 1px solid #ebeef5;
748
+ border-radius: 8px;
749
+ }
750
+
751
+ .report-source-table.is-finalize th.report-source-table__action,
752
+ .report-source-table.is-finalize td.report-source-table__action {
753
+ width: var(--report-source-action-col);
754
+ }
755
+
756
+ .report-sources-stage-wrapper {
757
+ flex-shrink: 0;
758
+ box-sizing: border-box;
759
+ padding: 5px;
760
+ border: 1px solid #ebeef5;
761
+ border-radius: 8px 8px 0 0;
762
+ }
763
+
764
+ .report-sources-stage {
765
+ flex-shrink: 0;
766
+ border-bottom-left-radius: 8px;
767
+ border-bottom-right-radius: 8px;
768
+ overflow: hidden;
769
+ }
770
+
771
+ .report-sources-library {
772
+ display: flex;
773
+ flex: 1 1 auto;
774
+ flex-direction: column;
775
+ box-sizing: border-box;
776
+ padding: 5px;
777
+ border: 1px solid #ebeef5;
778
+ border-top: 0;
779
+ border-radius: 0 0 8px 8px;
780
+ overflow: hidden;
781
+ }
782
+
783
+ .report-sources-more {
784
+ padding: 0;
785
+ border: 0;
786
+ background: none;
787
+ font-size: 13px;
788
+ color: #409eff;
789
+ text-decoration: none;
790
+ cursor: pointer;
791
+ }
792
+
793
+ .report-sources-more:hover {
794
+ color: #66b1ff;
795
+ }
796
+
797
+ .report-upload-zone {
798
+ position: relative;
799
+ display: flex;
800
+ flex-direction: column;
801
+ align-items: center;
802
+ justify-content: center;
803
+ gap: 6px;
804
+ min-height: 120px;
805
+ padding: 16px;
806
+ border: 1px dashed #c9cdd4;
807
+ border-radius: 8px;
808
+ background: #fafafa;
809
+ cursor: pointer;
810
+ text-align: center;
811
+ transition:
812
+ background 0.2s ease,
813
+ border-color 0.2s ease;
814
+ }
815
+
816
+ .report-upload-zone:hover {
817
+ background: #f5f5f5;
818
+ border-color: #c53355;
819
+ }
820
+
821
+ .report-upload-zone__plus {
822
+ font-size: 28px;
823
+ line-height: 1;
824
+ color: #86909c;
825
+ font-weight: 300;
826
+ margin-top: -20px;
827
+ }
828
+
829
+ .report-upload-zone__label {
830
+ font-size: 13px;
831
+ color: #4e5969;
832
+ }
833
+
834
+ .report-upload-zone__hint {
835
+ margin: 4px 0 0;
836
+ max-width: 100%;
837
+ font-size: 11px;
838
+ line-height: 1.5;
839
+ color: #86909c;
840
+ }
841
+
842
+ .report-upload-zone__input {
843
+ position: absolute;
844
+ width: 0;
845
+ height: 0;
846
+ opacity: 0;
847
+ pointer-events: none;
848
+ }
849
+
850
+ .report-pending {
851
+ margin-top: 5px;
852
+ box-sizing: border-box;
853
+ }
854
+
855
+ .report-pending__body {
856
+ max-height: calc(var(--report-source-row-h) * 20);
857
+ overflow-x: hidden;
858
+ overflow-y: auto;
859
+ scrollbar-width: thin;
860
+ scrollbar-color: #999 transparent;
861
+ }
862
+
863
+ .report-pending__header,
864
+ .report-pending__row {
865
+ display: grid;
866
+ grid-template-columns: var(--report-source-grid-cols);
867
+ align-items: center;
868
+ height: var(--report-source-row-h);
869
+ font-size: 13px;
870
+ }
871
+
872
+ .report-pending__row {
873
+ border-radius: 4px;
874
+ transition: background-color 0.2s ease;
875
+ }
876
+
877
+ .report-pending__row:hover {
878
+ background-color: #f5f7fa;
879
+ }
880
+
881
+ .report-pending__header {
882
+ background: #fafafa;
883
+ font-weight: 600;
884
+ color: #1a1a1a;
885
+ }
886
+
887
+ .report-pending__name-col,
888
+ .report-pending__name {
889
+ grid-column: 1 / 4;
890
+ padding: 0 10px;
891
+ min-width: 0;
892
+ height: var(--report-source-row-h);
893
+ display: flex;
894
+ align-items: center;
895
+ }
896
+
897
+ .report-pending__size-col,
898
+ .report-pending__size {
899
+ grid-column: 4;
900
+ padding: 0 10px;
901
+ height: var(--report-source-row-h);
902
+ display: flex;
903
+ align-items: center;
904
+ justify-content: flex-end;
905
+ text-align: right;
906
+ box-sizing: border-box;
907
+ }
908
+
909
+ .report-pending__delete-col {
910
+ grid-column: 5;
911
+ padding: 0 10px;
912
+ height: var(--report-source-row-h);
913
+ display: flex;
914
+ align-items: center;
915
+ justify-content: flex-end;
916
+ text-align: right;
917
+ box-sizing: border-box;
918
+ }
919
+
920
+ .report-pending__name {
921
+ overflow: hidden;
922
+ text-overflow: ellipsis;
923
+ white-space: nowrap;
924
+ }
925
+
926
+ .report-pending__size {
927
+ color: #4e5969;
928
+ white-space: nowrap;
929
+ }
930
+
931
+ .report-pending__size.is-over-limit {
932
+ color: #c53355;
933
+ font-weight: 600;
934
+ }
935
+
936
+ .report-pending__remove {
937
+ border: 0;
938
+ background: none;
939
+ font-size: 18px;
940
+ line-height: 1;
941
+ color: #86909c;
942
+ cursor: pointer;
943
+ flex-shrink: 0;
944
+ }
945
+
946
+ .report-pending__remove:hover {
947
+ color: #c53355;
948
+ }
949
+
950
+ .report-pending__actions {
951
+ display: flex;
952
+ gap: 10px;
953
+ padding: 10px;
954
+ }
955
+
956
+ .report-pending__actions--center {
957
+ justify-content: center;
958
+ }
959
+
960
+ .report-btn {
961
+ min-width: 72px;
962
+ padding: 6px 16px;
963
+ border-radius: 3px;
964
+ font-size: 13px;
965
+ font-weight: 600;
966
+ cursor: pointer;
967
+ transition: all 0.2s ease;
968
+ }
969
+
970
+ .report-btn--primary {
971
+ border: 1px solid #c53355;
972
+ background: #c53355;
973
+ color: #fff;
974
+ }
975
+
976
+ .report-btn--primary:hover {
977
+ background: #a82b48;
978
+ border-color: #a82b48;
979
+ }
980
+
981
+ .report-btn--primary:disabled,
982
+ .report-btn--outline:disabled {
983
+ opacity: 0.45;
984
+ cursor: not-allowed;
985
+ }
986
+
987
+ .report-btn--primary:disabled:hover {
988
+ background: #c53355;
989
+ border-color: #c53355;
990
+ }
991
+
992
+ .report-btn--outline:disabled:hover {
993
+ background: #fff;
994
+ }
995
+
996
+ .report-pending__actions--standalone {
997
+ margin-top: 8px;
998
+ }
999
+
1000
+ .report-btn--outline {
1001
+ border: 1px solid #c53355;
1002
+ background: #fff;
1003
+ color: #c53355;
1004
+ }
1005
+
1006
+ .report-btn--outline:hover {
1007
+ background: #fff5f6;
1008
+ }
1009
+
1010
+ .report-btn--sm {
1011
+ min-width: auto;
1012
+ padding: 4px 12px;
1013
+ font-size: 12px;
1014
+ margin-right: -16px;
1015
+ }
1016
+
1017
+ .report-source-toolbar {
1018
+ display: flex;
1019
+ align-items: center;
1020
+ justify-content: space-between;
1021
+ flex-wrap: wrap;
1022
+ gap: 10px;
1023
+ margin: 8px 0;
1024
+ flex-shrink: 0;
1025
+ }
1026
+
1027
+ .report-source-toolbar__tabs {
1028
+ display: inline-flex;
1029
+ align-items: center;
1030
+ gap: 8px;
1031
+ flex-wrap: wrap;
1032
+ }
1033
+
1034
+ .report-source-tab {
1035
+ min-width: 88px;
1036
+ padding: 4px 12px;
1037
+ border: 1px solid #e8eaec;
1038
+ border-radius: 2px;
1039
+ background: #fff;
1040
+ font-size: 13px;
1041
+ color: #4e5969;
1042
+ cursor: pointer;
1043
+ transition: all 0.2s ease;
1044
+ }
1045
+
1046
+ .report-source-tab--active {
1047
+ border-color: #c53355;
1048
+ color: #c53355;
1049
+ background: #fff5f6;
1050
+ }
1051
+
1052
+ .report-source-toolbar__search {
1053
+ display: flex;
1054
+ align-items: center;
1055
+ gap: 8px;
1056
+ flex-wrap: wrap;
1057
+ }
1058
+
1059
+ .report-source-toolbar__search-label {
1060
+ font-size: 13px;
1061
+ color: #4e5969;
1062
+ }
1063
+
1064
+ .report-source-toolbar__search-input {
1065
+ width: min(140px, 100%);
1066
+ height: 28px;
1067
+ padding: 2px 8px;
1068
+ border: 1px solid #c9cdd4;
1069
+ border-radius: 2px;
1070
+ font-size: 13px;
1071
+ box-sizing: border-box;
1072
+ }
1073
+
1074
+ .report-source-table-wrap {
1075
+ overflow: visible;
1076
+ }
1077
+
1078
+ .report-source-table-scroll {
1079
+ max-height: calc(var(--report-source-row-h) * 21);
1080
+ overflow-x: auto;
1081
+ overflow-y: auto;
1082
+ scrollbar-width: auto;
1083
+ scrollbar-color: #999 transparent;
1084
+ }
1085
+
1086
+ .report-source-table__bulk {
1087
+ display: grid;
1088
+ grid-template-columns: var(--report-source-grid-cols);
1089
+ align-items: center;
1090
+ padding: 8px 0 6px;
1091
+ }
1092
+
1093
+ .report-source-table {
1094
+ width: 100%;
1095
+ table-layout: fixed;
1096
+ border-collapse: separate;
1097
+ border-spacing: 0;
1098
+ font-size: 13px;
1099
+ }
1100
+
1101
+ .report-source-table__col-check {
1102
+ width: var(--report-source-check-col);
1103
+ }
1104
+
1105
+ .report-source-table__col-action {
1106
+ width: var(--report-source-action-col);
1107
+ }
1108
+
1109
+ .report-source-table th,
1110
+ .report-source-table td {
1111
+ height: var(--report-source-row-h);
1112
+ padding: 0 10px;
1113
+ text-align: left;
1114
+ background: #fff;
1115
+ vertical-align: middle;
1116
+ box-sizing: border-box;
1117
+ transition: background-color 0.2s ease;
1118
+ }
1119
+
1120
+ .report-source-table th.report-source-table__action,
1121
+ .report-source-table td.report-source-table__action {
1122
+ width: var(--report-source-action-col);
1123
+ padding: 0 2px;
1124
+ text-align: center;
1125
+ vertical-align: middle;
1126
+ }
1127
+
1128
+ .report-source-table th.report-source-table__action:last-child,
1129
+ .report-source-table td.report-source-table__action:last-child {
1130
+ padding-right: 4px;
1131
+ }
1132
+
1133
+ .report-source-table td.report-source-table__action .report-icon-btn {
1134
+ vertical-align: middle;
1135
+ }
1136
+
1137
+ .report-source-table tbody tr:hover > td {
1138
+ background-color: #f5f7fa;
1139
+ }
1140
+
1141
+ .report-source-table tbody tr:hover > td:first-child {
1142
+ border-radius: 4px 0 0 4px;
1143
+ }
1144
+
1145
+ .report-source-table tbody tr:hover > td:last-child {
1146
+ border-radius: 0 4px 4px 0;
1147
+ }
1148
+
1149
+ .report-source-table th {
1150
+ position: sticky;
1151
+ top: 0;
1152
+ z-index: 1;
1153
+ background: #fafafa;
1154
+ font-weight: 600;
1155
+ color: #1a1a1a;
1156
+ }
1157
+
1158
+ .report-source-table__check {
1159
+ width: var(--report-source-check-col);
1160
+ padding-left: 10px;
1161
+ padding-right: 0;
1162
+ text-align: left;
1163
+ vertical-align: middle;
1164
+ }
1165
+
1166
+ /* // 0914:全選/全不選 checkbox 列樣式 */
1167
+ .report-source-table__select-all {
1168
+ display: contents;
1169
+ cursor: pointer;
1170
+ }
1171
+
1172
+ .report-source-table__bulk-check {
1173
+ display: flex;
1174
+ align-items: center;
1175
+ padding-left: 10px;
1176
+ }
1177
+
1178
+ .report-source-table__bulk-label {
1179
+ padding: 0 10px;
1180
+ font-size: 13px;
1181
+ font-weight: 600;
1182
+ color: #1a1a1a;
1183
+ white-space: nowrap;
1184
+ }
1185
+
1186
+ .report-source-table__bulk-check input,
1187
+ .report-source-table__check input {
1188
+ accent-color: #c53355;
1189
+ }
1190
+ /* // 0914 */
1191
+
1192
+ .report-source-table__bulk-more {
1193
+ grid-column: 3 / -1;
1194
+ justify-self: end;
1195
+ align-self: center;
1196
+ padding-right: 10px;
1197
+ }
1198
+
1199
+ .report-source-table__name {
1200
+ overflow: hidden;
1201
+ text-overflow: ellipsis;
1202
+ white-space: nowrap;
1203
+ }
1204
+
1205
+ .report-source-table__empty {
1206
+ text-align: center;
1207
+ color: #86909c;
1208
+ height: auto;
1209
+ padding: 24px;
1210
+ }
1211
+
1212
+ @media (max-width: 960px) {
1213
+ .report-sources-panel {
1214
+ height: auto;
1215
+ }
1216
+
1217
+ .report-source-toolbar {
1218
+ flex-direction: column;
1219
+ align-items: stretch;
1220
+ }
1221
+
1222
+ .report-source-toolbar__search {
1223
+ width: 100%;
1224
+ }
1225
+
1226
+ .report-source-toolbar__search-input {
1227
+ flex: 1;
1228
+ width: auto;
1229
+ min-width: 0;
1230
+ }
1231
+
1232
+ .report-source-table__bulk {
1233
+ grid-template-columns: var(--report-source-check-col) 1fr;
1234
+ gap: 6px;
1235
+ }
1236
+
1237
+ .report-source-table__bulk-more {
1238
+ grid-column: 1 / -1;
1239
+ justify-self: end;
1240
+ padding-right: 10px;
1241
+ }
1242
+ }
1243
+
1244
+ @media (max-width: 849px) {
1245
+ .report-source-toolbar__search {
1246
+ display: flex;
1247
+ flex-wrap: wrap;
1248
+ align-items: center;
1249
+ width: 100%;
1250
+ min-width: 0;
1251
+ row-gap: 8px;
1252
+ }
1253
+
1254
+ /* Search input 等比例縮小佔滿剩餘寬度 */
1255
+ .report-source-toolbar__search-input {
1256
+ flex: 1 1 0;
1257
+ width: auto;
1258
+ min-width: 0;
1259
+ }
1260
+
1261
+ /* Refresh 換行,max-width 120,居右 */
1262
+ .report-source-toolbar__search .report-btn {
1263
+ flex: 0 0 100%;
1264
+ box-sizing: border-box;
1265
+ width: 100%;
1266
+ max-width: 120px;
1267
+ margin-left: auto;
1268
+ margin-right: 0;
1269
+ }
1270
+ }
1271
+ </style>
1272
+
1273
+ <style>
1274
+ /* 編輯者 / 待提交 / 資訊源列表:滾動條 8px 軌道,滑塊 50px(豎=高 / 橫=寬) */
1275
+ .report-editor-list::-webkit-scrollbar,
1276
+ .report-pending__body::-webkit-scrollbar,
1277
+ .report-source-table-scroll::-webkit-scrollbar,
1278
+ .report-sources-panel.is-embedded .report-source-table-wrap::-webkit-scrollbar {
1279
+ width: 8px !important;
1280
+ height: 8px !important;
1281
+ }
1282
+
1283
+ .report-editor-list::-webkit-scrollbar-track,
1284
+ .report-pending__body::-webkit-scrollbar-track,
1285
+ .report-source-table-scroll::-webkit-scrollbar-track,
1286
+ .report-sources-panel.is-embedded .report-source-table-wrap::-webkit-scrollbar-track {
1287
+ background: transparent !important;
1288
+ }
1289
+
1290
+ .report-editor-list::-webkit-scrollbar-thumb,
1291
+ .report-pending__body::-webkit-scrollbar-thumb,
1292
+ .report-source-table-scroll::-webkit-scrollbar-thumb,
1293
+ .report-sources-panel.is-embedded .report-source-table-wrap::-webkit-scrollbar-thumb {
1294
+ border-radius: 4px !important;
1295
+ background-color: #999 !important;
1296
+ /* 豎向滑塊高 50px;底部橫向滑塊寬 50px */
1297
+ min-width: 50px !important;
1298
+ min-height: 50px !important;
1299
+ }
1300
+
1301
+ .report-editor-list::-webkit-scrollbar-corner,
1302
+ .report-pending__body::-webkit-scrollbar-corner,
1303
+ .report-source-table-scroll::-webkit-scrollbar-corner,
1304
+ .report-sources-panel.is-embedded .report-source-table-wrap::-webkit-scrollbar-corner {
1305
+ background: transparent !important;
1306
+ }
1307
+
1308
+ .report-icon-btn {
1309
+ display: inline-flex;
1310
+ align-items: center;
1311
+ justify-content: center;
1312
+ width: 24px;
1313
+ height: 24px;
1314
+ padding: 0;
1315
+ border: 0;
1316
+ background: none;
1317
+ color: #4e5969;
1318
+ cursor: pointer;
1319
+ border-radius: 4px;
1320
+ vertical-align: middle;
1321
+ }
1322
+
1323
+ .report-icon-btn:hover {
1324
+ color: #c53355;
1325
+ background: #fff5f6;
1326
+ }
1327
+ </style>