feat(web-admin): add OA task transfer action on instance detail
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
35c60f95e8
commit
5350db1dc4
2 files changed
+141
-27
No files matched your search
@@ -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`,
|
||||
|
||||
@@ -46,6 +46,15 @@
|
||||
<el-icon><Plus /></el-icon>
|
||||
加签
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canApprove"
|
||||
class="btn-ghost"
|
||||
:loading="acting"
|
||||
@click="openTransfer"
|
||||
>
|
||||
<el-icon><Position /></el-icon>
|
||||
转交
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canCopyRestart"
|
||||
class="btn-main"
|
||||
@@ -103,8 +112,6 @@
|
||||
v-model="addSignUserIds"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="选择加签用户"
|
||||
style="width: 100%"
|
||||
:loading="userLoading"
|
||||
@@ -132,6 +139,51 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="transferVisible"
|
||||
title="转交"
|
||||
width="520px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
append-to-body
|
||||
modal-class="protocol-dialog-mask"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="转交给">
|
||||
<el-select
|
||||
v-model="transferUserId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择用户"
|
||||
style="width: 100%"
|
||||
:loading="userLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in transferUserOptions"
|
||||
:key="u.id"
|
||||
:label="`${u.nickName}(${u.userNo})`"
|
||||
:value="u.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="说明(可选)">
|
||||
<el-input v-model="transferComment" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="transferVisible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="acting" @click="submitTransfer">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定转交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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<string[]>([])
|
||||
const addSignComment = ref('')
|
||||
const transferVisible = ref(false)
|
||||
const transferUserId = ref('')
|
||||
const transferComment = ref('')
|
||||
const userLoading = ref(false)
|
||||
const userOptions = ref<Array<{ id: string; nickName: string; userNo: string }>>([])
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user