React 项目-后台管理系统

React 项目-后台管理系统

尚硅谷前端研究院

1 : 准备

1.1. 项目描述

1) 此项目为一个前后台分离后台管理的 SPA, 包括前端 PC 应用和后端应用

2) 包括用户管理 / 商品分类管理 / 商品管理 / 权限管理等功能模块

3) 前端: 使用 React 全家桶 + Antd + Axios + ES6 + Webpack 等技术

4) 后端: 使用 Node + Express + Mongodb 等技术

5) 采用模块化、组件化、工程化的模式开发

1.2. 项目功能界面

 

1.3. 技术选型

———————————————————————————

4

 

1.4. 前端路由

1.5. API/接口

———————————————————————————

5

 

1.6. 你能从此项目中学到什么?

1.6.1. 流程及开发方法

1) 熟悉一个项目的开发流程

2) 学会模块化、组件化、工程化的开发模式

3) 掌握使用 create-react-app 脚手架初始化 react 项目开发

4) 学会使用 node+express+mongoose+mongodb 搭建后台应用

1.6.2. React 插件或第三方库

1) 掌握使用 react-router-dom 开发单页应用

2) 学会使用 redux+react-redux+redux-thunk 管理应用组件状态

3) 掌握使用 axios/jsonp 与后端进行数据交互

4) 掌握使用 antd 组件库构建界面

5) 学会使用 echarts/bizcharts 实现数据可视化展现

6) 学会使用 react-draft-wysiwyg 实现富文本编辑器

1.7. npm/yarn 常用命令

yarn 命令文档: https://yarnpkg.com/zh-Hans/docs/cli/

npm 命令文档: https://docs.npmjs.com/cli-documentation/

## 设置淘宝镜像

npm config set registry https://registry.npm.taobao.org

yarn config set registry https://registry.npm.taobao.org

## 初始化项目:

yarn init -y

npm init -y

## 下载项目的所有声明的依赖:

yarn

npm install

———————————————————————————

6

 

## 下载指定的运行时依赖包:

yarn add webpack@3.2.1

npm install webpack@3.2.1 -S

## 下载指定的开发时依赖:

yarn add webpack@3.2.1 -D

npm install webpack@3.2.1 -D

## 全局下载指定包:

yarn global add webpack

npm install webpack -g

## 删除依赖包:

yarn remove webpack

npm remove webpack -S

yarn global remove webpack

npm remove webpack -g

## 运行项目中配置的 script:

yarn run xxx

npm run xxx

## 查看某个包的信息:

yarn info xxx

npm info xxx

1.8. git 常用基本命令

Git 在线参考手册: http://gitref.justjavac.com/

* git config --global user.name "username" //配置用户名

* git config --global user.email "xx@gmail.com" //配置邮箱

* git init //初始化生成一个本地仓库

* git add . //添加到暂存区

* git commit –m "message" //提交到本地仓库

* git remote add origin url //关联到远程仓库

* git push origin master //推送本地 master 分支到远程 master 分支

* git checkout -b dev //创建一个开发分支并切换到新分支

* git push ogigin dev //推送本地 dev 分支到远程 dev 分支

* git pull origin dev //从远程 dev 分支拉取到本地 dev 分支

* git clone url //将远程仓库克隆下载到本地

* git checkout -b dev origin/dev // 克隆仓库后切换到 dev 分支

2 : 应用开发详解

2.1. 开启项目开发

2.1.1. 使用 create-react-app(脚手架)搭建项目

1) create-react-app react 官方提供的用于搭建基于 react+webpack+es6 项目的脚手架

2) 操作:

npm install -g create-react-app : 全局下载工具

create-react-app react-admin :下载模板项目

cd react-admin

npm start

访问: localhost:3000

3) 部分同学可能会出现包版本差异的导致异常的问题

解决: 添加.env 配置文件忽略版本差异

SKIP_PREFLIGHT_CHECK=true

2.1.2. 编码测试与打包发布项目

1) 编码测试

npm start

访问: http://localhost:3000

8

 

编码, 自动编译打包刷新(live-reload), 查看效果

2) 打包发布

npm run build

npm install -g serve

serve build

访问: http://localhost:5000

2.2. 功能需求分析

演示项目功能, 对功能模块进行分析说明

2.3. 项目源码基本目录设计

2.3.1. 基本结构

2.3.2. App.js

import React, {Component} from 'react'

/*

应用根组件

*/

class App extends Component {

render() {

return (

<div>Appdiv>

)

}

}

export default App

2.3.3. index.js

import React from 'react'

import ReactDOM from 'react-dom'

import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))

2.4. 引入 antd

参考文档:

https://ant.design/docs/react/use-with-create-react-app-cn

2.4.1. 下载组件库包

yarn add antd

2.4.3. 实现组件的按需打包

1) 下载依赖模块

yarn add react-app-rewired customize-cra babel-plugin-import

2) 定义加载配置的 js 模块: config-overrides.js

const {override, fixBabelImports} = require('customize-cra');H5 前端课程系列

———————————————————————————

10

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

module.exports = override(

fixBabelImports('import', {

libraryName: 'antd',

libraryDirectory: 'es',

style: 'css',

}),

);

修改配置: package.json

"scripts": {

"start": "react-app-rewired start",

"build": "react-app-rewired build",

"test": "react-app-rewired test",

"eject": "react-scripts eject"

},

2.4.4. 在应用中使用 antd 组件

import React, {Component} from 'react'

import {Button, message} from 'antd'

/*

应用根组件

*/

class App extends Component {

handleClick = () => {

message.success('成功啦...');

}

render() {

return (

<Button type='primary' onClick={this.handleClick}>学习Button>

)

}

}

export default App

2.4.6. 自定义 antd 主题

需求:

使 antd 的默认基本颜色从 Blue 变为 Green

下载工具包:

yarn add less less-loader

修改 config-overrides.js

const {override, fixBabelImports, addLessLoader} = require('customize-cra');

module.exports = override(

fixBabelImports('import', {

libraryName: 'antd',

libraryDirectory: 'es',

style: true,

}),

addLessLoader({

javascriptEnabled: true,

modifyVars: {'@primary-color': '#1DA57A'},

}),

);

2.4.7. 应用中使用的组件

2.4.8. 项目中用到的 antd 组件

2.5. 引入路由

2.5.1. 下载路由包: react-router-dom

yarn add react-router-dom

2.5.2. 前台应用路由

2.5.3. 路由组件: pages/login/login.jsx

/*

用户登陆的路由组件

*/

import React, {Component} from 'react'

export default class Login extends Component {

render () {

return (

<div>logindiv>

)

}

}

2.5.4. 后台管理主路由组件: pages/admin/admin.jsx

/*

后台管理主路由组件

*/

import React, {Component} from 'react'

export default class Admin extends Component {

render() {

return (

<div>Admindiv>

)

}

}

2.5.5. 映射路由: App.js

import React, {Component} from 'react'

import {BrowserRouter, Switch, Route} from 'react-router-dom'

import Login from './pages/login/login'

import Admin from './pages/admin/admin'

/*

应用根组件

*/

class App extends Component {

render() {

return (

<BrowserRouter>

<Switch>

<Route path='/login' component={Login}/>

<Route path='/' component={Admin}/>

Switch>

BrowserRouter>

)

}

}

export default App

2.6. Login 组件(不与后台交互)

2.6.1. 静态组件

1. 图片资源

assets/images/logo.png

assets/images/bg.jpg

2. public/css/reset.css

html,

body,

p,

ol,

ul,

li,

dl,

dt,

dd,

blockquote,

figure,

fieldset,

legend,

textarea,

pre,

iframe,

hr,

h1,

h2,

h3,

h4,

h5,

h6 {

margin: 0;

padding: 0;

}

h1,

h2,

h3,

h4,

h5,

h6 {

font-size: 100%;

font-weight: normal;

}

ul {

list-style: none;

}

button,

input,

select,

textarea {

margin: 0;

}

html {

box-sizing: border-box;

}

*, *:before, *:after {

box-sizing: inherit;

}

img,

embed,

iframe,

object,

video {

height: auto;

max-width: 100%;

}

audio {

max-width: 100%;

}

iframe {

border: 0;

}

table {

border-collapse: collapse;

border-spacing: 0;

}

td,

th {

padding: 0;

text-align: left;

}

html, body {

height: 100%;

}

#root {

width: 100%;

height: 100%;

}

注意: 必须在 index.html 中引入

3. login/login.lessH5 前端课程系列

———————————————————————————

18

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

.login {

width: 100%;

height: 100%;

background-image: url('./images/bg.jpg');

background-size: 100% 100%;

.login-header {

display: flex;

align-items: center;

height: 80px;

background-color: rgba(21, 20, 13, 0.5);

img {

width: 40px;

height: 40px;

margin-left: 50px;

}

h1 {

font-size: 30px;

color: white;

margin: 0 0 0 15px;

}

}

.login-content {

margin: 50px auto;

width: 400px;

height: 300px;

background-color: #fff;

padding: 20px 40px;

h3 {

font-size: 30px;

font-weight: bold;

text-align: center;

margin-bottom: 20px;

}

.login-form {

.login-form-button {

width: 100%;

}

}

}

}H5 前端课程系列

———————————————————————————

19

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

4. login/login.jsx

import React, {Component} from 'react'

import {

Form,

Input,

Icon,

Button,

} from 'antd'

import logo from './images/logo.png'

import './login.less'

const Item = Form.Item

/*

登陆路由组件

*/

class Login extends Component {

render() {

return (

<div className='login'>

<header className='login-header'>

<img src={logo} alt="logo"/>

<h1>React 项目: 后台管理系统h1>

header>

<section className='login-content'>

<h3>用户登陆h3>

<Form onSubmit={this.login} className="login-form">

<Item>

<Input prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}

placeholder="用户名"/>

Item>

<Item>

<Input prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>}

type="password" placeholder="密码"/>

Item>

<Item>

<Button type="primary" htmlType="submit" className="login-form-button">

登录

Button>

Item>

Form>

section>

div>

)

}

}

export default Login

2.6.2. 前台表单验证与数据收集

用户名/密码的合法性要求:

/*

用户名/密码的的合法性要求

1). 必须输入

2). 必须大于等于 4

3). 必须小于等于 12

4). 必须是英文、数字或下划线组成

*/

import React, {Component} from 'react'

import {

Form,

Input,

Icon,

Button,

} from 'antd'

import logo from './images/logo.png'

import './login.less'

const Item = Form.Item

/*

登陆路由组件

*/

class Login extends Component {

/*

登陆

*/

login = (e) => {

// 阻止事件默认行为(不提交表单)

e.preventDefault()

// 进行表单所有控件的校验

this.props.form.validateFields(async (err, values) => {

if (!err) {

// 校验成功

const {username, password} = values

console.log('提交登陆请求', username, password)

} else {

// 校验失败

console.log(err)

}

})

}

/**

* 自定义表单的校验规则

*/

validator = (rule, value, callback) => {

// console.log(rule, value)

const length = value && value.length

const pwdReg = /^[a-zA-Z0-9_]+$/

if (!value) {

// callback 如果不传参代表校验成功,如果传参代表校验失败,并且会提示错误

callback('必须输入密码')

} else if (length < 4) {

callback('密码必须大于 4 ')

} else if (length > 12) {

callback('密码必须小于 12 ')

} else if (!pwdReg.test(value)) {

callback('密码必须是英文、数组或下划线组成')

} else {

callback() // 必须调用 callback

}

}

render() {

const {getFieldDecorator} = this.props.form

return (

<div className='login'>

<header className='login-header'>

<img src={logo} alt="logo"/>

<h1>React 项目: 后台管理系统h1>

header>

<section className='login-content'>

<h3>用户登陆h3>

<Form onSubmit={this.login} className="login-form">

<Item>

{

/*

getFieldDecorator 是一个高阶函数(返回值是一个函数)

getFieldDecorator(标识名称,配置对象)(组件标签) 返回新的标签

经过 getFieldDecorator 包装的表单控件会自动添加 value onChange,数据同步

将被 form 接管

*/

getFieldDecorator('username', {

// 根据内置验证规则进行声明式验证

rules: [

{required: true, whitespace: true, message: '必须输入用户名'},

{min: 4, message: '用户名必须大于 4 '},

{max: 12, message: '用户名必须小于 12 '},

{pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是英文、数组或下划线

组成'}

]

})(

<Input prefix={<Icon type="user" style={{color:

'rgba(0,0,0,.25)'}}/>} placeholder="用户名"/>

)

}

Item>

<Item>

{

getFieldDecorator('password', {

rules: [

// 自定义表单校验规则

{validator: this.validator}

]

})(

<Input prefix={<Icon type="lock" style={{color:

'rgba(0,0,0,.25)'}}/>} type="password"

placeholder="密码"/>

)

}

Item>

<Item>

<Button type="primary" htmlType="submit" className="login-form-button">

登录

Button>

Item>

Form>

section>

div>

)

}

}

/*

用户名/密码的的合法性要求

1). 必须输入

2). 必须大于 4

3). 必须小于 12

4). 必须是英文、数组或下划线组成

*/

export default Form.create()(Login)

2.7. 运行 server 端项目

2.7.1. 说明

1) 咱们的项目是一个前后台分离的项目: 前台应用与后台应用

2) 后台应用负责处理前台应用提交的请求, 并给前台应用返回 json 数据

3) 前台应用负责展现数据, 与用户交互, 与后台应用交互

2.7.2. 运行后台应用

1) 确保启动 mongodb 服务

2) 启动服务器应用: npm start

2.7.3. API 接口文档

## 目录:

1). 登陆

2). 添加用户

3). 更新用户

4). 获取所有用户列表

5). 删除用户

6). 获取一级或某个二级分类列表

7). 添加分类

8). 更新品类名称

9). 根据分类 ID 获取分类

10). 获取商品分页列表

11). 根据 ID/Name 搜索产品分页列表

12). 添加商品

13). 更新商品

14). 对商品进行上架/下架处理

15). 上传图片

16). 删除图片

17). 添加角色

18). 获取角色列表

19). 更新角色(给角色设置权限)

20). 获取天气信息(jsonp)H5 前端课程系列

———————————————————————————

26

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.14.4. 使用 postman 工具测试接口

1) postman 是用来测试 API 接口的工具

2) postman 可以看作活接口文档

2.8. 前后台交互 ajax

2.15.1. 下载依赖包

yarn add axios

2.15.2. 封装 ajax 请求模块

1) api/ajax.js

/*

能发送 ajax 请求的函数模块

包装 axios

函数的返回值是 promise 对象

axios.get()/post()返回的就是 promise 对象

返回自己创建的 promise 对象:

统一处理请求异常

异步返回结果数据, 而不是包含结果数据的 response

*/

import axios from 'axios'

import {message} from 'antd'

