<!-- src/views/statistics/organ.vue -->
|
<template>
|
<div class="statistics-page">
|
<!-- 筛选面板 -->
|
<FilterPanel
|
:fields="filterFields"
|
:default-value="query"
|
@search="handleSearch"
|
@reset="handleReset"
|
>
|
<template #extra-actions>
|
<el-button type="success" icon="el-icon-download" @click="handleExport">导出</el-button>
|
</template>
|
</FilterPanel>
|
|
<!-- 数据概览 -->
|
<el-row :gutter="20" class="overview-row">
|
<el-col :span="6" 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>
|
</div>
|
<EChartsWrapper
|
:id="'organ-trend-chart'"
|
:options="trendChartOptions"
|
height="400px"
|
/>
|
</el-card>
|
</el-col>
|
|
<el-col :span="12">
|
<el-card>
|
<div slot="header" class="clearfix">
|
<span>器官类型分布</span>
|
</div>
|
<EChartsWrapper
|
:id="'organ-distribution-chart'"
|
:options="distributionChartOptions"
|
height="400px"
|
/>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 数据表格 -->
|
<el-card>
|
<div slot="header" class="clearfix">
|
<span>捐献器官统计明细</span>
|
<el-button
|
style="float: right; margin-left: 10px;"
|
type="primary"
|
icon="el-icon-zoom-in"
|
@click="showOrganDetail = true"
|
>
|
查看器官明细
|
</el-button>
|
</div>
|
|
<el-table
|
v-loading="loading"
|
:data="tableData"
|
style="width: 100%"
|
border
|
stripe
|
>
|
<el-table-column label="年份" prop="year" align="center" />
|
<el-table-column label="器官名称" prop="organName" align="center" />
|
<el-table-column label="获取数量" prop="acquisitionCount" align="center" />
|
<el-table-column label="移植数量" prop="transplantCount" align="center" />
|
<el-table-column label="移植率" prop="transplantRate" align="center">
|
<template slot-scope="{ row }">
|
<el-tag :type="getRateTag(row.transplantRate)" size="small">
|
{{ row.transplantRate }}%
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="弃用数量" prop="abandonCount" align="center" />
|
<el-table-column label="地区" prop="region" align="center" />
|
<el-table-column label="操作" align="center" fixed="right">
|
<template slot-scope="{ row }">
|
<el-button
|
type="text"
|
size="mini"
|
@click="viewOrganDetail(row)"
|
>
|
详情
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<el-pagination
|
v-show="total > 0"
|
:total="total"
|
:page.sync="query.page"
|
:limit.sync="query.limit"
|
@current-change="handlePagination"
|
@size-change="handlePageSizeChange"
|
style="margin-top: 20px;"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page-sizes="[10, 20, 50, 100]"
|
/>
|
</el-card>
|
|
<!-- 器官明细弹窗 -->
|
<el-dialog
|
title="器官明细"
|
:visible.sync="showOrganDetail"
|
width="80%"
|
append-to-body
|
>
|
<OrganDetail
|
:query="detailQuery"
|
@close="showOrganDetail = false"
|
/>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
import FilterPanel from '@/components/charts/FilterPanel.vue';
|
import EChartsWrapper from '@/components/charts/EChartsWrapper.vue';
|
import OrganDetail from './components/OrganDetail.vue';
|
import { organ, organMoon, organType } from '@/api/businessApi';
|
import { toTotalsList, toMonth, toNum } from '@/utils/totals';
|
import statisticsMode from '@/utils/statisticsMode';
|
import { COLORS, PALETTE, axisTooltip, grid, xAxis, yAxis, legend, barSeries } from '@/utils/chartStyle';
|
|
export default {
|
name: 'OrganStatistics',
|
components: {
|
FilterPanel,
|
EChartsWrapper,
|
OrganDetail
|
},
|
data() {
|
return {
|
// 查询参数
|
query: {
|
region: [],
|
yearRange: [],
|
organType: null,
|
page: 1,
|
limit: 10
|
},
|
|
// 筛选字段配置
|
filterFields: [
|
{
|
label: '地区',
|
prop: 'region',
|
type: 'select',
|
multiple: true,
|
options: [
|
{ label: '青岛地区', value: '青岛地区' },
|
{ label: '日照地区', value: '日照地区' },
|
{ label: '济南地区', value: '济南地区' },
|
{ label: '烟台地区', value: '烟台地区' },
|
{ label: '威海地区', value: '威海地区' },
|
{ label: '潍坊地区', value: '潍坊地区' },
|
{ label: '临沂地区', value: '临沂地区' }
|
],
|
width: '300px'
|
},
|
{
|
label: '年份范围',
|
prop: 'yearRange',
|
type: 'daterange',
|
dateType: 'yearrange',
|
startPlaceholder: '开始年份',
|
endPlaceholder: '结束年份',
|
valueFormat: 'yyyy',
|
width: '300px'
|
},
|
{
|
label: '器官类型',
|
prop: 'organType',
|
type: 'select',
|
options: [
|
{ label: '全肝', value: '全肝' },
|
{ label: '肾脏', value: '肾脏' },
|
{ label: '心脏', value: '心脏' },
|
{ label: '肺', value: '肺' },
|
{ label: '胰腺', value: '胰腺' },
|
{ label: '眼角膜', value: '眼角膜' }
|
],
|
width: '200px'
|
}
|
],
|
|
// 加载状态
|
loading: false,
|
|
// 表格数据
|
tableData: [],
|
total: 0,
|
|
// 概览数据
|
overviewData: [
|
{ title: '总获取数量', value: 0, desc: '统计周期内', icon: 'el-icon-s-claim', color: '#5B8FF9' },
|
{ title: '总移植数量', value: 0, desc: '统计周期内', icon: 'el-icon-success', color: '#5AD8A6' },
|
{ title: '总移植率', value: '0%', desc: '统计周期内', icon: 'el-icon-s-data', color: '#F6BD16' },
|
{ title: '总弃用数量', value: 0, desc: '统计周期内', icon: 'el-icon-warning', color: '#E8684A' }
|
],
|
|
// 图表配置
|
trendChartOptions: {},
|
distributionChartOptions: {},
|
|
// 图表数据
|
chartData: {
|
trend: [],
|
distribution: []
|
},
|
|
// 弹窗控制
|
showOrganDetail: 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.yearRange && this.query.yearRange.length === 2) {
|
q.startTime = this.query.yearRange[0];
|
q.endTime = this.query.yearRange[1];
|
}
|
if (this.query.organType) {
|
q.organName = this.query.organType;
|
}
|
return q;
|
},
|
|
// 加载数据
|
async loadData() {
|
this.loading = true;
|
try {
|
const q = this.buildQuery();
|
const [moonRes, typeRes, organRes] = await Promise.all([
|
organMoon(q),
|
organType(q),
|
organ(q)
|
]);
|
|
// 获取/移植趋势
|
this.chartData.trend = toTotalsList(moonRes).map(item => ({
|
time: toMonth(item.donateTime) || toMonth(item.month),
|
acquisition: toNum(item.getCount),
|
transplant: toNum(item.useCount)
|
}));
|
|
// 器官类型分布
|
this.chartData.distribution = toTotalsList(typeRes).map(item => ({
|
name: item.organName || '',
|
value: toNum(item.getCount)
|
}));
|
|
// 明细表
|
const all = toTotalsList(organRes).map(item => {
|
const get = toNum(item.getCount);
|
const use = toNum(item.useCount);
|
const rate = get > 0 ? Number(((use / get) * 100).toFixed(1)) : 0;
|
return {
|
year: String(item.donateTime || item.year || '').slice(0, 4),
|
organName: item.organName || '',
|
acquisitionCount: get,
|
transplantCount: use,
|
transplantRate: rate,
|
abandonCount: get - use,
|
region: item.area || item.region || ''
|
};
|
});
|
this.total = all.length;
|
const start = (this.query.page - 1) * this.query.limit;
|
this.tableData = all.slice(start, start + this.query.limit);
|
|
// 更新概览
|
this.updateOverviewData(all);
|
|
// 更新图表
|
this.updateCharts();
|
} catch (error) {
|
console.error('加载数据失败:', error);
|
this.$message.error('数据加载失败');
|
} finally {
|
this.loading = false;
|
}
|
},
|
|
// 更新概览数据
|
updateOverviewData(data) {
|
const totalAcquisition = data.reduce((sum, item) => sum + toNum(item.acquisitionCount), 0);
|
const totalTransplant = data.reduce((sum, item) => sum + toNum(item.transplantCount), 0);
|
const totalAbandon = data.reduce((sum, item) => sum + toNum(item.abandonCount), 0);
|
const transplantRate = totalAcquisition > 0 ? ((totalTransplant / totalAcquisition) * 100).toFixed(1) : 0;
|
|
this.overviewData[0].value = totalAcquisition;
|
this.overviewData[1].value = totalTransplant;
|
this.overviewData[2].value = `${transplantRate}%`;
|
this.overviewData[3].value = totalAbandon;
|
},
|
|
// 更新图表
|
updateCharts() {
|
// 趋势图
|
this.trendChartOptions = {
|
tooltip: axisTooltip(),
|
legend: legend(['获取数量', '移植数量']),
|
grid: grid(),
|
xAxis: xAxis(this.chartData.trend.map(item => item.time)),
|
yAxis: yAxis(),
|
series: [
|
barSeries('获取数量', this.chartData.trend.map(item => item.acquisition), COLORS.blue),
|
barSeries('移植数量', this.chartData.trend.map(item => item.transplant), COLORS.green)
|
]
|
};
|
|
// 分布图
|
this.distributionChartOptions = {
|
color: PALETTE,
|
tooltip: {
|
trigger: 'item',
|
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
},
|
legend: {
|
orient: 'vertical',
|
left: 'left',
|
top: 'middle',
|
itemWidth: 10,
|
itemHeight: 10,
|
itemGap: 16,
|
textStyle: { fontSize: 14, color: '#606266' },
|
data: this.chartData.distribution.map(item => item.name)
|
},
|
series: [
|
{
|
name: '器官分布',
|
type: 'pie',
|
radius: '55%',
|
center: ['55%', '50%'],
|
itemStyle: { borderColor: '#fff', borderWidth: 2 },
|
label: { fontSize: 14, color: '#606266', formatter: '{b} {d}%' },
|
labelLine: { lineStyle: { color: '#c0c4cc' } },
|
data: this.chartData.distribution,
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
}
|
}
|
}
|
]
|
};
|
},
|
|
// 获取移植率标签类型
|
getRateTag(rate) {
|
if (rate >= 85) return 'success';
|
if (rate >= 75) return 'warning';
|
return 'danger';
|
},
|
|
// 处理查询
|
handleSearch(formData) {
|
this.query = { ...this.query, ...formData, page: 1 };
|
this.loadData();
|
},
|
|
// 处理重置
|
handleReset() {
|
this.query = {
|
region: [],
|
yearRange: [],
|
organType: null,
|
page: 1,
|
limit: 10
|
};
|
this.loadData();
|
},
|
|
// 处理分页
|
handlePagination(page) {
|
this.query.page = page;
|
this.loadData();
|
},
|
|
// 处理分页大小变化
|
handlePageSizeChange(size) {
|
this.query.limit = size;
|
this.query.page = 1;
|
this.loadData();
|
},
|
|
// 处理导出
|
handleExport() {
|
this.$message.info('导出功能开发中...');
|
},
|
|
// 查看器官详情
|
viewOrganDetail(row) {
|
this.detailQuery = {
|
year: row.year,
|
organType: row.organName,
|
region: row.region
|
};
|
this.showOrganDetail = 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>
|