uni-app+vue3+vant开发微信小程序探路...

Posted caryi_wong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uni-app+vue3+vant开发微信小程序探路...相关的知识,希望对你有一定的参考价值。

一、简单说说准备工作:

创建项目

  • 工具: HBuilder最新稳定版, uni-app官网可下载
  • 菜单栏:文件-> 新建 -> 项目,创建一个空白的基于vue3版本的模板目录即可

调试

  • 工具:预先下载安装 微信开发者工具

  • HBuilder中选中当前项目的任何一个文件

  • 菜单栏:运行-> 运行到小程序模拟器 ->微信开发者工具

  • 运行成功会自动启动微信开发者工具,等待编译完成会看到测试界面
    -

【坑】开发模式进行真机调试会白屏,需要在发行模式下进行真机调试

发布

  • 菜单栏:发行-> 小程序-微信
  • 此时需要一个小程序AppId,需在微信公众平台上开通

二、一些技巧和坑

1.引用vant组件

引入组件目录

  1. /pages 创建 /wxcomponents/vant 目录
  2. 下载微信小程序版本的vant代码
  3. 解压代码,把/dist目录内的文件拷贝进去新建的/wxcomponents/vant目录中
  4. 开发过程中应参考对应版本的文档:https://vant-contrib.gitee.io/vant-weapp 但需要把对应的引用语法改成vue的语法,如:
<van-cell-group>
    <van-field value=" value " placeholder="请输入用户名" border=" false "
    bind:change="onChange" />
</van-cell-group>

改为:

<van-cell-group>
    <van-field :value="value" placeholder="请输入用户名" :border="false"
    @change="onChange" />
</van-cell-group>

引用

修改pages.json文件

全局引用

"globalStyle": 属性下加入以下片段,可按需引入具体需要全局引入的组件,引入规则如下:

"usingComponents": 
   "van-cell-group": "/wxcomponents/vant/cell-group/index",
   "van-field": "/wxcomponents/vant/field/index"

单页引用

把上述的代码放在对应的页面配置内,如:

【坑】报错??

页面【wxcomponents/vant/info/index]错误:
 Error: module 'wxcomponents/vant/info/index.js' is not defined,
 require args is 'wxcomponents/vant/info/index.js'

WAServiceMainContext.js?t=wechat&s=1666746784000&v=2.27.0:1 SyntaxError: 
Cannot use import statement outside a module(env: Windows,mp,1.06.2209190; lib: 2.27.0)

【填坑】

这是因为微信开发者工具没有启用ES6转ES5功能而报错

微信开发者工具右上角:详情-> 本地设置 -> 勾选“将JS编译成ES5”即可

但可能重新编译或热重载后,该选项“将JS编译成ES5”又不勾选了,此时可以直接去修改项目文件manifest.json, 勾选微信小程序配置-> ES6转ES5 即可

如打开的是manifest.json的源码,可以在对应的位置增加以下配置:

【坑】引用van-toast组件无效或报错

1.未找到van-toast 节点,请确认 selector 及 context 是否正确

组件内引用切记参考官方文档,需要给页面引入一个van-toast组件,才能在script内使用 Toast() 的方法

<van-toast id="van-toast" />

注意:只要可能会调用Toast()的界面,都需要插入该组件

2.在非组件的js文件中引用Toast(),正常使用可能会像如下的方式:

开发模式下是可以正常调用,但是在发行模式下,会报错

vendor.js? [sm]:1 TypeError: s.Toast is not a function

TypeError: Cannot read property 'success' of undefined

如果把Toast打印出来,会发现为null, 原因是Toast并未正常引入

理由揭秘:

打开/wxcomponents/vant/toast/toast.js,你会发现,最后一句代码为:

export default Toast;

开发模式下,初次编译,在微信开发者工具打开这个文件,发现这句话没有变,编译过程并没有把它进行处理

