ant Disign Vue 级联组件

基于ant Design Vue级联组件a-cascader封装的级联组件
在这里插入图片描述

示例

结构

<a-form
  :label-col="formItemLayout.labelCol"
  :wrapper-col="formItemLayout.wrapperCol"
>
  <a-form-item label="整棵树">
    <grid-cascader v-model="model.unit" change-on-select @change="unitChange"></grid-cascader>
  </a-form-item>
  <a-form-item label="懒加载">
    <grid-cascader v-model="model.unit" lazy full-path change-on-select></grid-cascader>
  </a-form-item>
  <a-form-item :wrapper-col="formTailLayout.wrapperCol">
    <a-button type="primary" @click="print">print</a-button>
  </a-form-item>
</a-form>

<a-form
  :form="form"
  :label-col="formItemLayout.labelCol"
  :wrapper-col="formItemLayout.wrapperCol"
>
  <a-form-item label="所属网格">
    <grid-cascader
      v-decorator="[
        'unit',
        { rules: [{ required: true, message: '请选择' }] },
      ]"
      lazy
      full-path
      change-on-select
      placeholder="请选择"
    ></grid-cascader>
  </a-form-item>
  <a-form-item :wrapper-col="formTailLayout.wrapperCol">
    <a-button type="primary" @click="check">Check</a-button>
  </a-form-item>
</a-form>

逻辑

import GridCascader from '../components/grid-cascader';

export default {
  name: 'TestCascader',
  components: {
    GridCascader
  },
  data() {
    return {
      model: {
        unit: '3-17',
      },
      form: this.$form.createForm(this, { name: 'dynamic_rule' }),
      formItemLayout: {
        labelCol: { span: 4 },
        wrapperCol: { span: 12 },
      },
      formTailLayout: {
        labelCol: { span: 4 },
        wrapperCol: { span: 12, offset: 4 },
      },
    };
  },
  methods: {
    print() {
      console.log(this.model);
    },
    unitChange(value) {
      this.form.setFieldsValue({ unit: value });
    },
    check() {
      this.form.validateFields((err, value) => {
        console.log('--- validateFields --->', err, value);
        if (!err) {
          console.info('success');
        }
      });
    }
  }
}

样式

.test-cascader {
  padding: 300px 200px 0;
  display: flex;
  & > .ant-form {
    flex: 1;
  }
}

API

attributes

参数说明类型默认值
value指定选中项(最后一级)string-
fullPath是否显示全路径booleanfalse
lazy是否懒加载booleanfalse

event

事件名说明参数
change选择完成后的回调(value) => void

源码

组件代码

结构

<a-cascader
  v-bind="$attrs"
  :value="initData"
  :options="options"
  :load-data="loadData"
  :field-names="fieldNames"
  :displayRender="displayRender"
  style="width: 100%"
  @change="onChange"
></a-cascader>

逻辑

