当前位置:   article > 正文

鸿蒙应用开发之购物车的数据操作_鸿蒙购物车动画

鸿蒙购物车动画

基于上期购物车基本UI渲染后的数据操作


前言

本期实现对购物车的商品的数量加减,更新购物车,删除商品至购物车以及父子组件的数据双向同步。(删除购物车貌似接口有问题,正常调用,返回success和200,刷新购物车之后并没有实现删除)。

 需要用到黑马智慧商城接口。地址:wiki - 智慧商城-实战项目 (apifox.com)


一、商品数量更新

通过点击商品列表中的加或减按钮,实现对商品数量的加减操作。

上期讲过,使用@ObjectLink修饰的变量,他的值更改,能被检测到,并实时刷新UI。

那么我们只需要通过父组件传的值,对数量的加减操作就可以自动实现价格的变化了。

注意:如果商品的数量小于1或者大于库存量的话,则不能继续的加减。

代码:

  1. //
  2. Text('-')
  3. .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
  4. .fontSize(30).fontColor(Color.Black).fontWeight(800)
  5. .onClick(()=>{
  6. if(this.arr.number-1 < 1){
  7. promptAction.showToast({message:'商品数量不能小于1哦~'})
  8. }else {
  9. this.arr.number--
  10. }
  11. })
  12. //
  13. Text('+')
  14. .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
  15. .fontSize(30).fontColor(Color.Black).fontWeight(800)
  16. .onClick(()=>{
  17. if(this.arr.number+1 > this.arr.stock){
  18. promptAction.showToast({message:'购买的商品数量大于库存了哦~'})
  19. }else {
  20. this.arr.number++
  21. this.updateCartData(this.arr.id,this.arr.number)
  22. }
  23. })

二、商品购物车更新

当我们对商品的数量更新之后,应该要同步发送数据给后台,否则下次刷新还是以前的数据。

在子组件cartItem中,arr数组包含了商品的id, 数量等。所以直接发送请求。

代码:

  1. //更新购物车数量
  2. async updateCartData(gid,gnum){
  3. const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/update',{
  4. goodsId:gid,
  5. goodsNum:gnum,
  6. goodsSkuId:"0"//默认0
  7. },{
  8. headers:{
  9. ['Access-Token']:this.token,
  10. platform:'H5'
  11. }})
  12. promptAction.showToast({message:data.data.message})
  13. }

注意,子组件这里的token是根据父组件传递过来的。


三 、刷新购物车界面

当我们实现了第二步的操作之后,只是给后台发送了数据,但是我们UI并没有变化。

所以需要对UI进行刷新,我是通过将渲染UI的原数组清空,然后再重新赋值。

那么在子组件点击加减按钮时,需要对父组件的数组清空,并且重新赋值给父组件的数组,这里就需要进行父子双向数据同步。

需要在子组件用到@Link修饰器,那么父组件传值则需要使用$符传值。

详情请见官网@Link装饰器:父子双向同步 (openharmony.cn)

父组件向子组件传递数组代码:

  1. //给使用@Link修饰器的变量传值时,不需要用this,而是$。
  2. //list:UI渲染数组,index:循环中的位置索引,token:Token值。
  3. cartItem({list:$cartDataarr:item,index:index,token:this.token})

子组件代码:

  1. //商品数据
  2. @Link list:cartInfo[]

 那么我们在第二步之后,将数组清空,重新获取购物车数据即可。

代码:

  1. //更新购物车数量
  2. async updateCartData(gid,gnum){
  3. const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/update',{
  4. goodsId:gid,
  5. goodsNum:gnum,
  6. goodsSkuId:"0"//默认0
  7. },{
  8. headers:{
  9. ['Access-Token']:this.token,
  10. platform:'H5'
  11. }})
  12. promptAction.showToast({message:data.data.message})
  13. //将数组清空
  14. this.list=[]
  15. //重新赋值
  16. this.getCartData()
  17. }
  18. //获取购物车数据
  19. async getCartData(){
  20. const data = await axios.get('https://smart-shop.itheima.net/index.php?s=/api/cart/list',{
  21. headers:{
  22. ['Access-Token']:this.token,
  23. platform:'H5'
  24. }})
  25. const len = data.data.data.list.length
  26. const con = data.data.data.list
  27. for (let i = 0; i < len; i++) {
  28. 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)
  29. this.list.push(item)
  30. }
  31. }

四 、删除购物车

这里删除购物车有两种方式:

