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/views/statistics/willingness.vue | 464 ++++++++++++++++++++++++++++++---------------------------
1 files changed, 242 insertions(+), 222 deletions(-)
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>
--
Gitblit v1.9.3