Fix Bug #550: AI修复

This commit is contained in:
2026-05-27 03:00:08 +08:00
parent 8e6cb5c79f
commit 16c42ca108
5433 changed files with 171 additions and 778731 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,556 +0,0 @@
<template>
<div class="app-container">
<div class="config-header">
<el-button type="primary" @click="handleDefault">默认(M)</el-button>
<el-button type="success" @click="handleSave">保存(S)</el-button>
<el-button type="danger" @click="handleClose">关闭(X)</el-button>
</div>
<el-tabs v-model="activeTab" class="config-tabs">
<el-tab-pane label="划价收费1" name="tab1">
<div class="tab-content">划价收费1相关配置</div>
</el-tab-pane>
<el-tab-pane label="划价收费2" name="tab2">
<div class="tab-content">划价收费2相关配置</div>
</el-tab-pane>
<el-tab-pane label="挂号处理" name="tab3">
<el-form ref="formRef" :model="formData" label-width="150px" class="config-form compact-form">
<!-- 第一行 -->
<div class="form-row">
<el-form-item label="病历本费用(元)" prop="medicalRecordFee">
<el-input-number v-model="formData.medicalRecordFee" style="width: 150px" :controls-position="'right'" :step="1" :min="0" />
</el-form-item>
<el-form-item label="" prop="medicalRecordFlag" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.medicalRecordFlag" style="margin-right: 8px;" />
<span>病历费入账标志</span>
</div>
</template>
</el-form-item>
</div>
<!-- 第二行 -->
<div class="form-row">
<el-form-item label="就诊卡费(元)" prop="patientCardFee">
<el-input-number v-model="formData.patientCardFee" style="width: 150px" :controls-position="'right'" :step="1" :min="0" />
</el-form-item>
<el-form-item label="" prop="isNightShift" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.isNightShift" style="margin-right: 8px;" />
<span>是否启用晚班</span>
</div>
</template>
</el-form-item>
</div>
<!-- 第三行 -->
<div class="form-row">
<el-form-item label="" prop="patientCardFlag" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.patientCardFlag" style="margin-right: 8px;" />
<span>就诊卡记账标志</span>
</div>
</template>
</el-form-item>
<el-form-item label="上午接诊起始时间" prop="morningStartTime">
<el-input v-model="formData.morningStartTime" style="width: 150px" />
</el-form-item>
</div>
<!-- 第四行 -->
<div class="form-row">
<el-form-item label="" prop="autoGenerateOutpatientNo" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.autoGenerateOutpatientNo" style="margin-right: 8px;" />
<span>自动产生门诊号</span>
</div>
</template>
</el-form-item>
<el-form-item label="" prop="allowModifyOutpatientNo" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.allowModifyOutpatientNo" style="margin-right: 8px;" />
<span>建档时是否允许修改门诊号</span>
</div>
</template>
</el-form-item>
</div>
<!-- 第五行 -->
<div class="form-row">
<el-form-item label="下午起始时间" prop="afternoonStartTime">
<el-input v-model="formData.afternoonStartTime" style="width: 150px" />
</el-form-item>
<el-form-item label="晚上起始时间" prop="eveningStartTime" v-if="formData.isNightShift">
<el-input v-model="formData.eveningStartTime" style="width: 150px" />
</el-form-item>
</div>
<!-- 第六行 -->
<div class="form-row">
<el-form-item label="挂号有效期(天)" prop="registrationValidity">
<el-input-number v-model="formData.registrationValidity" style="width: 150px" :controls-position="'right'" :step="1" :min="0" />
</el-form-item>
<el-form-item label="挂号单据模式" prop="registrationDocumentMode">
<el-select v-model="formData.registrationDocumentMode" style="width: 150px">
<el-option label="使用发票" value="使用发票" />
<el-option label="普通单据" value="其他模式" />
</el-select>
</el-form-item>
</div>
<!-- 第七行 -->
<div class="form-row">
<el-form-item label="" prop="exemptFlag" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.exemptFlag" style="margin-right: 8px;" />
<span>减免标志</span>
</div>
</template>
</el-form-item>
<el-form-item label="" prop="consultationFlag" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.consultationFlag" style="margin-right: 8px;" />
<span>义诊标志</span>
</div>
</template>
</el-form-item>
<el-form-item label="" prop="enableHolidayFeeFloat" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.enableHolidayFeeFloat" style="margin-right: 8px;" />
<span>启用法定节假日挂号费浮动</span>
</div>
</template>
</el-form-item>
</div>
<!-- 第八行 -->
<div class="form-row">
<el-form-item label="监护人规定年龄(岁)" prop="guardianAge">
<el-input-number v-model="formData.guardianAge" style="width: 150px" :controls-position="'right'" :step="1" :min="0" />
</el-form-item>
<el-form-item label="" prop="enableDoubleScreen" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.enableDoubleScreen" style="margin-right: 8px;" />
<span>门诊挂号启用双屏</span>
</div>
</template>
</el-form-item>
</div>
<!-- 第九行 -->
<div class="form-row">
<el-form-item label="" prop="optionalRegistrationType" checkbox-label>
<template #label>
<div class="checkbox-label-container">
<el-checkbox v-model="formData.optionalRegistrationType" style="margin-right: 8px;" />
<span>挂号类型可选择</span>
</div>
</template>
</el-form-item>
</div>
</el-form>
</el-tab-pane>
<el-tab-pane label="挂号预约" name="tab4">
<div class="tab-content">挂号预约相关配置</div>
</el-tab-pane>
<el-tab-pane label="打印设置" name="tab5">
<el-checkbox v-model="formData.isPrint">是否打印挂号单</el-checkbox>
</el-tab-pane>
<el-tab-pane label="其他选项" name="tab6">
<div class="tab-content">其他选项相关配置</div>
</el-tab-pane>
<el-tab-pane label="病人账户" name="tab7">
<div class="tab-content">病人账户相关配置</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import {onMounted, reactive, ref} from 'vue';
import {useRouter} from 'vue-router';
import {ElMessage} from 'element-plus';
import {addConfig, listConfig, updateConfig} from '@/api/system/config';
const router = useRouter();
const formRef = ref(null);
const activeTab = ref('tab1');
// 表单数据
const formData = reactive({
medicalRecordFee: '1',
patientCardFee: '1',
medicalRecordFlag: true,
patientCardFlag: false,
autoGenerateOutpatientNo: true,
allowModifyOutpatientNo: false,
enableHolidayFeeFloat: true,
enableDoubleScreen: true,
isNightShift: false,
exemptFlag: false,
consultationFlag: false,
optionalRegistrationType: false,
morningStartTime: '08:00',
afternoonStartTime: '13:00',
eveningStartTime: '22:00',
registrationValidity: '5',
registrationDocumentMode: '使用发票',
guardianAge: '16',
isPrint: false,
});
// 加载配置数据
const loadConfigData = async (showSuccessMessage = true) => {
try {
// 调用系统配置API获取数据不设置configType过滤条件以获取所有配置
const response = await listConfig({ pageSize: 1000 });
// 处理响应数据,兼容不同的返回格式
let configs = [];
if (response && response.data) {
if (Array.isArray(response.data)) {
configs = response.data;
} else if (response.data.rows && Array.isArray(response.data.rows)) {
configs = response.data.rows;
} else {
console.error('返回数据格式不符合预期:', response);
}
} else if (response && response.rows && Array.isArray(response.rows)) {
configs = response.rows;
} else {
console.error('API返回空响应:', response);
}
console.log('loadConfigData - 获取到的配置列表:', configs);
console.log('loadConfigData - 配置总数:', configs.length);
// 将配置数据映射到表单
if (configs && configs.length > 0) {
configs.forEach(config => {
const { configKey, configValue } = config;
// 处理布尔类型字段
if (configKey in formData) {
if (typeof formData[configKey] === 'boolean') {
formData[configKey] = configValue === '1';
} else {
formData[configKey] = configValue;
}
}
});
}
if (showSuccessMessage) {
ElMessage.success('配置数据加载成功');
}
} catch (error) {
console.error('加载配置数据失败:', error);
ElMessage.warning('无法加载配置数据,使用默认值');
}
};
// 组件挂载时加载数据
onMounted(() => {
loadConfigData();
});
// 默认按钮点击事件
const handleDefault = () => {
// 重置为默认值
Object.assign(formData, {
medicalRecordFee: '1',
patientCardFee: '1',
medicalRecordFlag: true,
patientCardFlag: false,
autoGenerateOutpatientNo: true,
allowModifyOutpatientNo: false,
enableHolidayFeeFloat: true,
enableDoubleScreen: true,
isNightShift: false,
exemptFlag: false,
consultationFlag: false,
optionalRegistrationType: false,
morningStartTime: '08:00',
afternoonStartTime: '13:00',
eveningStartTime: '22:00',
registrationValidity: '5',
registrationDocumentMode: '使用发票',
guardianAge: '16',
isPrint: false,
});
ElMessage.success('已恢复默认值');
};
// 保存按钮点击事件
const handleSave = async () => {
try {
// 这里可以添加表单验证逻辑
if (formRef.value) {
await formRef.value.validate();
}
// 将表单数据转换为系统配置格式,并添加必要的默认值
const configData = [
{ configKey: 'medicalRecordFee', configValue: formData.medicalRecordFee, configName: '病历本费用', configType: 'Y' },
{ configKey: 'patientCardFee', configValue: formData.patientCardFee, configName: '就诊卡费', configType: 'Y' },
{ configKey: 'medicalRecordFlag', configValue: formData.medicalRecordFlag ? '1' : '0', configName: '病历费入账标志', configType: 'Y' },
{ configKey: 'isNightShift', configValue: formData.isNightShift ? '1' : '0', configName: '是否启用晚班', configType: 'Y' },
{ configKey: 'patientCardFlag', configValue: formData.patientCardFlag ? '1' : '0', configName: '就诊卡记账标志', configType: 'Y' },
{ configKey: 'morningStartTime', configValue: formData.morningStartTime, configName: '上午接诊起始时间', configType: 'Y' },
{ configKey: 'autoGenerateOutpatientNo', configValue: formData.autoGenerateOutpatientNo ? '1' : '0', configName: '自动产生门诊号', configType: 'Y' },
{ configKey: 'allowModifyOutpatientNo', configValue: formData.allowModifyOutpatientNo ? '1' : '0', configName: '建档时是否允许修改门诊号', configType: 'Y' },
{ configKey: 'afternoonStartTime', configValue: formData.afternoonStartTime, configName: '下午起始时间', configType: 'Y' },
{ configKey: 'eveningStartTime', configValue: formData.eveningStartTime, configName: '晚上起始时间', configType: 'Y' },
{ configKey: 'registrationValidity', configValue: formData.registrationValidity, configName: '挂号有效期(天)', configType: 'Y' },
{ configKey: 'registrationDocumentMode', configValue: formData.registrationDocumentMode, configName: '挂号单据模式', configType: 'Y' },
{ configKey: 'exemptFlag', configValue: formData.exemptFlag ? '1' : '0', configName: '减免标志', configType: 'Y' },
{ configKey: 'consultationFlag', configValue: formData.consultationFlag ? '1' : '0', configName: '义诊标志', configType: 'Y' },
{ configKey: 'enableHolidayFeeFloat', configValue: formData.enableHolidayFeeFloat ? '1' : '0', configName: '启用法定节假日挂号费浮动', configType: 'Y' },
{ configKey: 'guardianAge', configValue: formData.guardianAge, configName: '监护人规定年龄(岁)', configType: 'Y' },
{ configKey: 'enableDoubleScreen', configValue: formData.enableDoubleScreen ? '1' : '0', configName: '门诊挂号启用双屏', configType: 'Y' },
{ configKey: 'optionalRegistrationType', configValue: formData.optionalRegistrationType ? '1' : '0', configName: '挂号类型可选择', configType: 'Y' },
{ configKey: 'isPrint', configValue: formData.isPrint ? '1' : '0', configName: '是否打印挂号单', configType: 'Y' },
];
let successCount = 0;
let failedParams = [];
try {
// 先获取所有配置避免重复调用API
const allConfigsResponse = await listConfig({ pageSize: 1000 });
console.log('handleSave - listConfig返回完整结果:', JSON.stringify(allConfigsResponse));
// 检查返回结果结构
let allConfigs = [];
if (allConfigsResponse.code === 200) {
if (allConfigsResponse.data && Array.isArray(allConfigsResponse.data)) {
allConfigs = allConfigsResponse.data;
} else if (allConfigsResponse.data && allConfigsResponse.data.rows) {
allConfigs = allConfigsResponse.data.rows;
} else if (Array.isArray(allConfigsResponse.rows)) {
allConfigs = allConfigsResponse.rows;
} else if (Array.isArray(allConfigsResponse)) {
allConfigs = allConfigsResponse;
}
}
console.log('handleSave - 最终获取到的所有配置:', allConfigs);
console.log('handleSave - 配置总数:', allConfigs.length);
// 构建配置项映射表,方便快速查找
const configMap = new Map();
allConfigs.forEach(config => {
configMap.set(config.configKey, config);
console.log('handleSave - 添加到映射表:', config.configKey);
});
// 调用系统配置API保存每个参数
for (const config of configData) {
try {
const existingConfig = configMap.get(config.configKey);
console.log(`处理参数: ${config.configName} (${config.configKey})`);
console.log(`现有配置:`, existingConfig);
console.log(`要保存的值:`, config.configValue);
if (existingConfig && existingConfig.configId) {
// 如果存在则更新保留原有数据的configId和创建时间
console.log(`更新参数 ${config.configKey}使用configId: ${existingConfig.configId}`);
const updateResult = await updateConfig({
...config,
configId: existingConfig.configId,
createTime: existingConfig.createTime,
remark: existingConfig.remark || '收费系统配置参数',
configType: existingConfig.configType || 'N'
});
console.log(`更新结果:`, updateResult);
} else {
// 如果不存在则新增,添加默认备注
console.log(`新增参数 ${config.configKey}`);
const addResult = await addConfig({
...config,
remark: '收费系统配置参数',
configType: 'N'
});
console.log(`新增结果:`, addResult);
}
successCount++;
} catch (paramError) {
console.error(`保存参数 ${config.configName} (${config.configKey}) 失败:`, paramError);
console.error(`错误详情:`, paramError.response || paramError);
failedParams.push(config.configName);
// 继续处理下一个参数,不中断整体流程
}
}
} catch (error) {
console.error('获取配置列表失败:', error);
ElMessage.error('获取配置列表失败,无法保存参数');
return;
}
// 根据保存结果显示相应消息
if (failedParams.length === 0) {
ElMessage.success(`保存成功`);
// 保存成功后重新加载数据,确保页面显示最新配置
loadConfigData(false); // 不显示加载成功消息
} else if (successCount > 0) {
ElMessage.warning(`${successCount} 个参数保存成功,但以下 ${failedParams.length} 个参数保存失败: ${failedParams.join(', ')}`);
// 部分成功也重新加载数据
loadConfigData(false); // 不显示加载成功消息
} else {
ElMessage.error(`所有参数保存失败,请检查系统配置`);
}
} catch (error) {
console.error('保存过程中发生错误:', error);
ElMessage.error('保存操作异常,请重试');
}
};
// 关闭按钮点击事件
const handleClose = () => {
router.back();
};
</script>
<style scoped>
.app-container {
padding: 5px;
}
.config-header {
margin-bottom: 5px;
}
.config-header .el-button {
margin-right: 10px;
}
.config-tabs {
margin-top: 0;
}
.config-form {
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.form-row {
display: flex;
align-items: center;
margin-bottom: 15px;
flex-wrap: nowrap; /* 禁止行内元素换行 */
overflow-x: hidden; /* 防止溢出 */
}
.config-form .el-form-item {
margin-bottom: 0;
margin-right: 60px; /* 增加间距 */
min-width: 350px; /* 增加最小宽度 */
display: flex;
align-items: center;
flex-shrink: 0; /* 禁止压缩 */
}
/* 紧凑表单样式 */
.compact-form {
label-width: 150px !important;
}
.compact-form .el-form-item {
margin-bottom: 10px;
}
/* 所有表单标签的统一样式 */
.config-form .el-form-item__label {
font-weight: 500;
width: 220px !important; /* 足够宽的标签宽度 */
text-align: right;
padding-right: 15px;
white-space: nowrap !important; /* 强制不换行 */
overflow: visible !important; /* 允许内容溢出(确保不截断) */
text-overflow: clip !important; /* 不显示省略号 */
flex-shrink: 0 !important; /* 禁止压缩 */
font-size: 14px !important; /* 统一字体大小 */
line-height: 32px !important; /* 统一行高 */
height: 32px !important; /* 统一高度 */
margin: 0 !important; /* 清除默认边距 */
padding: 0 15px 0 0 !important; /* 统一内边距 */
}
/* 复选框标签样式 */
.compact-form .el-form-item[checkbox-label] .el-form-item__label {
padding-right: 10px;
width: 150px !important;
text-align: right;
}
.compact-form .el-form-item[checkbox-label] .el-form-item__content {
margin-left: 0 !important;
}
/* 自定义复选框标签容器 */
.checkbox-label-container {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
height: 100%;
}
.checkbox-label-container span {
font-size: 14px;
white-space: nowrap;
}
/* 空标签的样式,用于对齐 */
.config-form .el-form-item__label:empty {
width: 18px !important;
padding-right: 5px !important;
}
.config-form .el-form-item--medium .el-form-item__content {
line-height: 32px;
flex-shrink: 0;
height: 32px;
}
.config-form .el-checkbox {
margin-left: 0;
font-size: 14px;
}
/* 确保输入框与标签对齐 */
.config-form .el-input {
height: 32px;
font-size: 14px;
}
.config-form .el-input__wrapper {
height: 32px;
}
.tab-content {
padding: 10px;
color: #606266;
text-align: center;
line-height: 60px;
background-color: #f5f7fa;
border-radius: 4px;
}
</style>

View File

@@ -1,814 +0,0 @@
<template>
<div class="package-management">
<!-- 顶部筛选栏 -->
<div class="filter-section">
<el-form :model="queryParams" :inline="true" label-width="80px">
<el-form-item label="日期">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-form-item>
<el-form-item label="卫生机构">
<el-select v-model="queryParams.organization" placeholder="请选择机构" style="width: 150px" clearable filterable>
<el-option
v-for="org in organizationOptions"
:key="org.value"
:label="org.label"
:value="org.value"
/>
</el-select>
</el-form-item>
<el-form-item label="套餐名称">
<el-input
v-model="queryParams.packageName"
placeholder="请输入套餐名称"
style="width: 200px"
clearable
/>
</el-form-item>
<el-form-item label="套餐级别">
<el-select v-model="queryParams.packageLevel" placeholder="请选择套餐级别" style="width: 150px" clearable>
<el-option
v-for="item in packageLevelOptions"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item label="套餐类别">
<el-select v-model="queryParams.packageType" placeholder="请选择套餐类别" style="width: 150px" clearable>
<el-option label="检查套餐" value="检查套餐" />
</el-select>
</el-form-item>
<el-form-item label="科室">
<el-select v-model="queryParams.department" placeholder="请选择科室" style="width: 150px" clearable filterable>
<el-option
v-for="dept in departments"
:key="dept.dictValue"
:label="dept.dictLabel"
:value="dept.deptCode || dept.busNoPrefix || dept.rawOrg?.busNo || dept.dictLabel"
/>
</el-select>
</el-form-item>
<el-form-item label="用户">
<el-input
v-model="queryParams.user"
placeholder="请输入用户名称"
style="width: 150px"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" icon="Search">查询</el-button>
<el-button @click="handleReset" icon="Refresh">重置</el-button>
<el-button type="success" @click="handleAdd" icon="Plus">新增</el-button>
</el-form-item>
</el-form>
</div>
<!-- 表格展示区 -->
<div class="table-section">
<div class="table-wrapper">
<el-table
:data="tableData"
border
style="width: 100%"
v-loading="loading"
:max-height="600"
>
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="organization" label="卫生机构" width="120" align="center" />
<el-table-column prop="maintainDate" label="日期" width="120" align="center" />
<el-table-column prop="packageName" label="套餐名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="packageType" label="套餐类别" width="100" align="center" />
<el-table-column prop="packageLevel" label="套餐级别" width="100" align="center">
<template #default="{ row }">
{{ getLevelLabel(row.packageLevel) }}
</template>
</el-table-column>
<el-table-column prop="department" label="科室" width="150" align="center">
<template #default="{ row }">
<span :title="row.department && /^[A-Z]\d{2}$/.test(row.department.trim()) ? '旧编码格式,建议编辑套餐重新选择科室' : ''">
{{ getDeptName(row.department) }}
</span>
</template>
</el-table-column>
<el-table-column prop="user" label="用户" width="100" align="center" />
<el-table-column prop="packagePrice" label="金额" width="100" align="center">
<template #default="{ row }">
{{ (row.packagePrice || 0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="serviceFee" label="服务费" width="100" align="center">
<template #default="{ row }">
{{ (row.serviceFee || 0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="总金额" width="100" align="center">
<template #default="{ row }">
{{ ((row.packagePrice || 0) + (row.serviceFee || 0)).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="组合套餐" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.packagePriceEnabled === 1 ? 'success' : 'danger'">
{{ row.packagePriceEnabled === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="显示套餐名" width="110" align="center">
<template #default="{ row }">
<el-tag :type="row.showPackageName === 1 ? 'success' : 'info'">
{{ row.showPackageName === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用标志" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isDisabled === 0 ? 'success' : 'danger'">
{{ row.isDisabled === 0 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="creator" label="操作人" width="100" align="center" />
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="{ row }">
<div class="actions">
<el-button
class="btn btn-edit"
size="small"
circle
@click="handleEdit(row)"
title="编辑"
>
</el-button>
<el-button
class="btn btn-view"
size="small"
circle
@click="handleView(row)"
title="查看"
>
👁
</el-button>
<el-button
class="btn btn-delete"
size="small"
circle
@click="handleDelete(row)"
title="删除"
>
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 底部分页 -->
<div class="pagination-section">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleQuery"
@current-change="handleQuery"
/>
</div>
</div>
</template>
<script setup>
import {onMounted, reactive, ref} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {getDicts} from '@/api/system/dict/data'
import {listDept} from '@/api/system/dept'
import {delCheckPackage, getCheckPackage, listCheckPackage} from '@/api/system/checkType'
import {getTenantPage} from '@/api/system/tenant'
import request from '@/utils/request'
import useUserStore from '@/store/modules/user'
// 定义emit事件
const emit = defineEmits(['switch-to-settings'])
const userStore = useUserStore()
//删除
function handleDelete(row) {
const currentUser = userStore.name
console.log('当前用户:', currentUser, '套餐创建者:', row.creator)
//只有创建者本人才能删除creator为空时不能删除
if(!row.creator){
ElMessage.warning('该套餐创建者未知,无法删除')
return
}
if(row.creator !== currentUser){
ElMessage.warning(`该套餐由"${row.creator}"创建,您没有权限删除`)
return
}
ElMessageBox.confirm(
`确认删除套餐ID:${row.id} - ${row.packageName} 吗?删除后将无法恢复`,
'确认删除',
{
confirmButtonText:'确定删除',
cancelButtonText:'取消',
type: 'warning',
buttonSize:'default'
}
).then(async () => {
try{const response = await delCheckPackage(row.id)
if(response && response.code === 200 || response.code === 0){
ElMessage.success('删除成功')
handleQuery()
}else{
ElMessage.error(response?.msg || response?.message || '删除失败')
}
}catch(error){
console.error('删除失败:',error)
const errorMsg = error?.response?.data?.msg || error?.message || ''
if(errorMsg.includes('foreign key') || errorMsg.includes('violates foreign key')){
ElMessage.warning('该套餐已被使用,无法删除')
}else{
ElMessage.error('删除失败:'+(error.message || '未知错误'))
}
}
}).catch(() => {})
}
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
organization: '',
packageName: '',
packageLevel: '',
packageType: '',
department: '',
user: ''
})
// 日期范围
const dateRange = ref([])
// 表格数据
const tableData = ref([])
const total = ref(0)
const loading = ref(false)
// 套餐级别选项
const packageLevelOptions = ref([])
// 科室选项
const departments = ref([])
// 卫生机构选项
const organizationOptions = ref([])
// 初始化数据
onMounted(async () => {
// 获取套餐级别字典
try {
const levelResponse = await getDicts('examination_item_package_level')
if (levelResponse && levelResponse.data) {
packageLevelOptions.value = levelResponse.data
}
} catch (error) {
console.error('获取套餐级别字典失败:', error)
}
// 获取科室列表 - 使用Organization完整API包含编码和名称
try {
// 使用Organization完整API获取科室列表包含busNo编码
const orgResponse = await request({
url: '/base-data-manage/organization/organization',
method: 'get',
params: {
pageNo: 1,
pageSize: 1000 // 获取足够多的数据
}
})
let orgList = []
if (orgResponse) {
if (orgResponse.data) {
if (orgResponse.data.records && Array.isArray(orgResponse.data.records)) {
orgList = orgResponse.data.records
} else if (Array.isArray(orgResponse.data)) {
orgList = orgResponse.data
}
} else if (Array.isArray(orgResponse)) {
orgList = orgResponse
}
}
// 展开树结构过滤出科室类型typeEnum=2
if (orgList && orgList.length > 0) {
const flattenList = []
function flatten(nodes) {
nodes.forEach(node => {
if (node.typeEnum === 2 && node.busNo && node.name) {
flattenList.push(node)
}
if (node.children && node.children.length > 0) {
flatten(node.children)
}
})
}
flatten(orgList)
orgList = flattenList
}
if (orgList && orgList.length > 0) {
departments.value = orgList.map(org => {
const busNo = (org.busNo || org.code || '').trim()
const name = (org.name || org.deptName || '').trim()
const busNoPrefix = busNo ? busNo.split('.')[0] : ''
return {
dictValue: name,
dictLabel: name,
deptId: org.id || org.deptId,
deptCode: busNo || name,
busNoPrefix: busNoPrefix,
rawOrg: org
}
})
} else {
// 如果Organization API没有数据使用系统部门API
const deptResponse = await listDept()
let deptList = []
if (deptResponse) {
if (Array.isArray(deptResponse)) {
deptList = deptResponse
} else if (deptResponse.data) {
if (Array.isArray(deptResponse.data)) {
deptList = deptResponse.data
} else if (deptResponse.data.data && Array.isArray(deptResponse.data.data)) {
deptList = deptResponse.data.data
}
} else if (deptResponse.code === 200 && deptResponse.data) {
deptList = Array.isArray(deptResponse.data) ? deptResponse.data : []
}
}
if (deptList && deptList.length > 0) {
departments.value = deptList.map(dept => ({
dictValue: dept.deptName || dept.name,
dictLabel: dept.deptName || dept.name,
deptId: dept.deptId || dept.id,
deptCode: dept.deptName || dept.name
}))
} else {
// 如果获取失败,尝试使用字典方式
try {
const dictResponse = await getDicts('dept')
if (dictResponse && dictResponse.data) {
departments.value = dictResponse.data
}
} catch (dictError) {
console.error('获取科室字典失败:', dictError)
}
}
}
} catch (error) {
console.error('获取科室列表失败:', error)
// 如果获取失败,尝试使用字典方式
try {
const dictResponse = await getDicts('dept')
if (dictResponse && dictResponse.data) {
departments.value = dictResponse.data
}
} catch (dictError) {
console.error('获取科室字典也失败:', dictError)
}
}
// 获取卫生机构列表
try {
const tenantResponse = await getTenantPage({ pageNum: 1, pageSize: 100 })
if (tenantResponse && tenantResponse.code === 200) {
const tenantData = tenantResponse.data || {}
let tenantList = []
if (Array.isArray(tenantData)) {
tenantList = tenantData
} else if (tenantData.records) {
tenantList = tenantData.records
} else if (tenantData.rows) {
tenantList = tenantData.rows
} else if (tenantData.list) {
tenantList = tenantData.list
}
// 过滤启用的机构
organizationOptions.value = tenantList
.filter(item => item && item.status === '0')
.map(item => ({
value: item.tenantName || item.name || item.orgName || String(item.id),
label: item.tenantName || item.name || item.orgName || String(item.id)
}))
}
} catch (error) {
console.error('获取卫生机构列表失败:', error)
}
// 加载列表数据
handleQuery()
})
// 获取级别标签
function getLevelLabel(value) {
const item = packageLevelOptions.value.find(i => i.dictValue === value)
return item ? item.dictLabel : value
}
// 获取科室名称(根据编码或名称查找)
function getDeptName(deptValue) {
if (!deptValue) return ''
// 去除前后空格
const trimmedValue = String(deptValue).trim()
if (!trimmedValue) return ''
// 如果科室列表为空,直接返回原值
if (!departments.value || departments.value.length === 0) {
return trimmedValue
}
// 先尝试精确匹配编码(去除所有空格,转大写)
let dept = departments.value.find(d => {
const code = String(d.deptCode || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
return code && code === searchValue
})
// 如果找不到尝试通过busNo前缀匹配优先使用存储的前缀
if (!dept) {
dept = departments.value.find(d => {
const busNoPrefix = String(d.busNoPrefix || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
return busNoPrefix && busNoPrefix === searchValue
})
}
// 如果找不到尝试通过原始busNo精确匹配
if (!dept) {
dept = departments.value.find(d => {
const rawBusNo = String(d.rawOrg?.busNo || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
return rawBusNo && rawBusNo === searchValue
})
}
// 如果找不到尝试层级编码匹配busNo可能是 "A01.001",搜索值是 "A01"
if (!dept) {
dept = departments.value.find(d => {
const rawBusNo = String(d.rawOrg?.busNo || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
if (!rawBusNo) return false
// 如果busNo包含点号取点号前的部分进行匹配
const busNoPrefix = rawBusNo.split('.')[0]
// 匹配方式:前缀匹配、完全匹配、或者搜索值作为前缀
return busNoPrefix === searchValue ||
rawBusNo.startsWith(searchValue + '.') ||
rawBusNo === searchValue
})
}
// 如果找不到,尝试匹配名称
if (!dept) {
dept = departments.value.find(d => {
const label = String(d.dictLabel || '').trim()
const value = String(d.dictValue || '').trim()
return label === trimmedValue || value === trimmedValue
})
}
// 如果还是找不到,尝试部分匹配(编码可能不完整,或者编码格式不同)
if (!dept) {
dept = departments.value.find(d => {
const code = String(d.deptCode || '').trim().replace(/\s+/g, '').toUpperCase()
const rawBusNo = String(d.rawOrg?.busNo || '').trim().replace(/\s+/g, '').toUpperCase()
const searchValue = trimmedValue.replace(/\s+/g, '').toUpperCase()
if (!code && !rawBusNo) return false
// 尝试多种匹配方式
return (code && code.includes(searchValue)) ||
(code && searchValue.includes(code)) ||
(rawBusNo && rawBusNo.includes(searchValue)) ||
(rawBusNo && searchValue.includes(rawBusNo)) ||
(rawBusNo && rawBusNo.startsWith(searchValue)) ||
(code && code.length >= 3 && searchValue.length >= 3 && code.substring(0, 3) === searchValue.substring(0, 3))
})
}
if (dept && dept.dictLabel) {
return dept.dictLabel
}
// 无法匹配时,返回原始编码值
const isOldFormat = /^[A-Z]\d{2}$/.test(trimmedValue)
if (isOldFormat) {
return trimmedValue + ' (旧格式)'
}
return trimmedValue
}
// 查询
async function handleQuery() {
try {
loading.value = true
// 构建查询参数
const params = {
...queryParams
}
// 处理日期范围
if (dateRange.value && dateRange.value.length === 2) {
params.startDate = dateRange.value[0]
params.endDate = dateRange.value[1]
}
const response = await listCheckPackage(params)
if (response && response.data) {
// 处理不同的响应格式
if (Array.isArray(response.data)) {
tableData.value = response.data
total.value = response.data.length
} else if (response.data.records) {
tableData.value = response.data.records
total.value = response.data.total || 0
} else {
tableData.value = []
total.value = 0
}
}
} catch (error) {
console.error('查询失败:', error)
ElMessage.error('查询失败: ' + (error.message || '未知错误'))
} finally {
loading.value = false
}
}
// 重置
function handleReset() {
queryParams.pageNo = 1
queryParams.pageSize = 10
queryParams.organization = ''
queryParams.packageName = ''
queryParams.packageLevel = ''
queryParams.packageType = ''
queryParams.department = ''
queryParams.user = ''
dateRange.value = []
handleQuery()
}
// 新增
function handleAdd() {
emit('switch-to-settings', { mode: 'add', data: null })
}
// 编辑
async function handleEdit(row) {
try {
const response = await getCheckPackage(row.id)
if (response && (response.code === 200 || response.code === 0) && response.data) {
emit('switch-to-settings', { mode: 'edit', data: response.data })
} else {
ElMessage.error('加载套餐数据失败')
}
} catch (error) {
console.error('加载套餐数据失败:', error)
ElMessage.error('数据加载失败,请重试')
}
}
// 查看
async function handleView(row) {
try {
const response = await getCheckPackage(row.id)
if (response && (response.code === 200 || response.code === 0) && response.data) {
emit('switch-to-settings', { mode: 'view', data: response.data })
} else {
ElMessage.error('加载套餐数据失败')
}
} catch (error) {
console.error('加载套餐数据失败:', error)
ElMessage.error('数据加载失败,请重试')
}
}
// 查询参数
</script>
<style scoped>
.package-management {
padding: 24px;
background-color: #f5f7fa;
min-height: 100vh;
width: 100%;
height: 100%;
overflow: auto;
box-sizing: border-box;
}
.filter-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
width: 100%;
box-sizing: border-box;
}
.filter-section :deep(.el-form-item) {
margin-bottom: 16px;
}
.table-section {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.table-wrapper {
width: 100%;
overflow-x: auto;
overflow-y: auto;
max-height: calc(100vh - 400px);
min-height: 400px;
}
.pagination-section {
display: flex;
justify-content: flex-end;
padding: 16px 20px;
}
/* 统一的操作按钮样式 */
.actions {
display: flex;
justify-content: center;
gap: 6px;
position: relative;
z-index: 10;
}
.btn {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 14px;
font-weight: bold;
position: relative;
z-index: 10;
color: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
.btn:hover {
transform: translateY(-2px) scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}
.btn:active {
transform: translateY(0) scale(0.95);
}
.btn-confirm {
background: linear-gradient(135deg, #52C41A 0%, #73d13d 100%);
}
.btn-confirm:hover {
background: linear-gradient(135deg, #389E0D 0%, #52C41A 100%);
}
.btn-edit {
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
}
.btn-edit:hover {
background: linear-gradient(135deg, #096DD9 0%, #1890FF 100%);
}
.btn-add {
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
}
.btn-add:hover {
background: linear-gradient(135deg, #096DD9 0%, #1890FF 100%);
}
.btn-view {
background: linear-gradient(135deg, #722ED1 0%, #9254DE 100%);
}
.btn-view:hover {
background: linear-gradient(135deg, #531DAE 0%, #722ED1 100%);
}
.btn-delete {
background: linear-gradient(135deg, #FF4D4F 0%, #ff7875 100%);
z-index: 20;
pointer-events: auto;
}
.btn-delete:hover {
background: linear-gradient(135deg, #CF1322 0%, #FF4D4F 100%);
}
/* 优化滚动条样式 - 支持水平和垂直滚动 */
.table-wrapper {
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1;
}
.table-wrapper::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.table-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 确保表格可以水平滚动 */
.table-wrapper :deep(.el-table) {
min-width: 100%;
}
.table-wrapper :deep(.el-table__body-wrapper) {
overflow-x: auto;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.filter-section :deep(.el-form) {
display: flex;
flex-wrap: wrap;
}
}
@media (max-width: 768px) {
.package-management {
padding: 16px;
}
.filter-section,
.table-section,
.pagination-section {
padding: 16px;
}
.table-wrapper {
max-height: calc(100vh - 350px);
}
}
</style>

View File

@@ -1,930 +0,0 @@
<template>
<div class="lis-group-maintain">
<!-- 标题区域 -->
<div class="header">
<h2>LIS分组维护</h2>
</div>
<!-- 表格展示区域 -->
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="width: 50px;"></th>
<th style="width: 150px;">卫生机构</th>
<th style="width: 150px;">日期</th>
<th style="width: 200px;">LIS分组名称</th>
<th style="width: 120px;">采血管</th>
<th style="width: 300px;">备注</th>
<th style="width: 150px;">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in tableData"
:key="item.id || index"
:class="{ 'editing-row': item.editing, 'success-row': item.success, 'deleting-row': item.deleting }"
>
<td>{{ index + 1 }}</td>
<td>
<template v-if="item.editing">
<input
type="text"
v-model="item.healthInstitution"
disabled
>
</template>
<template v-else>
{{ item.healthInstitution }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" v-model="item.date" disabled>
</template>
<template v-else>
{{ item.date }}
</template>
</td>
<td>
<template v-if="item.editing">
<input
type="text"
v-model="item.lisGroupName"
placeholder="请输入分组名称"
:class="{ 'error-input': item.errors?.lisGroupName, 'focus-target': item.isNew }"
>
<span v-if="item.errors?.lisGroupName" class="error-message">{{ item.errors.lisGroupName }}</span>
</template>
<template v-else>
{{ item.lisGroupName }}
</template>
</td>
<td>
<template v-if="item.editing">
<select
v-model="item.bloodCollectionTube"
:class="{ 'error-input': item.errors?.bloodCollectionTube }"
>
<option value="">请选择采血管</option>
<option v-for="tube in bloodTubeOptions" :key="tube" :value="tube">
{{ tube }}
</option>
</select>
<span v-if="item.errors?.bloodCollectionTube" class="error-message">{{ item.errors.bloodCollectionTube }}</span>
</template>
<template v-else>
{{ item.bloodCollectionTube }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" v-model="item.remark" placeholder="请输入备注信息">
</template>
<template v-else>
{{ item.remark || '' }}
</template>
</td>
<td>
<div class="actions">
<template v-if="!item.editing">
<button class="btn btn-edit" @click="startEdit(item)"></button>
<button class="btn btn-primary" @click="addRow">+</button>
<button class="btn btn-delete" @click="deleteRow(index)">🗑</button>
</template>
<template v-else>
<button class="btn btn-confirm" @click="confirmEdit(item)"></button>
<button class="btn btn-cancel" @click="cancelEdit(item)">×</button>
</template>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页区域 -->
<div class="pagination">
<button class="pagination-btn" @click="prevPage" :disabled="currentPage <= 1"></button>
<span>{{ currentPage }}</span>
<button class="pagination-btn" @click="nextPage" :disabled="currentPage >= totalPages"></button>
</div>
</div>
</template>
<script>
import {computed, nextTick, onMounted, reactive, ref} from 'vue'
import {addLisGroup, delLisGroup, listLisGroup, updateLisGroup} from '@/api/system/checkType'
import {getDicts} from "@/api/system/dict/data";
import useUserStore from '@/store/modules/user';
import {ElMessage, ElMessageBox} from 'element-plus';
export default {
name: 'LisGroupMaintain',
setup() {
// 获取用户store
const userStore = useUserStore();
// 加载状态
const loading = ref(false)
// 采血管选项
const bloodTubeOptions = ref([]);
// 加载采血管颜色字典数据
const loadBloodTubeDict = async () => {
try {
const response = await getDicts('test_the_color_of_the_blood_collection_tube');
if (response.code === 200 && response.data) {
// 根据实际返回数据格式调整
bloodTubeOptions.value = response.data.map(item => item.label || item.dictLabel);
}
} catch (error) {
// 如果字典获取失败,使用默认选项作为备选
bloodTubeOptions.value = ['黄管', '紫管', '蓝管'];
}
};
// 分页相关数据
const currentPage = ref(1)
const pageSize = ref(10)
const totalItems = ref(2) // 总数据量,实际应用中应该从后端获取
// 总页数
const totalPages = computed(() => {
return Math.ceil(totalItems.value / pageSize.value)
})
// 获取当前用户的卫生机构名称
const getCurrentHospital = () => {
return userStore.hospitalName || userStore.orgName || '演示医院';
};
// 表格数据
const tableData = reactive([])
// 获取当前日期
const getCurrentDate = () => {
const date = new Date()
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 加载LIS分组数据
const loadLisGroups = async () => {
// 确保采血管字典数据已加载
if (bloodTubeOptions.value.length === 0) {
await loadBloodTubeDict();
}
try {
loading.value = true
// 构建查询参数
const query = {
pageNum: currentPage.value,
pageSize: pageSize.value
}
// 调用接口获取数据
const response = await listLisGroup(query)
// 清空现有数据
tableData.splice(0, tableData.length)
totalItems.value = 0
// 处理明确的数据结构
if (response && response.code === 200) {
// 清空现有数据
tableData.splice(0, tableData.length);
// 获取最内层的数据结构
const innerResponse = response.data;
if (innerResponse && innerResponse.code === 200) {
const data = innerResponse.data;
if (data && Array.isArray(data.records)) {
// 更新总记录数
totalItems.value = data.total || 0;
// 处理每条记录
data.records.forEach((item, index) => {
const formattedItem = {
id: item.id,
healthInstitution: item.hospital,
date: item.date,
lisGroupName: item.groupName,
bloodCollectionTube: item.tube,
remark: item.remark || '',
editing: false
};
tableData.push(formattedItem);
});
} else {
totalItems.value = 0;
}
} else {
totalItems.value = 0;
}
} else {
// 非成功状态码
const errorMsg = response.msg || response.message || `请求失败,状态码: ${response.code}`
showMessage(errorMsg, 'error')
totalItems.value = 0;
}
} catch (error) {
// 提取错误信息
let errorMessage = '加载LIS分组数据失败请稍后重试'
if (error.response) {
// 服务器响应了但状态码不是2xx
if (error.response.data) {
errorMessage = error.response.data.msg ||
error.response.data.message ||
error.response.data.error ||
`服务器错误 (${error.response.status})`
} else {
errorMessage = `服务器错误 (${error.response.status})`
}
} else if (error.request) {
// 请求已发出,但未收到响应
errorMessage = '网络错误,服务器未响应,请检查网络连接'
} else if (error.message) {
// 请求配置出错
errorMessage = error.message
}
// 显示错误信息
showMessage(errorMessage, 'error')
} finally {
loading.value = false
}
}
// 开始编辑
const startEdit = (item) => {
// 存储原始数据,用于取消操作
item.originalData = { ...item }
item.editing = true
}
// 验证数据
const validateData = (item) => {
const errors = {}
// 清除之前的错误信息
delete item.errors
// 验证LIS分组名称
if (!item.lisGroupName || item.lisGroupName.trim() === '') {
errors.lisGroupName = 'LIS分组名称不能为空'
} else if (item.lisGroupName.length > 50) {
errors.lisGroupName = 'LIS分组名称不能超过50个字符'
}
// 验证采血管
if (!item.bloodCollectionTube) {
errors.bloodCollectionTube = '请选择采血管'
}
// 验证备注长度
if (item.remark && item.remark.length > 200) {
errors.remark = '备注信息不能超过200个字符'
}
if (Object.keys(errors).length > 0) {
item.errors = errors
return false
}
return true
}
// 显示提示信息
const showMessage = (message, type = 'success') => {
// 使用Element Plus的消息组件
if (type === 'error') {
ElMessage.error(message)
} else {
ElMessage.success(message)
}
}
// 确认编辑
const confirmEdit = async (item) => {
// 验证数据
if (!validateData(item)) {
showMessage('请检查并修正错误信息', 'error')
return
}
// 检查是否有重复的分组名称(在同一个卫生机构内)
const duplicate = tableData.find(data =>
data.id !== item.id &&
data.healthInstitution === item.healthInstitution &&
data.lisGroupName === item.lisGroupName
)
if (duplicate) {
if (!item.errors) item.errors = {}
item.errors.lisGroupName = '该分组名称已存在'
showMessage('分组名称重复,请修改', 'error')
return
}
try {
loading.value = true
// 准备提交数据(使用后端期望的字段名)
const submitData = {
id: item.id,
hospital: item.healthInstitution, // 前端字段映射到后端字段
date: item.date,
groupName: item.lisGroupName, // 前端字段映射到后端字段
tube: item.bloodCollectionTube, // 前端字段映射到后端字段
remark: item.remark
}
let response
// 判断是新增还是修改
if (item.isNew) {
// 新增
response = await addLisGroup(submitData)
} else {
// 修改
response = await updateLisGroup(submitData)
}
// 处理响应
if (response.code === 200) {
// 保存编辑
item.editing = false
delete item.originalData
delete item.errors
// 先检查是否是新增记录再删除isNew属性
const isNewRecord = item.isNew
delete item.isNew
// 添加保存成功样式,包括新增记录
item.success = true
// 0.5秒后移除成功样式
setTimeout(() => {
item.success = false
}, 500)
showMessage('保存成功')
// 修改完成后重新请求后端本页数据,确保数据准确性
setTimeout(() => {
loadLisGroups()
}, 500)
} else {
showMessage(`保存失败: ${response.msg || '未知错误'}`, 'error')
}
} catch (error) {
showMessage(`保存失败: ${error.message || '未知错误'}`, 'error')
} finally {
loading.value = false
}
}
// 取消编辑
const cancelEdit = (item) => {
try {
if (item.isNew) {
// 如果是新增的行,从数组中移除
const index = tableData.findIndex(row => row.id === item.id)
if (index > -1) {
tableData.splice(index, 1)
// 更新总数
if (totalItems.value > 0) {
totalItems.value--
}
}
} else if (item.originalData) {
// 恢复原始数据
Object.assign(item, item.originalData)
delete item.originalData
item.editing = false
} else {
item.editing = false
}
} catch (error) {
showMessage('取消编辑失败', 'error')
}
}
// 添加新行
const addRow = async () => {
// 检查是否已有正在编辑的行
const editingRow = tableData.find(item => item.editing)
if (editingRow) {
showMessage('请先完成当前行的编辑', 'error')
return
}
const newId = String(Math.max(...tableData.map(item => parseInt(item.id) || 0), 0) + 1)
// 从用户信息中获取当前卫生机构
const currentHospital = getCurrentHospital()
tableData.push({
id: '',
healthInstitution: currentHospital, // 默认当前操作工号的卫生机构
date: getCurrentDate(), // 默认当前系统时间
lisGroupName: '',
bloodCollectionTube: '',
remark: '',
editing: true,
errors: null,
isNew: true // 标记为新记录
})
// 等待DOM更新后聚焦到第一个输入框
await nextTick()
const focusElement = document.querySelector('.focus-target')
if (focusElement) {
focusElement.focus()
// 清除focus-target类避免下次添加行时影响
focusElement.classList.remove('focus-target')
}
}
// 删除行
const deleteRow = async (index) => {
const item = tableData[index]
// 检查是否正在编辑
if (item.editing) {
showMessage('请先完成或取消编辑', 'error')
return
}
try {
// 使用Element Plus的确认对话框
await ElMessageBox.confirm(
`确定要删除"${item.lisGroupName}"这条记录吗?`,
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
loading.value = true
// 添加删除行标记,触发渐隐效果
item.deleting = true
// 等待300ms让渐隐效果完成
await new Promise(resolve => setTimeout(resolve, 300))
// 如果是新添加的记录,直接从数组中移除
if (item.isNew) {
tableData.splice(index, 1)
showMessage('删除成功')
totalItems.value--
} else {
// 调用API删除
const response = await delLisGroup(item.id)
if (response.code === 200) {
// 更新总数
totalItems.value--
// 检查当前页码是否仍然有效
const newTotalPages = Math.ceil(totalItems.value / pageSize.value)
if (currentPage.value > newTotalPages && newTotalPages > 0) {
// 如果当前页码大于总页数且总页数大于0调整为最后一页
currentPage.value = newTotalPages
}
// 重新加载当前页数据,保持页码不变
loadLisGroups()
showMessage('删除成功')
} else {
showMessage(`删除失败: ${response.msg || '未知错误'}`, 'error')
// 如果删除失败,移除删除标记
item.deleting = false
}
}
} catch (error) {
if (error !== 'cancel') {
showMessage(`删除失败: ${error.message || '未知错误'}`, 'error')
// 如果删除失败,移除删除标记
if (tableData[index]) {
tableData[index].deleting = false
}
}
} finally {
loading.value = false
}
}
// 分页方法
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
// 调用加载数据方法
loadLisGroups()
}
}
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
// 调用加载数据方法
loadLisGroups()
}
}
const goToPage = (page) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page
// 调用加载数据方法
loadLisGroups()
}
}
// 组件挂载时加载数据
onMounted(() => {
// 加载采血管字典数据
loadBloodTubeDict();
// 加载表格数据
loadLisGroups()
})
return {
tableData,
bloodTubeOptions,
startEdit,
confirmEdit,
cancelEdit,
addRow,
deleteRow,
currentPage,
totalPages,
prevPage,
nextPage,
goToPage,
loading
}
}
}
</script>
<style scoped>
.lis-group-maintain {
padding: 20px;
min-height: 100vh;
}
.header {
height: 56px;
border-bottom: 1px solid #e8e8e8;
margin-bottom: 20px;
display: flex;
align-items: center;
}
.header h2 {
font-size: 18px;
font-weight: 500;
color: #333;
margin: 0;
}
.table-container {
margin-bottom: 20px;
overflow-x: auto;
border: 1px solid #e8e8e8;
border-radius: 2px;
}
.data-table {
width: 100%;
border-collapse: collapse;
text-align: left;
background-color: #fff;
}
.data-table th,
.data-table td {
padding: 12px;
border: 1px solid #e8e8e8;
word-break: break-all;
vertical-align: middle;
}
.data-table th {
background-color: #fafafa;
font-weight: 500;
}
.editing-row {
background-color: #f0f8ff;
}
/* 保存成功行样式 */
.success-row {
background-color: #f6ffed !important;
transition: background-color 0.3s ease;
}
.actions {
white-space: nowrap;
}
.pagination-section {
height: 48px;
display: flex;
justify-content: center;
align-items: center;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
padding: 16px 0;
margin-top: 8px;
}
.pagination span {
font-size: 14px;
margin: 0 16px;
background-color: #1890FF;
color: white;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.pagination-btn {
background: none;
border: 1px solid #D9D9D9;
border-radius: 4px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.pagination-btn:hover {
border-color: #1890FF;
color: #1890FF;
}
.pagination-btn:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.pagination-number.active {
background-color: #1890ff;
color: #fff;
border-color: #1890ff;
}
.pagination-number.active:hover {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
.btn {
width: 24px;
height: 24px;
padding: 0;
margin-right: 5px;
border: 1px solid #d9d9d9;
background-color: #fff;
cursor: pointer;
border-radius: 4px;
font-size: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn:hover {
border-color: #40a9ff;
color: #40a9ff;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
border-color: #1890ff;
}
.btn-primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
.btn-confirm {
background-color: #52c41a;
color: #fff;
border-color: #52c41a;
}
.btn-confirm:hover {
background-color: #73d13d;
border-color: #73d13d;
color: #fff;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
border-color: #d9d9d9;
}
.btn-edit {
background-color: #faad14;
color: #fff;
border-color: #faad14;
}
.btn-edit:hover {
background-color: #ffc53d;
border-color: #ffc53d;
color: #fff;
}
.btn-delete {
background-color: #ff4d4f;
color: #fff;
border-color: #ff4d4f;
}
.btn-delete:hover {
background-color: #ff7875;
border-color: #ff7875;
color: #fff;
}
input, select {
width: 100%;
padding: 4px 6px;
border: 1px solid #d9d9d9;
border-radius: 2px;
box-sizing: border-box;
}
input:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
/* 错误状态样式 */
.error-input {
border-color: #ff4d4f !important;
}
.error-input:focus {
border-color: #ff4d4f !important;
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
}
.error-message {
display: block;
font-size: 12px;
color: #ff4d4f;
margin-top: 2px;
line-height: 1.2;
}
/* 错误提示区域样式 */
.error-message-container {
padding: 12px 16px;
background-color: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 2px;
margin-bottom: 16px;
}
.error-text {
color: #ff4d4f;
font-size: 14px;
}
/* 加载状态样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
/* 确保页面容器相对定位,使加载遮罩正常工作 */
.lis-group-maintain {
position: relative;
}
.table-container {
overflow-x: auto;
border: 1px solid #e8e8e8;
border-radius: 4px;
margin-bottom: 16px;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 12px 8px;
text-align: center;
border-bottom: 1px solid #e8e8e8;
}
.data-table th {
background-color: #fafafa;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
/* 响应式设计 */
@media (max-width: 768px) {
.lis-group-maintain {
padding: 10px;
}
.header h1 {
font-size: 16px;
}
.table-container {
border-radius: 0;
}
.data-table {
font-size: 12px;
border-collapse: collapse;
min-width: 768px; /* 确保表格在小屏幕上可以横向滚动 */
}
.data-table th,
.data-table td {
padding: 8px;
text-align: center;
}
.btn {
width: 20px;
height: 20px;
font-size: 11px;
margin-right: 2px;
}
.pagination-btn {
width: 28px;
height: 28px;
font-size: 12px;
margin: 0 2px;
}
}
/* 平滑过渡效果 */
.btn,
.pagination-btn,
.editing-row,
tr {
transition: all 0.3s ease;
}
/* 删除行渐隐效果 */
.deleting-row {
opacity: 0;
height: 0;
overflow: hidden;
transition: all 0.3s ease;
}
/* 美化输入框和选择框的焦点样式 */
input:focus,
select:focus {
outline: none;
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
</style>