- 将CTE查询重构为子查询以提高执行效率 - 为位置和医生查询添加LIMIT 1约束以减少数据量 - 移除不必要的GROUP BY子句以简化查询逻辑 - 在前端组件中实现异步数据加载和错误处理机制 - 使用可选链操作符处理空值情况避免报错 - 添加防抖机制解决单击双击冲突问题 - 优化患者列表和床位列表的并行加载逻辑 - 清理调试用的console.log语句并替换为有意义的信息
876 lines
26 KiB
Vue
876 lines
26 KiB
Vue
<template>
|
||
<div style="height: calc(100vh - 126px)">
|
||
<!-- 操作工具栏 -->
|
||
<div
|
||
style="
|
||
height: 51px;
|
||
border-bottom: 2px solid #e4e7ed;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 15px;
|
||
"
|
||
>
|
||
<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="other"></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"
|
||
@clear="onClear"
|
||
style="width: 240px; margin-right: 20px"
|
||
/>
|
||
|
||
<!-- 费用类型选择 -->
|
||
<el-select
|
||
v-model="formParams.chargeItemEnum"
|
||
placeholder="请选择费用类型"
|
||
clearable
|
||
style="width: 150px; margin-right: 20px"
|
||
>
|
||
<el-option
|
||
v-for="type in med_chrgitm_type"
|
||
:key="type.value"
|
||
:label="type.label"
|
||
:value="type.value"
|
||
/>
|
||
</el-select>
|
||
|
||
<!-- 执行科室选择 -->
|
||
<el-select
|
||
v-model="formParams.orgId"
|
||
placeholder="请选择执行科室"
|
||
clearable
|
||
style="width: 150px; margin-right: 20px"
|
||
>
|
||
<el-option
|
||
v-for="dept in orgOptions"
|
||
:key="dept.id"
|
||
:label="dept.name"
|
||
:value="dept.id"
|
||
/>
|
||
</el-select>
|
||
|
||
<!-- 查询按钮 -->
|
||
<el-button type="primary" @click="onReset">重置</el-button>
|
||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center">
|
||
<!-- 导出按钮 -->
|
||
<el-button @click="handleExport">导出</el-button>
|
||
|
||
<!-- 打印按钮 -->
|
||
<!-- <el-button @click="handlePrint" style="margin-left: 15px">打印</el-button> -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 费用明细列表区域 -->
|
||
<div
|
||
style="padding: 10px; background-color: #eef9fd; height: 100%; overflow-y: auto"
|
||
v-loading="loading"
|
||
>
|
||
<!-- 费用汇总信息 -->
|
||
<div style="background-color: white; padding: 15px; margin-bottom: 10px; border-radius: 4px">
|
||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||
<div>
|
||
<h3 style="margin: 0; font-weight: normal; color: #303133">费用汇总</h3>
|
||
<p style="margin: 5px 0; color: #606266; font-size: 14px">
|
||
费用周期:{{ formatDateRange() }}
|
||
</p>
|
||
</div>
|
||
<div style="text-align: right">
|
||
<p style="margin: 0; font-size: 18px; font-weight: bold; color: #ff4d4f">
|
||
合计金额:¥{{ formatNumber(totalAmount, 4) }}
|
||
</p>
|
||
<p style="margin: 5px 0; color: #606266; font-size: 14px">
|
||
明细项数:{{ groupedPrescriptionList.length }}项
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 列表内容容器 -->
|
||
<div
|
||
v-if="groupedPrescriptionList.length > 0"
|
||
style="
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 10px;
|
||
background-color: white;
|
||
border-radius: 4px;
|
||
"
|
||
>
|
||
<!-- 患者医嘱折叠面板 -->
|
||
<div class="prescription-collapse-container">
|
||
<el-collapse
|
||
v-model="activeCollapseNames"
|
||
accordion
|
||
border
|
||
style="--el-collapse-border-color: #e4e7ed"
|
||
@change="onCollapasChange"
|
||
>
|
||
<!-- 按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"
|
||
style="width: 100%; margin-top: 10px"
|
||
@sort-change="handleSortChange"
|
||
>
|
||
<el-table-column label="项目名称" prop="chargeName" min-width="200" />
|
||
<el-table-column
|
||
label="费用类型"
|
||
prop="chargeItemEnum_enumText"
|
||
width="120"
|
||
align="center"
|
||
/>
|
||
<el-table-column
|
||
label="单价"
|
||
prop="unitPrice"
|
||
width="100"
|
||
align="center"
|
||
sortable
|
||
>
|
||
<template #default="scope">
|
||
{{ formatNumber(scope.row.unitPrice, 4) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
label="数量"
|
||
prop="quantityValue"
|
||
width="100"
|
||
align="center"
|
||
sortable
|
||
/>
|
||
<el-table-column
|
||
label="金额"
|
||
prop="totalPrice"
|
||
width="100"
|
||
align="center"
|
||
sortable
|
||
>
|
||
<template #default="scope">
|
||
<span style="color: #ff4d4f">{{
|
||
formatNumber(scope.row.totalPrice, 4)
|
||
}}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column :prop="orgId" label="执行科室" width="120" align="center">
|
||
<template #default="scope">
|
||
{{ selectOrg(scope.row.orgId) }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="执行人" prop="practitioner" width="100" align="center" />
|
||
<el-table-column label="执行日期" prop="recordedTime" width="120" align="center">
|
||
<template #default="scope">
|
||
{{ moment(scope.row?.recordedTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column
|
||
label="医保类型"
|
||
prop="chrgitmLv_enumText"
|
||
width="100"
|
||
align="center"
|
||
>
|
||
</el-table-column>
|
||
<el-table-column label="备注" prop="remark" min-width="150" />
|
||
</el-table>
|
||
</div>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 无数据提示 -->
|
||
<el-empty
|
||
v-if="!loading && groupedPrescriptionList.length === 0"
|
||
description="暂无费用明细数据"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 打印预览弹窗 -->
|
||
<el-dialog
|
||
v-model="printDialogVisible"
|
||
title="打印预览"
|
||
width="80%"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div id="print-content">
|
||
<div style="text-align: center; margin-bottom: 20px">
|
||
<h2 style="margin: 0">费用明细清单</h2>
|
||
<p style="margin: 5px 0">患者姓名:{{ patientInfo || '未选择患者' }}</p>
|
||
<p style="margin: 5px 0">费用周期:{{ formatDateRange() }}</p>
|
||
</div>
|
||
|
||
<table style="width: 100%; border-collapse: collapse">
|
||
<thead>
|
||
<tr style="background-color: #eef9fd">
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">项目名称</th>
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">费用类型</th>
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">单价</th>
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">数量</th>
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">金额</th>
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">执行科室</th>
|
||
<th style="border: 1px solid #e4e7ed; padding: 8px">执行日期</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="item in feeDetailList" :key="item.id">
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">{{ item.itemName }}</td>
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">{{ item.feeTypeName }}</td>
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">
|
||
{{ item.unitPrice.toFixed(2) }}
|
||
</td>
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">{{ item.quantity }}</td>
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">{{ item.amount.toFixed(2) }}</td>
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">{{ item.execDept }}</td>
|
||
<td style="border: 1px solid #e4e7ed; padding: 8px">{{ item.executeDate }}</td>
|
||
</tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr style="background-color: #f5f7fa">
|
||
<td
|
||
colspan="4"
|
||
style="
|
||
border: 1px solid #e4e7ed;
|
||
padding: 8px;
|
||
text-align: right;
|
||
font-weight: bold;
|
||
"
|
||
>
|
||
合计:
|
||
</td>
|
||
<td
|
||
colspan="3"
|
||
style="border: 1px solid #e4e7ed; padding: 8px; color: #ff4d4f; font-weight: bold"
|
||
>
|
||
¥{{ totalAmount.toFixed(2) }}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<el-button @click="printDialogVisible = false">关闭</el-button>
|
||
<el-button type="primary" @click="doPrint">打印</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {computed, onMounted, reactive, ref, watch} from 'vue';
|
||
import moment from 'moment';
|
||
import {ElMessage} from 'element-plus';
|
||
import {patientInfoList} from '../../components/store/patient.js';
|
||
import {formatDateStr} from '@/utils/index';
|
||
import {getCostDetail} from './api.js';
|
||
import {getOrgList} from '../../../basicmanage/ward/components/api.js';
|
||
import {User} from '@element-plus/icons-vue';
|
||
|
||
const { proxy } = getCurrentInstance();
|
||
const { med_chrgitm_type } = proxy.useDict('med_chrgitm_type');
|
||
|
||
const groupedPrescriptionList = ref([]); // 按encounterId分组后的数据
|
||
const activeCollapseNames = ref(['patient-0']); // Collapse激活状态
|
||
|
||
// 响应式数据
|
||
const loading = ref(false);
|
||
const feeDetailList = ref([]);
|
||
const dateRange = ref('today'); // today, yesterday, other
|
||
const dateRangeValue = ref([]);
|
||
const execDepartment = ref('');
|
||
const feeTypeOptions = ref([]);
|
||
const patientInfo = ref('');
|
||
const orgOptions = ref([]);
|
||
const selectIndex = ref(0);
|
||
const formParams = reactive({
|
||
chargeItemEnum: undefined,
|
||
orgId: undefined,
|
||
recordedTimeSTime: undefined,
|
||
recordedTimeETime: undefined,
|
||
pageSize: 10,
|
||
encounterIds: '',
|
||
pageNo: 1,
|
||
});
|
||
|
||
const props = defineProps({
|
||
activeTab: {
|
||
type: String,
|
||
},
|
||
});
|
||
|
||
// 分页相关
|
||
const total = ref(0);
|
||
|
||
// 打印相关
|
||
const printDialogVisible = ref(false);
|
||
|
||
// 计算总金额
|
||
const totalAmount = computed(() => {
|
||
console.log('feeDetailList========>', feeDetailList.value);
|
||
|
||
return feeDetailList?.value?.reduce((sum, item) => {
|
||
return sum + (item.totalPrice || 0);
|
||
}, 0);
|
||
});
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
// 设置默认日期
|
||
const today = new Date();
|
||
dateRangeValue.value = [formatDateStr(today, 'YYYY-MM-DD'), formatDateStr(today, 'YYYY-MM-DD')];
|
||
});
|
||
|
||
watch(
|
||
() => patientInfoList,
|
||
(newValue) => {
|
||
if (newValue.value.length > 0) {
|
||
if (!(dateRangeValue.value == null || dateRangeValue.value == undefined)) {
|
||
formParams.recordedTimeSTime = dateRangeValue.value[0] + ' ' + '00:00:00';
|
||
formParams.recordedTimeETime = dateRangeValue.value[1] + ' ' + '23:59:59';
|
||
}
|
||
const encounterIds = newValue.value
|
||
.map((item) => {
|
||
return item.encounterId;
|
||
})
|
||
.join(',');
|
||
formParams.encounterIds = encounterIds;
|
||
if (props.activeTab === 'expenseDetail') {
|
||
getTableList();
|
||
}
|
||
}
|
||
if (newValue.value.length <= 0) {
|
||
feeDetailList.value = [];
|
||
groupedPrescriptionList.value = [];
|
||
}
|
||
},
|
||
{ deep: true }
|
||
);
|
||
// 获取列表信息
|
||
const getTableList = async () => {
|
||
const params = formParams;
|
||
try {
|
||
const res = await getCostDetail(params);
|
||
feeDetailList.value = res.data;
|
||
total.value = res.data?.total;
|
||
// 核心:按encounterId分组数据
|
||
groupedPrescriptionList.value = groupByEncounterId(res.data);
|
||
// 默认展开第一个患者面板
|
||
if (groupedPrescriptionList.value.length > 0 && activeCollapseNames.value.length === 0) {
|
||
activeCollapseNames.value = ['patient-0'];
|
||
}
|
||
} catch (error) {}
|
||
};
|
||
|
||
// 监听患者选择变化
|
||
function watchPatientSelection() {
|
||
// 定期检查患者选择状态变化
|
||
setInterval(() => {
|
||
if (patientInfoList.value && patientInfoList.value.length > 0) {
|
||
const selectedPatient = patientInfoList.value.find((patient) => patient.selected === true);
|
||
if (selectedPatient) {
|
||
patientInfo.value = selectedPatient.patientName || '';
|
||
} else {
|
||
patientInfo.value = '未选择患者';
|
||
}
|
||
} else {
|
||
patientInfo.value = '未选择患者';
|
||
}
|
||
}, 300);
|
||
}
|
||
|
||
/** 查询科室 */
|
||
const getLocationInfo = () => {
|
||
getOrgList().then((res) => {
|
||
orgOptions.value = res.data?.records[0]?.children;
|
||
});
|
||
};
|
||
getLocationInfo();
|
||
|
||
// 映射
|
||
const selectOrg = (itemid) => {
|
||
const item = orgOptions.value.find((item) => {
|
||
return item.id == itemid;
|
||
});
|
||
return item?.name;
|
||
};
|
||
// 重置
|
||
const onReset = () => {
|
||
const today = new Date();
|
||
dateRangeValue.value = [formatDateStr(today, 'YYYY-MM-DD'), formatDateStr(today, 'YYYY-MM-DD')];
|
||
formParams.orgId = undefined;
|
||
formParams.chargeItemEnum = undefined;
|
||
formParams.recordedTimeSTime = dateRangeValue.value[0] + ' ' + '00:00:00';
|
||
formParams.recordedTimeETime = dateRangeValue.value[1] + ' ' + '23:59:59';
|
||
dateRange.value = 'today';
|
||
getTableList();
|
||
};
|
||
|
||
// 加载费用类型选项
|
||
function loadFeeTypeOptions() {
|
||
// 模拟费用类型数据
|
||
feeTypeOptions.value = [
|
||
{ label: '检查费', value: 'examine' },
|
||
{ label: '治疗费', value: 'treatment' },
|
||
{ label: '药品费', value: 'medicine' },
|
||
{ label: '材料费', value: 'material' },
|
||
{ label: '床位费', value: 'bed' },
|
||
{ label: '其他费用', value: 'others' },
|
||
];
|
||
}
|
||
|
||
// 处理日期tabs点击
|
||
function handleDateTabClick(tab) {
|
||
const rangeType = tab.paneName;
|
||
const today = new Date();
|
||
const yesterday = new Date();
|
||
yesterday.setDate(today.getDate() - 1);
|
||
|
||
switch (rangeType) {
|
||
case 'today':
|
||
dateRangeValue.value = [
|
||
formatDateStr(today, 'YYYY-MM-DD'),
|
||
formatDateStr(today, 'YYYY-MM-DD'),
|
||
];
|
||
break;
|
||
case 'yesterday':
|
||
dateRangeValue.value = [
|
||
formatDateStr(yesterday, 'YYYY-MM-DD'),
|
||
formatDateStr(yesterday, 'YYYY-MM-DD'),
|
||
];
|
||
break;
|
||
// other 情况保持用户选择的值
|
||
}
|
||
if (!(dateRangeValue.value == null || dateRangeValue.value == undefined)) {
|
||
formParams.recordedTimeSTime = dateRangeValue.value[0] + ' ' + '00:00:00';
|
||
formParams.recordedTimeETime = dateRangeValue.value[1] + ' ' + '23:59:59';
|
||
}
|
||
getTableList();
|
||
}
|
||
|
||
// 处理日期选择器变化
|
||
function handleDatePickerChange() {
|
||
if (dateRangeValue?.value?.length > 0) {
|
||
// dateRange.value = 'other';
|
||
formParams.recordedTimeSTime = dateRangeValue.value[0] + ' ' + '00:00:00';
|
||
formParams.recordedTimeETime = dateRangeValue.value[1] + ' ' + '23:59:59';
|
||
} else {
|
||
formParams.recordedTimeSTime = undefined;
|
||
formParams.recordedTimeETime = undefined;
|
||
}
|
||
}
|
||
|
||
// 清空
|
||
const onClear = () => {
|
||
console.log('费用明细查询条件已清空');
|
||
|
||
const today = new Date();
|
||
dateRangeValue.value = [formatDateStr(today, 'YYYY-MM-DD'), formatDateStr(today, 'YYYY-MM-DD')];
|
||
formParams.orgId = undefined;
|
||
formParams.chargeItemEnum = undefined;
|
||
formParams.recordedTimeSTime = dateRangeValue.value[0] + ' ' + '00:00:00';
|
||
formParams.recordedTimeETime = dateRangeValue.value[1] + ' ' + '23:59:59';
|
||
dateRange.value = 'today';
|
||
getTableList();
|
||
};
|
||
|
||
// 格式化日期范围显示
|
||
function formatDateRange() {
|
||
if (dateRangeValue.value && dateRangeValue.value.length === 2) {
|
||
return `${dateRangeValue.value[0]} 至 ${dateRangeValue.value[1]}`;
|
||
}
|
||
return '';
|
||
}
|
||
|
||
// 查询按钮点击
|
||
function handleQuery() {
|
||
console.log('params=======>', formParams);
|
||
getTableList();
|
||
}
|
||
|
||
// 处理排序变化
|
||
function handleSortChange({ prop, order }) {
|
||
// const sortedData = [...feeDetailList.value];
|
||
const selectData = groupedPrescriptionList.value[selectIndex.value];
|
||
const sortedData = [...selectData];
|
||
|
||
if (order === 'ascending') {
|
||
sortedData.sort((a, b) => (a[prop] > b[prop] ? 1 : -1));
|
||
} else if (order === 'descending') {
|
||
sortedData.sort((a, b) => (a[prop] < b[prop] ? 1 : -1));
|
||
}
|
||
groupedPrescriptionList.value[selectIndex.value] = sortedData;
|
||
// feeDetailList.value = sortedData;
|
||
}
|
||
|
||
// 处理分页大小变化
|
||
function handleSizeChange(newSize) {
|
||
formParams.pageSize = newSize;
|
||
formParams.pageNo = 1;
|
||
getTableList();
|
||
}
|
||
|
||
// 处理当前页变化
|
||
function handleCurrentChange(newCurrent) {
|
||
formParams.pageNo = newCurrent;
|
||
getTableList();
|
||
}
|
||
|
||
// 导出
|
||
async function handleExport() {
|
||
if (groupedPrescriptionList.value.length === 0) {
|
||
ElMessage.warning('暂无数据可导出');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 实际项目中这里应该调用导出API或使用Excel库生成文件
|
||
await proxy.$download.downloadGet(
|
||
'/inhospitalnursestation/nursebilling/excel-out',
|
||
{
|
||
...formParams,
|
||
},
|
||
`dict_${new Date().getTime()}.xlsx`
|
||
);
|
||
} catch (error) {}
|
||
}
|
||
|
||
// 打印预览
|
||
function handlePrint() {
|
||
if (feeDetailList.value.length === 0) {
|
||
ElMessage.warning('暂无数据可打印');
|
||
return;
|
||
}
|
||
|
||
printDialogVisible.value = true;
|
||
}
|
||
|
||
// 执行打印
|
||
function doPrint() {
|
||
try {
|
||
// 获取要打印的内容
|
||
const printContent = document.getElementById('print-content').innerHTML;
|
||
|
||
// 创建临时窗口
|
||
const printWindow = window.open('', '_blank');
|
||
|
||
// 写入内容
|
||
printWindow.document.write(`
|
||
<html>
|
||
<head>
|
||
<title>费用明细清单</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th, td { border: 1px solid #ccc; padding: 8px; }
|
||
th { background-color: #f2f2f2; }
|
||
tfoot { font-weight: bold; }
|
||
.total-row { background-color: #f5f5f5; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${printContent}
|
||
</body>
|
||
</html>
|
||
`);
|
||
|
||
// 打印
|
||
printWindow.document.close();
|
||
printWindow.focus();
|
||
printWindow.print();
|
||
} catch (e) {
|
||
ElMessage.error('打印失败');
|
||
console.error('Print error:', e);
|
||
}
|
||
}
|
||
// 获取当前展开折叠板索引
|
||
function onCollapasChange(select) {
|
||
if (select) {
|
||
const selectArr = select.split('-');
|
||
if (selectArr && selectArr.length > 1) {
|
||
const idx = selectArr[1];
|
||
selectIndex.value = Number(idx);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========== 核心工具函数 ==========
|
||
/**
|
||
* 安全获取对象属性,避免空值报错
|
||
* @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] = [];
|
||
}
|
||
grouped[encounterId].push({
|
||
...item,
|
||
});
|
||
});
|
||
// 转换为数组格式
|
||
return Object.values(grouped);
|
||
};
|
||
|
||
/**
|
||
* 计算单个患者的总金额(保留4位小数)
|
||
* @param {Array} patientGroup - 患者医嘱列表
|
||
* @returns {String} 格式化后的金额字符串
|
||
*/
|
||
const calculatePatientTotal = (patientGroup) => {
|
||
const total = safeArray(patientGroup).reduce((sum, item) => {
|
||
return Math.round((sum + Number(safeGet(item, 'totalPrice', 0))) * 10000) / 10000;
|
||
}, 0);
|
||
return formatNumber(total, 4);
|
||
};
|
||
|
||
// 暴露方法供父组件调用
|
||
defineExpose({
|
||
handleQuery,
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 日期tabs样式 */
|
||
.date-tabs .el-tabs__header {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.date-tabs .el-tabs__content {
|
||
display: none;
|
||
}
|
||
|
||
/* :deep(.el-table__header th) {
|
||
background-color: #eef9fd !important;
|
||
} */
|
||
|
||
:deep(.el-table__row:hover > td) {
|
||
background-color: #eef9fd !important;
|
||
}
|
||
|
||
/* 折叠面板容器 */
|
||
.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;
|
||
padding-left: 10px;
|
||
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;
|
||
}
|
||
</style>
|