export default function ajax(url, data = {}, method = 'GET') {

return new Promise(function (resolve, reject) {

let promise

// 执行异步 ajax 请求

if (method === 'GET') {

promise = axios.get(url, {params: data}) // params 配置指定的是 query 参数

} else {

promise = axios.post(url, data)

}

promise.then(response => {

// 如果成功了, 调用 resolve(response.data)

resolve(response.data)

}).catch(error => { // 对所有 ajax 请求出错做统一处理, 外层就不用再处理错误了

// 如果失败了, 提示请求后台出错

message.error('请求错误: ' + error.message)

})

})

}

2) api/index.js

/*

包含 n 个接口请求函数的模块

每个函数返回 promise

*/

import ajax from './ajax'

// 登陆

export const reqLogin = (username, password) => ajax('/login', {username, password},

'POST')

2.15.3. 配置代理

package.json

"proxy": "http://localhost:5000"

2.15.4. 请求测试: login.jsx

// 请求后台登陆

login = async (username, password) => {

console.log('发送登陆的 ajax 请求', username, password)

const result = await reqLogin(username, password)

console.log('login()', result)

}

2.9. Login 组件(完成登陆功能)

2.9.1. 下载依赖

yarn add store

2.9.2. utils/memoryUtils.js

/*

用来在内存中缓存数据的工具对象

*/

export default {

user: {} // 内存中保存登陆的 user 信息对象

}

2.9.3. login/login.jsx

import React, {Component} from 'react'

import {

Form,

Icon,

Input,

Button,

message

} from 'antd'

import './login.less'

import logo from './images/logo.png'

import memoryUtils from '../../utils/memoryUtils'

import {reqLogin} from '../../api'

const Item = Form.Item // 不能写在 import 之前

/*

登陆的路由组件

*/

class Login extends Component {

handleSubmit = (event) => {

// 阻止事件的默认行为

event.preventDefault()

// 对所有表单字段进行检验

this.props.form.validateFields(async (err, values) => {

// 检验成功

if (!err) {

// console.log('提交登陆的 ajax 请求', values)

const {username, password} = values

const result = await reqLogin(username, password)

// console.log('login()', result)

if(result.status === 0) {

// 提示登录成功

message.success('登录成功', 2)

// 保存用户登录信息

memoryUtils.user = result.data

// 跳转到主页面

this.props.history.replace('/')

} else {

// 登录失败, 提示错误

message.error(result.msg)

}

} else {

console.log('检验失败!')

}

});

// 得到 form 对象

// const form = this.props.form

// // 获取表单项的输入数据

// const values = form.getFieldsValue()

// console.log('handleSubmit()', values)

}

/*

对密码进行自定义验证

*/

/*

用户名/密码的的合法性要求

1). 必须输入

2). 必须大于等于 4

3). 必须小于等于 12

4). 必须是英文、数字或下划线组成

*/

validatePwd = (rule, value, callback) => {

console.log('validatePwd()', rule, value)

if(!value) {

callback('密码必须输入')

} else if (value.length<4) {

callback('密码长度不能小于 4 ')

} else if (value.length>12) {

callback('密码长度不能大于 12 ')

} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {

callback('密码必须是英文、数字或下划线组成')

} else {

callback() // 验证通过

}

// callback('xxxx') // 验证失败, 并指定提示的文本

}

render () {

// 得到具强大功能的 form 对象

const form = this.props.form

const { getFieldDecorator } = form;

return (

<div className="login">

<header className="login-header">

<img src={logo} alt="logo"/>

<h1>React 项目: 后台管理系统h1>

header>

<section className="login-content">

<h2>用户登陆h2>

<Form onSubmit={this.handleSubmit} className="login-form">

<Item>

{

/*

用户名/密码的的合法性要求

1). 必须输入

2). 必须大于等于 4

3). 必须小于等于 12

4). 必须是英文、数字或下划线组成

*/

}

{

getFieldDecorator('username', { // 配置对象: 属性名是特定的一些名称

// 声明式验证: 直接使用别人定义好的验证规则进行验证

rules: [

{ required: true, whitespace: true, message: '用户名必须输入' },

{ min: 4, message: '用户名至少 4 ' },

{ max: 12, message: '用户名最多 12 ' },

{ pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是英文、数字或下划

线组成' },

],

initialValue: 'admin' //指定初始值

})(

<Input

prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}

placeholder="用户名"

/>

)

}

Item>

<Form.Item>

{

getFieldDecorator('password', {

rules: [

{

validator: this.validatePwd

}

]

})(

<Input

prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}

type="password"

placeholder="密码"

/>

)

}

Form.Item>

<Form.Item>

<Button type="primary" htmlType="submit" className="login-form-button">

登陆

Button>

Form.Item>

Form>

section>

div>

)

}

}

const WrapLogin = Form.create()(Login)

export default WrapLogin

2.9.4. admin/admin.jsx

import React, {Component} from 'react'

import {Redirect} from 'react-router-dom'

import memeoryUtils from '../../utils/memoryUtils'

/*

后台管理的路由组件

*/

export default class Admin extends Component {

render () {

const user = memeoryUtils.user

if(!user._id) {

return <Redirect to='/login'/>

}

return (

<div>

<h2>后台管理h2>

<div>Hello {user.username}div>

div>

)

}

}

———————————————————————————

33

 

2.10. 维持登陆与自动登陆

/*

1. 登陆后, 刷新后依然是已登陆状态 (维持登陆)

2. 登陆后, 关闭浏览器后打开浏览器访问依然是已登陆状态 (自动登陆)

3. 登陆后, 访问登陆路径自动跳转到管理界面

*/

2.10.1. utils/storageUtils.js

import store from 'store'

const USER_KEY = 'user_key'

/*

包含 n 个操作 local storage 的工具函数的模块

*/

export default {

saveUser(user) {

// localStroage 只能保存 string, 如果传递是对象, 会自动调用对象的 toString()并保存

//localStorage.setItem(USER_KEY, JSON.stringify(user)) // 保存的必须是对象的 json

store.set(USER_KEY, user) // 内部会自动转换成 json 再保存

},

getUser() { // 如果存在, 需要返回的是对象, 如果没有值, 返回{}

// return JSON.parse(localStorage.getItem(USER_KEY) || '{}') // [object, Object]

return store.get(USER_KEY) || {}

},

removeUser() {

// localStorage.removeItem(USER_KEY)

store.remove(USER_KEY)

}

}

———————————————————————————

34

 

2.10.2. login/login.jsx

// 判断登录是否成功

if (result.status === 0) {

// 登录成功

// 提示登录成功,保存用户登录信息,跳转到主页面

message.success('登录成功');

// 保存用户数据

const user = result.data

storageUtils.saveUser(user)

memoryUtils.user = user

// 跳转到后台管理路由(已经登录成功,不需要回退了)

this.props.history.replace('/')

}

render() {

// 如果用户已经登陆, 自动跳转到 admin

if (memoryUtils.user && memoryUtils.user._id) {

return <Redirect to='/'/>

}

}

2.10.3. src/index.js

import storageUtils from './utils/storageUtils'

import memoryUtils from './utils/memoryUtils'

// 如果 local 中保存了 user, user 保存到内存中

const user = storageUtils.getUser()

if(user && user._id) {

memoryUtils.user = user

}H5 前端课程系列

———————————————————————————

35

 

2.11. Admin 组件(搭建整体结构)

2.11.1. 整体组件组成

2.11.2. LeftNav 组件

1) components/left-nav/index.less

.left-nav {

color: white;

}

2) components/left-nav/index.jsx

import React, {Component} from 'react'

import './index.less'

/*

左侧导航组件

*/

export default class LeftNav extends Component {

render() {

return (

<div className="left-nav">

LeftNav

div>

———————————————————————————

36

 

)

}

}

2.11.3. Header 组件

1) components/header/index.less

.header {

height: 80px;

}

2) components/header/index.jsx

import React, {Component} from 'react'

import './index.less'

/*

头部组件

*/

export default class Header extends Component {

render() {

return (

<div className='header'>

Header

div>

)

}

}

2.11.4. Admin 组件

1) pages/admin/admin.jsx

import React, {Component} from 'react'

import {Redirect} from 'react-router-dom'

import { Layout } from 'antd'

———————————————————————————

37

 

import memeoryUtils from '../../utils/memoryUtils'

import Header from '../../components/header'

import LeftNav from '../../components/left-nav'

const { Footer, Sider, Content } = Layout

/*

后台管理的路由组件

*/

export default class Admin extends Component {

render () {

const user = memeoryUtils.user

if(!user._id) {

return <Redirect to='/login'/>

}

return (

<Layout style={{height: '100%'}}>

<Sider>

<LeftNav/>

Sider>

<Layout>

<Header>HeaderHeader>

<Content style={{backgroundColor: 'white'}}>ContentContent>

<Footer style={{textAlign: 'center', color: '#aaaaaa'}}>推荐使用谷歌浏览器,

可以获得更佳页面操作体验Footer>

Layout>

Layout>

)

}

}H5 前端课程系列

———————————————————————————

38

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.12. Admin 的子路由

2.12.1. 组成H5 前端课程系列

———————————————————————————

39

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.12.2. 定义各个子路由组件

2.12.3. 注册路由: admin.jsx

import React, {Component} from 'react'

import {Redirect, Route, Switch} from 'react-router-dom'

import {Layout} from 'antd'

import memeoryUtils from '../../utils/memoryUtils'

import Header from '../../components/header'

import LeftNav from '../../components/left-nav'

import Home from '../home/home'

import Category from '../category/category'

import Product from '../product/product'

import Role from '../role/role'

import User from '../user/user'

import Bar from '../charts/bar'

import Line from '../charts/line'

import Pie from '../charts/pie'

const {Footer, Sider, Content} = Layout

/*H5 前端课程系列

———————————————————————————

40

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

后台管理的路由组件

*/

export default class Admin extends Component {

render() {

const user = memeoryUtils.user

if (!user._id) {

return <Redirect to='/login'/>

}

return (

<Layout style={{height: '100%'}}>

<Sider>

<LeftNav/>

Sider>

<Layout>

<Header>HeaderHeader>

<Content style={{backgroundColor: 'white'}}>

<Switch>

<Route path='/home' component={Home}/>

<Route path='/category' component={Category}/>

<Route path='/product' component={Product}/>

<Route path='/role' component={Role}/>

<Route path='/user' component={User}/>

<Route path='/charts/bar' component={Bar}/>

<Route path='/charts/line' component={Line}/>

<Route path='/charts/pie' component={Pie}/>

<Redirect to='/home' />

Switch>

Content>

<Footer style={{textAlign: 'center', color: '#aaaaaa'}}>推荐使用谷歌浏览器,

可以获得更佳页面操作体验Footer>

Layout>

Layout>

)

}

}H5 前端课程系列

———————————————————————————

41

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.13. LeftNav 组件

2.13.1. 导航菜单配置: config/menuConfig.js

const menuList = [

{

title: '首页', // 菜单标题名称

key: '/home', // 对应的 path

icon: 'home', // 图标名称

},

{

title: '商品',

key: '/products',

icon: 'appstore',

children: [ // 子菜单列表

{

title: '品类管理',

key: '/category',

icon: 'bars'

},

{

title: '商品管理',

key: '/product',

icon: 'tool'

},

]

},

{

title: '用户管理',

key: '/user',

icon: 'user'

},

{

title: '角色管理',

key: '/role',

icon: 'safety',

},

{H5 前端课程系列

———————————————————————————

42

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

title: '图形图表',

key: '/charts',

icon: 'area-chart',

children: [

{

title: '柱形图',

key: '/charts/bar',

icon: 'bar-chart'

},

{

title: '折线图',

key: '/charts/line',

icon: 'line-chart'

},

{

title: '饼图',

key: '/charts/pie',

icon: 'pie-chart'

},

]

},

]

export default menuList

2.13.2. left-nav/index.less

.left-nav {

.logo-link {

display: flex;

align-items: center;

height: 80px;

background-color: #002140;

img {

width: 40px;

height: 40px;

margin: 0 15px 0 15px;

}

h1 {

margin-bottom: 0;H5 前端课程系列

———————————————————————————

43

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

color: white;

font-size: 20px;

}

}

}

2.13.3. 导航菜单组件: left-nav/index.jsx

import React, {Component} from 'react'

import {Link, withRouter} from 'react-router-dom'

import {Menu, Icon} from 'antd'

import menuConfig from '../../config/menuConfig'

import logo from '../../assets/images/logo.png'

import './index.less'

const SubMenu = Menu.SubMenu

/*

左侧导航组件

*/

class LeftNav extends Component {

/*

根据指定菜单数据列表产生

的子节点数组

使用 reduce() + 递归

*/

getMenuNodes = (menuList) => {

// 得到当前请求的 path

const path = this.props.location.pathname

return menuList.reduce((pre, item) => {

if (!item.children) {

pre.push((

<Menu.Item key={item.key}>

<Link to={item.key}>

<Icon type={item.icon}/>

<span>{item.title}span>

Link>

Menu.Item>H5 前端课程系列

———————————————————————————

44

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

))

} else {

pre.push((

<SubMenu

key={item.key}

title={

<span>

<Icon type={item.icon}/>

<span>{item.title}span>

span>

}

>

{this.getMenuNodes(item.children)}

SubMenu>

))

// 如果当前请求路由与当前菜单的某个子菜单的 key 匹配, 将菜单的 key 保存为 openKey

if(item.children.find(cItem => path.indexOf(cItem.key)===0)) {

this.openKey = item.key

}

}

return pre

}, [])

}

/*

根据指定菜单数据列表产生

的子节点数组

使用 map() + 递归

*/

getMenuNodes2 = (menuList) => {

// 得到当前请求的 path

const path = this.props.location.pathname

return menuList.map(item => {

if(!item.children) {

return (

<Menu.Item key={item.key}>

<Link to={item.key}>

<Icon type={item.icon}/>

<span>{item.title}span>

Link>H5 前端课程系列

———————————————————————————

45

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

Menu.Item>

)

} else {

// 如果当前请求路由与当前菜单的某个子菜单的 key 匹配, 将菜单的 key 保存为 openKey

if(item.children.find(cItem => path.indexOf(cItem.key)===0)) {

this.openKey = item.key

}

return (

<SubMenu

key={item.key}

title={

<span>

<Icon type={item.icon}/>

<span>{item.title}span>

span>

}

>

{this.getMenuNodes(item.children)}

SubMenu>

)

}

})

}

/*

在第一次 render()之前执行一次

一般可以在此同步为第一次 render()准备数据

*/

componentWillMount() {

// this.menuNodes = this.getMenuNodes(menuConfig)

this.menuNodes = this.getMenuNodes2(menuConfig)

}

render() {

// 得到当前请求路径, 作为选中菜单项的 key

const selectKey = this.props.location.pathname

const openKey = this.openKey

return (

<div className="left-nav">

<Link to='/home' className='logo-link'>

<img src={logo} alt="logo"/>H5 前端课程系列

———————————————————————————

46

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<h1>硅谷后台h1>

Link>

<Menu

mode="inline"

theme="dark"

selectedKeys={[selectKey]}

defaultOpenKeys={[openKey]}

>

{

this.menuNodes

}

Menu>

div>

)

}

}

