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

This commit is contained in:
2026-03-30 11:25:08 +08:00
26 changed files with 587 additions and 143 deletions

View File

@@ -0,0 +1,25 @@
import request from '@/utils/request'
/**
* 获取预约配置
* @returns
*/
export function getAppointmentConfig() {
return request({
url: '/dept/config',
method: 'get'
})
}
/**
* 保存预约配置
* @param {Object} data - 预约配置数据
* @returns
*/
export function saveAppointmentConfig(data) {
return request({
url: '/dept/config',
method: 'post',
data: data
})
}

View File

@@ -100,9 +100,9 @@
<el-form label-position="top" :model="appointmentSettingForm">
<el-form-item label="取消预约时间类型">
<el-select v-model="appointmentSettingForm.cancelAppointmentType" placeholder="请选择" style="width: 200px">
<el-option label="年" value=""></el-option>
<el-option label="月" value=""></el-option>
<el-option label="日" value=""></el-option>
<el-option label="年" value="YEAR"></el-option>
<el-option label="月" value="MONTH"></el-option>
<el-option label="日" value="DAY"></el-option>
</el-select>
</el-form-item>
<el-form-item label="取消预约次数">
@@ -390,6 +390,7 @@ import {useRouter} from 'vue-router'
import {ElDialog, ElForm, ElFormItem, ElInput, ElMessage, ElMessageBox, ElOption, ElSelect} from 'element-plus'
import {DocumentRemove, EditPen, View, Delete} from '@element-plus/icons-vue'
import {listDept, searchDept} from '@/api/appoinmentmanage/dept'
import {getAppointmentConfig, saveAppointmentConfig} from '@/api/appoinmentmanage/appointmentConfig'
import {getLocationTree, getPractitionerMetadata, getHealthcareMetadata} from '@/views/charge/outpatientregistration/components/outpatientregistration'
import {addDoctorSchedule, addDoctorScheduleWithDate, updateDoctorSchedule, deleteDoctorSchedule, getRegisterOrganizations, getDoctorScheduleListByDeptId, getDoctorScheduleListByDeptIdAndDateRange} from './api'
import {getClinicRoomList} from '@/api/appoinmentmanage/clinicRoom'
@@ -922,16 +923,51 @@ const handleReset = async () => {
}
// 预约设置弹窗显示
const handleAppointmentSetting = () => {
const handleAppointmentSetting = async () => {
// 获取当前机构的预约配置
try {
const res = await getAppointmentConfig()
if (res.code === 200 && res.data) {
// 回显已有配置
appointmentSettingForm.value = {
cancelAppointmentType: res.data.cancelAppointmentType || 'YEAR',
cancelAppointmentCount: res.data.cancelAppointmentCount || 0
}
} else {
// 无配置时使用默认值
appointmentSettingForm.value = {
cancelAppointmentType: 'YEAR',
cancelAppointmentCount: 0
}
}
} catch (error) {
console.error('获取预约配置失败:', error)
appointmentSettingForm.value = {
cancelAppointmentType: 'YEAR',
cancelAppointmentCount: 0
}
}
appointmentSettingDialog.value = true
}
// 预约设置确定
const handleAppointmentSettingConfirm = () => {
// 这里可以添加表单验证和提交逻辑
console.log('预约设置提交:', appointmentSettingForm.value)
ElMessage.success('预约设置保存成功')
appointmentSettingDialog.value = false
const handleAppointmentSettingConfirm = async () => {
try {
const res = await saveAppointmentConfig({
cancelAppointmentType: appointmentSettingForm.value.cancelAppointmentType,
cancelAppointmentCount: appointmentSettingForm.value.cancelAppointmentCount,
validFlag: 1
})
if (res.code === 200) {
ElMessage.success('预约设置保存成功')
appointmentSettingDialog.value = false
} else {
ElMessage.error(res.msg || '保存失败')
}
} catch (error) {
console.error('保存预约配置失败:', error)
ElMessage.error('保存失败')
}
}
// 预约设置取消

View File

@@ -11,7 +11,7 @@
<el-form-item label="所属机构" prop="institution" class="filter-item">
<el-select
v-model="queryParams.institution"
placeholder="演示医院"
placeholder="请选择"
clearable
style="width: 150px"
:popper-append-to-body="false"

View File

@@ -510,8 +510,7 @@ export default {
this.closeContextMenu();
}
}).catch(error => {
const errorMsg = error.message || '取消预约失败,请稍后重试';
ElMessage.error(`取消预约失败:${errorMsg}`);
console.error('取消预约失败:', error);
this.closeContextMenu();
});
},
@@ -590,12 +589,10 @@ export default {
ElMessage.success('预约成功,号源已锁定。患者到院签到时需缴费取号。');
}).catch(error => {
// 显示具体的错误信息
const errorMessage = error.response?.data?.msg || error.response?.data?.message || '预约失败,请稍后重试。';
ElMessage.error(errorMessage);
console.error('预约失败:', error);
});
} catch (error) {
ElMessage.error('预约信息格式错误,请重新操作。');
console.error('操作异常:', error);
}
},
// 切换侧边栏显示/隐藏

