feat: Spring Boot 3.5.14 全量升级 + 组件升级

核心升级:
- Spring Boot 2.7.18 → 3.5.14
- MyBatis Plus 3.5.5 → 3.5.16 (spring-boot3-starter)
- Springdoc 1.8.0 → 2.8.6 (OpenAPI 3)
- Flowable 6.8.0 → 7.1.0
- Druid 1.2.x → 1.2.28 (boot3-starter)
- kotlin-reflect 1.9.10 → 1.9.25

迁移适配:
- javax → jakarta 命名空间 (620+ 文件)
- Swagger 注解迁移到 OpenAPI 3 (@Tag/@Schema/@Operation/@Parameter)
- Spring Security 6.2 适配 (antMatchers→requestMatchers, EnableMethodSecurity)
- Druid 包名迁移 (boot→boot3)
- Redis 配置路径迁移 (spring.redis→spring.data.redis)
- Flyway 适配 (flyway-database-postgresql)
- Flowable 7.x 适配 (MULE_TASK_IMAGE 移除)

修复:
- spring-boot-maven-plugin 2.5.15→3.5.14 (SPI服务发现失效)
- mybatis-plus-boot-starter 3.5.5→3.5.16 (kotlin-reflect+fastjson2冲突)
- Flowable database-schema-update 启用自动建表

验证: 23/23 测试通过, 1374 API端点正常
This commit is contained in:
2026-06-04 22:39:10 +08:00
parent b8d719429d
commit 1d21661a78
781 changed files with 57907 additions and 1301 deletions

View File

@@ -0,0 +1,80 @@
import request from '@/utils/request';
// 查询调价申请详情
export function getPriceAdjustmentDetail (query) {
return request({
url: '/change/price/list/searchSupplyRequestInfo',
method: 'post',
params: query
});
}
// 查询挂号调价详情
export function searchSupplyRequestByHealth (query) {
return request({
url: '/inventory-examine-page/searchSupplyRequestByHealth',
method: 'post',
params: query
});
}
// 查询诊疗调价详情
export function searchSupplyRequestByActivity (query) {
return request({
url: '/inventory-examine-page/searchSupplyRequestByActivity',
method: 'post',
params: query
});
}
// 查询耗材调价详情
export function searchSupplyRequestByDevice (query) {
return request({
url: '/inventory-examine-page/searchSupplyRequestByDevice',
method: 'post',
params: query
});
}
// 查询药品调价详情
export function searchSupplyRequestByMed (query) {
return request({
url: '/inventory-examine-page/searchSupplyRequestByMed',
method: 'post',
params: query
});
}
// 获取审核状态选项数据
export function getExamineStatusOptions() {
return request({
url: '/inventory-examine-page/init',
method: 'get'
});
}
// 驳回价格调整申请
export function rejectPriceAdjustment(busNo) {
return request({
url: '/inventory-examine-page/updateExamineByRejected',
method: 'post',
params: { busNo }
});
}
// 审批通过价格调整申请
export function updateExamineByApproved(busNo) {
return request({
url: '/inventory-examine-page/updateExamineByApproved',
method: 'post',
params: { busNo }
});
}

View File