/*

withRouter: 高阶组件: 包装非路由组件返回一个包装后的新组件, 新组件会向被包装组件传递

history/location/match 属性

*/

export default withRouter(LeftNav)

/*

2 个问题:

1). 自动选中对应的菜单项

2). 有可能需要自动菜单项

*/

2.14. Header 组件

2.14.1. 下载依赖

yarn add jsonpH5 前端课程系列

———————————————————————————

47

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.14.2. api/index.js

百度地图天气预报在线接口:

http://api.map.baidu.com/telematics/v3/weather?location=xxx&ou

tput=json&ak=3p49MVra6urFRGOT9s8UBWr2

import jsonp from 'jsonp'

/*

通过 jsonp 请求获取天气信息

*/

export function reqWeather(city) {

const url =

`http://api.map.baidu.com/telematics/v3/weather?location=${city}&output=json&ak=3p4

9MVra6urFRGOT9s8UBWr2`

return new Promise((resolve, reject) => {

jsonp(url, {

param: 'callback'

}, (error, response) => {

if (!error && response.status == 'success') {

const {dayPictureUrl, weather} = response.results[0].weather_data[0]

resolve({dayPictureUrl, weather})

} else {

alert('获取天气信息失败')

}

})

})

}

2.14.3. header/index.less

.header {

height: 80px;

background-color: #fff;

.header-top {

height: 40px;

line-height: 40px;

text-align: right;

padding-right: 20px;

border-bottom: 1px solid #1DA57A;H5 前端课程系列

———————————————————————————

48

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

.header-bottom {

display: flex;

align-items: center;

height: 40px;

.header-bottom-left {

position: relative;

width: 25%;

font-size: 20px;

text-align: center;

&::after {

content: '';

position: absolute;

top: 30px;

right: 50%;

transform: translateX(50%);

border-top: 20px solid white;

border-right: 20px solid transparent;

border-bottom: 20px solid transparent;

border-left: 20px solid transparent;

}

}

.header-bottom-right {

width: 75%;

text-align: right;

margin-right: 30px;

img {

width: 30px;

height: 20px;

margin: 0 15px;

}

}

}

}

2.14.4. utils/dateUtils.js

/*H5 前端课程系列

———————————————————————————

49

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

包含 n 个日期时间处理的工具函数模块

*/

/*

格式化日期

*/

export function formateDate(time) {

if (!time) return ''

let date = new Date(time)

return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()

+ ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds()

}

2.14.5. 抽取通用组件: link-button

1). index.jsx

import React from 'react'

import './index.less'

/*

通用的看起来像链接的 button 组件

*/

export default function LinkButton (props) {

return <button {...props} className='link-button'>button>

}

2). index.less

.link-button {

border: none;

outline: none;

background-color: transparent;

color: #1DA57A;

cursor: pointer;

}H5 前端课程系列

———————————————————————————

50

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.14.6. header/header.jsx

import React, {Component} from 'react'

import {Modal} from 'antd'

import {withRouter} from 'react-router-dom'

import LinkButton from '../link-button'

import menuList from '../../config/menuConfig'

import {reqWeather} from '../../api'

import {formateDate} from '../../utils/dateUtils'

import memoryUtils from '../../utils/memoryUtils'

import storageUtils from '../../utils/storageUtils'

import './index.less'

/*

头部组件

*/

