AvueAvue
首页
  • 开发指南
  • Skill开发
  • 在线测试工具
  • Form组件
  • Crud组件
  • Default组件
  • Data组件
  • Component组件
产品
工作台
授权
联系
2.x文档
个人支付接口
首页
  • 开发指南
  • Skill开发
  • 在线测试工具
  • Form组件
  • Crud组件
  • Default组件
  • Data组件
  • Component组件
产品
工作台
授权
联系
2.x文档
个人支付接口
  • Form属性文档
  • Object对象用法
  • Input 输入框
  • InputOtp 一次性密码输入
  • Number 数字输入框
  • Select选择框
  • Cascader级联选择器
  • Checkbox多选框
  • Radio单选框
  • Date日期
  • Time 时间
  • Switch开关
  • Upload附件上传
  • Title标题
  • Array数组框
  • Dynamic子表单
  • Tree树型选择框
  • Icon图标选择器
  • Table表格选择器
  • Map坐标选择器
  • Color颜色选择器
  • Input Cron Cron表达式编辑器
  • InputTag 输标签输入框
  • Mention 提及框
  • Rate评价
  • Slider滑块
  • 表单布局
  • 表单验证
  • 表单默认值
  • 表单操作按钮
  • 表单自定义
  • 表单数据字典
  • 表单多级联动
  • 表单数据格式
  • 表单组件事件
  • 表单高级用法

Dynamic子表单

适用于订单明细、联系人、费用项目等一对多数据。父表单字段保存一个对象数组,children.column 定义每一行的字段;新增、编辑、删除和排序都会反映在该数组中。

复制示例时使用的结果展示组件和固定数据,见 示例辅助文件。

提示

子表单适用于较简单的编辑场景。需要独立分页、复杂权限或服务端查询时,可以通过 Form 自定义 嵌入 Crud 行编辑。

表格用法

内部组件为crud组件,大部分属性参考Crud文档

效果预览可直接操作下方示例

配置 type: 'dynamic' 和 children.column。点击表头加号新增一行,编辑名称与数量,再将鼠标移到序号处删除;结果区始终展示当前明细。rowAdd(done) 与 rowDel(row, done) 中调用 done 才会执行操作。

正在加载示例…
<template>
  <avue-form :option="option" v-model="form" />
  <form-demo-result :value="rows" hint="表头加号用于新增;鼠标移到行序号处可删除。结果只展示业务字段。" />
</template>

<script setup>
import { computed, ref } from 'vue';
import FormDemoResult from '../_shared/FormDemoResult.vue';

const form = ref({
  items: [
    { id: 'item-1', name: '设备调试', quantity: 1, category: 'service' },
    { id: 'item-2', name: '连接线', quantity: 2, category: 'material' },
  ],
});
let nextId = 3;
const rows = computed(() => (form.value.items || []).map(({ id, name, quantity, category }) => ({ id, name, quantity, category })));
const option = {
  menuBtn: false,
  labelWidth: 0,
  column: [{
    prop: 'items', type: 'dynamic', span: 24,
    children: {
      rowKey: 'id',
      rowAdd: (done) => done({ id: 'item-' + nextId++, name: '', quantity: 1, category: 'service' }),
      rowDel: (row, done) => done(),
      column: [
        { label: '项目名称', prop: 'name', minWidth: 160 },
        { label: '数量', prop: 'quantity', type: 'number', min: 1, precision: 0, minWidth: 130 },
        { label: '分类', prop: 'category', type: 'select', minWidth: 140, dicData: [
          { label: '服务', value: 'service' },
          { label: '材料', value: 'material' },
        ] },
      ],
    },
  }],
};
</script>

表单用法

内部组件为form组件,大部分属性参考Form文档

效果预览可直接操作下方示例

配置 children.type: 'form' 即可转为纵向表单,children.index: false 隐藏序号;适合每条记录字段较多的场景。仍通过同一个数组保存数据。

正在加载示例…
<template>
  <avue-form :option="option"
             v-model="form">
    <template #input="{ row }">
      <el-tag>序号:{{ row.$index }}-数据:{{ row.input }}</el-tag>
    </template>
  </avue-form>
</template>

<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus'; // 导入 ElMessage

const form = ref({
  dynamic: [
    { input: 1, select: 1 },
    { input: 2, select: 2 }
  ]
});

const option = ref({
  column: [
    {
      label: '子表单',
      prop: 'dynamic',
      type: 'dynamic',
      span: 24,
      children: {
        index: false,
        align: 'center',
        type: 'form',
        headerAlign: 'center',
        rowAdd: (done) => {
          ElMessage.success('新增回调'); // 使用 ElMessage
          done({ input: '默认值' });
        },
        rowDel: (row, done) => {
          ElMessage.success('删除回调' + JSON.stringify(row)); // 使用 ElMessage
          done();
        },
        column: [
          {
            width: 200,
            label: '输入框',
            prop: 'input'
          },
          {
            label: '选择框',
            prop: 'select',
            type: 'select',
            dicData: [
              { label: '测试1', value: 1 },
              { label: '测试2', value: 2 }
            ]
          }
        ]
      }
    }
  ]
});
</script>

