├── src ├── assets │ └── imgs │ │ └── logo.png ├── pages │ ├── login │ │ ├── images │ │ │ ├── 404.png │ │ │ └── bg.jpg │ │ ├── login.less │ │ └── login.jsx │ ├── home │ │ ├── home.less │ │ └── home.jsx │ ├── product │ │ ├── product.less │ │ ├── product.jsx │ │ ├── rich-text-editor.jsx │ │ ├── detail.jsx │ │ ├── pictures-wall.jsx │ │ ├── home.jsx │ │ └── add-update.jsx │ ├── not-found │ │ ├── not-found.less │ │ └── not-found.jsx │ ├── category │ │ ├── update-form.jsx │ │ ├── add-form.jsx │ │ └── category.jsx │ ├── role │ │ ├── add-form.jsx │ │ ├── role.jsx │ │ └── auth-form.jsx │ ├── chars │ │ ├── line.jsx │ │ ├── bar.jsx │ │ └── pie.jsx │ ├── user │ │ ├── user-form.jsx │ │ └── user.jsx │ └── admin │ │ └── admin.jsx ├── utils │ ├── constants.js │ ├── memoryUtils.js │ ├── dateUtils.js │ └── storageUtils.js ├── components │ ├── link-button │ │ ├── index.less │ │ └── index.jsx │ ├── left-nav │ │ ├── index.less │ │ └── index.jsx │ └── header │ │ ├── index.less │ │ └── index.jsx ├── index.js ├── redux │ ├── action-types.js │ ├── store.js │ ├── reducer.js │ └── actions.js ├── App.js ├── api │ ├── ajax.js │ └── index.jsx └── config │ └── menuConfig.js ├── src_react ├── assets │ └── imgs │ │ └── logo.png ├── pages │ ├── login │ │ ├── images │ │ │ ├── bg.jpg │ │ │ └── 404.png │ │ ├── login.less │ │ └── login.jsx │ ├── home │ │ ├── home.less │ │ └── home.jsx │ ├── chars │ │ ├── bar.jsx │ │ ├── line.jsx │ │ └── pie.jsx │ ├── product │ │ ├── product.less │ │ ├── product.jsx │ │ ├── rich-text-editor.jsx │ │ ├── detail.jsx │ │ ├── pictures-wall.jsx │ │ ├── home.jsx │ │ └── add-update.jsx │ ├── category │ │ ├── update-form.jsx │ │ ├── add-form.jsx │ │ └── category.jsx │ ├── role │ │ ├── add-form.jsx │ │ ├── role.jsx │ │ └── auth-form.jsx │ ├── user │ │ ├── user-form.jsx │ │ └── user.jsx │ └── admin │ │ └── admin.jsx ├── utils │ ├── constants.js │ ├── memoryUtils.js │ ├── dateUtils.js │ └── storageUtils.js ├── components │ ├── link-button │ │ ├── index.less │ │ └── index.jsx │ ├── left-nav │ │ ├── index.less │ │ └── index.jsx │ └── header │ │ ├── index.less │ │ └── index.jsx ├── redux │ ├── action-types.js │ ├── store.js │ ├── reducer.js │ └── actions.js ├── index.js ├── App.js ├── api │ ├── ajax.js │ └── index.jsx └── config │ └── menuConfig.js ├── .gitignore ├── public ├── index.html └── css │ └── reset.css ├── config-overrides.js ├── package.json └── README.md /src/assets/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qinnnn2021/React-Admin/HEAD/src/assets/imgs/logo.png -------------------------------------------------------------------------------- /src/pages/login/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qinnnn2021/React-Admin/HEAD/src/pages/login/images/404.png -------------------------------------------------------------------------------- /src/pages/login/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qinnnn2021/React-Admin/HEAD/src/pages/login/images/bg.jpg -------------------------------------------------------------------------------- /src_react/assets/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qinnnn2021/React-Admin/HEAD/src_react/assets/imgs/logo.png -------------------------------------------------------------------------------- /src_react/pages/login/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qinnnn2021/React-Admin/HEAD/src_react/pages/login/images/bg.jpg -------------------------------------------------------------------------------- /src_react/pages/login/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qinnnn2021/React-Admin/HEAD/src_react/pages/login/images/404.png -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | // 包含项目中所用到的常量 2 | 3 | export const PAGE_SIZE = 2 //每页显示的记录数 4 | 5 | export const BASE_IMG_URL = 'http://zlx.cool:5000/upload/' -------------------------------------------------------------------------------- /src_react/utils/constants.js: -------------------------------------------------------------------------------- 1 | // 包含项目中所用到的常量 2 | 3 | export const PAGE_SIZE = 2 //每页显示的记录数 4 | 5 | export const BASE_IMG_URL = 'http://zlx.cool:5000/upload/' -------------------------------------------------------------------------------- /src/utils/memoryUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | 用来在内存中保存一些数据的工具模块 3 | */ 4 | 5 | const memoryUtils = { 6 | user: {}, //保存当前登录的user 7 | } 8 | 9 | export default memoryUtils -------------------------------------------------------------------------------- /src_react/utils/memoryUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | 用来在内存中保存一些数据的工具模块 3 | */ 4 | 5 | const memoryUtils = { 6 | user: {}, //保存当前登录的user 7 | } 8 | 9 | export default memoryUtils -------------------------------------------------------------------------------- /src/components/link-button/index.less: -------------------------------------------------------------------------------- 1 | .linkButton { 2 | background-color: transparent; 3 | border: none; 4 | outline: none; 5 | color: #1da57a; 6 | cursor: pointer 7 | } -------------------------------------------------------------------------------- /src/pages/home/home.less: -------------------------------------------------------------------------------- 1 | .home-title { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | font-size: 30px; 8 | } -------------------------------------------------------------------------------- /src_react/components/link-button/index.less: -------------------------------------------------------------------------------- 1 | .linkButton { 2 | background-color: transparent; 3 | border: none; 4 | outline: none; 5 | color: #1da57a; 6 | cursor: pointer 7 | } -------------------------------------------------------------------------------- /src_react/pages/home/home.less: -------------------------------------------------------------------------------- 1 | .home-title { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | font-size: 30px; 8 | } -------------------------------------------------------------------------------- /src/components/link-button/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './index.less' 3 | 4 | // 外形像是链接的按钮 5 | 6 | export default function LinkButton(props) { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /src_react/components/link-button/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './index.less' 3 | 4 | // 外形像是链接的按钮 5 | 6 | export default function LinkButton(props) { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /src_react/pages/chars/bar.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | // 柱形图 4 | export default class Bar extends Component { 5 | render() { 6 | return ( 7 |
8 | Bar 9 |
10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src_react/pages/chars/line.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | // 折线图路由 4 | export default class Line extends Component { 5 | render() { 6 | return ( 7 |
8 | Line 9 |
10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src_react/pages/chars/pie.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | // 饼状图路由 4 | export default class Pie extends Component { 5 | render() { 6 | return ( 7 |
8 | Pie 9 |
10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import {Provider} from 'react-redux' 5 | 6 | import store from './redux/store' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root')) 13 | -------------------------------------------------------------------------------- /src/redux/action-types.js: -------------------------------------------------------------------------------- 1 | // 包含n个action type 常量名称的模块 2 | 3 | // 设置头部标题 4 | export const SET_HEAD_TITLE = 'set_head_title' 5 | 6 | // 接收新的用户 7 | export const RECEIVE_USER = 'receive_user' 8 | 9 | // 显示错误提示信息 10 | export const SHOW_ERROR_MSG = 'show_error_msg' 11 | 12 | // 重置用户 13 | export const RESET_USER = 'reset_user' 14 | -------------------------------------------------------------------------------- /src_react/redux/action-types.js: -------------------------------------------------------------------------------- 1 | // 包含n个action type 常量名称的模块 2 | 3 | // 设置头部标题 4 | export const SET_HEAD_TITLE = 'set_head_title' 5 | 6 | // 接收新的用户 7 | export const RECEIVE_USER = 'receive_user' 8 | 9 | // 显示错误提示信息 10 | export const SHOW_ERROR_MSG = 'show_error_msg' 11 | 12 | // 重置用户 13 | export const RESET_USER = 'reset_user' 14 | -------------------------------------------------------------------------------- /src/pages/home/home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './home.less' 3 | 4 | // 首页路由 5 | export default class Home extends Component { 6 | render() { 7 | return ( 8 |
9 | 欢迎使用硅谷后台管理系统! 10 |
11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/dateUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含n个日期事件处理的工具函数模块 3 | */ 4 | 5 | // 格式化日期 6 | export function formateDate(time) { 7 | if (!time) return '' 8 | let date = new Date(time) 9 | return `${date.getFullYear()}-${(date.getMonth() + 1)}-${date.getDate()} 10 | \xa0 ${date.getHours()} : ${date.getMinutes()} : ${date.getSeconds()} ` 11 | } -------------------------------------------------------------------------------- /src_react/pages/home/home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './home.less' 3 | 4 | // 首页路由 5 | export default class Home extends Component { 6 | render() { 7 | return ( 8 |
9 | 欢迎使用硅谷后台管理系统! 10 |
11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | // redux 最核心的管理对象 store 2 | 3 | import {createStore, applyMiddleware} from 'redux' 4 | import thunk from 'redux-thunk' 5 | import {composeWithDevTools} from 'redux-devtools-extension' 6 | 7 | import reducer from './reducer' 8 | 9 | // 向外暴露store对象 10 | export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk))) -------------------------------------------------------------------------------- /src_react/utils/dateUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含n个日期事件处理的工具函数模块 3 | */ 4 | 5 | // 格式化日期 6 | export function formateDate(time) { 7 | if (!time) return '' 8 | let date = new Date(time) 9 | return `${date.getFullYear()}-${(date.getMonth() + 1)}-${date.getDate()} 10 | \xa0 ${date.getHours()} : ${date.getMinutes()} : ${date.getSeconds()} ` 11 | } -------------------------------------------------------------------------------- /src_react/redux/store.js: -------------------------------------------------------------------------------- 1 | // redux 最核心的管理对象 store 2 | 3 | import {createStore, applyMiddleware} from 'redux' 4 | import thunk from 'redux-thunk' 5 | import {composeWithDevTools} from 'redux-devtools-extension' 6 | 7 | import reducer from './reducer' 8 | 9 | // 向外暴露store对象 10 | export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk))) -------------------------------------------------------------------------------- /src/pages/product/product.less: -------------------------------------------------------------------------------- 1 | .product-detail { 2 | .left { 3 | margin-right: 15px; 4 | font-size: 20px; 5 | font-weight: bold; 6 | } 7 | 8 | .product-img { 9 | width: 200px; 10 | height: 200px; 11 | border: 1px solid #002140; 12 | margin-left: 10px; 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /src_react/pages/product/product.less: -------------------------------------------------------------------------------- 1 | .product-detail { 2 | .left { 3 | margin-right: 15px; 4 | font-size: 20px; 5 | font-weight: bold; 6 | } 7 | 8 | .product-img { 9 | width: 200px; 10 | height: 200px; 11 | border: 1px solid #002140; 12 | margin-left: 10px; 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /src/components/left-nav/index.less: -------------------------------------------------------------------------------- 1 | .left-nav { 2 | .left-nav-header { 3 | display: flex; 4 | align-items: center; 5 | height: 80px; 6 | background-color: #002140; 7 | img { 8 | margin: 0 15px; 9 | width: 40px; 10 | height: 40px; 11 | } 12 | h1 { 13 | color: #fff; 14 | font-size: 20px; 15 | margin-bottom: 0; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src_react/components/left-nav/index.less: -------------------------------------------------------------------------------- 1 | .left-nav { 2 | .left-nav-header { 3 | display: flex; 4 | align-items: center; 5 | height: 80px; 6 | background-color: #002140; 7 | img { 8 | margin: 0 15px; 9 | width: 40px; 10 | height: 40px; 11 | } 12 | h1 { 13 | color: #fff; 14 | font-size: 20px; 15 | margin-bottom: 0; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/pages/not-found/not-found.less: -------------------------------------------------------------------------------- 1 | .not-found{ 2 | background-color: #f0f2f5; 3 | height: 100%; 4 | .left{ 5 | height: 100%; 6 | background: url('../login/images/404.png') no-repeat center; 7 | } 8 | .right{ 9 | padding-left: 50px; 10 | margin-top: 150px; 11 | h1{ 12 | font-size: 35px; 13 | } 14 | h2{ 15 | margin-bottom: 20px; 16 | font-size: 20px; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const {override,fixBabelImports,addLessLoader} = require('customize-cra') 2 | 3 | module.exports = override( 4 | fixBabelImports('import',{ 5 | libraryName:'antd', 6 | libraryDirectory:'es', 7 | style:true, 8 | }), 9 | addLessLoader({ 10 | lessOptions: { 11 | javascriptEnabled: true, 12 | modifyVars: { '@primary-color': '#1DA57A' }, 13 | }, 14 | }), 15 | 16 | ); -------------------------------------------------------------------------------- /src_react/index.js: -------------------------------------------------------------------------------- 1 | 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import {Provider} from 'react-redux' 5 | 6 | import storageUtils from './utils/storageUtils' 7 | import memoryUtils from './utils/memoryUtils' 8 | import store from './redux/store' 9 | 10 | // 读取local中保存的user 保存到内存中 11 | const user = storageUtils.getUser() 12 | memoryUtils.user = user 13 | 14 | ReactDOM.render( 15 | 16 | 17 | , 18 | document.getElementById('root')) 19 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {BrowserRouter,Route,Switch} from 'react-router-dom' 3 | 4 | import Login from './pages/login/login' 5 | import Admin from './pages/admin/admin' 6 | 7 | 8 | export default class App extends Component { 9 | render() { 10 | return ( 11 | 12 | {/*只匹配其中一个 */} 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src_react/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {BrowserRouter,Route,Switch} from 'react-router-dom' 3 | 4 | import Login from './pages/login/login' 5 | import Admin from './pages/admin/admin' 6 | 7 | 8 | export default class App extends Component { 9 | render() { 10 | return ( 11 | 12 | {/*只匹配其中一个 */} 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/utils/storageUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | 进行local 数据存储管理的工具模块 3 | */ 4 | import store from 'store' 5 | 6 | const USER_KEY = 'user_key' 7 | 8 | const storageUtils = { 9 | // 保存user 10 | saveUser(user) { 11 | // localStorage.setItem(USER_KEY,JSON.stringify(user)) 12 | store.set(USER_KEY,user) 13 | }, 14 | // 读取user 15 | getUser(){ 16 | // return JSON.parse(localStorage.getItem(USER_KEY) || '{}' ) 17 | return store.get(USER_KEY) || {} 18 | }, 19 | // 删除user 20 | removeUser(){ 21 | // localStorage.removeItem(USER_KEY) 22 | store.remove(USER_KEY) 23 | } 24 | } 25 | 26 | export default storageUtils -------------------------------------------------------------------------------- /src/pages/category/update-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Input} from 'antd' 3 | 4 | const Item = Form.Item 5 | 6 | // 更新分类的form组件 7 | const UpdateForm = (props) => { 8 | const [form] = Form.useForm(); 9 | 10 | const {categoryName} = props 11 | 12 | props.setForm(form) 13 | 14 | return ( 15 |
16 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | 26 | export default UpdateForm 27 | 28 | -------------------------------------------------------------------------------- /src_react/utils/storageUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | 进行local 数据存储管理的工具模块 3 | */ 4 | import store from 'store' 5 | 6 | const USER_KEY = 'user_key' 7 | 8 | const storageUtils = { 9 | // 保存user 10 | saveUser(user) { 11 | // localStorage.setItem(USER_KEY,JSON.stringify(user)) 12 | store.set(USER_KEY,user) 13 | }, 14 | // 读取user 15 | getUser(){ 16 | // return JSON.parse(localStorage.getItem(USER_KEY) || '{}' ) 17 | return store.get(USER_KEY) || {} 18 | }, 19 | // 删除user 20 | removeUser(){ 21 | // localStorage.removeItem(USER_KEY) 22 | store.remove(USER_KEY) 23 | } 24 | } 25 | 26 | export default storageUtils -------------------------------------------------------------------------------- /src_react/pages/category/update-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Input} from 'antd' 3 | 4 | const Item = Form.Item 5 | 6 | // 更新分类的form组件 7 | const UpdateForm = (props) => { 8 | const [form] = Form.useForm(); 9 | 10 | const {categoryName} = props 11 | 12 | props.setForm(form) 13 | 14 | return ( 15 |
16 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | 26 | export default UpdateForm 27 | 28 | -------------------------------------------------------------------------------- /src/pages/not-found/not-found.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Row,Col, Button} from 'antd' 3 | 4 | export default class NotFound extends Component { 5 | goHome = () => { 6 | this.props.history.replace('/home') 7 | } 8 | render() { 9 | return ( 10 | 11 | 12 | 13 |

404

14 |

抱歉 您所访问的界面不存在

15 |
16 | 19 |
20 | 21 |
22 | ) 23 | } 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/pages/product/product.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route,Switch,Redirect} from 'react-router-dom' 3 | 4 | import ProductHome from './home' 5 | import ProductAddUpdate from './add-update' 6 | import ProductDetail from './detail' 7 | import './product.less' 8 | 9 | // 商品路由 10 | export default class Product extends Component { 11 | render() { 12 | return ( 13 | 14 | {/* 路径完全匹配 */} 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src_react/pages/product/product.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route,Switch,Redirect} from 'react-router-dom' 3 | 4 | import ProductHome from './home' 5 | import ProductAddUpdate from './add-update' 6 | import ProductDetail from './detail' 7 | import './product.less' 8 | 9 | // 商品路由 10 | export default class Product extends Component { 11 | render() { 12 | return ( 13 | 14 | {/* 路径完全匹配 */} 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/role/add-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Input} from 'antd' 3 | import Layout from 'antd/lib/layout/layout'; 4 | 5 | const Item = Form.Item 6 | 7 | // 创建角色的form组件 8 | const ADDForm = (props) => { 9 | const [form] = Form.useForm(); 10 | props.setForm(form) 11 | // //指定Item布局的配置对象 12 | // const Layout = { 13 | // labelCol: {span:4}, 14 | // wrapperCol: {span: 16} 15 | // } 16 | return ( 17 |
18 | 21 | 22 | 23 |
24 | ) 25 | } 26 | 27 | export default ADDForm 28 | -------------------------------------------------------------------------------- /src_react/pages/role/add-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Input} from 'antd' 3 | import Layout from 'antd/lib/layout/layout'; 4 | 5 | const Item = Form.Item 6 | 7 | // 创建角色的form组件 8 | const ADDForm = (props) => { 9 | const [form] = Form.useForm(); 10 | props.setForm(form) 11 | // //指定Item布局的配置对象 12 | // const Layout = { 13 | // labelCol: {span:4}, 14 | // wrapperCol: {span: 16} 15 | // } 16 | return ( 17 |
18 | 21 | 22 | 23 |
24 | ) 25 | } 26 | 27 | export default ADDForm 28 | -------------------------------------------------------------------------------- /src_react/pages/login/login.less: -------------------------------------------------------------------------------- 1 | .login{ 2 | width: 100%; 3 | height: 100%; 4 | background-image: url(./images/bg.jpg); 5 | background-size: 100% 100%; 6 | 7 | .login-header{ 8 | display: flex; 9 | align-items: center; 10 | height: 80px; 11 | background-color: rgba(21,20,13,.5); 12 | img { 13 | width: 40px; 14 | height: 40px; 15 | margin: 0 15px 0 50px; 16 | } 17 | h1 { 18 | display: inline-block; 19 | font-size: 32px; 20 | color: #fff; 21 | padding-top: 10px; 22 | } 23 | } 24 | 25 | .login-content { 26 | width: 400px; 27 | height: 300px; 28 | background-color: #fff; 29 | margin: 50px auto; 30 | padding: 20px 40px; 31 | h2 { 32 | text-align: center; 33 | font-size: 28px; 34 | font-weight: bold; 35 | margin-bottom: 28px; 36 | } 37 | .login-form{ 38 | .login-form-button { 39 | width: 100%; 40 | } 41 | } 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /src_react/redux/reducer.js: -------------------------------------------------------------------------------- 1 | // reducer函数模块: 根据当前的state和指定action返回一个新的state 2 | 3 | import {combineReducers} from 'redux' 4 | import storageUtils from '../utils/storageUtils' 5 | 6 | import { 7 | SET_HEAD_TITLE, 8 | RECEIVE_USER, 9 | SHOW_ERROR_MSG 10 | } from './action-types' 11 | 12 | // 管理headTitle状态数据的reducer 13 | const initHeadTitle = '首页' 14 | function headTitle(state=initHeadTitle,action){ 15 | switch(action.type) { 16 | case SET_HEAD_TITLE: 17 | return action.data 18 | case SHOW_ERROR_MSG: 19 | const errorMsg = action.errorMsg 20 | // state.errorMsg = errorMsg 不要直接修改原本的状态数据 21 | return {...state,errorMsg} 22 | default: 23 | return state 24 | } 25 | } 26 | 27 | // 管理user状态数据的reducer 28 | const initUser = storageUtils.getUser() 29 | function user(state = initUser,action){ 30 | switch(action.type) { 31 | case RECEIVE_USER: 32 | return action.user 33 | default: 34 | return state 35 | } 36 | } 37 | 38 | 39 | //默认向外暴露的是合并成一个对象之后的reducer 40 | // 其中各自reducer是这个合并reducer对象的属性 41 | export default combineReducers({ 42 | headTitle, 43 | user 44 | }) -------------------------------------------------------------------------------- /src/redux/reducer.js: -------------------------------------------------------------------------------- 1 | // reducer函数模块: 根据当前的state和指定action返回一个新的state 2 | 3 | import {combineReducers} from 'redux' 4 | import storageUtils from '../utils/storageUtils' 5 | 6 | import { 7 | SET_HEAD_TITLE, 8 | RECEIVE_USER, 9 | SHOW_ERROR_MSG, 10 | RESET_USER 11 | } from './action-types' 12 | 13 | // 管理headTitle状态数据的reducer 14 | const initHeadTitle = '首页' 15 | function headTitle(state=initHeadTitle,action){ 16 | switch(action.type) { 17 | case SET_HEAD_TITLE: 18 | return action.data 19 | default: 20 | return state 21 | } 22 | } 23 | 24 | // 管理user状态数据的reducer 25 | const initUser = storageUtils.getUser() 26 | function user(state = initUser,action){ 27 | switch(action.type) { 28 | case RECEIVE_USER: 29 | return action.user 30 | case SHOW_ERROR_MSG: 31 | const errorMsg = action.errorMsg 32 | // state.errorMsg = errorMsg 不要直接修改原本的状态数据 33 | return {...state,errorMsg} 34 | case RESET_USER: 35 | return {} 36 | default: 37 | return state 38 | } 39 | } 40 | 41 | 42 | //默认向外暴露的是合并成一个对象之后的reducer 43 | // 其中各自reducer是这个合并reducer对象的属性 44 | export default combineReducers({ 45 | headTitle, 46 | user 47 | }) -------------------------------------------------------------------------------- /src/api/ajax.js: -------------------------------------------------------------------------------- 1 | // 可以发送异步ajax请求的函数模块 封装ajax库 2 | // 函数的返回值是一个promise对象 3 | 4 | /* 5 | 1、优化1:统一处理请求异常 6 | 在外层包一个自己创建的promise对象 7 | 在请求出错时 不执行reject(error) 而是显示错误提示 8 | 9 | 2、优化2:异步得到不是reponse 而是response.data 10 | 在请求成功resolve时:resolve(response.data) 11 | */ 12 | 13 | import axios from 'axios' 14 | import {message} from 'antd' 15 | 16 | export default function ajax(url,data={},type='GET'){ 17 | 18 | return new Promise((resolve,reject)=>{ 19 | let promise 20 | 21 | // 1、执行异步ajax请求 22 | if (type === 'GET') { //发送GET请求 23 | promise = axios.get(url,{ //配置对象 24 | params: data //指定请求参数 25 | }) 26 | } else { //发送POST请求 27 | promise = axios.post(url,data) 28 | } 29 | promise.then(response=>{ 30 | // 2、如果成功了 调用resolve(value) 31 | resolve(response.data) 32 | }).catch(error=>{ 33 | // 3、如果失败了 不调用reject(reason) 而是提示异常信息 34 | message.error('请求出错了:' + error.message) 35 | }) 36 | 37 | }) 38 | 39 | } 40 | 41 | // 请求登录接口 42 | // ajax('/login',{username:'tom',password:'12345'},'POST').then 43 | // 添加用户 44 | // ajax('/manage/user/add',{username:'tom',password:'12345',phone:'13765433456'},'POST').then -------------------------------------------------------------------------------- /src_react/api/ajax.js: -------------------------------------------------------------------------------- 1 | // 可以发送异步ajax请求的函数模块 封装ajax库 2 | // 函数的返回值是一个promise对象 3 | 4 | /* 5 | 1、优化1:统一处理请求异常 6 | 在外层包一个自己创建的promise对象 7 | 在请求出错时 不执行reject(error) 而是显示错误提示 8 | 9 | 2、优化2:异步得到不是reponse 而是response.data 10 | 在请求成功resolve时:resolve(response.data) 11 | */ 12 | 13 | import axios from 'axios' 14 | import {message} from 'antd' 15 | 16 | export default function ajax(url,data={},type='GET'){ 17 | 18 | return new Promise((resolve,reject)=>{ 19 | let promise 20 | 21 | // 1、执行异步ajax请求 22 | if (type === 'GET') { //发送GET请求 23 | promise = axios.get(url,{ //配置对象 24 | params: data //指定请求参数 25 | }) 26 | } else { //发送POST请求 27 | promise = axios.post(url,data) 28 | } 29 | promise.then(response=>{ 30 | // 2、如果成功了 调用resolve(value) 31 | resolve(response.data) 32 | }).catch(error=>{ 33 | // 3、如果失败了 不调用reject(reason) 而是提示异常信息 34 | message.error('请求出错了:' + error.message) 35 | }) 36 | 37 | }) 38 | 39 | } 40 | 41 | // 请求登录接口 42 | // ajax('/login',{username:'tom',password:'12345'},'POST').then 43 | // 添加用户 44 | // ajax('/manage/user/add',{username:'tom',password:'12345',phone:'13765433456'},'POST').then -------------------------------------------------------------------------------- /src/pages/category/add-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Select,Input} from 'antd' 3 | 4 | const Item = Form.Item 5 | const Option = Select.Option 6 | 7 | 8 | // 添加分类的form组件 9 | const ADDForm = (props) => { 10 | const [form] = Form.useForm(); 11 | const {categorys,parentId} = props 12 | props.setForm(form) 13 | 14 | // console.log('add',categorys); 15 | return ( 16 |
17 | 18 | 28 | 29 | 30 | 32 | 33 | 34 |
35 | ) 36 | } 37 | 38 | export default ADDForm 39 | -------------------------------------------------------------------------------- /src_react/pages/category/add-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Select,Input} from 'antd' 3 | 4 | const Item = Form.Item 5 | const Option = Select.Option 6 | 7 | 8 | // 添加分类的form组件 9 | const ADDForm = (props) => { 10 | const [form] = Form.useForm(); 11 | const {categorys,parentId} = props 12 | props.setForm(form) 13 | 14 | // console.log('add',categorys); 15 | return ( 16 |
17 | 18 | 28 | 29 | 30 | 32 | 33 | 34 |
35 | ) 36 | } 37 | 38 | export default ADDForm 39 | -------------------------------------------------------------------------------- /src_react/redux/actions.js: -------------------------------------------------------------------------------- 1 | // 包含n个用来创建action的工厂函数(action creator) 2 | // 同步action: 对象 {type:'xxx',data: 数据值} 3 | // 异步action: 函数 dispatch => {} 4 | 5 | 6 | import { 7 | SET_HEAD_TITLE, 8 | RECEIVE_USER, 9 | SHOW_ERROR_MSG 10 | } from './action-types' 11 | import {reqLogin} from '../api' 12 | import storageUtils from '../utils/storageUtils' 13 | 14 | // 设置头部标题的同步action对象 15 | export const setHeadTitle = (headTitle) => ({type: SET_HEAD_TITLE, data: headTitle}) 16 | 17 | // 接收用户的同步action 18 | export const receiveUser = (user) => ({type: RECEIVE_USER,user}) 19 | 20 | // 显示错误信息的同步action 21 | export const showErrorMsg = (errorMsg) => ({type: SHOW_ERROR_MSG,errorMsg}) 22 | 23 | // 登录的异步action 24 | export const login = (username,password) => { 25 | return async dispatch => { 26 | // 1、执行异步ajax请求 27 | const result = await reqLogin(username,password) 28 | //{status: 0,data: user} {status: 1,msg: 'xxx'} 29 | // 2.1、如果成功 分发成功的同步action 30 | if (result.status === 0){ 31 | const user = result.data 32 | //保存user 33 | storageUtils.saveUser(user) 34 | //分发接收用户的同步action 35 | dispatch(receiveUser(user)) 36 | } 37 | // 2.2、如果失败 分发失败的同步action 38 | const msg = result.msg 39 | dispatch(showErrorMsg(msg)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/header/index.less: -------------------------------------------------------------------------------- 1 | .header{ 2 | height: 80px; 3 | background-color: #fff; 4 | .header-top { 5 | height: 40px; 6 | line-height: 40px; 7 | padding-right: 35px; 8 | text-align: right; 9 | border-bottom: 1px solid #1da57a; 10 | span { 11 | margin-right: 20px; 12 | } 13 | } 14 | .header-bottom { 15 | height: 40px; 16 | display: flex; 17 | align-items: center; 18 | .header-bottom-left { 19 | position: relative; 20 | width: 25%; 21 | text-align: center; 22 | font-size: 20px; 23 | &::after{ 24 | content:''; 25 | position: absolute; 26 | right: 50%; 27 | transform: translateX(50%); 28 | top: 100%; 29 | border-top: 20px solid #fff; 30 | border-right: 20px solid transparent; 31 | border-bottom: 20px solid transparent; 32 | border-left: 20px solid transparent; 33 | } 34 | } 35 | .header-bottom-right { 36 | width: 75%; 37 | text-align: right; 38 | margin-right: 35px; 39 | img { 40 | margin: 0 15px; 41 | width: 30px; 42 | height: 30px; 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src_react/components/header/index.less: -------------------------------------------------------------------------------- 1 | .header{ 2 | height: 80px; 3 | background-color: #fff; 4 | .header-top { 5 | height: 40px; 6 | line-height: 40px; 7 | padding-right: 35px; 8 | text-align: right; 9 | border-bottom: 1px solid #1da57a; 10 | span { 11 | margin-right: 20px; 12 | } 13 | } 14 | .header-bottom { 15 | height: 40px; 16 | display: flex; 17 | align-items: center; 18 | .header-bottom-left { 19 | position: relative; 20 | width: 25%; 21 | text-align: center; 22 | font-size: 20px; 23 | &::after{ 24 | content:''; 25 | position: absolute; 26 | right: 50%; 27 | transform: translateX(50%); 28 | top: 100%; 29 | border-top: 20px solid #fff; 30 | border-right: 20px solid transparent; 31 | border-bottom: 20px solid transparent; 32 | border-left: 20px solid transparent; 33 | } 34 | } 35 | .header-bottom-right { 36 | width: 75%; 37 | text-align: right; 38 | margin-right: 35px; 39 | img { 40 | margin: 0 15px; 41 | width: 30px; 42 | height: 30px; 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /public/css/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | td, 45 | th { 46 | padding: 0; 47 | text-align: left; 48 | } 49 | 50 | html, 51 | body { 52 | height: 100%; 53 | width: 100%; 54 | } 55 | 56 | #root { 57 | height: 100%; 58 | width: 100%; 59 | } -------------------------------------------------------------------------------- /src/redux/actions.js: -------------------------------------------------------------------------------- 1 | // 包含n个用来创建action的工厂函数(action creator) 2 | // 同步action: 对象 {type:'xxx',data: 数据值} 3 | // 异步action: 函数 dispatch => {} 4 | 5 | 6 | import { 7 | SET_HEAD_TITLE, 8 | RECEIVE_USER, 9 | SHOW_ERROR_MSG, 10 | RESET_USER 11 | } from './action-types' 12 | import {reqLogin} from '../api' 13 | import storageUtils from '../utils/storageUtils' 14 | 15 | // 设置头部标题的同步action对象 16 | export const setHeadTitle = (headTitle) => ({type: SET_HEAD_TITLE, data: headTitle}) 17 | 18 | // 接收用户的同步action 19 | export const receiveUser = (user) => ({type: RECEIVE_USER,user}) 20 | 21 | // 显示错误信息的同步action 22 | export const showErrorMsg = (errorMsg) => ({type: SHOW_ERROR_MSG,errorMsg}) 23 | 24 | // 登录的异步action 25 | export const login = (username,password) => { 26 | return async dispatch => { 27 | // 1、执行异步ajax请求 28 | const result = await reqLogin(username,password) 29 | //{status: 0,data: user} {status: 1,msg: 'xxx'} 30 | // 2.1、如果成功 分发成功的同步action 31 | if (result.status === 0){ 32 | const user = result.data 33 | //保存user 34 | storageUtils.saveUser(user) 35 | //分发接收用户的同步action 36 | dispatch(receiveUser(user)) 37 | } 38 | // 2.2、如果失败 分发失败的同步action 39 | const msg = result.msg 40 | dispatch(showErrorMsg(msg)) 41 | } 42 | } 43 | 44 | // 退出登录的同步action 45 | export const logout = () => { 46 | // 删除local中的user 47 | storageUtils.removeUser() 48 | // 返回action对象 49 | return {type:RESET_USER} 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/login/login.less: -------------------------------------------------------------------------------- 1 | .login{ 2 | width: 100%; 3 | height: 100%; 4 | background-image: url(./images/bg.jpg); 5 | background-size: 100% 100%; 6 | 7 | .login-header{ 8 | display: flex; 9 | align-items: center; 10 | height: 80px; 11 | background-color: rgba(21,20,13,.5); 12 | img { 13 | width: 40px; 14 | height: 40px; 15 | margin: 0 15px 0 50px; 16 | } 17 | h1 { 18 | display: inline-block; 19 | font-size: 32px; 20 | color: #fff; 21 | padding-top: 10px; 22 | } 23 | } 24 | 25 | .login-content { 26 | position: relative; 27 | width: 400px; 28 | height: 300px; 29 | background-color: #fff; 30 | margin: 50px auto; 31 | padding: 20px 40px; 32 | h2 { 33 | text-align: center; 34 | font-size: 28px; 35 | font-weight: bold; 36 | margin-bottom: 28px; 37 | } 38 | .error-msg{ 39 | visibility: hidden; 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | text-align: center; 44 | height: 30px; 45 | width: 100%; 46 | background-color: #f60c1a; 47 | color: #fff; 48 | font-size: 16px; 49 | transform: translateY(-30px); 50 | transition: all .3s; 51 | &.show { 52 | visibility: visible; 53 | transform: translateY(0); 54 | } 55 | } 56 | .login-form{ 57 | .login-form-button { 58 | width: 100%; 59 | } 60 | } 61 | } 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/pages/chars/line.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,Button} from 'antd' 3 | import ReactEchars from 'echarts-for-react' 4 | 5 | // 柱形图 6 | export default class Line extends Component { 7 | 8 | state = { 9 | // 销量的数组 10 | datas: [820, 987, 901, 1074, 1290, 1330, 1590], 11 | // 库存的数组 12 | days: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 13 | } 14 | 15 | // 更新回调 16 | update = () => { 17 | this.setState(state => ({ 18 | datas: state.datas.map(data => data + Math.floor(Math.random() * 500 + 1)), 19 | })) 20 | } 21 | 22 | // 返回折线图的配置对象 23 | getOption = (datas,days) => { 24 | return { 25 | title: { 26 | text: '动态数据 + 时间坐标轴' 27 | }, 28 | xAxis: { 29 | type: 'category', 30 | data: days 31 | }, 32 | yAxis: { 33 | type: 'value' 34 | }, 35 | series: [{ 36 | data: datas, 37 | type: 'line', 38 | smooth: true 39 | }] 40 | }; 41 | 42 | } 43 | 44 | render() { 45 | const {datas,days} = this.state 46 | return ( 47 |
48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 |
57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/compatible": "^1.0.8", 7 | "@testing-library/jest-dom": "^5.11.9", 8 | "@testing-library/react": "^11.2.3", 9 | "@testing-library/user-event": "^12.6.0", 10 | "antd": "^4.10.3", 11 | "axios": "^0.21.1", 12 | "babel-plugin-import": "^1.13.3", 13 | "customize-cra": "^1.0.0", 14 | "draft-js": "^0.11.7", 15 | "draftjs-to-html": "^0.9.1", 16 | "echarts": "^5.0.2", 17 | "echarts-for-react": "^3.0.0", 18 | "html-to-draftjs": "^1.5.0", 19 | "jsonp": "^0.2.1", 20 | "less": "^4.1.0", 21 | "less-loader": "^7.2.1", 22 | "prop-types": "^15.7.2", 23 | "react": "^17.0.1", 24 | "react-app-rewired": "^2.1.8", 25 | "react-dom": "^17.0.1", 26 | "react-draft-wysiwyg": "^1.14.5", 27 | "react-redux": "^7.2.2", 28 | "react-router-dom": "^5.2.0", 29 | "react-scripts": "4.0.1", 30 | "redux": "^4.0.5", 31 | "redux-devtools-extension": "^2.13.8", 32 | "redux-thunk": "^2.3.0", 33 | "store": "^2.0.12", 34 | "web-vitals": "^0.2.4" 35 | }, 36 | "scripts": { 37 | "start": "react-app-rewired start", 38 | "build": "react-app-rewired build", 39 | "test": "react-app-rewired test", 40 | "eject": "react-app-rewired eject" 41 | }, 42 | "eslintConfig": { 43 | "extends": [ 44 | "react-app", 45 | "react-app/jest" 46 | ] 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | }, 60 | "proxy": "http://zlx.cool:5000" 61 | } 62 | -------------------------------------------------------------------------------- /src/config/menuConfig.js: -------------------------------------------------------------------------------- 1 | import { 2 | AppstoreOutlined, 3 | BarChartOutlined, 4 | AreaChartOutlined, 5 | LineChartOutlined, 6 | PieChartOutlined, 7 | DesktopOutlined, 8 | ContainerOutlined, 9 | MailOutlined, 10 | UserOutlined 11 | } from '@ant-design/icons'; 12 | 13 | const menuList = [ 14 | { 15 | title: '首页', //菜单标题名称 16 | key: '/home', //对应的path 17 | icon: , //图标名称 18 | isPublic: true //这是为公开的界面 所有用户都可以看到 19 | }, 20 | { 21 | title: '商品', 22 | key: '/products', 23 | icon: , 24 | children: [ //子菜单列表 25 | { 26 | title: '品类管理', 27 | key: '/category', 28 | icon: 29 | }, 30 | { 31 | title: '商品管理', 32 | key: '/product', 33 | icon: 34 | } 35 | ] 36 | }, 37 | { 38 | title: '用户管理', 39 | key: '/user', 40 | icon: , 41 | }, 42 | { 43 | title: '角色管理', 44 | key: '/role', 45 | icon: , 46 | }, 47 | { 48 | title: '图形图表', 49 | key: '/chars', 50 | icon: , 51 | children: [ 52 | { 53 | title: '柱形图', 54 | key: '/chars/bar', 55 | icon: 56 | }, 57 | { 58 | title: '折线图', 59 | key: '/chars/line', 60 | icon: 61 | }, 62 | { 63 | title: '饼型图', 64 | key: '/chars/pie', 65 | icon: 66 | } 67 | ] 68 | } 69 | ] 70 | 71 | export default menuList -------------------------------------------------------------------------------- /src_react/config/menuConfig.js: -------------------------------------------------------------------------------- 1 | import { 2 | AppstoreOutlined, 3 | BarChartOutlined, 4 | AreaChartOutlined, 5 | LineChartOutlined, 6 | PieChartOutlined, 7 | DesktopOutlined, 8 | ContainerOutlined, 9 | MailOutlined, 10 | UserOutlined 11 | } from '@ant-design/icons'; 12 | 13 | const menuList = [ 14 | { 15 | title: '首页', //菜单标题名称 16 | key: '/home', //对应的path 17 | icon: , //图标名称 18 | isPublic: true //这是为公开的界面 所有用户都可以看到 19 | }, 20 | { 21 | title: '商品', 22 | key: '/products', 23 | icon: , 24 | children: [ //子菜单列表 25 | { 26 | title: '品类管理', 27 | key: '/category', 28 | icon: 29 | }, 30 | { 31 | title: '商品管理', 32 | key: '/product', 33 | icon: 34 | } 35 | ] 36 | }, 37 | { 38 | title: '用户管理', 39 | key: '/user', 40 | icon: , 41 | }, 42 | { 43 | title: '角色管理', 44 | key: '/role', 45 | icon: , 46 | }, 47 | { 48 | title: '图形图表', 49 | key: '/chars', 50 | icon: , 51 | children: [ 52 | { 53 | title: '柱形图', 54 | key: '/chars/bar', 55 | icon: 56 | }, 57 | { 58 | title: '折线图', 59 | key: '/chars/line', 60 | icon: 61 | }, 62 | { 63 | title: '饼型图', 64 | key: '/chars/pie', 65 | icon: 66 | } 67 | ] 68 | } 69 | ] 70 | 71 | export default menuList -------------------------------------------------------------------------------- /src/pages/user/user-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Input,Select} from 'antd' 3 | 4 | const Item = Form.Item 5 | const Option = Select.Option 6 | 7 | // 创建角色的form组件 8 | const UserForm = (props) => { 9 | const [form] = Form.useForm(); 10 | props.setForm(form) 11 | const roles = props.roles 12 | // const user = props.user || {} 13 | const user = props.user 14 | // console.log(user.role_id); 15 | return ( 16 |
17 | 20 | 21 | 22 | { 23 | user._id ? null : 26 | 27 | 28 | } 29 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 44 | 45 |
46 | ) 47 | } 48 | 49 | export default UserForm 50 | -------------------------------------------------------------------------------- /src_react/pages/user/user-form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Form,Input,Select} from 'antd' 3 | 4 | const Item = Form.Item 5 | const Option = Select.Option 6 | 7 | // 创建角色的form组件 8 | const UserForm = (props) => { 9 | const [form] = Form.useForm(); 10 | props.setForm(form) 11 | const roles = props.roles 12 | // const user = props.user || {} 13 | const user = props.user 14 | // console.log(user.role_id); 15 | return ( 16 |
17 | 20 | 21 | 22 | { 23 | user._id ? null : 26 | 27 | 28 | } 29 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 44 | 45 |
46 | ) 47 | } 48 | 49 | export default UserForm 50 | -------------------------------------------------------------------------------- /src/pages/chars/bar.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,Button} from 'antd' 3 | import ReactEchars from 'echarts-for-react' 4 | 5 | // 柱形图 6 | export default class Bar extends Component { 7 | 8 | state = { 9 | // 销量的数组 10 | sales: [5, 20, 36, 10, 10, 20], 11 | // 库存的数组 12 | stores: [15, 23, 43, 16, 14, 29] 13 | } 14 | 15 | // 更新回调 16 | update = () => { 17 | this.setState(state => ({ 18 | sales: state.sales.map(sale => sale + 1), 19 | stores: state.stores.reduce((pre,store)=>{ 20 | pre.push(store - 1) 21 | return pre 22 | },[]) 23 | })) 24 | } 25 | 26 | // 返回柱状图的配置对象 27 | getOption = (sales,stores) => { 28 | return { 29 | title: { 30 | text: 'ECharts 入门示例' 31 | }, 32 | tooltip: {}, 33 | legend: { 34 | data:['销量','库存'] 35 | }, 36 | xAxis: { 37 | data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"] 38 | }, 39 | yAxis: {}, 40 | series: [{ 41 | name: '销量', 42 | type: 'bar', 43 | data: sales 44 | }, 45 | { 46 | name: '库存', 47 | type: 'bar', 48 | data: stores 49 | }] 50 | } 51 | 52 | } 53 | 54 | render() { 55 | const {sales,stores} = this.state 56 | return ( 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src_react/pages/admin/admin.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Redirect,Route,Switch } from 'react-router-dom' 3 | import { Layout } from 'antd'; 4 | 5 | import memoryUtils from '../../utils/memoryUtils' 6 | import LeftNav from '../../components/left-nav' 7 | import Header from '../../components/header' 8 | import Home from '../home/home' 9 | import Category from '../category/category' 10 | import Product from '../product/product' 11 | import User from '../user/user' 12 | import Role from '../role/role' 13 | import Bar from '../chars/bar' 14 | import Line from '../chars/line' 15 | import Pie from '../chars/pie' 16 | 17 | const { Footer, Sider, Content } = Layout; 18 | 19 | // 后台管理的路由组件 20 | export default class Admin extends Component { 21 | 22 | render() { 23 | const user = memoryUtils.user 24 | // 如果内存中没有存储user ==> 当前没有登录 25 | if(!user || !user._id) { 26 | // 自动跳转到登录页面(在render()中) 27 | return 28 | } 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 |
Header
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
推荐使用谷歌浏览器,可以获得更佳页面操作体验!
50 |
51 |
52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/pages/admin/admin.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Redirect,Route,Switch } from 'react-router-dom' 3 | import { Layout } from 'antd'; 4 | import {connect} from 'react-redux' 5 | 6 | import LeftNav from '../../components/left-nav' 7 | import Header from '../../components/header' 8 | import Home from '../home/home' 9 | import Category from '../category/category' 10 | import Product from '../product/product' 11 | import User from '../user/user' 12 | import Role from '../role/role' 13 | import Bar from '../chars/bar' 14 | import Line from '../chars/line' 15 | import Pie from '../chars/pie' 16 | import NotFound from '../not-found/not-found' 17 | 18 | const { Footer, Sider, Content } = Layout; 19 | 20 | // 后台管理的路由组件 21 | class Admin extends Component { 22 | 23 | render() { 24 | const user = this.props.user 25 | // 如果内存中没有存储user ==> 当前没有登录 26 | if(!user || !user._id) { 27 | // 自动跳转到登录页面(在render()中) 28 | return 29 | } 30 | return ( 31 | 32 | 33 | 34 | 35 | 36 |
Header
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {/* 上面没有一个匹配 直接显示 */} 49 | 50 | 51 | 52 |
推荐使用谷歌浏览器,可以获得更佳页面操作体验!
53 |
54 |
55 | ) 56 | } 57 | } 58 | 59 | export default connect( 60 | state => ({user: state.user}) 61 | )(Admin) 62 | -------------------------------------------------------------------------------- /src/pages/chars/pie.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,Button} from 'antd' 3 | import ReactEchars from 'echarts-for-react' 4 | 5 | // 柱形图 6 | export default class Line extends Component { 7 | 8 | state = { 9 | // 库存的数组 10 | categorys: ['rose1', 'rose2', 'rose3', 'rose4', 'rose5', 'rose6', 'rose7', 'rose8'] 11 | } 12 | 13 | // 更新回调 14 | update = () => { 15 | this.setState(state => ({ 16 | radiuses: state.radiuses.map(radius => radius.value + Math.floor(Math.random() * 10 + 1)), 17 | areas: state.areas.map(area => area.value + Math.floor(Math.random() * 10 + 1)), 18 | })) 19 | } 20 | 21 | // 返回折线图的配置对象 22 | getOption = () => { 23 | return { 24 | // height: '460px', 25 | title: { 26 | text: '某站点用户访问来源', 27 | // subtext: '纯属虚构', 28 | left: 'center' 29 | }, 30 | tooltip: { 31 | trigger: 'item' 32 | }, 33 | legend: { 34 | orient: 'vertical', 35 | left: 'left', 36 | }, 37 | series: [ 38 | { 39 | name: '访问来源', 40 | type: 'pie', 41 | radius: '50%', 42 | data: [ 43 | {value: 1048, name: '搜索引擎'}, 44 | {value: 735, name: '直接访问'}, 45 | {value: 580, name: '邮件营销'}, 46 | {value: 484, name: '联盟广告'}, 47 | {value: 300, name: '视频广告'} 48 | ], 49 | emphasis: { 50 | itemStyle: { 51 | shadowBlur: 10, 52 | shadowOffsetX: 0, 53 | shadowColor: 'rgba(0, 0, 0, 0.5)' 54 | } 55 | } 56 | } 57 | ] 58 | }; 59 | } 60 | 61 | render() { 62 | // const {categorys,radiuses,areas} = this.state 63 | return ( 64 |
65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 |
74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/login/login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form, Input, Button} from 'antd'; 3 | import { UserOutlined, LockOutlined } from '@ant-design/icons'; 4 | import {connect} from 'react-redux' 5 | 6 | import './login.less' 7 | import logo from '../../assets/imgs/logo.png' 8 | import {login} from '../../redux/actions' 9 | 10 | import { Redirect } from 'react-router-dom'; 11 | 12 | const Login = (props) => { 13 | 14 | const [form] = Form.useForm(); 15 | 16 | // 如果用户已经登录 自动跳转到管理界面 17 | const user = props.user 18 | if (user._id) { 19 | return 20 | } 21 | 22 | const onFinish = (values) => { 23 | // console.log('Received values of form: ', values); 24 | 25 | // 请求登录 26 | const {username,password} = values 27 | 28 | // 调用分发异步action的函数 ===》发送ajax请求 29 | // 有了结果之后更新状态 30 | props.login(username,password) 31 | 32 | }; 33 | 34 | return ( 35 |
36 |
37 | logo 38 |

React项目:后台管理系统

39 |
40 |
41 |
{user.errorMsg}
42 |

用户登陆

43 |
49 | 59 | } placeholder="用户名" style={{ color: 'rgba(0,0,0,.25)' }} /> 60 | 61 | 70 | } type="password" placeholder="密码" style={{ color: 'rgba(0,0,0,.25)' }}/> 71 | 72 | 73 | 76 | 77 |
78 |
79 |
80 | ); 81 | }; 82 | 83 | export default connect( 84 | state => ({user: state.user}), 85 | {login} 86 | )(Login) 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/pages/product/rich-text-editor.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Editor } from 'react-draft-wysiwyg' 4 | import {ContentState, convertToRaw, EditorState} from 'draft-js' 5 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' 6 | import draftToHtml from 'draftjs-to-html' 7 | import htmlToDraft from 'html-to-draftjs' 8 | 9 | // 用来指定商品详情的富文本编程器组件 10 | export default class RichTextEditor extends Component { 11 | 12 | static propTypes = { 13 | detail: PropTypes.string 14 | } 15 | state = { 16 | // 创建一个没有内容的编辑对象 17 | editorState: EditorState.createEmpty() 18 | } 19 | 20 | constructor(props) { 21 | super(props) 22 | const html = this.props.detail 23 | if (html) { //如果有值创建包含内容的html对象 24 | const contentBlock = htmlToDraft(html) 25 | if (contentBlock) { 26 | const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks) 27 | const editorState = EditorState.createWithContent(contentState) 28 | this.state = { 29 | editorState, 30 | } 31 | } 32 | } else { 33 | this.state = { 34 | editorState: EditorState.createEmpty() 35 | } 36 | } 37 | } 38 | 39 | 40 | 41 | // 输入过程中实时的回调 42 | onEditorStateChange = (editorState) => { 43 | this.setState({editorState}) 44 | } 45 | 46 | getDetail = () => { 47 | // 返回输入数据对应的html格式的文本 48 | return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent())) 49 | } 50 | 51 | uploadImageCallback = (file) => { 52 | return new Promise( 53 | (resolve,reject) => { 54 | const xhr = new XMLHttpRequest() 55 | xhr.open('POST','/manage/img/upload') 56 | const data = new FormData() 57 | data.append('image',file) 58 | xhr.send(data) 59 | xhr.addEventListener('load',()=>{ 60 | const response = JSON.parse(xhr.responseText) 61 | const url = response.data.url //得到图片的url 62 | resolve({data:{link:url}}) 63 | }) 64 | xhr.addEventListener('load',()=>{ 65 | const error = JSON.parse(xhr.responseText) 66 | reject(error) 67 | }) 68 | } 69 | ) 70 | } 71 | 72 | render() { 73 | const {editorState} = this.state 74 | return ( 75 | 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src_react/pages/product/rich-text-editor.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Editor } from 'react-draft-wysiwyg' 4 | import {ContentState, convertToRaw, EditorState} from 'draft-js' 5 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' 6 | import draftToHtml from 'draftjs-to-html' 7 | import htmlToDraft from 'html-to-draftjs' 8 | 9 | // 用来指定商品详情的富文本编程器组件 10 | export default class RichTextEditor extends Component { 11 | 12 | static propTypes = { 13 | detail: PropTypes.string 14 | } 15 | state = { 16 | // 创建一个没有内容的编辑对象 17 | editorState: EditorState.createEmpty() 18 | } 19 | 20 | constructor(props) { 21 | super(props) 22 | const html = this.props.detail 23 | if (html) { //如果有值创建包含内容的html对象 24 | const contentBlock = htmlToDraft(html) 25 | if (contentBlock) { 26 | const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks) 27 | const editorState = EditorState.createWithContent(contentState) 28 | this.state = { 29 | editorState, 30 | } 31 | } 32 | } else { 33 | this.state = { 34 | editorState: EditorState.createEmpty() 35 | } 36 | } 37 | } 38 | 39 | 40 | 41 | // 输入过程中实时的回调 42 | onEditorStateChange = (editorState) => { 43 | this.setState({editorState}) 44 | } 45 | 46 | getDetail = () => { 47 | // 返回输入数据对应的html格式的文本 48 | return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent())) 49 | } 50 | 51 | uploadImageCallback = (file) => { 52 | return new Promise( 53 | (resolve,reject) => { 54 | const xhr = new XMLHttpRequest() 55 | xhr.open('POST','/manage/img/upload') 56 | const data = new FormData() 57 | data.append('image',file) 58 | xhr.send(data) 59 | xhr.addEventListener('load',()=>{ 60 | const response = JSON.parse(xhr.responseText) 61 | const url = response.data.url //得到图片的url 62 | resolve({data:{link:url}}) 63 | }) 64 | xhr.addEventListener('load',()=>{ 65 | const error = JSON.parse(xhr.responseText) 66 | reject(error) 67 | }) 68 | } 69 | ) 70 | } 71 | 72 | render() { 73 | const {editorState} = this.state 74 | return ( 75 | 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /src/components/header/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import { Modal } from 'antd' 4 | import {connect} from 'react-redux' 5 | 6 | import {formateDate} from '../../utils/dateUtils' 7 | import {reqWeather} from '../../api' 8 | import LinkButton from '../../components/link-button' 9 | import {logout} from '../../redux/actions' 10 | import './index.less' 11 | 12 | class Header extends Component { 13 | 14 | // 设计状态 15 | state = { 16 | currentTime: formateDate(Date.now()), //当前时间的字符串形式 17 | weather: '' //天气的文本 18 | } 19 | 20 | getTime = () => { 21 | // 每隔一秒钟获取当前时间 并更新状态数据currentTime 22 | this.timer = setInterval(() => { 23 | const currentTime = formateDate(Date.now()) 24 | this.setState({currentTime}) 25 | }, 1000); 26 | } 27 | 28 | getWeather = async() => { 29 | // 调用接口请求异步获取 30 | const weather = await reqWeather(110101) 31 | // 更新状态 32 | this.setState({weather}) 33 | } 34 | 35 | /* getTitle = () => { 36 | // 得到当前请求路径 37 | const path = this.props.location.pathname 38 | let title 39 | menuList.forEach(item=>{ 40 | if (item.key === path) { //如果当前item对象的key与path匹配 则item的title就是需要显示的title 41 | title = item.title 42 | } else if (item.children) { 43 | // 在所有的子item中进行查找匹配 返回一个布尔值 44 | const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0) 45 | // 如果有值则说明成功匹配 且取出他的title显示 46 | if (cItem) { 47 | title = cItem.title 48 | } 49 | } 50 | }) 51 | return title 52 | } */ 53 | 54 | // 退出登录 55 | logout = () => { 56 | // 显示确认框 57 | Modal.confirm({ 58 | content: '确认退出?', 59 | onOk: () => { 60 | console.log('退出'); 61 | this.props.logout() 62 | } 63 | /* , 64 | onCancel() { 65 | console.log('取消'); 66 | } */ 67 | 68 | }) 69 | } 70 | 71 | // 在第一次render()之后执行一次 72 | // 一般在此执行异步操作:发送ajax请求以及启动定时器 73 | componentDidMount(){ 74 | // 获取当前时间 75 | this.getTime() 76 | // 获取当前天气文本 77 | this.getWeather() 78 | } 79 | 80 | // 清除定时器 81 | componentWillUnmount() { 82 | clearInterval(this.timer) 83 | } 84 | 85 | render() { 86 | // console.log('render'); 87 | const {currentTime,weather} = this.state 88 | const username = this.props.user.username 89 | // 得到当前需要显示的title 90 | // const title = this.getTitle() 91 | const title = this.props.headTitle 92 | return ( 93 |
94 |
95 | 欢迎,{username} 96 | {/* 退出 */} 97 | 退出 98 |
99 |
100 |
{title}
101 |
102 | {currentTime} 103 | 天气 104 | {weather} 105 |
106 |
107 |
108 | ) 109 | } 110 | } 111 | 112 | export default connect( 113 | state => ({headTitle: state.headTitle, user: state.user}), 114 | {logout} 115 | )(withRouter(Header)) 116 | -------------------------------------------------------------------------------- /src/api/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | 要求: 能够根据接口文档定义接口请求 3 | 包含应用中所有接口请求函数的模块 4 | 每个函数的返回值都是promise 5 | 6 | 基本要求:能够根据接口文档定义接口请求函数 7 | */ 8 | import ajax from './ajax' 9 | import jsonp from 'jsonp' 10 | import { message } from 'antd' 11 | 12 | const BASE = '' 13 | 14 | // 登录接口 15 | /* export function reqLogin(username,password) { 16 | return ajax('/login',{username,password},'POST') 17 | } */ 18 | export const reqLogin = (username,password) => ajax(BASE + '/login',{username,password},'POST') 19 | 20 | // 添加新用户 21 | // export const reqAddUser = (user) => ajax(BASE + '/manage/user/add',user,'POST') 22 | 23 | // jsonp请求的接口请请求函数 获取天气查询的接口 24 | export const reqWeather = (cityCode) => { 25 | 26 | return new Promise((resolve,reject)=>{ 27 | const url = `https://restapi.amap.com/v3/weather/weatherInfo?city=${cityCode}&key=36cc4d40112f012a9568e75cd1d8332e` 28 | // 发送jsonp请求 29 | jsonp(url,{},(err,data)=>{ 30 | // console.log(err,data); 31 | // 如果成功了 32 | if (!err && data.status === '1') { 33 | const {weather} = data.lives[0] 34 | // console.log(weather); 35 | resolve(weather) 36 | } else { 37 | // 如果失败了 38 | message.error('获取天气信息失败...') 39 | } 40 | }) 41 | }) 42 | 43 | } 44 | // reqWeather(110101) 45 | 46 | // 获取一级/二级分类的列表 47 | export const reqCategorys = (parentId) => ajax(BASE + '/manage/category/list',{parentId}) 48 | // 添加分类 49 | export const reqAddCategorys = (categoryName,parentId) => ajax(BASE + '/manage/category/add',{categoryName,parentId},'POST') 50 | 51 | // 更新分类 52 | export const reqUpdateCategorys = ({categoryId,categoryName}) => ajax(BASE + '/manage/category/update',{categoryName,categoryId},'POST') 53 | 54 | // 获取商品分页列表 55 | export const reqProducts = (pageNum,pageSize) => ajax(BASE+'/manage/product/list',{pageNum,pageSize}) 56 | 57 | // 搜索商品分页列表 (根据商品名称 / 商品描述) 58 | // searchType: 搜索的类型, productName/productDesc 59 | export const reqSearchProducts = ({pageNum,pageSize,searchName,searchType}) => ajax(BASE+'/manage/product/search',{ 60 | pageNum,pageSize, 61 | // 将一个变量的值变成属性名 就要加[] 62 | [searchType]: searchName 63 | }) 64 | 65 | // 搜索商品分页列表 (根据商品描述) 66 | // export const reqSearchProducts2 = ({pageNum,pageSize,searchName}) => ajax(BASE+'/manage/product/search',{ 67 | // pageNum,pageSize,productDesc: searchName 68 | // }) 69 | 70 | // 获取一个分类 71 | export const reqCategory = (categoryId) => ajax(BASE + '/manage/category/info',{categoryId}) 72 | 73 | // 更新商品的状态(上架或下架) 74 | export const reqUpdateStatus = (productId,status) => ajax(BASE + '/manage/product/updateStatus',{productId,status},'POST') 75 | 76 | // 删除指定名称图片的接口 77 | export const reqDeleteImg = (name) => ajax(BASE + '/manage/img/delete',{name},'POST') 78 | 79 | // 添加或者修改商品 80 | // export const reqAddOrUpdateProduct = (product) => ajax(BASE + '/manage/product/' + (product._id ? 'update':'add') ,product,'POST') 81 | // debugger 82 | export const reqAddProduct = (product) => ajax(BASE + '/manage/product/add',product,'POST') 83 | 84 | // 修改商品 85 | export const reaUpdateProduct = (product) => ajax(BASE+'/manage/product/update',product,'POST') 86 | 87 | // 获取所有角色的列表 88 | export const reqRoles = () => ajax(BASE + '/manage/role/list') 89 | 90 | // 添加角色 91 | export const reqAddRole = (roleName) => ajax(BASE+'/manage/role/add',{roleName},'POST') 92 | 93 | // 更新角色 94 | export const reqUpdateRole = (role) => ajax(BASE+'/manage/role/update',role,'POST') 95 | 96 | // 获取所有用户的列表 97 | export const reqUsers = () => ajax(BASE+'/manage/user/list') 98 | 99 | // 删除指定用户 100 | export const reqDeleteUser = (userId) => ajax(BASE+'/manage/user/delete',{userId},'POST') 101 | 102 | //添加/更新用户 103 | export const reqAddOrUpdateUser = (user) => ajax(BASE+'/manage/user/'+(user._id?'update':'add'),user,'POST') 104 | 105 | -------------------------------------------------------------------------------- /src_react/api/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | 要求: 能够根据接口文档定义接口请求 3 | 包含应用中所有接口请求函数的模块 4 | 每个函数的返回值都是promise 5 | 6 | 基本要求:能够根据接口文档定义接口请求函数 7 | */ 8 | import ajax from './ajax' 9 | import jsonp from 'jsonp' 10 | import { message } from 'antd' 11 | 12 | const BASE = '' 13 | 14 | // 登录接口 15 | /* export function reqLogin(username,password) { 16 | return ajax('/login',{username,password},'POST') 17 | } */ 18 | export const reqLogin = (username,password) => ajax(BASE + '/login',{username,password},'POST') 19 | 20 | // 添加新用户 21 | // export const reqAddUser = (user) => ajax(BASE + '/manage/user/add',user,'POST') 22 | 23 | // jsonp请求的接口请请求函数 获取天气查询的接口 24 | export const reqWeather = (cityCode) => { 25 | 26 | return new Promise((resolve,reject)=>{ 27 | const url = `https://restapi.amap.com/v3/weather/weatherInfo?city=${cityCode}&key=36cc4d40112f012a9568e75cd1d8332e` 28 | // 发送jsonp请求 29 | jsonp(url,{},(err,data)=>{ 30 | // console.log(err,data); 31 | // 如果成功了 32 | if (!err && data.status === '1') { 33 | const {weather} = data.lives[0] 34 | // console.log(weather); 35 | resolve(weather) 36 | } else { 37 | // 如果失败了 38 | message.error('获取天气信息失败...') 39 | } 40 | }) 41 | }) 42 | 43 | } 44 | // reqWeather(110101) 45 | 46 | // 获取一级/二级分类的列表 47 | export const reqCategorys = (parentId) => ajax(BASE + '/manage/category/list',{parentId}) 48 | // 添加分类 49 | export const reqAddCategorys = (categoryName,parentId) => ajax(BASE + '/manage/category/add',{categoryName,parentId},'POST') 50 | 51 | // 更新分类 52 | export const reqUpdateCategorys = ({categoryId,categoryName}) => ajax(BASE + '/manage/category/update',{categoryName,categoryId},'POST') 53 | 54 | // 获取商品分页列表 55 | export const reqProducts = (pageNum,pageSize) => ajax(BASE+'/manage/product/list',{pageNum,pageSize}) 56 | 57 | // 搜索商品分页列表 (根据商品名称 / 商品描述) 58 | // searchType: 搜索的类型, productName/productDesc 59 | export const reqSearchProducts = ({pageNum,pageSize,searchName,searchType}) => ajax(BASE+'/manage/product/search',{ 60 | pageNum,pageSize, 61 | // 将一个变量的值变成属性名 就要加[] 62 | [searchType]: searchName 63 | }) 64 | 65 | // 搜索商品分页列表 (根据商品描述) 66 | // export const reqSearchProducts2 = ({pageNum,pageSize,searchName}) => ajax(BASE+'/manage/product/search',{ 67 | // pageNum,pageSize,productDesc: searchName 68 | // }) 69 | 70 | // 获取一个分类 71 | export const reqCategory = (categoryId) => ajax(BASE + '/manage/category/info',{categoryId}) 72 | 73 | // 更新商品的状态(上架或下架) 74 | export const reqUpdateStatus = (productId,status) => ajax(BASE + '/manage/product/updateStatus',{productId,status},'POST') 75 | 76 | // 删除指定名称图片的接口 77 | export const reqDeleteImg = (name) => ajax(BASE + '/manage/img/delete',{name},'POST') 78 | 79 | // 添加或者修改商品 80 | // export const reqAddOrUpdateProduct = (product) => ajax(BASE + '/manage/product/' + (product._id ? 'update':'add') ,product,'POST') 81 | // debugger 82 | export const reqAddProduct = (product) => ajax(BASE + '/manage/product/add',product,'POST') 83 | 84 | // 修改商品 85 | export const reaUpdateProduct = (product) => ajax(BASE+'/manage/product/update',product,'POST') 86 | 87 | // 获取所有角色的列表 88 | export const reqRoles = () => ajax(BASE + '/manage/role/list') 89 | 90 | // 添加角色 91 | export const reqAddRole = (roleName) => ajax(BASE+'/manage/role/add',{roleName},'POST') 92 | 93 | // 更新角色 94 | export const reqUpdateRole = (role) => ajax(BASE+'/manage/role/update',role,'POST') 95 | 96 | // 获取所有用户的列表 97 | export const reqUsers = () => ajax(BASE+'/manage/user/list') 98 | 99 | // 删除指定用户 100 | export const reqDeleteUser = (userId) => ajax(BASE+'/manage/user/delete',{userId},'POST') 101 | 102 | //添加/更新用户 103 | export const reqAddOrUpdateUser = (user) => ajax(BASE+'/manage/user/'+(user._id?'update':'add'),user,'POST') 104 | 105 | -------------------------------------------------------------------------------- /src_react/components/header/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import { Modal } from 'antd' 4 | import {connect} from 'react-redux' 5 | 6 | import {formateDate} from '../../utils/dateUtils' 7 | import menuList from '../../config/menuConfig' 8 | import memoryUtils from '../../utils/memoryUtils' 9 | import storageUtils from '../../utils/storageUtils' 10 | import {reqWeather} from '../../api' 11 | import LinkButton from '../link-button' 12 | import './index.less' 13 | 14 | class Header extends Component { 15 | 16 | // 设计状态 17 | state = { 18 | currentTime: formateDate(Date.now()), //当前时间的字符串形式 19 | weather: '' //天气的文本 20 | } 21 | 22 | getTime = () => { 23 | // 每隔一秒钟获取当前时间 并更新状态数据currentTime 24 | this.timer = setInterval(() => { 25 | const currentTime = formateDate(Date.now()) 26 | this.setState({currentTime}) 27 | }, 1000); 28 | } 29 | 30 | getWeather = async() => { 31 | // 调用接口请求异步获取 32 | const weather = await reqWeather(110101) 33 | // 更新状态 34 | this.setState({weather}) 35 | } 36 | 37 | /* getTitle = () => { 38 | // 得到当前请求路径 39 | const path = this.props.location.pathname 40 | let title 41 | menuList.forEach(item=>{ 42 | if (item.key === path) { //如果当前item对象的key与path匹配 则item的title就是需要显示的title 43 | title = item.title 44 | } else if (item.children) { 45 | // 在所有的子item中进行查找匹配 返回一个布尔值 46 | const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0) 47 | // 如果有值则说明成功匹配 且取出他的title显示 48 | if (cItem) { 49 | title = cItem.title 50 | } 51 | } 52 | }) 53 | return title 54 | } */ 55 | 56 | // 退出登录 57 | logout = () => { 58 | // 显示确认框 59 | Modal.confirm({ 60 | content: '确认退出?', 61 | onOk: () => { 62 | console.log('退出'); 63 | // console.log(this.props); 64 | // 删除保存的数据 65 | storageUtils.removeUser() 66 | memoryUtils.user = {} 67 | 68 | // 跳转到login页面 69 | this.props.history.replace('/login') 70 | 71 | } 72 | /* , 73 | onCancel() { 74 | console.log('取消'); 75 | } */ 76 | 77 | }) 78 | } 79 | 80 | // 在第一次render()之后执行一次 81 | // 一般在此执行异步操作:发送ajax请求以及启动定时器 82 | componentDidMount(){ 83 | // 获取当前时间 84 | this.getTime() 85 | // 获取当前天气文本 86 | this.getWeather() 87 | } 88 | 89 | // 清除定时器 90 | componentWillUnmount() { 91 | clearInterval(this.timer) 92 | } 93 | 94 | render() { 95 | // console.log('render'); 96 | const {currentTime,weather} = this.state 97 | const username = memoryUtils.user.username 98 | // 得到当前需要显示的title 99 | // const title = this.getTitle() 100 | const title = this.props.headTitle 101 | return ( 102 |
103 |
104 | 欢迎,{username} 105 | {/* 退出 */} 106 | 退出 107 |
108 |
109 |
{title}
110 |
111 | {currentTime} 112 | 天气 113 | {weather} 114 |
115 |
116 |
117 | ) 118 | } 119 | } 120 | 121 | export default connect( 122 | state => ({headTitle: state.headTitle}), 123 | {} 124 | )(withRouter(Header)) 125 | -------------------------------------------------------------------------------- /src/pages/product/detail.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,List } from 'antd' 3 | import { ArrowLeftOutlined } from '@ant-design/icons'; 4 | 5 | import LinkButton from '../../components/link-button' 6 | // import {BASE_IMG_URL} from '../../utils/constants' 7 | import {reqCategory} from '../../api/index' 8 | 9 | const Item = List.Item 10 | 11 | // Product 的详情子路由组件 12 | export default class ProductDetail extends Component { 13 | 14 | // 定义状态储存两个分类名称 15 | state = { 16 | cName1: '', //一级分类名称 17 | cName2: '', //二级分类名称 18 | } 19 | 20 | // 异步发送请求 21 | async componentDidMount() { 22 | // 取出两个分类名称ID数值 23 | const {pCategoryId,categoryId} = this.props.location.state.product 24 | // 进行一级分类及二级分类的名称判断 25 | if (pCategoryId === '0') { 26 | // 说明显示的是一级分类的商品 27 | const result = await reqCategory(categoryId) 28 | // 得到分类名称 29 | const cName1 = result.data.name 30 | // 更新状态对象 31 | this.setState({cName1}) 32 | } else { 33 | /* 34 | // 通过多个await方式发送多个请求:效果不影响但存在效率问题 35 | // 即:后一个请求必须在前一个请求发送后得到响应结果之后才会发送 36 | 37 | // 二级分类下的产品 38 | const result1 = await reqCategory(pCategoryId) 39 | const result2 = await reqCategory(categoryId) 40 | // 得到分类名称 41 | const cName1 = result1.data.name 42 | const cName2 = result2.data.name 43 | */ 44 | 45 | // 解决方法:一次性发送多个请求 只有都成功了才正常处理 46 | const results = await Promise.all([reqCategory(pCategoryId),reqCategory(categoryId)]) 47 | const cName1 = results[0].data.name 48 | const cName2 = results[1].data.name 49 | 50 | // 更新状态 51 | this.setState({cName1,cName2}) 52 | 53 | } 54 | } 55 | 56 | render() { 57 | // console.log('detail',this.props.location.state.product); 58 | 59 | // 读取携带过来的state数据 60 | const {name,desc,price} = this.props.location.state.product 61 | // console.log('detail',detail); 62 | 63 | const {cName1,cName2} = this.state 64 | 65 | const title = ( 66 | 67 | 68 | this.props.history.goBack()}/> 71 | 72 | 商品详情 73 | 74 | ) 75 | return ( 76 | 77 | 78 | 79 |

