merge: t24 待办派单/转办

This commit is contained in:
2026-10-30 21:48:34 +08:00
5 changed files with 308 additions and 2 deletions
+12
View File
@@ -173,3 +173,15 @@ export interface ApprovalOrganization {
organizationCode?: string;
leaderId?: number;
}
/**
* 审批目录用户(派单候选池)
*/
export interface ApprovalUser {
userId: number;
username?: string;
nickname?: string;
realName?: string;
organizationId?: number;
organizationName?: string;
}
+28 -1
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import type { ApiResult } from '@/api';
import type { ApprovalInstance } from '../model';
import type { ApprovalInstance, ApprovalUser } from '../model';
import { THINK_API_URL } from '@/config/setting';
/**
@@ -70,6 +70,33 @@ export async function archiveTask(id: number) {
return Promise.reject(new Error(res.data.message));
}
/**
* 派单候选池(仅派单节点、本人待办)
*/
export async function listDispatchCandidates(id: number) {
const res = await request.get<ApiResult<ApprovalUser[]>>(
THINK_API_URL + '/approval/task/' + id + '/candidates'
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 派单(选定 1 人即完成派单节点)
*/
export async function dispatchTask(id: number, targetUserId: number) {
const res = await request.post<ApiResult<ApprovalInstance>>(
THINK_API_URL + '/approval/task/' + id + '/dispatch',
{ targetUserId }
);
if (res.data.code === 0 && res.data.data) {
return res.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 撤回
*/
+31 -1
View File
@@ -146,6 +146,13 @@
>
通过
</button>
<button
v-if="detail.myTask && detail.permissions.canDispatch"
class="action-button primary"
@click="openDispatch"
>
派单
</button>
<button
v-if="detail.myTask && detail.permissions.canReject"
class="action-button danger"
@@ -220,7 +227,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { getInstance, submitInstance } from '@/api/approval/instance';
import {
approveTask,
@@ -247,6 +254,7 @@ const hasActions = computed(() => {
return (
permissions.canApprove ||
permissions.canReject ||
permissions.canDispatch ||
permissions.canConfirm ||
permissions.canArchive ||
permissions.canWithdraw ||
@@ -261,11 +269,22 @@ const editLabel = computed(() =>
: '编辑'
);
// 首次进入由 onLoad 加载;从派单/编辑页返回时重新拉取,避免展示过期节点
let entered = false;
onLoad(options => {
instanceId.value = Number(options?.id) || 0;
load();
});
onShow(() => {
if (!entered) {
entered = true;
return;
}
load();
});
async function load() {
if (!instanceId.value) {
return;
@@ -302,6 +321,17 @@ function openApprove() {
actionDialog.value = 'approve';
}
function openDispatch() {
const taskId = detail.value?.myTask?.id;
if (!taskId) {
showError('任务已处理,请刷新后重试', null);
return;
}
uni.navigateTo({
url: '/packageApproval/pages/dispatch?taskId=' + taskId
});
}
function openReject() {
opinion.value = '';
actionDialog.value = 'reject';
+230
View File
@@ -0,0 +1,230 @@
<template>
<view class="dispatch-page">
<uni-card :border="false" :padding="'16px'" title="选择派单对象">
<text class="hint">
派单后由报送人与被派单人分别存档,请从受理部门启用成员中选择 1 人。
</text>
<view
v-for="item in candidates"
:key="item.userId"
class="candidate"
:class="{ active: selectedId === item.userId }"
@click="select(item)"
>
<view class="candidate-main">
<text class="candidate-name">{{ displayName(item) }}</text>
<text class="candidate-meta">
{{ item.username }}
<text v-if="item.organizationName"> · {{ item.organizationName }}</text>
</text>
</view>
<view class="candidate-radio" :class="{ checked: selectedId === item.userId }">
<view v-if="selectedId === item.userId" class="candidate-dot"></view>
</view>
</view>
<view v-if="loading" class="empty">加载中...</view>
<view v-else-if="candidates.length === 0" class="empty">
暂无可派单人员,请联系管理员维护受理部门成员
</view>
</uni-card>
<view class="footer">
<button
class="footer-button primary"
:loading="submitting"
:disabled="!selectedId || submitting"
@click="confirmDispatch"
>
确认派单
</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { dispatchTask, listDispatchCandidates } from '@/api/approval/task';
import type { ApprovalUser } from '@/api/approval/model';
import { showError } from '@/utils/app';
const taskId = ref(0);
const candidates = ref<ApprovalUser[]>([]);
const selectedId = ref(0);
const loading = ref(false);
const submitting = ref(false);
onLoad(options => {
taskId.value = Number(options?.taskId) || 0;
load();
});
async function load() {
if (!taskId.value) {
showError('任务不存在,请返回重试', null);
return;
}
loading.value = true;
try {
candidates.value = await listDispatchCandidates(taskId.value);
} catch (error: any) {
candidates.value = [];
showError(error?.message || '候选人员加载失败', null);
} finally {
loading.value = false;
}
}
function displayName(item: ApprovalUser) {
return item.realName || item.nickname || item.username || '—';
}
function select(item: ApprovalUser) {
if (submitting.value) {
return;
}
selectedId.value = item.userId;
}
function confirmDispatch() {
const target = candidates.value.find(item => item.userId === selectedId.value);
if (!target) {
showError('请选择派单对象', null);
return;
}
if (!taskId.value) {
showError('任务不存在,请返回重试', null);
return;
}
uni.showModal({
title: '派单确认',
content: '确定将单据派给「' + displayName(target) + '」?派单后不能更改。',
success: async result => {
if (!result.confirm) {
return;
}
submitting.value = true;
try {
const response = await dispatchTask(taskId.value, target.userId);
uni.showToast({ title: response.message || '派单成功', icon: 'none' });
setTimeout(() => {
uni.navigateBack({ delta: 1 });
}, 600);
} catch (error: any) {
showError(error?.message || '派单失败,请稍后重试', null);
} finally {
submitting.value = false;
}
}
});
}
</script>
<style lang="scss">
.dispatch-page {
min-height: 100vh;
background-color: #f5f6f8;
padding-bottom: 180rpx;
}
.hint {
display: block;
font-size: 24rpx;
line-height: 36rpx;
color: #909399;
margin-bottom: 16rpx;
}
.candidate {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 20rpx;
margin-top: 12rpx;
border: 2rpx solid #ebeef5;
border-radius: 12rpx;
background-color: #ffffff;
}
.candidate.active {
border-color: #1677ff;
background-color: #f0f7ff;
}
.candidate-main {
display: flex;
flex-direction: column;
flex: 1;
}
.candidate-name {
font-size: 28rpx;
font-weight: 600;
color: #303133;
}
.candidate-meta {
margin-top: 6rpx;
font-size: 24rpx;
color: #909399;
}
.candidate-radio {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #c0c4cc;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.candidate-radio.checked {
border-color: #1677ff;
}
.candidate-dot {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #1677ff;
}
.empty {
padding: 40rpx 0;
text-align: center;
font-size: 26rpx;
color: #909399;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
padding: 20rpx 24rpx;
background-color: #ffffff;
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.footer-button {
flex: 1;
height: 88rpx;
line-height: 88rpx;
font-size: 30rpx;
border-radius: 12rpx;
}
.footer-button.primary {
background-color: #1677ff;
color: #ffffff;
}
.footer-button.primary[disabled] {
background-color: #a0c4ff;
color: #ffffff;
}
</style>
+7
View File
@@ -63,6 +63,13 @@
"navigationBarTitleText": "内部协调单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/dispatch",
"style": {
"navigationBarTitleText": "派单",
"enablePullDownRefresh": false
}
}
]
},