├── .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 | [![React Native](https://img.shields.io/badge/react-^15.3.2-brightgreen.svg?style=flat-square)](https://github.com/facebook/react) 3 | [![Redux](https://img.shields.io/badge/redux-^4.4.5-yellowgreen.svg?style=flat-square)](https://github.com/reactjs/redux) 4 | [![Redux Immutablejs](https://img.shields.io/badge/immutablejs-^0.0.8-orange.svg?style=flat-square)](https://github.com/indexiatech/redux-immutablejs) 5 | [![Ant Design](https://img.shields.io/badge/ant--design-^2.7.2-yellowgreen.svg?style=flat-square)](https://github.com/ant-design/ant-design) 6 | 7 | [![MIT](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](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 | 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 | 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 | 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 | 40 | sosout}> 41 | 注销 42 | 43 | 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 | 47 | 48 | 49 | 50 | {!this.props.collapsed && 快速入门} 51 | 52 | 53 | 百度图表}> 54 | 折线图 55 | 56 | 基础组件}> 57 | 按钮 58 | 图标 59 | 60 | 61 | 62 | 63 | {!this.props.collapsed && 用户管理} 64 | 65 | 66 | 67 | 68 | 69 | {!this.props.collapsed && 系统设置} 70 | 71 | 72 | 73 | 74 | 75 | {!this.props.collapsed && 广告管理} 76 | 77 | 78 | UI组件} 80 | > 81 | 组件一 82 | 组件二 83 | 84 | 只展开当前父级菜单}> 85 | Option 9 86 | Option 10 87 | 88 | 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 | 94 | 96 | 97 |
98 | 99 | 101 | 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 | 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 |
79 | 80 | {getFieldDecorator('username', { initialValue: 'sosout', rules: [{ required: true, message: Config.message.usernameInput }, { validator: this.checkUsername }] })( 81 | 82 | )} 83 | 84 | 85 | {getFieldDecorator('password', { rules: [{ required: true, message: Config.message.passwordInput }, { validator: this.checkPassword }] })( 86 | 87 | )} 88 | 89 | 90 | 91 | 92 |
93 | 账号:sosout 94 | 密码:sosout 95 |
96 |
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 | }; --------------------------------------------------------------------------------