商品名称:

80 |

{name}

81 |
82 | 83 |

商品描述:

84 |

{desc}

85 |
86 | 87 |

商品价格:

88 |

{price}

89 |
90 | 91 |

所属分类:

92 |

{cName1} {cName2 ? '--->' + cName2 : ''}

93 |
94 | 95 |

商品图片:

96 |

97 | {/* { 98 | imgs.map(img => { 99 | return( 100 | 图片1 106 | ) 107 | }) 108 | } */} 109 | 图片1 113 | 图片 116 |

117 |
118 | 119 |

商品详情:

120 | {/* */} 121 |

122 |
123 |
124 |
125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src_react/pages/product/detail.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,List } from 'antd' 3 | import { ArrowLeftOutlined } from '@ant-design/icons'; 4 | 5 | import LinkButton from '../../components/link-button' 6 | // import {BASE_IMG_URL} from '../../utils/constants' 7 | import {reqCategory} from '../../api/index' 8 | 9 | const Item = List.Item 10 | 11 | // Product 的详情子路由组件 12 | export default class ProductDetail extends Component { 13 | 14 | // 定义状态储存两个分类名称 15 | state = { 16 | cName1: '', //一级分类名称 17 | cName2: '', //二级分类名称 18 | } 19 | 20 | // 异步发送请求 21 | async componentDidMount() { 22 | // 取出两个分类名称ID数值 23 | const {pCategoryId,categoryId} = this.props.location.state.product 24 | // 进行一级分类及二级分类的名称判断 25 | if (pCategoryId === '0') { 26 | // 说明显示的是一级分类的商品 27 | const result = await reqCategory(categoryId) 28 | // 得到分类名称 29 | const cName1 = result.data.name 30 | // 更新状态对象 31 | this.setState({cName1}) 32 | } else { 33 | /* 34 | // 通过多个await方式发送多个请求:效果不影响但存在效率问题 35 | // 即:后一个请求必须在前一个请求发送后得到响应结果之后才会发送 36 | 37 | // 二级分类下的产品 38 | const result1 = await reqCategory(pCategoryId) 39 | const result2 = await reqCategory(categoryId) 40 | // 得到分类名称 41 | const cName1 = result1.data.name 42 | const cName2 = result2.data.name 43 | */ 44 | 45 | // 解决方法:一次性发送多个请求 只有都成功了才正常处理 46 | const results = await Promise.all([reqCategory(pCategoryId),reqCategory(categoryId)]) 47 | const cName1 = results[0].data.name 48 | const cName2 = results[1].data.name 49 | 50 | // 更新状态 51 | this.setState({cName1,cName2}) 52 | 53 | } 54 | } 55 | 56 | render() { 57 | // console.log('detail',this.props.location.state.product); 58 | 59 | // 读取携带过来的state数据 60 | const {name,desc,price} = this.props.location.state.product 61 | // console.log('detail',detail); 62 | 63 | const {cName1,cName2} = this.state 64 | 65 | const title = ( 66 | 67 | 68 | this.props.history.goBack()}/> 71 | 72 | 商品详情 73 | 74 | ) 75 | return ( 76 | 77 | 78 | 79 |

