h5、vue3抓拍功能

废话不多说直接上代码,代码解析看下文。

import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import { setting } from '@/common/setting';
import { useQuestionStore } from '@/stores/modules/examStore.js';
// 试题store
const questionStore = useQuestionStore();

export function useCameraCapture({ time = 5000, photoNum = 5 }) {
  const videoSrc = ref('');
  const imgUrl = ref('');
  const video = ref(null);
  const fileUploadToken = ref('');
  const picList = ref([]);
  const attIds = ref([]);
  let captureInterval = null;

  const connectCamera = () => {
    try {
      navigator.mediaDevices
        .getUserMedia({
          video: true,
        })
        .then(stream => {
          video.value.srcObject = stream;
          video.value.onloadedmetadata = () => {
            video.value.play();
            //   setTimeout(() => {
            //     handleCapture();
            //   }, 1000);
          };
        })
        .catch(err => {
          console.error('获取设备失败:', err);
        });
    } catch (error) {
      console.error('获取设备失败:', error);
    }
  };

  const handleCapture = async () => {
    await nextTick();
    // 拍照次数达到上限,清除定时器
    if (picList.value.length >= photoNum) {
      clearInterval(captureInterval);
      return;
    }
    const videoElement = document.querySelector('video'); // 获取 video 节点
    if (!videoElement || !(videoElement instanceof HTMLVideoElement)) {
      console.error('Video element not found or not an HTMLVideoElement');
      return;
    }
    const canvas = document.createElement('canvas'); // 创建 canvas 节点
    const w = videoElement.clientWidth;
    const h = videoElement.clientHeight;
    canvas.width = w; // 设置宽高
    canvas.height = h; // 设置宽高
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      console.error('Canvas context not found');
      return;
    }
    ctx.drawImage(videoElement, 0, 0, w, h); // video 写入到 canvas
    imgUrl.value = canvas.toDataURL('image/png'); // 生成截图
    uni.uploadFile({
      url: `/api${setting.fileUrl}`, // 服务器上传接口地址
      filePath: imgUrl.value,
      name: 'file', // 必须填写,后台用来接收文件
      header: {
        'Blade-Auth': fileUploadToken.value,
        'Blade-Requested-With': 'BladeHttpRequest',
      },
      formData: {
        user: 'test', // 其他要传的参数
      },
      success: uploadFileRes => {
        let picData = JSON.parse(uploadFileRes.data);
        if (picList.value.length >= photoNum) {
          return;
        } else {
          let str = {};
          str.attachId = picData.data.attachId;
          str.src = picData.data.link;
          // picList.value.push(str);
          attIds.value.push(picData.data.attachId);
          // 将attIds存储到store中
          questionStore.setAttIds(attIds.value);
          // 从store中获取attIds
          picList.value = questionStore.getAttIds
        }
      },
      fail: uploadFileErr => {
        console.log('uploadFileErr', uploadFileErr);
      },
    });
  };

  onMounted(async () => {
    // 获取token(附件上传所需的token)
    const token = uni.getStorageSync('accessToken');
    fileUploadToken.value = `bearer ${token}`;
    await nextTick();
    connectCamera();
    captureInterval = setInterval(handleCapture, time); // 每隔5秒调用一次handleCapture
  });

  onUnmounted(() => {
    if (captureInterval) {
      clearInterval(captureInterval); // 清除定时器
    }
  });

  return {
    videoSrc,
    imgUrl,
    video,
    fileUploadToken,
    picList,
    attIds,
    time,
    handleCapture,
  };
}

这段代码是一个 Vue 3 的自定义 Hook,用于实现摄像头捕获功能,并将捕获的图像上传到服务器。以下是对这段代码的详细讲解:

导入依赖

import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import { setting } from '@/common/setting';
import { useQuestionStore } from '@/stores/modules/examStore.js';

定义 store 实例

const questionStore = useQuestionStore();
  • 创建一个 questionStore 实例,用于在 Hook 中访问和修改 store 中的数据。

定义 Hook

