前言:
后台搭建完以后开始搭建前端,使用create-react-app搭建项目非常方便。
前端主要是如何向后台请求数据,以及如何使用redux管理state,路由的配置.
前端github地址: https://github.com/www2388258980/rty-web
后台github地址: https://github.com/www2388258980/rty-service
项目访问地址: http://106.13.61.216:5000/ 账号/密码: root/root
准备工作:
1.需要安装node.js;
2.安装create-react-app脚手架;
准备知识:
1.React
2.Typescript
3.Redux
4.React-Router
5.adtd
以上点击都可以打开对应的文档.
正文:
1.使用 TypeScript 启动新的 Create React App 项目:
命令行:
npx create-react-app 项目名 –typescript
or
yarn create react-app 项目名 –typescript
然后运行:
cd 项目名
npm start
or
yarn start
此时浏览器会访问 http://localhost:3000/ ,看到 Welcome to React 的界面就算成功了。
按需引入antd组件库
[1]: 此时我们需要对 create-react-app 的默认配置进行自定义,这里我们使用 react-app-rewired (一个对 create-react-app 进行自定义配置的社区解决方案)。
cd 项目名
yarn add react
-app
-rewired customize
-cra
接着修改package.json
1
2
3
4
5
6 1"scripts": {
2 "start": "react-app-rewired start",
3 "build": "react-app-rewired build",
4 "test": "react-app-rewired test",
5}
6
2: 使用 babel-plugin-import,'是一个用于按需加载组件代码和样式的 babel 插件,现在我们尝试安装它并修改 config-overrides.js 文件。'
yarn add babel-plugin-import
然后在项目根目录创建一个 config-overrides.js 用于修改默认配置.
1
2
3
4
5
6
7
8
9
10 1const {override, fixBabelImports} = require('customize-cra');
2
3module.exports = override(
4 fixBabelImports('import', {
5 libraryName: 'antd',
6 libraryDirectory: 'es',
7 style: 'css',
8 }),
9);
10
3: 引入antd:
yarn add antd
- 封装ajax向后台请求数据:
[1]: > yarn add
isomorphic-fetch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 1import fetch from 'isomorphic-fetch';//考虑使用fetch
2
3class _Api {
4 constructor(opts) {
5 this.opts = opts || {};
6
7 if (!this.opts.baseURI)
8 throw new Error('baseURI option is required');
9
10 }
11
12 request = (path, method = 'post', params, data, callback, urlType) => {
13 return new Promise((resolve, reject) => {
14 let url = this.opts.baseURI + path;
15 if (urlType) {
16 url = this.opts[urlType + 'BaseURI'] + path;
17 }
18 if (path.indexOf('http://') == 0) {
19 url = path;
20 }
21
22 const opts = {
23 method: method,
24 };
25 if (this.opts.headers) {
26 opts.headers = this.opts.headers;
27 }
28
29 if (data) {
30 opts.headers['Content-Type'] = 'application/json; charset=utf-8';
31 opts.body = JSON.stringify(data);
32 }
33 if (params) {
34 opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
35 let queryString = '';
36 for (const param in params) {
37 const value = params[param];
38 if (value == null || value == undefined) {
39 continue;
40 }
41 queryString += (param + '=' + value + '&');
42 }
43 if (opts.method == 'get') {
44 if (url.indexOf('?') != -1) {
45 url += ('&' + queryString);
46 } else {
47 url += ('?' + queryString);
48 }
49 } else {
50 opts.body = queryString;
51 }
52 }
53 fetch(url, opts).then(function (response) {
54 if (response.status >= 400) {
55 throw new Error("Bad response from server");
56 }
57 return response.json().then(function (json) {
58 // callback();
59 return resolve(json);
60 })
61
62 }).catch(function (error) {
63 console.log(error);
64 });
65 })
66 }
67
68}
69
70const Api = _Api;
71
72export default Api
73
View Code
1
2
3
4
5
6
7
8
9
10
11
12
13 1import Api from './api';
2
3const api = new Api({
4 baseURI: 'http://106.13.61.216:8888',
5 // baseURI: 'http://127.0.0.1:8888',
6 headers: {
7 'Accept': '*/*',
8 'Content-Type': 'application/json; charset=utf-8'
9 }
10});
11
12export default api;
13
接下来举个计时器的例子引入说明Redux,React-router.
[1]: 在react+typescript下引入Redux 管理状态:
yarn add redux @types/redux
yarn add react-redux
@types/react-redux
action.tsx:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 1const namespace = 'test';
2
3// 增加 state 次数的方法
4export function increment() {
5 console.log("export function increment");
6 return {
7 type: 'INCREMENT',
8 isSpecial: true,
9 namespace,
10 }
11}
12
13// 减少 state 次数的方法
14export const decrement = () => ({
15 type: 'DECREMENT',
16 isSpecial: true,
17 namespace
18})
19
index.tsx:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 1import * as React from 'react';
2import {connect} from 'react-redux';
3import {Dispatch, bindActionCreators} from 'redux';
4import {decrement, increment} from '../test/action';
5
6// 创建类型接口
7export interface IProps {
8 value: number;
9 onIncrement: any;
10 onDecrement: any;
11}
12
13
14// 使用接口代替 PropTypes 进行类型校验
15class Counter extends React.PureComponent<IProps> {
16 public render() {
17 const {value, onIncrement, onDecrement} = this.props;
18 console.log("value: " + value);
19 console.log('onIncrement: ' + typeof onIncrement)
20 return (
21 <p>
22 Clicked: {value} times
23 <br/>
24 <br/>
25 <button onClick={onIncrement} style={{marginRight: 20}}> +</button>
26 <button onClick={onDecrement}> -</button>
27 </p>
28 )
29 }
30}
31
32// 将 reducer 中的状态插入到组件的 props 中
33const mapStateToProps = (state: { counterReducer: any }) => ({
34 value: state.counterReducer.count
35})
36
37// 将对应action 插入到组件的 props 中
38const mapDispatchToProps = (dispatch: Dispatch) => ({
39 onDecrement: bindActionCreators(decrement, dispatch),
40 onIncrement: bindActionCreators(increment, dispatch),
41})
42
43// 使用 connect 高阶组件对 Counter 进行包裹
44const CounterApp = connect(
45 mapStateToProps,
46 mapDispatchToProps
47)(Counter);
48
49export default CounterApp;
50
View Code
1
2
3
4
5
6 1export function isPromise(value) {
2 if (value !== null && typeof value === 'object') {
3 return value.promise && typeof value.promise.then === 'function';
4 }
5}
6
安装中间件:
yarn add redux-
thunk
在src下创建middlewares目录,新建promise-middleware.js(中间件),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 1import {isPromise} from '../utils/promise';
2
3const defaultTypes = ['PENDING', 'FULFILLED', 'REJECTED'];
4
5export default function promiseMiddleware(config = {}) {
6 const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypes;
7
8 return (_ref) => {
9 const dispatch = _ref.dispatch;
10
11 return next => action => {
12 if(!isPromise(action.payload)){
13 let originType = action.originType?action.originType:action.type;
14 let type = action.originType?action.type:action.namespace?`${action.namespace}_${action.type}`:`${action.type}`;
15 return next({
16 ...action,
17 originType,
18 type
19 });
20 }
21
22 const {type, payload, meta,isSpecial, resultType,namespace} = action;
23 const {promise, data} = payload;
24 const [ PENDING, FULFILLED, REJECTED ] = (meta || {}).promiseTypeSuffixes || promiseTypeSuffixes;
25
26 /**
27 * Dispatch the first async handler. This tells the
28 * reducers that an async action has been dispatched.
29 */
30 next({
31 originType:type,
32 type: namespace?`${namespace}_${type}_${PENDING}`:`${type}_${PENDING}`,
33 ...!!data ? {payload: data} : {},
34 ...!!meta ? {meta} : {},
35 isSpecial,
36 resultType,
37 namespace
38 });
39
40 const isAction = resolved => resolved && (resolved.meta || resolved.payload);
41 const isThunk = resolved => typeof resolved === 'function';
42 const getResolveAction = isError => ({
43 originType:type,
44 type: namespace?`${namespace}_${type}_${isError ? REJECTED : FULFILLED}`:`${type}_${isError ? REJECTED : FULFILLED}`,
45 ...!!meta ? {meta} : {},
46 ...!!isError ? {error: true} : {},
47 isSpecial,
48 resultType,
49 namespace
50 });
51
52
53 /**
54 * Re-dispatch one of:
55 * 1. a thunk, bound to a resolved/rejected object containing ?meta and type
56 * 2. the resolved/rejected object, if it looks like an action, merged into action
57 * 3. a resolve/rejected action with the resolve/rejected object as a payload
58 */
59 action.payload.promise = promise.then(
60 (resolved = {}) => {
61 const resolveAction = getResolveAction();
62 return dispatch(isThunk(resolved) ? resolved.bind(null, resolveAction) : {
63 ...resolveAction,
64 ...isAction(resolved) ? resolved : {
65 ...!!resolved && {payload: resolved}
66 }
67 });
68 },
69 (rejected = {}) => {
70 const resolveAction = getResolveAction(true);
71 return dispatch(isThunk(rejected) ? rejected.bind(null, resolveAction) : {
72 ...resolveAction,
73 ...isAction(rejected) ? rejected : {
74 ...!!rejected && {payload: rejected}
75 }
76 });
77 },
78 );
79
80 return action;
81 };
82 };
83}
84
View Code
[4]:
(1)src下新建store目录,在此新建base-reducer.js,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 1const initialState = {};
2
3export default function baseReducer(base, state = initialState, action = {}) {
4 switch (action.type) {
5 case `${base}_${action.originType}_PENDING`:
6 return {
7 ...state,
8 [`${action.originType}Result`]: state[`${action.originType}Result`] ? state[`${action.originType}Result`] : null,
9 [`${action.originType}Loading`]: true,
10 [`${action.originType}Meta`]: action.meta,
11 };
12
13 case `${base}_${action.originType}_SUCCESS`:
14 return {
15 ...state,
16 [`${action.originType}Result`]: action.resultType ? action.payload[action.resultType] : action.payload,
17 [`${action.originType}Loading`]: false,
18 [`${action.originType}Meta`]: action.meta,
19 };
20
21 case `${base}_${action.originType}_ERROR`:
22 return {
23 ...state,
24 [`${action.originType}Error`]: action.payload.errorMsg,
25 [`${action.originType}Loading`]: false
26 };
27
28 case `${base}_${action.originType}`:
29 return {...state, [action.originType]: action.data, [`${action.originType}Meta`]: action.meta};
30
31 default:
32 return {...state};
33 }
34}
35
(2) 在store目录下新建reducers目录,接着新建test.tsx文件,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1import baseReducer from '../base-reducer';
2
3const namespace = 'test';
4
5// 处理并返回 state
6export default function CounterReducer(state = {count: 0}, action: { type: string, isSpecial?: boolean }) {
7 if (!action.isSpecial) {
8 return baseReducer(namespace, state, action);
9 }
10 switch (action.type) {
11 case namespace + '_INCREMENT':
12 console.log('INCREMENT');
13 return Object.assign({}, state, {
14 count: state.count + 1 //计数器加一
15 });
16 case namespace + '_DECREMENT':
17 console.log('DECREMENT');
18 return Object.assign({}, state, {
19 count: state.count - 1 //计数器减一
20 });
21 default:
22 return state;
23 }
24}
25
(3) 在store目录下新建configure.store.tsx文件,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
2import thunkMiddleware from 'redux-thunk';
3
4import promiseMiddleware from '../middlewares/promise-middleware';
5import CounterReducer from './reducers/test';
6
7const reducer = combineReducers({
8 counterReducer: CounterReducer,
9})
10
11const enhancer = compose(
12 //你要使用的中间件,放在前面
13 applyMiddleware(
14 thunkMiddleware,
15 promiseMiddleware({promiseTypeSuffixes: ['PENDING', 'SUCCESS', 'ERROR']})
16 ),
17);
18
19
20export default function configureStore(initialState = {}) {
21 return createStore(
22 reducer,
23 initialState,
24 enhancer
25 );
26}
27
[5]: 更改App.tsx中的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 1import React from 'react';
2// import './App.css';
3import {Link} from 'react-router-dom';
4import {Layout, Menu, Icon} from 'antd';
5
6
7const {SubMenu} = Menu;
8const {Header, Content, Sider} = Layout;
9
10export interface AppProps {
11}
12
13export interface AppState {
14}
15
16class App extends React.Component<AppProps, AppState> {
17
18 rootSubmenuKeys = ['拨入', '审计', '测试'];
19 state = {
20 collapsed: false,
21 openKeys: ['拨入'],
22 };
23
24 componentDidMount(): void {
25 }
26
27 toggle = () => {
28 this.setState({
29 collapsed: !this.state.collapsed,
30 });
31 };
32
33 onOpenChange = (openKeys: Array<string>) => {
34 let latestOpenKey = openKeys.find(key => this.state.openKeys.indexOf(key) === -1);
35 latestOpenKey = latestOpenKey ? latestOpenKey : '';
36 if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
37 this.setState({openKeys});
38 } else {
39 this.setState({
40 openKeys: latestOpenKey ? [latestOpenKey] : [],
41 });
42 }
43 };
44
45
46 render() {
47 const mainSvg = () => (
48 <svg className="icon" viewBox="0 0 1024 1024" version="1.1"
49 xmlns="http://www.w3.org/2000/svg" p-id="3350" width="30" height="30">
50 <path
51 d="M512 170.666667c-11.946667 0-22.186667-4.266667-30.72-12.8-7.970133-7.970133-11.946667-17.92-11.946667-29.866667s3.976533-22.186667 11.946667-30.72c8.533333-7.970133 18.773333-11.946667 30.72-11.946667s21.896533 3.976533 29.866667 11.946667c8.533333 8.533333 12.8 18.773333 12.8 30.72s-4.266667 21.896533-12.8 29.866667c-7.970133 8.533333-17.92 12.8-29.866667 12.8z"
52 p-id="3351" fill="#1296db"></path>
53 <path
54 d="M768 955.733333a17.015467 17.015467 0 0 1-12.066133-5.000533L725.333333 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L640 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L554.666667 920.132267l-30.600534 30.600533a17.0496 17.0496 0 0 1-24.132266 0L469.333333 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L384 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L298.666667 920.132267l-30.600534 30.600533a17.0496 17.0496 0 1 1-24.132266-24.132267l42.666666-42.666666a17.0496 17.0496 0 0 1 24.132267 0L341.333333 914.5344l30.600534-30.600533a17.0496 17.0496 0 0 1 24.132266 0L426.666667 914.5344l30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0L512 914.5344l30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0L597.333333 914.5344l30.600534-30.600533a17.0496 17.0496 0 0 1 24.132266 0L682.666667 914.5344l30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0l42.666666 42.666666A17.0496 17.0496 0 0 1 768 955.733333z m-469.333333-128a17.015467 17.015467 0 0 1-12.066134-5.000533l-42.666666-42.666667a17.0496 17.0496 0 1 1 24.132266-24.132266l30.737067 30.754133 20.5824-20.104533 26.043733-88.712534v-0.0512l99.805867-341.230933 0.017067-0.1024 45.038933-152.6272a60.040533 60.040533 0 0 1-21.0944-13.9264c-11.229867-11.229867-16.930133-25.344-16.930133-41.9328 0-16.366933 5.563733-30.6176 16.554666-42.359467C481.3824 73.8304 495.633067 68.266667 512 68.266667c16.5888 0 30.702933 5.700267 41.9328 16.964266A58.504533 58.504533 0 0 1 571.733333 128c0 16.571733-6.2976 31.214933-18.210133 42.3424a53.589333 53.589333 0 0 1-19.848533 13.448533l44.936533 152.337067 0.238933 0.733867 50.1248 169.9328 0.238934 0.750933 50.1248 169.9328 0.221866 0.750933 25.975467 88.490667 19.831467 19.831467 30.600533-30.600534a17.0496 17.0496 0 1 1 24.132267 24.132267l-42.666667 42.666667a17.0496 17.0496 0 0 1-24.132267 0L682.666667 792.132267l-30.600534 30.600533a17.0496 17.0496 0 0 1-24.132266 0L597.333333 792.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L512 792.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L426.666667 792.132267l-30.600534 30.600533a17.0496 17.0496 0 0 1-24.132266 0l-30.2592-30.242133-31.0784 30.395733a17.1008 17.1008 0 0 1-11.9296 4.846933zM597.333333 750.933333c4.369067 0 8.738133 1.672533 12.066134 5.000534l30.600533 30.600533 27.630933-27.630933L650.257067 699.733333H374.596267l-17.5616 59.835734 26.9824 26.965333 30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0l30.600533 30.600533 30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0l30.600533 30.600533 30.600534-30.600533A16.9472 16.9472 0 0 1 597.333333 750.933333z m-212.6848-85.333333h255.556267l-40.260267-136.533333H424.9088l-40.260267 136.533333z m50.2272-170.666667h154.999467l-40.277333-136.533333h-75.1104l-39.611734 136.533333z m49.595734-170.666666h55.04L512 230.980267 484.471467 324.266667zM512 102.4c-7.645867 0-13.704533 2.338133-19.063467 7.355733-4.1984 4.539733-6.536533 10.5984-6.536533 18.244267 0 7.406933 2.2016 13.056 6.946133 17.783467 5.256533 5.256533 11.093333 7.748267 18.346667 7.816533h0.631467c7.099733-0.068267 12.373333-2.3552 17.066666-7.389867 5.922133-5.5808 8.209067-10.939733 8.209067-18.210133 0-7.406933-2.491733-13.329067-7.799467-18.653867C525.073067 104.6016 519.406933 102.4 512 102.4z"
55 p-id="3352" fill="#1296db"></path>
56 </svg>
57 );
58 const boruSvg = () => (
59 <svg className="icon" viewBox="0 0 1024 1024" version="1.1"
60 xmlns="http://www.w3.org/2000/svg" p-id="7168" width="20" height="20">
61 <path
62 d="M714 762.2h-98.2c-16.6 0-30 13.4-30 30s13.4 30 30 30H714c16.6 0 30-13.4 30-30s-13.4-30-30-30zM487.4 762.2H147.1c-16.6 0-30 13.4-30 30s13.4 30 30 30h340.3c16.6 0 30-13.4 30-30s-13.4-30-30-30z"
63 fill="#a6adb4" p-id="7169"></path>
64 <path d="M838.253 130.023l65.548 65.548-57.982 57.983-65.549-65.549z" fill="#a6adb4" p-id="7170"></path>
65 <path
66 d="M743.7 955.9H195.8c-53.7 0-97.4-43.7-97.4-97.4V174.8c0-53.7 43.7-97.4 97.4-97.4H615c16.6 0 30 13.4 30 30s-13.4 30-30 30H195.8c-20.6 0-37.4 16.8-37.4 37.4v683.7c0 20.6 16.8 37.4 37.4 37.4h547.9c20.6 0 37.4-16.8 37.4-37.4v-395c0-16.6 13.4-30 30-30s30 13.4 30 30v395.1c0 53.6-43.7 97.3-97.4 97.3z"
67 fill="#a6adb4" p-id="7171"></path>
68 <path
69 d="M907.7 122.1l-39.2-39.2c-24-24-65.1-21.9-91.7 4.7L419.5 445 347 643.6l198.6-72.4L903 213.8c12.1-12.1 19.6-27.7 21.1-44 1.8-18.1-4.3-35.5-16.4-47.7zM512.6 519.3L447.5 543l23.7-65.1 264.7-264.7 40.9 41.7-264.2 264.4z m348-347.9l-41.3 41.3-40.9-41.7 40.9-40.9c3.1-3.1 6.2-3.9 7.6-3.9l37.6 37.6c-0.1 1.3-0.9 4.5-3.9 7.6z"
70 fill="#a6adb4" p-id="7172"></path>
71 </svg>
72 );
73 const testSVg = () => (
74 <svg className="icon" viewBox="0 0 1024 1024" version="1.1"
75 xmlns="http://www.w3.org/2000/svg" p-id="4879" width="20" height="20">
76 <path
77 d="M199.111111 1024c-62.577778 0-113.777778-51.2-113.777778-113.777778V227.555556c0-62.577778 51.2-113.777778 113.777778-113.777778 19.911111 0 36.977778 17.066667 36.977778 36.977778v54.044444c-42.666667 11.377778-73.955556 25.6-73.955556 71.111111V853.333333c0 51.2 39.822222 93.866667 93.866667 93.866667h497.777778c51.2 0 93.866667-42.666667 93.866666-93.866667V275.911111c0-45.511111-31.288889-59.733333-76.8-68.266667V153.6c-2.844444-22.755556 14.222222-39.822222 34.133334-39.822222 62.577778 0 113.777778 51.2 113.777778 113.777778v682.666666c0 62.577778-51.2 113.777778-113.777778 113.777778H199.111111z m341.333333-304.355556h227.555556c19.911111 0 36.977778 17.066667 36.977778 36.977778S787.911111 796.444444 768 796.444444h-227.555556c-19.911111 0-36.977778-17.066667-36.977777-36.977777s17.066667-39.822222 36.977777-39.822223z m0-301.511111h227.555556c19.911111 0 36.977778 17.066667 36.977778 36.977778s-17.066667 36.977778-36.977778 36.977778h-227.555556c-19.911111 0-36.977778-17.066667-36.977777-36.977778s17.066667-36.977778 36.977777-36.977778z m-227.555555-227.555555V150.755556c0-19.911111 17.066667-36.977778 36.977778-36.977778h36.977777c0-62.577778 51.2-113.777778 113.777778-113.777778s113.777778 51.2 113.777778 113.777778H654.222222c19.911111 0 36.977778 17.066667 36.977778 36.977778v36.977777L312.888889 190.577778z m-99.555556 233.244444c5.688889-5.688889 17.066667-5.688889 25.6 0l62.577778 62.577778 136.533333-136.533333c8.533333-8.533333 22.755556-8.533333 28.444445 0 8.533333 8.533333 8.533333 19.911111 0 28.444444l-147.911111 147.911111c-5.688889 2.844444-11.377778 5.688889-17.066667 5.688889-5.688889 0-8.533333-2.844444-11.377778-5.688889l-73.955555-73.955555c-11.377778-11.377778-11.377778-22.755556-2.844445-28.444445z m207.644445 403.911111c-8.533333 8.533333-22.755556 8.533333-28.444445 0L332.8 768l-59.733333 59.733333c-8.533333 8.533333-22.755556 8.533333-28.444445 0-8.533333-8.533333-8.533333-22.755556 0-28.444444l59.733334-59.733333-59.733334-59.733334c-8.533333-8.533333-8.533333-22.755556 0-28.444444 8.533333-8.533333 19.911111-8.533333 28.444445 0l59.733333 59.733333 59.733333-59.733333c8.533333-8.533333 22.755556-8.533333 28.444445 0 8.533333 8.533333 8.533333 19.911111 0 28.444444L361.244444 739.555556l59.733334 59.733333c8.533333 5.688889 8.533333 19.911111 0 28.444444z"
78 fill="#9ca4ac" p-id="4880"></path>
79 </svg>
80 )
81
82 const MainIcon = (props: any) => <Icon component={mainSvg} {...props} />;
83 const BoruIcon = (props: any) => <Icon component={boruSvg} {...props} />;
84 const TestIcon = (props: any) => <Icon component={testSVg} {...props} />;
85
86 return (
87 <div className="app" >
88 <Layout>
89 <Sider className="slider" collapsible collapsed={this.state.collapsed} trigger={null} width={250}>
90 <div className="logo"
91 style={{
92 height: 32,
93 background: 'rgba(255, 255, 255, 0.2)',
94 margin: 16,
95 fontSize: 20,
96 color: "orange"
97 }}>
98 rty <MainIcon style={{color: 'red'}}/>
99 </div>
100 <Menu
101 theme="dark"
102 mode="inline"
103 defaultSelectedKeys={['1']}
104 defaultOpenKeys={['审计']}
105 style={{borderRight: 0,height: 950}}
106 openKeys={this.state.openKeys}
107 onOpenChange={this.onOpenChange}
108 >
109 <SubMenu
110 key="拨入"
111 title={<span><BoruIcon/>拨入</span>}
112 >
113 <Menu.Item key="1"><Link to="/boru/insert-record">新增拨入记录</Link></Menu.Item>
114 <Menu.Item key="2"><Link to="/boru/query-record">查询拨入记录</Link></Menu.Item>
115 <Menu.Item key="3"><Link to="/boru/insert-person">拨入人员列表</Link></Menu.Item>
116 <Menu.Item key="4"><Link to="/boru/query-person">查询拨入人员列表</Link></Menu.Item>
117 <Menu.Item key="5"><Link to="/boru/query-person-his">账号变更查询</Link></Menu.Item>
118 </SubMenu>
119 <SubMenu
120 key="审计"
121 title={<span><Icon type="user"/>审计</span>}
122 >
123 <Menu.Item key="6"><Link to="/shenji/insert-oa">添加OA账号</Link></Menu.Item>
124 <Menu.Item key="7"><Link to="/shenji/query-oa">账号查询</Link></Menu.Item>
125 <Menu.Item key="8"><Link to="/shenji/query-oa-his">账号变更查询</Link></Menu.Item>
126 </SubMenu>
127 <SubMenu
128 key="测试"
129 title={<span><TestIcon/>测试</span>}
130 >
131 <Menu.Item key="10"><Link to="/test">计时器测试</Link></Menu.Item>
132 </SubMenu>
133 </Menu>
134 </Sider>
135 <Layout>
136 <Header style={{background: '#fff', padding: 0}}>
137 <Icon
138 className="trigger"
139 type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
140 onClick={this.toggle}
141 />
142 </Header>
143 <Content
144 style={{
145 margin: '6px 4px',
146 padding: 6,
147 background: '#ffffff',
148 minHeight: 280,
149 }}
150 >
151 {this.props.children}
152 </Content>
153 </Layout>
154 </Layout>
155
156 </div>
157
158 );
159 }
160
161
162}
163
164export default App;
165
View Code
[6]: > yarn add
react-router-dom @types/react-router-dom
然后在src下新建MyRouer.tsx,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1/* 定义组件路由
2 * @author: yj
3 * 时间: 2019-12-18
4 */
5import React from 'react';
6import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
7import CounterApp from './containers/test/index';
8import App from "./App";
9
10const MyRouter = () => (
11 <Router>
12 <Route path="/" render={() =>
13 <App>
14 <Switch>
15 <Route path="/test" component={CounterApp}/>
16 <Route path="/" component={CounterApp}/>
17 </Switch>
18 </App>
19 }/>
20 </Router>
21);
22
23
24export default MyRouter;
25
[7]: 更改src/index.tsx中的内容,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1import React from 'react';
2import ReactDOM from 'react-dom';
3import './index.css';
4import * as serviceWorker from './serviceWorker';
5
6import {Provider} from 'react-redux';
7import configureStore from './store/configure-store';
8
9import MyRouterApp from './MyRouter';
10
11const store = configureStore();
12
13ReactDOM.render( //关联store
14 <Provider store={store}>
15 <MyRouterApp/>
16 </Provider>, document.getElementById('root'));
17
18// If you want your app to work offline and load faster, you can change
19// unregister() to register() below. Note this comes with some pitfalls.
20// Learn more about service workers: https://bit.ly/CRA-PWA
21serviceWorker.unregister();
22
然后启动项目即可.总结一下工作流程以及容易误解的地方:
(1): 点击 '+'号,触发onIncrement函数,onIncrement通过'
bindActionCreators'绑定了action的'increment'方法,本次action描述'计时器'要加1,派发action会交给reduce处理,reducer会改变state,
即state.counterReducer.count值加1,然后计时器重新渲染,值变成1.
(2): 本次的中间件会让api请求后台数据变成异步;
(3): 当action.
isSpecial为false的时候,
base-reducer.js是用来统一处理action的reducer,
1
2
3 1import api from '../../api/index';
2import {rtyDialOAPersonReq, rtyDialOAPerson} from './data'const namespace = 'shenji';
3
例如这个action,它会向
/rtyOADialPersons/getRtyOADialPersonsByFirstChar 接口请求数据, 拿到的json数据为:
1
2 1{"JSON":{"status":"success","errorCode":null,"message":"查询成功.","data":[{"dialPersonId":"6","firstName":"美羊羊","telecomNumber":"12341231234","description":"羊村之美羊羊-臭美","firstChar":"meiyangyang","departmentId":"1004","status":"是","createdBy":"root","modifiedBy":"root","billId":"DSFsdf34543fds","modifiedBillId":"7777777777","opType":"更新","effectiveDate":"2020-02-11 13:17:10","lastUpdatedStamp":"2020-02-21 10:05:23","createdStamp":"2020-02-11 13:17:28"}],"total":0,"success":true},
2
'resultType'表示从后台返回的json对象拿属性为data的数据,
1
2 1 [{"dialPersonId":"6","firstName":"美羊羊","telecomNumber":"12341231234",......,"createdStamp":"2020-02-11 13:17:28"}
2
'type'表示经过base-reducer.js处理后,把上面的数据封装到
rtyOADialPersonsByFirstCharSourceResult 里面.
rtyOADialPersonsByFirstCharSourceLoding一般和antd组件的loading属性绑定,
当数据加载的时候antd组件会转圈圈,加载成功以后转圈圈消失.
本人照着以上流程走一遍,可以运行。