商品名称:

80 |

{name}

81 |
82 | 83 |

商品描述:

84 |

{desc}

85 |
86 | 87 |

商品价格:

88 |

{price}

89 |
90 | 91 |

所属分类:

92 |

{cName1} {cName2 ? '--->' + cName2 : ''}

93 |
94 | 95 |

商品图片:

96 |

97 | {/* { 98 | imgs.map(img => { 99 | return( 100 | 图片1 106 | ) 107 | }) 108 | } */} 109 | 图片1 113 | 图片 116 |

117 |
118 | 119 |

商品详情:

120 | {/* */} 121 |

122 |
123 |
124 |
125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/pages/product/pictures-wall.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Upload, Modal, message } from 'antd'; 4 | import {PlusOutlined} from '@ant-design/icons'; 5 | 6 | import { reqDeleteImg } from '../../api' 7 | 8 | // 用于图片上传的组件 9 | 10 | function getBase64(file) { 11 | return new Promise((resolve, reject) => { 12 | const reader = new FileReader(); 13 | reader.readAsDataURL(file); 14 | reader.onload = () => resolve(reader.result); 15 | reader.onerror = error => reject(error); 16 | }); 17 | } 18 | 19 | export default class PicturesWall extends React.Component { 20 | 21 | static propTypes = { 22 | imgs: PropTypes.array 23 | } 24 | 25 | state = { 26 | previewVisible: false, //用来标识是否显示大图预览Modal 27 | previewImage: '', //大图的url 28 | fileList: [ 29 | /* { 30 | uid: '-1', //文件的唯一标识 建议设置为负数 以防和内部id产生冲突 31 | name: 'image.png', //文件名 32 | status: 'done', //状态之一 33 | // done: 已经完成上传 uploading: 正在上传中 removed: 已删除 34 | // 图片地址 35 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', 36 | }, */ 37 | ], 38 | }; 39 | 40 | constructor(props){ 41 | 42 | super(props) 43 | 44 | let fileList = [] 45 | 46 | // 如果传入了imgs属性 47 | const {imgs} = this.props 48 | if (imgs && imgs.length > 0 ) { 49 | fileList = imgs.map((img,index) => ({ 50 | uid: -index, 51 | name: img, 52 | status: 'done', 53 | url: 'http:localhost:5000/upload/' + img 54 | })) 55 | } 56 | 57 | // 初始化状态 58 | this.state = ({ 59 | previewVisible: false, 60 | previewImage: '', 61 | fileList 62 | }) 63 | } 64 | 65 | // 获取所有已上传图片文件名的数组 66 | getImgs = () => { 67 | return this.state.fileList.map(file => file.name) 68 | } 69 | 70 | //隐藏Modal 71 | handleCancel = () => this.setState({ previewVisible: false }); 72 | 73 | handlePreview = async file => { 74 | if (!file.url && !file.preview) { 75 | file.preview = await getBase64(file.originFileObj); 76 | } 77 | // 显示指定file的大图 78 | this.setState({ 79 | previewImage: file.url || file.preview, 80 | previewVisible: true, 81 | }); 82 | }; 83 | 84 | //file: 当前操作的图片文件(上传/删除) 85 | // fileList: 所有已上传图片文件对象的数组 86 | handleChange = async ({ file, fileList }) => { 87 | 88 | // console.log('handleChange',file.status,file,); 89 | 90 | // console.log(file === fileList[fileList.length-1]); false 91 | // 这两个是同一个文件 但是指向两个内容一致的对象 92 | 93 | // 一旦上传成功 将当前上传的file的信息修正(name url) 94 | if (file.status === 'done') { 95 | const result = file.response //{status: 0,data:{name:'xxx',url:'图片地址'}} 96 | if (result.status === 0) { 97 | message.success('上传图片成功') 98 | const {name,url} = result.data 99 | file = fileList[fileList.length-1] 100 | file.name = name 101 | file.url = url 102 | } else { 103 | message.error('上传图片失败') 104 | } 105 | } else if (file.status === 'removed') { 106 | // 删除图片的操作 107 | const result = await reqDeleteImg(file.name) 108 | if (result.status === 0) { 109 | message.success('删除图片成功') 110 | } else { 111 | message.error('删除图片失败') 112 | } 113 | } 114 | 115 | // 在操作(上传/删除)过程中更新fileList的状态 116 | this.setState({ fileList }) 117 | }; 118 | 119 | render() { 120 | const { previewVisible, previewImage, fileList } = this.state; 121 | const uploadButton = ( 122 |
123 | 124 |
Upload
125 |
126 | ); 127 | return ( 128 |
129 | 138 | {fileList.length >= 3 ? null : uploadButton} 139 | 140 | 141 | example 142 | 143 |
144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src_react/pages/product/pictures-wall.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Upload, Modal, message } from 'antd'; 4 | import {PlusOutlined} from '@ant-design/icons'; 5 | 6 | import { reqDeleteImg } from '../../api' 7 | 8 | // 用于图片上传的组件 9 | 10 | function getBase64(file) { 11 | return new Promise((resolve, reject) => { 12 | const reader = new FileReader(); 13 | reader.readAsDataURL(file); 14 | reader.onload = () => resolve(reader.result); 15 | reader.onerror = error => reject(error); 16 | }); 17 | } 18 | 19 | export default class PicturesWall extends React.Component { 20 | 21 | static propTypes = { 22 | imgs: PropTypes.array 23 | } 24 | 25 | state = { 26 | previewVisible: false, //用来标识是否显示大图预览Modal 27 | previewImage: '', //大图的url 28 | fileList: [ 29 | /* { 30 | uid: '-1', //文件的唯一标识 建议设置为负数 以防和内部id产生冲突 31 | name: 'image.png', //文件名 32 | status: 'done', //状态之一 33 | // done: 已经完成上传 uploading: 正在上传中 removed: 已删除 34 | // 图片地址 35 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', 36 | }, */ 37 | ], 38 | }; 39 | 40 | constructor(props){ 41 | 42 | super(props) 43 | 44 | let fileList = [] 45 | 46 | // 如果传入了imgs属性 47 | const {imgs} = this.props 48 | if (imgs && imgs.length > 0 ) { 49 | fileList = imgs.map((img,index) => ({ 50 | uid: -index, 51 | name: img, 52 | status: 'done', 53 | url: 'http:localhost:5000/upload/' + img 54 | })) 55 | } 56 | 57 | // 初始化状态 58 | this.state = ({ 59 | previewVisible: false, 60 | previewImage: '', 61 | fileList 62 | }) 63 | } 64 | 65 | // 获取所有已上传图片文件名的数组 66 | getImgs = () => { 67 | return this.state.fileList.map(file => file.name) 68 | } 69 | 70 | //隐藏Modal 71 | handleCancel = () => this.setState({ previewVisible: false }); 72 | 73 | handlePreview = async file => { 74 | if (!file.url && !file.preview) { 75 | file.preview = await getBase64(file.originFileObj); 76 | } 77 | // 显示指定file的大图 78 | this.setState({ 79 | previewImage: file.url || file.preview, 80 | previewVisible: true, 81 | }); 82 | }; 83 | 84 | //file: 当前操作的图片文件(上传/删除) 85 | // fileList: 所有已上传图片文件对象的数组 86 | handleChange = async ({ file, fileList }) => { 87 | 88 | // console.log('handleChange',file.status,file,); 89 | 90 | // console.log(file === fileList[fileList.length-1]); false 91 | // 这两个是同一个文件 但是指向两个内容一致的对象 92 | 93 | // 一旦上传成功 将当前上传的file的信息修正(name url) 94 | if (file.status === 'done') { 95 | const result = file.response //{status: 0,data:{name:'xxx',url:'图片地址'}} 96 | if (result.status === 0) { 97 | message.success('上传图片成功') 98 | const {name,url} = result.data 99 | file = fileList[fileList.length-1] 100 | file.name = name 101 | file.url = url 102 | } else { 103 | message.error('上传图片失败') 104 | } 105 | } else if (file.status === 'removed') { 106 | // 删除图片的操作 107 | const result = await reqDeleteImg(file.name) 108 | if (result.status === 0) { 109 | message.success('删除图片成功') 110 | } else { 111 | message.error('删除图片失败') 112 | } 113 | } 114 | 115 | // 在操作(上传/删除)过程中更新fileList的状态 116 | this.setState({ fileList }) 117 | }; 118 | 119 | render() { 120 | const { previewVisible, previewImage, fileList } = this.state; 121 | const uploadButton = ( 122 |
123 | 124 |
Upload
125 |
126 | ); 127 | return ( 128 |
129 | 138 | {fileList.length >= 3 ? null : uploadButton} 139 | 140 | 141 | example 142 | 143 |
144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src_react/pages/login/login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Form, Input, Button, message} from 'antd'; 3 | import { UserOutlined, LockOutlined } from '@ant-design/icons'; 4 | import {connect} from 'react-redux' 5 | 6 | import './login.less' 7 | import logo from '../../assets/imgs/logo.png' 8 | // import {reqLogin} from '../../api' 9 | // import memoryUtils from '../../utils/memoryUtils' 10 | // import storeageUtils from '../../utils/storageUtils' 11 | import { Redirect } from 'react-router-dom'; 12 | 13 | const Login = (props) => { 14 | 15 | const [form] = Form.useForm(); 16 | 17 | // 如果用户已经登录 自动跳转到管理界面 18 | const user = memoryUtils.user 19 | if (user._id) { 20 | return 21 | } 22 | 23 | const onFinish = (async (values) => { 24 | // console.log('Received values of form: ', values); 25 | 26 | // 请求登录 27 | const {username,password} = values 28 | 29 | /* reqLogin(username,password).then(response => { 30 | console.log('成功了',response.data); 31 | }).catch(error => { 32 | console.log('失败了',error.message); 33 | }) */ 34 | 35 | //#region 36 | // 优化1 37 | /* const response = await reqLogin(username,password) 38 | console.log('请求成功了',response.data); 39 | 40 | // 验证登录成功还是失败 41 | const result = response.data //{status:0, data:user} {status:1,msg:'xxx'} 42 | if (result.status === 0) { //登录成功 43 | // 提示登录成功 44 | message.success('登录成功') 45 | // 跳转到管理界面(不需要再回退回到登录) 46 | props.history.replace('/') 47 | } else { //登录失败 48 | message.error(result.msg) //提示错误信息 49 | } */ 50 | 51 | // 优化2 52 | //{status:0, data:user} {status:1,msg:'xxx'} 53 | /* const result = await reqLogin(username,password) 54 | 55 | if (result.status === 0) { //登录成功 56 | // 提示登录成功 57 | message.success('登录成功') 58 | 59 | // 保存user 60 | const user = result.data 61 | memoryUtils.user = user //保存在内存中 62 | storeageUtils.saveUser(user) //保存到local中去 63 | 64 | // 跳转到管理界面(不需要再回退回到登录) 65 | props.history.replace('/home') 66 | } else { //登录失败 67 | message.error(result.msg) //提示错误信息 68 | } */ 69 | //#endregion 70 | 71 | }); 72 | 73 | return ( 74 |
75 |
76 | logo 77 |

