@@ -1,514 +0,0 @@
< template >
< div class = "app-container" >
< el-row :gutter = "10" class = "mb8" >
< el-col :span = "1.5" >
< el-button type = "primary" plain icon = "Plus" @click ="onAdd" > 新设 ( A ) < / el -button >
< / el-col >
< el-col :span = "1.5" >
< el-button type = "danger" plain icon = "Delete" :disabled = "multiple" @click ="onDelete" > 删除 ( D ) < / el -button >
< / el-col >
< el-col :span = "1.5" >
< el-button type = "success" plain icon = "Check" :disabled = "multiple" @click ="() => onSave()" > 保存 ( S ) < / el -button >
< / el-col >
< el-col :span = "1.5" >
< el-button type = "warning" plain icon = "Close" @click ="onClose" > 关闭 ( X ) < / el -button >
< / el-col >
< el-col v-if = "canToggleViewAll" :span="4" >
< el -switch
v-model = "viewAll"
active -text = " 查看全部 "
inactive -text = " 仅本人 "
@change ="getList"
/ >
< / el-col >
< / el-row >
< el-table
v-loading = "loading"
:data = "tableData"
@ selection -change = " handleSelectionChange "
:row-class-name = "tableRowClassName"
>
< el-table-column type = "selection" width = "50" align = "center" / >
< el-table-column label = "序号" type = "index" width = "60" align = "center" / >
< el-table-column label = "操作员" prop = "operatorName" min -width = " 120 " / >
< el-table-column label = "员工工号" prop = "staffNo" min -width = " 120 " / >
< el-table-column label = "领用日期" prop = "receiveDate" min -width = " 140 " >
< template # default = "{ row }" >
< el-date-picker
v-if = "row._editing"
v-model = "row.receiveDate"
type = "date"
value -format = " YYYY -MM -DD "
placeholder = "选择日期"
style = "width: 100%"
/ >
< span v-else > {{ row.receiveDate }} < / span >
< / template >
< / el -table -column >
< el-table-column label = "起始号码" prop = "startNo" min -width = " 140 " >
< template # default = "{ row }" >
< el-input
v-if = "row._editing"
v -model .trim = " row.startNo "
@blur ="() => validateNumField(row, 'startNo')"
/ >
< span v-else > {{ row.startNo }} < / span >
< / template >
< / el -table -column >
< el-table-column label = "终止号码" prop = "endNo" min -width = " 140 " >
< template # default = "{ row }" >
< el-input
v-if = "row._editing"
v -model .trim = " row.endNo "
@blur ="() => validateNumField(row, 'endNo')"
/ >
< span v-else > {{ row.endNo }} < / span >
< / template >
< / el -table -column >
< el-table-column label = "使用号码" prop = "usedNo" min -width = " 140 " >
< template # default = "{ row }" >
< el-input
v-if = "row._editing"
v -model .trim = " row.usedNo "
@blur ="() => validateNumField(row, 'usedNo')"
/ >
< span v-else > {{ row.usedNo }} < / span >
< / template >
< / el -table -column >
< el-table-column label = "操作" width = "100" fixed = "right" >
< template # default = "scope" >
< el-button type = "primary" link icon = "Edit" @click ="() => openEdit(scope.row, scope.$index)" > 编辑 < / el -button >
< / template >
< / el-table-column >
< / el-table >
< pagination
v-show = "total > 0"
:total = "total"
v -model :page = "queryParams.pageNo"
v -model :limit = "queryParams.pageSize"
@pagination ="getList"
/ >
< / div >
<!-- 编辑弹窗 -- >
< el-dialog v-model = "dialogVisible" :title="dialogTitle" width="600px" append -to -body >
< el-form label -width = " 100px " >
< el-form-item label = "操作员" >
< el-input v-model = "editForm.operatorName" disabled / >
< / el-form-item >
< el-form-item label = "员工工号" >
< el-input v-model = "editForm.staffNo" disabled / >
< / el-form-item >
< el-form-item label = "领用日期" >
< el-date-picker v-model = "editForm.receiveDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" / >
< / el-form-item >
< el-form-item label = "起始号码" >
< el-input v -model .trim = " editForm.startNo " @blur ="() => validateNumField(editForm, 'startNo')" / >
< / el-form-item >
< el-form-item label = "终止号码" >
< el-input v -model .trim = " editForm.endNo " @blur ="() => validateNumField(editForm, 'endNo')" / >
< / el-form-item >
< el-form-item label = "使用号码" >
< el-input v -model .trim = " editForm.usedNo " @blur ="() => validateNumField(editForm, 'usedNo')" / >
< / el-form-item >
< / el-form >
< template # footer >
< div class = "dialog-footer" >
< el-button @click ="dialogVisible = false" > 取 消 < / el -button >
< el-button type = "primary" @click ="confirmEdit" > 确 定 < / el -button >
< / div >
< / template >
< / el-dialog >
< / template >
< script setup name = "InvoiceManagement" >
import useUserStore from '@/store/modules/user'
import { getConfigKey } from '@/api/system/config'
const { proxy } = getCurrentInstance ( )
const userStore = useUserStore ( )
const loading = ref ( false )
const tableData = ref ( [ ] )
const total = ref ( 0 )
const ids = ref ( [ ] )
const multiple = ref ( true )
const viewAll = ref ( false )
const canToggleViewAll = ref ( false )
const dialogVisible = ref ( false )
const dialogTitle = ref ( '编辑门诊号码段' )
const editIndex = ref ( - 1 )
const editForm = reactive ( {
receiveDate : '' ,
startNo : '' ,
endNo : '' ,
usedNo : '' ,
operatorName : '' ,
staffNo : '' ,
} )
const data = reactive ( {
queryParams : {
pageNo : 1 ,
pageSize : 10 ,
onlySelf : true ,
} ,
} )
const { queryParams } = toRefs ( data )
initConfig ( )
getList ( )
async function initConfig ( ) {
try {
const res = await getConfigKey ( 'outpatient_no_view_all' )
canToggleViewAll . value = ( res ? . msg === 'Y' || res ? . data === 'Y' )
} catch ( e ) {
canToggleViewAll . value = false
}
}
function handleSelectionChange ( selection ) {
ids . value = selection . map ( ( item ) => item . id )
multiple . value = ! selection . length
}
function onAdd ( ) {
const now = new Date ( )
const yyyy = now . getFullYear ( )
const mm = String ( now . getMonth ( ) + 1 ) . padStart ( 2 , '0' )
const dd = String ( now . getDate ( ) ) . padStart ( 2 , '0' )
tableData . value . push ( {
id : undefined ,
operatorId : userStore . id ,
operatorName : userStore . name || userStore . nickName ,
staffNo : userStore . id ,
receiveDate : ` ${ yyyy } - ${ mm } - ${ dd } ` ,
startNo : '' ,
endNo : '' ,
usedNo : '' ,
_editing : true ,
_error : false ,
} )
}
function onClose ( ) {
proxy . $tab . closePage ( )
}
function tableRowClassName ( { row } ) {
return row . _error ? 'error-row' : ''
}
function openEdit ( row , index ) {
editIndex . value = index
dialogTitle . value = '编辑门诊号码段'
editForm . receiveDate = row . receiveDate
editForm . startNo = row . startNo
editForm . endNo = row . endNo
editForm . usedNo = row . usedNo
editForm . operatorName = row . operatorName
editForm . staffNo = row . staffNo
dialogVisible . value = true
}
function confirmEdit ( ) {
const tmp = { ... tableData . value [ editIndex . value ] , ... editForm }
if ( ! validateRow ( tmp , editIndex . value ) ) return
tableData . value [ editIndex . value ] = {
... tableData . value [ editIndex . value ] ,
... editForm ,
_dirty : true , // 标记为已修改,顶部保存时提交
}
dialogVisible . value = false
}
function extractPrefix ( value ) {
if ( ! value ) return ''
const chars = value . split ( '' )
for ( let i = chars . length - 1 ; i >= 0 ; i -- ) {
if ( /[A-Za-z]/ . test ( chars [ i ] ) ) {
return value . slice ( 0 , i )
}
}
return ''
}
function extractTailNumber ( value ) {
if ( ! value ) return NaN
const m = value . match ( /(\d+)$/ )
if ( ! m ) return NaN
return parseInt ( m [ 1 ] , 10 )
}
function lengthWithinLimit ( value ) {
if ( ! value ) return false
const m = value . match ( /(\d+)$/ )
if ( ! m ) return false
return m [ 1 ] . length <= 12
}
function rangesOverlap ( aStart , aEnd , bStart , bEnd ) {
return Math . max ( aStart , bStart ) <= Math . min ( aEnd , bEnd )
}
function alertWarn ( msg ) {
if ( proxy . $modal && proxy . $modal . alertWarning ) {
proxy . $modal . alertWarning ( msg )
} else if ( proxy . $message ) {
proxy . $message . warning ( msg )
}
}
// 校验: 必须以数字结尾且尾部数字长度≤12
function isTailDigitsValid ( value ) {
const m = String ( value || '' ) . match ( /(\d+)$/ )
return ! ! m && m [ 1 ] . length <= 12
}
function validateNumField ( row , field , rowIndex ) {
if ( ! isTailDigitsValid ( row [ field ] ) ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中, 最大位数为12位, 且必须以数字结尾! ` : '最大位数为12位, 且必须以数字结尾'
alertWarn ( msg )
row . _error = true
row . _warned = row . _warned || { }
row . _warned [ field ] = true
return false
}
row . _error = false
row . _warned = row . _warned || { }
row . _warned [ field ] = false
return true
}
function onNumberInput ( row , field ) {
row . _warned = row . _warned || { }
const valid = isTailDigitsValid ( row [ field ] )
if ( ! valid && ! row . _warned [ field ] ) {
alertWarn ( '最大位数为12位, 且必须以数字结尾' )
row . _warned [ field ] = true
}
if ( valid ) {
row . _warned [ field ] = false
}
}
function validateRow ( row , rowIndex ) {
row . _error = false
if ( ! lengthWithinLimit ( row . startNo ) || ! lengthWithinLimit ( row . endNo ) || ! lengthWithinLimit ( row . usedNo ) ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中, 最大位数为12位, 且必须以数字结尾! ` : '最大位数为12位, 且必须以数字结尾'
alertWarn ( msg )
row . _error = true
return false
}
if ( ( row . startNo ? . length || 0 ) !== ( row . endNo ? . length || 0 ) ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中,起始号码与终止号码长度必须一致,请修改! ` : '起始号码与终止号码长度必须一致'
alertWarn ( msg )
row . _error = true
return false
}
const p1 = extractPrefix ( row . startNo )
const p2 = extractPrefix ( row . endNo )
const p3 = extractPrefix ( row . usedNo )
if ( ! ( p1 === p2 && p2 === p3 ) ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中,门诊号码的字母前缀必须相同,请修改! ` : '行数据中,门诊号码的字母前缀必须相同,请修改!'
alertWarn ( msg )
row . _error = true
return false
}
const sNum = extractTailNumber ( row . startNo )
const eNum = extractTailNumber ( row . endNo )
if ( Number . isNaN ( sNum ) || Number . isNaN ( eNum ) || sNum > eNum ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中,起始/终止号码不合法 ` : '起始/终止号码不合法'
alertWarn ( msg )
row . _error = true
return false
}
// 使用号码必须在起始与终止范围内
const uNum = extractTailNumber ( row . usedNo )
if ( Number . isNaN ( uNum ) || uNum < sNum || uNum > eNum ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中,使用号码必须在起始与终止号码范围内 ` : '使用号码必须在起始与终止号码范围内'
alertWarn ( msg )
row . _error = true
return false
}
const prefix = p1
for ( let i = 0 ; i < tableData . value . length ; i ++ ) {
const other = tableData . value [ i ]
if ( other === row || ! other . startNo || ! other . endNo ) continue
if ( extractPrefix ( other . startNo ) !== prefix ) continue
const os = extractTailNumber ( other . startNo )
const oe = extractTailNumber ( other . endNo )
if ( ! Number . isNaN ( os ) && ! Number . isNaN ( oe ) ) {
if ( rangesOverlap ( sNum , eNum , os , oe ) ) {
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData . value . indexOf ( row )
const lineNo = idxInTable >= 0 ? idxInTable + 1 : ( editIndex . value >= 0 ? editIndex . value + 1 : undefined )
const msg = lineNo ? ` 第【 ${ lineNo } 】行数据中,门诊号码和【 ${ i + 1 } 】行的门诊号码有冲突,请修改! ` : '门诊号码设置重复!'
alertWarn ( msg )
row . _error = true
return false
}
}
}
return true
}
function onSave ( row ) {
const rows = row ? [ row ] : tableData . value . filter ( r => ids . value . includes ( r . id ) || r . _dirty || r . _editing )
if ( ! rows . length ) return
for ( const r of rows ) {
const idx = tableData . value . indexOf ( r )
if ( ! validateRow ( r , idx ) ) return
}
const ok = lcUpsertMany ( rows . map ( ( r ) => ( {
id : r . id ,
operatorId : r . operatorId ,
operatorName : r . operatorName ,
staffNo : r . staffNo ,
receiveDate : r . receiveDate ,
startNo : r . startNo ,
endNo : r . endNo ,
usedNo : r . usedNo ,
} ) ) )
if ( ! ok ) return
if ( proxy . $modal ? . alertSuccess ) {
proxy . $modal . alertSuccess ( '保存成功!' )
} else {
proxy . $message . success ( '保存成功!' )
}
getList ( )
}
function onDelete ( ) {
const rows = tableData . value . filter ( ( r ) => ids . value . includes ( r . id ) )
if ( ! rows . length ) return
for ( const r of rows ) {
const canDeleteSelf = String ( r . operatorId ) === String ( userStore . id )
const neverUsed = r . usedNo === r . startNo
if ( ! canDeleteSelf ) {
alertWarn ( '只能删除自己维护的门诊号码段' )
return
}
if ( ! neverUsed ) {
alertWarn ( '已有门诊号码段已有使用的门诊号码,请核对!' )
return
}
}
const doRealDelete = ( ) => {
lcDeleteByIds ( rows . map ( ( r ) => r . id ) )
if ( proxy . $modal ? . alertSuccess ) {
proxy . $modal . alertSuccess ( '删除成功' )
} else {
proxy . $message . success ( '删除成功' )
}
getList ( )
}
if ( proxy . $modal ? . confirm ) {
proxy . $modal . confirm ( '是否确认删除选中数据项?' ) . then ( doRealDelete )
} else {
doRealDelete ( )
}
}
function getList ( ) {
loading . value = true
queryParams . value . onlySelf = ! viewAll . value
const res = lcList ( { ... queryParams . value } )
tableData . value = res . records . map ( ( it ) => ( {
... it ,
_editing : false ,
_error : false ,
_dirty : false ,
} ) )
total . value = res . total
loading . value = false
}
// (测试辅助功能已移除)
// 纯前端本地持久化方法( localStorage)
const STORAGE _KEY = 'ohis_outpatient_no_segments'
function lcReadAll ( ) {
try {
const raw = localStorage . getItem ( STORAGE _KEY )
const arr = raw ? JSON . parse ( raw ) : [ ]
return Array . isArray ( arr ) ? arr : [ ]
} catch ( e ) {
return [ ]
}
}
function lcWriteAll ( list ) {
localStorage . setItem ( STORAGE _KEY , JSON . stringify ( list || [ ] ) )
}
function lcList ( { pageNo = 1 , pageSize = 10 , onlySelf = true } ) {
const all = lcReadAll ( )
const filtered = onlySelf ? all . filter ( ( x ) => String ( x . operatorId ) === String ( userStore . id ) ) : all
const start = ( pageNo - 1 ) * pageSize
const end = start + pageSize
return { records : filtered . slice ( start , end ) , total : filtered . length , all }
}
function checkOverlapAll ( row , all ) {
const prefix = extractPrefix ( row . startNo )
const sNum = extractTailNumber ( row . startNo )
const eNum = extractTailNumber ( row . endNo )
for ( const it of all ) {
if ( row . id && it . id === row . id ) continue
if ( ! it . startNo || ! it . endNo ) continue
if ( extractPrefix ( it . startNo ) !== prefix ) continue
const os = extractTailNumber ( it . startNo )
const oe = extractTailNumber ( it . endNo )
if ( ! Number . isNaN ( os ) && ! Number . isNaN ( oe ) ) {
if ( rangesOverlap ( sNum , eNum , os , oe ) ) return true
}
}
return false
}
function lcUpsertMany ( rows ) {
const all = lcReadAll ( )
for ( const r of rows ) {
if ( checkOverlapAll ( r , all ) ) {
alertWarn ( '门诊号码设置重复!' )
return false
}
if ( ! r . id ) {
r . id = ` ${ Date . now ( ) } _ ${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } `
}
const idx = all . findIndex ( ( x ) => x . id === r . id )
if ( idx >= 0 ) all [ idx ] = { ... all [ idx ] , ... r }
else all . push ( { ... r } )
}
lcWriteAll ( all )
return true
}
function lcDeleteByIds ( idList ) {
const all = lcReadAll ( )
const remain = all . filter ( ( x ) => ! idList . includes ( x . id ) )
lcWriteAll ( remain )
}
< / script >
< style scoped >
. error - row { -- el - table - tr - bg - color : # fff7e6 ; }
. mb8 { margin - bottom : 8 px ; }
< / style >