@umijs/plugins 4.0.0-rc.2 → 4.0.0-rc.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/access.js CHANGED
@@ -1,18 +1,12 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
4
  };
11
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
12
7
  const path_1 = require("path");
13
8
  const withTmpPath_1 = require("./utils/withTmpPath");
14
9
  exports.default = (api) => {
15
- // TODO: route access
16
10
  api.describe({
17
11
  config: {
18
12
  schema(joi) {
@@ -21,19 +15,29 @@ exports.default = (api) => {
21
15
  },
22
16
  enableBy: api.EnableBy.config,
23
17
  });
24
- api.onGenerateFiles(() => __awaiter(void 0, void 0, void 0, function* () {
18
+ api.onGenerateFiles(async () => {
19
+ // allow enable access without access file
20
+ const hasAccessFile = ['js', 'jsx', 'ts', 'tsx'].some((ext) => fs_1.default.existsSync((0, path_1.join)(api.paths.absSrcPath, `access.${ext}`)));
25
21
  // runtime.tsx
26
22
  api.writeTmpFile({
27
23
  path: 'runtime.tsx',
28
24
  content: `
29
- import React from 'react';
30
- import accessFactory from '@/access';
25
+ import React from 'react';${hasAccessFile
26
+ ? `
27
+ import accessFactory from '@/access'
31
28
  import { useModel } from '@@/plugin-model';
29
+ `
30
+ : ''}
32
31
  import { AccessContext } from './context';
33
32
 
34
- function Provider(props) {
33
+ function Provider(props) {${hasAccessFile
34
+ ? `
35
35
  const { initialState } = useModel('@@initialState');
36
36
  const access = React.useMemo(() => accessFactory(initialState), [initialState]);
37
+ `
38
+ : `
39
+ const access = {};
40
+ `}
37
41
  return (
38
42
  <AccessContext.Provider value={access}>
39
43
  { props.children }
@@ -46,16 +50,74 @@ export function accessProvider(container) {
46
50
  }
47
51
  `,
48
52
  });
49
- // index.ts
53
+ // index.tsx
50
54
  api.writeTmpFile({
51
- path: 'index.ts',
55
+ path: 'index.tsx',
52
56
  content: `
53
57
  import React from 'react';
54
58
  import { AccessContext } from './context';
59
+ import type { IRoute } from 'umi';
55
60
 
56
61
  export const useAccess = () => {
57
62
  return React.useContext(AccessContext);
58
63
  };
64
+
65
+ export interface AccessProps {
66
+ accessible: boolean;
67
+ fallback?: React.ReactNode;
68
+ }
69
+ export const Access: React.FC<AccessProps> = (props) => {
70
+ if (process.env.NODE_ENV === 'development' && typeof props.accessible !== 'boolean') {
71
+ throw new Error('[access] the \`accessible\` property on <Access /> should be a boolean');
72
+ }
73
+
74
+ return <>{ props.accessible ? props.children : props.fallback }</>;
75
+ };
76
+
77
+ export const useAccessMarkedRoutes = (routes: IRoute[]) => {
78
+ const access = useAccess();
79
+ const markdedRoutes: IRoute[] = React.useMemo(() => {
80
+ const process = (route, parentAccessCode) => {
81
+ const accessCode = route.access || parentAccessCode;
82
+
83
+ // set default status
84
+ route.unaccessible = ${api.config.access.strictMode ? 'true' : 'false'};
85
+
86
+ // check access code
87
+ if (typeof accessCode === 'string') {
88
+ const detector = access[route.access];
89
+
90
+ if (typeof detector === 'function') {
91
+ route.unaccessible = !detector(route);
92
+ } else if (typeof detector === 'boolean') {
93
+ route.unaccessible = !detector;
94
+ } else if (typeof detector === 'undefined') {
95
+ route.unaccessible = true;
96
+ }
97
+ }
98
+
99
+ // check children access code
100
+ if (route.routes) {
101
+ const isNoAccessibleChild = !route.routes.reduce((hasAccessibleChild, child) => {
102
+ process(child, accessCode);
103
+
104
+ return hasAccessibleChild || !child.unaccessible;
105
+ }, false);
106
+
107
+ // make sure parent route is unaccessible if all children are unaccessible
108
+ if (isNoAccessibleChild) {
109
+ route.unaccessible = true;
110
+ }
111
+ }
112
+
113
+ return route;
114
+ }
115
+
116
+ return routes.map(route => process(route));
117
+ }, [routes.length]);
118
+
119
+ return markdedRoutes;
120
+ }
59
121
  `,
60
122
  });
61
123
  // context.ts
@@ -66,7 +128,7 @@ import React from 'react';
66
128
  export const AccessContext = React.createContext<any>(null);
67
129
  `,
68
130
  });
69
- }));
131
+ });
70
132
  api.addRuntimePlugin(() => {
71
133
  return [(0, withTmpPath_1.withTmpPath)({ api, path: 'runtime.tsx' })];
72
134
  });
package/dist/antd.js CHANGED
@@ -5,11 +5,16 @@ const plugin_utils_1 = require("umi/plugin-utils");
5
5
  const resolveProjectDep_1 = require("./utils/resolveProjectDep");
6
6
  const withTmpPath_1 = require("./utils/withTmpPath");
7
7
  exports.default = (api) => {
8
- const pkgPath = (0, resolveProjectDep_1.resolveProjectDep)({
9
- pkg: api.pkg,
10
- cwd: api.cwd,
11
- dep: 'antd',
12
- }) || (0, path_1.dirname)(require.resolve('antd/package.json'));
8
+ let pkgPath;
9
+ try {
10
+ pkgPath =
11
+ (0, resolveProjectDep_1.resolveProjectDep)({
12
+ pkg: api.pkg,
13
+ cwd: api.cwd,
14
+ dep: 'antd',
15
+ }) || (0, path_1.dirname)(require.resolve('antd/package.json'));
16
+ }
17
+ catch (e) { }
13
18
  api.describe({
14
19
  config: {
15
20
  schema(Joi) {
@@ -25,9 +30,20 @@ exports.default = (api) => {
25
30
  });
26
31
  },
27
32
  },
28
- enableBy: api.EnableBy.config,
33
+ enableBy({ userConfig }) {
34
+ // 由于本插件有 api.modifyConfig 的调用,以及 Umi 框架的限制
35
+ // 在其他插件中通过 api.modifyDefaultConfig 设置 antd 并不能让 api.modifyConfig 生效
36
+ // 所以这里通过环境变量来判断是否启用
37
+ return process.env.UMI_PLUGIN_ANTD_ENABLE || userConfig.antd;
38
+ },
29
39
  });
40
+ function checkPkgPath() {
41
+ if (!pkgPath) {
42
+ throw new Error(`Can't find antd package. Please install antd first.`);
43
+ }
44
+ }
30
45
  api.modifyAppData((memo) => {
46
+ checkPkgPath();
31
47
  const version = require(`${pkgPath}/package.json`).version;
32
48
  memo.antd = {
33
49
  pkgPath,
@@ -36,17 +52,32 @@ exports.default = (api) => {
36
52
  return memo;
37
53
  });
38
54
  api.modifyConfig((memo) => {
55
+ checkPkgPath();
56
+ const antd = memo.antd || {};
57
+ // defaultConfig 的取值在 config 之后,所以改用环境变量传默认值
58
+ if (process.env.UMI_PLUGIN_ANTD_ENABLE) {
59
+ const { defaultConfig } = JSON.parse(process.env.UMI_PLUGIN_ANTD_ENABLE);
60
+ Object.assign(antd, defaultConfig);
61
+ }
39
62
  // antd import
40
63
  memo.alias.antd = pkgPath;
41
64
  // moment > dayjs
42
- if (memo.antd.dayjs) {
65
+ if (antd.dayjs) {
43
66
  memo.alias.moment = (0, path_1.dirname)(require.resolve('dayjs/package.json'));
44
67
  }
45
68
  // dark mode & compact mode
46
- if (memo.antd.dark || memo.antd.compact) {
69
+ if (antd.dark || antd.compact) {
47
70
  const { getThemeVariables } = require('antd/dist/theme');
48
- memo.theme = Object.assign(Object.assign({}, getThemeVariables(memo.antd)), memo.theme);
71
+ memo.theme = {
72
+ ...getThemeVariables(antd),
73
+ ...memo.theme,
74
+ };
49
75
  }
76
+ // antd theme
77
+ memo.theme = {
78
+ 'root-entry-name': 'default',
79
+ ...memo.theme,
80
+ };
50
81
  return memo;
51
82
  });
52
83
  // babel-plugin-import
@@ -61,17 +92,19 @@ exports.default = (api) => {
61
92
  libraryDirectory: 'es',
62
93
  style: style === 'less' ? true : 'css',
63
94
  },
95
+ 'antd',
64
96
  ],
65
97
  ]
66
98
  : [];
67
99
  });
68
100
  // antd config provider
69
101
  api.onGenerateFiles(() => {
70
- if (!api.config.antd.config)
102
+ if (!api.config.antd.configProvider)
71
103
  return;
72
104
  api.writeTmpFile({
73
105
  path: `runtime.tsx`,
74
106
  content: plugin_utils_1.Mustache.render(`
107
+ import React from 'react';
75
108
  import { ConfigProvider, Modal, message, notification } from 'antd';
76
109
 
77
110
  export function rootContainer(container) {
@@ -90,12 +123,12 @@ export function rootContainer(container) {
90
123
  return <ConfigProvider {...finalConfig}>{container}</ConfigProvider>;
91
124
  }
92
125
  `.trim(), {
93
- config: JSON.stringify(api.config.antd.config),
126
+ config: JSON.stringify(api.config.antd.configProvider),
94
127
  }),
95
128
  });
96
129
  });
97
130
  api.addRuntimePlugin(() => {
98
- return api.config.antd.config
131
+ return api.config.antd.configProvider
99
132
  ? [(0, withTmpPath_1.withTmpPath)({ api, path: 'runtime.tsx' })]
100
133
  : [];
101
134
  });
package/dist/dva.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -21,6 +25,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
21
25
  Object.defineProperty(exports, "__esModule", { value: true });
22
26
  exports.getAllModels = exports.getModelUtil = void 0;
23
27
  const t = __importStar(require("@umijs/bundler-utils/compiled/babel/types"));
28
+ const utils_1 = require("@umijs/utils");
24
29
  const path_1 = require("path");
25
30
  const plugin_utils_1 = require("umi/plugin-utils");
26
31
  const modelUtils_1 = require("./utils/modelUtils");
@@ -32,6 +37,7 @@ exports.default = (api) => {
32
37
  schema(Joi) {
33
38
  return Joi.object({
34
39
  extraModels: Joi.array().items(Joi.string()),
40
+ immer: Joi.object(),
35
41
  });
36
42
  },
37
43
  },
@@ -51,6 +57,7 @@ exports.default = (api) => {
51
57
  return memo;
52
58
  });
53
59
  api.onGenerateFiles((args) => {
60
+ var _a, _b, _c, _d, _e, _f;
54
61
  const models = args.isFirstTime
55
62
  ? api.appData.pluginDva.models
56
63
  : getAllModels(api);
@@ -66,16 +73,29 @@ exports.default = (api) => {
66
73
  // It's faked dva
67
74
  // aliased to @umijs/plugins/templates/dva
68
75
  import { create, Provider } from 'dva';
76
+ import createLoading from '${(0, utils_1.winPath)(require.resolve('dva-loading'))}';
77
+ ${((_a = api.config.dva) === null || _a === void 0 ? void 0 : _a.immer)
78
+ ? `
79
+ import dvaImmer, { enableES5, enableAllPlugins } from '${(0, utils_1.winPath)(require.resolve('dva-immer'))}';
80
+ `
81
+ : ''}
69
82
  import React, { useRef } from 'react';
70
- import { history } from 'umi';
83
+ import { history, ApplyPluginsType, useAppData } from 'umi';
71
84
  import { models } from './models';
72
85
 
73
86
  export function RootContainer(props: any) {
87
+ const { pluginManager } = useAppData();
74
88
  const app = useRef<any>();
89
+ const runtimeDva = pluginManager.applyPlugins({
90
+ key: 'dva',
91
+ type: ApplyPluginsType.modify,
92
+ initialValue: {},
93
+ });
75
94
  if (!app.current) {
76
95
  app.current = create(
77
96
  {
78
97
  history,
98
+ ...(runtimeDva.config || {}),
79
99
  },
80
100
  {
81
101
  initialReducer: {},
@@ -87,6 +107,10 @@ export function RootContainer(props: any) {
87
107
  },
88
108
  },
89
109
  );
110
+ app.current.use(createLoading());
111
+ ${((_b = api.config.dva) === null || _b === void 0 ? void 0 : _b.immer) ? `app.current.use(dvaImmer());` : ''}
112
+ ${((_d = (_c = api.config.dva) === null || _c === void 0 ? void 0 : _c.immer) === null || _d === void 0 ? void 0 : _d.enableES5) ? `enableES5();` : ''}
113
+ ${((_f = (_e = api.config.dva) === null || _e === void 0 ? void 0 : _e.immer) === null || _f === void 0 ? void 0 : _f.enableAllPlugins) ? `enableAllPlugins();` : ''}
90
114
  for (const id of Object.keys(models)) {
91
115
  app.current.model(models[id].model);
92
116
  }
@@ -122,6 +146,7 @@ export { connect, useDispatch, useStore, useSelector } from 'dva';`,
122
146
  api.addRuntimePlugin(() => {
123
147
  return [(0, withTmpPath_1.withTmpPath)({ api, path: 'runtime.tsx' })];
124
148
  });
149
+ api.addRuntimePluginKey(() => ['dva']);
125
150
  // dva list model
126
151
  api.registerCommand({
127
152
  name: 'dva',
@@ -30,8 +30,8 @@ exports.default = (api) => {
30
30
  import React from 'react';
31
31
  import { useModel } from '@@/plugin-model';
32
32
  ${loading
33
- ? `import Loading from ${loading}`
34
- : `function Loading() { return <div>loading</div>; }`}
33
+ ? `import Loading from '${loading}'`
34
+ : `function Loading() { return <div />; }`}
35
35
  export default function InitialStateProvider(props: any) {
36
36
  const appLoaded = React.useRef(false);
37
37
  const { loading = false } = useModel("@@initialState") || {};
@@ -107,7 +107,7 @@ export default () => ({ loading: false, refresh: () => {} })
107
107
  content: `
108
108
  import React from 'react';
109
109
  import Provider from './Provider';
110
- export function innerProvider(container) {
110
+ export function dataflowProvider(container) {
111
111
  return <Provider>{ container }</Provider>;
112
112
  }
113
113
  `,
package/dist/layout.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -18,15 +22,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
18
22
  __setModuleDefault(result, mod);
19
23
  return result;
20
24
  };
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
25
  Object.defineProperty(exports, "__esModule", { value: true });
25
26
  const allIcons = __importStar(require("@ant-design/icons"));
26
- const assert_1 = __importDefault(require("assert"));
27
+ const fs_1 = require("fs");
27
28
  const path_1 = require("path");
28
29
  const plugin_utils_1 = require("umi/plugin-utils");
29
- const resolveProjectDep_1 = require("./utils/resolveProjectDep");
30
30
  const withTmpPath_1 = require("./utils/withTmpPath");
31
31
  exports.default = (api) => {
32
32
  api.describe({
@@ -39,11 +39,28 @@ exports.default = (api) => {
39
39
  },
40
40
  enableBy: api.EnableBy.config,
41
41
  });
42
- const pkgPath = (0, resolveProjectDep_1.resolveProjectDep)({
43
- pkg: api.pkg,
44
- cwd: api.cwd,
45
- dep: '@ant-design/pro-layout',
46
- }) || (0, path_1.dirname)(require.resolve('@ant-design/pro-layout/package.json'));
42
+ /**
43
+ * 优先去找 '@alipay/tech-ui',保证稳定性
44
+ */
45
+ const depList = ['@alipay/tech-ui', '@ant-design/pro-layout'];
46
+ const pkgHasDep = depList.find((dep) => {
47
+ var _a, _b;
48
+ const { pkg } = api;
49
+ if (((_a = pkg.dependencies) === null || _a === void 0 ? void 0 : _a[dep]) || ((_b = pkg.devDependencies) === null || _b === void 0 ? void 0 : _b[dep])) {
50
+ return true;
51
+ }
52
+ return false;
53
+ });
54
+ const getPkgPath = () => {
55
+ // 如果 layout 和 techui至少有一个在,找到他们的地址
56
+ if (pkgHasDep &&
57
+ (0, fs_1.existsSync)((0, path_1.join)(api.cwd, 'node_modules', pkgHasDep, 'package.json'))) {
58
+ return (0, path_1.join)(api.cwd, 'node_modules', pkgHasDep);
59
+ }
60
+ // 如果项目中没有去找插件以来的
61
+ return (0, path_1.dirname)(require.resolve('@ant-design/pro-layout/package.json'));
62
+ };
63
+ const pkgPath = getPkgPath();
47
64
  api.modifyAppData((memo) => {
48
65
  const version = require(`${pkgPath}/package.json`).version;
49
66
  memo.pluginLayout = {
@@ -53,8 +70,11 @@ exports.default = (api) => {
53
70
  return memo;
54
71
  });
55
72
  api.modifyConfig((memo) => {
56
- // import from @ant-design/pro-layout
57
- memo.alias['@ant-design/pro-layout'] = pkgPath;
73
+ // 只在没有自行依赖 @ant-design/pro-layout 或 @alipay/tech-ui 时
74
+ // 才使用插件中提供的 @ant-design/pro-layout
75
+ if (!pkgHasDep) {
76
+ memo.alias['@ant-design/pro-layout'] = pkgPath;
77
+ }
58
78
  return memo;
59
79
  });
60
80
  api.onGenerateFiles(() => {
@@ -63,17 +83,23 @@ exports.default = (api) => {
63
83
  api.writeTmpFile({
64
84
  path: 'Layout.tsx',
65
85
  content: `
66
- import { Link, useLocation, useNavigate, Outlet, useAppData, useRouteContext } from 'umi';
67
- import ProLayout, {
68
- PageLoading,
69
- } from '@ant-design/pro-layout';
86
+ import { Link, useLocation, useNavigate, Outlet, useAppData, useRouteData, matchRoutes } from 'umi';
87
+ import { useMemo } from 'react';
88
+ import {
89
+ ProLayout,
90
+ } from "${pkgHasDep || '@ant-design/pro-layout'}";
70
91
  import './Layout.less';
71
92
  import Logo from './Logo';
93
+ import Exception from './Exception';
72
94
  import { getRightRenderContent } from './rightRender';
73
95
  ${hasInitialStatePlugin
74
96
  ? `import { useModel } from '@@/plugin-model';`
75
97
  : 'const useModel = null;'}
76
-
98
+ ${api.config.access
99
+ ? `
100
+ import { useAccessMarkedRoutes } from '@@/plugin-access';
101
+ `.trim()
102
+ : 'const useAccessMarkedRoutes = (r) => r;'}
77
103
  ${api.config.locale
78
104
  ? `
79
105
  import { useIntl } from '@@/plugin-locale';
@@ -81,7 +107,7 @@ import { useIntl } from '@@/plugin-locale';
81
107
  : ''}
82
108
 
83
109
 
84
- export default () => {
110
+ export default (props: any) => {
85
111
  const location = useLocation();
86
112
  const navigate = useNavigate();
87
113
  const { clientRoutes, pluginManager } = useAppData();
@@ -104,9 +130,8 @@ const { formatMessage } = useIntl();
104
130
  ...initialInfo
105
131
  },
106
132
  });
107
- const route = clientRoutes.filter(r => {
108
- return r.id === 'ant-design-pro-layout';
109
- })[0];
133
+ const matchedRoute = useMemo(() => matchRoutes(clientRoutes, location.pathname).pop()?.route, [location.pathname]);
134
+ const [route] = useAccessMarkedRoutes(clientRoutes.filter(({ id }) => id === 'ant-design-pro-layout'));
110
135
  return (
111
136
  <ProLayout
112
137
  route={route}
@@ -128,7 +153,8 @@ const { formatMessage } = useIntl();
128
153
  }
129
154
  if (menuItemProps.path && location.pathname !== menuItemProps.path) {
130
155
  return (
131
- <Link to={menuItemProps.path} target={menuItemProps.target}>
156
+ // handle wildcard route path, for example /slave/* from qiankun
157
+ <Link to={menuItemProps.path.replace('/*', '')} target={menuItemProps.target}>
132
158
  {defaultDom}
133
159
  </Link>
134
160
  );
@@ -162,7 +188,16 @@ const { formatMessage } = useIntl();
162
188
  })
163
189
  }
164
190
  >
165
- <Outlet />
191
+ <Exception
192
+ route={matchedRoute}
193
+ notFound={runtimeConfig.notFound}
194
+ noAccessible={runtimeConfig.noAccessible}
195
+ >
196
+ {runtimeConfig.childrenRender
197
+ ? runtimeConfig.childrenRender(<Outlet />, props)
198
+ : <Outlet />
199
+ }
200
+ </Exception>
166
201
  </ProLayout>
167
202
  );
168
203
  }
@@ -172,9 +207,6 @@ const { formatMessage } = useIntl();
172
207
  const { icon } = api.appData.routes[id];
173
208
  if (icon) {
174
209
  const upperIcon = plugin_utils_1.lodash.upperFirst(plugin_utils_1.lodash.camelCase(icon));
175
- (0, assert_1.default)(
176
- // @ts-ignore
177
- allIcons[upperIcon] || allIcons[`${upperIcon}Outlined`], `Icon ${upperIcon} is not found`);
178
210
  // @ts-ignore
179
211
  if (allIcons[upperIcon]) {
180
212
  memo[upperIcon] = true;
@@ -219,7 +251,9 @@ export function patchRoutes({ routes }) {
219
251
  const { icon } = routes[key];
220
252
  if (icon && typeof icon === 'string') {
221
253
  const upperIcon = formatIcon(icon);
222
- routes[key].icon = React.createElement(icons[upperIcon] || icons[upperIcon + 'Outlined']);
254
+ if (icons[upperIcon] || icons[upperIcon + 'Outlined']) {
255
+ routes[key].icon = React.createElement(icons[upperIcon] || icons[upperIcon + 'Outlined']);
256
+ }
223
257
  }
224
258
  });
225
259
  }
@@ -464,6 +498,43 @@ const LogoIcon: React.FC = () => {
464
498
  export default LogoIcon;
465
499
  `,
466
500
  });
501
+ api.writeTmpFile({
502
+ path: 'Exception.tsx',
503
+ content: `
504
+ import React from 'react';
505
+ import { history, type IRoute } from 'umi';
506
+ import { Result, Button } from 'antd';
507
+
508
+ const Exception: React.FC<{
509
+ children: React.ReactNode;
510
+ route?: IRoute;
511
+ notFound?: React.ReactNode;
512
+ noAccessible?: React.ReactNode;
513
+ }> = (props) => (
514
+ // render custom 404
515
+ (!props.route && props.notFound) ||
516
+ // render custom 403
517
+ (props.route.unaccessible && props.noAccessible) ||
518
+ // render default exception
519
+ ((!props.route || props.route.unaccessible) && (
520
+ <Result
521
+ status={props.route ? '403' : '404'}
522
+ title={props.route ? '403' : '404'}
523
+ subTitle={props.route ? '抱歉,你无权访问该页面' : '抱歉,你访问的页面不存在'}
524
+ extra={
525
+ <Button type="primary" onClick={() => history.push('/')}>
526
+ 返回首页
527
+ </Button>
528
+ }
529
+ />
530
+ )) ||
531
+ // normal render
532
+ props.children
533
+ );
534
+
535
+ export default Exception;
536
+ `,
537
+ });
467
538
  });
468
539
  api.addLayouts(() => {
469
540
  return [