React16+React-Router4 从零打造企业级电商后台管理系统

本文主要是介绍React16+React-Router4 从零打造企业级电商后台管理系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、后台管理系统的需求分析和技术选型、接口文档规范
  • 二、知识储备
    • 1.页面加载过程
    • 2.ES6基础
    • 3.本地存储
  • 三、开发环境的搭建
    • 1.git的安装和项目的建立
    • 2.node.js和yarn的安装
    • 3.webpack配置
    • 4.第一次提交代码
  • 四、react查漏补缺
    • 1.react组件
    • 2.react生命周期
    • 3.React-Router
  • 五、通用部分的开发
    • 1.通用布局的开发
    • 2.头部导航开发
    • 3.侧边导航开发
    • 4.通用页面标题的开发
  • 六、基础功能模块的开发
    • 1.登录页面的开发
    • 2.登录状态管理
    • 3.首页的开发
    • 4.错误页面开发

一、后台管理系统的需求分析和技术选型、接口文档规范

  • 功能需求
· 商品管理 -> 添加商品/编辑商品,查看商品,下架
· 品类管理 -> 添加品类,查看品类
· 订单管理 -> 订单列表,订单详情、发货
· 用户管理 -> 管理员登录,用户列表
  • 技术选型
语言和框架:React、Sass、Bootstrap、ES6
架构:前后端分离、分层架构(工具层、逻辑层、服务层)、模块化开发
辅助工具:Yarn、Webpack(打包工具)、Git(代码管理工具)
  • 接口文档规范
    在这里插入图片描述

二、知识储备

1.页面加载过程

  • 资源加载过程
URL解析->DNS查询->资源加载->浏览器解析
  • URL结构
    在这里插入图片描述
    1.域名是用来查找服务器位置的,域名有一个ip地址,找到这个地址之后,如果把IP地址比作一间房子 ,端口就是出入这间房子的门
    2.http协议的默认端口是80端口,https默认端口是443端口,如果访问默认端口,端口号就可以省略
    3.路径是服务器接收到请求后,拿着路径在服务器上定位资源位置

  • DNS查询
    在这里插入图片描述
    1.DNS缓存主要用于减少DNS查询量
    2.DNS查询就是把上一步解析出来的域名,查找到改域名对于的ip地址
    在这里插入图片描述
    1.dns-prefetch作用:当页面一加载就把这几个域名做DNS查询,并且缓存起来,等到真正请求这些域名下资源的时候,就能省去DNS查询时间,提高页面加载速度

  • 请求资源
    在这里插入图片描述

  • 浏览器解析
    在这里插入图片描述
    1.最先加载的是html文件,在加载html文件的同时构建DOM树,遇到一个html节点就放到树里,假如在加载html文件的同时遇到js文件,那就听下构造DOM树的工作,执行完js再构建DOM树,DOM树执行完就构建渲染树(DOM树和CSS样式表结合的产物)

2.ES6基础

  • let,const
    1.let定义变量,const定义常量
    2.不能重复定义
    3.块级作用域
    4.不存在变量提升
//变量和常量
let r = 2;
r = 4;
console.log(r) //4
const pi = 3.1415926
pi = 10;//不能重复定义
var foo = 1;
var foo = 2;
console.log(foo)let bar = 1;
let bar = 2; //报错
console.log(bar)//块级作用域
if(true){var test = 1;
}
console.log(test);//1if(true){let test1 = 2;}console.log(test1)//报错let arr = [1,2,3,4]
for(var i = 0, iLength = arr.length;i<iLength;i++){
}
console.log(i)//4let arr = [1,2,3,4]
for(let i = 0, iLength = arr.length;i<iLength;i++){
}
console.log(i)//报错//不存在变量提升console.log(foo);//undefinedvar foo = 1;等价于var fooconsole.log(foo)foo = 1console.log(foo);//报错let foo = 1;
  • 箭头函数
    1.参数=>表达式/语句
    2.继承外层作用域
    3.不能用作构造函数
    4.没有prototype属性

在这里插入片描述

//没有独立作用域
var obj = {commonFn:function(){console.log(this)}arrowFn:()=>{console.log(this)}
}
obj.commonFn()//this指向obj作用域
obj.arrowFn()//this指向obj所在的作用域,Windows//不能用作构造函数
let Animal = function(){
}
let animal = new Animal()let Animal  = () = {}
let animal = new Animal()//没有prototype
let commonFn = function(){}
let arrowFn = () => {}
console.log(commonFn.prototype)
console.log(arrowFn.prototype)//undefined
  • 模板字符串
//基本用法
let str = `<div><h1 class="title"></h1></div>`;
document.querySelector('body').innerHTML = str;//嵌套变量用法
let name = 'ROSEN';
let str = `<div><h1 class="title">${name}</h1></div>`;
document.querySelector('body').innerHTML = str;//嵌套函数用法
let getName = () = >{ retrun 'ROSEN' };
let str = `<div><h1 class="title">${getName()}</h1></div>`;
document.querySelector('body').innerHTML = str;//循环嵌套
let names = ['a','b','c'];
let str = `<ul>${names.map(name => `<li>${name}<li>`).join('')}</ul>
`
document.querySelector('body').innerHTML = str;
  • Promise
    1.Promise对象
    2.关键词resolve,reject,then
//Promise结构
new Promise((resolve,reject)=>{$.ajax({url:'http://happymmall.com/user/get_user_info.do',type:'post',success(res){resolve(res)},error(err){reject(err)}})
}).then((res)=>{console.log(res)
},(err)=>{console.log(err)
})
//链式Promise
var promise1 = new Promise((resolve,reject)=>{$.ajax({url:'http://happymmall.com/user/get_user_info.do',type:'post',success(res){resolve(res)},error(err){reject(err)}})
});
var promise2 = new Promise((resolve,reject)=>{$.ajax({url:'http://happymmall.com/cart/get_cart_product.do',type:'post',success(res){resolve(res)},error(err){reject(err)}})
});
promise1.then(()=>{console.log('promise1 success')return promise1
}).then(()=>{console.log('promise2 success')
})
  • 面向对象-类
    1.关键词:class
    2.语法糖:对应function
    3.构造函数,constructor

  • 面向对象-类的继承
    1.extends:类的继承
    2.super:调用父类的构造函数

//class constructor
class Animal{constructor(name){this.name = name}getName(){retuen this.name}
}
let animal = new Animal('animal')
console.log(animal.getName())//animal//类的继承
class Animal{constructor(){this.name = 'animal'}getName(){retuen this.name}
}
class Car extends Animal{constructor(){super()//用了extends是没有this的,只能和父类共享一个this指针,必须写super()this.name = 'car'}
}
let animal = new Animal()
let car = new Car()
console.log(animal.getName())//animal
console.log(car.getName())//car
  • 面向对象-对象
    1.对象里属性的简写
    2.对象里的方法简写
    3.属性名可以为表达式
    4.其他扩展
let name = 'hml'
let age  = 18
let obj  = {//变量名可以直接用作对象的属性名称name,age,//对象里的方法可以缩写getName(){retrun this.name},//表达式作为属性名或方法名['get'+'Age'](){retrun this.age}
}
console.log(Object.keys(obj))//['name','age','getName','getAge']
console.log(Object.assign({a:1},{b:2}))//{a:1,b:2}
console.log(Object.assign({a:1,b:2},{b:3}))//{a:1,b:3}
//后面一个对象可以覆盖前面一个对象
  • ES6模块化
    1.解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程
    2.CommomJS,AMD,CMD
    3.关键词:export,import

文件 index.html

<script type="module" src="./index.js"></script>
//type要改成module

文件 module.js

let name = 'hml'
let age  = 18
let fun  = () = >{console.log('module')
}
export {name,age,fun
}
export default {a:1} //默认导出

文件 index.js

import {myname as name, age , fun} from 'module.js'
//非默认导出的名字必须和导出的名字一样,可以用as更改
import foo from 'module.js'
//默认导出的可以自定义名字console.log('name', myname)
console.log('age', age)
console.log('fun', fun)
console.log('foo', foo)
  • 不直接从文件夹打开html文件,通过在本地服务器上打开
    1.首先 npm install http-server -g
    2.进入html文件所在的文件夹
    3.执行 http-server

3.本地存储

