提交 ecf267ec authored 作者: kxjia's avatar kxjia

修改Tb7 的校验

上级 8c864de6
<template> <template>
<div class="bank-report-table" style="height: 80vh;"> <div class="bank-report-table" style="height: 80vh;" @click="closeAllTooltips">
<!-- 加载遮罩层 -->
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">
<div class="spinner"></div>
<div class="loading-text">加载中...</div>
</div>
</div>
<vxe-toolbar> <vxe-toolbar>
<template #button> <template #button>
...@@ -10,54 +17,11 @@ ...@@ -10,54 +17,11 @@
</template> </template>
<template #tools> <template #tools>
<vxe-button status="primary" icon="vxe-icon-edit" @click="handleOpenHistoryDrawer()" :disabled="loading">近5年数据填报</vxe-button> <vxe-button status="primary" icon="vxe-icon-edit" @click="handleOpenHistoryDrawer()" :disabled="loading">近5年数据填报</vxe-button>
<vxe-button status="primary" icon="vxe-icon-save" @click="checkData()">校验</vxe-button> <vxe-button status="primary" icon="vxe-icon-save" @click="validateData()">校验</vxe-button>
<vxe-button status="primary" icon="vxe-icon-save" @click="saveBatch()">保存</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"
...@@ -72,12 +36,11 @@ ...@@ -72,12 +36,11 @@
<vxe-column field="serialNumber" title="序号" width="80"></vxe-column> <vxe-column field="serialNumber" title="序号" width="80"></vxe-column>
<vxe-column field="project" title="项目" width="200"> <vxe-column field="project" title="项目" width="200">
<template #default="{ row }"> <template #default="{ row }">
<span style="font-weight: bold;size:20px">{{ row.project }}</span> <span style="font-weight: bold; font-size: 14px">{{ row.project }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="content" title="内容" row-resize> <vxe-column field="content" title="内容" row-resize>
<template #default="{ row }"> <template #default="{ row }">
<div class="content-cell"> <div class="content-cell">
<template v-if="row.type === 'AttachTable'"> <template v-if="row.type === 'AttachTable'">
<AttachTable <AttachTable
...@@ -95,54 +58,78 @@ ...@@ -95,54 +58,78 @@
<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 === 'yesno'"> <template v-else-if="item.type === 'yesno'">
<vxe-radio-group v-model="formData[row.code +'_'+ item.field]"> <vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio label="是" content="是"> <vxe-radio label="是" content="是"></vxe-radio>
</vxe-radio>
<vxe-radio label="否" content="否"></vxe-radio> <vxe-radio label="否" content="否"></vxe-radio>
</vxe-radio-group> </vxe-radio-group>
<template v-if="item.extraField||item.extraFields"> <template v-if="item.extraField || item.extraFields">
<div v-if="formData[row.code +'_'+ item.field]=='是'" style="margin-left:40px"> <div v-if="formData[getFieldKey(row.code, item.field)] === '是'" style="margin-left:40px">
如是, 如是,
<template v-if="item.extraField"> <template v-if="item.extraField">
{{item.extraLabel }} {{ item.extraLabel }}
<vxe-input v-model="formData[row.code + '_'+ item.extraField]" style="width: 300px;"></vxe-input> <vxe-input
v-model="formData[getFieldKey(row.code, item.extraField)]"
style="width: 300px;"
></vxe-input>
<span class="unit"> {{ item.unit }}</span> <span class="unit"> {{ item.unit }}</span>
</template> </template>
<template v-else-if="item.extraFields"> <template v-else-if="item.extraFields">
<template v-for="(ccopt,ccind) in item.extraFields" :key="ccind"> <template v-for="(ccopt, ccind) in item.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span> <span>{{ ccopt.label }}: </span>
<template v-if="ccopt.formType=='input'"> <template v-if="ccopt.formType === 'input'">
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" style="width:250px"></vxe-input> <vxe-input
v-model="formData[getFieldKey(row.code, ccopt.field)]"
style="width:250px"
></vxe-input>
</template> </template>
<template v-else-if="ccopt.formType=='number'"> <template v-else-if="ccopt.formType === 'number'">
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" type="number" style="width:250px"></vxe-input> <vxe-input
v-model="formData[getFieldKey(row.code, ccopt.field)]"
type="number"
style="width:250px"
></vxe-input>
<span class="unit"> {{ ccopt.unit }}</span> <span class="unit"> {{ ccopt.unit }}</span>
</template> </template>
<template v-else-if="ccopt.formType=='checkbox'"> <template v-else-if="ccopt.formType === 'checkbox'">
<vxe-checkbox-group v-model="formData[row.code + '_'+ ccopt.field]"> <vxe-checkbox-group v-model="formData[getFieldKey(row.code, ccopt.field)]">
<vxe-checkbox v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt"> <vxe-checkbox
</vxe-checkbox> v-for="(opt, optIndex) in ccopt.options"
:key="optIndex"
:label="opt"
:content="opt"
></vxe-checkbox>
</vxe-checkbox-group> </vxe-checkbox-group>
</template> </template>
<template v-else-if="ccopt.formType=='radio'"> <template v-else-if="ccopt.formType === 'radio'">
<vxe-radio-group v-model="formData[row.code + '_'+ ccopt.field]" :options="ccopt.options"> <vxe-radio-group v-model="formData[getFieldKey(row.code, ccopt.field)]">
<vxe-radio v-for="(opt, optIndex) in ccopt.options" :key="optIndex" :label="opt" :content="opt"> <vxe-radio
</vxe-radio> v-for="(opt, optIndex) in ccopt.options"
:key="optIndex"
:label="opt"
:content="opt"
></vxe-radio>
</vxe-radio-group> </vxe-radio-group>
</template> </template>
<template v-else> <template v-else>
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" :type="ccopt.formType" style="width:100px"></vxe-input> <vxe-input
v-model="formData[getFieldKey(row.code, ccopt.field)]"
:type="ccopt.formType"
style="width:100px"
></vxe-input>
</template> </template>
<br> <br>
<template v-if="ccopt.otherOption"> <template v-if="ccopt.otherOption">
其他:<vxe-input v-model="formData[row.code + '_'+ ccopt.otherField]" style="width: 300px;"></vxe-input> 其他:<vxe-input
v-model="formData[getFieldKey(row.code, ccopt.otherField)]"
style="width: 300px;"
></vxe-input>
</template> </template>
<br> <br>
</template> </template>
...@@ -150,71 +137,172 @@ ...@@ -150,71 +137,172 @@
</div> </div>
</template> </template>
</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[row.code +'_'+ item.field]"> <vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt">{{ opt }}</vxe-radio> <vxe-radio
v-for="(opt, optIndex) in item.options"
:key="optIndex"
:label="opt"
>{{ opt }}</vxe-radio>
</vxe-radio-group> </vxe-radio-group>
</span> </span>
<!-- 带额外字段的单选组 -->
<span v-else-if="item.type === 'radio-group-extraFields'" class="radio-group" style="width:100%"> <span v-else-if="item.type === 'radio-group-extraFields'" class="radio-group" style="width:100%">
<vxe-radio-group v-model="formData[row.code +'_'+ item.field]"> <vxe-radio-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-radio v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt.label"> <vxe-radio
v-for="(opt, optIndex) in item.options"
:key="optIndex"
:label="opt.label"
>
{{ opt.label }} {{ opt.label }}
<template v-if="opt.extraField&&formData[row.code +'_'+ item.field]==opt.label"> <template v-if="opt.extraField && formData[getFieldKey(row.code, item.field)] === opt.label">
<span>{{ opt.extraField }}</span> <span>{{ opt.extraField }}</span>
<vxe-input v-model="formData[row.code + '_'+ opt.otereField]" style="width: 200px;"></vxe-input> <vxe-input
v-model="formData[getFieldKey(row.code, opt.otereField)]"
style="width: 200px;"
></vxe-input>
</template> </template>
<template v-if="opt.extraFields&&formData[row.code +'_'+ item.field]==opt.label"> <template v-if="opt.extraFields && formData[getFieldKey(row.code, item.field)] === opt.label">
<template v-for="(ccopt,ccind) in opt.extraFields" :key="ccind"> <template v-for="(ccopt, ccind) in opt.extraFields" :key="ccind">
<span>{{ ccopt.label }}: </span> <span>{{ ccopt.label }}: </span>
<vxe-input v-model="formData[row.code + '_'+ ccopt.field]" style="width: 200px;"></vxe-input> <vxe-input
v-model="formData[getFieldKey(row.code, ccopt.field)]"
style="width: 200px;"
></vxe-input>
</template> </template>
</template> </template>
</vxe-radio> </vxe-radio>
</vxe-radio-group> </vxe-radio-group>
</span> </span>
<!-- 多选组 -->
<div v-else-if="item.type === 'checkbox-group'" class="checkbox-group"> <div v-else-if="item.type === 'checkbox-group'" class="checkbox-group">
<span class="checkbox-group"> <vxe-checkbox-group v-model="formData[getFieldKey(row.code, item.field)]">
<vxe-checkbox-group v-model="formData[row.code+'_'+item.field]"> <vxe-checkbox
<vxe-checkbox v-for="(opt, optIndex) in item.options" :key="optIndex" :label="opt"> v-for="(opt, optIndex) in item.options"
:key="optIndex"
:label="opt"
>
{{ opt }} {{ opt }}
</vxe-checkbox> </vxe-checkbox>
<template v-if="item.otherField"> <template v-if="item.otherField">
<vxe-checkbox label="其他"> <vxe-checkbox label="其他">
其他: 其他:
<vxe-input v-model="formData[row.code + '_'+ item.otherField]" <vxe-input
:disabled="!(formData[row.code+'_'+item.field]?.indexOf('其他')>-1)" v-model="formData[getFieldKey(row.code, item.otherField)]"
style="width: 200px;"> :disabled="!formData[getFieldKey(row.code, item.field)]?.includes('其他')"
</vxe-input> style="width: 200px;"
></vxe-input>
</vxe-checkbox> </vxe-checkbox>
</template> </template>
</vxe-checkbox-group> </vxe-checkbox-group>
</span>
</div> </div>
<template v-else-if="item.type=='textarea'">
<vxe-textarea v-model="formData[row.code +'_'+ item.field]" :rows="item.rows" :style="{width:item.width}"> <!-- 文本域 -->
</vxe-textarea> <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>
<!-- 其他输入类型 -->
<template v-else> <template v-else>
<vxe-input :type="item.type" v-model="formData[row.code +'_'+ item.field]" size="mini" class="table-input"> <div class="input-wrapper">
</vxe-input> <vxe-input
</template> :type="item.type"
v-model="formData[getFieldKey(row.code, item.field)]"
size="mini"
class="table-input"
:style="{ width: item.width }"
:disabled="!item.hasRight"
@blur="handleInputBlur(row.code, item.field, item.matchedFormula?.formula)"
/>
<span v-if="item.unit" class="unit"> {{ item.unit }}</span> <span v-if="item.unit" class="unit"> {{ item.unit }}</span>
<!-- 帮助图标 -->
<span
v-if="showHelpIcon(row.code, item.field, item)"
class="help-icon"
@click.stop="toggleTooltip(row.code, item.field)"
title="点击查看校验规则"
>
?
</span>
<!-- 错误图标 -->
<span
v-if="inputErrors[getFieldKey(row.code, item.field)]"
class="error-icon"
@click.stop="toggleErrorTooltip(row.code, item.field)"
title="点击查看错误详情"
>
</span>
<!-- 帮助提示 -->
<div
v-if="showTooltip && hoveredKey === getFieldKey(row.code, item.field)"
class="tooltip"
@click.stop
>
{{ item.matchedFormula?.des || '暂无校验规则' }}
</div>
<!-- 错误提示 -->
<div
v-if="showErrorTooltip && hoveredErrorKey === getFieldKey(row.code, item.field)"
class="error-tooltip"
@click.stop
>
{{ inputErrors[getFieldKey(row.code, item.field)].message }}
<br>
<span style="color: #ffcccc;">
公式: {{ inputErrors[getFieldKey(row.code, item.field)].formula }}
</span>
<template v-if="inputErrors[getFieldKey(row.code, item.field)].error">
<br>
<span style="color: #ff9999; font-size: 11px;">
错误: {{ inputErrors[getFieldKey(row.code, item.field)].error }}
</span>
</template>
</div>
</div>
</template>
</template> </template>
</template> </template>
</div> </div>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="remarks" title="备注" width="200"> <vxe-column field="remarks" title="备注" width="200">
<template #default="{ row }"> <template #default="{ row }">
<vxe-textarea :rows="row.remarks.rows" v-model="formData[row.code+'_'+row.remarks.field]"> <vxe-textarea
</vxe-textarea> :rows="row.remarks.rows"
v-model="formData[getFieldKey(row.code, row.remarks.field)]"
></vxe-textarea>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<!-- 历史填报检查组件 --> <!-- 历史填报检查组件 -->
<HistoryFillCheck ref="historyFillCheckRef" v-model="historyDrawerVisible" @closeDrawer="closeHistoryDrawer"/> <HistoryFillCheck
ref="historyFillCheckRef"
v-model="historyDrawerVisible"
@closeDrawer="closeHistoryDrawer"
/>
<!-- 校验结果抽屉 -->
<ValidationDrawer
ref="validationDrawerRef"
v-model="drawerVisible"
:tableFormData="tableFormData"
@validationResultClick="handleValidationResultClick"
/>
<!-- 验证公式帮助提示 --> <!-- 验证公式帮助提示 -->
<div <div
...@@ -230,392 +318,598 @@ ...@@ -230,392 +318,598 @@
<script lang="ts" setup> <script lang="ts" setup>
import AttachTable from '../tableComponents/AttachTable.vue' import AttachTable from '../tableComponents/AttachTable.vue'
import HistoryFillCheck from './check/historyFillCheck.vue' import HistoryFillCheck from './check/historyFillCheck.vue'
import ValidationDrawer from './check/ValidationDrawer.vue'
import { tableFormData } from '../../data/tb7.data';
import { ref, reactive,nextTick,onMounted,toRaw } from 'vue' import { tableFormData } from '../../data/tb7.data'
import { VxeUI,VxeToolbarInstance,VxeTablePropTypes } from 'vxe-table' import { ref, reactive, computed, nextTick, onMounted } from 'vue'
import { batchSaveOrUpdate, queryRecord,batchSaveOrUpdateBeforeDelete } from '../../record/BaosongTaskRecord.api' import { VxeUI } from 'vxe-table'
import { queryAllTplItemForUser, findUserRightForTplItem } from '../../alloc/BaosongTaskAlloc.api' import {
queryRecord,
batchSaveOrUpdateBeforeDelete
} from '../../record/BaosongTaskRecord.api'
import { 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'; import { useRoute } from 'vue-router'
const route = useRoute(); // 路由相关
const tableRef = ref(); const route = useRoute()
const tplItemMap = ref({});
const historyFillCheckRef = ref<any>(null); // 组件引用
const tableRef = ref()
const historyFillCheckRef = ref<any>(null)
const validationDrawerRef = ref<any>(null)
// 查询参数
const queryParam = ref({ const queryParam = ref({
taskId:-1, taskId: -1,
taskName:'', taskName: '',
tplId:-1, tplId: -1,
tplName:'', tplName: '',
tplCode:'' tplCode: ''
}) })
// 权限相关状态 // 状态变量
const loading = ref(false)
const formData = reactive<Record<string, any>>({})
const formValues = ref<FormData[]>([])
const tplItemMap = ref<Record<string, TplItemInfo>>({})
const childAttachTableRefs = ref<Record<string, any>>({})
// 工具提示状态
const showTooltip = ref(false)
const hoveredKey = ref('')
const showErrorTooltip = ref(false)
const hoveredErrorKey = ref('')
const inputErrors = ref<Record<string, InputError>>({})
const isInitialized = ref(false)
// 权限和验证相关
const userAllocItems = ref<string[]>([]) const userAllocItems = ref<string[]>([])
const validFormula = ref<any[]>([]) const validFormula = ref<FormulaItem[]>([])
const validationResultsList = ref<any[]>([])
const drawerVisible = ref(false) const drawerVisible = ref(false)
const historyDrawerVisible = ref(false) const historyDrawerVisible = ref(false)
// 验证公式提示
const validationTooltipVisible = ref(false) const validationTooltipVisible = ref(false)
const validationTooltipPosition = ref({ left: 0, top: 0 }) const validationTooltipPosition = ref({ left: 0, top: 0 })
const validationTooltipContent = ref('') const validationTooltipContent = ref('')
// 计算属性
const fieldKeys = computed(() => Object.keys(formData))
// 接口定义
interface FormulaItem {
formula: string
des: string
[key: string]: any
}
interface InputError {
message: string
formula: string
error?: string
}
interface FormData { interface FormData {
id: number | null id: number | null
taskid: number taskid: number
tplid: number tplid: number
itemid?: number itemid?: number
itempid?:number itempid?: number
content: string content: string
tplcode:string tplcode: string
rind:number rind: number
} }
onMounted(async ()=>{ interface TplItemInfo {
if (route.query.taskId) { pid: number
queryParam.value.taskId = Number(route.query.taskId); itemid: number
} formTp: string
if (route.query.taskName) { code: string
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);
}
// 获取权限和验证公式 // 生命周期钩子
onMounted(async () => {
await initializePage()
})
// 初始化页面
const initializePage = async () => {
try { try {
loading.value = true
await setQueryParams()
// 并行获取权限和验证公式
await Promise.all([
fetchUserRights(),
fetchValidationFormulas()
])
console.log('获取到的权限:', userAllocItems.value.length)
console.log('获取到的公式:', validFormula.value.length)
await setTplItemMap()
setFormItemRight()
await setData()
isInitialized.value = true
// 延迟刷新帮助图标,确保DOM已渲染
setTimeout(() => {
refreshHelpIcons()
}, 300)
} catch (error) {
console.error('初始化页面失败:', error)
showErrorMessage('初始化页面失败', error)
} finally {
loading.value = false
}
}
// 设置查询参数
const setQueryParams = () => {
const params = route.query
if (params.taskId) queryParam.value.taskId = Number(params.taskId)
if (params.taskName) queryParam.value.taskName = String(params.taskName)
if (params.tplId) queryParam.value.tplId = Number(params.tplId)
if (params.tplName) queryParam.value.tplName = String(params.tplName)
if (params.tplCode) queryParam.value.tplCode = String(params.tplCode)
}
// 获取用户权限
const fetchUserRights = async () => {
userAllocItems.value = await findUserRightForTplItem({ userAllocItems.value = await findUserRightForTplItem({
tplid: queryParam.value.tplId, tplid: queryParam.value.tplId,
taskid: queryParam.value.taskId taskid: queryParam.value.taskId
}) })
}
// 获取验证公式
const fetchValidationFormulas = async () => {
validFormula.value = await getTblvalidFormula({ validFormula.value = await getTblvalidFormula({
tplid: queryParam.value.tplId, tplid: queryParam.value.tplId,
}) })
} catch (error) { }
console.error('获取权限或验证公式失败:', error)
}
await setTplItemMap()
await setData();
})
const loading = ref(false)
const formData = reactive({});
const formValues = ref<FormData[]>([])
// 保存批量数据
const saveBatch = async () => { const saveBatch = async () => {
try { try {
loading.value = true
formValues.value = [] formValues.value = []
// 收集主表单数据
for (const strKey in formData) { for (const strKey in formData) {
const valData = formData[strKey] const valData = formData[strKey]
if (valData) { if (valData) {
let tmpAry = strKey.split("_") const [pcode, field] = strKey.split('_')
await setFormValues(tmpAry[0],tmpAry[1],valData,1) await setFormValues(pcode, field, valData, 1)
} }
} }
await getAttachTableFormData(); // 收集附件表格数据
await getAttachTableFormData()
// 保存数据
if (formValues.value.length > 0) { if (formValues.value.length > 0) {
await batchSaveOrUpdateBeforeDelete(formValues.value) await batchSaveOrUpdateBeforeDelete(formValues.value)
VxeUI.modal.message({ content: '保存成功', status: 'success' }) VxeUI.modal.message({ content: '保存成功', status: 'success' })
//await setData()
} else { } else {
VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' }) VxeUI.modal.message({ content: '没有需要保存的数据', status: 'warning' })
} }
} catch (error) { } catch (error) {
VxeUI.modal.message({ content: `保存失败: ${error.message}`, status: 'error' }) showErrorMessage('保存失败', error)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// 设置附件表格引用
const childAttachTableRefs = ref({}) const setAttachTableRef = (el: any, code: string) => {
const setAttachTableRef = (el: any, index: string) => {
if (el) { if (el) {
childAttachTableRefs.value[index] = el; // 保存子组件实例 childAttachTableRefs.value[code] = el
} }
}; }
// 获取附件表格表单数据
const getAttachTableFormData = async () => { const getAttachTableFormData = async () => {
for (const pcode in childAttachTableRefs.value) { for (const pcode in childAttachTableRefs.value) {
const child = childAttachTableRefs.value[pcode]; const child = childAttachTableRefs.value[pcode]
if (child) { if (child) {
const datas = child.getFormData() const datas = child.getFormData()
for (const code in datas) {
for(const code in datas) { await setFormValues(pcode, code, datas[code], 1)
await setFormValues(pcode,code,datas[code],1)
} }
} }
} }
}; }
// 设置表单值
const setFormValues = async (pcode: string, code: string, valData: any, rind: number) => {
if (!valData) return
const strKey = `${pcode}_${code}`
const item = tplItemMap.value[strKey]
if (!item) {
console.warn(`未找到对应的模板项: ${strKey}`)
return
}
const setFormValues = async (pcode,code,valData,rind) => { const vals = Array.isArray(valData) ? valData.join(',') : String(valData)
if(!valData) return;
let vals = Array.isArray(valData) ? valData.join(',') : valData
let strKey = pcode+"_"+code
const item = tplItemMap.value[strKey];
const { pid, itemid } = item ?? {};
let tempForm: FormData = { const tempForm: FormData = {
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: vals
}; }
formValues.value.push(tempForm) formValues.value.push(tempForm)
} }
async function setData() { // 加载数据
const setData = async () => {
try { try {
loading.value = true loading.value = true
const taskId = queryParam.value.taskId
const tplid = queryParam.value.tplId // 清空表单数据
Object.keys(formData).forEach(key => delete formData[key]) Object.keys(formData).forEach(key => delete formData[key])
await setTplItemMap();
const recordData = await queryRecord({taskid: taskId, tplid: tplid})
let valueObj = {}
for(let data of recordData) {
valueObj[data.itempid+"_" + data.itemid+"_"+ data.rind] = data["content"]
}
await setTplItemMap()
// 查询记录数据
const recordData = await queryRecord({
taskid: queryParam.value.taskId,
tplid: queryParam.value.tplId
})
// 构建数据映射
const valueMap: Record<string, string> = {}
recordData.forEach((data: any) => {
const key = `${data.itempid}_${data.itemid}_${data.rind}`
valueMap[key] = data.content
})
// 设置主表单数据
Object.keys(tplItemMap.value).forEach(strKey => { Object.keys(tplItemMap.value).forEach(strKey => {
const item = tplItemMap.value[strKey]; const item = tplItemMap.value[strKey]
const { pid, itemid,formTp } = item ?? {}; const dataKey = `${item.pid}_${item.itemid}_1`
const dataVal = valueObj[pid+"_"+itemid+"_1" ] const dataVal = valueMap[dataKey]
if(formTp == 'checkbox') {
formData[strKey] = dataVal?.split(",") if (item.formTp === 'checkbox') {
formData[strKey] = dataVal?.split(',') || []
} else { } else {
formData[strKey] = dataVal formData[strKey] = dataVal || ''
} }
}); })
// 设置附件表格数据
Object.keys(childAttachTableRefs.value).forEach(pcode => { Object.keys(childAttachTableRefs.value).forEach(pcode => {
const child = childAttachTableRefs.value[pcode]; const child = childAttachTableRefs.value[pcode]
const matchingKeys = Object.keys(tplItemMap.value).filter(key => key.startsWith(pcode + '_')); const matchingKeys = Object.keys(tplItemMap.value).filter(key => key.startsWith(pcode + '_'))
const matchingObjects = matchingKeys.map(key => tplItemMap.value[key]); const matchingObjects = matchingKeys.map(key => tplItemMap.value[key])
let attachTableData = {}
for(let tmpData of matchingObjects) { const attachTableData: Record<string, any> = {}
const { pid, itemid,formTp,code } = tmpData ?? {}; matchingObjects.forEach(tmpData => {
if(formTp == 'checkbox') { const dataKey = `${tmpData.pid}_${tmpData.itemid}_1`
attachTableData[code] = valueObj[pid+"_"+itemid+"_1"]?.split(",") const dataVal = valueMap[dataKey]
if (tmpData.formTp === 'checkbox') {
attachTableData[tmpData.code] = dataVal?.split(',') || []
} else { } else {
attachTableData[code] = valueObj[pid+"_"+itemid+"_1"] attachTableData[tmpData.code] = dataVal || ''
}
} }
child.setFormData(attachTableData)
}) })
child.setFormData(attachTableData)
})
} catch (error) { } catch (error) {
VxeUI.modal.message({ content: `加载数据失败: ${error.message}`, status: 'error' }) showErrorMessage('加载数据失败', error)
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// 设置模板项映射
async function setTplItemMap() { const setTplItemMap = async () => {
try { try {
const taskId = queryParam.value.taskId const tplItems = await allTplItems({
const tplid = queryParam.value.tplId tplid: queryParam.value.tplId,
const tplItem = await allTplItems({
tplid: tplid,
}) })
tplItem.forEach(item => { const map: Record<string, TplItemInfo> = {}
let strKey = item.pcode + "_" + item.xmlcode; tplItems.forEach(item => {
tplItemMap.value[strKey] = {pid:item.pid,itemid:item.id,formTp:item.formTp,code:item.xmlcode}; const strKey = `${item.pcode}_${item.xmlcode}`
map[strKey] = {
pid: item.pid,
itemid: item.id,
formTp: item.formTp,
code: item.xmlcode
}
}) })
tplItemMap.value = map
} catch (error) { } catch (error) {
VxeUI.modal.message({ content: `加载数据失败: ${error.message}`, status: 'error' }) showErrorMessage('加载模板项失败', error)
} finally {
} }
}
// 工具函数
const getFieldKey = (pcode: string | undefined, field: string): string => {
return pcode ? `${pcode}_${field}` : field
} }
// 校验数据 // 验证数据
const checkData = () => { const validateData = () => {
drawerVisible.value = true drawerVisible.value = true
validationDrawerRef.value.setValidateData(validFormula.value, formData)
} }
// 校验结果抽屉显示时触发 // 输入框失去焦点处理
const handleValidationDrawerShow = () => { const handleInputBlur = (rowCode: string, field: string, formula?: string) => {
performValidation() if (!formula) return
}
// 执行校验 const key = getFieldKey(rowCode, field)
const performValidation = () => { const value = formData[key]
validationResultsList.value = []
const process: string[] = []
process.push('开始校验')
process.push(`找到 ${validFormula.value.length} 个验证公式`)
validFormula.value.forEach((formulaItem: any) => { if (!value || value === '') {
process.push(`开始校验公式: ${formulaItem.des}`) delete inputErrors.value[key]
const result = evaluateFormula(formulaItem.formula, formulaItem.des, process) return
if (result) {
validationResultsList.value.push(result)
} }
})
process.push('校验完成') validateFieldFormula(rowCode, field, formula)
} }
// 解析和执行验证公式 // 验证字段公式
const evaluateFormula = (formula: string, description: string, process: string[]): any => { const validateFieldFormula = (rowCode: string, field: string, formula: string) => {
const key = getFieldKey(rowCode, field)
try { try {
const fieldMatch = formula.match(/\[(\w+)\]/) const expression = buildExpression(formula, rowCode)
if (!fieldMatch) { const isValid = eval(expression)
process.push(`跳过: 无法解析公式字段 ${formula}`)
return null
}
const fieldName = fieldMatch[1] if (!isValid) {
const row = tableFormData.find((r: any) => r.content?.some((c: any) => c.field === fieldName)) inputErrors.value[key] = {
if (!row) { message: "公式校验失败,请检查填写内容",
process.push(`跳过: 未找到字段 ${fieldName}`) formula
return null
} }
} else {
const fieldItem = row.content.find((c: any) => c.field === fieldName) delete inputErrors.value[key]
if (!fieldItem) { }
process.push(`跳过: 未找到字段 ${fieldName}`) } catch (error: any) {
return null inputErrors.value[key] = {
message: "公式校验失败,请检查填写内容",
formula,
error: error.message
} }
}
}
const strKey = `${row.code}_${fieldName}` // 构建表达式
const fieldValue = formData[strKey] const buildExpression = (formula: string, rowCode: string): string => {
const reservedWords = ['and', 'or', 'not', 'equal', 'less', 'greater', 'if', 'else', 'true', 'false']
// 检查是否有空字段规则 // 提取字段名
const emptyFieldRule = validFormula.value.find((f: any) => const fieldNames = formula.match(/\b[A-Za-z][A-Za-z0-9_]*\b/g) || []
f.formula.includes(fieldName) && f.des.includes('空字段') const uniqueFields = [...new Set(fieldNames.filter(f =>
) !reservedWords.includes(f.toLowerCase())
))]
if (emptyFieldRule && (!fieldValue || fieldValue === '')) { // 按长度降序排序,避免替换时出现部分匹配
process.push(`跳过: 字段 ${fieldName} 为空,跳过空字段规则`) uniqueFields.sort((a, b) => b.length - a.length)
return null
let expression = formula
for (const fieldName of uniqueFields) {
const key = getFieldKey(rowCode, fieldName)
const value = formData[key]
if (value !== undefined && value !== '') {
expression = expression.replace(
new RegExp(`\\b${fieldName}\\b`, 'g'),
`Number(${value})`
)
}
} }
const expression = formula.replace(/\[(\w+)\]/g, (_, field) => { return expression
const value = formData[`${row.code}_${field}`] }
return value !== undefined ? `Number(${value})` : '0'
})
process.push(`执行公式: ${expression}`) // 工具提示控制
const isValid = eval(expression) const toggleTooltip = (rowCode: string, field: string) => {
const key = getFieldKey(rowCode, field)
if (hoveredKey.value === key) {
showTooltip.value = false
hoveredKey.value = ''
} else {
showTooltip.value = true
hoveredKey.value = key
}
}
return { const toggleErrorTooltip = (rowCode: string, field: string) => {
field: fieldName, const key = getFieldKey(rowCode, field)
description: description, if (hoveredErrorKey.value === key) {
formula: formula, showErrorTooltip.value = false
isValid: isValid, hoveredErrorKey.value = ''
fieldValue: fieldValue, } else {
rowCode: row.code showErrorTooltip.value = true
hoveredErrorKey.value = key
} }
} catch (error) { }
process.push(`公式执行错误: ${error instanceof Error ? error.message : String(error)}`)
return null const closeAllTooltips = () => {
showTooltip.value = false
hoveredKey.value = ''
showErrorTooltip.value = false
hoveredErrorKey.value = ''
}
// 查找字段对应的最早公式
const findEarliestFormulaForField = (
formulas: FormulaItem[],
field: string
): { formula: FormulaItem | null, index: number } => {
let result = { formula: null, index: Infinity }
formulas?.forEach((f: FormulaItem, idx: number) => {
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 handleValidationResultClick = (result: any) => { const refreshHelpIcons = () => {
const row = tableFormData.find((r: any) => r.code === result.rowCode) console.log('刷新帮助图标...')
if (row) {
const fieldItem = row.content.find((c: any) => c.field === result.field) tableFormData.forEach((row: any) => {
if (fieldItem) { if (row.content && Array.isArray(row.content)) {
const strKey = `${row.code}_${result.field}` row.content.forEach((item: any) => {
const inputElement = document.querySelector(`[data-field="${strKey}"]`) as HTMLElement if (item.field) {
if (inputElement) { const key = getFieldKey(row.code, item.field)
inputElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) const hasRight = userAllocItems.value.includes(key)
inputElement.focus()
inputElement.style.border = '2px solid #ff4d4f' if (hasRight) {
setTimeout(() => { const formulaResult = findEarliestFormulaForField(validFormula.value, item.field)
inputElement.style.border = '' item.hasValidFormula = !!formulaResult.formula
}, 2000) item.matchedFormula = formulaResult.formula || null
} else {
item.hasValidFormula = false
item.matchedFormula = null
} }
} }
})
} }
})
nextTick(() => {
console.log('帮助图标刷新完成')
})
}
// 处理验证结果点击
const handleValidationResultClick = (result: any) => {
const message = `字段 ${result.field} 的校验结果: ${result.isValid ? '通过' : '失败'}`
const status = result.isValid ? 'success' : 'error'
VxeUI.modal.message({ content: message, status })
} }
// 显示验证公式帮助 // 显示帮助图标
const showValidationHelp = (formulaItem: any, event: MouseEvent) => { const showHelpIcon = (rowCode: string, field: string, item: any): boolean => {
const rect = (event.target as HTMLElement).getBoundingClientRect() if (item.hasValidFormula !== undefined) {
validationTooltipPosition.value = { return item.hasValidFormula === true
left: rect.left + rect.width / 2,
top: rect.top - 10
} }
validationTooltipContent.value = `验证公式: ${formulaItem.formula}\n说明: ${formulaItem.des}`
validationTooltipVisible.value = 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 closeTooltip = () => { const setFormItemRight = () => {
validationTooltipVisible.value = false console.log('开始设置表单权限...')
console.log('userAllocItems 数量:', userAllocItems.value.length)
console.log('validFormula 数量:', validFormula.value.length)
tableFormData.forEach((row: any) => {
if (row.content && Array.isArray(row.content)) {
row.content.forEach((item: any) => {
if (item.field) {
const key = getFieldKey(row.code, item.field)
item.hasRight = userAllocItems.value.includes(key)
console.log(`字段 ${key}: hasRight = ${item.hasRight}`)
if (item.hasRight) {
const formulaResult = findEarliestFormulaForField(validFormula.value, item.field)
item.hasValidFormula = !!formulaResult.formula
item.matchedFormula = formulaResult.formula || null
console.log(`字段 ${key}: hasValidFormula = ${item.hasValidFormula}`)
} else {
item.hasValidFormula = false
item.matchedFormula = null
}
}
})
}
})
console.log('表单权限设置完成')
} }
// 打开历史填报抽屉 // 历史数据填报
const handleOpenHistoryDrawer = () => { const handleOpenHistoryDrawer = () => {
historyDrawerVisible.value = true historyDrawerVisible.value = true
} }
// 关闭历史填报抽屉
const closeHistoryDrawer = () => { const closeHistoryDrawer = () => {
historyDrawerVisible.value = false historyDrawerVisible.value = false
} }
const mergeCells = ref<VxeTablePropTypes.MergeCells>([ // 错误消息显示
{ row: 7, col: 2, rowspan: 1, colspan: 2 }, const showErrorMessage = (title: string, error: any) => {
]) const message = error instanceof Error ? error.message : String(error)
VxeUI.modal.message({
content: `${title}: ${message}`,
status: 'error'
})
}
// 合并单元格配置
const mergeCells = ref([
{ row: 7, col: 2, rowspan: 1, colspan: 2 }
])
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
/* 基础字体和排版 */
.bank-report-table { .bank-report-table {
font-family: "SimSun", "宋体", serif; font-family: "SimSun", "宋体", serif;
font-size: 12px; font-size: 12px;
color: #000; color: #000;
margin: 5px auto; margin: 5px auto;
width: 90%; /* 统一使用一个宽度 */ width: 90%;
position: relative;
} }
/* 表格样式 */ /* 表格样式 */
.custom-table, .attachment-table { .custom-table {
width: 100%; width: 100%;
border: 1px solid #000; border: 1px solid #000;
border-collapse: collapse; /* 统一边框处理 */ border-collapse: collapse;
.vxe-header--column,
.vxe-body--column {
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 5px;
}
} }
/* 加载遮罩层样式 */ /* 加载遮罩层 */
.loading-overlay { .loading-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(255, 255, 255, 0.8); background-color: rgba(255, 255, 255, 0.9);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
...@@ -634,7 +928,7 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -634,7 +928,7 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
border: 3px solid #f3f3f3; border: 3px solid #f3f3f3;
border-top: 3px solid #3498db; border-top: 3px solid #3498db;
border-radius: 50%; border-radius: 50%;
animation: spin 2s linear infinite; animation: spin 1s linear infinite;
} }
@keyframes spin { @keyframes spin {
...@@ -648,197 +942,189 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([ ...@@ -648,197 +942,189 @@ const mergeCells = ref<VxeTablePropTypes.MergeCells>([
font-size: 14px; font-size: 14px;
} }
/* 校验结果样式 */ /* 内容单元格 */
.validation-results { .content-cell {
padding: 20px; line-height: 1.8;
height: 600px; min-height: 30px;
overflow-y: auto;
} }
.result-summary { /* 输入框样式 */
display: flex; .table-input,
gap: 20px; .vxe-input {
margin-bottom: 20px; height: 24px;
padding-bottom: 10px; line-height: 24px;
border-bottom: 1px solid #eee; min-width: 150px;
} margin: 0 2px;
padding: 0 5px;
border: 1px solid #d9d9d9;
border-radius: 2px;
background: #fff;
text-align: left;
.summary-item { &:disabled {
display: flex; background-color: #f5f5f5;
flex-direction: column; cursor: not-allowed;
}
} }
.summary-label { /* 表单组件样式 */
font-size: 12px; .vxe-radio-group,
color: #666; .vxe-checkbox-group {
display: inline-flex;
flex-wrap: wrap;
gap: 8px;
margin-right: 5px;
max-width: 100%;
} }
.summary-value { .vxe-radio,
font-size: 16px; .vxe-checkbox {
font-weight: bold; margin: 2px 5px;
color: #333; white-space: nowrap;
} }
.summary-value.success { .radio-group,
color: #52c41a; .checkbox-group {
display: inline-block;
margin-right: 5px;
max-width: 99%;
} }
.summary-value.error { /* 单位样式 */
color: #ff4d4f; .unit {
margin-left: 4px;
color: #666;
font-size: 12px;
} }
.results-list { /* 图标样式 */
margin-top: 10px; .input-wrapper {
display: inline-flex;
align-items: center;
position: relative;
gap: 4px;
} }
.result-item { .help-icon {
padding: 10px; display: inline-flex;
margin-bottom: 8px; align-items: center;
border-radius: 4px; justify-content: center;
width: 16px;
height: 16px;
background-color: #1890ff;
color: white;
border-radius: 50%;
font-size: 11px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; user-select: none;
}
.result-item:hover {
background-color: #f5f5f5;
}
.result-item.success { &:hover {
background-color: #f6ffed; background-color: #40a9ff;
border-left: 4px solid #52c41a; }
}
.result-item.error {
background-color: #fff2f0;
border-left: 4px solid #ff4d4f;
}
.result-field {
font-weight: bold;
margin-bottom: 5px;
}
.result-desc {
font-size: 12px;
color: #666;
margin-bottom: 5px;
} }
.result-value { .error-icon {
font-size: 12px; display: inline-flex;
color: #999; align-items: center;
} justify-content: center;
width: 16px;
height: 16px;
background-color: #ff4d4f;
color: white;
border-radius: 50%;
font-size: 11px;
cursor: pointer;
user-select: none;
.result-error { &:hover {
font-size: 12px; background-color: #ff7875;
color: #ff4d4f; }
margin-top: 5px;
} }
/* 验证公式帮助提示样式 */ /* 工具提示样式 */
.validation-tooltip { .tooltip {
position: absolute; position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
background-color: #333; background-color: #333;
color: #fff; color: #fff;
padding: 10px; padding: 8px 12px;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
z-index: 1000; z-index: 1000;
white-space: normal;
max-width: 300px;
word-wrap: break-word;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tooltip-content {
margin: 0;
white-space: pre-wrap;
}
/* 表格单元格样式 */ &::before {
.custom-table .vxe-header--column, content: '';
.custom-table .vxe-body--column, position: absolute;
.attachment-table .vxe-header--column, top: -4px;
.attachment-table .vxe-body--column { left: 10px;
border-right: 1px solid #000; border-left: 4px solid transparent;
border-bottom: 1px solid #000; border-right: 4px solid transparent;
padding: 5px; border-bottom: 4px solid #333;
} }
/* 内容单元格 */
.content-cell {
line-height: 1.8;
}
/* 输入框样式 */
.table-input,
.vxe-input {
height: 24px;
line-height: 30px;
min-width: 150px;
margin: 0 2px;
padding: 0 5px;
border: none; /* 去掉多余的边框,只留底部边框 */
border-bottom: 1px solid #333;
background: transparent;
text-align: center;
}
/* 表单元素的标签和图标 */
.vxe-radio--label,
.vxe-checkbox--label {
font-size: 12px;
} }
.vxe-radio--icon, .error-tooltip {
.vxe-checkbox--icon { position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
background-color: #fff2f0;
color: #ff4d4f;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px; font-size: 12px;
} z-index: 1000;
white-space: normal;
/* 单选框组和复选框组布局 */ max-width: 300px;
.vxe-radio-group, word-wrap: break-word;
.vxe-checkbox-group { border: 1px solid #ffccc7;
display: inline-block; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
margin-right: 5px;
max-width: 99%;
}
/* 其他辅助样式 */
.radio-group,
.checkbox-group {
display: inline-block;
margin-right: 5px;
max-width: 99%;
}
.vxe-radio,
.vxe-checkbox {
margin: 5px 5px 5px 30px;
white-space: nowrap;
}
/* 附件部分样式 */ &::before {
.attachments { content: '';
margin-top: 20px; position: absolute;
width: 100%; top: -4px;
left: 10px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #ffccc7;
}
} }
.attachments h4 { /* 验证公式提示 */
font-size: 14px; .validation-tooltip {
font-weight: bold; position: fixed;
margin-bottom: 10px; background-color: #333;
} color: #fff;
padding: 12px;
border-radius: 4px;
font-size: 12px;
z-index: 1001;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
max-width: 400px;
.attachment-table { .tooltip-content {
width: 100%; margin: 0;
border: 1px solid #000; white-space: pre-wrap;
border-collapse: collapse; /* 统一边框处理 */ word-break: break-word;
}
} }
/* 无用或重复定义已优化移除 */ /* 响应式调整 */
@media (max-width: 1200px) {
.bank-report-table {
width: 95%;
}
/* 其他特殊块 */ .table-input,
blockquote { .vxe-input {
padding: 0 5px; min-width: 120px;
color: black; }
cursor: pointer;
} }
</style> </style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论