2017-04-15 67 views
0

我刚开始使用Vue,并试图可视化嵌套列表。与Vue.js不确定的复选框

列表项应该包含三态复选框: 当一个子项被选中时,父项的复选框应该变成'不确定'。当所有的子复选框被选中时,父复选框也应该被选中。 当父项复选框被选中时,所有的子项复选框(也嵌套更深的)应该也被选中。

我有一种工作解决方案(检查此pen或下面的代码),但复选框逻辑仍然有缺陷。在这个例子中,选中的框为绿色,不确定的框为橙色,未选中的为红色。

我已经用尽了想法如何解决它。有人可以通过Vue了解如何实现这一点吗?

'use strict'; 
Vue.component("book-chapter", Vue.extend({ 
    name: "book-chapter", 
    props: ["data", "current-depth"], 
    data: function() { 
    return { 
     checked: this.data.checked, 
     indeterminate: this.data.indeterminate || false 
    }; 
    }, 
    methods: { 
    isChecked: function() { 
     return this.checked && !this.indeterminate; 
    }, 
    isIndeterminate: function(){ 
     return this.indeterminate; 
    }, 
    toggleCheckbox: function(eventData) { 
     if (this.currentDepth > 0){ 

     if (!this.data.children) { 
      this.checked != this.children 
     } else { 
      this.indeterminate = !this.indeterminate; 
     } 
     } 

     if (eventData) { 
     // fired by nested chapter 
     this.$emit('checked', eventData); 

     } else { 
     // fired by top level chapter 
     this.checked = !this.checked; 
     this.$emit('checked', { 
      data: this.data 
     }); 
     } 
    }, 
    isRootObject: function() { 
     return this.currentDepth === 0; 
    }, 
    isChild: function() { 
     return this.currentDepth === 2; 
    }, 
    isGrandChild: function() { 
     return this.currentDepth > 2; 
    } 
    }, 
    template: ` 
    <div class='book__chapters'> 
    <div 
     class='book__chapter' 
     v-bind:class="{ 'book__chapter--sub': isChild(), 'book__chapter--subsub': isGrandChild() }" 
     v-show='!isRootObject()'> 
     <div class='book__chapter__color'></div> 
     <div 
     class='book__chapter__content' 
     v-bind:class="{ 'book__chapter__content--sub': isChild(), 'book__chapter__content--subsub': isGrandChild() }"> 
     <div class='book__chapter__title'> 
      <span class='book__chapter__title__text'>{{data.title}}</span> 
     </div> 
     <div class='book__chapter__checkbox triple-checkbox'> 
      <div class='indeterminatecheckbox'> 
       <div 
        class='icon' 
        @click.stop="toggleCheckbox()" 
        v-bind:class="{'icon--checkbox-checked': isChecked(), 'icon--checkbox-unchecked': !isChecked(), 'icon--checkbox-indeterminate': isIndeterminate()}"> 
       </div> 
      </div> 
     </div> 
     </div> 
    </div> 
    <book-chapter 
     ref='chapter' 
     :current-depth='currentDepth + 1' 
     v-for='child in data.children' 
     key='child.id' 
     @checked='toggleCheckbox(arguments[0])' 
     :data='child'> 
    </book-chapter> 
</div> 
` 
})); 

Vue.component("book", Vue.extend({ 
    name: "book", 
    props: ["data"], 
    template: ` 
    <div class='book'> 
     <book-chapter 
     :data='this.data' 
     :currentDepth='0'> 
     </book-chapter> 
    </div> 
` 
})); 

var parent = new Vue({ 
    el: "#container", 
    data: function() { 
    return { 
     book: {} 
    }; 
    }, 
    mounted: function() { 
    this.book = { 
     "title": "Book", 
     "children": [{ 
     "title": "1 First title", 
     "children": [{ 
      "title": "1.1 Subtitle" 
     }, { 
      "title": "1.2 Subtitle" 
     }] 
     }, { 
     "title": "2 Second title", 
     "children": [{ 
      "title": "2.1 Subtitle", 
      "children": [{ 
      "title": "2.1.1 Sub-Sub title" 
      }, { 
      "title": "2.1.2 Another sub-sub title" 
      }] 
     }] 
     }] 
    } 
    } 
}); 

回答

1

更新:修复了@PhillSlevin发现的错误。见pen这里

检查this pen,这是你想达到什么?
我认为你可以使用eventbusvuex来解决这个问题,
如果你把每一节都当作一个组件处理。

