feat(M08-B): 补保洁统计明细
This commit is contained in:
+68
-23
@@ -53,19 +53,19 @@
|
||||
<section class="metric-grid" aria-label="保洁概览">
|
||||
<div class="metric">
|
||||
<span>待验收</span>
|
||||
<strong>{{ metrics.submitted }}</strong>
|
||||
<strong>{{ statistics.summary.pendingReview }}</strong>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span>已完成</span>
|
||||
<strong>{{ metrics.completed }}</strong>
|
||||
<strong>{{ statistics.summary.completed }}</strong>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span>待发放</span>
|
||||
<strong>{{ metrics.confirmed }}</strong>
|
||||
<strong>{{ money(statistics.summary.confirmedSettlementCents) }}</strong>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span>本页金额</span>
|
||||
<strong>{{ money(metrics.amountCents) }}</strong>
|
||||
<span>待结算</span>
|
||||
<strong>{{ money(statistics.summary.pendingSettlementCents) }}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -126,6 +126,15 @@
|
||||
@failure="handleFailure"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="统计" name="statistics">
|
||||
<CleaningStatisticsPanel
|
||||
:loading="loading.statistics"
|
||||
:statistics="statistics"
|
||||
:filters="statisticsFilters"
|
||||
@refresh="loadStatistics"
|
||||
@filter="changeStatisticsFilters"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</section>
|
||||
</main>
|
||||
@@ -144,6 +153,7 @@ import {
|
||||
} from '@lucide/vue';
|
||||
import CleaningTasksPanel from './components/CleaningTasksPanel.vue';
|
||||
import CleaningSettlementsPanel from './components/CleaningSettlementsPanel.vue';
|
||||
import CleaningStatisticsPanel from './components/CleaningStatisticsPanel.vue';
|
||||
import {
|
||||
ApiError,
|
||||
assignCleaningTask,
|
||||
@@ -151,6 +161,7 @@ import {
|
||||
confirmCleaningSettlement,
|
||||
executeWechatTransfer,
|
||||
generateCleaningSettlement,
|
||||
getCleaningStatistics,
|
||||
listCleaningSettlements,
|
||||
listCleaningTasks,
|
||||
reclaimCleaningTimeouts,
|
||||
@@ -160,6 +171,7 @@ import {
|
||||
} from './api';
|
||||
import type {
|
||||
CleaningSettlement,
|
||||
CleaningStatistics,
|
||||
CleaningTask,
|
||||
PageResult,
|
||||
SettlementStatus,
|
||||
@@ -175,7 +187,7 @@ const lastMessage = ref('');
|
||||
const taskStatus = ref<TaskStatus | ''>('SUBMITTED');
|
||||
const settlementStatus = ref<SettlementStatus | ''>('CONFIRMED');
|
||||
const session = computed(() => ({ token: tokenDraft.value }));
|
||||
const loading = reactive({ tasks: false, settlements: false });
|
||||
const loading = reactive({ tasks: false, settlements: false, statistics: false });
|
||||
const tasks = reactive<PageResult<CleaningTask>>({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
const settlements = reactive<PageResult<CleaningSettlement>>({
|
||||
items: [],
|
||||
@@ -183,13 +195,24 @@ const settlements = reactive<PageResult<CleaningSettlement>>({
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
});
|
||||
|
||||
const metrics = computed(() => ({
|
||||
submitted: tasks.items.filter((item) => item.status === 'SUBMITTED').length,
|
||||
completed: tasks.items.filter((item) => item.status === 'COMPLETED').length,
|
||||
confirmed: settlements.items.filter((item) => item.status === 'CONFIRMED').length,
|
||||
amountCents: settlements.items.reduce((sum, item) => sum + Number(item.totalRewardCents || 0), 0)
|
||||
}));
|
||||
const statisticsFilters = reactive({ from: '', to: '', storeId: '' });
|
||||
const statistics = reactive<CleaningStatistics>({
|
||||
summary: {
|
||||
taskTotal: 0,
|
||||
pendingReview: 0,
|
||||
active: 0,
|
||||
rejected: 0,
|
||||
completed: 0,
|
||||
rewardCents: 0,
|
||||
pendingSettlementCents: 0,
|
||||
confirmedSettlementCents: 0,
|
||||
paidSettlementCents: 0
|
||||
},
|
||||
byStatus: [],
|
||||
byStore: [],
|
||||
settlements: [],
|
||||
members: []
|
||||
});
|
||||
|
||||
function saveToken() {
|
||||
localStorage.setItem('qipai.admin.token', tokenDraft.value.trim());
|
||||
@@ -236,6 +259,23 @@ async function loadSettlements() {
|
||||
loading.settlements = false;
|
||||
}
|
||||
|
||||
async function loadStatistics() {
|
||||
loading.statistics = true;
|
||||
await runAction(async () => {
|
||||
const result = await getCleaningStatistics(session.value, {
|
||||
from: statisticsFilters.from || undefined,
|
||||
to: statisticsFilters.to || undefined,
|
||||
storeId: statisticsFilters.storeId || undefined
|
||||
});
|
||||
Object.assign(statistics.summary, result.summary);
|
||||
statistics.byStatus = result.byStatus;
|
||||
statistics.byStore = result.byStore;
|
||||
statistics.settlements = result.settlements;
|
||||
statistics.members = result.members;
|
||||
}, '统计已刷新');
|
||||
loading.statistics = false;
|
||||
}
|
||||
|
||||
function changeTaskStatus(status: TaskStatus | '') {
|
||||
taskStatus.value = status;
|
||||
tasks.page = 1;
|
||||
@@ -258,31 +298,36 @@ function changeSettlementPage(page: number) {
|
||||
void loadSettlements();
|
||||
}
|
||||
|
||||
function changeStatisticsFilters(input: { from: string; to: string; storeId: string }) {
|
||||
Object.assign(statisticsFilters, input);
|
||||
void loadStatistics();
|
||||
}
|
||||
|
||||
async function handleAssign(input: { taskId: string; cleanerUserId: string; note?: string }) {
|
||||
await runAction(async () => {
|
||||
await assignCleaningTask(session.value, input.taskId, input);
|
||||
await loadTasks();
|
||||
await Promise.all([loadTasks(), loadStatistics()]);
|
||||
}, '已指派');
|
||||
}
|
||||
|
||||
async function handleComplete(input: { taskId: string; note?: string }) {
|
||||
await runAction(async () => {
|
||||
await completeCleaningTask(session.value, input.taskId, input.note);
|
||||
await loadTasks();
|
||||
await Promise.all([loadTasks(), loadStatistics()]);
|
||||
}, '已验收');
|
||||
}
|
||||
|
||||
async function handleReject(input: { taskId: string; reason: string }) {
|
||||
await runAction(async () => {
|
||||
await rejectCleaningTask(session.value, input.taskId, input.reason);
|
||||
await loadTasks();
|
||||
await Promise.all([loadTasks(), loadStatistics()]);
|
||||
}, '已驳回');
|
||||
}
|
||||
|
||||
async function handleReclaim(input: { olderThanMinutes: number; limit: number }) {
|
||||
await runAction(async () => {
|
||||
await reclaimCleaningTimeouts(session.value, input);
|
||||
await loadTasks();
|
||||
await Promise.all([loadTasks(), loadStatistics()]);
|
||||
}, '已回收超时任务');
|
||||
}
|
||||
|
||||
@@ -291,14 +336,14 @@ async function handleGenerateSettlement(
|
||||
) {
|
||||
await runAction(async () => {
|
||||
await generateCleaningSettlement(session.value, input);
|
||||
await Promise.all([loadTasks(), loadSettlements()]);
|
||||
await Promise.all([loadTasks(), loadSettlements(), loadStatistics()]);
|
||||
}, '已生成结算单');
|
||||
}
|
||||
|
||||
async function handleConfirmSettlement(input: { settlementId: string; note?: string }) {
|
||||
await runAction(async () => {
|
||||
await confirmCleaningSettlement(session.value, input.settlementId, input.note);
|
||||
await loadSettlements();
|
||||
await Promise.all([loadSettlements(), loadStatistics()]);
|
||||
}, '已确认结算单');
|
||||
}
|
||||
|
||||
@@ -308,14 +353,14 @@ async function handleTransfer(input: { settlementId: string; mode: TransferMode;
|
||||
mode: input.mode,
|
||||
note: input.note
|
||||
});
|
||||
await loadSettlements();
|
||||
await Promise.all([loadSettlements(), loadStatistics()]);
|
||||
}, '已提交微信转账');
|
||||
}
|
||||
|
||||
async function handleSyncTransfer(input: { settlementId: string; note?: string }) {
|
||||
await runAction(async () => {
|
||||
await syncWechatTransfer(session.value, input.settlementId, input.note);
|
||||
await loadSettlements();
|
||||
await Promise.all([loadSettlements(), loadStatistics()]);
|
||||
}, '已同步微信状态');
|
||||
}
|
||||
|
||||
@@ -329,13 +374,13 @@ async function handleFailure(
|
||||
payoutReference: input.payoutReference,
|
||||
note: input.note
|
||||
});
|
||||
await loadSettlements();
|
||||
await Promise.all([loadSettlements(), loadStatistics()]);
|
||||
}, '已记录失败');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (session.value.token) {
|
||||
void Promise.all([loadTasks(), loadSettlements()]);
|
||||
void Promise.all([loadTasks(), loadSettlements(), loadStatistics()]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user