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滑块
  • 表单布局
  • 表单验证
  • 表单默认值
  • 表单操作按钮
  • 表单自定义
  • 表单数据字典
  • 表单多级联动
  • 表单数据格式
  • 表单组件事件
  • 表单高级用法

表单多级联动

多个独立字段之间存在依赖时,在上级字段上配置 cascader: ['下级prop'],并在下级的字典请求中使用 {{key}}。例如大区 → 站点 → 服务点,每次请求都取紧邻的上级字段值。

这与单个字段的 Cascader 级联选择器 不同:多级联动分别保存多个字段,Cascader 默认将整条路径保存在一个数组中。

请求位置配置示例取值来源
站点 URL'/api/sites/{{key}}'大区字段的值
服务点 URL'/api/locations/{{key}}'站点字段的值
站点查询参数{ region: '{{key}}', scene: '{{scene}}' }上级大区值,以及当前表单的 scene 字段

提示

{{key}} 的含义由使用场景决定:本页级联字段取上级值;远程搜索字段取搜索关键词;CRUD 普通逐行字典可取当前列的值。不要把这几种场景混用。字典编码与表单值的类型应保持一致。

下面的示例均使用固定数据和局部模拟接口,可直接操作并查看实际请求。需要复制辅助文件时,见 示例辅助文件。

Select多级联动

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

初始回显“北方大区 / 北京站 / 朝阳服务点”。将大区切换为“南方大区”,再选择站点和服务点,观察每次请求里的上级编码;清空上级后,下级值与选项会一起清空。

正在加载示例…
<template>
  <avue-form v-model="form" :option="option" />
  <form-demo-result :value="result" title="三级联动与实际请求" />
</template>

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

const form = ref({ region: 'north', site: 'beijing', location: 'chaoyang' });
const { baseUrl, requests } = useDictionaryMock(({ path }) => getRegionChildren(path.split('/').at(-1)));
const result = computed(() => ({
  region: form.value.region, site: form.value.site, location: form.value.location, requests: requests.value,
}));
const option = {
  menuBtn: false,
  column: [
    { label: '大区', prop: 'region', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicData: regionOptions, cascader: ['site'] },
    { label: '站点', prop: 'site', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicUrl: baseUrl + '/sites/{{key}}', cascader: ['location'] },
    { label: '服务点', prop: 'location', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicUrl: baseUrl + '/locations/{{key}}' },
  ],
};
</script>

上级与下级应按依赖顺序配置,避免相互引用形成循环。不要写 dicUrl: '/api/sites/' + form.region 作为一次性的初始快照,否则上级改变后仍可能请求旧编码。

InputTable+Select联动

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

点击“大区”表格选择框选择记录,站点下拉框使用该记录的 code 请求下级字典。表格的 onLoad 同时处理初始值回显和列表查询,字段最终保存编码而非整条记录。

正在加载示例…
<template>
  <avue-form v-model="form" :option="option" />
  <form-demo-result :value="result" title="表格选择值与下级请求" />
</template>

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

const form = ref({ region: 'north', site: 'beijing' });
const { baseUrl, requests } = useDictionaryMock(({ path }) => getRegionChildren(path.split('/').at(-1)));
const result = computed(() => ({ region: form.value.region, site: form.value.site, requests: requests.value }));
const option = {
  menuBtn: false,
  column: [
    {
      label: '大区', prop: 'region', type: 'table', span: 24, cascader: ['site'],
      props: { label: 'name', value: 'code' },
      formatter: (row) => row?.name || '',
      children: {
        border: true,
        rowKey: 'code',
        column: [
          { label: '编码', prop: 'code' },
          { label: '大区', prop: 'name', search: true, searchSpan: 24 },
        ],
      },
      onLoad({ value, page, data }, callback) {
        if (value) {
          callback(regionOptions.find((item) => item.code === value) || {});
          return;
        }
        const rows = regionOptions.filter((item) => item.name.includes(data?.name || ''));
        const current = page?.currentPage || 1;
        const size = page?.pageSize || 10;
        callback({ total: rows.length, data: rows.slice((current - 1) * size, current * size) });
      },
    },
    { label: '站点', prop: 'site', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicUrl: baseUrl + '/sites/{{key}}' },
  ],
};
</script>