而当回去修改了一下代码,热重载后,这句话就变成了:

exports.Toast = Toast;

估计就是微信开发者工具进行了再次编译,把一些组件目录的代码进行转换了,而uni-app没有对这些代码进行处理

发布模式下,不存在热重载,在微信开发者工具打开这个文件,就会发现它一直是

export default Toast;

而编译后引用这个文件的代码始终是一样的:

都是通过[module].Toast()来进行引用。

这就会导致一个问题,我们什么都不改直接引用的话,会出现热重载后是能正常,初始加载和发布都不能正常使用,因为
exports.Toastexport default Toast我们引入后的内容是不一样的

详细原理可参考这个地址

【填坑】

/wxcomponents/vant/toast/toast.js的最后一句导出语句修改为:

exports.Toast = Toast;

引入语句修改为:

const Toast = require('../wxcomponents/vant/toast/toast.js').Toast

export function loadInfo() 
	Toast('加载成功')

大功告成!😀

2.自定义顶部导航栏,以便于修改顶栏样式(还原UI设计图;加背景图、Logo之类…)

修改pages.json文件


    ...,
    "globalStyle": 
        "navigationStyle": "custom",
        ...
     


创建对应的自定义组件: components/commonHeader/commonHeader.vue, 在界面中引入即可(可按下方第4点的方式进行按需引入的设置)

私人tips

获取系统状态栏和菜单高度,用于设置自定义顶部导航栏的高度

修改App.vue文件

onLaunch: function() 
    console.log('App Launch')
    uni.getSystemInfo(
        success: function(e) 
            // #ifdef MP-WEIXIN
            uni.$StatusBarHeight = e.statusBarHeight;
            // getMenuButtonBoundingClientRect 用于获取页面右上角圆角按钮的位置信息
            let custom = wx.getMenuButtonBoundingClientRect();
            uni.$CustomBarHeight = custom.bottom - e.statusBarHeight + 8
            // #endif

            //uni 为全局对象,可以挂载全局参数,在其他组件可以直接使用
        
    )

3.自定义底部菜单栏tabbar

修改pages.json文件


    ...,
    "tabBar": 
        "custom": true,
        "list": [
                        "pagePath": "pages/index/index",
                        "text": "首页"
                ,
                
                        "pagePath": "pages/passed/index",
                        "text": "主页面1"
                ,
                
                        "pagePath": "pages/my/index",
                        "text": "个人中心"
                ...
        ]
    

创建对应的自定义组件: components/commonTabbar/commonTabbar.vue, 在对应主界面中引入即可,可以使用vanttabbar组件,可参考如下:

<template>
    <van-tabbar :active="active" active-color="#00C84A" inactive-color="#CDCDCD" :border="false" @change="onChange">
        <van-tabbar-item v-for="item in tabs" :key="item.name">
                <image slot="icon" :src="item.iconNormal" mode="contain" style="width: 48rpx; height: 48rpx;" />
                <image slot="icon-active" :src="item.iconActive" mode="contain" style="width: 48rpx; height: 48rpx;" />
                item.name
        </van-tabbar-item>
    </van-tabbar>
</template>
<script>
export default 
    props: 
        current: 
            type: Number,
            default: 1
        
    ,
    data() 
        return 
           active: 1,
           tabs: [
                    name: '主页面1',
                    iconNormal: '/static/passed.png',
                    iconActive: '/static/passed-active.png',
                    path: '/pages/passed/index'
                ,
                
                    name: '首页',
                    iconNormal: '/static/index.png',
                    iconActive: '/static/index-active.png',
                    path: '/pages/index/index'
                ,
                
                    name: '个人中心',
                    iconNormal: '/static/my.png',
                    iconActive: '/static/my-active.png',
                    path: '/pages/my/index'
                
            ]
        
    ,
    watch: 
        current: 
            immediate: true,
            handler: function(val) 
                    this.active = val
            
        
    ,
    methods: 
        onChange(e) 
            const current = e.detail
            uni.switchTab(
                    url: this.paths[current]
            )
        
    

