【微信小程序】连续拍照功能实现
前言:
最近在使用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>
欢迎各位大佬有意见的话评论区留言,互相交流学习~
相关文章:
【微信小程序】连续拍照功能实现
前言: 最近在使用uniapp开发微信小程序,遇到这样一个需求,用户想要连续拍照,拍完之后可以删除照片,保留自己想要的照片,然后上传到服务器上。由于原生的方法只能一个个拍照上传,所以只能自己通过…...
JavaSE:11、内部类
学习 资源1 学习资源 2 1、成员内部类 import com.test.*;public class Main {public static void main(String [] argv){Person personnew Person();//Person构造函数Person.Woman womanperson.new Woman();//woman构造函数} }package com.test;public class Person {publ…...
VTD激光雷达(7)——07_OptiX_Variables_Advanced
文章目录 前言一、总结 前言 一、 1 和上图蓝绿的区别在于 总结...
运维工程师面试整理-自动化运维
自动化运维是现代运维工作中不可或缺的一部分,它可以大幅提升效率,减少人为错误,并使得大规模环境管理变得可行。在面试中,面试官通常会通过自动化运维相关的问题来评估你在自动化工具使用、脚本编写、CI/CD 实践以及系统监控等方面的能力。以下是关于自动化运维的详细内容…...
【JAVA基础】实现Tomcat基本功能
文章目录 TCP/IP协议Socket编程ServletTomcat 在搜索了两三天之后,也是大概弄懂了Tomcat是个什么东西,我们在说Tomcat之前,先来了解一下下面这三个东西: TCP/IP协议 TCP/IP 是互联网通信的基础协议。TCP(传输控制协议…...
风力发电叶片缺陷检测数据集
风力发电叶片缺陷检测数据集】nc: 4 names: [Burn Mark, Coating_defects, Crack, EROSION ] 名称:【烧伤痕迹, 涂层缺陷, 裂缝,侵蚀】共1095张,8:1:1比例划分,(train;876张,val:109张ÿ…...
数据类型自动转换的解决方案
数据类型自动转换的解决方案 java8、jdk8背景 为方便测试框架数据处理以及方便查看一些数据,弄了一个工具类,部分要点简要说明。 主要涉及到字符串与其他类型的相互转换,无其他类型之间的相互转换。 轻量测试框架实现与使用的总篇可见此文…...
大厂校招:唯品会Java面试题及参考答案
SortedSet 的原理 SortedSet 是一个有序的集合接口,它继承自 Set 接口。在 Java 中,常见的实现类有 TreeSet。 TreeSet 实现了 SortedSet 接口,它使用红黑树来维护集合中元素的有序性。红黑树是一种自平衡的二叉搜索树,具有以下特点: 每个节点要么是红色,要么是黑色。根节…...
Qt常用控件——QLCDNumber
文章目录 QLCDNumber核心属性倒计时小程序倒计时小程序相关问题 QLCDNumber核心属性 QLCDNumber是专门用来显示数字的控件,类似于这样: 属性说明intValue获取的数字值(int).value获取的数字值(double)和intValue是联动的例如value设为1.5,in…...
专业学习|GERT网络概览(学习资源、原理介绍、变体介绍)
一、GERT 网络概览 GERT(Graphical Evaluation Review Technique,图示评审技术)是一种结合流线图理论(Flow Graphical Theory)、矩母函数(Moment Generating Function)、计划评审技术(Program Evaluation Review Technique)解决随机网络问题的方法,描述各…...
搭建一个基于角色的权限验证框架
说明:基于角色的权限验证(Role-Based Access Control,RBAC)框架,是目前大多数服务端的登录校验框架。本文介绍如何快速搭建一个这样的框架,不用Shiro、Spring Security、Sa-Token这样的“大框架”实现。 R…...
下载chromedriver驱动
首先进入关于ChromeDriver最新下载地址:Chrome for Testing availability 进入之后找到与自己所匹配的,在浏览器中查看版本号,下载版本号需要一致。 下载即可,解压,找到 直接放在pycharm下即可 因为在环境变量中早已配…...
在STM32工程中使用Mavlink与飞控通信
本文讲述如何在STM32工程中使用Mavlink协议与飞控通信,特别适合自制飞控外设模块的项目。 需求来源: 1、增稳云台里的STM32单片机需要通过串口接收飞控传来的云台俯仰、横滚控制指令和相机拍照控制指令; 2、自制的有害气体采集器需要接收飞…...
【Elasticsearch】-7.17.24版本接入
官网 https://www.elastic.co/cn/downloads/elasticsearch 本项目基于windows环境下,其他环境操作类似 1、初始化配置 打开config/elasticsearch.yaml 添加如下配置 cluster.name: dams_clusternetwork.host: 127.0.0.1 http.port: 9200# 不开启geo数据库 inge…...
ShouldSniffAttr在自动化测试中具体是如何应用?
在自动化测试中,ShouldSniffAttr 这样的函数名通常暗示它是一个用于断言(assertions)的工具,用于检查某个元素或属性是否符合预期的条件。 虽然这不是一个标准的函数名,但我们可以根据命名推测其用途。 例如…...
前端vue3打印,多页打印,不使用插件(工作中让我写一个打印功能)
说下总体思路,创建一个组件,里面放多个span字段,然后根据父组件传入的参数,生成子组件,最好我们打印子组件的信息即可。通过我多次ai,探索最后成功了。 子组件代码 media print 这个我要讲一下ÿ…...
传感技术是如何实现实时监测和控制的呢
传感技术在力士乐拧紧系统中实现实时监测和控制的方式主要通过以下几个步骤进行: 一、传感器数据采集 1. 传感器种类: 力士乐拧紧系统中可能包含多种传感器,如力矩传感器、角度传感器和转速传感器等。这些传感器各自负责检测拧紧过程中的不…...
为什么mac打不开rar文件 苹果电脑打不开rar压缩文件怎么办
你是否遇到过这样的情况,下载了一个rar文件,想要查看里面的内容,却发现Mac电脑无法打开。rar文件是一种常见的压缩文件格式,它可以将多个文件或文件夹压缩成一个文件,节省空间和传输时间。如此高效实用的压缩文档&…...
linux下日志系统setvbuf接口及结构体 handle_file_t成员介绍
typedef struct handle_file_t {uint8_t *wkey;//用于存储写入文件时可能需要的加密密钥int cflag;//用于表示日志文件的某些配置标志,例如是否启用压缩、是否启用加密等char *file_path;//用于存储日志文件的路径FILE *…...
ESP8266+httpServer+GET+POST实现网页验证密码
1. 代码 #include "esp_http_server.h" #include "esp_log.h" #include "web_server.h"// 辅助宏,用于计算两个数中的较小值 #define MIN(a, b) ((a) < (b) ? (a) : (b))static const char *TAG "wifi web_server";c…...
HarmonyOS6 半年磨一剑 - RcCheckboxGroup 组件与全选不确定态机制深度解析
文章目录前言一、RcCheckboxGroup 内部状态同步1.1 双层状态管理1.2 选中状态判断二、布局渲染架构2.1 横向与纵向的渲染分支2.2 itemGap 的类型安全处理2.3 属性透传机制三、全选与不确定态(indeterminate)3.1 三态状态机3.2 全选逻辑实现3.3 indetermi…...
5个步骤掌握MelonLoader:让Unity游戏模组开发变得轻松有趣
5个步骤掌握MelonLoader:让Unity游戏模组开发变得轻松有趣 【免费下载链接】MelonLoader The Worlds First Universal Mod Loader for Unity Games compatible with both Il2Cpp and Mono 项目地址: https://gitcode.com/gh_mirrors/me/MelonLoader 你是否曾…...
为什么头部AI工厂已全面切换PyTorch 3.0静态图训练?揭秘2024年Q2实测吞吐提升3.8倍、成本下降41%的关键配置
第一章:PyTorch 3.0静态图训练的企业级演进全景PyTorch 3.0标志着深度学习框架从动态优先范式向动静统一架构的关键跃迁。其核心突破在于TorchDynamo Inductor后端的深度融合,使torch.compile()不再仅是实验性优化器,而成为企业级生产训练流…...
Wan2.2-T2V-A5B开发环境配置:IntelliJ IDEA远程调试与GPU服务器连接
Wan2.2-T2V-A5B开发环境配置:IntelliJ IDEA远程调试与GPU服务器连接 你是不是也遇到过这种烦恼?本地电脑性能有限,跑个稍微大点的模型就卡成幻灯片,风扇呼呼作响,感觉下一秒就要起飞。但代码和模型都部署在远端的GPU服…...
React-primitives项目架构剖析:模块化设计与依赖注入原理
React-primitives项目架构剖析:模块化设计与依赖注入原理 【免费下载链接】react-primitives Primitive React Interfaces Across Targets 项目地址: https://gitcode.com/gh_mirrors/re/react-primitives React-primitives是一个跨平台UI开发框架࿰…...
【刚性 PINN 与时间自适应策略】第九章:综合案例实战:刚性化学反应动力学模拟
目录 9.1 问题描述与数据生成 9.1.1 Robertson 刚性化学反应模型构建 9.1.2 传统 PINN 的失败复现与诊断 第二部分:代码实现 9.1.1.1 三组分反应方程组及其刚性特征分析 9.1.1.2 基准解的生成(使用隐式求解器) 9.1.2.1 训练损失曲线与预测结果的偏差可视化 9.1.2.2 …...
《QGIS快速入门与应用基础》248:对齐工具(左对齐/居中对齐/右对齐)对齐工具(左对齐/居中对齐/右对齐)对齐工具(左对齐/居中对齐/右对齐)对齐工具(左对齐/居中对齐/右对齐)对齐工具(左对齐/
作者:翰墨之道,毕业于国际知名大学空间信息与计算机专业,获硕士学位,现任国内时空智能领域资深专家、CSDN知名技术博主。多年来深耕地理信息与时空智能核心技术研发,精通 QGIS、GrassGIS、OSG、OsgEarth、UE、Cesium、OpenLayers、Leaflet、MapBox 等主流工具与框架,兼具…...
别再乱调灯光和材质了!UE5渲染性能优化的三个核心禁忌与正确姿势
UE5渲染性能优化的三大禁忌与实战解决方案 在虚幻引擎5的渲染管线中,性能优化往往成为项目后期最棘手的挑战之一。许多开发者习惯性地将注意力集中在视觉效果上,却忽略了渲染效率的平衡。当场景复杂度达到临界点时,那些看似无害的高精度贴图…...
这个网站,我愿称之为生信云平台天花板
刚入门生信的你,是否也曾被这些问题折磨得想摔键盘?• Linux 环境配置:conda install 报错到怀疑人生,环境冲突让你原地崩溃。• 硬件瓶颈: 实验室服务器要排队,自己的轻薄本跑个比对就能当暖气片。• 代码…...
信号处理学习笔记5:卡尔曼滤波理论
卡尔曼滤波,用直白的话来讲, 就是有多个不确定的结果,经过分析、推理和计算,获得相对准确的结果。 它的核心特点是: 能够预测数据的未来趋势\({x}_{k}^{ }\) 结合当前数据进行修正,使预测更加准确 可以处理…...
