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>
即可。

浙公网安备 33010602011771号