): void;
onContextMenuVisibleChange?(visible: boolean): void;
}
diff --git a/packages/mlz-admin/src/tree/index.less b/packages/mlz-admin/src/tree/index.less
old mode 100644
new mode 100755
diff --git a/packages/mlz-adminer/package-lock.json b/packages/mlz-adminer/package-lock.json
new file mode 100755
index 00000000..c0ac0c81
--- /dev/null
+++ b/packages/mlz-adminer/package-lock.json
@@ -0,0 +1,156 @@
+{
+ "name": "@mlz/adminer",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.14.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
+ "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "mini-create-react-context": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
+ "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "tiny-warning": "^1.0.3"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.14.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
+ "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "react-router": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
+ "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "history": "^4.9.0",
+ "hoist-non-react-statics": "^3.1.0",
+ "loose-envify": "^1.3.1",
+ "mini-create-react-context": "^0.4.0",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.2",
+ "react-is": "^16.6.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.14.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
+ "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ },
+ "resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
+ "tiny-invariant": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
+ "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
+ },
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ }
+ }
+}
diff --git a/packages/mlz-adminer/package.json b/packages/mlz-adminer/package.json
index 7001b173..952473bb 100755
--- a/packages/mlz-adminer/package.json
+++ b/packages/mlz-adminer/package.json
@@ -1,6 +1,6 @@
{
"name": "@mlz/adminer",
- "version": "0.10.0",
+ "version": "1.1.9",
"description": "@mlz/admin配套的交互和业务代码库",
"license": "MPL2",
"keywords": [
@@ -19,12 +19,13 @@
"dependencies": {
"@babel/runtime": "^7.10.2",
"@mlz/admin": "^0.8.5",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-router": "^5.2.0",
"umi-request": "^1.3.5"
},
"peerDependencies": {
- "@mlz/admin": ">=0.8.5",
+ "@mlz/admin": ">=0.9.2",
"umi-request": ">=1.3.5"
},
"devDependencies": {
@@ -47,4 +48,4 @@
"registry": "https://registry.npmjs.org/"
},
"gitHead": "2fed11fc4cc30fd18219c3ddf5aea7ca5e778143"
-}
\ No newline at end of file
+}
diff --git a/packages/mlz-adminer/src/shared/basic-class/index.ts b/packages/mlz-adminer/src/shared/basic-class/index.ts
old mode 100644
new mode 100755
diff --git a/packages/mlz-adminer/src/shared/basic-request-hook/index.tsx b/packages/mlz-adminer/src/shared/basic-request-hook/index.tsx
old mode 100644
new mode 100755
index 3c5e93cc..de82660a
--- a/packages/mlz-adminer/src/shared/basic-request-hook/index.tsx
+++ b/packages/mlz-adminer/src/shared/basic-request-hook/index.tsx
@@ -1,26 +1,27 @@
import React, { useState, useEffect, useCallback } from 'react';
import { isCompiled } from '../service/constant';
import { IBasicHooksOptions } from './index.type';
+import { getRequestStatus } from '../utils';
+const Window = window as any;
const responseHandler = (res: any, responseSetter: Function, loadingToggler: Function, isCatchingError = false) => {
!isCatchingError && responseSetter(res);
loadingToggler(false);
return res;
};
-export const noManualSettedReminder = `you should try run/cancel with a spacified manual option setted in hooks`;
+export const noManualSettedReminder = `run/cancel function should ran with a spacified 'manual' option setted`;
/**
*
- * @param fetchPromise: Function
- * @param options: object
+ * @param promiseFunction
+ * @param options
*
- * @remarks
- * P是传入的Options的类型。
- * R是该请求发起完毕后,response的类型。
+ * means P-arams, is the type of 2nd param.
+ * means R-esponse, is the return type of 1st param's ran result, it usually to be a promise-resolved data.
*/
-const useBasicRequest = , R>(fetchPromise: (...args: any[]) => Promise, options?: P) => {
+const useBasicRequest = , R>(promiseFunction: (...args: any[]) => Promise, options?: P) => {
//
- const { manual, deps, requestParams, init } = options || {};
+ const { manual, deps, requestParams, singleton = false, cache = 0, init } = options || {};
//
const [response, setResponse] = useState();
@@ -32,28 +33,86 @@ const useBasicRequest = , R>(fetchPromise:
throw new Error(noManualSettedReminder);
}
toggleLoading(true);
- // REMARK: run方法携带的参数权重高于requestParams
- return fetchPromise(args || requestParams)
+
+ // ❗️REMARK: args of run() is important! than requestParams here
+ return promiseFunction(args || requestParams)
.then((res) => responseHandler(res, setResponse, toggleLoading))
.catch((err) => responseHandler(err, setResponse, toggleLoading, true));
}, []);
- //
+ // TODO: abort/throttle/debounce/cache etc,
const abort = useCallback(() => {}, []);
- // TODO: throttle/debounce/cache etc,
-
useEffect(() => {
if (!manual) {
- fetchPromise(requestParams)
- .then((res) => responseHandler(res, setResponse, toggleLoading))
- .catch((err) => responseHandler(err, setResponse, toggleLoading, true));
+ // 开启请求单例
+ const uniqueFrameKey = `${promiseFunction.name}-${JSON.stringify(requestParams)}`;
+ const fetcher = (flag: 'forced' | 'unforced', checker = Window.runningBasicRequestHooksPromise, fromCache?: boolean, initData?: unknown) => {
+ // read from cache
+ if (fromCache) {
+ let data = null as any;
+ // 获取缓存数据
+ if (typeof checker?.get(uniqueFrameKey) !== 'number') {
+ data = checker?.get(uniqueFrameKey);
+ }
+ responseHandler(data, setResponse, toggleLoading);
+ return Promise.resolve(data);
+ }
+
+ //
+ if (checker?.get(uniqueFrameKey) !== getRequestStatus('pending') || flag === 'forced') {
+ return promiseFunction(requestParams)
+ .then((res) => {
+ responseHandler(res, setResponse, toggleLoading);
+ checker?.set(uniqueFrameKey, res);
+ })
+ .catch((err) => {
+ responseHandler(err, setResponse, toggleLoading, true);
+ checker?.set(uniqueFrameKey, getRequestStatus('rejected'));
+ });
+ }
+ };
+ if (singleton) {
+ // keep promises to be a singleton. avoiding duplicated requests from serveral
+ // components who's using this or hooks those packaged with this.
+ const singletonTarget = Window.runningBasicRequestHooksPromise || new Map();
+ if (!singletonTarget) {
+ // mount a singleton pattern instance onto Window.
+ Window.runningBasicRequestHooksPromise = new Map();
+ }
+ if (singletonTarget.has(uniqueFrameKey)) {
+ if (singletonTarget.get(uniqueFrameKey) !== getRequestStatus('pending')) {
+ fetcher('unforced', singletonTarget);
+ singletonTarget.set(uniqueFrameKey, getRequestStatus('pending'));
+ } else if (singletonTarget.get(uniqueFrameKey) === getRequestStatus('pending')) {
+ // another request is launching, change fetcher to be getter for cache
+ const loadCache = async () => {
+ const cachedData = await fetcher('unforced', singletonTarget, true);
+ if (!cachedData) {
+ Window.requestAnimationFrame(loadCache);
+ }
+ };
+ Window.requestAnimationFrame(loadCache);
+ }
+ } else {
+ fetcher('unforced', singletonTarget);
+ singletonTarget.set(uniqueFrameKey, getRequestStatus('pending'));
+ }
+ Window.runningBasicRequestHooksPromise = singletonTarget;
+ } else {
+ fetcher('forced');
+ }
}
}, deps ?? []);
//
const devResult = { loading, run, data: response, abort, setResponse, toggleLoading };
- return isCompiled ? { loading, run, ...(!manual && { data: response, abort }) } : devResult;
+
+ type TDevResult = typeof devResult;
+ type TCompiledBundleType = { loading: boolean; run: Function; data: R; abort: Function };
+
+ const returnedValue = (isCompiled ? ({ loading, run, ...(!manual && { data: response, abort }) } as TCompiledBundleType) : devResult) as TDevResult;
+ return returnedValue;
};
export default useBasicRequest;
diff --git a/packages/mlz-adminer/src/shared/basic-request-hook/index.type.ts b/packages/mlz-adminer/src/shared/basic-request-hook/index.type.ts
old mode 100644
new mode 100755
index d5e98bae..b53ab255
--- a/packages/mlz-adminer/src/shared/basic-request-hook/index.type.ts
+++ b/packages/mlz-adminer/src/shared/basic-request-hook/index.type.ts
@@ -9,12 +9,16 @@ export interface IBasicHooksOptions {
debounce: boolean;
// 是否开启节流
throttle: boolean;
- // 是否开启缓存
- cache: boolean | supportedCacheMethods;
+ // 是否开启缓存(暂时只支持get方法)
+ cache: boolean | number;
+ //
init: {
loading: boolean;
};
// 请求相关
data: unknown;
+ // 发起的请求的参数
requestParams: object;
+ // 单例请求
+ singleton: boolean;
}
diff --git a/packages/mlz-adminer/src/shared/index.tsx b/packages/mlz-adminer/src/shared/index.tsx
old mode 100644
new mode 100755
diff --git a/packages/mlz-adminer/src/shared/service/index.ts b/packages/mlz-adminer/src/shared/service/index.ts
index c0a686d6..1dcacd40 100755
--- a/packages/mlz-adminer/src/shared/service/index.ts
+++ b/packages/mlz-adminer/src/shared/service/index.ts
@@ -44,7 +44,7 @@ export type IHttpObject = Record {
- $http[method] = async (url: URL['href'], opts?: Partial) => {
+ $http[method] = async (url: URL['href'], opts?: Partial): Promise => {
const { host = config[env]?.['hosts']?.['open-service'], data, params, ...rests } = opts || {};
return umiRequest[method](`${host}/${url.replace(/^\//g, '')}`, {
data,
diff --git a/packages/mlz-adminer/src/shared/utils/index.ts b/packages/mlz-adminer/src/shared/utils/index.ts
old mode 100644
new mode 100755
index b3186479..242cff63
--- a/packages/mlz-adminer/src/shared/utils/index.ts
+++ b/packages/mlz-adminer/src/shared/utils/index.ts
@@ -1,4 +1,73 @@
+import cloneDeep from 'lodash/cloneDeep';
+import groupBy from 'lodash/groupBy';
+
/**
*
*/
export const serielizeParams2Url = (obj: Record) => {};
+
+/**
+ *
+ * @param fn 需要缓存的函数
+ * @param duration 缓存保存时长
+ */
+export const memoizeFunctionResult = (fn: Function, duration: number) => {
+ if (typeof fn !== 'function') {
+ throw new Error('first param of memoizeFunctionResult should be a type of Function, but got ' + typeof fn);
+ }
+ const results = new Map();
+ return (...args) => {
+ const key = args.toString();
+ const now = +new Date();
+ if (results.has(key)) {
+ if (now - results.get(key).time > duration) {
+ // 保鲜期过了
+ return fn(args);
+ } else {
+ return results.get(key).result;
+ }
+ } else {
+ // 如果fn是一个普通函数
+ const result = fn(args);
+ results.set(key, {
+ result,
+ time: now,
+ });
+ return result;
+ }
+ };
+};
+
+/**
+ * 根据primaryKey和foldPointFlag的管道对应关系。
+ * 将一个flatten的数组树立为父子折叠结构结构
+ */
+export const foldArray = (flattenedArray: P[], opts: { primaryKey: string | number; foldPointFlag: string | number; childrenKey?: string }) => {
+ const { primaryKey, foldPointFlag, childrenKey = 'children' } = opts || {};
+
+ const newArray = cloneDeep(flattenedArray);
+ const groupedArray = groupBy(newArray, foldPointFlag);
+ const result = [] as any;
+
+ newArray.forEach((item) => {
+ const foldPointFlagValue = item[primaryKey];
+ if (foldPointFlagValue) {
+ const children = groupedArray[foldPointFlagValue] || [];
+ item[childrenKey] = children;
+ }
+ result.push(item);
+ });
+
+ return result as P[];
+};
+
+// eslint-disable-next-line no-shadow
+enum EBasicRequestHooksStatus {
+ 'waiting' = 0,
+ 'pending' = 1 << 0,
+ 'resolved' = 1 << 1,
+ 'rejected' = 1 << 2,
+}
+export const getRequestStatus = (statusNumber: number | keyof typeof EBasicRequestHooksStatus) => {
+ return EBasicRequestHooksStatus[statusNumber];
+};
diff --git a/packages/mlz-adminer/src/use-auth-guard/accessories/auth-menu.tsx b/packages/mlz-adminer/src/use-auth-guard/accessories/auth-menu.tsx
new file mode 100755
index 00000000..6a14f705
--- /dev/null
+++ b/packages/mlz-adminer/src/use-auth-guard/accessories/auth-menu.tsx
@@ -0,0 +1,64 @@
+import React, { createContext, useContext, useMemo, memo } from 'react';
+import { Menu } from 'antd';
+import useAuthGuard from '..';
+import { IAuthMenuProps, TMenuListItem } from '../index.type';
+import { MenuProps, MenuItemProps } from 'antd/lib/menu';
+import { foldArray } from '../../shared/utils';
+
+/**
+ *
+ * @param props
+ * @returns
+ */
+const { SubMenu } = Menu;
+interface InnerAuthMenuProps extends MenuProps {
+ menus: TMenuListItem[];
+ subMenuFlag: string;
+ primaryKeyFlag: string;
+ itemOptions?: MenuItemProps;
+}
+const InnerAuthMenu = memo((props: InnerAuthMenuProps) => {
+ const { menus, primaryKeyFlag, subMenuFlag, itemOptions, onClick, ...others } = props;
+ const foldedMenus = useMemo(() => {
+ return foldArray(menus, {
+ primaryKey: primaryKeyFlag,
+ foldPointFlag: subMenuFlag,
+ });
+ }, [menus, subMenuFlag]);
+
+ // 根据subMenuFlag group出父子菜单
+ return (
+
+ );
+});
+
+/**
+ *
+ */
+const AuthMenu = (props?: IAuthMenuProps) => {
+ const { AuthGuardRender } = useAuthGuard();
+ return (
+ {(info) => }
+ );
+};
+
+export default AuthMenu;
diff --git a/packages/mlz-adminer/src/use-auth-guard/accessories/auth-resource.tsx b/packages/mlz-adminer/src/use-auth-guard/accessories/auth-resource.tsx
new file mode 100755
index 00000000..e22a211c
--- /dev/null
+++ b/packages/mlz-adminer/src/use-auth-guard/accessories/auth-resource.tsx
@@ -0,0 +1,17 @@
+import React, { useMemo } from 'react';
+import useAuthGuard from '..';
+
+const AuthResource = (props: { code: string; children: React.ReactElement; disabledOrHidden?: 'disabled' | 'hidden' }) => {
+ const { code, children, disabledOrHidden = 'hidden' } = props;
+ const { AuthGuardRender } = useAuthGuard();
+ const nullChild = useMemo(() => {
+ if (disabledOrHidden === 'hidden') {
+ return <>>;
+ } else {
+ return React.cloneElement(children, { disabled: true });
+ }
+ }, [children, disabledOrHidden]);
+ return {({ resourceMap }) => (resourceMap.has(code) ? children : nullChild)};
+};
+
+export default AuthResource;
diff --git a/packages/mlz-adminer/src/use-auth-guard/controller.ts b/packages/mlz-adminer/src/use-auth-guard/controller.ts
new file mode 100755
index 00000000..ae2f5bd0
--- /dev/null
+++ b/packages/mlz-adminer/src/use-auth-guard/controller.ts
@@ -0,0 +1,45 @@
+import $http, { paramsDiverter } from '../shared/service';
+import config, { isCompiled, env, mockHost, redirectMiddleService, OpenServiceHOST } from '../shared/service/constant';
+import { TMenuResponse, TResourceResponse, IAuthRequestParams } from './index.type';
+
+const { timeout = 5000 } = config?.[env] || {};
+
+// 获取菜单列表
+export const getMenus = (params?: IAuthRequestParams): Promise => {
+ const { host } = params || {};
+ const GET_MENUS_MOCK_URL = `${mockHost}/menus-demo`;
+ const getMenusURL = isCompiled ? `${host || OpenServiceHOST}/decode/phone_number` : redirectMiddleService;
+ const requestUrl = new URL(getMenusURL);
+
+ return >$http
+ .post(requestUrl.pathname, {
+ timeout,
+ ...paramsDiverter(params || {}, {
+ data: { url: `${GET_MENUS_MOCK_URL}` },
+ }),
+ host: host || `https://${requestUrl.host}`,
+ })
+ .then((res: any) => {
+ return JSON.parse(res);
+ });
+};
+
+// 获取资源列表
+export const getResources = (params?: IAuthRequestParams): Promise => {
+ const { host } = params || {};
+ const GET_RESOURCES_MOCK_URL = `${mockHost}/resources-demo`;
+ const getResourcesURL = isCompiled ? `${OpenServiceHOST}/decode/phone_number` : redirectMiddleService;
+ const requestUrl = new URL(getResourcesURL);
+
+ return >$http
+ .post(requestUrl.pathname, {
+ timeout,
+ ...paramsDiverter(params || {}, {
+ data: { url: `${GET_RESOURCES_MOCK_URL}` },
+ }),
+ host: host || `https://${requestUrl.host}`,
+ })
+ .then((res: any) => {
+ return JSON.parse(res);
+ });
+};
diff --git a/packages/mlz-adminer/src/use-auth-guard/index.tsx b/packages/mlz-adminer/src/use-auth-guard/index.tsx
new file mode 100755
index 00000000..fe932af2
--- /dev/null
+++ b/packages/mlz-adminer/src/use-auth-guard/index.tsx
@@ -0,0 +1,107 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import useBasicRequest from '../shared/basic-request-hook';
+import { useAuthGuardOptions, TMenuListItem, TResourceListItem, IAuthGuardProps } from './index.type';
+import { RouteProps } from 'react-router';
+import AuthMenu from './accessories/auth-menu';
+import AuthResource from './accessories/auth-resource';
+import { default as Auth } from './model';
+
+// typings
+type IAuthRequestParams = Omit;
+
+/**
+ * 创建context
+ */
+export const Context = createContext<{ menus: TMenuListItem[]; resources: TResourceListItem[]; routes: any[] }>({
+ menus: [],
+ resources: [],
+ routes: [],
+});
+
+/**
+ *
+ */
+export const reactRouterMaker = (menuCode, opts?: RouteProps): RouteProps => {
+ return {
+ path: menuCode,
+ exact: true,
+ strict: true,
+ ...opts,
+ };
+};
+
+//
+const newAuth = Auth.create();
+const asyncGetAuthInfo = () => Promise.all([newAuth.getMenus(), newAuth.getResources()]);
+const useAuthGuard = (options?: useAuthGuardOptions, deps?: any[]) => {
+ //
+ const { passeds, forbiddens } = options || {};
+
+ //
+ const context = useContext(Context);
+
+ // TODO: 因为是全局型hooks,所以尽量使用缓存,在其它页面不要调用
+ const { data } = useBasicRequest(asyncGetAuthInfo, { singleton: true, deps, ...options });
+ const [menus, resources] = useMemo(() => {
+ if (data) {
+ return [data[0]?.menus, data[1]?.resources];
+ }
+ return [[], []];
+ }, [data]);
+
+ /**
+ * 需求:
+ * 1.根据menus分配前端动态路由,没有的不允许访问。
+ * 2.根据resources对应的resource_url,在status为YES的情况下,也要成为生成路由的素材。
+ * 3.根据passeds将一些权限完全
+ */
+ const routes = useMemo(() => {
+ return [
+ ...menus?.map(($m) => $m.menu_code),
+ ...resources?.map(($r) => {
+ if (['YES', 1, true].includes($r.status)) {
+ return $r.resource_url;
+ } else {
+ return undefined;
+ }
+ }),
+ ]
+ .concat(passeds)
+ .filter((item) => {
+ return item && !forbiddens?.some((r) => r.path === item.path);
+ })
+ .map((item) => reactRouterMaker(item));
+ }, [menus, resources, passeds, forbiddens]);
+
+ // REMARK: resources是一个weakMap结构的数据,而非object/array
+ const resourceMap = new Map();
+ resources.forEach((item: TResourceListItem) => {
+ resourceMap.set(item.resource_code, item);
+ });
+
+ const AuthGuardRender = useMemo(() => {
+ const contextPayload = {
+ menus,
+ resourceMap,
+ routes,
+ };
+ return (props: IAuthGuardProps) => {
+ const { children } = props;
+ return (
+ <>{children(contextPayload)}>
+
+ // 权限系统目前来看,可以视为一个常量,用于派生
+ // 对应的menus或resources,所以不需要context后续更改
+ //
+ //
+ // {() => children(contextPayload)}
+ //
+ );
+ };
+ }, [menus, resources, routes]);
+
+ return { menus, resourceMap, routes, AuthGuardRender, _Context: Context };
+};
+
+export { AuthMenu, AuthResource };
+export default useAuthGuard;
diff --git a/packages/mlz-adminer/src/use-auth-guard/index.type.ts b/packages/mlz-adminer/src/use-auth-guard/index.type.ts
new file mode 100755
index 00000000..d7baf525
--- /dev/null
+++ b/packages/mlz-adminer/src/use-auth-guard/index.type.ts
@@ -0,0 +1,68 @@
+import React from 'react';
+import { RouteProps } from 'react-router';
+import { MenuProps, MenuItemProps } from 'antd/lib/menu';
+
+export type TContact = {
+ id: string;
+ name: string;
+};
+
+export type TMenuListItem = {
+ menu_code: string;
+ menu_id: number;
+ menu_name: string;
+ order_number: number;
+ parent_id: number;
+ children?: TMenuListItem[];
+};
+
+export interface TMenuResponse extends Response {
+ readonly appid: number;
+ readonly app_name: string;
+ readonly authorizationContact: TContact;
+ readonly menu_list: TMenuListItem[];
+}
+
+export type TResourceListItem = {
+ description: string;
+ resourceId: number;
+ resource_code: string;
+ resource_name: string;
+ resource_url: string;
+ status: 'YES' | 'NO';
+};
+
+export interface TResourceResponse extends Response {
+ readonly app_id: number;
+ readonly app_name: string;
+ readonly menu_id: number;
+ readonly resources: TResourceListItem[];
+}
+
+export interface useAuthGuardOptions {
+ passeds?: RouteProps[];
+ forbiddens?: RouteProps[];
+}
+
+export interface IAuthRequestParams {
+ host?: string;
+}
+
+export interface IAuthGuardProps {
+ children: (...args: any) => React.ReactElement;
+}
+
+export interface IAuthGuardInfo {
+ menus?: TMenuListItem[];
+ resources?: TResourceListItem[];
+ routes?: RouteProps[];
+}
+
+export interface IAuthMenuProps extends MenuProps {
+ // 父菜单主键
+ primaryKeyFlag?: string;
+ // 子菜单的对应外键
+ subMenuFlag?: string;
+ //
+ itemOptions?: MenuItemProps;
+}
diff --git a/packages/mlz-adminer/src/use-auth-guard/model.ts b/packages/mlz-adminer/src/use-auth-guard/model.ts
new file mode 100755
index 00000000..ce59c022
--- /dev/null
+++ b/packages/mlz-adminer/src/use-auth-guard/model.ts
@@ -0,0 +1,19 @@
+import { getMenus, getResources } from './controller';
+import { Model } from '../shared/basic-class';
+import { TMenuListItem, TResourceListItem } from './index.type';
+
+export default class Auth extends Model {
+ static create = () => {
+ return new Auth();
+ };
+
+ // eslint-disable-next-line class-methods-use-this
+ getMenus() {
+ return getMenus();
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ getResources() {
+ return getResources();
+ }
+}
diff --git a/packages/mlz-adminer/src/use-staff-logout/__tests__/index.test.tsx b/packages/mlz-adminer/src/use-staff-logout/__tests__/index.test.tsx
old mode 100644
new mode 100755
diff --git a/packages/mlz-adminer/src/use-staff-logout/controller.ts b/packages/mlz-adminer/src/use-staff-logout/controller.ts
old mode 100644
new mode 100755
diff --git a/packages/mlz-adminer/src/use-staff-logout/index.type.ts b/packages/mlz-adminer/src/use-staff-logout/index.type.ts
old mode 100644
new mode 100755
diff --git a/packages/mlz-adminer/src/use-staff-logout/model.ts b/packages/mlz-adminer/src/use-staff-logout/model.ts
old mode 100644
new mode 100755
index 81878773..e3c798ff
--- a/packages/mlz-adminer/src/use-staff-logout/model.ts
+++ b/packages/mlz-adminer/src/use-staff-logout/model.ts
@@ -5,10 +5,6 @@ import { useStaffLogoutOptions } from './index.type';
class Staff extends Model {
readonly staff = {};
- // constructor() {
- // super();
- // }
-
// eslint-disable-next-line class-methods-use-this
lougout(opts?: useStaffLogoutOptions) {
return logout(opts);
diff --git a/scripts/common/constants.js b/scripts/common/constants.js
index 2d3fd782..73e998c2 100755
--- a/scripts/common/constants.js
+++ b/scripts/common/constants.js
@@ -8,4 +8,5 @@ module.exports = {
SRC_PATH: path.join(PACKAGE_PATH, './mlz-admin/src'),
ADMINER_SRC_PATH: path.join(PACKAGE_PATH, './mlz-adminer/src'),
SERVICE_PATH: path.join(PACKAGE_PATH, './mlz-adminer/src/shared/service'),
+ ANTD_PATH: path.join(ROOT_PATH, './node_modules/antd'),
};
diff --git a/scripts/common/copyFile.js b/scripts/common/copyFile.js
index 0caa78ea..198e2ba3 100755
--- a/scripts/common/copyFile.js
+++ b/scripts/common/copyFile.js
@@ -1,12 +1,33 @@
#!/usr/bin/env node
-const fs = require('fs');
+/* eslint-disable no-console */
+const fs = require('fs-extra');
-function copy(fromPath, toPath) {
+// 拷贝单个文件
+function copyFile(sourceFilePath, targetPathFileName) {
try {
- fs.createReadStream(fromPath, { bufferSize: 64 * 1024 }).pipe(fs.createWriteStream(toPath));
+ fs.createReadStream(sourceFilePath, { bufferSize: 64 * 1024 }).pipe(fs.createWriteStream(targetPathFileName));
} catch (err) {
console.error(`❌ ${err}`);
}
}
-module.exports = copy;
+// 拷贝文件夹
+function copyDir(sourceDir, targetDir, opt = { overwrite: true }) {
+ //
+ if (!fs.pathExistsSync(targetDir)) {
+ fs.mkdirSync(targetDir);
+ }
+ if (fs.pathExistsSync(targetDir) && fs.statSync(sourceDir).isDirectory()) {
+ fs.copy(sourceDir, targetDir, opt, (err) => {
+ if (err) {
+ console.error(`❌ ${sourceDir} hasn't found copied to ${targetDir}`);
+ } else {
+ console.log(`✅ copied ${sourceDir} to ${targetDir}`);
+ }
+ });
+ } else {
+ console.error(`❌ ${sourceDir} or ${targetDir} shouled be a directory`);
+ }
+}
+
+module.exports = { copyFile, copyDir };
diff --git a/scripts/common/genAdminerExports.js b/scripts/common/genAdminerExports.js
index 650bd94e..fb08d5c2 100755
--- a/scripts/common/genAdminerExports.js
+++ b/scripts/common/genAdminerExports.js
@@ -7,7 +7,7 @@ const genAdminerExports = () =>
genExports(ADMINER_SRC_PATH, {
donotCamelizes: [],
donotCompiles: ['service'],
- extraContents: [],
+ extraContents: [`export { AuthMenu } from './use-auth-guard';`, ` export { AuthResource } from './use-auth-guard';`],
});
module.exports = genAdminerExports;
diff --git a/scripts/common/genVersion.js b/scripts/common/genVersion.js
index feae99c4..27308694 100755
--- a/scripts/common/genVersion.js
+++ b/scripts/common/genVersion.js
@@ -1,6 +1,6 @@
#!/usr/bin/env node
-const pkg = require('../../package.json');
+const { version } = require('../../lerna.json');
const antdVersion = require('../../node_modules/antd/package.json').version;
-module.exports = (opt = {}) => `export const version = '${pkg.version}';\n\r
+module.exports = (opt = {}) => `export const version = '${version}';\n\r
export const AntdVersion = '${opt.antdVersion || antdVersion}';`;
diff --git a/scripts/common/joinGeneratorsIntoBuffer.js b/scripts/common/joinGeneratorsIntoBuffer.js
deleted file mode 100755
index abdfab90..00000000
--- a/scripts/common/joinGeneratorsIntoBuffer.js
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env node
-const joinGeneratorsIntoBuffer = (...$generators) => {
- if ($generators.some((item) => typeof item !== 'function')) {
- // eslint-disable-next-line no-console
- console.error('joinBuffer\'s params must be function whoes name start with "gen"');
- return '';
- }
- return $generators.reduce((prev, curr) => (prev += `${curr()}\r\n\r\n`), '');
-};
-
-module.exports = joinGeneratorsIntoBuffer;
diff --git a/scripts/common/joinMakersIntoBuffer.js b/scripts/common/joinMakersIntoBuffer.js
new file mode 100755
index 00000000..8506e8b9
--- /dev/null
+++ b/scripts/common/joinMakersIntoBuffer.js
@@ -0,0 +1,11 @@
+#!/usr/bin/env node
+const joinMakersIntoBuffer = (...$makers) => {
+ if ($makers.some((item) => typeof item !== 'function')) {
+ // eslint-disable-next-line no-console
+ console.error('joinBuffer\'s params must be function whoes name start with "gen"');
+ return '';
+ }
+ return $makers.reduce((prev, curr) => (prev += `${curr()}\r\n\r\n`), '');
+};
+
+module.exports = joinMakersIntoBuffer;
diff --git a/scripts/copyStyles.js b/scripts/copyStyles.js
new file mode 100755
index 00000000..0892a8fe
--- /dev/null
+++ b/scripts/copyStyles.js
@@ -0,0 +1,58 @@
+#!/usr/bin/env node
+/* eslint-disable no-loop-func */
+/* eslint-disable no-console */
+const fs = require('fs');
+const path = require('path');
+const { copyDir } = require('./common/copyFile');
+
+const { ANTD_PATH, SRC_PATH } = require('./common/constants');
+const antdDepsStat = fs.statSync(ANTD_PATH);
+const ignores = ['styles', 'create-icon-font-script', 'dark-theme-toggler', 'detail-card', 'error-boundary', 'index.tsx', 'select-range', 'watermark', 'shared'];
+
+const copyDestedStyleFolder = (targetDir) => {
+ const antdStyleDestDir = path.join(ANTD_PATH, '.', `./lib/style`);
+ copyDir(antdStyleDestDir, targetDir, { overwrite: false });
+};
+
+if (antdDepsStat.isDirectory()) {
+ const files = fs.readdirSync(SRC_PATH);
+ for (let i = 0; i < files.length; i++) {
+ const folderName = files[i];
+ const componentDistEsmDirPath = path.join(SRC_PATH, '..', `./es/${folderName}`);
+ const componentDistLibDirPath = path.join(SRC_PATH, '..', `./lib/${folderName}`);
+ console.log(folderName, ignores.includes(folderName), 1122);
+
+ // 忽略
+ if (ignores.includes(folderName)) {
+ console.error(`🥷 ${folderName} had been ignored`);
+ continue;
+ }
+
+ // 拷贝style产物文件夹
+ if (folderName === 'style') {
+ [componentDistEsmDirPath, componentDistLibDirPath].forEach((styleArtifactDir) => {
+ copyDestedStyleFolder(styleArtifactDir);
+ });
+ continue;
+ }
+
+ // 操作其余产物
+ const componentBuiltStyleDir = path.join(ANTD_PATH, `./lib/${folderName}/style`);
+ // copy对应的css文件
+ if (fs.statSync(componentBuiltStyleDir).isDirectory()) {
+ // 对应组件的style是一个文件夹
+ [componentDistEsmDirPath, componentDistLibDirPath].forEach((artifactPath) => {
+ const targetArtifactDir = `${artifactPath}/style`;
+ if (fs.statSync(artifactPath).isDirectory()) {
+ copyDir(componentBuiltStyleDir, targetArtifactDir);
+ } else {
+ console.error(`❌ ${folderName} hasn't found compiled css styles`);
+ }
+ });
+ } else {
+ console.error(`❌ ${componentBuiltStyleDir} does not exists`);
+ }
+ }
+} else {
+ console.error(`❌ we didn't found 'antd' in ${ANTD_PATH}`);
+}
diff --git a/scripts/genIndex.js b/scripts/genIndex.js
index 4637be1f..f64f899f 100755
--- a/scripts/genIndex.js
+++ b/scripts/genIndex.js
@@ -8,31 +8,31 @@ const genBuildTime = require('./common/genBuildTime');
const genSupporttingEnv = require('./common/genSupporttingEnv');
const genAdminerExports = require('./common/genAdminerExports');
const genAdminerConfig = require('./common/genAdminerConfig');
-const joinGeneratorsIntoBuffer = require('./common/joinGeneratorsIntoBuffer');
+const joinMakersIntoBuffer = require('./common/joinMakersIntoBuffer');
const { SRC_PATH, ADMINER_SRC_PATH, SERVICE_PATH } = require('./common/constants');
[
// 生成admin组件列表
{
path: SRC_PATH + '/index.tsx',
- bufferGenerators: [genVersion, genBuildTime, genSupporttingEnv],
+ bufferMakers: [genVersion, genBuildTime, genSupporttingEnv],
extra: genExports(),
},
// 生成adminer hooks列表
{
path: ADMINER_SRC_PATH + '/index.tsx',
- bufferGenerators: [genAdminerExports],
+ bufferMakers: [genAdminerExports],
extra: [],
},
// 生成adminer的config列表
{
path: SERVICE_PATH + '/constant/config.ts',
- bufferGenerators: [genAdminerConfig],
+ bufferMakers: [genAdminerConfig],
extra: [],
},
].forEach(($section) => {
- const { path, bufferGenerators, extra } = $section;
- fs.writeFileSync(path, Buffer.from(`${joinGeneratorsIntoBuffer(...bufferGenerators)}${extra || ''}`), (err) => {
+ const { path, bufferMakers, extra } = $section;
+ fs.writeFileSync(path, Buffer.from(`${joinMakersIntoBuffer(...bufferMakers)}${extra || ''}`), (err) => {
if (err) {
console.error(`❌ 发生错误:${err}`);
} else {
diff --git a/scripts/genUmiExtraConf.js b/scripts/genUmiExtraConf.js
index d197a49e..7453ddc7 100755
--- a/scripts/genUmiExtraConf.js
+++ b/scripts/genUmiExtraConf.js
@@ -27,7 +27,7 @@ const extraStyles = [
height: 100%;
border-radius: 0;
opacity: 0.25;
- background-image: linear-gradient(to right, #1890FF 0%,#E6F7FF 80%);
+ background-color: #5fb2ff;
transition: transform 0s, opacity 0.2s;
}
.__dumi-default-layout .__dumi-default-navbar > nav ul > li > a {
diff --git a/typings/global.d.ts b/typings/global.d.ts
index bcad982d..d322d362 100755
--- a/typings/global.d.ts
+++ b/typings/global.d.ts
@@ -5,5 +5,6 @@ declare interface Window extends global.Window {
__MLZ_ADMIN_BUILD_ENV__: SupportingEnv | undefined;
__MLZ_ADMIN_RUNTIME_ENV__: 'development' | 'production' | 'test' | 'staging';
__MLZ_ADMIN_VERSION__: string;
- requestIdleCallback?: (callback: () => void, options: any) => void;
+ requestIdleCallback?: (callback: () => void, options?: any) => void;
+ runningBasicRequestHooksPromise?: Map;
}