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

This commit is contained in:
wangjian963
2026-03-20 17:47:22 +08:00
3 changed files with 219 additions and 78 deletions

View File

@@ -146,7 +146,7 @@
v-model="scope.row.doseQuantity"
@input="
(value) => {
scope.row.dose = value / scope.row.unitConversionRatio;
scope.row.dose = value * scope.row.unitConversionRatio;
}
"
/>
@@ -164,7 +164,7 @@
v-model="scope.row.dose"
@input="
(value) => {
scope.row.doseQuantity = value * scope.row.unitConversionRatio;
scope.row.doseQuantity = value / scope.row.unitConversionRatio;
}
"
/>

View File

@@ -227,7 +227,7 @@
v-model="scope.row.doseQuantity"
@input="
(value) => {
scope.row.dose = value / scope.row.unitConversionRatio;
scope.row.dose = value * scope.row.unitConversionRatio;
}
"
/>
@@ -238,7 +238,7 @@
v-model="scope.row.dose"
@input="
(value) => {
scope.row.doseQuantity = value * scope.row.unitConversionRatio;
scope.row.doseQuantity = value / scope.row.unitConversionRatio;
}
"
/>

View File

@@ -268,14 +268,14 @@
<template #default="{ row }">
<template v-if="editingRowId === row.id">
<el-select v-model="row.feePackageId" placeholder="选择费用套餐" size="small" style="width: 100%;" filterable clearable
@change="(val) => { const pkg = feePackages.find(p => p.id === val); row.package = pkg ? pkg.packageName : ''; updateAmountFromPackage(row); }"
@change="(val) => { const pkg = feePackages.find(p => String(p.id) === String(val)); row.package = pkg ? pkg.packageName : ''; updateAmountFromPackage(row); }"
@visible-change="(visible) => { if (visible && feePackages.length === 0) getFeePackages() }">
<el-option label="选择费用套餐" value="" />
<el-option
v-for="pkg in feePackages"
:key="pkg.id"
:label="pkg.packageName"
:value="pkg.id"
:value="String(pkg.id)"
/>
</el-select>
</template>
@@ -353,7 +353,7 @@
v-for="item in (row.subItemOptions || [])"
:key="item.id"
:label="item.name"
:value="item.id"
:value="Number(item.id)"
/>
</el-select>
</template>
@@ -488,7 +488,24 @@
</div>
<div class="form-item">
<span class="form-label">卫生机构</span>
<el-input :model-value="userStore.orgName || '测试机构'" readonly />
<el-select
v-model="selectedTenantId"
placeholder="请选择卫生机构"
style="width: 100%;"
@change="handleTenantChange"
clearable
filterable
:loading="loadingTenant"
no-data-text="暂无数据请稍后重试"
@visible-change="handleTenantVisibleChange"
>
<el-option
v-for="item in tenantOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="form-item">
<span class="form-label">套餐金额</span>
@@ -1006,7 +1023,83 @@ const handleFocus = () => {
}
*/
};
// 获取当前登录用户信息
// --- 新增:存储租户/机构列表和当前选中的值 ---
const tenantOptions = ref([]); // 存储从后端获取的租户列表
const selectedTenantId = ref(null); // 存储当前选中的租户ID
// --- 新增:控制租户列表加载状态 ---
const loadingTenant = ref(false); // 控制下拉框的加载状态
const fetchTenantList = async () => {
if (loadingTenant.value) return; // 防止重复请求
loadingTenant.value = true;
try {
const response = await request({
url: '/system/tenant/page',
method: 'get',
params: {
pageNum: 1,
pageSize: 100
}
});
if (response.code === 200) {
let tenantData = [];
// --- 关键修改:优先检查 records 字段 ---
if (response.data && response.data.records && Array.isArray(response.data.records)) {
tenantData = response.data.records;
} else if (response.data && response.data.rows && Array.isArray(response.data.rows)) {
// 兼容旧的 rows 格式
tenantData = response.data.rows;
} else if (response.data && Array.isArray(response.data)) {
// 兼容最简单的数组格式
tenantData = response.data;
} else {
tenantData = [];
}
// 格式化数据以适应 el-select
tenantOptions.value = tenantData.map(item => ({
value: item.id,
label: item.tenantName || item.name || item.orgName || String(item.id) || '未知机构'
}));
// 如果仍未设置默认值且列表不为空,选择第一个
if (!selectedTenantId.value && tenantOptions.value.length > 0) {
selectedTenantId.value = tenantOptions.value[0].value;
}
} else {
ElMessage.error(response.msg || '获取机构列表失败,请联系管理员');
tenantOptions.value = [];
}
} catch (error) {
ElMessage.error('网络异常或数据解析错误,请检查控制台日志');
tenantOptions.value = [];
} finally {
// 确保无论成功还是失败都停止加载状态
loadingTenant.value = false;
}
};
// 展开时若列表为空则加载数据
const handleTenantVisibleChange = async (visible) => {
if (visible && tenantOptions.value.length === 0) { // 仅在展开且列表为空时加载
await fetchTenantList();
}
};
/**
* 获取数据方法
* 这里不再接收搜索关键词,而是直接拉取所有启用的套餐
@@ -1061,10 +1154,6 @@ const handlePackageVisibleChange = (visible) => {
// 可选:如果希望页面一打开就预加载一次,保留 onMounted
// 如果希望完全由用户点击触发,可以注释掉 onMounted
onMounted(() => {
// getFeePackages();
});
// ==============================
// 【修复】级联下拉框逻辑 (包含直接请求代码)
@@ -1160,24 +1249,37 @@ const handleEditRow = (row) => {
// 初始化行内属性
if (!row.subItemOptions) row.subItemOptions = [];
row.loadingSubItems = false;
// 回显名称
// 预加载费用套餐列表确保下拉框能显示名称而不是ID
if (feePackages.value.length === 0) {
getFeePackages();
}
// 如果已有子项ID但选项列表为空且有大类ID则主动加载子类列表
if (row.inspectionTypeId && row.subItemOptions.length === 0) {
row.loadingSubItems = true;
fetchInspectionTypesRequest({ parentId: row.inspectionTypeId }).then(res => {
let subData = [];
if (res.code === 200) {
if (res.data && Array.isArray(res.data)) subData = res.data;
else if (res.data && res.data.rows) subData = res.data.rows;
else if (res.data && res.data.data && Array.isArray(res.data.data)) subData = res.data.data;
}
row.subItemOptions = subData;
}).finally(() => {
row.loadingSubItems = false;
});
}
// 回显检验大类名称
if (row.inspectionTypeId && !row.inspectionTypeName) {
const p = parentTypeOptions.value.find(i => i.id === row.inspectionTypeId);
if (p) row.inspectionTypeName = p.name;
}
if (row.subItemId && row.subItemOptions.length > 0) {
const s = row.subItemOptions.find(i => i.id === row.subItemId);
if (s) row.subItemName = s.name;
}
};
onMounted(() => {
// ... 其他初始化代码
loadParentTypes(); // <--- 确保这一行存在
// ...
loadParentTypes();
});
@@ -1292,15 +1394,15 @@ const loadObservationItems = async (resetPage = false) => {
code: item.busNo || '',
name: item.name || '',
testType: item.inspectionTypeId_dictText || item.testType || '',
inspectionTypeId: item.inspectionTypeId || null,
inspectionTypeId: item.inspectionTypeId ? Number(item.inspectionTypeId) : null,
package: item.packageName || '',
feePackageId: item.feePackageId || null,
feePackageId: item.feePackageId ? String(item.feePackageId) : null,
sampleType: item.specimenCode || '',
amount: parseFloat(item.retailPrice || 0),
sortOrder: item.sortOrder || null,
serviceRange: item.serviceRange || '全部',
subItemName: item.subItemName || '',
subItemId: item.subItemId || null,
subItemId: item.subItemId ? Number(item.subItemId) : null,
remark: item.descriptionText || '',
status: true
}));
@@ -1319,8 +1421,8 @@ const packageFilter = ref('');
// 过滤后的检验项目数据(仅保留费用套餐的前端过滤,其他过滤已由后端处理)
const filteredInspectionItems = computed(() => {
return inspectionItems.value.filter(item => {
// 按费用套餐过滤(费用套餐是前端自定义字段,需要前端过滤
if (packageFilter.value && item.package !== packageFilter.value) {
// 按费用套餐过滤(用 feePackageId 与选中的套餐 id 比对
if (packageFilter.value && String(item.feePackageId) !== String(packageFilter.value)) {
return false;
}
return true;
@@ -1466,7 +1568,7 @@ let addingItem = false;
const addPackageItem = () => {
if (addingItem) return; // 防止重复调用
addingItem = true;
const newItem = {
name: '',
dosage: '',
@@ -1482,7 +1584,7 @@ const addPackageItem = () => {
origin: ''
};
packageItems.value.push(newItem);
// 延迟重置标志位,确保不会影响其他操作
setTimeout(() => {
addingItem = false;
@@ -1509,23 +1611,23 @@ const deletePackageItem = (index) => {
const updateItemAmount = (item) => {
// 计算项目原价金额
const originalAmount = (item.quantity || 1) * (item.unitPrice || 0.00);
// 应用折扣到项目金额
let discountedAmount = originalAmount;
if (discount.value && !isNaN(parseFloat(discount.value))) {
const discountRate = parseFloat(discount.value) / 100;
discountedAmount = originalAmount * (1 - discountRate);
}
// 更新项目金额
item.amount = parseFloat(discountedAmount.toFixed(2));
// 基于折扣后的金额计算服务费
item.serviceFee = calculateItemServiceFee(item);
// 更新项目总金额
updateItemTotalAmount(item);
// 重新计算套餐金额和服务费
calculateAmounts();
};
@@ -1581,7 +1683,7 @@ const cancelEditItem = (index) => {
// 计算单个项目的服务费(基于折扣后的金额)
const calculateItemServiceFee = (item) => {
if (!generateServiceFee.value) return 0;
// 服务费是项目折扣后金额的10%
return parseFloat((item.amount * 0.1).toFixed(2));
};
@@ -1596,7 +1698,7 @@ const redistributeServiceFee = () => {
});
return;
}
// 重新计算每个项目的服务费
packageItems.value.forEach(item => {
item.serviceFee = calculateItemServiceFee(item);
@@ -1610,27 +1712,27 @@ const calculateAmounts = () => {
packageItems.value.forEach(item => {
// 计算项目原价金额
const originalAmount = (item.quantity || 1) * (item.unitPrice || 0.00);
// 应用折扣到项目金额
let discountedAmount = originalAmount;
if (discount.value && !isNaN(parseFloat(discount.value))) {
const discountRate = parseFloat(discount.value) / 100;
discountedAmount = originalAmount * (1 - discountRate);
}
// 更新项目金额
item.amount = parseFloat(discountedAmount.toFixed(2));
});
// 重新分配所有项目的服务费
redistributeServiceFee();
// 计算套餐总金额(基于项目的折扣后金额)
const totalAmount = packageItems.value.reduce((sum, item) => sum + (item.amount || 0), 0);
// 更新套餐金额
packageAmount.value = parseFloat(totalAmount.toFixed(2));
// 计算套餐总服务费
if (generateServiceFee.value) {
serviceFee.value = parseFloat(packageItems.value.reduce((sum, item) => sum + (item.serviceFee || 0), 0).toFixed(2));
@@ -1724,12 +1826,12 @@ const handleConfirm = (row) => {
ElMessage.warning('请选择执行科室');
return;
}
// 注意:编码唯一性由后端统一校验;这里不做硬编码拦截(避免误判)
// 去除code字段的前后空格确保唯一性验证准确
submitData.code = submitData.code.trim();
// 区分新增和更新操作:使用 temp_ 前缀的临时ID判断避免时间戳阈值在不同年份失效
if (isTempId(row.id)) { // 新增的临时ID
// 处理 parentId如果是子类且 parentId 是临时ID需要先找到父类的真实 id
@@ -1740,7 +1842,7 @@ const handleConfirm = (row) => {
const parts = parseCodeParts(submitData.code);
if (parts.subRaw) {
const parentCode = parts.mainRaw;
const parent = tableData.value.find(r =>
const parent = tableData.value.find(r =>
!isTempId(r.id) && (r?.code ?? '').toString().trim() === parentCode
);
if (parent) {
@@ -1751,11 +1853,11 @@ const handleConfirm = (row) => {
}
}
}
// 新增数据时移除临时ID让后端自动生成主键
const { id, ...newData } = submitData;
newData.parentId = finalParentId || null; // 确保 parentId 正确设置(大类为 null子类为父类 id
addInspectionType(newData).then(response => {
ElMessage.success('新增成功');
getInspectionTypeList();
@@ -1778,7 +1880,7 @@ const handleConfirm = (row) => {
}
});
}
editingRowId.value = null;
};
@@ -1826,7 +1928,7 @@ const handleAdd = (row, index) => {
tableData.value.splice(index + 1, 0, newRow);
editingRowId.value = newRow.id;
};
const handleDelete = (id) => {
ElMessageBox.confirm('确定要删除该检验类型吗?', '提示', {
confirmButtonText: '确定',
@@ -1873,7 +1975,7 @@ const addNewItem = () => {
amount: 0.00,
sortOrder: inspectionItems.value.length + 1,
serviceRange: '全部',
sub医技Type: '',
subItemName: '',
remark: '',
status: true
};
@@ -1881,19 +1983,47 @@ const addNewItem = () => {
editingRowId.value = newItem.id;
};
const editItem = (item) => {
const editItem = async (item) => {
if (editingRowId.value === item.id) {
// 如果当前行已经在编辑模式,点击编辑按钮则保存
saveItem(item);
} else {
// 否则进入编辑模式
editingRowId.value = item.id;
return;
}
// 初始化行内属性
if (!item.subItemOptions) item.subItemOptions = [];
// 并行预加载:费用套餐列表 + 当前行的子类列表
const tasks = [];
if (feePackages.value.length === 0) {
tasks.push(getFeePackages());
}
if (item.inspectionTypeId && item.subItemOptions.length === 0) {
item.loadingSubItems = true;
tasks.push(
fetchInspectionTypesRequest({ parentId: item.inspectionTypeId }).then(res => {
let subData = [];
if (res.code === 200) {
if (res.data && Array.isArray(res.data)) subData = res.data;
else if (res.data && res.data.rows) subData = res.data.rows;
else if (res.data && res.data.data && Array.isArray(res.data.data)) subData = res.data.data;
}
item.subItemOptions = subData;
}).finally(() => {
item.loadingSubItems = false;
})
);
}
// 等所有数据加载完再切换编辑态,确保 el-select 能回显正确文字
await Promise.all(tasks);
editingRowId.value = item.id;
};
const updateAmountFromPackage = (item) => {
if (item.feePackageId) {
const selectedPackage = feePackages.value.find(pkg => pkg.id === item.feePackageId);
const selectedPackage = feePackages.value.find(pkg => String(pkg.id) === String(item.feePackageId));
if (selectedPackage) {
// 套餐总金额 = 套餐金额 + 服务费
const packageAmount = parseFloat(selectedPackage.packageAmount || 0);
@@ -1911,42 +2041,51 @@ const saveItem = async (item) => {
ElMessage.error('小类编码不能为空');
return;
}
// 验证小类编码格式4位数字
const codeRegex = /^\d{4}$/;
if (!codeRegex.test(item.code.trim())) {
ElMessage.error('小类编码必须为4位数字');
return;
}
if (!item.name || item.name.trim() === '') {
ElMessage.error('小类项目名称不能为空');
return;
}
if (!item.inspectionTypeId) {
ElMessage.error('检验类型不能为空');
return;
}
if (!item.sampleType) {
ElMessage.error('样本类型不能为空');
return;
}
// 验证小类编码唯一性
const isDuplicate = inspectionItems.value.some(i =>
const isDuplicate = inspectionItems.value.some(i =>
i.id !== item.id && i.code.trim() === item.code.trim()
);
if (isDuplicate) {
ElMessage.error('小类编码已存在');
return;
}
// 【新增】验证小类项目名称唯一性
// 逻辑:遍历列表,排除当前正在编辑的行(id不同),且名称(trim后)相同
const isDuplicateName = inspectionItems.value.some(i =>
i.id !== item.id && i.name.trim() === item.name.trim()
);
if (isDuplicateName) {
ElMessage.error('小类项目名称已存在');
return;
}
// 从费用套餐获取金额
updateAmountFromPackage(item);
try {
// 准备提交给后端的数据
const submitData = {
@@ -1964,7 +2103,7 @@ const saveItem = async (item) => {
sortOrder: item.sortOrder ? parseInt(item.sortOrder) : null,
serviceRange: item.serviceRange || '全部'
};
// 判断是新增还是更新
if (typeof item.id === 'number') { // 临时ID数字类型新增
const response = await addDiagnosisTreatment(submitData);
@@ -1984,7 +2123,7 @@ const saveItem = async (item) => {
ElMessage.error(response.msg || '更新失败');
}
}
editingRowId.value = null;
} catch (error) {
console.error('保存检验项目失败:', error);
@@ -2015,8 +2154,8 @@ const deleteItem = async (id) => {
};
const cancelEdit = (item) => {
// 如果是新添加的行,则直接删除
if (item.id.toString().length > 10) { // 临时ID使用Date.now()生成
// 如果是新添加的行,则直接删除临时ID为数字类型
if (typeof item.id === 'number') {
const index = inspectionItems.value.findIndex(i => i.id === item.id);
if (index !== -1) {
inspectionItems.value.splice(index, 1);
@@ -2048,19 +2187,19 @@ const exportTable = () => {
// 创建Blob对象
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
// 创建下载链接
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `检验项目导出_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
// 添加到DOM并触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success('导出成功');
};
@@ -2370,19 +2509,21 @@ const handlePackageManagement = () => {
const refreshPage = () => {
getInspectionTypeList();
// 刷新时也重新加载套餐项目
loadPackageItemsFromAPI();
// loadPackageItemsFromAPI();
};
// 页面加载时获取数据
onMounted(() => {
getInspectionTypeList();
getLisGroupList();
// 加载检验套餐明细项目
loadPackageItemsFromAPI();
// 初始化计算套餐金额和服务费
calculateAmounts();
// // 加载检验套餐明细项目
// loadPackageItemsFromAPI();
// // 初始化计算套餐金额和服务费
// calculateAmounts();
fetchTenantList(); // 页面加载时获取租户列表
});
// 监听检验分类代码,当字典数据加载完成后加载检验项目数据
watch(activity_category_code, (newVal) => {
if (newVal && newVal.length > 0 && inspectionItems.value.length === 0) {