欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 明星 > 【通用级联选择器回显与提交处理工具设计与实现】

【通用级联选择器回显与提交处理工具设计与实现】

2025/4/3 4:16:00 来源:https://blog.csdn.net/weixin_37342647/article/details/146882080  浏览:    关键词:【通用级联选择器回显与提交处理工具设计与实现】

通用级联选择器回显与提交处理工具设计与实现

1. 问题背景

在我们的企业级SaaS应用开发过程中,特别是在申报系统项目中,遇到了一个反复出现的问题。这个项目使用Vue 3和Element Plus构建,包含多个复杂表单,其中大量使用级联选择器来选择行业分类、区域等多层级数据。

真实场景是这样的:我们的一个表单页面(declare-form.vue)需要让用户选择"申报领域",这个领域是一个三层结构:

- 绿碳产业(id: 101110)- 清源产业(id: 201110)- 太能发电(id: 29)- 风能发电(id: 30111)- 节能环保产业(id: 311110)- 高效节能技术(id: 311111)

后端接口返回的详情数据只包含一个fieldId: "29",而前端级联选择器需要知道完整路径才能正确回显。

一位同事实现的临时解决方案是:

// 硬编码的映射关系
const fieldIdMap = {'29': [100, 200, 29],'30': [100, 200, 30],'31': [100, 300, 31],// ... 数十个映射
};// 使用映射设置值
formData.fieldId = fieldIdMap[detail.fieldId] || detail.fieldId;

这个方案有严重问题:

  1. 每增加一个选项都需要手动更新映射
  2. 领域结构变化时需要全面修改
  3. 无法复用到其他级联选择器

我们多个项目组都在用类似的方式处理这个问题,重复编写相似的代码。这促使我们开发一个通用解决方案。

2. 解决方案设计过程

2.1 初始探索

最初尝试的方案是直接修改Element Plus的级联选择器源码,增加一个属性支持单ID回显。但这会带来维护和升级问题。

接着我们考虑了以下方案:

  1. 修改组件库:侵入性太强,放弃
  2. 全局mixin:污染全局,不推荐
  3. 高阶组件封装:增加额外的复杂度
  4. 独立工具函数:最佳选择

2.2 方案迭代

第一版实现较为简单:

// v1: 只支持固定的字段名
function findPath(tree, targetId) {for (const node of tree) {if (node.id === targetId) return [node.id];if (node.children) {const path = findPath(node.children, targetId);if (path) return [node.id, ...path];}}return null;
}

在实际使用中,我们发现不同项目的级联数据结构各不相同:

  • 有的用id作为标识,有的用value
  • 有的需要返回完整路径,有的只需最终ID
  • 需要处理stringnumber类型混用的情况

经过三轮迭代,最终形成了现在的通用工具。

3. 实现细节与技术挑战

3.1 类型系统设计

TypeScript类型设计是个挑战。我们需要让工具既有强类型检查,又要足够灵活适应不同数据结构:

// 早期版本存在问题
function findNodePath<T>(nodes: T[],targetId: any
): T[] | null {// 实现
}// 改进后的泛型约束
function findNodePath<T extends { [key: string]: any, children?: T[] }>(nodes: T[],targetId: string | number,idKey: string = 'id'
): T[] | null {// 实现
}// 最终版本
function findNodePath<T extends CascaderNode>(nodes: T[],targetId: string | number,config: CascaderConfig,path: T[] = []
): T[] | null {// 实现
}

3.2 边缘情况处理

真实项目中的级联数据结构常有各种特殊情况:

  1. 空节点处理:有些节点的children可能是[]而非undefined
  2. ID类型不一致:后端返回字符串"29",但前端数据是数字29
  3. 循环引用:某些错误数据可能导致循环引用

代码中特别处理了这些情况:

// 类型安全的比较
if (String(node[idKey]) === String(targetId)) {return [...path, node];
}// 空检查
if (!nodes?.length) return null;// 防止无限递归
// (通过路径长度限制或循环检测可以实现,但当前场景不必要)

3.3 性能优化

