第一步 先創建組件目錄結構
第二步 封裝組件
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>
第三步引入組件並使用
效果預覽