已删除1个文件
已重命名1个文件
已修改16个文件
已添加2个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!-- StatisticsMain.vue --> |
| | | <template> |
| | | <div class="statistics-main"> |
| | | <el-tabs v-model="activeTab" @tab-click="handleTabChange"> |
| | | <el-tab-pane label="满æåº¦ç»è®¡" name="followup"> |
| | | <followup-statistics |
| | | v-if="activeTab === 'followup'" |
| | | ref="followupRef" |
| | | /> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="å¤è¯éç¥ç»è®¡" name="visitStatistics"> |
| | | <visit-Statistics |
| | | v-if="activeTab === 'visitStatistics'" |
| | | ref="visitStatisticsRef" |
| | | /> |
| | | </el-tab-pane> |
| | | |
| | | </el-tabs> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import FollowupStatistics from "./components/FollowupStatistics.vue"; |
| | | import visitStatistics from "./components/visitStatistics.vue"; |
| | | import SatisfactionStatistics from "./components/SatisfactionStatistics.vue"; |
| | | |
| | | export default { |
| | | name: "StatisticsMain", |
| | | components: { |
| | | FollowupStatistics, |
| | | SatisfactionStatistics, |
| | | visitStatistics, |
| | | }, |
| | | data() { |
| | | return { |
| | | activeTab: "followup", |
| | | }; |
| | | }, |
| | | methods: { |
| | | handleTabChange(tab) { |
| | | console.log("忢å°:", tab.name); |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .statistics-main { |
| | | padding: 20px; |
| | | background: #fff; |
| | | min-height: calc(100vh - 84px); |
| | | |
| | | ::v-deep .el-tabs__header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | ::v-deep .el-tabs__item { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | ::v-deep .el-tabs__nav-wrap::after { |
| | | height: 1px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | v-if="queryParams.statisticaltype == 1" |
| | | label="åºé¢ç
åº" |
| | | align="center" |
| | | sortable |
| | | key="leavehospitaldistrictname" |
| | | prop="leavehospitaldistrictname" |
| | | :show-overflow-tooltip="true" |
| | | :sort-method="sortChineseNumber" |
| | | min-width="120" |
| | | /> |
| | | |
| | |
| | | min-width="100" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.followUpRate !== null && scope.row.followUpRate !== undefined"> |
| | | <span |
| | | v-if=" |
| | | scope.row.followUpRate !== null && |
| | | scope.row.followUpRate !== undefined |
| | | " |
| | | > |
| | | {{ formatPercent(scope.row.followUpRate) }} |
| | | </span> |
| | | <span v-else>-</span> |
| | |
| | | min-width="100" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.joyTotal !== null && scope.row.joyTotal !== undefined"> |
| | | <span |
| | | v-if=" |
| | | scope.row.joyTotal !== null && scope.row.joyTotal !== undefined |
| | | " |
| | | > |
| | | {{ formatPercent(scope.row.joyTotal) }} |
| | | </span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æä½" |
| | | align="center" |
| | | fixed="right" |
| | | width="120" |
| | | > |
| | | <el-table-column label="æä½" align="center" fixed="right" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-button |
| | | type="text" |
| | | @click="getinfo(scope.row)" |
| | | > |
| | | <el-button type="text" @click="getinfo(scope.row)"> |
| | | <i class="el-icon-s-order" style="margin-right: 4px"></i> |
| | | æ¥ç详æ
|
| | | </el-button> |
| | |
| | | :close-on-click-modal="false" |
| | | > |
| | | <template #title> |
| | | <div style="display: flex; align-items: center;"> |
| | | <i class="el-icon-s-data" style="margin-right: 8px; color: #409EFF;"></i> |
| | | <div style="display: flex; align-items: center"> |
| | | <i |
| | | class="el-icon-s-data" |
| | | style="margin-right: 8px; color: #409eff" |
| | | ></i> |
| | | <span>{{ topicvalue.name }}</span> |
| | | <span style="margin-left: 10px; color: #666; font-size: 14px;">满æåº¦ææ 详æ
</span> |
| | | <span style="margin-left: 10px; color: #666; font-size: 14px" |
| | | >满æåº¦ææ 详æ
</span |
| | | > |
| | | </div> |
| | | </template> |
| | | <topic-dialog |
| | | v-if="topicVisible" |
| | | :row-data="currentRow" |
| | | :topicList="topiclist" |
| | | :query-params="queryParams" |
| | | @close="topicVisible = false" |
| | | /> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { getSfStatisticsJoy, getSfStatisticsJoyInfo, selectTimelyRate } from "@/api/system/user"; |
| | | import { |
| | | getSfStatisticsJoy, |
| | | getSfStatisticsJoyInfo, |
| | | selectTimelyRate, |
| | | } from "@/api/system/user"; |
| | | import ExcelJS from "exceljs"; |
| | | import { saveAs } from "file-saver"; |
| | | import SeedetailsDialog from './components/SeedetailsDialog.vue'; |
| | | import TopicDialog from './components/TopicDialog.vue'; |
| | | import SeedetailsDialog from "./components/SeedetailsDialog.vue"; |
| | | import TopicDialog from "./components/TopicDialog.vue"; |
| | | |
| | | export default { |
| | | name: 'FollowupStatistics', |
| | | name: "FollowupStatistics", |
| | | components: { |
| | | SeedetailsDialog, |
| | | TopicDialog |
| | | TopicDialog, |
| | | }, |
| | | data() { |
| | | return { |
| | | // æ¥è¯¢åæ° |
| | | queryParams: { |
| | | statisticaltype: 1, |
| | | leavehospitaldistrictcodes: [], |
| | | leavehospitaldistrictcodes: ["all"], |
| | | deptcodes: [], |
| | | serviceType: [2], |
| | | dateRange: [], |
| | | pageNum: 1, |
| | | pageSize: 20 |
| | | pageSize: 20, |
| | | }, |
| | | |
| | | // ç»è®¡ç±»åå表 |
| | | Statisticallist: [ |
| | | { label: "ç
åºç»è®¡", value: 1 }, |
| | | { label: "ç§å®¤ç»è®¡", value: 2 } |
| | | { label: "ç§å®¤ç»è®¡", value: 2 }, |
| | | ], |
| | | |
| | | // ç
åºå表 |
| | |
| | | // 满æåº¦è¯¦æ
æ°æ® |
| | | topiclist: [], |
| | | topicvalue: { |
| | | name: '' |
| | | name: "", |
| | | }, |
| | | |
| | | // æ¥æéæ©å¨é项 |
| | | pickerOptions: { |
| | | shortcuts: [ |
| | | { |
| | | text: 'æè¿ä¸å¨', |
| | | text: "æè¿ä¸å¨", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: 'æè¿ä¸ä¸ªæ', |
| | | text: "æè¿ä¸ä¸ªæ", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: 'æè¿ä¸ä¸ªæ', |
| | | text: "æè¿ä¸ä¸ªæ", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | ], |
| | | disabledDate(time) { |
| | | return time.getTime() > Date.now(); |
| | | } |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | }, |
| | | |
| | |
| | | this.options = this.$store.getters.tasktypes || []; |
| | | |
| | | // è·åç§å®¤å表 |
| | | this.flatArraydept = (this.$store.getters.belongDepts || []).map((dept) => { |
| | | return { |
| | | label: dept.deptName, |
| | | value: dept.deptCode |
| | | }; |
| | | }); |
| | | this.flatArraydept = (this.$store.getters.belongDepts || []).map( |
| | | (dept) => { |
| | | return { |
| | | label: dept.deptName, |
| | | value: dept.deptCode, |
| | | }; |
| | | } |
| | | ); |
| | | |
| | | // è·åç
åºå表 |
| | | this.flatArrayhospit = (this.$store.getters.belongWards || []).map((ward) => { |
| | | return { |
| | | label: ward.districtName, |
| | | value: ward.districtCode |
| | | }; |
| | | }); |
| | | this.flatArrayhospit = (this.$store.getters.belongWards || []).map( |
| | | (ward) => { |
| | | return { |
| | | label: ward.districtName, |
| | | value: ward.districtCode, |
| | | }; |
| | | } |
| | | ); |
| | | |
| | | // æ·»å å
¨é¨é项 |
| | | this.flatArraydept.push({ label: "å
¨é¨", value: "all" }); |
| | |
| | | // å¤çæ¥è¯¢åæ° |
| | | const params = { |
| | | configKey: "joyCount", |
| | | ...this.queryParams |
| | | ...this.queryParams, |
| | | }; |
| | | |
| | | // å¤çæ¥æèå´ |
| | | if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) { |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | params.startTime = this.queryParams.dateRange[0]; |
| | | params.endTime = this.queryParams.dateRange[1]; |
| | | } |
| | |
| | | // ç
åºç»è®¡ |
| | | if (params.leavehospitaldistrictcodes.includes("all")) { |
| | | // 妿鿩äº"å
¨é¨"ï¼åç§»é¤"all"å¼ |
| | | params.leavehospitaldistrictcodes = params.leavehospitaldistrictcodes.filter(item => item !== "all"); |
| | | params.leavehospitaldistrictcodes = |
| | | params.leavehospitaldistrictcodes.filter( |
| | | (item) => item !== "all" |
| | | ); |
| | | // 妿éè¦ä¼ ææç
åºä»£ç ï¼å¯ä»¥ä»storeä¸è·å |
| | | params.leavehospitaldistrictcodes = (this.$store.getters.belongWards || []).map(ward => ward.districtCode); |
| | | params.leavehospitaldistrictcodes = ( |
| | | this.$store.getters.belongWards || [] |
| | | ).map((ward) => ward.districtCode); |
| | | } |
| | | } else if (params.statisticaltype == 2) { |
| | | // ç§å®¤ç»è®¡ |
| | | if (params.deptcodes.includes("all")) { |
| | | // 妿鿩äº"å
¨é¨"ï¼åç§»é¤"all"å¼ |
| | | params.deptcodes = params.deptcodes.filter(item => item !== "all"); |
| | | params.deptcodes = params.deptcodes.filter( |
| | | (item) => item !== "all" |
| | | ); |
| | | // 妿éè¦ä¼ ææç§å®¤ä»£ç ï¼å¯ä»¥ä»storeä¸è·å |
| | | params.deptcodes = (this.$store.getters.belongDepts || []).map(dept => dept.deptCode); |
| | | params.deptcodes = (this.$store.getters.belongDepts || []).map( |
| | | (dept) => dept.deptCode |
| | | ); |
| | | } |
| | | } |
| | | |
| | | const response = await getSfStatisticsJoy(params); |
| | | this.userList = response.data || []; |
| | | this.userList = this.customSort(response.data) || []; |
| | | this.total = response.total || 0; |
| | | } catch (error) { |
| | | console.error('è·åç»è®¡å表失败:', error); |
| | | this.$message.error('è·åæ°æ®å¤±è´¥'); |
| | | console.error("è·åç»è®¡å表失败:", error); |
| | | this.$message.error("è·åæ°æ®å¤±è´¥"); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | sortChineseNumber(aRow, bRow) { |
| | | const a = aRow.leavehospitaldistrictname; |
| | | const b = bRow.leavehospitaldistrictname; |
| | | |
| | | // 䏿æ°åå°é¿æä¼¯æ°åçæ å°ï¼æ©å±å°45ï¼ |
| | | const chineseNumMap = { |
| | | ä¸: 1, |
| | | äº: 2, |
| | | ä¸: 3, |
| | | å: 4, |
| | | äº: 5, |
| | | å
: 6, |
| | | ä¸: 7, |
| | | å
«: 8, |
| | | ä¹: 9, |
| | | å: 10, |
| | | åä¸: 11, |
| | | åäº: 12, |
| | | åä¸: 13, |
| | | åå: 14, |
| | | åäº: 15, |
| | | åå
: 16, |
| | | åä¸: 17, |
| | | åå
«: 18, |
| | | åä¹: 19, |
| | | äºå: 20, |
| | | äºåä¸: 21, |
| | | äºåäº: 22, |
| | | äºåä¸: 23, |
| | | äºåå: 24, |
| | | äºåäº: 25, |
| | | äºåå
: 26, |
| | | äºåä¸: 27, |
| | | äºåå
«: 28, |
| | | äºåä¹: 29, |
| | | ä¸å: 30, |
| | | ä¸åä¸: 31, |
| | | ä¸åäº: 32, |
| | | ä¸åä¸: 33, |
| | | ä¸åå: 34, |
| | | ä¸åäº: 35, |
| | | ä¸åå
: 36, |
| | | ä¸åä¸: 37, |
| | | ä¸åå
«: 38, |
| | | ä¸åä¹: 39, |
| | | åå: 40, |
| | | ååä¸: 41, |
| | | ååäº: 42, |
| | | ååä¸: 43, |
| | | ååå: 44, |
| | | ååäº: 45, |
| | | }; |
| | | |
| | | // æå䏿æ°å |
| | | const getNumberFromText = (text) => { |
| | | if (!text || typeof text !== "string") return -1; |
| | | |
| | | // å¹é
䏿æ°åï¼æ¯æä¸å°ååäº |
| | | const match = text.match(/^([ä¸äºä¸åäºå
ä¸å
«ä¹å]+)/); |
| | | |
| | | if (match && match[1]) { |
| | | const chineseNum = match[1]; |
| | | return chineseNumMap[chineseNum] !== undefined |
| | | ? chineseNumMap[chineseNum] |
| | | : -1; |
| | | } |
| | | |
| | | // å¦ææ²¡æå¹é
å°ä¸ææ°åï¼å°è¯å¹é
é¿æä¼¯æ°å |
| | | const arabicMatch = text.match(/^(\d+)/); |
| | | if (arabicMatch && arabicMatch[1]) { |
| | | const num = parseInt(arabicMatch[1], 10); |
| | | return num >= 1 && num <= 45 ? num : -1; |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | const numA = getNumberFromText(a); |
| | | const numB = getNumberFromText(b); |
| | | |
| | | // å¤çæ æ³è§£æçæ
åµ |
| | | if (numA === -1 && numB === -1) { |
| | | return (a || "").localeCompare(b || ""); |
| | | } |
| | | if (numA === -1) return 1; |
| | | if (numB === -1) return -1; |
| | | |
| | | return numA - numB; |
| | | }, |
| | | customSort(data) { |
| | | // å®ä¹æ¨ææçç
åºé¡ºåºï¼æ©å±å°ååäºï¼ |
| | | const order = [ |
| | | "ä¸", |
| | | "äº", |
| | | "ä¸", |
| | | "å", |
| | | "äº", |
| | | "å
", |
| | | "ä¸", |
| | | "å
«", |
| | | "ä¹", |
| | | "å", |
| | | "åä¸", |
| | | "åäº", |
| | | "åä¸", |
| | | "åå", |
| | | "åäº", |
| | | "åå
", |
| | | "åä¸", |
| | | "åå
«", |
| | | "åä¹", |
| | | "äºå", |
| | | "äºåä¸", |
| | | "äºåäº", |
| | | "äºåä¸", |
| | | "äºåå", |
| | | "äºåäº", |
| | | "äºåå
", |
| | | "äºåä¸", |
| | | "äºåå
«", |
| | | "äºåä¹", |
| | | "ä¸å", |
| | | "ä¸åä¸", |
| | | "ä¸åäº", |
| | | "ä¸åä¸", |
| | | "ä¸åå", |
| | | "ä¸åäº", |
| | | "ä¸åå
", |
| | | "ä¸åä¸", |
| | | "ä¸åå
«", |
| | | "ä¸åä¹", |
| | | "åå", |
| | | "ååä¸", |
| | | "ååäº", |
| | | "ååä¸", |
| | | "ååå", |
| | | "ååäº", |
| | | ]; |
| | | |
| | | return data.sort((a, b) => { |
| | | // æåç
åºåç§°ä¸ç䏿æ°åé¨å |
| | | const getIndex = (name) => { |
| | | if (!name || typeof name !== "string") return -1; |
| | | |
| | | // å¹é
䏿æ°å |
| | | const chineseMatch = name.match(/^([ä¸äºä¸åäºå
ä¸å
«ä¹å]+)/); |
| | | if (chineseMatch && chineseMatch[1]) { |
| | | return order.indexOf(chineseMatch[1]); |
| | | } |
| | | |
| | | // å¹é
é¿æä¼¯æ°å |
| | | const arabicMatch = name.match(/^(\d+)/); |
| | | if (arabicMatch && arabicMatch[1]) { |
| | | const num = parseInt(arabicMatch[1], 10); |
| | | if (num >= 1 && num <= 45) { |
| | | return num - 1; // å 为æ°ç»ç´¢å¼ä»0å¼å§ |
| | | } |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | const indexA = getIndex(a.leavehospitaldistrictname); |
| | | const indexB = getIndex(b.leavehospitaldistrictname); |
| | | |
| | | // æåºé»è¾ |
| | | if (indexA === -1 && indexB === -1) { |
| | | return (a.leavehospitaldistrictname || "").localeCompare( |
| | | b.leavehospitaldistrictname || "" |
| | | ); |
| | | } |
| | | if (indexA === -1) return 1; |
| | | if (indexB === -1) return -1; |
| | | return indexA - indexB; |
| | | }); |
| | | }, |
| | | // å¤çç»è®¡ç±»ååå |
| | | handleStatisticalTypeChange(value) { |
| | | if (value === 1) { |
| | |
| | | serviceType: [2], |
| | | dateRange: [], |
| | | pageNum: 1, |
| | | pageSize: 20 |
| | | pageSize: 20, |
| | | }; |
| | | this.getList(); |
| | | }, |
| | |
| | | |
| | | // å¤çè¡éæ© |
| | | handleSelectionChange(selection) { |
| | | this.ids = selection.map(item => item.id); |
| | | this.ids = selection.map((item) => item.id); |
| | | this.single = selection.length !== 1; |
| | | this.multiple = !selection.length; |
| | | }, |
| | |
| | | |
| | | // æ ¼å¼åç¾åæ¯ |
| | | formatPercent(value) { |
| | | if (value === null || value === undefined) return '-'; |
| | | if (value === null || value === undefined) return "-"; |
| | | const num = parseFloat(value); |
| | | if (isNaN(num)) return '-'; |
| | | if (isNaN(num)) return "-"; |
| | | return `${(num * 100).toFixed(2)}%`; |
| | | }, |
| | | |
| | |
| | | // æ¥ç满æåº¦è¯¦æ
|
| | | async getinfo(row) { |
| | | this.currentRow = row; |
| | | this.topicVisible = true; |
| | | |
| | | try { |
| | | // å¤çæ¥è¯¢åæ° |
| | | const params = { |
| | | configKey: "joyCount", |
| | | ...this.queryParams |
| | | ...this.queryParams, |
| | | }; |
| | | |
| | | // å¤çæ¥æèå´ |
| | | if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) { |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | params.startTime = this.queryParams.dateRange[0]; |
| | | params.endTime = this.queryParams.dateRange[1]; |
| | | } |
| | |
| | | |
| | | const response = await getSfStatisticsJoyInfo(params); |
| | | this.topiclist = response.data || []; |
| | | this.topicVisible = true; |
| | | |
| | | } catch (error) { |
| | | console.error('è·å满æåº¦è¯¦æ
失败:', error); |
| | | this.$message.error('è·å详æ
失败'); |
| | | console.error("è·å满æåº¦è¯¦æ
失败:", error); |
| | | this.$message.error("è·å详æ
失败"); |
| | | } |
| | | }, |
| | | |
| | | // å¯¼åºæ°æ® |
| | | async handleExport() { |
| | | if (!this.userList.length) { |
| | | this.$message.warning('æ²¡ææ°æ®å¯å¯¼åº'); |
| | | this.$message.warning("æ²¡ææ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | |
| | |
| | | let dateRangeString = ""; |
| | | let sheetNameSuffix = ""; |
| | | |
| | | if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) { |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | const startDateFormatted = this.queryParams.dateRange[0]; |
| | | const endDateFormatted = this.queryParams.dateRange[1]; |
| | | dateRangeString = `${startDateFormatted}è³${endDateFormatted}`; |
| | |
| | | // å®ä¹æ ·å¼ |
| | | const titleStyle = { |
| | | font: { name: "微软é
é»", size: 16, bold: true }, |
| | | fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFE6F3FF" } }, |
| | | fill: { |
| | | type: "pattern", |
| | | pattern: "solid", |
| | | fgColor: { argb: "FFE6F3FF" }, |
| | | }, |
| | | alignment: { vertical: "middle", horizontal: "center" }, |
| | | border: { |
| | | top: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | left: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | bottom: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } } |
| | | } |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | }, |
| | | }; |
| | | |
| | | const headerStyle = { |
| | | font: { name: "微软é
é»", size: 11, bold: true }, |
| | | fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" } }, |
| | | fill: { |
| | | type: "pattern", |
| | | pattern: "solid", |
| | | fgColor: { argb: "FFF5F7FA" }, |
| | | }, |
| | | alignment: { vertical: "middle", horizontal: "center" }, |
| | | border: { |
| | | top: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | left: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | bottom: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } } |
| | | } |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | }, |
| | | }; |
| | | |
| | | const cellStyle = { |
| | |
| | | top: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | left: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | bottom: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } } |
| | | } |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | }, |
| | | }; |
| | | |
| | | // æ·»å æ»æ é¢ |
| | |
| | | "åæ¶ç", |
| | | "满æåº¦é¢ç®æ»é", |
| | | "满æåº¦å¡«æ¥é", |
| | | "宿æ¯ç" |
| | | "宿æ¯ç", |
| | | ]; |
| | | |
| | | const headerRow = worksheet.addRow(headers); |
| | |
| | | // æ·»å æ°æ®è¡ |
| | | this.userList.forEach((item) => { |
| | | const dataRow = worksheet.addRow([ |
| | | this.queryParams.statisticaltype == 1 ? item.leavehospitaldistrictname : item.deptname, |
| | | this.queryParams.statisticaltype == 1 |
| | | ? item.leavehospitaldistrictname |
| | | : item.deptname, |
| | | item.dischargeCount || 0, |
| | | item.nonFollowUp || 0, |
| | | item.followUpNeeded || 0, |
| | |
| | | item.rate ? this.formatPercent(item.rate) : "0%", |
| | | item.joyAllCount || 0, |
| | | item.joyCount || 0, |
| | | item.joyTotal ? this.formatPercent(item.joyTotal) : "0%" |
| | | item.joyTotal ? this.formatPercent(item.joyTotal) : "0%", |
| | | ]); |
| | | |
| | | dataRow.eachCell((cell) => { |
| | |
| | | { width: 12 }, |
| | | { width: 15 }, |
| | | { width: 15 }, |
| | | { width: 12 } |
| | | { width: 12 }, |
| | | ]; |
| | | |
| | | // çæå¹¶ä¸è½½æä»¶ |
| | | const buffer = await workbook.xlsx.writeBuffer(); |
| | | const blob = new Blob([buffer], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | |
| | | saveAs(blob, excelName); |
| | |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | |
| | | <template> |
| | | <div class="satisfaction-statistics"> |
| | | <!-- æ¥è¯¢æ¡ä»¶åºå --> |
| | | <div class="query-section"> |
| | | <el-form |
| | | :model="queryParams" |
| | | ref="queryForm" |
| | | size="medium" |
| | | :inline="true" |
| | | label-width="100px" |
| | | class="query-form" |
| | | > |
| | | <el-form-item label="æ£è
æ¥æº" prop="patientSource"> |
| | | <el-select |
| | | v-model="queryParams.patientSource" |
| | | placeholder="è¯·éæ©æ£è
æ¥æº" |
| | | clearable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="source in patientSourceList" |
| | | :key="source.value" |
| | | :label="source.label" |
| | | :value="source.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç§å®¤" prop="deptCode"> |
| | | <el-select |
| | | v-model="queryParams.deptCode" |
| | | placeholder="è¯·éæ©ç§å®¤" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.value" |
| | | :label="dept.label" |
| | | :value="dept.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç
åº" prop="wardCode"> |
| | | <el-select |
| | | v-model="queryParams.wardCode" |
| | | placeholder="è¯·éæ©ç
åº" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="ward in wardList" |
| | | :key="ward.value" |
| | | :label="ward.label" |
| | | :value="ward.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç»è®¡æ¶é´" prop="dateRange"> |
| | | <el-date-picker |
| | | v-model="queryParams.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="yyyy-MM-dd" |
| | | :picker-options="pickerOptions" |
| | | style="width: 380px" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | icon="el-icon-search" |
| | | @click="handleSearch" |
| | | :loading="loading" |
| | | > |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | <el-button icon="el-icon-refresh" @click="handleReset"> |
| | | éç½® |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- 满æåº¦åç±»ç»è®¡å¾è¡¨ --> |
| | | <div class="chart-section"> |
| | | <div class="chart-container"> |
| | | <div class="chart-title">满æåº¦ç±»åç»è®¡</div> |
| | | <div id="satisfactionBarChart" style="width: 100%; height: 400px"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- é¢ç®æç»è¡¨æ ¼ --> |
| | | <div class="detail-table-section"> |
| | | <div class="section-title">é¢ç®æç»ç»è®¡</div> |
| | | |
| | | <el-table |
| | | v-loading="detailLoading" |
| | | :data="questionDetailData" |
| | | :border="true" |
| | | style="width: 100%" |
| | | row-class-name="question-row" |
| | | > |
| | | <el-table-column |
| | | type="expand" |
| | | width="60" |
| | | <el-card shadow="never"> |
| | | <el-form |
| | | :model="queryParams" |
| | | ref="queryForm" |
| | | size="medium" |
| | | :inline="true" |
| | | label-width="100px" |
| | | class="query-form" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <div class="option-detail"> |
| | | <el-form-item label="æ£è
æ¥æº" prop="patientSource"> |
| | | <el-select |
| | | v-model="queryParams.patientSource" |
| | | placeholder="è¯·éæ©æ£è
æ¥æº" |
| | | clearable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="source in patientSourceList" |
| | | :key="source.value" |
| | | :label="source.label" |
| | | :value="source.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç§å®¤" prop="deptCode"> |
| | | <el-select |
| | | v-model="queryParams.deptCode" |
| | | placeholder="è¯·éæ©ç§å®¤" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.value" |
| | | :label="dept.label" |
| | | :value="dept.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç
åº" prop="wardCode"> |
| | | <el-select |
| | | v-model="queryParams.wardCode" |
| | | placeholder="è¯·éæ©ç
åº" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="ward in wardList" |
| | | :key="ward.value" |
| | | :label="ward.label" |
| | | :value="ward.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç»è®¡æ¶é´" prop="dateRange"> |
| | | <el-date-picker |
| | | v-model="queryParams.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="yyyy-MM-dd" |
| | | :picker-options="pickerOptions" |
| | | style="width: 380px" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | icon="el-icon-search" |
| | | @click="handleSearch" |
| | | :loading="loading" |
| | | > |
| | | æ¥è¯¢ |
| | | </el-button> |
| | | <el-button icon="el-icon-refresh" @click="handleReset"> |
| | | éç½® |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- 满æåº¦ç±»åç»è®¡å¾è¡¨ --> |
| | | <div class="chart-section"> |
| | | <el-card shadow="never"> |
| | | <div class="chart-container"> |
| | | <div class="chart-header"> |
| | | <h3 class="chart-title">满æåº¦ç±»åå¡«æ¥æ¯ä¾ç»è®¡</h3> |
| | | <div class="statistic-info"> |
| | | <div class="statistic-item"> |
| | | <span class="statistic-label">åéé®å·æ»éï¼</span> |
| | | <span class="statistic-value">{{ |
| | | totalSendCount.toLocaleString() |
| | | }}</span> |
| | | </div> |
| | | <div class="statistic-item"> |
| | | <span class="statistic-label">åæ¶é®å·æ»éï¼</span> |
| | | <span class="statistic-value">{{ |
| | | totalReceiveCount.toLocaleString() |
| | | }}</span> |
| | | </div> |
| | | <div class="statistic-item"> |
| | | <span class="statistic-label">æ»ä½åæ¶çï¼</span> |
| | | <span class="statistic-value">{{ |
| | | formatPercent(overallRecoveryRate) |
| | | }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div |
| | | id="satisfactionBarChart" |
| | | style="width: 100%; height: 400px" |
| | | ></div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- Tabæ ç¾é¡µ --> |
| | | <div class="tab-section"> |
| | | <el-card shadow="never"> |
| | | <el-tabs v-model="activeTab" @tab-click="handleTabClick"> |
| | | <el-tab-pane label="é¢ç®æç»ç»è®¡" name="questionDetail"> |
| | | <!-- é¢ç®æç»è¡¨æ ¼ --> |
| | | <div class="detail-table-section"> |
| | | <el-table |
| | | :data="row.options" |
| | | v-loading="detailLoading" |
| | | :data="questionDetailData" |
| | | :border="true" |
| | | style="width: 100%" |
| | | class="inner-table" |
| | | row-class-name="question-row" |
| | | > |
| | | <el-table-column type="expand" width="60"> |
| | | <template slot-scope="{ row }"> |
| | | <div class="option-detail"> |
| | | <el-table |
| | | :data="row.options" |
| | | :border="true" |
| | | style="width: 100%" |
| | | class="inner-table" |
| | | > |
| | | <el-table-column |
| | | label="é项" |
| | | prop="optionText" |
| | | align="center" |
| | | min-width="200" |
| | | /> |
| | | <el-table-column |
| | | label="éæ©äººæ°" |
| | | prop="chosenQuantity" |
| | | align="center" |
| | | min-width="120" |
| | | /> |
| | | <el-table-column |
| | | label="éæ©æ¯ä¾" |
| | | prop="chosenPercentage" |
| | | align="center" |
| | | min-width="120" |
| | | > |
| | | <template slot-scope="{ row: option }"> |
| | | {{ formatPercent(option.chosenPercentage) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="é项" |
| | | prop="optionText" |
| | | label="åºå·" |
| | | type="index" |
| | | align="center" |
| | | min-width="200" |
| | | width="60" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="éæ©äººæ°" |
| | | prop="chosenQuantity" |
| | | label="é¢ç®" |
| | | prop="scriptContent" |
| | | align="center" |
| | | min-width="120" |
| | | /> |
| | | <el-table-column |
| | | label="éæ©æ¯ä¾" |
| | | prop="chosenPercentage" |
| | | align="center" |
| | | min-width="120" |
| | | min-width="300" |
| | | > |
| | | <template slot-scope="{ row: option }"> |
| | | {{ formatPercent(option.chosenPercentage) }} |
| | | <template slot-scope="{ row }"> |
| | | <span>{{ row.scriptContent }}?</span> |
| | | <el-tag |
| | | :type="row.scriptType === 1 ? 'primary' : 'success'" |
| | | size="mini" |
| | | style="margin-left: 5px" |
| | | > |
| | | {{ row.scriptType === 1 ? "åéé¢" : "å¤éé¢" }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="å¹³åå¾å" |
| | | prop="averageScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ |
| | | row.averageScore.toFixed(1) |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æé«å¾å" |
| | | prop="maxScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ |
| | | row.maxScore.toFixed(1) |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æä½å¾å" |
| | | prop="minScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ |
| | | row.minScore.toFixed(1) |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="çé¢äººæ°" |
| | | prop="answerCount" |
| | | align="center" |
| | | width="100" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="æªçé¢äººæ°" |
| | | prop="unanswerCount" |
| | | align="center" |
| | | width="100" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | {{ row.totalCount - row.answerCount }} |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="çé¢ç" |
| | | prop="answerRate" |
| | | align="center" |
| | | width="100" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | {{ formatPercent(row.answerCount / row.totalCount) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 综åå¾åè¡ --> |
| | | <div class="summary-row"> |
| | | <div class="summary-content"> |
| | | <div class="summary-item"> |
| | | <span class="label">综åå¾åï¼</span> |
| | | <span class="value">{{ totalScore.toFixed(1) }}</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="label">æ»çé¢äººæ°ï¼</span> |
| | | <span class="value">{{ totalAnswerCount }}</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="label">æ»çé¢çï¼</span> |
| | | <span class="value">{{ |
| | | formatPercent(totalAnswerRate) |
| | | }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å页 --> |
| | | <div |
| | | class="pagination-section" |
| | | v-if="questionDetailData.length > 0" |
| | | > |
| | | <el-pagination |
| | | background |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :current-page="detailQueryParams.pageNum" |
| | | :page-size="detailQueryParams.pageSize" |
| | | :page-sizes="[10, 20, 30]" |
| | | :total="detailTotal" |
| | | @size-change="handleDetailSizeChange" |
| | | @current-change="handleDetailPageChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-tab-pane> |
| | | |
| | | <el-table-column |
| | | label="åºå·" |
| | | type="index" |
| | | align="center" |
| | | width="60" |
| | | /> |
| | | <el-tab-pane label="åç±»åç»è®¡æç»" name="typeDetail"> |
| | | <!-- åç±»åç»è®¡æç»è¡¨æ ¼ --> |
| | | <div class="type-detail-section"> |
| | | <el-table |
| | | v-loading="typeDetailLoading" |
| | | :data="typeDetailData" |
| | | :border="true" |
| | | style="width: 100%" |
| | | class="type-detail-table" |
| | | > |
| | | <el-table-column |
| | | label="åºå·" |
| | | type="index" |
| | | align="center" |
| | | width="60" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="é¢ç®" |
| | | prop="scriptContent" |
| | | align="center" |
| | | min-width="300" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span>{{ row.scriptContent }}?</span> |
| | | <el-tag |
| | | :type="row.scriptType === 1 ? 'primary' : 'success'" |
| | | size="mini" |
| | | style="margin-left: 5px" |
| | | > |
| | | {{ row.scriptType === 1 ? 'åéé¢' : 'å¤éé¢' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="满æåº¦ç±»å" |
| | | prop="typeName" |
| | | align="center" |
| | | min-width="150" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <div class="type-name-cell"> |
| | | <span class="type-name">{{ row.typeName }}</span> |
| | | <el-tag |
| | | v-if="row.isSpecial" |
| | | type="warning" |
| | | size="mini" |
| | | style="margin-left: 5px" |
| | | > |
| | | ç¹æ® |
| | | </el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="å¹³åå¾å" |
| | | prop="averageScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ row.averageScore.toFixed(1) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="åéé®å·æ°" |
| | | prop="sendCount" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="number-text">{{ |
| | | row.sendCount.toLocaleString() |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æé«å¾å" |
| | | prop="maxScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ row.maxScore.toFixed(1) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="åæ¶é®å·æ°" |
| | | prop="receiveCount" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="number-text">{{ |
| | | row.receiveCount.toLocaleString() |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æä½å¾å" |
| | | prop="minScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ row.minScore.toFixed(1) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="åæ¶ç" |
| | | prop="recoveryRate" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span |
| | | class="rate-text" |
| | | :class="getRateClass(row.recoveryRate)" |
| | | > |
| | | {{ formatPercent(row.recoveryRate) }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="çé¢äººæ°" |
| | | prop="answerCount" |
| | | align="center" |
| | | width="100" |
| | | /> |
| | | <el-table-column |
| | | label="å¹³åå" |
| | | prop="averageScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ |
| | | row.averageScore.toFixed(1) |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æªçé¢äººæ°" |
| | | prop="unanswerCount" |
| | | align="center" |
| | | width="100" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | {{ row.totalCount - row.answerCount }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="æé«å" |
| | | prop="maxScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ |
| | | row.maxScore.toFixed(1) |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="çé¢ç" |
| | | prop="answerRate" |
| | | align="center" |
| | | width="100" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | {{ formatPercent(row.answerCount / row.totalCount) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-table-column |
| | | label="æä½å" |
| | | prop="minScore" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span class="score-text">{{ |
| | | row.minScore.toFixed(1) |
| | | }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <!-- 综åå¾åè¡ --> |
| | | <div class="summary-row"> |
| | | <div class="summary-content"> |
| | | <div class="summary-item"> |
| | | <span class="label">综åå¾åï¼</span> |
| | | <span class="value">{{ totalScore.toFixed(1) }}</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="label">æ»çé¢äººæ°ï¼</span> |
| | | <span class="value">{{ totalAnswerCount }}</span> |
| | | </div> |
| | | <div class="summary-item"> |
| | | <span class="label">æ»çé¢çï¼</span> |
| | | <span class="value">{{ formatPercent(totalAnswerRate) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <el-table-column |
| | | label="满æåº¦ç级" |
| | | prop="satisfactionLevel" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <el-tag |
| | | :type="getLevelTagType(row.satisfactionLevel)" |
| | | effect="dark" |
| | | size="small" |
| | | > |
| | | {{ row.satisfactionLevel }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <!-- å页 --> |
| | | <div class="pagination-section" v-if="questionDetailData.length > 0"> |
| | | <el-pagination |
| | | background |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :current-page="detailQueryParams.pageNum" |
| | | :page-size="detailQueryParams.pageSize" |
| | | :page-sizes="[10, 20, 30]" |
| | | :total="detailTotal" |
| | | @size-change="handleDetailSizeChange" |
| | | @current-change="handleDetailPageChange" |
| | | /> |
| | | <el-table-column |
| | | label="è¶å¿" |
| | | prop="trend" |
| | | align="center" |
| | | width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <div class="trend-cell"> |
| | | <i |
| | | v-if="row.trend === 'up'" |
| | | class="el-icon-top trend-up" |
| | | :style="{ color: '#67C23A' }" |
| | | /> |
| | | <i |
| | | v-else-if="row.trend === 'down'" |
| | | class="el-icon-bottom trend-down" |
| | | :style="{ color: '#F56C6C' }" |
| | | /> |
| | | <i |
| | | v-else |
| | | class="el-icon-minus trend-stable" |
| | | :style="{ color: '#909399' }" |
| | | /> |
| | | <span class="trend-text">{{ |
| | | row.trend === "up" |
| | | ? "ä¸å" |
| | | : row.trend === "down" |
| | | ? "ä¸é" |
| | | : "稳å®" |
| | | }}</span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="æä½" |
| | | align="center" |
| | | width="120" |
| | | fixed="right" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | @click="handleTypeDetail(row)" |
| | | > |
| | | 详æ
|
| | | </el-button> |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | @click="handleExportData(row)" |
| | | > |
| | | å¯¼åº |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- ç±»åç»è®¡æ±æ» --> |
| | | <div class="type-summary-row"> |
| | | <div class="type-summary-content"> |
| | | <div class="type-summary-item"> |
| | | <span class="label">ç±»åæ»æ°ï¼</span> |
| | | <span class="value">{{ typeDetailData.length }}</span> |
| | | </div> |
| | | <div class="type-summary-item"> |
| | | <span class="label">å¹³ååæ¶çï¼</span> |
| | | <span class="value">{{ |
| | | formatPercent(averageRecoveryRate) |
| | | }}</span> |
| | | </div> |
| | | <div class="type-summary-item"> |
| | | <span class="label">ç±»åå¹³ååï¼</span> |
| | | <span class="value">{{ averageTypeScore.toFixed(1) }}</span> |
| | | </div> |
| | | <div class="type-summary-item"> |
| | | <span class="label">髿»¡æåº¦ç±»åï¼</span> |
| | | <span class="value high-count" |
| | | >{{ highSatisfactionCount }} 个</span |
| | | > |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from "echarts"; |
| | | |
| | | export default { |
| | | name: 'SatisfactionStatistics', |
| | | name: "SatisfactionStatistics", |
| | | data() { |
| | | return { |
| | | // æ¥è¯¢åæ° |
| | | queryParams: { |
| | | patientSource: '', |
| | | deptCode: '', |
| | | wardCode: '', |
| | | dateRange: [] |
| | | patientSource: "", |
| | | deptCode: "", |
| | | wardCode: "", |
| | | dateRange: [], |
| | | }, |
| | | |
| | | // å½åæ¿æ´»çtab |
| | | activeTab: "questionDetail", |
| | | |
| | | // æ£è
æ¥æºé项 |
| | | patientSourceList: [ |
| | | { value: '1', label: 'é¨è¯' }, |
| | | { value: '2', label: 'ä½é¢' }, |
| | | { value: '3', label: 'æ¥è¯' }, |
| | | { value: '4', label: '使£' } |
| | | { value: "1", label: "é¨è¯" }, |
| | | { value: "2", label: "ä½é¢" }, |
| | | { value: "3", label: "æ¥è¯" }, |
| | | { value: "4", label: "åºé¢" }, |
| | | ], |
| | | |
| | | // ç§å®¤å表 |
| | |
| | | // å è½½ç¶æ |
| | | loading: false, |
| | | detailLoading: false, |
| | | typeDetailLoading: false, |
| | | |
| | | // é¢ç®æç»æ°æ® |
| | | questionDetailData: [], |
| | |
| | | // é¢ç®æç»æ¥è¯¢åæ° |
| | | detailQueryParams: { |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | pageSize: 10, |
| | | }, |
| | | |
| | | // é¢ç®æç»æ»æ° |
| | |
| | | totalAnswerCount: 0, |
| | | totalAnswerRate: 0, |
| | | |
| | | // åç±»åç»è®¡æç»æ°æ® |
| | | typeDetailData: [], |
| | | |
| | | // ç»è®¡ä¿¡æ¯ |
| | | totalSendCount: 12560, |
| | | totalReceiveCount: 10240, |
| | | overallRecoveryRate: 0, |
| | | |
| | | // ç±»åç»è®¡æ±æ» |
| | | averageRecoveryRate: 0, |
| | | averageTypeScore: 0, |
| | | highSatisfactionCount: 0, |
| | | |
| | | // æ¥æéæ©å¨é项 |
| | | pickerOptions: { |
| | | shortcuts: [ |
| | | { |
| | | text: 'æè¿ä¸å¨', |
| | | text: "æè¿ä¸å¨", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: 'æè¿ä¸ä¸ªæ', |
| | | text: "æè¿ä¸ä¸ªæ", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: 'æè¿ä¸ä¸ªæ', |
| | | text: "æè¿ä¸ä¸ªæ", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | ], |
| | | disabledDate(time) { |
| | | return time.getTime() > Date.now(); |
| | | } |
| | | }, |
| | | }, |
| | | |
| | | // Mockæ°æ® - 满æåº¦åç±» |
| | | mockSatisfactionCategories: ['æå¡æåº¦', 'ææ¯æ°´å¹³', 'ç¯å¢è®¾æ½', 'æ²éææ', 'çå¾
æ¶é´', 'æ¶è´¹åçæ§'], |
| | | // Mockæ°æ® - 满æåº¦ç±»å |
| | | mockSatisfactionTypes: [ |
| | | { name: 'é常满æ', value: 85, color: '#36B37E' }, |
| | | { name: '满æ', value: 72, color: '#4CAF50' }, |
| | | { name: 'ä¸è¬', value: 60, color: '#FF9D4D' }, |
| | | { name: '䏿»¡æ', value: 15, color: '#FF5C5C' }, |
| | | { name: 'é叏䏿»¡æ', value: 5, color: '#F44336' } |
| | | ] |
| | | // 满æåº¦ç±»åæ°æ® |
| | | satisfactionTypes: [ |
| | | { id: 401, name: "åºé¢æ»¡æåº¦", color: "#36B37E" }, |
| | | { id: 402, name: "ä½é¢æ»¡æåº¦", color: "#4CAF50" }, |
| | | { id: 403, name: "é¨è¯æ»¡æåº¦", color: "#409EFF" }, |
| | | { id: 404, name: "å¸¸ç¨æ»¡æåº¦", color: "#FF9D4D" }, |
| | | ], |
| | | }; |
| | | }, |
| | | |
| | | mounted() { |
| | | this.initData(); |
| | | this.initChart(); |
| | | }, |
| | | |
| | | beforeDestroy() { |
| | |
| | | async initData() { |
| | | await this.getDeptList(); |
| | | await this.getWardList(); |
| | | this.initChart(); |
| | | await this.loadData(); |
| | | }, |
| | | |
| | | // è·åç§å®¤å表 |
| | | getDeptList() { |
| | | // 模æAPIè°ç¨è·åç§å®¤å表 |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | this.deptList = [ |
| | | { value: 'dept001', label: 'å¿è¡ç®¡å
ç§' }, |
| | | { value: 'dept002', label: 'ç¥ç»å
ç§' }, |
| | | { value: 'dept003', label: 'æ®å¤ç§' }, |
| | | { value: 'dept004', label: '骨ç§' }, |
| | | { value: 'dept005', label: 'å¦äº§ç§' }, |
| | | { value: 'dept006', label: 'å¿ç§' } |
| | | { value: "dept001", label: "å¿è¡ç®¡å
ç§" }, |
| | | { value: "dept002", label: "ç¥ç»å
ç§" }, |
| | | { value: "dept003", label: "æ®å¤ç§" }, |
| | | { value: "dept004", label: "骨ç§" }, |
| | | { value: "dept005", label: "å¦äº§ç§" }, |
| | | { value: "dept006", label: "å¿ç§" }, |
| | | ]; |
| | | resolve(); |
| | | }, 100); |
| | |
| | | |
| | | // è·åç
åºå表 |
| | | getWardList() { |
| | | // 模æAPIè°ç¨è·åç
åºå表 |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | this.wardList = [ |
| | | { value: 'ward001', label: 'å
ç§ä¸ç
åº' }, |
| | | { value: 'ward002', label: 'å
ç§äºç
åº' }, |
| | | { value: 'ward003', label: 'å¤ç§ä¸ç
åº' }, |
| | | { value: 'ward004', label: 'å¤ç§äºç
åº' }, |
| | | { value: 'ward005', label: 'å¦äº§ç§ç
åº' }, |
| | | { value: 'ward006', label: 'å¿ç§ç
åº' } |
| | | { value: "ward001", label: "å
ç§ä¸ç
åº" }, |
| | | { value: "ward002", label: "å
ç§äºç
åº" }, |
| | | { value: "ward003", label: "å¤ç§ä¸ç
åº" }, |
| | | { value: "ward004", label: "å¤ç§äºç
åº" }, |
| | | { value: "ward005", label: "å¦äº§ç§ç
åº" }, |
| | | { value: "ward006", label: "å¿ç§ç
åº" }, |
| | | ]; |
| | | resolve(); |
| | | }, 100); |
| | |
| | | async loadData() { |
| | | await Promise.all([ |
| | | this.loadChartData(), |
| | | this.loadQuestionDetailData() |
| | | this.loadQuestionDetailData(), |
| | | this.loadTypeDetailData(), |
| | | ]); |
| | | }, |
| | | |
| | | // å è½½å¾è¡¨æ°æ® |
| | | loadChartData() { |
| | | async loadChartData() { |
| | | this.loading = true; |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | this.renderChart(this.generateChartData()); |
| | | this.loading = false; |
| | | resolve(); |
| | | }, 500); |
| | | }); |
| | | try { |
| | | // 模æAPIè°ç¨ |
| | | const chartData = await this.generateChartData(); |
| | | this.renderChart(chartData); |
| | | |
| | | // è®¡ç®æ»ä½åæ¶ç |
| | | this.overallRecoveryRate = this.totalReceiveCount / this.totalSendCount; |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | | // å è½½é¢ç®æç»æ°æ® |
| | | loadQuestionDetailData() { |
| | | async loadQuestionDetailData() { |
| | | this.detailLoading = true; |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | const mockData = this.generateMockQuestionDetail(); |
| | | this.questionDetailData = mockData.list; |
| | | this.detailTotal = mockData.total; |
| | | try { |
| | | const mockData = await this.generateMockQuestionDetail(); |
| | | this.questionDetailData = mockData.list; |
| | | this.detailTotal = mockData.total; |
| | | this.calculateSummary(mockData); |
| | | } finally { |
| | | this.detailLoading = false; |
| | | } |
| | | }, |
| | | |
| | | // 计ç®ç»¼åå¾å |
| | | this.calculateSummary(mockData); |
| | | this.detailLoading = false; |
| | | resolve(); |
| | | }, 500); |
| | | }); |
| | | // å 载类åæç»æ°æ® |
| | | async loadTypeDetailData() { |
| | | this.typeDetailLoading = true; |
| | | try { |
| | | const mockData = await this.generateMockTypeDetail(); |
| | | this.typeDetailData = mockData; |
| | | |
| | | // 计ç®ç±»åç»è®¡æ±æ» |
| | | this.calculateTypeSummary(mockData); |
| | | } finally { |
| | | this.typeDetailLoading = false; |
| | | } |
| | | }, |
| | | |
| | | // 计ç®ç»¼åå¾å |
| | |
| | | let totalAnswerCount = 0; |
| | | let totalCount = 0; |
| | | |
| | | data.list.forEach(item => { |
| | | data.list.forEach((item) => { |
| | | totalScore += item.averageScore; |
| | | totalAnswerCount += item.answerCount; |
| | | totalCount += item.totalCount; |
| | | }); |
| | | |
| | | this.totalScore = data.list.length > 0 ? totalScore / data.list.length : 0; |
| | | this.totalScore = |
| | | data.list.length > 0 ? totalScore / data.list.length : 0; |
| | | this.totalAnswerCount = totalAnswerCount; |
| | | this.totalAnswerRate = totalCount > 0 ? totalAnswerCount / totalCount : 0; |
| | | }, |
| | | |
| | | // 计ç®ç±»åç»è®¡æ±æ» |
| | | calculateTypeSummary(data) { |
| | | if (data.length === 0) { |
| | | this.averageRecoveryRate = 0; |
| | | this.averageTypeScore = 0; |
| | | this.highSatisfactionCount = 0; |
| | | return; |
| | | } |
| | | |
| | | let totalRecoveryRate = 0; |
| | | let totalTypeScore = 0; |
| | | let highCount = 0; |
| | | |
| | | data.forEach((item) => { |
| | | totalRecoveryRate += item.recoveryRate; |
| | | totalTypeScore += item.averageScore; |
| | | if ( |
| | | item.satisfactionLevel === "ä¼ç§" || |
| | | item.satisfactionLevel === "è¯å¥½" |
| | | ) { |
| | | highCount++; |
| | | } |
| | | }); |
| | | |
| | | this.averageRecoveryRate = totalRecoveryRate / data.length; |
| | | this.averageTypeScore = totalTypeScore / data.length; |
| | | this.highSatisfactionCount = highCount; |
| | | }, |
| | | |
| | | // åå§åå¾è¡¨ |
| | | initChart() { |
| | | const echarts = require('echarts'); |
| | | const chartDom = document.getElementById('satisfactionBarChart'); |
| | | const chartDom = document.getElementById("satisfactionBarChart"); |
| | | if (!chartDom) return; |
| | | |
| | | this.barChart = echarts.init(chartDom); |
| | | |
| | | // çå¬çªå£åå |
| | | window.addEventListener('resize', this.handleChartResize); |
| | | window.addEventListener("resize", this.handleChartResize); |
| | | }, |
| | | |
| | | // çæå¾è¡¨æ°æ® |
| | | generateChartData() { |
| | | const categories = this.mockSatisfactionCategories; |
| | | const series = this.mockSatisfactionTypes.map(type => ({ |
| | | name: type.name, |
| | | type: 'bar', |
| | | barWidth: 25, |
| | | stack: '满æåº¦', |
| | | data: categories.map(() => Math.floor(Math.random() * 20) + 10), // éæºæ°æ® |
| | | itemStyle: { |
| | | color: type.color |
| | | } |
| | | })); |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | const data = this.satisfactionTypes.map((type) => ({ |
| | | name: type.name, |
| | | recoveryRate: Math.random() * 0.3 + 0.6, // 60%-90%çåæ¶ç |
| | | sendCount: Math.floor(Math.random() * 3000) + 1500, // 1500-4500 |
| | | receiveCount: 0, |
| | | color: type.color, |
| | | })); |
| | | |
| | | return { |
| | | categories, |
| | | legend: this.mockSatisfactionTypes.map(type => type.name), |
| | | series |
| | | }; |
| | | // 计ç®åæ¶æ°é |
| | | data.forEach((item) => { |
| | | item.receiveCount = Math.floor(item.sendCount * item.recoveryRate); |
| | | }); |
| | | |
| | | // æ´æ°æ»é |
| | | this.totalSendCount = data.reduce( |
| | | (sum, item) => sum + item.sendCount, |
| | | 0 |
| | | ); |
| | | this.totalReceiveCount = data.reduce( |
| | | (sum, item) => sum + item.receiveCount, |
| | | 0 |
| | | ); |
| | | |
| | | resolve({ |
| | | data: data.map((item) => ({ |
| | | name: item.name, |
| | | value: item.recoveryRate * 100, // 转æ¢ä¸ºç¾åæ¯ |
| | | sendCount: item.sendCount, |
| | | receiveCount: item.receiveCount, |
| | | itemStyle: { color: item.color }, |
| | | })), |
| | | }); |
| | | }, 300); |
| | | }); |
| | | }, |
| | | |
| | | // 渲æå¾è¡¨ |
| | |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '满æåº¦ç±»åç»è®¡', |
| | | left: 'center', |
| | | textStyle: { |
| | | fontSize: 16, |
| | | fontWeight: 'normal', |
| | | color: '#333' |
| | | } |
| | | text: "", |
| | | left: "center", |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | trigger: "axis", |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | type: "shadow", |
| | | }, |
| | | formatter: (params) => { |
| | | let result = `<div style="margin-bottom: 5px; font-weight: bold;">${params[0].name}</div>`; |
| | | let total = 0; |
| | | |
| | | params.forEach(param => { |
| | | result += `<div style="margin: 2px 0;"> |
| | | <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:5px;"></span> |
| | | ${param.seriesName}: <strong>${param.value}%</strong> |
| | | </div>`; |
| | | total += param.value; |
| | | }); |
| | | |
| | | result += `<div style="margin-top: 5px; padding-top: 5px; border-top: 1px solid #eee;"> |
| | | <strong>æ»è®¡: ${total}%</strong> |
| | | </div>`; |
| | | return result; |
| | | } |
| | | }, |
| | | legend: { |
| | | data: chartData.legend, |
| | | top: 20, |
| | | textStyle: { |
| | | fontSize: 12, |
| | | color: '#666' |
| | | } |
| | | const data = params[0]; |
| | | return ` |
| | | <div style="margin-bottom: 5px; font-weight: bold; color: #333;"> |
| | | ${data.name} |
| | | </div> |
| | | <div style="margin: 2px 0;"> |
| | | <span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:${ |
| | | data.color |
| | | };margin-right:5px;"></span> |
| | | å¡«æ¥æ¯ä¾: <strong>${data.value.toFixed(1)}%</strong> |
| | | </div> |
| | | <div style="margin: 2px 0;"> |
| | | <span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#eee;margin-right:5px;"></span> |
| | | åéé®å·: <strong>${data.data.sendCount.toLocaleString()}</strong> |
| | | </div> |
| | | <div style="margin: 2px 0;"> |
| | | <span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#eee;margin-right:5px;"></span> |
| | | åæ¶é®å·: <strong>${data.data.receiveCount.toLocaleString()}</strong> |
| | | </div> |
| | | `; |
| | | }, |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: 80, |
| | | containLabel: true |
| | | left: "3%", |
| | | right: "4%", |
| | | bottom: "3%", |
| | | top: 60, |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: chartData.categories, |
| | | type: "category", |
| | | data: chartData.data.map((item) => item.name), |
| | | axisLabel: { |
| | | interval: 0, |
| | | rotate: 0, |
| | | fontSize: 12, |
| | | color: '#666' |
| | | color: "#666", |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#DCDFE6' |
| | | } |
| | | color: "#DCDFE6", |
| | | }, |
| | | }, |
| | | axisTick: { |
| | | alignWithLabel: true |
| | | } |
| | | alignWithLabel: true, |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'ç¾åæ¯ (%)', |
| | | type: "value", |
| | | name: "å¡«æ¥æ¯ä¾ (%)", |
| | | min: 0, |
| | | max: 100, |
| | | axisLabel: { |
| | | formatter: '{value}%', |
| | | color: '#666' |
| | | formatter: "{value}%", |
| | | color: "#666", |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#DCDFE6' |
| | | } |
| | | color: "#DCDFE6", |
| | | }, |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | type: 'dashed', |
| | | color: '#E4E7ED' |
| | | } |
| | | } |
| | | type: "dashed", |
| | | color: "#E4E7ED", |
| | | }, |
| | | }, |
| | | }, |
| | | series: chartData.series |
| | | series: [ |
| | | { |
| | | name: "å¡«æ¥æ¯ä¾", |
| | | type: "bar", |
| | | barWidth: 40, |
| | | data: chartData.data, |
| | | itemStyle: { |
| | | color: (params) => { |
| | | return params.data.itemStyle.color; |
| | | }, |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: "top", |
| | | formatter: "{c}%", |
| | | fontSize: 12, |
| | | color: "#333", |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | this.barChart.setOption(option); |
| | |
| | | |
| | | // çæMocké¢ç®è¯¦æ
æ°æ® |
| | | generateMockQuestionDetail() { |
| | | const questions = [ |
| | | { |
| | | scriptContent: 'æ¨å¯¹å»æ¤äººåçæå¡æåº¦æ¯å¦æ»¡æ', |
| | | scriptType: 1, // 1: åéé¢, 2: å¤éé¢ |
| | | totalCount: 156, |
| | | answerCount: 145, |
| | | averageScore: 4.5, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { optionText: 'é常满æ', chosenQuantity: 89, chosenPercentage: 0.61 }, |
| | | { optionText: '满æ', chosenQuantity: 45, chosenPercentage: 0.31 }, |
| | | { optionText: 'ä¸è¬', chosenQuantity: 8, chosenPercentage: 0.06 }, |
| | | { optionText: '䏿»¡æ', chosenQuantity: 2, chosenPercentage: 0.01 }, |
| | | { optionText: 'é叏䏿»¡æ', chosenQuantity: 1, chosenPercentage: 0.01 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨å¯¹å»ççè¯çæ°´å¹³åææ¯è½åè¯ä»·å¦ä½', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 142, |
| | | averageScore: 4.7, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { optionText: 'é常ä¸ä¸', chosenQuantity: 95, chosenPercentage: 0.67 }, |
| | | { optionText: 'æ¯è¾ä¸ä¸', chosenQuantity: 40, chosenPercentage: 0.28 }, |
| | | { optionText: 'ä¸è¬', chosenQuantity: 5, chosenPercentage: 0.04 }, |
| | | { optionText: 'ä¸å¤ä¸ä¸', chosenQuantity: 2, chosenPercentage: 0.01 }, |
| | | { optionText: 'é常ä¸ä¸ä¸', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨å¯¹å»é¢çç¯å¢åå«çç¶åµæ¯å¦æ»¡æ', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 138, |
| | | averageScore: 4.3, |
| | | maxScore: 5, |
| | | minScore: 2, |
| | | options: [ |
| | | { optionText: 'é常满æ', chosenQuantity: 75, chosenPercentage: 0.54 }, |
| | | { optionText: '满æ', chosenQuantity: 50, chosenPercentage: 0.36 }, |
| | | { optionText: 'ä¸è¬', chosenQuantity: 10, chosenPercentage: 0.07 }, |
| | | { optionText: '䏿»¡æ', chosenQuantity: 3, chosenPercentage: 0.02 }, |
| | | { optionText: 'é叏䏿»¡æ', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨è®¤ä¸ºå»æ¤äººå䏿¨çæ²éæ¯å¦å
å', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 140, |
| | | averageScore: 4.6, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { optionText: 'æ²éé常å
å', chosenQuantity: 85, chosenPercentage: 0.61 }, |
| | | { optionText: 'æ²éæ¯è¾å
å', chosenQuantity: 45, chosenPercentage: 0.32 }, |
| | | { optionText: 'æ²éä¸è¬', chosenQuantity: 8, chosenPercentage: 0.06 }, |
| | | { optionText: 'æ²éä¸å¤å
å', chosenQuantity: 2, chosenPercentage: 0.01 }, |
| | | { optionText: 'æ²éé常ä¸å
å', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨å¯¹çå¾
å°±è¯åæ²»ççæ¶é´æ¯å¦æ»¡æ', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 135, |
| | | averageScore: 4.0, |
| | | maxScore: 5, |
| | | minScore: 1, |
| | | options: [ |
| | | { optionText: 'çå¾
æ¶é´å¾ç', chosenQuantity: 60, chosenPercentage: 0.44 }, |
| | | { optionText: 'çå¾
æ¶é´åç', chosenQuantity: 55, chosenPercentage: 0.41 }, |
| | | { optionText: 'çå¾
æ¶é´è¾é¿', chosenQuantity: 15, chosenPercentage: 0.11 }, |
| | | { optionText: 'çå¾
æ¶é´å¾é¿', chosenQuantity: 5, chosenPercentage: 0.04 }, |
| | | { optionText: 'æ æ³å¿åççå¾
', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨å¯¹å»é¢æ¶è´¹çéæåº¦ååçæ§è¯ä»·å¦ä½', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 130, |
| | | averageScore: 4.2, |
| | | maxScore: 5, |
| | | minScore: 2, |
| | | options: [ |
| | | { optionText: 'é常éæåç', chosenQuantity: 70, chosenPercentage: 0.54 }, |
| | | { optionText: 'æ¯è¾éæåç', chosenQuantity: 45, chosenPercentage: 0.35 }, |
| | | { optionText: 'ä¸è¬', chosenQuantity: 10, chosenPercentage: 0.08 }, |
| | | { optionText: 'ä¸å¤ªéæ', chosenQuantity: 5, chosenPercentage: 0.04 }, |
| | | { optionText: 'é常ä¸éæ', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨ä¼åäº²åæ¨èæä»¬å»é¢å', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 148, |
| | | averageScore: 4.8, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { optionText: 'éå¸¸æ¿ææ¨è', chosenQuantity: 100, chosenPercentage: 0.68 }, |
| | | { optionText: 'æ¯è¾æ¿ææ¨è', chosenQuantity: 40, chosenPercentage: 0.27 }, |
| | | { optionText: 'ä¸è¬', chosenQuantity: 6, chosenPercentage: 0.04 }, |
| | | { optionText: 'ä¸å¤ªæ¿ææ¨è', chosenQuantity: 2, chosenPercentage: 0.01 }, |
| | | { optionText: 'ç»å¯¹ä¸ä¼æ¨è', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨å¯¹ä»¥ä¸åªäºæ¹é¢æ¯è¾æ»¡æï¼å¤éï¼', |
| | | scriptType: 2, // å¤éé¢ |
| | | totalCount: 156, |
| | | answerCount: 150, |
| | | averageScore: 4.4, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { optionText: 'å»çææ¯æ°´å¹³', chosenQuantity: 120, chosenPercentage: 0.8 }, |
| | | { optionText: 'æå¡æåº¦', chosenQuantity: 110, chosenPercentage: 0.73 }, |
| | | { optionText: 'ç¯å¢å«ç', chosenQuantity: 90, chosenPercentage: 0.6 }, |
| | | { optionText: 'å»ç设å¤', chosenQuantity: 85, chosenPercentage: 0.57 }, |
| | | { optionText: 'æ¶è´¹éæåº¦', chosenQuantity: 70, chosenPercentage: 0.47 }, |
| | | { optionText: 'çå¾
æ¶é´', chosenQuantity: 60, chosenPercentage: 0.4 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨è®¤ä¸ºå»é¢åªäºæ¹é¢éè¦æ¹è¿ï¼å¤éï¼', |
| | | scriptType: 2, |
| | | totalCount: 156, |
| | | answerCount: 125, |
| | | averageScore: 3.8, |
| | | maxScore: 5, |
| | | minScore: 2, |
| | | options: [ |
| | | { optionText: 'çå¾
æ¶é´è¿é¿', chosenQuantity: 80, chosenPercentage: 0.64 }, |
| | | { optionText: 'å°±è¯æµç¨å¤æ', chosenQuantity: 70, chosenPercentage: 0.56 }, |
| | | { optionText: 'è´¹ç¨è¾é«', chosenQuantity: 60, chosenPercentage: 0.48 }, |
| | | { optionText: 'å车å°é¾', chosenQuantity: 50, chosenPercentage: 0.4 }, |
| | | { optionText: 'æå¼æ è¯ä¸æ¸
', chosenQuantity: 40, chosenPercentage: 0.32 }, |
| | | { optionText: 'ç½ç»é¢çº¦ä¸ä¾¿', chosenQuantity: 30, chosenPercentage: 0.24 } |
| | | ] |
| | | }, |
| | | { |
| | | scriptContent: 'æ¨å¯¹æ¬æ¬¡ä½é¢çæ´ä½ä½éªè¯å', |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 152, |
| | | averageScore: 4.6, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { optionText: '5åï¼é常满æï¼', chosenQuantity: 90, chosenPercentage: 0.59 }, |
| | | { optionText: '4åï¼æ»¡æï¼', chosenQuantity: 50, chosenPercentage: 0.33 }, |
| | | { optionText: '3åï¼ä¸è¬ï¼', chosenQuantity: 10, chosenPercentage: 0.07 }, |
| | | { optionText: '2åï¼ä¸æ»¡æï¼', chosenQuantity: 2, chosenPercentage: 0.01 }, |
| | | { optionText: '1åï¼é叏䏿»¡æï¼', chosenQuantity: 0, chosenPercentage: 0 } |
| | | ] |
| | | } |
| | | ]; |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | const questions = [ |
| | | { |
| | | scriptContent: "æ¨å¯¹å»æ¤äººåçæå¡æåº¦æ¯å¦æ»¡æ", |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 145, |
| | | averageScore: 4.5, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { |
| | | optionText: "é常满æ", |
| | | chosenQuantity: 89, |
| | | chosenPercentage: 0.61, |
| | | }, |
| | | { |
| | | optionText: "满æ", |
| | | chosenQuantity: 45, |
| | | chosenPercentage: 0.31, |
| | | }, |
| | | { |
| | | optionText: "ä¸è¬", |
| | | chosenQuantity: 8, |
| | | chosenPercentage: 0.06, |
| | | }, |
| | | { |
| | | optionText: "䏿»¡æ", |
| | | chosenQuantity: 2, |
| | | chosenPercentage: 0.01, |
| | | }, |
| | | { |
| | | optionText: "é叏䏿»¡æ", |
| | | chosenQuantity: 1, |
| | | chosenPercentage: 0.01, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | scriptContent: "æ¨å¯¹å»ççè¯çæ°´å¹³åææ¯è½åè¯ä»·å¦ä½", |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 142, |
| | | averageScore: 4.7, |
| | | maxScore: 5, |
| | | minScore: 3, |
| | | options: [ |
| | | { |
| | | optionText: "é常ä¸ä¸", |
| | | chosenQuantity: 95, |
| | | chosenPercentage: 0.67, |
| | | }, |
| | | { |
| | | optionText: "æ¯è¾ä¸ä¸", |
| | | chosenQuantity: 40, |
| | | chosenPercentage: 0.28, |
| | | }, |
| | | { |
| | | optionText: "ä¸è¬", |
| | | chosenQuantity: 5, |
| | | chosenPercentage: 0.04, |
| | | }, |
| | | { |
| | | optionText: "ä¸å¤ä¸ä¸", |
| | | chosenQuantity: 2, |
| | | chosenPercentage: 0.01, |
| | | }, |
| | | { |
| | | optionText: "é常ä¸ä¸ä¸", |
| | | chosenQuantity: 0, |
| | | chosenPercentage: 0, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | scriptContent: "æ¨å¯¹å»é¢çç¯å¢åå«çç¶åµæ¯å¦æ»¡æ", |
| | | scriptType: 1, |
| | | totalCount: 156, |
| | | answerCount: 138, |
| | | averageScore: 4.3, |
| | | maxScore: 5, |
| | | minScore: 2, |
| | | options: [ |
| | | { |
| | | optionText: "é常满æ", |
| | | chosenQuantity: 75, |
| | | chosenPercentage: 0.54, |
| | | }, |
| | | { |
| | | optionText: "满æ", |
| | | chosenQuantity: 50, |
| | | chosenPercentage: 0.36, |
| | | }, |
| | | { |
| | | optionText: "ä¸è¬", |
| | | chosenQuantity: 10, |
| | | chosenPercentage: 0.07, |
| | | }, |
| | | { |
| | | optionText: "䏿»¡æ", |
| | | chosenQuantity: 3, |
| | | chosenPercentage: 0.02, |
| | | }, |
| | | { |
| | | optionText: "é叏䏿»¡æ", |
| | | chosenQuantity: 0, |
| | | chosenPercentage: 0, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // å页å¤ç |
| | | const startIndex = (this.detailQueryParams.pageNum - 1) * this.detailQueryParams.pageSize; |
| | | const endIndex = startIndex + this.detailQueryParams.pageSize; |
| | | const paginatedData = questions.slice(startIndex, endIndex); |
| | | const startIndex = |
| | | (this.detailQueryParams.pageNum - 1) * |
| | | this.detailQueryParams.pageSize; |
| | | const endIndex = startIndex + this.detailQueryParams.pageSize; |
| | | const paginatedData = questions.slice(startIndex, endIndex); |
| | | |
| | | return { |
| | | list: paginatedData, |
| | | total: questions.length |
| | | }; |
| | | resolve({ |
| | | list: paginatedData, |
| | | total: questions.length, |
| | | }); |
| | | }, 300); |
| | | }); |
| | | }, |
| | | |
| | | // çæMockç±»åæç»æ°æ® |
| | | generateMockTypeDetail() { |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | // å¨ generateMockTypeDetail æ¹æ³ä¸æ¿æ¢ä¸ºï¼ |
| | | const types = [ |
| | | { |
| | | id: 401, |
| | | typeName: "åºé¢æ»¡æåº¦", |
| | | isSpecial: false, |
| | | sendCount: 2850, |
| | | receiveCount: 2680, |
| | | recoveryRate: 0.94, |
| | | averageScore: 4.8, |
| | | maxScore: 5, |
| | | minScore: 3.8, |
| | | satisfactionLevel: "ä¼ç§", |
| | | trend: "up", |
| | | }, |
| | | { |
| | | id: 402, |
| | | typeName: "ä½é¢æ»¡æåº¦", |
| | | isSpecial: false, |
| | | sendCount: 2620, |
| | | receiveCount: 2405, |
| | | recoveryRate: 0.918, |
| | | averageScore: 4.6, |
| | | maxScore: 5, |
| | | minScore: 3.5, |
| | | satisfactionLevel: "ä¼ç§", |
| | | trend: "stable", |
| | | }, |
| | | { |
| | | id: 403, |
| | | typeName: "é¨è¯æ»¡æåº¦", |
| | | isSpecial: false, |
| | | sendCount: 3780, |
| | | receiveCount: 3220, |
| | | recoveryRate: 0.852, |
| | | averageScore: 4.3, |
| | | maxScore: 5, |
| | | minScore: 2.5, |
| | | satisfactionLevel: "è¯å¥½", |
| | | trend: "up", |
| | | }, |
| | | { |
| | | id: 404, |
| | | typeName: "å¸¸ç¨æ»¡æåº¦", |
| | | isSpecial: true, |
| | | sendCount: 1950, |
| | | receiveCount: 1780, |
| | | recoveryRate: 0.913, |
| | | averageScore: 4.5, |
| | | maxScore: 5, |
| | | minScore: 3.2, |
| | | satisfactionLevel: "è¯å¥½", |
| | | trend: "stable", |
| | | }, |
| | | ]; |
| | | |
| | | resolve(types); |
| | | }, 300); |
| | | }); |
| | | }, |
| | | |
| | | // å¤çå¾è¡¨ååºå¼ |
| | |
| | | this.loadData(); |
| | | }, |
| | | |
| | | // å¤çTab忢 |
| | | handleTabClick(tab) { |
| | | if (tab.name === "typeDetail" && this.typeDetailData.length === 0) { |
| | | this.loadTypeDetailData(); |
| | | } |
| | | }, |
| | | |
| | | // å¤çæç»å页大å°åå |
| | | handleDetailSizeChange(size) { |
| | | this.detailQueryParams.pageSize = size; |
| | |
| | | this.loadQuestionDetailData(); |
| | | }, |
| | | |
| | | // å¤çç±»å详æ
|
| | | handleTypeDetail(row) { |
| | | this.$message.info(`æ¥çç±»å详æ
ï¼${row.typeName}`); |
| | | // è¿éå¯ä»¥è·³è½¬å°è¯¦æ
页颿æå¼è¯¦æ
å¯¹è¯æ¡ |
| | | }, |
| | | |
| | | // å¤çå¯¼åºæ°æ® |
| | | handleExportData(row) { |
| | | this.$message.success(`æ£å¨å¯¼åº ${row.typeName} æ°æ®...`); |
| | | // è¿éå¯ä»¥å®ç°å¯¼åºé»è¾ |
| | | }, |
| | | |
| | | // æ ¼å¼åç¾åæ¯ |
| | | formatPercent(value) { |
| | | if (value === null || value === undefined) return '-'; |
| | | if (value === null || value === undefined) return "-"; |
| | | const num = parseFloat(value); |
| | | if (isNaN(num)) return '-'; |
| | | if (isNaN(num)) return "-"; |
| | | return `${(num * 100).toFixed(2)}%`; |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // è·ååæ¶çæ ·å¼ç±» |
| | | getRateClass(rate) { |
| | | if (rate >= 0.9) return "rate-high"; |
| | | if (rate >= 0.8) return "rate-medium"; |
| | | return "rate-low"; |
| | | }, |
| | | |
| | | // è·å满æåº¦ç级æ ç¾ç±»å |
| | | getLevelTagType(level) { |
| | | const levelMap = { |
| | | ä¼ç§: "success", |
| | | è¯å¥½: "primary", |
| | | ä¸è¬: "warning", |
| | | è¾å·®: "danger", |
| | | å·®: "info", |
| | | }; |
| | | return levelMap[level] || "info"; |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .satisfaction-statistics { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | |
| | | .query-section { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | |
| | | .query-form { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | |
| | | ::v-deep .el-form-item { |
| | | margin-bottom: 20px; |
| | | margin-bottom: 0; |
| | | margin-right: 20px; |
| | | |
| | | &:not(:last-child) { |
| | | margin-right: 20px; |
| | | &:last-child { |
| | | margin-right: 0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-section { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | |
| | | .chart-container { |
| | | width: 100%; |
| | | |
| | | .chart-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | .chart-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 10px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | #satisfactionBarChart { |
| | | width: 100%; |
| | | height: 400px; |
| | | .chart-title { |
| | | margin: 0; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .statistic-info { |
| | | display: flex; |
| | | gap: 30px; |
| | | align-items: center; |
| | | |
| | | .statistic-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | |
| | | .statistic-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .statistic-value { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .detail-table-section { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | .tab-section { |
| | | ::v-deep .el-tabs__header { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | ::v-deep .el-tabs__content { |
| | | padding: 20px 0 0 0; |
| | | } |
| | | } |
| | | |
| | | .detail-table-section { |
| | | .option-detail { |
| | | padding: 20px; |
| | | padding: 15px; |
| | | background: #f8f9fa; |
| | | border-radius: 4px; |
| | | margin: 10px 0; |
| | |
| | | background-color: #f8f9fa; |
| | | font-weight: 600; |
| | | color: #333; |
| | | padding: 12px 0; |
| | | } |
| | | |
| | | td { |
| | | padding: 12px 0; |
| | | } |
| | | |
| | | .question-row { |
| | |
| | | margin-top: 20px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
| | | border-radius: 4px; |
| | | border-radius: 8px; |
| | | border: 1px solid #dee2e6; |
| | | |
| | | .summary-content { |
| | |
| | | |
| | | .label { |
| | | font-size: 16px; |
| | | color: #666; |
| | | color: #606266; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #1890ff; |
| | | color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pagination-section { |
| | | display: flex; |
| | | justify-content: center; |
| | | padding: 20px 0 0 0; |
| | | } |
| | | } |
| | | |
| | | .pagination-section { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-top: 20px; |
| | | .type-detail-section { |
| | | .type-detail-table { |
| | | ::v-deep .el-table__header-wrapper { |
| | | th { |
| | | background-color: #f0f7ff; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .type-name-cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .type-name { |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .number-text { |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .rate-text { |
| | | font-weight: 600; |
| | | font-size: 14px; |
| | | |
| | | &.rate-high { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | &.rate-medium { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | &.rate-low { |
| | | color: #f56c6c; |
| | | } |
| | | } |
| | | |
| | | .score-text { |
| | | font-weight: 600; |
| | | color: #409eff; |
| | | font-size: 15px; |
| | | } |
| | | |
| | | .trend-cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 5px; |
| | | |
| | | .trend-up, |
| | | .trend-down, |
| | | .trend-stable { |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .trend-text { |
| | | font-size: 13px; |
| | | color: #666; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .type-summary-row { |
| | | margin-top: 20px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%); |
| | | border-radius: 8px; |
| | | border: 1px solid #d0ebff; |
| | | |
| | | .type-summary-content { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 20px; |
| | | |
| | | .type-summary-item { |
| | | text-align: center; |
| | | min-width: 150px; |
| | | |
| | | .label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .high-count { |
| | | color: #67c23a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // å
å±è¡¨æ ¼æ ·å¼ |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .satisfaction-statistics { |
| | | padding: 10px; |
| | | |
| | | .query-section { |
| | | .query-form { |
| | | ::v-deep .el-form-item { |
| | | width: 100%; |
| | | margin-right: 0; |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chart-section { |
| | | .chart-container { |
| | | .chart-header { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | gap: 15px; |
| | | |
| | | .statistic-info { |
| | | width: 100%; |
| | | justify-content: space-between; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .detail-table-section { |
| | | .summary-content { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | } |
| | | |
| | | .type-detail-section { |
| | | .type-summary-content { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div class="topic-dialog"> |
| | | <div class="topicdia"> |
| | | <div style="overflow-x: hidden; overflow-y: auto; max-height: 65vh"> |
| | | <!-- ä¿®æ¹è¿éï¼ä½¿ç¨ processedTopicList è䏿¯ topicList --> |
| | | <div |
| | | v-for="(item, index) in topiclist" |
| | | :key="index" |
| | | v-for="(item, index) in processedTopicList" |
| | | :key="item.scriptid" |
| | | class="ttaabbcc" |
| | | > |
| | | <div class="describe"> |
| | | 第{{ index + 1 }}é¢ï¼ {{ item.scriptContent }}? |
| | | 第{{ index + 1 }}é¢ï¼ {{ item.scriptContent }} |
| | | <span>[{{ item.scriptType == 1 ? "åéé¢" : "å¤éé¢" }}]</span> |
| | | </div> |
| | | <div> |
| | |
| | | label="éæ©äººæ°" |
| | | align="center" |
| | | min-width="120" |
| | | /> |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | {{ row.chosenQuantity || 0 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="chosenPercentage" |
| | | label="æ¯ä¾" |
| | |
| | | min-width="120" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <span v-if="row.chosenPercentage !== null && row.chosenPercentage !== undefined"> |
| | | {{ formatPercent(row.chosenPercentage) }} |
| | | <span |
| | | v-if=" |
| | | row.chosenPercentage !== null && |
| | | row.chosenPercentage !== undefined |
| | | " |
| | | > |
| | | {{ (Number(row.chosenPercentage) * 100).toFixed(2) }}% |
| | | </span> |
| | | <span v-else>-</span> |
| | | </template> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div slot="footer" class="dialog-footer" style="text-align: center; padding-top: 20px;"> |
| | | <!-- å¦ææ²¡ææ°æ® --> |
| | | <div |
| | | v-if="!processedTopicList.length" |
| | | class="no-data" |
| | | style="text-align: center; padding: 50px 0" |
| | | > |
| | | <el-empty description="ææ æ°æ®"></el-empty> |
| | | </div> |
| | | |
| | | <div |
| | | slot="footer" |
| | | class="dialog-footer" |
| | | style="text-align: center; padding-top: 20px" |
| | | > |
| | | <el-button @click="handleClose">å
³ é</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'TopicDialog', |
| | | name: "TopicDialog", |
| | | props: { |
| | | rowData: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | default: () => ({}), |
| | | }, |
| | | queryParams: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | } |
| | | default: () => ({}), |
| | | }, |
| | | topicList: { |
| | | type: [Array, Object], |
| | | default: () => ({}), |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | topiclist: [] |
| | | processedTopicList: [], // å¤çåçæ°æ® |
| | | }; |
| | | }, |
| | | |
| | | mounted() { |
| | | this.loadData(); |
| | | watch: { |
| | | // çå¬ç¶ç»ä»¶ä¼ éçæ°æ®åå |
| | | topicList: { |
| | | immediate: true, |
| | | handler(newVal) { |
| | | console.log("TopicDialogæ¥æ¶å°ç¶ç»ä»¶æ°æ®:", newVal); |
| | | this.processTopicList(newVal); |
| | | }, |
| | | }, |
| | | }, |
| | | |
| | | mounted() { |
| | | console.log("TopicDialog mounted, props:", this.$props); |
| | | }, |
| | | methods: { |
| | | // å è½½æ°æ® |
| | | async loadData() { |
| | | try { |
| | | // è¿éä»ç¶ç»ä»¶ä¼ éæ°æ®ï¼ä¸éè¦éæ°è°ç¨API |
| | | this.topiclist = this.$parent.topiclist || []; |
| | | } catch (error) { |
| | | console.error('å è½½é¢ç®è¯¦æ
失败:', error); |
| | | this.$message.error('å è½½é¢ç®è¯¦æ
失败'); |
| | | // å¤çtopicListæ°æ® |
| | | processTopicList(data) { |
| | | console.log("å¼å§å¤çæ°æ®:", data); |
| | | |
| | | if (!data || typeof data !== "object") { |
| | | this.processedTopicList = []; |
| | | return; |
| | | } |
| | | |
| | | // å°å¯¹è±¡è½¬æ¢ä¸ºæ°ç» |
| | | const result = []; |
| | | |
| | | Object.keys(data).forEach((key) => { |
| | | const item = data[key]; |
| | | if (item && item.scriptContent) { |
| | | // æ·±æ·è´itemï¼é¿å
ä¿®æ¹åæ°æ® |
| | | const processedItem = JSON.parse(JSON.stringify(item)); |
| | | |
| | | // è¿æ»¤detailsï¼åªä¿çæéé¡¹ææ¬ç |
| | | if (processedItem.details && Array.isArray(processedItem.details)) { |
| | | processedItem.details = processedItem.details.filter( |
| | | (detail) => detail && detail.optionText |
| | | ); |
| | | } |
| | | |
| | | result.push(processedItem); |
| | | } |
| | | }); |
| | | |
| | | console.log("å¤çåçæ°æ®:", result); |
| | | this.processedTopicList = result; |
| | | }, |
| | | |
| | | // æ ¼å¼åç¾åæ¯ |
| | | formatPercent(value) { |
| | | if (value === null || value === undefined) return '-'; |
| | | if (value === null || value === undefined) return "-"; |
| | | const num = parseFloat(value); |
| | | if (isNaN(num)) return '-'; |
| | | return `${(num * 100).toFixed(2)}%`; |
| | | if (isNaN(num)) return "-"; |
| | | return `${num.toFixed(2)}%`; // 注æï¼ä½ çæ°æ®ä¸ç¾åæ¯å·²ç»æ¯0-100çå½¢å¼ |
| | | }, |
| | | |
| | | // å
³éå¯¹è¯æ¡ |
| | | handleClose() { |
| | | this.$emit('close'); |
| | | } |
| | | } |
| | | this.$emit("close"); |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="followup-statistics"> |
| | | <div class="query-section"> |
| | | <el-form |
| | | :model="queryParams" |
| | | ref="queryForm" |
| | | size="medium" |
| | | :inline="true" |
| | | label-width="100px" |
| | | class="query-form" |
| | | > |
| | | <el-form-item label="ç»è®¡ç±»å" prop="statisticaltype"> |
| | | <el-select |
| | | v-model="queryParams.statisticaltype" |
| | | placeholder="è¯·éæ©ç»è®¡ç±»å" |
| | | clearable |
| | | @change="handleStatisticalTypeChange" |
| | | > |
| | | <el-option |
| | | v-for="item in Statisticallist" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <!-- ç
åºéæ© --> |
| | | <el-form-item |
| | | v-if="queryParams.statisticaltype == 1" |
| | | label="ç
åº" |
| | | prop="leavehospitaldistrictcodes" |
| | | > |
| | | <el-select |
| | | v-model="queryParams.leavehospitaldistrictcodes" |
| | | placeholder="è¯·éæ©ç
åº" |
| | | multiple |
| | | collapse-tags |
| | | filterable |
| | | clearable |
| | | style="width: 300px" |
| | | > |
| | | <el-option |
| | | v-for="item in flatArrayhospit" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <!-- ç§å®¤éæ© --> |
| | | <el-form-item |
| | | v-if="queryParams.statisticaltype == 2" |
| | | label="ç§å®¤" |
| | | prop="deptcodes" |
| | | > |
| | | <el-select |
| | | v-model="queryParams.deptcodes" |
| | | placeholder="è¯·éæ©ç§å®¤" |
| | | multiple |
| | | collapse-tags |
| | | filterable |
| | | clearable |
| | | style="width: 300px" |
| | | > |
| | | <el-option |
| | | v-for="item in flatArraydept" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æå¡ç±»å" prop="serviceType"> |
| | | <el-select |
| | | v-model="queryParams.serviceType" |
| | | placeholder="è¯·éæ©æå¡ç±»å" |
| | | multiple |
| | | collapse-tags |
| | | clearable |
| | | style="width: 300px" |
| | | > |
| | | <el-option |
| | | v-for="item in options" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="é访æ¶é´" prop="dateRange"> |
| | | <el-date-picker |
| | | v-model="queryParams.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="yyyy-MM-dd" |
| | | :picker-options="pickerOptions" |
| | | style="width: 380px" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | icon="el-icon-search" |
| | | @click="handleQuery" |
| | | :loading="loading" |
| | | > |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button icon="el-icon-refresh" @click="resetQuery"> |
| | | éç½® |
| | | </el-button> |
| | | <el-button |
| | | type="warning" |
| | | icon="el-icon-download" |
| | | @click="handleExport" |
| | | :disabled="!userList.length" |
| | | > |
| | | å¯¼åº |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="userList" |
| | | :border="true" |
| | | style="width: 100%" |
| | | @selection-change="handleSelectionChange" |
| | | :row-key="getRowKey" |
| | | > |
| | | <!-- ç
åºå --> |
| | | <el-table-column |
| | | v-if="queryParams.statisticaltype == 1" |
| | | label="åºé¢ç
åº" |
| | | align="center" |
| | | sortable |
| | | key="leavehospitaldistrictname" |
| | | prop="leavehospitaldistrictname" |
| | | :show-overflow-tooltip="true" |
| | | :sort-method="sortChineseNumber" |
| | | min-width="120" |
| | | /> |
| | | |
| | | <!-- ç§å®¤å --> |
| | | <el-table-column |
| | | v-if="queryParams.statisticaltype == 2" |
| | | label="ç§å®¤" |
| | | align="center" |
| | | key="deptname" |
| | | prop="deptname" |
| | | :show-overflow-tooltip="true" |
| | | min-width="120" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="åºé¢äººæ¬¡" |
| | | align="center" |
| | | key="dischargeCount" |
| | | prop="dischargeCount" |
| | | min-width="100" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="æ éé访人次" |
| | | align="center" |
| | | key="nonFollowUp" |
| | | prop="nonFollowUp" |
| | | min-width="120" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="åºé访人次" |
| | | align="center" |
| | | key="followUpNeeded" |
| | | prop="followUpNeeded" |
| | | min-width="120" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="é访ç" |
| | | align="center" |
| | | key="followUpRate" |
| | | prop="followUpRate" |
| | | min-width="100" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <span |
| | | v-if=" |
| | | scope.row.followUpRate !== null && |
| | | scope.row.followUpRate !== undefined |
| | | " |
| | | > |
| | | {{ formatPercent(scope.row.followUpRate) }} |
| | | </span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="åæ¶ç" |
| | | align="center" |
| | | key="rate" |
| | | prop="rate" |
| | | min-width="100" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <el-button |
| | | v-if="scope.row.rate !== null && scope.row.rate !== undefined" |
| | | type="text" |
| | | @click="handleSeedetails(scope.row)" |
| | | > |
| | | {{ formatPercent(scope.row.rate) }} |
| | | </el-button> |
| | | <span v-else style="color: #909399">-</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="å¤è¯éç¥é¢ç®æ»é" |
| | | align="center" |
| | | key="joyAllCount" |
| | | prop="joyAllCount" |
| | | min-width="140" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="å¤è¯éç¥å¡«æ¥é" |
| | | align="center" |
| | | key="joyCount" |
| | | prop="joyCount" |
| | | min-width="120" |
| | | /> |
| | | |
| | | <el-table-column |
| | | label="宿æ¯ç" |
| | | align="center" |
| | | key="joyTotal" |
| | | prop="joyTotal" |
| | | min-width="100" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <span |
| | | v-if=" |
| | | scope.row.joyTotal !== null && scope.row.joyTotal !== undefined |
| | | " |
| | | > |
| | | {{ formatPercent(scope.row.joyTotal) }} |
| | | </span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="æä½" align="center" fixed="right" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" @click="getinfo(scope.row)"> |
| | | <i class="el-icon-s-order" style="margin-right: 4px"></i> |
| | | æ¥ç详æ
|
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- å页 --> |
| | | <div class="pagination-section" v-if="total > 0"> |
| | | <el-pagination |
| | | background |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :current-page="queryParams.pageNum" |
| | | :page-size="queryParams.pageSize" |
| | | :page-sizes="[10, 20, 30, 50]" |
| | | :total="total" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handlePageChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æªåæ¶é访详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | title="æªåæ¶é访æ£è
æå¡" |
| | | :visible.sync="SeedetailsVisible" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <SeedetailsDialog |
| | | v-if="SeedetailsVisible" |
| | | :row-data="currentRow" |
| | | :query-params="queryParams" |
| | | @close="SeedetailsVisible = false" |
| | | /> |
| | | </el-dialog> |
| | | |
| | | <!-- å¤è¯éç¥è¯¦æ
å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :visible.sync="topicVisible" |
| | | width="60%" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <template #title> |
| | | <div style="display: flex; align-items: center"> |
| | | <i |
| | | class="el-icon-s-data" |
| | | style="margin-right: 8px; color: #409eff" |
| | | ></i> |
| | | <span>{{ topicvalue.name }}</span> |
| | | <span style="margin-left: 10px; color: #666; font-size: 14px" |
| | | >å¤è¯éç¥ææ 详æ
</span |
| | | > |
| | | </div> |
| | | </template> |
| | | <topic-dialog |
| | | v-if="topicVisible" |
| | | :row-data="currentRow" |
| | | :topicList="topiclist" |
| | | :query-params="queryParams" |
| | | @close="topicVisible = false" |
| | | /> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | getSfStatisticsJoy, |
| | | getSfStatisticsJoyInfo, |
| | | selectTimelyRate, |
| | | } from "@/api/system/user"; |
| | | import ExcelJS from "exceljs"; |
| | | import { saveAs } from "file-saver"; |
| | | import SeedetailsDialog from "./components/SeedetailsDialog.vue"; |
| | | import TopicDialog from "./components/TopicDialog.vue"; |
| | | |
| | | export default { |
| | | name: "FollowupStatistics", |
| | | components: { |
| | | SeedetailsDialog, |
| | | TopicDialog, |
| | | }, |
| | | data() { |
| | | return { |
| | | // æ¥è¯¢åæ° |
| | | queryParams: { |
| | | statisticaltype: 1, |
| | | leavehospitaldistrictcodes: ["all"], |
| | | deptcodes: [], |
| | | serviceType: [2], |
| | | dateRange: [], |
| | | pageNum: 1, |
| | | pageSize: 20, |
| | | }, |
| | | |
| | | // ç»è®¡ç±»åå表 |
| | | Statisticallist: [ |
| | | { label: "ç
åºç»è®¡", value: 1 }, |
| | | { label: "ç§å®¤ç»è®¡", value: 2 }, |
| | | ], |
| | | |
| | | // ç
åºå表 |
| | | flatArrayhospit: [], |
| | | |
| | | // ç§å®¤å表 |
| | | flatArraydept: [], |
| | | |
| | | // æå¡ç±»åé项 |
| | | options: [], |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | userList: [], |
| | | |
| | | // æ»æ¡æ° |
| | | total: 0, |
| | | |
| | | // å è½½ç¶æ |
| | | loading: false, |
| | | |
| | | // éä¸çè¡ |
| | | ids: [], |
| | | single: true, |
| | | multiple: true, |
| | | |
| | | // å½åæä½çè¡ |
| | | currentRow: null, |
| | | |
| | | // å¯¹è¯æ¡æ¾ç¤ºæ§å¶ |
| | | SeedetailsVisible: false, |
| | | topicVisible: false, |
| | | |
| | | // å¤è¯éç¥è¯¦æ
æ°æ® |
| | | topiclist: [], |
| | | topicvalue: { |
| | | name: "", |
| | | }, |
| | | |
| | | // æ¥æéæ©å¨é项 |
| | | pickerOptions: { |
| | | shortcuts: [ |
| | | { |
| | | text: "æè¿ä¸å¨", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: "æè¿ä¸ä¸ªæ", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: "æè¿ä¸ä¸ªæ", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | ], |
| | | disabledDate(time) { |
| | | return time.getTime() > Date.now(); |
| | | }, |
| | | }, |
| | | }; |
| | | }, |
| | | |
| | | created() { |
| | | this.initData(); |
| | | }, |
| | | |
| | | methods: { |
| | | // åå§åæ°æ® |
| | | async initData() { |
| | | await this.getDeptTree(); |
| | | await this.getList(); |
| | | }, |
| | | |
| | | // è·åç§å®¤æ |
| | | getDeptTree() { |
| | | // è·åæå¡ç±»å |
| | | this.options = this.$store.getters.tasktypes || []; |
| | | |
| | | // è·åç§å®¤å表 |
| | | this.flatArraydept = (this.$store.getters.belongDepts || []).map( |
| | | (dept) => { |
| | | return { |
| | | label: dept.deptName, |
| | | value: dept.deptCode, |
| | | }; |
| | | } |
| | | ); |
| | | |
| | | // è·åç
åºå表 |
| | | this.flatArrayhospit = (this.$store.getters.belongWards || []).map( |
| | | (ward) => { |
| | | return { |
| | | label: ward.districtName, |
| | | value: ward.districtCode, |
| | | }; |
| | | } |
| | | ); |
| | | |
| | | // æ·»å å
¨é¨é项 |
| | | this.flatArraydept.push({ label: "å
¨é¨", value: "all" }); |
| | | this.flatArrayhospit.push({ label: "å
¨é¨", value: "all" }); |
| | | }, |
| | | |
| | | // è·åç»è®¡å表 |
| | | async getList() { |
| | | this.loading = true; |
| | | try { |
| | | // å¤çæ¥è¯¢åæ° |
| | | const params = { |
| | | configKey: "returnVisitCount", |
| | | ...this.queryParams, |
| | | }; |
| | | |
| | | // å¤çæ¥æèå´ |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | params.startTime = this.queryParams.dateRange[0]; |
| | | params.endTime = this.queryParams.dateRange[1]; |
| | | } |
| | | |
| | | // å¤çç
åº/ç§å®¤éæ© |
| | | if (params.statisticaltype == 1) { |
| | | // ç
åºç»è®¡ |
| | | if (params.leavehospitaldistrictcodes.includes("all")) { |
| | | // 妿鿩äº"å
¨é¨"ï¼åç§»é¤"all"å¼ |
| | | params.leavehospitaldistrictcodes = |
| | | params.leavehospitaldistrictcodes.filter( |
| | | (item) => item !== "all" |
| | | ); |
| | | // 妿éè¦ä¼ ææç
åºä»£ç ï¼å¯ä»¥ä»storeä¸è·å |
| | | params.leavehospitaldistrictcodes = ( |
| | | this.$store.getters.belongWards || [] |
| | | ).map((ward) => ward.districtCode); |
| | | } |
| | | } else if (params.statisticaltype == 2) { |
| | | // ç§å®¤ç»è®¡ |
| | | if (params.deptcodes.includes("all")) { |
| | | // 妿鿩äº"å
¨é¨"ï¼åç§»é¤"all"å¼ |
| | | params.deptcodes = params.deptcodes.filter( |
| | | (item) => item !== "all" |
| | | ); |
| | | // 妿éè¦ä¼ ææç§å®¤ä»£ç ï¼å¯ä»¥ä»storeä¸è·å |
| | | params.deptcodes = (this.$store.getters.belongDepts || []).map( |
| | | (dept) => dept.deptCode |
| | | ); |
| | | } |
| | | } |
| | | |
| | | const response = await getSfStatisticsJoy(params); |
| | | this.userList = this.customSort(response.data) || []; |
| | | this.total = response.total || 0; |
| | | } catch (error) { |
| | | console.error("è·åç»è®¡å表失败:", error); |
| | | this.$message.error("è·åæ°æ®å¤±è´¥"); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | sortChineseNumber(aRow, bRow) { |
| | | const a = aRow.leavehospitaldistrictname; |
| | | const b = bRow.leavehospitaldistrictname; |
| | | |
| | | // 䏿æ°åå°é¿æä¼¯æ°åçæ å°ï¼æ©å±å°45ï¼ |
| | | const chineseNumMap = { |
| | | ä¸: 1, |
| | | äº: 2, |
| | | ä¸: 3, |
| | | å: 4, |
| | | äº: 5, |
| | | å
: 6, |
| | | ä¸: 7, |
| | | å
«: 8, |
| | | ä¹: 9, |
| | | å: 10, |
| | | åä¸: 11, |
| | | åäº: 12, |
| | | åä¸: 13, |
| | | åå: 14, |
| | | åäº: 15, |
| | | åå
: 16, |
| | | åä¸: 17, |
| | | åå
«: 18, |
| | | åä¹: 19, |
| | | äºå: 20, |
| | | äºåä¸: 21, |
| | | äºåäº: 22, |
| | | äºåä¸: 23, |
| | | äºåå: 24, |
| | | äºåäº: 25, |
| | | äºåå
: 26, |
| | | äºåä¸: 27, |
| | | äºåå
«: 28, |
| | | äºåä¹: 29, |
| | | ä¸å: 30, |
| | | ä¸åä¸: 31, |
| | | ä¸åäº: 32, |
| | | ä¸åä¸: 33, |
| | | ä¸åå: 34, |
| | | ä¸åäº: 35, |
| | | ä¸åå
: 36, |
| | | ä¸åä¸: 37, |
| | | ä¸åå
«: 38, |
| | | ä¸åä¹: 39, |
| | | åå: 40, |
| | | ååä¸: 41, |
| | | ååäº: 42, |
| | | ååä¸: 43, |
| | | ååå: 44, |
| | | ååäº: 45, |
| | | }; |
| | | |
| | | // æå䏿æ°å |
| | | const getNumberFromText = (text) => { |
| | | if (!text || typeof text !== "string") return -1; |
| | | |
| | | // å¹é
䏿æ°åï¼æ¯æä¸å°ååäº |
| | | const match = text.match(/^([ä¸äºä¸åäºå
ä¸å
«ä¹å]+)/); |
| | | |
| | | if (match && match[1]) { |
| | | const chineseNum = match[1]; |
| | | return chineseNumMap[chineseNum] !== undefined |
| | | ? chineseNumMap[chineseNum] |
| | | : -1; |
| | | } |
| | | |
| | | // å¦ææ²¡æå¹é
å°ä¸ææ°åï¼å°è¯å¹é
é¿æä¼¯æ°å |
| | | const arabicMatch = text.match(/^(\d+)/); |
| | | if (arabicMatch && arabicMatch[1]) { |
| | | const num = parseInt(arabicMatch[1], 10); |
| | | return num >= 1 && num <= 45 ? num : -1; |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | const numA = getNumberFromText(a); |
| | | const numB = getNumberFromText(b); |
| | | |
| | | // å¤çæ æ³è§£æçæ
åµ |
| | | if (numA === -1 && numB === -1) { |
| | | return (a || "").localeCompare(b || ""); |
| | | } |
| | | if (numA === -1) return 1; |
| | | if (numB === -1) return -1; |
| | | |
| | | return numA - numB; |
| | | }, |
| | | customSort(data) { |
| | | // å®ä¹æ¨ææçç
åºé¡ºåºï¼æ©å±å°ååäºï¼ |
| | | const order = [ |
| | | "ä¸", |
| | | "äº", |
| | | "ä¸", |
| | | "å", |
| | | "äº", |
| | | "å
", |
| | | "ä¸", |
| | | "å
«", |
| | | "ä¹", |
| | | "å", |
| | | "åä¸", |
| | | "åäº", |
| | | "åä¸", |
| | | "åå", |
| | | "åäº", |
| | | "åå
", |
| | | "åä¸", |
| | | "åå
«", |
| | | "åä¹", |
| | | "äºå", |
| | | "äºåä¸", |
| | | "äºåäº", |
| | | "äºåä¸", |
| | | "äºåå", |
| | | "äºåäº", |
| | | "äºåå
", |
| | | "äºåä¸", |
| | | "äºåå
«", |
| | | "äºåä¹", |
| | | "ä¸å", |
| | | "ä¸åä¸", |
| | | "ä¸åäº", |
| | | "ä¸åä¸", |
| | | "ä¸åå", |
| | | "ä¸åäº", |
| | | "ä¸åå
", |
| | | "ä¸åä¸", |
| | | "ä¸åå
«", |
| | | "ä¸åä¹", |
| | | "åå", |
| | | "ååä¸", |
| | | "ååäº", |
| | | "ååä¸", |
| | | "ååå", |
| | | "ååäº", |
| | | ]; |
| | | |
| | | return data.sort((a, b) => { |
| | | // æåç
åºåç§°ä¸ç䏿æ°åé¨å |
| | | const getIndex = (name) => { |
| | | if (!name || typeof name !== "string") return -1; |
| | | |
| | | // å¹é
䏿æ°å |
| | | const chineseMatch = name.match(/^([ä¸äºä¸åäºå
ä¸å
«ä¹å]+)/); |
| | | if (chineseMatch && chineseMatch[1]) { |
| | | return order.indexOf(chineseMatch[1]); |
| | | } |
| | | |
| | | // å¹é
é¿æä¼¯æ°å |
| | | const arabicMatch = name.match(/^(\d+)/); |
| | | if (arabicMatch && arabicMatch[1]) { |
| | | const num = parseInt(arabicMatch[1], 10); |
| | | if (num >= 1 && num <= 45) { |
| | | return num - 1; // å 为æ°ç»ç´¢å¼ä»0å¼å§ |
| | | } |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | const indexA = getIndex(a.leavehospitaldistrictname); |
| | | const indexB = getIndex(b.leavehospitaldistrictname); |
| | | |
| | | // æåºé»è¾ |
| | | if (indexA === -1 && indexB === -1) { |
| | | return (a.leavehospitaldistrictname || "").localeCompare( |
| | | b.leavehospitaldistrictname || "" |
| | | ); |
| | | } |
| | | if (indexA === -1) return 1; |
| | | if (indexB === -1) return -1; |
| | | return indexA - indexB; |
| | | }); |
| | | }, |
| | | // å¤çç»è®¡ç±»ååå |
| | | handleStatisticalTypeChange(value) { |
| | | if (value === 1) { |
| | | this.queryParams.deptcodes = []; |
| | | } else { |
| | | this.queryParams.leavehospitaldistrictcodes = []; |
| | | } |
| | | this.queryParams.pageNum = 1; |
| | | this.getList(); |
| | | }, |
| | | |
| | | // å¤çæ¥è¯¢ |
| | | handleQuery() { |
| | | this.queryParams.pageNum = 1; |
| | | this.getList(); |
| | | }, |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | resetQuery() { |
| | | this.queryParams = { |
| | | statisticaltype: 1, |
| | | leavehospitaldistrictcodes: [], |
| | | deptcodes: [], |
| | | serviceType: [2], |
| | | dateRange: [], |
| | | pageNum: 1, |
| | | pageSize: 20, |
| | | }; |
| | | this.getList(); |
| | | }, |
| | | |
| | | // å¤çå页大å°åå |
| | | handleSizeChange(size) { |
| | | this.queryParams.pageSize = size; |
| | | this.queryParams.pageNum = 1; |
| | | this.getList(); |
| | | }, |
| | | |
| | | // å¤ç页ç åå |
| | | handlePageChange(page) { |
| | | this.queryParams.pageNum = page; |
| | | this.getList(); |
| | | }, |
| | | |
| | | // å¤çè¡éæ© |
| | | handleSelectionChange(selection) { |
| | | this.ids = selection.map((item) => item.id); |
| | | this.single = selection.length !== 1; |
| | | this.multiple = !selection.length; |
| | | }, |
| | | |
| | | // è·åè¡key |
| | | getRowKey(row) { |
| | | return row.statisticaltype === 1 |
| | | ? row.leavehospitaldistrictcode |
| | | : row.deptcode; |
| | | }, |
| | | |
| | | // æ ¼å¼åç¾åæ¯ |
| | | formatPercent(value) { |
| | | if (value === null || value === undefined) return "-"; |
| | | const num = parseFloat(value); |
| | | if (isNaN(num)) return "-"; |
| | | return `${(num * 100).toFixed(2)}%`; |
| | | }, |
| | | |
| | | // æ¥çæªåæ¶é访详æ
|
| | | handleSeedetails(row) { |
| | | this.currentRow = row; |
| | | this.SeedetailsVisible = true; |
| | | }, |
| | | |
| | | // æ¥çå¤è¯éç¥è¯¦æ
|
| | | async getinfo(row) { |
| | | this.currentRow = row; |
| | | |
| | | try { |
| | | // å¤çæ¥è¯¢åæ° |
| | | const params = { |
| | | configKey: "returnVisitCount", |
| | | ...this.queryParams, |
| | | }; |
| | | |
| | | // å¤çæ¥æèå´ |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | params.startTime = this.queryParams.dateRange[0]; |
| | | params.endTime = this.queryParams.dateRange[1]; |
| | | } |
| | | |
| | | if (this.queryParams.statisticaltype == 1) { |
| | | this.topicvalue.name = row.leavehospitaldistrictname; |
| | | params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode]; |
| | | } else { |
| | | this.topicvalue.name = row.deptname; |
| | | params.deptcodes = [row.deptcode]; |
| | | } |
| | | |
| | | const response = await getSfStatisticsJoyInfo(params); |
| | | this.topiclist = response.data || []; |
| | | console.log(this.topiclist); |
| | | this.topicVisible = true; |
| | | } catch (error) { |
| | | console.error("è·åå¤è¯éç¥è¯¦æ
失败:", error); |
| | | this.$message.error("è·å详æ
失败"); |
| | | } |
| | | }, |
| | | |
| | | // å¯¼åºæ°æ® |
| | | async handleExport() { |
| | | if (!this.userList.length) { |
| | | this.$message.warning("æ²¡ææ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | this.loading = true; |
| | | |
| | | // æå»ºæ¥æèå´å符串 |
| | | let dateRangeString = ""; |
| | | let sheetNameSuffix = ""; |
| | | |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | const startDateFormatted = this.queryParams.dateRange[0]; |
| | | const endDateFormatted = this.queryParams.dateRange[1]; |
| | | dateRangeString = `${startDateFormatted}è³${endDateFormatted}`; |
| | | sheetNameSuffix = `${startDateFormatted}è³${endDateFormatted}`; |
| | | } else { |
| | | const now = new Date(); |
| | | const currentMonth = now.getMonth() + 1; |
| | | dateRangeString = `${currentMonth}æ`; |
| | | sheetNameSuffix = `${currentMonth}æ`; |
| | | } |
| | | |
| | | const excelName = `é访ç»è®¡è¡¨_${dateRangeString}.xlsx`; |
| | | const worksheetName = `é访ç»è®¡_${sheetNameSuffix}`; |
| | | |
| | | // å建Excelå·¥ä½ç°¿ |
| | | const workbook = new ExcelJS.Workbook(); |
| | | const worksheet = workbook.addWorksheet(worksheetName); |
| | | |
| | | // å®ä¹æ ·å¼ |
| | | const titleStyle = { |
| | | font: { name: "微软é
é»", size: 16, bold: true }, |
| | | fill: { |
| | | type: "pattern", |
| | | pattern: "solid", |
| | | fgColor: { argb: "FFE6F3FF" }, |
| | | }, |
| | | alignment: { vertical: "middle", horizontal: "center" }, |
| | | border: { |
| | | top: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | left: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | bottom: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | }, |
| | | }; |
| | | |
| | | const headerStyle = { |
| | | font: { name: "微软é
é»", size: 11, bold: true }, |
| | | fill: { |
| | | type: "pattern", |
| | | pattern: "solid", |
| | | fgColor: { argb: "FFF5F7FA" }, |
| | | }, |
| | | alignment: { vertical: "middle", horizontal: "center" }, |
| | | border: { |
| | | top: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | left: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | bottom: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | }, |
| | | }; |
| | | |
| | | const cellStyle = { |
| | | font: { name: "å®ä½", size: 10 }, |
| | | alignment: { vertical: "middle", horizontal: "center" }, |
| | | border: { |
| | | top: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | left: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | bottom: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | right: { style: "thin", color: { argb: "FFD0D0D0" } }, |
| | | }, |
| | | }; |
| | | |
| | | // æ·»å æ»æ é¢ |
| | | worksheet.mergeCells(1, 1, 1, 10); |
| | | const titleCell = worksheet.getCell(1, 1); |
| | | titleCell.value = `é访ç»è®¡è¡¨ï¼${sheetNameSuffix}ï¼`; |
| | | titleCell.style = titleStyle; |
| | | worksheet.getRow(1).height = 35; |
| | | |
| | | // æ·»å 表头 |
| | | const headers = [ |
| | | this.queryParams.statisticaltype == 1 ? "åºé¢ç
åº" : "ç§å®¤", |
| | | "åºé¢äººæ¬¡", |
| | | "æ éé访人次", |
| | | "åºé访人次", |
| | | "é访ç", |
| | | "åæ¶ç", |
| | | "å¤è¯éç¥é¢ç®æ»é", |
| | | "å¤è¯éç¥å¡«æ¥é", |
| | | "宿æ¯ç", |
| | | ]; |
| | | |
| | | const headerRow = worksheet.addRow(headers); |
| | | headerRow.eachCell((cell) => { |
| | | cell.style = headerStyle; |
| | | }); |
| | | headerRow.height = 25; |
| | | |
| | | // æ·»å æ°æ®è¡ |
| | | this.userList.forEach((item) => { |
| | | const dataRow = worksheet.addRow([ |
| | | this.queryParams.statisticaltype == 1 |
| | | ? item.leavehospitaldistrictname |
| | | : item.deptname, |
| | | item.dischargeCount || 0, |
| | | item.nonFollowUp || 0, |
| | | item.followUpNeeded || 0, |
| | | item.followUpRate || "0%", |
| | | item.rate ? this.formatPercent(item.rate) : "0%", |
| | | item.joyAllCount || 0, |
| | | item.joyCount || 0, |
| | | item.joyTotal ? this.formatPercent(item.joyTotal) : "0%", |
| | | ]); |
| | | |
| | | dataRow.eachCell((cell) => { |
| | | cell.style = cellStyle; |
| | | }); |
| | | dataRow.height = 22; |
| | | }); |
| | | |
| | | // 设置å宽 |
| | | worksheet.columns = [ |
| | | { width: 20 }, |
| | | { width: 12 }, |
| | | { width: 12 }, |
| | | { width: 12 }, |
| | | { width: 12 }, |
| | | { width: 12 }, |
| | | { width: 15 }, |
| | | { width: 15 }, |
| | | { width: 12 }, |
| | | ]; |
| | | |
| | | // çæå¹¶ä¸è½½æä»¶ |
| | | const buffer = await workbook.xlsx.writeBuffer(); |
| | | const blob = new Blob([buffer], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | |
| | | saveAs(blob, excelName); |
| | | this.$message.success("å¯¼åºæå"); |
| | | } catch (error) { |
| | | console.error("导åºå¤±è´¥:", error); |
| | | this.$message.error(`导åºå¤±è´¥: ${error.message}`); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .followup-statistics { |
| | | .query-section { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | |
| | | .query-form { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | |
| | | ::v-deep .el-form-item { |
| | | margin-bottom: 20px; |
| | | |
| | | &:not(:last-child) { |
| | | margin-right: 20px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | margin-bottom: 20px; |
| | | |
| | | ::v-deep .el-table { |
| | | th { |
| | | background-color: #f8f9fa; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pagination-section { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | // 忬¡éè®¿æ°æ®æ´æ¿ |
| | | formtidy() { |
| | | this.form.visitType2 = this.form.visitType; |
| | | this.form.date2 = this.form.longSendTime; |
| | | this.form.date2 = this.form.visitTime; |
| | | // this.form.date1 = this.setCurrentDate(); |
| | | this.form.remark2 = this.form.remark; |
| | | }, |
| | |
| | | this.logsheetlist = res.rows[0].serviceSubtaskList; |
| | | this.templateid = this.form.templateid; |
| | | this.selectedTag = this.form.excep; |
| | | const targetDate = new Date(this.form.longSendTime); // ç®æ æ¥æ |
| | | const targetDate = new Date(this.form.visitTime); // ç®æ æ¥æ |
| | | const now = new Date(); // å½åæ¶é´ |
| | | if (now < targetDate && this.form.sendstate == 2) { |
| | | this.$confirm("å½åæå¡æªå°åéæ¶é´è¯·è°¨æ
ä¿®æ¹", "æç¤º", { |
| | |
| | | this.form.remark = |
| | | this.form.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.form); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | // 忬¡éè®¿æ°æ®æ´æ¿ |
| | | formtidy() { |
| | | this.form.visitType2 = this.form.visitType; |
| | | this.form.date2 = this.form.longSendTime; |
| | | this.form.date2 = this.form.visitTime; |
| | | // this.form.date1 = this.setCurrentDate(); |
| | | this.form.remark2 = this.form.remark; |
| | | }, |
| | |
| | | this.logsheetlist = res.rows[0].serviceSubtaskList; |
| | | this.templateid = this.form.templateid; |
| | | this.selectedTag = this.form.excep; |
| | | const targetDate = new Date(this.form.longSendTime); // ç®æ æ¥æ |
| | | const targetDate = new Date(this.form.visitTime); // ç®æ æ¥æ |
| | | const now = new Date(); // å½åæ¶é´ |
| | | if (now < targetDate && this.form.sendstate == 2) { |
| | | this.$confirm("å½åæå¡æªå°åéæ¶é´è¯·è°¨æ
ä¿®æ¹", "æç¤º", { |
| | |
| | | this.form.remark = |
| | | this.form.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.form); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | // 忬¡éè®¿æ°æ®æ´æ¿ |
| | | formtidy() { |
| | | this.form.visitType2 = this.form.visitType; |
| | | this.form.date2 = this.form.longSendTime; |
| | | this.form.date2 = this.form.visitTime; |
| | | this.form.remark2 = this.form.remark; |
| | | }, |
| | | // è·åæ£è
è®°å½ |
| | |
| | | } |
| | | this.logsheetlist = res.rows[0].serviceSubtaskList; |
| | | this.templateid = this.logsheetlist[0].templateid; |
| | | const targetDate = new Date(this.form.longSendTime); // ç®æ æ¥æ |
| | | const targetDate = new Date(this.form.visitTime); // ç®æ æ¥æ |
| | | const now = new Date(); // å½åæ¶é´ |
| | | this.form.endtime = this.formatTime(this.form.endtime); |
| | | if (now < targetDate && this.form.sendstate == 2) { |
| | |
| | | this.form.remark = |
| | | this.form.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.form); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | this.zcform.remark = |
| | | this.zcform.remark + "ã" + this.getCurrentTime() + "ã"; |
| | | let form = structuredClone(this.zcform); |
| | | form.longSendTime = this.formatTime(form.date1); |
| | | form.visitTime = this.formatTime(form.date1); |
| | | form.finishtime = ""; |
| | | if (form.resource) { |
| | | if (form.resource == 2) { |
| | |
| | | <el-col :span="20" |
| | | ><el-form-item label="éç¨ææ¯" prop="region"> |
| | | <el-select |
| | | v-model="operationcodes" |
| | | v-model="form.oplevelcode" |
| | | style="width: 400px" |
| | | @remove-tag="removeopera" |
| | | size="medium" |
| | | :remote-method="remoteopcode" |
| | | multiple |
| | | filterable |
| | | remote |
| | | placeholder="è¯·éæ©ææ¯" |
| | |
| | | <el-option |
| | | class="ruleFormaa" |
| | | v-for="item in baseoperaList" |
| | | :label="item.opdesc" |
| | | :value="item.opcode" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | > |
| | | </el-option> |
| | | </el-select> </el-form-item |
| | |
| | | dialogVisiblepatientjb: false, //æ·»å ç¾ç
å¼¹æ¡ |
| | | deptcodesWards: [], //ç§å®¤æ°æ® |
| | | leavehospitaldistrictcodes: [], //ç
åºæ°æ® |
| | | operationcodes: [], //ææ¯æ°æ® |
| | | illnesscodes: [], //ç¾ç
æ°æ® |
| | | radio: 1, |
| | | checkboxlist: [], |
| | | tableLabel: [], |
| | | questionList: [], |
| | | donorchargeList: [], |
| | | baseoperaList: [], |
| | | baseoperaList: [ |
| | | { value: "1", label: "ä¸çº§ææ¯" }, |
| | | { value: "2", label: "äºçº§ææ¯" }, |
| | | { value: "3", label: "ä¸çº§ææ¯" }, |
| | | { value: "4", label: "åçº§ææ¯" }, |
| | | ], |
| | | usable: [ |
| | | { value: "0", label: "å¯ç¨" }, |
| | | { value: "1", label: "åç¨" }, |
| | |
| | | ]; |
| | | if (this.form.appltype == 1) { |
| | | this.leavehospitaldistrictcodes = []; |
| | | this.operationcodes = []; |
| | | this.form.oplevelcode = null; |
| | | this.illnesscodes = []; |
| | | } else if (this.form.appltype == 2) { |
| | | this.deptcodesWards = []; |
| | | this.operationcodes = []; |
| | | this.form.oplevelcode = null; |
| | | this.illnesscodes = []; |
| | | } else if (this.form.appltype == 3) { |
| | | this.deptcodesWards = []; |
| | | this.leavehospitaldistrictcodes = []; |
| | | this.operationcodes = []; |
| | | this.form.oplevelcode = null; |
| | | } else if (this.form.appltype == 4) { |
| | | this.deptcodesWards = []; |
| | | this.illnesscodes = []; |
| | |
| | | this.deptcodesWards[0] || |
| | | this.leavehospitaldistrictcodes[0] || |
| | | this.diagglist[0] || |
| | | this.operationcodes[0] || |
| | | this.form.oplevelcode || |
| | | this.form.longTask == 2 || |
| | | this.serviceType == 3 |
| | | ) { |
| | |
| | | this.form.deptcode = this.deptcodesWards.join(","); |
| | | this.form.leavehospitaldistrictcode = |
| | | this.leavehospitaldistrictcodes.join(","); |
| | | this.form.opcode = this.operationcodes.join(","); |
| | | // this.form.opcode = this.operationcodes.join(","); |
| | | this.form.icd10code = this.diagglist |
| | | .map((item) => item.icdcode) |
| | | .join(","); |
| | |
| | | }).then((res) => { |
| | | this.donorchargeList = res.rows; |
| | | }); |
| | | getbaseopera({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | }).then((res) => { |
| | | this.baseoperaList = res.rows; |
| | | }); |
| | | // getbaseopera({ |
| | | // pageNum: 1, |
| | | // pageSize: 1000, |
| | | // }).then((res) => { |
| | | // this.baseoperaList = res.rows; |
| | | // }); |
| | | }, |
| | | // ææ¯æ¥è¯¢ |
| | | remoteopcode(name) { |
| | | if (name) { |
| | | getbaseopera({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | opdesc: name, |
| | | }).then((res) => { |
| | | this.baseoperaList = res.rows; |
| | | }); |
| | | } |
| | | // if (name) { |
| | | // getbaseopera({ |
| | | // pageNum: 1, |
| | | // pageSize: 1000, |
| | | // opdesc: name, |
| | | // }).then((res) => { |
| | | // this.baseoperaList = res.rows; |
| | | // }); |
| | | // } |
| | | }, |
| | | // ç¾ç
æ¥è¯¢ |
| | | remotedonor(name) { |
| | | if (name) { |
| | | getbaseopera({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | opdesc: name, |
| | | }).then((res) => { |
| | | this.baseoperaList = res.rows; |
| | | }); |
| | | } |
| | | // if (name) { |
| | | // getbaseopera({ |
| | | // pageNum: 1, |
| | | // pageSize: 1000, |
| | | // opdesc: name, |
| | | // }).then((res) => { |
| | | // this.baseoperaList = res.rows; |
| | | // }); |
| | | // } |
| | | }, |
| | | // å¤çé®é¢å±åé |
| | | Variablehandling(arr, type) { |
| | |
| | | label="åºå®£ææ¥æ" |
| | | width="200" |
| | | align="center" |
| | | key="longSendTime" |
| | | prop="longSendTime" |
| | | key="visitTime" |
| | | prop="visitTime" |
| | | > |
| | | <template slot-scope="scope"> |
| | | <span>{{ formatTime(scope.row.longSendTime) }}</span> |
| | | <span>{{ formatTime(scope.row.visitTime) }}</span> |
| | | </template></el-table-column |
| | | > |
| | | <el-table-column |
| | |
| | | getSfStatistics(params).then((response) => { |
| | | this.loading = false; |
| | | |
| | | // this.total = response.total; |
| | | this.total = response.total; |
| | | // this.userList = response.data; |
| | | this.userList = this.customSort(response.data); |
| | | }); |
| | | }, |
| | | sortChineseNumber(a, b) { |
| | | // æå䏿æ°å |
| | | const chineseNumbers = [ |
| | | "ä¸", |
| | | "äº", |
| | | "ä¸", |
| | | "å", |
| | | "äº", |
| | | "å
", |
| | | "ä¸", |
| | | "å
«", |
| | | "ä¹", |
| | | "å", |
| | | "åä¸", |
| | | "åäº", |
| | | ]; |
| | | sortChineseNumber(aRow, bRow) { |
| | | const a = aRow.leavehospitaldistrictname; |
| | | const b = bRow.leavehospitaldistrictname; |
| | | |
| | | // ä»åç¬¦ä¸²ä¸æåç
åºæ°åï¼å¦"åç
åº" -> "å" |
| | | // 䏿æ°åå°é¿æä¼¯æ°åçæ å°ï¼æ©å±å°45ï¼ |
| | | const chineseNumMap = { |
| | | ä¸: 1, |
| | | äº: 2, |
| | | ä¸: 3, |
| | | å: 4, |
| | | äº: 5, |
| | | å
: 6, |
| | | ä¸: 7, |
| | | å
«: 8, |
| | | ä¹: 9, |
| | | å: 10, |
| | | åä¸: 11, |
| | | åäº: 12, |
| | | åä¸: 13, |
| | | åå: 14, |
| | | åäº: 15, |
| | | åå
: 16, |
| | | åä¸: 17, |
| | | åå
«: 18, |
| | | åä¹: 19, |
| | | äºå: 20, |
| | | äºåä¸: 21, |
| | | äºåäº: 22, |
| | | äºåä¸: 23, |
| | | äºåå: 24, |
| | | äºåäº: 25, |
| | | äºåå
: 26, |
| | | äºåä¸: 27, |
| | | äºåå
«: 28, |
| | | äºåä¹: 29, |
| | | ä¸å: 30, |
| | | ä¸åä¸: 31, |
| | | ä¸åäº: 32, |
| | | ä¸åä¸: 33, |
| | | ä¸åå: 34, |
| | | ä¸åäº: 35, |
| | | ä¸åå
: 36, |
| | | ä¸åä¸: 37, |
| | | ä¸åå
«: 38, |
| | | ä¸åä¹: 39, |
| | | åå: 40, |
| | | ååä¸: 41, |
| | | ååäº: 42, |
| | | ååä¸: 43, |
| | | ååå: 44, |
| | | ååäº: 45, |
| | | }; |
| | | |
| | | // æå䏿æ°å |
| | | const getNumberFromText = (text) => { |
| | | if (!text) return -1; |
| | | if (!text || typeof text !== "string") return -1; |
| | | |
| | | // å¹é
䏿æ°åï¼æ¯æä¸å°ååäº |
| | | const match = text.match(/^([ä¸äºä¸åäºå
ä¸å
«ä¹å]+)/); |
| | | |
| | | if (match && match[1]) { |
| | | return chineseNumbers.indexOf(match[1]); |
| | | const chineseNum = match[1]; |
| | | return chineseNumMap[chineseNum] !== undefined |
| | | ? chineseNumMap[chineseNum] |
| | | : -1; |
| | | } |
| | | |
| | | // å¦ææ²¡æå¹é
å°ä¸ææ°åï¼å°è¯å¹é
é¿æä¼¯æ°å |
| | | const arabicMatch = text.match(/^(\d+)/); |
| | | if (arabicMatch && arabicMatch[1]) { |
| | | const num = parseInt(arabicMatch[1], 10); |
| | | return num >= 1 && num <= 45 ? num : -1; |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | const numA = getNumberFromText(a); |
| | | const numB = getNumberFromText(b); |
| | | |
| | | if (numA === -1 && numB === -1) return 0; |
| | | if (numA === -1) return 1; // æ æ³è§£æçæ¾å°åé¢ |
| | | if (numB === -1) return -1; // æ æ³è§£æçæ¾å°åé¢ |
| | | // å¤çæ æ³è§£æçæ
åµ |
| | | if (numA === -1 && numB === -1) { |
| | | return (a || "").localeCompare(b || ""); |
| | | } |
| | | if (numA === -1) return 1; |
| | | if (numB === -1) return -1; |
| | | |
| | | return numA - numB; |
| | | }, |
| | |
| | | } |
| | | }, |
| | | customSort(data) { |
| | | // å®ä¹æ¨ææçç
åºé¡ºåºï¼æ©å±å°ä¸åï¼ |
| | | // å®ä¹æ¨ææçç
åºé¡ºåºï¼æ©å±å°ååäºï¼ |
| | | const order = [ |
| | | "ä¸", |
| | | "äº", |
| | | "ä¸", |
| | | "å", |
| | |
| | | "äºåå
«", |
| | | "äºåä¹", |
| | | "ä¸å", |
| | | "ä¸åä¸", |
| | | "ä¸åäº", |
| | | "ä¸åä¸", |
| | | "ä¸åå", |
| | | "ä¸åäº", |
| | | "ä¸åå
", |
| | | "ä¸åä¸", |
| | | "ä¸åå
«", |
| | | "ä¸åä¹", |
| | | "åå", |
| | | "ååä¸", |
| | | "ååäº", |
| | | "ååä¸", |
| | | "ååå", |
| | | "ååäº", |
| | | ]; |
| | | |
| | | return data.sort((a, b) => { |
| | | // æåç
åºåç§°ä¸ç䏿æ°åé¨å[6](@ref) |
| | | // æåç
åºåç§°ä¸ç䏿æ°åé¨å |
| | | const getIndex = (name) => { |
| | | const numStr = name.match( |
| | | /^(äº|ä¸|å|äº|å
|ä¸|å
«|ä¹|å|åä¸|åäº|åä¸|åå|åäº|åå
|åä¸|åå
«|åä¹|äºå|äºåä¸|äºåäº|äºåä¸|äºåå|äºåäº|äºåå
|äºåä¸|äºåå
«|äºåä¹|ä¸å)/ |
| | | )?.[1]; |
| | | return order.indexOf(numStr); |
| | | if (!name || typeof name !== "string") return -1; |
| | | |
| | | // å¹é
䏿æ°å |
| | | const chineseMatch = name.match(/^([ä¸äºä¸åäºå
ä¸å
«ä¹å]+)/); |
| | | if (chineseMatch && chineseMatch[1]) { |
| | | return order.indexOf(chineseMatch[1]); |
| | | } |
| | | |
| | | // å¹é
é¿æä¼¯æ°å |
| | | const arabicMatch = name.match(/^(\d+)/); |
| | | if (arabicMatch && arabicMatch[1]) { |
| | | const num = parseInt(arabicMatch[1], 10); |
| | | if (num >= 1 && num <= 45) { |
| | | return num - 1; // å 为æ°ç»ç´¢å¼ä»0å¼å§ |
| | | } |
| | | } |
| | | |
| | | return -1; |
| | | }; |
| | | |
| | | const indexA = getIndex(a.leavehospitaldistrictname); |
| | | const indexB = getIndex(b.leavehospitaldistrictname); |
| | | |
| | | // 妿é½å¨å®ä¹ç顺åºä¸ï¼æå®ä¹é¡ºåºæï¼å¦åï¼æªå®ä¹çæå¨åé¢[2](@ref) |
| | | // æåºé»è¾ |
| | | if (indexA === -1 && indexB === -1) { |
| | | return (a.leavehospitaldistrictname || "").localeCompare( |
| | | b.leavehospitaldistrictname || "" |
| | | ); |
| | | } |
| | | if (indexA === -1) return 1; |
| | | if (indexB === -1) return -1; |
| | | return indexA - indexB; |