diff --git a/web-admin/src/api/oaApproval.ts b/web-admin/src/api/oaApproval.ts
index e3a547cd..44031153 100644
--- a/web-admin/src/api/oaApproval.ts
+++ b/web-admin/src/api/oaApproval.ts
@@ -288,6 +288,17 @@ export function fetchAddSignOaTask(
})
}
+export function fetchTransferOaTask(
+ taskId: string,
+ data: { targetUserId: string; comment?: string }
+) {
+ return request.post({
+ url: `${BASE}/tasks/${taskId}/transfer`,
+ data,
+ showSuccessMessage: true
+ })
+}
+
export function fetchWithdrawOaInstance(instanceId: string) {
return request.post({
url: `${BASE}/instances/${instanceId}/withdraw`,
diff --git a/web-admin/src/views/oa-approval/instances/detail.vue b/web-admin/src/views/oa-approval/instances/detail.vue
index 38aa8e93..62b3ac55 100644
--- a/web-admin/src/views/oa-approval/instances/detail.vue
+++ b/web-admin/src/views/oa-approval/instances/detail.vue
@@ -46,6 +46,15 @@
加签
+
+
+ 转交
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ 确定转交
+
+
+
@@ -140,7 +192,7 @@
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
- import { ArrowLeft, Check, Close, Plus, RefreshLeft, RefreshRight } from '@element-plus/icons-vue'
+ import { ArrowLeft, Check, Close, Plus, Position, RefreshLeft, RefreshRight } from '@element-plus/icons-vue'
import { fetchUserList } from '@/api/admin'
import {
fetchAddSignOaTask,
@@ -148,6 +200,7 @@
fetchOaInstanceDetail,
fetchRejectOaTask,
fetchReturnOaTask,
+ fetchTransferOaTask,
fetchWithdrawOaInstance
} from '@/api/oaApproval'
import OaProgressChain from '@/components/oa-approval/OaProgressChain.vue'
@@ -177,6 +230,9 @@
const addSignVisible = ref(false)
const addSignUserIds = ref([])
const addSignComment = ref('')
+ const transferVisible = ref(false)
+ const transferUserId = ref('')
+ const transferComment = ref('')
const userLoading = ref(false)
const userOptions = ref>([])
@@ -186,29 +242,38 @@
const timelineItems = computed(() => {
const list = detail.value?.progress
if (!Array.isArray(list) || !list.length) return []
- return list.map((p: any, idx: number) => ({
- key: `${p.kind}-${p.nodeKey || p.taskId || idx}-${p.roundNo || 1}`,
- title: p.title || '-',
- status: p.status || 'info',
- timestamp: p.timestamp || '',
- assignee: p.assigneeUserName || p.assigneeUserId || '',
- resolveSource: p.resolveSource || '',
- commentText: p.commentText || '',
- taskType: p.taskType || '',
- kind: p.kind || '',
- roundNo: p.roundNo || 1,
- signMode: p.signMode || '',
- people: Array.isArray(p.people)
- ? p.people.map((x: any) => ({
- id: x.id || '',
- nickName: x.nickName || x.id || '',
- status: x.status || '',
- timestamp: x.timestamp || '',
- commentText: x.commentText || '',
- taskId: x.taskId || ''
- }))
- : []
- }))
+ return list
+ .filter((p: any) => {
+ // 打回只保留审批人节点,过滤独立终态「已打回」行
+ const kind = String(p?.kind || '')
+ const status = String(p?.status || '')
+ const title = String(p?.title || '').trim()
+ if (kind === 'terminal' && (status === 'returned' || title === '已打回')) return false
+ return true
+ })
+ .map((p: any, idx: number) => ({
+ key: `${p.kind}-${p.nodeKey || p.taskId || idx}-${p.roundNo || 1}`,
+ title: p.title || '-',
+ status: p.status || 'info',
+ timestamp: p.timestamp || '',
+ assignee: p.assigneeUserName || p.assigneeUserId || '',
+ resolveSource: p.resolveSource || '',
+ commentText: p.commentText || '',
+ taskType: p.taskType || '',
+ kind: p.kind || '',
+ roundNo: p.roundNo || 1,
+ signMode: p.signMode || '',
+ people: Array.isArray(p.people)
+ ? p.people.map((x: any) => ({
+ id: x.id || '',
+ nickName: x.nickName || x.id || '',
+ status: x.status || '',
+ timestamp: x.timestamp || '',
+ commentText: x.commentText || '',
+ taskId: x.taskId || ''
+ }))
+ : []
+ }))
})
const currentUserId = computed(() => {
@@ -230,6 +295,16 @@
const canApprove = computed(() => !!myPendingTask.value)
+ const transferUserOptions = computed(() => {
+ const me = currentUserId.value
+ const pendingIds = new Set(
+ (detail.value?.tasks || [])
+ .filter((t: any) => t.status === 'pending' && t.taskType === 'approval')
+ .map((t: any) => String(t.assigneeUserId || ''))
+ )
+ return userOptions.value.filter((u) => u.id !== me && !pendingIds.has(u.id))
+ })
+
const canCopyRestart = computed(() => {
if (!isInitiator.value || !detail.value) return false
return ['returned', 'cancelled', 'rejected'].includes(String(detail.value.status || ''))
@@ -254,7 +329,8 @@
done: '已结束',
submitted: '已提交',
not_reached: '未到达',
- returned: '已打回'
+ returned: '已打回',
+ transferred: '已转交'
}
return map[s] || s
}
@@ -414,6 +490,33 @@
}
}
+ async function openTransfer() {
+ transferUserId.value = ''
+ transferComment.value = ''
+ transferVisible.value = true
+ if (!userOptions.value.length) await loadUsers()
+ }
+
+ async function submitTransfer() {
+ const task = myPendingTask.value
+ if (!task) return
+ if (!transferUserId.value) {
+ ElMessage.warning('请选择转交人')
+ return
+ }
+ acting.value = true
+ try {
+ await fetchTransferOaTask(task.id, {
+ targetUserId: transferUserId.value,
+ comment: transferComment.value || undefined
+ })
+ transferVisible.value = false
+ await load()
+ } finally {
+ acting.value = false
+ }
+ }
+
function goBack() {
router.back()
}