首页 文章 精选 留言 我的

精选列表

搜索[fota升级],共10000篇文章
优秀的个人博客,低调大师

uni-app 4.45 发布:性能与稳定性再升级

在移动开发领域,技术的每一次迭代都意味着更高效、更稳定的产品体验。近日,uni-app 4.45 版本正式发布,此次更新不仅修复了多个关键问题,还在性能与稳定性上实现了显著提升。 uni-app是一个使用Vue.js开发所有前端应用的框架,它支持一次编写代码,同时发布到iOS、Android、H5、以及各种小程序等多个平台,显著降低了跨平台开发的成本和时间,开源中国新版 APP 就是用 uni-app 开发的。 uni-app 4.45 版本修复了4.44版本部分vue3项目的API无法调用的问题。同时,微信小程序平台的vue2项目在调用getAppBaseInfo时出现的报错也被彻底解决。此外,针对鸿蒙真机的运行问题,uni-app 4.45 版本新增了提示信息,当手机与电脑端不在同一个局域网时,会在控制台窗口中给出明确提示,大大降低了开发者的调试难度。 在插件方面,uts插件在Web平台、App-iOS平台以及微信小程序平台上的getString等方法第二个参数(默认值)不生效的问题得到了修复。而在App-Android平台上,String.split的Java函数冲突问题也得到了解决,此外,App插件在Android和iOS平台上也进行了多项修复等。 特别值得一提的是,开源中国作为开源社区的先锋,其新版 APP 更是 uni-app 框架的杰出实践,诚邀你扫码或在应用市场搜索 “OSC 开源社区” APP下载体验,亲身感受它是否契合您的开发愿景与期待。

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

更新指南:Xinference v0.15.2 详细指引,轻松升级!📝

Xorbits Inference(Xinference)是一个 性能强大且功能全面的 分布式 推理框架。可用于大语言模型(LLM),语音识别模型,多模态模型等各种模型的推理。通过 Xorbits Inference,你可以轻松地 一键部署你自己的模型或内置的前沿开源模型 - https://github.com/xorbitsai/inference。无论你是研究者,开发者,或是数据科学家,都可以通过 Xorbits Inference 与最前沿的 AI 模型,发掘更多可能。 Xinference 的功能和亮点有: * 🌟 模型推理,轻而易举:大语言模型,语音识别模型,多模态模型的部署流程被大大简化。一个命令即可完成模型的部署工作。 * ⚡️ 前沿模型,应有尽有:框架内置众多中英文的前沿大语言模型,包括 baichuan,chatglm2 等,一键即可体验!内置模型列表还在快速更新中! * 🖥 异构硬件,快如闪电:通过 ggml,同时使用你的 GPU 与 CPU 进行推理,降低延迟,提高吞吐! * ⚙️ 接口调用,灵活多样:提供多种使用模型的接口,包括 OpenAI 兼容的 RESTful API(包括 Function Calling),RPC,命令行,web UI 等等。方便模型的管理与交互。 * 🌐 集群计算,分布协同:支持分布式部署,通过内置的资源调度器,让不同大小的模型按需调度到不同机器,充分使用集群资源。 * 🔌 开放生态,无缝对接:与流行的三方库无缝对接,包括 LangChain, LlamaIndex, Dify,以及 Chatbox。 Xinference v0.15.2 发布,Qwen 2.5 全系列登场 🎉。 更新指南 📝 pip:pip install 'xinference==0.15.2’ 📦 Docker:拉取最新版本即可,也可以直接在镜像内用 pip 更新 🚢 更新日志 📋 新增内置支持模型 🎈 qwen2.5 系列 🚀 BUG修复 🛠️ 修复 Dify 中使用 stable diffusion 在 Xinference 上生图的问题,现在可以使用 dify 的 stable diffusion tool 调用 Xinference 上的文生图模型,如 FLUX.1-dev 🖼️ 修复 CosyVoice 输出可能被截断的问题 🎤 修复自定义模型对老版本注册的模型的兼容问题 🔄 UI 🖥️ 修复注册非LLM自定义模型出错的问题 💻 即将废弃:🚀 v0.16.0 transformers引擎的continuous batching将代替现有的推理逻辑成为默认推理行为 🔄🤖。 我们感谢每一位参与的社区伙伴对 Xinference 的帮助和支持,也欢迎更多使用者和开发者参与体验和使用 Xinference。 欢迎您在 https://github.com/xorbitsai/inference 给我们一个 星标,这样你就可以在 GitHub 上及时收到每个新版本的通知。

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

