首页 文章 精选 留言 我的

精选列表

搜索[价值共创],共10000篇文章
优秀的个人博客,低调大师

大模型微调训练过程中,API 中转网关容易被忽略的工程价值

很多开发者在做大模型 SFT 微调、RLHF 对齐训练时,更多聚焦数据集清洗、参数调优、LoRA 权重配置,却容易忽略推理阶段的 API 网关架构。微调完成之后,模型需要大量做验证集推理、prompt 迭代、效果评估,这个环节会产生海量 API 调用。如果架构设计不合理,会出现限流、网络抖动、token 统计混乱,直接拖慢整个训练迭代周期。不少团队会选择 API 中转站作为推理层网关,以此简化多模型管理工作,本文结合实际项目实践聊聊其中的利弊与选型要点。

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

全员反诈意识文化构建机制与双向价值研究 —— 基于 Regions Bank Fraud EDU 项目实证分析

AI 技术迭代推动电信与商业欺诈手段持续精细化、伪装化,传统单一技术防护体系难以覆盖人为认知漏洞带来的欺诈入侵风险,企业反诈治理重心逐步从硬件、系统技术防御转向全员反诈文化长效培育。美国 Regions Bank 推出的 Fraud EDU 全年常态化反诈教育项目,构建了覆盖员工入职、在岗、家庭延伸的完整反诈意识培育路径,形成以 “Stop.Call.Confirm” 标准化核验流程为核心、常态化模拟演练、网络基础设施运维规范、职场防护习惯向个人生活迁移为支撑的闭环治理模式,有效抵御商业邮件劫持(BEC)、 pretexting 电话伪装、社交信息泄露、家庭网络入侵等复合型欺诈攻击。本文以 Fraud EDU 项目实践样本为基础,系统拆解新型欺诈攻击依托人性弱点实施渗透的底层逻辑,梳理企业反诈文化建设的实施框架、落地路径与双重收益;结合反网络钓鱼技术专家芦笛针对 BEC 仿冒域名、AI 深度伪造欺诈的技术研判,区分技术防护短板与员工认知漏洞两类风险来源,论证反诈文化作为 “人工防火墙” 的不可替代性;从组织管理、教育培训、网络运维、公私场景联动四个维度搭建可落地的反诈文化长效建设体系。研究证实,常态化反诈意识培育并非一次性合规培训,而是兼具企业资产防护、员工个人家庭财产保护的双向增益型风险投资;当前多数组织反诈治理存在 “重技术设备、轻人文认知、割裂职场与个人防护场景” 的结构性短板,Fraud EDU 项目的实践经验可为国内金融、实体企业完善全员反诈治理体系提供实证参考。

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

他一个人写了个价值100万的软件,却用来开源了!

