提交 9cb3fade authored 作者: liuluyu's avatar liuluyu

Merge branch 'master' of http://47.97.51.208/root/zrch-risk-39

......@@ -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",
......
......@@ -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: {}
......
<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>
<div class="input-wrapper">
<vxe-input
:disabled="item.relatedFiled&&formData[row.code +'_'+ item.relatedFiled]!='是'"
: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);
......@@ -207,22 +324,62 @@ 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,
});
await setFormItemRight();
await setTplItemMap()
await setData();
} catch (error) {
VxeUI.modal.message({
content: `初始化页面失败: ${error.message}`,
status: 'error'
});
} finally {
loading.value = false
}
})
const loading = ref(false)
const formData = reactive({});
const formValues = ref<FormData[]>([])
const setFormItemRight = async () => {
tableFormData.forEach(row => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach(item => {
if (item.field) {
let tmpKey = `${row.code}_${item.field}`;
item["hasRight"] = userAllocItems.value.indexOf(tmpKey)>-1
item["hasValidFormula"] = validFormula.value?.length > 0 &&
validFormula.value.some((f: any) => {
const formulaText = (f.formula || '').toString()
return formulaText.includes(item.field)
})
}
});
}
});
}
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;
......@@ -519,7 +895,6 @@ async function setData() {
}
async function setTplItemMap() {
try {
const tplid = queryParam.value.tplId
......@@ -615,4 +990,243 @@ async function setTplItemMap() {
border-right: none;
background: transparent;
}
.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>
<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>
<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;
......
<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>
<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"
......@@ -21,21 +75,36 @@
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
<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,31 +198,56 @@
</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: '',
});
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;
......@@ -189,7 +260,7 @@
rind: number;
}
onMounted(async () => {
onMounted(async ()=>{
if (route.query.taskId) {
queryParam.value.taskId = Number(route.query.taskId);
}
......@@ -206,6 +277,20 @@
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();
});
......@@ -433,6 +518,138 @@
} finally {
}
}
}
// 校验数据
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>
......@@ -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%;
}
.custom-table {
width: 100%;
border: 1px solid #000;
.loading-text {
font-size: 14px;
color: #666;
}
.custom-table .vxe-header--column,
.custom-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
/* 校验结果 */
.validation-results {
padding: 20px;
}
.content-cell {
line-height: 1.8;
.result-summary {
display: flex;
gap: 40px;
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 4px;
}
.table-input {
width: 80px;
margin: 0 2px;
.summary-item {
display: flex;
align-items: center;
gap: 10px;
}
/* 附件样式 */
.attachments {
margin-top: 20px;
width: 100%;
.summary-label {
font-weight: 500;
color: #666;
}
.attachments h4 {
font-size: 14px;
.summary-value {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.attachment-table {
width: 100%;
border: 1px solid #000;
.summary-value.success {
color: #52c41a;
}
.attachment-table .vxe-header--column,
.attachment-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
.summary-value.error {
color: #ff4d4f;
}
.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;
.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>
<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 #buttons>
<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;
......
......@@ -2,16 +2,62 @@
<div class="bank-report-table" style="height: 80vh;">
<vxe-toolbar>
<template #buttons>
<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 },
])
......@@ -390,9 +605,154 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 更整齐的边框 */
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,
......
<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 #buttons>
<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>
/* 基础字体和排版 */
......@@ -399,9 +621,154 @@ async function setTplItemMap() {
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 更整齐的边框 */
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,
......
<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>
<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>
/* 基础字体和排版 */
......@@ -522,9 +743,154 @@ async function setTplItemMap() {
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse; /* 更整齐的边框 */
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,
......
......@@ -51,11 +51,11 @@ const gridOptions = reactive<VxeGridProps<RowVO>>({
{ field: 'itemName', title: '字段名称',width: "15%"},
{ field: 'values', title: '最近5年填报数据',width: "75%",
children: [
{ field: 'value1', title: '第一次',width: "15%"},
{ field: 'value2', title: '第二次',width: "15%"},
{ field: 'value3', title: '第三次',width: "15%"},
{ field: 'value4', title: '第四次',width: "15%"},
{ field: 'value5', title: '第五次',width: "15%"},
{ field: 'value0', title: '第一次',width: "15%"},
{ field: 'value1', title: '第二次',width: "15%"},
{ field: 'value2', title: '第三次',width: "15%"},
{ field: 'value3', title: '第四次',width: "15%"},
{ field: 'value4', 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;
}
......
......@@ -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);
}
}
......@@ -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("查询失败,请稍后重试");
}
}
}
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;
......@@ -41,6 +41,7 @@ public class BaosongTaskRecordController extends JeecgController<BaosongTaskReco
@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);
}
}
......@@ -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("查询失败,请稍后重试");
}
}
}
......@@ -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;
}
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论