<!-- src/views/statistics/case.vue -->
|
<template>
|
<div class="statistics-page">
|
<!-- 筛选面板 -->
|
<el-card class="filter-card">
|
<el-form :inline="true" :model="query" class="filter-form">
|
<el-form-item label="地区">
|
<el-select
|
v-model="query.region"
|
placeholder="请选择地区"
|
clearable
|
multiple
|
collapse-tags
|
style="width: 200px"
|
>
|
<el-option
|
v-for="item in regionOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
</el-form-item>
|
|
<el-form-item label="月份范围">
|
<el-date-picker
|
v-model="query.monthRange"
|
type="monthrange"
|
range-separator="至"
|
start-placeholder="开始月份"
|
end-placeholder="结束月份"
|
value-format="yyyy-MM"
|
style="width: 300px"
|
/>
|
</el-form-item>
|
|
<el-form-item>
|
<el-button type="primary" icon="el-icon-search" @click="handleSearch"
|
>查询</el-button
|
>
|
<el-button icon="el-icon-refresh" @click="handleReset"
|
>重置</el-button
|
>
|
<el-button
|
type="success"
|
icon="el-icon-download"
|
@click="handleExport"
|
>导出</el-button
|
>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 数据概览 -->
|
<el-row :gutter="20" class="overview-row">
|
<el-col :span="8" v-for="item in overviewData" :key="item.title">
|
<el-card class="overview-card">
|
<div class="overview-content">
|
<div class="overview-icon" :style="{ backgroundColor: item.color }">
|
<i :class="item.icon"></i>
|
</div>
|
<div class="overview-info">
|
<div class="overview-title">{{ item.title }}</div>
|
<div class="overview-value">{{ item.value }}</div>
|
<div class="overview-desc">{{ item.desc }}</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 图表区域 -->
|
<el-row :gutter="20" class="chart-row">
|
<el-col :span="12">
|
<el-card>
|
<div slot="header" class="clearfix">
|
<span>案例趋势分析</span>
|
<el-button
|
style="float: right; padding: 3px 0"
|
type="text"
|
@click="toggleChartType('trend')"
|
>
|
{{ chartTypes.trend === "line" ? "柱状图" : "折线图" }}
|
</el-button>
|
</div>
|
<EChartsWrapper
|
:id="'trend-chart'"
|
:options="getTrendChartOptions()"
|
height="400px"
|
/>
|
</el-card>
|
</el-col>
|
|
<el-col :span="12">
|
<el-card>
|
<div slot="header" class="clearfix">
|
<span>地区分布对比</span>
|
</div>
|
<EChartsWrapper
|
:id="'region-chart'"
|
:options="getRegionChartOptions()"
|
height="400px"
|
/>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 数据表格 -->
|
<el-card>
|
<div slot="header" class="clearfix">
|
<span>案例统计明细</span>
|
<el-button
|
style="float: right;"
|
type="primary"
|
icon="el-icon-zoom-in"
|
@click="showDetail = true"
|
>
|
查看案例明细
|
</el-button>
|
</div>
|
|
<el-table
|
v-loading="loading"
|
:data="tableData"
|
style="width: 100%"
|
border
|
stripe
|
>
|
<el-table-column label="地区" prop="region" align="center" />
|
<el-table-column label="月份" prop="month" align="center">
|
<template slot-scope="{ row }">
|
{{ formatMonth(row.month) }}
|
</template>
|
</el-table-column>
|
<el-table-column
|
label="潜在捐献案例数"
|
prop="potentialCount"
|
align="center"
|
/>
|
<el-table-column
|
label="完成捐献数量"
|
prop="completedCount"
|
align="center"
|
/>
|
<el-table-column label="完成率" prop="completionRate" align="center">
|
<template slot-scope="{ row }">
|
<el-progress
|
:percentage="row.completionRate"
|
:color="getProgressColor(row.completionRate)"
|
:show-text="false"
|
/>
|
<span>{{ row.completionRate }}%</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="案例明细" align="center">
|
<template slot-scope="{ row }">
|
<el-button type="text" size="mini" @click="viewCaseDetail(row)">
|
查看({{ row.caseCount || 0 }})
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<el-pagination
|
v-show="total > 0"
|
:total="total"
|
:page.sync="query.page"
|
:limit.sync="query.limit"
|
@pagination="handlePagination"
|
style="margin-top: 20px;"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page-sizes="[10, 20, 50, 100]"
|
/>
|
</el-card>
|
|
<!-- 案例明细弹窗 -->
|
<el-dialog title="案例明细" :visible.sync="showDetail" append-to-body>
|
<case-detail :query="detailQuery" @close="showDetail = false" />
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
import EChartsWrapper from "@/components/charts/EChartsWrapper.vue";
|
import CaseDetail from "./components/CaseDetail.vue";
|
import { caseTrendArea, caseTrend, caseTrendMoon, totals,trendList } from "@/api/businessApi";
|
import { toTotalsList, toMonth, toNum } from "@/utils/totals";
|
import statisticsMode from "@/utils/statisticsMode";
|
import { COLORS, axisTooltip, grid, xAxis, yAxis, legend, barSeries, lineSeries } from "@/utils/chartStyle";
|
export default {
|
name: "CaseStatistics",
|
components: {
|
EChartsWrapper,
|
CaseDetail
|
},
|
data() {
|
return {
|
// 查询参数
|
query: {
|
region: [],
|
monthRange: [],
|
page: 1,
|
limit: 10
|
},
|
|
// 地区选项(value 直接传地区名称给接口 area 字段)
|
regionOptions: [
|
{ label: "青岛地区", value: "青岛地区" },
|
{ label: "日照地区", value: "日照地区" },
|
{ label: "济南地区", value: "济南地区" },
|
{ label: "烟台地区", value: "烟台地区" },
|
{ label: "威海地区", value: "威海地区" },
|
{ label: "潍坊地区", value: "潍坊地区" },
|
{ label: "临沂地区", value: "临沂地区" }
|
],
|
|
// 加载状态
|
loading: false,
|
|
// 表格数据
|
tableData: [],
|
total: 0,
|
|
// 概览数据
|
overviewData: [
|
{
|
title: "总潜在捐献案例",
|
value: 0,
|
desc: "统计周期内",
|
icon: "el-icon-s-flag",
|
color: "#5B8FF9"
|
},
|
{
|
title: "总完成捐献数量",
|
value: 0,
|
desc: "统计周期内",
|
icon: "el-icon-circle-check",
|
color: "#5AD8A6"
|
},
|
{
|
title: "平均完成率",
|
value: "0%",
|
desc: "统计周期内",
|
icon: "el-icon-s-data",
|
color: "#F6BD16"
|
}
|
],
|
|
// 图表类型
|
chartTypes: {
|
trend: "line"
|
},
|
|
// 图表数据
|
chartData: {
|
trend: [],
|
region: []
|
},
|
|
// 弹窗控制
|
showDetail: false,
|
detailQuery: {}
|
};
|
},
|
created() {
|
this.loadData();
|
// 数据源(mock/真实接口)切换时重新加载
|
this.$watch(() => statisticsMode.useMock, () => this.loadData());
|
},
|
methods: {
|
// 组装接口查询条件
|
buildQuery() {
|
const q = {};
|
if (this.query.region && this.query.region.length) {
|
q.area = this.query.region.join(",");
|
}
|
if (this.query.monthRange && this.query.monthRange.length === 2) {
|
q.startTime = this.query.monthRange[0];
|
q.endTime = this.query.monthRange[1];
|
}
|
return q;
|
},
|
|
// 加载数据
|
async loadData() {
|
this.loading = true;
|
try {
|
const q = this.buildQuery();
|
const [areaRes, trendRes, moonRes, totalsRes] = await Promise.all([
|
caseTrendArea(q),
|
caseTrend(q),
|
caseTrendMoon(q),
|
totals(q)
|
]);
|
|
// 地区分布对比
|
this.chartData.region = toTotalsList(areaRes).map(item => ({
|
region: item.area || item.region || "",
|
potential: toNum(item.potentialCount),
|
completed: toNum(item.completeCount)
|
}));
|
|
// 案例趋势
|
this.chartData.trend = toTotalsList(trendRes).map(item => ({
|
month: toMonth(item.donateTime) || toMonth(item.month),
|
potential: toNum(item.potentialCount),
|
completed: toNum(item.completeCount)
|
}));
|
|
// 明细表(地区 x 月份)
|
const all = toTotalsList(moonRes).map(item => {
|
const potential = toNum(item.potentialCount);
|
const completed = toNum(item.completeCount);
|
const rate =
|
potential > 0
|
? Number(((completed / potential) * 100).toFixed(1))
|
: 0;
|
return {
|
region: item.area || item.region || "",
|
month: toMonth(item.donateTime) || toMonth(item.month),
|
potentialCount: potential,
|
completedCount: completed,
|
completionRate: rate,
|
caseCount: toNum(item.totalCount) || potential
|
};
|
});
|
this.total = all.length;
|
const start = (this.query.page - 1) * this.query.limit;
|
this.tableData = all.slice(start, start + this.query.limit);
|
|
this.updateOverviewData(toTotalsList(totalsRes));
|
} catch (error) {
|
console.error("加载数据失败:", error);
|
this.$message.error("数据加载失败");
|
} finally {
|
this.loading = false;
|
}
|
},
|
|
// 更新概览数据(总潜在/总完成捐献数来自 /opo/totals/totals 接口)
|
updateOverviewData(totalsList) {
|
const totalPotential = totalsList.reduce(
|
(sum, item) => sum + toNum(item.totalPotential),
|
0
|
);
|
const totalCompleted = totalsList.reduce(
|
(sum, item) => sum + toNum(item.totalCompletion),
|
0
|
);
|
const avgRate =
|
totalPotential > 0
|
? ((totalCompleted / totalPotential) * 100).toFixed(1)
|
: 0;
|
|
this.overviewData[0].value = totalPotential;
|
this.overviewData[1].value = totalCompleted;
|
this.overviewData[2].value = `${avgRate}%`;
|
},
|
|
// 获取趋势图表配置
|
getTrendChartOptions() {
|
const isLine = this.chartTypes.trend === "line";
|
const xData = this.chartData.trend.map(item => item.month);
|
const potential = this.chartData.trend.map(item => item.potential);
|
const completed = this.chartData.trend.map(item => item.completed);
|
|
const series = isLine
|
? [
|
lineSeries("潜在捐献数", potential, COLORS.blue, { area: true }),
|
lineSeries("完成捐献数", completed, COLORS.green, { area: true })
|
]
|
: [
|
barSeries("潜在捐献数", potential, COLORS.blue),
|
barSeries("完成捐献数", completed, COLORS.green)
|
];
|
|
return {
|
tooltip: axisTooltip(),
|
legend: legend(["潜在捐献数", "完成捐献数"]),
|
grid: grid(),
|
xAxis: xAxis(xData),
|
yAxis: yAxis(),
|
series
|
};
|
},
|
|
// 获取地区图表配置
|
getRegionChartOptions() {
|
return {
|
tooltip: axisTooltip(),
|
legend: legend(["潜在捐献数", "完成捐献数"]),
|
grid: grid(),
|
xAxis: xAxis(this.chartData.region.map(item => item.region)),
|
yAxis: yAxis(),
|
series: [
|
barSeries("潜在捐献数", this.chartData.region.map(item => item.potential), COLORS.blue),
|
barSeries("完成捐献数", this.chartData.region.map(item => item.completed), COLORS.green)
|
]
|
};
|
},
|
|
// 获取进度条颜色
|
getProgressColor(percentage) {
|
if (percentage >= 60) return COLORS.green;
|
if (percentage >= 50) return COLORS.amber;
|
return COLORS.coral;
|
},
|
|
// 格式化月份显示
|
formatMonth(month) {
|
if (!month) return "";
|
return month.replace("-", "年") + "月";
|
},
|
|
// 处理查询
|
handleSearch() {
|
this.query.page = 1;
|
this.loadData();
|
},
|
|
// 处理重置
|
handleReset() {
|
this.query = {
|
region: [],
|
monthRange: [],
|
page: 1,
|
limit: 10
|
};
|
this.loadData();
|
},
|
|
// 处理分页
|
handlePagination({ page, limit }) {
|
this.query.page = page;
|
this.query.limit = limit;
|
this.loadData();
|
},
|
|
// 处理导出
|
handleExport() {
|
this.$message.info("导出功能开发中...");
|
},
|
|
// 切换图表类型
|
toggleChartType(type) {
|
this.chartTypes[type] = this.chartTypes[type] === "line" ? "bar" : "line";
|
},
|
|
// 查看案例详情
|
viewCaseDetail(row) {
|
this.detailQuery = {
|
region: row.region,
|
month: row.month
|
};
|
this.showDetail = true;
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.statistics-page {
|
padding: 20px;
|
}
|
|
.filter-card {
|
margin-bottom: 20px;
|
}
|
|
.filter-form {
|
margin-bottom: 0;
|
}
|
|
.overview-row {
|
margin-bottom: 20px;
|
}
|
|
.overview-card {
|
height: 100%;
|
}
|
|
.overview-content {
|
display: flex;
|
align-items: center;
|
}
|
|
.overview-icon {
|
width: 50px;
|
height: 50px;
|
border-radius: 8px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 15px;
|
}
|
|
.overview-icon i {
|
font-size: 24px;
|
color: white;
|
}
|
|
.overview-info {
|
flex: 1;
|
}
|
|
.overview-title {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 4px;
|
}
|
|
.overview-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #303133;
|
margin-bottom: 4px;
|
}
|
|
.overview-desc {
|
font-size: 12px;
|
color: #c0c4cc;
|
}
|
|
.chart-row {
|
margin-bottom: 20px;
|
}
|
</style>
|