import { getTreeData, getData, getPath } from './test';
import { treeFindPath, treeDataFormat } from '../utils/utils.js';
export default {
  name: 'GridCascader',
  model: {
    prop: 'value',
    event: 'change',  // v-decorator 通过 change 事件接受新值
  },
  props: {
    value: {
      type: String,
      // 当通过 a-from 标签包裹时:
      // 这个参数是 v-decorator 给子组件传值用的
      // 这里不要给默认值, 在 form 下使用会爆警告 Warning: SquareUpload `default value` can not collect,  please use `option.initialValue` to set default value.
    },
    fullPath: {
      // 是否显示全路径
      type: Boolean,
      default: false,
    },
    lazy: {
      // 是否懒加载
      type: Boolean,
      default: false
    },
  },
  data() {
    return {
      options: [],
      fieldNames: { label: 'title', value: 'key', children: 'children' },
    }
  },
  computed: {
    initData() {
      const path = treeFindPath(this.value, this.options, this.fieldNames);
      return path.map(item => (item[this.fieldNames.value]));
    }
  },
  watch: {
    value: {
      async handler(value) {
        if (!value || !this.lazy) return;
        const path = treeFindPath(value, this.options, this.fieldNames);
        if (path.length) return;
        // 数据回显 请求接口查询返回数据
        this.options = await this.fetchPath(value);
      },
      immediate: true
    }
  },
  async created() {
    if (!this.lazy) {
      this.options = await this.fetchTree();
    } else if (!this.value) {
      this.options = await this.fetchData('-1');
    }
  },
  methods: {
    async fetchTree() {
      // 返回整棵树
      const res = await getTreeData();
      if (!Array.isArray(res.result)) return [];
      const result = this.treeFormat(res.result);
      console.log('--- fetchTree --->', JSON.parse(JSON.stringify(result)));
      return result;
    },
    async fetchData(parentId = '-1') {
      // (动态加载选项) 返回传入父节点的子节点
      const res = await getData({ parentId });
      if (!Array.isArray(res.result)) return [];
      const result = this.treeFormat(res.result);
      console.log('--- fetchData --->', JSON.parse(JSON.stringify(result)));
      return result;
    },
    async fetchPath(treeId) {
      // (数据回显) 返回选择节点的路径/每一级数组数据
      if (!treeId) return [];
      const res = await getPath({ treeId });
      if (!Array.isArray(res.result.treeData)) return [];
      const result = this.treeFormat(res.result.treeData);
      console.log('--- fetchPath --->', JSON.parse(JSON.stringify(result)));
      return result;
    },
    treeFormat(data) {
      return treeDataFormat(data, (item) => ({
        ...item,
        isLeaf: item.leaf
      }));
    },
    loadData(selectedOptions) {
      const targetOption = selectedOptions[selectedOptions.length - 1];
      targetOption.loading = true;
      this.fetchData(targetOption.key).then((result) => {
        targetOption.children = result;
        this.options = [...this.options];
      }).finally(() => {
        targetOption.loading = false;
      });
    },
    displayRender({ labels }) {
      if (labels.length == 0) return '';
      if (this.fullPath) return labels.join(' / ');
      return labels.pop();
    },
    onChange(value) {
      this.$emit('change', value.pop());
    },
  },
}

工具方法

../utils/utils.js

/**
 * 搜索树形结构 返回路径
 * @param {String} key 搜索的key
 * @param {Array} treeData 数据
 * @param {Object} defaultProps 自定义 options 中 label value children 的字段
 * @returns {Array}
 */
export function treeFindPath(key, treeData, defaultProps, path = []) {
	if (!Array.isArray(treeData)) {
		console.warn('treeData is not array');
		return [];
	}
	const { label: labelField = 'label', value: valueField = 'value', children: childrenField = 'children' } = defaultProps || {};
	for (let i = 0; i < treeData.length; i++) {
		const node = treeData[i];
		const children = node[childrenField];
		const label = node[labelField];
		const value = node[valueField];

		path.push({
			[labelField]: label,
			[valueField]: value
		});

		if (value === key) {
			return path;
		}

		if (children) {
			const p = treeFindPath(key, children, defaultProps, path);
			if (p.length) {
				return p;
			}
		}

		path.pop();
	}
	return [];
}

/**
 * 树 格式化数据
 * @param {Array} data 数据
 * @param {Function} handle 处理函数
 */
export function treeDataFormat(data, handle) {
	if (!Array.isArray(data)) {
		console.warn('data is not array');
		return data;
	}
	if (typeof(handle) !== 'function') {
		console.warn('handle is not function');
		return data;
	}
	return data.map(item => {
    let children;
    if (Array.isArray(item.children)) {
      children = treeDataFormat(item.children, handle);
    }
		const newItem = handle(item);
    if (children) {
      newItem.children = children;
    }
		return newItem;
	});
}

mock数据

../utils/utils.js

import { treeFindPath } from '../utils/utils';

const fieldNames = { label: 'title', value: 'key', children: 'children' };