React项目:后台管理系统

78 |
79 |
80 |

用户登陆

81 |
87 | 103 | } placeholder="用户名" style={{ color: 'rgba(0,0,0,.25)' }} /> 104 | 105 | 114 | } type="password" placeholder="密码" style={{ color: 'rgba(0,0,0,.25)' }}/> 115 | 116 | 117 | 120 | 121 |
122 |
123 |
124 | ); 125 | }; 126 | 127 | export default Login 128 | 129 | /* 130 | async 和 await 131 | 1、作用? 132 | 简化promise对象的使用:不再使用then() 来指定成功/失败的回调函数 133 | 以同步编码(没有回调函数) 方式实现异步流程 134 | 2、哪里写await 135 | 在返回promise的表达式左侧写await: 不想要promise 想要promise异步执行的 136 | 成功的value数据 137 | 3、哪里写async 138 | await所在函数(最近的)的左侧写async 139 | */ 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/pages/user/user.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Button,Table,Modal,Card, message} from 'antd' 3 | 4 | import {formateDate} from '../../utils/dateUtils' 5 | import LinkButton from '../../components/link-button/index' 6 | import {reqDeleteUser, reqUsers,reqAddOrUpdateUser} from '../../api/index' 7 | import UserForm from './user-form' 8 | 9 | 10 | // 用户路由 11 | export default class User extends Component { 12 | state = { 13 | users: [], //用来存放用户列表的数组 14 | roles: [], //所有角色的列表 15 | isShow: false, //是否显示对话框 16 | } 17 | 18 | initColumns = () => { 19 | this.columns = [ 20 | { 21 | title: '用户名', 22 | dataIndex: 'username', 23 | }, 24 | { 25 | title: '邮箱', 26 | dataIndex: 'email', 27 | }, 28 | { 29 | title: '电话', 30 | dataIndex: 'phone', 31 | }, 32 | { 33 | title: '注册时间', 34 | dataIndex: 'create_time', 35 | render: formateDate 36 | }, 37 | { 38 | title: '所属角色', 39 | dataIndex: 'role_id', 40 | render: (role_id) => //this.roleNames[role_id] 41 | (this.state.roles.find(role => role._id===role_id) || this.state.roles[0] ).name 42 | // console.log('this',this.state.roles) 43 | }, 44 | { 45 | title: '操作', 46 | render: (user) => ( 47 | 48 | this.showUpdate(user)}>修改 49 | {this.deleteUser(user)}}>删除 50 | 51 | ) 52 | } 53 | ] 54 | } 55 | 56 | // 根据role的数组,生成包含所有角色名的对象(属性名用角色id值) 57 | initRoleNames = (roles) => { 58 | const roleNames = roles.reduce((pre,role) => { 59 | pre[role._id] = role.name 60 | return pre 61 | },{}) 62 | //保存值 63 | this.roleNames = roleNames 64 | } 65 | 66 | // 创建用户 67 | showAdd = () => { 68 | // 去除前面的user 69 | this.user = null 70 | this.setState({isShow:true}) 71 | } 72 | 73 | // 显示修改界面 74 | showUpdate = (user) => { 75 | // 保存user的值 76 | this.user = user 77 | // 显示界面 78 | this.setState({ 79 | isShow: true, 80 | }) 81 | } 82 | 83 | // 添加/更新用户 84 | addOrUpdateUser = async () => { 85 | 86 | this.setState({isShow:false}) 87 | // console.log(this.form); 88 | 89 | // 收集输入数据 90 | const user = this.form.getFieldsValue() 91 | // console.log(user); 92 | this.form.resetFields() 93 | 94 | // 如果是更新 需要给user指定_id属性 95 | if (this.user) { 96 | user._id = this.user._id 97 | } 98 | 99 | // 提交添加的请求 100 | const result = await reqAddOrUpdateUser(user) 101 | if (result.status === 0) { 102 | message.success(`${this.user?'修改':'添加'}用户成功!`) 103 | // 更新显示的列表 104 | this.getUsers() 105 | } 106 | } 107 | 108 | // 取消的回调 109 | handleCancel = () => { 110 | this.setState({isShow: false}) 111 | this.form.resetFields() 112 | } 113 | 114 | // 获取用户 115 | getUsers = async () => { 116 | const result = await reqUsers() 117 | if (result.status === 0) { 118 | const {users,roles} = result.data 119 | // console.log(result.data); 120 | this.initRoleNames(roles) 121 | this.setState({ 122 | users, 123 | roles, 124 | }) 125 | } 126 | } 127 | 128 | // 删除指定用户 129 | deleteUser = (user) => { 130 | Modal.confirm({ 131 | title: `确认删除${user.username}吗?`, 132 | onOk: async () => { 133 | const result = await reqDeleteUser(user._id) 134 | if (result.status === 0) { 135 | message.success('删除用户成功!') 136 | this.getUsers() 137 | } 138 | } 139 | }) 140 | } 141 | 142 | UNSAFE_componentWillMount() { 143 | this.initColumns() 144 | } 145 | 146 | componentDidMount(){ 147 | this.getUsers() 148 | } 149 | 150 | render() { 151 | const title = 153 | const {users,roles,isShow} = this.state 154 | const user = this.user || {} 155 | return ( 156 | 157 | 163 | 164 | 169 | this.form = form} 171 | user={user}/> 172 | 173 | 174 | ) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src_react/pages/user/user.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Button,Table,Modal,Card, message} from 'antd' 3 | 4 | import {formateDate} from '../../utils/dateUtils' 5 | import LinkButton from '../../components/link-button/index' 6 | import {reqDeleteUser, reqUsers,reqAddOrUpdateUser} from '../../api/index' 7 | import UserForm from './user-form' 8 | 9 | 10 | // 用户路由 11 | export default class User extends Component { 12 | state = { 13 | users: [], //用来存放用户列表的数组 14 | roles: [], //所有角色的列表 15 | isShow: false, //是否显示对话框 16 | } 17 | 18 | initColumns = () => { 19 | this.columns = [ 20 | { 21 | title: '用户名', 22 | dataIndex: 'username', 23 | }, 24 | { 25 | title: '邮箱', 26 | dataIndex: 'email', 27 | }, 28 | { 29 | title: '电话', 30 | dataIndex: 'phone', 31 | }, 32 | { 33 | title: '注册时间', 34 | dataIndex: 'create_time', 35 | render: formateDate 36 | }, 37 | { 38 | title: '所属角色', 39 | dataIndex: 'role_id', 40 | render: (role_id) => //this.roleNames[role_id] 41 | (this.state.roles.find(role => role._id===role_id) || this.state.roles[0] ).name 42 | // console.log('this',this.state.roles) 43 | }, 44 | { 45 | title: '操作', 46 | render: (user) => ( 47 | 48 | this.showUpdate(user)}>修改 49 | {this.deleteUser(user)}}>删除 50 | 51 | ) 52 | } 53 | ] 54 | } 55 | 56 | // 根据role的数组,生成包含所有角色名的对象(属性名用角色id值) 57 | initRoleNames = (roles) => { 58 | const roleNames = roles.reduce((pre,role) => { 59 | pre[role._id] = role.name 60 | return pre 61 | },{}) 62 | //保存值 63 | this.roleNames = roleNames 64 | } 65 | 66 | // 创建用户 67 | showAdd = () => { 68 | // 去除前面的user 69 | this.user = null 70 | this.setState({isShow:true}) 71 | } 72 | 73 | // 显示修改界面 74 | showUpdate = (user) => { 75 | // 保存user的值 76 | this.user = user 77 | // 显示界面 78 | this.setState({ 79 | isShow: true, 80 | }) 81 | } 82 | 83 | // 添加/更新用户 84 | addOrUpdateUser = async () => { 85 | 86 | this.setState({isShow:false}) 87 | // console.log(this.form); 88 | 89 | // 收集输入数据 90 | const user = this.form.getFieldsValue() 91 | // console.log(user); 92 | this.form.resetFields() 93 | 94 | // 如果是更新 需要给user指定_id属性 95 | if (this.user) { 96 | user._id = this.user._id 97 | } 98 | 99 | // 提交添加的请求 100 | const result = await reqAddOrUpdateUser(user) 101 | if (result.status === 0) { 102 | message.success(`${this.user?'修改':'添加'}用户成功!`) 103 | // 更新显示的列表 104 | this.getUsers() 105 | } 106 | } 107 | 108 | // 取消的回调 109 | handleCancel = () => { 110 | this.setState({isShow: false}) 111 | this.form.resetFields() 112 | } 113 | 114 | // 获取用户 115 | getUsers = async () => { 116 | const result = await reqUsers() 117 | if (result.status === 0) { 118 | const {users,roles} = result.data 119 | // console.log(result.data); 120 | this.initRoleNames(roles) 121 | this.setState({ 122 | users, 123 | roles, 124 | }) 125 | } 126 | } 127 | 128 | // 删除指定用户 129 | deleteUser = (user) => { 130 | Modal.confirm({ 131 | title: `确认删除${user.username}吗?`, 132 | onOk: async () => { 133 | const result = await reqDeleteUser(user._id) 134 | if (result.status === 0) { 135 | message.success('删除用户成功!') 136 | this.getUsers() 137 | } 138 | } 139 | }) 140 | } 141 | 142 | UNSAFE_componentWillMount() { 143 | this.initColumns() 144 | } 145 | 146 | componentDidMount(){ 147 | this.getUsers() 148 | } 149 | 150 | render() { 151 | const title = 153 | const {users,roles,isShow} = this.state 154 | const user = this.user || {} 155 | return ( 156 | 157 |
163 | 164 | 169 | this.form = form} 171 | user={user}/> 172 | 173 | 174 | ) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/pages/product/home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,Select,Input,Button,Table, message} from 'antd' 3 | import {PlusOutlined} from '@ant-design/icons' 4 | 5 | import LinkButton from '../../components/link-button' 6 | import { reqProducts,reqSearchProducts,reqUpdateStatus } from '../../api/index' 7 | import { PAGE_SIZE } from '../../utils/constants' 8 | 9 | 10 | const Option = Select.Option 11 | 12 | // Product的默认子路由组件 13 | export default class ProductHome extends Component { 14 | 15 | state = { 16 | products: [], //商品的数组 17 | total: 0, //商品的总数量 18 | loading: false, //标识数据正在加载 19 | searchName: '', //搜索的关键字 20 | searchType: 'productName', //根据哪个字段进行搜索 21 | } 22 | 23 | // 初始化table表格的列的数组 24 | initColumns = () => { 25 | this.columns = [ 26 | { 27 | title: '商品名称', 28 | dataIndex: 'name', 29 | }, 30 | { 31 | title: '商品描述', 32 | dataIndex: 'desc', 33 | }, 34 | { 35 | title: '价格', 36 | dataIndex: 'price', 37 | render: (price) => '¥' + price //当前指定了对应的属性 传入的是对应的属性值 38 | }, 39 | { 40 | width: '100px', 41 | title: '状态', 42 | // dataIndex: 'status', 43 | 44 | render: (product) => { 45 | const {status,_id} = product 46 | return ( 47 | 48 | 50 | {status===1?'在售':'已下架'} 51 | 52 | ) 53 | } 54 | }, 55 | { 56 | width: '100px', 57 | title: '操作', 58 | render: (product) => { 59 | return ( 60 | 61 | {/* 将product 对象使用state传递给目标路由组件 */} 62 | {this.props.history.push('/product/detail', {product} )}}>详情 63 | this.props.history.push('/product/addupdate',product)}>修改 64 | 65 | ) 66 | } 67 | }, 68 | ]; 69 | 70 | } 71 | 72 | // 获取指定页码的列表数据显示 73 | getProducts = async (pageNum) => { 74 | 75 | // 保存当前页码为下面更新商品状态中重新更新页面准备 76 | this.pageNum = pageNum 77 | 78 | // 发请求之前改为true 显示loading 79 | this.setState({loading:true}) 80 | 81 | const {searchName,searchType} = this.state 82 | // 如果搜索关键字有值,说明我们要做搜索分页 83 | let result 84 | if (searchName) { 85 | result = await reqSearchProducts({pageNum,pageSize:PAGE_SIZE,searchName,searchType}) 86 | } else { //一般分页 87 | result = await reqProducts(pageNum,PAGE_SIZE) 88 | } 89 | 90 | // 发完请求之后标识loading 91 | this.setState({loading:false}) 92 | 93 | if (result.status === 0) { 94 | // 取出分页数据 更新状态 显示分页列表 95 | const {total,list} = result.data 96 | this.setState({ 97 | total, 98 | products: list 99 | }) 100 | } 101 | } 102 | 103 | // 更新商品指定的状态 104 | updateStatus = async (productId,status) => { 105 | const result = await reqUpdateStatus(productId,status) 106 | if (result.status===0) { 107 | message.success('更新状态成功') 108 | // 重新渲染页面 109 | this.getProducts(this.pageNum) 110 | } 111 | } 112 | 113 | UNSAFE_componentWillMount(){ 114 | this.initColumns() 115 | } 116 | 117 | // 发请求动态获取商品信息 118 | componentDidMount() { 119 | this.getProducts(1) 120 | } 121 | 122 | render() { 123 | 124 | // 取出状态数据 125 | const {products,total,loading,searchName,searchType} = this.state 126 | // console.log('样式不对',products); 127 | 128 | const title = ( 129 | 130 | 135 | this.setState({searchName:e.target.value})} 136 | value={searchName} placeholder='关键字' style={{width:150,margin: '0 15px'}}/> 137 | 138 | 139 | ) 140 | 141 | const extra = ( 142 | 148 | ) 149 | return ( 150 | 151 |
163 | 164 | ) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src_react/pages/product/home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,Select,Input,Button,Table, message} from 'antd' 3 | import {PlusOutlined} from '@ant-design/icons' 4 | 5 | import LinkButton from '../../components/link-button' 6 | import { reqProducts,reqSearchProducts,reqUpdateStatus } from '../../api/index' 7 | import { PAGE_SIZE } from '../../utils/constants' 8 | 9 | 10 | const Option = Select.Option 11 | 12 | // Product的默认子路由组件 13 | export default class ProductHome extends Component { 14 | 15 | state = { 16 | products: [], //商品的数组 17 | total: 0, //商品的总数量 18 | loading: false, //标识数据正在加载 19 | searchName: '', //搜索的关键字 20 | searchType: 'productName', //根据哪个字段进行搜索 21 | } 22 | 23 | // 初始化table表格的列的数组 24 | initColumns = () => { 25 | this.columns = [ 26 | { 27 | title: '商品名称', 28 | dataIndex: 'name', 29 | }, 30 | { 31 | title: '商品描述', 32 | dataIndex: 'desc', 33 | }, 34 | { 35 | title: '价格', 36 | dataIndex: 'price', 37 | render: (price) => '¥' + price //当前指定了对应的属性 传入的是对应的属性值 38 | }, 39 | { 40 | width: '100px', 41 | title: '状态', 42 | // dataIndex: 'status', 43 | 44 | render: (product) => { 45 | const {status,_id} = product 46 | return ( 47 | 48 | 50 | {status===1?'在售':'已下架'} 51 | 52 | ) 53 | } 54 | }, 55 | { 56 | width: '100px', 57 | title: '操作', 58 | render: (product) => { 59 | return ( 60 | 61 | {/* 将product 对象使用state传递给目标路由组件 */} 62 | {this.props.history.push('/product/detail', {product} )}}>详情 63 | this.props.history.push('/product/addupdate',product)}>修改 64 | 65 | ) 66 | } 67 | }, 68 | ]; 69 | 70 | } 71 | 72 | // 获取指定页码的列表数据显示 73 | getProducts = async (pageNum) => { 74 | 75 | // 保存当前页码为下面更新商品状态中重新更新页面准备 76 | this.pageNum = pageNum 77 | 78 | // 发请求之前改为true 显示loading 79 | this.setState({loading:true}) 80 | 81 | const {searchName,searchType} = this.state 82 | // 如果搜索关键字有值,说明我们要做搜索分页 83 | let result 84 | if (searchName) { 85 | result = await reqSearchProducts({pageNum,pageSize:PAGE_SIZE,searchName,searchType}) 86 | } else { //一般分页 87 | result = await reqProducts(pageNum,PAGE_SIZE) 88 | } 89 | 90 | // 发完请求之后标识loading 91 | this.setState({loading:false}) 92 | 93 | if (result.status === 0) { 94 | // 取出分页数据 更新状态 显示分页列表 95 | const {total,list} = result.data 96 | this.setState({ 97 | total, 98 | products: list 99 | }) 100 | } 101 | } 102 | 103 | // 更新商品指定的状态 104 | updateStatus = async (productId,status) => { 105 | const result = await reqUpdateStatus(productId,status) 106 | if (result.status===0) { 107 | message.success('更新状态成功') 108 | // 重新渲染页面 109 | this.getProducts(this.pageNum) 110 | } 111 | } 112 | 113 | UNSAFE_componentWillMount(){ 114 | this.initColumns() 115 | } 116 | 117 | // 发请求动态获取商品信息 118 | componentDidMount() { 119 | this.getProducts(1) 120 | } 121 | 122 | render() { 123 | 124 | // 取出状态数据 125 | const {products,total,loading,searchName,searchType} = this.state 126 | // console.log('样式不对',products); 127 | 128 | const title = ( 129 | 130 | 135 | this.setState({searchName:e.target.value})} 136 | value={searchName} placeholder='关键字' style={{width:150,margin: '0 15px'}}/> 137 | 138 | 139 | ) 140 | 141 | const extra = ( 142 | 148 | ) 149 | return ( 150 | 151 |
163 | 164 | ) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/components/left-nav/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link, withRouter } from 'react-router-dom' 3 | import { Menu } from 'antd'; 4 | import {connect} from 'react-redux' 5 | 6 | 7 | import logo from '../../assets/imgs/logo.png' 8 | import menuList from '../../config/menuConfig' 9 | import {setHeadTitle} from '../../redux/actions' 10 | import './index.less' 11 | 12 | const { SubMenu } = Menu; 13 | // 左侧导航的组件 14 | class LeftNav extends Component { 15 | 16 | // 判断当前登录的用户对item的权限 17 | hasAuth = (item) => { 18 | const {key,isPublic} = item 19 | const menus = this.props.user.role.menus 20 | // console.log(memoryUtils.user); 21 | const username = this.props.user.username 22 | // 如果当前用户是 admin 23 | // 如果当前item是公开的 24 | // 如果用户有此 item 的权限: key有没有menus中 25 | if (username === 'admin' || isPublic || menus.indexOf(key) !== -1) { 26 | return true 27 | } else if(item.children) { 28 | // 如果当前用户有此item的某个子item的权限 29 | return !!item.children.find(child => menus.indexOf(child.key)!==-1) 30 | } 31 | return false 32 | } 33 | 34 | // 定义方法 根据menu 的数据数组生成对应的标签数组 35 | // 使用map + 递归调用 36 | getMenuNodes = (menuList) => { 37 | 38 | // 得到当前请求的路由路径 39 | const path = this.props.location.pathname 40 | 41 | return menuList.map(item => { 42 | /* 43 | 这里的item的形式如下: 44 | { 45 | title: '首页', //菜单标题名称 46 | key: '/home', //对应的path 47 | icon: 'home', //图标名称 48 | children: [] 可能有也可能没有 49 | }, 50 | */ 51 | 52 | // 如果当前用户有item对应的权限 才需要显示对应的菜单项 53 | if (this.hasAuth(item)) { 54 | if (!item.children) { 55 | 56 | // 判断item是否是当前对应的item 57 | if (item.key === path || path.indexOf(item.key)===0) { 58 | // 更新redux中的headTitle状态 59 | this.props.setHeadTitle(item.title) 60 | } 61 | 62 | return ( 63 | 64 | this.props.setHeadTitle(item.title)}>{item.title} 65 | 66 | ) 67 | } else { 68 | // 查找一个与当前请求路径匹配的子item 69 | const cItem = item.children.find(cItem => path.indexOf(cItem.key) === 0 ) 70 | // 如果存在 说明当前item的子列表需要打开 71 | if (cItem) {this.openKey = item.key} 72 | return ( 73 | 74 | {/* 递归调用 */} 75 | { 76 | this.getMenuNodes(item.children) 77 | } 78 | 79 | ) 80 | } 81 | } 82 | } 83 | ) 84 | } 85 | 86 | // 在第一次render() 之前执一次 为第一次render()准备数据(同步的) 87 | UNSAFE_componentWillMount(){ 88 | //组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次 89 | this.menuNodes =this.getMenuNodes(menuList) 90 | } 91 | 92 | //#region 93 | /* // 定义方法 reduce() + 递归调用 94 | getMenuNodes = (menuList) => { 95 | return menuList.reduce((pre,item)=>{ 96 | // 向pre添加 Menu.Item 或者 SubMenu 97 | if (!item.children) { 98 | // 添加 Menu.Item 99 | pre.push( 100 | ( 101 | {item.title} 102 | ) 103 | ) 104 | } else { 105 | // 添加 SubMenu 106 | pre.push(( 107 | 108 | { 109 | this.getMenuNodes(item.children) 110 | } 111 | 112 | )) 113 | } 114 | return pre 115 | },[ ]) 116 | } */ 117 | //#endregion 118 | 119 | render() { 120 | 121 | // const menuNodes = this.getMenuNodes(menuList) 122 | 123 | // 得到当前请求的路由路径 124 | let path = this.props.location.pathname 125 | 126 | if (path.indexOf('/product')===0) { 127 | // 说明现在显示 product或者product的子页面 128 | // 修改path 129 | path = '/product' 130 | } 131 | 132 | // 得到需要打开菜单项的key 133 | const openKey = this.openKey 134 | return ( 135 |
136 | 137 | logo 138 |