私人tips

为了使界面主要内容不受tabbar覆盖,可以在van-tabbar组件前面加入一个空节点,如下:

    <view class="footer-empty">&nbsp;</view>
    //css 样式
     .footer-empty 
	height: 100rpx;
	padding-bottom: env(safe-area-inset-bottom);
    

4.使用easycom自动按需加载自定义组件(为什么我的组件总是无法自动识别?)

修改pages.json文件


    ...,
    "easycom": 
            "autoscan": true,
            "custom": 
                "^uni-(.*)": "@/components/uni-$1.vue", // 匹配components目录内的vue文件
                "^vue-file-(.*)": "packageName/path/to/vue-file-$1.vue" // 匹配node_modules内的vue文件
            
    

只要开启了autoscan, 会自动扫描目录规则符合/components/组件名称/组件名称.vue的所有文件,自动进行引入,无需再在custom中引入

引入后page中直接使用组件即可

注意:如果新建的组件引用无效,在HBuilder中重新编译项目试试

5.引用富文本显示和编辑组件mp-html

官方文档

mp-html 默认只有显示富文本功能,需要支持编辑状态的话,要额外代码, 参考
editable模式设置文档
小程序示例代码

其实引用组件不麻烦,参照demo,可以按照自己的需求修改菜单icon之类的,比较方便

6.使用微信原生小程序提供的wxml-to-canvas进行动态海报生成以及保存到本地

官方文档

为了方便改造,不采用npm的方法进行安装,直接打开代码片段

在代码片段中对应的两个目录,拷贝到自己的项目中/wxcomponents目录下

改造目的和内容:

1.wxml-to-canvas通常用于生成海报,而海报又通常通过popup的形式弹出,所以需要改造渲染时机,等popup弹出后再渲染

默认引用即渲染,但是canvas没法在一个隐藏节点中渲染,所以把默认的渲染时机改为手动(通过自定义代码调用渲染)

修改wxml-to-canvas/index.js文件

  • 在methods中新增一个方法:
methods: 
    initCanvas() 
        ...
    

  • attached中的渲染代码剪切进去

  • canvas需要先初始化,初始化结束后再调用renderToCanvas进行绘制, 否则会报错
    网上很多方案都是用setTimeout来进行延迟调用, 但是我觉得这是不稳妥的, 延迟时间设长了设短了都不好, 于是我把initCanvas写成一个异步函数,如下:
methods: 
    initCanvas() 
        return new Promise((resolve,reject) => 
            const dpr = wx.getSystemInfoSync().pixelRatio
            const query = this.createSelectorQuery()
            this.dpr = dpr
            query.select('#canvas')
              .fields(node: true, size: true)
              .exec(res => 
                const canvas = res[0].node
                const ctx = canvas.getContext('2d')
                canvas.width = res[0].width * dpr
		canvas.height = res[0].height * dpr
                ctx.scale(dpr, dpr)
                this.ctx = ctx
                this.canvas = canvas
                resolve(canvas)
             )
        )
    ,
    ...
,

  • 页面代码中:

插入组件

<van-popup :show="showPopup" :close-on-click-overlay="true" custom-style="background:transparent" @close="showPopup = false" @after-enter="opened">
    <view>
      <wxml-to-canvas ref="widget" class="widget"></wxml-to-canvas>
    </view>
</van-popup>

在popup弹出后,调用方法

methods: 
    async opened() 
        const canvas = await this.$refs.widget.initCanvas()
        Toast.loading(
            message: '加载中',
            forbidClick: true
        );
        const obj =  wxml: '...', styles: '...'
        this.$refs.widget.renderToCanvas(obj).then(res => 
            Toast.clear()
        )
    