父子联动

效果预览可直接操作下方示例

切换父表单的大区后,子表的站点选项随之更新,并清空原大区下的站点值。下面使用本地字典保留已有明细与备注,无需重建整个父表单。

正在加载示例…
<template>
  <avue-form v-model="form" :option="option" />
  <form-demo-result :value="result" hint="切换大区会清空各行的站点,备注和行 ID 保持不变。" />
</template>

<script setup>
import { computed, ref, watch } from 'vue';
import FormDemoResult from '../_shared/FormDemoResult.vue';
import { getRegionChildren, regionOptions } from '../_shared/useDictionaryMock';

const form = ref({
  region: 'north',
  items: [{ id: 'site-1', site: 'beijing', remark: '保留这条备注' }],
});
let nextId = 2;
watch(() => form.value.region, () => {
  (form.value.items || []).forEach((row) => { row.site = ''; });
});
const result = computed(() => ({
  region: form.value.region,
  items: (form.value.items || []).map(({ id, site, remark }) => ({ id, site, remark })),
}));
const option = computed(() => ({
  menuBtn: false,
  labelWidth: 90,
  column: [
    { label: '大区', prop: 'region', type: 'select', span: 24, clearable: false,
      props: { label: 'name', value: 'code' }, dicData: regionOptions },
    { label: '服务站点', prop: 'items', type: 'dynamic', span: 24,
      children: {
        rowKey: 'id',
        rowAdd: (done) => done({ id: 'site-' + nextId++, site: '', remark: '' }),
        column: [
          { label: '站点', prop: 'site', type: 'select', minWidth: 160,
            props: { label: 'name', value: 'code' }, dicData: getRegionChildren(form.value.region) },
          { label: '备注', prop: 'remark', minWidth: 200 },
        ],
      } },
  ],
}));
</script>

拖拽排序与继续编辑

在表格型子表中配置 children.rowSort: true 和稳定唯一的 children.rowKey。按住行序号区域,稍作停留后拖动,绑定数组会自动按新顺序更新。3.9.5 修复了拖拽后显示顺序与数组不一致的问题,排序后可直接继续编辑。

效果预览可直接操作下方示例

将“现场支持”拖到第一行,观察下方 ID 顺序和事件记录,再修改该行的名称或数量。无需手动 splice 或调用 refreshTable,事件中只记录排序结果即可。

正在加载示例…
<template>
  <el-button @click="reset">恢复初始顺序</el-button>
  <avue-form v-model="form" :option="option" />
  <form-demo-result title="绑定数组与排序事件" :value="result" hint="按住行序号稍作停留再拖动,之后继续编辑任一行,核对 ID、名称和数量仍属于同一条记录。" />
</template>

<script setup>
import { computed, ref } from 'vue';
import FormDemoResult from '../_shared/FormDemoResult.vue';

const createRows = () => [
  { id: 'service-a', name: '需求沟通', quantity: 1 },
  { id: 'service-b', name: '设备调试', quantity: 2 },
  { id: 'service-c', name: '现场支持', quantity: 3 },
];
const form = ref({ items: createRows() });
const lastEvent = ref(null);
const result = computed(() => ({
  order: (form.value.items || []).map((row) => row.id),
  items: (form.value.items || []).map(({ id, name, quantity }) => ({ id, name, quantity })),
  lastEvent: lastEvent.value,
}));
const option = {
  menuBtn: false,
  labelWidth: 0,
  column: [{
    prop: 'items', type: 'dynamic', span: 24,
    children: {
      rowKey: 'id', rowSort: true, addBtn: false, delBtn: false,
      sortableChange(oldIndex, newIndex, row, list) {
        // The component updates the bound array. Do not move rows again here.
        lastEvent.value = { oldIndex, newIndex, movedId: row.id, order: list.map((item) => item.id) };
      },
      column: [
        { label: '项目名称', prop: 'name', minWidth: 180 },
        { label: '数量', prop: 'quantity', type: 'number', min: 1, precision: 0, minWidth: 130 },
      ],
    },
  }],
};
function reset() {
  form.value.items = createRows();
  lastEvent.value = null;
}
</script>

children.sortableChange(oldIndex, newIndex, row, list) 接收以下参数:

参数含义
oldIndex拖动前的数组位置,从 0 开始
newIndex拖动后的数组位置,从 0 开始
row本次移动的行对象
list排序后的数组,可用于读取新的 ID 顺序或保存到接口

绑定数据在事件后的更新周期完成同步,需要立即读取新顺序时使用事件的 list。不要在回调中再次对同一个数组执行相同的移动,否则可能重复排序。rowKey 应使用业务 ID,不要使用当前位置作为身份标识。

拖拽排序适用于显示行与数组顺序一致的普通表格。启用了列排序、过滤或展开导致显示行与源数据不一致时,本次拖动会被忽略。表单型布局 children.type: 'form' 不支持表格行拖拽。项目需要先加载 SortableJS,安装方式见 CRUD 拖拽排序。

最后更新:
贡献者: smallwei
Prev
Array数组框
Next
Tree树型选择框