officialAccount/pages/addoldman/camera.vue

195 lines
4.3 KiB
Vue
Raw Normal View History

2025-06-19 17:03:31 +08:00
<template>
<view class="container">
<!-- 原生相机预览 -->
<camera id="idCamera" class="camera-preview" device-position="back" flash="off" binderror="onCameraError" />
<!-- 隐藏的裁剪 Canvas -->
<canvas canvas-id="cropCanvas"
style="width:100%;height:100%;position:absolute;top:0;left:0;display:none;"></canvas>
<!-- 遮罩层 -->
<view class="mask">
<view class="mask-block top" />
<view class="mask-block middle">
<view class="side" />
<view class="cutout">
<text class="hint">请将身份证放入此框内</text>
</view>
<view class="side" />
</view>
<view class="mask-block bottom" />
</view>
<!-- 底部拍照按钮 -->
<view class="controls">
<button class="shutter" @tap="takePhoto"> </button>
</view>
</view>
</template>
<script setup>
import {
onMounted,
ref
} from 'vue';
let cameraContext;
const sysInfo = uni.getSystemInfoSync(); // 获取屏幕宽高
onMounted(() => {
cameraContext = uni.createCameraContext();
});
function onCameraError(e) {
console.error('Camera Error:', e);
}
function takePhoto() {
cameraContext.takePhoto({
quality: 'high',
success: async (res) => {
const src = res.tempImagePath;
// 2.1 计算屏幕上 cutout 区域px
const screenW = sysInfo.windowWidth;
const screenH = sysInfo.windowHeight;
const topMaskH = screenH * 0.25; // mask-block.top 高度
const middleH = screenH * 0.50; // mask-block.middle 高度
const cutoutH = middleH * 0.60; // cutout 高度
const cutoutW = cutoutH * 1.586; // 按 aspect-ratio:1.586 计算宽度
const cutoutX = (screenW - cutoutW) / 2; // 居中
const cutoutY = topMaskH + (middleH - cutoutH) / 2;
// 2.2 拿到原图真实尺寸,用于映射
const info = await uni.getImageInfo({
src
});
const origW = info.width;
const origH = info.height;
// 2.3 计算裁剪在原图上的参数
const ratioW = origW / screenW;
const ratioH = origH / screenH;
const sx = cutoutX * ratioW;
const sy = cutoutY * ratioH;
const sWidth = cutoutW * ratioW;
const sHeight = cutoutH * ratioH;
// uni.authorize({
// scope: 'scope.camera',
// success: () => console.log('已授权相机'),
// fail: () => uni.showModal({ title: '权限不足', content: '请在设置里打开相机权限' })
// });
// 3. 在 Canvas 上裁切
const ctx = uni.createCanvasContext('cropCanvas', {
enableScroll: false
});
// 设置画布尺寸为裁剪区域的像素大小
ctx.drawImage(src, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'cropCanvas',
x: 0,
y: 0,
width: sWidth,
height: sHeight,
destWidth: sWidth,
destHeight: sHeight,
success: (cropRes) => {
// 裁切完成,存入缓存并返回
uni.setStorageSync('idcardPhoto', cropRes.tempFilePath);
uni.navigateBack({
delta: 1
});
},
fail: (err) => {
console.error('Canvas to TempFile Fail:', err);
}
});
});
},
fail: (err) => {
console.error('Take Photo Fail:', err);
}
});
}
</script>
<style scoped>
.container {
position: relative;
width: 100%;
height: 100vh;
background: #000;
}
.camera-preview {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.mask-block {
width: 100%;
background: rgba(0, 0, 0, 0.5);
}
.mask-block.top,
.mask-block.bottom {
height: 25%;
}
.mask-block.middle {
display: flex;
flex-direction: row;
height: 50%;
}
.side {
flex: 1;
background: rgba(0, 0, 0, 0.5);
}
.cutout {
width: 100%;
height: 60%;
aspect-ratio: 1.586;
border: 2rpx dashed #fff;
position: relative;
}
.hint {
position: absolute;
bottom: 20rpx;
width: 100%;
text-align: center;
color: #fff;
font-size: 24rpx;
}
.controls {
position: absolute;
bottom: 50rpx;
width: 100%;
display: flex;
justify-content: center;
}
.shutter {
width: 300rpx;
height: 120rpx;
border-radius: 60rpx;
background: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
}
</style>