【uni-app】基于vite3+vue3.2+pinia+typeScript手写电商微信小程序( 微信开发者工具)【踩的那些坑】

vue3手写电商微信小程序( 微信开发者工具)【问题总结】

一、项目技术选型

1、该项目技术栈:

vite3 + vue3.2 + pinia + typeScript

2、基础框架:

cli创建的Vue3/Vite项目 与 使用HBuilderX导入插件 的包有差异,请直接访问 开源地址

二、踩坑总结:

1、样式穿透在微信开发者工具不适用:

解决方案: 全局引入一个scss文件,在其内根据微信小程序开发者工具上的标签层级单独写样式

2、v-html在微信开发者工具显示的是rich-text组件,导致样式不适用:图片大小不自适应,比如图片太大,只展示了一半

解决方案:

  • html代码:
    <rich-text class="content" :nodes="formatImg(state.info.detailMobileDesc)"</rich-text>
  • JavaScript代码:
 const formatImg = (html: string) => {
    var newContent = html.replace(/<img[^>]*>/gi, match => {
      let processed = null;
      if (!match.match(/style=\"(.*)\"/gi)) {
        processed = match.replace(
          /\<img/g,
          '<img style="width:100%;height:auto;display:block"',
        );
      } else {
        processed = match.replace(
          /style=\"(.*)\"/gi,
          'style="width:100%;height:auto;display:block"',
        );
      }
      return processed;
    });
    return newContent;
  };

3、uni-app组件里的checkbox-groupcheckbox写的样式在微信开发者工具里不匹配

审核元素发现在小程序开发者工具中,checkbox组件是被黑盒封装的,根本无法查看组件内部的类名,也就是说无法知晓修改的类名具体情况了。于是查阅资料发现,小程序平台checkbox组件实际内部类名和h5端不一致(类名及代码如下):

      <checkbox-group @change="checkboxChange">
        <uni-swipe-action>
          <uni-swipe-action-item
            class="uni-list-cell"
            v-for="(item, index) in state.cartList"
            :key="item.sku"
          >
            <view class="content-box">
              <view class="ceshi">
                <checkbox
                  :class="{ checked: item.checked }"
                  :value="item.value"
                  :checked="item.checked"
                  @click="checkOne(item, index)"
                />
              </view>

              <view class="box">
                <img
                  class="cellImage"
                  :src="JSON.parse(item.goodsImg)[0]"
                  alt=""
                />
                <view class="right">
                  <p class="nameTitle">
                    {{ item.goodsName }}
                  </p>
                  <p class="modelSec">
                    {{ item.model }}
                  </p>
                  <view class="priceSele">
                    ¥{{ item.price }}
                    <view class="numBox"
                      ><uni-number-box
                        :min="1"
                        :max="9999999999"
                        :isMax="item.count >= 9999999999 ? true : false"
                        :isMin="item.count === 1"
                        v-model="item.count"
                        @change="changeValue"
                    /></view>
                  </view>
                </view>
              </view>
            </view>
            <template v-slot:right>
              <view class="slot-button" @click="deleteCartItem(index)">
                <image
                  class="thumbnail_4"
                  referrerpolicy="no-referrer"
                  src="http://p0.qhimg.com/t01fd5d8f0e8cfe01c4.png"
                />
                <p> 删除 </p>
              </view>
            </template>
          </uni-swipe-action-item>
        </uni-swipe-action>
      </checkbox-group>

所以,若要兼容多平台需要写两套不同类名的样式,同时配合uni-app中的条件注释实现跨端兼容,通过在代码外层包裹条件注释,当ifdef后面的平台类型是MP-WEIXIN,代表下列代码只会在微信小程序端生效,其它平台不会执行:

 /* #ifdef MP-WEIXIN */
  uni-checkbox .uni-checkbox-input,
  checkbox .wx-checkbox-input {
    width: 35rpx;
    height: 35rpx;
    border-radius: 50% !important;
  }

  uni-checkbox .uni-checkbox-input,
  checkbox .wx-checkbox-input.wx-checkbox-input-checked {
    color: #fff !important;
    background-color: #3873fa !important;
  }
  /* #endif */

关于条件注释,如果还不是很明白,可以查看:条件编译 解决各端差异 - uni-app官网 (dcloud.io)

4、蓝湖UI上的图片链接在微信开发者工具上不可用

需要将图片下载下来放在图床上生成链接,最后统计再上传至白名单更改域名

5、问题: 元素隐式具有 “any“ 类型,因为类型为 “string“ 的表达式不能用于索引类型 “Object“。 在类型 “Object“ 上找不到具有类型为 “string“ 的参数的索引签名

描述: 在写代码的时候,取一个对象某一个key对应的value值,但是写完之后发现Typescript报错了,错误内容就是如题,有点奇怪,特此去了解一下:

for (const key in obejct) {
	// 处理...
	obejct[key]
	....
}

解决方案:

方案一:忽略

tsconfig.jsoncompilerOptions里面新增忽略的代码,如下所示,添加后则不会报错:

"suppressImplicitAnyIndexErrors": true
方案二:声明

在定义的Interface里对其进行声明,如下所示,声明过后,也不会再报错:

interface DAMNU_ENABLE {
    ....
    [key: string]: boolean, // 字段扩展声明
}; 

[key: string]: boolean, // 字段扩展声明 声明之后可以用方括号的方式去对象里边的值
方案三:对其使用keyof进行判断

TypeScript 允许我们遍历某种类型的属性,并通过 keyof 操作符提取其属性的名称。keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型

export function isValidKey(
    key: string | number | symbol,
    object: object
): key is keyof typeof object {
    return key in object;
}

定义一个函数:isValidKey(),然后对需要使用到的地方进行一次判断:

for (const key in obejct) {
	if(isValidKey(key,obejct)){
		// 处理...
		obejct[key]
		....
	}
}

这三种方式都可以解决如题的报错信息,但是个人比较倾向于第三种,并不是其他两种不好,只是说,写第一种有一点点写any的感觉,不到万不得已不这么弄;第二种得把用到的属性字段类型都写出来,有点费事

6、ts对于类型定义这块比较严谨,所以在传prop要传一个对象时,要使用泛类型进行定义:

// 接收外部传参
  interface propData {
    goodsItem: {
      goodsImg: string;
      itemName: string;
      model: string;
      price: string;
      count: string;
    };
  }
  const props = defineProps<propData>();

7、在calc时,在符号前面要增加空格,没有空格不会生效

height: calc(100vh - 80rpx);

8、小程序系列无法拦截原生tabbar及原生导航返回,如需拦截就需要自定义tabbar、header,在这里我们可以利用其他解决方案:

业务场景: 在首页home.vue中点击底部tabbar跳转至二级页面page.vue,跳转时需要进行角色验证,判断是否有该模块权限,在这里没有办法监测tabbar跳转,只能利用两个页面传值的方式进行监测:

  • page.vue文件(uni.$emit('open');
  onShow(() => {
    // 判断是否是游客
    const userStore = userInfoStoreStore();
    const userInfo = userStore.getUserInfo;
    if (!userInfo.userRole) {
      uni.switchTab({
        url: '/pages/home/index',
      });
      uni.$emit('open');
    } else {
    	// 该页面其他操作
		......
    }
  });
  • home.vue文件:
onShow(() => {
  // 游客角色点击产品
  uni.$on('open',function(){
     // 若是游客,首页做提示操作
     .......
  })
})

9、后端一次性返回大量数据,影响页面加载

虽然后端一次返回这么多数据,但用户的屏幕只能同时显示有限的数据。所以我们可以采用延迟加载的策略,根据用户的滚动位置动态渲染数据。

要获取用户的滚动位置,我们可以在列表末尾添加一个空节点空白。每当视口出现空白时,就意味着用户已经滚动到网页底部,这意味着我们需要继续渲染数据。

同时,我们可以使用getBoundingClientRect来判断空白是否在页面底部。

<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
const getList = () => {
  // code as before
}
const container = ref<HTMLElement>() // container element
const blank = ref<HTMLElement>() // blank element
const list = ref<any>([])
const page = ref(1)
const limit = 200
const maxPage = computed(() => Math.ceil(list.value.length / limit))
// List of real presentations
const showList = computed(() => list.value.slice(0, page.value * limit))
const handleScroll = () => {
  if (page.value > maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    // When the blank node appears in the viewport, the current page number is incremented by 1
    page.value++
  }
}
onMounted(async () => {
  const res = await getList()
  list.value = res
})
</script>
 
 
<template>
  <div id="container" @scroll="handleScroll" ref="container">
    <div class="sunshine" v-for="(item) in showList" :key="item.tid">
      <img :src="item.src" />
      <span>{{ item.text }}</span>
    </div>
    <div ref="blank"></div>
  </div>
</template>

10、uniapp微信小程序嵌入页面web-view配置

在uniapp微信小程序中嵌入页面web-view通常需要配置以下内容:

a. 域名白名单

在微信小程序中,只有在白名单中的域名才能在web-view中加载内容。因此,在嵌入web-view之前,您需要将要加载的 域名 添加到小程序的域名白名单中。可以使用微信开发者工具,在“项目”->“开发设置”->“服务器域名”中添加域名,或者使用wx.setStorageSync()方法动态添加.

b. 加载安全策略

为了确保在web-view中加载的内容是安全的,可以在web-view标签中添加一个’sandbox’属性。此属性启用了加载安全策略,可以防止来自web-view中加载的网页在浏览器中执行JavaScript代码,从而增加了安全性。

<web-view :src="url" sandbox></web-view>

c. 指定web-view的高度和宽度

在嵌入web-view之前,请确保为其指定合适的高度和宽度。可以使用CSS样式或uni-app的组件API来指定web-view的大小。例如:

<web-view :src="url" style="height: 100%; width: 100%;"></web-view>
uni.createSelectorQuery().select('#web-view').boundingClientRect(res => {
  console.log('webview size:', res.width, res.height);
}).exec();

d. 处理web-view事件

与普通微信小程序页面一样,可以在web-view中使用onLoad等生命周期方法或者事件监听来实现交互。例如:

<web-view :src="url" @load="onLoad"></web-view>
methods: {
  onLoad(e) {
    console.log(e);
  }
}

11、上传文件

a、上传file:///var/mobile/Media/PhotoData/Mutations/DCIM/104APPLE/IMG_4307/Adjustments/FullSizeRender.mov这个路径的文件

在微信小程序中,无法直接上传本地文件系统中的文件,包括 file:///var/mobile/Media/PhotoData/Mutations/DCIM/104APPLE/IMG_4307/Adjustments/FullSizeRender.mov 这个路径下的文件。

  • 如果你想要在微信小程序中上传 图片文件,可以使用小程序提供的 wx.chooseImage 接口来选择本地图片,并将图片上传到服务器。该接口会打开系统的相册或相机,让用户选择或拍摄一张图片,然后将图片的临时文件路径返回给小程序,你可以使用该路径来上传图片。

示例代码如下:

wx.chooseImage({
  success: function(res) {
    var tempFilePaths = res.tempFilePaths;
    wx.uploadFile({
      url: 'https://example.com/upload',
      filePath: tempFilePaths[0],
      name: 'file',
      success: function(res) {
        console.log(res.data);
      }
    })
  }
})

  • 如果你想要上传 视频文件,可以使用 wx.chooseVideo 接口来选择本地视频文件,并将视频文件上传到服务器。该接口会打开系统的相册或相机,让用户选择或拍摄一段视频,然后将视频的临时文件路径返回给小程序,你可以使用该路径来上传视频文件。

示例代码如下:

wx.chooseVideo({
  sourceType: ['album', 'camera'],
  maxDuration: 60,
  camera: 'back',
  success: function(res) {
    var tempFilePath = res.tempFilePath;
    wx.uploadFile({
      url: 'https://example.com/upload',
      filePath: tempFilePath,
      name: 'file',
      success: function(res) {
        console.log(res.data);
      }
    })
  }
})

12、长按保存图片至相册

需求:这个是一个商品详情页面,需要在详情部分实现长按保存图片(数据都是后端直接返回的html,需要从中截取出图片地址)

<rich-text
   class="content"
   :nodes="formatImg(state.info.detailMobileDesc || state.info.detailPcDesc)"
    @longpress="matcheUrl"
></rich-text>
 // 获取图片地址
  const matcheUrl = () => {
    const htmlString = state.info.detailMobileDesc || state.info.detailPcDesc;
    const regex = /<img[^>]+src="([^">]+)"/g;
    const matches = htmlString.match(regex);
    if (matches) {
      const imageUrls = matches.map((match: any) =>
        match.replace(/<img[^>]+src="([^">]+)"/, '$1'),
      );
      // 实现长按下载
      longtap(imageUrls);
    }
};
  const longtap = (imgList: any) => {
  // 这里是上级要求不要用原地址,需要后端用nginx转一下
    const updatedImageUrls = imgList.map((url: string) =>
      url.replace(/^https?:\/\/[^/]+/, 'https://xxx/img'),
    );
    console.log(updatedImageUrls);
    if (updatedImageUrls.length > 0) {
      uni.showActionSheet({
        itemList: ['保存全部图片'],
        success: res => {
          if (res.tapIndex === 0) {
          // 使用uni.authorize()方法请求用户授权访问相册
            uni.authorize({
              scope: 'scope.writePhotosAlbum',
              success: () => {
              // 成功的话 去下载
                for (let i = 0; i < updatedImageUrls.length; i++) {
                  uni.downloadFile({
                    url: updatedImageUrls[i],
                    success: res => {
                      if (res.statusCode === 200) {
                      	// 保存到相册
                        uni.saveImageToPhotosAlbum({
                          filePath: res.tempFilePath,
                          success: () => {
                            if (i === updatedImageUrls.length - 1) {
                              uni.showToast({
                                title: '保存成功',
                                icon: 'success',
                              });
                            }
                          },
                          fail: err => {
                            console.log(err);
                            uni.showToast({
                              title: '保存失败',
                              icon: 'none',
                            });
                          },
                        });
                      }
                    },
                    fail: err => {
                      console.log(err);
                      uni.showToast({
                        title: '下载失败',
                        icon: 'none',
                      });
                    },
                  });
                }
              },
              fail: () => {
                // 失败的话 清收用户授权相册权限
                uni.showModal({
                  title: '授权提示',
                  content: '您拒绝了相册权限,是否打开设置去授权?',
                  cancelText: '取消', // 取消按钮的文字
                  confirmText: '确认', // 确认按钮的文字
                  showCancel: true, // 是否显示取消按钮,默认为 true
                  confirmColor: '#f55850',
                  cancelColor: '#39B54A',
                  success: res => {
                    if (res.confirm) {
                      console.log('comfirm'); //点击确定之后执行的代码
                      uni.openSetting({
                        success(res) {
                          console.log(res.authSetting);
                        },
                      });
                    } else {
                      console.log('cancel'); //点击取消之后执行的代码
                    }
                  },
                });
              },
            });
          }
        },
      });
    }
  };

注意: 如果您的小程序是在微信公众平台中创建的,您需要在提交审核之前进行“写入相册”权限的设置。
在这里插入图片描述
在这里插入图片描述

三、总结:

后续还发现有什么问题我会继续更新博客,大家也可以在评论区沟通交流,拜拜!!!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值