UniApp身份证拍照组件
UniApp 身份证拍照
<template>
<view class="take-id-card-container">
<camera
device-position="back"
flash="off"
@error="error"
class="camera"
style="width: 100%; height: 100vh; position: absolute"
v-if="!photoImg"
@initdone="readyPhone"
></camera>
<view class="take-id-card-photo">
<view class="tips-top tips mask po-top-0">
<view class="p-b-20">将身份证人像面对准此区</view>
</view>
<view class="tips-bottom tips mask po-bottom-0">
<view class="p-t-20">拍摄要求:清晰完整,避免缺边、模糊、反光</view>
</view>
<view class="photo-area">
<view class="flex-1 mask"></view>
<view class="area-content" style="position: relative">
<image :src="photoImg" v-if="photoImg" />
<view class="area-border" v-else></view>
<view class="border po-top-0" style="border-bottom: none; border-right: none"></view>
<view class="border po-bottom-0" style="border-top: none; border-right: none"></view>
<view class="border po-bottom-0 po-right-0" style="border-top: none; border-left: none"></view>
<view class="border po-top-0 po-right-0" style="border-bottom: none; border-left: none"></view>
</view>
<view class="flex-1 mask"></view>
</view>
<view class="has-photo-btns po-bottom-0" v-if="photoImg">
<button class="cancel" @click="handleCancel">重拍</button>
<button class="confirm" @click="handleConfirm">确认使用</button>
</view>
<view class="take-photo-btns po-bottom-0 p-b-30" v-else>
<view style="position: absolute; left: -68px; color: #fff" @click="goBack">取消</view>
<view class="take-photo" @click="takePhoto">
<view class="outside"></view>
<view class="inside"></view>
</view>
</view>
</view>
<view style="width: 0; height: 0; overflow: hidden">
<canvas class="canvas" canvas-id="myCanvas" style="width: 330px; height: 210px"></canvas>
</view>
</view>
</template>
<script lang="ts">
import { nextTick, reactive, toRefs, getCurrentInstance, defineComponent, onMounted, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
export default defineComponent({
name: 'idCard',
setup() {
const idCardCameraRef = ref()
const state = reactive({
photoImg: '',
photoWidth: 332,
photoHeight: 210,
that: '',
cameraDisabled: true,
hasPhoneAuth: false
})
const takePhoto = () => {
if (state.cameraDisabled) {
uni.showToast({
title: '相机准备中,请稍等',
icon: 'none'
})
return
}
if (!state.hasPhoneAuth) {
uni.showToast({
title: '尚未进行授权,该功能将无法使用',
icon: 'none'
})
return
}
const ctx = uni.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: res => {
clipImg(res.tempImagePath)
},
error(e) {
console.log(e.detail)
}
})
}
onMounted(() => {
authorize()
state.that = getCurrentInstance()?.proxy
})
const authorize = () => {
uni.getSetting({
success: res => {
if (!res.authSetting['scope.camera']) {
uni.authorize({
scope: 'scope.camera',
success: () => {
console.log('授权成功')
state.hasPhoneAuth = true
},
fail: () => {
uni.showModal({
title: '提示',
content: '尚未进行授权,该功能将无法使用',
success: res1 => {
if (res1.confirm) {
uni.openSetting({
success: setting => {
if (setting.authSetting['scope.camera']) {
uni.showToast({
title: '授权成功!',
icon: 'none'
})
state.hasPhoneAuth = true
} else {
uni.showToast({
title: '授权失败!',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1000)
}
}
})
} else {
uni.navigateBack()
}
}
})
}
})
}
}
})
}
const clipImg = photoImg => {
const ctx = uni.createCanvasContext('myCanvas', state.that),
{ photoHeight, photoWidth } = state
let { windowWidth, windowHeight, devicePixelRatio } = uni.getSystemInfoSync()
const startX = windowWidth / 2 - photoWidth / 2
const statrY = windowHeight / 2 - photoHeight / 2 - 80
ctx.drawImage(photoImg, -startX, -statrY, windowWidth, windowHeight)
uni.showLoading({
title: '正在拍摄'
})
ctx.draw(
false,
(() => {
setTimeout(() => {
uni.canvasToTempFilePath(
{
canvasId: 'myCanvas',
width: photoWidth,
height: photoHeight,
destHeight: photoHeight,
destWidth: photoHeight,
quality: 1,
fileType: 'jpg',
success: res => {
ctx.clearRect(0, 0, photoWidth, photoHeight)
uni.canvasToTempFilePath(
{
canvasId: 'myCanvas',
width: photoWidth,
destHeight: photoHeight,
destWidth: photoWidth,
quality: 1,
fileType: 'jpg',
success: res => {
console.log(res.tempFilePath)
state.photoImg = res.tempFilePath
uni.hideLoading()
}
},
state.that
)
}
},
state.that
)
}, 500)
})()
)
}
const error = () => {
uni.showToast({
title: '尚未进行授权,该功能将无法使用',
icon: 'none'
})
}
const photoFail = error => {
console.log('拍照异常', error)
}
const handleCancel = () => {
state.photoImg = ''
}
const handleConfirm = () => {
}
const goBack = () => {
uni.navigateBack()
}
const readyPhone = () => {
state.cameraDisabled = false
}
const handleSuccess = (filePath: String) => {
console.log('身份证照片地址:', filePath)
}
return {
...toRefs(state),
idCardCameraRef,
takePhoto,
error,
handleCancel,
goBack,
handleConfirm,
readyPhone,
photoFail,
handleSuccess
}
}
})
</script>
<style scoped lang="scss">
.take-id-card-container {
position: relative;
}
.take-id-card-photo {
font-family: PingFang SC-粗体, PingFang SC;
position: relative;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
position: absolute;
text-align: center;
.mask {
background: rgba(0, 0, 0, 0.5);
}
.tips {
position: absolute;
width: 100%;
}
.tips-top {
font-size: 36rpx;
font-weight: normal;
color: #ffffff;
line-height: 20px;
display: flex;
flex-direction: column;
justify-content: flex-end;
height: calc(50vh - 185px);
}
.tips-bottom {
font-size: 28rpx;
font-weight: normal;
color: #ffffff;
line-height: 20px;
height: calc(50vh - 25px);
}
.camera {
width: 100%;
height: 100vh;
position: absolute;
}
.photo-area {
z-index: 2;
display: flex;
width: 100%;
position: absolute;
top: calc(50vh - 185px);
left: 50%;
transform: translateX(-50%);
image {
width: 332px;
height: 210px;
}
.area-content {
width: 332px;
height: 210px;
}
.area-border {
width: 332px;
height: 210px;
}
.border {
width: 16px;
height: 16px;
border: 3px solid #fd7359;
position: absolute;
}
}
.has-photo-btns {
width: 100vw;
background: #ffffff;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.0397);
display: flex;
position: fixed;
padding: 7px 14px 20px 14px;
font-size: 16px;
button {
line-height: 44px;
border-radius: 22px;
padding: 0 54px;
}
}
.cancel {
background: #ffffff;
border: 1px solid #dddddd;
color: #333333;
}
.confirm {
background: #fd7359;
box-shadow: 0px 4px 8px 0px rgba(253, 115, 89, 0.201);
color: #ffffff;
}
.take-photo-btns {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
.take-photo {
position: relative;
view {
border-radius: 50%;
}
}
.outside {
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.3);
}
.inside {
width: 64px;
height: 64px;
background: #ffffff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.po-bottom-0 {
bottom: 0;
}
.po-top-0 {
top: 0;
}
.po-left-0 {
left: 0;
}
.po-right-0 {
right: 0;
}
.border-top-none {
border-top: none;
}
.border-bottom-none {
border-bottom: none;
}
.border-left-none {
border-left: none;
}
.border-right-none {
border-right: none;
}
.flex-1 {
flex: 1;
}
}
</style>