摘要:但是如果一刹那我不想选江疏影了,我想选张雨绮因为胸大,首先我要从霍思燕换到高圆圆,然后转到张雨绮,选中展示出来,这时候就要先删除霍思燕,然后把高圆圆和张雨绮进来。
QingUI是一个UI组件库写在前面
目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, Checkbox, Radio, Switch, InputNumber, Input
ES6语法编写,无依赖
原生模块化,Chrome63以上支持,请开启静态服务器预览效果,静态服务器传送门
采用CSS变量配置样式
辛苦造轮子,欢迎来github仓库star:QingUI
去年年底项目中尝试着写过一个分页的Angular组件,然后就有了写QingUI的想法
过程还是非常有意思的
接下来我会用几篇文章分别介绍每个组件的大概思路,请大家耐心等待
这一篇介绍Cascader级联选择器
点个star就是对我最好的支持
repo: QingUI少废话,先上图 数据
既然是级联选择器,数据肯定是树形结构,像这样
const data = [ { label: "霍思燕", sub: [ { label: "江疏影", }, { label: "倪妮", }, ], }, { label: "高圆圆", sub: [ { label: "张雨绮", }, { label: "宋佳", }, ], }, ];
你猜我最喜欢哪位女明星 :)
而且用户点到哪一个label上,就呼出下一级列表
那么问题来了,我怎么知道我当前在哪一级,又在哪一个分支?
所以我需要给每一个分支做标记,点到哪一个label上,就取出标记,该标记能指引我找到对应的数据分支
我的方法是这样的,新建一个字段queue,字段值是一个字符串形式的数字,位数表示当前是第几级,数字表示当前是第几个分支
比如说queue = 131;,表示当前在第一个分支下面的第三个分支下面的第一个分支
buildQueue(data, queue = "") { for (let i = 0; i < data.length; i++) { const item = data[i]; const sub = item.sub; const newQueue = `${queue}${i}`; item[this.queue] = newQueue; if (sub) { this.buildQueue(sub, newQueue); } } }
字段名其实我并没有用queue,因为有可能已经被占用,我用的是时间戳组成的hash值,所以赋值的时候要写成item[this.queue] = newQueue;
渲染的时候给DOM元素加一个data-v="${item[this.queue]}就行了
那么怎么读呢?
这些数字其实就是数组的索引,加一个递归搞定
findSubByQueue(data, queue) { const n = Number.parseInt(queue.charAt(0)); for (let i = 0; i < data.length; i++) { if (i === n) { if (queue.length > 1) { return this.findSubByQueue(data[i].sub, queue.slice(1)); } else { return data[i].sub; } } } }事件
级联肯定要支持点击和悬浮两种事件触发机制
所以我用this.eventType来保存事件类型,其实两种事件大部分代码是可以复用的
for (let i = 0; i < $trunks.length; i++) { const $trunk = $trunks[i]; const v = $trunk.dataset.v; const label = $trunk.querySelector(".label").innerHTML; const CL = $trunk.classList; $trunk.addEventListener(this.eventType, function(event) { event.stopPropagation(); // 遍历清除trunk的active self.removeTrunkActive($trunks); // 当前trunk变成active CL.add("active"); // 构建路径 self.buildPath(v.length, label, false); // 找到子数据 const sub = self.findSubByQueue(self.data, v); // 填充子board $subBoard.innerHTML = self.renderCascade(sub); // 添加事件 self.$rowEvent($subBoard); }); }
注释也写的很清楚,首先是一个高亮的处理,然后要把当前路径保存下来,通过queue找到子数据,然后渲染出来,最后给子节点添加事件
要知道,分支可以分为两种,一种是下面还有分支,我把它称作trunk,另一种是末梢,下面没有分支了,我把它称作leaf
点击很容易,只给trunk添加事件就可以了
但是悬浮,leaf也要有事件,就是把之前的高亮和子数据清空
if (this.trigger === "hover") { for (let i = 0; i < $leafs.length; i++) { $leafs[i].addEventListener("mouseenter", function() { self.removeTrunkActive($trunks); $subBoard.innerHTML = ""; }); } // 离开curtain this.$curtain.addEventListener("mouseleave", function() { self.removeTrunkActive(self.$trunks); self.$subBoard.innerHTML = ""; }); }
那么怎么选中呢?
到leaf才是一个完整的路径,所以leaf特殊处理,无论是什么事件,点击leaf选中,把路径渲染出来
路径保存路径是一个动态的过程
因为我可能查看了某一个分支,然后又查看另一个分支,最终选中了别的分支
所以保存路径要根据路径的长度和当前级别来确定是添加还是删除
比如我现在在江疏影这里,还没有选中,那么当前是第二级,路径里只保存了霍思燕,如果我选中,那么简单,直接把江疏影push到数组里,展示出来。但是如果一刹那我不想选江疏影了,我想选张雨绮(因为胸大),首先我要从霍思燕换到高圆圆,然后转到张雨绮,选中展示出来,这时候就要先删除霍思燕,然后把高圆圆和张雨绮push进来。
buildPath(level, label, render) { if (this.path.length < level) { // 往下选择,直接push this.path.push(label); } else { // 退回选择,根据退回长度删除path元素,再push this.path = [...this.path.slice(0, level - 1), label]; } if (render) { this.renderPath(); } }搜索
突发奇想,我又想加一个搜索功能
比如说我现在搜集了好几千个女明星,打乱格式化成树形数据,那我选择起来可就困难了,难道每一个分支都查看一遍吗?如果有搜索,我只要搜张雨绮,所有包含张雨绮的级联都展示出来,是不是方便很多!
看起来很复杂的样子
其实,换一个思路,初始化的时候就把所有的路径都遍历出来,缓存在一个数组里,搜索的时候只要检索这些字符串有没有张雨绮,是不是回到我们熟悉的字符串操作上来了?
遍历所有路径
iterateAllPath() { const self = this; let temp = []; const data = pathPush([...this.data]); function pathPush(data, arr = []) { for (const item of data) { item.path = []; // 将路径存入item中的数组 item.path.push(...arr, item.label); } return data; } function recursive(data) { for (const item of data) { const sub = item.sub; if (sub) { // 将下一层放入temp temp.push(...pathPush(sub, item.path)); } else { // 没有下一层则路径结束 self.pathPool.push(item.path.join(self.seperator)); } } if (temp.length) { // 重新初始化 data = temp; temp = []; recursive(data); } } recursive(data); }
for (const item of this.pathPool) { const match = item.match(reg); if (!match) { continue; } result.push(item); }
别急,还有需求,我想把关键词高亮
比如说我搜张雨绮,所有结果中张雨绮都要高亮
我搜张雨,所有结果中张雨都要高亮
这个也不复杂,用关键词把路径截成三段,如果关键词在首尾那就截成两段
这里有一个小问题,如果分隔符与关键词之间有空格,展示结果总是不符合预期
后来才发现,如果标签内第一个字符是空格,空格会被忽略
所以还需要小小的处理一下
const reg = new RegExp(value, "i"); for (const item of this.pathPool) { const match = item.match(reg); if (!match) { continue; } result.push(item); const index = match.index; let [left, center, right] = [item.slice(0, index), match[0], item.slice(index + value.length)]; // 如果标签内第一个字符是空格,空格会被忽略 if (right && right.startsWith(" ")) { right = ` ${right.trimLeft()}`; } tpl += `写在后面${left ? `${left}` : ""} ${center} ${right ? `${right}` : ""}`; } if (!tpl) { tpl = "No Result"; }
Cascader比较核心的逻辑就在这里了
相较前几篇文档,隔的时间有点长,不过Cascader不会让你失望的
如果觉得QingUI还不错,点个star激励一下老夫
repo: QingUI
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95316.html
摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...
摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...
摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...
摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...
摘要:我们就可以将这些请求合并,达到一定数量我们统一提交。总结一个比较生动的例子给大家讲解了一些多线程的具体运用。学习多线程应该多思考多动手,才会有比较好的效果。地址徒手撸框架系列文章地址徒手撸框架实现徒手撸框架实现 原文地址:https://www.xilidou.com/2018/01/22/merge-request/ 在高并发系统中,我们经常遇到这样的需求:系统产生大量的请求,但是这...
阅读 2217·2023-04-26 01:50
阅读 689·2021-09-22 15:20
阅读 2509·2019-08-30 15:53
阅读 1543·2019-08-30 12:49
阅读 1667·2019-08-26 14:05
阅读 2670·2019-08-26 11:42
阅读 2268·2019-08-26 10:40
阅读 2559·2019-08-26 10:38