微信小程序云开发实战:网上商城(四)

微信小程序云开发实战:网上商城(四)

前言

本节我们来实现商品展示页面。代码下载
商品展示

服务端代码实现

上节提到要生成上图所示的商品画面,那我们的商品比原来多需要两个属性:

  • 类别
  • 是否属于推荐商品

具体实现时,可以在具体商品属性页上添加这两个属性(recommend、type),也可以设置专门的属性管理页面。前者方便单个控制,后者方便统一配置。对于以前录入的商品,缺乏这两个属性,处理时默认类别为【未分类】,推荐属性为【false】

监控数据库变化

如果进入每个具体商品详细信息页面时都去云端获取商品的类别,就属于重复取数据。因此获取并保存商品类别信息,而且在管理端修改商品类别后更新,而这个商品类别信息以页面互动的方式传递出去。虽然我们也可以使用一个商品类别的全局变量,然后当商品类别有变化时更新它。但我不习惯使用全局变量,而且腾讯提供的云数据库的监视器更有趣。

对云数据库的监控可以参考官方文档。这里我为程序的配置项建立了一张表(虽然目前只有商品类别这一有意义的字段),然后注册一个监视器,当更新configs表的goodsTypes字段时,连带更新程序暂存的商品类比数据。

在这里插入图片描述
数据表权限设置:
在这里插入图片描述

    this.data._db = wx.cloud.database()
    this.data._watcher = this.data._db.collection('configs').where({
      openid : this.data._openid
    }).watch({
      onChange : function(snapshot){
        if(snapshot.docChanges[0].updatedFields != null 
          &&snapshot.docChanges[0].updatedFields.goodsTypes != null){
          var types = []
          snapshot.docChanges[0].updatedFields.goodsTypes.forEach(item =>{
            types.push(item.title)
          })
          self.data._goodsTypes = types
        }
      },
      onError : function(err){
        console.error("db watcher:", err)
      }
    })

商品属性修改

在这里插入图片描述
这里复用add-item页面,当加载页面的时候,判断新增、修改这两种情况。修改时,通过页面的事件通道传递商品信息。

在页面的信息展示几乎是与数据表字段一一对应的情况下,使页面数据变量与数据表字段的名称保持一致,将会给批量操作带来便利。这里面也许会有个别字段不需要展示,属于冗余数据,但是缺省去了挨个给展示元素赋值的机械化代码,如果页面元素较多,这种便利将会很可观。特别是现在WXML仍然不支持变量的路径绑定。

    if (options.op == "edit") {
      const eventChannel = this.getOpenerEventChannel()
      eventChannel.on('showItem', function (data) {
        var keys = Object.keys(data)
        for (var index = 0; index < keys.length; index++) {
          console.debug(keys[index], data[keys[index]])
          var key = keys[index]
          this.setData({
            [`${key}`]: data[keys[index]]
          })
        }
        this.setData({
          unitIndex: this.data._units.indexOf(data.unit),
          previewImageUrls: data.imgs,
          _editting: true,
          _oldItem: data,
          _deletedImgs: [],
          title: '修改' + data.name + '信息'
        })
      })
    } else {
      this.setData({
        previewImageUrls: [],//图片预览时需要的本地路径数组
      })
    }

给页面展示添加两个属性:

      <view class="weui-cell weui-cell_switch">
        <view class="weui-cell__bd">推荐</view>
        <view class="weui-cell__ft">
          <switch model:checked="{{recommend}}" disabled="!{{editable}}" />
        </view>
      </view>

      <mp-cell ext-class="weui-cell_select weui-cell_select-after">
        <view slot="title" class="weui-label">类别</view>
        <picker bindchange="bindTypeChange" model:value="{{_goodsType}}" range="{{_goodsTypes}}">
          <view class="weui-select"> {{type}}</view>
        </picker>
      </mp-cell>

判断商品信息是否发生了变化:
我最先设想的是追踪商品的所有属性变化,但用户可能修改到最后的值与初始值一致,而且实在太烦琐了。后来实施的做法是在页面加载时保存商品信息到某个变量,如_old_xxx,当发生了修改时,商品信息会更新到_new_xxx变量中对应的属性,在页面unload时,以_new_xxx这个对象的属性集合为基础对比这两个对象的相对应的属性值,如果不同,就确认某个属性最终发生了变化。从技术角度我认为这样处理较为妥当,但后来我认为从实用情况出发,将判断数据是否发生了变化以及是否提交的工作交给用户更为合适,即,只要用户点击“确认”(提交)按钮,程序就上传数据,不再去判断。

