Revert "Fix Bug #550: AI修复"

This reverts commit 16c42ca108.
This commit is contained in:
2026-05-27 08:59:07 +08:00
parent bd14563691
commit 9db5ced4e3
5432 changed files with 778638 additions and 171 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,556 @@
<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

@@ -0,0 +1,814 @@
<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>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,930 @@
<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>