WXL
2026-09-17 3df460667d74868d0a54fce12d5e881bbd6c0c1a
src/views/statistics/organ.vue
@@ -81,20 +81,20 @@
        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"
@@ -139,6 +139,10 @@
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',
@@ -166,13 +170,13 @@
          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'
        },
@@ -191,12 +195,12 @@
          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'
        }
@@ -211,93 +215,97 @@
      // 概览数据
      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('数据加载失败');
@@ -308,12 +316,10 @@
    // 更新概览数据
    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;
@@ -325,47 +331,20 @@
    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}%)'
@@ -373,15 +352,23 @@
        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,