硅谷后台

139 | 140 | 146 | {/* }> 147 | 首页 148 | 149 | } title="商品"> 150 | }>品类管理 151 | }>商品管理 152 | */} 153 | 154 | {/* 根据导入的menuList动态生成菜单导航 */} 155 | { 156 | this.menuNodes 157 | } 158 | 159 | 160 |
161 | ) 162 | } 163 | } 164 | 165 | // 高阶组件:包装非路由组件 返回一个新的组件 166 | // 新的组件会向非路由组件传递三个属性: histroy location match 167 | // export default withRouter(LeftNav) 168 | 169 | export default connect( 170 | state => ({user: state.user}), 171 | {setHeadTitle} 172 | )(withRouter(LeftNav)) 173 | -------------------------------------------------------------------------------- /src_react/components/left-nav/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link, withRouter } from 'react-router-dom' 3 | import { Menu } from 'antd'; 4 | import {connect} from 'react-redux' 5 | 6 | 7 | import logo from '../../assets/imgs/logo.png' 8 | import menuList from '../../config/menuConfig' 9 | import memoryUtils from '../../utils/memoryUtils' 10 | import {setHeadTitle} from '../../redux/actions' 11 | import './index.less' 12 | 13 | const { SubMenu } = Menu; 14 | // 左侧导航的组件 15 | class LeftNav extends Component { 16 | 17 | // 判断当前登录的用户对item的权限 18 | hasAuth = (item) => { 19 | const {key,isPublic} = item 20 | const menus = memoryUtils.user.role.menus 21 | // console.log(memoryUtils.user); 22 | const username = memoryUtils.user.username 23 | // 如果当前用户是 admin 24 | // 如果当前item是公开的 25 | // 如果用户有此 item 的权限: key有没有menus中 26 | if (username === 'admin' || isPublic || menus.indexOf(key) !== -1) { 27 | return true 28 | } else if(item.children) { 29 | // 如果当前用户有此item的某个子item的权限 30 | return !!item.children.find(child => menus.indexOf(child.key)!==-1) 31 | } 32 | return false 33 | } 34 | 35 | // 定义方法 根据menu 的数据数组生成对应的标签数组 36 | // 使用map + 递归调用 37 | getMenuNodes = (menuList) => { 38 | 39 | // 得到当前请求的路由路径 40 | const path = this.props.location.pathname 41 | 42 | return menuList.map(item => { 43 | /* 44 | 这里的item的形式如下: 45 | { 46 | title: '首页', //菜单标题名称 47 | key: '/home', //对应的path 48 | icon: 'home', //图标名称 49 | children: [] 可能有也可能没有 50 | }, 51 | */ 52 | 53 | // 如果当前用户有item对应的权限 才需要显示对应的菜单项 54 | if (this.hasAuth(item)) { 55 | if (!item.children) { 56 | 57 | // 判断item是否是当前对应的item 58 | if (item.key === path || path.indexOf(item.key)===0) { 59 | // 更新redux中的headTitle状态 60 | this.props.setHeadTitle(item.title) 61 | } 62 | 63 | return ( 64 | 65 | this.props.setHeadTitle(item.title)}>{item.title} 66 | 67 | ) 68 | } else { 69 | // 查找一个与当前请求路径匹配的子item 70 | const cItem = item.children.find(cItem => path.indexOf(cItem.key) === 0 ) 71 | // 如果存在 说明当前item的子列表需要打开 72 | if (cItem) {this.openKey = item.key} 73 | return ( 74 | 75 | {/* 递归调用 */} 76 | { 77 | this.getMenuNodes(item.children) 78 | } 79 | 80 | ) 81 | } 82 | } 83 | 84 | }) 85 | } 86 | 87 | // 在第一次render() 之前执一次 为第一次render()准备数据(同步的) 88 | UNSAFE_componentWillMount(){ 89 | //组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次 90 | this.menuNodes =this.getMenuNodes(menuList) 91 | } 92 | 93 | //#region 94 | /* // 定义方法 reduce() + 递归调用 95 | getMenuNodes = (menuList) => { 96 | return menuList.reduce((pre,item)=>{ 97 | // 向pre添加 Menu.Item 或者 SubMenu 98 | if (!item.children) { 99 | // 添加 Menu.Item 100 | pre.push( 101 | ( 102 | {item.title} 103 | ) 104 | ) 105 | } else { 106 | // 添加 SubMenu 107 | pre.push(( 108 | 109 | { 110 | this.getMenuNodes(item.children) 111 | } 112 | 113 | )) 114 | } 115 | return pre 116 | },[ ]) 117 | } */ 118 | //#endregion 119 | 120 | render() { 121 | 122 | // const menuNodes = this.getMenuNodes(menuList) 123 | 124 | // 得到当前请求的路由路径 125 | let path = this.props.location.pathname 126 | 127 | if (path.indexOf('/product')===0) { 128 | // 说明现在显示 product或者product的子页面 129 | // 修改path 130 | path = '/product' 131 | } 132 | 133 | // 得到需要打开菜单项的key 134 | const openKey = this.openKey 135 | return ( 136 |
137 | 138 | logo 139 |

