@@ -10,119 +10,122 @@
< 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 >
< 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
< tr
v-for = "(item, index) in tableData"
: key = "item.id || index"
: class = "{ 'editing-row': item.editing }"
>
< td > { { index + 1 } } < / td >
< td >
< template v-if = "item.editing" >
< input
: 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"
placeholder = "请输入卫生机构"
: class = "{ 'error-input': item.errors?.healthInstitution }"
>
< span v-if = "item.errors?.healthInstitution" class="error-message" > {{ item.errors.healthInstitution }} < / span >
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 >
{{ item.healthInstit uti on }}
< button class = "btn btn-confirm" @click ="confirmEdit(item)" > √ < / b utt on>
< button class = "btn btn-cancel" @click ="cancelEdit(item)" > × < / button >
< / template >
< / t d>
< 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 }"
>
< 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 >
< / div >
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< ! - - 分页区域 - - >
< div class = "pagination" >
< button class = "pagination-btn" > ‹ < / button >
< span > 1 < / span >
< button class = "pagination-btn" > › < / button >
< / 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 , onMounted , reactive , ref } from 'vue'
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 )
@@ -138,7 +141,6 @@ export default {
bloodTubeOptions . value = response . data . map ( item => item . label || item . dictLabel ) ;
}
} catch ( error ) {
console . error ( '获取采血管颜色字典失败:' , error ) ;
// 如果字典获取失败,使用默认选项作为备选
bloodTubeOptions . value = [ '黄管' , '紫管' , '蓝管' ] ;
}
@@ -154,6 +156,11 @@ export default {
return Math . ceil ( totalItems . value / pageSize . value )
} )
// 获取当前用户的卫生机构名称
const getCurrentHospital = ( ) => {
return userStore . hospitalName || userStore . orgName || '演示医院' ;
} ;
// 表格数据
const tableData = reactive ( [ ] )
@@ -181,210 +188,78 @@ export default {
pageSize : pageSize . value
}
console . log ( '准备调用接口,查询参数:' , query )
// 调用接口获取数据
// 注意: 根据checkType.js中的定义, 这个接口使用GET方法和params参数
const response = await listLisGroup ( query )
console . log ( '接口返回结果类型:' , typeof response )
console . log ( '接口返回结果:' , response )
console . log ( '响应是否包含code字段:' , response && 'code' in response )
console . log ( '响应是否包含data字段:' , response && 'data' in response )
// 清空现有数据
tableData . splice ( 0 , tableData . length )
totalItems . value = 0
// 适配可能的不同响应格式
let items = [ ]
let total = 0
// 处理明确的数据结构
if ( response && response . code === 200 ) {
// 清空现有数据
tableData . splice ( 0 , tableData . length ) ;
// 检查响应是否存在
if ( response ) {
// 处理标准响应格式
if ( typeof response === 'object' ) {
// 检查是否是标准API响应格式 {code, data, msg}
if ( 'code' in response ) {
console . log ( '响应包含code字段:' , response . code )
// 获取最内层的数据结构
const innerResponse = response . data ;
if ( innerResponse && innerResponse . code === 200 ) {
const data = innerResponse . data ;
if ( data && Array . isArray ( data . records ) ) {
// 更新总记录数
totalItems . value = data . total || 0 ;
// 成功状态码 处理
if ( response . code === 200 || response . code === '200' || response . code === 0 ) {
console . log ( '响应状态码为成功状态' )
// 处理每条记录
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
} ;
// 检查data字段
if ( 'data' in response ) {
console . log ( '响应包含data字段, 数据类型:' , typeof response . data )
// 格式1: {data: {rows: [], total: number}}
if ( response . data && typeof response . data === 'object' ) {
// 处理双重嵌套格式 {data: {code, msg, data: []}}
if ( response . data . data && ( Array . isArray ( response . data . data ) || typeof response . data . data === 'object' ) ) {
console . log ( '匹配到格式: 双重嵌套 data.data' ) ;
// 如果data.data是数组, 直接使用
if ( Array . isArray ( response . data . data ) ) {
items = response . data . data ;
total = items . length ;
console . log ( '双重嵌套格式1: data.data是数组, 数据量:' , items . length ) ;
}
// 如果data.data是对象, 检查是否有rows字段
else if ( Array . isArray ( response . data . data . rows ) ) {
items = response . data . data . rows ;
total = response . data . data . total !== undefined ? response . data . data . total : items . length ;
console . log ( '双重嵌套格式2: data.data包含rows, 数据量:' , items . length , ',总数:' , total ) ;
}
}
// 标准格式检查
else if ( Array . isArray ( response . data . rows ) ) {
items = response . data . rows
total = response . data . total !== undefined ? response . data . total : items . length
console . log ( '匹配到格式1: response.data.rows, 数据量:' , items . length , ',总数:' , total )
}
// 格式2: {data: []}
else if ( Array . isArray ( response . data ) ) {
items = response . data
total = items . length
console . log ( '匹配到格式2: response.data直接是数组, 数据量:' , items . length )
}
// 检查是否有其他可能的格式
else {
console . log ( '响应data不是预期格式, 详细数据:' , response . data )
// 尝试将整个data对象作为单个条目处理( 有些API可能直接返回单个对象)
if ( response . data ) {
items = [ response . data ]
total = 1
console . log ( '尝试将整个data对象作为单个条目处理' )
}
}
}
} else {
console . log ( '响应不包含data字段' )
}
} else {
// 非成功状态码
const errorMsg = response . msg || response . message || ` 请求失败,状态码: ${ response . code } `
console . error ( '请求失败:' , errorMsg )
showMessage ( errorMsg , 'error' )
}
}
// 格式3: 响应本身是一个对象但没有code字段, 可能是单个记录
else if ( ! Array . isArray ( response ) ) {
console . log ( '响应是对象但没有code字段, 尝试作为单个记录处理' )
items = [ response ]
total = 1
}
}
// 格式4: 响应本身是数组
if ( Array . isArray ( response ) ) {
items = response
total = items . length
console . log ( '匹配到格式4: 响应本身是数组,数据量:' , items . length )
}
// 记录最终解析到的数据量
console . log ( '最终解析到的数据量:' , items . length , ',总数:' , total )
} else {
console . error ( '接口返回空数据' )
showMessage ( '获取LIS分组数据失败: 服务器返回空响应' , 'error' )
}
// 处理获取到的数据
if ( items && items . length > 0 ) {
// 处理每条数据,确保字段正确映射
items . forEach ( ( item , index ) => {
// 确保item是对象类型
if ( typeof item === 'object' && item !== null ) {
console . log ( ` 处理记录 ${ index + 1 } 原始数据: ` , item ) ;
// 创建格式化后的对象,确保字段存在
// 根据网络响应截图更正字段映射关系
const formattedItem = {
id : item . id || ` temp_ ${ Date . now ( ) } _ ${ index } ` , // 确保有ID
// 更正字段映射: hospital -> healthInstitution
healthInstitution : item . hospital || item . org || item . healthInstitution || '' ,
// 优先使用date字段
date : item . date || item . createTime || item . createDate || '' ,
// 更正字段映射: groupName -> lisGroupName
lisGroupName : item . groupName || item . name || item . lisGroupName || '' ,
// 更正字段映射: tube -> bloodCollectionTube
bloodCollectionTube : item . tube || item . property || item . bloodCollectionTube || item . bloodTube || '' ,
remark : item . remark || item . note || '' ,
editing : false ,
// 保留原始对象的所有其他属性
... item
} ;
tableData . push ( formattedItem ) ;
console . log ( ` 处理记录 ${ index + 1 } 格式化后数据: ` , {
id : formattedItem . id ,
healthInstitution : formattedItem . healthInstitution ,
date : formattedItem . date ,
lisGroupName : formattedItem . lisGroupName ,
bloodCollectionTube : formattedItem . bloodCollectionTube ,
remark : formattedItem . remark
tableData . push ( formattedItem ) ;
} ) ;
} else {
console . warn ( ` 记录 ${ index + 1 } 不是有效对象,跳过处理: ` , item ) ;
totalItems . value = 0 ;
}
} ) ;
totalItems . value = total ;
console . log ( '数据加载完成,表格显示' , tableData . length , '条记录,总数:' , totalItems . value ) ;
} else {
totalItems . value = 0 ;
}
} else {
console . log ( '未获取到有效数据,表格为空' ) ;
// 非成功状态码
const errorMsg = response . msg || response . message || ` 请求失败,状态码: ${ response . code } `
showMessage ( errorMsg , 'error' )
totalItems . value = 0 ;
}
} catch ( error ) {
// 详细的 错误信息记录
console . error ( '加载LIS分组数据异常:' , error )
// 提取更详细的错误信息
// 提取 错误信息
let errorMessage = '加载LIS分组数据失败, 请稍后重试'
if ( error . response ) {
// 服务器响应了, 但状态码不是2xx
console . error ( 'HTTP错误状态:' , error . response . status )
console . error ( 'HTTP错误响应体:' , error . response . data )
if ( error . response . data ) {
errorMessage = error . response . data . msg ||
error . response . data . message ||
error . response . data . error ||
` 服务器错误 ( ${ error . response . status } ) `
error . response . data . message ||
error . response . data . error ||
` 服务器错误 ( ${ error . response . status } ) `
} else {
errorMessage = ` 服务器错误 ( ${ error . response . status } ) `
}
// 额外记录响应体详情便于调试
if ( typeof error . response . data === 'object' ) {
console . error ( '响应体详细结构:' , JSON . stringify ( error . response . data , null , 2 ) )
}
} else if ( error . request ) {
// 请求已发出,但未收到响应
console . error ( '请求已发送但未收到响应:' , error . request )
errorMessage = '网络错误,服务器未响应,请检查网络连接'
} else if ( error . message ) {
// 请求配置出错
console . error ( '请求配置错误:' , error . message )
errorMessage = error . message
} else {
console . error ( '未知错误类型:' , typeof error )
}
// 显示错误信息
showMessage ( errorMessage , 'error' )
// 确保表格状态正确
console . log ( '异常处理后表格状态:' , {
tableDataLength : tableData . length ,
totalItems : totalItems . value ,
currentPage : currentPage . value
} )
} finally {
loading . value = false
console . log ( '数据加载操作完成, loading状态已更新为false' )
console . log ( '最终表格数据量:' , tableData . length , ',总数:' , totalItems . value )
}
}
@@ -402,13 +277,6 @@ export default {
// 清除之前的错误信息
delete item . errors
// 验证卫生机构
if ( ! item . healthInstitution || item . healthInstitution . trim ( ) === '' ) {
errors . healthInstitution = '卫生机构不能为空'
} else if ( item . healthInstitution . length > 100 ) {
errors . healthInstitution = '卫生机构名称不能超过100个字符'
}
// 验证LIS分组名称
if ( ! item . lisGroupName || item . lisGroupName . trim ( ) === '' ) {
errors . lisGroupName = 'LIS分组名称不能为空'
@@ -436,15 +304,11 @@ export default {
// 显示提示信息
const showMessage = ( message , type = 'success' ) => {
// 在实际项目中,这里可以 使用Element Plus或其他UI库 的消息组件
// 这里为了简单起见, 使用浏览器的alert, 但添加了类型标识
// 使用Element Plus的消息组件
if ( type === 'error' ) {
consol e. error ( message )
alert ( ` 错误: ${ message } ` )
ElMessag e. error ( message )
} else {
console . log ( message )
// 可以使用更友好的提示方式, 这里暂时注释掉alert以避免频繁弹窗
// alert(message)
ElMessage . success ( message )
}
}
@@ -458,9 +322,9 @@ export default {
// 检查是否有重复的分组名称(在同一个卫生机构内)
const duplicate = tableData . find ( data =>
data . id !== item . id &&
data . healthInstitution === item . healthInstitution &&
data . lisGroupName === item . lisGroupName
data . id !== item . id &&
data . healthInstitution === item . healthInstitution &&
data . lisGroupName === item . lisGroupName
)
if ( duplicate ) {
@@ -504,17 +368,23 @@ export default {
const isNewRecord = item . isNew
delete item . isNew
// 添加保存成功样式,包括新增记录
item . success = true
// 0.5秒后移除成功样式
setTimeout ( ( ) => {
item . success = false
} , 500 )
showMessage ( '保存成功' )
// 如果是新增,重新加载 数据以 确保数据一致 性
if ( isNewRecord ) {
// 修改完成后重新请求后端本页 数据, 确保数据准确 性
setTimeout ( ( ) => {
loadLisGroups ( )
}
} , 500 )
} else {
showMessage ( ` 保存失败: ${ response . msg || '未知错误' } ` , 'error' )
}
} catch ( error ) {
console . error ( '保存LIS分组数据失败:' , error )
showMessage ( ` 保存失败: ${ error . message || '未知错误' } ` , 'error' )
} finally {
loading . value = false
@@ -543,13 +413,12 @@ export default {
item . editing = false
}
} catch ( error ) {
console . error ( '取消编辑失败:' , error )
showMessage ( '取消编辑失败' , 'error' )
}
}
// 添加新行
const addRow = ( ) => {
const addRow = async ( ) => {
// 检查是否已有正在编辑的行
const editingRow = tableData . find ( item => item . editing )
if ( editingRow ) {
@@ -558,9 +427,11 @@ export default {
}
const newId = String ( Math . max ( ... tableData . map ( item => parseInt ( item . id ) || 0 ) , 0 ) + 1 )
// 从用户信息中获取当前卫生机构
const currentHospital = getCurrentHospital ( )
tableData . push ( {
id : newId ,
healthInstitution : '' , // 空 默认值
id : '' ,
healthInstitution : currentHospital , // 默认当前操作工号的卫生机构
date : getCurrentDate ( ) , // 默认当前系统时间
lisGroupName : '' ,
bloodCollectionTube : '' ,
@@ -570,7 +441,14 @@ export default {
isNew : true // 标记为新记录
} )
showMessage ( '请填写新记录信息' )
// 等待DOM更新后聚焦到第一个输入框
await nextTick ( )
const focusElement = document . querySelector ( '.focus-target' )
if ( focusElement ) {
focusElement . focus ( )
// 清除focus-target类, 避免下次添加行时影响
focusElement . classList . remove ( 'focus-target' )
}
}
// 删除行
@@ -582,35 +460,65 @@ export default {
return
}
if ( confirm ( ` 确定要删除" ${ item . lisGroupName } "这条记录吗? ` ) ) {
try {
loading . value = true
// 如果是新添加的记录,直接从数组中移除
if ( item . isNew ) {
tableData . splice ( index , 1 )
showMessage ( '删除成功 ')
totalItems . value --
} else {
// 调用API删除
const response = await delLisGroup ( item . id )
if ( response . code === 200 ) {
tableData . splice ( index , 1 )
showMessage ( '删除成功' )
// 更新总数
totalItems . value --
} else {
showMessage ( ` 删除失败: ${ response . msg || '未知错误' } ` , 'error' )
}
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 ) {
console . error ( '删除LIS分组数据失败:' , error )
showMessage ( ` 删除失败: ${ error . message || '未知错误' } ` , 'error' )
} finally {
loading . value = false
}
} catch ( error ) {
if ( error !== 'cancel' ) {
showMessage ( ` 删除失败: ${ error . message || '未知错误' } ` , 'error' )
// 如果删除失败,移除删除标记
if ( tableData [ index ] ) {
tableData [ index ] . deleting = false
}
}
} finally {
loading . value = false
}
}
@@ -680,7 +588,7 @@ export default {
align - items : center ;
}
. header h1 {
. header h2 {
font - size : 18 px ;
font - weight : 500 ;
color : # 333 ;
@@ -718,6 +626,12 @@ export default {
background - color : # f0f8ff ;
}
/* 保存成功行样式 */
. success - row {
background - color : # f6ffed ! important ;
transition : background - color 0.3 s ease ;
}
. actions {
white - space : nowrap ;
}
@@ -732,7 +646,7 @@ export default {
/* 分页样式 */
. pagination {
display : flex ;
justify - content : flex - end ;
justify - content : center ;
align - items : center ;
padding : 16 px 0 ;
margin - top : 8 px ;
@@ -741,6 +655,14 @@ export default {
. pagination span {
font - size : 14 px ;
margin : 0 16 px ;
background - color : # 1890 FF ;
color : white ;
width : 32 px ;
height : 32 px ;
display : flex ;
align - items : center ;
justify - content : center ;
border - radius : 4 px ;
}
. pagination - btn {
@@ -957,14 +879,15 @@ input:disabled {
. data - table {
font - size : 12 px ;
min - width : 600 px ; /* 确保表格在小屏幕上可以横向滚动 */
border - collapse : collapse ;
min - width : 768 px ; /* 确保表格在小屏幕上可以横向滚动 */
}
. data - table th ,
. data - table td {
padding : 8 px ;
text - align : center ;
}
. data - table td {
padding : 8 px ;
text - align : center ;
}
. btn {
width : 20 px ;
@@ -984,7 +907,16 @@ input:disabled {
/* 平滑过渡效果 */
. btn ,
. pagination - btn ,
. editing - row {
. editing - row ,
tr {
transition : all 0.3 s ease ;
}
/* 删除行渐隐效果 */
. deleting - row {
opacity : 0 ;
height : 0 ;
overflow : hidden ;
transition : all 0.3 s ease ;
}