1.创建项目
create-react-app umall_app
2.清空工作
1.src删除除了App.js index.js 之外的所有的文件
2.App.js重置 (rfc)
3.index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
3.目录
//1.assets
import "./assets/css/reset.css"
import "./assets/js/rem"
//2.components 公共组件
// 3.pages 路由组件
// 4.filters 过滤器
// 5.utils/http.js ajax请求
// 6.store
// 7.UI 框架
// 8.stylus 样式
react中使用stylus
1.安装stylus 依赖包
npm i stylus stylus-loader --save
2.package.json react-scripts版本号更改
“react-scripts:"3.2.0"
3.git 提交本地仓库
git add .
git commit -m "123"
4.导出配置文件
npm run eject
5.配置stylus
reac使用stylus,先运行npm run eject,在wenpack.config.js文件,module.export->module->oneOf添加一下代码
{
test: /\.styl$/,
use: [
require.resolve('style-loader'),
require.resolve('css-loader'),
require.resolve('stylus-loader')
]
},
6.src/stylus
index.styl 整合css
color.styl color
size.styl 大小
form.styl form
4.一级路由
1.创建相关组件
pages 下创建 login register index list detail
2.安装依赖包
npm i react-router-dom --save
3.路由模式 src/index.js
//配置路由模式 HashRouter:hash模式 BrowserRouter:history模式
import {HashRouter,BrowserRouter} from "react-router-dom"
ReactDOM.render(
<HashRouter>
<App />
</HashRouter>
,
document.getElementById('root')
);
4.路由出口 App.js Switch
import {Switch} from "react-router-dom"
export default function App() {
return (
<div>
{/* 一级路由出口 */}
<Switch>
</Switch>
</div>
)
}
5.定义路由规则 App.js
import {Switch,Route,Redirect} from "react-router-dom"
<div>
{/* 一级路由出口 */}
<Switch>
<Route path="/login" component={Login}></Route>
<Route path="/register" component={Register}></Route>
<Route path="/index" component={Index}></Route>
<Route path="/detail" component={Detail}></Route>
<Route path="/list" component={List}></Route>
<Redirect to="/login"></Redirect>
</Switch>
</div>
5.封装了Header组件
1.pages/Header/Header.js
import React, { Component } from 'react'
import "./Header.styl"
export default class Header extends Component {
render() {
let { title, back, register } = this.props
return (
<div className="header">
{back ? <span className="header-back">返回</span> : null}
<div className="header-title">{title}</div>
{register ? <span className="header-register">注册</span> : null}
</div>
)
}
}
2.Header.styl
@import("../../stylus/index.styl");
.header
width: 100vw
height $headerHeight
position fixed
left 0
top: 0
background: $primary
.header-back
position absolute
left 0
top: 0
margin-left: $margin
font-size: $h2
color: $font-fff
line-height: $headerHeight
z-index 2
.header-title
width: 100vw
height: $headerHeight
text-align: center
line-height: $headerHeight
font-size: $h2
color: $font-fff
z-index: 1
.header-register
position absolute
right: 0
top: 0
margin-right $margin
font-size: $h2
color: $font-fff
line-height: $headerHeight
z-index: 2
6.Index.js
1.底部导航
render() {
return (
<div className="index">
{/* 二级路由出口 */}
<Switch>
<Route path="/index/home" component={Home}></Route>
<Route path="/index/cate" component={Cate}></Route>
<Route path="/index/shop" component={Shop}></Route>
<Route path="/index/mine" component={Mine}></Route>
<Redirect to="/index/home"></Redirect>
</Switch>
<footer className="index-footer">
{/* 底部导航,NavLink有activeClassName,Link没有 */}
<NavLink to="/index/home" activeClassName="select">首页</NavLink>
<NavLink to="/index/cate" activeClassName="select">分类</NavLink>
<NavLink to="/index/shop" activeClassName="select">购物车</NavLink>
<NavLink to="/index/mine" activeClassName="select">我的</NavLink>
</footer>
</div>
)
}
7.导航的方式
1.组件导航
import { Link, NavLink } from "react-router-dom"
<Link to="/index/shop">购物车</Link>
<NavLink to="/index/shop">购物车</NavLink>
注意:NavLink有activeClassName,Link没有
2.编程式导航
this.props.history.push() //添加新的历史记录
this.props.history.replace()//是用新的记录替换当前历史记录
this.props.history.go(-1);//返回
路由组件可以直接使用编程式导航,非路由组件需要通过设置withRouter()
import { withRouter } from "react-router-dom"
class Nav {
}
export default withRouter(Nav)
8.list->detail 问号传参
1.src/pages/home/components/list/list.js
<div className="list">
{
goods.map(item=>{
return (
<Link to={"/detail?id="+item.id} className="list-item" key={item.id}>
<h3>{item.name}</h3>
<div className="btn">立即抢购</div>
</Link>
)
})
}
</div>
2.detail.js 接收参数
import querystring from "querystring"
componentDidMount(){
let str=this.props.location.search;
//'?id=1&a=10&b=20&c=30'-->{id:"1",a:"10",b:"20",c:"30"}
let result=querystring.parse(str.slice(1))
console.log(result);
//ajax
}
9.axios
1.配置代理
package.json
{
"proxy":"http://localhost:3000"
}
2.安装
cnpm i axios qs --save
10.图片
1.assets/img
import logo from "../../../../assets/img/logo.jpg"
export default function Info() {
return (
<div>
<img src={logo} alt=""/>
</div>
)
}
注意:css不能用该图片。
如果要使用背景图,使用定位处理。
2.将图片放到服务器,使用网址
<img src="https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1301561917,1696120670&fm=26&gp=0.jpg" alt=""/>
这种路径可以作为背景图
3.vue中使用图片
<template>
<div>
<img :src="logo" alt=""/>
</div>
</template>
<script>
import logo from "../../../../assets/img/logo.jpg"
export default {
data(){
return {
logo:logo
}
}
</script>
11.注册
1.http.js
//注册
export const reqRegister = (user) => {
return axios({
url: "/api/register",
method: "post",
data: qs.stringify(user)
})
}
2.register.js
export default class Register extends Component {
constructor(){
super()
this.state={
user:{
phone:"",
nickname:"",
password:""
}
}
}
changeUser(e,key){
this.setState({
user:{
...this.state.user,
[key]:e.target.value
}
})
}
submit(){
reqRegister(this.state.user).then(res=>{
if(res.data.code===200){
successAlert(res.data.msg)
this.props.history.push("/login")
}
})
}
render() {
return (
<div>
<Header title="注册" back></Header>
<div>手机号:<input type="text" onChange={(e)=>this.changeUser(e,"phone")}/></div>
<div>昵称:<input type="text" onChange={(e)=>this.changeUser(e,"nickname")}/></div>
<div>密码:<input type="text" onChange={(e)=>this.changeUser(e,"password")}/></div>
<div className="btn" onClick={()=>this.submit()}>注册</div>
</div>
)
}
}
3.utils/alert.js
export const successAlert = (msg) => {
alert(msg)
}
4.统一处理失败请求 utils/http.js
axios.interceptors.response.use(res => {
console.log("本次请求地址:" + res.config.url);
console.log(res);
//统一处理失败
if (res.data.code !== 200) {
successAlert(res.data.msg)
}
return res
})
12.登录
1.http.js
//登录
export const reqLogin = (user) => {
return axios({
url: "/api/login",
method: "post",
data: qs.stringify(user)
})
}
2.login.js
export default class Login extends Component {
constructor(){
super()
this.state={
user:{
phone:"",
password:""
}
}
}
changeUser(e,key){
this.setState({
user:{
...this.state.user,
[key]:e.target.value
}
})
}
login(){
reqLogin(this.state.user).then(res=>{
if(res.data.code===200){
//弹成功
successAlert(res.data.msg)
//存用户信息
sessionStorage.setItem("userInfo",JSON.stringify(res.data.list))
//跳页面
this.props.history.push("/index")
}
})
}
render() {
return (
<div>
<Header title="登录" register></Header>
<div>
账号:<input type="text" onChange={(e)=>this.changeUser(e,"phone")}/>
</div>
<div>
密码:<input type="text" onChange={(e)=>this.changeUser(e,"password")}/>
</div>
<div className="btn" onClick={()=>this.login()}>登录</div>
<Link to="/index">大首页</Link>
<NavLink to="/index">大首页</NavLink>
</div>
)
}
}
13.轮播图
1.http.js
//首页商品banner
export const reqHomeBanner = () => {
return axios({
url: "/api/getbanner",
method: "get"
})
}
2.Home.js 初始化数据
this.state={
//1.轮播图
banner:[]
}
3.发请求
componentDidMount(){
// 2.发请求
reqHomeBanner().then(res=>{
this.setState({
banner:res.data.list
})
})
}
4.传递给banner组件
<Banner banner={banner}></Banner>
14.详情页
1.http.js
//商品详情
export const reqDetail = (id) => {
return axios({
url: "/api/getgoodsinfo",
method: "get",
params:{
id:id
}
})
}
2.detail.js
constructor() {
super()
//1.初始化
this.state = {
detail: {}
}
this.des = React.createRef()
}
3.发请求
componentDidMount() {
let str = this.props.location.search;//'?id=1&a=10&b=20&c=30'-->{id:"1",a:"10",b:"20",c:"30"}
let result = querystring.parse(str.slice(1))
//2.ajax
reqDetail(result.id).then(res => {
let list=res.data.list[0]
list.specsattr=JSON.parse(list.specsattr)
this.setState({
detail: list
},()=>{
console.log(this.state.detail);
this.des.current.innerHTML=this.state.detail.description
})
})
}
4.展示数据
render() {
let { detail } = this.state;
return (
<div>
<Header title="商品详情" back></Header>
{/* 图片 */}
<img src={detail.img} alt="" />
{/* 商品信息 */}
{detail.goodsname ? <Info detail={detail}></Info> : null}
{/* 商品描述 */}
<div ref={this.des}></div>
</div>
)
}
15.分类 Cate
1.静态页面
<div>
<Header title="分类"></Header>
<div className="cate">
<div className="left">
{
data.map((item, index) => {
return <div onClick={() => this.changeN(index)} className={index === n ? 'select' : ''} key={item.id}>{item.catename}</div>
})
}
</div>
<div className="right">
{
rightList.map(item => {
return (
<div className="item" key={item.id} onClick={()=>this.toList(item.catename,item.id)}>
<div className="con">
<img src={item.img} alt="" />
<div>{item.catename}</div>
</div>
</div>
)
})
}
</div>
</div>
</div>
2.http.js
//分类
export const reqCate = () => {
return axios({
url: "/api/getcatetree",
method: "get",
})
}
3.初始化数据
constructor() {
super()
this.state = {
data: [],
n: 0
}
}
4.发请求
componentDidMount() {
reqCate().then(res => {
this.setState({
data: res.data.list
})
})
}
5.页面展示
6.修改n
//修改n
changeN(index) {
this.setState({
n: index
})
}
7.跳转到list
//跳转到list
toList(name,id){
this.props.history.push("/list/"+name+"/"+id)
}
8.修改list的路由规则 (App.js)
<Route path="/list/:name/:id" component={List}></Route>
16.分类列表
1.http.js
//分类商品
export const reqCateGoods = (id) => {
return axios({
url: "/api/getgoods",
method: "get",
params:{
fid:id
}
})
}
2.list.js
import List from "../Home/components/List/List"
export default class BigList extends Component {
constructor() {
super()
this.state = {
list: []
}
}
componentDidMount() {
reqCateGoods(this.props.match.params.id).then(res => {
this.setState({
list: res.data.list
})
})
}
render() {
return (
<div className="list">
<Header back title={this.props.match.params.name}></Header>
<List goods={this.state.list}></List>
</div>
)
}
}
17.购物车
1.详情页 加入购物车
- detail.js弹框状态
this.state = {
detail: {},
//控制弹框的状态
isshow: false
}
2.点了 按钮,出现弹框
<div className="btn" onClick={() => this.show()}>加入购物车</div>
//弹框出现
show() {
this.setState({
isshow: true
})
}
{/* 弹框 */}
{detail.goodsname && isshow ? <Picker detail={detail} hide={() => this.hide()}></Picker> : null}
3.picker 点击加入购物车
//加入购物车
add(){
reqShopAdd({
uid:JSON.parse(sessionStorage.getItem("userInfo")).uid,
goodsid:this.props.detail.id,
num:1
}).then(res=>{
if(res.data.code===200){
successAlert(res.data.msg)
this.props.hide()
}
})
}
2.购物车 + - 删除 全选 反选 查看 shop.js 1-15
18.UI
ant design :PC端
https://ant.design/index-cn
ant design mobile :移动端
https://mobile.ant.design/index-cn
1.安装
npm install antd-mobile --save
2.public/index.html
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
if(!window.Promise) {
document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
}
</script>
3.src/index.js 入口文件引入样式
// 7.UI 框架
import 'antd-mobile/dist/antd-mobile.css';