<template>
|
<div class="education-statistics">
|
<!-- 搜索区域 -->
|
<div class="search-container">
|
<el-form
|
:model="queryParams"
|
ref="queryForm"
|
size="small"
|
:inline="true"
|
label-width="100px"
|
>
|
<el-form-item label="统计维度" prop="groupType">
|
<el-select
|
v-model="queryParams.groupType"
|
placeholder="请选择统计维度"
|
@change="handleGroupTypeChange"
|
style="width: 180px"
|
>
|
<el-option label="按科室统计" value="1"></el-option>
|
<el-option label="按病区统计" value="2"></el-option>
|
</el-select>
|
<el-select
|
style="margin-left: 10px"
|
v-if="queryParams.groupType == '2'"
|
v-model="queryParams.hospitaldistrictcodes"
|
size="medium"
|
multiple
|
filterable
|
placeholder="请选择病区"
|
>
|
<el-option
|
v-for="item in flatArrayhospit"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
<el-select
|
v-else-if="queryParams.groupType == '1'"
|
v-model="queryParams.deptcodes"
|
size="medium"
|
multiple
|
filterable
|
placeholder="请选择科室"
|
>
|
<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="hospType">
|
<el-select
|
v-model="queryParams.hospType"
|
placeholder="请选择就诊类型"
|
clearable
|
style="width: 150px"
|
>
|
<el-option label="门诊" value="1"></el-option>
|
<el-option label="出院" value="2"></el-option>
|
<el-option label="专病" value="3"></el-option>
|
<el-option label="入院/外部导入" value="4"></el-option>
|
<el-option label="体检" value="5"></el-option>
|
</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"
|
style="width: 280px"
|
:picker-options="datePickerOptions"
|
>
|
</el-date-picker>
|
</el-form-item>
|
|
<el-form-item label="宣教发送时间" prop="visittime">
|
<el-date-picker
|
v-model="queryParams.visittime"
|
type="date"
|
placeholder="选择日期"
|
value-format="yyyy-MM-dd"
|
style="width: 180px"
|
>
|
</el-date-picker>
|
</el-form-item>
|
|
<el-form-item>
|
<el-button
|
type="primary"
|
icon="el-icon-search"
|
size="medium"
|
@click="handleQuery"
|
:loading="loading"
|
>搜索</el-button
|
>
|
<el-button icon="el-icon-refresh" size="medium" @click="resetQuery"
|
>重置</el-button
|
>
|
<el-button
|
type="warning"
|
plain
|
icon="el-icon-download"
|
size="medium"
|
@click="handleExport"
|
v-hasPermi="['system:statistics:export']"
|
>导出</el-button
|
>
|
</el-form-item>
|
</el-form>
|
</div>
|
|
<!-- 统计数据概览 -->
|
<div class="summary-cards" v-if="statisticsData.total > 0">
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<div class="summary-card">
|
<div class="card-title">发送总量</div>
|
<div class="card-value">{{ totalCount }}</div>
|
<div class="card-desc">总宣教发送次数</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="summary-card">
|
<div class="card-title">发送成功量</div>
|
<div class="card-value">{{ sendSuccessCount }}</div>
|
<div class="card-desc">已成功发送的宣教</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="summary-card">
|
<div class="card-title">已读量</div>
|
<div class="card-value">{{ readCount }}</div>
|
<div class="card-desc">患者已阅读的宣教</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="summary-card">
|
<div class="card-title">平均发送成功率</div>
|
<div class="card-value">{{ avgSendSuccessRate }}%</div>
|
<div class="card-desc">整体发送成功比例</div>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
|
<!-- 数据表格 -->
|
<div class="table-container">
|
<el-table
|
v-loading="loading"
|
:data="statisticsData.list"
|
:border="true"
|
style="width: 100%"
|
:default-sort="{ prop: 'totalCount', order: 'descending' }"
|
@sort-change="handleSortChange"
|
>
|
<el-table-column prop="groupName" label="分组名称" align="center" fixed>
|
<!-- <template slot-scope="scope">
|
<span class="group-name" @click="handleGroupDetail(scope.row)">
|
{{ scope.row.groupName }}
|
</span>
|
</template> -->
|
</el-table-column>
|
|
<el-table-column
|
prop="groupCode"
|
label="分组编码"
|
align="center"
|
></el-table-column>
|
|
<el-table-column
|
prop="totalCount"
|
label="宣教发送总量"
|
align="center"
|
sortable="custom"
|
>
|
<template slot-scope="scope">
|
<span class="count-highlight">{{ scope.row.totalCount }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column
|
prop="sendSuccessCount"
|
label="发送成功量"
|
align="center"
|
sortable="custom"
|
>
|
<template slot-scope="scope">
|
<span class="success-count">{{ scope.row.sendSuccessCount }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column
|
prop="readCount"
|
label="已读量"
|
align="center"
|
sortable="custom"
|
>
|
<template slot-scope="scope">
|
<span class="read-count">{{ scope.row.readCount }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column
|
prop="sendSuccessRate"
|
label="发送成功率"
|
align="center"
|
width="200"
|
sortable="custom"
|
>
|
<template slot-scope="scope">
|
<el-progress
|
:percentage="scope.row.sendSuccessRate * 100"
|
:stroke-width="8"
|
:show-text="false"
|
style="width: 80px; margin: 0 auto"
|
:color="getRateColor(scope.row.sendSuccessRate)"
|
/>
|
<span class="rate-text"
|
>{{ (scope.row.sendSuccessRate * 100).toFixed(1) }}%</span
|
>
|
</template>
|
</el-table-column>
|
|
<el-table-column
|
prop="readRate"
|
label="已读率"
|
align="center"
|
width="200"
|
sortable="custom"
|
>
|
<template slot-scope="scope">
|
<el-progress
|
:percentage="scope.row.readRate * 100"
|
:stroke-width="8"
|
:show-text="false"
|
style="width: 80px; margin: 0 auto"
|
:color="getRateColor(scope.row.readRate)"
|
/>
|
<span class="rate-text"
|
>{{ (scope.row.readRate * 100).toFixed(1) }}%</span
|
>
|
</template>
|
</el-table-column>
|
|
<!-- <el-table-column label="操作" align="center" width="200" fixed="right">
|
<template slot-scope="scope">
|
<el-button
|
type="text"
|
size="small"
|
@click="handleDetail(scope.row)"
|
icon="el-icon-document"
|
v-hasPermi="['system:statistics:detail']"
|
>详细数据</el-button
|
>
|
<el-button
|
type="text"
|
size="small"
|
@click="handleExportGroup(scope.row)"
|
icon="el-icon-download"
|
v-hasPermi="['system:statistics:export']"
|
>导出</el-button
|
>
|
</template>
|
</el-table-column> -->
|
</el-table>
|
|
<!-- 分页 -->
|
<pagination
|
v-show="statisticsData.total > 0"
|
:total="statisticsData.total"
|
:page.sync="queryParams.pageNum"
|
:limit.sync="queryParams.pageSize"
|
@pagination="getList"
|
/>
|
</div>
|
|
<!-- 分组详情弹窗 -->
|
<el-dialog
|
:title="detailTitle"
|
:visible.sync="detailVisible"
|
width="80%"
|
:before-close="handleCloseDetail"
|
>
|
<div v-loading="detailLoading">
|
<el-row :gutter="20" class="detail-header">
|
<el-col :span="6">
|
<div class="detail-item">
|
<label>分组名称:</label>
|
<span>{{ currentGroup.groupName }}</span>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="detail-item">
|
<label>分组编码:</label>
|
<span>{{ currentGroup.groupCode }}</span>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="detail-item">
|
<label>发送成功率:</label>
|
<span class="rate-highlight"
|
>{{ (currentGroup.sendSuccessRate * 100).toFixed(1) }}%</span
|
>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="detail-item">
|
<label>已读率:</label>
|
<span class="rate-highlight"
|
>{{ (currentGroup.readRate * 100).toFixed(1) }}%</span
|
>
|
</div>
|
</el-col>
|
</el-row>
|
|
<el-tabs v-model="detailActiveTab" class="detail-tabs">
|
<el-tab-pane label="趋势分析" name="trend">
|
<!-- 这里可以放置图表组件 -->
|
<div class="chart-placeholder">
|
趋势图表(可根据需求接入ECharts)
|
</div>
|
</el-tab-pane>
|
<el-tab-pane label="明细数据" name="detail">
|
<el-table
|
:data="detailList"
|
border
|
style="width: 100%; margin-top: 20px"
|
>
|
<el-table-column
|
prop="patientName"
|
label="患者姓名"
|
align="center"
|
width="120"
|
></el-table-column>
|
<el-table-column
|
prop="patientNo"
|
label="患者编号"
|
align="center"
|
width="120"
|
></el-table-column>
|
<el-table-column
|
prop="sendTime"
|
label="发送时间"
|
align="center"
|
width="180"
|
></el-table-column>
|
<el-table-column
|
prop="readTime"
|
label="阅读时间"
|
align="center"
|
width="180"
|
></el-table-column>
|
<el-table-column
|
prop="educationTitle"
|
label="宣教标题"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="status"
|
label="状态"
|
align="center"
|
width="100"
|
>
|
<template slot-scope="scope">
|
<el-tag
|
:type="
|
scope.row.status === '已读'
|
? 'success'
|
: scope.row.status === '发送成功'
|
? 'info'
|
: 'danger'
|
"
|
size="small"
|
>
|
{{ scope.row.status }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-tab-pane>
|
</el-tabs>
|
</div>
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="detailVisible = false">关 闭</el-button>
|
</span>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
import { gethelibraryCount } from "@/api/AiCentre/index";
|
|
export default {
|
name: "EducationStatistics",
|
data() {
|
return {
|
// 查询参数
|
queryParams: {
|
pageNum: 1,
|
pageSize: 1000,
|
groupType: "1", // 1-科室,2-病区
|
deptcodes: ['all'], // 科室code数组
|
hospitaldistrictcodes: [], // 病区code数组
|
hospType: undefined, // 就诊类型
|
starttime: undefined, // 入院开始时间
|
endtime: undefined, // 入院结束时间
|
visittime: undefined, // 宣教发送时间
|
dateRange: [], // 入院时间范围
|
orderBy: "totalCount", // 排序字段
|
order: "descending", // 排序方式
|
},
|
|
// 统计数据
|
statisticsData: {
|
total: 0,
|
list: [],
|
},
|
|
// 下拉选项
|
flatArraydept: [],
|
|
flatArrayhospit: [],
|
|
// 加载状态
|
loading: false,
|
detailLoading: false,
|
|
// 详情弹窗
|
detailVisible: false,
|
detailTitle: "",
|
detailActiveTab: "trend",
|
currentGroup: {},
|
detailList: [],
|
|
// 日期选择器配置
|
datePickerOptions: {
|
disabledDate(time) {
|
return time.getTime() > Date.now();
|
},
|
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]);
|
},
|
},
|
],
|
},
|
};
|
},
|
|
computed: {
|
// 计算总发送量
|
totalCount() {
|
return this.statisticsData.list.reduce(
|
(sum, item) => sum + item.totalCount,
|
0
|
);
|
},
|
|
// 计算总发送成功量
|
sendSuccessCount() {
|
return this.statisticsData.list.reduce(
|
(sum, item) => sum + item.sendSuccessCount,
|
0
|
);
|
},
|
|
// 计算总已读量
|
readCount() {
|
return this.statisticsData.list.reduce(
|
(sum, item) => sum + item.readCount,
|
0
|
);
|
},
|
|
// 计算平均发送成功率
|
avgSendSuccessRate() {
|
if (this.statisticsData.list.length === 0) return 0;
|
const totalRate = this.statisticsData.list.reduce(
|
(sum, item) => sum + item.sendSuccessRate,
|
0
|
);
|
return ((totalRate / this.statisticsData.list.length) * 100).toFixed(1);
|
},
|
},
|
|
created() {
|
this.flatArrayhospit = this.$store.getters.belongWards.map((ward) => {
|
return {
|
label: ward.districtName,
|
value: ward.districtCode,
|
};
|
});
|
this.flatArraydept = this.$store.getters.belongDepts.map((dept) => {
|
return {
|
label: dept.deptName,
|
value: dept.deptCode,
|
};
|
});
|
console.log(this.flatArrayhospit,'this.flatArrayhospit');
|
|
this.flatArraydept.push({ label: "全部", value: "all" });
|
this.flatArrayhospit.push({ label: "全部", value: "all" });
|
this.getList();
|
},
|
|
methods: {
|
// 获取统计数据列表
|
async getList() {
|
this.loading = true;
|
try {
|
console.log(this.queryParams.hospitaldistrictcodes);
|
|
// 构建请求参数
|
const params = {
|
pageNum: this.queryParams.pageNum,
|
pageSize: this.queryParams.pageSize,
|
hospitaldistrictcodes:
|
this.queryParams.hospitaldistrictcodes.includes("all")
|
? this.getAllWardCodes()
|
: this.queryParams.hospitaldistrictcodes,
|
deptcodes: this.queryParams.deptcodes.includes("all")
|
? this.getAllDeptCodes()
|
: this.queryParams.deptcodes,
|
};
|
|
// 根据统计维度设置参数
|
if (this.queryParams.groupType == "1") {
|
params.hospitaldistrictcodes = [];
|
} else if (this.queryParams.groupType == "2") {
|
params.deptcodes = [];
|
}
|
|
// 设置时间参数
|
if (
|
this.queryParams.dateRange &&
|
this.queryParams.dateRange.length === 2
|
) {
|
params.starttime = this.queryParams.dateRange[0];
|
params.endtime = this.queryParams.dateRange[1];
|
}
|
|
// 设置其他参数
|
if (this.queryParams.hospType) {
|
params.hospType = this.queryParams.hospType;
|
}
|
if (this.queryParams.visittime) {
|
params.visittime = this.queryParams.visittime;
|
}
|
|
// 调用接口
|
const response = await gethelibraryCount(params);
|
this.statisticsData = {
|
total: response.total || 0,
|
list: response.list || [],
|
};
|
} catch (error) {
|
console.error("获取统计数据失败:", error);
|
this.$message.error("获取统计数据失败");
|
this.statisticsData = { total: 0, list: [] };
|
} finally {
|
this.loading = false;
|
}
|
},
|
getAllWardCodes() {
|
return this.flatArrayhospit
|
.filter((item) => item.value !== "all")
|
.map((item) => item.value);
|
},
|
|
getAllDeptCodes() {
|
return this.flatArraydept
|
.filter((item) => item.value !== "all")
|
.map((item) => item.value);
|
},
|
// 统计维度变更
|
handleGroupTypeChange(value) {
|
// 切换维度时清空对应的选择
|
if (value === "dept") {
|
this.queryParams.hospitaldistrictcodes = [];
|
} else if (value === "ward") {
|
this.queryParams.deptcodes = [];
|
}
|
},
|
|
// 处理搜索
|
handleQuery() {
|
this.queryParams.pageNum = 1;
|
this.getList();
|
},
|
|
// 重置搜索条件
|
resetQuery() {
|
this.queryParams = {
|
pageNum: 1,
|
pageSize: 10,
|
groupType: "dept",
|
deptcodes: [],
|
hospitaldistrictcodes: [],
|
hospType: undefined,
|
starttime: undefined,
|
endtime: undefined,
|
visittime: undefined,
|
dateRange: [],
|
orderBy: "totalCount",
|
order: "descending",
|
};
|
this.getList();
|
},
|
|
// 表格排序
|
handleSortChange({ column, prop, order }) {
|
if (prop) {
|
this.queryParams.orderBy = prop;
|
this.queryParams.order = order;
|
this.getList();
|
}
|
},
|
|
// 根据成功率获取进度条颜色
|
getRateColor(rate) {
|
const percentage = rate * 100;
|
if (percentage >= 90) return "#67C23A";
|
if (percentage >= 80) return "#E6A23C";
|
if (percentage >= 60) return "#409EFF";
|
return "#F56C6C";
|
},
|
|
// 查看分组详情
|
handleGroupDetail(row) {
|
this.currentGroup = row;
|
this.detailTitle = `${row.groupName} - 宣教统计详情`;
|
this.detailActiveTab = "trend";
|
this.detailVisible = true;
|
this.loadDetailData();
|
},
|
|
// 加载详情数据
|
async loadDetailData() {
|
this.detailLoading = true;
|
try {
|
// 这里应该调用获取详情的接口
|
// 模拟数据
|
setTimeout(() => {
|
this.detailList = [
|
{
|
patientName: "张三",
|
patientNo: "P202312001",
|
sendTime: "2023-12-01 10:30:00",
|
readTime: "2023-12-01 14:20:00",
|
educationTitle: "骨科术后康复指导",
|
status: "已读",
|
},
|
{
|
patientName: "李四",
|
patientNo: "P202312002",
|
sendTime: "2023-12-01 11:15:00",
|
readTime: "",
|
educationTitle: "心血管疾病预防",
|
status: "发送成功",
|
},
|
{
|
patientName: "王五",
|
patientNo: "P202312003",
|
sendTime: "2023-12-02 09:45:00",
|
readTime: "2023-12-02 16:10:00",
|
educationTitle: "糖尿病饮食指导",
|
status: "已读",
|
},
|
];
|
this.detailLoading = false;
|
}, 500);
|
} catch (error) {
|
console.error("加载详情数据失败:", error);
|
this.$message.error("加载详情数据失败");
|
this.detailLoading = false;
|
}
|
},
|
|
// 查看详情
|
handleDetail(row) {
|
this.currentGroup = row;
|
this.detailTitle = `${row.groupName} - 明细数据`;
|
this.detailActiveTab = "detail";
|
this.detailVisible = true;
|
this.loadDetailData();
|
},
|
|
// 导出当前分组数据
|
handleExportGroup(row) {
|
this.$confirm(`确定要导出 "${row.groupName}" 的统计数据吗?`, "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
// 这里调用导出接口
|
this.$message.success("导出任务已开始,请稍后在下载中心查看");
|
})
|
.catch(() => {});
|
},
|
|
// 导出全部数据
|
handleExport() {
|
this.$confirm("确定要导出全部统计数据吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
// 构建导出参数
|
const exportParams = { ...this.queryParams };
|
delete exportParams.pageNum;
|
delete exportParams.pageSize;
|
|
// 这里调用导出接口
|
this.$message.success("导出任务已开始,请稍后在下载中心查看");
|
})
|
.catch(() => {});
|
},
|
|
// 关闭详情弹窗
|
handleCloseDetail(done) {
|
this.$confirm("确认关闭?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
done();
|
})
|
.catch(() => {});
|
},
|
},
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.education-statistics {
|
padding: 20px;
|
background: #fff;
|
min-height: calc(100vh - 84px);
|
|
.search-container {
|
background: #f8f9fa;
|
padding: 20px;
|
border-radius: 8px;
|
margin-bottom: 20px;
|
border: 1px solid #ebeef5;
|
}
|
|
.summary-cards {
|
margin-bottom: 20px;
|
|
.summary-card {
|
background: #fff;
|
border-radius: 8px;
|
padding: 20px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
text-align: center;
|
border: 1px solid #ebeef5;
|
transition: all 0.3s ease;
|
|
&:hover {
|
transform: translateY(-5px);
|
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
|
}
|
|
.card-title {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 10px;
|
}
|
|
.card-value {
|
font-size: 28px;
|
font-weight: 600;
|
color: #409eff;
|
margin-bottom: 5px;
|
}
|
|
.card-desc {
|
font-size: 12px;
|
color: #c0c4cc;
|
}
|
}
|
}
|
|
.table-container {
|
background: #fff;
|
border-radius: 8px;
|
padding: 20px;
|
border: 1px solid #ebeef5;
|
|
.group-name {
|
color: #409eff;
|
cursor: pointer;
|
text-decoration: underline;
|
transition: color 0.3s;
|
|
&:hover {
|
color: #66b1ff;
|
}
|
}
|
|
.count-highlight {
|
font-weight: 600;
|
color: #606266;
|
}
|
|
.success-count {
|
color: #67c23a;
|
font-weight: 600;
|
}
|
|
.read-count {
|
color: #e6a23c;
|
font-weight: 600;
|
}
|
|
.rate-text {
|
display: block;
|
margin-top: 5px;
|
font-size: 12px;
|
color: #606266;
|
}
|
}
|
|
.detail-header {
|
margin-bottom: 20px;
|
padding: 20px;
|
background: #f8f9fa;
|
border-radius: 8px;
|
|
.detail-item {
|
label {
|
color: #909399;
|
font-size: 14px;
|
}
|
|
span {
|
font-size: 16px;
|
font-weight: 500;
|
color: #303133;
|
}
|
|
.rate-highlight {
|
color: #409eff;
|
font-weight: 600;
|
}
|
}
|
}
|
|
.detail-tabs {
|
margin-top: 20px;
|
}
|
|
.chart-placeholder {
|
height: 300px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: #f8f9fa;
|
border-radius: 8px;
|
color: #909399;
|
border: 1px dashed #dcdfe6;
|
}
|
}
|
|
// 响应式调整
|
@media (max-width: 1200px) {
|
.education-statistics {
|
padding: 10px;
|
}
|
|
.summary-cards {
|
.el-col {
|
margin-bottom: 10px;
|
}
|
}
|
}
|
</style>
|