├── .babelrc
├── .gitignore
├── README.md
├── favicon.ico
├── index.html
├── package.json
├── postcss.config.js
├── server.js
├── src
├── app.jsx
├── assets
│ └── login-bg.jpg
├── component
│ ├── bcrumb
│ │ ├── bcrumb.jsx
│ │ └── style
│ │ │ └── bcrumb.less
│ ├── echarts
│ │ └── lineChart.jsx
│ └── layout
│ │ ├── layout.jsx
│ │ ├── lfooter.jsx
│ │ ├── lheader.jsx
│ │ ├── lmenu.jsx
│ │ └── style
│ │ └── layout.less
├── config
│ └── index.jsx
├── containers
│ ├── adver
│ │ └── adverIndex.jsx
│ ├── charts
│ │ └── lines.jsx
│ ├── general
│ │ ├── buttonIndex.jsx
│ │ └── iconIndex.jsx
│ ├── home
│ │ ├── homeIndex.jsx
│ │ └── style
│ │ │ └── home.less
│ ├── login
│ │ ├── login.jsx
│ │ └── style
│ │ │ └── login.less
│ ├── setting
│ │ └── settingIndex.jsx
│ ├── ui
│ │ ├── oneIndex.jsx
│ │ └── twoIndex.jsx
│ └── user
│ │ ├── style
│ │ └── user.less
│ │ └── userIndex.jsx
├── redux
│ ├── action
│ │ ├── index.jsx
│ │ └── login
│ │ │ └── loginAction.jsx
│ ├── constants
│ │ ├── dispatchTypes.jsx
│ │ └── loginTypes.jsx
│ ├── reducer
│ │ ├── index.jsx
│ │ └── login
│ │ │ └── loginReducer.jsx
│ └── store
│ │ └── store.jsx
├── router
│ └── route.jsx
├── services
│ ├── loginService.jsx
│ └── xhr
│ │ └── index.jsx
├── style
│ └── common.less
└── template
│ └── index.html
├── webpack.config.dev.js
├── webpack.config.dist.js
├── webpack.dll.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | "es2015",
5 | "stage-0",
6 | ],
7 | "plugins": [
8 | "transform-decorators-legacy",
9 | "transform-class-properties",
10 | ["import", { "libraryName": "antd", "style": true }]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .vscode/
3 | .idea
4 | .idea/
5 | .DS_Store
6 | ../.DS_Store
7 | node_modules/
8 | .project
9 | dist
10 | dist/*
11 | antd
12 | antd/*
13 | .happypack
14 | .happypack/
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-antd(新版后台很快就与大家见面了)
2 | [](https://github.com/facebook/react)
3 | [](https://github.com/reactjs/redux)
4 | [](https://github.com/indexiatech/redux-immutablejs)
5 | [](https://github.com/ant-design/ant-design)
6 |
7 | [](http://opensource.org/licenses/MIT)
8 |
9 | ## 相关推荐
10 | [基于vue + vuex + less + ES6/7 + webpack + fetch + vue-router + store + element-ui实现的SPA后台管理系统模板](https://github.com/sosout/vue2-element)
11 |
12 | [Redux源码解析-Redux的架构](https://github.com/sosout/redux-source-analyse)
13 |
14 | [Immutable源码解析-Immutable的架构](https://github.com/sosout/immutable-source-analyse)
15 |
16 |
17 | ## 最新更新
18 | > webpack版本升级2,同时引入Yarn缓存下载的每个包以及happypack利用了多进程,同时还利用缓存来使得rebuild 更快等
19 |
20 | > Redux使用调整
21 |
22 | > 路由模式更改为浏览器模式
23 |
24 | ## 前言
25 | > 本工程主要基于react + redux + immutable + less + ES6/7 + webpack2.0 + fetch + react-router + antd(1.x)实现的SPA后台管理系统模板。
26 |
27 | > 如果觉得不错的话,请star一下吧 😊
28 |
29 | > 编码时间:8:00——9:30, 下班时间——24:00,其他时间要工作。代码未优化,处女座代码必须要优化。由于代码延后,先向大家说声抱歉。您有什么问题可以私信我segmentfault。
30 |
31 | [线上demo](http://antd.sosout.com/)
32 |
33 | ## 关于我自己
34 |
35 | > 使用技术: react + redux + immutable + less + ES6/7 + webpack2.0 + fetch + react-router + antd(1.x)
36 |
37 | > 项目说明: 此项目是本人空余时间搭建的。希望大家提供宝贵的意见和建议,谢谢。
38 |
39 | > JS/React/Vue/Angular前端群: 599399742
40 |
41 | > 邮 箱: sosout@139.com
42 |
43 | > 个人网站: http://www.sosout.com/
44 |
45 | > 个人博客: http://blog.sosout.com/
46 |
47 | > 个人简书: http://www.jianshu.com/users/23b9a23b8849/latest_articles
48 |
49 | > segmentfault:https://segmentfault.com/u/sosout
50 |
51 | ### 下载
52 |
53 | ```
54 | # git clone
55 |
56 | git clone https://github.com/sosout/react-antd.git
57 |
58 | cd react-antd
59 | ```
60 |
61 | ### 安装
62 | ```bush
63 |
64 | // 安装前请先确保已安装node和npm
65 |
66 | // 安装成功后,再安装依赖,如果之前有用npm安装过,请先删掉node_modules
67 | yarn install
68 | ```
69 | ### 运行
70 | ```bush
71 | yarn run dev (开发版本,用于开发使用,热加载)
72 |
73 | yarn run dist (发布生产版本,对代码进行混淆压缩,提取公共代码,分离css文件)
74 | ```
75 |
76 | ### 访问
77 | 在浏览器地址栏输入[http://127.0.0.1:8888](http://127.0.0.1:8888)
78 |
79 | ### 目标功能
80 | - [x] 登录页面
81 | - [x] 全站布局
82 | - [x] 全站路由
83 | - [ ] 对接接口,优化代码(冗余代码,不规则写法,界面样式)
84 | - [ ] 后台系统常用场景会逐个完善
85 |
86 | ### 历史更新
87 | *2017.02.20*
88 |
89 | 1. 初始化项目目录;
90 |
91 | 2. webpack版本升级(webpack2.0),并加上yarn,happypack等(最新迭代);
92 |
93 | 3. 登录退出;
94 |
95 | 4. 整体布局;
96 |
97 | 5. 菜单映射路由(路由模式更改为浏览器模式);
98 |
99 | # 性能优化
100 |
101 | ## 如何正确地在React中处理事件
102 |
103 | [参考官网](https://facebook.github.io/react/docs/handling-events.html)
104 |
105 | #### 1、构造器内绑定this
106 | ```javascript
107 | class MyComponent extends React.Component {
108 | constructor(props) {
109 | super(props);
110 | this.state = {
111 | count: 0
112 | };
113 | this.handleClick = this.handleClick.bind(this);
114 | }
115 |
116 | handleClick() {
117 | this.setState({
118 | count: ++this.state.count
119 | });
120 | }
121 |
122 | render() {
123 | return (
124 |
125 |
{this.state.count}
126 |
127 |
128 | );
129 | }
130 | }
131 | ```
132 | 这种方式的好处是每次render,不会重新创建一个回调函数,没有额外的性能损失。需要注意的是,使用这种方式要在构造函数中为事件回调函数绑定this: this.handleClick = this.handleClick.bind(this),否则handleClick中的this是undefined。这是因为ES6 语法的缘故,ES6 的 Class 构造出来的对象上的方法默认不绑定到 this 上,需要我们手动绑定。
133 |
134 | #### 2、属性初始化
135 | 使用ES7的 property initializers,代码可以这样写:
136 | ```javascript
137 | class MyComponent extends React.Component {
138 | constructor(props) {
139 | super(props);
140 | this.state = {
141 | count: 0
142 | };
143 | }
144 |
145 | handleClick = () => {
146 | this.setState({
147 | count: ++this.state.count
148 | });
149 | }
150 |
151 | render() {
152 | return (
153 |
154 |
{this.state.count}
155 |
156 |
157 | );
158 | }
159 | }
160 | ```
161 | 这种方式就不需要手动绑定this了。但是你需要知道,这个特性还处于试验阶段,默认是不支持的。如果你是使用官方脚手架Create React App 创建的应用,那么这个特性是默认支持的。你也可以自行在项目中引入babel的transform-class-properties插件获取这个特性支持。
162 |
163 | #### 3、箭头函数
164 | ```javascript
165 | class MyComponent extends React.Component {
166 | render() {
167 | return (
168 |
171 | );
172 | }
173 | }
174 | ```
175 | 当事件响应逻辑比较复杂时,如果再把所有的逻辑直接写在onClick的大括号内,就会导致render函数变得臃肿,不容易直观地看出组件render出的元素结构。这时,可以把逻辑封装成组件的一个方法,然后在箭头函数中调用这个方法。如下所示:
176 | ```javascript
177 | class MyComponent extends React.Component {
178 | constructor(props) {
179 | super(props);
180 | this.state = {
181 | count: 0
182 | };
183 | }
184 | handleClick() {
185 | this.setState({
186 | count: ++this.state.count
187 | });
188 | }
189 | render() {
190 | return (
191 |
192 |
{this.state.number}
193 |
194 |
195 | );
196 | }
197 | }
198 | ```
199 | 这种方式最大的问题是,每次render调用时,都会重新创建一个事件的回调函数,带来额外的性能开销,当组件的层级越低时,这种开销就越大,因为任何一个上层组件的变化都可能会触发这个组件的render方法。当然,在大多数情况下,这点性能损失是可以不必在意的。这种方式也有一个好处,就是不需要考虑this的指向问题,因为这种写法保证箭头函数中的this指向的总是当前组件。
200 |
201 | #### 4、函数传递参数
202 | 事件的回调函数默认是会被传入一个事件对象Event作为参数的。如果我想传入其他参数给回调函数应该怎么办呢?
203 |
204 | 使用第一种方式(构造器内绑定this)的话,可以把绑定this的操作延迟到render中,在绑定this的同时,绑定额外的参数:
205 | ```javascript
206 | // 代码6
207 | class MyComponent extends React.Component {
208 | constructor(props) {
209 | super(props);
210 | this.state = {
211 | list: [1,2,3,4],
212 | current: 1
213 | };
214 | }
215 |
216 | handleClick(item) {
217 | this.setState({
218 | current: item
219 | });
220 | }
221 |
222 | render() {
223 | return (
224 |
225 | {this.state.list.map(
226 | (item)=>(
227 | - {item}
229 |
230 | )
231 | )}
232 |
233 | );
234 | }
235 | }
236 | ```
237 | 使用第二种方式(属性初始化),解决方案和第一种基本一致:
238 | ```javascript
239 | // 代码7
240 | class MyComponent extends React.Component {
241 | constructor(props) {
242 | super(props);
243 | this.state = {
244 | list: [1,2,3,4],
245 | current: 1
246 | };
247 | }
248 |
249 | handleClick = (item) => {
250 | this.setState({
251 | current: item
252 | });
253 | }
254 |
255 | render() {
256 | return (
257 |
258 | {this.state.list.map(
259 | (item)=>(
260 | - {item}
262 |
263 | )
264 | )}
265 |
266 | );
267 | }
268 | }
269 | ```
270 | 不过这种方式就有点鸡肋了,因为虽然你不需要通过bind函数绑定this,但仍然要使用bind函数来绑定其他参数。
271 |
272 | 使用第三种方式(函数传递参数)的话很简单,直接传就可以了:
273 | ```javascript
274 | class MyComponent extends React.Component {
275 | constructor(props) {
276 | super(props);
277 | this.state = {
278 | list: [1,2,3,4],
279 | current: 1
280 | };
281 | }
282 |
283 | handleClick(item,event) {
284 | this.setState({
285 | current: item
286 | });
287 | }
288 |
289 | render() {
290 | return (
291 |
292 | {this.state.list.map(
293 | (item)=>(
294 | - this.handleClick(item, event)}>{item}
296 |
297 | )
298 | )}
299 |
300 | );
301 | }
302 | }
303 | ```
304 |
305 | 关于事件响应的回调函数,还有一个地方需要注意。不管你在回调函数中有没有显式的声明事件参数Event,React都会把事件Event作为参数传递给回调函数,且参数Event的位置总是在其他自定义参数的后面。例如,在代码6和代码7中,handleClick的参数中虽然没有声明Event参数,但你依然可以通过arguments[1]获取到事件Event对象。
306 |
307 | 总结一下,三种绑定事件回调的方式,第一种有额外的性能损失;第二种需要手动绑定this,代码量增多;第三种用到了ES7的特性,目前并非默认支持,需要Babel插件的支持,但是写法最为简洁,也不需要手动绑定this。推荐使用第二种和第三种方式。
308 |
309 | ## Immutable 详解及 React 中实践 (https://github.com/camsong/blog/issues/3)
310 |
311 | ## react 实现pure render的时候,bind(this)隐患
312 | ```javascript
313 | export default class Parent extends Component {
314 | ...
315 | render() {
316 | const {name,age} =this.state;
317 | return (
318 |
319 | //bug 所在
320 |
321 | )
322 | }
323 | ...
324 | }
325 | ```
326 | 发现一个问题,对于Child这个子组件来说,在父组件re-render的时候,即使Child得前后两个props都没改变,它依旧会re-render。。即使用immutable.js也不好使。。。原来啊,父组件每次render,_handleClick都会执行bind(this) 这样_handleClick的引用每次都会改。。所以Child前后两次props其实是不一样的。。
327 | 那怎么办?把bind(this)去掉?不行 还必须得用。真正的答案是 让父组件每次render 不执行bind(this),直接提前在constructor执行好,修改之后
328 | ```javascript
329 | export default class Parent extends Component {
330 | constructor(props){
331 | super(props)
332 | this._handleClick=this._handleClick.bind(this)//改成这样
333 | }
334 | render() {
335 | const {name,age} =this.state;
336 | return (
337 |
338 |
339 |
340 | )
341 | }
342 | ...
343 | }
344 | ```
345 | ## 子组件跟随父组件re-render
346 | 想象一下这种场景,一个父组件下面一大堆子组件。然后呢,这个父组件re-render。是不是下面的子组件都得跟着re-render。可是很多子组件里面是冤枉的啊!!很多子组件的props 和 state 然而并没有改变啊!!虽然virtual dom 的diff 算法很快。。但是性能也不是这么浪费的啊!!
347 | ```javascript
348 | class Child extends Component {
349 | render() {
350 | console.log("我re-render了");
351 | const {name,age} = this.props;
352 |
353 | return (
354 |
355 | 姓名:
356 | {name}
357 | age:
358 | {age}
359 |
360 | )
361 | }
362 | }
363 | const Person = pureRender(Child);
364 | ```
365 | pureRender其实就是一个函数,接受一个Component。把这个Component搞一搞,返回一个Component看他pureRender的源代码就一目了然
366 | ```javascript
367 | function shouldComponentUpdate(nextProps, nextState) {
368 | return shallowCompare(this, nextProps, nextState);
369 | }
370 |
371 | function pureRende(component) {
372 | component.prototype.shouldComponentUpdate = shouldComponentUpdate;
373 | }
374 | module.exports = pureRender;
375 | ```
376 | pureRender很简单,就是把传进来的component的shouldComponentUpdate给重写掉了,原来的shouldComponentUpdate,无论怎样都是return ture,现在不了,我要用shallowCompare比一比,shallowCompare代码及其简单,如下
377 | ```javascript
378 | function shallowCompare(instance, nextProps, nextState) {
379 | return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
380 | }
381 | ```
382 | 一目了然。分别拿现在props&state和要传进来的props&state,用shallowEqual比一比,要是props&state都一样的话,就return false
383 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiubug/react-antd/663b085ae9559701b450d547834d2ffafe2c789b/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 基于react + redux + immutable + less + ES6/7 + webpack + fetch + react-router + antd(1.x)实现的SPA后台管理系统模板
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-antd",
3 | "version": "1.0.0",
4 | "description": "基于react + redux + immutable + less + ES6/7 + webpack + fetch + react-router + antd(1.x)实现的SPA后台管理系统模板",
5 | "main": "server.js",
6 | "scripts": {
7 | "dev": "node server.js",
8 | "dist": "yarn run dll && cross-env NODE_ENV=production webpack --display-error-details --config webpack.config.dist.js --progress --colors -p",
9 | "dll": "webpack --config webpack.dll.config.js --progress --colors -p"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/sosout/react-antd.git"
14 | },
15 | "keywords": [
16 | "react",
17 | "redux",
18 | "immutable",
19 | "less",
20 | "ES6/7",
21 | "webpack",
22 | "fetch",
23 | "react-router",
24 | "antd(1.x)",
25 | "SPA后台管理系统模板"
26 | ],
27 | "author": "sosout",
28 | "license": "ISC",
29 | "private": true,
30 | "bugs": {
31 | "url": "https://github.com/sosout/react-antd/issues"
32 | },
33 | "homepage": "https://github.com/sosout/react-antd#readme",
34 | "dependencies": {
35 | "antd": "^2.7.2",
36 | "babel-runtime": "^6.23.0",
37 | "echarts": "^3.6.2",
38 | "immutable": "^3.8.1",
39 | "react": "^15.3.2",
40 | "react-dom": "^15.3.2",
41 | "react-redux": "^4.4.5",
42 | "react-router": "^2.8.1",
43 | "redux": "^3.6.0",
44 | "redux-immutablejs": "^0.0.8",
45 | "redux-promise": "^0.5.3",
46 | "redux-thunk": "^2.1.0"
47 | },
48 | "devDependencies": {
49 | "assets-webpack-plugin": "^3.5.1",
50 | "autoprefixer": "^7.1.1",
51 | "autoprefixer-loader": "^3.2.0",
52 | "babel-core": "^6.18.2",
53 | "babel-loader": "^7.0.0",
54 | "babel-plugin-import": "^1.1.1",
55 | "babel-plugin-transform-class-properties": "^6.22.0",
56 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
57 | "babel-plugin-transform-runtime": "^6.23.0",
58 | "babel-polyfill": "^6.23.0",
59 | "babel-preset-es2015": "^6.6.0",
60 | "babel-preset-react": "^6.5.0",
61 | "babel-preset-react-hmre": "^1.1.1",
62 | "babel-preset-stage-0": "^6.16.0",
63 | "clean-webpack-plugin": "^0.1.16",
64 | "cross-env": "^3.1.3",
65 | "css-loader": "^0.23.1",
66 | "express": "^4.14.0",
67 | "extract-text-webpack-plugin": "^2.1.2",
68 | "file-loader": "^0.8.5",
69 | "happypack": "3.1.0",
70 | "html-webpack-plugin": "^2.22.0",
71 | "http-proxy-middleware": "^0.17.3",
72 | "isomorphic-fetch": "^2.2.1",
73 | "jsx-loader": "^0.13.2",
74 | "less": "^2.6.1",
75 | "less-loader": "^2.2.3",
76 | "opn": "^5.1.0",
77 | "postcss-loader": "^2.0.6",
78 | "pure-render-decorator": "^1.2.1",
79 | "react-hot-loader": "^1.3.1",
80 | "react-transform-catch-errors": "^1.0.2",
81 | "react-transform-hmr": "^1.0.4",
82 | "style-loader": "^0.13.1",
83 | "url-loader": "^0.5.7",
84 | "webpack": "2.2.1",
85 | "webpack-bundle-analyzer": "^2.8.3",
86 | "webpack-dev-middleware": "^1.12.0",
87 | "webpack-dev-server": "^2.6.1",
88 | "webpack-hot-middleware": "^2.18.2",
89 | "webpack-parallel-uglify-plugin": "^0.4.2"
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')({
4 | browsers: [
5 | "> 1%",
6 | "not ie <= 8",
7 | "Chrome >= 45",
8 | "Firefox >= 20"
9 | ]
10 | })
11 | ]
12 | }
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var express = require('express');
3 | var opn = require('opn');
4 | var config = require('./webpack.config.dev');
5 | var proxyMiddleware = require('http-proxy-middleware');
6 |
7 | var app = express();
8 | var compiler = webpack(config);
9 |
10 | app.use(require('webpack-dev-middleware')(compiler, {
11 | quiet: true, // 不显示控制台信息
12 | noInfo: true, // 不显示控制台信息(仅警告和错误)
13 | lazy: false, // 不切换懒惰模式
14 | publicPath: config.output.publicPath,
15 | hot: true, // 是否启用热更新
16 | historyApiFallback: true, // 所有的url路径均跳转到index.html,需要设置为true,否则比如访问localhost:8888,就跳转不到/home页
17 | inline: true, // 是否实时刷新,即代码有更改,自动刷新浏览器
18 | progress: true, // 在控制台输出webpack的编译进度
19 | stats: {
20 | colors: true // 不同类型的信息用不同的颜色显示
21 | },
22 | headers: {
23 | 'Access-Control-Allow-Origin': '*'
24 | }
25 | }));
26 |
27 | app.use(require('webpack-hot-middleware')(compiler));
28 |
29 | // 代理服务器
30 | app.use('/common', proxyMiddleware({
31 | target: 'http://admin.sosout.com',
32 | changeOrigin: true
33 | }));
34 |
35 | //将其他路由,全部返回index.html
36 | app.get('*', function(req, res) {
37 | res.sendFile(__dirname + '/index.html')
38 | });
39 |
40 |
41 | var port = process.env.PORT || 8082;
42 |
43 | /* 启动服务 */
44 | app.listen(port, 'localhost', function() {
45 | console.log('成功开启'+ port +'端口');
46 | var uri = 'http://localhost:' + port;
47 | console.log('Listening at ' + uri + '\n');
48 | opn(uri);
49 | });
--------------------------------------------------------------------------------
/src/app.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // react核心,用到jsx的地方,都需要这个
2 | import ReactDOM, { render } from 'react-dom'; // 渲染组件时需要
3 | import { Provider } from 'react-redux'; // react和redux连接的桥梁,就是这个Provider
4 | import store from './redux/store/store'; // 引入sotre
5 | import route from './router/route'; // 所有定义好的路由
6 |
7 | // babel本身只能转换ES6语法,但ES6新增的Map、Set、Generator等新功能不会转换,所以需要此插件
8 | import 'babel-polyfill';
9 |
10 | // 公共样式
11 | import './style/common.less';
12 |
13 | store.subscribe(() => { // 监听state变化
14 | // console.log(store.getState());
15 | });
16 |
17 | // 创建根组件
18 | render(
19 |
20 | {route}
21 | ,
22 | document.body.appendChild(document.createElement('div'))
23 | );
--------------------------------------------------------------------------------
/src/assets/login-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiubug/react-antd/663b085ae9559701b450d547834d2ffafe2c789b/src/assets/login-bg.jpg
--------------------------------------------------------------------------------
/src/component/bcrumb/bcrumb.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { is, fromJS } from 'immutable';
3 | import { Breadcrumb, Icon } from 'antd';
4 | import { Link } from 'react-router';
5 | import styles from './style/bcrumb.less';
6 |
7 | /**
8 | * 公共面包屑
9 | *
10 | * @export
11 | * @class Bcrumb
12 | * @extends {Component}
13 | */
14 | export class Bcrumb extends Component {
15 | constructor(props) {
16 | super(props); //后才能用this获取实例化对象
17 | }
18 | render() {
19 | return (
20 |
21 |
22 | 主页
23 |
24 |
25 | { this.props.title }
26 |
27 |
28 | )
29 | }
30 | }
--------------------------------------------------------------------------------
/src/component/bcrumb/style/bcrumb.less:
--------------------------------------------------------------------------------
1 | /* 面包屑 start */
2 |
3 | .bread-crumb {
4 | height: 64px;
5 | line-height: 64px;
6 | color: #666 !important;
7 | }
8 |
9 | .ant-breadcrumb>span:last-child {
10 | color: #999;
11 | font-weight: 400;
12 | }
13 |
14 | /* 面包屑 end */
--------------------------------------------------------------------------------
/src/component/echarts/lineChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS} from 'immutable';
4 | // 引入 ECharts 主模块
5 | import echarts from 'echarts/lib/echarts';
6 | // 引入折线图
7 | import 'echarts/lib/chart/line';
8 | // 标题插件
9 | import 'echarts/lib/component/title';
10 | import 'echarts/lib/component/tooltip';
11 | import 'echarts/lib/component/dataZoom';
12 | import 'echarts/lib/component/graphic';
13 | import 'echarts/lib/component/grid';
14 |
15 |
16 |
17 | /* 以类的方式创建一个组件 */
18 | class Linechart extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | myChart: null,
23 | symbolSize: 20
24 | }
25 | }
26 | shouldComponentUpdate(nextProps, nextState) {
27 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
28 | }
29 | componentDidMount() {
30 | this.initLine();
31 | }
32 | initLine() {
33 | const that = this;
34 | const { data } = that.props //外部传入的data数据
35 | const { symbolSize } = that.state;
36 | let myChart = echarts.init(that.lineChart) //初始化echarts
37 | that.setState({
38 | myChart: myChart
39 | });
40 | //我们要定义一个setPieOption函数将data传入option里面
41 | let options = that.setPieOption(data);
42 | let updatePosition = that.updatePosition;
43 | //设置options
44 | myChart.setOption(options);
45 | setTimeout(function () {
46 | // Add shadow circles (which is not visible) to enable drag.
47 | myChart.setOption({
48 | graphic: echarts.util.map(data, function (item, dataIndex) {
49 | return {
50 | type: 'circle',
51 | position: myChart.convertToPixel('grid', item),
52 | shape: {
53 | cx: 0,
54 | cy: 0,
55 | r: symbolSize / 2
56 | },
57 | invisible: true,
58 | draggable: true,
59 | ondrag: echarts.util.curry(that.onPointDragging, dataIndex, data, myChart),
60 | onmousemove: echarts.util.curry(that.showTooltip, dataIndex),
61 | onmouseout: echarts.util.curry(that.hideTooltip, dataIndex),
62 | z: 100
63 | };
64 | })
65 | });
66 | }, 0);
67 | window.addEventListener('resize', function() {
68 | that.updatePosition;
69 | });
70 | myChart.on('dataZoom', function() {
71 | that.updatePosition;
72 | });
73 | }
74 | //一个基本的echarts图表配置函数
75 | setPieOption = (data) => {
76 | const { symbolSize } = this.state;
77 | return {
78 | title: {
79 | text: '尝试拖动这些点'
80 | },
81 | tooltip: {
82 | triggerOn: 'none',
83 | formatter: function (params) {
84 | return 'X: ' + params.data[0].toFixed(2) + '
Y: ' + params.data[1].toFixed(2);
85 | }
86 | },
87 | grid: {},
88 | xAxis: {
89 | min: -100,
90 | max: 80,
91 | type: 'value',
92 | axisLine: {onZero: false}
93 | },
94 | yAxis: {
95 | min: -30,
96 | max: 60,
97 | type: 'value',
98 | axisLine: {onZero: false}
99 | },
100 | dataZoom: [{
101 | type: 'slider',
102 | xAxisIndex: 0,
103 | filterMode: 'empty'
104 | }, {
105 | type: 'slider',
106 | yAxisIndex: 0,
107 | filterMode: 'empty'
108 | }, {
109 | type: 'inside',
110 | xAxisIndex: 0,
111 | filterMode: 'empty'
112 | }, {
113 | type: 'inside',
114 | yAxisIndex: 0,
115 | filterMode: 'empty'
116 | }],
117 | series: [{
118 | id: 'a',
119 | type: 'line',
120 | smooth: true,
121 | symbolSize: symbolSize,
122 | data: data
123 | }]
124 | }
125 | }
126 | updatePosition = () => {
127 | const { data } = this.props //外部传入的data数据
128 | const { myChart } = this.state;
129 | myChart.setOption({
130 | graphic: echarts.util.map(data, function (item, dataIndex) {
131 | return {
132 | position: myChart.convertToPixel('grid', item)
133 | };
134 | })
135 | });
136 | }
137 | onPointDragging(dataIndex, data, myChart) {
138 | data[dataIndex] = myChart.convertFromPixel('grid', this.position);
139 | // Update data
140 | myChart.setOption({
141 | series: [{
142 | id: 'a',
143 | data: data
144 | }]
145 | });
146 | }
147 | hideTooltip = (dataIndex) => {
148 | const { myChart } = this.state;
149 | myChart.dispatchAction({
150 | type: 'hideTip'
151 | });
152 | }
153 | showTooltip = (dataIndex) => {
154 | const { myChart } = this.state;
155 | myChart.dispatchAction({
156 | type: 'showTip',
157 | seriesIndex: 0,
158 | dataIndex: dataIndex
159 | });
160 | }
161 | render() {
162 | return (
163 | {this.lineChart=ref}} style={{width: "100%", height: "500px"}}>
164 | );
165 | }
166 | }
167 |
168 | export default Linechart;
169 |
--------------------------------------------------------------------------------
/src/component/layout/layout.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes。PropTypes是用于检查props参数类型,可有可无,最好是有
2 | import pureRender from 'pure-render-decorator';
3 | import { History, Link } from 'react-router';
4 | import { connect } from 'react-redux';
5 | import { is, fromJS } from 'immutable';
6 | import Config from '../../config/index';
7 |
8 | // 公共头部
9 | import { Lheader } from './lheader';
10 | // 公共菜单
11 | import { Lmenu } from './lmenu';
12 | // 公共底部
13 | import { Lfooter } from './lfooter';
14 |
15 | // 布局样式
16 | import './style/layout.less';
17 |
18 | import { Layout, Menu, Breadcrumb, Icon } from 'antd';
19 | const { Content, Footer, Sider } = Layout;
20 | const SubMenu = Menu.SubMenu;
21 |
22 | /**
23 | * (路由根目录组件,显示当前符合条件的组件)
24 | *
25 | * @class Main
26 | * @extends {Component}
27 | */
28 | class Main extends Component {
29 | constructor(props) {
30 | super(props);
31 | const collapsed = Config.localItem('COLLAPSED') == 'YES' ? true : false;
32 | this.state = {
33 | collapsed: collapsed,
34 | mode: collapsed ? 'vertical' : 'inline',
35 | };
36 | }
37 | onCollapse = (collapsed) => {
38 | if(collapsed) Config.localItem('COLLAPSED', 'YES'); else Config.localItem('COLLAPSED', 'NO');
39 | this.setState({
40 | collapsed,
41 | mode: collapsed ? 'vertical' : 'inline'
42 | });
43 | }
44 | toggle = (collapsed) => {
45 | if(collapsed) Config.localItem('COLLAPSED', 'YES'); else Config.localItem('COLLAPSED', 'NO');
46 | this.setState({
47 | collapsed: collapsed,
48 | mode: collapsed ? 'vertical' : 'inline'
49 | });
50 | }
51 | shouldComponentUpdate(nextProps, nextState) {
52 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
53 | }
54 | render() {
55 | // 这个组件是一个包裹组件,所有的路由跳转的页面都会以this.props.children的形式加载到本组件下
56 | return (
57 |
58 |
59 |
60 |
61 |

62 |
{Config.logoText}
63 |
64 |
65 |
66 |
67 |
68 | this.toggle(collapsed) } />
69 |
70 | {this.props.children}
71 |
72 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
79 | export default Main;
--------------------------------------------------------------------------------
/src/component/layout/lfooter.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { is, fromJS } from 'immutable';
3 |
4 | import Config from '../../config/index';
5 |
6 | import { Layout } from 'antd';
7 | const { Footer } = Layout;
8 |
9 | /**
10 | * 公共底部
11 | *
12 | * @export
13 | * @class Lfooter
14 | * @extends {Component}
15 | */
16 | export class Lfooter extends Component {
17 | constructor(props, context) {
18 | super(props, context); //后才能用this获取实例化对象
19 | }
20 | shouldComponentUpdate(nextProps, nextState) {
21 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
22 | }
23 | render() {
24 | return (
25 |
28 | )
29 | }
30 | }
--------------------------------------------------------------------------------
/src/component/layout/lheader.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { Router } from 'react-router';
3 | import { is, fromJS } from 'immutable';
4 | import { Layout, Menu, Icon } from 'antd';
5 | import Config from '../../config/index';
6 | const SubMenu = Menu.SubMenu;
7 | const { Header } = Layout;
8 |
9 | /**
10 | * 公共头部
11 | *
12 | * @export
13 | * @class Lheader
14 | * @extends {Component}
15 | */
16 | export class Lheader extends Component {
17 | constructor(props, context) {
18 | super(props, context); //后才能用this获取实例化对象
19 | }
20 | shouldComponentUpdate(nextProps, nextState) {
21 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
22 | }
23 | toggle = () => {
24 | this.props.toggle(!this.props.collapsed);
25 | }
26 | logout= (e) => {
27 | // 模拟退出
28 | if(e.key == 'logout') {
29 | Config.removeLocalItem(Config.localKey.userToken);
30 | this.context.router.push({
31 | pathname: '/login'
32 | });
33 | }
34 | }
35 | render() {
36 | return (
37 |
38 |
39 |
44 |
45 | )
46 | }
47 | }
48 |
49 | Lheader.contextTypes = {
50 | router: React.PropTypes.object.isRequired
51 | };
--------------------------------------------------------------------------------
/src/component/layout/lmenu.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { is, fromJS } from 'immutable';
3 | import Config from '../../config/index';
4 | import { Router, Route, IndexRoute, browserHistory, Link } from 'react-router';
5 | import { Layout, Menu, Icon } from 'antd';
6 | const SubMenu = Menu.SubMenu;
7 | /**
8 | * 公共菜单
9 | *
10 | * @export
11 | * @class Lmenu
12 | * @extends {Component}
13 | */
14 | export class Lmenu extends Component {
15 | constructor(props, context) {
16 | super(props, context); //后才能用this获取实例化对象
17 | const openKeys = Config.localItem('OPENKEY') ? [Config.localItem('OPENKEY')] : [];
18 | this.state = {
19 | openKeys: openKeys
20 | };
21 | }
22 | onOpenChange = (openKeys) => {
23 | const state = this.state;
24 | const latestOpenKey = openKeys.find(key => !(state.openKeys.indexOf(key) > -1));
25 | const latestCloseKey = state.openKeys.find(key => !(openKeys.indexOf(key) > -1));
26 |
27 | let nextOpenKeys = [];
28 | if (latestOpenKey) {
29 | nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
30 | }
31 | if (latestCloseKey) {
32 | nextOpenKeys = this.getAncestorKeys(latestCloseKey);
33 | }
34 | Config.localItem('OPENKEY', nextOpenKeys);
35 | this.setState({ openKeys: nextOpenKeys });
36 | }
37 | getAncestorKeys = (key) => {
38 | const map = {
39 | sub3: ['sub2'],
40 | };
41 | return map[key] || [];
42 | }
43 | render() {
44 | const defaultSelectedKey = process.env.NODE_ENV !== 'production' ? [location.pathname.split('/')[location.pathname.split('/').length - 1] || 'home'] : [location.hash.split('/')[location.hash.split('/').length - 1].split('?')[0] || 'home'];
45 | return (
46 |
89 | )
90 | }
91 | }
--------------------------------------------------------------------------------
/src/component/layout/style/layout.less:
--------------------------------------------------------------------------------
1 | /* 页面布局start */
2 | .layout .ant-layout-sider-collapsed .ant-menu-submenu-vertical > .ant-menu-submenu-title:after {
3 | display: none;
4 | }
5 |
6 | .ant-layout-sider-trigger {
7 | background: #494949;
8 | }
9 |
10 | .layout {
11 | position: relative;
12 | height: 100vh;
13 | .ant-layout-sider-collapsed {
14 | .anticon {
15 | font-size: 16px;
16 | margin-left: 8px;
17 | }
18 | .nav-text {
19 | display: none;
20 | }
21 | }
22 | .layout-logo {
23 | position: relative;
24 | text-align: center;
25 | height: 40px;
26 | line-height: 40px;
27 | cursor: pointer;
28 | margin: 24px 0;
29 | overflow: hidden;
30 | .logo-img {
31 | width: 40px;
32 | margin-right: 8px;
33 | }
34 | .logo-text {
35 | vertical-align: text-bottom;
36 | font-size: 16px;
37 | text-transform: uppercase;
38 | display: inline-block;
39 | color: #999;
40 | }
41 | }
42 | .layout-content {
43 | margin: 0 16px;
44 | }
45 | }
46 |
47 | .layout-header {
48 | position: relative;
49 | box-shadow: 4px 4px 40px 0 rgba(0, 0, 0, .05);
50 | background: #fff;
51 | height: 46px;
52 | line-height: 46px;
53 | padding: 0;
54 | .trigger {
55 | height: 47px;
56 | width: 47px;
57 | line-height: 47px;
58 | text-align: center;
59 | font-size: 18px;
60 | cursor: pointer;
61 | position: absolute;
62 | -webkit-transition: all .3s cubic-bezier(.55, .055, .675, .19);
63 | transition: all .3s cubic-bezier(.55, .055, .675, .19);
64 | }
65 | .layout-header-menu {
66 | float: right;
67 | line-height: 45px;
68 | text-align: center;
69 | }
70 | }
71 |
72 | .layout-footer {
73 | text-align: center;
74 | height: 48px;
75 | line-height: 48px;
76 | padding: 0;
77 | background: #fff;
78 | margin-top: 20px;
79 | }
80 |
81 |
82 | /* 页面布局end */
83 |
--------------------------------------------------------------------------------
/src/config/index.jsx:
--------------------------------------------------------------------------------
1 | const Main = {
2 | target: process.env.NODE_ENV !== 'production' ? 'http://admin.sosout.com' : 'http://admin.sosout.com', //目标网站
3 | name: 'Ant Design Admin',
4 | prefix: 'antdAdmin',
5 | footerText: 'Ant Design Admin 版权所有 © 2017 由 sosout 支持',
6 | logoSrc: 'https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg',
7 | logoText: 'Antd Admin',
8 | needLogin: true,
9 | message: { // 提示信息
10 | usernameInput: '请输入用户名',
11 | usernameEng: '用户名必须是字母',
12 | passwordInput: '请输入密码',
13 | loginError: '用户名或者密码错误!'
14 | },
15 | localKey: { // 本地存储Key
16 | userToken: 'USER_AUTHORIZATION'
17 | },
18 | /**
19 | * 只能输入英文
20 | *
21 | * @param {any} str
22 | * @returns
23 | */
24 | checkEng(str) {
25 | var reg = new RegExp(/^[A-Za-z]+$/);
26 | return str && reg.test(str);
27 | },
28 | /**
29 | * 参数格式化
30 | *
31 | * @param {any} data
32 | * @returns
33 | */
34 | paramFormat(data) {
35 | let paramArr = [];
36 | let paramStr = '';
37 | for(let attr in data) {
38 | paramArr.push(attr + '=' + data[attr]);
39 | }
40 | paramStr = paramArr.join('&');
41 | return paramStr ? '?' + paramStr : paramStr;
42 | },
43 | /**
44 | * 本地数据存储或读取
45 | *
46 | * @param {any} key
47 | * @param {any} value
48 | * @returns
49 | */
50 | localItem(key, value) {
51 | if(arguments.length == 1) {
52 | return localStorage.getItem(key) && localStorage.getItem(key) !== 'null' ? localStorage.getItem(key) : null;
53 | } else {
54 | return localStorage.setItem(key, value);
55 | }
56 | },
57 | /**
58 | * 删除本地数据
59 | *
60 | * @param {any} k
61 | * @returns
62 | */
63 | removeLocalItem(key) {
64 | if(arguments.length == 1) {
65 | return localStorage.removeItem(key);
66 | } else {
67 | return localStorage.clear();
68 | }
69 | }
70 | };
71 |
72 | export default Main;
73 |
--------------------------------------------------------------------------------
/src/containers/adver/adverIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS} from 'immutable';
4 |
5 | /* 以类的方式创建一个组件 */
6 | class Adver extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
12 | }
13 | render() {
14 | return (
15 |
16 | 广告管理
17 |
18 | );
19 | }
20 | }
21 |
22 | export default Adver;
23 |
--------------------------------------------------------------------------------
/src/containers/charts/lines.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { Row, Col, Card } from 'antd';
3 | import { connect } from 'react-redux';
4 | import { is, fromJS} from 'immutable';
5 |
6 | // 公共面包屑
7 | import { Bcrumb } from '../../component/bcrumb/bcrumb';
8 |
9 | // 引入折线图
10 | import LineChart from '../../component/echarts/lineChart';
11 |
12 | /* 以类的方式创建一个组件 */
13 | class Lines extends Component {
14 | constructor(props) {
15 | super(props);
16 | }
17 | shouldComponentUpdate(nextProps, nextState) {
18 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
19 | }
20 | render() {
21 | const data = [[15, 0], [-50, 10], [-56.5, 20], [-46.5, 30], [-22.1, 40]];
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | export default Lines;
38 |
--------------------------------------------------------------------------------
/src/containers/general/buttonIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { Link } from 'react-router';
4 | import { is, fromJS } from 'immutable';
5 | import { Icon, Row, Col, Card, Button, Radio } from 'antd';
6 | const ButtonGroup = Button.Group;
7 |
8 | // 公共面包屑
9 | import { Bcrumb } from '../../component/bcrumb/bcrumb';
10 |
11 | /* 以类的方式创建一个组件 */
12 | class Main extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | loading: false,
17 | iconLoading: false,
18 | delayLoading: false,
19 | size: 'default'
20 | };
21 | }
22 | handleSizeChange = (e) => {
23 | this.setState({ size: e.target.value });
24 | }
25 | enterLoading = () => {
26 | this.setState({ loading: true });
27 | }
28 | enterIconLoading = () => {
29 | this.setState({ iconLoading: true });
30 | }
31 | delayLoading = () => {
32 | this.setState({
33 | delayLoading: true,
34 | });
35 | setTimeout(() => this.setState({
36 | delayLoading: false,
37 | }), 150);
38 | }
39 | render() {
40 | const size = this.state.size;
41 | return (
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 | 添加 loading 属性即可让按钮处于加载状态,最后两个按钮演示点击后进入加载状态。
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 当需要在 Button 内嵌入 Icon 时,可以设置 icon 属性,或者直接在 Button 内使用 Icon 组件。如果想控制 Icon 具体的位置,只能直接使用 Icon 组件,而非 icon 属性。
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | 可以将多个 Button 放入 Button.Group 的容器中。通过设置 size 为 large small 分别把按钮组合设为大、小尺寸。若不设置 size,则尺寸为中。
111 | Basic
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | With Icon
136 |
137 |
138 |
139 |
142 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | 按钮有大、中、小三种尺寸。通过设置 size 为 large small 分别把按钮设为大、小尺寸。若不设置 size,则尺寸为中。
159 |
160 | Large
161 | Default
162 | Small
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
175 |
178 |
179 |
180 |
181 |
182 |
183 | );
184 | }
185 | }
186 |
187 | export default Main;
188 |
189 |
190 |
--------------------------------------------------------------------------------
/src/containers/general/iconIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS } from 'immutable';
4 |
5 | /* 以类的方式创建一个组件 */
6 | class Main extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
12 | }
13 | render() {
14 | return (
15 |
16 | 图标
17 |
18 | );
19 | }
20 | }
21 |
22 | export default Main;
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/containers/home/homeIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import pureRender from 'pure-render-decorator';
3 | import { is, fromJS} from 'immutable';
4 | import { Router, Route, IndexRoute, browserHistory, History, Link } from 'react-router';
5 | import { connect } from 'react-redux';
6 |
7 | // 公共面包屑
8 | import { Bcrumb } from '../../component/bcrumb/bcrumb';
9 |
10 | import styles from './style/home.less';
11 |
12 | import { Icon, Row, Col, Card, Steps, Button, message } from 'antd';
13 | const Step = Steps.Step;
14 |
15 |
16 | /* 以类的方式创建一个组件 */
17 | class Main extends Component {
18 | constructor(props) {
19 | super(props);
20 | this.state = {
21 | current: 0
22 | };
23 | }
24 | next() {
25 | const current = this.state.current + 1;
26 | this.setState({ current });
27 | }
28 | prev() {
29 | const current = this.state.current - 1;
30 | this.setState({ current });
31 | }
32 | render() {
33 | let linkHtml = '';
34 | const steps = [{
35 | title: '下载',
36 | content: '$ git clone
$ git clone https://github.com/sosout/react-antd.git
$ cd react-antd
',
37 | }, {
38 | title: '安装',
39 | content: '// 安装前请先确保已安装node和npm
// 安装成功后,再安装依赖,如果之前有用npm安装过,请先删掉node_modules
$ yarn install
',
40 | }, {
41 | title: '运行',
42 | content: '$ yarn run dev (正常编译模式,注意:index.html里必须手动引用app.css,,否则没有样式)
$ yarn run hot (热替换编译模式,注意:热替换模式下index.html里去掉引用app.css)
$ yarn run dist (发布生产版本,对代码进行混淆压缩,提取公共代码,分离css文件)
',
43 | }];
44 | const { current } = this.state;
45 | return (
46 |
47 |
48 |
49 |
50 | 如果觉得不错的话,请star一下吧 😊} bordered={false}>
51 | 本工程主要基于react + redux + immutable + less + ES6/7 + webpack + fetch + react-router + antd(1.x)实现的SPA后台管理系统模板。
52 | 编码时间:8:00——9:30, 下班时间——24:00,其他时间要工作。代码未优化,处女座代码必须要优化。由于代码延后,先向大家说声抱歉。您有什么问题可以私信我segmentfault。
53 |
54 |
55 |
56 | {steps.map(item => )}
57 |
58 |
59 |
60 | {
61 | this.state.current < steps.length - 1
62 | &&
63 |
64 | }
65 | {
66 | this.state.current === steps.length - 1
67 | &&
68 |
69 | }
70 | {
71 | this.state.current > 0
72 | &&
73 |
76 | }
77 |
78 |
79 |
80 | 在浏览器地址栏输入http://127.0.0.1:8888
81 |
82 |
83 | 此项目是本人空余时间搭建的。希望大家提供宝贵的意见和建议,谢谢。
84 |
85 |
86 |
87 |
88 | );
89 | }
90 | }
91 |
92 | export default Main;
--------------------------------------------------------------------------------
/src/containers/home/style/home.less:
--------------------------------------------------------------------------------
1 | .home-container {
2 |
3 | }
4 |
5 | .steps-content {
6 | margin-top: 16px;
7 | border: 1px dashed #e9e9e9;
8 | border-radius: 6px;
9 | background-color: #fafafa;
10 | min-height: 150px;
11 | text-align: left;
12 | padding: 20px;
13 | }
14 |
15 | .steps-action {
16 | margin-top: 24px;
17 | }
18 |
--------------------------------------------------------------------------------
/src/containers/login/login.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'; // 引入了React
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { is, fromJS } from 'immutable';
5 | import Config from '../../config/index';
6 |
7 | import { initialState, goLogin } from '../../redux/action/login/loginAction';
8 |
9 | import styles from './style/login.less';
10 |
11 | import { Spin, Form, Input, Button, message } from 'antd';
12 | const FormItem = Form.Item;
13 |
14 | /* 以类的方式创建一个组件 */
15 | class Login extends Component {
16 | constructor(props) {
17 | super(props);
18 | this.state = {
19 | passwordDirty: false,
20 | loginBtnText: '登录'
21 | };
22 | }
23 | /**
24 | * 在初始化渲染执行之后立刻调用一次,仅客户端有效(服务器端不会调用)。
25 | * 在生命周期中的这个时间点,组件拥有一个 DOM 展现,
26 | * 你可以通过 this.getDOMNode() 来获取相应 DOM 节点。
27 | */
28 | componentDidMount() {
29 | const {actions} = this.props;
30 | // 初始化数据
31 | actions.initialState();
32 | }
33 | handleSubmit = (e) => { // 登录
34 | e.preventDefault();
35 | const {actions, form} = this.props;
36 | form.validateFieldsAndScroll((err, values) => {
37 | if (!err) {
38 | let username = values.username, // 用户名
39 | password = values.password, // 密码
40 | loginParams = { // 登录参数
41 | username: username,
42 | password: password
43 | };
44 | actions.goLogin(loginParams);
45 | }
46 | });
47 | }
48 | // 验证用户名
49 | checkUsername = (rule, value, callback) => {
50 | const form = this.props.form;
51 | if (!value) {
52 | callback();
53 | } else if (!Config.checkEng(value)) {
54 | callback(Config.message.usernameEng);
55 | } else {
56 | callback();
57 | }
58 | }
59 | // 验证密码
60 | checkPassword = (rule, value, callback) => {
61 | const form = this.props.form;
62 | if (value && this.state.passwordDirty) {
63 | form.validateFields(['confirm'], { force: true });
64 | }
65 | callback();
66 | }
67 | render() {
68 | const { loading, loginInfo, form } = this.props;
69 | const getFieldDecorator = form.getFieldDecorator;
70 | return (
71 |
72 |
73 |
74 |
75 |

76 |
Ant Design
77 |
78 |
97 |
98 |
99 |
100 | );
101 | }
102 | }
103 |
104 | const LoginForm = Form.create()(Login);
105 |
106 | // 将 store 中的数据作为 props 绑定到 LoginForm 上
107 | const mapStateToProps = (state, ownProps) => {
108 | let { Common, Login } = state;
109 | return {
110 | loading: Common.loading,
111 | loginInfo: Login.loginInfo
112 | }
113 | }
114 |
115 | // 将 action 作为 props 绑定到 Product 上。
116 | const mapDispatchToProps = (dispatch, ownProps) => ({
117 | actions: bindActionCreators({ initialState, goLogin }, dispatch)
118 | });
119 |
120 | const Main = connect(mapStateToProps, mapDispatchToProps)(LoginForm); // 连接redux
121 |
122 | export default Main;
123 |
--------------------------------------------------------------------------------
/src/containers/login/style/login.less:
--------------------------------------------------------------------------------
1 | .login-container {
2 | position: relative;
3 | width: 100%;
4 | height: 100vh;
5 | background: url(../../../assets/login-bg.jpg) no-repeat center center;
6 | background-size: cover;
7 | }
8 | .login-form {
9 | position: absolute;
10 | top: 50%;
11 | left: 50%;
12 | margin: -160px 0 0 -160px;
13 | width: 320px;
14 | height: 320px;
15 | padding: 36px;
16 | box-shadow: 0 0 100px rgba(0, 0, 0, .08);
17 | background-color: #fff;
18 | border-radius: 4px;
19 | button {
20 | width: 100%;
21 | }
22 | .login-account {
23 | color: #999;
24 | text-align: center;
25 | margin-top: 16px;
26 | span {
27 | &:first-child {
28 | margin-right: 16px;
29 | }
30 | }
31 | }
32 | }
33 |
34 | .login-logo {
35 | text-align: center;
36 | height: 40px;
37 | line-height: 40px;
38 | cursor: pointer;
39 | margin-bottom: 24px;
40 | img {
41 | width: 40px;
42 | margin-right: 8px;
43 | }
44 | span {
45 | vertical-align: text-bottom;
46 | font-size: 16px;
47 | text-transform: uppercase;
48 | display: inline-block;
49 | }
50 | }
51 |
52 | .ant-spin-container,
53 | .ant-spin-nested-loading {
54 | height: 100%;
55 | }
56 |
--------------------------------------------------------------------------------
/src/containers/setting/settingIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS } from 'immutable';
4 |
5 | /* 以类的方式创建一个组件 */
6 | class Main extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
12 | }
13 | render() {
14 | return (
15 |
16 | 系统设置
17 |
18 | );
19 | }
20 | }
21 |
22 | Main.contextTypes = {
23 | };
24 |
25 | export default Main;
--------------------------------------------------------------------------------
/src/containers/ui/oneIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS } from 'immutable';
4 |
5 | /* 以类的方式创建一个组件 */
6 | class Main extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
12 | }
13 | render() {
14 | return (
15 |
16 | 组件一
17 |
18 | );
19 | }
20 | }
21 |
22 | Main.contextTypes = {
23 | };
24 |
25 | export default Main;
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/containers/ui/twoIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS } from 'immutable';
4 |
5 | /* 以类的方式创建一个组件 */
6 | class Main extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
12 | }
13 | render() {
14 | return (
15 |
16 | 组件二
17 |
18 | );
19 | }
20 | }
21 |
22 | Main.contextTypes = {
23 | };
24 |
25 | export default Main;
26 |
--------------------------------------------------------------------------------
/src/containers/user/style/user.less:
--------------------------------------------------------------------------------
1 | .user-container {
2 |
3 | }
--------------------------------------------------------------------------------
/src/containers/user/userIndex.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'; // 引入了React和PropTypes
2 | import { connect } from 'react-redux';
3 | import { is, fromJS } from 'immutable';
4 | import Config from '../../config/index';
5 |
6 | // 公共面包屑
7 | import { Bcrumb } from '../../component/bcrumb/bcrumb';
8 |
9 | import styles from './style/user.less';
10 |
11 | /* 以类的方式创建一个组件 */
12 | class Main extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | loading: false,
17 | userInfo: []
18 | };
19 | }
20 | shouldComponentUpdate(nextProps, nextState) {
21 | return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
22 | }
23 | getUserInfo = (params={}) => { // 加载用户列表信息
24 | this.setState({ loading: true });
25 | this.props.getData('/user/userList', params, (res) => {
26 | this.saveLoading(false);
27 | if(res.code == Config.errorCode.success) {
28 | const pagination = { ...this.state.pagination };
29 | pagination.total = res.recordsTotal; // 总页数
30 | pagination.current = params.page; // 当前页数
31 | this.setState({
32 | userInfo: res.data,
33 | pagination
34 | });
35 | } else {
36 | message.error(res.msg);
37 | }
38 | }, 'userUsers', 'GET');
39 | }
40 | render() {
41 | const columns = [{
42 | title: '用户名',
43 | dataIndex: 'userName',
44 | key: 'userName'
45 | }, {
46 | title: '姓名',
47 | dataIndex: 'name',
48 | key: 'name'
49 | }, {
50 | title: '邮箱',
51 | dataIndex: 'email',
52 | key: 'email'
53 | }, {
54 | title: '角色',
55 | dataIndex: 'roleName',
56 | key: 'roleName'
57 | }, {
58 | title: '创建时间',
59 | dataIndex: 'createDate',
60 | key: 'createDate',
61 | render: (text, record) => (
62 |
63 | {Config.formatDateTime(text)}
64 |
65 | )
66 | }, {
67 | title: '操作',
68 | key: 'action',
69 | render: (text, record) => (
70 | (record.roleLevel&&record.roleLevel>curRoleLevel) ?
71 |
72 | 编辑
73 |
74 | {record.available==1?'停用':'启用'}
75 |
76 | 删除
77 |
78 | :
79 |
80 | 编辑
81 |
82 | {record.available==1?'停用':'启用'}
83 |
84 | 删除
85 |
86 | )
87 | }];
88 |
89 | let userInfo = this.state.userInfo; // 用户信息数据
90 | return (
91 |
92 |
93 | 用户管理
94 |
95 | );
96 | }
97 | }
98 |
99 | Main.contextTypes = {
100 | };
101 |
102 | export default Main;
103 |
104 |
--------------------------------------------------------------------------------
/src/redux/action/index.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 公共 action
3 | * @return
4 | */
5 |
6 | import { LOADING } from '../constants/dispatchTypes';
7 |
8 | /**
9 | * 用于页面和区块的加载中状态
10 | * @return
11 | */
12 | const loading = (loading) => {
13 | return {
14 | type: LOADING,
15 | loading
16 | }
17 | }
18 |
19 | export { loading };
20 |
--------------------------------------------------------------------------------
/src/redux/action/login/loginAction.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 登录界面action
3 | * @return
4 | */
5 | import { Message } from 'antd';
6 | import { browserHistory } from 'react-router';
7 | import Config from '../../../config/index';
8 | import { RES_LOGIN, INITIAL_STATE } from '../../constants/loginTypes';
9 | import LoginService from '../../../services/loginService';
10 | import { loading } from '../index';
11 |
12 | /**
13 | * 登录成功
14 | * @return
15 | */
16 | const resLogin = (res) => {
17 | return {
18 | type: RES_LOGIN,
19 | res
20 | }
21 | }
22 |
23 | /**
24 | * 初始化数据
25 | * @return
26 | */
27 | export const initialState = () => {
28 | return {
29 | type: INITIAL_STATE
30 | }
31 | }
32 |
33 | /**
34 | * 登录界面
35 | * @param {username} 用户名
36 | * @param {password} 密码
37 | * @return {登录信息}
38 | */
39 |
40 | export const goLogin = (params) => {
41 | return dispatch => {
42 | dispatch(loading(true));
43 | LoginService.goLogin(params, (res) => {
44 | dispatch(loading(false));
45 | dispatch(resLogin(res));
46 | if(res.length > 0) {
47 | Config.localItem(Config.localKey.userToken, (new Date()).getTime()); // 模拟登录成功返回的Token
48 | browserHistory.push('/home');
49 | } else {
50 | Message.error('用户名或密码错误');
51 | }
52 | })
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/redux/constants/dispatchTypes.jsx:
--------------------------------------------------------------------------------
1 | export const LOADING = 'LOADING'; // 用于页面和区块的加载中状态。
--------------------------------------------------------------------------------
/src/redux/constants/loginTypes.jsx:
--------------------------------------------------------------------------------
1 | export const RES_LOGIN = 'RES_LOGIN'; // 登录成功
2 | export const INITIAL_STATE = 'INITIAL_STATE'; // 初始化数据
--------------------------------------------------------------------------------
/src/redux/reducer/index.jsx:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 | import { LOADING } from '../constants/dispatchTypes';
3 |
4 | import Login from './login/loginReducer'; // 登录界面
5 |
6 | // 初始化state数据
7 | const initialState = {
8 | loading: false
9 | };
10 |
11 | /**
12 | * 公共reducer
13 | * @return
14 | */
15 | const Common = (state = initialState, action) => {
16 | switch(action.type) {
17 | case LOADING: // 用于页面和区块的加载中状态
18 | return fromJS(state).merge({loading: action.loading}).toJS();
19 | default:
20 | return state;
21 | }
22 | }
23 |
24 | export { Common, Login};
--------------------------------------------------------------------------------
/src/redux/reducer/login/loginReducer.jsx:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 | import { INITIAL_STATE, RES_LOGIN } from '../../constants/loginTypes';
3 |
4 | // 初始化state数据
5 | const initialState = {
6 | loginInfo: []
7 | };
8 |
9 | /**
10 | * 登录界面reducer
11 | * @return
12 | */
13 | const Login = (state = initialState, action) => {
14 | switch(action.type) {
15 | case INITIAL_STATE: // 初始化state数据
16 | return fromJS(state).merge({loginInfo: []}).toJS();
17 | case RES_LOGIN: // 登录成功
18 | return fromJS(state).merge({loginInfo: action.res}).toJS();
19 | default:
20 | return state;
21 | }
22 | }
23 |
24 | export default Login;
--------------------------------------------------------------------------------
/src/redux/store/store.jsx:
--------------------------------------------------------------------------------
1 | import {createStore, combineReducers, applyMiddleware} from 'redux';
2 | import * as reducer from '../reducer/index';
3 | import thunk from 'redux-thunk'; // 中间件,有了这个就可以支持异步action
4 |
5 | //创建一个 Redux store 来以存放应用中所有的 state,应用中应有且仅有一个 store。
6 |
7 | var store = createStore(
8 | combineReducers(reducer),
9 | applyMiddleware(thunk)
10 | );
11 |
12 | export default store;
--------------------------------------------------------------------------------
/src/router/route.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 疑惑一:
3 | * React createClass 和 extends React.Component 有什么区别?
4 | * 之前写法:
5 | * let app = React.createClass({
6 | * getInitialState: function(){
7 | * // some thing
8 | * }
9 | * })
10 | * ES6写法(通过es6类的继承实现时state的初始化要在constructor中声明):
11 | * class exampleComponent extends React.Component {
12 | * constructor(props) {
13 | * super(props);
14 | * this.state = {example: 'example'}
15 | * }
16 | * }
17 | */
18 |
19 | import React, {Component, PropTypes} from 'react'; // react核心
20 | import { Router, Route, Redirect, IndexRoute, browserHistory, hashHistory } from 'react-router'; // 创建route所需
21 | import Config from '../config/index';
22 | import layout from '../component/layout/layout'; // 布局界面
23 |
24 | import login from '../containers/login/login'; // 登录界面
25 |
26 | /**
27 | * (路由根目录组件,显示当前符合条件的组件)
28 | *
29 | * @class Roots
30 | * @extends {Component}
31 | */
32 | class Roots extends Component {
33 | render() {
34 | // 这个组件是一个包裹组件,所有的路由跳转的页面都会以this.props.children的形式加载到本组件下
35 | return (
36 | {this.props.children}
37 | );
38 | }
39 | }
40 |
41 | // const history = process.env.NODE_ENV !== 'production' ? browserHistory : hashHistory;
42 |
43 | // 快速入门
44 | const home = (location, cb) => {
45 | require.ensure([], require => {
46 | cb(null, require('../containers/home/homeIndex').default)
47 | }, 'home');
48 | }
49 |
50 | // 百度图表-折线图
51 | const chartLine = (location, cb) => {
52 | require.ensure([], require => {
53 | cb(null, require('../containers/charts/lines').default)
54 | }, 'chartLine');
55 | }
56 |
57 | // 基础组件-按钮
58 | const button = (location, cb) => {
59 | require.ensure([], require => {
60 | cb(null, require('../containers/general/buttonIndex').default)
61 | }, 'button');
62 | }
63 |
64 | // 基础组件-图标
65 | const icon = (location, cb) => {
66 | require.ensure([], require => {
67 | cb(null, require('../containers/general/iconIndex').default)
68 | }, 'icon');
69 | }
70 |
71 | // 用户管理
72 | const user = (location, cb) => {
73 | require.ensure([], require => {
74 | cb(null, require('../containers/user/userIndex').default)
75 | }, 'user');
76 | }
77 |
78 | // 系统设置
79 | const setting = (location, cb) => {
80 | require.ensure([], require => {
81 | cb(null, require('../containers/setting/settingIndex').default)
82 | }, 'setting');
83 | }
84 |
85 | // 广告管理
86 | const adver = (location, cb) => {
87 | require.ensure([], require => {
88 | cb(null, require('../containers/adver/adverIndex').default)
89 | }, 'adver');
90 | }
91 |
92 | // 组件一
93 | const oneui = (location, cb) => {
94 | require.ensure([], require => {
95 | cb(null, require('../containers/ui/oneIndex').default)
96 | }, 'oneui');
97 | }
98 |
99 | // 组件二
100 | const twoui = (location, cb) => {
101 | require.ensure([], require => {
102 | cb(null, require('../containers/ui/twoIndex').default)
103 | }, 'twoui');
104 | }
105 |
106 | // 登录验证
107 | const requireAuth = (nextState, replace) => {
108 | let token = (new Date()).getTime() - Config.localItem('USER_AUTHORIZATION');
109 | if(token > 7200000) { // 模拟Token保存2个小时
110 | replace({
111 | pathname: '/login',
112 | state: { nextPathname: nextState.location.pathname }
113 | });
114 | }
115 | }
116 |
117 | const RouteConfig = (
118 |
119 |
120 | // 默认加载的组件,比如访问www.test.com,会自动跳转到www.test.com/home
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | // 所有的访问,都跳转到Roots
132 | // 默认加载的组件,比如访问www.test.com,会自动跳转到www.test.com/home
133 |
134 |
135 |
136 | );
137 |
138 | export default RouteConfig;
139 |
--------------------------------------------------------------------------------
/src/services/loginService.jsx:
--------------------------------------------------------------------------------
1 | import Xhr from './xhr/index';
2 |
3 | /**
4 | * 封装ajax请求
5 | * @param {any}
6 | */
7 |
8 | class LoginService {
9 |
10 | /**
11 | * 登录界面
12 | * @param {username} 用户名
13 | * @param {password} 密码
14 | * @return {登录信息}
15 | */
16 | goLogin(params, success, fail) {
17 | return Xhr.post('/user/login', params, success, fail);
18 | }
19 | }
20 |
21 | // 实例化再导出
22 | export default new LoginService();
--------------------------------------------------------------------------------
/src/services/xhr/index.jsx:
--------------------------------------------------------------------------------
1 | import Config from '../../config/index';
2 | const Tool = {};
3 |
4 | const target = Config.target;
5 | /**
6 | * 发送ajax请求和服务器交互
7 | * @param {object} mySetting 配置ajax的配置
8 | */
9 | Tool.ajax = function (mySetting) {
10 | var setting = {
11 | url: window.location.pathname, //默认ajax请求地址
12 | async: true, //true。默认设置下,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为 false
13 | type: 'GET', //请求的方式
14 | data: {}, //发给服务器的数据
15 | dataType: 'json',
16 | success: function (text) { }, //请求成功执行方法
17 | error: function () { } //请求失败执行方法
18 | };
19 |
20 | var aData = []; //存储数据
21 | var sData = ''; //拼接数据
22 | //属性覆盖
23 | for (var attr in mySetting) {
24 | setting[attr] = mySetting[attr];
25 | }
26 | for (var attr in setting.data) {
27 | aData.push(attr + '=' + filter(setting.data[attr]));
28 | }
29 | sData = aData.join('&');
30 | setting.type = setting.type.toUpperCase();
31 |
32 | var xhr = new XMLHttpRequest();
33 | try {
34 | if (setting.type == 'GET' || setting.type == 'DELETE') { //get、delete方式请求
35 | sData = setting.url + '?' + sData;
36 | xhr.open(setting.type, sData + '&' + new Date().getTime(), setting.async);
37 | xhr.send();
38 | } else { //post方式请求
39 | xhr.open(setting.type, setting.url, setting.async);
40 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
41 | xhr.send(sData);
42 | }
43 | } catch (e) {
44 | return httpEnd();
45 | }
46 |
47 | if (setting.async) {
48 | xhr.addEventListener('readystatechange', httpEnd, false);
49 | } else {
50 | httpEnd();
51 | }
52 |
53 | function httpEnd() {
54 | if (xhr.readyState == 4) {
55 | var head = xhr.getAllResponseHeaders();
56 | var response = xhr.responseText;
57 | //将服务器返回的数据,转换成json
58 |
59 | if (/application\/json/.test(head) || setting.dataType === 'json' && /^(\{|\[)([\s\S])*?(\]|\})$/.test(response)) {
60 | response = JSON.parse(response);
61 | }
62 | if (xhr.status == 200) { // 请求成功
63 | setting.success(response, setting, xhr);
64 | } else { // 请求失败
65 | if(!xhr.withCredentials) {
66 | // 重新登录
67 | window.location.href = '/login';
68 | } else {
69 | setting.error(setting, xhr);
70 | }
71 | }
72 | }
73 | }
74 | xhr.end = function () {
75 | xhr.removeEventListener('readystatechange', httpEnd, false);
76 | }
77 |
78 | function filter(str) { //特殊字符转义
79 | str += ''; //隐式转换
80 | str = str.replace(/%/g, '%25');
81 | str = str.replace(/\+/g, '%2B');
82 | str = str.replace(/ /g, '%20');
83 | str = str.replace(/\//g, '%2F');
84 | str = str.replace(/\?/g, '%3F');
85 | str = str.replace(/&/g, '%26');
86 | str = str.replace(/\=/g, '%3D');
87 | str = str.replace(/#/g, '%23');
88 | return str;
89 | }
90 | return xhr;
91 | };
92 |
93 | /**
94 | * 封装ajax put请求
95 | * @param {string} pathname 服务器请求地址
96 | * @param {object} data 发送给服务器的数据
97 | * @param {function} success 请求成功执行方法
98 | * @param {function} error 请求失败执行方法
99 | */
100 | Tool.put = function (pathname, data, success, error) {
101 | var setting = {
102 | url: target + pathname, //默认ajax请求地址
103 | type: 'PUT', //请求的方式
104 | data: data, //发给服务器的数据
105 | success: success || function () { }, //请求成功执行方法
106 | error: error || function () { } //请求失败执行方法
107 | };
108 | return Tool.ajax(setting);
109 | };
110 |
111 | /**
112 | * 封装ajax delete请求
113 | * @param {string} pathname 服务器请求地址
114 | * @param {object} data 发送给服务器的数据
115 | * @param {function} success 请求成功执行方法
116 | * @param {function} error 请求失败执行方法
117 | */
118 | Tool.delete = function (pathname, data, success, error) {
119 | var setting = {
120 | url: target + pathname, //默认ajax请求地址
121 | type: 'DELETE', //请求的方式
122 | data: data, //发给服务器的数据
123 | success: success || function () { }, //请求成功执行方法
124 | error: error || function () { } //请求失败执行方法
125 | };
126 | return Tool.ajax(setting);
127 | };
128 |
129 | /**
130 | * 封装ajax post请求
131 | * @param {string} pathname 服务器请求地址
132 | * @param {object} data 发送给服务器的数据
133 | * @param {function} success 请求成功执行方法
134 | * @param {function} error 请求失败执行方法
135 | */
136 | Tool.post = function (pathname, data, success, error) {
137 | var setting = {
138 | url: target + pathname, //默认ajax请求地址
139 | type: 'POST', //请求的方式
140 | data: data, //发给服务器的数据
141 | success: success || function () { }, //请求成功执行方法
142 | error: error || function () { } //请求失败执行方法
143 | };
144 | return Tool.ajax(setting);
145 | };
146 |
147 | /**
148 | * 封装ajax get请求
149 | * @param {string} pathname 服务器请求地址
150 | * @param {object} data 发送给服务器的数据
151 | * @param {function} success 请求成功执行方法
152 | * @param {function} error 请求失败执行方法
153 | */
154 |
155 | Tool.get = function (pathname, data, success, error) {
156 | var setting = {
157 | url: target + pathname, //默认ajax请求地址
158 | type: 'GET', //请求的方式
159 | data: data, //发给服务器的数据
160 | success: success || function () { }, //请求成功执行方法
161 | error: error || function () { } //请求失败执行方法
162 | };
163 | return Tool.ajax(setting);
164 | };
165 |
166 | export default Tool;
--------------------------------------------------------------------------------
/src/style/common.less:
--------------------------------------------------------------------------------
1 | body {
2 | height: 100%;
3 | overflow-y: hidden;
4 | background-color: #f4f7ed;
5 | }
6 |
7 | // scrollbar
8 | ::-webkit-scrollbar-thumb {
9 | background-color: #e6e6e6;
10 | }
11 |
12 | ::-webkit-scrollbar {
13 | width: 8px;
14 | height: 8px;
15 | }
16 |
17 | :global .ant-breadcrumb {
18 | & > span {
19 | &:last-child {
20 | color: #999;
21 | font-weight: normal;
22 | }
23 | }
24 | }
25 |
26 | :global .ant-breadcrumb-link {
27 | .anticon + span {
28 | margin-left: 4px;
29 | }
30 | }
31 |
32 | :global .ant-table {
33 | .ant-table-thead > tr > th {
34 | text-align: center;
35 | }
36 | .ant-table-tbody > tr > td {
37 | text-align: center;
38 | }
39 | &.ant-table-small {
40 | .ant-table-thead > tr > th {
41 | background: #f7f7f7;
42 | }
43 | .ant-table-body > table {
44 | padding: 0;
45 | }
46 | }
47 | }
48 |
49 | :global .ant-table-pagination {
50 | float: none!important;
51 | display: table;
52 | margin: 16px auto !important;
53 | }
54 |
55 | :global .ant-popover-inner {
56 | border: none;
57 | border-radius: 0;
58 | box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
59 | }
60 |
61 | :global .vertical-center-modal {
62 | display: flex;
63 | align-items: center;
64 | justify-content: center;
65 | .ant-modal {
66 | top: 0;
67 | .ant-modal-body {
68 | max-height: 500px;
69 | overflow-y: auto;
70 | }
71 | }
72 | }
73 |
74 | :global .ant-form-item-control {
75 | vertical-align: middle;
76 | }
77 |
78 | .spin {
79 | :global .ant-spin-container {
80 | height: 100vh;
81 | }
82 | }
83 |
84 | .actived-menu-item {
85 | background-color: #108ee9;
86 | }
87 |
88 |
89 | /** 重定义 start **/
90 |
91 | .ant-menu-item > a {
92 | display: inline;
93 | }
94 |
95 |
96 | /** 重定义 end **/
97 |
98 |
99 | /** 外边距 start **/
100 |
101 | .mg-top20 {
102 | margin-top: 20px;
103 | }
104 |
105 |
106 | /** 外边距 end **/
107 |
108 |
--------------------------------------------------------------------------------
/src/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 基于react + redux + immutable + less + ES6/7 + webpack + fetch + react-router + antd(1.x)实现的SPA后台管理系统模板
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var path = require('path'); // node内置path模块
2 | var webpack = require('webpack'); // webpack打包工具
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); //css单独打包
4 | var HtmlWebpackPlugin = require('html-webpack-plugin'); //生成html
5 | var os = require('os');
6 | var HappyPack = require('happypack');
7 | var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length});
8 | // var bundleConfig = require("./antd/dist/bundle-config.json");
9 |
10 | // var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // 分析包体依赖
11 |
12 | // 定义地址
13 | var ROOT_PATH = path.resolve(__dirname);
14 | var APP_PATH = path.resolve(ROOT_PATH, 'src'); //__dirname 中的src目录,以此类推
15 | var APP_FILE = path.resolve(APP_PATH, 'app'); //根目录文件app.jsx地址
16 | var BUILD_PATH = path.resolve(ROOT_PATH, '/antd/dist'); // 发布文件所存放的目录
17 |
18 | module.exports = {
19 | devtool: 'cheap-module-eval-source-map',
20 | entry: {
21 | app: [
22 | 'webpack-hot-middleware/client',
23 | APP_FILE
24 | ]
25 | },
26 | output: {
27 | publicPath: '/antd/dist/', //编译好的文件,在服务器的路径,这是静态资源引用路径
28 | path: BUILD_PATH, //发布文件地址
29 | filename: '[name].js', //编译后的文件名字
30 | chunkFilename: '[name].[chunkhash:5].min.js'
31 | },
32 | module: {
33 | rules: [{
34 | test: /\.js$/,
35 | exclude: /node_modules/,
36 | use: ['happypack/loader?id=js'],
37 | include: [APP_PATH]
38 | }, {
39 | test: /\.css$/,
40 | exclude: /node_modules/,
41 | use: ['happypack/loader?id=css'],
42 | include: [APP_PATH]
43 | }, {
44 | test: /\.less$/, // 去掉exclude: /^node_modules$/和include: [APP_PATH]是为了babel-plugin-import按需加载antd资源
45 | use: ['happypack/loader?id=less']
46 | }, {
47 | test: /\.(eot|woff|svg|ttf|woff2|gif|appcache)(\?|$)/,
48 | exclude: /node_modules/,
49 | use: ['file-loader?name=[name].[ext]'],
50 | include: [APP_PATH]
51 | }, {
52 | test: /\.(png|jpg|gif)$/,
53 | exclude: /node_modules/,
54 | use: ['url-loader?limit=8192&name=images/[hash:8].[name].[ext]'],
55 | //注意后面那个limit的参数,当你图片大小小于这个限制的时候,会自动启用base64编码图片
56 | include: [APP_PATH]
57 | }, {
58 | test: /\.jsx$/,
59 | exclude: /node_modules/,
60 | use: ['happypack/loader?id=jsx'],
61 | include: [APP_PATH]
62 | }]
63 | },
64 | plugins: [
65 | new webpack.DefinePlugin({
66 | 'process.env': {
67 | NODE_ENV: JSON.stringify('development') //定义编译环境。 process.argv:当前进程的命令行参数数组。process.env:指向当前shell的环境变量,比如process.env.HOME。
68 | }
69 | }),
70 | new HtmlWebpackPlugin({ //根据模板插入css/js等生成最终HTML
71 | filename: '../index.html', //生成的html存放路径,相对于 path
72 | template: './src/template/index.html', //html模板路径
73 | // bundleName: bundleConfig.bundle.js,
74 | favicon: './favicon.ico',
75 | hash: false,
76 | }),
77 | new ExtractTextPlugin('[name].css'),
78 | // new BundleAnalyzerPlugin(),
79 | new webpack.HotModuleReplacementPlugin(), // 热更新插件
80 | new webpack.NoEmitOnErrorsPlugin(), // 即使有错误也不中断运行
81 | new HappyPack({
82 | id: 'js',
83 | threadPool: happyThreadPool,
84 | // verboseWhenProfiling:true,
85 | verbose: process.env.HAPPY_VERBOSE === '1',
86 | loaders: ['react-hot-loader', 'babel-loader?cacheDirectory'],
87 | // debug:true
88 | }),
89 | new HappyPack({
90 | id: 'jsx',
91 | threadPool: happyThreadPool,
92 | // verboseWhenProfiling:true,
93 | verbose: process.env.HAPPY_VERBOSE === '1',
94 | loaders: ['react-hot-loader', 'jsx-loader', 'babel-loader?cacheDirectory'],
95 | // debug:true
96 | }),
97 | new HappyPack({
98 | id: 'css',
99 | threadPool: happyThreadPool,
100 | // verboseWhenProfiling:true,
101 | verbose: process.env.HAPPY_VERBOSE === '1',
102 | loaders: ['style-loader', 'css-loader', 'postcss-loader'],
103 | // debug:true
104 | }),
105 | new HappyPack({
106 | id: 'less',
107 | threadPool: happyThreadPool,
108 | // verboseWhenProfiling:true,
109 | verbose: process.env.HAPPY_VERBOSE === '1',
110 | loaders: [ 'style-loader', 'css-loader', 'postcss-loader', 'less-loader' ],
111 | // debug:true
112 | }),
113 | // new webpack.DllReferencePlugin({
114 | // context: __dirname,
115 | // manifest: require('./antd/dist/bundle.manifest.json')
116 | // })
117 | ],
118 | resolve: {
119 | extensions: ['.js', '.jsx', '.less', '.css'], //后缀名自动补全
120 | }
121 | };
--------------------------------------------------------------------------------
/webpack.config.dist.js:
--------------------------------------------------------------------------------
1 | var path = require('path'); // 为了得到项目根路径
2 | var webpack = require('webpack'); // webpack核心
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 为了单独打包css
4 | // var CleanWebpackPlugin = require('clean-webpack-plugin'); // 清理文件夹
5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); //根据模板生成最终html文件
6 | var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
7 | var os = require('os');
8 | var HappyPack = require('happypack');
9 | var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length});
10 | var bundleConfig = require("./antd/dist/bundle-config.json");
11 |
12 | var ROOT_PATH = path.resolve(__dirname); // 项目根路径
13 | var APP_PATH = path.resolve(ROOT_PATH, 'src'); // 项目src目录
14 | var APP_FILE = path.resolve(APP_PATH, 'app'); // 项目的入口文件(即src/app.jsx)
15 | var BUILD_PATH = path.resolve(ROOT_PATH, 'antd/dist'); // 发布文件所存放的目录
16 |
17 | module.exports = {
18 | entry: {
19 | app: APP_FILE, // 需要被打包的文件,就这个入口文件
20 | // common: [
21 | // 'react',
22 | // 'react-dom',
23 | // 'react-router',
24 | // 'redux',
25 | // 'redux-thunk',
26 | // 'immutable'
27 | // ]
28 | },
29 | output: {
30 | publicPath: '/dist/', // 在生成的html中,文件的引入路径会相对于此地址,生成的css中,以及各类图片的URL都会相对于此地址
31 | // 因为打包后的所有文件,是要交给后台程序员,然后跟后台程序一起,组装成一个完整的项目,上线后,肯定有一个网址来访问,比如: www.test.com;
32 | // 那么前端代码中所有的URL地址,都是相对于这个网址而言的,所以这里配置publicPath为‘/’,比如首页的路径就是www.test.com/home,图片test.jpg的访问路径就是www.test.com/images/test.jpg
33 | // 最关键的是路由跳转,我们之后要配置react路由,比如这里配置的publicPath是‘/’,那路由中route访问主页,就应该配置为:
34 | //
35 | // 又比如publicPath配置的是'/xxx', route就应该是:
36 | //
37 | // 一般就配置为'/',因为一个项目上线后就会有一个顶级域名指向它,但我们自己测试的时候,比如你最终打包了,然后把代码放到tomcat中运行,tomcat访问肯定就是:http://localhost:8888/myreact,这不是顶级域名,你就应该配置publicPath为‘/myreact’,路由中也相应配置为/myreact/home
38 | path: BUILD_PATH, // 将文件打包到此目录下
39 | filename: '[name].[chunkhash].js', // 最终生成的文件名字,项目中为app.jsx,最终也会叫app.js
40 | chunkFilename: '[name].[chunkhash].min.js' // 这是配置一些非入口文件生成的最终文件名字,比如你用了代码分割,按需加载,把你的项目中某些文件单独打包了,就会用到这个。我们这里只有一个app.js,所以这个暂时用不上
41 | },
42 | module: {
43 | rules: [{
44 | test: /\.js$/, // 解析.js
45 | exclude: /node_modules/,
46 | use: ['happypack/loader?id=js']
47 | }, {
48 | test: /\.css$/, // 解析.css,注意这里要用这个插件作为loader,最后才能生成单独的css文件
49 | exclude: /node_modules/,
50 | use: ExtractTextPlugin.extract({
51 | fallback:'style-loader',
52 | use:['happypack/loader?id=css']
53 | }) // 用这种方式写的,表示此类文件单独打包成一个css文件
54 | }, {
55 | // 解析less,原理同上
56 | test: /\.less$/, // 去掉exclude: /^node_modules$/是为了babel-plugin-import按需加载antd资源
57 | use: ExtractTextPlugin.extract({
58 | fallback:'style-loader',
59 | use:['happypack/loader?id=less']
60 | })
61 | }, {
62 | test: /\.(eot|woff|svg|ttf|woff2|gif|appcache)(\?|$)/, // 解析各种非图片文件
63 | exclude: /node_modules/,
64 | use: ['file-loader?name=[name].[ext]']
65 | }, {
66 | // 解析图片,小于8kb的转换成base64
67 | // 注意配种中的name,就是生成到了images文件夹下
68 | test: /\.(png|jpg|gif)$/,
69 | exclude: /node_modules/,
70 | use: ['url-loader?limit=8192&name=images/[hash:8].[name].[ext]'],
71 | //注意后面那个limit的参数,当你图片大小小于这个限制的时候,会自动启用base64编码图
72 | }, {
73 | test: /\.jsx$/,
74 | exclude: /node_modules/,
75 | use: ['happypack/loader?id=jsx']
76 | }]
77 | },
78 | /* 额外的插件 */
79 | plugins: [
80 | new webpack.DefinePlugin({ // 一定要配置这个,这个是为了告诉webpack,当前用什么模式打包代码,dev开发环境,test测试环境,advance预发环境,production生产环境
81 | 'process.env': {
82 | NODE_ENV: JSON.stringify(process.env.NODE_ENV) //定义环境
83 | }
84 | }),
85 | // new CleanWebpackPlugin(['antd'], {
86 | // root: ROOT_PATH,
87 | // verbose: true,
88 | // dry: false
89 | // }),
90 | // 此插件详细教程 http://www.cnblogs.com/haogj/p/5160821.html
91 | new HtmlWebpackPlugin({ //根据模板插入css/js等生成最终HTML
92 | filename: '../index.html', //生成的html存放路径,相对于(比如前面配置的BUILD_PATH是“build/dist”,即index.html会生成到build下,其他文件会打包到build/dist下)
93 | template: './src/template/index.html', //html模板路径
94 | bundleName: bundleConfig.bundle.js,
95 | favicon: './favicon.ico',
96 | inject: 'body', // 是否将js放在body的末尾
97 | hash: false, // 是否为本页面所有资源文件添加一个独特的hash值
98 | }),
99 | // 配置了这个插件,再配合上面loader中的配置,将所有样式文件打包为一个单独的css文件
100 | new ExtractTextPlugin('[name].[chunkhash].css'),
101 | // 提取那些公共的模块、代码打包为一个单独的js文件
102 | // 下面这个方法第3个参数是自动去匹配,webpack遍历所有资源,发现是模块的,而且这个模块不是在src目录中的,就提取到公共js中
103 | // 即把所有node_modules中用到的包都单独打包到一个js中,如果有css,还会单独生成一个vendors.css文件
104 | new webpack.optimize.CommonsChunkPlugin({
105 | children: true,
106 | // (选择所有被选 chunks 的子 chunks)
107 | minChunks: 3,
108 | }),
109 | new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
110 | new HappyPack({
111 | id: 'js',
112 | threadPool: happyThreadPool,
113 | verboseWhenProfiling:true,
114 | verbose: process.env.HAPPY_VERBOSE === '1',
115 | loaders: [ 'babel-loader' ],
116 | // debug:true
117 | }),
118 | new HappyPack({
119 | id: 'jsx',
120 | threadPool: happyThreadPool,
121 | verboseWhenProfiling:true,
122 | verbose: process.env.HAPPY_VERBOSE === '1',
123 | loaders: [ 'jsx-loader', 'babel-loader'],
124 | // debug:true
125 | }),
126 | new HappyPack({
127 | id: 'css',
128 | threadPool: happyThreadPool,
129 | verboseWhenProfiling:true,
130 | verbose: process.env.HAPPY_VERBOSE === '1',
131 | loaders: [ 'css-loader', 'postcss-loader' ],
132 | // debug:true
133 | }),
134 | new HappyPack({
135 | id: 'less',
136 | threadPool: happyThreadPool,
137 | verboseWhenProfiling:true,
138 | verbose: process.env.HAPPY_VERBOSE === '1',
139 | loaders: [ 'css-loader', 'postcss-loader', 'less-loader' ],
140 | // debug:true
141 | }),
142 | new webpack.DllReferencePlugin({
143 | context: __dirname,
144 | manifest: require('./antd/dist/bundle.manifest.json')
145 | }),
146 | // Uglify 加密压缩源代码
147 | new ParallelUglifyPlugin({
148 | // include: BUILD_PATH,
149 | workerCount: os.cpus().length,
150 | uglifyJS:{
151 | output: {
152 | comments: false, // 删除代码中所有注释
153 | max_line_len: 50000
154 | },
155 | compress: {
156 | warnings: false, // 忽略警告
157 | drop_debugger: true,
158 | drop_console: true
159 | }
160 | }
161 | }),
162 | ],
163 | // 配置额外的解决方案
164 | resolve: {
165 | modules: [
166 | APP_PATH,
167 | "node_modules"
168 | ],
169 | extensions: ['.js', '.jsx', '.less', '.css'] //后缀名自动补全
170 | }
171 | };
172 |
--------------------------------------------------------------------------------
/webpack.dll.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var AssetsPlugin = require('assets-webpack-plugin');
3 | var CleanWebpackPlugin = require('clean-webpack-plugin'); // 清理文件夹
4 | var path = require('path');
5 |
6 | var ROOT_PATH = path.resolve(__dirname); // 项目根路径
7 | var BUILD_PATH = path.resolve(ROOT_PATH, 'antd/dist'); // 发布文件所存放的目录
8 |
9 |
10 | //dll尚未使用
11 | module.exports = {
12 | entry: {
13 | bundle: [
14 | 'react',
15 | 'react-dom',
16 | 'react-router',
17 | 'redux',
18 | 'redux-thunk',
19 | 'immutable'
20 | ],
21 | },
22 | output: {
23 | publicPath: '/dist/',
24 | path: BUILD_PATH,
25 | filename: '[name].[chunkhash].js',
26 | library: '[name]_library'
27 | },
28 | plugins: [
29 | new CleanWebpackPlugin(['antd'], {
30 | root: ROOT_PATH,
31 | verbose: true,
32 | dry: false
33 | }),
34 | new webpack.DllPlugin({
35 | path: './antd/dist/bundle.manifest.json',
36 | name: '[name]_library',
37 | }),
38 | new AssetsPlugin({
39 | filename: 'bundle-config.json',
40 | path: './antd/dist',
41 | }),
42 | ]
43 | };
--------------------------------------------------------------------------------