@@ -650,6 +650,7 @@ import {
getOrgTree ,
getInspectionPackageDetails
} from '../api'
import { getLabActivityDefinitionPage } from '@/api/lab/labActivityDefinition'
import useUserStore from '@/store/modules/user.js'
// 迁移到 hiprint
import { previewPrint } from '@/utils/printUtils.js'
@@ -1075,17 +1076,18 @@ const loadCategoryItems = async (categoryKey, loadMore = false) => {
searchKey : searchKeyword . value || ''
}
// 如果有类型ID, 添加筛选条件( 从检验项目维护页面获取数据)
// 如果有类型 ID, 添加筛选条件
if ( category . typeId ) {
params . inspectionTypeId = category . typeId
}
const res = await getInspectionItemList ( params )
const res = await getLabActivityDefinitionPage ( params )
// 解析数据
let records = [ ]
let total = 0
if ( res . data && res . data . records ) {
if ( res . code === 200 && res . data && res . data . records ) {
records = res . data . records
total = res . data . total || 0
} else if ( res . data && Array . isArray ( res . data ) ) {
@@ -1096,34 +1098,34 @@ const loadCategoryItems = async (categoryKey, loadMore = false) => {
total = records . length
}
// 映射数据格式
// 映射数据格式(从检验项目维护页面的数据结构映射)
const mappedItems = records . map ( item => {
// BugFix: 套餐项目使用套餐金额,普通项目使用零售价
// 套餐项目处理: 套餐项目使用套餐金额,普通项目使用零售价
// BugFix#404: 增加对空字符串的判断, 避免空字符串被误认为有效套餐ID
const isPackage = item . feePackageId != null && item . feePackageId !== '' && item . feePackageId !== 'null'
const itemPrice = isPackage
? ( item . packageAmount || item . retailPrice || item . price || 0 )
: ( item . retailPrice || item . price || 0 )
? ( parseFloat ( item . packageAmount || 0 ) || parseFloat ( item . retailPrice || 0 ) || parseFloat ( item . price || 0 ) )
: ( parseFloat ( item . retailPrice || 0 ) || parseFloat ( item . price || 0 ) )
return {
itemId : item . id || item . activityId || Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) ,
itemName : item . name || item . itemName || '' ,
itemId : item . id || Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) ,
itemName : item . name || '' ,
itemPrice : itemPrice ,
itemAmount : itemPrice ,
sampleType : item . specimenCode _dictText || item . sampleType || '血液' ,
unit : item . unit || '' ,
sampleType : item . specimenCode || '血液' ,
unit : item . permittedUnitCode _dictText || item . unit || '' ,
itemQty : 1 ,
serviceFee : item . serviceFee || 0 ,
serviceFee : 0 ,
type : category . label ,
isSelfPay : false ,
activityId : item . activityId ,
code : item . busNo || item . code || item . activityCode ,
inspectionType Id: item . inspectionTypeId || category . typeId , // 如果项目没有inspectionTypeId, 使用分类的typeId
code : item . busNo || '' ,
inspectionTypeId : item . inspectionTypeId ? Number ( item . inspectionTypeId ) : null ,
activity Id: item . id , // 保存 activityId 用于保存申请单时使用
// 套餐相关字段
isPackage : isPackage ,
feePackageId : item . feePackageId ,
packageName : item . packageName ,
children : isPackage ? [ ] : undefined , // 套餐项目预留children字段
children : isPackage ? [ ] : undefined ,
hasChildren : isPackage
}
} )
@@ -1142,6 +1144,7 @@ const loadCategoryItems = async (categoryKey, loadMore = false) => {
category . loaded = true
} catch ( error ) {
console . error ( '加载检验项目失败:' , error )
// 加载失败时设置空数据
if ( ! loadMore ) {
category . items = [ ]
@@ -1225,25 +1228,26 @@ const querySearchInspectionItems = async (queryString, cb) => {
searchKey : queryString
}
const res = await getInspectionItemList ( params )
const res = await getLabActivityDefinitionPage ( params )
let suggestions = [ ]
if ( res . data && res . data . records ) {
// 映射数据格式,与 loadInspectionItemsByType 保持一致
if ( res . code === 200 && res . data && res . data . records ) {
// 映射数据格式,与 loadCategoryItems 保持一致
suggestions = res . data . records . map ( item => ( {
itemId : item . id || item . activityId ,
itemName : item . name || item . itemName || '' ,
itemPrice : item . retailPrice || item . price || 0 ,
sampleType : item . specimenCode _dictText || item . sampleType || '血液' ,
unit : item . unit || '' ,
code : item . busNo || item . code || item . activityCode ,
activityId : item . activityI d,
inspectionTypeId : item . inspectionTypeId || null
itemId : item . id ,
itemName : item . name || '' ,
itemPrice : parseFloat ( item . packageAmount || 0 ) ,
sampleType : item . specimenCode || '血液' ,
unit : item . permittedUnitCode _dictText || item . unit || '' ,
code : item . busNo || '' ,
activityId : item . i d,
inspectionTypeId : item . inspectionTypeId ? Number ( item . inspectionTypeId ) : null
} ) )
}
cb ( suggestions )
} catch ( error ) {
console . error ( '搜索检验项目失败:' , error )
cb ( [ ] )
}
}
@@ -1656,62 +1660,17 @@ const isItemSelected = (item) => {
}
// 切换检验项目选择
// 切换检验项目选择状态(支持异步获取检验类型)
// BugFix#CodeReview: 移除竞态条件风险,使用加载标志替代 setTimeout sleep
const toggleInspectionItem = async ( item ) => {
const toggleInspectionItem = ( item ) => {
const index = selectedInspectionItems . value . findIndex ( selected => selected . itemId === item . itemId )
if ( index > - 1 ) {
// 取消选择项目
selectedInspectionItems . value . splice ( index , 1 )
// 检查剩余项目:如果为空则清空执行科室,否则保持不变
if ( selectedInspectionItems . value . length === 0 ) {
formData . executeDepartment = ''
}
} else {
// 选择新项目
// 创建新对象,包含 itemName 属性(等于 name 属性)
const newItem = {
... item ,
itemName : item . itemName ,
// BugFix: 套餐项目需要初始化展开属性,否则点击无法展开
expanded : false ,
childrenLoaded : false ,
loading : false ,
children : item . isPackage ? [ ] : undefined
}
selectedInspectionItems . value . push ( newItem )
// Bug #329: 根据检验项目所属类型自动设置执行科室默认值
const inspectionTypeId = item . inspectionTypeId
if ( inspectionTypeId ) {
// BugFix#CodeReview: 使用加载标志等待,替代 setTimeout sleep
// 确保执行科室列表已加载完成( 最多等待5秒)
if ( ! isExecuteDepartmentLoaded . value ) {
const maxWaitTime = 5000
const startTime = Date . now ( )
while ( ! isExecuteDepartmentLoaded . value && ( Date . now ( ) - startTime ) < maxWaitTime ) {
await new Promise ( resolve => setTimeout ( resolve , 100 ) )
}
if ( ! isExecuteDepartmentLoaded . value ) {
console . warn ( '执行科室列表加载超时,跳过自动设置' )
return
}
}
// 异步获取执行科室(支持动态获取缺失的检验类型)
const defaultDeptCode = await getDefaultPerformDeptCode ( inspectionTypeId )
if ( defaultDeptCode ) {
formData . executeDepartment = defaultDeptCode
} else {
// BugFix#CodeReview: 匹配失败时提示用户手动选择
console . warn ( '未找到检验类型对应的执行科室, typeId:' , inspectionTypeId )
ElMessage . warning ( '未能自动匹配执行科室,请手动选择' )
}
} else {
console . warn ( '检验项目没有 inspectionTypeId' )
}
itemName : item . itemName
} ;
selectedInspectionItems . value . push ( newItem ) ;
}
}
@@ -1720,61 +1679,12 @@ const removeInspectionItem = (item) => {
const index = selectedInspectionItems . value . findIndex ( selected => selected . itemId === item . itemId )
if ( index > - 1 ) {
selectedInspectionItems . value . splice ( index , 1 )
// 检查剩余项目:如果为空则清空执行科室,否则保持不变
if ( selectedInspectionItems . value . length === 0 ) {
formData . executeDepartment = ''
}
}
}
// 清空所有选择
const clearAllSelected = ( ) => {
selectedInspectionItems . value = [ ]
formData . executeDepartment = ''
}
// BugFix#326: 展开/收起套餐明细
const togglePackageExpand = async ( item ) => {
if ( ! item . isPackage ) return
item . expanded = ! item . expanded
// 如果展开且未加载明细,则加载
// BugFix#404: 增加对有效套餐ID的校验, 避免请求404
const feePackageIdStr = String ( item . feePackageId || '' ) . trim ( )
const validPackageId = feePackageIdStr && feePackageIdStr !== '' && feePackageIdStr !== 'null' && feePackageIdStr !== 'undefined'
console . log ( 'togglePackageExpand debug:' , {
feePackageId : item . feePackageId ,
feePackageIdStr ,
validPackageId ,
isPackage : item . isPackage ,
expanded : item . expanded ,
childrenLoaded : item . childrenLoaded
} )
if ( item . expanded && ! item . childrenLoaded && validPackageId ) {
item . loading = true
try {
console . log ( '正在请求套餐明细, packageId:' , feePackageIdStr )
const res = await getInspectionPackageDetails ( feePackageIdStr )
console . log ( '套餐明细响应:' , res )
if ( res . code === 200 && res . data ) {
item . children = res . data . map ( detail => ( {
detailId : detail . detailId ,
itemName : detail . itemName ,
unit : detail . unit ,
unitPrice : detail . unitPrice ,
quantity : detail . quantity
} ) )
item . childrenLoaded = true
}
} catch ( e ) {
console . error ( '加载套餐明细失败:' , e )
item . children = [ ]
} finally {
item . loading = false
}
}
}
// 分页大小改变
@@ -1793,75 +1703,6 @@ const handleSelectionChange = (selection) => {
selectedRows . value = selection
}
// Bug #326: 处理展开/收起
const handleExpandChange = ( row , expandedRows ) => {
// 更新展开状态
if ( expandedRows . includes ( row ) ) {
if ( ! expandedRowKeys . value . includes ( row . applicationId ) ) {
expandedRowKeys . value . push ( row . applicationId )
}
} else {
const index = expandedRowKeys . value . indexOf ( row . applicationId )
if ( index > - 1 ) {
expandedRowKeys . value . splice ( index , 1 )
}
}
}
// 判断是否展开
const isExpanded = ( applicationId ) => {
return expandedRowKeys . value . includes ( applicationId )
}
// 切换展开状态
const toggleExpand = ( row ) => {
if ( isExpanded ( row . applicationId ) ) {
// 收起
expandedRowKeys . value = expandedRowKeys . value . filter ( id => id !== row . applicationId )
} else {
// 展开
expandedRowKeys . value . push ( row . applicationId )
// 如果没有加载过明细,加载明细数据
if ( ! row . childrenLoaded ) {
loadInspectionItemDetails ( row )
}
}
}
// 加载检验项目明细(套餐子项)
const loadInspectionItemDetails = async ( row ) => {
try {
const res = await getInspectionApplyDetail ( row . applyNo )
if ( res . code === 200 && res . data ) {
const detail = res . data
if ( detail . labApplyItemList && detail . labApplyItemList . length > 0 ) {
// 将明细数据转换为 children
row . children = detail . labApplyItemList . map ( item => ( {
itemId : item . itemId || item . id ,
itemName : item . itemName || item . name ,
sampleType : item . sampleType || '未知' ,
unit : item . unit || '' ,
itemPrice : item . itemPrice || item . price || 0 ,
itemQty : item . itemQty || 1 ,
itemAmount : item . itemAmount || item . price || 0
} ) )
row . childrenLoaded = true
// 标记是否有子项
row . hasChildren = row . children . length > 0
} else {
row . children = [ ]
row . childrenLoaded = true
row . hasChildren = false
}
}
} catch ( error ) {
console . error ( '加载检验项目明细失败:' , error )
row . children = [ ]
row . childrenLoaded = true
row . hasChildren = false
}
}
const handlePrint = ( row ) => {
// 切换到申请单TAB
leftActiveTab . value = 'application'
@@ -1949,6 +1790,7 @@ const loadApplicationToForm = async (row) => {
// 根据申请单号获取完整详情
try {
const res = await getInspectionApplyDetail ( row . applyNo )
console . log ( '申请单详情API返回:' , res ) // 临时调试日志
if ( res . code === 200 && res . data ) {
const detail = res . data
// 加载完整的表单数据
@@ -1995,14 +1837,6 @@ const loadApplicationToForm = async (row) => {
itemName : item . itemName || item . name || '' ,
itemPrice : item . itemPrice || item . price || 0 ,
itemAmount : item . itemAmount || item . price || 0 ,
// BugFix: 套餐项目需要初始化展开属性,否则点击无法展开
// BugFix#404: 增加对空字符串的判断, 避免空字符串被误认为有效套餐ID
isPackage : item . feePackageId != null && item . feePackageId !== '' && item . feePackageId !== 'null' ,
feePackageId : item . feePackageId ,
expanded : false ,
childrenLoaded : false ,
loading : false ,
children : ( item . feePackageId != null && item . feePackageId !== '' && item . feePackageId !== 'null' ) ? [ ] : undefined
} ) )
} else if ( detail . inspectionItem || detail . itemName ) {
// 如果只有项目名称,尝试从本地分类中查找匹配项
@@ -2010,14 +1844,7 @@ const loadApplicationToForm = async (row) => {
inspectionCategories . value . forEach ( category => {
category . items . forEach ( item => {
if ( itemNames . includes ( item . itemName ) ) {
selectedInspectionItems . value . push ( {
... item ,
// BugFix: 套餐项目需要初始化展开属性
expanded : false ,
childrenLoaded : false ,
loading : false ,
children : item . isPackage ? [ ] : undefined
} )
selectedInspectionItems . value . push ( { ... item } )
}
} )
} )
@@ -2078,17 +1905,15 @@ watch(() => selectedInspectionItems.value, (newVal) => {
}
} , { deep : true } )
// 监听执行科室选项加载完成,不自动设置默认值
// Bug #329: 等待选择检验项目时根据检验类型自动设置
watch ( ( ) => executeDepartmentOptions . value , ( newVal ) => {
// 不再自动设置第一个值为默认值
// 监听执行科室字典数据,设置默认值为第一个选项
watch ( ( ) => inspection _lab _dept . value , ( newVal ) => {
if ( newVal && newVal . length > 0 && ! formData . executeDepartment ) {
formData . executeDepartment = newVal [ 0 ] . value
}
} , { immediate : true } )
// 组件挂载时预加载检验项目数据(不依赖 patientInfo)
// 组件挂载时预加载检验项目数据( 不依赖patientInfo)
onMounted ( async ( ) => {
// Bug #329: 先加载执行科室列表,确保选择检验项目时能正确映射
await loadExecuteDepartmentList ( )
// 再加载检验类型分类
await loadInspectionData ( )
} )
@@ -2120,18 +1945,6 @@ defineExpose({
padding : 0 ;
}
/* Bug #326: 展开内容样式 */
. expand - content {
padding : 10 px ;
}
. expand - empty {
padding : 10 px ;
text - align : center ;
color : # 999 ;
font - size : 12 px ;
}
/* Bug#334: 顶部操作按钮区 - 优化垂直空间利用率 */
. top - action - bar {
display : flex ;
@@ -2229,26 +2042,11 @@ defineExpose({
display : flex ;
flex - direction : column ;
gap : 8 px ;
height : calc ( 100 vh - 200 px ) ;
min - height : 600 px ;
}
. inspection - selector ,
. selected - items - area {
flex : 1 ;
min - height : 300 px ;
display : flex ;
flex - direction : column ;
}
/* 检验项目选择区 - 固定高度 */
. inspection - selector {
max - height : 350 px ;
}
/* 已选项目区 - 固定高度 */
. selected - items - area {
min - height : 300 px ;
height : auto ;
}
. card - title {
@@ -2256,144 +2054,6 @@ defineExpose({
color : var ( -- el - text - color - primary ) ;
}
/* 搜索输入框样式 */
. search - input {
margin - bottom : 8 px ;
}
/* 分类树样式 */
. category - tree {
flex : 1 ;
overflow - y : auto ;
}
. category - tree - item {
margin - bottom : 4 px ;
}
. category - tree - header {
padding : 8 px 12 px ;
cursor : pointer ;
border - radius : 4 px ;
display : flex ;
align - items : center ;
gap : 8 px ;
transition : all 0.3 s ;
}
. category - tree - header : hover {
background - color : # f5f7fa ;
}
. category - tree - header . active {
background - color : # e6f1ff ;
color : # 409 EFF ;
}
. category - tree - icon {
font - size : 12 px ;
color : # 909399 ;
}
. category - count {
font - size : 12 px ;
color : # 909399 ;
margin - left : auto ;
}
. category - tree - children {
padding - left : 24 px ;
margin - top : 4 px ;
}
. inspection - tree - item {
padding : 6 px 12 px ;
cursor : pointer ;
border - radius : 4 px ;
display : flex ;
align - items : center ;
gap : 8 px ;
transition : all 0.3 s ;
}
. inspection - tree - item : hover {
background - color : # f5f7fa ;
}
. inspection - tree - item . selected {
background - color : # e6f1ff ;
color : # 409 EFF ;
}
. item - itemName {
flex : 1 ;
font - size : 13 px ;
}
. item - price {
font - size : 12 px ;
color : # 67 c23a ;
font - weight : 500 ;
}
/* 已选项目列表样式 */
. selected - tree {
flex : 1 ;
overflow - y : auto ;
}
. selected - items - list {
margin - top : 8 px ;
}
. selected - list - item {
padding : 8 px 12 px ;
border - radius : 4 px ;
transition : all 0.3 s ;
}
. selected - list - item : hover {
background - color : # f5f7fa ;
}
. selected - item - content {
gap : 8 px ;
}
/* 加载状态样式 */
. loading - placeholder ,
. load - more ,
. no - more {
padding : 8 px 12 px ;
text - align : center ;
font - size : 12 px ;
color : # 909399 ;
}
. load - info {
margin - left : 8 px ;
font - size : 12 px ;
color : # 909399 ;
}
/* 展开内容样式 */
. expand - content {
padding : 12 px 16 px ;
background - color : # f5f7fa ;
border - radius : 4 px ;
margin : 8 px ;
}
. expand - empty {
padding : 20 px ;
text - align : center ;
color : # 909399 ;
font - size : 13 px ;
background - color : # f5f7fa ;
border - radius : 4 px ;
margin : 8 px ;
}
. search - input {
margin - bottom : 8 px ;
}
@@ -2550,65 +2210,6 @@ defineExpose({
font - weight : bold ;
}
/* BugFix#326: 套餐明细展开样式 */
. selected - item - wrapper {
margin - bottom : 4 px ;
}
. expand - icon {
cursor : pointer ;
margin - right : 4 px ;
transition : transform 0.3 s ;
}
. expand - icon : hover {
color : # 409 EFF ;
}
. package - details {
margin - left : 24 px ;
padding : 8 px 12 px ;
background : # f5f7fa ;
border - radius : 4 px ;
margin - top : 4 px ;
}
. package - details . loading {
display : flex ;
align - items : center ;
gap : 8 px ;
color : # 909399 ;
}
. package - detail - item {
display : flex ;
justify - content : space - between ;
padding : 4 px 0 ;
font - size : 12 px ;
color : # 606266 ;
border - bottom : 1 px dashed # e4e7ed ;
}
. package - detail - item : last - child {
border - bottom : none ;
}
. detail - name {
flex : 1 ;
}
. detail - unit {
width : 50 px ;
text - align : center ;
color : # 909399 ;
}
. detail - price {
width : 70 px ;
text - align : right ;
color : # e6a23c ;
}
. inspection - details {
margin - top : 30 px ;
padding : 20 px ;
@@ -2846,100 +2447,6 @@ defineExpose({
border - top : 1 px solid # ebeef5 ;
}
/* BugFix#326: 已选择区域树形结构样式(仿照项目选择区) */
. selected - tree - item {
border - bottom : 1 px solid # ebeef5 ;
}
. selected - tree - item : last - child {
border - bottom : none ;
}
. selected - tree - header {
display : flex ;
align - items : center ;
padding : 10 px 12 px ;
cursor : default ;
transition : all 0.3 s ease ;
background : # fff ;
border - radius : 4 px ;
margin : 2 px ;
}
. selected - tree - header : hover {
background - color : # f5f5f5 ;
}
. selected - tree - item . is - package . selected - tree - header {
cursor : pointer ;
}
. selected - tree - item . is - package . selected - tree - header : hover {
background - color : # e6f7ff ;
}
. selected - tree - item . is - package . selected - tree - header . expanded {
background - color : # e6f7ff ;
font - weight : bold ;
}
. selected - tree - icon {
margin - right : 8 px ;
font - size : 12 px ;
width : 12 px ;
text - align : center ;
color : # 51 A3F3 ;
}
. selected - tree - children {
background : # fafafa ;
border - top : 1 px solid # ebeef5 ;
padding : 8 px 0 ;
}
. selected - tree - detail {
display : flex ;
align - items : center ;
padding : 6 px 12 px 6 px 32 px ;
font - size : 12 px ;
color : # 606266 ;
}
. selected - tree - detail : hover {
background - color : # f0f0f0 ;
}
. selected - tree - detail . detail - name {
flex : 1 ;
min - width : 100 px ;
}
. selected - tree - detail . detail - unit {
width : 50 px ;
text - align : center ;
color : # 909399 ;
}
. selected - tree - detail . detail - qty {
width : 40 px ;
text - align : center ;
color : # 909399 ;
}
. selected - tree - detail . detail - price {
width : 60 px ;
text - align : right ;
color : # e6a23c ;
font - weight : 500 ;
}
. no - detail {
padding : 12 px 32 px ;
color : # 909399 ;
font - size : 12 px ;
text - align : center ;
}
/* 加载中占位样式 */
. loading - placeholder {
display : flex ;