├── .gitignore ├── .npmrc ├── README.md ├── craco.config.js ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── assets │ └── images │ │ ├── 404.png │ │ ├── bg1.jpg │ │ ├── bg2.jpg │ │ ├── bg3.jpg │ │ ├── bg4.jpg │ │ ├── default.png │ │ └── logo.svg ├── components │ ├── AsyncComponent │ │ └── index.js │ └── loading.js ├── index.js ├── mock │ ├── task.js │ └── user.js ├── routes │ ├── components.js │ └── index.js ├── store │ ├── index.js │ ├── reducers.js │ └── sagas.js ├── style │ ├── _variables.scss │ └── index.scss ├── utils │ ├── http.js │ └── index.js └── views │ ├── Articles │ ├── action.js │ ├── api.js │ ├── article.js │ ├── category.js │ ├── comment.js │ ├── constants.js │ ├── reducers.js │ └── sagas.js │ ├── Community │ ├── action.js │ ├── api.js │ ├── constants.js │ ├── message.js │ ├── reducers.js │ └── sagas.js │ ├── Home │ ├── constants.js │ ├── index.js │ └── style.module.scss │ ├── Layout │ ├── index.js │ └── index.module.scss │ ├── Login │ ├── forget.js │ ├── index.js │ ├── login2.js │ ├── style.module.scss │ └── style2.scss │ ├── NotFound │ ├── 404.js │ └── style.module.scss │ ├── Setting │ ├── basicInfo.js │ ├── constants.js │ ├── emailService.js │ ├── modifyPassword.js │ ├── style.module.scss │ └── websiteSetting.js │ ├── Test │ └── sagas.js │ └── User │ ├── index.js │ ├── role.js │ ├── updateRole.js │ └── updateUser.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /build 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | phantomjs_cdnurl=http://cnpmjs.org/downloads 2 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 3 | registry=https://registry.npm.taobao.org 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | // const CracoLessPlugin = require('craco-less') 2 | const path = require('path') 3 | 4 | const resolve = pathUrl => path.join(__dirname, pathUrl) 5 | 6 | module.exports = { 7 | webpack: { 8 | alias: { 9 | '@': resolve('src'), 10 | '@assets': resolve('src/assets') 11 | }, 12 | /** 13 | * 重写 webpack 任意配置 14 | * - configure 能够重写 webpack 相关的所有配置,但是,仍然推荐你优先阅读 craco 提供的快捷配置,把解决不了的配置放到 configure 里解决; 15 | * - 这里选择配置为函数,与直接定义 configure 对象方式互斥; 16 | */ 17 | configure: (webpackConfig, { 18 | env, paths 19 | }) => { 20 | // paths.appPath='public' 21 | paths.appBuild = 'build' 22 | webpackConfig.output = { 23 | ...webpackConfig.output, 24 | // ...{ 25 | // filename: whenDev(() => 'static/js/bundle.js', 'static/js/[name].js'), 26 | // chunkFilename: 'static/js/[name].js' 27 | // }, 28 | path: path.resolve(__dirname, 'build'), // 修改输出文件目录 29 | publicPath: './' 30 | } 31 | return webpackConfig 32 | } 33 | }, 34 | devServer: { 35 | port: 3002, 36 | hot: true 37 | }, 38 | // plugins: [ 39 | // { 40 | // plugin: CracoLessPlugin, 41 | // options: { 42 | // lessLoaderOptions: { 43 | // // modifyVars: { '@primary-color': '#1da57a' }, 44 | // javascriptEnabled: true 45 | // } 46 | // } 47 | // } 48 | // ] 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-antd-blog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^5.6.4", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "antd": "^4.2.5", 11 | "axios": "^0.21.1", 12 | "craco-less": "^1.16.0", 13 | "echarts": "^4.8.0", 14 | "prop-types": "^15.7.2", 15 | "react": "^16.13.1", 16 | "react-dom": "^16.13.1", 17 | "react-loadable": "^5.5.0", 18 | "react-redux": "^7.2.1", 19 | "react-router-config": "^5.1.1", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "3.4.1", 22 | "redux": "^4.0.5", 23 | "redux-saga": "^1.1.3" 24 | }, 25 | "scripts": { 26 | "start": "cross-env REACT_APP_ENV=mock craco start", 27 | "dev": "cross-env REACT_APP_ENV=development craco start", 28 | "build": "craco build", 29 | "test": "craco test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": "react-app" 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | }, 47 | "devDependencies": { 48 | "cross-env": "^7.0.2", 49 | "mockjs": "^1.1.0", 50 | "node-sass": "^4.14.1", 51 | "sass-loader": "^9.0.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React 后台管理demo 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/public/logo512.png -------------------------------------------------------------------------------- /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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/src/assets/images/404.png -------------------------------------------------------------------------------- /src/assets/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/src/assets/images/bg1.jpg -------------------------------------------------------------------------------- /src/assets/images/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/src/assets/images/bg2.jpg -------------------------------------------------------------------------------- /src/assets/images/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/src/assets/images/bg3.jpg -------------------------------------------------------------------------------- /src/assets/images/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/src/assets/images/bg4.jpg -------------------------------------------------------------------------------- /src/assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuRookie/react-tutorials/889148602ee6b4d84a6b64f886be056392ca80ba/src/assets/images/default.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/AsyncComponent/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | const asyncComponent = (importComponent) => { 4 | return class extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.state = { 8 | component: null 9 | } 10 | } 11 | componentDidMount() { 12 | const { history, route } = this.props 13 | const token = sessionStorage.getItem('token') 14 | if (route.requiredAuth && !token) { 15 | history.replace('/login') 16 | return 17 | } 18 | importComponent().then(cmp => { 19 | this.setState({ 20 | component: cmp.default.WrappedComponent || cmp.default 21 | }) 22 | }) 23 | } 24 | 25 | render() { 26 | const C = this.state.component 27 | return C ? : null 28 | } 29 | } 30 | } 31 | 32 | export default asyncComponent -------------------------------------------------------------------------------- /src/components/loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Spin } from 'antd' 3 | 4 | const loadingWrapper = { 5 | position: 'relative', 6 | height: '100vh' 7 | } 8 | const loadingSpin = { 9 | position: 'absolute', 10 | left: '50%', 11 | top: '45%', 12 | transform: 'translate(-50%, -45%)' 13 | } 14 | 15 | function Loading() { 16 | return ( 17 |
18 | 19 |
20 | ) 21 | } 22 | 23 | export default Loading -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { HashRouter } from 'react-router-dom' 4 | import { renderRoutes } from "react-router-config" 5 | 6 | import { Provider } from 'react-redux' 7 | 8 | import { ConfigProvider } from 'antd' 9 | import zhCN from 'antd/es/locale/zh_CN' 10 | import http from '@/utils/http' 11 | import routes from '@/routes' 12 | import '@/style/index.scss' 13 | 14 | import configureStore from '@/store' 15 | import rootSaga from '@/store/sagas' 16 | 17 | const store = configureStore() 18 | rootSaga.map(saga => store.runSaga(saga)) 19 | 20 | React.$http = http 21 | 22 | ReactDOM.render( 23 | 24 | 25 | 26 | {renderRoutes(routes)} 27 | 28 | 29 | , 30 | document.getElementById('root') 31 | ); 32 | -------------------------------------------------------------------------------- /src/mock/task.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | function randomTaskData() { 4 | let user = [] 5 | for (let i = 0; i < 7; i++) { 6 | let item = { 7 | key: Mock.mock('@id'), 8 | id: Mock.mock('@id'), 9 | taskName: Mock.mock('@cword(3, 5)'), 10 | taskTime: Mock.mock('@pick(["半小时", "一小时", "两小时", "三小时", "四小时", "五小时", "六小时"])'), 11 | taskStatus: Mock.mock('@pick(["已完成", "进行中", "未开始"])') 12 | } 13 | user.push(item) 14 | } 15 | return user 16 | } 17 | 18 | export const TaskData = randomTaskData -------------------------------------------------------------------------------- /src/mock/user.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | function randomUserData() { 4 | let user = [] 5 | for (let i = 0; i < 7; i++) { 6 | let item = { 7 | key: Mock.mock('@id'), 8 | id: Mock.mock('@id'), 9 | username: Mock.mock('@cname'), 10 | lastLoginTime: Mock.mock('@time("HH:mm")'), 11 | status: Mock.mock('@pick([0, 1])'), 12 | like: Mock.mock('@integer(15, 100)') 13 | } 14 | user.push(item) 15 | } 16 | return user 17 | } 18 | 19 | export const UserData = randomUserData -------------------------------------------------------------------------------- /src/routes/components.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Loadable from 'react-loadable' 3 | import { Spin } from 'antd' 4 | import Loading from '@/components/loading' 5 | 6 | const Layout = Loadable({ 7 | loader: () => import('@/views/Layout'), 8 | loading: Loading 9 | }) 10 | 11 | const Login2 = Loadable({ 12 | loader: () => import('@/views/Login'), 13 | loading() { return } 14 | }) 15 | 16 | const Login = Loadable({ 17 | loader: () => import('@/views/Login/login2'), 18 | loading: Loading 19 | }) 20 | 21 | const Forget = Loadable({ 22 | loader: () => import('@/views/Login/forget'), 23 | loading: Loading 24 | }) 25 | 26 | const NotFound = Loadable({ 27 | loader: () => import('@/views/NotFound/404'), 28 | loading: Loading 29 | }) 30 | 31 | const Home = Loadable({ 32 | loader: () => import('@/views/Home'), 33 | loading: Loading 34 | }) 35 | 36 | const User = Loadable({ 37 | loader: () => import('@/views/User'), 38 | loading: Loading 39 | }) 40 | 41 | const Role = Loadable({ 42 | loader: () => import('@/views/User/role'), 43 | loading: Loading 44 | }) 45 | 46 | const WebsiteSetting = Loadable({ 47 | loader: () => import('@/views/Setting/websiteSetting'), 48 | loading: Loading 49 | }) 50 | 51 | const EmailService = Loadable({ 52 | loader: () => import('@/views/Setting/emailService'), 53 | loading: Loading 54 | }) 55 | 56 | const BasicInfo = Loadable({ 57 | loader: () => import('@/views/Setting/basicInfo'), 58 | loading: Loading 59 | }) 60 | 61 | const ModifyPassword = Loadable({ 62 | loader: () => import('@/views/Setting/modifyPassword'), 63 | loading: Loading 64 | }) 65 | 66 | const Message = Loadable({ 67 | loader: () => import('@/views/Community/message'), 68 | loading: Loading 69 | }) 70 | 71 | const Article = Loadable({ 72 | loader: () => import('@/views/Articles/article'), 73 | loading: Loading 74 | }) 75 | 76 | const Category = Loadable({ 77 | loader: () => import('@/views/Articles/category'), 78 | loading: Loading 79 | }) 80 | 81 | const Comment = Loadable({ 82 | loader: () => import('@/views/Articles/comment'), 83 | loading: Loading 84 | }) 85 | 86 | export default { 87 | Layout, 88 | Login2, 89 | Login, 90 | Forget, 91 | NotFound, 92 | Home, 93 | User, 94 | Role, 95 | WebsiteSetting, 96 | EmailService, 97 | BasicInfo, 98 | ModifyPassword, 99 | Message, 100 | Article, 101 | Category, 102 | Comment 103 | } 104 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Redirect } from 'react-router-dom' 3 | 4 | import RouteComponents from './components' 5 | // import AsyncComponent from '@/components/AsyncComponent' 6 | 7 | function getToken() { 8 | return sessionStorage.getItem('token') 9 | } 10 | 11 | const routes = [ 12 | { 13 | path: '/login2', 14 | requiredAuth: false, 15 | // component: AsyncComponent(() => import('@/views/Login')) 16 | component: RouteComponents.Login2 17 | }, 18 | { 19 | path: '/login', 20 | requiredAuth: false, 21 | // component: AsyncComponent(() => import('@/views/Login/login2')) 22 | component: RouteComponents.Login 23 | }, 24 | { 25 | path: '/forget', 26 | requiredAuth: false, 27 | // component: AsyncComponent(() => import('@/views/Login/forget')) 28 | component: RouteComponents.Forget 29 | }, 30 | { 31 | path: '/404', 32 | requiredAuth: false, 33 | // component: AsyncComponent(() => import('@/views/NotFound/404')) 34 | component: RouteComponents.NotFound 35 | }, 36 | { 37 | // component: AsyncComponent(() => import('@/views/Layout')), 38 | // component: Loadable({ loader: () => import('@/views/Layout'), loading() { return }}), 39 | render: (props) => { 40 | const token = getToken() 41 | if (!token) { 42 | return 43 | } 44 | 45 | return 46 | }, 47 | requiredAuth: true, 48 | routes: [ 49 | { 50 | path: '/', 51 | exact: true, 52 | render: () => 53 | }, 54 | { 55 | path: '/home', 56 | requiredAuth: true, 57 | // component: AsyncComponent(() => import('@/views/Home')) 58 | // component: Loadable({ loader: () => import('@/views/Home'), loading() { return }}) 59 | component: RouteComponents.Home 60 | }, 61 | { 62 | path: '/user', 63 | requiredAuth: true, 64 | // component: AsyncComponent(() => import('@/views/User')) 65 | component: RouteComponents.User 66 | }, 67 | { 68 | path: '/roles', 69 | requiredAuth: true, 70 | // component: AsyncComponent(() => import('@/views/User/role')) 71 | component: RouteComponents.Role 72 | }, 73 | { 74 | path: '/website-setting', 75 | requiredAuth: true, 76 | // component: AsyncComponent(() => import('@/views/Setting/websiteSetting')) 77 | component: RouteComponents.WebsiteSetting 78 | }, 79 | { 80 | path: '/email-service', 81 | requiredAuth: true, 82 | // component: AsyncComponent(() => import('@/views/Setting/emailService')) 83 | component: RouteComponents.EmailService 84 | }, 85 | { 86 | path: '/basic-info', 87 | requiredAuth: true, 88 | // component: AsyncComponent(() => import('@/views/Setting/basicInfo')) 89 | component: RouteComponents.BasicInfo 90 | }, 91 | { 92 | path: '/modify-password', 93 | requiredAuth: true, 94 | // component: AsyncComponent(() => import('@/views/Setting/modifyPassword')) 95 | component: RouteComponents.ModifyPassword 96 | }, 97 | { 98 | path: '/message', 99 | requiredAuth: true, 100 | // component: AsyncComponent(() => import('@/views/Community/message')) 101 | // component: Loadable({ loader: () => import('@/views/Community/message'), loading() { return }}) 102 | component: RouteComponents.Message 103 | }, 104 | { 105 | path: '/articles', 106 | component: RouteComponents.Article 107 | }, 108 | { 109 | path: '/category', 110 | component: RouteComponents.Category 111 | }, 112 | { 113 | path: '/comments', 114 | component: RouteComponents.Comment 115 | }, 116 | { 117 | path: '*', 118 | render: () => 119 | } 120 | ] 121 | }, 122 | { 123 | path: '*', 124 | component: RouteComponents.NotFound 125 | } 126 | ] 127 | 128 | export default routes -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import createSagaMiddleware from 'redux-saga' 2 | import { createStore, applyMiddleware } from 'redux' 3 | import concatReducer from './reducers' 4 | 5 | export default function configureStore(initialState) { 6 | const sagaMiddleware = createSagaMiddleware() 7 | return { 8 | ...createStore(concatReducer, initialState, applyMiddleware(sagaMiddleware)), 9 | runSaga: sagaMiddleware.run 10 | } 11 | } -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | import messageReducer from '@/views/Community/reducers' 4 | import articleReducer from '@/views/Articles/reducers' 5 | 6 | const concatReducers = combineReducers({ 7 | messageReducer, 8 | articleReducer 9 | }) 10 | 11 | export default concatReducers -------------------------------------------------------------------------------- /src/store/sagas.js: -------------------------------------------------------------------------------- 1 | import messageSaga from '@/views/Community/sagas' 2 | import articleSaga from '@/views/Articles/sagas' 3 | 4 | 5 | export default [ 6 | messageSaga, 7 | articleSaga 8 | ] -------------------------------------------------------------------------------- /src/style/_variables.scss: -------------------------------------------------------------------------------- 1 | $colors: ( 2 | "white": #fff, 3 | "primary": #1890ff, 4 | "light-grey": #eaeaea, 5 | "light-grey-1": #efefef, 6 | "light-red": #FF5722, 7 | "yellow": #FFB800, 8 | "green": #5FB878, 9 | "grey-9": #999, 10 | "grey-6": #666, 11 | "grey-3": #333, 12 | ); 13 | 14 | $fontSizes: ( 15 | '12': 12px, 16 | '14': 14px, 17 | '16': 16px, 18 | '18': 18px, 19 | '20': 20px, 20 | '24': 24px, 21 | '30': 30px, 22 | '36': 36px, 23 | ); 24 | 25 | $spaces: ( 26 | '5': 5px, 27 | '10': 10px, 28 | '15': 15px, 29 | '20': 20px, 30 | '25': 25px, 31 | '30': 30px, 32 | ); 33 | 34 | $directions: (t:top, r:right, b:bottom, l:left); -------------------------------------------------------------------------------- /src/style/index.scss: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | @import './variables'; 3 | 4 | 5 | #root { 6 | height: 100%; 7 | } 8 | // 样式初始化 9 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; } 10 | h1, h2, h3, h4, h5, h6{ font-size:100%; } 11 | address, cite, dfn, em, var { font-style:normal; } 12 | code, kbd, pre, samp { font-family:couriernew, courier, monospace; } 13 | small{ font-size:12px; } 14 | ul, ol { list-style:none; } 15 | a { text-decoration:none; } 16 | a:hover { text-decoration: none; } 17 | sup { vertical-align:text-top; } 18 | sub{ vertical-align:text-bottom; } 19 | legend { color:#000; } 20 | fieldset, img { border:0; } 21 | button, input, select, textarea { font-size:100%; } 22 | table { border-collapse:collapse; border-spacing:0; } 23 | 24 | .w-100 { 25 | width: 100%; 26 | } 27 | .h-100 { 28 | height: 100%; 29 | } 30 | .d-flex { 31 | display: flex; 32 | } 33 | // color 34 | @each $colorKey, $colorValue in $colors { 35 | .text-#{$colorKey} { 36 | color: $colorValue; 37 | } 38 | .bg-#{$colorKey} { 39 | background-color: $colorValue; 40 | } 41 | } 42 | 43 | // font-size 44 | @each $font, $fontSize in $fontSizes { 45 | .fs-#{$font} { 46 | font-size: $fontSize; 47 | } 48 | } 49 | 50 | // text-align 51 | @each $direction, $value in $directions { 52 | .text-#{$value} { 53 | text-align: $value; 54 | } 55 | } 56 | 57 | // space 58 | @each $dKey, $dValue in $directions { 59 | @each $spaceKey, $spaceValue in $spaces { 60 | .m#{$dKey}-#{$spaceKey} { 61 | margin-#{$dValue}: $spaceValue; 62 | } 63 | .p#{$dKey}-#{$spaceKey} { 64 | padding-#{$dValue}: $spaceValue; 65 | } 66 | @if $dKey == 'l' { 67 | .mx-#{$spaceKey} { 68 | margin: 0 $spaceValue; 69 | } 70 | .px-#{$spaceKey} { 71 | padding: 0 $spaceValue; 72 | } 73 | } 74 | @if $dKey == 't' { 75 | .my-#{$spaceKey} { 76 | margin: $spaceValue 0; 77 | } 78 | .py-#{$spaceKey} { 79 | padding: $spaceValue 0; 80 | } 81 | } 82 | @if $dKey == 'r' { 83 | .m#{$spaceKey} { 84 | margin: $spaceValue; 85 | } 86 | .p#{$spaceKey} { 87 | padding: $spaceValue; 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/utils/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { message } from 'antd' 3 | message.config({ maxCount: 3 }) 4 | 5 | const MOCK_API = 'https://fastmock.site/mock/d451d648bf7d24e6bcfcd03fd9bfe605/react-tutorials/api/' 6 | const whiteApi = ['login', 'register', 'send/code', 'reset/password'] 7 | 8 | 9 | const http = axios.create({ 10 | baseURL: MOCK_API, 11 | // baseURL: process.env.REACT_APP_ENV === 'mock' ? MOCK_API : window.location.origin, 12 | // withCredentials: true, 13 | timeout: 1000 * 60 * 3 14 | }) 15 | 16 | http.interceptors.request.use(function(config) { 17 | const { url } = config 18 | if (!whiteApi.includes(url)) { 19 | const token = window.sessionStorage.getItem('token') 20 | if (!token) { 21 | return Promise.reject({ 22 | "code": 4002, 23 | "message": "为获取到令牌,请先登录", 24 | "data": null 25 | }) 26 | } 27 | config.headers.Authorization = token 28 | } 29 | return config 30 | }, function(error) { 31 | return Promise.reject(error) 32 | }) 33 | 34 | http.interceptors.response.use(function(response) { 35 | const data = response.data 36 | if (data.code === 200) { 37 | return data.data 38 | } else { 39 | message.error(data.message || data.desc) 40 | return Promise.reject(response) 41 | } 42 | }, function(error) { 43 | if (error.code === 4002) { 44 | message.error(error.message) 45 | window.location.href = '#/login' 46 | return Promise.reject(error) 47 | } 48 | return Promise.reject(error.response) 49 | }) 50 | 51 | export default http -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | // 金额格式化 2 | export const formatCurrency = (val, n = 0) => { 3 | if (val) { 4 | const num = parseInt(val, 10) 5 | return (num.toFixed(n).toString()).replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, '$1,') 6 | } 7 | return '0.00' 8 | } -------------------------------------------------------------------------------- /src/views/Articles/action.js: -------------------------------------------------------------------------------- 1 | import { 2 | ARTICLE_FETCH_LIST, 3 | ARTICLE_FETCH_EDIT, 4 | ARTICLE_FETCH_DELETE 5 | } from './constants' 6 | 7 | export function fetchArticleList(params, resolve) { 8 | return { 9 | type: ARTICLE_FETCH_LIST, 10 | payload: { 11 | params, 12 | resolve 13 | } 14 | } 15 | } 16 | 17 | export function fetchEditArticle(id, resolve) { 18 | return { 19 | type: ARTICLE_FETCH_EDIT, 20 | payload: { 21 | id, 22 | resolve 23 | } 24 | } 25 | } 26 | 27 | export function fetchDeleteArticle(id, resolve) { 28 | return { 29 | type: ARTICLE_FETCH_DELETE, 30 | payload: { 31 | id, 32 | resolve 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/views/Articles/api.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const requestArticleList = (params) => { 4 | const { $http } = React 5 | return $http.get('article/list', {params}).then(res => { 6 | return res 7 | }) 8 | } 9 | export const requestArticleDelete = (params) => { 10 | const { $http } = React 11 | return $http.delete('article/delete', {data: params}).then(res => { 12 | return res 13 | }) 14 | } 15 | export const requestArticleCreate= (params) => { 16 | const { $http } = React 17 | return $http.post('article/create', params).then(res => { 18 | return res 19 | }) 20 | } 21 | export const requestArticleEdit = (params) => { 22 | const { $http } = React 23 | return $http.put('article/edit', params).then(res => { 24 | return res 25 | }) 26 | } -------------------------------------------------------------------------------- /src/views/Articles/article.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import { Form, Row, Col, Input, Button, Select, Space, Card, Table, Tag, Modal, message } from 'antd' 5 | import { 6 | EditOutlined, 7 | PlusOutlined, 8 | DeleteOutlined, 9 | ExclamationCircleOutlined, 10 | ClearOutlined 11 | } from '@ant-design/icons' 12 | 13 | import { 14 | fetchArticleList, 15 | fetchDeleteArticle 16 | } from './action' 17 | 18 | const searchItem = [ 19 | { key: 'author', name: 'author', label: '作者', defaultValue: null, type: 'input', placeholder: '请输入' }, 20 | { key: 'title', name: 'title', label: '标题', defaultValue: null, type: 'input', placeholder: '请输入' }, 21 | { key: 'tag', name: 'tag', label: '标签', defaultValue: -1, type: 'select', options: [ 22 | { value: -1, label: '全部' }, 23 | { value: 0, label: '美食' }, 24 | { value: 1, label: '新闻'}, 25 | { value: 2, label: '体育'}, 26 | { value: 3, label: '音乐'}, 27 | { value: 4, label: '八卦'} 28 | ], placeholder: '请选择' } 29 | ] 30 | 31 | const tags = { 32 | 0: '美食', 33 | 1: '新闻', 34 | 2: '体育', 35 | 3: '音乐', 36 | 4: '八卦', 37 | } 38 | 39 | class Article extends React.Component { 40 | constructor(props) { 41 | super(props) 42 | this.state = { 43 | loading: false, 44 | articleTableData: [], 45 | control: { 46 | 'edit': { 47 | name: '编辑', 48 | message: '编辑成功', 49 | fn: (params, callback) => callback 50 | }, 51 | 'delete': { 52 | name: '删除', 53 | message: '删除成功', 54 | fn: props.onDeleteArticle 55 | }, 56 | 'takeDown': { 57 | name: '下架', 58 | message: '下架成功', 59 | fn: this.onTakeDown 60 | } 61 | } 62 | } 63 | } 64 | 65 | formRef = React.createRef() 66 | 67 | columns = [ 68 | { title: '文章标题', dataIndex: 'title' }, 69 | { title: '文章标签', dataIndex: 'tag', render: (text) => { 70 | return {tags[text] || '未知'} 71 | }}, 72 | { title: '文章作者', dataIndex: 'author' }, 73 | { title: '创建时间', dataIndex: 'createDate' }, 74 | { title: '发布状态', dataIndex: 'status', render: (text) => { 75 | const color = text === 0 ? 'green' : '' 76 | const txt = text === 0 ? '已发布' : '未发布' 77 | return ( 78 | {txt} 79 | ) 80 | }}, 81 | { title: '操作', key: 'action', render: (text, record) => { 82 | return ( 83 | 84 | 85 | {record.status === 1 ? 86 | 87 | : 88 | } 89 | 90 | ) 91 | }} 92 | ] 93 | 94 | componentDidMount() { 95 | const { onLoadArticleList, pagination = {} } = this.props 96 | this.setState({loading: true}) 97 | onLoadArticleList({ 98 | page: pagination.current, 99 | pageSize: pagination.pageSize 100 | }, () => { 101 | this.setState({loading: false}) 102 | }) 103 | } 104 | 105 | onSearch = values => { 106 | const { onLoadArticleList, pagination } = this.props 107 | this.setState({loading: true}) 108 | onLoadArticleList({ 109 | ...values, 110 | page: pagination.current, 111 | pageSize: pagination.pageSize 112 | }, () => { 113 | this.setState({loading: false}) 114 | }) 115 | } 116 | onOpenModal = () => { 117 | message.error('此操作你暂无权限!') 118 | } 119 | onTakeDown = (record, callback) => { 120 | // 下架请求 121 | callback && callback() 122 | } 123 | onControl = (type, record) => { 124 | const { onLoadArticleList, pagination } = this.props 125 | const { control } = this.state 126 | if (type === 'edit') { 127 | return message.error('此操作你暂无权限!') 128 | } 129 | Modal.confirm({ 130 | title: control[type]['name'] + '文章', 131 | icon: , 132 | content: (确认{control[type]['name']}文章{record.title}吗?), 133 | onOk: () => { 134 | this.setState({loading: true}) 135 | control[type].fn({id: record.id}, () => { 136 | message.success(control[type].message) 137 | const query = this.formRef.current.getFieldsValue() 138 | onLoadArticleList({ 139 | ...query, 140 | page: pagination.current, 141 | pageSize: pagination.pageSize 142 | }, () => { 143 | this.setState({loading: false}) 144 | })} 145 | ) 146 | } 147 | }) 148 | 149 | } 150 | paginationChange = (pagination) => { 151 | const { onLoadArticleList } = this.props 152 | this.setState({loading: true}) 153 | const query = this.formRef.current.getFieldsValue() 154 | onLoadArticleList({ 155 | ...query, 156 | page: pagination.current, 157 | pageSize: pagination.pageSize 158 | }, () => { 159 | this.setState({loading: false}) 160 | }) 161 | } 162 | 163 | render() { 164 | const { articleTableData, pagination } = this.props 165 | const { loading } = this.state; 166 | const getFields = () => { 167 | const children = searchItem.map(item => ( 168 | 169 | 173 | {item.type === 'input' ? 174 | : 175 | } 180 | 181 | 182 | )) 183 | return children 184 | } 185 | const searchControl = ( 186 | 187 | 188 | 189 | 190 | 191 | 192 | ) 193 | return ( 194 | 195 |
198 | 199 | {getFields()} 200 | {searchControl} 201 | 202 |
203 | 204 | 205 | 206 | 207 | 208 | ) 209 | } 210 | } 211 | 212 | const mapStateToProps = (state) => { 213 | return { 214 | articleTableData: state.articleReducer.articleList, 215 | pagination: state.articleReducer.pagination 216 | } 217 | } 218 | 219 | const mapDispatchToProps = dispatch => { 220 | return { 221 | onLoadArticleList: (params, resolve) => dispatch(fetchArticleList(params, resolve)), 222 | onDeleteArticle: (params, resolve) => dispatch(fetchDeleteArticle(params, resolve)), 223 | } 224 | } 225 | 226 | export default connect(mapStateToProps, mapDispatchToProps)(Article) -------------------------------------------------------------------------------- /src/views/Articles/category.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Form, Input, Button, Space, Card, Table, Modal, message } from 'antd' 4 | import { 5 | EditOutlined, 6 | PlusOutlined, 7 | DeleteOutlined, 8 | ExclamationCircleOutlined 9 | } from '@ant-design/icons' 10 | 11 | const { $http } = React 12 | const layout = { 13 | labelCol: { 14 | span: 4, 15 | }, 16 | wrapperCol: { 17 | span: 16, 18 | }, 19 | }; 20 | const tailLayout = { 21 | wrapperCol: { 22 | offset: 4, 23 | span: 16, 24 | }, 25 | }; 26 | 27 | class Category extends React.Component { 28 | constructor(props) { 29 | super(props) 30 | this.state = { 31 | loading: false, 32 | categoryTableData: [], 33 | modalVisible: false, 34 | modalType: 'add', 35 | modalForm: { 36 | categoryName: '', 37 | categoryId: '' 38 | }, 39 | modalLoading: false, 40 | } 41 | } 42 | 43 | formRef = React.createRef() 44 | 45 | columns = [ 46 | { title: '分类名', dataIndex: 'categoryName' }, 47 | { title: '创建时间', dataIndex: 'createDate' }, 48 | { title: '操作', key: 'action', render: (text, record) => { 49 | return ( 50 | 51 | 52 | 53 | 54 | ) 55 | }} 56 | ] 57 | 58 | componentDidMount() { 59 | this.setState({loading: true}) 60 | this.onLoadCategoryList() 61 | } 62 | 63 | onLoadCategoryList = () => { 64 | $http.get('category/list').then(res => { 65 | this.setState({ categoryTableData: res, loading: false }) 66 | }) 67 | } 68 | 69 | onOpenModal = () => { 70 | this.setState({ 71 | modalType: 'add', 72 | modalVisible: true 73 | }) 74 | } 75 | onEdit = (record) => { 76 | this.setState({ 77 | modalType: 'edit', 78 | modalVisible: true, 79 | modalForm: { 80 | categoryId: record.categoryId, 81 | categoryName: record.categoryName 82 | } 83 | }, () => { 84 | this.formRef.current.setFieldsValue({ 85 | categoryName: record.categoryName 86 | }) 87 | }) 88 | } 89 | onDelete = (record) => { 90 | Modal.confirm({ 91 | title: '删除分类', 92 | icon: , 93 | content: (确认删除分类{record.title}吗?), 94 | onOk: () => { 95 | this.setState({loading: true}) 96 | $http.delete('category/delete', {data: {id: record.id}}).then(() => { 97 | message.success('删除成功') 98 | this.onLoadCategoryList() 99 | }) 100 | } 101 | }) 102 | } 103 | onSave = values => { 104 | const { modalType, modalForm } = this.state 105 | this.setState({ modalLoading: true }) 106 | if (modalType === 'add') { 107 | $http.post('category/create', values).then(() => { 108 | message.success('添加成功') 109 | this.onCancel() 110 | this.onLoadCategoryList() 111 | }) 112 | } else { 113 | values.categoryId = modalForm.categoryId 114 | $http.put('category/edit', values).then(() => { 115 | message.success('编辑成功') 116 | this.onCancel() 117 | this.onLoadCategoryList() 118 | }) 119 | } 120 | } 121 | onCancel = () => { 122 | this.setState({ 123 | modalType: 'add', 124 | modalVisible: false, 125 | modalLoading: false, 126 | modalForm: { 127 | categoryId: '', 128 | categoryName: '' 129 | } 130 | }, () => { 131 | this.formRef.current.setFieldsValue({ 132 | categoryName: '' 133 | }) 134 | }) 135 | } 136 | 137 | render() { 138 | const { loading, categoryTableData, modalType, modalVisible, modalLoading } = this.state; 139 | 140 | return ( 141 | 142 | 143 | 144 | 145 |
146 | 153 |
154 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 |
168 | 169 | ) 170 | } 171 | } 172 | 173 | export default Category -------------------------------------------------------------------------------- /src/views/Articles/comment.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Form, Row, Col, Input, Select, Button, Space, Card, Table, Modal, message } from 'antd' 4 | import { 5 | EditOutlined, 6 | DeleteOutlined, 7 | ExclamationCircleOutlined 8 | } from '@ant-design/icons' 9 | 10 | const { $http } = React 11 | const layout = { 12 | labelCol: { 13 | span: 4, 14 | }, 15 | wrapperCol: { 16 | span: 16, 17 | }, 18 | }; 19 | const tailLayout = { 20 | wrapperCol: { 21 | offset: 4, 22 | span: 16, 23 | }, 24 | }; 25 | const searchItem = [ 26 | { key: 'commentator', name: 'commentator', label: '评论人', defaultValue: null, type: 'input', placeholder: '请输入' }, 27 | { key: 'content', name: 'content', label: '评论内容', defaultValue: null, type: 'input', placeholder: '请输入' } 28 | ] 29 | 30 | class Comment extends React.Component { 31 | constructor(props) { 32 | super(props) 33 | this.state = { 34 | loading: false, 35 | commentTableData: [], 36 | pagination: { 37 | current: 1, 38 | pageSize: 10, 39 | total: 0 40 | }, 41 | modalVisible: false, 42 | modalForm: { 43 | id: '', 44 | content: '' 45 | }, 46 | modalLoading: false 47 | } 48 | } 49 | 50 | searchRef = React.createRef() 51 | formRef = React.createRef() 52 | 53 | columns = [ 54 | { title: '评论人', dataIndex: 'commentator' }, 55 | { title: '评论内容', dataIndex: 'content' }, 56 | { title: '评论时间', dataIndex: 'createDate' }, 57 | { title: '操作', key: 'action', render: (text, record) => { 58 | return ( 59 | 60 | 61 | 62 | 63 | ) 64 | }} 65 | ] 66 | 67 | componentDidMount() { 68 | this.setState({loading: true}) 69 | this.onLoadCommentList() 70 | } 71 | 72 | onLoadCommentList = (params) => { 73 | const {pagination} = this.state 74 | const query = { 75 | ...params, 76 | page: pagination.current, 77 | pageSize: pagination.pageSize 78 | } 79 | $http.get('comment/list', {params: query}).then(res => { 80 | const {list, ...pagination} = res 81 | const {page, ...args} = pagination 82 | this.setState({ 83 | commentTableData: list, 84 | pagination: { 85 | current: page, 86 | ...args 87 | }, 88 | loading: false 89 | }) 90 | }) 91 | } 92 | onSearch = (values) => { 93 | this.setState({loading: true}) 94 | this.onLoadCommentList(values) 95 | } 96 | onOpenModal = () => { 97 | this.setState({ 98 | modalVisible: true 99 | }) 100 | } 101 | paginationChange = (pagination) => { 102 | this.setState({ 103 | loading: true, 104 | pagination: { 105 | page: pagination.page, 106 | pageSize: pagination.pageSize 107 | } 108 | }, () => { 109 | this.onLoadCommentList() 110 | }) 111 | } 112 | onEdit = (record) => { 113 | this.setState({ 114 | modalVisible: true, 115 | modalForm: { 116 | id: record.id, 117 | content: record.content 118 | } 119 | }, () => { 120 | this.formRef.current && this.formRef.current.setFieldsValue({ 121 | id: record.id, 122 | content: record.content 123 | }) 124 | }) 125 | } 126 | onDelete = (record) => { 127 | Modal.confirm({ 128 | title: '删除评论', 129 | icon: , 130 | content: (确认删除此条评论吗?), 131 | onOk: () => { 132 | this.setState({loading: true}) 133 | $http.delete('comment/delete', {data: {id: record.id}}).then(() => { 134 | message.success('删除成功') 135 | this.onLoadCommentList() 136 | }) 137 | } 138 | }) 139 | } 140 | onSave = values => { 141 | const { modalForm } = this.state 142 | this.setState({ modalLoading: true }) 143 | values.id = modalForm.id 144 | $http.put('comment/edit', values).then(() => { 145 | message.success('编辑成功') 146 | this.onCancel() 147 | this.onLoadCommentList() 148 | }) 149 | } 150 | onCancel = () => { 151 | this.setState({ 152 | modalVisible: false, 153 | modalLoading: false, 154 | modalForm: { 155 | id: '', 156 | content: '' 157 | } 158 | }, () => { 159 | this.formRef.current.setFieldsValue({ 160 | id: '', 161 | content: '' 162 | }) 163 | }) 164 | } 165 | 166 | render() { 167 | const { loading, commentTableData, pagination, modalVisible, modalForm, modalLoading } = this.state; 168 | const getFields = () => { 169 | const children = searchItem.map(item => ( 170 | 171 | 175 | {item.type === 'input' ? 176 | : 177 | } 182 | 183 | 184 | )) 185 | return children 186 | } 187 | const searchControl = ( 188 | 189 | 190 | 191 | 192 | 193 | 194 | ) 195 | return ( 196 | 197 |
200 | 201 | {getFields()} 202 | {searchControl} 203 | 204 | 205 |
206 | 212 |
213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 |
224 | 225 | ) 226 | } 227 | } 228 | 229 | export default Comment -------------------------------------------------------------------------------- /src/views/Articles/constants.js: -------------------------------------------------------------------------------- 1 | export const ARTICLE_FETCH_LIST = 'ARTICLE_FETCH_LIST' 2 | export const ARTICLE_FETCH_LIST_SUCCEEDED = 'ARTICLE_FETCH_LIST_SUCCEEDED' 3 | export const ARTICLE_FETCH_LIST_FAILED = 'ARTICLE_FETCH_LIST_FAILED' 4 | export const ARTICLE_FETCH_EDIT = 'ARTICLE_FETCH_EDIT' 5 | export const ARTICLE_FETCH_EDIT_SUCCEEDED = 'ARTICLE_FETCH_EDIT_SUCCEEDED' 6 | export const ARTICLE_FETCH_EDIT_FAILED = 'ARTICLE_FETCH_EDIT_FAILED' 7 | export const ARTICLE_FETCH_DELETE = 'ARTICLE_FETCH_DELETE' 8 | export const ARTICLE_FETCH_DELETE_SUCCEEDED = 'ARTICLE_FETCH_DELETE_SUCCEEDED' 9 | export const ARTICLE_FETCH_DELETE_FAILED = 'ARTICLE_FETCH_DELETE_FAILED' -------------------------------------------------------------------------------- /src/views/Articles/reducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | ARTICLE_FETCH_LIST_SUCCEEDED, 3 | ARTICLE_FETCH_LIST_FAILED, 4 | ARTICLE_FETCH_DELETE_SUCCEEDED, 5 | ARTICLE_FETCH_DELETE_FAILED 6 | } from './constants' 7 | 8 | const initialState = { 9 | articleList: [], 10 | pagination: { 11 | current: 1, 12 | pageSize: 10, 13 | total: 0 14 | } 15 | } 16 | 17 | export default (state = initialState, action) => { 18 | switch(action.type) { 19 | case ARTICLE_FETCH_LIST_SUCCEEDED: 20 | const { list, ...pagination } = action.result || {} 21 | const { page, ...args } = pagination 22 | return { ...state, articleList: list, pagination: {current: page, ...args}} 23 | case ARTICLE_FETCH_DELETE_SUCCEEDED: 24 | return state 25 | case ARTICLE_FETCH_LIST_FAILED: 26 | case ARTICLE_FETCH_DELETE_FAILED: 27 | return state 28 | default: 29 | return state 30 | } 31 | } -------------------------------------------------------------------------------- /src/views/Articles/sagas.js: -------------------------------------------------------------------------------- 1 | import { put, call, takeEvery, all } from 'redux-saga/effects' 2 | 3 | import { 4 | requestArticleList, 5 | requestArticleDelete 6 | } from './api' 7 | 8 | import { 9 | ARTICLE_FETCH_LIST, 10 | ARTICLE_FETCH_LIST_SUCCEEDED, 11 | ARTICLE_FETCH_LIST_FAILED, 12 | ARTICLE_FETCH_DELETE, 13 | ARTICLE_FETCH_DELETE_SUCCEEDED, 14 | ARTICLE_FETCH_EDIT_FAILED 15 | } from './constants' 16 | 17 | function* loadArticleList({ payload }) { 18 | try { 19 | const result = yield call(requestArticleList, payload.params) 20 | yield put({ type: ARTICLE_FETCH_LIST_SUCCEEDED, result }) 21 | payload.resolve() 22 | } catch (error) { 23 | yield put({ type: ARTICLE_FETCH_LIST_FAILED, message: error.message}) 24 | } 25 | } 26 | 27 | function* deleteArticle({ payload }) { 28 | try { 29 | const result = yield call(requestArticleDelete, payload.id) 30 | yield put({ type: ARTICLE_FETCH_DELETE_SUCCEEDED, result }) 31 | payload.resolve() 32 | } catch (error) { 33 | yield put({ type: ARTICLE_FETCH_EDIT_FAILED, message: error.message}) 34 | } 35 | } 36 | 37 | const articleSaga = function* () { 38 | yield all([ 39 | takeEvery(ARTICLE_FETCH_LIST, loadArticleList), 40 | takeEvery(ARTICLE_FETCH_DELETE, deleteArticle) 41 | ]) 42 | } 43 | 44 | export default articleSaga -------------------------------------------------------------------------------- /src/views/Community/action.js: -------------------------------------------------------------------------------- 1 | import { 2 | MESSAGE_FETCH_LIST, 3 | MESSAGE_FETCH_DELETE, 4 | MESSAGE_FETCH_STATUS 5 | } from './constants' 6 | 7 | export const fetchList = (params, resolve) => { 8 | return { 9 | type: MESSAGE_FETCH_LIST, 10 | payload: { 11 | params, 12 | resolve 13 | } 14 | } 15 | } 16 | export const fetchMessageDelete = (ids, resolve) => { 17 | return { 18 | type: MESSAGE_FETCH_DELETE, 19 | payload: { 20 | ids, 21 | resolve 22 | } 23 | } 24 | } 25 | export const fetchMessageStatus = (ids, resolve) => { 26 | return { 27 | type: MESSAGE_FETCH_STATUS, 28 | payload: { 29 | ids, 30 | status: 0, 31 | resolve 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/views/Community/api.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | // 获取消息列表 4 | export const requestMessageList = (params) => { 5 | const { $http } = React 6 | return $http.get('message', {params}).then(res => { 7 | return res 8 | }) 9 | } 10 | // 删除消息 11 | export const requestMessageDelete = (params) => { 12 | const { $http } = React 13 | return $http.delete('message/delete', {data: params}).then(res => { 14 | return res 15 | }) 16 | } 17 | // 修改消息状态 18 | export const requestMessageStatus = (params) => { 19 | const { $http } = React 20 | return $http.put('message/read', params).then(res => { 21 | return res 22 | }) 23 | } -------------------------------------------------------------------------------- /src/views/Community/constants.js: -------------------------------------------------------------------------------- 1 | export const MESSAGE_FETCH_LIST = 'MESSAGE_FETCH_LIST' 2 | export const MESSAGE_FETCH_SUCCEEDED = 'MESSAGE_FETCH_SUCCEEDED' 3 | export const MESSAGE_FETCH_FAILED = 'MESSAGE_FETCH_FAILED' 4 | export const MESSAGE_FETCH_DELETE = 'MESSAGE_FETCH_DELETE' 5 | export const MESSAGE_FETCH_STATUS = 'MESSAGE_FETCH_STATUS' 6 | export const MESSAGE_FETCH_DELETE_FAILED = 'MESSAGE_FETCH_DELETE_FAILED' 7 | export const MESSAGE_FETCH_STATUS_FAILED = 'MESSAGE_FETCH_STATUS_FAILED' -------------------------------------------------------------------------------- /src/views/Community/message.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { Space, Card, Table, Button, Row, Col, Select, Form, message } from 'antd' 4 | 5 | import { 6 | EditOutlined, 7 | DeleteOutlined, 8 | } from '@ant-design/icons' 9 | import { 10 | fetchList, 11 | fetchMessageDelete, 12 | fetchMessageStatus 13 | } from './action' 14 | 15 | const columns = [ 16 | { title: '消息内容', dataIndex: 'content' }, 17 | { title: '状态', dataIndex: 'status', render: (text) => { 18 | const styleClass = text === 0 ? 'text-green' : 'text-light-red' 19 | return ( 20 | {text === 0 ? '已读' : '未读'} 21 | ) 22 | }}, 23 | { title: '时间', dataIndex: 'createDate'}, 24 | ] 25 | 26 | class Message extends React.Component { 27 | constructor(props) { 28 | super(props) 29 | this.state = { 30 | status: -1, 31 | loading: true, 32 | selectedRowKeys: [], 33 | control: { 34 | delete: { 35 | message: '删除成功', 36 | fn: props.onMessageDelete 37 | }, 38 | read: { 39 | message: '标记成功', 40 | fn: props.onMessageStatus 41 | } 42 | } 43 | } 44 | } 45 | 46 | formRef = React.createRef() 47 | 48 | componentDidMount() { 49 | const { onLoadMessageList, pagination = {} } = this.props 50 | onLoadMessageList({ 51 | status: this.state.status, 52 | page: pagination.current, 53 | pageSize: pagination.pageSize 54 | }, () => { 55 | this.setState({loading: false}) 56 | }) 57 | } 58 | onSelectedChange = (selectedRowKeys) => { 59 | this.setState({ selectedRowKeys }) 60 | } 61 | onSearch = values => { 62 | const { onLoadMessageList, pagination = {} } = this.props 63 | this.setState({loading: true}) 64 | onLoadMessageList({ 65 | status: values.status, 66 | page: pagination.current, 67 | pageSize: pagination.pageSize 68 | }, () => { 69 | this.setState({loading: false}) 70 | }) 71 | } 72 | paginationChange = (pagination) => { 73 | const { onLoadMessageList } = this.props 74 | this.setState({loading: true}) 75 | onLoadMessageList({ 76 | status: this.state.status, 77 | page: pagination.current, 78 | pageSize: pagination.pageSize 79 | }, () => { 80 | this.setState({loading: false}) 81 | }) 82 | } 83 | onControl = (type) => { 84 | const { selectedRowKeys, control, status } = this.state 85 | const { onLoadMessageList, pagination } = this.props 86 | if (!selectedRowKeys.length) { 87 | return message.error('请先选择消息!') 88 | } 89 | control[type].fn(selectedRowKeys, () => { 90 | message.success(control[type].message) 91 | this.setState({ selectedRowKeys: [] }, () => { 92 | this.setState({loading: true}) 93 | onLoadMessageList({ 94 | status: status, 95 | page: pagination.current, 96 | pageSize: pagination.pageSize 97 | }, () => { 98 | this.setState({loading: false}) 99 | })} 100 | ) 101 | }) 102 | } 103 | 104 | render() { 105 | const { list, pagination } = this.props 106 | const { selectedRowKeys, loading, status } = this.state 107 | const rowSelection = { 108 | selectedRowKeys, 109 | onChange: this.onSelectedChange 110 | } 111 | return ( 112 | 113 |
116 | 117 |
118 | 122 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
142 | 143 | ) 144 | } 145 | } 146 | 147 | function mapStateToProps(state) { 148 | return { 149 | list: state.messageReducer.list, 150 | pagination: state.messageReducer.pagination 151 | } 152 | } 153 | 154 | function mapDispatchToProps(dispatch) { 155 | return { 156 | onLoadMessageList: (query, resolve) => dispatch(fetchList(query, resolve)), 157 | onMessageDelete: (ids, resolve) => dispatch(fetchMessageDelete(ids, resolve)), 158 | onMessageStatus: (ids, resolve) => dispatch(fetchMessageStatus(ids, resolve)), 159 | } 160 | } 161 | 162 | export default connect(mapStateToProps, mapDispatchToProps)(Message) -------------------------------------------------------------------------------- /src/views/Community/reducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | MESSAGE_FETCH_SUCCEEDED, 3 | MESSAGE_FETCH_FAILED 4 | } from './constants' 5 | 6 | const initialState = { 7 | list: [], 8 | pagination: { 9 | current: 1, 10 | pageSize: 10, 11 | total: 0 12 | } 13 | } 14 | 15 | export default (state = initialState, action) => { 16 | switch(action.type) { 17 | case MESSAGE_FETCH_SUCCEEDED: 18 | const { list, ...pagination } = action.result || {} 19 | const { page, ...args } = pagination 20 | return { ...state, list, pagination: {current: page, ...args}} 21 | case MESSAGE_FETCH_FAILED: 22 | return state 23 | default: 24 | return state 25 | } 26 | } -------------------------------------------------------------------------------- /src/views/Community/sagas.js: -------------------------------------------------------------------------------- 1 | import { put, call, takeEvery, all } from 'redux-saga/effects' 2 | import { 3 | requestMessageList, 4 | requestMessageDelete, 5 | requestMessageStatus 6 | } from './api' 7 | 8 | import { 9 | MESSAGE_FETCH_LIST, 10 | MESSAGE_FETCH_SUCCEEDED, 11 | MESSAGE_FETCH_FAILED, 12 | MESSAGE_FETCH_DELETE, 13 | MESSAGE_FETCH_DELETE_FAILED, 14 | MESSAGE_FETCH_STATUS, 15 | MESSAGE_FETCH_STATUS_FAILED 16 | } from './constants' 17 | 18 | function* fetchMessageList({ payload }) { 19 | try { 20 | const result = yield call(requestMessageList, payload.params) 21 | yield put({ type: MESSAGE_FETCH_SUCCEEDED, result }) 22 | payload.resolve() 23 | } catch (error) { 24 | yield put({ type: MESSAGE_FETCH_FAILED, message: error.message}) 25 | } 26 | } 27 | 28 | function* deleteMessage({ payload }) { 29 | try { 30 | const { ids, resolve } = payload 31 | const result = yield call(requestMessageDelete, { ids }) 32 | resolve(result) 33 | } catch (error) { 34 | yield put({ type: MESSAGE_FETCH_DELETE_FAILED, message: error.message}) 35 | } 36 | } 37 | 38 | function* modifyMessageStatus({ payload }) { 39 | try { 40 | const { ids, status, resolve } = payload 41 | const result = yield call(requestMessageStatus, { ids, status }) 42 | resolve(result) 43 | } catch (error) { 44 | yield put({ type: MESSAGE_FETCH_STATUS_FAILED, message: error.message}) 45 | } 46 | } 47 | 48 | const messageSaga = function* () { 49 | yield all([ 50 | takeEvery(MESSAGE_FETCH_LIST, fetchMessageList), 51 | takeEvery(MESSAGE_FETCH_DELETE, deleteMessage), 52 | takeEvery(MESSAGE_FETCH_STATUS, modifyMessageStatus), 53 | ]) 54 | } 55 | 56 | export default messageSaga -------------------------------------------------------------------------------- /src/views/Home/constants.js: -------------------------------------------------------------------------------- 1 | export const HEAD_BANNER = [ 2 | { 3 | key: 'week', 4 | title: '访问量', 5 | tagColor: '#1E9FFF', 6 | tagValue: '周', 7 | currency: 0, 8 | aliasCurrency: 'visit', 9 | desc: '总计访问量', 10 | count: '3.2万', 11 | icon: 'Flag' 12 | }, 13 | { 14 | key: 'month', 15 | title: '下载', 16 | tagColor: '#2F4056', 17 | tagValue: '月', 18 | currency: 0, 19 | aliasCurrency: 'download', 20 | desc: '新下载', 21 | count: '10%', 22 | icon: 'Smile' 23 | }, 24 | { 25 | key: 'year', 26 | title: '收入', 27 | tagColor: '#009688', 28 | tagValue: '年', 29 | currency: 0, 30 | aliasCurrency: 'income', 31 | desc: '总收入', 32 | count: '***', 33 | icon: 'Pound' 34 | }, 35 | { 36 | key: 'user', 37 | title: '活跃用户', 38 | tagColor: '#FFB800', 39 | tagValue: '月', 40 | currency: 0, 41 | aliasCurrency: 'activeUser', 42 | desc: '最近一个月', 43 | count: '15%', 44 | icon: 'User' 45 | } 46 | ] 47 | 48 | export const CHART_OPTION = { 49 | color: ['#009688', '#1f9fff', '#5eb878'], 50 | tooltip: { 51 | trigger: 'axis', 52 | axisPointer: { 53 | type: 'cross' 54 | } 55 | }, 56 | legend: { 57 | data: ["访问量","下载量","平均访问量"] 58 | }, 59 | grid: { 60 | left: '3%', 61 | right: '3%', 62 | bottom: '10%', 63 | containLabel: true 64 | }, 65 | xAxis: [ 66 | { 67 | type: 'category', 68 | boundaryGap: true, 69 | axisTick: { 70 | show: false, 71 | }, 72 | axisLine: { 73 | lineStyle: { 74 | color: '#009688', 75 | width: 2 76 | } 77 | }, 78 | splitLine: { 79 | show: true, 80 | lineStyle: { 81 | opacity: 0.3 82 | } 83 | }, 84 | axisLabel: { 85 | color: '#333' 86 | }, 87 | data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] 88 | } 89 | ], 90 | yAxis: [ 91 | { 92 | type: 'value', 93 | name: '访问量', 94 | min: 0, 95 | max: 1500, 96 | position: 'left', 97 | axisTick: { 98 | show: false, 99 | }, 100 | axisLine: { 101 | lineStyle: { 102 | color: '#009688', 103 | width: 2 104 | } 105 | }, 106 | splitLine: { 107 | show: true, 108 | lineStyle: { 109 | opacity: 0.3 110 | } 111 | }, 112 | axisLabel: { 113 | color: '#333', 114 | formatter: '{value} 万' 115 | } 116 | }, 117 | { 118 | type: 'value', 119 | name: '下载量', 120 | min: 0, 121 | max: 1250, 122 | position: 'right', 123 | axisTick: { 124 | show: false, 125 | }, 126 | axisLine: { 127 | lineStyle: { 128 | color: '#009688', 129 | width: 2 130 | } 131 | }, 132 | splitLine: { 133 | show: true, 134 | lineStyle: { 135 | opacity: 0.3 136 | } 137 | }, 138 | axisLabel: { 139 | color: '#333', 140 | formatter: '{value} 万' 141 | } 142 | }, 143 | ], 144 | series: [ 145 | { 146 | name: '访问量', 147 | type: 'line', 148 | position: 'left', 149 | smooth: true, 150 | axisLabel: { 151 | formatter: '{value} ml' 152 | }, 153 | data: [900, 850, 950, 1000, 1100, 1050, 1000, 1150, 1250, 1300, 1330, 1250] 154 | }, 155 | { 156 | name: '下载量', 157 | type: 'line', 158 | position: 'right', 159 | smooth: true, 160 | data: [1000, 1050, 950, 1150, 1200, 1250, 1200, 1105, 1350, 1450, 1380, 1300] 161 | }, 162 | { 163 | name: '平均访问量', 164 | type: 'line', 165 | smooth: true, 166 | data: [870, 850, 850, 950, 1050, 1050, 1000, 980, 1150, 1000, 1000, 1150] 167 | }, 168 | ] 169 | } -------------------------------------------------------------------------------- /src/views/Home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Card, Col, Row, Tag, Space, Progress, Radio, Table } from 'antd' 3 | import { 4 | FlagOutlined, 5 | SmileOutlined, 6 | PoundOutlined, 7 | UserOutlined, 8 | EllipsisOutlined, 9 | LikeOutlined, 10 | ClockCircleOutlined 11 | } from '@ant-design/icons'; 12 | 13 | import Echarts from 'echarts' 14 | import 'echarts/lib/chart/line' 15 | import 'echarts/lib/component/tooltip' 16 | import 'echarts/lib/component/title' 17 | 18 | import { formatCurrency } from '@/utils' 19 | import style from './style.module.scss' 20 | 21 | import { HEAD_BANNER, CHART_OPTION } from './constants' 22 | 23 | const { $http } = React 24 | const UserColumns = [ 25 | { 26 | title: '用户名', 27 | dataIndex: 'username', 28 | key: 'username', 29 | render: (text, record, index) => { 30 | switch(index) { 31 | case 0: 32 | return {text} 33 | case 1: 34 | return {text} 35 | case 2: 36 | return {text} 37 | default: 38 | return {text} 39 | } 40 | } 41 | }, 42 | { 43 | title: '最后登录时间', 44 | dataIndex: 'lastLoginTime', 45 | key: 'lastLoginTime', 46 | render: (text) => { 47 | return ( 48 | 49 | 50 | {text} 51 | 52 | ) 53 | } 54 | }, 55 | { 56 | title: '状态', 57 | dataIndex: 'status', 58 | key: 'status', 59 | render: (text, record) => { 60 | return ( 61 | 62 | {record.status === 0 ? '离线' : '在线'} 63 | 64 | ) 65 | } 66 | }, 67 | { 68 | title: '获得赞', 69 | dataIndex: 'like', 70 | key: 'like', 71 | render: (text, record) => { 72 | return ( 73 | 74 | {record.like} 75 | 76 | 77 | ) 78 | } 79 | } 80 | ] 81 | 82 | const TaskColumns = [ 83 | { 84 | title: '任务', 85 | dataIndex: 'taskName', 86 | key: 'taskName', 87 | render: (text, record, index) => { 88 | switch(index) { 89 | case 0: 90 | return {text} 91 | case 1: 92 | return {text} 93 | case 2: 94 | return {text} 95 | default: 96 | return {text} 97 | } 98 | } 99 | }, 100 | { 101 | title: '所需时间', 102 | dataIndex: 'taskTime', 103 | key: 'taskTime', 104 | }, 105 | { 106 | title: '完成状态', 107 | dataIndex: 'taskStatus', 108 | key: 'taskStatus', 109 | render: (text, record, index) => { 110 | const status = record.taskStatus === 0 ? '已完成' : record.taskStatus === 1 ? '进行中' : '未开始' 111 | switch(text) { 112 | case 0: 113 | return {status} 114 | case 1: 115 | return {status} 116 | case 2: 117 | return {status} 118 | default: 119 | return {status} 120 | } 121 | } 122 | }, 123 | ] 124 | 125 | class Home extends React.Component { 126 | constructor() { 127 | super() 128 | this.state = { 129 | chart: null, 130 | date: '1', 131 | headerBanner: { 132 | visit: [0, '0万'], 133 | download: [0, '0%'], 134 | income: [0, '***'], 135 | activeUser: [0, '0%'] 136 | }, 137 | chartData: {}, 138 | userTableData: [], 139 | taskTableData: [] 140 | } 141 | } 142 | 143 | componentDidMount() { 144 | // 顶部统计 145 | $http.get('count').then(res => { 146 | this.setState({ 147 | headerBanner: res 148 | }) 149 | }) 150 | // 图表 151 | this.chart = Echarts.init(document.getElementById('chart')) 152 | this.handleGetChart() 153 | // 表格 154 | $http.get('user/list').then(res => { 155 | const list = res.list.slice(0, 7) 156 | this.setState({userTableData: list}) 157 | }) 158 | $http.get('task/list').then(res => { 159 | const list = res.list.slice(0, 7) 160 | this.setState({taskTableData: list}) 161 | }) 162 | } 163 | 164 | showIcon = (type) => { 165 | switch(type) { 166 | case 'Flag': 167 | return 168 | case 'Smile': 169 | return 170 | case 'Pound': 171 | return 172 | case 'User': 173 | return 174 | default: 175 | return 176 | } 177 | } 178 | 179 | handleGetChart = () => { 180 | this.chart.showLoading({ color: '#5FB878'}) 181 | $http.get('visit/chart').then(res => { 182 | this.chart.hideLoading() 183 | this.setState({ chartData: res }) 184 | CHART_OPTION.series[0].data = res.visit 185 | CHART_OPTION.series[1].data = res.download 186 | CHART_OPTION.series[2].data = res.averageVisits 187 | 188 | this.chart.setOption(CHART_OPTION) 189 | }).catch(() => { 190 | this.chart.hideLoading() 191 | }) 192 | } 193 | handleYearChange = (e) => { 194 | this.setState({date: e.target.value}) 195 | this.handleGetChart() 196 | } 197 | 198 | render() { 199 | const { headerBanner, chartData, userTableData, taskTableData } = this.state 200 | return ( 201 |
202 |
203 | 204 | {HEAD_BANNER.map(item => ( 205 |
206 | {item.tagValue}}> 207 |

{formatCurrency(headerBanner[item.aliasCurrency][0])}

208 | 209 |
210 | {item.desc} 211 | 212 | 213 | 214 | {headerBanner[item.aliasCurrency][1]} 215 | {this.showIcon(item.icon)} 216 | 217 | 218 | 219 | 220 | 221 | ))} 222 | 223 | 224 |
225 | 227 | 今年 228 | 去年 229 | 230 | }> 231 | 232 |
233 |
234 | 235 | 236 |
237 |

