APP自定义身份证相机(Android +iOS)

基本上同时兼容安卓和苹果的插件都需要付费,这里我找了2个好用的免费插件

1.仅支持安卓: 自定义身份证相机(支持蒙版自定义),内置蒙版,照片预览,身份证裁剪 - DCloud 插件市场

2.支持iOS(已测),支持Android(未测,应该也可以用):

自定义相机 - DCloud 插件市场

第一个插件使用方法(仅支持安卓):

创建一个realName.vue文件

javascript 复制代码
<view class="personalInfo" style="margin-top: 20rpx;">
        <view class="label" style="margin-bottom: 8rpx;">
            <view class="blue"></view>
            <view class="title">证件图片</view>
        </view>
        <view class="tips">请拍摄或上传身份证原件照片,确保照片完整清晰</view>
        <view class="imgBox">
            <view class="front">
                <image :src="frontImg" :class="platform == 'ios' ? 'transformImg' : ''" @click="uploadImgNew('front')"/>
                <view class="frontBtn" @click="uploadImgNew('front')" v-if="frontImg == ''">
                    上传人像面
                </view>
            </view>
            <view class="back">
                <image :src="backImg" :class="platform == 'ios' ? 'transformImg' : ''" @click="uploadImgNew('back')"/>
                <view class="backBtn" @click="uploadImgNew('back')" v-if="backImg == ''">
                    上传国徽面
                </view>
            </view>
        </view>
    </view>
