相机模块代码:
<template> <view class="custom-upload" v-if="show"> <!-- 相机弹出层 --> <u-overlay :show="show" mask-click-able="false"> <view class="nav"> <view class="back-btn" @tap="back"> 退出 </view> <view class="flash" @tap="showOptionsPopup = true"> <!-- 闪光灯按钮 --> <image @click="setFlash()" v-if="flashStatus === 'auto'" src="@/static/flash/auto.png" mode="widthFix" style="width: 26px; height: 26px"></image> <image @click="setFlash()" v-if="flashStatus === 'off'" src="@/static/flash/off.png" mode="widthFix" style="width: 26px; height: 26px"></image> <image @click="setFlash()" v-if="flashStatus === 'torch'" src="@/static/flash/torch.png" mode="widthFix" style="width: 26px; height: 26px"></image> </view> </view> <view class="camera-container"> <view v-if="!isCameraReady" class="loading"> <u-loading mode="circle"></u-loading> <text>相机启动中...</text> </view> <camera v-if="show" device-position="back" :flash="flashStatus" style="width: 100%; height: 100%" @ready="onCameraReady" @error="onCameraError" :frame-size="frameSize" :resolution="resolution"> <cover-view class="camera-btn" @tap.stop="takePhoto"> <cover-view class="btn"></cover-view> </cover-view> </camera> </view> </u-overlay> </view> </template> <script> import { nextTick } from 'vue'; export default { name: "myCamera", props: { show: { type: Boolean, default: false, }, }, data() { return { showOptionsPopup: false, // 闪光灯选项弹出层 flashStatus: "auto", // 闪光灯,值为auto, on, off, torch flashList: ["auto", "torch", "off"], index: 0, cameraContext: null, isCameraReady: false, frameSize: "medium", // 使用中等尺寸,加快加载速度 resolution: "medium", // 使用中等分辨率,加快加载速度 }; }, watch: { show(newVal) { if (newVal) { // 显示时立即初始化相机 nextTick(() => { this.initCamera(); }) } else { this.isCameraReady = false; } }, }, methods: { initCamera() { if (!this.cameraContext) { this.cameraContext = uni.createCameraContext(); } }, onCameraReady() { console.log("相机就绪"); this.isCameraReady = true; }, onCameraError(err) { console.error("相机错误:", err); this.isCameraReady = false; uni.showToast({ title: "相机启动失败", icon: "none", }); }, async takePhoto() { if (!this.cameraContext) { this.initCamera(); } try { const res = await new Promise((resolve, reject) => { this.cameraContext.takePhoto({ quality: "normal", // 使用普通质量,加快拍照速度 success: resolve, fail: reject, }); }); const tempUrl = res.tempImagePath; this.$emit("getUrl", tempUrl); this.$emit("close"); } catch (err) { console.log("拍照报错:", err); uni.showToast({ title: "拍照失败", icon: "none", }); } }, back() { this.$emit("close"); }, setFlash() { if (this.index == 2) { this.index = 0; this.flashStatus = this.flashList[this.index]; } else { this.index++; this.flashStatus = this.flashList[this.index]; } }, }, beforeDestroy() { this.cameraContext = null; }, }; </script> <style lang="scss"> .custom-upload { // border: 1px dashed red; display: flex; flex-direction: row; flex-wrap: wrap; background-color: #fff; .file-item { position: relative; display: flex; flex-direction: column; justify-content: center; align-items: center; border-radius: 2px; margin: 0 8px 8px 0; box-sizing: border-box; .upload-deletable { position: absolute; top: 0; right: 0; background-color: #373737; height: 14px; width: 14px; display: flex; flex-direction: row; border-bottom-left-radius: 100px; align-items: center; justify-content: center; z-index: 3; .upload-deletable-icon { position: absolute; -webkit-transform: scale(0.7); transform: scale(0.7); top: 0px; right: 0px; } } .upload-success { position: absolute; bottom: 0; right: 0; display: flex; flex-direction: row; border-style: solid; border-top-color: transparent; border-left-color: transparent; border-bottom-color: #5ac725; border-right-color: #5ac725; border-width: 9px; align-items: center; justify-content: center; .upload-success-icon { position: absolute; -webkit-transform: scale(0.7); transform: scale(0.7); bottom: -10px; right: -10px; } } } .upload-button { padding: 10rpx; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #f4f5f7; border-radius: 2px; margin: 0 8px 8px 0; box-sizing: border-box; .upload-button-text { margin-top: 8rpx; color: #ccc; text-align: center; } } .option-list { display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 40rpx 40rpx 20rpx 40rpx; .option-btn { border-bottom: 1px solid #ccc6; padding: 30rpx; width: 100%; text-align: center; font-size: 16px; } .option-btn-close { padding: 30rpx; width: 100%; text-align: center; font-size: 16px; } } .nav { height: 100rpx; padding: 0 30rpx; padding-left: 40rpx; box-sizing: border-box; width: 100%; display: flex; flex-direction: row; justify-content: space-between; align-items: center; background-color: black; .back-btn { color: #fff; } } .camera-container { position: relative; width: 100vw; height: 100vh; background-color: #000; // .back-btn { // color: #fff; // position: absolute; // bottom: 140rpx; // left: 100rpx; // background-color: #fff; // border-radius: 50%; // padding: 15rpx; // display: flex; // place-content: center; // z-index: 999; // .back-btn-icon { // transform: rotate(180deg); // } // } // .flash-btn { // position: absolute; // top: 100rpx; // left: 100rpx; // } .camera-btn { position: absolute; bottom: 200rpx; left: 50%; transform: translateX(-50%); display: flex; align-items: center; justify-content: center; width: 170rpx; height: 170rpx; border-radius: 50%; background-color: #626264; .btn { width: 135rpx; height: 135rpx; border-radius: 50%; background-color: #fff; } } .loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; color: #fff; text { margin-top: 20rpx; font-size: 28rpx; } } } } </style>调用方式:
<cameraModule class="cameraModule" :show="cameraShow" @close="cameraClose" @getUrl="getPhoto" />const cameraShow = ref(false) const cameraClose = () => { console.log('关闭相机'); cameraShow.value = false } const getPhoto = async (url) => { console.log('获取照片地址', url); // 压缩图片 const compressRes = await new Promise((resolve, reject) => { uni.compressImage({ src: url, quality: 70, success: resolve, fail: reject }) }) // 上传图片 const uploadRes = await new Promise((resolve, reject) => { uni.uploadFile({ url: completion('(###上传地址###)'), filePath: compressRes.tempFilePath, name: 'file', header: { 'Authorization': 'Bearer ' + uni.getStorageSync( 'token') }, success: resolve, fail: reject }) }) // 处理返回数据 const data = JSON.parse(uploadRes.data) if (data.state !== 'success') throw new Error(data.message || '上传失败') // 添加到附件列表 deviceStatusAttachments.value.push(data.data.fileurl) uni.showToast({ icon: 'success', title: '上传成功', duration: 2000 }) }