feat(ui): restore reference mini program and admin design
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user