实际测试中,我们在一个有3000多节点的五层级联数据上运行工具函数,得到以下性能数据:

数据规模查找耗时(平均)内存消耗
100节点<1ms微小
1000节点~5ms~200KB
3000节点~15ms~600KB

这个性能完全满足我们的需求,因为级联数据通常不会特别庞大,且查找是一次性操作。

4. 实际应用案例

4.1 企业申报系统

这个工具最初用于我们的企业申报系统,用户需要选择所属行业和申报领域,这两个都是三层级联结构。

使用前的代码:

async function getDetail(declareId) {const res = await Api.getDeclareDetail(declareId);// ...其他字段处理...// 手动查找和构建路径,每个级联选择器都需要重复类似逻辑let fieldIdPath = null;for (const level1 of fieldOptions.value) {if (level1.id === res.fieldId) {fieldIdPath = [level1.id];break;}if (level1.children) {for (const level2 of level1.children) {if (level2.id === res.fieldId) {fieldIdPath = [level1.id, level2.id];break;}if (level2.children) {for (const level3 of level2.children) {if (level3.id === res.fieldId) {fieldIdPath = [level1.id, level2.id, level3.id];break;}}}}}}formData.value.fieldId = fieldIdPath || res.fieldId;
}

使用工具后的代码:

async function getDetail(declareId) {const res = await Api.getDeclareDetail(declareId);// ...其他字段处理...// 使用工具函数,一行代码解决formData.value.fieldId = prepareCascaderValue(fieldOptions.value,res.fieldId,{ value: 'id', emitPath: true });
}

4.2 多项目复用

我们将这个工具推广到团队的其他项目中,收到了积极反馈:

  1. 行政审批系统:用于区域选择、部门选择,减少约200行重复代码
  2. 智慧园区系统:用于设备分类、位置选择,简化表单处理
  3. 供应链管理系统:用于产品分类,提高开发效率

一位前端开发者反馈:“这个工具解决了我们项目中最头疼的问题之一,以前每次做表单编辑都要写一堆递归查找代码,现在一行就搞定。”

5. 与其他解决方案对比

我们调研了几种常见的解决方案:

解决方案优点缺点
手动映射表简单直接难以维护,不可扩展
修改组件库彻底解决问题升级困难,侵入性强
高阶组件封装复杂度增加额外组件,使用繁琐
本工具通用、独立、易用需要传入完整数据源

市场上也有类似的工具库,如el-cascader-helper,但它们通常依赖特定组件库,而我们的工具完全独立,可用于任何级联数据结构。

6. 实际使用反馈和改进

在实际使用过程中,我们收集了一些问题和改进点:

  1. 懒加载支持不足:当使用懒加载时,无法一次性获取完整数据,导致路径查找失败
  2. 类型错误:在某些复杂项目中,TypeScript类型推断不够精确
  3. 性能问题:在极大的数据集上,递归查找效率降低

针对这些问题,我们进行了改进:

// 改进版本支持懒加载场景
export async function prepareCascaderValueAsync<T extends CascaderNode>(options: T[],targetId: string | number,config: CascaderConfig & { loadChildren?: (node: T) => Promise<T[]> }
): Promise<string | number | (string | number)[] | undefined> {// 实现逻辑...
}

这个异步版本仍在开发中,计划在下一版本发布。

7. 工程化与最佳实践

7.1 单元测试

我们为工具函数编写了完整的单元测试,包括:

describe('prepareCascaderValue', () => {test('应正确查找一级节点', () => {const options = [{ id: '1', name: 'Node 1' },{ id: '2', name: 'Node 2' }];expect(prepareCascaderValue(options, '1', { value: 'id', emitPath: false })).toBe('1');expect(prepareCascaderValue(options, '1', { value: 'id', emitPath: true })).toEqual(['1']);});test('应正确查找多级节点', () => {const options = [{ id: '1', name: 'Node 1',children: [{ id: '1-1', name: 'Node 1-1' },{ id: '1-2', name: 'Node 1-2',children: [{ id: '1-2-1', name: 'Node 1-2-1' }]}]}];expect(prepareCascaderValue(options, '1-2-1', { value: 'id', emitPath: true })).toEqual(['1', '1-2', '1-2-1']);});// 更多测试用例...
});

