feat(M08-B): 补批量验收驳回

This commit is contained in:
Codex
2026-07-02 08:01:40 +08:00
parent 986a90806b
commit 5eff0a4a18
7 changed files with 137 additions and 13 deletions
+20
View File
@@ -106,7 +106,9 @@
@page-change="changeTaskPage"
@assign="handleAssign"
@complete="handleComplete"
@complete-many="handleCompleteMany"
@reject="handleReject"
@reject-many="handleRejectMany"
@reclaim="handleReclaim"
/>
</el-tab-pane>
@@ -411,6 +413,15 @@ async function handleComplete(input: { taskId: string; note?: string }) {
}, '已验收');
}
async function handleCompleteMany(input: { taskIds: string[]; note?: string }) {
await runAction(async () => {
for (const taskId of input.taskIds) {
await completeCleaningTask(session.value, taskId, input.note);
}
await Promise.all([loadTasks(), loadStatistics()]);
}, `已批量验收 ${input.taskIds.length} 个任务`);
}
async function handleReject(input: { taskId: string; reason: string }) {
await runAction(async () => {
await rejectCleaningTask(session.value, input.taskId, input.reason);
@@ -418,6 +429,15 @@ async function handleReject(input: { taskId: string; reason: string }) {
}, '已驳回');
}
async function handleRejectMany(input: { taskIds: string[]; reason: string }) {
await runAction(async () => {
for (const taskId of input.taskIds) {
await rejectCleaningTask(session.value, taskId, input.reason);
}
await Promise.all([loadTasks(), loadStatistics()]);
}, `已批量驳回 ${input.taskIds.length} 个任务`);
}
async function handleReclaim(input: { olderThanMinutes: number; limit: number }) {
await runAction(async () => {
await reclaimCleaningTimeouts(session.value, input);
+92 -2
View File
@@ -34,6 +34,22 @@
<el-button :icon="Search" @click="submitFilters">筛选</el-button>
</div>
<div class="toolbar-actions">
<el-button
type="success"
:icon="BadgeCheck"
:disabled="selectedSubmittedCount === 0"
@click="submitBatchComplete"
>
批量验收
</el-button>
<el-button
type="danger"
:icon="Undo2"
:disabled="selectedSubmittedCount === 0"
@click="openBatchReject"
>
批量驳回
</el-button>
<el-popover trigger="click" width="280">
<template #reference>
<el-button :icon="RotateCcw">回收</el-button>
@@ -56,7 +72,14 @@
</div>
</div>
<el-table :data="items" :loading="loading" class="data-table" row-key="id">
<el-table
:data="items"
:loading="loading"
class="data-table"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="44" :selectable="canSelectTask" />
<el-table-column prop="taskNo" label="任务" min-width="180" fixed>
<template #default="{ row }">
<div class="stack">
@@ -171,6 +194,29 @@
</template>
</el-dialog>
<el-dialog v-model="batchRejectDialog.open" title="批量驳回待验收任务" width="460px">
<div class="dialog-stack">
<el-alert
:title="`将驳回 ${selectedSubmittedCount} 个已选择的待验收任务`"
type="warning"
show-icon
:closable="false"
/>
<el-input
v-model="batchRejectDialog.reason"
type="textarea"
:rows="4"
maxlength="512"
show-word-limit
placeholder="请输入统一驳回原因"
/>
</div>
<template #footer>
<el-button @click="batchRejectDialog.open = false">取消</el-button>
<el-button type="danger" @click="submitBatchReject">批量驳回</el-button>
</template>
</el-dialog>
<el-dialog v-model="memberDialog.open" title="协作者管理" width="640px">
<div class="dialog-stack">
<div class="detail-header">
@@ -322,7 +368,7 @@
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { computed, reactive, ref, watch } from 'vue';
import {
BadgeCheck,
Check,
@@ -372,7 +418,9 @@ const emit = defineEmits<{
'page-change': [number];
assign: [{ taskId: string; cleanerUserId: string; note?: string }];
complete: [{ taskId: string; note?: string }];
'complete-many': [{ taskIds: string[]; note?: string }];
reject: [{ taskId: string; reason: string }];
'reject-many': [{ taskIds: string[]; reason: string }];
reclaim: [{ olderThanMinutes: number; limit: number }];
}>();
@@ -387,8 +435,10 @@ const statusOptions = [
];
const filterDraft = reactive({ storeId: props.filters.storeId, cleanerUserId: props.filters.cleanerUserId });
const reclaimForm = reactive({ olderThanMinutes: 60, limit: 20 });
const selectedTasks = ref<CleaningTask[]>([]);
const assignDialog = reactive({ open: false, taskId: '', cleanerUserId: '', note: '' });
const rejectDialog = reactive({ open: false, taskId: '', reason: '' });
const batchRejectDialog = reactive({ open: false, reason: '' });
const detailDialog = reactive<{
open: boolean;
loadingEvents: boolean;
@@ -411,6 +461,9 @@ const memberDialog = reactive({
note: '',
members: [] as CleaningTaskMember[]
});
const selectedSubmittedTasks = computed(() => selectedTasks.value
.filter((task) => task.status === 'SUBMITTED'));
const selectedSubmittedCount = computed(() => selectedSubmittedTasks.value.length);
watch(
() => props.filters,
@@ -425,6 +478,14 @@ function taskTag(status: TaskStatus) {
return 'info';
}
function canSelectTask(row: CleaningTask) {
return row.status === 'SUBMITTED';
}
function handleSelectionChange(rows: CleaningTask[]) {
selectedTasks.value = rows;
}
function cleanerLabel(cleaner: ManagedUser) {
const name = cleaner.nickname || `ID ${cleaner.id}`;
const phone = cleaner.maskedPhone ? ` · ${cleaner.maskedPhone}` : '';
@@ -484,6 +545,35 @@ function submitReject() {
rejectDialog.open = false;
}
function submitBatchComplete() {
const taskIds = selectedSubmittedTasks.value.map((task) => task.id);
if (!taskIds.length) {
ElMessage.warning('请选择待验收任务');
return;
}
emit('complete-many', { taskIds, note: '后台批量验收通过' });
}
function openBatchReject() {
if (!selectedSubmittedCount.value) {
ElMessage.warning('请选择待验收任务');
return;
}
batchRejectDialog.open = true;
batchRejectDialog.reason = '';
}
function submitBatchReject() {
const reason = batchRejectDialog.reason.trim();
if (!reason) {
ElMessage.warning('请输入驳回原因');
return;
}
const taskIds = selectedSubmittedTasks.value.map((task) => task.id);
emit('reject-many', { taskIds, reason });
batchRejectDialog.open = false;
}
async function loadMembers() {
memberDialog.loading = true;
try {