做一个多标签Tab好难!(场景设计)

582 阅读7分钟

领导:做一个多标签Tab功能吧,最好能集成到组件库里

我:好的

开始爬坑,工具-gpt4

应用场景

大型后台管理系统,技术栈:react、spa、ice、antd

期望实现形式

从组件库中直接调用,尽量以较少的代价改动旧有项目,可以对脚手架进行改造以适配多标签tab

开始设计

1.《根据 Tab 的改变触发页面跳转》 与 《根据页面跳转触发 Tabs 改变》

根据 Tab 的改变触发页面跳转

在这种方式下,用户与 Tabs 组件交互时(比如点击某个 Tab),组件会触发页面跳转(通过改变 URL 或使用路由库进行导航)。

优点:

  • 用户行为和视觉反馈紧密结合,点击 Tab 立即看到对应的视觉变化。
  • UI 组件控制导航,这在不需要与 URL 同步时更为简单直观。

缺点:

  • 如果应用依赖于 URL 来维持状态,你需要确保 Tabs 状态与 URL 保持同步。

这种方法比较适合那些不强依赖于 URL 状态的应用,或者在单页应用(SPA)中,所有的视图渲染都是由前端路由控制的情况。

根据页面跳转触发 Tabs 改变(*)

在这种方式下,页面的导航(URL 变化或路由变化)会触发 Tabs 组件状态的更新,确保当前激活的 Tab 与当前的页面内容一致。

优点:

  • 保持了 URL 与应用状态的一致性,用户可以通过 URL 直接访问特定的 Tab 页面。
  • 利于 SEO 和浏览器的前进后退按钮导航。

缺点:

  • 实现起来可能更复杂,因为需要同步 URL 变化和 Tabs 状态。
  • 对于非 SPA 应用,可能涉及到页面的重新加载。

这种方法比较适合那些需要直接通过 URL 访问特定 Tab 页面的应用,或者在需要保持前端路由与 Tabs 状态同步的单页应用中。

总结

选择困难了呀兄弟们,讲道理如果要少改动旧有项目,那我必然是选根据页面跳转触发Tabs改变,那我先按这个路径走吧。

2.获取Tab标题

既然我是以路由改变,那么先监听路由变化

import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom'; // 如果是ice框架,那么替换成ice
const MyComponent = () => {  
    const location = useLocation(); 
    useEffect(() => { 
        console.log('当前路由已变更', location.pathname); 
        // 你可以在这里根据 location 对象做进一步处理 
    }, [location]); 
    return ( <div>  {/*...其他组件代码*/} </div>); 
}; 
export default MyComponent;

第二步,获取路由配置。 第三步,flat路由配置。 第四步,从路由中检索需要的标题。

ice约定式路由,获取不到路由配置表咋办? 那我也不知道哈,有没有大佬说下。

3.欢迎页

假设我们的中后台系统有这个欢迎页,那么这个欢迎页应该是不占用tab内容的,欢迎页应该也是一个路由,所以需要在监听路由变化,然后修改tab的逻辑中处理,既然到了欢迎页,那么tabs的激活状态怎么处理呢? 按照正常逻辑,激活tab的值是欢迎页的pathname,不匹配tabs列表,所以没有被激活的tab。

4.登录页,404,502,无权限页,loading页,error页

先按优秀的设计方案来

  • 登录页的显示通过redux中的全局状态控制,这样登录之后直接显示本来的路由内容
  • 无权限页通过ice配置,在layout中显示无权限
  • 404页可以没有,不匹配路由重定向到第一个有权限的路由
  • 502页/loading页/error页与登录页一致,通过一个状态处理

这样看来,登录页,502,loading,error页不占用页面名额,但是502还是会占用页面名额。那么这两个页面如何处理呢?

我倾向于直接展示这个tab,此页面的title就是那个页面对应的title。

如果我们的系统是如下方式设计的

  • 登录页,error页,404页,502页,无权限页作为路由独立页面
  • error,404,502在layout中

