From 3df460667d74868d0a54fce12d5e881bbd6c0c1a Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期四, 17 九月 2026 17:11:18 +0800
Subject: [PATCH] 青岛维护统计
---
src/assets/styles/index.scss | 6
src/utils/statisticsMode.js | 21
src/views/business/transfer/TransportEdit.vue | 2
src/views/business/course/ccss.vue | 2
src/views/business/ethicalReview/ethicalReviewInfo.vue | 29
src/api/businessApi/index.js | 1
src/views/business/GetWitness/GetWitnessInfo.vue | 2
src/views/statistics/rate.vue | 406 +++--
src/views/business/OrganUtilization/OrganUtilizationInfo.vue | 2
src/utils/totals.js | 42
src/utils/chartStyle.js | 139 ++
src/assets/logo.zip | 0
src/views/business/affirm/affirmInfo.vue | 49
src/views/business/maintain/maintainInfo.vue | 2
src/views/statistics/organ.vue | 239 +--
src/views/statistics/case.vue | 369 ++---
src/views/business/followupVisit/info.vue | 2
src/views/business/assess/assessInfo.vue | 2
src/views/statistics/willingness.vue | 464 +++---
src/api/businessApi/ethicalReview.js | 8
src/views/business/decide/DecideInfo.vue | 2
src/views/statistics/index.vue | 50
src/store/modules/permission.js | 5
src/views/statistics/donor.vue | 580 +++-----
src/views/business/course/index.vue | 17
/dev/null | 0
src/api/businessApi/statistics.js | 60
src/views/business/course/components/EthicalReviewStage.vue | 18
src/views/business/allocation/allocationInfo.vue | 2
src/views/business/ethicalReview/index.vue | 131 +-
src/layout/components/AppMain.vue | 11
src/views/statistics/components/CaseDetail.vue | 92 +
src/api/businessApi/statisticsMock.js | 157 ++
src/components/FilePreviewDialog/index.vue | 63
src/views/statistics/utilization.vue | 657 ++--------
35 files changed, 1,885 insertions(+), 1,747 deletions(-)
diff --git "a/dist \0502\051.zip" "b/dist \0502\051.zip"
deleted file mode 100644
index c97fd34..0000000
--- "a/dist \0502\051.zip"
+++ /dev/null
Binary files differ
diff --git a/src/api/businessApi/ethicalReview.js b/src/api/businessApi/ethicalReview.js
index 7a28f1b..0ec8c8e 100644
--- a/src/api/businessApi/ethicalReview.js
+++ b/src/api/businessApi/ethicalReview.js
@@ -32,6 +32,14 @@
params: query
});
}
+// 浼︾悊瀹℃煡妗f涓嬭浇
+export function ethicalReviewDownload(query) {
+ return request({
+ url: "/project/ethicalreviewopinions/download",
+ method: "get",
+ params: query
+ });
+}
// 浼︾悊瀹℃煡淇℃伅璇︽儏
export function ethicalreviewInfo(id) {
return request({
diff --git a/src/api/businessApi/index.js b/src/api/businessApi/index.js
index 07a8fbe..e20ee31 100644
--- a/src/api/businessApi/index.js
+++ b/src/api/businessApi/index.js
@@ -9,3 +9,4 @@
export * from "./maintain";
export * from "./OrganUtilization";
export * from "./transfer";
+export * from "./statistics";
diff --git a/src/api/businessApi/statistics.js b/src/api/businessApi/statistics.js
new file mode 100644
index 0000000..bc19ff4
--- /dev/null
+++ b/src/api/businessApi/statistics.js
@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+import { isStatisticsMock } from '@/utils/statisticsMode'
+import { mockTotals } from './statisticsMock'
+
+// mock 妯″紡涓嬭繑鍥炴ā鎷熸暟鎹紝鍚﹀垯璇锋眰鐪熷疄鎺ュ彛
+function post(url, data) {
+ if (isStatisticsMock()) {
+ return Promise.resolve({ code: 200, msg: '鎿嶄綔鎴愬姛', data: mockTotals(url, data) })
+ }
+ return request({
+ url,
+ method: 'post',
+ data
+ })
+}
+
+// 妗堜緥瓒嬪娍鍖哄煙
+export function caseTrendArea(data) {
+ return post('/opo/totals/caseTrendArea', data)
+}
+// 妗堜緥瓒嬪娍
+export function caseTrend(data) {
+ return post('/opo/totals/caseTrend', data)
+}
+// 妗堜緥瓒嬪娍鏈堜唤
+export function caseTrendMoon(data) {
+ return post('/opo/totals/caseTrendMoon', data)
+}
+// 骞冲潎骞撮緞
+export function caseAvgAge(data) {
+ return post('/opo/totals/caseAvgAge', data)
+}
+// 鎬у埆涓庤鍨嬪垎甯�
+export function caseBloodType(data) {
+ return post('/opo/totals/caseBloodType', data)
+}
+// 鎹愮尞鑰呭勾榫勬�у埆鍒嗗竷
+export function caseSex(data) {
+ return post('/opo/totals/caseSex', data)
+}
+// 鎹愮尞鍣ㄥ畼缁熻鏄庣粏
+export function organ(data) {
+ return post('/opo/totals/organ', data)
+}
+// 鍣ㄥ畼鑾峰彇/绉绘瓒嬪娍
+export function organMoon(data) {
+ return post('/opo/totals/organMoon', data)
+}
+// 鍣ㄥ畼绫诲瀷鍒嗗竷
+export function organType(data) {
+ return post('/opo/totals/organType', data)
+}
+// 鎬荤粺璁★紙鎬绘綔鍦ㄦ崘鐚渚� / 鎬诲畬鎴愭崘鐚暟锛�
+export function totals(data) {
+ return post('/opo/totals/totals', data)
+}
+// 妗堜緥鏄庣粏鍒楄〃
+export function trendList(data) {
+ return post('/opo/totals/trendList', data)
+}
diff --git a/src/api/businessApi/statisticsMock.js b/src/api/businessApi/statisticsMock.js
new file mode 100644
index 0000000..34a52d4
--- /dev/null
+++ b/src/api/businessApi/statisticsMock.js
@@ -0,0 +1,157 @@
+// 缁熻妯″潡妯℃嫙鏁版嵁
+// 鍝嶅簲瀛楁涓� /opo/totals/* 杩斿洖鐨� Totals(opo缁熻) 瀵硅薄淇濇寔涓�鑷�
+// mock 妯″紡涓嬬敱 statistics.js 杩斿洖锛岄〉闈㈤�昏緫鏃犻渶鍖哄垎鏁版嵁鏉ユ簮
+
+const REGIONS = ['闈掑矝鍦板尯', '鏃ョ収鍦板尯', '娴庡崡鍦板尯', '鐑熷彴鍦板尯', '濞佹捣鍦板尯', '娼嶅潑鍦板尯', '涓存矀鍦板尯'];
+const MONTHS = ['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06'];
+const YEARS = ['2022', '2023', '2024'];
+const ORGANS = ['鍏ㄨ倽', '鑲捐剰', '蹇冭剰', '鑲�', '鑳拌吅', '鐪艰鑶�'];
+const BLOOD_TYPES = ['A', 'B', 'O', 'AB'];
+const AGE_GROUPS = ['1', '2', '3', '4'];
+
+// 纭畾鎬т吉闅忔満锛屼繚璇佹瘡娆℃覆鏌撴暟鎹竴鑷�
+function createRand(seed) {
+ let s = seed % 2147483647;
+ if (s <= 0) s += 2147483646;
+ return function () {
+ s = (s * 16807) % 2147483647;
+ return (s - 1) / 2147483646;
+ };
+}
+
+// 妗堜緥瓒嬪娍锛堟湀浠界淮搴︼級
+function mockCaseTrend() {
+ const r = createRand(11);
+ return MONTHS.map((m) => {
+ const potential = 40 + Math.round(r() * 40);
+ return { donateTime: m, potentialCount: potential, completeCount: Math.round(potential * (0.6 + r() * 0.2)), totalCount: potential };
+ });
+}
+
+// 妗堜緥瓒嬪娍锛堝湴鍖虹淮搴︼級
+function mockCaseTrendArea() {
+ const r = createRand(22);
+ return REGIONS.map((area) => {
+ const potential = 30 + Math.round(r() * 70);
+ return { area, potentialCount: potential, completeCount: Math.round(potential * (0.6 + r() * 0.2)) };
+ });
+}
+
+// 妗堜緥鏄庣粏锛堝湴鍖� x 鏈堜唤锛�
+function mockCaseTrendMoon() {
+ const r = createRand(33);
+ const rows = [];
+ REGIONS.forEach((area) => {
+ MONTHS.forEach((m) => {
+ const potential = 5 + Math.round(r() * 15);
+ rows.push({ area, donateTime: m, potentialCount: potential, completeCount: Math.round(potential * (0.5 + r() * 0.4)), totalCount: potential });
+ });
+ });
+ return rows;
+}
+
+// 骞冲潎骞撮緞
+function mockCaseAvgAge() {
+ return [{ avgAge: 45.6 }];
+}
+
+// 鎬у埆涓庤鍨嬪垎甯�
+function mockCaseBloodType() {
+ const r = createRand(44);
+ return BLOOD_TYPES.map((boodType) => {
+ const man = 20 + Math.round(r() * 30);
+ const woman = 20 + Math.round(r() * 30);
+ return { boodType, manCount: man, womanCount: woman, boodTypeCount: man + woman };
+ });
+}
+
+// 鎹愮尞鑰呭勾榫勬�у埆鍒嗗竷
+function mockCaseSex() {
+ const r = createRand(55);
+ return AGE_GROUPS.map((ageGroup) => ({
+ ageGroup,
+ manCount: 15 + Math.round(r() * 40),
+ womanCount: 15 + Math.round(r() * 40)
+ }));
+}
+
+// 鎹愮尞鍣ㄥ畼缁熻鏄庣粏锛堝勾浠� x 鍣ㄥ畼 x 鍦板尯锛�
+function mockOrgan() {
+ const r = createRand(66);
+ const rows = [];
+ YEARS.forEach((year) => {
+ ORGANS.forEach((organName) => {
+ REGIONS.forEach((area) => {
+ const get = 10 + Math.round(r() * 40);
+ rows.push({ donateTime: year, organName, area, getCount: get, useCount: Math.round(get * (0.7 + r() * 0.25)) });
+ });
+ });
+ });
+ return rows;
+}
+
+// 鍣ㄥ畼鑾峰彇/绉绘瓒嬪娍锛堟湀浠界淮搴︼級
+function mockOrganMoon() {
+ const r = createRand(77);
+ return MONTHS.map((m) => {
+ const get = 50 + Math.round(r() * 60);
+ return { donateTime: m, getCount: get, useCount: Math.round(get * (0.75 + r() * 0.2)) };
+ });
+}
+
+// 鍣ㄥ畼绫诲瀷鍒嗗竷
+function mockOrganType() {
+ const r = createRand(88);
+ return ORGANS.map((organName) => {
+ const get = 40 + Math.round(r() * 80);
+ return { organName, getCount: get, useCount: Math.round(get * (0.75 + r() * 0.2)) };
+ });
+}
+
+// 鎬荤粺璁★紙鎬绘綔鍦ㄦ崘鐚渚� / 鎬诲畬鎴愭崘鐚暟锛�
+function mockTotalsAll() {
+ return [{ totalPotential: 486, totalCompletion: 327 }];
+}
+
+// 妗堜緥鏄庣粏鍒楄〃锛坱rendList锛�
+function mockTrendList() {
+ const r = createRand(99);
+ const NAMES = ['寮犱笁', '鏉庡洓', '鐜嬩簲', '璧靛叚', '闄堜竷', '鍒樺叓', '鍛ㄤ節'];
+ const HOSPITALS = ['闈掑矝澶у闄勫睘鍖婚櫌', '闈掑矝甯傜珛鍖婚櫌', '闈掑矝甯備腑蹇冨尰闄�', '灞变笢澶у榻愰瞾鍖婚櫌(闈掑矝)'];
+ const DEATHS = ['鑴戝浼�', '蹇冩悘楠ゅ仠', '鑴戣绠℃剰澶�', '棰呰剳鑲跨槫', '缂烘哀鎬ц剳鐥�'];
+ const WORKFLOWS = [1, 2, 3, 4, 5, 6, 7, 99];
+ return NAMES.map((name, i) => {
+ const month = MONTHS[i % MONTHS.length];
+ const day = String(1 + Math.floor(r() * 27)).padStart(2, '0');
+ return {
+ id: 1000 + i,
+ caseNo: `C${month.replace('-', '')}00${i + 1}`,
+ name,
+ inpatientNo: `ZY${month.replace('-', '')}00${i + 1}`,
+ workflow: WORKFLOWS[i % WORKFLOWS.length],
+ organName: ORGANS[i % ORGANS.length],
+ donatetime: `${month}-${day} 10:30`,
+ deathReason: DEATHS[i % DEATHS.length],
+ treatmentHospitalName: HOSPITALS[i % HOSPITALS.length]
+ };
+ });
+}
+
+const MOCK_MAP = {
+ '/opo/totals/caseTrend': mockCaseTrend,
+ '/opo/totals/caseTrendArea': mockCaseTrendArea,
+ '/opo/totals/caseTrendMoon': mockCaseTrendMoon,
+ '/opo/totals/caseAvgAge': mockCaseAvgAge,
+ '/opo/totals/caseBloodType': mockCaseBloodType,
+ '/opo/totals/caseSex': mockCaseSex,
+ '/opo/totals/organ': mockOrgan,
+ '/opo/totals/organMoon': mockOrganMoon,
+ '/opo/totals/organType': mockOrganType,
+ '/opo/totals/totals': mockTotalsAll,
+ '/opo/totals/trendList': mockTrendList
+};
+
+export function mockTotals(url) {
+ const fn = MOCK_MAP[url];
+ return fn ? fn() : [];
+}
diff --git a/src/assets/logo.zip b/src/assets/logo.zip
new file mode 100644
index 0000000..323c979
--- /dev/null
+++ b/src/assets/logo.zip
Binary files differ
diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss
index 49eb8c9..877f0d2 100644
--- a/src/assets/styles/index.scss
+++ b/src/assets/styles/index.scss
@@ -132,6 +132,12 @@
// background-color: rgba(94, 255, 0, 0.198);
}
+// business 妯″潡锛氬ぇ瀹藉睆灞呬腑闄愬鐣欑櫧锛屽皬灞忚嚜鍔ㄩ摵婊�
+.app-main.business-mode > * {
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
.components-container {
margin: 30px 50px;
position: relative;
diff --git a/src/components/FilePreviewDialog/index.vue b/src/components/FilePreviewDialog/index.vue
index 4bfff0a..357c237 100644
--- a/src/components/FilePreviewDialog/index.vue
+++ b/src/components/FilePreviewDialog/index.vue
@@ -61,7 +61,7 @@
<!-- PDF棰勮 -->
<div v-else-if="fileType === 'pdf'" class="preview-container pdf-preview">
<div class="pdf-toolbar">
- <el-button-group>
+ <el-button-group v-if="!useNativePdf">
<el-button
size="mini"
@click="changePage(currentPage - 1)"
@@ -81,7 +81,7 @@
</el-button>
</el-button-group>
- <el-button-group class="zoom-controls">
+ <el-button-group v-if="!useNativePdf" class="zoom-controls">
<el-button size="mini" @click="zoomOut" :disabled="scale <= 50">
<i class="el-icon-zoom-out"></i> 缂╁皬
</el-button>
@@ -99,7 +99,15 @@
</el-button>
</div>
- <div class="pdf-content" v-loading="pdfLoading">
+ <!-- pdf.js 鍥犺法鍩�(CORS)鍔犺浇澶辫触鏃讹紝闄嶇骇涓烘祻瑙堝櫒鍘熺敓 PDF 棰勮锛坕frame 瀵艰埅涓嶅彈鍚屾簮绛栫暐闄愬埗锛� -->
+ <iframe
+ v-if="useNativePdf"
+ :src="fileUrl"
+ class="pdf-native-iframe"
+ frameborder="0"
+ ></iframe>
+
+ <div class="pdf-content" v-else v-loading="pdfLoading">
<pdf
ref="pdf"
:src="pdfUrl"
@@ -214,6 +222,8 @@
pageCount: 0,
scale: 100,
pageRotate: 0,
+ // pdf.js 鍔犺浇澶辫触锛堝 CORS锛夋椂闄嶇骇涓烘祻瑙堝櫒鍘熺敓棰勮
+ useNativePdf: false,
// 鍥剧墖鐩稿叧
imageScale: 100
};
@@ -267,30 +277,11 @@
},
/** 璁$畻 baseUrlHt */
calculateBaseUrl() {
- // 鑾峰彇褰撳墠娴忚鍣ㄥ湴鍧�
- const currentUrl = window.location.href;
- console.log("褰撳墠娴忚鍣ㄥ湴鍧�:", currentUrl);
-
- try {
- // 浣跨敤 URL 瀵硅薄瑙f瀽
- const urlObj = new URL(currentUrl);
- // 鑾峰彇涓绘満鍚嶏紙IP 鎴栧煙鍚嶏級
- const hostname = urlObj.hostname;
- // 鎷兼帴绔彛 :9095
- this.baseUrlHt = `http://${hostname}:9095`;
- console.log("璁$畻寰楀埌鐨� baseUrlHt:", this.baseUrlHt);
- } catch (error) {
- console.error("瑙f瀽URL澶辫触:", error);
- // 澶囩敤鏂规锛氫娇鐢ㄦ鍒欐彁鍙�
- const match = currentUrl.match(/https?:\/\/([^:\/]+)/);
- if (match) {
- this.baseUrlHt = `http://${match[1]}:9095`;
- } else {
- // 鏈�鍚庣殑澶囩敤鏂规
- this.baseUrlHt =
- process.env.VUE_APP_BASE_API || "http://localhost:9095";
- }
- }
+ // 璧板綋鍓嶅煙鍚嶏紙鍚屽煙鐩稿璺緞锛夛紝鐢� nginx 鐨� /profile/ 浠g悊杞彂鍒板悗绔� 9095銆�
+ // 鐩磋繛 9095 绔彛浼氬洜绔彛涓嶅悓浜х敓璺ㄥ煙(CORS)锛岃�屽悗绔瓨鍦ㄥ弻閲� CORS 鍝嶅簲澶撮棶棰橈紝
+ // 鍚屽煙璇锋眰涓嶈Е鍙� CORS 妫�鏌ワ紝鍙粠鏍规簮涓婄粫寮�璇ラ棶棰橈紙鍥剧墖/PDF/涓嬭浇鍏ㄩ儴鐢熸晥锛夈��
+ this.baseUrlHt = "";
+ console.log("鏂囦欢璁块棶璧板悓鍩熶唬鐞�: /profile/");
},
/** 鍒濆鍖栭瑙� */
@@ -302,6 +293,7 @@
// 澶勭悊URL锛屾浛鎹㈠煙鍚嶉儴鍒�
this.fileUrl = this.processFileUrl(originalUrl);
+ console.log(this.fileUrl,'this.fileUrl');
this.fileType = this.getFileType(this.fileName);
@@ -312,6 +304,7 @@
this.scale = 100;
this.imageScale = 100;
this.pageCount = 0;
+ this.useNativePdf = false;
// 妯℃嫙鍔犺浇寤惰繜
setTimeout(() => {
@@ -360,8 +353,13 @@
pdfErrorHandler(error) {
console.error("PDF鍔犺浇澶辫触:", error);
this.pdfLoading = false;
- this.$message.error("PDF鏂囦欢鍔犺浇澶辫触锛岃灏濊瘯涓嬭浇鍚庢煡鐪�");
- this.previewVisible = false;
+ // pdf.js 閫氳繃 fetch 鍔犺浇璺ㄥ煙 PDF 鍙� CORS 闄愬埗锛屽け璐ユ椂闄嶇骇涓烘祻瑙堝櫒鍘熺敓棰勮
+ // 锛坕frame 灞炰簬瀵艰埅璇锋眰锛屼笉鍙楀悓婧愮瓥鐣ラ檺鍒讹紝鍙互姝e父鍔犺浇璺ㄥ煙 PDF锛�
+ this.useNativePdf = true;
+ this.$message({
+ type: "info",
+ message: "宸插垏鎹负娴忚鍣ㄥ師鐢熼瑙堟ā寮�"
+ });
},
/** 鍥剧墖鍔犺浇瀹屾垚 */
@@ -513,6 +511,13 @@
align-items: flex-start;
}
+ /* 鍘熺敓 PDF 棰勮锛圕ORS 闄嶇骇鏂规锛� */
+ .pdf-native-iframe {
+ flex: 1;
+ border: none;
+ background: #f8f9fa;
+ }
+
/* Office棰勮鏍峰紡 */
.office-content {
flex: 1;
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
index 0c6f4b7..72d9569 100644
--- a/src/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -1,5 +1,5 @@
<template>
- <section class="app-main">
+ <section class="app-main" :class="{ 'business-mode': isBusiness }">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
@@ -17,6 +17,15 @@
},
key() {
return this.$route.path
+ },
+ isBusiness() {
+ return (this.$route.matched || []).some((r) => {
+ const cp = r.meta && r.meta.componentPath;
+ if (typeof cp !== 'string' || cp.indexOf('business/') !== 0) return false;
+ // 涓氬姟妯″潡鐨� index 鍒楄〃椤典笉闇�瑕侀檺瀹斤紱course/index 鏄鎯呴〉锛屼繚鐣欓檺瀹�
+ if (/^business\/[^/]+\/index$/.test(cp) && cp !== 'business/course/index') return false;
+ return true;
+ });
}
}
}
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
index dba9252..ce1c002 100644
--- a/src/store/modules/permission.js
+++ b/src/store/modules/permission.js
@@ -61,6 +61,11 @@
route.children = filterChildren(route.children)
}
if (route.component) {
+ // 璁板綍鍘熷缁勪欢璺緞锛屼緵甯冨眬灞傚垽鏂墍灞炴ā鍧楋紙business/project 绛夛級
+ if (typeof route.component === 'string') {
+ route.meta = route.meta || {};
+ route.meta.componentPath = route.component; // 渚嬪 "business/assess/index"
+ }
// Layout ParentView 缁勪欢鐗规畩澶勭悊
if (route.component === 'Layout') {
route.component = Layout
diff --git a/src/utils/chartStyle.js b/src/utils/chartStyle.js
new file mode 100644
index 0000000..9ba5bbe
--- /dev/null
+++ b/src/utils/chartStyle.js
@@ -0,0 +1,139 @@
+// 缁熻鍥捐〃缁熶竴娓呯埥绠�娲侀鏍硷紙echarts 4 鍏煎锛�
+// 渚� statistics/*.vue 澶嶇敤锛屼繚璇佽瑙変竴鑷�
+
+const toRgba = (hex, alpha) => {
+ const n = parseInt(hex.slice(1), 16);
+ const r = (n >> 16) & 255;
+ const g = (n >> 8) & 255;
+ const b = n & 255;
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+};
+
+export const COLORS = {
+ blue: '#5B8FF9',
+ green: '#5AD8A6',
+ amber: '#F6BD16',
+ coral: '#E8684A',
+ sky: '#6DC8EC',
+ purple: '#9270CA',
+ orange: '#FF9D4D',
+ teal: '#269A99'
+};
+
+export const PALETTE = Object.values(COLORS);
+
+const tipBase = {
+ backgroundColor: '#ffffff',
+ borderColor: '#e8eaf0',
+ borderWidth: 1,
+ padding: [10, 14],
+ textStyle: { color: '#333', fontSize: 13 },
+ extraCssText: 'box-shadow:0 6px 18px rgba(31,45,61,.1);border-radius:10px;'
+};
+
+// 鍧愭爣杞寸被 tooltip锛堟煴鐘�/鎶樼嚎锛�
+export function axisTooltip() {
+ return { trigger: 'axis', ...tipBase, axisPointer: { lineStyle: { color: '#c9d2e0' } } };
+}
+
+// 鍥惧舰绫� tooltip锛堥ゼ鍥撅級
+export function itemTooltip() {
+ return { trigger: 'item', ...tipBase, formatter: '{b}锛歿c} ({d}%)' };
+}
+
+export function grid() {
+ return { left: 8, right: 16, top: 44, bottom: 8, containLabel: true };
+}
+
+// 绫荤洰杞�
+export function xAxis(data) {
+ return {
+ type: 'category',
+ data,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { color: '#8a919f', fontSize: 12 }
+ };
+}
+
+// 鏁板�艰酱
+export function yAxis(name) {
+ return {
+ type: 'value',
+ name,
+ nameTextStyle: { color: '#b0b6c3', fontSize: 12 },
+ axisLabel: { color: '#8a919f', fontSize: 12 },
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f1f5' } }
+ };
+}
+
+export function legend(data) {
+ return {
+ data,
+ icon: 'roundRect',
+ itemWidth: 10,
+ itemHeight: 10,
+ itemGap: 18,
+ textStyle: { color: '#8a919f', fontSize: 12 }
+ };
+}
+
+// 鏌辩姸绯诲垪
+export function barSeries(name, data, color, opts = {}) {
+ return {
+ name,
+ type: 'bar',
+ data,
+ barMaxWidth: opts.maxWidth || 20,
+ itemStyle: { color, borderRadius: opts.radius || [4, 4, 0, 0] },
+ stack: opts.stack,
+ yAxisIndex: opts.yAxisIndex
+ };
+}
+
+// 鎶樼嚎绯诲垪
+export function lineSeries(name, data, color, opts = {}) {
+ const s = {
+ name,
+ type: 'line',
+ data,
+ smooth: true,
+ symbol: 'circle',
+ symbolSize: opts.symbolSize || 6,
+ lineStyle: { width: 3, color },
+ itemStyle: { color },
+ yAxisIndex: opts.yAxisIndex
+ };
+ if (opts.area) {
+ s.areaStyle = {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 0, color: toRgba(color, 0.24) },
+ { offset: 1, color: toRgba(color, 0) }
+ ]
+ }
+ };
+ }
+ return s;
+}
+
+// 鐜舰鍥剧郴鍒�
+export function donutSeries(name, data) {
+ return {
+ name,
+ type: 'pie',
+ radius: ['52%', '72%'],
+ center: ['50%', '50%'],
+ avoidLabelOverlap: false,
+ itemStyle: { borderColor: '#fff', borderWidth: 2 },
+ label: { show: false },
+ labelLine: { show: false },
+ data,
+ color: PALETTE
+ };
+}
diff --git a/src/utils/statisticsMode.js b/src/utils/statisticsMode.js
new file mode 100644
index 0000000..8e7713b
--- /dev/null
+++ b/src/utils/statisticsMode.js
@@ -0,0 +1,21 @@
+// 缁熻妯″潡鏁版嵁婧愬紑鍏筹細mock(妯℃嫙鏁版嵁) / real(鐪熷疄鎺ュ彛)
+// 鍏ㄥ眬鍏变韩锛屽垏鎹㈠悗缁熻鍚勯〉闈㈣鍙栧悓涓�鐘舵��
+import Vue from 'vue';
+
+const KEY = 'opo-statistics-mock';
+const initial = localStorage.getItem(KEY) === '1';
+
+const state = Vue.observable({
+ useMock: initial
+});
+
+export function setStatisticsMock(useMock) {
+ state.useMock = !!useMock;
+ localStorage.setItem(KEY, state.useMock ? '1' : '0');
+}
+
+export function isStatisticsMock() {
+ return state.useMock;
+}
+
+export default state;
diff --git a/src/utils/totals.js b/src/utils/totals.js
new file mode 100644
index 0000000..069239a
--- /dev/null
+++ b/src/utils/totals.js
@@ -0,0 +1,42 @@
+// /opo/totals/* 缁熻鎺ュ彛鍏叡宸ュ叿
+// 鎺ュ彛璇锋眰浣撲负 Totals(opo缁熻) 瀵硅薄锛屽搷搴斾负 RuoYi Result { code, msg, data, total }
+// data 鍙兘鏄� Totals 鏁扮粍銆佸崟涓� Totals 瀵硅薄锛屾垨鍖呬簡涓�灞� {list/rows} 鐨勫璞�
+
+// 褰掍竴鍖栬繑鍥炵粨鏋滀负 Totals 鏁扮粍
+export function toTotalsList(result) {
+ const data = result && result.data;
+ if (!data) return [];
+ if (Array.isArray(data)) return data;
+ if (Array.isArray(data.list)) return data.list;
+ if (Array.isArray(data.rows)) return data.rows;
+ // 鍗曚釜缁熻瀵硅薄
+ return [data];
+}
+
+// 缁勮鏌ヨ鏉′欢锛屽彧浼犳湁鍊肩殑瀛楁
+// area: 鍦板尯鍚嶇О(涓枃)锛泂tartTime/endTime: yyyy-MM锛沘geGroup/sex/boodType/organName 涓虹瓫閫夌淮搴�
+export function buildTotalsQuery({ area, startTime, endTime, ageGroup, sex, boodType, organName } = {}) {
+ const q = {};
+ if (area) q.area = area;
+ if (startTime) q.startTime = startTime;
+ if (endTime) q.endTime = endTime;
+ if (ageGroup) q.ageGroup = ageGroup;
+ if (sex) q.sex = sex;
+ if (boodType) q.boodType = boodType;
+ if (organName) q.organName = organName;
+ return q;
+}
+
+// 鏈堜唤/鏃堕棿瀛楃涓茬粺涓�鎴� yyyy-MM
+export function toMonth(value) {
+ if (!value) return '';
+ const s = String(value);
+ const m = s.match(/(\d{4})-(\d{2})/);
+ return m ? `${m[1]}-${m[2]}` : s;
+}
+
+// 鏁板瓧瀹夊叏鍙栨暣
+export function toNum(v) {
+ const n = Number(v);
+ return Number.isFinite(n) ? n : 0;
+}
diff --git a/src/views/business/GetWitness/GetWitnessInfo.vue b/src/views/business/GetWitness/GetWitnessInfo.vue
index ea5578b..ccf0995 100644
--- a/src/views/business/GetWitness/GetWitnessInfo.vue
+++ b/src/views/business/GetWitness/GetWitnessInfo.vue
@@ -1759,8 +1759,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/OrganUtilization/OrganUtilizationInfo.vue b/src/views/business/OrganUtilization/OrganUtilizationInfo.vue
index 181ffa6..cc5957b 100644
--- a/src/views/business/OrganUtilization/OrganUtilizationInfo.vue
+++ b/src/views/business/OrganUtilization/OrganUtilizationInfo.vue
@@ -2073,8 +2073,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/affirm/affirmInfo.vue b/src/views/business/affirm/affirmInfo.vue
index 1bc030e..09db00e 100644
--- a/src/views/business/affirm/affirmInfo.vue
+++ b/src/views/business/affirm/affirmInfo.vue
@@ -1,20 +1,27 @@
<template>
<div class="confirmation-detail">
- <case-basic-info :case-id="caseId" :show-attachment="true":column="3" />
+ <case-basic-info :case-id="caseId" :show-attachment="true" :column="3" />
<el-card class="detail-card paper-card">
<!-- 鍩虹淇℃伅 -->
<div slot="header" class="clearfix">
<span class="detail-title">鎹愮尞纭淇℃伅</span>
-
</div>
<el-form :model="form" ref="form" label-width="120px" class="paper-form">
<div class="form-row">
- <el-form-item label="鍗忚皟鍛�1" prop="coordinatedusernameo" class="col-1-3">
+ <el-form-item
+ label="鍗忚皟鍛�1"
+ prop="coordinatedusernameo"
+ class="col-1-3"
+ >
<el-input v-model="form.coordinatedusernameo" />
</el-form-item>
- <el-form-item label="鍗忚皟鍛�2" prop="coordinatedusernamet" class="col-1-3">
+ <el-form-item
+ label="鍗忚皟鍛�2"
+ prop="coordinatedusernamet"
+ class="col-1-3"
+ >
<el-input v-model="form.coordinatedusernamet" />
</el-form-item>
<!-- <el-col :span="8">
@@ -49,7 +56,7 @@
</el-alert>
<div style="margin-top: 15px;">
<el-button
- style="float: right; margin-bottom: 5px;"
+ style="float: right; margin-bottom: 5px;"
type="primary"
size="mini"
icon="el-icon-plus"
@@ -144,7 +151,11 @@
</div>
<div class="form-row">
- <el-form-item label="瀹跺睘鎰忚澶囨敞" prop="relativeRemark" class="col-full">
+ <el-form-item
+ label="瀹跺睘鎰忚澶囨敞"
+ prop="relativeRemark"
+ class="col-full"
+ >
<el-input
type="textarea"
:rows="3"
@@ -618,9 +629,6 @@
},
getAttachmentsByType(type) {
- console.log(type);
- console.log(this.attachmentData[type]);
-
return this.attachmentData[type] || [];
},
@@ -689,7 +697,6 @@
uploader: "褰撳墠鐢ㄦ埛"
};
this.attachmentData[this.currentUploadType].push(newAttachment);
- console.log(this.attachmentData,'this.attachmentData');
this.updateAssessannexField();
this.$message.success("涓婁紶鎴愬姛");
@@ -1079,8 +1086,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
@@ -1117,10 +1122,18 @@
display: none;
}
-.paper-form >>> .col-1-3 { flex: 0 0 33.3333%; }
-.paper-form >>> .col-half { flex: 0 0 50%; }
-.paper-form >>> .col-2-3 { flex: 0 0 66.6666%; }
-.paper-form >>> .col-full { flex: 0 0 100%; }
+.paper-form >>> .col-1-3 {
+ flex: 0 0 33.3333%;
+}
+.paper-form >>> .col-half {
+ flex: 0 0 50%;
+}
+.paper-form >>> .col-2-3 {
+ flex: 0 0 66.6666%;
+}
+.paper-form >>> .col-full {
+ flex: 0 0 100%;
+}
.paper-form >>> .el-form-item__label {
flex: 0 0 150px;
@@ -1139,7 +1152,9 @@
line-height: 1.3;
white-space: nowrap;
}
-.paper-form >>> .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label:before {
+.paper-form
+ >>> .el-form-item.is-required:not(.is-no-asterisk)
+ > .el-form-item__label:before {
color: #f56c6c;
margin-right: 2px;
}
diff --git a/src/views/business/allocation/allocationInfo.vue b/src/views/business/allocation/allocationInfo.vue
index d714426..24fd22c 100644
--- a/src/views/business/allocation/allocationInfo.vue
+++ b/src/views/business/allocation/allocationInfo.vue
@@ -1885,8 +1885,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/assess/assessInfo.vue b/src/views/business/assess/assessInfo.vue
index bb6f49c..2a5d5a8 100644
--- a/src/views/business/assess/assessInfo.vue
+++ b/src/views/business/assess/assessInfo.vue
@@ -1558,8 +1558,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/course/ccss.vue b/src/views/business/course/ccss.vue
index d3442f8..05e160a 100644
--- a/src/views/business/course/ccss.vue
+++ b/src/views/business/course/ccss.vue
@@ -486,8 +486,6 @@
.register-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
/* 姣忎竴琛屼负 flex 琛岋紝鑷甫涓婅竟妗嗕繚璇佺綉鏍艰繛缁�� */
diff --git a/src/views/business/course/components/EthicalReviewStage.vue b/src/views/business/course/components/EthicalReviewStage.vue
index 768af02..7d340ee 100644
--- a/src/views/business/course/components/EthicalReviewStage.vue
+++ b/src/views/business/course/components/EthicalReviewStage.vue
@@ -580,7 +580,7 @@
// 浼︾悊瀹℃煡缁撹鏂囨湰锛堝彧璇诲睍绀猴級
expertConclusionLabel() {
- const map = { 0: "涓嶅悓鎰�", 1: "鍚屾剰", 2: "瀹℃煡涓�" };
+ const map = { 0: "涓嶅悓鎰�", 1: "鍚屾剰", 2: "瀹℃煡涓�", 3: "寮冩潈", 4: "淇敼鍚庡悓鎰�" };
return map[this.form.expertConclusion] || "-";
},
@@ -947,7 +947,9 @@
console.log(file, "file");
this.currentPreviewFile = {
- fileName: file,
+ // 绛惧悕闄勪欢 URL 閫氬父鏃犳墿灞曞悕锛堝 .../file-1787645145583锛夛紝
+ // 棰勮缁勪欢渚濊禆鏂囦欢鍚嶆墿灞曞悕璇嗗埆鏂囦欢绫诲瀷锛岃繖閲岃ˉ .png 璁╁叾鎸夊浘鐗囬瑙�
+ fileName: "鎵嬪啓绛惧悕.png",
fileUrl: file,
fileType: "png"
};
@@ -1038,7 +1040,7 @@
getReviewStatusFilter(status) {
const statusMap = {
"0": "info", // 寰呮帴鏀�
- "1": "warning", // 鏈帴鏀�
+ "1": "warning", // 宸插彂閫�
"2": "success", // 宸叉帴鏀�
"3": "danger", // 瓒呮椂
"4": "danger", // 涓
@@ -1050,7 +1052,7 @@
getReviewStatusText(status) {
const statusMap = {
"0": "寰呮帴鏀�",
- "1": "鏈帴鏀�",
+ "1": "宸插彂閫�",
"2": "宸叉帴鏀�",
"3": "瓒呮椂",
"4": "涓",
@@ -1062,18 +1064,22 @@
// 缁撹杩囨护鍣�
getConclusionFilter(conclusion) {
const conclusionMap = {
+ "0": "danger", // 涓嶅悓鎰�
"1": "success", // 鍚屾剰
"2": "warning", // 瀹℃煡涓�
- "0": "danger" // 涓嶅悓鎰�
+ "3": "info", // 寮冩潈
+ "4": "success" // 淇敼鍚庡悓鎰�
};
return conclusionMap[conclusion] || "info";
},
getConclusionText(conclusion) {
const conclusionMap = {
+ "0": "涓嶅悓鎰�",
"1": "鍚屾剰",
"2": "瀹℃煡涓�",
- "0": "涓嶅悓鎰�"
+ "3": "寮冩潈",
+ "4": "淇敼鍚庡悓鎰�"
};
return conclusionMap[conclusion] || "鏈煡";
},
diff --git a/src/views/business/course/index.vue b/src/views/business/course/index.vue
index b0aa20f..6064df5 100644
--- a/src/views/business/course/index.vue
+++ b/src/views/business/course/index.vue
@@ -374,9 +374,8 @@
flex-direction: column;
gap: 20px;
min-height: 0;
+ min-width: 0;
/* 閲嶈锛氬厑璁竑lex瀛愰」鍘嬬缉 */
- max-width: 1600px;
- /* 瓒呭灞忎笂闄愶細閬垮厤鎻忚堪鍒楄〃姣忓垪杩囧銆佸唴瀹圭█鐤忥紝鍙充晶鐣欏嚭閫傞噺绌虹櫧 */
}
.basic-info-section {
@@ -433,6 +432,8 @@
flex-direction: column;
min-height: 400px;
/* 鏈�灏忛珮搴� */
+ min-width: 0;
+ /* 閲嶈锛氬厑璁� flex 瀛愰」鏀剁缉 */
/* max-height: 1000px; */
/* 鏈�澶ч珮搴︼紝鍙牴鎹渶瑕佽皟鏁� */
overflow: hidden;
@@ -442,11 +443,21 @@
.stage-content-wrapper {
flex: 1;
overflow-y: auto;
+ overflow-x: auto;
/* 闃舵璇︽儏鍐呴儴鍙粴鍔� */
margin-top: 20px;
padding-right: 8px;
min-height: 0;
- /* 閲嶈 */
+ min-width: 0;
+ /* 閲嶈锛氬厑璁� flex 瀛愰」鏀剁缉锛屽惁鍒欏浐瀹氬垪瀹界殑琛ㄦ牸浼氭妸鍐呭鎾戝骞舵孩鍑� */
+}
+
+/* 绾︽潫鍚� Stage 瀛愮粍浠舵牴鑺傜偣瀹藉害锛岄厤鍚堜笂鏂� min-width:0锛�
+ 璁� el-table 鍦ㄨ嚜韬唴閮ㄥ嚭鐜版í鍚戞粴鍔ㄦ潯锛岃�屼笉鏄澶栧眰 overflow:hidden 瑁佹帀 */
+.stage-content-wrapper :deep(> div) {
+ width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
}
.stage-content-wrapper::-webkit-scrollbar {
diff --git a/src/views/business/decide/DecideInfo.vue b/src/views/business/decide/DecideInfo.vue
index 15d8996..0590feb 100644
--- a/src/views/business/decide/DecideInfo.vue
+++ b/src/views/business/decide/DecideInfo.vue
@@ -1387,8 +1387,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/ethicalReview/ethicalReviewInfo.vue b/src/views/business/ethicalReview/ethicalReviewInfo.vue
index 2e175aa..6321ef7 100644
--- a/src/views/business/ethicalReview/ethicalReviewInfo.vue
+++ b/src/views/business/ethicalReview/ethicalReviewInfo.vue
@@ -41,6 +41,8 @@
<el-option label="鍚屾剰" value="1" />
<el-option label="瀹℃煡涓�" value="2" />
<el-option label="涓嶅悓鎰�" value="0" />
+ <el-option label="寮冩潈" value="3" />
+ <el-option label="淇敼鍚庡悓鎰�" value="4" />
</el-select>
</el-form-item>
<el-form-item label="浼︾悊瀹℃煡缁撹鏃堕棿" prop="expertTime" class="col-1-3">
@@ -725,7 +727,7 @@
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="12">
<div class="history-stat-item">
- <div class="history-stat-label">鏈帴鏀舵暟閲�</div>
+ <div class="history-stat-label">宸插彂閫佹湭鎺ユ敹鏁伴噺</div>
<div class="history-stat-value">
{{ expertHistoryData.notAcceptCount || 0 }}
</div>
@@ -1333,7 +1335,9 @@
console.log(file, "file");
this.currentPreviewFile = {
- fileName: file,
+ // 绛惧悕闄勪欢 URL 閫氬父鏃犳墿灞曞悕锛堝 .../file-1787645145583锛夛紝
+ // 棰勮缁勪欢渚濊禆鏂囦欢鍚嶆墿灞曞悕璇嗗埆鏂囦欢绫诲瀷锛岃繖閲岃ˉ .png 璁╁叾鎸夊浘鐗囬瑙�
+ fileName: "鎵嬪啓绛惧悕.png",
fileUrl: file,
fileType: "png"
};
@@ -1423,8 +1427,8 @@
// 瀹℃煡鐘舵�佽繃婊ゅ櫒
getReviewStatusFilter(status) {
const statusMap = {
- "0": "info", // 寰呮帴鏀�
- "1": "warning", // 鏈帴鏀�
+ "0": "info", // 寰呭彂閫�
+ "1": "warning", // 宸插彂閫�
"2": "success", // 宸叉帴鏀�
"3": "danger", // 瓒呮椂
"4": "danger", // 涓
@@ -1435,8 +1439,8 @@
getReviewStatusText(status) {
const statusMap = {
- "0": "寰呮帴鏀�",
- "1": "鏈帴鏀�",
+ "0": "寰呭彂閫�",
+ "1": "宸插彂閫�",
"2": "宸叉帴鏀�",
"3": "瓒呮椂",
"4": "涓",
@@ -1448,18 +1452,22 @@
// 缁撹杩囨护鍣�
getConclusionFilter(conclusion) {
const conclusionMap = {
+ "0": "danger", // 涓嶅悓鎰�
"1": "success", // 鍚屾剰
"2": "warning", // 瀹℃煡涓�
- "0": "danger" // 涓嶅悓鎰�
+ "3": "info", // 寮冩潈
+ "4": "success" // 淇敼鍚庡悓鎰�
};
return conclusionMap[conclusion] || "info";
},
getConclusionText(conclusion) {
const conclusionMap = {
+ "0": "涓嶅悓鎰�",
"1": "鍚屾剰",
"2": "瀹℃煡涓�",
- "0": "涓嶅悓鎰�"
+ "3": "寮冩潈",
+ "4": "淇敼鍚庡悓鎰�"
};
return conclusionMap[conclusion] || "鏈煡";
},
@@ -2005,7 +2013,7 @@
deptName: expert.unitname || "",
title: expert.title || "",
donorno: expert.telephone || "",
- receiveStatus: "0", // 寰呮帴鏀�
+ receiveStatus: "0", // 寰呭彂閫�
expertconclusion: "",
expertopinion: "",
conclusionannex: "",
@@ -2204,6 +2212,7 @@
if (index != -1) {
this.form.ethicalreviewopinionsList[index].receiveStatus = "1"; // 宸叉帴鏀�
+ this.form.ethicalreviewopinionsList[index].expertconclusion = "2"; // 瀹℃煡涓�
this.form.ethicalreviewopinionsList[
index
].startTime = this.sendForm.startTime;
@@ -2750,8 +2759,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/ethicalReview/index.vue b/src/views/business/ethicalReview/index.vue
index 3033248..0428f61 100644
--- a/src/views/business/ethicalReview/index.vue
+++ b/src/views/business/ethicalReview/index.vue
@@ -86,12 +86,6 @@
>淇敼</el-button
> -->
<el-button
- type="warning"
- icon="el-icon-download"
- @click="handleExport"
- >瀵煎嚭</el-button
- >
- <el-button
type="info"
icon="el-icon-check"
:disabled="single"
@@ -121,12 +115,7 @@
prop="inpatientno"
width="120"
/>
- <el-table-column
- label="濮撳悕"
- align="center"
- prop="name"
- width="100"
- />
+ <el-table-column label="濮撳悕" align="center" prop="name" width="100" />
<el-table-column label="鎬у埆" align="center" prop="sex" width="80">
<template slot-scope="scope">
@@ -195,7 +184,7 @@
<el-table-column
label="鎿嶄綔"
align="center"
- width="180"
+ width="240"
fixed="right"
class-name="small-padding fixed-width"
>
@@ -222,6 +211,13 @@
:disabled="scope.row.status === '2'"
>缁撴潫</el-button
>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownload(scope.row)"
+ >涓嬭浇</el-button
+ >
</template>
</el-table-column>
</el-table>
@@ -239,13 +235,23 @@
</template>
<script>
-import { reviewinitiateBaseInfoList, ethicalreviewedit, } from "@/api/businessApi";
+import {
+ reviewinitiateBaseInfoList,
+ ethicalreviewedit,
+ ethicalReviewDownload
+} from "@/api/businessApi";
import Pagination from "@/components/Pagination";
+import { getToken } from "@/utils/auth";
export default {
name: "EthicsReviewList",
components: { Pagination },
- dicts: ["sys_user_sex", "ReviewForm_status",'sys_ethical','expert_Conclusion'],
+ dicts: [
+ "sys_user_sex",
+ "ReviewForm_status",
+ "sys_ethical",
+ "expert_Conclusion"
+ ],
data() {
return {
// 閬僵灞�
@@ -291,9 +297,14 @@
delete params.reviewTimeRange;
// 濡傛灉鏈夋椂闂磋寖鍥村弬鏁�
- if (this.queryParams.reviewTimeRange && this.queryParams.reviewTimeRange.length === 2) {
- params.expertTimeStart = this.queryParams.reviewTimeRange[0] + ' 00:00:00';
- params.expertTimeEnd = this.queryParams.reviewTimeRange[1] + ' 23:59:59';
+ if (
+ this.queryParams.reviewTimeRange &&
+ this.queryParams.reviewTimeRange.length === 2
+ ) {
+ params.expertTimeStart =
+ this.queryParams.reviewTimeRange[0] + " 00:00:00";
+ params.expertTimeEnd =
+ this.queryParams.reviewTimeRange[1] + " 23:59:59";
}
reviewinitiateBaseInfoList(params)
@@ -333,7 +344,7 @@
handleView(row) {
this.$router.push({
path: "/case/ethicalReviewInfo",
- query: { infoid: row.infoid,id:row.id }
+ query: { infoid: row.infoid, id: row.id }
});
},
// 鏂板鎸夐挳鎿嶄綔
@@ -345,12 +356,13 @@
const infoid = row.infoid;
this.$router.push({
path: "/case/ethicalReviewInfo",
- query: { infoid: infoid,id:row.id }
+ query: { infoid: infoid, id: row.id }
});
},
// 缁撴潫瀹℃煡鎿嶄綔
handleEndReview(row) {
- const selectedRow = row || (this.selectedRows.length === 1 ? this.selectedRows[0] : null);
+ const selectedRow =
+ row || (this.selectedRows.length === 1 ? this.selectedRows[0] : null);
if (!selectedRow) {
this.$message.warning("璇烽�夋嫨涓�鏉¤褰�");
return;
@@ -365,7 +377,7 @@
// 璋冪敤淇敼鎺ュ彛锛屽皢鐘舵�佹敼涓虹粨鏉燂紙status: '2'锛�
const updateData = {
infoid: selectedRow.infoid,
- status: '2',
+ status: "2",
// 鍙互淇濈暀鍘熸湁鏁版嵁
expertName: selectedRow.expertName,
expertConclusion: selectedRow.expertConclusion,
@@ -385,50 +397,41 @@
})
.catch(() => {});
},
- // 瀵煎嚭鎸夐挳鎿嶄綔
- handleExport() {
- const queryParams = { ...this.queryParams };
- delete queryParams.pageNum;
- delete queryParams.pageSize;
-
- // 澶勭悊鏃堕棿鑼冨洿鍙傛暟
- if (queryParams.reviewTimeRange && queryParams.reviewTimeRange.length === 2) {
- queryParams.expertTimeStart = queryParams.reviewTimeRange[0] + ' 00:00:00';
- queryParams.expertTimeEnd = queryParams.reviewTimeRange[1] + ' 23:59:59';
- }
- delete queryParams.reviewTimeRange;
-
- this.$confirm("鏄惁纭瀵煎嚭鎵�鏈変鸡鐞嗗鏌ユ暟鎹紵", "璀﹀憡", {
- confirmButtonText: "纭畾",
- cancelButtonText: "鍙栨秷",
- type: "warning"
- })
- .then(() => {
- this.loading = true;
- // return ethicalreviewexport(queryParams);
- })
- .then(response => {
- if (response.code === 200) {
- this.$message.success("瀵煎嚭鎴愬姛");
- // 澶勭悊瀵煎嚭鏂囦欢涓嬭浇
- if (response.data) {
- const blob = new Blob([response.data], { type: 'application/vnd.ms-excel' });
- const downloadElement = document.createElement('a');
- const href = window.URL.createObjectURL(blob);
- downloadElement.href = href;
- downloadElement.download = '浼︾悊瀹℃煡鍒楄〃.xlsx';
- document.body.appendChild(downloadElement);
- downloadElement.click();
- document.body.removeChild(downloadElement);
- window.URL.revokeObjectURL(href);
- }
- } else {
- this.$message.error(response.msg || "瀵煎嚭澶辫触");
+ // 涓嬭浇浼︾悊瀹℃煡妗f
+ handleDownload(row) {
+ ethicalReviewDownload({ nitiateId: row.id })
+ .then(res => {
+ if (res.code !== 200 || !res.data || !res.data.downloadUrl) {
+ this.$message.error(res.msg || "涓嬭浇澶辫触");
+ return;
}
- this.loading = false;
+ const fileUrl = res.data;
+ // 鎷兼帴涓嬭浇鍦板潃锛歰rigin + /dev-api(寮�鍙戜唬鐞嗗墠缂�) + /profile + downloadUrl锛�
+ // 鍚庣闈欐�佹枃浠跺疄闄呮寕鍦� /profile 涓嬶紝downloadUrl 灏戜簡杩欏眰鍓嶇紑
+ const urlBase = process.env.VUE_APP_BASE_API;
+ const curWWWPath = window.document.location.href;
+ const pos = curWWWPath.indexOf(window.document.location.pathname);
+ const fullUrl =
+ curWWWPath.substring(0, pos) + urlBase + "/profile" + fileUrl.downloadUrl;
+
+ fetch(fullUrl, {
+ headers: { Authorization: "Bearer " + getToken() }
+ })
+ .then(response => response.blob())
+ .then(blob => {
+ const url = window.URL.createObjectURL(new Blob([blob]));
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", fileUrl.downloadName);
+ document.body.appendChild(link);
+ link.click();
+ link.parentNode.removeChild(link);
+ window.URL.revokeObjectURL(url);
+ });
})
- .catch(() => {
- this.loading = false;
+ .catch(error => {
+ console.error("涓嬭浇妗f澶辫触:", error);
+ this.$message.error("涓嬭浇澶辫触");
});
},
// 鏃堕棿鏍煎紡鍖�
diff --git a/src/views/business/followupVisit/info.vue b/src/views/business/followupVisit/info.vue
index 62142e3..5303dd5 100644
--- a/src/views/business/followupVisit/info.vue
+++ b/src/views/business/followupVisit/info.vue
@@ -740,8 +740,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/maintain/maintainInfo.vue b/src/views/business/maintain/maintainInfo.vue
index 8dbb0ef..155204f 100644
--- a/src/views/business/maintain/maintainInfo.vue
+++ b/src/views/business/maintain/maintainInfo.vue
@@ -1375,8 +1375,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/business/transfer/TransportEdit.vue b/src/views/business/transfer/TransportEdit.vue
index 3dc9bac..3d5c020 100644
--- a/src/views/business/transfer/TransportEdit.vue
+++ b/src/views/business/transfer/TransportEdit.vue
@@ -839,8 +839,6 @@
.paper-form {
border-left: 1px solid #d0d3db;
background: #fff;
- /* 瓒呭灞忛檺鍒惰〃鍗曟渶澶у搴︼紝鍙充晶鐣欑櫧锛岄伩鍏嶅瓧娈靛垎甯冭繃寮� */
- max-width: 1400px;
}
.paper-form >>> .form-row {
diff --git a/src/views/statistics/case.vue b/src/views/statistics/case.vue
index 0b7f00b..8bc4386 100644
--- a/src/views/statistics/case.vue
+++ b/src/views/statistics/case.vue
@@ -35,16 +35,25 @@
</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-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="6" v-for="item in overviewData" :key="item.title">
+ <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 }">
@@ -71,7 +80,7 @@
type="text"
@click="toggleChartType('trend')"
>
- {{ chartTypes.trend === 'line' ? '鏌辩姸鍥�' : '鎶樼嚎鍥�' }}
+ {{ chartTypes.trend === "line" ? "鏌辩姸鍥�" : "鎶樼嚎鍥�" }}
</el-button>
</div>
<EChartsWrapper
@@ -117,15 +126,23 @@
border
stripe
>
- <el-table-column label="鍦板尯" prop="region" align="center" />
- <el-table-column label="鏈堜唤" prop="month" align="center" >
+ <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" >
+ <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"
@@ -135,13 +152,9 @@
<span>{{ row.completionRate }}%</span>
</template>
</el-table-column>
- <el-table-column label="妗堜緥鏄庣粏" align="center" >
+ <el-table-column label="妗堜緥鏄庣粏" align="center">
<template slot-scope="{ row }">
- <el-button
- type="text"
- size="mini"
- @click="viewCaseDetail(row)"
- >
+ <el-button type="text" size="mini" @click="viewCaseDetail(row)">
鏌ョ湅({{ row.caseCount || 0 }})
</el-button>
</template>
@@ -161,26 +174,21 @@
</el-card>
<!-- 妗堜緥鏄庣粏寮圭獥 -->
- <el-dialog
- title="妗堜緥鏄庣粏"
- :visible.sync="showDetail"
-
- append-to-body
- >
- <case-detail
- :query="detailQuery"
- @close="showDetail = false"
- />
+ <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 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',
+ name: "CaseStatistics",
components: {
EChartsWrapper,
CaseDetail
@@ -195,15 +203,15 @@
limit: 10
},
- // 鍦板尯閫夐」
+ // 鍦板尯閫夐」锛坴alue 鐩存帴浼犲湴鍖哄悕绉扮粰鎺ュ彛 area 瀛楁锛�
regionOptions: [
- { 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: "涓存矀鍦板尯" }
],
// 鍔犺浇鐘舵��
@@ -215,15 +223,32 @@
// 姒傝鏁版嵁
overviewData: [
- { title: '鎬绘綔鍦ㄦ崘鐚渚�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-flag', color: '#409EFF' },
- { title: '鎬诲畬鎴愭崘鐚暟閲�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-circle-check', color: '#67C23A' },
- { title: '骞冲潎瀹屾垚鐜�', value: '0%', desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-data', color: '#E6A23C' },
- { title: '娑夊強妗堜緥鏁�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-document', color: '#F56C6C' }
+ {
+ 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'
+ trend: "line"
},
// 鍥捐〃鏁版嵁
@@ -234,200 +259,157 @@
// 寮圭獥鎺у埗
showDetail: false,
- detailQuery: {},
-
- // 妯℃嫙鏁版嵁
- mockData: {
- table: [
- { id: 1, region: '闈掑矝鍦板尯', month: '2024-01', potentialCount: 15, completedCount: 8, completionRate: 53.3, caseCount: 5 },
- { id: 2, region: '闈掑矝鍦板尯', month: '2024-02', potentialCount: 18, completedCount: 10, completionRate: 55.6, caseCount: 6 },
- { id: 3, region: '鏃ョ収鍦板尯', month: '2024-01', potentialCount: 8, completedCount: 4, completionRate: 50.0, caseCount: 3 },
- { id: 4, region: '鏃ョ収鍦板尯', month: '2024-02', potentialCount: 10, completedCount: 6, completionRate: 60.0, caseCount: 4 },
- { id: 5, region: '娴庡崡鍦板尯', month: '2024-01', potentialCount: 20, completedCount: 12, completionRate: 60.0, caseCount: 7 },
- { id: 6, region: '娴庡崡鍦板尯', month: '2024-02', potentialCount: 22, completedCount: 14, completionRate: 63.6, caseCount: 8 }
- ],
- trend: [
- { month: '2024-01', potential: 55, completed: 31 },
- { month: '2024-02', potential: 64, completed: 39 },
- { month: '2024-03', potential: 58, completed: 35 },
- { month: '2024-04', potential: 62, completed: 40 },
- { month: '2024-05', potential: 68, completed: 45 },
- { month: '2024-06', potential: 72, completed: 50 }
- ],
- region: [
- { region: '闈掑矝鍦板尯', potential: 150, completed: 85 },
- { region: '鏃ョ収鍦板尯', potential: 80, completed: 45 },
- { region: '娴庡崡鍦板尯', potential: 200, completed: 120 },
- { region: '鐑熷彴鍦板尯', potential: 100, completed: 60 }
- ]
- }
+ 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 {
- // 妯℃嫙鎺ュ彛璋冪敤
- await new Promise(resolve => setTimeout(resolve, 500));
+ const q = this.buildQuery();
+ const [areaRes, trendRes, moonRes, totalsRes] = await Promise.all([
+ caseTrendArea(q),
+ caseTrend(q),
+ caseTrendMoon(q),
+ totals(q)
+ ]);
- // 澶勭悊琛ㄦ牸鏁版嵁
- let filteredData = [...this.mockData.table];
+ // 鍦板尯鍒嗗竷瀵规瘮
+ this.chartData.region = toTotalsList(areaRes).map(item => ({
+ region: item.area || item.region || "",
+ potential: toNum(item.potentialCount),
+ completed: toNum(item.completeCount)
+ }));
- if (this.query.region.length > 0) {
- filteredData = filteredData.filter(item =>
- this.query.region.includes(item.region.replace('鍦板尯', '').toLowerCase())
- );
- }
+ // 妗堜緥瓒嬪娍
+ this.chartData.trend = toTotalsList(trendRes).map(item => ({
+ month: toMonth(item.donateTime) || toMonth(item.month),
+ potential: toNum(item.potentialCount),
+ completed: toNum(item.completeCount)
+ }));
- if (this.query.monthRange && this.query.monthRange.length === 2) {
- const [start, end] = this.query.monthRange;
- filteredData = filteredData.filter(item =>
- item.month >= start && item.month <= end
- );
- }
+ // 鏄庣粏琛紙鍦板尯 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.tableData = filteredData;
- this.total = filteredData.length;
-
- // 鏇存柊姒傝鏁版嵁
- this.updateOverviewData(filteredData);
-
- // 鏇存柊鍥捐〃鏁版嵁
- this.chartData.trend = [...this.mockData.trend];
- this.chartData.region = [...this.mockData.region];
-
+ this.updateOverviewData(toTotalsList(totalsRes));
} catch (error) {
- console.error('鍔犺浇鏁版嵁澶辫触:', error);
- this.$message.error('鏁版嵁鍔犺浇澶辫触');
+ console.error("鍔犺浇鏁版嵁澶辫触:", error);
+ this.$message.error("鏁版嵁鍔犺浇澶辫触");
} finally {
this.loading = false;
}
},
- // 鏇存柊姒傝鏁版嵁
- updateOverviewData(data) {
- if (data.length === 0) return;
-
- const totalPotential = data.reduce((sum, item) => sum + item.potentialCount, 0);
- const totalCompleted = data.reduce((sum, item) => sum + item.completedCount, 0);
- const avgRate = data.length > 0
- ? (data.reduce((sum, item) => sum + item.completionRate, 0) / data.length).toFixed(1)
- : 0;
- const totalCases = data.reduce((sum, item) => sum + (item.caseCount || 0), 0);
+ // 鏇存柊姒傝鏁版嵁锛堟�绘綔鍦�/鎬诲畬鎴愭崘鐚暟鏉ヨ嚜 /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}%`;
- this.overviewData[3].value = totalCases;
},
// 鑾峰彇瓒嬪娍鍥捐〃閰嶇疆
getTrendChartOptions() {
- const isLine = this.chartTypes.trend === 'line';
+ 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: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- legend: {
- data: ['娼滃湪鎹愮尞鏁�', '瀹屾垚鎹愮尞鏁�']
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: this.chartData.trend.map(item => this.formatMonth(item.month))
- },
- yAxis: {
- type: 'value',
- name: '鏁伴噺(涓�)'
- },
- series: [
- {
- name: '娼滃湪鎹愮尞鏁�',
- type: isLine ? 'line' : 'bar',
- data: this.chartData.trend.map(item => item.potential),
- itemStyle: { color: '#409EFF' },
- smooth: isLine,
- lineStyle: isLine ? { width: 3 } : {}
- },
- {
- name: '瀹屾垚鎹愮尞鏁�',
- type: isLine ? 'line' : 'bar',
- data: this.chartData.trend.map(item => item.completed),
- itemStyle: { color: '#67C23A' },
- smooth: isLine,
- lineStyle: isLine ? { width: 3 } : {}
- }
- ]
+ tooltip: axisTooltip(),
+ legend: legend(["娼滃湪鎹愮尞鏁�", "瀹屾垚鎹愮尞鏁�"]),
+ grid: grid(),
+ xAxis: xAxis(xData),
+ yAxis: yAxis(),
+ series
};
},
// 鑾峰彇鍦板尯鍥捐〃閰嶇疆
getRegionChartOptions() {
return {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- legend: {
- data: ['娼滃湪鎹愮尞鏁�', '瀹屾垚鎹愮尞鏁�']
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: this.chartData.region.map(item => item.region)
- },
- yAxis: {
- type: 'value',
- name: '鏁伴噺(涓�)'
- },
+ tooltip: axisTooltip(),
+ legend: legend(["娼滃湪鎹愮尞鏁�", "瀹屾垚鎹愮尞鏁�"]),
+ grid: grid(),
+ xAxis: xAxis(this.chartData.region.map(item => item.region)),
+ yAxis: yAxis(),
series: [
- {
- name: '娼滃湪鎹愮尞鏁�',
- type: 'bar',
- data: this.chartData.region.map(item => item.potential),
- itemStyle: { color: '#409EFF' }
- },
- {
- name: '瀹屾垚鎹愮尞鏁�',
- type: 'bar',
- data: this.chartData.region.map(item => item.completed),
- itemStyle: { color: '#67C23A' }
- }
+ 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 '#67C23A';
- if (percentage >= 50) return '#E6A23C';
- return '#F56C6C';
+ if (percentage >= 60) return COLORS.green;
+ if (percentage >= 50) return COLORS.amber;
+ return COLORS.coral;
},
// 鏍煎紡鍖栨湀浠芥樉绀�
formatMonth(month) {
- if (!month) return '';
- return month.replace('-', '骞�') + '鏈�';
+ if (!month) return "";
+ return month.replace("-", "骞�") + "鏈�";
},
// 澶勭悊鏌ヨ
@@ -456,13 +438,12 @@
// 澶勭悊瀵煎嚭
handleExport() {
- this.$message.info('瀵煎嚭鍔熻兘寮�鍙戜腑...');
- // 杩欓噷鍙互璋冪敤瀵煎嚭鎺ュ彛
+ this.$message.info("瀵煎嚭鍔熻兘寮�鍙戜腑...");
},
// 鍒囨崲鍥捐〃绫诲瀷
toggleChartType(type) {
- this.chartTypes[type] = this.chartTypes[type] === 'line' ? 'bar' : 'line';
+ this.chartTypes[type] = this.chartTypes[type] === "line" ? "bar" : "line";
},
// 鏌ョ湅妗堜緥璇︽儏
@@ -537,7 +518,7 @@
.overview-desc {
font-size: 12px;
- color: #C0C4CC;
+ color: #c0c4cc;
}
.chart-row {
diff --git a/src/views/statistics/components/CaseDetail.vue b/src/views/statistics/components/CaseDetail.vue
index 1d2d5d2..0d173bf 100644
--- a/src/views/statistics/components/CaseDetail.vue
+++ b/src/views/statistics/components/CaseDetail.vue
@@ -8,27 +8,25 @@
border
stripe
>
- <el-table-column label="妗堜緥缂栧彿" prop="caseNo" align="center" />
- <el-table-column label="鎹愮尞鑰呭鍚�" prop="donorName" align="center" />
- <el-table-column label="浣忛櫌鍙�" prop="inpatientNo" align="center" />
- <el-table-column label="妗堜緥鐘舵��" prop="status" align="center" >
+ <el-table-column label="妗堜緥缂栧彿" prop="caseNo" align="center" />
+ <el-table-column label="鎹愮尞鑰呭鍚�" prop="name" align="center" />
+ <el-table-column label="浣忛櫌鍙�" prop="inpatientNo" align="center" />
+ <el-table-column label="妗堜緥鐘舵��" prop="workflow" align="center">
<template slot-scope="{ row }">
- <el-tag :type="getStatusTag(row.status)" size="small">
- {{ getStatusText(row.status) }}
+ <el-tag :type="getStatusTag(row.workflow)" size="small">
+ {{ getStatusText(row.workflow) }}
</el-tag>
</template>
</el-table-column>
- <el-table-column label="鎹愮尞鍣ㄥ畼" prop="organs" align="center" >
+ <el-table-column label="鎹愮尞鍣ㄥ畼" prop="organName" align="center">
<template slot-scope="{ row }">
- <span v-if="row.organs && row.organs.length > 0">
- {{ row.organs.join('銆�') }}
- </span>
+ <span v-if="row.organName">{{ row.organName }}</span>
<span v-else>-</span>
</template>
</el-table-column>
- <el-table-column label="鎹愮尞鏃堕棿" prop="donationTime" align="center" />
- <el-table-column label="姝讳骸鍘熷洜" prop="deathCause" align="center" />
- <el-table-column label="鍖婚櫌" prop="hospital" align="center" width="200" />
+ <el-table-column label="妗堜緥鏃堕棿" prop="donatetime" align="center" />
+ <el-table-column label="姝讳骸鍘熷洜" prop="deathReason" align="center" />
+ <el-table-column label="鍖婚櫌" prop="treatmentHospitalName" align="center" width="200" />
</el-table>
<div style="text-align: center; margin-top: 20px;">
@@ -38,6 +36,9 @@
</template>
<script>
+import { trendList } from '@/api/businessApi';
+import { toTotalsList } from '@/utils/totals';
+
export default {
name: 'CaseDetail',
props: {
@@ -49,21 +50,64 @@
data() {
return {
loading: false,
- caseList: [
- { caseNo: 'C202401001', donorName: '寮犱笁', inpatientNo: 'ZY202401001', status: 'completed', organs: ['鑲濊剰', '鑲捐剰'], donationTime: '2024-01-15 10:30', deathCause: '鑴戝浼�', hospital: '闈掑矝澶у闄勫睘鍖婚櫌' },
- { caseNo: 'C202401002', donorName: '鏉庡洓', inpatientNo: 'ZY202401002', status: 'completed', organs: ['蹇冭剰', '鐪艰鑶�'], donationTime: '2024-01-20 14:20', deathCause: '蹇冩悘楠ゅ仠', hospital: '闈掑矝甯傜珛鍖婚櫌' },
- { caseNo: 'C202401003', donorName: '鐜嬩簲', inpatientNo: 'ZY202401003', status: 'potential', organs: [], donationTime: null, deathCause: '鑴戣绠℃剰澶�', hospital: '闈掑矝甯備腑蹇冨尰闄�' }
- ]
+ caseList: []
};
},
+ created() {
+ this.loadList();
+ },
+ watch: {
+ query: {
+ handler() {
+ this.loadList();
+ },
+ deep: true
+ }
+ },
methods: {
- getStatusTag(status) {
- const map = { completed: 'success', potential: 'warning', abandoned: 'danger' };
- return map[status] || 'info';
+ // 鍔犺浇妗堜緥鏄庣粏锛�/opo/totals/trendList
+ async loadList() {
+ this.loading = true;
+ try {
+ const params = {};
+ if (this.query.region) params.area = this.query.region;
+ if (this.query.month) {
+ params.startTime = `${this.query.month}-01`;
+ params.endTime = this.endOfMonth(this.query.month);
+ }
+ const res = await trendList(params);
+ this.caseList = toTotalsList(res);
+ } catch (error) {
+ console.error('鍔犺浇妗堜緥鏄庣粏澶辫触:', error);
+ this.$message.error('妗堜緥鏄庣粏鍔犺浇澶辫触');
+ } finally {
+ this.loading = false;
+ }
},
- getStatusText(status) {
- const map = { completed: '宸插畬鎴�', potential: '娼滃湪鎹愮尞', abandoned: '鏀惧純鎹愮尞' };
- return map[status] || '鏈煡';
+ // 鏈堜唤(yyyy-MM) -> 褰撴湀鏈�鍚庝竴澶�(yyyy-MM-dd)
+ endOfMonth(month) {
+ const [y, m] = String(month).split('-').map(Number);
+ const lastDay = new Date(y, m, 0).getDate();
+ return `${month}-${String(lastDay).padStart(2, '0')}`;
+ },
+ // 宸ヤ綔娴佺姸鎬佹枃妗�
+ getStatusText(workflow) {
+ const map = {
+ 1: '渚涜�呯淮鎶�',
+ 2: '鎹愮尞鎰忔効',
+ 3: '鍖诲璇勪及',
+ 4: '姝讳骸鍒ゅ畾',
+ 5: '浼︾悊瀹℃煡',
+ 6: '鍣ㄥ畼鍒嗛厤',
+ 7: '鍣ㄥ畼鍒╃敤',
+ 99: '鎹愮尞瀹屾垚'
+ };
+ return map[workflow] || '鏈煡';
+ },
+ getStatusTag(workflow) {
+ if (workflow === 99) return 'success';
+ if (workflow) return 'warning';
+ return 'info';
}
}
};
diff --git a/src/views/statistics/donor.vue b/src/views/statistics/donor.vue
index 192e13c..a2e8bb8 100644
--- a/src/views/statistics/donor.vue
+++ b/src/views/statistics/donor.vue
@@ -67,23 +67,27 @@
<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>
@@ -94,71 +98,54 @@
<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',
@@ -187,13 +174,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'
},
@@ -206,19 +193,6 @@
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: '鎬у埆',
@@ -258,115 +232,109 @@
// 鍥捐〃閰嶇疆
ageChartOptions: {},
genderBloodChartOptions: {},
- categoryChartOptions: {},
- deathChartOptions: {},
+
+ // 鎺ュ彛杩斿洖鏁版嵁
+ sexData: [],
+ bloodData: [],
+
+ // 鏃犳帴鍙e浘琛ㄧ殑妯℃嫙鏁版嵁锛堜粎 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 }
+ ],
+
+ // 鎹愮尞鑰呰缁嗕俊鎭ā鎷熸暟鎹紙鏃犲悗绔帴鍙o紝浠� 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('鏁版嵁鍔犺浇澶辫触');
@@ -376,220 +344,164 @@
},
// 鏇存柊姒傝鏁版嵁
- 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() {
- // 骞撮緞鍒嗗竷鍥�
+ // 骞撮緞鍒嗗竷鍥撅紙宸︾敺鍙冲コ锛屾竻鐖藉彂鏁i噾瀛楀锛�
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 = {
+ // 鎹愮尞鑰呰缁嗕俊鎭紙鏃犳帴鍙o紝妯℃嫙鏁版嵁琛ュ叏 + 杩囨护鍒嗛〉锛�
+ 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';
},
// 澶勭悊鏌ヨ
@@ -628,14 +540,6 @@
// 澶勭悊瀵煎嚭
handleExport() {
this.$message.info('瀵煎嚭鍔熻兘寮�鍙戜腑...');
- },
-
- // 鏌ョ湅鎹愮尞鑰呰鎯�
- viewDonorDetail(row) {
- this.$router.push({
- path: '/donor/detail',
- query: { id: row.id }
- });
}
}
};
diff --git a/src/views/statistics/index.vue b/src/views/statistics/index.vue
index 65545dd..8dfc7fe 100644
--- a/src/views/statistics/index.vue
+++ b/src/views/statistics/index.vue
@@ -1,6 +1,21 @@
<!-- src/views/statistics/index.vue -->
<template>
<div class="statistics-home">
+ <el-card class="mode-card" shadow="never">
+ <div class="mode-bar">
+ <span class="mode-label">缁熻鏁版嵁婧愶細</span>
+ <el-switch
+ v-model="useMock"
+ active-text="妯℃嫙鏁版嵁"
+ inactive-text="鐪熷疄鎺ュ彛"
+ active-color="#E6A23C"
+ inactive-color="#409EFF"
+ @change="handleModeChange"
+ />
+ <span class="mode-tip">鍒囨崲鍚庤繘鍏ュ悇缁熻椤甸潰鐢熸晥锛堟ā鎷熸暟鎹敤浜庢紨绀猴紝鐪熷疄鎺ュ彛璇诲彇鍚庣锛�</span>
+ </div>
+ </el-card>
+
<el-row :gutter="20">
<el-col :span="6" v-for="item in menuList" :key="item.path">
<el-card
@@ -24,38 +39,41 @@
</template>
<script>
+import { isStatisticsMock, setStatisticsMock } from '@/utils/statisticsMode';
+
export default {
name: 'StatisticsHome',
data() {
return {
+ useMock: isStatisticsMock(),
menuList: [
{
title: '妗堜緥缁熻',
desc: '鍦板尯銆佹湀浠界淮搴︾殑妗堜緥缁熻鍒嗘瀽',
icon: 'el-icon-s-data',
color: '#409EFF',
- path: '/statistics/case'
+ path: '/statics/caseStatistics'
},
{
title: '鎹愮尞鍣ㄥ畼缁熻',
desc: '鍣ㄥ畼鑾峰彇銆佺Щ妞嶆暟閲忕粺璁�',
icon: 'el-icon-s-claim',
color: '#67C23A',
- path: '/statistics/organ'
+ path: '/statics/organstatistics'
},
{
title: '鑾峰彇鐜囩粺璁�',
desc: '鎹愮尞鑾峰彇鐜囥�佸櫒瀹樺純鐢ㄧ巼鍒嗘瀽',
icon: 'el-icon-s-flag',
color: '#E6A23C',
- path: '/statistics/rate'
+ path: '/statics/statisticsStatistics'
},
{
title: '鎹愮尞鎰忔効缁熻',
desc: '鎹愮尞鎰忔効瓒嬪娍涓庡垎甯冨垎鏋�',
icon: 'el-icon-star-on',
color: '#F56C6C',
- path: '/statistics/willingness'
+ path: '/statics/donorstatistics'
},
{
title: '鎹愮尞鑰呭垎鏋�',
@@ -75,6 +93,9 @@
};
},
methods: {
+ handleModeChange(val) {
+ setStatisticsMock(val);
+ },
goToPage(path) {
this.$router.push(path);
}
@@ -87,6 +108,27 @@
padding: 20px;
}
+.mode-card {
+ margin-bottom: 20px;
+}
+
+.mode-bar {
+ display: flex;
+ align-items: center;
+}
+
+.mode-label {
+ font-size: 14px;
+ color: #303133;
+ margin-right: 10px;
+}
+
+.mode-tip {
+ margin-left: 16px;
+ font-size: 12px;
+ color: #909399;
+}
+
.stat-card {
cursor: pointer;
margin-bottom: 20px;
diff --git a/src/views/statistics/organ.vue b/src/views/statistics/organ.vue
index bff9883..9c420ae 100644
--- a/src/views/statistics/organ.vue
+++ b/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,
diff --git a/src/views/statistics/rate.vue b/src/views/statistics/rate.vue
index 58c3edc..9eeb6d3 100644
--- a/src/views/statistics/rate.vue
+++ b/src/views/statistics/rate.vue
@@ -53,7 +53,7 @@
<el-col :span="12">
<el-card>
<div slot="header" class="clearfix">
- <span>寮冪敤鐜囨瀯鎴愬垎鏋�</span>
+ <span>鍦板尯鑾峰彇/寮冪敤鐜囧姣�</span>
</div>
<EChartsWrapper
:id="'abandon-chart'"
@@ -104,6 +104,18 @@
<el-table-column label="鍣ㄥ畼鎬绘暟" prop="totalOrgans" align="center" />
<el-table-column label="绉绘鎬绘暟" prop="totalTransplants" 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-card>
</div>
</template>
@@ -111,6 +123,10 @@
<script>
import FilterPanel from '@/components/charts/FilterPanel.vue';
import EChartsWrapper from '@/components/charts/EChartsWrapper.vue';
+import { organ, organMoon, caseTrendMoon } from '@/api/businessApi';
+import { toTotalsList, toMonth, toNum } from '@/utils/totals';
+import statisticsMode from '@/utils/statisticsMode';
+import { COLORS, axisTooltip, grid, xAxis, legend, barSeries, lineSeries } from '@/utils/chartStyle';
export default {
name: 'RateStatistics',
@@ -136,13 +152,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'
},
@@ -166,25 +182,25 @@
{
key: 'acquisition',
title: '鎹愮尞鑾峰彇鐜�',
- value: '85.5%',
+ value: '0%',
trendIcon: 'el-icon-top',
- trendText: '鈫�2.7%',
+ trendText: '鈥�',
trendClass: 'up'
},
{
key: 'avg',
title: '骞冲潎鍣ㄥ畼鏁�',
- value: '3.2',
+ value: '0',
trendIcon: 'el-icon-top',
- trendText: '鈫�0.2',
+ trendText: '鈥�',
trendClass: 'up'
},
{
key: 'abandon',
title: '鍣ㄥ畼寮冪敤鐜�',
- value: '12.3%',
+ value: '0%',
trendIcon: 'el-icon-bottom',
- trendText: '鈫�2.7%',
+ trendText: '鈥�',
trendClass: 'down'
}
],
@@ -193,69 +209,150 @@
trendChartOptions: {},
abandonChartOptions: {},
+ // 鍥捐〃鏁版嵁
+ trendData: [],
+ regionCompareData: [],
+
// 琛ㄦ牸鏁版嵁
tableData: [],
- total: 0,
-
- // 妯℃嫙鏁版嵁
- mockData: {
- table: [
- { id: 1, year: '2024', region: '闈掑矝鍦板尯', acquisitionRate: 85.5, avgOrgans: 3.2, abandonRate: 12.3, totalDonation: 280, totalOrgans: 896, totalTransplants: 765 },
- { id: 2, year: '2024', region: '鏃ョ収鍦板尯', acquisitionRate: 80.3, avgOrgans: 2.9, abandonRate: 15.8, totalDonation: 140, totalOrgans: 406, totalTransplants: 325 },
- { id: 3, year: '2024', region: '娴庡崡鍦板尯', acquisitionRate: 89.1, avgOrgans: 3.4, abandonRate: 9.5, totalDonation: 320, totalOrgans: 1088, totalTransplants: 970 },
- { id: 4, year: '2023', region: '闈掑矝鍦板尯', acquisitionRate: 82.8, avgOrgans: 3.0, abandonRate: 15.0, totalDonation: 250, totalOrgans: 750, totalTransplants: 620 },
- { id: 5, year: '2023', region: '鏃ョ収鍦板尯', acquisitionRate: 78.5, avgOrgans: 2.7, abandonRate: 18.2, totalDonation: 120, totalOrgans: 324, totalTransplants: 252 },
- { id: 6, year: '2023', region: '娴庡崡鍦板尯', acquisitionRate: 85.2, avgOrgans: 3.1, abandonRate: 12.5, totalDonation: 280, totalOrgans: 868, totalTransplants: 740 }
- ],
- trend: [
- { year: '2020', acquisition: 78.5, avgOrgans: 2.8, abandon: 18.5 },
- { year: '2021', acquisition: 81.2, avgOrgans: 3.0, abandon: 16.3 },
- { year: '2022', acquisition: 84.6, avgOrgans: 3.2, abandon: 13.8 },
- { year: '2023', acquisition: 87.3, avgOrgans: 3.4, abandon: 11.2 },
- { year: '2024', acquisition: 89.1, avgOrgans: 3.5, abandon: 9.5 }
- ],
- abandon: [
- { name: '鍣ㄥ畼璐ㄩ噺涓嶅悎鏍�', value: 45 },
- { name: '渚涗綋鐤剧梾', value: 25 },
- { name: '杩愯緭闂', value: 15 },
- { name: '鍙椾綋鍖归厤闂', value: 10 },
- { name: '鍏朵粬鍘熷洜', value: 5 }
- ]
- }
+ total: 0
};
},
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];
+ }
+ return q;
+ },
+
// 鍔犺浇鏁版嵁
async loadData() {
this.loading = true;
try {
- await new Promise(resolve => setTimeout(resolve, 500));
+ const q = this.buildQuery();
+ const [organRes, moonRes, caseMoonRes] = await Promise.all([
+ organ(q),
+ organMoon(q),
+ caseTrendMoon(q)
+ ]);
- // 澶勭悊琛ㄦ牸鏁版嵁
- let filteredData = [...this.mockData.table];
+ // 鍣ㄥ畼鎸夋湀瓒嬪娍锛氳幏鍙栫巼(绉绘/鑾峰彇)銆佸純鐢ㄧ巼
+ this.trendData = toTotalsList(moonRes)
+ .map(item => {
+ const get = toNum(item.getCount);
+ const use = toNum(item.useCount);
+ return {
+ month: toMonth(item.donateTime) || toMonth(item.month),
+ acquisition: get > 0 ? Number(((use / get) * 100).toFixed(1)) : 0,
+ abandon: get > 0 ? Number((((get - use) / get) * 100).toFixed(1)) : 0
+ };
+ })
+ .sort((a, b) => String(a.month).localeCompare(String(b.month)));
- if (this.query.region.length > 0) {
- filteredData = filteredData.filter(item =>
- this.query.region.includes(item.region.replace('鍦板尯', '').toLowerCase())
- );
- }
+ // 鎸� 骞翠唤|鍦板尯 姹囨�诲櫒瀹樻暟鎹�
+ const organByKey = {};
+ toTotalsList(organRes).forEach(item => {
+ const year = String(item.donateTime || item.year || '').slice(0, 4);
+ const region = item.area || item.region || '';
+ const key = `${year}|${region}`;
+ if (!organByKey[key]) organByKey[key] = { year, region, organs: 0, transplants: 0 };
+ organByKey[key].organs += toNum(item.getCount);
+ organByKey[key].transplants += toNum(item.useCount);
+ });
- 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)
- );
- }
+ // 鎸� 骞翠唤|鍦板尯 姹囨�绘渚嬫暟鎹紙鎹愮尞鎬绘暟銆佹綔鍦ㄦ渚嬶級
+ const caseByKey = {};
+ toTotalsList(caseMoonRes).forEach(item => {
+ const month = toMonth(item.donateTime) || toMonth(item.month);
+ const year = String(month).slice(0, 4);
+ const region = item.area || item.region || '';
+ const key = `${year}|${region}`;
+ if (!caseByKey[key]) caseByKey[key] = { year, region, potential: 0, complete: 0 };
+ caseByKey[key].potential += toNum(item.potentialCount);
+ caseByKey[key].complete += toNum(item.completeCount);
+ });
- this.tableData = filteredData;
- this.total = filteredData.length;
+ // 鍚堝苟 骞翠唤|鍦板尯 鏄庣粏
+ const keys = new Set([...Object.keys(organByKey), ...Object.keys(caseByKey)]);
+ const rows = [];
+ keys.forEach(key => {
+ const o = organByKey[key] || {};
+ const c = caseByKey[key] || {};
+ const year = o.year || c.year || '';
+ const region = o.region || c.region || '';
+ const organs = o.organs || 0;
+ const transplants = o.transplants || 0;
+ const potential = c.potential || 0;
+ const complete = c.complete || 0;
- // 鏇存柊鍥捐〃
+ rows.push({
+ year,
+ region,
+ acquisitionRate: potential > 0 ? Number(((complete / potential) * 100).toFixed(1)) : 0,
+ avgOrgans: complete > 0 ? Number((organs / complete).toFixed(1)) : 0,
+ abandonRate: organs > 0 ? Number((((organs - transplants) / organs) * 100).toFixed(1)) : 0,
+ totalDonation: complete,
+ totalOrgans: organs,
+ totalTransplants: transplants
+ });
+ });
+ rows.sort((a, b) => String(a.year + a.region).localeCompare(String(b.year + b.region)));
+
+ this.total = rows.length;
+ const start = (this.query.page - 1) * this.query.limit;
+ this.tableData = rows.slice(start, start + this.query.limit);
+
+ // 鍦板尯缁村害鐨勮幏鍙栫巼/寮冪敤鐜囷紝鐢ㄦ綔鍦�/瀹屾垚銆佸櫒瀹�/绉绘鍘熷鏁版嵁姹囨��
+ const regionCompare = {};
+ toTotalsList(caseMoonRes).forEach(item => {
+ const region = item.area || item.region || '';
+ if (!regionCompare[region]) regionCompare[region] = { region, potential: 0, complete: 0 };
+ regionCompare[region].potential += toNum(item.potentialCount);
+ regionCompare[region].complete += toNum(item.completeCount);
+ });
+ const organRegion = {};
+ toTotalsList(organRes).forEach(item => {
+ const region = item.area || item.region || '';
+ if (!organRegion[region]) organRegion[region] = { organs: 0, transplants: 0 };
+ organRegion[region].organs += toNum(item.getCount);
+ organRegion[region].transplants += toNum(item.useCount);
+ });
+ const regionNames = new Set([...Object.keys(regionCompare), ...Object.keys(organRegion)]);
+ this.regionCompareData = [...regionNames].map(region => {
+ const c = regionCompare[region] || {};
+ const o = organRegion[region] || {};
+ const potential = c.potential || 0;
+ const complete = c.complete || 0;
+ const organs = o.organs || 0;
+ const transplants = o.transplants || 0;
+ return {
+ region,
+ acquisition: potential > 0 ? Number(((complete / potential) * 100).toFixed(1)) : 0,
+ abandon: organs > 0 ? Number((((organs - transplants) / organs) * 100).toFixed(1)) : 0
+ };
+ });
+
+ const totals = {
+ totalPotential: Object.values(caseByKey).reduce((s, c) => s + c.potential, 0),
+ totalDonation: Object.values(caseByKey).reduce((s, c) => s + c.complete, 0),
+ totalOrgans: Object.values(organByKey).reduce((s, o) => s + o.organs, 0),
+ totalTransplants: Object.values(organByKey).reduce((s, o) => s + o.transplants, 0)
+ };
+
+ this.updateOverviewData(totals);
this.updateCharts();
-
} catch (error) {
console.error('鍔犺浇鏁版嵁澶辫触:', error);
this.$message.error('鏁版嵁鍔犺浇澶辫触');
@@ -264,157 +361,107 @@
}
},
+ // 鏇存柊浠〃鐩�
+ updateOverviewData(totals) {
+ const { totalPotential, totalDonation, totalOrgans, totalTransplants } = totals;
+
+ const acquisitionRate = totalPotential > 0 ? Number(((totalDonation / totalPotential) * 100).toFixed(1)) : 0;
+ const avgOrgans = totalDonation > 0 ? Number((totalOrgans / totalDonation).toFixed(1)) : 0;
+ const abandonRate = totalOrgans > 0 ? Number((((totalOrgans - totalTransplants) / totalOrgans) * 100).toFixed(1)) : 0;
+
+ this.dashboardData[0].value = `${acquisitionRate}%`;
+ this.dashboardData[1].value = `${avgOrgans}`;
+ this.dashboardData[2].value = `${abandonRate}%`;
+ this.dashboardData[0].trendText = acquisitionRate >= 80 ? '鈫戣揪鏍�' : '鍋忎綆';
+ this.dashboardData[2].trendText = abandonRate <= 15 ? '鈫撹揪鏍�' : '鍋忛珮';
+ },
+
// 鏇存柊鍥捐〃
updateCharts() {
- // 瓒嬪娍鍥�
+ // 鑾峰彇鐜囪秼鍔垮浘
this.trendChartOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- label: {
- backgroundColor: '#6a7985'
- }
- }
- },
- legend: {
- data: ['鎹愮尞鑾峰彇鐜�', '骞冲潎鍣ㄥ畼鏁�', '鍣ㄥ畼寮冪敤鐜�']
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
+ tooltip: axisTooltip(),
+ legend: legend(['鍣ㄥ畼鍒╃敤鐜�', '鍣ㄥ畼寮冪敤鐜�']),
+ grid: grid(),
xAxis: {
type: 'category',
boundaryGap: false,
- data: this.mockData.trend.map(item => item.year + '骞�')
+ data: this.trendData.map(item => item.month),
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { color: '#8a919f', fontSize: 12 }
},
yAxis: {
type: 'value',
- axisLabel: {
- formatter: '{value}'
- }
+ axisLabel: { color: '#8a919f', fontSize: 12, formatter: '{value}%' },
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f1f5' } }
},
series: [
- {
- name: '鎹愮尞鑾峰彇鐜�',
- type: 'line',
- data: this.mockData.trend.map(item => item.acquisition),
- itemStyle: { color: '#409EFF' },
- smooth: true
- },
- {
- name: '骞冲潎鍣ㄥ畼鏁�',
- type: 'line',
- data: this.mockData.trend.map(item => item.avgOrgans * 20), // 缂╂斁鏄剧ず
- itemStyle: { color: '#67C23A' },
- smooth: true
- },
- {
- name: '鍣ㄥ畼寮冪敤鐜�',
- type: 'line',
- data: this.mockData.trend.map(item => item.abandon),
- itemStyle: { color: '#F56C6C' },
- smooth: true
- }
+ lineSeries('鍣ㄥ畼鍒╃敤鐜�', this.trendData.map(item => item.acquisition), COLORS.blue, { area: true }),
+ lineSeries('鍣ㄥ畼寮冪敤鐜�', this.trendData.map(item => item.abandon), COLORS.coral)
]
};
- // 寮冪敤鐜囨瀯鎴愬浘
+ // 鍦板尯鑾峰彇/寮冪敤鐜囧姣斿浘
this.abandonChartOptions = {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b} : {c} ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- data: this.mockData.abandon.map(item => item.name)
+ tooltip: axisTooltip(),
+ legend: legend(['鎹愮尞鑾峰彇鐜�', '鍣ㄥ畼寮冪敤鐜�']),
+ grid: grid(),
+ xAxis: xAxis(this.regionCompareData.map(item => item.region)),
+ yAxis: {
+ type: 'value',
+ axisLabel: { color: '#8a919f', fontSize: 12, formatter: '{value}%' },
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f1f5' } }
},
series: [
- {
- name: '寮冪敤鍘熷洜',
- type: 'pie',
- radius: ['40%', '70%'],
- avoidLabelOverlap: false,
- label: {
- show: false,
- position: 'center'
- },
- emphasis: {
- label: {
- show: true,
- fontSize: '20',
- fontWeight: 'bold'
- }
- },
- labelLine: {
- show: false
- },
- data: this.mockData.abandon
- }
+ barSeries('鎹愮尞鑾峰彇鐜�', this.regionCompareData.map(item => item.acquisition), COLORS.blue),
+ barSeries('鍣ㄥ畼寮冪敤鐜�', this.regionCompareData.map(item => item.abandon), COLORS.coral)
]
};
},
- // 鑾峰彇浠〃鐩樺浘琛ㄩ厤缃�
+ // 鑾峰彇浠〃鐩樺浘琛ㄩ厤缃紙琛ㄧ洏 + 鎸囬拡锛屾暟鍊肩敱涓婃柟澶ф暟瀛楀睍绀猴紝鍒诲害澶栫疆锛�
getDashboardOptions(item) {
const value = parseFloat(item.value);
const max = item.key === 'avg' ? 5 : 100;
- const data = [{ value, name: item.title }];
+ const color = item.key === 'abandon' ? COLORS.coral : item.key === 'acquisition' ? COLORS.blue : COLORS.green;
+ // 骞冲潎鍣ㄥ畼鏁拌寖鍥村皬鐢ㄦ暣鏁板埢搴︼紝鐧惧垎姣旂敤 0/25/50/75/100 浜斾釜鍒诲害锛岄伩鍏嶆暟瀛楁嫢鎸�
+ const splitNumber = item.key === 'avg' ? 5 : 4;
return {
- tooltip: {
- formatter: '{b}: {c}' + (item.key === 'abandon' ? '%' : item.key === 'acquisition' ? '%' : '')
- },
series: [
{
- name: item.title,
type: 'gauge',
- progress: {
- show: true,
- width: 18
- },
+ startAngle: 210,
+ endAngle: -30,
+ min: 0,
+ max,
+ splitNumber,
+ radius: '60%',
+ center: ['50%', '52%'],
axisLine: {
lineStyle: {
- width: 18
+ width: 10,
+ color: [[value / max, color], [1, '#eef0f5']]
}
},
- axisTick: {
- show: false
+ pointer: {
+ length: '60%',
+ width: 4,
+ itemStyle: { color }
},
- splitLine: {
- length: 15,
- lineStyle: {
- width: 2,
- color: '#999'
- }
- },
+ anchor: { show: false },
+ axisTick: { show: false },
+ // length 褰掗浂 + 璐熻窛绂伙紝浣垮埢搴︽暟瀛楄惤鍦ㄨ〃鐩樺渾寮у渚э紙澶栧洿锛�
+ splitLine: { show: false, length: 0 },
axisLabel: {
- distance: 25,
- color: '#999',
+ distance: -18,
+ color: '#8a919f',
fontSize: 12
},
- anchor: {
- show: true,
- showAbove: true,
- size: 25,
- itemStyle: {
- borderWidth: 10
- }
- },
- title: {
- show: false
- },
- detail: {
- valueAnimation: true,
- fontSize: 20,
- offsetCenter: [0, '70%']
- },
- data: data,
- max: max
+ title: { show: false },
+ detail: { show: false },
+ data: [{ value, name: item.title }]
}
]
};
@@ -455,6 +502,19 @@
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('瀵煎嚭鍔熻兘寮�鍙戜腑...');
diff --git a/src/views/statistics/utilization.vue b/src/views/statistics/utilization.vue
index 51ea1c9..76c03b1 100644
--- a/src/views/statistics/utilization.vue
+++ b/src/views/statistics/utilization.vue
@@ -29,124 +29,56 @@
<el-card style="margin-bottom: 20px;">
<div slot="header" class="clearfix">
<h3>2. 鍣ㄥ畼寮冪敤鍘熷洜鍒嗘瀽</h3>
- <el-form :inline="true" style="float: right;">
- <el-form-item label="鍣ㄥ畼绫诲瀷" style="margin-bottom: 0;">
- <el-select
- v-model="abandonQuery.organType"
- placeholder="璇烽�夋嫨鍣ㄥ畼绫诲瀷"
- clearable
- size="mini"
- style="width: 150px"
- @change="handleAbandonQuery"
- >
- <el-option
- v-for="item in organOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- </el-form>
</div>
<EChartsWrapper
+ v-if="useMock"
:id="'abandon-analysis-chart'"
:options="abandonChartOptions"
height="400px"
/>
+ <el-empty v-else description="鍚庣鏆傛湭鎻愪緵鍣ㄥ畼寮冪敤鍘熷洜缁熻鎺ュ彛"></el-empty>
</el-card>
<!-- 绗笁閮ㄥ垎锛氭瘡渚涗綋鍣ㄥ畼鑾峰彇鏁伴噺 -->
<el-card>
<div slot="header" class="clearfix">
<h3>3. 姣忎緵浣撳櫒瀹樿幏鍙栨暟閲�</h3>
- <el-form :inline="true" style="float: right;">
- <el-form-item label="骞翠唤" style="margin-bottom: 0;">
- <el-date-picker
- v-model="perDonorQuery.year"
- type="year"
- placeholder="閫夋嫨骞翠唤"
- value-format="yyyy"
- size="mini"
- style="width: 120px"
- @change="handlePerDonorQuery"
- />
- </el-form-item>
-
- <el-form-item label="鍣ㄥ畼绫诲瀷" style="margin-bottom: 0;">
- <el-select
- v-model="perDonorQuery.organType"
- placeholder="璇烽�夋嫨鍣ㄥ畼绫诲瀷"
- clearable
- size="mini"
- style="width: 150px"
- @change="handlePerDonorQuery"
- >
- <el-option
- v-for="item in organOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- </el-form>
</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="donorName" align="center" />
- <el-table-column label="鑾峰彇鏁伴噺" prop="acquisitionCount" align="center" >
- <template slot-scope="{ row }">
- <el-tag :type="getCountTag(row.acquisitionCount)" size="small">
- {{ row.acquisitionCount }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="鍏ㄨ倽" prop="liver" align="center" />
- <el-table-column label="宸﹀崐鑲�" prop="leftLiver" align="center" />
- <el-table-column label="鍙冲崐鑲�" prop="rightLiver" align="center" />
- <el-table-column label="蹇冭剰" prop="heart" align="center" />
- <el-table-column label="灏忚偁" prop="intestine" align="center" />
- <el-table-column label="鑳拌吅" prop="pancreas" align="center" />
- <el-table-column label="宸﹁偩" prop="leftKidney" align="center" />
- <el-table-column label="鍙宠偩" prop="rightKidney" align="center" />
- <el-table-column label="鍏ㄨ偤" prop="fullLung" align="center" />
- <el-table-column label="宸﹁偤" prop="leftLung" align="center" />
- <el-table-column label="鍙宠偤" prop="rightLung" align="center" />
- <el-table-column label="宸︾溂瑙掕啘" prop="leftCornea" align="center" />
- <el-table-column label="鍙崇溂瑙掕啘" prop="rightCornea" 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="viewDonorDetail(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]"
- />
+ <template v-if="useMock">
+ <el-table
+ v-loading="loading"
+ :data="perDonorTable"
+ style="width: 100%"
+ border
+ stripe
+ >
+ <el-table-column label="骞翠唤" prop="year" align="center" />
+ <el-table-column label="渚涗綋濮撳悕" prop="donorName" align="center" />
+ <el-table-column label="鑾峰彇鏁伴噺" prop="acquisitionCount" align="center">
+ <template slot-scope="{ row }">
+ <el-tag :type="getCountTag(row.acquisitionCount)" size="small">
+ {{ row.acquisitionCount }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍏ㄨ倽" prop="liver" align="center" />
+ <el-table-column label="宸﹀崐鑲�" prop="leftLiver" align="center" />
+ <el-table-column label="鍙冲崐鑲�" prop="rightLiver" align="center" />
+ <el-table-column label="蹇冭剰" prop="heart" align="center" />
+ <el-table-column label="灏忚偁" prop="intestine" align="center" />
+ <el-table-column label="鑳拌吅" prop="pancreas" align="center" />
+ <el-table-column label="宸﹁偩" prop="leftKidney" align="center" />
+ <el-table-column label="鍙宠偩" prop="rightKidney" align="center" />
+ <el-table-column label="鍏ㄨ偤" prop="fullLung" align="center" />
+ <el-table-column label="宸﹁偤" prop="leftLung" align="center" />
+ <el-table-column label="鍙宠偤" prop="rightLung" align="center" />
+ <el-table-column label="宸︾溂瑙掕啘" prop="leftCornea" align="center" />
+ <el-table-column label="鍙崇溂瑙掕啘" prop="rightCornea" align="center" />
+ <el-table-column label="鍦板尯" prop="region" align="center" />
+ </el-table>
+ </template>
+ <el-empty v-else description="鍚庣鏆傛湭鎻愪緵姣忎緵浣撳櫒瀹樿幏鍙栨槑缁嗘帴鍙�"></el-empty>
</el-card>
</div>
</template>
@@ -154,6 +86,10 @@
<script>
import FilterPanel from '@/components/charts/FilterPanel.vue';
import EChartsWrapper from '@/components/charts/EChartsWrapper.vue';
+import { organType } from '@/api/businessApi';
+import { toTotalsList, toNum } from '@/utils/totals';
+import statisticsMode, { isStatisticsMock } from '@/utils/statisticsMode';
+import { COLORS, axisTooltip, grid, xAxis, yAxis, legend, barSeries } from '@/utils/chartStyle';
export default {
name: 'OrganUtilization',
@@ -167,8 +103,6 @@
query: {
region: [],
monthRange: [],
- ageRange: [],
- chinaCategory: [],
organType: null,
page: 1,
limit: 10
@@ -182,13 +116,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'
},
@@ -203,60 +137,19 @@
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: '涓浗鍒嗙被',
- prop: 'chinaCategory',
- type: 'select',
- multiple: true,
- options: [
- { label: '涓浗涓�绫�(DBD)', value: '1' },
- { label: '涓浗浜岀被(DCD)', value: '2' },
- { label: '涓浗涓夌被(DBCD)', value: '3' }
- ],
- width: '200px'
- },
- {
label: '鍣ㄥ畼绫诲瀷',
prop: 'organType',
type: 'select',
options: [
- { label: '鍏ㄨ倽', value: 'liver' },
- { label: '鑲捐剰', value: 'kidney' },
- { label: '蹇冭剰', value: 'heart' },
- { label: '鑲�', value: 'lung' },
- { label: '鐪艰鑶�', value: 'cornea' }
+ { label: '鍏ㄨ倽', value: '鍏ㄨ倽' },
+ { label: '鑲捐剰', value: '鑲捐剰' },
+ { label: '蹇冭剰', value: '蹇冭剰' },
+ { label: '鑲�', value: '鑲�' },
+ { label: '鑳拌吅', value: '鑳拌吅' },
+ { label: '鐪艰鑶�', value: '鐪艰鑶�' }
],
width: '200px'
}
- ],
-
- // 鍣ㄥ畼閫夐」
- organOptions: [
- { label: '鍏ㄨ倽', value: 'liver' },
- { label: '宸﹀崐鑲�', value: 'leftLiver' },
- { label: '鍙冲崐鑲�', value: 'rightLiver' },
- { label: '蹇冭剰', value: 'heart' },
- { label: '灏忚偁', value: 'intestine' },
- { label: '鑳拌吅', value: 'pancreas' },
- { label: '宸﹁偩', value: 'leftKidney' },
- { label: '鍙宠偩', value: 'rightKidney' },
- { label: '鍏ㄨ偤', value: 'fullLung' },
- { label: '宸﹁偤', value: 'leftLung' },
- { label: '鍙宠偤', value: 'rightLung' },
- { label: '宸︾溂瑙掕啘', value: 'leftCornea' },
- { label: '鍙崇溂瑙掕啘', value: 'rightCornea' }
],
// 鍔犺浇鐘舵��
@@ -264,322 +157,33 @@
// 鍥捐〃閰嶇疆
acquisitionChartOptions: {},
- abandonChartOptions: {},
- // 琛ㄦ牸鏁版嵁
- tableData: [],
- total: 0,
+ // 鍣ㄥ畼绫诲瀷鍒嗗竷鏁版嵁
+ organData: [],
- // 寮冪敤鍒嗘瀽鏌ヨ鍙傛暟
- abandonQuery: {
- organType: null
- },
-
- // 姣忎緵浣撴煡璇㈠弬鏁�
- perDonorQuery: {
- year: '2024',
- organType: null
- },
-
- // 妯℃嫙鏁版嵁
- mockData: {
- acquisition: [
- {
- month: '2024-01',
- region: '闈掑矝鍦板尯',
- liver: { acquisition: 8, transplant: 7 },
- kidney: { acquisition: 12, transplant: 10 },
- heart: { acquisition: 3, transplant: 3 },
- lung: { acquisition: 5, transplant: 4 },
- cornea: { acquisition: 20, transplant: 18 }
- },
- {
- month: '2024-02',
- region: '闈掑矝鍦板尯',
- liver: { acquisition: 10, transplant: 9 },
- kidney: { acquisition: 15, transplant: 13 },
- heart: { acquisition: 4, transplant: 3 },
- lung: { acquisition: 6, transplant: 5 },
- cornea: { acquisition: 25, transplant: 22 }
- },
- {
- month: '2024-01',
- region: '鏃ョ収鍦板尯',
- liver: { acquisition: 5, transplant: 4 },
- kidney: { acquisition: 8, transplant: 7 },
- heart: { acquisition: 2, transplant: 2 },
- lung: { acquisition: 3, transplant: 2 },
- cornea: { acquisition: 15, transplant: 13 }
- }
- ],
- abandon: [
- { name: '鍣ㄥ畼璐ㄩ噺涓嶅悎鏍�', value: 45 },
- { name: '渚涗綋鐤剧梾', value: 25 },
- { name: '杩愯緭闂', value: 15 },
- { name: '鍙椾綋鍖归厤闂', value: 10 },
- { name: '鍏朵粬鍘熷洜', value: 5 }
- ],
- table: [
- {
- id: 1,
- year: '2024',
- donorName: '寮犱笁',
- acquisitionCount: 5,
- liver: 1,
- leftLiver: 0,
- rightLiver: 0,
- heart: 0,
- intestine: 0,
- pancreas: 0,
- leftKidney: 1,
- rightKidney: 1,
- fullLung: 0,
- leftLung: 0,
- rightLung: 0,
- leftCornea: 1,
- rightCornea: 1,
- region: '闈掑矝鍦板尯'
- },
- {
- id: 2,
- year: '2024',
- donorName: '鏉庡洓',
- acquisitionCount: 3,
- liver: 0,
- leftLiver: 0,
- rightLiver: 0,
- heart: 1,
- intestine: 0,
- pancreas: 0,
- leftKidney: 0,
- rightKidney: 0,
- fullLung: 0,
- leftLung: 1,
- rightLung: 1,
- leftCornea: 0,
- rightCornea: 0,
- region: '闈掑矝鍦板尯'
- },
- {
- id: 3,
- year: '2024',
- donorName: '鐜嬩簲',
- acquisitionCount: 6,
- liver: 1,
- leftLiver: 0,
- rightLiver: 0,
- heart: 0,
- intestine: 0,
- pancreas: 1,
- leftKidney: 1,
- rightKidney: 1,
- fullLung: 0,
- leftLung: 0,
- rightLung: 0,
- leftCornea: 1,
- rightCornea: 1,
- region: '鏃ョ収鍦板尯'
- },
- {
- id: 4,
- year: '2024',
- donorName: '璧靛叚',
- acquisitionCount: 4,
- liver: 0,
- leftLiver: 0,
- rightLiver: 0,
- heart: 0,
- intestine: 0,
- pancreas: 0,
- leftKidney: 1,
- rightKidney: 1,
- fullLung: 0,
- leftLung: 0,
- rightLung: 0,
- leftCornea: 1,
- rightCornea: 1,
- region: '娴庡崡鍦板尯'
- },
- {
- id: 5,
- year: '2023',
- donorName: '閽变竷',
- acquisitionCount: 3,
- liver: 0,
- leftLiver: 0,
- rightLiver: 0,
- heart: 1,
- intestine: 0,
- pancreas: 0,
- leftKidney: 0,
- rightKidney: 0,
- fullLung: 1,
- leftLung: 0,
- rightLung: 1,
- leftCornea: 0,
- rightCornea: 0,
- region: '鐑熷彴鍦板尯'
- }
- ]
- }
+ // 鏃犳帴鍙i儴鍒嗙殑妯℃嫙鏁版嵁锛堜粎 mock 妯″紡灞曠ず锛�
+ abandonReasons: [
+ { name: '鍣ㄥ畼璐ㄩ噺涓嶅悎鏍�', value: 45 },
+ { name: '渚涗綋鐤剧梾', value: 25 },
+ { name: '杩愯緭闂', value: 15 },
+ { name: '鍙椾綋鍖归厤闂', value: 10 },
+ { name: '鍏朵粬鍘熷洜', value: 5 }
+ ],
+ perDonorTable: [
+ { id: 1, year: '2024', donorName: '寮犱笁', acquisitionCount: 5, liver: 1, leftLiver: 0, rightLiver: 0, heart: 0, intestine: 0, pancreas: 0, leftKidney: 1, rightKidney: 1, fullLung: 0, leftLung: 0, rightLung: 0, leftCornea: 1, rightCornea: 1, region: '闈掑矝鍦板尯' },
+ { id: 2, year: '2024', donorName: '鏉庡洓', acquisitionCount: 3, liver: 0, leftLiver: 0, rightLiver: 0, heart: 1, intestine: 0, pancreas: 0, leftKidney: 0, rightKidney: 0, fullLung: 0, leftLung: 1, rightLung: 1, leftCornea: 0, rightCornea: 0, region: '闈掑矝鍦板尯' },
+ { id: 3, year: '2024', donorName: '鐜嬩簲', acquisitionCount: 6, liver: 1, leftLiver: 0, rightLiver: 0, heart: 0, intestine: 0, pancreas: 1, leftKidney: 1, rightKidney: 1, fullLung: 0, leftLung: 0, rightLung: 0, leftCornea: 1, rightCornea: 1, region: '鏃ョ収鍦板尯' },
+ { id: 4, year: '2024', donorName: '璧靛叚', acquisitionCount: 4, liver: 0, leftLiver: 0, rightLiver: 0, heart: 0, intestine: 0, pancreas: 0, leftKidney: 1, rightKidney: 1, fullLung: 0, leftLung: 0, rightLung: 0, leftCornea: 1, rightCornea: 1, region: '娴庡崡鍦板尯' },
+ { id: 5, year: '2023', donorName: '閽变竷', acquisitionCount: 3, liver: 0, leftLiver: 0, rightLiver: 0, heart: 1, intestine: 0, pancreas: 0, leftKidney: 0, rightKidney: 0, fullLung: 1, leftLung: 0, rightLung: 1, leftCornea: 0, rightCornea: 0, region: '鐑熷彴鍦板尯' }
+ ]
};
},
- created() {
- this.loadData();
- },
- methods: {
- // 鍔犺浇鏁版嵁
- async loadData() {
- this.loading = true;
- try {
- await new Promise(resolve => setTimeout(resolve, 500));
-
- // 澶勭悊琛ㄦ牸鏁版嵁
- let filteredData = [...this.mockData.table];
-
- 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) {
- // 杩欓噷鍙互娣诲姞鏈堜唤绛涢�夐�昏緫
- }
-
- if (this.query.ageRange.length > 0) {
- // 杩欓噷鍙互娣诲姞骞撮緞绛涢�夐�昏緫
- }
-
- if (this.query.chinaCategory.length > 0) {
- // 杩欓噷鍙互娣诲姞涓浗鍒嗙被绛涢�夐�昏緫
- }
-
- if (this.query.organType) {
- const organMap = { liver: 'liver', kidney: ['leftKidney', 'rightKidney'], heart: 'heart', lung: ['fullLung', 'leftLung', 'rightLung'], cornea: ['leftCornea', 'rightCornea'] };
- const organProp = organMap[this.query.organType];
- if (organProp) {
- if (Array.isArray(organProp)) {
- filteredData = filteredData.filter(item =>
- organProp.some(prop => item[prop] > 0)
- );
- } else {
- filteredData = filteredData.filter(item => item[organProp] > 0);
- }
- }
- }
-
- // 鍒嗛〉
- const start = (this.query.page - 1) * this.query.limit;
- this.tableData = filteredData.slice(start, start + this.query.limit);
- this.total = filteredData.length;
-
- // 鏇存柊鍥捐〃
- this.updateCharts();
-
- } catch (error) {
- console.error('鍔犺浇鏁版嵁澶辫触:', error);
- this.$message.error('鏁版嵁鍔犺浇澶辫触');
- } finally {
- this.loading = false;
- }
+ computed: {
+ useMock() {
+ return isStatisticsMock();
},
-
- // 鏇存柊鍥捐〃
- updateCharts() {
- // 鑾峰彇绉绘缁熻鍥�
- const regions = [...new Set(this.mockData.acquisition.map(item => item.region))];
- const months = [...new Set(this.mockData.acquisition.map(item => item.month))];
- const organTypes = ['liver', 'kidney', 'heart', 'lung', 'cornea'];
- const organNames = ['鑲濊剰', '鑲捐剰', '蹇冭剰', '鑲�', '鐪艰鑶�'];
-
- const seriesData = [];
- const xAxisData = [];
-
- // 鐢熸垚x杞存暟鎹�
- months.forEach(month => {
- regions.forEach(region => {
- xAxisData.push(`${month}\n${region}`);
- });
- });
-
- // 鐢熸垚绯诲垪鏁版嵁
- organTypes.forEach((organType, index) => {
- const data = [];
- months.forEach(month => {
- regions.forEach(region => {
- const item = this.mockData.acquisition.find(d => d.month === month && d.region === region);
- if (item) {
- data.push(item[organType]?.acquisition || 0);
- } else {
- data.push(0);
- }
- });
- });
-
- seriesData.push({
- name: `${organNames[index]}-鑾峰彇`,
- type: 'bar',
- stack: '鎬婚噺',
- data: data,
- itemStyle: this.getSeriesColor(index * 2)
- });
-
- const transplantData = [];
- months.forEach(month => {
- regions.forEach(region => {
- const item = this.mockData.acquisition.find(d => d.month === month && d.region === region);
- if (item) {
- transplantData.push(item[organType]?.transplant || 0);
- } else {
- transplantData.push(0);
- }
- });
- });
-
- seriesData.push({
- name: `${organNames[index]}-绉绘`,
- type: 'bar',
- stack: '鎬婚噺',
- data: transplantData,
- itemStyle: this.getSeriesColor(index * 2 + 1)
- });
- });
-
- this.acquisitionChartOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- legend: {
- data: seriesData.map(item => item.name),
- top: 20
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: xAxisData,
- axisLabel: {
- interval: 0,
- rotate: 45
- }
- },
- yAxis: {
- type: 'value',
- name: '鏁伴噺(涓�)'
- },
- series: seriesData
- };
-
- // 寮冪敤鍒嗘瀽鍥�
- this.abandonChartOptions = {
+ abandonChartOptions() {
+ return {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
@@ -587,7 +191,7 @@
legend: {
orient: 'vertical',
left: 'left',
- data: this.mockData.abandon.map(item => item.name)
+ data: this.abandonReasons.map(item => item.name)
},
series: [
{
@@ -595,7 +199,7 @@
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
- data: this.mockData.abandon,
+ data: this.abandonReasons,
emphasis: {
itemStyle: {
shadowBlur: 10,
@@ -606,18 +210,65 @@
}
]
};
+ }
+ },
+ 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.organType) {
+ q.organName = this.query.organType;
+ }
+ return q;
},
- // 鑾峰彇绯诲垪棰滆壊
- getSeriesColor(index) {
- const colors = [
- '#409EFF', '#8cc5ff', // 鑲濊剰
- '#67C23A', '#95d475', // 鑲捐剰
- '#E6A23C', '#f3c96b', // 蹇冭剰
- '#F56C6C', '#f89898', // 鑲�
- '#909399', '#b1b3b8' // 鐪艰鑶�
- ];
- return { color: colors[index % colors.length] };
+ // 鍔犺浇鏁版嵁
+ async loadData() {
+ this.loading = true;
+ try {
+ const q = this.buildQuery();
+ const typeRes = await organType(q);
+
+ this.organData = toTotalsList(typeRes).map(item => ({
+ name: item.organName || '鏈煡',
+ acquisition: toNum(item.getCount),
+ transplant: toNum(item.useCount)
+ }));
+
+ this.updateCharts();
+ } catch (error) {
+ console.error('鍔犺浇鏁版嵁澶辫触:', error);
+ this.$message.error('鏁版嵁鍔犺浇澶辫触');
+ } finally {
+ this.loading = false;
+ }
+ },
+
+ // 鏇存柊鍥捐〃
+ updateCharts() {
+ this.acquisitionChartOptions = {
+ tooltip: axisTooltip(),
+ legend: legend(['鑾峰彇鏁伴噺', '绉绘鏁伴噺']),
+ grid: grid(),
+ xAxis: xAxis(this.organData.map(item => item.name)),
+ yAxis: yAxis('鏁伴噺(涓�)'),
+ series: [
+ barSeries('鑾峰彇鏁伴噺', this.organData.map(item => item.acquisition), COLORS.blue),
+ barSeries('绉绘鏁伴噺', this.organData.map(item => item.transplant), COLORS.green)
+ ]
+ };
},
// 鑾峰彇鏁伴噺鏍囩
@@ -638,8 +289,6 @@
this.query = {
region: [],
monthRange: [],
- ageRange: [],
- chinaCategory: [],
organType: null,
page: 1,
limit: 10
@@ -647,42 +296,16 @@
this.loadData();
},
- // 澶勭悊鍒嗛〉
- handlePagination(page) {
- this.query.page = page;
- this.loadData();
- },
-
- // 澶勭悊鍒嗛〉澶у皬鍙樺寲
- handlePageSizeChange(size) {
- this.query.limit = size;
- this.query.page = 1;
- this.loadData();
- },
-
- // 澶勭悊寮冪敤鏌ヨ
- handleAbandonQuery() {
- // 杩欓噷鍙互鏍规嵁abandonQuery绛涢�夋暟鎹�
- console.log('寮冪敤鏌ヨ:', this.abandonQuery);
- },
-
- // 澶勭悊姣忎緵浣撴煡璇�
- handlePerDonorQuery() {
- this.loadData();
- },
-
// 澶勭悊瀵煎嚭
handleExport() {
this.$message.info('瀵煎嚭鍔熻兘寮�鍙戜腑...');
- },
-
- // 鏌ョ湅鎹愮尞鑰呰鎯�
- viewDonorDetail(row) {
- this.$router.push({
- path: '/donor/detail',
- query: { id: row.id }
- });
}
}
};
</script>
+
+<style scoped>
+.statistics-page {
+ padding: 20px;
+}
+</style>
diff --git a/src/views/statistics/willingness.vue b/src/views/statistics/willingness.vue
index 20e469b..f300ad7 100644
--- a/src/views/statistics/willingness.vue
+++ b/src/views/statistics/willingness.vue
@@ -79,7 +79,6 @@
{{ formatMonth(row.month) }}
</template>
</el-table-column>
- <el-table-column label="鍣ㄥ畼鍚嶇О" prop="organName" align="center" />
<el-table-column label="妗堜緥鏁伴噺" prop="caseCount" align="center" />
<el-table-column label="鎹愮尞鏁伴噺" prop="donationCount" align="center" />
<el-table-column label="鎹愮尞鎰忔効" prop="willingnessRate" align="center" >
@@ -98,6 +97,18 @@
</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>
</div>
</template>
@@ -105,7 +116,12 @@
<script>
import FilterPanel from '@/components/charts/FilterPanel.vue';
import EChartsWrapper from '@/components/charts/EChartsWrapper.vue';
+import { caseTrend, caseTrendArea, caseTrendMoon } from '@/api/businessApi';
+import { toTotalsList, toMonth, toNum } from '@/utils/totals';
+import statisticsMode from '@/utils/statisticsMode';
+import { COLORS, axisTooltip, grid, xAxis, legend, barSeries, lineSeries } from '@/utils/chartStyle';
+// 鎹愮尞鎰忔効 = 瀹屾垚鎹愮尞 / 娼滃湪鎹愮尞锛岀粺涓�澶嶇敤 case 鐩稿叧鎺ュ彛鎺ㄥ
export default {
name: 'WillingnessStatistics',
components: {
@@ -118,7 +134,6 @@
query: {
region: [],
monthRange: [],
- organType: null,
page: 1,
limit: 10
},
@@ -131,13 +146,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'
},
@@ -150,20 +165,6 @@
endPlaceholder: '缁撴潫鏈堜唤',
valueFormat: 'yyyy-MM',
width: '300px'
- },
- {
- label: '鍣ㄥ畼绫诲瀷',
- prop: 'organType',
- type: 'select',
- options: [
- { label: '鍏ㄨ倽', value: 'liver' },
- { label: '鑲捐剰', value: 'kidney' },
- { label: '蹇冭剰', value: 'heart' },
- { label: '鑲�', value: 'lung' },
- { label: '鐪艰鑶�', value: 'cornea' },
- { label: '澶氬櫒瀹�', value: 'multi' }
- ],
- width: '200px'
}
],
@@ -172,92 +173,125 @@
// 姒傝鏁版嵁
overviewData: [
- { title: '鎬绘渚嬫暟', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-flag', color: '#409EFF' },
- { title: '鎬绘崘鐚暟閲�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-check', color: '#67C23A' },
- { title: '骞冲潎鎹愮尞鎰忔効', value: '0%', desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-data', color: '#E6A23C' },
- { title: '瓒嬪娍鍙樺寲', value: '0%', desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-trend', color: '#F56C6C' }
+ { title: '鎬绘渚嬫暟', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-flag', color: '#5B8FF9' },
+ { title: '鎬绘崘鐚暟閲�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-check', color: '#5AD8A6' },
+ { title: '骞冲潎鎹愮尞鎰忔効', value: '0%', desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-data', color: '#F6BD16' },
+ { title: '瓒嬪娍鍙樺寲', value: '0%', desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-trend', color: '#E8684A' }
],
// 鍥捐〃閰嶇疆
trendChartOptions: {},
regionChartOptions: {},
+ // 鍥捐〃鏁版嵁
+ trendData: [],
+ regionData: [],
+
// 琛ㄦ牸鏁版嵁
tableData: [],
- total: 0,
-
- // 妯℃嫙鏁版嵁
- mockData: {
- table: [
- { id: 1, region: '闈掑矝鍦板尯', month: '2024-01', organName: '鍏ㄨ倽', caseCount: 15, donationCount: 10, willingnessRate: 66.7, monthCompare: 2.5 },
- { id: 2, region: '闈掑矝鍦板尯', month: '2024-01', organName: '鑲捐剰', caseCount: 20, donationCount: 16, willingnessRate: 80.0, monthCompare: 1.2 },
- { id: 3, region: '鏃ョ収鍦板尯', month: '2024-01', organName: '鍏ㄨ倽', caseCount: 8, donationCount: 5, willingnessRate: 62.5, monthCompare: 0.8 },
- { id: 4, region: '鏃ョ収鍦板尯', month: '2024-01', organName: '鐪艰鑶�', caseCount: 12, donationCount: 9, willingnessRate: 75.0, monthCompare: 3.1 },
- { id: 5, region: '闈掑矝鍦板尯', month: '2024-02', organName: '鍏ㄨ倽', caseCount: 18, donationCount: 12, willingnessRate: 66.7, monthCompare: 0.0 },
- { id: 6, region: '闈掑矝鍦板尯', month: '2024-02', organName: '蹇冭剰', caseCount: 5, donationCount: 4, willingnessRate: 80.0, monthCompare: 5.2 },
- { id: 7, region: '娴庡崡鍦板尯', month: '2024-02', organName: '鍏ㄨ倽', caseCount: 25, donationCount: 20, willingnessRate: 80.0, monthCompare: 1.8 },
- { id: 8, region: '娴庡崡鍦板尯', month: '2024-02', organName: '澶氬櫒瀹�', caseCount: 8, donationCount: 6, willingnessRate: 75.0, monthCompare: 2.3 }
- ],
- trend: [
- { month: '2023-10', willingness: 72.5, cases: 80, donations: 58 },
- { month: '2023-11', willingness: 73.8, cases: 85, donations: 63 },
- { month: '2023-12', willingness: 75.2, cases: 90, donations: 68 },
- { month: '2024-01', willingness: 76.8, cases: 95, donations: 73 },
- { month: '2024-02', willingness: 78.3, cases: 100, donations: 78 },
- { month: '2024-03', willingness: 79.1, cases: 105, donations: 83 }
- ],
- region: [
- { region: '闈掑矝鍦板尯', willingness: 78.3, cases: 150, donations: 118 },
- { region: '鏃ョ収鍦板尯', willingness: 72.5, cases: 80, donations: 58 },
- { region: '娴庡崡鍦板尯', willingness: 82.6, cases: 200, donations: 165 },
- { region: '鐑熷彴鍦板尯', willingness: 75.8, cases: 100, donations: 76 }
- ]
- }
+ total: 0
};
},
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;
+ },
+
+ // 璁$畻鎰忔効鐜�
+ calcWillingness(potential, complete) {
+ const p = toNum(potential);
+ return p > 0 ? Number(((toNum(complete) / p) * 100).toFixed(1)) : 0;
+ },
+
// 鍔犺浇鏁版嵁
async loadData() {
this.loading = true;
try {
- await new Promise(resolve => setTimeout(resolve, 500));
+ const q = this.buildQuery();
+ const [trendRes, areaRes, moonRes] = await Promise.all([
+ caseTrend(q),
+ caseTrendArea(q),
+ caseTrendMoon(q)
+ ]);
- // 澶勭悊琛ㄦ牸鏁版嵁
- let filteredData = [...this.mockData.table];
+ // 瓒嬪娍锛堟湀浠界淮搴︼級
+ this.trendData = toTotalsList(trendRes)
+ .map(item => {
+ const potential = toNum(item.potentialCount);
+ const complete = toNum(item.completeCount);
+ return {
+ month: toMonth(item.donateTime) || toMonth(item.month),
+ potential,
+ complete,
+ willingness: this.calcWillingness(potential, complete)
+ };
+ })
+ .sort((a, b) => String(a.month).localeCompare(String(b.month)));
- if (this.query.region.length > 0) {
- filteredData = filteredData.filter(item =>
- this.query.region.includes(item.region.replace('鍦板尯', '').toLowerCase())
- );
- }
+ // 鍦板尯瀵规瘮
+ this.regionData = toTotalsList(areaRes).map(item => {
+ const potential = toNum(item.potentialCount);
+ const complete = toNum(item.completeCount);
+ return {
+ region: item.area || item.region || '',
+ potential,
+ complete,
+ willingness: this.calcWillingness(potential, complete)
+ };
+ });
- if (this.query.monthRange && this.query.monthRange.length === 2) {
- const [start, end] = this.query.monthRange;
- filteredData = filteredData.filter(item =>
- item.month >= start && item.month <= end
- );
- }
+ // 鏄庣粏琛紙鍦板尯 x 鏈堜唤锛�
+ const rows = toTotalsList(moonRes)
+ .map(item => {
+ const potential = toNum(item.potentialCount);
+ const complete = toNum(item.completeCount);
+ return {
+ region: item.area || item.region || '',
+ month: toMonth(item.donateTime) || toMonth(item.month),
+ potential,
+ complete
+ };
+ })
+ .sort((a, b) => String(a.month).localeCompare(String(b.month)));
- if (this.query.organType) {
- const organMap = { liver: '鑲�', kidney: '鑲�', heart: '蹇冭剰', lung: '鑲�', cornea: '鐪艰鑶�', multi: '澶氬櫒瀹�' };
- const organName = organMap[this.query.organType];
- if (organName) {
- filteredData = filteredData.filter(item => item.organName.includes(organName));
+ // 璁$畻鐜瘮鍙樺寲锛堝悓涓�鍦板尯锛岀浉瀵逛笂涓�鏈堜唤鎰忔効鐜囧彉鍖栵紝鐧惧垎鐐癸級
+ rows.forEach(row => {
+ const willingness = this.calcWillingness(row.potential, row.complete);
+ // 璇ュ湴鍖烘寜鏈堜唤鎺掑簭鍚庣殑涓婁竴涓湀
+ const regionRows = rows.filter(r => r.region === row.region);
+ const idx = regionRows.indexOf(row);
+ let monthCompare = 0;
+ if (idx > 0) {
+ const prev = regionRows[idx - 1];
+ const prevWillingness = this.calcWillingness(prev.potential, prev.complete);
+ monthCompare = Number((willingness - prevWillingness).toFixed(1));
}
- }
+ row.willingnessRate = willingness;
+ row.caseCount = row.potential;
+ row.donationCount = row.complete;
+ row.monthCompare = monthCompare;
+ });
- this.tableData = filteredData;
- this.total = filteredData.length;
+ this.total = rows.length;
+ const start = (this.query.page - 1) * this.query.limit;
+ this.tableData = rows.slice(start, start + this.query.limit);
- // 鏇存柊姒傝鏁版嵁
- this.updateOverviewData(filteredData);
-
- // 鏇存柊鍥捐〃
+ this.updateOverviewData(rows);
this.updateCharts();
-
} catch (error) {
console.error('鍔犺浇鏁版嵁澶辫触:', error);
this.$message.error('鏁版嵁鍔犺浇澶辫触');
@@ -267,20 +301,18 @@
},
// 鏇存柊姒傝鏁版嵁
- updateOverviewData(data) {
- if (data.length === 0) return;
+ updateOverviewData(rows) {
+ const totalCases = rows.reduce((sum, r) => sum + toNum(r.potential), 0);
+ const totalDonations = rows.reduce((sum, r) => sum + toNum(r.complete), 0);
+ const avgWillingness = this.calcWillingness(totalCases, totalDonations);
- const totalCases = data.reduce((sum, item) => sum + item.caseCount, 0);
- const totalDonations = data.reduce((sum, item) => sum + item.donationCount, 0);
- const avgWillingness = data.length > 0
- ? (data.reduce((sum, item) => sum + parseFloat(item.willingnessRate), 0) / data.length).toFixed(1)
- : 0;
-
- // 璁$畻瓒嬪娍鍙樺寲锛堝彇鏈�杩戜袱涓湀鐨勫钩鍧囧彉鍖栵級
- const recentData = data.slice(-2);
- const trend = recentData.length >= 2
- ? ((recentData[1].willingnessRate - recentData[0].willingnessRate) / recentData[0].willingnessRate * 100).toFixed(1)
- : 0;
+ // 瓒嬪娍鍙樺寲锛氭渶杩戜袱涓湀鎰忔効鐜囧樊鍊�
+ let trend = 0;
+ if (this.trendData.length >= 2) {
+ const last = this.trendData[this.trendData.length - 1].willingness;
+ const prev = this.trendData[this.trendData.length - 2].willingness;
+ trend = Number((last - prev).toFixed(1));
+ }
this.overviewData[0].value = totalCases;
this.overviewData[1].value = totalDonations;
@@ -290,139 +322,55 @@
// 鏇存柊鍥捐〃
updateCharts() {
- // 瓒嬪娍鍥�
+ const dualAxis = [
+ {
+ type: 'value',
+ name: '鎰忔効鐜�(%)',
+ nameTextStyle: { color: '#b0b6c3', fontSize: 12 },
+ axisLabel: { color: '#8a919f', fontSize: 12, formatter: '{value}%' },
+ splitLine: { lineStyle: { type: 'dashed', color: '#f0f1f5' } }
+ },
+ {
+ type: 'value',
+ name: '鏁伴噺(涓�)',
+ nameTextStyle: { color: '#b0b6c3', fontSize: 12 },
+ axisLabel: { color: '#8a919f', fontSize: 12 },
+ splitLine: { show: false }
+ }
+ ];
+
+ // 瓒嬪娍鍥撅紙鎰忔効鐜囨姌绾� + 鏁伴噺鏌辩姸锛屽弻杞达級
this.trendChartOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- label: {
- backgroundColor: '#6a7985'
- }
- }
- },
- legend: {
- data: ['鎹愮尞鎰忔効', '妗堜緥鏁伴噺', '鎹愮尞鏁伴噺']
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
+ tooltip: axisTooltip(),
+ legend: legend(['鎹愮尞鎰忔効', '妗堜緥鏁伴噺', '鎹愮尞鏁伴噺']),
+ grid: grid(),
xAxis: {
type: 'category',
- boundaryGap: false,
- data: this.mockData.trend.map(item => item.month.replace('-', '骞�') + '鏈�')
+ boundaryGap: true,
+ data: this.trendData.map(item => item.month),
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { color: '#8a919f', fontSize: 12 }
},
- yAxis: [
- {
- type: 'value',
- name: '鎰忔効鐜�(%)',
- min: 60,
- max: 90,
- axisLabel: {
- formatter: '{value}%'
- }
- },
- {
- type: 'value',
- name: '鏁伴噺(涓�)',
- position: 'right'
- }
- ],
+ yAxis: dualAxis,
series: [
- {
- name: '鎹愮尞鎰忔効',
- type: 'line',
- smooth: true,
- data: this.mockData.trend.map(item => item.willingness),
- itemStyle: { color: '#409EFF' },
- markLine: {
- data: [
- { yAxis: 75, name: '鐩爣绾�' }
- ],
- lineStyle: {
- type: 'dashed',
- color: '#F56C6C'
- }
- }
- },
- {
- name: '妗堜緥鏁伴噺',
- type: 'bar',
- yAxisIndex: 1,
- data: this.mockData.trend.map(item => item.cases),
- itemStyle: { color: '#67C23A' }
- },
- {
- name: '鎹愮尞鏁伴噺',
- type: 'bar',
- yAxisIndex: 1,
- data: this.mockData.trend.map(item => item.donations),
- itemStyle: { color: '#E6A23C' }
- }
+ lineSeries('鎹愮尞鎰忔効', this.trendData.map(item => item.willingness), COLORS.blue, { area: true }),
+ barSeries('妗堜緥鏁伴噺', this.trendData.map(item => item.potential), COLORS.green, { yAxisIndex: 1 }),
+ barSeries('鎹愮尞鏁伴噺', this.trendData.map(item => item.complete), COLORS.amber, { yAxisIndex: 1 })
]
};
// 鍦板尯瀵规瘮鍥�
this.regionChartOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- legend: {
- data: ['鎹愮尞鎰忔効', '妗堜緥鏁伴噺', '鎹愮尞鏁伴噺']
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: this.mockData.region.map(item => item.region)
- },
- yAxis: [
- {
- type: 'value',
- name: '鎰忔効鐜�(%)',
- min: 60,
- max: 90,
- axisLabel: {
- formatter: '{value}%'
- }
- },
- {
- type: 'value',
- name: '鏁伴噺(涓�)',
- position: 'right'
- }
- ],
+ tooltip: axisTooltip(),
+ legend: legend(['鎹愮尞鎰忔効', '妗堜緥鏁伴噺', '鎹愮尞鏁伴噺']),
+ grid: grid(),
+ xAxis: xAxis(this.regionData.map(item => item.region)),
+ yAxis: dualAxis,
series: [
- {
- name: '鎹愮尞鎰忔効',
- type: 'bar',
- data: this.mockData.region.map(item => item.willingness),
- itemStyle: { color: '#409EFF' }
- },
- {
- name: '妗堜緥鏁伴噺',
- type: 'bar',
- yAxisIndex: 1,
- data: this.mockData.region.map(item => item.cases),
- itemStyle: { color: '#67C23A' }
- },
- {
- name: '鎹愮尞鏁伴噺',
- type: 'bar',
- yAxisIndex: 1,
- data: this.mockData.region.map(item => item.donations),
- itemStyle: { color: '#E6A23C' }
- }
+ barSeries('鎹愮尞鎰忔効', this.regionData.map(item => item.willingness), COLORS.blue),
+ barSeries('妗堜緥鏁伴噺', this.regionData.map(item => item.potential), COLORS.green, { yAxisIndex: 1 }),
+ barSeries('鎹愮尞鏁伴噺', this.regionData.map(item => item.complete), COLORS.amber, { yAxisIndex: 1 })
]
};
},
@@ -435,9 +383,9 @@
// 鑾峰彇杩涘害鏉¢鑹�
getProgressColor(percentage) {
- if (percentage >= 80) return '#67C23A';
- if (percentage >= 70) return '#E6A23C';
- return '#F56C6C';
+ if (percentage >= 80) return COLORS.green;
+ if (percentage >= 70) return COLORS.amber;
+ return COLORS.coral;
},
// 鑾峰彇姣旇緝鏍囩绫诲瀷
@@ -458,10 +406,22 @@
this.query = {
region: [],
monthRange: [],
- 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();
},
@@ -472,3 +432,63 @@
}
};
</script>
+
+<style scoped>
+.statistics-page {
+ padding: 20px;
+}
+
+.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>
diff --git "a/\347\256\241\347\220\206\347\253\257.zip" "b/\347\256\241\347\220\206\347\253\257.zip"
deleted file mode 100644
index fbf1609..0000000
--- "a/\347\256\241\347\220\206\347\253\257.zip"
+++ /dev/null
Binary files differ
diff --git "a/\347\256\241\347\220\206\347\253\2577.20.zip" "b/\347\256\241\347\220\206\347\253\2577.20.zip"
deleted file mode 100644
index d3cc747..0000000
--- "a/\347\256\241\347\220\206\347\253\2577.20.zip"
+++ /dev/null
Binary files differ
diff --git "a/\347\256\241\347\220\206\347\253\2578.20.zip" "b/\347\256\241\347\220\206\347\253\2578.20.zip"
deleted file mode 100644
index a17503e..0000000
--- "a/\347\256\241\347\220\206\347\253\2578.20.zip"
+++ /dev/null
Binary files differ
diff --git "a/\347\256\241\347\220\206\347\253\2578.3.zip" "b/\347\256\241\347\220\206\347\253\2578.3.zip"
deleted file mode 100644
index 5865108..0000000
--- "a/\347\256\241\347\220\206\347\253\2578.3.zip"
+++ /dev/null
Binary files differ
--
Gitblit v1.9.3