UNPKG

@ovine/core

Version:

Build flexible admin system with json.

293 lines (292 loc) 13.8 kB
/** * 路由切换器 组件 * * TODO: * 1. 添加 ICON 支持,showIcon, RouteIcon 请求中 LoadingIcon 之类的 * 2. 增加 可编辑/固定 模式 * 3. 将组件转为 纯 react 组件,容易控制 */ import { openContextMenus } from 'amis'; import { findTree } from 'amis/lib/utils/helper'; import { debounce } from 'lodash'; import React, { useEffect, useMemo, useRef } from 'react'; import { matchPath, withRouter } from 'react-router-dom'; import { app } from "../../app"; import { message, storage as storeKeys, rootRoute as defRouteRoute } from "../../constants"; import { getCurrRoutePath } from "../../routes/exports"; import { subscribe, unsubscribe } from "../../utils/message"; import { setGlobal } from "../../utils/store"; import * as cache from "./cache"; import ChromeTabs from "./chrome_tabs"; import { StyledRouteTabs } from "./styled"; const RouteTabs = (props) => { const { routes: routesProp, themeNs, history, maxCount = 20, storage, rootRoute: rootRouteProp = defRouteRoute, onContextMenu, } = props; const { location } = history; const $storeRef = useRef({ rootRoute: rootRouteProp, routeQuery: {}, routes: routesProp, $tabsDom: null, $tabs: undefined, tabs: {}, }); $storeRef.current.rootRoute = rootRouteProp; $storeRef.current.routes = routesProp; const notFindRoute = { label: '页面不存在', pathname: app.constants.notFound.route, }; // TODO: 优化跳转逻辑,跳转路由 const changePath = debounce((path) => { history.replace(path); }, 100); const routeQueryCtrl = (path, val = '') => { if (path === '') { $storeRef.current.routeQuery = {}; setGlobal(storeKeys.routeQuery, {}); return; } $storeRef.current.routeQuery[path] = val; setGlobal(storeKeys.routeQuery, $storeRef.current.routeQuery); }; // 获取路径信息 const getRouteInfo = (path = getCurrRoutePath()) => { let rootBackItem; let matchedItem; const { rootRoute, routes } = $storeRef.current; findTree(routes, (item, _, __, items) => { const { limitLabel, // routeTabInitQuery: initQuery, exact, strict, path: routePath = '', nodePath = '', } = item; const pathname = routePath || nodePath; const label = item.label || limitLabel || notFindRoute.label; if (!path || path === rootRoute) { const rootItem = Object.assign(Object.assign({}, item), { label, pathname: rootRoute, isRoot: true }); if (routePath === rootRoute) { matchedItem = rootItem; // 设置的 rootPath } else if (nodePath === rootRoute) { rootBackItem = rootItem; // 默认 root } return false; } const match = matchPath(path, { path: pathname, exact, strict, }); const isMatch = (match === null || match === void 0 ? void 0 : match.url) === path; if (isMatch) { const shared = !!findTree(items, (i) => i.routeTabShared); matchedItem = Object.assign(Object.assign({}, item), { label, pathname: path, shared }); } return isMatch; }); const curr = matchedItem || rootBackItem || notFindRoute; return curr; }; // 清空所有时 默认跳到 首页 const onClearAll = (tabDom, refreshRoot = true) => { const { tabs } = $storeRef.current; routeQueryCtrl(''); // 防止首次进入页面 直接刷新两次 if (refreshRoot !== false && getCurrRoutePath() !== $storeRef.current.rootRoute) { changePath($storeRef.current.rootRoute); } setTimeout(() => { if (tabDom) { tabs.removeTab(tabDom, { autoActive: false }); } else { tabs.tabEls.forEach((tabEl) => { if (tabEl.dataset.root !== $storeRef.current.rootRoute) { tabs.removeTab(tabEl, { autoActive: false }); } }); } cache.clearCachedTabs(); }, 100); }; const onRemove = (tabEl) => { const { tabs } = $storeRef.current; if (tabs.tabEls.length !== 1) { routeQueryCtrl(tabEl.dataset.path, ''); tabs.removeTab(tabEl); return; } if (!tabEl.dataset.root) { onClearAll(tabEl); } }; const onItemMenus = (e) => { if (e.button !== 2) { return; } const target = e.currentTarget; const { tabs } = $storeRef.current; const allTabs = tabs.tabEls; const onlyOne = allTabs.length === 1; const isLastOne = onlyOne ? true : allTabs[allTabs.length - 1] === target; const actions = [ tabs.activeTabEl === target && { label: '刷新页面', onSelect: () => { // TODO: 刷新页面 会丢失 查询参数 changePath(target.dataset.path || $storeRef.current.rootRoute); }, }, !onlyOne && { label: '关闭其他', onSelect: () => { allTabs.forEach((tabEl) => { if (tabEl !== target) { tabs.removeTab(tabEl); } }); cache.cacheTabs(tabs.tabEls); }, }, !onlyOne && !isLastOne && { label: '关闭右边', onSelect: () => { let isAfter = false; allTabs.forEach((tabEl) => { if (tabEl === target) { isAfter = true; } else if (isAfter) { tabs.removeTab(tabEl); } }); cache.cacheTabs(tabs.tabEls); }, }, !onlyOne && { label: '关闭所有', onSelect: onClearAll, }, ].filter(Boolean); const routeItemInfo = Object.assign(Object.assign({}, getRouteInfo()), { isRoot: !!target.dataset.root, active: target.hasAttribute('active') }); const menus = onContextMenu ? onContextMenu(actions, routeItemInfo) : actions; openContextMenus({ x: e.clientX, y: e.clientY, }, menus); }; const onMounted = () => { const $tabs = $($storeRef.current.$tabsDom); const tabs = new ChromeTabs(); const tabsEle = $tabs.get(0); tabs.init(tabsEle); $storeRef.current.$tabs = $tabs; $storeRef.current.tabs = tabs; $tabs.on('contextmenu', () => false).on('mousedown', '.chrome-tab', onItemMenus); tabsEle.addEventListener('activeTabChange', ({ detail }) => { const { tabEl, tabProperties = {} } = detail; const { changeRoute, isAdd } = tabProperties; const { path = '', state = '' } = tabEl.dataset; cache.cacheTabs(tabs.tabEls); // 添加时 , 路由相同 , 明确标明, 情况下,不需要跳转路由 if (!changeRoute || isAdd || window.location.pathname === path) { return; } // 携带状态信息一起跳转 changePath(`${path}${!state ? '' : `?${state}`}`); }); tabsEle.addEventListener('tabAdd', ({ detail }) => { const { tabEl, tabProperties } = detail; const { pathname, isRoot } = tabProperties; tabEl.dataset.path = pathname || ''; tabEl.dataset.root = isRoot ? pathname : ''; }); tabsEle.addEventListener('onTabRemove', ({ detail }) => { onRemove(detail.tabEl); }); subscribe(message.clearRouteTabs, ({ refreshRoot }) => { onClearAll(undefined, refreshRoot); }); subscribe(message.routeTabChange, () => { const query = window.location.href.split('?')[1] || ''; routeQueryCtrl(tabs.activeTabEl.dataset.path, query); tabs.activeTabEl.dataset.state = query; }); }; const onUnmounted = () => { unsubscribe([message.clearRouteTabs, message.routeTabChange]); }; useEffect(() => onUnmounted, []); useEffect(() => { const isMounted = !!$storeRef.current.$tabs; // 未初始化 先初始化 if (!isMounted) { onMounted(); } const { tabs, $tabs } = $storeRef.current; const curr = getRouteInfo(location.pathname); // 初次加载 首页时,回归上次一次 active tab if (!isMounted && curr.pathname === $storeRef.current.rootRoute) { const tabEl = $tabs.find('.chrome-tab[data-active]').get(0); if (tabEl) { tabs.setCurrentTab(tabEl, { changeRoute: false }); return; } } // 已经 在列表的 直接 定位当前路由 const tabEl = $tabs.find(`.chrome-tab[data-path="${curr.pathname}"]`).get(0); if (tabEl) { // tabEl.dataset.state = '' // 清除当前状态 tabs.setCurrentTab(tabEl, { changeRoute: false }); return; } // 超出最大值 先移除最左边的 if (tabs.tabEls.length >= maxCount) { tabs.removeTab(tabs.tabEls[0], { autoActive: false }); } // 共享 routeTab if (curr.shared) { const $shared = $tabs.find('.chrome-tab[data-active]'); const sharedEl = $shared.get(0); if (sharedEl) { $shared.find('.chrome-tab-title').html(curr.label); sharedEl.dataset.state = ''; // 清除当前状态 sharedEl.dataset.path = curr.pathname; cache.cacheTabs(tabs.tabEls); return; } } // 添加一个tab tabs.addTab(curr); }, [location]); const Tabs = useMemo(() => { const tabItems = !storage ? [] : cache.getValidCacheTabs(); return (React.createElement("div", { className: "chrome-tabs", ref: (dom) => { $storeRef.current.$tabsDom = dom; } }, React.createElement("div", { className: "chrome-tabs-content" }, tabItems.map((item) => (React.createElement("div", { key: item.id, className: "chrome-tab", "data-root": item.isRoot ? item.pathname : '', "data-path": item.pathname }, React.createElement("div", { className: "chrome-tab-dividers" }), React.createElement("div", { className: "chrome-tab-background" }, React.createElement("svg", { version: "1.1", xmlns: "http://www.w3.org/2000/svg" }, React.createElement("defs", null, React.createElement("symbol", { id: "chrome-tab-geometry-left", viewBox: "0 0 214 36" }, React.createElement("path", { d: "M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" })), React.createElement("symbol", { id: "chrome-tab-geometry-right", viewBox: "0 0 214 36" }, React.createElement("use", { xlinkHref: "#chrome-tab-geometry-left" })), React.createElement("clipPath", { id: "crop" }, React.createElement("rect", { className: "mask", width: "100%", height: "100%", x: "0" }))), React.createElement("svg", { width: "52%", height: "100%" }, React.createElement("use", { xlinkHref: "#chrome-tab-geometry-left", width: "214", height: "36", className: "chrome-tab-geometry" })), React.createElement("g", { transform: "scale(-1, 1)" }, React.createElement("svg", { width: "52%", height: "100%", x: "-100%", y: "0" }, React.createElement("use", { xlinkHref: "#chrome-tab-geometry-right", width: "214", height: "36", className: "chrome-tab-geometry" }))))), React.createElement("div", { className: "chrome-tab-content" }, React.createElement("div", { className: "chrome-tab-favicon" }), React.createElement("div", { className: "chrome-tab-title" }, item.label), React.createElement("div", { className: "chrome-tab-drag-handle" }), React.createElement("div", { className: "chrome-tab-close" }, React.createElement("svg", { viewBox: "0 0 1024 1024", version: "1.1", xmlns: "http://www.w3.org/2000/svg", "p-id": "2352", xmlnsXlink: "http://www.w3.org/1999/xlink", width: "200", height: "200" }, React.createElement("path", { d: "M518.5815877 469.42879156L240.02576609 190.87364889a34.76142882 34.76142882 0 1 0-49.17520097 49.12971238l278.55446374 278.57822643L190.85056508 797.15913522a34.76142882 34.76142882 0 1 0 49.15279619 49.15211725l278.57822651-278.55446378 278.57822634 278.55446378a34.76142882 34.76142882 0 1 0 49.15211728-49.12903345l-278.55446374-278.60063124 278.55446374-278.55514271a34.76142882 34.76142882 0 1 0-49.15211728-49.15279622L518.60467145 469.42879156z", "p-id": "2353" })))))))))); }, []); return (React.createElement(StyledRouteTabs, { className: `${themeNs}RouteTabs chrome-route-tabs` }, Tabs)); }; export default withRouter(RouteTabs);