浏览代码

feat: tab增加右键删除等功能

xyh 3 年之前
父节点
当前提交
28a496997a

+ 52 - 99
cypress/e2e/tab.cy.ts

@@ -1,95 +1,10 @@
-import {
-  beforeSetup,
-  clickMenu,
-  generateNetworkResult,
-  intercept,
-  validateDialog,
-} from './utils';
-
-const containerBasicData = {
-  id: '5',
-  department: '涂装班组',
-  departmentId: '000300010012',
-  type: '充电桩底座+1',
-  containerName: '特来电002',
-  code: '00002',
-  num: '9',
-  createTime: '2023-03-31 10:11:27',
-  modifyUser: 'admin',
-  modifyTime: '2023-03-31 10:46:30',
-  page: 0,
-  limit: 0,
-};
-
-const roleBasicData = {
-  id: 11,
-  roleCode: 'BH000011',
-  roleName: 'test账户',
-  createTime: '2023-03-30 14:50:46',
-  remarks: '测试用账户',
-  menu: '78,93,88,79,',
-  menuBefore: '93,88,79',
-  menuPda: '29,30,31,32,',
-  modifyUser: 'admin',
-  modifyTime: '2023-04-12 15:16:15',
-  page: 0,
-  limit: 0,
-};
-
-const menuBasicData = {
-  id: '7',
-  name: '系统设置',
-  url: '.',
-  pId: '0',
-  idCode: null,
-  type: 'PC',
-  page: 0,
-  limit: 0,
-  orderBy: '0',
-  menu: null,
-  img: 'xitongguanli',
-  modifyUser: 'admin',
-  modifyTime: '2023-03-29 13:42:47',
-  pid: '0',
-};
+import {beforeSetup, clickMenu} from './utils';
 
 describe('page tab', function () {
   beforeEach(function () {
     beforeSetup();
   });
 
-  beforeEach(function () {
-    intercept('/container/getContainer', function ({reply, search}) {
-      generateNetworkResult({
-        reply,
-        search,
-        basicData: containerBasicData,
-        title: 'code',
-      });
-    });
-
-    intercept('/role/getRole', function ({search, reply}) {
-      generateNetworkResult({
-        search,
-        basicData: roleBasicData,
-        reply,
-        title: 'roleCode',
-      });
-    });
-
-    intercept('/menu/getPage', function ({search, reply}) {
-      generateNetworkResult({
-        search,
-        reply,
-        basicData: menuBasicData,
-        title: 'name',
-        skipCondition(name) {
-          return name === 'pId' || name === 'type';
-        },
-      });
-    });
-  });
-
   function validateTabLength(length: number) {
     cy.getTestId('tab_list')
       .find('.ant-tabs-nav')
@@ -105,6 +20,16 @@ describe('page tab', function () {
       .should('have.text', text);
   }
 
+  function clickTab(eq: number, contextMenu = false) {
+    const el = cy
+      .getTestId('tab_list')
+      .find('.ant-tabs-nav-list')
+      .children('span')
+      .eq(eq);
+
+    contextMenu ? el.trigger('contextmenu') : el.find('.ant-tabs-tab').click();
+  }
+
   it('tab', function () {
     validateTabLength(1);
     // 判断新增是否正确
@@ -116,27 +41,55 @@ describe('page tab', function () {
     validateTabActiveText('菜单管理');
     validateTabLength(4);
 
-    // 判断删除当前选中的tab
-    cy.getTestId('tab_list')
-      .find('.ant-tabs-tab-active')
-      .find('button')
-      .click();
+    // 删除当前标签
+    clickTab(1);
+    clickTab(1, true);
+    cy.getTestId('remove').click();
     validateTabLength(3);
+    validateTabActiveText('首页');
+
+    // 删除非当前标签
+    clickTab(2);
+    clickTab(1, true);
+    cy.getTestId('remove').click();
+    validateTabLength(2);
+    validateTabActiveText('菜单管理');
+
+    clickMenu('基础资料', '容器管理');
+    clickMenu('系统设置', '角色管理');
+    clickMenu('系统设置', 'PDA菜单管理');
+
+    // 除此之外关闭
+    clickTab(3, true);
+    cy.getTestId('remove_other').click();
+    validateTabLength(2);
     validateTabActiveText('角色管理');
 
-    // 判断删除非选中的tab
+    clickMenu('基础资料', '容器管理');
     clickMenu('系统设置', '菜单管理');
-    cy.getTestId('tab_list').find('.ant-tabs-tab').eq(1).find('button').click();
+    clickMenu('系统设置', 'PDA菜单管理');
+
+    // 关闭右侧 移除包含自己
+    clickTab(2, true);
+    cy.getTestId('remove_right').click();
     validateTabLength(3);
-    validateTabActiveText('菜单管理');
+    validateTabActiveText('容器管理');
 
-    // 判断点击已添加的菜单是否会切换到指定tab
-    clickMenu('系统设置', '角色管理');
+    clickMenu('系统设置', '菜单管理');
+    clickMenu('系统设置', 'PDA菜单管理');
+    // 关闭右侧 不移除自己
+    clickTab(1);
+    clickTab(2, true);
+    cy.getTestId('remove_right').click();
+    validateTabLength(3);
     validateTabActiveText('角色管理');
 
-    // 清空tab
-    cy.getTestId('clear_tab_btn').click();
-    validateDialog('清除标签页', '你确定要关闭所有标签页吗?');
+    clickMenu('系统设置', '菜单管理');
+    clickMenu('系统设置', 'PDA菜单管理');
+    // 清除所有
+    clickTab(1, true);
+    cy.getTestId('clear').click();
     validateTabLength(1);
+    validateTabActiveText('首页');
   });
 });

+ 26 - 0
packages/app/src/pages/home/context.ts

@@ -11,6 +11,8 @@ type State = {
 type Action =
   | {type: 'ADD'; payload: State[0]}
   | {type: 'REMOVE'; payload: string}
+  | {type: 'REMOVE_OTHER'; payload: string}
+  | {type: 'REMOVE_RIGHT'; payload: string}
   | {type: 'CLEAR'};
 
 const defaultTab: State[0] = {key: '-1', url: '/main', label: '首页'};
@@ -49,6 +51,30 @@ function reducer(state: State, action: Action): State {
       nextState.splice(idx, 1);
       return nextState;
     }
+    case 'REMOVE_OTHER': {
+      const {payload} = action;
+      const idx = state.findIndex(val => val.key === payload);
+      if (idx < 0) return state;
+
+      dispatch(payload);
+
+      return [{...defaultTab}, {...state[idx]}];
+    }
+    case 'REMOVE_RIGHT': {
+      const {payload} = action;
+      const idx = state.findIndex(val => val.key === payload);
+      if (idx < 0) return state;
+
+      const {length} = state;
+      const nextState = [...state];
+      nextState.splice(idx + 1, length - idx - 1);
+
+      if (nextState.findIndex(val => val.key === key) < 0) {
+        dispatch(payload);
+      }
+
+      return nextState;
+    }
     case 'CLEAR': {
       dispatch('-1');
 

+ 28 - 0
packages/app/src/pages/home/main/hooks.tsx

@@ -5,6 +5,7 @@ import {ReactNode} from 'react';
 import css from './index.module.css';
 import {useStore} from 'zustand';
 import {tabStore} from '@stores';
+import {useContextMenu, ItemParams} from 'react-contexify';
 
 export type Tab = {
   key: string;
@@ -55,3 +56,30 @@ export function useTabActive() {
 
   return [activeKey, {onChange: setActiveKey, onClear, onEdit}] as const;
 }
+
+export function useMenu() {
+  const {show} = useContextMenu({id: 'content_menu'});
+  const dispatch = useContextSection(context, state => state[1]);
+
+  function onMenuitemClick(e: ItemParams<{key: string}, any>) {
+    const {props, id} = e;
+    if (!id || !props?.key) return;
+    const {key} = props;
+    switch (id) {
+      case 'clear':
+        dispatch({type: 'CLEAR'});
+        break;
+      case 'remove':
+        dispatch({type: 'REMOVE', payload: key});
+        break;
+      case 'remove_other':
+        dispatch({type: 'REMOVE_OTHER', payload: key});
+        break;
+      case 'remove_right':
+        dispatch({type: 'REMOVE_RIGHT', payload: key});
+        break;
+    }
+  }
+
+  return {show, onMenuitemClick};
+}

+ 61 - 27
packages/app/src/pages/home/main/index.tsx

@@ -1,37 +1,71 @@
-import {Button, Layout, Tabs} from 'antd';
+import {Layout, Tabs} from 'antd';
 import {FC} from 'react';
 import css from './index.module.css';
-import {useTabActive, useTabItems} from './hooks';
+import {useMenu, useTabActive, useTabItems} from './hooks';
+import {Menu, Item} from 'react-contexify';
 
 const Main: FC = function () {
   const tabItems = useTabItems();
-  const [activeKey, {onChange, onClear, onEdit}] = useTabActive();
+  const [activeKey, {onChange, onEdit}] = useTabActive();
+  const {show, onMenuitemClick} = useMenu();
 
   return (
-    <Layout.Content className='layout-content-custom'>
-      <Tabs
-        data-testid='tab_list'
-        size='small'
-        hideAdd
-        activeKey={activeKey}
-        onChange={onChange}
-        type='editable-card'
-        items={tabItems}
-        className={css.tabs}
-        onEdit={onEdit}
-        tabBarExtraContent={
-          <Button
-            danger
-            type='link'
-            className={css.clearBtn}
-            onClick={onClear}
-            data-testid='clear_tab_btn'
-          >
-            清除
-          </Button>
-        }
-      />
-    </Layout.Content>
+    <>
+      <Layout.Content className='layout-content-custom'>
+        <Tabs
+          data-testid='tab_list'
+          size='small'
+          hideAdd
+          activeKey={activeKey}
+          onChange={onChange}
+          items={tabItems}
+          className={css.tabs}
+          onEdit={onEdit}
+          renderTabBar={function (props, DefaultTab) {
+            return (
+              <DefaultTab {...props}>
+                {function (node) {
+                  return (
+                    <span
+                      onContextMenu={e =>
+                        node.key === '-1'
+                          ? e.preventDefault()
+                          : show({event: e, props: {key: node.key}})
+                      }
+                    >
+                      {node}
+                    </span>
+                  );
+                }}
+              </DefaultTab>
+            );
+          }}
+        />
+      </Layout.Content>
+
+      <Menu id='content_menu' style={{zIndex: '999999'}}>
+        <Item id='remove' data-testid='remove' onClick={onMenuitemClick}>
+          关闭
+        </Item>
+        <Item id='clear' data-testid='clear' onClick={onMenuitemClick}>
+          关闭所有
+        </Item>
+        <Item
+          id='remove_right'
+          data-testid='remove_right'
+          onClick={onMenuitemClick}
+        >
+          关闭右侧标签
+        </Item>
+        <Item
+          id='remove_other'
+          data-testid='remove_other'
+          onClick={onMenuitemClick}
+        >
+          关闭其他标签
+        </Item>
+      </Menu>
+    </>
   );
 };
 

+ 1 - 1
packages/app/src/pages/home/menu/index.tsx

@@ -33,7 +33,7 @@ const Menu: FC = function () {
         data-testid='menu'
         mode='inline'
         items={menus}
-        defaultSelectedKeys={['-1']}
+        selectedKeys={[]}
         onOpenChange={onOpenChange}
         openKeys={openKeys}
         className={css.sliderMenus}

+ 5 - 0
packages/app/src/styles/antd.css

@@ -49,6 +49,11 @@
   margin-right: auto;
 }
 
+.ant-tabs-tab {
+  padding: 8px 16px !important;
+  margin-left: 0 !important;
+}
+
 .ant-tabs-tabpane {
   position: absolute;
   top: 0;

+ 2 - 0
packages/app/src/styles/index.css

@@ -9,6 +9,8 @@
   --primary-color: #00a6ca;
   --error-color: #ff4d4f;
   --content-padding: 24px;
+  /* stylelint-disable-next-line custom-property-pattern */
+  --contexify-activeItem-bgColor: #00a6ca;
 }
 
 * {