Files
his/openhis-ui-vue3/src/views/inpatientNurse/InpatientBilling/components/feeDetailQuery.vue
2025-12-10 14:20:24 +08:00

611 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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"
style="width: 240px; margin-right: 20px"
/>
<!-- 费用类型选择 -->
<el-select
v-model="feeType"
placeholder="请选择费用类型"
clearable
style="width: 150px; margin-right: 20px"
>
<el-option
v-for="type in feeTypeOptions"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
<!-- 执行科室选择 -->
<el-select
v-model="execDepartment"
placeholder="请选择执行科室"
clearable
style="width: 150px; margin-right: 20px"
>
<el-option
v-for="dept in departmentOptions"
:key="dept.value"
:label="dept.label"
:value="dept.value"
/>
</el-select>
<!-- 查询按钮 -->
<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;">
合计金额¥{{ totalAmount.toFixed(2) }}
</p>
<p style="margin: 5px 0; color: #606266; font-size: 14px;">明细项数{{ feeDetailList.length }}</p>
</div>
</div>
</div>
<el-table
ref="tableRef"
:data="feeDetailList"
border
style="width: 100%"
:header-cell-style="{ background: '#eef9fd !important' }"
@sort-change="handleSortChange"
>
<el-table-column label="项目名称" prop="itemName" min-width="200" />
<el-table-column label="费用类型" prop="feeTypeName" width="120" align="center" />
<el-table-column label="单价" prop="unitPrice" width="100" align="center" sortable />
<el-table-column label="数量" prop="quantity" width="100" align="center" sortable />
<el-table-column label="金额" prop="amount" width="100" align="center" sortable>
<template #default="scope">
<span style="color: #ff4d4f">{{ scope.row.amount.toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="执行科室" prop="execDept" width="120" align="center" />
<el-table-column label="执行人" prop="executor" width="100" align="center" />
<el-table-column label="执行日期" prop="executeDate" width="120" align="center" />
<el-table-column label="医保类型" prop="insuranceType" width="100" align="center">
<template #default="scope">
<el-tag v-if="scope.row.insuranceType" size="small">{{ scope.row.insuranceType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="150" />
</el-table>
<!-- 分页 -->
<div style="margin-top: 15px; display: flex; justify-content: flex-end;">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 无数据提示 -->
<el-empty v-if="!loading && feeDetailList.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 { ref, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { patientInfoList } from '../../medicalOrderExecution/store/patient.js';
import { formatDateStr } from '@/utils/index';
// 响应式数据
const loading = ref(false);
const feeDetailList = ref([]);
const dateRange = ref('today'); // today, yesterday, other
const dateRangeValue = ref([]);
const feeType = ref('');
const execDepartment = ref('');
const departmentOptions = ref([]);
const feeTypeOptions = ref([]);
const tableRef = ref(null);
const patientInfo = ref('');
// 分页相关
const currentPage = ref(1);
const pageSize = ref(20);
const total = ref(0);
// 打印相关
const printDialogVisible = ref(false);
// 计算总金额
const totalAmount = computed(() => {
return feeDetailList.value.reduce((sum, item) => {
return sum + (item.amount || 0);
}, 0);
});
// 初始化
onMounted(() => {
// 设置默认日期
const today = new Date();
dateRangeValue.value = [formatDateStr(today, 'YYYY-MM-DD'), formatDateStr(today, 'YYYY-MM-DD')];
// 加载科室选项
loadDepartmentOptions();
// 加载费用类型选项
loadFeeTypeOptions();
// 监听患者选择变化
watchPatientSelection();
});
// 监听患者选择变化
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);
}
// 加载科室选项
function loadDepartmentOptions() {
// 模拟科室数据
departmentOptions.value = [
{ label: '内科', value: 'internal' },
{ label: '外科', value: 'surgery' },
{ label: '儿科', value: 'pediatrics' },
{ label: '妇产科', value: 'obstetrics' },
{ label: '检验科', value: 'laboratory' },
{ label: '影像科', value: 'imaging' },
{ label: '其他科室', value: 'others' },
];
}
// 加载费用类型选项
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 情况保持用户选择的值
}
}
// 处理日期选择器变化
function handleDatePickerChange() {
if (dateRangeValue.value.length > 0) {
dateRange.value = 'other';
}
}
// 格式化日期范围显示
function formatDateRange() {
if (dateRangeValue.value && dateRangeValue.value.length === 2) {
return `${dateRangeValue.value[0]}${dateRangeValue.value[1]}`;
}
return '';
}
// 生成模拟数据
function generateMockData() {
// 费用类型映射
const feeTypeMap = {
'examine': '检查费',
'treatment': '治疗费',
'medicine': '药品费',
'material': '材料费',
'bed': '床位费',
'others': '其他费用'
};
// 科室映射
const deptMap = {
'internal': '内科',
'surgery': '外科',
'pediatrics': '儿科',
'obstetrics': '妇产科',
'laboratory': '检验科',
'imaging': '影像科',
'others': '其他科室'
};
// 项目数据池
const itemPools = {
'examine': ['血常规检查', '尿常规检查', '肝功能检查', '肾功能检查', '胸部CT', '心电图', 'B超'],
'treatment': ['静脉输液', '肌肉注射', '氧气吸入', '导尿', '换药', '雾化吸入'],
'medicine': ['抗生素注射液', '维生素C片', '感冒药', '止痛药', '降压药', '消炎药'],
'material': ['一次性输液器', '注射器', '医用棉花', '纱布', '胶带', '一次性手套'],
'bed': ['普通病房床位', 'ICU床位', '单人病房床位', '双人病房床位'],
'others': ['护理费', '诊疗费', '挂号费', '病历费']
};
// 合并所有项目到allItems数组中修复添加这部分代码
let allItems = [];
Object.keys(itemPools).forEach(type => {
itemPools[type].forEach(item => {
allItems.push({
type: type,
name: item
});
});
});
// 筛选条件
if (feeType.value) {
allItems = allItems.filter(item => item.type === feeType.value);
}
// 生成模拟数据
const mockData = [];
const baseDate = new Date(dateRangeValue.value[0]);
const endDate = new Date(dateRangeValue.value[1]);
const daysDiff = Math.ceil((endDate - baseDate) / (1000 * 60 * 60 * 24)) + 1;
// 执行人池
const executors = ['护士A', '护士B', '医生A', '医生B', '技师A'];
// 生成数据
let id = 1;
for (let day = 0; day < daysDiff; day++) {
const currentDate = new Date(baseDate);
currentDate.setDate(baseDate.getDate() + day);
const dateStr = formatDateStr(currentDate, 'YYYY-MM-DD');
// 每天生成3-8条记录
const recordsCount = Math.floor(Math.random() * 6) + 3;
for (let i = 0; i < recordsCount; i++) {
// 随机选择一个项目
const randomItemIndex = Math.floor(Math.random() * allItems.length);
const selectedItem = allItems[randomItemIndex];
// 随机选择一个科室
const deptKeys = Object.keys(deptMap);
const randomDeptKey = deptKeys[Math.floor(Math.random() * deptKeys.length)];
// 如果有科室筛选且不符合,则跳过
if (execDepartment.value && randomDeptKey !== execDepartment.value) {
continue;
}
// 生成随机单价和数量
const unitPrice = Math.floor(Math.random() * 190) + 10; // 10-200元
const quantity = Math.floor(Math.random() * 3) + 1; // 1-4
// 医保类型
const insuranceTypes = ['', '甲类', '乙类', '丙类'];
const insuranceType = insuranceTypes[Math.floor(Math.random() * insuranceTypes.length)];
mockData.push({
id: `item-${id++}`,
itemName: selectedItem.name,
feeType: selectedItem.type,
feeTypeName: feeTypeMap[selectedItem.type],
unitPrice: unitPrice,
quantity: quantity,
amount: unitPrice * quantity,
execDept: deptMap[randomDeptKey],
executor: executors[Math.floor(Math.random() * executors.length)],
executeDate: dateStr,
insuranceType: insuranceType,
remark: ''
});
}
}
return mockData;
}
// 查询按钮点击
function handleQuery() {
// 添加调试日志,查看患者列表数据结构
console.log('患者列表数据:', patientInfoList.value);
// 更灵活的患者选择检测逻辑
let selectedPatient = null;
if (patientInfoList.value && patientInfoList.value.length > 0) {
// 尝试查找选中状态的患者
selectedPatient = patientInfoList.value.find(patient =>
patient.selected === true ||
patient.checked === true ||
patient.isChecked === true ||
(typeof patient.selected === 'string' && patient.selected === '1')
);
// 如果没有明确选中的患者,就使用列表中的第一个患者
if (!selectedPatient) {
selectedPatient = patientInfoList.value[0];
}
}
// 即使没有明确选中的患者标志,也应该允许查询
if (!selectedPatient) {
ElMessage.warning('请先选择患者');
return;
}
// 更新患者信息显示 - 修复:确保正确获取患者姓名
const patientName = selectedPatient.patientName ||
selectedPatient.name ||
selectedPatient.patientInfo ||
selectedPatient.patient ||
'未命名患者';
patientInfo.value = patientName;
loading.value = true;
// 模拟API调用延迟
setTimeout(() => {
try {
// 使用模拟数据
const allData = generateMockData();
total.value = allData.length;
// 分页处理
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
feeDetailList.value = allData.slice(start, end);
} catch (error) {
console.error('查询错误:', error);
ElMessage.error('查询失败,请重试');
feeDetailList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
}, 500);
}
// 处理排序变化
function handleSortChange({ prop, order }) {
const sortedData = [...feeDetailList.value];
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);
}
feeDetailList.value = sortedData;
}
// 处理分页大小变化
function handleSizeChange(newSize) {
pageSize.value = newSize;
currentPage.value = 1;
handleQuery();
}
// 处理当前页变化
function handleCurrentChange(newCurrent) {
currentPage.value = newCurrent;
handleQuery();
}
// 导出
function handleExport() {
if (feeDetailList.value.length === 0) {
ElMessage.warning('暂无数据可导出');
return;
}
// 模拟导出操作
ElMessage.success('导出成功');
// 实际项目中这里应该调用导出API或使用Excel库生成文件
}
// 打印预览
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);
}
}
// 暴露方法供父组件调用
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;
}
</style>