class Header extends Component {

state = {

sysTime: formateDate(Date.now()),

dayPictureUrl: '', // 天气图片的 url

weather: ''

}

/*

发异步 ajax 获取天气数据并更新状态

*/

getWeather = async () => {

const {dayPictureUrl, weather} = await reqWeather('北京')

this.setState({

dayPictureUrl,

weather

})

}

/*

启动循环定时器, 每隔 1s 更新一次 sysTime

*/

getSysTime = () => {

this.intervalId = setInterval(() => {H5 前端课程系列

———————————————————————————

51

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

this.setState({

sysTime: formateDate(Date.now())

})

}, 1000)

}

/*

退出登陆

*/

logout = () => {

Modal.confirm({

content: '确定退出吗?',

onOk: () => {

console.log('OK')

// 移除保存的 user

storageUtils.removeUser()

memoryUtils.user = {}

// 跳转到 login

this.props.history.replace('/login')

},

onCancel() {

console.log('Cancel')

},

})

}

/*

根据请求的 path 得到对应的标题

*/

getTitle = (path) => {

let title

menuList.forEach(menu => {

if(menu.key===path) {

title = menu.title

} else if (menu.children) {

menu.children.forEach(item => {

if(path.indexOf(item.key)===0) {

title = item.title

}

})

}

})H5 前端课程系列

———————————————————————————

52

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

return title

}

componentDidMount () {

this.getSysTime()

this.getWeather()

}

componentWillUnmount () {

// 清除定时器

clearInterval(this.intervalId)

}

render() {

const {sysTime, dayPictureUrl, weather} = this.state

// 得到当前用户

const user = memoryUtils.user

// 得到当前请求的路径

const path = this.props.location.pathname

// 得到对应的标题

const title = this.getTitle(path)

return (

<div className="header">

<div className="header-top">

<span>欢迎, {user.username}span>

<LinkButton onClick={this.logout}>退出LinkButton>

div>

<div className="header-bottom">

<div className="header-bottom-left">{title}div>

<div className="header-bottom-right">

<span>{sysTime}span>

<img src={dayPictureUrl} alt="weather"/>

<span>{weather}span>

div>

div>

div>

)

}H5 前端课程系列

———————————————————————————

53

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

export default withRouter(Header)

2.14.7. pages/admin/admin.jsx

<Content style={{backgroundColor: 'white', margin: '20px 20px 0'}}>

2.15. Home 界面

2.15.1. home.less

.home{

width: 100%;

height: 100%;

display: flex;

align-items: center;

justify-content: center;

font-size: 30px;

}

2.15.1. home.jsx

import React from 'react'

import './home.less'

/*

Home 路由组件

*/

export default function Home(props) {

return (

<div className="home">

欢迎使用硅谷后台管理系统

div>

)H5 前端课程系列

———————————————————————————

54

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

2.16. 分类管理

2.16.1. api/index.js

// 获取一级或某个二级分类列表

export const reqCategorys = (parentId) => ajax('/manage/category/list', {parentId})

// 添加分类

export const reqAddCategory = (parentId, categoryName) => ajax('/manage/category/add',

{

parentId,

categoryName

}, 'POST')

// 更新品类名称

export const reqUpdateCategory = ({categoryId, categoryName}) =>

ajax('/manage/category/update', {

categoryId,

categoryName

}, 'POST')

2.16.2. category.jsx

import React, {Component} from 'react'

import {

Card,

Table,

Button,

Icon,

message,

Modal

} from 'antd'

import UpdateForm from './update-form'H5 前端课程系列

———————————————————————————

55

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

import AddForm from './add-form'

import LinkButton from '../../components/link-button'

import {reqCategorys, reqAddCategory, reqUpdateCategory} from "../../api";

/*

分类管理路由组件

*/

export default class Category extends Component {

state = {

categorys: [], // 一级分类列表

subCategorys: [], // 二级分类列表

parentId: '0', // 父分类的 ID

parentName: '', // 父分类的名称

loading: false, // 标识是否正在加载中

showStatus: 0, // 是否显示对话框 0: 都不显示, 1: 显示添加, 2: 显示更新

}

/*

根据 parentId 异步获取分类列表显示

*/

getCategorys = async (parentId) => {

// 更新 loading 状态: 加载中

this.setState({

loading: true

})

// 优先使用指定的 parentId, 如果没有指定使用状态中的 parentId

parentId = parentId || this.state.parentId

// 异步获取分类列表

const result = await reqCategorys(parentId) // {status: 0, data: []}

// 更新 loading 状态: 加载完成

this.setState({

loading: false

})

if (result.status === 0) {

const categorys = result.data

if (parentId === '0') {

// 更新一级分类列表

this.setState({

categorys

})H5 前端课程系列

———————————————————————————

56

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

} else {

// 更新二级分类列表

this.setState({

subCategorys: categorys

})

}

} else {

// 获取列表失败

message.error('获取列表失败')

}

}

/*

显示指定分类的子分类列表

*/

showSubCates = (category) => {

// console.log('set 之前', this.state.parentId) // 0

// 更新状态: state 中的数据是异步更新(不会立即更新 state 中的数据)

this.setState({

parentId: category._id,

parentName: category.name

}, () => { // 在状态更新之后执行, 在回调函数中能得到最新的状态数据

this.getCategorys()

})

// console.log('set 之后', this.state.parentId) // xxx

}

/*

显示一级列表

*/

showCategorys = () => {

this.setState({

parentId: '0',

parentName: '',

subCategorys: [],

showStatus: 0,

})

}

/*

显示添加的对话框H5 前端课程系列

———————————————————————————

57

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

*/

showAdd = () => {

this.setState({

showStatus: 1

})

}

/*

显示修改的对话框

*/

showUpdate = (category) => {

// 保存 category

this.category = category

// 更新状态

this.setState({

showStatus: 2

})

}

/*

添加分类

*/

addCategory = async () => {

// 得到数据

const {parentId, categoryName} = this.form.getFieldsValue()

// 关闭对话框

this.setState({

showStatus: 0

})

// 重置表单

this.form.resetFields()

// 异步请求添加分类

const result = await reqAddCategory(categoryName, parentId)

if (result.status === 0) {

/*

添加一级分类

在当前分类列表下添加

*/

if( parentId===this.state.parentId) {

this.getCategorys()

} else if (parentId === '0') {H5 前端课程系列

———————————————————————————

58

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

this.getCategorys(parentId)

}

}

}

/*

更新分类

*/

updateCategory = async () => {

// 得到数据

const categoryId = this.category._id

const {categoryName} = this.form.getFieldsValue()

// 关闭对话框

this.setState({

showStatus: 0

})

// 重置表单

this.form.resetFields()

// 异步请求更新分类

const result = await reqUpdateCategory({categoryId, categoryName})

if (result.status === 0) {

// 重新获取列表

this.getCategorys()

}

}

componentWillMount() {

this.columns = [

{

title: '分类名称',

dataIndex: 'name',

},

{

title: '操作',

width: 300,

render: (category) => (

<span>

<LinkButton onClick={() => this.showUpdate(category)}>修改分类

LinkButton>   H5 前端课程系列

———————————————————————————

59

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

{this.state.parentId === '0' ?

<LinkButton onClick={() => this.showSubCates(category)}>查看子分类LinkButton> : null}

span>

)

}];

}

componentDidMount() {

this.getCategorys()

}

render() {

// 从状态中取数据

const {categorys, subCategorys, parentId, parentName, loading, showStatus} = this.state

// 从组件对象中数据

const category = this.category || {}

// Card 的左侧标题

const title = parentId === '0' ? '一级分类列表' : (

<span>

<LinkButton onClick={this.showCategorys}>一级分类列表LinkButton>   

<Icon type='arrow-right'/>  

<span>{parentName}span>

span>

)

// Card 的右侧 button

const extra = (

<Button type='primary' onClick={this.showAdd}>

<Icon type='plus'/> 添加

Button>

)

return (

<Card title={title} extra={extra}>

<Table

bordered

rowKey='_id'

dataSource={parentId === '0' ? categorys : subCategorys}

columns={this.columns}

loading={loading}

pagination={{pageSize: 5, showQuickJumper: true, showSizeChanger: true}}

/>H5 前端课程系列

———————————————————————————

60

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Modal

title="添加分类"

visible={showStatus === 1}

onOk={this.addCategory}

onCancel={() => this.setState({showStatus: 0})}

>

<AddForm

categorys={categorys}

parentId={parentId}

setForm={form => this.form = form}

/>

Modal>

<Modal

title="修改分类"

visible={showStatus === 2}

onOk={this.updateCategory}

onCancel={() => {

this.setState({showStatus: 0})

this.form.resetFields()

}}

>

<UpdateForm

categoryName={category.name}

setForm={form => this.form = form}

/>

Modal>

Card>

)

}

}

2.16.3. add-form.jsx

import React, {Component} from 'react'

import {Form, Select, Input} from 'antd'

import PropTypes from 'prop-types'H5 前端课程系列

———————————————————————————

61

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

const Item = Form.Item

const Option = Select.Option

/*

添加分类的 Form 组件

*/

class AddForm extends Component {

static propTypes = {

categorys: PropTypes.array.isRequired,

parentId: PropTypes.string.isRequired,

setForm: PropTypes.func.isRequired,

}

componentWillMount() {

this.props.setForm(this.props.form)

}

render() {

const {getFieldDecorator} = this.props.form

const {categorys, parentId} = this.props

return (

<Form>

<Item label='所属分类'>

{

getFieldDecorator('parentId', {

initialValue: parentId

})(

<Select>

<Option key='0' value='0'>一级分类Option>

{

categorys.map(c => <Option key={c._id}

value={c._id}>{c.name}Option>)

}

Select>

)

}

Item>

<Item label='分类名称'>H5 前端课程系列

———————————————————————————

62

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

{

getFieldDecorator('categoryName', {

initialValue: ''

})(

<Input placeholder='请输入分类名称'/>

)

}

Item>

Form>

)

}

}

export default AddForm = Form.create()(AddForm)

2.16.4. update-form.jsx

import React, {Component} from 'react'

import {Form, Input} from 'antd'

import PropTypes from 'prop-types'

const Item = Form.Item

/*

更新分类的 Form 组件

*/

class UpdateForm extends Component {

static propTypes = {

categoryName: PropTypes.string,

setForm: PropTypes.func.isRequired,

}

componentWillMount() {

this.props.setForm(this.props.form)

}

render() {H5 前端课程系列

———————————————————————————

63

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

const {getFieldDecorator} = this.props.form

const {categoryName} = this.props

return (

<Form>

<Item>

{

getFieldDecorator('categoryName', {

initialValue: categoryName

})(

<Input placeholder='请输入分类名称'/>

)

}

Item>

Form>

)

}

}

export default UpdateForm = Form.create()(UpdateForm)

2.17. 商品管理

2.17.1. 下载依赖

yarn add react-draft-wysiwyg draftjs-to-html

2.17.2. api/index.js

// 根据分类 ID 获取分类

export const reqCategory = (categoryId) => ajax('/manage/category/info', {categoryId})

// 获取商品分页列表

export const reqProducts = (pageNum, pageSize) => ajax('/manage/product/list', {pageNum,

pageSize})

// 根据 ID/Name 搜索产品分页列表H5 前端课程系列

———————————————————————————

64

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

export const reqSearchProducts = ({pageNum, pageSize, searchType, searchName}) =>

ajax('/manage/product/search', {

pageNum,

pageSize,

[searchType]: searchName,

})

// 添加/更新商品

export const reqAddOrUpdateProduct = (product) => ajax('/manage/product/' +

(product._id ? 'update' : 'add'), product, 'post')

// 对商品进行上架/下架处理

export const reqUpdateProductStatus = (productId, status) =>

ajax('/manage/product/updateStatus', {

productId,

status

}, 'POST')

// 删除图片

export const reqDeleteImg = (name) => ajax('/manage/img/delete', {name}, 'post')

2.17.3. product/product.jsx

import React, {Component} from 'react'

import {Switch, Route, Redirect} from 'react-router-dom'

import ProductHome from './home'

import ProductAddUpdate from './add-update'

import ProductDetail from './detail'

import './product.less'

/*

管理的商品管理路由组件

*/

export default class Product extends Component {

render() {

return (

<Switch>

<Route path='/product' exact component={ProductHome}/>H5 前端课程系列

———————————————————————————

65

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Route path='/product/addupdate' component={ProductAddUpdate}/>

<Route path='/product/detail' component={ProductDetail}/>

<Redirect to='/product'/>

Switch>

)

}

}

2.17.4. product/home.jsx

import React, {Component} from 'react'

import {

Card,

Select,

Input,

Button,

Icon,

Table,

message

} from 'antd'

import LinkButton from '../../components/link-button'

import {reqProducts, reqSearchProducts, reqUpdateProductStatus} from '../../api'

import {PAGE_SIZE} from '../../utils/constants'

const Option = Select.Option

/*

商品管理的主界面路由

*/

export default class ProductHome extends Component {

state = {

total: 0, // 商品的总数量

products: [], // 当前页列表数据

searchType: 'productName', // 搜索类型 productName / productDesc

searchName: '', // 搜索关键字

}H5 前端课程系列

———————————————————————————

66

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

/*

更新指定产品的状态

*/

updateProductStatus = async (productId, status) => {

const result = await reqUpdateProductStatus(productId, status)

if (result.status === 0) {

message.success('更新状态成功!')

this.getProducts(this.pageNum || 1)

}

}

/*

初始化生成 Tabe 所有列的数组

*/

initColumns = () => {

this.columns = [

{

title: '商品名称',

dataIndex: 'name'

},

{

title: '商品描述',

dataIndex: 'desc'

},

{

title: '价格',

dataIndex: 'price',

render: (price) => <span>¥{price}span>

},

{

title: '状态',

width: 100,

dataIndex: 'status',

render: (status, product) => { // 1: 在售, 2: 已下架

let btnText = '下架'

let statusText = '在售'

if (status === 2) {

btnText = '上架'

statusText = '已下架'

}H5 前端课程系列

———————————————————————————

67

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

status = status === 1 ? 2 : 1

return (

<span>

<Button type='primary' onClick={() =>

this.updateProductStatus(product._id, status)}>{btnText}Button>

<span>{statusText}span>

span>

)

}

},

{

title: '操作',

width: 100,

render: (product) => (

<span>

<LinkButton onClick={() => this.props.history.push('/product/detail',

product)}>详情LinkButton>

   

<LinkButton onClick={() => this.props.history.push('/product/addupdate',

product)}>修改LinkButton>

span>

)

},

]

}

/*

异步获取指定页的数据

*/

getProducts = async (pageNum) => {

this.pageNum = pageNum

const {searchType, searchName} = this.state

let result

if (searchName) { // 搜索分页

result = await reqSearchProducts({pageNum, pageSize: PAGE_SIZE, searchType,

searchName})

} else { // 一般分页

result = await reqProducts(pageNum, PAGE_SIZE)

}

console.log('getProducts()', result)H5 前端课程系列

———————————————————————————

68

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

if (result.status === 0) {

const {total, list} = result.data

this.setState({

total,

products: list

})

}

}

componentWillMount() {

this.initColumns()

}

componentDidMount() {

this.getProducts(1)

}

render() {

const {products, total, searchType} = this.state

const title = (

<span>

<Select value={searchType} onChange={value => this.setState({searchType:

value})}>

<Option key='productName' value='productName'>按名称搜索Option>

<Option key='productDesc' value='productDesc'>按描述搜索Option>

Select>

<Input style={{width: 150, marginLeft: 10, marginRight: 10}} placeholder='

关键字'

onChange={(e) => this.setState({searchName: e.target.value})}/>

<Button type='primary' onClick={() => this.getProducts(1)}>搜索Button>

span>

)

const extra = (

<Button type='primary' style={{float: 'right'}} onClick={() =>

this.props.history.push('/product/addupdate')}>

<Icon type='plus'/>

添加商品

Button>

)H5 前端课程系列

———————————————————————————

69

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

return (

<div>

<Card title={title} extra={extra}>

<Table

bordered

rowKey='_id'

columns={this.columns}

dataSource={products}

pagination={{

defaultPageSize: PAGE_SIZE,

total,

showQuickJumper: true,

onChange: this.getProducts

}}

/>

Card>

div>

)

}

}

2.17.5. product/detail.jsx

import React, {Component} from 'react'

import {List, Icon, Card} from 'antd'

import {reqCategory} from '../../api'

import {BASE_IMG_PATH} from '../../utils/constants'

import LinkButton from "../../components/link-button";

/*

商品详情组件

*/

export default class ProductDetail extends Component {

state = {

cName1: '', // 一级分类名称H5 前端课程系列

———————————————————————————

70

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

cName2: '' // 二级分类名称

}

/*

异步获取当前产品对应的分类名称

*/

getCategoryName = async () => {

const {categoryId, pCategoryId} = this.props.location.state

if (pCategoryId === '0') {

// 获取一级分类名称

const result = await reqCategory(categoryId)

const cName1 = result.data.name

this.setState({cName1})

} else {

// 获取一级分类名称

/*const result1 = await reqCategory(pCategoryId)

const cName1 = result1.data.name

// 获取二级分类名称

const result2 = await reqCategory(categoryId)

const cName2 = result2.data.name

this.setState({cName1, cName2})*/

/*

一次发多个请求, 等所有请求都返回后一起处理, 如果有一个请求出错了, 整个都会失败

Promise.all([promise1, promise2]) 返回值一个 promise 对象, 异步成功返回的是

[result1, result2]

*/

const results = await Promise.all([reqCategory(pCategoryId),

reqCategory(categoryId)])

const result1 = results[0]

const result2 = results[1]

const cName1 = result1.data.name

const cName2 = result2.data.name

this.setState({cName1, cName2})

}

}

componentDidMount() {

this.getCategoryName()

}

render() {H5 前端课程系列

———————————————————————————

71

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

const {name, desc, price, imgs, detail} = this.props.location.state

const {cName1, cName2} = this.state

const title = (

<span>

<LinkButton onClick={() => this.props.history.goBack()}>

<Icon type="arrow-left" style={{fontSize: 20}}/>

LinkButton>

  商品详情

span>

)

const imgStyle = {width: 150, height: 150, marginRight: 10, border: '1px solid black'}

return (

<Card className='product-detail' title={title}>

<List>

<List.Item>

<span className='left'>商品名称:span>

<span>{name}span>

List.Item>

<List.Item>

<span className='left'>商品描述:span>

<span>{desc}span>

List.Item>

<List.Item>

<span className='left'>商品价格:span>

<span>{price + ''}span>

List.Item>

<List.Item>

<span className='left'>所属分类:span>

<span>{cName1 + (cName2 ? ' --> ' + cName2 : '')}span>

List.Item>

<List.Item>

<span className='left'>商品图片:span>

<span>

{

imgs.map(img => (

<img src={BASE_IMG_PATH + img} alt="img" key={img} style={imgStyle}/>

))

}

span>

List.Item>H5 前端课程系列

———————————————————————————

72

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<List.Item>

<span className='left'>商品详情:span>

<div dangerouslySetInnerHTML={{__html: detail}}>div>

List.Item>

List>

Card>

)

}

}H5 前端课程系列

———————————————————————————

73

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.17.6. product/add-update.jsxH5 前端课程系列

———————————————————————————

74

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

import React, {Component} from 'react'

import {

Card,

Icon,

Form,

Input,

Cascader,

Button,

message

} from 'antd'

import LinkButton from '../../components/link-button'

import PicturesWall from './pictures-wall'

import RichTextEditor from './rich-text-editor'

import {reqCategorys, reqAddOrUpdateProduct} from '../../api'

const {Item} = Form

const {TextArea} = Input

/*

商品添加/更新的路由组件

*/

class ProductAddUpdate extends Component {

state = {

options: [], // 用来显示级联列表的数组

}

constructor(props) {

super(props);

this.pw = React.createRef();

this.editor = React.createRef();

}

/*

选择某个分类项时的回调

加载对应的二级分类显示

*/

loadData = async (selectedOptions) => {

// console.log('loadDate()', selectedOptions)

const targetOption = selectedOptions[selectedOptions.length - 1]

targetOption.loading = true // 显示 loadingH5 前端课程系列

———————————————————————————

75

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

// 异步请求获取对应的二级分类列表

const subCategorys = await this.getCategorys(targetOption.value) // await 的作用:

证完成执行完保存的分类数组才进入后面的语句

targetOption.loading = false // 隐藏 loading

if(subCategorys && subCategorys.length>0) { // 有子分类

// 生成一个二级的 options

const cOptions = subCategorys.map(c => ({

value: c._id,

label: c.name,

isLeaf: true,

}))

// 添加为对应的 option children(options)

targetOption.children = cOptions

} else { // 没有子分类

targetOption.isLeaf = true

}

// 更新 options 状态

this.setState({

options: [...this.state.options],

});

}

/*

获取指定分类 id 的子分类列表

如果 parentId 0 时获取一级列表

*/

getCategorys = async (parentId) => {

const result = await reqCategorys(parentId)

if(result.status===0) {

const categorys = result.data

if(parentId==='0') {

// 根据一级分类数组初始化生成 options 数组

this.initOptions(categorys)

} else { // 当前得到是二级分类列表

// 返回二级分类列表(作为 async 函数的 promise 对象的成功的 value )

return categorys

}

}H5 前端课程系列

———————————————————————————

76

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

/*

生成级联的一级列表

*/

initOptions = async (categorys) => {

// 根据一级分类数组生成 option 的数组

const options = categorys.map(c => ({

value: c._id,

label: c.name,

isLeaf: false,

}))

// 如果当前是更新, 且商品是一个二级分类的商品

const {product, isUpdate} = this

if(isUpdate && product.pCategoryId!=='0') {

// 异步获取 product.pCategoryId 的二级分类列表

const subCategorys = await this.getCategorys(product.pCategoryId)

if (subCategorys && subCategorys.length>0) {

// 生成二级的 option 数组

const cOptions = subCategorys.map(c => ({

value: c._id,

label: c.name,

isLeaf: true,

}))

// 找到对应的 option

const targetOption = options.find(option => option.value===product.pCategoryId)

// cOptions 添加为对应的一级 option children

targetOption.children = cOptions

}

}

// 更新状态

this.setState({

options

})

}

/*H5 前端课程系列

———————————————————————————

77

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

对商品价格进行自定义验证

*/

validatePrice = (rule, value, callback) => {

value = value * 1

if(value>0) {

callback()

} else {

callback('价格必须是大于 0 的数值')

}

}

// 添加/更新

submit = () => {

this.props.form.validateFields(async (err, values) => {

if (!err) {

// 收集产品相关信息

const {name, desc, price, categoryIds} = values

// 在父组件中得到子组件对象, 调用子组件对象的方法

const imgs = this.pw.current.getImgs()

const detail = this.editor.current.getDetail()

let pCategoryId = ''

let categoryId = ''

if(categoryIds.length===1) { // 选择的是一级分类

pCategoryId = '0'

categoryId = categoryIds[0]

} else { // 选择的是二级分类

pCategoryId = categoryIds[0]

categoryId = categoryIds[1]

}

// 封装成对象

const product = {name, desc, price, pCategoryId, categoryId, detail, imgs}

// 如果是更新, 指定 product _id 属性值

if(this.isUpdate) {

product._id = this.product._id

}

// 请求保存

const result = await reqAddOrUpdateProduct(product)

if(result.status===0) {

message.success('保存商品成功')

this.props.history.goBack()

} else {H5 前端课程系列

———————————————————————————

78

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

message.success('保存商品失败')

}

}

})

}

componentDidMount () {

// 异步获取一级分类列表

this.getCategorys('0')

}

componentWillMount () {

// 取出跳转传入的数据

const product = this.props.location.state

this.product = product || {}

this.isUpdate = !!product // !!xxx 将一个数据强制转换成布尔类型

}

render() {

const {product, isUpdate} = this

const {pCategoryId, categoryId} = product

const {options} = this.state

const {getFieldDecorator} = this.props.form

// 准备用于级联列表显示的数组

const categoryIds = []

if(isUpdate) {

if(pCategoryId==='0') {

categoryIds.push(categoryId)

} else {

categoryIds.push(pCategoryId)

categoryIds.push(categoryId)

}

}

const title = (

<span>

<LinkButton onClick={() => this.props.history.goBack()}>

<Icon type='arrow-left' style={{fontSize: 20}}/>

LinkButton>H5 前端课程系列

———————————————————————————

79

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

{isUpdate ? '修改商品' : '添加商品'}

span>

)

// 指定 form item 布局的对象

const formItemLayout = {

labelCol: { span: 2 },

wrapperCol: { span: 8 }

}

return (

<Card title={title}>

<Form>

<Item label="商品名称" {...formItemLayout}>

{

getFieldDecorator('name', {

initialValue: product.name,

rules: [

{required: true, message: '商品名称必须输入'}

]

})(

<Input placeholer='请输入商品名称'/>

)

}

Item>

<Item label="商品描述" {...formItemLayout}>

{

getFieldDecorator('desc', {

initialValue: product.desc,

rules: [

{required: true, message: '商品描述必须输入'}

]

})(

<TextArea placeholder="请输入商品描述" autosize />

)

}

Item>

<Item label="商品价格" {...formItemLayout}>

{

getFieldDecorator('price', {

initialValue: product.price,

rules: [H5 前端课程系列

———————————————————————————

80

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

{required: true, message: '商品价格必须输入'},

{validator: this.validatePrice}

]

})(

<Input type='number' placeholer='请输入商品价格' addonAfter=''/>

)

}

Item>

<Item label="商品分类" {...formItemLayout}>

{

getFieldDecorator('categoryIds', {

initialValue: categoryIds,

rules: [

{required: true, message: '商品分类必须输入'}

]

})(

<Cascader

options={options}

loadData={this.loadData}

/>

)

}

Item>

<Item label="商品图片" {...formItemLayout}>

<PicturesWall ref={this.pw} imgs={product.imgs}/>

Item>

<Item

label="商品详情"

labelCol={{ span: 2 }}

wrapperCol={{ span: 20 }}>

<RichTextEditor ref={this.editor} detail={product.detail}/>

Item>

<Button type='primary' onClick={this.submit}>提交Button>

Form>

Card>

)

}

}

export default Form.create()(ProductAddUpdate)H5 前端课程系列

———————————————————————————

81

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.17.7. product/pictures-wall.jsx

import React from 'react'

import PropTypes from 'prop-types'

import {Upload, Icon, Modal, message} from 'antd'

import {BASE_IMG_PATH, UPLOAD_IMG_NAME} from '../../utils/constants'

import {reqDeleteImg} from '../../api'

/*

管理商品图片的组件(上传/删除图片)

*/

export default class PicturesWall extends React.Component {

static propTypes = {

imgs: PropTypes.array

}

constructor (props) {

super(props)

let fileList = []

// 如果传入了 imgs, 生成一个对应的 fileList

const imgs = this.props.imgs

if (imgs && imgs.length > 0) {

fileList = imgs.map((img, index) => ({

uid: -index,

name: img,

status: 'done', // loading: 上传中, done: 上传完成, remove: 删除

url: BASE_IMG_PATH + img,

}))

}

//初始化状态

this.state = {

previewVisible: false, // 是否显示大图预览

previewImage: '', // 大图的 url

fileList: fileList // 所有需要显示的图片信息对象的数组

}

}H5 前端课程系列

———————————————————————————

82

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

/*

得到当前已上传的图片文件名的数组

*/

getImgs = () => this.state.fileList.map(file => file.name)

/*

关闭大图预览

*/

handleCancel = () => this.setState({previewVisible: false})

/*

预览大图

*/

handlePreview = (file) => {

this.setState({

previewImage: file.url || file.thumbUrl, // 需要显示的大图的 url

previewVisible: true,

})

}

/*

file: 当前操作文件信息对象

fileList: 所有文件信息对象的数组

*/

handleChange = async ({file, fileList}) => {

console.log('handleChange()', file, fileList)

// 如果上传图片完成

if (file.status === 'done') {

const result = file.response

if (result.status === 0) {

message.success('上传成功了')

const {name, url} = result.data

file = fileList[fileList.length - 1]

file.name = name

file.url = url

} else {

message.error('上传失败了')

}

} else if (file.status === 'removed') { // 删除图片

const result = await reqDeleteImg(file.name)

if(result.status===0) {

message.success('删除图片成功')H5 前端课程系列

———————————————————————————

83

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

} else {

message.error('删除图片失败')

}

}

// 更新 fileList 状态

this.setState({fileList})

}

render() {

const {previewVisible, previewImage, fileList} = this.state

const uploadButton = (

<div>

<Icon type="plus"/>

<div>上传图片div>

div>

)

return (

<div>

<Upload

action="/manage/img/upload"

accept="image/*"

name= {UPLOAD_IMG_NAME}

listType="picture-card"

fileList={fileList}

onPreview={this.handlePreview}

onChange={this.handleChange}

>

{uploadButton}

Upload>

<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>

<img alt="example" style={{width: '100%'}} src={previewImage}/>

Modal>

div>

)

}

}H5 前端课程系列

———————————————————————————

84

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.17.8. product/rich-text-editor.jsx

import React, {Component} from 'react'

import PropTypes from 'prop-types'

import {Editor} from 'react-draft-wysiwyg'

import {EditorState, convertToRaw, ContentState} from 'draft-js'

import draftToHtml from 'draftjs-to-html'

import htmlToDraft from 'html-to-draftjs'

import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'

/*

用来指定商品详情信息的富文本编程器组件

*/

export default class RichTextEditor extends Component {

static propTypes = {

detail: PropTypes.string

}

constructor (props) {

super(props)

// 根据传入的 html 文本初始显示

const detail = this.props.detail

let editorState

if(detail) { // 如果传入才需要做处理

const blocksFromHtml = htmlToDraft(detail)

const { contentBlocks, entityMap } = blocksFromHtml

const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap)

editorState = EditorState.createWithContent(contentState)

} else {

editorState = EditorState.createEmpty()

}

// 初始化状态

this.state = {

editorState

}

}

/*

当输入改变时立即保存状态数据

*/H5 前端课程系列

———————————————————————————

85

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

onEditorStateChange = (editorState) => {

this.setState({

editorState,

})

}

/*

得到输入的富文本数据

*/

getDetail = () => {

return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))

}

render() {

const {editorState} = this.state

return (

<Editor

editorState={editorState}

editorStyle={{height: 250, border: '1px solid #000', padding: '0 30px'}}

onEditorStateChange={this.onEditorStateChange}

/>

)

}

}

