业务自定义菜单设计初步规划(20220702)

背景

大多情况下,前端的菜单是按照前端路由来定义的,并且是限定死的,这种情况下一般是产品的定义和边界是清楚的,标准化的,不会给用户自定义。这种的大前提也是,公司具有一定的话语权,或者产品具有一定的成熟度,完整度。当业务或者产品需要的时候,会前端根据当前用户的角色做一些代码耦合的绑定。

还有少部分情况,产品体系里针对功能模块做了一些划分,希望针对用户权限,业务权限做一些分割,这样会把前端的访问入口作为资源类,然后访问前端页面时,按照注册式路由,只要是注册过的路由都是可以访问的。

然而这样的设计还不能满足我们的业务场景,在我们的业务场景下,用户从使用角度会希望有一些对自己友好的分类,而这个分类会体现到菜单上,这样定义的菜单是前端无法直接识别的;还有一种可能是前端的原来特定功能的页面会被灵活的迁移到任何的其他入口,而脱离最开始的场景。

有什么难点

有人会说,是这样又有什么问题?

或者,菜单是菜单,路由是路由,本来就没什么关系。简单这样想是没有问题的。但核心问题出在,当用户刷新当前地址的时候,或者收藏了某个地址的时候,当用户通过地址访问时,希望记录出来是在哪个菜单下,而如果前端角度没有记录当前地址是从什么入口,什么地址访问的,这个点就是不可实现的。换个简单点的说法,就是原本我们用布局,用iframe写的这种页面,你想让用户刷新的时候,记录你上次访问到的页面,几乎是不可能的,因为这两者没有必然的关系(前提,你认为菜单只是入口,和路由没关系)。

那如果菜单就是路由呢 ?那么访问本身没有问题了,问题还是在处理菜单的层级关系和高亮上假如我们拿到的权限菜单是全量的都在当前展示也是没有问题的,也是大多时候看到的管理后台。但我们的场景比这个更复杂, 你接受到的权限菜单可以理解为n个大的管理后台,每个都有自己一二三级菜单。在设置上,每个单独打开的一级后台设置的菜单地址都可能被设置到另外一个入口,被用来解决同个场景或者不同业务背景。

设计思路1

基本原则:菜单和路由分开设计,互不影响

菜单我们可以理解为就是入口,那么地址是什么?说白了,对前端来说,地址只是用来解决地址与前端页面组件对应关系的标识而已。至于地址本身不应该包含任何和菜单或者和交互有关的设计。

比如:你从一级菜单a打开list页面,和从菜单b打开list页面,地址应该是一样的,因为菜单ab对于前端,对于编码人员来说是无感知的。前端保证/list能访问到对应的唯一的页面即可。

那么菜单的问题怎么解决呢?在后端的领域里有个词叫rabc,专门解决权限问题的。大概会有这么几个界面:

界面1 :解决菜单的主要设置问题,包括层级关系,名称

界面2 :解决每个菜单与产品的对应关系,或者说与业务的对应关系,当你(你或者你所在的机构)购买了某产品就可看到这个产品对应的捆绑菜单

界面3:解决菜单与角色的关系,某些菜单的访问会针对系统本身,或者产品本身有不同的角色要求,比如某业务的审核员可见某个菜单

解题1:以会话为最小单位的 理念,解决交互的记忆问题

我们最初jq的时代是怎么解决一个大后台的问题呢?为了避免过度设计,一般我们保持几个基本原则

1 只有一个基本的管理后台的菜单界面,这个页面不会随着地址改变而刷新,刷新的只是内容窗口部分

2 相对来说,同级别下的菜单地址和名称绝对不会重复,即使都重复了,也必须保证基本的菜单id标识不同

3 当刚好业务的跳转地址是另外一个菜单地址时,选择的决定是否要把当前地址的对应菜单高亮

4 菜单地址入口的流程页面地址,默认情况下是不做记录的,因为一者记录成本太高,二者,这个页面的可访问性也无法保证,每个页面都要保证自己的来源页面和准备数据比较完善,这个对业务开发来说太难了。尤其目前在spa 具有store的这种情况下,很可能当前页的数据是在好几个页面聚合来的数据才能保证当前的显示结果。

所以我们的应用也尽量保持一样的原则,这是基本点。也就是页面发生跳转时,菜单部分保持不变,只更新内容视图。

解题2:交互原点的记录和获取

我的设计思路如下,提出一个概念,交互原点,以我们的场景为例,交互原点一定要有一个一级菜单的点击作为开始,然后会有二级菜单和三级菜单的点击,通过交互行为,我们能准确的记录下来这个页面的菜单状态。

菜单的状态存在session里,只要窗口在,那么这个状态就存在。这个记录和任何地址无关。当前会话打开的任何地址都不会破坏这个概念。

当你需要破坏这种设计呢?比如需要打开另外一个一级菜单,打开的一级菜单的入口需要显性的生命连接的入口中带有sessionIndex = menuId ,那么跳转的过程中发现有这个保留标识,就会重新记录交互原点。同理,当业务内需要跳转到指定的一级菜单(具有指定交互原点意义的)也必须带有sessionIndex = menuId 的标识。

那么刷新的场景下,也是类似的,优先从会话标识里,读取交互原点和后续操作点。