Radio+Select联动

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

点击不同大区单选项,观察站点选项和请求路径随上级变化。Radio 与 Select 使用相同的 cascader 配置方式。

正在加载示例…
<template>
  <avue-form v-model="form" :option="option" />
  <form-demo-result :value="result" title="当前值与上级参数" />
</template>

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

const form = ref({ region: 'north', site: 'beijing' });
const { baseUrl, requests } = useDictionaryMock(({ path }) => getRegionChildren(path.split('/').at(-1)));
const result = computed(() => ({ region: form.value.region, site: form.value.site, requests: requests.value }));
const option = {
  menuBtn: false,
  column: [
    { label: '大区', prop: 'region', type: 'radio', span: 24,
      props: { label: 'name', value: 'code' }, dicData: regionOptions, cascader: ['site'] },
    { label: '站点', prop: 'site', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicUrl: baseUrl + '/sites/{{key}}' },
  ],
};
</script>

Input+Select联动

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

输入 north 或 south 后,站点加载对应字典;未知编码返回空数组。实际项目中,频繁输入触发的请求可通过业务侧防抖或改用下拉框减少请求次数。

正在加载示例…
<template>
  <el-space wrap>
    <el-tag>north → 北方大区</el-tag>
    <el-tag type="success">south → 南方大区</el-tag>
  </el-space>
  <avue-form v-model="form" :option="option" />
  <form-demo-result :value="result" hint="未知编码返回空字典;清空编码后不再请求下级。" />
</template>

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

const form = ref({ region: 'north', site: 'beijing' });
const { baseUrl, requests } = useDictionaryMock(({ path }) => getRegionChildren(path.split('/').at(-1)));
const result = computed(() => ({ region: form.value.region, site: form.value.site, requests: requests.value }));
const option = {
  menuBtn: false,
  column: [
    { label: '大区编码', prop: 'region', span: 24, placeholder: '输入 north 或 south', cascader: ['site'] },
    { label: '站点', prop: 'site', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicUrl: baseUrl + '/sites/{{key}}' },
  ],
};
</script>

传递其他参数

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

先修改“场景”,再切换大区;站点的 dicQuery 同时收到 {{key}} 对应的大区值和 {{scene}} 对应的表单值。结果区展示实际 GET 参数,可确认不是初始化时固定拼接的旧值。

正在加载示例…
<template>
  <avue-form v-model="form" :option="option" />
  <form-demo-result :value="result" title="当前表单与请求参数" hint="先修改场景,再切换大区触发请求,核对 query.scene 是否为新值。" />
</template>

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

const form = ref({ scene: 'delivery', region: 'north', site: 'beijing' });
const { baseUrl, requests } = useDictionaryMock(({ query }) => {
  const sites = getRegionChildren(query.region);
  return query.scene === 'pickup' ? sites.slice(0, 1) : sites;
});
const result = computed(() => ({
  scene: form.value.scene, region: form.value.region, site: form.value.site, requests: requests.value,
}));
const option = {
  menuBtn: false,
  column: [
    { label: '场景', prop: 'scene', type: 'radio', span: 24, dicData: [
      { label: '配送(所有站点)', value: 'delivery' },
      { label: '自提(首个站点)', value: 'pickup' },
    ] },
    { label: '大区', prop: 'region', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicData: regionOptions, cascader: ['site'] },
    { label: '站点', prop: 'site', type: 'select', span: 24,
      props: { label: 'name', value: 'code' }, dicUrl: baseUrl + '/sites',
      dicQuery: { region: '{{key}}', scene: '{{scene}}' } },
  ],
};
</script>

额外模板参数会在级联请求发起时读取,修改一个普通附加字段不等同于修改级联上级,不会独立触发级联加载。这里通过再次选择上级发起请求。模板字段应写完整占位符,例如 scene: '{{scene}}';不要在查询参数中混合前缀字符串,例如 scene: 'prefix-{{scene}}'。

最后更新:
贡献者: smallwei
Prev
表单数据字典
Next
表单数据格式