ztree 是一个很经典的基于jquey开发的树结构编辑展示UI组件库。
创建一个文件 ztree.vue,代码如下:
<template>
<div>
<div class="ztree vue-giant-tree" :id="ztreeId"></div>
<div class="treeBox">
<span slot="footer" class="dialog-footer">
<el-button
type="primary"
@click="pushHandle"
style="background: #e64e3f; border: none"
>推 送</el-button
>
</span>
</div>
</div>
</template>
<script>
import * as $ from "jquery";
if (!window.jQuery) {
window.jQuery = $;
}
require("@ztree/ztree_v3/js/jquery.ztree.all");
export default {
props: {
setting: {
type: Object,
require: false,
default: function () {
return {};
},
},
nodes: {
type: Array,
require: true,
default: function () {
return [];
},
},
},
data() {
return {
ztreeId: "ztree_" + parseInt(Math.random() * 1e10),
ztreeObj: null,
list: [],
ztreeSetting: {
view: {
showIcon: false, // default to hide icon
},
callback: {
onAsyncError: (...arg) => {
this.$emit("onAsyncError", ...arg);
},
onAsyncSuccess: (...arg) => {
this.$emit("onAsyncSuccess", ...arg);
},
onCheck: (...arg) => {
this.$emit("onCheck", ...arg);
},
onClick: (...arg) => {
this.$emit("onClick", ...arg);
},
onCollapse: (...arg) => {
this.$emit("onCollapse", ...arg);
},
onDblClick: (...arg) => {
this.$emit("onDblClick", ...arg);
},
onDrag: (...arg) => {
this.$emit("onDrag", ...arg);
},
onDragMove: (...arg) => {
this.$emit("onDragMove", ...arg);
},
onDrop: (...arg) => {
this.$emit("onDrop", ...arg);
},
onExpand: (...arg) => {
this.$emit("onExpand", ...arg);
},
onMouseDown: (...arg) => {
this.$emit("onMouseDown", ...arg);
},
onMouseUp: (...arg) => {
this.$emit("onMouseUp", ...arg);
},
onRemove: (...arg) => {
this.$emit("onRemove", ...arg);
},
onRename: (...arg) => {
this.$emit("onRename", ...arg);
},
onRightClick: (...arg) => {
this.$emit("onRightClick", ...arg);
},
},
},
};
},
methods: {
pushHandle() {
var zTree = $.fn.zTree.getZTreeObj(this.ztreeId);
var dataNodes = [],
nodes = zTree.getCheckedNodes(true);
var users = "";
if (0 === nodes.length) {
this.$alert("请选择", "提示", {
confirmButtonText: "确定",
callback: (action) => {},
});
return;
}
let count = 0;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].is_last == 1) {
dataNodes.push(nodes[i].id);
users += nodes[i].id + ",";
}
count++;
if (count >= nodes.length) {
if (dataNodes.length == 0) {
this.$alert("请选择人员", "提示", {
confirmButtonText: "确定",
callback: (action) => {},
});
return;
} else {
users = users.slice(0, -1);
this.$emit("pushHandleData", users);
}
}
}
},
},
watch: {
nodes: {
handler: function (nodes) {
this.list = nodes;
// update tree
if (this.ztreeObj) {
this.ztreeObj.destroy();
}
this.$nextTick(() => {
this.ztreeObj = $.fn.zTree.init(
$("#" + this.ztreeId),
Object.assign({}, this.ztreeSetting, this.setting),
this.list
);
// var zTree = $.fn.zTree.getZTreeObj(this.ztreeId);
// var datas = nodes.split(',');
// for (let i = 0; i < datas.length; i++) {
// var node = zTree.getNodesByParam("id", datas[i], null)[0];//根据id获取节点
// zTree.checkNode(node, true, true);//选中节点
// zTree.setChkDisabled(node, true);// 禁止勾选节点
// }
this.$emit("onCreated", this.ztreeObj);
});
},
deep: true,
immediate: true,
},
},
};
</script>
<style>
/* beauty ztree! */
.ztree {
text-align: left;
font-size: 14px;
}
.treeBox {
display: flex;
justify-content: center;
margin-top: 10px;
}
.vue-giant-tree li {
list-style-type: none;
white-space: nowrap;
outline: none;
}
.vue-giant-tree li ul {
position: relative;
padding: 0 0 0 20px;
margin: 0;
}
.vue-giant-tree .line:before {
position: absolute;
top: 0;
left: 10px;
height: 100%;
content: "";
border-right: 1px dotted #dbdbdb;
}
.vue-giant-tree .roots_docu:before,
.vue-giant-tree .roots_docu:after,
.vue-giant-tree .center_docu:before,
.vue-giant-tree .bottom_docu:before,
.vue-giant-tree .center_docu:after,
.vue-giant-tree .bottom_docu:after {
position: absolute;
content: "";
border: 0 dotted #dbdbdb;
}
.vue-giant-tree .roots_docu:before {
left: 10px;
height: 50%;
top: 50%;
border-left-width: 1px;
}
.vue-giant-tree .roots_docu:after {
top: 50%;
left: 11px;
width: 50%;
border-top-width: 1px;
}
.vue-giant-tree .center_docu:before {
left: 10px;
height: 100%;
border-left-width: 1px;
}
.vue-giant-tree .center_docu:after {
top: 50%;
left: 11px;
width: 50%;
border-top-width: 1px;
}
.vue-giant-tree .bottom_docu:before {
left: 10px;
height: 50%;
border-left-width: 1px;
}
.vue-giant-tree .bottom_docu:after {
top: 50%;
left: 11px;
width: 50%;
border-top-width: 1px;
}
.vue-giant-tree li a {
display: inline-block;
line-height: 22px;
height: 22px;
margin: 0;
cursor: pointer;
transition: none;
vertical-align: middle;
color: #555555;
}
.vue-giant-tree .node_name {
display: inline-block;
padding: 0 3px;
border-radius: 4px;
}
.vue-giant-tree .curSelectedNode .node_name {
color: #000;
background-color: #c9e9f7;
}
.vue-giant-tree .curSelectedNode_Edit {
height: 20px;
opacity: 0.8;
color: #000;
border: 1px #6cc2e8 solid;
background-color: #9dd6f0;
}
.vue-giant-tree .tmpTargetNode_inner {
opacity: 0.8;
color: #fff;
background-color: #4fcbf0;
filter: alpha(opacity=80);
}
.vue-giant-tree .rename {
font-size: 12px;
line-height: 22px;
width: 80px;
height: 22px;
margin: 0;
padding: 0;
vertical-align: top;
border: 0;
background: none;
}
.vue-giant-tree .button {
position: relative;
display: inline-block;
line-height: 22px;
height: 22px;
width: 22px;
cursor: pointer;
text-align: center;
vertical-align: middle;
}
.vue-giant-tree .button.edit {
color: #25ae88;
}
.vue-giant-tree .button.remove {
color: #cb4042;
}
.vue-giant-tree .button.chk {
position: relative;
width: 14px;
height: 14px;
margin: 0 4px 0 0;
border: 1px solid #d7dde4;
border-radius: 2px;
background: #fff;
}
.vue-giant-tree .chk.radio_true_full,
.vue-giant-tree .chk.radio_false_full,
.vue-giant-tree .chk.radio_true_full_focus,
.vue-giant-tree .chk.radio_false_full_focus,
.vue-giant-tree .chk.radio_false_disable,
.vue-giant-tree .chk.radio_true_disable,
.vue-giant-tree .chk.radio_true_part,
.vue-giant-tree .chk.radio_false_part,
.vue-giant-tree .chk.radio_true_part_focus,
.vue-giant-tree .chk.radio_false_part_focus {
border-radius: 8px;
}
.vue-giant-tree .button.chk:after {
position: absolute;
top: 1px;
left: 4px;
width: 4px;
height: 8px;
content: "";
transition: -webkit-transform 0.2s ease-in-out;
transition: transform 0.2s ease-in-out;
transition: transform 0.2s ease-in-out, -webkit-transform 0.2s ease-in-out;
-webkit-transform: rotate(0deg) scale(0);
transform: rotate(0deg) scale(0);
border-right: 2px solid #fff;
border-bottom: 2px solid #fff;
}
.vue-giant-tree .button.checkbox_false_full_focus {
border-color: #ccc;
}
.vue-giant-tree .button.checkbox_true_full,
.vue-giant-tree .button.checkbox_true_full_focus,
.vue-giant-tree .button.checkbox_true_part,
.vue-giant-tree .button.checkbox_true_part_focus,
.vue-giant-tree .button.checkbox_true_disable {
border-color: #39f;
background-color: #39f;
}
.vue-giant-tree .button.checkbox_true_full:after,
.vue-giant-tree .button.checkbox_true_full_focus:after,
.vue-giant-tree .button.checkbox_true_disable:after {
-webkit-transform: rotate(45deg) scale(1);
transform: rotate(45deg) scale(1);
}
.vue-giant-tree .button.checkbox_true_part:after,
.vue-giant-tree .button.checkbox_true_part_focus:after {
top: 5px;
left: 2px;
width: 10px;
height: 1px;
-webkit-transform: rotate(0deg) scale(1);
transform: rotate(0deg) scale(1);
border-right: 0;
}
.vue-giant-tree .button.radio_true_full,
.vue-giant-tree .chk.radio_true_full_focus,
.vue-giant-tree .chk.radio_true_part,
.vue-giant-tree .chk.radio_true_part_focus {
border-color: #39f;
}
.vue-giant-tree .button.radio_true_full:after,
.vue-giant-tree .chk.radio_true_full_focus:after,
.vue-giant-tree .chk.radio_true_part:after,
.vue-giant-tree .chk.radio_true_part_focus:after {
top: 3px;
left: 3px;
width: 8px;
-webkit-transform: rotate(0deg) scale(1);
transform: rotate(0deg) scale(1);
border: 0;
border-radius: 4px;
background: #39f;
}
.vue-giant-tree .button.checkbox_true_disable,
.vue-giant-tree .button.checkbox_false_disable,
.vue-giant-tree .chk.radio_false_disable,
.vue-giant-tree .chk.radio_true_disable {
cursor: not-allowed;
}
.vue-giant-tree .button.checkbox_false_disable {
background-color: #f3f3f3;
}
.vue-giant-tree .button.noline_close:before,
.vue-giant-tree .button.noline_open:before,
.vue-giant-tree .button.root_open:before,
.vue-giant-tree .button.root_close:before,
.vue-giant-tree .button.roots_open:before,
.vue-giant-tree .button.roots_close:before,
.vue-giant-tree .button.bottom_open:before,
.vue-giant-tree .button.bottom_close:before,
.vue-giant-tree .button.center_open:before,
.vue-giant-tree .button.center_close:before {
position: absolute;
top: 5px;
left: 5px;
content: "";
transition: -webkit-transform ease 0.3s;
transition: transform ease 0.3s;
transition: transform ease 0.3s, -webkit-transform ease 0.3s;
-webkit-transform: rotateZ(0deg);
transform: rotateZ(0deg);
-webkit-transform-origin: 25% 50%;
transform-origin: 25% 50%;
border: 6px solid;
border-color: transparent transparent transparent #666;
}
.vue-giant-tree .button.noline_open:before,
.vue-giant-tree .button.root_open:before,
.vue-giant-tree .button.roots_open:before,
.vue-giant-tree .button.bottom_open:before,
.vue-giant-tree .button.center_open:before {
-webkit-transform: rotateZ(90deg);
transform: rotateZ(90deg);
}
.vue-giant-tree .button.ico_loading {
margin-right: 2px;
background: url("data:image/gif;base64,R0lGODlhEAAQAKIGAMLY8YSx5HOm4Mjc88/g9Ofw+v///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAGACwAAAAAEAAQAAADMGi6RbUwGjKIXCAA016PgRBElAVlG/RdLOO0X9nK61W39qvqiwz5Ls/rRqrggsdkAgAh+QQFCgAGACwCAAAABwAFAAADD2hqELAmiFBIYY4MAutdCQAh+QQFCgAGACwGAAAABwAFAAADD1hU1kaDOKMYCGAGEeYFCQAh+QQFCgAGACwKAAIABQAHAAADEFhUZjSkKdZqBQG0IELDQAIAIfkEBQoABgAsCgAGAAUABwAAAxBoVlRKgyjmlAIBqCDCzUoCACH5BAUKAAYALAYACgAHAAUAAAMPaGpFtYYMAgJgLogA610JACH5BAUKAAYALAIACgAHAAUAAAMPCAHWFiI4o1ghZZJB5i0JACH5BAUKAAYALAAABgAFAAcAAAMQCAFmIaEp1motpDQySMNFAgA7")
0 center no-repeat;
}
.vue-giant-tree .tmpTargetzTree {
opacity: 0.8;
background-color: #2ea9df;
filter: alpha(opacity=80);
}
.vue-giant-tree .tmpzTreeMove_arrow {
position: absolute;
width: 18px;
height: 18px;
color: #4fcbf0;
}
</style>
<style>
ul.ztree.zTreeDragUL {
margin: 0;
padding: 0;
position: absolute;
overflow: hidden;
background-color: #dedede;
border: 1px #4fcbf0 dotted;
border-radius: 4px;
opacity: 0.7;
}
.zTreeMask {
position: absolute;
z-index: 10000;
opacity: 0;
background-color: #cfcfcf;
}
</style>
在使用的页面中引入
注意:从接口获取到的树形数据中的 pId 字段名 中间的 ‘I’ 是大写,支持非树形结构数据,只需 pId 和 id 都有,ztree根据id及pId层级关系构建树结构
<template>
<div style="position: relative; margin: 15px">
<el-button
type="primary"
icon="el-icon-document-copy"
size="small"
@click="openZtree"
>保存</el-button>
<el-dialog
title="通过组织机构选择"
:visible.sync="dialogZTreeVisible"
top="1%"
width="40%"
>
<!-- 操作 -->
<div class="box">
<div v-if="loading">
<zTreeLoadings></zTreeLoadings>
</div>
<div v-else>
<ZTree
:setting="setting"
:nodes="nodes"
@onClick="onClick"
@onCheck="onCheck"
@onCreated="handleCreated"
@pushHandleData="pushHandleData"
/>
</div>
</div>
</el-dialog>
<wp-message
v-if="message"
:message="message"
@close="closeMessage"
@isOk="closeMessage"
></wp-message>
</div>
</template>
<script>
import * as Api from "@/api/wp-questionnaire";// 接口文件
import WpMessage from "@/components/wp-message"; // 弹窗组件
import ZTree from "@/components/wp-common/zTree";
import zTreeLoadings from "@/components/wp-common/loading";
export default {
components: {
WpMessage,
ZTree,
zTreeLoadings
},
data() {
return {
dialogZTreeVisible: false,
nodes: [],
showIndex: 0,
ztreeObj: null,
setting: {
check: {
enable: true,
//这里设置是否显示复选框
chkStyle: "checkbox",
chkboxType: {
Y: "s",
N: "ps",
}, //设置复选框是否与 父/子 级相关联
},
data: {
simpleData: {
enable: true, //设置是否启用简单数据格式(zTree支持标准数据格式跟简单数据格式)
idKey: "id", //设置启用简单数据格式时id对应的属性名称
pidKey: "pId", //设置启用简单数据格式时parentId对应的属性名称,ztree根据id及pid层级关系构建树结构
rootPId: null, // 根节点的parentId设置为null
},
key: {
name: "name",
},
},
view: {
selectedMulti: true, //设置是否能够同时选中多个节点
showIcon: false, //设置是否显示节点图标
showLine: true, //设置是否显示节点与节点之间的连线
},
callback: {
onClick: function (e, treeId, treeNode) {
checkNode_user = treeNode;
},
},
},
loading: false
};
},
methods: {
// 推送数据
pushHandleData(val) {
let param = {
Id: id, //推送的数据条Id
userIds: val, //推送用户列表,多个用户以逗号分隔
};
let peosonNum = val.split(",");
// 调用推送的接口
Api.questionUserAdd(param).then((res) => {
if (res.data.code == 200) {
this.toMessage("推送成功!共推送" + peosonNum.length + "人");
this.dialogZTreeVisible = false;
// this.$eventBus.$emit("getTableDataList");// 其他页面刷新页面,如果不需要可不加
} else if (res.data.code == 504) {
this.$notify({
title: "提示",
message: "当前推送数据量过大,请稍后刷新查看!",
type: "warning",
duration: 2000,
});
this.dialogZTreeVisible = false;
} else {
this.$message({
type: "error",
message: res.msg,
});
}
});
},
// 获取组织树数据
getZtreeData() {
this.dialogZTreeVisible = true
this.loading = true
let param = {
type: 3,
id: String(this.currentQuertionId),
};
Api.searchUserTreeList(param).then((res) => {
if (res.data.code == 200) {
this.nodes = res.data.data;
this.loading = false
}
});
},
// 打开树形结构弹窗
openZtree() {
this.getZtreeData();
this.dialogZTreeVisible = true;
},
clickRemove(treeNode) {
console.log("remove", treeNode);
this.ztreeObj && this.ztreeObj.removeNode(treeNode);
},
onClick(evt, treeId, treeNode) {
// 点击事件
console.log(evt.type, treeNode);
},
onCheck(evt, treeId, treeNode) {
// 选中事件
console.log(evt.type, treeNode);
},
handleCreated(ztreeObj) {
this.ztreeObj = ztreeObj;
ztreeObj.expandNode(ztreeObj.getNodes()[0], true);
}
},
};
</script>
创建弹窗组件 index.vue:
其中 xhnrtp.png 就是个关闭图片
<template>
<div class="wp-message">
<div class="message-container">
<h4 class="message-header">
信息
<img src="@/assets/login/xhnrtp.png" alt="" @click="close" />
</h4>
<div class="message-main">
{{ message }}
</div>
<div style="text-align: center; margin-bottom: 10px">
<el-button
type="primary"
icon="el-icon-document-copy"
size="small"
@click="handelOk"
>确定</el-button
>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
default: "",
},
},
methods: {
close() {
this.$emit("close");
},
handelOk() {
this.$emit("isOk");
},
},
};
</script>
<style lang="scss" scoped>
.wp-message {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 9999;
background-color: #00000060;
.message-container {
width: 300px;
border-radius: 4px;
background-color: #fff;
position: absolute;
left: 50%;
top: 300px;
transform: translateX(-50%);
padding-bottom: 10px;
.message-header {
background-color: #eee;
font-weight: normal;
padding: 0px 20px;
border-radius: 4px 4px 0px 0px;
margin: 0px;
height: 40px;
line-height: 40px;
color: #333;
position: relative;
img {
position: absolute;
right: 20px;
top: 11px;
cursor: pointer;
}
}
.message-main {
height: 100px;
text-align: center;
color: #333;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 20px;
}
}
}
</style>
由于数据量大加载比较慢,可创建一个加载组件 loading.vue
<template>
<div class="box">
<img class="imgs" src="@/assets/loading.gif" />
</div>
</template>
<script>
export default {
methods: {}
}
</script>
<style scoped>
.box {
display: flex;
justify-content: center;
align-items: center;
}
.imgs {
width: 50px;
height: 50px;
}
</style>
其中的 loading.gif 就是个动态图。