export function useCameraCapture({ time = 5000, photoNum = 5 }) {
  • 定义一个名为 useCameraCapture 的函数,接收一个包含 timephotoNum 的对象作为参数。
    • time:拍照间隔时间,默认为 5000 毫秒(5 秒)。
    • photoNum:拍照次数上限,默认为 5 次。

定义响应式数据

const videoSrc = ref('');
const imgUrl = ref('');
const video = ref(null);
const fileUploadToken = ref('');
const picList = ref([]);
const attIds = ref([]);
let captureInterval = null;

连接摄像头

const connectCamera = () => {
  try {
    navigator.mediaDevices
      .getUserMedia({
        video: true,
      })
      .then(stream => {
        video.value.srcObject = stream;
        video.value.onloadedmetadata = () => {
          video.value.play();
        };
      })
      .catch(err => {
        console.error('获取设备失败:', err);
      });
  } catch (error) {
    console.error('获取设备失败:', error);
  }
};

捕获图像

const handleCapture = async () => {
  await nextTick();
  if (picList.value.length >= photoNum) {
    clearInterval(captureInterval);
    return;
  }
  const videoElement = document.querySelector('video');
  if (!videoElement || !(videoElement instanceof HTMLVideoElement)) {
    console.error('Video element not found or not an HTMLVideoElement');
    return;
  }
  const canvas = document.createElement('canvas');
  const w = videoElement.clientWidth;
  const h = videoElement.clientHeight;
  canvas.width = w;
  canvas.height = h;
  const ctx = canvas.getContext('2d');
  if (!ctx) {
    console.error('Canvas context not found');
    return;
  }
  ctx.drawImage(videoElement, 0, 0, w, h);
  imgUrl.value = canvas.toDataURL('image/png');
  uni.uploadFile({
    url: `/api${setting.fileUrl}`,
    filePath: imgUrl.value,
    name: 'file',
    header: {
      'Blade-Auth': fileUploadToken.value,
      'Blade-Requested-With': 'BladeHttpRequest',
    },
   

 form

Data: {
      user: 'test',
    },
    success: uploadFileRes => {
      let picData = JSON.parse(uploadFileRes.data);
      if (picList.value.length >= photoNum) {
        return;
      } else {
        let str = {};
        str.attachId = picData.data.attachId;
        str.src = picData.data.link;
        attIds.value.push(picData.data.attachId);
        questionStore.setAttIds(attIds.value);
        picList.value = questionStore.getAttIds;
      }
    },
    fail: uploadFileErr => {
      console.log('uploadFileErr', uploadFileErr);
    },
  });
};
  • 使用 nextTick 确保 DOM 更新完成。
  • 检查拍照次数是否达到上限,如果达到则清除定时器并返回。
  • 获取视频元素,并将视频帧绘制到 Canvas 上。
  • 将 Canvas 转换为图像 URL,并上传到服务器。
  • 在上传成功后,将附件 ID 和图像 URL 存储到 picListattIds 中,并更新 store。

生命周期钩子

onMounted(async () => {
  const token = uni.getStorageSync('accessToken');
  fileUploadToken.value = `bearer ${token}`;
  await nextTick();
  connectCamera();
  captureInterval = setInterval(handleCapture, time);
});

onUnmounted(() => {
  if (captureInterval) {
    clearInterval(captureInterval);
  }
});
  • 在组件挂载时,获取上传令牌,连接摄像头,并设置定时器定期调用 handleCapture
  • 在组件卸载时,清除定时器。

返回值

return {
  videoSrc,
  imgUrl,
  video,
  fileUploadToken,
  picList,
  attIds,
  time,
  handleCapture,
};
  • 返回响应式数据和方法,以便在组件中使用。

总结

这个 Hook 实现了以下功能:

  1. 连接摄像头并获取视频流。
  2. 定期捕获视频帧并生成图像。
  3. 将生成的图像上传到服务器。
  4. 将上传的附件 ID 和图像 URL 存储到 Pinia store 中。
  5. 提供响应式数据和方法,以便在组件中使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieyucx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值