195 lines
4.3 KiB
Vue
195 lines
4.3 KiB
Vue
<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> |