Files
lehuo/apps/admin-web/src/views/DashboardView.vue
T

147 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>