首页 文章 精选 留言 我的

精选列表

搜索[开源鸿蒙],共10000篇文章
优秀的个人博客,低调大师

给 Antd Table 组件编写缩进指引线、子节点懒加载等功能,如何二次封装开源组件?

在业务需求中,有时候我们需要基于 antd 之类的组件库定制很多功能,本文就以我自己遇到的业务需求为例,一步步实现和优化一个树状表格组件,这个组件会支持: ✨ 每个层级 缩进指示线 ✨ 远程 懒加载子节点 ✨ 每个层级支持 分页 本系列分为两篇文章,这篇只是讲这些业务需求如何实现。 而下一篇,我会讲解怎么给组件也设计一套简单的插件机制,来解决代码耦合,难以维护的问题。 功能实现 层级缩进线 antd 的 Table 组件默认是没有提供这个功能的,它只是支持了树状结构: consttreeData=[ { function_name:`ReactTreeReconciliation`, count:100, children:[ { function_name:`ReactTreeReconciliation2`, count:100 } ] } ] 展示效果如下: antd-table 可以看出,在展示大量的函数堆栈的时候,没有缩进线就会很难受了,业务方也确实和我提过这个需求,可惜之前太忙了,就暂时放一边了。😁 参考 VSCode 中的缩进线效果,可以发现,缩进线是和节点的层级紧密相关的。 vscode 比如 src 目录对应的是第一级,那么它的子级 client 和 node 就只需要在 td 前面绘制一条垂直线,而 node 下的三个目录则绘制两条垂直线。 第 1 层:| text 第 2 层:|| text 第 3 层:||| text 只需要在自定义渲染单元格元素的时候,得到以下两个信息。 当前节点的层级信息。 当前节点的父节点是否是展开状态。 所以思路就是对数据进行一次递归处理,把层级写在节点上,并且要把父节点的引用也写上,之后再通过传给 Table 的 expandedRowKeys 属性来维护表格的展开行数据。 这里我是直接改写了原始数据,如果需要保证原始数据干净的话,也可以参考 React Fiber 的思路,构建一颗替身树进行数据写入,只要保留原始树节点的引用即可。 /** *递归树的通用函数 */ consttraverseTree=( treeList, childrenColumnName, callback )=>{ consttraverse=(list,parent=null,level=1)=>{ list.forEach(treeNode=>{ callback(treeNode,parent,level); const{[childrenColumnName]:next}=treeNode; if(Array.isArray(next)){ traverse(next,treeNode,level+1); } }); }; traverse(treeList); }; functionrewriteTree({dataSource}){ traverseTree(dataSource,childrenColumnName,(node,parent,level)=>{ //记录节点的层级 node[INTERNAL_LEVEL]=level //记录节点的父节点 node[INTERNAL_PARENT]=parent }) } 之后利用 Table 组件提供的 components 属性,自定义渲染 Cell 组件,也就是 td 元素。 constcomponents={ body:{ cell:(cellProps)=>( <TreeTableCell {...props} {...cellProps} expandedRowKeys={expandedRowKeys} /> ) } } 之后,在自定义渲染的 Cell 中,只需要获取两个信息,只需要根据层级和父节点的展开状态,来决定绘制几条垂直线即可。 constisParentExpanded=expandedRowKeys.includes( record?.[INTERNAL_PARENT]?.[rowKey] ) //只有当前是展示指引线的列且父节点是展开节点才会展示缩进指引线 if(dataIndex!==indentLineDataIndex||!isParentExpanded){ return<tdclassName={className}>{children}</td> } //只要知道层级就知道要在 td 中绘制几条垂直指引线举例来说: //第 2 层:|| text //第 3 层:||| text constlevel=record[INTERNAL_LEVEL] constindentLines=renderIndentLines(level) 这里的实现就不再赘述,直接通过绝对定位画几条垂直线,再通过对 level 进行循环时的下标 index 决定 left 的偏移值即可。 效果如图所示: 缩进线 远程懒加载子节点 这个需求就需要用比较 hack 的手段实现了,首先观察了一下 Table 组件的逻辑,只有在有 children 的子节点上才会展示「展开更多」的图标。 所以思路就是,和后端约定一个字段比如 has_next,之后预处理数据的时候先遍历这些节点,加上一个假的占位 children。 之后在点击展开的时候,把节点上的这个假 children 删除掉,并且把通过改写节点上一个特殊的 is_loading 字段,在自定义渲染 Icon 的代码中判断,并且展示 Loading Icon。 又来到递归树的逻辑中,我们加入这样的一段代码: functionrewriteTree({dataSource}){ traverseTree(dataSource,childrenColumnName,(node,parent,level)=>{ if(node[hasNextKey]){ //树表格组件要求next必须是非空数组才会渲染「展开按钮」 //所以这里手动添加一个占位节点数组 //后续在onExpand的时候再加载更多节点并且替换这个数组 node[childrenColumnName]=[generateInternalLoadingNode(rowKey)] } }) } 之后我们要实现一个 forceUpdate 函数,驱动组件强制渲染: const[_,forceUpdate]=useReducer((x)=>x+1,0) 再来到 onExpand 的逻辑中: constonExpand=async(expanded,record)=>{ if(expanded&&record[hasNextKey]&&onLoadMore){ //标识节点的loading record[INTERNAL_IS_LOADING]=true //移除用来展示展开箭头的假children record[childrenColumnName]=null forceUpdate() constchildList=awaitonLoadMore(record) record[hasNextKey]=false addChildList(record,childList) } onExpandProp?.(expanded,record) } functionaddChildList(record,childList){ record[childrenColumnName]=childList record[INTERNAL_IS_LOADING]=false rewriteTree({ dataSource:childList, parentNode:record }) forceUpdate() } 这里 onLoadMore 是用户传入的获取更多子节点的方法, 流程是这样的: 节点展开时,先给节点写入一个正在加载的标志,然后把子数据重置为空。这样虽然节点会变成展开状态,但是不会渲染子节点,然后强制渲染。 在加载完成后赋值了新的子节点 record[childrenColumnName] = childList 后,我们又通过 forceUpdate 去强制组件重渲染,展示出新的子节点。 需要注意,我们递归树加入逻辑的所有逻辑都在 rewriteTree 中,所以对于加入的新的子节点,也需要通过这个函数递归一遍,加入 level, parent 等信息。 新加入的节点的 level 需要根据父节点的 level 相加得出,不能从 1 开始,否则渲染的缩进线就乱掉了,所以这个函数需要改写,加入 parentNode 父节点参数,遍历时写入的 level 都要加上父节点已有的 level。 functionrewriteTree({ dataSource, //在动态追加子树节点的时候需要手动传入parent引用 parentNode=null }){ //在动态追加子树节点的时候需要手动传入父节点的level否则level会从1开始计算 conststartLevel=parentNode?.[INTERNAL_LEVEL]||0 traverseTree(dataSource,childrenColumnName,(node,parent,level)=>{ parent=parent||parentNode; //记录节点的层级 node[INTERNAL_LEVEL]=level+startLevel; //记录节点的父节点 node[INTERNAL_PARENT]=parent; if(node[hasNextKey]){ //树表格组件要求next必须是非空数组才会渲染「展开按钮」 //所以这里手动添加一个占位节点数组 //后续在onExpand的时候再加载更多节点并且替换这个数组 node[childrenColumnName]=[generateInternalLoadingNode(rowKey)] } }) } 自定义渲染 Loading Icon 就很简单了: //传入给Table组件的expandIcon属性即可 exportconstTreeTableExpandIcon=({ expanded, expandable, onExpand, record })=>{ if(record[INTERNAL_IS_LOADING]){ return<IconLoadingstyle={iconStyle}/> } } 功能完成,看一下效果: 远程懒加载 每个层级支持分页 这个功能和上一个功能也有点类似,需要在 rewriteTree 的时候根据外部传入的是否开启分页的字段,在符合条件的时候往子节点数组的末尾加入一个占位 Pagination 节点。 之后在 column 的 render 中改写这个节点的渲染逻辑。 改写 record: functionrewriteTree({ dataSource, //在动态追加子树节点的时候需要手动传入parent引用 parentNode=null }){ //在动态追加子树节点的时候需要手动传入父节点的level否则level会从1开始计算 conststartLevel=parentNode?.[INTERNAL_LEVEL]||0 traverseTree(dataSource,childrenColumnName,(node,parent,level)=>{ //加载更多逻辑 if(node[hasNextKey]){ //树表格组件要求next必须是非空数组才会渲染「展开按钮」 //所以这里手动添加一个占位节点数组 //后续在onExpand的时候再加载更多节点并且替换这个数组 node[childrenColumnName]=[generateInternalLoadingNode(rowKey)] } //分页逻辑 if(childrenPagination){ const{totalKey}=childrenPagination; constnodeChildren=node[childrenColumnName]||[]; const[lastChildNode]=nodeChildren.slice?.(-1); //渲染分页器,先加入占位节点 if( node[totalKey]>nodeChildren?.length&& //防止重复添加分页器占位符 !isInternalPaginationNode(lastChildNode,rowKey) ){ nodeChildren?.push?.(generateInternalPaginationNode(rowKey)); } } }) } 改写 columns: functionrewriteColumns(){ /** *根据占位符渲染分页组件 */ constrewritePaginationRender=(column)=>{ column.render=functionColumnRender(text,record){ if( isInternalPaginationNode(record,rowKey)&& dataIndex===indentLineDataIndex ){ return<Pagination/> } returnrender?.(text,record)??text } } columns.forEach((column)=>{ rewritePaginationRender(column) }) } 来看一下实现的分页效果: 重构和优化 随着编写功能的增多,逻辑被耦合在 Antd Table 的各个回调函数之中, 指引线的逻辑分散在 rewriteColumns, components中。 分页的逻辑被分散在 rewriteColumns 和 rewriteTree 中。 加载更多的逻辑被分散在 rewriteTree 和 onExpand 中 至此,组件的代码行数也已经来到了 300 行,大概看一下代码的结构,已经是比较混乱了: exportconstTreeTable=(rawProps)=>{ functionrewriteTree(){ //🎈加载更多逻辑 //🔖分页逻辑 } functionrewriteColumns(){ //🔖分页逻辑 //🏁缩进线逻辑 } constcomponents={ //🏁缩进线逻辑 } constonExpand=async(expanded,record)=>{ //🎈加载更多逻辑 } return<Table/> } 有没有一种机制,可以让代码按照功能点聚合,而不是散落在各个函数中? //🔖分页逻辑 constusePaginationPlugin=()=>{} //🎈加载更多逻辑 constuseLazyloadPlugin=()=>{} //🏁缩进线逻辑 constuseIndentLinePlugin=()=>{} exportconstTreeTable=(rawProps)=>{ usePaginationPlugin() useLazyloadPlugin() useIndentLinePlugin() return<Table/> } 没错,就是很像 VueCompositionAPI 和 React Hook 在逻辑解耦方面所做的改进,但是在这个回调函数的写法形态下,好像不太容易做到? 下一篇文章,我会聊聊如何利用自己设计的插件机制来优化这个组件的耦合代码。 记得关注后加我好友,我会不定期分享前端知识,行业信息。2021 陪你一起度过。 感谢大家 欢迎关注 ssh,前端潮流趋势、原创面试热点文章应有尽有。 记得关注后加我好友,我会不定期分享前端知识,行业信息。2021 陪你一起度过。 点赞并分享给你的朋友是最好的支持~ 本文分享自微信公众号 - 前端从进阶到入院(code_with_love)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

企业级java springcloud b2bc商城系统开源源码二次开发-负载均衡策略

简单轮询负载均衡以轮询的方式依次将请求调度不同的服务器,即每次调度执行i=(i+1) mod n 随机负载均衡随机选择状态为UP的server 加权响应时间负载均衡根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 区域感知负载均衡区域感知负载均衡内置电路跳闸逻辑,可被配置基于区域同源关系(Zone Affinity,也就是更倾向于选择发出调用的服务所在的托管区域内,这样可以降低延迟,节省成本)选择目标服务实例。它监控每个区域中运行实例的行为,而且能够实时的快速丢弃一整个区域。这样在面对整个区域故障时,帮我们提升了弹性。 Ribbon自带负载均衡策略比较从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。 JAVA ssm b2b2c多用户商城系统源码

优秀的个人博客,低调大师

wangEditor-基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、易用、开源免费(2)

1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>wangEditor上传图片到服务器</title> 7 </head> 8 9 <body> 10 11 <div id="editor"></div> 12 13 <!-- 注意, 只需要引用 JS,无需引用任何 CSS !!!--> 14 <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script> 15 <script type="text/javascript" src="script/wangEditor-3.1.1.js"></script> 16 <script type="text/javascript"> 17 // 声明富文本编辑器 18 var E = window.wangEditor; 19 20 // 初始化富文本编辑器 21 var editor = new E('#editor'); 22 23 // 上传图片到服务器 24 editor.customConfig.uploadImgServer = '/upload'; // 其中/upload是上传图片的服务器端接口 25 26 // 将图片大小限制为 3M 27 editor.customConfig.uploadImgMaxSize = 3 * 1024 * 1024; 28 29 // 默认为 10000 张(即不限制),需要限制可自己配置 30 // 限制一次最多上传 5 张图片 31 editor.customConfig.uploadImgMaxLength = 5; 32 33 // 上传图片时可自定义传递一些参数,例如传递验证的token等。参数会被添加到formdata中 34 editor.customConfig.uploadImgParams = { 35 // 如果版本 <=v3.1.0 ,属性值会自动进行 encode ,此处无需 encode 36 // 如果版本 >=v3.1.1 ,属性值不会自动 encode ,如有需要自己手动 encode 37 token: 'abcdef12345' 38 } 39 40 // 如果还需要将参数拼接到 url 中,可再加上如下配置 41 editor.customConfig.uploadImgParamsWithUrl = true; 42 43 // 上传图片时,可自定义filename,即在使用formdata.append(name, file)添加图片文件时,自定义第一个参数。 44 editor.customConfig.uploadFileName = 'yourFileName'; 45 46 // 上传图片时刻自定义设置 header 47 editor.customConfig.uploadImgHeaders = { 48 'Accept': 'text/x-json' 49 } 50 51 // 跨域上传中如果需要传递 cookie 需设置 withCredentials 52 editor.customConfig.withCredentials = true; 53 54 // 默认的 timeout 时间是 10 秒钟 55 // 将 timeout 时间改为 3s 56 editor.customConfig.uploadImgTimeout = 3000; 57 58 // 上传图片的错误提示默认使用alert弹出,你也可以自定义用户体验更好的提示方式 59 editor.customConfig.customAlert = function(info) { 60 // info 是需要提示的内容 61 alert('自定义提示:' + info); 62 } 63 64 // 可使用监听函数在上传图片的不同阶段做相应处理 65 editor.customConfig.uploadImgHooks = { 66 before: function(xhr, editor, files) { 67 // 图片上传之前触发 68 // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,files 是选择的图片文件 69 70 // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传 71 // return { 72 // prevent: true, 73 // msg: '放弃上传' 74 // } 75 }, 76 success: function(xhr, editor, result) { 77 // 图片上传并返回结果,图片插入成功之后触发 78 // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果 79 }, 80 fail: function(xhr, editor, result) { 81 // 图片上传并返回结果,但图片插入错误时触发 82 // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果 83 }, 84 error: function(xhr, editor) { 85 // 图片上传出错时触发 86 // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象 87 }, 88 timeout: function(xhr, editor) { 89 // 图片上传超时时触发 90 // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象 91 }, 92 93 // 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置 94 // (但是,服务器端返回的必须是一个 JSON 格式字符串!!!否则会报错) 95 customInsert: function(insertImg, result, editor) { 96 // 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!) 97 // insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果 98 99 // 举例:假如上传图片成功后,服务器端返回的是 {url:'....'} 这种格式,即可这样插入图片: 100 var url = result.url 101 insertImg(url) 102 103 // result 必须是一个 JSON 格式字符串!!!否则报错 104 } 105 } 106 107 // 如果想完全自己控制图片上传的过程,可以使用如下代码 108 editor.customConfig.customUploadImg = function(files, insert) { 109 // files 是 input 中选中的文件列表 110 // insert 是获取图片 url 后,插入到编辑器的方法 111 112 // 上传代码返回结果之后,将图片插入到编辑器中 113 insert(imgUrl) 114 } 115 116 // 创建编辑器1 117 editor.create(); 118 </script> 119 </body> 120 121 </html> GitHub:Fuck me on GitHub 留下你的足迹求推荐呦

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册