Dromara开源社区里有这么一款专业软件,作者Kevin笑称这软件得估值百万。今天小编就带大家一起来看看,这究竟是个什么玩意儿。 以下是小编与作者Kevin的对话摘录。 小编:这究竟是个啥软件啊? Kevin:专业术语叫量化交易软件。说人话就是,一个可以编程、并通过程序化执行交易逻辑,从而实现在期货、股票、外汇市场上进行买卖交易的软件。 小编:嗯,听上去好像很厉害的样子。真的能值100万么? Kevin:呵呵,其实一个软件值多少钱,得看你的估值模型。一般来讲,比较容易理解的估算模型有两种:一种是以投入成本估算的模型,比如一个软件投入了多少人多少工时,总花费成本是多少钱,那这个软件就值多少钱;还有一种是以它所带来的收入现金流来估算的模型,比如一年可以卖100万。 小编:那你这个是按哪一种算法呢? Kevin:其实都差不多。业界比较有名的付费商业软件,一般都是在1万元/年左右的价格。那么100万也就100个用户而已。要是按投入成本算,我的工时费还是蛮高的,具体就不告诉你了,哈哈。 小编:当时是怎么会想到开发这么个东西呢? Kevin:首先是兴趣。我很久之前就在想,怎么可以把交易逻辑写成程序,然后可以自动化执行。直到2015年才接触到了上面提到的那些商业软件。当时就买了来用,当时的1万块跟现在都不是一个概念,我可以说是咬着牙来买的。后来折腾了一年,交易上也没挣到什么钱,再加上这么贵的软件成本,就没继续买了。后来也了解到了一些其他的开源方案,一般都是用PYTHON开发的。我当时也试用了一下,感觉也不太好用。再后来接触到了一个用JAVA写的项目,因为我本身是做JAVA相关的开发的嘛,所以就特别感兴趣。但那个项目的作者没有提供足够的文档,对用户不太友好。结果我把心一横,求人不如求己嘛,就开始基于那个JAVA项目改造成自己心目中的样子。到目前为止,已经迭代了四个版本了。 小编:现在都有哪些功能了? Kevin:刚才提到的那些商业软件提供的功能,我这软件都能实现了。比如编写交易逻辑、执行交易逻辑、做历史回测、程序运行监控台等等。理论上说,那些商业软件能做的,我这也能做;他们做不了的,我这也能做。 小编:一路走来,迭代了四个版本,中途遇到过一些什么困难么? Kevin:个人觉得最难的莫过于软件建模。因为平时我们一般在公司做业务代码开发,大部分人都是做信息系统的开发,基本上都是面向数据库编程的。一般的信息系统的数据流,无非就是从接口流到数据库,又或者把请求转发到第三方的服务去,再把响应结果从第三方服务转回去前台。而这个交易平台,数据库只是一个辅助角色,很多计算要在运行时内存处理的,所以需要定义很多模型。最难的是,这玩意儿完全是要靠凭空定义出来的,没有一些最佳实践给你参考。所以要通过不断的尝试与迭代,才能找到一些比较好的设计方案。有几次都让我差点想放弃不做了。 小编:那后来是怎么坚持下来的呢? Kevin:很多时候就是太累了,才会萌生放弃的想法,然后不断自问我究竟是在折腾啥呢?每次我意识到自己这么丧的时候,我就去睡觉。睡醒一觉有了精神,很多之前没想通的问题就有了新的思路。就是这么坚持下来的。 小编:为什么选择走开源这条路呢? Kevin:一开始我其实没考虑太多,就是觉得有个自己的开源项目是件很酷的事情。但这一路走来,我也在不断反思这个问题,尤其是像log4j事件,给我触动挺大的。作者不单贡献了自己的时间,还招来了社区的一顿骂,这开源项目要是都这样做,以后都没人愿意做开源了。所以现在我觉得开源项目必须要有商业闭环,才能实现长久的发展。其实我们是已经有现成的模式可以参考的。开源项目本身就好比抖音上的网红,本质上就是网红模式。通过开源项目作为流量入口,把公域流量转化成私域流量,只要有流量就有变现的可能。这就是开源项目的商业化模式。 小编:那未来你这项目大概会怎么走呢? Kevin:我这项目的定位是针对入门级的量化交易爱好者的,用户需要有一些编程基础,也需要一些交易经验。目前我们的社群也有百来人吧,基本上都是这类人群为主。同时我也希望有更多志同道合的朋友来加入我们。我希望打造出一个生态,让每个社群的伙伴都能有所收获。比如有的在这里学会了做量化交易最终能从市场上赚到钱,有的提升了技术水平在职场上把钱赚回来,有的能从中找到一些付费服务机会为其他人提供服务等等。最后,我也分享一下项目的官网(opens new window),方便大家做更进一步的了解。 这个项目英文名叫Northstar代表北极星,喻意它可以帮我们在交易领域指明方向;中文名叫盈富,寓意着盈满则富。 项目面向的是单租户场景的个人应用软件,采用了 B/S 架构去解决交易平台会部署在云服务器7x24地运行的需求。项目主程序采用了JAVA + springboot的技术栈,前端监控台则采用js + vue的技术栈。 项目有三大精彩亮点: 项目的可扩展性设计:作者针对量化交易软件的特点,提供了高度可扩展的程序架构,使得用户可以按自己的需求自行扩展,轻松对接不同交易所的接口,以及实现不同类型的交易策略。 用户编程接口的简化设计:作者巧妙地采用了函数式编程模型,极大地简化了用户编写交易策略的难度。 交易平台的抽象模型设计:作者通过这个项目的磨炼,大大提升了系统架构设计能力,技术水平上升了一个数量级。 更多亮点欢迎各位自行挖掘。 附上项目的地址: 官网地址:https://northstar-doc-1gwbhfxd522a1168-1310148562.ap-shanghai.app.tcloudbase.com/(opens new window) 仓库地址:https://gitee.com/dromara/northstar(opens new window) 开源不易,欢迎点赞支持!

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

