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('
    ' + isEmpty + '
    '); - 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('
    ' + isEmpty + '
    '); + 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); + }); +});