aegon-dangerious 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/build-report/components/AiPromptGenerateDialog.vue +349 -0
  2. package/build-report/components/CapsuleScrollbar.vue +145 -0
  3. package/build-report/components/ChapterTitleScroll.vue +292 -0
  4. package/build-report/components/PromptField.vue +314 -0
  5. package/build-report/components/RecentReportsTable.vue +408 -0
  6. package/build-report/components/ReportChapterPreview.vue +437 -0
  7. package/build-report/components/ReportChapterWorkspace.vue +923 -0
  8. package/build-report/components/ReportCollapsibleSection.vue +132 -0
  9. package/build-report/components/ReportConfigPanel.vue +609 -0
  10. package/build-report/components/ReportGenerateSettingsPanel.vue +403 -0
  11. package/build-report/components/ReportGeneratingPanel.vue +908 -0
  12. package/build-report/components/ReportHistoryList.vue +157 -0
  13. package/build-report/components/ReportHistoryPanel.vue +140 -0
  14. package/build-report/components/ReportPromptPanel.vue +445 -0
  15. package/build-report/components/ReportSectionConfirmPopover.vue +232 -0
  16. package/build-report/components/ReportSourceFileSummary.vue +544 -0
  17. package/build-report/components/ReportSourcesMoreOverlay.vue +279 -0
  18. package/build-report/components/ReportSourcesPanel.vue +1055 -0
  19. package/build-report/components/ReportTemplateDetail.vue +176 -0
  20. package/build-report/components/ReportTemplateEditorFormPanel.vue +602 -0
  21. package/build-report/components/ReportTemplatePicker.vue +802 -0
  22. package/build-report/components/ReportTemplatePromptField.vue +314 -0
  23. package/build-report/components/ReportWorkflowSidebar.vue +104 -0
  24. package/build-report/components/reportEdit.vue +334 -0
  25. package/build-report/index.vue +646 -0
  26. package/build-report/report-list.vue +407 -0
  27. package/build-report/report-template-data.ts +248 -0
  28. package/build-report/styles/report-workflow-dialog.css +33 -0
  29. package/build-report/useReportWorkflow.ts +343 -0
  30. package/build-report/utils/report-edit-route.ts +109 -0
  31. package/build-report/utils/scroll.ts +361 -0
  32. package/package.json +12 -0