const data = [{
    key: '1',
    title: '福建省',
    parentId: '-1',
    leaf: false,
    children: [
      { key: '1-1', title: '福州市', parentId: '1', leaf: true }, 
      { key: '1-2', title: '厦门市', parentId: '1', leaf: true }, 
      { key: '1-3', title: '莆田市', parentId: '1', leaf: true }, 
      {
        key: '1-4',
        title: '三明市',
        parentId: '1',
        leaf: false,
        children: [
          { key: '1-4-1', title: '梅列区', parentId: '1-4', leaf: true },
          { key: '1-4-2', title: '三元区', parentId: '1-4', leaf: true },
          { key: '1-4-3', title: '明溪县', parentId: '1-4', leaf: true },
          { key: '1-4-4', title: '清流县', parentId: '1-4', leaf: true },
          { key: '1-4-5', title: '宁化县', parentId: '1-4', leaf: true },
          { key: '1-4-6', title: '大田县', parentId: '1-4', leaf: true },
          { key: '1-4-7', title: '尤溪县', parentId: '1-4', leaf: true },
          { key: '1-4-8', title: '沙县', parentId: '1-4', leaf: true },
          { key: '1-4-9', title: '将乐县', parentId: '1-4', leaf: true },
          { key: '1-4-10', title: '泰宁县', parentId: '1-4', leaf: true },
          { key: '1-4-11', title: '建宁县', parentId: '1-4', leaf: true },
          { key: '1-4-12', title: '永安市', parentId: '1-4', leaf: true },
        ]
      }, 
      { key: '1-5', title: '泉州市', parentId: '1', leaf: true }, 
      { key: '1-6', title: '漳州市', parentId: '1', leaf: true }, 
      { key: '1-7', title: '南平市', parentId: '1', leaf: true }, 
      { key: '1-8', title: '龙岩市', parentId: '1', leaf: true }, 
      { key: '1-9', title: '宁德市', parentId: '1', leaf: true },
    ]
  }, {
    key: '2',
    title: '江苏省',
    parentId: '-1',
    leaf: false,
    children: [
      { key: '2-1', title: '南京市', parentId: '2', leaf: true },
      { key: '2-2', title: '无锡市', parentId: '2', leaf: true },
      { key: '2-3', title: '徐州市', parentId: '2', leaf: true },
      { key: '2-4', title: '常州市', parentId: '2', leaf: true },
      {
        key: '2-5',
        title: '苏州市',
        parentId: '2',
        leaf: false,
        children: [
          { key: '2-5-1', title: '沧浪区', parentId: '2-5', leaf: true },
          { key: '2-5-2', title: '平江区', parentId: '2-5', leaf: true },
          { key: '2-5-3', title: '金阊区', parentId: '2-5', leaf: true },
          { key: '2-5-4', title: '虎丘区', parentId: '2-5', leaf: true },
          { key: '2-5-5', title: '吴中区', parentId: '2-5', leaf: true },
          { key: '2-5-6', title: '相城区', parentId: '2-5', leaf: true },
          { key: '2-5-7', title: '姑苏区', parentId: '2-5', leaf: true },
          { key: '2-5-8', title: '常熟市', parentId: '2-5', leaf: true },
          { key: '2-5-9', title: '张家港市', parentId: '2-5', leaf: true },
          { key: '2-5-10', title: '昆山市', parentId: '2-5', leaf: true },
          { key: '2-5-11', title: '吴江区', parentId: '2-5', leaf: true },
          { key: '2-5-12', title: '太仓市', parentId: '2-5', leaf: true },
          { key: '2-5-13', title: '新区', parentId: '2-5', leaf: true },
          { key: '2-5-14', title: '园区', parentId: '2-5', leaf: true },
        ]
      },
      { key: '2-6', title: '南通市', parentId: '2', leaf: true },
      { key: '2-7', title: '连云港市', parentId: '2', leaf: true },
      { key: '2-8', title: '淮安市', parentId: '2', leaf: true },
      { key: '2-9', title: '盐城市', parentId: '2', leaf: true },
      { key: '2-10', title: '扬州市', parentId: '2', leaf: true },
      { key: '2-11', title: '镇江市', parentId: '2', leaf: true },
      { key: '2-12', title: '泰州市', parentId: '2', leaf: true },
      { key: '2-13', title: '宿迁市', parentId: '2', leaf: true },
    ]
  }, {
    key: '3',
    title: '河南省',
    parentId: '-1',
    leaf: false,
    children: [
      { key: '3-1', title: '郑州市', parentId: '3', leaf: true },
      { key: '3-2', title: '开封市', parentId: '3', leaf: true },
      { key: '3-3', title: '洛阳市', parentId: '3', leaf: true },
      { key: '3-4', title: '平顶山市', parentId: '3', leaf: true },
      { key: '3-5', title: '安阳市', parentId: '3', leaf: true },
      { key: '3-6', title: '鹤壁市', parentId: '3', leaf: true },
      { key: '3-7', title: '新乡市', parentId: '3', leaf: true },
      { key: '3-8', title: '焦作市', parentId: '3', leaf: true },
      { key: '3-9', title: '濮阳市', parentId: '3', leaf: true },
      { key: '3-10', title: '许昌市', parentId: '3', leaf: true },
      { key: '3-11', title: '漯河市', parentId: '3', leaf: true },
      { key: '3-12', title: '三门峡市', parentId: '3', leaf: true },
      { key: '3-13', title: '南阳市', parentId: '3', leaf: true },
      { key: '3-14', title: '商丘市', parentId: '3', leaf: true },
      { key: '3-15', title: '信阳市', parentId: '3', leaf: true },
      {
        key: '3-16',
        title: '周口市',
        parentId: '3',
        leaf: false,
        children: [
          { key: '3-16-1', title: '川汇区', parentId: '3-16', leaf: true },
          { key: '3-16-2', title: '扶沟县', parentId: '3-16', leaf: true },
          { key: '3-16-3', title: '西华县', parentId: '3-16', leaf: true },
          { key: '3-16-4', title: '商水县', parentId: '3-16', leaf: true },
          { key: '3-16-5', title: '沈丘县', parentId: '3-16', leaf: true },
          { key: '3-16-6', title: '郸城县', parentId: '3-16', leaf: true },
          { key: '3-16-7', title: '淮阳县', parentId: '3-16', leaf: true },
          { key: '3-16-8', title: '太康县', parentId: '3-16', leaf: true },
          { key: '3-16-9', title: '鹿邑县', parentId: '3-16', leaf: true },
          { key: '3-16-10', title: '项城市', parentId: '3-16', leaf: true },
        ]
      },
      { key: '3-17', title: '驻马店市', parentId: '3', leaf: true },
    ]
  }
];

