Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
zrch-risk-39
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Administrator
zrch-risk-39
Commits
9cb3fade
提交
9cb3fade
authored
2月 02, 2026
作者:
liuluyu
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'master' of
http://47.97.51.208/root/zrch-risk-39
上级
d692b0a9
4030e116
隐藏空白字符变更
内嵌
并排
正在显示
18 个修改的文件
包含
3972 行增加
和
376 行删除
+3972
-376
package.json
zrch-risk-client-39/package.json
+1
-0
pnpm-lock.yaml
zrch-risk-client-39/pnpm-lock.yaml
+8
-0
Tb1.vue
...isk-client-39/src/views/baosong/report/components/Tb1.vue
+664
-51
Tb10.vue
...sk-client-39/src/views/baosong/report/components/Tb10.vue
+370
-4
Tb2.vue
...isk-client-39/src/views/baosong/report/components/Tb2.vue
+418
-2
Tb4.vue
...isk-client-39/src/views/baosong/report/components/Tb4.vue
+354
-2
Tb5.vue
...isk-client-39/src/views/baosong/report/components/Tb5.vue
+537
-263
Tb6.vue
...isk-client-39/src/views/baosong/report/components/Tb6.vue
+374
-6
Tb7.vue
...isk-client-39/src/views/baosong/report/components/Tb7.vue
+365
-5
Tb8.vue
...isk-client-39/src/views/baosong/report/components/Tb8.vue
+374
-7
Tb9.vue
...isk-client-39/src/views/baosong/report/components/Tb9.vue
+374
-8
HistoryFillCheck.vue
...iews/baosong/report/components/check/HistoryFillCheck.vue
+12
-7
BaosongDataValidController.java
...es/stm/baosong/controller/BaosongDataValidController.java
+11
-0
BaosongTaskAllocController.java
...es/stm/baosong/controller/BaosongTaskAllocController.java
+27
-0
BaosongTaskRecordController.java
...s/stm/baosong/controller/BaosongTaskRecordController.java
+73
-4
BaosongTplController.java
.../modules/stm/baosong/controller/BaosongTplController.java
+4
-1
BaosongDataValid.java
...rg/jeecg/modules/stm/baosong/entity/BaosongDataValid.java
+0
-14
application-dev.yml
...jeecg-system-start/src/main/resources/application-dev.yml
+6
-2
没有找到文件。
zrch-risk-client-39/package.json
浏览文件 @
9cb3fade
...
...
@@ -79,6 +79,7 @@
"swagger-ui-dist"
:
"^5.29.3"
,
"tinymce"
:
"6.6.2"
,
"vditor"
:
"^3.11.2"
,
"vkbeautify"
:
"^0.99.3"
,
"vue"
:
"^3.5.22"
,
"vue-cropper"
:
"^0.6.5"
,
"vue-cropperjs"
:
"^5.0.0"
,
...
...
zrch-risk-client-39/pnpm-lock.yaml
浏览文件 @
9cb3fade
...
...
@@ -176,6 +176,9 @@ importers:
vditor
:
specifier
:
^3.11.2
version
:
3.11.2
vkbeautify
:
specifier
:
^0.99.3
version
:
0.99.3
vue
:
specifier
:
^3.5.22
version
:
3.5.27(typescript@5.9.3)
...
...
@@ -7619,6 +7622,9 @@ packages:
yaml
:
optional
:
true
vkbeautify@0.99.3
:
resolution
:
{
integrity
:
sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==
}
vue-component-type-helpers@2.2.12
:
resolution
:
{
integrity
:
sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==
}
...
...
@@ -15963,6 +15969,8 @@ snapshots:
terser
:
5.46.0
tsx
:
4.21.0
vkbeautify@0.99.3
:
{}
vue-component-type-helpers@2.2.12
:
{}
vue-cropper@0.6.5
:
{}
...
...
zrch-risk-client-39/src/views/baosong/report/components/Tb1.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height:80vh"
>
<div
class=
"bank-report-table"
style=
"height:80vh"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
buttons
>
<div
style=
"margin:10px"
>
...
...
@@ -8,8 +16,9 @@
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<vxe-table
...
...
@@ -50,6 +59,7 @@
:calcSum=
"row.calcSum"
:showFooter=
"row.showFooter"
:ref=
"(el) => setAttachTableRef(el, row.code)"
:disabled=
"!row.hasRight"
style=
"margin:0px;padding:0px"
/>
</
template
>
...
...
@@ -62,6 +72,7 @@
:footerData=
"row.footerData"
:showFooter=
"row.showFooter"
:ref=
"(el) => setMyVxeTableRef(el, row.code)"
:disabled=
"!row.hasRight"
style=
"margin:0px;padding:0px"
/>
</
template
>
...
...
@@ -78,7 +89,7 @@
<span
v-else-if=
"item.type === 'radio-group'"
class=
"radio-group"
>
<vxe-radio-group
v-model=
"formData[row.code +'_'+ item.field]"
:disabled=
"
item.relatedFiled&&formData[row.code +'_'+ item.relatedFiled]!='是'
"
:disabled=
"
!item.hasRight || (item.relatedFiled&&formData[row.code +'_'+ item.relatedFiled]!='是')
"
>
<vxe-radio
v-for=
"(opt, optIndex) in item.options"
...
...
@@ -90,17 +101,17 @@
</span>
<div
v-else-if=
"item.type === '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>
<
template
v-if=
"item.otherField"
>
<div
style=
"width:100%"
>
<vxe-checkbox
label=
"其他"
>
<vxe-checkbox
label=
"其他"
:disabled=
"!item.hasRight"
>
其他:
</vxe-checkbox>
<vxe-input
v-model=
"formData[row.code + '_'+ item.otherField]"
:disabled=
"!(formData[row.code+'_'+item.field]?.indexOf('其他')>-1)"
:disabled=
"!(formData[row.code+'_'+item.field]?.indexOf('其他')>-1)
|| !item.hasRight
"
style=
"width: 50%"
>
</vxe-input>
...
...
@@ -112,10 +123,10 @@
<div
v-else-if=
"item.type === 'checkboxAndInput'"
>
<span
style=
"margin-left:20px"
>
<vxe-checkbox-group
v-model=
"formData[row.code+'_'+item.field]"
>
<vxe-checkbox
v-for=
"(opt, optIndex) in item.options"
:key=
"optIndex"
:label=
"opt"
style=
"margin-left: 0px;display: block;"
>
<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"
:disabled=
"!item.hasRight"
style=
"margin-left: 0px;display: block;"
>
{{ opt }}
<vxe-input
type=
"number"
v-model=
"formData[row.code + '_'+ item.optionItemField[optIndex]]"
style=
"width: 100px;margin: 0px;"
>
<vxe-input
type=
"number"
v-model=
"formData[row.code + '_'+ item.optionItemField[optIndex]]"
:disabled=
"!item.hasRight"
style=
"width: 100px;margin: 0px;"
>
</vxe-input>
<span
class=
"unit"
>
{{ item.optionItemUint }}
</span>
</vxe-checkbox>
...
...
@@ -123,18 +134,27 @@
</span>
</div>
<
template
v-else
>
<vxe-input
:disabled=
"item.relatedFiled&&formData[row.code +'_'+ item.relatedFiled]!='是'"
:type=
"item.type"
v-model=
"formData[row.code +'_'+ item.field]"
size=
"mini"
class=
"table-input"
:style=
"item.style"
>
</vxe-input>
<span
class=
"unit"
>
{{
item
.
unit
}}
</span>
<div
class=
"input-wrapper"
>
<vxe-input
:disabled=
"!item.hasRight || (item.relatedFiled&&formData[row.code +'_'+ item.relatedFiled]!='是')"
:type=
"item.type"
v-model=
"formData[row.code +'_'+ item.field]"
size=
"mini"
class=
"table-input"
:style=
"item.style"
: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>
...
...
@@ -148,7 +168,75 @@
</
template
>
</vxe-column>
</vxe-table>
<CheckTbData
ref=
"refCheckTbData"
></CheckTbData>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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>
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
</div>
</template>
...
...
@@ -156,21 +244,47 @@
import
MultiColumnTable
from
'../tableComponents/MultiColumnTable.vue'
import
AttachTable
from
'../tableComponents/AttachTable.vue'
import
MyVxeTable1
from
'../tableComponents/MyVxeTable.vue'
import
CheckTbData
from
'./CheckTbData.vue'
import
HistoryFillCheck
from
'./check/HistoryFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb1.data'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeToolbarInstance
,
VxeToolbarPropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
findUserRightForTplItem
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
allTplItems
}
from
'../../tpl/BaosongTplItem.api'
import
{
getTblvalidFormula
}
from
'../../tpl/BaosongDataValid.api'
import
{
useRoute
}
from
'vue-router'
;
const
refCheckTbData
=
ref
();
const
historyFillCheckRef
=
ref
();
const
historyDrawerVisible
=
ref
(
false
);
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
:
''
,
...
...
@@ -190,6 +304,9 @@ interface FormData {
rind
:
number
}
// 初始化loading状态
const
loading
=
ref
(
true
)
onMounted
(
async
()
=>
{
if
(
route
.
query
.
taskId
)
{
queryParam
.
value
.
taskId
=
Number
(
route
.
query
.
taskId
);
...
...
@@ -198,31 +315,71 @@ onMounted(async ()=>{
queryParam
.
value
.
taskName
=
String
(
route
.
query
.
taskName
);
}
if
(
route
.
query
.
tplId
)
{
queryParam
.
value
.
tplId
=
Number
(
route
.
query
.
tplId
);
queryParam
.
value
.
tplId
=
Number
(
route
.
query
.
tplId
);
}
if
(
route
.
query
.
tplName
)
{
queryParam
.
value
.
tplName
=
String
(
route
.
query
.
tplName
);
queryParam
.
value
.
tplName
=
String
(
route
.
query
.
tplName
);
}
if
(
route
.
query
.
tplCode
)
{
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
await
setTplItemMap
()
await
setData
();
})
const
loading
=
ref
(
false
)
const
formData
=
reactive
({});
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
});
const
formValues
=
ref
<
FormData
[]
>
([])
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
});
await
setFormItemRight
();
await
setTplItemMap
()
await
setData
();
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`初始化页面失败:
${
error
.
message
}
`
,
status
:
'error'
});
}
finally
{
loading
.
value
=
false
}
})
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
)
})
}
});
}
});
}
const
saveBatch
=
async
()
=>
{
try
{
const
saveLoading
=
ref
(
true
)
formValues
.
value
=
[]
await
getFillDatas
()
if
(
formValues
.
value
.
length
>
0
)
{
for
(
const
strKey
in
formData
)
{
const
valData
=
formData
[
strKey
]
if
(
valData
)
{
let
tmpAry
=
strKey
.
split
(
"_"
)
await
setFormValues
(
tmpAry
[
0
],
tmpAry
[
1
],
valData
,
1
)
}
}
await
getAttachTableFormData
();
await
getAllMultiTableFormData
()
if
(
formValues
.
value
.
length
>
0
)
{
await
batchSaveOrUpdateBeforeDelete
(
formValues
.
value
)
VxeUI
.
modal
.
message
({
content
:
'保存成功'
,
status
:
'success'
})
}
else
{
...
...
@@ -235,6 +392,235 @@ const saveBatch = async () => {
}
}
const
handleOpenHistoryDrawer
=
()
=>
{
historyFillCheckRef
.
value
.
onDrawerShow
(
queryParam
.
value
.
tplId
);
historyDrawerVisible
.
value
=
true
;
}
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
;
}
const
checkData
=
async
()
=>
{
validationResultsList
.
value
=
[]
drawerVisible
.
value
=
true
}
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
const
evaluateFormula
=
(
formula
:
string
,
formulaDes
:
string
,
process
:
string
[]):
ValidationResult
|
null
=>
{
const
fieldRegex
=
/COL
\d
+/g
const
fields
=
[...
new
Set
(
formula
.
match
(
fieldRegex
)
||
[])]
process
.
push
(
`公式包含字段:
${
fields
.
join
(
', '
)}
`
)
const
fieldValues
:
Record
<
string
,
any
>
=
{}
let
hasEmptyField
=
false
for
(
const
field
of
fields
)
{
const
value
=
getFieldValue
(
field
)
fieldValues
[
field
]
=
value
if
(
value
===
''
||
value
===
null
||
value
===
undefined
)
{
hasEmptyField
=
true
process
.
push
(
`字段
${
field
}
值为空,跳过此公式校验`
)
}
}
if
(
hasEmptyField
)
{
return
{
fieldTitle
:
formulaDes
,
fieldName
:
fields
.
join
(
', '
),
formula
,
isValid
:
true
,
resultMessage
:
'跳过校验 (有字段值为空)'
,
process
:
[...
process
]
}
}
process
.
push
(
'所有字段值都已获取,开始计算'
)
process
.
push
(
`字段值:
${
JSON
.
stringify
(
fieldValues
)}
`
)
try
{
const
sanitizedFormula
=
sanitizeAndEvaluate
(
formula
,
fieldValues
,
process
)
process
.
push
(
`计算公式:
${
sanitizedFormula
}
`
)
const
result
=
evaluateString
(
sanitizedFormula
)
process
.
push
(
`计算结果:
${
result
}
`
)
if
(
result
)
{
return
{
fieldTitle
:
formulaDes
,
fieldName
:
fields
.
join
(
', '
),
formula
,
isValid
:
true
,
resultMessage
:
'校验通过'
,
fieldValues
,
process
:
[...
process
]
}
}
else
{
return
{
fieldTitle
:
formulaDes
,
fieldName
:
fields
.
join
(
', '
),
formula
,
isValid
:
false
,
resultMessage
:
'校验失败 - 验证公式返回 false'
,
fieldValues
,
process
:
[...
process
]
}
}
}
catch
(
error
)
{
process
.
push
(
`计算出错:
${
error
instanceof
Error
?
error
.
message
:
'未知错误'
}
`
)
return
{
fieldTitle
:
formulaDes
,
fieldName
:
fields
.
join
(
', '
),
formula
,
isValid
:
false
,
resultMessage
:
`校验出错:
${
error
instanceof
Error
?
error
.
message
:
'未知错误'
}
`
,
fieldValues
,
process
:
[...
process
]
}
}
}
const
getFieldValue
=
(
field
:
string
):
any
=>
{
for
(
const
row
of
tableFormData
)
{
if
(
row
.
content
&&
Array
.
isArray
(
row
.
content
))
{
for
(
const
item
of
row
.
content
)
{
if
(
item
.
field
===
field
)
{
return
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
}
}
}
}
return
null
}
const
sanitizeAndEvaluate
=
(
formula
:
string
,
fieldValues
:
Record
<
string
,
any
>
,
process
:
string
[]):
string
=>
{
let
sanitizedFormula
=
formula
const
wordBoundaryRegex
=
/COL
\d
+/g
sanitizedFormula
=
sanitizedFormula
.
replace
(
wordBoundaryRegex
,
(
match
)
=>
{
const
value
=
fieldValues
[
match
]
process
.
push
(
`替换
${
match
}
=
${
value
}
`
)
return
value
!==
''
&&
value
!==
null
&&
value
!==
undefined
?
String
(
value
)
:
'null'
})
sanitizedFormula
=
sanitizedFormula
.
replace
(
/==/g
,
'==='
)
sanitizedFormula
=
sanitizedFormula
.
replace
(
/!==/g
,
'!=='
)
return
sanitizedFormula
}
const
evaluateString
=
(
str
:
string
):
any
=>
{
try
{
const
func
=
new
Function
(
'return '
+
str
)
return
func
()
}
catch
(
error
)
{
throw
new
Error
(
`公式计算错误:
${
str
}
`
)
}
}
const
getInputStatus
=
(
rowCode
:
string
,
field
:
string
):
'success'
|
'error'
|
''
=>
{
const
key
=
`
${
rowCode
}
_
${
field
}
`
return
validationResults
.
value
[
key
]
===
'error'
?
'error'
:
''
}
const
getErrorMessage
=
(
rowCode
:
string
,
field
:
string
):
string
=>
{
const
key
=
`
${
rowCode
}
_
${
field
}
`
return
validationMessages
.
value
[
key
]
||
''
}
const
handleInputChange
=
(
rowCode
:
string
,
field
:
string
)
=>
{
validateField
(
rowCode
,
field
)
}
const
validateField
=
(
rowCode
:
string
,
field
:
string
)
=>
{
const
key
=
`
${
rowCode
}
_
${
field
}
`
validationResults
.
value
[
key
]
=
''
validationMessages
.
value
[
key
]
=
''
const
relevantFormulas
=
validFormula
.
value
.
filter
((
f
:
any
)
=>
f
.
formula
.
includes
(
field
)
)
if
(
relevantFormulas
.
length
>
0
)
{
const
formula
=
relevantFormulas
[
0
]
const
fieldValues
:
Record
<
string
,
any
>
=
{}
const
fieldRegex
=
/COL
\d
+/g
const
fields
=
[...
new
Set
(
formula
.
formula
.
match
(
fieldRegex
)
||
[])]
let
hasEmptyField
=
false
for
(
const
fld
of
fields
)
{
const
value
=
getFieldValue
(
fld
)
fieldValues
[
fld
]
=
value
if
(
value
===
''
||
value
===
null
||
value
===
undefined
)
{
hasEmptyField
=
true
}
}
if
(
!
hasEmptyField
)
{
try
{
const
sanitizedFormula
=
sanitizeAndEvaluate
(
formula
.
formula
,
fieldValues
,
[])
const
result
=
evaluateString
(
sanitizedFormula
)
if
(
!
result
)
{
validationResults
.
value
[
key
]
=
'error'
validationMessages
.
value
[
key
]
=
`校验失败:
${
formula
.
des
}
`
}
}
catch
(
error
)
{
validationResults
.
value
[
key
]
=
'error'
validationMessages
.
value
[
key
]
=
`校验错误:
${
error
instanceof
Error
?
error
.
message
:
'未知错误'
}
`
}
}
}
}
const
toggleTooltip
=
(
rowCode
:
string
,
field
:
string
)
=>
{
const
key
=
`
${
rowCode
}
_
${
field
}
`
if
(
hoveredKey
.
value
===
key
)
{
showTooltip
.
value
=
false
hoveredKey
.
value
=
''
}
else
{
showTooltip
.
value
=
true
hoveredKey
.
value
=
key
}
}
const
closeTooltip
=
()
=>
{
showTooltip
.
value
=
false
hoveredKey
.
value
=
''
}
const
getValidationRule
=
(
rowCode
:
string
,
field
:
string
):
string
=>
{
const
relevantFormulas
=
validFormula
.
value
.
filter
((
f
:
any
)
=>
f
.
formula
.
includes
(
field
)
)
if
(
relevantFormulas
.
length
>
0
)
{
return
`
${
relevantFormulas
[
0
].
des
}
\n公式:
${
relevantFormulas
[
0
].
formula
}
`
}
return
'暂无校验规则'
}
const
formatJSON
=
(
obj
:
any
):
string
=>
{
return
JSON
.
stringify
(
obj
,
null
,
2
)
}
const
getFillDatas
=
async
()
=>
{
try
{
...
...
@@ -353,16 +739,6 @@ const setFormValues = async (pcode,code,valData,rind) => {
formValues
.
value
.
push
(
tempForm
)
}
async
function
checkData
()
{
let
tplName
=
queryParam
.
value
.
tplName
;
await
getFillDatas
();
if
(
formValues
.
value
.
length
>
0
)
{
refCheckTbData
.
value
.
setIniData
(
tplName
,
formValues
.
value
)
}
}
async
function
setData
()
{
try
{
loading
.
value
=
true
;
...
...
@@ -518,7 +894,6 @@ async function setData() {
}
}
async
function
setTplItemMap
()
{
try
{
...
...
@@ -615,4 +990,243 @@ async function setTplItemMap() {
border-right: none;
background: transparent;
}
</
style
>
\ No newline at end of file
.input-wrapper {
position: relative;
display: inline-block;
}
.unit {
margin-left: 5px;
font-size: 12px;
}
.help-icon {
margin-left: 5px;
cursor: pointer;
color: #1890ff;
font-weight: bold;
font-size: 14px;
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
background: #e6f7ff;
border-radius: 50%;
}
.error-tip {
position: absolute;
bottom: -20px;
left: 0;
color: #f5222d;
font-size: 12px;
}
.tooltip {
position: absolute;
top: 100%;
left: 0;
margin-top: 5px;
padding: 10px;
background: #333;
color: #fff;
font-size: 12px;
border-radius: 4px;
z-index: 1000;
white-space: pre-wrap;
max-width: 300px;
}
.tooltip::before {
content: '';
position: absolute;
bottom: 100%;
left: 10px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #333 transparent;
}
.validation-results {
max-height: calc(100vh - 200px);
overflow-y: auto;
}
.result-summary {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
}
.summary-item {
display: flex;
align-items: center;
gap: 5px;
}
.summary-value {
font-weight: bold;
font-size: 16px;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #f5222d;
}
.result-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.result-item {
padding: 10px;
border: 1px solid #e8e8e8;
border-radius: 4px;
}
.result-item.success {
border-left: 4px solid #52c41a;
}
.result-item.error {
border-left: 4px solid #f5222d;
}
.result-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #f0f0f0;
}
.result-item-name {
font-weight: bold;
font-size: 14px;
}
.result-item-status {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.result-item-status.success {
background: #f6ffed;
color: #52c41a;
}
.result-item-status.error {
background: #fff2f0;
color: #f5222d;
}
.result-item-content {
font-size: 13px;
}
.result-item-formula,
.result-item-result,
.result-item-values,
.result-item-process {
margin-bottom: 8px;
}
.result-item-values {
margin-bottom: 15px;
}
.values-label,
.formula-label,
.result-label,
.process-label {
font-weight: bold;
margin-right: 5px;
}
.values-value {
white-space: pre-wrap;
background: #f5f5f5;
padding: 8px;
border-radius: 4px;
font-size: 12px;
font-family: monospace;
margin-top: 5px;
}
.process-content {
margin-top: 5px;
background: #fafafa;
padding: 8px;
border-radius: 4px;
}
.process-step {
display: flex;
align-items: center;
gap: 5px;
margin-bottom: 4px;
font-size: 12px;
}
.step-number {
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
background: #e8e8e8;
border-radius: 50%;
font-size: 10px;
font-weight: bold;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 14px;
color: #666;
}
</
style
>
zrch-risk-client-39/src/views/baosong/report/components/Tb10.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height:80vh"
>
<div
class=
"bank-report-table"
style=
"height:80vh"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
button
s
>
<template
#
button
>
<div
class=
"toolbar-info"
>
<span
class=
"info-label"
>
填报任务:
{{
queryParam
?.
taskName
}}
</span>
<span
class=
"info-label"
>
表格:
{{
queryParam
?.
tplCode
}}
{{
queryParam
?.
tplName
}}
</span>
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
ref=
"tableRef"
...
...
@@ -197,21 +251,34 @@
</
template
>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
MultiColumnTable
from
'../tableComponents/MultiColumnTable.vue'
import
AttachTable
from
'../tableComponents/AttachTable.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb10.data'
import
{
ref
,
reactive
,
nextTick
,
onMounted
}
from
'vue'
import
{
VxeUI
,
VxeTablePropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdateBeforeDelete
,
queryRecord
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
findUserRightForTplItem
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
allTplItems
}
from
'../../tpl/BaosongTplItem.api'
import
{
getTblvalidFormula
}
from
'../../tpl/BaosongDataValid.api'
import
{
useRoute
}
from
'vue-router'
interface
FormData
{
id
:
number
|
null
taskid
:
number
...
...
@@ -226,6 +293,7 @@ interface FormData {
const
route
=
useRoute
()
const
tableRef
=
ref
()
const
tplItemMap
=
ref
<
Record
<
string
,
any
>>
({})
const
historyFillCheckRef
=
ref
<
any
>
(
null
)
const
loading
=
ref
(
false
)
const
formData
=
reactive
<
Record
<
string
,
any
>>
({})
const
formValues
=
ref
<
FormData
[]
>
([])
...
...
@@ -240,6 +308,16 @@ const queryParam = ref({
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
onMounted
(
async
()
=>
{
if
(
route
.
query
.
taskId
)
{
queryParam
.
value
.
taskId
=
Number
(
route
.
query
.
taskId
)
...
...
@@ -257,6 +335,20 @@ onMounted(async () => {
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
)
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
await
setData
()
})
...
...
@@ -505,6 +597,136 @@ async function setTplItemMap() {
}
}
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
const
mergeCells
=
ref
<
VxeTablePropTypes
.
MergeCells
>
([
{
row
:
0
,
col
:
1
,
rowspan
:
1
,
colspan
:
2
},
{
row
:
1
,
col
:
1
,
rowspan
:
1
,
colspan
:
2
},
...
...
@@ -550,6 +772,150 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
}
}
/* 加载遮罩层样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 10px;
color: #666;
font-size: 14px;
}
/* 校验结果样式 */
.validation-results {
padding: 20px;
height: 600px;
overflow-y: auto;
}
.result-summary {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.summary-item {
display: flex;
flex-direction: column;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
margin-top: 10px;
}
.result-item {
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.result-item:hover {
background-color: #f5f5f5;
}
.result-item.success {
background-color: #f6ffed;
border-left: 4px solid #52c41a;
}
.result-item.error {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.result-field {
font-weight: bold;
margin-bottom: 5px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.result-value {
font-size: 12px;
color: #999;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 5px;
}
/* 验证公式帮助提示样式 */
.validation-tooltip {
position: absolute;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip-content {
margin: 0;
white-space: pre-wrap;
}
.content-cell {
line-height: 1.8;
padding: 5px;
...
...
zrch-risk-client-39/src/views/baosong/report/components/Tb2.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
buttons
>
<div
class=
"toolbar-info"
>
...
...
@@ -8,9 +16,55 @@
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
ref=
"tableRef"
...
...
@@ -84,7 +138,19 @@
size=
"mini"
class=
"table-input"
:style=
"
{width:item.width}"
:disabled="!item.hasRight"
:data-field="getFieldKey(row.code, item.field)"
>
</vxe-input>
<!-- 帮助图标 -->
<span
v-if=
"item.hasValidFormula"
class=
"validation-help-icon"
@
mouseenter=
"(event) => showValidationHelp(item, event)"
@
mouseleave=
"closeTooltip"
@
click=
"(event) => showValidationHelp(item, event)"
>
?
</span>
</
template
>
<span
v-if=
"item.unit"
class=
"unit"
>
{{ item.unit }}
</span>
</template>
...
...
@@ -93,18 +159,32 @@
</template>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
MultiColumnTable
from
'../tableComponents/MultiColumnTable.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb2.data'
import
{
ref
,
reactive
,
nextTick
,
onMounted
}
from
'vue'
import
{
VxeUI
}
from
'vxe-table'
import
{
batchSaveOrUpdateBeforeDelete
,
queryRecord
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
findUserRightForTplItem
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
allTplItems
}
from
'../../tpl/BaosongTplItem.api'
import
{
getTblvalidFormula
}
from
'../../tpl/BaosongDataValid.api'
import
{
useRoute
}
from
'vue-router'
...
...
@@ -127,6 +207,17 @@ const formData = reactive<Record<string, any>>({})
const
formValues
=
ref
<
FormData
[]
>
([])
const
childMultiTableRefs
=
ref
<
Record
<
string
,
any
>>
({})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
historyFillCheckRef
=
ref
<
any
>
(
null
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
...
...
@@ -152,7 +243,22 @@ onMounted(async () => {
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
)
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
setFormItemRight
()
await
setData
()
})
...
...
@@ -330,6 +436,155 @@ async function setTplItemMap() {
}
}
// 设置表单项权限标识
const
setFormItemRight
=
()
=>
{
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
)
})
}
});
}
});
}
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
(
r
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
(
r
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
function
shouldShowExtraFields
(
opt
,
rowCode
,
itemField
)
{
return
formData
[
getFieldKey
(
rowCode
,
itemField
)]
===
opt
.
label
&&
...
...
@@ -500,4 +755,165 @@ async function setTplItemMap() {
gap: 8px;
}
/* 加载遮罩层 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 14px;
color: #666;
}
/* 校验结果 */
.validation-results {
padding: 20px;
}
.result-summary {
display: flex;
gap: 40px;
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 4px;
}
.summary-item {
display: flex;
align-items: center;
gap: 10px;
}
.summary-label {
font-weight: 500;
color: #666;
}
.summary-value {
font-size: 18px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
max-height: 500px;
overflow-y: auto;
}
.result-item {
padding: 12px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.result-item:hover {
transform: translateX(5px);
}
.result-item.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
}
.result-item.error {
background: #fff2f0;
border: 1px solid #ffccc7;
}
.result-field {
font-weight: bold;
margin-bottom: 4px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.result-value {
font-size: 12px;
color: #888;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 4px;
}
/* 验证公式帮助图标 */
.validation-help-icon {
display: inline-block;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
background: #1890ff;
color: white;
border-radius: 50%;
font-size: 12px;
margin-left: 4px;
cursor: pointer;
vertical-align: middle;
}
/* 验证公式帮助提示 */
.validation-tooltip {
position: fixed;
background: #333;
color: white;
padding: 10px 15px;
border-radius: 4px;
font-size: 12px;
z-index: 9999;
transform: translateX(-50%);
white-space: pre-wrap;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.tooltip-content {
margin: 0;
}
</
style
>
zrch-risk-client-39/src/views/baosong/report/components/Tb4.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
buttons
>
<div
style=
"margin:10px"
>
...
...
@@ -8,9 +16,55 @@
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<MyVxeTable
:title=
"tableFormData.project"
:data=
"tableFormData.data"
...
...
@@ -20,22 +74,37 @@
style=
"margin:0px;padding:0px;height:100%"
ref=
"refMyVxeTable"
/>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
MyVxeTable
from
'../tableComponents/MyVxeTable.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb4.data'
;
import
{
ref
,
reactive
,
onMounted
}
from
'vue'
import
{
VxeUI
}
from
'vxe-table'
import
{
batchSaveOrUpdateBeforeDelete
,
queryRecord
}
from
'../../record/BaosongTaskRecord.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
refMyVxeTable
=
ref
();
const
tplItemMap
=
ref
({});
const
historyFillCheckRef
=
ref
<
any
>
(
null
);
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
...
...
@@ -44,6 +113,16 @@ const queryParam = ref({
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
interface
FormData
{
id
:
number
|
null
taskid
:
number
...
...
@@ -72,6 +151,20 @@ onMounted(async ()=>{
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
await
setData
();
})
...
...
@@ -199,6 +292,118 @@ async function setTplItemMap() {
VxeUI
.
modal
.
message
({
content
:
`加载数据失败:
${
error
.
message
}
`
,
status
:
'error'
})
}
}
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
strKey
=
`
${
tableFormData
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
tableFormData
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
tableFormData
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
strKey
=
`
${
result
.
rowCode
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
</
script
>
<
style
lang=
"less"
scoped
>
...
...
@@ -242,4 +447,150 @@ async function setTplItemMap() {
background: transparent;
line-height: 50px;
}
/* 加载遮罩层 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 14px;
color: #666;
}
/* 校验结果 */
.validation-results {
padding: 20px;
}
.result-summary {
display: flex;
gap: 40px;
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 4px;
}
.summary-item {
display: flex;
align-items: center;
gap: 10px;
}
.summary-label {
font-weight: 500;
color: #666;
}
.summary-value {
font-size: 18px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
max-height: 500px;
overflow-y: auto;
}
.result-item {
padding: 12px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.result-item:hover {
transform: translateX(5px);
}
.result-item.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
}
.result-item.error {
background: #fff2f0;
border: 1px solid #ffccc7;
}
.result-field {
font-weight: bold;
margin-bottom: 4px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.result-value {
font-size: 12px;
color: #888;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 4px;
}
/* 验证公式帮助提示 */
.validation-tooltip {
position: fixed;
background: #333;
color: white;
padding: 10px 15px;
border-radius: 4px;
font-size: 12px;
z-index: 9999;
transform: translateX(-50%);
white-space: pre-wrap;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.tooltip-content {
margin: 0;
}
</
style
>
\ No newline at end of file
zrch-risk-client-39/src/views/baosong/report/components/Tb5.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height: 80vh"
>
<div
class=
"bank-report-table"
style=
"height: 80vh"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
buttons
>
<div
style=
"margin: 10px"
>
...
...
@@ -8,9 +16,47 @@
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
ref=
"tableRef"
...
...
@@ -152,44 +198,69 @@
</template>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
MultiColumnTable
from
'../tableComponents/MultiColumnTable.vue'
;
import
AttachTable
from
'../tableComponents/AttachTable.vue'
;
import
MultiColumnTable
from
'../tableComponents/MultiColumnTable.vue'
import
AttachTable
from
'../tableComponents/AttachTable.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb5.data'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
;
import
{
VxeUI
,
VxeToolbarInstance
,
VxeToolbarPropTypes
}
from
'vxe-table'
;
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
;
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
;
import
{
allTplItems
}
from
'../../tpl/BaosongTplItem.api'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeToolbarInstance
,
VxeToolbarPropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
,
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
historyFillCheckRef
=
ref
<
any
>
(
null
);
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
tplId
:
-
1
,
tplName
:
''
,
tplCode
:
''
,
});
interface
FormData
{
id
:
number
|
null
;
taskid
:
number
;
tplid
:
number
;
itemid
?:
number
;
itempid
?:
number
;
content
:
string
;
tplcode
:
string
;
rind
:
number
;
}
onMounted
(
async
()
=>
{
taskId
:
-
1
,
taskName
:
''
,
tplId
:
-
1
,
tplName
:
''
,
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
interface
FormData
{
id
:
number
|
null
;
taskid
:
number
;
tplid
:
number
;
itemid
?:
number
;
itempid
?:
number
;
content
:
string
;
tplcode
:
string
;
rind
:
number
;
}
onMounted
(
async
()
=>
{
if
(
route
.
query
.
taskId
)
{
queryParam
.
value
.
taskId
=
Number
(
route
.
query
.
taskId
);
}
...
...
@@ -206,233 +277,379 @@
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
await
setTplItemMap
();
await
setData
();
});
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
();
await
setData
();
});
const
loading
=
ref
(
false
);
const
formData
=
reactive
({});
const
loading
=
ref
(
false
);
const
formData
=
reactive
({});
const
formValues
=
ref
<
FormData
[]
>
([]);
const
formValues
=
ref
<
FormData
[]
>
([]);
const
saveBatch
=
async
()
=>
{
try
{
formValues
.
value
=
[];
const
saveBatch
=
async
()
=>
{
try
{
formValues
.
value
=
[];
for
(
const
strKey
in
formData
)
{
const
valData
=
formData
[
strKey
];
if
(
valData
)
{
let
tmpAry
=
strKey
.
split
(
'_'
);
await
setFormValues
(
tmpAry
[
0
],
tmpAry
[
1
],
valData
,
1
);
for
(
const
strKey
in
formData
)
{
const
valData
=
formData
[
strKey
];
if
(
valData
)
{
let
tmpAry
=
strKey
.
split
(
'_'
);
await
setFormValues
(
tmpAry
[
0
],
tmpAry
[
1
],
valData
,
1
);
}
}
}
await
getAttachTableFormData
();
await
getAllMultiTableFormData
();
await
getAttachTableFormData
();
await
getAllMultiTableFormData
();
if
(
formValues
.
value
.
length
>
0
)
{
await
batchSaveOrUpdateBeforeDelete
(
formValues
.
value
);
VxeUI
.
modal
.
message
({
content
:
'保存成功'
,
status
:
'success'
});
//await setData()
}
else
{
VxeUI
.
modal
.
message
({
content
:
'没有需要保存的数据'
,
status
:
'warning'
});
if
(
formValues
.
value
.
length
>
0
)
{
await
batchSaveOrUpdateBeforeDelete
(
formValues
.
value
);
VxeUI
.
modal
.
message
({
content
:
'保存成功'
,
status
:
'success'
});
//await setData()
}
else
{
VxeUI
.
modal
.
message
({
content
:
'没有需要保存的数据'
,
status
:
'warning'
});
}
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`保存失败:
${
error
.
message
}
`
,
status
:
'error'
});
}
finally
{
loading
.
value
=
false
;
}
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`保存失败:
${
error
.
message
}
`
,
status
:
'error'
});
}
finally
{
loading
.
value
=
false
;
}
};
};
const
childMultiTableRefs
=
ref
({});
const
setMultiColumnTableRef
=
(
el
:
any
,
index
:
string
)
=>
{
if
(
el
)
{
childMultiTableRefs
.
value
[
index
]
=
el
;
}
};
const
getAllMultiTableFormData
=
async
()
=>
{
for
(
const
pcode
in
childMultiTableRefs
.
value
)
{
const
child
=
childMultiTableRefs
.
value
[
pcode
];
if
(
child
)
{
const
datas
=
child
.
getFormData
();
let
rind
=
0
;
for
(
const
obj
of
datas
)
{
rind
++
;
Object
.
keys
(
obj
).
forEach
((
code
)
=>
{
setFormValues
(
pcode
,
code
,
obj
[
code
],
rind
);
});
const
childMultiTableRefs
=
ref
({});
const
setMultiColumnTableRef
=
(
el
:
any
,
index
:
string
)
=>
{
if
(
el
)
{
childMultiTableRefs
.
value
[
index
]
=
el
;
}
};
const
getAllMultiTableFormData
=
async
()
=>
{
for
(
const
pcode
in
childMultiTableRefs
.
value
)
{
const
child
=
childMultiTableRefs
.
value
[
pcode
];
if
(
child
)
{
const
datas
=
child
.
getFormData
();
let
rind
=
0
;
for
(
const
obj
of
datas
)
{
rind
++
;
Object
.
keys
(
obj
).
forEach
((
code
)
=>
{
setFormValues
(
pcode
,
code
,
obj
[
code
],
rind
);
});
}
}
}
}
};
};
const
childAttachTableRefs
=
ref
({});
const
setAttachTableRef
=
(
el
:
any
,
index
:
string
)
=>
{
if
(
el
)
{
childAttachTableRefs
.
value
[
index
]
=
el
;
// 保存子组件实例
}
};
const
childAttachTableRefs
=
ref
({});
const
setAttachTableRef
=
(
el
:
any
,
index
:
string
)
=>
{
if
(
el
)
{
childAttachTableRefs
.
value
[
index
]
=
el
;
// 保存子组件实例
}
};
const
getAttachTableFormData
=
async
()
=>
{
for
(
const
pcode
in
childAttachTableRefs
.
value
)
{
const
child
=
childAttachTableRefs
.
value
[
pcode
];
if
(
child
)
{
const
datas
=
child
.
getFormData
();
const
getAttachTableFormData
=
async
()
=>
{
for
(
const
pcode
in
childAttachTableRefs
.
value
)
{
const
child
=
childAttachTableRefs
.
value
[
pcode
];
if
(
child
)
{
const
datas
=
child
.
getFormData
();
for
(
const
code
in
datas
)
{
await
setFormValues
(
pcode
,
code
,
datas
[
code
],
1
);
for
(
const
code
in
datas
)
{
await
setFormValues
(
pcode
,
code
,
datas
[
code
],
1
);
}
}
}
}
};
const
setFormValues
=
async
(
pcode
,
code
,
valData
,
rind
)
=>
{
if
(
!
valData
)
return
;
let
vals
=
Array
.
isArray
(
valData
)
?
valData
.
join
(
','
)
:
valData
;
let
strKey
=
pcode
+
'_'
+
code
;
const
item
=
tplItemMap
.
value
[
strKey
];
const
{
pid
,
itemid
}
=
item
??
{};
let
tempForm
:
FormData
=
{
id
:
null
,
rind
:
rind
,
taskid
:
queryParam
.
value
.
taskId
,
tplid
:
queryParam
.
value
.
tplId
,
tplcode
:
code
,
itemid
:
itemid
,
itempid
:
pid
,
content
:
vals
,
};
formValues
.
value
.
push
(
tempForm
);
};
async
function
setData
()
{
try
{
loading
.
value
=
true
;
const
taskId
=
queryParam
.
value
.
taskId
;
const
tplid
=
queryParam
.
value
.
tplId
;
const
setFormValues
=
async
(
pcode
,
code
,
valData
,
rind
)
=>
{
if
(
!
valData
)
return
;
let
vals
=
Array
.
isArray
(
valData
)
?
valData
.
join
(
','
)
:
valData
;
let
strKey
=
pcode
+
'_'
+
code
;
const
item
=
tplItemMap
.
value
[
strKey
];
const
{
pid
,
itemid
}
=
item
??
{};
let
tempForm
:
FormData
=
{
id
:
null
,
rind
:
rind
,
taskid
:
queryParam
.
value
.
taskId
,
tplid
:
queryParam
.
value
.
tplId
,
tplcode
:
code
,
itemid
:
itemid
,
itempid
:
pid
,
content
:
vals
,
};
formValues
.
value
.
push
(
tempForm
);
};
Object
.
keys
(
formData
).
forEach
((
key
)
=>
delete
formData
[
key
]);
async
function
setData
()
{
try
{
loading
.
value
=
true
;
const
taskId
=
queryParam
.
value
.
taskId
;
const
tplid
=
queryParam
.
value
.
tplId
;
await
setTplItemMap
();
const
recordData
=
await
queryRecord
({
taskid
:
taskId
,
tplid
:
tplid
});
Object
.
keys
(
formData
).
forEach
((
key
)
=>
delete
formData
[
key
]);
const
valueObj
=
{};
for
(
const
data
of
recordData
)
{
const
key
=
`
${
data
.
itempid
}
_
${
data
.
itemid
}
_
${
data
.
rind
}
`
;
valueObj
[
key
]
=
data
.
content
;
}
const
shouldSkip
=
(
key
)
=>
{
const
refsToCheck
=
[
childMultiTableRefs
.
value
,
childAttachTableRefs
.
value
];
await
setTplItemMap
();
const
recordData
=
await
queryRecord
({
taskid
:
taskId
,
tplid
:
tplid
});
for
(
const
refs
of
refsToCheck
)
{
for
(
const
pcode
of
Object
.
keys
(
refs
))
{
if
(
key
.
startsWith
(
pcode
+
'_'
))
{
return
true
;
const
valueObj
=
{};
for
(
const
data
of
recordData
)
{
const
key
=
`
${
data
.
itempid
}
_
${
data
.
itemid
}
_
${
data
.
rind
}
`
;
valueObj
[
key
]
=
data
.
content
;
}
const
shouldSkip
=
(
key
)
=>
{
const
refsToCheck
=
[
childMultiTableRefs
.
value
,
childAttachTableRefs
.
value
];
for
(
const
refs
of
refsToCheck
)
{
for
(
const
pcode
of
Object
.
keys
(
refs
))
{
if
(
key
.
startsWith
(
pcode
+
'_'
))
{
return
true
;
}
}
}
}
return
false
;
};
return
false
;
};
for
(
const
strKey
of
Object
.
keys
(
tplItemMap
.
value
))
{
if
(
shouldSkip
(
strKey
))
{
continue
;
}
for
(
const
strKey
of
Object
.
keys
(
tplItemMap
.
value
))
{
if
(
shouldSkip
(
strKey
))
{
continue
;
}
const
item
=
tplItemMap
.
value
[
strKey
];
if
(
!
item
)
continue
;
const
item
=
tplItemMap
.
value
[
strKey
];
if
(
!
item
)
continue
;
const
{
pid
,
itemid
,
formTp
}
=
item
;
const
dataVal
=
valueObj
[
`
${
pid
}
_
${
itemid
}
_1`
];
const
{
pid
,
itemid
,
formTp
}
=
item
;
const
dataVal
=
valueObj
[
`
${
pid
}
_
${
itemid
}
_1`
];
if
(
!
dataVal
)
continue
;
if
(
!
dataVal
)
continue
;
if
(
formTp
===
'checkbox'
)
{
formData
[
strKey
]
=
dataVal
?.
split
(
','
)
||
[];
}
else
{
formData
[
strKey
]
=
dataVal
||
''
;
if
(
formTp
===
'checkbox'
)
{
formData
[
strKey
]
=
dataVal
?.
split
(
','
)
||
[];
}
else
{
formData
[
strKey
]
=
dataVal
||
''
;
}
}
}
for
(
const
pcode
of
Object
.
keys
(
childAttachTableRefs
.
value
))
{
const
child
=
childAttachTableRefs
.
value
[
pcode
];
const
matchingKeys
=
Object
.
keys
(
tplItemMap
.
value
).
filter
((
key
)
=>
key
.
startsWith
(
pcode
+
'_'
));
for
(
const
pcode
of
Object
.
keys
(
childAttachTableRefs
.
value
))
{
const
child
=
childAttachTableRefs
.
value
[
pcode
];
const
matchingKeys
=
Object
.
keys
(
tplItemMap
.
value
).
filter
((
key
)
=>
key
.
startsWith
(
pcode
+
'_'
));
const
attachTableData
=
{};
for
(
const
key
of
matchingKeys
)
{
const
tmpData
=
tplItemMap
.
value
[
key
];
if
(
!
tmpData
)
continue
;
const
attachTableData
=
{};
for
(
const
key
of
matchingKeys
)
{
const
tmpData
=
tplItemMap
.
value
[
key
];
if
(
!
tmpData
)
continue
;
const
{
pid
,
itemid
,
formTp
,
code
}
=
tmpData
;
const
valueKey
=
`
${
pid
}
_
${
itemid
}
_1`
;
const
{
pid
,
itemid
,
formTp
,
code
}
=
tmpData
;
const
valueKey
=
`
${
pid
}
_
${
itemid
}
_1`
;
if
(
formTp
===
'checkbox'
)
{
attachTableData
[
code
]
=
valueObj
[
valueKey
]?.
split
(
','
)
||
[];
}
else
{
attachTableData
[
code
]
=
valueObj
[
valueKey
]
||
''
;
if
(
formTp
===
'checkbox'
)
{
attachTableData
[
code
]
=
valueObj
[
valueKey
]?.
split
(
','
)
||
[];
}
else
{
attachTableData
[
code
]
=
valueObj
[
valueKey
]
||
''
;
}
}
child
.
setFormData
(
attachTableData
);
}
child
.
setFormData
(
attachTableData
);
}
for
(
const
pcode
of
Object
.
keys
(
childMultiTableRefs
.
value
))
{
const
child
=
childMultiTableRefs
.
value
[
pcode
];
const
matchingKeys
=
Object
.
keys
(
tplItemMap
.
value
).
filter
((
key
)
=>
key
.
startsWith
(
pcode
+
'_'
));
for
(
const
pcode
of
Object
.
keys
(
childMultiTableRefs
.
value
))
{
const
child
=
childMultiTableRefs
.
value
[
pcode
];
const
matchingKeys
=
Object
.
keys
(
tplItemMap
.
value
).
filter
((
key
)
=>
key
.
startsWith
(
pcode
+
'_'
));
const
rowsMap
=
{};
for
(
const
key
of
matchingKeys
)
{
const
tmpData
=
tplItemMap
.
value
[
key
];
if
(
!
tmpData
)
continue
;
const
rowsMap
=
{};
for
(
const
key
of
matchingKeys
)
{
const
tmpData
=
tplItemMap
.
value
[
key
];
if
(
!
tmpData
)
continue
;
const
{
pid
,
itemid
,
code
}
=
tmpData
;
const
valKeys
=
Object
.
keys
(
valueObj
).
filter
((
k
)
=>
k
.
startsWith
(
`
${
pid
}
_
${
itemid
}
_`
));
const
{
pid
,
itemid
,
code
}
=
tmpData
;
const
valKeys
=
Object
.
keys
(
valueObj
).
filter
((
k
)
=>
k
.
startsWith
(
`
${
pid
}
_
${
itemid
}
_`
));
for
(
const
valKey
of
valKeys
)
{
const
parts
=
valKey
.
split
(
'_'
);
const
rind
=
parts
[
2
];
for
(
const
valKey
of
valKeys
)
{
const
parts
=
valKey
.
split
(
'_'
)
;
const
rind
=
parts
[
2
];
if
(
!
rowsMap
[
rind
]
)
{
rowsMap
[
rind
]
=
{}
;
}
if
(
!
rowsMap
[
rind
])
{
rowsMap
[
rind
]
=
{};
rowsMap
[
rind
][
code
]
=
valueObj
[
valKey
];
}
rowsMap
[
rind
][
code
]
=
valueObj
[
valKey
];
}
const
tableDatas
=
Object
.
values
(
rowsMap
);
nextTick
();
child
.
setFormData
(
tableDatas
);
}
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`加载数据失败:
${
error
.
message
}
`
,
status
:
'error'
,
});
}
finally
{
loading
.
value
=
false
;
}
}
const
tableDatas
=
Object
.
values
(
rowsMap
);
nextTick
();
child
.
setFormData
(
tableDatas
);
async
function
setTplItemMap
()
{
try
{
const
taskId
=
queryParam
.
value
.
taskId
;
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
};
});
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`加载数据失败:
${
error
.
message
}
`
,
status
:
'error'
});
}
finally
{
}
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`加载数据失败:
${
error
.
message
}
`
,
status
:
'error'
,
});
}
finally
{
loading
.
value
=
false
;
}
}
async
function
setTplItemMap
()
{
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
taskId
=
queryParam
.
value
.
taskId
;
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
};
});
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
VxeUI
.
modal
.
message
({
content
:
`加载数据失败:
${
error
.
message
}
`
,
status
:
'error'
});
}
finally
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
</
script
>
<
style
lang=
"less"
scoped
>
...
...
@@ -485,95 +702,152 @@
white-space: nowrap;
}
.vxe-input--inner {
height: 24px;
line-height: 24px;
padding: 0 5px;
/* 加载遮罩层 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.vxe-radio--label,
.vxe-checkbox--label {
font-size: 12px;
.loading-spinner {
text-align: center;
}
.vxe-radio--icon,
.vxe-checkbox--icon {
font-size: 12px;
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
.vxe-radio-group,
.vxe-checkbox-group {
display: inline-block;
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.bank-report-table {
font-family: 'SimSun', '宋体', serif;
font-size: 12px;
color: #000;
margin: 5px auto;
width: 90%;
.loading-text {
font-size: 14px;
color: #666;
}
.custom-table {
width: 100%;
border: 1px solid #000
;
/* 校验结果 */
.validation-results {
padding: 20px
;
}
.custom-table .vxe-header--column,
.custom-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
.result-summary {
display: flex;
gap: 40px;
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 4px;
}
.content-cell {
line-height: 1.8;
.summary-item {
display: flex;
align-items: center;
gap: 10px;
}
.
table-input
{
width: 80px
;
margin: 0 2px
;
.
summary-label
{
font-weight: 500
;
color: #666
;
}
/* 附件样式 */
.attachments {
margin-top: 20px
;
width: 100%
;
.summary-value {
font-size: 18px;
font-weight: bold
;
color: #333
;
}
.attachments h4 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
.summary-value.success {
color: #52c41a;
}
.attachment-table {
width: 100%;
border: 1px solid #000;
.summary-value.error {
color: #ff4d4f;
}
.attachment-table .vxe-header--column,
.attachment-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
.results-list {
max-height: 500px;
overflow-y: auto;
}
.vxe-input {
border-bottom: 1px solid #333;
text-align: center;
margin: 5px 3px;
padding: 0;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
line-height: 50px;
}
blockquote {
padding: 0px 5px;
color: black;
.result-item {
padding: 12px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.result-item:hover {
transform: translateX(5px);
}
.result-item.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
}
.result-item.error {
background: #fff2f0;
border: 1px solid #ffccc7;
}
.result-field {
font-weight: bold;
margin-bottom: 4px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.result-value {
font-size: 12px;
color: #888;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 4px;
}
/* 验证公式帮助提示 */
.validation-tooltip {
position: fixed;
background: #333;
color: white;
padding: 10px 15px;
border-radius: 4px;
font-size: 12px;
z-index: 9999;
transform: translateX(-50%);
white-space: pre-wrap;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.tooltip-content {
margin: 0;
}
</
style
>
zrch-risk-client-39/src/views/baosong/report/components/Tb6.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height:80vh"
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
button
s
>
<template
#
button
>
<div
style=
"margin:10px"
>
<span
style=
"font-weight: bold"
>
填报任务:
{{
queryParam
?.
taskName
}}
</span>
<span
style=
"font-weight: bold;margin-left:20px"
>
表格:
{{
queryParam
?.
tplCode
}}
{{
queryParam
?.
tplName
}}
</span>
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
ref=
"tableRef"
...
...
@@ -157,6 +210,20 @@
</template>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
<!-- 原有检查组件 -->
<CheckTbData
ref=
"refCheckTbData"
></CheckTbData>
</div>
</template>
...
...
@@ -166,16 +233,18 @@ import MultiColumnTable from '../tableComponents/MultiColumnTable.vue'
import
AttachTable
from
'../tableComponents/AttachTable.vue'
import
MyVxeTable1
from
'../tableComponents/MyVxeTable.vue'
import
CheckTbData
from
'./CheckTbData.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb6.data'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeToolbarInstance
,
VxeToolbarPropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
findUserRightForTplItem
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
allTplItems
}
from
'../../tpl/BaosongTplItem.api'
import
{
getTblvalidFormula
}
from
'../../tpl/BaosongDataValid.api'
import
{
useRoute
}
from
'vue-router'
;
const
refCheckTbData
=
ref
();
const
historyFillCheckRef
=
ref
<
any
>
(
null
);
const
route
=
useRoute
();
const
tableRef
=
ref
();
...
...
@@ -188,6 +257,16 @@ const queryParam = ref({
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
interface
FormData
{
id
:
number
|
null
taskid
:
number
...
...
@@ -216,6 +295,20 @@ onMounted(async ()=>{
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
await
setData
();
})
...
...
@@ -363,7 +456,138 @@ const setFormValues = async (pcode,code,valData,rind) => {
}
async
function
checkData
()
{
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
// 原有检查方法
async
function
checkDataOrig
()
{
let
tplName
=
queryParam
.
value
.
tplName
;
await
getFillDatas
();
if
(
formValues
.
value
.
length
>
0
)
{
...
...
@@ -524,6 +748,150 @@ async function setTplItemMap() {
width: 60%;
}
/* 加载遮罩层样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 10px;
color: #666;
font-size: 14px;
}
/* 校验结果样式 */
.validation-results {
padding: 20px;
height: 600px;
overflow-y: auto;
}
.result-summary {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.summary-item {
display: flex;
flex-direction: column;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
margin-top: 10px;
}
.result-item {
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.result-item:hover {
background-color: #f5f5f5;
}
.result-item.success {
background-color: #f6ffed;
border-left: 4px solid #52c41a;
}
.result-item.error {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.result-field {
font-weight: bold;
margin-bottom: 5px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.result-value {
font-size: 12px;
color: #999;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 5px;
}
/* 验证公式帮助提示样式 */
.validation-tooltip {
position: absolute;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip-content {
margin: 0;
white-space: pre-wrap;
}
.custom-table {
width: 100%;
border: 1px solid #000;
...
...
zrch-risk-client-39/src/views/baosong/report/components/Tb7.vue
浏览文件 @
9cb3fade
...
...
@@ -2,16 +2,62 @@
<div
class=
"bank-report-table"
style=
"height: 80vh;"
>
<vxe-toolbar>
<template
#
button
s
>
<template
#
button
>
<div
style=
"margin:10px"
>
<span
style=
"font-weight: bold"
>
填报任务:
{{
queryParam
?.
taskName
}}
</span>
<span
style=
"font-weight: bold;margin-left:20px"
>
表格:
{{
queryParam
?.
tplCode
}}
{{
queryParam
?.
tplName
}}
</span>
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
ref=
"tableRef"
...
...
@@ -166,23 +212,38 @@
</
template
>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
AttachTable
from
'../tableComponents/AttachTable.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb7.data'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeToolbarInstance
,
VxeTablePropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
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
historyFillCheckRef
=
ref
<
any
>
(
null
);
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
...
...
@@ -191,6 +252,16 @@ const queryParam = ref({
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
interface
FormData
{
id
:
number
|
null
taskid
:
number
...
...
@@ -219,6 +290,20 @@ onMounted(async ()=>{
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
await
setData
();
})
...
...
@@ -371,6 +456,136 @@ async function setTplItemMap() {
}
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
const
mergeCells
=
ref
<
VxeTablePropTypes
.
MergeCells
>
([
{
row
:
7
,
col
:
2
,
rowspan
:
1
,
colspan
:
2
},
])
...
...
@@ -389,10 +604,155 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
/* 表格样式 */
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 更整齐的边框 */
}
border: 1px solid #000;
border-collapse: collapse; /* 统一边框处理 */
}
/* 加载遮罩层样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 10px;
color: #666;
font-size: 14px;
}
/* 校验结果样式 */
.validation-results {
padding: 20px;
height: 600px;
overflow-y: auto;
}
.result-summary {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.summary-item {
display: flex;
flex-direction: column;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
margin-top: 10px;
}
.result-item {
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.result-item:hover {
background-color: #f5f5f5;
}
.result-item.success {
background-color: #f6ffed;
border-left: 4px solid #52c41a;
}
.result-item.error {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.result-field {
font-weight: bold;
margin-bottom: 5px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.result-value {
font-size: 12px;
color: #999;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 5px;
}
/* 验证公式帮助提示样式 */
.validation-tooltip {
position: absolute;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip-content {
margin: 0;
white-space: pre-wrap;
}
/* 表格单元格样式 */
.custom-table .vxe-header--column,
.custom-table .vxe-body--column,
.attachment-table .vxe-header--column,
...
...
zrch-risk-client-39/src/views/baosong/report/components/Tb8.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
>
<div
class=
"bank-report-table"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
button
s
>
<template
#
button
>
<div
style=
"margin:10px"
>
<span
style=
"font-weight: bold"
>
填报任务:
{{
queryParam
?.
taskName
}}
</span>
<span
style=
"font-weight: bold;margin-left:20px"
>
表格:
{{
queryParam
?.
tplCode
}}
{{
queryParam
?.
tplName
}}
</span>
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
height=
"auto"
...
...
@@ -197,22 +250,37 @@
</
template
>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb8.data'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeToolbarInstance
,
VxeToolbarPropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
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
historyFillCheckRef
=
ref
<
any
>
(
null
);
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
...
...
@@ -221,6 +289,16 @@ const queryParam = ref({
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
interface
FormData
{
id
:
number
|
null
...
...
@@ -250,6 +328,20 @@ onMounted(async ()=>{
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
await
setData
();
})
...
...
@@ -383,6 +475,136 @@ async function setTplItemMap() {
}
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
</
script
>
<
style
lang=
"less"
scoped
>
/* 基础字体和排版 */
...
...
@@ -398,10 +620,155 @@ async function setTplItemMap() {
/* 表格样式 */
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 更整齐的边框 */
}
border: 1px solid #000;
border-collapse: collapse; /* 统一边框处理 */
}
/* 加载遮罩层样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 10px;
color: #666;
font-size: 14px;
}
/* 校验结果样式 */
.validation-results {
padding: 20px;
height: 600px;
overflow-y: auto;
}
.result-summary {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.summary-item {
display: flex;
flex-direction: column;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
margin-top: 10px;
}
.result-item {
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.result-item:hover {
background-color: #f5f5f5;
}
.result-item.success {
background-color: #f6ffed;
border-left: 4px solid #52c41a;
}
.result-item.error {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.result-field {
font-weight: bold;
margin-bottom: 5px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.result-value {
font-size: 12px;
color: #999;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 5px;
}
/* 验证公式帮助提示样式 */
.validation-tooltip {
position: absolute;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip-content {
margin: 0;
white-space: pre-wrap;
}
/* 表格单元格样式 */
.custom-table .vxe-header--column,
.custom-table .vxe-body--column,
.attachment-table .vxe-header--column,
...
...
zrch-risk-client-39/src/views/baosong/report/components/Tb9.vue
浏览文件 @
9cb3fade
<
template
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
>
<div
class=
"bank-report-table"
style=
"height: 80vh;"
@
click=
"closeTooltip"
>
<!-- 加载遮罩层 -->
<div
v-if=
"loading"
class=
"loading-overlay"
>
<div
class=
"loading-spinner"
>
<div
class=
"spinner"
></div>
<div
class=
"loading-text"
>
加载中...
</div>
</div>
</div>
<vxe-toolbar>
<template
#
button
s
>
<template
#
button
>
<div
style=
"margin:10px"
>
<span
style=
"font-weight: bold"
>
填报任务:
{{
queryParam
?.
taskName
}}
</span>
<span
style=
"font-weight: bold;margin-left:20px"
>
表格:
{{
queryParam
?.
tplCode
}}
{{
queryParam
?.
tplName
}}
</span>
</div>
</
template
>
<
template
#
tools
>
<vxe-button
status=
"primary"
icon=
"vxe-icon-edit"
@
click=
"handleOpenHistoryDrawer()"
:disabled=
"loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"checkData()"
>
校验
</vxe-button>
<vxe-button
status=
"primary"
icon=
"vxe-icon-save"
@
click=
"saveBatch()"
>
保存
</vxe-button>
</
template
>
</vxe-toolbar>
<!-- 校验结果抽屉 -->
<vxe-drawer
v-model=
"drawerVisible"
placement=
"right"
@
show=
"handleValidationDrawerShow"
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=
"results-list"
>
<div
v-for=
"(result, index) in validationResultsList"
:key=
"index"
class=
"result-item"
:class=
"
{ success: result.isValid, error: !result.isValid }"
@click="handleValidationResultClick(result)"
>
<div
class=
"result-field"
>
{{
result
.
field
}}
</div>
<div
class=
"result-desc"
>
{{
result
.
description
}}
</div>
<div
class=
"result-value"
>
值:
{{
result
.
fieldValue
||
'空'
}}
</div>
<div
v-if=
"!result.isValid"
class=
"result-error"
>
失败原因:
{{
result
.
description
}}
</div>
</div>
</div>
</div>
</
template
>
</vxe-drawer>
<vxe-table
border
ref=
"tableRef"
...
...
@@ -194,25 +247,39 @@
</
template
>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck
ref=
"historyFillCheckRef"
v-model=
"historyDrawerVisible"
@
closeDrawer=
"closeHistoryDrawer"
/>
<!-- 验证公式帮助提示 -->
<div
v-if=
"validationTooltipVisible"
class=
"validation-tooltip"
:style=
"{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
>
<pre
class=
"tooltip-content"
>
{{ validationTooltipContent }}
</pre>
</div>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
MultiColumnTable
from
'../tableComponents/MultiColumnTable.vue'
import
AttachTable
from
'../tableComponents/AttachTable.vue'
import
HistoryFillCheck
from
'./check/historyFillCheck.vue'
import
{
tableFormData
}
from
'../../data/tb9.data'
;
import
{
ref
,
reactive
,
nextTick
,
onMounted
,
toRaw
}
from
'vue'
import
{
VxeUI
,
VxeToolbarInstance
,
VxeToolbarPropTypes
}
from
'vxe-table'
import
{
batchSaveOrUpdate
,
queryRecord
,
batchSaveOrUpdateBeforeDelete
}
from
'../../record/BaosongTaskRecord.api'
import
{
queryAllTplItemForUser
}
from
'../../alloc/BaosongTaskAlloc.api'
import
{
queryAllTplItemForUser
,
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
historyFillCheckRef
=
ref
<
any
>
(
null
);
const
queryParam
=
ref
({
taskId
:
-
1
,
taskName
:
''
,
...
...
@@ -221,6 +288,16 @@ const queryParam = ref({
tplCode
:
''
})
// 权限相关状态
const
userAllocItems
=
ref
<
string
[]
>
([])
const
validFormula
=
ref
<
any
[]
>
([])
const
validationResultsList
=
ref
<
any
[]
>
([])
const
drawerVisible
=
ref
(
false
)
const
historyDrawerVisible
=
ref
(
false
)
const
validationTooltipVisible
=
ref
(
false
)
const
validationTooltipPosition
=
ref
({
left
:
0
,
top
:
0
})
const
validationTooltipContent
=
ref
(
''
)
interface
FormData
{
id
:
number
|
null
taskid
:
number
...
...
@@ -249,6 +326,20 @@ onMounted(async ()=>{
queryParam
.
value
.
tplCode
=
String
(
route
.
query
.
tplCode
);
}
// 获取权限和验证公式
try
{
userAllocItems
.
value
=
await
findUserRightForTplItem
({
tplid
:
queryParam
.
value
.
tplId
,
taskid
:
queryParam
.
value
.
taskId
})
validFormula
.
value
=
await
getTblvalidFormula
({
tplid
:
queryParam
.
value
.
tplId
,
})
}
catch
(
error
)
{
console
.
error
(
'获取权限或验证公式失败:'
,
error
)
}
await
setTplItemMap
()
await
setData
();
})
...
...
@@ -507,6 +598,136 @@ async function setTplItemMap() {
}
// 校验数据
const
checkData
=
()
=>
{
drawerVisible
.
value
=
true
}
// 校验结果抽屉显示时触发
const
handleValidationDrawerShow
=
()
=>
{
performValidation
()
}
// 执行校验
const
performValidation
=
()
=>
{
validationResultsList
.
value
=
[]
const
process
:
string
[]
=
[]
process
.
push
(
'开始校验'
)
process
.
push
(
`找到
${
validFormula
.
value
.
length
}
个验证公式`
)
validFormula
.
value
.
forEach
((
formulaItem
:
any
)
=>
{
process
.
push
(
`开始校验公式:
${
formulaItem
.
des
}
`
)
const
result
=
evaluateFormula
(
formulaItem
.
formula
,
formulaItem
.
des
,
process
)
if
(
result
)
{
validationResultsList
.
value
.
push
(
result
)
}
})
process
.
push
(
'校验完成'
)
}
// 解析和执行验证公式
const
evaluateFormula
=
(
formula
:
string
,
description
:
string
,
process
:
string
[]):
any
=>
{
try
{
const
fieldMatch
=
formula
.
match
(
/
\[(\w
+
)\]
/
)
if
(
!
fieldMatch
)
{
process
.
push
(
`跳过: 无法解析公式字段
${
formula
}
`
)
return
null
}
const
fieldName
=
fieldMatch
[
1
]
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
content
?.
some
((
c
:
any
)
=>
c
.
field
===
fieldName
))
if
(
!
row
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
fieldName
)
if
(
!
fieldItem
)
{
process
.
push
(
`跳过: 未找到字段
${
fieldName
}
`
)
return
null
}
const
strKey
=
`
${
row
.
code
}
_
${
fieldName
}
`
const
fieldValue
=
formData
[
strKey
]
// 检查是否有空字段规则
const
emptyFieldRule
=
validFormula
.
value
.
find
((
f
:
any
)
=>
f
.
formula
.
includes
(
fieldName
)
&&
f
.
des
.
includes
(
'空字段'
)
)
if
(
emptyFieldRule
&&
(
!
fieldValue
||
fieldValue
===
''
))
{
process
.
push
(
`跳过: 字段
${
fieldName
}
为空,跳过空字段规则`
)
return
null
}
const
expression
=
formula
.
replace
(
/
\[(\w
+
)\]
/g
,
(
_
,
field
)
=>
{
const
value
=
formData
[
`
${
row
.
code
}
_
${
field
}
`
]
return
value
!==
undefined
?
`Number(
${
value
}
)`
:
'0'
})
process
.
push
(
`执行公式:
${
expression
}
`
)
const
isValid
=
eval
(
expression
)
return
{
field
:
fieldName
,
description
:
description
,
formula
:
formula
,
isValid
:
isValid
,
fieldValue
:
fieldValue
,
rowCode
:
row
.
code
}
}
catch
(
error
)
{
process
.
push
(
`公式执行错误:
${
error
instanceof
Error
?
error
.
message
:
String
(
error
)}
`
)
return
null
}
}
// 处理校验结果点击,定位到表格
const
handleValidationResultClick
=
(
result
:
any
)
=>
{
const
row
=
tableFormData
.
find
((
r
:
any
)
=>
r
.
code
===
result
.
rowCode
)
if
(
row
)
{
const
fieldItem
=
row
.
content
.
find
((
c
:
any
)
=>
c
.
field
===
result
.
field
)
if
(
fieldItem
)
{
const
strKey
=
`
${
row
.
code
}
_
${
result
.
field
}
`
const
inputElement
=
document
.
querySelector
(
`[data-field="
${
strKey
}
"]`
)
as
HTMLElement
if
(
inputElement
)
{
inputElement
.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'center'
})
inputElement
.
focus
()
inputElement
.
style
.
border
=
'2px solid #ff4d4f'
setTimeout
(()
=>
{
inputElement
.
style
.
border
=
''
},
2000
)
}
}
}
}
// 显示验证公式帮助
const
showValidationHelp
=
(
formulaItem
:
any
,
event
:
MouseEvent
)
=>
{
const
rect
=
(
event
.
target
as
HTMLElement
).
getBoundingClientRect
()
validationTooltipPosition
.
value
=
{
left
:
rect
.
left
+
rect
.
width
/
2
,
top
:
rect
.
top
-
10
}
validationTooltipContent
.
value
=
`验证公式:
${
formulaItem
.
formula
}
\n说明:
${
formulaItem
.
des
}
`
validationTooltipVisible
.
value
=
true
}
// 关闭验证公式帮助
const
closeTooltip
=
()
=>
{
validationTooltipVisible
.
value
=
false
}
// 打开历史填报抽屉
const
handleOpenHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
true
}
// 关闭历史填报抽屉
const
closeHistoryDrawer
=
()
=>
{
historyDrawerVisible
.
value
=
false
}
</
script
>
<
style
lang=
"less"
scoped
>
/* 基础字体和排版 */
...
...
@@ -521,10 +742,155 @@ async function setTplItemMap() {
/* 表格样式 */
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 更整齐的边框 */
}
border: 1px solid #000;
border-collapse: collapse; /* 统一边框处理 */
}
/* 加载遮罩层样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 10px;
color: #666;
font-size: 14px;
}
/* 校验结果样式 */
.validation-results {
padding: 20px;
height: 600px;
overflow-y: auto;
}
.result-summary {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.summary-item {
display: flex;
flex-direction: column;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
color: #333;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.results-list {
margin-top: 10px;
}
.result-item {
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.result-item:hover {
background-color: #f5f5f5;
}
.result-item.success {
background-color: #f6ffed;
border-left: 4px solid #52c41a;
}
.result-item.error {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.result-field {
font-weight: bold;
margin-bottom: 5px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.result-value {
font-size: 12px;
color: #999;
}
.result-error {
font-size: 12px;
color: #ff4d4f;
margin-top: 5px;
}
/* 验证公式帮助提示样式 */
.validation-tooltip {
position: absolute;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip-content {
margin: 0;
white-space: pre-wrap;
}
/* 表格单元格样式 */
.custom-table .vxe-header--column,
.custom-table .vxe-body--column,
.attachment-table .vxe-header--column,
...
...
zrch-risk-client-39/src/views/baosong/report/components/check/HistoryFillCheck.vue
浏览文件 @
9cb3fade
...
...
@@ -51,11 +51,11 @@ const gridOptions = reactive<VxeGridProps<RowVO>>({
{
field
:
'itemName'
,
title
:
'字段名称'
,
width
:
"15%"
},
{
field
:
'values'
,
title
:
'最近5年填报数据'
,
width
:
"75%"
,
children
:
[
{
field
:
'value
1
'
,
title
:
'第一次'
,
width
:
"15%"
},
{
field
:
'value
2
'
,
title
:
'第二次'
,
width
:
"15%"
},
{
field
:
'value
3
'
,
title
:
'第三次'
,
width
:
"15%"
},
{
field
:
'value
4
'
,
title
:
'第四次'
,
width
:
"15%"
},
{
field
:
'value
5
'
,
title
:
'第五次'
,
width
:
"15%"
},
{
field
:
'value
0
'
,
title
:
'第一次'
,
width
:
"15%"
},
{
field
:
'value
1
'
,
title
:
'第二次'
,
width
:
"15%"
},
{
field
:
'value
2
'
,
title
:
'第三次'
,
width
:
"15%"
},
{
field
:
'value
3
'
,
title
:
'第四次'
,
width
:
"15%"
},
{
field
:
'value
4
'
,
title
:
'第五次'
,
width
:
"15%"
},
],
},
{
field
:
'opt'
,
title
:
'操作'
,
width
:
"10%"
},
...
...
@@ -67,19 +67,24 @@ async function onDrawerShow(tplid: number) {
const
retData
=
await
findFillHistoryForCheck
({
tplid
:
tplid
});
// 调试:查看数据结构
console
.
log
(
'原始数据:'
,
retData
);
// 处理 values 数据
const
processedData
=
retData
.
map
(
item
=>
{
const
processedData
=
retData
.
map
(
(
item
,
itemIndex
)
=>
{
const
processedItem
=
{...
item
};
// 将 values 数组中的 value 属性提取出来
if
(
Array
.
isArray
(
processedItem
.
values
))
{
processedItem
.
values
.
forEach
((
valueItem
,
index
)
=>
{
// 将每个 valueItem 的 value 属性设置到单独的字段中
processedItem
[
`value
${
index
}
`
]
=
valueItem
.
value
;
// 注意:index从0开始,但字段名应该从1开始
processedItem
[
`value
${
index
+
1
}
`
]
=
valueItem
.
value
;
});
}
return
processedItem
;
});
console
.
log
(
'处理后的数据:'
,
processedData
);
gridOptions
.
data
=
processedData
;
return
false
;
}
...
...
zrch-risk-server-39/jeecg-module-system/jeecg-module-stm/src/main/java/org/jeecg/modules/stm/baosong/controller/BaosongDataValidController.java
浏览文件 @
9cb3fade
...
...
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import
org.jeecg.common.api.vo.Result
;
import
org.jeecg.common.system.query.QueryGenerator
;
import
org.jeecg.modules.stm.baosong.entity.BaosongDataValid
;
import
org.jeecg.modules.stm.baosong.entity.BaosongTaskAlloc
;
import
org.jeecg.modules.stm.baosong.entity.BaosongTaskRecord
;
import
org.jeecg.modules.stm.baosong.entity.BaosongTplItem
;
import
org.jeecg.modules.stm.baosong.service.IBaosongDataValidService
;
...
...
@@ -224,4 +225,14 @@ public class BaosongDataValidController extends JeecgController<BaosongDataValid
return
Result
.
OK
(
retMap
);
}
@GetMapping
(
value
=
"/getTblvalidFormula"
)
public
Result
<
List
<
BaosongDataValid
>>
getTblvalidFormula
(
BaosongDataValid
dataValid
,
HttpServletRequest
req
)
{
List
<
BaosongDataValid
>
listFormula
=
baosongDataValidService
.
lambdaQuery
()
.
eq
(
BaosongDataValid:
:
getTplid
,
dataValid
.
getTplid
())
.
list
();
return
Result
.
OK
(
listFormula
);
}
}
zrch-risk-server-39/jeecg-module-system/jeecg-module-stm/src/main/java/org/jeecg/modules/stm/baosong/controller/BaosongTaskAllocController.java
浏览文件 @
9cb3fade
...
...
@@ -268,4 +268,31 @@ public class BaosongTaskAllocController extends JeecgController<BaosongTaskAlloc
list
.
addAll
(
0
,
rootItemList
);
}
}
@GetMapping
(
value
=
"/findUserRightForTplItem"
)
public
Result
<
Set
<
String
>>
findUserRightForTplItem
(
BaosongTaskAlloc
taskAlloc
,
HttpServletRequest
req
)
{
Set
<
String
>
retCodeSet
=
new
HashSet
<>();
try
{
String
userId
=
UserUtil
.
getUserId
();
List
<
BaosongTaskAlloc
>
allocTplList
=
baosongTaskAllocService
.
lambdaQuery
()
.
eq
(
BaosongTaskAlloc:
:
getFillUser
,
userId
)
.
eq
(
BaosongTaskAlloc:
:
getTaskid
,
taskAlloc
.
getTaskid
())
.
eq
(
BaosongTaskAlloc:
:
getTplid
,
taskAlloc
.
getTplid
())
.
list
();
Set
<
Integer
>
tplIdSet
=
new
HashSet
<>();
for
(
BaosongTaskAlloc
alloc
:
allocTplList
)
{
tplIdSet
.
add
(
alloc
.
getItemid
());
}
List
<
BaosongTplItem
>
itemList
=
baosongTplItemService
.
lambdaQuery
()
.
in
(
BaosongTplItem:
:
getId
,
tplIdSet
)
.
list
();
for
(
BaosongTplItem
item
:
itemList
)
{
retCodeSet
.
add
(
item
.
getPcode
()+
"_"
+
item
.
getCode
());
}
return
Result
.
OK
(
retCodeSet
);
}
catch
(
Exception
e
)
{
log
.
error
(
"查询用户模板权限失败"
,
e
);
return
Result
.
error
(
"查询失败,请稍后重试"
);
}
}
}
zrch-risk-server-39/jeecg-module-system/jeecg-module-stm/src/main/java/org/jeecg/modules/stm/baosong/controller/BaosongTaskRecordController.java
浏览文件 @
9cb3fade
package
org
.
jeecg
.
modules
.
stm
.
baosong
.
controller
;
import
java.util.
ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.
*
;
import
com.aliyun.oss.ServiceException
;
import
com.baomidou.mybatisplus.core.toolkit.CollectionUtils
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletResponse
;
...
...
@@ -40,6 +40,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
public
class
BaosongTaskRecordController
extends
JeecgController
<
BaosongTaskRecord
,
IBaosongTaskRecordService
>
{
@Autowired
private
IBaosongTaskRecordService
baosongTaskRecordService
;
/**
* 分页列表查询
...
...
@@ -261,4 +262,72 @@ public class BaosongTaskRecordController extends JeecgController<BaosongTaskReco
}
@GetMapping
(
value
=
"/findFillHistoryForCheck"
)
public
Result
<
List
<
Map
<
String
,
Object
>>>
findFillHistoryForCheckTraditional
(
BaosongQuery
baosongQuery
,
HttpServletRequest
req
)
{
// 参数校验
if
(
baosongQuery
==
null
)
{
return
Result
.
error
(
"查询参数不能为空"
);
}
List
<
Map
<
String
,
Object
>>
retList
=
new
ArrayList
<>();
try
{
List
<
BaosongQuery
>
list
=
baosongTaskRecordService
.
findTaskRecords
(
baosongQuery
);
if
(
CollectionUtils
.
isEmpty
(
list
))
{
return
Result
.
OK
(
Collections
.
emptyList
());
}
// 使用 LinkedHashMap 保持插入顺序
Map
<
Integer
,
List
<
Map
<
String
,
Object
>>>
tmpMap
=
new
LinkedHashMap
<>();
Map
<
Integer
,
String
>
itemNameMap
=
new
HashMap
<>();
Map
<
Integer
,
String
>
taskNameMap
=
new
HashMap
<>();
for
(
BaosongQuery
taskRecord
:
list
)
{
Integer
itemId
=
taskRecord
.
getItemid
();
// 获取或创建列表
List
<
Map
<
String
,
Object
>>
cList
=
tmpMap
.
computeIfAbsent
(
itemId
,
k
->
new
ArrayList
<>());
// 构建值Map
Map
<
String
,
Object
>
tmpVal
=
new
HashMap
<>(
3
);
tmpVal
.
put
(
"value"
,
taskRecord
.
getContent
());
tmpVal
.
put
(
"taskid"
,
taskRecord
.
getTaskid
());
tmpVal
.
put
(
"taskName"
,
taskRecord
.
getTaskName
());
cList
.
add
(
tmpVal
);
// 存储 itemName(如果已存在则跳过)
itemNameMap
.
putIfAbsent
(
itemId
,
taskRecord
.
getTplItemName
());
if
(
taskNameMap
.
get
(
taskRecord
.
getTaskid
())!=
null
){
taskNameMap
.
put
(
taskRecord
.
getTaskid
(),
taskRecord
.
getTaskName
());
}
}
// 构建结果
for
(
Map
.
Entry
<
Integer
,
List
<
Map
<
String
,
Object
>>>
entry
:
tmpMap
.
entrySet
())
{
Integer
itemId
=
entry
.
getKey
();
Map
<
String
,
Object
>
resultItem
=
new
HashMap
<>(
3
);
resultItem
.
put
(
"itemId"
,
itemId
);
resultItem
.
put
(
"itemName"
,
itemNameMap
.
get
(
itemId
));
resultItem
.
put
(
"values"
,
entry
.
getValue
());
retList
.
add
(
resultItem
);
}
// 可选:按 itemId 排序
retList
.
sort
(
Comparator
.
comparing
(
m
->
(
Integer
)
m
.
get
(
"itemId"
)));
}
catch
(
ServiceException
e
)
{
log
.
error
(
"查询填报历史记录失败,查询参数:{}"
,
baosongQuery
,
e
);
return
Result
.
error
(
"查询失败:"
+
e
.
getMessage
());
}
catch
(
Exception
e
)
{
log
.
error
(
"系统异常,查询填报历史记录失败"
,
e
);
return
Result
.
error
(
"系统异常,请稍后重试"
);
}
return
Result
.
OK
(
retList
);
}
}
zrch-risk-server-39/jeecg-module-system/jeecg-module-stm/src/main/java/org/jeecg/modules/stm/baosong/controller/BaosongTplController.java
浏览文件 @
9cb3fade
...
...
@@ -290,13 +290,14 @@ public class BaosongTplController extends JeecgController<BaosongTpl, IBaosongTp
}
@GetMapping
(
value
=
"/findUserRightForTpl"
)
public
Result
<
List
<
BaosongTpl
>>
findUserRightForTpl
(
HttpServletRequest
req
)
{
public
Result
<
List
<
BaosongTpl
>>
findUserRightForTpl
(
Integer
taskId
,
HttpServletRequest
req
)
{
List
<
BaosongTpl
>
retTplList
=
new
ArrayList
<>();
try
{
String
userId
=
UserUtil
.
getUserId
();
List
<
BaosongTaskAlloc
>
allocTplList
=
baosongTaskAllocService
.
lambdaQuery
()
.
eq
(
BaosongTaskAlloc:
:
getFillUser
,
userId
)
.
eq
(
BaosongTaskAlloc:
:
getTaskid
,
taskId
)
.
list
();
Set
<
Integer
>
tplIdSet
=
new
HashSet
<>();
for
(
BaosongTaskAlloc
alloc
:
allocTplList
)
{
...
...
@@ -319,4 +320,6 @@ public class BaosongTplController extends JeecgController<BaosongTpl, IBaosongTp
return
Result
.
error
(
"查询失败,请稍后重试"
);
}
}
}
zrch-risk-server-39/jeecg-module-system/jeecg-module-stm/src/main/java/org/jeecg/modules/stm/baosong/entity/BaosongDataValid.java
浏览文件 @
9cb3fade
...
...
@@ -34,30 +34,24 @@ public class BaosongDataValid implements Serializable {
/**id*/
@TableId
(
type
=
IdType
.
AUTO
)
private
java
.
lang
.
Integer
id
;
/**模板ID*/
@Excel
(
name
=
"模板"
,
width
=
15
)
private
Integer
tplid
;
/**公式编号*/
@Excel
(
name
=
"公式编号"
,
width
=
15
)
private
java
.
lang
.
String
code
;
/**名称*/
@Excel
(
name
=
"名称"
,
width
=
15
)
private
java
.
lang
.
String
name
;
/**验证公式*/
@Excel
(
name
=
"验证公式"
,
width
=
15
)
private
java
.
lang
.
String
formula
;
/**类型*/
@Excel
(
name
=
"类型"
,
width
=
15
)
private
java
.
lang
.
Integer
ctp
;
/**分类
* 1=表内校验
...
...
@@ -66,32 +60,24 @@ public class BaosongDataValid implements Serializable {
* 4=不同年份校验
* */
@Excel
(
name
=
"分类"
,
width
=
15
)
private
java
.
lang
.
Integer
tp
;
/**预期值*/
@Excel
(
name
=
"预期值"
,
width
=
15
)
private
java
.
lang
.
String
expectedValue
;
/**更新时间*/
@JsonFormat
(
timezone
=
"GMT+8"
,
pattern
=
"yyyy-MM-dd"
)
@DateTimeFormat
(
pattern
=
"yyyy-MM-dd"
)
private
java
.
util
.
Date
updateTime
;
/**创建时间*/
@JsonFormat
(
timezone
=
"GMT+8"
,
pattern
=
"yyyy-MM-dd"
)
@DateTimeFormat
(
pattern
=
"yyyy-MM-dd"
)
private
java
.
util
.
Date
createTime
;
/**参数*/
@Excel
(
name
=
"参数"
,
width
=
15
)
private
java
.
lang
.
String
params
;
/**说明*/
@Excel
(
name
=
"说明"
,
width
=
15
)
private
java
.
lang
.
String
des
;
@Excel
(
name
=
"itemid"
,
width
=
15
)
private
java
.
lang
.
Integer
tplItemid
;
}
zrch-risk-server-39/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
浏览文件 @
9cb3fade
...
...
@@ -151,9 +151,13 @@ spring:
slow-sql-millis
:
5000
datasource
:
master
:
url
:
jdbc:mysql://47.98.203.68:3306/zrch_stm_db_3.9?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
# url: jdbc:mysql://47.98.203.68:3306/zrch_stm_db_3.9?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
# username: root
# password: ZhongRunChangHong/123
url
:
jdbc:mysql://localhost:3306/zrch_stm_db_3.9?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username
:
root
password
:
ZhongRunChangHong/123
password
:
123456
driver-class-name
:
com.mysql.cj.jdbc.Driver
# # shardingjdbc数据源
# sharding-db:
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论