Vant之手机端上传图片只允许拍照上传

1.开发拍照上传页面 - andImgCapture.vue,包含镜头翻转功能

<template>
  <div>
    <van-button
      icon="plus"
      type="primary"
      :disabled="disabled"
      @click.stop="clickCamera"
      ref="uploadBtn"
      >拍照上传</van-button
    >
    <div class="camera-content" v-show="cameraVisible">
      <van-button
        icon="cross"
        class="close-photo"
        type="primary"
        @click.stop="stopVideoStream"
      ></van-button>
      <video
        class="camera-video"
        ref="videoElement"
        playsinline
        webkit-playsinline
      ></video>
      <van-button
        icon="photograph"
        class="take-photo"
        @click.stop="capturePhoto"
      ></van-button>
      <!-- 你可以使用适当的图标来表示切换摄像头 -->
      <van-button
        icon="share"
        class="switch-camera"
        @click.stop="switchCamera"
      ></van-button>
      <canvas ref="canvasElement" style="display: none"></canvas>
    </div>
    <div class="captured-image-content" v-if="photoVisible">
      <van-button class="cancel-photo" @click="cancelPhoto">取消</van-button>
      <van-button class="confirm-photo" @click="confirmPhoto">完成</van-button>
      <img
        class="captured-image"
        v-if="capturedImage"
        :src="capturedImage"
        alt="Captured Image"
      />
    </div>
  </div>
</template>
<script setup>
import {
  onBeforeUnmount,
  onMounted,
  ref,
  toRefs,
  defineAsyncComponent,
  computed,
  watch,
  nextTick,
  reactive,
} from "vue";
import { Toast } from "vant";
const props = defineProps({
  disabled: {
    type: Boolean,
    default: false,
  },
  afterRead: {
    type: Function,
    default: () => {
      return true;
    },
  },
  beforeRead: {
    type: Function,
    default: () => {},
  },
});
const emit = defineEmits(["clickUpload"]);
const cameraVisible = ref(false);
const photoVisible = ref(false);
const capturedImage = ref(null);
const videoElement = ref(null);
const canvasElement = ref(null);
const file = reactive({
  content: "",
  file: {},
});

watch(() => cameraVisible.value, newVal => {
  if (newVal) {
    document.getElementsByTagName("body")[0].style.backgroundColor = "#000000";
  } else {
    document.getElementsByTagName("body")[0].style.backgroundColor = "#ffffff";
  }
});

onBeforeUnmount(() => {
  stopVideoStream();
});

const clickCamera = async (e) => {
  emit("clickUpload", e);
  // 开启摄像头前检测是否触发了阻止默认事件
  if (e.defaultPrevented) {
    return;
  } else {
    setupCamera();
  }
};

const uploadBtn = ref(null);
// 默认使用后置摄像头
const facingMode = ref(sessionStorage.facingMode ?? "environment");
const switchCamera = () => {
  facingMode.value =
    facingMode.value === "environment" ? "user" : "environment";
  // 当点击切换按钮时,如果当前是后置摄像头,则切换到前置摄像头,反之亦然。
  sessionStorage.facingMode = facingMode.value;
  // 重新设置摄像头以应用更改
  stopVideoStream();
  nextTick(() => {
    uploadBtn.value.$el.click();
  });
};

const setupCamera = async () => {
  try {
    initFile();
    navigator.mediaDevices
      .getUserMedia({
        video: { facingMode: facingMode.value },
      })
      .then((stream) => {
        const videoElementObj = videoElement.value;
        videoElementObj.srcObject = stream;
        cameraVisible.value = true;
        if ("srcObject" in videoElementObj) {
          videoElementObj.srcObject = stream;
        } else {
          // 防止在新的浏览器里使用它,因为它已经不再支持了
          videoElementObj.src = window.URL.createObjectURL(stream);
        }
        videoElementObj.onloadedmetadata = function (e) {
          videoElementObj.play();
        };
      })
      .catch((err) => {
        Toast.fail("访问用户媒体设备失败:", err.message);
      });
  } catch (error) {
    cameraVisible.value = false;
    console.log("获取摄像头失败:", error);
    Toast.fail("获取摄像头失败");
  }
};

const capturePhoto = () => {
  const videoElementObj = videoElement.value;
  const canvasElementObj = canvasElement.value;
  const context = canvasElementObj.getContext("2d");

  canvasElementObj.width = videoElementObj.videoWidth;
  canvasElementObj.height = videoElementObj.videoHeight;

  context.drawImage(
    videoElementObj,
    0,
    0,
    canvasElementObj.width,
    canvasElementObj.height
  );
  capturedImage.value = canvasElementObj.toDataURL();
  photoVisible.value = true;
};