2.17.9. product/product.less

.product-detail {

.left {

margin-right: 10px;

font-size: 18px;

font-weight: bold;

}

}H5 前端课程系列

———————————————————————————

86

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.17.10. utils/constants.js

/*

包含 n 个应用中的常量字符串模块

*/

export const PAGE_SIZE = 5 // 每页显示条目数

export const BASE_IMG_PATH = 'http://localhost:5000/upload/' // 上传的图片的基础地址

export const UPLOAD_IMG_NAME = 'image' // 上传图片的参数名

2.18. 角色管理

2.18.1. api/index.js:

// 添加角色

export const reqAddRole = (roleName) => ajax('/manage/role/add', {roleName}, 'POST')

// 获取角色列表

export const reqRoles = () => ajax('/manage/role/list')

// 更新角色(给角色设置权限)

export const reqUpdateRole = (role) => ajax('/manage/role/update', role, 'POST')

2.18.2. add-form.jsx

import React, {PureComponent} from 'react'

import PropTypes from 'prop-types'

import {

Form,

Input,

} from 'antd'

/*

用来添加角色的 form 组件

*/H5 前端课程系列

———————————————————————————

87

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

class AddForm extends PureComponent {

static propTypes = {

setForm: PropTypes.func.isRequired

}

componentWillMount() {

this.props.setForm(this.props.form)

}

render() {

const {getFieldDecorator} = this.props.form

const formItemLayout = {

labelCol: {span: 5},

wrapperCol: {span: 16}

}

return (

<Form>

<Form.Item label="角色名称" {...formItemLayout}>

{

getFieldDecorator('roleName', {

initialValue: ''

})(

<Input type="text" placeholder="请输入角色名称"/>

)

}

Form.Item>

Form>

)

}

}

export default AddForm = Form.create()(AddForm)

2.18.3. auth-form.jsx

import React, {PureComponent} from 'react'

import PropTypes from 'prop-types'

import {H5 前端课程系列

———————————————————————————

88

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

Form,

Input,

Tree

} from 'antd'

import menuList from '../../config/menuConfig'

const Item = Form.Item

const { TreeNode } = Tree;

/*

添加分类的 form 组件

*/

export default class AuthForm extends PureComponent {

static propTypes = {

role: PropTypes.object

}

constructor (props) {

super(props)

// 根据传入角色的 menus 生成初始状态

const {menus} = this.props.role

this.state = {

checkedKeys: menus

}

}

/*

为父组件提交获取最新 menus 数据的方法

*/

getMenus = () => this.state.checkedKeys

getTreeNodes = (menuList) => {

return menuList.reduce((pre, item) => {

pre.push(

<TreeNode title={item.title} key={item.key}>

{item.children ? this.getTreeNodes(item.children) : null}

TreeNode>

)H5 前端课程系列

———————————————————————————

89

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

return pre

}, [])

}

// 选中某个 node 时的回调

onCheck = checkedKeys => {

console.log('onCheck', checkedKeys);

this.setState({ checkedKeys });

};

componentWillMount () {

this.treeNodes = this.getTreeNodes(menuList)

}

// 根据新传入的 role 来更新 checkedKeys 状态

/*

当组件接收到新的属性时自动调用

*/

componentWillReceiveProps (nextProps) {

console.log('componentWillReceiveProps()', nextProps)

const menus = nextProps.role.menus

this.setState({

checkedKeys: menus

})

// this.state.checkedKeys = menus

}

render() {

console.log('AuthForm render()')

const {role} = this.props

const {checkedKeys} = this.state

// 指定 Item 布局的配置对象

const formItemLayout = {

labelCol: { span: 4 }, // 左侧 label 的宽度

wrapperCol: { span: 15 }, // 右侧包裹的宽度

}

return (

<div>

<Item label='角色名称' {...formItemLayout}>

<Input value={role.name} disabled/>H5 前端课程系列

———————————————————————————

90

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

Item>

<Tree

checkable

defaultExpandAll={true}

checkedKeys={checkedKeys}

onCheck={this.onCheck}

>

<TreeNode title="平台权限" key="all">

{this.treeNodes}

TreeNode>

Tree>

div>

)

}

}

2.18.4. role.jsx

import React, {PureComponent} from 'react'

import {

Card,

Button,

Table,

Modal,

message

} from 'antd'

import {PAGE_SIZE} from "../../utils/constants"

import {reqRoles, reqAddRole, reqUpdateRole} from '../../api'

import AddForm from './add-form'

import AuthForm from './auth-form'

import memoryUtils from "../../utils/memoryUtils"

import {formateDate} from '../../utils/dateUtils'

/*

角色路由

*/H5 前端课程系列

———————————————————————————

91

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

export default class Role extends PureComponent {

state = {

roles: [], // 所有角色的列表

role: {}, // 选中的 role

isShowAdd: false, // 是否显示添加界面

isShowAuth: false, // 是否显示设置权限界面

}

constructor (props) {

super(props)

this.auth = React.createRef()

}

initColumn = () => {

this.columns = [

{

title: '角色名称',

dataIndex: 'name'

},

{

title: '创建时间',

dataIndex: 'create_time',

render: (create_time) => formateDate(create_time)

},

{

title: '授权时间',

dataIndex: 'auth_time',

render: formateDate

},

{

title: '授权人',

dataIndex: 'auth_name'

},

]

}

getRoles = async () => {

const result = await reqRoles()

if (result.status===0) {

const roles = result.dataH5 前端课程系列

———————————————————————————

92

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

this.setState({

roles

})

}

}

onRow = (role) => {

return {

onClick: event => { // 点击行

console.log('row onClick()', role)

// alert('点击行')

this.setState({

role

})

},

}

}

/*

添加角色

*/

addRole = () => {

// 进行表单验证, 只能通过了才向下处理

this.form.validateFields(async (error, values) => {

if (!error) {

// 隐藏确认框

this.setState({

isShowAdd: false

})

// 收集输入数据

const {roleName} = values

this.form.resetFields()

// 请求添加

const result = await reqAddRole(roleName)

// 根据结果提示/更新列表显示

if (result.status===0) {

message.success('添加角色成功')

// this.getRoles()H5 前端课程系列

———————————————————————————

93

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

// 新产生的角色

const role = result.data

// 更新 roles 状态

/*const roles = this.state.roles

roles.push(role)

this.setState({

roles

})*/

// 更新 roles 状态: 基于原本状态数据更新

this.setState(state => ({

roles: [...state.roles, role]

}))

} else {

message.success('添加角色失败')

}

}

})

}

/*

更新角色

*/

updateRole = async () => {

// 隐藏确认框

this.setState({

isShowAuth: false

})

const role = this.state.role

// 得到最新的 menus

const menus = this.auth.current.getMenus()

role.menus = menus

role.auth_time = Date.now()

role.auth_name = memoryUtils.user.username

// 请求更新H5 前端课程系列

———————————————————————————

94

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

const result = await reqUpdateRole(role)

if (result.status===0) {

message.success('设置角色权限成功')

// this.getRoles()

this.setState({

roles: [...this.state.roles]

})

}

}

componentWillMount () {

this.initColumn()

}

componentDidMount () {

this.getRoles()

}

render() {

console.log('Role render()')

const {roles, role, isShowAdd, isShowAuth} = this.state

const title = (

<span>

<Button type='primary' onClick={() => this.setState({isShowAdd: true})}>创建角

Button>   

<Button type='primary' disabled={!role._id} onClick={() =>

this.setState({isShowAuth: true})}>设置角色权限Button>

span>

)

return (

<Card title={title}>

<Table

bordered

rowKey='_id'

dataSource={roles}

columns={this.columns}

pagination={{defaultPageSize: PAGE_SIZE}}

rowSelection={{type: 'radio', selectedRowKeys: [role._id]}}

onRow={this.onRow}

/>H5 前端课程系列

———————————————————————————

95

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Modal

title="添加角色"

visible={isShowAdd}

onOk={this.addRole}

onCancel={() => {

this.setState({isShowAdd: false})

this.form.resetFields()

}}

>

<AddForm

setForm={(form) => this.form = form}

/>

Modal>

<Modal

title="设置角色权限"

visible={isShowAuth}

onOk={this.updateRole}

onCancel={() => {

this.setState({isShowAuth: false})

}}

>

<AuthForm ref={this.auth} role={role}/>

Modal>

Card>

)

}

}

2.19. 用户管理

2.19.1. api/index.js

// 添加/更新用户

export const reqAddOrUpdateUser = (user) => ajax('/manage/user/'+(user._id ? 'update' :

'add'), user, 'POST')H5 前端课程系列

———————————————————————————

96

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

// 获取用户列表

export const reqUsers = () => ajax('/manage/user/list')

// 删除用户

export const reqDeleteUser = (userId) => ajax('/manage/user/delete', {userId}, 'POST')

2.19.2. user-form.jsx

import React, {Component} from 'react'

import PropTypes from 'prop-types'

import {

Form,

Input,

Select,

} from 'antd'

const FormItem = Form.Item

const Option = Select.Option

/*

用来添加或更新的 form 组件

*/

