@@ -41,7 +41,7 @@
< / thead >
< tbody >
< tr
v-for = "(row, index) in tableData "
v-for = "(row, index) in pagedTypeRows "
:key = "row.id"
: class = "{ 'editing': editingRowId === row.id }"
>
@@ -148,12 +148,16 @@
< / div >
<!-- 页码区域 -- >
< div class = "pagination" >
< div class = "page-btn" > 上一页< / div >
< div class = "page-btn active" > 1 < / div >
< div class = "page-btn" > 2 < / div >
< div class = "page-btn" > 3 < / div >
< div class = "page-btn" > 下一页 < / div >
< div class = "pagination" v-if = "typeTotalPages > 1" >
< div class = "page-btn" : class = "{ disabled: typeCurrentPage === 1 }" @click ="typePrevPage" > 上一页< / div >
< div
v-for = "p in typePageButtons"
:key = "p"
class = "page-btn"
: class = "{ active: p === typeCurrentPage }"
@click ="typeGoPage(p)"
> { { p } } < / div >
< div class = "page-btn" : class = "{ disabled: typeCurrentPage >= typeTotalPages }" @click ="typeNextPage" > 下一页 < / div >
< / div >
< / template >
@@ -239,7 +243,7 @@
< / thead >
< tbody >
< tr
v-for = "(item, index) in filter edInspectionItems"
v-for = "(item, index) in pag edInspectionItems"
:key = "item.id"
: class = "{ 'editing': editingRowId === item.id }"
>
@@ -372,12 +376,16 @@
< / div >
<!-- 页码区域 -- >
< div class = "pagination" >
< div class = "page-btn" > 上一页< / div >
< div class = "page-btn active" > 1 < / div >
< div class = "page-btn" > 2 < / div >
< div class = "page-btn" > 3 < / div >
< div class = "page-btn" > 下一页 < / div >
< div class = "pagination" v-if = "inspectionTotalPages > 1" >
< div class = "page-btn" : class = "{ disabled: inspectionCurrentPage === 1 }" @click ="inspectionPrevPage" > 上一页< / div >
< div
v-for = "p in inspectionPageButtons"
:key = "p"
class = "page-btn"
: class = "{ active: p === inspectionCurrentPage }"
@click ="inspectionGoPage(p)"
> { { p } } < / div >
< div class = "page-btn" : class = "{ disabled: inspectionCurrentPage >= inspectionTotalPages }" @click ="inspectionNextPage" > 下一页 < / div >
< / div >
< / template >
@@ -687,9 +695,9 @@
< script setup >
import useUserStore from '@/store/modules/user' ;
import { computed , onMounted , ref , watch } from 'vue' ;
import { computed , nextTick , onActivated , onMounted , ref , watch } from 'vue' ;
import { ElLoading , ElMessage , ElMessageBox } from 'element-plus' ;
import { useRoute , useRouter } from 'vue-router' ;
import { onBeforeRouteUpdate , useRoute, useRouter } from 'vue-router' ;
import {
addInspectionType ,
delInspectionType ,
@@ -785,6 +793,97 @@ const activeNav = ref(0);
// 检验类型数据
const tableData = ref ( [ ] ) ;
// ==============================
// 分页(检验类型 tab)
// ==============================
const typeCurrentPage = ref ( 1 ) ;
// 每页条数: 按需求固定为10
const typePageSize = ref ( 10 ) ;
const typeTotalPages = computed ( ( ) => {
const total = tableData . value . length ;
return Math . max ( 1 , Math . ceil ( total / typePageSize . value ) ) ;
} ) ;
const typePageButtons = computed ( ( ) => {
const total = typeTotalPages . value ;
return Array . from ( { length : total } , ( _ , i ) => i + 1 ) ;
} ) ;
// 按“大类编码(code)”排序后再分页展示(不要按序号 sortOrder 排)
function parseCodeParts ( code ) {
const s = ( code ? ? '' ) . toString ( ) . trim ( ) ;
// 支持 1、01、3-001、3_001 等:用 -/_ 分隔
const parts = s . split ( /[-_]/g ) ;
const mainRaw = parts [ 0 ] ? ? '' ;
const subRaw = parts [ 1 ] ? ? '' ;
const mainNum = Number . parseInt ( mainRaw , 10 ) ;
const subNum = Number . parseInt ( subRaw , 10 ) ;
return {
raw : s ,
mainRaw ,
subRaw ,
mainIsNum : ! Number . isNaN ( mainNum ) ,
subIsNum : ! Number . isNaN ( subNum ) ,
mainNum ,
subNum
} ;
}
const sortedTypeRows = computed ( ( ) => {
return [ ... tableData . value ] . sort ( ( a , b ) => {
const pa = parseCodeParts ( a ? . code ) ;
const pb = parseCodeParts ( b ? . code ) ;
// 先按主编码:数字优先按数值,否则按字符串
if ( pa . mainIsNum && pb . mainIsNum ) {
if ( pa . mainNum !== pb . mainNum ) return pa . mainNum - pb . mainNum ;
} else {
const c = pa . mainRaw . localeCompare ( pb . mainRaw , 'zh-Hans-CN' , { numeric : true , sensitivity : 'base' } ) ;
if ( c !== 0 ) return c ;
}
// 同主编码时:无子编码的排在前(大类在子类之前)
const aHasSub = ! ! pa . subRaw ;
const bHasSub = ! ! pb . subRaw ;
if ( aHasSub !== bHasSub ) return aHasSub ? 1 : - 1 ;
// 再按子编码
if ( pa . subIsNum && pb . subIsNum ) {
if ( pa . subNum !== pb . subNum ) return pa . subNum - pb . subNum ;
} else {
const c = pa . subRaw . localeCompare ( pb . subRaw , 'zh-Hans-CN' , { numeric : true , sensitivity : 'base' } ) ;
if ( c !== 0 ) return c ;
}
// 最后兜底:按原字符串
return pa . raw . localeCompare ( pb . raw , 'zh-Hans-CN' , { numeric : true , sensitivity : 'base' } ) ;
} ) ;
} ) ;
const pagedTypeRows = computed ( ( ) => {
const start = ( typeCurrentPage . value - 1 ) * typePageSize . value ;
return sortedTypeRows . value . slice ( start , start + typePageSize . value ) ;
} ) ;
function typeGoPage ( p ) {
if ( p < 1 || p > typeTotalPages . value ) return ;
typeCurrentPage . value = p ;
}
function typePrevPage ( ) {
if ( typeCurrentPage . value <= 1 ) return ;
typeCurrentPage . value -= 1 ;
}
function typeNextPage ( ) {
if ( typeCurrentPage . value >= typeTotalPages . value ) return ;
typeCurrentPage . value += 1 ;
}
watch (
( ) => tableData . value . length ,
( ) => {
// 数据变化后,确保当前页有效
if ( typeCurrentPage . value > typeTotalPages . value ) {
typeCurrentPage . value = 1 ;
}
}
) ;
// 获取检验类型列表 - 从后端API获取
const getInspectionTypeList = ( ) => {
listInspectionType ( ) . then ( data => {
@@ -989,6 +1088,44 @@ const filteredInspectionItems = computed(() => {
} ) ;
} ) ;
// ==============================
// 分页(检验项目 tab)
// ==============================
const inspectionCurrentPage = ref ( 1 ) ;
// 每页条数: 按需求固定为10
const inspectionPageSize = ref ( 10 ) ;
const inspectionTotalPages = computed ( ( ) => {
const total = filteredInspectionItems . value . length ;
return Math . max ( 1 , Math . ceil ( total / inspectionPageSize . value ) ) ;
} ) ;
const inspectionPageButtons = computed ( ( ) => {
const total = inspectionTotalPages . value ;
return Array . from ( { length : total } , ( _ , i ) => i + 1 ) ;
} ) ;
const pagedInspectionItems = computed ( ( ) => {
const start = ( inspectionCurrentPage . value - 1 ) * inspectionPageSize . value ;
return filteredInspectionItems . value . slice ( start , start + inspectionPageSize . value ) ;
} ) ;
function inspectionGoPage ( p ) {
if ( p < 1 || p > inspectionTotalPages . value ) return ;
inspectionCurrentPage . value = p ;
}
function inspectionPrevPage ( ) {
if ( inspectionCurrentPage . value <= 1 ) return ;
inspectionCurrentPage . value -= 1 ;
}
function inspectionNextPage ( ) {
if ( inspectionCurrentPage . value >= inspectionTotalPages . value ) return ;
inspectionCurrentPage . value += 1 ;
}
watch (
( ) => filteredInspectionItems . value . length ,
( ) => {
// 过滤条件变化后,回到第一页,避免出现“当前页没数据但页码还在后面”的体验
inspectionCurrentPage . value = 1 ;
}
) ;
// 执行过滤
const filterItems = ( ) => {
// 过滤逻辑已经在computed属性中实现, 这里可以添加额外的逻辑
@@ -1371,7 +1508,9 @@ const addNewRow = () => {
ElMessage . warning ( '请先保存或取消当前正在编辑的行' ) ;
return ;
}
const newRow = { id : Date . now ( ) , code : '' , name : '' , department : departments . value [ 0 ] , sortOrder : tableData . value . length + 1 , remark : '' } ;
// department 在表格里是字符串(科室名称);这里不要直接塞对象,否则后续 trim/校验会异常
const defaultDeptName = departments . value ? . [ 0 ] ? . name || '' ;
const newRow = { id : Date . now ( ) , code : '' , name : '' , department : defaultDeptName , sortOrder : tableData . value . length + 1 , remark : '' } ;
tableData . value . push ( newRow ) ;
editingRowId . value = newRow . id ;
} ;
@@ -1391,41 +1530,28 @@ const handleConfirm = (row) => {
// 确保sortOrder字段存在且为数字类型
sortOrder : row . sortOrder ? Number ( row . sortOrder ) : 0
} ;
// 兼容 department 可能是对象的情况(例如 el-tree-select 返回节点对象)
if ( submitData . department && typeof submitData . department === 'object' ) {
submitData . department = submitData . department . name || submitData . department . label || '' ;
}
console . log ( '原始row数据:' , row ) ;
console . log ( '提交前的submitData:' , submitData ) ;
// 验证必填字段,如果为空则删除该行
// 验证必填字段:为空则提示并保留该行(不要直接删行,否则用户“点确定就没了”体验很差)
if ( ! submitData . code || submitData . code . trim ( ) === '' ) {
// 删除空的编辑行
const index = tableData . value . findIndex ( r => r . id === row . id ) ;
if ( index !== - 1 ) {
tableData . value . splice ( index , 1 ) ;
editingRowId . value = null ;
ElMessage . info ( '已删除空行' ) ;
}
ElMessage . warning ( '请输入大类编码' ) ;
return ;
}
if ( ! submitData . name || submitData . name . trim ( ) === '' ) {
// 删除空的编辑行
const index = tableData . value . findIndex ( r => r . id === row . id ) ;
if ( index !== - 1 ) {
tableData . value . splice ( index , 1 ) ;
editingRowId . value = null ;
ElMessage . info ( '已删除空行' ) ;
}
ElMessage . warning ( '请输入大类项目名称' ) ;
return ;
}
if ( ! submitData . department || submitData . department . trim ( ) === '' ) {
// 删除空的编辑行
const index = tableData . value . findIndex ( r => r . id === row . id ) ;
if ( index !== - 1 ) {
tableData . value . splice ( index , 1 ) ;
editingRowId . value = null ;
ElMessage . info ( '已删除空行' ) ;
}
if ( ! submitData . department || String ( submitData . department ) .trim ( ) === '' ) {
ElMessage . warning ( '请选择执行科室' ) ;
return ;
}
@@ -1486,7 +1612,40 @@ const handleAdd = (row, index) => {
ElMessage . warning ( '请先保存或取消当前正在编辑的行' ) ;
return ;
}
const newRow = { id : Date . now ( ) , code : '' , name : '' , department : row . department , sortOrder : row . sortOrder + 1 , remark : '' } ;
// 行内“+”:在当前大类下新增子类,编码按“父类编码-001”递增生成
const parentCode = ( row ? . code ? ? '' ) . toString ( ) . trim ( ) ;
if ( ! parentCode ) {
ElMessage . warning ( '请先填写并保存当前大类编码,再新增子类' ) ;
return ;
}
// 找到该父类下已有的子类编码,取最大序号 + 1
// 支持如: 1-001、01-002 等
const prefix = ` ${ parentCode } - ` ;
let maxSeq = 0 ;
for ( const r of tableData . value ) {
const code = ( r ? . code ? ? '' ) . toString ( ) ;
if ( code . startsWith ( prefix ) ) {
const suffix = code . slice ( prefix . length ) ;
const n = Number . parseInt ( suffix , 10 ) ;
if ( ! Number . isNaN ( n ) ) {
maxSeq = Math . max ( maxSeq , n ) ;
}
}
}
const nextSeq = maxSeq + 1 ;
const childCode = ` ${ parentCode } - ${ String ( nextSeq ) . padStart ( 3 , '0' ) } ` ;
const newRow = {
id : Date . now ( ) ,
code : childCode ,
name : '' ,
department : row . department ,
// 序号字段保持原逻辑:插入到父类下一行,默认沿用父类序号
sortOrder : row . sortOrder ,
remark : ''
} ;
tableData . value . splice ( index + 1 , 0 , newRow ) ;
editingRowId . value = newRow . id ;
} ;
@@ -2069,22 +2228,52 @@ onMounted(() => {
loadObservationItems ( ) ;
// 加载检验套餐明细项目
loadPackageItemsFromAPI ( ) ;
// 检查URL参数, 如果有tab参数则切换到对应导航项
const query = router . currentRoute . value . query ;
if ( query . tab === '0' || query . tab === '1' || query . tab === '2' ) {
activeNav . value = parseInt ( query . tab ) ;
}
// 检查URL参数中是否有packageId, 如果有则加载套餐数据( 用于编辑或查看)
if ( query . packageId ) {
// 切换到套餐设置标签页
activeNav . value = 2 ;
// 加载套餐数据
loadInspectionPackage ( query . packageId ) ;
}
// 初始化计算套餐金额和服务费
// 初始化计算套餐金额和服务费
calculateAmounts ( ) ;
} ) ;
/**
* 关键修复:
* 从“套餐管理”跳转到这里时,通常只是改变 query( packageId/mode/tab) , 组件不会重新挂载,
* 所以仅靠 onMounted 读取一次 query 会导致“查看/修改”出现空白,刷新才正常。
* 这里监听 query 变化,自动切换 tab 并加载套餐数据。
*/
watch (
( ) => route . query . tab ,
( tab ) => {
if ( tab === '0' || tab === '1' || tab === '2' ) {
activeNav . value = parseInt ( String ( tab ) ) ;
}
} ,
{ immediate : true }
) ;
const applyRouteForPackage = async ( query ) => {
const packageId = query ? . packageId ;
if ( ! packageId ) return ;
activeNav . value = 2 ;
await nextTick ( ) ;
loadInspectionPackage ( String ( packageId ) ) ;
} ;
watch (
( ) => route . query . packageId ,
async ( ) => {
await applyRouteForPackage ( route . query ) ;
} ,
{ immediate : true , flush : 'post' }
) ;
// 兜底:如果该页面被 keep-alive 缓存,从别的页面返回时不会触发 onMounted
onActivated ( ( ) => {
applyRouteForPackage ( route . query ) ;
} ) ;
// 兜底:同一路由复用/仅 query 变化时,确保能触发加载
onBeforeRouteUpdate ( ( to ) => {
applyRouteForPackage ( to . query ) ;
} ) ;
// 监听生成服务费选项变更
watch ( generateServiceFee , ( newVal ) => {
calculateAmounts ( ) ;
@@ -2422,6 +2611,12 @@ watch(packageItems, (newVal) => {
border - color : # 1890 ff ;
}
. page - btn . disabled {
opacity : 0.5 ;
cursor : not - allowed ;
pointer - events : none ;
}
/* 套餐设置样式 */
. top - bar {
height : 48 px ;