├── .gitignore
├── README.md
├── config
├── env.js
├── jest
│ ├── cssTransform.js
│ └── fileTransform.js
├── paths.js
├── polyfills.js
├── webpack.config.dev.js
├── webpack.config.prod.js
└── webpackDevServer.config.js
├── mockjs
└── index.js
├── package.json
├── public
├── favicon.ico
├── index.html
├── js
│ ├── LodopFuncs.js
│ └── printbase.js
└── manifest.json
├── scripts
├── build.js
├── start.js
└── test.js
└── src
├── api
├── config.js
├── index.js
└── resource.js
├── assets
├── imgs
│ ├── avar.gif
│ ├── demo.gif
│ ├── express.svg
│ ├── logo.png
│ ├── logo.svg
│ └── orders.svg
└── less
│ ├── app.less
│ ├── dashboard.less
│ ├── dialog.less
│ ├── nomatch.less
│ ├── order-detail.less
│ ├── order-print-preview.less
│ ├── sender-item.less
│ └── sender-new.less
├── components
├── DropOption
│ └── index.js
├── dashboard.jsx
├── loading.jsx
├── login.jsx
├── logout.jsx
├── order-assigned.jsx
├── order-detail.jsx
├── order-print-preview.jsx
├── order-unassign.jsx
├── order-vendor-list.jsx
├── orderlist-back.jsx
├── orderlist-finish.jsx
├── orderlist-msg.jsx
├── orderlist-new.jsx
├── orderlist-nomsg.jsx
├── orderlist.jsx
├── orders-back.jsx
├── printer-manager.jsx
├── return-visit-orders.jsx
├── sender-edit.jsx
├── sender-item.jsx
├── sender-new.jsx
├── sender-setting.jsx
├── unreturn-visit-orders.jsx
├── user-add.jsx
└── userlist.jsx
├── containers
├── app.jsx
├── dashboard.jsx
├── express.jsx
├── nomatch.jsx
├── orders.jsx
├── print.jsx
├── scroll-to-top.jsx
└── usermanager.jsx
├── http
└── index.js
├── main.js
├── redux
├── actions
│ ├── actionstype.js
│ ├── menu.jsx
│ └── user.jsx
├── reducers
│ ├── index.jsx
│ ├── menu.jsx
│ └── user.jsx
├── sagas
│ ├── fetchdata.jsx
│ ├── index.js
│ ├── signin.jsx
│ └── signout.jsx
└── store
│ └── index.js
├── registerServiceWorker.js
└── utils
├── city.js
├── index.js
├── init.js
├── menu.jsx
└── print.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .vscode/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-antd-webpack2-sagas-router4
2 | 学习使用react、antd、webpack2、react-routerV4、redux-saga等技术栈Demo
3 | # 说明
4 | >该项目是为了学习react、webpack、antd等相关技术栈而搭建的,希望能在学习的过程中,记录相关的问题。
5 | # 演示
6 | 
7 | ## 安装
8 | 进入源码主目录,运行`cnpm install`安装时,可能会比较慢。
9 |
10 | 这里,推荐使用[淘宝NPM镜像](http://npm.taobao.org/).
11 | 直接运行下面语句,以实现cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
12 | `npm install -g cnpm --registry=https://registry.npm.taobao.org`
13 |
14 | 然后,运行`cnpm install`,安装完相关的包后,即可进入下一步。
15 | ## 启动
16 | 运行`cnpm start`,正常情况下,会在默认浏览器中打开网址`http://localhost:3000`.
17 |
18 | 端口号可以在`scripts/start.js`中,如果已存在`process.evn.PORT`则直接修改其值`process.env.PORT = '3000';`,如果没有,则添加即可。
19 |
20 | ## RAP
21 | 使用`RAP`来模拟demo需要获取的数据,其用法参考[RAP官网](http://rapapi.org/platform/home.do)
22 | 不支持在http头里面带入`Authorization token`,所以代码中是将相关代码暂时屏蔽掉的。
23 |
24 | ## antd
25 | 蚂蚁金服出品,非常棒的UI库,使用起来还是挺方便的,其用法参考[antdesign官网](https://ant.design/index-cn)
26 | ## 其他库
27 | `axios`
28 |
29 | 基于 Promise 的 HTTP 请求客户端,可同时在浏览器和 node.js 中使用
30 |
31 | `redux-saga`
32 |
33 | [中文文档](http://leonshi.com/redux-saga-in-chinese/index.html)
34 |
35 | ## 打包部署
36 | 运行`cnpm run build`,将资源打包输出到`build`目录。
37 | ## 编译、运行问题
38 | 1. 如果编译出错,请删除node_modules目录,重新cnpm install再试。因为有些版本更新了依赖包,需要安装
39 | ## 初学知识点
40 | ### 一、create-react-app脚手架
41 | facebook官方推出的脚手架,功能还是很强大,对于新手开发完全够用了。具体配置请[点我](https://github.com/facebookincubator/create-react-app)
42 |
43 | ### 二、token机制
44 | 在成功登录后,将获取的token保存在redux和localStorage中, 以后每次api请求时,将token带到http头里面。具体可参考`src/http/index.js`
45 | http请求使用的事axios库,其提供了请求和响应的拦截器,这样可以方便我们进行相关处理,比如:NProgress控件的使用
46 | ```javascript
47 | axios.interceptors.request.use(
48 | (config) => {
49 | if (localStorage.token) {
50 | config.headers.Authorization = `token ${localStorage.token}`;
51 | }
52 | NProgress.start();//加载进度条显示
53 | return config;
54 | },
55 | (err) => {
56 | return Promise.reject(err);
57 | }
58 | );
59 | ```
60 | 如果遇到token过期等情况根据和服务端的约定,下发相关的状态码,在响应拦截器`axios.interceptors.response.use`中进行相关处理即可。
61 |
62 | 当刷新页面或者重新进入页面时,redux中保存的信息就会丢失,此时从localStorage中读取token相关信息,调用登录接口,具体实现`src/utils/init.js`中。
63 |
64 | ### 三、打包的js文件过大,怎么办?
65 |
66 | create-react-app默认打包出来只有一个js文件,这样就会出现js文件体积过大。
67 | 这里简单的说说我的做法:
68 | 1. 配置webpack, 将第三方库打包出独立的js文件, 至于为什么会多出manifest?主要解决每次打包出来的文件hash值都会该变的问题,具体代码如下:
69 | ```javascript
70 | entry: {
71 | main: [
72 | require.resolve('./polyfills'),
73 | paths.appIndexJs
74 | ],
75 | vendor: [
76 | 'react',
77 | 'react-router',
78 | 'react-redux',
79 | 'redux-saga',
80 | 'react-router-dom'
81 | ]
82 | }
83 | plugins: [
84 | new webpack.optimize.CommonsChunkPlugin({
85 | names: ['main', 'vendor', 'manifest']
86 | })
87 | ]
88 | ```
89 | 2. 使用`code split`,其原理是:按需加载,举例说明:进入登录页面,那么我们就只加载登录页面需要的资源;其它的资源先不加载,这样就加快了页面的呈现速度。网上的介绍文章也很多,这里推荐一篇[请点我](http://serverless-stack.com/chapters/code-splitting-in-create-react-app.html)。
90 |
91 | 我的代码实现是使用`react-loadable`库,具体实现在`src/components/loading.jsx`文件,调用的地方在`src/containers/express.jsx`文件,我这里只是为了演示代码分割的功能,所以只在express.jsx中使用了代码分割,并且该功能最好在`cnpm run build`打包时再使用,在开发模式中要是开启代码分割,好像会影响热加载速度(修改文件后,编译变慢)。
92 |
93 | ### 四、页面滚动后,当打开新页面时,页面还是停留在之前的位置,怎么办?
94 | 在开发spa页面时,会发现如果当前页面出现滚动条时,此时打开新的页面,如果新的页面比较长的情况下,就会发现新的页面不是从页面顶部显示。
95 | 解决办法: 切换页面时,使用`window.scroll(0, 0)`函数将页面滚动到顶部。
96 | 具体实现:参考`src/containers/scroll-to-top.jsx`组件,然后其使用是在`src/main.js`中;
97 |
98 | ### 五、前后端分离,如何解决跨域问题
99 | 1. 配置package.json
100 | 在开发模式,我们只需在`package.json`中增加字段`"proxy": 'http://serverip'`,将serverip替换成你的服务端提供的接口地址。
101 | 2. 使用Nginx反向代理
102 | 使用Nginx反向代理还是比较强大,不局限于开发模式。
103 | 使用时,只需配置Nginx的配置文件即可,但是需要注意一点:location的配置路径。
104 | 比如:服务端提供的接口地址是:http://192.168.16.200:8080, 我们的开发模式开启的端口假设是3000,
105 | ```javascript
106 | server {
107 | listen 80;
108 | server_name localhost;
109 |
110 | location / {
111 | proxy_pass http://127.0.0.1:3000;
112 | }
113 | #说明: 只有地址以'http://localhost/yq'开始的接口请求才会被Nginx代理到真正的服务器地址(http://192.168.16.200:8080),当然这里的/yq不是乱写的,和你的接口请求地址相关。其实可以这么理解:
114 | 你的代码中登录接口地址:'/yq/api/v1/user/sigin',通过Nginx访问时,请求地址成为`http://localhost/yq/api/v1/user/sigin`,Nginx检测到请求地址以'http://localhost/yq'开头,那么就会将该请求地址代理到真正的服务器接口地址上`http://192.168.16.200:8080/yq/api/v1/user/sigin`
115 | location /yq {
116 | proxy_pass http://192.168.16.200:8080;
117 | # nginx非80端口处理
118 | proxy_set_header Host $host:$server_port;
119 | # 获取真实IP
120 | proxy_set_header X-Real-IP $remote_addr;
121 | # 获取代理者的真实ip
122 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
123 | proxy_set_header X-Forwarded-Proto $scheme;
124 | }
125 | }
126 | ```
127 |
128 |
--------------------------------------------------------------------------------
/config/env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const paths = require('./paths');
6 |
7 | // Make sure that including paths.js after env.js will read .env variables.
8 | delete require.cache[require.resolve('./paths')];
9 |
10 | const NODE_ENV = process.env.NODE_ENV;
11 | if (!NODE_ENV) {
12 | throw new Error(
13 | 'The NODE_ENV environment variable is required but was not specified.'
14 | );
15 | }
16 |
17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18 | var dotenvFiles = [
19 | `${paths.dotenv}.${NODE_ENV}.local`,
20 | `${paths.dotenv}.${NODE_ENV}`,
21 | // Don't include `.env.local` for `test` environment
22 | // since normally you expect tests to produce the same
23 | // results for everyone
24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`,
25 | paths.dotenv,
26 | ].filter(Boolean);
27 |
28 | // Load environment variables from .env* files. Suppress warnings using silent
29 | // if this file is missing. dotenv will never modify any environment variables
30 | // that have already been set.
31 | // https://github.com/motdotla/dotenv
32 | dotenvFiles.forEach(dotenvFile => {
33 | if (fs.existsSync(dotenvFile)) {
34 | require('dotenv').config({
35 | path: dotenvFile,
36 | });
37 | }
38 | });
39 |
40 | // We support resolving modules according to `NODE_PATH`.
41 | // This lets you use absolute paths in imports inside large monorepos:
42 | // https://github.com/facebookincubator/create-react-app/issues/253.
43 | // It works similar to `NODE_PATH` in Node itself:
44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
48 | // We also resolve them to make sure all tools using them work consistently.
49 | const appDirectory = fs.realpathSync(process.cwd());
50 | process.env.NODE_PATH = (process.env.NODE_PATH || '')
51 | .split(path.delimiter)
52 | .filter(folder => folder && !path.isAbsolute(folder))
53 | .map(folder => path.resolve(appDirectory, folder))
54 | .join(path.delimiter);
55 |
56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
57 | // injected into the application via DefinePlugin in Webpack configuration.
58 | const REACT_APP = /^REACT_APP_/i;
59 |
60 | function getClientEnvironment(publicUrl) {
61 | const raw = Object.keys(process.env)
62 | .filter(key => REACT_APP.test(key))
63 | .reduce(
64 | (env, key) => {
65 | env[key] = process.env[key];
66 | return env;
67 | },
68 | {
69 | // Useful for determining whether we’re running in production mode.
70 | // Most importantly, it switches React into the correct mode.
71 | NODE_ENV: process.env.NODE_ENV || 'development',
72 | // Useful for resolving the correct path to static assets in `public`.
73 | // For example,
.
74 | // This should only be used as an escape hatch. Normally you would put
75 | // images into the `src` and `import` them in code to get their paths.
76 | PUBLIC_URL: publicUrl,
77 | }
78 | );
79 | // Stringify all values so we can feed into Webpack DefinePlugin
80 | const stringified = {
81 | 'process.env': Object.keys(raw).reduce((env, key) => {
82 | env[key] = JSON.stringify(raw[key]);
83 | return env;
84 | }, {}),
85 | };
86 |
87 | return { raw, stringified };
88 | }
89 |
90 | module.exports = getClientEnvironment;
91 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right
40 |
41 |
42 |
43 |