diff --git a/public/static/plugs/layui_exts/cascader.js b/public/static/plugs/layui_exts/cascader.js
deleted file mode 100644
index 229b233af..000000000
--- a/public/static/plugs/layui_exts/cascader.js
+++ /dev/null
@@ -1,2085 +0,0 @@
-/**
- * 仿element-ui,级联选择器
- * 已实现单选、多选、无关联选择
- * 其他功能:组件禁用、节点禁用、自定义属性、自定义空面板提示,自定义无选择时的提示、多选标签折叠、回显、搜索、动态加载、最大选中数量限制、禁用项固定等操作。
- * element-ui没有的功能:最大选中数量限制、禁用项固定
- * author: yixiaco
- * gitee: https://gitee.com/yixiacoco/lay_cascader
- * github: https://github.com/yixiaco/lay_cascader
- */
-layui.define(["jquery"], function (exports) {
- var $ = layui.jquery;
-
- /**
- * 级联各项节点对象
- * @param data 原始对象信息
- * @param cascader 级联对象
- * @param level 层级,从0开始
- * @param parentNode 父节点对象
- * @constructor
- */
- function Node(data, cascader, level, parentNode) {
- this.data = data;
- this.cascader = cascader;
- this.config = cascader.config;
- this.props = cascader.props;
- this.level = level;
- this.parentNode = parentNode;
- // 引入的icon图标
- this.icons = cascader.icons;
- //该节点是否被选中 0:未选中,1:选中,2:不定
- this._checked = 0;
- // 是否正在加载中
- this._loading = false;
- // 每个Node的唯一标识
- this.nodeId = cascader.data.nodeId++;
- }
-
- Node.prototype = {
- constructor: Node,
- /** 最顶级父节点 */
- get topParentNode() {
- return !this.parentNode && this || this.topParentNode;
- },
- /** 子节点 */
- childrenNode: undefined,
- get loading() {
- return this._loading;
- },
- set loading(loading) {
- var $li = this.$li;
- if ($li) {
- var rightIcon = this.icons.right;
- var loadingIcon = this.icons.loading;
- var $i = $li.find('i');
- if (loading) {
- $i.addClass(loadingIcon);
- $i.removeClass(rightIcon);
- } else {
- $i.addClass(rightIcon);
- $i.removeClass(loadingIcon);
- }
- }
- return this._loading = loading;
- },
- /** 当前节点的显示文本 */
- get label() {
- return this.data[this.props.label];
- },
- /** 当前节点的值 */
- get value() {
- return this.data[this.props.value];
- },
- /** 是否禁用 */
- get disabled() {
- var multiple = this.props.multiple;
- var maxSize = this.config.maxSize;
- var checkedNodeIds = this.cascader.data.checkedNodeIds;
- var disabledName = this.props.disabled;
- var checkStrictly = this.props.checkStrictly;
- // 检查是否超过最大值限制
- if (multiple && maxSize !== 0) {
- if (checkedNodeIds.length >= maxSize && checkedNodeIds.indexOf(this.nodeId) === -1) {
- // 如果是关联的多选,需要查询叶子节点是否有被选中的项
- if (!checkStrictly) {
- var leafChildren = this.getAllLeafChildren();
- var nodeIds = leafChildren.map(function (value) {
- return value.nodeId
- });
- // 如果叶子节点不包含,则直接返回true
- if (!nodeIds.some(function (nodeId) {
- return checkedNodeIds.indexOf(nodeId) !== -1;
- })) {
- return true;
- }
- } else {
- return true;
- }
- }
- }
- if (!checkStrictly) {
- var path = this.path;
- return path.some(function (node) {
- return node.data[disabledName];
- });
- } else {
- return this.data[disabledName];
- }
- },
- /** 子节点数据 */
- get children() {
- return this.data[this.props.children];
- },
- set children(children) {
- this.data[this.props.children] = children;
- },
- /** 叶子节点 */
- get leaf() {
- var leaf = this.data[this.props.leaf];
- if (typeof leaf === 'boolean') {
- return leaf;
- }
- // 如果children不为空,则判断是否是子节点
- if (this.children) {
- return this.children.length <= 0;
- }
- return true;
- },
- /** 当前单选值 */
- get activeNodeId() {
- return this.cascader.data.activeNodeId;
- },
- /** 当前复选框值 */
- get checkedNodeIds() {
- return this.cascader.data.checkedNodeIds;
- },
- /** 路径 */
- get path() {
- var parentNode = this.parentNode;
- if (parentNode) {
- return parentNode.path.concat([this]);
- } else {
- return [this];
- }
- },
- /** 是否正在搜索中 */
- get isFiltering() {
- return this.cascader.isFiltering;
- },
- /** 输入框的tag标签 */
- get $tag() {
- var cascader = this.cascader;
- var showAllLevels = this.config.showAllLevels;
- var disabled = this.config.disabled;
- var nodeDisabled = this.disabled;
- var disabledFixed = this.config.disabledFixed;
-
- var label = this.getPathLabel(showAllLevels);
- var $tag = cascader.get$tag(label, !disabled && (!nodeDisabled || !disabledFixed));
- var self = this;
- $tag.find('i').click(function (event) {
- event.stopPropagation();
- self.selectedValue();
- cascader.removeTag(self.value, self);
- });
- return $tag;
- },
- /**
- * 完整路径的标签
- * @param showAllLevels
- * @returns {string}
- */
- getPathLabel: function (showAllLevels) {
- var path = this.path;
- var separator = this.config.separator;
-
- var label;
- if (showAllLevels) {
- label = path.map(function (node) {
- return node.label;
- }).join(separator);
- } else {
- label = path[path.length - 1].label;
- }
- return label;
- },
- /**
- * 初始化
- */
- init: function () {
- var multiple = this.props.multiple;
- var checkStrictly = this.props.checkStrictly;
- var fromIcon = this.icons.from;
- var rightIcon = this.icons.right;
- var icon = '';
- var label = this.label;
- if (!this.leaf) {
- icon = rightIcon;
- }
- this.$li = $('
');
-
- // 节点渲染
- if (!multiple && !checkStrictly) {
- this._renderRadio();
- } else if (!multiple && checkStrictly) {
- this._renderRadioCheckStrictly();
- } else if (multiple && !checkStrictly) {
- this._renderMultiple();
- } else if (multiple && checkStrictly) {
- this._renderMultipleCheckStrictly();
- }
- },
- /**
- * 初始化可搜索li
- */
- initSuggestionLi: function () {
- var label = this.getPathLabel(true);
- this.$suggestionLi = $('' + label + '');
- // 节点渲染
- this._renderFiltering();
- },
- /**
- * 绑定到菜单中
- * @param $list li节点
- */
- bind: function ($list) {
- this.init();
- $list.append(this.$li);
- },
- /**
- * 绑定可搜索到列表中
- * @param $list
- */
- bindSuggestion: function ($list) {
- this.initSuggestionLi();
- $list.append(this.$suggestionLi);
- },
- /**
- * 可搜索渲染
- * @private
- */
- _renderFiltering: function () {
- var $li = this.$suggestionLi;
- var nodeId = this.nodeId;
- var fromIcon = this.icons.from;
- var okIcon = this.icons.ok;
- var self = this;
- var cascader = this.cascader;
- var multiple = this.props.multiple;
-
- var icon = '';
- $li.click(function (event) {
- event.stopPropagation();
- self.selectedValue();
- if (multiple) {
- if (self.checkedNodeIds.indexOf(nodeId) === -1) {
- $li.removeClass('is-checked');
- $li.find('.el-icon-check').remove();
- } else {
- $li.addClass('is-checked');
- $li.append(icon);
- }
- } else {
- // 关闭面板
- cascader.close();
- }
- });
-
- if (multiple && self.checkedNodeIds.indexOf(nodeId) !== -1
- || !multiple && self.activeNodeId === nodeId) {
- $li.addClass('is-checked');
- $li.append(icon)
- }
- },
- /**
- * 单选&&关联
- * @private
- */
- _renderRadio: function () {
- var $li = this.$li;
- var nodeId = this.nodeId;
- var fromIcon = this.icons.from;
- var okIcon = this.icons.ok;
- var level = this.level;
- var leaf = this.leaf;
- var self = this;
- var cascader = this.cascader;
- var activeNode = this.cascader.data.activeNode;
- var parentNode = this.parentNode;
-
- if (self.activeNodeId && activeNode.path.some(function (node) {
- return node.nodeId === nodeId;
- })) {
- if (self.activeNodeId === nodeId) {
- $li.prepend('');
- }
- $li.addClass('is-active');
- $li.addClass('in-checked-path');
- }
-
- // 是否禁用
- if (this.disabled) {
- $li.addClass('is-disabled');
- return;
- }
-
- $li.addClass('is-selectable');
-
- if (parentNode) {
- parentNode.$li.siblings().removeClass('in-active-path');
- parentNode.$li.addClass('in-active-path');
- }
-
- // 触发下一个节点
- this._liClick(function (event) {
- event.stopPropagation();
- var childrenNode = self.childrenNode;
- if (leaf && event.type === 'click') {
- self.selectedValue();
- // 关闭面板
- cascader.close();
- }
- // 添加下级菜单
- cascader._appendMenu(childrenNode, level + 1, self);
- });
- },
- /**
- * 单选&&非关联
- * @private
- */
- _renderRadioCheckStrictly: function () {
- var $li = this.$li;
- var nodeId = this.nodeId;
- var level = this.level;
- var leaf = this.leaf;
- var self = this;
- var cascader = this.cascader;
- var activeNode = cascader.data.activeNode;
- var parentNode = this.parentNode;
-
- $li.addClass('is-selectable');
- // 任意一级单选
- var $radio = $('');
- this.$radio = $radio;
- $li.prepend($radio);
- if (parentNode) {
- parentNode.$li.siblings().removeClass('in-active-path');
- parentNode.$li.addClass('in-active-path');
- }
-
- // 触发下一个节点
- this._liClick(function (event) {
- event.stopPropagation();
- var childrenNode = self.childrenNode;
- if (!self.disabled && leaf && event.type === 'click') {
- self.selectedValue();
- }
- // 添加下级菜单
- cascader._appendMenu(childrenNode, level + 1, self);
- });
-
- if (self.activeNodeId && activeNode.path.some(function (node) {
- return node.nodeId === nodeId;
- })) {
- if (self.activeNodeId === nodeId) {
- $radio.find('.el-radio__input').addClass('is-checked');
- }
- $li.addClass('is-active');
- $li.addClass('in-checked-path');
- }
-
- if (this.disabled) {
- $radio.addClass('is-disabled');
- $radio.find('.el-radio__input').addClass('is-disabled');
- return;
- }
- // 选中事件
- $radio.click(function (event) {
- event.preventDefault();
- !leaf && self.selectedValue();
- });
- },
- /**
- * 多选&&关联
- * @private
- */
- _renderMultiple: function () {
- var $li = this.$li;
- var level = this.level;
- var leaf = this.leaf;
- var self = this;
- var cascader = this.cascader;
- var checked = this._checked;
- var parentNode = this.parentNode;
-
- $li.addClass('el-cascader-node');
-
- // 多选框
- var $checked = $('');
- this.$checked = $checked;
- $li.prepend($checked);
-
- // 渲染
- if (checked === 1) {
- this.$checked.find('.el-checkbox__input').addClass('is-checked');
- } else if (checked === 2) {
- this.$checked.find('.el-checkbox__input').addClass('is-indeterminate');
- }
-
- if (parentNode) {
- parentNode.$li.siblings().removeClass('in-active-path');
- parentNode.$li.addClass('in-active-path');
- }
-
- // 触发下一个节点
- this._liClick(function (event) {
- event.stopPropagation();
- var childrenNode = self.childrenNode;
- if (!self.disabled && leaf && event.type === 'click') {
- // 最后一级就默认选择
- self.selectedValue();
- }
- // 添加下级菜单
- cascader._appendMenu(childrenNode, level + 1, self);
- });
-
- if (this.disabled) {
- $li.addClass('is-disabled');
- $checked.addClass('is-disabled');
- $checked.find('.el-checkbox__input').addClass('is-disabled');
- return;
- }
-
- // 选中事件
- $checked.click(function (event) {
- event.preventDefault();
- if (!leaf) {
- var childrenNode = self.childrenNode;
- self.selectedValue();
- cascader._appendMenu(childrenNode, level + 1, self);
- }
- });
- },
- /**
- * 多选&&非关联
- * @private
- */
- _renderMultipleCheckStrictly: function () {
- var $li = this.$li;
- var level = this.level;
- var leaf = this.leaf;
- var self = this;
- var cascader = this.cascader;
- var checkedNodeIds = cascader.data.checkedNodeIds;
- var checkedNodes = cascader.data.checkedNodes;
- var nodeId = this.nodeId;
- var parentNode = this.parentNode;
-
- $li.addClass('el-cascader-node is-selectable');
-
- // 多选框
- var $checked = $('');
- this.$checked = $checked;
- $li.prepend($checked);
-
- // 渲染
- var exist = checkedNodes.some(function (node) {
- return node.path.some(function (node) {
- return node.nodeId === nodeId;
- })
- });
- if (exist) {
- $li.addClass('in-checked-path');
- if (checkedNodeIds.indexOf(nodeId) !== -1) {
- this.$checked.find('.el-checkbox__input').addClass('is-checked');
- }
- }
-
- if (parentNode) {
- parentNode.$li.siblings().removeClass('in-active-path');
- parentNode.$li.addClass('in-active-path');
- }
-
- // 触发下一个节点
- this._liClick(function (event) {
- event.stopPropagation();
- var childrenNode = self.childrenNode;
- if (!self.disabled && leaf && event.type === 'click') {
- // 最后一级就默认选择
- self.selectedValue();
- }
- // 添加下级菜单
- cascader._appendMenu(childrenNode, level + 1, self);
- });
-
- if (this.disabled) {
- $checked.addClass('is-disabled');
- $checked.find('.el-checkbox__input').addClass('is-disabled');
- return;
- }
- // 选中事件
- $checked.click(function (event) {
- event.preventDefault();
- if (!leaf) {
- self.selectedValue();
- var childrenNode = self.childrenNode;
- // 添加下级菜单
- cascader._appendMenu(childrenNode, level + 1, self);
- }
- });
- },
- /**
- * 向上传递
- * @param callback 执行方法,如果返回false,则中断执行
- * @param advance 是否先执行一次
- * @param self 自身
- */
- transferParent: function (callback, advance, self) {
- if (!self) {
- self = this;
- }
- if (this !== self || advance) {
- var goOn = callback && callback(this);
- if (goOn === false) {
- return;
- }
- }
- this.parentNode && this.parentNode.transferParent(callback, advance, self);
- },
- /**
- * 向下传递
- * @param callback 执行的方法,如果返回false,则中断执行
- * @param advance 是否先执行一次
- * @param self 自身
- */
- transferChildren: function (callback, advance, self) {
- if (!self) {
- self = this;
- }
- if (this !== self || advance) {
- var goOn = callback && callback(this);
- if (goOn === false) {
- return;
- }
- }
- var children = this.getChildren();
- if (children && children.length > 0) {
- for (var index in children) {
- children[index].transferChildren(callback, advance, self);
- }
- }
- },
- /**
- * 设置级联值
- */
- selectedValue: function () {
- var nodeId = this.nodeId;
- var cascader = this.cascader;
- var multiple = this.props.multiple;
- var checkStrictly = this.props.checkStrictly;
- var leaf = this.leaf;
- if (!multiple && (leaf || checkStrictly)) {
- cascader._setActiveValue(nodeId, this);
- } else if (multiple) {
- var checkedNodeIds = cascader.data.checkedNodeIds;
- var checkedNodes = cascader.data.checkedNodes;
- var disabledFixed = this.config.disabledFixed;
- var paths;
- if (checkStrictly) {
- var index = checkedNodeIds.indexOf(nodeId);
- if (index === -1) {
- paths = checkedNodes.concat([this]);
- } else {
- paths = checkedNodes.concat();
- paths.splice(index, 1);
- }
- } else {
- var allLeafChildren = this.getAllLeafChildren();
- var checked;
- if (this._checked !== 1 && disabledFixed) {
- checked = this._getMultipleChecked(allLeafChildren);
- } else {
- checked = this._checked;
- }
- if (checked === 1) {
- // 选中->未选中
- paths = checkedNodes.filter(function (node1) {
- return !allLeafChildren.some(function (node2) {
- return node1.nodeId === node2.nodeId;
- });
- });
- } else {
- // 未选中、部分选中->选中
- var add = allLeafChildren.filter(function (node) {
- return checkedNodeIds.indexOf(node.nodeId) === -1;
- });
- paths = checkedNodes.concat(add);
- }
- }
- var nodeIds = paths.map(function (node) {
- return node.nodeId;
- });
- cascader._setCheckedValue(nodeIds, paths);
- }
- },
- _liLoad: function (event, callback) {
- var leaf = this.leaf;
- var lazy = this.props.lazy;
- var lazyLoad = this.props.lazyLoad;
- var children = this.children;
- var self = this;
- var cascader = this.cascader;
- var level = this.level;
- var multiple = this.props.multiple;
- var checkStrictly = this.props.checkStrictly;
- if (!leaf && (!children || children.length === 0) && lazy) {
- if (!self.loading) {
- self.loading = true;
- lazyLoad(self, function (nodes) {
- self.loading = false;
- self.setChildren(cascader.initNodes(nodes, level + 1, self));
- self.children = nodes;
- callback && callback(event);
- // 多选&关联时,重新同步下父级节点的样式
- multiple && !checkStrictly && self.transferParent(function (node) {
- node.syncStyle();
- }, true);
- });
- }
- } else {
- callback && callback(event);
- }
- },
- /**
- * 点击li事件
- * @param callback
- * @private
- */
- _liClick: function (callback) {
- var leaf = this.leaf;
- var $li = this.$li;
- var self = this;
-
- function load(event) {
- self._liLoad(event, callback);
- }
-
- if (this.props.expandTrigger === "click" || leaf) {
- $li.click(load);
- }
- if (this.props.expandTrigger === "hover") {
- $li.mouseenter(load);
- }
- },
- setChildren: function (children) {
- this.childrenNode = children;
- },
- getChildren: function () {
- return this.childrenNode;
- },
- /**
- * 同步样式
- */
- syncStyle: function () {
- var multiple = this.props.multiple;
- var checkStrictly = this.props.checkStrictly;
- if (multiple) {
- //多选
- if (checkStrictly) {
- this._sync.syncMultipleCheckStrictly(this);
- } else {
- this._sync.syncMultiple(this);
- }
- } else {
- //单选
- if (checkStrictly) {
- this._sync.syncRadioCheckStrictly(this);
- } else {
- this._sync.syncRadio(this);
- }
- }
- },
- /**
- * 同步本节点样式
- */
- _sync: {
- /**
- * 同步单选关联样式
- */
- syncRadio: function (self) {
- var $li = self.$li;
- var fromIcon = self.icons.from;
- var okIcon = self.icons.ok;
- var multiple = self.props.multiple;
- var checkStrictly = self.props.checkStrictly;
- var nodeId = self.nodeId;
- if (!$li || multiple || checkStrictly) {
- return;
- }
- var activeNode = self.cascader.data.activeNode;
- if (self.activeNodeId === nodeId) {
- var ok = $li.find('.' + okIcon);
- if (ok.length === 0) {
- $li.prepend('');
- }
- } else {
- $li.find('.' + okIcon).remove();
- }
- if (activeNode && activeNode.path.some(function (node) {
- return node.nodeId === nodeId;
- })) {
- $li.addClass('is-active');
- $li.addClass('in-checked-path');
- } else {
- $li.removeClass('is-active');
- $li.removeClass('in-checked-path');
- }
- },
- /**
- * 同步单选非关联样式
- */
- syncRadioCheckStrictly: function (self) {
- var $li = self.$li;
- var checkStrictly = self.props.checkStrictly;
- var multiple = self.props.multiple;
- if (!$li || multiple || !checkStrictly) {
- return;
- }
- var $radio = self.$radio;
- var activeNode = self.cascader.data.activeNode;
- var nodeId = self.nodeId;
- if (self.activeNodeId === nodeId) {
- $radio.find('.el-radio__input').addClass('is-checked');
- } else {
- $radio.find('.el-radio__input').removeClass('is-checked');
- }
- if (activeNode && activeNode.path.some(function (node) {
- return node.nodeId === nodeId;
- })) {
- $li.addClass('is-active');
- $li.addClass('in-checked-path');
- } else {
- $li.removeClass('is-active');
- $li.removeClass('in-checked-path');
- }
- },
- /**
- * 同步多选关联样式
- */
- syncMultiple: function (self) {
- var $li = self.$li;
- var checkStrictly = self.props.checkStrictly;
- var multiple = self.props.multiple;
- var disabledFixed = self.config.disabledFixed;
- if (!multiple || checkStrictly) {
- return;
- }
- var allLeafChildren = self.getAllLeafChildren(disabledFixed);
- // 全部未选中 0
- // 全部选中 1
- // 部分选中 2
- var checked = self._getMultipleChecked(allLeafChildren);
- self._checked = checked;
- if (!$li) {
- return;
- }
- var $checkbox = self.$checked.find('.el-checkbox__input');
- if (checked === 0) {
- $checkbox.removeClass('is-checked');
- $checkbox.removeClass('is-indeterminate');
- } else if (checked === 1) {
- $checkbox.removeClass('is-indeterminate');
- $checkbox.addClass('is-checked');
- } else if (checked === 2) {
- $checkbox.removeClass('is-checked');
- $checkbox.addClass('is-indeterminate');
- }
- },
- /**
- * 同步多选非关联样式
- */
- syncMultipleCheckStrictly: function (self) {
- var $li = self.$li;
- var checkStrictly = self.props.checkStrictly;
- var multiple = self.props.multiple;
- if (!$li || !multiple || !checkStrictly) {
- return;
- }
- var checkedNodes = self.cascader.data.checkedNodes;
- var checkedNodeIds = self.checkedNodeIds;
- var nodeId = self.nodeId;
- var exist = checkedNodes.some(function (node) {
- return node.path.some(function (node) {
- return node.nodeId === nodeId;
- });
- });
- var $checkbox = self.$checked.find('.el-checkbox__input');
- if (checkedNodeIds.some(function (checkedNodeId) {
- return checkedNodeId === nodeId;
- })) {
- // 选中
- $checkbox.addClass('is-checked');
- } else {
- // 未选中
- $checkbox.removeClass('is-checked');
- }
- if (exist) {
- $li.addClass('in-checked-path');
- } else {
- $li.removeClass('in-checked-path');
- }
- }
- },
- /**
- * 获取叶子节点value值
- * @param disabled 是否包含禁用,默认不包含
- * @returns {Node[]|*[]}
- */
- getAllLeafChildren: function (disabled) {
- var leaf = this.leaf;
- if (leaf) {
- return [this];
- } else {
- var leafs = [];
- this.transferChildren(function (node) {
- if (node.disabled && !disabled) {
- return false;
- }
- node.leaf && leafs.push(node);
- });
- return leafs;
- }
- },
- /**
- * 展开当前节点
- */
- expandPanel: function () {
- var path = this.path;
- var cascader = this.cascader;
- path.forEach(function (node, index, array) {
- if (index !== array.length - 1) {
- var childrenNode = node.childrenNode;
- cascader._appendMenu(childrenNode, node.level + 1, node.parentNode);
- }
- });
- },
- _getMultipleChecked: function (leafNodes) {
- var cascader = this.cascader;
- var checkedNodeIds = cascader.data.checkedNodeIds;
- var allLeafIds = leafNodes.map(function (node) {
- return node.nodeId;
- });
- // 全部未选中 0
- // 全部选中 1
- // 部分选中 2
- var checked = 0;
- for (var i = 0; i < allLeafIds.length; i++) {
- var child = allLeafIds[i];
- if (checked === 2) {
- break;
- }
- if (checkedNodeIds.indexOf(child) !== -1) {
- if (i > 0 && checked !== 1) {
- checked = 2;
- } else {
- checked = 1;
- }
- } else {
- // 当全部选中时,则改为部分选中,否则全部未选中
- checked = checked === 1 ? 2 : 0;
- }
- }
- return checked;
- }
- };
-
- function Cascader(config) {
- this.config = $.extend(true, {
- elem: '', //绑定元素
- value: null, //预设值
- options: [], //可选项数据源,键名可通过 Props 属性配置
- empty: '暂无数据', //无匹配选项时的内容
- placeholder: '请选择',//输入框占位文本
- disabled: false, //是否禁用
- clearable: false, //是否支持清空选项
- showAllLevels: true, //输入框中是否显示选中值的完整路径
- collapseTags: false, //多选模式下是否折叠Tag
- minCollapseTagsNumber: 1, //最小折叠标签数
- separator: ' / ', //选项分隔符
- filterable: false, //是否可搜索选项
- filterMethod: function (node, keyword) {
- return node.path.some(function (node) {
- return node.label.indexOf(keyword) !== -1;
- });
- }, //自定义搜索逻辑,第一个参数是节点node,第二个参数是搜索关键词keyword,通过返回布尔值表示是否命中
- debounce: 300, //搜索关键词输入的去抖延迟,毫秒
- beforeFilter: function (value) {
- return true;
- },//筛选之前的钩子,参数为输入的值,若返回 false,则停止筛选
- popperClass: '', // 自定义浮层类名 string
- extendClass: false, //继承class样式
- extendStyle: false, //继承style样式
- disabledFixed: false, //固定禁用项,使禁用项不被清理删除,禁用项只能通过函数添加或初始值添加,默认禁用项不可被函数或初始值添加
- maxSize: 0, // 多选选中的最大数量,0表示不限制
- props: {
- strictMode: false, //严格模式,设置value严格按照层级结构.例如:[[1,2,3],[1,2,4]]
- expandTrigger: 'click', //次级菜单的展开方式 string click / hover 'click'
- multiple: false, //是否多选 boolean - false
- checkStrictly: false, //是否严格的遵守父子节点不互相关联 boolean - false
- lazy: false, //是否动态加载子节点,需与 lazyLoad 方法结合使用 boolean - false
- lazyLoad: function (node, resolve) {
- }, //加载动态数据的方法,仅在 lazy 为 true 时有效 function(node, resolve),node为当前点击的节点,resolve为数据加载完成的回调(必须调用)
- value: 'value', //指定选项的值为选项对象的某个属性值 string — 'value'
- label: 'label', //指定选项标签为选项对象的某个属性值 string — 'label'
- children: 'children', //指定选项的子选项为选项对象的某个属性值 string — 'children'
- disabled: 'disabled', //指定选项的禁用为选项对象的某个属性值 string — 'disabled'
- leaf: 'leaf' //指定选项的叶子节点的标志位为选项对象的某个属性值 string — 'leaf'
- }
- }, config);
- this.data = {
- nodeId: 1, //nodeId的自增值
- nodes: [], //存储Node对象
- menuData: [], //压入菜单的数据
- activeNodeId: null, //存放NodeId
- activeNode: null, //存放Node
- checkedNodeIds: [], //存放多个NodeId
- checkedNodes: [] //存放多个Node
- };
- // 面板是否展开
- this.showPanel = false;
- this.event = {
- // 值变更事件
- change: [],
- // 打开事件
- open: [],
- // 关闭事件
- close: []
- }
- // 是否正在搜索中
- this.filtering = false;
- // 初始化
- this._init();
- // 面板关闭事件id
- this.closeEventId = 0;
- // 是否进入maxSize模式
- this._maxSizeMode = false;
- }
-
- Cascader.prototype = {
- constructor: Cascader,
- get props() {
- return this.config.props;
- },
- get isFiltering() {
- return this.filtering;
- },
- set isFiltering(filtering) {
- if (this.filtering === filtering) {
- return;
- }
- this.filtering = !!filtering;
- var $panel = this.$panel;
- if (this.filtering) {
- $panel.find('.el-cascader-panel').hide();
- $panel.find('.el-cascader__suggestion-panel').show();
- } else {
- $panel.find('.el-cascader-panel').show();
- $panel.find('.el-cascader__suggestion-panel').hide();
- this.$tagsInput && this.$tagsInput.val('')
- }
- },
- set maxSizeMode(maxSizeMode) {
- if (this._maxSizeMode !== maxSizeMode) {
- this._maxSizeMode = maxSizeMode;
- this.refreshMenu();
- }
- },
- icons: {
- from: 'layui-icon',
- down: 'layui-icon-down',
- close: 'layui-icon-close',
- right: 'layui-icon-right',
- ok: 'layui-icon-ok',
- loading: 'layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop'
- },
- // 初始化
- _init: function () {
- this._checkConfig();
- // 初始化输入框
- this._initInput();
- // 初始化面板
- this._initPanel();
- // 初始化选项值
- this.setOptions(this.config.options);
- var self = this;
- // 监听滚动条
- $(window).scroll(function () {
- self._resetXY();
- });
- // 监听窗口
- $(window).resize(function () {
- self._resetXY();
- });
- // 点击事件,展开面板
- this.$div.click(function (event) {
- if (self.config.disabled) {
- return;
- }
- var show = self.showPanel;
- if (!show) {
- self.open();
- } else {
- self.close();
- }
- });
- },
- /**
- * 检查配置
- * @private
- */
- _checkConfig: function () {
- var elem = this.config.elem;
- if (!elem || $(elem).length === 0) {
- throw new Error("缺少elem节点选择器");
- }
- var maxSize = this.config.maxSize;
- if (typeof maxSize !== 'number' || maxSize < 0) {
- throw new Error("maxSize应是一个大于等于0的有效的number值");
- }
- if (!Array.isArray(this.config.options)) {
- throw new Error("options不是一个有效的数组");
- }
- },
- /**
- * 初始化根目录
- * @private
- */
- _initRoot: function () {
- var lazy = this.props.lazy;
- var lazyLoad = this.props.lazyLoad;
- var self = this;
- var nodes = this.data.nodes;
- if (nodes.length > 0 || !lazy) {
- this._appendMenu(nodes, 0);
- } else if (lazy) {
- this._appendMenu(nodes, 0);
- lazyLoad({
- root: true,
- level: 0
- }, function (nodes) {
- self.data.nodes = self.initNodes(nodes, 0, null);
- self._appendMenu(self.data.nodes, 0);
- });
- }
- },
- /**
- * 设置选项值
- * @param options
- */
- setOptions: function (options) {
- this.config.options = options;
- // 初始化节点
- this.data.nodes = this.initNodes(options, 0, null);
- // 初始化根目录
- this._initRoot();
- // 初始化值
- this.setValue(this.config.value);
- },
- // 面板定位
- _resetXY: function () {
- var $div = this.$div;
- var offset = $div.offset();
- var $panel = this.$panel;
- if ($panel) {
- var windowHeight = window.innerHeight;
- var windowWidth = window.innerWidth;
- var panelHeight = $panel.height();
- var panelWidth = $panel.width();
- var divHeight = $div.height();
- var boundingClientRect = $div[0].getBoundingClientRect();
- var $arrow = $panel.find('.popper__arrow');
-
- // 距离右边界的偏差值
- var offsetDiff = Math.min(windowWidth - boundingClientRect.x - panelWidth - 5, 0);
-
- var bottomHeight = windowHeight - (boundingClientRect.top + divHeight);
- if (bottomHeight < panelHeight && boundingClientRect.top > panelHeight + 20) {
- $panel.attr('x-placement', 'top-start')
- // 向上
- $panel.css({
- top: offset.top - 20 - panelHeight + 'px',
- left: offset.left + offsetDiff + 'px'
- });
- } else {
- $panel.attr('x-placement', 'bottom-start');
- // 距离底部边界的偏差值
- var yOffset = Math.max(panelHeight - (windowHeight - boundingClientRect.y - divHeight - 15), 0);
- // 向下
- $panel.css({
- top: offset.top + divHeight - yOffset + 'px',
- left: offset.left + offsetDiff + 'px'
- });
- }
- // 箭头偏移
- $arrow.css("left", 35 - offsetDiff + "px");
- }
- },
- get $menus() {
- return this.$panel && this.$panel.find('.el-cascader-panel .el-cascader-menu');
- },
- // 初始化输入框
- _initInput: function () {
- var $e = $(this.config.elem);
- var self = this;
- // 当绑定的元素带有value属性,并且对象未设置值时,设置一个初始值
- if (this.config.value === null && $e.attr('value')) {
- this.config.value = $e.attr('value');
- }
- var placeholder = this.config.placeholder;
- var fromIcon = this.icons.from;
- var downIcon = this.icons.down;
- var multiple = this.props.multiple;
- var extendClass = this.config.extendClass;
- var extendStyle = this.config.extendStyle;
-
- this.$div = $('');
- if (extendStyle) {
- var style = $e.attr('style');
- if (style) {
- this.$div.attr('style', style);
- }
- }
- if (extendClass) {
- var className = $e.attr('class');
- if (className) {
- className.split(' ').forEach(function (name) {
- self.$div.addClass(name);
- });
- }
- }
- this.$input = $('' +
- '' +
- '' +
- '' +
- '' +
- '' +
- '
')
- this.$div.append(this.$input);
- this.$inputRow = this.$input.find('.el-input__inner');
- // 多选标签
- if (multiple) {
- this.$tags = $('');
- this.$div.append(this.$tags);
- }
- this._initHideElement($e);
- // 替换元素
- $e.replaceWith(this.$div);
- this.$icon = this.$input.find('i');
- this._initFilterableInputEvent();
- this.disabled(this.config.disabled);
- },
- /**
- * 初始化隐藏元素input,主要用于layui的表单验证
- * @param $e
- * @private
- */
- _initHideElement: function ($e) {
- // 保存原始元素
- var attributes = $e[0].attributes;
- var $input = $('');
- var keys = Object.keys(attributes);
- for (var key in keys) {
- var attribute = attributes[key];
- $input.attr(attribute.name, attribute.value);
- }
- $input.hide();
- $input.attr('type', 'hidden')
- this.$ec = $input;
- $e.before($input);
- },
- /**
- * 初始化可搜索监听事件
- * @private
- */
- _initFilterableInputEvent: function () {
- var filterable = this.config.filterable;
- if (!filterable) {
- return;
- }
- var timeoutID;
- var multiple = this.props.multiple;
- var debounce = this.config.debounce;
- var placeholder = this.config.placeholder;
- var beforeFilter = this.config.beforeFilter;
- var filterMethod = this.config.filterMethod;
- var checkStrictly = this.props.checkStrictly;
- var self = this;
-
- function filter(event) {
- var input = this;
- if (timeoutID) {
- clearTimeout(timeoutID);
- }
- timeoutID = setTimeout(function () {
- timeoutID = null;
- var val = $(input).val();
- if (!val) {
- self.isFiltering = false;
- return;
- }
- self.open();
- if (typeof beforeFilter === 'function' && beforeFilter(val)) {
- self.isFiltering = true;
- var nodes = self.getNodes();
- var filterNodes = nodes.filter(function (node) {
- var disabled;
- if (checkStrictly) {
- disabled = node.disabled;
- } else {
- disabled = node.path.some(function (node) {
- return node.disabled;
- });
- }
- if ((node.leaf || checkStrictly) && !disabled) {
- if (typeof filterMethod === 'function' && filterMethod(node, val)) {
- // 命中
- return true;
- }
- }
- return false;
- });
- self._setSuggestionMenu(filterNodes);
- }
- }, debounce);
- }
-
- if (multiple) {
- // 多选可搜索
- this.$tagsInput = $('');
- var $tagsInput = this.$tagsInput;
- this.$tags.append($tagsInput);
- $tagsInput.on('keydown', filter);
- $tagsInput.click(function (event) {
- if (self.isFiltering) {
- event.stopPropagation();
- }
- });
- } else {
- var $inputRow = this.$inputRow;
- // 单选可搜索
- $inputRow.removeAttr('readonly');
- $inputRow.on('keydown', filter);
- $inputRow.click(function (event) {
- if (self.isFiltering) {
- event.stopPropagation();
- }
- });
- }
- },
- // 初始化面板(panel(1))
- _initPanel: function () {
- var $panel = this.$panel;
- var popperClass = this.config.popperClass || '';
- if (!$panel) {
- // z-index:解决和layer.open默认19891016的冲突
- this.$panel = $('');
- $panel = this.$panel;
- $panel.appendTo('body');
- $panel.click(function (event) {
- // 阻止事件冒泡
- event.stopPropagation();
- });
- // 初始化可搜索面板
- this._initSuggestionPanel();
- }
- },
- /**
- * 添加菜单(panel(1)->menu(n))
- * @param nodes 当前层级数据
- * @param level 层级,从0开始
- * @param parentNode 父级节点
- * @param _menuItem 刷新时,传入的当前菜单的item数据
- * @private
- */
- _appendMenu: function (nodes, level, parentNode, _menuItem) {
- this._removeMenu(level);
-
- if (parentNode && parentNode.leaf) {
- return;
- }
-
- var menuData = this.data.menuData;
- var $div = $('');
- // 重新添加菜单
- this.$panel.find('.el-cascader-panel').append($div);
- // 渲染细项
- this._appendLi($div, nodes);
- var menuItem = {nodes: nodes, level: level, parentNode: parentNode, scrollbar: {top: 0, left: 0}};
- if (_menuItem) {
- menuItem.scrollbar = _menuItem.scrollbar
- }
- // 渲染滚动条
- this._initScrollbar($div, menuItem);
- // 重新定位面板
- this._resetXY();
- menuData.push(menuItem);
- },
- /**
- * 移除菜单
- * @param level
- * @private
- */
- _removeMenu: function(level) {
- // 除了上一层的所有菜单全部移除
- var number = level - 1;
- if (number !== -1) {
- this.$panel.find('.el-cascader-panel .el-cascader-menu:gt(' + number + ')').remove();
- } else {
- this.$panel.find('.el-cascader-panel .el-cascader-menu').remove();
- }
- // 保存菜单数据
- var menuData = this.data.menuData;
- if (menuData.length > level) {
- menuData.splice(level, menuData.length - level);
- }
- },
- /**
- * 添加细项(panel(1)->menu(n)->li(n))
- * @param $menu 当前菜单对象
- * @param nodes 当前层级数据
- * @private
- */
- _appendLi: function ($menu, nodes) {
- var $list = $menu.find('.el-cascader-menu__list');
- if (!nodes || nodes.length === 0) {
- var isEmpty = this.config.empty;
- $list.append('');
- return;
- }
- $.each(nodes, function (index, node) {
- node.bind($list);
- });
- },
- /**
- * 刷新菜单面板
- */
- refreshMenu: function () {
- // 先复制一个数组,避免刷新菜单时,数组的数据被改变
- var data = this.data.menuData.concat([]);
- var self = this;
- data.forEach(function (data) {
- self._appendMenu(data.nodes, data.level, data.parentNode, data);
- })
- },
- /**
- * 初始化可搜索面板
- * @private
- */
- _initSuggestionPanel: function () {
- var filterable = this.config.filterable;
- if (!filterable) {
- return;
- }
- var $suggestionPanel = this.$suggestionPanel;
- if (!$suggestionPanel) {
- this.$suggestionPanel = $('');
- $suggestionPanel = this.$suggestionPanel;
- this.$panel.find('.popper__arrow').before($suggestionPanel);
- $suggestionPanel.click(function (event) {
- // 阻止事件冒泡
- event.stopPropagation();
- });
- }
- },
- /**
- * 设置可搜索菜单
- * @param nodes
- * @private
- */
- _setSuggestionMenu: function (nodes) {
- var $suggestionPanel = this.$suggestionPanel;
- var $list = $suggestionPanel.find('.el-cascader__suggestion-list');
- $list.empty();
- $suggestionPanel.find('.el-scrollbar__bar').remove();
- if (!nodes || nodes.length === 0) {
- $list.append('无匹配数据');
- return;
- }
- $.each(nodes, function (index, node) {
- node.bindSuggestion($list);
- });
- this._initScrollbar($suggestionPanel,{scrollbar: {top: 0, left: 0}});
- this._resetXY();
- },
- /**
- * 初始化节点数据
- * @param data 原始数据
- * @param level 层级
- * @param parentNode 父级节点
- * @returns {*[]}
- */
- initNodes: function (data, level, parentNode) {
- var nodes = [];
- for (var key in data) {
- var datum = data[key];
- var node = new Node(datum, this, level, parentNode);
- nodes.push(node);
- if (node.children && node.children.length > 0) {
- node.setChildren(this.initNodes(node.children, level + 1, node));
- }
- }
- return nodes;
- },
- /**
- * 设置单选值
- * @param nodeId 节点id
- * @param node 节点对象
- * @private
- */
- _setActiveValue: function (nodeId, node) {
- if (this.data.activeNodeId !== nodeId) {
- var activeNode = this.data.activeNode;
- this.data.activeNodeId = nodeId;
- this.data.activeNode = node;
- activeNode && activeNode.transferParent(function (node) {
- node.syncStyle();
- }, true);
- node && node.transferParent(function (node) {
- node.syncStyle();
- }, true);
- // 填充路径
- this.change(node && node.value, node);
- if (nodeId !== null) {
- this._setClear();
- }
- }
- },
- /**
- * 设置多选值
- * @param nodeIds 值数组
- * @param nodes 节点数组
- * @private
- */
- _setCheckedValue: function (nodeIds, nodes) {
- var checkedNodes = this.data.checkedNodes;
- var maxSize = this.config.maxSize;
- var maxSizeMode;
- if (nodeIds.length > 0 && maxSize !== 0 && nodeIds.length >= maxSize) {
- nodeIds = nodeIds.slice(0, maxSize);
- nodes = nodes.slice(0, maxSize);
- maxSizeMode = true
- } else {
- maxSizeMode = false
- }
- this.data.checkedNodeIds = nodeIds || [];
- this.data.checkedNodes = nodes || [];
- var syncPath = [];
- var syncNodeIds = [];
- checkedNodes.forEach(function (node) {
- node.path.forEach(function (node) {
- if (syncNodeIds.indexOf(node.nodeId) === -1) {
- syncPath.push(node);
- syncNodeIds.push(node.nodeId);
- }
- });
- });
- nodes.forEach(function (node) {
- node.path.forEach(function (node) {
- if (syncNodeIds.indexOf(node.nodeId) === -1) {
- syncPath.push(node);
- syncNodeIds.push(node.nodeId);
- }
- });
- });
- syncPath.forEach(function (node) {
- node.syncStyle();
- });
- // 填充路径
- this.change(nodes.map(function (node) {
- return node.value;
- }), nodes);
- this._setClear();
- this.maxSizeMode = maxSizeMode;
- },
- /**
- * 设置值
- * @param value
- */
- setValue: function (value) {
- if (this.data.activeNodeId || this.data.checkedNodeIds.length > 0) {
- // 清空值
- this.clearCheckedNodes();
- }
- if (!value) {
- return;
- }
- var strictMode = this.props.strictMode;
- if (strictMode) {
- if (!Array.isArray(value)) {
- throw new Error("严格模式下,value必须是一个包含父子节点数组结构.");
- }
- }
- var nodes = this.getNodes(this.data.nodes);
- var checkStrictly = this.props.checkStrictly;
- var multiple = this.props.multiple;
- var disabledFixed = this.config.disabledFixed;
- if (multiple) {
- var paths = nodes.filter(function (node) {
- if ((checkStrictly || node.leaf) && (!node.disabled || disabledFixed)) {
- if (strictMode) {
- // 严格模式下
- // some:命中一个就为true
- // every:全部命中为true
- return value.some(function (levelValue) {
- if (!Array.isArray(levelValue)) {
- throw new Error("多选严格模式下,value必须是一个二维数组结构.");
- }
- var path = node.path;
- return levelValue.length === path.length && levelValue.every(function (rowValue, index) {
- return path[index].value === rowValue;
- });
- })
- } else {
- return value.indexOf(node.value) !== -1;
- }
- }
- return false;
- });
- var nodeIds = paths.map(function (node) {
- return node.nodeId;
- });
- this._setCheckedValue(nodeIds, paths);
- // 展开第一个节点
- if (paths.length > 0) {
- var first = paths[0];
- first.expandPanel();
- }
- } else {
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- if ((checkStrictly || node.leaf)) {
- var is = false;
- if (strictMode) {
- // 严格模式下
- // every:全部命中为true
- var path = node.path;
- is = value.length === path.length && value.every(function (rowValue, index) {
- return path[index].value === rowValue;
- });
- } else if (node.value === value) {
- is = true;
- }
- if (is) {
- this._setActiveValue(node.nodeId, node);
- // 展开节点
- node.expandPanel();
- break;
- }
- }
- }
- }
- },
- /**
- * 递归获取扁平的节点
- * @param nodes
- * @param container
- * @returns {*[]}
- */
- getNodes: function (nodes, container) {
- if (!container) {
- container = [];
- }
- if (!nodes) {
- nodes = this.data.nodes;
- }
- var self = this;
- nodes.forEach(function (node) {
- container.push(node);
- var children = node.getChildren();
- if (children) {
- self.getNodes(children, container);
- }
- });
- return container;
- },
- /**
- * 初始化滚动条
- * @param $menu 菜单的dom节点对象
- * @param menuItem 当前菜单数据
- * @private
- */
- _initScrollbar: function ($menu, menuItem) {
- var $div = $('');
- $menu.append($div);
- var vertical = $($div[1]).find('.el-scrollbar__thumb');
- var onhoriztal = $($div[0]).find('.el-scrollbar__thumb');
- var scrollbar = $menu.find('.el-scrollbar__wrap');
- var $panel = this.$panel;
- var $lis = $menu.find('li');
- var height = Math.max($panel.height(), $menu.height());
- var hScale = (height - 6) / ($lis.height() * $lis.length);
- var wScale = $panel.width() / $lis.width();
-
- // 滚动条监听事件
- function _scrollbarEvent(scrollTop, scrollLeft) {
- if (hScale < 1) {
- vertical.css('height', hScale * 100 + '%');
- vertical.css('transform', 'translateY(' + scrollTop / $menu.height() * 100 + '%)');
- }
- if (wScale < 1) {
- onhoriztal.css('width', wScale * 100 + '%');
- onhoriztal.css('transform', 'translateY(' + scrollLeft / $menu.width() * 100 + '%)');
- }
- }
-
- // 拖动事件
- vertical.mousedown(function (event) {
- event.stopImmediatePropagation();
- event.stopPropagation();
- // 禁止文本选择事件
- var selectstart = function () {
- return false;
- };
- $(document).bind("selectstart", selectstart);
- var y = event.clientY;
- var scrollTop = scrollbar.scrollTop();
- // 移动事件
- var mousemove = function (event) {
- event.stopImmediatePropagation();
- var number = scrollTop + (event.clientY - y) / hScale;
- scrollbar.scrollTop(number);
- };
- $(document).bind('mousemove', mousemove);
- // 鼠标松开事件
- $(document).one('mouseup', function (event) {
- event.stopPropagation();
- event.stopImmediatePropagation();
- $(document).off('mousemove', mousemove);
- $(document).off('selectstart', selectstart);
- });
- });
- // 监听滚动条事件
- scrollbar.scroll(function () {
- var scroll = $(this);
- menuItem.scrollbar.top = scroll.scrollTop()
- menuItem.scrollbar.left = scroll.scrollLeft()
- _scrollbarEvent(menuItem.scrollbar.top, menuItem.scrollbar.left);
- });
-
- // 初始化滚动条
- scrollbar.scrollTop(menuItem.scrollbar.top);
- _scrollbarEvent(menuItem.scrollbar.top, menuItem.scrollbar.left);
- },
- // 填充路径
- _fillingPath: function () {
- var multiple = this.props.multiple;
- var showAllLevels = this.config.showAllLevels;
- var separator = this.config.separator;
- var collapseTags = this.config.collapseTags;
- var $inputRow = this.$inputRow;
- var placeholder = this.config.placeholder;
- var self = this;
- if (!multiple) {
- var activeNode = this.data.activeNode;
- var path = activeNode && activeNode.path || [];
- if (showAllLevels) {
- this._$inputRowSetValue(path.map(function (node) {
- return node.label;
- }).join(separator));
- } else {
- this._$inputRowSetValue(activeNode && activeNode.label || "");
- }
- } else {
- // 复选框
-
- // 删除标签
- this.$tags.find('.el-tag').remove();
- var $tagsInput = this.$tagsInput;
- // 清除高度
- $inputRow.css('height', '');
- var checkedNodes = this.data.checkedNodes;
- var minCollapseTagsNumber = Math.max(this.config.minCollapseTagsNumber, 1);
- if (checkedNodes.length > 0) {
- var tags = [];
- var paths = checkedNodes;
- if (collapseTags) {
- // 折叠tags
- paths = checkedNodes.slice(0, Math.min(checkedNodes.length, minCollapseTagsNumber));
- }
- paths.forEach(function (node) {
- tags.push(node.$tag);
- });
- // 判断标签是否折叠
- if (collapseTags) {
- // 判断标签最小折叠数
- if (checkedNodes.length > minCollapseTagsNumber) {
- tags.push(self.get$tag('+ ' + (checkedNodes.length - minCollapseTagsNumber), false));
- }
- }
- tags.forEach(function (tag) {
- if ($tagsInput) {
- $tagsInput.before(tag)
- } else {
- self.$tags.append(tag);
- }
- });
- }
- var tagHeight = self.$tags.height();
- var inputHeight = $inputRow.height();
- if (tagHeight > inputHeight) {
- $inputRow.css('height', tagHeight + 4 + 'px');
- }
- // 重新定位
- this._resetXY();
- if (checkedNodes.length > 0) {
- $inputRow.removeAttr('placeholder');
- $tagsInput && $tagsInput.removeAttr('placeholder', placeholder);
- } else {
- $inputRow.attr('placeholder', placeholder);
- $tagsInput && $tagsInput.attr('placeholder', placeholder);
- }
- }
- },
- /**
- * 设置单选输入框的值
- * @param label
- * @private
- */
- _$inputRowSetValue: function (label) {
- label = label || "";
- var $inputRow = this.$inputRow;
- $inputRow.attr('value', label); //防止被重置
- $inputRow.val(label);
- },
- /**
- * 获取复选框标签对象
- * @param label
- * @param showCloseIcon 是否显示关闭的icon
- * @returns {jQuery|HTMLElement|*}
- */
- get$tag: function (label, showCloseIcon) {
- var fromIcon = this.icons.from;
- var closeIcon = this.icons.close;
- var icon = showCloseIcon ? '' : '';
- return $('' + label + '' + icon + '');
- },
- // 设置可清理
- _setClear: function () {
- var self = this;
-
- function enter() {
- self.$icon.removeClass(self.icons.down);
- self.$icon.addClass(self.icons.close);
- }
-
- function out() {
- self.$icon.removeClass(self.icons.close);
- self.$icon.addClass(self.icons.down);
- }
-
- self.$div.mouseenter(function () {
- enter();
- });
- self.$div.mouseleave(function () {
- out();
- });
- self.$icon.off('click');
- var multiple = this.props.multiple;
- var clear;
- if (multiple) {
- clear = this.data.checkedNodeIds.length > 0;
- } else {
- clear = !!this.data.activeNodeId;
- }
- if (clear && !this.config.disabled && this.config.clearable) {
- self.$icon.one('click', function (event) {
- event.stopPropagation();
- self.close();
- self.clearCheckedNodes();
- out();
- self.$icon.off('mouseenter');
- self.$div.off('mouseenter');
- self.$div.off('mouseleave');
- });
- } else {
- out();
- self.$icon.off('mouseenter');
- self.$div.off('mouseenter');
- self.$div.off('mouseleave');
- }
- },
- // 禁用
- disabled: function (isDisabled) {
- this.config.disabled = !!isDisabled;
- if (this.config.disabled) {
- this.$div.addClass('is-disabled');
- this.$div.find('.el-input--suffix').addClass('is-disabled');
- this.$inputRow.attr('disabled', 'disabled');
- this.$tagsInput && this.$tagsInput.attr('disabled', 'disabled').hide()
- } else {
- this.$div.removeClass('is-disabled');
- this.$div.find('.el-input--suffix').removeClass('is-disabled');
- this.$inputRow.removeAttr('disabled');
- this.$tagsInput && this.$tagsInput.removeAttr('disabled').show();
- }
- // 重新设置是否可被清理
- this._setClear();
- // 重新填充路径
- this._fillingPath();
- },
- /**
- * 当选中节点变化时触发 选中节点的值
- * @param value 值
- * @param node 节点
- */
- change: function (value, node) {
- var multiple = this.props.multiple;
- if (multiple) {
- if (value && value.length > 0) {
- this.$ec.attr('value', JSON.stringify(value));
- // this.$ec.val(JSON.stringify(value));
- } else {
- this.$ec.removeAttr('value');
- // this.$ec.val('');
- }
- } else {
- this.$ec.attr('value', value || "");
- // this.$ec.val(value);
- }
- // 填充路径
- this._fillingPath();
- this.event.change.forEach(function (e) {
- typeof e === 'function' && e(value, node)
- })
- },
- /**
- * 当失去焦点时触发 (event: Event)
- * @param eventId 不为空时,必须与closeEventId值相等,防止旧事件触发
- */
- close: function (eventId) {
- if (this.showPanel && (!eventId || this.closeEventId === eventId)) {
- this.showPanel = false;
- this.$div.find('.layui-icon-down').removeClass('is-reverse');
- this.$panel.slideUp(100);
- this.visibleChange(false);
- // 聚焦颜色
- this.$input.removeClass('is-focus');
- // 可搜索
- var filterable = this.config.filterable;
- if (filterable) {
- this.isFiltering = false;
- this._fillingPath();
- }
- this.event.close.forEach(function (e) {
- typeof e === 'function' && e()
- })
- }
- },
- /**
- * 当获得焦点时触发 (event: Event)
- */
- open: function () {
- if (!this.showPanel) {
- this.showPanel = true;
- this.closeEventId++;
- var self = this;
- // 当前传播事件结束后,添加点击背景关闭面板事件
- setTimeout(function () {
- $(document).one('click', self.close.bind(self, self.closeEventId));
- });
- // 重新定位面板
- this._resetXY();
- // 箭头icon翻转
- this.$div.find('.layui-icon-down').addClass('is-reverse');
- this.$panel.slideDown(200);
- this.visibleChange(true);
- // 聚焦颜色
- this.$input.addClass('is-focus');
- this.event.open.forEach(function (e) {
- typeof e === 'function' && e()
- })
- }
- },
- /**
- * 下拉框出现/隐藏时触发
- * @param visible 出现则为 true,隐藏则为 false
- */
- visibleChange: function (visible) {
- },
- /**
- * 在多选模式下,移除Tag时触发 移除的Tag对应的节点的值
- * @param tagValue 节点的值
- * @param node 节点对象
- */
- removeTag: function (tagValue, node) {
- },
- /**
- * 获取选中的节点值
- * @returns {null|[]}
- */
- getCheckedValues: function () {
- var strictMode = this.props.strictMode;
- if (this.props.multiple) {
- var checkedNodes = this.data.checkedNodes;
- if (strictMode) {
- return checkedNodes.map(function (node) {
- return node.path.map(function (node1) {
- return node1.value;
- });
- });
- }
- return checkedNodes.map(function (node) {
- return node.value;
- });
- } else {
- var activeNode = this.data.activeNode;
- if (strictMode) {
- return activeNode && activeNode.path.map(function (node) {
- return node.value;
- })
- }
- return activeNode && activeNode.value;
- }
- },
- /**
- * 获取选中的节点
- * @returns {null|[]}
- */
- getCheckedNodes: function () {
- var strictMode = this.props.strictMode;
- if (this.props.multiple) {
- var checkedNodes = this.data.checkedNodes;
- if (strictMode) {
- return checkedNodes && checkedNodes.map(function (node) {
- return node.path;
- });
- }
- return checkedNodes;
- } else {
- var activeNode = this.data.activeNode;
- if (strictMode) {
- return activeNode && activeNode.path;
- }
- return activeNode;
- }
- },
- /**
- * 清空选中的节点
- * @param force 强制清理禁用固定节点
- */
- clearCheckedNodes: function (force) {
- var multiple = this.props.multiple;
- if (multiple) {
- var disabledFixed = this.config.disabledFixed;
- if (!force && disabledFixed) {
- //禁用项被固定,则过滤出禁用项的选值出来
- var checkedNodes = this.data.checkedNodes;
- var disNodes = checkedNodes.filter(function (node) {
- return node.disabled;
- });
- var nodeIds = disNodes.map(function (node) {
- return node.nodeId;
- });
- this._setCheckedValue(nodeIds, disNodes);
- } else {
- this._setCheckedValue([], []);
- }
- } else {
- this._setActiveValue(null, null);
- }
- }
- };
-
- var thisCas = function () {
- var self = this;
- return {
- /**
- * 设置选项值
- * @param options
- */
- setOptions: function (options) {
- self.setOptions(options);
- },
- /**
- * 覆盖当前值
- * @param value 单选时传对象,多选时传数组
- */
- setValue: function (value) {
- self.setValue(value);
- },
- /**
- * 当节点变更时,执行回调
- * @param callback function(value,node){}
- */
- changeEvent: function (callback) {
- self.event.change.push(callback);
- },
- /**
- * 当面板关闭时,执行回调
- * @param callback function(){}
- */
- closeEvent: function (callback) {
- self.event.close.push(callback);
- },
- /**
- * 当面板打开时,执行回调
- * @param callback function(){}
- */
- openEvent: function (callback) {
- self.event.open.push(callback);
- },
- /**
- * 禁用组件
- * @param disabled true/false
- */
- disabled: function (disabled) {
- self.disabled(disabled);
- },
- /**
- * 收起面板
- */
- close: function () {
- self.close();
- },
- /**
- * 展开面板
- */
- open: function () {
- self.open();
- },
- /**
- * 获取选中的节点,如需获取路径,使用node.path获取,将获取各级节点的node对象
- * @returns {[]|*}
- */
- getCheckedNodes: function () {
- return self.getCheckedNodes();
- },
- /**
- * 获取选中的值
- * @returns {[]|*}
- */
- getCheckedValues: function () {
- return self.getCheckedValues();
- },
- /**
- * 清空选中的节点
- * @param force 强制清理禁用固定节点
- */
- clearCheckedNodes: function (force) {
- self.clearCheckedNodes(force);
- },
- /**
- * 展开面板到节点所在的层级
- * @param value 节点值,只能传单个值,不允许传数组
- */
- expandNode: function (value) {
- var nodes = self.getNodes(self.data.nodes);
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- if (node.value === value) {
- node.expandPanel();
- break;
- }
- }
- },
- /**
- * 获取当前配置副本
- */
- getConfig: function () {
- return $.extend(true, {}, self.config);
- },
- /**
- * 获取数据对象副本
- * @returns {*}
- */
- getData: function () {
- return $.extend(true, {}, self.data);
- }
- };
- };
-
- exports('layCascader', function (option) {
- var ins = new Cascader(option);
- return thisCas.call(ins);
- });
-});
diff --git a/public/static/plugs/layui_exts/cascader.css b/public/static/plugs/layui_exts/layCascader.css
similarity index 100%
rename from public/static/plugs/layui_exts/cascader.css
rename to public/static/plugs/layui_exts/layCascader.css
diff --git a/public/static/plugs/layui_exts/layCascader.js b/public/static/plugs/layui_exts/layCascader.js
new file mode 100644
index 000000000..069d75f4c
--- /dev/null
+++ b/public/static/plugs/layui_exts/layCascader.js
@@ -0,0 +1,2085 @@
+/**
+ * 仿element-ui,级联选择器
+ * 已实现单选、多选、无关联选择
+ * 其他功能:组件禁用、节点禁用、自定义属性、自定义空面板提示,自定义无选择时的提示、多选标签折叠、回显、搜索、动态加载、最大选中数量限制、禁用项固定等操作。
+ * element-ui没有的功能:最大选中数量限制、禁用项固定
+ * author: yixiaco
+ * gitee: https://gitee.com/yixiacoco/lay_cascader
+ * github: https://github.com/yixiaco/lay_cascader
+ */
+layui.define(["jquery"], function (exports) {
+ var $ = layui.jquery;
+
+ /**
+ * 级联各项节点对象
+ * @param data 原始对象信息
+ * @param cascader 级联对象
+ * @param level 层级,从0开始
+ * @param parentNode 父节点对象
+ * @constructor
+ */
+ function Node(data, cascader, level, parentNode) {
+ this.data = data;
+ this.cascader = cascader;
+ this.config = cascader.config;
+ this.props = cascader.props;
+ this.level = level;
+ this.parentNode = parentNode;
+ // 引入的icon图标
+ this.icons = cascader.icons;
+ //该节点是否被选中 0:未选中,1:选中,2:不定
+ this._checked = 0;
+ // 是否正在加载中
+ this._loading = false;
+ // 每个Node的唯一标识
+ this.nodeId = cascader.data.nodeId++;
+ }
+
+ Node.prototype = {
+ constructor: Node,
+ /** 最顶级父节点 */
+ get topParentNode() {
+ return !this.parentNode && this || this.topParentNode;
+ },
+ /** 子节点 */
+ childrenNode: undefined,
+ get loading() {
+ return this._loading;
+ },
+ set loading(loading) {
+ var $li = this.$li;
+ if ($li) {
+ var rightIcon = this.icons.right;
+ var loadingIcon = this.icons.loading;
+ var $i = $li.find('i');
+ if (loading) {
+ $i.addClass(loadingIcon);
+ $i.removeClass(rightIcon);
+ } else {
+ $i.addClass(rightIcon);
+ $i.removeClass(loadingIcon);
+ }
+ }
+ return this._loading = loading;
+ },
+ /** 当前节点的显示文本 */
+ get label() {
+ return this.data[this.props.label];
+ },
+ /** 当前节点的值 */
+ get value() {
+ return this.data[this.props.value];
+ },
+ /** 是否禁用 */
+ get disabled() {
+ var multiple = this.props.multiple;
+ var maxSize = this.config.maxSize;
+ var checkedNodeIds = this.cascader.data.checkedNodeIds;
+ var disabledName = this.props.disabled;
+ var checkStrictly = this.props.checkStrictly;
+ // 检查是否超过最大值限制
+ if (multiple && maxSize !== 0) {
+ if (checkedNodeIds.length >= maxSize && checkedNodeIds.indexOf(this.nodeId) === -1) {
+ // 如果是关联的多选,需要查询叶子节点是否有被选中的项
+ if (!checkStrictly) {
+ var leafChildren = this.getAllLeafChildren();
+ var nodeIds = leafChildren.map(function (value) {
+ return value.nodeId
+ });
+ // 如果叶子节点不包含,则直接返回true
+ if (!nodeIds.some(function (nodeId) {
+ return checkedNodeIds.indexOf(nodeId) !== -1;
+ })) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+ if (!checkStrictly) {
+ var path = this.path;
+ return path.some(function (node) {
+ return node.data[disabledName];
+ });
+ } else {
+ return this.data[disabledName];
+ }
+ },
+ /** 子节点数据 */
+ get children() {
+ return this.data[this.props.children];
+ },
+ set children(children) {
+ this.data[this.props.children] = children;
+ },
+ /** 叶子节点 */
+ get leaf() {
+ var leaf = this.data[this.props.leaf];
+ if (typeof leaf === 'boolean') {
+ return leaf;
+ }
+ // 如果children不为空,则判断是否是子节点
+ if (this.children) {
+ return this.children.length <= 0;
+ }
+ return true;
+ },
+ /** 当前单选值 */
+ get activeNodeId() {
+ return this.cascader.data.activeNodeId;
+ },
+ /** 当前复选框值 */
+ get checkedNodeIds() {
+ return this.cascader.data.checkedNodeIds;
+ },
+ /** 路径 */
+ get path() {
+ var parentNode = this.parentNode;
+ if (parentNode) {
+ return parentNode.path.concat([this]);
+ } else {
+ return [this];
+ }
+ },
+ /** 是否正在搜索中 */
+ get isFiltering() {
+ return this.cascader.isFiltering;
+ },
+ /** 输入框的tag标签 */
+ get $tag() {
+ var cascader = this.cascader;
+ var showAllLevels = this.config.showAllLevels;
+ var disabled = this.config.disabled;
+ var nodeDisabled = this.disabled;
+ var disabledFixed = this.config.disabledFixed;
+
+ var label = this.getPathLabel(showAllLevels);
+ var $tag = cascader.get$tag(label, !disabled && (!nodeDisabled || !disabledFixed));
+ var self = this;
+ $tag.find('i').click(function (event) {
+ event.stopPropagation();
+ self.selectedValue();
+ cascader.removeTag(self.value, self);
+ });
+ return $tag;
+ },
+ /**
+ * 完整路径的标签
+ * @param showAllLevels
+ * @returns {string}
+ */
+ getPathLabel: function (showAllLevels) {
+ var path = this.path;
+ var separator = this.config.separator;
+
+ var label;
+ if (showAllLevels) {
+ label = path.map(function (node) {
+ return node.label;
+ }).join(separator);
+ } else {
+ label = path[path.length - 1].label;
+ }
+ return label;
+ },
+ /**
+ * 初始化
+ */
+ init: function () {
+ var multiple = this.props.multiple;
+ var checkStrictly = this.props.checkStrictly;
+ var fromIcon = this.icons.from;
+ var rightIcon = this.icons.right;
+ var icon = '';
+ var label = this.label;
+ if (!this.leaf) {
+ icon = rightIcon;
+ }
+ this.$li = $('');
+
+ // 节点渲染
+ if (!multiple && !checkStrictly) {
+ this._renderRadio();
+ } else if (!multiple && checkStrictly) {
+ this._renderRadioCheckStrictly();
+ } else if (multiple && !checkStrictly) {
+ this._renderMultiple();
+ } else if (multiple && checkStrictly) {
+ this._renderMultipleCheckStrictly();
+ }
+ },
+ /**
+ * 初始化可搜索li
+ */
+ initSuggestionLi: function () {
+ var label = this.getPathLabel(true);
+ this.$suggestionLi = $('' + label + '');
+ // 节点渲染
+ this._renderFiltering();
+ },
+ /**
+ * 绑定到菜单中
+ * @param $list li节点
+ */
+ bind: function ($list) {
+ this.init();
+ $list.append(this.$li);
+ },
+ /**
+ * 绑定可搜索到列表中
+ * @param $list
+ */
+ bindSuggestion: function ($list) {
+ this.initSuggestionLi();
+ $list.append(this.$suggestionLi);
+ },
+ /**
+ * 可搜索渲染
+ * @private
+ */
+ _renderFiltering: function () {
+ var $li = this.$suggestionLi;
+ var nodeId = this.nodeId;
+ var fromIcon = this.icons.from;
+ var okIcon = this.icons.ok;
+ var self = this;
+ var cascader = this.cascader;
+ var multiple = this.props.multiple;
+
+ var icon = '';
+ $li.click(function (event) {
+ event.stopPropagation();
+ self.selectedValue();
+ if (multiple) {
+ if (self.checkedNodeIds.indexOf(nodeId) === -1) {
+ $li.removeClass('is-checked');
+ $li.find('.el-icon-check').remove();
+ } else {
+ $li.addClass('is-checked');
+ $li.append(icon);
+ }
+ } else {
+ // 关闭面板
+ cascader.close();
+ }
+ });
+
+ if (multiple && self.checkedNodeIds.indexOf(nodeId) !== -1
+ || !multiple && self.activeNodeId === nodeId) {
+ $li.addClass('is-checked');
+ $li.append(icon)
+ }
+ },
+ /**
+ * 单选&&关联
+ * @private
+ */
+ _renderRadio: function () {
+ var $li = this.$li;
+ var nodeId = this.nodeId;
+ var fromIcon = this.icons.from;
+ var okIcon = this.icons.ok;
+ var level = this.level;
+ var leaf = this.leaf;
+ var self = this;
+ var cascader = this.cascader;
+ var activeNode = this.cascader.data.activeNode;
+ var parentNode = this.parentNode;
+
+ if (self.activeNodeId && activeNode.path.some(function (node) {
+ return node.nodeId === nodeId;
+ })) {
+ if (self.activeNodeId === nodeId) {
+ $li.prepend('');
+ }
+ $li.addClass('is-active');
+ $li.addClass('in-checked-path');
+ }
+
+ // 是否禁用
+ if (this.disabled) {
+ $li.addClass('is-disabled');
+ return;
+ }
+
+ $li.addClass('is-selectable');
+
+ if (parentNode) {
+ parentNode.$li.siblings().removeClass('in-active-path');
+ parentNode.$li.addClass('in-active-path');
+ }
+
+ // 触发下一个节点
+ this._liClick(function (event) {
+ event.stopPropagation();
+ var childrenNode = self.childrenNode;
+ if (leaf && event.type === 'click') {
+ self.selectedValue();
+ // 关闭面板
+ cascader.close();
+ }
+ // 添加下级菜单
+ cascader._appendMenu(childrenNode, level + 1, self);
+ });
+ },
+ /**
+ * 单选&&非关联
+ * @private
+ */
+ _renderRadioCheckStrictly: function () {
+ var $li = this.$li;
+ var nodeId = this.nodeId;
+ var level = this.level;
+ var leaf = this.leaf;
+ var self = this;
+ var cascader = this.cascader;
+ var activeNode = cascader.data.activeNode;
+ var parentNode = this.parentNode;
+
+ $li.addClass('is-selectable');
+ // 任意一级单选
+ var $radio = $('');
+ this.$radio = $radio;
+ $li.prepend($radio);
+ if (parentNode) {
+ parentNode.$li.siblings().removeClass('in-active-path');
+ parentNode.$li.addClass('in-active-path');
+ }
+
+ // 触发下一个节点
+ this._liClick(function (event) {
+ event.stopPropagation();
+ var childrenNode = self.childrenNode;
+ if (!self.disabled && leaf && event.type === 'click') {
+ self.selectedValue();
+ }
+ // 添加下级菜单
+ cascader._appendMenu(childrenNode, level + 1, self);
+ });
+
+ if (self.activeNodeId && activeNode.path.some(function (node) {
+ return node.nodeId === nodeId;
+ })) {
+ if (self.activeNodeId === nodeId) {
+ $radio.find('.el-radio__input').addClass('is-checked');
+ }
+ $li.addClass('is-active');
+ $li.addClass('in-checked-path');
+ }
+
+ if (this.disabled) {
+ $radio.addClass('is-disabled');
+ $radio.find('.el-radio__input').addClass('is-disabled');
+ return;
+ }
+ // 选中事件
+ $radio.click(function (event) {
+ event.preventDefault();
+ !leaf && self.selectedValue();
+ });
+ },
+ /**
+ * 多选&&关联
+ * @private
+ */
+ _renderMultiple: function () {
+ var $li = this.$li;
+ var level = this.level;
+ var leaf = this.leaf;
+ var self = this;
+ var cascader = this.cascader;
+ var checked = this._checked;
+ var parentNode = this.parentNode;
+
+ $li.addClass('el-cascader-node');
+
+ // 多选框
+ var $checked = $('');
+ this.$checked = $checked;
+ $li.prepend($checked);
+
+ // 渲染
+ if (checked === 1) {
+ this.$checked.find('.el-checkbox__input').addClass('is-checked');
+ } else if (checked === 2) {
+ this.$checked.find('.el-checkbox__input').addClass('is-indeterminate');
+ }
+
+ if (parentNode) {
+ parentNode.$li.siblings().removeClass('in-active-path');
+ parentNode.$li.addClass('in-active-path');
+ }
+
+ // 触发下一个节点
+ this._liClick(function (event) {
+ event.stopPropagation();
+ var childrenNode = self.childrenNode;
+ if (!self.disabled && leaf && event.type === 'click') {
+ // 最后一级就默认选择
+ self.selectedValue();
+ }
+ // 添加下级菜单
+ cascader._appendMenu(childrenNode, level + 1, self);
+ });
+
+ if (this.disabled) {
+ $li.addClass('is-disabled');
+ $checked.addClass('is-disabled');
+ $checked.find('.el-checkbox__input').addClass('is-disabled');
+ return;
+ }
+
+ // 选中事件
+ $checked.click(function (event) {
+ event.preventDefault();
+ if (!leaf) {
+ var childrenNode = self.childrenNode;
+ self.selectedValue();
+ cascader._appendMenu(childrenNode, level + 1, self);
+ }
+ });
+ },
+ /**
+ * 多选&&非关联
+ * @private
+ */
+ _renderMultipleCheckStrictly: function () {
+ var $li = this.$li;
+ var level = this.level;
+ var leaf = this.leaf;
+ var self = this;
+ var cascader = this.cascader;
+ var checkedNodeIds = cascader.data.checkedNodeIds;
+ var checkedNodes = cascader.data.checkedNodes;
+ var nodeId = this.nodeId;
+ var parentNode = this.parentNode;
+
+ $li.addClass('el-cascader-node is-selectable');
+
+ // 多选框
+ var $checked = $('');
+ this.$checked = $checked;
+ $li.prepend($checked);
+
+ // 渲染
+ var exist = checkedNodes.some(function (node) {
+ return node.path.some(function (node) {
+ return node.nodeId === nodeId;
+ })
+ });
+ if (exist) {
+ $li.addClass('in-checked-path');
+ if (checkedNodeIds.indexOf(nodeId) !== -1) {
+ this.$checked.find('.el-checkbox__input').addClass('is-checked');
+ }
+ }
+
+ if (parentNode) {
+ parentNode.$li.siblings().removeClass('in-active-path');
+ parentNode.$li.addClass('in-active-path');
+ }
+
+ // 触发下一个节点
+ this._liClick(function (event) {
+ event.stopPropagation();
+ var childrenNode = self.childrenNode;
+ if (!self.disabled && leaf && event.type === 'click') {
+ // 最后一级就默认选择
+ self.selectedValue();
+ }
+ // 添加下级菜单
+ cascader._appendMenu(childrenNode, level + 1, self);
+ });
+
+ if (this.disabled) {
+ $checked.addClass('is-disabled');
+ $checked.find('.el-checkbox__input').addClass('is-disabled');
+ return;
+ }
+ // 选中事件
+ $checked.click(function (event) {
+ event.preventDefault();
+ if (!leaf) {
+ self.selectedValue();
+ var childrenNode = self.childrenNode;
+ // 添加下级菜单
+ cascader._appendMenu(childrenNode, level + 1, self);
+ }
+ });
+ },
+ /**
+ * 向上传递
+ * @param callback 执行方法,如果返回false,则中断执行
+ * @param advance 是否先执行一次
+ * @param self 自身
+ */
+ transferParent: function (callback, advance, self) {
+ if (!self) {
+ self = this;
+ }
+ if (this !== self || advance) {
+ var goOn = callback && callback(this);
+ if (goOn === false) {
+ return;
+ }
+ }
+ this.parentNode && this.parentNode.transferParent(callback, advance, self);
+ },
+ /**
+ * 向下传递
+ * @param callback 执行的方法,如果返回false,则中断执行
+ * @param advance 是否先执行一次
+ * @param self 自身
+ */
+ transferChildren: function (callback, advance, self) {
+ if (!self) {
+ self = this;
+ }
+ if (this !== self || advance) {
+ var goOn = callback && callback(this);
+ if (goOn === false) {
+ return;
+ }
+ }
+ var children = this.getChildren();
+ if (children && children.length > 0) {
+ for (var index in children) {
+ children[index].transferChildren(callback, advance, self);
+ }
+ }
+ },
+ /**
+ * 设置级联值
+ */
+ selectedValue: function () {
+ var nodeId = this.nodeId;
+ var cascader = this.cascader;
+ var multiple = this.props.multiple;
+ var checkStrictly = this.props.checkStrictly;
+ var leaf = this.leaf;
+ if (!multiple && (leaf || checkStrictly)) {
+ cascader._setActiveValue(nodeId, this);
+ } else if (multiple) {
+ var checkedNodeIds = cascader.data.checkedNodeIds;
+ var checkedNodes = cascader.data.checkedNodes;
+ var disabledFixed = this.config.disabledFixed;
+ var paths;
+ if (checkStrictly) {
+ var index = checkedNodeIds.indexOf(nodeId);
+ if (index === -1) {
+ paths = checkedNodes.concat([this]);
+ } else {
+ paths = checkedNodes.concat();
+ paths.splice(index, 1);
+ }
+ } else {
+ var allLeafChildren = this.getAllLeafChildren();
+ var checked;
+ if (this._checked !== 1 && disabledFixed) {
+ checked = this._getMultipleChecked(allLeafChildren);
+ } else {
+ checked = this._checked;
+ }
+ if (checked === 1) {
+ // 选中->未选中
+ paths = checkedNodes.filter(function (node1) {
+ return !allLeafChildren.some(function (node2) {
+ return node1.nodeId === node2.nodeId;
+ });
+ });
+ } else {
+ // 未选中、部分选中->选中
+ var add = allLeafChildren.filter(function (node) {
+ return checkedNodeIds.indexOf(node.nodeId) === -1;
+ });
+ paths = checkedNodes.concat(add);
+ }
+ }
+ var nodeIds = paths.map(function (node) {
+ return node.nodeId;
+ });
+ cascader._setCheckedValue(nodeIds, paths);
+ }
+ },
+ _liLoad: function (event, callback) {
+ var leaf = this.leaf;
+ var lazy = this.props.lazy;
+ var lazyLoad = this.props.lazyLoad;
+ var children = this.children;
+ var self = this;
+ var cascader = this.cascader;
+ var level = this.level;
+ var multiple = this.props.multiple;
+ var checkStrictly = this.props.checkStrictly;
+ if (!leaf && (!children || children.length === 0) && lazy) {
+ if (!self.loading) {
+ self.loading = true;
+ lazyLoad(self, function (nodes) {
+ self.loading = false;
+ self.setChildren(cascader.initNodes(nodes, level + 1, self));
+ self.children = nodes;
+ callback && callback(event);
+ // 多选&关联时,重新同步下父级节点的样式
+ multiple && !checkStrictly && self.transferParent(function (node) {
+ node.syncStyle();
+ }, true);
+ });
+ }
+ } else {
+ callback && callback(event);
+ }
+ },
+ /**
+ * 点击li事件
+ * @param callback
+ * @private
+ */
+ _liClick: function (callback) {
+ var leaf = this.leaf;
+ var $li = this.$li;
+ var self = this;
+
+ function load(event) {
+ self._liLoad(event, callback);
+ }
+
+ if (this.props.expandTrigger === "click" || leaf) {
+ $li.click(load);
+ }
+ if (this.props.expandTrigger === "hover") {
+ $li.mouseenter(load);
+ }
+ },
+ setChildren: function (children) {
+ this.childrenNode = children;
+ },
+ getChildren: function () {
+ return this.childrenNode;
+ },
+ /**
+ * 同步样式
+ */
+ syncStyle: function () {
+ var multiple = this.props.multiple;
+ var checkStrictly = this.props.checkStrictly;
+ if (multiple) {
+ //多选
+ if (checkStrictly) {
+ this._sync.syncMultipleCheckStrictly(this);
+ } else {
+ this._sync.syncMultiple(this);
+ }
+ } else {
+ //单选
+ if (checkStrictly) {
+ this._sync.syncRadioCheckStrictly(this);
+ } else {
+ this._sync.syncRadio(this);
+ }
+ }
+ },
+ /**
+ * 同步本节点样式
+ */
+ _sync: {
+ /**
+ * 同步单选关联样式
+ */
+ syncRadio: function (self) {
+ var $li = self.$li;
+ var fromIcon = self.icons.from;
+ var okIcon = self.icons.ok;
+ var multiple = self.props.multiple;
+ var checkStrictly = self.props.checkStrictly;
+ var nodeId = self.nodeId;
+ if (!$li || multiple || checkStrictly) {
+ return;
+ }
+ var activeNode = self.cascader.data.activeNode;
+ if (self.activeNodeId === nodeId) {
+ var ok = $li.find('.' + okIcon);
+ if (ok.length === 0) {
+ $li.prepend('');
+ }
+ } else {
+ $li.find('.' + okIcon).remove();
+ }
+ if (activeNode && activeNode.path.some(function (node) {
+ return node.nodeId === nodeId;
+ })) {
+ $li.addClass('is-active');
+ $li.addClass('in-checked-path');
+ } else {
+ $li.removeClass('is-active');
+ $li.removeClass('in-checked-path');
+ }
+ },
+ /**
+ * 同步单选非关联样式
+ */
+ syncRadioCheckStrictly: function (self) {
+ var $li = self.$li;
+ var checkStrictly = self.props.checkStrictly;
+ var multiple = self.props.multiple;
+ if (!$li || multiple || !checkStrictly) {
+ return;
+ }
+ var $radio = self.$radio;
+ var activeNode = self.cascader.data.activeNode;
+ var nodeId = self.nodeId;
+ if (self.activeNodeId === nodeId) {
+ $radio.find('.el-radio__input').addClass('is-checked');
+ } else {
+ $radio.find('.el-radio__input').removeClass('is-checked');
+ }
+ if (activeNode && activeNode.path.some(function (node) {
+ return node.nodeId === nodeId;
+ })) {
+ $li.addClass('is-active');
+ $li.addClass('in-checked-path');
+ } else {
+ $li.removeClass('is-active');
+ $li.removeClass('in-checked-path');
+ }
+ },
+ /**
+ * 同步多选关联样式
+ */
+ syncMultiple: function (self) {
+ var $li = self.$li;
+ var checkStrictly = self.props.checkStrictly;
+ var multiple = self.props.multiple;
+ var disabledFixed = self.config.disabledFixed;
+ if (!multiple || checkStrictly) {
+ return;
+ }
+ var allLeafChildren = self.getAllLeafChildren(disabledFixed);
+ // 全部未选中 0
+ // 全部选中 1
+ // 部分选中 2
+ var checked = self._getMultipleChecked(allLeafChildren);
+ self._checked = checked;
+ if (!$li) {
+ return;
+ }
+ var $checkbox = self.$checked.find('.el-checkbox__input');
+ if (checked === 0) {
+ $checkbox.removeClass('is-checked');
+ $checkbox.removeClass('is-indeterminate');
+ } else if (checked === 1) {
+ $checkbox.removeClass('is-indeterminate');
+ $checkbox.addClass('is-checked');
+ } else if (checked === 2) {
+ $checkbox.removeClass('is-checked');
+ $checkbox.addClass('is-indeterminate');
+ }
+ },
+ /**
+ * 同步多选非关联样式
+ */
+ syncMultipleCheckStrictly: function (self) {
+ var $li = self.$li;
+ var checkStrictly = self.props.checkStrictly;
+ var multiple = self.props.multiple;
+ if (!$li || !multiple || !checkStrictly) {
+ return;
+ }
+ var checkedNodes = self.cascader.data.checkedNodes;
+ var checkedNodeIds = self.checkedNodeIds;
+ var nodeId = self.nodeId;
+ var exist = checkedNodes.some(function (node) {
+ return node.path.some(function (node) {
+ return node.nodeId === nodeId;
+ });
+ });
+ var $checkbox = self.$checked.find('.el-checkbox__input');
+ if (checkedNodeIds.some(function (checkedNodeId) {
+ return checkedNodeId === nodeId;
+ })) {
+ // 选中
+ $checkbox.addClass('is-checked');
+ } else {
+ // 未选中
+ $checkbox.removeClass('is-checked');
+ }
+ if (exist) {
+ $li.addClass('in-checked-path');
+ } else {
+ $li.removeClass('in-checked-path');
+ }
+ }
+ },
+ /**
+ * 获取叶子节点value值
+ * @param disabled 是否包含禁用,默认不包含
+ * @returns {Node[]|*[]}
+ */
+ getAllLeafChildren: function (disabled) {
+ var leaf = this.leaf;
+ if (leaf) {
+ return [this];
+ } else {
+ var leafs = [];
+ this.transferChildren(function (node) {
+ if (node.disabled && !disabled) {
+ return false;
+ }
+ node.leaf && leafs.push(node);
+ });
+ return leafs;
+ }
+ },
+ /**
+ * 展开当前节点
+ */
+ expandPanel: function () {
+ var path = this.path;
+ var cascader = this.cascader;
+ path.forEach(function (node, index, array) {
+ if (index !== array.length - 1) {
+ var childrenNode = node.childrenNode;
+ cascader._appendMenu(childrenNode, node.level + 1, node.parentNode);
+ }
+ });
+ },
+ _getMultipleChecked: function (leafNodes) {
+ var cascader = this.cascader;
+ var checkedNodeIds = cascader.data.checkedNodeIds;
+ var allLeafIds = leafNodes.map(function (node) {
+ return node.nodeId;
+ });
+ // 全部未选中 0
+ // 全部选中 1
+ // 部分选中 2
+ var checked = 0;
+ for (var i = 0; i < allLeafIds.length; i++) {
+ var child = allLeafIds[i];
+ if (checked === 2) {
+ break;
+ }
+ if (checkedNodeIds.indexOf(child) !== -1) {
+ if (i > 0 && checked !== 1) {
+ checked = 2;
+ } else {
+ checked = 1;
+ }
+ } else {
+ // 当全部选中时,则改为部分选中,否则全部未选中
+ checked = checked === 1 ? 2 : 0;
+ }
+ }
+ return checked;
+ }
+ };
+
+ function Cascader(config) {
+ this.config = $.extend(true, {
+ elem: '', //绑定元素
+ value: null, //预设值
+ options: [], //可选项数据源,键名可通过 Props 属性配置
+ empty: '暂无数据', //无匹配选项时的内容
+ placeholder: '请选择',//输入框占位文本
+ disabled: false, //是否禁用
+ clearable: false, //是否支持清空选项
+ showAllLevels: true, //输入框中是否显示选中值的完整路径
+ collapseTags: false, //多选模式下是否折叠Tag
+ minCollapseTagsNumber: 1, //最小折叠标签数
+ separator: ' / ', //选项分隔符
+ filterable: false, //是否可搜索选项
+ filterMethod: function (node, keyword) {
+ return node.path.some(function (node) {
+ return node.label.indexOf(keyword) !== -1;
+ });
+ }, //自定义搜索逻辑,第一个参数是节点node,第二个参数是搜索关键词keyword,通过返回布尔值表示是否命中
+ debounce: 300, //搜索关键词输入的去抖延迟,毫秒
+ beforeFilter: function (value) {
+ return true;
+ },//筛选之前的钩子,参数为输入的值,若返回 false,则停止筛选
+ popperClass: '', // 自定义浮层类名 string
+ extendClass: false, //继承class样式
+ extendStyle: false, //继承style样式
+ disabledFixed: false, //固定禁用项,使禁用项不被清理删除,禁用项只能通过函数添加或初始值添加,默认禁用项不可被函数或初始值添加
+ maxSize: 0, // 多选选中的最大数量,0表示不限制
+ props: {
+ strictMode: false, //严格模式,设置value严格按照层级结构.例如:[[1,2,3],[1,2,4]]
+ expandTrigger: 'click', //次级菜单的展开方式 string click / hover 'click'
+ multiple: false, //是否多选 boolean - false
+ checkStrictly: false, //是否严格的遵守父子节点不互相关联 boolean - false
+ lazy: false, //是否动态加载子节点,需与 lazyLoad 方法结合使用 boolean - false
+ lazyLoad: function (node, resolve) {
+ }, //加载动态数据的方法,仅在 lazy 为 true 时有效 function(node, resolve),node为当前点击的节点,resolve为数据加载完成的回调(必须调用)
+ value: 'value', //指定选项的值为选项对象的某个属性值 string — 'value'
+ label: 'label', //指定选项标签为选项对象的某个属性值 string — 'label'
+ children: 'children', //指定选项的子选项为选项对象的某个属性值 string — 'children'
+ disabled: 'disabled', //指定选项的禁用为选项对象的某个属性值 string — 'disabled'
+ leaf: 'leaf' //指定选项的叶子节点的标志位为选项对象的某个属性值 string — 'leaf'
+ }
+ }, config);
+ this.data = {
+ nodeId: 1, //nodeId的自增值
+ nodes: [], //存储Node对象
+ menuData: [], //压入菜单的数据
+ activeNodeId: null, //存放NodeId
+ activeNode: null, //存放Node
+ checkedNodeIds: [], //存放多个NodeId
+ checkedNodes: [] //存放多个Node
+ };
+ // 面板是否展开
+ this.showPanel = false;
+ this.event = {
+ // 值变更事件
+ change: [],
+ // 打开事件
+ open: [],
+ // 关闭事件
+ close: []
+ }
+ // 是否正在搜索中
+ this.filtering = false;
+ // 初始化
+ this._init();
+ // 面板关闭事件id
+ this.closeEventId = 0;
+ // 是否进入maxSize模式
+ this._maxSizeMode = false;
+ }
+
+ Cascader.prototype = {
+ constructor: Cascader,
+ get props() {
+ return this.config.props;
+ },
+ get isFiltering() {
+ return this.filtering;
+ },
+ set isFiltering(filtering) {
+ if (this.filtering === filtering) {
+ return;
+ }
+ this.filtering = !!filtering;
+ var $panel = this.$panel;
+ if (this.filtering) {
+ $panel.find('.el-cascader-panel').hide();
+ $panel.find('.el-cascader__suggestion-panel').show();
+ } else {
+ $panel.find('.el-cascader-panel').show();
+ $panel.find('.el-cascader__suggestion-panel').hide();
+ this.$tagsInput && this.$tagsInput.val('')
+ }
+ },
+ set maxSizeMode(maxSizeMode) {
+ if (this._maxSizeMode !== maxSizeMode) {
+ this._maxSizeMode = maxSizeMode;
+ this.refreshMenu();
+ }
+ },
+ icons: {
+ from: 'layui-icon',
+ down: 'layui-icon-down',
+ close: 'layui-icon-close',
+ right: 'layui-icon-right',
+ ok: 'layui-icon-ok',
+ loading: 'layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop'
+ },
+ // 初始化
+ _init: function () {
+ this._checkConfig();
+ // 初始化输入框
+ this._initInput();
+ // 初始化面板
+ this._initPanel();
+ // 初始化选项值
+ this.setOptions(this.config.options);
+ var self = this;
+ // 监听滚动条
+ $(window).scroll(function () {
+ self._resetXY();
+ });
+ // 监听窗口
+ $(window).resize(function () {
+ self._resetXY();
+ });
+ // 点击事件,展开面板
+ this.$div.click(function (event) {
+ if (self.config.disabled) {
+ return;
+ }
+ var show = self.showPanel;
+ if (!show) {
+ self.open();
+ } else {
+ self.close();
+ }
+ });
+ },
+ /**
+ * 检查配置
+ * @private
+ */
+ _checkConfig: function () {
+ var elem = this.config.elem;
+ if (!elem || $(elem).length === 0) {
+ throw new Error("缺少elem节点选择器");
+ }
+ var maxSize = this.config.maxSize;
+ if (typeof maxSize !== 'number' || maxSize < 0) {
+ throw new Error("maxSize应是一个大于等于0的有效的number值");
+ }
+ if (!Array.isArray(this.config.options)) {
+ throw new Error("options不是一个有效的数组");
+ }
+ },
+ /**
+ * 初始化根目录
+ * @private
+ */
+ _initRoot: function () {
+ var lazy = this.props.lazy;
+ var lazyLoad = this.props.lazyLoad;
+ var self = this;
+ var nodes = this.data.nodes;
+ if (nodes.length > 0 || !lazy) {
+ this._appendMenu(nodes, 0);
+ } else if (lazy) {
+ this._appendMenu(nodes, 0);
+ lazyLoad({
+ root: true,
+ level: 0
+ }, function (nodes) {
+ self.data.nodes = self.initNodes(nodes, 0, null);
+ self._appendMenu(self.data.nodes, 0);
+ });
+ }
+ },
+ /**
+ * 设置选项值
+ * @param options
+ */
+ setOptions: function (options) {
+ this.config.options = options;
+ // 初始化节点
+ this.data.nodes = this.initNodes(options, 0, null);
+ // 初始化根目录
+ this._initRoot();
+ // 初始化值
+ this.setValue(this.config.value);
+ },
+ // 面板定位
+ _resetXY: function () {
+ var $div = this.$div;
+ var offset = $div.offset();
+ var $panel = this.$panel;
+ if ($panel) {
+ var windowHeight = window.innerHeight;
+ var windowWidth = window.innerWidth;
+ var panelHeight = $panel.height();
+ var panelWidth = $panel.width();
+ var divHeight = $div.height();
+ var boundingClientRect = $div[0].getBoundingClientRect();
+ var $arrow = $panel.find('.popper__arrow');
+
+ // 距离右边界的偏差值
+ var offsetDiff = Math.min(windowWidth - boundingClientRect.x - panelWidth - 5, 0);
+
+ var bottomHeight = windowHeight - (boundingClientRect.top + divHeight);
+ if (bottomHeight < panelHeight && boundingClientRect.top > panelHeight + 20) {
+ $panel.attr('x-placement', 'top-start')
+ // 向上
+ $panel.css({
+ top: offset.top - 20 - panelHeight + 'px',
+ left: offset.left + offsetDiff + 'px'
+ });
+ } else {
+ $panel.attr('x-placement', 'bottom-start');
+ // 距离底部边界的偏差值
+ var yOffset = Math.max(panelHeight - (windowHeight - boundingClientRect.y - divHeight - 15), 0);
+ // 向下
+ $panel.css({
+ top: offset.top + divHeight - yOffset + 'px',
+ left: offset.left + offsetDiff + 'px'
+ });
+ }
+ // 箭头偏移
+ $arrow.css("left", 35 - offsetDiff + "px");
+ }
+ },
+ get $menus() {
+ return this.$panel && this.$panel.find('.el-cascader-panel .el-cascader-menu');
+ },
+ // 初始化输入框
+ _initInput: function () {
+ var $e = $(this.config.elem);
+ var self = this;
+ // 当绑定的元素带有value属性,并且对象未设置值时,设置一个初始值
+ if (this.config.value === null && $e.attr('value')) {
+ this.config.value = $e.attr('value');
+ }
+ var placeholder = this.config.placeholder;
+ var fromIcon = this.icons.from;
+ var downIcon = this.icons.down;
+ var multiple = this.props.multiple;
+ var extendClass = this.config.extendClass;
+ var extendStyle = this.config.extendStyle;
+
+ this.$div = $('');
+ if (extendStyle) {
+ var style = $e.attr('style');
+ if (style) {
+ this.$div.attr('style', style);
+ }
+ }
+ if (extendClass) {
+ var className = $e.attr('class');
+ if (className) {
+ className.split(' ').forEach(function (name) {
+ self.$div.addClass(name);
+ });
+ }
+ }
+ this.$input = $('' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
')
+ this.$div.append(this.$input);
+ this.$inputRow = this.$input.find('.el-input__inner');
+ // 多选标签
+ if (multiple) {
+ this.$tags = $('');
+ this.$div.append(this.$tags);
+ }
+ this._initHideElement($e);
+ // 替换元素
+ $e.replaceWith(this.$div);
+ this.$icon = this.$input.find('i');
+ this._initFilterableInputEvent();
+ this.disabled(this.config.disabled);
+ },
+ /**
+ * 初始化隐藏元素input,主要用于layui的表单验证
+ * @param $e
+ * @private
+ */
+ _initHideElement: function ($e) {
+ // 保存原始元素
+ var attributes = $e[0].attributes;
+ var $input = $('');
+ var keys = Object.keys(attributes);
+ for (var key in keys) {
+ var attribute = attributes[key];
+ $input.attr(attribute.name, attribute.value);
+ }
+ $input.hide();
+ $input.attr('type', 'hidden')
+ this.$ec = $input;
+ $e.before($input);
+ },
+ /**
+ * 初始化可搜索监听事件
+ * @private
+ */
+ _initFilterableInputEvent: function () {
+ var filterable = this.config.filterable;
+ if (!filterable) {
+ return;
+ }
+ var timeoutID;
+ var multiple = this.props.multiple;
+ var debounce = this.config.debounce;
+ var placeholder = this.config.placeholder;
+ var beforeFilter = this.config.beforeFilter;
+ var filterMethod = this.config.filterMethod;
+ var checkStrictly = this.props.checkStrictly;
+ var self = this;
+
+ function filter(event) {
+ var input = this;
+ if (timeoutID) {
+ clearTimeout(timeoutID);
+ }
+ timeoutID = setTimeout(function () {
+ timeoutID = null;
+ var val = $(input).val();
+ if (!val) {
+ self.isFiltering = false;
+ return;
+ }
+ self.open();
+ if (typeof beforeFilter === 'function' && beforeFilter(val)) {
+ self.isFiltering = true;
+ var nodes = self.getNodes();
+ var filterNodes = nodes.filter(function (node) {
+ var disabled;
+ if (checkStrictly) {
+ disabled = node.disabled;
+ } else {
+ disabled = node.path.some(function (node) {
+ return node.disabled;
+ });
+ }
+ if ((node.leaf || checkStrictly) && !disabled) {
+ if (typeof filterMethod === 'function' && filterMethod(node, val)) {
+ // 命中
+ return true;
+ }
+ }
+ return false;
+ });
+ self._setSuggestionMenu(filterNodes);
+ }
+ }, debounce);
+ }
+
+ if (multiple) {
+ // 多选可搜索
+ this.$tagsInput = $('');
+ var $tagsInput = this.$tagsInput;
+ this.$tags.append($tagsInput);
+ $tagsInput.on('keydown', filter);
+ $tagsInput.click(function (event) {
+ if (self.isFiltering) {
+ event.stopPropagation();
+ }
+ });
+ } else {
+ var $inputRow = this.$inputRow;
+ // 单选可搜索
+ $inputRow.removeAttr('readonly');
+ $inputRow.on('keydown', filter);
+ $inputRow.click(function (event) {
+ if (self.isFiltering) {
+ event.stopPropagation();
+ }
+ });
+ }
+ },
+ // 初始化面板(panel(1))
+ _initPanel: function () {
+ var $panel = this.$panel;
+ var popperClass = this.config.popperClass || '';
+ if (!$panel) {
+ // z-index:解决和layer.open默认19891016的冲突
+ this.$panel = $('');
+ $panel = this.$panel;
+ $panel.appendTo('body');
+ $panel.click(function (event) {
+ // 阻止事件冒泡
+ event.stopPropagation();
+ });
+ // 初始化可搜索面板
+ this._initSuggestionPanel();
+ }
+ },
+ /**
+ * 添加菜单(panel(1)->menu(n))
+ * @param nodes 当前层级数据
+ * @param level 层级,从0开始
+ * @param parentNode 父级节点
+ * @param _menuItem 刷新时,传入的当前菜单的item数据
+ * @private
+ */
+ _appendMenu: function (nodes, level, parentNode, _menuItem) {
+ this._removeMenu(level);
+
+ if (parentNode && parentNode.leaf) {
+ return;
+ }
+
+ var menuData = this.data.menuData;
+ var $div = $('');
+ // 重新添加菜单
+ this.$panel.find('.el-cascader-panel').append($div);
+ // 渲染细项
+ this._appendLi($div, nodes);
+ var menuItem = {nodes: nodes, level: level, parentNode: parentNode, scrollbar: {top: 0, left: 0}};
+ if (_menuItem) {
+ menuItem.scrollbar = _menuItem.scrollbar
+ }
+ // 渲染滚动条
+ this._initScrollbar($div, menuItem);
+ // 重新定位面板
+ this._resetXY();
+ menuData.push(menuItem);
+ },
+ /**
+ * 移除菜单
+ * @param level
+ * @private
+ */
+ _removeMenu: function (level) {
+ // 除了上一层的所有菜单全部移除
+ var number = level - 1;
+ if (number !== -1) {
+ this.$panel.find('.el-cascader-panel .el-cascader-menu:gt(' + number + ')').remove();
+ } else {
+ this.$panel.find('.el-cascader-panel .el-cascader-menu').remove();
+ }
+ // 保存菜单数据
+ var menuData = this.data.menuData;
+ if (menuData.length > level) {
+ menuData.splice(level, menuData.length - level);
+ }
+ },
+ /**
+ * 添加细项(panel(1)->menu(n)->li(n))
+ * @param $menu 当前菜单对象
+ * @param nodes 当前层级数据
+ * @private
+ */
+ _appendLi: function ($menu, nodes) {
+ var $list = $menu.find('.el-cascader-menu__list');
+ if (!nodes || nodes.length === 0) {
+ var isEmpty = this.config.empty;
+ $list.append('');
+ return;
+ }
+ $.each(nodes, function (index, node) {
+ node.bind($list);
+ });
+ },
+ /**
+ * 刷新菜单面板
+ */
+ refreshMenu: function () {
+ // 先复制一个数组,避免刷新菜单时,数组的数据被改变
+ var data = this.data.menuData.concat([]);
+ var self = this;
+ data.forEach(function (data) {
+ self._appendMenu(data.nodes, data.level, data.parentNode, data);
+ })
+ },
+ /**
+ * 初始化可搜索面板
+ * @private
+ */
+ _initSuggestionPanel: function () {
+ var filterable = this.config.filterable;
+ if (!filterable) {
+ return;
+ }
+ var $suggestionPanel = this.$suggestionPanel;
+ if (!$suggestionPanel) {
+ this.$suggestionPanel = $('');
+ $suggestionPanel = this.$suggestionPanel;
+ this.$panel.find('.popper__arrow').before($suggestionPanel);
+ $suggestionPanel.click(function (event) {
+ // 阻止事件冒泡
+ event.stopPropagation();
+ });
+ }
+ },
+ /**
+ * 设置可搜索菜单
+ * @param nodes
+ * @private
+ */
+ _setSuggestionMenu: function (nodes) {
+ var $suggestionPanel = this.$suggestionPanel;
+ var $list = $suggestionPanel.find('.el-cascader__suggestion-list');
+ $list.empty();
+ $suggestionPanel.find('.el-scrollbar__bar').remove();
+ if (!nodes || nodes.length === 0) {
+ $list.append('无匹配数据');
+ return;
+ }
+ $.each(nodes, function (index, node) {
+ node.bindSuggestion($list);
+ });
+ this._initScrollbar($suggestionPanel, {scrollbar: {top: 0, left: 0}});
+ this._resetXY();
+ },
+ /**
+ * 初始化节点数据
+ * @param data 原始数据
+ * @param level 层级
+ * @param parentNode 父级节点
+ * @returns {*[]}
+ */
+ initNodes: function (data, level, parentNode) {
+ var nodes = [];
+ for (var key in data) {
+ var datum = data[key];
+ var node = new Node(datum, this, level, parentNode);
+ nodes.push(node);
+ if (node.children && node.children.length > 0) {
+ node.setChildren(this.initNodes(node.children, level + 1, node));
+ }
+ }
+ return nodes;
+ },
+ /**
+ * 设置单选值
+ * @param nodeId 节点id
+ * @param node 节点对象
+ * @private
+ */
+ _setActiveValue: function (nodeId, node) {
+ if (this.data.activeNodeId !== nodeId) {
+ var activeNode = this.data.activeNode;
+ this.data.activeNodeId = nodeId;
+ this.data.activeNode = node;
+ activeNode && activeNode.transferParent(function (node) {
+ node.syncStyle();
+ }, true);
+ node && node.transferParent(function (node) {
+ node.syncStyle();
+ }, true);
+ // 填充路径
+ this.change(node && node.value, node);
+ if (nodeId !== null) {
+ this._setClear();
+ }
+ }
+ },
+ /**
+ * 设置多选值
+ * @param nodeIds 值数组
+ * @param nodes 节点数组
+ * @private
+ */
+ _setCheckedValue: function (nodeIds, nodes) {
+ var checkedNodes = this.data.checkedNodes;
+ var maxSize = this.config.maxSize;
+ var maxSizeMode;
+ if (nodeIds.length > 0 && maxSize !== 0 && nodeIds.length >= maxSize) {
+ nodeIds = nodeIds.slice(0, maxSize);
+ nodes = nodes.slice(0, maxSize);
+ maxSizeMode = true
+ } else {
+ maxSizeMode = false
+ }
+ this.data.checkedNodeIds = nodeIds || [];
+ this.data.checkedNodes = nodes || [];
+ var syncPath = [];
+ var syncNodeIds = [];
+ checkedNodes.forEach(function (node) {
+ node.path.forEach(function (node) {
+ if (syncNodeIds.indexOf(node.nodeId) === -1) {
+ syncPath.push(node);
+ syncNodeIds.push(node.nodeId);
+ }
+ });
+ });
+ nodes.forEach(function (node) {
+ node.path.forEach(function (node) {
+ if (syncNodeIds.indexOf(node.nodeId) === -1) {
+ syncPath.push(node);
+ syncNodeIds.push(node.nodeId);
+ }
+ });
+ });
+ syncPath.forEach(function (node) {
+ node.syncStyle();
+ });
+ // 填充路径
+ this.change(nodes.map(function (node) {
+ return node.value;
+ }), nodes);
+ this._setClear();
+ this.maxSizeMode = maxSizeMode;
+ },
+ /**
+ * 设置值
+ * @param value
+ */
+ setValue: function (value) {
+ if (this.data.activeNodeId || this.data.checkedNodeIds.length > 0) {
+ // 清空值
+ this.clearCheckedNodes();
+ }
+ if (!value) {
+ return;
+ }
+ var strictMode = this.props.strictMode;
+ if (strictMode) {
+ if (!Array.isArray(value)) {
+ throw new Error("严格模式下,value必须是一个包含父子节点数组结构.");
+ }
+ }
+ var nodes = this.getNodes(this.data.nodes);
+ var checkStrictly = this.props.checkStrictly;
+ var multiple = this.props.multiple;
+ var disabledFixed = this.config.disabledFixed;
+ if (multiple) {
+ var paths = nodes.filter(function (node) {
+ if ((checkStrictly || node.leaf) && (!node.disabled || disabledFixed)) {
+ if (strictMode) {
+ // 严格模式下
+ // some:命中一个就为true
+ // every:全部命中为true
+ return value.some(function (levelValue) {
+ if (!Array.isArray(levelValue)) {
+ throw new Error("多选严格模式下,value必须是一个二维数组结构.");
+ }
+ var path = node.path;
+ return levelValue.length === path.length && levelValue.every(function (rowValue, index) {
+ return path[index].value === rowValue;
+ });
+ })
+ } else {
+ return value.indexOf(node.value) !== -1;
+ }
+ }
+ return false;
+ });
+ var nodeIds = paths.map(function (node) {
+ return node.nodeId;
+ });
+ this._setCheckedValue(nodeIds, paths);
+ // 展开第一个节点
+ if (paths.length > 0) {
+ var first = paths[0];
+ first.expandPanel();
+ }
+ } else {
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if ((checkStrictly || node.leaf)) {
+ var is = false;
+ if (strictMode) {
+ // 严格模式下
+ // every:全部命中为true
+ var path = node.path;
+ is = value.length === path.length && value.every(function (rowValue, index) {
+ return path[index].value === rowValue;
+ });
+ } else if (node.value === value) {
+ is = true;
+ }
+ if (is) {
+ this._setActiveValue(node.nodeId, node);
+ // 展开节点
+ node.expandPanel();
+ break;
+ }
+ }
+ }
+ }
+ },
+ /**
+ * 递归获取扁平的节点
+ * @param nodes
+ * @param container
+ * @returns {*[]}
+ */
+ getNodes: function (nodes, container) {
+ if (!container) {
+ container = [];
+ }
+ if (!nodes) {
+ nodes = this.data.nodes;
+ }
+ var self = this;
+ nodes.forEach(function (node) {
+ container.push(node);
+ var children = node.getChildren();
+ if (children) {
+ self.getNodes(children, container);
+ }
+ });
+ return container;
+ },
+ /**
+ * 初始化滚动条
+ * @param $menu 菜单的dom节点对象
+ * @param menuItem 当前菜单数据
+ * @private
+ */
+ _initScrollbar: function ($menu, menuItem) {
+ var $div = $('');
+ $menu.append($div);
+ var vertical = $($div[1]).find('.el-scrollbar__thumb');
+ var onhoriztal = $($div[0]).find('.el-scrollbar__thumb');
+ var scrollbar = $menu.find('.el-scrollbar__wrap');
+ var $panel = this.$panel;
+ var $lis = $menu.find('li');
+ var height = Math.max($panel.height(), $menu.height());
+ var hScale = (height - 6) / ($lis.height() * $lis.length);
+ var wScale = $panel.width() / $lis.width();
+
+ // 滚动条监听事件
+ function _scrollbarEvent(scrollTop, scrollLeft) {
+ if (hScale < 1) {
+ vertical.css('height', hScale * 100 + '%');
+ vertical.css('transform', 'translateY(' + scrollTop / $menu.height() * 100 + '%)');
+ }
+ if (wScale < 1) {
+ onhoriztal.css('width', wScale * 100 + '%');
+ onhoriztal.css('transform', 'translateY(' + scrollLeft / $menu.width() * 100 + '%)');
+ }
+ }
+
+ // 拖动事件
+ vertical.mousedown(function (event) {
+ event.stopImmediatePropagation();
+ event.stopPropagation();
+ // 禁止文本选择事件
+ var selectstart = function () {
+ return false;
+ };
+ $(document).bind("selectstart", selectstart);
+ var y = event.clientY;
+ var scrollTop = scrollbar.scrollTop();
+ // 移动事件
+ var mousemove = function (event) {
+ event.stopImmediatePropagation();
+ var number = scrollTop + (event.clientY - y) / hScale;
+ scrollbar.scrollTop(number);
+ };
+ $(document).bind('mousemove', mousemove);
+ // 鼠标松开事件
+ $(document).one('mouseup', function (event) {
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+ $(document).off('mousemove', mousemove);
+ $(document).off('selectstart', selectstart);
+ });
+ });
+ // 监听滚动条事件
+ scrollbar.scroll(function () {
+ var scroll = $(this);
+ menuItem.scrollbar.top = scroll.scrollTop()
+ menuItem.scrollbar.left = scroll.scrollLeft()
+ _scrollbarEvent(menuItem.scrollbar.top, menuItem.scrollbar.left);
+ });
+
+ // 初始化滚动条
+ scrollbar.scrollTop(menuItem.scrollbar.top);
+ _scrollbarEvent(menuItem.scrollbar.top, menuItem.scrollbar.left);
+ },
+ // 填充路径
+ _fillingPath: function () {
+ var multiple = this.props.multiple;
+ var showAllLevels = this.config.showAllLevels;
+ var separator = this.config.separator;
+ var collapseTags = this.config.collapseTags;
+ var $inputRow = this.$inputRow;
+ var placeholder = this.config.placeholder;
+ var self = this;
+ if (!multiple) {
+ var activeNode = this.data.activeNode;
+ var path = activeNode && activeNode.path || [];
+ if (showAllLevels) {
+ this._$inputRowSetValue(path.map(function (node) {
+ return node.label;
+ }).join(separator));
+ } else {
+ this._$inputRowSetValue(activeNode && activeNode.label || "");
+ }
+ } else {
+ // 复选框
+
+ // 删除标签
+ this.$tags.find('.el-tag').remove();
+ var $tagsInput = this.$tagsInput;
+ // 清除高度
+ $inputRow.css('height', '');
+ var checkedNodes = this.data.checkedNodes;
+ var minCollapseTagsNumber = Math.max(this.config.minCollapseTagsNumber, 1);
+ if (checkedNodes.length > 0) {
+ var tags = [];
+ var paths = checkedNodes;
+ if (collapseTags) {
+ // 折叠tags
+ paths = checkedNodes.slice(0, Math.min(checkedNodes.length, minCollapseTagsNumber));
+ }
+ paths.forEach(function (node) {
+ tags.push(node.$tag);
+ });
+ // 判断标签是否折叠
+ if (collapseTags) {
+ // 判断标签最小折叠数
+ if (checkedNodes.length > minCollapseTagsNumber) {
+ tags.push(self.get$tag('+ ' + (checkedNodes.length - minCollapseTagsNumber), false));
+ }
+ }
+ tags.forEach(function (tag) {
+ if ($tagsInput) {
+ $tagsInput.before(tag)
+ } else {
+ self.$tags.append(tag);
+ }
+ });
+ }
+ var tagHeight = self.$tags.height();
+ var inputHeight = $inputRow.height();
+ if (tagHeight > inputHeight) {
+ $inputRow.css('height', tagHeight + 4 + 'px');
+ }
+ // 重新定位
+ this._resetXY();
+ if (checkedNodes.length > 0) {
+ $inputRow.removeAttr('placeholder');
+ $tagsInput && $tagsInput.removeAttr('placeholder', placeholder);
+ } else {
+ $inputRow.attr('placeholder', placeholder);
+ $tagsInput && $tagsInput.attr('placeholder', placeholder);
+ }
+ }
+ },
+ /**
+ * 设置单选输入框的值
+ * @param label
+ * @private
+ */
+ _$inputRowSetValue: function (label) {
+ label = label || "";
+ var $inputRow = this.$inputRow;
+ $inputRow.attr('value', label); //防止被重置
+ $inputRow.val(label);
+ },
+ /**
+ * 获取复选框标签对象
+ * @param label
+ * @param showCloseIcon 是否显示关闭的icon
+ * @returns {jQuery|HTMLElement|*}
+ */
+ get$tag: function (label, showCloseIcon) {
+ var fromIcon = this.icons.from;
+ var closeIcon = this.icons.close;
+ var icon = showCloseIcon ? '' : '';
+ return $('' + label + '' + icon + '');
+ },
+ // 设置可清理
+ _setClear: function () {
+ var self = this;
+
+ function enter() {
+ self.$icon.removeClass(self.icons.down);
+ self.$icon.addClass(self.icons.close);
+ }
+
+ function out() {
+ self.$icon.removeClass(self.icons.close);
+ self.$icon.addClass(self.icons.down);
+ }
+
+ self.$div.mouseenter(function () {
+ enter();
+ });
+ self.$div.mouseleave(function () {
+ out();
+ });
+ self.$icon.off('click');
+ var multiple = this.props.multiple;
+ var clear;
+ if (multiple) {
+ clear = this.data.checkedNodeIds.length > 0;
+ } else {
+ clear = !!this.data.activeNodeId;
+ }
+ if (clear && !this.config.disabled && this.config.clearable) {
+ self.$icon.one('click', function (event) {
+ event.stopPropagation();
+ self.close();
+ self.clearCheckedNodes();
+ out();
+ self.$icon.off('mouseenter');
+ self.$div.off('mouseenter');
+ self.$div.off('mouseleave');
+ });
+ } else {
+ out();
+ self.$icon.off('mouseenter');
+ self.$div.off('mouseenter');
+ self.$div.off('mouseleave');
+ }
+ },
+ // 禁用
+ disabled: function (isDisabled) {
+ this.config.disabled = !!isDisabled;
+ if (this.config.disabled) {
+ this.$div.addClass('is-disabled');
+ this.$div.find('.el-input--suffix').addClass('is-disabled');
+ this.$inputRow.attr('disabled', 'disabled');
+ this.$tagsInput && this.$tagsInput.attr('disabled', 'disabled').hide()
+ } else {
+ this.$div.removeClass('is-disabled');
+ this.$div.find('.el-input--suffix').removeClass('is-disabled');
+ this.$inputRow.removeAttr('disabled');
+ this.$tagsInput && this.$tagsInput.removeAttr('disabled').show();
+ }
+ // 重新设置是否可被清理
+ this._setClear();
+ // 重新填充路径
+ this._fillingPath();
+ },
+ /**
+ * 当选中节点变化时触发 选中节点的值
+ * @param value 值
+ * @param node 节点
+ */
+ change: function (value, node) {
+ var multiple = this.props.multiple;
+ if (multiple) {
+ if (value && value.length > 0) {
+ this.$ec.attr('value', JSON.stringify(value));
+ // this.$ec.val(JSON.stringify(value));
+ } else {
+ this.$ec.removeAttr('value');
+ // this.$ec.val('');
+ }
+ } else {
+ this.$ec.attr('value', value || "");
+ // this.$ec.val(value);
+ }
+ // 填充路径
+ this._fillingPath();
+ this.event.change.forEach(function (e) {
+ typeof e === 'function' && e(value, node)
+ })
+ },
+ /**
+ * 当失去焦点时触发 (event: Event)
+ * @param eventId 不为空时,必须与closeEventId值相等,防止旧事件触发
+ */
+ close: function (eventId) {
+ if (this.showPanel && (!eventId || this.closeEventId === eventId)) {
+ this.showPanel = false;
+ this.$div.find('.layui-icon-down').removeClass('is-reverse');
+ this.$panel.slideUp(100);
+ this.visibleChange(false);
+ // 聚焦颜色
+ this.$input.removeClass('is-focus');
+ // 可搜索
+ var filterable = this.config.filterable;
+ if (filterable) {
+ this.isFiltering = false;
+ this._fillingPath();
+ }
+ this.event.close.forEach(function (e) {
+ typeof e === 'function' && e()
+ })
+ }
+ },
+ /**
+ * 当获得焦点时触发 (event: Event)
+ */
+ open: function () {
+ if (!this.showPanel) {
+ this.showPanel = true;
+ this.closeEventId++;
+ var self = this;
+ // 当前传播事件结束后,添加点击背景关闭面板事件
+ setTimeout(function () {
+ $(document).one('click', self.close.bind(self, self.closeEventId));
+ });
+ // 重新定位面板
+ this._resetXY();
+ // 箭头icon翻转
+ this.$div.find('.layui-icon-down').addClass('is-reverse');
+ this.$panel.slideDown(200);
+ this.visibleChange(true);
+ // 聚焦颜色
+ this.$input.addClass('is-focus');
+ this.event.open.forEach(function (e) {
+ typeof e === 'function' && e()
+ })
+ }
+ },
+ /**
+ * 下拉框出现/隐藏时触发
+ * @param visible 出现则为 true,隐藏则为 false
+ */
+ visibleChange: function (visible) {
+ },
+ /**
+ * 在多选模式下,移除Tag时触发 移除的Tag对应的节点的值
+ * @param tagValue 节点的值
+ * @param node 节点对象
+ */
+ removeTag: function (tagValue, node) {
+ },
+ /**
+ * 获取选中的节点值
+ * @returns {null|[]}
+ */
+ getCheckedValues: function () {
+ var strictMode = this.props.strictMode;
+ if (this.props.multiple) {
+ var checkedNodes = this.data.checkedNodes;
+ if (strictMode) {
+ return checkedNodes.map(function (node) {
+ return node.path.map(function (node1) {
+ return node1.value;
+ });
+ });
+ }
+ return checkedNodes.map(function (node) {
+ return node.value;
+ });
+ } else {
+ var activeNode = this.data.activeNode;
+ if (strictMode) {
+ return activeNode && activeNode.path.map(function (node) {
+ return node.value;
+ })
+ }
+ return activeNode && activeNode.value;
+ }
+ },
+ /**
+ * 获取选中的节点
+ * @returns {null|[]}
+ */
+ getCheckedNodes: function () {
+ var strictMode = this.props.strictMode;
+ if (this.props.multiple) {
+ var checkedNodes = this.data.checkedNodes;
+ if (strictMode) {
+ return checkedNodes && checkedNodes.map(function (node) {
+ return node.path;
+ });
+ }
+ return checkedNodes;
+ } else {
+ var activeNode = this.data.activeNode;
+ if (strictMode) {
+ return activeNode && activeNode.path;
+ }
+ return activeNode;
+ }
+ },
+ /**
+ * 清空选中的节点
+ * @param force 强制清理禁用固定节点
+ */
+ clearCheckedNodes: function (force) {
+ var multiple = this.props.multiple;
+ if (multiple) {
+ var disabledFixed = this.config.disabledFixed;
+ if (!force && disabledFixed) {
+ //禁用项被固定,则过滤出禁用项的选值出来
+ var checkedNodes = this.data.checkedNodes;
+ var disNodes = checkedNodes.filter(function (node) {
+ return node.disabled;
+ });
+ var nodeIds = disNodes.map(function (node) {
+ return node.nodeId;
+ });
+ this._setCheckedValue(nodeIds, disNodes);
+ } else {
+ this._setCheckedValue([], []);
+ }
+ } else {
+ this._setActiveValue(null, null);
+ }
+ }
+ };
+
+ var thisCas = function () {
+ var self = this;
+ return {
+ /**
+ * 设置选项值
+ * @param options
+ */
+ setOptions: function (options) {
+ self.setOptions(options);
+ },
+ /**
+ * 覆盖当前值
+ * @param value 单选时传对象,多选时传数组
+ */
+ setValue: function (value) {
+ self.setValue(value);
+ },
+ /**
+ * 当节点变更时,执行回调
+ * @param callback function(value,node){}
+ */
+ changeEvent: function (callback) {
+ self.event.change.push(callback);
+ },
+ /**
+ * 当面板关闭时,执行回调
+ * @param callback function(){}
+ */
+ closeEvent: function (callback) {
+ self.event.close.push(callback);
+ },
+ /**
+ * 当面板打开时,执行回调
+ * @param callback function(){}
+ */
+ openEvent: function (callback) {
+ self.event.open.push(callback);
+ },
+ /**
+ * 禁用组件
+ * @param disabled true/false
+ */
+ disabled: function (disabled) {
+ self.disabled(disabled);
+ },
+ /**
+ * 收起面板
+ */
+ close: function () {
+ self.close();
+ },
+ /**
+ * 展开面板
+ */
+ open: function () {
+ self.open();
+ },
+ /**
+ * 获取选中的节点,如需获取路径,使用node.path获取,将获取各级节点的node对象
+ * @returns {[]|*}
+ */
+ getCheckedNodes: function () {
+ return self.getCheckedNodes();
+ },
+ /**
+ * 获取选中的值
+ * @returns {[]|*}
+ */
+ getCheckedValues: function () {
+ return self.getCheckedValues();
+ },
+ /**
+ * 清空选中的节点
+ * @param force 强制清理禁用固定节点
+ */
+ clearCheckedNodes: function (force) {
+ self.clearCheckedNodes(force);
+ },
+ /**
+ * 展开面板到节点所在的层级
+ * @param value 节点值,只能传单个值,不允许传数组
+ */
+ expandNode: function (value) {
+ var nodes = self.getNodes(self.data.nodes);
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if (node.value === value) {
+ node.expandPanel();
+ break;
+ }
+ }
+ },
+ /**
+ * 获取当前配置副本
+ */
+ getConfig: function () {
+ return $.extend(true, {}, self.config);
+ },
+ /**
+ * 获取数据对象副本
+ * @returns {*}
+ */
+ getData: function () {
+ return $.extend(true, {}, self.data);
+ }
+ };
+ };
+
+ exports('layCascader', function (option) {
+ var ins = new Cascader(option);
+ return thisCas.call(ins);
+ });
+});