"dependencies": {
"@better-scroll/core": "^2.4.2",
"core-js": "^3.8.3",
"vue": "^3.2.13",
},
{
value: '110000',
text: '北京市',
children: [
{
value: '110100',
text: '北京市',
children: [
{
value: '110101',
text: '东城区',
},
{
value: '110102',
text: '西城区',
},
{
value: '110103',
text: '崇文区',
},
{
value: '110104',
text: '宣武区',
},
{
value: '110105',
text: '朝阳区',
},
{
value: '110106',
text: '丰台区',
},
{
value: '110107',
text: '石景山区',
},
{
value: '110108',
text: '海淀区',
},
{
value: '110109',
text: '门头沟区',
},
{
value: '110111',
text: '房山区',
},
{
value: '110112',
text: '通州区',
},
{
value: '110113',
text: '顺义区',
},
{
value: '110114',
text: '昌平区',
},
{
value: '110115',
text: '大兴区',
},
{
value: '110116',
text: '怀柔区',
},
{
value: '110117',
text: '平谷区',
},
{
value: '110228',
text: '密云县',
},
{
value: '110229',
text: '延庆县',
},
{
value: '110230',
text: '其它区',
},
],
},
],
},
<template>
<div>
<Tree :list="list" @on-confirm="onConfirm"></Tree>
</div>
</template>
<script>
import {cityData} from './data.js'
import Tree from './components/tree.vue'
export default {
name: 'App',
components:{
Tree
},
data() {
return {
list: cityData
}
},
methods: {
onConfirm(data) {
console.log(data)
window.alert(`选中,${data[0].text},${data[0].value}`)
}
}
}
</script>
<template>
<div class="tree-container">
<div class="button-container">
<div @click="onLastLevel">返回上一级</div>
<div @click="onConfirm" v-if="selectAll">确定</div>
</div>
<div class="wrapper" ref="wrapper">
<div class="content">
<div class="tree-item">
<tree-item
v-for="item in dataList"
:key="item[value]"
:data="item"
:checkedList="checkedList"
@on-select="onSelect"
@on-show-children="onShowChildren"
></tree-item>
</div>
</div>
</div>
</div>
</template>
<script>
import BScroll from '@better-scroll/core';
import TreeItem from './tree-item.vue';
export default {
name: 'Tree',
components: {
TreeItem,
},
provide() {
return {
children: this.children,
value: this.value,
text: this.text,
selectAll: this.selectAll
};
},
props: {
list: {
type: Array,
default: () => [],
},
children: {
type: String,
default: 'children',
},
value: {
type: String,
default: 'value',
},
text: {
type: String,
default: 'text',
},
selectAll: {
type: Boolean,
default: false,
},
},
data() {
return {
dataList: [],
currentList: [],
checkedList: [],
currenParentId: -1,
treeItems: [],
scroll: null,
};
},
created() {
this.currentList = this.list.map((item) => {
item.parentId = -1;
const objValue = item[this.value];
if (item.children && item.children.length) {
item.children.forEach((it) => {
this.setParentId(objValue, it);
});
}
return item;
});
this.dataList = this.currentList;
},
mounted() {
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.wrapper, {
click: true,
probeType: 3,
});
});
console.log('mounted')
},
watch: {
currenParentId(id) {
console.log(id)
if (id) {
this.checkedList = [];
console.log('currenParentId');
}
},
checkedList(list) {
this.treeItems.forEach((tItem) => {
tItem.onChecked(list);
});
},
},
methods: {
onSelect(item, isAdd) {
if (isAdd) {
this.onAdd(item);
} else {
this.onSub(item);
}
},
onShowChildren(children) {
this.dataList = children;
this.onscrollTo(0);
},
onLastLevel() {
if (this.dataList[0].parentId > 0) {
let target = this.findParent(this.dataList[0].parentId, this.list);
const parentId = target[0].parentId;
this.currenParentId = parentId;
this.dataList = this.findParent(parentId, this.list, 'parentId');
this.onscrollTo(0)
} else {
this.currenParentId = -1;
this.dataList = this.currentList;
}
},
findParent(parentId, list, key) {
key ? '' : (key = this.value);
let target = [];
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item[key] === parentId) {
target.push(item);
} else {
if (item.children && item.children.length) {
let children = this.findParent(parentId, item.children, key) || [];
target.push(...children);
}
}
}
return target;
},
setParentId(parentId, obj) {
obj.parentId = parentId;
const objValue = obj[this.value];
if (obj.children && obj.children.length) {
obj.children.forEach((child) => {
this.setParentId(objValue, child);
});
}
},
onAdd(item) {
if (!this.selectAll) {
this.checkedList = [item];
this.onConfirm()
} else {
this.checkedList.push(item);
}
},
onSub(item) {
if (!this.selectAll) {
this.checkedList = [item];
} else {
this.checkedList = this.checkedList.filter((it) => {
return it[this.value] !== item[this.value];
});
}
},
onAddTreeItem(item) {
this.treeItems.push(item);
},
onConfirm() {
console.log(this.checkedList)
this.$emit('on-confirm', this.checkedList);
},
onscrollTo(x) {
this.scroll.scrollTo(x, 0, 0);
this.$nextTick(() => {
this.scroll.refresh();
});
},
},
};
</script>
<style scoped>
.tree-container {
height: 100%;
}
.button-container {
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
height: 44px;
border-bottom: 1px solid #ccc;
}
.button-container div {
height: 100%;
padding: 0 16px;
font-size: 14px;
line-height: 44px;
background-color: transparent;
border: none;
cursor: pointer;
}
.wrapper {
height: 40vh;
margin-top: 10px;
position: relative;
overflow: hidden;
}
</style>
<template>
<div class="tree-item-container">
<label
class="for-input-label"
:class="{ hasChecked: isChecked }"
v-if="selectAll"
>
<input
type="checkbox"
@change="onSelect($event)"
v-model="isChecked"
class="multi-checkbox"
/>
</label>
<label class="single-label" v-else
>选择
<input
type="checkbox"
@change="onSelect($event)"
v-model="isChecked"
class="single-input"
/>
</label>
<span @click="onShowChildren" class="desc-text">{{ data[text] }}</span>
</div>
</template>
<script>
export default {
name: 'TreeItem',
inject: ['children', 'value', 'text', 'selectAll'],
props: {
data: {
type: Object,
default: () => {},
},
checkedList: {
type: Array,
default: () => [],
},
},
data() {
return {
isChecked: false,
};
},
created() {
this.$parent.onAddTreeItem(this);
},
methods: {
onSelect(event) {
console.log('onSelect');
this.isChecked = event.currentTarget.checked;
this.$emit('on-select', this.data, this.isChecked);
},
onShowChildren() {
console.log('onShowChildren');
if (this.data[this.children] && this.data[this.children].length) {
this.$emit('on-show-children', this.data[this.children]);
}
},
onChecked(list) {
let flag = false;
list.forEach((item) => {
if (item[this.value] === this.data[this.value]) {
flag = true;
}
});
this.isChecked = flag;
},
},
};
</script>
<style scoped>
.tree-item-container {
display: flex;
align-items: center;
margin: 20px 0;
}
.for-input-label:before {
content: '\2713';
color: transparent;
display: inline-block;
border: 1px solid #ccc;
font-size: 14px;
line-height: 20px;
margin: -5px -5px 0 0;
height: 16px;
width: 16px;
text-align: center;
vertical-align: middle;
transition: color ease 0.3s;
border-radius: 2px;
}
.tree-item-container .for-input-label.hasChecked::before {
color: #fff;
background-color: #1989fa;
border-color: #1989fa;
}
input[type='checkbox'] {
visibility: hidden;
}
.desc-text {
font-size: 16px;
}
.single-label {
width: 60px;
margin-right: 20px;
border-radius: 2px;
text-align: center;
color: white;
background-color: #1989fa;
}
.single-input {
width: 0;
height: 0;
border: 0;
padding: 0;
margin: 0;
}
</style>