1.左滑单个商品,显示删除按钮,可以进行删除。

2.点击菜单编辑按钮,出现单选框,可以进行删除。

1.左滑删除

利用ListItem 自带的属性,左滑展示删除按钮。点击按钮即可实现删除商品。

删除商品之后也要更新购物车,即第三步方法。

注意:需要传递商品id。

给后台发请求代码:

  1. //删除购物车
  2. async deleteCart(id){
  3. const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/clear',{
  4. cartIds:[id]
  5. },{
  6. headers:{
  7. ['Access-Token']:this.token,
  8. platform:'H5'
  9. }})
  10. promptAction.showToast({message:data.data.message})
  11. }

点击事件进行了场景优化:作为平台方,当然不希望用户从购物车删除商品。

所以增加一个确认取消的提示框。

点击删除按钮的代码:

  1. //err:点击非提示框区域时的事件,res.index:两个按钮的索引,0:取消,1:确认
  2. Button('删除')
  3. .width('20%').type(ButtonType.Circle).backgroundColor(Color.Red)
  4. .onClick(()=>{
  5. promptAction.showDialog({message:'确定要删除该商品吗',
  6. buttons:[{text:'取消' ,color: '#000000'},{text:'确认' ,color: '#000000'}]},(err,res)=>{
  7. if(err){
  8. promptAction.showToast({message:'用户取消操作'})
  9. }else{
  10. if(res.index == 0){
  11. return
  12. }else{
  13. this.deleteCart(id)
  14. this.cartData = []
  15. this.getCartData()
  16. }
  17. }
  18. })
  19. })

 2.编辑删除

当我们点击编辑按钮时,在每个商品列表的前面展示一个单选框,用户点击选择,就可以删除指定商品。我们可以用一个状态变量来是否展示编辑时的按钮。

  1. //是否删除状态
  2. @State isDelete:boolean=true

当点击编辑按钮的时候,其本身内容也需要变化。

  1. //编辑按钮组件
  2. Text(this.isDelete ? '编辑' : '返回')
  3. .fontSize(25).fontWeight(800)
  4. .onClick(()=>{
  5. this.isDelete = !this.isDelete
  6. })

 同时底部结算栏需要展示按钮(删除)。

  1. if(this.isDelete){
  2. //底部
  3. Row(){
  4. Text('合计:').fontSize(20).margin({left:10})
  5. Text(this.cartData.reduce((acc,obj)=>acc+obj.number*obj.price,0).toString())
  6. .width('40%').fontSize(20).fontColor(Color.Red)
  7. Button('结算')
  8. .width('35%').backgroundColor(Color.Red).margin({left:10})
  9. .onClick(()=>{
  10. })
  11. }.width('100%').padding('2%').layoutWeight(1)
  12. }else {
  13. //底部
  14. Row(){
  15. Button('删除商品')
  16. .width('98%').backgroundColor(Color.Red).margin({left:10})
  17. .onClick(()=>{
  18. })
  19. }.width('100%').padding('2%').layoutWeight(1).justifyContent(FlexAlign.Center)
  20. }

子组件cartItem也需要根据状态是否展示单选框。

  1. //正常情况,不显示单选框,默认一个空白占位
  2. if(this.show){
  3. Text()
  4. .width('5%')
  5. }else {
  6. //删除情况:则展示单选框
  7. Radio({ value: 'Radio1', group: 'radioGroup' })
  8. .width('5%')
  9. .checked(this.arr.flag)
  10. .onChange((value: boolean) => {
  11. this.deleteIndex = this.index
  12. })
  13. }

同时还需要获取用户点击的是哪个单选框,则需要获取索引位置。

  1. //子组件:
  2. @Link deleteIndex:number//要删除额索引
  3. //父组件:
  4. @State Index:number = 0//删除的索引
  5. //父组件传值:
  6. cartItem({show:$isDelete,list:$cartData,deleteIndex:$Index,arr:item,index:index,token:this.token})

当用户点击了其中一个单选框的时候,父组件获得了其索引。

那么用户再点击删除按钮的时候,就可以进行对商品删除,删除完之后,需要复原状态。

代码:

  1. Button('删除商品')
  2. .width('98%').backgroundColor(Color.Red).margin({left:10})
  3. .onClick(()=>{
  4. //传递商品数组索引中的id值
  5. this.deleteCart(this.cartData[this.Index].id)
  6. this.cartData = []
  7. this.getCartData()
  8. //复原状态
  9. this.isDelete = !this.isDelete
  10. })

总结

