| | |
| | | <span>中国分类分布</span> |
| | | </div> |
| | | <EChartsWrapper |
| | | :id="'category-chart'" |
| | | :options="categoryChartOptions" |
| | | v-if="useMock" |
| | | :id="'china-category-chart'" |
| | | :options="chinaCategoryOptions" |
| | | height="400px" |
| | | /> |
| | | <el-empty v-else description="后端暂未提供中国分类统计接口"></el-empty> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <div slot="header" class="clearfix"> |
| | | <span>死亡原因分析</span> |
| | | <span>死亡原因分布</span> |
| | | </div> |
| | | <EChartsWrapper |
| | | :id="'death-chart'" |
| | | :options="deathChartOptions" |
| | | v-if="useMock" |
| | | :id="'death-cause-chart'" |
| | | :options="deathCauseOptions" |
| | | height="400px" |
| | | /> |
| | | <el-empty v-else description="后端暂未提供死亡原因统计接口"></el-empty> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <span>捐献者详细信息</span> |
| | | </div> |
| | | |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | style="width: 100%" |
| | | border |
| | | stripe |
| | | > |
| | | <el-table-column label="捐献者编号" prop="donorNo" align="center" /> |
| | | <el-table-column label="姓名" prop="name" align="center" /> |
| | | <el-table-column label="性别" prop="gender" align="center" > |
| | | <template slot-scope="{ row }"> |
| | | <el-tag :type="row.gender === '男' ? 'primary' : 'danger'" size="small"> |
| | | {{ row.gender }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="年龄" prop="age" align="center" /> |
| | | <el-table-column label="血型" prop="bloodType" align="center" > |
| | | <template slot-scope="{ row }"> |
| | | <el-tag :type="getBloodTypeTag(row.bloodType)" size="small"> |
| | | {{ row.bloodType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="地区" prop="region" align="center" /> |
| | | <el-table-column label="捐献时间" prop="donationTime" align="center" /> |
| | | <el-table-column label="中国分类" prop="chinaCategory" align="center" /> |
| | | <el-table-column label="死亡原因" prop="deathCause" align="center" /> |
| | | <el-table-column label="捐献器官" prop="organs" align="center" > |
| | | <template slot-scope="{ row }"> |
| | | <span>{{ row.organs.join('、') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center" " fixed="right"> |
| | | <template slot-scope="{ row }"> |
| | | <el-button |
| | | type="text" |
| | | size="mini" |
| | | @click="viewDonorDetail(row)" |
| | | > |
| | | 详情 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template v-if="useMock"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | style="width: 100%" |
| | | border |
| | | stripe |
| | | > |
| | | <el-table-column label="捐献者编号" prop="donorNo" align="center" /> |
| | | <el-table-column label="姓名" prop="name" align="center" /> |
| | | <el-table-column label="性别" prop="gender" align="center" /> |
| | | <el-table-column label="年龄" prop="age" align="center" /> |
| | | <el-table-column label="血型" prop="bloodType" align="center" /> |
| | | <el-table-column label="地区" prop="region" align="center" /> |
| | | <el-table-column label="捐献时间" prop="donationTime" align="center" /> |
| | | </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-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]" |
| | | /> |
| | | </template> |
| | | <el-empty v-else description="后端暂未提供捐献者详细信息接口"></el-empty> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | // 修正导入路径 |
| | | import FilterPanel from '@/components/charts/FilterPanel.vue'; |
| | | import EChartsWrapper from '@/components/charts/EChartsWrapper.vue'; |
| | | import { caseSex, caseBloodType, caseAvgAge } from '@/api/businessApi'; |
| | | import { toTotalsList, toNum } from '@/utils/totals'; |
| | | import statisticsMode, { isStatisticsMock } from '@/utils/statisticsMode'; |
| | | import { COLORS, PALETTE, axisTooltip, grid, legend } from '@/utils/chartStyle'; |
| | | |
| | | const AGE_GROUP_MAP = { |
| | | '1': '<17岁', |
| | | '2': '18-49岁', |
| | | '3': '50-69岁', |
| | | '4': '>69岁' |
| | | }; |
| | | |
| | | export default { |
| | | name: 'DonorStatistics', |
| | |
| | | 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' |
| | | }, |
| | |
| | | endPlaceholder: '结束月份', |
| | | valueFormat: 'yyyy-MM', |
| | | width: '300px' |
| | | }, |
| | | { |
| | | label: '年龄段', |
| | | prop: 'ageRange', |
| | | type: 'select', |
| | | multiple: true, |
| | | options: [ |
| | | { label: '<17岁', value: '0-17' }, |
| | | { label: '18-49岁', value: '18-49' }, |
| | | { label: '50-69岁', value: '50-69' }, |
| | | { label: '>69岁', value: '70-100' } |
| | | ], |
| | | width: '200px' |
| | | }, |
| | | { |
| | | label: '性别', |
| | |
| | | // 图表配置 |
| | | ageChartOptions: {}, |
| | | genderBloodChartOptions: {}, |
| | | categoryChartOptions: {}, |
| | | deathChartOptions: {}, |
| | | |
| | | // 接口返回数据 |
| | | sexData: [], |
| | | bloodData: [], |
| | | |
| | | // 无接口图表的模拟数据(仅 mock 模式展示) |
| | | chinaCategoryData: [ |
| | | { name: '中国一类(DBD)', value: 45 }, |
| | | { name: '中国二类(DCD)', value: 35 }, |
| | | { name: '中国三类(DBCD)', value: 20 } |
| | | ], |
| | | deathCauseData: [ |
| | | { name: '脑血管疾病', value: 40 }, |
| | | { name: '颅脑损伤', value: 30 }, |
| | | { name: '心脏疾病', value: 15 }, |
| | | { name: '其他原因', value: 15 } |
| | | ], |
| | | |
| | | // 捐献者详细信息模拟数据(无后端接口,仅 mock 模式展示) |
| | | donorList: [ |
| | | { donorNo: 'D2024001', name: '张伟', gender: '男', age: 45, bloodType: 'A', region: '青岛地区', donationTime: '2024-03-15' }, |
| | | { donorNo: 'D2024002', name: '王芳', gender: '女', age: 38, bloodType: 'B', region: '青岛地区', donationTime: '2024-03-22' }, |
| | | { donorNo: 'D2024003', name: '李娜', gender: '女', age: 52, bloodType: 'O', region: '日照地区', donationTime: '2024-04-02' }, |
| | | { donorNo: 'D2024004', name: '刘洋', gender: '男', age: 29, bloodType: 'AB', region: '青岛地区', donationTime: '2024-04-18' }, |
| | | { donorNo: 'D2024005', name: '陈静', gender: '女', age: 47, bloodType: 'A', region: '济南地区', donationTime: '2024-05-06' }, |
| | | { donorNo: 'D2024006', name: '杨强', gender: '男', age: 33, bloodType: 'B', region: '烟台地区', donationTime: '2024-05-21' }, |
| | | { donorNo: 'D2024007', name: '赵敏', gender: '女', age: 41, bloodType: 'O', region: '青岛地区', donationTime: '2024-06-03' }, |
| | | { donorNo: 'D2024008', name: '黄磊', gender: '男', age: 55, bloodType: 'A', region: '潍坊地区', donationTime: '2024-06-17' }, |
| | | { donorNo: 'D2024009', name: '周杰', gender: '男', age: 62, bloodType: 'O', region: '临沂地区', donationTime: '2024-07-01' }, |
| | | { donorNo: 'D2024010', name: '吴霞', gender: '女', age: 26, bloodType: 'AB', region: '日照地区', donationTime: '2024-07-15' }, |
| | | { donorNo: 'D2024011', name: '徐磊', gender: '男', age: 48, bloodType: 'B', region: '青岛地区', donationTime: '2024-08-09' }, |
| | | { donorNo: 'D2024012', name: '孙丽', gender: '女', age: 35, bloodType: 'O', region: '威海地区', donationTime: '2024-08-24' }, |
| | | { donorNo: 'D2024013', name: '马超', gender: '男', age: 44, bloodType: 'A', region: '济南地区', donationTime: '2024-09-05' }, |
| | | { donorNo: 'D2024014', name: '朱婷', gender: '女', age: 58, bloodType: 'B', region: '青岛地区', donationTime: '2024-09-19' }, |
| | | { donorNo: 'D2024015', name: '胡军', gender: '男', age: 31, bloodType: 'O', region: '烟台地区', donationTime: '2024-10-08' }, |
| | | { donorNo: 'D2024016', name: '郭涛', gender: '男', age: 68, bloodType: 'A', region: '青岛地区', donationTime: '2024-10-22' }, |
| | | { donorNo: 'D2024017', name: '何平', gender: '女', age: 39, bloodType: 'AB', region: '潍坊地区', donationTime: '2024-11-06' }, |
| | | { donorNo: 'D2024018', name: '高翔', gender: '男', age: 50, bloodType: 'B', region: '临沂地区', donationTime: '2024-11-20' }, |
| | | { donorNo: 'D2024019', name: '林芳', gender: '女', age: 27, bloodType: 'O', region: '青岛地区', donationTime: '2024-12-04' }, |
| | | { donorNo: 'D2024020', name: '罗刚', gender: '男', age: 42, bloodType: 'A', region: '日照地区', donationTime: '2024-12-18' }, |
| | | { donorNo: 'D2023021', name: '郑爽', gender: '女', age: 34, bloodType: 'B', region: '青岛地区', donationTime: '2023-06-10' }, |
| | | { donorNo: 'D2023022', name: '梁宇', gender: '男', age: 57, bloodType: 'O', region: '济南地区', donationTime: '2023-07-25' }, |
| | | { donorNo: 'D2023023', name: '谢娜', gender: '女', age: 49, bloodType: 'A', region: '威海地区', donationTime: '2023-08-14' }, |
| | | { donorNo: 'D2023024', name: '韩冰', gender: '男', age: 23, bloodType: 'AB', region: '青岛地区', donationTime: '2023-09-30' } |
| | | ], |
| | | |
| | | // 表格数据 |
| | | tableData: [], |
| | | total: 0, |
| | | |
| | | // 模拟数据 |
| | | mockData: { |
| | | table: [ |
| | | { id: 1, donorNo: 'D2024001', name: '张三', gender: '男', age: 45, bloodType: 'A', region: '青岛地区', donationTime: '2024-01-15', chinaCategory: '中国一类(DBD)', deathCause: '脑外伤', organs: ['全肝', '左肾', '右肾'] }, |
| | | { id: 2, donorNo: 'D2024002', name: '李四', gender: '女', age: 32, bloodType: 'B', region: '青岛地区', donationTime: '2024-01-20', chinaCategory: '中国二类(DCD)', deathCause: '心搏骤停', organs: ['心脏', '眼角膜'] }, |
| | | { id: 3, donorNo: 'D2024003', name: '王五', gender: '男', age: 58, bloodType: 'O', region: '日照地区', donationTime: '2024-02-10', chinaCategory: '中国三类(DBCD)', deathCause: '脑血管意外', organs: ['全肝', '胰腺'] }, |
| | | { id: 4, donorNo: 'D2024004', name: '赵六', gender: '男', age: 28, bloodType: 'AB', region: '济南地区', donationTime: '2024-02-15', chinaCategory: '中国一类(DBD)', deathCause: '交通事故', organs: ['全肺', '左肾', '右肾'] }, |
| | | { id: 5, donorNo: 'D2024005', name: '钱七', gender: '女', age: 65, bloodType: 'A', region: '烟台地区', donationTime: '2024-02-20', chinaCategory: '中国二类(DCD)', deathCause: '心肌梗死', organs: ['心脏'] }, |
| | | { id: 6, donorNo: 'D2024006', name: '孙八', gender: '男', age: 42, bloodType: 'B', region: '威海地区', donationTime: '2024-03-05', chinaCategory: '中国一类(DBD)', deathCause: '脑肿瘤', organs: ['左肾', '右肾', '眼角膜'] }, |
| | | { id: 7, donorNo: 'D2024007', name: '周九', gender: '男', age: 37, bloodType: 'O', region: '潍坊地区', donationTime: '2024-03-10', chinaCategory: '中国三类(DBCD)', deathCause: '脑外伤', organs: ['肝脏', '胰腺', '眼角膜'] }, |
| | | { id: 8, donorNo: 'D2024008', name: '吴十', gender: '女', age: 25, bloodType: 'AB', region: '临沂地区', donationTime: '2024-03-15', chinaCategory: '中国二类(DCD)', deathCause: '交通事故', organs: ['心脏', '肺'] } |
| | | ], |
| | | age: [ |
| | | { ageRange: '<17岁', male: 5, female: 3 }, |
| | | { ageRange: '18-49岁', male: 45, female: 30 }, |
| | | { ageRange: '50-69岁', male: 35, female: 25 }, |
| | | { ageRange: '>69岁', male: 10, female: 8 } |
| | | ], |
| | | gender: [ |
| | | { name: '男性', value: 95 }, |
| | | { name: '女性', value: 66 } |
| | | ], |
| | | blood: [ |
| | | { name: 'A型', value: 45 }, |
| | | { name: 'B型', value: 38 }, |
| | | { name: 'O型', value: 52 }, |
| | | { name: 'AB型', value: 26 } |
| | | ], |
| | | category: [ |
| | | { name: '中国一类(DBD)', value: 85 }, |
| | | { name: '中国二类(DCD)', value: 55 }, |
| | | { name: '中国三类(DBCD)', value: 21 } |
| | | ], |
| | | death: [ |
| | | { name: '脑外伤', value: 45 }, |
| | | { name: '心搏骤停', value: 35 }, |
| | | { name: '脑血管意外', value: 35 }, |
| | | { name: '交通事故', value: 20 }, |
| | | { name: '心肌梗死', value: 25 }, |
| | | { name: '其他', value: 15 } |
| | | ] |
| | | } |
| | | total: 0 |
| | | }; |
| | | }, |
| | | computed: { |
| | | useMock() { |
| | | return isStatisticsMock(); |
| | | }, |
| | | chinaCategoryOptions() { |
| | | return this.buildPieOptions('中国分类', this.chinaCategoryData); |
| | | }, |
| | | deathCauseOptions() { |
| | | return this.buildPieOptions('死亡原因', this.deathCauseData); |
| | | } |
| | | }, |
| | | 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]; |
| | | } |
| | | if (this.query.sex) q.sex = this.query.sex; |
| | | if (this.query.bloodType) q.boodType = this.query.bloodType; |
| | | return q; |
| | | }, |
| | | |
| | | // 加载数据 |
| | | async loadData() { |
| | | this.loading = true; |
| | | try { |
| | | await new Promise(resolve => setTimeout(resolve, 500)); |
| | | const q = this.buildQuery(); |
| | | const [sexRes, bloodRes, avgRes] = await Promise.all([ |
| | | caseSex(q), |
| | | caseBloodType(q), |
| | | caseAvgAge(q) |
| | | ]); |
| | | |
| | | // 处理表格数据 |
| | | let filteredData = [...this.mockData.table]; |
| | | this.sexData = toTotalsList(sexRes); |
| | | this.bloodData = toTotalsList(bloodRes); |
| | | const avgList = toTotalsList(avgRes); |
| | | |
| | | if (this.query.region.length > 0) { |
| | | filteredData = filteredData.filter(item => |
| | | this.query.region.includes(item.region.replace('地区', '').toLowerCase()) |
| | | ); |
| | | } |
| | | |
| | | if (this.query.monthRange && this.query.monthRange.length === 2) { |
| | | const [start, end] = this.query.monthRange; |
| | | filteredData = filteredData.filter(item => |
| | | item.donationTime >= start && item.donationTime <= end |
| | | ); |
| | | } |
| | | |
| | | if (this.query.ageRange.length > 0) { |
| | | filteredData = filteredData.filter(item => { |
| | | const age = item.age; |
| | | return this.query.ageRange.some(range => { |
| | | if (range === '0-17') return age < 18; |
| | | if (range === '18-49') return age >= 18 && age <= 49; |
| | | if (range === '50-69') return age >= 50 && age <= 69; |
| | | if (range === '70-100') return age >= 70; |
| | | return false; |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | if (this.query.gender) { |
| | | filteredData = filteredData.filter(item => item.gender === this.query.gender); |
| | | } |
| | | |
| | | if (this.query.bloodType) { |
| | | filteredData = filteredData.filter(item => item.bloodType === this.query.bloodType); |
| | | } |
| | | |
| | | // 分页 |
| | | const start = (this.query.page - 1) * this.query.limit; |
| | | this.tableData = filteredData.slice(start, start + this.query.limit); |
| | | this.total = filteredData.length; |
| | | |
| | | // 更新概览数据 |
| | | this.updateOverviewData(filteredData); |
| | | |
| | | // 更新图表 |
| | | this.updateOverviewData(avgList); |
| | | this.updateCharts(); |
| | | |
| | | // 捐献者详细信息无 /opo/totals 聚合接口,使用模拟数据补全 |
| | | this.updateDonorTable(); |
| | | } catch (error) { |
| | | console.error('加载数据失败:', error); |
| | | this.$message.error('数据加载失败'); |
| | |
| | | }, |
| | | |
| | | // 更新概览数据 |
| | | updateOverviewData(data) { |
| | | if (data.length === 0) return; |
| | | |
| | | const total = data.length; |
| | | const males = data.filter(item => item.gender === '男').length; |
| | | const females = data.filter(item => item.gender === '女').length; |
| | | const avgAge = data.reduce((sum, item) => sum + item.age, 0) / total; |
| | | updateOverviewData(avgList) { |
| | | const males = this.sexData.reduce((sum, item) => sum + toNum(item.manCount), 0); |
| | | const females = this.sexData.reduce((sum, item) => sum + toNum(item.womanCount), 0); |
| | | const total = males + females; |
| | | const avgAge = avgList.length |
| | | ? toNum(avgList.reduce((sum, item) => sum + toNum(item.avgAge), 0) / avgList.length) |
| | | : 0; |
| | | |
| | | this.overviewData[0].value = total; |
| | | this.overviewData[1].value = `${((males / total) * 100).toFixed(1)}%`; |
| | | this.overviewData[2].value = `${((females / total) * 100).toFixed(1)}%`; |
| | | this.overviewData[1].value = total > 0 ? `${((males / total) * 100).toFixed(1)}%` : '0%'; |
| | | this.overviewData[2].value = total > 0 ? `${((females / total) * 100).toFixed(1)}%` : '0%'; |
| | | this.overviewData[3].value = avgAge.toFixed(1); |
| | | }, |
| | | |
| | | // 更新图表 |
| | | updateCharts() { |
| | | // 年龄分布图 |
| | | // 年龄分布图(左男右女,清爽发散金字塔) |
| | | this.ageChartOptions = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | tooltip: axisTooltip(), |
| | | legend: legend(['男性', '女性']), |
| | | grid: grid(), |
| | | xAxis: { |
| | | type: 'value', |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#8a919f', fontSize: 12, formatter: value => Math.abs(value) }, |
| | | splitLine: { lineStyle: { type: 'dashed', color: '#f0f1f5' } } |
| | | }, |
| | | legend: { |
| | | data: ['男性', '女性'] |
| | | yAxis: { |
| | | type: 'category', |
| | | data: this.sexData.map(item => AGE_GROUP_MAP[item.ageGroup] || item.ageGroup || '未知'), |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#606266', fontSize: 13 } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: [ |
| | | { |
| | | type: 'value', |
| | | position: 'bottom', |
| | | axisLabel: { |
| | | formatter: function(value) { |
| | | return Math.abs(value); |
| | | } |
| | | } |
| | | } |
| | | ], |
| | | yAxis: [ |
| | | { |
| | | type: 'category', |
| | | axisTick: { |
| | | show: false |
| | | }, |
| | | data: this.mockData.age.map(item => item.ageRange) |
| | | } |
| | | ], |
| | | series: [ |
| | | { |
| | | name: '男性', |
| | | type: 'bar', |
| | | stack: '性别', |
| | | data: this.mockData.age.map(item => item.male), |
| | | itemStyle: { color: '#409EFF' } |
| | | barMaxWidth: 18, |
| | | data: this.sexData.map(item => toNum(item.manCount)), |
| | | itemStyle: { color: COLORS.blue, borderRadius: [4, 0, 0, 4] } |
| | | }, |
| | | { |
| | | name: '女性', |
| | | type: 'bar', |
| | | stack: '性别', |
| | | data: this.mockData.age.map(item => -item.female), |
| | | itemStyle: { color: '#F56C6C' } |
| | | barMaxWidth: 18, |
| | | data: this.sexData.map(item => -toNum(item.womanCount)), |
| | | itemStyle: { color: COLORS.coral, borderRadius: [0, 4, 4, 0] } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | // 性别与血型组合图 |
| | | // 性别与血型分布图(同心环,配色清爽) |
| | | const males = this.bloodData.reduce((sum, item) => sum + toNum(item.manCount), 0); |
| | | const females = this.bloodData.reduce((sum, item) => sum + toNum(item.womanCount), 0); |
| | | const bloodPie = this.bloodData.map(item => ({ |
| | | name: item.boodType || '未知', |
| | | value: toNum(item.boodTypeCount) || (toNum(item.manCount) + toNum(item.womanCount)) |
| | | })); |
| | | |
| | | this.genderBloodChartOptions = { |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | data: [ |
| | | '男性', '女性', |
| | | 'A型', 'B型', 'O型', 'AB型' |
| | | ], |
| | | top: 20 |
| | | }, |
| | | grid: { |
| | | top: '20%', |
| | | bottom: '20%', |
| | | left: '10%', |
| | | right: '10%' |
| | | }, |
| | | legend: { data: ['男性', '女性', ...this.bloodData.map(item => item.boodType || '未知')], top: 20 }, |
| | | series: [ |
| | | { |
| | | name: '性别分布', |
| | | type: 'pie', |
| | | radius: [0, '30%'], |
| | | radius: [0, '40%'], |
| | | center: ['25%', '50%'], |
| | | label: { |
| | | position: 'inner', |
| | | fontSize: 12 |
| | | }, |
| | | data: this.mockData.gender |
| | | label: { position: 'inner', fontSize: 12 }, |
| | | data: [ |
| | | { name: '男性', value: males }, |
| | | { name: '女性', value: females } |
| | | ], |
| | | color: [COLORS.blue, COLORS.coral] |
| | | }, |
| | | { |
| | | name: '血型分布', |
| | | type: 'pie', |
| | | radius: ['40%', '55%'], |
| | | radius: ['55%', '75%'], |
| | | center: ['25%', '50%'], |
| | | data: this.mockData.blood |
| | | }, |
| | | { |
| | | name: '性别血型对比', |
| | | type: 'pie', |
| | | radius: [0, '30%'], |
| | | center: ['75%', '50%'], |
| | | label: { |
| | | position: 'inner', |
| | | fontSize: 12 |
| | | }, |
| | | data: [ |
| | | { name: '男-A', value: 20 }, |
| | | { name: '男-B', value: 15 }, |
| | | { name: '男-O', value: 25 }, |
| | | { name: '男-AB', value: 10 }, |
| | | { name: '女-A', value: 12 }, |
| | | { name: '女-B', value: 8 }, |
| | | { name: '女-O', value: 10 }, |
| | | { name: '女-AB', value: 5 } |
| | | ] |
| | | label: { fontSize: 14, color: '#606266' }, |
| | | data: bloodPie, |
| | | color: [COLORS.amber, COLORS.teal, COLORS.purple, COLORS.orange] |
| | | } |
| | | ] |
| | | }; |
| | | }, |
| | | |
| | | // 中国分类分布图 |
| | | this.categoryChartOptions = { |
| | | // 捐献者详细信息(无接口,模拟数据补全 + 过滤分页) |
| | | updateDonorTable() { |
| | | let list = this.donorList; |
| | | if (this.query.region && this.query.region.length) { |
| | | list = list.filter(item => this.query.region.includes(item.region)); |
| | | } |
| | | if (this.query.monthRange && this.query.monthRange.length === 2) { |
| | | const start = this.query.monthRange[0]; |
| | | const end = this.query.monthRange[1]; |
| | | list = list.filter(item => { |
| | | const m = item.donationTime.slice(0, 7); |
| | | return m >= start && m <= end; |
| | | }); |
| | | } |
| | | if (this.query.gender) { |
| | | list = list.filter(item => item.gender === this.query.gender); |
| | | } |
| | | if (this.query.bloodType) { |
| | | list = list.filter(item => item.bloodType === this.query.bloodType); |
| | | } |
| | | this.total = list.length; |
| | | const start = (this.query.page - 1) * this.query.limit; |
| | | this.tableData = list.slice(start, start + this.query.limit); |
| | | }, |
| | | |
| | | // 生成饼图配置(无接口图表的 mock 展示) |
| | | buildPieOptions(name, data) { |
| | | return { |
| | | color: PALETTE, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b} : {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | right: 10, |
| | | top: 'center', |
| | | data: this.mockData.category.map(item => item.name) |
| | | left: 'left', |
| | | top: 'middle', |
| | | itemWidth: 10, |
| | | itemHeight: 10, |
| | | itemGap: 16, |
| | | textStyle: { fontSize: 14, color: '#606266' }, |
| | | data: data.map(item => item.name) |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '中国分类', |
| | | name, |
| | | type: 'pie', |
| | | radius: ['30%', '70%'], |
| | | center: ['40%', '50%'], |
| | | roseType: 'radius', |
| | | label: { |
| | | show: true |
| | | }, |
| | | radius: '55%', |
| | | center: ['55%', '50%'], |
| | | itemStyle: { borderColor: '#fff', borderWidth: 2 }, |
| | | label: { fontSize: 15, color: '#606266', formatter: '{b} {d}%' }, |
| | | labelLine: { lineStyle: { color: '#c0c4cc' } }, |
| | | data, |
| | | emphasis: { |
| | | label: { |
| | | show: true |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | }, |
| | | data: this.mockData.category |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | // 死亡原因分析图 |
| | | this.deathChartOptions = { |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 10, |
| | | top: 'center', |
| | | data: this.mockData.death.map(item => item.name) |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '死亡原因', |
| | | type: 'pie', |
| | | radius: ['30%', '60%'], |
| | | center: ['60%', '50%'], |
| | | avoidLabelOverlap: false, |
| | | label: { |
| | | show: true, |
| | | formatter: '{b|{b}}\n{c|{c} ({d}%)}', |
| | | rich: { |
| | | b: { |
| | | fontSize: 12, |
| | | lineHeight: 20 |
| | | }, |
| | | c: { |
| | | fontSize: 10, |
| | | color: '#999' |
| | | } |
| | | } |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: true, |
| | | fontSize: '16', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | data: this.mockData.death |
| | | } |
| | | ] |
| | | }; |
| | | }, |
| | | |
| | | // 获取血型标签类型 |
| | | getBloodTypeTag(type) { |
| | | const map = { 'A': 'danger', 'B': 'primary', 'O': 'success', 'AB': 'warning' }; |
| | | return map[type] || 'info'; |
| | | }, |
| | | |
| | | // 处理查询 |
| | |
| | | // 处理导出 |
| | | handleExport() { |
| | | this.$message.info('导出功能开发中...'); |
| | | }, |
| | | |
| | | // 查看捐献者详情 |
| | | viewDonorDetail(row) { |
| | | this.$router.push({ |
| | | path: '/donor/detail', |
| | | query: { id: row.id } |
| | | }); |
| | | } |
| | | } |
| | | }; |