class UserForm extends Component {

static propTypes = {

setForm: PropTypes.func.isRequired,

user: PropTypes.object,

roles: PropTypes.array

}

componentWillMount() {

this.props.setForm(this.props.form)

}

render() {

const {getFieldDecorator} = this.props.form

const formItemLayout = {

labelCol: {span: 4},

wrapperCol: {span: 16}H5 前端课程系列

———————————————————————————

97

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

const {user, roles} = this.props

return (

<Form {...formItemLayout}>

<FormItem label="用户名">

{

getFieldDecorator('username', {

initialValue: user.username

})(

<Input type="text" placeholder="请输入用户名"/>

)

}

FormItem>

{

!user._id ?

(

<FormItem label="密码">

{

getFieldDecorator('password', {

initialValue: ''

})(

<Input type="password" placeholder="请输入密码"/>

)

}

FormItem>

) : null

}

<FormItem label="手机号">

{

getFieldDecorator('phone', {

initialValue: user.phone

})(

<Input type="phone" placeholder="请输入手机号"/>

)

}

FormItem>H5 前端课程系列

———————————————————————————

98

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<FormItem label="邮箱">

{

getFieldDecorator('email', {

initialValue: user.email

})(

<Input type="email" placeholder="请输入邮箱"/>

)

}

FormItem>

<FormItem label="角色">

{

getFieldDecorator('role_id', {

initialValue: user.role_id

})(

<Select style={{width: 200}} placeholder='请选择角色'>

{

roles.map(role => <Option key={role._id}

value={role._id}>{role.name}Option>)

}

Select>

)

}

FormItem>

Form>

)

}

}

export default Form.create()(UserForm)

2.19.3. user.jsx

import React, {Component} from 'react'

import {

Card,

Button,

Table,

Modal,H5 前端课程系列

———————————————————————————

99

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

} from 'antd'

import LinkButton from '../../components/link-button'

import UserForm from './user-form'

import {

reqUsers,

reqAddOrUpdateUser,

reqDeleteUser

} from '../../api'

import {formateDate} from '../../utils/dateUtils'

import {PAGE_SIZE} from "../../utils/constants";

/*

后台管理的用户管理路由组件

*/

export default class User extends Component {

state = {

isShow: false, // 是否显示对话框

users: [], // 所有用户的列表

roles: [], // 所有角色的列表

}

/*

初始化 Table 的字段列表

*/

initColumns = () => {

this.columns = [

{

title: '用户名',

dataIndex: 'username'

},

{

title: '邮箱',

dataIndex: 'email'

},

{

title: '电话',

dataIndex: 'phone'

},

{

title: '注册时间',H5 前端课程系列

———————————————————————————

100

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

dataIndex: 'create_time',

render: formateDate

},

{

title: '所属角色',

dataIndex: 'role_id',

render: value => this.roleNames[value]

},

{

title: '操作',

render: (user) => (

<span>

<LinkButton onClick={() => this.showUpdate(user)}>修改LinkButton>

  

<LinkButton onClick={() => this.clickDelete(user)}>删除LinkButton>

span>

)

},

]

}

/*

根据角色的数组生成一个包含所有角色名的对象容器

*/

initRoleNames = (roles) => {

this.roleNames = roles.reduce((pre, role) => {

pre[role._id] = role.name

return pre

}, {})

}

/*

响应点击删除用户

*/

clickDelete = (user) => {

Modal.confirm({

content: `确定删除${user.username}?`,

onOk: async () => {

const result = await reqDeleteUser(user._id)

if (result.status === 0) {

this.getUsers()

}H5 前端课程系列

———————————————————————————

101

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

})

}

/*

显示修改用户的界面

*/

showUpdate = (user) => {

// 保存 user

this.user = user

this.setState({

isShow: true

})

}

/*

异步获取所有用户列表

*/

getUsers = async () => {

const result = await reqUsers()

if (result.status === 0) {

const {users, roles} = result.data

// 初始化生成一个包含所有角色名的对象容器 {_id1: name1, _id2: nam2}

this.initRoleNames(roles)

this.setState({

users,

roles

})

}

}

/*

显示添加用户的界面

*/

showAddUser = () => {

this.user = null

this.setState({

isShow: true

})

}H5 前端课程系列

———————————————————————————

102

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

/*

添加/更新用户

*/

AddOrUpdateUser = async () => {

// 获取表单数据

const user = this.form.getFieldsValue()

this.form.resetFields()

if (this.user) {

user._id = this.user._id

}

this.setState({

isShow: false

})

const result = await reqAddOrUpdateUser(user)

if (result.status === 0) {

this.getUsers()

}

}

componentWillMount() {

this.initColumns()

}

componentDidMount() {

this.getUsers()

}

render() {

const {users, roles, isShow} = this.state

const user = this.user || {}

const title = <Button type="primary" onClick={this.showAddUser}>创建用户Button>

return (

<div>

<Card title={title}>

<Table

columns={this.columns}

rowKey='_id'H5 前端课程系列

———————————————————————————

103

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

dataSource={users}

bordered

pagination={{defaultPageSize: PAGE_SIZE, showQuickJumper: true}}

/>

<Modal

title={user._id ? '修改用户' : '添加用户'}

visible={isShow}

onCancel={() => this.setState({isShow: false})}

onOk={this.AddOrUpdateUser}

>

<UserForm

setForm={(form) => this.form = form}

user={user}

roles={roles}

/>

Modal>

Card>

div>

)

}

}

2.19.4. 导航权限控制

1) left-nav.jsx

/*

判断当前用户是否有看到当前 item 对应菜单项的权限

*/

hasAuth = (item) => {

const key = item.key

const menuSet = this.menuSet

/*

1. 如果菜单项标识为公开

2. 如果当前用户是 admin

3. 如果菜单项的 key 在用户的 menus H5 前端课程系列

———————————————————————————

104

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

*/

if(item.isPublic || memoryUtils.user.username==='admin' || menuSet.has(key)) {

return true

// 4. 如果有子节点, 需要判断有没有一个 child key menus

} else if(item.children){

return !!item.children.find(child => menuSet.has(child.key))

}

}

/*

返回包含 n 的数组

*/

getMenuNodes = (list) => {

// 得到当前请求的 path

const path = this.props.location.pathname

return list.reduce((pre, item) => {

if (this.hasAuth(item)) {

if (!item.children) {

pre.push((

<Menu.Item key={item.key}>

<Link to={item.key}>

<Icon type={item.icon}/>

<span>{item.title}span>

Link>

Menu.Item>

))

} else {

pre.push((

<SubMenu key={item.key} title={<span><Icon

type={item.icon}/><span>{item.title}span>span>}>

{

this.getMenuNodes(item.children)

}

SubMenu>

))

if(item.children.find(cItem => path.indexOf(cItem.key)===0)) {

this.openKey = item.key

}

}H5 前端课程系列

———————————————————————————

105

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

return pre

}, [])

}

componentWillMount () {

this.menuSet = new Set(memoryUtils.user.role.menus || [])

this.menuNodes = this.getMenuNodes(menuList)

}

2) config/menuConfig.js

{

title: '首页', // 菜单标题名称

key: '/home', // 对应的 path

icon: 'home', // 图标名称

isPublic: true, // 开放的

}

2.20. 使用 redux 管理状态

2.20.1. 下载依赖

yarn add redux react-redux redux-thunk redux-devtools-extension

2.20.2. redux/store.js

/*

redux 最核心的管理对象: store

*/

import {createStore, applyMiddleware} from 'redux'

import thunk from 'redux-thunk'

import {composeWithDevTools} from 'redux-devtools-extension'

import reducer from './reducer'

// 向外暴露 store 对象H5 前端课程系列

———————————————————————————

106

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk))) //

store 对象内部会第一次调用 reducer()得到初始状态值

2.20.3. redux/reducer.js

/*

reducer 函数模块: 根据当前 state 和指定 action 返回一个新的 state

*/

import {combineReducers} from 'redux'

import {

SET_HEAD_TITLE,

RECEIVE_USER,

SHOW_ERROR_MSG,

RESET_USER

} from './action-types'

import storageUtils from "../utils/storageUtils";

/*

管理 headTitle 状态数据的 reducer

*/

const initHeadTitle = '首页'

function headTitle(state = initHeadTitle, action) {

console.log('headTitle()', state, action)

switch (action.type) {

case SET_HEAD_TITLE:

return action.data

default:

return state

}

}

/*

管理 user 状态数据的 reducer

*/

const initUser = storageUtils.getUser()H5 前端课程系列

———————————————————————————

107

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

function user(state = initUser, action) {

console.log('user()', state, action)

switch (action.type) {

case RECEIVE_USER:

return action.user

case SHOW_ERROR_MSG:

return {...state, errorMsg: action.errorMsg}

case RESET_USER:

return {}

default:

return state

}

}

/*

向外暴露合并后产生的总 reducer 函数

总的 state 的结构:

{

headerTitle: '',

user: {}

}

*/

export default combineReducers({

headTitle,

user,

})

2.20.4. redux/actions.js

/*

包含 n 个用来创建 action 的工厂函数(action creator)

*/

import {

SET_HEAD_TITLE,

RECEIVE_USER,

SHOW_ERROR_MSG,H5 前端课程系列

———————————————————————————

108

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

RESET_USER

} from './action-types'

import {reqLogin} from '../api'

import storageUtils from "../utils/storageUtils";

/*

设置头部标题的同步 action

*/

export const setHeadTitle = (headTitle) => ({type: SET_HEAD_TITLE, data: headTitle})

/*

接收用户的同步 action

*/

export const receiveUser = (user) => ({type: RECEIVE_USER, user})

/*

显示错误信息的同步 action

*/

export const showErrorMsg = (errorMsg) => ({type: SHOW_ERROR_MSG, errorMsg})

/*

退出登陆的同步 action

*/

export const logout = () => {

storageUtils.removeUser()

return {type: RESET_USER}

}

/*

登陆的异步 action

*/

export const login = (username, password) => {

return async dispatch => {

const result = await reqLogin(username, password)

if (result.status === 0) {

const user = result.data

storageUtils.saveUser(user)

dispatch(receiveUser(user))

} else {

const msg = result.msg

dispatch(showErrorMsg(msg))H5 前端课程系列

———————————————————————————

109

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}

}

}

2.20.5. redux/action-types.js

/*

包含 n action type 常量名称的模块

*/

export const SET_HEAD_TITLE = 'set_head_title' // 设置头部标题

export const RECEIVE_USER = 'receive_user' // 接收新的用户

export const SHOW_ERROR_MSG = 'show_error_msg' // 显示错误提示信息

export const RESET_USER = 'reset_user' // 重置用户

2.20.6. index.js

/*

入口 js

*/

import React from 'react'

import ReactDOM from 'react-dom'

import {Provider} from 'react-redux'

import store from './redux/store'

import App from './App'

ReactDOM.render((

<Provider store={store}>

<App />H5 前端课程系列

———————————————————————————

110

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

Provider>

), document.getElementById('root'))

2.20.7. components/left-nav/left-nav.js

import React, {Component} from 'react'

import PropTypes from 'prop-types'

import {Link, withRouter} from 'react-router-dom'

import {Menu, Icon} from 'antd'

import {connect} from 'react-redux'

import {setHeadTitle} from '../../redux/actions'

import logo from '../../assets/images/logo.png'

import menuList from '../../config/menuConfig'

import './index.less'

const SubMenu = Menu.SubMenu;

/*

左侧导航的组件

*/

class LeftNav extends Component {

/*

判断当前登陆用户对 item 是否有权限

*/

hasAuth = (item) => {

const {key, isPublic} = item

const menus = this.props.user.role.menus

const username = this.props.user.username

/*

1. 如果当前用户是 admin

2. 如果当前 item 是公开的

3. 当前用户有此 item 的权限: key 有没有 menus

*/

if (username === 'admin' || isPublic || menus.indexOf(key) !== -1) {

return trueH5 前端课程系列

———————————————————————————

111

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

} else if (item.children) { // 4. 如果当前用户有此 item 的某个子 item 的权限

return !!item.children.find(child => menus.indexOf(child.key) !== -1)

}

return false

}

/*

根据 menu 的数据数组生成对应的标签数组

使用 map() + 递归调用

*/

getMenuNodes_map = (menuList) => {

return menuList.map(item => {

if (!item.children) {

return (

<Menu.Item key={item.key}>

<Link to={item.key}>

<Icon type={item.icon}/>

<span>{item.title}span>

Link>

Menu.Item>

)

} else {

return (

<SubMenu

key={item.key}

title={

<span>

<Icon type={item.icon}/>

<span>{item.title}span>

span>

}

>

{this.getMenuNodes(item.children)}

SubMenu>

)

}

})

}

/*H5 前端课程系列

———————————————————————————

112

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

根据 menu 的数据数组生成对应的标签数组

使用 reduce() + 递归调用

*/

getMenuNodes = (menuList) => {

// 得到当前请求的路由路径

const path = this.props.location.pathname

// 如果当前请求的是根路径, 设置头部标题为首页

if(path==='/') {

this.props.setHeadTitle('首页')

}

return menuList.reduce((pre, item) => {

// 如果当前用户有 item 对应的权限, 才需要显示对应的菜单项

if (this.hasAuth(item)) {

// pre 添加

if (!item.children) {

// 一旦请求路径匹配上当前 item, item title 保存到 redux

if(item.key===path || path.indexOf(item.key)===0) {

this.props.setHeadTitle(item.title)

}

pre.push((

<Menu.Item key={item.key}>

<Link to={item.key} onClick={() => this.props.setHeadTitle(item.title)}>

<Icon type={item.icon}/>

<span>{item.title}span>

Link>

Menu.Item>

))

} else {

// 查找一个与当前请求路径匹配的子 Item

const cItem = item.children.find(cItem => path.indexOf(cItem.key) === 0)

// 如果存在, 说明当前 item 的子列表需要打开

if (cItem) {

this.openKey = item.key

}

// pre 添加

pre.push((H5 前端课程系列

———————————————————————————

113

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<SubMenu

key={item.key}

title={

<span>

<Icon type={item.icon}/>

<span>{item.title}span>

span>

}

>

{this.getMenuNodes(item.children)}

SubMenu>

))

}

}

return pre

}, [])

}

/*

在第一次 render()之前执行一次

为第一个 render()准备数据(必须同步的)

*/

componentWillMount() {

this.menuNodes = this.getMenuNodes(menuList)

}

render() {

// 得到当前请求的路由路径

let path = this.props.location.pathname

console.log('render()', path)

if (path.indexOf('/product') === 0) { // 当前请求的是商品或其子路由界面

path = '/product'

}

// 得到需要打开菜单项的 key

const openKey = this.openKey

return (

<div className="left-nav">H5 前端课程系列

———————————————————————————

114

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Link to='/' className="left-nav-header">

<img src={logo} alt="logo"/>

<h1>硅谷后台h1>

Link>

<Menu

mode="inline"

theme="dark"

selectedKeys={[path]}

defaultOpenKeys={[openKey]}

>

{

this.menuNodes

}

Menu>

div>

)

}

}

/*

withRouter 高阶组件:

包装非路由组件, 返回一个新的组件

新的组件向非路由组件传递 3 个属性: history/location/match

*/

export default connect(

state => ({user: state.user}),

{setHeadTitle}

)(withRouter(LeftNav))

2.20.8. components/header/header.jsx

import React, {Component} from 'react'

import {withRouter} from 'react-router-dom'

import { Modal} from 'antd'

import {connect} from 'react-redux'H5 前端课程系列

———————————————————————————

115

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

import {logout} from '../../redux/actions'

import LinkButton from '../link-button'

import {reqWeather} from '../../api'

import menuList from '../../config/menuConfig'

import {formateDate} from '../../utils/dateUtils'

import './index.less'

/*

左侧导航的组件

*/