完整代码如下:大家可以自行优化。

父组件(cart):

  1. import axios from '@ohos/axios'
  2. import preferences from '@ohos.data.preferences'
  3. import { topContent } from '../components/topContent'
  4. import common from '@ohos.app.ability.common'
  5. import { cartInfo } from '../class/cartInfo'
  6. import promptAction from '@ohos.promptAction'
  7. import { cartItem } from '../components/cartItem'
  8. @Entry
  9. @Component
  10. struct Cart{
  11. //上下文
  12. context:common.UIAbilityContext
  13. //token
  14. @State token:string = ''
  15. //获取商品购物车信息
  16. @State cartData:cartInfo[]=[]
  17. //是否删除状态
  18. @State isDelete:boolean=true
  19. //删除的索引
  20. @State Index:number = 0
  21. async aboutToAppear(){
  22. const a = await preferences.getPreferences(this.context,"tokensss")
  23. const b = await a.get("tokensss","")
  24. this.token = b.toString()//将首选项中的Token值赋给变量
  25. this.getCartData()
  26. }
  27. //获取购物车列表数据
  28. async getCartData(){
  29. const data = await axios.get('https://smart-shop.itheima.net/index.php?s=/api/cart/list',{
  30. //头部参数
  31. headers:{
  32. ['Access-Token']:this.token,
  33. platform:'H5'
  34. }})
  35. //定义数据的长度
  36. const len = data.data.data.list.length
  37. //简化
  38. const con = data.data.data.list
  39. //循环并赋值,将数据存储到数组中,cartInfo为一个类。
  40. for (let i = 0; i < len; i++) {
  41. 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)
  42. this.cartData.push(item)
  43. }
  44. }
  45. //删除购物车
  46. async deleteCart(id){
  47. const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/clear',{
  48. cartIds:[id]
  49. },{
  50. headers:{
  51. ['Access-Token']:this.token,
  52. platform:'H5'
  53. }})
  54. promptAction.showToast({message:data.data.message})
  55. }
  56. build(){
  57. Column(){
  58. //头部
  59. Row(){
  60. topContent({title:'购物车'})
  61. }.width('100%').padding('2%').layoutWeight(1)
  62. //菜单
  63. Row(){
  64. Text(){
  65. Span('共')
  66. Span(this.cartData.length.toString()).fontSize(25).fontColor(Color.Red)
  67. Span('种商品')
  68. }.fontSize(25)
  69. Text(this.isDelete ? '编辑' : '返回')
  70. .fontSize(25).fontWeight(800)
  71. .onClick(()=>{
  72. this.isDelete = !this.isDelete
  73. })
  74. }.width('100%').padding('2%').justifyContent(FlexAlign.SpaceBetween).layoutWeight(1)
  75. //商品列表
  76. List({space:5}){
  77. ForEach(this.cartData,(item:cartInfo,index:number)=>{
  78. ListItem(){
  79. //封装的商品子组件
  80. cartItem({show:$isDelete,list:$cartData,deleteIndex:$Index,arr:item,index:index,token:this.token})
  81. }.swipeAction({end:this.deleteCartShop(item.id)})
  82. })
  83. }.width('100%').layoutWeight(9).padding('2%')
  84. if(this.isDelete){
  85. //底部
  86. Row(){
  87. Text('合计:').fontSize(20).margin({left:10})
  88. Text(this.cartData.reduce((acc,obj)=>acc+obj.number*obj.price,0).toString())
  89. .width('40%').fontSize(20).fontColor(Color.Red)
  90. Button('结算')
  91. .width('35%').backgroundColor(Color.Red).margin({left:10})
  92. .onClick(()=>{
  93. })
  94. }.width('100%').padding('2%').layoutWeight(1)
  95. }else {
  96. //底部
  97. Row(){
  98. Button('删除商品')
  99. .width('98%').backgroundColor(Color.Red).margin({left:10})
  100. .onClick(()=>{
  101. this.deleteCart(this.cartData[this.Index].id)
  102. this.cartData = []
  103. this.getCartData()
  104. this.isDelete = !this.isDelete
  105. })
  106. }.width('100%').padding('2%').layoutWeight(1).justifyContent(FlexAlign.Center)
  107. }
  108. }
  109. .width('100%')
  110. .height('100%')
  111. }
  112. @Builder deleteCartShop(id:string){
  113. Row(){
  114. Button('删除')
  115. .width('20%').type(ButtonType.Circle).backgroundColor(Color.Red)
  116. .onClick(()=>{
  117. promptAction.showDialog({message:'确定要删除该商品吗',
  118. buttons:[{text:'取消' ,color: '#000000'},{text:'确认' ,color: '#000000'}]},(err,res)=>{
  119. if(err){
  120. promptAction.showToast({message:'用户取消操作'})
  121. }else{
  122. if(res.index == 0){
  123. return
  124. }else{
  125. this.deleteCart(id)
  126. this.cartData = []
  127. this.getCartData()
  128. }
  129. }
  130. })
  131. })
  132. }
  133. }
  134. }

