123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- <template>
- <div :class="comClasses">
- <Tree-node
- v-for="(item, i) in stateTree"
- :key="i"
- :data="item"
- visible
- :multiple="multiple"
- :show-checkbox="showCheckbox"
- :editable="editable"
- :children-key="childrenKey"
- >
- </Tree-node>
- <div :class="[prefixCls + '-empty']" v-if="!stateTree.length">
- {{ emptyText }}
- </div>
- </div>
- </template>
- <script>
- import TreeNode from "./node.vue";
- import Emitter from "./emitter";
- const prefixCls = "ivu-tree";
- export default {
- name: "TreeList",
- mixins: [Emitter],
- components: { TreeNode },
- props: {
- data: {
- type: Array,
- default() {
- return [];
- }
- },
- multiple: {
- type: Boolean,
- default: false
- },
- extroRender: {
- type: Function,
- default(node) {
- return {};
- }
- },
- editable: {
- type: Boolean,
- default: false
- },
- showCheckbox: {
- type: Boolean,
- default: false
- },
- emptyText: {
- type: String,
- default: "暂无数据"
- },
- childrenKey: {
- type: String,
- default: "children"
- },
- loadData: {
- type: Function
- },
- render: {
- type: Function
- },
- onNodeEdit: {
- type: Function,
- default() {
- return {};
- }
- },
- onNodeAppend: {
- type: Function,
- default() {
- return {};
- }
- },
- onNodeRemove: {
- type: Function,
- default() {
- return {};
- }
- }
- },
- computed: {
- comClasses() {
- return [
- `${prefixCls}`,
- {
- [`${prefixCls}-edit`]: this.editable
- }
- ];
- }
- },
- data() {
- return {
- prefixCls: prefixCls,
- stateTree: this.data,
- flatState: []
- };
- },
- watch: {
- data: {
- deep: true,
- handler() {
- this.stateTree = this.data;
- this.flatState = this.compileFlatState();
- this.rebuildTree();
- }
- }
- },
- methods: {
- compileFlatState() {
- // so we have always a relation parent/children of each node
- const that = this;
- let keyCounter = 0;
- let childrenKey = this.childrenKey;
- const flatTree = [];
- function flattenChildren(node, parent) {
- // 额外字段
- let extroInfo = that.extroRender(node);
- for (let key in extroInfo) {
- node[key] = extroInfo[key];
- }
- node.nodeKey = keyCounter++;
- flatTree[node.nodeKey] = { node: node, nodeKey: node.nodeKey };
- if (typeof parent != "undefined") {
- flatTree[node.nodeKey].parent = parent.nodeKey;
- flatTree[parent.nodeKey][childrenKey].push(node.nodeKey);
- }
- if (node[childrenKey]) {
- flatTree[node.nodeKey][childrenKey] = [];
- node[childrenKey].forEach(child => flattenChildren(child, node));
- }
- }
- this.stateTree.forEach(rootNode => {
- flattenChildren(rootNode);
- });
- return flatTree;
- },
- updateTreeUp(nodeKey) {
- const parentKey = this.flatState[nodeKey].parent;
- if (typeof parentKey == "undefined") return;
- const node = this.flatState[nodeKey].node;
- const parent = this.flatState[parentKey].node;
- if (
- node.checked == parent.checked &&
- node.indeterminate == parent.indeterminate
- )
- return; // no need to update upwards
- if (node.checked == true) {
- this.$set(
- parent,
- "checked",
- parent[this.childrenKey].every(node => node.checked)
- );
- this.$set(parent, "indeterminate", !parent.checked);
- } else {
- this.$set(parent, "checked", false);
- this.$set(
- parent,
- "indeterminate",
- parent[this.childrenKey].some(
- node => node.checked || node.indeterminate
- )
- );
- }
- this.updateTreeUp(parentKey);
- },
- rebuildTree() {
- // only called when `data` prop changes
- const checkedNodes = this.getCheckedNodes();
- checkedNodes.forEach(node => {
- this.updateTreeDown(node, { checked: true });
- // propagate upwards
- const parentKey = this.flatState[node.nodeKey].parent;
- if (!parentKey && parentKey !== 0) return;
- const parent = this.flatState[parentKey].node;
- const childHasCheckSetter =
- typeof node.checked != "undefined" && node.checked;
- if (childHasCheckSetter && parent.checked != node.checked) {
- this.updateTreeUp(node.nodeKey); // update tree upwards
- }
- });
- },
- getSelectedNodes() {
- /* public API */
- return this.flatState
- .filter(obj => obj.node.selected)
- .map(obj => obj.node);
- },
- getCheckedNodes() {
- /* public API */
- return this.flatState
- .filter(obj => obj.node.checked)
- .map(obj => obj.node);
- },
- updateTreeDown(node, changes = {}) {
- for (let key in changes) {
- this.$set(node, key, changes[key]);
- }
- if (node[this.childrenKey]) {
- node[this.childrenKey].forEach(child => {
- this.updateTreeDown(child, changes);
- });
- }
- },
- handleSelect(nodeKey) {
- const node = this.flatState[nodeKey].node;
- if (!this.multiple) {
- // reset previously selected node
- const currentSelectedKey = this.flatState.findIndex(
- obj => obj.node.selected
- );
- if (currentSelectedKey >= 0 && currentSelectedKey !== nodeKey)
- this.$set(this.flatState[currentSelectedKey].node, "selected", false);
- }
- this.$set(node, "selected", !node.selected);
- this.$emit("on-select-change", this.getSelectedNodes());
- },
- handleCheck({ checked, nodeKey }) {
- const node = this.flatState[nodeKey].node;
- this.$set(node, "checked", checked);
- this.$set(node, "indeterminate", false);
- this.updateTreeUp(nodeKey); // propagate up
- this.updateTreeDown(node, { checked, indeterminate: false }); // reset `indeterminate` when going down
- this.$emit("on-check-change", this.getCheckedNodes());
- },
- handlerNodeEdit(nodeKey) {
- const node = this.flatState[nodeKey];
- this.onNodeEdit(node);
- },
- handlerNodeAppend(nodeKey) {
- const node = this.flatState[nodeKey];
- this.onNodeAppend(node);
- },
- handlerNodeRemove(nodeKey) {
- const root = this.flatState;
- const node = root[nodeKey];
- this.onNodeRemove(root, node, () => {
- if (node.parent || node.parent === 0) {
- const parentKey = node.parent;
- const parent = root.find(el => el.nodeKey === parentKey).node;
- const index = parent.children.indexOf(node.node);
- parent.children.splice(index, 1);
- } else {
- const index = this.stateTree.indexOf(node.node);
- this.stateTree.splice(index, 1);
- }
- });
- }
- },
- created() {
- this.flatState = this.compileFlatState();
- this.rebuildTree();
- },
- mounted() {
- this.$on("on-check", this.handleCheck);
- this.$on("on-selected", this.handleSelect);
- this.$on("toggle-expand", node => this.$emit("on-toggle-expand", node));
- if (this.editable) {
- this.$on("node-edit", this.handlerNodeEdit);
- this.$on("node-append", this.handlerNodeAppend);
- this.$on("node-remove", this.handlerNodeRemove);
- }
- }
- };
- </script>
- <style>
- .ivu-tree.ivu-tree-edit ul li {
- margin: 0 0 3px 0;
- }
- </style>
|