| | |
| | | <template> |
| | | <div class="topic-dialog"> |
| | | <div class="dialog-header"> |
| | | <span class="title"> {{ configTitle }}指标详情 </span> |
| | | <el-button |
| | | type="primary" |
| | | size="mini" |
| | | icon="el-icon-download" |
| | | :disabled="!processedTopicList.length" |
| | | @click="exportTopicDetail" |
| | | > |
| | | 导出 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <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> |
| | | </template> |
| | | |
| | | <script> |
| | | import ExcelJS from "exceljs"; |
| | | import { saveAs } from "file-saver"; |
| | | |
| | | export default { |
| | | name: 'TopicDialog', |
| | | name: "TopicDialog", |
| | | props: { |
| | | rowData: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | default: () => ({}), |
| | | }, |
| | | queryParams: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | } |
| | | default: () => ({}), |
| | | }, |
| | | topType: { |
| | | type: String, |
| | | }, |
| | | topicList: { |
| | | type: [Array, Object], |
| | | default: () => ({}), |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | topiclist: [] |
| | | processedTopicList: [], |
| | | orgname: "", // 新增:医院名称 |
| | | }; |
| | | }, |
| | | |
| | | mounted() { |
| | | this.loadData(); |
| | | computed: { |
| | | configTitle() { |
| | | const key = this.queryParams?.configKey; |
| | | return key === "returnVisitCount" ? "复诊通知" : "满意度"; |
| | | }, |
| | | }, |
| | | |
| | | created() { |
| | | // 获取医院名称 |
| | | this.orgname = localStorage.getItem("orgname") || ""; |
| | | }, |
| | | watch: { |
| | | topicList: { |
| | | immediate: true, |
| | | handler(newVal) { |
| | | this.processTopicList(newVal); |
| | | }, |
| | | }, |
| | | }, |
| | | methods: { |
| | | // 加载数据 |
| | | async loadData() { |
| | | processTopicList(data) { |
| | | if (!data || typeof data !== "object") { |
| | | this.processedTopicList = []; |
| | | return; |
| | | } |
| | | |
| | | const result = []; |
| | | Object.keys(data).forEach((key) => { |
| | | const item = data[key]; |
| | | if (item && item.scriptContent) { |
| | | const processedItem = JSON.parse(JSON.stringify(item)); |
| | | |
| | | if (processedItem.details && Array.isArray(processedItem.details)) { |
| | | processedItem.details = processedItem.details.filter( |
| | | (detail) => detail && detail.optionText |
| | | ); |
| | | } |
| | | result.push(processedItem); |
| | | } |
| | | }); |
| | | |
| | | this.processedTopicList = result; |
| | | }, |
| | | |
| | | // 格式化日期范围字符串(与主页面一致) |
| | | formatDateRangeForExport() { |
| | | let dateRangeString = ""; |
| | | let sheetNameSuffix = ""; |
| | | const isLishuiHospital = this.orgname == "丽水市中医院"; |
| | | |
| | | if ( |
| | | this.queryParams.dateRange && |
| | | this.queryParams.dateRange.length === 2 |
| | | ) { |
| | | const startDateStr = this.queryParams.dateRange[0]; |
| | | const endDateStr = this.queryParams.dateRange[1]; |
| | | |
| | | if (isLishuiHospital) { |
| | | // 丽水市中医院:只显示年月 |
| | | const formatMonthOnly = (dateStr) => { |
| | | const date = new Date(dateStr); |
| | | const year = date.getFullYear(); |
| | | const month = date.getMonth() + 1; |
| | | return `${year}年${month}月`; |
| | | }; |
| | | const startDateFormatted = formatMonthOnly(startDateStr); |
| | | const endDateFormatted = formatMonthOnly(endDateStr); |
| | | dateRangeString = `${startDateFormatted}至${endDateFormatted}`; |
| | | sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`; |
| | | } else { |
| | | // 其他医院:显示年月日 |
| | | const formatDateForDisplay = (dateStr) => { |
| | | return dateStr.split(" ")[0]; // 如果包含时间部分,只取日期 |
| | | }; |
| | | const startDateFormatted = formatDateForDisplay(startDateStr); |
| | | const endDateFormatted = formatDateForDisplay(endDateStr); |
| | | dateRangeString = `${startDateFormatted}至${endDateFormatted}`; |
| | | sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`; |
| | | } |
| | | } else { |
| | | const now = new Date(); |
| | | const currentMonth = now.getMonth() + 1; |
| | | const currentYear = now.getFullYear(); |
| | | |
| | | if (isLishuiHospital) { |
| | | // 丽水市中医院:显示年月 |
| | | dateRangeString = `${currentYear}年${currentMonth}月`; |
| | | sheetNameSuffix = `${currentYear}年${currentMonth}月`; |
| | | } else { |
| | | // 其他医院:显示月份 |
| | | dateRangeString = `${currentMonth}月`; |
| | | sheetNameSuffix = `${currentMonth}月`; |
| | | } |
| | | } |
| | | |
| | | return { dateRangeString, sheetNameSuffix }; |
| | | }, |
| | | |
| | | // 导出题目明细 |
| | | async exportTopicDetail() { |
| | | if (!this.processedTopicList.length) { |
| | | this.$message.warning("暂无数据可导出"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 这里从父组件传递数据,不需要重新调用API |
| | | this.topiclist = this.$parent.topiclist || []; |
| | | // 获取格式化后的日期范围 |
| | | const { dateRangeString, sheetNameSuffix } = |
| | | this.formatDateRangeForExport(); |
| | | |
| | | const workbook = new ExcelJS.Workbook(); |
| | | const sheetName = `${ |
| | | this.rowData.leavehospitaldistrictname || this.rowData.deptname |
| | | }${this.configTitle}明细_${sheetNameSuffix}`; |
| | | const worksheet = workbook.addWorksheet(sheetName); |
| | | |
| | | // 样式定义(保持不变) |
| | | const titleStyle = { |
| | | font: { name: "微软雅黑", size: 14, bold: true }, |
| | | alignment: { horizontal: "center", vertical: "middle" }, |
| | | fill: { |
| | | type: "pattern", |
| | | pattern: "solid", |
| | | fgColor: { argb: "FFF5F7FA" }, |
| | | }, |
| | | }; |
| | | |
| | | const subtitleStyle = { |
| | | font: { name: "微软雅黑", size: 12, bold: true }, |
| | | alignment: { horizontal: "left", vertical: "middle" }, |
| | | }; |
| | | |
| | | const headerStyle = { |
| | | font: { name: "微软雅黑", size: 11, bold: true }, |
| | | fill: { |
| | | type: "pattern", |
| | | pattern: "solid", |
| | | fgColor: { argb: "FFEBEEF5" }, |
| | | }, |
| | | alignment: { horizontal: "center", vertical: "middle" }, |
| | | border: { |
| | | top: { style: "thin" }, |
| | | left: { style: "thin" }, |
| | | bottom: { style: "thin" }, |
| | | right: { style: "thin" }, |
| | | }, |
| | | }; |
| | | |
| | | const cellStyle = { |
| | | font: { name: "宋体", size: 10 }, |
| | | alignment: { horizontal: "center", vertical: "middle" }, |
| | | border: { |
| | | top: { style: "thin" }, |
| | | left: { style: "thin" }, |
| | | bottom: { style: "thin" }, |
| | | right: { style: "thin" }, |
| | | }, |
| | | }; |
| | | |
| | | // 标题区 |
| | | worksheet.mergeCells(1, 1, 1, 4); |
| | | worksheet.getCell( |
| | | 1, |
| | | 1 |
| | | ).value = `${this.configTitle}题目明细(${dateRangeString})`; |
| | | worksheet.getCell(1, 1).style = titleStyle; |
| | | |
| | | worksheet.mergeCells(2, 1, 2, 4); |
| | | worksheet.getCell(2, 1).value = `统计对象:${ |
| | | this.rowData.leavehospitaldistrictname || this.rowData.deptname |
| | | }`; |
| | | worksheet.getCell(2, 1).style = subtitleStyle; |
| | | |
| | | let currentRow = 4; |
| | | |
| | | // 逐题写入 |
| | | this.processedTopicList.forEach((item, index) => { |
| | | worksheet.mergeCells(currentRow, 1, currentRow, 4); |
| | | worksheet.getCell(currentRow, 1).value = `第${index + 1}题:${ |
| | | item.scriptContent |
| | | } [${item.scriptType == 1 ? "单选题" : "多选题"}]`; |
| | | worksheet.getCell(currentRow, 1).style = subtitleStyle; |
| | | currentRow++; |
| | | |
| | | const headerRow = worksheet.addRow([ |
| | | "问题选项", |
| | | "选择人数", |
| | | "占比", |
| | | "", |
| | | ]); |
| | | headerRow.eachCell((cell) => { |
| | | cell.style = headerStyle; |
| | | }); |
| | | currentRow++; |
| | | |
| | | item.details.forEach((detail) => { |
| | | const percent = |
| | | detail.chosenPercentage != null |
| | | ? (Number(detail.chosenPercentage) * 100).toFixed(2) + "%" |
| | | : "-"; |
| | | |
| | | const row = worksheet.addRow([ |
| | | detail.optionText, |
| | | detail.chosenQuantity || 0, |
| | | percent, |
| | | "", |
| | | ]); |
| | | row.eachCell((cell) => { |
| | | cell.style = cellStyle; |
| | | }); |
| | | currentRow++; |
| | | }); |
| | | |
| | | currentRow++; |
| | | }); |
| | | |
| | | // 设置列宽 |
| | | worksheet.columns = [ |
| | | { width: 40 }, |
| | | { width: 12 }, |
| | | { width: 12 }, |
| | | { width: 10 }, |
| | | ]; |
| | | |
| | | // 生成文件名(与主页面保持一致) |
| | | const fileName = `${ |
| | | this.rowData.leavehospitaldistrictname || this.rowData.deptname |
| | | }${this.configTitle}明细_${dateRangeString}.xlsx`; |
| | | |
| | | const buffer = await workbook.xlsx.writeBuffer(); |
| | | saveAs( |
| | | new Blob([buffer], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }), |
| | | fileName |
| | | ); |
| | | |
| | | this.$message.success("导出成功"); |
| | | } catch (error) { |
| | | console.error('加载题目详情失败:', error); |
| | | this.$message.error('加载题目详情失败'); |
| | | console.error("导出失败:", error); |
| | | this.$message.error(`导出失败: ${error.message}`); |
| | | } |
| | | }, |
| | | |
| | | // 格式化百分比 |
| | | 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)}%`; |
| | | }, |
| | | |
| | | // 关闭对话框 |
| | | handleClose() { |
| | | this.$emit('close'); |
| | | } |
| | | } |
| | | this.$emit("close"); |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .dialog-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 12px 16px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | background: #fafafa; |
| | | |
| | | .title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | } |
| | | |
| | | ::v-deep .el-table th { |
| | | background-color: #f1f5f9; |
| | | color: #333; |