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


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

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

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

     自定义相机 - DCloud 插件市场

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

    创建一个realName.vue文件

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

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

    在realName.vue文件中加入:

    "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. }

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

  • 相关阅读:
    Android问题笔记四十六:解决open failed: EACCES (Permission denied) 问题
    46-3 护网溯源 - 溯源报告编写
    IDEA2020.3无法输入中文问题解决
    剑指 Offer 12. 矩阵中的路径【中等难度】
    SelfKG代码阅读及相关工作
    【八大经典排序算法】快速排序
    视觉目标检测大模型套件detrex-调研
    数组:
    计算机竞赛 题目:基于卷积神经网络的手写字符识别 - 深度学习
    【无标题】
  • 原文地址:https://blog.csdn.net/Rio_Gaven/article/details/138153584