文章目录
一、后台管理系统的需求分析和技术选型、接口文档规范
- 功能需求
· 商品管理 -> 添加商品/编辑商品,查看商品,下架
· 品类管理 -> 添加品类,查看品类
· 订单管理 -> 订单列表,订单详情、发货
· 用户管理 -> 管理员登录,用户列表
- 技术选型
语言和框架: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 & 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')
)