| | |
| | | </div> |
| | | |
| | | <el-form :model="{ activities }" ref="formRef" label-position="top"> |
| | | <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> |
| | | <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> |
| | | <el-step |
| | | v-for="(activity, index) in activities" |
| | | :key="index" |
| | | finish-status="success" |
| | | :title="getNodeTitle(index, activities.length)" |
| | | :description="activity.approveNodeUser" |
| | | :icon="getNodeIcon(activity, index)" |
| | | v-for="(group, gIndex) in stepGroups" |
| | | :key="gIndex" |
| | | finish-status="success" |
| | | :title="getNodeTitle(gIndex, stepGroups.length)" |
| | | :icon="getNodeIcon(group)" |
| | | > |
| | | <template #icon> |
| | | <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"><WarningFilled /></el-icon> |
| | | <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"><Edit /></el-icon> |
| | | <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"><Check /></el-icon> |
| | | <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon> |
| | | </template> |
| | | <template #icon> |
| | | <el-icon v-if="group.some(a => a.approveNodeStatus === 2)" color="red" :size="22"><WarningFilled /></el-icon> |
| | | <el-icon v-else-if="group.some(a => a.isShen)" color="#1890ff" :size="22"><Edit /></el-icon> |
| | | <el-icon v-else-if="group.every(a => a.approveNodeStatus === 1)" color="#67C23A" :size="26"><Check /></el-icon> |
| | | <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon> |
| | | </template> |
| | | <template #title> |
| | | <span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span> |
| | | <span style="color: #000000">{{ getNodeTitle(gIndex, stepGroups.length) }}</span> |
| | | <el-tag :type="group[0]?.approveMode === 'and' ? 'warning' : 'info'" size="small" style="margin-left: 6px">{{ group[0]?.approveMode === 'and' ? '会签' : '或签' }}</el-tag> |
| | | </template> |
| | | <template #description> |
| | | <div class="node-user"> |
| | | <div class="avatar-wrapper"> |
| | | <img :src="userStore.avatar" class="user-avatar" alt=""/> |
| | | <div v-for="(activity, aIndex) in group" :key="activity.id || aIndex" class="node-user-item"> |
| | | <div class="node-user"> |
| | | <div class="avatar-wrapper"> |
| | | <img :src="userStore.avatar" class="user-avatar" alt=""/> |
| | | </div> |
| | | <span style="color: #000000">{{ activity.approveNodeUser }}({{ activity.isApproval }})</span> |
| | | </div> |
| | | <span style="color: #000000">{{ activity.approveNodeUser }}-{{activity.isApproval}}</span> |
| | | </div> |
| | | <div v-if="!activity.isShen" class="node-reason"> |
| | | <span>审批意见:</span>{{ activity.approveNodeReason }} |
| | | </div> |
| | | <div v-else-if="activity.isShen"> |
| | | <el-form-item |
| | | :prop="'activities.' + index + '.approveNodeReason'" |
| | | :rules="[{ required: true, message: '审批意见不能为空', trigger: 'blur' }]" |
| | | > |
| | | <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input> |
| | | </el-form-item> |
| | | <div v-if="!activity.isShen" class="node-reason"> |
| | | <span>审批意见:</span>{{ activity.approveNodeReason }} |
| | | </div> |
| | | <div v-else-if="activity.isShen"> |
| | | <el-form-item |
| | | :prop="'activities.' + getActivityIndex(activity) + '.approveNodeReason'" |
| | | :rules="[{ required: true, message: '审批意见不能为空', trigger: 'blur' }]" |
| | | > |
| | | <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input> |
| | | </el-form-item> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-step> |
| | |
| | | return '审批'; |
| | | }; |
| | | |
| | | // 获取当前激活步骤 |
| | | // 获取当前激活步骤(分组后:含待审人的组下标,无则返回组总数=全部完成) |
| | | const getActiveStep = () => { |
| | | // 如果所有 isShen 都为 false,返回最后一个步骤(全部完成) |
| | | const hasActive = activities.value.some(a => a.isShen === true); |
| | | if (!hasActive) return activities.value.length; |
| | | // 当前节点索引 |
| | | return activities.value.findIndex(a => a.isShen == true); |
| | | const idx = stepGroups.value.findIndex(group => group.some(a => a.isShen === true)); |
| | | if (idx === -1) return stepGroups.value.length; |
| | | return idx; |
| | | }; |
| | | // 步骤icon |
| | | const getNodeIcon = (activity, index) => { |
| | | if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // 不通过 |
| | | if (activity.isShen) return 'Edit'; |
| | | // 步骤icon(组级:任一人驳回/任一人待审) |
| | | const getNodeIcon = (group) => { |
| | | if (group.some(a => a.approveNodeStatus === 2)) return 'el-icon-warning'; // 不通过 |
| | | if (group.some(a => a.isShen)) return 'Edit'; |
| | | return ''; |
| | | }; |
| | | // 审批节点分组(同一 approveNodeOrder = 同一节点的多名审批人,或签/会签) |
| | | const stepGroups = computed(() => { |
| | | const map = new Map(); |
| | | (activities.value || []).forEach(a => { |
| | | const order = a.approveNodeOrder; |
| | | if (!map.has(order)) map.set(order, []); |
| | | map.get(order).push(a); |
| | | }); |
| | | return Array.from(map.values()); |
| | | }); |
| | | // 获取节点成员在 activities 中的索引(用于表单校验 prop) |
| | | const getActivityIndex = (activity) => { |
| | | return activities.value.findIndex(a => a.id === activity.id); |
| | | }; |
| | | |
| | | // 打开弹框 |
| | |
| | | return; |
| | | } |
| | | currentActivity.approveNodeStatus = status; |
| | | // 判断是否为最后一步 |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; |
| | | updateApproveNode({ ...currentActivity, isLast }).then(() => { |
| | | // 流转判断由后端完成,前端不再传 isLast |
| | | updateApproveNode({ ...currentActivity }).then(() => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }); |
| | |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .node-user-item { |
| | | margin: 8px 0; |
| | | } |
| | | .node-status { |
| | | color: #1890ff; |
| | | margin-left: 8px; |