View File

@@ -628,11 +628,11 @@ async function handleSaveDiagnosis() {
// 开始加载状态,防止重复提交
saveLoading.value = true;
// 保存前按排序号排序,并转换日期格式
// 保存前按排序号排序,并转换日期格式为ISO字符串
const diagnosisChildList = form.value.diagnosisList.map(item => ({
...item,
onsetDate: item.onsetDate ? new Date(item.onsetDate) : null,
diagnosisTime: item.diagnosisTime ? new Date(item.diagnosisTime) : null
onsetDate: item.onsetDate ? new Date(item.onsetDate).toISOString() : null,
diagnosisTime: item.diagnosisTime ? new Date(item.diagnosisTime).toISOString() : null
}));
// 调用保存诊断接口

View File

@@ -194,10 +194,13 @@ import {ElMessage} from 'element-plus';
import {getLocationTree} from '@/views/charge/outpatientregistration/components/outpatientregistration';
import {listInspectionPackage, delInspectionPackage} from '@/api/system/inspectionPackage';
import { getTenantPage } from '@/api/system/tenant';
import useTagsViewStore from '@/store/modules/tagsView';
// 创建路由实例
const router = useRouter();
const tagsViewStore = useTagsViewStore();
// 侧边栏状态
const sidebarActive = ref(false);
@@ -207,7 +210,6 @@ const departments = ref([]);
// 获取科室数据 - 与门诊挂号页面保持一致
function getDepartmentList() {
console.log('调用getLocationTree API...');
getLocationTree().then((response) => {
@@ -430,13 +432,17 @@ const fetchTenantList = async () => {
// 初始化数据
// 整合后的 onMounted 钩子
onMounted(async () => {
// 1. 加载科室数据
// 1. 加载科室数据(不需要等待)
getDepartmentList();
// 2. 加载机构列表 (包含默认选中逻辑)
await fetchTenantList();
// 2. 并行发起:机构列表 + 表格数据,减少串行等待
const tenantPromise = fetchTenantList();
// 3. 等待 DOM 更新
// 先用默认参数(不带 tenantId立刻加载表格让用户尽快看到数据
loadData();
// 3. 等待机构列表加载完成
await tenantPromise;
await nextTick();
// 4. 防御性检查
@@ -444,9 +450,10 @@ onMounted(async () => {
selectedTenantId.value = tenantOptions.value[0].value;
}
// 5. 加载表格数据
loadData();
// 5. 如果选中了机构,用 tenantId 重新过滤一次
if (selectedTenantId.value) {
loadData();
}
});
// 过滤后的数据 - 现在直接从API获取这里保留前端过滤作为补充
@@ -583,51 +590,27 @@ const pageButtons = computed(() => {
// 处理新增
function handleAdd() {
router.push({
path: '/maintainSystem/Inspection',
query: {
tab: '2',
mode: 'add'
}
});
window.location.href = '/maintainSystem/Inspection?tab=2&mode=add';
}
// 处理编辑
function handleEdit(item) {
// 跳转到套餐设置主界面并传递套餐ID用于加载数据
// 后端接口使用 basicInformationId 作为路径参数
const packageId = item.basicInformationId || item.id;
if (!packageId) {
ElMessage.error('无法获取套餐ID请刷新页面后重试');
return;
}
router.push({
path: '/maintainSystem/Inspection',
query: {
tab: '2',
packageId: packageId,
mode: 'edit'
}
});
window.location.href = `/maintainSystem/Inspection?tab=2&mode=edit&packageId=${packageId}`;
}
// 处理查看
function handleView(item) {
// 跳转到套餐设置主界面并传递套餐ID用于加载数据
// 后端接口使用 basicInformationId 作为路径参数
const packageId = item.basicInformationId || item.id;
if (!packageId) {
ElMessage.error('无法获取套餐ID请刷新页面后重试');
return;
}
router.push({
path: '/maintainSystem/Inspection',
query: {
tab: '2',
packageId: packageId,
mode: 'view'
}
});
window.location.href = `/maintainSystem/Inspection?tab=2&mode=view&packageId=${packageId}`;
}
// 处理删除

View File

@@ -813,7 +813,8 @@ import {
updateInspectionPackage,
getInspectionPackage,
listInspectionPackageDetails,
saveInspectionPackageDetails
saveInspectionPackageDetails,
batchSaveInspectionPackageDetails
} from '@/api/system/inspectionPackage';
import { getTenantPage } from '@/api/system/tenant';
import {deptTreeSelect} from '@/api/system/user';
@@ -1470,6 +1471,8 @@ const packageItems = ref([]);
const packageMode = ref('add');
// 当前编辑的套餐ID用于编辑和查看模式
const currentPackageId = ref(null);
// 正在从后端加载套餐数据的标志,加载期间跳过 calculateAmounts 防止覆盖后端金额
const isLoadingPackage = ref(false);
// 是否为查看模式(只读)
const isViewMode = computed(() => packageMode.value === 'view');
@@ -2364,6 +2367,10 @@ const savePackageData = async (basicInfo, detailData) => {
if (isEditMode) {
// 编辑模式调用更新API
basicResponse = await updateInspectionPackage(basicInfo);
if (basicResponse.code !== 200) {
loading.close();
throw new Error(basicResponse.msg || '更新基本信息失败');
}
packageId = currentPackageId.value;
} else {
// 新增模式调用新增API
@@ -2390,29 +2397,41 @@ const savePackageData = async (basicInfo, detailData) => {
// 验证套餐ID是否存在
if (!packageId) {
loading.close();
console.error('无法从响应中获取套餐ID完整响应:', basicResponse);
throw new Error('保存成功但未返回套餐ID。请检查后端接口是否正确返回了packageId或id字段');
}
}
// 2. 分别保存每个明细数据到明细表,并将后端返回的 id 回填到 packageItems
for (let i = 0; i < detailData.length; i++) {
const detailItem = {
...detailData[i],
packageId: packageId
// 2. 保存明细数据:编辑模式用批量接口(先删旧后插新),新增模式逐条新增
if (isEditMode) {
// 编辑模式:调用批量保存接口,后端会先删除旧明细再插入新明细
const batchPayload = {
basicInformationId: packageId,
details: detailData.map(item => ({ ...item, packageId: packageId }))
};
const detailResponse = await saveInspectionPackageDetails(detailItem);
if (detailResponse.code !== 200) {
const batchResponse = await batchSaveInspectionPackageDetails(batchPayload);
if (batchResponse.code !== 200) {
loading.close();
throw new Error(`保存第 ${i + 1} 个明细项失败: ${detailResponse.msg || '未知错误'}`);
throw new Error(batchResponse.msg || '保存明细数据失败');
}
} else {
// 新增模式:逐条保存并回填后端生成的明细 id
for (let i = 0; i < detailData.length; i++) {
const detailItem = {
...detailData[i],
packageId: packageId
};
// 回填后端生成的明细 id防止取消编辑时被误判为新增行
// 后端返回字段名是 detailId见 InspectionPackageDetail.java
if (detailResponse.data && (detailResponse.data.detailId || detailResponse.data.id)) {
packageItems.value[i].id = detailResponse.data.detailId || detailResponse.data.id;
const detailResponse = await saveInspectionPackageDetails(detailItem);
if (detailResponse.code !== 200) {
loading.close();
throw new Error(`保存第 ${i + 1} 个明细项失败: ${detailResponse.msg || '未知错误'}`);
}
// 回填后端生成的明细 id防止取消编辑时被误判为新增行
if (detailResponse.data && (detailResponse.data.detailId || detailResponse.data.id)) {
packageItems.value[i].id = detailResponse.data.detailId || detailResponse.data.id;
}
}
}
@@ -2421,8 +2440,14 @@ const savePackageData = async (basicInfo, detailData) => {
ElMessage.success('保存成功');
// 保存成功后重置表单
doResetForm();
if (isEditMode) {
// 编辑模式:重新加载最新数据,保持在编辑页面
loadInspectionPackage(String(packageId));
} else {
// 新增模式:保存成功后重置表单
doResetForm();
}
} catch (error) {
// 确保在错误时也关闭loading
@@ -2495,7 +2520,7 @@ const doResetForm = () => {
// 清空明细数据
packageItems.value = [];
// 重置模式为新增
// 重置为新增模式(仅在外部未指定 mode 时,由调用方负责设置 packageMode
packageMode.value = 'add';
currentPackageId.value = null;
@@ -2527,6 +2552,9 @@ const loadInspectionPackage = async (packageId) => {
const basicData = basicResponse.data;
const detailData = detailResponse.data || [];
// 【关键】暂停 packageItems 的 watch防止赋值时触发 calculateAmounts 覆盖后端返回的金额
isLoadingPackage.value = true;
// 填充基本信息
packageLevel.value = basicData.packageLevel;
packageName.value = basicData.packageName;
@@ -2536,8 +2564,8 @@ const loadInspectionPackage = async (packageId) => {
showPackageName.value = basicData.showPackageName !== false;
generateServiceFee.value = basicData.generateServiceFee !== false;
enablePackagePrice.value = basicData.enablePackagePrice !== false;
packageAmount.value = basicData.packageAmount || 0.00;
serviceFee.value = basicData.serviceFee || 0.00;
packageAmount.value = parseFloat(basicData.packageAmount || 0);
serviceFee.value = parseFloat(basicData.serviceFee || 0);
selectedLisGroup.value = basicData.lisGroup || '';
bloodVolume.value = basicData.bloodVolume || '';
remarks.value = basicData.remarks || '';
@@ -2559,11 +2587,15 @@ const loadInspectionPackage = async (packageId) => {
origin: item.origin || ''
}));
// 恢复监听
isLoadingPackage.value = false;
loading.close();
ElMessage.success('套餐数据加载成功');
} catch (error) {
console.error('加载套餐数据失败:', error);
isLoadingPackage.value = false;
ElMessage.error(error.message || '加载套餐数据失败');
}
};
@@ -2597,47 +2629,43 @@ watch(activity_category_code, (newVal) => {
}, { immediate: true });
/**
* 关键修复:
* 从“套餐管理”跳转到这里时,通常只是改变 querypackageId/mode/tab组件不会重新挂载
* 所以仅靠 onMounted 读取一次 query 会导致“查看/修改”出现空白,刷新才正常。
* 这里监听 query 变化,自动切换 tab 并加载套餐数据。
* 统一监听 query 变化,处理 tab 切换和套餐数据加载。
* 使用 watch(route) 深度监听,确保在 keep-alive 场景下同路由 query 变化也能响应。
*/
watch(
() => route.query.tab,
(tab) => {
if (tab === '0' || tab === '1' || tab === '2') {
activeNav.value = parseInt(String(tab));
}
},
{ immediate: true }
);
const applyRouteForPackage = async (query) => {
const tab = query?.tab;
const packageId = query?.packageId;
const mode = query?.mode || 'add';
// 设置当前模式和套餐ID
packageMode.value = mode;
currentPackageId.value = packageId ? String(packageId) : null;
// 如果是新增模式,重置表单
// 先切换 tab
if (tab === '0' || tab === '1' || tab === '2') {
activeNav.value = parseInt(tab);
};
// 只有 tab=2套餐设置才处理套餐数据
if (activeNav.value !== 2) return;
// 新增模式:重置表单,然后明确设置 mode
if (mode === 'add' || !packageId) {
activeNav.value = 2;
doResetForm();
doResetForm(); // 内部会设 packageMode='add',符合预期
return;
}
activeNav.value = 2;
// 编辑/查看模式:先设置 mode 和 ID再加载数据
packageMode.value = mode;
currentPackageId.value = String(packageId);
await nextTick();
loadInspectionPackage(String(packageId));
};
// 监听整个 route.query确保任何 query 变化tab/packageId/mode都能触发
watch(
() => route.query.packageId,
async () => {
await applyRouteForPackage(route.query);
() => route.query,
(newQuery) => {
applyRouteForPackage(newQuery);
},
{ immediate: true, flush: 'post' }
{ immediate: true, deep: true }
);
// 兜底:如果该页面被 keep-alive 缓存,从别的页面返回时不会触发 onMounted
@@ -2652,11 +2680,14 @@ onBeforeRouteUpdate((to) => {
// 监听生成服务费选项变更
watch(generateServiceFee, (newVal) => {
if (isLoadingPackage.value) return;
calculateAmounts();
});
// 监听套餐项目变化
watch(packageItems, (newVal) => {
// 加载期间跳过计算,防止覆盖后端返回的金额
if (isLoadingPackage.value) return;
calculateAmounts();
}, { deep: true });
// 样本类型数据

View File

@@ -152,11 +152,11 @@
<el-form-item label="服务费">
<el-input-number
v-model="formData.serviceFee"
:precision="2"
:precision="2"
:min="0"
placeholder="请输入服务费"
placeholder="自动合计"
style="width: 100%"
:disabled="isReadOnly"
disabled
/>
</el-form-item>
</el-col>
@@ -281,6 +281,12 @@
<span v-else>{{ row.quantity }}</span>
</template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="80" align="center">
<template #default="{ row }">
<span v-if="row.editing">{{ row.unit || '-' }}</span>
<span v-else>{{ row.unit || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价" width="150" align="center">
<template #default="{ row }">
<el-input
@@ -309,7 +315,7 @@
placeholder="服务费"
style="width: 100%"
:controls="false"
@change="calculateTotal(row)"
@input="(val) => handleServiceChargeInput(val, row)"
/>
<span v-else>{{ row.serviceCharge?.toFixed(2) || '0.00' }}</span>
</template>
@@ -539,6 +545,7 @@ function loadPackageData(data) {
days: item.days || '',
quantity: item.quantity || 1,
unitPrice: item.unitPrice || 0,
unit: item.unit || '',
amount: item.amount || 0,
serviceCharge: item.serviceCharge || 0,
total: item.total || 0,
@@ -553,6 +560,9 @@ function loadPackageData(data) {
console.log('formData 加载后:', formData)
console.log('detailData 加载后:', detailData.value)
// 加载数据后自动计算总服务费
calculateTotalServiceFee()
}
onMounted(async () => {
@@ -781,10 +791,12 @@ async function loadDiagnosisTreatmentItem(itemId, itemData) {
async function loadDiagnosisTreatmentList(forceRefresh = false) {
// 如果不是强制刷新且已有数据且未过期,直接返回
if (!forceRefresh && diagnosisTreatmentList.value.length > 0) {
return
// 由于缓存过期时间改为0始终视为过期需要重新加载
// 这里直接跳过,不返回,让它重新加载
}
// 从session缓存读取
let useCache = false
try {
const cachedData = cache.session.getJSON(DIAGNOSIS_TREATMENT_CACHE_KEY)
if (cachedData && cachedData.timestamp) {
@@ -812,7 +824,7 @@ async function loadDiagnosisTreatmentList(forceRefresh = false) {
if (allItems.length > 0) {
diagnosisTreatmentList.value = allItems
// 保存到缓存
cache.session.setJSON(DIAGNOSIS_TREATMENT_CACHE_KEY, {
data: allItems,
@@ -877,6 +889,7 @@ function handleAddRow() {
frequency: '',
days: '',
quantity: 1,
unit: '',
unitPrice: 0,
amount: 0,
serviceCharge: 0,
@@ -914,6 +927,7 @@ function handleDeleteRow(index) {
}).then(() => {
detailData.value.splice(index, 1)
calculatePackagePrice()
calculateTotalServiceFee()
ElMessage.success('删除成功')
}).catch(() => {})
}
@@ -1027,7 +1041,9 @@ function handleItemSelect(row) {
row.itemName = item.name || item.itemName || ''
row.code = item.busNo || item.code || item.itemCode || ''
row.unitPrice = parseFloat(item.retailPrice || item.unitPrice || item.price || 0)
console.log('设置单价:', row.unitPrice)
// permittedUnitCode_dictText是字典翻译后的值permittedUnitCode是后端返回的原始值
row.unit = item.permittedUnitCode_dictText || item.permittedUnitCode || ''
// 缓存选中的项目
loadDiagnosisTreatmentItem(row.itemId, item)
@@ -1044,10 +1060,23 @@ function calculateAmount(row) {
calculateTotal(row)
}
// 处理服务费输入
function handleServiceChargeInput(val, row) {
row.serviceCharge = val || 0
calculateTotal(row)
}
// 计算总金额
function calculateTotal(row) {
row.total = (row.amount || 0) + (row.serviceCharge || 0)
calculatePackagePrice()
calculateTotalServiceFee()
}
// 计算总服务费(合计所有明细行的服务费)
function calculateTotalServiceFee() {
const totalServiceFee = detailData.value.reduce((sum, item) => sum + (item.serviceCharge || 0), 0)
formData.serviceFee = totalServiceFee
}
// 计算套餐金额(应用折扣)
@@ -1203,6 +1232,7 @@ async function handleSave() {
frequency: item.frequency || '',
days: item.days || '',
quantity: parseInt(item.quantity) || 1,
unit: item.unit || '',
unitPrice: parseFloat(item.unitPrice) || 0,
amount: parseFloat(item.amount) || 0,
serviceCharge: parseFloat(item.serviceCharge) || 0,