硅谷后台

140 | 141 | 147 | {/* }> 148 | 首页 149 | 150 | } title="商品"> 151 | }>品类管理 152 | }>商品管理 153 | */} 154 | 155 | {/* 根据导入的menuList动态生成菜单导航 */} 156 | { 157 | this.menuNodes 158 | } 159 | 160 | 161 |
162 | ) 163 | } 164 | } 165 | 166 | // 高阶组件:包装非路由组件 返回一个新的组件 167 | // 新的组件会向非路由组件传递三个属性: histroy location match 168 | // export default withRouter(LeftNav) 169 | 170 | export default connect( 171 | state => ({}), 172 | {setHeadTitle} 173 | )(withRouter(LeftNav)) 174 | -------------------------------------------------------------------------------- /src_react/pages/role/role.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Card , Button, Table,Modal, message} from 'antd' 3 | import {reqRoles,reqAddRole,reqUpdateRole} from '../../api/index' 4 | import ADDForm from './add-form' 5 | import AuthForm from './auth-form' 6 | import memoryUtils from '../../utils/memoryUtils' 7 | import storageUtils from '../../utils/storageUtils' 8 | import {formateDate} from '../../utils/dateUtils' 9 | 10 | // 角色路由 11 | export default class Role extends PureComponent { 12 | state = { 13 | roles: [], //用来存放各种角色的数组 14 | role: {}, //选中的role 15 | isShowAdd: false, //是否显示添加界面 16 | isShowAuth: false, //是否显示设置权限界面 17 | } 18 | 19 | 20 | constructor(props){ 21 | super(props) 22 | 23 | this.auth = React.createRef() 24 | } 25 | 26 | initColumn = () => { 27 | this.columns = [ 28 | { 29 | title: '角色名称', 30 | dataIndex: 'name', 31 | }, 32 | { 33 | title: '创建时间', 34 | dataIndex: 'create_time', 35 | render: (create_time) => formateDate(create_time) 36 | }, 37 | { 38 | title: '授权时间', 39 | dataIndex: 'auth_time', 40 | render: formateDate 41 | }, 42 | { 43 | title: '授权人', 44 | dataIndex: 'auth_name' 45 | } 46 | ] 47 | } 48 | 49 | // 获取角色的列表数据 50 | getRoles = async () => { 51 | const result = await reqRoles() 52 | if (result.status === 0) { 53 | const roles = result.data 54 | this.setState({roles}) 55 | } 56 | } 57 | 58 | onRow = (role) => { 59 | return { 60 | onClick: event => { 61 | this.setState({role}) 62 | } 63 | } 64 | } 65 | 66 | // 添加角色成功的回调函数 67 | addRole = () => { 68 | //进行表单验证 69 | // console.log(this.form); 70 | this.form.validateFields() 71 | .then(async (values)=>{ 72 | //隐藏确认框 73 | this.setState({isShowAdd:false}) 74 | //收集输入数据 75 | const {roleName} = values 76 | this.form.resetFields() 77 | //发请求添加 78 | const result = await reqAddRole(roleName) 79 | if (result.status===0) { 80 | // this.getRoles() 81 | // 新产生的角色 82 | const role = result.data 83 | // 更新roles状态 更新后的数据和之前的状态数据有关 84 | this.setState(state=>({ 85 | roles: [...state.roles,role] 86 | })) 87 | } else { 88 | message.error('添加角色失败') 89 | } 90 | } 91 | ) 92 | .catch(errorInfo => { 93 | alert('出错',errorInfo) 94 | }); 95 | } 96 | 97 | //取消添加角色的回调 98 | handleCancel = () => { 99 | this.form.resetFields() 100 | this.setState({isShowAdd:false}) 101 | } 102 | 103 | // 更新角色的回调函数 104 | updateRole = async () => { 105 | // 隐藏确认框 106 | this.setState({ 107 | isShowAuth: false 108 | }) 109 | const role = this.state.role 110 | // 得到最新的menus 111 | const menus = this.auth.current.getMenus() 112 | role.menus = menus 113 | role.auth_time = Date.now() 114 | role.auth_name = memoryUtils.user.username 115 | 116 | // 请求更新 117 | const result = await reqUpdateRole(role) 118 | if (result.status === 0) { 119 | // 如果当前更新的是自己角色的权限 需要强制退出 120 | if (role._id === memoryUtils.user.role_id) { 121 | // 清空本地存储 122 | memoryUtils.user = {} 123 | storageUtils.removeUser() 124 | this.props.history.replace('/login') 125 | message.success('用户权限已更新,请重新登录!') 126 | } else { 127 | message.success('设置权限成功') 128 | // 获取显示列表的两种方式 129 | // this.getRoles() 130 | this.setState({ 131 | roles: [...this.state.roles] 132 | }) 133 | } 134 | 135 | 136 | } 137 | } 138 | 139 | // 取消更新角色的回调 140 | handleCancelUpdate = () => { 141 | this.setState({isShowAuth:false}) 142 | // this.form.resetFields() 143 | } 144 | 145 | UNSAFE_componentWillMount(){ 146 | this.initColumn() 147 | } 148 | 149 | componentDidMount(){ 150 | //发送请求获取角色列表数据 151 | this.getRoles() 152 | } 153 | render() { 154 | const {roles,role, isShowAdd,isShowAuth} = this.state 155 | 156 | const title = ( 157 | 158 |     159 | 161 | 162 | ) 163 | return ( 164 | 165 |
{this.setState({role})} 174 | }} 175 | onRow={this.onRow} /> 176 | 182 | this.form = form} /> 183 | 184 | 190 | 191 | 192 | 193 | ) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/pages/role/role.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Card , Button, Table,Modal, message} from 'antd' 3 | import { connect } from 'react-redux' 4 | 5 | import {reqRoles,reqAddRole,reqUpdateRole} from '../../api/index' 6 | import ADDForm from './add-form' 7 | import AuthForm from './auth-form' 8 | // import storageUtils from '../../utils/storageUtils' 9 | import {formateDate} from '../../utils/dateUtils' 10 | import {logout} from '../../redux/actions' 11 | 12 | 13 | // 角色路由 14 | class Role extends PureComponent { 15 | state = { 16 | roles: [], //用来存放各种角色的数组 17 | role: {}, //选中的role 18 | isShowAdd: false, //是否显示添加界面 19 | isShowAuth: false, //是否显示设置权限界面 20 | } 21 | 22 | 23 | constructor(props){ 24 | super(props) 25 | 26 | this.auth = React.createRef() 27 | } 28 | 29 | initColumn = () => { 30 | this.columns = [ 31 | { 32 | title: '角色名称', 33 | dataIndex: 'name', 34 | }, 35 | { 36 | title: '创建时间', 37 | dataIndex: 'create_time', 38 | render: (create_time) => formateDate(create_time) 39 | }, 40 | { 41 | title: '授权时间', 42 | dataIndex: 'auth_time', 43 | render: formateDate 44 | }, 45 | { 46 | title: '授权人', 47 | dataIndex: 'auth_name' 48 | } 49 | ] 50 | } 51 | 52 | // 获取角色的列表数据 53 | getRoles = async () => { 54 | const result = await reqRoles() 55 | if (result.status === 0) { 56 | const roles = result.data 57 | this.setState({roles}) 58 | } 59 | } 60 | 61 | onRow = (role) => { 62 | return { 63 | onClick: event => { 64 | this.setState({role}) 65 | } 66 | } 67 | } 68 | 69 | // 添加角色成功的回调函数 70 | addRole = () => { 71 | //进行表单验证 72 | // console.log(this.form); 73 | this.form.validateFields() 74 | .then(async (values)=>{ 75 | //隐藏确认框 76 | this.setState({isShowAdd:false}) 77 | //收集输入数据 78 | const {roleName} = values 79 | this.form.resetFields() 80 | //发请求添加 81 | const result = await reqAddRole(roleName) 82 | if (result.status===0) { 83 | // this.getRoles() 84 | // 新产生的角色 85 | const role = result.data 86 | // 更新roles状态 更新后的数据和之前的状态数据有关 87 | this.setState(state=>({ 88 | roles: [...state.roles,role] 89 | })) 90 | } else { 91 | message.error('添加角色失败') 92 | } 93 | } 94 | ) 95 | .catch(errorInfo => { 96 | alert('出错',errorInfo) 97 | }); 98 | } 99 | 100 | //取消添加角色的回调 101 | handleCancel = () => { 102 | this.form.resetFields() 103 | this.setState({isShowAdd:false}) 104 | } 105 | 106 | // 更新角色的回调函数 107 | updateRole = async () => { 108 | // 隐藏确认框 109 | this.setState({ 110 | isShowAuth: false 111 | }) 112 | const role = this.state.role 113 | // 得到最新的menus 114 | const menus = this.auth.current.getMenus() 115 | role.menus = menus 116 | role.auth_time = Date.now() 117 | role.auth_name = this.props.user.username 118 | 119 | // 请求更新 120 | const result = await reqUpdateRole(role) 121 | if (result.status === 0) { 122 | // 如果当前更新的是自己角色的权限 需要强制退出 123 | if (role._id === this.props.user.role_id) { 124 | this.props.logout() 125 | message.success('用户权限已更新,请重新登录!') 126 | } else { 127 | message.success('设置权限成功') 128 | // 获取显示列表的两种方式 129 | // this.getRoles() 130 | this.setState({ 131 | roles: [...this.state.roles] 132 | }) 133 | } 134 | 135 | 136 | } 137 | } 138 | 139 | // 取消更新角色的回调 140 | handleCancelUpdate = () => { 141 | this.setState({isShowAuth:false}) 142 | // this.form.resetFields() 143 | } 144 | 145 | UNSAFE_componentWillMount(){ 146 | this.initColumn() 147 | } 148 | 149 | componentDidMount(){ 150 | //发送请求获取角色列表数据 151 | this.getRoles() 152 | } 153 | render() { 154 | const {roles,role, isShowAdd,isShowAuth} = this.state 155 | 156 | const title = ( 157 | 158 |     159 | 161 | 162 | ) 163 | return ( 164 | 165 |
{this.setState({role})} 174 | }} 175 | onRow={this.onRow} /> 176 | 182 | this.form = form} /> 183 | 184 | 190 | 191 | 192 | 193 | ) 194 | } 195 | } 196 | 197 | export default connect( 198 | state => ({user:state.user}), 199 | {logout} 200 | )(Role) 201 | -------------------------------------------------------------------------------- /src/pages/role/auth-form.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import {Form,Input,Tree} from 'antd' 3 | import PropTypes from 'prop-types' 4 | import menuList from '../../config/menuConfig' 5 | 6 | 7 | const Item = Form.Item 8 | 9 | // 创建角色的form组件 10 | export default class AuthForm extends Component { 11 | 12 | static propTypes = { 13 | role: PropTypes.object 14 | } 15 | 16 | // state = { 17 | // treeData: [{ 18 | // title: '平台权限', 19 | // key: 'all', 20 | // children: [] 21 | // }], 22 | // checkedKeys:[] 23 | // } 24 | 25 | constructor(props) { 26 | super(props) 27 | // console.log('role',this.props.role); 28 | const {menus} = this.props.role 29 | 30 | this.state = { 31 | treeData: [{ 32 | title: '平台权限', 33 | key: 'all', 34 | children: [] 35 | }], 36 | checkedKeys: menus, 37 | } 38 | // console.log('menus',menus); 39 | // console.log('state-checkedKeys',this.state.checkedKeys); 40 | } 41 | 42 | getTreeNodes = (menuList) => { 43 | return menuList.reduce((pre,item)=>{ 44 | pre.push( 45 | { 46 | title: item.title, 47 | key: item.key, 48 | children: item.children ? this.getTreeNodes(item.children):null 49 | } 50 | ) 51 | return pre 52 | },[]) 53 | } 54 | 55 | // 为父组件获取最新menus的方法 56 | getMenus = () => this.state.checkedKeys 57 | 58 | 59 | // 选中某个node 60 | onCheck = checkedKeys => { 61 | console.log('onCheck', checkedKeys); 62 | // setCheckedKeys(checkedKeys); 63 | this.setState({checkedKeys}) 64 | }; 65 | 66 | // 根据新传入的role来更新checkedKeys状态 67 | // 当组件接收到新的属性时自动调用 68 | UNSAFE_componentWillReceiveProps(nextProps){ 69 | const menus = nextProps.role.menus 70 | this.setState({ 71 | checkedKeys: menus 72 | }) 73 | // this.state.checkedKeys = menus 74 | } 75 | 76 | 77 | UNSAFE_componentWillMount(){ 78 | // this.state.treeData[0].children = this.getTreeNodes(menuList) 79 | let treeDatas = this.state.treeData 80 | treeDatas[0].children = this.getTreeNodes(menuList) 81 | this.setState({ 82 | treeData: treeDatas 83 | }) 84 | 85 | } 86 | 87 | render() { 88 | const {role} = this.props 89 | const {treeData,checkedKeys} = this.state 90 | // console.log('render之前,',checkedKeys); 91 | // console.log('render',this.state.checkedKeys); 92 | // console.log('treeData',treeData); 93 | return ( 94 |
95 | 96 | 97 | 98 | 105 |
106 | ) 107 | } 108 | } 109 | 110 | // import React, { useState } from 'react'; 111 | // import { Tree } from 'antd'; 112 | // const treeData = [ 113 | // { 114 | // title: '0-0', 115 | // key: '0-0', 116 | // children: [ 117 | // { 118 | // title: '0-0-0', 119 | // key: '0-0-0', 120 | // children: [ 121 | // { 122 | // title: '0-0-0-0', 123 | // key: '0-0-0-0', 124 | // }, 125 | // { 126 | // title: '0-0-0-1', 127 | // key: '0-0-0-1', 128 | // }, 129 | // { 130 | // title: '0-0-0-2', 131 | // key: '0-0-0-2', 132 | // }, 133 | // ], 134 | // }, 135 | // { 136 | // title: '0-0-1', 137 | // key: '0-0-1', 138 | // children: [ 139 | // { 140 | // title: '0-0-1-0', 141 | // key: '0-0-1-0', 142 | // }, 143 | // { 144 | // title: '0-0-1-1', 145 | // key: '0-0-1-1', 146 | // }, 147 | // { 148 | // title: '0-0-1-2', 149 | // key: '0-0-1-2', 150 | // }, 151 | // ], 152 | // }, 153 | // { 154 | // title: '0-0-2', 155 | // key: '0-0-2', 156 | // }, 157 | // ], 158 | // }, 159 | // { 160 | // title: '0-1', 161 | // key: '0-1', 162 | // children: [ 163 | // { 164 | // title: '0-1-0-0', 165 | // key: '0-1-0-0', 166 | // }, 167 | // { 168 | // title: '0-1-0-1', 169 | // key: '0-1-0-1', 170 | // }, 171 | // { 172 | // title: '0-1-0-2', 173 | // key: '0-1-0-2', 174 | // }, 175 | // ], 176 | // }, 177 | // { 178 | // title: '0-2', 179 | // key: '0-2', 180 | // }, 181 | // ]; 182 | 183 | // const AuthForm = () => { 184 | // const [expandedKeys, setExpandedKeys] = useState(['0-0-0', '0-0-1','0-1']); 185 | // const [checkedKeys, setCheckedKeys] = useState(['0-0-0']); 186 | // const [selectedKeys, setSelectedKeys] = useState([]); 187 | // const [autoExpandParent, setAutoExpandParent] = useState(true); 188 | 189 | // const onExpand = (expandedKeys) => { 190 | // console.log('onExpand', expandedKeys); // if not set autoExpandParent to false, if children expanded, parent can not collapse. 191 | // // or, you can remove all expanded children keys. 192 | 193 | // setExpandedKeys(expandedKeys); 194 | // setAutoExpandParent(false); 195 | // }; 196 | 197 | // const onCheck = (checkedKeys) => { 198 | // console.log('onCheck', checkedKeys); 199 | // setCheckedKeys(checkedKeys); 200 | // }; 201 | 202 | // const onSelect = (selectedKeys, info) => { 203 | // console.log('onSelect', info); 204 | // setSelectedKeys(selectedKeys); 205 | // }; 206 | 207 | // return ( 208 | // 219 | // ); 220 | // }; 221 | 222 | // export default AuthForm 223 | 224 | -------------------------------------------------------------------------------- /src_react/pages/role/auth-form.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import {Form,Input,Tree} from 'antd' 3 | import PropTypes from 'prop-types' 4 | import menuList from '../../config/menuConfig' 5 | 6 | 7 | const Item = Form.Item 8 | 9 | // 创建角色的form组件 10 | export default class AuthForm extends Component { 11 | 12 | static propTypes = { 13 | role: PropTypes.object 14 | } 15 | 16 | // state = { 17 | // treeData: [{ 18 | // title: '平台权限', 19 | // key: 'all', 20 | // children: [] 21 | // }], 22 | // checkedKeys:[] 23 | // } 24 | 25 | constructor(props) { 26 | super(props) 27 | // console.log('role',this.props.role); 28 | const {menus} = this.props.role 29 | 30 | this.state = { 31 | treeData: [{ 32 | title: '平台权限', 33 | key: 'all', 34 | children: [] 35 | }], 36 | checkedKeys: menus, 37 | } 38 | // console.log('menus',menus); 39 | // console.log('state-checkedKeys',this.state.checkedKeys); 40 | } 41 | 42 | getTreeNodes = (menuList) => { 43 | return menuList.reduce((pre,item)=>{ 44 | pre.push( 45 | { 46 | title: item.title, 47 | key: item.key, 48 | children: item.children ? this.getTreeNodes(item.children):null 49 | } 50 | ) 51 | return pre 52 | },[]) 53 | } 54 | 55 | // 为父组件获取最新menus的方法 56 | getMenus = () => this.state.checkedKeys 57 | 58 | 59 | // 选中某个node 60 | onCheck = checkedKeys => { 61 | console.log('onCheck', checkedKeys); 62 | // setCheckedKeys(checkedKeys); 63 | this.setState({checkedKeys}) 64 | }; 65 | 66 | // 根据新传入的role来更新checkedKeys状态 67 | // 当组件接收到新的属性时自动调用 68 | UNSAFE_componentWillReceiveProps(nextProps){ 69 | const menus = nextProps.role.menus 70 | this.setState({ 71 | checkedKeys: menus 72 | }) 73 | // this.state.checkedKeys = menus 74 | } 75 | 76 | 77 | UNSAFE_componentWillMount(){ 78 | // this.state.treeData[0].children = this.getTreeNodes(menuList) 79 | let treeDatas = this.state.treeData 80 | treeDatas[0].children = this.getTreeNodes(menuList) 81 | this.setState({ 82 | treeData: treeDatas 83 | }) 84 | 85 | } 86 | 87 | render() { 88 | const {role} = this.props 89 | const {treeData,checkedKeys} = this.state 90 | // console.log('render之前,',checkedKeys); 91 | // console.log('render',this.state.checkedKeys); 92 | // console.log('treeData',treeData); 93 | return ( 94 |
95 | 96 | 97 | 98 | 105 |
106 | ) 107 | } 108 | } 109 | 110 | // import React, { useState } from 'react'; 111 | // import { Tree } from 'antd'; 112 | // const treeData = [ 113 | // { 114 | // title: '0-0', 115 | // key: '0-0', 116 | // children: [ 117 | // { 118 | // title: '0-0-0', 119 | // key: '0-0-0', 120 | // children: [ 121 | // { 122 | // title: '0-0-0-0', 123 | // key: '0-0-0-0', 124 | // }, 125 | // { 126 | // title: '0-0-0-1', 127 | // key: '0-0-0-1', 128 | // }, 129 | // { 130 | // title: '0-0-0-2', 131 | // key: '0-0-0-2', 132 | // }, 133 | // ], 134 | // }, 135 | // { 136 | // title: '0-0-1', 137 | // key: '0-0-1', 138 | // children: [ 139 | // { 140 | // title: '0-0-1-0', 141 | // key: '0-0-1-0', 142 | // }, 143 | // { 144 | // title: '0-0-1-1', 145 | // key: '0-0-1-1', 146 | // }, 147 | // { 148 | // title: '0-0-1-2', 149 | // key: '0-0-1-2', 150 | // }, 151 | // ], 152 | // }, 153 | // { 154 | // title: '0-0-2', 155 | // key: '0-0-2', 156 | // }, 157 | // ], 158 | // }, 159 | // { 160 | // title: '0-1', 161 | // key: '0-1', 162 | // children: [ 163 | // { 164 | // title: '0-1-0-0', 165 | // key: '0-1-0-0', 166 | // }, 167 | // { 168 | // title: '0-1-0-1', 169 | // key: '0-1-0-1', 170 | // }, 171 | // { 172 | // title: '0-1-0-2', 173 | // key: '0-1-0-2', 174 | // }, 175 | // ], 176 | // }, 177 | // { 178 | // title: '0-2', 179 | // key: '0-2', 180 | // }, 181 | // ]; 182 | 183 | // const AuthForm = () => { 184 | // const [expandedKeys, setExpandedKeys] = useState(['0-0-0', '0-0-1','0-1']); 185 | // const [checkedKeys, setCheckedKeys] = useState(['0-0-0']); 186 | // const [selectedKeys, setSelectedKeys] = useState([]); 187 | // const [autoExpandParent, setAutoExpandParent] = useState(true); 188 | 189 | // const onExpand = (expandedKeys) => { 190 | // console.log('onExpand', expandedKeys); // if not set autoExpandParent to false, if children expanded, parent can not collapse. 191 | // // or, you can remove all expanded children keys. 192 | 193 | // setExpandedKeys(expandedKeys); 194 | // setAutoExpandParent(false); 195 | // }; 196 | 197 | // const onCheck = (checkedKeys) => { 198 | // console.log('onCheck', checkedKeys); 199 | // setCheckedKeys(checkedKeys); 200 | // }; 201 | 202 | // const onSelect = (selectedKeys, info) => { 203 | // console.log('onSelect', info); 204 | // setSelectedKeys(selectedKeys); 205 | // }; 206 | 207 | // return ( 208 | // 219 | // ); 220 | // }; 221 | 222 | // export default AuthForm 223 | 224 | -------------------------------------------------------------------------------- /src/pages/category/category.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Card,Table,Button,message,Modal } from 'antd' 3 | import {PlusOutlined,ArrowRightOutlined} from '@ant-design/icons' 4 | 5 | import LinkButton from '../../components/link-button' 6 | import {reqCategorys,reqAddCategorys,reqUpdateCategorys} from '../../api/index' 7 | import AddForm from './add-form' 8 | import UpdateForm from './update-form' 9 | 10 | // 商品分类路由 11 | export default class Category extends Component { 12 | // 定义状态 13 | state = { 14 | loading:false, //标识数据是否正在加载中 15 | categorys: [], //一级分类列表 16 | subCategorys: [], //二级分类列表 17 | parentId: '0', //当前需要显示的分类列表的父分类ID 18 | parentName: '', //当前需要显示的分类列表的父分类名称 19 | showStatus: 0, //标识添加/更新的确认框是否显示 20 | // 0: 都不显示 1: 显示添加 2:显示更新 21 | } 22 | 23 | // 初始化TABLE所有列的数组 24 | initColumns = () => { 25 | // 显示列的分类 26 | this.columns = [ 27 | { 28 | title: '分类名称', 29 | dataIndex: 'name', //显示数据对应的属性名 30 | }, 31 | { 32 | title: '操作', 33 | width: 300, 34 | render: (category) => ( //返回需要显示的界面标签 35 | 36 | {this.showUpdate(category)}}>修改分类 37 | {/* 如何向事件回调函数传递参数:先定义一个匿名函数 在函数调用处理的函数并传入数据 */} 38 | 39 | {this.state.parentId==='0'?{this.showSubCategorys(category)}}>查看子分类:null} 40 | 41 | ) 42 | }, 43 | ]; 44 | } 45 | 46 | // 异步获取一级/二级分类列表显示 47 | // parentId: 如果没有指定根据状态中的parentId请求 如果有则为指定的 48 | getCategorys = async (parentId) => { 49 | // 发送请求前 显示loading 50 | this.setState({loading:true}) 51 | 52 | parentId = parentId || this.state.parentId 53 | //reqCategorys('0') 得到的是一个promise对象 54 | // 获取结果数据 55 | const result = await reqCategorys(parentId) 56 | 57 | // 请求完成后 隐藏Loading 58 | this.setState({loading:false}) 59 | 60 | if (result.status === 0) { 61 | // 说明成功取出分类数组 (可能是一级的也可能是二级) 62 | const categorys = result.data 63 | if (parentId === '0') { 64 | // 说明是一级列表 65 | // 更新状态 66 | this.setState({categorys}) 67 | } else { 68 | // 二级列表 69 | this.setState({subCategorys:categorys}) 70 | } 71 | } else { 72 | message.error('获取数据失败') 73 | } 74 | } 75 | 76 | // 显示指定一级分类对象的二级子列表 77 | showSubCategorys = (category) => { 78 | // 更新状态----> 异步 79 | this.setState({ 80 | parentId: category._id, 81 | parentName: category.name, 82 | },()=>{ 83 | // 回调函数会在状态更新并且重新render()后执行 84 | 85 | // console.log('parentId',this.state.parentId); parentId,'xxxxx' 86 | 87 | // 获取二级分类列表 88 | this.getCategorys() 89 | }) 90 | // setState()不能立即获取最新的状态:因为是异步更新状态的 91 | // console.log('parentId',this.state.parentId); parentId,0 92 | } 93 | 94 | // 显示一级分类列表 95 | showCategorys = () => { 96 | // 更新为显示一级列表的状态 97 | this.setState({ 98 | parentId: '0', 99 | parentName: '', 100 | subCategorys: [] 101 | }) 102 | } 103 | 104 | // 响应点击取消 105 | handleCancel = () => { 106 | // 清除输入文本框数据 107 | this.form.resetFields() 108 | // 隐藏确认框 109 | this.setState({ 110 | showStatus: 0 111 | }) 112 | 113 | } 114 | 115 | // 显示添加的确认框 116 | showAdd = () => { 117 | this.setState({ 118 | showStatus: 1 119 | }) 120 | } 121 | 122 | // 添加分类 123 | addCategory = () => { 124 | // 进行表单验证 125 | this.form.validateFields(async (err,values)=>{ 126 | if (!err) { 127 | // 隐藏确认框 128 | this.setState({showStatus:0}) 129 | // 收集数据并提交添加分类请求 130 | const {parentId,categoryName} = values 131 | // 清除输入数据 132 | this.form.resetFields() 133 | const result = await reqAddCategorys(categoryName,parentId) 134 | if (result.status === 0) { 135 | // 代表成功 136 | 137 | // 添加的分类就是当前分类列表下的分类 138 | if (parentId === this.state.parentId) { 139 | // 重新获取当前分类列表显示 140 | this.getCategorys() 141 | } else if (parentId==='0') { 142 | // 在二级分类列表下添加一级分类 重新获取一级分类列表 但是不需要显示一级列表 143 | this.getCategorys('0') 144 | } 145 | } 146 | } 147 | }) 148 | } 149 | 150 | // 显示更新对话框 151 | showUpdate = (category) => { 152 | 153 | // 保存分类对象 154 | this.category = category 155 | // console.log('this', this.category); 156 | 157 | this.setState({ 158 | showStatus: 2, 159 | }) 160 | 161 | } 162 | 163 | // 更新分类 164 | updateCategory = () => { 165 | // 进行表单验证 只有通过才能验证 166 | this.form.validateFields(async (err,values)=>{ 167 | if (!err) { 168 | // 1、隐藏显示框 169 | this.setState({ 170 | showStatus:0 171 | }) 172 | 173 | // 准备数据 174 | const categoryId = this.category._id 175 | 176 | // console.log('fansili ',this.form); 177 | 178 | // const categoryName = this.form.getFieldValue('categoryName') 179 | const {categoryName} = values 180 | 181 | // 清除输入文本框数据 182 | this.form.resetFields() 183 | 184 | // 2、发请求更新分类 185 | const result = await reqUpdateCategorys({categoryId,categoryName}) 186 | if (result.status === 0) { 187 | // 3、重新显示列表 188 | this.getCategorys() 189 | } 190 | } 191 | } 192 | ) 193 | 194 | 195 | 196 | } 197 | 198 | // 为第一次render() 准备数据 199 | UNSAFE_componentWillMount() { 200 | this.initColumns() 201 | } 202 | 203 | // 发送异步ajax请求 204 | componentDidMount() { 205 | // 获取一级列表 206 | this.getCategorys() 207 | } 208 | 209 | render() { 210 | 211 | // 读取状态数据 212 | const {categorys,loading,subCategorys,parentId,parentName, 213 | showStatus} = this.state 214 | 215 | // 读取指定的分类 216 | const category = this.category || {} //如果没有则先选择一个空对象以防报错 217 | // console.log( 'undefined',category.name); 218 | // const {category} = this 219 | // console.log('category',category); 220 | 221 | // card的左侧 222 | const title = parentId==='0'? '一级分类列表' : ( 223 | 224 | 一级分类列表 225 | 226 | {parentName} 227 | 228 | ) 229 | 230 | // card的右侧 231 | const extra = ( 232 | 236 | ) 237 | 238 | return ( 239 | 240 |
243 | 249 | {this.form = form}}/> 251 | 252 | 253 | 259 | {this.form = form}}/> 261 | 262 | 263 | 264 | ) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src_react/pages/category/category.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Card,Table,Button,message,Modal } from 'antd' 3 | import {PlusOutlined,ArrowRightOutlined} from '@ant-design/icons' 4 | 5 | import LinkButton from '../../components/link-button' 6 | import {reqCategorys,reqAddCategorys,reqUpdateCategorys} from '../../api/index' 7 | import AddForm from './add-form' 8 | import UpdateForm from './update-form' 9 | 10 | // 商品分类路由 11 | export default class Category extends Component { 12 | // 定义状态 13 | state = { 14 | loading:false, //标识数据是否正在加载中 15 | categorys: [], //一级分类列表 16 | subCategorys: [], //二级分类列表 17 | parentId: '0', //当前需要显示的分类列表的父分类ID 18 | parentName: '', //当前需要显示的分类列表的父分类名称 19 | showStatus: 0, //标识添加/更新的确认框是否显示 20 | // 0: 都不显示 1: 显示添加 2:显示更新 21 | } 22 | 23 | // 初始化TABLE所有列的数组 24 | initColumns = () => { 25 | // 显示列的分类 26 | this.columns = [ 27 | { 28 | title: '分类名称', 29 | dataIndex: 'name', //显示数据对应的属性名 30 | }, 31 | { 32 | title: '操作', 33 | width: 300, 34 | render: (category) => ( //返回需要显示的界面标签 35 | 36 | {this.showUpdate(category)}}>修改分类 37 | {/* 如何向事件回调函数传递参数:先定义一个匿名函数 在函数调用处理的函数并传入数据 */} 38 | 39 | {this.state.parentId==='0'?{this.showSubCategorys(category)}}>查看子分类:null} 40 | 41 | ) 42 | }, 43 | ]; 44 | } 45 | 46 | // 异步获取一级/二级分类列表显示 47 | // parentId: 如果没有指定根据状态中的parentId请求 如果有则为指定的 48 | getCategorys = async (parentId) => { 49 | // 发送请求前 显示loading 50 | this.setState({loading:true}) 51 | 52 | parentId = parentId || this.state.parentId 53 | //reqCategorys('0') 得到的是一个promise对象 54 | // 获取结果数据 55 | const result = await reqCategorys(parentId) 56 | 57 | // 请求完成后 隐藏Loading 58 | this.setState({loading:false}) 59 | 60 | if (result.status === 0) { 61 | // 说明成功取出分类数组 (可能是一级的也可能是二级) 62 | const categorys = result.data 63 | if (parentId === '0') { 64 | // 说明是一级列表 65 | // 更新状态 66 | this.setState({categorys}) 67 | } else { 68 | // 二级列表 69 | this.setState({subCategorys:categorys}) 70 | } 71 | } else { 72 | message.error('获取数据失败') 73 | } 74 | } 75 | 76 | // 显示指定一级分类对象的二级子列表 77 | showSubCategorys = (category) => { 78 | // 更新状态----> 异步 79 | this.setState({ 80 | parentId: category._id, 81 | parentName: category.name, 82 | },()=>{ 83 | // 回调函数会在状态更新并且重新render()后执行 84 | 85 | // console.log('parentId',this.state.parentId); parentId,'xxxxx' 86 | 87 | // 获取二级分类列表 88 | this.getCategorys() 89 | }) 90 | // setState()不能立即获取最新的状态:因为是异步更新状态的 91 | // console.log('parentId',this.state.parentId); parentId,0 92 | } 93 | 94 | // 显示一级分类列表 95 | showCategorys = () => { 96 | // 更新为显示一级列表的状态 97 | this.setState({ 98 | parentId: '0', 99 | parentName: '', 100 | subCategorys: [] 101 | }) 102 | } 103 | 104 | // 响应点击取消 105 | handleCancel = () => { 106 | // 清除输入文本框数据 107 | this.form.resetFields() 108 | // 隐藏确认框 109 | this.setState({ 110 | showStatus: 0 111 | }) 112 | 113 | } 114 | 115 | // 显示添加的确认框 116 | showAdd = () => { 117 | this.setState({ 118 | showStatus: 1 119 | }) 120 | } 121 | 122 | // 添加分类 123 | addCategory = () => { 124 | // 进行表单验证 125 | this.form.validateFields(async (err,values)=>{ 126 | if (!err) { 127 | // 隐藏确认框 128 | this.setState({showStatus:0}) 129 | // 收集数据并提交添加分类请求 130 | const {parentId,categoryName} = values 131 | // 清除输入数据 132 | this.form.resetFields() 133 | const result = await reqAddCategorys(categoryName,parentId) 134 | if (result.status === 0) { 135 | // 代表成功 136 | 137 | // 添加的分类就是当前分类列表下的分类 138 | if (parentId === this.state.parentId) { 139 | // 重新获取当前分类列表显示 140 | this.getCategorys() 141 | } else if (parentId==='0') { 142 | // 在二级分类列表下添加一级分类 重新获取一级分类列表 但是不需要显示一级列表 143 | this.getCategorys('0') 144 | } 145 | } 146 | } 147 | }) 148 | } 149 | 150 | // 显示更新对话框 151 | showUpdate = (category) => { 152 | 153 | // 保存分类对象 154 | this.category = category 155 | // console.log('this', this.category); 156 | 157 | this.setState({ 158 | showStatus: 2, 159 | }) 160 | 161 | } 162 | 163 | // 更新分类 164 | updateCategory = () => { 165 | // 进行表单验证 只有通过才能验证 166 | this.form.validateFields(async (err,values)=>{ 167 | if (!err) { 168 | // 1、隐藏显示框 169 | this.setState({ 170 | showStatus:0 171 | }) 172 | 173 | // 准备数据 174 | const categoryId = this.category._id 175 | 176 | // console.log('fansili ',this.form); 177 | 178 | // const categoryName = this.form.getFieldValue('categoryName') 179 | const {categoryName} = values 180 | 181 | // 清除输入文本框数据 182 | this.form.resetFields() 183 | 184 | // 2、发请求更新分类 185 | const result = await reqUpdateCategorys({categoryId,categoryName}) 186 | if (result.status === 0) { 187 | // 3、重新显示列表 188 | this.getCategorys() 189 | } 190 | } 191 | } 192 | ) 193 | 194 | 195 | 196 | } 197 | 198 | // 为第一次render() 准备数据 199 | UNSAFE_componentWillMount() { 200 | this.initColumns() 201 | } 202 | 203 | // 发送异步ajax请求 204 | componentDidMount() { 205 | // 获取一级列表 206 | this.getCategorys() 207 | } 208 | 209 | render() { 210 | 211 | // 读取状态数据 212 | const {categorys,loading,subCategorys,parentId,parentName, 213 | showStatus} = this.state 214 | 215 | // 读取指定的分类 216 | const category = this.category || {} //如果没有则先选择一个空对象以防报错 217 | // console.log( 'undefined',category.name); 218 | // const {category} = this 219 | // console.log('category',category); 220 | 221 | // card的左侧 222 | const title = parentId==='0'? '一级分类列表' : ( 223 | 224 | 一级分类列表 225 | 226 | {parentName} 227 | 228 | ) 229 | 230 | // card的右侧 231 | const extra = ( 232 | 236 | ) 237 | 238 | return ( 239 | 240 |
243 | 249 | {this.form = form}}/> 251 | 252 | 253 | 259 | {this.form = form}}/> 261 | 262 | 263 | 264 | ) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/pages/product/add-update.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Card,Form,Input,Cascader,Button, message} from 'antd' 3 | import LinkButton from '../../components/link-button' 4 | import { reqCategorys,reqAddProduct,reaUpdateProduct} from '../../api/index' 5 | import PicturesWall from './pictures-wall' 6 | import RichTextEditor from './rich-text-editor' 7 | 8 | import { ArrowLeftOutlined } from '@ant-design/icons'; 9 | 10 | 11 | const {Item} = Form 12 | const {TextArea} = Input 13 | 14 | /* 15 | 1、子组件调用父组件的方法: 16 | 将父组件的方法以函数属性的形式传递给子组件子组件就可以调用 17 | 18 | 2、父组件调用子组件的方法: 19 | 在父组件中通过ref得到子组件标签对象(也就是组件对象) 调用其方法 20 | 21 | */ 22 | 23 | 24 | // Product的添加和更新的子路由组件 25 | export default class ProductAddUpdate extends Component { 26 | 27 | formRef = React.createRef(); 28 | 29 | state = { 30 | options: [], 31 | } 32 | 33 | constructor(props) { 34 | super(props) 35 | // 创建用来保存ref标识的标签对象的容器 36 | this.pw = React.createRef() 37 | this.editor = React.createRef() 38 | } 39 | 40 | // 更新options数组 41 | initOptions = async (categorys) => { 42 | // 根据categorys 生成options数组 43 | const options = categorys.map(c => ({ 44 | value: c._id, 45 | label: c.name, 46 | isLeaf: false, //不是叶子 即还有其他子集分类 47 | })) 48 | 49 | // 如果是一个二级分类商品的更新 50 | const {isUpdate,product} = this 51 | const {pCategoryId} = product 52 | if (isUpdate && pCategoryId !== '0') { 53 | // 获取相应的二级分类列表 54 | const subCategorys = await this.getCategorys(pCategoryId) 55 | // 生成二级下拉列表的options 56 | const childOptions = subCategorys.map(c=>({ 57 | value: c._id, 58 | label: c.name, 59 | isLeaf: true, 60 | })) 61 | // 找到当前商品相应的一级option对象 62 | const targetOption = options.find(option => option.value === pCategoryId) 63 | // 关联到对应的一级option上 64 | targetOption.children = childOptions 65 | } 66 | 67 | // 更新options状态 68 | this.setState({options}) 69 | } 70 | 71 | 72 | // 用于异步获取一级/二级分类列表 并且显示 73 | // async函数的返回值就是一个新的promise对象 promise的结果和值由async的结果决定 74 | 75 | getCategorys = async (parentId) => { 76 | const result = await reqCategorys(parentId) 77 | if (result.status === 0) { 78 | const categorys = result.data 79 | // 判断 一级列表还是二级列表 80 | if (parentId === '0') { 81 | this.initOptions(categorys) 82 | } else { 83 | // 二级列表 84 | // 返回二级列表 ===> 当前async函数返回的promise就会成功且value为categorys 85 | return categorys 86 | } 87 | } 88 | } 89 | 90 | // 定义验证价格的函数 91 | validatePrice = (rule,value,callback) => { 92 | if (value * 1 > 0) { 93 | // callback() //验证通过 94 | return Promise.resolve() 95 | } else { 96 | // 验证不通过 97 | // callback('价格必须要大于0!') 会有警告 返回一个promise 98 | return Promise.reject('价格必须要大于0!') 99 | } 100 | } 101 | 102 | // 用于加载下一级列表的回调函数 103 | loadData = async selectedOptions => { 104 | // 得到点击的列表项 即option对象 105 | const targetOption = selectedOptions[0] 106 | // 显示loading效果 107 | targetOption.loading = true 108 | 109 | // 根据选中的分类 请求获取二级分类列表 110 | const subCategorys = await this.getCategorys(targetOption.value) 111 | // 隐藏loading 112 | targetOption.loading = false 113 | 114 | if (subCategorys && subCategorys.length > 0) { 115 | // 说明现在存在二级分类 116 | // 生成一个二级列表的options单 117 | const cOptions = subCategorys.map(c=>({ 118 | value: c._id, 119 | label: c.name, 120 | isLeaf: true, 121 | })) 122 | // 关联到当前的target.option身上 ===> children 123 | targetOption.children = cOptions 124 | } else { 125 | // 当前选中的列表项没有二级分类 126 | targetOption.isLeaf = true 127 | } 128 | 129 | // 更新options状态 130 | this.setState({ 131 | options: [...this.state.options], 132 | }) 133 | } 134 | 135 | submit = () => { 136 | // 进行表单验证 通过才发送请求 137 | this.formRef.current.validateFields() 138 | .then( async values => { 139 | 140 | // 1、收集数据 141 | // console.log('values',values); 142 | const {name,desc,price,categoryIds} = values 143 | let pCategoryId,categoryId 144 | if (categoryIds.length === 1) { 145 | pCategoryId = '0' 146 | categoryId = categoryIds[0] 147 | } else { 148 | pCategoryId = categoryIds[0] 149 | categoryId = categoryIds[1] 150 | } 151 | const imgs= this.pw.current.getImgs() 152 | const detail = this.editor.current.getDetail() 153 | 154 | // 封装成product对象 155 | const product = {name,desc,price,imgs,detail} 156 | console.log('product',product); 157 | 158 | /* // 如果是更新需要添加_id 159 | if (this.isUpdate) { 160 | product._id = this.product._id 161 | } 162 | 163 | // 2、调用接口请求函数去添加/更新 164 | const result = await reqAddOrUpdateProduct(product) 165 | console.log(result); 166 | // 3、根据结果显示 167 | if (result.status === 0) { 168 | message.success(`${this.isUpdate ? '更新':'添加'}商品成功!`) 169 | this.props.history.goBack() 170 | } else { 171 | message.error(`${this.isUpdate ? '更新':'添加'}商品失败!`) 172 | } */ 173 | 174 | let result 175 | // 如果是更新需要添加_id 176 | if (this.isUpdate) { 177 | product._id = this.product._id 178 | result = await reaUpdateProduct(product) 179 | if (result.status === 0) { 180 | message.success(` 更新商品成功!`) 181 | this.props.history.goBack() 182 | } else { 183 | message.error(`更新商品失败!`) 184 | } 185 | } else { 186 | result = await reqAddProduct(product) 187 | // console.log('添加',result); result.status===1 188 | if (result.status === 0) { 189 | message.success(` 添加商品成功!`) 190 | this.props.history.goBack() 191 | } else { 192 | message.error(`添加商品失败!`) 193 | } 194 | } 195 | 196 | 197 | 198 | 199 | // alert('提交请求成功') 200 | // console.log('submitvalue',values); 201 | // console.log('submit', this.pw.current.getImgs()); 202 | // const imgs= this.pw.current.getImgs() 203 | // const detail = this.editor.current.getDetail() 204 | // console.log('submit',imgs,detail); 205 | }) 206 | .catch(errInfo => { 207 | console.log('请求提交错误', errInfo); 208 | }) 209 | } 210 | 211 | componentDidMount() { 212 | this.getCategorys('0') //获取一级列表 213 | } 214 | 215 | UNSAFE_componentWillMount(){ 216 | // 如果是添加则会没值 否则有值 217 | const product = this.props.location.state 218 | // 保存是否为更新的标识 219 | this.isUpdate = !!product //强制转换为一个布尔值 220 | // 保存商品 若没有 保存一个空对象 则下面设置初始值则不会报错 221 | this.product = product || {} 222 | } 223 | 224 | render() { 225 | 226 | // 指定Item配置对象 227 | const layout = { 228 | labelCol: { 229 | span: 2, //指定左侧label的宽度 230 | }, 231 | wrapperCol: { 232 | span: 8, //指定右侧包裹的宽度 233 | }, 234 | } 235 | 236 | const {isUpdate,product} = this 237 | const {pCategoryId,categoryId,imgs,detail} = product 238 | // 用来接收级联分类ID的数组 239 | const categorys = [] 240 | if (isUpdate) { 241 | // 商品处于一级分类列表中 242 | if (pCategoryId === '0') { 243 | categorys.push(categoryId) 244 | } else { 245 | // 商品为二级分类 246 | categorys.push(pCategoryId) 247 | categorys.push(categoryId) 248 | } 249 | } 250 | 251 | const title = ( 252 | 253 | this.props.history.goBack()}> 254 | 255 | 256 | {isUpdate?'修改商品':'添加商品'} 257 | 258 | ) 259 | 260 | return ( 261 | 262 |
263 | 266 | 267 | 268 | 271 |