一、需求前提
特殊場(chǎng)景中,需要拍照的同時(shí)打開(kāi)閃光燈,(例如黑暗場(chǎng)景下的設(shè)備維護(hù)巡檢功能)。
起初我是用的uviewui
中的u-upload
組件自帶的拍照功能,但是這個(gè)不支持拍照時(shí)打開(kāi)閃光燈,也不支持從通知欄中打開(kāi)閃光燈。
二、解決方案
采用組合形式解決:
- 使用uniapp官方內(nèi)置組件中的 媒體組件:camera 實(shí)現(xiàn)閃光燈拍照,
uni.createCameraContext()
獲取返回圖片結(jié)果 - 結(jié)合uniapp官方內(nèi)置組件中的 視圖容器:cover-view 做定制化布局
1. 媒體組件:camera
camera
是頁(yè)面內(nèi)嵌的區(qū)域相機(jī)組件。注意這不是點(diǎn)擊后全屏打開(kāi)的相機(jī)。
其中flash
屬性可以動(dòng)態(tài)實(shí)現(xiàn)拍照閃光燈的功能,值為auto, on, off, torch
拍照動(dòng)作可以使用uni.createCameraContext()
獲取拍照的圖片結(jié)果,再做后續(xù)操作。
注意:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-860403.html
- camera 組件是由客戶端創(chuàng)建的原生組件,它的層級(jí)是最高的,不能通過(guò) z-index 控制層級(jí)??墒褂?cover-view 、cover-image 覆蓋在上面。
- 同一頁(yè)面只能插入一個(gè) camera 組件。(多次打開(kāi)自定義的拍照界面可以使用
v-if
做銷(xiāo)毀)
2. 視圖容器:cover-view
cover-view
是覆蓋在原生組件上的文本視圖。
app-vue和小程序框架,渲染引擎是webview的。但為了優(yōu)化體驗(yàn),部分組件如map、video、textarea、canvas通過(guò)原生控件實(shí)現(xiàn),原生組件層級(jí)高于前端組件(類(lèi)似flash層級(jí)高于div)。為了能正常覆蓋原生組件,設(shè)計(jì)了cover-view。
注意:
- 容器內(nèi)的每一個(gè)元素最好都用
cover-view
標(biāo)簽包裹(包括文字內(nèi)容),否則會(huì)出現(xiàn)渲染異常問(wèn)題。
三、 示例
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-860403.html
<!--
* @Description: 自定義文件上傳組件,支持拍照、閃光燈、本地圖片選擇
* @Doc: 雙向綁定使用 <customUpload :modelValue.sync="test"></customUpload>
* @Author: y
* @Date: 2024-03-07 09:51:25
-->
<template>
<view class="custom-upload">
<!-- 預(yù)覽圖片 -->
<template v-if="previewImage">
<view class="file-item" v-for="(item,index) in fileList" :key="index" :style="[{width,height}]">
<view v-if="item.status ==='uploading'" class="file-uploading">
<u-loading-icon color="#19be6b"></u-loading-icon>
</view>
<u--image v-else :showLoading="true" :src="item.thumb || item.url" :width="width" :height="height"
@tap="onPreviewImage(item)">
<template v-slot:loading>
<!-- 此處后期需要優(yōu)化為本地文件地址,避免走兩次加載 -->
<u-loading-icon text="加載中" textSize="18"></u-loading-icon>
</template>
</u--image>
<!-- 刪除按鈕角標(biāo) -->
<view class="upload-deletable" @tap.stop="deleteItem(index)">
<view class="upload-deletable-icon">
<u-icon name="close" color="#ffffff" size="10"></u-icon>
</view>
</view>
<!-- 文件狀態(tài)角標(biāo) -->
<view class="upload-success" v-if="item.status === 'success'">
<view class="upload-success-icon">
<u-icon name="checkmark" color="#ffffff" size="12"></u-icon>
</view>
</view>
</view>
</template>
<!-- 如果圖片數(shù)量在設(shè)定范圍內(nèi) -->
<template v-if="isInCount">
<view class="upload-button" @tap="chooseOperationType" :style="[{width,height}]">
<u-icon name="plus" size="26" color="#2979ff"></u-icon>
<text v-if="uploadText" class="upload-button-text">{{ uploadText }}</text>
<text v-else class="upload-button-text">上傳</text>
</view>
</template>
<!-- 選項(xiàng)彈出層 -->
<u-popup :show="showOptionsPopup" :round="10" mode="bottom" :closeable="true" @close="this.showOptionsPopup=false">
<view class="option-list">
<view v-if="showTakePhoto" class="option-btn" @tap="onTakePhoto">拍照</view>
<view v-if="showChoosePhoto" class="option-btn" @tap="onChoosePhoto">從相冊(cè)選擇</view>
<view class="option-btn-close" @tap="this.showOptionsPopup=false">取消</view>
</view>
</u-popup>
<!-- 相機(jī)彈出層 -->
<u-overlay v-if="showCameraPopup" :show="showCameraPopup" mask-click-able="false">
<!-- 添加v-if避免緩存相機(jī),每次打開(kāi)都需要重新創(chuàng)建 -->
<view class="camera-container">
<camera device-position="back" :flash="flashStatus" style="width: 100%; height: calc(100% - 200rpx);">
<cover-view class="user-location">
<!-- 此處只可以使用cover-image插入圖片(待開(kāi)發(fā)) -->
<cover-view v-if="!userLocationRefreshing" class="icon-location"></cover-view>
<cover-view v-else class="icon-location-refreshing"></cover-view>
<cover-view v-if="userLocationRefreshing" style="color: #ff9900;">
加載中...
</cover-view>
<cover-view>{{userLocation||'---'}}</cover-view>
</cover-view>
</camera>
<view class="camera-option-list">
<view class="option-btn" @tap.stop="$u.throttle(refreshLocation, 1000)">刷新定位</view>
<view class="option-btn" @tap.stop="takePhoto">拍照</view>
<view class="option-btn" @tap.stop="openFlash">{{flashStatus==='auto'?'閃光燈長(zhǎng)亮':'閃光燈自動(dòng)'}}</view>
</view>
</view>
</u-overlay>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { apiUrl } from '@/utils/env.js'; // 全局項(xiàng)目地址
export default {
name: "customUpload",
props: {
// 對(duì)外:上傳的文件列表 {status:success|uploading|fail, url:''}
modelValue: {
type: Array,
default: () => []
},
showTakePhoto: {
type: Boolean,
default: true
},
showChoosePhoto: {
type: Boolean,
default: true
},
// 上傳組件的寬度
width: {
type: String,
default: '180rpx'
},
// 上傳組件的高度
height: {
type: String,
default: '180rpx'
},
// 上傳圖標(biāo)的文字
uploadText: {
type: String,
default: ''
},
// 上傳文件的存儲(chǔ)位置
fileStorageLocation: {
type: String,
default: 'yhtest'
},
},
data() {
return {
fileList: [], // 對(duì)內(nèi):上傳的文件列表 {status:success|uploading|fail, url:''}
isFileError: false, // 文件列表出現(xiàn)故障(待開(kāi)發(fā))
previewImage: false, // 預(yù)覽圖片
isInCount: true, // 是在限制的文件數(shù)量范圍內(nèi)
showOptionsPopup: false, // 選項(xiàng)彈出層
showCameraPopup: false, // 相機(jī)彈出層
flashStatus: 'auto', // 閃光燈,值為auto, on, off, torch
userLocationRefreshing: false, // 用戶位置刷新中
userLocation: '', // 用戶位置
};
},
watch: {
// 監(jiān)聽(tīng)文件列表數(shù)據(jù)長(zhǎng)度變化,存在數(shù)據(jù)則顯示預(yù)覽
fileList(newData, oldData) {
this.$emit('update:modelValue', newData);
this.previewImage = newData.length ? true : false;
},
modelValue: {
handler: function(newData, oldData) {
this.fileList = newData;
},
immediate: true,
deep: true
}
},
computed: {
...mapState(['userInfo']),
},
async created() {
this.flashStatus = 'auto';
},
methods: {
// 引入vuex中方法
...mapActions(['getUserLocation']),
// 選擇操作類(lèi)型
chooseOperationType() {
this.showOptionsPopup = true;
this.refreshLocation(); // 獲取定位
},
// 拍照
onTakePhoto() {
this.flashStatus = 'auto';
this.showOptionsPopup = false;
this.showCameraPopup = true;
},
//從文件夾選擇
onChoosePhoto() {
this.showOptionsPopup = false;
uni.chooseMedia({
count: 9,
mediaType: ['image', 'video'], // 文件類(lèi)型
sourceType: ['album'], // 指定從相冊(cè)獲取
maxDuration: 30,
success: async (res) => {
// 按順序執(zhí)行異步操作,異步迭代
for (let item of res.tempFiles) {
const tempUrl = item.tempFilePath;
console.log('拍照的臨時(shí)圖片地址:', tempUrl);
this.fileList.push({
status: 'uploading', // 狀態(tài)為上傳中
url: tempUrl, // 文件的臨時(shí)地址
thumb: tempUrl, // 文件的臨時(shí)地址
});
const realUrl = await this.uploadFilePromise(item.tempFilePath); // 上傳圖片
console.log('上傳返回的真實(shí)圖片地址:', realUrl);
this.fileList.pop();
this.fileList.push({
status: 'success', // 狀態(tài)為上傳中
url: realUrl, // 文件的真實(shí)地址
thumb: tempUrl, // 文件的臨時(shí)地址
});
}
},
fail: (err) => {
console.log('文件夾選擇報(bào)錯(cuò):', err);
},
})
},
// 手動(dòng)拍照
async takePhoto() {
console.log('拍照按鈕點(diǎn)擊---------', new Date());
// 創(chuàng)建并返回 camera 組件的上下文 cameraContext 對(duì)象
const ctx = uni.createCameraContext();
setTimeout(() => {
this.showCameraPopup = false; // 關(guān)閉彈出層
}, 200);
await ctx.takePhoto({
quality: 'high',
success: async (res) => {
uni.$u.toast('拍攝成功');
// 返回照片文件的臨時(shí)路徑
const tempUrl = res.tempImagePath;
console.log('拍照的臨時(shí)圖片地址:', tempUrl);
this.fileList.push({
status: 'uploading', // 狀態(tài)為上傳中
url: tempUrl, // 文件的臨時(shí)地址
thumb: tempUrl, // 文件的臨時(shí)地址
});
const realUrl = await this.uploadFilePromise(res.tempImagePath); // 上傳圖片
console.log('上傳返回的真實(shí)圖片地址:', realUrl);
this.fileList.pop();
this.fileList.push({
status: 'success', // 狀態(tài)為上傳中
url: realUrl, // 文件的真實(shí)地址
thumb: tempUrl, // 文件的臨時(shí)地址
});
},
fail: (err) => {
console.log('手動(dòng)拍照?qǐng)?bào)錯(cuò):', err);
},
});
},
// 打開(kāi)閃光燈
openFlash() {
if (this.flashStatus === 'auto') {
this.flashStatus = 'torch'; // 閃光燈長(zhǎng)亮
} else {
this.flashStatus = 'auto'; // 閃光燈長(zhǎng)亮
}
},
// 刷新定位
async refreshLocation() {
this.userLocationRefreshing = true;
this.userLocation = await this.getUserLocation(); // 獲取用戶位置信息
setTimeout(() => {
this.userLocationRefreshing = false;
}, 1000)
},
// 上傳圖片
async uploadFilePromise(filePath) {
return new Promise((resolve, reject) => {
let token = "Bearer ";
token += uni.getStorageSync('token');
let a = uni.uploadFile({
url: `${apiUrl}/wx/wxfile/upload`, // 接口地址
filePath: filePath,
name: 'multipartFile', // 此處默認(rèn)值是file,實(shí)際需要根據(jù)后端接口做更改
header: {
'Content-Type': 'multipart/form-data',
'Authorization': token
},
// HTTP 請(qǐng)求中其他額外的 form data
formData: {
"cameraMan": this.userInfo.nickName || '---', // 拍攝人
"cameraSite": this.userLocation || '---', // 拍攝位置
"customPath": this.fileStorageLocation, // 自定義文件存放路徑
},
success: (res) => {
let parseData = JSON.parse(res.data);
console.log("上傳成功的地址", parseData);
resolve(parseData.data);
}
});
})
},
// 按下標(biāo)刪除圖片
deleteItem(index) {
this.fileList.splice(index, 1);
},
// 預(yù)覽圖片
onPreviewImage(item) {
if (item.status !== 'success') return;
uni.previewImage({
// 先f(wàn)ilter找出為圖片的item,再返回filter結(jié)果中的圖片url
urls: this.fileList.filter((item) => item.status === 'success' && item.url).map((item) => item.url || item
.thumb),
current: item.url || item.thumb,
fail() {
uni.$u.toast('預(yù)覽圖片失敗')
},
});
},
}
}
</script>
<style lang="scss">
.custom-upload {
// border: 1px dashed red;
display: flex;
flex-direction: row;
flex-wrap: wrap;
.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;
}
}
.camera-container {
position: relative;
width: 100%;
height: 100%;
.user-location {
position: absolute;
bottom: 20rpx;
left: 20rpx;
padding: 20rpx;
background-color: #cccccc9c;
color: #fff;
border-radius: 10rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.icon-location {
width: 30rpx;
height: 30rpx;
border-radius: 50%;
background-color: #19be6b;
margin: 6rpx;
border: 2px solid #ecddd5;
}
.icon-location-refreshing {
width: 30rpx;
height: 30rpx;
border-radius: 50%;
background-color: #ff9900;
margin: 6rpx;
border: 2px solid #ecddd5;
}
}
.camera-option-list {
width: 100%;
height: 200rpx;
background-color: #f4f5f7;
display: flex;
flex-direction: row;
.option-btn {
display: flex;
flex-direction: column;
justify-content: center;
border: 2px solid #2979ff;
box-sizing: border-box;
height: 100%;
width: 33.33%;
text-align: center;
font-size: 18px;
}
}
}
}
</style>
到了這里,關(guān)于【uniapp】uniapp小程序中實(shí)現(xiàn)拍照同時(shí)打開(kāi)閃光燈的功能,拍照閃光燈實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!