  • cookie
    前端和后端进行数据交互的时候,是用http请求的,但是http是一种无状态的协议,无状态的意思就是他收到一个请求然后返回一个响应,而不关心请求者的身份,http也不是一种持久性的链接。cookie的出现就是为了在用户端中保存用户者的身份,浏览器会在操作系统里面开辟一个文件,专门存放cookie,这个文件只要不删除就会一直存在,而在每次请求后端时,都会在http头里带上这个cookie信息,后端就会知道这个http请求是谁发来的,一个请求只能操作自己有权限的cookie。
1.用户端保存请求信息的机制
2.分号分割的多个key-value字段
3.存储在本地的加密文件
4.域名和路径限制,domain是有作用域概念的,比如说二级域名是可以操作一级域名的cookie,而不能操作其他二级域名的cookie,也不能操作其三级域名的cookie
1.name:cookie名称
2.domain:cookie生效的域名
3.path:cookie生效的路径
4.expries:cookie过期时间(格林威治时间)
//获取当前格林威治时间
var d = new Date()
console.log(d.toUTCString())
//Sat, 06 Jan 2018 13:38:45 GMT
//查看cookie
document.cookie
//添加cookie
document.cookie = 'name=HML;domanin=happymmall.com;path=/index.html;expries=Sat, 06 Jan 2018 13:38:45 GMT'
//修改cookie
//浏览器通过domanin和path来判断要修改哪个cookie
document.cookie = 'name=HML2;domanin=happymmall.com;path=/index.html;expries=Sat, 06 Jan 2018 13:38:45 GMT'
//删除cookie
//expries过期就相当于删除,把expries设为0
document.cookie = 'name=HML2;domanin=happymmall.com;path=/index.html;expries=0'
  • localStorage
1.H5的新特性
2.有域名限制,不存在作用域概念
3.只有key-value
4.没有过期时间
5.浏览器关闭后不消失
  • sessionStrage
1.和localStorage极其相似
2.浏览器关闭后消失(会话结束后消失)
//添加localStorage
//如果存的东西是对象,就会执行对象的toString()方法,所以要把json对象变成json字符串
windows.localStorage.setItem('name',JSON.stringify({name:'hml'}))
//查看localStorage
windows.localStorage.getItem('name')
//删除localStorage
windows.localStorage.removeItem('name')

三、开发环境的搭建

开发环境依赖

git=>gitee.com //代码托管
webpacke、yarn.、node.js、node-sass

webpacke的配置

 - webpacke的安装和配置- 系统里多个版本webpacke项目共存的处理- webpacke对各种类型文件的处理方式- webpacke-dev-server的安装和配置

1.git的安装和项目的建立

  • git安装
    1.安装完git后对.gitconfig文件进行配置
//进入当前用户目录下的gitconfig文件
vim ~/.gitconfig

按下i 启动输入模式

[user]name=hezeyingemail=357261045@qq.com
[alias]co=checkoutci=commitst=statuspl=pullps=pushdt=difftoolca=commit -amb=branch

按下esc建
:wq退出

  • 项目的建立
    1.生成ssh公钥
//生成ssh公钥
ssh-keygen -t rsa -b 4096 -C "xxxxxxxxx@qq.com"
ls -al
cat id_rsd.pub

在这里插入图片描述

 ls -al

在这里插入图片描述

cd .ssh/
ls -al

在这里插入图片描述

cat id_rsa.pub

在这里插入图片描述
2.在gitee.com创建git项目
3.在gitee添加个人公钥匙
4.把项目clone到本地

cd
ls

在这里插入图片描述

//创建本地文件夹
mkdir react
cd react
git clone git@gitee.com:react-project-hzy/react-business-admin.git

在这里插入图片描述

ls

在这里插入图片描述

cd react-business-admin
ls

在这里插入图片描述

ls -al

在这里插入图片描述

vim .gitignore

i输入

.DS_Store
node_modules
dist
*.log

esc
:qw

git st

在这里插入图片描述

git add .
git ca "initial"

在这里插入图片描述

git push

在这里插入图片描述

2.node.js和yarn的安装

yarn安装

npm install yarn -g

在这里插入图片描述
用yarn初始化项目后,最后一次推送到master分支

ls
cd react
cd react-business-admin
yarn init //初始化

在这里插入图片描述

cat package.json

在这里插入图片描述

git st

在这里插入图片描述

git add .
git ca "yarn init"
git push

3.webpack配置

