|
|
@@ -63,26 +63,52 @@
|
|
63
|
63
|
|
|
64
|
64
|
<!-- 绩效分析内容区域 -->
|
|
65
|
65
|
<div class="analysis-content">
|
|
66
|
|
- <!-- 科室分析 - 单行面板 -->
|
|
67
|
|
- <div class="panel-item full-width" :class="{ 'loading-overlay': departmentLoading }">
|
|
68
|
|
- <div class="panel-header">
|
|
69
|
|
- <h3>科室</h3>
|
|
70
|
|
- </div>
|
|
71
|
|
- <!-- 折线图 -->
|
|
72
|
|
- <div class="chart-container">
|
|
73
|
|
- <div ref="departmentChartRef" class="echarts-chart"></div>
|
|
74
|
|
- </div>
|
|
75
|
|
- <div class="panel-header">
|
|
76
|
|
- <h3>明细</h3>
|
|
77
|
|
- <span>得分(环比)</span>
|
|
|
66
|
+ <!-- 大队和科室分析 - 双列面板 -->
|
|
|
67
|
+ <div class="two-panel-layout">
|
|
|
68
|
+ <!-- 大队分析 -->
|
|
|
69
|
+ <div class="panel-item" :class="{ 'loading-overlay': brigadeLoading }">
|
|
|
70
|
+ <div class="panel-header">
|
|
|
71
|
+ <h3>大队</h3>
|
|
|
72
|
+ </div>
|
|
|
73
|
+ <!-- 折线图 -->
|
|
|
74
|
+ <div class="chart-container">
|
|
|
75
|
+ <div ref="brigadeChartRef" class="echarts-chart"></div>
|
|
|
76
|
+ </div>
|
|
|
77
|
+ <div class="panel-header">
|
|
|
78
|
+ <h3>明细</h3>
|
|
|
79
|
+ <span>得分(环比)</span>
|
|
|
80
|
+ </div>
|
|
|
81
|
+ <!-- 明细表格 -->
|
|
|
82
|
+ <div class="table-container">
|
|
|
83
|
+ <el-table :data="brigadeTableData" style="width: 100%" size="small">
|
|
|
84
|
+ <el-table-column prop="time" label="" width="150" />
|
|
|
85
|
+ <el-table-column v-for="column in brigadeTableColumn" :key="column.column" :prop="column.column"
|
|
|
86
|
+ :label="column.name" />
|
|
|
87
|
+ </el-table>
|
|
|
88
|
+ </div>
|
|
78
|
89
|
</div>
|
|
79
|
|
- <!-- 明细表格 -->
|
|
80
|
|
- <div class="table-container">
|
|
81
|
|
- <el-table :data="departmentTableData" style="width: 100%" size="small">
|
|
82
|
|
- <el-table-column prop="time" label="" width="150" />
|
|
83
|
|
- <el-table-column v-for="column in departmentTableColumn" :key="column.column" :prop="column.column"
|
|
84
|
|
- :label="column.name" />
|
|
85
|
|
- </el-table>
|
|
|
90
|
+
|
|
|
91
|
+ <!-- 科室分析 -->
|
|
|
92
|
+ <div class="panel-item" :class="{ 'loading-overlay': departmentLoading }">
|
|
|
93
|
+ <div class="panel-header">
|
|
|
94
|
+ <h3>科室</h3>
|
|
|
95
|
+ </div>
|
|
|
96
|
+ <!-- 折线图 -->
|
|
|
97
|
+ <div class="chart-container">
|
|
|
98
|
+ <div ref="departmentChartRef" class="echarts-chart"></div>
|
|
|
99
|
+ </div>
|
|
|
100
|
+ <div class="panel-header">
|
|
|
101
|
+ <h3>明细</h3>
|
|
|
102
|
+ <span>得分(环比)</span>
|
|
|
103
|
+ </div>
|
|
|
104
|
+ <!-- 明细表格 -->
|
|
|
105
|
+ <div class="table-container">
|
|
|
106
|
+ <el-table :data="departmentTableData" style="width: 100%" size="small">
|
|
|
107
|
+ <el-table-column prop="time" label="" width="150" />
|
|
|
108
|
+ <el-table-column v-for="column in departmentTableColumn" :key="column.column" :prop="column.column"
|
|
|
109
|
+ :label="column.name" />
|
|
|
110
|
+ </el-table>
|
|
|
111
|
+ </div>
|
|
86
|
112
|
</div>
|
|
87
|
113
|
</div>
|
|
88
|
114
|
|
|
|
@@ -212,12 +238,15 @@ const personalSortOrder = ref('desc') // desc: 倒序, asc: 正序
|
|
212
|
238
|
|
|
213
|
239
|
// 加载状态
|
|
214
|
240
|
const loading = ref(false)
|
|
|
241
|
+const brigadeLoading = ref(false)
|
|
215
|
242
|
const departmentLoading = ref(false)
|
|
216
|
243
|
const teamLoading = ref(false)
|
|
217
|
244
|
const personalLoading = ref(false)
|
|
218
|
245
|
|
|
219
|
246
|
// 真实数据
|
|
220
|
247
|
const realData = ref({
|
|
|
248
|
+ brigade: [],
|
|
|
249
|
+ brigadeList: [],
|
|
221
|
250
|
team: [],
|
|
222
|
251
|
teamList: [],
|
|
223
|
252
|
personal: [],
|
|
|
@@ -322,11 +351,13 @@ const personalTabOptions = [
|
|
322
|
351
|
]
|
|
323
|
352
|
|
|
324
|
353
|
// 图表容器引用
|
|
|
354
|
+const brigadeChartRef = ref(null)
|
|
325
|
355
|
const departmentChartRef = ref(null)
|
|
326
|
356
|
const teamChartRef = ref(null)
|
|
327
|
357
|
const personalChartRef = ref(null)
|
|
328
|
358
|
|
|
329
|
359
|
// 图表实例
|
|
|
360
|
+const { setOption: setBrigadeOption, dispose: disposeBrigade } = useEcharts(brigadeChartRef)
|
|
330
|
361
|
const { setOption: setDepartmentOption, dispose: disposeDepartment } = useEcharts(departmentChartRef)
|
|
331
|
362
|
const { setOption: setteamListOption, dispose: disposeTeam } = useEcharts(teamChartRef)
|
|
332
|
363
|
const { setOption: setPersonalOption, dispose: disposePersonal } = useEcharts(personalChartRef)
|
|
|
@@ -340,6 +371,10 @@ watch([() => queryForm.value.dateRangeType, () => queryForm.value.startMonth, ()
|
|
340
|
371
|
})
|
|
341
|
372
|
|
|
342
|
373
|
// 表格数据
|
|
|
374
|
+const brigadeTableData = ref([
|
|
|
375
|
+
|
|
|
376
|
+])
|
|
|
377
|
+
|
|
343
|
378
|
const departmentTableData = ref([
|
|
344
|
379
|
|
|
345
|
380
|
])
|
|
|
@@ -352,6 +387,35 @@ const personalTableData = ref([
|
|
352
|
387
|
|
|
353
|
388
|
])
|
|
354
|
389
|
|
|
|
390
|
+// 大队图表配置
|
|
|
391
|
+const brigadeChartOptions = {
|
|
|
392
|
+ tooltip: {
|
|
|
393
|
+ trigger: 'axis'
|
|
|
394
|
+ },
|
|
|
395
|
+ legend: {
|
|
|
396
|
+ show: true,
|
|
|
397
|
+ icon: 'rect',
|
|
|
398
|
+ },
|
|
|
399
|
+ grid: {
|
|
|
400
|
+ left: '3%',
|
|
|
401
|
+ right: '4%',
|
|
|
402
|
+ bottom: '3%',
|
|
|
403
|
+ containLabel: true
|
|
|
404
|
+ },
|
|
|
405
|
+ xAxis: {
|
|
|
406
|
+ type: 'category',
|
|
|
407
|
+ boundaryGap: false,
|
|
|
408
|
+ data: []
|
|
|
409
|
+ },
|
|
|
410
|
+ yAxis: {
|
|
|
411
|
+ type: 'value',
|
|
|
412
|
+ name: '得分'
|
|
|
413
|
+ },
|
|
|
414
|
+ series: [
|
|
|
415
|
+
|
|
|
416
|
+ ]
|
|
|
417
|
+}
|
|
|
418
|
+
|
|
355
|
419
|
// 科室图表配置
|
|
356
|
420
|
const departmentChartOptions = {
|
|
357
|
421
|
tooltip: {
|
|
|
@@ -651,9 +715,82 @@ const buildPersonalApiParams = () => {
|
|
651
|
715
|
return params
|
|
652
|
716
|
}
|
|
653
|
717
|
|
|
|
718
|
+// 构建大队API参数 (dimension = 4,没有dimensionType参数)
|
|
|
719
|
+const buildBrigadeApiParams = () => {
|
|
|
720
|
+ const params = {
|
|
|
721
|
+ resultField: performanceDimensionMap[queryForm.value.performanceDimension],
|
|
|
722
|
+ sortOrder: 1
|
|
|
723
|
+ }
|
|
|
724
|
+
|
|
|
725
|
+ // 处理时间范围
|
|
|
726
|
+ if (queryForm.value.dateRangeType === 'CUSTOM' && queryForm.value.startMonth && queryForm.value.endMonth) {
|
|
|
727
|
+ // 处理自定义月份范围选择
|
|
|
728
|
+ const startDate = new Date(queryForm.value.startMonth)
|
|
|
729
|
+ const endDate = new Date(queryForm.value.endMonth)
|
|
|
730
|
+
|
|
|
731
|
+ // 开始月的第一天
|
|
|
732
|
+ const startTime = new Date(startDate.getFullYear(), startDate.getMonth(), 1)
|
|
|
733
|
+ // 结束月的最后一天
|
|
|
734
|
+ const endTime = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0)
|
|
|
735
|
+
|
|
|
736
|
+ params.startTime = formatDate(startTime)
|
|
|
737
|
+ params.endTime = formatDate(endTime)
|
|
|
738
|
+ } else {
|
|
|
739
|
+ // 处理预设时间范围
|
|
|
740
|
+ const endDate = new Date()
|
|
|
741
|
+ endDate.setDate(0) // 设置为上个月最后一天,不包括本月
|
|
|
742
|
+ let startDate = new Date(endDate)
|
|
|
743
|
+
|
|
|
744
|
+ if (queryForm.value.dateRangeType === 'THREE_MONTHS') {
|
|
|
745
|
+ startDate.setMonth(endDate.getMonth() - 2)
|
|
|
746
|
+ } else if (queryForm.value.dateRangeType === 'SIX_MONTHS') {
|
|
|
747
|
+ startDate.setMonth(endDate.getMonth() - 5)
|
|
|
748
|
+ }
|
|
|
749
|
+
|
|
|
750
|
+ params.startTime = formatDate(startDate)
|
|
|
751
|
+ params.endTime = formatDate(endDate)
|
|
|
752
|
+ }
|
|
|
753
|
+
|
|
|
754
|
+ return params
|
|
|
755
|
+}
|
|
|
756
|
+
|
|
654
|
757
|
|
|
655
|
758
|
|
|
656
|
759
|
// 数据请求函数
|
|
|
760
|
+// 获取大队数据
|
|
|
761
|
+const fetchBrigadeData = async () => {
|
|
|
762
|
+ brigadeLoading.value = true
|
|
|
763
|
+ try {
|
|
|
764
|
+ // 大队数据 (dimension = 4,没有dimensionType参数)
|
|
|
765
|
+ const brigadeChartParams = buildBrigadeApiParams()
|
|
|
766
|
+ brigadeChartParams.dimension = 4
|
|
|
767
|
+ const brigadeTableParams = buildBrigadeApiParams()
|
|
|
768
|
+ brigadeTableParams.dimension = 4
|
|
|
769
|
+
|
|
|
770
|
+ // 并行请求大队图表和表格数据
|
|
|
771
|
+ const [brigadeChartResponse, brigadeTableResponse] = await Promise.all([
|
|
|
772
|
+ getCalculateByTime(brigadeChartParams),
|
|
|
773
|
+ getCalculateByTimeList(brigadeTableParams)
|
|
|
774
|
+ ])
|
|
|
775
|
+
|
|
|
776
|
+ // 设置大队数据
|
|
|
777
|
+ if (brigadeChartResponse.data) {
|
|
|
778
|
+ realData.value.brigade = brigadeChartResponse.data || []
|
|
|
779
|
+ }
|
|
|
780
|
+ if (brigadeTableResponse.data) {
|
|
|
781
|
+ realData.value.brigadeList = brigadeTableResponse.data || []
|
|
|
782
|
+ }
|
|
|
783
|
+
|
|
|
784
|
+ // 请求完成后直接更新大队数据
|
|
|
785
|
+ updateBrigadeData()
|
|
|
786
|
+ } catch (error) {
|
|
|
787
|
+ console.error('大队数据请求失败:', error)
|
|
|
788
|
+ ElMessage.error('大队数据加载失败')
|
|
|
789
|
+ } finally {
|
|
|
790
|
+ brigadeLoading.value = false
|
|
|
791
|
+ }
|
|
|
792
|
+}
|
|
|
793
|
+
|
|
657
|
794
|
// 获取科室数据
|
|
658
|
795
|
const fetchDepartmentData = async () => {
|
|
659
|
796
|
departmentLoading.value = true
|
|
|
@@ -760,9 +897,10 @@ const fetchData = async () => {
|
|
760
|
897
|
loading.value = true
|
|
761
|
898
|
|
|
762
|
899
|
try {
|
|
763
|
|
- // 并行请求科室、班组和个人数据
|
|
|
900
|
+ // 并行请求大队、科室、班组和个人数据
|
|
764
|
901
|
// 数据更新已经在各个数据请求函数中完成
|
|
765
|
902
|
await Promise.all([
|
|
|
903
|
+ fetchBrigadeData(),
|
|
766
|
904
|
fetchDepartmentData(),
|
|
767
|
905
|
fetchTeamData(),
|
|
768
|
906
|
fetchPersonalData()
|
|
|
@@ -777,6 +915,8 @@ const fetchData = async () => {
|
|
777
|
915
|
}
|
|
778
|
916
|
|
|
779
|
917
|
|
|
|
918
|
+// 表格列定义
|
|
|
919
|
+const brigadeTableColumn = ref([])
|
|
780
|
920
|
const departmentTableColumn = ref([])
|
|
781
|
921
|
const teamTableColumn = ref([])
|
|
782
|
922
|
const personalTableColumn = ref([])
|
|
|
@@ -809,6 +949,54 @@ const getColumnData = (arr, column) => {
|
|
809
|
949
|
return res
|
|
810
|
950
|
}
|
|
811
|
951
|
|
|
|
952
|
+// 更新大队数据(包含图表和表格)
|
|
|
953
|
+const updateBrigadeData = () => {
|
|
|
954
|
+ // 更新大队图表
|
|
|
955
|
+ if (realData.value.brigade) {
|
|
|
956
|
+ const xAxisData = realData.value.brigade.timeAxis;
|
|
|
957
|
+ let seriesData = [];
|
|
|
958
|
+ let timeDataList = realData.value.brigade.timeDataList;
|
|
|
959
|
+ let arr = {};
|
|
|
960
|
+ timeDataList.forEach(item => {
|
|
|
961
|
+ item.dataItems.forEach(ele => {
|
|
|
962
|
+ if (!arr[ele.name]) {
|
|
|
963
|
+ arr[ele.name] = [];
|
|
|
964
|
+ }
|
|
|
965
|
+ arr[ele.name].push(ele.value);
|
|
|
966
|
+ })
|
|
|
967
|
+ })
|
|
|
968
|
+ Object.keys(arr).forEach((item, index) => {
|
|
|
969
|
+ seriesData.push({
|
|
|
970
|
+ name: item,
|
|
|
971
|
+ type: 'line',
|
|
|
972
|
+ symbol: 'circle',
|
|
|
973
|
+ symbolSize: 6,
|
|
|
974
|
+ data: arr[item],
|
|
|
975
|
+ itemStyle: { color: colorList[index % colorList.length] },
|
|
|
976
|
+ lineStyle: { color: colorList[index % colorList.length], width: 2 }
|
|
|
977
|
+ })
|
|
|
978
|
+ })
|
|
|
979
|
+
|
|
|
980
|
+ setBrigadeOption({
|
|
|
981
|
+ ...brigadeChartOptions,
|
|
|
982
|
+ xAxis: { ...brigadeChartOptions.xAxis, data: xAxisData },
|
|
|
983
|
+ series: seriesData
|
|
|
984
|
+ })
|
|
|
985
|
+ }
|
|
|
986
|
+
|
|
|
987
|
+ // 更新大队表格数据
|
|
|
988
|
+ if (realData.value.brigadeList) {
|
|
|
989
|
+ const timeDataList = realData.value.brigadeList.timeDataList;
|
|
|
990
|
+ brigadeTableColumn.value = []
|
|
|
991
|
+ if (timeDataList && timeDataList.length > 0 && timeDataList[0].dataItems) {
|
|
|
992
|
+ timeDataList[0].dataItems.forEach((item, index) => {
|
|
|
993
|
+ brigadeTableColumn.value.push({ name: item.name, column: `brigade${index}` })
|
|
|
994
|
+ })
|
|
|
995
|
+ }
|
|
|
996
|
+ brigadeTableData.value = getColumnData(timeDataList, brigadeTableColumn.value)
|
|
|
997
|
+ }
|
|
|
998
|
+}
|
|
|
999
|
+
|
|
812
|
1000
|
// 更新科室数据(包含图表和表格)
|
|
813
|
1001
|
const updateDepartmentData = () => {
|
|
814
|
1002
|
// 更新科室图表
|
|
|
@@ -1144,6 +1332,7 @@ onMounted(() => {
|
|
1144
|
1332
|
|
|
1145
|
1333
|
// 组件卸载时销毁图表
|
|
1146
|
1334
|
onUnmounted(() => {
|
|
|
1335
|
+ disposeBrigade()
|
|
1147
|
1336
|
disposeDepartment()
|
|
1148
|
1337
|
disposeTeam()
|
|
1149
|
1338
|
disposePersonal()
|