重构: 拆ProjectDetail审批对话框(strategy前端)
- 新建 components/project/ApprovalDialog.vue(110行): 审批对话框+handleApproval/handleApprovalMulti/isMultipleSelect/multiDecisions+submitting(UX-260618-17防双击保留) - ProjectDetail.vue 811→743(-68): template→<ApprovalDialog> + 删迁移script - props visible/store透传 + emit cancel/later(单一store源 _storeInstance) 主代兜底: vue-tsc 0 + grep ApprovalDialog抽离/ProjectDetail import/emit印证 strategy: 前端views, 子组件抽审批对话框; UX-260618-17 submitting保留; 余743(style 315逻辑428, 后续TaskList/InfoCard可拆) git add指定(ProjectDetail.vue+ApprovalDialog.vue)
This commit is contained in:
110
src/components/project/ApprovalDialog.vue
Normal file
110
src/components/project/ApprovalDialog.vue
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 人工审批对话框(从 ProjectDetail.vue 抽离 UX-260618-17 submitting 保留)-->
|
||||||
|
<div v-if="visible" class="modal-overlay">
|
||||||
|
<div class="modal-box" style="width: 500px">
|
||||||
|
<h3 class="modal-title">{{ $t('projectDetail.approvalTitle') }}</h3>
|
||||||
|
<div v-if="pendingApproval">
|
||||||
|
<h3>{{ pendingApproval.title }}</h3>
|
||||||
|
<p style="margin: 16px 0; color: var(--df-text-secondary);">{{ pendingApproval.description }}</p>
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<p style="margin-bottom: 8px; font-size: 14px;">{{ $t('projectDetail.approvalHint') }}</p>
|
||||||
|
<!-- F-260615-01: select_type=multiple → checkbox 多选,缺省 single → 按钮单选 -->
|
||||||
|
<template v-if="isMultipleSelect">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px;">
|
||||||
|
<label
|
||||||
|
v-for="(option, idx) in pendingApproval.options"
|
||||||
|
:key="idx"
|
||||||
|
style="display: flex; align-items: center; gap: 8px; cursor: pointer;"
|
||||||
|
>
|
||||||
|
<input type="checkbox" :value="option" v-model="multiDecisions" />
|
||||||
|
<span>{{ option }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
:disabled="submitting || multiDecisions.length === 0"
|
||||||
|
@click="handleApprovalMulti"
|
||||||
|
>
|
||||||
|
{{ $t('projectDetail.approvalConfirm', { count: multiDecisions.length }) }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||||
|
<button
|
||||||
|
v-for="(option, idx) in pendingApproval.options"
|
||||||
|
:key="idx"
|
||||||
|
class="btn"
|
||||||
|
:class="idx === 0 ? 'btn-primary' : 'btn-ghost'"
|
||||||
|
:disabled="submitting"
|
||||||
|
@click="handleApproval(option)"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn btn-ghost" @click="emit('cancel')">{{ $t('projectDetail.approvalCancel') }}</button>
|
||||||
|
<button class="btn btn-ghost" @click="emit('later')">{{ $t('projectDetail.approvalLater') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { useProjectStore } from '@/stores/project'
|
||||||
|
|
||||||
|
// 抽离自 ProjectDetail.vue(L175-225 模板 + L416-471 审批逻辑/watch)
|
||||||
|
// store 共享:父级 useProjectStore() 实例传入(同源 store,响应式共享 pendingApproval)
|
||||||
|
// submitting 自治:本组件持有禁用态(UX-260618-17 双击防抖)
|
||||||
|
// ProjectStore 类型未 export(store.ts:135 内部 type),用 ReturnType 自推导同源类型
|
||||||
|
type ProjectStore = ReturnType<typeof useProjectStore>
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
store: ProjectStore
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
cancel: []
|
||||||
|
later: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const pendingApproval = computed(() => props.store.pendingApproval)
|
||||||
|
const isMultipleSelect = computed(() => pendingApproval.value?.select_type === 'multiple')
|
||||||
|
const multiDecisions = ref<string[]>([])
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
async function handleApproval(decision: string) {
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await props.store.approveHumanApproval([decision])
|
||||||
|
emit('cancel')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// F-260615-01: 多选审批(UX-260618-17 submitting 防双击保留)
|
||||||
|
async function handleApprovalMulti() {
|
||||||
|
if (multiDecisions.value.length === 0) return
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
// 统一传 decisions 数组,store 按 select_type 分派单/多选
|
||||||
|
await props.store.approveHumanApproval([...multiDecisions.value])
|
||||||
|
multiDecisions.value = []
|
||||||
|
emit('cancel')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// F-260615-01: 每次新审批清空多选状态(避免上轮残留)
|
||||||
|
watch(() => pendingApproval.value, (newApproval) => {
|
||||||
|
if (newApproval) {
|
||||||
|
multiDecisions.value = []
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
@@ -172,57 +172,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 人工审批对话框 -->
|
<!-- 人工审批对话框(抽离至 components/project/ApprovalDialog.vue)-->
|
||||||
<div v-if="showApprovalDialog" class="modal-overlay">
|
<ApprovalDialog
|
||||||
<div class="modal-box" style="width: 500px">
|
:visible="showApprovalDialog"
|
||||||
<h3 class="modal-title">{{ $t('projectDetail.approvalTitle') }}</h3>
|
:store="store"
|
||||||
<div v-if="store.pendingApproval">
|
@cancel="handleCancelApproval"
|
||||||
<h3>{{ store.pendingApproval.title }}</h3>
|
@later="showApprovalDialog = false"
|
||||||
<p style="margin: 16px 0; color: var(--df-text-secondary);">{{ store.pendingApproval.description }}</p>
|
/>
|
||||||
<div style="margin-bottom: 20px;">
|
|
||||||
<p style="margin-bottom: 8px; font-size: 14px;">{{ $t('projectDetail.approvalHint') }}</p>
|
|
||||||
<!-- F-260615-01: select_type=multiple → checkbox 多选,缺省 single → 按钮单选 -->
|
|
||||||
<template v-if="isMultipleSelect">
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px;">
|
|
||||||
<label
|
|
||||||
v-for="(option, idx) in store.pendingApproval.options"
|
|
||||||
:key="idx"
|
|
||||||
style="display: flex; align-items: center; gap: 8px; cursor: pointer;"
|
|
||||||
>
|
|
||||||
<input type="checkbox" :value="option" v-model="multiDecisions" />
|
|
||||||
<span>{{ option }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary"
|
|
||||||
:disabled="submitting || multiDecisions.length === 0"
|
|
||||||
@click="handleApprovalMulti"
|
|
||||||
>
|
|
||||||
{{ $t('projectDetail.approvalConfirm', { count: multiDecisions.length }) }}
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
|
||||||
<button
|
|
||||||
v-for="(option, idx) in store.pendingApproval.options"
|
|
||||||
:key="idx"
|
|
||||||
class="btn"
|
|
||||||
:class="idx === 0 ? 'btn-primary' : 'btn-ghost'"
|
|
||||||
:disabled="submitting"
|
|
||||||
@click="handleApproval(option)"
|
|
||||||
>
|
|
||||||
{{ option }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button class="btn btn-ghost" @click="handleCancelApproval">{{ $t('projectDetail.approvalCancel') }}</button>
|
|
||||||
<button class="btn btn-ghost" @click="showApprovalDialog = false">{{ $t('projectDetail.approvalLater') }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 确认弹层(删除/重定位确认,替代原生 window.confirm/alert) -->
|
<!-- 确认弹层(删除/重定位确认,替代原生 window.confirm/alert) -->
|
||||||
<ConfirmDialog :visible="confirmState.visible" :msg="confirmState.msg" @result="answerConfirm" />
|
<ConfirmDialog :visible="confirmState.visible" :msg="confirmState.msg" @result="answerConfirm" />
|
||||||
@@ -241,6 +197,7 @@ import { formatDate } from '@/utils/time'
|
|||||||
import { parseStack } from '@/utils/project'
|
import { parseStack } from '@/utils/project'
|
||||||
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
||||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||||
|
import ApprovalDialog from '@/components/project/ApprovalDialog.vue'
|
||||||
import { useConfirm } from '@/composables/useConfirm'
|
import { useConfirm } from '@/composables/useConfirm'
|
||||||
import { useRendered } from '@/composables/useMarkdown'
|
import { useRendered } from '@/composables/useMarkdown'
|
||||||
|
|
||||||
@@ -414,32 +371,9 @@ async function handleImportDir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 审批处理 ──
|
// ── 审批处理 ──
|
||||||
async function handleApproval(decision: string) {
|
// 审批对话框 UI + 单/多选决策逻辑抽离至 components/project/ApprovalDialog.vue
|
||||||
submitting.value = true
|
// handleApproval/handleApprovalMulti/isMultipleSelect/multiDecisions 已迁移;
|
||||||
try {
|
// submitting(UX-260618-17)在子组件自治,父级 submitting 仅用于 submitNewTask
|
||||||
await store.approveHumanApproval([decision])
|
|
||||||
showApprovalDialog.value = false
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// F-260615-01: 多选审批
|
|
||||||
const isMultipleSelect = computed(() => store.pendingApproval?.select_type === 'multiple')
|
|
||||||
const multiDecisions = ref<string[]>([])
|
|
||||||
|
|
||||||
async function handleApprovalMulti() {
|
|
||||||
if (multiDecisions.value.length === 0) return
|
|
||||||
submitting.value = true
|
|
||||||
try {
|
|
||||||
// 统一传 decisions 数组,store 按 select_type 分派单/多选
|
|
||||||
await store.approveHumanApproval([...multiDecisions.value])
|
|
||||||
multiDecisions.value = []
|
|
||||||
showApprovalDialog.value = false
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 取消审批(调 cancel_workflow_node IPC 置节点 Cancelled)──
|
// ── 取消审批(调 cancel_workflow_node IPC 置节点 Cancelled)──
|
||||||
async function handleCancelApproval() {
|
async function handleCancelApproval() {
|
||||||
@@ -461,11 +395,9 @@ watch(formattedEvents, () => {
|
|||||||
if (logListRef.value) logListRef.value.scrollTop = logListRef.value.scrollHeight
|
if (logListRef.value) logListRef.value.scrollTop = logListRef.value.scrollHeight
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听审批请求
|
// 监听审批请求(pendingApproval → 开弹窗;多选清空已在 ApprovalDialog 内 watch)
|
||||||
watch(() => store.pendingApproval, (newApproval) => {
|
watch(() => store.pendingApproval, (newApproval) => {
|
||||||
if (newApproval) {
|
if (newApproval) {
|
||||||
// F-260615-01: 每次新审批清空多选状态(避免上轮残留)
|
|
||||||
multiDecisions.value = []
|
|
||||||
showApprovalDialog.value = true
|
showApprovalDialog.value = true
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user