Compare commits
5 Commits
b3186158fe
...
eeb5af8fc1
| Author | SHA1 | Date | |
|---|---|---|---|
| eeb5af8fc1 | |||
| 56cd024949 | |||
| b37033d87e | |||
| 9e07546027 | |||
|
|
cb146ade45 |
32
BUG_522_ANALYSIS.md
Normal file
32
BUG_522_ANALYSIS.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Bug #522 分析报告
|
||||
|
||||
## Bug 描述
|
||||
[住院护士站-三测单] 体征录入点击保存后缺乏执行反馈且窗口异常自动关闭
|
||||
|
||||
## 涉及文件
|
||||
- 前端: `openhis-ui-vue3/src/views/inpatientNurse/tprChart/components/addTprDialog.vue`
|
||||
- API: `openhis-ui-vue3/src/views/inpatientNurse/tprChart/components/api.js`
|
||||
- 父组件: `openhis-ui-vue3/src/views/inpatientNurse/tprChart/index.vue`
|
||||
|
||||
## 根因分析
|
||||
|
||||
### 问题1:弹窗异常自动关闭 — 根因
|
||||
|
||||
在 `addTprDialog.vue` 模板中,保存按钮使用了 `:disabled="buttonDisabled"`(第50行和第108行),但 **`buttonDisabled` 变量在整个 script setup 中从未声明**。
|
||||
|
||||
在 Vue 3 `<script setup>` + Composition API 中,模板引用的变量必须在 script 中声明。未声明的变量会触发 `ReferenceError`,导致组件渲染失败或运行时异常。这个错误会破坏组件的响应式系统,使得 `dialogVisible` 的响应式绑定失效,从而导致弹窗在保存操作后异常关闭。
|
||||
|
||||
### 问题2:缺乏保存成功反馈 — 连带结果
|
||||
|
||||
虽然 `confirmCharge()` 函数在第1087行已有 `proxy.$modal.msgSuccess('保存成功')` 的调用,但由于 `buttonDisabled` 未声明引发的异常,导致代码执行路径被破坏,success 回调中的提示逻辑可能未能正常执行。
|
||||
|
||||
## 修复方案
|
||||
|
||||
1. **在 `addTprDialog.vue` 的 script setup 中新增 `buttonDisabled` ref 声明**,初始值为 `false`
|
||||
2. **在保存操作中添加 loading 状态**:点击保存后将按钮禁用,API 返回后恢复,防止重复提交的同时也保证了响应式状态的一致性
|
||||
|
||||
## 验收标准
|
||||
- [ ] 点击保存后弹窗保持开启状态
|
||||
- [ ] 保存成功后弹出"保存成功"提示
|
||||
- [ ] 左侧体征历史记录列表自动刷新
|
||||
- [ ] 录入区域表单被清空,方便继续录入下一条
|
||||
26
BUG_539_ANALYSIS.md
Normal file
26
BUG_539_ANALYSIS.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Bug #539 分析报告
|
||||
|
||||
## Bug 描述
|
||||
住院护士站点击后只有一个标签可见,缺少入出转管理、护理记录等功能模块。
|
||||
|
||||
## 根因分析
|
||||
|
||||
### 数据库菜单结构
|
||||
`hisdev.sys_menu` 中,住院护士站(menu_id=295)是一个目录类型(M),路径为 `statistics`,**没有设置 component 和 redirect**。
|
||||
|
||||
其下有 15 个子菜单(门户、入出转管理、护理记录、三测单等),都正确分配给了护士角色。
|
||||
|
||||
### 前端路由问题
|
||||
1. `inpatientNurseStation/index.vue` 包含 10 个 `el-tab-pane`(入出转管理、住院病历、医嘱校对、医嘱执行、护理记录、体温单、住院记账、汇总领药、科室耗材盘点、滚费配置),**但这个页面不在数据库菜单中!**
|
||||
|
||||
2. 菜单 296(门户)的 component 指向 `inpatientNurse/home/index`——这是一个**患者列表页面**,没有任何标签页。
|
||||
|
||||
3. 当护士登录后,点击"住院护士站"(目录类型 M),由于没有 redirect,系统默认打开第一个子菜单"门户",显示的是患者列表而非带标签的护士站主页。
|
||||
|
||||
4. `constants/navigation.js` 中的路径(如 `/inHospital/statistics/inOut`)与实际前端路由(`/inpatientNurse/inOut`)不匹配。
|
||||
|
||||
### 结论
|
||||
根本原因:`inpatientNurseStation/index.vue`(带 10 个功能标签的页面)未被纳入数据库菜单体系。护士实际打开的是 `home/index.vue`(患者列表),该页面没有标签页,导致"功能没有就一个标签"。
|
||||
|
||||
## 修复方案
|
||||
将菜单 296(门户)的 component 从 `inpatientNurse/home/index` 改为 `inpatientNurse/inpatientNurseStation/index`,使护士站入口默认加载带所有标签的主页面。同时修正 navigation.js 中的路径。
|
||||
@@ -873,6 +873,32 @@ function ensureOrgTreeLoaded() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 待签发且未收费(chargeStatus=5 为已收费) */
|
||||
function isPendingUnsignedAndUnpaid(item) {
|
||||
return item.statusEnum == 1 && item.chargeStatus != 5
|
||||
}
|
||||
|
||||
/**
|
||||
* 门诊划价:仅允许操作本人开立(bizRequestFlag==1 或空)。
|
||||
* 手术计费:列表接口需按库中 generate_source_enum 查询(当前多为 1),故子组件仍传 generateSourceEnum=1;
|
||||
* 通过 patientInfo.generateSourceEnum===6(手术计费在 chargePatientInfo 中已写入)识别场景,删除时不卡 bizRequestFlag。
|
||||
*/
|
||||
function isSurgeryChargeBillingContext() {
|
||||
const fromPatient = props.patientInfo?.generateSourceEnum
|
||||
return fromPatient != null && Number(fromPatient) === 6
|
||||
}
|
||||
|
||||
function isBizRequestAllowedForDelete(item) {
|
||||
if (isSurgeryChargeBillingContext()) {
|
||||
return true
|
||||
}
|
||||
const src = props.generateSourceEnum != null ? Number(props.generateSourceEnum) : NaN
|
||||
if (src === 6) {
|
||||
return true
|
||||
}
|
||||
return Number(item.bizRequestFlag) === 1 || !item.bizRequestFlag
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
// 🔧 修复:使用 groupIndexList 而不是 check 属性
|
||||
// 因为 watch 监听器会在数据更新时重置 check 为 false
|
||||
@@ -881,80 +907,93 @@ function handleDelete() {
|
||||
return;
|
||||
}
|
||||
|
||||
let deleteList = groupIndexList.value.map((index) => {
|
||||
const item = prescriptionList.value[index];
|
||||
// 只删除待签发且未收费的项目
|
||||
if (item.statusEnum != 1 || item.chargeStatus == 5) {
|
||||
return null;
|
||||
}
|
||||
// 🔧 Bug #442: 非本人创建的医嘱不允许删除(与签发/签退逻辑保持一致)
|
||||
if (Number(item.bizRequestFlag) !== 1 && item.bizRequestFlag) {
|
||||
return null;
|
||||
}
|
||||
// 🔧 Bug #442: 已保存的行必须有有效的 requestId,否则跳过(避免后端删除不存在的记录)
|
||||
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
requestId: item.requestId,
|
||||
dbOpType: '3',
|
||||
adviceType: item.adviceType,
|
||||
};
|
||||
}).filter(item => item !== null); // 过滤掉已签发、已收费、非本人创建或无 requestId 的项目
|
||||
|
||||
if (deleteList.length == 0) {
|
||||
proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
|
||||
return;
|
||||
const canDeleteRow = (item) =>
|
||||
isPendingUnsignedAndUnpaid(item) && isBizRequestAllowedForDelete(item)
|
||||
|
||||
const anySelectedDeletable = groupIndexList.value.some((index) =>
|
||||
canDeleteRow(prescriptionList.value[index])
|
||||
)
|
||||
if (!anySelectedDeletable) {
|
||||
proxy.$modal.msgWarning(
|
||||
'只能删除「待签发」且「未收费」的项目;门诊划价还需为本人开立。已签发、已收费或非本人开立项不可删。'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let deleteList = groupIndexList.value
|
||||
.map((index) => {
|
||||
const item = prescriptionList.value[index]
|
||||
if (!canDeleteRow(item)) {
|
||||
return null
|
||||
}
|
||||
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
requestId: item.requestId,
|
||||
dbOpType: '3',
|
||||
adviceType: item.adviceType,
|
||||
}
|
||||
})
|
||||
.filter((item) => item !== null)
|
||||
|
||||
// 删除逻辑:按索引从大到小排序,避免删除后索引变化
|
||||
const sortedIndexes = groupIndexList.value.sort((a, b) => b - a);
|
||||
let hasSavedItem = false;
|
||||
|
||||
const sortedIndexes = [...groupIndexList.value].sort((a, b) => b - a)
|
||||
let hasSavedItem = false
|
||||
|
||||
for (const index of sortedIndexes) {
|
||||
const item = prescriptionList.value[index];
|
||||
if (item.statusEnum != 1) {
|
||||
continue; // 跳过已签发的项目
|
||||
const item = prescriptionList.value[index]
|
||||
if (!canDeleteRow(item)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if (!item.requestId) {
|
||||
// 新增的行(未保存到数据库),直接删除
|
||||
prescriptionList.value.splice(index, 1);
|
||||
prescriptionList.value.splice(index, 1)
|
||||
} else {
|
||||
hasSavedItem = true;
|
||||
hasSavedItem = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSavedItem) {
|
||||
// 🔧 Bug #454: 删除前弹出确认提示,告知用户将级联删除关联检验申请单
|
||||
const hasLabItem = deleteList.some(item => item.adviceType === 3);
|
||||
const confirmMsg = hasLabItem
|
||||
? '删除此医嘱将同时删除关联的检验申请单,是否确认删除?'
|
||||
: '确认删除选中的医嘱项目吗?';
|
||||
|
||||
proxy.$modal.confirm(confirmMsg).then(() => {
|
||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||
if (res.code == 200) {
|
||||
proxy.$modal.msgSuccess('操作成功');
|
||||
getListInfo(false);
|
||||
expandOrder.value = [];
|
||||
groupIndexList.value = [];
|
||||
groupList.value = [];
|
||||
isAdding.value = false;
|
||||
adviceQueryParams.value.adviceType = undefined;
|
||||
}
|
||||
});
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
} else {
|
||||
// 只有新增行,已经在前端删除完成
|
||||
proxy.$modal.msgSuccess('操作成功');
|
||||
const cleanupAfterDelete = () => {
|
||||
expandOrder.value = [];
|
||||
groupIndexList.value = [];
|
||||
groupList.value = [];
|
||||
isAdding.value = false;
|
||||
adviceQueryParams.value.adviceType = undefined;
|
||||
};
|
||||
|
||||
if (hasSavedItem) {
|
||||
if (deleteList.length === 0) {
|
||||
proxy.$modal.msgWarning('没有可提交删除的已保存医嘱,请刷新后重试');
|
||||
getListInfo(false);
|
||||
cleanupAfterDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bug #454: 删除前确认;检验医嘱提示级联删除申请单
|
||||
const hasLabItem = deleteList.some((item) => item.adviceType === 3);
|
||||
const confirmMsg = hasLabItem
|
||||
? '删除此医嘱将同时删除关联的检验申请单,是否确认删除?'
|
||||
: '确认删除选中的医嘱项目吗?';
|
||||
|
||||
proxy.$modal
|
||||
.confirm(confirmMsg)
|
||||
.then(() => {
|
||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||
if (res.code == 200) {
|
||||
proxy.$modal.msgSuccess('操作成功');
|
||||
getListInfo(false);
|
||||
cleanupAfterDelete();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
} else {
|
||||
proxy.$modal.msgSuccess('操作成功');
|
||||
cleanupAfterDelete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1057,6 +1096,9 @@ function handleSave() {
|
||||
adviceTableName: item.adviceTableName,
|
||||
adviceDefinitionId: item.adviceDefinitionId,
|
||||
chargeItemId: item.chargeItemId,
|
||||
// 🔧 Bug Fix: 签发时显式设置手术计费关键字段,避免后端 prescription_no / generateSourceEnum 回退为默认值导致查询无法匹配
|
||||
generateSourceEnum: props.generateSourceEnum ?? parsedContent.generateSourceEnum,
|
||||
sourceBillNo: props.sourceBillNo ?? parsedContent.sourceBillNo,
|
||||
};
|
||||
});
|
||||
// 确保 organizationId 不为 undefined(手术计费场景下可能缺失 orgId)
|
||||
|
||||
@@ -380,6 +380,28 @@ watch(
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 编辑模式下,applicationListAll 加载完成后重新回显已选项目
|
||||
watch(
|
||||
() => applicationListAll.value,
|
||||
() => {
|
||||
if (!props.editData?.requestFormId) return;
|
||||
if (!props.editData.requestFormDetailList?.length) return;
|
||||
if (!applicationListAll.value.length) return;
|
||||
|
||||
const selectedIds = [];
|
||||
props.editData.requestFormDetailList.forEach((detail) => {
|
||||
const matched = applicationListAll.value.find(
|
||||
(item) => item.adviceName === detail.adviceName
|
||||
);
|
||||
if (matched) {
|
||||
selectedIds.push(matched.adviceDefinitionId);
|
||||
}
|
||||
});
|
||||
transferValue.value = selectedIds;
|
||||
}
|
||||
);
|
||||
|
||||
const submit = () => {
|
||||
if (transferValue.value.length == 0) {
|
||||
return proxy.$message.error('请选择申请单');
|
||||
|
||||
@@ -777,6 +777,9 @@ const InputOptions = ref([
|
||||
// 日期范围 - 体征信息搜索时间
|
||||
const receptionTime = ref(null);
|
||||
|
||||
// 按钮禁用状态(保存按钮依赖)
|
||||
const buttonDisabled = ref(false);
|
||||
|
||||
// 表单数据 - 体征录入
|
||||
const formData = ref({
|
||||
recordingDate: '',
|
||||
|
||||
Reference in New Issue
Block a user