月访问数

238 |

同上期增长

239 | 240 |
241 |
242 |

月下载数

243 |

同上期增长

244 | 245 |
246 |
247 |

月收入

248 |

同上期增长

249 | 250 |
251 | 252 | 253 | 254 | 255 |
256 | 257 |
258 | 259 |
260 | 261 | 262 | 263 | 264 |
265 | 266 | 267 | 268 | 269 | 270 | ) 271 | } 272 | } 273 | 274 | export default Home -------------------------------------------------------------------------------- /src/views/Home/style.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style//variables'; 2 | 3 | .page-home { 4 | 5 | 6 | :global { 7 | 8 | .ant-card-head { 9 | padding: 0 15px; 10 | min-height: 42px; 11 | font-size: map-get($fontSizes, '14'); 12 | } 13 | .ant-card-head-title { 14 | padding: 10px 0; 15 | } 16 | .ant-card-extra { 17 | padding: 10px 0; 18 | font-size: map-get($fontSizes, '12'); 19 | } 20 | .ant-tag { 21 | margin-right: 0; 22 | transform: scale(0.9); 23 | } 24 | .ant-card-body { 25 | padding: 15px; 26 | } 27 | } 28 | 29 | .count { 30 | font-size: map-get($fontSizes, '36'); 31 | color: #666; 32 | line-height: 36px; 33 | padding: 5px 0 10px; 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | word-break: break-all; 37 | white-space: nowrap; 38 | } 39 | 40 | .chart-title { 41 | line-height: map-get($spaces, '20'); 42 | } 43 | .table-border { 44 | border: 1px solid map-get($colors, 'light-grey-1');; 45 | } 46 | } -------------------------------------------------------------------------------- /src/views/Layout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { HashRouter } from 'react-router-dom' 3 | import { renderRoutes } from 'react-router-config' 4 | 5 | import { Layout, Menu, Breadcrumb, Avatar, Dropdown, Button } from 'antd' 6 | import { 7 | MenuUnfoldOutlined, 8 | MenuFoldOutlined, 9 | UserOutlined, 10 | HomeOutlined, 11 | SettingOutlined, 12 | CodepenCircleOutlined, 13 | DownOutlined, 14 | ReadOutlined, 15 | FileTextOutlined 16 | } from '@ant-design/icons'; 17 | const { Header, Sider, Content } = Layout 18 | const { $http } = React 19 | const style = require('./index.module.scss') 20 | 21 | const mapMenu = [ 22 | { key: 'home', name: '首页' }, 23 | { key: 'user', name: '用户列表', parentName: '用户管理', parentKey: 'user-menu' }, 24 | { key: 'roles', name: '角色管理', parentName: '用户管理', parentKey: 'user-menu' }, 25 | { key: 'articles', name: '文章列表', parentName: '文章管理', parentKey: 'article-menu' }, 26 | { key: 'category', name: '文章分类', parentName: '文章管理', parentKey: 'article-menu' }, 27 | { key: 'comments', name: '文章评论', parentName: '文章管理', parentKey: 'article-menu' }, 28 | { key: 'message', name: '消息中心', parentName: '社区管理', parentKey: 'community' }, 29 | { key: 'website-setting', name: '网站设置', parentName: '设置管理', parentKey: 'setting-menu' }, 30 | { key: 'email-service', name: '邮件服务', parentName: '设置管理', parentKey: 'setting-menu' }, 31 | { key: 'basic-info', name: '基本资料', parentName: '设置管理', parentKey: 'setting-menu' }, 32 | { key: 'modify-password', name: '修改密码', parentName: '设置管理', parentKey: 'setting-menu' }, 33 | ] 34 | 35 | class LayoutContainer extends React.Component { 36 | constructor(props) { 37 | super(props) 38 | const currentPath = this.splitPath() 39 | const openKeys = this.handleFindOpenMenu(currentPath) 40 | const userInfo = JSON.parse(sessionStorage.getItem('userInfo')) || {} 41 | this.state = { 42 | collapsed: false, 43 | title: '后台管理系统', 44 | selectedKeys: [currentPath], 45 | defaultOpenKeys: [openKeys.menuKey], 46 | breadcrumb: openKeys.breadcrumb, 47 | userInfo 48 | } 49 | } 50 | 51 | splitPath = () => { 52 | const { location } = this.props 53 | return location.pathname.substr(1) 54 | } 55 | toggle = () => { 56 | this.setState({ 57 | collapsed: !this.state.collapsed, 58 | title: this.state.collapsed ? '后台管理系统' : '' 59 | }) 60 | } 61 | handleFindOpenMenu = (selectedKeys) => { 62 | const findMenu = mapMenu.find(subMenu => subMenu.key === selectedKeys) 63 | let breadcrumb = [] 64 | breadcrumb.push(findMenu.parentName, findMenu.name) 65 | breadcrumb = breadcrumb.filter(v => v) 66 | return { 67 | menuKey: findMenu && findMenu.parentKey, 68 | subMenu: findMenu && findMenu.key, 69 | breadcrumb 70 | } 71 | } 72 | handleMenuClick = (item) => { 73 | const { history } = this.props 74 | const { key } = item 75 | if (key === 'logout') { 76 | $http.get('logout').then(() => { 77 | sessionStorage.clear() 78 | history.push('/login') 79 | }) 80 | } else { 81 | this.setState({ 82 | selectedKeys: [item.key] 83 | }, () => { 84 | history.push(item.key) 85 | }) 86 | } 87 | } 88 | handleRouter = (item) => { 89 | const { history } = this.props 90 | const findMenu = mapMenu.find(subMenu => subMenu.key === item.key) 91 | let breadcrumb = [] 92 | breadcrumb.push(findMenu.parentName, findMenu.name) 93 | breadcrumb = breadcrumb.filter(v => v) 94 | this.setState({ 95 | selectedKeys: [item.key], 96 | breadcrumb 97 | }, () => { 98 | history.push(item.key) 99 | }) 100 | } 101 | 102 | render() { 103 | const { route } = this.props 104 | const { collapsed, title, selectedKeys, userInfo, defaultOpenKeys, breadcrumb } = this.state 105 | 106 | const userDropdownMenu = ( 107 | 108 | 基本资料 109 | 修改密码 110 | 111 | 退出 112 | 113 | ) 114 | return ( 115 | 116 | 117 | 118 |
119 | 120 | {title} 121 |
122 | 126 | }> 127 | 首页 128 | 129 | 133 | 134 | 用户管理 135 | 136 | } 137 | > 138 | 用户列表 139 | 角色管理 140 | 141 | 145 | 146 | 文章管理 147 | 148 | } 149 | > 150 | 文章列表 151 | 文章分类 152 | 文章评论 153 | 154 | 158 | 159 | 社区管理 160 | 161 | } 162 | > 163 | 消息中心 164 | 165 | 169 | 170 | 设置管理 171 | 172 | } 173 | > 174 | 175 | 网站设置 176 | 邮件服务 177 | 178 | 179 | 基本资料 180 | 修改密码 181 | 182 | 183 | 184 |
185 | 186 |
187 | {React.createElement(this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, { 188 | className: style['trigger'], 189 | onClick: this.toggle, 190 | })} 191 |
192 |
193 | 194 | document.getElementsByClassName('info')[0]}> 197 | 200 | 201 |
202 |
203 |
204 | 205 | 206 | {breadcrumb.map((item, index) => {item})} 207 | 208 |
209 | {renderRoutes(route.routes)} 210 |
211 |
212 |
213 |
214 |
215 | ) 216 | } 217 | } 218 | 219 | export default LayoutContainer 220 | -------------------------------------------------------------------------------- /src/views/Layout/index.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style//variables'; 2 | 3 | .layout-container { 4 | height: 100%; 5 | 6 | .logo { 7 | margin: 16px; 8 | height: 32px; 9 | line-height: 32px; 10 | color: map-get($colors, 'white'); 11 | 12 | .icon { 13 | vertical-align: middle; 14 | font-size: map-get($fontSizes, '30'); 15 | margin-right: 5px; 16 | } 17 | } 18 | .trigger { 19 | font-size: map-get($fontSizes, '18');; 20 | line-height: 64px; 21 | padding: 0 24px; 22 | cursor: pointer; 23 | transition: color 0.3s; 24 | 25 | &:hover { 26 | color: #1890ff; 27 | } 28 | } 29 | 30 | .header-right { 31 | float: right; 32 | position: relative; 33 | } 34 | .btn-user { 35 | padding: 4px 8px; 36 | } 37 | .layout-header { 38 | padding: 0; 39 | border: 1px solid map-get($colors, 'light-grey'); 40 | } 41 | .layout-nav { 42 | height: 50px; 43 | line-height: 50px; 44 | padding: 0 16px; 45 | background-color: map-get($colors, 'white'); 46 | box-shadow: 0 1px 2px 0 rgba(0,0,0,.05); 47 | } 48 | .layout-content--info { 49 | padding: 15px; 50 | min-height: calc(100% - 86px); 51 | } 52 | } -------------------------------------------------------------------------------- /src/views/Login/forget.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | import { message } from 'antd' 5 | import './style2.scss' 6 | 7 | const { $http } = React 8 | const PhoneRegexp = /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/ 9 | 10 | class Forget extends React.Component { 11 | constructor(){ 12 | super() 13 | this.state = { 14 | validateForm: { 15 | phone: '', 16 | imgCode: '', 17 | code: '' 18 | }, 19 | form: { 20 | password: '', 21 | repeatPassword: '' 22 | }, 23 | formType: 'validate', 24 | codeText: '获取验证码', 25 | disabled: false 26 | } 27 | } 28 | 29 | handleNext = (event) => { 30 | event.preventDefault() 31 | const { validateForm } = this.state 32 | if (!validateForm.phone) { 33 | return message.error('请输入手机号码') 34 | } 35 | if (!validateForm.imgCode) { 36 | return message.error('请输入图形验证码') 37 | } 38 | if (!validateForm.code) { 39 | return message.error('请输入验证码') 40 | } 41 | this.setState({ 42 | formType: 'password', 43 | validateForm: { 44 | ...validateForm, 45 | code: '' 46 | } 47 | }) 48 | } 49 | handlePrev = (event) => { 50 | event.preventDefault() 51 | this.setState({ 52 | formType: 'validate', 53 | form: { 54 | password: '', 55 | repeatPassword: '' 56 | } 57 | }) 58 | } 59 | handleValidate = (e) => { 60 | const value = e.target.value 61 | if (value && !PhoneRegexp.test(value)) { 62 | message.error('手机号码格式不正确') 63 | } 64 | } 65 | handleInputChange = (event, formType, name) => { 66 | const { validateForm, form } = this.state 67 | const value = event.target.value 68 | if (formType === 'validate') { 69 | validateForm[name] = value 70 | this.setState({ validateForm }) 71 | } else { 72 | form[name] = value 73 | this.setState({ form }) 74 | } 75 | } 76 | // 验证码已发送到手机,请注意查收 77 | handleGetCode = () => { 78 | if (!this.state.validateForm.phone || !PhoneRegexp.test(this.state.validateForm.phone)) { 79 | return message.error('请输入正确手机号码') 80 | } 81 | let s = 59 82 | let timer = null 83 | message.success('验证码已发送到手机,请注意查收') 84 | this.setState({ 85 | disabled: true, 86 | codeText: `${s}秒后重新获取` 87 | }, () => { 88 | timer = setInterval(() => { 89 | s -= 1 90 | this.setState({ 91 | codeText: `${s}秒后重新获取` 92 | }) 93 | if (s === 0) { 94 | clearInterval(timer) 95 | this.setState({ 96 | codeText: '获取验证码', 97 | disabled: false 98 | }) 99 | } 100 | }, 1000) 101 | }) 102 | // 获取验证码 103 | $http.get('send/code', { params: {phone: this.state.validateForm.phone}}).then(res => { 104 | clearInterval(timer) 105 | this.setState({ 106 | validateForm: {...this.state.validateForm, code: res.code}, 107 | codeText: '获取验证码', 108 | disabled: false 109 | }) 110 | }) 111 | } 112 | handleResetPassword = (event) => { 113 | event.preventDefault() 114 | const { form } = this.state 115 | if (!form.password) { 116 | return message.error('请输入密码') 117 | } 118 | if (!form.repeatPassword) { 119 | return message.error('请输入确认密码') 120 | } 121 | $http.post('reset/password', {form}).then(() => { 122 | message.success('密码重置成功,请到重新登录账号') 123 | setTimeout(() => { 124 | this.props.history.replace('/login') 125 | }, 1500) 126 | }) 127 | } 128 | 129 | render() { 130 | const { formType, validateForm, codeText, disabled, form } = this.state 131 | return ( 132 |
133 |
134 |
135 |
136 |

