feat(approval): 员工端待办/已办与通过驳回撤回 (#20)

- 审批中心三个 tab 接入待办/已办接口,支持状态+关键词筛选与下拉刷新
- 详情页新增通过(意见可选)、驳回(意见必填)、撤回(二次确认)
- 驳回/撤回后提供修改并重新提交入口,按钮按 permissions 显隐
This commit is contained in:
2026-10-30 20:45:26 +08:00
parent 0a595d1c11
commit 7b6e1a36d6
5 changed files with 352 additions and 58 deletions
+217 -8
View File
@@ -109,13 +109,35 @@
<view v-if="detail.records.length === 0" class="empty">暂无记录</view>
</uni-card>
<view v-if="detail.permissions.canEdit || detail.permissions.canSubmit" class="actions">
<view v-if="hasActions" class="actions">
<button
v-if="detail.permissions.canEdit"
class="action-button ghost"
@click="editDraft"
v-if="detail.myTask && detail.permissions.canApprove"
class="action-button primary"
@click="openApprove"
>
编辑
通过
</button>
<button
v-if="detail.myTask && detail.permissions.canReject"
class="action-button danger"
@click="openReject"
>
驳回
</button>
<button
v-if="detail.permissions.canWithdraw"
class="action-button ghost"
:loading="submitting"
@click="withdraw"
>
撤回
</button>
<button
v-if="showEditButton"
class="action-button ghost"
@click="editAndResubmit"
>
{{ editLabel }}
</button>
<button
v-if="detail.permissions.canSubmit"
@@ -123,9 +145,27 @@
:loading="submitting"
@click="submitDraft"
>
提交
{{ detail.status === 'DRAFT' ? '提交' : '重新提交' }}
</button>
</view>
<view v-if="actionDialog" class="dialog-mask">
<view class="dialog">
<text class="dialog-title">{{ actionDialog === 'approve' ? '审批意见' : '驳回意见' }}</text>
<textarea
v-model="opinion"
class="dialog-input"
:maxlength="500"
:placeholder="actionDialog === 'approve' ? '请输入意见(可选)' : '请输入驳回意见(必填)'"
/>
<view class="dialog-actions">
<button class="dialog-button ghost" @click="closeDialog">取消</button>
<button class="dialog-button primary" :loading="submitting" @click="confirmAction">
确定
</button>
</view>
</view>
</view>
</view>
<view v-else class="loading">
<text>{{ loading ? '加载中...' : '单据不存在' }}</text>
@@ -134,9 +174,14 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getInstance, submitInstance } from '@/api/approval/instance';
import {
approveTask,
rejectTask,
withdrawInstance
} from '@/api/approval/task';
import type { ApprovalInstance } from '@/api/approval/model';
import { showError } from '@/utils/app';
@@ -144,6 +189,28 @@ const detail = ref<ApprovalInstance | null>(null);
const loading = ref(false);
const submitting = ref(false);
const instanceId = ref(0);
const actionDialog = ref<'' | 'approve' | 'reject'>('');
const opinion = ref('');
const hasActions = computed(() => {
const permissions = detail.value?.permissions;
if (!permissions) {
return false;
}
return (
permissions.canApprove ||
permissions.canReject ||
permissions.canWithdraw ||
permissions.canSubmit ||
permissions.canEdit
);
});
const showEditButton = computed(() => !!detail.value?.permissions.canEdit);
const editLabel = computed(() =>
detail.value?.status === 'REJECTED' || detail.value?.status === 'WITHDRAWN'
? '修改并重新提交'
: '编辑'
);
onLoad(options => {
instanceId.value = Number(options?.id) || 0;
@@ -164,13 +231,85 @@ async function load() {
}
}
function editDraft() {
function editAndResubmit() {
if (detail.value?.docType !== 'completion') {
showError('该单据类型暂不支持在线修改', null);
return;
}
uni.navigateTo({
url:
'/packageApproval/pages/form-completion?mode=edit&id=' + instanceId.value
});
}
function openApprove() {
opinion.value = '';
actionDialog.value = 'approve';
}
function openReject() {
opinion.value = '';
actionDialog.value = 'reject';
}
function closeDialog() {
if (submitting.value) {
return;
}
actionDialog.value = '';
}
async function confirmAction() {
const taskId = detail.value?.myTask?.id;
if (!taskId) {
showError('任务已处理,请刷新后重试', null);
return;
}
const isApprove = actionDialog.value === 'approve';
if (!isApprove && !opinion.value.trim()) {
uni.showToast({ title: '请填写驳回意见', icon: 'none' });
return;
}
submitting.value = true;
try {
const response = isApprove
? await approveTask(taskId, opinion.value.trim() || undefined)
: await rejectTask(taskId, opinion.value.trim());
actionDialog.value = '';
uni.showToast({
title: response.message || (isApprove ? '已通过' : '已驳回'),
icon: 'none'
});
await load();
} catch (error: any) {
showError(error?.message || '操作失败', null);
} finally {
submitting.value = false;
}
}
function withdraw() {
uni.showModal({
title: '撤回确认',
content: '撤回后单据回到报送人,可修改后重新提交。确认撤回?',
success: async result => {
if (!result.confirm) {
return;
}
submitting.value = true;
try {
const response = await withdrawInstance(instanceId.value);
uni.showToast({ title: response.message || '撤回成功', icon: 'none' });
await load();
} catch (error: any) {
showError(error?.message || '撤回失败', null);
} finally {
submitting.value = false;
}
}
});
}
function submitDraft() {
uni.showModal({
title: '提交确认',
@@ -411,12 +550,14 @@ function formatSize(value?: number) {
.actions {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
padding: 24rpx 32rpx 48rpx;
}
.action-button {
flex: 1;
min-width: 200rpx;
font-size: 30rpx;
border-radius: 12rpx;
}
@@ -432,6 +573,74 @@ function formatSize(value?: number) {
border: 1rpx solid #dcdfe6;
}
.action-button.danger {
background: #ffffff;
color: #cf1322;
border: 1rpx solid #cf1322;
}
.dialog-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.dialog {
width: 600rpx;
padding: 32rpx;
border-radius: 16rpx;
background: #ffffff;
display: flex;
flex-direction: column;
}
.dialog-title {
font-size: 30rpx;
font-weight: 600;
color: #303133;
}
.dialog-input {
width: 100%;
height: 180rpx;
margin-top: 20rpx;
padding: 16rpx;
box-sizing: border-box;
font-size: 26rpx;
background: #f5f6f8;
border-radius: 12rpx;
}
.dialog-actions {
display: flex;
gap: 20rpx;
margin-top: 24rpx;
}
.dialog-button {
flex: 1;
font-size: 28rpx;
border-radius: 12rpx;
}
.dialog-button.primary {
background: #d81e06;
color: #ffffff;
}
.dialog-button.ghost {
background: #ffffff;
color: #303133;
border: 1rpx solid #dcdfe6;
}
.loading {
padding: 160rpx 0;
text-align: center;