@@ -0,0 +1,544 @@
1
+ <template>
2
+ <ReportCollapsibleSection ref="sectionRef" v-model:open="panelOpen" title="编辑者"
3
+ @before-collapse="handleBeforeCollapse">
4
+ <template #actions>
5
+ <button v-if="canManageReportEditors" ref="addBtnRef" type="button" class="report-icon-btn" aria-label="添加"
6
+ @click.stop="toggleAddOpen">
7
+ <el-icon>
8
+ <Plus />
9
+ </el-icon>
10
+ </button>
11
+ <button v-if="canManageReportEditors" ref="shareBtnRef" type="button" class="report-icon-btn" aria-label="轉移發起人"
12
+ @click.stop="toggleShareOpen">
13
+ <el-icon>
14
+ <Share />
15
+ </el-icon>
16
+ </button>
17
+ </template>
18
+
19
+ <div class="report-editor-panel">
20
+ <ul class="report-editor-list">
21
+ <li v-for="editor in editors" :key="editor.id" class="report-editor-list__row">
22
+ <span class="report-editor-list__name" :title="`${editor.name}(${editor.employeeId})`">
23
+ {{ editor.name }}({{ editor.employeeId }})
24
+ </span>
25
+ <span class="report-editor-list__dept">{{ editor.department }}</span>
26
+ <span class="report-editor-list__role">{{ editorRoleLabel(editor.role) }}</span>
27
+ <span class="report-editor-list__action">
28
+ <button v-if="canManageReportEditors && editor.role !== 'initiator'" type="button" class="report-icon-btn"
29
+ :aria-label="`移除${editor.name}`" @click="requestRemoveEditor(editor, $event)">
30
+ <el-icon>
31
+ <Close />
32
+ </el-icon>
33
+ </button>
34
+ </span>
35
+ </li>
36
+ </ul>
37
+ </div>
38
+ </ReportCollapsibleSection>
39
+
40
+ <el-popover :visible="addOpen" :virtual-ref="shareToggleRef" placement="bottom-start" :width="popoverWidth"
41
+ :popper-options="popperOptions" :popper-style="addPopperStyle" popper-class="report-config-popover"
42
+ :transition="popoverInstant ? 'report-config-popover-instant' : 'report-config-popover-fade'"
43
+ :gpu-acceleration="false" :teleported="true">
44
+ <div class="report-source-transfer">
45
+ <h4 class="report-source-transfer__title">添加编辑者</h4>
46
+ <label class="report-source-transfer__field">
47
+ <span class="report-source-transfer__label">用戶</span>
48
+ <el-select v-model="addSelectedUser" class="report-source-transfer__select" filterable clearable
49
+ placeholder="請選擇用戶">
50
+ <el-option v-for="user in addEditorOptions" :key="user.id"
51
+ :label="`${user.name}(${user.employeeId})`" :value="user.id" />
52
+ </el-select>
53
+ </label>
54
+ <div class="report-source-transfer__actions">
55
+ <button type="button" class="report-source-transfer__btn" @click="addOpen = false">取消</button>
56
+ <button type="button" class="report-source-transfer__btn report-source-transfer__btn--primary"
57
+ @click="confirmAdd">
58
+ 確認
59
+ </button>
60
+ </div>
61
+ </div>
62
+ </el-popover>
63
+
64
+ <el-popover :visible="shareOpen" :virtual-ref="shareToggleRef" placement="bottom-start" :width="popoverWidth"
65
+ :popper-options="popperOptions" :popper-style="sharePopperStyle" popper-class="report-config-popover"
66
+ :transition="popoverInstant ? 'report-config-popover-instant' : 'report-config-popover-fade'"
67
+ :gpu-acceleration="false" :teleported="true">
68
+ <div class="report-source-transfer">
69
+ <h4 class="report-source-transfer__title">轉移報告發起人權限</h4>
70
+ <label class="report-source-transfer__field">
71
+ <span class="report-source-transfer__label">用戶</span>
72
+ <el-select v-model="transferSelectedUser" class="report-source-transfer__select" filterable clearable
73
+ placeholder="請選擇用戶">
74
+ <el-option v-for="user in transferUserOptions" :key="user.id"
75
+ :label="`${user.name}(${user.employeeId})`" :value="user.id" />
76
+ </el-select>
77
+ </label>
78
+ <div class="report-source-transfer__actions">
79
+ <button type="button" class="report-source-transfer__btn" @click="shareOpen = false">取消</button>
80
+ <button type="button" class="report-source-transfer__btn report-source-transfer__btn--primary"
81
+ @click="confirmTransfer">
82
+ 確認
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </el-popover>
87
+
88
+ <ReportSectionConfirmPopover :visible="deleteConfirmOpen" :virtual-ref="deleteConfirmVirtualRef"
89
+ :boundary-el="sectionBoundaryEl" :trigger-btn="deleteConfirmTriggerBtn" :message="deleteConfirmMessage"
90
+ :instant="popoverInstant" @confirm="confirmRemoveEditor" @cancel="closeDeleteConfirm" />
91
+ </template>
92
+
93
+ <script setup lang="ts">
94
+ import { Close, Plus, Share } from '@element-plus/icons-vue'
95
+ import { ElIcon, ElMessage, ElOption, ElPopover, ElSelect } from 'element-plus'
96
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
97
+ import {
98
+ MOCK_UMS_EDITOR_USERS,
99
+ type ReportEditor,
100
+ type ReportEditorRole,
101
+ } from '../useReportWorkflow'
102
+ import { useReportWorkflow } from '../useReportWorkflow'
103
+ import ReportCollapsibleSection from './ReportCollapsibleSection.vue'
104
+ import ReportSectionConfirmPopover from './ReportSectionConfirmPopover.vue'
105
+
106
+ const workflow = useReportWorkflow()
107
+ const { reportEditors, canManageReportEditors } = workflow
108
+
109
+ const editors = reportEditors
110
+ const addOpen = ref(false)
111
+ const shareOpen = ref(false)
112
+ const popoverInstant = ref(false)
113
+ const addSelectedUser = ref('')
114
+ const transferSelectedUser = ref('')
115
+
116
+ const addBtnRef = ref<HTMLElement | null>(null)
117
+ const shareBtnRef = ref<HTMLElement | null>(null)
118
+ const sectionRef = ref<InstanceType<typeof ReportCollapsibleSection> | null>(null)
119
+
120
+ const shareToggleRef = computed(() => sectionRef.value?.toggleRef ?? null)
121
+
122
+ const popoverWidthPx = ref(240)
123
+ const arrowAlignKey = ref(0)
124
+ const popoverWidth = computed(() => popoverWidthPx.value)
125
+
126
+ const addEditorOptions = computed(() => {
127
+ const existingIds = new Set(editors.value.map((editor) => editor.employeeId))
128
+ return MOCK_UMS_EDITOR_USERS.filter((user) => !existingIds.has(user.employeeId))
129
+ })
130
+
131
+ const transferUserOptions = computed(() => {
132
+ const currentInitiator = editors.value.find((editor) => editor.role === 'initiator')
133
+ const candidates = new Map<string, (typeof MOCK_UMS_EDITOR_USERS)[number]>()
134
+
135
+ for (const user of MOCK_UMS_EDITOR_USERS) {
136
+ if (user.id !== currentInitiator?.id) {
137
+ candidates.set(user.id, user)
138
+ }
139
+ }
140
+ for (const editor of editors.value) {
141
+ if (editor.id !== currentInitiator?.id) {
142
+ candidates.set(editor.id, {
143
+ id: editor.id,
144
+ name: editor.name,
145
+ employeeId: editor.employeeId,
146
+ department: editor.department,
147
+ })
148
+ }
149
+ }
150
+
151
+ return Array.from(candidates.values())
152
+ })
153
+
154
+ const panelOpen = ref(true)
155
+
156
+ const deleteConfirmOpen = ref(false)
157
+ const deleteConfirmMessage = ref('')
158
+ const deleteConfirmVirtualRef = ref<HTMLElement | null>(null)
159
+ const deleteConfirmTriggerBtn = ref<HTMLElement | null>(null)
160
+ const deleteTargetId = ref<string | null>(null)
161
+
162
+ const sectionBoundaryEl = computed(() => sectionRef.value?.rootRef ?? null)
163
+
164
+ function editorRoleLabel(role: ReportEditorRole) {
165
+ return role === 'initiator' ? '發起人' : '編輯者'
166
+ }
167
+
168
+ function requestRemoveEditor(editor: ReportEditor, event: MouseEvent) {
169
+ if (!canManageReportEditors.value) return
170
+ const btn = event.currentTarget as HTMLElement
171
+ deleteConfirmVirtualRef.value = btn.closest('.report-editor-list__row') as HTMLElement ?? btn
172
+ deleteConfirmTriggerBtn.value = btn
173
+ deleteTargetId.value = editor.id
174
+ deleteConfirmMessage.value = `確定刪除「${editor.name}(${editor.employeeId})」?`
175
+ deleteConfirmOpen.value = true
176
+ }
177
+
178
+ function confirmRemoveEditor() {
179
+ if (deleteTargetId.value) {
180
+ editors.value = editors.value.filter((item) => item.id !== deleteTargetId.value)
181
+ }
182
+ closeDeleteConfirm()
183
+ }
184
+
185
+ function closeDeleteConfirm() {
186
+ deleteConfirmOpen.value = false
187
+ deleteTargetId.value = null
188
+ }
189
+
190
+ function hasOpenOverlays() {
191
+ return addOpen.value || shareOpen.value || deleteConfirmOpen.value
192
+ }
193
+
194
+ function closeAllOverlays() {
195
+ addOpen.value = false
196
+ shareOpen.value = false
197
+ deleteConfirmOpen.value = false
198
+ deleteTargetId.value = null
199
+ }
200
+
201
+ function handleBeforeCollapse(done: (hadOpenOverlays: boolean) => void) {
202
+ const hadOpen = hasOpenOverlays()
203
+ if (hadOpen) {
204
+ popoverInstant.value = true
205
+ closeAllOverlays()
206
+ window.setTimeout(() => {
207
+ popoverInstant.value = false
208
+ }, 320)
209
+ }
210
+ done(hadOpen)
211
+ }
212
+
213
+ function getSectionContentWidth() {
214
+ const sectionEl = sectionRef.value?.rootRef
215
+ if (!sectionEl) return 240
216
+ const styles = getComputedStyle(sectionEl)
217
+ const paddingX = parseFloat(styles.paddingLeft) + parseFloat(styles.paddingRight)
218
+ return Math.round(sectionEl.clientWidth - paddingX)
219
+ }
220
+
221
+ function getArrowPopperStyle(triggerBtn: HTMLElement | null): Record<string, string> {
222
+ void arrowAlignKey.value
223
+ void panelOpen.value
224
+ const sectionEl = sectionRef.value?.rootRef
225
+ if (!triggerBtn || !sectionEl) return {}
226
+ const sectionRect = sectionEl.getBoundingClientRect()
227
+ const paddingLeft = parseFloat(getComputedStyle(sectionEl).paddingLeft)
228
+ const btnRect = triggerBtn.getBoundingClientRect()
229
+ const left = btnRect.left + btnRect.width / 2 - sectionRect.left - paddingLeft - 8
230
+ return { '--report-config-arrow-left': `${left}px` }
231
+ }
232
+
233
+ const addPopperStyle = computed(() => getArrowPopperStyle(addBtnRef.value))
234
+
235
+ const sharePopperStyle = computed(() => getArrowPopperStyle(shareBtnRef.value))
236
+
237
+ const popperOptions = computed(() => {
238
+ const boundary = sectionRef.value?.rootRef
239
+ return {
240
+ modifiers: [
241
+ {
242
+ name: 'preventOverflow',
243
+ options: {
244
+ boundary: boundary ?? 'viewport',
245
+ padding: 0,
246
+ altAxis: true,
247
+ },
248
+ },
249
+ {
250
+ name: 'flip',
251
+ enabled: false,
252
+ },
253
+ ],
254
+ }
255
+ })
256
+
257
+ function syncPopoverWidth() {
258
+ popoverWidthPx.value = getSectionContentWidth()
259
+ arrowAlignKey.value += 1
260
+ }
261
+
262
+ function toggleAddOpen() {
263
+ if (!canManageReportEditors.value) return
264
+ syncPopoverWidth()
265
+ shareOpen.value = false
266
+ addOpen.value = !addOpen.value
267
+ if (addOpen.value) {
268
+ void nextTick(() => {
269
+ arrowAlignKey.value += 1
270
+ })
271
+ }
272
+ }
273
+
274
+ function toggleShareOpen() {
275
+ if (!canManageReportEditors.value) return
276
+ syncPopoverWidth()
277
+ addOpen.value = false
278
+ shareOpen.value = !shareOpen.value
279
+ if (shareOpen.value) {
280
+ void nextTick(() => {
281
+ arrowAlignKey.value += 1
282
+ })
283
+ }
284
+ }
285
+
286
+ function confirmAdd() {
287
+ if (!canManageReportEditors.value) {
288
+ addOpen.value = false
289
+ return
290
+ }
291
+
292
+ const candidate = MOCK_UMS_EDITOR_USERS.find((user) => user.id === addSelectedUser.value)
293
+ if (!candidate) {
294
+ addOpen.value = false
295
+ return
296
+ }
297
+
298
+ const alreadyExists = editors.value.some((editor) => editor.employeeId === candidate.employeeId)
299
+ if (alreadyExists) {
300
+ ElMessage.warning('該用戶已是報告編輯者')
301
+ addOpen.value = false
302
+ addSelectedUser.value = ''
303
+ return
304
+ }
305
+
306
+ editors.value.push({
307
+ id: candidate.id,
308
+ name: candidate.name,
309
+ employeeId: candidate.employeeId,
310
+ department: candidate.department,
311
+ role: 'editor',
312
+ })
313
+ addSelectedUser.value = ''
314
+ addOpen.value = false
315
+ ElMessage.success('已添加編輯者')
316
+ }
317
+
318
+ function confirmTransfer() {
319
+ if (!canManageReportEditors.value) {
320
+ shareOpen.value = false
321
+ return
322
+ }
323
+
324
+ const targetId = transferSelectedUser.value
325
+ if (!targetId) {
326
+ shareOpen.value = false
327
+ return
328
+ }
329
+
330
+ const currentInitiator = editors.value.find((editor) => editor.role === 'initiator')
331
+ let target = editors.value.find((editor) => editor.id === targetId)
332
+
333
+ if (!target) {
334
+ const umsUser = MOCK_UMS_EDITOR_USERS.find((user) => user.id === targetId)
335
+ if (!umsUser) {
336
+ shareOpen.value = false
337
+ return
338
+ }
339
+ target = {
340
+ id: umsUser.id,
341
+ name: umsUser.name,
342
+ employeeId: umsUser.employeeId,
343
+ department: umsUser.department,
344
+ role: 'editor',
345
+ }
346
+ editors.value.push(target)
347
+ }
348
+
349
+ if (currentInitiator) {
350
+ currentInitiator.role = 'editor'
351
+ }
352
+ target.role = 'initiator'
353
+
354
+ transferSelectedUser.value = ''
355
+ shareOpen.value = false
356
+ ElMessage.success('已轉移報告發起人權限')
357
+ }
358
+
359
+ watch(panelOpen, (open) => {
360
+ if (!open) closeAllOverlays()
361
+ })
362
+
363
+ let sectionResizeObserver: ResizeObserver | null = null
364
+
365
+ onMounted(() => {
366
+ void nextTick(() => {
367
+ syncPopoverWidth()
368
+ const sectionEl = sectionRef.value?.rootRef
369
+ if (sectionEl) {
370
+ sectionResizeObserver = new ResizeObserver(() => syncPopoverWidth())
371
+ sectionResizeObserver.observe(sectionEl)
372
+ }
373
+ })
374
+ window.addEventListener('resize', syncPopoverWidth)
375
+ })
376
+
377
+ onUnmounted(() => {
378
+ window.removeEventListener('resize', syncPopoverWidth)
379
+ sectionResizeObserver?.disconnect()
380
+ })
381
+ </script>
382
+
383
+ <style scoped>
384
+ .report-editor-panel {
385
+ box-sizing: border-box;
386
+ }
387
+
388
+ .report-editor-list {
389
+ margin: 0;
390
+ padding: 0;
391
+ list-style: none;
392
+ }
393
+
394
+ .report-editor-list__row {
395
+ display: grid;
396
+ grid-template-columns: minmax(0, 1fr) auto auto 24px;
397
+ align-items: center;
398
+ gap: 10px;
399
+ height: 36px;
400
+ padding: 0 10px;
401
+ border-radius: 4px;
402
+ font-size: 12px;
403
+ line-height: 1.5;
404
+ color: #303133;
405
+ background: #fff;
406
+ box-sizing: border-box;
407
+ transition: background-color 0.2s ease;
408
+ }
409
+
410
+ .report-editor-list__row:hover {
411
+ background-color: #f5f7fa;
412
+ }
413
+
414
+ .report-editor-list__name {
415
+ overflow: hidden;
416
+ text-overflow: ellipsis;
417
+ white-space: nowrap;
418
+ }
419
+
420
+ .report-editor-list__dept,
421
+ .report-editor-list__role {
422
+ white-space: nowrap;
423
+ color: #303133;
424
+ }
425
+
426
+ .report-editor-list__action {
427
+ display: flex;
428
+ justify-content: flex-end;
429
+ }
430
+
431
+ .report-source-transfer__title {
432
+ margin: 0 0 12px;
433
+ font-size: 14px;
434
+ font-weight: 700;
435
+ color: #303133;
436
+ }
437
+
438
+ .report-source-transfer__field {
439
+ display: grid;
440
+ gap: 6px;
441
+ }
442
+
443
+ .report-source-transfer__label {
444
+ font-size: 13px;
445
+ color: #909399;
446
+ }
447
+
448
+ .report-source-transfer__select {
449
+ width: 100%;
450
+ }
451
+
452
+ .report-source-transfer__select :deep(.el-select__wrapper) {
453
+ min-height: 32px;
454
+ }
455
+
456
+ .report-source-transfer__actions {
457
+ display: flex;
458
+ justify-content: flex-end;
459
+ gap: 8px;
460
+ margin-top: 12px;
461
+ }
462
+
463
+ .report-source-transfer__btn {
464
+ min-width: 72px;
465
+ padding: 6px 10px;
466
+ border: 1px solid #dcdfe6;
467
+ border-radius: 4px;
468
+ background: #fff;
469
+ font-size: 12px;
470
+ color: #606266;
471
+ cursor: pointer;
472
+ }
473
+
474
+ .report-source-transfer__btn--primary {
475
+ border-color: #c53355;
476
+ background: #c53355;
477
+ color: #fff;
478
+ }
479
+
480
+ .report-source-transfer__btn--primary:hover {
481
+ background: #fff;
482
+ color: #c53355;
483
+ }
484
+ </style>
485
+
486
+ <style>
487
+ .report-icon-btn {
488
+ display: inline-flex;
489
+ align-items: center;
490
+ justify-content: center;
491
+ width: 24px;
492
+ height: 24px;
493
+ padding: 0;
494
+ border: 0;
495
+ background: none;
496
+ color: #4e5969;
497
+ cursor: pointer;
498
+ border-radius: 4px;
499
+ vertical-align: middle;
500
+ }
501
+
502
+ .report-icon-btn:hover {
503
+ color: #c53355;
504
+ background: #fff5f6;
505
+ }
506
+
507
+ .report-config-popover.el-popover.el-popper {
508
+ max-width: 100%;
509
+ padding: 12px;
510
+ box-sizing: border-box;
511
+ overflow: hidden;
512
+ }
513
+
514
+ .report-config-popover.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
515
+ left: var(--report-config-arrow-left) !important;
516
+ right: auto !important;
517
+ top: -6px !important;
518
+ transform: translateX(-50%) !important;
519
+ }
520
+
521
+ .report-config-popover.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::before {
522
+ bottom: -1px;
523
+ }
524
+
525
+ .report-config-popover-fade-enter-active,
526
+ .report-config-popover-fade-leave-active {
527
+ transition: opacity 0.12s ease;
528
+ }
529
+
530
+ .report-config-popover-fade-enter-from,
531
+ .report-config-popover-fade-leave-to {
532
+ opacity: 0;
533
+ }
534
+
535
+ .report-config-popover-instant-enter-active,
536
+ .report-config-popover-instant-leave-active {
537
+ transition: none !important;
538
+ }
539
+
540
+ .report-config-popover-instant-enter-from,
541
+ .report-config-popover-instant-leave-to {
542
+ opacity: 0;
543
+ }
544
+ </style>