效果
思路
- 通过
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>