| | |
| | | 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" > |
| | | <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"> |
| | | <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" |
| | |
| | | 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', |
| | |
| | | type: 'select', |
| | | multiple: true, |
| | | options: [ |
| | | { label: '青岛地区', value: 'qingdao' }, |
| | | { label: '日照地区', value: 'rizhao' }, |
| | | { label: '济南地区', value: 'jinan' }, |
| | | { label: '烟台地区', value: 'yantai' }, |
| | | { label: '威海地区', value: 'weihai' }, |
| | | { label: '潍坊地区', value: 'weifang' }, |
| | | { label: '临沂地区', value: 'linyi' } |
| | | { label: '青岛地区', value: '青岛地区' }, |
| | | { label: '日照地区', value: '日照地区' }, |
| | | { label: '济南地区', value: '济南地区' }, |
| | | { label: '烟台地区', value: '烟台地区' }, |
| | | { label: '威海地区', value: '威海地区' }, |
| | | { label: '潍坊地区', value: '潍坊地区' }, |
| | | { label: '临沂地区', value: '临沂地区' } |
| | | ], |
| | | width: '300px' |
| | | }, |
| | |
| | | prop: 'organType', |
| | | type: 'select', |
| | | options: [ |
| | | { label: '全肝', value: 'liver' }, |
| | | { label: '肾脏', value: 'kidney' }, |
| | | { label: '心脏', value: 'heart' }, |
| | | { label: '肺', value: 'lung' }, |
| | | { label: '胰腺', value: 'pancreas' }, |
| | | { label: '眼角膜', value: 'cornea' } |
| | | { label: '全肝', value: '全肝' }, |
| | | { label: '肾脏', value: '肾脏' }, |
| | | { label: '心脏', value: '心脏' }, |
| | | { label: '肺', value: '肺' }, |
| | | { label: '胰腺', value: '胰腺' }, |
| | | { label: '眼角膜', value: '眼角膜' } |
| | | ], |
| | | width: '200px' |
| | | } |
| | |
| | | |
| | | // 概览数据 |
| | | overviewData: [ |
| | | { title: '总获取数量', value: 0, desc: '统计周期内', icon: 'el-icon-s-claim', color: '#409EFF' }, |
| | | { title: '总移植数量', value: 0, desc: '统计周期内', icon: 'el-icon-success', color: '#67C23A' }, |
| | | { title: '总移植率', value: '0%', desc: '统计周期内', icon: 'el-icon-s-data', color: '#E6A23C' }, |
| | | { title: '总弃用数量', value: 0, desc: '统计周期内', icon: 'el-icon-warning', color: '#F56C6C' } |
| | | { 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: {}, |
| | | |
| | | // 模拟数据 |
| | | mockData: { |
| | | table: [ |
| | | { id: 1, year: '2024', organName: '全肝', acquisitionCount: 15, transplantCount: 12, transplantRate: 80.0, abandonCount: 3, region: '青岛地区' }, |
| | | { id: 2, year: '2024', organName: '肾脏', acquisitionCount: 20, transplantCount: 18, transplantRate: 90.0, abandonCount: 2, region: '青岛地区' }, |
| | | { id: 3, year: '2024', organName: '全肝', acquisitionCount: 8, transplantCount: 6, transplantRate: 75.0, abandonCount: 2, region: '日照地区' }, |
| | | { id: 4, year: '2024', organName: '心脏', acquisitionCount: 5, transplantCount: 4, transplantRate: 80.0, abandonCount: 1, region: '青岛地区' }, |
| | | { id: 5, year: '2023', organName: '全肝', acquisitionCount: 12, transplantCount: 10, transplantRate: 83.3, abandonCount: 2, region: '青岛地区' }, |
| | | { id: 6, year: '2023', organName: '眼角膜', acquisitionCount: 25, transplantCount: 22, transplantRate: 88.0, abandonCount: 3, region: '济南地区' } |
| | | ], |
| | | trend: [ |
| | | { year: '2020', acquisition: 120, transplant: 100 }, |
| | | { year: '2021', acquisition: 150, transplant: 130 }, |
| | | { year: '2022', acquisition: 180, transplant: 160 }, |
| | | { year: '2023', acquisition: 220, transplant: 200 }, |
| | | { year: '2024', acquisition: 250, transplant: 230 } |
| | | ], |
| | | distribution: [ |
| | | { name: '全肝', value: 85 }, |
| | | { name: '肾脏', value: 120 }, |
| | | { name: '心脏', value: 45 }, |
| | | { name: '肺', value: 60 }, |
| | | { name: '胰腺', value: 30 }, |
| | | { name: '眼角膜', value: 200 } |
| | | ] |
| | | } |
| | | 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 { |
| | | await new Promise(resolve => setTimeout(resolve, 500)); |
| | | const q = this.buildQuery(); |
| | | const [moonRes, typeRes, organRes] = await Promise.all([ |
| | | organMoon(q), |
| | | organType(q), |
| | | organ(q) |
| | | ]); |
| | | |
| | | // 处理表格数据 |
| | | let filteredData = [...this.mockData.table]; |
| | | // 获取/移植趋势 |
| | | this.chartData.trend = toTotalsList(moonRes).map(item => ({ |
| | | time: toMonth(item.donateTime) || toMonth(item.month), |
| | | acquisition: toNum(item.getCount), |
| | | transplant: toNum(item.useCount) |
| | | })); |
| | | |
| | | if (this.query.region.length > 0) { |
| | | filteredData = filteredData.filter(item => |
| | | this.query.region.includes(item.region.replace('地区', '').toLowerCase()) |
| | | ); |
| | | } |
| | | // 器官类型分布 |
| | | this.chartData.distribution = toTotalsList(typeRes).map(item => ({ |
| | | name: item.organName || '', |
| | | value: toNum(item.getCount) |
| | | })); |
| | | |
| | | if (this.query.yearRange && this.query.yearRange.length === 2) { |
| | | const [start, end] = this.query.yearRange; |
| | | filteredData = filteredData.filter(item => |
| | | parseInt(item.year) >= parseInt(start) && parseInt(item.year) <= parseInt(end) |
| | | ); |
| | | } |
| | | |
| | | if (this.query.organType) { |
| | | const organMap = { liver: '肝', kidney: '肾', heart: '心脏', lung: '肺', pancreas: '胰腺', cornea: '眼角膜' }; |
| | | const organName = organMap[this.query.organType]; |
| | | if (organName) { |
| | | filteredData = filteredData.filter(item => item.organName.includes(organName)); |
| | | } |
| | | } |
| | | |
| | | // 分页 |
| | | // 明细表 |
| | | 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 = filteredData.slice(start, start + this.query.limit); |
| | | this.total = filteredData.length; |
| | | this.tableData = all.slice(start, start + this.query.limit); |
| | | |
| | | // 更新概览数据 |
| | | this.updateOverviewData(filteredData); |
| | | // 更新概览 |
| | | this.updateOverviewData(all); |
| | | |
| | | // 更新图表 |
| | | this.updateCharts(); |
| | | |
| | | } catch (error) { |
| | | console.error('加载数据失败:', error); |
| | | this.$message.error('数据加载失败'); |
| | |
| | | |
| | | // 更新概览数据 |
| | | updateOverviewData(data) { |
| | | if (data.length === 0) return; |
| | | |
| | | const totalAcquisition = data.reduce((sum, item) => sum + item.acquisitionCount, 0); |
| | | const totalTransplant = data.reduce((sum, item) => sum + item.transplantCount, 0); |
| | | const totalAbandon = data.reduce((sum, item) => sum + item.abandonCount, 0); |
| | | const transplantRate = totalAcquisition > 0 ? (totalTransplant / totalAcquisition * 100).toFixed(1) : 0; |
| | | 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; |
| | |
| | | updateCharts() { |
| | | // 趋势图 |
| | | this.trendChartOptions = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['获取数量', '移植数量'] |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: this.mockData.trend.map(item => item.year + '年') |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: '数量(个)' |
| | | }, |
| | | tooltip: axisTooltip(), |
| | | legend: legend(['获取数量', '移植数量']), |
| | | grid: grid(), |
| | | xAxis: xAxis(this.chartData.trend.map(item => item.time)), |
| | | yAxis: yAxis(), |
| | | series: [ |
| | | { |
| | | name: '获取数量', |
| | | type: 'bar', |
| | | data: this.mockData.trend.map(item => item.acquisition), |
| | | itemStyle: { color: '#409EFF' } |
| | | }, |
| | | { |
| | | name: '移植数量', |
| | | type: 'bar', |
| | | data: this.mockData.trend.map(item => item.transplant), |
| | | itemStyle: { color: '#67C23A' } |
| | | } |
| | | 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', |
| | | data: this.mockData.distribution.map(item => item.name) |
| | | 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: ['50%', '60%'], |
| | | data: this.mockData.distribution, |
| | | 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, |