<!-- checkbox.vue -->
<template>
<label>
<span>
<input
type="checkbox"
:disabled="disabled"
:checked="currentValue"
@change="change">
</span>
<slot></slot>
</label>
</template>
<script>
import Emitter from '@/mixins/emitter.js';
export default {
name: 'iCheckbox',
mixins: [ Emitter ],
props: {
disabled: {
type: Boolean,
default: false
},
value: {
type: [String, Number, Boolean],
default: false
},
trueValue: {
type: [String, Number, Boolean],
default: true
},
falseValue: {
type: [String, Number, Boolean],
default: false
}
},
data () {
return {
currentValue: this.value
};
},
watch: {
value (val) {
if (val === this.trueValue || val === this.falseValue) {
this.updateModel();
} else {
throw 'Value should be trueValue or falseValue.';
}
}
},
methods: {
change (event) {
if (this.disabled) {
return false;
}
const checked = event.target.checked;
this.currentValue = checked;
const value = checked ? this.trueValue : this.falseValue;
this.$emit('input', value);
this.$emit('on-change', value);
this.dispatch('iFormItem', 'on-form-change', value);
},
updateModel () {
this.currentValue = this.value === this.trueValue;
}
}
}
</script>
<!-- src/components/tree/node.vue node.vue 是一個遞歸組件 -->
<template>
<ul class="tree-ul">
<li class="tree-li">
<span class="tree-expand" @click="handleExpand">
<span v-if="data.children && data.children.length && !data.expand">+</span>
<span v-if="data.children && data.children.length && data.expand">-</span>
</span>
<i-checkbox
v-if="showCheckbox"
:value="data.checked"
@input="handleCheck"
></i-checkbox>
<span>{{ data.title }}</span>
<!-- 遞歸組件有兩個必要條件:name 特性和終結條件 當 `data.children` 不存在或爲空數組時,遞歸也就停止了。 -->
<!-- data.expand 是否展開子節點 -->
<tree-node
v-if="data.expand"
v-for="(item, index) in data.children"
:key="index"
:data="item"
:show-checkbox="showCheckbox"
></tree-node>
</li>
</ul>
</template>
<script>
import iCheckbox from './checkbox.vue';
import { findComponentUpward } from '@/libs/assist.js';
export default {
name: 'TreeNode',
components: { iCheckbox },
props: {
data: {
type: Object,
default () {
return {};
}
},
showCheckbox: {
type: Boolean,
default: false
}
},
data () {
return {
tree: findComponentUpward(this, 'Tree')
}
},
methods: {
handleExpand () {
this.$set(this.data, 'expand', !this.data.expand);
if (this.tree) {
this.tree.emitEvent('on-toggle-expand', this.data);
}
},
handleCheck (checked) {
this.updateTreeDown(this.data, checked);
if (this.tree) {
this.tree.emitEvent('on-check-change', this.data);
console.log(this.tree);
}
},
updateTreeDown (data, checked) {
this.$set(data, 'checked', checked);
if (data.children && data.children.length) {
data.children.forEach(item => {
this.updateTreeDown(item, checked);
});
}
}
},
watch: {
'data.children': {
handler (data) {
if (data) {
const checkedAll = !data.some(item => !item.checked);
this.$set(this.data, 'checked', checkedAll);
}
},
deep: true
}
}
}
</script>
<style>
.tree-ul, .tree-li{
list-style: none;
padding-left: 10px;
}
.tree-expand{
cursor: pointer;
}
</style>
<template>
<div>
<tree-node
v-for="(item, index) in cloneData"
:key="index"
:data="item"
:show-checkbox="showCheckbox"
></tree-node>
</div>
</template>
<script>
import TreeNode from './node.vue';
import { deepCopy } from '@/libs/assist.js';
export default {
name: 'Tree',
components: { TreeNode },
props: {
data: {
type: Array,
default () {
return [];
}
},
showCheckbox: {
type: Boolean,
default: false
}
},
data () {
return {
cloneData: []
}
},
created () {
this.rebuildData();
},
watch: {
data () {
this.rebuildData();
}
},
methods: {
rebuildData () {
this.cloneData = deepCopy(this.data);
},
emitEvent (eventName, data) {
this.$emit(eventName, data, this.cloneData);
}
}
}
</script>