Logo
一梦五千年
  • 工具箱
  • AI 平台
  • 生活随笔
  • 友链
获取天气...
登录
系统状态: 在线
内核版本: 5.15.0-88-GENERIC时区: UTC+8运行时间:
© 2026 一梦五千年
未分类

《基于 form-create 的动态列与动态查询表单实战指南(含回显与踩坑详解)》

4/28/202639 分钟读完0 次浏览

作者信息

MM
mml
@mml

这个人很懒,什么都没写。

目录

暂无可提取的标题

互动

表单数据页动态列与动态查询条件实现文档

一、整体架构概述

┌─────────────────────────────────────────────────────────────┐
│                      form-data/index.vue                     │
├─────────────────────────────────────────────────────────────┤
│  useFormDataCenter (Composable)                              │
│  ├── formList          - 表单列表                            │
│  ├── currentFormId     - 当前选中表单ID                      │
│  ├── formConfigCache   - 表单配置缓存                        │
│  ├── queryFields       - [计算属性] 动态查询字段             │
│  ├── tableColumns      - [计算属性] 动态表格列               │
│  ├── tableData         - 表格数据                            │
│  ├── queryParams       - 查询参数                            │
│  └── loadRemoteOptions - 加载远程选项                        │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  FormQuery 组件                                              │
│  └── 根据 fields 动态渲染查询表单项                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  vxe-grid 表格                                               │
│  └── 根据 tableColumns 动态渲染列                            │
└─────────────────────────────────────────────────────────────┘

二、动态列实现

2.1 核心思路

通过表单配置动态生成列配置数组,而非写死列定义。

2.2 实现代码

// useFormDataCenter.ts // 1. 表格列配置 - 计算属性 const tableColumns = computed(() => { if (!currentFormId.value || !formConfigCache.value[currentFormId.value]) return [] const fields = formConfigCache.value[currentFormId.value].fields || [] // 关键:根据 isShowInTable === '1' 筛选需要显示的字段 const dynamicColumns = fields .filter((field: any) => field.isShowInTable === '1') .map((field: any) => { const col: any = { field: field.field, // 列字段名(对应数据key) title: String(field.title), // 列标题 minWidth: field.minWidth || 120 } // 2. 为特定类型添加格式化器 const formatter = getColumnFormatter(field) if (formatter) { col.formatter = formatter } return col }) // 3. 追加操作列(固定在右侧) dynamicColumns.push({ title: '操作', width: 150, fixed: 'right', slots: { default: 'operate' } // 使用插槽渲染自定义内容 }) return dynamicColumns })

2.3 模板中使用

<vxe-grid ref="tableRef" :data="tableData" :columns="tableColumns" <!-- 动态列配置 --> > <!-- 操作列插槽 --> <template #operate="{ row }"> <vxe-button type="text" @click="viewData(row)">详情</vxe-button> <vxe-button type="text" @click="removeData(row)">删除</vxe-button> </template> </vxe-grid>

三、动态查询条件实现

3.1 核心思路

筛选设置了 isSearchCondition === '1' 的字段,映射为查询组件配置。

3.2 实现代码

// 1. 查询字段配置 - 计算属性 const queryFields = computed(() => { if (!currentFormId.value || !formConfigCache.value[currentFormId.value]) return [] const fields = formConfigCache.value[currentFormId.value].fields || [] return fields .filter((field: any) => field.isSearchCondition === '1') // 关键筛选条件 .map((field: any) => { let type = field.type let multiple = undefined // 类型适配:checkbox/radio 转 select if (type === 'checkbox') { type = 'select' multiple = true } else if (type === 'radio') { type = 'select' multiple = false } return { field: field.field, // 字段名 label: field.title, // 字段标签 type, // 组件类型 options: field.options, // 选项数据 props: field.props, // 其他属性 multiple // 是否多选 } }) })

3.2 FormQuery 组件渲染

<FormQuery v-if="queryFields.length > 0" :fields="queryFields" v-model="queryParams" @query="handleQuery" @reset="handleReset" />

3.3 FormQuery 组件实现

