###购物车来啦
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;

贴图说明

这里基本就完成啦

然后就是获取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实现购物车相关推荐

  1. React加购物车抛物线动画的实现

    在做React的项目中,遇到了一个动画问题,在做加入购物车时,有个从指定位置向右上角的购物篮抛的动画. <!DOCTYPE html> <html lang="en&quo ...

  2. react+react-router 4.0+redux 构建购物车实战项目

    前言 前些日子抽空学习了下react,因为近期忙着找工作,没时间写博客,今天我们就来看看用react全家桶,构建一个项目把,可能我学的也不是特别好,但是经过各种查资料,总算是能够构建出一个像模像样的栗 ...

  3. react 购物车组件

    import React from 'react' import { Component } from 'react' import "./cart.css" // 纯显示的组件 ...

  4. React(二):jsx事件绑定、条件渲染、列表渲染、jsx的本质、购物车案例

    React(二) 一.jsx事件绑定 1.this的绑定方式 2.jsx中绑定this的三种方式 3.事件对象和传参 (1)事件对象怎么传 (2)其他参数怎么传? 二.条件渲染 1.直接if-else ...

  5. React 函数式组件封装购物车(本文章对入门选手不是很友好)

    分析说明: 注意:React 脚手架默认支持 sass,但是需要自己手动安装 sass 依赖包(用来解析 sass 语法)安装命令:yarn/npm add sass 步骤: 根据模板搭建基本页面结构 ...

  6. React组件设计之边界划分原则

    简述 结合SOLID中的单一职责原则来进行组件的设计 Do one thing and do it well javaScript作为一个弱类型并在函数式和面对对象的领域里疯狂试探语言.SOLID原则 ...

  7. react 监听组合键_投资组合中需要的5个React项目

    react 监听组合键 You've put in the work and now you have a solid understanding of the React library. 您已经完 ...

  8. React 深入系列3:Props 和 State

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列3:Props 和 State React 深入系列,深入讲解了React中的重点概念.特性和模式 ...

  9. [转] React 是什么

    用脚本进行DOM操作的代价很昂贵.有个贴切的比喻,把DOM和JavaScript各自想象为一个岛屿,它们之间用收费桥梁连接,js每次访问DOM,都要途径这座桥,并交纳"过桥费",访 ...

最新文章

  1. MySQL/MariaDB查询执行路径
  2. Eureka服务器端启动时报错:Connection refused :connect
  3. 机器学习导论(张志华):核定义
  4. UVA272--TEX Quotes【字符串】
  5. linux命令-tar命令
  6. bzoj 1121: [POI2008]激光发射器SZK
  7. Jackson2 json 转换Bean, Bean 里没有对应的值 jackson Un的解决方式
  8. 如何保障科技产品供应链的安全?
  9. 坚持努力,在黑暗中寻找光明——我的2014
  10. Hive 的SQL基本操作
  11. android plc,基于Android的智能PLC操控软件设计与实现
  12. 挽救婚姻从“心”开始
  13. 网易视频云互动直播公测正式启动
  14. 万马齐喑究可哀-中文编程的又一波讨论
  15. 【游戏介绍】aiwi体感balance
  16. 听java技术讲座心得体会_听讲座心得体会范文(精选3篇)
  17. 中国五十六个民族列表
  18. 命题逻辑在计算机中的作用,离散数学的命题逻辑.ppt
  19. GP技术的展望——先有鸿钧后有天
  20. zan php demo,zanphp源码解读 - 环境安装

热门文章

  1. (ENLCA)AAAI2022:Effificient Non-Local Contrastive Attention for Image Super-Resolution(ENLCA))
  2. TMD2771x光感和接近芯片的理解
  3. 教麦叔了解J-Link、ST-Link、ULink、JTAG、SWD、SWIM的区别
  4. Cocos Creator资源管理AssetManager细说一二
  5. Trucksim横纵坡场景搭建
  6. 《逻辑与计算机设计基础(原书第5版)》——2.12 习题
  7. 提升领导力(一)沟通技巧
  8. 《数据结构(信息管理)》
  9. python workspace_python报错汇总
  10. dhtmlxGantt 甘特图 一行展示多条数据