基于 ElementUI 手撸穿梭树(一)

现在,一直在忙着写一个自己独立承担前端部分的项目。公司刚完成的上百个开发工程师开发了一年的大项目,用的技术栈是React。 由于,自己主导的这个项目比较小,项目复杂度比之前的项目差远了。所以,就选了,开发灵活性没 React 高,但开发速度较快的 Vue,同时UI框架用的是 ElementUI。我们之前用 React 的大项目中,就有很多的地方用到了,穿梭书组件(两个 ElementUI Tree 左树的数据可以穿梭到右面,右树的数据可以穿梭到左面)。好多节点的穿梭书是我们自己封装的。但这个小项目我们又用到了穿梭书,然而比较尴尬是,这次用的是 Vue,所以之前用 React 写的组件就只具备程序设计上的参考意义了。同时,我参考了,Element的树的写法的基础上,去是实现了基于Vue的穿梭树。由于,业务比较复杂,这篇文章就先之写 Tree 实现的核心思想吧!

Tree 程序结构

当然这个项目中记得引入 ElementUI。 树的每个节点都要记住自己的当前状态,树闭合展开选中的操作,也通过改变当前节点状态来改变。所以我们就需要,去把数据进行遍历,生成一个有状态,同时每个节点有改变的状态的构造树。

对数据进行遍历

树的 Data

const treeData = [
    {
      label: "一级 1",
      children: [
        {
          label: "二级 1-1",
        }
      ]
    },
    {
      label: "一级 2",
      children: [
        {
          label: "二级 2-1",
          children: [
            {
              label: "三级 2-1-1"
            }
          ]
        }
      ]
    }
]
复制代码

在 Tree.vue 中传入,将 treeData 传入构造函数中TreeStore。 new TreeStore({data: treeData}) 生成了我们想要的有状态,还可以通过调用内部函数改变自身节点状态的树形数据。

tree-store.js 代码

import Node from './node.js'
export default class TreeStore {
  constructor(options) {
    for (let option in options) {
      if (options.hasOwnProperty(option)) {
        this[option] = options[option]
      }
    }
    this.root = new Node({
      data: this.data,
      store: this
    })
  }
}
复制代码

node.js 代码

import objectAssign from 'element-ui/src/utils/merge';
const getPropertyFromData = function(node, prop) {
    const data = node.data || {};
    if (typeof prop === 'string') {
        return data[prop];
    } else {
        throw new Error('label data type is not string');
    }
};
export default class Node {
    constructor(options) {
        this.level = 0;
        this.childNodes = [];
        this.expanded = true; //展开关闭状态
        for (let option in options) {
            if (options.hasOwnProperty(option)) {
                this[option] = options[option]
            }
        }
        this.setData(this.data)
    }
    get isLeaf() {
        return !!this.childNodes.length
    }
    get label() {
        return getPropertyFromData(this, 'label');
    }
    setExpanded() { // 控制 expanded 来控制页面中树的闭合展开
        this.expanded = !this.expanded;
    }
    setData(data) {
        this.data = data;
        this.childNodes = []; // 子节点数组
        let children;
        if (this.level === 0 && this.data instanceof Array) {
            children = this.data;
        } else {
            children = getPropertyFromData(this, 'children') || [];
        }
        for (let i = 0; i < children.length; i++) {
            this.insertChild({ data: children[i] });
        }
    }
    insertChild(child, index, batch) {
        if (!child) throw new Error('insertChild error: child is required.');
        if (!(child instanceof Node)) {
            objectAssign(child, {
                parent: this,
                store: this.store
            });
            child = new Node(child);
        }

        child.level = this.level + 1;
        this.childNodes.push(child);
    }
}
复制代码

将新的数据进行遍历

生成新的数据后,我们就可以在 Tree.vue 中对新数据进行遍历。将构造的数据中的文本渲染到页面中。

Tree.vue 中代码

<template>
  <div class="el-tree" role="tree">
    <tree-node
      v-for="(child, index) in root.childNodes"
      :key="index"
      :node="child"
     />
  </div>
</template>

<script>
import TreeStore from "./modal/tree-store.js";
import TreeNode from "./TreeNode.vue";

export default {
  name: "Tree",
  data() {
    return {
      store: null,
      root: null
    };
  },
  props: {
    data: {
      type: Array
    }
  },
  created() {
    this.store = new TreeStore({
      data: this.data
    })
    this.root = this.store.root;
  },
  components: {
    TreeNode
  }
};
</script>
复制代码

TreeNode.vue 中代码

<template>
  <div class="tree">
    <div class="el-tree-node__content">
      <span
        :class="[
          {'expanded': node.expanded},
          'el-tree-node__expand-icon'
        ]"
        class="el-icon-caret-right"
        @click.stop="handleExpandIconClick"
      ></span>
      <span class="el-tree-node__label">{{node.label}}</span>
    </div>
    <el-collapse-transition> <!-- 使用了 elementUI 中的动画组件 -->
      <div
        v-show="node.expanded"
        class="el-tree-node__children"
        style="padding-left: 18px;"
      >
        <tree-node
          v-for="(item, index) in node.childNodes"
          :key="index"
          :node="item"
        />
      </div>
    </el-collapse-transition>
  </div>
</template>

<script>
export default {
  name: "TreeNode",
  props: {
    node: Object
  },
  data() {
    return {
      data: Array,
      isExpande: true
    };
  },
  methods: {
    handleExpandIconClick() {
      this.node.setExpanded();
    }
  }
};
</script>
复制代码

好了,这个树是我用 Vue 实现的最最基本的树。抛去了一切业务逻辑,一切只剩最根本,很好理解很简单。当然写成这么简单,有很大一部分原因也希望我女朋友能够看懂。 后面的我会把有业务复杂逻辑的加进来。

转载于:https://juejin.im/post/5d00e2906fb9a07ee1692177

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值