Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
zrch-risk-39
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Administrator
zrch-risk-39
Commits
0f145c05
提交
0f145c05
authored
1月 29, 2026
作者:
kxjia
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
提交
上级
73cbfed5
隐藏空白字符变更
内嵌
并排
正在显示
1 个修改的文件
包含
887 行增加
和
25 行删除
+887
-25
Tb3.vue
...isk-client-39/src/views/baosong/report/components/Tb3.vue
+887
-25
没有找到文件。
zrch-risk-client-39/src/views/baosong/report/components/Tb3.vue
浏览文件 @
0f145c05
<
template
>
<div
class=
"bank-report-table"
>
<div
class=
"bank-report-table"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
...
...
@@ -16,6 +16,7 @@
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"validateAndShowResults()"
:disabled=
"loading"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
:disabled=
"loading"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
...
...
@@ -29,7 +30,7 @@
height=
"auto"
class=
"custom-table"
:merge-cells=
"mergeCells"
:loading=
"loading"
<!
--
添加表格
loading
--
>
:loading=
"loading"
>
<vxe-column
field=
"serialNumber"
title=
"序号"
width=
"50"
></vxe-column>
<vxe-column
field=
"project"
title=
"项目"
width=
"40"
>
...
...
@@ -57,6 +58,7 @@
</
template
>
<
template
v-else-if=
"row.type === 'AttachTable'"
>
<AttachTable
:disabled=
"!row.hasRight"
:title=
"row.project"
:records=
"row.datas"
:fields=
"row.content"
...
...
@@ -68,6 +70,7 @@
<
template
v-else-if=
"row.type === 'ExportTable'"
>
<ExportTable
:disabled=
"!row.hasRight"
:title=
"row.project"
:records=
"row.datas"
:fields=
"row.content"
...
...
@@ -88,7 +91,7 @@
</span>
<span
v-else-if=
"item.type === 'radio-group'"
class=
"radio-group"
>
<vxe-radio-group
v-model=
"formData[row.code +'_'+ item.field]"
>
<vxe-radio-group
v-model=
"formData[row.code +'_'+ item.field]"
:disabled=
"!item.hasRight"
>
<vxe-radio
v-for=
"(opt, optIndex) in item.options"
:key=
"optIndex"
...
...
@@ -100,7 +103,7 @@
<div
v-else-if=
"item.type === 'checkbox-group'"
class=
"checkbox-group"
>
<span
class=
"checkbox-group"
>
<vxe-checkbox-group
v-model=
"formData[row.code+'_'+item.field]"
>
<vxe-checkbox-group
v-model=
"formData[row.code+'_'+item.field]"
:disabled=
"!item.hasRight"
>
<vxe-checkbox
v-for=
"(opt, optIndex) in item.options"
:key=
"optIndex"
:label=
"opt"
>
{{ opt }}
</vxe-checkbox>
...
...
@@ -116,7 +119,8 @@
</div>
<
template
v-else-if=
"item.type === 'textarea'"
>
<vxe-textarea
<vxe-textarea
:disabled=
"!item.hasRight"
v-model=
"formData[row.code +'_'+ item.field]"
size=
"mini"
class=
"table-input"
...
...
@@ -128,18 +132,28 @@
</
template
>
<
template
v-else
>
<vxe-input
:type=
"item.type"
v-model=
"formData[row.code +'_'+ item.field]"
size=
"mini"
class=
"table-input"
:style=
"
{width:item.width}"
>
</vxe-input>
<span
class=
"unit"
>
{{
item
.
unit
}}
</span>
<div
class=
"input-wrapper"
>
<vxe-input
:disabled=
"!item.hasRight"
:type=
"item.type"
v-model=
"formData[row.code +'_'+ item.field]"
size=
"mini"
class=
"table-input"
:style=
"
{width:item.width}"
:status="getInputStatus(row.code, item.field)"
@input="handleInputChange(row.code, item.field)"
>
</vxe-input>
<span
class=
"unit"
>
{{
item
.
unit
}}
</span>
<span
v-if=
"item.hasValidFormula"
class=
"help-icon"
@
click
.
stop=
"toggleTooltip(row.code, item.field)"
>
?
</span>
<div
v-if=
"getInputStatus(row.code, item.field) === 'error'"
class=
"error-tip"
>
{{
getErrorMessage
(
row
.
code
,
item
.
field
)
}}
</div>
<div
v-if=
"showTooltip && hoveredKey === row.code +'_'+ item.field"
class=
"tooltip"
@
click
.
stop
>
{{
getValidationRule
(
row
.
code
,
item
.
field
)
}}
</div>
</div>
</
template
>
</template>
</template>
</div>
...
...
@@ -147,11 +161,79 @@
</vxe-column>
<vxe-column
field=
"remarks"
title=
"备注"
width=
"60"
>
<
template
#
default=
"{ row }"
>
<vxe-textarea
:rows=
"row.remarks.rows"
v-model=
"formData[row.code+'_'+row.remarks.field]"
>
<vxe-textarea
:disabled=
"row.remarks.hasRight"
:rows=
"row.remarks.rows"
v-model=
"formData[row.code+'_'+row.remarks.field]"
>
</vxe-textarea>
</
template
>
</vxe-column>
</vxe-table>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"onDrawerShow"
title=
"校验结果"
width=
"40%"
:footer=
"{ show: true }"
>
<
template
#
default
>
<div
class=
"validation-results"
>
<div
class=
"result-summary"
>
<div
class=
"summary-item"
>
<span
class=
"summary-label"
>
校验字段数:
</span>
<span
class=
"summary-value"
>
{{
validationResultsList
.
length
}}
</span>
</div>
<div
class=
"summary-item"
>
<span
class=
"summary-label"
>
通过:
</span>
<span
class=
"summary-value success"
>
{{
validationResultsList
.
filter
(
item
=>
item
.
isValid
).
length
}}
</span>
</div>
<div
class=
"summary-item"
>
<span
class=
"summary-label"
>
失败:
</span>
<span
class=
"summary-value error"
>
{{
validationResultsList
.
filter
(
item
=>
!
item
.
isValid
).
length
}}
</span>
</div>
</div>
<div
class=
"result-list"
>
<div
v-for=
"(item, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: item.isValid, error: !item.isValid }"
>
<div
class=
"result-item-header"
>
<span
class=
"result-item-name"
>
{{
item
.
fieldTitle
||
item
.
fieldName
}}
</span>
<span
class=
"result-item-status"
:class=
"
{ success: item.isValid, error: !item.isValid }">
{{
item
.
isValid
?
'通过'
:
'失败'
}}
</span>
</div>
<div
class=
"result-item-content"
>
<div
class=
"result-item-formula"
>
<span
class=
"formula-label"
>
验证公式:
</span>
<span
class=
"formula-value"
>
{{
item
.
formula
}}
</span>
</div>
<div
class=
"result-item-result"
>
<span
class=
"result-label"
>
验证结果:
</span>
<span
class=
"result-value"
>
{{
item
.
resultMessage
}}
</span>
</div>
<div
class=
"result-item-values"
v-if=
"item.fieldValues"
>
<span
class=
"values-label"
>
字段值:
</span>
<pre
class=
"values-value"
>
{{
formatJSON
(
item
.
fieldValues
)
}}
</pre>
</div>
<div
class=
"result-item-process"
v-if=
"item.process"
>
<span
class=
"process-label"
>
校验过程:
</span>
<div
class=
"process-content"
>
<div
v-for=
"(step, stepIndex) in item.process"
:key=
"stepIndex"
class=
"process-step"
>
<span
class=
"step-number"
>
{{
stepIndex
+
1
}}
</span>
<span
class=
"step-text"
>
{{
step
}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
</div>
</template>
...
...
@@ -164,13 +246,39 @@ import { tableFormData } from '../../data/tb3.data';
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeTablePropTypes
,
VxeToolbarPropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
findUserRightForTplItem
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
allTplItems
}
from
'../../tpl/BaosongTplItem.api'
import
{
getTblvalidFormula
}
from
'../../tpl/BaosongDataValid.api'
import
{
useRoute
}
from
'vue-router'
;
const
route
=
useRoute
();
const
tableRef
=
ref
();
const
tplItemMap
=
ref
({});
const
userAllocItems
=
ref
([])
const
validFormula
=
ref
([])
const
validationResults
=
ref
<
Record
<
string
,
string
>>
({})
const
validationMessages
=
ref
<
Record
<
string
,
string
>>
({})
const
formData
=
reactive
({});
const
formValues
=
ref
<
FormData
[]
>
([])
const
showTooltip
=
ref
(
false
)
const
hoveredKey
=
ref
(
''
)
// 校验结果抽屉相关
const
drawerVisible
=
ref
(
false
)
const
validationResultsList
=
ref
<
any
[]
>
([])
interface
ValidationResult
{
fieldTitle
:
string
fieldName
:
string
formula
:
string
isValid
:
boolean
resultMessage
:
string
process
?:
string
[]
}
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
...
...
@@ -211,8 +319,19 @@ onMounted(async ()=>{
}
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
});
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
});
await
setFormItemRight
();
await
setTplItemMap
()
await
setData
();
await
validateAllInputs
();
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`初始化页面失败:
${
error
.
message
}
`
,
...
...
@@ -223,11 +342,32 @@ onMounted(async ()=>{
}
})
const
formData
=
reactive
({});
const
formValues
=
ref
<
FormData
[]
>
([])
const
setFormItemRight
=
async
()
=>
{
tableFormData
.
forEach
(
row
=>
{
if
(
row
.
content
&&
Array
.
isArray
(
row
.
content
))
{
row
.
content
.
forEach
(
item
=>
{
if
(
item
.
field
)
{
let
tmpKey
=
`
${
row
.
code
}
_
${
item
.
field
}
`
;
item
[
"hasRight"
]
=
userAllocItems
.
value
.
indexOf
(
tmpKey
)
>-
1
item
[
"hasValidFormula"
]
=
validFormula
.
value
?.
length
>
0
&&
validFormula
.
value
.
some
((
f
:
any
)
=>
{
const
formulaText
=
(
f
.
formula
||
''
).
toString
()
return
formulaText
.
includes
(
item
.
field
)
})
}
});
}
if
(
row
.
remarks
&&
row
.
remarks
.
field
)
{
let
tmpKey
=
`
${
row
.
code
}
_
${
row
.
remarks
.
field
}
`
;
row
.
remarks
[
"hasRight"
]
=
userAllocItems
.
value
.
indexOf
(
tmpKey
)
>-
1
row
.
remarks
[
"hasValidFormula"
]
=
validFormula
.
value
?.
length
>
0
&&
validFormula
.
value
.
some
((
f
:
any
)
=>
{
const
formulaText
=
(
f
.
formula
||
''
).
toString
()
return
formulaText
.
includes
(
row
.
remarks
.
field
)
})
}
});
}
const
saveBatch
=
async
()
=>
{
try
{
...
...
@@ -321,6 +461,7 @@ const setFormValues = async (pcode,code,valData,rind) => {
};
formValues
.
value
.
push
(
tempForm
)
}
async
function
setData
()
{
try
{
loading
.
value
=
true
;
// 开始加载
...
...
@@ -335,12 +476,14 @@ async function setData() {
valueObj
[
key
]
=
data
.
content
;
}
for
(
const
row
of
tableFormData
)
{
if
(
!
row
.
type
&&
row
.
content
)
{
for
(
const
cdata
of
row
.
content
)
{
if
(
cdata
.
field
&&
cdata
.
field
.
length
>
0
)
{
const
strKey
=
`
${
row
.
code
}
_
${
cdata
.
field
}
`
;
const
item
=
tplItemMap
.
value
[
strKey
];
if
(
!
item
)
continue
;
const
{
pid
,
itemid
,
formTp
}
=
item
;
const
dataVal
=
valueObj
[
`
${
pid
}
_
${
itemid
}
_1`
];
...
...
@@ -423,21 +566,30 @@ async function setData() {
}
async
function
setTplItemMap
()
{
try
{
loading
.
value
=
true
;
// 开始加载
loading
.
value
=
true
;
const
tplid
=
queryParam
.
value
.
tplId
const
tplItem
=
await
allTplItems
({
tplid
:
tplid
,
})
tplItem
.
forEach
(
item
=>
{
let
strKey
=
item
.
pcode
+
"_"
+
item
.
xmlcode
;
tplItemMap
.
value
[
strKey
]
=
{
pid
:
item
.
pid
,
itemid
:
item
.
id
,
formTp
:
item
.
formTp
,
code
:
item
.
xmlcode
};
tplItemMap
.
value
[
strKey
]
=
{
pid
:
item
.
pid
,
itemid
:
item
.
id
,
formTp
:
item
.
formTp
,
code
:
item
.
xmlcode
,
isDisable
:
!
userAllocItems
.
value
.
some
(
itemid
=>
itemid
==
item
.
id
)
}
})
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`加载数据失败:
${
error
.
message
}
`
,
status
:
'error'
})
}
finally
{
loading
.
value
=
false
;
// 结束加载
}
}
const
mergeCells
=
ref
<
VxeTablePropTypes
.
MergeCells
>
([
{
row
:
0
,
col
:
1
,
rowspan
:
9
,
colspan
:
1
},
{
row
:
9
,
col
:
1
,
rowspan
:
1
,
colspan
:
2
},
...
...
@@ -448,6 +600,374 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
{
row
:
9
,
col
:
3
,
rowspan
:
1
,
colspan
:
2
},
])
const
handleInputChange
=
(
rowCode
:
string
,
itemField
:
string
)
=>
{
const
key
=
`
${
rowCode
}
_
${
itemField
}
`
validateInput
(
key
)
}
const
getInputStatus
=
(
rowCode
:
string
,
itemField
:
string
):
'default'
|
'error'
=>
{
const
key
=
`
${
rowCode
}
_
${
itemField
}
`
return
validationResults
.
value
[
key
]
||
''
}
const
getErrorMessage
=
(
rowCode
:
string
,
itemField
:
string
):
string
=>
{
const
key
=
`
${
rowCode
}
_
${
itemField
}
`
return
validationMessages
.
value
[
key
]
||
''
}
const
toggleTooltip
=
(
rowCode
:
string
,
itemField
:
string
)
=>
{
const
key
=
`
${
rowCode
}
_
${
itemField
}
`
if
(
hoveredKey
.
value
===
key
)
{
showTooltip
.
value
=
false
hoveredKey
.
value
=
''
}
else
{
showTooltip
.
value
=
true
hoveredKey
.
value
=
key
}
}
const
getValidationRule
=
(
rowCode
:
string
,
itemField
:
string
):
string
=>
{
if
(
!
validFormula
.
value
||
validFormula
.
value
.
length
===
0
)
{
return
''
}
try
{
const
formulaItems
=
validFormula
.
value
const
field
=
itemField
const
targetFormula
=
formulaItems
.
find
((
f
:
any
)
=>
{
const
formulaText
=
(
f
.
formula
||
''
).
toString
()
const
regex
=
new
RegExp
(
`\\b
${
field
}
\\b`
,
'gi'
)
return
regex
.
test
(
formulaText
)
})
if
(
targetFormula
)
{
return
targetFormula
.
des
||
`验证规则:
${
targetFormula
.
formula
}
`
}
return
''
}
catch
(
error
)
{
console
.
error
(
'获取验证规则失败:'
,
error
)
return
'获取验证规则失败'
}
}
const
closeTooltip
=
(
event
:
Event
)
=>
{
const
target
=
event
.
target
as
HTMLElement
if
(
!
target
.
closest
(
'.help-icon'
))
{
showTooltip
.
value
=
false
hoveredKey
.
value
=
''
}
}
const
formatJSON
=
(
obj
:
any
):
string
=>
{
if
(
!
obj
)
{
return
''
}
try
{
return
JSON
.
stringify
(
obj
,
null
,
2
)
}
catch
(
error
)
{
return
JSON
.
stringify
({
error
:
'序列化失败'
})
}
}
const
onDrawerShow
=
()
=>
{
// 抽屉显示时,设置容器高度为屏幕高度
setTimeout
(()
=>
{
const
drawer
=
document
.
querySelector
(
'.vxe-drawer--body'
)
if
(
drawer
)
{
const
availableHeight
=
window
.
innerHeight
-
280
// 减去顶部和底部的空间
const
resultList
=
drawer
.
querySelector
(
'.result-list'
)
if
(
resultList
)
{
resultList
.
classList
.
add
(
'result-list--scrolling'
)
resultList
.
style
.
maxHeight
=
`
${
availableHeight
}
px`
}
}
},
100
)
}
const
validateAndShowResults
=
async
()
=>
{
if
(
!
validFormula
.
value
||
validFormula
.
value
.
length
===
0
)
{
VxeUI
.
modal
.
message
({
content
:
'没有配置验证规则'
,
status
:
'warning'
})
return
}
try
{
loading
.
value
=
true
validationResultsList
.
value
=
[]
const
codeVal
=
await
buildCodeValueMap
()
const
formulaItems
=
validFormula
.
value
for
(
const
strKey
in
formData
)
{
const
val
=
formData
[
strKey
]
if
(
val
===
undefined
||
val
===
null
||
val
===
''
)
{
continue
}
const
field
=
strKey
.
split
(
'_'
)[
1
]
const
targetFormula
=
formulaItems
.
find
((
f
:
any
)
=>
{
const
formulaText
=
(
f
.
formula
||
''
).
toString
()
const
regex
=
new
RegExp
(
`\\b
${
field
}
\\b`
,
'gi'
)
return
regex
.
test
(
formulaText
)
})
if
(
targetFormula
)
{
const
formulaText
=
(
targetFormula
.
formula
||
''
).
toString
()
let
isValid
=
false
let
resultMessage
=
''
let
process
:
string
[]
=
[]
let
fieldTitle
=
''
// 获取字段标题
for
(
const
key
in
tplItemMap
.
value
)
{
if
(
key
.
endsWith
(
`_
${
field
}
`
))
{
const
item
=
tplItemMap
.
value
[
key
]
if
(
item
&&
item
.
name
)
{
fieldTitle
=
item
.
name
break
}
}
}
try
{
process
.
push
(
`开始校验字段:
${
field
}
`
)
process
.
push
(
`验证公式:
${
formulaText
}
`
)
process
.
push
(
`字段值:
${
JSON
.
stringify
(
codeVal
)}
`
)
const
result
=
await
evaluateFormulaWithProcess
(
formulaText
,
codeVal
,
process
)
isValid
=
result
.
isValid
resultMessage
=
result
.
message
if
(
isValid
)
{
process
.
push
(
`校验结果: 通过`
)
}
else
{
process
.
push
(
`校验结果: 失败 -
${
resultMessage
}
`
)
}
}
catch
(
error
)
{
isValid
=
true
resultMessage
=
`校验跳过:
${
error
.
message
}
`
process
.
push
(
`校验异常:
${
error
.
message
}
`
)
}
validationResultsList
.
value
.
push
({
fieldTitle
:
fieldTitle
,
fieldName
:
field
,
formula
:
formulaText
,
isValid
,
resultMessage
,
process
,
fieldValues
:
codeVal
})
}
}
if
(
validationResultsList
.
value
.
length
>
0
)
{
drawerVisible
.
value
=
true
}
else
{
VxeUI
.
modal
.
message
({
content
:
'没有找到需要校验的字段'
,
status
:
'warning'
})
}
}
catch
(
error
)
{
console
.
error
(
'校验失败:'
,
error
)
VxeUI
.
modal
.
message
({
content
:
`校验失败:
${
error
.
message
}
`
,
status
:
'error'
})
}
finally
{
loading
.
value
=
false
}
}
const
evaluateFormulaWithProcess
=
async
(
formula
:
string
,
codeVal
:
Record
<
string
,
string
>
,
process
:
string
[]
):
Promise
<
{
isValid
:
boolean
;
message
:
string
}
>
=>
{
try
{
process
.
push
(
'检查公式中用到的字段...'
)
const
fieldPattern
=
/
\b([
a-zA-Z_
][
a-zA-Z0-9_
]
*
)\b
/g
const
fieldsInFormula
=
new
Set
<
string
>
()
let
match
while
((
match
=
fieldPattern
.
exec
(
formula
))
!==
null
)
{
fieldsInFormula
.
add
(
match
[
1
])
}
process
.
push
(
`公式中用到的字段:
${
Array
.
from
(
fieldsInFormula
).
join
(
', '
)}
`
)
for
(
const
field
of
fieldsInFormula
)
{
if
(
!
codeVal
.
hasOwnProperty
(
field
)
||
codeVal
[
field
]
===
undefined
||
codeVal
[
field
]
===
null
||
codeVal
[
field
]
===
''
||
codeVal
[
field
]
===
'undefined'
||
codeVal
[
field
]
===
'null'
)
{
process
.
push
(
`字段
${
field
}
为空,跳过校验`
)
return
{
isValid
:
true
,
message
:
`字段
${
field
}
为空,跳过校验`
}
}
}
process
.
push
(
'所有字段都有值,开始执行公式计算...'
)
let
processedFormula
=
formula
for
(
const
[
key
,
value
]
of
Object
.
entries
(
codeVal
))
{
const
paramRegex
=
new
RegExp
(
`\\b
${
key
}
\\b`
,
'g'
)
processedFormula
=
processedFormula
.
replace
(
paramRegex
,
value
)
}
process
.
push
(
`处理后的公式:
${
processedFormula
}
`
)
const
result
=
eval
(
processedFormula
)
const
resultStr
=
String
(
result
)
if
(
resultStr
===
'true'
||
resultStr
===
'True'
||
resultStr
===
'1'
||
result
===
true
)
{
process
.
push
(
`公式计算结果:
${
resultStr
}
(通过)`
)
return
{
isValid
:
true
,
message
:
`通过 (
${
resultStr
}
)`
}
}
else
{
process
.
push
(
`公式计算结果:
${
resultStr
}
(失败)`
)
return
{
isValid
:
false
,
message
:
`不通过 (
${
resultStr
}
)`
}
}
}
catch
(
error
)
{
process
.
push
(
`公式计算失败:
${
error
.
message
}
`
)
return
{
isValid
:
true
,
message
:
`校验跳过 -
${
error
.
message
}
`
}
}
}
const
validateInput
=
async
(
key
:
string
)
=>
{
if
(
!
validFormula
.
value
||
validFormula
.
value
.
length
===
0
)
{
validationResults
.
value
[
key
]
=
'default'
validationMessages
.
value
[
key
]
=
''
return
}
try
{
const
formulaItems
=
validFormula
.
value
const
field
=
key
.
split
(
'_'
)[
1
]
const
targetFormula
=
formulaItems
.
find
((
f
:
any
)
=>
{
const
formulaText
=
(
f
.
formula
||
''
).
toString
()
const
regex
=
new
RegExp
(
`\\b
${
field
}
\\b`
,
'gi'
)
return
regex
.
test
(
formulaText
)
})
if
(
targetFormula
)
{
const
codeVal
=
await
buildCodeValueMap
()
const
formulaText
=
(
targetFormula
.
formula
||
''
).
toString
()
const
isValid
=
await
evaluateFormula
(
formulaText
,
codeVal
)
if
(
isValid
)
{
validationResults
.
value
[
key
]
=
'default'
validationMessages
.
value
[
key
]
=
''
}
else
{
validationResults
.
value
[
key
]
=
'error'
validationMessages
.
value
[
key
]
=
targetFormula
.
des
||
`验证失败:
${
formulaText
}
`
}
}
else
{
validationResults
.
value
[
key
]
=
'default'
validationMessages
.
value
[
key
]
=
''
}
}
catch
(
error
)
{
console
.
error
(
'验证失败:'
,
error
)
validationResults
.
value
[
key
]
=
'error'
validationMessages
.
value
[
key
]
=
'验证计算失败'
}
}
const
buildCodeValueMap
=
async
():
Promise
<
Record
<
string
,
string
>>
=>
{
const
codeVal
:
Record
<
string
,
string
>
=
{}
for
(
const
strKey
in
formData
)
{
const
val
=
formData
[
strKey
]
if
(
val
!==
undefined
&&
val
!==
null
&&
val
!==
''
)
{
const
parts
=
strKey
.
split
(
'_'
)
if
(
parts
.
length
>=
2
)
{
const
field
=
parts
[
1
]
codeVal
[
field
]
=
Array
.
isArray
(
val
)
?
val
.
join
(
','
)
:
String
(
val
)
}
}
}
return
codeVal
}
const
evaluateFormula
=
async
(
formula
:
string
,
codeVal
:
Record
<
string
,
string
>
):
Promise
<
boolean
>
=>
{
try
{
// 提取公式中用到的所有字段名(假设字段名是字母数字下划线组合)
const
fieldPattern
=
/
\b([
a-zA-Z_
][
a-zA-Z0-9_
]
*
)\b
/g
const
fieldsInFormula
=
new
Set
<
string
>
()
let
match
while
((
match
=
fieldPattern
.
exec
(
formula
))
!==
null
)
{
fieldsInFormula
.
add
(
match
[
1
])
}
// 检查所有用到的字段是否都有值
for
(
const
field
of
fieldsInFormula
)
{
// 如果字段在 codeVal 中但值为空,或者不在 codeVal 中,都认为是空
if
(
!
codeVal
.
hasOwnProperty
(
field
)
||
codeVal
[
field
]
===
undefined
||
codeVal
[
field
]
===
null
||
codeVal
[
field
]
===
''
||
codeVal
[
field
]
===
'undefined'
||
codeVal
[
field
]
===
'null'
)
{
return
true
// 有字段为空,不进行校验,默认通过
}
}
let
processedFormula
=
formula
for
(
const
[
key
,
value
]
of
Object
.
entries
(
codeVal
))
{
const
paramRegex
=
new
RegExp
(
`\\b
${
key
}
\\b`
,
'g'
)
processedFormula
=
processedFormula
.
replace
(
paramRegex
,
value
)
}
const
result
=
eval
(
processedFormula
)
return
Boolean
(
result
)
}
catch
(
error
)
{
console
.
error
(
'公式计算失败:'
,
error
,
'公式:'
,
formula
)
return
true
// 计算失败时也通过,避免因为计算错误导致验证失败
}
}
const
validateAllInputs
=
async
()
=>
{
if
(
!
validFormula
.
value
||
validFormula
.
value
.
length
===
0
)
{
return
}
try
{
const
formulaItems
=
validFormula
.
value
const
codeVal
=
await
buildCodeValueMap
()
validationResults
.
value
=
{}
validationMessages
.
value
=
{}
for
(
const
strKey
in
formData
)
{
const
val
=
formData
[
strKey
]
if
(
val
!==
undefined
&&
val
!==
null
&&
val
!==
''
)
{
const
field
=
strKey
.
split
(
'_'
)[
1
]
const
targetFormula
=
formulaItems
.
find
((
f
:
any
)
=>
{
const
formulaText
=
(
f
.
formula
||
''
).
toString
()
const
regex
=
new
RegExp
(
`\\b
${
field
}
\\b`
,
'gi'
)
return
regex
.
test
(
formulaText
)
})
if
(
targetFormula
)
{
const
formulaText
=
(
targetFormula
.
formula
||
''
).
toString
()
const
isValid
=
await
evaluateFormula
(
formulaText
,
codeVal
)
if
(
isValid
)
{
validationResults
.
value
[
strKey
]
=
'default'
validationMessages
.
value
[
strKey
]
=
''
}
else
{
validationResults
.
value
[
strKey
]
=
'error'
validationMessages
.
value
[
strKey
]
=
targetFormula
.
des
||
`验证失败:
${
formulaText
}
`
}
}
}
}
}
catch
(
error
)
{
console
.
error
(
'全量验证失败:'
,
error
)
}
}
</
script
>
<
style
lang=
"less"
scoped
>
.bank-report-table {
...
...
@@ -630,4 +1150,345 @@ blockquote {
cursor: pointer;
}
.input-wrapper {
position: relative;
display: inline-block;
}
.input-wrapper .unit {
margin-left: 4px;
}
.input-wrapper .error-tip {
position: absolute;
top: 100%;
left: 0;
margin-top: 2px;
padding: 4px 8px;
background: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
font-size: 11px;
color: #ff4d4f;
line-height: 1.4;
max-width: 200px;
word-wrap: break-word;
z-index: 1000;
white-space: normal;
box-shadow: 0 2px 8px rgba(255, 77, 79, 0.15);
}
.input-wrapper .error-tip::before {
content: '';
position: absolute;
top: -5px;
left: 10px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #ffccc7;
}
.vxe-input.status--error .vxe-input--inner {
border-color: #ff4d4f !important;
background-color: #fff2f0 !important;
}
.input-wrapper .help-icon {
display: inline-block;
width: 16px;
height: 16px;
background: #1890ff;
color: white;
border-radius: 50%;
text-align: center;
line-height: 16px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
margin-left: 4px;
user-select: none;
transition: all 0.3s;
}
.input-wrapper .help-icon:hover {
background: #40a9ff;
transform: scale(1.1);
}
.input-wrapper .tooltip {
position: absolute;
top: 100%;
left: 0;
margin-top: 2px;
padding: 6px 10px;
background: #1f1f1f;
color: white;
border-radius: 6px;
font-size: 12px;
line-height: 1.5;
max-width: 250px;
word-wrap: break-word;
z-index: 1001;
white-space: normal;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.input-wrapper .tooltip::before {
content: '';
position: absolute;
top: -5px;
left: 12px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #1f1f1f;
}
/* 校验结果抽屉样式 */
.validation-results {
padding: 16px;
height: 90vh;
}
.result-summary {
display: flex;
gap: 16px;
margin-bottom: 16px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
}
.summary-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.result-list {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 100%;
overflow-y: auto;
}
.result-item {
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 12px;
transition: all 0.3s;
}
.result-item.success {
border-color: #52c41a;
background: #f6ffed;
}
.result-item.error {
border-color: #ff4d4f;
background: #fff2f0;
}
.result-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #e8e8e8;
}
.result-item-name {
font-weight: bold;
font-size: 14px;
}
.result-item-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.result-item-status.success {
background: #f6ffed;
color: #52c41a;
border: 1px solid #95de64;
}
.result-item-status.error {
background: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.result-item-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.result-item-formula,
.result-item-result,
.result-item-process {
display: flex;
gap: 8px;
align-items: flex-start;
}
.result-item-formula {
font-family: 'Courier New', monospace;
background: #fafafa;
padding: 8px;
border-radius: 4px;
border: 1px solid #e8e8e8;
}
.formula-label,
.result-label,
.formula-label,
.result-label,
.process-label,
.values-label {
font-weight: bold;
font-size: 12px;
color: #666;
min-width: 80px;
}
.formula-value,
.result-value,
.values-value {
font-size: 12px;
flex: 1;
}
.result-value {
color: #333;
}
.process-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.process-step {
display: flex;
gap: 8px;
align-items: flex-start;
font-size: 12px;
color: #666;
}
.step-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
background: #1890ff;
color: white;
border-radius: 50%;
font-size: 10px;
font-weight: bold;
flex-shrink: 0;
}
.step-text {
flex: 1;
}
.result-item-process {
border-top: 1px solid #e8e8e8;
padding-top: 8px;
}
.result-item-values {
display: flex;
gap: 8px;
align-items: flex-start;
}
.values-label {
font-weight: bold;
font-size: 12px;
color: #666;
min-width: 80px;
}
.values-value {
font-family: 'Courier New', monospace;
font-size: 11px;
line-height: 1.5;
background: #fafafa;
padding: 8px;
border-radius: 4px;
border: 1px solid #e8e8e8;
overflow-x: auto;
max-height: 200px;
overflow-y: auto;
flex: 1;
white-space: pre;
word-break: break-all;
}
/* 抽屉样式调整 */
.vxe-drawer--right {
width: 700px !important;
}
.vxe-drawer--header {
border-bottom: 1px solid #e8e8e8;
padding-bottom: 12px;
}
.vxe-drawer--title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.vxe-drawer--body {
padding: 16px;
max-height: calc(100vh - 140px);
overflow-y: auto;
}
.result-list {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 500px;
overflow-y: auto;
}
.result-list--scrolling {
max-height: calc(100vh - 280px) !important;
}
</
style
>
\ No newline at end of file
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论