bug 588 628 642 700 714 715

This commit is contained in:
Ranyunqiao
2026-06-15 16:48:27 +08:00
parent 43ab5b4498
commit b25d2fbaa9
7 changed files with 652 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
package com.core.framework.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
@@ -14,6 +15,9 @@ import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -55,9 +59,18 @@ public class ApplicationConfig {
@Bean
public JsonMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> {
builder.defaultTimeZone(TimeZone.getDefault());
builder.defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
SimpleModule module = new SimpleModule("HealthLinkLocalDateTime");
module.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
module.addSerializer(LocalDateTime.class, LOCAL_DATE_TIME_SERIALIZER);
module.addSerializer(java.sql.Date.class, new ValueSerializer<java.sql.Date>() {
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void serialize(java.sql.Date value, JsonGenerator gen, SerializationContext ctx) throws JacksonException {
gen.writeString(sdf.format(value));
}
});
builder.addModule(module);
};
}

View File

@@ -378,10 +378,21 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
*/
@Override
public R<?> getInPatientPendingList(InpatientAdviceParam inpatientAdviceParam, Integer pageNo, Integer pageSize) {
// 提取deadline手动处理防止自动拼接列名不存在的错误
String deadline = inpatientAdviceParam.getDeadline();
inpatientAdviceParam.setDeadline(null);
// 构建查询条件
QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
// 手动拼接截止时间条件request_time <= deadline
if (StringUtils.isNotEmpty(deadline)) {
Date deadlineDate = DateUtils.parseDate(deadline);
if (deadlineDate != null) {
queryWrapper.le("request_time", deadlineDate);
}
}
// 患者医嘱分页列表
Page<InpatientAdviceDto> inpatientAdvicePage = atdManageAppMapper.selectInpatientAdvicePage(
new Page<>(pageNo, pageSize), queryWrapper, CommonConstants.TableName.MED_MEDICATION_REQUEST,

View File

@@ -191,6 +191,14 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
// 手动拼接截止时间条件request_time <= deadline
if (StringUtils.isNotEmpty(deadline)) {
Date deadlineDate = DateUtils.parseDate(deadline);
if (deadlineDate != null) {
queryWrapper.le("request_time", deadlineDate);
}
}
// 手动拼接requestStatus条件COMPLETED(3)时同时包含CHECK_VERIFIED(10)和PENDING_RECEIVE(11)
// UNION查询外层列名为request_statusT1.status_enum AS request_status不是status_enum
if (requestStatus != null) {

View File

@@ -235,10 +235,21 @@ public class NurseBillingAppService implements INurseBillingAppService {
inpatientAdviceParam.setEncounterIds(null);
Integer exeStatus = inpatientAdviceParam.getExeStatus();
inpatientAdviceParam.setExeStatus(null);
// 提取deadline手动处理防止自动拼接列名不存在的错误
String deadline = inpatientAdviceParam.getDeadline();
inpatientAdviceParam.setDeadline(null);
// 构建查询条件
QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
// 手动拼接截止时间条件request_time <= deadline
if (StringUtils.isNotEmpty(deadline)) {
Date deadlineDate = DateUtils.parseDate(deadline);
if (deadlineDate != null) {
queryWrapper.le("request_time", deadlineDate);
}
}
// 手动拼接住院患者id条件
if (encounterIds != null && !encounterIds.isEmpty()) {
List<Long> encounterIdList

View File

@@ -4,11 +4,11 @@
*/
import {hiprint} from 'vue-plugin-hiprint';
import useUserStore from '@/store/modules/user';
import {ElMessage} from 'element-plus';
// 打印模板映射表 .
const TEMPLATE_MAP = {
// CLINIC_CHARGE: () => import('@/views/charge/cliniccharge/components/template.json'),
// DISPOSAL: () => import('@/views/clinicmanagement/disposal/components/disposalTemplate.json'),
//处方签
PRESCRIPTION: () => import('@/components/Print/Prescription.json'),
//处置单
@@ -137,29 +137,53 @@ export async function simplePrintWithDialog(
// 导出模板名称常量
export const PRINT_TEMPLATE = {
// CLINIC_CHARGE: 'CLINIC_CHARGE',
DAY_END: 'DAY_END',
WESTERN_MEDICINE: 'WESTERN_MEDICINE',
IN_HOSPITAL_DISPENSING: 'IN_HOSPITAL_DISPENSING',
//门诊挂号
OUTPATIENT_REGISTRATION: 'OUTPATIENT_REGISTRATION',
// 门诊手术计费
OUTPATIENT_SURGERY_CHARGE: 'OUTPATIENT_SURGERY_CHARGE',
//门诊收费
OUTPATIENT_CHARGE: 'OUTPATIENT_CHARGE',
//处方签
PRESCRIPTION: 'PRESCRIPTION',
//处置单
DISPOSAL: 'DISPOSAL',
//门诊病历
OUTPATIENT_MEDICAL_RECORD: 'OUTPATIENT_MEDICAL_RECORD',
//门诊输液贴
OUTPATIENT_INFUSION: 'OUTPATIENT_INFUSION',
//手术记录
OPERATIVE_RECORD: 'OPERATIVE_RECORD',
//红旗门诊病历
HQOUTPATIENT_MEDICAL_RECORD: 'HQOUTPATIENT_MEDICAL_RECORD',
//预交金
ADVANCE_PAYMENT: 'ADVANCE_PAYMENT',
//中药处方单
CHINESE_MEDICINE_PRESCRIPTION: 'CHINESE_MEDICINE_PRESCRIPTION',
//药房处方单
PHARMACY_PRESCRIPTION: 'PHARMACY_PRESCRIPTION',
//中药医生处方单
DOC_CHINESE_MEDICINE_PRESCRIPTION: 'DOC_CHINESE_MEDICINE_PRESCRIPTION',
// ========== 新增模板原LODOP迁移==========
//腕带
WRIST_BAND: 'WRIST_BAND',
//分诊条
TRIAGE_TICKET: 'TRIAGE_TICKET',
//输液标签
INJECT_LABEL: 'INJECT_LABEL',
//床头卡
BED_CARD: 'BED_CARD',
//护理交接班
CHANGE_SHIFT_BILL: 'CHANGE_SHIFT_BILL',
//医嘱执行单
EXE_ORDER_SHEET: 'EXE_ORDER_SHEET',
//体温单
TEMPERATURE_SHEET: 'TEMPERATURE_SHEET',
//会诊申请单
CONSULTATION: 'CONSULTATION',
};
@@ -182,20 +206,28 @@ export function getPrinterList() {
}
}
import useUserStore from '@/store/modules/user';
import {ElMessage} from 'element-plus';
/**
* 获取当前登录用户ID
* @returns {string} 用户ID
*/
function getCurrentUserId() {
try {
// 从Pinia store中获取当前用户ID
const userStore = useUserStore();
return userStore.id || '';
} catch (e) {
console.error('获取用户ID失败:', e);
return '';
}
}
/**
* 生成打印机缓存键
* @param {string} businessName 打印业务名称
* @returns {string} 缓存键
*/
function getPrinterCacheKey(businessName) {
const userId = getCurrentUserId();
@@ -204,6 +236,8 @@ function getPrinterCacheKey(businessName) {
/**
* 从缓存获取上次选择的打印机
* @param {string} businessName 打印业务名称
* @returns {string} 打印机名称
*/
export function getCachedPrinter(businessName = 'default') {
const cacheKey = getPrinterCacheKey(businessName);
@@ -212,6 +246,8 @@ export function getCachedPrinter(businessName = 'default') {
/**
* 保存打印机选择到缓存
* @param {string} printerName 打印机名称
* @param {string} businessName 打印业务名称
*/
export function savePrinterToCache(printerName, businessName = 'default') {
if (printerName) {
@@ -221,7 +257,13 @@ export function savePrinterToCache(printerName, businessName = 'default') {
}
/**
* 执行打印操作 (带自动二维码生成逻辑)
* 执行打印操作
* @param {Array} data 打印数据
* @param {Object} template 打印模板
* @param {string} printerName 打印机名称(可选)
* @param {Object} options 打印选项(可选)
* @param {string} businessName 打印业务名称(可选)
* @returns {Promise} 打印结果Promise
*/
export function executePrint(data, template, printerName, options = {}, businessName = 'default') {
return new Promise((resolve, reject) => {
@@ -286,6 +328,12 @@ export function executePrint(data, template, printerName, options = {}, business
/**
* 选择打印机并执行打印
* @param {Array} data 打印数据
* @param {Object} template 打印模板
* @param {Function} showPrinterDialog 显示打印机选择对话框的函数
* @param {Object} modal 消息提示对象
* @param {Function} callback 打印完成后的回调函数
* @param {string} businessName 打印业务名称(可选)
*/
export async function selectPrinterAndPrint(
data,
@@ -296,24 +344,29 @@ export async function selectPrinterAndPrint(
businessName = 'default'
) {
try {
// 获取打印机列表
const printerList = getPrinterList();
if (printerList.length === 0) {
modal.msgWarning('未检测到可用打印机');
return;
}
// 获取缓存的打印机
const cachedPrinter = getCachedPrinter(businessName);
let selectedPrinter = '';
// 判断打印机选择逻辑
if (printerList.length === 1) {
selectedPrinter = printerList[0].name;
await executePrint(data, template, selectedPrinter, {}, businessName);
if (callback) callback();
} else if (cachedPrinter && printerList.some((p) => p.name === cachedPrinter)) {
} else if (cachedPrinter && printerList.some((printer) => printer.name === cachedPrinter)) {
selectedPrinter = cachedPrinter;
await executePrint(data, template, selectedPrinter, {}, businessName);
if (callback) callback();
} else {
// 调用显示打印机选择对话框的函数
showPrinterDialog(printerList, async (chosenPrinter) => {
try {
await executePrint(data, template, chosenPrinter, {}, businessName);
@@ -324,40 +377,375 @@ export async function selectPrinterAndPrint(
});
}
} catch (error) {
modal.msgError(error.message || '获取打印机失败');
modal.msgError(error.message || '获取打印机列表失败');
}
}
/**
* 预览打印
*/
// 预览打印
export function previewPrint(elementDom) {
if (elementDom) {
// 初始化已在 main.js 中完成,无需重复调用
// hiprint.init();
const hiprintTemplate = new hiprint.PrintTemplate();
// printByHtml为预览打印
hiprintTemplate.printByHtml(elementDom, {});
} else {
ElMessage.error('加载模版失败');
ElMessage({
type: 'error',
message: '加载模版失败',
});
}
}
/**
* 打印门诊挂号收据
* 打印门诊挂号收据(使用浏览器打印,模板与补打挂号一致)
* @param {Object} data 打印数据
* @param {Object} options 打印选项
* @returns {Promise} 打印结果 Promise
*/
export function printRegistrationReceipt(data, options = {}) {
// 此处保持原有的 HTML 拼接逻辑(略)
return Promise.resolve({ success: true, message: '打印窗口已打开' });
return new Promise((resolve, reject) => {
try {
// 构建打印内容的 HTML
const printContent = `
<div class="print-header">
<div class="header-content">
<div class="document-title">门诊预约挂号凭条</div>
<div class="print-time">打印时间:${data.printTime || new Date().toLocaleString()}</div>
</div>
</div>
<div class="print-section">
<div class="section-title">患者基本信息</div>
<div class="info-row">
<span class="label">患者姓名:</span>
<span class="value">${data.patientName || '-'}</span>
</div>
<div class="info-row">
<span class="label">就诊卡号:</span>
<span class="value">${data.cardNo || data.busNo || '-'}</span>
</div>
<div class="info-row">
<span class="label">身份证号:</span>
<span class="value">${data.idCard ? maskIdCard(data.idCard) : '-'}</span>
</div>
<div class="info-row">
<span class="label">联系电话:</span>
<span class="value">${data.phone || '-'}</span>
</div>
</div>
<div class="print-section">
<div class="section-title">挂号信息</div>
<div class="info-row">
<span class="label">就诊科室:</span>
<span class="value">${data.organizationName || '-'}</span>
</div>
<div class="info-row">
<span class="label">医生姓名:</span>
<span class="value">${data.practitionerName || '-'}</span>
</div>
<div class="info-row">
<span class="label">挂号类型:</span>
<span class="value">${data.healthcareName || '-'}</span>
</div>
<div class="info-row">
<span class="label">挂号时间:</span>
<span class="value">${data.visitTime || data.chargeTime || '-'}</span>
</div>
</div>
<div class="print-section">
<div class="section-title">费用信息</div>
<table class="fee-table">
<thead>
<tr>
<th>项目</th>
<th>数量</th>
<th>单价</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<tr>
<td>挂号费</td>
<td>1</td>
<td>¥${parseFloat(data.price || 0).toFixed(2)}</td>
<td>¥${parseFloat(data.price || 0).toFixed(2)}</td>
</tr>
${parseFloat(data.activityPrice || 0) > 0 ? `
<tr>
<td>诊疗费</td>
<td>1</td>
<td>¥${parseFloat(data.activityPrice || 0).toFixed(2)}</td>
<td>¥${parseFloat(data.activityPrice || 0).toFixed(2)}</td>
</tr>` : ''}
${parseFloat(data.medicalRecordFee || 0) > 0 ? `
<tr>
<td>病历费</td>
<td>1</td>
<td>¥${parseFloat(data.medicalRecordFee || 0).toFixed(2)}</td>
<td>¥${parseFloat(data.medicalRecordFee || 0).toFixed(2)}</td>
</tr>` : ''}
</tbody>
<tfoot>
<tr>
<td colspan="3" class="total-label">合计:</td>
<td class="total-value">¥${parseFloat(data.totalPrice || data.amount || 0).toFixed(2)}</td>
</tr>
</tfoot>
</table>
</div>
<!-- 流水号显示在左下角 -->
<div class="serial-number-bottom-left">
<span class="serial-label">流水号:</span>
<span class="serial-value">${data.serialNo || data.encounterId || '-'}</span>
</div>
<div class="print-footer">
<div class="reminder">温馨提示:请妥善保管此凭条,就诊时请携带。</div>
</div>
<!-- 二维码区域 -->
<div class="qr-code-section">
<div class="qr-code-container">
<div id="qrcode-print" class="qrcode-print"></div>
<div class="qr-code-label">扫码查看挂号信息</div>
</div>
</div>
`;
// 创建新窗口用于打印
const printWindow = window.open('', '_blank');
if (!printWindow) {
reject(new Error('无法打开打印窗口,请检查浏览器弹窗设置'));
return;
}
// 写入打印内容
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>门诊预约挂号凭条</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; margin: 0; }
.print-header { margin-bottom: 20px; position: relative; }
.header-content { text-align: center; }
.document-title { font-size: 16px; font-weight: bold; margin-bottom: 10px; }
.print-time { font-size: 12px; color: #666; text-align: right; }
.print-section { margin-bottom: 20px; }
.section-title { font-size: 14px; font-weight: bold; margin-bottom: 10px; border-bottom: 1px solid #ddd; padding-bottom: 5px; }
.info-row { margin-bottom: 8px; font-size: 13px; }
.label { display: inline-block; width: 100px; font-weight: bold; }
.value { display: inline-block; }
.fee-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.fee-table th, .fee-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.fee-table th { background-color: #f5f5f5; font-weight: bold; }
.total-label { font-weight: bold; text-align: right; }
.total-value { font-weight: bold; color: red; }
.serial-number-bottom-left { position: absolute; bottom: 20px; left: 20px; font-size: 14px; font-weight: bold; }
.serial-number-bottom-left .serial-label { font-weight: bold; margin-right: 5px; }
.serial-number-bottom-left .serial-value { font-weight: bold; color: #333; }
.print-content { position: relative; min-height: 500px; padding-bottom: 60px; }
.print-footer { margin-top: 20px; font-size: 12px; color: #666; }
.reminder { text-align: center; padding: 10px; background-color: #f9f9f9; border-radius: 4px; }
.qr-code-section { margin-top: 20px; display: flex; justify-content: center; align-items: center; padding: 15px; }
.qr-code-container { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.qrcode-print { width: 120px; height: 120px; }
.qr-code-label { font-size: 12px; color: #666; text-align: center; }
@media print { body { padding: 0; } .qr-code-section { page-break-inside: avoid; } }
</style>
</head>
<body>
<div class="print-content">
${printContent}
</div>
</body>
</html>
`);
printWindow.document.close();
printWindow.onload = function() {
setTimeout(() => {
printWindow.print();
resolve({ success: true, message: '打印窗口已打开' });
}, 250);
};
} catch (error) {
console.error('打印门诊挂号收据失败:', error);
reject(error);
}
});
}
/**
* 脱敏身份证号
* @param {string} idCard 身份证号
* @returns {string} 脱敏后的身份证号
*/
function maskIdCard(idCard) {
if (!idCard) return '';
if (idCard.length >= 10) {
const prefix = idCard.substring(0, 6);
const suffix = idCard.substring(idCard.length - 4);
const stars = '*'.repeat(Math.max(0, idCard.length - 10));
return prefix + stars + suffix;
} else if (idCard.length >= 6) {
const prefix = idCard.substring(0, 3);
const suffix = idCard.substring(idCard.length - 1);
return prefix + '*'.repeat(idCard.length - 4) + suffix;
}
return idCard;
}
/**
* 打印入院证
* @param {Object} data 入院证数据
* @returns {Promise}
*/
export function printAdmissionCertificate(data) {
// 此处保持原有的 HTML 拼接逻辑(略)
return Promise.resolve({ success: true, message: '打印窗口已打开' });
return new Promise((resolve, reject) => {
try {
const printContent = `
<div class="certificate">
<div class="title">${data.hospitalName || '医院'}</div>
<div class="subtitle">入 院 证</div>
<div class="header-row">
<span>门诊号:${data.outpatientNo || '—'}</span>
<span>住院号:${data.inpatientNo || '—'}</span>
</div>
<div class="info-grid">
<div class="info-item"><span class="label">姓名:</span><span class="value">${data.patientName || '—'}</span></div>
<div class="info-item"><span class="label">性别:</span><span class="value">${data.gender || '—'}</span></div>
<div class="info-item"><span class="label">年龄:</span><span class="value">${data.age || '—'}</span></div>
<div class="info-item"><span class="label">费用类型:</span><span class="value">${data.feeType || '—'}</span></div>
</div>
<div class="info-grid">
<div class="info-item"><span class="label">身份证号:</span><span class="value">${data.idCard || '—'}</span></div>
<div class="info-item"><span class="label">电话:</span><span class="value">${data.phone || '—'}</span></div>
</div>
<div class="info-grid">
<div class="info-item full-width"><span class="label">住址:</span><span class="value">${data.address || '—'}</span></div>
</div>
<div class="info-grid">
<div class="info-item full-width"><span class="label">联系人:</span><span class="value">${data.contactPerson || '—'}${data.contactRelation ? '' + data.contactRelation + '' : ''} ${data.contactPhone || ''}</span></div>
</div>
<div class="divider"></div>
<div class="info-grid three-col">
<div class="info-item"><span class="label">入院科室:</span><span class="value">${data.department || '—'}</span></div>
<div class="info-item"><span class="label">入院类型:</span><span class="value">${data.admissionType || '—'}</span></div>
<div class="info-item"><span class="label">入院病区:</span><span class="value">${data.ward || '—'}</span></div>
</div>
<div class="info-grid three-col">
<div class="info-item"><span class="label">入院方式:</span><span class="value">${data.admissionMethod || '—'}</span></div>
<div class="info-item"><span class="label">患者病情:</span><span class="value">${data.patientCondition || '—'}</span></div>
</div>
<div class="divider"></div>
<div class="diagnosis-section">
<div class="label">入院诊断:</div>
<div class="diagnosis-item">1. ${data.westernDiagnosis || '—'}(西医)</div>
<div class="diagnosis-item">2. ${data.tcmDiagnosis || '—'}(中医)</div>
</div>
<div class="divider"></div>
<div class="info-grid">
<div class="info-item"><span class="label">开单医生:</span><span class="value">${data.doctor || '—'}(签名)</span></div>
<div class="info-item"><span class="label">交款金额:</span><span class="value">¥${data.paymentAmount || '0.00'} 元(盖章有效)</span></div>
</div>
<div class="info-grid">
<div class="info-item"><span class="label">申请日期:</span><span class="value">${data.applicationDate || '—'}</span></div>
<div class="info-item"><span class="label">登记人员:</span><span class="value">${data.registrar || '—'}(签章)</span></div>
</div>
<div class="divider"></div>
<div class="reminder">
<div class="reminder-title">【温馨提示】</div>
<div class="reminder-item">1. 医保患者请于24小时内持医保卡至住院窗口办理联网逾期无法报销。</div>
<div class="reminder-item">2. 住院期间请勿随身携带贵重物品,请携带必要洗漱用具。</div>
<div class="reminder-item">3. 本证3日内有效。</div>
</div>
</div>
`;
const printWindow = window.open('', '_blank');
if (!printWindow) {
reject(new Error('无法打开打印窗口,请检查浏览器弹窗设置'));
return;
}
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>入院证</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: SimSun, '宋体', serif; padding: 20px; color: #000; }
.certificate { max-width: 700px; margin: 0 auto; }
.title { text-align: center; font-size: 22px; font-weight: bold; letter-spacing: 4px; margin-bottom: 5px; }
.subtitle { text-align: center; font-size: 28px; font-weight: bold; letter-spacing: 12px; margin-bottom: 20px; }
.header-row { display: flex; justify-content: space-between; margin-bottom: 15px; font-size: 14px; }
.info-grid { display: flex; flex-wrap: wrap; margin-bottom: 10px; }
.info-grid.three-col .info-item { width: 33.33%; }
.info-item { width: 50%; margin-bottom: 8px; font-size: 14px; }
.info-item.full-width { width: 100%; }
.info-item .label { font-weight: bold; }
.info-item .value { }
.divider { border-bottom: 1px dashed #999; margin: 12px 0; }
.diagnosis-section { margin-bottom: 10px; font-size: 14px; }
.diagnosis-section .label { font-weight: bold; margin-bottom: 5px; }
.diagnosis-item { margin-left: 20px; margin-bottom: 5px; }
.reminder { margin-top: 15px; font-size: 13px; }
.reminder-title { font-weight: bold; margin-bottom: 5px; }
.reminder-item { margin-bottom: 3px; }
@media print {
body { padding: 0; }
@page { size: A4; margin: 20mm; }
}
</style>
</head>
<body>
${printContent}
</body>
</html>
`);
printWindow.document.close();
printWindow.onload = function () {
setTimeout(() => {
printWindow.print();
resolve({ success: true, message: '打印窗口已打开' });
}, 300);
};
} catch (error) {
console.error('打印入院证失败:', error);
reject(error);
}
});
}
/**
* 格式化日期
* @param {string|Date} date 日期
* @returns {string} 格式化后的日期字符串
*/
export function formatDate(date) {
if (!date) return '';
@@ -372,7 +760,7 @@ export function formatDate(date) {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 默认导出
// 默认导出简化的打印方法
export default {
print: simplePrint,
printWithDialog: simplePrintWithDialog,

View File

@@ -487,6 +487,33 @@ function getList() {
return;
}
// 格式化工具:将后端返回的日期字符串转为可读格式
// 当时间为 00:00:00 时(历史数据 date 列迁移),只显示日期部分
function formatDisplayDate(val) {
if (!val) return '';
const pad = (n) => String(n).padStart(2, '0');
function formatFull(d) {
const datePart = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const h = d.getHours(), m = d.getMinutes(), s = d.getSeconds();
if (h === 0 && m === 0 && s === 0) return datePart;
return `${datePart} ${pad(h)}:${pad(m)}:${pad(s)}`;
}
if (typeof val === 'string') {
if (val.includes('T')) {
const d = new Date(val);
if (!isNaN(d.getTime())) return formatFull(d);
}
// yyyy-MM-dd HH:mm:ss 或 yyyy/MM/dd HH:mm:ss
const m = val.match(/^(\d{4}[-/]\d{1,2}[-/]\d{1,2})(?:\s+(\d{1,2}:\d{2}(?::\d{2})?))?/);
if (m) {
if (m[2] && m[2] !== '00:00:00' && m[2] !== '00:00') return val;
return m[1];
}
}
if (val instanceof Date && !isNaN(val.getTime())) return formatFull(val);
return val;
}
// 先加载西医诊断,再加载中医诊断(避免竞态覆盖)
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
if (res.code == 200) {
@@ -502,6 +529,7 @@ function getList() {
syndromeDefinitionId: '',
syndromeGroupNo: '',
showPopover: false,
diagnosisTime: formatDisplayDate(item.diagnosisTime),
};
if (obj.diagSrtNo == null) {
obj.diagSrtNo = 1;
@@ -540,11 +568,16 @@ function getList() {
iptDiseTypeCode: item.iptDiseTypeCode,
showPopover: false,
diagnosisDoctor: item.diagnosisDoctor || props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: item.diagnosisTime || new Date().toLocaleString('zh-CN')
diagnosisTime: formatDisplayDate(item.diagnosisTime) || new Date().toLocaleString('zh-CN')
});
});
// 将新数据添加到现有列表现有列表
// 先移除已有的中医诊断防止重复追加getList 可能被多次调用)
form.value.diagnosisList = form.value.diagnosisList.filter(
(item) => item.diagnosisSystem !== '中医'
);
// 将新数据添加到现有列表
form.value.diagnosisList.push(...newList);
// 重新排序整个列表
@@ -899,80 +932,109 @@ function handleSaveDiagnosis() {
item.diagSrtNo = index + 1;
});
// 日期格式化工具:将 Date 对象或 ISO 字符串转为后端期望的 yyyy/M/d HH:mm:ss 格式
function formatDateForBackend(val) {
if (!val) return '';
if (typeof val === 'string') {
// 已经是字符串且符合 yyyy/M/d HH:mm:ss 格式则直接返回
if (/^\d{4}\/\d{1,2}\/\d{1,2} \d{1,2}:\d{2}:\d{2}$/.test(val)) return val;
// 已经是 yyyy-MM-dd HH:mm:ss 格式则转换为 yyyy/M/d HH:mm:ss
const m = val.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}:\d{2})$/);
if (m) return `${m[1]}/${parseInt(m[2])}/${parseInt(m[3])} ${m[4]}`;
// ISO 或其他格式则解析为 Date
val = new Date(val);
}
if (val instanceof Date && !isNaN(val.getTime())) {
const y = val.getFullYear();
const M = val.getMonth() + 1;
const d = val.getDate();
const hh = String(val.getHours()).padStart(2, '0');
const mm = String(val.getMinutes()).padStart(2, '0');
const ss = String(val.getSeconds()).padStart(2, '0');
return `${y}/${M}/${d} ${hh}:${mm}:${ss}`;
}
return '';
}
// 步骤3拆分为西医诊断和中医诊断
const westernList = sortedList.filter((item) => item.diagnosisSystem !== '中医');
const tcmList = sortedList.filter((item) => item.diagnosisSystem === '中医');
const savePromises = [];
// 顺序执行保存,避免并行竞态:
// saveDoctorDiagnosis西医会 deleteEncounterDiagnosisInfos 删除全部诊断,
// saveTcmDiagnosis中医只追加不删除必须等西医保存完成后再保存中医
(async () => {
try {
// 先保存西医诊断(会先清空再插入)
if (westernList.length > 0) {
await saveDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: westernList.map(item => ({
...item,
diagnosisTime: formatDateForBackend(item.diagnosisTime),
onsetDate: formatDateForBackend(item.onsetDate),
})),
});
}
// 保存西医诊断
if (westernList.length > 0) {
savePromises.push(
saveDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: westernList,
})
);
}
// 再逐个保存医诊断(只追加,不清空)
for (const item of tcmList) {
const syndromeGroupNo = item.conditionId
? `${item.conditionId}-${item.tcmSyndromeCode || Date.now()}`
: `${Date.now()}-${item.tcmSyndromeCode || '0'}`;
await saveTcmDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: [
// 病illness
{
conditionId: item.conditionId || null,
name: item.name,
ybNo: item.ybNo,
definitionId: item.definitionId || null,
diagSrtNo: item.diagSrtNo,
medTypeCode: item.medTypeCode,
maindiseFlag: item.maindiseFlag,
verificationStatusEnum: item.verificationStatusEnum,
diagnosisDesc: item.diagnosisDesc || '',
diagnosisDoctor: item.diagnosisDoctor || '',
diagnosisTime: formatDateForBackend(item.diagnosisTime),
iptDiseTypeCode: item.iptDiseTypeCode,
syndromeGroupNo: syndromeGroupNo,
},
// 证syndrome
{
name: item.tcmSyndromeName,
ybNo: item.tcmSyndromeCode,
definitionId: item.syndromeDefinitionId || null,
diagSrtNo: null,
syndromeGroupNo: syndromeGroupNo,
},
],
});
}
// 保存中医诊断
tcmList.forEach((item) => {
const syndromeGroupNo = item.conditionId
? `${item.conditionId}-${item.tcmSyndromeCode || Date.now()}`
: `${Date.now()}-${item.tcmSyndromeCode || '0'}`;
savePromises.push(
saveTcmDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: [
// 病illness
{
conditionId: item.conditionId || null,
name: item.name,
ybNo: item.ybNo,
definitionId: item.definitionId || null,
diagSrtNo: item.diagSrtNo,
medTypeCode: item.medTypeCode,
maindiseFlag: item.maindiseFlag,
verificationStatusEnum: item.verificationStatusEnum,
diagnosisDesc: item.diagnosisDesc || '',
diagnosisDoctor: item.diagnosisDoctor || '',
diagnosisTime: item.diagnosisTime || '',
iptDiseTypeCode: item.iptDiseTypeCode,
syndromeGroupNo: syndromeGroupNo,
},
// 证syndrome
{
name: item.tcmSyndromeName,
ybNo: item.tcmSyndromeCode,
definitionId: item.syndromeDefinitionId || null,
diagSrtNo: null,
syndromeGroupNo: syndromeGroupNo,
},
],
})
);
});
// 所有保存完成后刷新
emits('diagnosisSave', false);
proxy.$modal.msgSuccess('诊断已保存');
getList();
Promise.all(savePromises).then(() => {
emits('diagnosisSave', false);
proxy.$modal.msgSuccess('诊断已保存');
// 保存成功后从服务器重新加载数据,确保前后端数据一致
getList();
// 食源性疾病逻辑
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
// 食源性疾病逻辑
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
if (res2.code === 20 && res2.data) {
window.open(res2.data, '_blank');
}
});
}).finally(() => {
setTimeout(() => {
isSaving.value = false;
}, 100);
});
} catch (e) {
console.error('保存诊断失败', e);
proxy.$modal.msgError('保存诊断失败');
} finally {
setTimeout(() => {
isSaving.value = false;
}, 100);
}
})();
}
});
}

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="inpatientDoctor-order-container" style="width: 100%">
<div style="margin-bottom: 5px" class="order-operate-btn">
<div style="height: 44px; display: flex; align-items: center; flex: none">
@@ -216,10 +216,14 @@
/>
</el-select>
<el-popover
:popper-style="{ padding: '0' }"
:popper-style="advicePopperStyle"
placement="bottom-start"
popper-class="order-advice-popper"
:offset="0"
:visible="scope.row.showPopover"
:width="1200"
:teleported="true"
:popper-options="advicePopperOptions"
>
<adviceBaseList
ref="adviceTableRef"
@@ -307,15 +311,17 @@
</vxe-column>
<vxe-column title="频次/用法" align="center" field="" width="180">
<template #default="scope">
<span v-if="!scope.row.isEdit && scope.row.adviceType == 1" style="text-align: right">
<span v-if="!scope.row.isEdit && (scope.row.adviceType == 1 || scope.row.adviceType == 7 || scope.row.adviceType == 8)" style="text-align: right">
{{
[
scope.row.rateCode_dictText,
scope.row.dispensePerDuration ? scope.row.dispensePerDuration + '' : '',
scope.row.methodCode_dictText,
]
.filter(Boolean)
.join(' ')
scope.row.adviceType == 8
? (scope.row.rateCode_dictText || scope.row.rateCode || '-')
: [
scope.row.rateCode_dictText,
scope.row.dispensePerDuration ? scope.row.dispensePerDuration + '' : '',
scope.row.methodCode_dictText,
]
.filter(Boolean)
.join(' ')
}}
</span>
</template>
@@ -495,6 +501,24 @@ const loading = ref(false);
// Bug #587: 标记弹窗刚被关闭的行uniqueKey防止关闭弹窗时误触发行展开
const popoverJustClosedByKey = ref(null);
// 医嘱检索下拉浮框对齐:跟踪表格水平滚动偏移与主体区域边界限制
const tableScrollLeft = ref(0);
const mainBoundary = ref(null);
const advicePopperStyle = computed(() => ({
padding: '0',
marginLeft: `-${tableScrollLeft.value}px`,
}));
const advicePopperOptions = computed(() => ({
modifiers: [
{
name: 'preventOverflow',
options: {
boundary: mainBoundary.value || 'viewport',
},
},
],
}));
// 停嘱弹窗
const stopDialogVisible = ref(false);
const stopForm = reactive({
@@ -655,11 +679,28 @@ const statusOption = [
// loadingInstance removed - using loading ref instead
onMounted(() => {
document.addEventListener('keydown', escKeyListener);
// 监听表格水平滚动,同步更新医嘱检索下拉浮框的水平偏移
nextTick(() => {
const scrollWrapper = document.querySelector('.vxe-table--body-wrapper');
if (scrollWrapper) {
scrollWrapper.addEventListener('scroll', onTableScroll, { passive: true });
}
// 获取主体区域容器作为 Popper 边界,防止浮框向左移入患者列表
mainBoundary.value = document.querySelector('.inpatientDoctor-home-main');
});
});
onBeforeUnmount(() => {
document.removeEventListener('keydown', escKeyListener);
const scrollWrapper = document.querySelector('.vxe-table--body-wrapper');
if (scrollWrapper) {
scrollWrapper.removeEventListener('scroll', onTableScroll);
}
});
function onTableScroll(e) {
tableScrollLeft.value = e.target.scrollLeft || 0;
}
watch(
() => expandOrder.value,
(newValue) => {
@@ -758,6 +799,9 @@ function getListInfo(addNewRow) {
// 🔧 修复:同时保存 orgName当 orgId 在科室树中匹配不到时作为兜底显示
// 优先从科室树查找名称,其次用 positionName后端已保存的科室名最后用 contentJson 中的 orgName
orgName: findOrgName(item.positionId || parsedContent?.orgId || item.orgId) || item.positionName || parsedContent?.orgName || undefined,
// 确保文字医嘱的 rateCode / rateCode_dictText 不被 item 中的 null/undefined 覆盖
rateCode: item.rateCode || parsedContent?.rateCode || undefined,
rateCode_dictText: item.rateCode_dictText || parsedContent?.rateCode_dictText || undefined,
// Bug #589: 从contentJson检测出院带药标记恢复类型显示
// 后端存储时adviceType转为1药品通过prescriptionCategory=3标识出院带药
...(parsedContent?.prescriptionCategory == 3 ? {
@@ -991,6 +1035,13 @@ function clickRowDb({ row, column, event }) {
if (row.statusEnum == 1) {
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
row.therapyEnum = String(row.therapyEnum ?? '1');
if (row.adviceType == 8) {
if (!row.orgId && userStore.orgId) {
row.orgId = userStore.orgId;
row.positionId = userStore.orgId;
row.orgName = userStore.orgName || findOrgName(userStore.orgId) || '';
}
}
row.isEdit = true;
const index = prescriptionList.value.findIndex((item) => item.uniqueKey === row.uniqueKey);
rowIndex.value = index;
@@ -1073,6 +1124,14 @@ function handleDiagnosisChange(item) {
function expandTextRow(rowIndex) {
const row = filterPrescriptionList.value[rowIndex];
if (!row) return;
// 自动获取当前用户所在的科室
if (!row.orgId && userStore.orgId) {
row.orgId = userStore.orgId;
row.positionId = userStore.orgId;
row.orgName = userStore.orgName || findOrgName(userStore.orgId) || '';
}
expandOrder.value = [row.uniqueKey];
nextTick(() => {
if (prescriptionRef.value?.setRowExpand) {
@@ -1083,6 +1142,11 @@ function expandTextRow(rowIndex) {
function handleFocus(row, index) {
rowIndex.value = index;
// 同步表格水平滚动偏移,确保浮框位置正确
const scrollWrapper = document.querySelector('.vxe-table--body-wrapper');
if (scrollWrapper) {
tableScrollLeft.value = scrollWrapper.scrollLeft || 0;
}
// 文字医嘱(type=8)不弹药品搜索框,直接展开填写面板
const adviceType = row.adviceType !== undefined ? row.adviceType : adviceQueryParams.value.adviceType;
if (adviceType == 8) {
@@ -2974,4 +3038,9 @@ defineExpose({ getListInfo, getDiagnosisInfo });
background: #fff;
}
}
// 医嘱检索下拉浮框teleported 到 body需用 :global 选择
:global(.order-advice-popper) {
transition: margin-left 0.05s ease-out;
}
</style>