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

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

  • 功能需求
· 商品管理 -> 添加商品/编辑商品,查看商品,下架
· 品类管理 -> 添加品类,查看品类
· 订单管理 -> 订单列表,订单详情、发货
· 用户管理 -> 管理员登录,用户列表
  • 技术选型
语言和框架: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);//1

if(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)//4

let arr = [1,2,3,4]
for(let i = 0, iLength = arr.length;i<iLength;i++){
}
console.log(i)//报错
 
 //不存在变量提升
 console.log(foo);//undefined
 var foo = 1;
 等价于
 var foo
 console.log(foo)
 foo = 1
 
 console.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=hezeying
	email=357261045@qq.com
[alias]
	co=checkout
	ci=commit
	st=status
	pl=pull
	ps=push
	dt=difftool
	ca=commit -am
	b=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的url
                            name: 'resource/[name].[ext]'//指定路径
                        }
                    }
                ]
            },
            //字体的配置
            {
                test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的url
                            name: 'resource/[name].[ext]'//指定路径
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        //打包html
        new HtmlWebpackPlugin(
            {
                template: './src/index.html',//自定义html模版
            }
        ),
        //打包css
        new 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')
    }
    //将要接受父组件传来的props
    componentWillReceiveProps() {
        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
        }
    }
    //改变props
    onPropsChange() {
        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>
                    <Route
                        path={`${this.props.match.path}/sub`}
                        exact //完全匹配
                        render={
                            (route) => {
                                return <div>当前组件是sub</div>
                            }
                        }
                    />
                    <Route
                        path={`${this.props.match.path}`}
                        exact //完全匹配
                        render={
                            () => {
                                return <div>当前是不带参数的组件A</div>
                            }
                        }
                    />
                    {/* 把通配放最后面,防止和/a/sub冲突 */}
                    <Route
                        path={`${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的url
                            name: 'resource/[name].[ext]'//指定路径
                        }
                    }
                ]
            },
            //字体的配置
            {
                test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的url
                            name: 'resource/[name].[ext]'//指定路径
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        //打包html
        new HtmlWebpackPlugin(
            {
                template: './src/index.html',//自定义html模版
            }
        ),
        //打包css
        new 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() {
        // 设置浏览器title
        document.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.name
        let inputValue = e.target.value
        this.setState({ [inputName]: inputValue })
    }
    //当用户提交表单
    onSubmit() {
        const { username, password, redirect } = this.state
        let 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">
                                <input
                                    type="text"
                                    name="username"
                                    className="form-control"
                                    placeholder="用户名"
                                    onKeyUp={(e) => { this.onInputKeyUp(e) }}
                                    onChange={(e) => { this.onInputChange(e) }}
                                />
                            </div>
                            <div className="form-group">
                                <input
                                    type="password"
                                    name="password"
                                    className="form-control"
                                    placeholder="密码"
                                    onKeyUp={(e) => { this.onInputKeyUp(e) }}
                                    onChange={(e) => { this.onInputChange(e) }}
                                />
                            </div>
                            <button
                                className="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 } = loginInfo
        if (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的url
                            name: 'resource/[name].[ext]'//指定路径
                        }
                    }
                ]
            },
            //字体的配置
            {
                test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192, //文件大于8k时候,才单独形成一个文件,否则是一个base64的url
                            name: 'resource/[name].[ext]'//指定路径
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        //打包html
        new HtmlWebpackPlugin(
            {
                template: './src/index.html',//自定义html模版
                favicon: './favicon.ico'
            }
        ),
        //打包css
        new 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.name
        let inputValue = e.target.value
        this.setState({ [inputName]: inputValue })
    }
    //当用户提交表单
    onSubmit() {
        const { username, password, redirect } = this.state
        let 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">
                                <input
                                    type="text"
                                    name="username"
                                    className="form-control"
                                    placeholder="用户名"
                                    onKeyUp={(e) => { this.onInputKeyUp(e) }}
                                    onChange={(e) => { this.onInputChange(e) }}
                                />
                            </div>
                            <div className="form-group">
                                <input
                                    type="password"
                                    name="password"
                                    className="form-control"
                                    placeholder="密码"
                                    onKeyUp={(e) => { this.onInputKeyUp(e) }}
                                    onChange={(e) => { this.onInputChange(e) }}
                                />
                            </div>
                            <button
                                className="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.state
        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>
                            {
                                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 } = loginInfo
        if (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.state
        return (
            <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')
)

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值