class Header extends Component {

state = {

currentTime: formateDate(Date.now()), // 当前时间字符串

dayPictureUrl: '', // 天气图片 url

weather: '', // 天气的文本

}

getTime = () => {

// 每隔 1s 获取当前时间, 并更新状态数据 currentTime

this.intervalId = setInterval(() => {

const currentTime = formateDate(Date.now())

this.setState({currentTime})

}, 1000)

}

getWeather = async () => {

// 调用接口请求异步获取数据

const {dayPictureUrl, weather} = await reqWeather('北京')

// 更新状态

this.setState({dayPictureUrl, weather})

}

getTitle = () => {

// 得到当前请求路径

const path = this.props.location.pathname

let title

menuList.forEach(item => {

if (item.key===path) { // 如果当前 item 对象的 key path 一样,item title 就是需要

显示的 title

title = item.titleH5 前端课程系列

———————————————————————————

116

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

} else if (item.children) {

// 在所有子 item 中查找匹配的

const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0)

// 如果有值才说明有匹配的

if(cItem) {

// 取出它的 title

title = cItem.title

}

}

})

return title

}

/*

退出登陆

*/

logout = () => {

// 显示确认框

Modal.confirm({

content: '确定退出吗?',

onOk: () => {

this.props.logout()

}

})

}

/*

第一次 render()之后执行一次

一般在此执行异步操作: ajax 请求/启动定时器

*/

componentDidMount () {

// 获取当前的时间

this.getTime()

// 获取当前天气

this.getWeather()

}

/*

// 不能这么做: 不会更新显示

componentWillMount () {

this.title = this.getTitle()

}*/H5 前端课程系列

———————————————————————————

117

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

/*

当前组件卸载之前调用

*/

componentWillUnmount () {

// 清除定时器

clearInterval(this.intervalId)

}

render() {

const {currentTime, dayPictureUrl, weather} = this.state

const username = this.props.user.username

// 得到当前需要显示的 title

const title = this.props.headTitle

return (

<div className="header">

<div className="header-top">

<span>欢迎, {username}span>

<LinkButton onClick={this.logout}>退出LinkButton>

div>

<div className="header-bottom">

<div className="header-bottom-left">{title}div>

<div className="header-bottom-right">

<span>{currentTime}span>

<img src={dayPictureUrl} alt="weather"/>

<span>{weather}span>

div>

div>

div>

)

}

}

export default connect(

state => ({

user: state.user,

headTitle: state.headTitle

}),

{logout}H5 前端课程系列

———————————————————————————

118

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

)(withRouter(Header))

2.20.9. pages/admin/admin.jsx

import React, {Component} from 'react'

import {Redirect, Route, Switch} from 'react-router-dom'

import { Layout } from 'antd'

import {connect} from 'react-redux'

import LeftNav from '../../components/left-nav'

import Header from '../../components/header'

import Home from '../home/home'

import Category from '../category/category'

import Product from '../product/product'

import Role from '../role/role'

import User from '../user/user'

import Bar from '../charts/bar'

import Line from '../charts/line'

import Pie from '../charts/pie'

const { Footer, Sider, Content } = Layout

/*

后台管理的路由组件

*/

class Admin extends Component {

render () {

const user = this.props.user

// 如果内存没有存储 user ==> 当前没有登陆

if(!user || !user._id) {

// 自动跳转到登陆(render())

return <Redirect to='/login'/>

}

return (

<Layout style={{minHeight: '100%'}}>

<Sider>

<LeftNav/>H5 前端课程系列

———————————————————————————

119

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

Sider>

<Layout>

<Header>HeaderHeader>

<Content style={{margin: 20, backgroundColor: '#fff'}}>

<Switch>

<Route path='/home' component={Home}/>

<Route path='/category' component={Category}/>

<Route path='/product' component={Product}/>

<Route path='/role' component={Role}/>

<Route path='/user' component={User}/>

<Route path='/charts/bar' component={Bar}/>

<Route path='/charts/line' component={Line}/>

<Route path='/charts/pie' component={Pie}/>

<Redirect to='/home'/>

Switch>

Content>

<Footer style={{textAlign: 'center', color: '#cccccc'}}>推荐使用谷歌浏览器,

可以获得更佳页面操作体验Footer>

Layout>

Layout>

)

}

}

export default connect(

state => ({user: state.user})

)(Admin)

2.20.10. pages/login/login.jsx

import React, {PureComponent} from 'react'

import {Redirect} from 'react-router-dom'

import {connect} from 'react-redux'

import {

Form,

Icon,

Input,

Button,H5 前端课程系列

———————————————————————————

120

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

message

} from 'antd'

import './login.less'

import logo from '../../assets/images/logo.png'

import {login} from '../../redux/actions'

const Item = Form.Item // 不能写在 import 之前

/*

登陆的路由组件

*/

class Login extends PureComponent {

handleSubmit = (event) => {

// 阻止事件的默认行为

event.preventDefault()

// 对所有表单字段进行检验

this.props.form.validateFields(async (err, values) => {

// 检验成功

if (!err) {

// 请求登陆

const {username, password} = values

this.props.login(username, password)

}

});

// 得到 form 对象

// const form = this.props.form

// // 获取表单项的输入数据

// const values = form.getFieldsValue()

// console.log('handleSubmit()', values)

}

/*

对密码进行自定义验证

*/

/*

用户名/密码的的合法性要求

1). 必须输入

2). 必须大于等于 4 H5 前端课程系列

———————————————————————————

121

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

3). 必须小于等于 12

4). 必须是英文、数字或下划线组成

*/

validatePwd = (rule, value, callback) => {

console.log('validatePwd()', rule, value)

if (!value) {

callback('密码必须输入')

} else if (value.length < 4) {

callback('密码长度不能小于 4 ')

} else if (value.length > 12) {

callback('密码长度不能大于 12 ')

} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {

callback('密码必须是英文、数字或下划线组成')

} else {

callback() // 验证通过

}

// callback('xxxx') // 验证失败, 并指定提示的文本

}

render() {

// 如果用户已经登陆, 自动跳转到管理界面

const user = this.props.user

if (user && user._id) {

return <Redirect to='/home'/>

}

// 得到具强大功能的 form 对象

const form = this.props.form

const {getFieldDecorator} = form;

return (

<div className="login">

<header className="login-header">

<img src={logo} alt="logo"/>

<h1>React 项目: 后台管理系统h1>

header>

<section className="login-content">

<div className={user.errorMsg ? 'error-msg show' :

'error-msg'}>{user.errorMsg}div>

<h2>用户登陆h2>

<Form onSubmit={this.handleSubmit} className="login-form">H5 前端课程系列

———————————————————————————

122

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Item>

{

/*

用户名/密码的的合法性要求

1). 必须输入

2). 必须大于等于 4

3). 必须小于等于 12

4). 必须是英文、数字或下划线组成

*/

}

{

getFieldDecorator('username', { // 配置对象: 属性名是特定的一些名称

// 声明式验证: 直接使用别人定义好的验证规则进行验证

rules: [

{required: true, whitespace: true, message: '用户名必须输入'},

{min: 4, message: '用户名至少 4 '},

{max: 12, message: '用户名最多 12 '},

{pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是英文、数字或下划线

组成'},

],

initialValue: 'admin', // 初始值

})(

<Input

prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}

placeholder="用户名"

/>

)

}

Item>

<Form.Item>

{

getFieldDecorator('password', {

rules: [

{

validator: this.validatePwd

}

]

})(

<Input

prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>}

type="password"

placeholder="密码"H5 前端课程系列

———————————————————————————

123

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

/>

)

}

Form.Item>

<Form.Item>

<Button type="primary" htmlType="submit" className="login-form-button">

登陆

Button>

Form.Item>

Form>

section>

div>

)

}

}

/*

1. 高阶函数

1). 一类特别的函数

a. 接受函数类型的参数

b. 返回值是函数

2). 常见

a. 定时器: setTimeout()/setInterval()

b. Promise: Promise(() => {}) then(value => {}, reason => {})

c. 数组遍历相关的方法: forEach()/filter()/map()/reduce()/find()/findIndex()

d. 函数对象的 bind()

e. Form.create()() / getFieldDecorator()()

3). 高阶函数更新动态, 更加具有扩展性

2. 高阶组件

1). 本质就是一个函数

2). 接收一个组件(被包装组件), 返回一个新的组件(包装组件), 包装组件会向被包装组件传入特

定属性

3). 作用: 扩展组件的功能

4). 高阶组件也是高阶函数: 接收一个组件函数, 返回是一个新的组件函数

*/

/*

包装 Form 组件生成一个新的组件: Form(Login)

新组件会向 Form 组件传递一个强大的对象属性: form

*/

const WrapLogin = Form.create()(Login)H5 前端课程系列

———————————————————————————

124

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

export default connect(

state => ({user: state.user}),

{login}

)(WrapLogin)

.error-msg {

visibility: hidden;

position: absolute;

top: 0;

left: 0;

text-align: center;

height: 30px;

width: 100%;

background: #f60c1a;

color: #ffffff;

font-size: 16px;

transform: translateY(-30px);

transition: all .3s;

&.show {

visibility: visible;

transform: translateY(0);

}

}

2.20.11. pages/role/role.jsx

import React, {Component} from 'react'

import {

Card,

Button,

Table,

Modal,

message

} from 'antd'

import {connect} from 'react-redux'

import {PAGE_SIZE} from "../../utils/constants"H5 前端课程系列

———————————————————————————

125

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

import {reqRoles, reqAddRole, reqUpdateRole} from '../../api'

import AddForm from './add-form'

import AuthForm from './auth-form'

import {formateDate} from '../../utils/dateUtils'

import {logout} from '../../redux/actions'

/*

角色路由

*/

class Role extends Component {

state = {

roles: [], // 所有角色的列表

role: {}, // 选中的 role

isShowAdd: false, // 是否显示添加界面

isShowAuth: false, // 是否显示设置权限界面

}

constructor (props) {

super(props)

this.auth = React.createRef()

}

initColumn = () => {

this.columns = [

{

title: '角色名称',

dataIndex: 'name'

},

{

title: '创建时间',

dataIndex: 'create_time',

render: (create_time) => formateDate(create_time)

},

{

title: '授权时间',

dataIndex: 'auth_time',

render: formateDate

},

{

title: '授权人',H5 前端课程系列

———————————————————————————

126

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

dataIndex: 'auth_name'

},

]

}

getRoles = async () => {

const result = await reqRoles()

if (result.status===0) {

const roles = result.data

this.setState({

roles

})

}

}

onRow = (role) => {

return {

onClick: event => { // 点击行

console.log('row onClick()', role)

// alert('点击行')

this.setState({

role

})

},

}

}

/*

添加角色

*/

addRole = () => {

// 进行表单验证, 只能通过了才向下处理

this.form.validateFields(async (error, values) => {

if (!error) {

// 隐藏确认框

this.setState({

isShowAdd: false

})

// 收集输入数据H5 前端课程系列

———————————————————————————

127

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

const {roleName} = values

this.form.resetFields()

// 请求添加

const result = await reqAddRole(roleName)

// 根据结果提示/更新列表显示

if (result.status===0) {

message.success('添加角色成功')

// this.getRoles()

// 新产生的角色

const role = result.data

// 更新 roles 状态

/*const roles = this.state.roles

roles.push(role)

this.setState({

roles

})*/

// 更新 roles 状态: 基于原本状态数据更新

this.setState(state => ({

roles: [...state.roles, role]

}))

} else {

message.success('添加角色失败')

}

}

})

}

/*

更新角色

*/

updateRole = async () => {

// 隐藏确认框

this.setState({

isShowAuth: false

})H5 前端课程系列

———————————————————————————

128

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

const role = this.state.role

// 得到最新的 menus

const menus = this.auth.current.getMenus()

role.menus = menus

role.auth_time = Date.now()

role.auth_name = this.props.user.username

// 请求更新

const result = await reqUpdateRole(role)

if (result.status===0) {

// this.getRoles()

// 如果当前更新的是自己角色的权限, 强制退出

if (role._id === this.props.user.role_id) {

this.props.logout()

message.success('当前用户角色权限成功')

} else {

message.success('设置角色权限成功')

this.setState({

roles: [...this.state.roles]

})

}

}

}

componentWillMount () {

this.initColumn()

}

componentDidMount () {

this.getRoles()

}

render() {

const {roles, role, isShowAdd, isShowAuth} = this.state

const title = (

<span>

<Button type='primary' onClick={() => this.setState({isShowAdd: true})}>创建角

Button>   H5 前端课程系列

———————————————————————————

129

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Button type='primary' disabled={!role._id} onClick={() =>

this.setState({isShowAuth: true})}>设置角色权限Button>

span>

)

return (

<Card title={title}>

<Table

bordered

rowKey='_id'

dataSource={roles}

columns={this.columns}

pagination={{defaultPageSize: PAGE_SIZE}}

rowSelection={{

type: 'radio',

selectedRowKeys: [role._id],

onSelect: (role) => { // 选择某个 radio 时回调

this.setState({

role

})

}

}}

onRow={this.onRow}

/>

<Modal

title="添加角色"

visible={isShowAdd}

onOk={this.addRole}

onCancel={() => {

this.setState({isShowAdd: false})

this.form.resetFields()

}}

>

<AddForm

setForm={(form) => this.form = form}

/>

Modal>

<Modal

title="设置角色权限"H5 前端课程系列

———————————————————————————

130

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

visible={isShowAuth}

onOk={this.updateRole}

onCancel={() => {

this.setState({isShowAuth: false})

}}

>

<AuthForm ref={this.auth} role={role}/>

Modal>

Card>

)

}

}

export default connect(

state => ({user: state.user}),

{logout}

)(Role)

2.21. 可视化图表

2.21.1. 常用数据可视化图表库

1) echarts

a. https://echarts.baidu.com/

b. 百度开源, 如果要在 react 项目中使用, 需要下载 echarts-for-react

2) G2

a. https://antv.alipay.com/zh-cn/g2/3.x/index.html

b. 阿里开源

3) bizcharts

a. https://bizcharts.net/products/bizCharts

b. 基于 react 包装 G2 的开源库

c. 需要额外下载 @antv/data-set

4) d3

a. https://d3js.org.cn/

b. 国外的免费可视化图表库H5 前端课程系列

———————————————————————————

131

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.21.2. 下载依赖

yarn add echarts echarts-for-react

yarn add bizcharts @antv/data-set

2.21.3. bar.jsx: 柱状图

import React, {Component} from 'react'

import {Card, Button} from 'antd'

import ReactEcharts from 'echarts-for-react'

/*

后台管理的柱状图路由组件

*/

export default class Bar extends Component {

state = {

sales: [5, 20, 36, 10, 10, 20],

inventorys: [15, 30, 46, 20, 20, 40]

}

getOption = () => {

const {sales, inventorys} = this.state

return {

title: {

text: 'ECharts 入门示例'

},

tooltip: {},

legend: {

data:['销量', '库存']

},

xAxis: {

data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]

},

yAxis: {},

series: [{

name: '销量',

type: 'bar',

data:salesH5 前端课程系列

———————————————————————————

132

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

}, {

name: '库存',

type: 'bar',

data: inventorys

}]

}

}

