feat: react-pc

This commit is contained in:
NICE CODE BY DEV 2022-05-25 12:28:35 +08:00
parent 671157aee5
commit cd2e8824e0
45 changed files with 1035 additions and 34 deletions

16
.editorconfig Executable file
View File

@ -0,0 +1,16 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

2
.env Normal file
View File

@ -0,0 +1,2 @@
BROWSER=none
ESLINT=1

4
.eslintrc.js Executable file
View File

@ -0,0 +1,4 @@
module.exports = {
extends: [require.resolve('uniubi-lint/typescript/react')],
rules: {},
};

23
.gitignore vendored Executable file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/yarn.lock
/package-lock.json
# production
/dist
# misc
.DS_Store
# umi
/src/.umi
/src/.umi-production
/src/.umi-test
/.env.local
# editor directories and files
.idea

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production
.umi-test

7
.umirc.pre.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'umi';
export default defineConfig({
define: {
'process.env.BASE_API': '/api',
},
});

10
.umirc.production.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from 'umi';
export default defineConfig({
define: {
'process.env.BASE_API': '/api',
},
extraBabelPlugins: [
['transform-remove-console', { "exclude": [ "error", "warn"] }]
],
});

7
.umirc.release.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'umi';
export default defineConfig({
define: {
'process.env.BASE_API': '/api',
},
});

7
.umirc.test.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'umi';
export default defineConfig({
define: {
'process.env.BASE_API': '/api',
},
});

107
.umirc.ts Normal file
View File

