Files
his/openhis-ui-vue3/src/views/inHospitalManagement/charge/feeSettlement/index.vue
chenqi 14cb913943 refactor(table): 更新表格组件的单元格合并配置和事件处理
- 将所有表格的单元格合并方法从数组格式 [rowspan, colspan] 改为对象格式 { rowspan, colspan }
- 为 vxe-table 组件添加 checkbox-config 配置以支持复选框保留选择功能
- 移除复选框的 :reserve-selection 属性并改用 checkbox-config 配置
- 全局注册 VxeTableCompat 组件来归一化 cell-click 和 current-change 事件参数
- 更新技术执行和技术审批页面的表格组件配置和操作逻辑
- 优化
2026-06-05 11:44:31 +08:00

915 lines
27 KiB
Vue
Executable File
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
v-loading="readCardLoading"
style="display: flex; justify-content: space-between"
class="app-container"
:element-loading-text="loadingText"
>
<el-card style="width: 30%">
<template #header>
<span style="vertical-align: middle">患者列表</span>
</template>
<div style="width: 100%">
<el-input
v-model="queryParams.searchKey"
placeholder="请输入患者名/住院号/床号"
clearable
style="width: 48%; margin-bottom: 10px; margin-right: 10px"
@keyup.enter="getPatientList"
>
<template #append>
<el-button
icon="Search"
@click="getPatientList"
/>
</template>
</el-input>
<el-select
v-model="queryParams.encounterStatus"
style="width: 48%; margin-bottom: 10px; margin-right: 10px"
placeholder="结算状态"
@change="getPatientList"
>
<el-option
v-for="item in chargeStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div style="width: 100%">
<el-date-picker
v-model="receptionTime"
type="daterange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
placement="bottom"
value-format="YYYY-MM-DD"
style="width: 84%; margin-bottom: 10px; margin-right: 10px"
@change="getPatientList"
/>
<el-button
type="primary"
style="margin-bottom: 10px"
@click="getPatientList"
>
搜索
</el-button>
</div>
<vxe-table
ref="patientListRef"
height="620"
:data="patientList"
:row-config="{ keyField: 'encounterId', keyField: 'id' }"
@cell-click="clickRow"
>
<vxe-column
title="住院号"
align="center"
field="encounterBusNo"
/>
<!-- <vxe-column title="床号" align="center" field="bedNo" /> -->
<vxe-column
title="姓名"
align="center"
field="patientName"
/>
<vxe-column
title="账户余额"
align="center"
field="balanceAmount"
/>
<!-- <vxe-column title="时间" align="center" field="receptionTime" width="160">
<template #default="scope">
{{ formatDate(scope.row.receptionTime) }}
</template>
</vxe-column> -->
<vxe-column
title="结算状态"
align="center"
field="encounterStatus_enumText"
/>
</vxe-table>
</div>
</el-card>
<div style="width: 69%">
<el-card style="margin-bottom: 20px">
<template #header>
<span style="vertical-align: middle">基本信息</span>
</template>
<el-descriptions :column="5">
<el-descriptions-item label="姓名:">
{{ patientInfo.patientName }}
</el-descriptions-item>
<el-descriptions-item label="性别:">
{{ patientInfo.genderEnum_enumText }}
</el-descriptions-item>
<el-descriptions-item label="年龄:">
{{ patientInfo.age }}
</el-descriptions-item>
<!-- <el-descriptions-item label="科室:">
{{ patientInfo.organizationName }}
</el-descriptions-item> -->
<!-- <el-descriptions-item label="床号:">
{{ patientInfo.bedNo }}
</el-descriptions-item>
<el-descriptions-item label="就诊时间:">
{{ formatDateStr(patientInfo.receptionTime, 'YYYY-MM-DD HH:mm:ss') }}
</el-descriptions-item> -->
</el-descriptions>
</el-card>
<el-card>
<el-date-picker
v-model="costSearchTime"
type="daterange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="结束时间"
placement="bottom"
value-format="YYYY-MM-DD"
style="width: 40%; margin-bottom: 10px; margin-right: 10px"
@change="getPatientList"
/>
<el-button
type="primary"
style="margin-bottom: 10px"
@click="costSearch"
>
搜索
</el-button>
</el-card>
<el-card style="min-width: 1100px; margin-top: 20px">
<template #header>
<span style="vertical-align: middle">收费项目</span>
</template>
<div style="margin-bottom: 10px">
<el-button
type="primary"
:disabled="buttonDisabled"
@click="confirmCharge()"
>
确认收费
</el-button>
<el-button
type="primary"
plain
style="width: 65px"
@click="handleReadCard('01')"
>
电子凭证
</el-button>
<el-button
type="primary"
plain
style="width: 65px"
:disabled="true"
@click="handleReadCard('02')"
>
身份证
</el-button>
<el-button
type="primary"
plain
style="width: 65px"
@click="handleReadCard('03')"
>
医保卡
</el-button>
<el-button
type="primary"
style="margin-left: 20px"
:disabled="buttonDisabled"
@click="payToSelt()"
>
医保转自费
</el-button>
<el-button
type="primary"
style="margin-left: 20px"
:disabled="buttonDisabled"
@click="patToMedicalInsurance()"
>
自费转医保
</el-button>
<el-button
type="primary"
style="margin-left: 20px"
:disabled="buttonDisabled"
@click="studentPayTosStudentSelf()"
>
学生医保转学生自费
</el-button>
<el-button
type="primary"
style="margin-left: 20px"
:disabled="buttonDisabled"
@click="studentSelfToStudentPay()"
>
学生自费转学生医保
</el-button>
<span style="float: right">合计金额{{ totalAmounts ? totalAmounts.toFixed(2) : 0 }}</span>
</div>
<vxe-table
ref="chargeListRef"
v-loading="chargeLoading"
height="530"
:data="chargeFilterList"
:span-method="objectSpanMethod"
border
@checkbox-change="handleSelectionChange"
>
<vxe-column
type="checkbox"
:selectable="checkSelectable"
width="55"
/>
<vxe-column
title="单据号"
align="center"
field="busNo"
width="180"
/>
<vxe-column
title=" 开立科室"
align="center"
field="requestingOrgId"
width="180"
/>
<vxe-column
title="处方号"
align="center"
field="prescriptionNo"
width="180"
/>
<vxe-column
title="收费项目"
align="center"
field="itemName"
width="200"
/>
<vxe-column
title="数量"
align="center"
field="quantityValue"
width="80"
/>
<vxe-column
title="医疗类型"
align="center"
field="medTypeCode_dictText"
/>
<vxe-column
title="医保编码"
align="center"
field="ybNo"
/>
<vxe-column
title="费用性质"
align="center"
field="contractName"
/>
<vxe-column
title="结算状态"
align="center"
field="statusEnum_enumText"
width="150"
>
<template #default="scope">
<!-- 1代收费 2代结算 5已收费 8已退费 -->
<el-tag
v-if="scope.row.statusEnum === 1"
disable-transitions
>
{{ scope.row.statusEnum_enumText }}
</el-tag>
<el-tag
v-else-if="scope.row.statusEnum === 5"
type="success"
disable-transitions
>
{{ scope.row.statusEnum_enumText }}
</el-tag>
<el-tag
v-else-if="scope.row.statusEnum === 8"
type="danger"
disable-transitions
>
{{ scope.row.statusEnum_enumText }}
</el-tag>
<el-tag
v-else
type="warning"
disable-transitions
>
{{ scope.row.statusEnum_enumText }}
</el-tag>
</template>
</vxe-column>
<vxe-column
title="金额"
align="right"
field="totalPrice"
header-align="center"
>
<template #default="scope">
{{ scope.row.totalPrice.toFixed(2) + ' 元' || '0.00' + ' 元' }}
</template>
</vxe-column>
<vxe-column
title="收款人"
align="center"
field="entererId_dictText"
/>
<vxe-column
title="收费时间"
align="center"
field="billDate"
width="180"
/>
<vxe-column
title="应收金额"
align="right"
field="receivableAmount"
header-align="center"
>
<template #default="scope">
{{ scope.row.receivableAmount.toFixed(2) + ' 元' || '0.00' + ' 元' }}
</template>
</vxe-column>
<vxe-column
title="实收金额"
align="right"
field="receivedAmount"
header-align="center"
>
<template #default="scope">
{{ scope.row.receivedAmount.toFixed(2) + ' 元' || '0.00' + ' 元' }}
</template>
</vxe-column>
<vxe-column
title="优惠金额"
align="right"
field="discountAmount"
header-align="center"
>
<template #default="scope">
{{ scope.row.discountAmount.toFixed(2) + ' 元' || '0.00' + ' 元' }}
</template>
</vxe-column>
<vxe-column
title="折扣率"
align="right"
field="discountRate"
header-align="center"
>
<template #default="scope">
<!-- discountRate是一个字符串类型的 -->
{{ Number(scope.row.discountRate).toFixed(2) + '%' || '0.00' + '%' }}
</template>
</vxe-column>
<vxe-column
title="操作"
align="center"
fixed="right"
header-align="center"
class-name="no-hover-column"
>
<template #default="scope">
<el-button
:disabled="!scope.row.paymentId"
link
type="primary"
@click="printCharge(scope.row)"
>
打印
</el-button>
</template>
</vxe-column>
</vxe-table>
</el-card>
</div>
<ChargeDialog
ref="chargeDialogRef"
:open="openDialog"
:category="patientInfo.categoryEnum"
:total-amount="totalAmount"
:patient-info="patientInfo"
:charge-item-ids="chargeItemIdList"
:consumables-id-list="consumablesIdList"
:chrg-bchno-list="chrgBchnoList"
:user-card-info="userCardInfo"
:payment-id="paymentId"
:details="details"
:charged-items="chargedItems"
@close="handleClose"
:new-id="newId"
:old-id="oldId"
@refresh="getPatientList"
/>
</div>
</template>
<script setup name="ClinicCharge">
import {
changeStudentPayTosStudentSelf,
changeStudentSelfToStudentPay,
changeToMedicalInsurance,
changeToSelfPay,
getChargeInfo,
getChargeList,
getList1,
init1,
precharge,
} from './components/api';
import {invokeYbPlugin5001} from '@/api/public';
import ChargeDialog from './components/chargeDialog.vue';
import {formatDateStr} from '@/utils';
import useUserStore from '@/store/modules/user';
import Decimal from 'decimal.js';
import dayjs from 'dayjs';
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const queryParams = ref({
pageNum: 1,
pageSize: 50,
encounterStatus: 4,
});
const totalAmounts = ref(0);
const selectedRows = ref([]);
const patientList = ref([]);
// 收费项目列表
const chargeList = ref([]);
// 根据时间过滤的收费项目列表
const chargeFilterList = ref([]);
const chargeItemIdList = ref([]);
const chrgBchnoList = ref([]);
const chargeLoading = ref(false);
const encounterId = ref('');
const paymentId = ref('');
const newId = ref('');
const oldId = ref('');
const patientInfo = ref({});
const openDialog = ref(false);
const totalAmount = ref(0);
const chargeListRef = ref();
const details = ref({});
const chargeStatusOptions = ref([]);
const receptionTime = ref([
formatDateStr(new Date(), 'YYYY-MM-DD'),
formatDateStr(new Date(), 'YYYY-MM-DD'),
]);
// 计算当前时间+6天
const accumulateDay = () => {
// 获取当前时间
let now = dayjs();
// 在当前时间上加一天
let tomorrow = now.add(6, 'days');
// 格式化为年-月-日的格式
let formattedDate = tomorrow.format('YYYY-MM-DD');
return formattedDate;
};
// 收费项目时间段搜索
const costSearchTime = ref([formatDateStr(new Date(), 'YYYY-MM-DD'), accumulateDay()]);
// 测试数据
const items = [
{ id: 1, name: 'Item 1', date: '2025-12-15' },
{ id: 2, name: 'Item 2', date: '2025-12-18' },
{ id: 3, name: 'Item 3', date: '2025-12-20' },
];
// 根据时间短搜索
const costSearch = () => {
console.log(costSearchTime.value === null);
if (costSearchTime.value === null) {
chargeFilterList.value = chargeList.value;
} else {
const startTime = dayjs(costSearchTime.value[0], 'YYYY-MM-DD');
const endTime = dayjs(costSearchTime.value[1], 'YYYY-MM-DD');
const filterData = chargeList.value.filter((item) => {
const itemDate = dayjs(item.billDate || '', 'YYYY-MM-DD'); // 将数据项的日期也格式化为moment对象
return itemDate.isBetween(startTime, endTime, null, '[]'); // 使用isBetween方法进行范围判断'[]'表示包含边界值
});
chargeFilterList.value = filterData;
}
};
const buttonDisabled = computed(() => {
return Object.keys(patientInfo.value).length === 0;
});
const chargedItems = ref([]);
watch(
() => selectedRows.value,
(newVlaue) => {
if (newVlaue && newVlaue.length > 0) {
handleTotalAmount();
} else {
totalAmounts.value = 0;
}
},
{ immediate: true }
);
watch(
() => chargeList.value,
(newVlaue) => {
if (newVlaue && newVlaue.length > 0) {
chargeFilterList.value = newVlaue;
}
},
{ immediate: true }
);
function handleSelectionChange(selection) {
selectedRows.value = selection;
}
function handleTotalAmount() {
totalAmounts.value = selectedRows.value.reduce((accumulator, currentRow) => {
return new Decimal(accumulator).add(currentRow.totalPrice.toFixed(2) || 0);
}, 0);
}
getPatientList();
initOption();
/**
* 患者列表
*/
function getPatientList() {
if (receptionTime.value.length > 0) {
queryParams.value.startTimeSTime = receptionTime.value[0] + ' 00:00:00';
queryParams.value.startTimeETime = receptionTime.value[1] + ' 23:59:59';
} else {
queryParams.value.startTimeSTime = undefined;
queryParams.value.startTimeETime = undefined;
}
getList1(queryParams.value).then((res) => {
console.log('患者列表', res);
patientList.value = res.data.records;
});
}
function initOption() {
init1().then((res) => {
console.log('初始化收费项目状态', res);
chargeStatusOptions.value = res.data.encounterStatusOptions;
console.log('res.data.chargeItemStatusOptions======>', JSON.stringify(res.data));
});
}
function checkSelectable(row, index) {
// 已结算时禁用选择框
return row.statusEnum == 1 || row.statusEnum == 2;
}
/**
* 点击患者列表行 获取处方列表
*/
function clickRow(row) {
patientInfo.value = row;
chargeLoading.value = true;
encounterId.value = row.encounterId;
getChargeList(row.encounterId).then((res) => {
chargeList.value = res.data;
setTimeout(() => {
chargeLoading.value = false;
chargeListRef.value.toggleAllCheckboxRow();
}, 100);
});
}
function handleClose(value, msg) {
openDialog.value = false;
if (value == 'success') {
proxy.$modal.msgSuccess(msg);
chargeLoading.value = true;
getChargeList(patientInfo.value.encounterId).then((res) => {
chargeList.value = res.data;
setTimeout(() => {
chargeLoading.value = false;
}, 100);
});
}
}
const consumablesIdList = ref([]);
// 确认收费
function confirmCharge() {
let selectRows = chargeListRef.value.getSelectionRows();
if (selectRows.length == 0) {
proxy.$modal.msgWarning('请选择一条收费项目');
return;
}
chargeItemIdList.value = selectRows.map((item) => {
return item.id;
});
//wor_device_request 器材
consumablesIdList.value = selectRows
.filter((item) => {
return item.serviceTable == 'wor_device_request';
})
.map((item) => {
return item.id;
});
chargedItems.value = selectRows;
// totalAmount.value = selectRows.reduce((accumulator, currentRow) => {
// return accumulator + (currentRow.totalPrice || 0);
// }, 0);
precharge({
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
chargeItemIds: chargeItemIdList.value,
}).then((res) => {
if (res.code == 200 && res.data) {
const item = res.data.paymentRecDetailDtoList?.find((i) => {
return i.payEnum == 220000;
});
totalAmount.value = item?.amount ?? 0;
paymentId.value = res.data.id;
newId.value = res.data.newId;
oldId.value = res.data.oldId;
chrgBchnoList.value = res.data.chrgBchnoList;
details.value = res.data.paymentRecDetailDtoList?.filter((item) => {
return item.amount > 0;
}) || [];
openDialog.value = true;
} else {
proxy.$modal.msgError(res?.msg || '预结算失败');
}
});
// console.log(patientInfo)
}
let userCardInfo = ref({});
const readCardLoading = ref(false);
const loadingText = ref('');
const BusiCardInfo = ref(''); // miyao
async function handleReadCard(value) {
// if (window.CefSharp === undefined) {
// alert('请在医保版本中调用读卡功能!');
// } else {
try {
// await CefSharp.BindObjectAsync('boundAsync');
// string url,
// string fixmedins_code,
// string businessType,
// string operatorCode,
// string operatorName,
// string officeId,
// string officeName
// readCardLoading.value = true;
let jsonResult;
let cardInfo;
let userMessage = undefined;
switch (value) {
case '01': // 电子凭证
// readCardLoading.value = true;
await invokeYbPlugin5001({
FunctionId: 3,
url: 'http://10.47.0.67:8089/localcfc/api/hsecfc/localQrCodeQuery',
orgId: 'H22010200672',
businessType: '01101',
operatorId: userStore.id.toString(),
operatorName: userStore.name,
officeId: 'D83',
officeName: '财务科',
})
.then((res) => {
readCardLoading.value = true;
loadingText.value = '正在读取...';
jsonResult = res.data;
})
.catch(() => {
readCardLoading.value = false;
})
.finally(() => {
readCardLoading.value = false;
});
cardInfo = JSON.parse(JSON.stringify(jsonResult));
let message = JSON.parse(cardInfo.message);
userMessage = {
certType: '02', // 证件类型
certNo: message.data.idNo, // 身份证号
psnCertType: '02', // 居民身份证
};
userCardInfo = {
certType: '01', // 证件类型
certNo: message.data.idNo, // 身份证号
psnCertType: '01', // 居民身份证
busiCardInfo: message.data.ecToken, // 令牌
};
BusiCardInfo.value = message.data.ecToken;
console.log(BusiCardInfo.value);
break;
case '02':
break;
case '03': // 社保卡
readCardLoading.value = true;
loadingText.value = '正在读取...';
await invokeYbPlugin5001(
JSON.stringify({
FunctionId: 1,
IP: 'ddjk.jlhs.gov.cn',
PORT: 20215,
TIMEOUT: 60,
SFZ_DRIVER_TYPE: 1,
})
)
.then((res) => {
jsonResult = JSON.stringify(res.data);
})
.finally(() => {
readCardLoading.value = false;
});
// console.log(
// 'jsonResult',
// JSON.parse({
// IssuingAreaCode: '310000',
// SocialSecurityNumber: '371324198810224515',
// CardNumber: 'M501A1A78',
// CardIdentificationCode: '310000D15600000535925154E880AB97',
// Name: '\u5218\u5CF0',
// CardResetInfo: '00814A444686603100333E4FA9',
// SpecificationVersion: '3.00',
// IssuingDate: '20190313',
// ExpirationDate: '20290313',
// TerminalNumber: '000000000000',
// TerminalDeviceNumber: '00041161201901000005',
// Code: 0,
// ErrorMessage: null,
// })
// );
let message1 = JSON.parse(jsonResult);
userMessage = {
certType: '02', // 证件类型
certNo: message1.SocialSecurityNumber, // 身份证号
psnCertType: '02', // 居民身份证
};
userCardInfo = {
certType: '02', // 证件类型
certNo: message1.SocialSecurityNumber, // 身份证号
psnCertType: '02', // 居民身份证
busiCardInfo: message1.BusiCardInfo, //卡号
};
BusiCardInfo.value = message1.BusiCardInfo;
console.log(message1.BusiCardInfo);
break;
case '99':
break;
}
readCardLoading.value = false;
if (userMessage.certNo) {
let selectRows = chargeListRef.value.getSelectionRows();
if (selectRows.length == 0) {
proxy.$modal.msgWarning('请选择一条收费项目');
return;
}
chargeItemIdList.value = selectRows.map((item) => {
return item.id;
});
totalAmount.value = selectRows.reduce((accumulator, currentRow) => {
return accumulator + (currentRow.totalPrice || 0);
}, 0);
precharge({
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
chargeItemIds: chargeItemIdList.value,
ybMdtrtCertType: userCardInfo.psnCertType,
busiCardInfo: userCardInfo.busiCardInfo,
}).then((res) => {
if (res.code == 200 && res.data) {
paymentId.value = res.data.id;
totalAmount.value = res.data.paymentRecDetailDtoList?.find(
(item) => item.payEnum == 220000
)?.amount ?? 0;
details.value = res.data.paymentRecDetailDtoList || [];
// chrgBchnoList.value = res.data.chrgBchnoList;
chargeItemIdList.value = selectRows.map((item) => {
return item.id;
});
// 打印项目赋值
chargedItems.value = selectRows;
newId.value = res.data.newId;
oldId.value = res.data.oldId;
consumablesIdList.value = selectRows
.filter((item) => {
return item.serviceTable == 'wor_device_request';
})
.map((item) => {
return item.id;
});
openDialog.value = true;
} else {
proxy.$modal.msgError(res?.msg || '预结算失败');
}
});
}
} catch (error) {
console.error('调用失败:', error);
readCardLoading.value = false;
}
// }
}
/**
* 医保转自费
*/
function payToSelt() {
changeToSelfPay(encounterId.value).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
}
});
}
/**
* 自费转医保
*/
function patToMedicalInsurance() {
changeToMedicalInsurance(encounterId.value).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
}
});
}
/**
* 学生医保转学生自费
*/
function studentPayTosStudentSelf() {
changeStudentPayTosStudentSelf(encounterId.value).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
}
});
}
/**
* 学生自费转学生医保
*/
function studentSelfToStudentPay() {
changeStudentSelfToStudentPay(encounterId.value).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess('操作成功');
}
});
}
// 添加行合并方法
function objectSpanMethod({ row, column, rowIndex, columnIndex }) {
// 只对操作列进行合并(根据列索引判断,操作列为最后一列)
if (columnIndex === 10) {
// 操作列索引为10从0开始计数
// 如果当前行的paymentId为空则不合并
if (!row.paymentId) {
return { rowspan: 1, colspan: 1 };
}
// 查找相同paymentId的连续行
let spanCount = 1;
if (rowIndex === 0 || chargeList.value[rowIndex - 1].paymentId !== row.paymentId) {
// 这是具有相同paymentId的第一行计算需要合并的行数
for (let i = rowIndex + 1; i < chargeList.value.length; i++) {
if (chargeList.value[i].paymentId === row.paymentId) {
spanCount++;
} else {
break;
}
}
return { rowspan: spanCount, colspan: 1 };
} else {
// 这不是具有相同paymentId的第一行返回0表示不显示
return { rowspan: 0, colspan: 0 };
}
}
// 其他列不合并
return { rowspan: 1, colspan: 1 };
}
function printCharge(row) {
// 打印功能实现
let rows = [];
chargeList.value.forEach((item, index) => {
if (item.paymentId === row.paymentId) {
rows.push(item);
}
});
chargedItems.value = rows;
getChargeInfo({ paymentId: row.paymentId }).then((res) => {
proxy.$refs['chargeDialogRef'].printReceipt(res.data);
});
}
</script>
<style scoped>
:deep(.no-hover-column) .cell:hover {
background-color: transparent !important;
}
:deep(.vxe-table--body) tr:hover td.no-hover-column {
background-color: inherit !important;
}
</style>