uni-app+vue3+vant开发微信小程序探路...
使用uni-app+vue3+vant开发微信小程序过程中的一些技巧和坑
一、简单说说准备工作:
创建项目
- 工具: HBuilder最新稳定版, uni-app官网可下载
- 菜单栏:文件-> 新建 -> 项目,创建一个空白的基于vue3版本的模板目录即可
调试
-
工具:预先下载安装 微信开发者工具
-
HBuilder中选中当前项目的任何一个文件
-
菜单栏:运行-> 运行到小程序模拟器 ->微信开发者工具
-
运行成功会自动启动
微信开发者工具
,等待编译完成会看到测试界面
-
【坑】开发模式进行真机调试会白屏,需要在发行模式下进行真机调试
发布
- 菜单栏:发行-> 小程序-微信
- 此时需要一个小程序AppId,需在微信公众平台上开通
二、一些技巧和坑
1.引用vant组件
引入组件目录
- 与
/pages
创建/wxcomponents/vant
目录 - 下载微信小程序版本的vant代码
- 解压代码,把
/dist
目录内的文件拷贝进去新建的/wxcomponents/vant
目录中 - 开发过程中应参考对应版本的文档: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.Toast
和export 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
, 在对应主界面中引入即可,可以使用vant
的tabbar
组件,可参考如下:
<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"> </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('您取消了授权')
}
});
}
}
【坑】明明添加了内容,页面却还是空白
- wxml及样式的编写需要严格遵循使用规则,有些固定的要求及支持的样式,不遵循的话可能会导致渲染不出来
【坑】真机调试报错 fail canvas has not been created
- 按照上述的方法应该不会报这个问题
- 还报的话,检查是否canvas内的元素的渲染尺寸超出了canvas的实际宽高,超出则可能渲染失败
这次分享这么多,谢谢观看!
仍有无法解决的问题欢迎交流!
ps: 为了图快,本项目其实并没有用到vue3的一些新特性(懒得查文档), 就这么凑合吧哈哈
更多推荐
所有评论(0)