feat(M08-B): 补待结算候选预览

This commit is contained in:
Codex
2026-06-30 18:15:36 +08:00
parent 78fb6ed2d0
commit 67e7796652
11 changed files with 176 additions and 15 deletions
+13
View File
@@ -225,6 +225,19 @@ export function listCleaningSettlements(
return request<PageResult<CleaningSettlement>>(session, `/cleaning/settlements?${params}`);
}
export function listCleaningSettlementCandidates(
session: ApiSession,
input: { page: number; pageSize: number; storeId?: string; cleanerUserId?: string }
) {
const params = new URLSearchParams({
page: String(input.page),
pageSize: String(input.pageSize)
});
if (input.storeId) params.set('storeId', input.storeId);
if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId);
return request<PageResult<CleaningTask>>(session, `/cleaning/settlement-candidates?${params}`);
}
export function generateCleaningSettlement(
session: ApiSession,
input: { cleanerUserId: string; storeId?: string; note?: string }
@@ -34,6 +34,9 @@
<el-button :icon="Search" @click="submitFilters">筛选</el-button>
</div>
<div class="toolbar-actions">
<el-button :icon="ListChecks" @click="openCandidates">
候选
</el-button>
<el-button :icon="FilePlus2" type="primary" @click="generateDialog.open = true">
生成
</el-button>
@@ -174,6 +177,54 @@
</template>
</el-dialog>
<el-dialog v-model="candidateDialog.open" title="待结算候选任务" width="760px">
<div class="dialog-stack">
<div class="detail-grid">
<span>候选任务<strong>{{ candidateDialog.total }}</strong></span>
<span>当前页金额<strong>{{ money(candidatePageRewardCents) }}</strong></span>
<span>门店筛选<strong>{{ compactText(filterDraft.storeId || '全部') }}</strong></span>
<span>保洁员筛选<strong>{{ compactText(filterDraft.cleanerUserId || '全部') }}</strong></span>
</div>
<el-table :data="candidateDialog.items" size="small" v-loading="candidateDialog.loading">
<el-table-column prop="taskNo" label="任务" min-width="170">
<template #default="{ row }">
<div class="stack">
<strong>{{ row.taskNo }}</strong>
<span>{{ compactText(row.orderNo) }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="房间" min-width="150">
<template #default="{ row }">
{{ row.storeName }} · {{ row.roomName || row.roomNo }}
</template>
</el-table-column>
<el-table-column prop="cleanerUserId" label="保洁员" width="110">
<template #default="{ row }">{{ compactText(row.cleanerUserId) }}</template>
</el-table-column>
<el-table-column prop="rewardCents" label="金额" width="110">
<template #default="{ row }">{{ money(row.rewardCents) }}</template>
</el-table-column>
<el-table-column prop="completedAt" label="完成" width="130">
<template #default="{ row }">{{ shortDate(row.completedAt) }}</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination
layout="prev, pager, next, total"
:current-page="candidateDialog.page"
:page-size="candidateDialog.pageSize"
:total="candidateDialog.total"
@current-change="changeCandidatePage"
/>
</div>
</div>
<template #footer>
<el-button @click="candidateDialog.open = false">关闭</el-button>
<el-button type="primary" @click="useCandidateFilters">用当前筛选生成</el-button>
</template>
</el-dialog>
<el-dialog v-model="transferDialog.open" title="微信转账" width="420px">
<el-form label-position="top">
<el-form-item label="模式">
@@ -290,7 +341,7 @@
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { computed, reactive, watch } from 'vue';
import {
BadgeCheck,
CircleAlert,
@@ -302,11 +353,16 @@ import {
Send
} from '@lucide/vue';
import { ElMessage } from 'element-plus';
import { getCleaningSettlementDetail, preflightWechatTransfer } from '../api';
import {
getCleaningSettlementDetail,
listCleaningSettlementCandidates,
preflightWechatTransfer
} from '../api';
import { compactText, money, shortDate } from '../format';
import type {
CleaningSettlement,
CleaningSettlementDetail,
CleaningTask,
ManagedUser,
PageResult,
SettlementStatus,
@@ -382,6 +438,23 @@ const detailDialog = reactive<{
loading: false,
detail: null
});
const candidateDialog = reactive<{
open: boolean;
loading: boolean;
items: CleaningTask[];
total: number;
page: number;
pageSize: number;
}>({
open: false,
loading: false,
items: [],
total: 0,
page: 1,
pageSize: 10
});
const candidatePageRewardCents = computed(() => candidateDialog.items
.reduce((sum, item) => sum + item.rewardCents, 0));
function settlementTag(status: SettlementStatus) {
if (status === 'PAID') return 'success';
@@ -477,4 +550,40 @@ async function openDetail(row: CleaningSettlement) {
detailDialog.loading = false;
}
}
async function openCandidates() {
candidateDialog.open = true;
candidateDialog.page = 1;
await loadCandidates();
}
async function changeCandidatePage(page: number) {
candidateDialog.page = page;
await loadCandidates();
}
async function loadCandidates() {
candidateDialog.loading = true;
try {
const result = await listCleaningSettlementCandidates(props.session, {
page: candidateDialog.page,
pageSize: candidateDialog.pageSize,
storeId: filterDraft.storeId || undefined,
cleanerUserId: filterDraft.cleanerUserId || undefined
});
candidateDialog.items = result.items;
candidateDialog.total = result.total;
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '待结算候选加载失败');
} finally {
candidateDialog.loading = false;
}
}
function useCandidateFilters() {
generateDialog.open = true;
generateDialog.storeId = filterDraft.storeId;
generateDialog.cleanerUserId = filterDraft.cleanerUserId;
candidateDialog.open = false;
}
</script>