提交 6538bd96 authored 作者: kxjia's avatar kxjia

修改TB3

上级 9ea05c50
...@@ -89,6 +89,9 @@ importers: ...@@ -89,6 +89,9 @@ importers:
dayjs: dayjs:
specifier: ^1.11.18 specifier: ^1.11.18
version: 1.11.19 version: 1.11.19
docx-preview:
specifier: ^0.3.7
version: 0.3.7
dom-align: dom-align:
specifier: ^1.12.4 specifier: ^1.12.4
version: 1.12.4 version: 1.12.4
...@@ -3661,6 +3664,9 @@ packages: ...@@ -3661,6 +3664,9 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
docx-preview@0.3.7:
resolution: {integrity: sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==}
dom-align@1.12.4: dom-align@1.12.4:
resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==}
...@@ -11584,6 +11590,10 @@ snapshots: ...@@ -11584,6 +11590,10 @@ snapshots:
dependencies: dependencies:
esutils: 2.0.3 esutils: 2.0.3
docx-preview@0.3.7:
dependencies:
jszip: 3.10.1
dom-align@1.12.4: {} dom-align@1.12.4: {}
dom-scroll-into-view@2.0.1: {} dom-scroll-into-view@2.0.1: {}
......
...@@ -33,7 +33,7 @@ export const tableFormData = [ ...@@ -33,7 +33,7 @@ export const tableFormData = [
{ type: 'text', value: '(1)重要信息系统总数(不含基础设施类):' }, { type: 'text', value: '(1)重要信息系统总数(不含基础设施类):' },
{ type: 'input', value: '', unit: '个', field: 'COL0' }, { type: 'input', value: '', unit: '个', field: 'COL0' },
{ type: 'brspace' }, { type: 'brspace' },
{ type: 'text', value: '其中:(银行业)' }, { type: 'text', value: '其中:(银行业)' },
{ type: 'brspace' }, { type: 'brspace' },
{ type: 'text', value: '综合业务类:' }, { type: 'text', value: '综合业务类:' },
{ type: 'number', value: '', unit: '个', field: 'COL1' }, { type: 'number', value: '', unit: '个', field: 'COL1' },
...@@ -41,42 +41,47 @@ export const tableFormData = [ ...@@ -41,42 +41,47 @@ export const tableFormData = [
{ type: 'number', value: '', unit: '个', field: 'COL2' }, { type: 'number', value: '', unit: '个', field: 'COL2' },
{ type: 'text', value: ',客户管理类:' }, { type: 'text', value: ',客户管理类:' },
{ type: 'number', value: '', unit: '个', field: 'COL3' }, { type: 'number', value: '', unit: '个', field: 'COL3' },
{ type: 'text', value: ',产品管理类:' }, { type: 'brspace' },
{ type: 'text', value: '产品管理类:' },
{ type: 'number', value: '', unit: '个', field: 'COL4' }, { type: 'number', value: '', unit: '个', field: 'COL4' },
{ type: 'text', value: '财务管理类:' }, { type: 'text', value: '财务管理类:' },
{ type: 'number', value: '', unit: '个', field: 'COL5' }, { type: 'number', value: '', unit: '个', field: 'COL5' },
{ type: 'text', value: ',决策支持类:' }, { type: 'text', value: ',决策支持类:' },
{ type: 'number', value: '', unit: '个', field: 'COL6' }, { type: 'number', value: '', unit: '个', field: 'COL6' },
{ type: 'text', value: ',共享支持类:' }, { type: 'brspace' },
{ type: 'text', value: '共享支持类:' },
{ type: 'number', value: '', unit: '个', field: 'COL7' }, { type: 'number', value: '', unit: '个', field: 'COL7' },
{ type: 'text', value: ',其他:' }, { type: 'text', value: ',其他:' },
{ type: 'number', value: '', unit: '个', field: 'COL8' }, { type: 'number', value: '', unit: '个', field: 'COL8' },
{ type: 'brspace' }, { type: 'brspace' },
{ type: 'text', value: ',(保险业)' }, // { type: 'text', value: '其中(保险业)' },
{ type: 'brspace' }, // { type: 'brspace' },
{ type: 'text', value: '核心业务处理类:' }, // { type: 'text', value: '核心业务处理类:' },
{ type: 'number', value: '', unit: '个', field: 'C20B001' }, // { type: 'number', value: '', unit: '个', field: 'C20B001' },
{ type: 'text', value: ',销售及服务类:' }, // { type: 'text', value: ',销售及服务类:' },
{ type: 'number', value: '', unit: '个', field: 'C20B002' }, // { type: 'number', value: '', unit: '个', field: 'C20B002' },
{ type: 'text', value: ',客户管理类:' }, // { type: 'text', value: ',客户管理类:' },
{ type: 'number', value: '', unit: '个', field: 'COL3' }, // { type: 'number', value: '', unit: '个', field: 'COL3' },
{ type: 'text', value: ',综合管理类:' }, // { type: 'text', value: ',综合管理类:' },
{ type: 'number', value: '', unit: '个', field: 'C20B003' }, // { type: 'number', value: '', unit: '个', field: 'C20B003' },
{ type: 'text', value: ',财务管理类:' }, // { type: 'brspace' },
{ type: 'number', value: '', unit: '个', field: 'COL5' }, // { type: 'text', value: ',财务管理类:' },
{ type: 'text', value: ',内控合规类:' }, // { type: 'number', value: '', unit: '个', field: 'COL5' },
{ type: 'number', value: '', unit: '个', field: 'C20B004' }, // { type: 'text', value: ',内控合规类:' },
{ type: 'text', value: ',其他:' }, // { type: 'number', value: '', unit: '个', field: 'C20B004' },
{ type: 'number', value: '', unit: '个', field: 'COL8' }, // { type: 'text', value: ',其他:' },
// { type: 'number', value: '', unit: '个', field: 'COL8' },
{ type: 'br' }, { type: 'br' },
{ type: 'text', value: '(2)安全保护等级:' }, { type: 'text', value: '(2)安全保护等级:' },
{ type: 'brspace' },
{ type: 'text', value: '1级:' }, { type: 'text', value: '1级:' },
{ type: 'number', value: '', unit: '个', field: 'COL9' }, { type: 'number', value: '', unit: '个', field: 'COL9' },
{ type: 'text', value: ',2级:' }, { type: 'text', value: ',2级:' },
{ type: 'number', value: '', unit: '个', field: 'COL10' }, { type: 'number', value: '', unit: '个', field: 'COL10' },
{ type: 'text', value: ',3级:' }, { type: 'text', value: ',3级:' },
{ type: 'number', value: '', unit: '个', field: 'COL11' }, { type: 'number', value: '', unit: '个', field: 'COL11' },
{ type: 'brspace' },
{ type: 'text', value: '4级:' }, { type: 'text', value: '4级:' },
{ type: 'number', value: '', unit: '个', field: 'COL12' }, { type: 'number', value: '', unit: '个', field: 'COL12' },
{ type: 'text', value: ',5级:' }, { type: 'text', value: ',5级:' },
...@@ -113,14 +118,16 @@ export const tableFormData = [ ...@@ -113,14 +118,16 @@ export const tableFormData = [
{ type: 'text', value: ',纳入同城又纳入异地灾备:' }, { type: 'text', value: ',纳入同城又纳入异地灾备:' },
{ type: 'number', value: '', unit: '个', field: 'COL22' }, { type: 'number', value: '', unit: '个', field: 'COL22' },
{ type: 'brspace' }, { type: 'brspace' },
{ type: 'text', value: '灾难恢复能力等级:' }, { type: 'text', value: '<strong>灾难恢复能力等级:</strong>:' },
{ type: 'brspace' },
{ type: 'text', value: '1级:' }, { type: 'text', value: '1级:' },
{ type: 'number', value: '', unit: '个', field: 'COL23' }, { type: 'number', value: '', unit: '个', field: 'COL23' },
{ type: 'text', value: ',2级:' }, { type: 'text', value: ',2级:' },
{ type: 'number', value: '', unit: '个', field: 'COL24' }, { type: 'number', value: '', unit: '个', field: 'COL24' },
{ type: 'text', value: ',3级:' }, { type: 'text', value: ',3级:' },
{ type: 'number', value: '', unit: '个', field: 'COL25' }, { type: 'number', value: '', unit: '个', field: 'COL25' },
{ type: 'text', value: ',4级:' }, { type: 'brspace' },
{ type: 'text', value: '4级:' },
{ type: 'number', value: '', unit: '个', field: 'COL26' }, { type: 'number', value: '', unit: '个', field: 'COL26' },
{ type: 'text', value: ',5级:' }, { type: 'text', value: ',5级:' },
{ type: 'number', value: '', unit: '个', field: 'COL27' }, { type: 'number', value: '', unit: '个', field: 'COL27' },
...@@ -368,38 +375,41 @@ export const tableFormData = [ ...@@ -368,38 +375,41 @@ export const tableFormData = [
}, },
{ {
name: '灾备情况-区域', name: '灾备情况-区域',
formType: 'checkbox-group', formType: 'combinaform',
field: 'COL80', childs: [
options: ['同城灾备', '异地灾备'], {
extraFields: { label: '备份地域',
同城灾备: [ formType: 'checkbox',
{ field: 'COL80',
label: '同城灾备级别', options: [
field: 'COL81', { label: '同城灾备', value: '同城灾备' },
formType: 'radio', { label: '异地灾备', value: '异地灾备' },
options: [ ]
{ label: '数据级', value: '数据级' }, },
{ label: '系统级', value: '系统级' },
{ label: '应用级', value: '应用级' }, {
], label: '同城灾备级别',
width: '500px', formType: 'radio',
}, field: 'COL81',
], options: [
异地灾备: [ { label: '数据级', value: '数据级' },
{ { label: '系统级', value: '系统级' },
label: '异地灾备级别', { label: '应用级', value: '应用级' },
field: 'COL82', ]
formType: 'radio', },
options: [ {
{ label: '数据级', value: '数据级' }, label: '异地灾备级别',
{ label: '系统级', value: '系统级' }, formType: 'radio',
{ label: '应用级', value: '应用级' }, field: 'COL82',
], options: [
width: '500px', { label: '数据级', value: '数据级' },
}, { label: '系统级', value: '系统级' },
], { label: '应用级', value: '应用级' },
}, ]
},
],
}, },
{ {
name: '建立应急预案', name: '建立应急预案',
formType: 'radio-group', formType: 'radio-group',
......
<template> <template>
<div class="bank-report-table" @click="closeAllTooltips"> <div
class="bank-report-table"
style="height:80vh"
@click="closeAllTooltips"
>
<!-- 加载遮罩层 --> <!-- 加载遮罩层 -->
<div v-if="loading" class="loading-overlay"> <div v-if="loading" class="loading-overlay">
<div class="loading-spinner"> <div class="loading-spinner">
...@@ -7,176 +11,260 @@ ...@@ -7,176 +11,260 @@
<div class="loading-text">加载中...</div> <div class="loading-text">加载中...</div>
</div> </div>
</div> </div>
<!-- 工具栏 -->
<vxe-toolbar> <vxe-toolbar>
<template #buttons> <template #buttons>
<div style="margin:10px"> <div style="margin:10px">
<span style="font-weight: bold"> 填报任务:{{ queryParam?.taskName }} </span> <span class="table-info-text">填报任务:{{ queryParam.taskName }}</span>
<span style="font-weight: bold;margin-left:20px"> 表格:{{ queryParam?.tplCode }} {{ queryParam?.tplName }}</span> <span class="table-info-text" style="margin-left:20px">
表格:{{ queryParam.tplCode }} {{ queryParam.tplName }}
</span>
</div> </div>
</template> </template>
<template #tools v-if="!queryParam.comfrom"> <template #tools v-if="!queryParam.comfrom">
<vxe-button status="primary" icon="vxe-icon-edit" @click="handleOpenHistoryDrawer()" :disabled="loading">近5年数据填报</vxe-button> <vxe-button
<vxe-button status="primary" icon="vxe-icon-edit" @click="validateData()" :disabled="loading">校验</vxe-button> status="primary"
<vxe-button status="primary" icon="vxe-icon-save" @click="saveBatch()" :disabled="loading">保存</vxe-button> icon="vxe-icon-edit"
@click="handleOpenHistoryDrawer"
:disabled="loading"
>
近5年数据填报
</vxe-button>
<vxe-button
status="primary"
icon="vxe-icon-save"
@click="validateData"
>
校验
</vxe-button>
<vxe-button
status="primary"
icon="vxe-icon-save"
@click="saveBatch"
>
保存
</vxe-button>
</template> </template>
</vxe-toolbar> </vxe-toolbar>
<!-- 主表格 -->
<vxe-table <vxe-table
border border
ref="tableRef" ref="tableRef"
height="auto"
show-header-overflow show-header-overflow
:data="tableFormData" :data="tableFormData"
:column-config="{ resizable: true }" :column-config="{ resizable: true }"
:row-config="{ resizable: true }" :row-config="{ resizable: true }"
height="auto"
class="custom-table" class="custom-table"
:merge-cells="mergeCells"
:loading="loading"
> >
<vxe-column field="serialNumber" title="序号" width="50"></vxe-column> <vxe-column field="serialNumber" title="序号" width="50"></vxe-column>
<vxe-column field="project" title="项目" width="40">
<template #default="{ row }"> <vxe-column field="project" title="项目" width="200">
<span style="font-weight: bold;size:20px">{{ row.project }}</span>
</template>
</vxe-column>
<vxe-column field="project2" title="" width="120">
<template #default="{ row }"> <template #default="{ row }">
<span style="font-weight: bold;size:20px">{{ row.project2 }}</span> <span class="project-name">{{ row.project }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="content" title="内容" row-resize>
<vxe-column field="content" title="内容">
<template #default="{ row }"> <template #default="{ row }">
<div class="content-cell"> <div class="content-cell">
<!-- 多列表格 -->
<template v-if="row.type === 'MultiColumnTable'"> <template v-if="row.type === 'MultiColumnTable'">
<MultiColumnTable <MultiColumnTable
:title="row.project" :title="row.project"
:records="row.datas" :columnsPerRow="1"
:records="[]"
:fields="row.content" :fields="row.content"
:ref="(el) => setMultiColumnTableRef(el, row.code)" :ref="(el) => setMultiColumnTableRef(el, row.code)"
style="margin:0px;padding:0px" style="margin:0px;padding:0px"
/> />
</template> </template>
<!-- 附件表格 -->
<template v-else-if="row.type === 'AttachTable'"> <template v-else-if="row.type === 'AttachTable'">
<AttachTable <AttachTable
:disabled="!row.hasRight"
:title="row.project" :title="row.project"
:records="row.datas" :records="row.datas"
:fields="row.content" :fields="row.content"
:pcode="row.code" :pcode="row.code"
:calcSum="row.calcSum"
:showFooter="row.showFooter"
:ref="(el) => setAttachTableRef(el, row.code)" :ref="(el) => setAttachTableRef(el, row.code)"
:disabled="!row.hasRight"
style="margin:0px;padding:0px" style="margin:0px;padding:0px"
/> />
</template> </template>
<template v-else-if="row.type === 'ExportTable'"> <!-- Vxe表格 -->
<ExportTable <template v-else-if="row.type === 'VxeTable'">
:disabled="!row.hasRight" <MyVxeTable1
:title="row.project" :title="row.project"
:records="row.datas" :data="row.data"
:fields="row.content" :columns="row.columns"
:pcode="row.code" :pcode="row.code"
:ref="(el) => setExportTableRef(el, row.code)" :footerData="row.footerData"
:showFooter="row.showFooter"
:ref="(el) => setMyVxeTableRef(el, row.code)"
:disabled="!row.hasRight"
style="margin:0px;padding:0px" style="margin:0px;padding:0px"
/> />
</template> </template>
<!-- 普通字段渲染 -->
<template v-else> <template v-else>
<template v-for="(item, index) in row.content" :key="index"> <template v-for="(item, index) in row.content" :key="index">
<!-- 文本类型 -->
<span v-if="item.type === 'text'" v-html="item.value"></span> <span v-if="item.type === 'text'" v-html="item.value"></span>
<!-- 换行 -->
<span v-else-if="item.type === 'br'"><br></span> <span v-else-if="item.type === 'br'"><br></span>
<!-- 带缩进的换行 -->
<span v-else-if="item.type === 'brspace'"> <span v-else-if="item.type === 'brspace'">
<br> <br>
<template v-for="i in item.value||1" :key="i"> <template v-for="i in (item.value || 1)" :key="i">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</template> </template>
</span> </span>
<!-- 单选组 -->
<span v-else-if="item.type === 'radio-group'" class="radio-group"> <span v-else-if="item.type === 'radio-group'" class="radio-group">
<vxe-radio-group v-model="formData[row.code +'_'+ item.field]" :disabled="!item.hasRight"> <vxe-radio-group
v-model="formData[getFieldKey(row.code, item.field)]"
:disabled="!item.hasRight || isRadioDisabled(row.code, item)"
>
<vxe-radio <vxe-radio
v-for="(opt, optIndex) in item.options" v-for="(opt, optIndex) in item.options"
:key="optIndex" :key="optIndex"
:label="opt" :label="opt"
>{{ opt }}</vxe-radio
> >
{{ opt }}
</vxe-radio>
</vxe-radio-group> </vxe-radio-group>
</span> </span>
<div v-else-if="item.type === 'checkbox-group'" class="checkbox-group">
<!-- 多选组(带其他选项) -->
<div v-else-if="item.type === 'checkbox-group'">
<span class="checkbox-group"> <span class="checkbox-group">
<vxe-checkbox-group v-model="formData[row.code+'_'+item.field]" :disabled="!item.hasRight"> <vxe-checkbox-group
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt"> v-model="formData[getFieldKey(row.code, item.field)]"
{{ opt }} :disabled="!item.hasRight"
</vxe-checkbox> >
<template v-if="item.otereField"> <vxe-checkbox
<vxe-checkbox style="margin:0px" label="其他"> 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="其他" :disabled="!item.hasRight">
其他: 其他:
</vxe-checkbox> </vxe-checkbox>
<vxe-input v-model="formData[row.code + '_'+ item.otereField]" style="width: 100px;"> <vxe-input
</vxe-input> v-model="formData[getFieldKey(row.code, item.otherField)]"
</template> :disabled="!isOtherFieldEnabled(row.code, item) || !item.hasRight"
</vxe-checkbox-group> style="width: 50%"
</span> @blur="handleInputBlur(row.code, item.otherField, item.matchedFormula?.formula)"
/>
</div>
</template>
</vxe-checkbox-group>
<!-- 其他选项 -->
</span>
</div> </div>
<template v-else-if="item.type === 'textarea'">
<vxe-textarea <!-- 多选带输入框 -->
:disabled="!item.hasRight" <div v-else-if="item.type === 'checkboxAndInput'">
v-model="formData[row.code +'_'+ item.field]" <span style="margin-left:20px">
size="mini" <vxe-checkbox-group
class="table-input" v-model="formData[getFieldKey(row.code, item.field)]"
rows="1" :disabled="!item.hasRight"
style="width:300px" >
> <vxe-checkbox
</vxe-textarea> v-for="(opt, optIndex) in item.options"
</template> :key="optIndex"
:label="opt"
:disabled="!item.hasRight"
style="margin-left: 0px;display: block;"
>
{{ opt }}
<vxe-input
type="number"
v-model="formData[getFieldKey(row.code, item.optionItemField[optIndex])]"
:disabled="!item.hasRight"
style="width: 100px;margin: 0px;"
@blur="handleInputBlur(row.code, item.optionItemField[optIndex], item.matchedFormula?.formula)"
/>
<span class="unit"> {{ item.optionItemUint }}</span>
</vxe-checkbox>
</vxe-checkbox-group>
</span>
</div>
<!-- 普通输入框 -->
<template v-else> <template v-else>
<div class="input-wrapper"> <div class="input-wrapper">
<vxe-input <vxe-input
:disabled="!item.hasRight" :disabled="!item.hasRight || isInputDisabled(row.code, item)"
:type="item.type" :type="item.type"
v-model="formData[row.code +'_'+ item.field]" v-model="formData[getFieldKey(row.code, item.field)]"
size="mini" size="mini"
class="table-input" class="table-input"
:style="{width:item.width}" :style="item.style"
:status="getInputStatus(row.code, item.field)"
@blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)" @blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
> />
</vxe-input> <span class="unit">{{ item.unit }}</span>
<span class="unit"> {{ item.unit }}</span>
<span <!-- 帮助图标 -->
v-if="showHelpIcon(row.code, item.field, item)" <span
class="help-icon" v-if="item.hasValidFormula"
class="help-icon"
@click.stop="toggleTooltip(row.code, item.field)" @click.stop="toggleTooltip(row.code, item.field)"
title="点击查看校验规则"
> >
? ?
</span> </span>
<span
v-if="getInputStatus(row.code, item.field) === 'error'" <!-- 错误图标 -->
class="error-icon" <span
v-if="inputErrors[getFieldKey(row.code, item.field)]"
class="error-icon"
@click.stop="toggleErrorTooltip(row.code, item.field)" @click.stop="toggleErrorTooltip(row.code, item.field)"
> >
<i class="vxe-icon-error"></i> <i class="vxe-icon-error"></i>
</span> </span>
<div <!-- 错误提示 -->
v-if="showTooltip && hoveredKey === row.code +'_'+ item.field" <div
class="tooltip" v-if="showErrorTooltip && hoveredErrorKey === getFieldKey(row.code, item.field)"
class="error-tooltip"
@click.stop @click.stop
> >
{{ item.matchedFormula?.des || '暂无校验规则' }} {{ inputErrors[getFieldKey(row.code, item.field)].message }}
<br>
<span style="color: #ffcccc;">
公式: {{ inputErrors[getFieldKey(row.code, item.field)].formula }}
</span>
<template v-if="inputErrors[getFieldKey(row.code, item.field)].error">
<br>
<span style="color: #ff9999; font-size: 11px;">
错误: {{ inputErrors[getFieldKey(row.code, item.field)].error }}
</span>
</template>
</div> </div>
<div <!-- 校验规则提示 -->
v-if="showErrorTooltip && hoveredErrorKey === row.code +'_'+ item.field" <div
class="error-tooltip" v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)"
class="tooltip"
@click.stop @click.stop
> >
{{ getErrorMessage(row.code, item.field) }} {{ item.matchedFormula?.des || '暂无校验规则' }}
<br>
<span style="color: #ffcccc;">
公式: {{ item.matchedFormula?.formula }}
</span>
</div> </div>
</div> </div>
</template> </template>
...@@ -185,17 +273,18 @@ ...@@ -185,17 +273,18 @@
</div> </div>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="remarks" title="备注" width="60">
<!-- 备注列 -->
<vxe-column field="remarks" title="备注" width="200">
<template #default="{ row }"> <template #default="{ row }">
<vxe-textarea :disabled="row.remarks.hasRight" :rows="row.remarks.rows" v-model="formData[row.code+'_'+row.remarks.field]"> <vxe-textarea
</vxe-textarea> :rows="row.remarks.rows"
v-model="formData[getFieldKey(row.code, row.remarks.field)]"
/>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<!-- 历史填报检查组件 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @closeDrawer="closeHistoryDrawer"/>
<!-- 校验抽屉 --> <!-- 校验抽屉 -->
<ValidationDrawer <ValidationDrawer
ref="validationDrawerRef" ref="validationDrawerRef"
...@@ -203,1043 +292,911 @@ ...@@ -203,1043 +292,911 @@
:tableFormData="tableFormData" :tableFormData="tableFormData"
@validationResultClick="handleValidationResultClick" @validationResultClick="handleValidationResultClick"
/> />
</div>
<!-- 历史填报抽屉 -->
<HistoryFillCheck
ref="historyFillCheckRef"
v-model="historyDrawerVisible"
@closeDrawer="closeHistoryDrawer"
/>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { VxeUI, VxeTableInstance } from 'vxe-table'
import 'vxe-table/lib/style.css'
import { useRoute } from 'vue-router'
// 组件导入
import MultiColumnTable from '../tableComponents/MultiColumnTable.vue' import MultiColumnTable from '../tableComponents/MultiColumnTable.vue'
import AttachTable from '../tableComponents/AttachTable.vue' import AttachTable from '../tableComponents/AttachTable.vue'
import MyVxeTable1 from '../tableComponents/MyVxeTable.vue'
import HistoryFillCheck from './check/HistoryFillCheck.vue' import HistoryFillCheck from './check/HistoryFillCheck.vue'
import ValidationDrawer from './check/ValidationDrawer.vue' import ValidationDrawer from './check/ValidationDrawer.vue'
import { tableFormData } from '../../data/tb3.data'; // 数据导入
import { ref, reactive, nextTick, onMounted, toRaw, computed, } from 'vue' import { tableFormData } from '../../data/tb3.data'
import { VxeUI,VxeTablePropTypes,VxeToolbarPropTypes } from 'vxe-table'
import { batchSaveOrUpdate, queryRecord,batchSaveOrUpdateBeforeDelete } from '../../record/BaosongTaskRecord.api' // API 导入
import { findUserRightForTplItem } from '../../alloc/BaosongTaskAlloc.api' import {
batchSaveOrUpdate,
queryRecord,
batchSaveOrUpdateBeforeDelete
} from '../../record/BaosongTaskRecord.api'
import {
queryAllTplItemForUser,
findUserRightForTplItem
} from '../../alloc/BaosongTaskAlloc.api'
import { allTplItems } from '../../tpl/BaosongTplItem.api' import { allTplItems } from '../../tpl/BaosongTplItem.api'
import { getTblvalidFormula } from '../../tpl/BaosongDataValid.api' import { getTblvalidFormula } from '../../tpl/BaosongDataValid.api'
import { useRoute } from 'vue-router'; // 类型定义
interface QueryParam {
taskId: number
taskName: string
tplId: number
tplName: string
tplCode: string
comfrom: string
}
interface FormDataItem {
id: number | null
taskid: number
tplid: number
itemid?: number
itempid?: number
content: string
tplcode: string
rind: number
}
interface FormulaItem {
formula: string
des: string
[key: string]: any
}
interface ContentItem {
type: string
field: string
value?: any
hasRight?: boolean
hasValidFormula?: boolean
matchedFormula?: FormulaItem
relatedFiled?: string
otherField?: string
optionItemField?: string[]
[key: string]: any
}
interface TableRow {
code: string
project: string
type?: string
content: ContentItem[] | any
remarks: { rows: number; field: string }
hasRight?: boolean
[key: string]: any
}
interface InputError {
message: string
formula: string
error?: string
}
const historyFillCheckRef = ref(); interface TplItemMapValue {
const validationDrawerRef = ref(); pid: number
const historyDrawerVisible = ref(false); itemid: number
formTp: string
code: string
}
const route = useRoute(); // 路由和组件引用
const tableRef = ref(); const route = useRoute()
const tplItemMap = ref({}); const tableRef = ref<VxeTableInstance>()
const userAllocItems = ref([]) const historyFillCheckRef = ref()
const validFormula = ref([]) const validationDrawerRef = ref()
const validationResults = ref<Record<string, string>>({})
const validationMessages = ref<Record<string, string>>({})
const formData = reactive({}); // 响应式状态
const formValues = ref<FormData[]>([]) const loading = ref(true)
const historyDrawerVisible = ref(false)
const drawerVisible = ref(false)
const showTooltip = ref(false) const showTooltip = ref(false)
const hoveredKey = ref('') const hoveredKey = ref('')
const showErrorTooltip = ref(false) const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('') const hoveredErrorKey = ref('')
const isInitialized = ref(false)
const fieldKeys = computed(() => Object.keys(formData)); // 数据状态
const queryParam = ref<QueryParam>({
taskId: -1,
taskName: '',
tplId: -1,
tplName: '',
tplCode: '',
comfrom: ''
})
const getFieldKey = (rowCode: string, field: string): string => { const formData = reactive<Record<string, any>>({})
return `${rowCode}_${field}`; const formValues = ref<FormDataItem[]>([])
}; const inputErrors = ref<Record<string, InputError>>({})
const userAllocItems = ref<string[]>([])
const validFormula = ref<FormulaItem[]>([])
const tplItemMap = ref<Record<string, TplItemMapValue>>({})
const showHelpIcon = (rowCode: string, field: string, item: any): boolean => { // 子组件引用
if (item.hasValidFormula !== undefined) { const childMultiTableRefs = ref<Record<string, any>>({})
return item.hasValidFormula === true; const childAttachTableRefs = ref<Record<string, any>>({})
} const childMyVexTableRefs = ref<Record<string, any>>({})
const key = getFieldKey(rowCode, field);
const hasRight = userAllocItems.value.includes(key);
if (!hasRight) return false;
const formulaResult = findEarliestFormulaForField(validFormula.value, field);
return !!formulaResult.formula;
};
// 校验结果抽屉相关 // 计算属性
const drawerVisible = ref(false) const fieldKeys = computed(() => Object.keys(formData))
const validationResultsList = ref<any[]>([])
interface FormulaItem { // 工具函数
formula: string; const getFieldKey = (rowCode: string, field: string): string => {
des: string; return `${rowCode}_${field}`
[key: string]: any;
} }
interface InputError { const isRadioDisabled = (rowCode: string, item: ContentItem): boolean => {
message: string; if (!item.relatedFiled) return false
formula: string; const relatedKey = getFieldKey(rowCode, item.relatedFiled)
error?: string; return formData[relatedKey] !== '是'
} }
interface ValidationResult { const isInputDisabled = (rowCode: string, item: ContentItem): boolean => {
fieldTitle: string if (!item.relatedFiled) return false
fieldName: string const relatedKey = getFieldKey(rowCode, item.relatedFiled)
formula: string return formData[relatedKey] !== '是'
isValid: boolean
resultMessage: string
process?: string[]
}
const queryParam = ref({
taskId:-1,
taskName:'',
tplId:-1,
tplName:'',
tplCode:'',
comfrom:''
})
interface FormData {
id: number | null
taskid: number
tplid: number
itemid?: number
itempid?:number
content: string
tplcode:string
rind:number
} }
// 初始化loading状态 const isOtherFieldEnabled = (rowCode: string, item: ContentItem): boolean => {
const loading = ref(true) const checkboxKey = getFieldKey(rowCode, item.field)
const checkboxValue = formData[checkboxKey] || []
return checkboxValue.includes('其他')
}
onMounted(async ()=>{ // 初始化
if (route.query.taskId) { onMounted(async () => {
queryParam.value.taskId = Number(route.query.taskId); await initPage()
} })
if (route.query.taskName) {
queryParam.value.taskName = String(route.query.taskName);
}
if (route.query.tplId) {
queryParam.value.tplId = Number(route.query.tplId);
}
if (route.query.tplName) {
queryParam.value.tplName = String(route.query.tplName);
}
if (route.query.tplCode) {
queryParam.value.tplCode = String(route.query.tplCode);
}
if (route.query.comfrom) {
queryParam.value.comfrom = String(route.query.comfrom);
}
const initPage = async () => {
try { try {
userAllocItems.value = await findUserRightForTplItem({ loading.value = true
tplid:queryParam.value.tplId, await initQueryParams()
taskid:queryParam.value.taskId await Promise.all([
}); loadUserRights(),
loadValidationFormulas(),
validFormula.value = await getTblvalidFormula({ loadTemplateItems()
tplid:queryParam.value.tplId, ])
}); await setFormItemRight()
await loadTableData()
await setFormItemRight(); } catch (error: any) {
await setTplItemMap() showErrorMessage(`初始化页面失败: ${error.message}`)
await setData();
// 标记初始化完成
isInitialized.value = true
// 延迟刷新一次,确保问号图标显示
setTimeout(() => {
refreshHelpIcons()
}, 300)
} catch (error) {
VxeUI.modal.message({
content: `初始化页面失败: ${error.message}`,
status: 'error'
});
} finally { } finally {
loading.value = false loading.value = false
} }
})
const setFormItemRight = async () => {
tableFormData.forEach(row => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach(item => {
if (item.field) {
let tmpKey = `${row.code}_${item.field}`;
item["hasRight"] = userAllocItems.value.indexOf(tmpKey)>-1
item["hasValidFormula"] = validFormula.value?.length > 0 &&
validFormula.value.some((f: any) => {
const formulaText = (f.formula || '').toString()
return formulaText.includes(item.field)
})
}
});
}
if (row.remarks && row.remarks.field) {
let tmpKey = `${row.code}_${row.remarks.field}`;
row.remarks["hasRight"] = userAllocItems.value.indexOf(tmpKey)>-1
row.remarks["hasValidFormula"] = validFormula.value?.length > 0 &&
validFormula.value.some((f: any) => {
const formulaText = (f.formula || '').toString()
return formulaText.includes(row.remarks.field)
})
}
});
} }
const saveBatch = async () => { const initQueryParams = () => {
try { const { taskId, taskName, tplId, tplName, tplCode,comfrom } = route.query
// 保存时也显示loading
const saveLoading = ref(true) if (taskId) queryParam.value.taskId = Number(taskId)
formValues.value = [] if (taskName) queryParam.value.taskName = String(taskName)
if (tplId) queryParam.value.tplId = Number(tplId)
for (const strKey in formData) { if (tplName) queryParam.value.tplName = String(tplName)
const valData = formData[strKey] if (tplCode) queryParam.value.tplCode = String(tplCode)
if (valData) { if (comfrom) queryParam.value.comfrom = String(comfrom)
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' })
//await setData()
} else {
VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' })
}
} catch (error) {
VxeUI.modal.message({ content: `保存失败: ${error.message}`, status: 'error' })
} finally {
loading.value = false
}
} }
const childMultiTableRefs = ref({}) const loadUserRights = async () => {
const setMultiColumnTableRef = (el: any, index: string) => { userAllocItems.value = await findUserRightForTplItem({
if (el) {
childMultiTableRefs.value[index] = el;
}
};
const getAllMultiTableFormData = async () => {
for (const pcode in childMultiTableRefs.value) {
const child = childMultiTableRefs.value[pcode];
if (child) {
const datas = child.getFormData()
let rind = 0
for(const obj of datas) {
rind++
Object.keys(obj).forEach(code => {
setFormValues(pcode,code,obj[code],rind)
});
}
}
}
};
const childAttachTableRefs = ref({})
const setAttachTableRef = (el: any, index: string) => {
if (el) {
childAttachTableRefs.value[index] = el; // 保存子组件实例
}
};
const childExportTableRefs = ref({})
const setExportTableRef = (el: any, index: string) => {
if (el) {
childExportTableRefs.value[index] = el; // 保存子组件实例
}
};
const getAttachTableFormData = async () => {
for (const pcode in childAttachTableRefs.value) {
const child = childAttachTableRefs.value[pcode];
if (child) {
const datas = child.getFormData()
for(const code in datas) {
await setFormValues(pcode,code,datas[code],1)
}
}
}
};
const setFormValues = async (pcode,code,valData,rind) => {
if(!valData) return;
let vals = Array.isArray(valData) ? valData.join(',') : valData
let strKey = pcode+"_"+code
const item = tplItemMap.value[strKey];
const { pid, itemid } = item ?? {};
let tempForm: FormData = {
id: null,
rind:rind,
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId, tplid: queryParam.value.tplId,
tplcode: code, taskid: queryParam.value.taskId
itemid: itemid, })
itempid: pid,
content:vals
};
formValues.value.push(tempForm)
} }
async function setData() { const loadValidationFormulas = async () => {
try { validFormula.value = await getTblvalidFormula({
loading.value = true; // 开始加载 tplid: queryParam.value.tplId
const taskId = queryParam.value.taskId; })
const tplid = queryParam.value.tplId;
Object.keys(formData).forEach(key => delete formData[key]);
await setTplItemMap();
const recordData = await queryRecord({ taskid: taskId, tplid: tplid });
const valueObj = {};
for (const data of recordData) {
const key = `${data.itempid}_${data.itemid}_${data.rind}`;
valueObj[key] = data.content;
}
for (const row of tableFormData) {
if(!row.type&&row.content) {
for (const cdata of row.content) {
if(cdata.field&&cdata.field.length>0) {
const strKey = `${row.code}_${cdata.field}`;
const item = tplItemMap.value[strKey];
if (!item) continue;
const { pid, itemid, formTp } = item;
const dataVal = valueObj[`${pid}_${itemid}_1`];
if (!dataVal) continue;
if (formTp === 'checkbox') {
formData[strKey] = dataVal?.split(",") || [];
} else {
formData[strKey] = dataVal || "";
}
if(cdata.type&&cdata.type=="checkboxAndInput"){
for (const itemField of cdata.optionItemField) {
const strItemKey = `${row.code}_${itemField}`;
const item2 = tplItemMap.value[strItemKey];
if (!item2) continue;
const { pid:pid2, itemid:itemid2 } = item2;
const dataVal2 = valueObj[`${pid2}_${itemid2}_1`];
formData[strItemKey] = dataVal2 || "";
}
}
}
}
} else if(row.type=="AttachTable"&& row.datas) {
const curAttachTable = childAttachTableRefs.value[row.code];
const attachTableData = {};
for (const cdata of row.datas) {
Object.keys(cdata).forEach(key => {
if(cdata[key]["field"]){
const strKey = `${row.code}_${cdata[key]["field"]}`;
const item = tplItemMap.value[strKey];
if (item) {
const { pid, itemid, formTp, code } = item;
const dataVal = valueObj[`${pid}_${itemid}_1`];
if (dataVal) {
if (formTp === 'checkbox') {
attachTableData[code] = dataVal?.split(",") || [];
} else {
attachTableData[code] = dataVal || "";
}
}
}
}
});
}
curAttachTable.setFormData(attachTableData);
} else if(row.type=="MultiColumnTable"&& row.content) {
const curMultiTable = childMultiTableRefs.value[row.code];
const rowsMap = {};
for (const cdata of row.content) {
if(cdata.field&&cdata.field.length>0) {
const strKey = `${row.code}_${cdata.field}`;
const tmpData = tplItemMap.value[strKey];
if (!tmpData) continue;
const { pid, itemid, code } = tmpData;
const valKeys = Object.keys(valueObj).filter(k =>
k.startsWith(`${pid}_${itemid}_`)
);
for (const valKey of valKeys) {
const parts = valKey.split('_');
const rind = parts[2];
if (!rowsMap[rind]) {
rowsMap[rind] = {};
}
rowsMap[rind][code] = valueObj[valKey];
}
}
}
const tableDatas = Object.values(rowsMap);
curMultiTable.setFormData(tableDatas);
}
}
} catch (error) {
console.log(error)
VxeUI.modal.message({
content: `加载数据失败:`,
status: 'error'
});
} finally {
loading.value = false; // 结束加载
}
} }
async function setTplItemMap() {
try {
loading.value = true;
const tplid = queryParam.value.tplId
const tplItem = await allTplItems({
tplid: tplid,
})
tplItem.forEach(item => {
let strKey = item.pcode + "_" + item.xmlcode;
tplItemMap.value[strKey] = {
pid:item.pid,
itemid:item.id,
formTp:item.formTp,
code:item.xmlcode,
title:item.title||item.name,
isDisable:!userAllocItems.value.some(itemid=>itemid==item.id)
}
})
} catch (error) { const loadTemplateItems = async () => {
VxeUI.modal.message({ content: `加载数据失败: ${error.message}`, status: 'error' }) const items = await allTplItems({
} finally { tplid: queryParam.value.tplId
loading.value = false; // 结束加载 })
}
tplItemMap.value = items.reduce((acc: Record<string, TplItemMapValue>, item) => {
const key = `${item.pcode}_${item.xmlcode}`
acc[key] = {
pid: item.pid,
itemid: item.id,
formTp: item.formTp,
code: item.xmlcode
}
return acc
}, {})
} }
const setFormItemRight = () => {
const mergeCells = ref<VxeTablePropTypes.MergeCells>([ tableFormData.forEach((row: TableRow) => {
{ row: 0, col: 1, rowspan: 9, colspan: 1 },
{ row: 9, col: 1, rowspan: 1, colspan: 2 },
{ row: 10, col: 1, rowspan: 1, colspan: 2 },
{ row: 11, col: 1, rowspan: 2, colspan: 1 },
{ row: 11, col: 3, rowspan: 1, colspan: 2 },
{ row: 10, col: 3, rowspan: 1, colspan: 2 },
{ row: 9, col: 3, rowspan: 1, colspan: 2 },
])
const refreshHelpIcons = () => {
console.log('刷新帮助图标...');
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) { if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => { row.content.forEach((item: ContentItem) => {
if (item.field) { if (item.field) {
const key = getFieldKey(row.code, item.field); const key = getFieldKey(row.code, item.field)
const hasRight = userAllocItems.value.includes(key); item.hasRight = userAllocItems.value.includes(key)
if (hasRight) { if (item.hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field); const { formula } = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formulaResult.formula; item.hasValidFormula = !!formula
item.matchedFormula = formulaResult.formula || null; item.matchedFormula = formula
} else {
item.hasValidFormula = false;
item.matchedFormula = null;
} }
} }
}); })
} }
}); })
}
nextTick(() => {
console.log('帮助图标刷新完成');
});
};
const findEarliestFormulaForField = ( // 保存相关
formulas: FormulaItem[], const saveBatch = async () => {
field: string try {
): { formula: FormulaItem | null, index: number } => { if (!await validateForm()) return
let result = { formula: null, index: Infinity };
formulas?.forEach((f: FormulaItem) => {
const text = f.formula || '';
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g');
const match = regex.exec(text);
if (match && match.index < result.index) { await prepareFormData()
result = { formula: f, index: match.index };
if (formValues.value.length === 0) {
showWarningMessage('没有需要保存的数据')
return
} }
});
await batchSaveOrUpdateBeforeDelete(formValues.value)
return result; showSuccessMessage('保存成功')
}; } catch (error: any) {
showErrorMessage(`保存失败: ${error.message}`)
const validateData = () => { }
drawerVisible.value = true; }
validationDrawerRef.value.setValidateData(validFormula.value, formData);
};
const handleInputBlur = (rowCode: string, field: string, formula?: string) => { const validateForm = async (): Promise<boolean> => {
if (!formula) return; const $table = tableRef.value
if (!$table) return true
const key = getFieldKey(rowCode, field);
const value = formData[key];
if (!value || value === '') { const checkResult = $table.validate()
delete validationResults.value[key]; if (!checkResult) {
delete validationMessages.value[key]; showErrorMessage('表单验证失败,请检查填写内容')
return; return false
} }
return true
validateFieldFormula(rowCode, field, formula); }
};
const validateFieldFormula = (rowCode: string, field: string, formula: string) => { const prepareFormData = async () => {
const key = getFieldKey(rowCode, field); formValues.value = []
try { // 处理主表单数据
const expression = buildExpression(formula, rowCode); for (const [key, value] of Object.entries(formData)) {
const isValid = eval(expression); if (value !== undefined && value !== null && value !== '') {
if (!isValid) { const [pcode, code] = key.split('_')
validationResults.value[key] = 'error'; await addFormValue(pcode, code, value, 1)
validationMessages.value[key] = "公式校验失败,请检查填写内容";
} else {
delete validationResults.value[key];
delete validationMessages.value[key];
} }
} catch (error: any) {
validationResults.value[key] = 'error';
validationMessages.value[key] = "公式校验失败,请检查填写内容";
} }
};
// 处理子表格数据
await Promise.all([
collectAttachTableData(),
collectMultiTableData(),
collectVxeTableData()
])
}
const buildExpression = (formula: string, rowCode: string): string => { const addFormValue = async (pcode: string, code: string, value: any, rind: number) => {
const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []; if (!value) return
const uniqueFields = [...new Set(fieldNames.filter(f =>
!['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase())
))];
uniqueFields.sort((a, b) => b.length - a.length); const stringValue = Array.isArray(value) ? value.join(',') : String(value)
const key = getFieldKey(pcode, code)
const item = tplItemMap.value[key]
let expression = formula; if (!item) return
for (const fieldName of uniqueFields) {
const key = getFieldKey(rowCode, fieldName); const formItem: FormDataItem = {
const value = formData[key]; id: null,
if (value !== undefined) { rind,
expression = expression.replace( taskid: queryParam.value.taskId,
new RegExp(`\\b${fieldName}\\b`, 'g'), tplid: queryParam.value.tplId,
`Number(${value})` tplcode: code,
); itemid: item.itemid,
} itempid: item.pid,
content: stringValue
} }
return expression; formValues.value.push(formItem)
};
const handleInputChange = (rowCode: string, itemField: string) => {
const key = `${rowCode}_${itemField}`
validateInput(key)
} }
const getInputStatus = (rowCode: string, itemField: string): 'default' | 'error' => { // 数据收集函数
const key = `${rowCode}_${itemField}` const collectAttachTableData = async () => {
return validationResults.value[key] || '' for (const [pcode, child] of Object.entries(childAttachTableRefs.value)) {
if (child) {
const datas = await child.getFormData()
for (const [code, value] of Object.entries(datas)) {
await addFormValue(pcode, code, value, 1)
}
}
}
} }
const getErrorMessage = (rowCode: string, itemField: string): string => { const collectMultiTableData = async () => {
const key = `${rowCode}_${itemField}` for (const [pcode, child] of Object.entries(childMultiTableRefs.value)) {
return validationMessages.value[key] || '' if (child) {
const datas = await child.getFormData()
let rind = 0
for (const obj of datas) {
rind++
for (const [code, value] of Object.entries(obj)) {
await addFormValue(pcode, code, value, rind)
}
}
}
}
} }
const toggleTooltip = (rowCode: string, itemField: string) => { const collectVxeTableData = async () => {
const key = `${rowCode}_${itemField}` for (const [pcode, child] of Object.entries(childMyVexTableRefs.value)) {
if (hoveredKey.value === key) { if (child) {
showTooltip.value = false const formData = await child.getFormData()
hoveredKey.value = '' let rind = 0
} else { // 表格数据
showTooltip.value = true for (const obj of formData.datas) {
hoveredKey.value = key rind++
for (const [code, value] of Object.entries(obj)) {
await addFormValue(pcode, code, value, rind)
}
}
if (formData.footer) {
for (const [code, value] of Object.entries(formData.footer)) {
await addFormValue(formData.footerPcode, code, value, 1)
}
}
}
} }
} }
const getValidationRule = (rowCode: string, itemField: string): string => { // 数据加载
if (!validFormula.value || validFormula.value.length === 0) { const loadTableData = async () => {
return ''
}
try { try {
const formulaItems = validFormula.value loading.value = true
const field = itemField
const targetFormula = formulaItems.find((f: any) => { // 清空现有数据
const formulaText = (f.formula || '').toString() Object.keys(formData).forEach(key => delete formData[key])
const regex = new RegExp(`\\b${field}\\b`, 'gi')
return regex.test(formulaText) const recordData = await queryRecord({
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId
}) })
if (targetFormula) { // 转换记录数据为便于查找的结构
return targetFormula.des || `验证规则: ${targetFormula.formula}` const valueMap = recordData.reduce((acc: Record<string, string>, data) => {
} const key = `${data.itempid}_${data.itemid}_${data.rind}`
acc[key] = data.content
return acc
}, {})
return '' // 填充表格数据
} catch (error) { await fillTableData(valueMap)
console.error('获取验证规则失败:', error) } catch (error: any) {
return '获取验证规则失败' showErrorMessage(`加载数据失败: ${error.message}`)
} finally {
loading.value = false
} }
} }
const closeTooltip = (event: Event) => { const fillTableData = async (valueMap: Record<string, string>) => {
const target = event.target as HTMLElement for (const row of tableFormData) {
if (!target.closest('.help-icon')) { if (!row.type && row.content) {
showTooltip.value = false await fillSimpleFields(row, valueMap)
hoveredKey.value = '' } else if (row.type === 'AttachTable' && row.datas) {
await fillAttachTable(row, valueMap)
} else if (row.type === 'MultiColumnTable' && row.content) {
await fillMultiColumnTable(row, valueMap)
} else if (row.type === 'VxeTable' && row.columns) {
await fillVxeTable(row, valueMap)
}
} }
} }
const formatJSON = (obj: any): string => { const fillSimpleFields = async (row: TableRow, valueMap: Record<string, string>) => {
if (!obj) { if (!row.content || !Array.isArray(row.content)) return
return ''
} for (const item of row.content) {
try { if (!item.field) continue
return JSON.stringify(obj, null, 2)
} catch (error) { const key = getFieldKey(row.code, item.field)
return JSON.stringify({ error: '序列化失败' }) const tplItem = tplItemMap.value[key]
if (!tplItem) continue
const { pid, itemid, formTp } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal === undefined) continue
if (formTp === 'checkbox') {
formData[key] = dataVal.split(',').filter(Boolean)
} else {
formData[key] = dataVal
}
// 处理特殊字段类型
if (item.type === 'checkboxAndInput' && item.optionItemField) {
await fillCheckboxInputFields(row, item, valueMap)
}
if (item.otherField) {
await fillOtherField(row, item, valueMap)
}
} }
} }
const onDrawerShow = () => { const fillCheckboxInputFields = async (
// 抽屉显示时,设置容器高度为屏幕高度 row: TableRow,
setTimeout(() => { item: ContentItem,
const drawer = document.querySelector('.vxe-drawer--body') valueMap: Record<string, string>
if (drawer) { ) => {
const availableHeight = window.innerHeight - 280 // 减去顶部和底部的空间 for (const field of item.optionItemField || []) {
const resultList = drawer.querySelector('.result-list') const key = getFieldKey(row.code, field)
if (resultList) { const tplItem = tplItemMap.value[key]
resultList.classList.add('result-list--scrolling') if (!tplItem) continue
resultList.style.maxHeight = `${availableHeight}px`
} const { pid, itemid } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal !== undefined) {
formData[key] = dataVal
} }
}, 100) }
} }
const validateAndShowResults = async () => { const fillOtherField = async (
if (!validFormula.value || validFormula.value.length === 0) { row: TableRow,
VxeUI.modal.message({ content: '没有配置验证规则', status: 'warning' }) item: ContentItem,
return valueMap: Record<string, string>
) => {
const key = getFieldKey(row.code, item.otherField!)
const tplItem = tplItemMap.value[key]
if (!tplItem) return
const { pid, itemid } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal !== undefined) {
formData[key] = dataVal
} }
}
try { const fillAttachTable = async (row: TableRow, valueMap: Record<string, string>) => {
loading.value = true const child = childAttachTableRefs.value[row.code]
validationResultsList.value = [] if (!child) return
const codeVal = await buildCodeValueMap() const tableData: Record<string, any> = {}
const formulaItems = validFormula.value
for (const data of row.datas || []) {
for (const strKey in formData) { for (const [key, value] of Object.entries(data)) {
const val = formData[strKey] if (typeof value === 'object' && value.field) {
if (val === undefined || val === null || val === '') { const fieldKey = getFieldKey(row.code, value.field)
continue const tplItem = tplItemMap.value[fieldKey]
} if (!tplItem) continue
const field = strKey.split('_')[1] const { pid, itemid, formTp, code } = tplItem
const targetFormula = formulaItems.find((f: any) => { const valueKey = `${pid}_${itemid}_1`
const formulaText = (f.formula || '').toString() const dataVal = valueMap[valueKey]
const regex = new RegExp(`\\b${field}\\b`, 'gi')
return regex.test(formulaText) if (dataVal) {
}) tableData[code] = formTp === 'checkbox' ? dataVal.split(',') : dataVal
if (targetFormula) {
const formulaText = (targetFormula.formula || '').toString()
let isValid = false
let resultMessage = ''
let process: string[] = []
let fieldTitle = ''
// 获取字段标题
for (const key in tplItemMap.value) {
if (key.endsWith(`_${field}`)) {
const item = tplItemMap.value[key]
if (item && item.title) {
fieldTitle = item.title
break
}
}
}
try {
process.push(`开始校验字段: ${field}`)
process.push(`验证公式: ${formulaText}`)
process.push(`字段值: ${JSON.stringify(codeVal)}`)
const result = await evaluateFormulaWithProcess(formulaText, codeVal, process)
isValid = result.isValid
resultMessage = result.message
if (isValid) {
process.push(`校验结果: 通过`)
} else {
process.push(`校验结果: 失败 - ${resultMessage}`)
}
} catch (error) {
isValid = true
resultMessage = `校验跳过: ${error.message}`
process.push(`校验异常: ${error.message}`)
} }
validationResultsList.value.push({
fieldTitle: fieldTitle,
fieldName: field,
formula: formulaText,
isValid,
resultMessage,
process,
fieldValues: codeVal
})
} }
} }
if (validationResultsList.value.length > 0) {
drawerVisible.value = true
} else {
VxeUI.modal.message({ content: '没有找到需要校验的字段', status: 'warning' })
}
} catch (error) {
console.error('校验失败:', error)
VxeUI.modal.message({ content: `校验失败: ${error.message}`, status: 'error' })
} finally {
loading.value = false
} }
child.setFormData(tableData)
} }
const evaluateFormulaWithProcess = async ( function collectAllFields(content, fields = []) {
formula: string, for (const item of content) {
codeVal: Record<string, string>, if (item.field) {
process: string[] fields.push({ field: item.field, formType: item.formType });
): Promise<{ isValid: boolean; message: string }> => {
try {
process.push('检查公式中用到的字段...')
const fieldPattern = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g
const fieldsInFormula = new Set<string>()
let match
while ((match = fieldPattern.exec(formula)) !== null) {
fieldsInFormula.add(match[1])
} }
process.push(`公式中用到的字段: ${Array.from(fieldsInFormula).join(', ')}`) if (item.otherField) {
fields.push({ field: item.otherField, formType: 'input' });
for (const field of fieldsInFormula) { }
if (!codeVal.hasOwnProperty(field) ||
codeVal[field] === undefined || if (item.formType === 'combinaform' && item.childs) {
codeVal[field] === null || collectAllFields(item.childs, fields);
codeVal[field] === '' || }
codeVal[field] === 'undefined' ||
codeVal[field] === 'null') { if (item.formType === 'select' && item.options) {
process.push(`字段 ${field} 为空,跳过校验`) for (const opt of item.options) {
return { isValid: true, message: `字段 ${field} 为空,跳过校验` } if (opt.extraFields) {
collectAllFields(opt.extraFields, fields);
}
if (opt.otherOption && opt.extraField) {
fields.push({ field: opt.extraField, formType: 'input' });
}
} }
} }
process.push('所有字段都有值,开始执行公式计算...') if (item.formType === 'radio-group-extraFields' && item.extraFields) {
let processedFormula = formula for (const key in item.extraFields) {
collectAllFields(item.extraFields[key], fields);
for (const [key, value] of Object.entries(codeVal)) { }
const paramRegex = new RegExp(`\\b${key}\\b`, 'g')
processedFormula = processedFormula.replace(paramRegex, value)
} }
process.push(`处理后的公式: ${processedFormula}`) if (item.formType === 'AttachTable' && item.gridOptions && item.gridOptions.datas) {
for (const data of item.gridOptions.datas) {
const result = eval(processedFormula) for (const key in data) {
const resultStr = String(result) if (data[key] && typeof data[key] === 'object' && data[key].field) {
fields.push({ field: data[key].field, formType: data[key].formType });
if (resultStr === 'true' || resultStr === 'True' || resultStr === '1' || result === true) { if (data[key].otherField) {
process.push(`公式计算结果: ${resultStr} (通过)`) fields.push({ field: data[key].otherField, formType: 'input' });
return { isValid: true, message: `通过 (${resultStr})` } }
} else { }
process.push(`公式计算结果: ${resultStr} (失败)`) }
return { isValid: false, message: `不通过 (${resultStr})` } }
} }
} catch (error) {
process.push(`公式计算失败: ${error.message}`)
return { isValid: true, message: `校验跳过 - ${error.message}` }
} }
return fields;
} }
const validateInput = async (key: string) => { const fillMultiColumnTable = async (row: TableRow, valueMap: Record<string, string>) => {
if (!validFormula.value || validFormula.value.length === 0) { const child = childMultiTableRefs.value[row.code]
validationResults.value[key] = 'default' if (!child || !Array.isArray(row.content)) return
validationMessages.value[key] = ''
return const rowsMap: Record<string, Record<string, any>> = {}
} const allFields = collectAllFields(row.content)
try { for (const fieldInfo of allFields) {
const formulaItems = validFormula.value const fieldKey = getFieldKey(row.code, fieldInfo.field)
const field = key.split('_')[1] const tplItem = tplItemMap.value[fieldKey]
if (!tplItem) continue
const targetFormula = formulaItems.find((f: any) => { const { pid, itemid, code, formTp } = tplItem
const formulaText = (f.formula || '').toString() const valueKeys = Object.keys(valueMap).filter(k =>
const regex = new RegExp(`\\b${field}\\b`, 'gi') k.startsWith(`${pid}_${itemid}_`)
return regex.test(formulaText) )
})
if (targetFormula) { for (const valueKey of valueKeys) {
const codeVal = await buildCodeValueMap() const parts = valueKey.split('_')
const formulaText = (targetFormula.formula || '').toString() const rind = parts[2]
const isValid = await evaluateFormula(formulaText, codeVal)
if (isValid) { if (!rowsMap[rind]) {
validationResults.value[key] = 'default' rowsMap[rind] = {}
validationMessages.value[key] = ''
} else {
validationResults.value[key] = 'error'
validationMessages.value[key] = targetFormula.des || `验证失败: ${formulaText}`
} }
} else {
validationResults.value[key] = 'default' let value = valueMap[valueKey]
validationMessages.value[key] = '' if (formTp === 'checkbox' && value) {
value = value.split(',').filter(Boolean)
}
rowsMap[rind][code] = value
} }
} catch (error) { }
console.error('验证失败:', error)
validationResults.value[key] = 'error' const tableDatas = Object.values(rowsMap)
validationMessages.value[key] = '验证计算失败' if (child && tableDatas.length > 0) {
child.setFormData(tableDatas)
} }
} }
const buildCodeValueMap = async (): Promise<Record<string, string>> => { const fillVxeTable = async (row: TableRow, valueMap: Record<string, string>) => {
const codeVal: Record<string, string> = {} const child = childMyVexTableRefs.value[row.code]
if (!child || !Array.isArray(row.columns)) return
const rowsMap: Record<string, Record<string, string>> = {}
for (const strKey in formData) { for (const column of row.columns) {
const val = formData[strKey] if (!column.field) continue
if (val !== undefined && val !== null && val !== '') {
const parts = strKey.split('_') const key = getFieldKey(row.code, column.field)
if (parts.length >= 2) { const tplItem = tplItemMap.value[key]
const field = parts[1] if (!tplItem || Object.keys(tplItem).length === 0) continue
codeVal[field] = Array.isArray(val) ? val.join(',') : String(val)
const { pid, itemid, code } = tplItem
const valueKeys = Object.keys(valueMap).filter(k =>
k.startsWith(`${pid}_${itemid}_`)
)
for (const valueKey of valueKeys) {
const [, , rind] = valueKey.split('_')
if (!rowsMap[rind]) {
rowsMap[rind] = {}
} }
rowsMap[rind][code] = valueMap[valueKey]
} }
} }
return codeVal const tableDatas = Object.values(rowsMap)
child.setFormData(tableDatas)
}
// 校验相关
const validateData = () => {
drawerVisible.value = true
validationDrawerRef.value.setValidateData(validFormula.value,formData);
}
const handleInputBlur = (rowCode: string, field: string, formula?: string) => {
if (!formula) return
const key = getFieldKey(rowCode, field)
const value = formData[key]
if (!value || value === '') {
delete inputErrors.value[key]
return
}
validateFieldFormula(rowCode, field, formula)
} }
const evaluateFormula = async (formula: string, codeVal: Record<string, string>): Promise<boolean> => { const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
const key = getFieldKey(rowCode, field)
try { try {
// 提取公式中用到的所有字段名(假设字段名是字母数字下划线组合) const expression = buildExpression(formula, rowCode)
const fieldPattern = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g const isValid = eval(expression)
const fieldsInFormula = new Set<string>() if (!isValid) {
let match inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
while ((match = fieldPattern.exec(formula)) !== null) { formula
fieldsInFormula.add(match[1])
}
// 检查所有用到的字段是否都有值
for (const field of fieldsInFormula) {
// 如果字段在 codeVal 中但值为空,或者不在 codeVal 中,都认为是空
if (!codeVal.hasOwnProperty(field) ||
codeVal[field] === undefined ||
codeVal[field] === null ||
codeVal[field] === '' ||
codeVal[field] === 'undefined' ||
codeVal[field] === 'null') {
return true // 有字段为空,不进行校验,默认通过
} }
} else {
delete inputErrors.value[key]
} }
} catch (error: any) {
let processedFormula = formula inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
for (const [key, value] of Object.entries(codeVal)) { formula,
const paramRegex = new RegExp(`\\b${key}\\b`, 'g') error: error.message
processedFormula = processedFormula.replace(paramRegex, value)
} }
const result = eval(processedFormula)
return Boolean(result)
} catch (error) {
console.error('公式计算失败:', error, '公式:', formula)
return true // 计算失败时也通过,避免因为计算错误导致验证失败
} }
} }
const validateAllInputs = async () => { const buildExpression = (formula: string, rowCode: string): string => {
if (!validFormula.value || validFormula.value.length === 0) { const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []
return const uniqueFields = [...new Set(fieldNames.filter(f =>
} !['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase())
))]
try { uniqueFields.sort((a, b) => b.length - a.length)
const formulaItems = validFormula.value
const codeVal = await buildCodeValueMap() let expression = formula
validationResults.value = {} for (const fieldName of uniqueFields) {
validationMessages.value = {} const key = getFieldKey(rowCode, fieldName)
for (const strKey in formData) { const value = formData[key]
const val = formData[strKey]
if (val !== undefined && val !== null && val !== '') { if (value !== undefined) {
const field = strKey.split('_')[1] // 检查字段类型,如果是日期类型,转换为时间戳进行比较
const targetFormula = formulaItems.find((f: any) => { const row = tableFormData.find((r: TableRow) => r.code === rowCode)
const formulaText = (f.formula || '').toString() let processedValue = value
const regex = new RegExp(`\\b${field}\\b`, 'gi')
return regex.test(formulaText) if (row && row.content && Array.isArray(row.content)) {
}) const fieldItem = row.content.find((c: ContentItem) => c.field === fieldName)
if (targetFormula) { if (fieldItem && fieldItem.type === 'date') {
const formulaText = (targetFormula.formula || '').toString() // 将日期字符串转换为时间戳(毫秒)
const isValid = await evaluateFormula(formulaText, codeVal) const date = new Date(value)
if (!isNaN(date.getTime())) {
if (isValid) { processedValue = date.getTime()
validationResults.value[strKey] = 'default'
validationMessages.value[strKey] = ''
} else {
validationResults.value[strKey] = 'error'
validationMessages.value[strKey] = targetFormula.des || `验证失败: ${formulaText}`
} }
} }
} }
expression = expression.replace(
new RegExp(`\\b${fieldName}\\b`, 'g'),
`Number(${processedValue})`
)
} }
} catch (error) { }
console.error('全量验证失败:', error)
return expression
}
// 提示工具相关
const toggleTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field)
if (hoveredKey.value === key) {
showTooltip.value = false
hoveredKey.value = ''
} else {
showTooltip.value = true
hoveredKey.value = key
} }
} }
const toggleErrorTooltip = (rowCode: string, field: string) => { const toggleErrorTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field); const key = getFieldKey(rowCode, field)
if (hoveredErrorKey.value === key) { if (hoveredErrorKey.value === key) {
showErrorTooltip.value = false; showErrorTooltip.value = false
hoveredErrorKey.value = ''; hoveredErrorKey.value = ''
} else { } else {
showErrorTooltip.value = true; showErrorTooltip.value = true
hoveredErrorKey.value = key; hoveredErrorKey.value = key
} }
}; }
const closeAllTooltips = () => { const closeAllTooltips = () => {
showTooltip.value = false; showTooltip.value = false
hoveredKey.value = ''; hoveredKey.value = ''
showErrorTooltip.value = false; showErrorTooltip.value = false
hoveredErrorKey.value = ''; hoveredErrorKey.value = ''
}; }
const handleValidationResultClick = (result: any) => {
const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`;
const status = result.isValid ? 'success' : 'error';
VxeUI.modal.message({ content: message, status });
};
// 历史填报
const handleOpenHistoryDrawer = () => { const handleOpenHistoryDrawer = () => {
historyFillCheckRef.value?.onDrawerShow(queryParam.value.tplId); historyFillCheckRef.value?.onDrawerShow(queryParam.value.tplId)
historyDrawerVisible.value = true; historyDrawerVisible.value = true
}; }
const closeHistoryDrawer = () => { const closeHistoryDrawer = () => {
historyDrawerVisible.value = false; historyDrawerVisible.value = false
};
}
</script> // 校验结果处理
<style lang="less" scoped> const handleValidationResultClick = (result: any) => {
.bank-report-table { const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
font-family: "SimSun", "宋体", serif; const status = result.isValid ? 'success' : 'error'
font-size: 12px;
color: #000; VxeUI.modal.message({ content: message, status })
margin: 5px auto;
width: 60%;
height:80vh;
position: relative; /* 为loading遮罩层定位 */
} }
/* 加载遮罩层样式 */ // 工具函数
.loading-overlay { const findEarliestFormulaForField = (
position: fixed; formulas: FormulaItem[],
top: 0; field: string
left: 0; ): { formula: FormulaItem | null, index: number } => {
right: 0; let result = { formula: null, index: Infinity }
bottom: 0;
background: rgba(255, 255, 255, 0.8); formulas?.forEach((f: FormulaItem) => {
display: flex; const text = f.formula || ''
align-items: center; const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
justify-content: center; const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g')
z-index: 9999; const match = regex.exec(text)
if (match && match.index < result.index) {
result = { formula: f, index: match.index }
}
})
return result
} }
.loading-spinner { // 消息提示
text-align: center; const showSuccessMessage = (message: string) => {
VxeUI.modal.message({ content: message, status: 'success' })
} }
.spinner { const showWarningMessage = (message: string) => {
width: 40px; VxeUI.modal.message({ content: message, status: 'warning' })
height: 40px;
margin: 0 auto 10px;
border: 3px solid #f3f3f3;
border-top: 3px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
} }
@keyframes spin { const showErrorMessage = (message: string) => {
0% { transform: rotate(0deg); } VxeUI.modal.message({ content: message, status: 'error' })
100% { transform: rotate(360deg); }
} }
.loading-text { // 子组件引用设置
font-size: 14px; const setMultiColumnTableRef = (el: any, code: string) => {
color: #1890ff; if (el) childMultiTableRefs.value[code] = el
font-weight: bold; }
const setAttachTableRef = (el: any, code: string) => {
if (el) childAttachTableRefs.value[code] = el
} }
.custom-table { const setMyVxeTableRef = (el: any, code: string) => {
width: 100%; if (el) childMyVexTableRefs.value[code] = el
border: 1px solid #000; }
</script>
<style lang="less" scoped>
// Base table styles
.bank-report-table {
font-family: "SimSun", "宋体", serif;
font-size: 12px;
color: #000;
margin: 5px auto;
width: 90%;
position: relative;
// Shared table styles
.custom-table, .attachment-table {
width: 100%;
border: 1px solid #000;
.vxe-header--column,
.vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
}
} }
.custom-table .vxe-header--column,
.custom-table .vxe-body--column { // 表格信息文本
border-right: 1px solid #000; .table-info-text {
border-bottom: 1px solid #000; font-weight: bold;
padding: 5px;
} }
.custom-table .vxe-cell {
padding: 5px; // 项目名称
.project-name {
font-weight: bold;
font-size: 14px;
} }
// Cell styles
.content-cell { .content-cell {
line-height: 1.8; line-height: 1.8;
} }
.table-input {
width: 80px;
margin: 0 2px;
}
.radio-group { // 表单控件组
display: inline-block; .radio-group, .checkbox-group {
margin-right: 10px;
}
.checkbox-group {
display: inline-block; display: inline-block;
margin-right: 10px; margin-left: 10px;
}
.vxe-radio, .vxe-radio, .vxe-checkbox {
.vxe-checkbox { margin-left: 0px;
margin-right: 8px; white-space: nowrap;
white-space: nowrap; line-height: 30px;
} padding: 0px 10px;
.vxe-input--inner {
height: 24px; &--label {
line-height: 24px; font-size: 12px;
padding: 0 5px; }
}
.vxe-radio--label, &--icon {
.vxe-checkbox--label { font-size: 12px;
font-size: 12px; }
}
} }
.vxe-radio--icon,
.vxe-checkbox--icon { // VXE specific overrides
font-size: 12px; .vxe {
&-radio-group, &-checkbox-group {
display: inline-block;
}
&-cell {
padding: 5px;
}
} }
.vxe-radio-group,
.vxe-checkbox-group { .vxe-input {
display: inline-block; border-bottom: 1px solid #333;
text-align: center;
margin: 5px 3px;
padding: 0;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
} }
// 输入框包装器
.input-wrapper { .input-wrapper {
position: relative; position: relative;
display: inline-block; display: inline-block;
} }
// 单位文本
.unit { .unit {
margin-left: 5px; margin-left: 5px;
font-size: 12px; font-size: 12px;
} }
// 帮助图标
.help-icon { .help-icon {
margin-left: 5px; margin-left: 5px;
cursor: pointer; cursor: pointer;
...@@ -1260,6 +1217,7 @@ const closeHistoryDrawer = () => { ...@@ -1260,6 +1217,7 @@ const closeHistoryDrawer = () => {
} }
} }
// 错误图标
.error-icon { .error-icon {
margin-left: 5px; margin-left: 5px;
cursor: pointer; cursor: pointer;
...@@ -1277,8 +1235,8 @@ const closeHistoryDrawer = () => { ...@@ -1277,8 +1235,8 @@ const closeHistoryDrawer = () => {
} }
} }
.tooltip, // 提示框
.error-tooltip { .tooltip, .error-tooltip {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
...@@ -1311,335 +1269,53 @@ const closeHistoryDrawer = () => { ...@@ -1311,335 +1269,53 @@ const closeHistoryDrawer = () => {
background: #8b0000; background: #8b0000;
color: #fff; color: #fff;
border: 1px solid #ff4d4f; border: 1px solid #ff4d4f;
&::before {
content: '';
position: absolute;
bottom: 100%;
left: 5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #8b0000 transparent;
}
} }
.bank-report-table { // 加载遮罩
font-family: "SimSun", "宋体", serif; .loading-overlay {
font-size: 12px; position: absolute;
color: #000; top: 0;
margin: 5px auto; left: 0;
width: 90%; right: 0;
} bottom: 0;
.custom-table { background: rgba(255, 255, 255, 0.8);
width: 100%;
border: 1px solid #000;
}
.custom-table .vxe-header--column,
.custom-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
.content-cell {
line-height: 1.8;
}
.table-input {
width: 80px;
margin: 0 2px;
}
.radio-group {
display: inline-block;
margin-right: 10px;
}
eckbox-group {
display: inline-block;
margin-right: 10px;
}
.vxe-radio,
.vxe-checkbox {
margin-right: 8px;
white-space: nowrap;
}
/* 附件样式 */
.attachments {
margin-top: 20px;
width: 100%;
}
.attachments h4 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.attachment-table {
width: 100%;
border: 1px solid #000;
}
.attachment-table .vxe-header--column,
.attachment-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
.vxe-input,.vxe-textarea {
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;
cursor: pointer;
}
.vxe-input.status--error .vxe-input--inner {
border-color: #ff4d4f !important;
background-color: #fff2f0 !important;
}
/* 校验结果抽屉样式 */
.validation-results {
padding: 16px;
height: 90vh;
}
.result-summary {
display: flex;
gap: 16px;
margin-bottom: 16px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
}
.summary-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 16px;
font-weight: bold;
}
.summary-value.success {
color: #52c41a;
}
.summary-value.error {
color: #ff4d4f;
}
.result-list {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 100%;
overflow-y: auto;
}
.result-item {
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 12px;
transition: all 0.3s;
}
.result-item.success {
border-color: #52c41a;
background: #f6ffed;
}
.result-item.error {
border-color: #ff4d4f;
background: #fff2f0;
}
.result-item-header {
display: flex; display: flex;
justify-content: space-between; justify-content: center;
align-items: center; align-items: center;
margin-bottom: 8px; z-index: 1000;
padding-bottom: 8px;
border-bottom: 1px solid #e8e8e8;
}
.result-item-name {
font-weight: bold;
font-size: 14px;
}
.result-item-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.result-item-status.success {
background: #f6ffed;
color: #52c41a;
border: 1px solid #95de64;
}
.result-item-status.error {
background: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.result-item-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.result-item-formula,
.result-item-result,
.result-item-process {
display: flex;
gap: 8px;
align-items: flex-start;
}
.result-item-formula {
font-family: 'Courier New', monospace;
background: #fafafa;
padding: 8px;
border-radius: 4px;
border: 1px solid #e8e8e8;
}
.formula-label,
.result-label,
.formula-label,
.result-label,
.process-label,
.values-label {
font-weight: bold;
font-size: 12px;
color: #666;
min-width: 80px;
}
.formula-value,
.result-value,
.values-value {
font-size: 12px;
flex: 1;
}
.result-value {
color: #333;
}
.process-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
} }
.process-step { .loading-spinner {
display: flex; text-align: center;
gap: 8px;
align-items: flex-start;
font-size: 12px;
color: #666;
} }
.step-number { .spinner {
display: inline-flex; width: 40px;
align-items: center; height: 40px;
justify-content: center; border: 3px solid #f3f3f3;
width: 16px; border-top: 3px solid #3498db;
height: 16px;
background: #1890ff;
color: white;
border-radius: 50%; border-radius: 50%;
font-size: 10px; animation: spin 1s linear infinite;
font-weight: bold; margin: 0 auto 10px;
flex-shrink: 0;
}
.step-text {
flex: 1;
}
.result-item-process {
border-top: 1px solid #e8e8e8;
padding-top: 8px;
} }
.result-item-values { @keyframes spin {
display: flex; 0% { transform: rotate(0deg); }
gap: 8px; 100% { transform: rotate(360deg); }
align-items: flex-start;
} }
.values-label { .loading-text {
font-weight: bold; font-size: 14px;
font-size: 12px;
color: #666; color: #666;
min-width: 80px;
}
.values-value {
font-family: 'Courier New', monospace;
font-size: 11px;
line-height: 1.5;
background: #fafafa;
padding: 8px;
border-radius: 4px;
border: 1px solid #e8e8e8;
overflow-x: auto;
max-height: 200px;
overflow-y: auto;
flex: 1;
white-space: pre;
word-break: break-all;
}
/* 抽屉样式调整 */
.vxe-drawer--right {
width: 700px !important;
}
.vxe-drawer--header {
border-bottom: 1px solid #e8e8e8;
padding-bottom: 12px;
} }
.vxe-drawer--title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.vxe-drawer--body {
padding: 16px;
max-height: calc(100vh - 140px);
overflow-y: auto;
}
.result-list {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 500px;
overflow-y: auto;
}
.result-list--scrolling {
max-height: calc(100vh - 280px) !important;
}
</style> </style>
\ No newline at end of file
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
</vxe-radio-group> </vxe-radio-group>
<template v-if="field.otherOption&&row[field.field]=='是'"> <template v-if="field.otherOption&&row[field.field]=='是'">
<span>{{field.otherLabel}}</span> <span>{{field.otherLabel}}</span>
<vxe-input v-model="row[field.otherField]" style="width: 200px;"> <vxe-input v-model="row[field.otherField]" style="width: 400px;">
</vxe-input> </vxe-input>
</template> </template>
</template> </template>
...@@ -79,7 +79,8 @@ ...@@ -79,7 +79,8 @@
></vxe-radio> ></vxe-radio>
</vxe-radio-group> </vxe-radio-group>
<template v-if="field.otherOption&&row[field.field]=='其他'"> <template v-if="field.otherOption&&row[field.field]=='其他'">
<vxe-input v-model="row[field.otherField]" placeholder="请输入其他"> <span>其他: </span>
<vxe-input v-model="row[field.otherField]" placeholder="请输入其他" style="width: 200px;">
</vxe-input> </vxe-input>
</template> </template>
</template> </template>
...@@ -108,7 +109,8 @@ ...@@ -108,7 +109,8 @@
></vxe-radio> ></vxe-radio>
</vxe-radio-group> </vxe-radio-group>
<template v-if="field.otherOption&&row[field.field]=='其他'"> <template v-if="field.otherOption&&row[field.field]=='其他'">
<vxe-input v-model="row[field.otherField]" placeholder="请输入其他" style="width: 100px;"> <span>其他: </span>
<vxe-input v-model="row[field.otherField]" placeholder="请输入其他" style="width: 200px;">
</vxe-input> </vxe-input>
</template> </template>
...@@ -133,7 +135,8 @@ ...@@ -133,7 +135,8 @@
) )
" "
> >
<vxe-input v-model="row[ccopt.otherField]" placeholder="请输入其他" style="width: 100px;"> <span>其他: </span>
<vxe-input v-model="row[ccopt.otherField]" placeholder="请输入其他" style="width: 200px;">
</vxe-input> </vxe-input>
</template> </template>
<span class="unit"> {{ ccopt.unit }}</span> <span class="unit"> {{ ccopt.unit }}</span>
...@@ -149,9 +152,14 @@ ...@@ -149,9 +152,14 @@
:value="opt" :value="opt"
:content="opt" :content="opt"
></vxe-checkbox> ></vxe-checkbox>
<template v-if="field.otherField">
<vxe-checkbox label="其他" value="其他">
其他:
</vxe-checkbox>
</template>
</vxe-checkbox-group> </vxe-checkbox-group>
<template v-if="field.otherField&&row[field.field]?.includes('其他')"> <template v-if="field.otherField&&row[field.field]?.includes('其他')">
<vxe-input v-model="row[field.otherField]" placeholder="请输入其他" style="width: 100px;"> <vxe-input v-model="row[field.otherField]" placeholder="请输入其他" style="width: 200px; margin-left: 4px;">
</vxe-input> </vxe-input>
</template> </template>
...@@ -196,8 +204,8 @@ ...@@ -196,8 +204,8 @@
) )
" "
> >
<span class="extra-label">{{ item.otherLabel}}</span> <span class="extra-label">{{ item.otherLabel}} </span>
<vxe-input v-model="row[item.otherField]" placeholder="请输入" style="width: 150px;"> <vxe-input v-model="row[item.otherField]" placeholder="请输入" style="width: 300px;">
</vxe-input> </vxe-input>
</template> </template>
</template> </template>
...@@ -247,10 +255,11 @@ ...@@ -247,10 +255,11 @@
) )
" "
> >
<span>其他: </span>
<vxe-input <vxe-input
v-model="row[extraObject.otherField]" v-model="row[extraObject.otherField]"
placeholder="请输入其他" placeholder="请输入其他"
style="width: 150px; margin-left: 8px;" style="width: 300px; margin-left: 4px;"
/> />
</template> </template>
<span v-if="extraObject.extraUnit" class="unit">{{ extraObject.unit }}</span> <span v-if="extraObject.extraUnit" class="unit">{{ extraObject.unit }}</span>
...@@ -532,6 +541,15 @@ const setFormData = (dataVale) => { ...@@ -532,6 +541,15 @@ const setFormData = (dataVale) => {
tableData.value = dataVale.map((row, rowIndex) => { tableData.value = dataVale.map((row, rowIndex) => {
const newRow = { ...row } const newRow = { ...row }
props.fields.forEach(field => {
if ((field.formType === 'checkbox' || field.formType === 'checkbox-group') && newRow[field.field]) {
if (!Array.isArray(newRow[field.field])) {
newRow[field.field] = newRow[field.field].split(',').filter(Boolean)
}
}
})
return newRow return newRow
}) })
...@@ -571,7 +589,11 @@ const setFormData = (dataVale) => { ...@@ -571,7 +589,11 @@ const setFormData = (dataVale) => {
if (fieldWithChilds.childs && Array.isArray(fieldWithChilds.childs)) { if (fieldWithChilds.childs && Array.isArray(fieldWithChilds.childs)) {
fieldWithChilds.childs.forEach((child: any) => { fieldWithChilds.childs.forEach((child: any) => {
if (child.formType === 'checkbox' && !Array.isArray(row[child.field])) { if (child.formType === 'checkbox' && !Array.isArray(row[child.field])) {
row[child.field] = [] if (row[child.field]) {
row[child.field] = row[child.field].split(',').filter(Boolean)
} else {
row[child.field] = []
}
} }
}) })
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论