<template> <div class="form-query"> <el-form :model="modelValue" :inline="true" @submit.prevent="handleQuery"> <el-form-item v-for="field in fields" :key="field.field" :label="field.label"> <!-- 文本输入 --> <el-input v-if="field.type === 'input' || !field.type" v-model="modelValue[field.field]" placeholder="请输入xxx" clearable /> <!-- 下拉选择(静态/远程选项) --> <el-select v-else-if="field.type === 'select' || field.type === 'checkbox' || field.type === 'radio'" v-model="modelValue[field.field]" :multiple="field.multiple" clearable > <el-option v-for="opt in field.options" :key="opt.value" :label="opt.label" :value="opt.value" /> </el-select> <!-- 日期选择 --> <el-date-picker v-else-if="field.type === 'date'" v-model="modelValue[field.field]" type="date" clearable /> <!-- 日期范围 --> <el-date-picker v-else-if="field.type === 'dateRange'" v-model="modelValue[field.field]" type="daterange" /> <!-- 时间选择 --> <el-time-picker v-else-if="field.type === 'time'" v-model="modelValue[field.field]" clearable /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">查询</el-button> <el-button @click="handleReset">重置</el-button> </el-form-item> </el-form> </div> </template>

四、Label 回显核心逻辑

4.1 表格列格式化器

// 根据字段类型返回对应的格式化函数 function getColumnFormatter(field: any) { const { type, options } = field switch (type) { // ==================== 静态选项 ==================== case 'select': case 'radio': case 'userSelect': if (!options || !options.length) return undefined return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return findLabel(options, cellValue) // 静态options匹配 } case 'checkbox': if (!options || !options.length) return undefined return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return formatCheckbox(options, cellValue) // 多选逗号拼接 } // ==================== 树形选项 ==================== case 'departmentPicker': if (!options || !options.length) return undefined return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return findLabelFromTree(options, cellValue) || '-' } // ==================== 日期时间 ==================== case 'datePicker': case 'timePicker': case 'dateTimePicker': return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return formatDate(cellValue) } // ==================== 文件上传 ==================== case 'upload': return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return formatUpload(cellValue) } // ==================== 布尔/数值 ==================== case 'switch': return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return cellValue ? '是' : '否' } case 'rate': return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return '⭐'.repeat(cellValue) || '-' } case 'inputNumber': case 'slider': return ({ cellValue }) => { if (isEmpty(cellValue)) return '-' return String(cellValue) } default: return undefined // input等直接显示原始值 } }

4.2 静态选项回显

// formDataFormatter.ts /** * 静态选项:根据 value 查找 label * @param options - [{label: '选项A', value: 1}, {label: '选项B', value: 2}] * @param value - 存储的值 (1 或 2) */ export function findLabel(options: any[], value: any): string { if (!options || !options.length) return String(value) const found = options.find((opt: any) => opt.value === value) return found ? found.label : String(value) // 未找到时返回原值 } /** * 多选回显:返回逗号拼接的标签字符串 */ export function formatCheckbox(options: any[], value: any): string { if (!value) return '-' const values = Array.isArray(value) ? value : [value] return values.map(v => findLabel(options, v)).join(', ') }

4.3 树形选项回显(部门选择器)

/** * 树形结构递归查找 label * @param options - [{label: '总部', value: '1', children: [{label: '研发部', value: '2'}]}] * @param value - 存储的值 */ export function findLabelFromTree(options: any[], value: any): string | null { if (!options || !value) return null for (const opt of options) { if (opt.value === value) return opt.label if (opt.children) { const found = findLabelFromTree(opt.children, value) if (found) return found } } return null }

4.4 远程选项回显

关键点:先加载远程选项,再渲染表格

// 1. 加载远程选项 async function loadRemoteOptions(fields: any[], formId: number | string) { if (!fields || !fields.length) return // 筛选需要远程加载的字段 const remoteFields = fields.filter((field: any) => { const optionType = field.props?._optionType // optionType === '1' 表示远程选项 return (optionType === '1' || optionType === 1) && field.effect?.fetch?.action }) if (!remoteFields.length) return await Promise.all( remoteFields.map(async (field: any) => { const fetch = field.effect.fetch try { // 发起远程请求 const res = await request({ url: fetch.action, method: (fetch.method || 'get').toLowerCase(), params: fetch.query, data: fetch.data }) // 解析选项数据(支持自定义parse函数) let list = res if (fetch.parse && typeof fetch.parse === 'string') { const fn = new Function('res', fetch.parse) list = fn(res) } if (!Array.isArray(list)) { console.warn(`[${field.title}] 远程选项解析结果不是数组`) return } // 关键:将远程数据挂载到 field.options field.options = list } catch (error) { console.warn(`加载字段 [${field.title}] 远程选项失败:`, error) } }) ) } // 2. 选择表单时调用 const selectForm = async (formId, formQueryCode) => { currentFormId.value = formId // 首次选择,需要获取配置并加载远程选项 if (!formConfigCache.value[formId]) { const res = await getDesignerFormConfig({ id: formId }) formConfigCache.value[formId] = { fields: res.data } // ⭐ 关键:加载远程选项后再设置表格列 await loadRemoteOptions(formConfigCache.value[formId].fields, formId) } await fetchTableData() }