@@ -0,0 +1,254 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="'价格调整详情'"
width="90%"
:close-on-click-modal="false"
destroy-on-close
>
<div class="detail-container">
<div
v-if="itemList.length > 0"
class="detail-content"
>
<el-table
:data="itemList"
style="width: 100%"
size="small"
border
>
<!-- 挂号调价单特殊显示 -->
<template v-if="categoryType.includes('挂号调价')">
<el-table-column
label="科室"
align="center"
prop="orgName"
min-width="150"
/>
<el-table-column
label="号源"
align="center"
prop="name"
min-width="200"
/>
<el-table-column
label="当前进货价"
align="center"
prop="originBuyingPrice"
min-width="100"
/>
<el-table-column
label="调后进货价"
align="center"
prop="newBuyingPrice"
min-width="100"
/>
<el-table-column
label="当前零售价"
align="center"
prop="originRetailPrice"
min-width="100"
/>
<el-table-column
label="调后零售价"
align="center"
prop="newRetailPrice"
min-width="100"
/>
<el-table-column
label="原因"
align="center"
prop="reason"
min-width="200"
/>
</template>
<!-- 其他调价类型标准显示 -->
<template v-else>
<el-table-column
label="项目编码"
align="center"
prop="targetId"
min-width="180"
/>
<el-table-column
label="项目名称"
align="center"
prop="chargeName"
min-width="200"
/>
<el-table-column
label="规格"
align="center"
prop="volume"
min-width="120"
/>
<el-table-column
label="当前进货价"
align="center"
prop="originBuyingPrice"
min-width="100"
/>
<el-table-column
label="调后进货价"
align="center"
prop="newBuyingPrice"
min-width="100"
/>
<el-table-column
label="当前零售价"
align="center"
prop="originRetailPrice"
min-width="100"
/>
<el-table-column
label="调后零售价"
align="center"
prop="newRetailPrice"
min-width="100"
/>
<el-table-column
label="调价原因"
align="center"
prop="reason"
min-width="200"
/>
</template>
</el-table>
<div class="creator-info">
<span class="creator-label">制单人{{ detailData?.createName || '-' }}</span>
</div>
</div>
<div
v-else
class="empty-tip"
>
暂无调价项目数据
</div>
</div>
<template #footer>
<span class="dialog-footer">
<!-- 当状态为驳回或同意时不显示审核和驳回按钮 -->
<template
v-if="
!detailData.statusEnum_enumText ||
!['驳回', '同意'].includes(detailData.statusEnum_enumText)
"
>
<el-button
type="primary"
:plain="true"
@click="handleApprove"
>审核</el-button>
<el-button
type="danger"
:plain="true"
@click="handleReject"
>驳回</el-button>
</template>
<el-button
:plain="true"
@click="closeDialog"
>关闭</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import {computed, ref, watch} from 'vue';
// 定义props
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
detailData: {
type: Object,
default: () => ({}),
},
categoryType: {
type: String,
default: '',
},
});
// 定义事件
const emit = defineEmits(['update:visible', 'close']);
// 响应式数据
const dialogVisible = ref(false);
// 计算属性:获取需要显示的数据列表
const itemList = computed(() => {
console.log('detailData:', props.detailData);
if (!props.detailData) return [];
// 优先使用items字段从index.vue传递的结构
if (Array.isArray(props.detailData.items)) {
return props.detailData.items;
}
// 如果detailData本身是数组
if (Array.isArray(props.detailData)) {
return props.detailData;
}
return [];
});
// 监听visible变化
watch(
() => props.visible,
(newVal) => {
dialogVisible.value = newVal;
}
);
// 监听dialogVisible变化
watch(dialogVisible, (newVal) => {
emit('update:visible', newVal);
});
// 关闭对话框
const closeDialog = () => {
dialogVisible.value = false;
emit('close');
};
// 处理审核通过
const handleApprove = () => {
emit('approve', props.detailData);
};
// 处理驳回
const handleReject = () => {
// 直接触发事件由父组件处理API调用和状态管理
emit('reject', props.detailData);
};
</script>
<style scoped>
.detail-container {
padding: 10px 0;
}
.creator-info {
text-align: left;
padding: 10px 0;
border-top: 1px solid #ebeef5;
margin-top: 10px;
}
.creator-label {
font-size: 14px;
color: #606266;
}
.empty-tip {
text-align: center;
padding: 40px 0;
color: #999;
}
</style>

View File