  • 备注
1.一个前端资源加载/打包工具
2.使用版本:3.10.0
3.yarn add webpack@3.10.0 --dev
4.多版本webpack共存解决方案
  • 需要处理的文件类型
html --> html-webpack-plugin
脚本 --> babel+babel-preset-react
样式 --> css-loader+sass-loader
图片字体 -->url-loader+file-loader
  • webpack常用类型
1.html-webpack-plugin,html单独打包成文件
2.extract-text-webpack-plugin,样式打包成单独文件
3.CommonsChunkPlugin,提出通用模块
  • webpack-dev-server
1.为webpack项目提供web服务
2.使用版本2.9.7
3.更改代码自动刷新,路径转发
4.yarn add webpack-dev-server@2.9.7 --dev
5.解决多版本共存问题

webpack.config.js文件

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');//html-webpack-plugin:创建html的插件
const ExtractTextPlugin = require("extract-text-webpack-plugin");//创建css的插件
const webpack = require('webpack');module.exports = {entry: './src/app.jsx',//项目入口文件//输出的文件output: {path: path.resolve(__dirname, 'dist'),//__dirname是当前目录publicPath: '/dist/',filename: 'js/app.js'},module: {rules: [//es6,react语法处理{test: /\.jsx$/,exclude: /(node_modules)/,//不做处理的文件use: [{loader: 'babel-loader',options: {presets: ['env', 'react']//presets: ['env']的功能是自动根据环境来打包}}]},//css文件处理{test: /\.css$/,use: ExtractTextPlugin.extract({fallback: "style-loader",use: "css-loader"})},//scss文件处理{test: /\.scss$/,use: ExtractTextPlugin.extract({fallback: 'style-loader',//如果需要,可以在 sass-loader 之前将 resolve-url-loader 链接进来use: ['css-loader', 'sass-loader']})},//图片的配置{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的urlname: 'resource/[name].[ext]'//指定路径}}]},//字体的配置{test: /\.(woff|woff2|eot|ttf|otf|svg)$/,use: [{loader: 'url-loader',options: {limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的urlname: 'resource/[name].[ext]'//指定路径}}]}]},plugins: [//打包htmlnew HtmlWebpackPlugin({template: './src/index.html',//自定义html模版}),//打包cssnew ExtractTextPlugin("css/[name].css"),//提出公共模块new webpack.optimize.CommonsChunkPlugin({name: 'common',//自己定义的公共模块filename: 'js/base.js'//把通用的东西打包成一个js放到该目录下})],devServer: {port:8086,//默认端口//contentBase: './dist'//因为配置了publikPath:'/dist/',这里可以去掉},};
//因为没有全局安装webpack,所以用在命令行中通过node_modules/.bin/webpack命令打包
//Babel 是一个 JavaScript 编译器,这里安装了babel-core,babel-preset-env,babel-loader
//加载 CSS 安装模块 style-loader css-loader
//因为加载顺序,css要等前面所有js执行完才去解析,才去做样式渲染,所以要把css放到一个单独的文件里,在index.html,用style标签来引入
//用extract-text-webpack-plugin插件将所有.css独立到一个css文件中
//提取sass用到sass-loader,node-sass(这两个是一个依赖关系)
//url-loader依赖与file-loader

打包后的dist文件
在这里插入图片描述

  • Font Awesome 字体图标使用

安装

yarn add font-awesome

引入

import 'font-awesome/css/font-awesome.min.css'//引入font-awesome的css文件

4.第一次提交代码

  • 代码提交过程
1.从master切换到开发分支上
2.git merge origin master,拉取远程仓库最新代码
3.git add .,追踪文件的变化
4.git commit -am “备注信息”,将代码提交到本地仓库
5.git push,本地仓库代码推送到远程仓库
6.提交pull request,管理员审核

过程

git diff //每一个文件的具体变化
git dt 新旧文件对比
git co -b dev1.0 //新建dev1.0分支
git b //查看当前所有分支和所在分支
git ca "初始化" //将代码提交到本地仓库
git push //本地仓库代码推送到远程仓库
//因为当前分支和远程没有对应的分支,所以要执行 
git push --set-upstream origin dev1.0

四、react查漏补缺

1.react组件

  • 容器式组件
1.类似于插槽,这种组件会接受父组件传来的JSX
2.children表示title标签包起来的部分
//子组件 Title
class Title extends React.Component{constructor(props){super(props)}render(){return(<h1>{this.props.children<h1>)}
}
//父组件
class App extends React.Component{constructor(props){super(props)}render(){return(<Title><span>APP</span><a href=''>link</a></Title>)}
}

2.react生命周期

–生命周期节点

Mounting:挂载阶段
Updating:运行时阶段
Unmounting:卸载阶段
Error Handling:错误处理
import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'class Zpp extends Component {//组件最先执行的构造函数constructor(props) {super(props)this.state = {data: 'old state'}console.log('初始化数据')}//组件将要加载componentWillMount() {console.log('componentWillMount')}//组件加载完成componentDidMount() {console.log('componentDidMount')}//将要接受父组件传来的propscomponentWillReceiveProps() {console.log('componentWillReceiveProps')}//组件是不是应该更新shouldComponentUpdate() {console.log('shouldComponentUpdate')return true //默认}//组件将要更新componentWillUpdate() {console.log('componentWillUpdate')}//组件更新完成componentDidUpdate() {console.log('componentDidUpdate')}//组件将要销毁componentWillUnmount(){console.log('componentWillUnmount')}//点击事件buttonClick() {console.log('更新')this.setState({ data: 'new state' })}//渲染render() {console.log('render')return (<div><div>{this.state.data}</div><button onClick={() => { this.buttonClick() }}>更新组件</button></div>)}
}class App extends Component {constructor(props) {super()this.state = {data: 'old props',zppShow: true}}//改变propsonPropsChange() {console.log('父组件传来新的props')this.setState({ data: 'new props' })}//销毁组件zppShowChange(){console.log('销毁组件')this.setState({ zppShow: false })}render() {return (<div>{this.state.zppShow && <Zpp data={this.state.data} />}<div>{this.state.data}</div><button onClick={() => { this.onPropsChange() }}>改变Props</button><button onClick={() => { this.zppShowChange() }}>销毁组件</button></div>)}
}ReactDOM.render(<App />,document.getElementById('app')
)

刚进入页面
在这里插入图片描述
点击更新组件(state发生改变)
在这里插入图片描述
点击‘改变 props’(props发生改变)
在这里插入图片描述
点击销毁组件
在这里插入图片描述

3.React-Router

  • 常见router
页面router
Hash Router
H5 Router
//页面路由
window.location.href = 'http://www.baidu.com'
history.back()
//hash路由
window.location = '#hash'
//监听hash
window.onhashchange = function(){console.log(window.location.hash)
}
//h5路由
//推进一个状态
history.pushState('name','title','/path')
//替换一个状态
history.replaceState('name','title','/path')
//popState 监听h5路由改变
window.onpopstate = function(){console.log(window.location.href)console.log(window.location.pathname)console.log(window.location.hash)console.log(window.location.search)
}
  • React-Router
<BrowserRouter>/<HashRouter>,路由方式
<Route>,路由规则
<Switch>,路由选项
<Link/>/<NavLink>,跳转导航
<Redirect>自动跳转
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom'class A extends Component {constructor(props) {super()}render() {return (<div>A<Switch><Routepath={`${this.props.match.path}/sub`}exact //完全匹配render={(route) => {return <div>当前组件是sub</div>}}/><Routepath={`${this.props.match.path}`}exact //完全匹配render={() => {return <div>当前是不带参数的组件A</div>}}/>{/* 把通配放最后面,防止和/a/sub冲突 */}<Routepath={`${this.props.match.path}/:id`}exact //完全匹配render={(route) => {return <div>当前是带参数的组件A,参数是{route.match.params.id}</div>}}/></Switch></div>)}
}class B extends Component {constructor(props) {super()this.state = {}}render() {return (<div>B</div>)}
}
//容器
class Wrapper extends Component {constructor(props) {super()this.state = {}}render() {return (<div><Link to='/a'>组件A</Link><br />{/* 带参数跳 */}<Link to='/a/123'>带参数的组件A</Link><br />{/* 带子路径跳 */}<Link to='/a/sub'>/a/sub</Link><br /><Link to='/b'>组件B</Link>{this.props.children}</div>)}
}ReactDOM.render(<Router><Wrapper><Route path='/a' component={A} /><Route path='/b' component={B} /></Wrapper></Router>,document.getElementById('app')
)

五、通用部分的开发

1.通用布局的开发

