feat(M08-B): 补待结算候选预览
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user