diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/IDoctorPhraseAppService.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/IDoctorPhraseAppService.java index c9e47334..efe928c1 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/IDoctorPhraseAppService.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/IDoctorPhraseAppService.java @@ -1,16 +1,14 @@ package com.openhis.web.doctorstation.appservice; -import com.core.common.core.domain.R; import com.openhis.template.domain.DoctorPhrase; +import java.util.List; + public interface IDoctorPhraseAppService { - R getDoctorPhraseList(); + List getDoctorPhraseList(); + List searchDoctorPhraseList(String phraseName, Integer phraseType); + Boolean addDoctorPhrase(DoctorPhrase doctorPhrase); + Boolean updateDoctorPhrase(DoctorPhrase doctorPhrase); + Boolean deleteDoctorPhrase(Integer doctorPhraseId); - R searchDoctorPhraseList(String phraseName ,Integer phraseType); - - R addDoctorPhrase(DoctorPhrase doctorPhrase); - - R updateDoctorPhrase(DoctorPhrase doctorPhrase); - - R deleteDoctorPhrase(Integer doctorPhraseId); } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java index a3113190..64ba8ae2 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java @@ -2,30 +2,71 @@ package com.openhis.web.doctorstation.appservice.impl; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.core.common.core.domain.R; +import com.core.common.utils.SecurityUtils; +import com.openhis.common.enums.BindingType; import com.openhis.template.domain.DoctorPhrase; import com.openhis.template.service.IDoctorPhraseService; import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; +@Slf4j @Service public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService { @Resource private IDoctorPhraseService doctorPhraseService; - @Override - public R getDoctorPhraseList() { - List list = doctorPhraseService.list(); - return R.ok(list); +@Override +public List getDoctorPhraseList() { + Long orgId = SecurityUtils.getLoginUser().getOrgId(); + if (log.isDebugEnabled()) { + log.debug("orgId: {}", orgId); } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 1. 获取当前登录用户信息 + Long userId = SecurityUtils.getUserId(); + // 2. 权限判定:非管理员才需要过滤 + // 如果是超级管理员,默认可以看到所有 + if (!SecurityUtils.isAdmin(userId)) { + // 3. 获取当前医生的科室编码 + String deptCode = ""; + if (orgId != null) { + deptCode = String.valueOf(orgId); + } + // final 变量用于 Lambda 表达式 + String finalDeptCode = deptCode; + // 4. 核心逻辑:三级数据共享 + wrapper.and(w -> w + .eq(DoctorPhrase::getStaffId, userId.intValue()) // 1. 个人的:只看 staffId 是我的 + .or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.HOSPITAL.getValue())) // 2. 全院的:类型为 3 + .or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.ORGANIZATION.getValue()) // 3. 科室的:类型为 2 且 科室编码匹配 + .eq(DoctorPhrase::getDeptCode, finalDeptCode)) + ); + } + // 5. 按排序号排序(可选优化,让常用语显示更整齐) + List list = doctorPhraseService.list(wrapper); + return list; +} @Override - public R searchDoctorPhraseList(String phraseName,Integer phraseType) { + public List searchDoctorPhraseList(String phraseName,Integer phraseType) { + // 1. 获取当前登录用户信息 + Long userId = SecurityUtils.getUserId(); + //2.获取到当前医生当前科室的id + Long orgId = SecurityUtils.getLoginUser().getOrgId(); + if (log.isDebugEnabled()) { + log.debug("Search phrase - orgId: {}, phraseName: {}, phraseType: {}", orgId, phraseName, phraseType); + } + String deptCode = ""; + if (orgId != null) { + deptCode = String.valueOf(orgId); + } + String finalDeptCode = deptCode; LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); if (phraseName !=null && ObjectUtil.isNotEmpty(phraseName)) { wrapper.like(DoctorPhrase::getPhraseName, phraseName); @@ -33,50 +74,135 @@ public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService { if (phraseType !=null && ObjectUtil.isNotEmpty(phraseType)) { wrapper.eq(DoctorPhrase::getPhraseType, phraseType); } + Long currentUserId = SecurityUtils.getUserId(); + if (!SecurityUtils.isAdmin(currentUserId)) { + // 建议统一使用 staffId 进行业务隔离 + wrapper.and(w -> w + .eq(DoctorPhrase::getStaffId, userId.intValue()) // 1. 个人的:只看 staffId 是我的 + .or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.HOSPITAL.getValue())) // 2. 全院的:类型为 3 + .or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.ORGANIZATION.getValue()) // 3. 科室的:类型为 2 且 科室编码匹配 + .eq(DoctorPhrase::getDeptCode, finalDeptCode)) + ); + } //2.查询 List list = doctorPhraseService.list(wrapper); - return R.ok(list); + return list; } @Override - public R addDoctorPhrase(DoctorPhrase doctorPhrase) { - //1.数据校验 - if(ObjectUtil.isEmpty(doctorPhrase)){ - return R.fail("医生常用语不能为空"); + public Boolean addDoctorPhrase(DoctorPhrase doctorPhrase) { + // 1. 基础校验 + if (ObjectUtil.isEmpty(doctorPhrase) || ObjectUtil.isEmpty(doctorPhrase.getPhraseName())) { + throw new IllegalArgumentException("新增失败:常用语名称不能为空"); } - //2.名称唯一性校验 - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DoctorPhrase::getPhraseName, doctorPhrase.getPhraseName()); - DoctorPhrase one = doctorPhraseService.getOne(wrapper); - if(ObjectUtil.isNotEmpty(one)){ - return R.fail("该名称已经存在"); + + Long currentUserId = SecurityUtils.getUserId(); + + /* + * 如果前端没传类型,必须给个默认值(比如 1-个人) + * 否则存成 NULL,查询列表时会被过滤掉,导致"新增了却看不见" + */ + if (doctorPhrase.getPhraseType() == null) { + doctorPhrase.setPhraseType(BindingType.PERSONAL.getValue()); } - //3.新增 - boolean save = doctorPhraseService.save(doctorPhrase); - System.out.println(save); - return R.ok(save); + + // 2. 注入归属信息 + doctorPhrase.setStaffId(currentUserId.intValue()); + doctorPhrase.setCreatorId(currentUserId.intValue()); + + // 注入科室 (处理 null 情况) + Long orgId = SecurityUtils.getLoginUser().getOrgId(); + if (orgId != null) { + // 检查dept_code字段长度,避免数据库错误 + String deptCode = String.valueOf(orgId); + if (deptCode.length() > 50) { // 假设字段长度为50,根据实际情况调整 + // 如果超过字段长度限制,可以考虑截断或抛出有意义的错误 + throw new IllegalArgumentException("科室ID过长,无法保存"); + } + doctorPhrase.setDeptCode(deptCode); + } + + // =========== 【修复点 2】查重范围限制在"个人" =========== + // 使用 QueryWrapper 而不是 UpdateWrapper + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DoctorPhrase::getPhraseName, doctorPhrase.getPhraseName()) + .eq(DoctorPhrase::getStaffId, currentUserId.intValue()); // 重点:只查自己名下的! + + if (doctorPhraseService.count(queryWrapper) > 0) { + throw new IllegalArgumentException("新增失败:您已存在同名的常用语"); + } + + return doctorPhraseService.save(doctorPhrase); } @Override - public R updateDoctorPhrase(DoctorPhrase doctorPhrase) { - //1.数据校验 - if(ObjectUtil.isEmpty(doctorPhrase)){ - return R.fail("医生常用语不能为空"); + public Boolean updateDoctorPhrase(DoctorPhrase doctorPhrase) { + // 1. 基础校验 + if (ObjectUtil.isEmpty(doctorPhrase) || doctorPhrase.getId() == null) { + throw new IllegalArgumentException("修改失败:ID不能为空"); } - //2.更新 - boolean updateById = doctorPhraseService.updateById(doctorPhrase); - return R.ok(updateById); + + // 2. 查旧数据 + DoctorPhrase original = doctorPhraseService.getById(doctorPhrase.getId()); + if (original == null) { + throw new IllegalArgumentException("修改失败:该常用语不存在或已被删除"); + } + + Long currentUserId = SecurityUtils.getUserId(); + + // 3. 【权限校验优化】 + if (!SecurityUtils.isAdmin(currentUserId)) { + // 规则 A:严禁修改全院公共模板 (Type=3) + // 这一步是关键!之前你的代码漏了这里,所以普通医生可能改动全院模板 + if (BindingType.HOSPITAL.getValue().equals(original.getPhraseType())) { + throw new SecurityException("无权操作:全院公共常用语仅限管理员修改"); + } + + // 规则 B:严禁修改他人的模板 + if (!original.getStaffId().equals(currentUserId.intValue())) { + throw new SecurityException("无权操作:您只能修改自己创建的常用语"); + } + } + + // 4. 数据保护:防止篡改归属 + doctorPhrase.setStaffId(original.getStaffId()); + doctorPhrase.setCreatorId(original.getCreatorId()); + Long orgId = SecurityUtils.getLoginUser().getOrgId(); + if (orgId != null) { + // 检查dept_code字段长度,避免数据库错误 + String deptCode = String.valueOf(orgId); + if (deptCode.length() > 50) { // 假设字段长度为50,根据实际情况调整 + throw new IllegalArgumentException("科室ID过长,无法保存"); + } + doctorPhrase.setDeptCode(deptCode); + } + + return doctorPhraseService.updateById(doctorPhrase); } @Override - public R deleteDoctorPhrase(Integer doctorPhraseId) { - //1.数据校验 - if(doctorPhraseId == null){ - return R.fail("ID不能为空"); + public Boolean deleteDoctorPhrase(Integer doctorPhraseId) { + if (doctorPhraseId == null) { + throw new IllegalArgumentException("删除失败:ID不能为空"); } - //2.删除 - boolean removeById = doctorPhraseService.removeById(doctorPhraseId); - return R.ok(removeById); + + DoctorPhrase original = doctorPhraseService.getById(doctorPhraseId); + if (original == null) { + throw new IllegalArgumentException("删除失败:数据不存在"); + } + + Long currentUserId = SecurityUtils.getUserId(); + + // 权限校验 + if (!SecurityUtils.isAdmin(currentUserId)) { + if (BindingType.HOSPITAL.getValue().equals(original.getPhraseType())) { + throw new SecurityException("无权操作:全院公共常用语仅限管理员删除"); + } + if (!original.getStaffId().equals(currentUserId.intValue())) { + throw new SecurityException("无权操作:您只能删除自己创建的常用语"); + } + } + return doctorPhraseService.removeById(doctorPhraseId); } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorPhraseController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorPhraseController.java index 0e4b05a2..a1d959d6 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorPhraseController.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorPhraseController.java @@ -1,6 +1,7 @@ package com.openhis.web.doctorstation.controller; import com.core.common.core.domain.R; +import com.openhis.common.enums.BindingType; import com.openhis.template.domain.DoctorPhrase; import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService; import lombok.extern.slf4j.Slf4j; @@ -23,7 +24,13 @@ public class DoctorPhraseController { */ @GetMapping("/list") public R getDoctorPhraseList(){ - return R.ok(doctorPhraseAppService.getDoctorPhraseList()); + try { + return R.ok(doctorPhraseAppService.getDoctorPhraseList()); + } catch (Exception e) { + // 系统异常,使用error级别日志 + log.error("获取医生常用语列表系统异常", e); + return R.fail("获取医生常用语列表失败: " + e.getMessage()); + } } /** @@ -37,7 +44,13 @@ public class DoctorPhraseController { @RequestParam(required = false) Integer phraseType, @RequestParam(required = false) String phraseName ){ - return R.ok(doctorPhraseAppService.searchDoctorPhraseList(phraseName,phraseType)); + try { + return R.ok(doctorPhraseAppService.searchDoctorPhraseList(phraseName, phraseType)); + } catch (Exception e) { + // 系统异常,使用error级别日志 + log.error("查询医生常用语系统异常", e); + return R.fail("查询医生常用语失败: " + e.getMessage()); + } } /** @@ -48,7 +61,22 @@ public class DoctorPhraseController { */ @PostMapping("/add") public R addDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){ - return R.ok(doctorPhraseAppService.addDoctorPhrase(doctorPhrase)); + try { + Boolean result = doctorPhraseAppService.addDoctorPhrase(doctorPhrase); + if (result != null && result) { + return R.ok("新增成功"); + } else { + return R.fail("新增失败"); + } + } catch (IllegalArgumentException e) { + // 参数错误异常,使用warn级别日志 + log.warn("新增医生常用语参数错误: {}", e.getMessage()); + return R.fail(e.getMessage()); + } catch (Exception e) { + // 系统异常,使用error级别日志 + log.error("新增医生常用语系统异常", e); + return R.fail("新增失败: " + e.getMessage()); + } } /** @@ -59,7 +87,26 @@ public class DoctorPhraseController { */ @PutMapping("/update") public R updateDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){ - return R.ok(doctorPhraseAppService.updateDoctorPhrase(doctorPhrase)); + try { + Boolean result = doctorPhraseAppService.updateDoctorPhrase(doctorPhrase); + if (result != null && result) { + return R.ok("更新成功"); + } else { + return R.fail("更新失败"); + } + } catch (IllegalArgumentException e) { + // 参数错误异常,使用warn级别日志 + log.warn("更新医生常用语参数错误: {}", e.getMessage()); + return R.fail(e.getMessage()); + } catch (SecurityException e) { + // 权限相关异常,使用warn级别日志 + log.warn("更新医生常用语权限异常: {}", e.getMessage()); + return R.fail(e.getMessage()); + } catch (Exception e) { + // 系统异常,使用error级别日志 + log.error("更新医生常用语系统异常", e); + return R.fail("更新失败: " + e.getMessage()); + } } /** @@ -70,7 +117,26 @@ public class DoctorPhraseController { */ @DeleteMapping("/delete/{DoctorPhraseId}") public R deleteDoctorPhrase(@PathVariable Integer DoctorPhraseId){ - return R.ok(doctorPhraseAppService.deleteDoctorPhrase(DoctorPhraseId)); + try { + Boolean result = doctorPhraseAppService.deleteDoctorPhrase(DoctorPhraseId); + if (result != null && result) { + return R.ok("删除成功"); + } else { + return R.fail("删除失败"); + } + } catch (IllegalArgumentException e) { + // 参数错误异常,使用warn级别日志 + log.warn("删除医生常用语参数错误: {}", e.getMessage()); + return R.fail(e.getMessage()); + } catch (SecurityException e) { + // 权限相关异常,使用warn级别日志 + log.warn("删除医生常用语权限异常: {}", e.getMessage()); + return R.fail(e.getMessage()); + } catch (Exception e) { + // 系统异常,使用error级别日志 + log.error("删除医生常用语系统异常", e); + return R.fail("删除失败: " + e.getMessage()); + } } } diff --git a/openhis-ui-vue3/src/views/doctorstation/doctorphrase/index.vue b/openhis-ui-vue3/src/views/doctorstation/doctorphrase/index.vue index 0d61d84a..9ec4fea7 100644 --- a/openhis-ui-vue3/src/views/doctorstation/doctorphrase/index.vue +++ b/openhis-ui-vue3/src/views/doctorstation/doctorphrase/index.vue @@ -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('更新操作失败: 网络请求错误') } }