提交 12b89e58 authored 作者: kxjia's avatar kxjia

完善功能

上级 574734b0
...@@ -33,7 +33,7 @@ export const tableFormData = [ ...@@ -33,7 +33,7 @@ export const tableFormData = [
{ type: 'text', value: '<strong>网络安全责任制建立情况</strong>' }, { type: 'text', value: '<strong>网络安全责任制建立情况</strong>' },
{ type: 'br' }, { type: 'br' },
{ type: 'text', value: '建立并落实网络安全工作责任制' }, { type: 'text', value: '建立并落实网络安全工作责任制' },
{ type: 'radio-group', field: 'C23A001', options: ['是', '否'] }, { type: 'radio-group', field: 'C23A001', options: ['是', '否'] ,value:''},
{ type: 'br' }, { type: 'br' },
{ type: 'text', value: '网络安全第一责任人:' }, { type: 'text', value: '网络安全第一责任人:' },
{ type: 'text', value: '姓名' }, { type: 'text', value: '姓名' },
......
...@@ -652,7 +652,6 @@ const collectVxeTableData = async () => { ...@@ -652,7 +652,6 @@ const collectVxeTableData = async () => {
} }
} }
if (formData.footer) { if (formData.footer) {
alert(formData.footerPcode)
for (const [code, value] of Object.entries(formData.footer)) { for (const [code, value] of Object.entries(formData.footer)) {
await addFormValue(formData.footerPcode, code, value, 1) await addFormValue(formData.footerPcode, code, value, 1)
} }
......
<template> <template>
<div class="bank-report-table" style="height:80vh" @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">
...@@ -8,233 +12,236 @@ ...@@ -8,233 +12,236 @@
</div> </div>
</div> </div>
<!-- 工具栏 -->
<vxe-toolbar> <vxe-toolbar>
<template #button> <template #buttons>
<div class="toolbar-info"> <div style="margin:10px">
<span class="info-label">填报任务:{{ queryParam?.taskName }}</span> <span class="table-info-text">填报任务:{{ queryParam.taskName }}</span>
<span class="info-label">表格:{{ 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-save" @click="checkData()">校验</vxe-button> status="primary"
<vxe-button status="primary" icon="vxe-icon-save" @click="saveBatch">保存</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>
<!-- 校验抽屉 --> <!-- 主表格 -->
<ValidationDrawer
ref="validationDrawerRef"
v-model="drawerVisible"
:tableFormData="tableFormData"
@validationResultClick="handleValidationResultClick"
/>
<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 }"
class="custom-table" class="custom-table"
height="auto"
:merge-cells="mergeCells"
> >
<vxe-column field="serialNumber" title="序号" width="80"></vxe-column> <vxe-column field="serialNumber" title="序号" width="50"></vxe-column>
<vxe-column field="project" title="项目" width="50">
<template #default="{ row }"> <vxe-column field="project" title="项目" width="100">
<span class="project-title">{{ row.project }}</span>
</template>
</vxe-column>
<vxe-column field="project2" title="" width="50">
<template #default="{ row }"> <template #default="{ row }">
<span class="project-title">{{ row.project2 }}</span> <span class="project-name">{{ row.project }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="content" title="内容" row-resize>
<template #default="{ row }"> <vxe-column field="content" title="内容">
<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"
:columnsPerRow="6"
:records="[]" :records="[]"
:fields="row.content" :fields="row.content"
:ref="(el) => setMultiColumnTableRef(el, row.code)" :ref="(el) => setMultiColumnTableRef(el, row.code)"
class="embedded-table" style="margin:0px;padding:0px"
/> />
</template> </template>
<!-- 附件表格 -->
<template v-else-if="row.type === 'AttachTable'"> <template v-else-if="row.type === 'AttachTable'">
<AttachTable <AttachTable
: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)"
class="embedded-table" :disabled="!row.hasRight"
style="margin:0px;padding:0px"
/>
</template>
<!-- Vxe表格 -->
<template v-else-if="row.type === 'VxeTable'">
<MyVxeTable1
:title="row.project"
:data="row.data"
:columns="row.columns"
:pcode="row.code"
:footerData="row.footerData"
:showFooter="row.showFooter"
:ref="(el) => setMyVxeTableRef(el, row.code)"
:disabled="!row.hasRight"
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>
<template v-else-if="item.type === 'AttachTable'">
<AttachTable
:title="item.project"
:records="item.datas"
:fields="item.content"
:pcode="item.code"
:ref="(el) => setAttachTableRef(el, item.code)"
class="embedded-table"
/>
</template>
<template v-else-if="item.type === 'MultiColumnTable'">
<MultiColumnTable
:title="item.project"
:records="[]"
:fields="item.content"
:ref="(el) => setMultiColumnTableRef(el, item.code)"
class="embedded-table"
/>
</template>
<template v-else-if="item.type === 'yesno'"> <!-- 单选组 -->
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio label="是" content="是"></vxe-radio>
<vxe-radio label="否" content="否"></vxe-radio>
</vxe-radio-group>
<template v-if="item.extraField || item.extraFields">
<div v-if="formData[getFieldKey(row.code, item.field)] === '是'" class="extra-fields-container">
如是,
<template v-if="item.extraField">
{{ item.extraLabel }}
<vxe-input v-model="formData[getFieldKey(row.code, item.extraField)]" class="extra-input"></vxe-input>
<span class="unit">{{ item.unit }}</span>
</template>
<template v-else-if="item.extraFields">
<template v-for="(ccopt, ccind) in item.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span>
<template v-if="ccopt.formType === 'input'">
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" class="extra-input"></vxe-input>
</template>
<template v-else-if="ccopt.formType === 'number'">
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" type="number" class="extra-input"></vxe-input>
<span class="unit">{{ ccopt.unit }}</span>
</template>
<template v-else-if="ccopt.formType === 'checkbox'">
<vxe-checkbox-group v-model="formData[getFieldKey(row.code, ccopt.field)]">
<vxe-checkbox v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt"></vxe-checkbox>
</vxe-checkbox-group>
</template>
<template v-else-if="ccopt.formType === 'radio'">
<vxe-radio-group v-model="formData[getFieldKey(row.code, ccopt.field)]" :options="ccopt.options">
<vxe-radio v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt"></vxe-radio>
</vxe-radio-group>
</template>
<template v-else>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" :type="ccopt.formType" class="extra-input"></vxe-input>
</template>
<br>
<template v-if="ccopt.otherOption">
其他:<vxe-input v-model="formData[getFieldKey(row.code, ccopt.otherField)]" class="extra-input"></vxe-input>
</template>
<br>
</template>
</template>
</div>
</template>
</template>
<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[getFieldKey(row.code, item.field)]" style="margin-left: 20px;"> <vxe-radio-group
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt" style="width:100%">{{ opt }}</vxe-radio> v-model="formData[getFieldKey(row.code, item.field)]"
</vxe-radio-group> :disabled="!item.hasRight || isRadioDisabled(row.code, item)"
</span> >
<span v-else-if="item.type === 'radio-group-extraFields'" class="radio-group"> <vxe-radio
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]"> v-for="(opt, optIndex) in item.options"
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt.label"> :key="optIndex"
{{ opt.label }} :label="opt"
<template v-if="opt.extraField && formData[getFieldKey(row.code, item.field)] === opt.label"> >
<span>{{ opt.extraLabel }}</span> {{ opt }}
<vxe-input v-model="formData[getFieldKey(row.code, opt.extraField)]" class="extra-input"></vxe-input> </vxe-radio>
</template> </vxe-radio-group>
<template v-if="opt.extraFields && formData[getFieldKey(row.code, item.field)] === opt.label">
<template v-for="(ccopt, ccind) in opt.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" class="extra-input"></vxe-input>
</template>
</template>
</vxe-radio>
</vxe-radio-group>
</span> </span>
<!-- 多选组(带其他选项) -->
<div v-else-if="item.type === 'checkbox-group'"> <div v-else-if="item.type === 'checkbox-group'">
<div style="width:400px;margin-left:30px"> <span class="checkbox-group">
<vxe-checkbox-group v-model="formData[getFieldKey(row.code, item.field)]"> <vxe-checkbox-group
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt"> v-model="formData[getFieldKey(row.code, item.field)]"
:disabled="!item.hasRight"
>
<vxe-checkbox
v-for="(opt, optIndex) in item.options"
:key="optIndex"
:label="opt"
>
{{ opt }} {{ opt }}
</vxe-checkbox> </vxe-checkbox>
<template v-if="item.otherOption" > <template v-if="item.otherField">
<vxe-checkbox label="其他"> <div style="width:100%">
其他: <vxe-checkbox label="其他" :disabled="!item.hasRight">
<vxe-input v-model="formData[getFieldKey(row.code, item.otherField)]" class="extra-input" 其他:
:disabled="!(formData[row.code+'_'+item.field]?.indexOf('其他')>-1)" </vxe-checkbox>
></vxe-input> <vxe-input
</vxe-checkbox> v-model="formData[getFieldKey(row.code, item.otherField)]"
:disabled="!isOtherFieldEnabled(row.code, item) || !item.hasRight"
style="width: 50%"
@blur="handleInputBlur(row.code, item.otherField, item.matchedFormula?.formula)"
/>
</div>
</template> </template>
</vxe-checkbox-group> </vxe-checkbox-group>
</div>
<!-- 其他选项 -->
</span>
</div> </div>
<template v-else-if="item.type === 'textarea'">
<vxe-textarea v-model="formData[getFieldKey(row.code, item.field)]" :rows="item.rows" :style="{ width: item.width }"></vxe-textarea> <!-- 多选带输入框 -->
</template> <div v-else-if="item.type === 'checkboxAndInput'">
<span style="margin-left:20px">
<vxe-checkbox-group
v-model="formData[getFieldKey(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[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 || isInputDisabled(row.code, item)"
:type="item.type" :type="item.type"
v-model="formData[getFieldKey(row.code, item.field)]" v-model="formData[getFieldKey(row.code, item.field)]"
size="mini" size="mini"
class="table-input" class="table-input"
:style="item.style"
@blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)" @blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
/> />
<span v-if="item.unit" 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="inputErrors[getFieldKey(row.code, item.field)]" <span
class="error-icon" 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)"
title="点击查看错误详情"
> >
<i class="vxe-icon-error"></i> <i class="vxe-icon-error"></i>
</span> </span>
<div <!-- 错误提示 -->
v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)" <div
class="tooltip" v-if="showErrorTooltip && hoveredErrorKey === getFieldKey(row.code, item.field)"
@click.stop class="error-tooltip"
>
{{ item.matchedFormula?.des || '暂无校验规则' }}
</div>
<div
v-if="showErrorTooltip && hoveredErrorKey === getFieldKey(row.code, item.field)"
class="error-tooltip"
@click.stop @click.stop
> >
{{ inputErrors[getFieldKey(row.code, item.field)].message }} {{ inputErrors[getFieldKey(row.code, item.field)].message }}
...@@ -249,6 +256,15 @@ ...@@ -249,6 +256,15 @@
</span> </span>
</template> </template>
</div> </div>
<!-- 校验规则提示 -->
<div
v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)"
class="tooltip"
@click.stop
>
{{ item.matchedFormula?.des || '暂无校验规则' }}
</div>
</div> </div>
</template> </template>
</template> </template>
...@@ -256,34 +272,75 @@ ...@@ -256,34 +272,75 @@
</div> </div>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="remarks" title="备注" width="100">
<!-- 备注列 -->
<vxe-column field="remarks" title="备注" width="60">
<template #default="{ row }"> <template #default="{ row }">
<vxe-textarea :rows="row.remarks.rows" v-model="formData[row.code+'_'+row.remarks.field]" style="height:100%;"> <vxe-textarea
</vxe-textarea> :rows="row.remarks.rows"
v-model="formData[getFieldKey(row.code, row.remarks.field)]"
/>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<!-- 历史填报检查组件 --> <!-- 校验抽屉 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @closeDrawer="closeHistoryDrawer"/> <ValidationDrawer
ref="validationDrawerRef"
v-model="drawerVisible"
:tableFormData="tableFormData"
@validationResultClick="handleValidationResultClick"
/>
<!-- 历史填报抽屉 -->
<HistoryFillCheck
ref="historyFillCheckRef"
v-model="historyDrawerVisible"
@closeDrawer="closeHistoryDrawer"
/>
</div> </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 HistoryFillCheck from './check/historyFillCheck.vue' import MyVxeTable1 from '../tableComponents/MyVxeTable.vue'
import HistoryFillCheck from './check/HistoryFillCheck.vue'
import ValidationDrawer from './check/ValidationDrawer.vue' import ValidationDrawer from './check/ValidationDrawer.vue'
// 数据导入
import { tableFormData } from '../../data/tb10.data' import { tableFormData } from '../../data/tb10.data'
import { ref, reactive, nextTick, onMounted } from 'vue'
import { VxeUI,VxeTablePropTypes } from 'vxe-table' // API 导入
import { batchSaveOrUpdateBeforeDelete, queryRecord } from '../../record/BaosongTaskRecord.api' import {
import { queryAllTplItemForUser, findUserRightForTplItem } from '../../alloc/BaosongTaskAlloc.api' 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 FormData { // 类型定义
interface QueryParam {
taskId: number
taskName: string
tplId: number
tplName: string
tplCode: string
comfrom: string
}
interface FormDataItem {
id: number | null id: number | null
taskid: number taskid: number
tplid: number tplid: number
...@@ -294,18 +351,65 @@ interface FormData { ...@@ -294,18 +351,65 @@ interface FormData {
rind: number 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
}
interface TplItemMapValue {
pid: number
itemid: number
formTp: string
code: string
}
// 路由和组件引用
const route = useRoute() const route = useRoute()
const tableRef = ref() const tableRef = ref<VxeTableInstance>()
const tplItemMap = ref<Record<string, any>>({}) const historyFillCheckRef = ref()
const historyFillCheckRef = ref<any>(null)
const validationDrawerRef = ref() const validationDrawerRef = ref()
const loading = ref(false)
const formData = reactive<Record<string, any>>({})
const formValues = ref<FormData[]>([])
const childMultiTableRefs = ref<Record<string, any>>({})
const childAttachTableRefs = ref<Record<string, any>>({})
const queryParam = ref({ // 响应式状态
const loading = ref(true)
const historyDrawerVisible = ref(false)
const drawerVisible = ref(false)
const showTooltip = ref(false)
const hoveredKey = ref('')
const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
// 数据状态
const queryParam = ref<QueryParam>({
taskId: -1, taskId: -1,
taskName: '', taskName: '',
tplId: -1, tplId: -1,
...@@ -314,483 +418,584 @@ const queryParam = ref({ ...@@ -314,483 +418,584 @@ const queryParam = ref({
comfrom: '' comfrom: ''
}) })
// 权限相关状态 const formData = reactive<Record<string, any>>({})
const formValues = ref<FormDataItem[]>([])
const inputErrors = ref<Record<string, InputError>>({})
const userAllocItems = ref<string[]>([]) const userAllocItems = ref<string[]>([])
const validFormula = ref<any[]>([]) const validFormula = ref<FormulaItem[]>([])
const drawerVisible = ref(false) const tplItemMap = ref<Record<string, TplItemMapValue>>({})
const historyDrawerVisible = ref(false)
const showTooltip = ref(false)
const hoveredKey = ref('')
const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
const inputErrors = ref<Record<string, any>>({})
const isInitialized = ref(false)
interface FormulaItem { // 子组件引用
formula: string const childMultiTableRefs = ref<Record<string, any>>({})
des: string const childAttachTableRefs = ref<Record<string, any>>({})
[key: string]: any const childMyVexTableRefs = ref<Record<string, any>>({})
// 计算属性
const fieldKeys = computed(() => Object.keys(formData))
// 工具函数
const getFieldKey = (rowCode: string, field: string): string => {
return `${rowCode}_${field}`
} }
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] !== '是'
} }
onMounted(async () => { const isInputDisabled = (rowCode: string, item: ContentItem): boolean => {
if (route.query.taskId) { if (!item.relatedFiled) return false
queryParam.value.taskId = Number(route.query.taskId) const relatedKey = getFieldKey(rowCode, item.relatedFiled)
} return formData[relatedKey] !== '是'
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 isOtherFieldEnabled = (rowCode: string, item: ContentItem): boolean => {
setQueryParams(); const checkboxKey = getFieldKey(rowCode, item.field)
const checkboxValue = formData[checkboxKey] || []
return checkboxValue.includes('其他')
}
// 获取权限和验证公式 // 初始化
try { onMounted(async () => {
userAllocItems.value = await findUserRightForTplItem({ await initPage()
tplid: queryParam.value.tplId, })
taskid: queryParam.value.taskId
})
validFormula.value = await getTblvalidFormula({ const initPage = async () => {
tplid: queryParam.value.tplId, try {
}) loading.value = true
} catch (error) { await initQueryParams()
console.error('获取权限或验证公式失败:', error) await Promise.all([
loadUserRights(),
loadValidationFormulas(),
loadTemplateItems()
])
await setFormItemRight()
await loadTableData()
} catch (error: any) {
showErrorMessage(`初始化页面失败: ${error.message}`)
} finally {
loading.value = false
} }
}
await setTplItemMap() const initQueryParams = () => {
setFormItemRight() const { taskId, taskName, tplId, tplName, tplCode,comfrom } = route.query
await setData()
isInitialized.value = true if (taskId) queryParam.value.taskId = Number(taskId)
if (taskName) queryParam.value.taskName = String(taskName)
if (tplId) queryParam.value.tplId = Number(tplId)
if (tplName) queryParam.value.tplName = String(tplName)
if (tplCode) queryParam.value.tplCode = String(tplCode)
if (comfrom) queryParam.value.comfrom = String(comfrom)
}
const loadUserRights = async () => {
userAllocItems.value = await findUserRightForTplItem({
tplid: queryParam.value.tplId,
taskid: queryParam.value.taskId
})
}
const loadValidationFormulas = async () => {
validFormula.value = await getTblvalidFormula({
tplid: queryParam.value.tplId
})
}
const loadTemplateItems = async () => {
const items = await allTplItems({
tplid: queryParam.value.tplId
})
setTimeout(() => { tplItemMap.value = items.reduce((acc: Record<string, TplItemMapValue>, item) => {
refreshHelpIcons() const key = `${item.pcode}_${item.xmlcode}`
}, 300) acc[key] = {
}) pid: item.pid,
itemid: item.id,
formTp: item.formTp,
code: item.xmlcode
}
return acc
}, {})
}
const getFieldKey = (pcode: string | undefined, field: string): string => { const setFormItemRight = () => {
return pcode ? `${pcode}_${field}` : field tableFormData.forEach((row: TableRow) => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: ContentItem) => {
if (item.field) {
const key = getFieldKey(row.code, item.field)
item.hasRight = userAllocItems.value.includes(key)
if (item.hasRight) {
const { formula } = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formula
item.matchedFormula = formula
}
}
})
}
})
} }
// 保存相关
const saveBatch = async () => { const saveBatch = async () => {
try { try {
loading.value = true if (!await validateForm()) return
formValues.value = []
await prepareFormData()
// Process main form data
for (const strKey in formData) { if (formValues.value.length === 0) {
const valData = formData[strKey] showWarningMessage('没有需要保存的数据')
if (valData) { return
const [pcode, code] = strKey.split('_')
await setFormValues(pcode, code, valData, 1)
}
} }
await batchSaveOrUpdateBeforeDelete(formValues.value)
showSuccessMessage('保存成功')
} catch (error: any) {
showErrorMessage(`保存失败: ${error.message}`)
}
}
const validateForm = async (): Promise<boolean> => {
const $table = tableRef.value
if (!$table) return true
const checkResult = $table.validate()
if (!checkResult) {
showErrorMessage('表单验证失败,请检查填写内容')
return false
}
return true
}
await getAttachTableFormData() const prepareFormData = async () => {
await getAllMultiTableFormData() formValues.value = []
if (formValues.value.length > 0) { // 处理主表单数据
await batchSaveOrUpdateBeforeDelete(formValues.value) for (const [key, value] of Object.entries(formData)) {
VxeUI.modal.message({ content: '保存成功', status: 'success' }) if (value !== undefined && value !== null && value !== '') {
} else { const [pcode, code] = key.split('_')
VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' }) await addFormValue(pcode, code, value, 1)
} }
} catch (error) {
console.error('保存失败:', error)
VxeUI.modal.message({ content: `保存失败: ${error instanceof Error ? error.message : String(error)}`, status: 'error' })
} finally {
loading.value = false
} }
// 处理子表格数据
await Promise.all([
collectAttachTableData(),
collectMultiTableData(),
collectVxeTableData()
])
} }
const setMultiColumnTableRef = (el: any, code: string) => { const addFormValue = async (pcode: string, code: string, value: any, rind: number) => {
if (el) { if (!value) return
childMultiTableRefs.value[code] = el
const stringValue = Array.isArray(value) ? value.join(',') : String(value)
const key = getFieldKey(pcode, code)
const item = tplItemMap.value[key]
if (!item) return
const formItem: FormDataItem = {
id: null,
rind,
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId,
tplcode: code,
itemid: item.itemid,
itempid: item.pid,
content: stringValue
} }
formValues.value.push(formItem)
} }
const setAttachTableRef = (el: any, code: string) => { // 数据收集函数
if (el) { const collectAttachTableData = async () => {
childAttachTableRefs.value[code] = el 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 getAllMultiTableFormData = async () => { const collectMultiTableData = async () => {
for (const pcode in childMultiTableRefs.value) { for (const [pcode, child] of Object.entries(childMultiTableRefs.value)) {
const child = childMultiTableRefs.value[pcode]
if (child) { if (child) {
const datas = child.getFormData() const datas = await child.getFormData()
let rind = 0 let rind = 0
for (const obj of datas) { for (const obj of datas) {
rind++ rind++
Object.keys(obj).forEach(code => { for (const [code, value] of Object.entries(obj)) {
setFormValues(pcode, code, obj[code], rind) await addFormValue(pcode, code, value, rind)
}) }
} }
} }
} }
} }
const getAttachTableFormData = async () => { const collectVxeTableData = async () => {
for (const pcode in childAttachTableRefs.value) { for (const [pcode, child] of Object.entries(childMyVexTableRefs.value)) {
const child = childAttachTableRefs.value[pcode]
if (child) { if (child) {
const datas = child.getFormData() const formData = await child.getFormData()
for (const code in datas) { let rind = 0
await setFormValues(pcode, code, datas[code], 1) // 表格数据
for (const obj of formData.datas) {
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 setFormValues = async (pcode: string, code: string, valData: any, rind: number) => { // 数据加载
if (!valData) return const loadTableData = async () => {
try {
if(Array.isArray(valData)&& valData.length==0) return loading.value = true
const vals = Array.isArray(valData) ? valData.join(',') : valData // 清空现有数据
const strKey = `${pcode}_${code}` Object.keys(formData).forEach(key => delete formData[key])
const item = tplItemMap.value[strKey]
const { pid, itemid } = item ?? {} const recordData = await queryRecord({
taskid: queryParam.value.taskId,
const tempForm: FormData = { tplid: queryParam.value.tplId
id: null, })
rind: rind,
taskid: queryParam.value.taskId, // 转换记录数据为便于查找的结构
tplid: queryParam.value.tplId, const valueMap = recordData.reduce((acc: Record<string, string>, data) => {
tplcode: code, const key = `${data.itempid}_${data.itemid}_${data.rind}`
itemid: itemid, acc[key] = data.content
itempid: pid, return acc
content: vals }, {})
// 填充表格数据
await fillTableData(valueMap)
} catch (error: any) {
showErrorMessage(`加载数据失败: ${error.message}`)
} finally {
loading.value = false
} }
}
formValues.value.push(tempForm) const fillTableData = async (valueMap: Record<string, string>) => {
for (const row of tableFormData) {
if (!row.type && row.content) {
await fillSimpleFields(row, valueMap)
} 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)
}
}
} }
async function setData() { const fillSimpleFields = async (row: TableRow, valueMap: Record<string, string>) => {
try { if (!row.content || !Array.isArray(row.content)) return
loading.value = true;
const taskId = queryParam.value.taskId; for (const item of row.content) {
const tplid = queryParam.value.tplId; if (!item.field) continue
const key = getFieldKey(row.code, item.field)
const tplItem = tplItemMap.value[key]
if (!tplItem) continue
Object.keys(formData).forEach(key => delete formData[key]); const { pid, itemid, formTp } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
await setTplItemMap(); if (dataVal === undefined) continue
const recordData = await queryRecord({ taskid: taskId, tplid: tplid });
const valueObj = {}; if (formTp === 'checkbox') {
for (const data of recordData) { formData[key] = dataVal.split(',').filter(Boolean)
const key = `${data.itempid}_${data.itemid}_${data.rind}`; } else {
valueObj[key] = data.content; formData[key] = dataVal
} }
const shouldSkip = (key) => {
const refsToCheck = [ // 处理特殊字段类型
childMultiTableRefs.value, if (item.type === 'checkboxAndInput' && item.optionItemField) {
childAttachTableRefs.value, await fillCheckboxInputFields(row, item, valueMap)
]; }
for (const refs of refsToCheck) { if (item.otherField) {
for (const pcode of Object.keys(refs)) { await fillOtherField(row, item, valueMap)
if (key.startsWith(pcode + '_')) { }
return true; }
} }
}
}
return false;
};
for (const strKey of Object.keys(tplItemMap.value)) {
if (shouldSkip(strKey)) {
continue;
}
const item = tplItemMap.value[strKey];
if (!item) continue;
const { pid, itemid, formTp } = item;
const dataVal = valueObj[`${pid}_${itemid}_1`];
if (!dataVal) continue; const fillCheckboxInputFields = async (
row: TableRow,
if (formTp === 'checkbox') { item: ContentItem,
formData[strKey] = dataVal?.split(",") || []; valueMap: Record<string, string>
} else { ) => {
formData[strKey] = dataVal || ""; for (const field of item.optionItemField || []) {
} const key = getFieldKey(row.code, field)
const tplItem = tplItemMap.value[key]
if (!tplItem) continue
const { pid, itemid } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal !== undefined) {
formData[key] = dataVal
} }
}
}
for (const pcode of Object.keys(childAttachTableRefs.value)) { const fillOtherField = async (
const child = childAttachTableRefs.value[pcode]; row: TableRow,
const matchingKeys = Object.keys(tplItemMap.value).filter(key => item: ContentItem,
key.startsWith(pcode + '_') valueMap: Record<string, string>
); ) => {
const key = getFieldKey(row.code, item.otherField!)
const attachTableData = {}; const tplItem = tplItemMap.value[key]
for (const key of matchingKeys) { if (!tplItem) return
const tmpData = tplItemMap.value[key];
if (!tmpData) continue; const { pid, itemid } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal !== undefined) {
formData[key] = dataVal
}
}
const fillAttachTable = async (row: TableRow, valueMap: Record<string, string>) => {
const child = childAttachTableRefs.value[row.code]
if (!child) return
const tableData: Record<string, any> = {}
for (const data of row.datas || []) {
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'object' && value.field) {
const fieldKey = getFieldKey(row.code, value.field)
const tplItem = tplItemMap.value[fieldKey]
if (!tplItem) continue
const { pid, itemid, formTp, code } = tmpData; const { pid, itemid, formTp, code } = tplItem
const valueKey = `${pid}_${itemid}_1`; const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (formTp === 'checkbox') { if (dataVal) {
attachTableData[code] = valueObj[valueKey]?.split(",") || []; tableData[code] = formTp === 'checkbox' ? dataVal.split(',') : dataVal
} else {
attachTableData[code] = valueObj[valueKey] || "";
} }
} }
child.setFormData(attachTableData);
} }
}
child.setFormData(tableData)
}
for (const pcode of Object.keys(childMultiTableRefs.value)) { const fillMultiColumnTable = async (row: TableRow, valueMap: Record<string, string>) => {
const child = childMultiTableRefs.value[pcode]; const child = childMultiTableRefs.value[row.code]
const matchingKeys = Object.keys(tplItemMap.value).filter(key => if (!child || !Array.isArray(row.content)) return
key.startsWith(pcode + '_')
); const rowsMap: Record<string, Record<string, string>> = {}
const rowsMap = {}; for (const item of row.content) {
for (const key of matchingKeys) { if (!item.field) continue
const tmpData = tplItemMap.value[key];
if (!tmpData) continue; const key = getFieldKey(row.code, item.field)
const tplItem = tplItemMap.value[key]
const { pid, itemid, code } = tmpData; if (!tplItem) continue
const valKeys = Object.keys(valueObj).filter(k =>
k.startsWith(`${pid}_${itemid}_`) const { pid, itemid, code } = tplItem
); const valueKeys = Object.keys(valueMap).filter(k =>
k.startsWith(`${pid}_${itemid}_`)
for (const valKey of valKeys) { )
const parts = valKey.split('_');
const rind = parts[2]; for (const valueKey of valueKeys) {
const [, , rind] = valueKey.split('_')
if (!rowsMap[rind]) { if (!rowsMap[rind]) {
rowsMap[rind] = {}; rowsMap[rind] = {}
}
rowsMap[rind][code] = valueObj[valKey];
}
} }
rowsMap[rind][code] = valueMap[valueKey]
const tableDatas = Object.values(rowsMap);
nextTick();
child.setFormData(tableDatas);
} }
} catch (error) {
VxeUI.modal.message({
content: `加载数据失败: ${error.message}`,
status: 'error'
});
} finally {
loading.value = false;
} }
const tableDatas = Object.values(rowsMap)
child.setFormData(tableDatas)
} }
const fillVxeTable = async (row: TableRow, valueMap: Record<string, string>) => {
async function setTplItemMap() { const child = childMyVexTableRefs.value[row.code]
try { if (!child || !Array.isArray(row.columns)) return
const taskId = queryParam.value.taskId
const tplid = queryParam.value.tplId const rowsMap: Record<string, Record<string, string>> = {}
const tplItem = await allTplItems({
tplid: tplid for (const column of row.columns) {
}) if (!column.field) continue
tplItemMap.value = tplItem.reduce((acc, item) => { const key = getFieldKey(row.code, column.field)
const strKey = `${item.pcode}_${item.xmlcode}` const tplItem = tplItemMap.value[key]
acc[strKey] = { if (!tplItem || Object.keys(tplItem).length === 0) continue
pid: item.pid,
itemid: item.id, const { pid, itemid, code } = tplItem
formTp: item.formTp, const valueKeys = Object.keys(valueMap).filter(k =>
code: item.xmlcode k.startsWith(`${pid}_${itemid}_`)
)
for (const valueKey of valueKeys) {
const [, , rind] = valueKey.split('_')
if (!rowsMap[rind]) {
rowsMap[rind] = {}
} }
return acc rowsMap[rind][code] = valueMap[valueKey]
}, {} as Record<string, any>) }
} catch (error) {
console.error('加载数据失败:', error)
VxeUI.modal.message({ content: `加载数据失败: ${error instanceof Error ? error.message : String(error)}`, status: 'error' })
} }
const tableDatas = Object.values(rowsMap)
child.setFormData(tableDatas)
} }
// 校验数据 // 校验相关
const checkData = () => { const validateData = () => {
drawerVisible.value = true drawerVisible.value = true
validationDrawerRef.value.setValidateData(validFormula.value, formData) validationDrawerRef.value.setValidateData(validFormula.value,formData);
} }
const handleInputBlur = (rowCode: string, field: string, formula?: string) => { const handleInputBlur = (rowCode: string, field: string, formula?: string) => {
if (!formula) return; if (!formula) return
const key = getFieldKey(rowCode, field); const key = getFieldKey(rowCode, field)
const value = formData[key]; const value = formData[key]
if (!value || value === '') { if (!value || value === '') {
delete inputErrors.value[key]; delete inputErrors.value[key]
return; return
} }
validateFieldFormula(rowCode, field, formula); validateFieldFormula(rowCode, field, formula)
}; }
const validateFieldFormula = (rowCode: string, field: string, formula: string) => { const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
const key = getFieldKey(rowCode, field); const key = getFieldKey(rowCode, field)
try { try {
const expression = buildExpression(formula, rowCode); const expression = buildExpression(formula, rowCode)
const isValid = eval(expression); const isValid = eval(expression)
if (!isValid) { if (!isValid) {
inputErrors.value[key] = { inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容", message: "公式校验失败,请检查填写内容",
formula formula
}; }
} else { } else {
delete inputErrors.value[key]; delete inputErrors.value[key]
} }
} catch (error: any) { } catch (error: any) {
inputErrors.value[key] = { inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容", message: "公式校验失败,请检查填写内容",
formula, formula,
error: error.message error: error.message
}; }
} }
}; }
const buildExpression = (formula: string, rowCode: string): string => { const buildExpression = (formula: string, rowCode: string): string => {
const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []; const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []
const uniqueFields = [...new Set(fieldNames.filter(f => const uniqueFields = [...new Set(fieldNames.filter(f =>
!['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false'] !['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase()) .includes(f.toLowerCase())
))]; ))]
uniqueFields.sort((a, b) => b.length - a.length); uniqueFields.sort((a, b) => b.length - a.length)
let expression = formula; let expression = formula
for (const fieldName of uniqueFields) { for (const fieldName of uniqueFields) {
const key = getFieldKey(rowCode, fieldName); const key = getFieldKey(rowCode, fieldName)
const value = formData[key]; const value = formData[key]
if (value !== undefined) { if (value !== undefined) {
// 检查字段类型,如果是日期类型,转换为时间戳进行比较
const row = tableFormData.find((r: TableRow) => r.code === rowCode)
let processedValue = value
if (row && row.content && Array.isArray(row.content)) {
const fieldItem = row.content.find((c: ContentItem) => c.field === fieldName)
if (fieldItem && fieldItem.type === 'date') {
// 将日期字符串转换为时间戳(毫秒)
const date = new Date(value)
if (!isNaN(date.getTime())) {
processedValue = date.getTime()
}
}
}
expression = expression.replace( expression = expression.replace(
new RegExp(`\\b${fieldName}\\b`, 'g'), new RegExp(`\\b${fieldName}\\b`, 'g'),
`Number(${value})` `Number(${processedValue})`
); )
} }
} }
return expression; return expression
}; }
// 提示工具相关
const toggleTooltip = (rowCode: string, field: string) => { const toggleTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field); const key = getFieldKey(rowCode, field)
if (hoveredKey.value === key) { if (hoveredKey.value === key) {
showTooltip.value = false; showTooltip.value = false
hoveredKey.value = ''; hoveredKey.value = ''
} else { } else {
showTooltip.value = true; showTooltip.value = true
hoveredKey.value = key; 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 showHelpIcon = (rowCode: string, field: string, item: any): boolean => {
if (item.hasValidFormula !== undefined) {
return item.hasValidFormula === true;
}
const key = getFieldKey(rowCode, field);
const hasRight = userAllocItems.value.includes(key);
if (!hasRight) return false;
const formulaResult = findEarliestFormulaForField(validFormula.value, field);
return !!formulaResult.formula;
};
// 处理校验结果点击
const 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)
historyDrawerVisible.value = true historyDrawerVisible.value = true
} }
// 关闭历史填报抽屉
const closeHistoryDrawer = () => { const closeHistoryDrawer = () => {
historyDrawerVisible.value = false historyDrawerVisible.value = false
} }
const setFormItemRight = () => { // 校验结果处理
console.log('开始设置表单权限...') const handleValidationResultClick = (result: any) => {
console.log('userAllocItems 数量:', userAllocItems.value.length) const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
console.log('validFormula 数量:', validFormula.value.length) const status = result.isValid ? 'success' : 'error'
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => {
if (item.field) {
const key = getFieldKey(row.code, item.field)
item.hasRight = userAllocItems.value.includes(key)
console.log(`字段 ${key}: hasRight = ${item.hasRight}`)
if (item.hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formulaResult.formula
item.matchedFormula = formulaResult.formula || null
console.log(`字段 ${key}: hasValidFormula = ${item.hasValidFormula}`)
} else {
item.hasValidFormula = false
item.matchedFormula = null
}
}
})
}
})
console.log('表单权限设置完成') VxeUI.modal.message({ content: message, status })
} }
// 工具函数
const findEarliestFormulaForField = ( const findEarliestFormulaForField = (
formulas: FormulaItem[], formulas: FormulaItem[],
field: string field: string
...@@ -811,185 +1016,129 @@ const findEarliestFormulaForField = ( ...@@ -811,185 +1016,129 @@ const findEarliestFormulaForField = (
return result return result
} }
const refreshHelpIcons = () => { // 消息提示
console.log('刷新帮助图标...') const showSuccessMessage = (message: string) => {
tableFormData.forEach((row: any) => { VxeUI.modal.message({ content: message, status: 'success' })
if (row.content && Array.isArray(row.content)) { }
row.content.forEach((item: any) => {
if (item.field) { const showWarningMessage = (message: string) => {
const key = getFieldKey(row.code, item.field) VxeUI.modal.message({ content: message, status: 'warning' })
const hasRight = userAllocItems.value.includes(key) }
if (hasRight) { const showErrorMessage = (message: string) => {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field) VxeUI.modal.message({ content: message, status: 'error' })
item.hasValidFormula = !!formulaResult.formula
item.matchedFormula = formulaResult.formula || null
} else {
item.hasValidFormula = false
item.matchedFormula = null
}
}
})
}
})
nextTick(() => {
})
} }
const mergeCells = ref<VxeTablePropTypes.MergeCells>([ // 子组件引用设置
{ row: 0, col: 1, rowspan: 1, colspan: 2 }, const setMultiColumnTableRef = (el: any, code: string) => {
{ row: 1, col: 1, rowspan: 1, colspan: 2 }, if (el) childMultiTableRefs.value[code] = el
{ row: 2, col: 1, rowspan: 1, colspan: 2 }, }
{ row: 3, col: 1, rowspan: 6, colspan: 1 },
{ row: 9, col: 1, rowspan: 1, colspan: 2}, const setAttachTableRef = (el: any, code: string) => {
{ row: 10, col: 1, rowspan: 1, colspan: 2}, if (el) childAttachTableRefs.value[code] = el
]) }
const setMyVxeTableRef = (el: any, code: string) => {
if (el) childMyVexTableRefs.value[code] = el
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
// Base table styles
.bank-report-table { .bank-report-table {
font-family: "SimSun", "宋体", serif; font-family: "SimSun", "宋体", serif;
font-size: 12px; font-size: 12px;
color: #000;
margin: 5px auto; margin: 5px auto;
width: 90%; width: 90%;
} position: relative;
.toolbar-info {
margin: 10px;
.info-label { // Shared table styles
font-weight: bold; .custom-table, .attachment-table {
margin-left: 20px; width: 100%;
border: 1px solid #000;
&:first-child { .vxe-header--column,
margin-left: 0; .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
} }
} }
} }
.custom-table { // 表格信息文本
width: 100%; .table-info-text {
border: 1px solid #000; font-weight: bold;
border-collapse: collapse;
:deep(.vxe-header--column),
:deep(.vxe-body--column) {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
}
/* 加载遮罩层样式 */
.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; .project-name {
color: #666; font-weight: bold;
font-size: 14px; font-size: 14px;
} }
// Cell styles
.content-cell { .content-cell {
line-height: 1.8; line-height: 1.8;
padding: 5px;
} }
.project-title { // 表单控件组
font-weight: bold; .radio-group, .checkbox-group {
font-size: 14px;
}
.table-input,
.vxe-input {
height: 24px;
line-height: 24px;
min-width: 150px;
margin: 0 2px;
padding: 0 5px;
border: none;
border-bottom: 1px solid #333;
background: transparent;
text-align: center;
}
.extra-input {
width: 200px;
margin-left: 5px;
}
.extra-fields-container {
margin-left: 40px;
margin-top: 5px;
}
.vxe-radio-group,
.vxe-checkbox-group {
display: inline-block; display: inline-block;
margin-right: 5px; margin-left: 10px;
max-width: 99%;
} .vxe-radio, .vxe-checkbox {
margin-left: 0px;
.radio-group, white-space: nowrap;
.checkbox-group { line-height: 30px;
display: inline-block; padding: 0px 10px;
max-width: 99%;
} &--label {
font-size: 12px;
.vxe-radio, }
.vxe-checkbox {
margin: 10px; &--icon {
white-space: nowrap; font-size: 12px;
}
}
} }
.unit { // VXE specific overrides
margin-left: 3px; .vxe {
color: #666; &-radio-group, &-checkbox-group {
display: inline-block;
}
&-cell {
padding: 5px;
}
} }
.embedded-table { .vxe-input {
margin: 0; border-bottom: 1px solid #333;
text-align: center;
margin: 5px 3px;
padding: 0; padding: 0;
border: none; 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 {
margin-left: 5px;
font-size: 12px;
}
// 帮助图标
.help-icon { .help-icon {
margin-left: 5px; margin-left: 5px;
cursor: pointer; cursor: pointer;
...@@ -1004,27 +1153,13 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -1004,27 +1153,13 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
background: #e6f7ff; background: #e6f7ff;
border-radius: 50%; border-radius: 50%;
transition: all 0.3s; transition: all 0.3s;
opacity: 0;
animation: fadeIn 0.5s forwards;
animation-delay: 0.3s;
&:hover { &:hover {
background: #bae7ff; background: #bae7ff;
transform: scale(1.1);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
} }
} }
// 错误图标
.error-icon { .error-icon {
margin-left: 5px; margin-left: 5px;
cursor: pointer; cursor: pointer;
...@@ -1042,6 +1177,7 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -1042,6 +1177,7 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
} }
} }
// 提示框
.tooltip, .error-tooltip { .tooltip, .error-tooltip {
position: absolute; position: absolute;
top: 100%; top: 100%;
...@@ -1074,15 +1210,54 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -1074,15 +1210,54 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
.error-tooltip { .error-tooltip {
background: #8b0000; background: #8b0000;
color: #fff; color: #fff;
border: 1px solid #ff4d4f;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
bottom: 100%; bottom: 100%;
left: 10px; left: 5px;
border-width: 5px; border-width: 5px;
border-style: solid; border-style: solid;
border-color: transparent transparent #8b0000 transparent; border-color: transparent transparent #8b0000 transparent;
} }
} }
// 加载遮罩
.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> </style>
\ No newline at end of file
<template> <template>
<div class="bank-report-table" style="height: 80vh" @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">
...@@ -8,6 +12,7 @@ ...@@ -8,6 +12,7 @@
</div> </div>
</div> </div>
<!-- 工具栏 -->
<vxe-toolbar> <vxe-toolbar>
<template #buttons> <template #buttons>
<div style="margin:10px"> <div style="margin:10px">
...@@ -43,14 +48,7 @@ ...@@ -43,14 +48,7 @@
</template> </template>
</vxe-toolbar> </vxe-toolbar>
<!-- 校验抽屉 --> <!-- 主表格 -->
<ValidationDrawer
ref="validationDrawerRef"
v-model="drawerVisible"
:tableFormData="tableFormData"
@validationResultClick="handleValidationResultClick"
/>
<vxe-table <vxe-table
border border
ref="tableRef" ref="tableRef"
...@@ -70,17 +68,20 @@ ...@@ -70,17 +68,20 @@
</vxe-column> </vxe-column>
<vxe-column field="content" title="内容"> <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="[]" :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
:title="row.project" :title="row.project"
...@@ -109,162 +110,107 @@ ...@@ -109,162 +110,107 @@
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" style="margin-right: 10px"></span> <!-- 文本类型 -->
<span v-else-if="item.type === 'br'"><br /></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 === 'brspace'"> <span v-else-if="item.type === 'brspace'">
<br /> <br>
<template v-for="i in item.value || 1" :key="i"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </template> <template v-for="i in (item.value || 1)" :key="i">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</template>
</span> </span>
<template v-else-if="item.type === 'MultiColumnTable'"> <!-- 单选组 -->
<MultiColumnTable
:title="item.project"
:records="[]"
:fields="item.content"
:ref="(el) => setMultiColumnTableRef(el, item.code)"
style="margin: 0px; padding: 0px"
/>
</template>
<template v-else-if="item.type === 'AttachTable'">
<AttachTable
:title="item.project"
:records="item.datas"
:fields="item.content"
:pcode="item.code"
:calcSum="item.calcSum"
:showFooter="item.showFooter"
:ref="(el) => setAttachTableRef(el, item.code)"
style="margin: 0px; padding: 0px"
/>
</template>
<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]"> <vxe-radio-group
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt">{{ opt }}</vxe-radio> v-model="formData[getFieldKey(row.code, item.field)]"
:disabled="!item.hasRight || isRadioDisabled(row.code, item)"
>
<vxe-radio
v-for="(opt, optIndex) in item.options"
:key="optIndex"
:label="opt"
>
{{ opt }}
</vxe-radio>
</vxe-radio-group> </vxe-radio-group>
</span> </span>
<div v-else-if="item.type === 'radio-group-extraFields'" class="radio-group">
<vxe-radio-group v-model="formData[row.code + '_' + item.field]"> <!-- 多选组(带其他选项) -->
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt.value">{{ opt.label }}</vxe-radio> <div v-else-if="item.type === 'checkbox-group'">
</vxe-radio-group> <span class="checkbox-group">
<div v-if="formData[row.code + '_' + item.field]" class="extra-fields"> <vxe-checkbox-group
<template v-for="(option, optIndex) in item.options" :key="optIndex"> v-model="formData[getFieldKey(row.code, item.field)]"
<template v-if="option.value === formData[row.code + '_' + item.field] && option.extraFields"> :disabled="!item.hasRight"
<template v-for="(ccopt, ccind) in option.extraFields" :key="ccind"> >
<span>{{ ccopt.label }}: </span> <vxe-checkbox
<template v-if="ccopt.formType === 'number'"> v-for="(opt, optIndex) in item.options"
<vxe-input v-model="formData[row.code + '_' + ccopt.field]" type="number" style="width: 80px"></vxe-input> :key="optIndex"
</template> :label="opt"
<template v-else-if="ccopt.formType === 'input'"> >
<vxe-input v-model="formData[row.code + '_' + ccopt.field]" style="width: 250px"></vxe-input>
</template>
<template v-else>
<vxe-input v-model="formData[row.code + '_' + ccopt.field]" :type="ccopt.formType" style="width: 100px"></vxe-input>
</template>
<span class="unit"> {{ ccopt.unit }}</span>
</template>
</template>
</template>
</div>
</div>
<div v-else-if="item.type === 'checkbox-group'" class="checkbox-group">
<span class="checkbox-group" style="max-width: 800px; 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="line-height: 30px; margin-left: 0px">
{{ opt }} {{ opt }}
</vxe-checkbox> </vxe-checkbox>
<template v-if="item.otherOption"> <template v-if="item.otherField">
<vxe-checkbox label="其他" style="margin: 0px"> 其他: </vxe-checkbox> <div style="width:100%">
<vxe-input <vxe-checkbox label="其他" :disabled="!item.hasRight">
v-model="formData[row.code + '_' + item.otherField]" 其他:
:disabled="!(formData[row.code + '_' + item.field]?.indexOf('其他') > -1)" </vxe-checkbox>
style="width: 300px" <vxe-input
> v-model="formData[getFieldKey(row.code, item.otherField)]"
</vxe-input> :disabled="!isOtherFieldEnabled(row.code, item) || !item.hasRight"
style="width: 50%"
@blur="handleInputBlur(row.code, item.otherField, item.matchedFormula?.formula)"
/>
</div>
</template> </template>
</vxe-checkbox-group> </vxe-checkbox-group>
<!-- 其他选项 -->
</span> </span>
</div> </div>
<!-- 多选带输入框 -->
<div v-else-if="item.type === 'checkboxAndInput'"> <div v-else-if="item.type === 'checkboxAndInput'">
<span class="checkbox-group"> <span style="margin-left:20px">
<vxe-checkbox-group v-model="formData[row.code + '_' + item.field]"> <vxe-checkbox-group
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt" style="display: block; margin: 0px 0px"> v-model="formData[getFieldKey(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 }} {{ opt }}
<vxe-input <vxe-input
type="number" type="number"
v-model="formData[row.code + '_' + item.optionItemField[optIndex]]" v-model="formData[getFieldKey(row.code, item.optionItemField[optIndex])]"
style="width: 100px; margin: 0px" :disabled="!item.hasRight"
> style="width: 100px;margin: 0px;"
</vxe-input> @blur="handleInputBlur(row.code, item.optionItemField[optIndex], item.matchedFormula?.formula)"
/>
<span class="unit"> {{ item.optionItemUint }}</span> <span class="unit"> {{ item.optionItemUint }}</span>
</vxe-checkbox> </vxe-checkbox>
</vxe-checkbox-group> </vxe-checkbox-group>
</span> </span>
</div>
<div v-else-if="item.type === 'yesno'" class="yesno-group">
<vxe-radio-group v-model="formData[row.code + '_' + item.field]">
<vxe-radio label="是"></vxe-radio>
<vxe-radio label="否"></vxe-radio>
</vxe-radio-group>
<div v-if="formData[row.code + '_' + item.field] === '是'" class="extra-fields">
<!-- 处理单个extraField的情况 -->
<template v-if="item.extraField">
<div style="margin-top: 5px">
<span>{{ item.extraLabel || '' }}: </span>
<vxe-input v-model="formData[row.code + '_' + item.extraField]" style="width: 100px"></vxe-input>
<span v-if="item.unit" class="unit"> {{ item.unit }}</span>
</div>
</template>
<!-- 处理extraFields数组的情况 -->
<template v-else-if="item.extraFields">
<template v-for="(ccopt, ccind) in item.extraFields" :key="ccind">
<div style="margin-top: 5px">
<span>{{ ccopt.label }}: </span>
<template v-if="ccopt.formType === 'input'">
<vxe-input v-model="formData[row.code + '_' + ccopt.field]" style="width: 250px"></vxe-input>
</template>
<template v-else-if="ccopt.formType === 'checkbox'">
<vxe-checkbox-group v-model="formData[row.code + '_' + ccopt.field]">
<vxe-checkbox v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt">{{ opt }}</vxe-checkbox>
</vxe-checkbox-group>
<template v-if="ccopt.otherOption">
<div style="margin-top: 5px; margin-left: 20px">
其他:<vxe-input
v-model="formData[row.code + '_' + ccopt.otherField]"
:disabled="
!(
Array.isArray(formData[row.code + '_' + ccopt.field]) &&
formData[row.code + '_' + ccopt.field].indexOf('其他') > -1
)
"
style="width: 200px"
>
</vxe-input>
</div>
</template>
</template>
<template v-else-if="ccopt.formType === 'radio'">
<vxe-radio-group v-model="formData[row.code + '_' + ccopt.field]">
<vxe-radio v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt">{{ opt }}</vxe-radio>
</vxe-radio-group>
<template v-if="ccopt.otherOption && formData[row.code + '_' + ccopt.field] === '其他'">
<div style="margin-top: 5px; margin-left: 20px">
其他:<vxe-input v-model="formData[row.code + '_' + ccopt.otherField]" style="width: 200px"></vxe-input>
</div>
</template>
</template>
</div>
</template>
</template>
</div>
</div> </div>
<!-- 普通输入框 -->
<template v-else> <template v-else>
<div class="input-wrapper"> <div class="input-wrapper">
<vxe-input <vxe-input
:disabled="!item.hasRight || isInputDisabled(row.code, item)"
:type="item.type" :type="item.type"
v-model="formData[getFieldKey(row.code, item.field)]" v-model="formData[getFieldKey(row.code, item.field)]"
size="mini" size="mini"
...@@ -272,14 +218,13 @@ ...@@ -272,14 +218,13 @@
:style="item.style" :style="item.style"
@blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)" @blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
/> />
<span class="unit"> {{ item.unit }}</span> <span class="unit">{{ item.unit }}</span>
<!-- 帮助图标 - 修改这里 --> <!-- 帮助图标 -->
<span <span
v-if="showHelpIcon(row.code, item.field, item)" v-if="item.hasValidFormula"
class="help-icon" class="help-icon"
@click.stop="toggleTooltip(row.code, item.field)" @click.stop="toggleTooltip(row.code, item.field)"
title="点击查看校验规则"
> >
? ?
</span> </span>
...@@ -289,7 +234,6 @@ ...@@ -289,7 +234,6 @@
v-if="inputErrors[getFieldKey(row.code, item.field)]" v-if="inputErrors[getFieldKey(row.code, item.field)]"
class="error-icon" class="error-icon"
@click.stop="toggleErrorTooltip(row.code, item.field)" @click.stop="toggleErrorTooltip(row.code, item.field)"
title="点击查看错误详情"
> >
<i class="vxe-icon-error"></i> <i class="vxe-icon-error"></i>
</span> </span>
...@@ -315,7 +259,7 @@ ...@@ -315,7 +259,7 @@
<!-- 校验规则提示 --> <!-- 校验规则提示 -->
<div <div
v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field) && item.matchedFormula" v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)"
class="tooltip" class="tooltip"
@click.stop @click.stop
> >
...@@ -328,937 +272,983 @@ ...@@ -328,937 +272,983 @@
</div> </div>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<!-- 校验抽屉 -->
<ValidationDrawer
ref="validationDrawerRef"
v-model="drawerVisible"
:tableFormData="tableFormData"
@validationResultClick="handleValidationResultClick"
/>
<!-- 历史填报检查组件 --> <!-- 历史填报抽屉 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @close-drawer="closeHistoryDrawer" /> <HistoryFillCheck
<!-- 原有检查组件 --> ref="historyFillCheckRef"
<CheckTbData ref="refCheckTbData"></CheckTbData> v-model="historyDrawerVisible"
@closeDrawer="closeHistoryDrawer"
/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import MultiColumnTable from '../tableComponents/MultiColumnTable.vue'; import { ref, reactive, onMounted, computed } from 'vue'
import AttachTable from '../tableComponents/AttachTable.vue'; import { VxeUI, VxeTableInstance } from 'vxe-table'
import MyVxeTable1 from '../tableComponents/MyVxeTable.vue';
import CheckTbData from './CheckTbData.vue'; import 'vxe-table/lib/style.css'
import HistoryFillCheck from './check/historyFillCheck.vue'; import { useRoute } from 'vue-router'
import ValidationDrawer from './check/ValidationDrawer.vue'; // 组件导入
import { tableFormData } from '../../data/tb6.data'; import MultiColumnTable from '../tableComponents/MultiColumnTable.vue'
import { ref, reactive, nextTick, onMounted, toRaw, computed, watch } from 'vue'; import AttachTable from '../tableComponents/AttachTable.vue'
import { VxeUI, VxeToolbarInstance, VxeToolbarPropTypes } from 'vxe-table'; import MyVxeTable1 from '../tableComponents/MyVxeTable.vue'
import { batchSaveOrUpdate, queryRecord, batchSaveOrUpdateBeforeDelete } from '../../record/BaosongTaskRecord.api'; import HistoryFillCheck from './check/HistoryFillCheck.vue'
import { queryAllTplItemForUser, findUserRightForTplItem } from '../../alloc/BaosongTaskAlloc.api'; import ValidationDrawer from './check/ValidationDrawer.vue'
import { allTplItems } from '../../tpl/BaosongTplItem.api';
import { getTblvalidFormula } from '../../tpl/BaosongDataValid.api'; // 数据导入
import { useRoute } from 'vue-router'; import { tableFormData } from '../../data/tb6.data'
const refCheckTbData = ref(); // API 导入
const historyFillCheckRef = ref<any>(null); import {
const validationDrawerRef = ref(); batchSaveOrUpdate,
queryRecord,
const route = useRoute(); batchSaveOrUpdateBeforeDelete
const tableRef = ref(); } from '../../record/BaosongTaskRecord.api'
const tplItemMap = ref({}); import {
const queryParam = ref({ queryAllTplItemForUser,
taskId: -1, findUserRightForTplItem
taskName: '', } from '../../alloc/BaosongTaskAlloc.api'
tplId: -1, import { allTplItems } from '../../tpl/BaosongTplItem.api'
tplName: '', import { getTblvalidFormula } from '../../tpl/BaosongDataValid.api'
tplCode: '',
comfrom: '' // 类型定义
}); interface QueryParam {
taskId: number
// 权限相关状态 taskName: string
const userAllocItems = ref<string[]>([]); tplId: number
const validFormula = ref<any[]>([]); tplName: string
const drawerVisible = ref(false); tplCode: string
const historyDrawerVisible = ref(false); comfrom: string
const showTooltip = ref(false); }
const hoveredKey = ref('');
const showErrorTooltip = ref(false); interface FormDataItem {
const hoveredErrorKey = ref(''); id: number | null
const isInitialized = ref(false); taskid: number
tplid: number
interface FormData { itemid?: number
id: number | null; itempid?: number
taskid: number; content: string
tplid: number; tplcode: string
itemid?: number; rind: number
itempid?: number; }
content: string;
tplcode: string; interface FormulaItem {
rind: number; formula: string
} des: string
[key: string]: any
interface FormulaItem { }
formula: string;
des: string; interface ContentItem {
[key: string]: any; type: string
} field: string
value?: any
interface InputError { hasRight?: boolean
message: string; hasValidFormula?: boolean
formula: string; matchedFormula?: FormulaItem
error?: string; 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
}
interface TplItemMapValue {
pid: number
itemid: number
formTp: string
code: string
}
// 路由和组件引用
const route = useRoute()
const tableRef = ref<VxeTableInstance>()
const historyFillCheckRef = ref()
const validationDrawerRef = ref()
// 响应式状态
const loading = ref(true)
const historyDrawerVisible = ref(false)
const drawerVisible = ref(false)
const showTooltip = ref(false)
const hoveredKey = ref('')
const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
// 数据状态
const queryParam = ref<QueryParam>({
taskId: -1,
taskName: '',
tplId: -1,
tplName: '',
tplCode: '',
comfrom: ''
})
const formData = reactive<Record<string, any>>({})
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 childMultiTableRefs = ref<Record<string, any>>({})
const childAttachTableRefs = ref<Record<string, any>>({})
const childMyVexTableRefs = ref<Record<string, any>>({})
// 计算属性
const fieldKeys = computed(() => Object.keys(formData))
// 工具函数
const getFieldKey = (rowCode: string, field: string): string => {
return `${rowCode}_${field}`
}
const isRadioDisabled = (rowCode: string, item: ContentItem): boolean => {
if (!item.relatedFiled) return false
const relatedKey = getFieldKey(rowCode, item.relatedFiled)
return formData[relatedKey] !== '是'
}
const isInputDisabled = (rowCode: string, item: ContentItem): boolean => {
if (!item.relatedFiled) return false
const relatedKey = getFieldKey(rowCode, item.relatedFiled)
return formData[relatedKey] !== '是'
}
const isOtherFieldEnabled = (rowCode: string, item: ContentItem): boolean => {
const checkboxKey = getFieldKey(rowCode, item.field)
const checkboxValue = formData[checkboxKey] || []
return checkboxValue.includes('其他')
}
// 初始化
onMounted(async () => {
await initPage()
})
const initPage = async () => {
try {
loading.value = true
await initQueryParams()
await Promise.all([
loadUserRights(),
loadValidationFormulas(),
loadTemplateItems()
])
await setFormItemRight()
await loadTableData()
} catch (error: any) {
showErrorMessage(`初始化页面失败: ${error.message}`)
} finally {
loading.value = false
} }
}
onMounted(async () => { const initQueryParams = () => {
if (route.query.taskId) { const { taskId, taskName, tplId, tplName, tplCode,comfrom } = route.query
queryParam.value.taskId = Number(route.query.taskId);
} if (taskId) queryParam.value.taskId = Number(taskId)
if (route.query.taskName) { if (taskName) queryParam.value.taskName = String(taskName)
queryParam.value.taskName = String(route.query.taskName); if (tplId) queryParam.value.tplId = Number(tplId)
} if (tplName) queryParam.value.tplName = String(tplName)
if (route.query.tplId) { if (tplCode) queryParam.value.tplCode = String(tplCode)
queryParam.value.tplId = Number(route.query.tplId); if (comfrom) queryParam.value.comfrom = String(comfrom)
} }
if (route.query.tplName) {
queryParam.value.tplName = String(route.query.tplName); const loadUserRights = async () => {
} userAllocItems.value = await findUserRightForTplItem({
if (route.query.tplCode) { tplid: queryParam.value.tplId,
queryParam.value.tplCode = String(route.query.tplCode); taskid: queryParam.value.taskId
} })
if (route.query.comfrom) { }
queryParam.value.comfrom = String(route.query.comfrom);
const loadValidationFormulas = async () => {
validFormula.value = await getTblvalidFormula({
tplid: queryParam.value.tplId
})
}
const loadTemplateItems = async () => {
const items = await allTplItems({
tplid: queryParam.value.tplId
})
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
// 初始化数据 }, {})
await refCheckTbData.value.initData(); }
// 获取权限和验证公式 const setFormItemRight = () => {
try { tableFormData.forEach((row: TableRow) => {
userAllocItems.value = await findUserRightForTplItem({ if (row.content && Array.isArray(row.content)) {
tplid: queryParam.value.tplId, row.content.forEach((item: ContentItem) => {
taskid: queryParam.value.taskId, if (item.field) {
}); const key = getFieldKey(row.code, item.field)
item.hasRight = userAllocItems.value.includes(key)
validFormula.value = await getTblvalidFormula({
tplid: queryParam.value.tplId, if (item.hasRight) {
}); const { formula } = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formula
console.log('获取到的权限:', userAllocItems.value.length); item.matchedFormula = formula
console.log('获取到的公式:', validFormula.value.length); }
} catch (error) { }
console.error('获取权限或验证公式失败:', error); })
} }
})
}
await setTplItemMap(); // 保存相关
await setFormItemRight(); const saveBatch = async () => {
await setData(); try {
if (!await validateForm()) return
// 标记初始化完成 await prepareFormData()
isInitialized.value = true;
// 延迟刷新一次,确保问号图标显示 if (formValues.value.length === 0) {
setTimeout(() => { showWarningMessage('没有需要保存的数据')
refreshHelpIcons(); return
}, 300);
});
const loading = ref(false);
const formData = reactive({});
const formValues = ref<FormData[]>([]);
const inputErrors = ref<Record<string, InputError>>({});
const fieldKeys = computed(() => Object.keys(formData));
const getFieldKey = (rowCode: string, field: string): string => {
return `${rowCode}_${field}`;
};
// 添加帮助图标显示条件计算
const showHelpIcon = (rowCode: string, field: string, item: any): boolean => {
// 如果已经有 hasValidFormula 属性,直接使用
if (item.hasValidFormula !== undefined) {
return item.hasValidFormula === true;
} }
// 否则重新计算 await batchSaveOrUpdateBeforeDelete(formValues.value)
const key = getFieldKey(rowCode, field); showSuccessMessage('保存成功')
const hasRight = userAllocItems.value.includes(key); } catch (error: any) {
showErrorMessage(`保存失败: ${error.message}`)
if (!hasRight) return false; }
}
const formulaResult = findEarliestFormulaForField(validFormula.value, field);
return !!formulaResult.formula;
};
const saveBatch = async () => {
try {
formValues.value = [];
await getFillDatas();
if (formValues.value.length > 0) {
await batchSaveOrUpdateBeforeDelete(formValues.value);
VxeUI.modal.message({ content: '保存成功', status: 'success' });
} else {
VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' });
}
} catch (error: any) {
VxeUI.modal.message({ content: `保存失败: ${error.message}`, status: 'error' });
} finally {
loading.value = false;
}
};
const getFillDatas = async () => {
try {
formValues.value = [];
for (const strKey in formData) { const validateForm = async (): Promise<boolean> => {
const valData = formData[strKey]; const $table = tableRef.value
if (valData !== undefined && valData !== '') { if (!$table) return true
let tmpAry = strKey.split('_');
await setFormValues(tmpAry[0], tmpAry[1], valData, 1); const checkResult = $table.validate()
} if (!checkResult) {
} showErrorMessage('表单验证失败,请检查填写内容')
await getAttachTableFormData(); return false
await getAllMultiTableFormData(); }
await getVxeTableFormData(); return true
} catch (error) { }
console.log(error);
} finally {
}
};
const childMultiTableRefs = ref({}); const prepareFormData = async () => {
const setMultiColumnTableRef = (el: any, index: string) => { formValues.value = []
if (el) {
childMultiTableRefs.value[index] = el; // 处理主表单数据
for (const [key, value] of Object.entries(formData)) {
if (value !== undefined && value !== null && value !== '') {
const [pcode, code] = key.split('_')
await addFormValue(pcode, code, value, 1)
} }
}; }
const getAllMultiTableFormData = async () => { // 处理子表格数据
for (const pcode in childMultiTableRefs.value) { await Promise.all([
const child = childMultiTableRefs.value[pcode]; collectAttachTableData(),
if (child) { collectMultiTableData(),
const datas = await child.getFormData(); collectVxeTableData()
let rind = 0; ])
for (const obj of datas) { }
rind++;
Object.keys(obj).forEach((code) => { const addFormValue = async (pcode: string, code: string, value: any, rind: number) => {
setFormValues(pcode, code, obj[code], rind); if (!value) return
});
} const stringValue = Array.isArray(value) ? value.join(',') : String(value)
const key = getFieldKey(pcode, code)
const item = tplItemMap.value[key]
if (!item) return
const formItem: FormDataItem = {
id: null,
rind,
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId,
tplcode: code,
itemid: item.itemid,
itempid: item.pid,
content: stringValue
}
formValues.value.push(formItem)
}
// 数据收集函数
const collectAttachTableData = async () => {
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 childAttachTableRefs = ref({});
const setAttachTableRef = (el: any, index: string) => { const collectMultiTableData = async () => {
if (el) { for (const [pcode, child] of Object.entries(childMultiTableRefs.value)) {
childAttachTableRefs.value[index] = el; // 保存子组件实例 if (child) {
} const datas = await child.getFormData()
}; let rind = 0
for (const obj of datas) {
const childMyVexTableRefs = ref({}); rind++
const setMyVxeTableRef = (el: any, index: string) => { for (const [code, value] of Object.entries(obj)) {
if (el) { await addFormValue(pcode, code, value, rind)
childMyVexTableRefs.value[index] = el; // 保存子组件实例
}
};
const getAttachTableFormData = async () => {
for (const pcode in childAttachTableRefs.value) {
const child = childAttachTableRefs.value[pcode];
if (child) {
const datas = await child.getFormData();
for (const code in datas) {
await setFormValues(pcode, code, datas[code], 1);
} }
} }
} }
}; }
}
const getVxeTableFormData = async () => {
for (const pcode in childMyVexTableRefs.value) { const collectVxeTableData = async () => {
const child = childMyVexTableRefs.value[pcode]; for (const [pcode, child] of Object.entries(childMyVexTableRefs.value)) {
if (child) { if (child) {
const formData = await child.getFormData(); const formData = await child.getFormData()
console.log('formData', formData); let rind = 0
let rind = 0; // 表格数据
for (const obj of formData.datas) { for (const obj of formData.datas) {
rind++; rind++
Object.keys(obj).forEach((code) => { for (const [code, value] of Object.entries(obj)) {
setFormValues(pcode, code, obj[code], rind); await addFormValue(pcode, code, value, rind)
});
} }
if (formData.footer) { }
Object.keys(formData.footer).forEach((code) => { if (formData.footer) {
setFormValues(pcode, code, formData.footer[code], 1); for (const [code, value] of Object.entries(formData.footer)) {
}); await addFormValue(formData.footerPcode, code, value, 1)
} }
} }
} }
}; }
}
const setFormValues = async (pcode, code, valData, rind) => {
if (!valData) return; // 数据加载
let vals = Array.isArray(valData) ? valData.join(',') : valData; const loadTableData = async () => {
let strKey = pcode + '_' + code; try {
const item = tplItemMap.value[strKey]; loading.value = true
const { pid, itemid } = item ?? {};
let tempForm: FormData = {
id: null,
rind: rind,
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId,
tplcode: code,
itemid: itemid,
itempid: pid,
content: vals,
};
formValues.value.push(tempForm);
};
// 校验数据
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]; Object.keys(formData).forEach(key => delete formData[key])
if (!value || value === '') { const recordData = await queryRecord({
delete inputErrors.value[key]; taskid: queryParam.value.taskId,
return; tplid: queryParam.value.tplId
} })
validateFieldFormula(rowCode, field, formula); // 转换记录数据为便于查找的结构
}; const valueMap = recordData.reduce((acc: Record<string, string>, data) => {
const key = `${data.itempid}_${data.itemid}_${data.rind}`
const validateFieldFormula = (rowCode: string, field: string, formula: string) => { acc[key] = data.content
const key = getFieldKey(rowCode, field); return acc
}, {})
try { // 填充表格数据
const expression = buildExpression(formula, rowCode); await fillTableData(valueMap)
const isValid = eval(expression); } catch (error: any) {
if (!isValid) { showErrorMessage(`加载数据失败: ${error.message}`)
inputErrors.value[key] = { } finally {
message: "公式校验失败,请检查填写内容", loading.value = false
formula }
}; }
} else {
delete inputErrors.value[key]; const fillTableData = async (valueMap: Record<string, string>) => {
} for (const row of tableFormData) {
} catch (error: any) { if (!row.type && row.content) {
inputErrors.value[key] = { await fillSimpleFields(row, valueMap)
message: "公式校验失败,请检查填写内容", } else if (row.type === 'AttachTable' && row.datas) {
formula, await fillAttachTable(row, valueMap)
error: error.message } else if (row.type === 'MultiColumnTable' && row.content) {
}; await fillMultiColumnTable(row, valueMap)
} else if (row.type === 'VxeTable' && row.columns) {
await fillVxeTable(row, valueMap)
} }
}; }
}
const buildExpression = (formula: string, rowCode: string): string => {
const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []; const fillSimpleFields = async (row: TableRow, valueMap: Record<string, string>) => {
const uniqueFields = [...new Set(fieldNames.filter(f => if (!row.content || !Array.isArray(row.content)) return
!['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase()) for (const item of row.content) {
))]; if (!item.field) continue
uniqueFields.sort((a, b) => b.length - a.length); const key = getFieldKey(row.code, item.field)
const tplItem = tplItemMap.value[key]
if (!tplItem) continue
let expression = formula; const { pid, itemid, formTp } = tplItem
for (const fieldName of uniqueFields) { const valueKey = `${pid}_${itemid}_1`
const key = getFieldKey(rowCode, fieldName); const dataVal = valueMap[valueKey]
const value = formData[key];
if (value !== undefined) {
expression = expression.replace(
new RegExp(`\\b${fieldName}\\b`, 'g'),
`Number(${value})`
);
}
}
return expression; if (dataVal === undefined) continue
};
if (formTp === 'checkbox') {
const toggleTooltip = (rowCode: string, field: string) => { formData[key] = dataVal.split(',').filter(Boolean)
const key = getFieldKey(rowCode, field);
if (hoveredKey.value === key) {
showTooltip.value = false;
hoveredKey.value = '';
} else {
showTooltip.value = true;
hoveredKey.value = key;
}
};
const toggleErrorTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field);
if (hoveredErrorKey.value === key) {
showErrorTooltip.value = false;
hoveredErrorKey.value = '';
} else { } else {
showErrorTooltip.value = true; formData[key] = dataVal
hoveredErrorKey.value = key;
} }
};
const closeAllTooltips = () => {
showTooltip.value = false;
hoveredKey.value = '';
showErrorTooltip.value = false;
hoveredErrorKey.value = '';
};
const setFormItemRight = () => {
console.log('开始设置表单权限...');
console.log('userAllocItems 数量:', userAllocItems.value.length);
console.log('validFormula 数量:', validFormula.value.length);
tableFormData.forEach((row: any) => { // 处理特殊字段类型
if (row.content && Array.isArray(row.content)) { if (item.type === 'checkboxAndInput' && item.optionItemField) {
row.content.forEach((item: any) => { await fillCheckboxInputFields(row, item, valueMap)
if (item.field) { }
const key = getFieldKey(row.code, item.field);
item.hasRight = userAllocItems.value.includes(key);
console.log(`字段 ${key}: hasRight = ${item.hasRight}`);
if (item.hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field);
item.hasValidFormula = !!formulaResult.formula;
item.matchedFormula = formulaResult.formula || null;
console.log(`字段 ${key}: hasValidFormula = ${item.hasValidFormula}`);
} else {
item.hasValidFormula = false;
item.matchedFormula = null;
}
}
});
}
});
console.log('表单权限设置完成');
};
const findEarliestFormulaForField = (
formulas: FormulaItem[],
field: string
): { formula: FormulaItem | null, index: number } => {
let result = { formula: null, index: Infinity };
formulas?.forEach((f: FormulaItem) => {
const text = f.formula || '';
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g');
const match = regex.exec(text);
if (match && match.index < result.index) {
result = { formula: f, index: match.index };
}
});
return result; if (item.otherField) {
}; await fillOtherField(row, item, valueMap)
}
// 处理校验结果点击 }
const handleValidationResultClick = (result: any) => { }
const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`;
const status = result.isValid ? 'success' : 'error'; const fillCheckboxInputFields = async (
row: TableRow,
item: ContentItem,
valueMap: Record<string, string>
) => {
for (const field of item.optionItemField || []) {
const key = getFieldKey(row.code, field)
const tplItem = tplItemMap.value[key]
if (!tplItem) continue
VxeUI.modal.message({ content: message, status }); const { pid, itemid } = tplItem
}; const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
// 打开历史填报抽屉
const handleOpenHistoryDrawer = () => {
historyDrawerVisible.value = true;
};
// 关闭历史填报抽屉
const closeHistoryDrawer = () => {
historyDrawerVisible.value = false;
};
// 添加刷新帮助图标的方法
const refreshHelpIcons = () => {
console.log('刷新帮助图标...');
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => {
if (item.field) {
const key = getFieldKey(row.code, item.field);
const hasRight = userAllocItems.value.includes(key);
if (hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field);
item.hasValidFormula = !!formulaResult.formula;
item.matchedFormula = formulaResult.formula || null;
} else {
item.hasValidFormula = false;
item.matchedFormula = null;
}
}
});
}
});
// 强制更新视图 if (dataVal !== undefined) {
nextTick(() => { formData[key] = dataVal
console.log('帮助图标刷新完成');
});
};
// 原有检查方法
async function checkDataOrig() {
let tplName = queryParam.value.tplName;
await getFillDatas();
if (formValues.value.length > 0) {
refCheckTbData.value.setIniData(tplName, formValues.value);
} }
} }
}
const fillOtherField = async (
row: TableRow,
item: ContentItem,
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
}
}
async function setData() { const fillAttachTable = async (row: TableRow, valueMap: Record<string, string>) => {
try { const child = childAttachTableRefs.value[row.code]
loading.value = true; if (!child) return
const taskId = queryParam.value.taskId;
const tplid = queryParam.value.tplId; const tableData: Record<string, any> = {}
Object.keys(formData).forEach((key) => delete formData[key]); for (const data of row.datas || []) {
for (const [key, value] of Object.entries(data)) {
await setTplItemMap(); if (typeof value === 'object' && value.field) {
const recordData = await queryRecord({ taskid: taskId, tplid: tplid }); const fieldKey = getFieldKey(row.code, value.field)
const tplItem = tplItemMap.value[fieldKey]
const valueObj = {}; if (!tplItem) continue
for (const data of recordData) {
const key = `${data.itempid}_${data.itemid}_${data.rind}`; const { pid, itemid, formTp, code } = tplItem
valueObj[key] = data.content; const valueKey = `${pid}_${itemid}_1`
} const dataVal = valueMap[valueKey]
const shouldSkip = (key) => {
const refsToCheck = [childMultiTableRefs.value, childAttachTableRefs.value]; if (dataVal) {
tableData[code] = formTp === 'checkbox' ? dataVal.split(',') : dataVal
for (const refs of refsToCheck) {
for (const pcode of Object.keys(refs)) {
if (key.startsWith(pcode + '_')) {
return true;
}
}
}
return false;
};
for (const strKey of Object.keys(tplItemMap.value)) {
if (shouldSkip(strKey)) {
continue;
}
const item = tplItemMap.value[strKey];
if (!item) continue;
const { pid, itemid, formTp } = item;
const dataVal = valueObj[`${pid}_${itemid}_1`];
if (!dataVal) continue;
if (formTp === 'checkbox') {
formData[strKey] = dataVal?.split(',') || [];
} else {
formData[strKey] = dataVal || '';
}
}
for (const pcode of Object.keys(childAttachTableRefs.value)) {
const child = childAttachTableRefs.value[pcode];
const matchingKeys = Object.keys(tplItemMap.value).filter((key) => key.startsWith(pcode + '_'));
const attachTableData = {};
for (const key of matchingKeys) {
const tmpData = tplItemMap.value[key];
if (!tmpData) continue;
const { pid, itemid, formTp, code } = tmpData;
const valueKey = `${pid}_${itemid}_1`;
if (formTp === 'checkbox') {
attachTableData[code] = valueObj[valueKey]?.split(',') || [];
} else {
attachTableData[code] = valueObj[valueKey] || '';
}
}
child.setFormData(attachTableData);
}
for (const pcode of Object.keys(childMultiTableRefs.value)) {
const child = childMultiTableRefs.value[pcode];
const matchingKeys = Object.keys(tplItemMap.value).filter((key) => key.startsWith(pcode + '_'));
const rowsMap = {};
for (const key of matchingKeys) {
const tmpData = tplItemMap.value[key];
if (!tmpData) continue;
const { pid, itemid, code } = tmpData;
const valKeys = Object.keys(valueObj).filter((k) => k.startsWith(`${pid}_${itemid}_`));
for (const valKey of valKeys) {
const parts = valKey.split('_');
const rind = parts[2];
if (!rowsMap[rind]) {
rowsMap[rind] = {};
}
rowsMap[rind][code] = valueObj[valKey];
}
} }
const tableDatas = Object.values(rowsMap);
nextTick();
child.setFormData(tableDatas);
} }
} catch (error: any) {
VxeUI.modal.message({
content: `加载数据失败: ${error.message}`,
status: 'error',
});
} finally {
loading.value = false;
} }
} }
child.setFormData(tableData)
}
async function setTplItemMap() { const fillMultiColumnTable = async (row: TableRow, valueMap: Record<string, string>) => {
try { const child = childMultiTableRefs.value[row.code]
const tplid = queryParam.value.tplId; if (!child || !Array.isArray(row.content)) return
const tplItem = await allTplItems({
tplid: tplid, const rowsMap: Record<string, Record<string, string>> = {}
});
for (const item of row.content) {
tplItemMap.value = {}; if (!item.field) continue
tplItem.forEach((item) => {
let strKey = item.pcode + '_' + item.xmlcode; const key = getFieldKey(row.code, item.field)
tplItemMap.value[strKey] = { pid: item.pid, itemid: item.id, formTp: item.formTp, code: item.xmlcode }; const tplItem = tplItemMap.value[key]
}); if (!tplItem) continue
} catch (error: any) {
VxeUI.modal.message({ content: `加载数据失败: ${error.message}`, status: 'error' }); const { pid, itemid, code } = tplItem
} finally { 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]
} }
} }
</script>
const tableDatas = Object.values(rowsMap)
<style lang="less" scoped> child.setFormData(tableDatas)
.bank-report-table { }
font-family: 'SimSun', '宋体', serif;
font-size: 12px;
color: #000;
margin: 5px auto;
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 { const fillVxeTable = async (row: TableRow, valueMap: Record<string, string>) => {
display: flex; const child = childMyVexTableRefs.value[row.code]
flex-direction: column; if (!child || !Array.isArray(row.columns)) return
align-items: center;
const rowsMap: Record<string, Record<string, string>> = {}
for (const column of row.columns) {
if (!column.field) continue
const key = getFieldKey(row.code, column.field)
const tplItem = tplItemMap.value[key]
if (!tplItem || Object.keys(tplItem).length === 0) continue
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]
}
} }
.spinner { const tableDatas = Object.values(rowsMap)
width: 40px; child.setFormData(tableDatas)
height: 40px; }
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db; // 校验相关
border-radius: 50%; const validateData = () => {
animation: spin 2s linear infinite; 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)
}
@keyframes spin { const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
0% { const key = getFieldKey(rowCode, field)
transform: rotate(0deg);
try {
const expression = buildExpression(formula, rowCode)
const isValid = eval(expression)
if (!isValid) {
inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
formula
}
} else {
delete inputErrors.value[key]
} }
100% { } catch (error: any) {
transform: rotate(360deg); inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
formula,
error: error.message
} }
} }
}
.loading-text {
margin-top: 10px; const buildExpression = (formula: string, rowCode: string): string => {
color: #666; const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []
font-size: 14px; const uniqueFields = [...new Set(fieldNames.filter(f =>
} !['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase())
))]
uniqueFields.sort((a, b) => b.length - a.length)
let expression = formula
.custom-table { for (const fieldName of uniqueFields) {
width: 100%; const key = getFieldKey(rowCode, fieldName)
border: 1px solid #000; const value = formData[key]
}
if (value !== undefined) {
.custom-table .vxe-header--column, // 检查字段类型,如果是日期类型,转换为时间戳进行比较
.custom-table .vxe-body--column { const row = tableFormData.find((r: TableRow) => r.code === rowCode)
border-right: 1px solid #000; let processedValue = value
border-bottom: 1px solid #000;
padding: 5px; if (row && row.content && Array.isArray(row.content)) {
} const fieldItem = row.content.find((c: ContentItem) => c.field === fieldName)
if (fieldItem && fieldItem.type === 'date') {
.custom-table .vxe-cell { // 将日期字符串转换为时间戳(毫秒)
padding: 5px; const date = new Date(value)
} if (!isNaN(date.getTime())) {
processedValue = date.getTime()
.content-cell { }
line-height: 1.8; }
} }
.table-input { expression = expression.replace(
width: 80px; new RegExp(`\\b${fieldName}\\b`, 'g'),
margin: 0 2px; `Number(${processedValue})`
} )
}
.radio-group {
display: inline-block;
margin-right: 10px;
}
.checkbox-group {
display: inline-block;
margin-right: 10px;
}
.vxe-radio,
.vxe-checkbox {
margin-right: 4px;
white-space: nowrap;
}
.vxe-input--inner {
height: 24px;
line-height: 24px;
padding: 0 5px;
}
.vxe-radio--label,
.vxe-checkbox--label {
font-size: 12px;
}
.vxe-radio--icon,
.vxe-checkbox--icon {
font-size: 12px;
}
.vxe-radio-group,
.vxe-checkbox-group {
display: inline-block;
}
.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;
}
.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: 150px;
margin: 0 2px;
}
.radio-group {
display: inline-block;
margin-right: 10px;
}
.checkbox-group {
display: inline-block;
margin-right: 10px;
} }
.vxe-radio, return expression
.vxe-checkbox { }
margin-right: 8px;
// 提示工具相关
white-space: nowrap; 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
} }
}
/* 附件样式 */
.attachments { const toggleErrorTooltip = (rowCode: string, field: string) => {
margin-top: 20px; const key = getFieldKey(rowCode, field)
width: 100%; if (hoveredErrorKey.value === key) {
showErrorTooltip.value = false
hoveredErrorKey.value = ''
} else {
showErrorTooltip.value = true
hoveredErrorKey.value = key
} }
}
const closeAllTooltips = () => {
showTooltip.value = false
hoveredKey.value = ''
showErrorTooltip.value = false
hoveredErrorKey.value = ''
}
// 历史填报
const handleOpenHistoryDrawer = () => {
historyFillCheckRef.value?.onDrawerShow(queryParam.value.tplId)
historyDrawerVisible.value = true
}
const closeHistoryDrawer = () => {
historyDrawerVisible.value = false
}
.attachments h4 { // 校验结果处理
font-size: 14px; const handleValidationResultClick = (result: any) => {
font-weight: bold; const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
margin-bottom: 10px; const status = result.isValid ? 'success' : 'error'
}
VxeUI.modal.message({ content: message, status })
}
// 工具函数
const findEarliestFormulaForField = (
formulas: FormulaItem[],
field: string
): { formula: FormulaItem | null, index: number } => {
let result = { formula: null, index: Infinity }
formulas?.forEach((f: FormulaItem) => {
const text = f.formula || ''
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g')
const match = regex.exec(text)
if (match && match.index < result.index) {
result = { formula: f, index: match.index }
}
})
return result
}
// 消息提示
const showSuccessMessage = (message: string) => {
VxeUI.modal.message({ content: message, status: 'success' })
}
const showWarningMessage = (message: string) => {
VxeUI.modal.message({ content: message, status: 'warning' })
}
const showErrorMessage = (message: string) => {
VxeUI.modal.message({ content: message, status: 'error' })
}
// 子组件引用设置
const setMultiColumnTableRef = (el: any, code: string) => {
if (el) childMultiTableRefs.value[code] = el
}
const setAttachTableRef = (el: any, code: string) => {
if (el) childAttachTableRefs.value[code] = el
}
const setMyVxeTableRef = (el: any, code: string) => {
if (el) childMyVexTableRefs.value[code] = el
}
</script>
.attachment-table { <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%; width: 100%;
border: 1px solid #000; 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 {
border-bottom: 1px solid #333;
text-align: center;
margin: 5px 3px;
padding: 0;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
}
blockquote {
padding: 0px 5px;
color: black;
cursor: pointer;
}
// 输入框包装器
.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%;
transition: all 0.3s;
opacity: 0;
animation: fadeIn 0.5s forwards;
animation-delay: 0.3s;
&:hover { .vxe-header--column,
background: #bae7ff; .vxe-body--column {
transform: scale(1.1); border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
} }
} }
}
@keyframes fadeIn {
from { // 表格信息文本
opacity: 0; .table-info-text {
transform: scale(0.8); font-weight: bold;
}
// 项目名称
.project-name {
font-weight: bold;
font-size: 14px;
}
// Cell styles
.content-cell {
line-height: 1.8;
}
// 表单控件组
.radio-group, .checkbox-group {
display: inline-block;
margin-left: 10px;
.vxe-radio, .vxe-checkbox {
margin-left: 0px;
white-space: nowrap;
line-height: 30px;
padding: 0px 10px;
&--label {
font-size: 12px;
} }
to {
opacity: 1; &--icon {
transform: scale(1); font-size: 12px;
} }
} }
}
// 错误图标 // VXE specific overrides
.error-icon { .vxe {
margin-left: 5px; &-radio-group, &-checkbox-group {
cursor: pointer;
color: #ff4d4f;
font-size: 16px;
display: inline-block; display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
transition: all 0.3s;
&:hover {
transform: scale(1.1);
}
} }
// 提示框 &-cell {
.tooltip, .error-tooltip { padding: 5px;
position: absolute;
top: 100%;
left: 0;
margin-top: 5px;
padding: 10px;
font-size: 12px;
border-radius: 4px;
z-index: 1000;
white-space: pre-wrap;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
} }
}
.tooltip {
background: #333; .vxe-input {
color: #fff; border-bottom: 1px solid #333;
text-align: center;
&::before { margin: 5px 3px;
content: ''; padding: 0;
position: absolute; border-top: none;
bottom: 100%; border-left: none;
left: 10px; border-right: none;
border-width: 5px; background: transparent;
border-style: solid; }
border-color: transparent transparent #333 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%;
transition: all 0.3s;
&:hover {
background: #bae7ff;
} }
}
// 错误图标
.error-icon {
margin-left: 5px;
cursor: pointer;
color: #ff4d4f;
font-size: 16px;
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
transition: all 0.3s;
&:hover {
transform: scale(1.1);
}
}
// 提示框
.tooltip, .error-tooltip {
position: absolute;
top: 100%;
left: 0;
margin-top: 5px;
padding: 10px;
font-size: 12px;
border-radius: 4px;
z-index: 1000;
white-space: pre-wrap;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip {
background: #333;
color: #fff;
&::before {
content: '';
position: absolute;
bottom: 100%;
left: 10px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #333 transparent;
}
}
.error-tooltip { .error-tooltip {
background: #8b0000; background: #8b0000;
color: #fff; color: #fff;
border: 1px solid #ff4d4f; border: 1px solid #ff4d4f;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
bottom: 100%; bottom: 100%;
left: 5px; left: 5px;
border-width: 5px; border-width: 5px;
border-style: solid; border-style: solid;
border-color: transparent transparent #8b0000 transparent; border-color: transparent transparent #8b0000 transparent;
}
} }
}
// 加载遮罩
.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> </style>
\ No newline at end of file
<template> <template>
<div class="bank-report-table" style="height: 80vh;" @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">
...@@ -8,87 +12,68 @@ ...@@ -8,87 +12,68 @@
</div> </div>
</div> </div>
<!-- 工具栏 -->
<vxe-toolbar> <vxe-toolbar>
<template #button> <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-save" @click="validateData">校验</vxe-button> status="primary"
<vxe-button status="primary" icon="vxe-icon-save" @click="saveBatch">保存</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-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 <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 }"
class="custom-table" class="custom-table"
height="auto"
> >
<vxe-column field="serialNumber" title="序号" width="80"></vxe-column> <vxe-column field="serialNumber" title="序号" width="50"></vxe-column>
<vxe-column field="project" title="项目" width="200">
<vxe-column field="project" title="项目" width="100">
<template #default="{ row }"> <template #default="{ row }">
<span style="font-weight: bold;size:20px">{{ row.project }}</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">
<!-- MultiColumnTable 类型 --> <!-- 多列表格 -->
<template v-if="row.type === 'MultiColumnTable'"> <template v-if="row.type === 'MultiColumnTable'">
<MultiColumnTable <MultiColumnTable
:title="row.project" :title="row.project"
:columnsPerRow="6"
:records="[]" :records="[]"
:fields="row.content" :fields="row.content"
:ref="(el) => setMultiColumnTableRef(el, row.code)" :ref="(el) => setMultiColumnTableRef(el, row.code)"
...@@ -96,203 +81,190 @@ ...@@ -96,203 +81,190 @@
/> />
</template> </template>
<!-- AttachTable 类型 --> <!-- 附件表格 -->
<template v-else-if="row.type === 'AttachTable'"> <template v-else-if="row.type === 'AttachTable'">
<AttachTable <AttachTable
: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"
/>
</template>
<!-- Vxe表格 -->
<template v-else-if="row.type === 'VxeTable'">
<MyVxeTable1
:title="row.project"
:data="row.data"
:columns="row.columns"
:pcode="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>
<template v-else-if="item.type === 'AttachTable'">
<AttachTable
:title="item.project"
:records="item.datas"
:fields="item.content"
:pcode="item.code"
:ref="(el) => setAttachTableRef(el, item.code)"
style="margin:0px;padding:0px"
/>
</template>
<template v-else-if="item.type === 'MultiColumnTable'">
<MultiColumnTable
:title="item.project"
:records="[]"
:fields="item.content"
:ref="(el) => setMultiColumnTableRef(el, item.code)"
style="margin:0px;padding:0px"
/>
</template>
<template v-else-if="item.type === 'yesno'">
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio label="是" content="是">
</vxe-radio>
<vxe-radio label="否" content="否"></vxe-radio>
</vxe-radio-group>
<template v-if="item.extraField||item.extraFields">
<div v-if="formData[getFieldKey(row.code, item.field)]=='是'" style="margin-left:40px">
如是,
<template v-if="item.extraField">
{{item.extraLabel }}
<vxe-input v-model="formData[getFieldKey(row.code, item.extraField)]" style="width: 300px;"></vxe-input>
<span class="unit"> {{ item.unit }}</span>
</template>
<template v-else-if="item.extraFields">
<template v-for="(ccopt,ccind) in item.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span>
<template v-if="ccopt.formType=='input'">
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" style="width:250px"></vxe-input>
</template>
<template v-else-if="ccopt.formType=='number'">
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" type="number" style="width:250px"></vxe-input>
<span class="unit"> {{ ccopt.unit }}</span>
</template>
<template v-else-if="ccopt.formType=='checkbox'">
<vxe-checkbox-group v-model="formData[getFieldKey(row.code, ccopt.field)]">
<vxe-checkbox v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt">
</vxe-checkbox>
</vxe-checkbox-group>
</template>
<template v-else-if="ccopt.formType=='radio'">
<vxe-radio-group v-model="formData[getFieldKey(row.code, ccopt.field)]" :options="ccopt.options">
<vxe-radio v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt">
</vxe-radio>
</vxe-radio-group>
</template>
<template v-else>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" :type="ccopt.formType" style="width:100px"></vxe-input>
</template>
<br>
<template v-if="ccopt.otherOption">
其他:<vxe-input v-model="formData[getFieldKey(row.code, ccopt.otherField)]" style="width: 300px;"></vxe-input>
</template>
<br>
</template>
</template>
</div>
</template>
</template>
<!-- 单选组 -->
<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[getFieldKey(row.code, item.field)]"> <vxe-radio-group
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt">{{ opt }}</vxe-radio> v-model="formData[getFieldKey(row.code, item.field)]"
</vxe-radio-group> :disabled="!item.hasRight || isRadioDisabled(row.code, item)"
</span> >
<vxe-radio
<span v-else-if="item.type === 'radio-group-extraFields'" class="radio-group" style="width:100%"> v-for="(opt, optIndex) in item.options"
<vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]"> :key="optIndex"
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt.label"> :label="opt"
{{ opt.label }} >
<template v-if="opt.extraField&&formData[getFieldKey(row.code, item.field)]==opt.label"> {{ opt }}
<span>{{ opt.extraFieldLabel }}</span>
<vxe-input v-model="formData[getFieldKey(row.code, opt.otherField)]" style="width: 200px;"></vxe-input>
</template>
<template v-if="opt.extraFields&&formData[getFieldKey(row.code, item.field)]==opt.label">
<template v-for="(ccopt,ccind) in opt.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span>
<vxe-input v-model="formData[getFieldKey(row.code, ccopt.field)]" style="width: 200px;"></vxe-input>
</template>
</template>
</vxe-radio> </vxe-radio>
</vxe-radio-group> </vxe-radio-group>
</span> </span>
<!-- 多选组(带其他选项) -->
<div v-else-if="item.type === 'checkbox-group'"> <div v-else-if="item.type === 'checkbox-group'">
<span> <span class="checkbox-group">
<vxe-checkbox-group v-model="formData[getFieldKey(row.code, item.field)]"> <vxe-checkbox-group
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt"> v-model="formData[getFieldKey(row.code, item.field)]"
:disabled="!item.hasRight"
>
<vxe-checkbox
v-for="(opt, optIndex) in item.options"
:key="optIndex"
:label="opt"
>
{{ opt }} {{ opt }}
</vxe-checkbox> </vxe-checkbox>
<template v-if="item.otherOption"> <template v-if="item.otherField">
<vxe-checkbox label="其他"> <div style="width:100%">
其他: <vxe-checkbox label="其他" :disabled="!item.hasRight">
<vxe-input v-model="formData[getFieldKey(row.code, item.otherField)]" style="width: 200px;" 其他:
:disabled="!(formData[getFieldKey(row.code, item.field)]?.indexOf('其他')>-1)"> </vxe-checkbox>
</vxe-input> <vxe-input
</vxe-checkbox> v-model="formData[getFieldKey(row.code, item.otherField)]"
:disabled="!isOtherFieldEnabled(row.code, item) || !item.hasRight"
style="width: 50%"
@blur="handleInputBlur(row.code, item.otherField, item.matchedFormula?.formula)"
/>
</div>
</template> </template>
</vxe-checkbox-group> </vxe-checkbox-group>
<!-- 其他选项 -->
</span>
</div>
<!-- 多选带输入框 -->
<div v-else-if="item.type === 'checkboxAndInput'">
<span style="margin-left:20px">
<vxe-checkbox-group
v-model="formData[getFieldKey(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[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> </span>
</div> </div>
<template v-else-if="item.type=='textarea'">
<vxe-textarea v-model="formData[getFieldKey(row.code, item.field)]" :rows="item.rows" :style="{width:item.width}">
</vxe-textarea>
</template>
<!-- 普通输入框 -->
<template v-else> <template v-else>
<div class="input-wrapper"> <div class="input-wrapper">
<vxe-input <vxe-input
:type="item.type" :disabled="!item.hasRight || isInputDisabled(row.code, item)"
v-model="formData[getFieldKey(row.code, item.field)]" :type="item.type"
size="mini" v-model="formData[getFieldKey(row.code, item.field)]"
size="mini"
class="table-input" class="table-input"
:disabled="!item.hasRight" :style="item.style"
@blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)" @blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
/> />
<span v-if="item.unit" 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="inputErrors[getFieldKey(row.code, item.field)]" <span
class="error-icon" 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)"
title="点击查看错误详情"
> >
<i class="vxe-icon-error"></i> <i class="vxe-icon-error"></i>
</span> </span>
<div <!-- 错误提示 -->
v-if="showTooltip && hoveredKey === getFieldKey(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 }}
</div>
<div
v-if="showErrorTooltip && hoveredErrorKey === getFieldKey(row.code, item.field)"
class="error-tooltip"
@click.stop
>
{{ inputErrors[getFieldKey(row.code, item.field)]?.message }}
<br> <br>
<span style="color: #ffcccc;"> <span style="color: #ffcccc;">
公式: {{ inputErrors[getFieldKey(row.code, item.field)]?.formula }} 公式: {{ inputErrors[getFieldKey(row.code, item.field)].formula }}
</span> </span>
<template v-if="inputErrors[getFieldKey(row.code, item.field)]?.error"> <template v-if="inputErrors[getFieldKey(row.code, item.field)].error">
<br> <br>
<span style="color: #ff9999; font-size: 11px;"> <span style="color: #ff9999; font-size: 11px;">
错误: {{ inputErrors[getFieldKey(row.code, item.field)]?.error }} 错误: {{ inputErrors[getFieldKey(row.code, item.field)].error }}
</span> </span>
</template> </template>
</div> </div>
<!-- 校验规则提示 -->
<div
v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)"
class="tooltip"
@click.stop
>
{{ item.matchedFormula?.des || '暂无校验规则' }}
</div>
</div> </div>
</template> </template>
</template> </template>
...@@ -301,72 +273,74 @@ ...@@ -301,72 +273,74 @@
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="remarks" title="备注" width="150"> <!-- 备注列 -->
<vxe-column field="remarks" title="备注" width="60">
<template #default="{ row }"> <template #default="{ row }">
<vxe-textarea :rows="row.remarks.rows" v-model="formData[getFieldKey(row.code, row.remarks.field)]" style="height:100%;"> <vxe-textarea
</vxe-textarea> :rows="row.remarks.rows"
v-model="formData[getFieldKey(row.code, row.remarks.field)]"
/>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<!-- 历史填报检查组件 --> <!-- 校验抽屉 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @close-drawer="closeHistoryDrawer"/> <ValidationDrawer
ref="validationDrawerRef"
<!-- 验证公式帮助提示 --> v-model="drawerVisible"
<div :tableFormData="tableFormData"
v-if="validationTooltipVisible" @validationResultClick="handleValidationResultClick"
class="validation-tooltip" />
:style="{ left: validationTooltipPosition.left + 'px', top: validationTooltipPosition.top + 'px' }"
> <!-- 历史填报抽屉 -->
<pre class="tooltip-content">{{ validationTooltipContent }}</pre> <HistoryFillCheck
</div> ref="historyFillCheckRef"
v-model="historyDrawerVisible"
@closeDrawer="closeHistoryDrawer"
/>
</div> </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 HistoryFillCheck from './check/historyFillCheck.vue' import MyVxeTable1 from '../tableComponents/MyVxeTable.vue'
import HistoryFillCheck from './check/HistoryFillCheck.vue'
import { tableFormData } from '../../data/tb9.data'; import ValidationDrawer from './check/ValidationDrawer.vue'
import { ref, reactive, nextTick, onMounted, computed } from 'vue'
import { VxeUI } from 'vxe-table' // 数据导入
import { queryRecord, batchSaveOrUpdateBeforeDelete } from '../../record/BaosongTaskRecord.api' import { tableFormData } from '../../data/tb9.data'
import { findUserRightForTplItem } from '../../alloc/BaosongTaskAlloc.api'
// 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';
const route = useRoute();
const tableRef = ref();
const tplItemMap = ref<Record<string, any>>({});
const historyFillCheckRef = ref<any>(null);
const queryParam = ref({
taskId: -1,
taskName: '',
tplId: -1,
tplName: '',
tplCode: '',
comfrom: ''
})
// 权限相关状态 // 类型定义
const userAllocItems = ref<string[]>([]) interface QueryParam {
const validFormula = ref<any[]>([]) taskId: number
const validationResultsList = ref<any[]>([]) taskName: string
const drawerVisible = ref(false) tplId: number
const historyDrawerVisible = ref(false) tplName: string
const showTooltip = ref(false) tplCode: string
const hoveredKey = ref('') comfrom: string
const showErrorTooltip = ref(false) }
const hoveredErrorKey = ref('')
const inputErrors = ref<Record<string, any>>({})
const isInitialized = ref(false)
const validationTooltipVisible = ref(false)
const validationTooltipPosition = ref({ left: 0, top: 0 })
const validationTooltipContent = ref('')
interface FormData { interface FormDataItem {
id: number | null id: number | null
taskid: number taskid: number
tplid: number tplid: number
...@@ -377,534 +351,541 @@ interface FormData { ...@@ -377,534 +351,541 @@ interface FormData {
rind: number rind: number
} }
onMounted(async () => { interface FormulaItem {
if (route.query.taskId) { formula: string
queryParam.value.taskId = Number(route.query.taskId); des: string
} [key: string]: any
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);
}
// 设置查询参数
setQueryParams();
// 获取权限和验证公式
try {
userAllocItems.value = await findUserRightForTplItem({
tplid: queryParam.value.tplId,
taskid: queryParam.value.taskId
})
validFormula.value = await getTblvalidFormula({ interface ContentItem {
tplid: queryParam.value.tplId, type: string
}) field: string
value?: any
hasRight?: boolean
hasValidFormula?: boolean
matchedFormula?: FormulaItem
relatedFiled?: string
otherField?: string
optionItemField?: string[]
[key: string]: any
}
console.log('获取到的权限:', userAllocItems.value.length); interface TableRow {
console.log('获取到的公式:', validFormula.value.length); code: string
} catch (error) { project: string
console.error('获取权限或验证公式失败:', error) type?: string
} content: ContentItem[] | any
remarks: { rows: number; field: string }
hasRight?: boolean
[key: string]: any
}
await setTplItemMap() interface InputError {
setFormItemRight() message: string
await setData() formula: string
error?: string
}
isInitialized.value = true interface TplItemMapValue {
pid: number
itemid: number
formTp: string
code: string
}
// 路由和组件引用
const route = useRoute()
const tableRef = ref<VxeTableInstance>()
const historyFillCheckRef = ref()
const validationDrawerRef = ref()
// 响应式状态
const loading = ref(true)
const historyDrawerVisible = ref(false)
const drawerVisible = ref(false)
const showTooltip = ref(false)
const hoveredKey = ref('')
const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
setTimeout(() => { // 数据状态
refreshHelpIcons() const queryParam = ref<QueryParam>({
}, 300) taskId: -1,
taskName: '',
tplId: -1,
tplName: '',
tplCode: '',
comfrom: ''
}) })
const loading = ref(false) const formData = reactive<Record<string, any>>({})
const formData = reactive<Record<string, any>>({}); 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 childMultiTableRefs = ref<Record<string, any>>({})
const childAttachTableRefs = ref<Record<string, any>>({})
const childMyVexTableRefs = ref<Record<string, any>>({})
const formValues = ref<FormData[]>([]) // 计算属性
const fieldKeys = computed(() => Object.keys(formData))
// 获取字段key的统一方法 // 工具函数
const getFieldKey = (rowCode: string, field: string): string => { const getFieldKey = (rowCode: string, field: string): string => {
return `${rowCode}_${field}` return `${rowCode}_${field}`
} }
const saveBatch = async () => { const isRadioDisabled = (rowCode: string, item: ContentItem): boolean => {
try { if (!item.relatedFiled) return false
loading.value = true const relatedKey = getFieldKey(rowCode, item.relatedFiled)
formValues.value = [] return formData[relatedKey] !== '是'
}
for (const strKey in formData) { const isInputDisabled = (rowCode: string, item: ContentItem): boolean => {
const valData = formData[strKey] if (!item.relatedFiled) return false
if (valData) { const relatedKey = getFieldKey(rowCode, item.relatedFiled)
let tmpAry = strKey.split("_") return formData[relatedKey] !== '是'
await setFormValues(tmpAry[0], tmpAry[1], valData, 1) }
}
}
await getAttachTableFormData(); const isOtherFieldEnabled = (rowCode: string, item: ContentItem): boolean => {
await getAllMultiTableFormData() const checkboxKey = getFieldKey(rowCode, item.field)
const checkboxValue = formData[checkboxKey] || []
return checkboxValue.includes('其他')
}
if (formValues.value.length > 0) { // 初始化
await batchSaveOrUpdateBeforeDelete(formValues.value) onMounted(async () => {
VxeUI.modal.message({ content: '保存成功', status: 'success' }) await initPage()
} else { })
VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' })
} const initPage = async () => {
try {
loading.value = true
await initQueryParams()
await Promise.all([
loadUserRights(),
loadValidationFormulas(),
loadTemplateItems()
])
await setFormItemRight()
await loadTableData()
} catch (error: any) { } catch (error: any) {
VxeUI.modal.message({ content: `保存失败: ${error.message}`, status: 'error' }) showErrorMessage(`初始化页面失败: ${error.message}`)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
const childMultiTableRefs = ref<Record<string, any>>({}) const initQueryParams = () => {
const setMultiColumnTableRef = (el: any, index: string) => { const { taskId, taskName, tplId, tplName, tplCode,comfrom } = route.query
if (el) {
childMultiTableRefs.value[index] = el; if (taskId) queryParam.value.taskId = Number(taskId)
} if (taskName) queryParam.value.taskName = String(taskName)
}; if (tplId) queryParam.value.tplId = Number(tplId)
if (tplName) queryParam.value.tplName = String(tplName)
if (tplCode) queryParam.value.tplCode = String(tplCode)
if (comfrom) queryParam.value.comfrom = String(comfrom)
}
const getAllMultiTableFormData = async () => { const loadUserRights = async () => {
for (const pcode in childMultiTableRefs.value) { userAllocItems.value = await findUserRightForTplItem({
const child = childMultiTableRefs.value[pcode]; tplid: queryParam.value.tplId,
if (child) { taskid: queryParam.value.taskId
const datas = child.getFormData() })
let rind = 0 }
for (const obj of datas) {
rind++ const loadValidationFormulas = async () => {
Object.keys(obj).forEach(code => { validFormula.value = await getTblvalidFormula({
setFormValues(pcode, code, obj[code], rind) tplid: queryParam.value.tplId
}); })
} }
const loadTemplateItems = async () => {
const items = await allTplItems({
tplid: queryParam.value.tplId
})
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 childAttachTableRefs = ref<Record<string, any>>({}) const setFormItemRight = () => {
const setAttachTableRef = (el: any, index: string) => { tableFormData.forEach((row: TableRow) => {
if (el) { if (row.content && Array.isArray(row.content)) {
childAttachTableRefs.value[index] = el; row.content.forEach((item: ContentItem) => {
if (item.field) {
const key = getFieldKey(row.code, item.field)
item.hasRight = userAllocItems.value.includes(key)
if (item.hasRight) {
const { formula } = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formula
item.matchedFormula = formula
}
}
})
}
})
}
// 保存相关
const saveBatch = async () => {
try {
if (!await validateForm()) return
await prepareFormData()
if (formValues.value.length === 0) {
showWarningMessage('没有需要保存的数据')
return
}
await batchSaveOrUpdateBeforeDelete(formValues.value)
showSuccessMessage('保存成功')
} catch (error: any) {
showErrorMessage(`保存失败: ${error.message}`)
} }
}; }
const getAttachTableFormData = async () => { const validateForm = async (): Promise<boolean> => {
for (const pcode in childAttachTableRefs.value) { const $table = tableRef.value
const child = childAttachTableRefs.value[pcode]; if (!$table) return true
if (child) {
const datas = child.getFormData() const checkResult = $table.validate()
if (!checkResult) {
showErrorMessage('表单验证失败,请检查填写内容')
return false
}
return true
}
for (const code in datas) { const prepareFormData = async () => {
await setFormValues(pcode, code, datas[code], 1) formValues.value = []
}
// 处理主表单数据
for (const [key, value] of Object.entries(formData)) {
if (value !== undefined && value !== null && value !== '') {
const [pcode, code] = key.split('_')
await addFormValue(pcode, code, value, 1)
} }
} }
};
// 处理子表格数据
const setFormValues = async (pcode: string, code: string, valData: any, rind: number) => { await Promise.all([
if (!valData) return; collectAttachTableData(),
let vals = Array.isArray(valData) ? valData.join(',') : valData collectMultiTableData(),
let strKey = getFieldKey(pcode, code) collectVxeTableData()
const item = tplItemMap.value[strKey]; ])
const { pid, itemid } = item ?? {}; }
let tempForm: FormData = { const addFormValue = async (pcode: string, code: string, value: any, rind: number) => {
if (!value) return
const stringValue = Array.isArray(value) ? value.join(',') : String(value)
const key = getFieldKey(pcode, code)
const item = tplItemMap.value[key]
if (!item) return
const formItem: FormDataItem = {
id: null, id: null,
rind: rind, rind,
taskid: queryParam.value.taskId, taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId, tplid: queryParam.value.tplId,
tplcode: code, tplcode: code,
itemid: itemid, itemid: item.itemid,
itempid: pid, itempid: item.pid,
content: vals content: stringValue
}; }
formValues.value.push(tempForm)
formValues.value.push(formItem)
} }
async function setData() { // 数据收集函数
try { const collectAttachTableData = async () => {
loading.value = true; for (const [pcode, child] of Object.entries(childAttachTableRefs.value)) {
const taskId = queryParam.value.taskId; if (child) {
const tplid = queryParam.value.tplId; const datas = await child.getFormData()
for (const [code, value] of Object.entries(datas)) {
Object.keys(formData).forEach(key => delete formData[key]); await addFormValue(pcode, code, value, 1)
}
await setTplItemMap();
const recordData = await queryRecord({ taskid: taskId, tplid: tplid });
const valueObj: Record<string, any> = {};
for (const data of recordData) {
const key = `${data.itempid}_${data.itemid}_${data.rind}`;
valueObj[key] = data.content;
} }
}
}
const shouldSkip = (key: string) => { const collectMultiTableData = async () => {
const refsToCheck = [ for (const [pcode, child] of Object.entries(childMultiTableRefs.value)) {
childMultiTableRefs.value, if (child) {
childAttachTableRefs.value, const datas = await child.getFormData()
]; let rind = 0
for (const obj of datas) {
for (const refs of refsToCheck) { rind++
for (const pcode of Object.keys(refs)) { for (const [code, value] of Object.entries(obj)) {
if (key.startsWith(pcode + '_')) { await addFormValue(pcode, code, value, rind)
if (key === "TB9006_COL64") {
return false;
}
return true;
}
} }
} }
return false;
};
for (const strKey of Object.keys(tplItemMap.value)) {
if (shouldSkip(strKey)) {
continue;
}
const item = tplItemMap.value[strKey];
if (!item) continue;
const { pid, itemid, formTp } = item;
const dataVal = valueObj[`${pid}_${itemid}_1`];
if (!dataVal) continue;
if (formTp === 'checkbox') {
formData[strKey] = dataVal?.split(",") || [];
} else {
formData[strKey] = dataVal || "";
}
} }
}
}
for (const pcode of Object.keys(childAttachTableRefs.value)) { const collectVxeTableData = async () => {
const child = childAttachTableRefs.value[pcode]; for (const [pcode, child] of Object.entries(childMyVexTableRefs.value)) {
const matchingKeys = Object.keys(tplItemMap.value).filter(key => if (child) {
key.startsWith(pcode + '_') const formData = await child.getFormData()
); let rind = 0
// 表格数据
const attachTableData: Record<string, any> = {}; for (const obj of formData.datas) {
for (const key of matchingKeys) { rind++
const tmpData = tplItemMap.value[key]; for (const [code, value] of Object.entries(obj)) {
if (!tmpData) continue; await addFormValue(pcode, code, value, rind)
const { pid, itemid, formTp, code } = tmpData;
const valueKey = `${pid}_${itemid}_1`;
if (formTp === 'checkbox') {
attachTableData[code] = valueObj[valueKey]?.split(",") || [];
} else {
attachTableData[code] = valueObj[valueKey] || "";
} }
} }
if (formData.footer) {
child.setFormData(attachTableData); for (const [code, value] of Object.entries(formData.footer)) {
} await addFormValue(formData.footerPcode, code, value, 1)
for (const pcode of Object.keys(childMultiTableRefs.value)) {
const child = childMultiTableRefs.value[pcode];
const matchingKeys = Object.keys(tplItemMap.value).filter(key =>
key.startsWith(pcode + '_')
);
const rowsMap: Record<string, Record<string, any>> = {};
for (const key of matchingKeys) {
const tmpData = tplItemMap.value[key];
if (!tmpData) continue;
const { pid, itemid, code } = tmpData;
const valKeys = Object.keys(valueObj).filter(k =>
k.startsWith(`${pid}_${itemid}_`)
);
for (const valKey of valKeys) {
const parts = valKey.split('_');
const rind = parts[2];
if (!rowsMap[rind]) {
rowsMap[rind] = {};
}
rowsMap[rind][code] = valueObj[valKey];
} }
} }
const tableDatas = Object.values(rowsMap);
nextTick();
child.setFormData(tableDatas);
} }
} catch (error: any) {
VxeUI.modal.message({
content: `加载数据失败: ${error.message}`,
status: 'error'
});
} finally {
loading.value = false;
} }
} }
async function setTplItemMap() { // 数据加载
const loadTableData = async () => {
try { try {
const tplid = queryParam.value.tplId loading.value = true
const tplItem = await allTplItems({
tplid: tplid, // 清空现有数据
}) Object.keys(formData).forEach(key => delete formData[key])
tplItemMap.value = {} const recordData = await queryRecord({
tplItem.forEach((item: any) => { taskid: queryParam.value.taskId,
let strKey = getFieldKey(item.pcode, item.xmlcode) tplid: queryParam.value.tplId
tplItemMap.value[strKey] = { pid: item.pid, itemid: item.id, formTp: item.formTp, code: item.xmlcode }
}) })
// 转换记录数据为便于查找的结构
const valueMap = recordData.reduce((acc: Record<string, string>, data) => {
const key = `${data.itempid}_${data.itemid}_${data.rind}`
acc[key] = data.content
return acc
}, {})
// 填充表格数据
await fillTableData(valueMap)
} catch (error: any) { } catch (error: any) {
VxeUI.modal.message({ content: `加载数据失败: ${error.message}`, status: 'error' }) showErrorMessage(`加载数据失败: ${error.message}`)
} finally {
loading.value = false
} }
} }
// 校验数据 const fillTableData = async (valueMap: Record<string, string>) => {
const validateData = () => { for (const row of tableFormData) {
drawerVisible.value = true if (!row.type && row.content) {
} await fillSimpleFields(row, valueMap)
} else if (row.type === 'AttachTable' && row.datas) {
// 校验结果抽屉显示时触发 await fillAttachTable(row, valueMap)
const handleValidationDrawerShow = () => { } else if (row.type === 'MultiColumnTable' && row.content) {
performValidation() await fillMultiColumnTable(row, valueMap)
} } else if (row.type === 'VxeTable' && row.columns) {
await fillVxeTable(row, valueMap)
// 执行校验
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 fillSimpleFields = async (row: TableRow, valueMap: Record<string, string>) => {
const evaluateFormula = (formula: string, description: string, process: string[]): any => { if (!row.content || !Array.isArray(row.content)) return
try {
const fieldMatch = formula.match(/\[(\w+)\]/) for (const item of row.content) {
if (!fieldMatch) { if (!item.field) continue
process.push(`跳过: 无法解析公式字段 ${formula}`)
return null const key = getFieldKey(row.code, item.field)
} const tplItem = tplItemMap.value[key]
if (!tplItem) continue
const fieldName = fieldMatch[1]
const row = tableFormData.find((r: any) => r.content?.some((c: any) => c.field === fieldName)) const { pid, itemid, formTp } = tplItem
if (!row) { const valueKey = `${pid}_${itemid}_1`
process.push(`跳过: 未找到字段 ${fieldName}`) const dataVal = valueMap[valueKey]
return null
} if (dataVal === undefined) continue
const fieldItem = row.content.find((c: any) => c.field === fieldName) if (formTp === 'checkbox') {
if (!fieldItem) { formData[key] = dataVal.split(',').filter(Boolean)
process.push(`跳过: 未找到字段 ${fieldName}`) } else {
return null formData[key] = dataVal
} }
const strKey = getFieldKey(row.code, fieldName) // 处理特殊字段类型
const fieldValue = formData[strKey] if (item.type === 'checkboxAndInput' && item.optionItemField) {
await fillCheckboxInputFields(row, item, valueMap)
// 检查是否有空字段规则
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) => { if (item.otherField) {
const value = formData[getFieldKey(row.code, field)] await fillOtherField(row, item, valueMap)
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 fillCheckboxInputFields = async (
const showValidationHelp = (formulaItem: any, event: MouseEvent) => { row: TableRow,
const rect = (event.target as HTMLElement).getBoundingClientRect() item: ContentItem,
validationTooltipPosition.value = { valueMap: Record<string, string>
left: rect.left + rect.width / 2, ) => {
top: rect.top - 10 for (const field of item.optionItemField || []) {
const key = getFieldKey(row.code, field)
const tplItem = tplItemMap.value[key]
if (!tplItem) continue
const { pid, itemid } = tplItem
const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal !== undefined) {
formData[key] = dataVal
}
} }
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
}
interface FormulaItem {
formula: string
des: string
[key: string]: any
} }
interface InputError { const fillOtherField = async (
message: string row: TableRow,
formula: string item: ContentItem,
error?: string valueMap: Record<string, string>
} ) => {
const key = getFieldKey(row.code, item.otherField!)
const fieldKeys = computed(() => Object.keys(formData)) const tplItem = tplItemMap.value[key]
if (!tplItem) return
const showHelpIcon = (rowCode: string, field: string, item: any): boolean => {
if (item.hasValidFormula !== undefined) { const { pid, itemid } = tplItem
return item.hasValidFormula === true const valueKey = `${pid}_${itemid}_1`
const dataVal = valueMap[valueKey]
if (dataVal !== undefined) {
formData[key] = dataVal
} }
const key = getFieldKey(rowCode, field)
const hasRight = userAllocItems.value.includes(key)
if (!hasRight) return false
const formulaResult = findEarliestFormulaForField(validFormula.value, field)
return !!formulaResult.formula
} }
const setFormItemRight = () => { const fillAttachTable = async (row: TableRow, valueMap: Record<string, string>) => {
console.log('开始设置表单权限...') const child = childAttachTableRefs.value[row.code]
console.log('userAllocItems 数量:', userAllocItems.value.length) if (!child) return
console.log('validFormula 数量:', validFormula.value.length)
const tableData: Record<string, any> = {}
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) { for (const data of row.datas || []) {
row.content.forEach((item: any) => { for (const [key, value] of Object.entries(data)) {
if (item.field) { if (typeof value === 'object' && value.field) {
const key = getFieldKey(row.code, item.field) const fieldKey = getFieldKey(row.code, value.field)
item.hasRight = userAllocItems.value.includes(key) const tplItem = tplItemMap.value[fieldKey]
if (!tplItem) continue
console.log(`字段 ${key}: hasRight = ${item.hasRight}`)
const { pid, itemid, formTp, code } = tplItem
if (item.hasRight) { const valueKey = `${pid}_${itemid}_1`
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field) const dataVal = valueMap[valueKey]
item.hasValidFormula = !!formulaResult.formula if (dataVal) {
item.matchedFormula = formulaResult.formula || null tableData[code] = formTp === 'checkbox' ? dataVal.split(',') : dataVal
console.log(`字段 ${key}: hasValidFormula = ${item.hasValidFormula}`)
} else {
item.hasValidFormula = false
item.matchedFormula = null
}
} }
}) }
} }
}) }
console.log('表单权限设置完成') child.setFormData(tableData)
} }
const findEarliestFormulaForField = ( const fillMultiColumnTable = async (row: TableRow, valueMap: Record<string, string>) => {
formulas: FormulaItem[], const child = childMultiTableRefs.value[row.code]
field: string if (!child || !Array.isArray(row.content)) return
): { formula: FormulaItem | null, index: number } => {
let result = { formula: null, index: Infinity } const rowsMap: Record<string, Record<string, string>> = {}
formulas?.forEach((f: FormulaItem) => { for (const item of row.content) {
const text = f.formula || '' if (!item.field) continue
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g') const key = getFieldKey(row.code, item.field)
const match = regex.exec(text) const tplItem = tplItemMap.value[key]
if (!tplItem) continue
if (match && match.index < result.index) {
result = { formula: f, index: match.index } 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 result const tableDatas = Object.values(rowsMap)
child.setFormData(tableDatas)
} }
const refreshHelpIcons = () => { const fillVxeTable = async (row: TableRow, valueMap: Record<string, string>) => {
console.log('刷新帮助图标...') const child = childMyVexTableRefs.value[row.code]
tableFormData.forEach((row: any) => { if (!child || !Array.isArray(row.columns)) return
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => { const rowsMap: Record<string, Record<string, string>> = {}
if (item.field) {
const key = getFieldKey(row.code, item.field) for (const column of row.columns) {
const hasRight = userAllocItems.value.includes(key) if (!column.field) continue
if (hasRight) { const key = getFieldKey(row.code, column.field)
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field) const tplItem = tplItemMap.value[key]
item.hasValidFormula = !!formulaResult.formula if (!tplItem || Object.keys(tplItem).length === 0) continue
item.matchedFormula = formulaResult.formula || null
} else { const { pid, itemid, code } = tplItem
item.hasValidFormula = false const valueKeys = Object.keys(valueMap).filter(k =>
item.matchedFormula = null k.startsWith(`${pid}_${itemid}_`)
} )
}
}) for (const valueKey of valueKeys) {
const [, , rind] = valueKey.split('_')
if (!rowsMap[rind]) {
rowsMap[rind] = {}
}
rowsMap[rind][code] = valueMap[valueKey]
} }
}) }
const tableDatas = Object.values(rowsMap)
child.setFormData(tableDatas)
}
nextTick(() => { // 校验相关
console.log('帮助图标刷新完成') const validateData = () => {
}) drawerVisible.value = true
validationDrawerRef.value.setValidateData(validFormula.value,formData);
} }
const handleInputBlur = (rowCode: string, field: string, formula?: string) => { const handleInputBlur = (rowCode: string, field: string, formula?: string) => {
if (!formula) return if (!formula) return
const key = getFieldKey(rowCode, field) const key = getFieldKey(rowCode, field)
const value = formData[key] const value = formData[key]
if (!value || value === '') { if (!value || value === '') {
delete inputErrors.value[key] delete inputErrors.value[key]
return return
} }
validateFieldFormula(rowCode, field, formula) validateFieldFormula(rowCode, field, formula)
} }
const validateFieldFormula = (rowCode: string, field: string, formula: string) => { const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
const key = getFieldKey(rowCode, field) const key = getFieldKey(rowCode, field)
try { try {
const expression = buildExpression(formula, rowCode) const expression = buildExpression(formula, rowCode)
const isValid = eval(expression) const isValid = eval(expression)
...@@ -927,28 +908,45 @@ const validateFieldFormula = (rowCode: string, field: string, formula: string) = ...@@ -927,28 +908,45 @@ const validateFieldFormula = (rowCode: string, field: string, formula: string) =
const buildExpression = (formula: string, rowCode: string): string => { const buildExpression = (formula: string, rowCode: string): string => {
const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || [] const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []
const uniqueFields = [...new Set(fieldNames.filter(f => const uniqueFields = [...new Set(fieldNames.filter(f =>
!['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false'] !['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
.includes(f.toLowerCase()) .includes(f.toLowerCase())
))] ))]
uniqueFields.sort((a, b) => b.length - a.length) uniqueFields.sort((a, b) => b.length - a.length)
let expression = formula let expression = formula
for (const fieldName of uniqueFields) { for (const fieldName of uniqueFields) {
const key = getFieldKey(rowCode, fieldName) const key = getFieldKey(rowCode, fieldName)
const value = formData[key] const value = formData[key]
if (value !== undefined) { if (value !== undefined) {
// 检查字段类型,如果是日期类型,转换为时间戳进行比较
const row = tableFormData.find((r: TableRow) => r.code === rowCode)
let processedValue = value
if (row && row.content && Array.isArray(row.content)) {
const fieldItem = row.content.find((c: ContentItem) => c.field === fieldName)
if (fieldItem && fieldItem.type === 'date') {
// 将日期字符串转换为时间戳(毫秒)
const date = new Date(value)
if (!isNaN(date.getTime())) {
processedValue = date.getTime()
}
}
}
expression = expression.replace( expression = expression.replace(
new RegExp(`\\b${fieldName}\\b`, 'g'), new RegExp(`\\b${fieldName}\\b`, 'g'),
`Number(${value})` `Number(${processedValue})`
) )
} }
} }
return expression return expression
} }
// 提示工具相关
const toggleTooltip = (rowCode: string, field: string) => { const toggleTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field) const key = getFieldKey(rowCode, field)
if (hoveredKey.value === key) { if (hoveredKey.value === key) {
...@@ -976,285 +974,171 @@ const closeAllTooltips = () => { ...@@ -976,285 +974,171 @@ const closeAllTooltips = () => {
hoveredKey.value = '' hoveredKey.value = ''
showErrorTooltip.value = false showErrorTooltip.value = false
hoveredErrorKey.value = '' hoveredErrorKey.value = ''
validationTooltipVisible.value = false
}
const handleValidationResultClick = (result: any) => {
const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
const status = result.isValid ? 'success' : 'error'
VxeUI.modal.message({ content: message, status })
} }
</script> // 历史填报
<style lang="less" scoped> const handleOpenHistoryDrawer = () => {
/* 基础字体和排版 */ historyFillCheckRef.value?.onDrawerShow(queryParam.value.tplId)
.bank-report-table { historyDrawerVisible.value = true
font-family: "SimSun", "宋体", serif;
font-size: 12px;
color: #000;
margin: 5px auto;
width: 90%;
} }
/* 表格样式 */ const closeHistoryDrawer = () => {
.custom-table, historyDrawerVisible.value = false
.attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse;
} }
/* 加载遮罩层样式 */ // 校验结果处理
.loading-overlay { const handleValidationResultClick = (result: any) => {
position: absolute; const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
top: 0; const status = result.isValid ? 'success' : 'error'
left: 0;
width: 100%; VxeUI.modal.message({ content: message, status })
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; const findEarliestFormulaForField = (
flex-direction: column; formulas: FormulaItem[],
align-items: center; field: string
): { formula: FormulaItem | null, index: number } => {
let result = { formula: null, index: Infinity }
formulas?.forEach((f: FormulaItem) => {
const text = f.formula || ''
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const regex = new RegExp(`\\b${escapedField}\\b(?!\\w)`, 'g')
const match = regex.exec(text)
if (match && match.index < result.index) {
result = { formula: f, index: match.index }
}
})
return result
} }
.spinner { // 消息提示
width: 40px; const showSuccessMessage = (message: string) => {
height: 40px; VxeUI.modal.message({ content: message, status: 'success' })
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
} }
@keyframes spin { const showWarningMessage = (message: string) => {
0% { VxeUI.modal.message({ content: message, status: 'warning' })
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
.loading-text { const showErrorMessage = (message: string) => {
margin-top: 10px; VxeUI.modal.message({ content: message, status: 'error' })
color: #666;
font-size: 14px;
} }
/* 校验结果样式 */ // 子组件引用设置
.validation-results { const setMultiColumnTableRef = (el: any, code: string) => {
padding: 20px; if (el) childMultiTableRefs.value[code] = el
height: 600px;
overflow-y: auto;
} }
.result-summary { const setAttachTableRef = (el: any, code: string) => {
display: flex; if (el) childAttachTableRefs.value[code] = el
gap: 20px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
} }
.summary-item { const setMyVxeTableRef = (el: any, code: string) => {
display: flex; if (el) childMyVexTableRefs.value[code] = el
flex-direction: column;
} }
</script>
.summary-label { <style lang="less" scoped>
// Base table styles
.bank-report-table {
font-family: "SimSun", "宋体", serif;
font-size: 12px; font-size: 12px;
color: #666; 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;
}
}
} }
.summary-value { // 表格信息文本
font-size: 16px; .table-info-text {
font-weight: bold; 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 { // 项目名称
.project-name {
font-weight: bold; font-weight: bold;
margin-bottom: 5px; font-size: 14px;
}
.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,
.attachment-table .vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
} }
/* 内容单元格 */ // Cell styles
.content-cell { .content-cell {
line-height: 1.8; line-height: 1.8;
} }
/* 输入框包装器 */ // 表单控件组
.input-wrapper { .radio-group, .checkbox-group {
position: relative;
display: inline-block; display: inline-block;
margin-left: 10px;
.vxe-radio, .vxe-checkbox {
margin-left: 0px;
white-space: nowrap;
line-height: 30px;
padding: 0px 10px;
&--label {
font-size: 12px;
}
&--icon {
font-size: 12px;
}
}
}
// VXE specific overrides
.vxe {
&-radio-group, &-checkbox-group {
display: inline-block;
}
&-cell {
padding: 5px;
}
} }
/* 输入框样式 */
.table-input,
.vxe-input { .vxe-input {
height: 24px;
line-height: 30px;
min-width: 150px;
margin: 0 2px;
padding: 0 5px;
border: none;
border-bottom: 1px solid #333; border-bottom: 1px solid #333;
background: transparent;
text-align: center; text-align: center;
margin: 5px 3px;
padding: 0;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
} }
/* 表单元素的标签和图标 */ // 输入框包装器
.vxe-radio--label, .input-wrapper {
.vxe-checkbox--label { position: relative;
font-size: 12px;
}
.vxe-radio--icon,
.vxe-checkbox--icon {
font-size: 12px;
}
/* 单选框组和复选框组布局 */
.vxe-radio-group,
.vxe-checkbox-group {
display: inline-block;
margin-right: 5px;
max-width: 99%;
}
/* 其他辅助样式 */
.radio-group,
.checkbox-group {
display: inline-block; display: inline-block;
margin-right: 5px;
max-width: 99%;
min-width: 50%;
}
.vxe-radio,
.vxe-checkbox {
margin: 5px 5px 5px 30px;
white-space: nowrap;
}
/* 附件部分样式 */
.attachments {
margin-top: 20px;
width: 100%;
}
.attachments h4 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.attachment-table {
width: 100%;
border: 1px solid #000;
border-collapse: collapse;
} }
/* 其他特殊块 */ // 单位文本
blockquote {
padding: 0 5px;
color: black;
cursor: pointer;
}
/* 单位文本 */
.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;
...@@ -1269,29 +1153,13 @@ blockquote { ...@@ -1269,29 +1153,13 @@ blockquote {
background: #e6f7ff; background: #e6f7ff;
border-radius: 50%; border-radius: 50%;
transition: all 0.3s; transition: all 0.3s;
opacity: 0;
animation: fadeIn 0.5s forwards;
animation-delay: 0.3s;
&:hover { &:hover {
background: #bae7ff; background: #bae7ff;
transform: scale(1.1);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
} }
} }
/* 错误图标 */ // 错误图标
.error-icon { .error-icon {
margin-left: 5px; margin-left: 5px;
cursor: pointer; cursor: pointer;
...@@ -1303,15 +1171,14 @@ blockquote { ...@@ -1303,15 +1171,14 @@ blockquote {
line-height: 20px; line-height: 20px;
text-align: center; text-align: center;
transition: all 0.3s; transition: all 0.3s;
&:hover { &:hover {
transform: scale(1.1); transform: scale(1.1);
} }
} }
/* 提示框 */ // 提示框
.tooltip, .tooltip, .error-tooltip {
.error-tooltip {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
...@@ -1328,7 +1195,7 @@ blockquote { ...@@ -1328,7 +1195,7 @@ blockquote {
.tooltip { .tooltip {
background: #333; background: #333;
color: #fff; color: #fff;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
...@@ -1344,7 +1211,7 @@ blockquote { ...@@ -1344,7 +1211,7 @@ blockquote {
background: #8b0000; background: #8b0000;
color: #fff; color: #fff;
border: 1px solid #ff4d4f; border: 1px solid #ff4d4f;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
...@@ -1355,4 +1222,42 @@ blockquote { ...@@ -1355,4 +1222,42 @@ blockquote {
border-color: transparent transparent #8b0000 transparent; border-color: transparent transparent #8b0000 transparent;
} }
} }
// 加载遮罩
.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> </style>
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<!--引用表格--> <!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection"> <BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #action="{ record }"> <template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" /> <TableAction :actions="getTableAction(record)" />
</template> </template>
</BasicTable> </BasicTable>
<!-- 表单区域 --> <!-- 表单区域 -->
...@@ -93,7 +93,6 @@ ...@@ -93,7 +93,6 @@
} }
async function handlePreview(record) { async function handlePreview(record) {
alert(JSON.stringify(record));
await gotoPage(record); await gotoPage(record);
} }
...@@ -205,7 +204,9 @@ const gotoPage = async (item) => { ...@@ -205,7 +204,9 @@ const gotoPage = async (item) => {
path: targetPath, path: targetPath,
query: { query: {
taskId: taskid, taskId: taskid,
taskName: "",
tplId: tplid, tplId: tplid,
tplName: "",
comfrom: 'archive' comfrom: 'archive'
} }
}); });
......
...@@ -597,7 +597,7 @@ ...@@ -597,7 +597,7 @@
keyword.value = value; keyword.value = value;
} }
function areaChange(value) { function areaChange(value) {
alert(value); //alert(value);
} }
return { return {
......
...@@ -42,7 +42,6 @@ ...@@ -42,7 +42,6 @@
let formData = {}; let formData = {};
const queryById = '/problem/stProblemCheck/queryById'; const queryById = '/problem/stProblemCheck/queryById';
async function initFormData() { async function initFormData() {
alert(1)
let params = { id: props.formData.dataId }; let params = { id: props.formData.dataId };
const data = await defHttp.get({ url: queryById, params }); const data = await defHttp.get({ url: queryById, params });
formData = { ...data }; formData = { ...data };
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论