价值50亿美元:Dish与AT&T签署十年期网络服务协议

美国卫星电视运营商Dish Network签署了一项为期十年的战略服务协议,这将使AT&T取代T-Mobile美国成为其大约1000万移动虚拟网络运营(MVNO)用户的网络提供商。 协议使Dish Network能够获得AT&T的漫游和传输服务,在部署自己基于设施的网络时,该公司将利用这些服务实现5G服务。 Dish Network表示,它也将利用AT&T的网络在农村市场提供无线服务,在那里它目前提供卫星服务。 Dish Network首席运营官兼无线零售集团总裁John Swieringa解释说,这笔交易将为其Boost Mobile、Ting和Republic wireless的MVNO业务用户带来“增强的覆盖范围和服务”。 就在上周,AT&T宣布其5G网络覆盖了美国2.5亿用户,Dish network称这是一个里程碑,对其客户来说是一个利好消息。 这家运营商拥有Dish Network在卫星业务方面的最大竞争对手DirecTV,目前正将30%的股权分拆给德太投资(TPG Capital)。 协议条款允许AT&T在特定市场为其自己的客户接入Dish Network频谱。 在提交给美国证券交易委员会的文件中,Dish Network表示,它在协议期内将向AT&T支付至少50亿美元。 Cowen and Company高级股票研究分析师Colby Synesael估计,在协议签订的最初几年,Dish Network可能每年向AT&T支付逾5亿美元的费用,随着这家新兴运营商能够更多地依赖自己的网络,这笔费用将有所下降。 Synesael补充说,这笔交易似乎反映了Dish Network对T-Mobile美国的失望。 T-Mobile美国敦促联邦通信委员会重新拍卖12GHz频段的牌照,而Dish Network拥有大量该频段的牌照,目前用于卫星服务。 此外,T-Mobile美国计划关闭其CDMA网络,对一些Dish network用户构成威胁。 Dish Network正在构建一张原生云的开放式RAN网络,无线通过其MVNO业务来处理。该业务是通过收购Boost Mobile、Ting和Republic wireless建立的。

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

一道价值25k的腾讯递归组件面试题(Vue3 + TS 实现)

