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,727 @@
1
+ <template>
2
+ <ReportCollapsibleSection
3
+ ref="sectionRef"
4
+ v-model:open="panelOpen"
5
+ title="報告配置"
6
+ card-hover
7
+ @before-collapse="handleBeforeCollapse"
8
+ >
9
+ <div class="report-config-panel">
10
+ <div class="report-config-field">
11
+ <span class="report-config-field__label">名稱</span>
12
+ <span class="report-config-field__value">{{ displayName }}</span>
13
+ </div>
14
+
15
+ <div ref="modelFieldRef" class="report-config-field report-config-field--editable">
16
+ <span class="report-config-field__label">模型</span>
17
+ <span class="report-config-field__value">{{ displayModel }}</span>
18
+ <button
19
+ ref="modelEditBtnRef"
20
+ type="button"
21
+ class="report-config-field__edit"
22
+ aria-label="編輯模型"
23
+ @click.stop="openFieldEdit('model')"
24
+ >
25
+ <el-icon>
26
+ <Edit />
27
+ </el-icon>
28
+ </button>
29
+ </div>
30
+
31
+ <div ref="researchFieldRef" class="report-config-field report-config-field--editable">
32
+ <span class="report-config-field__label">調研主要出發點</span>
33
+ <span class="report-config-field__value">{{ draft.researchFocus || '—' }}</span>
34
+ <button
35
+ ref="researchEditBtnRef"
36
+ type="button"
37
+ class="report-config-field__edit"
38
+ aria-label="編輯調研主要出發點"
39
+ @click.stop="openFieldEdit('research')"
40
+ >
41
+ <el-icon>
42
+ <Edit />
43
+ </el-icon>
44
+ </button>
45
+ </div>
46
+
47
+ <div class="report-config-field report-config-field--readonly">
48
+ <span class="report-config-field__label">年份</span>
49
+ <span class="report-config-field__value">{{ draft.year || '—' }}</span>
50
+ </div>
51
+
52
+ <div ref="languageFieldRef" class="report-config-field report-config-field--editable">
53
+ <span class="report-config-field__label">報告語言</span>
54
+ <span class="report-config-field__value">{{ languageLabel }}</span>
55
+ <button
56
+ ref="languageEditBtnRef"
57
+ type="button"
58
+ class="report-config-field__edit"
59
+ aria-label="編輯報告語言"
60
+ @click.stop="openFieldEdit('language')"
61
+ >
62
+ <el-icon>
63
+ <Edit />
64
+ </el-icon>
65
+ </button>
66
+ </div>
67
+
68
+ <button
69
+ type="button"
70
+ class="report-config-advanced-toggle"
71
+ @click="advancedOpen = !advancedOpen"
72
+ >
73
+ <span>進階參數</span>
74
+ <span
75
+ class="report-config-advanced-toggle__arrow"
76
+ :class="{ 'is-open': advancedOpen }"
77
+ aria-hidden="true"
78
+ />
79
+ </button>
80
+
81
+ <div v-show="advancedOpen" class="report-config-advanced">
82
+ <div class="report-config-field report-config-field--stack">
83
+ <span class="report-config-field__label">搜尋模式</span>
84
+ <el-select
85
+ v-model="advancedBuffers.searchMode"
86
+ class="report-config-editor__select"
87
+ size="small"
88
+ @change="applyAdvanced('searchMode', advancedBuffers.searchMode)"
89
+ >
90
+ <el-option v-for="mode in searchModeOptions" :key="mode" :label="mode" :value="mode" />
91
+ </el-select>
92
+ </div>
93
+ <div class="report-config-field report-config-field--stack">
94
+ <span class="report-config-field__label">每章節字數</span>
95
+ <el-input
96
+ v-model="advancedBuffers.wordsPerChapter"
97
+ size="small"
98
+ placeholder="50-5000"
99
+ @blur="applyWordsPerChapter"
100
+ />
101
+ </div>
102
+ <div class="report-config-field report-config-field--stack report-config-field--slider">
103
+ <span class="report-config-field__label">權重</span>
104
+ <div class="report-config-slider">
105
+ <el-slider
106
+ v-model="advancedBuffers.weight"
107
+ :min="0"
108
+ :max="1"
109
+ :step="0.1"
110
+ :show-tooltip="false"
111
+ @change="applyAdvanced('weight', advancedBuffers.weight)"
112
+ />
113
+ <span class="report-config-slider__num">{{ advancedBuffers.weight }}</span>
114
+ </div>
115
+ </div>
116
+ <div class="report-config-field report-config-field--stack">
117
+ <span class="report-config-field__label">Rerank</span>
118
+ <el-switch
119
+ v-model="advancedBuffers.rerank"
120
+ size="small"
121
+ class="report-config-switch"
122
+ @change="applyAdvanced('rerank', advancedBuffers.rerank)"
123
+ />
124
+ </div>
125
+ <p v-if="draft.searchMode === 'Vector Search'" class="report-config-advanced__hint">
126
+ 搜尋模式為「Vector Search」時,權重與 Rerank 相關參數將被忽略。
127
+ </p>
128
+ </div>
129
+ </div>
130
+ </ReportCollapsibleSection>
131
+
132
+ <el-popover
133
+ :visible="editModelOpen"
134
+ :virtual-ref="modelFieldRef"
135
+ placement="bottom-start"
136
+ :width="popoverWidth"
137
+ :popper-options="popperOptions"
138
+ :popper-style="modelPopperStyle"
139
+ popper-class="report-config-popover"
140
+ :transition="popoverInstant ? 'report-config-popover-instant' : 'report-config-popover-fade'"
141
+ :gpu-acceleration="false"
142
+ :teleported="true"
143
+ >
144
+ <div class="report-config-editor">
145
+ <el-select
146
+ v-model="editBuffers.model"
147
+ class="report-config-editor__select"
148
+ filterable
149
+ allow-create
150
+ default-first-option
151
+ placeholder=""
152
+ >
153
+ <el-option v-for="item in modelOptions" :key="item" :label="item" :value="item" />
154
+ </el-select>
155
+ <div class="report-config-editor__actions">
156
+ <button type="button" class="report-config-editor__btn" @click="editModelOpen = false">
157
+ 取消
158
+ </button>
159
+ <button
160
+ type="button"
161
+ class="report-config-editor__btn report-config-editor__btn--primary"
162
+ @click="applyField('model', editBuffers.model, () => (editModelOpen = false))"
163
+ >
164
+ 確認
165
+ </button>
166
+ </div>
167
+ </div>
168
+ </el-popover>
169
+
170
+ <el-popover
171
+ :visible="editResearchOpen"
172
+ :virtual-ref="researchFieldRef"
173
+ placement="bottom-start"
174
+ :width="popoverWidth"
175
+ :popper-options="popperOptions"
176
+ :popper-style="researchPopperStyle"
177
+ popper-class="report-config-popover"
178
+ :transition="popoverInstant ? 'report-config-popover-instant' : 'report-config-popover-fade'"
179
+ :gpu-acceleration="false"
180
+ :teleported="true"
181
+ >
182
+ <div class="report-config-editor">
183
+ <el-select
184
+ v-model="editBuffers.researchFocus"
185
+ class="report-config-editor__select"
186
+ filterable
187
+ allow-create
188
+ default-first-option
189
+ clearable
190
+ placeholder=""
191
+ >
192
+ <el-option v-for="item in researchFocusOptions" :key="item" :label="item" :value="item" />
193
+ </el-select>
194
+ <div class="report-config-editor__actions">
195
+ <button type="button" class="report-config-editor__btn" @click="editResearchOpen = false">
196
+ 取消
197
+ </button>
198
+ <button
199
+ type="button"
200
+ class="report-config-editor__btn report-config-editor__btn--primary"
201
+ @click="
202
+ applyField('researchFocus', editBuffers.researchFocus, () => (editResearchOpen = false))
203
+ "
204
+ >
205
+ 確認
206
+ </button>
207
+ </div>
208
+ </div>
209
+ </el-popover>
210
+
211
+ <el-popover
212
+ :visible="editLanguageOpen"
213
+ :virtual-ref="languageFieldRef"
214
+ placement="bottom-start"
215
+ :width="popoverWidth"
216
+ :popper-options="popperOptions"
217
+ :popper-style="languagePopperStyle"
218
+ popper-class="report-config-popover"
219
+ :transition="popoverInstant ? 'report-config-popover-instant' : 'report-config-popover-fade'"
220
+ :gpu-acceleration="false"
221
+ :teleported="true"
222
+ >
223
+ <div class="report-config-editor">
224
+ <el-select v-model="editBuffers.language" class="report-config-editor__select" placeholder="">
225
+ <el-option
226
+ v-for="item in languageOptions"
227
+ :key="item.value"
228
+ :label="item.label"
229
+ :value="item.value"
230
+ />
231
+ </el-select>
232
+ <div class="report-config-editor__actions">
233
+ <button type="button" class="report-config-editor__btn" @click="editLanguageOpen = false">
234
+ 取消
235
+ </button>
236
+ <button
237
+ type="button"
238
+ class="report-config-editor__btn report-config-editor__btn--primary"
239
+ @click="applyField('language', editBuffers.language, () => (editLanguageOpen = false))"
240
+ >
241
+ 確認
242
+ </button>
243
+ </div>
244
+ </div>
245
+ </el-popover>
246
+ </template>
247
+
248
+ <script lang="ts">
249
+ export const DEFAULT_REPORT_MODEL = 'gpt-666'
250
+
251
+ export const MODEL_OPTIONS = [
252
+ 'gpt-666',
253
+ 'GPT-4o',
254
+ 'GPT-4.1',
255
+ 'Claude 3.5 Sonnet',
256
+ 'Qwen-Max',
257
+ ] as const
258
+
259
+ export const RESEARCH_FOCUS_OPTIONS = ['中國香港', '中國內地', '東南亞', '歐美市場'] as const
260
+
261
+ export const LANGUAGE_OPTIONS = [
262
+ { value: 'zh-Hant', label: '繁體中文' },
263
+ { value: 'zh-Hans', label: '簡體中文' },
264
+ { value: 'en', label: 'English' },
265
+ ] as const
266
+
267
+ export const SEARCH_MODE_OPTIONS = ['Hybrid Search', 'Vector Search'] as const
268
+ </script>
269
+
270
+ <script setup lang="ts">
271
+ import { Edit } from '@element-plus/icons-vue'
272
+ import { ElIcon, ElInput, ElOption, ElPopover, ElSelect, ElSlider, ElSwitch } from 'element-plus'
273
+ import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
274
+ import { useReportWorkflow } from '../useReportWorkflow'
275
+ import { ReportCollapsibleSection } from '../../common'
276
+ const WORDS_MIN = 50
277
+ const WORDS_MAX = 5000
278
+
279
+ const { draft, patchDraft } = useReportWorkflow()
280
+
281
+ const panelOpen = ref(true)
282
+ const advancedOpen = ref(false)
283
+ const editModelOpen = ref(false)
284
+ const editResearchOpen = ref(false)
285
+ const editLanguageOpen = ref(false)
286
+ const popoverInstant = ref(false)
287
+
288
+ const modelFieldRef = ref<HTMLElement | null>(null)
289
+ const researchFieldRef = ref<HTMLElement | null>(null)
290
+ const languageFieldRef = ref<HTMLElement | null>(null)
291
+ const modelEditBtnRef = ref<HTMLElement | null>(null)
292
+ const researchEditBtnRef = ref<HTMLElement | null>(null)
293
+ const languageEditBtnRef = ref<HTMLElement | null>(null)
294
+ const sectionRef = ref<InstanceType<typeof ReportCollapsibleSection> | null>(null)
295
+
296
+ const popoverWidthPx = ref(240)
297
+ const arrowAlignKey = ref(0)
298
+ const popoverWidth = computed(() => popoverWidthPx.value)
299
+
300
+ function getSectionContentWidth() {
301
+ const sectionEl = sectionRef.value?.rootRef
302
+ if (!sectionEl) return 240
303
+ const styles = getComputedStyle(sectionEl)
304
+ const paddingX = parseFloat(styles.paddingLeft) + parseFloat(styles.paddingRight)
305
+ return Math.round(sectionEl.clientWidth - paddingX)
306
+ }
307
+
308
+ function getArrowPopperStyle(editBtn: HTMLElement | null): Record<string, string> {
309
+ void arrowAlignKey.value
310
+ void panelOpen.value
311
+ const sectionEl = sectionRef.value?.rootRef
312
+ if (!editBtn || !sectionEl) return {}
313
+ const sectionRect = sectionEl.getBoundingClientRect()
314
+ const paddingLeft = parseFloat(getComputedStyle(sectionEl).paddingLeft)
315
+ const btnRect = editBtn.getBoundingClientRect()
316
+ const left = btnRect.left + btnRect.width / 2 - sectionRect.left - paddingLeft - 8
317
+ return { '--report-config-arrow-left': `${left}px` }
318
+ }
319
+
320
+ const modelPopperStyle = computed(() => getArrowPopperStyle(modelEditBtnRef.value))
321
+
322
+ const researchPopperStyle = computed(() => getArrowPopperStyle(researchEditBtnRef.value))
323
+
324
+ const languagePopperStyle = computed(() => getArrowPopperStyle(languageEditBtnRef.value))
325
+
326
+ const popperOptions = computed(() => {
327
+ const boundary = sectionRef.value?.rootRef
328
+ return {
329
+ modifiers: [
330
+ {
331
+ name: 'preventOverflow',
332
+ options: {
333
+ boundary: boundary ?? 'viewport',
334
+ padding: 0,
335
+ altAxis: true,
336
+ },
337
+ },
338
+ {
339
+ name: 'flip',
340
+ enabled: false,
341
+ },
342
+ ],
343
+ }
344
+ })
345
+
346
+ function syncPopoverWidth() {
347
+ popoverWidthPx.value = getSectionContentWidth()
348
+ arrowAlignKey.value += 1
349
+ }
350
+
351
+ function hasOpenFieldEdits() {
352
+ return editModelOpen.value || editResearchOpen.value || editLanguageOpen.value
353
+ }
354
+
355
+ function closeAllFieldEdits() {
356
+ editModelOpen.value = false
357
+ editResearchOpen.value = false
358
+ editLanguageOpen.value = false
359
+ }
360
+
361
+ function handleBeforeCollapse(done: (hadOpenOverlays: boolean) => void) {
362
+ const hadOpen = hasOpenFieldEdits()
363
+ if (hadOpen) {
364
+ popoverInstant.value = true
365
+ closeAllFieldEdits()
366
+ window.setTimeout(() => {
367
+ popoverInstant.value = false
368
+ }, 320)
369
+ }
370
+ done(hadOpen)
371
+ }
372
+
373
+ type EditableField = 'model' | 'research' | 'language'
374
+
375
+ function openFieldEdit(field: EditableField) {
376
+ syncPopoverWidth()
377
+ editModelOpen.value = field === 'model' ? !editModelOpen.value : false
378
+ editResearchOpen.value = field === 'research' ? !editResearchOpen.value : false
379
+ editLanguageOpen.value = field === 'language' ? !editLanguageOpen.value : false
380
+ if (
381
+ field === 'model'
382
+ ? editModelOpen.value
383
+ : field === 'research'
384
+ ? editResearchOpen.value
385
+ : editLanguageOpen.value
386
+ ) {
387
+ void nextTick(() => {
388
+ arrowAlignKey.value += 1
389
+ })
390
+ }
391
+ }
392
+
393
+ watch(panelOpen, (open) => {
394
+ if (!open) closeAllFieldEdits()
395
+ })
396
+
397
+ let sectionResizeObserver: ResizeObserver | null = null
398
+
399
+ onMounted(() => {
400
+ void nextTick(() => {
401
+ syncPopoverWidth()
402
+ const sectionEl = sectionRef.value?.rootRef
403
+ if (sectionEl) {
404
+ sectionResizeObserver = new ResizeObserver(() => syncPopoverWidth())
405
+ sectionResizeObserver.observe(sectionEl)
406
+ }
407
+ })
408
+ window.addEventListener('resize', syncPopoverWidth)
409
+ })
410
+
411
+ onUnmounted(() => {
412
+ window.removeEventListener('resize', syncPopoverWidth)
413
+ sectionResizeObserver?.disconnect()
414
+ })
415
+
416
+ const modelOptions = MODEL_OPTIONS
417
+ const researchFocusOptions = RESEARCH_FOCUS_OPTIONS
418
+ const languageOptions = LANGUAGE_OPTIONS
419
+ const searchModeOptions = SEARCH_MODE_OPTIONS
420
+
421
+ const editBuffers = reactive({
422
+ model: DEFAULT_REPORT_MODEL,
423
+ researchFocus: '',
424
+ language: 'zh-Hant',
425
+ })
426
+
427
+ const advancedBuffers = reactive({
428
+ searchMode: 'Hybrid Search',
429
+ wordsPerChapter: '',
430
+ weight: 0.5,
431
+ rerank: true,
432
+ })
433
+
434
+ const displayName = computed(() => draft.reportName || '—')
435
+ const displayModel = computed(() => draft.model || DEFAULT_REPORT_MODEL)
436
+
437
+ const languageLabel = computed(() => {
438
+ const found = LANGUAGE_OPTIONS.find((item) => item.value === draft.language)
439
+ return found?.label ?? '繁體中文'
440
+ })
441
+
442
+ function syncBuffersFromDraft() {
443
+ editBuffers.model = draft.model || DEFAULT_REPORT_MODEL
444
+ editBuffers.researchFocus = draft.researchFocus
445
+ editBuffers.language = draft.language || 'zh-Hant'
446
+ advancedBuffers.searchMode = draft.searchMode
447
+ advancedBuffers.wordsPerChapter = draft.wordsPerChapter
448
+ advancedBuffers.weight = draft.weight
449
+ advancedBuffers.rerank = draft.rerank
450
+ }
451
+
452
+ watch(
453
+ () => [editModelOpen.value, editResearchOpen.value, editLanguageOpen.value] as const,
454
+ ([modelOpen, researchOpen, languageOpen]) => {
455
+ if (modelOpen || researchOpen || languageOpen) syncBuffersFromDraft()
456
+ }
457
+ )
458
+
459
+ watch(advancedOpen, (open) => {
460
+ if (open) syncBuffersFromDraft()
461
+ })
462
+
463
+ syncBuffersFromDraft()
464
+
465
+ function applyField<K extends 'model' | 'researchFocus' | 'language'>(
466
+ key: K,
467
+ value: (typeof editBuffers)[K],
468
+ close: () => void
469
+ ) {
470
+ patchDraft({ [key]: value })
471
+ close()
472
+ }
473
+
474
+ function applyAdvanced<K extends 'searchMode' | 'weight' | 'rerank'>(
475
+ key: K,
476
+ value: (typeof advancedBuffers)[K]
477
+ ) {
478
+ patchDraft({ [key]: value })
479
+ }
480
+
481
+ function applyWordsPerChapter() {
482
+ const raw = advancedBuffers.wordsPerChapter.trim()
483
+ if (!raw) {
484
+ patchDraft({ wordsPerChapter: '' })
485
+ return
486
+ }
487
+ const value = Math.round(Number(raw))
488
+ if (Number.isNaN(value)) {
489
+ advancedBuffers.wordsPerChapter = ''
490
+ patchDraft({ wordsPerChapter: '' })
491
+ return
492
+ }
493
+ const clamped = Math.min(WORDS_MAX, Math.max(WORDS_MIN, value))
494
+ advancedBuffers.wordsPerChapter = String(clamped)
495
+ patchDraft({ wordsPerChapter: String(clamped) })
496
+ }
497
+ </script>
498
+
499
+ <style scoped>
500
+ .report-config-panel {
501
+ box-sizing: border-box;
502
+ }
503
+
504
+ .report-config-panel__title {
505
+ margin: 0 0 4px;
506
+ font-size: 15px;
507
+ font-weight: 600;
508
+ color: #1a1a1a;
509
+ }
510
+
511
+ .report-config-panel__subtitle {
512
+ margin: 0 0 8px;
513
+ font-size: 13px;
514
+ font-weight: 600;
515
+ color: #303133;
516
+ }
517
+
518
+ .report-config-field {
519
+ display: grid;
520
+ grid-template-columns: 88px minmax(0, 1fr) auto;
521
+ align-items: center;
522
+ gap: 8px;
523
+ height: 40px;
524
+ padding: 0;
525
+ font-size: 14px;
526
+ font-weight: 400;
527
+ color: auto;
528
+ box-sizing: border-box;
529
+ }
530
+
531
+ .report-config-field--readonly {
532
+ grid-template-columns: 88px minmax(0, 1fr);
533
+ }
534
+
535
+ .report-config-field--editable {
536
+ width: 100%;
537
+ cursor: default;
538
+ }
539
+
540
+ .report-config-field--stack {
541
+ grid-template-columns: 1fr;
542
+ gap: 6px;
543
+ height: auto;
544
+ min-height: 40px;
545
+ padding: 8px 0;
546
+ font-size: 13px;
547
+ }
548
+
549
+ .report-config-field--slider {
550
+ align-items: flex-start;
551
+ }
552
+
553
+ .report-config-field__label {
554
+ color: auto;
555
+ font-size: 14px;
556
+ font-weight: 400;
557
+ white-space: nowrap;
558
+ }
559
+
560
+ .report-config-field__value {
561
+ text-align: right;
562
+ color: auto;
563
+ font-size: 14px;
564
+ font-weight: 400;
565
+ overflow: hidden;
566
+ text-overflow: ellipsis;
567
+ white-space: nowrap;
568
+ }
569
+
570
+ .report-config-field__edit {
571
+ display: inline-flex;
572
+ align-items: center;
573
+ justify-content: center;
574
+ padding: 0;
575
+ border: 0;
576
+ background: none;
577
+ color: #606266;
578
+ font-size: 14px;
579
+ cursor: pointer;
580
+ }
581
+
582
+ .report-config-field__edit:hover {
583
+ color: #c53355;
584
+ }
585
+
586
+ .report-config-advanced-toggle {
587
+ display: flex;
588
+ align-items: center;
589
+ justify-content: space-between;
590
+ width: 100%;
591
+ margin-top: 10px;
592
+ padding: 0;
593
+ border: 0;
594
+ background: none;
595
+ font-size: 13px;
596
+ font-weight: 700;
597
+ color: #1a1a1a;
598
+ cursor: pointer;
599
+ }
600
+
601
+ .report-config-advanced-toggle__arrow {
602
+ display: inline-block;
603
+ width: 0;
604
+ height: 0;
605
+ border-left: 5px solid transparent;
606
+ border-right: 5px solid transparent;
607
+ border-bottom: none;
608
+ /* 收起朝下;展開朝上(與折疊區塊一致) */
609
+ border-top: 7px solid #c53355;
610
+ transition: transform 0.2s ease;
611
+ }
612
+
613
+ .report-config-advanced-toggle__arrow.is-open {
614
+ transform: rotate(180deg);
615
+ }
616
+
617
+ .report-config-advanced {
618
+ padding-top: 4px;
619
+ }
620
+
621
+ .report-config-advanced__hint {
622
+ margin: 4px 0 0;
623
+ font-size: 12px;
624
+ line-height: 1.5;
625
+ color: #86909c;
626
+ }
627
+
628
+ .report-config-editor__select {
629
+ width: 100%;
630
+ }
631
+
632
+ .report-config-editor__select :deep(.el-select__wrapper) {
633
+ min-height: 32px;
634
+ }
635
+
636
+ .report-config-editor__actions {
637
+ display: flex;
638
+ justify-content: flex-end;
639
+ gap: 8px;
640
+ margin-top: 10px;
641
+ }
642
+
643
+ .report-config-editor__btn {
644
+ min-width: 72px;
645
+ padding: 6px 10px;
646
+ border: 1px solid #dcdfe6;
647
+ border-radius: 4px;
648
+ background: #fff;
649
+ font-size: 12px;
650
+ color: #606266;
651
+ cursor: pointer;
652
+ }
653
+
654
+ .report-config-editor__btn--primary {
655
+ border-color: #c53355;
656
+ background: #c53355;
657
+ color: #fff;
658
+ }
659
+
660
+ .report-config-editor__btn--primary:hover {
661
+ background: #fff;
662
+ color: #c53355;
663
+ }
664
+
665
+ .report-config-slider {
666
+ display: flex;
667
+ align-items: center;
668
+ gap: 8px;
669
+ width: 100%;
670
+ }
671
+
672
+ .report-config-slider :deep(.el-slider) {
673
+ flex: 1;
674
+ --el-slider-main-bg-color: #c53355;
675
+ }
676
+
677
+ .report-config-slider__num {
678
+ width: 28px;
679
+ font-size: 12px;
680
+ color: #606266;
681
+ text-align: right;
682
+ }
683
+
684
+ .report-config-switch {
685
+ --el-switch-on-color: #c53355;
686
+ }
687
+ </style>
688
+
689
+ <style>
690
+ .report-config-popover.el-popover.el-popper {
691
+ max-width: 100%;
692
+ padding: 12px;
693
+ box-sizing: border-box;
694
+ overflow: hidden;
695
+ }
696
+
697
+ .report-config-popover.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
698
+ left: var(--report-config-arrow-left) !important;
699
+ right: auto !important;
700
+ top: -6px !important;
701
+ transform: translateX(-50%) !important;
702
+ }
703
+
704
+ .report-config-popover.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::before {
705
+ bottom: -1px;
706
+ }
707
+
708
+ .report-config-popover-fade-enter-active,
709
+ .report-config-popover-fade-leave-active {
710
+ transition: opacity 0.12s ease;
711
+ }
712
+
713
+ .report-config-popover-fade-enter-from,
714
+ .report-config-popover-fade-leave-to {
715
+ opacity: 0;
716
+ }
717
+
718
+ .report-config-popover-instant-enter-active,
719
+ .report-config-popover-instant-leave-active {
720
+ transition: none !important;
721
+ }
722
+
723
+ .report-config-popover-instant-enter-from,
724
+ .report-config-popover-instant-leave-to {
725
+ opacity: 0;
726
+ }
727
+ </style>