文字识别技术升级:Airtest与PaddleOCR模型的协作小技巧

👆对私有云感兴趣可以进入公众号回复“私有云”哦。 一、前言 在进行自动化测试的过程中, ocr 文字识别一直是大家最想要实现以及最需要的能力,今天就来介绍一个由百度飞浆提供的一个免费的 ocr 识别库—— PaddleOCR ,以及探讨一下, PaddleOCR 与 Airtest 协作能擦出怎么样的火花~ 二、PaddleOCR介绍 PaddleOCR 是百度飞浆AI研发的一个 ocr 工具库,目前支持多种目标识别,更多的介绍可以戳官方项目去看看: https://github.com/PaddlePaddle/PaddleOCR ,我们今天来讲讲,在与Airtest协作中,我们可能会用到 PaddleOCR 的什么功能。 在使用 PaddleOCR 之前,需要在自己的本地环境中安装对应的 PaddleOCR 库,方便我们后续调用。我们建议是安装到本地的python环境内,后续将AirtestIDE的python环境更换为本地python环境即可。 #安装PaddlePaddlepip install paddlepaddle#安装PaddleOCR whl包pip install paddleocr 在开始使用之前,我们来了解一下 PaddleOCR 识别的效果以及返回的情况, PaddleOCR 主要是通过我们传入需要识别的图片,通过调用 PaddleOCR 模型库,去进行识别并返回识别结果或识别后的图片,例如下图这样: 通过 PaddleOCR 识别打印文字信息如下,分别以列表的形式返回了所识别到的文字的左上角坐标、右上角坐标、右下角坐标、左下角坐标、识别到的文字以及识别置信度: [[[154.0,9.0], [492.0,9.0], [492.0,33.0], [154.0,33.0]], ('小学数学第八册第三单元测试题',0.9987085461616516)][[[238.0,47.0], [319.0,47.0], [319.0,66.0], [238.0,66.0]], ('(小数乘法)',0.9819607138633728)][[[109.0,80.0], [160.0,80.0], [160.0,103.0], [109.0,103.0]], ('姓名:',0.9986250996589661)][[[305.0,81.0], [358.0,81.0], [358.0,104.0], [305.0,104.0]], ('班别:',0.9987398982048035)]............. 接下来,我们来看一下如何使用 PaddleOCR 库来识别图像中的文字,并与 Airtest 进行联动,实现基于 ocr 的文字识别点击功能。 首先,我们需要导入 PaddleOCR 库,并准备好要识别的图片文件。通过调用 PaddleOCR 的接口,我们可以将图片路径作为参数传入。在识别过程中, PaddleOCR 会在 result 变量中返回识别结果,其中包括文本框的坐标和对应的文字内容。 frompaddleocrimportPaddleOCR, draw_ocr# Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换# 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`ocr = PaddleOCR(use_angle_cls=True, lang="ch")# need to run only once to download and load model into memoryimg_path ='./imgs/11.jpg'#开始ocr识别图片文字信息result = ocr.ocr(img_path, cls=True) 通过 PaddleOCR 获取的图像中的文字信息,我们可以利用 Airtest 提供的接口和语句,创建一个名为 ocr_touch() 的函数。这个函数将执行以下步骤: 使用 Airtest 进行屏幕截图。 将截图传递给 PaddleOCR 进行文字识别。 从 PaddleOCR 获取识别到的文字及其在图像中的位置坐标。 利用 Airtest 根据坐标信息点击文字的中心点。 通过这种方式, ocr_touch() 函数将实现从识别到点击的自动化操作,简化了整个流程。具体参考代码如下: defocr_touch(target_text) :# 截屏pic_path=r"./now.png"snapshot(pic_path)# 使用PaddleOCR识别文字ocr_result = ocr.ocr(pic_path, cls=True)# 遍历识别结果,找到目标文字的坐标target_coords =Noneforlineinocr_result:forword_infoinline:#获取识别结果的文字信息textinfo = word_info[1][0]print(textinfo)iftarget_textintextinfo:# 获取文字的坐标(中心点)x1, y1 = word_info[0][0]x2, y2 = word_info[0][2]target_coords = ((x1 + x2) /2, (y1 + y2) /2)breakiftarget_coords:break# 点击坐标iftarget_coords:touch(target_coords)else:print(f"未找到目标文字:{target_text}") 三、PaddleOCR与Airtest协作案例 那么我们一起来通过一个小案例来看一下,在日常测试中的表现情况吧~ 参考代码: # -*- encoding=utf8 -*-__author__ ="Airtest"fromairtest.core.apiimport*auto_setup(__file__)frompaddleocrimportPaddleOCRdefocr_touch(target_text) :# 截屏当前画面pic_path=r"./now.png"snapshot(pic_path)# 使用PaddleOCR识别图片文字ocr_result = ocr.ocr(pic_path, cls=True)# 遍历识别结果,找到目标文字的坐标target_coords =Noneforlineinocr_result:forword_infoinline:#获取识别结果的文字信息textinfo = word_info[1][0]print(textinfo)iftarget_textintextinfo:# 获取文字的坐标(中心点)x1, y1 = word_info[0][0]x2, y2 = word_info[0][2]target_coords = ((x1 + x2) /2, (y1 + y2) /2)breakiftarget_coords:break# 使用Airtest点击坐标iftarget_coords:touch(target_coords)else:print(f"未找到目标文字:{target_text}")if__name__ =="__main__":# 初始化PaddleOCRocr = PaddleOCR(use_angle_cls=True, lang='ch')# 可以根据需要选择语言#打开微博start_app("com.sina.weibo")wait(Template(r"tpl1719891218354.png", record_pos=(0.021,0.998), resolution=(1080,2520)))# 传入并点击“发现”ocr_touch("发现")wait(Template(r"tpl1719891346582.png", record_pos=(0.008, -0.957), resolution=(1080,2520)))# 传入并点击“更多热搜”ocr_touch("更多热搜")wait(Template(r"tpl1719891584526.png", record_pos=(-0.291, -0.606), resolution=(1080,2520)))# 传入并点击“热搜”ocr_touch("热搜") 四、Airtest_ocr的表现情况 在 Airtest 私有云内,我们也有对应的一款商业化的 Airtest-ocr 的文字识别库,这个库已经封装好对应的 ocr 的识别以及对应的操作执行能力,具体介绍可以看 往期推文 ,我们是封装好了日常使用较多的操作,在购买我们的商业化版本后,可以直接调用的~ # 点击某文字ocr_touch("开始")# 与 ocr_click("开始") 等价# 双击某文字ocr_double_click("开始")# 某些情况下,文字处于按钮附近(比如按钮下方),使用offset设置点击偏移量ocr_touch("开始", offset=(0, -20))# 以“开始”文字的中心为起点, x轴不偏移,y轴向上偏移20个像素点ocr_double_click("开始", offset=(0, -20))# 指定高优先级区域 (version>=1.0.10)ocr_touch("开始", location=(100,100), threshold=0.98)# 对于置信度高于0.98的多个 “开始”, 优先点击靠近 (100,100) 的# ocr_swipe 与 swipe函数类似ocr_swipe("生活","学习")# 从“生活”滑向“学习”ocr_swipe("生活", vector=(0,100))# 从“生活”向下滑动100个像素ocr_swipe((780,1842), (780,1000))# 从坐标1滑向坐标2...... 我们 Airtest-ocr 的图片文字识别情况如下图: 与 PaddleOCR 相比,我们 Airtest-ocr 除了可以直接识别传入图片内容外,还可以动态识别所连接的设备画面。具体的动态识别情况如下(在本地python环境中已加入Airtest-ocr库): 五、小结 我们本期推文介绍了 PaddleOCR 的文字识别能力,以及如何与 Airtest 协作,实现在脚本中可以 ocr 文字识别并点击的操作。当然,我们这里只是简单的封装实现了一个识别文字并点击的操作,还有更多的日常操作可以通过自己的需求去进行封装实现。或者有需要的同学,也可以联系我们,购买我们 Airtest 私有云,就可以直接调用 Airtest-ocr 的操作接口~ 如果在测试的过程中,遇到了问题,或者有任何想要深入了解的知识点,欢迎在官方交流群(526033840)里告诉我们或者提交issue,也欢迎大家投稿其他不同的使用小技巧。 (猜你还想看 ) ▼ 自动化测试小技巧之Airtest-Selenium和Excel的无缝协作 Airtest-Selenium实操小课④:微信读书上阅读书籍 Airtest脚本的重构与优化:提升测试效率和可读性 本文分享自微信公众号 - AirtestProject(AirtestProject)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

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

