<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"
|
key="leavehospitaldistrictname"
|
prop="leavehospitaldistrictname"
|
:show-overflow-tooltip="true"
|
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"
|
: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: [],
|
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: "joyCount",
|
...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 = response.data || [];
|
this.total = response.total || 0;
|
} catch (error) {
|
console.error('获取统计列表失败:', error);
|
this.$message.error('获取数据失败');
|
} finally {
|
this.loading = false;
|
}
|
},
|
|
// 处理统计类型变化
|
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;
|
this.topicVisible = true;
|
|
try {
|
// 处理查询参数
|
const params = {
|
configKey: "joyCount",
|
...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 || [];
|
} 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>
|