feat(ui): restore reference mini program and admin design

This commit is contained in:
2026-08-13 10:58:04 +08:00
parent 9622d249be
commit 58787e738a
26 changed files with 1027 additions and 329 deletions
+146
View File
@@ -0,0 +1,146 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { SERVICE_CATEGORY_LABELS, type StatisticsOverview } from '@lehuo/contracts';
import { fetchStatistics } from '../api/statistics';
const emit = defineEmits<{ navigate: [view: string] }>();
const data = ref<StatisticsOverview | null>(null);
const loading = ref(true);
const message = ref('');
const maxMonthly = computed(() =>
Math.max(1, ...(data.value?.monthly.map((item) => item.applications) ?? [1])),
);
const totalCategory = computed(() =>
Math.max(1, data.value?.byCategory.reduce((sum, item) => sum + item.count, 0) ?? 1),
);
const palette = ['#378fe8', '#ff7041', '#ffb84a', '#57bf58', '#7a67d8'];
async function load() {
loading.value = true;
message.value = '';
try {
data.value = await fetchStatistics();
} catch (error) {
message.value = error instanceof Error ? error.message : '数据加载失败';
} finally {
loading.value = false;
}
}
onMounted(load);
</script>
<template>
<main class="content-area dashboard-page">
<header class="page-header">
<div>
<p class="page-kicker">管理后台</p>
<h1>工作概览</h1>
<p>业务数据由系统实时汇总,帮助团队掌握服务进展。</p>
</div>
<button class="secondary-button compact-button" @click="load">刷新数据</button>
</header>
<p v-if="message" class="global-message" role="alert">{{ message }}</p>
<section v-if="loading" class="summary-grid">
<div v-for="n in 4" :key="n" class="summary-card table-skeleton"></div>
</section>
<template v-else-if="data">
<section class="summary-grid dashboard-summary">
<article class="summary-card">
<span>服务对象总数</span
><strong>{{ data.uniqueBeneficiaryCount.toLocaleString() }}</strong
><small>统计区间内去重人数</small>
</article>
<article class="summary-card">
<span>服务工单完成</span><strong>{{ data.completedServiceCount.toLocaleString() }}</strong
><small>已完成服务事实</small>
</article>
<article class="summary-card">
<span>志愿者人数</span><strong>{{ data.volunteerCount.toLocaleString() }}</strong
><small>已登记志愿者</small>
</article>
<article class="summary-card">
<span>服务人次</span><strong>{{ data.servicePersonTimes.toLocaleString() }}</strong
><small>累计完成记录</small>
</article>
</section>
<section class="dashboard-charts">
<article class="panel chart-panel">
<div class="panel-heading">
<div>
<h2>服务工作趋势</h2>
<p>申请量与完成服务量</p>
</div>
<span>{{ data.period.startDate }} — {{ data.period.endDate }}</span>
</div>
<div class="bar-chart" role="img" aria-label="按月服务工作趋势">
<div v-for="item in data.monthly" :key="item.month" class="bar-column">
<div class="bar-values">
<i
class="bar bar--application"
:style="{ height: `${Math.max(5, (item.applications / maxMonthly) * 100)}%` }"
:title="`申请 ${item.applications}`"
></i
><i
class="bar bar--completed"
:style="{
height: `${Math.max(5, (item.completedServices / maxMonthly) * 100)}%`,
}"
:title="`完成 ${item.completedServices}`"
></i>
</div>
<small>{{ item.month.slice(5) }}月</small>
</div>
</div>
<div class="chart-legend">
<span><i class="legend-dot legend-dot--blue"></i>申请</span
><span><i class="legend-dot legend-dot--orange"></i>完成服务</span>
</div>
</article>
<article class="panel chart-panel">
<div class="panel-heading">
<div>
<h2>服务类型分布</h2>
<p>按申请服务类别统计</p>
</div>
</div>
<div class="distribution-list">
<div v-for="(item, index) in data.byCategory" :key="item.category">
<div>
<span
><i
class="legend-dot"
:style="{ background: palette[index % palette.length] }"
></i
>{{ SERVICE_CATEGORY_LABELS[item.category] }}</span
><strong>{{ item.count }}</strong>
</div>
<progress :value="item.count" :max="totalCategory"></progress>
</div>
<p v-if="!data.byCategory.length" class="muted-text">当前区间暂无分类数据</p>
</div>
</article>
</section>
<section class="panel quick-panel">
<div class="panel-heading">
<div>
<h2>快捷入口</h2>
<p>进入高频业务工作台</p>
</div>
</div>
<div class="quick-actions">
<button @click="emit('navigate', 'applications')">
<b>申</b><span>新增服务跟进</span></button
><button @click="emit('navigate', 'cases')">
<b class="blue">单</b><span>处理服务工单</span></button
><button @click="emit('navigate', 'volunteers')">
<b class="green">愿</b><span>志愿者审核</span></button
><button @click="emit('navigate', 'content')">
<b class="gold">讯</b><span>发布公益内容</span>
</button>
</div>
</section>
</template>
</main>
</template>