javascript 复制代码
export default {
  data () {
    return {
        frontImg: '',
        backImg: '',
		canvasSiz:{
			width:188,
			height:273
		},
        flag: true
    }
  },
javascript 复制代码
methods:{
    uploadImgNew(types){
        console.log('打开相机');
        let platform = uni.getSystemInfoSync().platform
        if(!this.flag){
            uni.showToast({
                title: '图片正在上传中,请误做其他操作', 
                icon: 'none'
            })
            return
        }
        if(platform == 'ios'){
            // ios的另外用了别的插件,下面会讲到
            uni.navigateTo({
                url: `./idcard?dotype=${types == 'front' ? 'face' : 'badge'}`
            })
        }else{
            var cameraModule = uni.requireNativePlugin('yun-camerax-module');
            //无需蒙版可将type设置为非参数值,例如 type:99
            cameraModule.takePhoto({ 
                type: types == 'front' ? 0 : 1, 
                imageIndex: 2, fullSrc: true,
                text: types == 'front' ? '将身份证正面置于此区域内并对齐边缘' : '将身份证背面置于此区域内并对齐边缘'
            }, res => {
                console.log(res);
                uni.showModal({
                    title: '提示',
                    // content: JSON.stringify(res),
                    content: '请再次确认使用该图',
                    success: (res1) => {
                        if (res1.confirm) {
                            console.log('用户点击确定',res);
                            this.upLoadImg(res.file,types)
                        } else if (res1.cancel) {
                            console.log('用户点击取消');
                        }
                    }
                });
            });
        }
    },
    async upLoadImg(path,type) {
      setTimeout(()=>{
        uni.showToast({
              title: '上传中',
              icon: 'none'
        })
        this.flag = false
        uni.uploadFile({
          url: 'xxxxxx/upload', //后端上传接口
          filePath: path,
          name: "file",
          success: (res) => {
            console.log('res222',res);
            if(JSON.parse(res.data).code == 0){
                console.log('JSON.parse(res.data)',JSON.parse(res.data));
                if(type == 'front'){
                    this.frontImg = JSON.parse(res.data).data.rel_path
                    this.$forceUpdate()
                }else{
                    this.backImg = JSON.parse(res.data).data.rel_path
                    this.$forceUpdate()
                }
                this.flag = true   
            }else{
              uni.showToast({
                  title: JSON.parse(res.data).msg,
                  icon: 'none'
              })
            }
              
          },
          fail(e) {
            this.showTip("上传图片失败");
          },
        });
      },300)
    }, 

第二个插件使用方法:

需要用到live-pusher直播推流,在manifest.json中勾选,真机调试需要重新打自定义基座再重新运行

为了防止样式兼容问题,另外需配置如下:

在同级目录下创建idcard.nvue文件

然后把下面代码整个copy进去

javascript 复制代码
<template>
	<view class="live-camera" :style="{ width: windowWidth, height: windowHeight }">
		<view class="preview" :style="{ width: windowWidth, height: windowHeight - 65 }">
			<live-pusher
				id="livePusher"
				ref="livePusher"
				class="livePusher"
				mode="FHD"
				beauty="0"
				whiteness="0"
				:aspect="aspect"
				min-bitrate="1000"
				audio-quality="16KHz"
				:device-position="back"
				:auto-focus="true"
				:muted="true"
				:enable-camera="true"
				:enable-mic="false"
				:zoom="false"
				@statechange="statechange"
				:style="{ width: cameraWidth, height: cameraHeight }"
			></live-pusher>

			<!--提示语-->
			<cover-view class="remind">
				<text class="remind-text" style="">{{ message }}</text>
			</cover-view>

			<!--辅助线-->
			<cover-view class="outline-box" :style="{ width: windowWidth, height: windowHeight - 80}">
				<cover-image
					class="outline-img"
					:src="dotype == 'face' ? '/static/live-camera/outline/idcardface.png' : '/static/live-camera/outline/idcardbadge.png'"
					style=""
				></cover-image>
			</cover-view>
		</view>

		<view class="menu">
			<!--底部菜单区域背景-->
			<cover-image class="menu-mask" src="/static/live-camera/bar.png"></cover-image>

			<!--返回键-->
			<cover-image class="menu-back" @tap="back" src="/static/live-camera/back.png"></cover-image>

			<!--快门键-->
			<cover-image class="menu-snapshot" @tap="snapshot" src="/static/live-camera/shutter.png"></cover-image>

			<!--反转键-->
			<cover-image class="menu-flip" @tap="flip" src="/static/live-camera/flip.png"></cover-image>
		</view>
	</view>
</template>

<script>
let _this = null;
export default {
	data() {
		return {
			poenCarmeInterval: null, //打开相机的轮询
			dotype: 'face', //操作类型
			message: '', //提示
			aspect: '2:3', //比例
			cameraWidth: '', //相机画面宽度
			cameraHeight: '', //相机画面宽度
			windowWidth: '', //屏幕可用宽度
			windowHeight: '', //屏幕可用高度
			camerastate: false, //相机准备好了
			livePusher: null, //流视频对象
			snapshotsrc: null //快照
		};
	},
	onLoad(e) {
		console.log('e',e);
		_this = this;
		this.dotype = e.dotype;
		this.initCamera();
	},
	onReady() {
		uni.showToast({
			title: '相机加载中...',
			icon: 'none',
			duration: 800
		})
		this.livePusher = uni.createLivePusherContext('livePusher', this);
		console.log('this.livePusher',this.livePusher);
		this.startPreview(); //开启预览并设置摄像头
		this.poenCarme();
	},
	methods: {
		//轮询打开
		poenCarme() {
			//#ifdef APP-PLUS
			if (plus.os.name == 'Android') {
				console.log('111');
				this.poenCarmeInterval = setInterval(function() {
					console.log(_this.camerastate);
					if (!_this.camerastate) _this.startPreview();
				}, 2500);
			}else{
				console.log('2222');
			}
			//#endif
		},
		//初始化相机
		initCamera() {
			//处理安卓手机异步授权问题
			uni.getSystemInfo({
				success: (res) => {
					console.log('resxxxx',res);
					this.windowWidth = res.windowWidth;
					this.windowHeight = res.windowHeight;
					this.cameraWidth = res.windowWidth;
					this.cameraHeight = res.windowWidth * 1.5;
				}
			});
		},

		//开始预览
		startPreview() {
			console.log('执行开始预览');
			this.livePusher.startPreview({
				success: (a) => {
					console.log('aaa',a);
				}
			});
		},

		//停止预览
		stopPreview() {
			this.livePusher.stopPreview({
				success: a => {
					console.log('停止预览',a);
					this.camerastate = false; //标记相机未启动
				}
			});
		},

		//状态
		statechange(e) {
			//状态改变
			console.log(e);
			if (e.detail.code == 1007) {
				_this.camerastate = true;
			} else if (e.detail.code == -1301) {
				_this.camerastate = false;
			}
		},

		//返回
		back() {
			uni.navigateBack();
		},

		//抓拍
		snapshot() {
			this.livePusher.snapshot({
				success: e => {
					console.log('快门',e);
					this.snapshotsrc = e.message.tempImagePath;
					this.stopPreview();
					this.setImage();
					uni.navigateBack();
				}
			});
		},

		//反转
		flip() {
			this.livePusher.switchCamera();
		},

		//设置
		setImage() {
			let pages = getCurrentPages();
			let prevPage = pages[pages.length - 2]; //上一个页面

			//直接调用上一个页面的setImage()方法,把数据存到上一个页面中去
			prevPage.$vm.setImage({ path: this.snapshotsrc, dotype: this.dotype });
		}
	}
};
</script>

<style lang="scss">

.live-camera {
	.preview {
		justify-content: center;
		align-items: center;
		.outline-box {
			position: absolute;
			top: 0;
			left: 0;
			bottom: 0;
			z-index: 99;
			align-items: center;
			justify-content: center;
			.outline-img {
				width: 750rpx;
				height: 1125rpx;
			}
		}
		.remind {
			position: absolute;
			top: 880rpx;
			width: 750rpx;
			z-index: 100;
			align-items: center;
			justify-content: center;
			.remind-text {
				color: #dddddd;
				font-weight: bold;
			}
		}
	}
	.menu {
		position: absolute;
		left: 0;
		bottom: 0;
		width: 750rpx;
		height: 180rpx;
		z-index: 98;
		align-items: center;
		justify-content: center;
		.menu-mask {
			position: absolute;
			left: 0;
			bottom: 0;
			width: 750rpx;
			height: 180rpx;
			z-index: 98;
		}
		.menu-back {
			position: absolute;
			left: 30rpx;
			bottom: 50rpx;
			width: 80rpx;
			height: 80rpx;
			z-index: 99;
			align-items: center;
			justify-content: center;
		}
		.menu-snapshot {
			width: 130rpx;
			height: 130rpx;
			z-index: 99;
		}
		.menu-flip {
			position: absolute;
			right: 30rpx;
			bottom: 50rpx;
			width: 80rpx;
			height: 80rpx;
			z-index: 99;
			align-items: center;
			justify-content: center;
		}
	}
}

</style>

因为第一次使用nvue,代码整个都成灰色,需要在vscode设置中按下图配置,代码就会和.vue文件一样变彩色,方便阅读

在realName.vue文件中加入:

javascript 复制代码
<canvas id="canvas-clipper" canvas-id="canvas-clipper" type="2d" :style="{width: canvasSiz.width+'px',height: canvasSiz.height+'px',position: 'absolute',left:'-500000px',top: '-500000px'}" />

这里用到canvas是为了把live-pusher横屏拍摄的身份证裁剪

javascript 复制代码
methods:{
    //设置图片
	setImage(e) {
		console.log('跳回这个页面',e);
        let type = e.dotype == 'face' ? 'front' : 'back'
		//显示在页面
        console.log('type',type);
        // this.upLoadImg(e.path,type)
        this.zjzClipper(e.path,type)
	},
    //证件照裁切
	zjzClipper(path,type) {
		uni.getImageInfo({
			src: path,
			success: (image) => {
				console.log('image',image);
				this.canvasSiz.width =188;
				this.canvasSiz.height =273;
				
				
				//因为nvue页面使用canvas比较麻烦,所以在此页面上处理图片
				let ctx = uni.createCanvasContext('canvas-clipper', this);
				
				ctx.drawImage(
					path,
					parseInt(0.15 * image.width),
					parseInt(0.17 * image.height),
					parseInt(0.69 * image.width),
					parseInt(0.65 * image.height),
					0,
					0,
					188,
					273
				);
				
				ctx.draw(false, () => {
					uni.canvasToTempFilePath(
						{
							destWidth: 188,
							destHeight: 273,
							canvasId: 'canvas-clipper',
							fileType: 'jpg',
							success: (res) => {
								// this.savePhoto(res.tempFilePath);
                                // this.frontImg = res.tempFilePath
                                this.upLoadImg(res.tempFilePath,type)
							}
						},
						this
					);
				});
			}
		});
	},

如果想保存拍摄的照片还可以使用下面方法

javascript 复制代码
savePhoto(path){
		this.imagesrc = path;
		//保存到相册
		uni.saveImageToPhotosAlbum({
			filePath: path,
			success: () => {
				uni.showToast({
					title: '已保存至相册',
					duration: 2000
				});
			}
		});
	},

realName.vue的css样式部分

javascript 复制代码
.personalInfo{
        padding: 27rpx 30rpx;
        box-sizing: border-box;
        background-color: #fff;
        .label{
            display: flex;
            align-items: center;
            margin-bottom: 50rpx;
            .blue{
                width: 8rpx;
                height: 30rpx;
                background-color: #3E71FE;
                margin-right: 12rpx;
            }
            .title{
                font-weight: 500;
                font-size: 32rpx;
                color: #1D1B1A;
            }
        }
        .realName{
            display: flex;
            align-items: center;
            margin-bottom: 30rpx;
            padding-left: 20rpx;
            box-sizing: border-box;
            .name{
                font-weight: 500;
                font-size: 28rpx;
                color: #1D1B1A;
            }
        }
        .tips{
            font-weight: normal;
            font-size: 24rpx;
            color: #929292;
            margin-bottom: 30rpx;
        }
        .lineBottom{
            width: 650rpx;
            height: 2rpx;
            background: #F5F5F5;
            margin: auto;
            margin-bottom: 30rpx;
        }
        .imgBox{
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            padding: 0 82rpx;
            box-sizing: border-box;
            .front{
                position: relative;
                width: 526rpx;
                height: 288rpx;
                border-radius: 20rpx 20rpx 20rpx 20rpx;
                margin-bottom: 28rpx;
                background: url('https://res.qyjpay.cn/static/res/yewuban-smrz-zhengmian.png') no-repeat center;
                background-size: contain;
                .frontBtn{
                    position: absolute;
                    bottom: 50rpx;
                    left: 123rpx;
                    width: 280rpx;
                    height: 90rpx;
                    line-height: 90rpx;
                    background: #3E71FE;
                    box-shadow: 0rpx 8rpx 12rpx 1rpx rgba(62,113,254,0.15);
                    border-radius: 45rpx 45rpx 45rpx 45rpx;
                    font-size: 32rpx;
                    color: #FFFFFF;
                    font-weight: normal;
                    text-align: center;
                }
                image{
                    position: absolute;
                    left: 0;
                    top: 0;
                    width: 526rpx;
                    height: 288rpx;
                    border-radius: 20rpx 20rpx 20rpx 20rpx;
                }
            }
            .transformImg{
                position: absolute;
                left: 120rpx !important;
                top: -120rpx !important;
                transform: rotate(-90deg);
                width: 288rpx !important;
                height: 526rpx !important;
                z-index: 999;
            }
            .back{
                position: relative;
                width: 526rpx;
                height: 288rpx;
                border-radius: 20rpx 20rpx 20rpx 20rpx;
                background: url('https://res.qyjpay.cn/static/res/yewuban-smrz-fanmian.png') no-repeat center;
                background-size: contain;
                .backBtn{
                    position: absolute;
                    bottom: 50rpx;
                    left: 123rpx;
                    width: 280rpx;
                    height: 90rpx;
                    line-height: 90rpx;
                    background: #3E71FE;
                    box-shadow: 0rpx 8rpx 12rpx 1rpx rgba(62,113,254,0.15);
                    border-radius: 45rpx 45rpx 45rpx 45rpx;
                    font-size: 32rpx;
                    color: #FFFFFF;
                    font-weight: normal;
                    text-align: center;
                }
                image{
                    position: absolute;
                    left: 0;
                    top: 0;
                    width: 526rpx;
                    height: 288rpx;
                    border-radius: 20rpx 20rpx 20rpx 20rpx;
                }
            }
        }
    }

里面的图片和结构根据项目需求自行修改

相关推荐
HerayChen6 分钟前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野7 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11239 分钟前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
GIS程序媛—椰子22 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
小黄人软件35 分钟前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252031 小时前
group_concat配置影响程序出bug
android·bug
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
半开半落1 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt
周全全1 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql