<template>
|
<div class="satisfaction-statistics">
|
<!-- 查询条件区域 -->
|
<div class="query-section">
|
<el-card shadow="never">
|
<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>
|
</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
|
v-loading="detailLoading"
|
:data="questionDetailData"
|
:border="true"
|
style="width: 100%"
|
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="序号"
|
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="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>
|
</el-tab-pane>
|
|
<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="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="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="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="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="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="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>
|
|
<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",
|
data() {
|
return {
|
// 查询参数
|
queryParams: {
|
patientSource: "",
|
deptCode: "",
|
wardCode: "",
|
dateRange: [],
|
},
|
|
// 当前激活的tab
|
activeTab: "questionDetail",
|
|
// 患者来源选项
|
patientSourceList: [
|
{ value: "1", label: "门诊" },
|
{ value: "2", label: "住院" },
|
{ value: "3", label: "急诊" },
|
{ value: "4", label: "出院" },
|
],
|
|
// 科室列表
|
deptList: [],
|
|
// 病区列表
|
wardList: [],
|
|
// 图表实例
|
barChart: null,
|
|
// 加载状态
|
loading: false,
|
detailLoading: false,
|
typeDetailLoading: false,
|
|
// 题目明细数据
|
questionDetailData: [],
|
|
// 题目明细查询参数
|
detailQueryParams: {
|
pageNum: 1,
|
pageSize: 10,
|
},
|
|
// 题目明细总数
|
detailTotal: 0,
|
|
// 综合得分
|
totalScore: 0,
|
totalAnswerCount: 0,
|
totalAnswerRate: 0,
|
|
// 各类型统计明细数据
|
typeDetailData: [],
|
|
// 统计信息
|
totalSendCount: 12560,
|
totalReceiveCount: 10240,
|
overallRecoveryRate: 0,
|
|
// 类型统计汇总
|
averageRecoveryRate: 0,
|
averageTypeScore: 0,
|
highSatisfactionCount: 0,
|
|
// 日期选择器选项
|
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();
|
},
|
},
|
|
// 满意度类型数据
|
satisfactionTypes: [
|
{ id: 401, name: "出院满意度", color: "#36B37E" },
|
{ id: 402, name: "住院满意度", color: "#4CAF50" },
|
{ id: 403, name: "门诊满意度", color: "#409EFF" },
|
{ id: 404, name: "常用满意度", color: "#FF9D4D" },
|
],
|
};
|
},
|
|
mounted() {
|
this.initData();
|
},
|
|
beforeDestroy() {
|
if (this.barChart) {
|
this.barChart.dispose();
|
this.barChart = null;
|
}
|
},
|
|
methods: {
|
// 初始化数据
|
async initData() {
|
await this.getDeptList();
|
await this.getWardList();
|
this.initChart();
|
await this.loadData();
|
},
|
|
// 获取科室列表
|
getDeptList() {
|
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: "儿科" },
|
];
|
resolve();
|
}, 100);
|
});
|
},
|
|
// 获取病区列表
|
getWardList() {
|
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: "儿科病区" },
|
];
|
resolve();
|
}, 100);
|
});
|
},
|
|
// 加载数据
|
async loadData() {
|
await Promise.all([
|
this.loadChartData(),
|
this.loadQuestionDetailData(),
|
this.loadTypeDetailData(),
|
]);
|
},
|
|
// 加载图表数据
|
async loadChartData() {
|
this.loading = true;
|
try {
|
// 模拟API调用
|
const chartData = await this.generateChartData();
|
this.renderChart(chartData);
|
|
// 计算总体回收率
|
this.overallRecoveryRate = this.totalReceiveCount / this.totalSendCount;
|
} finally {
|
this.loading = false;
|
}
|
},
|
|
// 加载题目明细数据
|
async loadQuestionDetailData() {
|
this.detailLoading = true;
|
try {
|
const mockData = await this.generateMockQuestionDetail();
|
this.questionDetailData = mockData.list;
|
this.detailTotal = mockData.total;
|
this.calculateSummary(mockData);
|
} finally {
|
this.detailLoading = false;
|
}
|
},
|
|
// 加载类型明细数据
|
async loadTypeDetailData() {
|
this.typeDetailLoading = true;
|
try {
|
const mockData = await this.generateMockTypeDetail();
|
this.typeDetailData = mockData;
|
|
// 计算类型统计汇总
|
this.calculateTypeSummary(mockData);
|
} finally {
|
this.typeDetailLoading = false;
|
}
|
},
|
|
// 计算综合得分
|
calculateSummary(data) {
|
let totalScore = 0;
|
let totalAnswerCount = 0;
|
let totalCount = 0;
|
|
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.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 chartDom = document.getElementById("satisfactionBarChart");
|
if (!chartDom) return;
|
|
this.barChart = echarts.init(chartDom);
|
window.addEventListener("resize", this.handleChartResize);
|
},
|
|
// 生成图表数据
|
generateChartData() {
|
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,
|
}));
|
|
// 计算回收数量
|
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);
|
});
|
},
|
|
// 渲染图表
|
renderChart(chartData) {
|
if (!this.barChart) return;
|
|
const option = {
|
title: {
|
text: "",
|
left: "center",
|
},
|
tooltip: {
|
trigger: "axis",
|
axisPointer: {
|
type: "shadow",
|
},
|
formatter: (params) => {
|
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: 60,
|
containLabel: true,
|
},
|
xAxis: {
|
type: "category",
|
data: chartData.data.map((item) => item.name),
|
axisLabel: {
|
interval: 0,
|
rotate: 0,
|
fontSize: 12,
|
color: "#666",
|
},
|
axisLine: {
|
lineStyle: {
|
color: "#DCDFE6",
|
},
|
},
|
axisTick: {
|
alignWithLabel: true,
|
},
|
},
|
yAxis: {
|
type: "value",
|
name: "填报比例 (%)",
|
min: 0,
|
max: 100,
|
axisLabel: {
|
formatter: "{value}%",
|
color: "#666",
|
},
|
axisLine: {
|
lineStyle: {
|
color: "#DCDFE6",
|
},
|
},
|
splitLine: {
|
lineStyle: {
|
type: "dashed",
|
color: "#E4E7ED",
|
},
|
},
|
},
|
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() {
|
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);
|
|
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);
|
});
|
},
|
|
// 处理图表响应式
|
handleChartResize() {
|
if (this.barChart) {
|
this.barChart.resize();
|
}
|
},
|
|
// 处理查询
|
handleSearch() {
|
this.detailQueryParams.pageNum = 1;
|
this.loadData();
|
},
|
|
// 处理重置
|
handleReset() {
|
this.$refs.queryForm.resetFields();
|
this.queryParams.dateRange = [];
|
this.detailQueryParams.pageNum = 1;
|
this.loadData();
|
},
|
|
// 处理Tab切换
|
handleTabClick(tab) {
|
if (tab.name === "typeDetail" && this.typeDetailData.length === 0) {
|
this.loadTypeDetailData();
|
}
|
},
|
|
// 处理明细分页大小变化
|
handleDetailSizeChange(size) {
|
this.detailQueryParams.pageSize = size;
|
this.detailQueryParams.pageNum = 1;
|
this.loadQuestionDetailData();
|
},
|
|
// 处理明细页码变化
|
handleDetailPageChange(page) {
|
this.detailQueryParams.pageNum = page;
|
this.loadQuestionDetailData();
|
},
|
|
// 处理类型详情
|
handleTypeDetail(row) {
|
this.$message.info(`查看类型详情:${row.typeName}`);
|
// 这里可以跳转到详情页面或打开详情对话框
|
},
|
|
// 处理导出数据
|
handleExportData(row) {
|
this.$message.success(`正在导出 ${row.typeName} 数据...`);
|
// 这里可以实现导出逻辑
|
},
|
|
// 格式化百分比
|
formatPercent(value) {
|
if (value === null || value === undefined) return "-";
|
const num = parseFloat(value);
|
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 {
|
margin-bottom: 20px;
|
|
.query-form {
|
display: flex;
|
flex-wrap: wrap;
|
align-items: center;
|
|
::v-deep .el-form-item {
|
margin-bottom: 0;
|
margin-right: 20px;
|
|
&:last-child {
|
margin-right: 0;
|
}
|
}
|
}
|
}
|
|
.chart-section {
|
margin-bottom: 20px;
|
|
.chart-container {
|
.chart-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
padding-bottom: 15px;
|
border-bottom: 1px solid #f0f0f0;
|
|
.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;
|
}
|
}
|
}
|
}
|
}
|
}
|
|
.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: 15px;
|
background: #f8f9fa;
|
border-radius: 4px;
|
margin: 10px 0;
|
}
|
|
::v-deep .el-table {
|
th {
|
background-color: #f8f9fa;
|
font-weight: 600;
|
color: #333;
|
padding: 12px 0;
|
}
|
|
td {
|
padding: 12px 0;
|
}
|
|
.question-row {
|
td {
|
background-color: #fff;
|
}
|
|
&:hover {
|
td {
|
background-color: #f5f7fa;
|
}
|
}
|
}
|
}
|
|
.score-text {
|
font-weight: 600;
|
color: #1890ff;
|
font-size: 16px;
|
}
|
|
.summary-row {
|
margin-top: 20px;
|
padding: 20px;
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
border-radius: 8px;
|
border: 1px solid #dee2e6;
|
|
.summary-content {
|
display: flex;
|
justify-content: space-around;
|
align-items: center;
|
|
.summary-item {
|
text-align: center;
|
|
.label {
|
font-size: 16px;
|
color: #606266;
|
margin-right: 8px;
|
}
|
|
.value {
|
font-size: 24px;
|
font-weight: 600;
|
color: #409eff;
|
}
|
}
|
}
|
}
|
|
.pagination-section {
|
display: flex;
|
justify-content: center;
|
padding: 20px 0 0 0;
|
}
|
}
|
|
.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;
|
}
|
}
|
}
|
}
|
}
|
|
// 内层表格样式
|
.inner-table {
|
::v-deep .el-table__header-wrapper {
|
th {
|
background-color: #f0f7ff !important;
|
color: #333;
|
font-weight: 600;
|
}
|
}
|
|
::v-deep .el-table__body-wrapper {
|
tr {
|
background-color: #fff;
|
|
&:hover {
|
background-color: #f5f7fa;
|
}
|
}
|
}
|
}
|
}
|
|
@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>
|