ModStartBlog v9.5.0 博客编辑器升级,已知问题优化

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。 系统完全开源,基于 Apache 2.0 开源协议。 功能特性 丰富的模块市场,后台一键快速安装 会员模块通用且完整,支持完整的API调用 大文件分片上传,进度条显示,已上传文件管理 强大的模块扩展功能,所有模块可以无缝集成,支持在线安装、卸载模块 完善的开发助手,实现模块、主题的的一键创建 完善的后台权限管理,支持基于RBAC的权限管理系统 后台管理支持使用手机、平板、PC,无论何时何地都可方便管理 第三方登录(QQ、微信、微博、支付宝、微信小程序) 第三方支付支持(微信、支付宝、支付宝当面付、微信扫码、微信小程序) 第三方云存储支持,支持云储存分片上传(阿里云、百度云、华为云、腾讯云、FTP、七牛云、UCloud、又拍云) 第三方短信支持(阿里云、腾讯云、华为云、百度云、253云通讯、聚合、七牛云、融云、赛邮、UCloud、云片、网易云) V9.5.0版本更新 2024年06月21日ModStartBlog发布v9.5.0版本,增加了以下20个特性: [新功能] Grid、Form、Detail 支持自定义 view 和 viewData [新功能] 增加命令执行函数和数组多键值排序方法 [新功能] 文件流式下载工具类,加密方式默认使用安全 Key [新功能] Hidden 组件支持序列化类型,避免入库异常 [新功能] 后台支持自定义样式文件 modstart.admin.styles [系统优化] AgentUtil 工具类新增浏览器版本判断,方便不同场景兼容 [系统优化] Response 下载方法浏览器兼容优化 [系统优化] 临时文件清理调度默认调整到后台运行 [系统优化] 代码清理和重构优化 [系统优化] 搜索蜘蛛检测工具类优化 [系统优化] 组件 JsonIdItems 数据预览接口返回兼容 .records 格式 [系统优化] EChart 折线图样式优化 [系统优化] JsonKeyValue 字段显示优化 [系统优化] 并发访问原子类性能优化 [系统优化] 图片处理工具类水印处理方法增加图片类型检查 [系统优化] Grid 列表错误异常渲染优化 [系统优化] Grid 增加按钮默认使用 titleAdd 属性 [系统优化] Code 换行符默认替换为 \n 问题 [系统优化] 组件 Image、File、Video、Audio 支持一键恢复默认值 [Bug修复] 网站导航链接选择不支持博客多级分类 模块市场一键安装 系统内置模块市场,有行业应用、插件、云存储、云短信等功能模块,后台支持一键安装、启用、禁用、卸载,可快速搭建属于自己的系统应用。 系统演示与文档 码云仓库:https://gitee.com/modstart/ModStartBlog Github仓库:https://github.com/modstart/ModStartBlog 系统演示:https://blog.demo.tecmz.com/ 框架功能演示:https://demo.modstart.com/ 下载使用:https://modstart.com/download 开发者文档:https://modstart.com/doc 模块市场:https://modstart.com/store

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

