From f51c5018dc9088f393f34e3e2508d7caf80002c4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 19 八月 2026 15:37:16 +0800
Subject: [PATCH] feat(approval): 支持多审批人并行审批功能
---
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | 100 ++++++++------
src/views/safeProduction/safeWorkApproval/index.vue | 16 +
src/views/collaborativeApproval/approvalProcess/index.vue | 16 +
src/views/safeProduction/safeWorkApproval/components/approvalDia.vue | 119 +++++++++-------
src/views/collaborativeApproval/approvalManagement/index.vue | 155 ++++++++++++++--------
5 files changed, 244 insertions(+), 162 deletions(-)
diff --git a/src/views/collaborativeApproval/approvalManagement/index.vue b/src/views/collaborativeApproval/approvalManagement/index.vue
index e265cfe..41a8dda 100644
--- a/src/views/collaborativeApproval/approvalManagement/index.vue
+++ b/src/views/collaborativeApproval/approvalManagement/index.vue
@@ -36,7 +36,7 @@
<div class="header-info">
<span class="type-name">{{ currentApproveTypeName }}</span>
<el-tag :type="approverList.length > 0 ? 'success' : 'warning'" size="small" effect="light">
- {{ approverList.length > 0 ? `宸查厤缃� ${approverList.length} 涓鎵逛汉` : '鏈厤缃鎵逛汉' }}
+ {{ approverCountText }}
</el-tag>
</div>
</div>
@@ -62,18 +62,18 @@
class="flow-item"
>
<!-- 瀹℃壒鑺傜偣鍗$墖 -->
- <div class="node-card" :class="{ 'empty': !item.approverId }">
+ <div class="node-card" :class="{ 'empty': !(item.approverIds && item.approverIds.length) }">
<!-- 椤堕儴搴忓彿鍜岀骇鍒� -->
<div class="node-badge">{{ index + 1 }}</div>
-
+
<!-- 澶村儚鍖哄煙 -->
<div class="node-avatar-section">
- <div
- class="node-avatar"
- :class="{ 'has-user': item.approverId }"
- :style="item.approverId ? { backgroundColor: getAvatarColor(item.approverName) } : {}"
+ <div
+ class="node-avatar"
+ :class="{ 'has-user': item.approverIds && item.approverIds.length }"
+ :style="item.approverIds && item.approverIds.length ? { backgroundColor: getAvatarColor(item.approverNames?.[0] || '') } : {}"
>
- <span v-if="item.approverId">{{ item.approverName.charAt(0) }}</span>
+ <span v-if="item.approverIds && item.approverIds.length">{{ (item.approverNames?.[0] || '?').charAt(0) }}</span>
<el-icon v-else :size="24"><User /></el-icon>
</div>
<div class="node-level">{{ getLevelText(index) }}</div>
@@ -82,8 +82,11 @@
<!-- 閫夋嫨鍖哄煙 -->
<div class="node-select-section">
<el-select
- v-model="item.approverId"
- placeholder="閫夋嫨瀹℃壒浜�"
+ v-model="item.approverIds"
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ placeholder="閫夋嫨瀹℃壒浜猴紙鍙閫夛級"
filterable
size="default"
@change="(val) => handleApproverChange(val, item)"
@@ -95,6 +98,13 @@
:value="user.userId"
/>
</el-select>
+ <div class="node-mode">
+ <span class="mode-label">瀹℃壒鏂瑰紡</span>
+ <el-radio-group v-model="item.approveMode" size="small">
+ <el-radio-button value="or">鎴栫</el-radio-button>
+ <el-radio-button value="and">浼氱</el-radio-button>
+ </el-radio-group>
+ </div>
</div>
<!-- 鎿嶄綔鎸夐挳 -->
@@ -150,7 +160,7 @@
<div class="add-icon-wrapper">
<el-icon :size="28"><Plus /></el-icon>
</div>
- <span class="add-text">鏂板瀹℃壒浜�</span>
+ <span class="add-text">鏂板瀹℃壒鑺傜偣</span>
</div>
</div>
</div>
@@ -166,7 +176,7 @@
<div class="empty-subtext">鐐瑰嚮涓嬫柟鎸夐挳娣诲姞绗竴涓鎵逛汉</div>
<el-button type="primary" size="large" @click="handleAdd" class="empty-add-btn">
<el-icon><Plus /></el-icon>
- 鏂板瀹℃壒浜�
+ 鏂板瀹℃壒鑺傜偣
</el-button>
</div>
</div>
@@ -175,7 +185,7 @@
<!-- 搴曢儴鎻愮ず -->
<div class="bottom-tips">
<el-icon><InfoFilled /></el-icon>
- <span>鎻愮ず锛氭瘡涓祦绋嬭嚦灏戦厤缃竴涓鎵逛汉锛屽鎵规寜椤哄簭娴佽浆锛屽彲閫氳繃绠ご璋冩暣椤哄簭</span>
+ <span>鎻愮ず锛氭瘡涓妭鐐瑰彲閰嶇疆澶氬悕瀹℃壒浜哄苟閫夋嫨鎴栫/浼氱锛涙垨绛�=浠绘剰涓�浜洪�氳繃鍗虫祦杞紝浼氱=闇�鍏ㄩ儴浜洪�氳繃锛涘鎵规寜鑺傜偣椤哄簭娴佽浆</span>
</div>
</div>
</template>
@@ -186,7 +196,7 @@
import {
Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting,
Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked,
- Van, ArrowRight, User, InfoFilled
+ Van, ArrowRight, User, InfoFilled, Stamp, WarningFilled
} from '@element-plus/icons-vue';
import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement';
import { userListNoPage } from '@/api/system/user';
@@ -203,6 +213,7 @@
{ value: '5', label: '閲囪喘瀹℃壒', icon: 'ShoppingCart', color: '#909399' },
{ value: '6', label: '鎶ヤ环瀹℃壒', icon: 'DocumentChecked', color: '#9B59B6' },
{ value: '7', label: '鍙戣揣瀹℃壒', icon: 'Van', color: '#1ABC9C' },
+ { value: '8', label: '鍗遍櫓浣滀笟瀹℃壒', icon: 'WarningFilled', color: '#E74C3C' },
{ value: '9', label: '鐢ㄥ嵃瀹℃壒', icon: 'Stamp', color: '#7D3C98' },
];
@@ -215,6 +226,7 @@
5: '閲囪喘瀹℃壒',
6: '鎶ヤ环瀹℃壒',
7: '鍙戣揣瀹℃壒',
+ 8: '鍗遍櫓浣滀笟瀹℃壒',
9: '鐢ㄥ嵃瀹℃壒',
};
@@ -227,6 +239,7 @@
5: 'ShoppingCart',
6: 'DocumentChecked',
7: 'Van',
+ 8: 'WarningFilled',
9: 'Stamp',
};
@@ -239,6 +252,7 @@
5: '#909399',
6: '#9B59B6',
7: '#1ABC9C',
+ 8: '#E74C3C',
9: '#7D3C98',
};
@@ -253,6 +267,14 @@
// 褰撳墠瀹℃壒绫诲瀷
const currentApproveType = computed(() => {
return Number(activeTab.value);
+});
+
+// 澶撮儴缁熻鏂囨锛氳妭鐐规暟 / 瀹℃壒浜烘暟
+const approverCountText = computed(() => {
+ const nodes = approverList.value.length;
+ const users = approverList.value.reduce((sum, item) => sum + (item.approverIds ? item.approverIds.length : 0), 0);
+ if (nodes === 0) return '鏈厤缃鎵逛汉';
+ return `宸查厤缃� ${nodes} 鑺傜偣 / ${users} 瀹℃壒浜篳;
});
// 鑾峰彇绫诲瀷鍥炬爣
@@ -295,7 +317,7 @@
loadData();
};
-// 鍔犺浇瀹℃壒閰嶇疆鏁版嵁锛堟ā鎷燂級
+// 鍔犺浇瀹℃壒閰嶇疆鏁版嵁锛氭帴鍙f墎骞宠 鈫� 鎸� nodeOrder 鍒嗙粍鍚堝苟涓鸿妭鐐瑰崱鐗囷紙鍚岃妭鐐瑰瀹℃壒浜猴級
const loadData = async () => {
loading.value = true;
try {
@@ -307,12 +329,19 @@
: Array.isArray(res?.data?.records)
? res.data.records
: [];
- const data = source.map((item, index) => ({
- ...item,
- sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1,
- }));
- approverList.value = data.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
- originalList.value = JSON.parse(JSON.stringify(approverList.value));
+ const map = new Map();
+ source.forEach(item => {
+ const order = item.nodeOrder ?? 1;
+ if (!map.has(order)) {
+ map.set(order, { nodeOrder: order, approverIds: [], approverNames: [], approveMode: item.approveMode || 'or' });
+ }
+ const card = map.get(order);
+ card.approverIds.push(item.approverId);
+ if (item.approverName) card.approverNames.push(item.approverName);
+ });
+ const cards = [...map.values()].sort((a, b) => a.nodeOrder - b.nodeOrder);
+ approverList.value = cards;
+ originalList.value = JSON.parse(JSON.stringify(cards));
} catch (error) {
approverList.value = [];
originalList.value = [];
@@ -332,29 +361,28 @@
}
};
-// 瀹℃壒浜洪�夋嫨鍙樺寲
-const handleApproverChange = (userId, row) => {
- const user = userList.value.find((u) => u.userId === userId);
- if (user) {
- row.approverName = user.nickName;
- }
+// 瀹℃壒浜洪�夋嫨鍙樺寲锛堝閫夛級
+const handleApproverChange = (ids, row) => {
+ row.approverNames = (ids || []).map(id => {
+ const user = userList.value.find((u) => u.userId === id);
+ return user ? user.nickName : '';
+ }).filter(Boolean);
};
-// 鏂板瀹℃壒浜�
+// 鏂板瀹℃壒鑺傜偣
const handleAdd = () => {
const newOrder = approverList.value.length + 1;
approverList.value.push({
- id: null,
- approveType: currentApproveType.value,
- approverId: null,
- approverName: '',
- sortOrder: newOrder,
+ nodeOrder: newOrder,
+ approverIds: [],
+ approverNames: [],
+ approveMode: 'or',
});
};
-// 鍒犻櫎瀹℃壒浜�
+// 鍒犻櫎瀹℃壒鑺傜偣
const handleDelete = (index) => {
- ElMessageBox.confirm('纭畾鍒犻櫎璇ュ鎵逛汉鍚楋紵', '鎻愮ず', {
+ ElMessageBox.confirm('纭畾鍒犻櫎璇ュ鎵硅妭鐐瑰悧锛�', '鎻愮ず', {
confirmButtonText: '纭畾',
cancelButtonText: '鍙栨秷',
type: 'warning',
@@ -362,7 +390,7 @@
.then(() => {
approverList.value.splice(index, 1);
approverList.value.forEach((item, idx) => {
- item.sortOrder = idx + 1;
+ item.nodeOrder = idx + 1;
});
ElMessage.success('鍒犻櫎鎴愬姛');
})
@@ -375,8 +403,7 @@
const temp = approverList.value[index];
approverList.value[index] = approverList.value[index - 1];
approverList.value[index - 1] = temp;
- approverList.value[index].sortOrder = index + 1;
- approverList.value[index - 1].sortOrder = index;
+ approverList.value.forEach((item, idx) => { item.nodeOrder = idx + 1; });
};
// 鍚庣Щ
@@ -385,38 +412,38 @@
const temp = approverList.value[index];
approverList.value[index] = approverList.value[index + 1];
approverList.value[index + 1] = temp;
- approverList.value[index].sortOrder = index + 1;
- approverList.value[index + 1].sortOrder = index + 2;
+ approverList.value.forEach((item, idx) => { item.nodeOrder = idx + 1; });
};
-// 淇濆瓨閰嶇疆
+// 淇濆瓨閰嶇疆锛氳妭鐐瑰崱鐗囨寜瀹℃壒浜哄睍寮�涓哄琛� payload
const handleSave = async () => {
if (approverList.value.length === 0) {
- ElMessage.warning('璇疯嚦灏戦厤缃竴涓鎵逛汉');
+ ElMessage.warning('璇疯嚦灏戦厤缃竴涓鎵硅妭鐐�');
return;
}
- const hasEmptyApprover = approverList.value.some((item) => !item.approverId);
+ const hasEmptyApprover = approverList.value.some((item) => !item.approverIds || item.approverIds.length === 0);
if (hasEmptyApprover) {
- ElMessage.warning('璇烽�夋嫨鎵�鏈夊鎵逛汉');
- return;
- }
-
- const approverIds = approverList.value.map((item) => item.approverId);
- const uniqueIds = [...new Set(approverIds)];
- if (uniqueIds.length !== approverIds.length) {
- ElMessage.warning('瀹℃壒浜轰笉鑳介噸澶�');
+ ElMessage.warning('璇蜂负姣忎釜鑺傜偣閫夋嫨瀹℃壒浜�');
return;
}
saveLoading.value = true;
try {
- const payload = approverList.value.map((item, index) => ({
- approveType: currentApproveType.value,
- nodeOrder: index + 1,
- approverId: item.approverId,
- approverName: item.approverName,
- }));
+ const payload = [];
+ approverList.value.forEach((card) => {
+ const uniqueIds = [...new Set(card.approverIds || [])];
+ uniqueIds.forEach(id => {
+ const user = userList.value.find(u => u.userId === id);
+ payload.push({
+ approveType: currentApproveType.value,
+ nodeOrder: card.nodeOrder,
+ approverId: id,
+ approverName: user ? user.nickName : '',
+ approveMode: card.approveMode || 'or',
+ });
+ });
+ });
await addApproveProcessConfigNode(payload);
ElMessage.success('淇濆瓨鎴愬姛');
await loadData();
@@ -684,6 +711,20 @@
width: 100%;
}
+.node-mode {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px dashed var(--el-border-color-light, #ebeef5);
+}
+
+.mode-label {
+ font-size: 12px;
+ color: var(--el-text-color-secondary, #909399);
+}
+
.node-actions {
display: flex;
justify-content: center;
diff --git a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
index 90802a5..425c3b6 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -173,41 +173,43 @@
</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>
@@ -281,19 +283,31 @@
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);
};
// 鎵撳紑寮规
@@ -445,9 +459,8 @@
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();
});
@@ -479,6 +492,9 @@
align-items: center;
gap: 8px;
}
+.node-user-item {
+ margin: 8px 0;
+}
.node-status {
color: #1890ff;
margin-left: 8px;
diff --git a/src/views/collaborativeApproval/approvalProcess/index.vue b/src/views/collaborativeApproval/approvalProcess/index.vue
index 6ea576b..a15b121 100644
--- a/src/views/collaborativeApproval/approvalProcess/index.vue
+++ b/src/views/collaborativeApproval/approvalProcess/index.vue
@@ -287,11 +287,11 @@
}
);
- // 褰撳墠瀹℃壒浜哄垪
+ // 褰撳墠瀹℃壒浜哄垪锛堟敮鎸佸悓鑺傜偣澶氬鎵逛汉锛�
baseColumns.push({
label: "褰撳墠瀹℃壒浜�",
- prop: "approveUserCurrentName",
- width: 120
+ prop: "currentApproverNames",
+ width: 180
});
// 鎿嶄綔鍒�
@@ -318,11 +318,10 @@
openApprovalDia("approval", row);
},
disabled: (row) =>
- row.approveUserCurrentId == null ||
row.approveStatus == 2 ||
row.approveStatus == 3 ||
row.approveStatus == 4 ||
- row.approveUserCurrentId !== userStore.id
+ !row._canApprove
},
{
name: "璇︽儏",
@@ -387,7 +386,12 @@
tableLoading.value = true;
approveProcessListPage({...page, ...searchForm.value, approveType: currentApproveType.value}).then(res => {
tableLoading.value = false;
- tableData.value = res.data.records
+ // 瀵屽寲琛屾暟鎹細褰撳墠瀹℃壒浜烘樉绀猴紙澶氬�煎厹搴曟棫鍗曞�硷級+ 褰撳墠鐢ㄦ埛鏄惁鍙鏍�
+ tableData.value = (res.data.records || []).map(r => ({
+ ...r,
+ currentApproverNames: r.approveUserCurrentNames || r.approveUserCurrentName || '-',
+ _canApprove: String(r.approveUserCurrentIds ?? '').split(',').filter(Boolean).includes(String(userStore.id)) || r.approveUserCurrentId === userStore.id
+ }));
page.total = res.data.total;
// 鏇存柊褰撳墠绫诲瀷鏁伴噺
typeCounts.value[activeTab.value] = res.data.total;
diff --git a/src/views/safeProduction/safeWorkApproval/components/approvalDia.vue b/src/views/safeProduction/safeWorkApproval/components/approvalDia.vue
index 5a3a5de..b7167be 100644
--- a/src/views/safeProduction/safeWorkApproval/components/approvalDia.vue
+++ b/src/views/safeProduction/safeWorkApproval/components/approvalDia.vue
@@ -198,29 +198,28 @@
<el-form :model="{ activities }"
ref="formRef"
label-position="top">
- <el-steps :active="getActiveStep()"
+ <el-steps :active="getActiveStep()"
finish-status="success"
process-status="process"
align-center
direction="vertical">
- <el-step v-for="(activity, index) in activities"
- :key="index"
+ <el-step v-for="(group, gIndex) in stepGroups"
+ :key="gIndex"
finish-status="success"
- :title="getNodeTitle(index, activities.length)"
- :description="activity.approveNodeUser"
- :icon="getNodeIcon(activity, index)">
+ :title="getNodeTitle(gIndex, stepGroups.length)"
+ :icon="getNodeIcon(group)">
<template #icon>
- <el-icon v-if="activity.approveNodeStatus === 2"
+ <el-icon v-if="group.some(a => a.approveNodeStatus === 2)"
color="red"
:size="22">
<WarningFilled />
</el-icon>
- <el-icon v-else-if="activity.isShen"
+ <el-icon v-else-if="group.some(a => a.isShen)"
color="#1890ff"
:size="22">
<Edit />
</el-icon>
- <el-icon v-else-if="activity.approveNodeStatus === 1"
+ <el-icon v-else-if="group.every(a => a.approveNodeStatus === 1)"
color="#67C23A"
:size="26">
<Check />
@@ -232,37 +231,42 @@
</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-if="!activity.isShen"
- class="node-reason">
- <span>绛惧悕锛�</span>
- <img :src="activity.urlTem"
- class="signImg"
- alt=""
- v-if="activity.urlTem" />
- </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-if="!activity.isShen"
+ class="node-reason">
+ <span>绛惧悕锛�</span>
+ <img :src="activity.urlTem"
+ class="signImg"
+ alt=""
+ v-if="activity.urlTem" />
+ </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>
@@ -348,19 +352,31 @@
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);
};
// 鎵撳紑寮规
@@ -474,10 +490,8 @@
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();
});
@@ -506,6 +520,9 @@
align-items: center;
gap: 8px;
}
+ .node-user-item {
+ margin: 8px 0;
+ }
.node-status {
color: #1890ff;
margin-left: 8px;
diff --git a/src/views/safeProduction/safeWorkApproval/index.vue b/src/views/safeProduction/safeWorkApproval/index.vue
index 2d8362e..1d40ab3 100644
--- a/src/views/safeProduction/safeWorkApproval/index.vue
+++ b/src/views/safeProduction/safeWorkApproval/index.vue
@@ -179,11 +179,11 @@
}
);
- // 褰撳墠瀹℃壒浜哄垪
+ // 褰撳墠瀹℃壒浜哄垪锛堟敮鎸佸悓鑺傜偣澶氬鎵逛汉锛�
baseColumns.push({
label: "褰撳墠瀹℃壒浜�",
- prop: "approveUserCurrentName",
- width: 120,
+ prop: "currentApproverNames",
+ width: 180,
});
// 鎿嶄綔鍒�
@@ -212,11 +212,10 @@
openApprovalDia("approval", row);
},
disabled: row =>
- row.approveUserCurrentId == null ||
row.approveStatus == 2 ||
row.approveStatus == 3 ||
row.approveStatus == 4 ||
- row.approveUserCurrentId !== userStore.id,
+ !row._canApprove,
},
{
name: "璇︽儏",
@@ -273,7 +272,12 @@
})
.then(res => {
tableLoading.value = false;
- tableData.value = res.data.records;
+ // 瀵屽寲琛屾暟鎹細褰撳墠瀹℃壒浜烘樉绀猴紙澶氬�煎厹搴曟棫鍗曞�硷級+ 褰撳墠鐢ㄦ埛鏄惁鍙鏍�
+ tableData.value = (res.data.records || []).map(r => ({
+ ...r,
+ currentApproverNames: r.approveUserCurrentNames || r.approveUserCurrentName || '-',
+ _canApprove: String(r.approveUserCurrentIds ?? '').split(',').filter(Boolean).includes(String(userStore.id)) || r.approveUserCurrentId === userStore.id
+ }));
page.total = res.data.total;
})
.catch(err => {
--
Gitblit v1.9.3