效果
思路
- 通過
live-pusher
這個視頻推流的組件來獲取攝像頭 - 拿到視頻的一幀圖片之后,跳轉到正常的 vue 頁面,通過 canvas 來處理圖片+水印
源碼
live-pusher
這個組件必須是 nvue
的
至于什么是 nvue,看這個官方文檔吧 https://uniapp.dcloud.net.cn/tutorial/nvue-outline.html
// index.nvue
<template><view class="pengke-camera" :style="{ width: windowWidth, height: windowHeight }"><!-- live-pusher 顯示實時畫面 --><live-pusherid="livePusher"class="livePusher"mode="FHD"device-position="back":muted="true":enable-camera="true":enable-mic="false":auto-focus="true":zoom="false":style="{ width: windowWidth, height: windowHeight }"></live-pusher><!-- 拍照按鈕 --><view class="menu"><cover-image class="menu-snapshot" @tap="snapshot" src="./icon/snapshot.png"></cover-image></view></view>
</template><script>
export default {data() {return {windowWidth: '',windowHeight: '',livePusher: null}},onLoad() {this.initCamera()},onReady() {const pages = getCurrentPages()const currentPage = pages[pages.length - 1]this.livePusher = uni.createLivePusherContext('livePusher', currentPage)this.livePusher.startPreview()},methods: {initCamera() {uni.getSystemInfo({success: res => {this.windowWidth = res.windowWidththis.windowHeight = res.windowHeight}})},snapshot() {this.livePusher.snapshot({success: e => {const path = e.message.tempImagePathuni.navigateTo({url: `/pages/camera/preview?img=${encodeURIComponent(path)}`})},fail: err => {console.error('拍照失敗', err)}})}}
}
</script><style lang="less">
.pengke-camera {position: relative;.livePusher {position: absolute;top: 0;left: 0;width: 100%;height: 100%;}.menu {position: absolute;bottom: 60rpx;left: 0;right: 0;display: flex;justify-content: center;align-items: center;z-index: 10;pointer-events: auto;.menu-snapshot {width: 130rpx;height: 130rpx;background-color: #fff;border-radius: 65rpx;box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.2);}}
}</style>
預覽文件 preview.vue
// preview.vue
<template><view class="preview-page"><!-- 可見 canvas 顯示圖 + 水印 --><canvascanvas-id="watermarkCanvas"id="watermarkCanvas"class="watermark-canvas":style="{ width: screenWidth + 'px', height: screenHeight + 'px' }"></canvas><!-- 操作按鈕 --><view class="preview-actions"><button class="action-btn cancel-btn" @click="goBack">取消</button><button class="action-btn confirm-btn" @click="exportImage">確認</button></view></view>
</template><script setup>
import { ref, nextTick, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import message from '@/utils/message'const imgUrl = ref('')
const canvasWidth = ref(0)
const canvasHeight = ref(0)
const currentTime = ref('')
const locationText = ref('正在獲取位置信息...')const screenWidth = uni.getSystemInfoSync().windowWidth
const screenHeight = uni.getSystemInfoSync().windowHeightonLoad((options) => {if (options.img) {imgUrl.value = decodeURIComponent(options.img)initImage()}updateTime()updateLocation()
})function updateTime() {const now = new Date()currentTime.value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
}function updateLocation() {uni.getLocation({type: 'wgs84',success: (res) => {locationText.value = `經度: ${res.longitude} 緯度: ${res.latitude}`drawCanvas()},fail: () => {locationText.value = '位置信息獲取失敗'drawCanvas()}})
}function initImage() {uni.getImageInfo({src: imgUrl.value,success(res) {canvasWidth.value = res.widthcanvasHeight.value = res.heightdrawCanvas()},fail(err) {console.error('圖片信息獲取失敗:', err)}})
}// 繪制預覽 canvas
function drawCanvas() {if (!imgUrl.value || !canvasWidth.value || !canvasHeight.value) returnnextTick(() => {const ctx = uni.createCanvasContext('watermarkCanvas')// 繪制原圖ctx.drawImage(imgUrl.value, 0, 0, screenWidth, screenHeight)// 設置水印樣式ctx.setFontSize(16)ctx.setFillStyle('white')ctx.setTextAlign('left')ctx.fillText(currentTime.value, 20, screenHeight - 160)ctx.setFontSize(16)ctx.fillText(locationText.value, 20, screenHeight - 120)ctx.draw()})
}// 點擊確認導出
function exportImage() {// 顯示加載提示uni.showLoading({title: '導出中...',mask: true // 防止點擊遮罩層關閉})uni.canvasToTempFilePath({canvasId: 'watermarkCanvas',destWidth: canvasWidth.value,destHeight: canvasHeight.value,success: (res) => {// 隱藏加載提示uni.hideLoading()console.log('導出成功:', res.tempFilePath)message.success(`導出成功! 文件路徑為 ${res.tempFilePath}`)uni.previewImage({ urls: [res.tempFilePath] })},fail: (err) => {// 隱藏加載提示uni.hideLoading()console.error('導出失敗:', err)uni.showToast({ title: '導出失敗', icon: 'none' })}})
}function goBack() {uni.navigateBack()
}
</script><style scoped lang="scss">
.preview-page {width: 100vw;height: 100vh;position: relative;background: #000;overflow: hidden;
}.watermark-canvas {position: absolute;left: 0;top: 0;z-index: 1;
}.preview-actions {position: fixed;left: 0;right: 0;bottom: 60rpx;display: flex;justify-content: center;gap: 40rpx;z-index: 10;
}.action-btn {padding: 0 40rpx;height: 80rpx;line-height: 80rpx;border-radius: 40rpx;font-size: 28rpx;background: #fff;color: #333;border: none;opacity: 0.9;
}.cancel-btn {background: #eee;
}.confirm-btn {background: #19be6b;color: #fff;
}
</style>