逻辑升级,深度解析如何实现业务中的且或组件

在业务实现的过程中,时常会出现且或关系逻辑的拼接。逻辑运算的组合使用,是实现复杂业务规则和决策支持系统的关键技术。 目前袋鼠云的指标管理平台、客户数据洞察平台、数据资产平台都有在使用。并且,且或组件已经在 RC 5.0 中添加到组件库,企业现在可以更加灵活地构建和实施复杂的业务规则。 本文将从前期分析、组件封装、具体实现三个维度深入探讨如何实现业务中的且或组件。 前期分析 01 确定好数据结构 因为是嵌套结构,可以通过 ➕➖ 来增加层级或者数据,因此采用树形结构来存储数据。 export interface IFilterValue<T> { key: string; level?: number; // 当前节点的层级,用于判断一些按钮的展示 type?: number; // 当前节点的条件关系,1 | 2 rowValues?: T; // Form 节点的相关的信息(子节点无条件节点时才有) children?: IFilterValue<T>[]; // 子节点的信息(子节点存在条件节点时才有) } 上述的图片的数据为: { "key": "qTipLrlUt", "level": 1, "children": [ { "key": "B6Jrbqcfof", "type": 2, "level": 2, "children": [ { "rowValues": { "condition": 1, "rowPermission": "" }, "key": "deg8x8UgZ", "level": 2 }, { "key": "_sczw_1h8H", "type": 1, "level": 3, "children": [ { "key": "Z5UkUPJoA", "rowValues": { "condition": 1, "rowPermission": "" }, "level": 3 }, { "key": "MbpJILqHGx", "rowValues": { "condition": 1, "rowPermission": "" }, "level": 3 } ] } ] }, { "rowValues": { "condition": 1, "rowPermission": "" }, "key": "qx6bG0o5H", "level": 1 } ], "type": 1 } 02 明确每个操作按钮的实现 03 明确组件的封装 · 组件只希望实现条件节点/线条/操作按钮的展示,因此后面的组件需要作为参数 component 传入 · 组件对层级有一个控制,支持 maxLevel 来控制 · 每一次新增数据的时候,默认值需要传入 initValues · 支持两种模式:「编辑状态」和「查看状态」 · 支持受控和非受控两种模式 组件封装 01 FilterRules 提供给用户使用的组件,实现数据的增删改查操作,可以采用受控和非受控两种模式。它接受的参数如下: interface IProps<T> { value?: IFilterValue<T>; disabled?: boolean; maxLevel?: number; initValues: T; notEmpty?: { data: boolean; message?: string }; component: (props: IComponentProps<T>) => React.ReactNode; onChange?: (value: IFilterValue<T> | undefined) => void; } export const FilterRules = <T>(props: IProps<T>) => { const { component, maxLevel = 5, disabled = false, notEmpty = { data: true, message: '必须有一条数据' }, value, initValues, onChange, } = props; // 查找当前操作的节点 const finRelationNode = ( parentData: IFilterValue<T>, targetKey: string, needCurrent?: boolean, ): IFilterValue<T> | null | undefined => {}; const handleAddCondition = (keyObj: { key: string; isOut?: boolean }) => {}; // 增加新的数据,判断是在当前节点下新增或者新生成一个条件节点 const addCondition = ( treeNode: any, keyObj: { key: string; isOut?: boolean }, initRowValue: T, ) => {}; const handleDeleteCondition = (key: string) => {}; // 删除节点,删除当前节点下的一条数据或者是删除一个条件节点 const deleteCondition = (parentData: IFilterValue<T>, key: string) => {}; // 删除一个条件节点时,更新当前数据的层级 const updateLevel = (node: IFilterValue<T>) => {}; // 更改条件节点的条件 const handleChangeCondition = ( key: string, type: ROW_PERMISSION_RELATION, ) => {}; // 改变节点的的数据 const handleChangeRowValues = (key: string, values: T) => {}; return ( <RulesController<T> maxLevel={maxLevel} disabled={disabled} value={value} component={component} onAddCondition={handleAddCondition} onDeleteCondition={handleDeleteCondition} onChangeCondition={handleChangeCondition} onChangeRowValues={handleChangeRowValues} /> ); }; ● 编辑情况 · 非受控组件使用 <Form form={form}> <Form.Item name={'condition'}> <FilterRules<IRowValue> component={(props) => ( <RowColumnConfig columns={record?.columns ?? []} {...props} /> )} maxLevel={MAX_LEVEL} initValues={INIT_ROW_VALUES} /> </Form.Item> </Form>; // RowColumnConfig 实现,name 可能是 children[0].formValues <Form.Item name={['condition', ...name, 'column']} rules={[{ message: '请选择字段', required: true }]} initialValue={column} > <Select placeholder="请选择字段"> {columns.map((item) => ( <Option key={item} value={item}> {item} </Option> ))} </Select> </Form.Item>; // 最后通过 form.validateFields() 拿到的和上述的数据结构一致 · 受控组件使用 const [ruleData, setRuleData] = useState({ key: shortid(), level: 0, rowValues: { column: first.column, condition: first.condition, rowPermission: first?.value, }, }); <FilterRules<IRowValue> value={ruleData} component={(props) => ( <RowColumnConfig columns={record?.columns ?? []} {...props} /> )} maxLevel={MAX_LEVEL} initValues={INIT_ROW_VALUES} onChange={setRuleData} />; // 通过 ruleData 就能够拿到最后的结果 ● 查看使用 <FilterRules component={(props) => <RowColumnConfig columns={[]} {...props} />} disabled value={value} /> ● 编辑查看使用(后续新增) 上图为最后实现的效果,适用于部分数据禁用且可以编辑其他数据。常见业务情景:上一次保存的数据不可修改,但需要在当前基础上继续新增数据。 在这种使用模式下,FilterRules 组件上的 props 依旧为 false,通过设置 value 中每一个节点的 disabled 属性来实现上述功能。 // 修改 IFilterValue 的类型 // 💭注意,如果当前节点是条件节点,children 内节点的状态和当前节点的 disabled 息息相关 export interface IFilterValue<T> { key: string; level?: number; // 当前节点的层级,用于判断一些按钮的展示 type?: number; // 当前节点的条件关系,1 | 2 + disabled?: boolean; // 当前节点的状态 rowValues?: T; // Form 节点的相关的信息(子节点无条件节点时才有) children?: IFilterValue<T>[]; // 子节点的信息(子节点存在条件节点时才有) } 上述图片的数据结构如下: const INIT_CHECK_DATA = { key: shortid(), level: 0, type: 1, children: [ { rowValues: { input: '', }, disabled: true, key: shortid(), level: 1, }, { key: shortid(), type: 1, level: 2, disabled: true, children: [ { rowValues: { input: '', }, key: shortid(), level: 2, }, { key: shortid(), rowValues: { input: '', }, level: 2, }, ], }, { rowValues: { input: '', }, key: shortid(), level: 1, }, { rowValues: { input: '', }, key: shortid(), level: 1, }, ], }; 在这种模式下,要去计算对应的高度和渲染正确的样式时,对于其 disabled 的计算需要改为 FilterRules 的 disabled 和当前节点的 disabled 做整合,disabled || !!item.disabled。 02 RulesController 做节点的展示,渲染正确的组件。 具体实现 01 编辑时高度计算 ● 计算每个节点的高度 · 如果是普通节点(蓝色),它的高度为 ITEM_HEIGHT + MARGIN (输入框的高度 + marginBottom) · 如果是条件节点(灰色),它的高度为 children 中每一个节点的高度 + 添加节点的高度 ITEM_HEIGHT const calculateTreeItemHeight = (item, isEdit) => { if (!item?.children) return weakMap.set(item, { height: ITEM_HEIGHT + MARGIN, lineHeight: ITEM_HEIGHT, }); item.children.map((child) => calculateTreeItemHeight(child, disabled)); const height = item.children.reduce( (prev, curr) => prev + weakMap.get(curr).height, ITEM_HEIGHT, ); weakMap.set(item, { height }); }; ● 计算每个节点的连线高度 · 如果是最后一个条件节点 线条长度(红色线条)为:块级高度 - (第一个节点高度 - MARGIN)/2 - 最后一个节点/2 · 如果不是最后一个条件节点 线条长度为:firstNodeLineHeight + 剩余子节点高度 + 添加节点/2 a.第一个子节点是普通节点(蓝色线条):firstNodeLineHeight = 节点高度/2 + MARGIN b.第一个子节点是条件节点(绿色线条):firstNodeLineHeight = 子节点线条高度 + 添加节点/2 const calculateTreeItemHeight = (item: IFilterValue<T>, disabled: boolean) => { if (!item?.children) return weakMap.set(item, { height: ITEM_HEIGHT + MARGIN, lineHeight: ITEM_HEIGHT, }); item.children.map((child) => calculateTreeItemHeight(child, disabled)); const isLastCondition = !item.children.some(isCondition); const firstNodeIsCondition = isCondition(item.children[0]); const height = item.children.reduce( (prev, curr) => prev + weakMap.get(curr).height, ITEM_HEIGHT, ); let lineHeight; // 如果当前节点是最后的判断节点 if (isLastCondition) { const firstNodeLineHeight = weakMap.get(item.children[0]).height - MARGIN; const lastNodeHeight = ITEM_HEIGHT; lineHeight = height - firstNodeLineHeight / 2 - lastNodeHeight / 2; } else { const firstNodeLineHeight = firstNodeIsCondition ? weakMap.get(item.children[0]).lineHeight / 2 + ITEM_HEIGHT / 2 : ITEM_HEIGHT / 2 + MARGIN; lineHeight = firstNodeLineHeight + item.children ?.slice(1) .reduce( (prev, curr) => prev + weakMap.get(curr).height, ITEM_HEIGHT / 2, ); } weakMap.set(item, { height, lineHeight }); }; 02 查看时高度计算 ● 计算每个节点的高度 节点高度,等于每一个节点的高度之和。 const calculateTreeItemHeight = (item: IFilterValue<T>, disabled: boolean) => { if (!item?.children) return weakMap.set(item, { height: ITEM_HEIGHT + MARGIN, lineHeight: ITEM_HEIGHT, }); item.children.map((child) => calculateTreeItemHeight(child, disabled)); const height = item.children.reduce( (prev, curr) => prev + weakMap.get(curr).height, 0, ); weakMap.set(item, { height }); }; 具体的高度图如下图所示: ● 计算每个节点的连线高度 连线高度为:firstNodeLineHeight + 中间节点高度 + lastNodeLineHeight · 如果是最后一个条件节点 lineHeight(红色)= 块级高度(蓝色)- MARGIN - ITEM_HEIGHT/2 - ITEM_HEIGHT/2(紫色) · 如果不是最后一个条件节点,需要根据其子节点再做计算 对于上述这种情况,我们需要递归计算当前条件节点的第一个节点应该减去的高度和最后节点应该减去的高度(蓝色部分)。 const firstNodeLineHeight = firstNode.height - getNodeReduceHeight(item, true); const lastNodeLineHeight = lastNode.height - MARGIN - getNodeReduceHeight(item, false); // 如果是普通节点,返回值为 ITEM_HEIGHT / 2 // 如果是条件节点,返回值 currentNode.lineHeight /2 + getNodeReduceHeight(currentNode, isFirst)。需要递归遍历对应的节点算出总共要减去的高度 const getNodeReduceHeight = (item: IFilterValue<T>, isFirst) => { const currentNode = isFirst ? item?.children?.[0] : item?.children?.[item?.children?.length - 1]; if (!currentNode) return ITEM_HEIGHT / 2; const currentNodeIsCondition = isCondition(currentNode); if (currentNodeIsCondition) { return ( currentNode.lineHeight / 2 + getNodeReduceHeight(currentNode, isFirst) ); } return ITEM_HEIGHT / 2; }; 03 添加新内容 · 最外层的添加(红色) 直接操作当前层级(最外层)的 children,添加一组 INIT_ROW_VALUES · 嵌套层的最下添加按钮(黄色) 获取到当前层的 children,添加一组 INIT_ROW_VALUES · 嵌套层的每一行添加按钮(紫色) 会新增一个嵌套关系 // 根据点击的按钮,来获取相关的 Node,对于红色/黄色按钮来说获取当前层级 Node const finRelationNode = ( parentData: IFilterValue<T>, targetKey: string, needCurrent?: boolean, ) => { const parentDataTemp = parentData; if (parentDataTemp.key === targetKey) return parentDataTemp; if (!parentDataTemp.children?.length) return null; for (let i = 0; i < parentDataTemp.children.length; i++) { const current = parentDataTemp.children[i]; if (current.key === targetKey) return needCurrent ? current : parentDataTemp; const node: IFilterValue<T> | null | undefined = finRelationNode( current, targetKey, needCurrent, ); if (node) return node; } }; const handleAddCondition = (keyObj: { key: string; isOut?: boolean }) => { const cloneData = clone(value); const appendNode = finRelationNode( cloneData as IFilterValue<T>, keyObj.key, keyObj.isOut, ); addCondition(appendNode, keyObj, initValues as T); onChange?.(cloneData); }; const addCondition = ( treeNode: any, keyObj: { key: string; isOut?: boolean }, initRowValue: T, ) => { const key = keyObj.key; if (keyObj.isOut) return treeNode.children.push( Object.assign( {}, { rowValues: initRowValue }, { key: shortId(), level: treeNode.level }, ), ); const children = treeNode?.children; if (!children) { const newNode = { key: treeNode.key, level: treeNode.level + 1, type: ROW_PERMISSION_RELATION.AND, children: [ { rowValues: treeNode.rowValues, key: shortId(), level: treeNode?.level + 1, }, { rowValues: initRowValue, key: shortId(), level: treeNode?.level + 1 }, ], }; delete treeNode.rowValues; Object.assign(treeNode, newNode); return; } for (let i = 0; i < children.length; i += 1) { if (children[i].key !== key) continue; if (treeNode?.level <= maxLevel) { children[i] = { key: children[i].key, type: ROW_PERMISSION_RELATION.AND, level: treeNode?.level + 1, children: [ Object.assign({}, children[i], { key: shortId(), level: treeNode?.level + 1, }), Object.assign({ key: shortId(), rowValues: initRowValue, level: treeNode?.level + 1, }), ], }; } } }; 04 点击删除内容 · 点击紫色按钮,第二个条件节点只剩一个 children,需要删除第二个条件节点,且重新计算每一行的层级 · 点击黄色按钮,当前条件节点的 children 删除一行数据 const deleteCondition = (parentData: IFilterValue<T>, key: string) => { let parentDataTemp = parentData; parentDataTemp.children = parentDataTemp?.children?.filter( (item) => item.key !== key, ); if (parentDataTemp?.children?.length === 1) { const newChild = updateLevel(parentDataTemp.children[0]); const key = parentDataTemp.key; delete parentDataTemp.children; delete parentDataTemp.type; parentDataTemp = Object.assign(parentDataTemp, { ...newChild, key, level: newChild.level, }); } }; const updateLevel = (node: IFilterValue<T>) => { let newChildren; if (node.children) newChildren = node.children.map((element) => updateLevel(element)); const newNode: IFilterValue<T> = { ...node, children: newChildren, level: (node?.level as number) - 1, }; return newNode; }; 05 切换条件节点 获取到当前层级的节点,改变对应的 type 值。 const handleChangeCondition = (key: string, type: ROW_PERMISSION_RELATION) => { const cloneData = clone(value); const changeNode = finRelationNode(cloneData, key, true); changeNode.type = type === ROW_PERMISSION_RELATION.AND ? ROW_PERMISSION_RELATION.OR : ROW_PERMISSION_RELATION.AND; onChange?.(cloneData); }; 06 改变组件数据 const handleChangeRowValues = (key: string, values: T) => { const cloneData = clone(value); const changeNode = finRelationNode(cloneData, key, true); changeNode.rowValues = { ...(changeNode.rowValues ?? {}), ...values, }; onChange?.(cloneData); }; 至此,且或组件已经实现完成,FilterRules 主要是操作数据,RuleController 主要是条件/线条/组件的渲染,支持用户自定义 component 传入 FilterRules。 《行业指标体系白皮书》下载地址:https://www.dtstack.com/resources/1057?src=szsm 《数栈产品白皮书》下载地址:https://www.dtstack.com/resources/1004?src=szsm 《数据治理行业实践白皮书》下载地址:https://www.dtstack.com/resources/1001?src=szsm 想了解或咨询更多有关大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=szkyzg

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

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部分的功能。

用户登录
用户注册