忘记密码

137 | this.handleInputChange(event, 'validate', 'phone')} name="phone" placeholder="请输入手机号码" /> 138 |
139 | this.handleInputChange(event, 'validate', 'imgCode')} name="imgCode" placeholder="图形验证码" /> 140 | code 141 |
142 |
143 | this.handleInputChange(event, 'validate', 'code')} name="code" placeholder="短信验证码" /> 144 | 145 |
146 | 147 | 已有账号,去登录 148 | 149 |
150 |
151 |
152 |

设置密码

153 | this.handleInputChange(event, 'form', 'password')} name="phone" placeholder="请输入密码" /> 154 | this.handleInputChange(event, 'from', 'repeatPassword')} name="code" placeholder="请确认密码" /> 155 | 156 | 157 | 158 |
159 |
160 |
161 | ) 162 | } 163 | } 164 | 165 | export default Forget -------------------------------------------------------------------------------- /src/views/Login/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Card, Form, Input, Button, Space, message } from 'antd' 3 | 4 | import style from './style.module.scss' 5 | 6 | const { $http } = React 7 | const layout = { 8 | labelCol: { span: 4 }, 9 | wrapperCol: { span: 16 } 10 | } 11 | 12 | const footerLayout = { 13 | wrapperCol: { 14 | offset: 4, 15 | span: 16 16 | } 17 | } 18 | 19 | class Login extends React.Component { 20 | constructor() { 21 | super() 22 | this.state = { 23 | form: { 24 | username: '', 25 | password: '', 26 | validateCode: '' 27 | } 28 | } 29 | } 30 | 31 | handleSubmit = (values) => { 32 | $http.post('login', values).then(res => { 33 | const { token, ...user } = res 34 | sessionStorage.setItem('token', res.token) 35 | sessionStorage.setItem('userInfo', JSON.stringify(user)) 36 | message.success('登录成功') 37 | this.props.history.push('/home') 38 | }) 39 | } 40 | 41 | render() { 42 | return ( 43 |
44 | 45 |
46 | ({ 49 | validator(rule, value) { 50 | if (!value) { 51 | return Promise.reject('请输入用户名') 52 | } 53 | return Promise.resolve() 54 | } 55 | }) 56 | ]}> 57 | 58 | 59 | ({ 62 | validator(rule, value) { 63 | if (!value) { 64 | return Promise.reject('请输入密码') 65 | } 66 | return Promise.resolve() 67 | } 68 | }) 69 | ]}> 70 | 71 | 72 | ({ 75 | validator(rule, value) { 76 | if (!value) { 77 | return Promise.reject('请输入验证码') 78 | } 79 | return Promise.resolve() 80 | } 81 | }) 82 | ]}> 83 | 85 | } /> 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 |
95 | ) 96 | } 97 | } 98 | 99 | export default Login -------------------------------------------------------------------------------- /src/views/Login/login2.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | import { message, notification } from 'antd' 5 | import { 6 | LoadingOutlined 7 | } from '@ant-design/icons'; 8 | import './style2.scss' 9 | 10 | const { $http } = React 11 | 12 | class Login2 extends React.Component { 13 | constructor() { 14 | super() 15 | this.state = { 16 | loginType: true, 17 | loading: false, 18 | loginForm: { 19 | username: '', 20 | password: '' 21 | }, 22 | registerForm: { 23 | username: '', 24 | email: '', 25 | password: '' 26 | } 27 | } 28 | } 29 | 30 | toggleClass = () => { 31 | this.setState((state) => { 32 | if (state.loginType) { 33 | return { 34 | loginType: state.loginType, 35 | registerForm: { 36 | username: '', 37 | email: '', 38 | password: '' 39 | } 40 | } 41 | } 42 | return { loginType: state.loginType } 43 | }) 44 | this.setState({ loginType: !this.state.loginType }) 45 | } 46 | handleInputChange = (event, formType, labelName) => { 47 | const { loginForm, registerForm } = this.state 48 | if (formType === 'register') { 49 | registerForm[labelName] = event.target.value 50 | this.setState({ registerForm }) 51 | } else { 52 | loginForm[labelName] = event.target.value 53 | this.setState({ 54 | loginForm, 55 | registerForm: { 56 | username: '', 57 | email: '', 58 | password: '' 59 | } 60 | }) 61 | } 62 | } 63 | handleLogin = (event) => { 64 | event.preventDefault() 65 | const { loginForm } = this.state 66 | if (!loginForm.username) { 67 | return message.error('用户名不能为空') 68 | } 69 | if (!loginForm.password) { 70 | return message.error('密码不能为空') 71 | } 72 | this.setState({loading: true}) 73 | $http.post('login', { ...loginForm }).then((res) => { 74 | const { token, ...user } = res 75 | sessionStorage.setItem('token', res.token) 76 | sessionStorage.setItem('userInfo', JSON.stringify(user)) 77 | message.success('登录成功') 78 | this.setState({loading: false}) 79 | this.props.history.push('/home') 80 | }) 81 | } 82 | handleRegister = (event) => { 83 | event.preventDefault() 84 | const { registerForm } = this.state 85 | if (!registerForm.username) { 86 | return message.error('用户名不能为空') 87 | } 88 | if (!registerForm.email) { 89 | return message.error('邮箱不能为空') 90 | } 91 | if (!registerForm.password) { 92 | return message.error('密码不能为空') 93 | } 94 | $http.post('register', { ...registerForm }).then(() => { 95 | notification.success({ 96 | key: 'register', 97 | message: '注册成功!', 98 | description: '账号注册成功,正在为你登录...', 99 | }) 100 | setTimeout(() => { 101 | // 注册成功后,直接登录 102 | $http.post('login', { 103 | username: registerForm.username, 104 | password: registerForm.password 105 | }).then((res) => { 106 | notification.close('register') 107 | const { token, ...user } = res 108 | sessionStorage.setItem('token', res.token) 109 | sessionStorage.setItem('userInfo', JSON.stringify(user)) 110 | message.success('登录成功') 111 | this.props.history.push('/home') 112 | }) 113 | }, 2000) 114 | }) 115 | } 116 | 117 | render() { 118 | const { loginType, registerForm, loginForm, loading } = this.state 119 | const activeClass = !loginType ? 'right-panel-active' : '' 120 | return ( 121 |
122 |
123 |
124 |
125 |

注册

126 | this.handleInputChange(event, 'register', 'username')} placeholder="用户名" /> 127 | this.handleInputChange(event, 'register', 'email')} placeholder="邮箱" /> 128 | this.handleInputChange(event, 'register', 'password')} placeholder="密码" /> 129 | 130 | 131 |
132 |
133 |
134 |

登录

135 | this.handleInputChange(event, 'login', 'username')} name="username" placeholder="用户名" /> 136 | this.handleInputChange(event, 'login', 'password')} name="password" placeholder="密码" /> 137 | 忘记密码 138 | 141 | 142 |
143 |
144 |
145 |
146 |

欢迎回来!

147 |

请您先登录的个人信息,进行操作。

148 | 149 |
150 |
151 |

注册新账号!

152 |

输入您的个人信息注册账号。

153 | 154 |
155 |
156 |
157 |
158 |
159 | ) 160 | } 161 | } 162 | 163 | export default Login2 -------------------------------------------------------------------------------- /src/views/Login/style.module.scss: -------------------------------------------------------------------------------- 1 | .page-login { 2 | position: relative; 3 | height: 100%; 4 | background-color: #f9f9f9; 5 | 6 | .login-wrapper { 7 | width: 500px; 8 | position: absolute; 9 | top: 40%; 10 | left: 50%; 11 | transform: translate(-50%, -50%); 12 | box-shadow: 0px 5px 10px 0px #eaeaea; 13 | } 14 | 15 | :global(.ant-input-group-addon) { 16 | padding: 0; 17 | } 18 | } -------------------------------------------------------------------------------- /src/views/Login/style2.scss: -------------------------------------------------------------------------------- 1 | .login-wrapper { 2 | background: #f6f5f7; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-direction: column; 7 | font-family: 'Montserrat', sans-serif; 8 | height: 100vh; 9 | margin: -20px 0 50px; 10 | 11 | h1 { 12 | font-size: 2em; 13 | font-weight: bold; 14 | } 15 | 16 | h2 { 17 | text-align: center; 18 | } 19 | 20 | p { 21 | font-size: 14px; 22 | font-weight: 100; 23 | line-height: 20px; 24 | letter-spacing: 0.5px; 25 | margin: 20px 0 30px; 26 | } 27 | 28 | span { 29 | font-size: 12px; 30 | } 31 | 32 | a { 33 | color: #333; 34 | font-size: 14px; 35 | /* text-decoration: none; */ 36 | margin: 15px 0; 37 | } 38 | 39 | button { 40 | border-radius: 20px; 41 | border: 1px solid #eee; 42 | background-color: #fff; 43 | color: #333; 44 | font-size: 12px; 45 | font-weight: bold; 46 | padding: 12px 45px; 47 | letter-spacing: 1px; 48 | text-transform: uppercase; 49 | transition: transform 80ms ease-in; 50 | cursor: pointer; 51 | } 52 | button[data-type="primary"] { 53 | border: 1px solid #1890ff; 54 | background-color: #1890ff; 55 | color: #FFFFFF; 56 | } 57 | 58 | button:active { 59 | transform: scale(0.95); 60 | } 61 | 62 | button:focus { 63 | outline: none; 64 | } 65 | 66 | button.ghost { 67 | background-color: transparent; 68 | border-color: #FFFFFF; 69 | } 70 | 71 | form { 72 | background-color: #FFFFFF; 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-direction: column; 77 | padding: 0 50px; 78 | height: 100%; 79 | text-align: center; 80 | } 81 | 82 | input { 83 | background-color: #eee; 84 | border: none; 85 | padding: 12px 15px; 86 | margin: 8px 0; 87 | width: 100%; 88 | } 89 | 90 | .container { 91 | background-color: #fff; 92 | border-radius: 10px; 93 | box-shadow: 0 14px 28px rgba(0,0,0,0.25), 94 | 0 10px 10px rgba(0,0,0,0.22); 95 | position: relative; 96 | overflow: hidden; 97 | width: 768px; 98 | max-width: 100%; 99 | min-height: 480px; 100 | } 101 | 102 | .form-container { 103 | position: absolute; 104 | top: 0; 105 | height: 100%; 106 | transition: all 0.6s ease-in-out; 107 | } 108 | 109 | .sign-in-container { 110 | left: 0; 111 | width: 50%; 112 | z-index: 2; 113 | } 114 | 115 | .container.right-panel-active .sign-in-container { 116 | transform: translateX(100%); 117 | } 118 | 119 | .sign-up-container { 120 | left: 0; 121 | width: 50%; 122 | opacity: 0; 123 | z-index: 1; 124 | } 125 | 126 | .container.right-panel-active .sign-up-container { 127 | transform: translateX(100%); 128 | opacity: 1; 129 | z-index: 5; 130 | animation: show 0.6s; 131 | } 132 | 133 | @keyframes show { 134 | 0%, 49.99% { 135 | opacity: 0; 136 | z-index: 1; 137 | } 138 | 139 | 50%, 100% { 140 | opacity: 1; 141 | z-index: 5; 142 | } 143 | } 144 | 145 | .overlay-container { 146 | position: absolute; 147 | top: 0; 148 | left: 50%; 149 | width: 50%; 150 | height: 100%; 151 | overflow: hidden; 152 | transition: transform 0.6s ease-in-out; 153 | z-index: 100; 154 | } 155 | 156 | .container.right-panel-active .overlay-container{ 157 | transform: translateX(-100%); 158 | } 159 | 160 | .overlay { 161 | background: #369eff; 162 | background: -webkit-linear-gradient(to right, #1890ff, #369eff); 163 | background: linear-gradient(to right, #1890ff, #369eff); 164 | background-repeat: no-repeat; 165 | background-size: cover; 166 | background-position: 0 0; 167 | color: #FFFFFF; 168 | position: relative; 169 | left: -100%; 170 | height: 100%; 171 | width: 200%; 172 | transform: translateX(0); 173 | transition: transform 0.6s ease-in-out; 174 | } 175 | 176 | .container.right-panel-active .overlay { 177 | transform: translateX(50%); 178 | } 179 | 180 | .overlay-panel { 181 | position: absolute; 182 | display: flex; 183 | align-items: center; 184 | justify-content: center; 185 | flex-direction: column; 186 | padding: 0 40px; 187 | text-align: center; 188 | top: 0; 189 | height: 100%; 190 | width: 50%; 191 | transform: translateX(0); 192 | transition: transform 0.6s ease-in-out; 193 | } 194 | 195 | .overlay-left { 196 | transform: translateX(-20%); 197 | } 198 | 199 | .container.right-panel-active .overlay-left { 200 | transform: translateX(0); 201 | } 202 | 203 | .overlay-right { 204 | right: 0; 205 | transform: translateX(0); 206 | } 207 | 208 | .container.right-panel-active .overlay-right { 209 | transform: translateX(20%); 210 | } 211 | 212 | .social-container { 213 | margin: 20px 0; 214 | } 215 | 216 | .social-container a { 217 | border: 1px solid #DDDDDD; 218 | border-radius: 50%; 219 | display: inline-flex; 220 | justify-content: center; 221 | align-items: center; 222 | margin: 0 5px; 223 | height: 40px; 224 | width: 40px; 225 | } 226 | } 227 | 228 | .forget { 229 | 230 | .container { 231 | width: 480px; 232 | } 233 | .container.right-panel-active .validate-container { 234 | transform: translateX(-100%); 235 | } 236 | .container.right-panel-active .password-container { 237 | left: 0; 238 | } 239 | 240 | .img-code { 241 | align-self: center; 242 | height: 46px; 243 | margin-left: 10px; 244 | border: 1px solid #eee; 245 | } 246 | .code { 247 | width: 194px; 248 | height: 46px; 249 | padding: 0; 250 | align-self: center; 251 | margin-left: 10px; 252 | border-radius: 0; 253 | } 254 | .code-disabled { 255 | color: #d2d2d2; 256 | cursor: not-allowed; 257 | } 258 | .validate-container { 259 | left: 0; 260 | width: 100%; 261 | z-index: 2; 262 | } 263 | .password-container { 264 | left: 100%; 265 | width: 100%; 266 | z-index: 3; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/views/NotFound/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from 'antd' 3 | import style from './style.module.scss' 4 | 5 | const NotFound = () => ( 6 |
7 |
8 |
9 | 404 11 |

12 | 您访问的页面走失了 13 |

14 | 15 |
16 |
17 |
18 | ) 19 | 20 | export default NotFound -------------------------------------------------------------------------------- /src/views/NotFound/style.module.scss: -------------------------------------------------------------------------------- 1 | .page-404 { 2 | 3 | .lost { 4 | height: 80%; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | } 10 | .back { 11 | color: #fff; 12 | background-color: #727d99; 13 | } 14 | } -------------------------------------------------------------------------------- /src/views/Setting/basicInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Card, Form, Input, Button, Select, Space, Radio, Upload, Spin, message } from 'antd' 4 | import { 5 | LoadingOutlined, 6 | PlusOutlined 7 | } from '@ant-design/icons'; 8 | 9 | import { ROLES } from './constants' 10 | import style from './style.module.scss' 11 | 12 | const { $http } = React 13 | 14 | const layout = { 15 | labelCol: { span: 4 }, 16 | wrapperCol: { span: 16 } 17 | } 18 | 19 | const footerLayout = { 20 | wrapperCol: { 21 | offset: 4, 22 | span: 16 23 | } 24 | } 25 | const defaultAvatar = require('@assets/images/default.png') 26 | const EmailRegexp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/ 27 | const PhoneRegexp = /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/ 28 | 29 | 30 | function getBase64(img, callback) { 31 | const reader = new FileReader(); 32 | reader.addEventListener('load', () => callback(reader.result)); 33 | reader.readAsDataURL(img); 34 | } 35 | 36 | function beforeUpload(file) { 37 | const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; 38 | if (!isJpgOrPng) { 39 | message.error('You can only upload JPG/PNG file!'); 40 | } 41 | const isLt2M = file.size / 1024 / 1024 < 2; 42 | if (!isLt2M) { 43 | message.error('Image must smaller than 2MB!'); 44 | } 45 | return isJpgOrPng && isLt2M; 46 | } 47 | 48 | class EmailService extends React.Component { 49 | constructor() { 50 | super() 51 | this.state = { 52 | loading: false, 53 | spinning: true, 54 | imageUrl: defaultAvatar, 55 | form: { 56 | role: '', 57 | username: '', 58 | nickname: '', 59 | sex: 0, 60 | avatar: '', 61 | phone: '', 62 | email: '', 63 | remark: '' 64 | } 65 | } 66 | } 67 | 68 | componentDidMount() { 69 | const userInfo = JSON.parse(sessionStorage.getItem('userInfo')) || {} 70 | $http.get('user/details/' + userInfo.userId).then(res => { 71 | this.setState({imageUrl: res.avatar, spinning: false}) 72 | this.formRef.current.setFieldsValue(res) 73 | }) 74 | } 75 | 76 | formRef = React.createRef() 77 | 78 | handleChange = info => { 79 | if (info.file.status === 'uploading') { 80 | this.setState({ loading: true }); 81 | return; 82 | } 83 | if (info.file.status === 'done') { 84 | // Get this url from response in real world. 85 | getBase64(info.file.originFileObj, imageUrl => 86 | this.setState({ 87 | imageUrl, 88 | loading: false, 89 | form: {...this.state.form, ...{avatar: imageUrl}} 90 | }), 91 | ); 92 | } 93 | } 94 | handleSubmit = values => { 95 | const userInfo = JSON.parse(sessionStorage.getItem('userInfo')) || {} 96 | values.userId = userInfo.userId 97 | this.setState({spinning: true}) 98 | $http.put('user/edit', values).then(res => { 99 | this.setState({ spinning: false}) 100 | message.success('修改成功') 101 | }) 102 | } 103 | handleReset = () => { 104 | this.formRef.current.resetFields() 105 | this.setState({imageUrl: ''}) 106 | } 107 | 108 | render() { 109 | const uploadButton = ( 110 |
111 | {this.state.loading ? : } 112 |
上传
113 |
114 | ); 115 | const { imageUrl, spinning } = this.state 116 | return ( 117 |
118 | 119 | 120 |
121 | 122 | 125 | 130 | 131 | 132 | 当前角色不可更改为其他角色 133 | 134 | 135 | 136 | 139 | 140 | 141 | 142 | 不可修改。一般用于后台登入名 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 164 | {imageUrl ? avatar : uploadButton} 165 | 166 | 167 | 171 | 172 | 173 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 |
190 |
191 |
192 | ) 193 | } 194 | } 195 | 196 | export default EmailService -------------------------------------------------------------------------------- /src/views/Setting/constants.js: -------------------------------------------------------------------------------- 1 | export const ROLES = [ 2 | { label: '超级管理员', value: 0 }, 3 | { label: '普通管理员', value: 1 }, 4 | { label: '审核员', value: 2 }, 5 | { label: '业务员', value: 3 }, 6 | { label: '测试员', value: 4 }, 7 | ] -------------------------------------------------------------------------------- /src/views/Setting/emailService.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Card, Form, Input, Button, Space } from 'antd' 4 | 5 | const layout = { 6 | labelCol: { span: 4 }, 7 | wrapperCol: { span: 16 } 8 | } 9 | 10 | const footerLayout = { 11 | wrapperCol: { 12 | offset: 4, 13 | span: 16 14 | } 15 | } 16 | 17 | const EmailRegexp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/ 18 | const DomainRegexp = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/ 19 | const NumberRegexp = /^(0|[1-9][0-9]*)$/ 20 | 21 | class EmailService extends React.Component { 22 | constructor() { 23 | super() 24 | this.state = { 25 | form: { 26 | smtpServer: 'baidu.com', 27 | smtpPort: '465', 28 | sendUserEmail: '234871928@qq.com', 29 | sendUserName: '李贤安', 30 | emailPassword: '123456' 31 | } 32 | } 33 | } 34 | 35 | formRef = React.createRef() 36 | 37 | handleSubmit = values => { 38 | console.log(values) 39 | } 40 | 41 | render() { 42 | return ( 43 |
44 | 45 |
46 | 47 | 53 | 54 | 55 | 56 | 如:smtp.163.com 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 一般为 25 或 465 70 | 71 | 72 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
91 |
92 | ) 93 | } 94 | } 95 | 96 | export default EmailService -------------------------------------------------------------------------------- /src/views/Setting/modifyPassword.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Card, Form, Input, Button, Space, Spin, message } from 'antd' 4 | 5 | const { $http } = React 6 | const layout = { 7 | labelCol: { span: 4 }, 8 | wrapperCol: { span: 16 } 9 | } 10 | 11 | const footerLayout = { 12 | wrapperCol: { 13 | offset: 4, 14 | span: 16 15 | } 16 | } 17 | 18 | // 必须包含大小写字母和数字的组合,不能使用特殊字符,长度在6-16之间 19 | const Regexp = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,16}$/ 20 | 21 | class ModifyPassword extends React.Component { 22 | constructor() { 23 | super() 24 | this.state = { 25 | spinning: false, 26 | form: { 27 | password: '', 28 | rePassword: '', 29 | } 30 | } 31 | } 32 | 33 | formRef = React.createRef() 34 | 35 | handleSubmit = values => { 36 | this.setState({spinning: true}) 37 | $http.put('password/modify', values).then(() => { 38 | message.success('密码修改成功') 39 | this.formRef.current.resetFields() 40 | this.setState({spinning: false}) 41 | }).catch(() => { 42 | this.setState({spinning: false}) 43 | }) 44 | } 45 | 46 | render() { 47 | const { spinning } = this.state 48 | return ( 49 |
50 | 51 | 52 |
53 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 必须包含大小写字母和数字的组合,不能使用特殊字符,长度在6-16之间 70 | 71 | 72 | ({ 78 | validator(rule, value) { 79 | 80 | if (!value || getFieldValue('newPassword') === value) { 81 | return Promise.resolve() 82 | } 83 | 84 | return Promise.reject('两次输入密码不一致') 85 | } 86 | }) 87 | ]}> 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 |
98 |
99 | ) 100 | } 101 | } 102 | 103 | export default ModifyPassword -------------------------------------------------------------------------------- /src/views/Setting/style.module.scss: -------------------------------------------------------------------------------- 1 | .page-basic-info { 2 | 3 | .avatar :global(.ant-form-item-label) { 4 | margin-top: 15px; 5 | } 6 | } -------------------------------------------------------------------------------- /src/views/Setting/websiteSetting.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Card, Form, Input, InputNumber, Button, Select, Space } from 'antd' 4 | 5 | const layout = { 6 | labelCol: { span: 4 }, 7 | wrapperCol: { span: 16 } 8 | } 9 | 10 | const footerLayout = { 11 | wrapperCol: { 12 | offset: 4, 13 | span: 16 14 | } 15 | } 16 | 17 | const Regexp = /^(?=^.{3,255}$)(http(s)?:\/\/)?(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+\.\w+)*(\/)?$/ 18 | 19 | class WebsiteSetting extends React.Component { 20 | constructor() { 21 | super() 22 | this.state = { 23 | form: { 24 | name: 'layuiAdmin', 25 | domain: 'https://www.baidu.com', 26 | cacheDate: 0, 27 | maxFileSizes: 2048, 28 | fileType: ['jpg', 'png', 'jpeg'], 29 | title: 'layuiAdmin 通用后台管理模板系统', 30 | keywords: 'React,React-router-dom,Redux', 31 | description: '作为 layui 官方推出的后台模板,从初版的饱受争议,到后续的埋头丰富,逐步占据了国内后台系统应用的主要市场。', 32 | copyright: '©️ 2020 layui.com MIT license' 33 | } 34 | } 35 | } 36 | 37 | websiteFormRef = React.createRef() 38 | 39 | handleSubmit = values => { 40 | console.log(values) 41 | } 42 | // handleReset = () => { 43 | // this.websiteFormRef.current.resetFields() 44 | // } 45 | 46 | render() { 47 | return ( 48 |
49 | 50 |
51 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 72 | 73 | 74 | 75 | 分钟 76 | 本地开发一般推荐设置为 0,线上环境建议设置为 10。 77 | 78 | 79 | 80 | 86 | 87 | 88 | 89 | KB 90 | 提示: 1M = 1024KB 91 | 92 | 93 | 97 | 105 | 106 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
129 |
130 | ) 131 | } 132 | } 133 | 134 | export default WebsiteSetting -------------------------------------------------------------------------------- /src/views/Test/sagas.js: -------------------------------------------------------------------------------- 1 | export const testSaga = function*() { 2 | console.log('hello') 3 | } -------------------------------------------------------------------------------- /src/views/User/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form, Row, Col, Input, Button, Select, Space, Card, Table, Modal, message } from 'antd' 3 | 4 | import { 5 | EditOutlined, 6 | PlusOutlined, 7 | DeleteOutlined, 8 | ExclamationCircleOutlined 9 | } from '@ant-design/icons' 10 | 11 | import UpdateUser from './updateUser' 12 | 13 | const {$http} = React 14 | const searchItem = [ 15 | { key: 'username', name: 'username', label: '用户名', defaultValue: null, type: 'input', placeholder: '请输入' }, 16 | { key: 'email', name: 'email', label: '邮箱', defaultValue: null, type: 'input', placeholder: '请输入' }, 17 | { key: 'sex', name: 'sex', label: '性别', defaultValue: -1, type: 'select', options: [ 18 | { value: -1, label: '不限' }, 19 | { value: 0, label: '男' }, 20 | { value: 1, label: '女'} 21 | ], placeholder: '请选择' } 22 | ] 23 | 24 | class User extends React.Component { 25 | constructor() { 26 | super() 27 | this.state = { 28 | selectedRowKeys: [], 29 | loading: false, 30 | query: { 31 | username: '', 32 | email: '', 33 | sex: -1 34 | }, 35 | userTableData: [], 36 | pagination: { 37 | current: 1, 38 | pageSize: 10, 39 | total: 0 40 | }, 41 | columns: [ 42 | { title: '用户名', dataIndex: 'username' }, 43 | { title: '性别', dataIndex: 'sex', render: (text, record) => { 44 | return ( 45 | {text === 0 ? '男' : '女'} 46 | ) 47 | }}, 48 | { title: '联系方式', dataIndex: 'phone' }, 49 | { title: '邮箱地址', dataIndex: 'email' }, 50 | { title: '创建时间', dataIndex: 'createDate' }, 51 | { title: '操作', key: 'action', render: (text, record) => { 52 | return ( 53 | 54 | 55 | 56 | 57 | ) 58 | }}, 59 | ], 60 | modalVisible: false, 61 | modalForm: {}, 62 | modalType: 'add' 63 | } 64 | } 65 | 66 | formRef = React.createRef() 67 | modalRef = null 68 | 69 | componentDidMount() { 70 | this.getUserList() 71 | } 72 | 73 | getUserList = () => { 74 | const { query, pagination } = this.state 75 | const params = {} 76 | for (let key in query) { 77 | if (query[key]) params[key] = query[key] 78 | } 79 | params['page'] = pagination.current 80 | params['pageSize'] = pagination.pageSize 81 | 82 | this.setState({loading: true}) 83 | $http.get('user/list', { params }).then(res => { 84 | const { list, ...pagination } = res 85 | this.setState({ userTableData: list, pagination: { 86 | current: pagination.page, 87 | pageSize: pagination.pageSize, 88 | total: pagination.total 89 | }, loading: false }) 90 | }) 91 | } 92 | 93 | onSearch = values => { 94 | this.setState({ query: values }, () => { 95 | this.getUserList() 96 | }) 97 | } 98 | onSelectedChange = (selectedRowKeys) => { 99 | this.setState({ selectedRowKeys }) 100 | } 101 | paginationChange = (pagination) => { 102 | this.setState({pagination: { 103 | current: pagination.current, 104 | pageSize: pagination.pageSize, 105 | }}, () => { 106 | this.getUserList() 107 | }) 108 | } 109 | onEdit = (record) => { 110 | this.setState({ 111 | modalVisible: true, 112 | modalForm: record, 113 | modalType: 'edit' 114 | }, () => { 115 | // ref 引用问题 116 | setTimeout(() => { 117 | this.modalRef.formRef.current.setFieldsValue({ 118 | id: record.id, 119 | username: record.username, 120 | sex: record.sex, 121 | phone: record.phone, 122 | email: record.email 123 | }) 124 | }, 100) 125 | }) 126 | } 127 | onDelete = (record) => { 128 | Modal.confirm({ 129 | title: '删除用户', 130 | icon: , 131 | content: (确认删除用户{record.username}吗?), 132 | onOk: () => { 133 | $http.delete('user/delete', { data: {id: record.id} }).then(res => { 134 | message.success('删除成功') 135 | this.getUserList() 136 | }) 137 | } 138 | }) 139 | } 140 | onMultipleDelete = () => { 141 | const { selectedRowKeys } = this.state 142 | if (!selectedRowKeys.length) { 143 | return message.error('请先选择删除的用户!') 144 | } 145 | $http.delete('user/delete', { data: {ids: selectedRowKeys} }).then(res => { 146 | message.success('删除成功') 147 | this.setState({ selectedRowKeys: [] }, () => this.getUserList()) 148 | }) 149 | } 150 | onOpenModal = () => { 151 | this.setState({modalVisible: true, modalType: 'add', modalForm: null}) 152 | } 153 | onModalSave = (values) => { 154 | const { modalForm, modalType } = this.state 155 | if (modalType === 'add') { 156 | $http.post('user/create', values).then(res => { 157 | message.success('添加成功') 158 | this.onModalCancel() 159 | }) 160 | } else { 161 | values.id = modalForm.id 162 | $http.put('user/edit', values).then(res => { 163 | message.success('修改成功') 164 | this.onModalCancel() 165 | }) 166 | } 167 | } 168 | onModalCancel = () => { 169 | this.modalRef.formRef.current.resetFields() 170 | this.setState({modalVisible: false, modalType: 'add', modalForm: null}) 171 | } 172 | 173 | render() { 174 | const { loading, selectedRowKeys, columns, userTableData, pagination, modalVisible, modalType } = this.state; 175 | const rowSelection = { 176 | selectedRowKeys, 177 | onChange: this.onSelectedChange 178 | } 179 | const getFields = () => { 180 | const children = searchItem.map(item => ( 181 | 182 | 186 | {item.type === 'input' ? 187 | : 188 | } 193 | 194 | 195 | )) 196 | return children 197 | } 198 | const searchControl = ( 199 | 200 | 201 | 202 | 203 | 204 | 205 | ) 206 | return ( 207 | 208 |
211 | 212 | {getFields()} 213 | {searchControl} 214 | 215 | 216 | 217 | 218 | 219 | 220 |
221 | this.modalRef = f} modalType={modalType} visible={modalVisible} onSave={this.onModalSave} onCancel={this.onModalCancel} /> 222 | 223 | ) 224 | } 225 | } 226 | 227 | export default User -------------------------------------------------------------------------------- /src/views/User/role.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form, Row, Col, Input, Button, Select, Space, Card, Table, Modal, message } from 'antd' 3 | 4 | import { 5 | EditOutlined, 6 | PlusOutlined, 7 | DeleteOutlined, 8 | ExclamationCircleOutlined 9 | } from '@ant-design/icons' 10 | 11 | import UpdateRole from './updateRole' 12 | 13 | const {$http} = React 14 | const searchItem = [ 15 | { key: 'role', name: 'role', label: '角色筛选', defaultValue: -1, type: 'select', options: [ 16 | { value: -1, label: '全部角色' }, 17 | { value: 0, label: '超级管理员' }, 18 | { value: 1, label: '管理员'}, 19 | { value: 2, label: '纠错员'}, 20 | { value: 3, label: '采购员'}, 21 | { value: 4, label: '推销员'}, 22 | { value: 5, label: '运营人员'}, 23 | { value: 6, label: '编辑'}, 24 | ], placeholder: '请选择' } 25 | ] 26 | 27 | class Role extends React.Component { 28 | constructor() { 29 | super() 30 | this.state = { 31 | selectedRowKeys: [], 32 | loading: false, 33 | role: -1, 34 | roleTableData: [], 35 | columns: [ 36 | { title: '角色名', dataIndex: 'roleName' }, 37 | { title: '拥有权限', dataIndex: 'auth' }, 38 | { title: '具体描述', dataIndex: 'description' }, 39 | { title: '操作', key: 'action', render: (text, record) => { 40 | return ( 41 | 42 | 43 | 44 | 45 | ) 46 | }}, 47 | ], 48 | modalVisible: false, 49 | modalForm: null, 50 | modalType: 'add' 51 | } 52 | } 53 | 54 | formRef = React.createRef() 55 | modalRef = null 56 | 57 | componentDidMount() { 58 | this.getRoleList() 59 | } 60 | 61 | getRoleList = () => { 62 | const { query } = this.state 63 | const params = {} 64 | for (let key in query) { 65 | if (query[key]) params[key] = query[key] 66 | } 67 | 68 | this.setState({loading: true}) 69 | $http.get('role/list', { params }).then(res => { 70 | const { list } = res 71 | this.setState({ roleTableData: list, loading: false }) 72 | }).catch((err) => { 73 | this.setState({loading: false}) 74 | const {data} = err 75 | message.error(data.desc || '列表获取失败') 76 | }) 77 | } 78 | 79 | onSearch = values => { 80 | this.setState({ query: values }, () => { 81 | this.getRoleList() 82 | }) 83 | } 84 | onSelectedChange = (selectedRowKeys) => { 85 | this.setState({ selectedRowKeys }) 86 | } 87 | onEdit = (record) => { 88 | $http.get('role/details', {id: record.id}).then(res => { 89 | this.setState({ 90 | modalVisible: true, 91 | modalForm: res, 92 | modalType: 'edit' 93 | }) 94 | }) 95 | } 96 | onDelete = (record) => { 97 | Modal.confirm({ 98 | title: '删除角色', 99 | icon: , 100 | content: (确认删除角色{record.roleName}吗?), 101 | onOk: () => { 102 | $http.delete('role/delete', { data: {id: record.id} }).then(res => { 103 | message.success('删除成功') 104 | this.getRoleList() 105 | }) 106 | } 107 | }) 108 | } 109 | onMultipleDelete = () => { 110 | const { selectedRowKeys } = this.state 111 | if (!selectedRowKeys.length) { 112 | return message.error('请先选择删除的角色!') 113 | } 114 | $http.delete('role/delete', { data: {ids: selectedRowKeys} }).then(res => { 115 | message.success('删除成功') 116 | this.setState({ selectedRowKeys: [] }, () => this.getRoleList()) 117 | }) 118 | } 119 | onOpenModal = () => { 120 | this.setState({modalVisible: true, modalType: 'add', modalForm: null}) 121 | } 122 | onModalSave = (values) => { 123 | const { modalForm, modalType } = this.state 124 | if (modalType === 'add') { 125 | $http.post('role/create', values).then(res => { 126 | message.success('添加成功') 127 | this.onModalCancel() 128 | }) 129 | } else { 130 | values.id = modalForm.id 131 | $http.put('role/edit', values).then(res => { 132 | message.success('修改成功') 133 | this.onModalCancel() 134 | }) 135 | } 136 | } 137 | onModalCancel = () => { 138 | this.modalRef.formRef.current.resetFields() 139 | this.setState({modalVisible: false, modalType: 'add', modalForm: null}) 140 | } 141 | 142 | render() { 143 | const { loading, selectedRowKeys, columns, roleTableData, modalVisible, modalForm } = this.state; 144 | const rowSelection = { 145 | selectedRowKeys, 146 | onChange: this.onSelectedChange 147 | } 148 | const getFields = () => { 149 | const children = searchItem.map(item => ( 150 | 151 | 155 | {item.type === 'input' ? 156 | : 157 | } 162 | 163 | 164 | )) 165 | return children 166 | } 167 | const searchControl = ( 168 | 169 | 170 | 171 | 172 | 173 | 174 | ) 175 | return ( 176 | 177 |
180 | 181 | {getFields()} 182 | {searchControl} 183 | 184 | 185 | 186 | 187 | 188 | 189 |
190 | this.modalRef = f} visible={modalVisible} modalForm={modalForm} onSave={this.onModalSave} onCancel={this.onModalCancel} /> 191 | 192 | ) 193 | } 194 | } 195 | 196 | export default Role -------------------------------------------------------------------------------- /src/views/User/updateRole.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form, Modal, Button, Input, Space, Select, Checkbox } from 'antd' 3 | 4 | const layout = { 5 | labelCol: { 6 | span: 6, 7 | }, 8 | wrapperCol: { 9 | span: 16, 10 | }, 11 | } 12 | const tailLayout = { 13 | wrapperCol: { 14 | offset: 6, 15 | span: 16, 16 | }, 17 | } 18 | const roleOptions = [ 19 | { label: '管理员', value: 1 }, 20 | { label: '超级管理员', value: 0 }, 21 | { label: '纠错员', value: 2 }, 22 | { label: '采购员', value: 3 }, 23 | { label: '推销员', value: 4 }, 24 | { label: '运营人员', value: 5 }, 25 | { label: '文章撰写员', value: 6 } 26 | ] 27 | const authOptions = [ 28 | { label: '内容系统', value: 1 }, 29 | { label: '社区系统', value: 0 }, 30 | { label: '用户', value: 2 }, 31 | { label: '角色', value: 3 }, 32 | { label: '评论审核', value: 4 }, 33 | { label: '采购', value: 5 }, 34 | { label: '系统设置', value: 6 }, 35 | { label: '发送邮件', value: 7 }, 36 | { label: '发送短信', value: 8 }, 37 | { label: '审核', value: 9 } 38 | ] 39 | 40 | class UpdateRole extends React.PureComponent { 41 | 42 | formRef = React.createRef() 43 | 44 | render() { 45 | const { visible, onSave, onCancel, modalForm } = this.props 46 | return ( 47 | 48 |
49 | 51 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | ) 72 | } 73 | } 74 | 75 | export default UpdateRole -------------------------------------------------------------------------------- /src/views/User/updateUser.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form, Modal, Button, Input, Radio, Space } from 'antd' 3 | 4 | const layout = { 5 | labelCol: { 6 | span: 6, 7 | }, 8 | wrapperCol: { 9 | span: 16, 10 | }, 11 | } 12 | const tailLayout = { 13 | wrapperCol: { 14 | offset: 6, 15 | span: 16, 16 | }, 17 | } 18 | const EmailRegexp = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/ 19 | const PhoneRegexp = /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/ 20 | 21 | class UpdateUser extends React.PureComponent { 22 | constructor(props) { 23 | super(props) 24 | this.state = { 25 | form: { 26 | username: '', 27 | sex: 0, 28 | phone: '', 29 | email: '' 30 | } 31 | } 32 | } 33 | 34 | formRef = React.createRef() 35 | 36 | render() { 37 | const { visible, onSave, onCancel, modalType } = this.props 38 | 39 | return ( 40 | 41 |
42 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | ) 75 | } 76 | } 77 | 78 | export default UpdateUser --------------------------------------------------------------------------------