diff --git a/README.md b/README.md
index 4df1ccf..e998b62 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
## 目录
- `backend/`:Fastify + TypeScript 后端 API;若实际后端位于仓库根目录,以 `docs/repository-map.md` 为准
-- `admin/`:Vue3 后台管理端,已接入 M08-B 保洁运营工作台、协作者管理、任务详情照片预览、任务操作流水、结算详情、待结算候选预览与导出、结算列表导出、统计明细、每日趋势、保洁员资料管理、微信绑定状态、保洁员选择器和任务/结算/统计筛选,必须适配桌面、平板和手机
+- `admin/`:Vue3 后台管理端,已接入 M08-B 保洁运营工作台、待验收任务批量验收/驳回、协作者管理、任务详情照片预览、任务操作流水、结算详情、待结算候选预览与导出、结算列表导出、统计明细、每日趋势、保洁员资料管理、微信绑定状态、保洁员选择器和任务/结算/统计筛选,必须适配桌面、平板和手机
- `miniapp/`:微信原生小程序
- `database/`:迁移、种子和兼容 SQL
- `deploy/`:Nginx、PM2、EMQX 模板与部署版本
@@ -121,7 +121,7 @@ WSL 已验证:EMQX `5.8.9`、MQTTX CLI `1.13.0`、EMQX 服务 `active (running
项目已开发部分模块。具体完成度不得从 README 猜测,必须以现有代码、测试、数据库迁移、Git 历史以及 `docs/current-baseline.md`、`docs/module-status.md`、`docs/feature-status.md` 为准。
- 当前执行游标:`M08-B`(PARTIAL)
-- 最近工程提交:`M08-B任务事件流水增量`,本轮补齐后台保洁任务事件 API 和任务详情操作流水。
+- 最近工程提交:`M08-B批量验收驳回增量`,本轮补齐后台待验收任务多选批量验收和批量驳回。
- 下一工程目标:继续 M08-B,补更完整保洁运营管理界面。
## 版本递进
diff --git a/admin/src/App.vue b/admin/src/App.vue
index 09f52a8..130b6b8 100644
--- a/admin/src/App.vue
+++ b/admin/src/App.vue
@@ -106,7 +106,9 @@
@page-change="changeTaskPage"
@assign="handleAssign"
@complete="handleComplete"
+ @complete-many="handleCompleteMany"
@reject="handleReject"
+ @reject-many="handleRejectMany"
@reclaim="handleReclaim"
/>
@@ -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);
diff --git a/admin/src/components/CleaningTasksPanel.vue b/admin/src/components/CleaningTasksPanel.vue
index c510650..116f9d2 100644
--- a/admin/src/components/CleaningTasksPanel.vue
+++ b/admin/src/components/CleaningTasksPanel.vue
@@ -34,6 +34,22 @@