第一步 先创建组件目录结构
第二步 封装组件
index.vue
<template>
<div class="tree-select-box">
<air-cell type="text" :label="label" :placeholder="placeholder" :downIcon="downIcon" v-model="text" @click="onSelect"></air-cell>
<mt-popup v-model="showPopup" position="bottom" :closeOnClickModal="closeOnClickModal">
<div class="popup-box">
<div class="popup-header">
<span @click="handleCancel">取消</span>
<span class="title">{{ label }}</span>
<span @click="handleConfirm">确认</span>
</div>
<div class="popup-content">
<Tree ref="tree" :data="treeData"/>
</div>
</div>
</mt-popup>
</div>
</template>
<script>
import { MessageBox } from 'mint-ui'
import { filterMultipleData } from '@/utils/air-service/commonMethods.js'
import { mannagerModule, airServiceModule } from '@/service/api/commonApi.js'
import { getStorage } from 'hr-utils'
import AirCell from '@/components/air-service/common/search/cell.vue'
import Tree from './tree.vue'
export default {
name: 'treeSelect',
components: {
AirCell,
Tree
},
props: {
label: {
type: String,
default: '所属部门'
},
downIcon: {
type: Boolean,
default: true
},
// 展示的文字内容
text: {
type: [String, Number],
default: ''
},
value: {
type: [String, Number],
default: ''
},
// 默认是部门
type: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请选择'
},
loading: {
type: Boolean,
default: false
},
data: {
type: Array,
default () {
return []
}
},
// 兼容不同字段的tree数据
treeParams: {
type: Object,
default () {
return {
id: 'id',
name: 'label',
children: 'children',
isLeaf: ''
}
}
},
// 是否展开子级
showTree: {
type: Boolean,
default: false
},
// 处理单选和多选
checkbox: {
type: Boolean,
default: false
},
// 是否可点击遮罩层关闭
closeOnClickModal: {
type: Boolean,
required: false,
default: function () {
return true
},
},
// 懒加载
lazy: {
type: Boolean,
default: false,
},
// 判断值是否能为空
require: {
type: Boolean,
default: false
},
// 判断是否全选
checkAll: {
type: Boolean,
default: false
}
},
provide() {
return {
treeParams: this.treeParams,
showTree: this.showTree,
checkbox: this.checkbox,
type: this.type,
lazy: this.lazy
}
},
data () {
return {
showPopup: false,
treeData: this.data,
// 用户信息
userinfo: getStorage('userinfo')
}
},
created () {
this.initData()
},
methods: {
// 初始话
async initData () {
try {
await this.getShowData()
if (this.checkAll) {
await this.$refs.tree.handleCheckAllChange(true)
this.setUpData()
}
} catch (error) {
}
},
// 选择内容
onSelect () {
this.showPopup = true
},
// 取消按钮
handleCancel () {
this.showPopup = false
this.$emit('cancel')
},
// 确认按钮
handleConfirm () {
this.selectData = this.$refs.tree.getSelectData()
if (this.require && !this.selectData.length) {
MessageBox('温馨提示', '请选择数据')
return
}
this.setUpData()
},
// 获取选中的数据并更新
setUpData () {
const { id, name } = this.treeParams
this.selectData = this.$refs.tree.getSelectData()
let selectId = []
let selectName = []
this.showPopup = false
this.selectData.map(item => {
selectId.push(item[id])
selectName.push(item[name])
})
this.upData(selectName.join(','), selectId.join(','))
},
// 更新值
upData (text, value) {
this.$emit('update:text', text)
this.$emit('update:value', value)
this.$emit('change', { text: text, value: value })
this.$nextTick(() => {
this.$refs.tree.setSelectData(value.split(','))
})
},
// 获取展示数据
async getShowData () {
try {
const { id, name, isLeaf } = this.treeParams
this.$emit('update:loading', true)
// 部门
if (this.type === 'department') {
const res = await mannagerModule.getUserMDept()
let deptData = res.data.deptList.map(item => {
return {
...item,
[isLeaf]: item[isLeaf] === '1' ? true : false
}
})
this.treeData = filterMultipleData([...deptData] || [])
// 部门单选设置默认值
if (!this.checkbox) {
let defaultData = deptData.find(item => item[id] === this.userinfo.pk_dept)
this.upData(defaultData[name], defaultData[id])
}
}
// 人员类别
if (this.type === 'category') {
const res = await airServiceModule.queryReferencePsn({
refType: 'pk_psncl',
test: 'test',
})
let list = res && res.data && res.data.body ? res.data.body : []
list = list.slice(1) || [] // 第一条是目录,剔除掉
this.treeData = list
}
} catch (error) {
console.log(error)
} finally {
}
}
}
}
</script>
<style lang="less" scoped>
.popup-box {
width: 100vw;
height: 50vh;
display: flex;
flex-direction: column;
.popup-header {
height: 0.9rem;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 0 0.3rem;
border-bottom: 1px solid #f2f2f2;
span {
color: #b4c4d4;
}
span.title {
color: #333333;
}
}
.popup-content {
flex-grow: 1;
overflow: auto;
padding: 0.3rem;
box-sizing: border-box;
}
}
</style>
tree.vue
<template>
<div class="popup-select-tree">
<div class="select-box" v-if="checkbox">
<span @click="handleCheckAllChange(true)">全选</span>
<span @click="handleCheckAllChange(false)">取消全选</span>
</div>
<tree-item
v-for="node in treeData"
:key="node[treeParams.id]"
:node="node"
:treeParams="treeParams"
@selectNode="selectNode">
</tree-item>
</div>
</template>
<script>
import treeItem from './tree-item.vue'
export default {
name: 'popup-select-tree',
components: {
treeItem
},
inject: ['treeParams', 'checkbox'],
props: {
data: {
type: Array,
default () {
return []
}
}
},
watch: {
data: {
handler(newVal, oldVal) {
this.treeData = newVal
},
deep: true,
}
},
data () {
return {
treeData: this.data,
// 模拟数据
data2: [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1'
}, {
id: 10,
label: '三级 1-1-2'
}]
}]
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1'
}, {
id: 6,
label: '二级 2-2'
}]
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1'
}, {
id: 8,
label: '二级 3-2',
children: [{
id: 11,
label: '三级 3-2-1'
}, {
id: 12,
label: '三级 3-2-2'
}, {
id: 13,
label: '三级 3-2-3'
}]
}]
}],
// 存放选择的数据
selects: [],
// 用于记录单选上一次选中的节点,作清除处理
oldNode: null
}
},
methods: {
// 仅支持父子级非严格关联情况,严格关联未开发
selectNode (node, type = '') {
const { children } = this.treeParams
let nodeChildren = node[children]
if (this.checkbox) { // 多选
// 判断是否已选中该节点,有则删除、没有则添加
if (node.checkbox && !type) {
this.$set(node, 'checkbox', false) // 取消选中状态
} else {
if (!node.checkbox) {
this.$set(node, 'checkbox', true) // 设置选中状态
}
// 选择父级节点默认将子节点也选中
if (nodeChildren && nodeChildren.length) {
nodeChildren.map(nodes => {
this.selectNode(nodes, 'add')
})
}
}
} else { // 单选
if (this.oldNode) {
this.$set(this.oldNode, 'checkbox', false)
}
this.$set(node, 'checkbox', true)
}
this.oldNode = node
},
handleSelectData (data = this.treeData) {
const { children } = this.treeParams
data.map(item => {
if (item.checkbox) {
this.selects.push({...item})
}
if (item[children] && item[children].length) {
this.handleSelectData(item[children])
}
})
},
// 获取选中状态的数据
getSelectData () {
this.selects = []
this.handleSelectData()
return this.selects
},
// 设置选中值
setSelectData (ids, data = this.treeData) {
const { id, children } = this.treeParams
data.map(node => {
let nodeChildren = node[children]
this.$set(node, 'checkbox', false)
if (ids.includes(node[id])) {
this.oldNode = node
this.$set(node, 'checkbox', true)
}
if (nodeChildren && nodeChildren.length) {
this.setSelectData(ids, nodeChildren)
}
})
},
// 获取到data内所有的ids、labels,用来做全选、反选处理
getAllKeyData (data = this.treeData, ids = [], labels = []) {
const { id, name, children} = this.treeParams
data.map(item => {
labels.push(item[name])
ids.push(item[id])
if (item[children] && item[children].length) {
this.getAllKeyData(item[children], ids, labels)
}
})
return {
labels,
ids
}
},
// 全选 和 反选
handleCheckAllChange (bool) {
if (bool) {
const { ids } = this.getAllKeyData()
this.setSelectData(ids)
} else {
this.setSelectData([])
}
}
}
}
</script>
<style lang="less" scoped>
.select-box {
margin-bottom: 15px;
display: flex;
span {
padding: 2px 5px;
color: #00BBBB;
cursor: pointer;
font-size: 14px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
user-select:none;
}
}
</style>
tree-item.vue
<template>
<div class="tree-item-box">
<div class="tree-item-list">
<span class="arrow-box">
<img
v-if="(node[treeParams.children] && node[treeParams.children].length) || node[treeParams.isLeaf]"
class="arrow"
:class="{'arrow-down': show}"
:src="!laoding.require ? arrowIcon : loadingIcon"
alt=""
@click="showchildren(node)">
</span>
<span class="name" @click="selectNode(node)">{{ node[treeParams.name] }}</span>
<img class="tick" :src="tickTrue" v-show="node.checkbox" alt="" @click="selectNode(node)">
</div>
<tree-item
v-show="show"
class="tree-children"
v-for="item in node[treeParams.children]"
:key="item[treeParams.id]"
:node="item"
@selectNode="selectNode"/>
</div>
</template>
<script>
/*
* showTree false为默认不展开子级 true默认展开子级
* checkbox false为单选 true为多选
*/
import { mannagerModule } from '@/service/api/commonApi.js'
export default {
name: 'tree-item',
inject: ['treeParams', 'showTree', 'checkbox', 'type', 'lazy'],
props: {
node: {
type: Object,
default() {
return {};
}
}
},
data () {
return {
laoding: {
require: false
},
show: this.showTree,
tick: require('../../../../../static/img/pages/airService/tick.png'),
tickTrue: require('../../../../../static/img/pages/airService/tick-true.png'),
arrowIcon: require('../../../../../static/img/pages/airService/arrow.png'),
loadingIcon: require('../../../../../static/img/pages/airService/loading.gif'),
}
},
methods: {
// 显示或隐藏子级
async showchildren (node) {
try {
console.log(node)
await this.hanldeChildrenNode(node)
this.show = !this.show
} catch (error) {
console.log(error)
}
},
// 处理子节点数据
async hanldeChildrenNode (node) {
try {
const { id, children, isLeaf } = this.treeParams
let childrenNode = []
// 懒加载情况下,更新node节点数据
if (this.lazy && node[isLeaf] && node[children] && !node[children].length) {
if (this.type === 'department') {
this.laoding.require = true
const res = await mannagerModule.getUserMDept({'pk_dept': node[id]})
childrenNode = res.data.deptList.map(item => {
return {
...item,
[isLeaf]: item[isLeaf] === '1' ? true : false,
[children]: []
}
})
}
this.updataChildrenNode(node, childrenNode)
}
} catch (error) {
console.log(error)
} finally {
this.laoding.require = false
}
},
// 更新子节点数据
updataChildrenNode (node, childrenNode) {
const { children, isLeaf } = this.treeParams
node[children] = childrenNode
this.$set(node, node[children], childrenNode)
if (!childrenNode.length) {
this.$set(node, isLeaf, false)
}
},
// 选择树节点
selectNode (node) {
this.$emit('selectNode', node)
}
}
}
</script>
<style lang="less" scoped>
.tree-item-box {
.tree-item-list {
display: flex;
align-items: center;
height: 0.5rem;
touch-action: none;
.arrow-box {
display: flex;
align-items: center;
width: 0.3rem;
.arrow {
width: 100%;
&.arrow-down {
transform: rotate(90deg);
}
}
}
.name {
margin-left: 0.1rem;
flex-grow: 1;
}
.tick {
width: 0.5rem;
margin-left: auto;
}
}
.tree-children {
margin-left: 0.2rem;
}
}
</style>
第三步引入组件并使用
效果预览