那么error,502,404如果有tab的话,标题就无法确定,因为已经在另一个路由上了,所以只能走到不显示tab的模式,如果不显示tab,那么tabs的状态是什么呢?展示一个404或502的页面,tabs无激活tab。就让人摸不着头脑,感觉已经走到了死路,不过也只能这样了。

5.关闭标签页

终于到了比较轻松的部分,关闭标签页产生的副作用大致上直接借鉴chrome的tabs处理。在这时我们需要区分几种情况。

当有欢迎页时:

  1. 关闭当前激活的标签:

    • 移除当前激活的标签页。
    • 尝试激活右侧的标签页。
    • 如果右侧没有标签,则尝试激活左侧的标签页。
    • 如果左侧也没有标签,则跳转到欢迎页。
  2. 关闭非激活的标签:

    • 直接在标签列表中移除该标签。

当没有欢迎页时:

  1. 关闭当前激活的标签:

    • 移除当前激活的标签页。
    • 尝试激活右侧的标签页。
    • 如果右侧没有标签,则尝试激活左侧的标签页。
    • 如果左侧也没有标签,则关闭整个页面(或显示一个默认页面,如果窗口或标签页不能被关闭)。
  2. 关闭非激活的标签:

    • 直接在标签列表中移除该标签。

此逻辑需要写在tabs的onEdit函数中,action是remove

6.新增/定位标签页

因为我们是根据路由变化来处理标签页的逻辑,因此标签页的新增,定位在路由变更那需要相应的处理逻辑,还需要处理的是标签页的手动点击切换

路由变化处逻辑处理

  1. 检测到路由变更,在存量tabs中检索是否存在此tab
  2. 不存在此tab则将此tab加入到tabs中,并且置为激活项
  3. 存在此tab则将此tab置为激活项

给标签切换的onChange函数加入逻辑

  1. 点击当前激活标签,直接return
  2. 点击其他标签页,路由push/replace,不知道选择哪个,看看点击返回会对tab造成的影响吧

7.页面返回对tabs造成的影响

使用push的情况

  1. 打开列表页(历史记录:列表页)
  2. 点击进入列表详情(历史记录:列表页 -> 详情页)
  3. 关闭列表页tab,历史记录不变(历史记录:列表页 -> 详情页)
  4. 点击浏览器后退,会回到列表页,并且详情页tab仍然保留(历史记录:列表页)

使用replace的情况

  1. 打开列表页(历史记录:列表页)
  2. 点击进入列表详情,使用replace(历史记录:详情页)
  3. 关闭列表页tab,历史记录不变(历史记录:详情页)
  4. 再点击列表页tab,使用replace(历史记录:列表页)
  5. 再点击详情页tab,使用replace(历史记录:详情页)
  6. 点击浏览器后退,由于最后一次导航使用了replace,浏览器历史记录里面没有其他记录,页面没有变化(用户可能会退出这个单页应用,如果之前没有其他浏览历史)

标签页之间的点击如果是用push,那么浏览器记录会变得非常多,所以我会选择replace,尽管会发生用户不能利用浏览器的后退和前进按钮在不同的tab间进行前后导航。

8.右键Tab唤出菜单

  1. 菜单选项,关闭右侧,关闭左侧,关闭其他,关闭全部
  2. 关闭全部在没有欢迎页情况下不知如何处理,针对没有欢迎页的项目,没有关闭全部。
  3. 右键点击时,如何获取被点击的Tab,是否是在Tab上加入右击事件,是否会和鼠标默认右键发生冲突-使用e.preventEvent禁止默认行为,
  4. 菜单被关闭的逻辑

关闭左/右侧逻辑
当激活页在被关闭页内,则当前右键页面置为激活页
关闭其他 当前右键页面被置为激活页

9.tab之间进行拖拽交换

目前只能想到react-dnd,像chrome那种交互暂时无法处理,react-sortable-hoc只能处理简单dom元素之间的顺序交换

其他,keepAlive,tab标签数量限制

想到再补