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>
|