├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── config ├── antd.theme.js ├── webpack.config.js └── webpack.dist.config.js ├── gulpfile.js ├── package.json ├── res ├── iconfont │ ├── iconfont.eot │ ├── iconfont.svg │ ├── iconfont.ttf │ └── iconfont.woff ├── images │ ├── computer.png │ ├── dongwuyuan.jpg │ ├── pin.png │ └── position.jpg └── media │ └── tip.mp3 ├── src ├── app.jsx ├── components │ ├── layout │ │ ├── layout.jsx │ │ └── layout.less │ ├── layout2 │ │ ├── layout2.jsx │ │ └── layout2.less │ └── modal │ │ ├── modal.jsx │ │ └── modal.less ├── index.html ├── index.js ├── main.jsx ├── pages │ ├── item1 │ │ ├── item1.jsx │ │ ├── item1.less │ │ ├── userConfig.jsx │ │ ├── userConfig.less │ │ └── userDelete.jsx │ ├── item2 │ │ ├── item2.jsx │ │ ├── item2.less │ │ ├── line.jsx │ │ └── pie.jsx │ ├── item3 │ │ ├── item3.jsx │ │ └── item3.less │ ├── item4 │ │ ├── item4.jsx │ │ └── item4.less │ ├── item5 │ │ ├── item5.jsx │ │ └── item5.less │ ├── item6 │ │ └── item6.jsx │ ├── item7 │ │ └── item7.jsx │ └── item8 │ │ ├── item8.jsx │ │ └── item8.less ├── store │ ├── echartstore.js │ ├── menustore.js │ ├── positionstore.js │ └── tablestore.js ├── testdata │ ├── localdata │ │ ├── linelist.json │ │ ├── menulist.json │ │ ├── menulist2.json │ │ ├── pielist.json │ │ ├── positionlist.json │ │ └── tablelist.json │ └── mockdata │ │ └── menulist.js └── utils │ ├── ajax.js │ ├── errorcode.js │ ├── localdata.js │ └── mockdata.js ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ], 6 | "plugins": [ 7 | ["transform-decorators-legacy"], 8 | ["import", { "libraryName": "antd", "style": true }], // `style: true` 会加载 less 文件 9 | ["transform-class-properties"], 10 | ["transform-object-rest-spread"] 11 | ] 12 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser" : true 6 | }, 7 | "rules": { 8 | "quotes": [2, "single"], 9 | "semi": 2, 10 | "max-len": [1, 120, 2], 11 | "arrow-body-style": [1, "as-needed"], 12 | "comma-dangle": [2, "never"], 13 | "no-debugger": 2, 14 | "no-console": 2, 15 | "object-curly-spacing": [2, "always"], 16 | "no-undef": [1], 17 | "new-cap": 1, 18 | "no-param-reassign": 2, 19 | "import/no-extraneous-dependencies": 0, 20 | "import/no-unresolved": 0, 21 | "import/extensions": 0, 22 | "global-require": 0, 23 | "react/forbid-prop-types": [2, { "forbid": ["any"] }], 24 | "import/no-dynamic-require": 0, 25 | "no-mixed-operators": 0, 26 | "class-methods-use-this": 0, 27 | "jsx-a11y/media-has-caption": 0, 28 | "react/prefer-stateless-function": 0 29 | } 30 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | build/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于React搭建的管理后台系统 2 | 3 | ## 技术框架 4 | 5 | - ES6/7 Vanilla JS 6 | - ESLint (代码规范) 7 | - React 8 | - Mobx (统一状态管理) 9 | - React Router (路由) 10 | - Antd (UI框架) 11 | - ECharts (常规图表框架) 12 | - Mock.js (模拟数据服务) 13 | - Babel (ES6/7代码转译浏览器可执行) 14 | - Webpack (打包工具) 15 | - React Amap (高德地图) 16 | - Visjs (拓扑图) 17 | 18 | ## 目录结构 19 | 20 | ```bash 21 | . 22 | ├── config # webpack 配置目录 23 | ├── res # 静态文件目录 24 | └── src # 前端源码目录 25 |    ├── index.html # layout 26 |    ├── main.js # 入口文件 27 |    ├── component # 组件目录 28 |    ├── pages # 页面目录 29 |    ├── store # Mobx状态管理 30 |    ├── testdata # 模拟数据目录 31 |    ├── localdata # 本地测试数据 32 |    │   └── mockdata # 模拟数据服务器 33 |    └── utils # 基础配置文件 34 | ``` 35 | ## 安装 36 | 37 | ```bash 38 | git clone https://github.com/labnize/react-router-antd-webpack-gulp.git 39 | cd react-router-antd-webpack-gulp 40 | npm install 41 | ``` 42 | 43 | ## 调试 44 | 45 | Just run "gulp" in this root folder 46 | ``` 47 | gulp 48 | ``` 49 | 50 | ## 打包 51 | 52 | Just run "gulp build" in this root folder 53 | ``` 54 | gulp build 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /config/antd.theme.js: -------------------------------------------------------------------------------- 1 | // const path = require('path'); 2 | 3 | module.exports = { 4 | 'font-size-base': '14px', 5 | '@btn-font-size-lg': '14px', 6 | '@modal-mask-bg': 'rgba(255, 255, 255, 0.45)', 7 | '@icon-url': '"../../../../../res/iconfont/iconfont"' 8 | // '@icon-url': `"${path.relative('./~/antd/lib/style/*', './res/iconfont/iconfont')}"` 9 | }; 10 | -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const theme = require('./antd.theme'); 6 | 7 | const webpackConfig = { 8 | entry: { 9 | app: path.join(__dirname, '../src/index.js') 10 | }, 11 | output: { 12 | path: path.join(__dirname, '../build'), 13 | filename: '[name].js', 14 | publicPath: '/' 15 | }, 16 | cache: true, 17 | devtool: 'inline-source-map', 18 | resolve: { 19 | extensions: ['.js', '.jsx'], 20 | alias: { 21 | components: path.join(__dirname, '../src/components'), 22 | images: path.join(__dirname, '../res/images'), 23 | media: path.join(__dirname, '../res/media'), 24 | pages: path.join(__dirname, '../src/pages'), 25 | localData: path.join(__dirname, '../src/testdata/localdata'), 26 | mockData: path.join(__dirname, '../src/testdata/mockdata'), 27 | util: path.join(__dirname, '../src/utils'), 28 | store: path.join(__dirname, '../src/store'), 29 | jquery: path.join(__dirname, '../node_modules/jquery/dist/jquery.min.js') 30 | } 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /.jsx?$/, 36 | use: [ 37 | 'react-hot-loader/webpack', 38 | 'babel-loader' 39 | ], 40 | exclude: /node_modules/ 41 | }, 42 | { 43 | test: /\.css$/, 44 | use: [ 45 | 'style-loader', 46 | 'css-loader' 47 | ] 48 | }, 49 | { 50 | test: /\.(png|jpg|gif)$/, 51 | use: ['url-loader?limit=1&name=images/[name].[hash:8].[ext]'] 52 | }, 53 | { 54 | test: /\.(woff|woff2|eot|ttf|svg)$/, 55 | use: ['url-loader?limit=1&name=iconfont/[name].[hash:8].[ext]'] 56 | }, 57 | { 58 | test: /\.mp3$/, 59 | use: ['file-loader?name=media/[name].[hash:8].[ext]'] 60 | }, 61 | { 62 | test(file) { 63 | return /\.less$/.test(file) && !/\.module\.less$/.test(file); 64 | }, 65 | use: ExtractTextPlugin.extract('css-loader?sourceMap&-autoprefixer!' + 66 | `less-loader?{"sourceMap":true,"modifyVars":${JSON.stringify(theme)}}`) 67 | } 68 | ] 69 | }, 70 | plugins: [ 71 | new webpack.HotModuleReplacementPlugin(), 72 | new ExtractTextPlugin({ 73 | filename: 'styles.[contenthash].css', 74 | disable: false, 75 | allChunks: false 76 | }), 77 | new webpack.NoEmitOnErrorsPlugin(), 78 | new webpack.ProvidePlugin({ 79 | $: 'jquery', 80 | jQuery: 'jquery', 81 | 'window.jQuery': 'jquery' 82 | }), 83 | new HtmlWebpackPlugin({ 84 | filename: 'index.html', 85 | template: path.resolve(__dirname, '../src/index.html'), 86 | inject: true 87 | }) 88 | ] 89 | }; 90 | 91 | module.exports = webpackConfig; 92 | -------------------------------------------------------------------------------- /config/webpack.dist.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 8 | const theme = require('./antd.theme'); 9 | 10 | const webpackConfig = { 11 | entry: { 12 | app: path.join(__dirname, '../src/index.js'), 13 | vendor: ['react', 'react-dom', 'react-router-dom', 'mobx', 'mobx-react', 'jquery', 'echarts', 'mockjs', 'antd', 'vis'] 14 | }, 15 | output: { 16 | path: path.join(__dirname, '../build'), 17 | filename: '[name].[chunkhash].js', 18 | publicPath: '/' 19 | }, 20 | cache: false, 21 | devtool: false, 22 | resolve: { 23 | extensions: ['.js', '.jsx'], 24 | alias: { 25 | components: path.join(__dirname, '../src/components'), 26 | images: path.join(__dirname, '../res/images'), 27 | media: path.join(__dirname, '../res/media'), 28 | pages: path.join(__dirname, '../src/pages'), 29 | localData: path.join(__dirname, '../src/testdata/localdata'), 30 | mockData: path.join(__dirname, '../src/testdata/mockdata'), 31 | util: path.join(__dirname, '../src/utils'), 32 | store: path.join(__dirname, '../src/store'), 33 | jquery: path.join(__dirname, '../node_modules/jquery/dist/jquery.min.js') 34 | } 35 | }, 36 | module: { 37 | rules: [ 38 | { 39 | test: /.jsx?$/, 40 | use: [ 41 | { 42 | loader: 'react-hot-loader/webpack' 43 | }, 44 | { 45 | loader: 'babel-loader' 46 | } 47 | ], 48 | exclude: /node_modules/ 49 | }, 50 | { 51 | test: /\.css$/, 52 | use: [ 53 | 'style-loader', 54 | 'css-loader' 55 | ] 56 | }, 57 | { 58 | test: /\.(png|jpg|gif)$/, 59 | use: ['url-loader?limit=1&name=images/[name].[hash:8].[ext]'] 60 | }, 61 | { 62 | test: /\.(woff|woff2|eot|ttf|svg)$/, 63 | use: ['url-loader?limit=1&name=iconfont/[name].[hash:8].[ext]'] 64 | }, 65 | { 66 | test: /\.mp3$/, 67 | use: ['file-loader?name=media/[name].[hash:8].[ext]'] 68 | }, 69 | { 70 | test(file) { 71 | return /\.less$/.test(file) && !/\.module\.less$/.test(file); 72 | }, 73 | use: ExtractTextPlugin.extract('css-loader?sourceMap&-autoprefixer!' + 74 | `less-loader?{"sourceMap":true,"modifyVars":${JSON.stringify(theme)}}`) 75 | } 76 | ] 77 | }, 78 | plugins: [ 79 | new BundleAnalyzerPlugin(), 80 | new webpack.DefinePlugin({ 81 | 'process.env': { 82 | NODE_ENV: JSON.stringify('production') 83 | } 84 | }), 85 | new webpack.HashedModuleIdsPlugin(), 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: ['vendor', 'runtime'], 88 | minChunks: 2 89 | }), 90 | new ExtractTextPlugin({ 91 | filename: 'styles.[contenthash].css', 92 | disable: false, 93 | allChunks: false 94 | }), 95 | new webpack.optimize.UglifyJsPlugin({ 96 | compress: { 97 | warnings: false 98 | }, 99 | mangle: { 100 | except: ['$super', '$', 'exports', 'require'] // 以上变量‘$super’, ‘$’, ‘exports’ or ‘require’,不会被混淆 101 | }, 102 | output: { 103 | comments: false 104 | } 105 | }), 106 | new webpack.LoaderOptionsPlugin({ 107 | minimize: true 108 | }), 109 | new webpack.NoEmitOnErrorsPlugin(), 110 | new ProgressBarPlugin({ 111 | format: ' build [:bar] :percent (:elapsed seconds)', 112 | clear: false, 113 | width: 60 114 | }), 115 | new webpack.ProvidePlugin({ 116 | $: 'jquery', 117 | jQuery: 'jquery', 118 | 'window.jQuery': 'jquery' 119 | }), 120 | new HtmlWebpackPlugin({ 121 | filename: 'index.html', 122 | template: path.resolve(__dirname, '../src/index.html'), 123 | inject: true, 124 | minify: { 125 | removeAttributeQuotes: true, 126 | removeComments: true, 127 | removeEmptyAttributes: true, 128 | collapseWhitespace: true 129 | } 130 | }) 131 | ] 132 | }; 133 | 134 | module.exports = webpackConfig; 135 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const open = require('open'); 3 | const del = require('del'); 4 | const webpack = require('webpack'); 5 | const gulpWebpack = require('webpack-stream'); 6 | const WebpackDevServer = require('webpack-dev-server'); 7 | const webpackDistConfig = require('./config/webpack.dist.config.js'); 8 | const webpackDevConfig = require('./config/webpack.config.js'); 9 | const path = require('path'); 10 | 11 | gulp.task('dev', () => { 12 | const compiler = webpack(webpackDevConfig); 13 | new WebpackDevServer(compiler, { 14 | contentBase: './', 15 | historyApiFallback: true, 16 | hot: true, 17 | noInfo: false, 18 | publicPath: '/', 19 | stats: { colors: true }, 20 | host: 'localhost' 21 | }).listen(8090, 'localhost', () => { 22 | console.log('http://127.0.0.1:8090'); 23 | console.log('Opening your system browser...'); 24 | open('http://127.0.0.1:8090'); 25 | }); 26 | }); 27 | 28 | gulp.task('default', ['dev']); 29 | 30 | gulp.task('clean', () => { 31 | console.log('build folder has been cleaned successfully'); 32 | return del(['build/**/*']); 33 | }); 34 | 35 | gulp.task('build', ['clean'], () => gulp.src(path.join(__dirname, '../src')) 36 | .pipe(gulpWebpack(webpackDistConfig)) 37 | .pipe(gulp.dest('build/'))); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frame", 3 | "version": "0.0.1", 4 | "description": "React Antd demo", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "gulp", 9 | "build": "gulp build" 10 | }, 11 | "keywords": [ 12 | "react", 13 | "webpack", 14 | "gulp" 15 | ], 16 | "author": "zhuli", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "antd": "^3.0.1", 20 | "autoprefixer": "^7.1.6", 21 | "babel-core": "^6.26.0", 22 | "babel-eslint": "^8.0.2", 23 | "babel-loader": "^7.1.2", 24 | "babel-plugin-import": "^1.6.2", 25 | "babel-plugin-transform-class-properties": "^6.24.1", 26 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 27 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 28 | "babel-polyfill": "^6.26.0", 29 | "babel-preset-env": "^1.6.1", 30 | "babel-preset-react": "^6.24.1", 31 | "css-loader": "^0.28.7", 32 | "echarts": "^3.8.2", 33 | "eslint": "^4.10.0", 34 | "eslint-config-airbnb": "^16.1.0", 35 | "eslint-plugin-import": "^2.8.0", 36 | "eslint-plugin-jsx-a11y": "^6.0.2", 37 | "eslint-plugin-react": "^7.4.0", 38 | "extract-text-webpack-plugin": "^3.0.2", 39 | "file-loader": "^1.1.5", 40 | "gulp": "^3.9.1", 41 | "html-webpack-plugin": "^2.30.1", 42 | "jquery": "^3.2.1", 43 | "json-loader": "^0.5.7", 44 | "less": "^2.7.3", 45 | "less-loader": "^4.0.5", 46 | "mobx": "^3.3.1", 47 | "mobx-react": "^4.3.4", 48 | "mockjs": "^1.0.1-beta3", 49 | "open": "0.0.5", 50 | "progress-bar-webpack-plugin": "^1.10.0", 51 | "prop-types": "^15.6.0", 52 | "react": "^16.1.0", 53 | "react-amap": "^1.0.3", 54 | "react-dom": "^16.1.0", 55 | "react-hot-loader": "^3.1.2", 56 | "react-router-dom": "^4.2.2", 57 | "reconnectingwebsocket": "^1.0.0", 58 | "style-loader": "^0.19.0", 59 | "url-loader": "^0.6.2", 60 | "vis": "^4.21.0", 61 | "webpack": "^3.8.1", 62 | "webpack-bundle-analyzer": "^2.9.1", 63 | "webpack-dev-server": "^2.9.4", 64 | "webpack-stream": "^4.0.0" 65 | }, 66 | "dependencies": { 67 | "gojs": "^1.8.6" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /res/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/iconfont/iconfont.eot -------------------------------------------------------------------------------- /res/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /res/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/iconfont/iconfont.woff -------------------------------------------------------------------------------- /res/images/computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/images/computer.png -------------------------------------------------------------------------------- /res/images/dongwuyuan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/images/dongwuyuan.jpg -------------------------------------------------------------------------------- /res/images/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/images/pin.png -------------------------------------------------------------------------------- /res/images/position.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/images/position.jpg -------------------------------------------------------------------------------- /res/media/tip.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labnize/react-router-mobx-antd-webpack-gulp/b1daeebe8ca942b6a1b75ad550bbc36ad967994b/res/media/tip.mp3 -------------------------------------------------------------------------------- /src/app.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Route, Switch } from 'react-router-dom'; 3 | 4 | import item1 from 'pages/item1/item1'; 5 | import item2 from 'pages/item2/item2'; 6 | import item3 from 'pages/item3/item3'; 7 | import item4 from 'pages/item4/item4'; 8 | import item5 from 'pages/item5/item5'; 9 | import item6 from 'pages/item6/item6'; 10 | import item7 from 'pages/item7/item7'; 11 | import item8 from 'pages/item8/item8'; 12 | 13 | class App extends Component { 14 | render() { 15 | return ( 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | ); 30 | } 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /src/components/layout/layout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { browserHistory } from 'react-router'; 3 | import PropTypes from 'prop-types'; 4 | import { Layout, Menu, Breadcrumb, Icon, Dropdown } from 'antd'; 5 | import menuList from 'data/menulist.json'; 6 | // import Util from 'extend/common/util'; 7 | import './layout.less'; 8 | 9 | const { SubMenu } = Menu; 10 | const { Header, Content, Footer, Sider } = Layout; 11 | 12 | class PageComponent extends Component { 13 | constructor(props) { 14 | super(props); 15 | let name = this.props.name ? this.props.name : ''; 16 | let menusAll = this.findMenu(JSON.parse(JSON.stringify(menuList.list)), name); 17 | 18 | this.state = { 19 | menusAll: menusAll 20 | }; 21 | 22 | this.handleMenuClick = this.handleMenuClick.bind(this); 23 | this.handleSubMenuClick = this.handleSubMenuClick.bind(this); 24 | this.handleProfileMenuClick = this.handleProfileMenuClick.bind(this); 25 | } 26 | 27 | componentDidMount() { 28 | let layout = $('.layout-content-layout'); 29 | let height = $(window).height() - 176 + 'px'; 30 | layout.css('height', height); 31 | $(window).resize(function() { 32 | let height = $(window).height() - 176 + 'px'; 33 | layout.css('height', height); 34 | }); 35 | } 36 | 37 | findMenu(list, key) { 38 | let result = []; 39 | if (key !== '') { 40 | list.forEach((item) => { 41 | if (JSON.stringify(item).indexOf(`"${key}"`) !== -1 || JSON.stringify(item).indexOf(`:${key}`) !== -1) { 42 | if (item.key === key) { 43 | delete item.sub; 44 | result.push(item); 45 | } 46 | if (item.sub && item.sub.length > 0) { 47 | const subResult = this.findMenu(item.sub, key); 48 | delete item.sub; 49 | result = result.concat(item, subResult); 50 | } 51 | } 52 | }); 53 | } 54 | return result; 55 | } 56 | 57 | handleMenuClick({ item, key, selectedKeys }) { 58 | console.log(key); 59 | browserHistory.push(`/${key}`); 60 | } 61 | 62 | handleSubMenuClick({ item, key, selectedKeys }) { 63 | console.log(key); 64 | let { menusAll } = this.state; 65 | let url = `${menusAll[0].key}/${key}`; 66 | browserHistory.push(`/${url}`); 67 | } 68 | 69 | handleProfileMenuClick({ item, key, selectedKeys }) { 70 | if (key === 'exit') { 71 | // Util.deleteToken(); 72 | browserHistory.push(''); 73 | return; 74 | } 75 | browserHistory.push(`/${key}`); 76 | } 77 | 78 | render() { 79 | let { children, activeTab } = this.props; 80 | let [firstMenu, secondMenu, thirdMenu, firstKey, secondKey, thirdKey] = ['', '', '', '', '', '']; 81 | let { menusAll } = this.state; 82 | if (menusAll && menusAll.length > 0) { 83 | if (menusAll.length < 2) { 84 | firstMenu = menusAll[0].name; 85 | firstKey = menusAll[0].key; 86 | } else { 87 | [firstMenu, secondMenu, thirdMenu, firstKey, secondKey, thirdKey] = 88 | [menusAll[0].name, menusAll[1].name, menusAll[2].name, menusAll[0].key, menusAll[1].key, menusAll[2].key]; 89 | } 90 | } 91 | let subMenus = menuList.list[activeTab - 1].sub; 92 | 93 | return ( 94 | 95 |
96 |
97 | 104 | {menuList.list.map((item) => { 105 | return ( 106 | {item.name} 107 | ); 108 | })} 109 | 110 |
111 | 112 | 113 | {firstMenu} 114 | {secondMenu} 115 | {thirdMenu} 116 | 117 | 118 | { 122 | console.log(collapsed, type); 123 | }} 124 | > 125 | 132 | { 133 | subMenus ? subMenus.map((item) => { 134 | return ( 135 | {item.name}} > 136 | { 137 | item.sub ? item.sub.map((subitem) => { 138 | return ( 139 | {subitem.name} 140 | ); 141 | }) : '' 142 | } 143 | 144 | ); 145 | }) : '' 146 | } 147 | 148 | 149 | 150 | 151 | {children} 152 | 153 | 154 | 155 | 158 |
159 | ); 160 | } 161 | } 162 | 163 | PageComponent.propTypes = { 164 | children: PropTypes.element.isRequired, 165 | activeTab: PropTypes.number.isRequired, 166 | name: PropTypes.string.isRequired 167 | }; 168 | 169 | export default PageComponent; 170 | -------------------------------------------------------------------------------- /src/components/layout/layout.less: -------------------------------------------------------------------------------- 1 | .layout{ 2 | width: 100%; 3 | height: 100%; 4 | position: fixed; 5 | } 6 | 7 | .ant-menu-horizontal{ 8 | border-bottom: 0px; 9 | } 10 | 11 | .logo{ 12 | width: 200px; 13 | height: 64px; 14 | float: left; 15 | } 16 | 17 | .ant-layout-header{ 18 | overflow: auto; 19 | } 20 | .layout-content{ 21 | padding: 0 50px; 22 | .layout-content-layout{ 23 | padding: 24px 0; 24 | background: #fff; 25 | } 26 | } -------------------------------------------------------------------------------- /src/components/layout2/layout2.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { Layout, Menu, Icon } from 'antd'; 5 | import MenuStore from 'store/menustore'; 6 | import { observer } from 'mobx-react'; 7 | import menus from 'localData/menulist2.json'; 8 | import './layout2.less'; 9 | 10 | const { 11 | Header, Content, Footer, Sider 12 | } = Layout; 13 | const menuStore = new MenuStore(); 14 | 15 | @observer 16 | class PageComponent extends Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | componentDidMount() { 22 | } 23 | 24 | handleMenuClick({ item, key, selectedKeys }) { 25 | console.log(key); 26 | // browserHistory.push(`/${key}`); 27 | } 28 | 29 | render() { 30 | const { children, name } = this.props; 31 | const menuList = menus.list; 32 | const defaultKey = location.pathname.split('/')[1] ? location.pathname.split('/')[1] : 'item1'; 33 | // const menuList = menuStore.list; //菜单通过接口获得 34 | // const { menuList } = this.state; 35 | // const firstKey = menuList.list[0].key; 36 | return ( 37 | 38 | { 42 | console.log(collapsed, type); 43 | }} 44 | > 45 |
46 | 47 | {menuList ? menuList.map(item => ( 48 | 49 | 50 | 51 | 52 | {item.name} 53 | 54 | 55 | )) : ''} 56 | 57 | 58 | 59 |
60 | 61 |
62 | {children} 63 |
64 |
65 |
66 | July Design ©2017 Created by July 67 |
68 | 69 | 70 | ); 71 | } 72 | } 73 | 74 | PageComponent.propTypes = { 75 | children: PropTypes.element.isRequired, 76 | name: PropTypes.string.isRequired 77 | }; 78 | 79 | export default PageComponent; 80 | -------------------------------------------------------------------------------- /src/components/layout2/layout2.less: -------------------------------------------------------------------------------- 1 | .layout{ 2 | width: 100%; 3 | height: 100%; 4 | position: fixed; 5 | .content-layout{ 6 | padding: 24px; 7 | background: rgb(255, 255, 255); 8 | min-height: 360px; 9 | overflow: auto; 10 | } 11 | } 12 | 13 | .logo{ 14 | width: 200px; 15 | height: 64px; 16 | float: left; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/modal/modal.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import { Modal, Spin, Alert } from 'antd'; 4 | 5 | import './modal.less'; 6 | 7 | class PageComponent extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | modal2Visible: false, 13 | option: {} 14 | }; 15 | this.eventListener = this.eventListener.bind(this); 16 | this.defaultOptions = { 17 | type: '', 18 | errorMessage: '', 19 | title: '' 20 | }; 21 | } 22 | 23 | setModal2Visible(modal2Visible) { 24 | this.setState({ modal2Visible }); 25 | } 26 | 27 | show(option) { 28 | this.setState({ 29 | modal2Visible: true, 30 | option 31 | }); 32 | } 33 | 34 | close() { 35 | this.setState({ 36 | modal2Visible: false, 37 | option: this.state.option 38 | }); 39 | } 40 | 41 | eventListener(param) { 42 | this.state.option.ok(param); 43 | } 44 | 45 | render() { 46 | const { modal2Visible, option } = this.state; 47 | const options = Object.assign({}, this.defaultOptions, option); 48 | let content = ''; 49 | if (options.type === 'loading') { 50 | content = ( 51 | 60 | 61 | 62 | ); 63 | } else if (options.type === 'notification') { 64 | content = ( 65 | this.setModal2Visible(false)} 72 | className="modal-header" 73 | > 74 | 80 | 81 | ); 82 | } else if (options.type === 'error') { 83 | content = ( 84 | this.setModal2Visible(false)} 90 | onCancel={() => this.setModal2Visible(false)} 91 | className="modal-header" 92 | > 93 | 100 | 101 | ); 102 | } else if (options.type === 'dialog') { 103 | content = ( 104 | this.setModal2Visible(false)} 111 | className="modal-header" 112 | > 113 | { 114 | options.Dialog ? () : '' 115 | } 116 | 117 | ); 118 | } 119 | return ( 120 |
121 | {content} 122 |
123 | ); 124 | } 125 | } 126 | 127 | const dialogDom = ReactDom.render(, document.getElementById('dialog')); 128 | const modal = {}; 129 | 130 | modal.showModel = (option) => { 131 | dialogDom.show(option); 132 | }; 133 | 134 | modal.closeModel = () => { 135 | dialogDom.close(); 136 | }; 137 | 138 | export default modal; 139 | -------------------------------------------------------------------------------- /src/components/modal/modal.less: -------------------------------------------------------------------------------- 1 | .vertical-center-modal { 2 | text-align: center; 3 | white-space: nowrap; 4 | } 5 | 6 | .vertical-center-modal:before { 7 | content: ''; 8 | display: inline-block; 9 | height: 100%; 10 | vertical-align: middle; 11 | width: 0; 12 | } 13 | 14 | .vertical-center-modal .ant-modal { 15 | display: inline-block; 16 | vertical-align: middle; 17 | top: 0; 18 | text-align: center; 19 | } 20 | 21 | .modal-header{ 22 | .ant-btn-lg{ 23 | font-size: 14px; 24 | } 25 | .noti-alert{ 26 | line-height: 35px; 27 | margin-top: 25px; 28 | text-align: left; 29 | .ant-alert-icon{ 30 | top: 18px; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Demo 11 | 12 | 13 |
14 |
15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 5 | import { LocaleProvider } from 'antd'; 6 | import App from './app'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('app') 15 | ); 16 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 5 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 6 | import { LocaleProvider } from 'antd'; 7 | 8 | import item1 from 'pages/item1/item1'; 9 | import item2 from 'pages/item2/item2'; 10 | import item3 from 'pages/item3/item3'; 11 | import item4 from 'pages/item4/item4'; 12 | import item5 from 'pages/item5/item5'; 13 | import item6 from 'pages/item6/item6'; 14 | 15 | function App(props) { 16 | return ( 17 | 18 |
19 | {props.children} 20 |
21 |
22 | ); 23 | } 24 | 25 | App.propTypes = { 26 | children: PropTypes.element.isRequired 27 | }; 28 | 29 | const routers = ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | 43 | ReactDOM.render(routers, document.getElementById('app')); 44 | -------------------------------------------------------------------------------- /src/pages/item1/item1.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Table, Input, Button } from 'antd'; 3 | import Tablestore from 'store/tablestore'; 4 | import { observer } from 'mobx-react'; 5 | import { observable } from 'mobx'; 6 | import Layout from 'components/layout2/layout2'; 7 | import modal from 'components/modal/modal'; 8 | import UserConfig from './userConfig'; 9 | import UserDelete from './userDelete'; 10 | import './item1.less'; 11 | 12 | const Search = Input.Search; 13 | const store = new Tablestore(); 14 | const url = 'claa/tablelist'; 15 | const columns = [{ 16 | title: '用户名', 17 | dataIndex: 'username', 18 | key: 'username', 19 | render: text => {text} 20 | }, { 21 | title: '角色名', 22 | dataIndex: 'rolename', 23 | key: 'rolename' 24 | }, { 25 | title: '归属组织', 26 | dataIndex: 'belongOrg', 27 | key: 'belongOrg' 28 | }, { 29 | title: '归属用户', 30 | dataIndex: 'belongUser', 31 | key: 'belongUser' 32 | }, { 33 | title: '创建时间', 34 | dataIndex: 'createTime', 35 | key: 'createTime', 36 | sorter: true 37 | }, { 38 | title: '操作', 39 | dataIndex: 'action', 40 | key: 'action', 41 | render: (text, record, index) => ( 42 | 43 | this.editUser(index)} role="presentation" >编辑 44 | 45 | this.deleteUser(index)} role="presentation" >删除 46 | 47 | ) 48 | }]; 49 | 50 | @observer 51 | class PageComponent extends Component { 52 | constructor(props) { 53 | super(props); 54 | 55 | this.handleTableChange = this.handleTableChange.bind(this); 56 | this.createUser = this.createUser.bind(this); 57 | this.editUser = this.editUser.bind(this); 58 | this.searchUser = this.searchUser.bind(this); 59 | } 60 | 61 | componentDidMount() { 62 | this.doQuery(); 63 | } 64 | 65 | // onSelectChange = (selectedRowKeys, selectedRows) => { 66 | // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); 67 | // }; 68 | 69 | title = () => '用户管理'; 70 | 71 | @observable obserdata = { 72 | sorterField: '', 73 | sorterOrder: '', 74 | tableList: [], 75 | searchValue: '' 76 | }; 77 | 78 | doQuery() { 79 | const param = { 80 | loadingFlag: true, 81 | url, 82 | method: 'GET', 83 | data: { 84 | pageSize: store.data.pagination.pageSize, 85 | current: store.data.pagination.current, 86 | sorterField: this.obserdata.sorterField, 87 | sorterOrder: this.obserdata.sorterOrder, 88 | searchValue: this.obserdata.searchValue 89 | } 90 | }; 91 | store.fetchData(param); 92 | } 93 | 94 | handleTableChange(pagination, filters, sorter) { 95 | store.changeCurrentPage(pagination.current); 96 | this.obserdata.sorterField = sorter.field; 97 | this.obserdata.sorterOrder = sorter.order; 98 | this.doQuery(); 99 | } 100 | 101 | createUser() { 102 | modal.showModel({ 103 | type: 'dialog', 104 | title: '新增用户', 105 | Dialog: UserConfig, 106 | ok: (value) => { 107 | this.okHandler(value, 0); 108 | }, 109 | param: {} 110 | }); 111 | } 112 | 113 | okHandler(value, type) { 114 | const that = this; 115 | const params = { 116 | loadingFlag: false, 117 | url, 118 | method: 'POST', 119 | data: { 120 | type, 121 | username: value.username, 122 | rolename: value.rolename, 123 | userDesc: value.userDesc 124 | }, 125 | successFn() { 126 | that.doQuery(); 127 | } 128 | }; 129 | store.createUser(params); 130 | } 131 | 132 | editUser(index) { 133 | modal.showModel({ 134 | type: 'dialog', 135 | title: '编辑用户', 136 | Dialog: UserConfig, 137 | ok: (value) => { 138 | this.okHandler(value, 1); 139 | }, 140 | param: this.obserdata.tableList[index] 141 | }); 142 | } 143 | 144 | deleteUser(index) { 145 | const that = this; 146 | modal.showModel({ 147 | type: 'dialog', 148 | title: '删除用户', 149 | Dialog: UserDelete, 150 | ok: () => { 151 | const params = { 152 | loadingFlag: false, 153 | url, 154 | method: 'POST', 155 | data: { 156 | id: [index] 157 | }, 158 | successFn() { 159 | that.doQuery(); 160 | } 161 | }; 162 | store.deleteUser(params); 163 | }, 164 | param: { 165 | text: '确定要删除该用户吗?' 166 | } 167 | }); 168 | } 169 | 170 | searchUser(value) { 171 | this.obserdata.searchValue = value; 172 | this.doQuery(); 173 | } 174 | 175 | render() { 176 | const dataSource = store.data.list.slice(); 177 | this.obserdata.tableList = dataSource; 178 | const rowSelection = { 179 | onChange: this.onSelectChange 180 | }; 181 | return ( 182 | 183 |
184 |
185 | this.searchUser(value)} 189 | /> 190 | 191 | 192 |
193 |
194 | 202 | 203 | 204 | 205 | ); 206 | } 207 | } 208 | 209 | export default PageComponent; 210 | -------------------------------------------------------------------------------- /src/pages/item1/item1.less: -------------------------------------------------------------------------------- 1 | .item1{ 2 | position: relative; 3 | .search{ 4 | position: absolute; 5 | right: 0; 6 | z-index: 99; 7 | .apart-line { 8 | position: relative; 9 | display: inline-block; 10 | width: 1px; 11 | height: 28px; 12 | top: 9px; 13 | margin: 0 10px; 14 | background: rgb(229, 235, 241); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/item1/userConfig.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Form, Input, Radio, Button } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import modal from 'components/modal/modal'; 5 | 6 | import './userConfig.less'; 7 | 8 | const FormItem = Form.Item; 9 | const { TextArea } = Input; 10 | 11 | class PageComponent extends Component { 12 | static cancelClickHandler() { 13 | modal.closeModel(); 14 | } 15 | 16 | handleSubmit = (e) => { 17 | e.preventDefault(); 18 | this.props.form.validateFields((err, values) => { 19 | if (!err) { 20 | console.log('Received values of form: ', values); 21 | 22 | const param = { 23 | username: values.username, 24 | rolename: values.rolename, 25 | userDesc: values.userDesc 26 | }; 27 | this.props.onTrigger(param); 28 | } 29 | }); 30 | }; 31 | 32 | render() { 33 | const formItemLayout = { 34 | labelCol: { span: 5 }, 35 | wrapperCol: { span: 16 } 36 | }; 37 | const { getFieldDecorator } = this.props.form; 38 | return ( 39 |
40 |
41 | 45 | {getFieldDecorator('rolename', { 46 | rules: [{ required: true, message: '请选择用户类型!' }] 47 | })( 48 | 49 | 普通用户 50 | 管理员 51 | 52 | )} 53 | 54 | 58 | {getFieldDecorator('username', { 59 | rules: [{ required: true, message: '请输入用户名!' }] 60 | })( 61 | 62 | )} 63 | 64 | 68 | {getFieldDecorator('userDesc', { 69 | rules: [{ required: true, message: '请输入用户描述!' }] 70 | })( 71 |