【微信小程序】连续拍照功能实现

server/2024/9/22 23:29:29/

前言:
最近在使用uniapp开发小程序>微信小程序,遇到这样一个需求,用户想要连续拍照,拍完之后可以删除照片,保留自己想要的照片,然后上传到服务器上。由于原生的方法只能一个个拍照上传,所以只能自己通过视频流截取帧的方式开发一个拍照功能组件,交互和界面都是自己开发,供项目其他需要的地方使用。现做下记录,以下是完整代码:

// Camera.vue 页面,子组件
<template><view v-if="showCamera" class="page-body"><!--图片预览--><!-- <template v-if="previewSrc"><image @click="handleBack" src="../../static/images/common/back.png" class="close-icon"></image><image :src="previewSrc" class="preview-img"></image></template> --><!--图片上传--><image @click="handleCancel" src="../../static/images/common/close.png" class="close-icon"></image><!--摄像头组件--><cameradevice-position="back"flash="off"ref="camera"class="page-camera"></camera><!--拍照--><image @click="takePhoto" src="../../static/images/common/photo.png" class="photo"></image><!--选择的图片--><view class="select-photo"><template v-if="imageList?.length > 0"><view class="storage"><view v-for="(item, index) in imageList" :key="index" style="position: relative;"><image :src="item.tempImagePath" class="select-img" @click="handlePreviewImg(item)"></image><image @click="handleDelete(index)" src="../../static/images/common/cross.png" class="back-icon"></image></view></view></template><view class="finish" @click="handleFinish">确认</view></view><!--添加水印--><view style="position: absolute; top: -999999px;"><canvas style="width: 60px; height: 60px" id="uploadCanvas" canvas-id="uploadCanvas"></canvas></view></view>
</template><script setup name="MyCamera">import { ref, onMounted, getCurrentInstance } from 'vue'import { getToken } from '@/utils/auth.js'import { useUserStore, useHomeStore } from '@/store/index.js'import { $showToast, validateNull, timestampToDateTime } from '@/utils/index.js'const userStore = useUserStore()const homeStore= useHomeStore()const props = defineProps({showCamera: {type: Boolean,default: false},photos: {type: Array,default: []}})const { proxy } = getCurrentInstance()const imageList = ref([]) // 选择图片const uploadFileArr = ref([]) // 已上传的图片const previewSrc = ref('') // 图片预览const $emit = defineEmits(['handleCancel', 'handleFinish'])onMounted(() => {imageList.value = []uploadFileArr.value = []})// 添加水印const waterMarkerOperate = (filePath) => {const address = '江苏省xxxxx'uni.getImageInfo({src: filePath,success: ress => {let ctx = uni.createCanvasContext('uploadCanvas', proxy);// 将图片绘制到canvas内 60-宽, 60-高const cWidth = 40;const cHeight = 60;ctx.drawImage(filePath, 0, 0, 60, cHeight);const fontSize = 2;ctx.setFillStyle('rgba(128, 128, 128, 0.9)'); // 设置背景色ctx.fillRect(0, 65, cWidth, 34); // 设置背景位置ctx.setFontSize(fontSize); // 设置字体大小ctx.setFillStyle('#FFFFFF'); // 设置字体颜色const lineHeight = 2;  // 行高设置let textToWidth = (ress.width / 3) * 0.01; // 绘制文本的左下角x坐标位置let textToHeight = (ress.height / 3) * 0.1; // 绘制文本的左下角y坐标位置const nowTime = timestampToDateTime(); // 当前日期ctx.fillText(`日     期:${nowTime}`, textToWidth, textToHeight);textToHeight += lineHeight;const lines = [];let line = '';// 遍历字符并拆分行for (const char of address) {const testLine = line + char;const testWidth = ctx.measureText(testLine).width;if (testWidth > 24) {lines.push(line);line = char;} else {line = testLine;}}// 加入最后一行lines.push(line);const addressLabel = '地     址:';for (let i = 0; i < lines.length; i++) {const textLine = lines[i];// 仅在第一行添加地址标签const lineText = i === 0 ? addressLabel + textLine : textLine;ctx.fillText(lineText, textToWidth, textToHeight);textToHeight += lineHeight;}// 绘制完成后,在下一个事件循环将 canvas 内容导出为临时图片地址ctx.draw(false, (() => {setTimeout(() => {uni.canvasToTempFilePath({canvasId: 'uploadCanvas',success: res1 => {// 生成水印imageList.value.push({tempImagePath: res1.tempFilePath})},fail: error => {console.log('错误', error);},}, proxy);}, 500);})())}});}// 拍照const takePhoto = () => {// 最多拍摄6张const totalImages = (imageList.value?.length || 0) + (props.photos?.length || 0);if (totalImages > 5) {$showToast('已达拍照上限')return}uni.createCameraContext().takePhoto({quality: 'high',success: (res) => {waterMarkerOperate(res.tempImagePath)}})}// 拍照完成const handleFinish = async () => {// 调用上传接口const uploadPromises = imageList.value?.map(item => {return new Promise((resolve, reject) => {uni.uploadFile({url: config.baseUrl + '/uploadUrl',filePath: item?.tempImagePath,name: 'file',header: {Authorization: 'Bearer ' + getToken()},success: (res) => {try {const data = JSON.parse(res.data)if (data.fileName) {resolve(data.fileName)}} catch (e) {reject(e)}}})})})const imgList = await Promise.all(uploadPromises);imgList.forEach(img => {uploadFileArr.value.push(img)})$emit('handleFinish', JSON.stringify({uploadFileArr: uploadFileArr.value}))$showToast('上传成功')imageList.value = []uploadFileArr.value = []}// 图片预览const handlePreviewImg = (item) => {previewSrc.value = item?.tempImagePath}// 返回const handleBack = () => {previewSrc.value = ''}// 关闭const handleCancel = () => {imageList.value = []uploadFileArr.value = []$emit('handleCancel')}// 删除图片未上传const handleDelete = (index) => {imageList.value.splice(index, 1)}
</script><style lang="scss">
.page-body {width: 100%;height: 100%;position: fixed;top: 0;left: 0;z-index: 99;background: rgba(0, 0, 0, 0.6);display: flex;justify-content: center;align-items: center;.close-icon {position: absolute;left: 30rpx;top: 100rpx;width: 44rpx;height: 44rpx;z-index: 99;}.page-camera {width: 100%;height: 90%;position: absolute;top: 0;}.photo {width: 140rpx;height: 140rpx;position: absolute;bottom: 256rpx;}.select-photo {width: 100%;height: 180rpx;display: flex;flex-direction: row;align-items: center;margin-bottom: 12rpx;background: #000;position: absolute;bottom: 0;.storage {max-width: 540rpx; overflow-x: auto;display: flex;flex-direction: row;}.finish {width: 120rpx;height: 60rpx;line-height: 60rpx;font-size: 28rpx;color: #fff;text-align: center;position: absolute;right: 37rpx;background: #0fad70;border-radius: 10rpx;}.select-img {width: 120rpx;height: 120rpx;margin-right: 16rpx;border-radius: 10rpx;}.back-icon {width: 30rpx;height: 30rpx;position: absolute;right: 0;top: -10rpx;}}.preview-img {width: 100%;height: auto;object-fit: contain;}
}
</style>// 时间戳转成时间
const timestampToDateTime = (timestamp) => {const date = timestamp ? new Date(timestamp) : /* @__PURE__ */ new Date();const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, "0");const day = String(date.getDate()).padStart(2, "0");const hours = String(date.getHours()).padStart(2, "0");const minutes = String(date.getMinutes()).padStart(2, "0");const seconds = String(date.getSeconds()).padStart(2, "0");return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};// 父组件使用
// index.vue
import Camera from '@/components/Camera.vue'<my-camera ref="cameraRef" showCamera="true" :photos="form.photo" @handleFinish="handleFinish" @handleCancel="handleCancel"></my-camera><script setup>const handleFinish = () => {// 上传图片完成的逻辑处理}const handleCancel = () => {// 上传图片取消的逻辑处理}
</script>

欢迎各位大佬有意见的话评论区留言,互相交流学习~


http://www.ppmy.cn/server/120522.html

相关文章

ETCD学习使用

一、介绍 etcd&#xff08;分布式键值存储&#xff09;是一个开源的分布式系统工具&#xff0c;用于可靠地存储和提供键值对数据。etcd 通常通过 HTTP 或 gRPC 提供 API&#xff0c;允许应用程序通过简单的接口与其交互。由于其可靠性和稳定性&#xff0c;etcd 在构建可扩展、分…

Android 系统WIFI AP模式

在 Android 系统中&#xff0c;AP 模式&#xff08;Access Point Mode&#xff0c;热点模式&#xff09;允许设备作为 Wi-Fi 热点&#xff0c;其他设备可以通过连接这个热点进行互联网访问或局域网通信。要让 Android 设备工作在 AP 模式&#xff0c;你可以通过应用层的 API 控…

Redis面试真题总结(三)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 什么是缓存雪崩&#xff1f;该如何解决&#xff1f; 缓存雪崩是指…

前端vue-父传子

父传子的话是在components中创建一个子组件MyTest.vue&#xff0c;并且在父组件中先导入(import MyTest from "./components/MyTest")&#xff0c;再注册&#xff08;在expo二default中写上 compnents:{MyTest}&#xff09;&#xff0c;再使用标签&#xff08;<My…

前端开发中的防抖与节流

在前端开发的世界里&#xff0c;防抖&#xff08;debounce&#xff09;和节流&#xff08;throttle&#xff09;是两个非常重要的概念&#xff0c;它们能够帮助我们更好地处理频繁触发的事件&#xff0c;提升用户体验和系统性能。 一、防抖&#xff08;debounce&#xff09; …

错误码与错误提示设计

1、背景介绍 在软件开发的复杂世界中&#xff0c;错误是不可避免的。无论是因为外部系统的变化、用户输入的错误&#xff0c;还是内部逻辑的缺陷&#xff0c;错误都会出现。为了有效管理这些错误&#xff0c;并向用户和开发者提供清晰、有用的反馈&#xff0c;设计一套合理的错…

LabVIEW提高开发效率技巧----采用并行任务提高性能

在复杂的LabVIEW开发项目中&#xff0c;合理利用并行任务可以显著提高系统的整体性能和响应速度。并行编程是一种强大的技术手段&#xff0c;尤其适用于实时控制、数据采集以及多任务处理等场景。LabVIEW的数据流编程模型天然支持并行任务的执行&#xff0c;结合多核处理器的硬…

统信服务器操作系统【1050e版】安装手册

统信服务器操作系统1050e版本的安装 文章目录 功能概述一、准备环境二、安装方式介绍安装步骤步骤一:制作启动盘步骤二:系统的安装步骤三:安装引导界面步骤四:图形化界面安装步骤五:选择安装引导程序语言步骤六:进入安装界面步骤七:设置键盘步骤八:设置系统语言步骤九:…