赞
踩
基于上期购物车基本UI渲染后的数据操作
本期实现对购物车的商品的数量加减,更新购物车,删除商品至购物车以及父子组件的数据双向同步。(删除购物车貌似接口有问题,正常调用,返回success和200,刷新购物车之后并没有实现删除)。
需要用到黑马智慧商城接口。地址:wiki - 智慧商城-实战项目 (apifox.com)
通过点击商品列表中的加或减按钮,实现对商品数量的加减操作。
上期讲过,使用@ObjectLink修饰的变量,他的值更改,能被检测到,并实时刷新UI。
那么我们只需要通过父组件传的值,对数量的加减操作就可以自动实现价格的变化了。
注意:如果商品的数量小于1或者大于库存量的话,则不能继续的加减。
代码:
- //减
- Text('-')
- .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
- .fontSize(30).fontColor(Color.Black).fontWeight(800)
- .onClick(()=>{
- if(this.arr.number-1 < 1){
- promptAction.showToast({message:'商品数量不能小于1哦~'})
- }else {
- this.arr.number--
- }
- })
- //加
- Text('+')
- .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
- .fontSize(30).fontColor(Color.Black).fontWeight(800)
- .onClick(()=>{
- if(this.arr.number+1 > this.arr.stock){
- promptAction.showToast({message:'购买的商品数量大于库存了哦~'})
- }else {
- this.arr.number++
- this.updateCartData(this.arr.id,this.arr.number)
- }
- })
当我们对商品的数量更新之后,应该要同步发送数据给后台,否则下次刷新还是以前的数据。
在子组件cartItem中,arr数组包含了商品的id, 数量等。所以直接发送请求。
代码:
- //更新购物车数量
- async updateCartData(gid,gnum){
- const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/update',{
- goodsId:gid,
- goodsNum:gnum,
- goodsSkuId:"0"//默认0
- },{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- promptAction.showToast({message:data.data.message})
- }
注意,子组件这里的token是根据父组件传递过来的。
当我们实现了第二步的操作之后,只是给后台发送了数据,但是我们UI并没有变化。
所以需要对UI进行刷新,我是通过将渲染UI的原数组清空,然后再重新赋值。
那么在子组件点击加减按钮时,需要对父组件的数组清空,并且重新赋值给父组件的数组,这里就需要进行父子双向数据同步。
需要在子组件用到@Link修饰器,那么父组件传值则需要使用$符传值。
详情请见官网@Link装饰器:父子双向同步 (openharmony.cn)
父组件向子组件传递数组代码:
- //给使用@Link修饰器的变量传值时,不需要用this,而是$。
- //list:UI渲染数组,index:循环中的位置索引,token:Token值。
- cartItem({list:$cartDataarr:item,index:index,token:this.token})
子组件代码:
- //商品数据
- @Link list:cartInfo[]
那么我们在第二步之后,将数组清空,重新获取购物车数据即可。
代码:
- //更新购物车数量
- async updateCartData(gid,gnum){
- const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/update',{
- goodsId:gid,
- goodsNum:gnum,
- goodsSkuId:"0"//默认0
- },{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- promptAction.showToast({message:data.data.message})
- //将数组清空
- this.list=[]
- //重新赋值
- this.getCartData()
- }
-
-
- //获取购物车数据
- async getCartData(){
- const data = await axios.get('https://smart-shop.itheima.net/index.php?s=/api/cart/list',{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- const len = data.data.data.list.length
- const con = data.data.data.list
- for (let i = 0; i < len; i++) {
- var item = new cartInfo(true,con[i].goods_id,con[i].goods.goods_images[0].preview_url,con[i].goods.goods_name,con[i].goods.goods_price_min,con[i].goods_num,con[i].goods.stock_total)
- this.list.push(item)
- }
- }
这里删除购物车有两种方式:
1.左滑单个商品,显示删除按钮,可以进行删除。
2.点击菜单编辑按钮,出现单选框,可以进行删除。
利用ListItem 自带的属性,左滑展示删除按钮。点击按钮即可实现删除商品。
删除商品之后也要更新购物车,即第三步方法。
注意:需要传递商品id。
给后台发请求代码:
- //删除购物车
- async deleteCart(id){
- const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/clear',{
- cartIds:[id]
- },{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- promptAction.showToast({message:data.data.message})
- }
点击事件进行了场景优化:作为平台方,当然不希望用户从购物车删除商品。
所以增加一个确认取消的提示框。
点击删除按钮的代码:
- //err:点击非提示框区域时的事件,res.index:两个按钮的索引,0:取消,1:确认
- Button('删除')
- .width('20%').type(ButtonType.Circle).backgroundColor(Color.Red)
- .onClick(()=>{
- promptAction.showDialog({message:'确定要删除该商品吗',
- buttons:[{text:'取消' ,color: '#000000'},{text:'确认' ,color: '#000000'}]},(err,res)=>{
- if(err){
- promptAction.showToast({message:'用户取消操作'})
- }else{
- if(res.index == 0){
- return
- }else{
- this.deleteCart(id)
- this.cartData = []
- this.getCartData()
- }
- }
- })
- })
当我们点击编辑按钮时,在每个商品列表的前面展示一个单选框,用户点击选择,就可以删除指定商品。我们可以用一个状态变量来是否展示编辑时的按钮。
- //是否删除状态
- @State isDelete:boolean=true
当点击编辑按钮的时候,其本身内容也需要变化。
- //编辑按钮组件
- Text(this.isDelete ? '编辑' : '返回')
- .fontSize(25).fontWeight(800)
- .onClick(()=>{
- this.isDelete = !this.isDelete
- })
同时底部结算栏需要展示按钮(删除)。
- if(this.isDelete){
- //底部
- Row(){
- Text('合计:').fontSize(20).margin({left:10})
- Text(this.cartData.reduce((acc,obj)=>acc+obj.number*obj.price,0).toString())
- .width('40%').fontSize(20).fontColor(Color.Red)
- Button('结算')
- .width('35%').backgroundColor(Color.Red).margin({left:10})
- .onClick(()=>{
- })
- }.width('100%').padding('2%').layoutWeight(1)
- }else {
- //底部
- Row(){
- Button('删除商品')
- .width('98%').backgroundColor(Color.Red).margin({left:10})
- .onClick(()=>{
- })
- }.width('100%').padding('2%').layoutWeight(1).justifyContent(FlexAlign.Center)
- }
子组件cartItem也需要根据状态是否展示单选框。
- //正常情况,不显示单选框,默认一个空白占位
- if(this.show){
- Text()
- .width('5%')
- }else {
- //删除情况:则展示单选框
- Radio({ value: 'Radio1', group: 'radioGroup' })
- .width('5%')
- .checked(this.arr.flag)
- .onChange((value: boolean) => {
- this.deleteIndex = this.index
- })
- }
同时还需要获取用户点击的是哪个单选框,则需要获取索引位置。
- //子组件:
- @Link deleteIndex:number//要删除额索引
- //父组件:
- @State Index:number = 0//删除的索引
- //父组件传值:
- cartItem({show:$isDelete,list:$cartData,deleteIndex:$Index,arr:item,index:index,token:this.token})
当用户点击了其中一个单选框的时候,父组件获得了其索引。
那么用户再点击删除按钮的时候,就可以进行对商品删除,删除完之后,需要复原状态。
代码:
- Button('删除商品')
- .width('98%').backgroundColor(Color.Red).margin({left:10})
- .onClick(()=>{
- //传递商品数组索引中的id值
- this.deleteCart(this.cartData[this.Index].id)
- this.cartData = []
- this.getCartData()
- //复原状态
- this.isDelete = !this.isDelete
- })
完整代码如下:大家可以自行优化。
父组件(cart):
- import axios from '@ohos/axios'
- import preferences from '@ohos.data.preferences'
- import { topContent } from '../components/topContent'
- import common from '@ohos.app.ability.common'
- import { cartInfo } from '../class/cartInfo'
- import promptAction from '@ohos.promptAction'
- import { cartItem } from '../components/cartItem'
- @Entry
- @Component
- struct Cart{
-
- //上下文
- context:common.UIAbilityContext
- //token
- @State token:string = ''
- //获取商品购物车信息
- @State cartData:cartInfo[]=[]
- //是否删除状态
- @State isDelete:boolean=true
- //删除的索引
- @State Index:number = 0
-
- async aboutToAppear(){
- const a = await preferences.getPreferences(this.context,"tokensss")
- const b = await a.get("tokensss","")
- this.token = b.toString()//将首选项中的Token值赋给变量
- this.getCartData()
- }
-
- //获取购物车列表数据
- async getCartData(){
- const data = await axios.get('https://smart-shop.itheima.net/index.php?s=/api/cart/list',{
- //头部参数
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- //定义数据的长度
- const len = data.data.data.list.length
- //简化
- const con = data.data.data.list
- //循环并赋值,将数据存储到数组中,cartInfo为一个类。
- for (let i = 0; i < len; i++) {
- var item = new cartInfo(false,con[i].goods_id,con[i].goods.goods_images[0].preview_url,con[i].goods.goods_name,con[i].goods.goods_price_min,con[i].goods_num,con[i].goods.stock_total)
- this.cartData.push(item)
- }
- }
-
- //删除购物车
- async deleteCart(id){
- const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/clear',{
- cartIds:[id]
- },{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- promptAction.showToast({message:data.data.message})
- }
-
- build(){
- Column(){
- //头部
- Row(){
- topContent({title:'购物车'})
- }.width('100%').padding('2%').layoutWeight(1)
- //菜单
- Row(){
- Text(){
- Span('共')
- Span(this.cartData.length.toString()).fontSize(25).fontColor(Color.Red)
- Span('种商品')
- }.fontSize(25)
- Text(this.isDelete ? '编辑' : '返回')
- .fontSize(25).fontWeight(800)
- .onClick(()=>{
- this.isDelete = !this.isDelete
- })
- }.width('100%').padding('2%').justifyContent(FlexAlign.SpaceBetween).layoutWeight(1)
- //商品列表
- List({space:5}){
- ForEach(this.cartData,(item:cartInfo,index:number)=>{
- ListItem(){
- //封装的商品子组件
- cartItem({show:$isDelete,list:$cartData,deleteIndex:$Index,arr:item,index:index,token:this.token})
- }.swipeAction({end:this.deleteCartShop(item.id)})
- })
- }.width('100%').layoutWeight(9).padding('2%')
-
- if(this.isDelete){
- //底部
- Row(){
- Text('合计:').fontSize(20).margin({left:10})
- Text(this.cartData.reduce((acc,obj)=>acc+obj.number*obj.price,0).toString())
- .width('40%').fontSize(20).fontColor(Color.Red)
- Button('结算')
- .width('35%').backgroundColor(Color.Red).margin({left:10})
- .onClick(()=>{
- })
- }.width('100%').padding('2%').layoutWeight(1)
- }else {
- //底部
- Row(){
- Button('删除商品')
- .width('98%').backgroundColor(Color.Red).margin({left:10})
- .onClick(()=>{
- this.deleteCart(this.cartData[this.Index].id)
- this.cartData = []
- this.getCartData()
- this.isDelete = !this.isDelete
- })
- }.width('100%').padding('2%').layoutWeight(1).justifyContent(FlexAlign.Center)
- }
-
- }
- .width('100%')
- .height('100%')
- }
-
-
- @Builder deleteCartShop(id:string){
- Row(){
- Button('删除')
- .width('20%').type(ButtonType.Circle).backgroundColor(Color.Red)
- .onClick(()=>{
- promptAction.showDialog({message:'确定要删除该商品吗',
- buttons:[{text:'取消' ,color: '#000000'},{text:'确认' ,color: '#000000'}]},(err,res)=>{
- if(err){
- promptAction.showToast({message:'用户取消操作'})
- }else{
- if(res.index == 0){
- return
- }else{
- this.deleteCart(id)
- this.cartData = []
- this.getCartData()
- }
- }
- })
- })
- }
- }
-
- }
子组件(cartItem):
- import promptAction from '@ohos.promptAction'
- import axios from '@ohos/axios'
- import { cartInfo } from '../class/cartInfo'
- @Component
- export struct cartItem{
-
- //是否展示
- @Link show:boolean
- //商品数据
- @Link list:cartInfo[]
- //要删除额索引
- @Link deleteIndex:number
- //foreach渲染过来的数据
- @ObjectLink arr:cartInfo
- //在list中的索引
- index:number = 0
- //token值
- token:string=''
-
- //更新购物车数量
- async updateCartData(gid,gnum){
- const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/update',{
- goodsId:gid,
- goodsNum:gnum,
- goodsSkuId:"0"//默认0
- },{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- promptAction.showToast({message:data.data.message})
- this.list=[]
- this.getCartData()
- }
-
- //获取购物车数据
- async getCartData(){
- const data = await axios.get('https://smart-shop.itheima.net/index.php?s=/api/cart/list',{
- headers:{
- ['Access-Token']:this.token,
- platform:'H5'
- }})
- const len = data.data.data.list.length
- const con = data.data.data.list
- for (let i = 0; i < len; i++) {
- var item = new cartInfo(true,con[i].goods_id,con[i].goods.goods_images[0].preview_url,con[i].goods.goods_name,con[i].goods.goods_price_min,con[i].goods_num,con[i].goods.stock_total)
- this.list.push(item)
- }
- }
-
- build(){
-
- Row({space:5}){
- //正常情况,不显示单选框,默认一个空白占位
- if(this.show){
- Text()
- .width('5%')
- }else {
- //删除情况:则展示单选框
- Radio({ value: 'Radio1', group: 'radioGroup' })
- .width('5%')
- .checked(this.arr.flag)
- .onChange((value: boolean) => {
- this.deleteIndex = this.index
- })
- }
- //商品图片
- Image(this.arr.url)
- .width('30%').objectFit(ImageFit.Fill)
- Column({space:10}){
- //商品名称
- Text(this.arr.name).fontSize(20).fontWeight(700)
- .width('85%').maxLines(3).textOverflow({overflow:TextOverflow.Ellipsis})
-
- Row({space:10}){
- //商品的总价
- Text((this.arr.price*this.arr.number).toString())
- .width('30%')
- .fontSize(22).fontColor(Color.Red)
- //占位符
- Blank()
- Text('-')
- .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
- .fontSize(30).fontColor(Color.Black).fontWeight(800)
- .onClick(()=>{
- if(this.arr.number-1 < 1){
- promptAction.showToast({message:'商品数量不能小于1哦~'})
- }else {
- this.arr.number--
- this.updateCartData(this.arr.id,this.arr.number)
- }
- })
- Text(this.arr.number.toString())
- .fontSize(25).fontWeight(FontWeight.Bold)
- Text('+')
- .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
- .fontSize(30).fontColor(Color.Black).fontWeight(800)
- .onClick(()=>{
- if(this.arr.number+1 > this.arr.stock){
- promptAction.showToast({message:'购买的商品数量大于库存了哦~'})
- }else {
- this.arr.number++
- this.updateCartData(this.arr.id,this.arr.number)
- }
- })
- }
- }.width('65%').height('100%').alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceEvenly)
- }.width('100%').height(180).border({width:2,color:"#ff480047"}).borderRadius(20)
- }
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。