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.
- package/gen-ai/build-report/chartStreamRefs.ts +227 -0
- package/gen-ai/build-report/coherence-check/index.vue +559 -0
- package/gen-ai/build-report/components/ReportChapterWorkspace.vue +4571 -0
- package/gen-ai/build-report/components/ReportConfigPanel.vue +727 -0
- package/gen-ai/build-report/components/ReportGenerateSettingsPanel.vue +2361 -0
- package/gen-ai/build-report/components/ReportGeneratingPanel.vue +1765 -0
- package/gen-ai/build-report/components/ReportPromptPanel.vue +625 -0
- package/gen-ai/build-report/components/ReportSourceFileSummary.vue +665 -0
- package/gen-ai/build-report/components/ReportSourcesPanel.vue +1327 -0
- package/gen-ai/build-report/components/ReportTemplateDetail.vue +191 -0
- package/gen-ai/build-report/components/ReportTemplateEditorFormPanel.vue +667 -0
- package/gen-ai/build-report/components/ReportTemplatePicker.vue +302 -0
- package/gen-ai/build-report/components/ReportWorkflowSidebar.vue +148 -0
- package/gen-ai/build-report/components/paramsSetting.vue +886 -0
- package/gen-ai/build-report/components/reportEdit.vue +2283 -0
- package/gen-ai/build-report/index.vue +856 -0
- package/gen-ai/build-report/report-domain.ts +1133 -0
- package/gen-ai/build-report/report-template-data.ts +295 -0
- package/gen-ai/build-report/useReportWorkflow.ts +1386 -0
- package/gen-ai/common/assets/NotoSans-SC.woff2 +0 -0
- package/gen-ai/common/assets/NotoSans-TC.woff2 +0 -0
- package/gen-ai/common/assets/NotoSans.woff2 +0 -0
- package/gen-ai/common/assets/fonts.css +278 -0
- package/gen-ai/common/assets/pagination.css +163 -0
- package/gen-ai/common/directives/btnLoading.ts +455 -0
- package/gen-ai/common/directives/debounce.ts +98 -0
- package/gen-ai/common/directives/index.ts +30 -0
- package/gen-ai/common/en.md +417 -0
- package/gen-ai/common/index.ts +36 -0
- package/gen-ai/common/types/component-expose.ts +9 -0
- package/gen-ai/common/utils/api-res-status.ts +7 -0
- package/gen-ai/common/utils/navigation.ts +59 -0
- package/gen-ai/common/utils/remote-request.ts +45 -0
- package/gen-ai/common/utils/scroll.ts +577 -0
- package/gen-ai/components/AIExport/docx-export.ts +950 -0
- package/gen-ai/components/AIExport/docxExport.ts +2566 -0
- package/gen-ai/components/AIExport/index.ts +52 -0
- package/gen-ai/components/AIExport/parseMarkdownHtml.ts +653 -0
- package/gen-ai/components/CapsuleScrollbar.vue +149 -0
- package/gen-ai/components/ChapterTitleScroll.vue +468 -0
- package/gen-ai/components/EntryHeroNavCards.vue +379 -0
- package/gen-ai/components/GenAiPagination.vue +52 -0
- package/gen-ai/components/ModelCard.vue +1114 -0
- package/gen-ai/components/Preview/FilePreviewPage.vue +949 -0
- package/gen-ai/components/Preview/PreviewCapsuleScroll.vue +205 -0
- package/gen-ai/components/Preview/PreviewOutlineTree.vue +148 -0
- package/gen-ai/components/Preview/index.ts +11 -0
- package/gen-ai/components/Preview/preview.ts +724 -0
- package/gen-ai/components/PromptField.vue +505 -0
- package/gen-ai/components/RecentReportsTable.vue +974 -0
- package/gen-ai/components/ReportCollapsibleSection.vue +172 -0
- package/gen-ai/components/ReportHistoryList.vue +637 -0
- package/gen-ai/components/ReportSectionConfirmPopover.vue +251 -0
- package/gen-ai/components/editor/ChapterTiptapEditor.vue +3718 -0
- package/gen-ai/components/editor/StructureBlockView.vue +441 -0
- package/gen-ai/components/editor/TiptapToolbar.vue +1036 -0
- package/gen-ai/components/editor/TypewriterDocEditor.vue +422 -0
- package/gen-ai/components/editor/_StructureOrgNode.vue +326 -0
- package/gen-ai/components/editor/index.ts +28 -0
- package/gen-ai/components/editor/parseMarkdownHtml.ts +754 -0
- package/gen-ai/components/editor/reviewCitationTip.ts +163 -0
- package/gen-ai/gen-entry/api/index.ts +32 -0
- package/gen-ai/gen-entry/index.vue +287 -0
- package/gen-ai/gen-entry/types/index.ts +4 -0
- package/gen-ai/i.mark +2 -0
- package/gen-ai/management-center/index.vue +1815 -0
- package/gen-ai/management-center/mockData.ts +248 -0
- package/gen-ai/management-center/types/index.ts +37 -0
- package/gen-ai/model-manager/TemplateEditorFormPanel.vue +661 -0
- package/gen-ai/model-manager/api/index.ts +32 -0
- package/gen-ai/model-manager/api/mockApi.ts +33 -0
- package/gen-ai/model-manager/index.vue +1583 -0
- package/gen-ai/model-manager/mockData.ts +177 -0
- package/gen-ai/model-manager/template-detail-shared.ts +154 -0
- package/gen-ai/model-manager/template-editor.vue +345 -0
- package/gen-ai/model-manager/types/index.ts +40 -0
- package/gen-ai/model-manager/utils.ts +25 -0
- package/package.json +12 -0
|
@@ -0,0 +1,4571 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="report-chapter-workspace"
|
|
4
|
+
v-loading="chapterContentAwaitingFirstChunk"
|
|
5
|
+
element-loading-text="章節內容生成中..."
|
|
6
|
+
element-loading-background="rgba(255, 255, 255, 0.72)"
|
|
7
|
+
>
|
|
8
|
+
<header class="report-chapter-workspace__toolbar">
|
|
9
|
+
<div v-if="showFinalizeToolbar" class="report-chapter-workspace__chapter-box">
|
|
10
|
+
<el-button class="chatbot-report-btn-outline" @click="handleBackToChapterEdit">
|
|
11
|
+
返回章節編輯
|
|
12
|
+
</el-button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!--
|
|
16
|
+
// new prompt sections
|
|
17
|
+
章節下拉(本組件內實現,不拆新組件):
|
|
18
|
+
- 無章節時觸發器空白(不顯示 0);空列表僅 footer「新增章節」
|
|
19
|
+
- >1024 彈層最小 500px + 長標題換行(樣式見本文件 style 底部同標記區塊)
|
|
20
|
+
- 整行可拖排序;拖後顯示「確認排序」;最多 14 章可見
|
|
21
|
+
-->
|
|
22
|
+
<div v-else-if="showChapterEditToolbar" class="report-chapter-workspace__chapter-box">
|
|
23
|
+
<!--
|
|
24
|
+
//0920:章节下拉防闪
|
|
25
|
+
根因:el-select 挂载/选项注册前 selected-item 空→满会闪一帧;
|
|
26
|
+
做法:外层稳定文案盖住内部文字(内部 transparent),标题未就绪不挂 select。
|
|
27
|
+
-->
|
|
28
|
+
<div
|
|
29
|
+
class="report-chapter-workspace__chapter-select-shell"
|
|
30
|
+
:class="{
|
|
31
|
+
'is-disabled': chapterEditMode,
|
|
32
|
+
'is-pending': !chapterSelectReady,
|
|
33
|
+
}"
|
|
34
|
+
>
|
|
35
|
+
<span class="report-chapter-workspace__chapter-select-facade" aria-hidden="true">{{
|
|
36
|
+
chapterSelectPendingLabel
|
|
37
|
+
}}</span>
|
|
38
|
+
<el-select
|
|
39
|
+
v-if="chapterSelectReady"
|
|
40
|
+
ref="chapterSelectRef"
|
|
41
|
+
:model-value="chapterSelectModelValue"
|
|
42
|
+
class="report-chapter-workspace__chapter-select"
|
|
43
|
+
teleported
|
|
44
|
+
placement="bottom-start"
|
|
45
|
+
:fallback-placements="['bottom-start', 'top-start', 'bottom-end', 'top-end']"
|
|
46
|
+
:offset="10"
|
|
47
|
+
:popper-options="chapterSelectPopperOptions"
|
|
48
|
+
popper-class="report-chapter-workspace__chapter-select-popper"
|
|
49
|
+
placeholder=""
|
|
50
|
+
:disabled="chapterEditMode"
|
|
51
|
+
@update:model-value="onChapterSelectModelUpdate"
|
|
52
|
+
@visible-change="onChapterSelectVisibleChange"
|
|
53
|
+
>
|
|
54
|
+
<el-option
|
|
55
|
+
v-for="(title, index) in chapterTitles"
|
|
56
|
+
:key="genAiSectionItems[index]?.sectionId ?? `chapter-opt-${index}`"
|
|
57
|
+
:label="title"
|
|
58
|
+
:value="index"
|
|
59
|
+
:class="{
|
|
60
|
+
'is-dragging': selectDragSortIndex === index,
|
|
61
|
+
'is-drag-over': selectDragOverSortIndex === index,
|
|
62
|
+
}"
|
|
63
|
+
>
|
|
64
|
+
<div class="report-chapter-workspace__chapter-option">
|
|
65
|
+
<span class="report-chapter-workspace__sort-handle" aria-hidden="true">
|
|
66
|
+
<el-icon>
|
|
67
|
+
<Sort />
|
|
68
|
+
</el-icon>
|
|
69
|
+
</span>
|
|
70
|
+
<span
|
|
71
|
+
class="report-chapter-workspace__chapter-option-title"
|
|
72
|
+
:class="{ 'is-active': index === activeChapterIndex }"
|
|
73
|
+
>
|
|
74
|
+
{{ title }}
|
|
75
|
+
</span>
|
|
76
|
+
<span class="report-chapter-workspace__chapter-option-status">{{
|
|
77
|
+
getChapterStatus(index)
|
|
78
|
+
}}</span>
|
|
79
|
+
</div>
|
|
80
|
+
</el-option>
|
|
81
|
+
<template #empty>
|
|
82
|
+
<div class="report-chapter-workspace__chapter-select-empty" aria-hidden="true" />
|
|
83
|
+
</template>
|
|
84
|
+
<template #footer>
|
|
85
|
+
<div class="report-chapter-workspace__chapter-select-footer">
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-workspace__chapter-select-footer-btn"
|
|
89
|
+
@click="openAddChapterDialog"
|
|
90
|
+
>
|
|
91
|
+
新增章節
|
|
92
|
+
</button>
|
|
93
|
+
<!-- 默認隱藏;下拉內拖動重排後才顯示 -->
|
|
94
|
+
<button
|
|
95
|
+
v-if="showConfirmSortButton"
|
|
96
|
+
type="button"
|
|
97
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-workspace__chapter-select-footer-btn"
|
|
98
|
+
@click="handleConfirmSort"
|
|
99
|
+
>
|
|
100
|
+
確認排序
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
104
|
+
</el-select>
|
|
105
|
+
<div
|
|
106
|
+
v-else
|
|
107
|
+
class="report-chapter-workspace__chapter-select report-chapter-workspace__chapter-select--pending"
|
|
108
|
+
aria-hidden="true"
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
<!-- //0920 -->
|
|
112
|
+
</div>
|
|
113
|
+
<!-- // new prompt sections end -->
|
|
114
|
+
|
|
115
|
+
<div class="report-chapter-workspace__toolbar-right">
|
|
116
|
+
<template v-if="showFinalizeToolbar">
|
|
117
|
+
<el-button class="chatbot-report-btn-primary" @click="handleApplyCreateTemplate">
|
|
118
|
+
申請創建模板
|
|
119
|
+
</el-button>
|
|
120
|
+
<el-button
|
|
121
|
+
class="chatbot-report-btn-primary"
|
|
122
|
+
:loading="isExportingWord"
|
|
123
|
+
@click="handleExportWord1"
|
|
124
|
+
>
|
|
125
|
+
導出(Word-AI)
|
|
126
|
+
</el-button>
|
|
127
|
+
<el-button
|
|
128
|
+
class="chatbot-report-btn-primary"
|
|
129
|
+
:loading="isExportingWord"
|
|
130
|
+
@click="handleExportWord2"
|
|
131
|
+
>
|
|
132
|
+
導出(Word-Transform)
|
|
133
|
+
</el-button>
|
|
134
|
+
<template v-if="showCoherenceResultActions">
|
|
135
|
+
<el-button
|
|
136
|
+
class="chatbot-report-btn-primary chatbot-report-btn-primary--wide"
|
|
137
|
+
@click="handleViewCoherenceCheck"
|
|
138
|
+
>
|
|
139
|
+
查看報告連貫性檢查結果
|
|
140
|
+
</el-button>
|
|
141
|
+
<!-- <el-button
|
|
142
|
+
class="chatbot-report-btn-primary"
|
|
143
|
+
:loading="isExportingWord"
|
|
144
|
+
@click="handleExportWord"
|
|
145
|
+
>
|
|
146
|
+
導出(Word)
|
|
147
|
+
</el-button> -->
|
|
148
|
+
</template>
|
|
149
|
+
</template>
|
|
150
|
+
|
|
151
|
+
<template v-else>
|
|
152
|
+
<!-- 編輯態:取消 | 存為草稿 | 儲存 | 生成報告(max-width 100px、16px) -->
|
|
153
|
+
<button
|
|
154
|
+
v-if="chapterEditMode"
|
|
155
|
+
type="button"
|
|
156
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--outline report-chapter-workspace__action-btn"
|
|
157
|
+
@click="handleCancelEdit"
|
|
158
|
+
>
|
|
159
|
+
取消
|
|
160
|
+
</button>
|
|
161
|
+
<button
|
|
162
|
+
v-if="chapterEditMode"
|
|
163
|
+
type="button"
|
|
164
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-workspace__action-btn"
|
|
165
|
+
@click="handleSaveDraft"
|
|
166
|
+
>
|
|
167
|
+
存為草稿
|
|
168
|
+
</button>
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-workspace__action-btn"
|
|
172
|
+
@click="handleEditSave"
|
|
173
|
+
>
|
|
174
|
+
{{ chapterEditMode ? '儲存' : '編輯/儲存' }}
|
|
175
|
+
</button>
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-workspace__action-btn"
|
|
179
|
+
@click="handleGenerateReport"
|
|
180
|
+
>
|
|
181
|
+
生成報告
|
|
182
|
+
</button>
|
|
183
|
+
</template>
|
|
184
|
+
</div>
|
|
185
|
+
</header>
|
|
186
|
+
|
|
187
|
+
<div
|
|
188
|
+
v-if="isReportSettingsMode"
|
|
189
|
+
ref="wordExportRootRef"
|
|
190
|
+
class="report-chapter-workspace__settings-doc"
|
|
191
|
+
>
|
|
192
|
+
<ReportTypewriterDocEditor
|
|
193
|
+
:chapters="settingsDocChapters"
|
|
194
|
+
:ready="editorContentReady"
|
|
195
|
+
empty-text="請在右側「生成報告設置」勾選至少一個章節"
|
|
196
|
+
:replay-key="settingsTypewriterReplayKey"
|
|
197
|
+
:replay-token="settingsTypewriterReplayToken"
|
|
198
|
+
:disable-typewriter="!reportGenerateStreamActive"
|
|
199
|
+
:typewriter-settle-ms="reportGenerateStreamEnded ? 0 : 1500"
|
|
200
|
+
@typing-change="setSettingsTypingChapterKey"
|
|
201
|
+
@printing-change="setSettingsTypewriterPrinting"
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div v-else ref="wordExportRootRef" class="report-chapter-workspace__editor">
|
|
206
|
+
<!-- //0918-1:contentIdentity=tw-{applyToken} 重启打字机,不 remount -->
|
|
207
|
+
<!-- //0920:静态预览绑冻结快照,版本列表二次套用不再改编辑器 props -->
|
|
208
|
+
<!-- // 0924:stream-pending 流未结束不关打字机 -->
|
|
209
|
+
<ReportChapterTiptapEditor
|
|
210
|
+
v-if="editorContentReady"
|
|
211
|
+
:key="editorKey"
|
|
212
|
+
class="report-chapter-workspace__tiptap"
|
|
213
|
+
:class="{ 'is-paint-ready': editorPaintReady }"
|
|
214
|
+
:chapter-title="editorChapterTitle"
|
|
215
|
+
:chapter-intro="editorChapterIntro"
|
|
216
|
+
:editable="false"
|
|
217
|
+
:disable-typewriter="!chapterTypewriterActive"
|
|
218
|
+
:stream-pending="chapterContentStreamLive"
|
|
219
|
+
:content-identity="editorContentIdentity"
|
|
220
|
+
:show-review-markers="false"
|
|
221
|
+
plain-content
|
|
222
|
+
@typewriter-complete="onChapterTypewriterComplete"
|
|
223
|
+
/>
|
|
224
|
+
<!-- //0920 -->
|
|
225
|
+
<div v-else class="report-chapter-workspace__editor-shell" aria-hidden="true">
|
|
226
|
+
<div class="report-chapter-workspace__editor-shell-title" />
|
|
227
|
+
<div class="report-chapter-workspace__editor-shell-line" />
|
|
228
|
+
<div class="report-chapter-workspace__editor-shell-line is-short" />
|
|
229
|
+
<div class="report-chapter-workspace__editor-shell-line" />
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<el-dialog
|
|
234
|
+
v-model="chapterDialogVisible"
|
|
235
|
+
width="60%"
|
|
236
|
+
append-to-body
|
|
237
|
+
:show-close="false"
|
|
238
|
+
modal-class="report-workflow-dialog-overlay"
|
|
239
|
+
class="report-workflow-dialog report-chapter-dialog"
|
|
240
|
+
header-class="report-workflow-dialog__header"
|
|
241
|
+
@closed="handleChapterDialogClosed"
|
|
242
|
+
>
|
|
243
|
+
<template #header="{ close }">
|
|
244
|
+
<span class="report-workflow-dialog__title">新增章節</span>
|
|
245
|
+
<button
|
|
246
|
+
type="button"
|
|
247
|
+
class="report-workflow-dialog__close"
|
|
248
|
+
aria-label="關閉"
|
|
249
|
+
@click="close"
|
|
250
|
+
>
|
|
251
|
+
×
|
|
252
|
+
</button>
|
|
253
|
+
</template>
|
|
254
|
+
|
|
255
|
+
<div class="report-chapter-dialog__form">
|
|
256
|
+
<label class="report-chapter-dialog__field report-chapter-dialog__field--row">
|
|
257
|
+
<span class="report-chapter-dialog__label">
|
|
258
|
+
章節標題<span class="report-chapter-dialog__required">*</span>
|
|
259
|
+
</span>
|
|
260
|
+
<div class="report-chapter-dialog__control">
|
|
261
|
+
<el-input
|
|
262
|
+
v-model="newChapterForm.title"
|
|
263
|
+
:maxlength="CHAPTER_TITLE_MAX"
|
|
264
|
+
show-word-limit
|
|
265
|
+
class="report-chapter-dialog__input"
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
</label>
|
|
269
|
+
|
|
270
|
+
<label
|
|
271
|
+
class="report-chapter-dialog__field report-chapter-dialog__field--row report-chapter-dialog__field--textarea"
|
|
272
|
+
>
|
|
273
|
+
<span class="report-chapter-dialog__label">
|
|
274
|
+
章節描述<span class="report-chapter-dialog__required">*</span>
|
|
275
|
+
</span>
|
|
276
|
+
<div class="report-chapter-dialog__control">
|
|
277
|
+
<el-input
|
|
278
|
+
v-model="newChapterForm.description"
|
|
279
|
+
type="textarea"
|
|
280
|
+
:rows="4"
|
|
281
|
+
:maxlength="CHAPTER_DESC_MAX"
|
|
282
|
+
show-word-limit
|
|
283
|
+
resize="none"
|
|
284
|
+
class="report-chapter-dialog__textarea"
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
</label>
|
|
288
|
+
|
|
289
|
+
<!-- rows 加大,避免字數統計被 footer 裁切 -->
|
|
290
|
+
<ReportTemplatePromptField
|
|
291
|
+
v-model="newChapterForm.prompt"
|
|
292
|
+
:maxlength="CHAPTER_PROMPT_MAX"
|
|
293
|
+
:rows="8"
|
|
294
|
+
label-width="100px"
|
|
295
|
+
field-gap="16px"
|
|
296
|
+
layout="row"
|
|
297
|
+
@ai-assist="openAiPromptDialog"
|
|
298
|
+
/>
|
|
299
|
+
|
|
300
|
+
<!-- Prompt 下方:資訊源範圍(全部 / 部分豎排 + 選擇按鈕) -->
|
|
301
|
+
<div
|
|
302
|
+
class="report-chapter-dialog__field report-chapter-dialog__field--row report-chapter-dialog__field--source"
|
|
303
|
+
>
|
|
304
|
+
<span class="report-chapter-dialog__label">
|
|
305
|
+
資訊源<span class="report-chapter-dialog__required">*</span>
|
|
306
|
+
</span>
|
|
307
|
+
<div class="report-chapter-dialog__control report-chapter-dialog__source-control">
|
|
308
|
+
<el-radio-group
|
|
309
|
+
v-model="newChapterForm.sourceMode"
|
|
310
|
+
class="report-chapter-dialog__source-radios"
|
|
311
|
+
>
|
|
312
|
+
<el-radio value="all">所有資訊源檔案</el-radio>
|
|
313
|
+
<div class="report-chapter-dialog__source-partial-row">
|
|
314
|
+
<el-radio value="partial">部分資訊源檔案</el-radio>
|
|
315
|
+
<a
|
|
316
|
+
v-if="newChapterForm.sourceMode === 'partial'"
|
|
317
|
+
href="javascript:void(0)"
|
|
318
|
+
class="report-chapter-dialog__source-link"
|
|
319
|
+
@click.prevent="openChapterSourcePicker"
|
|
320
|
+
>
|
|
321
|
+
選擇資訊源
|
|
322
|
+
</a>
|
|
323
|
+
<span
|
|
324
|
+
v-if="
|
|
325
|
+
newChapterForm.sourceMode === 'partial' &&
|
|
326
|
+
newChapterForm.selectedSourceIds.length
|
|
327
|
+
"
|
|
328
|
+
class="report-chapter-dialog__source-hint"
|
|
329
|
+
>
|
|
330
|
+
已選 {{ newChapterForm.selectedSourceIds.length }} 個
|
|
331
|
+
</span>
|
|
332
|
+
</div>
|
|
333
|
+
</el-radio-group>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<template #footer>
|
|
339
|
+
<div class="report-chapter-dialog__footer">
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--outline report-chapter-dialog__footer-btn"
|
|
343
|
+
@click="closeChapterDialog"
|
|
344
|
+
>
|
|
345
|
+
取消
|
|
346
|
+
</button>
|
|
347
|
+
<button
|
|
348
|
+
type="button"
|
|
349
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-dialog__footer-btn"
|
|
350
|
+
@click="handleAddChapterSubmit"
|
|
351
|
+
>
|
|
352
|
+
提交並生成章節內容
|
|
353
|
+
</button>
|
|
354
|
+
</div>
|
|
355
|
+
</template>
|
|
356
|
+
</el-dialog>
|
|
357
|
+
|
|
358
|
+
<!-- 新增章節:部分資訊源時選擇檔案 -->
|
|
359
|
+
<el-dialog
|
|
360
|
+
v-model="chapterSourcePickerVisible"
|
|
361
|
+
width="520px"
|
|
362
|
+
append-to-body
|
|
363
|
+
:show-close="false"
|
|
364
|
+
class="report-chapter-source-picker"
|
|
365
|
+
header-class="report-workflow-dialog__header"
|
|
366
|
+
>
|
|
367
|
+
<template #header="{ close }">
|
|
368
|
+
<span class="report-workflow-dialog__title">選擇資訊源</span>
|
|
369
|
+
<button
|
|
370
|
+
type="button"
|
|
371
|
+
class="report-workflow-dialog__close"
|
|
372
|
+
aria-label="關閉"
|
|
373
|
+
@click="close"
|
|
374
|
+
>
|
|
375
|
+
×
|
|
376
|
+
</button>
|
|
377
|
+
</template>
|
|
378
|
+
|
|
379
|
+
<div class="report-chapter-source-picker__body">
|
|
380
|
+
<el-checkbox
|
|
381
|
+
v-model="chapterSourcePickerCheckAll"
|
|
382
|
+
:indeterminate="chapterSourcePickerIndeterminate"
|
|
383
|
+
@change="onChapterSourceCheckAll"
|
|
384
|
+
>
|
|
385
|
+
全選
|
|
386
|
+
</el-checkbox>
|
|
387
|
+
<el-checkbox-group
|
|
388
|
+
v-model="chapterSourcePickerDraft"
|
|
389
|
+
class="report-chapter-source-picker__list"
|
|
390
|
+
>
|
|
391
|
+
<el-checkbox
|
|
392
|
+
v-for="file in sourceFiles"
|
|
393
|
+
:key="file.id"
|
|
394
|
+
:value="file.id"
|
|
395
|
+
class="report-chapter-source-picker__item"
|
|
396
|
+
>
|
|
397
|
+
{{ file.name }}
|
|
398
|
+
</el-checkbox>
|
|
399
|
+
</el-checkbox-group>
|
|
400
|
+
<p v-if="!sourceFiles.length" class="report-chapter-source-picker__empty">暫無資訊源檔案</p>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<template #footer>
|
|
404
|
+
<div class="report-chapter-dialog__footer">
|
|
405
|
+
<button
|
|
406
|
+
type="button"
|
|
407
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--outline report-chapter-dialog__footer-btn"
|
|
408
|
+
@click="chapterSourcePickerVisible = false"
|
|
409
|
+
>
|
|
410
|
+
取消
|
|
411
|
+
</button>
|
|
412
|
+
<button
|
|
413
|
+
type="button"
|
|
414
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary report-chapter-dialog__footer-btn"
|
|
415
|
+
@click="confirmChapterSourcePicker"
|
|
416
|
+
>
|
|
417
|
+
確定
|
|
418
|
+
</button>
|
|
419
|
+
</div>
|
|
420
|
+
</template>
|
|
421
|
+
</el-dialog>
|
|
422
|
+
|
|
423
|
+
<el-dialog
|
|
424
|
+
v-model="applyTemplateDialogVisible"
|
|
425
|
+
width="80%"
|
|
426
|
+
append-to-body
|
|
427
|
+
:z-index="10000"
|
|
428
|
+
:show-close="false"
|
|
429
|
+
modal-class="apply-template-overlay"
|
|
430
|
+
class="apply-template-dialog"
|
|
431
|
+
header-class="apply-template-dialog__header"
|
|
432
|
+
@closed="resetApplyTemplateForm"
|
|
433
|
+
>
|
|
434
|
+
<template #header="{ close, titleId, titleClass }">
|
|
435
|
+
<span :id="titleId" :class="['apply-template-dialog__title', titleClass]"
|
|
436
|
+
>申請創建模板</span
|
|
437
|
+
>
|
|
438
|
+
<button type="button" class="apply-template-dialog__close" aria-label="關閉" @click="close">
|
|
439
|
+
×
|
|
440
|
+
</button>
|
|
441
|
+
</template>
|
|
442
|
+
|
|
443
|
+
<el-scrollbar v-if="applyTemplateDetail" class="apply-template-dialog__scroll">
|
|
444
|
+
<div class="apply-template-form">
|
|
445
|
+
<div class="apply-template-meta">
|
|
446
|
+
<label class="apply-template-field apply-template-field--row">
|
|
447
|
+
<span class="apply-template-label">來源報告</span>
|
|
448
|
+
<div class="apply-template-field__control">
|
|
449
|
+
<span class="apply-template-source">{{ applyTemplateDetail.sourceReport }}</span>
|
|
450
|
+
</div>
|
|
451
|
+
</label>
|
|
452
|
+
|
|
453
|
+
<label class="apply-template-field apply-template-field--row">
|
|
454
|
+
<span class="apply-template-label"
|
|
455
|
+
>主要語言<span class="apply-template-label__required">*</span></span
|
|
456
|
+
>
|
|
457
|
+
<div class="apply-template-field__control apply-template-field__control--lang">
|
|
458
|
+
<el-input
|
|
459
|
+
:model-value="applyTemplateLanguageDisplay"
|
|
460
|
+
disabled
|
|
461
|
+
class="apply-template-field__input"
|
|
462
|
+
/>
|
|
463
|
+
</div>
|
|
464
|
+
</label>
|
|
465
|
+
|
|
466
|
+
<label class="apply-template-field apply-template-field--row">
|
|
467
|
+
<span class="apply-template-label"
|
|
468
|
+
>模板標題<span class="apply-template-label__required">*</span></span
|
|
469
|
+
>
|
|
470
|
+
<div class="apply-template-field__control">
|
|
471
|
+
<el-input
|
|
472
|
+
v-model="applyTemplateTitle"
|
|
473
|
+
:maxlength="100"
|
|
474
|
+
show-word-limit
|
|
475
|
+
class="apply-template-field__input"
|
|
476
|
+
/>
|
|
477
|
+
</div>
|
|
478
|
+
</label>
|
|
479
|
+
|
|
480
|
+
<label
|
|
481
|
+
class="apply-template-field apply-template-field--row apply-template-field--textarea-row"
|
|
482
|
+
>
|
|
483
|
+
<span class="apply-template-label"
|
|
484
|
+
>模板描述<span class="apply-template-label__required">*</span></span
|
|
485
|
+
>
|
|
486
|
+
<div class="apply-template-field__control">
|
|
487
|
+
<el-input
|
|
488
|
+
v-model="applyTemplateDescription"
|
|
489
|
+
type="textarea"
|
|
490
|
+
:rows="4"
|
|
491
|
+
:maxlength="3000"
|
|
492
|
+
show-word-limit
|
|
493
|
+
resize="none"
|
|
494
|
+
class="apply-template-field__textarea"
|
|
495
|
+
/>
|
|
496
|
+
</div>
|
|
497
|
+
</label>
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
<div
|
|
501
|
+
v-for="chapter in applyTemplateDisplayChapters"
|
|
502
|
+
:key="chapter.id"
|
|
503
|
+
class="apply-template-chapter"
|
|
504
|
+
>
|
|
505
|
+
<h4 class="apply-template-chapter__title">{{ chapter.title || '章節標題' }}</h4>
|
|
506
|
+
|
|
507
|
+
<label
|
|
508
|
+
class="apply-template-field apply-template-field--row apply-template-field--textarea-row"
|
|
509
|
+
>
|
|
510
|
+
<span class="apply-template-label">章節Prompt</span>
|
|
511
|
+
<div class="apply-template-field__control">
|
|
512
|
+
<el-input
|
|
513
|
+
:model-value="chapter.prompt"
|
|
514
|
+
type="textarea"
|
|
515
|
+
:rows="8"
|
|
516
|
+
:maxlength="1300"
|
|
517
|
+
show-word-limit
|
|
518
|
+
resize="none"
|
|
519
|
+
disabled
|
|
520
|
+
class="apply-template-field__textarea"
|
|
521
|
+
/>
|
|
522
|
+
</div>
|
|
523
|
+
</label>
|
|
524
|
+
|
|
525
|
+
<label
|
|
526
|
+
class="apply-template-field apply-template-field--row apply-template-field--textarea-row"
|
|
527
|
+
>
|
|
528
|
+
<span class="apply-template-label">章節描述</span>
|
|
529
|
+
<div class="apply-template-field__control">
|
|
530
|
+
<el-input
|
|
531
|
+
:model-value="chapter.description"
|
|
532
|
+
type="textarea"
|
|
533
|
+
:rows="8"
|
|
534
|
+
:maxlength="1000"
|
|
535
|
+
show-word-limit
|
|
536
|
+
resize="none"
|
|
537
|
+
disabled
|
|
538
|
+
class="apply-template-field__textarea"
|
|
539
|
+
/>
|
|
540
|
+
</div>
|
|
541
|
+
</label>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<section class="apply-template-process">
|
|
545
|
+
<label
|
|
546
|
+
class="apply-template-field apply-template-field--row apply-template-process__row"
|
|
547
|
+
>
|
|
548
|
+
<span class="apply-template-label"
|
|
549
|
+
>處理事項<span class="apply-template-label__required">*</span></span
|
|
550
|
+
>
|
|
551
|
+
<div class="apply-template-field__control">
|
|
552
|
+
<el-radio-group
|
|
553
|
+
v-model="applyTemplateAction"
|
|
554
|
+
class="apply-template-process__radios"
|
|
555
|
+
>
|
|
556
|
+
<el-radio value="reject">拒絕</el-radio>
|
|
557
|
+
<el-radio value="approve">同意</el-radio>
|
|
558
|
+
</el-radio-group>
|
|
559
|
+
</div>
|
|
560
|
+
</label>
|
|
561
|
+
|
|
562
|
+
<label
|
|
563
|
+
class="apply-template-field apply-template-field--row apply-template-field--textarea-row"
|
|
564
|
+
>
|
|
565
|
+
<span class="apply-template-label">意見</span>
|
|
566
|
+
<div class="apply-template-field__control">
|
|
567
|
+
<el-input
|
|
568
|
+
v-model="applyTemplateOpinion"
|
|
569
|
+
type="textarea"
|
|
570
|
+
:rows="4"
|
|
571
|
+
:maxlength="250"
|
|
572
|
+
show-word-limit
|
|
573
|
+
resize="none"
|
|
574
|
+
class="apply-template-field__textarea"
|
|
575
|
+
/>
|
|
576
|
+
</div>
|
|
577
|
+
</label>
|
|
578
|
+
|
|
579
|
+
<div class="apply-template-process__actions">
|
|
580
|
+
<button
|
|
581
|
+
type="button"
|
|
582
|
+
class="apply-template-btn apply-template-btn--ghost"
|
|
583
|
+
@click="applyTemplateDialogVisible = false"
|
|
584
|
+
>
|
|
585
|
+
取消
|
|
586
|
+
</button>
|
|
587
|
+
<button
|
|
588
|
+
type="button"
|
|
589
|
+
class="apply-template-btn apply-template-btn--primary"
|
|
590
|
+
@click="handleApplyTemplateSubmit"
|
|
591
|
+
>
|
|
592
|
+
提交
|
|
593
|
+
</button>
|
|
594
|
+
</div>
|
|
595
|
+
</section>
|
|
596
|
+
|
|
597
|
+
<section class="apply-template-approval">
|
|
598
|
+
<h4 class="apply-template-approval__title">審批明細</h4>
|
|
599
|
+
<el-table
|
|
600
|
+
:data="applyTemplateDetail.approvalRecords"
|
|
601
|
+
border
|
|
602
|
+
class="apply-template-approval-table"
|
|
603
|
+
>
|
|
604
|
+
<el-table-column label="操作人員" min-width="180" prop="operator" />
|
|
605
|
+
<el-table-column label="處理狀態" width="120" prop="status" />
|
|
606
|
+
<el-table-column label="處理完成時間" min-width="180" prop="completedAt" />
|
|
607
|
+
<el-table-column label="詳情" min-width="200" prop="detail" show-overflow-tooltip />
|
|
608
|
+
</el-table>
|
|
609
|
+
</section>
|
|
610
|
+
</div>
|
|
611
|
+
</el-scrollbar>
|
|
612
|
+
</el-dialog>
|
|
613
|
+
|
|
614
|
+
<el-dialog
|
|
615
|
+
v-model="aiPromptDialogVisible"
|
|
616
|
+
width="60%"
|
|
617
|
+
append-to-body
|
|
618
|
+
:show-close="false"
|
|
619
|
+
modal-class="report-workflow-dialog-overlay"
|
|
620
|
+
class="report-workflow-dialog ai-prompt-generate-dialog"
|
|
621
|
+
header-class="report-workflow-dialog__header"
|
|
622
|
+
@closed="handleAiPromptDialogClosed"
|
|
623
|
+
>
|
|
624
|
+
<template #header="{ close }">
|
|
625
|
+
<span class="report-workflow-dialog__title">{{ aiPromptDialogTitle }}</span>
|
|
626
|
+
<button
|
|
627
|
+
type="button"
|
|
628
|
+
class="report-workflow-dialog__close"
|
|
629
|
+
aria-label="關閉"
|
|
630
|
+
@click="close"
|
|
631
|
+
>
|
|
632
|
+
×
|
|
633
|
+
</button>
|
|
634
|
+
</template>
|
|
635
|
+
|
|
636
|
+
<div v-if="aiPromptDialogMode === 'input'" class="ai-prompt-generate-dialog__form">
|
|
637
|
+
<label class="ai-prompt-generate-dialog__field ai-prompt-generate-dialog__field--row">
|
|
638
|
+
<span class="ai-prompt-generate-dialog__label">章節標題</span>
|
|
639
|
+
<div class="ai-prompt-generate-dialog__control">
|
|
640
|
+
<el-input
|
|
641
|
+
v-model="aiPromptForm.title"
|
|
642
|
+
:maxlength="AI_PROMPT_TITLE_MAX"
|
|
643
|
+
show-word-limit
|
|
644
|
+
class="ai-prompt-generate-dialog__input"
|
|
645
|
+
/>
|
|
646
|
+
</div>
|
|
647
|
+
</label>
|
|
648
|
+
|
|
649
|
+
<label
|
|
650
|
+
class="ai-prompt-generate-dialog__field ai-prompt-generate-dialog__field--row ai-prompt-generate-dialog__field--textarea"
|
|
651
|
+
>
|
|
652
|
+
<span class="ai-prompt-generate-dialog__label">章節描述</span>
|
|
653
|
+
<div class="ai-prompt-generate-dialog__control">
|
|
654
|
+
<el-input
|
|
655
|
+
v-model="aiPromptForm.description"
|
|
656
|
+
type="textarea"
|
|
657
|
+
:rows="4"
|
|
658
|
+
:maxlength="AI_PROMPT_DESC_MAX"
|
|
659
|
+
show-word-limit
|
|
660
|
+
resize="none"
|
|
661
|
+
class="ai-prompt-generate-dialog__textarea"
|
|
662
|
+
/>
|
|
663
|
+
</div>
|
|
664
|
+
</label>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<div v-else class="ai-prompt-generate-dialog__preview">
|
|
668
|
+
<div class="ai-prompt-generate-dialog__preview-row">
|
|
669
|
+
<span class="ai-prompt-generate-dialog__label">章節標題</span>
|
|
670
|
+
<p class="ai-prompt-generate-dialog__preview-text">{{ aiPromptForm.title }}</p>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
<div class="ai-prompt-generate-dialog__preview-row">
|
|
674
|
+
<span class="ai-prompt-generate-dialog__label">章節描述</span>
|
|
675
|
+
<p
|
|
676
|
+
class="ai-prompt-generate-dialog__preview-text ai-prompt-generate-dialog__preview-text--multiline"
|
|
677
|
+
>
|
|
678
|
+
{{ aiPromptForm.description }}
|
|
679
|
+
</p>
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
<div class="ai-prompt-generate-dialog__divider" aria-hidden="true" />
|
|
683
|
+
|
|
684
|
+
<div
|
|
685
|
+
class="ai-prompt-generate-dialog__preview-row ai-prompt-generate-dialog__preview-row--prompt"
|
|
686
|
+
>
|
|
687
|
+
<span class="ai-prompt-generate-dialog__label">生成的 prompt</span>
|
|
688
|
+
<p
|
|
689
|
+
class="ai-prompt-generate-dialog__preview-text ai-prompt-generate-dialog__preview-text--multiline"
|
|
690
|
+
>
|
|
691
|
+
{{ aiPromptGeneratedText }}
|
|
692
|
+
</p>
|
|
693
|
+
</div>
|
|
694
|
+
</div>
|
|
695
|
+
|
|
696
|
+
<template #footer>
|
|
697
|
+
<div class="ai-prompt-generate-dialog__footer">
|
|
698
|
+
<button
|
|
699
|
+
v-if="aiPromptDialogMode === 'input'"
|
|
700
|
+
type="button"
|
|
701
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--outline ai-prompt-generate-dialog__footer-btn"
|
|
702
|
+
@click="closeAiPromptDialog"
|
|
703
|
+
>
|
|
704
|
+
取消
|
|
705
|
+
</button>
|
|
706
|
+
<button
|
|
707
|
+
v-else
|
|
708
|
+
type="button"
|
|
709
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--outline ai-prompt-generate-dialog__footer-btn"
|
|
710
|
+
@click="aiPromptDialogMode = 'input'"
|
|
711
|
+
>
|
|
712
|
+
退回
|
|
713
|
+
</button>
|
|
714
|
+
|
|
715
|
+
<button
|
|
716
|
+
v-if="aiPromptDialogMode === 'input'"
|
|
717
|
+
type="button"
|
|
718
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary ai-prompt-generate-dialog__footer-btn"
|
|
719
|
+
:disabled="aiPromptGenerating"
|
|
720
|
+
@click="generateAiPrompt"
|
|
721
|
+
>
|
|
722
|
+
{{ aiPromptGenerating ? '生成中' : '生成 Prompt' }}
|
|
723
|
+
</button>
|
|
724
|
+
<button
|
|
725
|
+
v-else
|
|
726
|
+
type="button"
|
|
727
|
+
class="template-center-toolbar-btn gen-ai-action-btn template-center-toolbar-btn--primary ai-prompt-generate-dialog__footer-btn"
|
|
728
|
+
@click="confirmAiPrompt"
|
|
729
|
+
>
|
|
730
|
+
確認
|
|
731
|
+
</button>
|
|
732
|
+
</div>
|
|
733
|
+
</template>
|
|
734
|
+
</el-dialog>
|
|
735
|
+
</section>
|
|
736
|
+
</template>
|
|
737
|
+
|
|
738
|
+
<script setup lang="ts">
|
|
739
|
+
import genAiReportEditorApi, {
|
|
740
|
+
pickAdjustInstanceText,
|
|
741
|
+
pickExistsInstanceVersionNo,
|
|
742
|
+
} from '@/api/modules/build-report/reportEditor'
|
|
743
|
+
import genAiReportPermissionApi from '@/api/modules/build-report/reportPermission'
|
|
744
|
+
import { useGenAiBuildReportStore } from '@/store/modules/gen-ai'
|
|
745
|
+
import type {
|
|
746
|
+
GenAiAdjustInstanceContent,
|
|
747
|
+
GenAiAdjustInstanceData,
|
|
748
|
+
GenAiAdjustInstanceFileItem,
|
|
749
|
+
GenAiAdjustInstanceSection,
|
|
750
|
+
GenAiGetSectionListParams,
|
|
751
|
+
GenAiReportSectionItem,
|
|
752
|
+
GenAiSectionLatestStatus,
|
|
753
|
+
GenAiSectionVersionItem,
|
|
754
|
+
} from '@/types/gen-ai/build-report/reportEditor'
|
|
755
|
+
import {
|
|
756
|
+
DOCX_HEADER_CLASSIFICATION,
|
|
757
|
+
exportMarkdownToDocxWord,
|
|
758
|
+
} from '../../components/AIExport'
|
|
759
|
+
import { Sort } from '@element-plus/icons-vue'
|
|
760
|
+
import type { SelectInstance } from 'element-plus'
|
|
761
|
+
import {
|
|
762
|
+
ElButton,
|
|
763
|
+
ElCheckbox,
|
|
764
|
+
ElCheckboxGroup,
|
|
765
|
+
ElDialog,
|
|
766
|
+
ElIcon,
|
|
767
|
+
ElInput,
|
|
768
|
+
ElMessage,
|
|
769
|
+
ElMessageBox,
|
|
770
|
+
ElOption,
|
|
771
|
+
ElRadio,
|
|
772
|
+
ElRadioGroup,
|
|
773
|
+
ElScrollbar,
|
|
774
|
+
ElSelect,
|
|
775
|
+
ElTable,
|
|
776
|
+
ElTableColumn,
|
|
777
|
+
} from 'element-plus'
|
|
778
|
+
import Sortable from 'sortablejs'
|
|
779
|
+
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
|
780
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
781
|
+
import {
|
|
782
|
+
ReportChapterTiptapEditor,
|
|
783
|
+
PromptField as ReportTemplatePromptField,
|
|
784
|
+
ReportTypewriterDocEditor,
|
|
785
|
+
} from '../../common'
|
|
786
|
+
import { notifyChartStreamRefsUpdated } from '../chartStreamRefs' // 0924
|
|
787
|
+
import { isGenAiResponseOk } from '../../common/utils/api-res-status'
|
|
788
|
+
import {
|
|
789
|
+
buildApplyTemplateDetailFromReport,
|
|
790
|
+
findFinalizedReportById,
|
|
791
|
+
getApplyTemplateDisplayChapters,
|
|
792
|
+
getApplyTemplateLanguageDisplay,
|
|
793
|
+
getReportLanguageLabel,
|
|
794
|
+
loadCoherenceCheckSession,
|
|
795
|
+
openReportCoherenceCheckResult,
|
|
796
|
+
stripChapterNumberPrefix,
|
|
797
|
+
translateChapterLabel,
|
|
798
|
+
translateChapterTitles,
|
|
799
|
+
type ApplyTemplateDetail,
|
|
800
|
+
type ApplyTemplateProcessAction,
|
|
801
|
+
} from '../report-domain'
|
|
802
|
+
import {
|
|
803
|
+
CHAPTER_DRAFT_HISTORY_TITLE,
|
|
804
|
+
createDefaultPromptChapterForm,
|
|
805
|
+
registerChapterHistoryReloader,
|
|
806
|
+
useReportWorkflow,
|
|
807
|
+
type PromptChapterForm,
|
|
808
|
+
type ReportHistoryItem,
|
|
809
|
+
} from '../useReportWorkflow'
|
|
810
|
+
|
|
811
|
+
const genAiBuildReportStore = useGenAiBuildReportStore()
|
|
812
|
+
const route = useRoute()
|
|
813
|
+
|
|
814
|
+
function resolveInstanceId() {
|
|
815
|
+
return String(genAiBuildReportStore.instanceId ?? '').trim()
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const CHAPTER_PANEL_MAX_ROWS = 14
|
|
819
|
+
const CHAPTER_PANEL_ROW_HEIGHT = 40
|
|
820
|
+
const CHAPTER_TITLE_MAX = 100
|
|
821
|
+
const CHAPTER_DESC_MAX = 1000
|
|
822
|
+
const CHAPTER_PROMPT_MAX = 1300
|
|
823
|
+
const AI_PROMPT_TITLE_MAX = 100
|
|
824
|
+
const AI_PROMPT_DESC_MAX = 1000
|
|
825
|
+
|
|
826
|
+
type AiPromptDialogMode = 'input' | 'preview'
|
|
827
|
+
|
|
828
|
+
const aiPromptDialogVisible = ref(false)
|
|
829
|
+
const aiPromptDialogMode = ref<AiPromptDialogMode>('input')
|
|
830
|
+
const aiPromptGeneratedText = ref('')
|
|
831
|
+
const aiPromptGenerating = ref(false)
|
|
832
|
+
const aiPromptForm = reactive({
|
|
833
|
+
title: '',
|
|
834
|
+
description: '',
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const aiPromptDialogTitle = computed(() =>
|
|
838
|
+
aiPromptDialogMode.value === 'input' ? '智能化生成 Prompt' : '智能化生成 Prompt 預覽'
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
function resetAiPromptDialogForm(initialTitle = '', initialDescription = '') {
|
|
842
|
+
aiPromptForm.title = initialTitle.trim()
|
|
843
|
+
aiPromptForm.description = initialDescription.trim()
|
|
844
|
+
aiPromptGeneratedText.value = ''
|
|
845
|
+
aiPromptDialogMode.value = 'input'
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function openAiPromptDialog() {
|
|
849
|
+
requestOpenAiPromptDialog('add-chapter')
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function closeAiPromptDialog() {
|
|
853
|
+
aiPromptDialogVisible.value = false
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function handleAiPromptDialogClosed() {
|
|
857
|
+
aiPromptGenerating.value = false
|
|
858
|
+
resetAiPromptDialogForm()
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const {
|
|
862
|
+
draft,
|
|
863
|
+
selectedTemplate,
|
|
864
|
+
generatedChapterBodies,
|
|
865
|
+
patchGeneratedChapterBody,
|
|
866
|
+
generatedChapterTitles,
|
|
867
|
+
sourceFiles,
|
|
868
|
+
uiShowFinalizeToolbar,
|
|
869
|
+
uiShowChapterEditToolbar,
|
|
870
|
+
uiShowSettingsDoc,
|
|
871
|
+
chapterEditMode,
|
|
872
|
+
historyPreviewDirty,
|
|
873
|
+
historyPreviewRecordId,
|
|
874
|
+
historyPreviewApplyToken,
|
|
875
|
+
historyPreviewChapterBodies,
|
|
876
|
+
resetHistoryPreviewState,
|
|
877
|
+
resetWorkspaceHistoryOnEnter,
|
|
878
|
+
chapterHistoryItems,
|
|
879
|
+
chapterHistoryOpen,
|
|
880
|
+
chapterHistoryPreviewBody,
|
|
881
|
+
chapterHistoryApplyToken,
|
|
882
|
+
activeChapterHistoryKey,
|
|
883
|
+
enterChapterEditMode,
|
|
884
|
+
commitChapterEdit,
|
|
885
|
+
exitChapterEditMode,
|
|
886
|
+
isChapterEditDirty,
|
|
887
|
+
promptChapterForm,
|
|
888
|
+
saveDraftToSession,
|
|
889
|
+
aiPromptDialogRequestToken,
|
|
890
|
+
aiPromptDialogSource,
|
|
891
|
+
requestOpenAiPromptDialog,
|
|
892
|
+
settingsChapterPresets,
|
|
893
|
+
settingsSelectedChapters,
|
|
894
|
+
settingsChapterOrder,
|
|
895
|
+
settingsChapterVersions,
|
|
896
|
+
settingsTypewriterReplayKey,
|
|
897
|
+
settingsTypewriterReplayToken,
|
|
898
|
+
setSettingsTypingChapterKey,
|
|
899
|
+
setSettingsTypewriterPrinting,
|
|
900
|
+
chapterTypewriterActive,
|
|
901
|
+
chapterContentAwaitingFirstChunk,
|
|
902
|
+
chapterContentStreamLive,
|
|
903
|
+
activeChapterSectionId,
|
|
904
|
+
stopChapterContentTypewriter,
|
|
905
|
+
requestSettingsTypewriterReplay,
|
|
906
|
+
reportGenerateStreamText,
|
|
907
|
+
reportGenerateStreamActive,
|
|
908
|
+
reportGenerateStreamEnded,
|
|
909
|
+
clearReportGenerateStream,
|
|
910
|
+
ensureSettingsChapterLayout,
|
|
911
|
+
applyAdjustInstanceSections,
|
|
912
|
+
applyAdjustInstanceFiles,
|
|
913
|
+
clearAdjustInstanceBindings,
|
|
914
|
+
adjustInstanceContents,
|
|
915
|
+
setAdjustInstanceContents,
|
|
916
|
+
setExistsInstanceVersionNo,
|
|
917
|
+
coherenceResultReady,
|
|
918
|
+
setCoherenceCheckResultReady,
|
|
919
|
+
markAdjustInstanceSettingSynced,
|
|
920
|
+
beginSuppressAdjustInstanceSettingWatch,
|
|
921
|
+
endSuppressAdjustInstanceSettingWatch,
|
|
922
|
+
existsInstanceVersionNo,
|
|
923
|
+
resolveChapterChartAuditEventId,
|
|
924
|
+
upsertChapterChartStreamRefsFromSources,
|
|
925
|
+
} = useReportWorkflow()
|
|
926
|
+
|
|
927
|
+
/** 先渲染工具列,再掛載 TipTap,避免進頁中欄空白 */
|
|
928
|
+
const editorContentReady = ref(false)
|
|
929
|
+
//0920:首帧灌文完成后再显示,避免 remount/滚动条挤宽造成「小一号→再正常」闪三次
|
|
930
|
+
const editorPaintReady = ref(false)
|
|
931
|
+
//0920
|
|
932
|
+
|
|
933
|
+
const router = useRouter()
|
|
934
|
+
|
|
935
|
+
/** 接口章節下拉(getSectionList);有數據時優先驅動下拉,覆蓋 mock/模板 */
|
|
936
|
+
const genAiSectionItems = ref<GenAiReportSectionItem[]>([])
|
|
937
|
+
const chapterTitles = ref<string[]>([])
|
|
938
|
+
const activeChapterIndex = ref(0)
|
|
939
|
+
// 0924:当前章节 sectionId,供生成章节内容入参
|
|
940
|
+
watch(
|
|
941
|
+
() => genAiSectionItems.value[activeChapterIndex.value]?.sectionId ?? '',
|
|
942
|
+
(sectionId) => {
|
|
943
|
+
activeChapterSectionId.value = String(sectionId || '').trim()
|
|
944
|
+
},
|
|
945
|
+
{ immediate: true }
|
|
946
|
+
)
|
|
947
|
+
// 0924
|
|
948
|
+
/** //0920:章节下拉标题就绪后再挂 el-select,避免 wrapper 闪动 */
|
|
949
|
+
const chapterSelectReady = ref(false)
|
|
950
|
+
//0920
|
|
951
|
+
/** 進頁拉列表時抑制 watch(activeChapterIndex) 重複打版本接口 */
|
|
952
|
+
let suppressSectionVersionReload = false
|
|
953
|
+
|
|
954
|
+
//0920:同内容不重写 chapterTitles,避免 el-select 选项整表重挂
|
|
955
|
+
function setChapterTitlesIfChanged(next: string[]) {
|
|
956
|
+
const prev = chapterTitles.value
|
|
957
|
+
if (prev.length === next.length && prev.every((title, i) => title === next[i])) return
|
|
958
|
+
chapterTitles.value = next
|
|
959
|
+
}
|
|
960
|
+
//0920
|
|
961
|
+
|
|
962
|
+
watch(aiPromptDialogRequestToken, (token) => {
|
|
963
|
+
if (!token) return
|
|
964
|
+
if (aiPromptDialogSource.value === 'prompt-panel') {
|
|
965
|
+
const section = genAiSectionItems.value[activeChapterIndex.value]
|
|
966
|
+
resetAiPromptDialogForm(
|
|
967
|
+
section?.sectionName ||
|
|
968
|
+
stripChapterNumberPrefix(chapterTitles.value[activeChapterIndex.value] || ''),
|
|
969
|
+
section?.sectionDesc || ''
|
|
970
|
+
)
|
|
971
|
+
} else {
|
|
972
|
+
resetAiPromptDialogForm(newChapterForm.title, newChapterForm.description)
|
|
973
|
+
}
|
|
974
|
+
aiPromptDialogVisible.value = true
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
async function generateAiPrompt() {
|
|
978
|
+
const title = aiPromptForm.title.trim()
|
|
979
|
+
const description = aiPromptForm.description.trim()
|
|
980
|
+
|
|
981
|
+
if (!title) {
|
|
982
|
+
ElMessage.warning('請輸入章節標題')
|
|
983
|
+
return
|
|
984
|
+
}
|
|
985
|
+
if (!description) {
|
|
986
|
+
ElMessage.warning('請輸入章節描述')
|
|
987
|
+
return
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
aiPromptGenerating.value = true
|
|
991
|
+
const res: any = await genAiReportEditorApi
|
|
992
|
+
.adjustGenAiInstance({
|
|
993
|
+
instanceId: resolveInstanceId(),
|
|
994
|
+
promptType: 'AI_PROMPT',
|
|
995
|
+
sectionTitle: title,
|
|
996
|
+
sectionDesc: description,
|
|
997
|
+
})
|
|
998
|
+
.catch((err) => {
|
|
999
|
+
console.error('接口异常', err)
|
|
1000
|
+
return null
|
|
1001
|
+
})
|
|
1002
|
+
aiPromptGenerating.value = false
|
|
1003
|
+
|
|
1004
|
+
try {
|
|
1005
|
+
if (!isGenAiResponseOk(res)) {
|
|
1006
|
+
ElMessage.warning(res?.message || '接口异常')
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
1009
|
+
const text = pickAdjustInstanceText(res?.data)
|
|
1010
|
+
if (!text) {
|
|
1011
|
+
ElMessage.warning('接口异常')
|
|
1012
|
+
return
|
|
1013
|
+
}
|
|
1014
|
+
aiPromptGeneratedText.value = text
|
|
1015
|
+
aiPromptDialogMode.value = 'preview'
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
console.error('接口异常', err)
|
|
1018
|
+
ElMessage.warning('接口异常')
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function confirmAiPrompt() {
|
|
1023
|
+
if (aiPromptDialogSource.value === 'prompt-panel') {
|
|
1024
|
+
promptChapterForm.prompt = aiPromptGeneratedText.value
|
|
1025
|
+
} else {
|
|
1026
|
+
newChapterForm.prompt = aiPromptGeneratedText.value
|
|
1027
|
+
}
|
|
1028
|
+
closeAiPromptDialog()
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const SECTION_STATUS_LABEL: Record<GenAiSectionLatestStatus, string> = {
|
|
1032
|
+
PENDING: '未生成',
|
|
1033
|
+
EDITING: '編輯中',
|
|
1034
|
+
CREATED: '已生成',
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function formatGenAiSectionTitle(item: GenAiReportSectionItem) {
|
|
1038
|
+
// 圖中展示:「1. 摘要與本年總結」= orderSeq + sectionName
|
|
1039
|
+
return `${item.orderSeq}. ${item.sectionName}`
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function applyGenAiSectionList(list: GenAiReportSectionItem[]) {
|
|
1043
|
+
const sorted = [...list].sort((a, b) => a.orderSeq - b.orderSeq)
|
|
1044
|
+
genAiSectionItems.value = sorted
|
|
1045
|
+
// 以接口為準完整覆蓋下拉標題
|
|
1046
|
+
//0920
|
|
1047
|
+
setChapterTitlesIfChanged(sorted.map(formatGenAiSectionTitle))
|
|
1048
|
+
//0920:已是 0 勿再賦值,避免觸發 watch(activeChapterIndex) 二次拉版本造成中欄閃屏
|
|
1049
|
+
if (activeChapterIndex.value !== 0) activeChapterIndex.value = 0
|
|
1050
|
+
//0920
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/** 進頁 / 失敗:清空 mock 章節歷史與 Prompt,避免殘留覆蓋接口 */
|
|
1054
|
+
function clearChapterHistoryAndPromptForApi() {
|
|
1055
|
+
genAiSectionVersionItems.value = []
|
|
1056
|
+
chapterHistoryItems.value = []
|
|
1057
|
+
activeChapterHistoryKey.value = ''
|
|
1058
|
+
chapterHistoryOpen.value = false
|
|
1059
|
+
Object.assign(promptChapterForm, createDefaultPromptChapterForm())
|
|
1060
|
+
chapterHistoryPreviewBody.value = null
|
|
1061
|
+
chapterHistoryApplyToken.value += 1
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* 進頁拉取章節下拉;成功則用接口數據替換 chapterTitles
|
|
1066
|
+
* instanceId 取自 genAiBuildReportStore(當前頁正確來源)
|
|
1067
|
+
* 無 instanceId / 接口失敗 / 空列表:界面置空,不阻斷主流程、不回落 mock
|
|
1068
|
+
*/
|
|
1069
|
+
async function loadGenAiSectionListOnEnter() {
|
|
1070
|
+
const instanceId = String(genAiBuildReportStore.instanceId ?? '').trim()
|
|
1071
|
+
if (!instanceId) {
|
|
1072
|
+
console.warn('[report editor] 無 instanceId,章節下拉置空')
|
|
1073
|
+
genAiSectionItems.value = []
|
|
1074
|
+
//0920:用 dedupe setter,避免同空再触发无谓更新
|
|
1075
|
+
setChapterTitlesIfChanged([])
|
|
1076
|
+
clearChapterHistoryAndPromptForApi()
|
|
1077
|
+
return false
|
|
1078
|
+
}
|
|
1079
|
+
const result = await genAiSectionList({ instanceId })
|
|
1080
|
+
if (!result.ok) {
|
|
1081
|
+
console.warn('[report editor] getSectionList 失敗,章節下拉置空')
|
|
1082
|
+
genAiSectionItems.value = []
|
|
1083
|
+
setChapterTitlesIfChanged([])
|
|
1084
|
+
clearChapterHistoryAndPromptForApi()
|
|
1085
|
+
return false
|
|
1086
|
+
}
|
|
1087
|
+
if (!result.sectionList.length) {
|
|
1088
|
+
genAiSectionItems.value = []
|
|
1089
|
+
setChapterTitlesIfChanged([])
|
|
1090
|
+
clearChapterHistoryAndPromptForApi()
|
|
1091
|
+
return true
|
|
1092
|
+
}
|
|
1093
|
+
applyGenAiSectionList(result.sectionList)
|
|
1094
|
+
suppressSectionVersionReload = true
|
|
1095
|
+
try {
|
|
1096
|
+
await loadGenAiSectionVersionListForActiveChapter()
|
|
1097
|
+
} finally {
|
|
1098
|
+
suppressSectionVersionReload = false
|
|
1099
|
+
}
|
|
1100
|
+
return true
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
onMounted(() => {
|
|
1104
|
+
registerChapterHistoryReloader(() => loadGenAiSectionVersionListForActiveChapter())
|
|
1105
|
+
|
|
1106
|
+
// 每次進入:清預覽態,再清空 mock,隨後以接口完整覆蓋
|
|
1107
|
+
resetWorkspaceHistoryOnEnter()
|
|
1108
|
+
activeChapterIndex.value = 0
|
|
1109
|
+
void init()
|
|
1110
|
+
|
|
1111
|
+
//0920:数据齐且 preview 稳定后,再挂 TipTap 且直接 paint-ready,禁止「hidden 灌文 → opacity 显现」双阶段闪动
|
|
1112
|
+
void (async () => {
|
|
1113
|
+
editorPaintReady.value = false
|
|
1114
|
+
editorContentReady.value = false
|
|
1115
|
+
if (isReportSettingsMode.value) {
|
|
1116
|
+
syncLocalizedChapterTitles()
|
|
1117
|
+
await waitPreviewStable()
|
|
1118
|
+
commitStaticEditorSnapshot()
|
|
1119
|
+
editorPaintReady.value = true
|
|
1120
|
+
editorContentReady.value = true
|
|
1121
|
+
await nextTick()
|
|
1122
|
+
// 0924:编辑器挂上后再匹配一次 refList
|
|
1123
|
+
notifyChartStreamRefsUpdated()
|
|
1124
|
+
// 0924
|
|
1125
|
+
return
|
|
1126
|
+
}
|
|
1127
|
+
//0920:先进口后写好标题再挂 el-select;进页勿先清空 titles(防 facade 先空再满)
|
|
1128
|
+
chapterSelectReady.value = false
|
|
1129
|
+
clearChapterHistoryAndPromptForApi()
|
|
1130
|
+
genAiSectionItems.value = []
|
|
1131
|
+
await loadGenAiSectionListOnEnter()
|
|
1132
|
+
await nextTick()
|
|
1133
|
+
chapterSelectReady.value = true
|
|
1134
|
+
//0920
|
|
1135
|
+
await waitPreviewStable()
|
|
1136
|
+
commitStaticEditorSnapshot()
|
|
1137
|
+
editorPaintReady.value = true
|
|
1138
|
+
editorContentReady.value = true
|
|
1139
|
+
await nextTick()
|
|
1140
|
+
// 0924:编辑器挂上后再匹配一次 refList
|
|
1141
|
+
notifyChartStreamRefsUpdated()
|
|
1142
|
+
// 0924
|
|
1143
|
+
})()
|
|
1144
|
+
//0920
|
|
1145
|
+
})
|
|
1146
|
+
|
|
1147
|
+
//0920:等标题/正文稳定(挂载前调用,避免先灌后显)
|
|
1148
|
+
async function waitPreviewStable() {
|
|
1149
|
+
await nextTick()
|
|
1150
|
+
let lastTitle = previewChapterTitle.value
|
|
1151
|
+
let lastIntro = previewChapterIntro.value
|
|
1152
|
+
for (let i = 0; i < 12; i++) {
|
|
1153
|
+
await new Promise<void>((resolve) => {
|
|
1154
|
+
requestAnimationFrame(() => resolve())
|
|
1155
|
+
})
|
|
1156
|
+
await new Promise<void>((resolve) => {
|
|
1157
|
+
window.setTimeout(resolve, 24)
|
|
1158
|
+
})
|
|
1159
|
+
const t = previewChapterTitle.value
|
|
1160
|
+
const intro = previewChapterIntro.value
|
|
1161
|
+
//0920:空正文也算稳定,勿空等 12 轮;有内容则等不再抖动
|
|
1162
|
+
if (t === lastTitle && intro === lastIntro) break
|
|
1163
|
+
//0920
|
|
1164
|
+
lastTitle = t
|
|
1165
|
+
lastIntro = intro
|
|
1166
|
+
}
|
|
1167
|
+
await nextTick()
|
|
1168
|
+
await new Promise<void>((resolve) => {
|
|
1169
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
1170
|
+
})
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/** //0920:切章后短暂藏编辑器,灌稳再显示 */
|
|
1174
|
+
async function settleEditorPaintReady() {
|
|
1175
|
+
editorPaintReady.value = false
|
|
1176
|
+
await waitPreviewStable()
|
|
1177
|
+
commitStaticEditorSnapshot()
|
|
1178
|
+
editorPaintReady.value = true
|
|
1179
|
+
}
|
|
1180
|
+
//0920
|
|
1181
|
+
|
|
1182
|
+
const isReportSettingsMode = computed(() => uiShowSettingsDoc.value)
|
|
1183
|
+
|
|
1184
|
+
const isViewingReportHistory = computed(
|
|
1185
|
+
() => !!historyPreviewRecordId.value && !historyPreviewDirty.value
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
function normalizeChapterMatchTitle(value: string) {
|
|
1189
|
+
return stripChapterNumberPrefix(String(value || ''))
|
|
1190
|
+
.replace(/\s+/g, '')
|
|
1191
|
+
.toLowerCase()
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/** 多候選中優先取有正文的 instanceContent,避免空殼占位擋住真實數據 */
|
|
1195
|
+
function pickAdjustInstanceContent(
|
|
1196
|
+
candidates: Array<{ sectionTitle: string; sectionContent: string } | undefined>
|
|
1197
|
+
) {
|
|
1198
|
+
const list = candidates.filter(
|
|
1199
|
+
(item): item is { sectionTitle: string; sectionContent: string } => !!item
|
|
1200
|
+
)
|
|
1201
|
+
if (!list.length) return undefined
|
|
1202
|
+
return list.find((item) => String(item.sectionContent || '').trim()) ?? list[0]
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const SETTINGS_UNAVAILABLE_LABELS = new Set(['附件與數'])
|
|
1206
|
+
|
|
1207
|
+
/** 設置模式:僅 AdjustInstance / AdjustInstanceSetting 正文;無接口則空 */
|
|
1208
|
+
const settingsPreviewChapters = computed(() => {
|
|
1209
|
+
const ordered = settingsChapterOrder.value
|
|
1210
|
+
.map((key) => settingsChapterPresets.value.find((preset) => preset.key === key))
|
|
1211
|
+
.filter((preset): preset is { key: string; label: string } => !!preset)
|
|
1212
|
+
|
|
1213
|
+
const visible = ordered.filter(
|
|
1214
|
+
(preset) =>
|
|
1215
|
+
!!settingsSelectedChapters.value[preset.key] && !SETTINGS_UNAVAILABLE_LABELS.has(preset.label)
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
return visible.map((preset, visibleIndex) => {
|
|
1219
|
+
const label = translateChapterLabel(preset.label, draft.language, 'zh-Hant')
|
|
1220
|
+
const contents = adjustInstanceContents.value
|
|
1221
|
+
const presetsLen = settingsChapterPresets.value.length
|
|
1222
|
+
const sourceIndex = Number(preset.key.replace(/^chapter-/, ''))
|
|
1223
|
+
const apiDefaultOrder = settingsChapterPresets.value.map((item) => item.key)
|
|
1224
|
+
const orderIsApiDefault =
|
|
1225
|
+
settingsChapterOrder.value.length === apiDefaultOrder.length &&
|
|
1226
|
+
settingsChapterOrder.value.every((key, i) => key === apiDefaultOrder[i])
|
|
1227
|
+
|
|
1228
|
+
// 1) byTitle:標題對齊(首選)
|
|
1229
|
+
// 2) byOrder:僅當 contents 與可見列表等長,且為「僅勾選 subset」或「已拖拽重排後的全量列表序」
|
|
1230
|
+
// —— AdjustInstanceSetting 常按 versionIdList / 拖拽序回寫,此時 visibleIndex 才安全
|
|
1231
|
+
// 3) byKey:僅全量平行 + 順序仍是 chapter-0..n(首包 AdjustInstance)→ chapter-N → contents[N]
|
|
1232
|
+
// —— 禁止在拖拽後對「按列表序回寫的全量 contents」用 byKey(會搶在 byOrder 前錯配)
|
|
1233
|
+
const want = normalizeChapterMatchTitle(preset.label)
|
|
1234
|
+
const byTitle = contents.find(
|
|
1235
|
+
(item) => normalizeChapterMatchTitle(item.sectionTitle || '') === want
|
|
1236
|
+
)
|
|
1237
|
+
const byOrder =
|
|
1238
|
+
contents.length === visible.length &&
|
|
1239
|
+
(contents.length < presetsLen || !orderIsApiDefault)
|
|
1240
|
+
? contents[visibleIndex]
|
|
1241
|
+
: undefined
|
|
1242
|
+
const byKey =
|
|
1243
|
+
contents.length === presetsLen &&
|
|
1244
|
+
orderIsApiDefault &&
|
|
1245
|
+
Number.isFinite(sourceIndex)
|
|
1246
|
+
? contents[sourceIndex]
|
|
1247
|
+
: undefined
|
|
1248
|
+
const content = pickAdjustInstanceContent([byTitle, byOrder, byKey])
|
|
1249
|
+
|
|
1250
|
+
return {
|
|
1251
|
+
key: preset.key,
|
|
1252
|
+
displayTitle: stripChapterNumberPrefix(content?.sectionTitle?.trim() || label),
|
|
1253
|
+
// 原文交給 editor 做 Markdown + HTML 解析,此處不剝標籤
|
|
1254
|
+
intro: content ? String(content.sectionContent || '') : '',
|
|
1255
|
+
}
|
|
1256
|
+
})
|
|
1257
|
+
})
|
|
1258
|
+
|
|
1259
|
+
/** 供獨立打字機組件:僅傳章節內容,不耦合打字/滾動狀態 */
|
|
1260
|
+
const settingsTypewriterChapters = computed(() =>
|
|
1261
|
+
settingsPreviewChapters.value.map((chapter, index) => {
|
|
1262
|
+
const bareTitle = stripChapterNumberPrefix(String(chapter.displayTitle || ''))
|
|
1263
|
+
// 接口 sectionContent 常以 **標題** / <strong>標題</strong> / ## 標題 開頭,
|
|
1264
|
+
// 與下方 buildPlainContentHtml 注入的 <h2>N. 標題</h2> 重複 → 剝掉這一行
|
|
1265
|
+
let intro = String(chapter.intro || '')
|
|
1266
|
+
const lead = intro.match(
|
|
1267
|
+
/^\s*(?:<p[^>]*>\s*)?(?:#{1,6}\s+([^\n<#]+)|(?:\*\*|__|<strong[^>]*>)\s*([\s\S]*?)\s*(?:\*\*|__|<\/strong>))\s*(?:<\/p>)?\s*(?:\n+|$)/i
|
|
1268
|
+
)
|
|
1269
|
+
const leadTitle = (lead?.[1] || lead?.[2] || '').trim()
|
|
1270
|
+
if (
|
|
1271
|
+
lead &&
|
|
1272
|
+
leadTitle &&
|
|
1273
|
+
normalizeChapterMatchTitle(leadTitle) === normalizeChapterMatchTitle(bareTitle)
|
|
1274
|
+
) {
|
|
1275
|
+
intro = intro.slice(lead[0].length)
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
key: chapter.key,
|
|
1279
|
+
title: `${index + 1}. ${bareTitle}`,
|
|
1280
|
+
intro,
|
|
1281
|
+
}
|
|
1282
|
+
})
|
|
1283
|
+
)
|
|
1284
|
+
|
|
1285
|
+
const settingsStreamChapters = computed(() => [
|
|
1286
|
+
{
|
|
1287
|
+
key: 'report-generate-stream',
|
|
1288
|
+
title: draft.reportName?.trim() || '報告',
|
|
1289
|
+
intro: reportGenerateStreamText.value,
|
|
1290
|
+
},
|
|
1291
|
+
])
|
|
1292
|
+
|
|
1293
|
+
const settingsDocChapters = computed(() =>
|
|
1294
|
+
reportGenerateStreamActive.value ? settingsStreamChapters.value : settingsTypewriterChapters.value
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
watch(
|
|
1298
|
+
isReportSettingsMode,
|
|
1299
|
+
(enabled) => {
|
|
1300
|
+
if (!enabled) {
|
|
1301
|
+
setSettingsTypingChapterKey(null)
|
|
1302
|
+
setSettingsTypewriterPrinting(false)
|
|
1303
|
+
clearAdjustInstanceBindings()
|
|
1304
|
+
clearReportGenerateStream()
|
|
1305
|
+
return
|
|
1306
|
+
}
|
|
1307
|
+
stopChapterContentTypewriter()
|
|
1308
|
+
// 章節 / 正文 / 資訊源由 AdjustInstance 回寫;不預填模板 mock
|
|
1309
|
+
// 從 session / existsInstanceVersionNo 恢復:顯示「查看結果 / 導出 Word」
|
|
1310
|
+
const session = loadCoherenceCheckSession()
|
|
1311
|
+
if (String(session?.beenFinalizedFlag ?? '').toUpperCase() === 'Y') {
|
|
1312
|
+
setCoherenceCheckResultReady(true, session?.coherenceCheckContentText ?? '')
|
|
1313
|
+
} else if (String(existsInstanceVersionNo.value ?? '').trim()) {
|
|
1314
|
+
setCoherenceCheckResultReady(true, '')
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
{ immediate: true }
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
const showFinalizeToolbar = computed(() => uiShowFinalizeToolbar.value)
|
|
1321
|
+
const showChapterEditToolbar = computed(() => uiShowChapterEditToolbar.value)
|
|
1322
|
+
/** existsInstanceVersionNo 有值,或 CoherenceCheck 返回 Y → 顯示結果 / 導出 Word */
|
|
1323
|
+
const showCoherenceResultActions = computed(
|
|
1324
|
+
() => coherenceResultReady.value || !!String(existsInstanceVersionNo.value ?? '').trim()
|
|
1325
|
+
)
|
|
1326
|
+
|
|
1327
|
+
const settingsHistoryPreviewChapterIndex = ref(0)
|
|
1328
|
+
|
|
1329
|
+
const emit = defineEmits<{
|
|
1330
|
+
'back-to-chapter-edit': []
|
|
1331
|
+
'generate-report': []
|
|
1332
|
+
}>()
|
|
1333
|
+
|
|
1334
|
+
const applyTemplateDialogVisible = ref(false)
|
|
1335
|
+
const applyTemplateDetail = ref<ApplyTemplateDetail | null>(null)
|
|
1336
|
+
const applyTemplateTitle = ref('')
|
|
1337
|
+
const applyTemplateDescription = ref('')
|
|
1338
|
+
const applyTemplateAction = ref<'' | ApplyTemplateProcessAction>('')
|
|
1339
|
+
const applyTemplateOpinion = ref('')
|
|
1340
|
+
|
|
1341
|
+
const applyTemplateLanguageDisplay = computed(() =>
|
|
1342
|
+
applyTemplateDetail.value
|
|
1343
|
+
? getApplyTemplateLanguageDisplay(applyTemplateDetail.value.language)
|
|
1344
|
+
: ''
|
|
1345
|
+
)
|
|
1346
|
+
const applyTemplateDisplayChapters = computed(() =>
|
|
1347
|
+
getApplyTemplateDisplayChapters(applyTemplateDetail.value)
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
function mapReportLanguageToTemplateLanguage(language: string) {
|
|
1351
|
+
if (language === 'en') return 'en'
|
|
1352
|
+
return 'zh'
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function resetApplyTemplateForm() {
|
|
1356
|
+
applyTemplateTitle.value = applyTemplateDetail.value?.title ?? ''
|
|
1357
|
+
applyTemplateDescription.value = applyTemplateDetail.value?.description ?? ''
|
|
1358
|
+
applyTemplateAction.value = ''
|
|
1359
|
+
applyTemplateOpinion.value = ''
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function buildApplyTemplateDetail() {
|
|
1363
|
+
const previewRecord = historyPreviewRecordId.value
|
|
1364
|
+
? findFinalizedReportById(historyPreviewRecordId.value)
|
|
1365
|
+
: null
|
|
1366
|
+
|
|
1367
|
+
const sourceReport =
|
|
1368
|
+
previewRecord?.displayTitle ?? `${draft.reportName || '未命名報告'} ${draft.year}`.trim()
|
|
1369
|
+
|
|
1370
|
+
const chapterSource = chapterTitles.value.length
|
|
1371
|
+
? chapterTitles.value
|
|
1372
|
+
: (selectedTemplate.value?.chapters ?? [])
|
|
1373
|
+
|
|
1374
|
+
return buildApplyTemplateDetailFromReport({
|
|
1375
|
+
sourceReport,
|
|
1376
|
+
language: mapReportLanguageToTemplateLanguage(draft.language),
|
|
1377
|
+
title: draft.reportName?.trim() || '',
|
|
1378
|
+
description: '',
|
|
1379
|
+
chapterTitles: chapterSource,
|
|
1380
|
+
chapterPrompt: promptChapterForm.prompt,
|
|
1381
|
+
// 審批明細以接口為準;無數據則空表,不填本地假操作人
|
|
1382
|
+
approvalRecords: [],
|
|
1383
|
+
})
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// report editor api
|
|
1387
|
+
/**
|
|
1388
|
+
* 查詢章節下拉列表
|
|
1389
|
+
* 調試:data[] 含 instanceId/sectionId/orderSeq/sectionName/sectionDesc/latestStatus
|
|
1390
|
+
* latestStatus:PENDING 未生成 / EDITING 編輯中 / CREATED 已生成
|
|
1391
|
+
*/
|
|
1392
|
+
const genAiSectionList = async (params: GenAiGetSectionListParams) => {
|
|
1393
|
+
const res: any = await genAiReportEditorApi
|
|
1394
|
+
.fetchGenAiSectionList({
|
|
1395
|
+
instanceId: params.instanceId,
|
|
1396
|
+
})
|
|
1397
|
+
.catch((err) => {
|
|
1398
|
+
console.error('接口异常', err)
|
|
1399
|
+
return null
|
|
1400
|
+
})
|
|
1401
|
+
|
|
1402
|
+
try {
|
|
1403
|
+
if (!isGenAiResponseOk(res)) {
|
|
1404
|
+
console.warn('[report editor] getSectionList', res?.message || '接口异常')
|
|
1405
|
+
return {
|
|
1406
|
+
ok: false as const,
|
|
1407
|
+
sectionList: [] as GenAiReportSectionItem[],
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
const sectionList = (Array.isArray(res.data) ? res.data : []) as GenAiReportSectionItem[]
|
|
1412
|
+
console.log('章节下拉列表', sectionList)
|
|
1413
|
+
return { ok: true as const, sectionList }
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
console.error('接口异常', err)
|
|
1416
|
+
return {
|
|
1417
|
+
ok: false as const,
|
|
1418
|
+
sectionList: [] as GenAiReportSectionItem[],
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* 更新章節順序
|
|
1425
|
+
* 調試:傳 sections[{ instanceId, sectionId, orderSeq }],orderSeq 為調整後新順序
|
|
1426
|
+
*/
|
|
1427
|
+
const updateGenAiSectionOrder = async () => {
|
|
1428
|
+
const sections = genAiSectionItems.value.map((item) => ({
|
|
1429
|
+
instanceId: item.instanceId,
|
|
1430
|
+
sectionId: item.sectionId,
|
|
1431
|
+
orderSeq: item.orderSeq,
|
|
1432
|
+
}))
|
|
1433
|
+
if (!sections.length) {
|
|
1434
|
+
return { ok: false as const }
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
const res: any = await genAiReportEditorApi.updateGenAiSectionOrder({ sections }).catch((err) => {
|
|
1438
|
+
console.error('接口异常', err)
|
|
1439
|
+
return null
|
|
1440
|
+
})
|
|
1441
|
+
|
|
1442
|
+
try {
|
|
1443
|
+
if (!isGenAiResponseOk(res)) {
|
|
1444
|
+
ElMessage.warning(res?.message || '接口异常')
|
|
1445
|
+
return { ok: false as const }
|
|
1446
|
+
}
|
|
1447
|
+
console.log('章节顺序已更新', sections)
|
|
1448
|
+
return { ok: true as const }
|
|
1449
|
+
} catch (err) {
|
|
1450
|
+
console.error('接口异常', err)
|
|
1451
|
+
ElMessage.warning('接口异常')
|
|
1452
|
+
return { ok: false as const }
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/** 寫死調試參數(與接口文檔一致;後續換登錄用戶 / 當前章節) */
|
|
1457
|
+
const GEN_AI_HARDCODE_SECTION = {
|
|
1458
|
+
categoryId: 1,
|
|
1459
|
+
sectionId: '87E7D6DD-B6E2-4E37-9D38-DC816BD37809',
|
|
1460
|
+
userId: '8740004',
|
|
1461
|
+
userName: 'LOCAL ADMIN',
|
|
1462
|
+
userUnitSeq: '1',
|
|
1463
|
+
} as const
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* 更新章節狀態(點「編輯/儲存」進入編輯時)
|
|
1467
|
+
* 調試:action "EDIT" → 後臺狀態改為 EDITING;參數暫用接口文檔寫死數據
|
|
1468
|
+
*/
|
|
1469
|
+
const changeGenAiSectionStatus = async () => {
|
|
1470
|
+
const data = {
|
|
1471
|
+
categoryId: GEN_AI_HARDCODE_SECTION.categoryId,
|
|
1472
|
+
sectionId: GEN_AI_HARDCODE_SECTION.sectionId,
|
|
1473
|
+
action: 'EDIT' as const,
|
|
1474
|
+
updateUserId: GEN_AI_HARDCODE_SECTION.userId,
|
|
1475
|
+
updateUserName: GEN_AI_HARDCODE_SECTION.userName,
|
|
1476
|
+
updateUserUnitSeq: GEN_AI_HARDCODE_SECTION.userUnitSeq,
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
const res: any = await genAiReportEditorApi.changeGenAiSectionStatus(data).catch((err) => {
|
|
1480
|
+
console.error('接口异常', err)
|
|
1481
|
+
return null
|
|
1482
|
+
})
|
|
1483
|
+
|
|
1484
|
+
try {
|
|
1485
|
+
if (!isGenAiResponseOk(res)) {
|
|
1486
|
+
ElMessage.warning(res?.message || '接口异常')
|
|
1487
|
+
return { ok: false as const }
|
|
1488
|
+
}
|
|
1489
|
+
console.log('章节状态已更新为 EDITING', data)
|
|
1490
|
+
return { ok: true as const }
|
|
1491
|
+
} catch (err) {
|
|
1492
|
+
console.error('接口异常', err)
|
|
1493
|
+
ElMessage.warning('接口异常')
|
|
1494
|
+
return { ok: false as const }
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* 版本[保存]/[存為草稿] saveSectionVersion
|
|
1500
|
+
* draftFlag "N"=保存 / "Y"=存為草稿
|
|
1501
|
+
* instanceId/versionId/versionNo/hasOldAiContent:優先取當前選中版本原值
|
|
1502
|
+
*/
|
|
1503
|
+
const saveGenAiSectionVersion = async (draftFlag: 'Y' | 'N') => {
|
|
1504
|
+
const versionId = activeChapterHistoryKey.value
|
|
1505
|
+
const version =
|
|
1506
|
+
genAiSectionVersionItems.value.find((item) => item.versionId === versionId) ??
|
|
1507
|
+
genAiSectionVersionItems.value[0]
|
|
1508
|
+
|
|
1509
|
+
const data = {
|
|
1510
|
+
categoryId: GEN_AI_HARDCODE_SECTION.categoryId,
|
|
1511
|
+
instanceId:
|
|
1512
|
+
version?.instanceId ||
|
|
1513
|
+
String(genAiBuildReportStore.instanceId ?? '') ||
|
|
1514
|
+
'62D6B74B-13C8-4113-9F97-54117FEBA9A6',
|
|
1515
|
+
sectionId: GEN_AI_HARDCODE_SECTION.sectionId,
|
|
1516
|
+
versionId: version?.versionId || versionId || '2DE1F24F-61A2-4539-BA66-CC5B75C79CC5',
|
|
1517
|
+
versionNo: version?.versionNo ?? 1,
|
|
1518
|
+
draftFlag,
|
|
1519
|
+
promptDetail: promptChapterForm.prompt,
|
|
1520
|
+
promptByGen: (version?.promptByGen ?? 'N') as 'Y' | 'N',
|
|
1521
|
+
contentLengthLimit: promptChapterForm.enableBodyContent
|
|
1522
|
+
? promptChapterForm.bodyWordLimit || 500
|
|
1523
|
+
: (null as unknown as number),
|
|
1524
|
+
// 存草稿時正文可空;後臺不存該字段
|
|
1525
|
+
contentText: draftFlag === 'Y' ? '' : chapterHistoryPreviewBody.value || '',
|
|
1526
|
+
fileIndexIdList: version?.fileIndexIdList?.length
|
|
1527
|
+
? [...version.fileIndexIdList]
|
|
1528
|
+
: sourceFiles.value.filter((f) => f.selected).map((f) => f.id),
|
|
1529
|
+
createdUserId: GEN_AI_HARDCODE_SECTION.userId,
|
|
1530
|
+
createdUserName: GEN_AI_HARDCODE_SECTION.userName,
|
|
1531
|
+
createdUserUnitSeq: GEN_AI_HARDCODE_SECTION.userUnitSeq,
|
|
1532
|
+
tableInfo: promptChapterForm.enableTable
|
|
1533
|
+
? {
|
|
1534
|
+
tableTitle: promptChapterForm.tableTitle,
|
|
1535
|
+
columnLabel: promptChapterForm.tableRowLabels,
|
|
1536
|
+
barLabel: promptChapterForm.tableColumnLabels,
|
|
1537
|
+
content: promptChapterForm.tableContent,
|
|
1538
|
+
}
|
|
1539
|
+
: null,
|
|
1540
|
+
chartInfo: promptChapterForm.enableSourceChart
|
|
1541
|
+
? { chartDesc: promptChapterForm.chartDescription }
|
|
1542
|
+
: null,
|
|
1543
|
+
// 須原值回傳
|
|
1544
|
+
hasOldAiContent: (version?.hasOldAiContent ?? 'N') as 'Y' | 'N',
|
|
1545
|
+
auditEventId: resolveChapterChartAuditEventId(),
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
const res: any = await genAiReportEditorApi.saveGenAiSectionVersion(data).catch((err) => {
|
|
1549
|
+
console.error('接口异常', err)
|
|
1550
|
+
return null
|
|
1551
|
+
})
|
|
1552
|
+
|
|
1553
|
+
try {
|
|
1554
|
+
if (!isGenAiResponseOk(res)) {
|
|
1555
|
+
ElMessage.warning(res?.message || '接口异常')
|
|
1556
|
+
return { ok: false as const }
|
|
1557
|
+
}
|
|
1558
|
+
console.log(draftFlag === 'Y' ? '已存为草稿' : '已保存版本', data)
|
|
1559
|
+
return { ok: true as const }
|
|
1560
|
+
} catch (err) {
|
|
1561
|
+
console.error('接口异常', err)
|
|
1562
|
+
ElMessage.warning('接口异常')
|
|
1563
|
+
return { ok: false as const }
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* —— 章節歷史 / Prompt:接口 GenAiSectionVersionItem ↔ 組件字段一一對應 ——
|
|
1569
|
+
*
|
|
1570
|
+
* 【章節歷史 card】
|
|
1571
|
+
* key / recordId ← versionId(點選後按 versionId 回填 Prompt)
|
|
1572
|
+
* kind ← draftFlag==="Y" → 'draft'(走草稿 card UI:繼續編輯/刪除)
|
|
1573
|
+
* ← 否則 'history'(普通版本 card)
|
|
1574
|
+
* title ← draftFlag==="Y" → CHAPTER_DRAFT_HISTORY_TITLE
|
|
1575
|
+
* 「您有未生成章節內容的草稿...」
|
|
1576
|
+
* ← finalizedFlag==="Y" → objectName ||「定稿版本」
|
|
1577
|
+
* ← 否則 objectName || `V${versionNo}`
|
|
1578
|
+
* author ← updateUserName || createdUserName
|
|
1579
|
+
* finalizedAt ← updateDatetime || createdDatetime
|
|
1580
|
+
* 列表順序 ← 草稿(draftFlag=Y)置頂,其餘按 versionNo 降序
|
|
1581
|
+
*
|
|
1582
|
+
* 【Prompt / 正文】
|
|
1583
|
+
* prompt ← promptDetail
|
|
1584
|
+
* enableBodyContent ← hasOldAiContent==="Y"(後臺標識,保存時須原值回傳,見 version 緩存)
|
|
1585
|
+
* bodyWordLimit ← contentLengthLimit
|
|
1586
|
+
* enableTable + table* ← tableInfo(null 關閉;columnLabel→列,barLabel→欄/行)
|
|
1587
|
+
* enableSourceChart+desc ← chartInfo(null 關閉;chartDesc)
|
|
1588
|
+
* 中欄正文 ← contentText
|
|
1589
|
+
*
|
|
1590
|
+
* 【資訊源反顯】僅用戶點選章節歷史 / 繼續編輯時
|
|
1591
|
+
* fileIndexIdList → sourceFiles.selected;正文靜態反顯(不啟動打字機)
|
|
1592
|
+
*
|
|
1593
|
+
* 【僅緩存在 genAiSectionVersionItems,UI 暫不單獨展示】
|
|
1594
|
+
* backupFlag / promptByGen / promptDetailExt(忽略) / fileIndexIdList /
|
|
1595
|
+
* created* / update* / versionNo(保存須原值回傳)
|
|
1596
|
+
*/
|
|
1597
|
+
const genAiSectionVersionItems = ref<GenAiSectionVersionItem[]>([])
|
|
1598
|
+
|
|
1599
|
+
function mapVersionToHistoryItem(version: GenAiSectionVersionItem): ReportHistoryItem {
|
|
1600
|
+
const isDraft = version.draftFlag === 'Y'
|
|
1601
|
+
const isFinalized = version.finalizedFlag === 'Y'
|
|
1602
|
+
return {
|
|
1603
|
+
key: version.versionId,
|
|
1604
|
+
// draftFlag=Y:必須用組件約定的草稿文案,觸發 ReportHistoryList 草稿 card
|
|
1605
|
+
title: isDraft
|
|
1606
|
+
? CHAPTER_DRAFT_HISTORY_TITLE
|
|
1607
|
+
: isFinalized
|
|
1608
|
+
? version.objectName?.trim() || '定稿版本'
|
|
1609
|
+
: version.objectName?.trim() || `V${version.versionNo}`,
|
|
1610
|
+
author: version.updateUserName || version.createdUserName || '',
|
|
1611
|
+
finalizedAt: version.updateDatetime || version.createdDatetime || '',
|
|
1612
|
+
kind: isDraft ? 'draft' : 'history',
|
|
1613
|
+
recordId: version.versionId,
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function mapVersionToPromptForm(version: GenAiSectionVersionItem): PromptChapterForm {
|
|
1618
|
+
const table = version.tableInfo
|
|
1619
|
+
const chart = version.chartInfo
|
|
1620
|
+
// Prompt 全量反顯:checkbox / input / textarea 與接口字段一一對齊
|
|
1621
|
+
const limitRaw = version.contentLengthLimit
|
|
1622
|
+
const hasBodyLimit = limitRaw != null && Number(limitRaw) > 0
|
|
1623
|
+
const tableEnabled =
|
|
1624
|
+
table != null && String((table as { enableTable?: string }).enableTable ?? 'Y') !== 'N'
|
|
1625
|
+
const chartEnabled =
|
|
1626
|
+
chart != null && String((chart as { enableChart?: string }).enableChart ?? 'Y') !== 'N'
|
|
1627
|
+
return {
|
|
1628
|
+
...createDefaultPromptChapterForm(),
|
|
1629
|
+
prompt: String(version.promptDetail ?? ''),
|
|
1630
|
+
// 正文內容:有有效字數限制則勾選(與 chartAsStream contentLengthLimit null=關 對齊)
|
|
1631
|
+
enableBodyContent: hasBodyLimit,
|
|
1632
|
+
bodyWordLimit: hasBodyLimit ? Number(limitRaw) : 500,
|
|
1633
|
+
enableTable: tableEnabled,
|
|
1634
|
+
tableTitle: String(table?.tableTitle ?? ''),
|
|
1635
|
+
// 列標籤 ← columnLabel;欄標籤 ← barLabel(與類型註釋 / UI 文案一致)
|
|
1636
|
+
tableRowLabels: String(table?.columnLabel ?? ''),
|
|
1637
|
+
tableColumnLabels: String(table?.barLabel ?? ''),
|
|
1638
|
+
tableContent: String(table?.content ?? ''),
|
|
1639
|
+
enableSourceChart: chartEnabled,
|
|
1640
|
+
chartDescription: String(chart?.chartDesc ?? ''),
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/** 程式內更新 activeChapterHistoryKey(切章拉列表等)時跳過 watch,避免誤觸 reverseUi */
|
|
1645
|
+
let suppressChapterHistoryKeyWatch = false
|
|
1646
|
+
|
|
1647
|
+
/** 將當前選中版本寫入 Prompt 表單 + 中欄正文預覽(覆蓋組件內 mock / session) */
|
|
1648
|
+
function applyGenAiVersionToPromptAndBody(versionId: string, reverseUi = false) {
|
|
1649
|
+
const version = genAiSectionVersionItems.value.find((item) => item.versionId === versionId)
|
|
1650
|
+
if (!version) return false
|
|
1651
|
+
// 0924:静态预览先备 refList,contentText 里 [ref:chunkId] 才能出圆点
|
|
1652
|
+
upsertChapterChartStreamRefsFromSources(version)
|
|
1653
|
+
// 0924
|
|
1654
|
+
// reverseUi=true:用戶點選歷史 / 繼續編輯 → 停流、反顯資訊源、正文靜態展示(不啟動打字機)
|
|
1655
|
+
// reverseUi=false:切章 / 刷新列表 → 保持原行為,不改資訊源、不額外停流
|
|
1656
|
+
if (reverseUi) {
|
|
1657
|
+
stopChapterContentTypewriter({ cancelStream: true })
|
|
1658
|
+
}
|
|
1659
|
+
const nextPrompt = mapVersionToPromptForm(version)
|
|
1660
|
+
// Prompt 全量覆蓋寫入(checkbox / input / textarea),保證歷史切換時 UI 立即反顯
|
|
1661
|
+
Object.assign(promptChapterForm, nextPrompt)
|
|
1662
|
+
if (reverseUi) {
|
|
1663
|
+
const selectedSet = new Set(
|
|
1664
|
+
(Array.isArray(version.fileIndexIdList) ? version.fileIndexIdList : []).map((id) =>
|
|
1665
|
+
String(id)
|
|
1666
|
+
)
|
|
1667
|
+
)
|
|
1668
|
+
for (const file of sourceFiles.value) {
|
|
1669
|
+
file.selected = selectedSet.has(String(file.id))
|
|
1670
|
+
}
|
|
1671
|
+
// 按接口原文靜態反顯;空正文也寫入,避免回落到舊 generated 緩存導致數據不准
|
|
1672
|
+
const exactBody = String(version.contentText ?? '')
|
|
1673
|
+
chapterHistoryPreviewBody.value = exactBody
|
|
1674
|
+
chapterHistoryApplyToken.value += 1
|
|
1675
|
+
patchGeneratedChapterBody(activeChapterIndex.value, exactBody)
|
|
1676
|
+
//0920:用户点历史:更新快照并 bump rev,确保正文/ tip 立即反显
|
|
1677
|
+
commitStaticEditorSnapshot()
|
|
1678
|
+
staticEditorRev.value += 1
|
|
1679
|
+
//0920
|
|
1680
|
+
return true
|
|
1681
|
+
}
|
|
1682
|
+
//0920:正文相对快照变了才提交(保存/切版本可见);同文不提交(防闪)
|
|
1683
|
+
// tip 由 upsert→CHART_STREAM_REFS_UPDATED_EVENT→forceApplyTipHtml 补,不靠 bump rev
|
|
1684
|
+
const nextStaticBody = String(version.contentText || '')
|
|
1685
|
+
const prevStaticBody = String(chapterHistoryPreviewBody.value ?? '')
|
|
1686
|
+
chapterHistoryPreviewBody.value = version.contentText || null
|
|
1687
|
+
if (nextStaticBody !== prevStaticBody) {
|
|
1688
|
+
chapterHistoryApplyToken.value += 1
|
|
1689
|
+
}
|
|
1690
|
+
if (nextStaticBody !== String(staticEditorIntro.value ?? '')) {
|
|
1691
|
+
commitStaticEditorSnapshot()
|
|
1692
|
+
staticEditorRev.value += 1
|
|
1693
|
+
}
|
|
1694
|
+
//0920
|
|
1695
|
+
// 寫入本地緩存,切章時可先清空再秒開,無需乾等版本接口
|
|
1696
|
+
const body = String(version.contentText || '')
|
|
1697
|
+
if (body.trim()) {
|
|
1698
|
+
patchGeneratedChapterBody(activeChapterIndex.value, body)
|
|
1699
|
+
}
|
|
1700
|
+
return true
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function applyGenAiSectionVersionList(list: GenAiSectionVersionItem[]) {
|
|
1704
|
+
// 0924:列表内各版本 refList 先入库,默认展示才能匹配圆点
|
|
1705
|
+
upsertChapterChartStreamRefsFromSources(list)
|
|
1706
|
+
// 0924
|
|
1707
|
+
// 草稿置頂,其餘 versionNo 降序
|
|
1708
|
+
const sorted = [...list].sort((a, b) => {
|
|
1709
|
+
const aDraft = a.draftFlag === 'Y' ? 1 : 0
|
|
1710
|
+
const bDraft = b.draftFlag === 'Y' ? 1 : 0
|
|
1711
|
+
if (aDraft !== bDraft) return bDraft - aDraft
|
|
1712
|
+
return b.versionNo - a.versionNo
|
|
1713
|
+
})
|
|
1714
|
+
genAiSectionVersionItems.value = sorted
|
|
1715
|
+
chapterHistoryItems.value = sorted.map(mapVersionToHistoryItem)
|
|
1716
|
+
// 有內容才展開;空列表保持收起
|
|
1717
|
+
chapterHistoryOpen.value = chapterHistoryItems.value.length > 0
|
|
1718
|
+
const firstKey = chapterHistoryItems.value[0]?.key ?? ''
|
|
1719
|
+
// 程式刷新列表時抑制 watch,避免誤走 reverseUi(改資訊源 / 停流)
|
|
1720
|
+
//0920:suppress 必须撑过本轮 flush,否则 watch 在 suppress 已复位后再次套用正文(进页/刷新闪一次)
|
|
1721
|
+
suppressChapterHistoryKeyWatch = true
|
|
1722
|
+
activeChapterHistoryKey.value = firstKey
|
|
1723
|
+
if (firstKey) {
|
|
1724
|
+
applyGenAiVersionToPromptAndBody(firstKey)
|
|
1725
|
+
} else {
|
|
1726
|
+
Object.assign(promptChapterForm, createDefaultPromptChapterForm())
|
|
1727
|
+
chapterHistoryPreviewBody.value = null
|
|
1728
|
+
chapterHistoryApplyToken.value += 1
|
|
1729
|
+
}
|
|
1730
|
+
void nextTick(() => {
|
|
1731
|
+
void nextTick(() => {
|
|
1732
|
+
suppressChapterHistoryKeyWatch = false
|
|
1733
|
+
})
|
|
1734
|
+
})
|
|
1735
|
+
//0920
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/**
|
|
1739
|
+
* 查詢版本列表 getSectionVersionList
|
|
1740
|
+
* 調試:保存/存草稿須原值回傳 versionNo、hasOldAiContent;draftFlag 等為 Y/N
|
|
1741
|
+
*/
|
|
1742
|
+
const genAiSectionVersionList = async (params: { instanceId: string; sectionId: string }) => {
|
|
1743
|
+
const res: any = await genAiReportEditorApi
|
|
1744
|
+
.fetchGenAiSectionVersionList({
|
|
1745
|
+
instanceId: params.instanceId,
|
|
1746
|
+
sectionId: params.sectionId,
|
|
1747
|
+
})
|
|
1748
|
+
.catch((err) => {
|
|
1749
|
+
console.error('接口异常', err)
|
|
1750
|
+
return null
|
|
1751
|
+
})
|
|
1752
|
+
|
|
1753
|
+
try {
|
|
1754
|
+
if (!isGenAiResponseOk(res)) {
|
|
1755
|
+
console.warn('[report editor] getSectionVersionList', res?.message || '接口异常')
|
|
1756
|
+
return {
|
|
1757
|
+
ok: false as const,
|
|
1758
|
+
versionList: [] as GenAiSectionVersionItem[],
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
const versionList = (Array.isArray(res.data) ? res.data : []) as GenAiSectionVersionItem[]
|
|
1762
|
+
console.log('章节版本列表', versionList)
|
|
1763
|
+
return { ok: true as const, versionList }
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
console.error('接口异常', err)
|
|
1766
|
+
return {
|
|
1767
|
+
ok: false as const,
|
|
1768
|
+
versionList: [] as GenAiSectionVersionItem[],
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/** 按當前下拉選中章節拉取版本歷史 → 完整覆蓋章節歷史列表 + Prompt */
|
|
1774
|
+
async function loadGenAiSectionVersionListForActiveChapter() {
|
|
1775
|
+
const instanceId = String(genAiBuildReportStore.instanceId ?? '').trim()
|
|
1776
|
+
const section = genAiSectionItems.value[activeChapterIndex.value]
|
|
1777
|
+
if (!instanceId || !section?.sectionId) {
|
|
1778
|
+
console.warn('[report editor] 無 instanceId/sectionId,章節歷史置空')
|
|
1779
|
+
clearChapterHistoryAndPromptForApi()
|
|
1780
|
+
return false
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
const result = await genAiSectionVersionList({
|
|
1784
|
+
instanceId,
|
|
1785
|
+
sectionId: section.sectionId,
|
|
1786
|
+
})
|
|
1787
|
+
if (!result.ok) {
|
|
1788
|
+
// 版本列表失敗不影響章節下拉;歷史 / Prompt / 正文置空
|
|
1789
|
+
console.warn('[report editor] getSectionVersionList 失敗,章節歷史置空')
|
|
1790
|
+
clearChapterHistoryAndPromptForApi()
|
|
1791
|
+
return false
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// 即使空列表也覆蓋(清空 mock)
|
|
1795
|
+
applyGenAiSectionVersionList(result.versionList)
|
|
1796
|
+
return true
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* 生成報告 AdjustInstance
|
|
1801
|
+
* 點「生成報告」→ 先跳報告頁(settings)→ 綁定
|
|
1802
|
+
* instanceContents / instanceSections / instanceFileList;
|
|
1803
|
+
* existsInstanceVersionNo 有值 = 已通過連貫性檢查
|
|
1804
|
+
*/
|
|
1805
|
+
const adjustGenAiInstance = async (params: { instanceId: string }) => {
|
|
1806
|
+
const res: any = await genAiReportEditorApi
|
|
1807
|
+
.adjustGenAiInstance({
|
|
1808
|
+
instanceId: params.instanceId,
|
|
1809
|
+
})
|
|
1810
|
+
.catch((err) => {
|
|
1811
|
+
console.error('接口异常', err)
|
|
1812
|
+
return null
|
|
1813
|
+
})
|
|
1814
|
+
|
|
1815
|
+
try {
|
|
1816
|
+
if (!isGenAiResponseOk(res)) {
|
|
1817
|
+
console.warn('[report editor] AdjustInstance', res?.message || '接口异常')
|
|
1818
|
+
return {
|
|
1819
|
+
ok: false as const,
|
|
1820
|
+
data: null as GenAiAdjustInstanceData | null,
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
const raw = (res?.data ?? {}) as Partial<GenAiAdjustInstanceData>
|
|
1824
|
+
const data: GenAiAdjustInstanceData = {
|
|
1825
|
+
instanceContents: (Array.isArray(raw.instanceContents)
|
|
1826
|
+
? raw.instanceContents
|
|
1827
|
+
: []) as GenAiAdjustInstanceContent[],
|
|
1828
|
+
instanceSections: (Array.isArray(raw.instanceSections)
|
|
1829
|
+
? raw.instanceSections
|
|
1830
|
+
: []) as GenAiAdjustInstanceSection[],
|
|
1831
|
+
instanceFileList: (Array.isArray(raw.instanceFileList)
|
|
1832
|
+
? raw.instanceFileList
|
|
1833
|
+
: []) as GenAiAdjustInstanceFileItem[],
|
|
1834
|
+
existsInstanceVersionNo: pickExistsInstanceVersionNo(raw),
|
|
1835
|
+
}
|
|
1836
|
+
console.log('生成报告 AdjustInstance', data)
|
|
1837
|
+
return { ok: true as const, data }
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
console.error('接口异常', err)
|
|
1840
|
+
return {
|
|
1841
|
+
ok: false as const,
|
|
1842
|
+
data: null as GenAiAdjustInstanceData | null,
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
/** 報告頁:調 AdjustInstance 並綁定中欄 / 右側章節 / 資訊源;無 instanceId / 失敗則清空綁定 */
|
|
1848
|
+
async function loadAdjustInstanceForReportPage() {
|
|
1849
|
+
const instanceId = resolveInstanceId()
|
|
1850
|
+
if (!instanceId) {
|
|
1851
|
+
console.warn('[report editor] 無 instanceId,跳過 AdjustInstance 並清空綁定')
|
|
1852
|
+
setAdjustInstanceContents([])
|
|
1853
|
+
clearAdjustInstanceBindings()
|
|
1854
|
+
return false
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
const result = await adjustGenAiInstance({ instanceId })
|
|
1858
|
+
if (!result.ok || !result.data) {
|
|
1859
|
+
setAdjustInstanceContents([])
|
|
1860
|
+
clearAdjustInstanceBindings()
|
|
1861
|
+
return false
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
const { instanceContents, instanceSections, instanceFileList, existsInstanceVersionNo } =
|
|
1865
|
+
result.data
|
|
1866
|
+
|
|
1867
|
+
beginSuppressAdjustInstanceSettingWatch()
|
|
1868
|
+
try {
|
|
1869
|
+
// 中欄報告內容
|
|
1870
|
+
setAdjustInstanceContents(instanceContents)
|
|
1871
|
+
// 已存在報告版本號 → 報告歷史 card 選中對照
|
|
1872
|
+
setExistsInstanceVersionNo(existsInstanceVersionNo)
|
|
1873
|
+
// 右側「生成報告設置」章節列表 + 版本下拉(text 如 V3)
|
|
1874
|
+
if (instanceSections.length) {
|
|
1875
|
+
applyAdjustInstanceSections(instanceSections)
|
|
1876
|
+
} else {
|
|
1877
|
+
const titles = instanceContents
|
|
1878
|
+
.map((item) => item.sectionTitle?.trim())
|
|
1879
|
+
.filter((title): title is string => !!title)
|
|
1880
|
+
if (titles.length) ensureSettingsChapterLayout(titles)
|
|
1881
|
+
else clearAdjustInstanceBindings()
|
|
1882
|
+
}
|
|
1883
|
+
// 資訊源
|
|
1884
|
+
applyAdjustInstanceFiles(instanceFileList)
|
|
1885
|
+
// 標記當前版本組合已同步,避免 Settings watch 立刻再打 AdjustInstanceSetting 造成中欄閃爍
|
|
1886
|
+
markAdjustInstanceSettingSynced(
|
|
1887
|
+
settingsChapterOrder.value
|
|
1888
|
+
.map((key) => String(settingsChapterVersions.value[key] ?? '').trim())
|
|
1889
|
+
.filter(Boolean)
|
|
1890
|
+
)
|
|
1891
|
+
// existsInstanceVersionNo 有值 = 已通過連貫性檢查 → 顯示結果 / 導出 Word
|
|
1892
|
+
if (String(existsInstanceVersionNo ?? '').trim()) {
|
|
1893
|
+
setCoherenceCheckResultReady(true, '')
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// 正文就緒後重播中欄打字機(避免空內容先 complete → 接口晚到只能靜態灌入)
|
|
1897
|
+
const firstKey =
|
|
1898
|
+
settingsChapterOrder.value.find((key) => settingsSelectedChapters.value[key]) ??
|
|
1899
|
+
settingsChapterOrder.value[0]
|
|
1900
|
+
if (firstKey && instanceContents.length) {
|
|
1901
|
+
void nextTick(() => requestSettingsTypewriterReplay(firstKey))
|
|
1902
|
+
}
|
|
1903
|
+
} finally {
|
|
1904
|
+
endSuppressAdjustInstanceSettingWatch()
|
|
1905
|
+
}
|
|
1906
|
+
return true
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// —— report editor api end ——
|
|
1910
|
+
|
|
1911
|
+
/** 進入報告頁(settings)後清空本地佈局再調 AdjustInstance */
|
|
1912
|
+
watch(
|
|
1913
|
+
isReportSettingsMode,
|
|
1914
|
+
(enabled) => {
|
|
1915
|
+
if (!enabled) return
|
|
1916
|
+
clearAdjustInstanceBindings()
|
|
1917
|
+
void loadAdjustInstanceForReportPage()
|
|
1918
|
+
},
|
|
1919
|
+
{ immediate: true }
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* 點選章節歷史 / 繼續編輯 → 反顯 Prompt + 正文 + 資訊源(靜態,不啟動打字機)
|
|
1924
|
+
* 切章拉列表等程式寫 key 時由 suppressChapterHistoryKeyWatch 跳過
|
|
1925
|
+
*/
|
|
1926
|
+
watch(activeChapterHistoryKey, (key) => {
|
|
1927
|
+
if (suppressChapterHistoryKeyWatch) return
|
|
1928
|
+
if (!key || !genAiSectionVersionItems.value.length) return
|
|
1929
|
+
applyGenAiVersionToPromptAndBody(key, true)
|
|
1930
|
+
})
|
|
1931
|
+
|
|
1932
|
+
/** 重命名成功後 chapterHistoryItems.title 回寫 → 同步接口緩存 objectName */
|
|
1933
|
+
watch(
|
|
1934
|
+
chapterHistoryItems,
|
|
1935
|
+
(items) => {
|
|
1936
|
+
for (const item of items) {
|
|
1937
|
+
if (item.kind === 'draft') continue
|
|
1938
|
+
const versionId = item.recordId || item.key
|
|
1939
|
+
const version = genAiSectionVersionItems.value.find((entry) => entry.versionId === versionId)
|
|
1940
|
+
if (version && item.title && version.objectName !== item.title) {
|
|
1941
|
+
version.objectName = item.title
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
},
|
|
1945
|
+
{ deep: true }
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1948
|
+
/** 切章時先壓空正文,避免舊章在接口返回前殘留 ~1s */
|
|
1949
|
+
const chapterBodyCleared = ref(false)
|
|
1950
|
+
|
|
1951
|
+
/** 切換章節下拉:先清空屏幕 → 再展示新章(緩存秒開 / 接口覆蓋) */
|
|
1952
|
+
watch(activeChapterIndex, async (index, prevIndex) => {
|
|
1953
|
+
if (suppressSectionVersionReload) return
|
|
1954
|
+
if (isReportSettingsMode.value) return
|
|
1955
|
+
|
|
1956
|
+
const isSwitch = prevIndex !== undefined && index !== prevIndex
|
|
1957
|
+
if (isSwitch) {
|
|
1958
|
+
//0920:切章先藏编辑器,灌文稳定后再显示,避免闪小一号
|
|
1959
|
+
editorPaintReady.value = false
|
|
1960
|
+
//0920
|
|
1961
|
+
stopChapterContentTypewriter({ cancelStream: true })
|
|
1962
|
+
// 1) 立刻清空正文(標題隨 index 更新)
|
|
1963
|
+
chapterHistoryPreviewBody.value = ''
|
|
1964
|
+
chapterBodyCleared.value = true
|
|
1965
|
+
await nextTick()
|
|
1966
|
+
const wrap = document.querySelector(
|
|
1967
|
+
'.report-edit-page__main--anchored .report-edit-page__col-scroll .el-scrollbar__wrap'
|
|
1968
|
+
) as HTMLElement | null
|
|
1969
|
+
if (wrap) wrap.scrollTop = 0
|
|
1970
|
+
// 2) 有本地緩存則秒開,減少等接口的空白
|
|
1971
|
+
const cached = String(generatedChapterBodies.value[index] ?? '')
|
|
1972
|
+
if (cached.trim()) {
|
|
1973
|
+
chapterHistoryPreviewBody.value = cached
|
|
1974
|
+
}
|
|
1975
|
+
chapterBodyCleared.value = false
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
if (!genAiSectionItems.value.length) return
|
|
1979
|
+
void loadGenAiSectionVersionListForActiveChapter().finally(() => {
|
|
1980
|
+
if (!isSwitch) return
|
|
1981
|
+
//0920
|
|
1982
|
+
void settleEditorPaintReady()
|
|
1983
|
+
//0920
|
|
1984
|
+
})
|
|
1985
|
+
})
|
|
1986
|
+
|
|
1987
|
+
function syncLocalizedChapterTitles() {
|
|
1988
|
+
// 章節編輯:下拉僅由 getSectionList 驅動,不回落本地假標題
|
|
1989
|
+
if (!isReportSettingsMode.value) {
|
|
1990
|
+
//0920:有接口章才覆盖;加载中空列表勿清已有标题(防 select 闪空)
|
|
1991
|
+
if (genAiSectionItems.value.length) {
|
|
1992
|
+
setChapterTitlesIfChanged(genAiSectionItems.value.map(formatGenAiSectionTitle))
|
|
1993
|
+
}
|
|
1994
|
+
//0920
|
|
1995
|
+
return
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// 優先:生成接口寫入的章節標題
|
|
1999
|
+
if (generatedChapterTitles.value.length) {
|
|
2000
|
+
setChapterTitlesIfChanged(
|
|
2001
|
+
translateChapterTitles(
|
|
2002
|
+
generatedChapterTitles.value,
|
|
2003
|
+
draft.language,
|
|
2004
|
+
isReportSettingsMode.value ? 'zh-Hant' : draft.chapterContentLanguage
|
|
2005
|
+
)
|
|
2006
|
+
)
|
|
2007
|
+
return
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
if (historyPreviewRecordId.value && !historyPreviewDirty.value) {
|
|
2011
|
+
syncHistoryPreviewChapterTitles()
|
|
2012
|
+
return
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
// 僅用已選模板章節(接口/選模結果);無則空
|
|
2016
|
+
const sourceChapters = selectedTemplate.value?.chapters?.length
|
|
2017
|
+
? selectedTemplate.value.chapters
|
|
2018
|
+
: []
|
|
2019
|
+
setChapterTitlesIfChanged(
|
|
2020
|
+
sourceChapters.length
|
|
2021
|
+
? translateChapterTitles(
|
|
2022
|
+
sourceChapters,
|
|
2023
|
+
draft.language,
|
|
2024
|
+
isReportSettingsMode.value ? 'zh-Hant' : draft.chapterContentLanguage
|
|
2025
|
+
)
|
|
2026
|
+
: []
|
|
2027
|
+
)
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
watch(
|
|
2031
|
+
() =>
|
|
2032
|
+
[
|
|
2033
|
+
selectedTemplate.value?.chapters,
|
|
2034
|
+
draft.language,
|
|
2035
|
+
draft.chapterContentLanguage,
|
|
2036
|
+
generatedChapterTitles.value,
|
|
2037
|
+
] as const,
|
|
2038
|
+
() => {
|
|
2039
|
+
syncLocalizedChapterTitles()
|
|
2040
|
+
},
|
|
2041
|
+
{ immediate: true, deep: true }
|
|
2042
|
+
)
|
|
2043
|
+
|
|
2044
|
+
watch(
|
|
2045
|
+
() => historyPreviewApplyToken.value,
|
|
2046
|
+
() => {
|
|
2047
|
+
if (isReportSettingsMode.value) {
|
|
2048
|
+
settingsHistoryPreviewChapterIndex.value = 0
|
|
2049
|
+
return
|
|
2050
|
+
}
|
|
2051
|
+
syncHistoryPreviewChapterTitles()
|
|
2052
|
+
}
|
|
2053
|
+
)
|
|
2054
|
+
|
|
2055
|
+
function syncHistoryPreviewChapterTitles() {
|
|
2056
|
+
const recordId = historyPreviewRecordId.value
|
|
2057
|
+
if (!recordId) return
|
|
2058
|
+
|
|
2059
|
+
const record = findFinalizedReportById(recordId)
|
|
2060
|
+
if (!record) return
|
|
2061
|
+
|
|
2062
|
+
const titles = record.chapters.map((chapter, index) => `${index + 1}. ${chapter.label}`)
|
|
2063
|
+
chapterTitles.value = translateChapterTitles(titles, draft.language, draft.chapterContentLanguage)
|
|
2064
|
+
activeChapterIndex.value = 0
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
const chapterSelectRef = ref<SelectInstance>()
|
|
2068
|
+
const mainBoundaryEl = ref<HTMLElement | null>(null)
|
|
2069
|
+
|
|
2070
|
+
function refreshMainBoundary() {
|
|
2071
|
+
mainBoundaryEl.value = document.querySelector(
|
|
2072
|
+
'.report-edit-page__main--anchored'
|
|
2073
|
+
) as HTMLElement | null
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
/** 章節下拉浮層限制在中間 main card 內,避免超出 report-edit-page__main--anchored */
|
|
2077
|
+
const chapterSelectPopperOptions = computed(() => {
|
|
2078
|
+
const boundary = mainBoundaryEl.value ?? 'clippingParents'
|
|
2079
|
+
return {
|
|
2080
|
+
modifiers: [
|
|
2081
|
+
{
|
|
2082
|
+
name: 'preventOverflow',
|
|
2083
|
+
options: {
|
|
2084
|
+
boundary,
|
|
2085
|
+
padding: 8,
|
|
2086
|
+
altAxis: true,
|
|
2087
|
+
},
|
|
2088
|
+
},
|
|
2089
|
+
{
|
|
2090
|
+
name: 'flip',
|
|
2091
|
+
options: {
|
|
2092
|
+
boundary,
|
|
2093
|
+
padding: 8,
|
|
2094
|
+
},
|
|
2095
|
+
},
|
|
2096
|
+
],
|
|
2097
|
+
}
|
|
2098
|
+
})
|
|
2099
|
+
|
|
2100
|
+
const chapterDialogVisible = ref(false)
|
|
2101
|
+
/** 拖動重排後顯示「確認排序」;點擊確認後隱藏 */
|
|
2102
|
+
const showConfirmSortButton = ref(false)
|
|
2103
|
+
const selectDragSortIndex = ref<number | null>(null)
|
|
2104
|
+
const selectDragOverSortIndex = ref<number | null>(null)
|
|
2105
|
+
const newChapterForm = reactive({
|
|
2106
|
+
title: '',
|
|
2107
|
+
description: '',
|
|
2108
|
+
prompt: '',
|
|
2109
|
+
/** all = 所有資訊源檔案;partial = 部分資訊源檔案 */
|
|
2110
|
+
sourceMode: 'all' as 'all' | 'partial',
|
|
2111
|
+
selectedSourceIds: [] as string[],
|
|
2112
|
+
})
|
|
2113
|
+
|
|
2114
|
+
const chapterSourcePickerVisible = ref(false)
|
|
2115
|
+
const chapterSourcePickerDraft = ref<string[]>([])
|
|
2116
|
+
|
|
2117
|
+
const chapterSourcePickerCheckAll = computed({
|
|
2118
|
+
get: () =>
|
|
2119
|
+
sourceFiles.value.length > 0 &&
|
|
2120
|
+
chapterSourcePickerDraft.value.length === sourceFiles.value.length,
|
|
2121
|
+
set: (checked: boolean) => {
|
|
2122
|
+
chapterSourcePickerDraft.value = checked ? sourceFiles.value.map((f) => f.id) : []
|
|
2123
|
+
},
|
|
2124
|
+
})
|
|
2125
|
+
|
|
2126
|
+
const chapterSourcePickerIndeterminate = computed(
|
|
2127
|
+
() =>
|
|
2128
|
+
chapterSourcePickerDraft.value.length > 0 &&
|
|
2129
|
+
chapterSourcePickerDraft.value.length < sourceFiles.value.length
|
|
2130
|
+
)
|
|
2131
|
+
|
|
2132
|
+
function onChapterSourceCheckAll(checked: boolean | string | number) {
|
|
2133
|
+
chapterSourcePickerCheckAll.value = Boolean(checked)
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
function openChapterSourcePicker() {
|
|
2137
|
+
if (!sourceFiles.value.length) {
|
|
2138
|
+
ElMessage.warning('暫無資訊源檔案,請先在資訊源面板添加')
|
|
2139
|
+
return
|
|
2140
|
+
}
|
|
2141
|
+
chapterSourcePickerDraft.value = [...newChapterForm.selectedSourceIds]
|
|
2142
|
+
chapterSourcePickerVisible.value = true
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function confirmChapterSourcePicker() {
|
|
2146
|
+
if (!chapterSourcePickerDraft.value.length) {
|
|
2147
|
+
ElMessage.warning('請至少選擇一個資訊源檔案')
|
|
2148
|
+
return
|
|
2149
|
+
}
|
|
2150
|
+
newChapterForm.selectedSourceIds = [...chapterSourcePickerDraft.value]
|
|
2151
|
+
chapterSourcePickerVisible.value = false
|
|
2152
|
+
ElMessage.success(`已選擇 ${newChapterForm.selectedSourceIds.length} 個資訊源`)
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
watch(
|
|
2156
|
+
() => newChapterForm.sourceMode,
|
|
2157
|
+
(mode) => {
|
|
2158
|
+
if (mode === 'all') {
|
|
2159
|
+
newChapterForm.selectedSourceIds = []
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
)
|
|
2163
|
+
/** 下拉內 Sortable 實例(彈層打開時綁定,關閉時銷毀) */
|
|
2164
|
+
let selectSortableInstance: Sortable | null = null
|
|
2165
|
+
let selectListClickGuard: ((event: Event) => void) | null = null
|
|
2166
|
+
let selectDragMoved = false
|
|
2167
|
+
|
|
2168
|
+
function closeChapterSelect() {
|
|
2169
|
+
chapterSelectRef.value?.blur()
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
/** 確認當前下拉拖動後的章節順序(調用 updateSectionOrder) */
|
|
2173
|
+
async function handleConfirmSort() {
|
|
2174
|
+
if (!genAiSectionItems.value.length) {
|
|
2175
|
+
return
|
|
2176
|
+
}
|
|
2177
|
+
const result = await updateGenAiSectionOrder()
|
|
2178
|
+
if (!result.ok) return
|
|
2179
|
+
showConfirmSortButton.value = false
|
|
2180
|
+
closeChapterSelect()
|
|
2181
|
+
ElMessage.success('章節順序已更新')
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
function moveActiveIndexAfterReorder(activeIndex: number, oldIndex: number, newIndex: number) {
|
|
2185
|
+
if (activeIndex === oldIndex) return newIndex
|
|
2186
|
+
if (oldIndex < activeIndex && newIndex >= activeIndex) return activeIndex - 1
|
|
2187
|
+
if (oldIndex > activeIndex && newIndex <= activeIndex) return activeIndex + 1
|
|
2188
|
+
return activeIndex
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
function reorderStringList(list: string[], oldIndex: number, newIndex: number) {
|
|
2192
|
+
const next = [...list]
|
|
2193
|
+
const [moved] = next.splice(oldIndex, 1)
|
|
2194
|
+
if (moved == null) return list
|
|
2195
|
+
next.splice(newIndex, 0, moved)
|
|
2196
|
+
return next
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function reorderList<T>(list: T[], oldIndex: number, newIndex: number): T[] {
|
|
2200
|
+
const next = [...list]
|
|
2201
|
+
const [moved] = next.splice(oldIndex, 1)
|
|
2202
|
+
if (moved === undefined) return list
|
|
2203
|
+
next.splice(newIndex, 0, moved)
|
|
2204
|
+
return next
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
function renumberChapterTitles(titles: string[]) {
|
|
2208
|
+
return titles.map((title, index) => {
|
|
2209
|
+
const label = stripChapterNumberPrefix(title)
|
|
2210
|
+
return label ? `${index + 1}. ${label}` : `${index + 1}.`
|
|
2211
|
+
})
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
watch(
|
|
2215
|
+
() => chapterTitles.value.length,
|
|
2216
|
+
(length) => {
|
|
2217
|
+
if (activeChapterIndex.value >= length) {
|
|
2218
|
+
activeChapterIndex.value = Math.max(0, length - 1)
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
)
|
|
2222
|
+
|
|
2223
|
+
const activeChapterTitle = computed(() => chapterTitles.value[activeChapterIndex.value] ?? '')
|
|
2224
|
+
|
|
2225
|
+
// new prompt sections
|
|
2226
|
+
/** 無章節時不綁 0,觸發器顯示空白 */
|
|
2227
|
+
const chapterSelectModelValue = computed(() =>
|
|
2228
|
+
chapterTitles.value.length ? activeChapterIndex.value : undefined
|
|
2229
|
+
)
|
|
2230
|
+
|
|
2231
|
+
/** //0920:pending 占位与即将显示的标题一致,避免尺寸跳变 */
|
|
2232
|
+
const chapterSelectPendingLabel = computed(
|
|
2233
|
+
() => chapterTitles.value[activeChapterIndex.value] || '\u00a0'
|
|
2234
|
+
)
|
|
2235
|
+
//0920
|
|
2236
|
+
|
|
2237
|
+
function onChapterSelectModelUpdate(value: unknown) {
|
|
2238
|
+
if (value == null || value === '') return
|
|
2239
|
+
if (typeof value !== 'number' || Number.isNaN(value)) return
|
|
2240
|
+
if (value < 0 || value >= chapterTitles.value.length) return
|
|
2241
|
+
if (value === activeChapterIndex.value) return
|
|
2242
|
+
// 編輯中禁止切章
|
|
2243
|
+
if (chapterEditMode.value) return
|
|
2244
|
+
// 清空 / 停打字 / 拉版本列表由 watch(activeChapterIndex) 統一處理
|
|
2245
|
+
activeChapterIndex.value = value
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
/** // 0924:流还在推时打字机已追平。不能在这里关打字机,否则逐字效果被掐断 */
|
|
2249
|
+
const chapterTypewriterIdleDuringStream = ref(false)
|
|
2250
|
+
|
|
2251
|
+
function freezeChapterTypewriterToStatic() {
|
|
2252
|
+
const body = String(chapterHistoryPreviewBody.value ?? '')
|
|
2253
|
+
if (body.trim()) patchGeneratedChapterBody(activeChapterIndex.value, body)
|
|
2254
|
+
staticEditorTitle.value = previewChapterTitle.value
|
|
2255
|
+
staticEditorIntro.value = previewChapterIntro.value
|
|
2256
|
+
chapterTypewriterIdleDuringStream.value = false
|
|
2257
|
+
stopChapterContentTypewriter()
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function onChapterTypewriterComplete() {
|
|
2261
|
+
const body = String(chapterHistoryPreviewBody.value ?? '')
|
|
2262
|
+
if (body.trim()) {
|
|
2263
|
+
patchGeneratedChapterBody(activeChapterIndex.value, body)
|
|
2264
|
+
}
|
|
2265
|
+
// 0924:SSE 未结束先不关打字机;流结束后先冻结满文再关
|
|
2266
|
+
if (chapterContentStreamLive.value) {
|
|
2267
|
+
chapterTypewriterIdleDuringStream.value = true
|
|
2268
|
+
return
|
|
2269
|
+
}
|
|
2270
|
+
staticEditorTitle.value = previewChapterTitle.value
|
|
2271
|
+
staticEditorIntro.value = previewChapterIntro.value
|
|
2272
|
+
stopChapterContentTypewriter()
|
|
2273
|
+
// 0924
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// 新 chunk 到达:打字机要续打,取消“已追平”标记
|
|
2277
|
+
watch(chapterHistoryPreviewBody, () => {
|
|
2278
|
+
if (!chapterContentStreamLive.value || !chapterTypewriterActive.value) return
|
|
2279
|
+
chapterTypewriterIdleDuringStream.value = false
|
|
2280
|
+
})
|
|
2281
|
+
|
|
2282
|
+
// 0924:流结束且打字机已经追平,才冻结标题正文并收口
|
|
2283
|
+
watch(chapterContentStreamLive, (live) => {
|
|
2284
|
+
if (live) {
|
|
2285
|
+
chapterTypewriterIdleDuringStream.value = false
|
|
2286
|
+
return
|
|
2287
|
+
}
|
|
2288
|
+
if (!chapterTypewriterActive.value || !chapterTypewriterIdleDuringStream.value) return
|
|
2289
|
+
freezeChapterTypewriterToStatic()
|
|
2290
|
+
})
|
|
2291
|
+
// 0924
|
|
2292
|
+
// new prompt sections end
|
|
2293
|
+
|
|
2294
|
+
const activeChapterIntro = computed(() => {
|
|
2295
|
+
// 切章瞬間:強制空白,避免舊章正文殘留
|
|
2296
|
+
if (chapterBodyCleared.value) return ''
|
|
2297
|
+
// 打字機進行中只認 preview body,避免 '' 被當成空而回落到舊 generated 正文
|
|
2298
|
+
if (chapterTypewriterActive.value) {
|
|
2299
|
+
return String(chapterHistoryPreviewBody.value ?? '')
|
|
2300
|
+
}
|
|
2301
|
+
// 原文交給 editor 解析(Markdown / HTML / tip),此處不剝標籤
|
|
2302
|
+
if (!isReportSettingsMode.value) {
|
|
2303
|
+
if (chapterHistoryPreviewBody.value) return String(chapterHistoryPreviewBody.value)
|
|
2304
|
+
const generated = generatedChapterBodies.value[activeChapterIndex.value]
|
|
2305
|
+
if (generated) return String(generated)
|
|
2306
|
+
return ''
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
if (chapterHistoryPreviewBody.value) return String(chapterHistoryPreviewBody.value)
|
|
2310
|
+
|
|
2311
|
+
const generated = generatedChapterBodies.value[activeChapterIndex.value]
|
|
2312
|
+
if (generated) return String(generated)
|
|
2313
|
+
|
|
2314
|
+
return ''
|
|
2315
|
+
})
|
|
2316
|
+
|
|
2317
|
+
const previewChapterTitle = computed(() => {
|
|
2318
|
+
if (isReportSettingsMode.value && isViewingReportHistory.value) {
|
|
2319
|
+
const record = findFinalizedReportById(historyPreviewRecordId.value!)
|
|
2320
|
+
const chapter = record?.chapters[settingsHistoryPreviewChapterIndex.value]
|
|
2321
|
+
if (!chapter) return '報告預覽'
|
|
2322
|
+
const label = translateChapterLabel(chapter.label, draft.language, 'zh-Hant')
|
|
2323
|
+
return `${settingsHistoryPreviewChapterIndex.value + 1}. ${label}`
|
|
2324
|
+
}
|
|
2325
|
+
return activeChapterTitle.value
|
|
2326
|
+
})
|
|
2327
|
+
|
|
2328
|
+
const previewChapterIntro = computed(() => {
|
|
2329
|
+
if (isReportSettingsMode.value && isViewingReportHistory.value) {
|
|
2330
|
+
const historyBody = historyPreviewChapterBodies.value[settingsHistoryPreviewChapterIndex.value]
|
|
2331
|
+
if (historyBody) return String(historyBody)
|
|
2332
|
+
return '暫無報告歷史預覽內容。'
|
|
2333
|
+
}
|
|
2334
|
+
return activeChapterIntro.value
|
|
2335
|
+
})
|
|
2336
|
+
|
|
2337
|
+
//0920:静态进页只提交一次快照;其后 token/历史 watch 再写 preview 不再进编辑器
|
|
2338
|
+
const staticEditorTitle = ref('')
|
|
2339
|
+
const staticEditorIntro = ref('')
|
|
2340
|
+
|
|
2341
|
+
function commitStaticEditorSnapshot() {
|
|
2342
|
+
staticEditorTitle.value = previewChapterTitle.value
|
|
2343
|
+
staticEditorIntro.value = previewChapterIntro.value
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
/** //0920:用户切历史时递增,驱动编辑器 identity,确保反显一定灌文 */
|
|
2347
|
+
const staticEditorRev = ref(0)
|
|
2348
|
+
|
|
2349
|
+
const editorChapterTitle = computed(() =>
|
|
2350
|
+
chapterTypewriterActive.value ? previewChapterTitle.value : staticEditorTitle.value
|
|
2351
|
+
)
|
|
2352
|
+
const editorChapterIntro = computed(() =>
|
|
2353
|
+
chapterTypewriterActive.value ? previewChapterIntro.value : staticEditorIntro.value
|
|
2354
|
+
)
|
|
2355
|
+
const editorContentIdentity = computed(() =>
|
|
2356
|
+
chapterTypewriterActive.value
|
|
2357
|
+
? `tw-${chapterHistoryApplyToken.value}`
|
|
2358
|
+
: `static-${activeChapterIndex.value}-${staticEditorRev.value}`
|
|
2359
|
+
)
|
|
2360
|
+
//0920
|
|
2361
|
+
|
|
2362
|
+
const editorKey = computed(() => {
|
|
2363
|
+
if (isReportSettingsMode.value) {
|
|
2364
|
+
return `settings-${historyPreviewRecordId.value ?? 'draft'}-${historyPreviewApplyToken.value}`
|
|
2365
|
+
}
|
|
2366
|
+
//0918-1
|
|
2367
|
+
//0920:只按章节 index remount;history/title 变化走 props 灌文,禁止进页闪三次
|
|
2368
|
+
return `chapter-${activeChapterIndex.value}`
|
|
2369
|
+
//0920
|
|
2370
|
+
//0918-1
|
|
2371
|
+
})
|
|
2372
|
+
|
|
2373
|
+
function destroySelectSortable() {
|
|
2374
|
+
const list = getSelectDropdownListEl()
|
|
2375
|
+
if (list && selectListClickGuard) {
|
|
2376
|
+
list.removeEventListener('click', selectListClickGuard, true)
|
|
2377
|
+
}
|
|
2378
|
+
selectListClickGuard = null
|
|
2379
|
+
selectSortableInstance?.destroy()
|
|
2380
|
+
selectSortableInstance = null
|
|
2381
|
+
selectDragSortIndex.value = null
|
|
2382
|
+
selectDragOverSortIndex.value = null
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function getSelectDropdownListEl() {
|
|
2386
|
+
return document.querySelector(
|
|
2387
|
+
'.report-chapter-workspace__chapter-select-popper .el-select-dropdown__list'
|
|
2388
|
+
) as HTMLElement | null
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
function initSelectSortable() {
|
|
2392
|
+
destroySelectSortable()
|
|
2393
|
+
const list = getSelectDropdownListEl()
|
|
2394
|
+
if (!list) return
|
|
2395
|
+
|
|
2396
|
+
selectListClickGuard = (event: Event) => {
|
|
2397
|
+
if (!selectDragMoved) return
|
|
2398
|
+
event.preventDefault()
|
|
2399
|
+
event.stopImmediatePropagation()
|
|
2400
|
+
}
|
|
2401
|
+
list.addEventListener('click', selectListClickGuard, true)
|
|
2402
|
+
|
|
2403
|
+
selectSortableInstance = Sortable.create(list, {
|
|
2404
|
+
animation: 150,
|
|
2405
|
+
draggable: '.el-select-dropdown__item',
|
|
2406
|
+
forceFallback: true,
|
|
2407
|
+
fallbackOnBody: true,
|
|
2408
|
+
fallbackClass: 'report-chapter-workspace__select-sortable-fallback',
|
|
2409
|
+
// Sortable 新版類型已無 distance;用 touchStartThreshold + fallbackTolerance 等效防誤拖
|
|
2410
|
+
fallbackTolerance: 4,
|
|
2411
|
+
touchStartThreshold: 4,
|
|
2412
|
+
ghostClass: 'is-drag-over',
|
|
2413
|
+
chosenClass: 'is-dragging',
|
|
2414
|
+
dragClass: 'is-dragging',
|
|
2415
|
+
onStart({ oldIndex }) {
|
|
2416
|
+
selectDragMoved = true
|
|
2417
|
+
selectDragSortIndex.value = oldIndex ?? null
|
|
2418
|
+
},
|
|
2419
|
+
onMove(evt) {
|
|
2420
|
+
const related = evt.related
|
|
2421
|
+
selectDragOverSortIndex.value = related
|
|
2422
|
+
? Array.from(list.children).indexOf(related as HTMLElement)
|
|
2423
|
+
: null
|
|
2424
|
+
return true
|
|
2425
|
+
},
|
|
2426
|
+
onEnd({ oldIndex, newIndex }) {
|
|
2427
|
+
selectDragSortIndex.value = null
|
|
2428
|
+
selectDragOverSortIndex.value = null
|
|
2429
|
+
|
|
2430
|
+
const shouldReorder = oldIndex != null && newIndex != null && oldIndex !== newIndex
|
|
2431
|
+
|
|
2432
|
+
if (shouldReorder) {
|
|
2433
|
+
if (genAiSectionItems.value.length) {
|
|
2434
|
+
genAiSectionItems.value = reorderList(genAiSectionItems.value, oldIndex, newIndex).map(
|
|
2435
|
+
(item, i) => ({
|
|
2436
|
+
...item,
|
|
2437
|
+
orderSeq: i + 1,
|
|
2438
|
+
})
|
|
2439
|
+
)
|
|
2440
|
+
chapterTitles.value = genAiSectionItems.value.map(formatGenAiSectionTitle)
|
|
2441
|
+
} else {
|
|
2442
|
+
chapterTitles.value = renumberChapterTitles(
|
|
2443
|
+
reorderStringList(chapterTitles.value, oldIndex, newIndex)
|
|
2444
|
+
)
|
|
2445
|
+
}
|
|
2446
|
+
activeChapterIndex.value = moveActiveIndexAfterReorder(
|
|
2447
|
+
activeChapterIndex.value,
|
|
2448
|
+
oldIndex,
|
|
2449
|
+
newIndex
|
|
2450
|
+
)
|
|
2451
|
+
showConfirmSortButton.value = true
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
window.setTimeout(() => {
|
|
2455
|
+
selectDragMoved = false
|
|
2456
|
+
}, 0)
|
|
2457
|
+
|
|
2458
|
+
if (shouldReorder) {
|
|
2459
|
+
void nextTick(() => {
|
|
2460
|
+
initSelectSortable()
|
|
2461
|
+
})
|
|
2462
|
+
}
|
|
2463
|
+
},
|
|
2464
|
+
})
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
async function onChapterSelectVisibleChange(visible: boolean) {
|
|
2468
|
+
if (!visible) {
|
|
2469
|
+
destroySelectSortable()
|
|
2470
|
+
selectDragMoved = false
|
|
2471
|
+
return
|
|
2472
|
+
}
|
|
2473
|
+
refreshMainBoundary()
|
|
2474
|
+
await nextTick()
|
|
2475
|
+
requestAnimationFrame(() => {
|
|
2476
|
+
const preferred = CHAPTER_PANEL_MAX_ROWS * CHAPTER_PANEL_ROW_HEIGHT
|
|
2477
|
+
const main = document.querySelector('.report-edit-page__main--anchored') as HTMLElement | null
|
|
2478
|
+
const selectEl = chapterSelectRef.value?.$el as HTMLElement | undefined
|
|
2479
|
+
let available = preferred
|
|
2480
|
+
if (main && selectEl) {
|
|
2481
|
+
const mainRect = main.getBoundingClientRect()
|
|
2482
|
+
const selectRect = selectEl.getBoundingClientRect()
|
|
2483
|
+
const footerReserve = 56
|
|
2484
|
+
const gap = 18
|
|
2485
|
+
const spaceBelow = mainRect.bottom - selectRect.bottom - gap - footerReserve
|
|
2486
|
+
const spaceAbove = selectRect.top - mainRect.top - gap - footerReserve
|
|
2487
|
+
available = Math.max(
|
|
2488
|
+
CHAPTER_PANEL_ROW_HEIGHT * 3,
|
|
2489
|
+
Math.min(preferred, Math.max(spaceBelow, spaceAbove))
|
|
2490
|
+
)
|
|
2491
|
+
}
|
|
2492
|
+
const listMaxHeight = `${Math.floor(available)}px`
|
|
2493
|
+
const wrap = document.querySelector(
|
|
2494
|
+
'.report-chapter-workspace__chapter-select-popper .el-select-dropdown__wrap'
|
|
2495
|
+
) as HTMLElement | null
|
|
2496
|
+
if (wrap) {
|
|
2497
|
+
wrap.style.setProperty('max-height', listMaxHeight, 'important')
|
|
2498
|
+
wrap.style.setProperty('height', 'auto', 'important')
|
|
2499
|
+
wrap.style.setProperty('overflow-y', 'auto', 'important')
|
|
2500
|
+
wrap.style.setProperty('scrollbar-width', 'none', 'important')
|
|
2501
|
+
}
|
|
2502
|
+
const scrollbar = document.querySelector(
|
|
2503
|
+
'.report-chapter-workspace__chapter-select-popper .el-select-dropdown > .el-scrollbar'
|
|
2504
|
+
) as HTMLElement | null
|
|
2505
|
+
if (scrollbar) {
|
|
2506
|
+
scrollbar.style.setProperty('max-height', listMaxHeight, 'important')
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
const needsScroll = !!wrap && wrap.scrollHeight > wrap.clientHeight + 1
|
|
2510
|
+
const bar = document.querySelector(
|
|
2511
|
+
'.report-chapter-workspace__chapter-select-popper .el-scrollbar__bar.is-vertical'
|
|
2512
|
+
) as HTMLElement | null
|
|
2513
|
+
const thumb = bar?.querySelector('.el-scrollbar__thumb') as HTMLElement | null
|
|
2514
|
+
if (bar) {
|
|
2515
|
+
bar.style.setProperty('display', needsScroll ? 'block' : 'none', 'important')
|
|
2516
|
+
bar.style.setProperty('width', '8px', 'important')
|
|
2517
|
+
bar.style.setProperty('opacity', '1', 'important')
|
|
2518
|
+
}
|
|
2519
|
+
if (thumb && needsScroll) {
|
|
2520
|
+
thumb.style.setProperty('width', '8px', 'important')
|
|
2521
|
+
thumb.style.setProperty('height', '50px', 'important')
|
|
2522
|
+
thumb.style.setProperty('min-height', '50px', 'important')
|
|
2523
|
+
thumb.style.setProperty('opacity', '1', 'important')
|
|
2524
|
+
thumb.style.setProperty('background-color', '#999', 'important')
|
|
2525
|
+
}
|
|
2526
|
+
initSelectSortable()
|
|
2527
|
+
})
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
onUnmounted(() => {
|
|
2531
|
+
registerChapterHistoryReloader(null)
|
|
2532
|
+
destroySelectSortable()
|
|
2533
|
+
selectDragMoved = false
|
|
2534
|
+
})
|
|
2535
|
+
|
|
2536
|
+
function getChapterStatus(index: number) {
|
|
2537
|
+
if (index === activeChapterIndex.value && chapterEditMode.value) {
|
|
2538
|
+
return '編輯中'
|
|
2539
|
+
}
|
|
2540
|
+
const apiItem = genAiSectionItems.value[index]
|
|
2541
|
+
if (apiItem?.latestStatus) {
|
|
2542
|
+
return SECTION_STATUS_LABEL[apiItem.latestStatus] ?? apiItem.latestStatus
|
|
2543
|
+
}
|
|
2544
|
+
return genAiSectionItems.value.length ? '未生成' : ''
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function resetNewChapterForm() {
|
|
2548
|
+
newChapterForm.title = ''
|
|
2549
|
+
newChapterForm.description = ''
|
|
2550
|
+
newChapterForm.prompt = ''
|
|
2551
|
+
newChapterForm.sourceMode = 'all'
|
|
2552
|
+
newChapterForm.selectedSourceIds = []
|
|
2553
|
+
chapterSourcePickerDraft.value = []
|
|
2554
|
+
chapterSourcePickerVisible.value = false
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// new prompt sections
|
|
2558
|
+
function openAddChapterDialog() {
|
|
2559
|
+
closeChapterSelect()
|
|
2560
|
+
resetNewChapterForm()
|
|
2561
|
+
chapterDialogVisible.value = true
|
|
2562
|
+
}
|
|
2563
|
+
// new prompt sections end
|
|
2564
|
+
|
|
2565
|
+
function closeChapterDialog() {
|
|
2566
|
+
chapterDialogVisible.value = false
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
function handleChapterDialogClosed() {
|
|
2570
|
+
resetNewChapterForm()
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
function handleAddChapterSubmit() {
|
|
2574
|
+
const title = newChapterForm.title.trim()
|
|
2575
|
+
const description = newChapterForm.description.trim()
|
|
2576
|
+
const prompt = newChapterForm.prompt.trim()
|
|
2577
|
+
|
|
2578
|
+
if (!title) {
|
|
2579
|
+
ElMessage.warning('請輸入章節標題')
|
|
2580
|
+
return
|
|
2581
|
+
}
|
|
2582
|
+
if (!description) {
|
|
2583
|
+
ElMessage.warning('請輸入章節描述')
|
|
2584
|
+
return
|
|
2585
|
+
}
|
|
2586
|
+
if (!prompt) {
|
|
2587
|
+
ElMessage.warning('請輸入 Prompt')
|
|
2588
|
+
return
|
|
2589
|
+
}
|
|
2590
|
+
if (newChapterForm.sourceMode === 'partial' && !newChapterForm.selectedSourceIds.length) {
|
|
2591
|
+
ElMessage.warning('請選擇資訊源檔案')
|
|
2592
|
+
return
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
chapterTitles.value = [...chapterTitles.value, title]
|
|
2596
|
+
activeChapterIndex.value = chapterTitles.value.length - 1
|
|
2597
|
+
closeChapterDialog()
|
|
2598
|
+
ElMessage.success('章節已提交並生成內容')
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
function handleGenerateReport() {
|
|
2602
|
+
// if (!sourceFiles.value.length) {
|
|
2603
|
+
// ElMessage.warning('請至少添加一個資訊源檔案')
|
|
2604
|
+
// return
|
|
2605
|
+
// }
|
|
2606
|
+
stopChapterContentTypewriter()
|
|
2607
|
+
// 先跳轉報告頁(settings);進頁後 watch 調 AdjustInstance
|
|
2608
|
+
emit('generate-report')
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
function handleBackToChapterEdit() {
|
|
2612
|
+
resetHistoryPreviewState()
|
|
2613
|
+
emit('back-to-chapter-edit')
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function handleApplyCreateTemplate() {
|
|
2617
|
+
applyTemplateDetail.value = buildApplyTemplateDetail()
|
|
2618
|
+
resetApplyTemplateForm()
|
|
2619
|
+
applyTemplateDialogVisible.value = true
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
async function handleApplyTemplateSubmit() {
|
|
2623
|
+
if (!applyTemplateTitle.value.trim()) {
|
|
2624
|
+
ElMessage.warning('請輸入模板標題')
|
|
2625
|
+
return
|
|
2626
|
+
}
|
|
2627
|
+
if (!applyTemplateDescription.value.trim()) {
|
|
2628
|
+
ElMessage.warning('請輸入模板描述')
|
|
2629
|
+
return
|
|
2630
|
+
}
|
|
2631
|
+
if (!applyTemplateAction.value) {
|
|
2632
|
+
ElMessage.warning('請選擇處理事項')
|
|
2633
|
+
return
|
|
2634
|
+
}
|
|
2635
|
+
if (applyTemplateAction.value === 'reject' && !applyTemplateOpinion.value.trim()) {
|
|
2636
|
+
ElMessage.warning('拒絕時請填寫意見')
|
|
2637
|
+
return
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
const isReject = applyTemplateAction.value === 'reject'
|
|
2641
|
+
try {
|
|
2642
|
+
await ElMessageBox.confirm(
|
|
2643
|
+
isReject ? '確定拒絕嗎?' : '確定通過嗎?',
|
|
2644
|
+
isReject ? '拒絕' : '通過',
|
|
2645
|
+
{
|
|
2646
|
+
confirmButtonText: '確定',
|
|
2647
|
+
cancelButtonText: '取消',
|
|
2648
|
+
type: 'warning',
|
|
2649
|
+
}
|
|
2650
|
+
)
|
|
2651
|
+
} catch {
|
|
2652
|
+
return
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
applyTemplateDialogVisible.value = false
|
|
2656
|
+
if (isReject) {
|
|
2657
|
+
ElMessage.success('已拒絕該模板申請')
|
|
2658
|
+
return
|
|
2659
|
+
}
|
|
2660
|
+
ElMessage.success('已提交模板申請,待審批')
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
function handleViewCoherenceCheck() {
|
|
2664
|
+
const session = loadCoherenceCheckSession()
|
|
2665
|
+
if (session) {
|
|
2666
|
+
openReportCoherenceCheckResult(router, session)
|
|
2667
|
+
return
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
const recordId = historyPreviewRecordId.value
|
|
2671
|
+
if (recordId) {
|
|
2672
|
+
const record = findFinalizedReportById(recordId)
|
|
2673
|
+
if (record) {
|
|
2674
|
+
openReportCoherenceCheckResult(router, {
|
|
2675
|
+
templateId: selectedTemplate.value?.id ?? record.templateId,
|
|
2676
|
+
reportName: draft.reportName,
|
|
2677
|
+
year: draft.year,
|
|
2678
|
+
chapters: record.chapters,
|
|
2679
|
+
chapterContentLanguage: record.chapterContentLanguage,
|
|
2680
|
+
reportLanguage: record.reportLanguage,
|
|
2681
|
+
})
|
|
2682
|
+
return
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
ElMessage.warning('請先進行報告連貫性檢查')
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
const wordExportRootRef = ref<HTMLElement | null>(null)
|
|
2690
|
+
const isExportingWord = ref(false)
|
|
2691
|
+
|
|
2692
|
+
const instanceId = ref('')
|
|
2693
|
+
const editors = ref<any[]>([])
|
|
2694
|
+
const editorList = ref<any[]>([])
|
|
2695
|
+
/** 以接口 res.data 為準:有 data 才視為可用來源 */
|
|
2696
|
+
const editorsHasData = ref(false)
|
|
2697
|
+
const editorListHasData = ref(false)
|
|
2698
|
+
|
|
2699
|
+
const userGenAiBuildReport = genAiBuildReportStore
|
|
2700
|
+
|
|
2701
|
+
const init = async () => {
|
|
2702
|
+
await userGenAiBuildReport.initFromStorage()
|
|
2703
|
+
const instanceIdRoute: any = route.query.instanceId
|
|
2704
|
+
instanceId.value = route.query.instanceId
|
|
2705
|
+
? instanceIdRoute
|
|
2706
|
+
: (userGenAiBuildReport.instanceId as any) || ''
|
|
2707
|
+
if (instanceId.value) {
|
|
2708
|
+
userGenAiBuildReport.setInstanceId(String(instanceId.value))
|
|
2709
|
+
}
|
|
2710
|
+
getReportRoles({
|
|
2711
|
+
categoryId: 1,
|
|
2712
|
+
instanceId: route.query.instanceId ? instanceIdRoute : userGenAiBuildReport.instanceId,
|
|
2713
|
+
})
|
|
2714
|
+
getEditorList()
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
// report所有编辑者
|
|
2718
|
+
const getReportRoles = async (params: any) => {
|
|
2719
|
+
const res: any = await genAiReportPermissionApi.fetchGenAiReportPermissionList(params)
|
|
2720
|
+
try {
|
|
2721
|
+
if (res.data) {
|
|
2722
|
+
console.log('res.data + xx :', res.data)
|
|
2723
|
+
|
|
2724
|
+
const realSortCreator = res.data.find((item: any) => item.userRole === 'CREATOR')
|
|
2725
|
+
const realSortEditor = res.data.filter((item: any) => item.userRole !== 'CREATOR')
|
|
2726
|
+
editors.value = realSortCreator ? [realSortCreator, ...realSortEditor] : realSortEditor
|
|
2727
|
+
editorsHasData.value = true
|
|
2728
|
+
} else {
|
|
2729
|
+
editors.value = []
|
|
2730
|
+
editorsHasData.value = false
|
|
2731
|
+
}
|
|
2732
|
+
} catch {
|
|
2733
|
+
editorsHasData.value = false
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
const getEditorList = async () => {
|
|
2738
|
+
const res: any = await genAiReportPermissionApi.fetchGenAiReportPermissionEditorList({})
|
|
2739
|
+
try {
|
|
2740
|
+
if (res.data) {
|
|
2741
|
+
editorList.value = res.data
|
|
2742
|
+
editorListHasData.value = true
|
|
2743
|
+
} else {
|
|
2744
|
+
editorList.value = []
|
|
2745
|
+
editorListHasData.value = false
|
|
2746
|
+
}
|
|
2747
|
+
} catch {
|
|
2748
|
+
editorListHasData.value = false
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
/** 兩條導出共用:始終取權威正文(非打字機半成品 / 非流式佔位) */
|
|
2753
|
+
function getWordExportChapters() {
|
|
2754
|
+
if (isReportSettingsMode.value) {
|
|
2755
|
+
// 用 settingsTypewriterChapters(AdjustInstance / 歷史回寫後的完整 Markdown)
|
|
2756
|
+
// 禁止 settingsDocChapters:流式生成時會變成單條 stream stub
|
|
2757
|
+
return settingsTypewriterChapters.value.map((chapter) => ({
|
|
2758
|
+
title: chapter.title,
|
|
2759
|
+
content: chapter.intro,
|
|
2760
|
+
}))
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// Prompt 模式:當前章取最新(打字機目標正文 / 預覽 / 已生成緩存)
|
|
2764
|
+
// 打字機進行動畫時 preview 已是完整目標串,不是 DOM 上已打出的片段
|
|
2765
|
+
return [
|
|
2766
|
+
{
|
|
2767
|
+
title: previewChapterTitle.value,
|
|
2768
|
+
content: previewChapterIntro.value,
|
|
2769
|
+
},
|
|
2770
|
+
]
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
/** 導出前等 Vue 刷完(歷史切換 / 章節勾選回寫後的 computed) */
|
|
2774
|
+
async function settleWordExportSource() {
|
|
2775
|
+
await nextTick()
|
|
2776
|
+
await nextTick()
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
/** 按 res.data 可用性合併 getReportRoles / getEditorList,映射為導出 members */
|
|
2780
|
+
function mapEditorListToExportMembers() {
|
|
2781
|
+
const rows: any[] = []
|
|
2782
|
+
const seen = new Set<string>()
|
|
2783
|
+
|
|
2784
|
+
const pushAll = (list: any[]) => {
|
|
2785
|
+
for (const item of list || []) {
|
|
2786
|
+
const department = String(item?.userUnitSeqDesc ?? '').trim()
|
|
2787
|
+
const name = String(item?.userName ?? '').trim()
|
|
2788
|
+
if (!department && !name) continue
|
|
2789
|
+
const key = String(item?.userId ?? `${department}\0${name}`).trim()
|
|
2790
|
+
if (!key || seen.has(key)) continue
|
|
2791
|
+
seen.add(key)
|
|
2792
|
+
rows.push({ department, name })
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
// 以 res.data 為判斷:有 data 的來源才納入;兩者都有則合併(roles 在前,保 CREATOR 序)
|
|
2797
|
+
if (editorsHasData.value) pushAll(editors.value)
|
|
2798
|
+
if (editorListHasData.value) pushAll(editorList.value)
|
|
2799
|
+
|
|
2800
|
+
return rows
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
/** 報告配置「名稱」→ 導出封面 / 檔名同源 */
|
|
2804
|
+
function getReportConfigNameForExport() {
|
|
2805
|
+
return (draft.reportName || '').trim() || '行業調研報告'
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
/** 對齊行業調研導出模板:封面 / 目錄 / 小組成員 / 參考文獻 */
|
|
2809
|
+
function getWordExportReportMeta() {
|
|
2810
|
+
const chapters = getWordExportChapters()
|
|
2811
|
+
|
|
2812
|
+
return {
|
|
2813
|
+
reportName: getReportConfigNameForExport(),
|
|
2814
|
+
year: String(draft.year || '').trim(),
|
|
2815
|
+
members: mapEditorListToExportMembers(),
|
|
2816
|
+
sourceFileNames: sourceFiles.value
|
|
2817
|
+
.filter((file) => file.selected || sourceFiles.value.every((f) => !f.selected))
|
|
2818
|
+
.map((file) => file.name)
|
|
2819
|
+
.filter(Boolean),
|
|
2820
|
+
tocTitles: chapters.map((chapter) => String(chapter.title ?? '').trim()).filter(Boolean),
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
function assertWordExportChaptersReady() {
|
|
2825
|
+
if (isReportSettingsMode.value && !settingsPreviewChapters.value.length) {
|
|
2826
|
+
ElMessage.warning('請先勾選至少一個章節')
|
|
2827
|
+
return null
|
|
2828
|
+
}
|
|
2829
|
+
const chapters = getWordExportChapters()
|
|
2830
|
+
const hasContent = chapters.some(
|
|
2831
|
+
(chapter) => String(chapter.title ?? '').trim() || String(chapter.content ?? '').trim()
|
|
2832
|
+
)
|
|
2833
|
+
if (!hasContent) {
|
|
2834
|
+
ElMessage.warning('暫無可導出內容')
|
|
2835
|
+
return null
|
|
2836
|
+
}
|
|
2837
|
+
return chapters
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
/**
|
|
2841
|
+
* 兩條導出按鈕共用:權威章節 Markdown → AIExport 打包。
|
|
2842
|
+
* 不走 DOM/altChunk 半成品路徑,避免遷移環境出現 --------tDocumentpart-- 殘片。
|
|
2843
|
+
*/
|
|
2844
|
+
async function runWordExport(filename: string) {
|
|
2845
|
+
if (isExportingWord.value) return
|
|
2846
|
+
await settleWordExportSource()
|
|
2847
|
+
const chapters = assertWordExportChaptersReady()
|
|
2848
|
+
if (!chapters) return
|
|
2849
|
+
|
|
2850
|
+
try {
|
|
2851
|
+
isExportingWord.value = true
|
|
2852
|
+
await nextTick()
|
|
2853
|
+
await exportMarkdownToDocxWord(chapters, {
|
|
2854
|
+
filename,
|
|
2855
|
+
headerText: DOCX_HEADER_CLASSIFICATION,
|
|
2856
|
+
showPageNumbers: true,
|
|
2857
|
+
reportMeta: getWordExportReportMeta(),
|
|
2858
|
+
})
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
console.error('Word 導出失敗:', error)
|
|
2861
|
+
ElMessage.error(
|
|
2862
|
+
error instanceof Error && error.message.trim()
|
|
2863
|
+
? error.message
|
|
2864
|
+
: 'Word 導出失敗,請查看瀏覽器控制檯'
|
|
2865
|
+
)
|
|
2866
|
+
} finally {
|
|
2867
|
+
isExportingWord.value = false
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
/** 版本號統一為 Vn(對齊生成報告設置 / existsInstanceVersionNo) */
|
|
2872
|
+
function normalizeExportVersionNo(raw: unknown): string {
|
|
2873
|
+
if (raw == null || raw === '') return 'V1'
|
|
2874
|
+
let text = String(raw).trim()
|
|
2875
|
+
if (!text) return 'V1'
|
|
2876
|
+
text = text.replace(/^報告版本\s*/i, '').trim()
|
|
2877
|
+
text = text.toUpperCase()
|
|
2878
|
+
if (!text) return 'V1'
|
|
2879
|
+
return text.startsWith('V') ? text : `V${text}`
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
function sanitizeExportFilenamePart(value: string) {
|
|
2883
|
+
return value.replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, ' ').trim()
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
/**
|
|
2887
|
+
* 檔名:[GenAI草稿] [報告名稱] [版本號] [語言] ([年份]年).docx
|
|
2888
|
+
* 報告名稱 / 語言 / 年份 ← 報告配置;版本號 ← 生成報告設置 existsInstanceVersionNo
|
|
2889
|
+
*/
|
|
2890
|
+
function buildWordExportFilename() {
|
|
2891
|
+
const name = sanitizeExportFilenamePart(getReportConfigNameForExport()) || '行業調研報告'
|
|
2892
|
+
const version = normalizeExportVersionNo(existsInstanceVersionNo.value)
|
|
2893
|
+
const language = sanitizeExportFilenamePart(
|
|
2894
|
+
getReportLanguageLabel(String(draft.language || 'zh-Hant'))
|
|
2895
|
+
)
|
|
2896
|
+
const year =
|
|
2897
|
+
String(draft.year || '').trim() || String(new Date().getFullYear())
|
|
2898
|
+
return `[GenAI草稿] ${name} ${version} ${language} (${year}年).docx`
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
async function handleExportWord1() {
|
|
2902
|
+
await runWordExport(buildWordExportFilename())
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
async function handleExportWord2() {
|
|
2906
|
+
await runWordExport(buildWordExportFilename())
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
async function handleCancelEdit() {
|
|
2910
|
+
if (!chapterEditMode.value) return
|
|
2911
|
+
|
|
2912
|
+
if (!isChapterEditDirty()) {
|
|
2913
|
+
exitChapterEditMode(true)
|
|
2914
|
+
return
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
try {
|
|
2918
|
+
await ElMessageBox.confirm('您已修改部分內容且未儲存,確定要進行「取消」的操作嗎?', '提示', {
|
|
2919
|
+
confirmButtonText: '確定',
|
|
2920
|
+
cancelButtonText: '取消',
|
|
2921
|
+
type: 'warning',
|
|
2922
|
+
})
|
|
2923
|
+
exitChapterEditMode(true)
|
|
2924
|
+
} catch {
|
|
2925
|
+
// 用戶取消操作,保持編輯狀態
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
async function handleEditSave() {
|
|
2930
|
+
if (!chapterEditMode.value) {
|
|
2931
|
+
// 點「編輯」:嘗試 changeSectionStatus;失敗不阻斷進入編輯
|
|
2932
|
+
void changeGenAiSectionStatus()
|
|
2933
|
+
enterChapterEditMode()
|
|
2934
|
+
return
|
|
2935
|
+
}
|
|
2936
|
+
// 點「儲存」:draftFlag=N
|
|
2937
|
+
const result = await saveGenAiSectionVersion('N')
|
|
2938
|
+
if (!result.ok) return
|
|
2939
|
+
commitChapterEdit()
|
|
2940
|
+
ElMessage.success('章節內容已儲存')
|
|
2941
|
+
// 刷新章節歷史(失敗則保持當前,不阻斷)
|
|
2942
|
+
void loadGenAiSectionVersionListForActiveChapter()
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
/** 編輯態「存為草稿」:draftFlag=Y 調 saveSectionVersion */
|
|
2946
|
+
async function handleSaveDraft() {
|
|
2947
|
+
const result = await saveGenAiSectionVersion('Y')
|
|
2948
|
+
if (!result.ok) return
|
|
2949
|
+
saveDraftToSession()
|
|
2950
|
+
ElMessage.success('已存為草稿')
|
|
2951
|
+
void loadGenAiSectionVersionListForActiveChapter()
|
|
2952
|
+
}
|
|
2953
|
+
</script>
|
|
2954
|
+
|
|
2955
|
+
<style scoped>
|
|
2956
|
+
.report-chapter-workspace {
|
|
2957
|
+
--report-accent: #c53355;
|
|
2958
|
+
--report-accent-soft: #fdf0f2;
|
|
2959
|
+
--report-card-bg: #f5f5f5;
|
|
2960
|
+
--report-table-row-hover-bg: #f5f7fa;
|
|
2961
|
+
position: relative;
|
|
2962
|
+
display: flex;
|
|
2963
|
+
flex-direction: column;
|
|
2964
|
+
gap: 0;
|
|
2965
|
+
min-width: 0;
|
|
2966
|
+
flex: 1 0 auto;
|
|
2967
|
+
min-height: 0;
|
|
2968
|
+
height: auto;
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
.report-chapter-workspace :deep(.el-loading-spinner .circular) {
|
|
2972
|
+
width: 56px;
|
|
2973
|
+
height: 56px;
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
.report-chapter-workspace :deep(.el-loading-spinner .el-loading-text) {
|
|
2977
|
+
margin-top: 12px;
|
|
2978
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
2979
|
+
font-size: 16px;
|
|
2980
|
+
font-weight: 500;
|
|
2981
|
+
color: var(--report-accent);
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
.report-chapter-workspace__toolbar {
|
|
2985
|
+
flex-shrink: 0;
|
|
2986
|
+
display: flex;
|
|
2987
|
+
align-items: flex-start;
|
|
2988
|
+
justify-content: space-between;
|
|
2989
|
+
gap: 12px;
|
|
2990
|
+
box-sizing: border-box;
|
|
2991
|
+
/* 隨中欄滾動時吸頂,工具列本身不滾走 */
|
|
2992
|
+
position: sticky;
|
|
2993
|
+
top: 0;
|
|
2994
|
+
z-index: 20;
|
|
2995
|
+
margin: 0;
|
|
2996
|
+
/* 下邊距與中欄 card 上邊距(16px)一致 */
|
|
2997
|
+
padding-bottom: 16px;
|
|
2998
|
+
background: #fff;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
.report-chapter-workspace__editor-shell {
|
|
3002
|
+
display: flex;
|
|
3003
|
+
flex-direction: column;
|
|
3004
|
+
gap: 14px;
|
|
3005
|
+
min-height: 280px;
|
|
3006
|
+
padding-top: 8px;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
.report-chapter-workspace__editor-shell-title {
|
|
3010
|
+
width: min(420px, 70%);
|
|
3011
|
+
height: 28px;
|
|
3012
|
+
border-radius: 4px;
|
|
3013
|
+
background: linear-gradient(90deg, #f2f3f5 25%, #e9ebef 37%, #f2f3f5 63%);
|
|
3014
|
+
background-size: 400% 100%;
|
|
3015
|
+
animation: report-chapter-editor-shell-shimmer 1.2s ease-in-out infinite;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
.report-chapter-workspace__editor-shell-line {
|
|
3019
|
+
width: 100%;
|
|
3020
|
+
height: 14px;
|
|
3021
|
+
border-radius: 4px;
|
|
3022
|
+
background: linear-gradient(90deg, #f5f6f8 25%, #eceef2 37%, #f5f6f8 63%);
|
|
3023
|
+
background-size: 400% 100%;
|
|
3024
|
+
animation: report-chapter-editor-shell-shimmer 1.2s ease-in-out infinite;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
.report-chapter-workspace__editor-shell-line.is-short {
|
|
3028
|
+
width: 55%;
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
@keyframes report-chapter-editor-shell-shimmer {
|
|
3032
|
+
0% {
|
|
3033
|
+
background-position: 100% 0;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
100% {
|
|
3037
|
+
background-position: 0 0;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
.report-chapter-workspace__chapter-box {
|
|
3042
|
+
position: relative;
|
|
3043
|
+
min-width: 0;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
/* //0920:外壳 + facade 文案;内部 selected-item 透明,杜绝 wrapper 空→满闪 */
|
|
3047
|
+
.report-chapter-workspace__chapter-select-shell {
|
|
3048
|
+
position: relative;
|
|
3049
|
+
width: 220px;
|
|
3050
|
+
min-width: 180px;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
.report-chapter-workspace__chapter-select-facade {
|
|
3054
|
+
position: absolute;
|
|
3055
|
+
z-index: 1;
|
|
3056
|
+
top: 0;
|
|
3057
|
+
bottom: 0;
|
|
3058
|
+
left: 12px;
|
|
3059
|
+
right: 28px;
|
|
3060
|
+
display: flex;
|
|
3061
|
+
align-items: center;
|
|
3062
|
+
overflow: hidden;
|
|
3063
|
+
text-overflow: ellipsis;
|
|
3064
|
+
white-space: nowrap;
|
|
3065
|
+
pointer-events: none;
|
|
3066
|
+
font-size: 15px;
|
|
3067
|
+
font-weight: 400;
|
|
3068
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3069
|
+
color: #303133;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
.report-chapter-workspace__chapter-select-shell:hover:not(.is-disabled)
|
|
3073
|
+
.report-chapter-workspace__chapter-select-facade,
|
|
3074
|
+
.report-chapter-workspace__chapter-select-shell:focus-within:not(.is-disabled)
|
|
3075
|
+
.report-chapter-workspace__chapter-select-facade {
|
|
3076
|
+
color: var(--report-accent);
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
.report-chapter-workspace__chapter-select-shell.is-disabled
|
|
3080
|
+
.report-chapter-workspace__chapter-select-facade {
|
|
3081
|
+
color: #a8abb2;
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
.report-chapter-workspace__chapter-select {
|
|
3085
|
+
width: 100%;
|
|
3086
|
+
min-width: 0;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__wrapper) {
|
|
3090
|
+
min-height: 39px;
|
|
3091
|
+
padding: 0 12px;
|
|
3092
|
+
border-radius: 4px;
|
|
3093
|
+
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
|
3094
|
+
transition: box-shadow 0.2s ease;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
/* 内部选中字透明:视觉只认 facade,避免 EP 注册 option 前闪空 */
|
|
3098
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__selected-item),
|
|
3099
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__placeholder) {
|
|
3100
|
+
color: transparent !important;
|
|
3101
|
+
-webkit-text-fill-color: transparent;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
.report-chapter-workspace__chapter-select--pending {
|
|
3105
|
+
box-sizing: border-box;
|
|
3106
|
+
min-height: 39px;
|
|
3107
|
+
border-radius: 4px;
|
|
3108
|
+
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
|
3109
|
+
background: #fff;
|
|
3110
|
+
pointer-events: none;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
/* pending 时补箭头,尺寸与真实 select 一致 */
|
|
3114
|
+
.report-chapter-workspace__chapter-select-shell.is-pending::after {
|
|
3115
|
+
content: '';
|
|
3116
|
+
position: absolute;
|
|
3117
|
+
z-index: 2;
|
|
3118
|
+
top: 50%;
|
|
3119
|
+
right: 12px;
|
|
3120
|
+
width: 0;
|
|
3121
|
+
height: 0;
|
|
3122
|
+
margin-top: -3px;
|
|
3123
|
+
border-left: 5px solid transparent;
|
|
3124
|
+
border-right: 5px solid transparent;
|
|
3125
|
+
border-top: 6px solid #dcdfe6;
|
|
3126
|
+
pointer-events: none;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
/* 覆盖下方旧选中字色规则:始终透明,文案只走 facade */
|
|
3130
|
+
.report-chapter-workspace__chapter-select
|
|
3131
|
+
:deep(.el-select__wrapper.is-disabled .el-select__selected-item),
|
|
3132
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__selected-item),
|
|
3133
|
+
.report-chapter-workspace__chapter-select
|
|
3134
|
+
:deep(.el-select__wrapper:hover .el-select__selected-item),
|
|
3135
|
+
.report-chapter-workspace__chapter-select
|
|
3136
|
+
:deep(.el-select__wrapper.is-focused .el-select__selected-item) {
|
|
3137
|
+
color: transparent !important;
|
|
3138
|
+
-webkit-text-fill-color: transparent;
|
|
3139
|
+
}
|
|
3140
|
+
/* //0920 */
|
|
3141
|
+
|
|
3142
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__wrapper:hover:not(.is-disabled)),
|
|
3143
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__wrapper.is-focused:not(.is-disabled)) {
|
|
3144
|
+
box-shadow: 0 0 0 1px var(--report-accent) inset;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__wrapper.is-disabled) {
|
|
3148
|
+
cursor: not-allowed;
|
|
3149
|
+
background-color: #f5f5f5;
|
|
3150
|
+
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
.report-chapter-workspace__chapter-select
|
|
3154
|
+
:deep(.el-select__wrapper.is-disabled .el-select__selected-item) {
|
|
3155
|
+
color: #a8abb2;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__selected-item) {
|
|
3159
|
+
font-size: 15px;
|
|
3160
|
+
font-weight: 400;
|
|
3161
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3162
|
+
color: #303133;
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
.report-chapter-workspace__chapter-select
|
|
3166
|
+
:deep(.el-select__wrapper:hover .el-select__selected-item),
|
|
3167
|
+
.report-chapter-workspace__chapter-select
|
|
3168
|
+
:deep(.el-select__wrapper.is-focused .el-select__selected-item) {
|
|
3169
|
+
color: var(--report-accent);
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__caret) {
|
|
3173
|
+
color: #dcdfe6;
|
|
3174
|
+
font-size: 15px;
|
|
3175
|
+
transition: color 0.2s ease;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__caret svg) {
|
|
3179
|
+
color: inherit;
|
|
3180
|
+
fill: currentColor;
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__wrapper:hover .el-select__caret),
|
|
3184
|
+
.report-chapter-workspace__chapter-select :deep(.el-select__wrapper.is-focused .el-select__caret) {
|
|
3185
|
+
color: var(--report-accent);
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
.report-chapter-workspace__chapter-option {
|
|
3189
|
+
display: flex;
|
|
3190
|
+
align-items: center;
|
|
3191
|
+
justify-content: space-between;
|
|
3192
|
+
gap: 8px;
|
|
3193
|
+
width: 100%;
|
|
3194
|
+
min-width: 0;
|
|
3195
|
+
font-size: 15px;
|
|
3196
|
+
font-weight: 400;
|
|
3197
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
.report-chapter-workspace__chapter-option .report-chapter-workspace__sort-handle {
|
|
3201
|
+
margin-top: 0;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
.report-chapter-workspace__chapter-option-title {
|
|
3205
|
+
flex: 1;
|
|
3206
|
+
min-width: 0;
|
|
3207
|
+
overflow: hidden;
|
|
3208
|
+
text-overflow: ellipsis;
|
|
3209
|
+
white-space: nowrap;
|
|
3210
|
+
font-size: 15px;
|
|
3211
|
+
font-weight: 400;
|
|
3212
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3213
|
+
color: #303133;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
.report-chapter-workspace__chapter-option-title.is-active {
|
|
3217
|
+
color: var(--report-accent);
|
|
3218
|
+
font-weight: 400;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
.report-chapter-workspace__chapter-option-status {
|
|
3222
|
+
flex-shrink: 0;
|
|
3223
|
+
font-size: 15px;
|
|
3224
|
+
font-weight: 400;
|
|
3225
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3226
|
+
color: #606266;
|
|
3227
|
+
white-space: nowrap;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
/* // new prompt sections — toolbar 側 footer 佈局(彈層內按鈕樣式見下方同標記區塊) */
|
|
3231
|
+
.report-chapter-workspace__chapter-select-footer {
|
|
3232
|
+
display: flex;
|
|
3233
|
+
align-items: center;
|
|
3234
|
+
justify-content: center;
|
|
3235
|
+
gap: 12px;
|
|
3236
|
+
padding: 10px 12px;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
.report-chapter-workspace__chapter-select-footer-btn {
|
|
3240
|
+
min-width: 120px;
|
|
3241
|
+
}
|
|
3242
|
+
/* // new prompt sections end */
|
|
3243
|
+
|
|
3244
|
+
.report-chapter-workspace__toolbar-right {
|
|
3245
|
+
display: flex;
|
|
3246
|
+
align-items: center;
|
|
3247
|
+
gap: 12px;
|
|
3248
|
+
flex-shrink: 0;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
/* 覆蓋 Element Plus .el-button+.el-button 默認 margin-left:12px,避免與 gap 疊加;按鈕間距統一為 12px */
|
|
3252
|
+
.report-chapter-workspace__toolbar-right :deep(.el-button) {
|
|
3253
|
+
margin-left: 0 !important;
|
|
3254
|
+
margin-right: 0 !important;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
/* 取消 / 存為草稿 / 儲存 / 編輯/儲存 / 生成報告 */
|
|
3258
|
+
.report-chapter-workspace__action-btn {
|
|
3259
|
+
width: 100px;
|
|
3260
|
+
min-width: 100px;
|
|
3261
|
+
max-width: 100px;
|
|
3262
|
+
padding: 0 8px;
|
|
3263
|
+
font-size: 16px;
|
|
3264
|
+
font-weight: 500;
|
|
3265
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
@media (max-width: 1024px) {
|
|
3269
|
+
.report-chapter-workspace__toolbar {
|
|
3270
|
+
flex-wrap: wrap;
|
|
3271
|
+
align-items: stretch;
|
|
3272
|
+
gap: 10px;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
/* 章節 select:隨容器等比例縮小 */
|
|
3276
|
+
.report-chapter-workspace__chapter-box {
|
|
3277
|
+
flex: 1 1 auto;
|
|
3278
|
+
min-width: 0;
|
|
3279
|
+
max-width: 100%;
|
|
3280
|
+
width: 100%;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
.report-chapter-workspace__chapter-select {
|
|
3284
|
+
width: 100%;
|
|
3285
|
+
min-width: 0;
|
|
3286
|
+
max-width: 100%;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
/* 操作按鈕整組換行 */
|
|
3290
|
+
.report-chapter-workspace__toolbar-right {
|
|
3291
|
+
flex: 1 1 100%;
|
|
3292
|
+
width: 100%;
|
|
3293
|
+
flex-wrap: wrap;
|
|
3294
|
+
justify-content: flex-start;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
.report-chapter-workspace__action-btn {
|
|
3298
|
+
width: 100px;
|
|
3299
|
+
min-width: 0;
|
|
3300
|
+
max-width: 100px;
|
|
3301
|
+
flex: 0 1 auto;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
.report-chapter-workspace__sort-handle {
|
|
3306
|
+
display: inline-flex;
|
|
3307
|
+
align-items: center;
|
|
3308
|
+
justify-content: center;
|
|
3309
|
+
flex-shrink: 0;
|
|
3310
|
+
width: 16px;
|
|
3311
|
+
height: 16px;
|
|
3312
|
+
margin-top: 3px;
|
|
3313
|
+
color: #86909c;
|
|
3314
|
+
cursor: grab;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
.report-chapter-workspace__sort-handle:active {
|
|
3318
|
+
cursor: grabbing;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
.report-chapter-workspace__editor {
|
|
3322
|
+
flex: 1 0 auto;
|
|
3323
|
+
min-height: 420px;
|
|
3324
|
+
border-radius: 8px;
|
|
3325
|
+
overflow: visible;
|
|
3326
|
+
display: flex;
|
|
3327
|
+
flex-direction: column;
|
|
3328
|
+
background: #fff;
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
/* //0920:灌文/滚动条 gutter 未稳定前隐藏,杜绝进页「正常→小一号→正常」 */
|
|
3332
|
+
.report-chapter-workspace__tiptap:not(.is-paint-ready) {
|
|
3333
|
+
opacity: 0 !important;
|
|
3334
|
+
visibility: hidden !important;
|
|
3335
|
+
pointer-events: none !important;
|
|
3336
|
+
}
|
|
3337
|
+
.report-chapter-workspace__tiptap.is-paint-ready {
|
|
3338
|
+
opacity: 1;
|
|
3339
|
+
visibility: visible;
|
|
3340
|
+
}
|
|
3341
|
+
/* //0920 */
|
|
3342
|
+
|
|
3343
|
+
.report-chapter-workspace__settings-doc {
|
|
3344
|
+
flex: 1 0 auto;
|
|
3345
|
+
min-height: 0;
|
|
3346
|
+
overflow: visible;
|
|
3347
|
+
display: flex;
|
|
3348
|
+
flex-direction: column;
|
|
3349
|
+
background: #fff;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
.report-chapter-workspace__settings-doc
|
|
3353
|
+
:deep(.ProseMirror > *:first-child:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6)),
|
|
3354
|
+
.report-chapter-workspace__editor
|
|
3355
|
+
:deep(.ProseMirror > *:first-child:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6)) {
|
|
3356
|
+
margin-top: 0 !important;
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
.report-chapter-workspace__settings-doc :deep(.ProseMirror h2),
|
|
3360
|
+
.report-chapter-workspace__editor :deep(.ProseMirror h2) {
|
|
3361
|
+
margin: 24px 0 !important;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
.gen-ai-action-btn,
|
|
3365
|
+
.template-center-toolbar-btn {
|
|
3366
|
+
font-size: 15px;
|
|
3367
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3368
|
+
font-weight: 500;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
.template-center-toolbar-btn {
|
|
3372
|
+
display: inline-flex;
|
|
3373
|
+
align-items: center;
|
|
3374
|
+
justify-content: center;
|
|
3375
|
+
gap: 6px;
|
|
3376
|
+
box-sizing: border-box;
|
|
3377
|
+
height: 32px;
|
|
3378
|
+
min-width: 120px;
|
|
3379
|
+
padding: 0 14px;
|
|
3380
|
+
border-radius: 4px;
|
|
3381
|
+
line-height: 1;
|
|
3382
|
+
white-space: nowrap;
|
|
3383
|
+
cursor: pointer;
|
|
3384
|
+
transition: all 0.2s ease;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
.template-center-toolbar-btn--wide {
|
|
3388
|
+
min-width: 160px;
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
.template-center-toolbar-btn:disabled {
|
|
3392
|
+
opacity: 0.45;
|
|
3393
|
+
cursor: not-allowed;
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
.template-center-toolbar-btn--primary {
|
|
3397
|
+
border: 1px solid var(--report-accent);
|
|
3398
|
+
background: var(--report-accent);
|
|
3399
|
+
color: #fff;
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
.template-center-toolbar-btn--primary:hover:not(:disabled) {
|
|
3403
|
+
background: #fff;
|
|
3404
|
+
border-color: var(--report-accent);
|
|
3405
|
+
color: var(--report-accent);
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
.template-center-toolbar-btn--outline {
|
|
3409
|
+
border: 1px solid var(--report-accent);
|
|
3410
|
+
color: var(--report-accent);
|
|
3411
|
+
background: #fff;
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
.template-center-toolbar-btn--outline:hover:not(:disabled) {
|
|
3415
|
+
border-color: var(--report-accent);
|
|
3416
|
+
color: var(--report-accent);
|
|
3417
|
+
background: var(--report-accent-soft);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
.chatbot-report-btn-outline.el-button,
|
|
3421
|
+
.chatbot-report-btn-primary.el-button {
|
|
3422
|
+
height: 32px !important;
|
|
3423
|
+
padding-inline: 14px !important;
|
|
3424
|
+
border-radius: 4px !important;
|
|
3425
|
+
font-size: 16px !important;
|
|
3426
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif) !important;
|
|
3427
|
+
font-weight: 500 !important;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
.chatbot-report-btn-outline.el-button {
|
|
3431
|
+
border: 1px solid var(--report-accent) !important;
|
|
3432
|
+
color: var(--report-accent) !important;
|
|
3433
|
+
background: #fff !important;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
.chatbot-report-btn-outline.el-button:hover {
|
|
3437
|
+
background: var(--report-accent-soft) !important;
|
|
3438
|
+
color: var(--report-accent) !important;
|
|
3439
|
+
border-color: var(--report-accent) !important;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
.chatbot-report-btn-outline--wide.el-button {
|
|
3443
|
+
min-width: 0;
|
|
3444
|
+
max-width: 220px;
|
|
3445
|
+
white-space: normal;
|
|
3446
|
+
line-height: 1.3;
|
|
3447
|
+
height: auto !important;
|
|
3448
|
+
min-height: 32px;
|
|
3449
|
+
padding-block: 6px !important;
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
.chatbot-report-btn-primary.el-button {
|
|
3453
|
+
border: 1px solid var(--report-accent) !important;
|
|
3454
|
+
background: var(--report-accent) !important;
|
|
3455
|
+
color: #fff !important;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
.chatbot-report-btn-primary.el-button:hover {
|
|
3459
|
+
background: #fff !important;
|
|
3460
|
+
color: var(--report-accent) !important;
|
|
3461
|
+
border-color: var(--report-accent) !important;
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
.chatbot-report-btn-primary--wide.el-button {
|
|
3465
|
+
min-width: 0;
|
|
3466
|
+
max-width: 220px;
|
|
3467
|
+
white-space: normal;
|
|
3468
|
+
line-height: 1.3;
|
|
3469
|
+
height: auto !important;
|
|
3470
|
+
min-height: 32px;
|
|
3471
|
+
padding-block: 6px !important;
|
|
3472
|
+
}
|
|
3473
|
+
</style>
|
|
3474
|
+
|
|
3475
|
+
<style>
|
|
3476
|
+
/* 新增章節彈窗:尺寸 / 位置由 reportEdit 頁 CSS 變量對齊中間卡片 */
|
|
3477
|
+
.report-chapter-dialog.report-workflow-dialog.el-dialog {
|
|
3478
|
+
--el-dialog-padding-primary: 0;
|
|
3479
|
+
--report-accent: #c53355;
|
|
3480
|
+
--report-accent-soft: #fdf0f2;
|
|
3481
|
+
border-radius: 4px;
|
|
3482
|
+
overflow: hidden;
|
|
3483
|
+
display: flex;
|
|
3484
|
+
flex-direction: column;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
.report-workflow-dialog .el-dialog__header,
|
|
3488
|
+
.report-workflow-dialog__header {
|
|
3489
|
+
display: flex;
|
|
3490
|
+
align-items: center;
|
|
3491
|
+
justify-content: space-between;
|
|
3492
|
+
gap: 16px;
|
|
3493
|
+
margin: 0;
|
|
3494
|
+
padding: 14px 16px 14px 20px;
|
|
3495
|
+
/* 標題欄樣式見 fonts.css(全局 dialog) */
|
|
3496
|
+
background: transparent;
|
|
3497
|
+
border-bottom: 1px solid #ebeef5;
|
|
3498
|
+
flex-shrink: 0;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
/* 1. 新增章節:去掉標題欄下劃線 */
|
|
3502
|
+
.report-chapter-dialog .el-dialog__header,
|
|
3503
|
+
.report-chapter-dialog .report-workflow-dialog__header {
|
|
3504
|
+
border-bottom: none !important;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
.report-workflow-dialog__title {
|
|
3508
|
+
flex: 1;
|
|
3509
|
+
min-width: 0;
|
|
3510
|
+
color: #303133;
|
|
3511
|
+
font-size: 18px;
|
|
3512
|
+
font-weight: 600;
|
|
3513
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3514
|
+
line-height: 1.4;
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
.report-workflow-dialog__close {
|
|
3518
|
+
flex-shrink: 0;
|
|
3519
|
+
width: 32px;
|
|
3520
|
+
height: 32px;
|
|
3521
|
+
margin: 0;
|
|
3522
|
+
padding: 0;
|
|
3523
|
+
border: none;
|
|
3524
|
+
background: transparent;
|
|
3525
|
+
color: #303133;
|
|
3526
|
+
font-size: 18px;
|
|
3527
|
+
font-weight: 400;
|
|
3528
|
+
line-height: 1;
|
|
3529
|
+
cursor: pointer;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
.report-workflow-dialog__close:hover {
|
|
3533
|
+
color: #606266;
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
.report-chapter-dialog .template-center-toolbar-btn {
|
|
3537
|
+
display: inline-flex;
|
|
3538
|
+
align-items: center;
|
|
3539
|
+
justify-content: center;
|
|
3540
|
+
gap: 6px;
|
|
3541
|
+
box-sizing: border-box;
|
|
3542
|
+
height: 32px;
|
|
3543
|
+
min-width: 120px;
|
|
3544
|
+
padding: 0 14px;
|
|
3545
|
+
border-radius: 4px;
|
|
3546
|
+
line-height: 1;
|
|
3547
|
+
white-space: nowrap;
|
|
3548
|
+
cursor: pointer;
|
|
3549
|
+
font-size: 15px;
|
|
3550
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3551
|
+
font-weight: 400;
|
|
3552
|
+
transition: all 0.2s ease;
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
.report-chapter-dialog .template-center-toolbar-btn--primary {
|
|
3556
|
+
border: 1px solid var(--report-accent);
|
|
3557
|
+
background: var(--report-accent);
|
|
3558
|
+
color: #fff;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
.report-chapter-dialog .template-center-toolbar-btn--primary:hover:not(:disabled) {
|
|
3562
|
+
background: #fff;
|
|
3563
|
+
border-color: var(--report-accent);
|
|
3564
|
+
color: var(--report-accent);
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
.report-chapter-dialog .template-center-toolbar-btn--outline {
|
|
3568
|
+
border: 1px solid var(--report-accent);
|
|
3569
|
+
color: var(--report-accent);
|
|
3570
|
+
background: #fff;
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
.report-chapter-dialog .template-center-toolbar-btn--outline:hover:not(:disabled) {
|
|
3574
|
+
border-color: var(--report-accent);
|
|
3575
|
+
color: var(--report-accent);
|
|
3576
|
+
background: var(--report-accent-soft);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
.report-chapter-dialog .el-dialog__body {
|
|
3580
|
+
flex: 1 1 auto;
|
|
3581
|
+
min-height: 0;
|
|
3582
|
+
/* 3. 加大內容區,給 Prompt 字數統計留空 */
|
|
3583
|
+
padding: 24px 20px 32px;
|
|
3584
|
+
background: #fff;
|
|
3585
|
+
overflow-y: auto;
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
.report-chapter-dialog .el-dialog__footer {
|
|
3589
|
+
/* 4. 原下邊距 16px × 1.5 = 24px */
|
|
3590
|
+
padding: 12px 16px 24px;
|
|
3591
|
+
background: #fff;
|
|
3592
|
+
display: flex;
|
|
3593
|
+
justify-content: center;
|
|
3594
|
+
flex-shrink: 0;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
.report-chapter-dialog__form {
|
|
3598
|
+
display: flex;
|
|
3599
|
+
flex-direction: column;
|
|
3600
|
+
gap: 18px;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
.report-chapter-dialog__field {
|
|
3604
|
+
margin: 0;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
.report-chapter-dialog__field--row {
|
|
3608
|
+
display: grid;
|
|
3609
|
+
grid-template-columns: 100px minmax(0, 1fr);
|
|
3610
|
+
gap: 16px;
|
|
3611
|
+
align-items: start;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
.report-chapter-dialog__field--textarea {
|
|
3615
|
+
align-items: start;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
.report-chapter-dialog__label {
|
|
3619
|
+
padding-top: 8px;
|
|
3620
|
+
font-size: 15px;
|
|
3621
|
+
font-weight: 500;
|
|
3622
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3623
|
+
color: #303133;
|
|
3624
|
+
line-height: 1.4;
|
|
3625
|
+
white-space: nowrap;
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
.report-chapter-dialog__required {
|
|
3629
|
+
color: #c53355;
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
.report-chapter-dialog__control {
|
|
3633
|
+
min-width: 0;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
.report-chapter-dialog__input :deep(.el-input__wrapper),
|
|
3637
|
+
.report-chapter-dialog__textarea :deep(.el-textarea__inner) {
|
|
3638
|
+
border-radius: 2px;
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
.report-chapter-dialog__textarea :deep(.el-textarea__inner) {
|
|
3642
|
+
min-height: 250px;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
.report-chapter-dialog__footer {
|
|
3646
|
+
display: flex;
|
|
3647
|
+
align-items: center;
|
|
3648
|
+
justify-content: center;
|
|
3649
|
+
gap: 16px;
|
|
3650
|
+
width: 100%;
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
.report-chapter-dialog__field--source {
|
|
3654
|
+
align-items: flex-start;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
.report-chapter-dialog__source-control {
|
|
3658
|
+
min-height: 32px;
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
.report-chapter-dialog__source-radios {
|
|
3662
|
+
display: flex;
|
|
3663
|
+
flex-direction: column;
|
|
3664
|
+
align-items: flex-start;
|
|
3665
|
+
gap: 10px;
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
.report-chapter-dialog__source-partial-row {
|
|
3669
|
+
display: flex;
|
|
3670
|
+
flex-wrap: wrap;
|
|
3671
|
+
align-items: center;
|
|
3672
|
+
gap: 12px;
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
.report-chapter-dialog__source-radios :deep(.el-radio) {
|
|
3676
|
+
margin-right: 0;
|
|
3677
|
+
height: auto;
|
|
3678
|
+
font-size: 15px;
|
|
3679
|
+
font-weight: 400;
|
|
3680
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3681
|
+
color: var(--report-accent, #c53355);
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
.report-chapter-dialog__source-radios :deep(.el-radio__label) {
|
|
3685
|
+
font-size: 15px;
|
|
3686
|
+
font-weight: 400;
|
|
3687
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3688
|
+
color: var(--report-accent, #c53355) !important;
|
|
3689
|
+
padding-left: 8px;
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
.report-chapter-dialog__source-link {
|
|
3693
|
+
font-size: 15px;
|
|
3694
|
+
font-weight: 500;
|
|
3695
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3696
|
+
color: var(--report-accent, #c53355);
|
|
3697
|
+
text-decoration: underline;
|
|
3698
|
+
cursor: pointer;
|
|
3699
|
+
line-height: 1.4;
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
.report-chapter-dialog__source-link:hover {
|
|
3703
|
+
color: #a82845;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
.report-chapter-dialog__source-hint {
|
|
3707
|
+
font-size: 13px;
|
|
3708
|
+
color: #909399;
|
|
3709
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
.report-chapter-dialog__footer-btn {
|
|
3713
|
+
min-width: 140px;
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
.report-chapter-source-picker.el-dialog {
|
|
3717
|
+
--el-dialog-padding-primary: 0;
|
|
3718
|
+
border-radius: 4px;
|
|
3719
|
+
overflow: hidden;
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
.report-chapter-source-picker .el-dialog__header {
|
|
3723
|
+
border-bottom: none !important;
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
.report-chapter-source-picker .el-dialog__body {
|
|
3727
|
+
padding: 8px 20px 20px;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
.report-chapter-source-picker .el-dialog__footer {
|
|
3731
|
+
padding: 12px 16px 24px;
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
.report-chapter-source-picker__body {
|
|
3735
|
+
display: flex;
|
|
3736
|
+
flex-direction: column;
|
|
3737
|
+
gap: 12px;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
.report-chapter-source-picker__list {
|
|
3741
|
+
display: flex;
|
|
3742
|
+
flex-direction: column;
|
|
3743
|
+
gap: 8px;
|
|
3744
|
+
max-height: 320px;
|
|
3745
|
+
overflow-y: auto;
|
|
3746
|
+
padding: 8px 12px;
|
|
3747
|
+
border: 1px solid #ebeef5;
|
|
3748
|
+
border-radius: 4px;
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
.report-chapter-source-picker__item {
|
|
3752
|
+
margin-right: 0;
|
|
3753
|
+
height: auto;
|
|
3754
|
+
white-space: normal;
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
.report-chapter-source-picker__empty {
|
|
3758
|
+
margin: 0;
|
|
3759
|
+
padding: 24px 0;
|
|
3760
|
+
text-align: center;
|
|
3761
|
+
color: #909399;
|
|
3762
|
+
font-size: 14px;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
.report-workflow-dialog-overlay {
|
|
3766
|
+
overflow: auto;
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
.report-workflow-dialog.el-dialog {
|
|
3770
|
+
--el-dialog-padding-primary: 0;
|
|
3771
|
+
/* 寬高 / 對齊由 reportEdit 頁 --report-edit-main-* 變量控制;此處僅作無錨點時的兜底 */
|
|
3772
|
+
width: 60% !important;
|
|
3773
|
+
max-width: 80%;
|
|
3774
|
+
min-height: 60vh;
|
|
3775
|
+
height: auto;
|
|
3776
|
+
max-height: 90vh;
|
|
3777
|
+
margin-top: 5% !important;
|
|
3778
|
+
margin-left: 21% !important;
|
|
3779
|
+
margin-right: auto !important;
|
|
3780
|
+
border-radius: 4px;
|
|
3781
|
+
overflow: hidden;
|
|
3782
|
+
display: flex;
|
|
3783
|
+
flex-direction: column;
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
.report-workflow-dialog .el-dialog__header {
|
|
3787
|
+
flex-shrink: 0;
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3790
|
+
.report-workflow-dialog .el-dialog__body {
|
|
3791
|
+
flex: 1;
|
|
3792
|
+
min-height: 0;
|
|
3793
|
+
overflow-y: auto;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
.report-workflow-dialog .el-dialog__footer {
|
|
3797
|
+
flex-shrink: 0;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
.report-chapter-workspace__chapter-select-popper.el-popper {
|
|
3801
|
+
--report-accent: #c53355;
|
|
3802
|
+
--report-accent-soft: #fdf0f2;
|
|
3803
|
+
width: 450px !important;
|
|
3804
|
+
min-width: 450px !important;
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
/* 箭頭對齊 select 右側 caret 正下方(select 寬 220px) */
|
|
3808
|
+
.report-chapter-workspace__chapter-select-popper.el-popper .el-popper__arrow {
|
|
3809
|
+
left: 200px !important;
|
|
3810
|
+
right: auto !important;
|
|
3811
|
+
transform: translateX(-50%) !important;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
.report-chapter-workspace__chapter-select-popper.el-popper[data-popper-placement^='bottom']
|
|
3815
|
+
.el-popper__arrow {
|
|
3816
|
+
top: -5px !important;
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
.report-chapter-workspace__chapter-select-popper.el-popper[data-popper-placement^='top']
|
|
3820
|
+
.el-popper__arrow {
|
|
3821
|
+
bottom: -5px !important;
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown {
|
|
3825
|
+
width: 450px;
|
|
3826
|
+
min-width: 450px;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
/* 最多顯示 14 章(14 × 40px),超出出現滾動條;只保留一條 EP 自定義條 */
|
|
3830
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown > .el-scrollbar {
|
|
3831
|
+
max-height: 560px !important;
|
|
3832
|
+
height: auto !important;
|
|
3833
|
+
overflow: hidden !important;
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__wrap,
|
|
3837
|
+
.report-chapter-workspace__chapter-select-popper .el-scrollbar__wrap,
|
|
3838
|
+
.report-chapter-workspace__chapter-select-popper .el-scrollbar__wrap--hidden-default {
|
|
3839
|
+
max-height: 560px !important;
|
|
3840
|
+
height: auto !important;
|
|
3841
|
+
max-width: 100% !important;
|
|
3842
|
+
overflow-x: hidden !important;
|
|
3843
|
+
overflow-y: auto !important;
|
|
3844
|
+
/* 徹底隱藏原生滾動條,避免雙條 */
|
|
3845
|
+
scrollbar-width: none !important;
|
|
3846
|
+
-ms-overflow-style: none !important;
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__wrap::-webkit-scrollbar,
|
|
3850
|
+
.report-chapter-workspace__chapter-select-popper .el-scrollbar__wrap::-webkit-scrollbar,
|
|
3851
|
+
.report-chapter-workspace__chapter-select-popper
|
|
3852
|
+
.el-scrollbar__wrap--hidden-default::-webkit-scrollbar {
|
|
3853
|
+
width: 0 !important;
|
|
3854
|
+
height: 0 !important;
|
|
3855
|
+
display: none !important;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
.report-chapter-workspace__chapter-select-popper .el-scrollbar__view {
|
|
3859
|
+
display: block !important;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
.report-chapter-workspace__chapter-select-popper .el-scrollbar__bar.is-horizontal {
|
|
3863
|
+
display: none !important;
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
.report-chapter-workspace__chapter-select-popper .el-scrollbar__bar.is-vertical {
|
|
3867
|
+
width: 8px !important;
|
|
3868
|
+
right: 4px !important;
|
|
3869
|
+
top: 2px !important;
|
|
3870
|
+
bottom: 2px !important;
|
|
3871
|
+
height: auto !important;
|
|
3872
|
+
opacity: 1 !important;
|
|
3873
|
+
z-index: 10;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
.report-chapter-workspace__chapter-select-popper
|
|
3877
|
+
.el-scrollbar__bar.is-vertical
|
|
3878
|
+
.el-scrollbar__thumb {
|
|
3879
|
+
display: block !important;
|
|
3880
|
+
width: 8px !important;
|
|
3881
|
+
height: 50px !important;
|
|
3882
|
+
min-height: 50px !important;
|
|
3883
|
+
border-radius: 4px !important;
|
|
3884
|
+
background-color: #999 !important;
|
|
3885
|
+
opacity: 1 !important;
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__list {
|
|
3889
|
+
/* 首尾章節向內 5px,避免貼邊 */
|
|
3890
|
+
padding: 5px 0 !important;
|
|
3891
|
+
margin: 0 !important;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__item {
|
|
3895
|
+
display: flex;
|
|
3896
|
+
align-items: center;
|
|
3897
|
+
height: 40px !important;
|
|
3898
|
+
min-height: 40px !important;
|
|
3899
|
+
max-height: 40px !important;
|
|
3900
|
+
margin: 0 5px;
|
|
3901
|
+
padding: 0 12px !important;
|
|
3902
|
+
line-height: 1.4;
|
|
3903
|
+
border-radius: 8px;
|
|
3904
|
+
border: 1px solid transparent;
|
|
3905
|
+
box-sizing: border-box;
|
|
3906
|
+
cursor: grab;
|
|
3907
|
+
user-select: none;
|
|
3908
|
+
font-size: 15px;
|
|
3909
|
+
font-weight: 400;
|
|
3910
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3911
|
+
color: #303133;
|
|
3912
|
+
transition:
|
|
3913
|
+
background 0.15s ease,
|
|
3914
|
+
box-shadow 0.15s ease,
|
|
3915
|
+
opacity 0.15s ease;
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__item.is-selected {
|
|
3919
|
+
color: var(--report-accent);
|
|
3920
|
+
font-weight: 400;
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__item.is-hovering {
|
|
3924
|
+
background-color: #f5f7fa;
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__item.is-dragging {
|
|
3928
|
+
opacity: 0.5;
|
|
3929
|
+
cursor: grabbing;
|
|
3930
|
+
background: #fff;
|
|
3931
|
+
border-color: var(--report-accent);
|
|
3932
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__item.is-drag-over {
|
|
3936
|
+
background: var(--report-accent-soft);
|
|
3937
|
+
box-shadow: inset 0 0 0 1px rgba(197, 51, 85, 0.35);
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
.report-chapter-workspace__select-sortable-fallback {
|
|
3941
|
+
opacity: 0.5;
|
|
3942
|
+
cursor: grabbing;
|
|
3943
|
+
background: #fff !important;
|
|
3944
|
+
border: 1px solid #c53355 !important;
|
|
3945
|
+
border-radius: 8px !important;
|
|
3946
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
3947
|
+
list-style: none;
|
|
3948
|
+
z-index: 10001;
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
/* Element Plus 內置 .el-select-dropdown__footer(border-top + padding),不在業務源碼裏,需在此覆盖 */
|
|
3952
|
+
/* // new prompt sections — footer「新增章節」按鈕區 */
|
|
3953
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__footer {
|
|
3954
|
+
border-top: none;
|
|
3955
|
+
padding: 0;
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
.report-chapter-workspace__chapter-select-popper .report-chapter-workspace__chapter-select-footer {
|
|
3959
|
+
display: flex;
|
|
3960
|
+
align-items: center;
|
|
3961
|
+
justify-content: center;
|
|
3962
|
+
gap: 12px;
|
|
3963
|
+
padding: 10px 12px;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
.report-chapter-workspace__chapter-select-popper
|
|
3967
|
+
.report-chapter-workspace__chapter-select-footer-btn {
|
|
3968
|
+
--report-accent: #c53355;
|
|
3969
|
+
--report-accent-soft: #fdf0f2;
|
|
3970
|
+
display: inline-flex;
|
|
3971
|
+
align-items: center;
|
|
3972
|
+
justify-content: center;
|
|
3973
|
+
box-sizing: border-box;
|
|
3974
|
+
min-width: 120px;
|
|
3975
|
+
height: 32px;
|
|
3976
|
+
padding: 0 14px;
|
|
3977
|
+
border-radius: 4px;
|
|
3978
|
+
border: 1px solid var(--report-accent);
|
|
3979
|
+
background: var(--report-accent);
|
|
3980
|
+
color: #fff;
|
|
3981
|
+
font-size: 15px;
|
|
3982
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
3983
|
+
font-weight: 400;
|
|
3984
|
+
line-height: 1;
|
|
3985
|
+
white-space: nowrap;
|
|
3986
|
+
cursor: pointer;
|
|
3987
|
+
transition: all 0.2s ease;
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
.report-chapter-workspace__chapter-select-popper
|
|
3991
|
+
.report-chapter-workspace__chapter-select-footer-btn:hover {
|
|
3992
|
+
background: #fff;
|
|
3993
|
+
border-color: var(--report-accent);
|
|
3994
|
+
color: var(--report-accent);
|
|
3995
|
+
}
|
|
3996
|
+
/* // new prompt sections end */
|
|
3997
|
+
|
|
3998
|
+
.apply-template-form {
|
|
3999
|
+
--apply-template-label-w: 100px;
|
|
4000
|
+
--apply-template-field-gap: 16px;
|
|
4001
|
+
/* 與章節 card 內邊距一致,保證頂部標籤與下方「章節Prompt」左對齊 */
|
|
4002
|
+
--apply-template-content-inset: 16px;
|
|
4003
|
+
display: flex;
|
|
4004
|
+
flex-direction: column;
|
|
4005
|
+
gap: 20px;
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
.apply-template-meta {
|
|
4009
|
+
display: flex;
|
|
4010
|
+
flex-direction: column;
|
|
4011
|
+
gap: 20px;
|
|
4012
|
+
padding: 0 var(--apply-template-content-inset);
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
.apply-template-field {
|
|
4016
|
+
margin: 0;
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
.apply-template-field--row {
|
|
4020
|
+
display: grid;
|
|
4021
|
+
grid-template-columns: var(--apply-template-label-w) minmax(0, 1fr);
|
|
4022
|
+
column-gap: var(--apply-template-field-gap);
|
|
4023
|
+
align-items: center;
|
|
4024
|
+
width: 100%;
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
.apply-template-field--textarea-row {
|
|
4028
|
+
align-items: start;
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
.apply-template-field--textarea-row .apply-template-label {
|
|
4032
|
+
padding-top: 8px;
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
.apply-template-label {
|
|
4036
|
+
flex-shrink: 0;
|
|
4037
|
+
font-size: 14px;
|
|
4038
|
+
font-weight: 500;
|
|
4039
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4040
|
+
color: #303133;
|
|
4041
|
+
white-space: nowrap;
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
.apply-template-label__required {
|
|
4045
|
+
margin-left: 2px;
|
|
4046
|
+
color: #f56c6c;
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
.apply-template-field__control {
|
|
4050
|
+
min-width: 0;
|
|
4051
|
+
width: 100%;
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
.apply-template-field__control--lang {
|
|
4055
|
+
width: auto;
|
|
4056
|
+
max-width: 270px;
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
.apply-template-source {
|
|
4060
|
+
font-size: 14px;
|
|
4061
|
+
color: #303133;
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
.apply-template-field__input,
|
|
4065
|
+
.apply-template-field__textarea {
|
|
4066
|
+
width: 100%;
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
.apply-template-field--textarea-row .apply-template-field__control {
|
|
4070
|
+
position: relative;
|
|
4071
|
+
padding-bottom: 20px;
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
.apply-template-field__input :deep(.el-input__wrapper),
|
|
4075
|
+
.apply-template-field__textarea :deep(.el-textarea__inner) {
|
|
4076
|
+
font-size: clamp(13px, calc(14 * 100vw / 1920), 14px);
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
/* textarea 字數統計移出框外独占一行,避免遮挡文字 */
|
|
4080
|
+
.apply-template-field__textarea :deep(.el-textarea) {
|
|
4081
|
+
position: relative;
|
|
4082
|
+
overflow: visible;
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
.apply-template-field__textarea :deep(.el-input__count) {
|
|
4086
|
+
position: absolute !important;
|
|
4087
|
+
top: auto !important;
|
|
4088
|
+
right: 0 !important;
|
|
4089
|
+
bottom: -18px !important;
|
|
4090
|
+
left: auto !important;
|
|
4091
|
+
z-index: 1;
|
|
4092
|
+
display: block;
|
|
4093
|
+
width: auto;
|
|
4094
|
+
max-width: 100%;
|
|
4095
|
+
margin: 0;
|
|
4096
|
+
padding: 0;
|
|
4097
|
+
line-height: 18px;
|
|
4098
|
+
background: transparent !important;
|
|
4099
|
+
font-size: 12px;
|
|
4100
|
+
color: #909399;
|
|
4101
|
+
text-align: right;
|
|
4102
|
+
pointer-events: none;
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
.apply-template-field__input :deep(.el-input__count) {
|
|
4106
|
+
font-size: 12px;
|
|
4107
|
+
color: #909399;
|
|
4108
|
+
background: transparent;
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
.apply-template-chapter {
|
|
4112
|
+
padding: var(--apply-template-content-inset, 16px);
|
|
4113
|
+
border: 1px solid #ebeef5;
|
|
4114
|
+
border-radius: 4px;
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
.apply-template-chapter__title {
|
|
4118
|
+
margin: 0 0 16px;
|
|
4119
|
+
font-size: 16px;
|
|
4120
|
+
font-weight: 500;
|
|
4121
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4122
|
+
color: #606266;
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
.apply-template-process {
|
|
4126
|
+
padding: 20px 16px 16px;
|
|
4127
|
+
border: 1px solid #ebeef5;
|
|
4128
|
+
border-radius: 4px;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
.apply-template-process__row {
|
|
4132
|
+
align-items: center;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
.apply-template-process__radios {
|
|
4136
|
+
display: flex;
|
|
4137
|
+
align-items: center;
|
|
4138
|
+
gap: 24px;
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
.apply-template-process__actions {
|
|
4142
|
+
display: flex;
|
|
4143
|
+
align-items: center;
|
|
4144
|
+
justify-content: center;
|
|
4145
|
+
gap: 16px;
|
|
4146
|
+
margin-top: 8px;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
.apply-template-btn {
|
|
4150
|
+
display: inline-flex;
|
|
4151
|
+
align-items: center;
|
|
4152
|
+
justify-content: center;
|
|
4153
|
+
box-sizing: border-box;
|
|
4154
|
+
min-width: 120px;
|
|
4155
|
+
height: 32px;
|
|
4156
|
+
padding: 0 20px;
|
|
4157
|
+
border-radius: 4px;
|
|
4158
|
+
font-size: 14px;
|
|
4159
|
+
line-height: 1;
|
|
4160
|
+
cursor: pointer;
|
|
4161
|
+
transition: all 0.2s ease;
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
.apply-template-btn--primary {
|
|
4165
|
+
border: 1px solid #c53355;
|
|
4166
|
+
background: #c53355;
|
|
4167
|
+
color: #fff;
|
|
4168
|
+
font-weight: 500;
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
.apply-template-btn--primary:hover {
|
|
4172
|
+
background: #fff;
|
|
4173
|
+
color: #c53355;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
.apply-template-btn--ghost {
|
|
4177
|
+
border: 1px solid #dcdfe6;
|
|
4178
|
+
background: #fff;
|
|
4179
|
+
color: #606266;
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
.apply-template-btn--ghost:hover {
|
|
4183
|
+
border-color: #c53355;
|
|
4184
|
+
color: #c53355;
|
|
4185
|
+
background: #fdf0f2;
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4188
|
+
.apply-template-approval {
|
|
4189
|
+
margin-top: 24px;
|
|
4190
|
+
border: 1px solid #dcdfe6;
|
|
4191
|
+
border-radius: 4px;
|
|
4192
|
+
overflow: hidden;
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
.apply-template-approval__title {
|
|
4196
|
+
margin: 0;
|
|
4197
|
+
padding: 12px 16px;
|
|
4198
|
+
font-size: 16px;
|
|
4199
|
+
font-weight: 500;
|
|
4200
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4201
|
+
color: #606266;
|
|
4202
|
+
background: transparent;
|
|
4203
|
+
border-bottom: 1px solid #dcdfe6;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
.apply-template-approval-table {
|
|
4207
|
+
width: 100%;
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
.apply-template-approval-table :deep(.el-table--border) {
|
|
4211
|
+
border: none;
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
.apply-template-approval-table :deep(.el-table--border::before),
|
|
4215
|
+
.apply-template-approval-table :deep(.el-table--border::after),
|
|
4216
|
+
.apply-template-approval-table :deep(.el-table__inner-wrapper::before),
|
|
4217
|
+
.apply-template-approval-table :deep(.el-table__inner-wrapper::after) {
|
|
4218
|
+
display: none;
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
.apply-template-approval-table :deep(.el-table__border-left-patch) {
|
|
4222
|
+
display: none;
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
.apply-template-approval-table :deep(.el-table__header th.el-table__cell) {
|
|
4226
|
+
background: rgb(249, 250, 251);
|
|
4227
|
+
color: #606266;
|
|
4228
|
+
font-weight: 500;
|
|
4229
|
+
font-size: 16px;
|
|
4230
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
.apply-template-approval-table :deep(.el-table__body tr.el-table__row) {
|
|
4234
|
+
color: #303133;
|
|
4235
|
+
font-size: 15px;
|
|
4236
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4237
|
+
}
|
|
4238
|
+
</style>
|
|
4239
|
+
|
|
4240
|
+
<style>
|
|
4241
|
+
.apply-template-overlay {
|
|
4242
|
+
overflow: hidden !important;
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
.apply-template-dialog.el-dialog {
|
|
4246
|
+
--el-dialog-padding-primary: 0;
|
|
4247
|
+
width: 80% !important;
|
|
4248
|
+
max-width: 80%;
|
|
4249
|
+
margin-top: 5vh;
|
|
4250
|
+
border-radius: 4px;
|
|
4251
|
+
overflow: hidden;
|
|
4252
|
+
display: flex;
|
|
4253
|
+
flex-direction: column;
|
|
4254
|
+
max-height: 90vh;
|
|
4255
|
+
/* 給 body / scrollbar 明確高度,避免 max-height + height:auto 導致滾動失效 */
|
|
4256
|
+
height: min(90vh, 900px);
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
.apply-template-dialog .el-dialog__header,
|
|
4260
|
+
.apply-template-dialog__header {
|
|
4261
|
+
display: flex;
|
|
4262
|
+
align-items: center;
|
|
4263
|
+
justify-content: space-between;
|
|
4264
|
+
gap: 16px;
|
|
4265
|
+
margin: 0;
|
|
4266
|
+
padding: 14px 16px 14px 20px;
|
|
4267
|
+
background: transparent;
|
|
4268
|
+
border-bottom: none;
|
|
4269
|
+
flex-shrink: 0;
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
.apply-template-dialog__title,
|
|
4273
|
+
.apply-template-dialog .el-dialog__title {
|
|
4274
|
+
flex: 1;
|
|
4275
|
+
min-width: 0;
|
|
4276
|
+
color: #303133;
|
|
4277
|
+
font-size: 18px;
|
|
4278
|
+
font-weight: 600;
|
|
4279
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4280
|
+
line-height: 1.4;
|
|
4281
|
+
text-decoration: none;
|
|
4282
|
+
border-bottom: none;
|
|
4283
|
+
box-shadow: none;
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
.apply-template-dialog__close {
|
|
4287
|
+
flex-shrink: 0;
|
|
4288
|
+
width: 32px;
|
|
4289
|
+
height: 32px;
|
|
4290
|
+
margin: 0;
|
|
4291
|
+
padding: 0;
|
|
4292
|
+
border: none;
|
|
4293
|
+
background: transparent;
|
|
4294
|
+
color: #303133;
|
|
4295
|
+
font-size: 18px;
|
|
4296
|
+
line-height: 1;
|
|
4297
|
+
cursor: pointer;
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
.apply-template-dialog__close:hover {
|
|
4301
|
+
color: #606266;
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
.apply-template-dialog .el-dialog__body {
|
|
4305
|
+
flex: 1 1 auto;
|
|
4306
|
+
min-height: 0;
|
|
4307
|
+
height: 100%;
|
|
4308
|
+
padding: 0;
|
|
4309
|
+
background: #fff;
|
|
4310
|
+
overflow: hidden;
|
|
4311
|
+
display: flex;
|
|
4312
|
+
flex-direction: column;
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
/* 與編輯器 / 生成頁同款:el-scrollbar,滑塊按比例計算(僅 min-height,勿鎖死 height) */
|
|
4316
|
+
.apply-template-dialog__scroll {
|
|
4317
|
+
flex: 1 1 auto;
|
|
4318
|
+
min-height: 0;
|
|
4319
|
+
height: 100%;
|
|
4320
|
+
width: 100%;
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4323
|
+
.apply-template-dialog__scroll .el-scrollbar__view {
|
|
4324
|
+
padding: 24px 20px;
|
|
4325
|
+
box-sizing: border-box;
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
.apply-template-dialog__scroll .el-scrollbar__wrap {
|
|
4329
|
+
overflow-x: hidden !important;
|
|
4330
|
+
overflow-y: auto !important;
|
|
4331
|
+
max-height: 100% !important;
|
|
4332
|
+
scrollbar-width: none !important;
|
|
4333
|
+
-ms-overflow-style: none !important;
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
.apply-template-dialog__scroll .el-scrollbar__wrap::-webkit-scrollbar {
|
|
4337
|
+
width: 0 !important;
|
|
4338
|
+
height: 0 !important;
|
|
4339
|
+
display: none !important;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
.apply-template-dialog__scroll .el-scrollbar__bar.is-horizontal {
|
|
4343
|
+
display: none !important;
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
.apply-template-dialog__scroll .el-scrollbar__bar.is-vertical {
|
|
4347
|
+
width: 8px !important;
|
|
4348
|
+
right: 4px !important;
|
|
4349
|
+
top: 2px !important;
|
|
4350
|
+
bottom: 2px !important;
|
|
4351
|
+
height: auto !important;
|
|
4352
|
+
opacity: 1 !important;
|
|
4353
|
+
z-index: 10;
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
.apply-template-dialog__scroll .el-scrollbar__bar.is-vertical .el-scrollbar__thumb {
|
|
4357
|
+
display: block !important;
|
|
4358
|
+
width: 8px !important;
|
|
4359
|
+
min-height: 50px !important;
|
|
4360
|
+
border-radius: 4px !important;
|
|
4361
|
+
background-color: #999 !important;
|
|
4362
|
+
opacity: 1 !important;
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
.ai-prompt-generate-dialog__form,
|
|
4366
|
+
.ai-prompt-generate-dialog__preview {
|
|
4367
|
+
display: flex;
|
|
4368
|
+
flex-direction: column;
|
|
4369
|
+
gap: 18px;
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
.ai-prompt-generate-dialog__field {
|
|
4373
|
+
margin: 0;
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
.ai-prompt-generate-dialog__field--row {
|
|
4377
|
+
display: grid;
|
|
4378
|
+
grid-template-columns: 100px minmax(0, 1fr);
|
|
4379
|
+
gap: 16px;
|
|
4380
|
+
align-items: start;
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
.ai-prompt-generate-dialog__field--textarea {
|
|
4384
|
+
align-items: start;
|
|
4385
|
+
}
|
|
4386
|
+
|
|
4387
|
+
.ai-prompt-generate-dialog__label {
|
|
4388
|
+
padding-top: 8px;
|
|
4389
|
+
font-size: 15px;
|
|
4390
|
+
font-weight: 500;
|
|
4391
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4392
|
+
color: #303133;
|
|
4393
|
+
line-height: 1.4;
|
|
4394
|
+
white-space: nowrap;
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
.ai-prompt-generate-dialog__control {
|
|
4398
|
+
min-width: 0;
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
.ai-prompt-generate-dialog__preview-row {
|
|
4402
|
+
display: grid;
|
|
4403
|
+
grid-template-columns: 100px minmax(0, 1fr);
|
|
4404
|
+
gap: 16px;
|
|
4405
|
+
align-items: start;
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
.ai-prompt-generate-dialog__preview-row--prompt .ai-prompt-generate-dialog__label {
|
|
4409
|
+
padding-top: 0;
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
.ai-prompt-generate-dialog__preview-text {
|
|
4413
|
+
margin: 0;
|
|
4414
|
+
font-size: 14px;
|
|
4415
|
+
line-height: 1.6;
|
|
4416
|
+
color: #303133;
|
|
4417
|
+
word-break: break-word;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
.ai-prompt-generate-dialog__preview-text--multiline {
|
|
4421
|
+
white-space: pre-wrap;
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
.ai-prompt-generate-dialog__divider {
|
|
4425
|
+
height: 0;
|
|
4426
|
+
border-top: 1px dashed #dcdfe6;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
.ai-prompt-generate-dialog__footer {
|
|
4430
|
+
display: flex;
|
|
4431
|
+
align-items: center;
|
|
4432
|
+
justify-content: center;
|
|
4433
|
+
gap: 16px;
|
|
4434
|
+
width: 100%;
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
.ai-prompt-generate-dialog__footer-btn {
|
|
4438
|
+
min-width: 140px;
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
.ai-prompt-generate-dialog {
|
|
4442
|
+
--report-accent: #c53355;
|
|
4443
|
+
--report-accent-soft: #fdf0f2;
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
.ai-prompt-generate-dialog .template-center-toolbar-btn {
|
|
4447
|
+
display: inline-flex;
|
|
4448
|
+
align-items: center;
|
|
4449
|
+
justify-content: center;
|
|
4450
|
+
gap: 6px;
|
|
4451
|
+
box-sizing: border-box;
|
|
4452
|
+
height: 32px;
|
|
4453
|
+
min-width: 120px;
|
|
4454
|
+
padding: 0 14px;
|
|
4455
|
+
border-radius: 4px;
|
|
4456
|
+
line-height: 1;
|
|
4457
|
+
white-space: nowrap;
|
|
4458
|
+
cursor: pointer;
|
|
4459
|
+
font-size: 15px;
|
|
4460
|
+
font-family: var(--gen-ai-font-family, 'Noto Sans', 'Noto Sans TC', sans-serif);
|
|
4461
|
+
font-weight: 400;
|
|
4462
|
+
transition: all 0.2s ease;
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
.ai-prompt-generate-dialog .template-center-toolbar-btn--primary {
|
|
4466
|
+
border: 1px solid var(--report-accent);
|
|
4467
|
+
background: var(--report-accent);
|
|
4468
|
+
color: #fff;
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
.ai-prompt-generate-dialog .template-center-toolbar-btn--primary:hover:not(:disabled) {
|
|
4472
|
+
background: #fff;
|
|
4473
|
+
border-color: var(--report-accent);
|
|
4474
|
+
color: var(--report-accent);
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
.ai-prompt-generate-dialog .template-center-toolbar-btn--outline {
|
|
4478
|
+
border: 1px solid var(--report-accent);
|
|
4479
|
+
color: var(--report-accent);
|
|
4480
|
+
background: #fff;
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
.ai-prompt-generate-dialog .template-center-toolbar-btn--outline:hover:not(:disabled) {
|
|
4484
|
+
border-color: var(--report-accent);
|
|
4485
|
+
color: var(--report-accent);
|
|
4486
|
+
background: var(--report-accent-soft);
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
.ai-prompt-generate-dialog.el-dialog {
|
|
4490
|
+
--el-dialog-padding-primary: 0;
|
|
4491
|
+
border-radius: 4px;
|
|
4492
|
+
overflow: hidden;
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
.ai-prompt-generate-dialog .el-dialog__header,
|
|
4496
|
+
.ai-prompt-generate-dialog .report-workflow-dialog__header {
|
|
4497
|
+
border-bottom: none !important;
|
|
4498
|
+
}
|
|
4499
|
+
|
|
4500
|
+
.ai-prompt-generate-dialog .el-dialog__body {
|
|
4501
|
+
padding: 24px 20px;
|
|
4502
|
+
background: #fff;
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
.ai-prompt-generate-dialog .el-dialog__footer {
|
|
4506
|
+
padding: 12px 16px 30px;
|
|
4507
|
+
background: #fff;
|
|
4508
|
+
display: flex;
|
|
4509
|
+
justify-content: center;
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
.ai-prompt-generate-dialog__input .el-input__wrapper,
|
|
4513
|
+
.ai-prompt-generate-dialog__textarea .el-textarea__inner {
|
|
4514
|
+
border-radius: 2px;
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
/* // new prompt sections — 章節下拉:空態空白 / >1024 最小 500px / 長標題換行(集中放置) */
|
|
4518
|
+
.report-chapter-workspace__chapter-select-empty {
|
|
4519
|
+
min-height: 48px;
|
|
4520
|
+
background: #fff;
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__empty {
|
|
4524
|
+
padding: 0 !important;
|
|
4525
|
+
color: transparent !important;
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
@media (min-width: 1025px) {
|
|
4529
|
+
.report-chapter-workspace__chapter-select-popper.el-popper {
|
|
4530
|
+
width: max-content !important;
|
|
4531
|
+
min-width: 500px !important;
|
|
4532
|
+
max-width: min(720px, calc(100vw - 48px)) !important;
|
|
4533
|
+
}
|
|
4534
|
+
|
|
4535
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown {
|
|
4536
|
+
width: 100% !important;
|
|
4537
|
+
min-width: 500px !important;
|
|
4538
|
+
max-width: 100% !important;
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
.report-chapter-workspace__chapter-select-popper .el-select-dropdown__item {
|
|
4543
|
+
height: auto !important;
|
|
4544
|
+
min-height: 40px !important;
|
|
4545
|
+
max-height: none !important;
|
|
4546
|
+
align-items: flex-start !important;
|
|
4547
|
+
padding-top: 8px !important;
|
|
4548
|
+
padding-bottom: 8px !important;
|
|
4549
|
+
white-space: normal !important;
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4552
|
+
.report-chapter-workspace__chapter-select-popper .report-chapter-workspace__chapter-option {
|
|
4553
|
+
align-items: flex-start;
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
.report-chapter-workspace__chapter-select-popper .report-chapter-workspace__chapter-option-title {
|
|
4557
|
+
overflow: visible;
|
|
4558
|
+
text-overflow: unset;
|
|
4559
|
+
white-space: normal;
|
|
4560
|
+
word-break: break-word;
|
|
4561
|
+
overflow-wrap: anywhere;
|
|
4562
|
+
line-height: 1.4;
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
.report-chapter-workspace__chapter-select-popper .report-chapter-workspace__sort-handle,
|
|
4566
|
+
.report-chapter-workspace__chapter-select-popper .report-chapter-workspace__chapter-option-status {
|
|
4567
|
+
margin-top: 2px;
|
|
4568
|
+
flex-shrink: 0;
|
|
4569
|
+
}
|
|
4570
|
+
/* // new prompt sections end */
|
|
4571
|
+
</style>
|