管理功能实现

在管理选项页添加两个功能入口:类别、推荐

在这里插入图片描述

本来想搞得跟win10的磁贴菜单似的,但本人没有美感,配色怎么弄都觉得难看,样式也看着别扭,先忍着吧。

在这里插入图片描述

商品类别管理

该功能采用离开管理页面时更新的方式(可以跟采用用户提交的方式对比一下),向左滑动选项会出现删除按钮。
在这里插入图片描述
虽然数据项看起来不多,但这个页面功能上增、删、改、查都涉及了。用户操作上没有限制,退出页面时,可能操作了多个数据项。这里采用的方法是:

  • 删除 只是隐藏数据项,记录下索引
  • 修改 原始数据保存在_old开头的变量中,供最终对比,也作为区别新增、修改的标识

在页面的unload函数中做的工作比较多,主要是图片要上传云存储,数据库更新成功、失败都要区别处理:

     var modifiedTypes = []
    var newTypes = []
    var deletedTypes = []

    console.debug("goodsTypes:", this.data.goodsTypes)

    var deletedImgs = [] //如果更新成功,删除项的图片要从云存储删除
    var uploadedImgs = [] //如果更新不成功,新上传的图片要删除
    var oldImgs = [] //如果更新成功,原有图片要删除

    for (var index = 0; index < this.data.goodsTypes.length; index++) {

      let self = this
      var item = this.data.goodsTypes[index]
      if (item.deleted) {//删除的项只需要索引
        deletedTypes.push(index)
        deletedImgs.push(this.data.goodsTypes[index].img) //保存要删除的图像列表
        continue
      }
      if (Object.keys(item).filter(key => key.startsWith("old_") ).length == 0
        && Object.keys(item).filter(key =>key.startsWith("new_") ).length == 0) {
        continue//忽略没有更改过
      }
      if ( (item.old_title == item.title && item.old_desc == item.desc)//忽略虽然有更改过程但结果与最初数据一致的
      || item.title == ''
      || item.title == null) { //忽略没有标题的
        continue
      }
      var type = {
        title: item.title,
        desc: item.desc,
        img: item.img,
        index : index
      }
      if (!item.img.startsWith("cloud://") || item.old_img != null) {
        wx.showLoading({
          title: '正在上传图片',
        })
        var ret = await wx.cloud.uploadFile({
          cloudPath: item.imgCloud,
          filePath: item.img,
        })
        if(ret.fileID != null){
          uploadedImgs.push(ret.fileID)
        }
        type.img = ret.fileID
        if (item.old_img != null) {
          oldImgs.push(item.old_img) 
        }
        wx.hideLoading({
          success: (res) => {},
        })
      }
      if (Object.keys(item).filter(key => key.startsWith("old_")).length != 0) {
        modifiedTypes.push(type)
      }
      else {
        delete type.index
        newTypes.push(type)
      }
    }
    if (modifiedTypes.length == 0
      && newTypes.length == 0
      && deletedTypes.length == 0) {
      console.debug("没有更改信息")
      return
    }

    wx.showLoading({
      title: '正在变更信息',
    })
    console.debug(modifiedTypes)
    var ret = await wx.cloud.callFunction({
      name: 'config',
      data: {
        cmd: "goodsTypes-set",
        data: {
          modified: modifiedTypes,
          added: newTypes,
          deleted: deletedTypes
        }
      }
    })
    wx.hideLoading({
      success: (res) => {},
    })
    console.debug(ret)
    var imgsTodelete = []
    if(ret.result.success ){
      imgsTodelete = deletedImgs.concat(oldImgs)      
    }else{
      imgsTodelete = uploadedImgs
    }
    if(imgsTodelete.length > 0){
      var ret = await wx.cloud.deleteFile({
        fileList: deletedImgs
      })
      console.debug("已删除:", ret.fileList)
    }

