React实现购物车
###购物车来啦
2020年疫情刚刚结束,好久没写React了,有点慌,为了不遗漏知识点,查漏补缺,写个简单的购物车,基本的功能有列表、新增、修改、删除
开发用的框架React+Antd+Koa+Mysql,开发工具hbuilder-x,上传代码gitee
好啦,开始写啦
1:新建一个文件夹,起名shopWeb,里面新建shop文件夹,存放前台所有代码,新建shopServer文件夹,存放后台接口api所有文件
2:先讲解前台部分吧,进入shop文件夹,安装项目,用官方提供的脚手架初始化一下
create-react-app shop
等待安装完成后,基本的脚手架就搭建完了
下面安装所有依赖
cnpm i antd axios react-router-dom babel-plugin-import --save
这个就是需要的安装包
这里antd最好用按需加载的方式动态引入css,我这里偷懒就不用了
3:要实现的效果基本如下图
我们先把路由定义好了在说,
src下面新建一个router文件夹,下面新建router.js
import React from 'react'
//导入路由所有需要的包 必须这样写 否则报错
import {BrowserRouter as Router,Link,Route} from 'react-router-dom';import './router.css';import Shop from '../page/shop.js';
import User from '../page/user.js'import Add from '../page/add.js';
import Edit from '../page/edit.js'var router = ()=>{return(<Router><ul className="router"><li><Link to="/shoplist" >去购物车</Link></li><li><Link to="/user" >去个人中心</Link></li></ul><div className="linkTag"> <Link to="/shopAdd" className="add" >新增</Link><Link to="/shopEdit/66" className="edit">修改测试</Link></div><Route exact path='/shoplist' component={Shop}></Route><Route path='/user' component={User}></Route><Route path='/shopAdd' exact component={Add}></Route><Route path='/shopEdit/:id' exact component={Edit}></Route></Router>)}export default router;
先配置好路由才可以哦,这样才可以切换呀~~~
记得要在index.js里面引入这个路由js哦,否则无法生效哦
当然也可以直接在app.js中直接映入也是可以的
4:src下面新建page文件夹,存放所有页面js
新建一个shop.js这就是我们的主页面,列表页面
基本代码如下
import React,{Component} from 'react';
import { BrowserRouter as Router,Route,Link,withRouter} from 'react-router-dom'
import '../assets/page/shop.css';import axios from 'axios';
import { Table, Tag ,message,Modal,Button} from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons'//注意 setState({}) 我在这里浪费一个小时 = 导致浪费了1个小时找错误
const { confirm } = Modal;let api = "http://localhost:9090/";//import historyRouter from '../history/index.js';class Shop extends Component{constructor(props){super(props);this.state={isShow:false,username:"",list:[],columns: [{title: 'ID',dataIndex: 'id',key: 'id',}, {title: '名称',dataIndex: 'name',key: 'name',},{title: '价格',dataIndex: 'price',key: 'price'},{title: '数量',dataIndex: 'number',key: 'number'},{title: '操作',key: 'action',render: (text, record) => (<span className="actionBtn"><Button type="primary" className="btn editBtn" onClick={this.goEdit.bind(this,record)}>编辑</Button><Button type="primary" danger className="btn deleteBtn" onClick={this.deleteOne.bind(this,record)} >删除</Button></span>),}]};}//初始化查询列表async getData(){let res = await axios.get(api+"list");this.setState({list:res.data.data})}goEdit(event){let id = event.id;//let { history } = this.props;//history.push("/shopEdit/"+id);this.props.history.push({pathname:'/shopEdit/'+id});}//删除deleteOne(event){let id = event.id;var that = this;confirm({title: '确定删除这条记录么?',icon: <ExclamationCircleOutlined />,content: '删除后不可恢复哦~~~',onOk() {console.log('OK');axios.get(api+"delete/"+id).then(res=>{var result = res.data.data;if(res.data.code==1){message.success('删除成功');//再次查询that.getData();}else{message.success('删除失败');};});},onCancel() {console.log('Cancel');},});}//组件初始化componentDidMount(){this.getData();}componentWillReceiveProps(nextProps){if (nextProps.location.pathname != this.props.location.pathname) {this.getData();} }render(){return (<div><Table dataSource={this.state.list} columns={this.state.columns} /></div>)}}export default withRouter(Shop);
分享我遇到的坑:
1:antd的table展示,最好有key,不然会一直提醒你
2:列表跳转到Add路由时候,最好用路由,不要用变量显示隐藏,不然刷新就没有了,如果用BrowserRouter就需要后台配合
3:路由最好单独提出去写,否则会乱七八糟。
4:在点击修改的时候,路由跳转遇到问题,
this.props.history.push({pathname:'/shopEdit/'+id});
提示找不到props,最后用了withRouter才解决,当然也可以用别的办法,比如自定义history路由
—好了,接下来讲解新增吧,新建add.js
import React,{Component,useEffect,useState} from 'react'
import axios from 'axios'
import { Modal, Button,Input,message } from 'antd';
import '../assets/page/add.css';import { withRouter } from 'react-router-dom'class Add extends Component{constructor(props) {super(props);this.state={ModalText: 'Content of the modal',visible: true,confirmLoading: false,username:'',price:'',number:''}};showModal = () => {this.setState({visible: true});};handleOk = () => {console.log(this.username.value);console.log(this.price.value);console.log(this.number.value);let name = this.username.value;let price = this.price.value;let number= this.number.value;let key = null;if(!name){message.error('必须输入名称');return;}else if(!price){message.error('必须输入价格')return;}else if(!number){message.error('必须输入数量')return;}else{}let { history } = this.props//var that = this;let api = "http://localhost:9090/";
/* axios.get(api+"add?name="+name+"&price="+price+"&number="+number).then(res=>{let code = res.data.code;if(code ==1){message.success('添加成功');this.setState({ModalText: 'The modal will be closed after two seconds',confirmLoading: true,});setTimeout(() => {this.setState({visible: true,confirmLoading: false});window.location.href="/shoplist";//history.push({pathname: '/shoplist'})}, 2000);}});
*/ let postData={"name":name,"price":price,"number":number};axios.post(api+"add",postData).then(res=>{let code = res.data.code;if(code ==1){message.success('添加成功');this.setState({ModalText: 'The modal will be closed after two seconds',confirmLoading: true,});setTimeout(() => {this.setState({visible: true,confirmLoading: false});window.location.href="/shoplist";//history.push({pathname: '/shoplist'})}, 2000);}});};handleCancel = () => {console.log('Clicked cancel button');this.setState({visible: false});};render(){const { visible, confirmLoading, ModalText } = this.state;return(<div className="dialog"><Modaltitle="Title"visible={visible}onOk={this.handleOk}confirmLoading={confirmLoading}onCancel={this.handleCancel}><form><input type='text' placeholder="商品名称" defaultValue={this.state.username} ref={input => this.username = input} /><input type='text' placeholder="商品价格" defaultValue={this.state.price} ref={input => this.price = input} /><input type='text' placeholder="商品数量" defaultValue={this.state.number} ref={input => this.number = input} /></form></Modal></div>) }
}export default Add;
新增这里注意的是,表单的可控组件和不可控,注意要defaultValue的使用,获取值我用的是ref
接下来是修改,新建edit.js
代码如下
import React,{Component,useEffect,useState} from 'react'
import axios from 'axios'
import { Modal, Button,Input,message } from 'antd';
import '../assets/page/add.css';import { withRouter } from 'react-router-dom'class Edit extends Component{constructor(props) {super(props);this.state={sendId:0,ModalText: 'Content of the modal',visible: true,confirmLoading: false,username:'',price:'',number:''}};async initGetData(){let api = "http://localhost:9090/";let sendId=this.props.match.params.id ;//query 传值//let {location} = this.props;//let {id} =location.query;let res = await axios.get(api+"select/"+sendId);if(res.data.code ==1){let data = res.data.data[0];let name = data.name;let price = data.price;let number= data.number;this.setState({sendId:sendId,username:name,price:price,number:number})};}componentDidMount(){this.initGetData()}showModal = () => {this.setState({visible: true});};handleOk = () => {let name = this.state.username;let price = this.state.price;let number= this.state.number;let key = null;if(!name){message.error('必须输入名称');return;}else if(!price){message.error('必须输入价格')return;}else if(!number){message.error('必须输入数量')return;}else{}console.log(name,price,number);let { history } = this.props//var that = this;let api = "http://localhost:9090/";
/* axios.get(api+"add?name="+name+"&price="+price+"&number="+number).then(res=>{let code = res.data.code;if(code ==1){message.success('添加成功');this.setState({ModalText: 'The modal will be closed after two seconds',confirmLoading: true,});setTimeout(() => {this.setState({visible: true,confirmLoading: false});window.location.href="/shoplist";//history.push({pathname: '/shoplist'})}, 2000);}});
*/ let postData={"id":this.state.sendId * 1,"name":name,"price":price,"number":number,"mark":Math.floor(Math.random()*100)};axios.post(api+"update",postData).then(res=>{let code = res.data.code;if(code ==1){message.success('修改成功');this.setState({ModalText: 'The modal will be closed after two seconds',confirmLoading: true,});setTimeout(() => {this.setState({visible: true,confirmLoading: false});history.push({pathname: '/shoplist'})}, 2000);}});};handleCancel = () => {console.log('Clicked cancel button');this.setState({visible: false});};//值改变输入changeValue(event){let name =event.target.name;let value =event.target.valuethis.setState({[name]:value})}render(){const { visible, confirmLoading, ModalText } = this.state;return(<div className="dialog"><Modaltitle="请输入内容"visible={visible}onOk={this.handleOk}confirmLoading={confirmLoading}onCancel={this.handleCancel}><form><Input placeholder="输入名称" value={this.state.username} name="username" onChange={this.changeValue.bind(this)} /><Input placeholder="输入价格" value={this.state.price} name="price" onChange={this.changeValue.bind(this)} /><Input placeholder="输入数量" value={this.state.number} name="number" onChange={this.changeValue.bind(this)} /></form></Modal></div>) }}export default Edit;
修改我用的是antd的Input组件,然后用的onChange结合value实现,初始化填值,输入改变值存储来实现的,这里为了方便用name,动态传递参数
<form><Input placeholder="输入名称" value={this.state.username} name="username" onChange={this.changeValue.bind(this)} /><Input placeholder="输入价格" value={this.state.price} name="price" onChange={this.changeValue.bind(this)} /><Input placeholder="输入数量" value={this.state.number} name="number" onChange={this.changeValue.bind(this)} /></form>
这里绑定事件,注意this的指向
-this指向绑定有3中方法哦
1:点击的时候绑定
onChange={this.changeValue.bind(this)}
2:构造器里面绑定
this.changeValue=this.changeValue.bind(this)
3:点击的时候箭头函数绑定
onChange={()=>this.changeValue}
好了,接着讲表单输入取值,这里name注意是动态的
//值改变输入changeValue(event){let name =event.target.name;let value =event.target.valuethis.setState({[name]:value})}
对啦,忘记讲解一下修改页面,如何动态路由传递参数啦
A:首先得再路由中定义一下呗,看图
这里用的params传递参数
B:然后再shop.js点击修改的时候,方法里面
当然也可以用简单写法
history.push("/shopEdit/"+id);
或者query也可以哦
this.props.history.push({pathname:'/shopEdit',query:{id:id}});
看你自己的喜欢啦~~~~
然后再edit.js里面就是获取路由传递过来的参数呗
如果是pamrms传递过来就是这么取值
let sendId=this.props.match.params.id ;
如果是query传递过来就这么取值
//query 传值let {location} = this.props;let {id} =location.query;
贴图说明
这里基本就完成啦
![](/assets/blank.gif)
然后就是获取id,请求后台接口,获取后台数据,填充到页面就好啦
#####现在来讲解后台啦
1:当然先安装mysql数据库啦,然后phpstudy管理工具
建立好shop表,字段如上,明白人一看就懂了吧~~~
2:进入shopServer文件夹,安装所有包
cnpm i koa koa-bodyparser koa-router koa2-cors mysql nodemon --save
说明一下:
nodemon是一个自动修改配置重启生效的工具很好用
koa2-cors是跨域会用到的,前端访问后端会遇到跨域问题
类似上面这样,等会儿我们来解决吧~~~
安装好所有依赖后,先写个接口试试呗
3:好就先列表接口呗
src下面新建index.js
//列表
router.get("/list",async (ctx)=>{ let results= await mysqlData.query();if(results.length > 0){ctx.body={"code":1,"msg":"ok","data":results}}else{ctx.body={"code":0,"msg":"fail","data":"获取失败"}}});
这里就是连接数据库,查询数据啦
这里连接数据库,启动端口9090
var mysql = require('mysql')
var mysqlConfig = require('../config/config.js')//列表查询query(){return new Promise((resolve,reject)=>{mysqlConfig.getConnection((err,connection)=>{const listSql = "select * from shop";connection.query(listSql,(err,results,fields)=>{if(err){throw err};resolve(results);})})}) }
启动后效果图
看到启动成功~~~~
虽然启动成功啦,但是前台访问后台跨域问题还没解决,怎么办了?
如果有webpack当然,跨域配置proxy,但是我们这里没有,我们用koa2-cors这个中间来处理下
var cors = require('koa2-cors');// 配置跨域
app.use(async (ctx, next) => {ctx.set('Access-Control-Allow-Headers', 'Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With')ctx.set('Access-Control-Allow-Origin', '*');ctx.set('Access-Control-Allow-Methods', 'PUT,DELETE,POST,GET');ctx.set('Access-Control-Allow-Credentials', true);ctx.set('Access-Control-Max-Age', 3600 * 24);await next();
})app.use(cors({// origin: function(ctx) {// if (ctx.url === '/test') {// return false;// }// return '*';// },origin:'http://127.0.0.1:3000',exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],maxAge: 5,credentials: true,allowMethods: ['GET', 'POST', 'DELETE'],allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}));app.use(router.routes()); /*启动路由*/
app.use(router.allowedMethods());app.listen("9090");
console.log('app started ...');
最重要的origin,这里我们的跨域就配置成功啦!!
查询接口完成,接下来就是删除mysql文件下index.js
//删除delete(id){return new Promise((resolve,reject)=>{mysqlConfig.getConnection((err,connection)=>{const deleteSql = "delete from shop where id =" +id;connection.query(deleteSql ,(err,results,fields)=>{if(err){throw err};console.log(results);resolve(results)})}) })}
删除调用
//删除
router.get("/delete/:id",async (ctx)=>{let id = ctx.params.id;let data2 = await mysqlData.delete(id);let affectedRows = data2.affectedRows;if(affectedRows==1){ctx.body={"code":1,"msg":"ok","data":"删除成功"}}else{ctx.body={"code":0,"msg":"fail","data":"删除失败"}}});
注意因为所有的数据库操作都是异步,所以我们必须用promise异步处理下,然后返回处理结果才可以
新增接口
//新增数据add(name,price,number,key){return new Promise((resolve,reject)=>{mysqlConfig.getConnection((err,connection)=>{const addSql = `insert into shop values(null,${name},${price},${number},${key},null)`;connection.query(addSql,(err,results, fields)=>{if(err){throw err}console.log("result", results)//表中数据resolve(results)connection.release()})});})}
新增调用
//用post请求
router.post("/add",async (ctx)=>{let body=ctx.request.body;let name=ctx.request.body.name;let price=ctx.request.body.price;let number=ctx.request.body.number;let key= Math.floor(Math.random()*100);//调取数据库插入方法let isSuccess = await mysqlData.add(name,price,number,key);let insertId = isSuccess.insertId;if(insertId){ctx.body={"code":1,"msg":"ok","data":"商品添加成功"}}else{ctx.body={"code":0,"msg":"fail","data":"商品失败"}}});
注意这里的新增是post提交请求,获取数据必须用到koa-bodyparser这个中间件才可以哦
修改前查询接口
//修改查询select(id){return new Promise((resolve,reject)=>{mysqlConfig.getConnection((err,connection)=>{let selectSql = 'select * from shop where id='+id;connection.query(selectSql,(err,results,fields)=>{if(err){throw err};//console.log(results)resolve(results);})})})}
修改前查询调用
//修改 先根据id查询结果 1
router.get("/select/:id",async (ctx)=>{let id = ctx.params.id;let result = await mysqlData.select(id);if(result.length > 0){ctx.body={"code":1,"msg":"ok","data":result}}else{ctx.body={"code":0,"msg":"faile","data":"查询失败"}}});
修改更新接口
//修改更新数据update(sendId,name,price,number,mark){return new Promise((resolve,reject)=>{mysqlConfig.getConnection((err,connection)=>{const updateSql = `update shop set name = "${name}",price = ${price},number = ${number},mark=${mark} where id = ${sendId}`;connection.query(updateSql,(err,results, fields)=>{if(err){throw err}console.log("result", results)//表中数据resolve(results)connection.release()})});})}
修改更新接口调用
router.post("/update",async (ctx)=>{//body中间件解析post提交数据let body=ctx.request.body;let sendId = ctx.request.body.id * 1 ;let name=ctx.request.body.name;let price=ctx.request.body.price;let number=ctx.request.body.number;let key= Math.floor(Math.random()*100);let mark= Math.floor(Math.random()*100);let isSuccess = await mysqlData.update(sendId,name,price,number,mark);if(isSuccess.affectedRows == 1){ctx.body={"code":1,"msg":"ok","data":"商品修改成功"}}else{ctx.body={"code":0,"msg":"fail","data":"商品修改失败"}}});
注意接口写完了,可以用postmon先测试下,注意我碰到过修改语句一直报错的问题
1:新增必须键名值对应,顺序很重要,null都可以,否则添加失败,也可以手动字段名称,防止出错,key很关键
const addSql = `insert into shop values(null,${name},${price},${number},${key},null)`;
2:修改更新时候,老是sql异常,找了很久百度才知道name只能是string类型才可以,单独加个“”就可以啦
const updateSql = `update shop set name = "${name}",price = ${price},number = ${number},mark=${mark} where id = ${sendId}`;
最后上几个效果图
新增
删除
修改
React实现购物车相关推荐
- React加购物车抛物线动画的实现
在做React的项目中,遇到了一个动画问题,在做加入购物车时,有个从指定位置向右上角的购物篮抛的动画. <!DOCTYPE html> <html lang="en&quo ...
- react+react-router 4.0+redux 构建购物车实战项目
前言 前些日子抽空学习了下react,因为近期忙着找工作,没时间写博客,今天我们就来看看用react全家桶,构建一个项目把,可能我学的也不是特别好,但是经过各种查资料,总算是能够构建出一个像模像样的栗 ...
- react 购物车组件
import React from 'react' import { Component } from 'react' import "./cart.css" // 纯显示的组件 ...
- React(二):jsx事件绑定、条件渲染、列表渲染、jsx的本质、购物车案例
React(二) 一.jsx事件绑定 1.this的绑定方式 2.jsx中绑定this的三种方式 3.事件对象和传参 (1)事件对象怎么传 (2)其他参数怎么传? 二.条件渲染 1.直接if-else ...
- React 函数式组件封装购物车(本文章对入门选手不是很友好)
分析说明: 注意:React 脚手架默认支持 sass,但是需要自己手动安装 sass 依赖包(用来解析 sass 语法)安装命令:yarn/npm add sass 步骤: 根据模板搭建基本页面结构 ...
- React组件设计之边界划分原则
简述 结合SOLID中的单一职责原则来进行组件的设计 Do one thing and do it well javaScript作为一个弱类型并在函数式和面对对象的领域里疯狂试探语言.SOLID原则 ...
- react 监听组合键_投资组合中需要的5个React项目
react 监听组合键 You've put in the work and now you have a solid understanding of the React library. 您已经完 ...
- React 深入系列3:Props 和 State
文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列3:Props 和 State React 深入系列,深入讲解了React中的重点概念.特性和模式 ...
- [转] React 是什么
用脚本进行DOM操作的代价很昂贵.有个贴切的比喻,把DOM和JavaScript各自想象为一个岛屿,它们之间用收费桥梁连接,js每次访问DOM,都要途径这座桥,并交纳"过桥费",访 ...
最新文章
- MySQL/MariaDB查询执行路径
- Eureka服务器端启动时报错:Connection refused :connect
- 机器学习导论(张志华):核定义
- UVA272--TEX Quotes【字符串】
- linux命令-tar命令
- bzoj 1121: [POI2008]激光发射器SZK
- Jackson2 json 转换Bean, Bean 里没有对应的值 jackson Un的解决方式
- 如何保障科技产品供应链的安全?
- 坚持努力,在黑暗中寻找光明——我的2014
- Hive 的SQL基本操作
- android plc,基于Android的智能PLC操控软件设计与实现
- 挽救婚姻从“心”开始
- 网易视频云互动直播公测正式启动
- 万马齐喑究可哀-中文编程的又一波讨论
- 【游戏介绍】aiwi体感balance
- 听java技术讲座心得体会_听讲座心得体会范文(精选3篇)
- 中国五十六个民族列表
- 命题逻辑在计算机中的作用,离散数学的命题逻辑.ppt
- GP技术的展望——先有鸿钧后有天
- zan php demo,zanphp源码解读 - 环境安装
热门文章
- (ENLCA)AAAI2022:Effificient Non-Local Contrastive Attention for Image Super-Resolution(ENLCA))
- TMD2771x光感和接近芯片的理解
- 教麦叔了解J-Link、ST-Link、ULink、JTAG、SWD、SWIM的区别
- Cocos Creator资源管理AssetManager细说一二
- Trucksim横纵坡场景搭建
- 《逻辑与计算机设计基础(原书第5版)》——2.12 习题
- 提升领导力(一)沟通技巧
- 《数据结构(信息管理)》
- python workspace_python报错汇总
- dhtmlxGantt 甘特图 一行展示多条数据