'use strict'; 
 

 
var bus = new Vue(); 
 

 
var book = { 
 
    "title": "Book", 
 
    "children": [{ 
 
    "title": "1 First title", 
 
    "children": [{ 
 
     "title": "1.1 Subtitle" 
 
    }, { 
 
     "title": "1.2 Subtitle" 
 
    }] 
 
    }, { 
 
    "title": "2 Second title", 
 
    "children": [{ 
 
     "title": "2.1 Subtitle", 
 
     "children": [{ 
 
     "title": "2.1.1 Sub-Sub title" 
 
     }, { 
 
     "title": "2.1.2 Another sub-sub title" 
 
     }] 
 
    }] 
 
    }] 
 
}; 
 

 
Vue.component('book', { 
 
    template: ` 
 
<div class="book__chapter"> 
 
    <p :class="'book__title ' + status" @click="clickEvent">{{title}} {{parent}}</p> 
 
    <book v-for="child in children" :key="child" :info="child"></book> 
 
</div> 
 
`, 
 
    props: ['info'], 
 
    data() { 
 
    return { 
 
     parent: this.info.parent, 
 
     title: this.info.title, 
 
     children: [], 
 
     status: this.info.status, 
 
    }; 
 
    }, 
 
    created() { 
 
    const info = this.info; 
 
    if(info.children) { 
 
     info.children.forEach(child => { 
 
     child.status = "unchecked"; 
 
     // use title as ID 
 
     child.parent = info.title; 
 
     }); 
 
     this.children = info.children; 
 
    } 
 
    }, 
 
    mounted() { 
 
    const vm = this; 
 
    bus.$on('upside', (payload) => { 
 
     const targetArr = vm.children.filter((child) => child.title === payload.from); 
 
     if (targetArr.length === 1) { 
 
     const target = targetArr[0]; 
 
     target.status = payload.status; 
 
     if (vm.children.every(ele => ele.status === 'checked')) { 
 
      vm.status = 'checked'; 
 
     } else if (vm.children.every(ele => ele.status === 'unchecked')) { 
 
      vm.status = 'unchecked'; 
 
     } else { 
 
      vm.status = 'indeterminate'; 
 
     } 
 
     bus.$emit('upside', { 
 
      from: vm.title, 
 
      status: vm.status, 
 
     }); 
 
     } 
 
    }); 
 
    
 
    bus.$on('downside', (payload) => { 
 
     if (payload.from === this.parent) { 
 
     if (payload.status === 'checked') { 
 
      vm.status = 'checked'; 
 
      vm.children.forEach(child => child.status = 'checked'); 
 
     } else if (payload.status === 'unchecked') { 
 
      vm.status = 'unchecked'; 
 
      vm.children.forEach(child => child.status = 'unchecked') 
 
     } 
 
     bus.$emit('downside', { 
 
      from: vm.title, 
 
      status: vm.status, 
 
     }) 
 
     } 
 
    }); 
 
    }, 
 
    methods: { 
 
    clickEvent() { 
 
     if (this.status === 'checked') { 
 
     this.status = 'unchecked'; 
 
     this.children.forEach(child => child.status = 'unchecked'); 
 
     } else { 
 
     this.status = 'checked'; 
 
     this.children.forEach(child => child.status = 'checked'); 
 
     } 
 
     
 
     const vm = this; 
 
     bus.$emit('upside', { 
 
     from: vm.title, 
 
     status: vm.status, 
 
     }); 
 
     bus.$emit('downside', { 
 
     from: vm.title, 
 
     status: vm.status, 
 
     }); 
 
    }, 
 
    } 
 
}); 
 

 
var parent = new Vue({ 
 
    el: "#container", 
 
    data: function() { 
 
    return { 
 
     book 
 
    }; 
 
    }, 
 
});
.book__title.unchecked::after { 
 
    content: '□'; 
 
} 
 

 
.book__title.indeterminate::after { 
 
    content: '△'; 
 
} 
 

 
.book__title.checked::after { 
 
    content: '■'; 
 
} 
 

 
.book__chapter { 
 
    display: block; 
 
    position: reletive; 
 
    margin-left: 40px; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.js"></script> 
 
<div id="container"> 
 
    <book :info="book" :parent="'container'"></book> 
 
</div>

+1

谢谢您的回答!在分析您的解决方案时,我遇到了一个错误: 当您第一次检查第一个父项并且之后取消选中其中一个子项时,父项状态被错误地更新为“未选中”。这是由于儿童的状态没有被正确更新造成的。 请参阅下面的[笔](https://codepen.io/phillslevin/pen/mmVLzO)修复 – PhillSlevin

+0

固定!谢谢。 – choasia