const arr = [{ key: '-1', children: data }];

const map = new Map();
(function () {
  const func = (arr) => {
    arr.forEach((item) => {
      if (item.children && item.children.length) {
        map.set(
          item.key,
          item.children.map((item) => ({
            [fieldNames.label]: item[fieldNames.label],
            [fieldNames.value]: item[fieldNames.value],
            leaf: item.leaf,
          }))
        );
        func(item.children);
      }
    });
  };
  func(arr);
})();

function delay(duration = 300) {
  return new Promise((resolve) => {
    setTimeout(resolve, duration);
  });
}

/**
 * 返回整棵树
 * @returns {Promise<Array>}
 */
export async function getTreeData() {
  await delay();
  const res = {
    code: 200,
    message: '',
    result: data,
    success: true,
    timestamp: Date.now()
  };
  return res;
}

/**
 * (动态加载选项) 返回传入父节点的子节点
 * @param {Object} param = { parentId: '父节点' }
 * @returns {Promise<Array>}
 */
export async function getData({ parentId = '-1' }) {
  await delay();
  const res = {
    code: 200,
    message: '',
    result: map.get(parentId),
    success: true,
    timestamp: Date.now()
  };
  return res;
}

/**
 * (数据回显) 返回选择节点的路径/每一级数组数据
 * @param {Object} param = { treeId: '选择节点' }
 * @returns
 */
export async function getPath({ treeId = '' }) {
  if (!treeId) throw new Error('treeId is required');
  await delay();
  const path = treeFindPath(treeId, data, fieldNames);
  const treeData = [];
  if (path.length) {
    let list = map.get('-1');
    treeData.push(...list);
    for (let i = 0; i < path.length - 1; i++) {
      const item = list.find((item) => item.key == path[i].key);
      list = map.get(path[i].key);
      // treeData.push(list);
      item.children = list;
    }
  }
  const res = {
    code: 200,
    message: '',
    result: {
      path,
      treeData
    },
    success: true,
    timestamp: Date.now()
  };
  return res;
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值