7.2 文档和示例

我们创建了详细的文档和示例,帮助团队成员快速上手:

  • Markdown格式的使用指南
  • 在各种场景下的代码示例
  • 常见问题的解决方案

8. 总结与经验分享

开发这个工具的过程中,我们得到了一些有价值的经验:

  1. 小而专注的工具更有用:解决一个具体问题比创建大而全的库更有价值
  2. 类型设计至关重要:良好的TypeScript类型定义使工具更易用
  3. 实战案例促进改进:在实际项目中使用发现的问题促使我们不断完善

这个工具虽然代码量不大,但通过解决团队中反复出现的问题,节省了大量开发时间。它强调了我们团队的工程价值观:通过抽象共性问题,创建可复用解决方案,提高整体开发效率。

未来,我们计划将这个工具发布为开源库,让更多开发者受益,并吸收社区的反馈和贡献,使其更加完善。


从一个实际项目中的具体问题出发,通过分析、设计和优化,我们创造了一个通用的解决方案。这个过程体现了软件工程中"发现共性,抽象复用"的理念,也展示了如何将日常工作中的痛点转化为有价值的工具。

通过这个小工具,我们不仅解决了级联选择器的技术问题,也为团队建立了一个良好的工具开发和共享机制,提升了整体的开发效率和代码质量。

源码