update = () => {

const sales = this.state.sales.map(sale => sale + 1)

const inventorys = this.state.inventorys.map(inventory => inventory -1)

this.setState({

sales,

inventorys

})

}

render() {

return (

<div>

<Card>

<Button type='primary' onClick={this.update}>更新Button>

Card>

<Card title='柱状图一'>

<ReactEcharts option={this.getOption()} style={{height: 300}}/>

Card>

div>

)

}

}

2.21.4. line.jsx: 折线图

import React, {Component} from 'react'

import {Card, Button} from 'antd'

import ReactEcharts from 'echarts-for-react'

/*

后台管理的折线图路由组件H5 前端课程系列

———————————————————————————

133

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

*/

export default class Line extends Component {

state = {

sales: [5, 20, 36, 10, 10, 20],

inventorys: [15, 30, 46, 20, 20, 40]

}

getOption = () => {

const {sales, inventorys} = this.state

return {

title: {

text: 'ECharts 入门示例'

},

tooltip: {},

legend: {

data:['销量', '库存']

},

xAxis: {

data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]

},

yAxis: {},

series: [{

name: '销量',

type: 'line',

data:sales

}, {

name: '库存',

type: 'line',

data: inventorys

}]

}

}

getOption2 = () => {

return {

xAxis: {

type: 'category',

boundaryGap: false,

data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

},

yAxis: {

type: 'value'

},H5 前端课程系列

———————————————————————————

134

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

series: [{

data: [820, 932, 901, 934, 1290, 1330, 1320],

type: 'line',

areaStyle: {}

}]

};

}

update = () => {

const sales = this.state.sales.map(sale => sale + 1)

const inventorys = this.state.inventorys.map(inventory => inventory -1)

this.setState({

sales,

inventorys

})

}

render() {

return (

<div>

<Card>

<Button type='primary' onClick={this.update}>更新Button>

Card>

<Card title='折线图一'>

<ReactEcharts option={this.getOption()} style={{height: 300}}/>

Card>

<Card title='折线图二'>

<ReactEcharts option={this.getOption2()} style={{height: 300}}/>

Card>

div>

)

}

}H5 前端课程系列

———————————————————————————

135

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.21.5. pie.jsx: 饼状图

import React, {Component} from 'react'

import {Card} from 'antd'

import ReactEcharts from 'echarts-for-react'

/*

后台管理的饼图路由组件

*/

export default class Pie extends Component {

getOption = () => {

return {

title : {

text: '某站点用户访问来源',

subtext: '纯属虚构',

x:'center'

},

tooltip : {

trigger: 'item',

formatter: "{a}
{b} : {c} ({d}%)"

},

legend: {

orient: 'vertical',

left: 'left',

data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎']

},

series : [

{

name: '访问来源',

type: 'pie',

radius : '55%',

center: ['50%', '60%'],

data:[

{value:335, name:'直接访问'},

{value:310, name:'邮件营销'},

{value:234, name:'联盟广告'},

{value:135, name:'视频广告'},

{value:1548, name:'搜索引擎'}

],

itemStyle: {H5 前端课程系列

———————————————————————————

136

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

emphasis: {

shadowBlur: 10,

shadowOffsetX: 0,

shadowColor: 'rgba(0, 0, 0, 0.5)'

}

}

}

]

};

}

getOption2 = () => {

return {

backgroundColor: '#2c343c',

title: {

text: 'Customized Pie',

left: 'center',

top: 20,

textStyle: {

color: '#ccc'

}

},

tooltip : {

trigger: 'item',

formatter: "{a}
{b} : {c} ({d}%)"

},

visualMap: {

show: false,

min: 80,

max: 600,

inRange: {

colorLightness: [0, 1]

}

},

series : [

{

name:'访问来源',

type:'pie',H5 前端课程系列

———————————————————————————

137

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

radius : '55%',

center: ['50%', '50%'],

data:[

{value:335, name:'直接访问'},

{value:310, name:'邮件营销'},

{value:274, name:'联盟广告'},

{value:235, name:'视频广告'},

{value:400, name:'搜索引擎'}

].sort(function (a, b) { return a.value - b.value; }),

roseType: 'radius',

label: {

normal: {

textStyle: {

color: 'rgba(255, 255, 255, 0.3)'

}

}

},

labelLine: {

normal: {

lineStyle: {

color: 'rgba(255, 255, 255, 0.3)'

},

smooth: 0.2,

length: 10,

length2: 20

}

},

itemStyle: {

normal: {

color: '#c23531',

shadowBlur: 200,

shadowColor: 'rgba(0, 0, 0, 0.5)'

}

},

animationType: 'scale',

animationEasing: 'elasticOut',

animationDelay: function (idx) {

return Math.random() * 200;

}

}

]H5 前端课程系列

———————————————————————————

138

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

};

}

render() {

return (

<div>

<Card title='饼图一'>

<ReactEcharts option={this.getOption()} style={{height: 300}}/>

Card>

<Card title='饼图二'>

<ReactEcharts option={this.getOption2()} style={{height: 300}}/>

Card>

div>

)

}

}

2.21.6. 首页图表

1) home/line.jsx

import React from "react"

import {

Chart,

Geom,

Axis,

Tooltip,

Legend,

} from "bizcharts"

import DataSet from "@antv/data-set"

export default class Line extends React.Component {

render() {

const data = [

{

month: "Jan",

a: 7.0,

b: 3.9,

c: 5.9

},H5 前端课程系列

———————————————————————————

139

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

{

month: "Feb",

a: 6.9,

b: 4.2,

c: 1.9

},

{

month: "Mar",

a: 9.5,

b: 5.7,

c: 3.9

},

{

month: "Apr",

a: 14.5,

b: 8.5,

c: 5.5

},

{

month: "May",

a: 18.4,

b: 11.9,

c: 8.9

},

{

month: "Jun",

a: 21.5,

b: 15.2,

c: 10.0

},

{

month: "Jul",

a: 25.2,

b: 17.0,

c: 12.9

},

{

month: "Aug",

a: 26.5,

b: 16.6,

c: 15.9

},H5 前端课程系列

———————————————————————————

140

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

{

month: "Sep",

a: 23.3,

b: 14.2,

c: 20.7

},

{

month: "Oct",

a: 18.3,

b: 10.3,

c: 25.9

},

{

month: "Nov",

a: 13.9,

b: 6.6,

c: 30.9

},

{

month: "Dec",

a: 9.6,

b: 4.8,

c: 35.9

}

]

const ds = new DataSet()

const dv = ds.createView().source(data)

dv.transform({

type: "fold",

fields: ["a", "b", "c"],

// 展开字段集

key: "city",

// key 字段

value: "temperature" // value 字段

})

const cols = {

month: {

range: [0, 1]

}

}

return (

<div style={{float: 'right', width: 750, height: 300}}>H5 前端课程系列

———————————————————————————

141

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

<Chart height={250} data={dv} scale={cols} forceFit>

<Legend/>

<Axis name="month"/>

<Axis

name="temperature"

label={{

formatter: val => `${val}万个`

}}

/>

<Tooltip

crosshairs={{

type: "y"

}}

/>

<Geom

type="line"

position="month*temperature"

size={2}

color={"city"}

shape={"smooth"}

/>

<Geom

type="point"

position="month*temperature"

size={4}

shape={"circle"}

color={"city"}

style={{

stroke: "#fff",

lineWidth: 1

}}

/>

Chart>

div>

)

}

}

2) home/bar.jsxH5 前端课程系列

———————————————————————————

142

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

import React from "react"

import {

Chart,

Geom,

Axis,

Tooltip,

} from "bizcharts"

export default class Bar extends React.Component {

render() {

const data = [

{

year: "1 ",

sales: 38

},

{

year: "2 ",

sales: 52

},

{

year: "3 ",

sales: 61

},

{

year: "4 ",

sales: 145

},

{

year: "5 ",

sales: 48

},

{

year: "6 ",

sales: 38

},

{

year: "7 ",

sales: 28

},

{

year: "8 ",

sales: 38H5 前端课程系列

———————————————————————————

143

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

},

{

year: "59 ",

sales: 68

},

{

year: "10 ",

sales: 38

},

{

year: "11 ",

sales: 58

},

{

year: "12 ",

sales: 38

}

]

const cols = {

sales: {

tickInterval: 20

}

}

return (

<div style={{width: '100%', marginLeft: -30}}>

<Chart height={338} data={data} scale={cols} forceFit>

<Axis name="year"/>

<Axis name="sales"/>

<Tooltip

crosshairs={{

type: "y"

}}

/>

<Geom type="interval" position="year*sales"/>

Chart>

div>

)

}

}

3) home/home.lessH5 前端课程系列

———————————————————————————

144

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

.home {

padding: 24px;

background: #fff;

min-height: 850px;

.home-card {

float: left;

}

.home-content {

position: absolute;

top: 420px;

width: 76%;

border: 1px solid #e8e8e8;

.home-menu {

font-size: 20px;

span {

cursor: pointer;

}

.home-menu-active {

border-bottom: 2px solid #1DA57A;

color: #1DA57A;

padding: 0 0 16px 0;

}

.home-menu-visited {

margin-right: 40px;

}

}

.home-table-left {

float: left;

width: 60%;

}

.home-table-right {

float: right;

width: 330px;

}

}

}

4) Home/home.jsx

import React, {Component} from 'react'

import {

Icon,H5 前端课程系列

———————————————————————————

145

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

Card,

Statistic,

DatePicker,

Timeline

} from 'antd'

import moment from 'moment'

import Line from './line'

import Bar from './bar'

import './home.less'

const dateFormat = 'YYYY/MM/DD'

const {RangePicker} = DatePicker

export default class Home extends Component {

state = {

isVisited: true

}

handleChange = (isVisited) => {

return () => this.setState({isVisited})

}

render() {

const {isVisited} = this.state

return (

<div className='home'>

<Card

className="home-card"

title="商品总量"

extra={<Icon style={{color: 'rgba(0,0,0,.45)'}} type="question-circle"/>}

style={{width: 250}}

headStyle={{color: 'rgba(0,0,0,.45)'}}

>

<Statistic

value={1128163}

suffix=""

style={{fontWeight: 'bolder'}}

/>

<StatisticH5 前端课程系列

———————————————————————————

146

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

value={15}

valueStyle={{fontSize: 15}}

prefix={'周同比'}

suffix={<div>%<Icon style={{color: 'red', marginLeft: 10}}

type="arrow-down"/>div>}

/>

<Statistic

value={10}

valueStyle={{fontSize: 15}}

prefix={'日同比'}

suffix={<div>%<Icon style={{color: '#3f8600', marginLeft: 10}}

type="arrow-up"/>div>}

/>

Card>

<Line/>

<Card

className="home-content"

title={<div className="home-menu">

<span className={isVisited ? "home-menu-active home-menu-visited" :

'home-menu-visited'}

onClick={this.handleChange(true)}>访问量span>

<span className={isVisited ? "" : 'home-menu-active'}

onClick={this.handleChange(false)}>销售量span>

div>}

extra={<RangePicker

defaultValue={[moment('2019/01/01', dateFormat), moment('2019/06/01',

dateFormat)]}

format={dateFormat}

/>}

>

<Card

className="home-table-left"

title={isVisited ? '访问趋势' : '销售趋势'}

bodyStyle={{padding: 0, height: 275}}

extra={<Icon type="reload"/>}

>

<Bar/>

Card>

<Card title='任务' extra={<Icon type="reload"/>}H5 前端课程系列

———————————————————————————

147

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

className="home-table-right">

<Timeline>

<Timeline.Item color="green">新版本迭代会Timeline.Item>

<Timeline.Item color="green">完成网站设计初版Timeline.Item>

<Timeline.Item color="red">

<p>联调接口p>

<p>功能验收p>

Timeline.Item>

<Timeline.Item>

<p>登录功能设计p>

<p>权限验证p>

<p>页面排版p>

Timeline.Item>

Timeline>

Card>

Card>

div>

)

}

}

2.22. 前台 404 界面

2.22.1. not-found/not-found.jsx

import React, {Component} from 'react'

import {Button, Row, Col} from 'antd'

import {connect} from 'react-redux'

import {setHeadTitle} from '../../redux/actions'

import './not-found.less'

/*

前台 404 页面

*/

class NotFound extends Component {H5 前端课程系列

———————————————————————————

148

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

goHome = () => {

this.props.setHeadTitle('首页')

this.props.history.replace('/home')

}

render() {

return (

<Row className='not-found'>

<Col span={12} className='left'>Col>

<Col span={12} className='right'>

<h1>404h1>

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

<div>

<Button type='primary' onClick={this.goHome}>

回到首页

Button>

div>

Col>

Row>

)

}

}

export default connect(

null,

{setHeadTitle}

)(NotFound)

2.22.2. not-found/not-found.less

.not-found{

background-color: #f0f2f5;

height: 100%;

.left {

height: 100%;

background: url('./images/404.png') no-repeat center;

}

.right {

padding-left: 50px;H5 前端课程系列

———————————————————————————

149

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

margin-top: 150px;

h1 {

font-size: 35px;

}

h2 {

margin-bottom: 20px;

font-size: 20px;

}

}

}

2.22.3. admin/admin.jsx

<Redirect from='/' to='/home' exact/>

<Route path='/home' component={Home}/>

<Route path='/category' component={Category}/>

<Route path='/product' component={Product}/>

<Route path='/role' component={Role}/>

<Route path='/user' component={User}/>

<Route path='/charts/bar' component={Bar}/>

<Route path='/charts/line' component={Line}/>

<Route path='/charts/pie' component={Pie}/>

<Route component={NotFound}/>

2.23. 打包项目并运行

2.23.1. 打包项目

yarn run buildH5 前端课程系列

———————————————————————————

150

更多 Java –大数据 前端 –python 人工智能资料下载,可访问百度:尚硅谷官网

2.23.1. 运行打包项目

1) 与服务器端项目独立运行

问题: 存在 ajax 请求跨域问题

解决: 由服务器端工程师配置代理服务器(前端工程师不用亲自操作)

2) 合并到服务端项目一起运行

不再有 ajax 请求跨域问题

3) 使用 BrowserRouter 的问题

a. 问题: 刷新某个路由路径时, 会出现 404 的错误

b. 原因: 项目根路径后的 path 路径会被当作后台路由路径, 去请求对应的后台路由,

但没有

c. 解决: 使用自定义中间件去读取返回 index 页面展现

const fs = require('fs')

// 必须在路由器中间之后声明使用

app.use((req, res) => {

fs.readFile(__dirname + '/public/index.html', (err, data)=>{

if(err){

console.log(err)

res.send('后台错误')

} else {

res.writeHead(200, {

'Content-Type': 'text/html; charset=utf-8',

});

res.end(data)

}

})

})

d. 注意: 前端路由的路径不要与后台路由路径相同(并且请求方式也相同)

展开阅读全文

没有更多推荐了,返回首页