五、可能的坑及解决方案

坑1:远程选项加载时机问题

问题后端返回的 options 为空或 optionType 未正确设置,导致回显失败
原因loadRemoteOptions 未被调用,或远程数据加载晚于表格渲染
解决- 确保 optionType === '1' 条件正确<br>- 使用 await loadRemoteOptions() 等待加载完成<br>- 添加 loading 状态防止闪烁
// ⭐ 推荐:在 selectForm 中 await 等待远程选项加载完成 const selectForm = async (formId, formQueryCode) => { // ... if (!formConfigCache.value[formId]) { const res = await getDesignerFormConfig({ id: formId }) formConfigCache.value[formId] = { fields: res.data } // 等待远程选项加载完成 await loadRemoteOptions(formConfigCache.value[formId].fields, formId) } // ... }

坑2:选项数据与表格列使用不同的对象引用

问题修改了 field.options,但 tableColumns 使用的仍是旧引用
原因computed 属性缓存了 fields 数组的初次解析结果
解决将 options 直接修改到原 field 对象上(引用传递),或强制刷新
// ✅ 正确做法:直接修改 field.options(引用传递) field.options = list // 触发响应式更新 // ❌ 错误做法:创建新数组 field.options = [...list] // 破坏引用关系

坑3:静态选项与远程选项混淆

问题某些字段同时配置了静态 options 和远程加载逻辑
解决优先使用远程数据覆盖静态 options
// 判断逻辑:optionType === '1' 优先 const optionType = field.props?._optionType if (optionType === '1') { // 使用远程数据 } else { // 使用静态 options }

坑4:多选组件值类型不匹配