解题3:菜单标识的约定(也是业务标识)

上一个议题,我们提到了当你想跳转到另外一个交互原点时,需要一个确切的标识,这里说的确切标识,其实是具有业务约束的。什么意思?就是当我们希望前端跳转到某个业务界面时,一定是有业务意义的,能产品自己说清楚的,是唯一的,而不是a场景下跳转1,b场景下跳转2,c场景下跳转3,因为如果这自定义或者扩展,前端的硬编码会无法做。

所以从这个角度,当业务确实需要跳转到某个业务意义的地址时,加的菜单标识其实是产品标识,产品可以有多个版本来解决不同的需求,同一个用户,应该产品只会有一个版本,我们可以不用知道具体的产品版本,只要知道配置了这个产品的跳转到这个产品含有的版本里即可。我们可以通过约定产品标识前缀来解决这个问题。

举例如下:

1 平安校园,产品标识,safety-campus-xxxx

2 教务管理,产品标识,teach-manage-xxxxx

那么,当我们另外一个业务在完成某个动作之后,希望跳转到平安校园的某个页面地址时,前端的地址只要写router.push('/xxx?sessionIndex=safety-campus-')即可,菜单的中间件会帮我识别出来这个标识在目前用户的匹配的唯一的一级菜单,并得到对应的页面。

达成这个基本约定后,我们自定义菜单也会更加科学,把真的属于一类的菜单命名出不同版本,并一定的避免过于杂乱的没有任何业务价值的菜单。

解题4:快捷菜单

之所以提出这个概念,还是处于挖掘用户需求,许多时候我们把客户的需求理解错了,就比如自定义菜单这个事情,自定义菜单是为了解决产品归类,功能归类还是只是为了解决访问方便的问题。

如果是解决前者,那么我们提的具有产品意义的标识分类法就非常好。

但如果是后者,只是为了方便访问,那么我们应该提供快捷菜单,快捷菜单就是用户可直达某个地址,不通过复杂的菜单交互,同时多个快捷菜单之间也没有任何产品关系。比如我把查看消息和查看个人等级都会列为快捷菜单,但我们不可能把这两个变为一个常规的层级菜单。

解题5:地址访问可用性,用产品的收藏地址,自定义收藏行为来解决

我们的地址除了常规的地址还有很多中转页地址,然而如果用户手动记录保存很多我们没有处理的页面地址就会导致访问本身有问题。这种情况下,我将在常用的高频的产品页提供一键收藏,这样在我们自己的产品里,可以通过手动收藏的页面地址直接进入,其条件是经过程序员设计过的是完整的,甚至比常规流程进来还能缓存很多交互信息,事半功倍。

解题6:新窗口地址的访问

地址访问拿到页面没有任何问题,问题出在如何得到这个地址是哪个菜单的。按照之前的设计,我们是把交互原点定在了每个具体会话里。同时,我们也不能保证用户一定是从某个会话开始之后才访问一个新的页面。这种情况下,有以下几个基本逻辑。

1 会话级别的交互逻辑会在浏览器持久缓存里也备份一次,并按照栈的结构,每次新开会话压入。当新的地址打开时,我们会首先做一些出栈判断,当前的地址在不在历史已打开的交互中,如果存在优先使用。当然这个策略是可调整的

2 基于3的设计,我们通过地址访问一定是可以向上匹配拿到这个地址对应的菜单标识的,但结果可能不止一个,所以如果1的逻辑获取不到时,会按照rabc的权限菜单做一次循环查询,使用第一个匹配了这个地址的菜单作为交互原点。如果没有匹配到任何地址,跳珠到首页

解题7:菜单权限或者路由权限的控制

菜单权限通过rabc返回的菜单控制,返回的所有菜单都是有权限的。

路由权限,因为rabc里并没有包含全部前端路由,所以通过地址访问的时候,我们必须要保证以下几个基本点:

1 菜单的部分可以对应到路由的权限识别,建议按照一级以及二级路由严格匹配。比如前端一个订单的所有的页面的地址前缀必须是/order/开始的。这样我只要保证我对应的菜单权限具有一级路由的权限就可以访问下属路由,并且这里可以约定是模糊限制,还是严格限制。

2 有些路由是不需要任何权限的,或者说只要某个角色匹配就可以,与菜单分组没关系的,都要设计额外的白名单,访问策略。

设计思路2

基本原则:地址包括菜单和路由,做路径约定

基本解释,为了更加方便的解决用户通过地址访问的问题,也可以直接将菜单级别的地址映射到路径上。只不过为了方便分析,我们需要在rabc里将这个菜单的路径和前端路由的路径严格分为两个字段,然后访问的时候再拼接起来。

有以下的基本注意事项:

1 菜单的地址都需要拼接菜单地址

2 前端代码中push逻辑原先是跳转的绝对前端路由地址,有了这种地址之后,需要做push方法的改造,跳转的地址需要拼接菜单标识地址

3 为了支撑2 ,需要定义一些基本的上下文,来保证2发生跳转时拼接的标识地址是正确的,否则按照当前会话的菜单标识去反问

4 前端路由解析时,必须截取菜单路径以后的路径作为真实路由表查找匹配页面组件

猜你喜欢

转载自juejin.im/post/7115709023176884254