config云函数:

 case 'goodsTypes-set' :{
      var data = {
        openid : wxContext.OPENID,
        modified: request.data.modified,
        added : request.data.added,
        deleted : request.data.deleted
      }
      console.log("data:", data)  
      var ret = await col.where({
        openid : data.openid
      }).count()
      console.log("total:",ret.total)
      if(ret.total == 0){
        //新增配置
        console.log("data:", data)
        ret = await col.add({
          data:  {
            openid : data.openid,
            goodsTypes:  data.added
          }
        })
        console.log(ret)
        return {
          success :true,
          data : ret._id
        }
      }else{
        //更新类型列表
        ret = await col.field({
          goodsTypes : true
        }).where({
          openid : data.openid
        }).get()
        console.log(ret)
        var goodsTypes = ret.data[0].goodsTypes
        var finalTypes = []        
        if(data.modified.length !=0){
          for(var index = 0; index < data.modified.length; index++){
            goodsTypes[data.modified[index].index] = data.modified[index]
            delete goodsTypes[data.modified[index].index].index
          }
        }
        for(var index =0; index < goodsTypes.length; index++){
          if(data.deleted.findIndex(o => o == index) != -1){
            console.log("deleted:", index)
            continue
          }
          finalTypes.push(goodsTypes[index])
        }

        finalTypes = finalTypes.concat(data.added)
        console.log("finale types:", finalTypes)
        ret = await col.where({
          openid : data.openid
        }).update({
          data:{
            goodsTypes : finalTypes
          }
        })
        return {
          success : ret.stats.updated == 1
        }
      }
      break;
推荐商品管理

在这里插入图片描述

 var onList = []
    var offList = []
    for (var index = 0; index < this.data.goodsInfos.length; index++) {
      if (this.data.goodsInfos[index].recommend == this.data._old[index]) {
        continue
      }
      if (!this.data._old[index]) {
        onList.push(this.data.goodsInfos[index]._id)
      }
      else {
        offList.push(this.data.goodsInfos[index]._id)
      }
    }
    if(onList.length == 0 && offList.length == 0){
      return
    }
    wx.cloud.callFunction({
      name : 'goods-op',
      data :{
        cmd : 'update-recommend',
        onList : onList,
        offList : offList
      }
    }).then(res=>{
      console.debug(res)

    }).catch(err =>{
      console.error(err)
    })
    case 'update-recommend': {
      var onres = await collection.where({
        _id: _.in(request.onList)
      }).update({
        data: {
          recommend: true
        }
      })

      var offres = await collection.where({
        _id: _.in(request.offList)
      }).update({
        data: {
          recommend: false
        }
      })

客户端代码实现

在这里插入图片描述

为了实现左边的类别tab与商品网格化显示,我们需要引入几个新控件:
在这里插入图片描述
实际的商品类别中并没有“推荐”,也没有“未分类”,这两个类别选项卡是页面加载时人为处理出来的:

	this.data._goodsTypes.push({ title: "推荐" })
    //获取商品分类
    var ret = await wx.cloud.callFunction({
      name: 'config',
      data: {
        cmd: 'goodsTypes-get'
      }
    })
    var items = ret.result.data
    if (items != null) {
      self.data._goodsTypes = self.data._goodsTypes.concat(ret.result.data)
    }
    self.data._goodsTypes.push({ title: "未分类" })
    self.setData({
      goodsTypesForVtabs: self.data._goodsTypes.map(item => ({ title: item.title }))
    })
    this.data.total_price = 0
    wx.cloud.callFunction({
      name: 'goods-op',
      data: {
        cmd: 'get-recommend'
      }
    }).then(res => {
      if (res.result.success) {
        this.makeShowInfos(res.result.data)//生成展示需要的信息
      }
    })

展示商品详细信息

在这里插入图片描述

关于直接访问数据表的错误:
Unhandled promise rejection Error: document.get:fail document.get:fail cannot find document with _id xxxx, please make sure that the document exists and you have the corresponding access permission
如果出现类似上述的信息,通常是由于在客户端中直接访问云数据库时缺乏相关的权限造成的。只需要在云控制台中赋予非创建者访问权限即可。

商品详细信息展示页此处实现为组件,需要注意样式隔离设置:
在这里插入图片描述

参考下官方文档,styleIsolation 选项从基础库版本 2.6.5 开始支持。它支持以下取值:

  • isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);
  • apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;
  • shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件。(这个选项在插件中不可用。)

接下来

下一节我们将会处理购物车

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值