@ -0,0 +1,107 @@
import { defineConfig } from 'umi';
import Icon from './public/favicon.ico'
export default defineConfig({
favicon: Icon,
hash: true,
dva: {
immer: true,
hmr: false,
},
webpack5: {},
dynamicImport: {
loading: '@/components/PageLoading/index',
},
routes: [
{
path: '/window',
component: '@/layouts/WindowLayout',
routes: [
{
path: 'demo',
component: '@/pages/index',
name: '一级菜单',
title: '一级菜单',
icon: 'EntranceOutlined',
}
]
},
{
path: '/',
component: '@/layouts/BasicLayout',
// wrappers: ['@/wrappers/SecurityLayout'],
routes: [
{ exact: true, path: '/', redirect: '/a' },
{
path: 'a',
component: '@/pages/index',
name: '一级菜单',
title: '一级菜单',
icon: 'EntranceOutlined',
},
{
path: 'b',
name: '一级菜单',
title: '一级菜单',
icon: 'EntranceOutlined',
routes: [
{ exact: true, path: '/b', redirect: '/b/c' },
{
path: 'c',
component: '@/pages/index',
name: '二级菜单',
title: '二级菜单',
},
],
},
],
},
],
define: {
'process.env.BASE_API': '/api',
},
proxy: {
'/api': {
target: 'http://mock.com',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
theme: {
// 'primary-color': '#2228e0',
// 'info-color': '#2228e0',
// 'processing-color': '#2228e0',
// 'link-color': '#2228e0',
// 'success-color': '#46cf84',
// 'warning-color': '#ffa42e',
// 'error-color': '#fa4646',
// 'highlight-color': '#fa4646',
// 'normal-color': '#e0e0e0',
// 'heading-color': '#000000',
// 'text-color': '#4a4a4a',
// 'text-color-secondary': '#7c7c7c',
// 'disabled-color': '#c7c7c7',
// 'border-radius-base': '3px',
// 'border-color-base': '#e0e0e0',
// 'font-family':
// '"Alibaba PuHuiTi", "SourceHanSans TW", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
// 'padding-lg': '24px',
// 'padding-md': '16px',
// 'padding-sm': '12px',
// 'padding-xs': '8px',
// 'padding-xxs': '4px',
// 'margin-lg': '24px',
// 'margin-md': '16px',
// 'margin-sm': '12px',
// 'margin-xs': '8px',
// 'margin-xxs': '4px',
},
locale: {
default: 'zh-CN',
antd: true,
},
ignoreMomentLocale: true,
targets: {
ie: 10,
}
});

40
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,40 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"react",
"typescript",
"typescriptreact",
"vue"
],
"editor.formatOnSave": true,
"[typescriptreact]": {
"editor.defaultFormatter": null
},
"[javascript]": {
"editor.defaultFormatter": null
},
"[typescript]": {
"editor.defaultFormatter": null
},
"[javascriptreact]": {
"editor.defaultFormatter": null
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

0
CHANGELOG.md Normal file
View File

View File

@ -1,34 +1 @@
## 目前已有脚手架列表
| 名称 | 说明 | 技术栈 |
| ------- | --------------------------- | --------------------- |
| nextJs | 基于 React 的服务端渲染方案 | nextJs + axios + antd |
| ReactJs | 基于 React 的业务型脚手架 | umiJs + axios + antd |
| TaroJs | 基于 React 的多端适配方案 | TaroJs 全家桶 |
| Vue | 基于 Vue 的业务型脚手架 | vueJs 全家桶 |
| Gulp | 基于 Gulp 的清凉型脚手架 | -- |
## 快速上手
### 1. 安装相关依赖
```js
// 推荐
yarn global add @nicecode/cli
// or
// npm install @nicecode/cli -g
```
### 2. 运行命令
```js
// 查看脚手架版本号,是否安装成功
nice - V
```
### 3. 创建项目
```js
nice create ${项目名称}
```
# react-template

0
mock/.gitkeep Normal file
View File

55
package.json Normal file
View File

@ -0,0 +1,55 @@
{
"private": true,
"scripts": {
"start": "umi dev",
"build": "umi build",
"build:test": "cross-env UMI_ENV=test umi build",
"build:release": "cross-env UMI_ENV=release umi build",
"build:pre": "cross-env UMI_ENV=pre umi build",
"build:production": "cross-env UMI_ENV=production umi build",
"lint": "eslint 'src/**/*.{js,jsx,tsx,ts}'",
"lint:fix": "eslint 'src/**/*.{js,jsx,tsx,ts}' --fix",
"prettier": "prettier --write '**/*.{less,css,md,json}'",
"test": "umi-test",
"test:coverage": "umi-test --coverage"
},
"gitHooks": {
"pre-commit": "tsc --noEmit && lint-staged"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"npm run lint:fix",
"git add ."
],
"*.{less,css,md,json}": [
"npm run prettier",
"git add ."
]
},
"dependencies": {
"@ant-design/pro-layout": "^6.15.4",
"@materials/user-avatar": "^1.0.1",
"@ant-design/icons-react": "^2.0.10",
"antd": "^100.0.1",
"axios": "^0.19.2",
"classnames": "^2.2.6",
"js-cookie": "^2.2.1",
"poseidon-web-monitoring": "^1.1.3",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"umi": "^3.5.15"
},
"devDependencies": {
"@types/classnames": "^2.2.10",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.0.16",
"babel-plugin-transform-remove-console": "^6.9.4",
"cross-env": "^7.0.2",
"end-type-to-front-type": "^1.2.1",
"eslint": "^7.16.0",
"lint-staged": "^10.0.7",
"prettier": "^1.19.1",
"typescript": "^4.1.3",
"yorkie": "^2.0.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

5
src/app.ts Normal file
View File

@ -0,0 +1,5 @@
import { enableES5 } from 'immer';
import '@/styles/index.less';
import '@/styles/reset.less';
enableES5();

2
src/assets/data/code.ts Normal file
View File

@ -0,0 +1,2 @@
// 业务错误码
export default {};

View File

@ -0,0 +1,5 @@
export const DATE_FORMAT = 'yyyy-MM-dd';
export const TIME_FORMAT = 'HH:mm:ss';
export const DATE_TIME_FORMAT = `${DATE_FORMAT} ${TIME_FORMAT}`;

BIN
src/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,3 @@
import { PageLoading } from '@ant-design/pro-layout';
export default PageLoading;

View File

@ -0,0 +1,12 @@
import UserAvatar from '@materials/user-avatar';
const User = () => {
return (
<UserAvatar name="admin">
<div></div>
<div>退</div>
</UserAvatar>
);
};
export default User;

26
src/interfaces/base.ts Normal file
View File

@ -0,0 +1,26 @@
export interface BaseProps {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}
export interface Pagination {
total: number;
size: number;
index: number;
length: number;
beginIndex: number;
endIndex: number;
}
export interface BaseResponse {
code: string;
msg: string;
memo: string;
data: any;
page?: Pagination;
result: number;
success: boolean;
requestId: string;
linkTime: number;
}

View File

@ -0,0 +1,44 @@
import React from 'react';
import classnames from 'classnames';
import BreadcrumbItem from './BreadcrumbItem';
import './index.less';
interface BreadcrumbProps {
routes: any[];
}
const Breadcrumb: React.FC<BreadcrumbProps> = ({ routes }) => {
const validRoutes = routes.filter(item => !!item);
return (
<div className="g-basic-layout-header-breadcrumb">
<BreadcrumbItem.Root
className={classnames({
'g-basic-layout-header-breadcrumb-item-active': !validRoutes.length,
})}
/>
{validRoutes.map(
(item: any, index) =>
item && (
<span key={item.key}>
<span className="g-basic-layout-header-breadcrumb-divider">
/
</span>
<BreadcrumbItem
path={item.path}
className={classnames({
'g-basic-layout-header-breadcrumb-item-active':
index === validRoutes.length - 1,
})}
disabled={index === validRoutes.length - 1}
>
{item.name}
</BreadcrumbItem>
</span>
),
)}
</div>
);
};
export default Breadcrumb;

View File

@ -0,0 +1,48 @@
import React from 'react';
import { history } from 'umi';
import { Button } from 'antd';
import { ButtonProps } from 'antd/lib/button';
import { HomeFilled } from '@ant-design/icons-react';
import classnames from 'classnames';
import './index.less';
interface BreadcrumbItemProps extends ButtonProps {
path: string;
}
const BreadcrumbItem = ({
path,
children,
className,
...rest
}: BreadcrumbItemProps): JSX.Element => {
return (
<Button
className={classnames('g-basic-layout-header-breadcrumb-item', className)}
type="text"
onClick={() => history.push(path)}
{...rest}
>
{children}
</Button>
);
};
const RootItem: React.FC<ButtonProps> = ({ className, ...rest }) => (
<Button
className={classnames(
'g-basic-layout-header-breadcrumb-item',
'g-basic-layout-header-breadcrumb-item-root',
className,
)}
type="text"
onClick={() => history.push('/')}
{...rest}
>
<HomeFilled />
</Button>
);
BreadcrumbItem.Root = RootItem;
export default BreadcrumbItem;

View File

@ -0,0 +1,84 @@
@import "../../styles/var.less";
.g-basic-layout {
&-header {
display: flex;
&-breadcrumb {
.g-basic-layout-header-breadcrumb-item {
color: @M4;
padding: 0;
&:active {
color: @M4;
}
&:hover {
color: @S3;
background-color: @M7;
}
&:focus {
background-color: @M7;
}
&-active {
color: @M2;
&[disabled],
&[disabled]:hover,
&[disabled]:focus,
&[disabled]:active {
color: @M2;
cursor: default;
}
}
}
.g-basic-layout-header-breadcrumb-divider {
color: @M4;
margin: 0 @Sp-3;
}
}
}
.ant-pro-sider-logo img {
width: 32px;
height: 32px;
}
.ant-pro-global-header {
background-color: @M7;
box-shadow: none;
}
.ant-pro-basicLayout-content {
margin: @Sp-5 @Sp-8;
}
.ant-page-header-heading {
&-left {
margin: 0;
}
&-title {
font-size: @Fs-4;
line-height: @Lh-4;
}
}
.ant-layout {
background-color: @M7;
}
.ant-layout-sider,
.ant-menu.ant-menu-dark,
.ant-menu-dark .ant-menu-sub,
.ant-menu.ant-menu-dark .ant-menu-sub {
background-color: @S1;
}
.ant-layout-content {
min-height: calc(100vh - 72px);
}
}

View File

@ -0,0 +1,77 @@
import React from 'react';
import { history, Link } from 'umi';
import { EntranceOutlined } from '@ant-design/icons-react';
import ProLayout, { MenuDataItem } from '@ant-design/pro-layout';
import { HeaderViewProps } from '@ant-design/pro-layout/lib/Header';
import User from '@/components/User';
import Breadcrumb from './Breadcrumb';
import './index.less';
const BasicLayout: React.FC = ({ children, ...rest }) => {
const iconMap = {
EntranceOutlined: <EntranceOutlined />,
};
// 带子菜单的一级导航
const renderSubMenuItem = (itemProps: MenuDataItem): React.ReactNode => {
return (
<>
{itemProps.icon && iconMap[itemProps.icon as string]}
<span>{itemProps.name}</span>
</>
);
};
// 不带子菜单的导航
const renderMenuItem = (itemProps: MenuDataItem): React.ReactNode => {
return itemProps.isUrl || !itemProps.path ? (
<>
{itemProps.icon && iconMap[itemProps.icon as string]}
<span>{itemProps.name}</span>
</>
) : (
<Link to={itemProps.path}>
{itemProps.icon && iconMap[itemProps.icon as string]}
<span>{itemProps.name}</span>
</Link>
);
};
// 面包屑
const renderHeaderContent: (
props: HeaderViewProps,
) => React.ReactNode = props => {
// 匹配到到路由和面包屑信息
const { matchMenuKeys, breadcrumb } = props as any;
const matchRoutes = matchMenuKeys.map((item: any) => breadcrumb[item]);
return (
<div className="g-basic-layout-header">
<Breadcrumb routes={matchRoutes} />
</div>
);
};
// 用户信息
const renderUserAvatar = () => <User />;
return (
<ProLayout
className="g-basic-layout"
logo="https://fe-cloud.uni-ubi.com/image/1625038486292-logo-r.png?x-oss-process=img/q/80"
title="Uni-Ubi"
siderWidth={180}
fixedHeader
fixSiderbar
onMenuHeaderClick={() => history.push('/')}
subMenuItemRender={renderSubMenuItem}
menuItemRender={renderMenuItem}
headerContentRender={renderHeaderContent}
rightContentRender={renderUserAvatar}
{...rest}
>
{children}
</ProLayout>
);
};
export default BasicLayout;

View File

@ -0,0 +1,5 @@
import React from 'react';
const BlankLayout: React.FC = ({ children }) => <>{children}</>;
export default BlankLayout;

View File

@ -0,0 +1,33 @@
import { history } from 'umi';
import { Image, Divider } from 'antd';
import User from '@/components/User';
import './index.less';
const Header = () => {
return (
<div className="g-window-layout-header">
<div
className="g-window-layout-header-logo"
onClick={() => history.push('/')}
>
<Image
src={require('../../assets/images/logo.png')}
alt="logo"
preview={false}
width={36}
height={24}
/>
<Divider
type="vertical"
className="g-window-layout-header-logo-divider"
/>
<span className="g-window-layout-header-logo-text">react-template</span>
</div>
<div className="g-window-layout-header-actions">
<User />
</div>
</div>
);
};
export default Header;

View File

@ -0,0 +1,65 @@
@import "../../styles/var.less";
@headerHeader: 64px;
.g-window-layout {
display: flex;
flex-direction: column;
min-width: 1200px;
min-height: 100vh;
background-color: @M7;
&-header {
display: flex;
justify-content: space-between;
align-items: center;
height: @headerHeader;
width: 1200px;
margin: 0 auto;
&-wrapper {
background-color: #000000;
}
&-logo {
display: flex;
justify-content: center;
align-items: center;
color: #ffffff;
cursor: pointer;
&-divider.ant-divider {
border-color: #ffffff;
height: 12px;
margin: 0 @Sp-5;
}
&-text {
font-size: @Fs-3;
}
}
&-actions {
color: #fff;
}
}
&-content {
flex: 1;
width: 1200px;
min-height: 600px;
margin: 0 auto;
padding: @Sp-8;
background-color: #ffffff;
box-shadow: @Sh-2;
border-radius: @Ra-2;
&-wrapper {
display: flex;
flex-direction: column;
flex: 1;
min-height: 500px;
padding: @Sp-8 0;
}
}
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import Header from './Header';
import './index.less';
const WindowLayout: React.FC = ({ children }) => {
return (
<div className="g-window-layout">
<div className="g-window-layout-header-wrapper">
<Header />
</div>
<div className="g-window-layout-content-wrapper">
<div className="g-window-layout-content">{children}</div>
</div>
</div>
);
};
export default WindowLayout;

1
src/locales/zh-CN.ts Normal file
View File

@ -0,0 +1 @@
export default {};

5
src/models/connect.ts Normal file
View File

@ -0,0 +1,5 @@
import { UserModelState } from './user';
export interface ConnectState {
user: UserModelState;
}

56
src/models/user.ts Normal file
View File

@ -0,0 +1,56 @@
import { ImmerReducer, Effect } from 'umi';
import { SubscriptionsMapObject } from 'dva';
import { getUserInfo } from '@/services/user';
export interface UserInfo {
name?: string;
}
export interface UserModelState {
info: UserInfo;
token: string;
}
export interface UserModelType {
namespace: 'user';
state: UserModelState;
effects: {
fetchUser: Effect;
};
reducers: {
setUserInfo: ImmerReducer<UserModelState>;
setToken: ImmerReducer<UserModelState>;
};
subscriptions: SubscriptionsMapObject;
}
const UserModel: UserModelType = {
namespace: 'user',
state: {
info: {},
token: '',
},
effects: {
// 获取用户信息
*fetchUser(_, { call, put }) {
const response = yield call(getUserInfo);
yield put({
type: 'setUserInfo',
payload: response,
});
},
},
reducers: {
// 修改用户信息
setUserInfo(state, { payload }) {
state.info = payload;
},
// 修改 token
setToken(state, { payload }) {
state.token = payload || '';
},
},
subscriptions: {},
};
export default UserModel;

3
src/pages/index.less Normal file
View File

@ -0,0 +1,3 @@
.title {
background: rgb(121, 242, 157);
}

9
src/pages/index.tsx Normal file
View File

@ -0,0 +1,9 @@
import styles from './index.less';
export default () => {
return (
<div>
<h1 className={styles.title}>index</h1>
</div>
);
};

7
src/services/user.ts Normal file
View File

@ -0,0 +1,7 @@
import request from '@/utils/request';
export async function getUserInfo(): Promise<any> {
return request('/developer/info', {
method: 'get',
});
}

1
src/styles/index.less Normal file
View File

@ -0,0 +1 @@
@import "./var.less";

15
src/styles/reset.less Normal file
View File

@ -0,0 +1,15 @@
@import "var.less";
.ant-picker {
width: 100%;
}
.ant-pagination {
display: flex;
justify-content: flex-end;
width: 100%;
&-total-text {
flex: 1;
}
}

90
src/styles/var.less Normal file
View File

@ -0,0 +1,90 @@
// 颜色
@S1: #25282a;
@S2: #1c21b8;
@S3: #2228e0;
@S4: #4046ff;
@S5: #9ca8ff;
@S6: #c9d2ff;
@M1: #000000;
@M2: #4a4a4a;
@M3: #7c7c7c;
@M4: #959595;
@M5: #c7c7c7;
@M6: #e0e0e0;
@M7: #f2f2f2;
@M8: #f9f9f9;
@M9: #ffffff;
@D1: #fac105;
@D2: #ffab24;
@D3: #ff6952;
@D4: #ff3c73;
@D5: #ee62d5;
@D6: #8475ff;
@D7: #4f73ff;
@D8: #148aff;
@D9: #0cc0c9;
@D10: #0cc991;
@D11: #83d615;
@D1-1: #fef8e5;
@D2-1: #fff6e9;
@D3-1: #fff0ed;
@D4-1: #ffebf1;
@D5-1: #fdeffa;
@D6-1: #f2f1ff;
@D7-1: #edf1ff;
@D8-1: #e7f3ff;
@D9-1: #e6f8f9;
@D10-1: #e6f9f3;
@D11-1: #f2fae7;
@F-info: #4766ff;
@F-success: #46cf84;
@F-warning: #ffa42e;
@F-red: #fa4646;
/* 字体 */
@Fs-1: 12px;
@Fs-2: 14px;
@Fs-3: 16px;
@Fs-4: 18px;
@Fs-5: 20px;
@Fs-6: 22px;
@Fs-7: 24px;
@Fs-8: 28px;
@Fs-9: 32px;
/* 行高 */
@Lh-1: 20px;
@Lh-2: 22px;
@Lh-3: 24px;
@Lh-4: 26px;
@Lh-5: 28px;
@Lh-6: 30px;
@Lh-7: 32px;
@Lh-8: 36px;
@Lh-9: 42px;
// 间距
@Sp-1: 2px;
@Sp-2: 4px;
@Sp-3: 8px;
@Sp-4: 10px;
@Sp-5: 12px;
@Sp-6: 16px;
@Sp-7: 20px;
@Sp-8: 24px;
@Sp-9: 30px;
@Sp-10: 32px;
@Sp-11: 40px;
// 阴影
@Sh-1: 0 0 8px 0 rgba(0, 0, 0, 0.05);
@Sh-2: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
@Sh-3: 0 0 12px 0 rgba(0, 0, 0, 0.1);
@Sh-4: -4px 0 20px 0 rgba(0, 0, 0, 0.1);
@Sh-5: 0 2px 20px 0 rgba(0, 0, 0, 0.3);
// 圆角
@Ra-1: 2px;
@Ra-2: 3px;
@Ra-3: 6px;
@Ra-4: 10px;

0
src/utils/index.ts Normal file
View File

66
src/utils/request.ts Normal file
View File

@ -0,0 +1,66 @@
import axios from 'axios';
import { message } from 'antd';
import CodeMsg from '@/assets/data/code';
import { BaseResponse } from '@/interfaces/base';
export const DEFAULT_TIP_MESSAGE = '请求失败,请刷新重试';
/**
*
* @param data {Object}
*/
export function handleError(data: BaseResponse): void {
const msg = CodeMsg[data.code] || data.msg || DEFAULT_TIP_MESSAGE;
message.error(msg);
}
// create an axios instance
const service = axios.create({
baseURL: process.env.BASE_API, // api的base_url
// timeout: 5000, // request timeout
});
// request interceptor
service.interceptors.request.use(
config => {
// 防止 GET 请求缓存GET
if (config.method === 'get') {
const t = new Date().getTime();
config.params = config.params ? { ...config.params, t } : { t };
}
return config;
},
error => {
// Do something with request error
if (error.status === '504') {
message.error('网关超时,请重试!');
} else {
message.error(`网络异常[-${error.status}]`);
console.log(error); // for debug
}
Promise.reject(error);
},
);
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data;
if (!res.success) {
if (res.code === '1007') {
// 登录失效
window.location.href = '/';
return;
}
handleError(res);
}
return res;
},
error => {
handleError(error);
console.log(`err${error}`); // for debug
return Promise.reject(error);
},
);
export default service;

View File

@ -0,0 +1,29 @@
import React, { useEffect } from 'react';
import { Redirect, connect, Dispatch, useLocation } from 'umi';
import { ConnectState } from '@/models/connect';
export interface SecurityWrapperProps {
token: string;
dispatch: Dispatch;
}
const SecurityWrapper: React.FC<SecurityWrapperProps> = ({
token,
children,
}) => {
useEffect(() => {
// dispatch && dispatch({ type: 'user/fetchUser' });
}, []);
const isLogin = !!token;
const { pathname } = useLocation();
if (!isLogin && pathname !== '/') {
return <Redirect to="/" />;
}
return <>{children}</>;
};
export default connect(({ user }: ConnectState) => ({
token: user.token,
}))(SecurityWrapper);

34
tsconfig.json Normal file
View File

@ -0,0 +1,34 @@
{
"compilerOptions": {
"outDir": "build/dist",
"module": "esnext",
"target": "esnext",
"lib": ["esnext", "dom"],
"sourceMap": true,
"baseUrl": ".",
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"allowJs": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"strict": true,
"paths": {
"@/*": ["./src/*"],
"@@/*": ["./src/.umi/*"]
}
},
"exclude": [
"node_modules",
"build",
"dist",
"scripts",
"src/.umi/*",
"webpack",
"jest"
]
}

5
typings.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.css';
declare module '*.less';
declare module '*.png';
declare module '*.jpeg';
declare module '*.jpg';