@@ -1 +0,0 @@ | |||
preview.pro.ant.design |
@@ -1 +0,0 @@ | |||
(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[2571],{7594:function(u,_,t){"use strict";t.r(_);var P=t(57106),s=t(6129),d=t(57663),E=t(71577),e=t(67294),a=t(48971),n=t(85893),o=function(){return(0,n.jsx)(s.ZP,{status:"404",title:"404",subTitle:"Sorry, the page you visited does not exist.",extra:(0,n.jsx)(E.Z,{type:"primary",onClick:function(){return a.m8.push("/")},children:"Back Home"})})};_.default=o}}]); |
@@ -1 +0,0 @@ | |||
.ant-result{padding:48px 32px}.ant-result-success .ant-result-icon>.anticon{color:#52c41a}.ant-result-error .ant-result-icon>.anticon{color:#ff4d4f}.ant-result-info .ant-result-icon>.anticon{color:#fa541c}.ant-result-warning .ant-result-icon>.anticon{color:#faad14}.ant-result-image{width:250px;height:295px;margin:auto}.ant-result-icon{margin-bottom:24px;text-align:center}.ant-result-icon>.anticon{font-size:72px}.ant-result-title{color:rgba(0,0,0,.85);font-size:24px;line-height:1.8;text-align:center}.ant-result-subtitle{color:rgba(0,0,0,.45);font-size:14px;line-height:1.6;text-align:center}.ant-result-extra{margin:24px 0 0;text-align:center}.ant-result-extra>*{margin-right:8px}.ant-result-extra>:last-child{margin-right:0}.ant-result-content{margin-top:24px;padding:24px 40px;background-color:#fafafa}.ant-result-rtl{direction:rtl}.ant-result-rtl .ant-result-extra>*{margin-right:0;margin-left:8px}.ant-result-rtl .ant-result-extra>:last-child{margin-left:0} |
@@ -1 +0,0 @@ | |||
.ant-pro-descriptions .ant-descriptions-view{overflow:visible!important;overflow:initial!important} |
@@ -1 +0,0 @@ | |||
(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[961],{67971:function(){}}]); |
@@ -1,5 +0,0 @@ | |||
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg"> | |||
<g> | |||
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/> | |||
</g> | |||
</svg> |
@@ -0,0 +1,21 @@ | |||
// @ts-nocheck | |||
import { createBrowserHistory, History } from 'D:/Work/BPA.SAAS.Web/node_modules/umi/node_modules/@umijs/runtime'; | |||
let options = { | |||
"basename": "/" | |||
}; | |||
if ((<any>window).routerBase) { | |||
options.basename = (<any>window).routerBase; | |||
} | |||
// remove initial history because of ssr | |||
let history: History = process.env.__IS_SERVER ? null : createBrowserHistory(options); | |||
export const createHistory = (hotReload = false) => { | |||
if (!hotReload) { | |||
history = createBrowserHistory(options); | |||
} | |||
return history; | |||
}; | |||
export { history }; |
@@ -0,0 +1,8 @@ | |||
// @ts-nocheck | |||
import { Plugin } from 'D:/Work/BPA.SAAS.Web/node_modules/umi/node_modules/@umijs/runtime'; | |||
const plugin = new Plugin({ | |||
validKeys: ['modifyClientRenderOpts','patchRoutes','rootContainer','render','onRouteChange','__mfsu','getInitialState','initialStateConfig','locale','layout','layoutActionRef','request',], | |||
}); | |||
export { plugin }; |
@@ -0,0 +1,362 @@ | |||
// Created by Umi Plugin | |||
export interface IConfigFromPlugins { | |||
"404"?: boolean | |||
routes?: { | |||
/** | |||
* Any valid URL path | |||
*/ | |||
path?: string | |||
/** | |||
* A React component to render only when the location matches. | |||
*/ | |||
component?: (string | (() => any)) | |||
wrappers?: string[] | |||
/** | |||
* navigate to a new location | |||
*/ | |||
redirect?: string | |||
/** | |||
* When true, the active class/style will only be applied if the location is matched exactly. | |||
*/ | |||
exact?: boolean | |||
routes?: any[] | |||
[k: string]: any | |||
}[] | |||
history?: { | |||
type?: ("browser" | "hash" | "memory") | |||
options?: { | |||
} | |||
} | |||
polyfill?: { | |||
imports?: string[] | |||
} | |||
alias?: { | |||
} | |||
analyze?: { | |||
analyzerMode?: ("server" | "static" | "disabled") | |||
analyzerHost?: string | |||
analyzerPort?: any | |||
openAnalyzer?: boolean | |||
generateStatsFile?: boolean | |||
statsFilename?: string | |||
logLevel?: ("info" | "warn" | "error" | "silent") | |||
defaultSizes?: ("stat" | "parsed" | "gzip") | |||
[k: string]: any | |||
} | |||
/** | |||
* postcss autoprefixer, default flexbox: no-2009 | |||
*/ | |||
autoprefixer?: { | |||
} | |||
base?: string | |||
chainWebpack?: (() => any) | |||
chunks?: string[] | |||
/** | |||
* more css-loader options see https://webpack.js.org/loaders/css-loader/#options | |||
*/ | |||
cssLoader?: { | |||
url?: (boolean | (() => any)) | |||
import?: (boolean | (() => any)) | |||
modules?: (boolean | string | { | |||
}) | |||
sourceMap?: boolean | |||
importLoaders?: number | |||
onlyLocals?: boolean | |||
esModule?: boolean | |||
localsConvention?: ("asIs" | "camelCase" | "camelCaseOnly" | "dashes" | "dashesOnly") | |||
} | |||
cssModulesTypescriptLoader?: { | |||
mode?: ("emit" | "verify") | |||
} | |||
cssnano?: { | |||
} | |||
copy?: any[] | |||
define?: { | |||
} | |||
devScripts?: { | |||
} | |||
/** | |||
* devServer configs | |||
*/ | |||
devServer?: { | |||
/** | |||
* devServer port, default 8000 | |||
*/ | |||
port?: number | |||
host?: string | |||
https?: ({ | |||
key?: string | |||
cert?: string | |||
http2?: boolean | |||
[k: string]: any | |||
} | boolean) | |||
headers?: { | |||
} | |||
writeToDisk?: (boolean | (() => any)) | |||
[k: string]: any | |||
} | |||
devtool?: string | |||
/** | |||
* Code splitting for performance optimization | |||
*/ | |||
dynamicImport?: { | |||
/** | |||
* loading the component before loaded | |||
*/ | |||
loading?: string | |||
} | |||
/** | |||
* Code splitting for import statement syntax | |||
*/ | |||
dynamicImportSyntax?: { | |||
} | |||
exportStatic?: { | |||
htmlSuffix?: boolean | |||
dynamicRoot?: boolean | |||
supportWin?: boolean | |||
/** | |||
* extra render paths only enable in ssr | |||
*/ | |||
extraRoutePaths?: (() => any) | |||
} | |||
externals?: ({ | |||
} | string | (() => any)) | |||
extraBabelIncludes?: any[] | |||
extraBabelPlugins?: any[] | |||
extraBabelPresets?: any[] | |||
extraPostCSSPlugins?: any[] | |||
/** | |||
* fork-ts-checker-webpack-plugin options see https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options | |||
*/ | |||
forkTSChecker?: { | |||
async?: boolean | |||
typescript?: (boolean | { | |||
}) | |||
eslint?: { | |||
} | |||
issue?: { | |||
} | |||
formatter?: (string | { | |||
}) | |||
logger?: { | |||
} | |||
[k: string]: any | |||
} | |||
fastRefresh?: { | |||
} | |||
hash?: boolean | |||
ignoreMomentLocale?: boolean | |||
inlineLimit?: number | |||
lessLoader?: { | |||
} | |||
manifest?: { | |||
fileName?: string | |||
publicPath?: "" | |||
basePath?: string | |||
writeToFileEmit?: boolean | |||
} | |||
/** | |||
* open mfsu feature | |||
*/ | |||
mfsu?: { | |||
development?: { | |||
output?: string | |||
} | |||
production?: { | |||
output?: string | |||
} | |||
mfName?: string | |||
exportAllMembers?: { | |||
} | |||
chunks?: string[] | |||
ignoreNodeBuiltInModules?: boolean | |||
} | |||
mountElementId?: "" | |||
mpa?: { | |||
} | |||
nodeModulesTransform?: { | |||
type?: ("all" | "none") | |||
exclude?: string[] | |||
} | |||
outputPath?: "" | |||
plugins?: string[] | |||
postcssLoader?: { | |||
} | |||
presets?: string[] | |||
proxy?: { | |||
} | |||
publicPath?: string | |||
runtimePublicPath?: boolean | |||
ssr?: { | |||
/** | |||
* force execing Page getInitialProps functions | |||
*/ | |||
forceInitial?: boolean | |||
/** | |||
* remove window.g_initialProps in html | |||
*/ | |||
removeWindowInitialProps?: boolean | |||
/** | |||
* disable serve-side render in umi dev mode. | |||
*/ | |||
devServerRender?: boolean | |||
mode?: ("stream" | "string") | |||
/** | |||
* static markup in static site | |||
*/ | |||
staticMarkup?: boolean | |||
} | |||
singular?: boolean | |||
styleLoader?: { | |||
} | |||
targets?: { | |||
} | |||
terserOptions?: { | |||
} | |||
theme?: { | |||
} | |||
runtimeHistory?: { | |||
} | |||
webpack5?: { | |||
lazyCompilation?: { | |||
entries?: boolean | |||
imports?: boolean | |||
test?: any | |||
} | |||
} | |||
workerLoader?: { | |||
} | |||
favicon?: string | |||
headScripts?: any[] | |||
links?: any[] | |||
metas?: any[] | |||
scripts?: any[] | |||
styles?: any[] | |||
title?: string | |||
mock?: { | |||
exclude?: string[] | |||
} | |||
ProBlockOption?: string | |||
themeConfig?: { | |||
} | |||
logo?: (string | boolean) | |||
mode?: any | |||
description?: string | |||
locales?: string[][] | |||
resolve?: { | |||
} | |||
menus?: { | |||
} | |||
navs?: (any[] | { | |||
}) | |||
algolia?: { | |||
appId?: string | |||
apiKey?: string | |||
indexName?: string | |||
debug?: boolean | |||
} | |||
sitemap?: { | |||
hostname?: string | |||
excludes?: string[] | |||
} | |||
apiParser?: { | |||
} | |||
antd?: { | |||
dark?: boolean | |||
compact?: boolean | |||
mobile?: boolean | |||
disableBabelPluginImport?: boolean | |||
config?: { | |||
} | |||
} | |||
dva?: { | |||
disableModelsReExport?: boolean | |||
/** | |||
* lazy load dva model avoiding the import modules from umi undefined | |||
*/ | |||
lazyLoad?: boolean | |||
extraModels?: string[] | |||
hmr?: boolean | |||
immer?: (boolean | { | |||
}) | |||
skipModelValidate?: boolean | |||
} | |||
locale?: { | |||
default?: string | |||
useLocalStorage?: boolean | |||
baseNavigator?: boolean | |||
title?: boolean | |||
antd?: boolean | |||
baseSeparator?: string | |||
} | |||
layout?: { | |||
} | |||
request?: { | |||
dataField?: "" | |||
} | |||
block?: number | |||
esbuild?: { | |||
target?: (string | string[]) | |||
format?: ("iife" | "cjs" | "esm") | |||
} | |||
openAPI?: ({ | |||
requestLibPath?: string | |||
schemaPath?: string | |||
mock?: boolean | |||
projectName?: string | |||
apiPrefix?: (string | (() => any)) | |||
namespace?: string | |||
hook?: { | |||
customFunctionName?: (() => any) | |||
customClassName?: (() => any) | |||
} | |||
}[] | { | |||
requestLibPath?: string | |||
schemaPath?: string | |||
mock?: boolean | |||
projectName?: string | |||
apiPrefix?: (string | (() => any)) | |||
namespace?: string | |||
hook?: { | |||
customFunctionName?: (() => any) | |||
customClassName?: (() => any) | |||
} | |||
}) | |||
[k: string]: any | |||
} |
@@ -0,0 +1,40 @@ | |||
// @ts-nocheck | |||
import { plugin } from './plugin'; | |||
import * as Plugin_0 from '../../app.jsx'; | |||
import * as Plugin_1 from '@@/plugin-antd-icon-config/app.ts'; | |||
import * as Plugin_2 from 'D:/Work/BPA.SAAS.Web/src/.umi-production/plugin-access/rootContainer.ts'; | |||
import * as Plugin_3 from '../plugin-initial-state/runtime'; | |||
import * as Plugin_4 from 'D:/Work/BPA.SAAS.Web/src/.umi-production/plugin-locale/runtime.tsx'; | |||
import * as Plugin_5 from '@@/plugin-layout/runtime.tsx'; | |||
import * as Plugin_6 from '../plugin-model/runtime'; | |||
plugin.register({ | |||
apply: Plugin_0, | |||
path: '../../app.jsx', | |||
}); | |||
plugin.register({ | |||
apply: Plugin_1, | |||
path: '@@/plugin-antd-icon-config/app.ts', | |||
}); | |||
plugin.register({ | |||
apply: Plugin_2, | |||
path: 'D:/Work/BPA.SAAS.Web/src/.umi-production/plugin-access/rootContainer.ts', | |||
}); | |||
plugin.register({ | |||
apply: Plugin_3, | |||
path: '../plugin-initial-state/runtime', | |||
}); | |||
plugin.register({ | |||
apply: Plugin_4, | |||
path: 'D:/Work/BPA.SAAS.Web/src/.umi-production/plugin-locale/runtime.tsx', | |||
}); | |||
plugin.register({ | |||
apply: Plugin_5, | |||
path: '@@/plugin-layout/runtime.tsx', | |||
}); | |||
plugin.register({ | |||
apply: Plugin_6, | |||
path: '../plugin-model/runtime', | |||
}); | |||
export const __mfsu = 1; |
@@ -0,0 +1,4 @@ | |||
// @ts-nocheck | |||
import 'core-js'; | |||
import 'regenerator-runtime/runtime'; | |||
export {}; |
@@ -0,0 +1,395 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import { ApplyPluginsType, dynamic } from 'D:/Work/BPA.SAAS.Web/node_modules/umi/node_modules/@umijs/runtime'; | |||
import * as umiExports from './umiExports'; | |||
import { plugin } from './plugin'; | |||
import LoadingComponent from '@ant-design/pro-layout/es/PageLoading'; | |||
export function getRoutes() { | |||
const routes = [ | |||
{ | |||
"path": "/", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 't__plugin-layout__Layout' */'D:/Work/BPA.SAAS.Web/src/.umi-production/plugin-layout/Layout.tsx'), loading: LoadingComponent}), | |||
"routes": [ | |||
{ | |||
"path": "/user", | |||
"layout": false, | |||
"routes": [ | |||
{ | |||
"name": "系统登录", | |||
"path": "/user/login", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__user__login' */'D:/Work/BPA.SAAS.Web/src/pages/user/login'), loading: LoadingComponent}), | |||
"access": "k1", | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "系统管理", | |||
"icon": "SettingOutlined", | |||
"path": "/sys", | |||
"routes": [ | |||
{ | |||
"name": "系统菜单", | |||
"icon": "smile", | |||
"path": "/sys/menus", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__sys__menus' */'D:/Work/BPA.SAAS.Web/src/pages/sys/menus'), loading: LoadingComponent}), | |||
"access": "k6", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "字典类型", | |||
"icon": "smile", | |||
"path": "/sys/dictionary/dicttype", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__sys__dictionary__dicttype' */'D:/Work/BPA.SAAS.Web/src/pages/sys/dictionary/dicttype'), loading: LoadingComponent}), | |||
"access": "k6", | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "加盟商管理", | |||
"icon": "SettingOutlined", | |||
"path": "/company", | |||
"routes": [ | |||
{ | |||
"name": "账号管理", | |||
"icon": "smile", | |||
"path": "/company/account", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__company__account' */'D:/Work/BPA.SAAS.Web/src/pages/company/account'), loading: LoadingComponent}), | |||
"access": "k2", | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "组织管理", | |||
"icon": "SettingOutlined", | |||
"path": "/org", | |||
"routes": [ | |||
{ | |||
"name": "机构管理", | |||
"icon": "smile", | |||
"path": "/org/orgamange", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__org__orgamange' */'D:/Work/BPA.SAAS.Web/src/pages/org/orgamange'), loading: LoadingComponent}), | |||
"access": "k2", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "角色管理", | |||
"icon": "smile", | |||
"path": "/org/roles", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__org__roles' */'D:/Work/BPA.SAAS.Web/src/pages/org/roles'), loading: LoadingComponent}), | |||
"access": "k5", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "用户账号管理", | |||
"icon": "smile", | |||
"path": "/org/users", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__org__users' */'D:/Work/BPA.SAAS.Web/src/pages/org/users'), loading: LoadingComponent}), | |||
"access": "k5", | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "元数据管理", | |||
"icon": "DropboxSquareFilled", | |||
"path": "/database", | |||
"routes": [ | |||
{ | |||
"name": "物料管理", | |||
"icon": "smile", | |||
"path": "/database/basic/batching", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__database__basic__batching' */'D:/Work/BPA.SAAS.Web/src/pages/database/basic/batching'), loading: LoadingComponent}), | |||
"access": "k7", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "商品管理", | |||
"icon": "smile", | |||
"path": "/database", | |||
"routes": [ | |||
{ | |||
"name": "商品类型", | |||
"icon": "smile", | |||
"path": "/database/goods/goodstypemanage", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__database__goods__goodstypemanage' */'D:/Work/BPA.SAAS.Web/src/pages/database/goods/goodstypemanage'), loading: LoadingComponent}), | |||
"access": "k7", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "商品多属性", | |||
"icon": "smile", | |||
"path": "/database/goods/goodsattribute", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__database__goods__goodsattribute' */'D:/Work/BPA.SAAS.Web/src/pages/database/goods/goodsattribute'), loading: LoadingComponent}), | |||
"access": "k7", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "商品基础信息", | |||
"icon": "smile", | |||
"path": "/database/goods/newgoods", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__database__goods__newgoods' */'D:/Work/BPA.SAAS.Web/src/pages/database/goods/newgoods'), loading: LoadingComponent}), | |||
"access": "k7", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "添加商品基础信息", | |||
"icon": "smile", | |||
"path": "/database/goods/goodsInfo", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__database__goods__goodsInfo' */'D:/Work/BPA.SAAS.Web/src/pages/database/goods/goodsInfo'), loading: LoadingComponent}), | |||
"access": "k7", | |||
"exact": true | |||
} | |||
] | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "设备管理", | |||
"icon": "BankFilled", | |||
"path": "/device", | |||
"routes": [ | |||
{ | |||
"name": "产品管理", | |||
"icon": "smile", | |||
"path": "/device/product", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__device__product' */'D:/Work/BPA.SAAS.Web/src/pages/device/product'), loading: LoadingComponent}), | |||
"access": "k12", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "设备信息", | |||
"icon": "smile", | |||
"path": "/device/deviceInfo", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__device__deviceInfo' */'D:/Work/BPA.SAAS.Web/src/pages/device/deviceInfo'), loading: LoadingComponent}), | |||
"access": "k14", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "版本管理", | |||
"icon": "smile", | |||
"path": "/device/deviceVesion", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__device__deviceVesion' */'D:/Work/BPA.SAAS.Web/src/pages/device/deviceVesion'), loading: LoadingComponent}), | |||
"access": "k14", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "产品功能", | |||
"icon": "smile", | |||
"path": "/device/productmanage", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__device__productmanage' */'D:/Work/BPA.SAAS.Web/src/pages/device/productmanage'), loading: LoadingComponent}), | |||
"access": "k14", | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "设备数据下发", | |||
"icon": "BankFilled", | |||
"path": "/push", | |||
"routes": [ | |||
{ | |||
"name": "商品数据下发", | |||
"icon": "smile", | |||
"path": "/push/goodspush", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__push__goodspush' */'D:/Work/BPA.SAAS.Web/src/pages/push/goodspush'), loading: LoadingComponent}), | |||
"access": "k12", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "工艺数据下发", | |||
"icon": "smile", | |||
"path": "/push/technologypush", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__push__technologypush' */'D:/Work/BPA.SAAS.Web/src/pages/push/technologypush'), loading: LoadingComponent}), | |||
"access": "k14", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "物料数据下发", | |||
"icon": "smile", | |||
"path": "/push/batchingpush", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__push__batchingpush' */'D:/Work/BPA.SAAS.Web/src/pages/push/batchingpush'), loading: LoadingComponent}), | |||
"access": "k14", | |||
"exact": true | |||
}, | |||
{ | |||
"name": "配方数据下发", | |||
"icon": "smile", | |||
"path": "/push/bompush", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__push__bompush' */'D:/Work/BPA.SAAS.Web/src/pages/push/bompush'), loading: LoadingComponent}), | |||
"access": "k14", | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"path": "/shop", | |||
"icon": "SettingOutlined", | |||
"name": "门店管理", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shop' */'D:/Work/BPA.SAAS.Web/src/pages/shop'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "小程序管理", | |||
"icon": "BankFilled", | |||
"path": "/applet", | |||
"routes": [ | |||
{ | |||
"name": "页面管理", | |||
"icon": "smile", | |||
"path": "/applet/paytemplate", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__applet__paytemplate' */'D:/Work/BPA.SAAS.Web/src/pages/applet/paytemplate'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "素材管理", | |||
"icon": "smile", | |||
"path": "/applet/basicconfiguration", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__applet__basicconfiguration' */'D:/Work/BPA.SAAS.Web/src/pages/applet/basicconfiguration'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "小程序基础配置", | |||
"icon": "smile", | |||
"path": "/applet/appidmanager", | |||
"routes": [ | |||
{ | |||
"name": "支付配置", | |||
"icon": "smile", | |||
"path": "/applet/appidmanager/payment", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__applet__appidmanager__payment' */'D:/Work/BPA.SAAS.Web/src/pages/applet/appidmanager/payment'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "APPID", | |||
"icon": "smile", | |||
"path": "/applet/appidmanager/appid", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__applet__appidmanager__appid' */'D:/Work/BPA.SAAS.Web/src/pages/applet/appidmanager/appid'), loading: LoadingComponent}), | |||
"exact": true | |||
} | |||
] | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "门店管理", | |||
"icon": "BankFilled", | |||
"path": "/shopmanage", | |||
"routes": [ | |||
{ | |||
"name": "员工管理", | |||
"icon": "smile", | |||
"path": "/shopmanage/storeStaff", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__storeStaff' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/storeStaff'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "打印机模板", | |||
"icon": "smile", | |||
"path": "/shopmanage/printerTemplate", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__printerTemplate' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/printerTemplate'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "店铺小票打印机", | |||
"icon": "smile", | |||
"path": "/shopmanage/Printer", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__Printer' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/Printer'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "店铺桌面码", | |||
"icon": "smile", | |||
"path": "/shopmanage/storeDesktopNumber", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__storeDesktopNumber' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/storeDesktopNumber'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "店铺广告", | |||
"icon": "smile", | |||
"path": "/shopmanage/storeAdvertisement", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__storeAdvertisement' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/storeAdvertisement'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "店铺菜谱", | |||
"icon": "smile", | |||
"path": "/shopmanage/storeGoodsInfo", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__storeGoodsInfo' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/storeGoodsInfo'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "加购商品", | |||
"icon": "smile", | |||
"hideInMenu": true, | |||
"path": "/shopmanage/storeAddGoodsInfo", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__shopmanage__storeAddGoodsInfo' */'D:/Work/BPA.SAAS.Web/src/pages/shopmanage/storeAddGoodsInfo'), loading: LoadingComponent}), | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"name": "订单管理", | |||
"icon": "BankFilled", | |||
"path": "/order", | |||
"routes": [ | |||
{ | |||
"name": "订单流水", | |||
"icon": "smile", | |||
"path": "/order/orderflow", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__order__orderflow' */'D:/Work/BPA.SAAS.Web/src/pages/order/orderflow'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "第三方订单", | |||
"icon": "smile", | |||
"path": "/order/thirdOrder", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__order__thirdOrder' */'D:/Work/BPA.SAAS.Web/src/pages/order/thirdOrder'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"name": "授权管理", | |||
"icon": "smile", | |||
"path": "/order/thirdAuthorize", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__order__thirdAuthorize' */'D:/Work/BPA.SAAS.Web/src/pages/order/thirdAuthorize'), loading: LoadingComponent}), | |||
"exact": true | |||
} | |||
] | |||
}, | |||
{ | |||
"path": "/index.html", | |||
"redirect": "/welcome", | |||
"exact": true | |||
}, | |||
{ | |||
"path": "/", | |||
"redirect": "/welcome", | |||
"exact": true | |||
}, | |||
{ | |||
"path": "/welcome", | |||
"name": "welcome", | |||
"icon": "smile", | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__Welcome' */'D:/Work/BPA.SAAS.Web/src/pages/Welcome'), loading: LoadingComponent}), | |||
"exact": true | |||
}, | |||
{ | |||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__404' */'D:/Work/BPA.SAAS.Web/src/pages/404'), loading: LoadingComponent}), | |||
"exact": true | |||
} | |||
] | |||
} | |||
]; | |||
// allow user to extend routes | |||
plugin.applyPlugins({ | |||
key: 'patchRoutes', | |||
type: ApplyPluginsType.event, | |||
args: { routes }, | |||
}); | |||
return routes; | |||
} |
@@ -0,0 +1,12 @@ | |||
// @ts-nocheck | |||
export { history } from './history'; | |||
export { plugin } from './plugin'; | |||
export * from '../plugin-access/access'; | |||
export * from '../plugin-initial-state/exports'; | |||
export * from '../plugin-locale/localeExports'; | |||
export * from '../plugin-locale/SelectLang'; | |||
export * from '../plugin-layout/layoutExports'; | |||
export * from '../plugin-model/useModel'; | |||
export * from '../plugin-request/request'; | |||
export * from '../plugin-helmet/exports'; | |||
export * from '../preset-ui/UmiUIFlag'; |
@@ -0,0 +1,40 @@ | |||
// @ts-nocheck | |||
import React, { useMemo } from 'react'; | |||
import { IRoute } from 'umi'; | |||
import { useModel } from '../core/umiExports'; | |||
import accessFactory from '../../access'; | |||
import AccessContext, { AccessInstance } from './context'; | |||
import { traverseModifyRoutes } from './runtimeUtil'; | |||
type Routes = IRoute[]; | |||
interface Props { | |||
routes: Routes; | |||
children: React.ReactNode; | |||
} | |||
const AccessProvider: React.FC<Props> = props => { | |||
if (typeof useModel !== 'function') { | |||
throw new Error('[plugin-access]: useModel is not a function, @umijs/plugin-initial-state is needed.') | |||
} | |||
const { children } = props; | |||
const { initialState } = useModel('@@initialState'); | |||
const access: AccessInstance = useMemo(() => accessFactory(initialState as any), [initialState]); | |||
if (process.env.NODE_ENV === 'development' && (access === undefined || access === null)) { | |||
console.warn('[plugin-access]: the access instance created by access.ts(js) is nullish, maybe you need check it.'); | |||
} | |||
return React.createElement( | |||
AccessContext.Provider, | |||
{ value: access }, | |||
React.cloneElement(children, { | |||
...children.props, | |||
routes:traverseModifyRoutes(props.routes, access) | |||
}), | |||
); | |||
}; | |||
export default AccessProvider; |
@@ -0,0 +1,33 @@ | |||
// @ts-nocheck | |||
import React, { useContext } from 'react'; | |||
import AccessContext, { AccessInstance as AccessInstanceType } from './context'; | |||
import { traverseModifyRoutes } from './runtimeUtil'; | |||
export { traverseModifyRoutes }; | |||
export type AccessInstance = AccessInstanceType; | |||
export const useAccess = () => { | |||
const access = useContext(AccessContext); | |||
return access; | |||
}; | |||
export interface AccessProps { | |||
accessible: boolean; | |||
fallback?: React.ReactNode; | |||
} | |||
export const Access: React.FC<AccessProps> = props => { | |||
const { accessible, fallback, children } = props; | |||
if (process.env.NODE_ENV === 'development' && typeof accessible === 'function') { | |||
console.warn( | |||
'[plugin-access]: provided "accessible" prop is a function named "' + | |||
(accessible as Function).name + | |||
'" instead of a boolean, maybe you need check it.', | |||
); | |||
} | |||
return <>{accessible ? children : fallback}</>; | |||
}; |
@@ -0,0 +1,9 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import accessFactory from '@/access'; | |||
export type AccessInstance = ReturnType<typeof accessFactory>; | |||
const AccessContext = React.createContext<AccessInstance>(null!); | |||
export default AccessContext; |
@@ -0,0 +1,7 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import AccessProvider from './AccessProvider'; | |||
export function rootContainer(container: React.ReactNode, { routes }) { | |||
return React.createElement(AccessProvider, { routes }, container); | |||
} |
@@ -0,0 +1,80 @@ | |||
// @ts-nocheck | |||
import { IRoute } from 'umi'; | |||
type ChildrenList = IRoute[]; | |||
const oldChildrenPropsName = 'routes'; | |||
export function traverseModifyRoutes(childrenList: ChildrenList, access: any): ChildrenList { | |||
const resultChildrenList: ChildrenList = [] | |||
.concat(childrenList as any) | |||
.map((resultRoute: IRoute) => { | |||
const childList = resultRoute.children || resultRoute[oldChildrenPropsName]; | |||
if (childList && childList?.length) { | |||
return { | |||
...resultRoute, | |||
children: childList?.map((route: any) => ({ ...route })), | |||
// return new route to routes. | |||
[oldChildrenPropsName]: childList?.map((route: any) => ({ ...route })), | |||
}; | |||
} | |||
return resultRoute; | |||
}); | |||
return resultChildrenList.map((currentRoute) => { | |||
let currentRouteAccessible = | |||
typeof currentRoute.unaccessible === 'boolean' ? !currentRoute.unaccessible : true; | |||
// 判断路由是否有权限的具体代码 | |||
if (currentRoute && currentRoute.access) { | |||
if (typeof currentRoute.access !== 'string') { | |||
throw new Error( | |||
'[plugin-access]: "access" field set in "' + | |||
currentRoute.path + | |||
'" route should be a string.', | |||
); | |||
} | |||
const accessProp = access[currentRoute.access]; | |||
// 如果是方法需要执行以下 | |||
if (typeof accessProp === 'function') { | |||
currentRouteAccessible = accessProp(currentRoute); | |||
} else if (typeof accessProp === 'boolean') { | |||
// 不是方法就直接 copy | |||
currentRouteAccessible = accessProp; | |||
} | |||
currentRoute.unaccessible = !currentRouteAccessible; | |||
} | |||
const childList = currentRoute.children || currentRoute[oldChildrenPropsName]; | |||
// 筛选子路由 | |||
if (childList && Array.isArray(childList) && childList.length) { | |||
if (!Array.isArray(childList)) { | |||
return currentRoute; | |||
} | |||
// 父亲没权限,理论上每个孩子都没权限 | |||
// 可能有打平 的事情发生,所以都执行一下 | |||
childList.forEach((childRoute) => { | |||
childRoute.unaccessible = !currentRouteAccessible; | |||
}); | |||
const finallyChildList = traverseModifyRoutes(childList, access); | |||
// 如果每个子节点都没有权限,那么自己也属于没有权限 | |||
const isAllChildChildrenUnaccessible = | |||
Array.isArray(finallyChildList) && finallyChildList.every((route) => route.unaccessible); | |||
if (!currentRoute.unaccessible && isAllChildChildrenUnaccessible) { | |||
currentRoute.unaccessible = true; | |||
} | |||
if (finallyChildList && finallyChildList?.length > 0) { | |||
return { | |||
...currentRoute, | |||
children: finallyChildList, | |||
[oldChildrenPropsName]: finallyChildList, | |||
}; | |||
} | |||
delete currentRoute.routes; | |||
delete currentRoute.children; | |||
} | |||
return currentRoute; | |||
}); | |||
} |
@@ -0,0 +1,51 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import allIcons from '@@/plugin-antd-icon/icons'; | |||
export interface MenuDataItem { | |||
children?: MenuDataItem[]; | |||
routes?: MenuDataItem[]; | |||
hideChildrenInMenu?: boolean; | |||
hideInMenu?: boolean; | |||
icon?: string; | |||
locale?: string; | |||
name?: string; | |||
key?: string; | |||
path?: string; | |||
[key: string]: any; | |||
} | |||
function toHump(name: string) { | |||
return name.replace(/\-(\w)/g, function (all, letter) { | |||
return letter.toUpperCase(); | |||
}); | |||
} | |||
function formatter(data: MenuDataItem[]): MenuDataItem[] { | |||
if (!Array.isArray(data)) { | |||
return data; | |||
} | |||
(data || []).forEach((item = { path: '/' }) => { | |||
if (item.icon) { | |||
const { icon } = item; | |||
const v4IconName = toHump(icon.replace(icon[0], icon[0].toUpperCase())); | |||
const NewIcon = allIcons[icon] || allIcons[`${v4IconName}Outlined`]; | |||
if (NewIcon) { | |||
try { | |||
item.icon = React.createElement(NewIcon); | |||
} catch (error) { | |||
console.log(error); | |||
} | |||
} | |||
} | |||
if (item.routes || item.children) { | |||
const children = formatter(item.routes || item.children); | |||
// Reduce memory usage | |||
item.children = children; | |||
} | |||
}); | |||
return data; | |||
} | |||
export function patchRoutes({ routes }) { | |||
formatter(routes); | |||
} |
@@ -0,0 +1,14 @@ | |||
// @ts-nocheck | |||
import SettingOutlined from '@ant-design/icons/SettingOutlined'; | |||
import SmileOutlined from '@ant-design/icons/SmileOutlined'; | |||
import DropboxSquareFilled from '@ant-design/icons/DropboxSquareFilled'; | |||
import BankFilled from '@ant-design/icons/BankFilled' | |||
export default { | |||
SettingOutlined, | |||
SmileOutlined, | |||
DropboxSquareFilled, | |||
BankFilled | |||
} | |||
@@ -0,0 +1,3 @@ | |||
// @ts-nocheck | |||
// @ts-ignore | |||
export { Helmet } from 'D:/Work/BPA.SAAS.Web/node_modules/react-helmet'; |
@@ -0,0 +1,37 @@ | |||
// @ts-nocheck | |||
import React, { useRef, useEffect } from "react"; | |||
import { plugin } from "../core/umiExports"; | |||
import { ApplyPluginsType } from 'umi'; | |||
import { useModel } from "../plugin-model/useModel"; | |||
if (typeof useModel !== "function") { | |||
throw new Error( | |||
"[plugin-initial-state]: useModel is not a function, @umijs/plugin-model is required." | |||
); | |||
} | |||
interface Props { | |||
children: React.ReactNode; | |||
} | |||
export default (props: Props) => { | |||
const { children } = props; | |||
const appLoaded = useRef(false); | |||
// 获取用户的配置,暂时只支持 loading | |||
const useRuntimeConfig = | |||
plugin.applyPlugins({ | |||
key: "initialStateConfig", | |||
type: ApplyPluginsType.modify, | |||
initialValue: {}, | |||
}) || {}; | |||
const { loading = false } = useModel("@@initialState") || {}; | |||
useEffect(() => { | |||
if (!loading) { | |||
appLoaded.current = true; | |||
} | |||
}, [loading]); | |||
// initial state loading 时,阻塞渲染 | |||
if (loading && !appLoaded.current) { | |||
return useRuntimeConfig.loading || null; | |||
} | |||
return children; | |||
}; |
@@ -0,0 +1,7 @@ | |||
// @ts-nocheck | |||
// @ts-ignore | |||
import { InitialState as InitialStateType } from '../plugin-initial-state\models\initialState'; | |||
export type InitialState = InitialStateType; | |||
export const __PLUGIN_INITIAL_STATE = 1; |
@@ -0,0 +1,63 @@ | |||
// @ts-nocheck | |||
// @ts-nocheck | |||
import { useState, useEffect, useCallback } from 'react'; | |||
import { Models } from '../../plugin-model/useModel'; | |||
import * as app from '../../../app'; | |||
const sleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay||0)); | |||
export type InitialState = Models<'@@initialState'>; | |||
async function getInitialState() { | |||
return await app.getInitialState(); | |||
} | |||
type ThenArg<T> = T extends Promise<infer U> ? U : T; | |||
const initState = { | |||
initialState: undefined as ThenArg<ReturnType<typeof getInitialState>> | undefined, | |||
loading: true, | |||
error: undefined as Error | undefined, | |||
}; | |||
type InitialStateType = ThenArg<ReturnType<typeof getInitialState>> | undefined; | |||
type InitialStateTypeFn = ( | |||
initialState: InitialStateType, | |||
) => ThenArg<ReturnType<typeof getInitialState>> | undefined; | |||
export default () => { | |||
const [state, setState] = useState(initState); | |||
const refresh = useCallback(async () => { | |||
setState((s) => ({ ...s, loading: true, error: undefined })); | |||
try { | |||
const asyncFunc = () => new Promise<InitialStateType>((res) => res(getInitialState())); | |||
const ret = await asyncFunc(); | |||
setState((s) => ({ ...s, initialState: ret, loading: false })); | |||
} catch (e) { | |||
setState((s) => ({ ...s, error: e, loading: false })); | |||
} | |||
await sleep(10) | |||
}, []); | |||
const setInitialState = useCallback(async (initialState: InitialStateType | InitialStateTypeFn) => { | |||
setState((s) => { | |||
if (typeof initialState === 'function') { | |||
return { ...s, initialState: initialState(s.initialState), loading: false }; | |||
} | |||
return { ...s, initialState, loading: false }; | |||
}); | |||
await sleep(10) | |||
}, []); | |||
useEffect(() => { | |||
refresh(); | |||
}, []); | |||
return { | |||
...state, | |||
refresh, | |||
setInitialState, | |||
}; | |||
}; |
@@ -0,0 +1,13 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import Provider from './Provider'; | |||
export function rootContainer(container: React.ReactNode) { | |||
return React.createElement( | |||
// 这里的 plugin-initial-state 不能从 constant 里取,里面有 path 依赖 | |||
// 但 webpack-5 没有 node 补丁(包括 path) | |||
Provider, | |||
null, | |||
container, | |||
); | |||
} |
@@ -0,0 +1,53 @@ | |||
// @ts-nocheck | |||
import React, { useState, useEffect } from "react"; | |||
import { ApplyPluginsType, useModel , useIntl, traverseModifyRoutes, useAccess } from "umi"; | |||
import { plugin } from "../core/umiExports"; | |||
import LayoutComponent from './layout/layout/index.tsx'; | |||
export default props => { | |||
const [runtimeConfig, setRuntimeConfig] = useState(null); | |||
const initialInfo = (useModel && useModel("@@initialState")) || { | |||
initialState: undefined, | |||
loading: false, | |||
setInitialState: null | |||
}; // plugin-initial-state 未开启 | |||
const access = useAccess?.(); | |||
useEffect(() => { | |||
const useRuntimeConfig = | |||
plugin.applyPlugins({ | |||
key: "layout", | |||
type: ApplyPluginsType.modify, | |||
initialValue: { | |||
...initialInfo, | |||
traverseModifyRoutes: (menuData) => {return traverseModifyRoutes?.(menuData, access);}, | |||
}, | |||
}) || {}; | |||
if (useRuntimeConfig instanceof Promise) { | |||
useRuntimeConfig.then((config) => { | |||
setRuntimeConfig(config); | |||
}); | |||
return; | |||
} | |||
setRuntimeConfig(useRuntimeConfig); | |||
}, [initialInfo?.initialState, access]); | |||
const userConfig = { | |||
...{'name':'ant-design-pro','theme':'PRO','locale':false,'showBreadcrumb':true,'siderWidth':208,'navTheme':'dark','primaryColor':'#FA541C','layout':'side','contentWidth':'Fluid','fixedHeader':false,'fixSiderbar':true,'colorWeak':false,'title':'黑菠萝智慧门店','pwa':false,'logo':'/logo.svg','iconfontUrl':''}, | |||
...runtimeConfig || {} | |||
}; | |||
const { formatMessage } = useIntl(); | |||
if(!runtimeConfig){ | |||
return null | |||
} | |||
return React.createElement(LayoutComponent, { | |||
userConfig, | |||
formatMessage, | |||
...props | |||
}); | |||
}; |
@@ -0,0 +1,12 @@ | |||
// @ts-nocheck | |||
import SettingOutlined from '@ant-design/icons/es/icons/SettingOutlined'; | |||
import SmileOutlined from '@ant-design/icons/es/icons/SmileOutlined'; | |||
import DropboxSquareFilled from '@ant-design/icons/es/icons/DropboxSquareFilled'; | |||
import BankFilled from '@ant-design/icons/es/icons/BankFilled' | |||
export default { | |||
SettingOutlined, | |||
SmileOutlined, | |||
DropboxSquareFilled, | |||
BankFilled | |||
} |
@@ -0,0 +1,56 @@ | |||
import React from 'react'; | |||
import { Result, Button } from 'antd'; | |||
import { history } from 'umi'; | |||
import { IRouteLayoutConfig } from '../../types/interface.d'; | |||
function backToHome() { | |||
history.push('/'); | |||
} | |||
const Exception404 = () => ( | |||
<Result | |||
status="404" | |||
title="404" | |||
subTitle="抱歉,你访问的页面不存在" | |||
extra={ | |||
<Button type="primary" onClick={backToHome}> | |||
返回首页 | |||
</Button> | |||
} | |||
/> | |||
); | |||
const Exception403 = () => ( | |||
<Result | |||
status="403" | |||
title="403" | |||
subTitle="抱歉,你无权访问该页面" | |||
extra={ | |||
<Button type="primary" onClick={backToHome}> | |||
返回首页 | |||
</Button> | |||
} | |||
/> | |||
); | |||
const WithExceptionOpChildren: React.FC<{ | |||
currentPathConfig?: IRouteLayoutConfig; | |||
children: any; | |||
noFound: React.ReactNode; | |||
unAccessible: React.ReactNode; | |||
}> = (props) => { | |||
const { children, currentPathConfig } = props; | |||
// 404 现在应该很少会发生 | |||
if (!currentPathConfig) { | |||
return props.noFound || <Exception404 />; | |||
} | |||
/** | |||
* 这里是没有权限的意思 | |||
*/ | |||
if (currentPathConfig.unAccessible || currentPathConfig.unaccessible) { | |||
return props.unAccessible || <Exception403 />; | |||
} | |||
return children; | |||
}; | |||
export { Exception404, Exception403, WithExceptionOpChildren }; |
@@ -0,0 +1,91 @@ | |||
import React from 'react'; | |||
const LogoIcon: React.FC = () => { | |||
return ( | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
width="32" | |||
height="32" | |||
viewBox="0 0 200 200" | |||
> | |||
<defs> | |||
<linearGradient | |||
id="linearGradient-1" | |||
x1="62.102%" | |||
x2="108.197%" | |||
y1="0%" | |||
y2="37.864%" | |||
> | |||
<stop offset="0%" stopColor="#4285EB"></stop> | |||
<stop offset="100%" stopColor="#2EC7FF"></stop> | |||
</linearGradient> | |||
<linearGradient | |||
id="linearGradient-2" | |||
x1="69.644%" | |||
x2="54.043%" | |||
y1="0%" | |||
y2="108.457%" | |||
> | |||
<stop offset="0%" stopColor="#29CDFF"></stop> | |||
<stop offset="37.86%" stopColor="#148EFF"></stop> | |||
<stop offset="100%" stopColor="#0A60FF"></stop> | |||
</linearGradient> | |||
<linearGradient | |||
id="linearGradient-3" | |||
x1="69.691%" | |||
x2="16.723%" | |||
y1="-12.974%" | |||
y2="117.391%" | |||
> | |||
<stop offset="0%" stopColor="#FA816E"></stop> | |||
<stop offset="41.473%" stopColor="#F74A5C"></stop> | |||
<stop offset="100%" stopColor="#F51D2C"></stop> | |||
</linearGradient> | |||
<linearGradient | |||
id="linearGradient-4" | |||
x1="68.128%" | |||
x2="30.44%" | |||
y1="-35.691%" | |||
y2="114.943%" | |||
> | |||
<stop offset="0%" stopColor="#FA8E7D"></stop> | |||
<stop offset="51.264%" stopColor="#F74A5C"></stop> | |||
<stop offset="100%" stopColor="#F51D2C"></stop> | |||
</linearGradient> | |||
</defs> | |||
<g fill="none" fillRule="evenodd" stroke="none" strokeWidth="1"> | |||
<g transform="translate(-20 -20)"> | |||
<g transform="translate(20 20)"> | |||
<g> | |||
<g fillRule="nonzero"> | |||
<g> | |||
<path | |||
fill="url(#linearGradient-1)" | |||
d="M91.588 4.177L4.18 91.513a11.981 11.981 0 000 16.974l87.408 87.336a12.005 12.005 0 0016.989 0l36.648-36.618c4.209-4.205 4.209-11.023 0-15.228-4.208-4.205-11.031-4.205-15.24 0l-27.783 27.76c-1.17 1.169-2.945 1.169-4.114 0l-69.802-69.744c-1.17-1.169-1.17-2.942 0-4.11l69.802-69.745c1.17-1.169 2.944-1.169 4.114 0l27.783 27.76c4.209 4.205 11.032 4.205 15.24 0 4.209-4.205 4.209-11.022 0-15.227L108.581 4.056c-4.719-4.594-12.312-4.557-16.993.12z" | |||
></path> | |||
<path | |||
fill="url(#linearGradient-2)" | |||
d="M91.588 4.177L4.18 91.513a11.981 11.981 0 000 16.974l87.408 87.336a12.005 12.005 0 0016.989 0l36.648-36.618c4.209-4.205 4.209-11.023 0-15.228-4.208-4.205-11.031-4.205-15.24 0l-27.783 27.76c-1.17 1.169-2.945 1.169-4.114 0l-69.802-69.744c-1.17-1.169-1.17-2.942 0-4.11l69.802-69.745c2.912-2.51 7.664-7.596 14.642-8.786 5.186-.883 10.855 1.062 17.009 5.837L108.58 4.056c-4.719-4.594-12.312-4.557-16.993.12z" | |||
></path> | |||
</g> | |||
<path | |||
fill="url(#linearGradient-3)" | |||
d="M153.686 135.855c4.208 4.205 11.031 4.205 15.24 0l27.034-27.012c4.7-4.696 4.7-12.28 0-16.974l-27.27-27.15c-4.218-4.2-11.043-4.195-15.254.013-4.209 4.205-4.209 11.022 0 15.227l18.418 18.403c1.17 1.169 1.17 2.943 0 4.111l-18.168 18.154c-4.209 4.205-4.209 11.023 0 15.228z" | |||
></path> | |||
</g> | |||
<ellipse | |||
cx="100.519" | |||
cy="100.437" | |||
fill="url(#linearGradient-4)" | |||
rx="23.6" | |||
ry="23.581" | |||
></ellipse> | |||
</g> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
); | |||
}; | |||
export default LogoIcon; |
@@ -0,0 +1,7 @@ | |||
export default ({ children }: { children: any }) => { | |||
console.error( | |||
'@umijs/plugin-layout 需要安装 @ant-design/pro-layout 才可运行', | |||
); | |||
console.error('https://prolayout.ant.design/'); | |||
return children; | |||
}; |
@@ -0,0 +1,37 @@ | |||
const getLayoutRenderConfig = (currentPathConfig: { | |||
layout: | |||
| { | |||
hideMenu: boolean; | |||
hideNav: boolean; | |||
hideFooter: boolean; | |||
} | |||
| false; | |||
hideFooter: boolean; | |||
}) => { | |||
const layoutRender: any = {}; | |||
if (currentPathConfig?.hideFooter) { | |||
layoutRender.footerRender = false; | |||
} | |||
if (currentPathConfig?.layout == false) { | |||
layoutRender.pure = true; | |||
return layoutRender; | |||
} | |||
if (currentPathConfig?.layout?.hideMenu) { | |||
layoutRender.menuRender = false; | |||
} | |||
if (currentPathConfig?.layout?.hideFooter) { | |||
layoutRender.footerRender = false; | |||
} | |||
if (currentPathConfig?.layout?.hideNav) { | |||
layoutRender.headerRender = false; | |||
} | |||
return layoutRender; | |||
}; | |||
export default getLayoutRenderConfig; |
@@ -0,0 +1,135 @@ | |||
import React, { useState,useMemo } from 'react'; | |||
// @ts-ignore | |||
import { Link, useModel, history, traverseModifyRoutes, useAccess } from 'umi'; | |||
import ProLayout, { | |||
BasicLayoutProps, | |||
} from "@ant-design/pro-layout"; | |||
import './style.less'; | |||
// @ts-ignore | |||
import renderRightContent from '@@/plugin-layout/renderRightContent'; | |||
import { WithExceptionOpChildren } from '../component/Exception'; | |||
import { getMatchMenu, MenuDataItem, transformRoute } from '@umijs/route-utils'; | |||
// @ts-ignore | |||
import logo from '../component/logo'; | |||
import getLayoutRenderConfig from './getLayoutRenderConfig'; | |||
const BasicLayout = (props: any) => { | |||
const { children, userConfig = {}, location, route, ...restProps } = props; | |||
const initialInfo = (useModel && useModel('@@initialState')) || { | |||
initialState: undefined, | |||
loading: false, | |||
setInitialState: null, | |||
}; | |||
// plugin-initial-state 未开启 | |||
const { initialState, loading, setInitialState } = initialInfo; | |||
const currentPathConfig = useMemo(() => { | |||
const { menuData } = transformRoute( | |||
props?.route?.routes || [], | |||
undefined, | |||
undefined, | |||
true, | |||
); | |||
// 动态路由匹配 | |||
const currentPathConfig = getMatchMenu(location.pathname, menuData).pop(); | |||
return currentPathConfig || {}; | |||
},[location?.pathname, props?.route?.routes]); | |||
// layout 是否渲染相关 | |||
const layoutRestProps: BasicLayoutProps & { | |||
rightContentRender?: | |||
| false | |||
| (( | |||
props: BasicLayoutProps, | |||
dom: React.ReactNode, | |||
config: any, | |||
) => React.ReactNode); | |||
} = { | |||
itemRender: (route) => <Link to={route.path}>{route.breadcrumbName}</Link>, | |||
...userConfig, | |||
...restProps, | |||
...getLayoutRenderConfig(currentPathConfig as any ||{}), | |||
}; | |||
const access = useAccess?.(); | |||
return ( | |||
<ProLayout | |||
route={route} | |||
location={location} | |||
title={userConfig?.name || userConfig?.title} | |||
navTheme="dark" | |||
siderWidth={256} | |||
onMenuHeaderClick={(e) => { | |||
e.stopPropagation(); | |||
e.preventDefault(); | |||
history.push('/'); | |||
}} | |||
menu={ { locale: userConfig.locale } } | |||
// 支持了一个 patchMenus,其实应该用 menuDataRender | |||
menuDataRender={ | |||
userConfig.patchMenus | |||
? (menuData) => userConfig?.patchMenus(menuData, initialInfo) | |||
: undefined | |||
} | |||
formatMessage={userConfig?.formatMessage} | |||
logo={logo} | |||
menuItemRender={(menuItemProps, defaultDom) => { | |||
if (menuItemProps.isUrl) { | |||
return defaultDom; | |||
} | |||
if (menuItemProps.path && location.pathname !== menuItemProps.path) { | |||
return ( | |||
<Link to={menuItemProps.path} target={menuItemProps.target}> | |||
{defaultDom} | |||
</Link> | |||
); | |||
} | |||
return defaultDom; | |||
}} | |||
disableContentMargin | |||
fixSiderbar | |||
fixedHeader | |||
postMenuData={ | |||
traverseModifyRoutes | |||
? (menuData) => traverseModifyRoutes?.(menuData, access) | |||
: undefined | |||
} | |||
{...layoutRestProps} | |||
rightContentRender={ | |||
// === false 应该关闭这个功能 | |||
layoutRestProps?.rightContentRender !== false && | |||
((layoutProps) => { | |||
const dom = renderRightContent?.( | |||
userConfig, | |||
loading, | |||
initialState, | |||
setInitialState, | |||
); | |||
if (layoutRestProps.rightContentRender) { | |||
return layoutRestProps.rightContentRender(layoutProps, dom, { | |||
userConfig, | |||
loading, | |||
initialState, | |||
setInitialState, | |||
}); | |||
} | |||
return dom; | |||
}) | |||
} | |||
> | |||
<WithExceptionOpChildren | |||
noFound={userConfig?.noFound} | |||
unAccessible={userConfig?.unAccessible} | |||
currentPathConfig={currentPathConfig} | |||
> | |||
{userConfig.childrenRender | |||
? userConfig.childrenRender(children, props) | |||
: children} | |||
</WithExceptionOpChildren> | |||
</ProLayout> | |||
); | |||
}; | |||
export default BasicLayout; |
@@ -0,0 +1,59 @@ | |||
@import '~antd/es/style/themes/default.less'; | |||
@pro-header-hover-bg: rgba(0, 0, 0, 0.025); | |||
@media screen and (max-width: @screen-xs) { | |||
// 在小屏幕的时候可以有更好的体验 | |||
.umi-plugin-layout-container { | |||
width: 100% !important; | |||
} | |||
.umi-plugin-layout-container > * { | |||
border-radius: 0 !important; | |||
} | |||
} | |||
.umi-plugin-layout-menu { | |||
:global(.anticon) { | |||
margin-right: 8px; | |||
} | |||
:global(.ant-dropdown-menu-item) { | |||
min-width: 160px; | |||
} | |||
} | |||
.umi-plugin-layout-right { | |||
display: flex; | |||
float: right; | |||
height: 100%; | |||
margin-left: auto; | |||
overflow: hidden; | |||
.umi-plugin-layout-action { | |||
display: flex; | |||
align-items: center; | |||
height: 100%; | |||
padding: 0 12px; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
> i { | |||
color: @text-color; | |||
vertical-align: middle; | |||
} | |||
&:hover { | |||
background: @pro-header-hover-bg; | |||
} | |||
&:global(.opened) { | |||
background: @pro-header-hover-bg; | |||
} | |||
} | |||
.umi-plugin-layout-search { | |||
padding: 0 12px; | |||
&:hover { | |||
background: transparent; | |||
} | |||
} | |||
} | |||
.umi-plugin-layout-name { | |||
margin-left: 8px; | |||
} |
@@ -0,0 +1,13 @@ | |||
import { BasicLayoutProps } from '@ant-design/pro-layout'; | |||
import { Models } from '@@/plugin-model/useModel'; | |||
export type RunTimeLayoutConfig = ( | |||
initData: Models<'@@initialState'>, | |||
) => BasicLayoutProps & { | |||
childrenRender?: (dom: JSX.Element, props: BasicLayoutProps) => React.ReactNode, | |||
unAccessible?: JSX.Element, | |||
noFound?: JSX.Element, | |||
}; | |||
// avoid `export *` error | |||
export default {}; |
@@ -0,0 +1,3 @@ | |||
declare module '*.less'; | |||
export * from './interface'; |
@@ -0,0 +1,89 @@ | |||
import { IRoute } from 'umi'; | |||
interface Error { | |||
componentStack?: string; | |||
error?: string; | |||
[key: string]: any; | |||
} | |||
/** | |||
* 插件编译时配置 | |||
*/ | |||
export interface LayoutConfig { | |||
name?: string; | |||
logo?: string; | |||
theme?: string; | |||
locale?: any; // same with locale plugin | |||
showBreadcrumb?: boolean; // TODO 面包屑功能暂不支持 | |||
layoutComponent?: Record<string, string>; // 自定义主题 | |||
} | |||
/** | |||
* 插件运行时配置 | |||
*/ | |||
export interface ILayoutRuntimeConfig { | |||
/** 导航用户退出的逻辑 默认不做处理 */ | |||
logout?: (initialState: any) => void; | |||
// TODO IMPORT initinfo type from init plugin | |||
/** 自定义导航头右上角 ,有默认 UI, 接受 initialState & 修改 initialState 的方法 */ | |||
rightRender?: ( | |||
initialState: any, | |||
setInitialState: any, | |||
runtimeLayout: ILayoutRuntimeConfig, | |||
) => React.ReactNode; | |||
errorBoundary?: { | |||
/** 发生错误后的回调(可做一些错误日志上报,打点等) */ | |||
onError?: (error: Error, info: any) => void; | |||
/** 发生错误后展示的组件,接受 error */ | |||
ErrorComponent?: (error: Error) => React.ReactElement<any>; | |||
}; | |||
} | |||
export interface IRouteMenuConfig { | |||
/** 当前菜单名 */ | |||
name: string; | |||
/** antd 的 icon name 和 url */ | |||
icon?: string; | |||
/** 在菜单中隐藏他的子项 */ | |||
hideChildren?: boolean; | |||
/** 默认为false 在菜单中只隐藏此项,子项往上提,仍旧展示 */ | |||
flatMenu?: boolean; | |||
[key: string]: any; | |||
} | |||
export interface IRouteLayoutConfig { | |||
/** 默认 false */ | |||
hideMenu?: boolean; | |||
/** 默认 false */ | |||
hideNav?: boolean; | |||
/** 默认 false */ | |||
hideFooter?: boolean; | |||
[key: string]: any; | |||
} | |||
/** | |||
* 路由配置 | |||
*/ | |||
export interface IBestAFSRoute extends IRoute { | |||
/** 权限:https://yuque.antfin-inc.com/bigfish/best_afs/nxuhgb */ | |||
access?: string; | |||
/** 当前页面的面包屑是否隐藏 */ | |||
showBreadcrumb?: boolean; | |||
/** 默认为 false,在菜单中隐藏此项包括子项 */ | |||
menu?: false | IRouteMenuConfig; | |||
/** 默认为 true ,是否显示 Layout */ | |||
layout?: boolean | IRouteLayoutConfig; | |||
} | |||
export interface TechMenuItem { | |||
title: string | React.ReactNode; | |||
icon: string; | |||
link: string | React.ReactNode; | |||
children?: TechMenuItem[]; | |||
externalLink: boolean; | |||
} |
@@ -0,0 +1,38 @@ | |||
import { utils } from 'umi'; | |||
import { join } from 'path'; | |||
import { readFileSync, copyFileSync, statSync, writeFileSync } from 'fs'; | |||
export default ({ | |||
cwd, | |||
absTmpPath, | |||
config, | |||
}: { | |||
cwd: string; | |||
absTmpPath: string; | |||
config: object; | |||
}) => { | |||
const files = utils.glob.sync('**/*', { | |||
cwd, | |||
}); | |||
const base = join(absTmpPath, 'plugin-layout', 'layout'); | |||
utils.mkdirp.sync(base); | |||
files.forEach(async (file) => { | |||
if (['index.ts', 'runtime.tsx.tpl'].includes(file)) return; | |||
const source = join(cwd, file); | |||
const target = join(base, file); | |||
if (statSync(source).isDirectory()) { | |||
utils.mkdirp.sync(target); | |||
} else { | |||
if (target.endsWith('.tpl')) { | |||
const sourceContent = readFileSync(source, 'utf-8'); | |||
await writeFileSync( | |||
target.replace(/\.tpl$/, ''), | |||
utils.Mustache.render(sourceContent, config), | |||
'utf-8', | |||
); | |||
} else { | |||
await copyFileSync(source, target); | |||
} | |||
} | |||
}); | |||
}; |
@@ -0,0 +1,152 @@ | |||
import { LayoutConfig } from '../types/interface.d'; | |||
export default ( | |||
userConfig: LayoutConfig, | |||
path: string, | |||
formatMessage: boolean, | |||
hasAccess: boolean, | |||
) => `import React, { useState, useEffect } from "react"; | |||
import { ApplyPluginsType, useModel ${ | |||
// 没有 formatMessage 就不打开国际化 | |||
formatMessage ? `, useIntl` : '' | |||
}${hasAccess ? ', traverseModifyRoutes, useAccess' : ''} } from "umi"; | |||
import { plugin } from "../core/umiExports"; | |||
import LayoutComponent from '${path}'; | |||
export default props => { | |||
const [runtimeConfig, setRuntimeConfig] = useState(null); | |||
const initialInfo = (useModel && useModel("@@initialState")) || { | |||
initialState: undefined, | |||
loading: false, | |||
setInitialState: null | |||
}; // plugin-initial-state 未开启 | |||
${hasAccess ? 'const access = useAccess?.();' : ''} | |||
useEffect(() => { | |||
const useRuntimeConfig = | |||
plugin.applyPlugins({ | |||
key: "layout", | |||
type: ApplyPluginsType.modify, | |||
initialValue: { | |||
...initialInfo, | |||
${ | |||
hasAccess | |||
? 'traverseModifyRoutes: (menuData) => {return traverseModifyRoutes?.(menuData, access);},' | |||
: '' | |||
} | |||
}, | |||
}) || {}; | |||
if (useRuntimeConfig instanceof Promise) { | |||
useRuntimeConfig.then((config) => { | |||
setRuntimeConfig(config); | |||
}); | |||
return; | |||
} | |||
setRuntimeConfig(useRuntimeConfig); | |||
}, [initialInfo?.initialState, ${hasAccess ? 'access' : ''}]); | |||
const userConfig = { | |||
...${JSON.stringify(userConfig).replace(/"/g, "'")}, | |||
...runtimeConfig || {} | |||
}; | |||
${formatMessage ? 'const { formatMessage } = useIntl();' : ''} | |||
if(!runtimeConfig){ | |||
return null | |||
} | |||
return React.createElement(LayoutComponent, { | |||
userConfig, | |||
${formatMessage ? 'formatMessage,' : ''} | |||
...props | |||
}); | |||
}; | |||
`; | |||
const genRenderRightContent = (props: { | |||
locale: boolean; | |||
initialState: boolean; | |||
}) => { | |||
if (!props.initialState) { | |||
return `export default function renderRightContent() { | |||
return null; | |||
} | |||
`; | |||
} | |||
return `import React from 'react'; | |||
import { Avatar, Dropdown, Menu, Spin } from 'antd'; | |||
${props.locale ? "import { SelectLang } from 'umi';" : ''} | |||
import { LogoutOutlined } from '@ant-design/icons'; | |||
import { ILayoutRuntimeConfig } from '../types/interface.d'; | |||
export default function renderRightContent( | |||
runtimeLayout: ILayoutRuntimeConfig, | |||
loading: boolean, | |||
initialState: any, | |||
setInitialState: any, | |||
) { | |||
if (runtimeLayout.rightRender) { | |||
return runtimeLayout.rightRender( | |||
initialState, | |||
setInitialState, | |||
runtimeLayout, | |||
); | |||
} | |||
const menu = ( | |||
<Menu className="umi-plugin-layout-menu"> | |||
<Menu.Item | |||
key="logout" | |||
onClick={() => | |||
runtimeLayout.logout && runtimeLayout?.logout(initialState) | |||
} | |||
> | |||
<LogoutOutlined /> | |||
退出登录 | |||
</Menu.Item> | |||
</Menu> | |||
); | |||
const avatar = ( | |||
<span className="umi-plugin-layout-action"> | |||
<Avatar | |||
size="small" | |||
className="umi-plugin-layout-avatar" | |||
src={ | |||
initialState?.avatar || | |||
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png' | |||
} | |||
alt="avatar" | |||
/> | |||
<span className="umi-plugin-layout-name">{initialState?.name}</span> | |||
</span> | |||
); | |||
if (loading) { | |||
return ( | |||
<div className="umi-plugin-layout-right"> | |||
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} /> | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div className="umi-plugin-layout-right anticon"> | |||
{runtimeLayout.logout ? ( | |||
<Dropdown overlay={menu} overlayClassName="umi-plugin-layout-container"> | |||
{avatar} | |||
</Dropdown> | |||
) : ( | |||
avatar | |||
)} | |||
${props.locale ? '{SelectLang && <SelectLang />}' : ''} | |||
</div> | |||
); | |||
} | |||
`; | |||
}; | |||
export { genRenderRightContent }; |
@@ -0,0 +1,14 @@ | |||
// @ts-nocheck | |||
import { BasicLayoutProps } from '@ant-design/pro-layout'; | |||
import { Models } from '@@/plugin-model/useModel'; | |||
export type RunTimeLayoutConfig = ( | |||
initData: Models<'@@initialState'>, | |||
) => BasicLayoutProps & { | |||
childrenRender?: (dom: JSX.Element, props: BasicLayoutProps) => React.ReactNode, | |||
unAccessible?: JSX.Element, | |||
noFound?: JSX.Element, | |||
}; | |||
// avoid `export *` error | |||
export default {}; |
@@ -0,0 +1,72 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import { Avatar, Dropdown, Menu, Spin } from 'antd'; | |||
import { SelectLang } from 'umi'; | |||
import { LogoutOutlined } from '@ant-design/icons'; | |||
import { ILayoutRuntimeConfig } from '../types/interface.d'; | |||
export default function renderRightContent( | |||
runtimeLayout: ILayoutRuntimeConfig, | |||
loading: boolean, | |||
initialState: any, | |||
setInitialState: any, | |||
) { | |||
if (runtimeLayout.rightRender) { | |||
return runtimeLayout.rightRender( | |||
initialState, | |||
setInitialState, | |||
runtimeLayout, | |||
); | |||
} | |||
const menu = ( | |||
<Menu className="umi-plugin-layout-menu"> | |||
<Menu.Item | |||
key="logout" | |||
onClick={() => | |||
runtimeLayout.logout && runtimeLayout?.logout(initialState) | |||
} | |||
> | |||
<LogoutOutlined /> | |||
退出登录 | |||
</Menu.Item> | |||
</Menu> | |||
); | |||
const avatar = ( | |||
<span className="umi-plugin-layout-action"> | |||
<Avatar | |||
size="small" | |||
className="umi-plugin-layout-avatar" | |||
src={ | |||
initialState?.avatar || | |||
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png' | |||
} | |||
alt="avatar" | |||
/> | |||
<span className="umi-plugin-layout-name">{initialState?.name}</span> | |||
</span> | |||
); | |||
if (loading) { | |||
return ( | |||
<div className="umi-plugin-layout-right"> | |||
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} /> | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div className="umi-plugin-layout-right anticon"> | |||
{runtimeLayout.logout ? ( | |||
<Dropdown overlay={menu} overlayClassName="umi-plugin-layout-container"> | |||
{avatar} | |||
</Dropdown> | |||
) : ( | |||
avatar | |||
)} | |||
{SelectLang && <SelectLang />} | |||
</div> | |||
); | |||
} | |||
@@ -0,0 +1,56 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
// @ts-ignore | |||
import allIcons from './icons'; | |||
export interface MenuDataItem { | |||
children?: MenuDataItem[]; | |||
routes?: MenuDataItem[]; | |||
hideChildrenInMenu?: boolean; | |||
hideInMenu?: boolean; | |||
icon?: string; | |||
locale?: string; | |||
name?: string; | |||
key?: string; | |||
path?: string; | |||
[key: string]: any; | |||
} | |||
function toHump(name: string) { | |||
return name.replace(/\-(\w)/g, function(all, letter) { | |||
return letter.toUpperCase(); | |||
}); | |||
} | |||
function formatter(data: MenuDataItem[]): MenuDataItem[] { | |||
if (!Array.isArray(data)) { | |||
return data; | |||
} | |||
(data || []).forEach((item = { path: '/' }) => { | |||
// 兼容旧的写法 menu:{icon:""} | |||
const icon = item.icon ? item.icon : item.menu ? item.menu.icon : ''; | |||
if (icon && typeof icon === "string") { | |||
const v4IconName = toHump(icon.replace(icon[0], icon[0].toUpperCase())); | |||
const NewIcon = allIcons[icon] || allIcons[`${v4IconName}Outlined`]; | |||
if (NewIcon) { | |||
try { | |||
if (item.icon) | |||
item.icon = React.createElement(NewIcon); | |||
if (item.menu) | |||
item.menu.icon = React.createElement(NewIcon); | |||
} catch (error) { | |||
console.log(error); | |||
} | |||
} | |||
} | |||
if (item.routes || item.children) { | |||
const children = formatter(item.routes || item.children); | |||
// Reduce memory usage | |||
item.children = children; | |||
} | |||
}); | |||
return data; | |||
} | |||
export function patchRoutes({ routes }) { | |||
formatter(routes); | |||
} |
@@ -0,0 +1,473 @@ | |||
// @ts-nocheck | |||
import React,{ useState } from 'react'; | |||
import { Menu, Dropdown } from 'antd'; | |||
import { ClickParam } from 'antd/es/menu'; | |||
import { DropDownProps } from 'antd/es/dropdown'; | |||
import { getLocale, getAllLocales, setLocale } from './localeExports'; | |||
export interface HeaderDropdownProps extends DropDownProps { | |||
overlayClassName?: string; | |||
placement?: | |||
| 'bottomLeft' | |||
| 'bottomRight' | |||
| 'topLeft' | |||
| 'topCenter' | |||
| 'topRight' | |||
| 'bottomCenter'; | |||
} | |||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ | |||
overlayClassName: cls, | |||
...restProps | |||
}) => ( | |||
<Dropdown | |||
overlayClassName={cls} | |||
{...restProps} | |||
/> | |||
); | |||
interface LocalData { | |||
lang: string, | |||
label?: string, | |||
icon?: string, | |||
title?: string, | |||
} | |||
interface SelectLangProps { | |||
globalIconClassName?: string; | |||
postLocalesData?: (locales: LocalData[]) => LocalData[]; | |||
onItemClick?: (params: ClickParam) => void; | |||
className?: string; | |||
reload?: boolean; | |||
icon?: React.ReactNode; | |||
} | |||
const transformArrayToObject = (allLangUIConfig:LocalData[])=>{ | |||
return allLangUIConfig.reduce((obj, item) => { | |||
if(!item.lang){ | |||
return obj; | |||
} | |||
return { | |||
...obj, | |||
[item.lang]: item, | |||
}; | |||
}, {}); | |||
} | |||
const defaultLangUConfigMap = { | |||
'ar-EG': { | |||
lang: 'ar-EG', | |||
label: 'العربية', | |||
icon: '🇪🇬', | |||
title: 'لغة' | |||
}, | |||
'az-AZ': { | |||
lang: 'az-AZ', | |||
label: 'Azərbaycan dili', | |||
icon: '🇦🇿', | |||
title: 'Dil' | |||
}, | |||
'bg-BG': { | |||
lang: 'bg-BG', | |||
label: 'Български език', | |||
icon: '🇧🇬', | |||
title: 'език' | |||
}, | |||
'bn-BD': { | |||
lang: 'bn-BD', | |||
label: 'বাংলা', | |||
icon: '🇧🇩', | |||
title: 'ভাষা' | |||
}, | |||
'ca-ES': { | |||
lang: 'ca-ES', | |||
label: 'Catalá', | |||
icon: '🇨🇦', | |||
title: 'llengua' | |||
}, | |||
'cs-CZ': { | |||
lang: 'cs-CZ', | |||
label: 'Čeština', | |||
icon: '🇨🇿', | |||
title: 'Jazyk' | |||
}, | |||
'da-DK': { | |||
lang: 'da-DK', | |||
label: 'Dansk', | |||
icon: '🇩🇰', | |||
title: 'Sprog' | |||
}, | |||
'de-DE': { | |||
lang: 'de-DE', | |||
label: 'Deutsch', | |||
icon: '🇩🇪', | |||
title: 'Sprache' | |||
}, | |||
'el-GR': { | |||
lang: 'el-GR', | |||
label: 'Ελληνικά', | |||
icon: '🇬🇷', | |||
title: 'Γλώσσα' | |||
}, | |||
'en-GB': { | |||
lang: 'en-GB', | |||
label: 'English', | |||
icon: '🇬🇧', | |||
title: 'Language' | |||
}, | |||
'en-US': { | |||
lang: 'en-US', | |||
label: 'English', | |||
icon: '🇺🇸', | |||
title: 'Language' | |||
}, | |||
'es-ES': { | |||
lang: 'es-ES', | |||
label: 'Español', | |||
icon: '🇪🇸', | |||
title: 'Idioma' | |||
}, | |||
'et-EE': { | |||
lang: 'et-EE', | |||
label: 'Eesti', | |||
icon: '🇪🇪', | |||
title: 'Keel' | |||
}, | |||
'fa-IR': { | |||
lang: 'fa-IR', | |||
label: 'فارسی', | |||
icon: '🇮🇷', | |||
title: 'زبان' | |||
}, | |||
'fi-FI': { | |||
lang: 'fi-FI', | |||
label: 'Suomi', | |||
icon: '🇫🇮', | |||
title: 'Kieli' | |||
}, | |||
'fr-BE': { | |||
lang: 'fr-BE', | |||
label: 'Français', | |||
icon: '🇧🇪', | |||
title: 'Langue' | |||
}, | |||
'fr-FR': { | |||
lang: 'fr-FR', | |||
label: 'Français', | |||
icon: '🇫🇷', | |||
title: 'Langue' | |||
}, | |||
'ga-IE': { | |||
lang: 'ga-IE', | |||
label: 'Gaeilge', | |||
icon: '🇮🇪', | |||
title: 'Teanga' | |||
}, | |||
'he-IL': { | |||
lang: 'he-IL', | |||
label: 'עברית', | |||
icon: '🇮🇱', | |||
title: 'שפה' | |||
}, | |||
'hi-IN': { | |||
lang: 'hi-IN', | |||
label: 'हिन्दी, हिंदी', | |||
icon: '🇮🇳', | |||
title: 'भाषा: हिन्दी' | |||
}, | |||
'hr-HR': { | |||
lang: 'hr-HR', | |||
label: 'Hrvatski jezik', | |||
icon: '🇭🇷', | |||
title: 'Jezik' | |||
}, | |||
'hu-HU': { | |||
lang: 'hu-HU', | |||
label: 'Magyar', | |||
icon: '🇭🇺', | |||
title: 'Nyelv' | |||
}, | |||
'hy-AM': { | |||
lang: 'hu-HU', | |||
label: 'Հայերեն', | |||
icon: '🇦🇲', | |||
title: 'Լեզու' | |||
}, | |||
'id-ID': { | |||
lang: 'id-ID', | |||
label: 'Bahasa Indonesia', | |||
icon: '🇮🇩', | |||
title: 'Bahasa' | |||
}, | |||
'it-IT': { | |||
lang: 'it-IT', | |||
label: 'Italiano', | |||
icon: '🇮🇹', | |||
title: 'Linguaggio' | |||
}, | |||
'is-IS': { | |||
lang: 'is-IS', | |||
label: 'Íslenska', | |||
icon: '🇮🇸', | |||
title: 'Tungumál' | |||
}, | |||
'ja-JP': { | |||
lang: 'ja-JP', | |||
label: '日本語', | |||
icon: '🇯🇵', | |||
title: '言語' | |||
}, | |||
'ku-IQ': { | |||
lang: 'ku-IQ', | |||
label: 'کوردی', | |||
icon: '🇮🇶', | |||
title: 'Ziman' | |||
}, | |||
'kn-IN': { | |||
lang: 'zh-TW', | |||
label: 'ಕನ್ನಡ', | |||
icon: '🇮🇳', | |||
title: 'ಭಾಷೆ' | |||
}, | |||
'ko-KR': { | |||
lang: 'ko-KR', | |||
label: '한국어', | |||
icon: '🇰🇷', | |||
title: '언어' | |||
}, | |||
'lv-LV': { | |||
lang: 'lv-LV', | |||
label: 'Latviešu valoda', | |||
icon: '🇱🇮', | |||
title: 'Kalba' | |||
}, | |||
'mk-MK': { | |||
lang: 'mk-MK', | |||
label: 'македонски јазик', | |||
icon: '🇲🇰', | |||
title: 'Јазик' | |||
}, | |||
'mn-MN': { | |||
lang: 'mn-MN', | |||
label: 'Монгол хэл', | |||
icon: '🇲🇳', | |||
title: 'Хэл' | |||
}, | |||
'ms-MY': { | |||
lang: 'ms-MY', | |||
label: 'بهاس ملايو', | |||
icon: '🇲🇾', | |||
title: 'Bahasa' | |||
}, | |||
'nb-NO': { | |||
lang: 'nb-NO', | |||
label: 'Norsk', | |||
icon: '🇳🇴', | |||
title: 'Språk' | |||
}, | |||
'ne-NP': { | |||
lang: 'ne-NP', | |||
label: 'नेपाली', | |||
icon: '🇳🇵', | |||
title: 'भाषा' | |||
}, | |||
'nl-BE': { | |||
lang: 'nl-BE', | |||
label: 'Vlaams', | |||
icon: '🇧🇪', | |||
title: 'Taal' | |||
}, | |||
'nl-NL': { | |||
lang: 'nl-NL', | |||
label: 'Vlaams', | |||
icon: '🇳🇱', | |||
title: 'Taal' | |||
}, | |||
'pl-PL': { | |||
lang: 'pl-PL', | |||
label: 'Polski', | |||
icon: '🇵🇱', | |||
title: 'Język' | |||
}, | |||
'pt-BR': { | |||
lang: 'pt-BR', | |||
label: 'Português', | |||
icon: '🇧🇷', | |||
title: 'Idiomas' | |||
}, | |||
'pt-PT': { | |||
lang: 'pt-PT', | |||
label: 'Português', | |||
icon: '🇵🇹', | |||
title: 'Idiomas' | |||
}, | |||
'ro-RO': { | |||
lang: 'ro-RO', | |||
label: 'Română', | |||
icon: '🇷🇴', | |||
title: 'Limba' | |||
}, | |||
'ru-RU': { | |||
lang: 'ru-RU', | |||
label: 'Русский', | |||
icon: '🇷🇺', | |||
title: 'язык' | |||
}, | |||
'sk-SK': { | |||
lang: 'sk-SK', | |||
label: 'Slovenčina', | |||
icon: '🇸🇰', | |||
title: 'Jazyk' | |||
}, | |||
'sr-RS': { | |||
lang: 'sr-RS', | |||
label: 'српски језик', | |||
icon: '🇸🇷', | |||
title: 'Језик' | |||
}, | |||
'sl-SI': { | |||
lang: 'sl-SI', | |||
label: 'Slovenščina', | |||
icon: '🇸🇱', | |||
title: 'Jezik' | |||
}, | |||
'sv-SE': { | |||
lang: 'sv-SE', | |||
label: 'Svenska', | |||
icon: '🇸🇪', | |||
title: 'Språk' | |||
}, | |||
'ta-IN': { | |||
lang: 'ta-IN', | |||
label: 'தமிழ்', | |||
icon: '🇮🇳', | |||
title: 'மொழி' | |||
}, | |||
'th-TH': { | |||
lang: 'th-TH', | |||
label: 'ไทย', | |||
icon: '🇹🇭', | |||
title: 'ภาษา' | |||
}, | |||
'tr-TR': { | |||
lang: 'tr-TR', | |||
label: 'Türkçe', | |||
icon: '🇹🇷', | |||
title: 'Dil' | |||
}, | |||
'uk-UA': { | |||
lang: 'uk-UA', | |||
label: 'Українська', | |||
icon: '🇺🇰', | |||
title: 'Мова' | |||
}, | |||
'vi-VN': { | |||
lang: 'vi-VN', | |||
label: 'Tiếng Việt', | |||
icon: '🇻🇳', | |||
title: 'Ngôn ngữ' | |||
}, | |||
'zh-CN': { | |||
lang: 'zh-CN', | |||
label: '简体中文', | |||
icon: '🇨🇳', | |||
title: '语言' | |||
}, | |||
'zh-TW': { | |||
lang: 'zh-TW', | |||
label: '繁體中文', | |||
icon: '🇭🇰', | |||
title: '語言' | |||
} | |||
}; | |||
export const SelectLang: React.FC<SelectLangProps> = (props) => { | |||
const { | |||
globalIconClassName, | |||
postLocalesData, | |||
onItemClick, | |||
icon, | |||
style, | |||
reload, | |||
...restProps | |||
} = props; | |||
const [selectedLang, setSelectedLang] = useState(() => getLocale()); | |||
const changeLang = ({ key }: ClickParam): void => { | |||
setLocale(key, reload); | |||
setSelectedLang(getLocale()) | |||
}; | |||
const defaultLangUConfig = getAllLocales().map( | |||
(key) => | |||
defaultLangUConfigMap[key] || { | |||
lang: key, | |||
label: key, | |||
icon: "🌐", | |||
title: key, | |||
} | |||
); | |||
const allLangUIConfig = | |||
postLocalesData?.(defaultLangUConfig) || defaultLangUConfig; | |||
const handleClick = onItemClick | |||
? (params: ClickParam) => onItemClick(params) | |||
: changeLang; | |||
const menuItemStyle = { minWidth: "160px" }; | |||
const menuItemIconStyle = { marginRight: "8px" }; | |||
const langMenu = ( | |||
<Menu selectedKeys={[selectedLang]} onClick={handleClick}> | |||
{allLangUIConfig.map((localeObj) => { | |||
return ( | |||
<Menu.Item key={localeObj.lang || localeObj.key} style={menuItemStyle}> | |||
<span role="img" aria-label={localeObj?.label || "en-US"} style={menuItemIconStyle}> | |||
{localeObj?.icon || "🌐"} | |||
</span> | |||
{localeObj?.label || "en-US"} | |||
</Menu.Item> | |||
); | |||
})} | |||
</Menu> | |||
); | |||
const inlineStyle = { | |||
cursor: "pointer", | |||
padding: "12px", | |||
display: "inline-flex", | |||
alignItems: "center", | |||
justifyContent: "center", | |||
fontSize: 18, | |||
verticalAlign: "middle", | |||
...style, | |||
}; | |||
return ( | |||
<HeaderDropdown overlay={langMenu} placement="bottomRight" {...restProps}> | |||
<span className={globalIconClassName} style={inlineStyle}> | |||
<i className="anticon" title={allLangUIConfig[selectedLang]?.title}> | |||
{ icon ? | |||
icon : ( | |||
<svg | |||
viewBox="0 0 24 24" | |||
focusable="false" | |||
width="1em" | |||
height="1em" | |||
fill="currentColor" | |||
aria-hidden="true" | |||
> | |||
<path d="M0 0h24v24H0z" fill="none" /> | |||
<path | |||
d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z " | |||
className="css-c4d79v" | |||
/> | |||
</svg> | |||
)} | |||
</i> | |||
</span> | |||
</HeaderDropdown> | |||
); | |||
return <></> | |||
}; |
@@ -0,0 +1,60 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import EventEmitter from 'events'; | |||
import { ConfigProvider } from 'antd'; | |||
import moment from 'moment'; | |||
import 'moment/locale/zh-cn'; | |||
import 'moment/locale/zh-tw'; | |||
import { RawIntlProvider, getLocale, getDirection , setIntl, getIntl, localeInfo } from './localeExports'; | |||
// @ts-ignore | |||
export const event = new EventEmitter(); | |||
event.setMaxListeners(5); | |||
export const LANG_CHANGE_EVENT = Symbol('LANG_CHANGE'); | |||
export function _onCreate() { | |||
const locale = getLocale(); | |||
if (moment?.locale) { | |||
moment.locale(localeInfo[locale]?.momentLocale || ''); | |||
} | |||
setIntl(locale); | |||
} | |||
const useIsomorphicLayoutEffect = | |||
typeof window !== 'undefined' && | |||
typeof window.document !== 'undefined' && | |||
typeof window.document.createElement !== 'undefined' | |||
? React.useLayoutEffect | |||
: React.useEffect | |||
export const _LocaleContainer = (props:any) => { | |||
const [locale, setLocale] = React.useState(() => getLocale()); | |||
const [intl, setContainerIntl] = React.useState(() => getIntl(locale, true)); | |||
const handleLangChange = (locale:string) => { | |||
if (moment?.locale) { | |||
moment.locale(localeInfo[locale]?.momentLocale || 'en'); | |||
} | |||
setLocale(locale); | |||
setContainerIntl(getIntl(locale)); | |||
}; | |||
useIsomorphicLayoutEffect(() => { | |||
event.on(LANG_CHANGE_EVENT, handleLangChange); | |||
return () => { | |||
event.off(LANG_CHANGE_EVENT, handleLangChange); | |||
}; | |||
}, []); | |||
const defaultAntdLocale = { | |||
} | |||
const direction = getDirection(); | |||
return ( | |||
<ConfigProvider direction={direction} locale={localeInfo[locale]?.antd || defaultAntdLocale}> | |||
<RawIntlProvider value={intl}>{props.children}</RawIntlProvider> | |||
</ConfigProvider> | |||
) | |||
}; |
@@ -0,0 +1,277 @@ | |||
// @ts-nocheck | |||
import { | |||
createIntl, | |||
IntlShape, | |||
MessageDescriptor, | |||
} from 'D:/Work/BPA.SAAS.Web/node_modules/react-intl'; | |||
import { ApplyPluginsType } from 'umi'; | |||
import { event, LANG_CHANGE_EVENT } from './locale'; | |||
// @ts-ignore | |||
import warning from 'D:/Work/BPA.SAAS.Web/node_modules/@umijs/plugin-locale/node_modules/warning/warning.js'; | |||
import { plugin } from '../core/plugin'; | |||
export { | |||
createIntl, | |||
}; | |||
export { | |||
FormattedDate, | |||
FormattedDateParts, | |||
FormattedDisplayName, | |||
FormattedHTMLMessage, | |||
FormattedList, | |||
FormattedMessage, | |||
FormattedNumber, | |||
FormattedNumberParts, | |||
FormattedPlural, | |||
FormattedRelativeTime, | |||
FormattedTime, | |||
FormattedTimeParts, | |||
IntlContext, | |||
IntlProvider, | |||
RawIntlProvider, | |||
createIntlCache, | |||
defineMessages, | |||
injectIntl, | |||
useIntl, | |||
} from 'D:/Work/BPA.SAAS.Web/node_modules/react-intl'; | |||
let g_intl: IntlShape; | |||
const useLocalStorage = true; | |||
import enUS0 from 'antd/es/locale/en_US'; | |||
import lang_enUS0 from "D:/Work/BPA.SAAS.Web/src/locales/en-US.js"; | |||
import zhCN0 from 'antd/es/locale/zh_CN'; | |||
import lang_zhCN0 from "D:/Work/BPA.SAAS.Web/src/locales/zh-CN.js"; | |||
import zhTW0 from 'antd/es/locale/zh_TW'; | |||
import lang_zhTW0 from "D:/Work/BPA.SAAS.Web/src/locales/zh-TW.js"; | |||
export const localeInfo: {[key: string]: any} = { | |||
'en-US': { | |||
messages: { | |||
...lang_enUS0, | |||
}, | |||
locale: 'en-US', | |||
antd: { | |||
...enUS0, | |||
}, | |||
momentLocale: '', | |||
}, | |||
'zh-CN': { | |||
messages: { | |||
...lang_zhCN0, | |||
}, | |||
locale: 'zh-CN', | |||
antd: { | |||
...zhCN0, | |||
}, | |||
momentLocale: 'zh-cn', | |||
}, | |||
'zh-TW': { | |||
messages: { | |||
...lang_zhTW0, | |||
}, | |||
locale: 'zh-TW', | |||
antd: { | |||
...zhTW0, | |||
}, | |||
momentLocale: 'zh-tw', | |||
}, | |||
}; | |||
/** | |||
* 增加一个新的国际化语言 | |||
* @param name 语言的 key | |||
* @param messages 对应的枚举对象 | |||
* @param extraLocale momentLocale, antd 国际化 | |||
*/ | |||
export const addLocale = ( | |||
name: string, | |||
messages: Object, | |||
extraLocales: { | |||
momentLocale:string; | |||
antd:string | |||
}, | |||
) => { | |||
if (!name) { | |||
return; | |||
} | |||
// 可以合并 | |||
const mergeMessages = localeInfo[name]?.messages | |||
? Object.assign({}, localeInfo[name].messages, messages) | |||
: messages; | |||
const { momentLocale, antd } = extraLocales || {}; | |||
const locale = name.split('-')?.join('-') | |||
localeInfo[name] = { | |||
messages: mergeMessages, | |||
locale, | |||
momentLocale: momentLocale, | |||
antd, | |||
}; | |||
// 如果这是的 name 和当前的locale 相同需要重新设置一下,不然更新不了 | |||
if (locale === getLocale()) { | |||
event.emit(LANG_CHANGE_EVENT, locale); | |||
} | |||
}; | |||
/** | |||
* 获取当前的 intl 对象,可以在 node 中使用 | |||
* @param locale 需要切换的语言类型 | |||
* @param changeIntl 是否不使用 g_intl | |||
* @returns IntlShape | |||
*/ | |||
export const getIntl = (locale?: string, changeIntl?: boolean) => { | |||
// 如果全局的 g_intl 存在,且不是 setIntl 调用 | |||
if (g_intl && !changeIntl && !locale) { | |||
return g_intl; | |||
} | |||
// 如果存在于 localeInfo 中 | |||
if (locale&&localeInfo[locale]) { | |||
return createIntl(localeInfo[locale]); | |||
} | |||
// 不存在需要一个报错提醒 | |||
warning( | |||
!locale||!!localeInfo[locale], | |||
`The current popular language does not exist, please check the locales folder!`, | |||
); | |||
// 使用 zh-CN | |||
if (localeInfo["zh-CN"]) return createIntl(localeInfo["zh-CN"]); | |||
// 如果还没有,返回一个空的 | |||
return createIntl({ | |||
locale: "zh-CN", | |||
messages: {}, | |||
}); | |||
}; | |||
/** | |||
* 切换全局的 intl 的设置 | |||
* @param locale 语言的key | |||
*/ | |||
export const setIntl = (locale: string) => { | |||
g_intl = getIntl(locale, true); | |||
}; | |||
/** | |||
* 获取当前选择的语言 | |||
* @returns string | |||
*/ | |||
export const getLocale = () => { | |||
const runtimeLocale = plugin.applyPlugins({ | |||
key: 'locale', | |||
type: ApplyPluginsType.modify, | |||
initialValue: {}, | |||
}); | |||
// runtime getLocale for user define | |||
if (typeof runtimeLocale?.getLocale === 'function') { | |||
return runtimeLocale.getLocale(); | |||
} | |||
// please clear localStorage if you change the baseSeparator config | |||
// because changing will break the app | |||
const lang = | |||
navigator.cookieEnabled && typeof localStorage !== 'undefined' && useLocalStorage | |||
? window.localStorage.getItem('umi_locale') | |||
: ''; | |||
// support baseNavigator, default true | |||
let browserLang; | |||
const isNavigatorLanguageValid = | |||
typeof navigator !== 'undefined' && typeof navigator.language === 'string'; | |||
browserLang = isNavigatorLanguageValid | |||
? navigator.language.split('-').join('-') | |||
: ''; | |||
return lang || browserLang || "zh-CN"; | |||
}; | |||
/** | |||
* 获取当前选择的方向 | |||
* @returns string | |||
*/ | |||
export const getDirection = () => { | |||
const lang = getLocale(); | |||
// array with all prefixs for rtl langueges ex: ar-EG , he-IL | |||
const rtlLangs = ['he', 'ar', 'fa', 'ku'] | |||
const direction = rtlLangs.filter(lng => lang.startsWith(lng)).length ? 'rtl' : 'ltr'; | |||
return direction; | |||
}; | |||
/** | |||
* 切换语言 | |||
* @param lang 语言的 key | |||
* @param realReload 是否刷新页面,默认刷新 | |||
* @returns string | |||
*/ | |||
export const setLocale = (lang: string, realReload: boolean = true) => { | |||
const runtimeLocale = plugin.applyPlugins({ | |||
key: 'locale', | |||
type: ApplyPluginsType.modify, | |||
initialValue: {}, | |||
}); | |||
const updater = () => { | |||
if (getLocale() !== lang) { | |||
if (navigator.cookieEnabled && typeof window.localStorage !== 'undefined' && useLocalStorage) { | |||
window.localStorage.setItem('umi_locale', lang || ''); | |||
} | |||
setIntl(lang); | |||
if (realReload) { | |||
window.location.reload(); | |||
} else { | |||
event.emit(LANG_CHANGE_EVENT, lang); | |||
// chrome 不支持这个事件。所以人肉触发一下 | |||
if (window.dispatchEvent) { | |||
const event = new Event('languagechange'); | |||
window.dispatchEvent(event); | |||
} | |||
} | |||
} | |||
} | |||
if (typeof runtimeLocale?.setLocale === 'function') { | |||
runtimeLocale.setLocale({ | |||
lang, | |||
realReload, | |||
updater: updater, | |||
}); | |||
return; | |||
} | |||
updater(); | |||
}; | |||
let firstWaring = true; | |||
/** | |||
* intl.formatMessage 的语法糖 | |||
* @deprecated 使用此 api 会造成切换语言的时候无法自动刷新,请使用 useIntl 或 injectIntl | |||
* @param descriptor { id : string, defaultMessage : string } | |||
* @param values { [key:string] : string } | |||
* @returns string | |||
*/ | |||
export const formatMessage: IntlShape['formatMessage'] = ( | |||
descriptor: MessageDescriptor, | |||
values: any, | |||
) => { | |||
if (firstWaring) { | |||
warning( | |||
false, | |||
`Using this API will cause automatic refresh when switching languages, please use useIntl or injectIntl. | |||
使用此 api 会造成切换语言的时候无法自动刷新,请使用 useIntl 或 injectIntl。 | |||
http://j.mp/37Fkd5Q | |||
`, | |||
); | |||
firstWaring = false; | |||
} | |||
return g_intl.formatMessage(descriptor, values); | |||
}; | |||
/** | |||
* 获取语言列表 | |||
* @returns string[] | |||
*/ | |||
export const getAllLocales = () => Object.keys(localeInfo); |
@@ -0,0 +1,10 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
// @ts-ignore | |||
import { _LocaleContainer } from './locale'; | |||
import { getIntl, getLocale } from './localeExports'; | |||
export function rootContainer(container: Element) { | |||
return React.createElement(_LocaleContainer, null, container); | |||
} | |||
@@ -0,0 +1,39 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
import initialState from 'D:/Work/BPA.SAAS.Web/src/.umi-production/plugin-initial-state/models/initialState'; | |||
// @ts-ignore | |||
import Dispatcher from './helpers/dispatcher'; | |||
// @ts-ignore | |||
import Executor from './helpers/executor'; | |||
// @ts-ignore | |||
import { UmiContext } from './helpers/constant'; | |||
export const models = { '@@initialState': initialState, }; | |||
export type Model<T extends keyof typeof models> = { | |||
[key in keyof typeof models]: ReturnType<typeof models[T]>; | |||
}; | |||
export type Models<T extends keyof typeof models> = Model<T>[T] | |||
const dispatcher = new Dispatcher!(); | |||
const Exe = Executor!; | |||
export default ({ children }: { children: React.ReactNode }) => { | |||
return ( | |||
<UmiContext.Provider value={dispatcher}> | |||
{ | |||
Object.entries(models).map(pair => ( | |||
<Exe key={pair[0]} namespace={pair[0]} hook={pair[1] as any} onUpdate={(val: any) => { | |||
const [ns] = pair as [keyof typeof models, any]; | |||
dispatcher.data[ns] = val; | |||
dispatcher.update(ns); | |||
}} /> | |||
)) | |||
} | |||
{children} | |||
</UmiContext.Provider> | |||
) | |||
} |
@@ -0,0 +1,4 @@ | |||
// @ts-nocheck | |||
import React from 'react'; | |||
export const UmiContext = React.createContext({}); |
@@ -0,0 +1,19 @@ | |||
// @ts-nocheck | |||
export default class Dispatcher { | |||
callbacks = {}; | |||
data = {}; | |||
update = (namespace: string) => { | |||
(this.callbacks[namespace] || []).forEach( | |||
(callback: (val: any) => void) => { | |||
try { | |||
const data = this.data[namespace]; | |||
callback(data); | |||
} catch (e) { | |||
callback(undefined); | |||
} | |||
}, | |||
); | |||
}; | |||
} |
@@ -0,0 +1,83 @@ | |||
// @ts-nocheck | |||
import React, { useEffect, useRef, useMemo } from 'react'; | |||
interface ExecutorProps { | |||
hook: () => any; | |||
onUpdate: (val: any) => void; | |||
namespace: string; | |||
} | |||
export default (props: ExecutorProps) => { | |||
const { hook, onUpdate, namespace } = props; | |||
const updateRef = useRef(onUpdate); | |||
updateRef.current = onUpdate; | |||
const initialLoad = useRef(false); | |||
let data: any; | |||
try { | |||
data = hook(); | |||
if ( | |||
process.env.NODE_ENV === 'development' && | |||
typeof document !== 'undefined' | |||
) { | |||
try { | |||
let count = Object.keys( | |||
((window as any)._umi_useModel_dev_tool_log || {})[namespace] || {}, | |||
).length; | |||
(window as any)._umi_useModel_dev_tool = Object.assign( | |||
(window as any)._umi_useModel_dev_tool || {}, | |||
{ | |||
[namespace]: data, | |||
}, | |||
); | |||
(window as any)._umi_useModel_dev_tool_log = Object.assign( | |||
(window as any)._umi_useModel_dev_tool_log || {}, | |||
{ | |||
[namespace]: Object.assign( | |||
((window as any)._umi_useModel_dev_tool_log || {})[namespace] || | |||
{}, | |||
{ | |||
[count]: data, | |||
}, | |||
), | |||
}, | |||
); | |||
window.dispatchEvent( | |||
new CustomEvent('_umi_useModel_update', { | |||
detail: { | |||
namespace, | |||
time: Date.now(), | |||
data, | |||
index: count, | |||
}, | |||
}), | |||
); | |||
} catch (e) { | |||
// dev tool 记录失败、可能是低版本浏览器,忽略 | |||
} | |||
} | |||
} catch (e) { | |||
console.error( | |||
`plugin-model: Invoking '${namespace || 'unknown'}' model failed:`, | |||
e, | |||
); | |||
} | |||
// 首次执行时立刻返回初始值 | |||
useMemo(() => { | |||
updateRef.current(data); | |||
initialLoad.current = false; | |||
}, []); | |||
// React 16.13 后 update 函数用 useEffect 包裹 | |||
useEffect(() => { | |||
if (initialLoad.current) { | |||
updateRef.current(data); | |||
} else { | |||
initialLoad.current = true; | |||
} | |||
}); | |||
return <></>; | |||
}; |
@@ -0,0 +1,12 @@ | |||
// @ts-nocheck | |||
/* eslint-disable import/no-dynamic-require */ | |||
import React from 'react'; | |||
import Provider from './Provider'; | |||
export function rootContainer(container: React.ReactNode) { | |||
return React.createElement( | |||
Provider, | |||
null, | |||
container, | |||
); | |||
} |
@@ -0,0 +1,71 @@ | |||
// @ts-nocheck | |||
import { useState, useEffect, useContext, useRef } from 'react'; | |||
// @ts-ignore | |||
import isEqual from 'D:/Work/BPA.SAAS.Web/node_modules/@umijs/plugin-model/node_modules/fast-deep-equal/index.js'; | |||
// @ts-ignore | |||
import { UmiContext } from './helpers/constant'; | |||
import { Model, models } from './Provider'; | |||
export type Models<T extends keyof typeof models> = Model<T>[T] | |||
export function useModel<T extends keyof Model<T>>(model: T): Model<T>[T] | |||
export function useModel<T extends keyof Model<T>, U>(model: T, selector: (model: Model<T>[T]) => U): U | |||
export function useModel<T extends keyof Model<T>, U>( | |||
namespace: T, | |||
updater?: (model: Model<T>[T]) => U | |||
) : typeof updater extends undefined ? Model<T>[T] : ReturnType<NonNullable<typeof updater>>{ | |||
type RetState = typeof updater extends undefined ? Model<T>[T] : ReturnType<NonNullable<typeof updater>> | |||
const dispatcher = useContext<any>(UmiContext); | |||
const updaterRef = useRef(updater); | |||
updaterRef.current = updater; | |||
const [state, setState] = useState<RetState>( | |||
() => updaterRef.current ? updaterRef.current(dispatcher.data![namespace]) : dispatcher.data![namespace] | |||
); | |||
const stateRef = useRef<any>(state); | |||
stateRef.current = state; | |||
const isMount = useRef(false); | |||
useEffect(() => { | |||
isMount.current = true; | |||
return () => { | |||
isMount.current = false; | |||
} | |||
}, []) | |||
useEffect(() => { | |||
const handler = (e: any) => { | |||
if(!isMount.current) { | |||
// 如果 handler 执行过程中,组件被卸载了,则强制更新全局 data | |||
setTimeout(() => { | |||
dispatcher.data![namespace] = e; | |||
dispatcher.update(namespace); | |||
}); | |||
} else { | |||
if(updater && updaterRef.current){ | |||
const currentState = updaterRef.current(e); | |||
const previousState = stateRef.current | |||
if(!isEqual(currentState, previousState)){ | |||
setState(currentState); | |||
} | |||
} else { | |||
setState(e); | |||
} | |||
} | |||
} | |||
try { | |||
dispatcher.callbacks![namespace]!.add(handler); | |||
dispatcher.update(namespace); | |||
} catch (e) { | |||
dispatcher.callbacks![namespace] = new Set(); | |||
dispatcher.callbacks![namespace]!.add(handler); | |||
dispatcher.update(namespace); | |||
} | |||
return () => { | |||
dispatcher.callbacks![namespace]!.delete(handler); | |||
} | |||
}, [namespace]); | |||
return state; | |||
}; |
@@ -0,0 +1,41 @@ | |||
// @ts-nocheck | |||
// This file is generated by Umi automatically | |||
// DO NOT CHANGE IT MANUALLY! | |||
import { useEffect, useState } from 'react'; | |||
import { SwaggerUIBundle } from 'swagger-ui-dist'; | |||
import 'swagger-ui-dist/swagger-ui.css'; | |||
const App = () => { | |||
const [value, setValue] = useState("openapi" ); | |||
useEffect(() => { | |||
SwaggerUIBundle({ | |||
url: `/umi-plugins_${value}.json`, | |||
dom_id: '#swagger-ui', | |||
}); | |||
}, [value]); | |||
return ( | |||
<div | |||
style={{ | |||
padding: 24, | |||
}} | |||
> | |||
<select | |||
style={{ | |||
position: "fixed", | |||
right: "16px", | |||
top: "8px", | |||
}} | |||
onChange={(e) => setValue(e.target.value)} | |||
> | |||
<option value="openapi">openapi</option> | |||
<option value="swagger">swagger</option> | |||
</select> | |||
<div id="swagger-ui" /> | |||
</div> | |||
); | |||
}; | |||
export default App; |
@@ -0,0 +1,278 @@ | |||
// @ts-nocheck | |||
/** | |||
* Base on https://github.com/umijs/D:/Work/BPA.SAAS.Web/node_modules/umi-request | |||
*/ | |||
import { | |||
extend, | |||
Context, | |||
RequestOptionsInit, | |||
OnionMiddleware, | |||
RequestOptionsWithoutResponse, | |||
RequestMethod, | |||
RequestOptionsWithResponse, | |||
RequestResponse, | |||
RequestInterceptor, | |||
ResponseInterceptor, | |||
} from 'D:/Work/BPA.SAAS.Web/node_modules/umi-request'; | |||
// @ts-ignore | |||
import { ApplyPluginsType } from 'umi'; | |||
import { history, plugin } from '../core/umiExports'; | |||
// decoupling with antd UI library, you can using `alias` modify the ui methods | |||
// @ts-ignore | |||
import { message, notification } from '@umijs/plugin-request/lib/ui'; | |||
import useUmiRequest, { UseRequestProvider } from 'D:/Work/BPA.SAAS.Web/node_modules/@ahooksjs/use-request'; | |||
import { | |||
BaseOptions, | |||
BasePaginatedOptions, | |||
BaseResult, | |||
CombineService, | |||
LoadMoreFormatReturn, | |||
LoadMoreOptions, | |||
LoadMoreOptionsWithFormat, | |||
LoadMoreParams, | |||
LoadMoreResult, | |||
OptionsWithFormat, | |||
PaginatedFormatReturn, | |||
PaginatedOptionsWithFormat, | |||
PaginatedParams, | |||
PaginatedResult, | |||
} from 'D:/Work/BPA.SAAS.Web/node_modules/@ahooksjs/use-request/lib/types'; | |||
type ResultWithData<T = any> = { data?: T; [key: string]: any }; | |||
function useRequest< | |||
R = any, | |||
P extends any[] = any, | |||
U = any, | |||
UU extends U = any, | |||
>( | |||
service: CombineService<R, P>, | |||
options: OptionsWithFormat<R, P, U, UU>, | |||
): BaseResult<U, P>; | |||
function useRequest<R extends ResultWithData = any, P extends any[] = any>( | |||
service: CombineService<R, P>, | |||
options?: BaseOptions<R['data'], P>, | |||
): BaseResult<R['data'], P>; | |||
function useRequest<R extends LoadMoreFormatReturn = any, RR = any>( | |||
service: CombineService<RR, LoadMoreParams<R>>, | |||
options: LoadMoreOptionsWithFormat<R, RR>, | |||
): LoadMoreResult<R>; | |||
function useRequest< | |||
R extends ResultWithData<LoadMoreFormatReturn | any> = any, | |||
RR extends R = any, | |||
>( | |||
service: CombineService<R, LoadMoreParams<R['data']>>, | |||
options: LoadMoreOptions<RR['data']>, | |||
): LoadMoreResult<R['data']>; | |||
function useRequest<R = any, Item = any, U extends Item = any>( | |||
service: CombineService<R, PaginatedParams>, | |||
options: PaginatedOptionsWithFormat<R, Item, U>, | |||
): PaginatedResult<Item>; | |||
function useRequest<Item = any, U extends Item = any>( | |||
service: CombineService< | |||
ResultWithData<PaginatedFormatReturn<Item>>, | |||
PaginatedParams | |||
>, | |||
options: BasePaginatedOptions<U>, | |||
): PaginatedResult<Item>; | |||
function useRequest(service: any, options: any = {}) { | |||
return useUmiRequest(service, { | |||
formatResult: result => result?.data, | |||
requestMethod: (requestOptions: any) => { | |||
if (typeof requestOptions === 'string') { | |||
return request(requestOptions); | |||
} | |||
if (typeof requestOptions === 'object') { | |||
const { url, ...rest } = requestOptions; | |||
return request(url, rest); | |||
} | |||
throw new Error('request options error'); | |||
}, | |||
...options, | |||
}); | |||
} | |||
export interface RequestConfig extends RequestOptionsInit { | |||
errorConfig?: { | |||
errorPage?: string; | |||
adaptor?: (resData: any, ctx: Context) => ErrorInfoStructure; | |||
}; | |||
middlewares?: OnionMiddleware[]; | |||
requestInterceptors?: RequestInterceptor[]; | |||
responseInterceptors?: ResponseInterceptor[]; | |||
} | |||
export enum ErrorShowType { | |||
SILENT = 0, | |||
WARN_MESSAGE = 1, | |||
ERROR_MESSAGE = 2, | |||
NOTIFICATION = 4, | |||
REDIRECT = 9, | |||
} | |||
interface ErrorInfoStructure { | |||
success: boolean; | |||
data?: any; | |||
errorCode?: string; | |||
errorMessage?: string; | |||
showType?: ErrorShowType; | |||
traceId?: string; | |||
host?: string; | |||
[key: string]: any; | |||
} | |||
interface RequestError extends Error { | |||
data?: any; | |||
info?: ErrorInfoStructure; | |||
request?: Context['req']; | |||
response?: Context['res']; | |||
} | |||
const DEFAULT_ERROR_PAGE = '/exception'; | |||
let requestMethodInstance: RequestMethod; | |||
const getRequestMethod = () => { | |||
if (requestMethodInstance) { | |||
// request method 已经示例化 | |||
return requestMethodInstance; | |||
} | |||
// runtime 配置可能应为依赖顺序的问题在模块初始化的时候无法获取,所以需要封装一层在异步调用后初始化相关方法 | |||
// 当用户的 app.ts 中依赖了该文件的情况下就该模块的初始化时间就会被提前,无法获取到运行时配置 | |||
const requestConfig: RequestConfig = plugin.applyPlugins({ | |||
key: 'request', | |||
type: ApplyPluginsType.modify, | |||
initialValue: {}, | |||
}); | |||
const errorAdaptor = | |||
requestConfig.errorConfig?.adaptor || ((resData) => resData); | |||
requestMethodInstance = extend({ | |||
errorHandler: (error: RequestError) => { | |||
// @ts-ignore | |||
if (error?.request?.options?.skipErrorHandler) { | |||
throw error; | |||
} | |||
let errorInfo: ErrorInfoStructure | undefined; | |||
if (error.name === 'ResponseError' && error.data && error.request) { | |||
const ctx: Context = { | |||
req: error.request, | |||
res: error.response, | |||
}; | |||
errorInfo = errorAdaptor(error.data, ctx); | |||
error.message = errorInfo?.errorMessage || error.message; | |||
error.data = error.data; | |||
error.info = errorInfo; | |||
} | |||
errorInfo = error.info; | |||
if (errorInfo) { | |||
const errorMessage = errorInfo?.errorMessage; | |||
const errorCode = errorInfo?.errorCode; | |||
const errorPage = | |||
requestConfig.errorConfig?.errorPage || DEFAULT_ERROR_PAGE; | |||
switch (errorInfo?.showType) { | |||
case ErrorShowType.SILENT: | |||
// do nothing | |||
break; | |||
case ErrorShowType.WARN_MESSAGE: | |||
message.warn(errorMessage); | |||
break; | |||
case ErrorShowType.ERROR_MESSAGE: | |||
message.error(errorMessage); | |||
break; | |||
case ErrorShowType.NOTIFICATION: | |||
notification.open({ | |||
description: errorMessage, | |||
message: errorCode, | |||
}); | |||
break; | |||
case ErrorShowType.REDIRECT: | |||
// @ts-ignore | |||
history.push({ | |||
pathname: errorPage, | |||
query: { errorCode, errorMessage }, | |||
}); | |||
// redirect to error page | |||
break; | |||
default: | |||
message.error(errorMessage); | |||
break; | |||
} | |||
} else { | |||
message.error(error.message || 'Request error, please retry.'); | |||
} | |||
throw error; | |||
}, | |||
...requestConfig, | |||
}); | |||
// 中间件统一错误处理 | |||
// 后端返回格式 { success: boolean, data: any } | |||
// 按照项目具体情况修改该部分逻辑 | |||
requestMethodInstance.use(async (ctx, next) => { | |||
await next(); | |||
const { req, res } = ctx; | |||
// @ts-ignore | |||
if (req.options?.skipErrorHandler) { | |||
return; | |||
} | |||
const { options } = req; | |||
const { getResponse } = options; | |||
const resData = getResponse ? res.data : res; | |||
const errorInfo = errorAdaptor(resData, ctx); | |||
if (errorInfo.success === false) { | |||
// 抛出错误到 errorHandler 中处理 | |||
const error: RequestError = new Error(errorInfo.errorMessage); | |||
error.name = 'BizError'; | |||
error.data = resData; | |||
error.info = errorInfo; | |||
error.response = res; | |||
throw error; | |||
} | |||
}); | |||
// Add user custom middlewares | |||
const customMiddlewares = requestConfig.middlewares || []; | |||
customMiddlewares.forEach((mw) => { | |||
requestMethodInstance.use(mw); | |||
}); | |||
// Add user custom interceptors | |||
const requestInterceptors = requestConfig.requestInterceptors || []; | |||
const responseInterceptors = requestConfig.responseInterceptors || []; | |||
requestInterceptors.map((ri) => { | |||
requestMethodInstance.interceptors.request.use(ri); | |||
}); | |||
responseInterceptors.map((ri) => { | |||
requestMethodInstance.interceptors.response.use(ri); | |||
}); | |||
return requestMethodInstance; | |||
}; | |||
interface RequestMethodInUmi<R = false> { | |||
<T = any>( | |||
url: string, | |||
options: RequestOptionsWithResponse & { skipErrorHandler?: boolean }, | |||
): Promise<RequestResponse<T>>; | |||
<T = any>( | |||
url: string, | |||
options: RequestOptionsWithoutResponse & { skipErrorHandler?: boolean }, | |||
): Promise<T>; | |||
<T = any>( | |||
url: string, | |||
options?: RequestOptionsInit & { skipErrorHandler?: boolean }, | |||
): R extends true ? Promise<RequestResponse<T>> : Promise<T>; | |||
} | |||
const request: RequestMethodInUmi = (url: any, options: any) => { | |||
const requestMethod = getRequestMethod(); | |||
return requestMethod(url, options); | |||
}; | |||
export { request, useRequest, UseRequestProvider }; |