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-buttontype="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 treeif (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-buttontype="primary"icon="el-icon-document-copy"size="small"@click="openZtree">保存</el-button><el-dialogtitle="通过组织机构选择":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-messagev-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, //推送的数据条IduserIds: 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 = truethis.loading = truelet 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-buttontype="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 就是个动态图。