/*** 级联选择器辅助工具* 用于解决级联选择器数据回显问题*//*** 级联选择器节点接口*/
export interface CascaderNode {// 节点ID[idKey: string]: any;// 子节点children?: CascaderNode[];
}/*** 级联选择器配置接口*/
export interface CascaderConfig {// ID字段名value: string;// 是否返回完整路径emitPath: boolean;// 是否可选任意级别checkStrictly?: boolean;
}/*** 查找节点在树形结构中的完整路径* @param nodes 级联数据源* @param targetId 目标节点ID* @param config 级联选择器配置* @param path 当前路径* @returns 找到的节点路径或null*/
function findNodePath<T extends CascaderNode>(nodes: T[],targetId: string | number,config: CascaderConfig,path: T[] = []
): T[] | null {if (!nodes?.length) return null;const idKey = config.value;for (const node of nodes) {// 检查当前节点是否匹配if (String(node[idKey]) === String(targetId)) {return [...path, node];}// 递归检查子节点if (node.children?.length) {const foundPath = findNodePath<T>(node.children as T[], targetId, config, [...path, node]);if (foundPath) return foundPath;}}return null;
}/*** 准备级联选择器回显数据* 解决级联选择器无法正确回显嵌套数据的问题** @param options 级联选择器数据源* @param targetId 后端返回的目标ID* @param config 级联选择器配置* @returns 适合级联选择器回显的值*/
export function prepareCascaderValue<T extends CascaderNode>(options: T[],targetId: string | number | undefined,config: CascaderConfig
): string | number | (string | number)[] | undefined {if (!targetId || !options?.length) return undefined;// 查找节点路径const nodePath = findNodePath<T>(options, targetId, config);if (!nodePath) {console.warn(`未找到ID为${targetId}的节点路径`);return targetId; // 作为兜底返回原始值}const idKey = config.value;// 根据emitPath配置决定返回形式if (config.emitPath) {// 返回完整ID路径return nodePath.map((node) => node[idKey]);} else {// 只返回最后一个节点的IDreturn targetId;}
}/*** 递归查找并返回节点* @param nodes 级联数据源* @param targetId 目标节点ID* @param config 级联选择器配置* @returns 找到的节点或undefined*/
export function findCascaderNode<T extends CascaderNode>(nodes: T[],targetId: string | number,config: CascaderConfig
): T | undefined {if (!nodes?.length) return undefined;const idKey = config.value;for (const node of nodes) {// 检查当前节点if (String(node[idKey]) === String(targetId)) {return node;}// 检查子节点if (node.children?.length) {const found = findCascaderNode<T>(node.children as T[], targetId, config);if (found) return found;}}return undefined;
}/*** 准备级联选择器值用于表单提交* 处理可能是数组的级联选择器值,确保返回单一ID用于后端提交** @param cascaderValue 级联选择器的当前值* @returns 适合提交到后端的单一ID值*/
export function prepareCascaderValueForSubmit(cascaderValue: string | number | (string | number)[] | undefined
): string | number | undefined {if (!cascaderValue) return undefined;// 如果是数组,取最后一个值(叶子节点ID)if (Array.isArray(cascaderValue)) {return cascaderValue[cascaderValue.length - 1];}// 否则直接返回原值return cascaderValue;
}

MD-EXAMPLE

# 级联选择器回显辅助工具这个工具用于解决级联选择器中,从后端获取的 ID 无法正确回显为级联路径的问题。## 问题场景在使用 Element Plus 的级联选择器(el-cascader)时,常见以下场景:1. 后端只返回了叶子节点 ID,如 `fieldId: "29"`
2. 但级联选择器的数据是多层嵌套结构,ID 为 29 的节点可能在多层级下
3. 简单赋值后,级联选择器无法正确回显选中的值
4. 提交表单时,需要将级联选择器可能的数组值转换回单一 ID## 解决方案`cascader-helper.ts` 工具函数可以解决上述问题,自动查找并构建级联选择器需要的数据结构。## 使用方法### 1. 复制工具文件将 `cascader-helper.ts` 文件复制到你的项目中。### 2. 在组件中使用 - 数据回显```vue
<template><el-cascader v-model="selectedValue" :options="options" :props="cascaderProps" />
</template><script setup lang="ts">
import { ref, onMounted } from 'vue';
import { prepareCascaderValue } from './path/to/cascader-helper';// 级联选择器配置
const cascaderProps = ref({label: 'name', // 显示的标签字段value: 'id', // 值字段emitPath: false, // 是否返回完整路径checkStrictly: true // 是否可选任意级别
});// 级联选择器数据源
const options = ref([]);
// 选中的值
const selectedValue = ref();// 从后端获取表单详情
async function getDetail(id) {const res = await api.getDetail(id);// 处理级联选择器回显selectedValue.value = prepareCascaderValue(options.value, // 级联选择器数据源res.someFieldId, // 后端返回的ID{// 级联选择器配置value: 'id',emitPath: cascaderProps.value.emitPath});
}// 初始化
onMounted(async () => {// 加载级联数据options.value = await api.getOptions();// 如果有ID,加载详情if (props.id) {await getDetail(props.id);}
});
</script>

3. 表单提交时处理级联选择器值

当你需要提交表单时,需要确保级联选择器的值符合后端期望的格式(通常是单一 ID 而非路径数组):

import { prepareCascaderValueForSubmit } from './path/to/cascader-helper';// 提交表单方法
async function submitForm() {const formData = {// 其他表单字段...// 处理级联选择器值,确保提交给后端的是单一IDfieldId: prepareCascaderValueForSubmit(selectedValue.value)};// 提交到后端await api.saveForm(formData);
}

prepareCascaderValueForSubmit 函数会自动处理以下情况:

  • 如果值是数组(多级路径),返回最后一个元素(叶子节点 ID)
  • 如果值是单一 ID,则原样返回
  • 如果值是 undefined 或 null,则返回 undefined

4. 处理类型兼容性问题

如果你的表单字段不接受数组类型,但 cascaderProps 配置为emitPath: true时:

// 使用封装的工具函数处理级联选择器回显
const cascaderValue = prepareCascaderValue(options.value, res.someFieldId, {value: 'id',emitPath: cascaderProps.value.emitPath
});// 确保类型兼容性
if (Array.isArray(cascaderValue)) {// 如果emitPath为true但字段不支持数组,使用最后一个值formData.someField = cascaderValue[cascaderValue.length - 1];
} else {formData.someField = cascaderValue;
}

适用场景

  • 后端只返回叶子节点 ID,前端需要找到完整路径
  • 级联选择器配置了 checkStrictly=true,允许选择任意级别
  • 需要处理 emitPath 配置,支持完整路径或单一值
  • 表单提交时需要将级联选择器的路径数组转换为单一 ID

其他功能

工具还提供了findCascaderNode函数,可以直接查找并返回指定 ID 的节点对象:

import { findCascaderNode } from './path/to/cascader-helper';// 查找指定ID的节点
const node = findCascaderNode(options.value, targetId, { value: 'id', emitPath: false });// 使用查找到的节点
if (node) {console.log('节点名称:', node.name);
}

通用级联选择器辅助工具补充:更多使用场景与方法

9. 扩展使用场景

除了文章中已经提到的应用场景,这个工具在以下情况中也特别有用:

9.1 跨框架使用方案

由于工具是纯TypeScript/JavaScript实现,不依赖任何框架,所以可以在各种前端框架中使用:

React中使用示例
import { useState, useEffect } from 'react';
import { Cascader } from 'antd';
import { prepareCascaderValue } from './cascader-helper';function CascaderForm({ recordId }) {const [options, setOptions] = useState([]);const [selectedValue, setSelectedValue] = useState(null);// 加载选项数据useEffect(() => {async function loadOptions() {const data = await api.getOptions();setOptions(data);}loadOptions();}, []);// 加载详情数据useEffect(() => {if (recordId && options.length) {async function loadDetail() {const detail = await api.getDetail(recordId);// 使用工具处理回显const value = prepareCascaderValue(options,detail.categoryId,{ value: 'value', emitPath: false });setSelectedValue(value);}loadDetail();}}, [recordId, options]);return (<Cascaderoptions={options}value={selectedValue}onChange={setSelectedValue}fieldNames={{ label: 'name', value: 'value', children: 'children' }}/>);
}
Angular中使用示例
// component.ts
import { Component, OnInit, Input } from '@angular/core';
import { prepareCascaderValue } from './cascader-helper';@Component({selector: 'app-cascader-form',templateUrl: './cascader-form.component.html'
})
export class CascaderFormComponent implements OnInit {@Input() recordId: string;options: any[] = [];selectedValue: any;constructor(private apiService: ApiService) {}ngOnInit() {this.loadOptions();if (this.recordId) {this.loadDetail();}}async loadOptions() {this.options = await this.apiService.getOptions();}async loadDetail() {if (!this.options.length) await this.loadOptions();const detail = await this.apiService.getDetail(this.recordId);this.selectedValue = prepareCascaderValue(this.options,detail.categoryId,{ value: 'value', emitPath: true });}// 提交表单时处理值submitForm() {const formData = {// 其他表单数据...categoryId: prepareCascaderValueForSubmit(this.selectedValue)};this.apiService.save(formData);}
}

9.2 复杂业务场景应用

多级联动表单

在某些复杂表单中,一个级联选择会影响另一个级联选择的选项:

const categoryOptions = ref([]);
const subCategoryOptions = ref([]);// 第一个级联选择器变化时
function onCategoryChange(value) {const categoryId = prepareCascaderValueForSubmit(value);// 加载第二个级联选择器的选项api.getSubCategories(categoryId).then(data => {subCategoryOptions.value = data;// 如果已有详情数据,处理第二个级联的回显if (detailData.value?.subCategoryId) {formData.subCategoryId = prepareCascaderValue(subCategoryOptions.value,detailData.value.subCategoryId,{ value: 'id', emitPath: true });}});
}
动态生成表单字段

在表单字段由后端动态生成的系统中:

// 表单配置由后端返回
const formConfig = ref({fields: []
});// 处理动态表单中的级联选择器
function processFormData(detail) {formConfig.value.fields.forEach(field => {if (field.type === 'cascader' && detail[field.code]) {// 根据字段配置处理回显formData[field.code] = prepareCascaderValue(field.options,detail[field.code],{ value: field.valueKey || 'value', emitPath: field.emitPath !== false });}});
}

10. 特殊情况处理

10.1 不规则数据结构处理

实际项目中,有时会遇到不规则的级联数据结构:

// 处理不规则数据结构
const irregularOptions = [{name: "分类A",code: "A",subItems: [  // 不是标准的children{name: "子分类A1",id: "A1",subList: [  // 又一层不规则嵌套{ name: "项目A1-1", itemId: "A1-1" }]}]}
];// 需要先标准化数据结构
function standardizeOptions(options) {return options.map(item => ({id: item.code || item.id || item.itemId,name: item.name,children: item.subItems ? standardizeOptions(item.subItems) :item.subList ? standardizeOptions(item.subList) : undefined}));
}// 然后使用工具函数
const standardOptions = standardizeOptions(irregularOptions);
const value = prepareCascaderValue(standardOptions,"A1-1",{ value: 'id', emitPath: true }
);

10.2 懒加载数据处理

当使用懒加载级联选择器时,可能没有完整的数据结构。可以采用以下策略:

// 懒加载场景的处理函数
async function handleLazyLoadCascader(id, options, config) {// 先在已加载的数据中查找const value = prepareCascaderValue(options, id, config);if (value) return value;// 如果找不到,请求完整路径const path = await api.getNodePath(id);if (!path?.length) return id;// 手动构建完整路径的值return config.emitPath ? path : id;
}

10.3 同时处理多个级联选择器

在复杂表单中可能有多个级联选择器需要同时处理:

// 批量处理多个级联选择器
function processCascaders(detail, fieldsConfig) {const result = {};for (const [field, config] of Object.entries(fieldsConfig)) {if (detail[field]) {result[field] = prepareCascaderValue(config.options,detail[field],{ value: config.valueKey, emitPath: config.emitPath });}}return result;
}// 使用示例
const cascadersConfig = {industry: { options: industryOptions, valueKey: 'id', emitPath: true },region: { options: regionOptions, valueKey: 'code', emitPath: false },category: { options: categoryOptions, valueKey: 'value', emitPath: true }
};const cascaderValues = processCascaders(detailData, cascadersConfig);
Object.assign(formData, cascaderValues);

11. 性能与优化建议

11.1 缓存查找结果

对于频繁使用的大型数据结构,可以缓存查找结果:

// 使用Map缓存查找结果
const pathCache = new Map();function getCachedCascaderValue(options, targetId, config) {// 生成缓存键const cacheKey = `${targetId}-${config.value}-${config.emitPath}`;if (pathCache.has(cacheKey)) {return pathCache.get(cacheKey);}const result = prepareCascaderValue(options, targetId, config);pathCache.set(cacheKey, result);return result;
}

11.2 大数据结构的分页加载策略

对于特别大的数据结构,可以考虑分层加载:

// 示例:首先只加载第一层,然后按需加载更深层级
async function loadTopLevelOptions() {const topLevel = await api.getTopLevelCategories();options.value = topLevel.map(item => ({...item,leaf: false,children: [] // 占位,表示有子节点但未加载}));
}// el-cascader的懒加载处理函数
async function loadNode(node, resolve) {if (node.level === 0) {// 根节点,已在初始化时加载return resolve(options.value);}// 加载子节点const children = await api.getChildren(node.data.id);resolve(children);// 如果有详情数据需要回显,检查是否需要继续展开节点if (detailId.value && needToExpandNode(node.data.id)) {// 查找并触发下一级节点加载const targetChild = children.find(child => isInDetailPath(child.id, detailId.value));if (targetChild) {// 模拟展开该节点cascaderRef.value.expandNode(targetChild);}}
}

12. 实用场景示例

12.1 行政区划选择器

行政区划数据通常有3-4层结构:省/市/区/街道,非常适合使用此工具:

// 行政区划选择器应用
const areaOptions = ref([]);// 获取行政区划数据
async function getAreaOptions() {// 通常是一个较大的数据结构areaOptions.value = await api.getAreaTree();
}// 处理详情回显
function processDetailAddress(detail) {if (detail.areaCode) {// 地区代码通常是叶子节点formData.areaCode = prepareCascaderValue(areaOptions.value,detail.areaCode,{ value: 'code',  // 行政区划通常使用code作为标识emitPath: true  // 选择完整路径:省/市/区});}
}

12.2 商品分类场景

电商系统中的商品分类通常是多层级结构:

// 商品分类选择
async function loadProductDetail(productId) {const detail = await api.getProduct(productId);// 商品可能属于多级分类if (detail.categoryId) {// 处理商品分类回显formData.categoryId = prepareCascaderValue(categoryOptions.value,detail.categoryId,{ value: 'id', emitPath: true });}// 在提交时转换回单一IDfunction submitProduct() {const data = {...formData,categoryId: prepareCascaderValueForSubmit(formData.categoryId)};api.saveProduct(data);}
}

12.3 组织架构选择器

企业应用中常见的组织架构选择:

// 组织架构选择
async function handleUserDepartment() {const user = await api.getUserInfo(userId);// 用户所在部门可能是多级部门结构if (user.departmentId) {formData.departmentId = prepareCascaderValue(organizationOptions.value,user.departmentId,{ value: 'id', emitPath: true,// 组织架构通常允许选择任意层级checkStrictly: true });}
}

13. 不同UI框架下的应用示例

13.1 Element Plus完整示例

<template><el-form :model="form" label-width="120px"><el-form-item label="所属行业"><el-cascaderv-model="form.industryId":options="industryOptions":props="{label: 'name',value: 'id',emitPath: true,checkStrictly: true}"placeholder="请选择所属行业"/></el-form-item><el-button type="primary" @click="onSubmit">提交</el-button></el-form>
</template><script setup>
import { ref, onMounted } from 'vue';
import { prepareCascaderValue, prepareCascaderValueForSubmit } from './utils/cascader-helper';const industryOptions = ref([]);
const form = ref({ industryId: undefined });// 加载选项
onMounted(async () => {industryOptions.value = await api.getIndustryTree();// 如果是编辑模式,加载详情if (props.id) {const detail = await api.getDetail(props.id);// 处理回显form.value.industryId = prepareCascaderValue(industryOptions.value,detail.industryId,{ value: 'id', emitPath: true });}
});// 提交表单
async function onSubmit() {const data = {...form.value,// 转换为单一ID提交industryId: prepareCascaderValueForSubmit(form.value.industryId)};await api.save(data);
}
</script>

13.2 Ant Design Pro示例

import { useEffect, useState } from 'react';
import { Form, Cascader, Button } from 'antd';
import { prepareCascaderValue, prepareCascaderValueForSubmit } from './cascader-helper';const CategoryForm = ({ id }) => {const [form] = Form.useForm();const [options, setOptions] = useState([]);// 加载选项和详情useEffect(() => {const loadData = async () => {const categoryTree = await fetchCategoryTree();setOptions(categoryTree);if (id) {const detail = await fetchDetail(id);// 处理回显const categoryValue = prepareCascaderValue(categoryTree, detail.categoryId,{ value: 'value',  // Ant Design使用value作为默认值字段emitPath: true });form.setFieldsValue({ categoryId: categoryValue });}};loadData();}, [id, form]);// 处理提交const handleSubmit = async (values) => {const formData = {...values,categoryId: prepareCascaderValueForSubmit(values.categoryId)};await saveData(formData);};return (<Form form={form} onFinish={handleSubmit} layout="vertical"><Form.Item name="categoryId" label="分类" rules={[{ required: true }]}><Cascaderoptions={options}placeholder="请选择分类"fieldNames={{ label: 'name', value: 'value', children: 'children' }}/></Form.Item><Form.Item><Button type="primary" htmlType="submit">提交</Button></Form.Item></Form>);
};

14. 结语

通过以上扩展使用场景和方法,可以看出这个级联选择器辅助工具具有极高的通用性和灵活性。不仅可以应用于不同的前端框架,还可以处理各种复杂的业务场景。

虽然只有短短几十行代码,但它解决了前端开发中一个常见的痛点问题,体现了"小而美"工具的价值。通过将其纳入团队的工具库,可以显著提高开发效率和代码质量。

在实践中,每当遇到级联选择器回显问题,只需引入这个工具函数,无需再编写重复的递归查找代码,真正做到了一次编写,到处使用。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词