当前位置: 代码迷 >> 综合 >> 微信小程序云开发实战:网上商城(四)
  详细解决方案

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

热度:81   发布时间:2024-01-09 03:46:58.0

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

前言

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

服务端代码实现

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

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

具体实现时,可以在具体商品属性页上添加这两个属性(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 = thisvar 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.fileIDif (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.indexnewTypes.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].goodsTypesvar 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.dataif (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 = 0wx.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 的自定义组件。(这个选项在插件中不可用。)

接下来

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

在这里插入图片描述