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

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

相关推荐
阿巴斯甜1 小时前
ARouter
android
Andya_net2 小时前
MySQL | MySQL 8.0 权限管理实践-精确赋予库、表只读等权限
android·数据库·mysql
阿巴斯甜3 小时前
Map
android
巫山老妖3 小时前
鹅厂十年:三段式技术成长复盘
android·人工智能·程序员
爱上好庆祝3 小时前
学习js的第七天(wed APIs的开始)
前端·javascript·css·学习·html·css3
阿巴斯甜3 小时前
List集合
android
ooseabiscuit4 小时前
Laravel6.x核心优化与特性全解析
android·开发语言·javascript
gogoing5 小时前
CSS 属性值计算过程(Computed Value)
前端·css
阿巴斯甜5 小时前
Kotlin 协程 Coroutine
android
Jomurphys6 小时前
Compose 适配 - 通过 UiMediaScope 获取设备信息
android·compose