From e46d8c0f0af34229e0421eaf501d31023f9c6b04 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 30 Jun 2026 17:30:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(M08-B):=20=E8=A1=A5=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- admin/src/App.vue | 26 ++++++++- admin/src/api.ts | 8 ++- .../components/CleaningSettlementsPanel.vue | 56 +++++++++++++++++-- admin/src/components/CleaningTasksPanel.vue | 56 +++++++++++++++++-- admin/src/styles.css | 8 +++ .../src/cleaning/cleaning-task-repository.ts | 28 +++++++++- backend/src/routes/cleaning.ts | 12 +++- backend/tests/cleaning-route.test.mjs | 8 ++- docs/devlogs/2026-06-25-M08-B-保洁端.md | 15 ++++- docs/feature-status.md | 6 +- docs/module-status.md | 6 +- scripts/check-admin-m08-b.mjs | 14 ++++- 13 files changed, 212 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 9d97b16..9a497f6 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保洁员选择器增量`,本轮补齐任务指派、协作者管理和结算生成中的可搜索保洁员选择器。 +- 最近工程提交:`M08-B任务结算筛选增量`,本轮补齐任务/结算列表的门店和保洁员筛选。 - 下一工程目标:继续 M08-B,补更完整保洁运营管理界面。 ## 版本递进 diff --git a/admin/src/App.vue b/admin/src/App.vue index 23c393e..9a9044d 100644 --- a/admin/src/App.vue +++ b/admin/src/App.vue @@ -99,8 +99,10 @@ :page="tasks.page" :page-size="tasks.pageSize" :status="taskStatus" + :filters="taskFilters" @refresh="loadTasks" @status-change="changeTaskStatus" + @filter="changeTaskFilters" @page-change="changeTaskPage" @assign="handleAssign" @complete="handleComplete" @@ -118,8 +120,10 @@ :page="settlements.page" :page-size="settlements.pageSize" :status="settlementStatus" + :filters="settlementFilters" @refresh="loadSettlements" @status-change="changeSettlementStatus" + @filter="changeSettlementFilters" @page-change="changeSettlementPage" @generate="handleGenerateSettlement" @confirm="handleConfirmSettlement" @@ -226,6 +230,8 @@ const settlements = reactive>({ pageSize: 20 }); const cleaners = reactive>({ items: [], total: 0, page: 1, pageSize: 20 }); +const taskFilters = reactive({ storeId: '', cleanerUserId: '' }); +const settlementFilters = reactive({ storeId: '', cleanerUserId: '' }); const statisticsFilters = reactive({ from: '', to: '', storeId: '' }); const statistics = reactive({ summary: { @@ -271,7 +277,9 @@ async function loadTasks() { const result = await listCleaningTasks(session.value, { page: tasks.page, pageSize: tasks.pageSize, - status: taskStatus.value || undefined + status: taskStatus.value || undefined, + storeId: taskFilters.storeId || undefined, + cleanerUserId: taskFilters.cleanerUserId || undefined }); Object.assign(tasks, result); }, '任务已刷新'); @@ -284,7 +292,9 @@ async function loadSettlements() { const result = await listCleaningSettlements(session.value, { page: settlements.page, pageSize: settlements.pageSize, - status: settlementStatus.value || undefined + status: settlementStatus.value || undefined, + storeId: settlementFilters.storeId || undefined, + cleanerUserId: settlementFilters.cleanerUserId || undefined }); Object.assign(settlements, result); }, '结算已刷新'); @@ -335,12 +345,24 @@ function changeTaskStatus(status: TaskStatus | '') { void loadTasks(); } +function changeTaskFilters(input: { storeId: string; cleanerUserId: string }) { + Object.assign(taskFilters, input); + tasks.page = 1; + void loadTasks(); +} + function changeSettlementStatus(status: SettlementStatus | '') { settlementStatus.value = status; settlements.page = 1; void loadSettlements(); } +function changeSettlementFilters(input: { storeId: string; cleanerUserId: string }) { + Object.assign(settlementFilters, input); + settlements.page = 1; + void loadSettlements(); +} + function changeTaskPage(page: number) { tasks.page = page; void loadTasks(); diff --git a/admin/src/api.ts b/admin/src/api.ts index bc61b68..b8f76f8 100644 --- a/admin/src/api.ts +++ b/admin/src/api.ts @@ -57,13 +57,15 @@ async function request( export function listCleaningTasks( session: ApiSession, - input: { page: number; pageSize: number; status?: TaskStatus } + input: { page: number; pageSize: number; status?: TaskStatus; storeId?: string; cleanerUserId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); + if (input.storeId) params.set('storeId', input.storeId); + if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/tasks?${params}`); } @@ -210,13 +212,15 @@ export function reclaimCleaningTimeouts( export function listCleaningSettlements( session: ApiSession, - input: { page: number; pageSize: number; status?: SettlementStatus } + input: { page: number; pageSize: number; status?: SettlementStatus; storeId?: string; cleanerUserId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); + if (input.storeId) params.set('storeId', input.storeId); + if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/settlements?${params}`); } diff --git a/admin/src/components/CleaningSettlementsPanel.vue b/admin/src/components/CleaningSettlementsPanel.vue index a3dafcb..38b817c 100644 --- a/admin/src/components/CleaningSettlementsPanel.vue +++ b/admin/src/components/CleaningSettlementsPanel.vue @@ -1,11 +1,38 @@