2.canvas自适应宽高改造:

  • 获取实际屏幕宽度和设计稿的比例,计算每个元素的实际尺寸和边距等

  • 修改wxml-to-canvas/index.js文件

    • 修改attached()方法
    attached() 
        const sys = wx.getSystemInfoSync();
        //拿到设备的宽度,跟设计图宽度的比例
        const screenRatio = sys.screenWidth / 375;
        this.setData(
                screenRatio,
                width: this.data.width * screenRatio,
                height: this.data.height * screenRatio,
        );
    
    
    • 修改initCanvas()方法
    initCanvas() 
        // ...省略
        // canvas.width = res[0].width * dpr
        // canvas.height = res[0].height * dpr
        canvas.width = this.data.width * dpr;
        canvas.height = this.data.height * dpr;
        // ...省略
    
    
    • 修改renderToCanvas()方法
    renderToCanvas() 
        // const wxml, style = args
        const  wxml, fnGetStyle  = args
         // 通过fnGetStyle方法传入屏幕与设计稿的比例,用以计算样式
        const style = fnGetStyle(this.data.screenRatio)
        // ...省略
    
    
    
  • 可新建一个文件进行canvas节点和样式的配置, 如canvasConfig.js:

// canvasConfig.js
// function 形式便于传入动态数据
export default function(info) 
    const wxml = `<view class="container">
        <view class="absolute mainBox"></view>
    </view>`
    const fnGetStyle = (screenRatio) => 
        return 
            // 编写样式, 名称需以驼峰命名
            absolute: 
               position: 'absolute'
            ,
            container: 
                width: 300*screenRatio, //通过乘以倍率,计算出在当前屏幕分辨率下的实际宽度, 其他尺寸同理
                height: 471*screenRatio,
                position: 'relative'
            ,
            mainBox: 
                width: 200*screenRatio,
                padding: 10*screenRatio,
                margin: 10*screenRatio,
                color: '#eeeeee'
            
        
    
    return 
        wxml,
        fnGetStyle
    


  • 修改页面中的组件调用方法:
import canvasConfig from './canvasConfig.js'
// ...省略
methods: 
    opened() 
        ...
        const info =  title: '标题' 
        const obj = canvasConfig(info)
        this.$refs.widget.renderToCanvas(obj).then(res => 
           Toast.clear()
        )
    

生成图片并保存到本地

页面代码中:

