套餐设置套餐管理完善

This commit is contained in:
2026-02-03 14:03:07 +08:00
parent 3acf8ad50a
commit 3d31b3482a
4 changed files with 136 additions and 80 deletions

View File

@@ -69,7 +69,9 @@ public class InspectionTypeController extends BaseController {
// 验证code字段是否唯一
QueryWrapper<InspectionType> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("code", inspectionType.getCode());
queryWrapper.eq("code", inspectionType.getCode())
// 只在“有效(valid_flag=1)”范围内判重;逻辑删除(valid_flag=0)的不阻止复用编码
.eq("valid_flag", 1);
// 查询是否存在相同编码的记录
List<InspectionType> existingRecords = inspectionTypeService.list(queryWrapper);
@@ -86,7 +88,8 @@ public class InspectionTypeController extends BaseController {
return transactionTemplate.execute(status -> {
// 再次检查,防止并发问题
QueryWrapper<InspectionType> checkWrapper = new QueryWrapper<>();
checkWrapper.eq("code", inspectionType.getCode());
checkWrapper.eq("code", inspectionType.getCode())
.eq("valid_flag", 1);
List<InspectionType> finalCheck = inspectionTypeService.list(checkWrapper);
if (!finalCheck.isEmpty()) {
return AjaxResult.error("检验类型编码已存在");
@@ -123,7 +126,8 @@ public class InspectionTypeController extends BaseController {
// 验证code字段是否唯一排除自身
QueryWrapper<InspectionType> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("code", inspectionType.getCode())
.ne("id", inspectionType.getId());
.ne("id", inspectionType.getId())
.eq("valid_flag", 1);
if (inspectionTypeService.count(queryWrapper) > 0) {
return AjaxResult.error("检验类型编码已存在");
}

View File

@@ -31,6 +31,10 @@ public class InspectionType {
/** 所属科室 */
private String department;
/** 父类ID为空表示是大类有值表示是子类 */
@TableField("parent_id")
private Long parentId;
/** 排序 */
@TableField("\"order\"")
private Integer sortOrder;

View File

@@ -398,8 +398,13 @@ function resetQuery() {
getPageList();
}
function getPageList() {
function getPageList(pagination) {
loading.value = true;
// 如果传入了分页参数更新queryParams
if (pagination) {
queryParams.value.pageNo = pagination.page;
queryParams.value.pageSize = pagination.limit;
}
// 创建API调用的查询参数副本处理classEnum的转换
const apiParams = { ...queryParams.value };
if (Array.isArray(apiParams.classEnum)) {
@@ -435,6 +440,7 @@ function getPageList() {
organization.value = processedData;
total.value = res.data.total;
}).catch(error => {
console.error('获取科室列表失败:', error);
proxy.$modal.msgError('获取科室列表失败,请稍后重试');

View File

@@ -86,7 +86,7 @@
</td>
<td>
<template v-if="editingRowId === row.id">
<input v-model="row.sortOrder" type="text" :style="inputStyle">
<input v-model="row.sortOrder" type="text" :style="inputStyle" :disabled="isChildTypeRow(row)">
</template>
<template v-else>
{{ row.sortOrder }}
@@ -717,7 +717,7 @@ import {
listInspectionPackageDetails,
saveInspectionPackageDetails
} from '@/api/system/inspectionPackage';
import {getLocationTree} from '@/views/charge/outpatientregistration/components/outpatientregistration';
import {deptTreeSelect} from '@/api/system/user';
// 获取当前登录用户信息
const userStore = useUserStore();
@@ -807,7 +807,7 @@ const typePageButtons = computed(() => {
const total = typeTotalPages.value;
return Array.from({ length: total }, (_, i) => i + 1);
});
// 按“大类编码(code)”排序后再分页展示(不要按序号 sortOrder 排
// 按“序号(sortOrder)”排序后再分页展示(需求:不要按大类编码排序
function parseCodeParts(code) {
const s = (code ?? '').toString().trim();
// 支持 1、01、3-001、3_001 等:用 -/_ 分隔
@@ -827,33 +827,37 @@ function parseCodeParts(code) {
};
}
function isTempId(id) {
return typeof id === 'string' && id.startsWith('temp_');
}
function isChildTypeRow(row) {
const p = parseCodeParts(row?.code);
return !!p.subRaw;
}
const sortedTypeRows = computed(() => {
// 需求:新增/编辑时不要“实时排序”,避免行在编辑过程中跳动;
// 仅在保存成功后(编辑结束、临时行消失/刷新数据)再按编码排序。
const hasTempRow = (tableData.value || []).some(r => isTempId(r?.id));
if (activeNav.value === 0 && (editingRowId.value || hasTempRow)) {
return [...tableData.value];
}
return [...tableData.value].sort((a, b) => {
const aOrder = a?.sortOrder === null || a?.sortOrder === undefined ? Number.POSITIVE_INFINITY : Number(a.sortOrder);
const bOrder = b?.sortOrder === null || b?.sortOrder === undefined ? Number.POSITIVE_INFINITY : Number(b.sortOrder);
if (aOrder !== bOrder) return aOrder - bOrder;
// 同序号:按编码保证稳定性(大类在子类之前,子类按后缀排序)
const pa = parseCodeParts(a?.code);
const pb = parseCodeParts(b?.code);
// 先按主编码:数字优先按数值,否则按字符串
if (pa.mainIsNum && pb.mainIsNum) {
if (pa.mainNum !== pb.mainNum) return pa.mainNum - pb.mainNum;
} else {
const c = pa.mainRaw.localeCompare(pb.mainRaw, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
if (c !== 0) return c;
if (pa.mainRaw !== pb.mainRaw) {
return pa.mainRaw.localeCompare(pb.mainRaw, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
}
// 同主编码时:无子编码的排在前(大类在子类之前)
const aHasSub = !!pa.subRaw;
const bHasSub = !!pb.subRaw;
if (aHasSub !== bHasSub) return aHasSub ? 1 : -1;
// 再按子编码
if (pa.subIsNum && pb.subIsNum) {
if (pa.subNum !== pb.subNum) return pa.subNum - pb.subNum;
} else {
const c = pa.subRaw.localeCompare(pb.subRaw, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
if (c !== 0) return c;
}
// 最后兜底:按原字符串
return pa.raw.localeCompare(pb.raw, 'zh-Hans-CN', { numeric: true, sensitivity: 'base' });
});
});
@@ -905,31 +909,53 @@ const editingRowId = ref(null);
const inputStyle = { width: '100%', height: '28px', padding: '0 4px', border: '1px solid #d9d9d9', borderRadius: '2px' };
const departments = ref([]);
/** 查询就诊科室 - 与门诊挂号页面保持一致 */
/** 查询执行科室 - 与“科室管理”页面保持完全一致的过滤与展示口径 */
function getLocationInfo() {
console.log('调用getLocationTree API...');
getLocationTree().then((response) => {
console.log('getLocationTree API完整返回:', response);
console.log('getLocationTree API数据结构:', JSON.stringify(response.data, null, 2));
// 检查数据结构并转换为适合el-tree-select的格式
if (Array.isArray(response.data)) {
// 直接使用数组数据
departments.value = response.data;
} else if (response.data && response.data.records) {
// 处理分页格式数据
departments.value = response.data.records;
} else if (response.data && response.data.rows) {
// 处理另一种分页格式数据
departments.value = response.data.rows;
} else {
console.error('API返回数据格式不符合预期:', response.data);
departments.value = [];
console.log('========== 检验项目设置 - 获取执行科室 ==========');
// 与科室管理页 queryParams 对齐pageNo/pageSize/typeEnum/classEnum/name
// deptTreeSelect 内部默认 typeEnum=2科室这里显式传入确保不会被外部覆盖
const queryParams = {
pageNo: 1,
pageSize: 1000, // 获取足够多的数据
name: undefined,
typeEnum: 2,
classEnum: undefined
};
console.log('请求参数:', queryParams);
deptTreeSelect(queryParams).then((response) => {
console.log('API 原始响应:', response);
let orgList = [];
// 处理分页响应格式
if (response && response.data) {
if (response.data.records && Array.isArray(response.data.records)) {
orgList = response.data.records;
console.log('从 response.data.records 获取数据,数量:', orgList.length);
} else if (Array.isArray(response.data)) {
orgList = response.data;
console.log('从 response.data 获取数据,数量:', orgList.length);
}
} else if (Array.isArray(response)) {
orgList = response;
console.log('从 response 直接获取数据,数量:', orgList.length);
}
console.log('最终科室数据:', JSON.stringify(departments.value, null, 2));
console.log('处理前的原始科室列表(包含 children:', orgList);
/**
* 关键:科室管理页面使用 el-table 展示的是 records顶层列表并不会展示 children 子节点。
* 为了“完全一致”,这里也只使用 records 顶层数据,并移除 children避免出现科室管理页看不到的下级节点。
*/
const topLevel = (orgList || []).map(n => {
const { children, ...rest } = (n || {});
return rest;
});
// 与科室管理页保持一致:即使 name 为空也保留该行(科室管理表格仍会显示一行)
departments.value = topLevel;
console.log('========== 检验项目设置 - 执行科室加载完成 ==========');
}).catch((error) => {
console.error('获取科室数据失败:', error);
console.error('获取执行科室失败:', error);
departments.value = [];
});
}
@@ -1510,7 +1536,8 @@ const addNewRow = () => {
}
// department 在表格里是字符串(科室名称);这里不要直接塞对象,否则后续 trim/校验会异常
const defaultDeptName = departments.value?.[0]?.name || '';
const newRow = { id: Date.now(), code: '', name: '', department: defaultDeptName, sortOrder: tableData.value.length + 1, remark: '' };
// 新增大类时,parentId 为 null
const newRow = { id: `temp_${Date.now()}`, code: '', name: '', department: defaultDeptName, sortOrder: tableData.value.length + 1, remark: '', parentId: null };
tableData.value.push(newRow);
editingRowId.value = newRow.id;
};
@@ -1524,6 +1551,16 @@ const handleEdit = (row) => {
};
const handleConfirm = (row) => {
// 子类序号必须与主类相同,且不可更改:保存时强制回填
const parts = parseCodeParts(row?.code);
if (parts.subRaw) {
const parentCode = parts.mainRaw;
const parent = tableData.value.find(r => (r?.code ?? '').toString().trim() === parentCode);
if (parent) {
row.sortOrder = parent.sortOrder;
}
}
// 准备提交给后端的数据保留sortOrder字段名后端会自动映射到数据库的order字段
const submitData = {
...row,
@@ -1555,51 +1592,56 @@ const handleConfirm = (row) => {
return;
}
// 检查是否是已知的重复编码
if (submitData.code.trim() === '21') {
alert('检验类型编码21已存在请使用其他编码');
return;
}
// 输出调试信息
console.log('原始code值:', submitData.code, '长度:', submitData.code.length);
// 注意:编码唯一性由后端统一校验;这里不做硬编码拦截(避免误判)
// 去除code字段的前后空格确保唯一性验证准确
submitData.code = submitData.code.trim();
console.log('去除空格后的code值:', submitData.code, '长度:', submitData.code.length);
console.log('准备提交的数据:', submitData);
// 区分新增和更新操作
if (row.id.toString().length > 10) { // 新增的临时ID
// 区分新增和更新操作:使用 temp_ 前缀的临时ID判断避免时间戳阈值在不同年份失效
if (isTempId(row.id)) { // 新增的临时ID
// 处理 parentId如果是子类且 parentId 是临时ID需要先找到父类的真实 id
let finalParentId = submitData.parentId;
if (finalParentId && isTempId(finalParentId)) {
// parentId 是临时ID说明父类还没保存这种情况不应该出现因为要求先保存父类
// 但为了容错,尝试通过 code 找到父类
const parts = parseCodeParts(submitData.code);
if (parts.subRaw) {
const parentCode = parts.mainRaw;
const parent = tableData.value.find(r =>
!isTempId(r.id) && (r?.code ?? '').toString().trim() === parentCode
);
if (parent) {
finalParentId = parent.id;
} else {
ElMessage.warning('请先保存父类,再保存子类');
return;
}
}
}
// 新增数据时移除临时ID让后端自动生成主键
const { id, ...newData } = submitData;
console.log('删除ID后的newData:', newData);
newData.parentId = finalParentId || null; // 确保 parentId 正确设置(大类为 null子类为父类 id
addInspectionType(newData).then(response => {
console.log('新增成功响应:', response);
ElMessage.success('新增成功');
getInspectionTypeList();
}).catch(error => {
console.error('新增检验类型失败:', error);
if (error.response && error.response.data) {
alert('新增失败: ' + error.response.data.msg);
ElMessage.error(error.response.data.msg || '新增失败');
} else {
alert('新增失败,请重试');
ElMessage.error('新增失败,请重试');
}
});
} else { // 更新操作
// 更新数据时保留ID
console.log('更新时的submitData:', submitData);
updateInspectionType(submitData).then(response => {
console.log('更新成功响应:', response);
ElMessage.success('保存成功');
getInspectionTypeList();
}).catch(error => {
console.error('更新检验类型失败:', error);
if (error.response && error.response.data) {
alert('更新失败: ' + error.response.data.msg);
ElMessage.error(error.response.data.msg || '保存失败');
} else {
alert('更新失败,请重试');
ElMessage.error('保存失败,请重试');
}
});
}
@@ -1637,14 +1679,16 @@ const handleAdd = (row, index) => {
const nextSeq = maxSeq + 1;
const childCode = `${parentCode}-${String(nextSeq).padStart(3, '0')}`;
// 新增子类时parentId 指向父类的 id如果父类已保存有真实 id否则先存父类的临时 id保存时再处理
const newRow = {
id: Date.now(),
id: `temp_${Date.now()}`,
code: childCode,
name: '',
department: row.department,
// 序号字段保持原逻辑:插入到父类下一行,默认沿用父类序号
sortOrder: row.sortOrder,
remark: ''
remark: '',
parentId: row.id // 子类的 parentId 指向父类的 id可能是真实 id 或临时 id
};
tableData.value.splice(index + 1, 0, newRow);
editingRowId.value = newRow.id;
@@ -1662,9 +1706,8 @@ const handleAdd = (row, index) => {
const numericId = Number(id);
console.log('转换后的ID:', numericId, '类型:', typeof numericId);
// 判断是否为临时ID临时ID是通过Date.now()生成的值很大通常大于2000000000000
const isTemporaryId = numericId > 2e12;
console.log('是否为临时ID:', isTemporaryId, '判断阈值:', 2e12);
// 判断是否为临时ID前端 temp_ 前缀
const isTemporaryId = isTempId(id);
if (!isTemporaryId) { // 真实数据库ID
console.log('调用删除APIID:', numericId, 'API路径:', `/system/inspection-type/${numericId}`);
@@ -1686,8 +1729,7 @@ const handleAdd = (row, index) => {
});
} else {
// 删除临时新增的行
console.log('删除临时行ID:', numericId);
tableData.value = tableData.value.filter(row => Number(row.id) !== numericId);
tableData.value = tableData.value.filter(row => row.id !== id);
ElMessage.success('删除成功');
}
}).catch(() => {