当前位置:   article > 正文

APP自定义身份证相机(Android +iOS)_vue 拍摄身份证组件

vue 拍摄身份证组件

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

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

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

 自定义相机 - DCloud 插件市场

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

创建一个realName.vue文件

  1. <view class="personalInfo" style="margin-top: 20rpx;">
  2. <view class="label" style="margin-bottom: 8rpx;">
  3. <view class="blue"></view>
  4. <view class="title">证件图片</view>
  5. </view>
  6. <view class="tips">请拍摄或上传身份证原件照片,确保照片完整清晰</view>
  7. <view class="imgBox">
  8. <view class="front">
  9. <image :src="frontImg" :class="platform == 'ios' ? 'transformImg' : ''" @click="uploadImgNew('front')"/>
  10. <view class="frontBtn" @click="uploadImgNew('front')" v-if="frontImg == ''">
  11. 上传人像面
  12. </view>
  13. </view>
  14. <view class="back">
  15. <image :src="backImg" :class="platform == 'ios' ? 'transformImg' : ''" @click="uploadImgNew('back')"/>
  16. <view class="backBtn" @click="uploadImgNew('back')" v-if="backImg == ''">
  17. 上传国徽面
  18. </view>
  19. </view>
  20. </view>
  21. </view>
  1. export default {
  2. data () {
  3. return {
  4. frontImg: '',
  5. backImg: '',
  6. canvasSiz:{
  7. width:188,
  8. height:273
  9. },
  10. flag: true
  11. }
  12. },
  1. methods:{
  2. uploadImgNew(types){
  3. console.log('打开相机');
  4. let platform = uni.getSystemInfoSync().platform
  5. if(!this.flag){
  6. uni.showToast({
  7. title: '图片正在上传中,请误做其他操作',
  8. icon: 'none'
  9. })
  10. return
  11. }
  12. if(platform == 'ios'){
  13. // ios的另外用了别的插件,下面会讲到
  14. uni.navigateTo({
  15. url: `./idcard?dotype=${types == 'front' ? 'face' : 'badge'}`
  16. })
  17. }else{
  18. var cameraModule = uni.requireNativePlugin('yun-camerax-module');
  19. //无需蒙版可将type设置为非参数值,例如 type:99
  20. cameraModule.takePhoto({
  21. type: types == 'front' ? 0 : 1,
  22. imageIndex: 2, fullSrc: true,
  23. text: types == 'front' ? '将身份证正面置于此区域内并对齐边缘' : '将身份证背面置于此区域内并对齐边缘'
  24. }, res => {
  25. console.log(res);
  26. uni.showModal({
  27. title: '提示',
  28. // content: JSON.stringify(res),
  29. content: '请再次确认使用该图',
  30. success: (res1) => {
  31. if (res1.confirm) {
  32. console.log('用户点击确定',res);
  33. this.upLoadImg(res.file,types)
  34. } else if (res1.cancel) {
  35. console.log('用户点击取消');
  36. }
  37. }
  38. });
  39. });
  40. }
  41. },
  42. async upLoadImg(path,type) {
  43. setTimeout(()=>{
  44. uni.showToast({
  45. title: '上传中',
  46. icon: 'none'
  47. })
  48. this.flag = false
  49. uni.uploadFile({
  50. url: 'xxxxxx/upload', //后端上传接口
  51. filePath: path,
  52. name: "file",
  53. success: (res) => {
  54. console.log('res222',res);
  55. if(JSON.parse(res.data).code == 0){
  56. console.log('JSON.parse(res.data)',JSON.parse(res.data));
  57. if(type == 'front'){
  58. this.frontImg = JSON.parse(res.data).data.rel_path
  59. this.$forceUpdate()
  60. }else{
  61. this.backImg = JSON.parse(res.data).data.rel_path
  62. this.$forceUpdate()
  63. }
  64. this.flag = true
  65. }else{
  66. uni.showToast({
  67. title: JSON.parse(res.data).msg,
  68. icon: 'none'
  69. })
  70. }
  71. },
  72. fail(e) {
  73. this.showTip("上传图片失败");
  74. },
  75. });
  76. },300)
  77. },

第二个插件使用方法:

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

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

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

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

  1. <template>
  2. <view class="live-camera" :style="{ width: windowWidth, height: windowHeight }">
  3. <view class="preview" :style="{ width: windowWidth, height: windowHeight - 65 }">
  4. <live-pusher
  5. id="livePusher"
  6. ref="livePusher"
  7. class="livePusher"
  8. mode="FHD"
  9. beauty="0"
  10. whiteness="0"
  11. :aspect="aspect"
  12. min-bitrate="1000"
  13. audio-quality="16KHz"
  14. :device-position="back"
  15. :auto-focus="true"
  16. :muted="true"
  17. :enable-camera="true"
  18. :enable-mic="false"
  19. :zoom="false"
  20. @statechange="statechange"
  21. :style="{ width: cameraWidth, height: cameraHeight }"
  22. ></live-pusher>
  23. <!--提示语-->
  24. <cover-view class="remind">
  25. <text class="remind-text" style="">{{ message }}</text>
  26. </cover-view>
  27. <!--辅助线-->
  28. <cover-view class="outline-box" :style="{ width: windowWidth, height: windowHeight - 80}">
  29. <cover-image
  30. class="outline-img"
  31. :src="dotype == 'face' ? '/static/live-camera/outline/idcardface.png' : '/static/live-camera/outline/idcardbadge.png'"
  32. style=""
  33. ></cover-image>
  34. </cover-view>
  35. </view>
  36. <view class="menu">
  37. <!--底部菜单区域背景-->
  38. <cover-image class="menu-mask" src="/static/live-camera/bar.png"></cover-image>
  39. <!--返回键-->
  40. <cover-image class="menu-back" @tap="back" src="/static/live-camera/back.png"></cover-image>
  41. <!--快门键-->
  42. <cover-image class="menu-snapshot" @tap="snapshot" src="/static/live-camera/shutter.png"></cover-image>
  43. <!--反转键-->
  44. <cover-image class="menu-flip" @tap="flip" src="/static/live-camera/flip.png"></cover-image>
  45. </view>
  46. </view>
  47. </template>
  48. <script>
  49. let _this = null;
  50. export default {
  51. data() {
  52. return {
  53. poenCarmeInterval: null, //打开相机的轮询
  54. dotype: 'face', //操作类型
  55. message: '', //提示
  56. aspect: '2:3', //比例
  57. cameraWidth: '', //相机画面宽度
  58. cameraHeight: '', //相机画面宽度
  59. windowWidth: '', //屏幕可用宽度
  60. windowHeight: '', //屏幕可用高度
  61. camerastate: false, //相机准备好了
  62. livePusher: null, //流视频对象
  63. snapshotsrc: null //快照
  64. };
  65. },
  66. onLoad(e) {
  67. console.log('e',e);
  68. _this = this;
  69. this.dotype = e.dotype;
  70. this.initCamera();
  71. },
  72. onReady() {
  73. uni.showToast({
  74. title: '相机加载中...',
  75. icon: 'none',
  76. duration: 800
  77. })
  78. this.livePusher = uni.createLivePusherContext('livePusher', this);
  79. console.log('this.livePusher',this.livePusher);
  80. this.startPreview(); //开启预览并设置摄像头
  81. this.poenCarme();
  82. },
  83. methods: {
  84. //轮询打开
  85. poenCarme() {
  86. //#ifdef APP-PLUS
  87. if (plus.os.name == 'Android') {
  88. console.log('111');
  89. this.poenCarmeInterval = setInterval(function() {
  90. console.log(_this.camerastate);
  91. if (!_this.camerastate) _this.startPreview();
  92. }, 2500);
  93. }else{
  94. console.log('2222');
  95. }
  96. //#endif
  97. },
  98. //初始化相机
  99. initCamera() {
  100. //处理安卓手机异步授权问题
  101. uni.getSystemInfo({
  102. success: (res) => {
  103. console.log('resxxxx',res);
  104. this.windowWidth = res.windowWidth;
  105. this.windowHeight = res.windowHeight;
  106. this.cameraWidth = res.windowWidth;
  107. this.cameraHeight = res.windowWidth * 1.5;
  108. }
  109. });
  110. },
  111. //开始预览
  112. startPreview() {
  113. console.log('执行开始预览');
  114. this.livePusher.startPreview({
  115. success: (a) => {
  116. console.log('aaa',a);
  117. }
  118. });
  119. },
  120. //停止预览
  121. stopPreview() {
  122. this.livePusher.stopPreview({
  123. success: a => {
  124. console.log('停止预览',a);
  125. this.camerastate = false; //标记相机未启动
  126. }
  127. });
  128. },
  129. //状态
  130. statechange(e) {
  131. //状态改变
  132. console.log(e);
  133. if (e.detail.code == 1007) {
  134. _this.camerastate = true;
  135. } else if (e.detail.code == -1301) {
  136. _this.camerastate = false;
  137. }
  138. },
  139. //返回
  140. back() {
  141. uni.navigateBack();
  142. },
  143. //抓拍
  144. snapshot() {
  145. this.livePusher.snapshot({
  146. success: e => {
  147. console.log('快门',e);
  148. this.snapshotsrc = e.message.tempImagePath;
  149. this.stopPreview();
  150. this.setImage();
  151. uni.navigateBack();
  152. }
  153. });
  154. },
  155. //反转
  156. flip() {
  157. this.livePusher.switchCamera();
  158. },
  159. //设置
  160. setImage() {
  161. let pages = getCurrentPages();
  162. let prevPage = pages[pages.length - 2]; //上一个页面
  163. //直接调用上一个页面的setImage()方法,把数据存到上一个页面中去
  164. prevPage.$vm.setImage({ path: this.snapshotsrc, dotype: this.dotype });
  165. }
  166. }
  167. };
  168. </script>
  169. <style lang="scss">
  170. .live-camera {
  171. .preview {
  172. justify-content: center;
  173. align-items: center;
  174. .outline-box {
  175. position: absolute;
  176. top: 0;
  177. left: 0;
  178. bottom: 0;
  179. z-index: 99;
  180. align-items: center;
  181. justify-content: center;
  182. .outline-img {
  183. width: 750rpx;
  184. height: 1125rpx;
  185. }
  186. }
  187. .remind {
  188. position: absolute;
  189. top: 880rpx;
  190. width: 750rpx;
  191. z-index: 100;
  192. align-items: center;
  193. justify-content: center;
  194. .remind-text {
  195. color: #dddddd;
  196. font-weight: bold;
  197. }
  198. }
  199. }
  200. .menu {
  201. position: absolute;
  202. left: 0;
  203. bottom: 0;
  204. width: 750rpx;
  205. height: 180rpx;
  206. z-index: 98;
  207. align-items: center;
  208. justify-content: center;
  209. .menu-mask {
  210. position: absolute;
  211. left: 0;
  212. bottom: 0;
  213. width: 750rpx;
  214. height: 180rpx;
  215. z-index: 98;
  216. }
  217. .menu-back {
  218. position: absolute;
  219. left: 30rpx;
  220. bottom: 50rpx;
  221. width: 80rpx;
  222. height: 80rpx;
  223. z-index: 99;
  224. align-items: center;
  225. justify-content: center;
  226. }
  227. .menu-snapshot {
  228. width: 130rpx;
  229. height: 130rpx;
  230. z-index: 99;
  231. }
  232. .menu-flip {
  233. position: absolute;
  234. right: 30rpx;
  235. bottom: 50rpx;
  236. width: 80rpx;
  237. height: 80rpx;
  238. z-index: 99;
  239. align-items: center;
  240. justify-content: center;
  241. }
  242. }
  243. }
  244. </style>

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

在realName.vue文件中加入:

<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横屏拍摄的身份证裁剪

  1. methods:{
  2. //设置图片
  3. setImage(e) {
  4. console.log('跳回这个页面',e);
  5. let type = e.dotype == 'face' ? 'front' : 'back'
  6. //显示在页面
  7. console.log('type',type);
  8. // this.upLoadImg(e.path,type)
  9. this.zjzClipper(e.path,type)
  10. },
  11. //证件照裁切
  12. zjzClipper(path,type) {
  13. uni.getImageInfo({
  14. src: path,
  15. success: (image) => {
  16. console.log('image',image);
  17. this.canvasSiz.width =188;
  18. this.canvasSiz.height =273;
  19. //因为nvue页面使用canvas比较麻烦,所以在此页面上处理图片
  20. let ctx = uni.createCanvasContext('canvas-clipper', this);
  21. ctx.drawImage(
  22. path,
  23. parseInt(0.15 * image.width),
  24. parseInt(0.17 * image.height),
  25. parseInt(0.69 * image.width),
  26. parseInt(0.65 * image.height),
  27. 0,
  28. 0,
  29. 188,
  30. 273
  31. );
  32. ctx.draw(false, () => {
  33. uni.canvasToTempFilePath(
  34. {
  35. destWidth: 188,
  36. destHeight: 273,
  37. canvasId: 'canvas-clipper',
  38. fileType: 'jpg',
  39. success: (res) => {
  40. // this.savePhoto(res.tempFilePath);
  41. // this.frontImg = res.tempFilePath
  42. this.upLoadImg(res.tempFilePath,type)
  43. }
  44. },
  45. this
  46. );
  47. });
  48. }
  49. });
  50. },

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

  1. savePhoto(path){
  2. this.imagesrc = path;
  3. //保存到相册
  4. uni.saveImageToPhotosAlbum({
  5. filePath: path,
  6. success: () => {
  7. uni.showToast({
  8. title: '已保存至相册',
  9. duration: 2000
  10. });
  11. }
  12. });
  13. },

realName.vue的css样式部分

  1. .personalInfo{
  2. padding: 27rpx 30rpx;
  3. box-sizing: border-box;
  4. background-color: #fff;
  5. .label{
  6. display: flex;
  7. align-items: center;
  8. margin-bottom: 50rpx;
  9. .blue{
  10. width: 8rpx;
  11. height: 30rpx;
  12. background-color: #3E71FE;
  13. margin-right: 12rpx;
  14. }
  15. .title{
  16. font-weight: 500;
  17. font-size: 32rpx;
  18. color: #1D1B1A;
  19. }
  20. }
  21. .realName{
  22. display: flex;
  23. align-items: center;
  24. margin-bottom: 30rpx;
  25. padding-left: 20rpx;
  26. box-sizing: border-box;
  27. .name{
  28. font-weight: 500;
  29. font-size: 28rpx;
  30. color: #1D1B1A;
  31. }
  32. }
  33. .tips{
  34. font-weight: normal;
  35. font-size: 24rpx;
  36. color: #929292;
  37. margin-bottom: 30rpx;
  38. }
  39. .lineBottom{
  40. width: 650rpx;
  41. height: 2rpx;
  42. background: #F5F5F5;
  43. margin: auto;
  44. margin-bottom: 30rpx;
  45. }
  46. .imgBox{
  47. display: flex;
  48. flex-direction: column;
  49. justify-content: space-between;
  50. padding: 0 82rpx;
  51. box-sizing: border-box;
  52. .front{
  53. position: relative;
  54. width: 526rpx;
  55. height: 288rpx;
  56. border-radius: 20rpx 20rpx 20rpx 20rpx;
  57. margin-bottom: 28rpx;
  58. background: url('https://res.qyjpay.cn/static/res/yewuban-smrz-zhengmian.png') no-repeat center;
  59. background-size: contain;
  60. .frontBtn{
  61. position: absolute;
  62. bottom: 50rpx;
  63. left: 123rpx;
  64. width: 280rpx;
  65. height: 90rpx;
  66. line-height: 90rpx;
  67. background: #3E71FE;
  68. box-shadow: 0rpx 8rpx 12rpx 1rpx rgba(62,113,254,0.15);
  69. border-radius: 45rpx 45rpx 45rpx 45rpx;
  70. font-size: 32rpx;
  71. color: #FFFFFF;
  72. font-weight: normal;
  73. text-align: center;
  74. }
  75. image{
  76. position: absolute;
  77. left: 0;
  78. top: 0;
  79. width: 526rpx;
  80. height: 288rpx;
  81. border-radius: 20rpx 20rpx 20rpx 20rpx;
  82. }
  83. }
  84. .transformImg{
  85. position: absolute;
  86. left: 120rpx !important;
  87. top: -120rpx !important;
  88. transform: rotate(-90deg);
  89. width: 288rpx !important;
  90. height: 526rpx !important;
  91. z-index: 999;
  92. }
  93. .back{
  94. position: relative;
  95. width: 526rpx;
  96. height: 288rpx;
  97. border-radius: 20rpx 20rpx 20rpx 20rpx;
  98. background: url('https://res.qyjpay.cn/static/res/yewuban-smrz-fanmian.png') no-repeat center;
  99. background-size: contain;
  100. .backBtn{
  101. position: absolute;
  102. bottom: 50rpx;
  103. left: 123rpx;
  104. width: 280rpx;
  105. height: 90rpx;
  106. line-height: 90rpx;
  107. background: #3E71FE;
  108. box-shadow: 0rpx 8rpx 12rpx 1rpx rgba(62,113,254,0.15);
  109. border-radius: 45rpx 45rpx 45rpx 45rpx;
  110. font-size: 32rpx;
  111. color: #FFFFFF;
  112. font-weight: normal;
  113. text-align: center;
  114. }
  115. image{
  116. position: absolute;
  117. left: 0;
  118. top: 0;
  119. width: 526rpx;
  120. height: 288rpx;
  121. border-radius: 20rpx 20rpx 20rpx 20rpx;
  122. }
  123. }
  124. }
  125. }

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/683043
推荐阅读
相关标签
  

闽ICP备14008679号