methods: 
  //...
  extraImage() 
    this.$refs.widget.canvasToTempFilePath().then(res => 
        this.saveImg(res.tempFilePath)
    )
  , 
  async saveImg (tempFilePath) 
    const _this = this
    uni.saveImageToPhotosAlbum(
        filePath: tempFilePath,
        success: async (res) => 
            uni.showModal(
                content: '图片已保存,分享给好友吧!',
                showCancel: false,
                confirmText: '好的',
                confirmColor: '#333',
                success: function (res) 
                    // ...
                ,
                fail: function (res) 
                   // ...
                
            );
        ,
        fail: function (res) 
            Toast.fail('您取消了授权')
        
    

11.uni-app开发微信小程序实现消息订阅

使用场景

在我们开发的小程序中司机首次登录需要先认证从业资质和车辆。司机在小程序端提交资料后,后台需要审核相关资料。审核结束后需要向司机推送相关的审核结果,这里就需要实现微信小程序的消息订阅

订阅消息

订阅消息包括两种:一次性订阅消息长期订阅消息

一次性订阅消息

一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。

长期订阅消息

一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。

目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。

实现步骤

1.获取模板ID

在微信公众平台手动配置获取模板 ID:登录https://mp.weixin.qq.com 获取模板,如果没有合适的模板,可以申请添加新模板,审核通过后可使用。

在这里插入图片描述

我们可以通过关键字搜索找到符合我们使用场景的模板,点击选用即可

在这里插入图片描述

如上图所示,每个应用最多同时只能使用50个模板。上面我们也说了可以自定义模板,待系统审核通过后才可以使用,自定义模板的按钮在公共模板库的最后一页(就是这么神奇O(∩_∩)O哈哈~)

在这里插入图片描述

2.uni.requestSubscribeMessage

我们的小程序是使用uni-app开发的,uni-app官方也给我们提供了相应的api即uni.requestSubscribeMessage,具体的介绍如下图

在这里插入图片描述

需要注意的是这个需要用户的点击行为才能触发,而且是一次性订阅消息,所以需要在用户指定的需要接收订阅消息的页面由用户的点击操作来触发。

在本小程序中,用户的从业资质认证车辆认证页面都需要订阅审核消息,而且在后台审核不通过后重新提交认证资料后仍需要订阅下一次的审核消息,所以我们采取的策略是在用户提交认证资料之前先订阅消息,然后再保存数据

saveVehicleData: function() {
    var checkRes = graceChecker.check(_self.formData, rule);
    if(checkRes){
        uni.requestSubscribeMessage({
            provider: 'weixin',
            tmplIds: ['您的模板ID'],
            success: function(res) {
            console.log(JSON.stringify(res));
            },
            fail: function() {

            },
            complete: function() {
                // 保存认证信息数据
                _self.submitVehicleAuth();
            }
        });
    } else {
        _self.message = graceChecker.error;
        _self.$refs.toast.show();
        return;
    }
},

3.subscribeMessage.send

在这里插入图片描述

通过官方的api我们可以看到该接口需要以HTTPS调用,对于该接口的使用我们先定义一个方法

public static JSONObject sendSubscribeMessage(String accessToken, SubscribeMessageVO subscribeMessageVO) {
        String requestUrl = " https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken;
        JSONObject data = JSONObject.parseObject(JSONObject.toJSONString(subscribeMessageVO));
        return JSONObject.parseObject(HttpClientUtils.httpClientPostJSON(requestUrl,
                data));
    }

至于参数accessToken的获取,我们可以看微信小程序的官方文档,如下图

在这里插入图片描述

对于accessToken的获取,我们定义如下方法

public static String getAccessToken(RedisUtils redisUtils, String appId, String appSecret) {
		return Optional.ofNullable(redisUtils.get("accessToken_" + appId)).orElseGet(() -> {
				String requestUrl = "https://api.weixin.qq" +
                    ".com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
				JSONObject jsonObject = JSONObject.parseObject(HttpClientUtils.httpClientGet(requestUrl));
				if (jsonObject.getIntValue("errcode") != 0) {
						// 获取token失败
            log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getIntValue("errcode"),jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        redisUtils.set("accessToken_" + appId, jsonObject.getString("access_token"),jsonObject.getLongValue("expires_in"));
            return jsonObject.getString("access_token");
        }).toString();
}

这里我们首先从redis里面获取accessToken,redis里面没有我们再调用接口获取,将获取到的accessToken保存到redis中

最后我们就可以在需要下发订阅消息的方法中直接调用上面定义的方法sendSubscribeMessage即可。

4.测试

我们在小程序的车辆认证页面订阅了审核消息,当提交认证资料的时候会弹出订阅消息提示框,如下图我们选择“允许”

在这里插入图片描述

之后我们在后台的车辆审核里面点击审核不通过,如下图

在这里插入图片描述

随后我们在微信中就会收到系统推送的订阅消息,如下

在这里插入图片描述

uni-app开发微信小程序实现消息订阅就到这里了,老铁们给个赞啊(#.#)

以上是关于uni-app+vue3+vant开发微信小程序探路...的主要内容,如果未能解决你的问题,请参考以下文章

uni-app开发微信小程序使用微信小程序的插件

10.uni-app开发微信小程序的一些小操作

11.uni-app开发微信小程序实现消息订阅

uni-app.10.开发微信小程序的一些小操作

uni-app.11.开发微信小程序实现消息订阅

uni-app开发微信小程序的几天时间