  • 入口文件 app.jsx
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Link, Redirect, Route, Switch } from 'react-router-dom'import Layout from 'component/layout/index.jsx'
//页面
import Home from 'page/home/index.jsx'
class App extends Component {constructor(props) {super()this.state = {}}render() {return (// Router只能含有一个子组件<Router><Layout>{/* Switch只匹配到第一个匹配的东西 */}<Switch><Route exact path="/" component={Home} />{/* 匹配不到,自动跳转/ */}<Redirect from="*" to="/" /></Switch></Layout></Router>)}
}ReactDOM.render(<App />,document.getElementById('app')
)
  • 入口文件 index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>react-business-admin</title><!-- 浏览器可能对https有安全限制,引入的cdn的协议改成http --><!-- bootstrap --><link href="http://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><!-- font-awesome --><link href="http://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head><body><div id="app"></div>
</body></html>
  • 页面布局
    component/layout/index.jsx
import React, { Component } from 'react';import NavTop from 'component/nav-top/index.jsx';
import NavSide from 'component/nav-side/index.jsx';
import './theme.css';export default class Layout extends Component {constructor(props) {super(props)}render() {return (<div id="wrapper"><NavTop /><NavSide />{this.props.children}</div>)}
}

theme.css(皮肤文件引入的整体样式)

/* 皮肤地址https://webthemez.com/demo/insight-free-bootstrap-html5-admin-template/ui-elements.html# */
/* 皮肤文件的css样式 */
body {font-family:'Open Sans',sans-serif;background:#f3f3f3;
}
#wrapper {width:100%;
}
#page-wrapper {padding:15px 15px;
}.text-center {text-align:center;
}
h1,.h1,h2,.h2,h3,.h3 {margin-top:7px;margin-bottom:-5px;
}
h2 {color:#000;
}
h4 {padding-top:10px;
}
.square-btn-adjust {border:0px solid transparent;-webkit-border-radius:0px;-moz-border-radius:0px;border-radius:0px;
}
p {font-size:16px;line-height:25px;padding-top:20px;
}
.panel {border-radius:0px;
}
.navbar-side .nav > li > a > i {color:#B5B5B5;padding:8px;width:30px;text-align:center;
}
.top-navbar {position:fixed;width:100%;z-index:300;-webkit-box-shadow:0 3px 3px rgba(0,0,0,0.05);-moz-box-shadow:0 3px 3px rgba(0,0,0,0.05);box-shadow:0 3px 3px rgba(0,0,0,0.05);
}
.navbar-side {z-index:1;position:fixed;width:260px;top:60px;bottom: 0px;overflow-y: auto;background-color: #2b2e33;
}
#page-wrapper {position:relative;top:55px;margin:0 0 0 260px;padding:15px 30px;
}
.top-navbar .nav > li > a:hover,.top-navbar .nav > li > a:focus {text-decoration:none;background-color:#2497BA;color:#fff;
}
.nav .open > a,.nav .open > a:hover,.nav .open > a:focus {background-color:#2497BA;border-color:#428bca;
}
.breadcrumb {padding:0;margin-bottom:20px;list-style:none;/* background-color:#FAFAFA;*/
border-radius:0;
}
/*----------------------------------------------DASHBOARD STYLES    
------------------------------------------------*/
.page-header {padding-bottom:9px;margin:10px 0 20px;border-bottom:1px solid transparent;
}
.panel-left {width:35%;height:158px;background:#5CB85C;
}
.panel-left .fa-5x {font-size:11em;color:rgba(255,255,255,0.15);padding:40px 0;margin-bottom:30px;
}
.panel-right {width:65%;height:158px;background:transparent;margin-bottom:0;color:#fff;
}
.panel-right h3 {font-size:50px;padding:31px 10px 13px;color:rgba(255,255,255,0.96);
}
.panel-back {background-color:#fff;
}
.panel-default {border-color:#ECECEC;
}
.panel-default > .panel-heading {color:#000;border-color:#FFF;font-weight:bold;background:#FFFFFF;font-size:16px;
}
.panel-heading {padding:15px 15px 0px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px;
}
.jumbotron,.well {background:#fff;
}
.noti-box {min-height:100px;padding:20px;
}
.noti-box .icon-box {display:block;float:left;margin:0 15px 10px 0;width:70px;height:70px;line-height:75px;vertical-align:middle;text-align:center;font-size:40px;
}
.text-box p {margin:0 0 3px;
}
.main-text {font-size:25px;font-weight:600;
}
.set-icon {-webkit-border-radius:50px;-moz-border-radius:50px;border-radius:50px;
}
.panel-primary {display:inline-block;margin-bottom:30px;width:100%;
}
.green {background-color:#5cb85c;color:#fff;
}
.blue {background-color:#4CB1CF;color:#fff
}
.red {background-color:#F0433D;color:#fff;
}
.brown {background-color:#f0ad4e;color:#fff;
}
.back-footer-red {background-color:#F0433D;color:#fff;border-top:0px solid #fff;
}
.icon-box-right {display:block;float:right;margin:0 15px 10px 0;width:70px;height:70px;line-height:75px;vertical-align:middle;text-align:center;font-size:40px;
}
.main-temp-back {background:#8702A8;color:#FFFFFF;font-size:16px;font-weight:300;text-align:center;
}
.main-temp-back .text-temp {font-size:40px;
}
.back-dash {padding:20px;font-size:20px;font-weight:500;-webkit-border-radius:0px;-moz-border-radius:0px;border-radius:0px;background-color:#2EA7EB;color:#fff;
}
.back-dash p {padding-top:16px;font-size:13px;color:#fff;line-height:25px;text-align:justify;
}
.color-bottom-txt {color:#000;font-size:16px;line-height:30px;
}
/*CHAT PANEL*/
/*Charts*/.main-chart {background:#fff;
}
.easypiechart-panel {text-align:center;padding:1px 0;margin-bottom:20px;
}
.placeholder h2 {margin-bottom:0px;
}
.donut {width:100%;
}
.easypiechart {position:relative;text-align:center;width:120px;height:120px;margin:20px auto 10px auto;
}
.easypiechart .percent {display:block;position:absolute;font-size:26px;top:38px;width:120px;
}
#easypiechart-blue .percent {color:#30a5ff;
}
#easypiechart-teal .percent {color:#1ebfae;
}
#easypiechart-orange .percent {color:#ffb53e;
}
#easypiechart-red .percent {color:#ef4040;
}
.chat-panel .panel-body {height:450px;overflow-y:scroll;
}
.chat-box {margin:0;padding:0;list-style:none;
}
.chat-box li {margin-bottom:15px;padding-bottom:5px;border-bottom:1px dotted #808080;
}
.chat-box li.left .chat-body {margin-left:90px;
}
.chat-box li .chat-body p {margin:0;color:#8d8888;
}
.chat-img>img {margin-left:20px;
}
footer p {font-size:14px;
}
/*----------------------------------------------MENU STYLES    
------------------------------------------------*/.user-image {margin:25px auto;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;max-height:170px;max-width:170px;
}
.top-navbar {margin:0px;
}
.top-navbar .navbar-brand {color:#fff;width:260px;text-align:left;height:60px;font-size:30px;font-weight:700;text-transform:uppercase;line-height:30px;background:#32323a;
}
.navbar-brand b {color:#2DAFCB;
}
.top-navbar .nav > li {position:relative;display:inline-block;margin:0px;padding:0px;
}
.top-navbar .nav > li > a {position:relative;display:block;padding:20px;color:#FFFFFF;margin:0px;
}
.top-navbar .nav > li > a:hover,.top-navbar .nav > li > a:focus {text-decoration:none;color:#319DB5 !important;background:transparent;
}
.top-navbar .dropdown-menu {min-width:230px;border-radius:0 0 4px 4px;
}
.top-navbar .dropdown-menu > li > a:hover,.top-navbar .dropdown-menu > li > a:focus {color:#225081;background:none;
}
.dropdown-tasks {width:255px;
}
.dropdown-tasks .progress {height:8px;margin-bottom:8px;overflow:hidden;background-color:#f5f5f5;border-radius:0px;
}
.dropdown-tasks > li > a {padding:0px 15px;
}
.dropdown-tasks p {font-size:13px;line-height:21px;padding-top:4px;
}
.active-menu {background-color:#2DAFCB !important;color:#fff !important;
}
.active-menu i {color:#fff !important;
}
.arrow {float:right;margin-top:8px;
}
.fa.arrow:before {content:"\f104";
}
.active > a > .fa.arrow:before {content:"\f107";
}
.nav-second-level li,.nav-third-level li {border-bottom:none !important;
}
.nav-second-level li a {padding-left:37px;
}
.nav-third-level li a {padding-left:55px;
}
.sidebar-collapse,.sidebar-collapse .nav {background:none;
}
.sidebar-collapse .nav {padding:0;
}
.sidebar-collapse .nav > li > a {color:#B5B5B5;background:transparent;text-shadow:none;
}
.sidebar-collapse > .nav > li > a {padding:12px 10px;
}
.sidebar-collapse > .nav > li {border-bottom:1px solid rgba(107,108,109,0.19);
}
ul.nav.nav-second-level.collapse.in {background:#17191B;
}
.sidebar-collapse .nav > li > a:hover,.sidebar-collapse .nav > li > a:focus {outline:0;
}
.navbar-side {border:none;
}
.top-navbar {background:#fff;border-bottom:none;
}
.top-navbar .nav > li > a > i {margin-right:2px;
}
.top-navbar .navbar-brand:hover {color:#2DAFCB;background-color:rgb(43,46,51);
}
.dropdown-user li {margin:8px 0;
}
.navbar-default {border:0px solid black;
}
.navbar-header {background:transparent;
}
.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus {background-color:#B40101;
}
.navbar-default .navbar-toggle {border-color:#fff;
}
.navbar-default .navbar-toggle .icon-bar {background-color:#FFF;
}
.nav > li > a > i {margin-right:10px;color:#666;
}
/*----------------------------------------------UI ELEMENTS STYLES     
------------------------------------------------*/
.btn-circle {width:50px;height:50px;padding:6px 0;-webkit-border-radius:25px;-moz-border-radius:25px;border-radius:25px;text-align:center;font-size:12px;line-height:1.428571429;
}
/*----------------------------------------------MEDIA QUERIES     
------------------------------------------------*/@media(max-width:768px) {.top-navbar{position: relative;}.top-navbar .navbar-brand{width: 100%;}#page-wrapper {margin:0;top:0;padding:15px 30px;}.navbar-side {z-index:1;width:100%;top:0px;position: relative;}
}
  • 左侧导航 component/nav-side/index.jsx