问题checkbox 回显时,单元格显示空白或 undefined
原因数据库存储的是 JSON 字符串,但代码按数组处理
解决统一值类型处理
export function formatCheckbox(options: any[], value: any): string { if (!value) return '-' // 统一转为数组处理 let values if (typeof value === 'string') { try { values = JSON.parse(value) // 解析 JSON 字符串 } catch { values = [value] } } else { values = Array.isArray(value) ? value : [value] } return values.map(v => findLabel(options, v)).join(', ') }

坑5:部门选择器树形结构递归查找性能

问题树形层级深时,递归查找效率低
解决使用 Map 缓存或预先构建 value->label 映射
// 优化:预先构建 Map const labelMap = new Map() function buildMap(options: any[]) { for (const opt of options) { labelMap.set(opt.value, opt.label) if (opt.children) buildMap(opt.children) } } export function findLabelFromTreeFast(options: any[], value: any): string | null { buildMap(options) return labelMap.get(value) || null }

坑6:查询参数空值过滤

问题空字符串或空数组导致后端查询报错或返回异常
解决提交前过滤空值
const fetchTableData = async () => { // 过滤空值 const filteredParams: Record<string, any> = {} Object.entries(queryParams.value).forEach(([key, value]) => { if ( value !== '' && value !== null && value !== undefined && !(Array.isArray(value) && value.length === 0) ) { filteredParams[key] = value } }) const params = { pageNum: pagination.value.pageNum, pageSize: pagination.value.pageSize, formData: filteredParams // 只传有值的参数 } const res = await getFormDataByFormId(curQueryDataFormId.value, params) // ... }

坑7:动态列与 vxe-grid 高度计算问题

问题表格出现滚动条异常或高度计算错误
原因动态列切换时表格未重新计算尺寸
解决列配置变化后调用 recalculate()
// 切换表单后手动触发重新计算 const selectForm = async (formId, formQueryCode) => { // ... await fetchTableData() // 等待 DOM 更新后重新计算 nextTick(() => { tableRef.value?.recalculate() }) } // 同时监听窗口 resize window.addEventListener('resize', () => { tableRef.value?.recalculate() })

坑8:FormQuery 组件未正确处理远程选项

问题切换表单后,查询组件的选项未更新
原因queryFields computed 未响应 field.options 的变化
解决确保 options 引用变化时触发 computed 重新计算
// queryFields 中直接使用 field.options(引用传递) const queryFields = computed(() => { const fields = formConfigCache.value[currentFormId.value]?.fields || [] return fields .filter((field: any) => field.isSearchCondition === '1') .map((field: any) => ({ field: field.field, label: field.title, type: field.type, options: field.options, // 直接引用,远程加载后会更新 multiple: field.multiple })) })

六、完整数据流程图

┌──────────────────────────────────────────────────────────────────────────┐
│                              用户选择表单                                  │
└──────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────┐
│  1. getDesignerFormConfig() 获取表单配置                                  │
│     - 包含 fields[] 数组,每个 field 有 type、options、isShowInTable 等   │
└──────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────┐
│  2. loadRemoteOptions() 加载远程选项                                     │
│     - 筛选 optionType === '1' 的字段                                      │
│     - 请求远程 API 获取选项数据                                           │
│     - 将结果赋值给 field.options                                          │
└──────────────────────────────────────────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    ▼                               ▼
        ┌───────────────────┐           ┌───────────────────┐
        │   queryFields     │           │   tableColumns    │
        │  (计算属性)        │           │  (计算属性)        │
        │                   │           │                   │
        │ 筛选 isSearchCon- │           │ 筛选 isShowInTable │
        │ dition === '1'   │           │ === '1'           │
        │                   │           │                   │
        │ 输出:             │           │ 输出:              │
        │ - field           │           │ - field           │
        │ - label           │           │ - title           │
        │ - type            │           │ - formatter       │
        │ - options ←───────┼───────────┼─ options ←────────┘
        │ - multiple        │           │
        └───────────────────┘           └───────────────────┘
                    │                               │
                    ▼                               ▼
        ┌───────────────────┐           ┌───────────────────┐
        │  FormQuery 组件   │           │   vxe-grid 表格   │
        │                   │           │                   │
        │  渲染动态表单项   │           │  渲染动态列        │
        │  - el-input       │           │  - formatter 回显  │
        │  - el-select      │           │  - 静态/远程选项   │
        │  - el-date-picker │           │  - 操作列插槽      │
        └───────────────────┘           └───────────────────┘
                    │                               │
                    ▼                               ▼
        ┌───────────────────────────────────────────────────┐
        │  用户点击查询 → fetchTableData()                  │
        │  - 过滤空值后的 queryParams                        │
        │  - 携带分页参数请求后端                            │
        └───────────────────────────────────────────────────┘

七、字段配置结构参考

// 表单字段配置(后端返回的 fields 数组中每个元素) interface FieldConfig { field: string // 字段名(英文标识) title: string // 字段标题(中文显示) type: string // 字段类型(input/select/checkbox/datePicker等) options?: Array<{ // 静态选项(仅 select/checkbox/radio 等需要) label: string value: any }> props?: { _optionType?: '0' | '1' | 0 | 1 // 0=静态 1=远程 // ... 其他组件属性 } effect?: { fetch?: { action: string // 远程API地址 method?: string // 请求方法 query?: object // URL参数 data?: object // body参数 parse?: string // JS表达式,如 'res.data.list' } } isShowInTable?: '0' | '1' // 是否在表格中显示 isSearchCondition?: '0' | '1' // 是否作为查询条件 minWidth?: number // 表格列最小宽度 }

八、关键要点总结

序号要点说明
1动态列tableColumns computed 根据 isShowInTable === '1' 筛选字段
2动态查询条件queryFields computed 根据 isSearchCondition === '1' 筛选字段
3静态选项回显findLabel() 从 options 数组匹配 value → label
4远程选项回显先 loadRemoteOptions() 加载数据到 field.options,再渲染表格
5多选回显formatCheckbox() 将值数组转为逗号拼接的标签字符串
6树形回显findLabelFromTree() 递归查找树形结构的 label
7时序保证await loadRemoteOptions() 确保选项加载后再渲染
8空值过滤查询前过滤 ''、null、空数组
9引用传递修改 field.options 时保持原引用以触发响应式更新
10表格重算动态列变化后调用 tableRef.value?.recalculate()

评论区

0 条评论
评论输入
>
* 访问被拒绝:请登录后发表评论 *