前言 小伙伴们好久不见,最近刚入职新公司,需求排的很满,平常是实在没时间写文章了,更新频率会变得比较慢。 周末在家闲着无聊,突然小弟过来紧急求助,说是面试腾讯的时候,对方给了个 Vue 的递归菜单要求实现,回来找我复盘。 正好这周是小周,没想着出去玩,就在家写写代码吧,我看了一下需求,确实是比较复杂,需要利用好递归组件,正好趁着这个机会总结一篇 Vue3 + TS 实现递归组件的文章。 需求 可以先在 Github Pages[1] 中预览一下效果。 需求是这样的,后端会返回一串可能有无限层级的菜单,格式如下: [{id:1,father_id:0,status:1,name:'生命科学竞赛',_child:[{id:2,father_id:1,status:1,name:'野外实习类',_child:[{id:3,father_id:2,status:1,name:'植物学'}],},{id:7,father_id:1,status:1,name:'科学研究类',_child:[{id:8,father_id:7,status:1,name:'植物学与植物生理学'},{id:9,father_id:7,status:1,name:'动物学与动物生理学'},{id:10,father_id:7,status:1,name:'微生物学'},{id:11,father_id:7,status:1,name:'生态学'},],},{id:71,father_id:1,status:1,name:'添加'},],},{id:56,father_id:0,status:1,name:'考研相关',_child:[{id:57,father_id:56,status:1,name:'政治'},{id:58,father_id:56,status:1,name:'外国语'},],},] 每一层的菜单元素如果有 _child 属性, 这一项菜单被选中以后就要继续展示这一项的所有子菜单,预览一下动图: 并且点击其中的任意一个层级,都需要把菜单的 完整的 id 链路 传递到最外层,给父组件请求数据用。比如点击了 科学研究类。那么向外 emit 的时候还需要带上它的第一个子菜单 植物学与植物生理学 的 id,以及它的父级菜单 生命科学竞赛 的 id,也就是 [1, 7, 8]。 每一层的样式还可以自己定制。 实现 这很显然是一个递归组件的需求,在设计递归组件的时候,我们要先想清楚数据到视图的映射。 在后端返回的数据中,数组的每一层可以分别对应一个菜单项,那么数组的层则就对应视图中的一行,当前这层的菜单中,被点击选中 的那一项菜单的 child 就会被作为子菜单数据,交给递归的 NestMenu 组件,直到某一层的高亮菜单不再有 child,则递归终止。 由于需求要求每一层的样式可能是不同的,所以再每次调用递归组件的时候,我们都需要从父组件的 props 中拿到一个 depth 代表层级,并且把这个 depth + 1 继续传递给递归的 NestMenu 组件。 重点主要就是这些,接下来编码实现。 先看 NestMenu 组件的 template 部分的大致结构: <template><divclass="wrap"><divclass="menu-wrap"><divclass="menu-item"v-for="menuItemindata">{{menuItem.name}}</div></div><nest-menu:key="activeId":data="subMenu":depth="depth+1"></nest-menu></div></template> 和我们预想设计中的一样, menu-wrap 代表当前菜单层, nest-menu 则就是组件本身,它负责递归的渲染子组件。 首次渲染 在第一次获取到整个菜单的数据的时候,我们需要先把每层菜单的选中项默认设置为第一个子菜单,由于它很可能是异步获取的,所以我们最好是 watch 这个数据来做这个操作。 //菜单数据源发生变化的时候默认选中当前层级的第一项constactiveId=ref<number|null>(null)watch(()=>props.data,(newData)=>{if(!activeId.value){if(newData&&newData.length){activeId.value=newData[0].id}}},{immediate:true,}) 现在我们从最上层开始讲起,第一层的 activeId 被设置成了 生命科学竞赛 的 id,注意我们传递给递归子组件的 data ,也就是 生命科学竞赛 的 child,是通过 subMenu 获取到的,它是一个计算属性: constgetActiveSubMenu=()=>{returndata.find(({id})=>id===activeId.value)._child}constsubMenu=computed(getActiveSubMenu) 这样,就拿到了 生命科学竞赛 的 child,作为子组件的数据传递下去了。 点击菜单项 回到之前的需求设计,在点击了菜单项后,无论点击的是哪层,都需要把完整的 id 链路通过 emit 传递到最外层去,所以这里我们需要多做一些处理: /***递归收集子菜单第一项的id*/constgetSubIds=(child)=>{constsubIds=[]consttraverse=(data)=>{if(data&&data.length){constfirst=data[0]subIds.push(first.id)traverse(first._child)}}traverse(child)returnsubIds}constonMenuItemClick=(menuItem)=>{constnewActiveId=menuItem.idif(newActiveId!==activeId.value){activeId.value=newActiveIdconstchild=getActiveSubMenu()constsubIds=getSubIds(child)//把子菜单的默认第一项ids也拼接起来向父组件emitcontext.emit('change',[newActiveId,...subIds])}} 由于我们之前定的规则是,点击了新的菜单以后默认选中子菜单的第一项,所以这里我们也递归去找子菜单数据里的第一项,放到 subIds 中,直到最底层。 注意这里的 context.emit("change", [newId, ...subIds]);,这里是把事件向上 emit,如果这个菜单是中间层级的菜单,那么它的父组件也是 NestMenu,我们需要在父层级递归调用 NestMenu 组件的时候监听这个 change 事件。 <nest-menu:key="activeId"v-if="activeId!==null":data="getActiveSubMenu()":depth="depth+1"@change="onSubActiveIdChange"></nest-menu> 在父层级的菜单接受到了子层级的菜单的 change 事件后,需要怎么做呢?没错,需要进一步的再向上传递: constonSubActiveIdChange=(ids)=>{context.emit('change',[activeId.value].concat(ids))} 这里就只需要简单的把自己当前的 activeId 拼接到数组的最前面,再继续向上传递即可。 这样,任意一层的组件点击了菜单后,都会先用自己的 activeId 拼接好所有子层级的默认 activeId,再一层层向上 emit。并且向上的每一层父菜单都会把自己的 activeId 拼在前面,就像接力一样。 最后,我们在应用层级的组件里,就可以轻松的拿到完整的 id 链路: <template><nest-menu:data="menu"@change="activeIdsChange"/></template>exportdefault{methods:{activeIdsChange(ids){this.ids=ids;console.log("当前选中的id路径",ids);},}, 样式区分 由于我们每次调用递归组件的时候,都会把 depth + 1,那么就可以通过把这个数字拼接到类名后面来实现样式区分了。 <template><divclass="wrap"><divclass="menu-wrap":class="`menu-wrap-${depth}`"><divclass="menu-item">{{menuItem.name}}</div></div><nest-menu/></div></template><style>.menu-wrap-0{background:#ffccc7;}.menu-wrap-1{background:#fff7e6;}.menu-wrap-2{background:#fcffe6;}</style> 默认高亮 上面的代码写完后,应对没有默认值时的需求已经足够了,这时候面试官说,产品要求这个组件能通过传入任意一个层级的 id 来默认展示高亮。 其实这也难不倒我们,稍微改造一下代码,在父组件里假设我们通过 url 参数或者任意方式拿到了一个 activeId,先通过深度优先遍历的方式查找到这个 id 的所有父级。 constactiveId=7constfindPath=(menus,targetId)=>{letidsconsttraverse=(subMenus,prev)=>{if(ids){return}if(!subMenus){return}subMenus.forEach((subMenu)=>{if(subMenu.id===activeId){ids=[...prev,activeId]return}traverse(subMenu._child,[...prev,subMenu.id])})}traverse(menus,[])returnids}constids=findPath(data,activeId) 这里我选择在递归的时候带上上一层的 id,在找到了目标 id 以后就能轻松的拼接处完整的父子 id 数组。 然后我们把构造好的 ids 作为 activeIds 传递给 NestMenu,此时这时候 NestMenu 就要改变一下设计,成为一个「受控组件」,它的渲染状态是受我们外层传递的数据控制的。 所以我们需要在初始化参数的时候改变一下取值逻辑,优先取 activeIds[depth] ,并且在点击菜单项的时候,要在最外层的页面组件中,接收到 change 事件时,把 activeIds 的数据同步改变。这样继续传递下去才不会导致 NestMenu 接收到的数据混乱。 <template><nest-menu:data="data":defaultActiveIds="ids"@change="activeIdsChange"/></template> NestMenu 初始化的时候,对有默认值的情况做一下处理,优先使用数组中取到的 id 值。 setup(props:IProps,context){const{depth=0,activeIds}=props;/***这里activeIds也可能是异步获取到的所以用watch保证初始化*/constactiveId=ref<number|null|undefined>(null);watch(()=>activeIds,(newActiveIds)=>{if(newActiveIds){constnewActiveId=newActiveIds[depth];if(newActiveId){activeId.value=newActiveId;}}},{immediate:true,});} 这样,如果 activeIds 数组中取不到的话,默认还是 null,在 watch 到菜单数据变化的逻辑中,如果 activeId 是 null 的话,会被初始化为第一个子菜单的 id。 watch(()=>props.data,(newData)=>{if(!activeId.value){if(newData&&newData.length){activeId.value=newData[0].id}}},{immediate:true,}) 在最外层页面容器监听到 change 事件的时候,要把数据源同步一下: <template><nest-menu:data="data":activeIds="ids"@change="activeIdsChange"/></template><script>import{ref}from"vue";exportdefault{name:"App",setup(){constactiveIdsChange=(newIds)=>{ids.value=newIds;};return{ids,activeIdsChange,};},};</script> 如此一来,外部传入 activeIds 的时候,就可以控制整个 NestMenu 的高亮选中逻辑了。 数据源变动引发的 bug。 这时候,面试官对着你的 App 文件稍作改动,然后演示了这样一个 bug: App.vue 的 setup 函数中加了这样的一段逻辑: onMounted(()=>{setTimeout(()=>{menu.value=[data[0]].slice()},1000)}) 也就是说,组件渲染完成后过了一秒,菜单的最外层只剩下一项了,这时候面试官在一秒之内点击了最外层的第二项,这个组件在数据源改变之后,会报错: 这是因为数据源已经改变了,但是组件内部的 activeId 状态依然停留在了一个已经不存在了的 id 上。 这会导致 subMenu 这个 computed 属性在计算时出错。 我们对 watch data 观测数据源的这段逻辑稍加改动: watch(()=>props.data,(newData)=>{if(!activeId.value){if(newData&&newData.length){activeId.value=newData[0].id}}//如果当前层级的data中遍历无法找到`activeId`的值说明这个值失效了//把它调整成数据源中第一个子菜单项的idif(!props.data.find(({id})=>id===activeId.value)){activeId.value=props.data?.[0].id}},{immediate:true,//在观测到数据变动之后同步执行这样会防止渲染发生错乱flush:'sync',}) 注意这里的 flush: "sync" 很关键,Vue3 对于 watch 到数据源变动之后触发 callback 这一行为,默认是以 post 也就是渲染之后再执行的,但是在当前的需求下,如果我们用错误的 activeId 去渲染,就会直接导致报错了,所以我们需要手动把这个 watch 变成一个同步行为。 这下再也不用担心数据源变动导致渲染错乱了。 完整代码 App.vue <template><nest-menu:data="data":activeIds="ids"@change="activeIdsChange"/></template><script>import{ref}from"vue";importNestMenufrom"./components/NestMenu.vue";importdatafrom"./menu.js";import{getSubIds}from"./util";exportdefault{name:"App",setup(){//假设默认选中id为7constactiveId=7;constfindPath=(menus,targetId)=>{letids;consttraverse=(subMenus,prev)=>{if(ids){return;}if(!subMenus){return;}subMenus.forEach((subMenu)=>{if(subMenu.id===activeId){ids=[...prev,activeId];return;}traverse(subMenu._child,[...prev,subMenu.id]);});};traverse(menus,[]);returnids;};constids=ref(findPath(data,activeId));constactiveIdsChange=(newIds)=>{ids.value=newIds;console.log("当前选中的id路径",newIds);};return{ids,activeIdsChange,data,};},components:{NestMenu,},};</script> NestMenu.vue <template><divclass="wrap"><divclass="menu-wrap":class="`menu-wrap-${depth}`"><divclass="menu-item"v-for="menuItemindata":class="getActiveClass(menuItem.id)"@click="onMenuItemClick(menuItem)":key="menuItem.id">{{menuItem.name}}</div></div><nest-menu:key="activeId"v-if="subMenu&&subMenu.length":data="subMenu":depth="depth+1":activeIds="activeIds"@change="onSubActiveIdChange"></nest-menu></div></template><scriptlang="ts">import{watch,ref,onMounted,computed}from"vue";importdatafrom"../menu";interfaceIProps{data:typeofdata;depth:number;activeIds?:number[];}exportdefault{name:"NestMenu",props:["data","depth","activeIds"],setup(props:IProps,context){const{depth=0,activeIds,data}=props;/***这里activeIds也可能是异步获取到的所以用watch保证初始化*/constactiveId=ref<number|null|undefined>(null);watch(()=>activeIds,(newActiveIds)=>{if(newActiveIds){constnewActiveId=newActiveIds[depth];if(newActiveId){activeId.value=newActiveId;}}},{immediate:true,flush:'sync'});/***菜单数据源发生变化的时候默认选中当前层级的第一项*/watch(()=>props.data,(newData)=>{if(!activeId.value){if(newData&&newData.length){activeId.value=newData[0].id;}}//如果当前层级的data中遍历无法找到`activeId`的值说明这个值失效了//把它调整成数据源中第一个子菜单项的idif(!props.data.find(({id})=>id===activeId.value)){activeId.value=props.data?.[0].id;}},{immediate:true,//在观测到数据变动之后同步执行这样会防止渲染发生错乱flush:"sync",});constonMenuItemClick=(menuItem)=>{constnewActiveId=menuItem.id;if(newActiveId!==activeId.value){activeId.value=newActiveId;constchild=getActiveSubMenu();constsubIds=getSubIds(child);//把子菜单的默认第一项ids也拼接起来向父组件emitcontext.emit("change",[newActiveId,...subIds]);}};/***接受到子组件更新activeId的同时*需要作为一个中介告知父组件activeId更新了*/constonSubActiveIdChange=(ids)=>{context.emit("change",[activeId.value].concat(ids));};constgetActiveSubMenu=()=>{returnprops.data?.find(({id})=>id===activeId.value)._child;};constsubMenu=computed(getActiveSubMenu);/***样式相关*/constgetActiveClass=(id)=>{if(id===activeId.value){return"menu-active";}return"";};/***递归收集子菜单第一项的id*/constgetSubIds=(child)=>{constsubIds=[];consttraverse=(data)=>{if(data&&data.length){constfirst=data[0];subIds.push(first.id);traverse(first._child);}};traverse(child);returnsubIds;};return{depth,activeId,subMenu,onMenuItemClick,onSubActiveIdChange,getActiveClass,};},};</script><style>.wrap{padding:12px0;}.menu-wrap{display:flex;flex-wrap:wrap;}.menu-wrap-0{background:#ffccc7;}.menu-wrap-1{background:#fff7e6;}.menu-wrap-2{background:#fcffe6;}.menu-item{margin-left:16px;cursor:pointer;white-space:nowrap;}.menu-active{color:#f5222d;}</style> 源码地址 https://github.com/sl1673495/vue-nested-menu 总结 一个递归的菜单组件,说简单也简单,说难也有它的难点。如果我们不理解 Vue 的异步渲染和观察策略,可能中间的 bug 就会困扰我们许久。所以适当学习原理还是挺有必要的。 在开发通用组件的时候,一定要注意数据源的传入时机(同步、异步),对于异步传入的数据,要利用好 watch 这个 API 去观测变动,做相应的操作。并且要考虑数据源的变化是否会和组件内原来保存的状态冲突,在适当的时机要做好清理操作。 另外留下一个小问题,我在 NestMenu 组件 watch 数据源的时候,选择这样去做: watch((()=>props.data); 而不是解构后再去观测: const{data}=props;watch(()=>data); 这两者之间有区别吗?这又是一道考察深度的面试题。 开发优秀组件的路还是很漫长的,欢迎各位也在评论区留下你的看法~ 招聘 字节跳动内推啦,Client Infrastructure是字节跳动终端基础架构团队,面向字节跳动全业务线的移动端、Web、Desktop等终端业务的基础架构部门,为公司业务的高效迭代、质量保证、研发效率和体验提供平台、工具、框架和专项技术能力支撑。研发领域包括但不限于APP框架和基础组件、研发体系、自动化测试、APM、跨平台框架、端智能解决方案、Web开发引擎、Node.js基建以及下一代移动开发技术的预研等,目前在北上广深杭五地均设有研发中心。 上海的同学点这里一键投递,来我们部门和我做同事吧~ https://job.toutiao.com/s/JhRtmQv 其他地区(北上广深杭)也可以自己搜索你想要的业务线和工作地点,通过我的下方内推链接直接投递即可。 https://job.toutiao.com/s/JhRDWep 校招的同学看这里: 投递链接: https://job.toutiao.com/s/JhRV7nC ❤️ 感谢大家 1.如果本文对你有帮助,就点个赞支持下吧,你的「赞」是我创作的动力。 2.关注公众号「前端从进阶到入院」即可加我好友,我拉你进「前端进阶交流群」,大家一起共同交流和进步。 参考资料 [1]Github Pages: https://sl1673495.github.io/vue-nested-menu/ 本文分享自微信公众号 - 前端从进阶到入院(code_with_love)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

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

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

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

用户登录
用户注册