import React, { Component } from 'react'export default class NavSide extends Component {render() {return (<div className="navbar-default navbar-side" role="navigation"><div className="sidebar-collapse"><ul className="nav" id="main-menu"><li><a className="active-menu" href="index.html"><i className="fa fa-dashboard"></i> Dashboard</a></li><li><a href="ui-elements.html"><i className="fa fa-desktop"></i> UI Elements</a></li><li><a href="chart.html"><i className="fa fa-bar-chart-o"></i> Charts</a></li><li><a href="tab-panel.html"><i className="fa fa-qrcode"></i> Tabs &amp; Panels</a></li><li><a href="table.html"><i className="fa fa-table"></i> Responsive Tables</a></li><li><a href="form.html"><i className="fa fa-edit"></i> Forms </a></li><li><a href="#"><i className="fa fa-sitemap"></i> Multi-Level Dropdown<span className="fa arrow"></span></a><ul className="nav nav-second-level collapse"><li><a href="#">Second Level Link</a></li><li><a href="#">Second Level Link</a></li><li><a href="#">Second Level Link<span className="fa arrow"></span></a><ul className="nav nav-third-level collapse"><li><a href="#">Third Level Link</a></li><li><a href="#">Third Level Link</a></li><li><a href="#">Third Level Link</a></li></ul></li></ul></li><li><a href="empty.html"><i className="fa fa-fw fa-file"></i> Empty Page</a></li></ul></div></div>)}
}
  • 顶部导航component/nav-top/index.jsx