子组件(cartItem):

  1. import promptAction from '@ohos.promptAction'
  2. import axios from '@ohos/axios'
  3. import { cartInfo } from '../class/cartInfo'
  4. @Component
  5. export struct cartItem{
  6. //是否展示
  7. @Link show:boolean
  8. //商品数据
  9. @Link list:cartInfo[]
  10. //要删除额索引
  11. @Link deleteIndex:number
  12. //foreach渲染过来的数据
  13. @ObjectLink arr:cartInfo
  14. //在list中的索引
  15. index:number = 0
  16. //token值
  17. token:string=''
  18. //更新购物车数量
  19. async updateCartData(gid,gnum){
  20. const data = await axios.post('https://smart-shop.itheima.net/index.php?s=/api/cart/update',{
  21. goodsId:gid,
  22. goodsNum:gnum,
  23. goodsSkuId:"0"//默认0
  24. },{
  25. headers:{
  26. ['Access-Token']:this.token,
  27. platform:'H5'
  28. }})
  29. promptAction.showToast({message:data.data.message})
  30. this.list=[]
  31. this.getCartData()
  32. }
  33. //获取购物车数据
  34. async getCartData(){
  35. const data = await axios.get('https://smart-shop.itheima.net/index.php?s=/api/cart/list',{
  36. headers:{
  37. ['Access-Token']:this.token,
  38. platform:'H5'
  39. }})
  40. const len = data.data.data.list.length
  41. const con = data.data.data.list
  42. for (let i = 0; i < len; i++) {
  43. 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)
  44. this.list.push(item)
  45. }
  46. }
  47. build(){
  48. Row({space:5}){
  49. //正常情况,不显示单选框,默认一个空白占位
  50. if(this.show){
  51. Text()
  52. .width('5%')
  53. }else {
  54. //删除情况:则展示单选框
  55. Radio({ value: 'Radio1', group: 'radioGroup' })
  56. .width('5%')
  57. .checked(this.arr.flag)
  58. .onChange((value: boolean) => {
  59. this.deleteIndex = this.index
  60. })
  61. }
  62. //商品图片
  63. Image(this.arr.url)
  64. .width('30%').objectFit(ImageFit.Fill)
  65. Column({space:10}){
  66. //商品名称
  67. Text(this.arr.name).fontSize(20).fontWeight(700)
  68. .width('85%').maxLines(3).textOverflow({overflow:TextOverflow.Ellipsis})
  69. Row({space:10}){
  70. //商品的总价
  71. Text((this.arr.price*this.arr.number).toString())
  72. .width('30%')
  73. .fontSize(22).fontColor(Color.Red)
  74. //占位符
  75. Blank()
  76. Text('-')
  77. .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
  78. .fontSize(30).fontColor(Color.Black).fontWeight(800)
  79. .onClick(()=>{
  80. if(this.arr.number-1 < 1){
  81. promptAction.showToast({message:'商品数量不能小于1哦~'})
  82. }else {
  83. this.arr.number--
  84. this.updateCartData(this.arr.id,this.arr.number)
  85. }
  86. })
  87. Text(this.arr.number.toString())
  88. .fontSize(25).fontWeight(FontWeight.Bold)
  89. Text('+')
  90. .width('15%').border({width:1,color:Color.Orange}).textAlign(TextAlign.Center)
  91. .fontSize(30).fontColor(Color.Black).fontWeight(800)
  92. .onClick(()=>{
  93. if(this.arr.number+1 > this.arr.stock){
  94. promptAction.showToast({message:'购买的商品数量大于库存了哦~'})
  95. }else {
  96. this.arr.number++
  97. this.updateCartData(this.arr.id,this.arr.number)
  98. }
  99. })
  100. }
  101. }.width('65%').height('100%').alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceEvenly)
  102. }.width('100%').height(180).border({width:2,color:"#ff480047"}).borderRadius(20)
  103. }
  104. }
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号