const cancelPhoto = () => {
  photoVisible.value = false;
  capturedImage.value = "";
};

const confirmPhoto = () => {
  stopVideoStream();
  file.content = capturedImage.value;
  file.file = base64ToFile(capturedImage.value, "photo" + Date.now());

  const passBeforeRead = props.beforeRead(file);
  if (passBeforeRead || passBeforeRead === undefined) {
    props.afterRead(file);
  }
};

const initFile = () => {
  file.content = "";
  file.file = "";
  capturedImage.value = "";
};

const stopVideoStream = () => {
  photoVisible.value = false;
  cameraVisible.value = false;
  if (videoElement.value && videoElement.value.srcObject) {
    const tracks = videoElement.value.srcObject.getTracks();
    tracks.forEach((track) => {
      track.stop();
    });
  }
};

const base64ToFile = (dataurl, fileName) => {
  let arr = dataurl.split(",");
  let mime = arr[0].match(/:(.*?);/)[1];
  // suffix是该文件的后缀
  let suffix = mime.split("/")[1];
  // atob 对经过 base-64 编码的字符串进行解码
  let bstr = atob(arr[1]);
  // n 是解码后的长度
  let n = bstr.length;
  // Uint8Array 数组类型表示一个 8 位无符号整型数组 初始值都是 数子0
  let u8arr = new Uint8Array(n);
  // charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  // new File返回File对象 第一个参数是 ArraryBuffer 或 Bolb 或Arrary 第二个参数是文件名
  // 第三个参数是 要放到文件中的内容的 MIME 类型
  return new File([u8arr], `${fileName}.${suffix}`, {
    type: mime,
  });
};
</script>
<style lang="less" scoped>
.camera-content {
  width: 100%;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  background: #000000;
  z-index: 99;
}
.close-photo {
  position: absolute;
  left: 0;
  z-index: 100;
  border: unset;
  background: #000000;
}
.camera-video {
  width: 100vw;
  height: auto;
  position: absolute;
  top: 50%;
  left: 0px;
  transform: translateY(-55%);
}
.take-photo {
  width: 70px;
  height: 70px;
  position: absolute;
  z-index: 100;
  bottom: 40px;
  left: 20%;
  // transform: translateX(-50%);
  border-radius: 50%;
  font-size: 28px;
  background: #ffffff;
}
.switch-camera {
  width: 70px;
  height: 70px;
  position: absolute;
  z-index: 100;
  bottom: 40px;
  right: 20%;
  // transform: translateX(-50%);
  border-radius: 50%;
  font-size: 28px;
  background: #ffffff;
}
.captured-image-content {
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  background: #000000;
}
.cancel-photo {
  color: #ffffff;
  border: unset;
  background: none;
  top: 0;
  left: 0;
  position: fixed;
}
.confirm-photo {
  color: #0570db;
  border: unset;
  background: none;
  top: 0;
  right: 0;
  position: fixed;
}
.captured-image {
  width: 100%;
  height: auto;
}
</style>

ps:代码使用ref绑定上传按钮到uploadBtn,使用uploadBtn.value.$el.click()触发按钮点击事件。

由于是Vant按钮组件,所以需要使用$el来获取原生DOM。

nextTick函数可以在DOM更新完成后执行回调。

假如使用ref绑定组件到orgPicker,那么可以使用orgPicker.value.show()来显示组件。

const orgPicker = ref(null);
const goToSelectNodePeople = () => {
  orgPicker.value.show();
};

 

2.编写androidOrIos.js

export const checkMobileModel = () => {
  let u = navigator.userAgent;
  let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;
  let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);

  if (isAndroid) {
    return 'Android'
  }

  if (isIOS) {
    return 'IOS'
  }

   return ''
}

 

3.导入两个文件

import AndImgCapture from "@/components/andImgCapture/andImgCapture.vue";
import { checkMobileModel } from "@/utils/androidOrIos";

 

4.在主页面实现拍照上传基本功能

<AndImgCapture
  v-if="checkMobileModel() === 'Android'"
  :after-read="(file) => uploaderAfter(file, 'promotionPhoto')"
  @click-upload="openUpload($event, item)"
>
</AndImgCapture>
<van-uploader
  v-else
  :multiple="true"
  :preview-image="false"
  :max-size="50000 * 1024"
  :after-read="(file) => uploaderAfter(file, 'promotionPhoto')"
  @click-upload="openUpload($event, item)"
  @oversize="onOversize"
  capture="camera"
  accept="image/*"
>
  <van-button icon="plus" type="primary">拍照上传</van-button>
</van-uploader>

即可。

posted @ 2024-09-23 15:41  罗毅豪  阅读(248)  评论(0编辑  收藏  举报