@@ -0,0 +1,606 @@
<template>
<div class="app-container">
<!-- 查询条件区域 -->
<div class="query-form">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="审批状态">
<el-select
v-model="queryParams.statusEnum"
placeholder="请选择审批状态"
clearable
filterable
@focus="loadExamineStatusOptions"
>
<el-option
v-for="option in examineStatusOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="申请日期">
<el-date-picker
v-model="queryParams.applicantTime"
type="date"
placeholder="请选择日期"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="6">
<div class="query-buttons">
<el-button
plain
type="primary"
@click="handleSearch"
>
查询
</el-button>
<el-button
plain
type="primary"
@click="handleReset"
>
重置
</el-button>
</div>
</el-col>
</el-row>
</div>
<el-table
v-loading="loading"
:data="adjustmentList"
tooltip-effect="dark"
:show-overflow-tooltip="true"
style="width: 100%"
>
<el-table-column
label="单据编号"
align="center"
prop="busNo"
min-width="180"
/>
<el-table-column
label="调价类型"
align="center"
prop="categoryEnum_enumText"
min-width="120"
/>
<el-table-column
label="审核状态"
align="center"
prop="statusEnum"
min-width="100"
>
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.statusEnum)">
{{ scope.row.statusEnum_enumText || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column
label="制单人"
align="center"
prop="applicantId_dictText"
min-width="120"
/>
<el-table-column
label="申请日期"
align="center"
prop="applicantTime"
min-width="180"
>
<template #default="scope">
{{ parseTime(scope.row.applicantTime) }}
</template>
</el-table-column>
<el-table-column
label="审核人"
align="center"
prop="approverId_dictText"
min-width="120"
/>
<el-table-column
label="审核日期"
align="center"
prop="approvalTime"
min-width="180"
>
<template #default="scope">
{{ scope.row.approvalTime ? parseTime(scope.row.approvalTime) : '-' }}
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
min-width="180"
>
<template #default="scope">
<el-button
size="small"
type="primary"
plain
@click="handleDetail(scope.row)"
>
{{
['驳回', '同意'].includes(scope.row.statusEnum_enumText) ? '查看详情' : '查看并审核'
}}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="handlePagination"
/>
<!-- 详情弹窗组件 -->
<DetailDialog
v-model:visible="detailDialogVisible"
:detail-data="selectedRow"
:category-type="categoryType"
@close="handleDetailClose"
@approve="handleApprove"
@reject="handleReject"
/>
</div>
</template>
<script setup>
import {onMounted, reactive, ref} from 'vue';
import {ElMessage} from 'element-plus';
import {parseTime} from '@/utils/openhis';
import Pagination from '@/components/Pagination';
import request from '@/utils/request';
//import { getPriceAdjustmentPage, getPriceAdjustmentDetail, cancelPriceAdjustment } from './components/api';
import {
getExamineStatusOptions,
rejectPriceAdjustment,
searchSupplyRequestByActivity,
searchSupplyRequestByDevice,
searchSupplyRequestByHealth,
searchSupplyRequestByMed,
updateExamineByApproved,
} from './components/api';
import DetailDialog from './components/detailDialog.vue';
// 表格数据
const adjustmentList = ref([]);
const loading = ref(false);
const total = ref(0);
const activeName = ref('1'); // 当前激活的标签页
// 审核状态选项 - 初始为空数组后续会通过API加载
const examineStatusOptions = ref([]);
const optionsLoading = ref(false);
// 查询参数
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
statusEnum: undefined, // 审核状态
applicantTime: undefined, // 申请日期
});
// 详情弹窗相关
const detailDialogVisible = ref(false);
const selectedRow = ref({});
const categoryType = ref('');
// 状态标签类型
const getStatusTagType = (status) => {
const typeMap = {
1: 'warning', // 待审核
2: 'primary', // 审核中
3: 'success', // 同意
4: 'danger', // 驳回
};
return typeMap[status] || 'info';
};
// 处理分页事件
const handlePagination = (params) => {
// 将分页组件的参数映射到查询参数
queryParams.pageNum = params.page;
queryParams.pageSize = params.limit;
getList();
};
// 查询数据
const getList = async () => {
loading.value = true;
try {
// 构造符合后端要求的请求参数
const requestParams = {
statusEnum: queryParams.statusEnum,
pageNo: queryParams.pageNum,
pageSize: queryParams.pageSize,
};
// 根据后端要求的格式处理日期参数
if (queryParams.applicantTime) {
const dateStr = queryParams.applicantTime;
// requestParams.applicantTime = dateStr; // 原始日期值
requestParams.applicantTimeSTime = `${dateStr} 00:00:00`; // 当天开始时间
requestParams.applicantTimeETime = `${dateStr} 23:59:59`; // 当天结束时间
console.log('applicantTime', requestParams.applicantTime);
console.log('applicantTimeSTime', requestParams.applicantTimeSTime);
console.log('applicantTimeETime', requestParams.applicantTimeETime);
}
// 调用新的后端API接口
const response = await request({
url: '/inventory-examine-page/getPageByExamine',
method: 'get',
params: requestParams,
});
// 处理返回的数据
if (response && response.code === 200) {
// 直接从records字段获取数据
adjustmentList.value = response.data?.records || [];
// 检查total字段是否正确获取增加更多调试日志
total.value = response.data?.total || response.data?.totalCount || 0;
} else {
ElMessage.error(response?.msg || '获取调价审核列表失败');
adjustmentList.value = [];
total.value = 0;
}
} catch (error) {
ElMessage.error('获取数据异常,请稍后重试');
adjustmentList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
// 操作按钮处理函数
const handleDetail = async (row) => {
// 保存当前行的调价类型
categoryType.value = row.categoryEnum_enumText || '';
// 调用详情API获取数据
try {
// 显示加载状态
loading.value = true;
// 准备API参数
const params = {
busNo: row.busNo,
};
// 根据itemCategoryEnum选择不同的API接口
let response;
if (row.itemCategoryEnum === 49) {
// 挂号
response = await searchSupplyRequestByHealth(params);
} else if (row.itemCategoryEnum === 48) {
// 诊疗
response = await searchSupplyRequestByActivity(params);
} else if (row.itemCategoryEnum === 47) {
// 耗材
response = await searchSupplyRequestByDevice(params);
} else if (row.itemCategoryEnum === 46) {
// 药品
response = await searchSupplyRequestByMed(params);
}
if (response && response.code === 200) {
// 日志记录原始返回数据
// 准备显示数据
let items = [];
// 根据API实际返回格式处理数据
if (typeof response.data === 'object' && response.data !== null) {
// 首先尝试获取data数组根据截图API返回格式
if (Array.isArray(response.data.data)) {
items = response.data.data;
} else if (Array.isArray(response.data.items)) {
items = response.data.items;
} else if (Array.isArray(response.data)) {
items = response.data;
} else {
// 如果只是单个对象,包装成数组
items = [response.data];
}
} else if (Array.isArray(response.data)) {
items = response.data;
}
// 转换数据格式以便显示
const formattedItems = items.map((item) => {
// 基础字段映射
const baseFields = {
targetId: item.targetId || item.busNo || item.itemId || item.code || item.id || '-',
chargeName: item.chargeName || item.itemName || item.name || '-',
volume: item.totalVolume || item.spec || item.specification || '-',
price: item.price || item.newprice || item.newPrice || item.afterPrice || '-',
reason: item.reason || item.sreason || item.adjustReason || item.remark || '-',
orgName: item.orgName || item.org || '-',
// 额外字段,用于挂号调价单显示
name: item.name || item.chargeName || item.itemName || '-',
originPrice: item.originPrice || '-',
retailPrice: item.retailPrice || '-',
originBuyingPrice: item.originBuyingPrice || '-',
newBuyingPrice: item.newBuyingPrice || '-',
originRetailPrice: item.originRetailPrice || '-',
newRetailPrice: item.newRetailPrice || '-',
};
return baseFields;
});
// 设置selectedRow确保格式一致
selectedRow.value = {
items: formattedItems,
createName: row.applicantId_dictText || '-',
busNo: row.busNo, // 添加busNo字段用于后续的审核和驳回操作
statusEnum_enumText: row.statusEnum_enumText, // 添加状态字段,用于控制按钮显示
};
// 显示弹窗
detailDialogVisible.value = true;
} else {
ElMessage.error(response?.msg || '获取详情数据失败');
}
} catch (error) {
ElMessage.error('获取详情数据失败');
} finally {
loading.value = false;
}
};
// 关闭详情弹窗处理
const handleDetailClose = () => {
// 重置选中的行数据和类型
selectedRow.value = {};
categoryType.value = '';
};
// 处理审批通过事件
const handleApprove = async () => {
try {
// 获取当前选中行的业务编号
const busNo = selectedRow.value.busNo;
// 调用审核通过API
const response = await updateExamineByApproved(busNo);
if (response && response.code === 200) {
ElMessage.success('审核通过成功');
// 关闭详情弹窗
detailDialogVisible.value = false;
// 刷新页面数据
getList();
} else {
ElMessage.error(response?.msg || '审核通过失败');
}
} catch (error) {
ElMessage.error('审核通过失败');
}
};
// 处理驳回事件
const handleReject = async () => {
try {
// 获取当前选中行的业务编号
const busNo = selectedRow.value.busNo;
// 调用驳回API
const response = await rejectPriceAdjustment(busNo);
if (response && response.code === 200) {
ElMessage.success('驳回成功');
// 关闭详情弹窗
detailDialogVisible.value = false;
// 延迟一点时间再刷新,确保弹窗完全关闭
setTimeout(async () => {
await getList();
}, 300);
} else {
ElMessage.error(response?.msg || '驳回失败');
}
} catch (error) {
ElMessage.error('驳回失败');
}
};
// 处理查询
const handleSearch = () => {
queryParams.pageNum = 1; // 重置页码
getList();
};
// 加载审核状态选项
const loadExamineStatusOptions = async () => {
// 如果已经加载过,不再重复加载
if (examineStatusOptions.value.length > 0) {
return;
}
optionsLoading.value = true;
try {
const response = await getExamineStatusOptions();
// 先清空选项数组
examineStatusOptions.value = [];
if (response && response.code === 200 && response.data) {
// 1. 首先检查是否有supplyStatusOptions字段
if (response.data.supplyStatusOptions) {
if (Array.isArray(response.data.supplyStatusOptions)) {
// supplyStatusOptions是数组
examineStatusOptions.value = response.data.supplyStatusOptions.map((item) => ({
value: item.code || item.key || item.value,
label: item.name || item.label || item.value,
}));
} else if (typeof response.data.supplyStatusOptions === 'object') {
// supplyStatusOptions是对象
examineStatusOptions.value = Object.entries(response.data.supplyStatusOptions).map(
([key, value]) => ({
value: key,
label: value,
})
);
}
}
// 2. 如果没有supplyStatusOptions字段但response.data本身是数组
else if (Array.isArray(response.data)) {
examineStatusOptions.value = response.data.map((item) => {
// 处理可能的JSON字符串格式
if (typeof item === 'string') {
try {
const parsed = JSON.parse(item);
return {
value: parsed.value || parsed.code || parsed.key,
label: parsed.label || parsed.name || parsed.value,
};
} catch (e) {
return {
value: item,
label: item,
};
}
}
return {
value: item.value || item.code || item.key,
label: item.label || item.name || item.value,
};
});
}
// 3. 如果response.data是对象
else if (typeof response.data === 'object') {
examineStatusOptions.value = Object.entries(response.data).map(([key, value]) => ({
value: key,
label: value,
}));
}
// 确保选项格式正确
if (examineStatusOptions.value.length === 0) {
// 如果没有数据,添加默认的选项
examineStatusOptions.value = [
{ value: '', label: '全部' },
{ value: '1', label: '待审核' },
{ value: '2', label: '已审核' },
{ value: '3', label: '已拒绝' },
];
} else {
// 在选项开头添加"全部"选项
examineStatusOptions.value.unshift({ value: '', label: '全部' });
}
} else {
// 如果API调用失败使用默认选项
examineStatusOptions.value = [
{ value: '', label: '全部' },
{ value: '1', label: '待审核' },
{ value: '2', label: '已审核' },
{ value: '3', label: '已拒绝' },
];
}
} catch (error) {
// 错误情况下使用默认选项
examineStatusOptions.value = [
{ value: '', label: '全部' },
{ value: '1', label: '待审核' },
{ value: '2', label: '已审核' },
{ value: '3', label: '已拒绝' },
];
} finally {
optionsLoading.value = false;
}
};
// 处理重置
const handleReset = () => {
// 重置查询参数
queryParams.statusEnum = undefined;
queryParams.applicantTime = undefined;
queryParams.pageNum = 1;
getList();
};
// 生命周期 - 加载
onMounted(() => {
// 初始加载审核状态选项
loadExamineStatusOptions();
getList();
});
</script>
<style scoped>
.app-container {
padding: 20px;
}
.query-form {
background-color: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.query-buttons {
display: flex;
gap: 10px;
align-items: center;
height: 100%;
}
/* 确保表单元素垂直居中对齐 */
:deep(.el-form-item) {
margin-bottom: 0;
display: flex;
align-items: center;
height: 100%;
}
/* 确保标签和输入框垂直居中 */
:deep(.el-form-item__label-wrap) {
display: flex;
align-items: center;
}
:deep(.el-form-item__content) {
display: flex;
align-items: center;
}
:deep(.el-tabs__content) {
height: auto;
}
:deep(.demo-tabs > .el-tabs__content) {
color: #6b778c;
font-size: 14px;
}
:deep(.el-table) {
border: 1px solid #ebeef5;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
:deep(.el-table__header-wrapper) {
border-radius: 8px 8px 0 0;
overflow: hidden;
}
:deep(.el-table__body-wrapper) {
border-radius: 0 0 8px 8px;
overflow: hidden;
}
</style>