├── .env ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo.svg └── manifest.json ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── App.js ├── App.test.js ├── axios │ └── index.js ├── components │ ├── backtotop │ │ ├── Type1.jsx │ │ ├── Type2.jsx │ │ ├── Type3.jsx │ │ └── Type4.jsx │ ├── chart │ │ └── index.jsx │ ├── map │ │ ├── TencentMap.jsx │ │ └── spot_location.png │ ├── taglist │ │ └── index.js │ └── wysiwyg │ │ └── index.js ├── index.css ├── index.js ├── redux │ ├── actions │ │ ├── index.js │ │ ├── login.js │ │ ├── type.js │ │ └── ui.js │ ├── reducers │ │ ├── all.js │ │ └── index.js │ └── store.js ├── registerServiceWorker.js ├── router │ ├── index.js │ └── menus.js ├── utils │ ├── debounce.js │ └── index.js └── views │ ├── components │ ├── backtotop │ │ └── index.js │ ├── chart │ │ ├── keyboard │ │ │ └── index.js │ │ ├── linechart │ │ │ └── index.js │ │ └── mixchart │ │ │ └── index.js │ ├── drag │ │ ├── components │ │ │ ├── Header.jsx │ │ │ ├── LeftItem.jsx │ │ │ └── RightItem.jsx │ │ ├── index.js │ │ ├── index.less │ │ └── server.js │ ├── map │ │ └── index.js │ └── wysiwyg │ │ └── index.js │ ├── dashboard │ ├── chartData.js │ ├── component │ │ ├── AntdCard.jsx │ │ ├── LineChart.jsx │ │ └── PanelGroup.jsx │ ├── index.js │ └── index.module.less │ ├── error │ ├── 401 │ │ └── index.js │ ├── 404 │ │ └── index.js │ └── index.module.less │ ├── form │ └── index.js │ ├── layout │ ├── Content.jsx │ ├── Header.jsx │ ├── index.js │ ├── index.less │ ├── sider │ │ ├── Menu.jsx │ │ └── index.js │ └── tags │ │ ├── component │ │ ├── Dropdown.jsx │ │ └── Tag.jsx │ │ └── index.js │ ├── login │ ├── index.js │ ├── index.less │ └── params.js │ ├── map │ └── index.js │ ├── permission │ ├── intercept │ │ └── index.js │ └── toggle │ │ └── index.js │ └── table │ ├── basic │ └── index.js │ ├── dynamic │ ├── component │ │ ├── FixedTable.jsx │ │ └── SortedTable.jsx │ └── index.js │ └── edit │ └── index.js └── static ├── css ├── main.41f2bccb.css └── main.41f2bccb.css.map ├── dashboard.gif ├── js ├── main.e49e6188.js └── main.e49e6188.js.map ├── login.gif ├── permission.gif ├── table.gif └── tags.gif /.env: -------------------------------------------------------------------------------- 1 | PORT=3303 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "globals": { 9 | "$": true, 10 | "process": true, 11 | "__dirname": true 12 | }, 13 | "parser": "babel-eslint", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "experimentalObjectRestSpread": true, 17 | "jsx": true 18 | }, 19 | "sourceType": "module", 20 | "ecmaVersion": 7 21 | }, 22 | "plugins": [ 23 | "react" 24 | ], 25 | "rules": { 26 | "quotes": [2, "single"], //单引号 27 | "no-console": 0, //不禁用console 28 | "no-debugger":0, //禁用debugger 29 | "no-var": 0, //对var警告 30 | // "semi": [2, "never"], //不强制使用分号 31 | "no-irregular-whitespace": 0, //不规则的空白不允许 32 | "no-trailing-spaces": 1, //一行结束后面有空格就发出警告 33 | "eol-last": 0, //文件以单一的换行符结束 34 | "no-unused-vars": [1, {"vars": "all", "args": "after-used"}], //不能有声明后未被使用的变量或参数 35 | "no-underscore-dangle": 0, //标识符不能以_开头或结尾 36 | // "no-alert": 2, //禁止使用alert confirm prompt 37 | "no-lone-blocks": 0, //禁止不必要的嵌套块 38 | "no-class-assign": 2, //禁止给类赋值 39 | "no-cond-assign": 2, //禁止在条件表达式中使用赋值语句 40 | "no-const-assign": 2, //禁止修改const声明的变量 41 | "no-delete-var": 2, //不能对var声明的变量使用delete操作符 42 | "no-dupe-keys": 2, //在创建对象字面量时不允许键重复 43 | "no-duplicate-case": 2, //switch中的case标签不能重复 44 | "no-dupe-args": 2, //函数参数不能重复 45 | "no-empty": 2, //块语句中的内容不能为空 46 | "no-func-assign": 2, //禁止重复的函数声明 47 | "no-invalid-this": 0, //禁止无效的this,只能用在构造器,类,对象字面量 48 | "no-redeclare": 2, //禁止重复声明变量 49 | "no-spaced-func": 2, //函数调用时 函数名与()之间不能有空格 50 | "no-this-before-super": 0, //在调用super()之前不能使用this或super 51 | "no-undef": 2, //不能有未定义的变量 52 | "no-use-before-define": 0, //未定义前不能使用 53 | "camelcase": 0, //强制驼峰法命名 54 | // "jsx-quotes": [1, "prefer"], //强制在JSX属性(jsx-quotes)中一致使用双引号 55 | "react/display-name": 0, //防止在React组件定义中丢失displayName 56 | "react/forbid-prop-types": [2, {"forbid": ["any"]}], //禁止某些propTypes 57 | "react/jsx-boolean-value": 2, //在JSX中强制布尔属性符号 58 | "react/jsx-closing-bracket-location": 1, //在JSX中验证右括号位置 59 | "react/jsx-curly-spacing": [2, {"when": "never", "children": true}], //在JSX属性和表达式中加强或禁止大括号内的空格。 60 | "react/jsx-indent-props": [1, 4], //验证JSX中的props缩进 61 | "react/jsx-key": 2, //在数组或迭代器中验证JSX具有key属性 62 | "react/jsx-max-props-per-line": [1, {"maximum": 1}], // 限制JSX中单行上的props的最大数量 63 | "react/jsx-no-bind": 0, //JSX中不允许使用箭头函数和bind 64 | "react/jsx-no-duplicate-props": 2, //防止在JSX中重复的props 65 | "react/jsx-no-literals": 0, //防止使用未包装的JSX字符串 66 | "react/jsx-no-undef": 1, //在JSX中禁止未声明的变量 67 | "react/jsx-pascal-case": 0, //为用户定义的JSX组件强制使用PascalCase 68 | "react/jsx-sort-props": 2, //强化props按字母排序 69 | "react/jsx-uses-react": 1, //防止反应被错误地标记为未使用 70 | "react/jsx-uses-vars": 2, //防止在JSX中使用的变量被错误地标记为未使用 71 | "react/no-danger": 0, //防止使用危险的JSX属性 72 | "react/no-did-mount-set-state": 0, //防止在componentDidMount中使用setState 73 | "react/no-did-update-set-state": 1, //防止在componentDidUpdate中使用setState 74 | "react/no-direct-mutation-state": 2, //防止this.state的直接变异 75 | "react/no-multi-comp": 2, //防止每个文件有多个组件定义 76 | "react/no-set-state": 0, //防止使用setState 77 | "react/no-unknown-property": 2, //防止使用未知的DOM属性 78 | "react/prefer-es6-class": 2, //为React组件强制执行ES5或ES6类 79 | "react/prop-types": 0, //防止在React组件定义中丢失props验证 80 | "react/react-in-jsx-scope": 2, //使用JSX时防止丢失React 81 | "react/self-closing-comp": 0, //防止没有children的组件的额外结束标签 82 | "react/sort-comp": 2, //强制组件方法顺序 83 | "no-extra-boolean-cast": 0, //禁止不必要的bool转换 84 | "react/no-array-index-key": 0, //防止在数组中遍历中使用数组key做索引 85 | "react/no-deprecated": 1, //不使用弃用的方法 86 | "react/jsx-equals-spacing": 2, //在JSX属性中强制或禁止等号周围的空格 87 | "no-unreachable": 0, //不能有无法执行的代码 88 | "comma-dangle": 0, //对象字面量项尾不能有逗号 89 | "no-mixed-spaces-and-tabs": 0, //禁止混用tab和空格 90 | "prefer-arrow-callback": 0, //比较喜欢箭头回调 91 | "arrow-parens": 0, //箭头函数用小括号括起来 92 | "arrow-spacing": 0 //=>的前/后括号 93 | }, 94 | "settings": { 95 | "import/ignore": [ 96 | "node_modules" 97 | ] 98 | } 99 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | */node_modules 6 | client/node_modules 7 | server/node_modules 8 | mqtt/node_modules 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | .idea 28 | package-lock.json 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: 5 | - yarn 6 | - yarn build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-admin-antd 2 | 3 | ### 前言 4 | 5 | > 基于react和antd的后台管理系统,支持响应式,IE10+ 6 | 7 | - [预览地址](http://www.1994sx.top/react-antd-admin)(已增加响应式,可手机预览😄) 8 | - 该项目基于[create-react-app](https://github.com/facebook/create-react-app)创建 9 | ### 依赖模块 10 | - [react@16.3.2](https://facebook.github.io/react/) 11 | - [react-router@4.2.2](https://react-guide.github.io/react-router-cn/)(注意,v4和v3的使用区别差距较大,坑也比较多,自行斟酌) 12 | - [react-redux]() 状态管理库;用redux-logger打印日志,方便调试;用redux-thunk实现异步操作 13 | - [css-modules@4.7.2](https://react-guide.github.io/react-router-cn/)(避免样式命名冲突,书写方式也更简单 14 | 一般小项目用不到) 15 | - [antd@3.0.1](https://ant.design/index-cn)(蚂蚁金服开源的react ui组件框架,精美简约) 16 | - [axios@0.16.1](https://github.com/mzabriskie/axios)(一个常用的http请求库,可以实现全局请求拦截,响应拦截) 17 | - [echarts@4.1.0](https://github.com/apache/incubator-echarts)(可视化图表,习惯用这个了,同款推荐 [echarts-for-react](https://github.com/hustcc/echarts-for-react)) 18 | - [react-draft-wysiwyg@1.12.13](https://github.com/jpuri/react-draft-wysiwyg)(基于react的富文本封装) 19 | - [react-sortable-hoc@0.7.2](https://github.com/clauderic/react-sortable-hoc)(简单的拖拽模块) 20 | - [react-transition-group@2.3.1](https://github.com/reactjs/react-transition-group)(用来实现过渡效果,如果你用过vue的transition,这玩意也差不多) 21 | - [react-particles-js@2.2.0](https://github.com/Wufe/react-particles-js)(用来实现登录页背景的粒子效果) 22 | 23 | 24 | ### 项目截图 25 | #### 首页 26 | ![首页](./static/dashboard.gif) 27 | 28 | #### 标签页缓存功能 29 | ![标签页缓存功能](./static/tags.gif) 30 | 31 | #### 权限切换 32 | ![权限切换](./static/permission.gif) 33 | 34 | #### 登录页 35 | ![登录页](./static/login.gif) 36 | 37 | #### 表格编辑 38 | ![表格编辑](./static/table.gif) 39 | 40 | 41 | 42 | 43 | 44 | ### 代码目录 45 | ```js 46 | +-- build/ ---打包的文件目录 47 | +-- config/ ---npm run eject 后的配置文件目录 48 | +-- node_modules/ ---npm下载文件目录 49 | +-- public/ 50 | | --- index.html ---首页入口html文件 51 | | --- images ---项目图片 52 | +-- src/ ---主要代码 53 | | +-- axios ---http请求库 54 | | | --- index.js 55 | | +-- components ---所有可复用组件,公用组件 56 | | | +-- backtotop ---返回顶部组件 57 | | | | --- ... 58 | | | +-- chart ---图表组件 59 | | | | --- ... 60 | | | +-- taglist --- 标签按钮 61 | | | | --- ... 62 | | | +-- map ---腾讯地图 63 | | | | --- ... 64 | | | +-- wysiwyg --- 富文本 65 | | +-- utils --- 工具文件存放目录 66 | | +-- views --- 路由页面,对应左侧菜单栏,每一个文件夹都是一个页面 67 | | +-- router --- 路由相关 68 | | | +-- index.js --- content视图区的(src/views/layout/Content.jsx)的路由配置 69 | | | | --- ... 70 | | | +-- menus.js --- 左侧菜单栏的路由配置 71 | | | | --- ... 72 | | +-- redux --- 状态管理 73 | | | +-- store.js --- store对象 74 | | | | --- ... 75 | | | +-- action --- 所有action 76 | | | | --- ... 77 | | | +-- reducers --- 所有reducers 78 | | --- App.js --- 组件入口文件 79 | | --- index.js --- 项目入口文件 80 | --- .env --- 启动项目自定义端口配置文件 81 | --- .eslintrc.js --- 自定义eslint配置文件,包括增加的react jsx语法限制 82 | --- package.json 83 | ``` 84 | ### 文档 85 | 86 | #### 路由 87 | 88 | - 路由的跳转事件的三种获取方式 89 | 90 | 1. withRouter 高阶函数 91 | ```js 92 | import { withRouter } from 'react-router-dom' 93 | const Component = props=>{ 94 | const {history} = props 95 | return ( 96 |
点击跳转路由
97 | ) 98 | } 99 | export default withRouter(Component) 100 | ``` 101 | 102 | 2. 通过context拿到history对象,实现跳转 103 | ```js 104 | import PropTypes from 'prop-types' 105 | const Component = (props,context)=>{ 106 | const {history} = context.router 107 | return ( 108 |
点击跳转路由
109 | ) 110 | } 111 | Component.contextTypes = { 112 | router: PropTypes.object.isRequired 113 | } 114 | 115 | export default Component 116 | ``` 117 | 118 | #### 样式模块化 119 | 120 | - 全局样式 121 | 直接创建一个样式文件xxx.less 122 | ```js 123 | import './index.less' 124 |
125 | ``` 126 | - css模块化 127 | 创建一个文件xxx.module.less的样式文件,以module.less会后缀的样式文件里的class,会自动添加hash值,从而实现样式模块化,避免class的命名冲突 128 | ```js 129 | import CSSModules from 'react-css-modules' 130 | import styles from '../index.module.less' 131 |
134 | export default CSSModules(Component,styles) 135 | ``` 136 | #### 登录逻辑 137 | 138 | 发送登录请求后,后台验证返回token,接着再发送getUserInfo请求,获取用户信息(头像、姓名等) 139 | 140 | 141 | #### 权限验证 142 | 权限由前端控制,根据后台roles生成路由表和菜单栏 143 | 144 | #### 多环境 145 | 146 | 设置不同的环境变量 147 | ```js 148 | // package.json 149 | "build:prod": "set REACT_APP_XXX= 'XXX' && npm run build", 150 | "build:sit": "set REACT_APP_XXX= 'XXX' && npm run build" 151 | ``` 152 | 153 | 之后可以在代码里自行判断 154 | if(process.env.REACT_APP_XXX === 'xxx'){ 155 | ... 156 | } 157 | 158 | 159 | ### 安装运行,打包 160 | ```js 161 | npm i 162 | ``` 163 | ##### 启动项目 164 | ```js 165 | npm start 166 | ``` 167 | ##### 打包项目 168 | ```js 169 | npm run build 170 | ``` 171 | 172 | ##### 测试打包后的项目 173 | ```js 174 | serve -s build 175 | ``` 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | REACT_APP_BASE_URL:'https://easy-mock.com/mock/5a800c31c8aeca15e29df018/test', 77 | PUBLIC_URL: publicUrl, 78 | } 79 | ); 80 | // Stringify all values so we can feed into Webpack DefinePlugin 81 | const stringified = { 82 | 'process.env': Object.keys(raw).reduce((env, key) => { 83 | env[key] = JSON.stringify(raw[key]); 84 | return env; 85 | }, {}), 86 | }; 87 | 88 | return { raw, stringified }; 89 | } 90 | 91 | module.exports = getClientEnvironment; 92 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 18 | 27 | React App 28 | 29 | 30 | 31 | 34 |
35 | 36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const path = require('path'); 18 | const chalk = require('chalk'); 19 | const fs = require('fs-extra'); 20 | const webpack = require('webpack'); 21 | const config = require('../config/webpack.config.prod'); 22 | const paths = require('../config/paths'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 25 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 26 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 27 | 28 | const measureFileSizesBeforeBuild = 29 | FileSizeReporter.measureFileSizesBeforeBuild; 30 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 31 | const useYarn = fs.existsSync(paths.yarnLockFile); 32 | 33 | // These sizes are pretty large. We'll warn for bundles exceeding them. 34 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 35 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // First, read the current file sizes in build directory. 43 | // This lets us display how much they changed later. 44 | measureFileSizesBeforeBuild(paths.appBuild) 45 | .then(previousFileSizes => { 46 | // Remove all content but keep the directory so that 47 | // if you're in it, you don't end up in Trash 48 | fs.emptyDirSync(paths.appBuild); 49 | // Merge with the public folder 50 | copyPublicFolder(); 51 | // Start the webpack build 52 | return build(previousFileSizes); 53 | }) 54 | .then( 55 | ({ stats, previousFileSizes, warnings }) => { 56 | if (warnings.length) { 57 | console.log(chalk.yellow('Compiled with warnings.\n')); 58 | console.log(warnings.join('\n\n')); 59 | console.log( 60 | '\nSearch for the ' + 61 | chalk.underline(chalk.yellow('keywords')) + 62 | ' to learn more about each warning.' 63 | ); 64 | console.log( 65 | 'To ignore, add ' + 66 | chalk.cyan('// eslint-disable-next-line') + 67 | ' to the line before.\n' 68 | ); 69 | } else { 70 | console.log(chalk.green('Compiled successfully.\n')); 71 | } 72 | 73 | console.log('File sizes after gzip:\n'); 74 | printFileSizesAfterBuild( 75 | stats, 76 | previousFileSizes, 77 | paths.appBuild, 78 | WARN_AFTER_BUNDLE_GZIP_SIZE, 79 | WARN_AFTER_CHUNK_GZIP_SIZE 80 | ); 81 | console.log(); 82 | 83 | const appPackage = require(paths.appPackageJson); 84 | const publicUrl = paths.publicUrl; 85 | const publicPath = config.output.publicPath; 86 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 87 | printHostingInstructions( 88 | appPackage, 89 | publicUrl, 90 | publicPath, 91 | buildFolder, 92 | useYarn 93 | ); 94 | }, 95 | err => { 96 | console.log(chalk.red('Failed to compile.\n')); 97 | console.log((err.message || err) + '\n'); 98 | process.exit(1); 99 | } 100 | ); 101 | 102 | // Create the production build and print the deployment instructions. 103 | function build(previousFileSizes) { 104 | console.log('Creating an optimized production build...'); 105 | 106 | let compiler = webpack(config); 107 | return new Promise((resolve, reject) => { 108 | compiler.run((err, stats) => { 109 | if (err) { 110 | return reject(err); 111 | } 112 | const messages = formatWebpackMessages(stats.toJson({}, true)); 113 | if (messages.errors.length) { 114 | return reject(new Error(messages.errors.join('\n\n'))); 115 | } 116 | if ( 117 | process.env.CI && 118 | (typeof process.env.CI !== 'string' || 119 | process.env.CI.toLowerCase() !== 'false') && 120 | messages.warnings.length 121 | ) { 122 | console.log( 123 | chalk.yellow( 124 | '\nTreating warnings as errors because process.env.CI = true.\n' + 125 | 'Most CI servers set it automatically.\n' 126 | ) 127 | ); 128 | return reject(new Error(messages.warnings.join('\n\n'))); 129 | } 130 | return resolve({ 131 | stats, 132 | previousFileSizes, 133 | warnings: messages.warnings, 134 | }); 135 | }); 136 | }); 137 | } 138 | 139 | function copyPublicFolder() { 140 | fs.copySync(paths.appPublic, paths.appBuild, { 141 | dereference: true, 142 | filter: file => file !== paths.appHtml, 143 | }); 144 | } 145 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const fs = require('fs'); 18 | const chalk = require('chalk'); 19 | const webpack = require('webpack'); 20 | const WebpackDevServer = require('webpack-dev-server'); 21 | const clearConsole = require('react-dev-utils/clearConsole'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils'); 29 | const openBrowser = require('react-dev-utils/openBrowser'); 30 | const paths = require('../config/paths'); 31 | const config = require('../config/webpack.config.dev'); 32 | const createDevServerConfig = require('../config/webpackDevServer.config'); 33 | 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | const isInteractive = process.stdout.isTTY; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // Tools like Cloud9 rely on this. 43 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 44 | const HOST = process.env.HOST || '0.0.0.0'; 45 | 46 | // We attempt to use the default port but if it is busy, we offer the user to 47 | // run on a different port. `detect()` Promise resolves to the next free port. 48 | choosePort(HOST, DEFAULT_PORT) 49 | .then(port => { 50 | if (port == null) { 51 | // We have not found a port. 52 | return; 53 | } 54 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 55 | const appName = require(paths.appPackageJson).name; 56 | const urls = prepareUrls(protocol, HOST, port); 57 | // Create a webpack compiler that is configured with custom messages. 58 | const compiler = createCompiler(webpack, config, appName, urls, useYarn); 59 | // Load proxy config 60 | const proxySetting = require(paths.appPackageJson).proxy; 61 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 62 | // Serve webpack assets generated by the compiler over a web sever. 63 | const serverConfig = createDevServerConfig( 64 | proxyConfig, 65 | urls.lanUrlForConfig 66 | ); 67 | const devServer = new WebpackDevServer(compiler, serverConfig); 68 | // Launch WebpackDevServer. 69 | devServer.listen(port, HOST, err => { 70 | if (err) { 71 | return console.log(err); 72 | } 73 | if (isInteractive) { 74 | clearConsole(); 75 | } 76 | console.log(chalk.cyan('Starting the development server...\n')); 77 | openBrowser(urls.localUrlForBrowser); 78 | }); 79 | 80 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 81 | process.on(sig, function() { 82 | devServer.close(); 83 | process.exit(); 84 | }); 85 | }); 86 | }) 87 | .catch(err => { 88 | if (err && err.message) { 89 | console.log(err.message); 90 | } 91 | process.exit(1); 92 | }); 93 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Provider} from 'react-redux' 3 | import { HashRouter as Router, Route, Switch} from 'react-router-dom' 4 | import Layout from './views/layout' 5 | import Login from './views/login' 6 | import store from '@/redux/store' 7 | 8 | class App extends Component { 9 | // 监听路由 10 | componentWillMount(){ 11 | window.onhashchange= ()=>{ 12 | if(window.location.hash!=='/login'){ 13 | if(localStorage.getItem('token')===null){ 14 | window.location.href = '#/login' 15 | } 16 | } 17 | } 18 | } 19 | render(){ 20 | return ( 21 | 22 | 23 | 24 | 29 | 33 | 34 | 35 | 36 | ) 37 | } 38 | } 39 | 40 | export default App 41 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/axios/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/redux/store' 3 | import {message} from 'antd' 4 | const api = axios.create({ 5 | baseURL: process.env.REACT_APP_BASE_URL, 6 | timeout: 5000 7 | }) 8 | 9 | //请求拦截 10 | api 11 | .interceptors 12 | .request 13 | .use(function (config) { 14 | // 在发送请求之前做些什么 15 | // 通过reudx的store拿到拿到全局状态树的token ,添加到请求报文,后台会根据该报文返回status 16 | const token = store.getState().user.token || localStorage.getItem('token') 17 | config.headers['X-Token'] = token 18 | return config 19 | 20 | }, function (error) { 21 | // 对请求错误做些什么 22 | message.error(error) 23 | 24 | return Promise.reject(error) 25 | }) 26 | 27 | // 添加响应拦截器 28 | api 29 | .interceptors 30 | .response 31 | .use(function (response) { 32 | // 对响应数据做点什么 33 | 34 | if (response.data.success === false) { 35 | 36 | message.error(response.data.message) 37 | } 38 | 39 | return response 40 | 41 | }, function (error) { 42 | // 对响应错误做点什么 43 | if (error.response) { 44 | 45 | 46 | if (error.response.status === 401) { 47 | // 如果返回401 即没有权限,跳到登录页重新登录 48 | message.error(error.response.data.message) 49 | 50 | window.location.href = '#/login' 51 | 52 | } 53 | } 54 | 55 | return Promise.reject(error) 56 | }) 57 | 58 | export default api 59 | -------------------------------------------------------------------------------- /src/components/backtotop/Type1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Icon} from 'antd' 3 | 4 | const styles = { 5 | backgroundColor: '#fff', 6 | width: 40, 7 | height: 40, 8 | borderRadius: 20, 9 | cursor: 'pointer', 10 | transition: '.3s', 11 | boxShadow: '0 0 6px rgba(0,0,0,.12)', 12 | zIndex: 5, 13 | marginBottom: 30 14 | } 15 | const iconStyles = { 16 | color: '#409eff', 17 | display: 'block', 18 | lineHeight: '40px', 19 | textAlign: 'center', 20 | fontZize: 18 21 | } 22 | const Type1 = () => { 23 | return ( 24 |
25 | 29 |
30 | ) 31 | } 32 | 33 | export default Type1 -------------------------------------------------------------------------------- /src/components/backtotop/Type2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Icon} from 'antd' 3 | 4 | const styles = { 5 | backgroundColor: '#f6f9fa', 6 | width: 45, 7 | height: 45, 8 | borderRadius: 4, 9 | cursor: 'pointer', 10 | transition: '.3s', 11 | boxShadow: '0 0 6px rgba(0,0,0,.12)', 12 | zIndex: 5, 13 | border:'1px solid #e5e9ef' 14 | } 15 | 16 | const iconStyles = { 17 | color: '#99a2aa', 18 | display: 'block', 19 | lineHeight: '45px', 20 | textAlign: 'center', 21 | fontZize: 24, 22 | fontWeight:700 23 | } 24 | 25 | const Type2 = () => { 26 | return ( 27 |
28 | 32 |
33 | ) 34 | } 35 | 36 | export default Type2 -------------------------------------------------------------------------------- /src/components/backtotop/Type3.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const styles = { 4 | height: 45, 5 | width: 45, 6 | lineHeight: '45px', 7 | borderRadius: 4, 8 | backgroundColor: '#1088e9', 9 | color:'#fff', 10 | textAlign: 'center', 11 | fontZize: 20 12 | } 13 | 14 | 15 | const Type3 = () => { 16 | return ( 17 |
18 | UP 19 |
20 | ) 21 | } 22 | 23 | export default Type3 -------------------------------------------------------------------------------- /src/components/backtotop/Type4.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const styles = { 4 | backgroundColor: '#f6f9fa', 5 | width: 36, 6 | lineHeight:1.5, 7 | textAlign: 'center', 8 | padding:10, 9 | cursor: 'pointer', 10 | transition: '.3s', 11 | boxShadow: '0 0 6px rgba(0,0,0,.12)', 12 | zIndex: 5, 13 | border:'1px solid #DDD', 14 | fontZize: 12, 15 | color:'#999' 16 | } 17 | 18 | 19 | 20 | const Type4 = () => { 21 | return ( 22 | // 23 |
24 | 回顶部 25 |
26 | //
27 | ) 28 | } 29 | 30 | export default Type4 -------------------------------------------------------------------------------- /src/components/chart/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {PropTypes} from 'prop-types' 3 | import echarts from 'echarts' 4 | import {debounce} from '@/utils' 5 | import {connect} from 'react-redux' 6 | 7 | class Chart extends Component { 8 | static propTypes = { 9 | width: PropTypes.string.isRequired, 10 | height: PropTypes.string.isRequired, 11 | className: PropTypes.string.isRequired, 12 | style: PropTypes.object.isRequired, 13 | chartData: PropTypes.object.isRequired, 14 | 15 | } 16 | static defaultProps = { 17 | width: '100%', 18 | height: '340px', 19 | className: 'shadow-radius', 20 | style: {}, 21 | chartData: {} 22 | } 23 | 24 | state = { 25 | chart: null 26 | } 27 | 28 | componentDidMount() { 29 | debounce(this.initChart.bind(this), 500)() //初始化图表 30 | window.addEventListener('resize', () => this.resize()) // 监听窗口,变化时重置图表 31 | } 32 | 33 | componentWillReceiveProps(nextProps) { 34 | if (nextProps.collapsed !== this.props.collapsed) { 35 | this.resize() 36 | } 37 | } 38 | 39 | componentWillUnmount() { 40 | this.dispose() 41 | } 42 | 43 | // 重置图表 44 | resize() { 45 | const chart = this.state.chart 46 | if (chart) { 47 | debounce(chart.resize.bind(this), 300)() 48 | } 49 | } 50 | 51 | initChart() { 52 | if(!this.el) return 53 | this.setState({ 54 | chart: echarts.init(this.el, 'macarons') 55 | }, () => { 56 | this.state.chart.setOption(this.props.chartData,true) 57 | }) 58 | } 59 | 60 | dispose (){ 61 | if (!this.state.chart) { 62 | return 63 | } 64 | window.removeEventListener('resize', () => this.resize()) // 移除窗口,变化时重置图表 65 | this.setState({chart: null}) 66 | } 67 | render() { 68 | const {className, height, width, style} = this.props 69 | return ( 70 |
(this.el = el)} 73 | style={{ 74 | ...style, 75 | height, 76 | width 77 | }} 78 | />) 79 | } 80 | 81 | } 82 | 83 | const mapStateToProps = state => ({collapsed: state.UI.collapsed}) 84 | 85 | export default connect(mapStateToProps)(Chart) 86 | -------------------------------------------------------------------------------- /src/components/map/TencentMap.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactQMap from 'react-qmap'; 3 | 4 | const getContianer = dom => { 5 | const middleControl = document.createElement('div'); 6 | middleControl.style.cssText = 'width: 22px;height: 30px;position: absolute;left: 50%;top: 50%;z-index: 999;margin-left: -23px;margin-top: -23px;'; 7 | middleControl.innerHTML = ``; 8 | dom.appendChild(middleControl); 9 | } 10 | 11 | export default () => ( 12 | 20 | ); -------------------------------------------------------------------------------- /src/components/map/spot_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/src/components/map/spot_location.png -------------------------------------------------------------------------------- /src/components/taglist/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import PropTypes from 'prop-types' 4 | import { Tag } from 'antd'; 5 | import {cutTaglist} from '@/redux/actions' 6 | 7 | const TagList = (props,context)=>{ 8 | 9 | const {title,path} = props,currentPath = context.router.history.location.pathname 10 | 11 | const handleClose = path=> { 12 | 13 | const {cutTaglist,taglist} = props,{history} = context.router 14 | const length = taglist.length 15 | 16 | // 如果关闭的是当前页,跳转到最后一个tag 17 | if(path === currentPath){ 18 | history.push(taglist[length - 1].path) 19 | } 20 | // 如果关闭的是最后的tag ,且当前显示的也是最后的tag对应的页面,才做路由跳转 21 | if(path===taglist[length-1].path && currentPath ===taglist[length-1].path){ 22 | // 因为cutTaglist在最后执行,所以跳转到上一个tags的对应的路由,应该-2 23 | if(length - 2 >0){ 24 | history.push(taglist[length - 2].path) 25 | }else if(length===2) { 26 | history.push(taglist[0].path) 27 | } 28 | } 29 | 30 | // 先跳转路由,再修改state树的taglist 31 | cutTaglist(path) 32 | 33 | } 34 | 35 | const handleClick =(path,event)=>{ 36 | 37 | const {history} = context.router 38 | // 如果点击的是关闭按钮,或点击的tag是当前页面,不执行 39 | if(event.target.tagName==='I' || currentPath===path) return 40 | history.push(path) 41 | } 42 | 43 | return ( 44 | {handleClose(path)}} 46 | closable 47 | color={currentPath===path?'geekblue':'gold'} 48 | onClick={e=>handleClick(path,e)} 49 | >{title} 50 | ) 51 | } 52 | 53 | TagList.contextTypes = { 54 | router: PropTypes.object.isRequired 55 | } 56 | 57 | const mapStateToProps = state => ({taglist: state.UI.taglist}) 58 | const mapDispatchToProps = dispatch => ({ 59 | cutTaglist: playload => { 60 | dispatch(cutTaglist(playload)) 61 | } 62 | }) 63 | 64 | export default connect(mapStateToProps,mapDispatchToProps)(TagList) 65 | -------------------------------------------------------------------------------- /src/components/wysiwyg/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Row, Col, Card } from 'antd' 3 | import { Editor } from 'react-draft-wysiwyg' 4 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' 5 | import draftToHtml from 'draftjs-to-html' 6 | import draftToMarkdown from 'draftjs-to-markdown' 7 | 8 | const rawContentState = {'entityMap':{'0':{'type':'IMAGE','mutability':'MUTABLE','data':{'src':'http://i.imgur.com/aMtBIep.png','height':'auto','width':'100%'}}},'blocks':[{'key':'9unl6','text':'','type':'unstyled','depth':0,'inlineStyleRanges':[],'entityRanges':[],'data':{}},{'key':'95kn','text':' ','type':'atomic','depth':0,'inlineStyleRanges':[],'entityRanges':[{'offset':0,'length':1,'key':0}],'data':{}},{'key':'7rjes','text':'','type':'unstyled','depth':0,'inlineStyleRanges':[],'entityRanges':[],'data':{}}]} 9 | 10 | class Wysiwyg extends Component { 11 | state = { 12 | editorContent: undefined, 13 | contentState: rawContentState, 14 | editorState: '', 15 | } 16 | 17 | onEditorChange = editorContent => { 18 | this.setState({ 19 | editorContent, 20 | }) 21 | } 22 | 23 | clearContent = () => { 24 | this.setState({ 25 | contentState: '', 26 | }) 27 | } 28 | 29 | onContentStateChange = contentState => { 30 | console.log('contentState', contentState) 31 | } 32 | 33 | onEditorStateChange = editorState => { 34 | this.setState({ 35 | editorState, 36 | }) 37 | } 38 | 39 | imageUploadCallBack = file => new Promise( 40 | (resolve, reject) => { 41 | const xhr = new XMLHttpRequest() 42 | xhr.open('POST', 'https://api.imgur.com/3/image') 43 | xhr.setRequestHeader('Authorization', 'Client-ID 8d26ccd12712fca') 44 | const data = new FormData() 45 | data.append('image', file) 46 | xhr.send(data) 47 | xhr.addEventListener('load', () => { 48 | const response = JSON.parse(xhr.responseText) 49 | resolve(response) 50 | }) 51 | xhr.addEventListener('error', () => { 52 | const error = JSON.parse(xhr.responseText) 53 | reject(error) 54 | }) 55 | } 56 | ) 57 | 58 | render() { 59 | const { editorContent, editorState } = this.state 60 | return ( 61 | 62 | 66 | {console.log('blur')}} 85 | onContentStateChange={this.onEditorChange} 86 | onEditorStateChange={this.onEditorStateChange} 87 | onFocus={() => {console.log('focus')}} 88 | onTab={() => {console.log('tab'); return true}} 89 | placeholder="输入正文,可以同步转换格式" 90 | spellCheck 91 | toolbar={{ 92 | history: { inDropdown: true }, 93 | inline: { inDropdown: false }, 94 | list: { inDropdown: true }, 95 | textAlign: { inDropdown: true }, 96 | image: { uploadCallback: this.imageUploadCallBack }, 97 | }} 98 | toolbarClassName="home-toolbar" 99 | wrapperClassName="home-wrapper" 100 | /> 101 | 102 | 107 | 108 | ) 109 | } 110 | } 111 | 112 | export default Wysiwyg -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*css 初始化 */ 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | font-family: sans-serif; 7 | text-rendering: optimizeLegibility; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | html, 13 | body, 14 | :global(#root) { 15 | height: 100%; 16 | } 17 | :global(.ant-layout) { 18 | min-height: 100%; 19 | } 20 | 21 | html, 22 | body, 23 | ul, 24 | li, 25 | ol, 26 | dl, 27 | dd, 28 | dt, 29 | p, 30 | h1, 31 | h2, 32 | h3, 33 | h4, 34 | h5, 35 | h6, 36 | form, 37 | fieldset, 38 | legend, 39 | img { 40 | margin: 0; 41 | padding: 0; 42 | font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif 43 | } 44 | 45 | fieldset, 46 | img, 47 | input, 48 | button { 49 | border: none; 50 | padding: 0; 51 | margin: 0; 52 | outline-style: none; 53 | } 54 | 55 | ul, 56 | ol { 57 | list-style: none; 58 | } 59 | 60 | input { 61 | padding-top: 0; 62 | padding-bottom: 0; 63 | font-family: "SimSun", "宋体"; 64 | } 65 | 66 | select, 67 | input { 68 | vertical-align: middle; 69 | } 70 | 71 | select, 72 | input, 73 | textarea { 74 | font-size: 12px; 75 | margin: 0; 76 | } 77 | 78 | textarea { 79 | resize: none; 80 | } 81 | 82 | /*防止拖动*/ 83 | 84 | img { 85 | border: 0; 86 | vertical-align: middle; 87 | } 88 | 89 | /* 去掉图片低测默认的3像素空白缝隙*/ 90 | 91 | table { 92 | border-collapse: collapse; 93 | } 94 | 95 | body { 96 | font: 12px/150% Arial, Verdana, "\5b8b\4f53"; 97 | color: #666; 98 | background: #fff; 99 | width: 1263px; 100 | } 101 | 102 | .clearfix:before, 103 | .clearfix:after { 104 | content: ""; 105 | display: table; 106 | } 107 | 108 | .clearfix:after { 109 | clear: both; 110 | } 111 | 112 | .clearfix { 113 | *zoom: 1; 114 | /*IE/7/6*/ 115 | } 116 | /* 路由过渡效果 */ 117 | .fade-enter { 118 | opacity: 0; 119 | transform: translateX(-30px); 120 | } 121 | 122 | .fade-enter-active, 123 | .fade-exit-active 124 | { 125 | opacity: 1; 126 | transition: all 500ms ease-out; 127 | transform: translateX(0); 128 | } 129 | 130 | .fade-exit { 131 | opacity: 0; 132 | transform: translateX(30px); 133 | } 134 | 135 | 136 | 137 | .shadow-radius { 138 | box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1), 0 1px 4px rgba(0, 0, 0, 0.1); 139 | border-radius: 2px; 140 | padding: 8px; 141 | background-color: #fff !important; 142 | } 143 | 144 | canvas { 145 | display: block; 146 | } 147 | 148 | .globalSpin { 149 | width: 100%; 150 | margin: 40px 0 !important; 151 | } 152 | 153 | .ant-spin-container { 154 | overflow: visible !important; 155 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { AppContainer } from 'react-hot-loader' 4 | import './index.css' 5 | import App from './App' 6 | import registerServiceWorker from './registerServiceWorker' 7 | 8 | const render = Component => { // 增加react-hot-loader保持状态刷新操作,如果不需要可去掉并把下面注释的打开 9 | ReactDOM.render( 10 | 11 | 12 | 13 | ,document.getElementById('root') 14 | ) 15 | } 16 | 17 | render(App) 18 | 19 | // Webpack Hot Module Replacement API 20 | if (module.hot) { 21 | // 隐藏You cannot change it will be ignored 错误提示 22 | // react-hot-loader 使用在react-router 3.x上引起的提示,react-router 4.x不存在 23 | // 详情可参照https://github.com/gaearon/react-hot-loader/issues/298 24 | const orgError = console.error // eslint-disable-line no-console 25 | console.error = (...args) => { // eslint-disable-line no-console 26 | if (args && args.length === 1 && typeof args[0] === 'string' && args[0].indexOf('You cannot change ') > -1) { 27 | // React route changed 28 | } else { 29 | // Log the error as normally 30 | orgError.apply(console, args) 31 | } 32 | } 33 | module.hot.accept('./App', () => { 34 | render(App) 35 | }) 36 | } 37 | 38 | registerServiceWorker() 39 | -------------------------------------------------------------------------------- /src/redux/actions/index.js: -------------------------------------------------------------------------------- 1 | import { login,getUserInfo,deleteToken } from './login' 2 | import { changeIsMobile,changeCollapsed,addTaglist,cutTaglist,emptyTaglist } from './ui' 3 | 4 | export { 5 | login, 6 | getUserInfo, 7 | deleteToken, 8 | changeIsMobile, 9 | changeCollapsed, 10 | addTaglist, 11 | cutTaglist, 12 | emptyTaglist 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/redux/actions/login.js: -------------------------------------------------------------------------------- 1 | import api from '@/axios' 2 | import { message } from 'antd' 3 | import { SET_TOKEN,SET_USERINFO,DELETE_TOKEN } from './type.js' 4 | const login = params => dispatch => { 5 | /** 6 | * 登录需要发送两次请求,第一次获取token 7 | * 第二次请求再把token传给后台,获取用户信息 8 | **/ 9 | return new Promise(reslove => { 10 | api.post('/login', params).then(res => { 11 | // 账号密码正确,不发送第二次请求 12 | if (res.data.success) { 13 | //设置全局token并缓存token 14 | dispatch({ 15 | type: SET_TOKEN, 16 | playload: res.data.datas.token 17 | }) 18 | localStorage.setItem('token', res.data.datas.token) 19 | } 20 | reslove(res.data.success) 21 | }) 22 | }) 23 | } 24 | 25 | const deleteToken = ()=> dispatch=>{ 26 | localStorage.removeItem('token') 27 | dispatch({ 28 | type: DELETE_TOKEN 29 | }) 30 | message.success('已退出登录') 31 | window.location = '#/login' 32 | } 33 | 34 | const getUserInfo = () => dispatch => { 35 | return new Promise(reslove=> { 36 | api.post('/getUserInfo').then(res => { 37 | 38 | // 获取用户信息成功,将用户信息保存到全局状态树 39 | 40 | if (res.data.success) { 41 | 42 | localStorage.setItem('userInfo', JSON.stringify(res.data.datas)) 43 | 44 | dispatch({ 45 | type: SET_USERINFO, 46 | playload: res.data.datas 47 | }) 48 | } 49 | reslove(res.data.success) 50 | }) 51 | }) 52 | } 53 | export { 54 | login, 55 | getUserInfo, 56 | deleteToken 57 | } -------------------------------------------------------------------------------- /src/redux/actions/type.js: -------------------------------------------------------------------------------- 1 | const CHANGE_ISMOBILE = 'CHANGE_ISMOBILE' 2 | const CHANGE_COLLAPSED = 'CHANGE_COLLAPSED' 3 | const ADD_TAGLIST = 'ADD_TAGLIST' 4 | const CUT_TAGLIST = 'CUT_TAGLIST' 5 | const EMPTY_TAGLIST = 'EMPTY_TAGLIST' 6 | const SET_TOKEN = 'SET_TOKEN' // 设置token 7 | const SET_USERINFO = 'SET_USERINFO' // 设置用户信息 8 | const DELETE_TOKEN = 'DELETE_TOKEN' // 清除token 9 | export { 10 | CHANGE_ISMOBILE, 11 | CHANGE_COLLAPSED, 12 | ADD_TAGLIST, 13 | CUT_TAGLIST, 14 | EMPTY_TAGLIST, 15 | SET_TOKEN, 16 | SET_USERINFO, 17 | DELETE_TOKEN 18 | } -------------------------------------------------------------------------------- /src/redux/actions/ui.js: -------------------------------------------------------------------------------- 1 | import * as type from './type' 2 | import { menus } from '@/router/menus' 3 | 4 | // 改变左侧菜单栏宽度(是否显示) 5 | const changeIsMobile = playload => ({ 6 | type: type.CHANGE_ISMOBILE, 7 | playload 8 | }) 9 | 10 | // 改变左侧菜单栏宽度(展开或者收缩) 11 | const changeCollapsed = playload => ({ 12 | type: type.CHANGE_COLLAPSED, 13 | playload 14 | }) 15 | 16 | // 减少tags标签页 17 | const cutTaglist = playload => ({ 18 | type: type.CUT_TAGLIST, 19 | playload 20 | }) 21 | // 清空tags标签页 22 | const emptyTaglist = { 23 | type: type.EMPTY_TAGLIST 24 | } 25 | 26 | // 增加tags标签页 27 | const addTaglist = playload => (dispatch, getState) => { 28 | 29 | const currentTaglist = getState().UI.taglist 30 | 31 | // 如果当前taglist已存在待添加成员,不执行 32 | if (currentTaglist.filter(ele=>ele.path ===playload).length) return 33 | 34 | // 如果当前taglist数量>=10,先删除第一个tag,再添加,taglist最多只有10个成员 35 | if (currentTaglist.length >= 10) { 36 | dispatch(cutTaglist(currentTaglist[0].path)) 37 | } 38 | 39 | 40 | function handleDispatch(obj){ 41 | dispatch({ 42 | type: type.ADD_TAGLIST, 43 | playload: obj 44 | }) 45 | } 46 | 47 | menus.forEach(ele=>{ 48 | if(ele.path===playload) { 49 | handleDispatch(ele) 50 | }else if(ele.children){ 51 | ele.children.forEach(ele2=>{ 52 | if(ele2.path===playload){ 53 | handleDispatch(ele2) 54 | } 55 | }) 56 | } 57 | }) 58 | } 59 | 60 | 61 | export { 62 | changeIsMobile, 63 | changeCollapsed, 64 | addTaglist, 65 | cutTaglist, 66 | emptyTaglist 67 | } 68 | -------------------------------------------------------------------------------- /src/redux/reducers/all.js: -------------------------------------------------------------------------------- 1 | const userState = { 2 | token: '', 3 | userInfo:{ 4 | roles: '', 5 | introduction: '', 6 | name: '', 7 | avatar: '', 8 | message:'' 9 | } 10 | } 11 | 12 | const user = (state = userState, action) => { 13 | switch (action.type) { 14 | case 'SET_TOKEN': 15 | return { 16 | ...state, 17 | token: action.playload 18 | } 19 | break 20 | 21 | case 'DELETE_TOKEN': 22 | return { 23 | ...state, 24 | token: null 25 | } 26 | break 27 | case 'SET_USERINFO': 28 | return { 29 | ...state, 30 | userInfo: action.playload 31 | } 32 | break 33 | 34 | default: 35 | return state 36 | break 37 | } 38 | } 39 | 40 | 41 | const UIState = { 42 | collapsed: false, 43 | isMobile: false, 44 | taglist:[] 45 | } 46 | const UI = (state = UIState, action) => { 47 | switch (action.type) { 48 | case 'CHANGE_ISMOBILE': 49 | return { 50 | ...state, 51 | isMobile: action.playload 52 | } 53 | break 54 | 55 | case 'CHANGE_COLLAPSED': 56 | return { 57 | ...state, 58 | collapsed: action.playload 59 | } 60 | break 61 | 62 | case 'ADD_TAGLIST': 63 | return { 64 | ...state, 65 | taglist: [...state.taglist,action.playload] 66 | } 67 | break 68 | case 'CUT_TAGLIST': 69 | return { 70 | ...state, 71 | taglist: [...state.taglist.filter(ele=>ele.path!==action.playload)] 72 | } 73 | break 74 | case 'EMPTY_TAGLIST': 75 | return { 76 | ...state, 77 | taglist: [] 78 | } 79 | break 80 | 81 | default: 82 | return state 83 | break 84 | } 85 | } 86 | 87 | 88 | export { 89 | UI, 90 | user 91 | } -------------------------------------------------------------------------------- /src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux' 2 | import * as reducers from './all' 3 | export default combineReducers({...reducers}) -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import { createLogger } from 'redux-logger' 3 | import thunk from 'redux-thunk' 4 | import reducers from './reducers' 5 | const loggerMiddleware = createLogger() 6 | 7 | const store = createStore( 8 | reducers, 9 | applyMiddleware(thunk, loggerMiddleware) 10 | ) 11 | export default store 12 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Dashboard from '@/views/dashboard' 2 | import Error401 from '@/views/error/401' 3 | import Error404 from '@/views/error/404' 4 | import FormCreate from '@/views/form' 5 | import Map from '@/views/map' 6 | import PermissionIntercept from '@/views/permission/intercept' 7 | import PermissionToggle from '@/views/permission/toggle' 8 | 9 | import ComponentWysiwyg from '@/views/components/wysiwyg' 10 | import ComponentDrag from '@/views/components/drag' 11 | import ComponentBackToTop from '@/views/components/backtotop' 12 | 13 | import LineChart from '@/views/components/chart/linechart' 14 | import Keyboard from '@/views/components/chart/keyboard' 15 | import Mixchart from '@/views/components/chart/mixchart' 16 | 17 | import TableBasic from '@/views/table/basic' 18 | import TableEdit from '@/views/table/edit' 19 | import TableDynamic from '@/views/table/dynamic' 20 | 21 | 22 | export const routes = [ 23 | 24 | { path: '/dashboard', component: Dashboard }, 25 | { path: '/chart/line', component: LineChart }, 26 | { path: '/chart/keyboard', component: Keyboard }, 27 | { path: '/chart/mixchart', component: Mixchart }, 28 | 29 | { path: '/permission/toggle', component: PermissionToggle }, 30 | { path: '/permission/intercept', component: PermissionIntercept, permission:'admin' }, 31 | 32 | { path: '/table/basic', component: TableBasic }, 33 | { path: '/table/edit', component: TableEdit }, 34 | { path: '/table/dynamic', component: TableDynamic }, 35 | 36 | 37 | { path: '/form', component: FormCreate, permission:'admin'}, 38 | 39 | { path: '/components/wysiwyg', component: ComponentWysiwyg }, 40 | { path: '/components/drag', component: ComponentDrag }, 41 | { path: '/components/backToTop', component: ComponentBackToTop }, 42 | 43 | 44 | { path: '/error/401', component: Error401}, 45 | { path: '/error/404', component: Error404}, 46 | 47 | { 48 | path: '/map', component: Map 49 | } 50 | ] -------------------------------------------------------------------------------- /src/router/menus.js: -------------------------------------------------------------------------------- 1 | export const menus = [ 2 | { path: '/dashboard', title: '首页', icon: 'home' }, 3 | { 4 | path: '/chart', title: '图表', icon: 'area-chart', 5 | children: [ 6 | { path: '/chart/line', title: '折线图' }, 7 | { path: '/chart/keyboard', title: '键盘图表' }, 8 | { path: '/chart/mixchart', title: '混合图表'}, 9 | ], 10 | }, 11 | 12 | { 13 | path: '/permission', title: '权限测试', icon: 'rocket', 14 | children: [ 15 | { path: '/permission/toggle', title: '权限切换' }, 16 | { path: '/permission/intercept', title: '路由拦截', permission:'admin'} 17 | ], 18 | }, 19 | { 20 | path: '/table', title: '表格', icon: 'copy', 21 | children: [ 22 | { path: '/table/basic', title: '基础表格' }, 23 | { path: '/table/edit', title: '表格编辑' }, 24 | { path: '/table/dynamic', title: '动态列表格' }, 25 | ], 26 | }, 27 | { 28 | path: '/form', title: '表单', icon: 'edit', permission:'admin' 29 | }, 30 | { 31 | path: '/components', title: '组件', icon: 'scan', 32 | children: [ 33 | { path: '/components/wysiwyg', title: '富文本'}, 34 | { path: '/components/drag', title: '拖拽'}, 35 | { path: '/components/backToTop', title: '返回顶部'} 36 | ], 37 | }, 38 | 39 | { 40 | path: '/error', title: '错误页面', icon: 'switcher', 41 | children: [ 42 | { path: '/error/401', title: '401'}, 43 | { path: '/error/404', title: '404'}, 44 | ], 45 | }, 46 | { 47 | path: '/map', title: '地图', icon: 'star', 48 | } 49 | ] -------------------------------------------------------------------------------- /src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | export function debounce(func, wait, immediate) { 2 | let timeout, args, context, timestamp, result 3 | 4 | const later = function() { 5 | // 据上一次触发时间间隔 6 | const last = +new Date() - timestamp 7 | 8 | // 上次被包装函数被调用时间间隔last小于设定时间间隔wait 9 | if (last < wait && last > 0) { 10 | timeout = setTimeout(later, wait - last) 11 | } else { 12 | timeout = null 13 | // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 14 | if (!immediate) { 15 | result = func.apply(context, args) 16 | if (!timeout) context = args = null 17 | } 18 | } 19 | } 20 | 21 | return function(...args) { 22 | context = this 23 | timestamp = +new Date() 24 | const callNow = immediate && !timeout 25 | // 如果延时不存在,重新设定延时 26 | if (!timeout) timeout = setTimeout(later, wait) 27 | if (callNow) { 28 | result = func.apply(context, args) 29 | context = args = null 30 | } 31 | 32 | return result 33 | } 34 | } -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import {debounce} from './debounce' 2 | 3 | export {debounce} -------------------------------------------------------------------------------- /src/views/components/backtotop/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BackTop} from 'antd' 3 | import Type1 from '@/components/backtotop/Type1' 4 | import Type2 from '@/components/backtotop/Type2' 5 | import Type3 from '@/components/backtotop/Type3' 6 | import Type4 from '@/components/backtotop/Type4' 7 | 8 | 9 | const BackToTop = ()=>{ 10 | return ( 11 |
14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 | ) 27 | } 28 | 29 | export default BackToTop -------------------------------------------------------------------------------- /src/views/components/chart/keyboard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Chart from '@/components/chart' 3 | 4 | const xAxisData = [] 5 | const data = [] 6 | const data2 = [] 7 | for (let i = 0; i < 50; i++) { 8 | xAxisData.push(i) 9 | data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5) 10 | data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3) 11 | } 12 | const chartData = { 13 | backgroundColor: '#08263a', 14 | xAxis: [ 15 | { 16 | show: false, 17 | data: xAxisData 18 | }, { 19 | show: false, 20 | data: xAxisData 21 | } 22 | ], 23 | visualMap: { 24 | show: false, 25 | min: 0, 26 | max: 50, 27 | dimension: 0, 28 | inRange: { 29 | color: [ 30 | '#4a657a', 31 | '#308e92', 32 | '#b1cfa5', 33 | '#f5d69f', 34 | '#f5898b', 35 | '#ef5055' 36 | ] 37 | } 38 | }, 39 | yAxis: { 40 | axisLine: { 41 | show: false 42 | }, 43 | axisLabel: { 44 | textStyle: { 45 | color: '#4a657a' 46 | } 47 | }, 48 | splitLine: { 49 | show: true, 50 | lineStyle: { 51 | color: '#08263f' 52 | } 53 | }, 54 | axisTick: { 55 | show: false 56 | } 57 | }, 58 | series: [ 59 | { 60 | name: 'back', 61 | type: 'bar', 62 | data: data2, 63 | z: 1, 64 | itemStyle: { 65 | normal: { 66 | opacity: 0.4, 67 | barBorderRadius: 5, 68 | shadowBlur: 3, 69 | shadowColor: '#111' 70 | } 71 | } 72 | }, { 73 | name: 'Simulate Shadow', 74 | type: 'line', 75 | data, 76 | z: 2, 77 | showSymbol: false, 78 | animationDelay: 0, 79 | animationEasing: 'linear', 80 | animationDuration: 0, 81 | lineStyle: { 82 | normal: { 83 | color: 'transparent' 84 | } 85 | }, 86 | areaStyle: { 87 | normal: { 88 | color: '#08263a', 89 | shadowBlur: 50, 90 | shadowColor: '#000' 91 | } 92 | } 93 | }, { 94 | name: 'front', 95 | type: 'bar', 96 | data, 97 | xAxisIndex: 1, 98 | z: 3, 99 | itemStyle: { 100 | normal: { 101 | barBorderRadius: 5 102 | } 103 | } 104 | } 105 | ], 106 | animationEasing: 'elasticOut', 107 | animationEasingUpdate: 'elasticOut', 108 | animationDelay(idx) { 109 | return idx * 20 110 | }, 111 | animationDelayUpdate(idx) { 112 | return idx * 20 113 | } 114 | } 115 | 116 | const Keyboard = () => { 117 | return ( 118 | ) 125 | } 126 | 127 | export default Keyboard -------------------------------------------------------------------------------- /src/views/components/chart/linechart/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Chart from '@/components/chart' 3 | import echarts from 'echarts' 4 | const chartData = { 5 | backgroundColor: '#fff', 6 | title: { 7 | top: 20, 8 | text: 'title', 9 | textStyle: { 10 | fontWeight: 'normal', 11 | fontSize: 16, 12 | color: '#57617B' 13 | }, 14 | left: '1%' 15 | }, 16 | tooltip: { 17 | trigger: 'axis', 18 | axisPointer: { 19 | type: 'cross' 20 | }, 21 | padding: [5, 10] 22 | }, 23 | // tab 24 | legend: { 25 | top: 20, 26 | icon: 'rect', 27 | itemWidth: 14, 28 | itemHeight: 5, 29 | itemGap: 13, 30 | right: '4%', 31 | textStyle: { 32 | fontSize: 12, 33 | color: '#57617B' 34 | } 35 | }, 36 | 37 | // 图表 38 | grid: { 39 | top: 100, 40 | left: '3%', 41 | right: '4%', 42 | bottom: '4%', 43 | containLabel: true 44 | }, 45 | // x轴 46 | xAxis: [ 47 | { 48 | type: 'category', //分类 49 | boundaryGap: false, 50 | axisLine: { 51 | lineStyle: { 52 | color: '#57617B' 53 | } 54 | }, 55 | data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55'] 56 | } 57 | ], 58 | yAxis: [ 59 | { 60 | type: 'value', 61 | name: '(%)', 62 | axisTick: { 63 | show: false 64 | }, 65 | axisLine: { 66 | lineStyle: { 67 | color: '#57617B' 68 | } 69 | }, 70 | axisLabel: { 71 | margin: 10, 72 | textStyle: { 73 | fontSize: 14 74 | } 75 | } 76 | } 77 | ], 78 | series: [ 79 | { 80 | name: 'A1', 81 | type: 'line', 82 | smooth: true, 83 | symbol: 'circle', 84 | symbolSize: 5, 85 | showSymbol: false, 86 | lineStyle: { 87 | normal: { 88 | width: 1 89 | } 90 | }, 91 | areaStyle: { 92 | normal: { 93 | color: new echarts 94 | .graphic 95 | .LinearGradient(0, 0, 0, 1, [ 96 | { 97 | offset: 0, 98 | color: 'rgba(137, 189, 27, 0.3)' 99 | }, { 100 | offset: 0.8, 101 | color: 'rgba(137, 189, 27, 0)' 102 | } 103 | ], false), 104 | shadowColor: 'rgba(0, 0, 0, 0.1)', 105 | shadowBlur: 10 106 | } 107 | }, 108 | itemStyle: { 109 | normal: { 110 | color: 'rgb(137,189,27)', 111 | borderColor: 'rgba(137,189,2,0.27)', 112 | borderWidth: 12 113 | } 114 | }, 115 | data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122] 116 | }, { 117 | name: 'A2', 118 | type: 'line', 119 | smooth: true, 120 | symbol: 'circle', 121 | symbolSize: 5, 122 | showSymbol: false, 123 | lineStyle: { 124 | normal: { 125 | width: 1 126 | } 127 | }, 128 | areaStyle: { 129 | normal: { 130 | color: new echarts 131 | .graphic 132 | .LinearGradient(0, 0, 0, 1, [ 133 | { 134 | offset: 0, 135 | color: 'rgba(0, 136, 212, 0.3)' 136 | }, { 137 | offset: 0.8, 138 | color: 'rgba(0, 136, 212, 0)' 139 | } 140 | ], false), 141 | shadowColor: 'rgba(0, 0, 0, 0.1)', 142 | shadowBlur: 10 143 | } 144 | }, 145 | itemStyle: { 146 | normal: { 147 | color: 'rgb(0,136,212)', 148 | borderColor: 'rgba(0,136,212,0.2)', 149 | borderWidth: 12 150 | } 151 | }, 152 | data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150] 153 | }, { 154 | name: 'A3', 155 | type: 'line', 156 | smooth: true, 157 | symbol: 'circle', 158 | symbolSize: 5, 159 | showSymbol: false, 160 | lineStyle: { 161 | normal: { 162 | width: 1 163 | } 164 | }, 165 | areaStyle: { 166 | normal: { 167 | color: new echarts 168 | .graphic 169 | .LinearGradient(0, 0, 0, 1, [ 170 | { 171 | offset: 0, 172 | color: 'rgba(219, 50, 51, 0.3)' 173 | }, { 174 | offset: 0.8, 175 | color: 'rgba(219, 50, 51, 0)' 176 | } 177 | ], false), 178 | shadowColor: 'rgba(0, 0, 0, 0.1)', 179 | shadowBlur: 10 180 | } 181 | }, 182 | itemStyle: { 183 | normal: { 184 | color: 'rgb(219,50,51)', 185 | borderColor: 'rgba(219,50,51,0.2)', 186 | borderWidth: 12 187 | } 188 | }, 189 | data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122] 190 | } 191 | ] 192 | } 193 | 194 | const LineChart = () => { 195 | return ( 196 | ) 203 | } 204 | 205 | export default LineChart -------------------------------------------------------------------------------- /src/views/components/chart/mixchart/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Chart from '@/components/chart' 3 | 4 | 5 | 6 | let xData = new Array() 7 | for (let i = 0; i < 12; i++) { 8 | xData[i] = i+1 + 'month' 9 | } 10 | const chartData = { 11 | backgroundColor: '#344b58', 12 | title: { 13 | text: 'mixChart', 14 | x: '20', 15 | top: '15', 16 | textStyle: { 17 | color: '#fff', 18 | fontSize: '20' 19 | }, 20 | subtextStyle: { 21 | color: '#90979c', 22 | fontSize: '16' 23 | } 24 | }, 25 | tooltip: { 26 | trigger: 'axis', 27 | axisPointer: { 28 | textStyle: { 29 | color: '#fff' 30 | } 31 | } 32 | }, 33 | grid: { 34 | borderWidth: 0, 35 | top: 110, 36 | bottom: 95, 37 | textStyle: { 38 | color: '#fff' 39 | } 40 | }, 41 | legend: { 42 | x: '5%', 43 | top: '10%', 44 | textStyle: { 45 | color: '#90979c' 46 | }, 47 | data: ['female', 'male', 'average'] 48 | }, 49 | calculable: true, 50 | xAxis: [{ 51 | type: 'category', 52 | axisLine: { 53 | lineStyle: { 54 | color: '#90979c' 55 | } 56 | }, 57 | splitLine: { 58 | show: false 59 | }, 60 | axisTick: { 61 | show: false 62 | }, 63 | splitArea: { 64 | show: false 65 | }, 66 | axisLabel: { 67 | interval: 0 68 | 69 | }, 70 | data: xData 71 | }], 72 | yAxis: [{ 73 | type: 'value', 74 | splitLine: { 75 | show: false 76 | }, 77 | axisLine: { 78 | lineStyle: { 79 | color: '#90979c' 80 | } 81 | }, 82 | axisTick: { 83 | show: false 84 | }, 85 | axisLabel: { 86 | interval: 0 87 | }, 88 | splitArea: { 89 | show: false 90 | } 91 | }], 92 | dataZoom: [{ 93 | show: true, 94 | height: 30, 95 | xAxisIndex: [0], 96 | bottom: 30, 97 | start: 10, 98 | end: 80, 99 | handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h5' + 100 | '9.8c2.2,0,4,1.8,4,4V413z', 101 | handleSize: '110%', 102 | handleStyle: { 103 | color: '#d3dee5' 104 | 105 | }, 106 | textStyle: { 107 | color: '#fff' 108 | }, 109 | borderColor: '#90979c' 110 | 111 | }, { 112 | type: 'inside', 113 | show: true, 114 | height: 15, 115 | start: 1, 116 | end: 35 117 | }], 118 | series: [{ 119 | name: 'female', 120 | type: 'bar', 121 | stack: 'total', 122 | barMaxWidth: 35, 123 | barGap: '10%', 124 | itemStyle: { 125 | normal: { 126 | color: 'rgba(255,144,128,1)', 127 | label: { 128 | show: true, 129 | textStyle: { 130 | color: '#fff' 131 | }, 132 | position: 'insideTop', 133 | formatter (p) { 134 | return p.value > 0 ? p.value : '' 135 | } 136 | } 137 | } 138 | }, 139 | data: [ 140 | 709, 141 | 1917, 142 | 2455, 143 | 2610, 144 | 1719, 145 | 1433, 146 | 1544, 147 | 3285, 148 | 5208, 149 | 3372, 150 | 2484, 151 | 4078 152 | ] 153 | }, 154 | 155 | { 156 | name: 'male', 157 | type: 'bar', 158 | stack: 'total', 159 | itemStyle: { 160 | normal: { 161 | color: 'rgba(0,191,183,1)', 162 | barBorderRadius: 0, 163 | label: { 164 | show: true, 165 | position: 'top', 166 | formatter (p) { 167 | return p.value > 0 ? p.value : '' 168 | } 169 | } 170 | } 171 | }, 172 | data: [ 173 | 327, 174 | 1776, 175 | 507, 176 | 1200, 177 | 800, 178 | 482, 179 | 204, 180 | 1390, 181 | 1001, 182 | 951, 183 | 381, 184 | 220 185 | ] 186 | }, { 187 | name: 'average', 188 | type: 'line', 189 | stack: 'total', 190 | symbolSize: 10, 191 | symbol: 'circle', 192 | itemStyle: { 193 | normal: { 194 | color: 'rgba(252,230,48,1)', 195 | barBorderRadius: 0, 196 | label: { 197 | show: true, 198 | position: 'top', 199 | formatter (p) { 200 | return p.value > 0 ? p.value : '' 201 | } 202 | } 203 | } 204 | }, 205 | data: [ 206 | 1036, 207 | 3693, 208 | 2962, 209 | 3810, 210 | 2519, 211 | 1915, 212 | 1748, 213 | 4675, 214 | 6209, 215 | 4323, 216 | 2865, 217 | 4298 218 | ] 219 | } 220 | ] 221 | } 222 | 223 | 224 | 225 | 226 | const Mixchart = () => { 227 | return ( 228 | ) 233 | } 234 | 235 | export default Mixchart -------------------------------------------------------------------------------- /src/views/components/drag/components/Header.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/src/views/components/drag/components/Header.jsx -------------------------------------------------------------------------------- /src/views/components/drag/components/LeftItem.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc' 3 | import '../index.less' 4 | 5 | class LeftItem extends Component { 6 | constructor(props) { 7 | super(props) 8 | this.state = { 9 | items: [] 10 | } 11 | } 12 | 13 | componentWillReceiveProps(nextProps) { 14 | if (nextProps.list !== this.props.list) { 15 | this.setState({items: nextProps.list}) 16 | } 17 | } 18 | onSortEnd = ({oldIndex, newIndex}) => { 19 | this.setState({ 20 | items: arrayMove(this.state.items, oldIndex, newIndex) 21 | }) 22 | } 23 | 24 | shouldCancelStart = e => { 25 | // 如果是a标签,终止拖动,并关闭当前行 26 | if (e.target.tagName.toLowerCase() === 'a') { 27 | 28 | const index = e 29 | .target 30 | .getAttribute('data-index') 31 | 32 | const result = this 33 | .state 34 | .items 35 | .filter((ele, i) => i != index) 36 | this.setState({items: result}) 37 | 38 | return true 39 | } else { 40 | return false 41 | } 42 | 43 | } 44 | 45 | render() { 46 | const SortableItem = SortableElement(({text, color, sortIndex}) => { 47 | return ( 48 |
  • 49 | {text} 50 | 关闭 57 |
  • 58 | ) 59 | }) 60 | const SortableList = SortableContainer(({items}) => { 61 | return ( 62 |
      63 | {items.map((ele, index) => ())} 70 |
    71 | ) 72 | }) 73 | 74 | return 79 | 80 | } 81 | } 82 | 83 | export default LeftItem -------------------------------------------------------------------------------- /src/views/components/drag/components/RightItem.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc' 3 | import '../index.less' 4 | 5 | const SortableItem = SortableElement(({color}) =>
  • 6 | {color} 7 |
  • ) 8 | const SortableList = SortableContainer(({items}) => { 9 | return ( 10 |
      11 | {items.map((ele, index) => ())} 16 |
    17 | ) 18 | }) 19 | 20 | class RightItems extends Component { 21 | 22 | state = { 23 | items: [] 24 | } 25 | 26 | componentWillReceiveProps(nextProps) { 27 | if (nextProps.list !== this.props.list) { 28 | this.setState({items: nextProps.list}) 29 | } 30 | } 31 | onSortEnd = ({oldIndex, newIndex}) => { 32 | this.setState({ 33 | items: arrayMove(this.state.items, oldIndex, newIndex) 34 | }) 35 | } 36 | render() { 37 | return 42 | 43 | } 44 | } 45 | 46 | export default RightItems -------------------------------------------------------------------------------- /src/views/components/drag/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import LeftItem from './components/LeftItem' 3 | import RightItem from './components/RightItem' 4 | import {getListData} from './server' 5 | class Drag extends Component { 6 | state = { 7 | list1: [], 8 | list2:[] 9 | } 10 | 11 | componentWillMount(){ 12 | getListData().then(data=>{ 13 | const {list1,list2} = data 14 | this.setState({list1,list2}) 15 | }) 16 | } 17 | componentWillUnmount () { 18 | // componentWillMount进行异步操作时且在callback中进行了setState操作时,需要在组件卸载时清除state 19 | this.setState = ()=>{ 20 | return 21 | } 22 | } 23 | render () { 24 | return ( 25 |
    26 | 27 | 28 |
    29 | ) 30 | } 31 | } 32 | 33 | export default Drag -------------------------------------------------------------------------------- /src/views/components/drag/index.less: -------------------------------------------------------------------------------- 1 | .draggable-wrap { 2 | display: flex; 3 | justify-content: space-between; 4 | margin-top: 20px; 5 | .left-item-ul { 6 | padding: 20px; 7 | width: 49%; 8 | min-height: 400px; 9 | .drap-list { 10 | padding: 0 16px; 11 | width: 100%; 12 | line-height: 48px; 13 | transition: all 0.45s cubic-bezier(0.23, 1, 0.32, 1); 14 | cursor: move; 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | font-size: 16px; 19 | &:hover { 20 | background-color: rgba(0, 0, 0, 0.1); 21 | } 22 | a.close { 23 | height: 20px; 24 | display: inline-block; 25 | line-height: 20px; 26 | padding: 0 6px; 27 | border-radius: 3px; 28 | font-size: 12px; 29 | font-weight: bold; 30 | color: #fff; 31 | cursor: pointer; 32 | } 33 | } 34 | } 35 | 36 | .right-item-ul { 37 | width: 49%; 38 | display: flex; 39 | flex-wrap: wrap; 40 | min-height: 400px; 41 | & > li { 42 | width: 33.3%; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/views/components/drag/server.js: -------------------------------------------------------------------------------- 1 | import api from '@/axios' 2 | 3 | 4 | export const getListData = () => { 5 | return new Promise(reslove => { 6 | api.post('/GetDnfList').then(res => { 7 | const { datas } = res.data 8 | reslove(datas) 9 | }) 10 | }) 11 | 12 | } -------------------------------------------------------------------------------- /src/views/components/map/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Tencent from '@/components/map/TencentMap' 3 | import { Row, Col, Card } from 'antd' 4 | 5 | export default () => ( 6 |
    7 | 8 | 9 |
    10 | 13 | 14 | 15 |
    16 | 17 |
    18 |
    19 | ) -------------------------------------------------------------------------------- /src/views/components/wysiwyg/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Row, Col, Card } from 'antd' 3 | import { Editor } from 'react-draft-wysiwyg' 4 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' 5 | import draftToHtml from 'draftjs-to-html' 6 | import draftToMarkdown from 'draftjs-to-markdown' 7 | 8 | const rawContentState = {'entityMap':{'0':{'type':'IMAGE','mutability':'MUTABLE','data':{'src':'http://i.imgur.com/aMtBIep.png','height':'auto','width':'100%'}}},'blocks':[{'key':'9unl6','text':'','type':'unstyled','depth':0,'inlineStyleRanges':[],'entityRanges':[],'data':{}},{'key':'95kn','text':' ','type':'atomic','depth':0,'inlineStyleRanges':[],'entityRanges':[{'offset':0,'length':1,'key':0}],'data':{}},{'key':'7rjes','text':'','type':'unstyled','depth':0,'inlineStyleRanges':[],'entityRanges':[],'data':{}}]} 9 | 10 | class Wysiwyg extends Component { 11 | state = { 12 | editorContent: undefined, 13 | contentState: rawContentState, 14 | editorState: '', 15 | } 16 | 17 | onEditorChange = editorContent => { 18 | this.setState({ 19 | editorContent, 20 | }) 21 | } 22 | 23 | clearContent = () => { 24 | this.setState({ 25 | contentState: '', 26 | }) 27 | } 28 | 29 | onContentStateChange = contentState => { 30 | console.log('contentState', contentState) 31 | } 32 | 33 | onEditorStateChange = editorState => { 34 | this.setState({ 35 | editorState, 36 | }) 37 | } 38 | 39 | imageUploadCallBack = file => new Promise( 40 | (resolve, reject) => { 41 | const xhr = new XMLHttpRequest() // eslint-disable-line no-undef 42 | xhr.open('POST', 'https://api.imgur.com/3/image') 43 | xhr.setRequestHeader('Authorization', 'Client-ID 8d26ccd12712fca') 44 | const data = new FormData() // eslint-disable-line no-undef 45 | data.append('image', file) 46 | xhr.send(data) 47 | xhr.addEventListener('load', () => { 48 | const response = JSON.parse(xhr.responseText) 49 | resolve(response) 50 | }) 51 | xhr.addEventListener('error', () => { 52 | const error = JSON.parse(xhr.responseText) 53 | reject(error) 54 | }) 55 | } 56 | ) 57 | 58 | render() { 59 | const { editorContent, editorState } = this.state 60 | return ( 61 |
    62 | 63 | 66 |
    67 | 70 | {console.log('blur')}} 89 | onContentStateChange={this.onEditorChange} 90 | onEditorStateChange={this.onEditorStateChange} 91 | onFocus={() => {console.log('focus')}} 92 | onTab={() => {console.log('tab') ;return true}} 93 | placeholder="输入正文,可以同步转换格式" 94 | spellCheck 95 | toolbar={{ 96 | history: { inDropdown: true }, 97 | inline: { inDropdown: false }, 98 | list: { inDropdown: true }, 99 | textAlign: { inDropdown: true }, 100 | image: { uploadCallback: this.imageUploadCallBack }, 101 | }} 102 | toolbarClassName="home-toolbar" 103 | wrapperClassName="home-wrapper" 104 | /> 105 | 106 | 111 | 112 |
    113 | 114 | 117 | 120 |
    {draftToHtml(editorContent)}
    121 |
    122 | 123 | 126 | 129 |
    {draftToMarkdown(editorContent)}
    130 |
    131 | 132 | 135 | 138 |
    {JSON.stringify(editorContent)}
    139 |
    140 | 141 |
    142 |
    143 | ) 144 | } 145 | } 146 | 147 | export default Wysiwyg -------------------------------------------------------------------------------- /src/views/dashboard/chartData.js: -------------------------------------------------------------------------------- 1 | const barChartData = { 2 | tooltip: { 3 | trigger: 'axis', 4 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 5 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 6 | } 7 | }, 8 | grid: { 9 | top: 15, 10 | left: '2%', 11 | right: '2%', 12 | bottom: '3%', 13 | containLabel: true 14 | }, 15 | xAxis: [ 16 | { 17 | type: 'category', 18 | data: [ 19 | 'Mon', 20 | 'Tue', 21 | 'Wed', 22 | 'Thu', 23 | 'Fri', 24 | 'Sat', 25 | 'Sun' 26 | ], 27 | axisTick: { 28 | alignWithLabel: true 29 | } 30 | } 31 | ], 32 | yAxis: [ 33 | { 34 | type: 'value', 35 | axisTick: { 36 | show: false 37 | } 38 | } 39 | ], 40 | series: [ 41 | { 42 | name: 'pageA', 43 | type: 'bar', 44 | stack: 'vistors', 45 | barWidth: '60%', 46 | data: [ 47 | 79, 48 | 52, 49 | 200, 50 | 334, 51 | 390, 52 | 330, 53 | 220 54 | ], 55 | animationDuration: 4000 56 | }, { 57 | name: 'pageB', 58 | type: 'bar', 59 | stack: 'vistors', 60 | barWidth: '60%', 61 | data: [ 62 | 80, 63 | 52, 64 | 200, 65 | 334, 66 | 390, 67 | 330, 68 | 220 69 | ], 70 | animationDuration: 4000 71 | }, { 72 | name: 'pageC', 73 | type: 'bar', 74 | stack: 'vistors', 75 | barWidth: '60%', 76 | data: [ 77 | 30, 78 | 52, 79 | 200, 80 | 334, 81 | 390, 82 | 330, 83 | 220 84 | ], 85 | animationDuration: 4000 86 | } 87 | ] 88 | } 89 | 90 | const raddarChartData = { 91 | tooltip: { 92 | trigger: 'axis', 93 | axisPointer: { // 坐标轴指示器,坐标轴触发有效 94 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 95 | } 96 | }, 97 | radar: { 98 | radius: '66%', 99 | center: [ 100 | '50%', '42%' 101 | ], 102 | splitNumber: 8, 103 | splitArea: { 104 | areaStyle: { 105 | color: 'rgba(127,95,132,.3)', 106 | opacity: 1, 107 | shadowBlur: 45, 108 | shadowColor: 'rgba(0,0,0,.5)', 109 | shadowOffsetX: 0, 110 | shadowOffsetY: 15 111 | } 112 | }, 113 | indicator: [ 114 | { 115 | name: 'Sales', 116 | max: 10000 117 | }, { 118 | name: 'Administration', 119 | max: 20000 120 | }, { 121 | name: 'Information Techology', 122 | max: 20000 123 | }, { 124 | name: 'Customer Support', 125 | max: 20000 126 | }, { 127 | name: 'Development', 128 | max: 20000 129 | }, { 130 | name: 'Marketing', 131 | max: 20000 132 | } 133 | ] 134 | }, 135 | legend: { 136 | left: 'center', 137 | bottom: '0', 138 | data: ['Allocated Budget', 'Expected Spending', 'Actual Spending'] 139 | }, 140 | series: [ 141 | { 142 | type: 'radar', 143 | symbolSize: 0, 144 | areaStyle: { 145 | normal: { 146 | shadowBlur: 13, 147 | shadowColor: 'rgba(0,0,0,.2)', 148 | shadowOffsetX: 0, 149 | shadowOffsetY: 10, 150 | opacity: 1 151 | } 152 | }, 153 | data: [ 154 | { 155 | value: [ 156 | 5000, 157 | 7000, 158 | 12000, 159 | 11000, 160 | 15000, 161 | 14000 162 | ], 163 | name: 'Allocated Budget' 164 | }, { 165 | value: [ 166 | 4000, 167 | 9000, 168 | 15000, 169 | 15000, 170 | 13000, 171 | 11000 172 | ], 173 | name: 'Expected Spending' 174 | }, { 175 | value: [ 176 | 5500, 177 | 11000, 178 | 12000, 179 | 15000, 180 | 12000, 181 | 12000 182 | ], 183 | name: 'Actual Spending' 184 | } 185 | ], 186 | animationDuration: 3000 187 | } 188 | ] 189 | } 190 | 191 | const pieChartData = { 192 | tooltip: { 193 | trigger: 'item', 194 | formatter: '{a}
    {b} : {c} ({d}%)' 195 | }, 196 | legend: { 197 | left: 'center', 198 | bottom: '0', 199 | data: ['Industries', 'Technology ', 'Forex', 'Gold', 'Forecasts'] 200 | }, 201 | calculable: true, 202 | series: [ 203 | { 204 | name: 'WEEKLY WRITE ARTICLES', 205 | type: 'pie', 206 | roseType: 'radius', 207 | radius: [ 208 | 15, 95 209 | ], 210 | center: [ 211 | '50%', '38%' 212 | ], 213 | data: [ 214 | { 215 | value: 320, 216 | name: 'Industries' 217 | }, { 218 | value: 240, 219 | name: 'Technology' 220 | }, { 221 | value: 149, 222 | name: 'Forex' 223 | }, { 224 | value: 100, 225 | name: 'Gold' 226 | }, { 227 | value: 59, 228 | name: 'Forecasts' 229 | } 230 | ], 231 | animationEasing: 'cubicInOut', 232 | animationDuration: 2600 233 | } 234 | ] 235 | } 236 | 237 | const lineChartData = { 238 | NewVisits: { 239 | expectedData: [ 240 | 100, 241 | 120, 242 | 161, 243 | 134, 244 | 105, 245 | 160, 246 | 165 247 | ], 248 | actualData: [ 249 | 120, 250 | 82, 251 | 91, 252 | 154, 253 | 162, 254 | 140, 255 | 145 256 | ] 257 | }, 258 | Messages: { 259 | expectedData: [ 260 | 200, 261 | 192, 262 | 120, 263 | 144, 264 | 160, 265 | 130, 266 | 140 267 | ], 268 | actualData: [ 269 | 180, 270 | 160, 271 | 151, 272 | 106, 273 | 145, 274 | 150, 275 | 130 276 | ] 277 | }, 278 | Purchases: { 279 | expectedData: [ 280 | 80, 281 | 100, 282 | 121, 283 | 104, 284 | 105, 285 | 90, 286 | 100 287 | ], 288 | actualData: [ 289 | 120, 290 | 90, 291 | 100, 292 | 138, 293 | 142, 294 | 130, 295 | 130 296 | ] 297 | }, 298 | Shoppings: { 299 | expectedData: [ 300 | 130, 301 | 140, 302 | 141, 303 | 142, 304 | 145, 305 | 150, 306 | 160 307 | ], 308 | actualData: [ 309 | 120, 310 | 82, 311 | 91, 312 | 154, 313 | 162, 314 | 140, 315 | 130 316 | ] 317 | } 318 | } 319 | 320 | 321 | 322 | export {barChartData,raddarChartData,pieChartData,lineChartData} -------------------------------------------------------------------------------- /src/views/dashboard/component/AntdCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Card, Avatar} from 'antd' 3 | const {Meta} = Card; 4 | 5 | const AntdCard = () => { 6 | 7 | return ( 8 | } 14 | > 15 | 16 | } 18 | description='尽管在野外有孟加拉白虎出没的记载,但它们非常罕见。这可能是因为孟加拉白虎的毛皮里缺少色素,这种天生的缺陷降低了它们在自然界的存活率。如今,人们看到的孟加拉白虎都是动物园的园宝。孟加拉白虎在许多文化中具有很强的神话色彩,包括在韩国,它是力量和信任的象征。因此韩国奥林匹克委员会选择了孟加拉白虎作为2018年冬季奥运会的吉祥物之一。' 19 | title='孟加拉白虎' 20 | /> 21 | 22 | ) 23 | } 24 | export default AntdCard -------------------------------------------------------------------------------- /src/views/dashboard/component/LineChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { PropTypes } from 'prop-types' 3 | import {connect} from 'react-redux' 4 | import echarts from 'echarts' 5 | import { debounce } from '@/utils' 6 | import CSSModules from 'react-css-modules' 7 | import styles from '../index.module.less' 8 | require('echarts/theme/macarons') // echarts theme 9 | 10 | class LineChart extends Component { 11 | static propTypes = { 12 | width: PropTypes.string.isRequired, 13 | height: PropTypes.string.isRequired, 14 | className: PropTypes.string.isRequired, 15 | chartData: PropTypes.object.isRequired, 16 | styles: PropTypes.object.isRequired 17 | } 18 | static defaultProps = { 19 | width: '100%', 20 | height: '340px', 21 | styles: {}, 22 | chartData: {}, 23 | className: 'shadow-radius' 24 | } 25 | state = { 26 | chart: null 27 | } 28 | 29 | componentDidMount () { 30 | debounce(this.initChart.bind(this), 300)() 31 | window.addEventListener('resize', () => this.resize()) 32 | } 33 | componentWillReceiveProps(nextProps){ 34 | if (nextProps.collapsed !== this.props.collapsed){ 35 | this.resize() 36 | } 37 | if (nextProps.chartData !== this.props.chartData){ 38 | debounce(this.initChart.bind(this), 300)() 39 | } 40 | } 41 | 42 | componentWillUnmount() { 43 | this.dispose() 44 | } 45 | 46 | resize() { 47 | const chart = this.state.chart 48 | if (chart) { 49 | debounce(chart.resize.bind(this), 300)() 50 | } 51 | } 52 | 53 | dispose (){ 54 | if (!this.state.chart) { 55 | return 56 | } 57 | window.removeEventListener('resize', () => this.resize()) // 移除窗口,变化时重置图表 58 | this.setState({chart: null}) 59 | } 60 | 61 | 62 | setOptions({ expectedData, actualData } = {}) { 63 | this.state.chart.setOption({ 64 | backgroundColor: '#fff', 65 | xAxis: { 66 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 67 | boundaryGap: false, 68 | axisTick: { 69 | show: false 70 | } 71 | }, 72 | grid: { 73 | left: 10, 74 | right: 10, 75 | bottom: 10, 76 | top: 30, 77 | containLabel: true 78 | }, 79 | tooltip: { 80 | trigger: 'axis', 81 | axisPointer: { 82 | type: 'cross' 83 | }, 84 | padding: [5, 10] 85 | }, 86 | yAxis: { 87 | axisTick: { 88 | show: false 89 | } 90 | }, 91 | legend: { 92 | data: ['expected', 'actual'] 93 | }, 94 | series: [{ 95 | name: 'expected', itemStyle: { 96 | normal: { 97 | color: '#FF005A', 98 | lineStyle: { 99 | color: '#FF005A', 100 | width: 2 101 | } 102 | } 103 | }, 104 | smooth: true, 105 | type: 'line', 106 | data: expectedData, 107 | animationDuration: 2800, 108 | animationEasing: 'cubicInOut' 109 | }, 110 | { 111 | name: 'actual', 112 | smooth: true, 113 | type: 'line', 114 | itemStyle: { 115 | normal: { 116 | color: '#3888fa', 117 | lineStyle: { 118 | color: '#3888fa', 119 | width: 2 120 | }, 121 | areaStyle: { 122 | color: '#f3f8ff' 123 | } 124 | } 125 | }, 126 | data: actualData, 127 | animationDuration: 2800, 128 | animationEasing: 'quadraticOut' 129 | }] 130 | }) 131 | } 132 | 133 | initChart () { 134 | if(!this.el) return 135 | this.setState({ chart: echarts.init(this.el)},()=>{ 136 | this.setOptions(this.props.chartData) 137 | }) 138 | } 139 | 140 | render() { 141 | 142 | const { className, height, width, styles} = this.props 143 | return ( 144 |
    (this.el = el)} 147 | style={{ 148 | ...styles, 149 | height, 150 | width 151 | }} 152 | /> 153 | ) 154 | } 155 | } 156 | 157 | const mapStateToProps = state=>({ 158 | collapsed: state.UI.collapsed 159 | }) 160 | export default connect(mapStateToProps)(CSSModules(LineChart, styles)) -------------------------------------------------------------------------------- /src/views/dashboard/component/PanelGroup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Row, Col, Icon} from 'antd' 3 | import CountUp from 'react-countup' 4 | import CSSModules from 'react-css-modules' 5 | import styles from '../index.module.less' 6 | const PanelGroup = props => { 7 | const chartList = [ 8 | { 9 | type: 'NewVisits', 10 | className: 'people', 11 | icon: 'user-add', 12 | num: 13600, 13 | color: '#BFA3A9' 14 | }, { 15 | type: 'Messages', 16 | className: 'message', 17 | icon: 'message', 18 | num: 12400, 19 | color: '#5DA0F0' 20 | }, { 21 | type: 'Purchases', 22 | className: 'money', 23 | icon: 'pay-circle', 24 | num: 498200, 25 | color: '#BF6883' 26 | }, { 27 | type: 'Shoppings', 28 | className: 'shoppingCard', 29 | icon: 'shopping-cart', 30 | num: 29600, 31 | color: '#f6ab40' 32 | } 33 | ] 34 | 35 | return ( 36 |
    37 | 38 | {chartList.map((ele, i) => 44 |
    45 |
    48 | 52 |
    53 |
    54 |

    55 | {ele.type} 56 |

    57 | 62 |
    63 |
    64 | ) 65 | } 66 | 67 |
    68 |
    69 | ) 70 | } 71 | 72 | export default CSSModules(PanelGroup, styles) -------------------------------------------------------------------------------- /src/views/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Row, Col} from 'antd' 3 | import PanelGroup from './component/PanelGroup' 4 | import {raddarChartData,barChartData,pieChartData,lineChartData} from './chartData' 5 | import CommonChart from '@/components/chart' 6 | import LineChart from './component/LineChart' 7 | import AntdCard from './component/AntdCard' 8 | 9 | 10 | class Dashboard extends Component { 11 | 12 | state = { 13 | lineChartDataVal: lineChartData['NewVisits'] 14 | } 15 | 16 | handleSetLineChartData = type => { 17 | this.setState({lineChartDataVal: lineChartData[type]}) 18 | } 19 | 20 | render() { 21 | return ( 22 |
    23 | 24 | 25 | 33 | 34 | 38 | 39 | 45 | 46 | 47 | 51 | 57 | 58 | 62 | 68 | 69 | 70 | 71 | 72 |
    73 | ) 74 | } 75 | 76 | } 77 | export default Dashboard -------------------------------------------------------------------------------- /src/views/dashboard/index.module.less: -------------------------------------------------------------------------------- 1 | .panel-group-container { 2 | .content { 3 | background-color: #fff; 4 | text-align: center; 5 | color: #000; 6 | padding: 8px; 7 | height: 108px; 8 | box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1), 0 1px 4px rgba(0, 0, 0, 0.1); 9 | border-radius: 2px; 10 | display: flex; 11 | justify-content: space-around; 12 | align-items: center; 13 | cursor: pointer; 14 | margin-bottom:25px; 15 | &:hover { 16 | .icon-wrap { 17 | i { 18 | color: #fff !important; 19 | } 20 | } 21 | .icon-wrap.people { 22 | background-color: #bfa3a9; 23 | } 24 | .icon-wrap.money { 25 | background-color: #bf6883; 26 | } 27 | .icon-wrap.message { 28 | background-color: #5da0f0; 29 | } 30 | .icon-wrap.shoppingCard { 31 | background-color: #f6ab40; 32 | } 33 | } 34 | .icon-wrap { 35 | padding: 12px 14px; 36 | margin-left: -20px; 37 | transition: all 0.4s ease-out; 38 | border-radius: 4px; 39 | } 40 | .description { 41 | p.text { 42 | margin-bottom: 12px; 43 | line-height: 18px; 44 | font-size: 16px; 45 | color: rgba(0, 0, 0, 0.45); 46 | font-weight: bold; 47 | text-align: left; 48 | } 49 | span.num { 50 | display: block; 51 | font-weight: bold; 52 | text-align: left; 53 | font-size: 20px; 54 | color: #666; 55 | } 56 | } 57 | } 58 | } 59 | 60 | .bgc-radius-shadow { 61 | padding: 8px; 62 | background-color: #fff!important; 63 | } -------------------------------------------------------------------------------- /src/views/error/401/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button ,Row,Col} from 'antd' 3 | import CSSModules from 'react-css-modules' 4 | import styles from '../index.module.less' 5 | 6 | const Error401 = props=>{ 7 | 8 | const goback = ()=>{ 9 | const {history} = props 10 | history.push('/dashboard') 11 | } 12 | return ( 13 | 17 | 23 | 24 | 30 |

    401

    31 |

    抱歉,你无权访问该页面

    32 |
    33 | 37 |
    38 | 39 | 40 |
    41 | ) 42 | } 43 | 44 | 45 | 46 | export default CSSModules(Error401,styles) -------------------------------------------------------------------------------- /src/views/error/404/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button ,Row,Col} from 'antd' 3 | import CSSModules from 'react-css-modules' 4 | import styles from '../index.module.less' 5 | 6 | const Error404 = props=>{ 7 | 8 | const goback = ()=>{ 9 | const {history} = props 10 | history.push('/dashboard') 11 | } 12 | return ( 13 | 17 | 23 | 24 | 30 |

    404

    31 |

    抱歉,你访问的页面不存在

    32 |
    33 | 37 |
    38 | 39 | 40 |
    41 | ) 42 | } 43 | 44 | 45 | 46 | export default CSSModules(Error404,styles) -------------------------------------------------------------------------------- /src/views/error/index.module.less: -------------------------------------------------------------------------------- 1 | .wrap-401, 2 | .wrap-404 { 3 | min-height: 500px; 4 | padding-top: 40px; 5 | .img-box { 6 | height: 360px; 7 | max-width: 430px; 8 | background-repeat: no-repeat; 9 | background-position: 50% 50%; 10 | background-size: contain; 11 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg'); 12 | } 13 | .content { 14 | padding-top: 40px; 15 | h1 { 16 | color: #434e59; 17 | font-size: 72px; 18 | font-weight: 600; 19 | line-height: 72px; 20 | margin-bottom: 24px; 21 | } 22 | .desc { 23 | color: rgba(0, 0, 0, .45); 24 | font-size: 20px; 25 | line-height: 28px; 26 | margin-bottom: 16px; 27 | } 28 | } 29 | } 30 | 31 | .wrap-404 { 32 | .img-box { 33 | height: 360px; 34 | max-width: 430px; 35 | background-repeat: no-repeat; 36 | background-position: 50% 50%; 37 | background-size: contain; 38 | background-image: url("https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg") 39 | } 40 | } -------------------------------------------------------------------------------- /src/views/form/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import {Form, Select, InputNumber, Switch, Radio,Slider, Button, Upload, Icon, Rate} from 'antd' 3 | const FormItem = Form.Item 4 | const Option = Select.Option 5 | const RadioButton = Radio.Button 6 | const RadioGroup = Radio.Group 7 | 8 | class FormExample extends Component { 9 | handleSubmit = (e) => { 10 | e.preventDefault() 11 | this.props.form.validateFields((err, values) => { 12 | if (!err) { 13 | console.log('Received values of form: ', values) 14 | } 15 | }) 16 | } 17 | normFile = (e) => { 18 | if (Array.isArray(e)) { 19 | return e 20 | } 21 | return e && e.fileList 22 | } 23 | render() { 24 | const { getFieldDecorator } = this.props.form 25 | const formItemLayout = { 26 | labelCol: { span: 6 }, 27 | wrapperCol: { span: 14 }, 28 | } 29 | return ( 30 |
    31 |
    32 | 36 | China 37 | 38 | 43 | {getFieldDecorator('select', { 44 | rules: [ 45 | { required: true, message: 'Please select your country!' }, 46 | ], 47 | })( 48 | 52 | )} 53 | 54 | 55 | 59 | {getFieldDecorator('select-multiple', { 60 | rules: [ 61 | { required: true, message: 'Please select your favourite colors!', type: 'array' }, 62 | ], 63 | })( 64 | 71 | )} 72 | 73 | 74 | 78 | {getFieldDecorator('input-number', { initialValue: 3 })( 79 | 82 | )} 83 | machines 84 | 85 | 86 | 90 | {getFieldDecorator('switch', { valuePropName: 'checked' })( 91 | 92 | )} 93 | 94 | 95 | 99 | {getFieldDecorator('slider')( 100 | 101 | )} 102 | 103 | 104 | 108 | {getFieldDecorator('radio-group')( 109 | 110 | item 1 111 | item 2 112 | item 3 113 | 114 | )} 115 | 116 | 117 | 121 | {getFieldDecorator('radio-button')( 122 | 123 | item 1 124 | item 2 125 | item 3 126 | 127 | )} 128 | 129 | 130 | 134 | {getFieldDecorator('rate', { 135 | initialValue: 3.5, 136 | })( 137 | 138 | )} 139 | 140 | 141 | 146 | {getFieldDecorator('upload', { 147 | valuePropName: 'fileList', 148 | getValueFromEvent: this.normFile, 149 | })( 150 | 154 | 157 | 158 | )} 159 | 160 | 161 | 165 |
    166 | {getFieldDecorator('dragger', { 167 | valuePropName: 'fileList', 168 | getValueFromEvent: this.normFile, 169 | })( 170 | 173 |

    174 | 175 |

    176 |

    Click or drag file to this area to upload

    177 |

    Support for a single or bulk upload.

    178 |
    179 | )} 180 |
    181 |
    182 | 183 | 186 | 189 | 190 |
    191 |
    192 | ) 193 | } 194 | } 195 | 196 | export default Form.create()(FormExample) 197 | -------------------------------------------------------------------------------- /src/views/layout/Content.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout } from 'antd' 3 | import { Redirect, withRouter, Route, Switch } from 'react-router-dom' 4 | import { TransitionGroup, CSSTransition } from 'react-transition-group' 5 | import { routes } from '@/router' 6 | import { connect } from 'react-redux' 7 | 8 | const { Content } = Layout 9 | 10 | 11 | 12 | const Main = ({ location }) => { 13 | 14 | const roles = localStorage.getItem('userInfo') && JSON.parse(localStorage.getItem('userInfo')).roles 15 | const handleFilter = permission =>{ 16 | // 过滤没有权限的页面 17 | if(!permission ||permission===roles ) return true 18 | return false 19 | 20 | } 21 | 22 | return ( 23 | 24 | 29 | 30 | 31 | { 32 | routes.map(ele => { 33 | return handleFilter(ele.permission) && 38 | } 39 | 40 | ) 41 | } 42 | 46 | 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | 54 | const mapStateToProps = state => ({userInfo: state.user.userInfo}) 55 | export default withRouter(connect(mapStateToProps)(Main)) 56 | -------------------------------------------------------------------------------- /src/views/layout/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Layout, Icon, Popover,Avatar,Dropdown,Menu,Badge} from 'antd' 3 | import {connect} from 'react-redux' 4 | import {changeCollapsed} from '@/redux/actions' 5 | import {deleteToken,getUserInfo} from '@/redux/actions' 6 | 7 | import MenuList from './sider/Menu' 8 | import './index.less' 9 | const {Header} = Layout 10 | 11 | const LayoutHeader = props => { 12 | 13 | const {collapsed, changeCollapsed, isMobile,deleteToken,getUserInfo} = props 14 | // 获取用户信息,如果state树没有数据,则读取缓存 15 | 16 | const historyUserInfo = JSON.parse(localStorage.getItem('userInfo')) 17 | 18 | let userInfo = props.userInfo ?props.userInfo:historyUserInfo 19 | 20 | if(!userInfo.roles){ 21 | getUserInfo() 22 | } 23 | 24 | const handleLogout =() => deleteToken() 25 | 26 | 27 | const DropdownList = ( 28 | 29 | 30 | 31 | {userInfo.name} 32 | 33 | 37 | 38 | 个人设置 39 | 40 | 44 | 45 | 系统设置 46 | 47 | 51 | 52 | 退出登录 53 | 54 | 55 | ) 56 | 57 | 58 | return ( 59 |
    60 |
    61 | {isMobile 62 | ? } 65 | placement='bottomLeft' 66 | trigger='click' 67 | > 68 | 69 | 74 | 75 | 76 | : 79 | 84 | 85 | } 86 |
    87 | 88 |
    89 |
    90 | 91 | 95 | 96 |
    97 | 115 |
    116 | 117 |
    118 | ) 119 | } 120 | 121 | 122 | const mapStateToProps = state => ( 123 | { 124 | collapsed: state.UI.collapsed, 125 | isMobile: state.UI.isMobile, 126 | userInfo:state.user.userInfo 127 | } 128 | ) 129 | 130 | const mapDispatchToProps = ({ 131 | changeCollapsed: playload => { 132 | changeCollapsed(playload) 133 | }, 134 | deleteToken:()=>deleteToken(), 135 | getUserInfo:()=> getUserInfo() 136 | }) 137 | export default connect(mapStateToProps, mapDispatchToProps)(LayoutHeader) 138 | -------------------------------------------------------------------------------- /src/views/layout/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react' 3 | import { connect } from 'react-redux' 4 | import { Layout } from 'antd' 5 | import Content from './Content' 6 | import Header from './Header' 7 | import Sider from './sider' 8 | import Tabs from './tags' 9 | import { debounce } from '@/utils' 10 | import { changeIsMobile, changeCollapsed } from '@/redux/actions' 11 | 12 | 13 | class LayoutComponent extends Component { 14 | 15 | componentWillMount () { 16 | this.getClientWidth() 17 | // 节流函数 18 | window.onresize = debounce(this.getClientWidth, 100) 19 | } 20 | 21 | getClientWidth = () => { 22 | // 通过context上下文拿到store的dispatch事件,发起action修改store状态树 23 | const { changeCollapsed,changeIsMobile } = this.props, clientWidth = document.body.clientWidth; 24 | changeIsMobile(clientWidth <= 992) 25 | changeCollapsed(clientWidth <= 992) 26 | console.log(`当前屏幕宽度:${clientWidth}`) 27 | } 28 | render () { 29 | const {collapsed,isMobile} = this.props 30 | let marginLeft = collapsed?80:250 31 | isMobile && (marginLeft=0) 32 | return ( 33 | 34 | 35 | 36 |
    37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | } 44 | 45 | const mapStateToProps = state => ( 46 | { 47 | collapsed: state.UI.collapsed, 48 | isMobile: state.UI.isMobile 49 | } 50 | ) 51 | 52 | const mapDispatchToProps = dispatch => ({ 53 | changeCollapsed: playload => { 54 | dispatch(changeCollapsed(playload)) 55 | }, 56 | changeIsMobile:playload => { 57 | dispatch(changeIsMobile(playload)) 58 | }, 59 | }) 60 | export default connect(mapStateToProps, mapDispatchToProps)(LayoutComponent) 61 | -------------------------------------------------------------------------------- /src/views/layout/index.less: -------------------------------------------------------------------------------- 1 | .ant-layout-sider { 2 | box-shadow: 0 1px 6px rgba(0, 0, 0, .117647), 0 1px 4px rgba(0, 0, 0, .117647); 3 | .logo { 4 | position: relative; 5 | height: 64px; 6 | padding-left: 24px; 7 | cursor: pointer; 8 | &>i.anticon-github { 9 | font-size: 30px; 10 | margin-right: 15px; 11 | line-height: 66px; 12 | } 13 | &>span { 14 | position: absolute; 15 | top: 50%; 16 | transform: translateY(-50%); 17 | font-size: 24px; 18 | display: inline-block; 19 | } 20 | span.hide { 21 | display: none; 22 | } 23 | } 24 | } 25 | 26 | .trigger-wrap { 27 | display: inline-block; 28 | font-size: 20px; 29 | line-height: 64px; 30 | padding: 0 24px; 31 | cursor: pointer; 32 | transition: color .3s; 33 | &:hover { 34 | background-color: #e6f7ff; 35 | color: #1890ff; 36 | } 37 | } 38 | 39 | .ant-layout-header { 40 | background-color: #fff; 41 | padding: 0; 42 | display: flex; 43 | justify-content: space-between; 44 | &>.header-right { 45 | padding-right: 15px; 46 | display: flex; 47 | .news-wrap { 48 | margin:2px 40px 0 0; 49 | cursor: pointer; 50 | } 51 | 52 | } 53 | } 54 | 55 | .dropdown-wrap { 56 | cursor: pointer; 57 | .ant-dropdown-menu-item { 58 | padding: 6px 20px 8px 15px; 59 | i { 60 | padding-right: 15px; 61 | } 62 | } 63 | .anticon-caret-down { 64 | vertical-align: bottom; 65 | padding:0 0 12px 6px; 66 | } 67 | } 68 | 69 | 70 | .tags-row-wrap { 71 | display: flex; 72 | overflow-x: auto; 73 | overflow-y: hidden; 74 | width: 100%; 75 | justify-content: space-between; 76 | height: 36px; 77 | line-height: 35px; 78 | border-bottom: 1px solid #d8dce5; 79 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); 80 | background-color: #fff; 81 | padding: 0 16px; 82 | .tags-wrap { 83 | padding: 0; 84 | &>li { 85 | display: inline-block; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/views/layout/sider/Menu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Menu, Icon } from 'antd' 3 | import { menus } from '@/router/menus' 4 | import { Link } from 'react-router-dom' 5 | import {connect} from 'react-redux' 6 | import {withRouter} from 'react-router-dom' 7 | import {addTaglist} from '@/redux/actions' 8 | const SubMenu = Menu.SubMenu 9 | 10 | 11 | const MenuComponent = props => { 12 | const { history } = props 13 | // 当前选中的菜单栏,当前权限角色 14 | const menuSelected = history.location.pathname, roles= localStorage.getItem('userInfo') && JSON.parse(localStorage.getItem('userInfo')).roles 15 | // 当前展开的菜单栏 16 | const menuOpened = `/${menuSelected.split('/')[1]}` 17 | 18 | const handleFilter = permission =>{ 19 | // 过滤没有权限的页面 20 | if(!permission ||permission===roles ) return true 21 | return false 22 | } 23 | // 每次点击菜单栏时,判断tagslist数组有没存在当前path,如果没有,添加进去 24 | // tagslist最多存在10个,>=10时,删除第一个tag 25 | const handleMenuSelect =({key})=>{ 26 | props.addTaglist(key) 27 | } 28 | 29 | return ( 30 | 40 | { 41 | menus.map(ele => { 42 | if(ele.children){ 43 | return ( 44 | handleFilter(ele.permission) && {ele.title}} 46 | > 47 | {ele.children.map(subItem => 48 | handleFilter(subItem.permission) && 49 | 50 | {subItem.title} 51 | 52 | 53 | )} 54 | 55 | ) 56 | }else { 57 | return ( 58 | handleFilter(ele.permission) && 59 | 60 | 61 | 62 | {ele.title} 63 | 64 | 65 | 66 | ) 67 | } 68 | 69 | }) 70 | } 71 | 72 | ) 73 | } 74 | 75 | const mapStateToProps = state => ({userInfo: state.user.userInfo,taglist: state.UI.taglist}) 76 | const mapDispatchToProps = dispatch => ({ 77 | addTaglist: playload => { 78 | dispatch(addTaglist(playload)) 79 | } 80 | }) 81 | export default withRouter(connect(mapStateToProps,mapDispatchToProps)(MenuComponent)) 82 | -------------------------------------------------------------------------------- /src/views/layout/sider/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Layout,Icon } from 'antd' 4 | import Menu from './Menu' 5 | const { Sider } = Layout 6 | 7 | class SiderComponent extends Component { 8 | render () { 9 | const { collapsed, isMobile } = this.props 10 | 11 | return ( 12 | 19 |
    20 | { 23 | collapsed || Github 24 | } 25 |
    26 | 29 | 30 | ) 31 | } 32 | } 33 | 34 | const mapStateToProps = state => ({collapsed: state.UI.collapsed, isMobile: state.UI.isMobile}) 35 | export default connect(mapStateToProps)(SiderComponent) 36 | -------------------------------------------------------------------------------- /src/views/layout/tags/component/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import {emptyTaglist} from '@/redux/actions' 3 | import {connect} from 'react-redux' 4 | import { Menu, Dropdown, Icon ,Popconfirm,message} from 'antd' 5 | 6 | 7 | 8 | class Dropdowns extends Component { 9 | 10 | handleCloseAll(){ 11 | this.props.emptyTaglist() 12 | } 13 | catchTags(){ 14 | localStorage.setItem('taglist',JSON.stringify(this.props.taglist)) 15 | message.success('缓存成功,下次进入系统时,将自动读取当前标签页') 16 | } 17 | removeHistory(){ 18 | localStorage.removeItem('taglist') 19 | message.success('已清空历史缓存') 20 | } 21 | 22 | render(){ 23 | const menu = ( 24 | 25 | 26 | 关闭所有标签 27 | 28 | 29 | 缓存当前标签 30 | 31 | 32 | 37 | 清空历史缓存 38 | 39 | 40 | 41 | ) 42 | return ( 43 |
    44 | 47 | 48 | 49 |
    50 | ) 51 | } 52 | } 53 | const mapStateToProps = state => ({taglist: state.UI.taglist}) 54 | const mapDispatchToProps = dispatch => ({ 55 | emptyTaglist: () => { 56 | dispatch(emptyTaglist) 57 | } 58 | }) 59 | export default connect(mapStateToProps,mapDispatchToProps)(Dropdowns) 60 | -------------------------------------------------------------------------------- /src/views/layout/tags/component/Tag.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import TagList from '@/components/taglist' 3 | import {addTaglist} from '@/redux/actions' 4 | import {connect} from 'react-redux' 5 | import {withRouter} from 'react-router-dom' 6 | 7 | class Tags extends Component { 8 | 9 | componentWillMount(){ 10 | const { addTaglist } = this.props, 11 | currentPath = this.props.history.location.pathname //当前页面路径 12 | // 当页面初始化时,添加tag 13 | const catchTaglist = JSON.parse(localStorage.getItem('taglist')) 14 | 15 | // 先给taglist添加当前页面对应的tag 16 | addTaglist(currentPath) 17 | // 如果缓存中有taglist列表 读取改列表并添加 18 | if(catchTaglist && catchTaglist.length) { 19 | catchTaglist.forEach(ele=>{addTaglist(ele.path)}) 20 | } 21 | } 22 | 23 | render(){ 24 | const {taglist} = this.props, 25 | currentPath = this.props.history.location.pathname 26 | return ( 27 |
      28 | { 29 | taglist.map(ele=>( 30 |
    • 31 | 36 |
    • 37 | )) 38 | } 39 |
    40 | ) 41 | } 42 | } 43 | const mapStateToProps = state => ({taglist: state.UI.taglist}) 44 | const mapDispatchToProps = dispatch => ({ 45 | addTaglist: playload => { 46 | dispatch(addTaglist(playload)) 47 | } 48 | }) 49 | export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Tags)) 50 | -------------------------------------------------------------------------------- /src/views/layout/tags/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {withRouter} from 'react-router-dom' 3 | import Tag from './component/Tag' 4 | import Dropdown from './component/Dropdown' 5 | 6 | const TagsRow = ()=>{ 7 | return ( 8 |
    9 | 10 | 11 |
    12 | ) 13 | } 14 | 15 | export default withRouter(TagsRow) 16 | -------------------------------------------------------------------------------- /src/views/login/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import './index.less' 3 | import {connect} from 'react-redux' 4 | import {Button, Input, Icon, Form, Checkbox,Spin } from 'antd' 5 | import {login, getUserInfo} from '@/redux/actions' 6 | import PropTypes from 'prop-types' 7 | import Particles from 'react-particles-js' 8 | import {particles} from './params' 9 | const FormItem = Form.Item 10 | 11 | class Login extends Component { 12 | 13 | static contextTypes = { 14 | router: PropTypes.object 15 | } 16 | 17 | state = { 18 | loading: false 19 | } 20 | 21 | componentDidMount(){ 22 | 23 | const historyUser = localStorage.getItem('user') 24 | if(historyUser){ 25 | const {user,password} = JSON.parse(historyUser) 26 | this.props.form.setFieldsValue({user,password,remember:true}) 27 | } 28 | } 29 | 30 | handleSubmit = e => { 31 | e.preventDefault() 32 | this.props.form.validateFields((err, values) => { 33 | if (!err) { 34 | const {user,password,remember} = values 35 | remember?localStorage.setItem('user',JSON.stringify({user,password})):localStorage.removeItem('user') 36 | this.login({user,password}) 37 | } 38 | }) 39 | } 40 | 41 | setFromData = user => { 42 | this.props.form.setFieldsValue({user,password:'123456'}) 43 | } 44 | 45 | login = formVal=> { 46 | const {handleLogin} = this.props 47 | // 登录完成后 发送请求 调用接口获取用户信息 48 | this.setState({loading:true}) 49 | handleLogin(formVal).then(status => { 50 | this.setState({loading:false}) 51 | status && this.getUserInfo() 52 | }) 53 | } 54 | 55 | getUserInfo = ()=> { 56 | this.setState({loading:true}) 57 | const { getUserInfo} = this.props,{history} = this.context.router 58 | // 发送请求 调用接口获取用户信息 59 | getUserInfo().then(status =>{ 60 | this.setState({loading:false}) 61 | status && history.replace('/dashboard') 62 | }) 63 | } 64 | 65 | render() { 66 | const { getFieldDecorator } = this.props.form 67 | return ( 68 | 69 |
    70 | 76 |
    80 | 84 | 85 | {getFieldDecorator('user', { 86 | rules: [{ required: true, message: '请输入账号!' }], 87 | })( 88 | } 95 | 96 | /> 97 | )} 98 | 99 | 100 | {getFieldDecorator('password', { 101 | rules: [{ required: true, message: '请输入密码!' }] 102 | })( 103 | } 110 | type="password" 111 | /> 112 | )} 113 | 114 | 115 | {getFieldDecorator('remember', { 116 | valuePropName: 'checked' 117 | })( 118 | 记住密码 119 | )} 120 | 忘记密码 121 | 128 | 129 | 130 |
    131 | 137 | 143 |
    144 |
    145 | 146 |
    147 | 148 |
    149 |
    150 | 151 | 152 | ) 153 | } 154 | } 155 | const mapDispatchToProps = ({ 156 | handleLogin: params => login(params), 157 | getUserInfo: () => getUserInfo() 158 | }) 159 | 160 | export default connect(state => ({collapsed: state.UI.taglist}), mapDispatchToProps)(Form.create()(Login)) 161 | -------------------------------------------------------------------------------- /src/views/login/index.less: -------------------------------------------------------------------------------- 1 | .login-container { 2 | background-color:#E9F0F5; 3 | height: 100%; 4 | overflow: hidden; 5 | position: relative; 6 | &>.title { 7 | color: #eee; 8 | font-size: 26px; 9 | font-weight: 400; 10 | margin: 0px auto 30px auto; 11 | text-align: center; 12 | font-weight: bold; 13 | letter-spacing: 1px; 14 | } 15 | .spin-wrap { 16 | text-align: center; 17 | background-color: rgba(255,255,255,.3); 18 | border-radius: 4px; 19 | position: absolute; 20 | left: 0; 21 | top: 0; 22 | width: 100vw; 23 | height: 100vh; 24 | line-height: 90vh; 25 | z-index: 999; 26 | } 27 | .content { 28 | position: absolute; 29 | background-color: #fff; 30 | left: 50%; 31 | top: 50%; 32 | width: 320px; 33 | padding: 30px 30px 0 30px; 34 | transform: translate(-50%,-60%); 35 | box-shadow: 0 0 10px 2px rgba(40, 138, 204, 0.16); 36 | border-radius: 3px; 37 | 38 | .two-button-wrap { 39 | display: flex; 40 | justify-content: space-between; 41 | } 42 | 43 | .row-container { 44 | display: flex; 45 | justify-content: space-between; 46 | } 47 | 48 | .user-type-button { 49 | width: 45%; 50 | } 51 | 52 | .login-form-button { 53 | width: 100%; 54 | } 55 | .login-form-forgot { 56 | float: right; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/views/login/params.js: -------------------------------------------------------------------------------- 1 | export const particles= { 2 | number: { 3 | value: 60 4 | }, 5 | 'color': { 6 | 'value': '#f2895c' 7 | }, 8 | 'size': { 9 | 'value': 6, 10 | 'random': true, 11 | 'anim': { 12 | 'enable': false, 13 | 'speed': 80, 14 | 'size_min': .5, 15 | 'sync': true 16 | } 17 | }, 18 | 'move': { 19 | 'enable': true, 20 | 'speed': 3, 21 | 'direction': 'none', 22 | 'random': true, 23 | 'straight': false, 24 | 'out_mode': 'out', 25 | 'bounce': false, 26 | 'attract': { 27 | 'enable': true, 28 | 'rotateX': 600, 29 | 'rotateY': 1200 30 | } 31 | }, 32 | line_linked: { 33 | enable_auto: true, 34 | distance: 220, 35 | color: '#ffffff', 36 | opacity: 1, 37 | width: 1, 38 | condensed_mode: { 39 | enable: false, 40 | rotateX: 600, 41 | rotateY: 600 42 | } 43 | }, 44 | interactivity: { 45 | enable: true, 46 | mouse: { 47 | distance: 300 48 | }, 49 | detect_on: 'canvas', // "canvas" or "window" 50 | mode: 'grab', 51 | line_linked: { 52 | opacity: .7 53 | }, 54 | events: { 55 | onclick: { 56 | enable: true, 57 | mode: 'push', // "push" or "remove" 58 | nb: 4 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/views/map/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Tencent from '@/components/map/TencentMap' 3 | import { Row, Col, Card } from 'antd' 4 | 5 | export default () => ( 6 |
    7 | 8 | 9 |
    10 | 13 | 14 | 15 |
    16 | 17 |
    18 |
    19 | ) -------------------------------------------------------------------------------- /src/views/permission/intercept/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import { Radio , Row} from 'antd' 3 | import {getUserInfo} from '@/redux/actions' 4 | import {connect} from 'react-redux' 5 | 6 | const RadioGroup = Radio.Group 7 | const RadioButton = Radio.Button 8 | 9 | class Toggle extends Component { 10 | 11 | state = { 12 | value: null 13 | } 14 | 15 | 16 | onChange = e => { 17 | const value = e.target.value 18 | this.setState({value}) 19 | // 重置token并且重新获取用户信息 20 | this.props.setToken(value) 21 | localStorage.setItem('token', value) 22 | this.props.getUserInfo() 23 | } 24 | render(){ 25 | return ( 26 |
    30 | 31 | 35 | 超级管理员 36 | 普通用户 37 | 38 | 39 | 40 | 41 | 只有超级管理员可以看到当前页面,切换到普通用户时,无法访问当前页面和表单页面 42 | 43 | 44 |
    45 | 46 | ) 47 | } 48 | } 49 | const mapStateToProps = state => ({token: state.user.token}) 50 | const mapDispatchToProps = ({ 51 | setToken: params=> ({type:'SET_TOKEN',playload:params}), 52 | getUserInfo: () => getUserInfo() 53 | }) 54 | export default connect(mapStateToProps, mapDispatchToProps)(Toggle) 55 | -------------------------------------------------------------------------------- /src/views/permission/toggle/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import { Radio ,Input,Row,Col} from 'antd' 3 | import {getUserInfo} from '@/redux/actions' 4 | import {connect} from 'react-redux' 5 | 6 | const RadioGroup = Radio.Group 7 | const RadioButton = Radio.Button 8 | 9 | class Toggle extends Component { 10 | 11 | state = { 12 | value: null, 13 | password: '******' 14 | } 15 | 16 | 17 | componentWillReceiveProps(nextProps){ 18 | if(nextProps.token==2){ 19 | this.setState({password:123456}) 20 | }else { 21 | this.setState({password:'******'}) 22 | } 23 | } 24 | onChange = e => { 25 | const value = e.target.value 26 | this.setState({value}) 27 | // 重置token并且重新获取用户信息 28 | this.props.setToken(value) 29 | localStorage.setItem('token', value) 30 | this.props.getUserInfo() 31 | } 32 | render(){ 33 | return ( 34 |
    38 | 39 | 只有超级管理员可以看到密码 40 | 41 | 45 | 超级管理员 46 | 普通用户 47 | 48 | 49 | 50 | 51 | 52 | 56 | 超级管理员 57 | 58 | 62 | 63 | 67 | 72 | 73 | 74 | 75 | 76 | 80 | 普通用户 81 | 82 | 86 | 87 | 91 | 96 | 97 | 98 |
    99 | 100 | ) 101 | } 102 | } 103 | const mapStateToProps = state => ({token: state.user.token}) 104 | const mapDispatchToProps = ({ 105 | setToken:params=> ({type:'SET_TOKEN',playload:params}), 106 | getUserInfo: () => getUserInfo() 107 | }) 108 | export default connect(mapStateToProps, mapDispatchToProps)(Toggle) 109 | -------------------------------------------------------------------------------- /src/views/table/basic/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import { Table } from 'antd' 3 | import api from '@/axios' 4 | 5 | const columns = [{ 6 | title: 'Name', 7 | dataIndex: 'name', 8 | sorter: true, 9 | render: name => `${name.first} ${name.last}`, 10 | width: '20%', 11 | }, { 12 | title: 'Gender', 13 | dataIndex: 'gender', 14 | filters: [ 15 | { text: 'Male', value: 'male' }, 16 | { text: 'Female', value: 'female' }, 17 | ], 18 | width: '20%', 19 | }, { 20 | title: 'Email', 21 | dataIndex: 'email', 22 | }] 23 | 24 | class App extends Component { 25 | 26 | state = { 27 | data: [], 28 | pagination: {}, 29 | loading: false, 30 | } 31 | 32 | componentWillMount() { 33 | this.fetch() 34 | } 35 | 36 | componentWillUnmount(){ 37 | // componentWillMount进行异步操作时且在callback中进行了setState操作时,需要在组件卸载时清除state 38 | this.setState = ()=>{ 39 | return 40 | } 41 | } 42 | 43 | handleTableChange = (pagination, filters, sorter) => { 44 | const pager = { ...this.state.pagination } 45 | pager.current = pagination.current 46 | this.setState({ 47 | pagination: pager, 48 | }) 49 | this.fetch({ 50 | results: pagination.pageSize, 51 | page: pagination.current, 52 | sortField: sorter.field, 53 | sortOrder: sorter.order, 54 | ...filters, 55 | }) 56 | } 57 | fetch = () => { 58 | this.setState({ loading: true }) 59 | api({ 60 | url: 'https://randomuser.me/api?results=10', 61 | method: 'get', 62 | type: 'json', 63 | }).then(data => { 64 | 65 | const pagination = { ...this.state.pagination } 66 | // Read total count from server 67 | // pagination.total = data.totalCount 68 | pagination.total = 200 69 | this.setState({ 70 | loading: false, 71 | data: data.data.results, 72 | pagination, 73 | }) 74 | }) 75 | } 76 | 77 | render() { 78 | return ( 79 |
    80 | record.registered} 88 | /> 89 | 90 | ) 91 | } 92 | } 93 | 94 | export default App -------------------------------------------------------------------------------- /src/views/table/dynamic/component/FixedTable.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import { Table ,Checkbox } from 'antd' 3 | const CheckboxGroup = Checkbox.Group 4 | 5 | const data= [ 6 | { 7 | 'name': '范杰', 8 | 'email': 't.hee@zjok.bf', 9 | 'address': '山西省 晋城市 阳城县', 10 | 'date': '2006-08-30' 11 | }, 12 | { 13 | 'name': '段强', 14 | 'email': 's.turg@oxbxcgofx.biz', 15 | 'address': '江西省 景德镇市 昌江区', 16 | 'date': '2015-05-15' 17 | }, 18 | { 19 | 'name': '姜超', 20 | 'email': 't.uhlmwtv@hbbsngg.tr', 21 | 'address': '内蒙古自治区 锡林郭勒盟 正镶白旗', 22 | 'date': '1986-12-21' 23 | }, 24 | { 25 | 'name': '曾杰', 26 | 'email': 'r.biitopmqg@qubxsk.中国', 27 | 'address': '广西壮族自治区 贵港市 平南县', 28 | 'date': '1991-12-10' 29 | }, 30 | { 31 | 'name': '陆芳', 32 | 'email': 's.mjzxwuzb@sxbguagfpd.li', 33 | 'address': '广东省 江门市 蓬江区', 34 | 'date': '2002-12-08' 35 | } 36 | ] 37 | 38 | class FixedTable extends Component { 39 | state = { 40 | data, 41 | columns :[ 42 | { 43 | title: 'Name', 44 | dataIndex: 'name', 45 | width: '20%' 46 | }, { 47 | title: 'Date', 48 | dataIndex: 'date', 49 | width: '20%' 50 | }, { 51 | title: 'Email', 52 | dataIndex: 'email' 53 | },{ 54 | title:'Address', 55 | dataIndex: 'address' 56 | } 57 | ], 58 | columnsChecked:[], 59 | checked:['name','date','email','address'] 60 | } 61 | 62 | componentWillMount(){ 63 | this.changeColumnsChecked() 64 | } 65 | 66 | changeColumnsChecked(){ 67 | let newData= [] 68 | this.state.columns.forEach(ele=>{ 69 | if(this.state.checked.indexOf(ele.dataIndex)>-1){ 70 | newData.push(ele) 71 | } 72 | }) 73 | this.setState({columnsChecked:newData}) 74 | } 75 | 76 | onChange(checkedValues){ 77 | // 最少选中一个 78 | this.setState({checked:checkedValues},()=>{ 79 | this.changeColumnsChecked() 80 | }) 81 | 82 | } 83 | 84 | 85 | render() { 86 | const options = [ 87 | { label: 'name', value: 'name' }, 88 | { label: 'date', value: 'date' }, 89 | { label: 'email', value: 'email' }, 90 | { label: 'address', value: 'address' } 91 | ] 92 | return ( 93 |
    94 |
    95 |

    96 | 97 | 固定顺序列 :     98 | 99 | 104 |

    105 |
    106 |
    107 |
    row.date} 113 | style={{minHeight:360}} 114 | /> 115 | 116 | ) 117 | } 118 | } 119 | 120 | export default FixedTable -------------------------------------------------------------------------------- /src/views/table/dynamic/component/SortedTable.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import { Table ,Checkbox } from 'antd' 3 | const CheckboxGroup = Checkbox.Group 4 | 5 | const data= [ 6 | { 7 | 'name': '龙明', 8 | 'email': 'x.lpvc@fkyejfw.mt', 9 | 'address': '江苏省 南通市 港闸区', 10 | 'date': '1980-02-29' 11 | }, 12 | { 13 | 'name': '傅勇', 14 | 'email': 'c.kymaby@hhtahqoguz.aw', 15 | 'address': '天津 天津市 河东区', 16 | 'date': '1984-09-05' 17 | }, 18 | { 19 | 'name': '杜艳', 20 | 'email': 't.sdolw@abhbuict.ph', 21 | 'address': '江西省 赣州市 上犹县', 22 | 'date': '2005-01-20' 23 | }, 24 | { 25 | 'name': '姚艳', 26 | 'email': 'b.wzndiez@bgzi.cn', 27 | 'address': '江西省 赣州市 信丰县', 28 | 'date': '2013-03-10' 29 | }, 30 | { 31 | 'name': '杜丽', 32 | 'email': 'x.obqgkmwp@tsyq.中国', 33 | 'address': '香港特别行政区 九龙 深水埗区', 34 | 'date': '1970-03-04' 35 | } 36 | ] 37 | 38 | 39 | class SortedTable extends Component { 40 | state = { 41 | data, 42 | columns :[ 43 | { 44 | title: 'Name', 45 | dataIndex: 'name', 46 | width: '20%' 47 | }, { 48 | title: 'Date', 49 | dataIndex: 'date', 50 | width: '20%', 51 | }, { 52 | title: 'Email', 53 | dataIndex: 'email' 54 | },{ 55 | title:'Address', 56 | dataIndex: 'address' 57 | } 58 | ], 59 | columnsChecked:[], 60 | checked:['name','date','email','address'] 61 | } 62 | componentWillMount(){ 63 | this.changeColumnsChecked() 64 | } 65 | 66 | 67 | onChange(checkedValues){ 68 | // 最少选中一个 69 | this.setState({checked:checkedValues},()=>{ 70 | this.changeColumnsChecked() 71 | }) 72 | 73 | } 74 | 75 | changeColumnsChecked(){ 76 | let newData= [] 77 | this.state.checked.forEach(ele=>{ 78 | this.state.columns.forEach(ele2=>{ 79 | if(ele2.dataIndex===ele){ 80 | newData.push(ele2) 81 | } 82 | }) 83 | }) 84 | this.setState({columnsChecked:newData}) 85 | } 86 | 87 | render() { 88 | const options = [ 89 | { label: 'name', value: 'name' }, 90 | { label: 'date', value: 'date' }, 91 | { label: 'email', value: 'email' }, 92 | { label: 'address', value: 'address' } 93 | ] 94 | return ( 95 |
    99 |
    100 |

    101 | 102 | 自定义顺序列 :     103 | 104 | 109 |

    110 |
    111 |
    112 |
    row.date} 118 | /> 119 | 120 | ) 121 | } 122 | } 123 | 124 | export default SortedTable -------------------------------------------------------------------------------- /src/views/table/dynamic/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FixedTable from './component/FixedTable' 3 | import SortedTable from './component/SortedTable' 4 | const TableDynamic = ()=>{ 5 | return ( 6 |
    7 | 8 | 9 |
    10 | ) 11 | } 12 | 13 | export default TableDynamic -------------------------------------------------------------------------------- /src/views/table/edit/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import { Table ,Button,Input,message,Popconfirm} from 'antd' 3 | 4 | const data= [ 5 | { 6 | 'name': '范杰', 7 | 'email': 't.hee@zjok.bf', 8 | 'address': '山西省 晋城市 阳城县', 9 | 'date': '2006-08-30' 10 | }, 11 | { 12 | 'name': '段强', 13 | 'email': 's.turg@oxbxcgofx.biz', 14 | 'address': '江西省 景德镇市 昌江区', 15 | 'date': '2015-05-15' 16 | }, 17 | { 18 | 'name': '姜超', 19 | 'email': 't.uhlmwtv@hbbsngg.tr', 20 | 'address': '内蒙古自治区 锡林郭勒盟 正镶白旗', 21 | 'date': '1986-12-21' 22 | }, 23 | { 24 | 'name': '曾杰', 25 | 'email': 'r.biitopmqg@qubxsk.中国', 26 | 'address': '广西壮族自治区 贵港市 平南县', 27 | 'date': '1991-12-10' 28 | }, 29 | { 30 | 'name': '陆芳', 31 | 'email': 's.mjzxwuzb@sxbguagfpd.li', 32 | 'address': '广东省 江门市 蓬江区', 33 | 'date': '2002-12-08' 34 | }, 35 | { 36 | 'name': '龙明', 37 | 'email': 'x.lpvc@fkyejfw.mt', 38 | 'address': '江苏省 南通市 港闸区', 39 | 'date': '1980-02-29' 40 | }, 41 | { 42 | 'name': '傅勇', 43 | 'email': 'c.kymaby@hhtahqoguz.aw', 44 | 'address': '天津 天津市 河东区', 45 | 'date': '1984-09-05' 46 | }, 47 | { 48 | 'name': '杜艳', 49 | 'email': 't.sdolw@abhbuict.ph', 50 | 'address': '江西省 赣州市 上犹县', 51 | 'date': '2005-01-20' 52 | }, 53 | { 54 | 'name': '姚艳', 55 | 'email': 'b.wzndiez@bgzi.cn', 56 | 'address': '江西省 赣州市 信丰县', 57 | 'date': '2013-03-10' 58 | }, 59 | { 60 | 'name': '杜丽', 61 | 'email': 'x.obqgkmwp@tsyq.中国', 62 | 'address': '香港特别行政区 九龙 深水埗区', 63 | 'date': '1970-03-04' 64 | } 65 | ] 66 | class TableEdit extends Component { 67 | state = { 68 | data, 69 | editable:[], 70 | editValue:[], 71 | columns :[ 72 | { 73 | title: 'Name', 74 | dataIndex: 'name', 75 | render: (name,row,index) => { 76 | const editable = this.state.editable 77 | return ( 78 | editable[index]?( 79 | 80 | 88 | 92 | 93 | ):( 94 | `${name}` 95 | ) 96 | ) 97 | }, 98 | width: '22%' 99 | }, { 100 | title: 'Date', 101 | dataIndex: 'date', 102 | width: '22%', 103 | }, { 104 | title: 'Email', 105 | dataIndex: 'email' 106 | },{ 107 | title:'handle', 108 | dataIndex: 'control', 109 | width: '18%', 110 | render: (text,row,index) => { 111 | const editable = this.state.editable 112 | return ( 113 |
    114 | { 115 | editable[index]?( 116 | 120 | ):( 121 | 127 | ) 128 | } 129 | this.deleteRow(index)} 131 | title="确认删除?" 132 | > 133 | 138 | 139 | 140 |
    141 | ) 142 | } 143 | } 144 | ] 145 | } 146 | 147 | // 点击修改时,将当前行改成可编辑状态,并将当前Input的值修改为当前行name的值 148 | setEditable(index){ 149 | const newEditable= [...this.state.editable],newEditValue = [...this.state.editValue],name = this.state.data[index].name 150 | 151 | newEditValue.splice(index,1,name) 152 | newEditable.splice(index,1,true) 153 | 154 | this.setState({editable:newEditable,editValue:newEditValue}) 155 | } 156 | 157 | changInputVal(index,e){ 158 | const newData = [...this.state.editValue] 159 | newData.splice(index,1,e.target.value) 160 | this.setState({editValue:newData}) 161 | } 162 | 163 | // 修改后保存 164 | save(index) { 165 | const inputVal =this.state.editValue[index].trim() ,newData =[...this.state.data] 166 | 167 | // 当前行input框内容不为空时,保存 168 | if(inputVal){ 169 | const item = newData[index] 170 | newData.splice(index,1,{...item,name:inputVal}) 171 | this.setState({data:newData}) 172 | message.success('修改成功') 173 | // 修改完成后 取消可编辑状态 174 | this.cancel(index) 175 | } 176 | 177 | } 178 | 179 | deleteRow(index) { 180 | // 删除当前行的数据、编辑状态、编辑值 181 | const newData =[...this.state.data] 182 | newData.splice(index,1) 183 | 184 | const newEditable =[...this.state.editable] 185 | newEditable.splice(index,1) 186 | 187 | const newEditValue =[...this.state.editValue] 188 | newEditValue.splice(index,1) 189 | 190 | this.setState({data:newData,editable:newEditValue,editValue:newEditable}) 191 | 192 | message.success('删除成功') 193 | } 194 | // 取消修改,将编辑状态改为false 195 | cancel(index){ 196 | const newEditable = [...this.state.editable] 197 | newEditable.splice(index,1,false) 198 | this.setState({editable:newEditable}) 199 | } 200 | 201 | render() { 202 | 203 | return ( 204 |
    205 |
    row.date} 211 | /> 212 | 213 | ) 214 | } 215 | } 216 | 217 | export default TableEdit -------------------------------------------------------------------------------- /static/dashboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/static/dashboard.gif -------------------------------------------------------------------------------- /static/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/static/login.gif -------------------------------------------------------------------------------- /static/permission.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/static/permission.gif -------------------------------------------------------------------------------- /static/table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/static/table.gif -------------------------------------------------------------------------------- /static/tags.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-tree/react-antd-admin/e242e1a05388e4362d7a67ce24d4d6ecf84ee81b/static/tags.gif --------------------------------------------------------------------------------