- 将多个按钮组件从 type="text" 改为 link 属性,提升界面美观性 - 修复 PatientList 组件中姓名显示的文本截断功能 - 在住院记录模板中添加对 patientInfo 变化的监听,自动更新表单数据 - 优化打印机列表获取逻辑,添加连接状态检查和警告信息 - 移除不必要的防抖和重复请求防护逻辑,简化代码实现 - 修复多处组件中对 patientInfo 属性访问的安全性问题 - 优化病历数据加载时机,移除防抖包装直接调用加载函数 - 改进数据设置逻辑,避免覆盖未传入字段的原有值 - 调整组件属性定义,使 patientInfo 参数变为可选并设置默认值 - 优化患者切换时的组件重置和数据加载流程
920 lines
27 KiB
Vue
920 lines
27 KiB
Vue
<template>
|
||
<div style="height: calc(100vh - 126px); display: flex; flex-direction: column">
|
||
<!-- 操作工具栏 -->
|
||
<div
|
||
style="
|
||
height: 51px;
|
||
border-bottom: 2px solid #e4e7ed;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 15px;
|
||
flex-shrink: 0;
|
||
"
|
||
>
|
||
<div style="display: flex; align-items: center">
|
||
<!-- 日期选择tabs -->
|
||
<el-tabs
|
||
v-model="dateRange"
|
||
type="card"
|
||
class="date-tabs"
|
||
@tab-click="handleDateTabClick"
|
||
style="margin-right: 20px"
|
||
>
|
||
<el-tab-pane label="今日" name="today"></el-tab-pane>
|
||
<el-tab-pane label="昨日" name="yesterday"></el-tab-pane>
|
||
<el-tab-pane label="自定义" name="custom"></el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<!-- 日期选择器 -->
|
||
<el-date-picker
|
||
v-model="dateRangeValue"
|
||
type="daterange"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
@change="handleDatePickerChange"
|
||
:disabled="dateRange !== 'custom'"
|
||
style="width: 240px; margin-right: 20px"
|
||
/>
|
||
|
||
<!-- 查询按钮 -->
|
||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center">
|
||
<!-- 新增划价 -->
|
||
<el-button type="primary" @click="handAddBilling">新增划价</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 费用列表区域 - Collapse折叠面板+按encounterId分组版 -->
|
||
<div
|
||
style="flex: 1; display: flex; flex-direction: column; overflow: hidden"
|
||
v-loading="loading"
|
||
element-loading-text="数据加载中..."
|
||
element-loading-spinner="el-icon-loading"
|
||
>
|
||
<!-- 列表统计信息 -->
|
||
<div
|
||
style="
|
||
padding: 10px 15px;
|
||
background-color: #f8f9fa;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
flex-shrink: 0;
|
||
"
|
||
>
|
||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||
<div>
|
||
<span class="text-primary font-bold">患者医嘱列表</span>
|
||
<span style="margin-left: 15px; color: #666">
|
||
共 <span class="text-danger">{{ groupedPrescriptionList.length }}</span> 位患者
|
||
</span>
|
||
</div>
|
||
<!-- <div>
|
||
<el-button size="small" link @click="exportData">
|
||
<el-icon><Download /></el-icon> 导出数据
|
||
</el-button>
|
||
</div> -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 列表内容容器 -->
|
||
<div style="flex: 1; overflow-y: auto; padding: 10px; background-color: #f5f7fa">
|
||
<!-- 空状态 -->
|
||
<div v-if="groupedPrescriptionList.length === 0 && !loading" class="empty-container">
|
||
<el-empty description="暂无患者划价数据,请选择查询条件后点击查询" :image-size="120">
|
||
<el-button type="primary" @click="handleQuery">立即查询</el-button>
|
||
</el-empty>
|
||
</div>
|
||
|
||
<!-- 患者医嘱折叠面板 -->
|
||
<div v-else class="prescription-collapse-container">
|
||
<el-collapse
|
||
v-model="activeCollapseNames"
|
||
accordion
|
||
border
|
||
style="--el-collapse-border-color: #e4e7ed"
|
||
>
|
||
<!-- 按encounterId分组渲染患者折叠项 -->
|
||
<el-collapse-item
|
||
v-for="(patientGroup, index) in groupedPrescriptionList"
|
||
:key="`patient-${index}-${safeGet(patientGroup[0], 'encounterId', index)}`"
|
||
:name="`patient-${index}`"
|
||
class="patient-collapse-item"
|
||
>
|
||
<!-- 折叠面板头部 - 患者信息 -->
|
||
<template #title>
|
||
<div class="patient-header">
|
||
<div class="patient-basic-info">
|
||
<el-avatar :icon="User" size="small" style="margin-right: 10px"></el-avatar>
|
||
<div>
|
||
<span class="patient-name">{{
|
||
safeGet(patientGroup[0], 'patientName', '未知患者')
|
||
}}</span>
|
||
<span class="patient-info-tag"
|
||
>{{ safeGet(patientGroup[0], 'genderEnum_enumText', '未知') }} /
|
||
{{ safeGet(patientGroup[0], 'age', '未知') }}</span
|
||
>
|
||
<span class="patient-info-tag"
|
||
>{{ safeGet(patientGroup[0], 'bedName', '无床位') }}【{{
|
||
safeGet(patientGroup[0], 'busNo', '未知编号')
|
||
}}】</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="patient-ext-info">
|
||
<div class="ext-item">
|
||
<span class="label">住院医生:</span>
|
||
<span class="value">{{
|
||
safeGet(patientGroup[0], 'admittingDoctorName', '未知')
|
||
}}</span>
|
||
</div>
|
||
<div class="ext-item">
|
||
<span class="label">预交金余额:</span>
|
||
<span class="value amount">{{
|
||
formatNumber(safeGet(patientGroup[0], 'balanceAmount', 0), 4)
|
||
}}</span>
|
||
</div>
|
||
<div class="ext-item">
|
||
<span class="label">诊断:</span>
|
||
<span class="value" :title="safeGet(patientGroup[0], 'conditionNames', '无')">
|
||
{{ truncateText(safeGet(patientGroup[0], 'conditionNames', '无'), 20) }}
|
||
</span>
|
||
</div>
|
||
<div class="ext-item">
|
||
<el-tag size="small">{{
|
||
safeGet(patientGroup[0], 'contractName', '未知')
|
||
}}</el-tag>
|
||
</div>
|
||
<div class="patient-amount-preview">
|
||
小计:<span class="amount">{{ calculatePatientTotal(patientGroup) }}</span> 元
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 折叠面板内容 - 医嘱表格 -->
|
||
<div class="prescription-table-container">
|
||
<el-table
|
||
:data="safeArray(patientGroup)"
|
||
border
|
||
size="small"
|
||
:ref="(el) => (tableRef[index] = el)"
|
||
@selection-change="(val) => handleTableSelectionChange(index, val)"
|
||
:header-cell-style="{ background: '#eef9fd', color: '#333' }"
|
||
:row-class-name="
|
||
({ row }) => (safeGet(row, 'status') === 'priced' ? 'priced-row' : '')
|
||
"
|
||
>
|
||
<el-table-column type="selection" width="50" align="center" />
|
||
<el-table-column label="医嘱类型" prop="therapyEnum_enumText" width="90">
|
||
<template #default="scope">
|
||
<el-tag
|
||
size="small"
|
||
:type="safeGet(scope.row, 'therapyEnum') === 1 ? 'warning' : 'primary'"
|
||
>
|
||
{{ safeGet(scope.row, 'therapyEnum_enumText', '未知') }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="划价内容" min-width="200">
|
||
<template #default="scope">
|
||
<div class="advice-content">
|
||
<span>{{ safeGet(scope.row, 'adviceName', '无') }}</span>
|
||
<template v-if="safeGet(scope.row, 'adviceTable') === 'wor_device_request'">
|
||
<span class="advice-detail">
|
||
规格:{{ safeGet(scope.row, 'volume', '无') }} / 数量:{{
|
||
safeGet(scope.row, 'quantity', 0)
|
||
}}{{ safeGet(scope.row, 'unitCode_dictText', '') }}
|
||
</span>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="数量" prop="quantity" width="60">
|
||
<template #default="scope">
|
||
{{ formatNumber(safeGet(scope.row, 'quantity', 0), 0) }}
|
||
{{ safeGet(scope.row, 'unitCode_dictText', '') }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="申请时间" prop="requestTime" width="160" />
|
||
<el-table-column label="执行/发放" prop="positionName" width="120" />
|
||
<el-table-column label="单价(元)" width="90">
|
||
<template #default="scope">
|
||
<!-- 实际项目单价可能需要从收费项目配置中获取,这里先显示0.0000 -->
|
||
{{ formatNumber(safeGet(scope.row, 'unitPrice', 0), 4) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80">
|
||
<template #default="scope">
|
||
<el-button
|
||
size="small"
|
||
type="text"
|
||
@click="handleSingleDelete(scope.row)"
|
||
:disabled="safeGet(scope.row, 'status') === 'priced'"
|
||
>
|
||
撤销
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<!-- 患者级操作栏 -->
|
||
<div class="patient-actions">
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
plain
|
||
@click="selectAllPatientItems(index)"
|
||
>
|
||
全选
|
||
</el-button>
|
||
<el-button size="small" type="primary" @click="handleBatchDelete(index)">
|
||
批量撤销
|
||
</el-button>
|
||
<el-button size="small" type="primary" @click="handleCalculate(patientGroup)">
|
||
新增划价
|
||
</el-button>
|
||
<span class="patient-amount">
|
||
小计:<span class="amount">{{ calculatePatientTotal(patientGroup) }}</span>
|
||
元
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 计费弹窗 -->
|
||
<FeeDialog
|
||
v-model:visible="dialogVisible"
|
||
:initial-data="selectedFeeItems"
|
||
:patient-info="currentPatientInfo"
|
||
@confirm="handleFeeDialogConfirm"
|
||
@cancel="handleFeeDialogCancel"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {computed, nextTick, onMounted, ref, watch} from 'vue';
|
||
import {ElMessage, ElMessageBox} from 'element-plus';
|
||
// Element Plus 图标导入
|
||
import {User} from '@element-plus/icons-vue';
|
||
import {patientInfoList} from '../../components/store/patient.js';
|
||
import {formatDateStr} from '@/utils/index';
|
||
import FeeDialog from './FeeDialog.vue';
|
||
import {addBilling, deleteBilling, queryBilling} from './api.js';
|
||
import useUserStore from '@/store/modules/user';
|
||
|
||
// ========== 核心工具函数 ==========
|
||
/**
|
||
* 安全获取对象属性,避免空值报错
|
||
* @param {Object} obj - 源对象
|
||
* @param {String} path - 属性路径
|
||
* @param {Any} defaultValue - 默认值
|
||
* @returns {Any}
|
||
*/
|
||
const safeGet = (obj, path, defaultValue = '') => {
|
||
// 1. 前置校验:如果源对象不是对象类型,直接返回默认值
|
||
if (!obj || typeof obj !== 'object') return defaultValue;
|
||
|
||
// 2. 拆分路径:把 "info.basic.name" 拆成 ["info", "basic", "name"]
|
||
const paths = path.split('.');
|
||
|
||
// 3. 初始化结果为源对象
|
||
let result = obj;
|
||
|
||
// 4. 循环遍历路径数组,逐层访问属性
|
||
for (const p of paths) {
|
||
// 5. 关键:如果当前层属性不存在(undefined/null),直接返回默认值
|
||
if (result[p] === undefined || result[p] === null) return defaultValue;
|
||
// 6. 存在则继续访问下一层
|
||
result = result[p];
|
||
}
|
||
|
||
// 7. 所有层级都存在,返回最终属性值
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* 安全转换为数组
|
||
* @param {Any} data - 待转换数据
|
||
* @returns {Array}
|
||
*/
|
||
const safeArray = (data) => {
|
||
return Array.isArray(data) ? data : [];
|
||
};
|
||
|
||
/**
|
||
* 格式化数字(保留4位小数,金额专用)
|
||
* @param {Number} num - 数字
|
||
* @param {Number} decimal - 小数位数(默认4位)
|
||
* @returns {String}
|
||
*/
|
||
const formatNumber = (num, decimal = 4) => {
|
||
if (isNaN(Number(num))) return '0.0000';
|
||
// 保留指定小数位,不足补0
|
||
return Number(num).toFixed(decimal);
|
||
};
|
||
|
||
/**
|
||
* 文本截断(超出长度显示省略号)
|
||
* @param {String} text - 待处理文本
|
||
* @param {Number} length - 最大长度
|
||
* @returns {String}
|
||
*/
|
||
const truncateText = (text, length = 20) => {
|
||
if (!text || text.length <= length) return text;
|
||
return `${text.slice(0, length)}...`;
|
||
};
|
||
|
||
/**
|
||
* 按encounterId分组数据
|
||
* @param {Array} data - 原始数据
|
||
* @returns {Array} 分组后的数据(二维数组)
|
||
*/
|
||
const groupByEncounterId = (data) => {
|
||
const grouped = {};
|
||
safeArray(data).forEach((item) => {
|
||
const encounterId = safeGet(item, 'encounterId', 'unknown');
|
||
if (!grouped[encounterId]) {
|
||
grouped[encounterId] = [];
|
||
}
|
||
// 为每个项添加默认状态(未划价)和单价(默认0,实际应从收费项目中获取)
|
||
grouped[encounterId].push({
|
||
...item,
|
||
status: safeGet(item, 'status', 'unpriced'), // 默认未划价
|
||
unitPrice: safeGet(item, 'unitPrice', 0), // 单价默认0,实际需要对接收费项目
|
||
});
|
||
});
|
||
// 转换为数组格式
|
||
return Object.values(grouped);
|
||
};
|
||
|
||
// ========== 响应式数据 ==========
|
||
const loading = ref(false);
|
||
const dateRange = ref('today');
|
||
const dateRangeValue = ref([]);
|
||
const tableRef = ref([]);
|
||
const rawPrescriptionList = ref([]); // 原始未分组数据
|
||
const groupedPrescriptionList = ref([]); // 按encounterId分组后的数据
|
||
const activeCollapseNames = ref([]); // Collapse激活状态
|
||
const selectedRows = ref({}); // 选中的行数据
|
||
const totalItemsCount = ref(0); // 总医嘱项数
|
||
const totalAmount = ref(0); // 总金额(保留4位小数)
|
||
const dialogVisible = ref(false);
|
||
const selectedFeeItems = ref([]);
|
||
const currentPatientInfo = ref(null);
|
||
const queryParams = ref({
|
||
pageSize: 10000,
|
||
pageNo: 1,
|
||
});
|
||
|
||
// 用户信息
|
||
const userStore = useUserStore();
|
||
const userId = ref(safeGet(userStore, 'id', ''));
|
||
const orgId = ref(safeGet(userStore, 'orgId', ''));
|
||
|
||
// ========== 计算属性 ==========
|
||
// 计算总统计信息(总项数、总金额)
|
||
const calculateTotalStats = computed(() => {
|
||
let itemsCount = 0;
|
||
let amount = 0;
|
||
|
||
safeArray(groupedPrescriptionList.value).forEach((patientGroup) => {
|
||
safeArray(patientGroup).forEach((item) => {
|
||
itemsCount++;
|
||
// 累加单价,保留4位小数精度
|
||
amount = Math.round((amount + Number(safeGet(item, 'unitPrice', 0))) * 10000) / 10000;
|
||
});
|
||
});
|
||
|
||
totalItemsCount.value = itemsCount;
|
||
totalAmount.value = amount;
|
||
});
|
||
|
||
// ========== 方法 ==========
|
||
/**
|
||
* 计算单个患者的总金额(保留4位小数)
|
||
* @param {Array} patientGroup - 患者医嘱列表
|
||
* @returns {String} 格式化后的金额字符串
|
||
*/
|
||
const calculatePatientTotal = (patientGroup) => {
|
||
const total = safeArray(patientGroup).reduce((sum, item) => {
|
||
return (
|
||
Math.round(
|
||
(sum + Number(safeGet(item, 'unitPrice', 0) * safeGet(item, 'quantity', 0))) * 10000
|
||
) / 10000
|
||
);
|
||
}, 0);
|
||
return formatNumber(total, 4);
|
||
};
|
||
|
||
/**
|
||
* 全选单个患者的所有项
|
||
* @param {Number} index - 患者索引
|
||
*/
|
||
const selectAllPatientItems = (index) => {
|
||
nextTick(() => {
|
||
const table = safeArray(tableRef.value)[index];
|
||
if (table && typeof table.toggleAllSelection === 'function') {
|
||
table.toggleAllSelection();
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 处理表格选择变化
|
||
* @param {Number} index - 患者索引
|
||
* @param {Array} val - 选中行
|
||
*/
|
||
const handleTableSelectionChange = (index, val) => {
|
||
selectedRows.value[index] = safeArray(val);
|
||
console.log('selectedRows:', selectedRows.value);
|
||
// 合并所有选中行
|
||
const allSelected = [];
|
||
Object.values(selectedRows.value).forEach((rows) => {
|
||
allSelected.push(...safeArray(rows));
|
||
});
|
||
selectedFeeItems.value = allSelected;
|
||
console.log('selectedFeeItems:', selectedFeeItems.value);
|
||
};
|
||
|
||
/**
|
||
* 日期Tab切换
|
||
* @param {Object} tab - 标签页
|
||
*/
|
||
const handleDateTabClick = (tab) => {
|
||
const today = new Date();
|
||
const yesterday = new Date(today);
|
||
yesterday.setDate(today.getDate() - 1);
|
||
const format = (date) => formatDateStr(date, 'YYYY-MM-DD');
|
||
|
||
switch (safeGet(tab, 'paneName')) {
|
||
case 'today':
|
||
dateRangeValue.value = [format(today), format(today)];
|
||
break;
|
||
case 'yesterday':
|
||
dateRangeValue.value = [format(yesterday), format(yesterday)];
|
||
break;
|
||
case 'custom':
|
||
if (!dateRangeValue.value.length) {
|
||
dateRangeValue.value = [format(today), format(today)];
|
||
}
|
||
break;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 日期选择器变化
|
||
* @param {Array} val - 选中日期
|
||
*/
|
||
const handleDatePickerChange = (val) => {
|
||
const dateVal = safeArray(val);
|
||
if (dateVal.length === 2) {
|
||
dateRange.value = 'custom';
|
||
const start = new Date(dateVal[0]);
|
||
const end = new Date(dateVal[1]);
|
||
if (start > end) {
|
||
ElMessage.warning('开始日期不能晚于结束日期');
|
||
dateRangeValue.value = [dateVal[1], dateVal[0]];
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 查询数据并按encounterId分组
|
||
*/
|
||
const handleQuery = async () => {
|
||
// 基础校验
|
||
const patientList = safeArray(patientInfoList.value);
|
||
if (patientList.length === 0) {
|
||
ElMessage.warning('请先选择患者');
|
||
return;
|
||
}
|
||
if (safeArray(dateRangeValue.value).length < 2) {
|
||
ElMessage.warning('请选择有效的查询日期');
|
||
return;
|
||
}
|
||
|
||
loading.value = true;
|
||
try {
|
||
queryParams.value = {
|
||
...queryParams.value,
|
||
encounterIds: patientList
|
||
.map((p) => safeGet(p, 'encounterId'))
|
||
.filter((id) => id)
|
||
.join(','),
|
||
startTime: `${safeArray(dateRangeValue.value)[0]} 00:00:00`,
|
||
endTime: `${safeArray(dateRangeValue.value)[1]} 23:59:59`,
|
||
};
|
||
|
||
const response = await queryBilling(queryParams.value);
|
||
const rawData = safeArray(safeGet(response, 'data.records', []));
|
||
rawPrescriptionList.value = rawData;
|
||
|
||
// 核心:按encounterId分组数据
|
||
groupedPrescriptionList.value = groupByEncounterId(rawData);
|
||
|
||
// 默认展开第一个患者面板
|
||
if (groupedPrescriptionList.value.length > 0 && activeCollapseNames.value.length === 0) {
|
||
activeCollapseNames.value = ['patient-0'];
|
||
}
|
||
|
||
// 重置选中状态
|
||
selectedRows.value = {};
|
||
selectedFeeItems.value = [];
|
||
|
||
ElMessage.success(`查询成功,共找到 ${groupedPrescriptionList.value.length} 位患者的划价数据`);
|
||
} catch (error) {
|
||
console.error('查询失败:', error);
|
||
ElMessage.error(`查询失败:${safeGet(error, 'message', '网络异常')}`);
|
||
rawPrescriptionList.value = [];
|
||
groupedPrescriptionList.value = [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 打开新增弹窗
|
||
*/
|
||
const handAddBilling = () => {
|
||
const patientList = safeArray(patientInfoList.value);
|
||
if (patientList.length === 0) {
|
||
ElMessage.warning('请先选择患者');
|
||
return;
|
||
}
|
||
if (patientList.length > 1) {
|
||
ElMessage.warning('新增划价仅支持单患者操作');
|
||
return;
|
||
}
|
||
|
||
currentPatientInfo.value = patientList[0];
|
||
dialogVisible.value = true;
|
||
};
|
||
//新增划价
|
||
const handleCalculate = (patientGroup) => {
|
||
const patientList = safeArray(patientInfoList.value);
|
||
if (patientList.length === 0) {
|
||
ElMessage.warning('请先选择患者');
|
||
return;
|
||
}
|
||
if (patientGroup.length === 0) {
|
||
ElMessage.warning('该患者暂无医嘱信息,无法新增划价');
|
||
return;
|
||
}
|
||
|
||
const currentPatient = patientList.find(
|
||
(p) => safeGet(p, 'encounterId') === safeGet(patientGroup[0], 'encounterId')
|
||
);
|
||
if (!currentPatient) {
|
||
ElMessage.warning('无法获取当前患者信息');
|
||
return;
|
||
}
|
||
currentPatientInfo.value = currentPatient;
|
||
dialogVisible.value = true;
|
||
};
|
||
/**
|
||
* 弹窗确认
|
||
* @param {Object} data - 弹窗数据
|
||
*/
|
||
const handleFeeDialogConfirm = async (data) => {
|
||
try {
|
||
if (!data || typeof data !== 'object') {
|
||
ElMessage.warning('请填写计费信息');
|
||
return;
|
||
}
|
||
|
||
loading.value = true;
|
||
await addBilling({
|
||
...data,
|
||
operatorId: userId.value,
|
||
orgId: orgId.value,
|
||
});
|
||
ElMessage.success('计费新增成功');
|
||
dialogVisible.value = false;
|
||
await handleQuery();
|
||
} catch (error) {
|
||
ElMessage.error(`计费失败:${safeGet(error, 'message', '提交异常')}`);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 弹窗取消
|
||
*/
|
||
const handleFeeDialogCancel = () => {
|
||
dialogVisible.value = false;
|
||
selectedFeeItems.value = [];
|
||
currentPatientInfo.value = null;
|
||
};
|
||
|
||
/**
|
||
* 单个患者批量撤销
|
||
* @param {Number} index - 患者索引
|
||
*/
|
||
const handleBatchDelete = (index) => {
|
||
const rows = safeArray(selectedRows.value[index]);
|
||
if (rows.length === 0) {
|
||
ElMessage.warning('请先选择要撤销的划价项目');
|
||
return;
|
||
}
|
||
|
||
// 第一步:显示确认弹窗
|
||
ElMessageBox.confirm(`确认对选中的 ${rows.length} 项划价内容进行批量撤销?`, '批量撤销', {
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
// 用户确认后执行撤销逻辑
|
||
loading.value = true;
|
||
|
||
// 构造请求参数
|
||
const param = rows.map((row) => ({
|
||
requestId: safeGet(row, 'requestId'),
|
||
adviceType:
|
||
safeGet(row, 'adviceTable') === 'wor_device_request'
|
||
? 2
|
||
: safeGet(row, 'adviceTable') === 'wor_service_request'
|
||
? 3
|
||
: 1,
|
||
}));
|
||
|
||
// 调用批量撤销接口(Promise 链式调用)
|
||
return deleteBilling(param);
|
||
})
|
||
.then(() => {
|
||
// 接口调用成功
|
||
ElMessage.success('批量撤销成功');
|
||
handleQuery(); // 重新查询刷新数据
|
||
})
|
||
.catch((error) => {
|
||
// 异常处理(取消操作/接口失败)
|
||
if (error !== 'cancel') {
|
||
// 排除用户点击取消的情况
|
||
ElMessage.error(`批量撤销失败:${safeGet(error, 'message', '操作异常')}`);
|
||
}
|
||
})
|
||
.finally(() => {
|
||
// 无论成功/失败,最终关闭加载状态
|
||
loading.value = false;
|
||
});
|
||
};
|
||
const handleSingleDelete = (row) => {
|
||
if (!row || row.length === 0) {
|
||
ElMessage.warning('请先选择要撤销的划价项目');
|
||
return;
|
||
}
|
||
// 第一步:显示确认弹窗
|
||
ElMessageBox.confirm(`确认对选中的 ${row.adviceName} 项划价内容进行批量撤销?`, '批量撤销', {
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
// 用户确认后执行撤销逻辑
|
||
loading.value = true;
|
||
|
||
// 构造请求参数
|
||
const rowArr = safeArray([row]);
|
||
const param = rowArr.map((row) => ({
|
||
requestId: safeGet(row, 'requestId'),
|
||
adviceType:
|
||
safeGet(row, 'adviceTable') === 'wor_device_request'
|
||
? 2
|
||
: safeGet(row, 'adviceTable') === 'wor_service_request'
|
||
? 3
|
||
: 1,
|
||
}));
|
||
|
||
// 调用批量撤销接口(Promise 链式调用)
|
||
return deleteBilling(param);
|
||
})
|
||
.then(() => {
|
||
// 接口调用成功
|
||
ElMessage.success('批量撤销成功');
|
||
handleQuery(); // 重新查询刷新数据
|
||
})
|
||
.catch((error) => {
|
||
// 异常处理(取消操作/接口失败)
|
||
if (error !== 'cancel') {
|
||
// 排除用户点击取消的情况
|
||
ElMessage.error(`批量撤销失败:${safeGet(error, 'message', '操作异常')}`);
|
||
}
|
||
})
|
||
.finally(() => {
|
||
// 无论成功/失败,最终关闭加载状态
|
||
loading.value = false;
|
||
});
|
||
};
|
||
// ========== 初始化 ==========
|
||
onMounted(() => {
|
||
// 设置默认日期
|
||
const today = new Date();
|
||
const defaultDate = formatDateStr(today, 'YYYY-MM-DD');
|
||
dateRangeValue.value = [defaultDate, defaultDate];
|
||
|
||
// 监听日期变化自动查询
|
||
watch(
|
||
[dateRange, dateRangeValue],
|
||
([newRange, newVal], [oldRange, oldVal]) => {
|
||
if (oldRange !== undefined && safeArray(newVal).length === 2) {
|
||
handleQuery();
|
||
}
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
// 初始化统计信息
|
||
calculateTotalStats.value;
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 基础样式 */
|
||
.font-bold {
|
||
font-weight: 600;
|
||
}
|
||
.text-primary {
|
||
color: #409eff;
|
||
}
|
||
.text-danger {
|
||
color: #f56c6c;
|
||
}
|
||
.text-success {
|
||
color: #67c23a;
|
||
}
|
||
|
||
/* 空状态容器 */
|
||
.empty-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 100%;
|
||
}
|
||
|
||
/* 折叠面板容器 */
|
||
.prescription-collapse-container {
|
||
--el-collapse-item-border-radius: 8px;
|
||
}
|
||
|
||
/* 患者折叠项 */
|
||
.patient-collapse-item {
|
||
margin-bottom: 12px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
/* 患者头部 */
|
||
.patient-header {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 4px 0;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.patient-basic-info {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.patient-name {
|
||
font-weight: 600;
|
||
font-size: 15px;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.patient-info-tag {
|
||
color: #666;
|
||
font-size: 13px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.patient-ext-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.ext-item {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 13px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.ext-item .label {
|
||
color: #999;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.ext-item .amount {
|
||
color: #e6a23c;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.patient-amount-preview {
|
||
font-size: 13px;
|
||
color: #333;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.patient-amount-preview .amount {
|
||
color: #e6a23c;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 医嘱表格容器 */
|
||
.prescription-table-container {
|
||
padding: 10px 0;
|
||
}
|
||
|
||
:deep(.el-table) {
|
||
--el-table-header-text-color: #333;
|
||
--el-table-row-hover-bg-color: #f8f9fa;
|
||
--el-table-border-color: #e4e7ed;
|
||
}
|
||
|
||
.priced-row {
|
||
background-color: #f0f9ff !important;
|
||
}
|
||
|
||
.advice-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.advice-detail {
|
||
font-size: 12px;
|
||
color: #447c95;
|
||
margin-top: 2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 患者操作栏 */
|
||
.patient-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0 0;
|
||
margin-top: 8px;
|
||
border-top: 1px dashed #e4e7ed;
|
||
}
|
||
|
||
.patient-amount {
|
||
font-size: 14px;
|
||
color: #333;
|
||
}
|
||
|
||
.patient-amount .amount {
|
||
color: #e6a23c;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 日期Tabs样式 */
|
||
.date-tabs :deep(.el-tabs__header) {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.date-tabs :deep(.el-tabs__content) {
|
||
display: none;
|
||
}
|
||
|
||
/* 折叠面板样式优化 */
|
||
:deep(.el-collapse-item__header) {
|
||
padding: 12px 15px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
:deep(.el-collapse-item__content) {
|
||
padding: 0 15px 15px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
:deep(.el-collapse) {
|
||
--el-collapse-header-text-color: #333;
|
||
--el-collapse-content-bg-color: #fff;
|
||
}
|
||
|
||
/* 表格单元格溢出处理 */
|
||
:deep(.el-table__cell) {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
</style>
|