@@ -4,12 +4,6 @@
<!-- 页面标题和用户信息 -- >
< div class = "header-section" >
< h2 > 发票管理 < / h2 >
< div class = "user-info" >
< span class = "welcome-text" > 欢迎 , { { currentUser . name } } < / span >
< span class = "role-badge" :class = "currentUser.role" >
{ { currentUser . role === 'admin' ? '管理员' : '操作员' } }
< / span >
< / div >
< / div >
<!-- 操作按钮区域 -- >
@@ -58,35 +52,14 @@
< td class = "sequence-number" > { { index + 1 } } < / td >
< td class = "employee-info" >
< div class = "input-container" >
< select
v-if = "item.isActive"
v-model = "item.operator"
class = "form-control"
>
< option value = "" > 请选择操作员 < / option >
< option
v-for = "user in userList"
:key = "user.employeeId"
:value = "user.name"
:data-employee-id = "user.employeeId"
@change ="updateEmployeeId(item, user.employeeId)"
>
{ { user . name } }
< / option >
< / select >
< span v-else class = "employee-name" > { { item . operator || '-' } } < / span >
<!-- 操作员字段始终不可编辑 -- >
< span class = "employee-name" > { { item . operator || '-' } } < / span >
< / div >
< / td >
< td class = "employee-id-cell" >
< div class = "input-container" >
< input
v-if = "item.isActive"
v-model = "item.employeeId"
class = "form-control"
placeholder = "请输入员工工号"
maxlength = "10"
/ >
< span v-else > {{ item.employeeId | | ' - ' }} < / span >
<!-- 员工工号字段始终不可编辑 -- >
< span > { { item . employeeId || '-' } } < / span >
< / div >
< / td >
< td class = "date-cell" >
@@ -191,6 +164,7 @@
< script >
import { listUser } from '@/api/system/user' ;
import request from '@/utils/request' ; // 导入请求工具
import useUserStore from '@/store/modules/user' ; // 导入用户store
export default {
name : 'InvoiceManagement' ,
@@ -198,9 +172,10 @@ export default {
return {
// 用户信息和权限
currentUser : {
name : 'admin ' ,
employeeId : '1702 ' ,
role : 'admin' // operator: 普通操作员, admin: 管理员
name : '' ,
nickName : '' ,
employeeId : '' ,
role : '' // operator: 普通操作员, admin: 管理员
} ,
// 用户列表, 用于操作员下拉选择( 将在created中从后端获取)
userList : [ ] ,
@@ -222,6 +197,17 @@ export default {
} ,
created ( ) {
console . log ( '组件初始化,开始加载数据' ) ;
// 获取当前登录用户信息
const userStore = useUserStore ( ) ;
this . currentUser = {
name : userStore . nickName || userStore . name ,
nickName : userStore . nickName ,
employeeId : userStore . id || userStore . practitionerId ,
role : userStore . roles && userStore . roles . length > 0 ? userStore . roles [ 0 ] : 'operator'
} ;
console . log ( '当前登录用户信息:' , this . currentUser ) ;
// 从后端获取用户列表后再加载发票数据,确保用户信息可用
this . getUserList ( ) . then ( ( ) => {
console . log ( '用户列表加载完成,开始加载发票数据' ) ;
@@ -247,12 +233,17 @@ export default {
methods : {
// 获取用户列表
getUserList ( ) {
return listUser ( ) . then ( ( res ) => {
// 传递分页参数,获取所有用户数据
const queryParams = {
pageNum : 1 ,
pageSize : 1000 // 设置较大的pageSize以获取所有用户
} ;
return listUser ( queryParams ) . then ( ( res ) => {
// 从响应中提取用户列表,并转换为需要的格式
this . userList = res . data . records . map ( user => ( {
name : user . nickName , // 使用用户昵称作为显示名称
name : user . nickName || user . username || user . name , // 尝试多种可能的名称字段
employeeId : user . userId , // 使用用户ID作为员工工号
role : user . practitionerRolesDtoList ? . some ( role => role . roleKey === 'admin' ) ? 'admin' : 'operator'
role : user . role || 'operator' // 默认为普通操作员
} ) ) ;
console . log ( '获取到的用户列表:' , this . userList ) ;
return Promise . resolve ( ) ;
@@ -451,42 +442,33 @@ export default {
return user ? user . name : '' ;
} ,
// 检查是否有权限修改指定记录
canModifyRecord ( record ) {
// 管理员可以修改所有记录,普通用户只能修改自己的
return this . isAdmin || record . employeeId === this . currentUser . employeeId ;
} ,
// 检查权限并执行操作
checkPermissionAndExecute ( record , operation ) {
if ( this . canModifyRecord ( record ) ) {
operation ( ) ;
} else {
alert ( '您没有权限修改此记录!' ) ;
}
} ,
// 更新员工ID
updateEmployeeId ( item , employeeId ) {
item . employeeId = employeeId ;
} ,
addNewRow ( ) {
// 新增行时自动填充当前用户信息
// 使用负数作为临时ID, 避免与后端数据库ID冲突
cons t new Id = - Math . max ( ... this . invoiceData . map ( item => Math . abs ( item . id ) ) , 0 ) - 1 ;
this . invoiceData . push ( {
le t max Id = 0 ;
if ( this. invoiceData . length > 0 ) {
maxId = Math . max ( ... this . invoiceData . map ( item => Math . abs ( item . id ) ) ) ;
}
const newId = - ( maxId + 1 ) ;
const newSegmentId = Date . now ( ) ; // 生成唯一的segmentId
const currentDate = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ;
const newRecord = {
id : newId ,
segmentId : newSegmentId , // 为新记录设置segmentId
operator : this . currentUser . name , // 自动使用当前用户名称
employeeId : this . currentUser . employeeId ,
date : new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ,
date : currentDate , // 自动填充当日日期
startNum : '' ,
endNum : '' ,
currentNum : '' ,
status : '未使用' ,
isActive : true , // 新增行默认处于编辑状态
isNewRecord : true // 添加标记表示这是新记录
} ) ;
} ;
console . log ( '添加新行,自动填充领用日期为当日:' , { newRecord } ) ;
this . invoiceData . push ( newRecord ) ;
} ,
deleteRow ( record ) {
@@ -495,6 +477,19 @@ export default {
return ;
}
// 对于新添加的记录, 直接从前端移除, 不需要调用API
if ( record . isNewRecord ) {
if ( confirm ( '确定要删除这条未保存的记录吗?' ) ) {
const index = this . invoiceData . findIndex ( item => item . keyId === record . keyId ) ;
if ( index > - 1 ) {
this . invoiceData . splice ( index , 1 ) ;
this . filterDataByPermission ( ) ;
alert ( '删除成功' ) ;
}
}
return ;
}
// 严格检查权限:只有管理员或记录所有者可以删除
if ( this . currentUser . role !== 'admin' && record . employeeId !== this . currentUser . employeeId ) {
alert ( '您没有权限删除此记录!操作员只能删除自己的记录。' ) ;
@@ -815,17 +810,15 @@ export default {
} ,
// 从字符串末尾向前提取前缀,直到遇到第一个字母
// 规则:前缀定义为从号码最末位开始往前推直到出现字母为止的 前面字符, 若无字母则无前缀
// 规则:前缀定义为从号码最末位开始往前推直到出现字母为止,其 前面的 字符全部称为前缀; 若无字母则无前缀
extractPrefixFromEnd ( str ) {
if ( ! str ) return '' ;
// 从末尾向前遍历字符串
for ( let i = str . length - 1 ; i >= 0 ; i -- ) {
// 如果遇到字母,返回从开始到该字母之前的部分
// 例如:对于"ABC123",从末尾向前找到第一个字母'C',前缀就是'AB'
// 对于"123ABC456",从末尾向前找到第一个字母'C',前缀就是"123AB"
// 如果遇到字母,返回从开始到该字母的所有字符
if ( /[A-Za-z]/ . test ( str [ i ] ) ) {
return str . substring ( 0 , i ) ;
return str . substring ( 0 , i + 1 ) ;
}
}
// 如果没有找到字母,则无前缀
@@ -924,6 +917,21 @@ export default {
} ,
// 检查号码范围是否与其他记录重叠
// 提取数字部分进行比较的辅助方法
extractNumber ( str ) {
if ( ! str ) return 0 ;
const match = str . match ( /\d+$/ ) ;
return match ? parseInt ( match [ 0 ] , 10 ) : 0 ;
} ,
// 提取完整前缀的辅助方法(包括字母和数字)
extractPrefix ( str ) {
if ( ! str ) return '' ;
// 匹配开头的所有非数字字符(字母等)和随后的数字部分,直到遇到第一个非数字字符为止
const match = str . match ( /^[A-Za-z0-9]+/ ) ;
return match ? match [ 0 ] : '' ;
} ,
checkRangeOverlap ( record ) {
// 确保当前记录有必要的数据
if ( ! record || ! record . startNum || ! record . endNum ) {
@@ -934,25 +942,10 @@ export default {
// 使用keyId作为比较依据, 因为它是前端唯一标识符
const otherRecords = this . invoiceData . filter ( item => item . keyId !== record . keyId ) ;
// 提取数字部分进行比较的辅助函数
const extractNumber = function ( str ) {
if ( ! str ) return 0 ;
const match = str . match ( /\d+$/ ) ;
return match ? parseInt ( match [ 0 ] , 10 ) : 0 ;
} ;
// 提取完整前缀的辅助函数(包括字母和数字)
const extractPrefix = function ( str ) {
if ( ! str ) return '' ;
// 匹配开头的所有非数字字符(字母等)和随后的数字部分,直到遇到第一个非数字字符为止
const match = str . match ( /^[A-Za-z0-9]+/ ) ;
return match ? match [ 0 ] : '' ;
} ;
// 提取当前记录的前缀和数字范围
const currentPrefix = extractPrefix ( record . startNum ) ;
const currentStartNum = extractNumber ( record . startNum ) ;
const currentEndNum = extractNumber ( record . endNum ) ;
const currentPrefix = this . extractPrefix ( record . startNum ) ;
const currentStartNum = this . extractNumber ( record . startNum ) ;
const currentEndNum = this . extractNumber ( record . endNum ) ;
// 确保当前记录的起始号码小于等于终止号码
if ( currentStartNum > currentEndNum ) {
@@ -963,14 +956,14 @@ export default {
if ( ! item . startNum || ! item . endNum ) continue ;
// 提取其他记录的前缀
const otherPrefix = extractPrefix ( item . startNum ) ;
const otherPrefix = this . extractPrefix ( item . startNum ) ;
// 检查前缀是否匹配(实现数字对应数字,字母对应字母的匹配规则)
if ( currentPrefix !== otherPrefix ) continue ;
// 提取其他记录的数字范围
const otherStartNum = extractNumber ( item . startNum ) ;
const otherEndNum = extractNumber ( item . endNum ) ;
const otherStartNum = this . extractNumber ( item . startNum ) ;
const otherEndNum = this . extractNumber ( item . endNum ) ;
// 全面检查范围重叠
const hasOverlap =
@@ -996,12 +989,6 @@ export default {
// 如果当前号码为空,允许保存
if ( ! currentNum ) return true ;
// 数字部分比较(纯数字或字母前缀+数字)
const extractNumber = ( str ) => {
const match = str . match ( /\d+$/ ) ;
return match ? parseInt ( match [ 0 ] , 10 ) : 0 ;
} ;
// 检查两个字符串的每一位是否类型匹配(数字对应数字,字母对应字母)
const checkCharacterTypesMatch = ( str1 , str2 ) => {
const maxLength = Math . max ( str1 . length , str2 . length ) ;
@@ -1023,18 +1010,13 @@ export default {
return false ;
}
const currentNumValue = extractNumber ( currentNum ) ;
const startNumValue = extractNumber ( startNum ) ;
const endNumValue = extractNumber ( endNum ) ;
const currentNumValue = this . extractNumber ( currentNum ) ;
const startNumValue = this . extractNumber ( startNum ) ;
const endNumValue = this . extractNumber ( endNum ) ;
return currentNumValue >= startNumValue && currentNumValue <= endNumValue ;
} ,
// 验证单个记录
// 测试用例说明:
// 1. 测试起始和终止号码前缀一致性:例如"AB123"和"AB456"通过,"AB123"和"AC456"失败
// 2. 测试当前号码与起始号码前缀一致性:例如"AB123"作为起始,"AB125"作为当前号码通过,"AC125"失败
// 3. 测试无字母情况:纯数字"123456"作为起始、终止和当前号码都通过验证
validateRecord ( record , rowIndex ) {
const errors = [ ] ;
const rowInfo = rowIndex ? ` 第 ${ rowIndex } 行 ` : '' ;
@@ -1145,24 +1127,23 @@ export default {
const savePromises = recordsToSave . map ( ( record , index ) => {
// 准备发送到后端的数据格式, 适配adm_invoice_segment表结构
const dataToSend = {
// 对于更新操作, 同时传递id和segmentId, 确保后端能正确识别
... ( ! record . isNewRecord && { id : record . keyId } ) ,
// 确保segmentId不为null, 优先使用keyId, 然后是segmentId, 新记录时使用生成的临时ID
segmentId : record . keyId || record . segmentId || Date . now ( ) ,
// 员工和开票员信息
employeeId : record . employeeId ,
employeeName : record . operator || this . getUserNameById ( record . employeeId ) ,
// 员工和开票员信息, 确保字段名称与后端API期望一致
invoicingStaffId : record . employeeId ,
invoicingStaffName : record . operator || this . getUserNameById ( record . employeeId ) ,
// 日期信息
employeeId : record . employeeId , // 同时提供employeeId字段以保持一致性
// 日期信息, 确保同时设置billBusDate和createDate字段
billBusDate : record . date ? new Date ( record . date ) : null ,
createDate : record . date ? new Date ( record . date ) : null ,
// 号码信息,只 使用数据库表对应 的字段名
// 号码信息,使用后端API期望 的字段名
beginNumber : record . startNum ,
startNum : record . startNum , // 同时提供startNum字段以保持一致性
endNumber : record . endNum ,
endNum : record . endNum , // 同时提供endNum字段以保持一致性
currentNumber : record . currentNum ,
currentNum : record . currentNum , // 同时提供currentNum字段以保持一致性
// 状态信息
status : record . status || '未使用' ,
statusEnum : { value : record . status || '未使用' } , // 同时提供statusEnum字段
// 备注信息
remark : record . currentNum ?
( record . remark && ! record . remark . includes ( '当前使用号码:' ) ?
@@ -1174,6 +1155,13 @@ export default {
deleteFlag : '0'
} ;
// 对于更新记录, 设置id字段; 对于新记录, 不设置id, 让后端自动生成
if ( ! record . isNewRecord ) {
dataToSend . id = record . id || record . keyId ;
}
// 对于所有记录, 确保设置segmentId字段, 因为数据库表中segment_id是必填的
dataToSend . segmentId = record . segmentId || Date . now ( ) ; // 确保segmentId有值
// 添加调试信息,打印完整的记录对象
console . log ( ` 准备 ${ record . isNewRecord ? '新增' : '更新' } 的原始记录对象: ` , {
keyId : record . keyId ,
@@ -1200,6 +1188,8 @@ export default {
return response ;
} ) . catch ( err => {
console . error ( ` ${ operation } 记录 ${ index + 1 } 失败: ` , err ) ;
console . error ( ` ${ operation } 记录 ${ index + 1 } 失败详情: ` , JSON . stringify ( err , null , 2 ) ) ;
console . error ( ` ${ operation } 记录 ${ index + 1 } 请求数据: ` , JSON . stringify ( dataToSend , null , 2 ) ) ;
throw err ; // 重新抛出错误以便上层catch捕获
} ) ;
} ) ;