当前位置: 首页 > article >正文

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

前言:
最近在使用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>
    <!--摄像头组件-->
    <camera
      device-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.kler.cn/a/315503.html

相关文章:

  • 「Mac玩转仓颉内测版12」PTA刷题篇3 - L1-003 个位数统计
  • Linux如何更优质调节系统性能
  • INQUIRE:一个包含五百万张自然世界图像,涵盖10,000个不同物种的专为专家级文本到图像检索任务设计的新型基准数据集。
  • 大数据开发面试宝典
  • HelloMeme 上手即用教程
  • 使用elementUI实现表格行拖拽改变顺序,无需引入外部库
  • kafka 消息位移提交几种方式:消息重复消息、消息丢失的关键
  • Docker_基础初识
  • 新能源汽车知识点集萃
  • Python办公自动化教程(003):PDF的加密
  • HarmonyOS Next开发----使用XComponent自定义绘制
  • 【乐企-工具篇】有关乐企发票文件生成- OFD和PDF文件生成
  • 四、JVM原理-4.1、JVM介绍
  • vue中 <template> 与 <template lang=“jade“>的对比,哪个性能好
  • 数据结构之希尔排序
  • 轻代码的概念学习笔记
  • http和https的区别及get和post请求的区别
  • Vue3新组件transition(动画过渡)
  • Java API 之集合框架进阶
  • 软件测试面试题(5)——二面(游戏测试)
  • 【PLW003】设备器材云端管理平台v1.0(SpringBoot+Mybatis+NodeJS+MySQL前后端分离)
  • LeetCode题练习与总结:回文链表--234
  • [JavaEE]———进程、进程的数据结构、进程的调度
  • 【优选算法之二分查找】No.5--- 经典二分查找算法
  • Linux之实战命令03:stat应用实例(三十七)
  • 如何使用 maxwell 同步到 redis?