提交 56d2318b authored 作者: kxjia's avatar kxjia

Tb9.vue 验证

上级 9457bcbd
<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 class="loading-spinner">
......@@ -16,9 +16,9 @@
</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>
<vxe-button status="primary" icon="vxe-icon-edit" @click="handleOpenHistoryDrawer" :disabled="loading">近5年数据填报</vxe-button>
<vxe-button status="primary" icon="vxe-icon-save" @click="validateData">校验</vxe-button>
<vxe-button status="primary" icon="vxe-icon-save" @click="saveBatch">保存</vxe-button>
</template>
</vxe-toolbar>
......@@ -83,8 +83,8 @@
</vxe-column>
<vxe-column field="content" title="内容" row-resize>
<template #default="{ row }">
<div class="content-cell">
<!-- MultiColumnTable 类型 -->
<template v-if="row.type === 'MultiColumnTable'">
<MultiColumnTable
:title="row.project"
......@@ -95,6 +95,8 @@
style="margin:0px;padding:0px"
/>
</template>
<!-- AttachTable 类型 -->
<template v-else-if="row.type === 'AttachTable'">
<AttachTable
:title="row.project"
......@@ -105,6 +107,8 @@
style="margin:0px;padding:0px"
/>
</template>
<!-- 其他类型 -->
<template v-else>
<template v-for="(item, index) in row.content" :key="index">
<span v-if="item.type === 'text'" v-html="item.value"></span>
......@@ -138,118 +142,175 @@
</template>
<template v-else-if="item.type === 'yesno'">
<vxe-radio-group v-model="formData[row.code +'_'+ item.field]">
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio label="是" content="是">
</vxe-radio>
<vxe-radio label="否" content="否"></vxe-radio>
</vxe-radio-group>
<template v-if="item.extraField||item.extraFields">
<div v-if="formData[row.code +'_'+ item.field]=='是'" style="margin-left:40px">
<div v-if="formData[getFieldKey(row.code, item.field)]=='是'" style="margin-left:40px">
如是,
<template v-if="item.extraField">
{{item.extraLabel }}
<vxe-input v-model="formData[row.code + '_'+ item.extraField]" style="width: 300px;"></vxe-input>
<vxe-input v-model="formData[getFieldKey(row.code, item.extraField)]" style="width: 300px;"></vxe-input>
<span class="unit"> {{ item.unit }}</span>
</template>
<template v-else-if="item.extraFields">
<template v-for="(ccopt,ccind) in item.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span>
<template v-if="ccopt.formType=='input'">
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" style="width:250px"></vxe-input>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" style="width:250px"></vxe-input>
</template>
<template v-else-if="ccopt.formType=='number'">
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" type="number" style="width:250px"></vxe-input>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" type="number" style="width:250px"></vxe-input>
<span class="unit"> {{ ccopt.unit }}</span>
</template>
<template v-else-if="ccopt.formType=='checkbox'">
<vxe-checkbox-group v-model="formData[row.code + '_'+ ccopt.field]">
<vxe-checkbox-group v-model="formData[getFieldKey(row.code, ccopt.field)]">
<vxe-checkbox v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt">
</vxe-checkbox>
</vxe-checkbox-group>
</template>
<template v-else-if="ccopt.formType=='radio'">
<vxe-radio-group v-model="formData[row.code + '_'+ ccopt.field]" :options="ccopt.options">
<vxe-radio-group v-model="formData[getFieldKey(row.code, ccopt.field)]" :options="ccopt.options">
<vxe-radio v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt">
</vxe-radio>
</vxe-radio-group>
</template>
<template v-else>
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" :type="ccopt.formType" style="width:100px"></vxe-input>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" :type="ccopt.formType" style="width:100px"></vxe-input>
</template>
<br>
<template v-if="ccopt.otherOption">
其他:<vxe-input v-model="formData[row.code + '_'+ ccopt.otherField]" style="width: 300px;"></vxe-input>
其他:<vxe-input v-model="formData[getFieldKey(row.code, ccopt.otherField)]" style="width: 300px;"></vxe-input>
</template>
<br>
</template>
</template>
</div>
</template>
</template>
</template>
<span v-else-if="item.type === 'radio-group'" class="radio-group">
<vxe-radio-group v-model="formData[row.code +'_'+ item.field]">
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt">{{ opt }}</vxe-radio>
</vxe-radio-group>
</span>
<span v-else-if="item.type === 'radio-group-extraFields'" class="radio-group" style="width:100%">
<vxe-radio-group v-model="formData[row.code +'_'+ item.field]">
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt.label">
{{ opt.label }}
<template v-if="opt.extraField&&formData[row.code +'_'+ item.field]==opt.label">
<template v-if="opt.extraField&&formData[getFieldKey(row.code, item.field)]==opt.label">
<span>{{ opt.extraFieldLabel }}</span>
<vxe-input v-model="formData[row.code + '_'+ opt.otereField]" style="width: 200px;"></vxe-input>
<vxe-input v-model="formData[getFieldKey(row.code, opt.otherField)]" style="width: 200px;"></vxe-input>
</template>
<template v-if="opt.extraFields&&formData[row.code +'_'+ item.field]==opt.label">
<template v-if="opt.extraFields&&formData[getFieldKey(row.code, item.field)]==opt.label">
<template v-for="(ccopt,ccind) in opt.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span>
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" style="width: 200px;"></vxe-input>
<span>{{ ccopt.label }}: </span>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" style="width: 200px;"></vxe-input>
</template>
</template>
</vxe-radio>
</vxe-radio-group>
</span>
<div v-else-if="item.type === 'checkbox-group'">
<span>
<vxe-checkbox-group v-model="formData[row.code+'_'+item.field]">
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt">
{{ opt }}
<vxe-checkbox-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt">
{{ opt }}
</vxe-checkbox>
<template v-if="item.otherOption">
<vxe-checkbox label="其他">
其他:
<vxe-input v-model="formData[getFieldKey(row.code, item.otherField)]" style="width: 200px;"
:disabled="!(formData[getFieldKey(row.code, item.field)]?.indexOf('其他')>-1)">
</vxe-input>
</vxe-checkbox>
<template v-if="item.otherOption">
<vxe-checkbox label="其他">
其他:
<vxe-input v-model="formData[row.code + '_'+ item.otherField]" style="width: 200px;"
:disabled="!(formData[row.code+'_'+item.field]?.indexOf('其他')>-1)">
</vxe-input>
</vxe-checkbox>
</template>
</vxe-checkbox-group>
</span>
</template>
</vxe-checkbox-group>
</span>
</div>
<template v-else-if="item.type=='textarea'">
<vxe-textarea v-model="formData[row.code +'_'+ item.field]" :rows="item.rows" :style="{width:item.width}">
<vxe-textarea v-model="formData[getFieldKey(row.code, item.field)]" :rows="item.rows" :style="{width:item.width}">
</vxe-textarea>
</template>
<template v-else>
<vxe-input :type="item.type" v-model="formData[row.code +'_'+ item.field]" size="mini" class="table-input">
</vxe-input>
<div class="input-wrapper">
<vxe-input
:type="item.type"
v-model="formData[getFieldKey(row.code, item.field)]"
size="mini"
class="table-input"
:disabled="!item.hasRight"
@blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
/>
<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>
<span v-if="item.unit" class="unit"> {{ item.unit }}</span>
</template>
</template>
</div>
</template>
</vxe-column>
<vxe-column field="remarks" title="备注" width="150">
<template #default="{ row }">
<vxe-textarea :rows="row.remarks.rows" v-model="formData[row.code+'_'+row.remarks.field]" style="height:100%;">
</vxe-textarea>
<vxe-textarea :rows="row.remarks.rows" v-model="formData[getFieldKey(row.code, row.remarks.field)]" style="height:100%;">
</vxe-textarea>
</template>
</vxe-column>
</vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @closeDrawer="closeHistoryDrawer"/>
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @close-drawer="closeHistoryDrawer"/>
<!-- 验证公式帮助提示 -->
<div
......@@ -268,24 +329,24 @@ 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, findUserRightForTplItem } from '../../alloc/BaosongTaskAlloc.api'
import { ref, reactive, nextTick, onMounted, computed } from 'vue'
import { VxeUI } from 'vxe-table'
import { queryRecord, batchSaveOrUpdateBeforeDelete } 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 tableRef = ref();
const tplItemMap = ref({});
const tplItemMap = ref<Record<string, any>>({});
const historyFillCheckRef = ref<any>(null);
const queryParam = ref({
taskId:-1,
taskName:'',
tplId:-1,
tplName:'',
tplCode:''
taskId: -1,
taskName: '',
tplId: -1,
tplName: '',
tplCode: ''
})
// 权限相关状态
......@@ -294,6 +355,12 @@ const validFormula = ref<any[]>([])
const validationResultsList = ref<any[]>([])
const drawerVisible = ref(false)
const historyDrawerVisible = ref(false)
const showTooltip = ref(false)
const hoveredKey = ref('')
const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
const inputErrors = ref<Record<string, any>>({})
const isInitialized = ref(false)
const validationTooltipVisible = ref(false)
const validationTooltipPosition = ref({ left: 0, top: 0 })
const validationTooltipContent = ref('')
......@@ -303,28 +370,28 @@ interface FormData {
taskid: number
tplid: number
itemid?: number
itempid?:number
itempid?: number
content: string
tplcode:string
rind:number
tplcode: string
rind: number
}
onMounted(async ()=>{
if (route.query.taskId) {
queryParam.value.taskId = Number(route.query.taskId);
}
if (route.query.taskName) {
onMounted(async () => {
if (route.query.taskId) {
queryParam.value.taskId = Number(route.query.taskId);
}
if (route.query.taskName) {
queryParam.value.taskName = String(route.query.taskName);
}
if (route.query.tplId) {
queryParam.value.tplId = Number(route.query.tplId);
}
if (route.query.tplName) {
queryParam.value.tplName = String(route.query.tplName);
}
if (route.query.tplCode) {
queryParam.value.tplCode = String(route.query.tplCode);
}
}
if (route.query.tplId) {
queryParam.value.tplId = Number(route.query.tplId);
}
if (route.query.tplName) {
queryParam.value.tplName = String(route.query.tplName);
}
if (route.query.tplCode) {
queryParam.value.tplCode = String(route.query.tplCode);
}
// 获取权限和验证公式
try {
......@@ -336,116 +403,122 @@ onMounted(async ()=>{
validFormula.value = await getTblvalidFormula({
tplid: queryParam.value.tplId,
})
console.log('获取到的权限:', userAllocItems.value.length);
console.log('获取到的公式:', validFormula.value.length);
} catch (error) {
console.error('获取权限或验证公式失败:', error)
}
await setTplItemMap()
await setData();
})
setFormItemRight()
await setData()
isInitialized.value = true
setTimeout(() => {
refreshHelpIcons()
}, 300)
})
const loading = ref(false)
const formData = reactive({});
const formData = reactive<Record<string, any>>({});
const formValues = ref<FormData[]>([])
// 获取字段key的统一方法
const getFieldKey = (rowCode: string, field: string): string => {
return `${rowCode}_${field}`
}
const saveBatch = async () => {
try {
loading.value = true
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)
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' })
//await setData()
} else {
VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' })
}
} catch (error) {
} catch (error: any) {
VxeUI.modal.message({ content: `保存失败: ${error.message}`, status: 'error' })
} finally {
loading.value = false
}
}
const childMultiTableRefs = ref({})
const childMultiTableRefs = ref<Record<string, any>>({})
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 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 childAttachTableRefs = ref<Record<string, any>>({})
const setAttachTableRef = (el: any, index: string) => {
if (el) {
childAttachTableRefs.value[index] = el; // 保存子组件实例
childAttachTableRefs.value[index] = el;
}
};
const getAttachTableFormData = async () => {
const getAttachTableFormData = async () => {
for (const pcode in childAttachTableRefs.value) {
const child = childAttachTableRefs.value[pcode];
if (child) {
const datas = child.getFormData()
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 ?? {};
const setFormValues = async (pcode: string, code: string, valData: any, rind: number) => {
if (!valData) return;
let vals = Array.isArray(valData) ? valData.join(',') : valData
let strKey = getFieldKey(pcode, code)
const item = tplItemMap.value[strKey];
const { pid, itemid } = item ?? {};
let tempForm: FormData = {
id: null,
rind:rind,
rind: rind,
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId,
tplcode: code,
itemid: itemid,
itempid: pid,
content:vals
content: vals
};
formValues.value.push(tempForm)
}
......@@ -455,27 +528,30 @@ async function setData() {
loading.value = true;
const taskId = queryParam.value.taskId;
const tplid = queryParam.value.tplId;
Object.keys(formData).forEach(key => delete formData[key]);
await setTplItemMap();
const recordData = await queryRecord({ taskid: taskId, tplid: tplid });
const valueObj = {};
const valueObj: Record<string, any> = {};
for (const data of recordData) {
const key = `${data.itempid}_${data.itemid}_${data.rind}`;
valueObj[key] = data.content;
}
const shouldSkip = (key) => {
const shouldSkip = (key: string) => {
const refsToCheck = [
childMultiTableRefs.value,
childAttachTableRefs.value,
];
for (const refs of refsToCheck) {
for (const pcode of Object.keys(refs)) {
if (key.startsWith(pcode + '_')) {
if (key === "TB9006_COL64") {
return false;
}
return true;
}
}
......@@ -484,24 +560,18 @@ async function setData() {
};
for (const strKey of Object.keys(tplItemMap.value)) {
if (shouldSkip(strKey)) {
if(strKey=="TB9006_COL64") {
} else {
continue;
}
continue;
}
const item = tplItemMap.value[strKey];
if (!item) continue;
const { pid, itemid, formTp } = item;
const dataVal = valueObj[`${pid}_${itemid}_1`];
if (!dataVal) continue;
if (formTp === 'checkbox') {
formData[strKey] = dataVal?.split(",") || [];
} else {
......@@ -511,95 +581,90 @@ async function setData() {
for (const pcode of Object.keys(childAttachTableRefs.value)) {
const child = childAttachTableRefs.value[pcode];
const matchingKeys = Object.keys(tplItemMap.value).filter(key =>
const matchingKeys = Object.keys(tplItemMap.value).filter(key =>
key.startsWith(pcode + '_')
);
const attachTableData = {};
const attachTableData: Record<string, any> = {};
for (const key of matchingKeys) {
const tmpData = tplItemMap.value[key];
if (!tmpData) continue;
const { pid, itemid, formTp, code } = tmpData;
const valueKey = `${pid}_${itemid}_1`;
if (formTp === 'checkbox') {
attachTableData[code] = valueObj[valueKey]?.split(",") || [];
} else {
attachTableData[code] = valueObj[valueKey] || "";
}
}
child.setFormData(attachTableData);
}
for (const pcode of Object.keys(childMultiTableRefs.value)) {
const child = childMultiTableRefs.value[pcode];
const matchingKeys = Object.keys(tplItemMap.value).filter(key =>
const matchingKeys = Object.keys(tplItemMap.value).filter(key =>
key.startsWith(pcode + '_')
);
const rowsMap = {};
const rowsMap: Record<string, Record<string, any>> = {};
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 =>
const valKeys = Object.keys(valueObj).filter(k =>
k.startsWith(`${pid}_${itemid}_`)
);
for (const valKey of valKeys) {
const parts = valKey.split('_');
const rind = parts[2];
if (!rowsMap[rind]) {
rowsMap[rind] = {};
}
rowsMap[rind][code] = valueObj[valKey];
}
}
const tableDatas = Object.values(rowsMap);
nextTick();
child.setFormData(tableDatas);
}
} catch (error) {
VxeUI.modal.message({
content: `加载数据失败: ${error.message}`,
status: 'error'
} catch (error: any) {
VxeUI.modal.message({
content: `加载数据失败: ${error.message}`,
status: 'error'
});
} finally {
loading.value = false;
}
}
async function setTplItemMap() {
try {
const tplid = queryParam.value.tplId
const tplid = queryParam.value.tplId
const tplItem = await allTplItems({
tplid: tplid,
})
tplItem.forEach(item => {
let strKey = item.pcode + "_" + item.xmlcode;
tplItemMap.value[strKey] = {pid:item.pid,itemid:item.id,formTp:item.formTp,code:item.xmlcode};
tplItemMap.value = {}
tplItem.forEach((item: any) => {
let strKey = getFieldKey(item.pcode, item.xmlcode)
tplItemMap.value[strKey] = { pid: item.pid, itemid: item.id, formTp: item.formTp, code: item.xmlcode }
})
} catch (error) {
} catch (error: any) {
VxeUI.modal.message({ content: `加载数据失败: ${error.message}`, status: 'error' })
} finally {
}
}
// 校验数据
const checkData = () => {
const validateData = () => {
drawerVisible.value = true
}
......@@ -647,11 +712,11 @@ const evaluateFormula = (formula: string, description: string, process: string[]
return null
}
const strKey = `${row.code}_${fieldName}`
const strKey = getFieldKey(row.code, fieldName)
const fieldValue = formData[strKey]
// 检查是否有空字段规则
const emptyFieldRule = validFormula.value.find((f: any) =>
const emptyFieldRule = validFormula.value.find((f: any) =>
f.formula.includes(fieldName) && f.des.includes('空字段')
)
......@@ -661,7 +726,7 @@ const evaluateFormula = (formula: string, description: string, process: string[]
}
const expression = formula.replace(/\[(\w+)\]/g, (_, field) => {
const value = formData[`${row.code}_${field}`]
const value = formData[getFieldKey(row.code, field)]
return value !== undefined ? `Number(${value})` : '0'
})
......@@ -682,26 +747,6 @@ const evaluateFormula = (formula: string, description: string, process: string[]
}
}
// 处理校验结果点击,定位到表格
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()
......@@ -728,22 +773,229 @@ const closeHistoryDrawer = () => {
historyDrawerVisible.value = false
}
interface FormulaItem {
formula: string
des: string
[key: string]: any
}
interface InputError {
message: string
formula: string
error?: string
}
const fieldKeys = computed(() => Object.keys(formData))
const showHelpIcon = (rowCode: string, field: string, item: any): boolean => {
if (item.hasValidFormula !== undefined) {
return item.hasValidFormula === true
}
const key = getFieldKey(rowCode, field)
const hasRight = userAllocItems.value.includes(key)
if (!hasRight) return false
const formulaResult = findEarliestFormulaForField(validFormula.value, field)
return !!formulaResult.formula
}
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(() => {
console.log('帮助图标刷新完成')
})
}
const handleInputBlur = (rowCode: string, field: string, formula?: string) => {
if (!formula) return
const key = getFieldKey(rowCode, field)
const value = formData[key]
if (!value || value === '') {
delete inputErrors.value[key]
return
}
validateFieldFormula(rowCode, field, formula)
}
const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
const key = getFieldKey(rowCode, field)
try {
const expression = buildExpression(formula, rowCode)
const isValid = eval(expression)
if (!isValid) {
inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
formula
}
} else {
delete inputErrors.value[key]
}
} catch (error: any) {
inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
formula,
error: error.message
}
}
}
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})`
)
}
}
return expression
}
const toggleTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field)
if (hoveredKey.value === key) {
showTooltip.value = false
hoveredKey.value = ''
} else {
showTooltip.value = true
hoveredKey.value = key
}
}
const toggleErrorTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field)
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 = ''
validationTooltipVisible.value = false
}
const handleValidationResultClick = (result: any) => {
const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
const status = result.isValid ? 'success' : 'error'
VxeUI.modal.message({ content: message, status })
}
</script>
<style lang="less" scoped>
/* 基础字体和排版 */
.bank-report-table {
font-family: "SimSun", "宋体", serif;
font-size: 12px;
color: #000;
margin: 5px auto;
width: 90%; /* 统一使用一个宽度 */
}
/* 表格样式 */
.custom-table, .attachment-table {
width: 100%;
<style lang="less" scoped>
/* 基础字体和排版 */
.bank-report-table {
font-family: "SimSun", "宋体", serif;
font-size: 12px;
color: #000;
margin: 5px auto;
width: 90%;
}
/* 表格样式 */
.custom-table,
.attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 统一边框处理 */
border-collapse: collapse;
}
/* 加载遮罩层样式 */
......@@ -776,8 +1028,13 @@ const closeHistoryDrawer = () => {
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-text {
......@@ -891,93 +1148,204 @@ const closeHistoryDrawer = () => {
}
/* 表格单元格样式 */
.custom-table .vxe-header--column,
.custom-table .vxe-body--column,
.attachment-table .vxe-header--column,
.attachment-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
/* 内容单元格 */
.content-cell {
line-height: 1.8;
}
/* 输入框样式 */
.table-input,
.vxe-input {
height: 24px;
line-height: 30px;
min-width: 150px;
margin: 0 2px;
padding: 0 5px;
border: none; /* 去掉多余的边框,只留底部边框 */
border-bottom: 1px solid #333;
background: transparent;
text-align: center;
}
/* 表单元素的标签和图标 */
.vxe-radio--label,
.vxe-checkbox--label {
font-size: 12px;
}
.vxe-radio--icon,
.vxe-checkbox--icon {
font-size: 12px;
}
/* 单选框组和复选框组布局 */
.vxe-radio-group,
.vxe-checkbox-group {
display: inline-block;
margin-right: 5px;
.custom-table .vxe-header--column,
.custom-table .vxe-body--column,
.attachment-table .vxe-header--column,
.attachment-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
/* 内容单元格 */
.content-cell {
line-height: 1.8;
}
/* 输入框包装器 */
.input-wrapper {
position: relative;
display: inline-block;
}
/* 输入框样式 */
.table-input,
.vxe-input {
height: 24px;
line-height: 30px;
min-width: 150px;
margin: 0 2px;
padding: 0 5px;
border: none;
border-bottom: 1px solid #333;
background: transparent;
text-align: center;
}
/* 表单元素的标签和图标 */
.vxe-radio--label,
.vxe-checkbox--label {
font-size: 12px;
}
.vxe-radio--icon,
.vxe-checkbox--icon {
font-size: 12px;
}
/* 单选框组和复选框组布局 */
.vxe-radio-group,
.vxe-checkbox-group {
display: inline-block;
margin-right: 5px;
max-width: 99%;
}
}
/* 其他辅助样式 */
/* 其他辅助样式 */
.radio-group,
.checkbox-group {
display: inline-block;
margin-right: 5px;
.checkbox-group {
display: inline-block;
margin-right: 5px;
max-width: 99%;
min-width: 50%;
}
.vxe-radio,
.vxe-checkbox {
margin: 5px 5px 5px 30px;
white-space: nowrap;
}
/* 附件部分样式 */
.attachments {
margin-top: 20px;
width: 100%;
}
.attachments h4 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 统一边框处理 */
}
/* 无用或重复定义已优化移除 */
/* 其他特殊块 */
blockquote {
padding: 0 5px;
color: black;
cursor: pointer;
}
</style>
\ No newline at end of file
}
.vxe-radio,
.vxe-checkbox {
margin: 5px 5px 5px 30px;
white-space: nowrap;
}
/* 附件部分样式 */
.attachments {
margin-top: 20px;
width: 100%;
}
.attachments h4 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse;
}
/* 其他特殊块 */
blockquote {
padding: 0 5px;
color: black;
cursor: pointer;
}
/* 单位文本 */
.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%;
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;
border: 1px solid #ff4d4f;
&::before {
content: '';
position: absolute;
bottom: 100%;
left: 5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #8b0000 transparent;
}
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论