@ovine/core
Version:
Build flexible admin system with json.
238 lines (237 loc) • 11.4 kB
JavaScript
/**
* APP 路由相关组件
* 优化: 由于route读取数据时,会有短暂的 404
* BUG: 页面切换太快时,会导致页面报错
* Uncaught (in promise) Error: [mobx-state-tree] Cannot modify 'ErrorDetail[]@/errorData [dead]', the object is protected and can only be modified by using an action.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { Spinner } from 'amis';
import { eachTree, flattenTree } from 'amis/lib/utils/helper';
import { isFunction, map, get, cloneDeep, omit, pick } from 'lodash';
import React, { createContext, lazy, useContext, useMemo, Suspense, useState, useEffect, isValidElement, useRef, } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { app } from "../app";
import NotFound from "../components/404";
import { Amis } from "../components/amis/schema";
import { useDebounceRender } from "../components/debounce_render";
import ErrorBoundary from "../components/error_boundary";
import { isSubStr } from "../utils/tool";
import { setRoutesConfig } from "./config";
import { getNodePath, getPageMockSource, getPagePreset, getRoutePath, currPath, getPageFileAsync, } from "./exports";
import { clearRouteStore, getAsideMenus, getAuthRoutes } from "./limit";
import { checkLimitByKeys } from "./limit/exports";
const PageSpinner = React.createElement(Spinner, { overlay: true, show: true, size: "lg", key: "pageLoading" });
// 根据 path,pathToComponent 参数 懒加载 `pages/xxx` 组件
export const getPageAsync = (option) => {
return lazy(() => getPageFileAsync(option).then((file) => {
const { default: content = {}, schema, getSchema } = file;
const defaultSchema = {
type: 'wrapper',
body: '请传入正确的 schema',
};
const compProps = {};
if (isFunction(content)) {
compProps.LazyFileComponent = content;
}
else {
if (schema || getSchema) {
content.schema = isFunction(getSchema) ? getSchema(option) : schema || defaultSchema;
}
if (!content.schema) {
content.schema = defaultSchema;
}
compProps.lazyFileAmisProps = content;
}
return {
default: () => React.createElement(PrestComponent, Object.assign({}, option, compProps)),
};
}));
};
// 登录路由拦截
export const PrivateRoute = (props) => {
const { onAuth, onRedirect, redirect, children } = props, rest = __rest(props, ["onAuth", "onRedirect", "redirect", "children"]);
const [isAuth, setAuth] = useState(null);
const isMounted = useRef(null);
const redirectPath = (onRedirect ? onRedirect() : redirect) || app.constants.loginRoute;
const checkAuth = () => __awaiter(void 0, void 0, void 0, function* () {
if (isFunction(onAuth)) {
const authRes = yield onAuth();
if (isMounted.current) {
setAuth(authRes);
}
return;
}
setAuth(true);
});
useEffect(() => {
isMounted.current = true;
checkAuth();
return () => {
isMounted.current = false;
};
}, []);
if (isAuth === null) {
return React.createElement("div", null);
}
return (React.createElement(Route, Object.assign({}, rest, { render: ({ location }) => {
if (isAuth) {
return children;
}
if (redirectPath) {
return (React.createElement(Redirect, { to: {
pathname: redirectPath,
state: { from: location },
} }));
}
return React.createElement("div", null, "unauthorized route.");
} })));
};
// usePresetContext 可获取 preset 值,与 checkLimit 校验权限 方法
const PresetContext = createContext({
route: {},
});
export const usePresetContext = () => {
const preset = useContext(PresetContext);
const checkLimit = (keys, option) => checkLimitByKeys(keys, Object.assign({ nodePath: preset.nodePath }, option));
return Object.assign(Object.assign({}, preset), { checkLimit });
};
// 将 preset 注入组件,可全局通过 usePresetContext 获取 preset 值
const PrestComponent = (props) => {
const { LazyFileComponent, lazyFileAmisProps, RouteComponent } = props, rest = __rest(props, ["LazyFileComponent", "lazyFileAmisProps", "RouteComponent"]);
const { path, exact, children, pathToComponent, nodePath: propNodePath } = rest;
const preset = useMemo(() => {
const fileOption = { path, pathToComponent, nodePath: propNodePath };
const mockSource = !app.env.isMock ? undefined : getPageMockSource(fileOption);
const nodePath = getNodePath(fileOption);
const presetConf = cloneDeep(Object.assign(Object.assign({}, pick(rest, ['limits', 'apis'])), (getPagePreset(fileOption) || get(lazyFileAmisProps, 'schema.preset') || {})));
presetConf.nodePath = exact && children && path ? path : nodePath;
map(presetConf.apis, (api) => {
// 自动注入规则
if (api.url && api.mock !== false && !api.mockSource && mockSource) {
api.mockSource = mockSource[api.api || api.url] || mockSource;
}
});
return presetConf;
}, [path, pathToComponent, propNodePath]);
const contextValue = Object.assign(Object.assign({}, preset), { route: omit(rest, Object.keys(preset)) });
let Component = React.createElement("div", null, "Not Found \u8BF7\u68C0\u67E5\u8DEF\u7531\u8BBE\u7F6E");
if (LazyFileComponent) {
Component = React.createElement(LazyFileComponent, Object.assign({}, rest));
}
if (RouteComponent) {
Component = React.createElement(RouteComponent, Object.assign({}, rest));
}
if (lazyFileAmisProps) {
const contextRef = get(lazyFileAmisProps, 'props.contextRef');
if (isFunction(contextRef)) {
contextRef(contextValue);
}
lazyFileAmisProps.schema.preset = Object.assign(Object.assign({}, lazyFileAmisProps.schema.preset), preset);
Component = React.createElement(Amis, Object.assign({}, rest, lazyFileAmisProps));
}
return React.createElement(PresetContext.Provider, { value: contextValue }, Component);
};
// 处理每个路由,包裹 PrestComponent 组件
export const PrestRoute = (props) => {
var _a;
const { component, children, withSuspense = true, fallback = PageSpinner, path = '', exact = true, debounceRoute = 0 } = props, rest = __rest(props, ["component", "children", "withSuspense", "fallback", "path", "exact", "debounceRoute"]);
const routePath = getRoutePath(path);
const locationKey = ((_a = rest === null || rest === void 0 ? void 0 : rest.location) === null || _a === void 0 ? void 0 : _a.key) || '';
const keyRef = useRef('');
if (locationKey) {
keyRef.current = locationKey;
}
const RouteComponent = (React.createElement(Route, Object.assign({}, rest, { path: routePath, exact: exact, component: !component
? getPageAsync(props)
: () => React.createElement(PrestComponent, Object.assign({}, props, { RouteComponent: component })) }), isValidElement(children) ? children : null));
const getRouteComponent = () => {
if (exact && !isSubStr(routePath, ':') && routePath !== window.location.pathname) {
return React.createElement(Redirect, { to: app.constants.notFound.route });
}
if (withSuspense) {
return (React.createElement(ErrorBoundary, { type: "page" },
React.createElement(Suspense, { fallback: fallback }, RouteComponent)));
}
return RouteComponent;
};
// TODO: 在 qiankun 中, 每次点击路由切换会,刷新强制触发页面,两次更新。在飞 qiankun 页面下,没问题。
// 该解决方案会导致页面切换时会闪一下
const CachedRoute = useDebounceRender({
getComponent: getRouteComponent,
wait: debounceRoute,
}, [keyRef.current]);
return debounceRoute ? CachedRoute : getRouteComponent();
};
// TODO: 支持自定义 404
const NotFoundRoute = () => {
let Component = NotFound;
const notFoundFilePath = app.constants.notFound.pagePath;
if (notFoundFilePath) {
try {
Component = lazy(() => getPageFileAsync({
nodePath: currPath(notFoundFilePath, '404'),
}));
}
catch (_) {
Component = NotFound;
}
}
return React.createElement(Route, { path: "*", component: Component });
};
// 将 routeConfig 转换为 route
export const AppMenuRoutes = (props) => {
const menuRoutes = [];
const { debounceRoute, pathPrefix = '', authRoutes, fallback: FallBack } = props;
// eslint-disable-next-line
eachTree(authRoutes, (item) => {
const { path, limitOnly } = item;
if (path && !limitOnly) {
const routeProps = Object.assign({ debounceRoute, key: menuRoutes.length + 1, fallback: React.createElement(FallBack, null) }, item);
routeProps.path = `${pathPrefix}${path}`;
menuRoutes.push(React.createElement(PrestRoute, Object.assign({}, routeProps)));
}
});
return (React.createElement(Switch, null,
menuRoutes,
React.createElement(NotFoundRoute, null)));
};
/**
* 将routes配置转为路由的hooks,可用于更加方便创建自定义 layout
*/
export function useRoutesConfig(options) {
const { routes, pathPrefix } = options;
const { AuthRoutes, asideMenus = [], authRoutes = [] } = useMemo(() => {
clearRouteStore();
setRoutesConfig(routes);
const configs = {
authRoutes: getAuthRoutes(),
asideMenus: flattenTree(getAsideMenus())
.filter((i) => !!i.path)
.map((i) => {
i.path = `${pathPrefix || ''}${i.path}`;
return i;
}),
};
return Object.assign(Object.assign({}, configs), { AuthRoutes: (React.createElement(AppMenuRoutes, { pathPrefix: pathPrefix, fallback: "loading...", authRoutes: configs.authRoutes })) });
}, [routes]);
return { AuthRoutes, asideMenus, authRoutes };
}