import React, { Component } from 'react'export default class NavTop extends Component {render() {return (<div className="navbar navbar-default top-navbar" role="navigation"><div className="navbar-header"><a className="navbar-brand" href="index.html"><b>In</b>sight</a></div><ul className="nav navbar-top-links navbar-right"><li className="dropdown"><a className="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="false"><i className="fa fa-envelope fa-fw"></i> <i className="fa fa-caret-down"></i></a><ul className="dropdown-menu dropdown-messages"><li><a href="#"><div><strong>John Doe</strong><span className="pull-right text-muted"><em>Today</em></span></div><div>Lorem Ipsum has been the industry's standard dummy text ever since the 1500s...</div></a></li><li className="divider"></li><li><a href="#"><div><strong>John Smith</strong><span className="pull-right text-muted"><em>Yesterday</em></span></div><div>Lorem Ipsum has been the industry's standard dummy text ever since an kwilnw...</div></a></li><li className="divider"></li><li><a href="#"><div><strong>John Smith</strong><span className="pull-right text-muted"><em>Yesterday</em></span></div><div>Lorem Ipsum has been the industry's standard dummy text ever since the...</div></a></li><li className="divider"></li><li><a className="text-center" href="#"><strong>Read All Messages</strong><i className="fa fa-angle-right"></i></a></li></ul></li><li className="dropdown"><a className="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="false"><i className="fa fa-tasks fa-fw"></i> <i className="fa fa-caret-down"></i></a><ul className="dropdown-menu dropdown-tasks"><li><a href="#"><div><p><strong>Task 1</strong><span className="pull-right text-muted">60% Complete</span></p><div className="progress progress-striped active"><div className="progress-bar progress-bar-success" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"><span className="sr-only">60% Complete (success)</span></div></div></div></a></li><li className="divider"></li><li><a href="#"><div><p><strong>Task 2</strong><span className="pull-right text-muted">28% Complete</span></p><div className="progress progress-striped active"><div className="progress-bar progress-bar-info" role="progressbar" aria-valuenow="28" aria-valuemin="0" aria-valuemax="100"><span className="sr-only">28% Complete</span></div></div></div></a></li><li className="divider"></li><li><a href="#"><div><p><strong>Task 3</strong><span className="pull-right text-muted">60% Complete</span></p><div className="progress progress-striped active"><div className="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"><span className="sr-only">60% Complete (warning)</span></div></div></div></a></li><li className="divider"></li><li><a href="#"><div><p><strong>Task 4</strong><span className="pull-right text-muted">85% Complete</span></p><div className="progress progress-striped active"><div className="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="85" aria-valuemin="0" aria-valuemax="100"><span className="sr-only">85% Complete (danger)</span></div></div></div></a></li><li className="divider"></li><li><a className="text-center" href="#"><strong>See All Tasks</strong><i className="fa fa-angle-right"></i></a></li></ul></li><li className="dropdown"><a className="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="false"><i className="fa fa-bell fa-fw"></i> <i className="fa fa-caret-down"></i></a><ul className="dropdown-menu dropdown-alerts"><li><a href="#"><div><i className="fa fa-comment fa-fw"></i> New Comment<span className="pull-right text-muted small">4 min</span></div></a></li><li className="divider"></li><li><a href="#"><div><i className="fa fa-twitter fa-fw"></i> 3 New Followers<span className="pull-right text-muted small">12 min</span></div></a></li><li className="divider"></li><li><a href="#"><div><i className="fa fa-envelope fa-fw"></i> Message Sent<span className="pull-right text-muted small">4 min</span></div></a></li><li className="divider"></li><li><a href="#"><div><i className="fa fa-tasks fa-fw"></i> New Task<span className="pull-right text-muted small">4 min</span></div></a></li><li className="divider"></li><li><a href="#"><div><i className="fa fa-upload fa-fw"></i> Server Rebooted<span className="pull-right text-muted small">4 min</span></div></a></li><li className="divider"></li><li><a className="text-center" href="#"><strong>See All Alerts</strong><i className="fa fa-angle-right"></i></a></li></ul></li><li className="dropdown"><a className="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="false"><i className="fa fa-user fa-fw"></i> <i className="fa fa-caret-down"></i></a><ul className="dropdown-menu dropdown-user"><li><a href="#"><i className="fa fa-user fa-fw"></i> User Profile</a></li><li><a href="#"><i className="fa fa-gear fa-fw"></i> Settings</a></li><li className="divider"></li><li><a href="#"><i className="fa fa-sign-out fa-fw"></i> Logout</a></li></ul></li></ul></div>)}
}
  • 首页 page/home/index.jsx
import React, { Component } from 'react'export default class Home extends Component {render() {return (<div id="page-wrapper">首页</div>)}
}
  • webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');//html-webpack-plugin:创建html的插件
const ExtractTextPlugin = require("extract-text-webpack-plugin");//创建css的插件
const webpack = require('webpack');module.exports = {entry: './src/app.jsx',//项目入口文件//输出的文件output: {path: path.resolve(__dirname, 'dist'),//__dirname是当前目录publicPath: '/dist/',filename: 'js/app.js'},resolve: {//处理别名,省去写层级alias: {page: path.resolve(__dirname, 'src/page'),component: path.resolve(__dirname, 'src/component'),}},module: {rules: [//es6,react语法处理{test: /\.jsx$/,exclude: /(node_modules)/,//不做处理的文件use: [{loader: 'babel-loader',options: {presets: ['env', 'react']//presets: ['env']的功能是自动根据环境来打包}}]},//css文件处理{test: /\.css$/,use: ExtractTextPlugin.extract({fallback: "style-loader",use: "css-loader"})},//scss文件处理{test: /\.scss$/,use: ExtractTextPlugin.extract({fallback: 'style-loader',//如果需要,可以在 sass-loader 之前将 resolve-url-loader 链接进来use: ['css-loader', 'sass-loader']})},//图片的配置{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的urlname: 'resource/[name].[ext]'//指定路径}}]},//字体的配置{test: /\.(woff|woff2|eot|ttf|otf|svg)$/,use: [{loader: 'url-loader',options: {limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的urlname: 'resource/[name].[ext]'//指定路径}}]}]},plugins: [//打包htmlnew HtmlWebpackPlugin({template: './src/index.html',//自定义html模版}),//打包cssnew ExtractTextPlugin("css/[name].css"),//提出公共模块new webpack.optimize.CommonsChunkPlugin({name: 'common',//自己定义的公共模块filename: 'js/base.js'//把通用的东西打包成一个js放到该目录下})],devServer: {//port:8086,//默认端口//contentBase: './dist'//因为配置了publikPath:'/dist/',这里可以去掉//404或找到不页面之后跳转的地方historyApiFallback: {index: '/dist/index.html'}},};

2.头部导航开发

头部导航component/nav-top/index.js

import React, { Component } from 'react'
import { Link } from 'react-router-dom'export default class NavTop extends Component {//退出登录onLogout() {}render() {return (<div className="navbar navbar-default top-navbar"><div className="navbar-header"><Link className="navbar-brand" to="/"><b>REACT</b>ADMIN</Link></div><ul className="nav navbar-top-links navbar-right"><li className="dropdown">{/* href="javascript:;"表示不执行js代码 */}<a className="dropdown-toggle" data-toggle="dropdown" href="javascript:;"><i className="fa fa-user fa-fw"></i><span>欢迎adminxxx</span><i className="fa fa-caret-down"></i></a><ul className="dropdown-menu dropdown-user"><li><a onClick={() => { this.onLogout() }}><i className="fa fa-sign-out fa-fw"></i><span>退出登录</span></a></li></ul></li></ul></div>)}
}

3.侧边导航开发

component/nav-side

import React, { Component } from 'react'
import { Link, NavLink } from 'react-router-dom'export default class NavSide extends Component {render() {return (<div className="navbar-default navbar-side"><div className="sidebar-collapse"><ul className="nav"><li>{/* 当页面路径和NavLink跳转的路径匹配时,就会加上动态加上一个class类activeClassName */}{/* exact表示必须完全匹配才加上activeClassName */}<NavLink exact activeClassName="active-menu" to="/"><i className="fa fa-dashboard"></i><span>首页</span></NavLink></li><li className="active"><Link to="/product"><i className="fa fa-sitemap"></i><span>商品</span><span className="fa arrow"></span></Link><ul className="nav nav-second-level collapse in"><li><NavLink to="/product" activeClassName="active-menu">商品管理</NavLink></li><li><NavLink to="/product-category" activeClassName="active-menu">品类管理</NavLink></li></ul></li><li className="active"><Link to="/order"><i className="fa fa-sitemap"></i><span>订单</span><span className="fa arrow"></span></Link><ul className="nav nav-second-level collapse in"><li><NavLink to="/order" activeClassName="active-menu">订单管理</NavLink></li></ul></li><li className="active"><Link to="/user"><i className="fa fa-sitemap"></i><span>用户</span><span className="fa arrow"></span></Link><ul className="nav nav-second-level collapse in"><li><NavLink to="/user" activeClassName="active-menu">用户管理</NavLink></li></ul></li></ul></div></div>)}
}

4.通用页面标题的开发

component/page-title

import React, { Component } from 'react'export default class PageTitle extends Component {constructor(props) {super(props)}componentWillMount() {// 设置浏览器titledocument.title = this.props.title + ' - REACTADMIN'}render() {return (<div className="row"><div className="col-md-12"><h1 className="page-header">{this.props.title}</h1>{this.props.children}</div></div>)}
}

首页
page/home

import React, { Component } from 'react'import PageTitle from 'component/page-title/index.jsx'export default class Home extends Component {render() {return (<div id="page-wrapper"><PageTitle title="首页"><button>test</button></PageTitle><div className="row"><div className="col-md-12">boby</div></div></div>)}
}

在这里插入图片描述

六、基础功能模块的开发

1.登录页面的开发

  • login页

page/login/index.jsx

import React, { Component } from 'react'
import MUtil from 'util/mm.js'
import User from 'service/user-service.js'
import './index.scss'const _mm = new MUtil()
const _user = new User()
export default class Login extends Component {constructor(props) {super(props)this.state = {username: '',password: '',redirect: _mm.getUrlparam('redirect') || '/',//跳转login页面的上一个路径}}componentWillMount() {document.title = '登录 - REACTADMIN'}//当输入框的值发生改变onInputChange(e) {let inputName = e.target.namelet inputValue = e.target.valuethis.setState({ [inputName]: inputValue })}//当用户提交表单onSubmit() {const { username, password, redirect } = this.statelet loginInfo = { username, password }//信息校验let checkResult = _user.checkLogiInfo(loginInfo)//验证通过if (checkResult.status) {_user.login(loginInfo).then((res) => {this.props.history.push(redirect)}, (errMsg) => {_mm.errorTips(errMsg)})} else {_mm.errorTips(checkResult.msg)}}//监听键盘回车键onInputKeyUp(e) {if (e.keyCode === 13) {this.onSubmit()}}render() {return (<div className="col-md-4 col-md-offset-4"><div className="panel panel-default login-panel"><div className="panel-heading">欢迎登录 - REACTADMIN</div><div className="panel-body"><div><div className="form-group"><inputtype="text"name="username"className="form-control"placeholder="用户名"onKeyUp={(e) => { this.onInputKeyUp(e) }}onChange={(e) => { this.onInputChange(e) }}/></div><div className="form-group"><inputtype="password"name="password"className="form-control"placeholder="密码"onKeyUp={(e) => { this.onInputKeyUp(e) }}onChange={(e) => { this.onInputChange(e) }}/></div><buttonclassName="btn btn-lg btn-primary btn-block"onClick={() => { this.onSubmit() }}>登录</button></div></div></div></div>)}
}

page/login/index.scss

.login-panel{margin-top: 25%;
}
  • 工具类

util/mm.js

//工具类
export default class MUtil {//请求方法封装request(param) {return new Promise((resolve, reject) => {$.ajax({type: param.type || 'get',url: param.url || '',dateType: param.dateType || 'json',data: param.data || null,success(res) {//数据请求成功if (res.status === 0) {typeof resolve === 'function' && resolve(res.data, res.msg)}//没有登录状态,强制登录else if (res.status === 10) {this.doLogin()}//数据请求失败else {typeof reject === 'function' && reject(res.msg || res.data)}},error(err) {typeof reject === 'function' && reject(err.statusText)}})})}//跳转登录doLogin() {//跳转回登录页,并记住从那个页面跳转回来//encodeURIComponent()处理特殊字符window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname)}//获取url参数getUrlparam(name) {let queryString = window.location.search.split('?')[1] || ''let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)")let result = queryString.match(reg)return result ? decodeURIComponent(result[2]) : null}//错误提示errorTips(errMsg) {alert(errMsg || '不对')}
};
  • 服务层(用户接口)
    service/user-service.js
import MUtil from 'util/mm.js'
const _mm = new MUtil()
export default class User {//用户登录login(loginInfo) {return _mm.request({type: 'post',url: '/manage/user/login.do',data: loginInfo})}//检查登录接口数据是否合法checkLogiInfo(loginInfo) {console.log(loginInfo)const { username, password } = loginInfoif (typeof username !== 'string' || username.length === 0) {return {status: false,msg: '用户名不能为空'}}if (typeof password !== 'string' || password.length === 0) {return {status: false,msg: '密码不能为空'}}return {status: true,msg: '验证通过'}}
}
  • webpack配置
    webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');//html-webpack-plugin:创建html的插件
const ExtractTextPlugin = require("extract-text-webpack-plugin");//创建css的插件
const webpack = require('webpack');module.exports = {entry: './src/app.jsx',//项目入口文件//输出的文件output: {path: path.resolve(__dirname, 'dist'),//__dirname是当前目录publicPath: '/dist/',filename: 'js/app.js'},resolve: {//处理别名,省去写层级alias: {page: path.resolve(__dirname, 'src/page'),component: path.resolve(__dirname, 'src/component'),util: path.resolve(__dirname, 'src/util'),service: path.resolve(__dirname, 'src/service'),}},module: {rules: [//es6,react语法处理{test: /\.jsx$/,exclude: /(node_modules)/,//不做处理的文件use: [{loader: 'babel-loader',options: {presets: ['env', 'react']//presets: ['env']的功能是自动根据环境来打包}}]},//css文件处理{test: /\.css$/,use: ExtractTextPlugin.extract({fallback: "style-loader",use: "css-loader"})},//scss文件处理{test: /\.scss$/,use: ExtractTextPlugin.extract({fallback: 'style-loader',//如果需要,可以在 sass-loader 之前将 resolve-url-loader 链接进来use: ['css-loader', 'sass-loader']})},//图片的配置{test: /\.(png|jpg|gif)$/,use: [{loader: 'url-loader',options: {limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的urlname: 'resource/[name].[ext]'//指定路径}}]},//字体的配置{test: /\.(woff|woff2|eot|ttf|otf|svg)$/,use: [{loader: 'url-loader',options: {limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的urlname: 'resource/[name].[ext]'//指定路径}}]}]},plugins: [//打包htmlnew HtmlWebpackPlugin({template: './src/index.html',//自定义html模版favicon: './favicon.ico'}),//打包cssnew ExtractTextPlugin("css/[name].css"),//提出公共模块new webpack.optimize.CommonsChunkPlugin({name: 'common',//自己定义的公共模块filename: 'js/base.js'//把通用的东西打包成一个js放到该目录下})],devServer: {//port:8086,//默认端口//contentBase: './dist'//因为配置了publikPath:'/dist/',这里可以去掉//404或找到不页面之后跳转的地方historyApiFallback: {index: '/dist/index.html'},// 接口代理(劫持)proxy: {//只要是/manage,都代理到指定的域名'/manage': {target: 'http://admintest.happymmall.com',changeOrigin: true,//伪装成target发出的请求}}},};

2.登录状态管理

  • 登录页

page/login/index.jsx

import React, { Component } from 'react'
import MUtil from 'util/mm.js'
import User from 'service/user-service.js'
import './index.scss'const _mm = new MUtil()
const _user = new User()
export default class Login extends Component {constructor(props) {super(props)this.state = {username: '',password: '',redirect: _mm.getUrlparam('redirect') || '/',//跳转login页面的上一个路径}}componentWillMount() {document.title = '登录 - REACTADMIN'}//当输入框的值发生改变onInputChange(e) {let inputName = e.target.namelet inputValue = e.target.valuethis.setState({ [inputName]: inputValue })}//当用户提交表单onSubmit() {const { username, password, redirect } = this.statelet loginInfo = { username, password }//信息校验let checkResult = _user.checkLogiInfo(loginInfo)//验证通过if (checkResult.status) {_user.login(loginInfo).then((res) => {_mm.setStorage('userInfo', res.data)this.props.history.push(redirect)}, (errMsg) => {_mm.errorTips(errMsg)})} else {_mm.errorTips(checkResult.msg)}}//监听键盘回车键onInputKeyUp(e) {if (e.keyCode === 13) {this.onSubmit()}}render() {return (<div className="col-md-4 col-md-offset-4"><div className="panel panel-default login-panel"><div className="panel-heading">欢迎登录 - REACTADMIN</div><div className="panel-body"><div><div className="form-group"><inputtype="text"name="username"className="form-control"placeholder="用户名"onKeyUp={(e) => { this.onInputKeyUp(e) }}onChange={(e) => { this.onInputChange(e) }}/></div><div className="form-group"><inputtype="password"name="password"className="form-control"placeholder="密码"onKeyUp={(e) => { this.onInputKeyUp(e) }}onChange={(e) => { this.onInputChange(e) }}/></div><buttonclassName="btn btn-lg btn-primary btn-block"onClick={() => { this.onSubmit() }}>登录</button></div></div></div></div>)}
}
  • 顶部导航
    component/nav-top/index.jsx
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import MUtil from 'util/mm.js'
import User from 'service/user-service.js'const _mm = new MUtil()
const _user = new User()
export default class NavTop extends Component {constructor(props) {super(props)this.state = {username: _mm.getStorage('userInfo').username || ''}}//退出登录onLogout() {_user.logout().then(res => {_mm.removeStorage('userInfo')window.location.href = '/login'// this.props.history.push('/login')}, errMsg => {_mm.errorTips(errMsg)})}render() {const { username } = this.statereturn (<div className="navbar navbar-default top-navbar"><div className="navbar-header"><Link className="navbar-brand" to="/"><b>REACT</b>ADMIN</Link></div><ul className="nav navbar-top-links navbar-right"><li className="dropdown">{/* href="javascript:;"表示不执行js代码 */}<a className="dropdown-toggle" data-toggle="dropdown" href="javascript:;"><i className="fa fa-user fa-fw"></i>{username? <span>{`欢迎,${username}`}</span>: <span>欢迎您</span>}<i className="fa fa-caret-down"></i></a><ul className="dropdown-menu dropdown-user"><li><a onClick={() => { this.onLogout() }}><i className="fa fa-sign-out fa-fw"></i><span>退出登录</span></a></li></ul></li></ul></div>)}
}
  • 用户接口请求
    service/user-service.js
import MUtil from 'util/mm.js'
const _mm = new MUtil()
export default class User {//用户登录login(loginInfo) {return _mm.request({type: 'post',url: '/manage/user/login.do',data: loginInfo})}//检查登录接口数据是否合法checkLogiInfo(loginInfo) {const { username, password } = loginInfoif (typeof username !== 'string' || username.length === 0) {return {status: false,msg: '用户名不能为空'}}if (typeof password !== 'string' || password.length === 0) {return {status: false,msg: '密码不能为空'}}return {status: true,msg: '验证通过'}}//退出登录logout() {return _mm.request({type: 'post',url: '/user/logout.do',})}
}
  • 工具类
    util/mm.js
//工具类
export default class MUtil {//请求方法封装request(param) {return new Promise((resolve, reject) => {$.ajax({type: param.type || 'get',url: param.url || '',dateType: param.dateType || 'json',data: param.data || null,success(res) {//数据请求成功if (res.status === 0) {typeof resolve === 'function' && resolve(res)}//没有登录状态,强制登录else if (res.status === 10) {this.doLogin()}//数据请求失败else {typeof reject === 'function' && reject(res.msg || res.data)}},error(err) {typeof reject === 'function' && reject(err.statusText)}})})}//跳转登录doLogin() {//跳转回登录页,并记住从那个页面跳转回来//encodeURIComponent()处理特殊字符window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname)}//获取url参数getUrlparam(name) {let queryString = window.location.search.split('?')[1] || ''let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)")let result = queryString.match(reg)return result ? decodeURIComponent(result[2]) : null}//错误提示errorTips(errMsg) {alert(errMsg || '不对')}//本地存储setStorage(name, data) {let dataType = typeof data//json类型if (dataType === 'object') {window.localStorage.setItem(name, JSON.stringify(data))}//基础类型else if (['number', 'string', 'boolean'].indexOf(dataType) >= 0) {window.localStorage.setItem(name, data)}else {alert('该类型不能用于本地存储')}}//取出本地存储getStorage(name) {let data = window.localStorage.getItem(name)if (data) {return JSON.parse(data)} else {return ''}}//删除本地存储removeStorage(name) {window.localStorage.removeItem(name)}
};

3.首页的开发

  • 首页
    page/home/index.jsx
import React, { Component } from 'react'
import PageTitle from 'component/page-title/index.jsx'
import { Link } from 'react-router-dom'
import MUtil from 'util/mm.js'
import Statistic from 'service/statistic-service.js'
import './index.scss'const _mm = new MUtil()
const _statistic = new Statistic()export default class Home extends Component {constructor(props) {super(props)this.state = {userCount: '--',productCount: '--',orderCount: '--',}}componentDidMount() {this.loadCount()}loadCount() {_statistic.getHomeCount().then(res => {this.setState(res.data)}, errMsg => {_mm.errorTips(errMsg)})}render() {const { userCount, productCount, orderCount } = this.statereturn (<div id="page-wrapper"><PageTitle title="首页" /><div className="row"><div className="col-md-4"><Link to='/user' className="color-box brown"><p className="count">{userCount}</p><p className="desc"><i className="fa fa-user-o"></i><span>用户总数</span></p></Link></div><div className="col-md-4"><Link to='/product' className="color-box green"><p className="count">{productCount}</p><p className="desc"><i className="fa fa-list"></i><span>商品总数</span></p></Link></div><div className="col-md-4"><Link to='/order' className="color-box blue"><p className="count">{orderCount}</p><p className="desc"><i className="fa fa-check-square-o"></i><span>订单总数</span></p></Link></div></div></div>)}
}

page/home/index.scss

.color-box{display: block;height: 160px;text-align: center;padding: 20px 0;opacity: .9;transition: all 0.3;&:hover{text-decoration: none;color: #555;opacity: 1;transform: scale(1.08);}&:focus{text-decoration: none;}.count{font-size: 50px;height: 80px;line-height: 80px;}.desc{font-size: 18px;.fa{margin-right: 5px;}}
}
  • 首页数据统计接口
    service/statistic-service.js
import MUtil from 'util/mm.js'
const _mm = new MUtil()
export default class Statistic {//统计用户、商品、订单数量接口getHomeCount() {return _mm.request({url: '/manage/statistic/base_count.do',})}}

在这里插入图片描述

4.错误页面开发

  • 错误页

page/error/index.jsx

import React, { Component } from 'react'
import PageTitle from 'component/page-title/index.jsx'
import { Link } from 'react-router-dom'
export default class Error extends Component {render() {return (<div id="page-wrapper"><PageTitle title="出错啦!" /><div className="row"><div className="col-md-12"><span>找不到该路径,</span><Link to='/'>点我返回首页</Link></div></div></div>)}
}
  • 路由
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Link, Redirect, Route, Switch } from 'react-router-dom'import Layout from 'component/layout/index.jsx'
//页面
import Home from 'page/home/index.jsx'
import Login from 'page/login/index.jsx'
import ErrorPage from 'page/error/index.jsx'
class App extends Component {constructor(props) {super()this.state = {}}render() {return (// Router只能含有一个子组件<Router><Switch>{/* Switch只匹配到第一个匹配的东西 */}<Route path="/login" component={Login} /><Route path="/" render={props => (<Layout><Switch><Route exact path="/" component={Home} />{/* 匹配不到,自动跳转/ */}{/* <Redirect from="*" to="/" /> */}<Route exact path="/product" component={Home} /><Route exact path="/product-category" component={Home} /><Route component={ErrorPage} /></Switch></Layout>)} /></Switch></Router>)}
}ReactDOM.render(<App />,document.getElementById('app')
)

在这里插入图片描述

这篇关于React16+React-Router4 从零打造企业级电商后台管理系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/516174

相关文章

前端如何通过nginx访问本地端口

《前端如何通过nginx访问本地端口》:本文主要介绍前端如何通过nginx访问本地端口的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、nginx安装1、下载(1)下载地址(2)系统选择(3)版本选择2、安装部署(1)解压(2)配置文件修改(3)启动(4)

HTML中meta标签的常见使用案例(示例详解)

《HTML中meta标签的常见使用案例(示例详解)》HTMLmeta标签用于提供文档元数据,涵盖字符编码、SEO优化、社交媒体集成、移动设备适配、浏览器控制及安全隐私设置,优化页面显示与搜索引擎索引... 目录html中meta标签的常见使用案例一、基础功能二、搜索引擎优化(seo)三、社交媒体集成四、移动

HTML input 标签示例详解

《HTMLinput标签示例详解》input标签主要用于接收用户的输入,随type属性值的不同,变换其具体功能,本文通过实例图文并茂的形式给大家介绍HTMLinput标签,感兴趣的朋友一... 目录通用属性输入框单行文本输入框 text密码输入框 password数字输入框 number电子邮件输入编程框

HTML img标签和超链接标签详细介绍

《HTMLimg标签和超链接标签详细介绍》:本文主要介绍了HTML中img标签的使用,包括src属性(指定图片路径)、相对/绝对路径区别、alt替代文本、title提示、宽高控制及边框设置等,详细内容请阅读本文,希望能对你有所帮助... 目录img 标签src 属性alt 属性title 属性width/h

CSS3打造的现代交互式登录界面详细实现过程

《CSS3打造的现代交互式登录界面详细实现过程》本文介绍CSS3和jQuery在登录界面设计中的应用,涵盖动画、选择器、自定义字体及盒模型技术,提升界面美观与交互性,同时优化性能和可访问性,感兴趣的朋... 目录1. css3用户登录界面设计概述1.1 用户界面设计的重要性1.2 CSS3的新特性与优势1.

HTML5 中的<button>标签用法和特征

《HTML5中的<button>标签用法和特征》在HTML5中,button标签用于定义一个可点击的按钮,它是创建交互式网页的重要元素之一,本文将深入解析HTML5中的button标签,详细介绍其属... 目录引言<button> 标签的基本用法<button> 标签的属性typevaluedisabled

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

CSS place-items: center解析与用法详解

《CSSplace-items:center解析与用法详解》place-items:center;是一个强大的CSS简写属性,用于同时控制网格(Grid)和弹性盒(Flexbox)... place-items: center; 是一个强大的 css 简写属性,用于同时控制 网格(Grid) 和 弹性盒(F

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求