VUE-ElementUI 對話框多層級樹形控件,穿越框複雜業務實作
請忽略css,主要是邏輯,看圖,圖檔為子元件,左側樹形控件,中間選擇框,右側展示框

父元件
<template>
<div class="box">
<div class="ad-primary choose-btn" @click="chooseOne">請選擇人員</div>
<div class="ad-primary choose-btn" @click="chooseTwo">請選擇管理者</div>
<div>選擇人員{{attendanceOfficerNameList}}</div>
<div>選擇管理者{{attendanceOfficerNameListF}}</div>
<select-officer
@dialogFun="dialogOne"
:post-visible="dialogVisibleOne"
:post-person="kqPerson"
:post-frame="kqCount"
:post-user="kqCountu"></select-officer>
<select-officer
@dialogFun="dialogTwo"
:post-visible="dialogVisibleTwo"
:post-person="labelPerson"
:post-frame="labelCount"
:post-user="labelCountu"></select-officer>
</div>
</template>
<script>
import attendanceOfficer from "@/pages/common/attendanceOfficer"
export default {
name: 'attendanceAdd',
components: {
'select-officer': attendanceOfficer
},
data() {
return {
dialogVisibleOne: false,
dialogVisibleTwo: false,
kqPerson: [],
kqCount: 0,
kqCountu: 0,
labelPerson: [],
labelCount: 0,
labelCountu: 0,
attendanceOfficerIds: [],
attendanceOfficerNameList: "",
attendanceOfficerIdsF: [],
attendanceOfficerNameListF: "",
}
},
created() {
this.getDatas();
},
methods: {
/** 考勤類型清單 */
getDatas() {
function getQuery (variable) {
let code = window.location.search.substring(1)
//let enUrl = 'user=dev-test&pwd=11111111'
//let enUrls = encodeURIComponent(enUrl)
//console.log(enUrls)
let codes = decodeURIComponent(code)
let vars = codes.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return (false);
}
let that = this;
let u = getQuery('kqId');
//console.log('kq = '+u)
if (u) {
let params_show = {
//"requestId":"4333532244114",
"authToken": "12312",
"userToken": "26cea5f746ae4c7fbf7c3a4018b23f28",
"data": {
"kqId": u
}
}
this.http.post('www.centby.com/show', params_show).then(
res => {
if (res.data.kqPersons != null) {
that.kqPerson = res.data.kqPersons;
let j = 0;
let k = 0;
res.data.kqPersons.forEach(function (item) {
if (nodeType == 0) {
j++;
} else if (nodeType == 1) {
k++
}
});
that.kqCount = j;
that.kqCountu = k;
}
if (res.data.kqLiablePersons != null) {
that.labelPerson = res.data.kqLiablePersons;
let m = 0;
let n = 0;
res.data.kqLiablePersons.forEach(function (item) {
if (nodeType == 0) {
m++;
} else if (nodeType == 1) {
n++
}
});
that.labelCount = m;
that.labelCountu = n;
}
//console.log(JSON.stringify(res))
},
error => {
console.log(error.message)
}
)
}
},
chooseOne(){
this.dialogVisibleOne = true;
},
chooseTwo(){
this.dialogVisibleTwo = true;
},
dialogOne(data){
//console.log(data)
this.dialogVisibleOne = data.emita;
this.attendanceOfficerIds = data.emitb;
this.attendanceOfficerNameList = data.emitc;
},
dialogTwo(data){
//console.log(data)
this.dialogVisibleTwo = data.emita;
this.attendanceOfficerIdsF = data.emitb;
this.attendanceOfficerNameListF = data.emitc;
}
}
}
</script>
<style scoped>
.choose-btn {
width: 80px;
height: 30px;
line-height: 30px;
padding: 0 20px;
margin-bottom: 10px;
font-size: 14px;
font-weight: 600;
border-radius: 4px;
cursor: pointer;
}
</style>
子元件
<template>
<el-dialog
title="選擇人員"
align="left"
:visible.sync="dialogVisible"
width="50%"
:before-close="handleClose">
<div class="chosen-modal">
<div class="chosen-modal-left">
<div class="chosen-modal-hd">
<div class="chosen-modal-hd-search">
<i class="el-icon-search" @click="searchSelect"></i>
<input type="text" class="aui-input" placeholder="搜尋" v-model="searchWord" id="chosenSearch">
</div>
</div>
<div class="chosen-modal-bd" style="position: relative;">
<div class="chosen-left" style="width: 200px;">
<el-tree :data="treeData" :props="defaultProps" node-key="id" @node-click="handleNodeClick">
<span class="custom-tree-node" slot-scope="{ node, data }">
<i class="el-icon-folder-opened" v-show="data.nodeType == 0 ? true : false"></i>
<i class="el-icon-s-custom" v-show="data.nodeType == 0 ? false : true"></i>
<span>{{ node.label }}</span>
</span>
</el-tree>
</div>
<div class="chosen-right">
<el-checkbox v-model="checkAll" @change="handleCheckAllChange" :disabled="forbid">全選</el-checkbox>
<div style="margin: 15px 0;"></div>
<el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
<el-checkbox v-for="item in category" :label="item.id" :key="item.id" :disabled="forbid">
<i class="el-icon-folder-opened" v-show="item.nodeType == 0 ? true : false"></i>
<i class="el-icon-s-custom" v-show="item.nodeType == 0 ? false : true"></i>
{{item.name}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="chosen-modal-right">
<div class="chosen-modal-right-hd">
<p id="tipText">已選{{frameCount}}個架構,{{userCount}}名使用者</p>
</div>
<div class="chosen-modal-right-bd">
<div class="chosen-person-org" v-for="items in selectList" :key="items.id" @click="delList(items.id)">
<i class="el-icon-folder-opened" v-show="items.nodeType == 0 ? true : false"></i>
<i class="el-icon-s-custom" v-show="items.nodeType == 0 ? false : true"></i>
<span class="js-chosen-rs-text">{{items.name}}</span>
<i class="el-icon-close"></i>
</div>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" class="ad-primary" @click="next">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
//centby.com
export default {
name: 'attendanceOfficer',
props: {
postFrame: {
type: Number
},
postUser: {
type: Number
},
postVisible: {
type: Boolean
},
postPerson: {
type: Array,
default: () => []
}
},
data () {
return {
searchWord: "",
defaultProps: {
children: 'children',
label: 'label'
},
treeData: [],//樹形渲染
category: [],//選中框渲染
selectList: [],//展示框渲染
checkAll: false,//全選按鈕狀态
checkedCities: [],//選中框選中的 id 清單
listId:[],//選中框所有 id 清單
parentList: [],//目前樹形節點所有父級 id 清單包含自己
forbid: false,//選中框所有禁選狀态,預設可選
frameCount: 0,
userCount: 0,
attendanceOfficerIds: [],//選中的 id 傳給父元件
attendanceOfficerNameList: "",//選中字元串傳給父元件,做展示
reviewData: '',//展示框請求回顯資料
dialogVisible: false//對話框打開或關閉狀态
}
},
watch: {
postFrame: {
handler(newName, oldName) {
this.frameCount = newName;
},
immediate: true,
deep: true
},
postUser: {
handler(newName, oldName) {
this.userCount = newName;
},
immediate: true,
deep: true
},
postPerson: {
handler(newName, oldName) {
this.selectList = newName;
},
immediate: true,
deep: true
},
postVisible: {
handler(newName, oldName) {
this.dialogVisible = newName;
},
type: Boolean
}
},
created(){
this.treePool();
},
methods: {
handleClose(){
this.dialogVisible = false;
this.$emit('dialogFun', this.dialogVisible);
},
next() {
let list = this.selectList;
let arr = [];
list.forEach(item => {
this.attendanceOfficerIds.push(item.id);
arr.push(item.name);
})
this.attendanceOfficerNameList = arr.join(",");
console.log(this.attendanceOfficerIds)
console.log(this.attendanceOfficerNameList)
this.dialogVisible = false;
//對話框打開關閉狀态,選中 id 清單,選中 name 清單傳回給父元件
this.$emit('dialogFun', {emita:this.dialogVisible,emitb:this.attendanceOfficerIds,emitc:this.attendanceOfficerNameList});
},
treePool () {
//展示框資料
// this.selectList = [{
// id: '7387601',
// name: '科技部',
// nodeType: '0'
// }]
//樹形資料
/*
[{
"id": "0",
"label": "學生",
"nodeType": 0,
"orgType": 1,
"children": [
{
"id": "3736461431145987660",
"label": "國中部",
"nodeType": 0,
"orgType": 1,
"children": [
{
"id": "3736461431145987661",
"label": "2011級",
"nodeType": 0,
"orgType": 1,
"children": [
{
"id": "3736461431145987662",
"label": "3班",
"nodeType": 0,
"orgType": 1,
"children": [
{
"id": "3736461431145987663",
"label": "小牛奶",
"nodeType": 1,
"orgType": 1,
"children": null
},
{
"id": "3736461431145987663",
"label": "小牛奶",
"nodeType": 1,
"orgType": 1,
"children": null
}
]
}
]
}
]
}
]
}]
*/
let that = this;
let params = {
//"requestId":"4333532244114",
"authToken":"12312",
"userToken":"26cea5f746ae4c7fbf7c3a4018b23f28",
"data":{
}
}
this.http.post('www.centby.com/whole', params).then(
res => {
that.treeData = res.data;
//console.log(JSON.stringify(treeData))
},
error => {
console.log(error.message)
}
)
},
searchSelect() {
console.log(this.searchWord);
let that = this;
let keyWord = this.searchWord;
let params = {
//"requestId":"userToken1574677356105xRx1",
"authToken":"666",
"userToken":"666",
"data":{
"keyWord": keyWord
}
}
this.http.post('www.centby.com/word', params).then(
res => {
console.log(JSON.stringify(res))
that.treeData = res.data
},
error => {
console.log(error.message)
}
)
},
handleCheckAllChange(val) {
let all = JSON.parse(JSON.stringify(this.listId));
//console.log("all === "+all)
this.checkedCities = val ? all : [];
//this.isIndeterminate = false;
//console.log("list === "+this.checkedCities);
let that = this;
let categorys = JSON.parse(JSON.stringify(this.category));
if(val === true){
//全部展示
categorys.forEach(function(item){
categorys.find(function(s){
if(s.id === item.id){
//console.log(s)
that.selectList = that.selectList.concat([s])
//放入展示框,并去重
let k = {};
that.selectList = that.selectList.reduce(function (subitem, next) {
k[next.id] ? '' : k[next.id] = true && subitem.push(next);
return subitem;
}, []);
}
})
})
}else{
//取消展示
categorys.forEach(function(item){
for(let j = 0;j < that.selectList.length;j++){
if(that.selectList[j].id === item.id){
that.selectList.splice(j,1);
}
}
})
}
//計算展示框數量
let j = 0;
let k = 0;
that.selectList.forEach(function(item){
if(item.nodeType == 0){
j++;
}else if(item.nodeType == 1){
k++
}
});
that.frameCount = j;
that.userCount = k;
},
handleCheckedCitiesChange(value) {
//console.log("sub === "+JSON.stringify(value))
/*
//選中 id 清單資料示例
//checkedCities = [3736461431145987743]
//選擇框資料示例
category = [
{
"id": "3736461431145987660",
"name": "國中部",
"nodeType": 0
},
{
"id": "3736461431145987743",
"name": "國小部",
"nodeType": 0
}
]
* * */
let categorys = this.category;
console.log("value === " + this.checkedCities )
let checkedCount = value.length;
this.checkAll = checkedCount === this.category.length;
//this.isIndeterminate = checkedCount > 0 && checkedCount < this.category.length;
let that = this;
//console.log('list = '+this.listId)
let deepCopy = JSON.stringify(this.listId);
let unChecked = JSON.parse(deepCopy);
//選中ID展示
value.forEach(function (item) {
//console.log(item)
//添加到展示框
categorys.find(function(s){
if(s.id === item){
//console.log(s)
that.selectList = that.selectList.concat([s])
//放入展示框,并去重
let k = {};
that.selectList = that.selectList.reduce(function (subitem, next) {
k[next.id] ? '' : k[next.id] = true && subitem.push(next);
return subitem;
}, []);
}
})
//console.log('uncheck = '+JSON.stringify(unChecked))
//擷取不選中的id
for(let i = 0;i < unChecked.length;i++){
// /console.log(item)
if(unChecked[i] == item){
unChecked.splice(i,1);
}
}
})
//console.log('res = '+JSON.stringify(unChecked))
//把未選中的從展示框删除
unChecked.forEach(function(item){
for(let j = 0;j < that.selectList.length;j++){
if(that.selectList[j].id == item){
that.selectList.splice(j,1);
}
}
})
//計算展示框數量
let j = 0;
let k = 0;
that.selectList.forEach(function(item){
if(item.nodeType == 0){
j++;
}else if(item.nodeType == 1){
k++
}
});
that.frameCount = j;
that.userCount = k;
},
delList(val){
console.log('del '+JSON.stringify(val))
//console.log('forbid = '+ this.forbid)
let that = this;
//let checkedMenu = JSON.parse(JSON.stringify(this.checkedCities));
let categorys = JSON.parse(JSON.stringify(that.category));
//第一步先從展示框删除
for(let j = 0;j < that.selectList.length;j++){
if(that.selectList[j].id == val){
that.selectList.splice(j,1);
}
}
let permission = this.forbid;
if(permission === true){
//禁止選擇狀态
//第二步拿删除目前id後的展示框清單 和 所有樹形父級 id 清單對比
let hasOne = false;
let parent = JSON.parse(JSON.stringify(that.parentList));
let showList = JSON.parse(JSON.stringify(that.selectList));
console.log('after == '+ JSON.stringify(showList))
console.log('parent == '+ JSON.stringify(parent))
parent.forEach(function(item){
showList.forEach(function(show){
if(show.id == item){
hasOne = true;
}
})
})
//沒有父級,改變禁選狀态為可選,并重新渲染選擇框清單
if(hasOne === false){
//改變禁選狀态為可選
that.forbid = false;
//從展示框删除
for(let j = 0;j < that.selectList.length;j++){
if(that.selectList[j].id == val){
that.selectList.splice(j,1);
}
}
//重新渲染選中框
that.checkAll = false;
that.checkedCities = [];
that.selectList.forEach(function (item) {
//console.log(item)
categorys.find(function(s){
if(s.id === item.id){
//console.log(s)
that.checkedCities.push(item.id)
//選中等于目前所有選中框清單全選操作
if(that.checkedCities.length == categorys.length){
that.checkAll = true;
}
}
})
})
}
}else{
//非禁選狀态
//重新渲染選中框
for(let j = 0;j < that.checkedCities.length;j++){
if(that.checkedCities[j] == val){
that.checkedCities.splice(j,1);
that.checkAll = false;
}
}
}
//計算展示框數量
let j = 0;
let k = 0;
that.selectList.forEach(function(item){
if(item.nodeType == 0){
j++;
}else if(item.nodeType == 1){
k++
}
});
that.frameCount = j;
that.userCount = k;
},
handleNodeClick(data) {
//點選樹狀清單
//console.log(data);
/*
//選擇框資料示例
{
"code": 0,
"message": "調用成功",
"data": {
"parentIds": [],
"nodes": [
{
"id": "3736461431145987660",
"name": "國中部",
"nodeType": 0
},
{
"id": "3736461431145987743",
"name": "國小部",
"nodeType": 0
}
]
}
}
//選中 id 資料示例
checkedCities = [3736461431145987743]
//選擇框展示資料示例,展示框資料結構和選擇框資料結構相同,選擇框選中的清單為數組結構 id 清單
category = [
{
"id": "3736461431145987660",
"name": "國中部",
"nodeType": 0
},
{
"id": "3736461431145987743",
"name": "國小部",
"nodeType": 0
}
]
* * */
let that = this;
let idx = data.id;
let types = data.orgType;
let params = {
//"requestId":"userToken1574677356105xRx1",
"authToken":"666",
"userToken":"666",
"data":{
"orgId": idx,
"orgType": types
}
}
this.http.post('www.centby.com/info', params).then(
res => {
//console.log(res)
that.category = res.data.nodes;
//提取所有ID并儲存
let arr = [];
that.category.forEach(function(item){
//console.log(item.id)
arr.push(item.id)
});
that.listId = arr;
//所有id儲存,that.listId = ['3736461431145987600','3736461431145987601','3736461431145987602'];
//初始化并儲存目前所有樹形父級 id 清單包含自己,展示框删除操作會調用此資料
that.parentList = JSON.parse(JSON.stringify(res.data.parentIds));
console.log('parent = '+ JSON.stringify(that.parentList));
//初始化禁選判斷,禁選,全選按鈕,已選中
let permission = false;
that.forbid = false;
that.checkAll = false;
that.checkedCities = [];
//使用JSON轉換解決深拷貝問題
let parent = JSON.parse(JSON.stringify(that.parentList));
let showList = JSON.parse(JSON.stringify(that.selectList));
//每次點選樹狀清單,拿目前所有樹形父級 id 清單(parentIds)包含自己,和展示框清單 id 對比,如果有,選擇框展示全部全選并且禁止狀态
//展示框删除動作,會取此禁選狀态做展示框删除動作
parent.forEach(function(item){
showList.forEach(function(show){
if(show.id == item){
permission = true;
}
})
})
if(permission == true){
//禁止選擇操作
that.checkedCities = that.listId;
that.checkAll = true;
that.forbid = true;
// that.selectList.forEach(function (item) {
// if(item.id == idx){
// that.checkedCities = that.listId;
// that.checkAll = true;
// that.forbid = true;
// }
// })
}else{
//不禁止狀态回顯渲染
that.selectList.forEach(function (item) {
//console.log(item)
that.category.find(function(s){
if(s.id === item.id){
//console.log(s)
that.checkedCities.push(item.id)
//選中等于目前所有選中框清單全選操作
if(that.checkedCities.length == that.listId.length){
that.checkAll = true;
}
}
})
})
}
},
error => {
console.log(error.message)
}
)
},
}
}
</script>
<style scoped lang="scss">
.box >>> .el-checkbox {
display: block;
}
.chosen-modal {
display: flex;
height: 598px;
padding: 15px;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
.chosen-modal-left {
width: 430px;
}
.chosen-modal-hd {
padding: 20px;
display: flex;
border-top: 1px solid #dddee1;
border-right: 1px solid #dddee1;
border-left: 1px solid #dddee1;
}
.chosen-modal-hd .chosen-modal-hd-search {
position: relative;
flex: 1;
}
.chosen-modal-hd .chosen-modal-hd-search i {
width: 32px;
height: 32px;
line-height: 32px;
font-size: 16px;
text-align: center;
color: #80848f;
position: absolute;
right: 0;
z-index: 1;
cursor: pointer;
}
.aui-input {
display: inline-block;
width: 100%;
height: 32px;
line-height: 1.5;
padding: 4px 32px 4px 7px;
box-sizing: border-box;
font-size: 12px;
border: 1px solid #dddee1;
border-radius: 4px;
color: #6e7d8f;
background-color: #fff;
background-image: none;
position: relative;
cursor: text;
transition: border .2s ease-in-out,background .2s ease-in-out,box-shadow .2s ease-in-out;
}
.chosen-modal-bd {
display: flex;
height: 526px;
border: 1px solid #dddee1;
}
.chosen-left {
margin-top: -1px;
width: 200px;
min-width: 200px;
height: 525px;
overflow-y: auto;
position: absolute;
background: #fff;
z-index: 2;
overflow-x: hidden;
border-top: 1px solid #dddee1;
border-right: 1px solid #dddee1;
}
.chosen-left-device {
margin-top: -1px;
width: 200px;
min-width: 200px;
height: 525px;
overflow-y: auto;
position: absolute;
background: #fff;
z-index: 2;
overflow-x: hidden;
border-top: 1px solid #dddee1;
}
.chosen-right {
flex: 1;
padding-left: 200px;
z-index: 1;
overflow-y: scroll;
}
.chosen-right .el-checkbox {
padding: 10px 15px;
color: #0cb181;
}
.aui-checkbox-label {
align-items: center;
width: 100%;
white-space: normal;
word-wrap: break-word;
word-break: break-all;
margin-bottom: 0;
line-height: 1.3;
}
.chosen-modal-right {
flex: 1;
border: 1px solid #dddee1;
margin-left: 20px;
}
.chosen-modal-right-hd {
padding: 20px;
}
.chosen-modal-right-bd {
height: 538px;
overflow: auto;
}
.chosen-person-org {
display: flex;
padding: 6px 20px;
height: 20px;
align-items: center;
}
.chosen-modal-right-bd>div>i {
font-size: 12px;
color: #0cb181;
margin-right: 5px;
align-self: baseline;
position: relative;
top: 6px;
}
.chosen-modal-right-bd>div>span {
flex: 1;
word-wrap: break-word;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
word-break: break-all;
overflow: hidden;
}
</style>