微信 h5 项目开发已经告一段落,交付客户使用,总结一下开发过程遇到的问题
ios 设备点击输入框,页面放大
安卓设备目前没发现该问题,页面放大导致在失焦后,页面边缘位置内容显示不全
解决方案:
设置maxium-scale=1.0, user-scalable=no
禁止缩放
xml
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
ios 设备底部导航
在使用vue-router
的push
方法跳转页面时候,目标页面默认会生成底部导航条
解决方案:
页面跳转使用replace
,全局自定义导航,参考往期文章
下拉刷新操作和页面下拉冲突
微信浏览器中网页默认支持下拉操作,如果页面内部分区域做了下拉刷新。现在当下拉未做下拉刷新的区域,会触发浏览器的下拉动画,之后再去下拉刷新,可能导致下拉刷新不能正确触发
解决方案:
有下拉刷新的页面,非下拉刷新区域禁止下拉操作,例如在页面头部禁止滑动操作
xml
<template>
<div class="layout">
<div class="layout-header" ref="layoutHeaderRef">
<div class="flex items-center justify-center h-full">
<van-icon
v-if="state.showBack"
name="arrow-left"
size="20"
class="!absolute top-[12px] left-[17px]"
@click="historyStore.back"
/>
<div class="text-[17px] font-[700] text-[#000]">{{ state.title }}</div>
</div>
</div>
<div class="layout-content">
<router-view />
</div>
</div>
</template>
<script setup>
const layoutHeaderRef = ref(null)
onMounted(() => {
// layoutHeaderRef 禁止滚动事件
layoutHeaderRef.value.addEventListener('touchmove', (e) => {
e.preventDefault()
})
})
</script>
van-list @load 事件异常触发
笔者在使用van-pull-refresh
和van-list
实现了类似微信账单的功能,如果在滚动页面后,点击日期查询数据,预期是走根据日期查询的逻辑,但是这里同时会自动触发van-list
的 load 事件,经排查在查询前置空数据,会导致 load 事件异常触发
load 在非预期的情况调用,导致程序异常
解决方案:
根据日期查询时,禁止滚动加载
ini
<van-list
v-model:loading="state.swipeUpLoading"
@load="onLoad"
:disabled="state.isListDisabled"
:finished="state.finished"
:immediate-check="false"
finished-text=""
>
</van-list>
function initData() {
// 禁止滚动加载
state.isListDisabled = true
state.list = []
// 日期查询逻辑
getRecordList()
}
function getRecordList() {
// 请求完成恢复滚动加载
state.isListDisabled = false
}
vant 样式覆盖问题
如果在项目内同时使用van-popup
和Toast
相关组件,开发环境正常,打包项目运行,碰到Toast
相关样式异常问题,查看样式代码发现相关样式被覆盖
解决方案:
main.js
入口文件重新引入Toast
相关样式
arduino
import 'vant/es/toast/style'
https 配置
笔者项目需要调用摄像头,本地调试需要 https 协议,本地代理需要配置 https,这里用 openssl 生成测试证书
解决方案:
本地生成测试证书,openssl下载地址
csharp
# 1. 生成私钥(有效期10年)
openssl genrsa -out server.key 2048
# 2. 创建证书请求(CSR)
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=YourCompany/CN=yourdomain.com"
# 3. 生成自签名证书(有效期365天)
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
vite.config.js
配置代理,这样就可以在手机端预览页面使用拍照
javascript
export default defineConfig(({ command, mode }) => {
return {
server: {
host: '0.0.0.0',
cors: true,
port: envConfig.VITE_PORT,
https: {
key: readFileSync('c:/Users/Administrator/key.pem'),
cert: readFileSync('c:/Users/Administrator/cert.pem'),
},
proxy: {
'/api/api/face': {
target: envConfig.VITE_API_FACEURL,
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/api/face/, '/api/face'),
},
'/api': {
target: envConfig.VITE_API_BASEURL,
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, ''),
},
},
},
}
}
拍照,裁剪
拍照裁剪可以参考之前文章,这里讲一下拍照后 canvas 绘制图片的问题,在使用drawImage
方法绘制图片时候,如果不处理好图片偏移量和宽高,就无法做到图片居中
解决方案:
- 通过获取视频和屏幕的宽高比,比较两个比例大小,确定使用宽度还是高度为基准进行缩放
- 在使用宽度或者高度进行缩放的时候,另外一边可能比屏幕大或者小,因此需要计算两者差值的一半,将图像居中
javascript
// 拍照功能
const takePhoto = () => {
if (!videoRef.value || !canvasRef.value) return
const video = videoRef.value
const canvas = canvasRef.value
// 计算适当的缩放比例以保持宽高比
const videoAspectRatio = video.videoWidth / video.videoHeight
const screenAspectRatio = window.innerWidth / window.innerHeight
let drawWidth,
drawHeight,
offsetX = 0,
offsetY = 0
if (videoAspectRatio > screenAspectRatio) {
// 视频更宽,以高度为基准
drawHeight = window.innerHeight - 44
drawWidth = drawHeight * videoAspectRatio
offsetX = (drawWidth - window.innerWidth) / 2
} else {
// 视频更高,以宽度为基准
drawWidth = window.innerWidth
drawHeight = drawWidth / videoAspectRatio
offsetY = (drawHeight - window.innerHeight) / 2
}
// 设置画布尺寸为屏幕尺寸
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const ctx = canvas.getContext('2d')
if (!ctx) return
// 处理前置摄像头的镜像效果
if (state.isFrontCamera) {
ctx.translate(canvas.width, 0)
ctx.scale(-1, 1)
}
// 绘制时考虑偏移量,确保图像居中
ctx.drawImage(video, -offsetX, -offsetY, drawWidth, drawHeight)
// 如果是前置摄像头,恢复画布变换
if (state.isFrontCamera) {
ctx.setTransform(1, 0, 0, 1, 0, 0)
}
state.imgUrl = canvas.toDataURL('image/jpeg', 0.8)
stopCamera()
// 初始化裁剪器
nextTick(() => {
if (cropperRef.value) {
state.cropper = new Cropper(cropperRef.value, {
viewMode: 0,
dragMode: 'move',
aspectRatio: 1,
modal: true,
guides: true,
restore: false,
cropBoxMovable: false,
// cropBoxResizable: false,
// toggleDragModeOnDblclick: false,
autoCropArea: 1,
// minContainerWidth: window.innerWidth,
// minContainerHeight: window.innerHeight,
ready() {
state.cropper.setCropBoxData({
width: 300,
height: 300,
left: (this.cropper.containerData.width - 300) / 2,
top: 145,
})
},
})
}
})
}