提交 723b3f68 authored 作者: kxjia's avatar kxjia

Tb10 校验

上级 56d2318b
<template> <template>
<div class="bank-report-table" style="height:80vh" @click="closeTooltip"> <div class="bank-report-table" style="height:80vh" @click="closeAllTooltips">
<!-- 加载遮罩层 --> <!-- 加载遮罩层 -->
<div v-if="loading" class="loading-overlay"> <div v-if="loading" class="loading-overlay">
<div class="loading-spinner"> <div class="loading-spinner">
...@@ -22,48 +22,13 @@ ...@@ -22,48 +22,13 @@
</template> </template>
</vxe-toolbar> </vxe-toolbar>
<!-- 校验结果抽屉 --> <!-- 校验抽屉 -->
<vxe-drawer <ValidationDrawer
ref="validationDrawerRef"
v-model="drawerVisible" v-model="drawerVisible"
placement="right" :tableFormData="tableFormData"
@show="handleValidationDrawerShow" @validationResultClick="handleValidationResultClick"
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 <vxe-table
border border
...@@ -231,14 +196,61 @@ ...@@ -231,14 +196,61 @@
<vxe-textarea v-model="formData[getFieldKey(row.code, item.field)]" :rows="item.rows" :style="{ width: item.width }"></vxe-textarea> <vxe-textarea v-model="formData[getFieldKey(row.code, item.field)]" :rows="item.rows" :style="{ width: item.width }"></vxe-textarea>
</template> </template>
<template v-else> <template v-else>
<div class="input-wrapper">
<vxe-input <vxe-input
:type="item.type" :type="item.type"
v-model="formData[getFieldKey(row.code, item.field)]" v-model="formData[getFieldKey(row.code, item.field)]"
size="mini" size="mini"
class="table-input" class="table-input"
></vxe-input> @blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
</template> />
<span v-if="item.unit" class="unit">{{ item.unit }}</span> <span v-if="item.unit" class="unit">{{ item.unit }}</span>
<span
v-if="showHelpIcon(row.code, item.field, item)"
class="help-icon"
@click.stop="toggleTooltip(row.code, item.field)"
title="点击查看校验规则"
>
?
</span>
<span
v-if="inputErrors[getFieldKey(row.code, item.field)]"
class="error-icon"
@click.stop="toggleErrorTooltip(row.code, item.field)"
title="点击查看错误详情"
>
<i class="vxe-icon-error"></i>
</span>
<div
v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)"
class="tooltip"
@click.stop
>
{{ item.matchedFormula?.des || '暂无校验规则' }}
</div>
<div
v-if="showErrorTooltip && hoveredErrorKey === getFieldKey(row.code, item.field)"
class="error-tooltip"
@click.stop
>
{{ inputErrors[getFieldKey(row.code, item.field)].message }}
<br>
<span style="color: #ffcccc;">
公式: {{ inputErrors[getFieldKey(row.code, item.field)].formula }}
</span>
<template v-if="inputErrors[getFieldKey(row.code, item.field)].error">
<br>
<span style="color: #ff9999; font-size: 11px;">
错误: {{ inputErrors[getFieldKey(row.code, item.field)].error }}
</span>
</template>
</div>
</div>
</template>
</template> </template>
</template> </template>
</div> </div>
...@@ -254,15 +266,6 @@ ...@@ -254,15 +266,6 @@
<!-- 历史填报检查组件 --> <!-- 历史填报检查组件 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @closeDrawer="closeHistoryDrawer"/> <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> </div>
</template> </template>
...@@ -270,6 +273,7 @@ ...@@ -270,6 +273,7 @@
import MultiColumnTable from '../tableComponents/MultiColumnTable.vue' import MultiColumnTable from '../tableComponents/MultiColumnTable.vue'
import AttachTable from '../tableComponents/AttachTable.vue' import AttachTable from '../tableComponents/AttachTable.vue'
import HistoryFillCheck from './check/historyFillCheck.vue' import HistoryFillCheck from './check/historyFillCheck.vue'
import ValidationDrawer from './check/ValidationDrawer.vue'
import { tableFormData } from '../../data/tb10.data' import { tableFormData } from '../../data/tb10.data'
import { ref, reactive, nextTick, onMounted } from 'vue' import { ref, reactive, nextTick, onMounted } from 'vue'
import { VxeUI,VxeTablePropTypes } from 'vxe-table' import { VxeUI,VxeTablePropTypes } from 'vxe-table'
...@@ -294,6 +298,7 @@ const route = useRoute() ...@@ -294,6 +298,7 @@ const route = useRoute()
const tableRef = ref() const tableRef = ref()
const tplItemMap = ref<Record<string, any>>({}) const tplItemMap = ref<Record<string, any>>({})
const historyFillCheckRef = ref<any>(null) const historyFillCheckRef = ref<any>(null)
const validationDrawerRef = ref()
const loading = ref(false) const loading = ref(false)
const formData = reactive<Record<string, any>>({}) const formData = reactive<Record<string, any>>({})
const formValues = ref<FormData[]>([]) const formValues = ref<FormData[]>([])
...@@ -311,12 +316,26 @@ const queryParam = ref({ ...@@ -311,12 +316,26 @@ const queryParam = ref({
// 权限相关状态 // 权限相关状态
const userAllocItems = ref<string[]>([]) const userAllocItems = ref<string[]>([])
const validFormula = ref<any[]>([]) const validFormula = ref<any[]>([])
const validationResultsList = ref<any[]>([])
const drawerVisible = ref(false) const drawerVisible = ref(false)
const historyDrawerVisible = ref(false) const historyDrawerVisible = ref(false)
const validationTooltipVisible = ref(false) const showTooltip = ref(false)
const validationTooltipPosition = ref({ left: 0, top: 0 }) const hoveredKey = ref('')
const validationTooltipContent = ref('') const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
const inputErrors = ref<Record<string, any>>({})
const isInitialized = ref(false)
interface FormulaItem {
formula: string
des: string
[key: string]: any
}
interface InputError {
message: string
formula: string
error?: string
}
onMounted(async () => { onMounted(async () => {
if (route.query.taskId) { if (route.query.taskId) {
...@@ -350,7 +369,14 @@ onMounted(async () => { ...@@ -350,7 +369,14 @@ onMounted(async () => {
} }
await setTplItemMap() await setTplItemMap()
setFormItemRight()
await setData() await setData()
isInitialized.value = true
setTimeout(() => {
refreshHelpIcons()
}, 300)
}) })
const getFieldKey = (pcode: string | undefined, field: string): string => { const getFieldKey = (pcode: string | undefined, field: string): string => {
...@@ -600,121 +626,119 @@ async function setTplItemMap() { ...@@ -600,121 +626,119 @@ async function setTplItemMap() {
// 校验数据 // 校验数据
const checkData = () => { const checkData = () => {
drawerVisible.value = true drawerVisible.value = true
validationDrawerRef.value.setValidateData(validFormula.value, formData)
} }
// 校验结果抽屉显示时触发 const handleInputBlur = (rowCode: string, field: string, formula?: string) => {
const handleValidationDrawerShow = () => { if (!formula) return;
performValidation()
}
// 执行校验 const key = getFieldKey(rowCode, field);
const performValidation = () => { const value = formData[key];
validationResultsList.value = []
const process: string[] = []
process.push('开始校验')
process.push(`找到 ${validFormula.value.length} 个验证公式`)
validFormula.value.forEach((formulaItem: any) => { if (!value || value === '') {
process.push(`开始校验公式: ${formulaItem.des}`) delete inputErrors.value[key];
const result = evaluateFormula(formulaItem.formula, formulaItem.des, process) return;
if (result) {
validationResultsList.value.push(result)
} }
})
process.push('校验完成')
}
// 解析和执行验证公式 validateFieldFormula(rowCode, field, formula);
const evaluateFormula = (formula: string, description: string, process: string[]): any => { };
const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
const key = getFieldKey(rowCode, field);
try { try {
const fieldMatch = formula.match(/\[(\w+)\]/) const expression = buildExpression(formula, rowCode);
if (!fieldMatch) { const isValid = eval(expression);
process.push(`跳过: 无法解析公式字段 ${formula}`) if (!isValid) {
return null inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
formula
};
} else {
delete inputErrors.value[key];
} }
} catch (error: any) {
const fieldName = fieldMatch[1] inputErrors.value[key] = {
const row = tableFormData.find((r: any) => r.content?.some((c: any) => c.field === fieldName)) message: "公式校验失败,请检查填写内容",
if (!row) { formula,
process.push(`跳过: 未找到字段 ${fieldName}`) error: error.message
return null };
}
};
const buildExpression = (formula: string, rowCode: string): string => {
const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || [];
const uniqueFields = [...new Set(fieldNames.filter(f =>
!['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase())
))];
uniqueFields.sort((a, b) => b.length - a.length);
let expression = formula;
for (const fieldName of uniqueFields) {
const key = getFieldKey(rowCode, fieldName);
const value = formData[key];
if (value !== undefined) {
expression = expression.replace(
new RegExp(`\\b${fieldName}\\b`, 'g'),
`Number(${value})`
);
} }
const fieldItem = row.content.find((c: any) => c.field === fieldName)
if (!fieldItem) {
process.push(`跳过: 未找到字段 ${fieldName}`)
return null
} }
const strKey = `${row.code}_${fieldName}` return expression;
const fieldValue = formData[strKey] };
// 检查是否有空字段规则 const toggleTooltip = (rowCode: string, field: string) => {
const emptyFieldRule = validFormula.value.find((f: any) => const key = getFieldKey(rowCode, field);
f.formula.includes(fieldName) && f.des.includes('空字段') if (hoveredKey.value === key) {
) showTooltip.value = false;
hoveredKey.value = '';
} else {
showTooltip.value = true;
hoveredKey.value = key;
}
};
if (emptyFieldRule && (!fieldValue || fieldValue === '')) { const toggleErrorTooltip = (rowCode: string, field: string) => {
process.push(`跳过: 字段 ${fieldName} 为空,跳过空字段规则`) const key = getFieldKey(rowCode, field);
return null if (hoveredErrorKey.value === key) {
showErrorTooltip.value = false;
hoveredErrorKey.value = '';
} else {
showErrorTooltip.value = true;
hoveredErrorKey.value = key;
}
};
const closeAllTooltips = () => {
showTooltip.value = false;
hoveredKey.value = '';
showErrorTooltip.value = false;
hoveredErrorKey.value = '';
};
const showHelpIcon = (rowCode: string, field: string, item: any): boolean => {
if (item.hasValidFormula !== undefined) {
return item.hasValidFormula === true;
} }
const expression = formula.replace(/\[(\w+)\]/g, (_, field) => { const key = getFieldKey(rowCode, field);
const value = formData[`${row.code}_${field}`] const hasRight = userAllocItems.value.includes(key);
return value !== undefined ? `Number(${value})` : '0'
})
process.push(`执行公式: ${expression}`) if (!hasRight) return false;
const isValid = eval(expression)
return { const formulaResult = findEarliestFormulaForField(validFormula.value, field);
field: fieldName, return !!formulaResult.formula;
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 handleValidationResultClick = (result: any) => {
const row = tableFormData.find((r: any) => r.code === result.rowCode) const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
if (row) { const status = result.isValid ? 'success' : 'error'
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)
}
}
}
}
// 显示验证公式帮助 VxeUI.modal.message({ content: message, status })
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
} }
// 打开历史填报抽屉 // 打开历史填报抽屉
...@@ -727,6 +751,85 @@ const closeHistoryDrawer = () => { ...@@ -727,6 +751,85 @@ const closeHistoryDrawer = () => {
historyDrawerVisible.value = false historyDrawerVisible.value = false
} }
const setFormItemRight = () => {
console.log('开始设置表单权限...')
console.log('userAllocItems 数量:', userAllocItems.value.length)
console.log('validFormula 数量:', validFormula.value.length)
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => {
if (item.field) {
const key = getFieldKey(row.code, item.field)
item.hasRight = userAllocItems.value.includes(key)
console.log(`字段 ${key}: hasRight = ${item.hasRight}`)
if (item.hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formulaResult.formula
item.matchedFormula = formulaResult.formula || null
console.log(`字段 ${key}: hasValidFormula = ${item.hasValidFormula}`)
} else {
item.hasValidFormula = false
item.matchedFormula = null
}
}
})
}
})
console.log('表单权限设置完成')
}
const findEarliestFormulaForField = (
formulas: FormulaItem[],
field: string
): { formula: FormulaItem | null, index: number } => {
let result = { formula: null, index: Infinity }
formulas?.forEach((f: FormulaItem) => {
const text = f.formula || ''
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g')
const match = regex.exec(text)
if (match && match.index < result.index) {
result = { formula: f, index: match.index }
}
})
return result
}
const refreshHelpIcons = () => {
console.log('刷新帮助图标...')
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => {
if (item.field) {
const key = getFieldKey(row.code, item.field)
const hasRight = userAllocItems.value.includes(key)
if (hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formulaResult.formula
item.matchedFormula = formulaResult.formula || null
} else {
item.hasValidFormula = false
item.matchedFormula = null
}
}
})
}
})
nextTick(() => {
})
}
const mergeCells = ref<VxeTablePropTypes.MergeCells>([ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
{ row: 0, col: 1, rowspan: 1, colspan: 2 }, { row: 0, col: 1, rowspan: 1, colspan: 2 },
{ row: 1, col: 1, rowspan: 1, colspan: 2 }, { row: 1, col: 1, rowspan: 1, colspan: 2 },
...@@ -812,110 +915,6 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -812,110 +915,6 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
font-size: 14px; 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 { .content-cell {
line-height: 1.8; line-height: 1.8;
padding: 5px; padding: 5px;
...@@ -978,4 +977,105 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -978,4 +977,105 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
padding: 0; padding: 0;
border: none; border: none;
} }
.input-wrapper {
position: relative;
display: inline-block;
}
.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%;
transition: all 0.3s;
opacity: 0;
animation: fadeIn 0.5s forwards;
animation-delay: 0.3s;
&:hover {
background: #bae7ff;
transform: scale(1.1);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.error-icon {
margin-left: 5px;
cursor: pointer;
color: #ff4d4f;
font-size: 16px;
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
transition: all 0.3s;
&:hover {
transform: scale(1.1);
}
}
.tooltip, .error-tooltip {
position: absolute;
top: 100%;
left: 0;
margin-top: 5px;
padding: 10px;
font-size: 12px;
border-radius: 4px;
z-index: 1000;
white-space: pre-wrap;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip {
background: #333;
color: #fff;
&::before {
content: '';
position: absolute;
bottom: 100%;
left: 10px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #333 transparent;
}
}
.error-tooltip {
background: #8b0000;
color: #fff;
&::before {
content: '';
position: absolute;
bottom: 100%;
left: 10px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #8b0000 transparent;
}
}
</style> </style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论