Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
2026-01-28 14:29:29 +08:00
42 changed files with 1389 additions and 563 deletions

View File

@@ -42,7 +42,6 @@ import {defineEmits, ref, unref} from 'vue';
import {deleteRecord, getRecordByEncounterIdList} from '../api';
import {ElMessage} from 'element-plus';
import {patientInfo} from '../../store/patient.js';
import apiRequestManager from '@/utils/apiRequestManager.js';
const emits = defineEmits(['historyClick']);
const props = defineProps({
@@ -68,30 +67,15 @@ const queryParams = ref({
isPage: 0,
});
const historyData = ref([]);
// 防止重复加载的标志
let isLoadingHistory = false;
const queryList = async () => {
// 防止重复加载
if (isLoadingHistory) {
console.log('History data is already loading, skipping duplicate call');
return;
}
isLoadingHistory = true;
try {
if (patientInfo.value.encounterId && unref(definitionId) && unref(definitionId) !== '') {
const res = await apiRequestManager.execute(
getRecordByEncounterIdList,
'/document/record/getRecordByEncounterIdList',
{
isPage: 0, // 确保参数一致,便于去重
encounterId: patientInfo.value.encounterId,
patientId: patientInfo.value.patientId,
definitionId: unref(definitionId),
}
);
const res = await getRecordByEncounterIdList({
...queryParams.value,
encounterId: patientInfo.value.encounterId,
patientId: patientInfo.value.patientId,
definitionId: unref(definitionId),
});
historyData.value = res.data || [];
} else {
historyData.value = [];
@@ -99,8 +83,6 @@ const queryList = async () => {
} catch (error) {
// ElMessage.error(' 获取模板树失败 ');
historyData.value = [];
} finally {
isLoadingHistory = false; // 重置加载标志
}
};
const handleNodeClick = (data) => {

View File

@@ -103,7 +103,6 @@ import dayjs from 'dayjs';
// 打印工具
import {PRINT_TEMPLATE, simplePrint} from '@/utils/printUtils.js';
import {getEncounterDiagnosis} from '../api';
import apiRequestManager from '@/utils/apiRequestManager.js';
import History from './components/history';
import Template from './components/template';
import TemplateEdit from './components/templateEdit.vue';
@@ -206,7 +205,7 @@ const handleNodeClick = (data, node) => {
// 选择任何病历模板后,都加载该病历类型的最新历史记录
if (node.isLeaf && props.patientInfo && props.patientInfo.patientId) {
debouncedLoadLatestMedicalRecord();
loadLatestMedicalRecord();
}
}, 100);
});
@@ -280,7 +279,7 @@ const handleSubmitOk = async (data) => {
// 等待历史记录列表更新后,重新加载最新病历并更新选中状态
setTimeout(() => {
debouncedLoadLatestMedicalRecord();
loadLatestMedicalRecord();
}, 100);
} catch (error) {
ElMessage.error('提交失败');
@@ -411,7 +410,7 @@ const selectOutpatientMedicalRecordTemplate = async () => {
// 等待模板加载完成,然后获取并回显最新病历数据
setTimeout(() => {
historyRef.value?.queryList();
debouncedLoadLatestMedicalRecord();
loadLatestMedicalRecord();
}, 500);
});
} else {
@@ -422,36 +421,19 @@ const selectOutpatientMedicalRecordTemplate = async () => {
// 当前选中的历史病历ID用于在History组件中高亮显示
const selectedHistoryRecordId = ref('');
import { debounce } from 'lodash-es';
// 防止重复加载的标志
let isLoadingLatestRecord = false;
// 加载最新的病历数据并回显
const loadLatestMedicalRecord = async () => {
if (!patientInfo.value.encounterId || !currentSelectTemplate.value.id) return;
// 防止重复加载
if (isLoadingLatestRecord) {
console.log('Latest medical record is already loading, skipping duplicate call');
return;
}
isLoadingLatestRecord = true;
loading.value = true;
try {
// 获取患者的历史病历记录
const res = await apiRequestManager.execute(
getRecordByEncounterIdList,
'/document/record/getRecordByEncounterIdList',
{
isPage: 0,
encounterId: patientInfo.value.encounterId,
patientId: patientInfo.value.patientId,
definitionId: currentSelectTemplate.value.id,
}
);
const res = await getRecordByEncounterIdList({
isPage: 0,
encounterId: patientInfo.value.encounterId,
patientId: patientInfo.value.patientId,
definitionId: currentSelectTemplate.value.id,
});
const historyRecords = res.data || [];
if (historyRecords.length > 0) {
@@ -537,12 +519,8 @@ const loadLatestMedicalRecord = async () => {
});
} finally {
loading.value = false;
isLoadingLatestRecord = false; // 重置加载标志
}
};
// 防抖版本的加载最新病历数据函数
const debouncedLoadLatestMedicalRecord = debounce(loadLatestMedicalRecord, 300);
const templateRef = ref(null);
const handleTemplateClick = (data) => {
@@ -772,7 +750,7 @@ const selectDefaultTemplate = () => {
// 直接加载最新病历数据不再使用额外的setTimeout延迟
// 因为handleNodeClick中已经有nextTick和setTimeout处理组件渲染
debouncedLoadLatestMedicalRecord();
loadLatestMedicalRecord();
});
} else {
console.log('未找到门诊病历模板');

View File

@@ -59,8 +59,8 @@
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template #default="scope">
<el-button type="text" size="small" @click="handlePrint(scope.row)">打印</el-button>
<el-button type="text" size="small" style="color: #f56c6c" @click="handleDelete(scope.row)">删除</el-button>
<el-button link size="small" @click="handlePrint(scope.row)">打印</el-button>
<el-button link size="small" style="color: #f56c6c" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -379,7 +379,7 @@
<!-- 标题栏 -->
<div class="selected-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee;">
<span style="font-weight: bold; color: #1a2b6d">已选择</span>
<el-button type="text" @click="clearAllSelected" style="color: #f56c6c">清空</el-button>
<el-button link @click="clearAllSelected" style="color: #f56c6c">清空</el-button>
</div>
<!-- 已选项目列表 -->
@@ -393,7 +393,7 @@
<span class="item-itemName">{{ item.itemName }}</span>
<span class="item-price">¥{{ item.itemPrice }}</span>
<el-button
type="text"
link
size="small"
style="color: #f56c6c; margin-left: auto"
@click="removeInspectionItem(item)"

View File

@@ -268,14 +268,15 @@ const validatePhraseName = (phraseName, excludeId = null) => {
// 所有数据(用于客户端分页处理)
const allData = ref([])
// 获取医生常用语列表数据
// 获取医生常用语列表数据
const fetchDoctorPhraseList = async () => {
try {
const response = await getDoctorPhraseList()
// 处理后端返回的数据结构data.data
if (response.code === 200 && response.data && response.data.data) {
// 【关键修改】去掉 response.data.data直接取 response.data
if (response.code === 200 && response.data) {
// 按照sortNo由小到大排序保证列表顺序正确
allData.value = response.data.data.sort((a, b) => a.sortNo - b.sortNo)
allData.value = response.data.sort((a, b) => a.sortNo - b.sortNo)
total.value = allData.value.length
// 执行客户端分页逻辑
applyPagination()
@@ -285,7 +286,7 @@ const fetchDoctorPhraseList = async () => {
total.value = 0
}
} catch (error) {
console.error('获取列表失败:', error) // 增加控制台日志便于调试
console.error('获取列表失败:', error)
ElMessage.error('获取数据失败: 网络请求错误')
allData.value = []
total.value = 0
@@ -322,19 +323,18 @@ const handleCurrentChange = (val) => {
applyPagination()
}
// 搜索功能核心方法
// 搜索功能核心方法
const handleSearch = async () => {
try {
// searchScope可能是null未选择、1=个人2=科室3=全院
const phraseType = searchScope.value === null ? undefined : searchScope.value
// 调用搜索接口phraseName, phraseType
const response = await searchDoctorPhraseList(searchKeyword.value, phraseType)
if (response.code === 200 && response.data && response.data.data) {
// 按照sortNo由小到大排序
allData.value = response.data.data.sort((a, b) => a.sortNo - b.sortNo)
// 【关键修改】去掉 response.data.data直接取 response.data
if (response.code === 200 && response.data) {
allData.value = response.data.sort((a, b) => a.sortNo - b.sortNo)
total.value = allData.value.length
currentPage.value = 1 // 搜索后重置到第一页
applyPagination() // 应用分页
currentPage.value = 1
applyPagination()
} else {
ElMessage.error('搜索失败: ' + (response.msg || '未知错误'))
allData.value = []
@@ -349,20 +349,30 @@ const handleSearch = async () => {
}
// 打开新增模态框方法
// index.vue
const showAddDialog = () => {
// 重置表单数据
// 1. 算出当前最大的排序号
// 如果列表是空的,就从 1 开始;如果不空,取第一条(因为我们排过序了)或遍历找最大值
let maxSortNo = 0
if (allData.value && allData.value.length > 0) {
// 既然 allData 已经按 sortNo 排序了,那最后一个就是最大的?
// 或者保险起见,用 Math.max 算一下
maxSortNo = Math.max(...allData.value.map(item => item.sortNo || 0))
}
// 2. 重置表单,并将排序号设为 最大值 + 1
addForm.value = {
phraseName: '',
phraseContent: '',
sortNo: 1,
sortNo: maxSortNo + 1, // <--- 这样每次打开就是 2, 3, 4...
phraseType: 1,
phraseCategory: ''
}
// 重置表单验证状态
if (addFormRef.value) {
addFormRef.value.clearValidate()
}
// 打开模态框
addDialogVisible.value = true
}
@@ -434,7 +444,6 @@ const handleDelete = async (row) => {
// 用户取消删除时不提示错误
if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error('删除操作失败: 网络异常或权限不足')
}
}
}
@@ -455,39 +464,41 @@ const showEditDialog = (row) => {
}
// 编辑表单提交保存方法
// 修改 index.vue 中的 handleEditSave 方法
const handleEditSave = async () => {
try {
// 先执行表单验证
// 1. 表单校验
const validateResult = await editFormRef.value.validate()
if (!validateResult) return
// 名称唯一性校验排除当前编辑的这条记录ID
// 2. 名称唯一性校验
const nameValidation = validatePhraseName(editForm.value.phraseName, editForm.value.id)
if (!nameValidation.valid) {
ElMessage.error(nameValidation.message)
return
}
// 准备更新数据修复时间格式为ISO字符串适配后端LocalDateTime
// 3. 准备数据
const updateData = {
...editForm.value,
enableFlag: 1,
updateTime: new Date().toISOString() // 前端临时赋值,后端最终以自己的为准
updateTime: new Date().toISOString()
}
// 调用更新接口
// 4. 调用接口
const response = await updateDoctorPhrase(updateData)
// 【核心修改】直接判断 code === 200 即可
// 因为后端现在失败会返回 R.fail (code!=200),所以只要是 200 就是成功
if (response.code === 200) {
ElMessage.success('更新成功')
ElMessage.success(response.msg || '更新成功') // 优先显示后端返回的消息
editDialogVisible.value = false
// 重新拉取数据,保证列表数据最新
fetchDoctorPhraseList()
} else {
ElMessage.error('更新失败: ' + (response.msg || '未知错误'))
ElMessage.error(response.msg || '更新失败')
}
} catch (error) {
console.error('更新失败:', error)
ElMessage.error('更新操作失败: 网络请求错误')
}
}

View File

@@ -99,6 +99,10 @@
{{ userStore.nickName }}
</el-descriptions-item>
<el-descriptions-item label="" width="300">
<el-radio-group v-model="firstEnum">
<el-radio :label="1">初诊</el-radio>
<el-radio :label="2">复诊</el-radio>
</el-radio-group>
<el-button type="primary" plain @click.stop="handleFinish(patientInfo.encounterId)">
完诊
</el-button>
@@ -209,7 +213,6 @@ import useUserStore from '@/store/modules/user';
import { nextTick } from 'vue';
import { updatePatientInfo } from './components/store/patient.js';
import { ElMessage, ElMessageBox } from 'element-plus';
import { debounce } from 'lodash-es';
// // 监听路由离开事件
// onBeforeRouteLeave((to, from, next) => {
@@ -276,6 +279,7 @@ const loading = ref(false);
const { proxy } = getCurrentInstance();
const visitType = ref('');
const firstVisitDate = ref('');
const firstEnum = ref(1); // 初复诊标识1=初诊2=复诊
const disabled = computed(() => {
// 只有在有患者信息但某些条件不满足时才启用覆盖层
// 当前逻辑保持不变,但我们将在按钮级别处理禁用状态
@@ -488,8 +492,7 @@ function handleOpen() {
patientDrawerRef.value.refreshList();
}
// 原始的handleCardClick函数
function handleCardClickOriginal(item, index) {
function handleCardClick(item, index) {
console.log('handleCardClick 被调用');
console.log('点击的患者项目:', item);
console.log('患者项目中的encounterId:', item.encounterId);
@@ -506,6 +509,15 @@ function handleCardClickOriginal(item, index) {
console.log('patientInfo.value 设置为:', patientInfo.value);
console.log('patientInfo.value.encounterId:', patientInfo.value?.encounterId);
// 根据患者信息设置初复诊标识
const backendValue = item.firstEnum ?? item.first_enum;
if (backendValue !== undefined && backendValue !== null) {
firstEnum.value = Number(backendValue); // 确保是数字类型
} else {
firstEnum.value = 1;
}
// 确保患者信息包含必要的字段
if (!patientInfo.value.encounterId) {
console.error('患者信息缺少encounterId字段:', patientInfo.value);
@@ -546,9 +558,6 @@ function handleCardClickOriginal(item, index) {
});
}
// 使用防抖的handleCardClick函数防止短时间内多次点击
const handleCardClick = debounce(handleCardClickOriginal, 500);
function handleLeave(encounterId) {
leaveEncounter(encounterId).then((res) => {
if (res.code == 200) {
@@ -566,11 +575,18 @@ function handleFinish(encounterId) {
patientInfo.value = {};
visitType.value = ''; // 重置初复诊标识
visitTypeDisabled.value = false; // 重置禁用状态
firstEnum.value = 1; // 重置为初诊
getPatientList();
}
});
}
// 监听初复诊标识变化
watch(firstEnum, (newValue) => {
// 这里可以添加更新后端的逻辑,如果需要实时同步到后端
// 例如updateEncounterFirstEnum(patientInfo.value.encounterId, newValue)
});
function handleTimeChange(value) {
queryParams.value.registerTimeSTime = value + ' 00:00:00';
queryParams.value.registerTimeETime = value + ' 23:59:59';
@@ -594,7 +610,7 @@ function handleHospitalizationClick() {
// 接诊回调
function handleReceive(row) {
handleCardClickOriginal(row);
handleCardClick(row);
currentEncounterId.value = row.encounterId;
drawer.value = false;
getPatientList();
@@ -781,7 +797,7 @@ const markSeen = async () => {
currentCallPatient.value = {};
};
const callThis = (row) => {
handleCardClickOriginal(row);
handleCardClick(row);
currentCallPatient.value = row;
dialogVisible.value = false;
// 刷新患者列表和候诊列表