TreeList.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <template>
  2. <div :class="comClasses">
  3. <Tree-node
  4. v-for="(item, i) in stateTree"
  5. :key="i"
  6. :data="item"
  7. visible
  8. :multiple="multiple"
  9. :show-checkbox="showCheckbox"
  10. :editable="editable"
  11. :children-key="childrenKey"
  12. >
  13. </Tree-node>
  14. <div :class="[prefixCls + '-empty']" v-if="!stateTree.length">
  15. {{ emptyText }}
  16. </div>
  17. </div>
  18. </template>
  19. <script>
  20. import TreeNode from "./node.vue";
  21. import Emitter from "./emitter";
  22. const prefixCls = "ivu-tree";
  23. export default {
  24. name: "TreeList",
  25. mixins: [Emitter],
  26. components: { TreeNode },
  27. props: {
  28. data: {
  29. type: Array,
  30. default() {
  31. return [];
  32. }
  33. },
  34. multiple: {
  35. type: Boolean,
  36. default: false
  37. },
  38. extroRender: {
  39. type: Function,
  40. default(node) {
  41. return {};
  42. }
  43. },
  44. editable: {
  45. type: Boolean,
  46. default: false
  47. },
  48. showCheckbox: {
  49. type: Boolean,
  50. default: false
  51. },
  52. emptyText: {
  53. type: String,
  54. default: "暂无数据"
  55. },
  56. childrenKey: {
  57. type: String,
  58. default: "children"
  59. },
  60. loadData: {
  61. type: Function
  62. },
  63. render: {
  64. type: Function
  65. },
  66. onNodeEdit: {
  67. type: Function,
  68. default() {
  69. return {};
  70. }
  71. },
  72. onNodeAppend: {
  73. type: Function,
  74. default() {
  75. return {};
  76. }
  77. },
  78. onNodeRemove: {
  79. type: Function,
  80. default() {
  81. return {};
  82. }
  83. }
  84. },
  85. computed: {
  86. comClasses() {
  87. return [
  88. `${prefixCls}`,
  89. {
  90. [`${prefixCls}-edit`]: this.editable
  91. }
  92. ];
  93. }
  94. },
  95. data() {
  96. return {
  97. prefixCls: prefixCls,
  98. stateTree: this.data,
  99. flatState: []
  100. };
  101. },
  102. watch: {
  103. data: {
  104. deep: true,
  105. handler() {
  106. this.stateTree = this.data;
  107. this.flatState = this.compileFlatState();
  108. this.rebuildTree();
  109. }
  110. }
  111. },
  112. methods: {
  113. compileFlatState() {
  114. // so we have always a relation parent/children of each node
  115. const that = this;
  116. let keyCounter = 0;
  117. let childrenKey = this.childrenKey;
  118. const flatTree = [];
  119. function flattenChildren(node, parent) {
  120. // 额外字段
  121. let extroInfo = that.extroRender(node);
  122. for (let key in extroInfo) {
  123. node[key] = extroInfo[key];
  124. }
  125. node.nodeKey = keyCounter++;
  126. flatTree[node.nodeKey] = { node: node, nodeKey: node.nodeKey };
  127. if (typeof parent != "undefined") {
  128. flatTree[node.nodeKey].parent = parent.nodeKey;
  129. flatTree[parent.nodeKey][childrenKey].push(node.nodeKey);
  130. }
  131. if (node[childrenKey]) {
  132. flatTree[node.nodeKey][childrenKey] = [];
  133. node[childrenKey].forEach(child => flattenChildren(child, node));
  134. }
  135. }
  136. this.stateTree.forEach(rootNode => {
  137. flattenChildren(rootNode);
  138. });
  139. return flatTree;
  140. },
  141. updateTreeUp(nodeKey) {
  142. const parentKey = this.flatState[nodeKey].parent;
  143. if (typeof parentKey == "undefined") return;
  144. const node = this.flatState[nodeKey].node;
  145. const parent = this.flatState[parentKey].node;
  146. if (
  147. node.checked == parent.checked &&
  148. node.indeterminate == parent.indeterminate
  149. )
  150. return; // no need to update upwards
  151. if (node.checked == true) {
  152. this.$set(
  153. parent,
  154. "checked",
  155. parent[this.childrenKey].every(node => node.checked)
  156. );
  157. this.$set(parent, "indeterminate", !parent.checked);
  158. } else {
  159. this.$set(parent, "checked", false);
  160. this.$set(
  161. parent,
  162. "indeterminate",
  163. parent[this.childrenKey].some(
  164. node => node.checked || node.indeterminate
  165. )
  166. );
  167. }
  168. this.updateTreeUp(parentKey);
  169. },
  170. rebuildTree() {
  171. // only called when `data` prop changes
  172. const checkedNodes = this.getCheckedNodes();
  173. checkedNodes.forEach(node => {
  174. this.updateTreeDown(node, { checked: true });
  175. // propagate upwards
  176. const parentKey = this.flatState[node.nodeKey].parent;
  177. if (!parentKey && parentKey !== 0) return;
  178. const parent = this.flatState[parentKey].node;
  179. const childHasCheckSetter =
  180. typeof node.checked != "undefined" && node.checked;
  181. if (childHasCheckSetter && parent.checked != node.checked) {
  182. this.updateTreeUp(node.nodeKey); // update tree upwards
  183. }
  184. });
  185. },
  186. getSelectedNodes() {
  187. /* public API */
  188. return this.flatState
  189. .filter(obj => obj.node.selected)
  190. .map(obj => obj.node);
  191. },
  192. getCheckedNodes() {
  193. /* public API */
  194. return this.flatState
  195. .filter(obj => obj.node.checked)
  196. .map(obj => obj.node);
  197. },
  198. updateTreeDown(node, changes = {}) {
  199. for (let key in changes) {
  200. this.$set(node, key, changes[key]);
  201. }
  202. if (node[this.childrenKey]) {
  203. node[this.childrenKey].forEach(child => {
  204. this.updateTreeDown(child, changes);
  205. });
  206. }
  207. },
  208. handleSelect(nodeKey) {
  209. const node = this.flatState[nodeKey].node;
  210. if (!this.multiple) {
  211. // reset previously selected node
  212. const currentSelectedKey = this.flatState.findIndex(
  213. obj => obj.node.selected
  214. );
  215. if (currentSelectedKey >= 0 && currentSelectedKey !== nodeKey)
  216. this.$set(this.flatState[currentSelectedKey].node, "selected", false);
  217. }
  218. this.$set(node, "selected", !node.selected);
  219. this.$emit("on-select-change", this.getSelectedNodes());
  220. },
  221. handleCheck({ checked, nodeKey }) {
  222. const node = this.flatState[nodeKey].node;
  223. this.$set(node, "checked", checked);
  224. this.$set(node, "indeterminate", false);
  225. this.updateTreeUp(nodeKey); // propagate up
  226. this.updateTreeDown(node, { checked, indeterminate: false }); // reset `indeterminate` when going down
  227. this.$emit("on-check-change", this.getCheckedNodes());
  228. },
  229. handlerNodeEdit(nodeKey) {
  230. const node = this.flatState[nodeKey];
  231. this.onNodeEdit(node);
  232. },
  233. handlerNodeAppend(nodeKey) {
  234. const node = this.flatState[nodeKey];
  235. this.onNodeAppend(node);
  236. },
  237. handlerNodeRemove(nodeKey) {
  238. const root = this.flatState;
  239. const node = root[nodeKey];
  240. this.onNodeRemove(root, node, () => {
  241. if (node.parent || node.parent === 0) {
  242. const parentKey = node.parent;
  243. const parent = root.find(el => el.nodeKey === parentKey).node;
  244. const index = parent.children.indexOf(node.node);
  245. parent.children.splice(index, 1);
  246. } else {
  247. const index = this.stateTree.indexOf(node.node);
  248. this.stateTree.splice(index, 1);
  249. }
  250. });
  251. }
  252. },
  253. created() {
  254. this.flatState = this.compileFlatState();
  255. this.rebuildTree();
  256. },
  257. mounted() {
  258. this.$on("on-check", this.handleCheck);
  259. this.$on("on-selected", this.handleSelect);
  260. this.$on("toggle-expand", node => this.$emit("on-toggle-expand", node));
  261. if (this.editable) {
  262. this.$on("node-edit", this.handlerNodeEdit);
  263. this.$on("node-append", this.handlerNodeAppend);
  264. this.$on("node-remove", this.handlerNodeRemove);
  265. }
  266. }
  267. };
  268. </script>
  269. <style>
  270. .ivu-tree.ivu-tree-edit ul li {
  271. margin: 0 0 3px 0;
  272. }
  273. </style>