feat(M08-B): 补保洁统计趋势

This commit is contained in:
Codex
2026-06-30 10:52:38 +08:00
parent 326c534ba7
commit d35c39e33f
13 changed files with 200 additions and 21 deletions
+3 -1
View File
@@ -240,7 +240,8 @@ const statistics = reactive<CleaningStatistics>({
byStatus: [],
byStore: [],
settlements: [],
members: []
members: [],
trend: []
});
function saveToken() {
@@ -301,6 +302,7 @@ async function loadStatistics() {
statistics.byStore = result.byStore;
statistics.settlements = result.settlements;
statistics.members = result.members;
statistics.trend = result.trend;
}, '统计已刷新');
loading.statistics = false;
}
@@ -71,6 +71,30 @@
</section>
</div>
<section class="stat-block">
<header>
<h3>每日趋势</h3>
<span>任务 / 待验 / 发放</span>
</header>
<div class="trend-list">
<div v-for="row in statistics.trend" :key="row.date" class="trend-row">
<div class="trend-date">{{ row.date }}</div>
<div class="trend-track" aria-label="每日任务趋势">
<span class="trend-bar" :style="{ width: trendWidth(row.taskTotal) }" />
</div>
<div class="trend-meta">
<strong>{{ row.taskTotal }}</strong>
<span>待验 {{ row.pendingReview }}</span>
<span>完成 {{ row.completed }}</span>
<span>驳回 {{ row.rejected }}</span>
<span>奖励 {{ money(row.rewardCents) }}</span>
<span>发放 {{ money(row.paidSettlementCents) }}</span>
</div>
</div>
<el-empty v-if="statistics.trend.length === 0" description="暂无趋势数据" :image-size="72" />
</div>
</section>
<section class="stat-block">
<header>
<h3>门店明细</h3>
@@ -116,7 +140,7 @@
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { computed, reactive, watch } from 'vue';
import { RefreshCw, Search } from '@lucide/vue';
import { compactText, money } from '../format';
import type { CleaningStatistics } from '../types';
@@ -138,6 +162,7 @@ const quickOptions = [
{ label: '近7天', value: '7d' },
{ label: '本月', value: 'month' }
];
const maxTrendTotal = computed(() => Math.max(1, ...props.statistics.trend.map((row) => row.taskTotal)));
watch(
() => props.filters,
@@ -174,4 +199,8 @@ function formatDate(date: Date) {
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function trendWidth(total: number) {
return `${Math.max(6, Math.round((total / maxTrendTotal.value) * 100))}%`;
}
</script>
+62
View File
@@ -314,6 +314,54 @@ textarea {
background: #f5f8fb;
}
.trend-list {
display: grid;
gap: 10px;
}
.trend-row {
display: grid;
grid-template-columns: 92px minmax(80px, 1fr) minmax(360px, 2.4fr);
gap: 12px;
align-items: center;
min-height: 38px;
}
.trend-date {
color: #334155;
font-size: 13px;
font-weight: 700;
}
.trend-track {
height: 8px;
overflow: hidden;
background: #e9eff5;
border-radius: 999px;
}
.trend-bar {
display: block;
height: 100%;
background: #2563eb;
border-radius: inherit;
}
.trend-meta {
display: flex;
gap: 10px;
align-items: center;
min-width: 0;
color: #60708a;
font-size: 12px;
white-space: nowrap;
}
.trend-meta strong {
color: #101828;
font-size: 14px;
}
.detail-grid strong {
overflow: hidden;
color: #18212f;
@@ -421,6 +469,15 @@ textarea {
.metric-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.trend-row {
grid-template-columns: 92px minmax(80px, 1fr);
}
.trend-meta {
grid-column: 1 / -1;
flex-wrap: wrap;
}
}
@media (max-width: 560px) {
@@ -447,8 +504,13 @@ textarea {
.member-form,
.detail-grid,
.trend-row,
.stats-filter,
.stats-grid {
grid-template-columns: 1fr;
}
.trend-meta {
grid-column: auto;
}
}
+9
View File
@@ -156,6 +156,15 @@ export interface CleaningStatistics {
pendingSettlementCents: number;
settledRewardCents: number;
}>;
trend: Array<{
date: string;
taskTotal: number;
pendingReview: number;
completed: number;
rejected: number;
rewardCents: number;
paidSettlementCents: number;
}>;
}
export type WechatTransferPreflightStatus = 'PASS' | 'WARN' | 'FAIL';