Просмотр исходного кода

Tree: add defaultExpandKeys & defaultCheckedKeys. (#1088)

FuryBean 8 лет назад
Родитель
Сommit
5203280af1

+ 12 - 18
examples/docs/en-US/tree.md

@@ -1,5 +1,5 @@
 <script>
-  var data = [{
+  const data = [{
     label: 'Level one 1',
     children: [{
       label: 'Level two 1-1'
@@ -20,23 +20,17 @@
     }]
   }];
 
-  var regions = [{
+  const regions = [{
     'name': 'region1'
   }, {
     'name': 'region2'
   }];
 
-  var count = 1;
+  let count = 1;
 
-  var props = {
+  const props = {
     label: 'name',
-    children: 'zones',
-    icon(data, node) {
-      if (node.isLeaf) {
-        return 'el-icon-close';
-      }
-      return 'el-icon-search';
-    }
+    children: 'zones'
   };
 
   var defaultProps = {
@@ -53,10 +47,10 @@
         console.log(data);
       },
       loadNode(node, resolve) {
-        if (node.level === -1) {
-          return resolve([{ name: 'region1' }, { name: 'region2' }]);
+        if (node.level === 0) {
+          return resolve([{ name: 'Root1' }, { name: 'Root2' }]);
         }
-        if (node.level > 4) return resolve([]);
+        if (node.level > 3) return resolve([]);
         var hasChild;
         if (node.data.name === 'region1') {
           hasChild = true;
@@ -67,7 +61,7 @@
         }
 
         setTimeout(function() {
-          var data;
+          let data;
           if (hasChild) {
             data = [{
               name: 'zone' + count++
@@ -185,10 +179,10 @@ Used for node selection. In the following example, data for each layer is acquir
         console.log(data);
       },
       loadNode(node, resolve) {
-        if (node.level === -1) {
-          return resolve([{ name: 'region1' }, { name: 'region2' }]);
+        if (node.level === 0) {
+          return resolve([{ name: 'Root1' }, { name: 'Root2' }]);
         }
-        if (node.level > 4) return resolve([]);
+        if (node.level > 3) return resolve([]);
 
         var hasChild;
         if (node.data.name === 'region1') {

+ 18 - 16
examples/docs/zh-CN/tree.md

@@ -11,7 +11,7 @@
 </style>
 
 <script>
-  var data = [{
+  const data = [{
     label: '一级 1',
     children: [{
       label: '二级 1-1'
@@ -32,23 +32,17 @@
     }]
   }];
 
-  var regions = [{
+  const regions = [{
     'name': 'region1'
   }, {
     'name': 'region2'
   }];
 
-  var count = 1;
+  let count = 1;
 
-  var props = {
+  const props = {
     label: 'name',
-    children: 'zones',
-    icon(data, node) {
-      if (node.isLeaf) {
-        return 'el-icon-close';
-      }
-      return 'el-icon-search';
-    }
+    children: 'zones'
   };
 
   var defaultProps = {
@@ -65,10 +59,10 @@
         console.log(data);
       },
       loadNode(node, resolve) {
-        if (node.level === -1) {
+        if (node.level === 0) {
           return resolve([{ name: 'region1' }, { name: 'region2' }]);
         }
-        if (node.level > 4) return resolve([]);
+        if (node.level > 3) return resolve([]);
         var hasChild;
         if (node.data.name === 'region1') {
           hasChild = true;
@@ -197,10 +191,10 @@
         console.log(data);
       },
       loadNode(node, resolve) {
-        if (node.level === -1) {
+        if (node.level === 0) {
           return resolve([{ name: 'region1' }, { name: 'region2' }]);
         }
-        if (node.level > 4) return resolve([]);
+        if (node.level > 3) return resolve([]);
 
         var hasChild;
         if (node.data.name === 'region1') {
@@ -236,11 +230,18 @@
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | data     | 展示数据 | array | — | — |
+| empty-text | 内容为空的时候展示的文本 | String | — | — |
+| node-key | 每个树节点用来作为唯一标识的属性,整颗树应该是唯一的 | String | — | — |
 | props | 配置选项,具体看下表 | object | — | — |
 | load | 加载子树数据的方法 | function(node, resolve) | — | — |
-| show-checkbox | 节点是否可被选择 | boolean | — | false |
 | render-content | 树节点的内容区的渲染 Function | Function(h, { node } | - | - |
 | highlight-current | 是否高亮当前选中节点,默认值是 false。| boolean | - | false |
+| default-expand-all | 是否默认展开所有节点 | boolean | - | false |
+| auto-expand-parent | 展开子节点的时候是否自动展开父节点 | boolean | — | true |
+| default-expand-keys | 默认展开的节点的 key 的数组 | array | — | — |
+| show-checkbox | 节点是否可被选择 | boolean | — | false |
+| check-strictly | 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false | boolean | — | false |
+| default-checked-keys | 默认勾选的节点的 key 的数组 | array | — | — |
 
 ### props
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
@@ -253,6 +254,7 @@
 | 方法名 | 说明 | 参数 |
 |------|--------|------|
 | getCheckedNodes | 若节点可被选择(即 `show-checkbox` 为 `true`),<br>则返回目前被选中的节点所组成的数组 | 接收一个 boolean 类型的参数,若为 `true` 则<br>仅返回被选中的叶子节点,默认值为 `false` |
+| setCheckedNodes | 设置目前勾选的节点,使用此方法必须设置 node-keys 属性 | 接收勾选节点数据的数组 |
 
 ### Events
 | 事件名称      | 说明    | 回调参数      |

+ 17 - 2
packages/theme-default/src/tree.css

@@ -6,6 +6,20 @@
     cursor: default;
     background: #ffffff;
     border: 1px solid #d3dce6;
+
+    @e empty-block {
+      display: table;
+      min-height: 60px;
+      text-align: center;
+      width: 100%;
+      height: 100%;
+    }
+
+    @e empty-text {
+      display: table-cell;
+      vertical-align: middle;
+      color: #5e6d82;
+    }
   }
 
   @b tree-node {
@@ -63,9 +77,10 @@
       display: inline-block;
     }
 
-    @e icon {
+    @e loading-icon {
       display: inline-block;
       vertical-align: middle;
+      margin-right: 4px;
       font-size: 14px;
       color: #99a9bf;
     }
@@ -77,7 +92,7 @@
       display: none;
     }
 
-    &.expanded > .el-tree-node__children {
+    &.is-expanded > .el-tree-node__children {
       display: block;
     }
   }

+ 61 - 25
packages/tree/src/model/node.js

@@ -1,4 +1,3 @@
-let nodeIdSeed = 0;
 import objectAssign from 'element-ui/src/utils/merge';
 
 const reInitChecked = function(node) {
@@ -27,7 +26,7 @@ const reInitChecked = function(node) {
 };
 
 const getPropertyFromData = function(node, prop) {
-  const props = node.props;
+  const props = node._tree.props;
   const data = node.data || {};
   const config = props[prop];
 
@@ -40,6 +39,8 @@ const getPropertyFromData = function(node, prop) {
   }
 };
 
+let nodeIdSeed = 0;
+
 export default class Node {
   constructor(options) {
     this.id = nodeIdSeed++;
@@ -48,9 +49,7 @@ export default class Node {
     this.indeterminate = false;
     this.data = null;
     this.expanded = false;
-    this.props = null;
     this.parent = null;
-    this.lazy = false;
 
     for (let name in options) {
       if (options.hasOwnProperty(name)) {
@@ -59,7 +58,7 @@ export default class Node {
     }
 
     // internal
-    this.level = -1;
+    this.level = 0;
     this.loaded = false;
     this.childNodes = [];
     this.loading = false;
@@ -68,8 +67,39 @@ export default class Node {
       this.level = this.parent.level + 1;
     }
 
-    if (this.lazy !== true && this.data) {
+    const tree = this._tree;
+    if (!tree) {
+      throw new Error('[Node]_tree is required!');
+    }
+    tree.registerNode(this);
+
+    if (tree.lazy !== true && this.data) {
       this.setData(this.data);
+
+      if (tree.defaultExpandAll) {
+        this.expanded = true;
+      }
+    } else if (this.level > 0 && tree.lazy && tree.defaultExpandAll) {
+      this.expand();
+    }
+
+    if (!this.data) return;
+    const defaultExpandedKeys = tree.defaultExpandedKeys;
+    const key = tree.key;
+    if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) {
+      if (tree.autoExpandParent) {
+        let parent = this.parent;
+        while (parent.level > 0) {
+          parent.expanded = true;
+          parent = parent.parent;
+        }
+      }
+
+      this.expand();
+    }
+
+    if (tree.lazy) {
+      tree._initDefaultCheckedNode(this);
     }
   }
 
@@ -87,7 +117,7 @@ export default class Node {
     this.childNodes = [];
 
     let children;
-    if (this.level === -1 && this.data instanceof Array) {
+    if (this.level === 0 && this.data instanceof Array) {
       children = this.data;
     } else {
       children = getPropertyFromData(this, 'children') || [];
@@ -106,15 +136,19 @@ export default class Node {
     return getPropertyFromData(this, 'icon');
   }
 
+  get key() {
+    const nodeKey = this._tree.key;
+    if (this.data) return this.data[nodeKey];
+    return null;
+  }
+
   insertChild(child, index) {
     if (!child) throw new Error('insertChild error: child is required.');
 
     if (!(child instanceof Node)) {
       objectAssign(child, {
         parent: this,
-        lazy: this.lazy,
-        load: this.load,
-        props: this.props
+        _tree: this._tree
       });
       child = new Node(child);
     }
@@ -132,6 +166,7 @@ export default class Node {
     const index = this.childNodes.indexOf(child);
 
     if (index > -1) {
+      this._tree && this._tree.deregisterNode(child);
       child.parent = null;
       this.childNodes.splice(index, 1);
     }
@@ -154,14 +189,13 @@ export default class Node {
     if (this.shouldLoadData()) {
       this.loadData((data) => {
         if (data instanceof Array) {
-          callback();
+          this.expanded = true;
+          if (callback) callback();
         }
       });
     } else {
       this.expanded = true;
-      if (callback) {
-        callback();
-      }
+      if (callback) callback();
     }
   }
 
@@ -176,7 +210,7 @@ export default class Node {
   }
 
   shouldLoadData() {
-    return this.lazy === true && this.load && !this.loaded;
+    return this._tree.lazy === true && this._tree.load && !this.loaded;
   }
 
   get isLeaf() {
@@ -185,7 +219,7 @@ export default class Node {
 
   hasChild() {
     const childNodes = this.childNodes;
-    if (!this.lazy || (this.lazy === true && this.loaded === true)) {
+    if (!this._tree.lazy || (this._tree.lazy === true && this.loaded === true)) {
       return childNodes && childNodes.length > 0;
     }
     return true;
@@ -195,7 +229,7 @@ export default class Node {
     this.indeterminate = value === 'half';
     this.checked = value === true;
 
-    const handleDeep = () => {
+    const handleDescendants = () => {
       if (deep) {
         const childNodes = this.childNodes;
         for (let i = 0, j = childNodes.length; i < j; i++) {
@@ -205,28 +239,30 @@ export default class Node {
       }
     };
 
-    if (this.shouldLoadData()) {
+    if (!this._tree.checkStrictly && this.shouldLoadData()) {
       // Only work on lazy load data.
       this.loadData(() => {
-        handleDeep();
+        handleDescendants();
       }, {
         checked: value !== false
       });
     } else {
-      handleDeep();
+      handleDescendants();
     }
 
     const parent = this.parent;
-    if (parent.level === -1) return;
+    if (!parent || parent.level === 0) return;
 
-    reInitChecked(parent);
+    if (!this._tree.checkStrictly) {
+      reInitChecked(parent);
+    }
   }
 
   getChildren() { // this is data
     const data = this.data;
     if (!data) return null;
 
-    const props = this.props;
+    const props = this._tree.props;
     let children = 'children';
     if (props) {
       children = props.children || 'children';
@@ -259,7 +295,7 @@ export default class Node {
   }
 
   loadData(callback, defaultProps = {}) {
-    if (this.lazy === true && this.load && !this.loaded) {
+    if (this._tree.lazy === true && this._tree.load && !this.loaded && !this.loading) {
       this.loading = true;
 
       const resolve = (children) => {
@@ -274,7 +310,7 @@ export default class Node {
         }
       };
 
-      this.load(this, resolve);
+      this._tree.load(this, resolve);
     } else {
       if (callback) {
         callback.call(this);

+ 81 - 7
packages/tree/src/model/tree.js

@@ -8,37 +8,111 @@ export default class Tree {
       }
     }
 
+    this.nodesMap = {};
+
     this.root = new Node({
       data: this.data,
-      lazy: this.lazy,
-      props: this.props,
-      load: this.load
+      _tree: this
     });
 
     if (this.lazy && this.load) {
       const loadFn = this.load;
       loadFn(this.root, (data) => {
         this.root.doCreateChildren(data);
+        this._initDefaultCheckedNodes();
       });
+    } else {
+      this._initDefaultCheckedNodes();
+    }
+  }
+
+  setData(newVal) {
+    const instanceChanged = newVal !== this.root.data;
+    this.root.setData(newVal);
+    if (instanceChanged) {
+      this._initDefaultCheckedNodes();
+    }
+  }
+
+  _initDefaultCheckedNodes() {
+    const defaultCheckedKeys = this.defaultCheckedKeys || [];
+    const nodesMap = this.nodesMap;
+
+    defaultCheckedKeys.forEach((checkedKey) => {
+      const node = nodesMap[checkedKey];
+
+      if (node) {
+        node.setChecked(true, !this.checkStrictly);
+      }
+    });
+  }
+
+  _initDefaultCheckedNode(node) {
+    const defaultCheckedKeys = this.defaultCheckedKeys || [];
+
+    if (defaultCheckedKeys.indexOf(node.key) !== -1) {
+      node.setChecked(true, !this.checkStrictly);
     }
   }
 
+  setDefaultCheckedKey(newVal) {
+    if (newVal !== this.defaultCheckedKeys) {
+      this.defaultCheckedKeys = newVal;
+      this._initDefaultCheckedNodes();
+    }
+  }
+
+  registerNode(node) {
+    const key = this.key;
+    if (!key || !node || !node.data) return;
+
+    this.nodesMap[node.key] = node;
+  }
+
+  deregisterNode(node) {
+    const key = this.key;
+    if (!key || !node || !node.data) return;
+
+    delete this.nodesMap[node.key];
+  }
+
   getCheckedNodes(leafOnly) {
     const checkedNodes = [];
-    const walk = function(node) {
+    const traverse = function(node) {
       const childNodes = node.root ? node.root.childNodes : node.childNodes;
 
-      childNodes.forEach(function(child) {
+      childNodes.forEach((child) => {
         if ((!leafOnly && child.checked) || (leafOnly && child.isLeaf && child.checked)) {
           checkedNodes.push(child.data);
         }
 
-        walk(child);
+        traverse(child);
       });
     };
 
-    walk(this);
+    traverse(this);
 
     return checkedNodes;
   }
+
+  setCheckedNodes(array) {
+    const key = this.key;
+    const checkedKeys = {};
+    array.forEach((item) => {
+      checkedKeys[(item || {})[key]] = true;
+    });
+
+    const allNodes = [];
+    const nodesMap = this.nodesMap;
+    for (let nodeKey in nodesMap) {
+      if (nodesMap.hasOwnProperty(nodeKey)) {
+        allNodes.push(nodesMap[nodeKey]);
+      }
+    }
+
+    allNodes.sort((a, b) => a.level > b.level ? -1 : 1);
+    allNodes.forEach((node) => {
+      node.setChecked(!!checkedKeys[(node.data || {})[key]], !this.checkStrictly);
+    });
+  }
 };

+ 28 - 11
packages/tree/src/tree-node.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="el-tree-node"
     @click.stop="handleClick"
-    :class="{ expanded: childNodeRendered && expanded, 'is-current': $tree.currentNode === _self }">
+    :class="{ 'is-expanded': childNodeRendered && expanded, 'is-current': $tree.currentNode === _self }">
     <div class="el-tree-node__content"
-      :style="{ 'padding-left': node.level * 16 + 'px' }"
+      :style="{ 'padding-left': (node.level - 1) * 16 + 'px' }"
       @click="handleExpandIconClick">
       <span
         class="el-tree-node__expand-icon"
@@ -18,7 +18,7 @@
       </el-checkbox>
       <span
         v-if="node.loading"
-        class="el-tree-node__icon el-icon-loading">
+        class="el-tree-node__loading-icon el-icon-loading">
       </span>
       <node-content :node="node"></node-content>
     </div>
@@ -29,6 +29,7 @@
         <el-tree-node
           :render-content="renderContent"
           v-for="child in node.childNodes"
+          :key="getNodeKey(child)"
           :node="child">
         </el-tree-node>
       </div>
@@ -89,10 +90,25 @@
 
       'node.checked'(val) {
         this.handleSelectChange(val, this.node.indeterminate);
+      },
+
+      'node.expanded'(val) {
+        this.expanded = val;
+        if (val) {
+          this.childNodeRendered = true;
+        }
       }
     },
 
     methods: {
+      getNodeKey(node, index) {
+        const nodeKey = this.$tree.nodeKey;
+        if (nodeKey && node) {
+          return node.data[nodeKey];
+        }
+        return index;
+      },
+
       handleSelectChange(checked, indeterminate) {
         if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) {
           this.$tree.$emit('check-change', this.node.data, checked, indeterminate);
@@ -112,31 +128,27 @@
           target.nodeName.toUpperCase() === 'LABEL') return;
         if (this.expanded) {
           this.node.collapse();
-          this.expanded = false;
         } else {
-          this.node.expand(() => {
-            this.expanded = true;
-            this.childNodeRendered = true;
-          });
+          this.node.expand();
         }
         this.$tree.$emit('node-click', this.node.data, this.node, this);
       },
 
       handleUserClick() {
         if (this.node.indeterminate) {
-          this.node.setChecked(this.node.checked, true);
+          this.node.setChecked(this.node.checked, !this.$tree.checkStrictly);
         }
       },
 
       handleCheckChange(ev) {
         if (!this.node.indeterminate) {
-          this.node.setChecked(ev.target.checked, true);
+          this.node.setChecked(ev.target.checked, !this.$tree.checkStrictly);
         }
       }
     },
 
     created() {
-      var parent = this.$parent;
+      const parent = this.$parent;
 
       if (parent.$isTree) {
         this.$tree = parent;
@@ -157,6 +169,11 @@
       }
 
       this.showCheckbox = tree.showCheckbox;
+
+      if (this.node.expanded) {
+        this.expanded = true;
+        this.childNodeRendered = true;
+      }
     }
   };
 </script>

+ 33 - 5
packages/tree/src/tree.vue

@@ -6,11 +6,15 @@
       :props="props"
       :render-content="renderContent">
     </el-tree-node>
+    <div class="el-tree__empty-block" v-if="!tree.root.childNodes || tree.root.childNodes.length === 0">
+      <span class="el-tree__empty-text">{{ emptyText }}</span>
+    </div>
   </div>
 </template>
 
 <script type="text/ecmascript-6">
   import Tree from './model/tree';
+  import { t } from 'element-ui/src/locale';
 
   export default {
     name: 'el-tree',
@@ -19,6 +23,19 @@
       data: {
         type: Array
       },
+      emptyText: {
+        type: String,
+        default: t('el.tree.emptyText')
+      },
+      nodeKey: String,
+      checkStrictly: Boolean,
+      defaultExpandAll: Boolean,
+      autoExpandParent: {
+        type: Boolean,
+        default: true
+      },
+      defaultCheckedKeys: Array,
+      defaultExpandedKeys: Array,
       renderContent: Function,
       showCheckbox: {
         type: Boolean,
@@ -38,19 +55,23 @@
         default: false
       },
       highlightCurrent: Boolean,
-      load: {
-        type: Function
-      }
+      load: Function
     },
 
     created() {
       this.$isTree = true;
 
       this.tree = new Tree({
+        key: this.nodeKey,
         data: this.data,
         lazy: this.lazy,
         props: this.props,
-        load: this.load
+        load: this.load,
+        checkStrictly: this.checkStrictly,
+        defaultCheckedKeys: this.defaultCheckedKeys,
+        defaultExpandedKeys: this.defaultExpandedKeys,
+        autoExpandParent: this.autoExpandParent,
+        defaultExpandAll: this.defaultExpandAll
       });
     },
 
@@ -78,13 +99,20 @@
 
     watch: {
       data(newVal) {
-        this.tree.root.setData(newVal);
+        this.tree.setData(newVal);
+      },
+      defaultCheckedKeys(newVal) {
+        this.tree.setDefaultCheckedKey(newVal);
       }
     },
 
     methods: {
       getCheckedNodes(leafOnly) {
         return this.tree.getCheckedNodes(leafOnly);
+      },
+      setCheckedNodes(nodes) {
+        if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedNodes');
+        this.tree.setCheckedNodes(nodes);
       }
     }
   };

+ 3 - 0
src/locale/lang/de.js

@@ -77,6 +77,9 @@ export default {
       confirmFilter: 'filtern',
       resetFilter: 'rücksetzen',
       clearFilter: 'alles'
+    },
+    tree: {
+      emptyText: 'keine Daten'
     }
   }
 };

+ 3 - 0
src/locale/lang/en.js

@@ -77,6 +77,9 @@ export default {
       confirmFilter: 'Confirm',
       resetFilter: 'Reset',
       clearFilter: 'All'
+    },
+    tree: {
+      emptyText: 'No Data'
     }
   }
 };

+ 3 - 0
src/locale/lang/pt.js

@@ -77,6 +77,9 @@ export default {
       confirmFilter: 'Confirmar',
       resetFilter: 'Limpar',
       clearFilter: 'Todos'
+    },
+    tree: {
+      emptyText: 'Sem dados'
     }
   }
 };

+ 3 - 0
src/locale/lang/zh-CN.js

@@ -77,6 +77,9 @@ export default {
       confirmFilter: '筛选',
       resetFilter: '重置',
       clearFilter: '全部'
+    },
+    tree: {
+      emptyText: '暂无数据'
     }
   }
 };

+ 123 - 13
test/unit/specs/tree.spec.js

@@ -1,5 +1,7 @@
 import { createVue, destroyVM } from '../util';
 
+const DELAY = 10;
+
 describe('Tree', () => {
   let vm;
   afterEach(() => {
@@ -9,30 +11,44 @@ describe('Tree', () => {
   const getTreeVm = (props, options) => {
     return createVue(Object.assign({
       template: `
-        <el-tree :data="data" ${ props }></el-tree>
+        <el-tree ref="tree" :data="data" ${ props }></el-tree>
         `,
 
       data() {
         return {
+          defaultExpandedKeys: [],
+          defaultCheckedKeys: [],
           clickedNode: null,
           count: 1,
           data: [{
+            id: 1,
             label: '一级 1',
             children: [{
-              label: '二级 1-1'
+              id: 11,
+              label: '二级 1-1',
+              children: [{
+                id: 111,
+                label: '三级 1-1'
+              }]
             }]
           }, {
+            id: 2,
             label: '一级 2',
             children: [{
+              id: 21,
               label: '二级 2-1'
             }, {
+              id: 22,
               label: '二级 2-2'
             }]
           }, {
+            id: 3,
             label: '一级 3',
             children: [{
+              id: 31,
               label: '二级 3-1'
             }, {
+              id: 32,
               label: '二级 3-2'
             }]
           }],
@@ -45,11 +61,13 @@ describe('Tree', () => {
     }, options), true);
   };
 
+  const ALL_NODE_COUNT = 9;
+
   it('create', () => {
     vm = getTreeVm(':props="defaultProps"');
     expect(document.querySelector('.el-tree')).to.exist;
     expect(document.querySelectorAll('.el-tree > .el-tree-node').length).to.equal(3);
-    expect(document.querySelectorAll('.el-tree .el-tree-node').length).to.equal(8);
+    expect(document.querySelectorAll('.el-tree .el-tree-node').length).to.equal(ALL_NODE_COUNT);
     vm.data[1].children = [{ label: '二级 2-1' }];
     const tree = vm.$children[0];
     expect(tree.children).to.deep.equal(vm.data);
@@ -63,16 +81,25 @@ describe('Tree', () => {
         }
       }
     });
-    const firstNode = document.querySelector('.el-tree-node__content');
+    const firstNode = vm.$el.querySelector('.el-tree-node__content');
     firstNode.click();
     expect(vm.clickedNode.label).to.equal('一级 1');
-    vm.$nextTick(() => {
-      expect(document.querySelector('.el-tree-node').classList.contains('expanded')).to.true;
+    setTimeout(() => {
+      expect(vm.$el.querySelector('.el-tree-node').classList.contains('is-expanded')).to.true;
       firstNode.click();
-      vm.$nextTick(() => {
-        expect(document.querySelector('.el-tree-node').classList.contains('expanded')).to.false;
+      setTimeout(() => {
+        expect(vm.$el.querySelector('.el-tree-node').classList.contains('is-expanded')).to.false;
         done();
-      });
+      }, DELAY);
+    }, DELAY);
+  });
+
+  it('emptyText', (done) => {
+    vm = getTreeVm(':props="defaultProps"');
+    vm.data = [];
+    vm.$nextTick(() => {
+      expect(vm.$el.querySelectorAll('.el-tree__empty-block').length).to.equal(1);
+      done();
     });
   });
 
@@ -86,12 +113,62 @@ describe('Tree', () => {
     });
   });
 
+  it('defaultExpandAll', () => {
+    vm = getTreeVm(':props="defaultProps" default-expand-all');
+    expect(vm.$el.querySelectorAll('.el-tree-node.is-expanded').length).to.equal(ALL_NODE_COUNT);
+  });
+
+  it('defaultExpandedKeys', () => {
+    vm = getTreeVm(':props="defaultProps" :default-expanded-keys="defaultExpandedKeys" node-key="id"', {
+      created() {
+        this.defaultExpandedKeys = [1, 3];
+      }
+    });
+    expect(vm.$el.querySelectorAll('.el-tree-node.is-expanded').length).to.equal(2);
+  });
+
+  it('autoExpandParent = true', () => {
+    vm = getTreeVm(':props="defaultProps" :default-expanded-keys="defaultExpandedKeys" node-key="id"', {
+      created() {
+        this.defaultExpandedKeys = [111];
+      }
+    });
+    expect(vm.$el.querySelectorAll('.el-tree-node.is-expanded').length).to.equal(3);
+  });
+
+  it('autoExpandParent = false', () => {
+    vm = getTreeVm(':props="defaultProps" :default-expanded-keys="defaultExpandedKeys" node-key="id" :auto-expand-parent="false"', {
+      created() {
+        this.defaultExpandedKeys = [111];
+      }
+    });
+    expect(vm.$el.querySelectorAll('.el-tree-node.is-expanded').length).to.equal(1);
+  });
+
+  it('defaultCheckedKeys & check-strictly = false', () => {
+    vm = getTreeVm(':props="defaultProps" default-expand-all show-checkbox :default-checked-keys="defaultCheckedKeys" node-key="id"', {
+      created() {
+        this.defaultCheckedKeys = [1];
+      }
+    });
+    expect(vm.$el.querySelectorAll('.el-checkbox .is-checked').length).to.equal(3);
+  });
+
+  it('defaultCheckedKeys & check-strictly', () => {
+    vm = getTreeVm(':props="defaultProps" default-expand-all show-checkbox :default-checked-keys="defaultCheckedKeys" node-key="id" check-strictly', {
+      created() {
+        this.defaultCheckedKeys = [1];
+      }
+    });
+    expect(vm.$el.querySelectorAll('.el-checkbox .is-checked').length).to.equal(1);
+  });
+
   it('show checkbox', done => {
     vm = getTreeVm(':props="defaultProps" show-checkbox');
     const tree = vm.$children[0];
-    const secondNode = document.querySelectorAll('.el-tree-node__content')[2];
+    const secondNode = document.querySelectorAll('.el-tree-node__content')[3];
     const nodeCheckbox = secondNode.querySelector('.el-checkbox');
-    expect(nodeCheckbox).to.exist;
+    expect(nodeCheckbox).to.be.exist;
     nodeCheckbox.click();
     expect(tree.getCheckedNodes().length).to.equal(3);
     expect(tree.getCheckedNodes(true).length).to.equal(2);
@@ -104,6 +181,39 @@ describe('Tree', () => {
     });
   });
 
+  it('setCheckedNodes', (done) => {
+    vm = getTreeVm(':props="defaultProps" show-checkbox node-key="id"');
+    const tree = vm.$children[0];
+    const secondNode = document.querySelectorAll('.el-tree-node__content')[3];
+    const nodeCheckbox = secondNode.querySelector('.el-checkbox');
+    expect(nodeCheckbox).to.be.exist;
+    nodeCheckbox.click();
+    expect(tree.getCheckedNodes().length).to.equal(3);
+    expect(tree.getCheckedNodes(true).length).to.equal(2);
+    vm.$nextTick(() => {
+      tree.setCheckedNodes([]);
+      expect(tree.getCheckedNodes().length).to.equal(0);
+      done();
+    });
+  });
+
+  it('check strictly', (done) => {
+    vm = getTreeVm(':props="defaultProps" show-checkbox check-strictly');
+    const tree = vm.$children[0];
+    const secondNode = document.querySelectorAll('.el-tree-node__content')[3];
+    const nodeCheckbox = secondNode.querySelector('.el-checkbox');
+    nodeCheckbox.click();
+    expect(tree.getCheckedNodes().length).to.equal(1);
+    expect(tree.getCheckedNodes(true).length).to.equal(0);
+    const firstLeaf = secondNode.nextElementSibling.querySelector('.el-tree-node__content');
+    const leafCheckbox = firstLeaf.querySelector('.el-checkbox');
+    vm.$nextTick(() => {
+      leafCheckbox.click();
+      expect(tree.getCheckedNodes().length).to.equal(2);
+      done();
+    });
+  });
+
   it('render content', () => {
     vm = getTreeVm(':props="defaultProps" :render-content="renderContent"', {
       methods: {
@@ -127,10 +237,10 @@ describe('Tree', () => {
     vm = getTreeVm(':props="defaultProps" lazy :load="loadNode" show-checkbox', {
       methods: {
         loadNode(node, resolve) {
-          if (node.level === -1) {
+          if (node.level === 0) {
             return resolve([{ label: 'region1' }, { label: 'region2' }]);
           }
-          if (node.level > 3) return resolve([]);
+          if (node.level > 4) return resolve([]);
           setTimeout(() => {
             resolve([{
               label: 'zone' + this.count++