Files
his/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/adviceBaseList.vue
zhaoyun 3e7d27ee61 fix(#591): 请修复 Bug #591:【住院医生站-临床医嘱】长期医嘱点击停嘱未弹出时间录入弹窗
根因:
- Bug #请修复 Bug #591 存在的问题

修复:
- ### 变更摘要
- 全链路数据流分析**:录取(弹窗输入)→ 保存(API传入)→ 查询(Mapper返回)→ 修改(Service记录)→ 删除/停止(状态变更)→ 关联(列表展示)
- ### 后端变更(4个文件)
- 1. `AdviceBatchOpParam.java`** — 停嘱参数添加 `stopTime` 字段
- 新增 `@JsonFormat Date stopTime`,支持前端传入停嘱时间
- 2. `RequestBaseDto.java`** — 查询DTO添加 `stopUserName`、`stopTime` 字段
- 新增 `String stopUserName`(停嘱医生姓名)
- 新增 `Date stopTime`(停嘱时间)
- 3. `AdviceManageAppServiceImpl.java`** — 停嘱Service增强
- 优先使用前端传入的 `stopTime`,兜底用当前时间
- 通过 `SecurityUtils.getNickName()` 获取当前操作用户昵称,记录到 `updateBy`
- 药品和诊疗两个更新入口均已同步修改
- 4. `AdviceManageAppMapper.xml`** — 三个UNION ALL子查询添加字段
- 药品子查询:`T1.effective_dose_end AS stop_time` + `T1.update_by AS stop_user_name`
- 耗材子查询:`NULL AS stop_time` + `'' AS stop_user_name`
- 诊疗子查询:`T1.occurrence_end_time AS stop_time` + `T1.update_by AS stop_user_name`
- ### 前端变更(1个文件)
- `order/index.vue`**:
- 1. **停嘱时间弹窗** — 点击「停嘱」后弹出 `el-dialog`,内含 `el-date-picker`(datetime类型,默认当前时间),确定后才调用API
- 2. **表格列** — 在「皮试」列后面、「诊断」列前面新增两列:
- 「停嘱医生」`prop="stopUserName"`,宽度120px
- 「停嘱时间」`prop="stopTime"`,宽度170px
- 3. **`handleStopAdvice`** — 保留原有校验(未保存/未签发/已停止检查),校验通过后弹出时间选择弹窗而非直接调API
- 4. **`confirmStopAdvice`** — 新增确认函数,将 `stopTime` 拼入请求参数后调用 `stopAdvice` API
- ### 验证结果
-  前端 Lint 检查通过(仅1个预存的 `vue/no-dupe-keys` 警告)
-  后端 Maven 编译通过(BUILD SUCCESS)
2026-05-29 00:39:28 +08:00

285 lines
7.8 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
ref="tableWrapper"
tabindex="0"
@keyup="handleKeyDown"
>
<el-table
ref="adviceBaseRef"
height="400"
:data="adviceBaseList"
highlight-current-row
row-key="patientId"
@current-change="handleCurrentChange"
@cell-click="clickRow"
@row-click="clickRow"
>
<el-table-column
label="名称"
align="center"
prop="adviceName"
/>
<el-table-column
label="类型"
align="center"
prop="activityType_dictText"
/>
<el-table-column
label="包装单位"
align="center"
prop="unitCode_dictText"
/>
<el-table-column
label="最小单位"
align="center"
prop="minUnitCode_dictText"
/>
<el-table-column
label="规格"
align="center"
prop="volume"
/>
<el-table-column
label="用法"
align="center"
prop="methodCode_dictText"
/>
<el-table-column
label="频次"
align="center"
prop="rateCode_dictText"
/>
<el-table-column
label="单次剂量"
align="center"
prop="dose"
/>
<el-table-column
label="剂量单位"
align="center"
prop="doseUnitCode_dictText"
/>
<el-table-column
label="注射药品"
align="center"
prop="injectFlag_enumText"
/>
<el-table-column
label="皮试"
align="center"
prop="skinTestFlag_enumText"
/>
</el-table>
</div>
</template>
<script setup>
import {nextTick} from 'vue';
import {getAdviceBaseInfo} from './api';
import {throttle} from 'lodash-es';
const props = defineProps({
adviceQueryParams: {
type: Object,
default: '',
},
patientInfo: {
type: Object,
required: true,
},
popoverVisible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['selectAdviceBase']);
const total = ref(0);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0); // 当前选中行索引
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 100,
pageNum: 1,
adviceType: undefined,
categoryCode: '',
});
const adviceBaseList = ref([]);
// 节流函数
const throttledGetList = throttle(
() => {
// 触发数据加载
getList();
},
300,
{ leading: true, trailing: true }
);
watch(
() => props.adviceQueryParams,
(newValue) => {
// 始终同步参数到 queryParams避免弹窗打开时使用旧参数
queryParams.value.searchKey = newValue?.searchKey;
queryParams.value.adviceType = newValue?.adviceType;
queryParams.value.categoryCode = newValue?.categoryCode;
// 只有在弹窗打开时才触发 API 请求
if (!props.popoverVisible) {
return;
}
throttledGetList();
},
{ deep: true }
);
// 监听弹窗打开状态,当弹窗打开时主动加载数据
watch(
() => props.popoverVisible,
(visible) => {
if (visible) {
// 弹窗打开时,确保 adviceQueryParams 同步到 queryParams
if (props.adviceQueryParams) {
queryParams.value.searchKey = props.adviceQueryParams.searchKey;
queryParams.value.adviceType = props.adviceQueryParams.adviceType;
queryParams.value.categoryCode = props.adviceQueryParams.categoryCode;
console.log('[adviceBaseList] 弹窗打开,参数:', JSON.stringify({
adviceType: queryParams.value.adviceType,
categoryCode: queryParams.value.categoryCode
}));
}
// 主动触发数据加载
getList();
} else {
// 弹窗关闭时,清空列表数据,避免显示错误的数据
adviceBaseList.value = [];
total.value = 0;
}
}
);
// 移除组件初始化时的 getList() 调用,避免在没有 adviceType 时查询所有类型的数据
// getList();
function getList() {
// 验证是否已选择患者
if (!props.patientInfo || Object.keys(props.patientInfo).length === 0) {
console.log('[adviceBaseList] getList() 跳过:未选择患者');
return; // 不执行API调用
}
// 只有在弹窗打开时才执行查询
if (!props.popoverVisible) {
console.log('[adviceBaseList] getList() 跳过:弹窗未打开');
return;
}
// 🔧 Bug #448 修复:显式构建请求参数,确保 adviceType 正确传递
// 不直接使用 queryParams.value避免 undefined 值被发送到后端导致过滤失效
const requestParams = {
pageSize: queryParams.value.pageSize,
pageNum: queryParams.value.pageNum,
organizationId: props.patientInfo.orgId,
};
// 只在 adviceType 有值时添加0 是无效值undefined/null 会导致后端查询所有类型)
if (queryParams.value.adviceType != null && queryParams.value.adviceType !== 0) {
requestParams.adviceType = queryParams.value.adviceType;
}
// 只在 categoryCode 有值时添加
if (queryParams.value.categoryCode) {
requestParams.categoryCode = queryParams.value.categoryCode;
}
// 只在 searchKey 有值时添加
if (queryParams.value.searchKey) {
requestParams.searchKey = queryParams.value.searchKey;
}
console.log('[adviceBaseList] getList() 请求参数:', JSON.stringify(requestParams));
getAdviceBaseInfo(requestParams).then((res) => {
console.log('[adviceBaseList] getList() 响应数据:', {
total: res.data?.total,
recordsCount: res.data?.records?.length || 0,
firstRecord: res.data?.records?.[0]?.adviceName || '无数据',
adviceType: requestParams.adviceType
});
adviceBaseList.value = res.data.records || [];
total.value = res.data.total || 0;
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0) {
adviceBaseRef.value?.setCurrentRow(adviceBaseList.value[0]);
}
});
}).catch((err) => {
console.error('[adviceBaseList] getList() 请求失败:', err);
adviceBaseList.value = [];
total.value = 0;
});
}
// 处理键盘事件
const handleKeyDown = (event) => {
const key = event.key;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp': // 上箭头
event.preventDefault(); // 阻止默认滚动行为
if (currentIndex.value > 0) {
currentIndex.value--;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown': // 下箭头`
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter': // 回车键
// const currentRow = adviceBaseRef.value.getSelectionRows();
event.preventDefault();
if (currentSelectRow.value) {
// 这里可以触发自定义逻辑,如弹窗、跳转等
emit('selectAdviceBase', currentSelectRow.value);
}
break;
}
};
// 设置选中行(带滚动)
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
// 滚动到选中行
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引
const handleCurrentChange = (currentRow) => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
function clickRow(row, column, cell, event) {
// cell-click 事件会传递 row, column, cell, event 四个参数
// 确保传递的是完整的行数据
if (row) {
emit('selectAdviceBase', row);
}
}
defineExpose({
handleKeyDown,
});
</script>
<style scoped>
.popover-table-wrapper:focus {
outline: 2px solid #409eff; /* 聚焦时的高亮效果 */
}
</style>