uniapp小程序ocr-navigator身份证拍照上传替换方案

发布于:2025-08-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

拍照上传身份证

原ocr代码(官方已废弃,无法使用)

<!--
 * @Date: 2024-02-22 18:53:33
 * @LastEditTime: 2025-07-29 13:49:25
 * @Description: 上传身份证
-->
<template>
    <view class="wrap">
        <image class="bg-img" :src="TOP_BG_PNG" mode="widthFix" />
        <view class="main-content">
            <!-- 1-1.验证和识别通过,提示“验证通过”,并覆盖页面的身份证信息缓存(照片信息、格式化信息)
                 1-2.验证不通过,报错“验证失败,请保证照片的清晰重新上传” -->
            <view class="upload-wrap box-shadow">
                <view class="upload-label">
                    <view class="label-title">头像面</view>
                    <view class="label-subtitle">拍摄或上传</view>
                </view>
                <view class="ocr-area">
                    <ocr-navigator certificate-type="idCard" :opposite="false" @onSuccess="handleSuccessTop">
                        <view class="upload-area">
                            <view class="angle-wrap">
                                <view class="angle top-left" />
                                <view class="angle top-right" />
                                <view class="angle bottom-right" />
                                <view class="angle bottom-left" />
                            </view>
                            <!-- 微信OCR -->
                            <view v-if="!viewCertImgUrl" class="inner-area" @click="handleUploadClick">
                                <view class="upload-icon cross" />
                                <view class="upload-text">拍照或上传身份证头像面</view>
                            </view>
                            <!-- 身份证照片 -->
                            <image v-else class="cert-image" :src="viewCertImgUrl" />
                        </view>
                    </ocr-navigator>
                </view>
            </view>
            <view v-if="!isInputShow && certNoLimit" class="forget-text" @click="showInput">忘记带身份证?</view>
            <view v-else-if="isInputShow && certNoLimit" class="id-card-input-wrap box-shadow">
                <view class="input-label">请输入身份证号</view>
                <view class="input-wrap">
                    <input
                        type="text"
                        focus
                        placeholder="请输入身份证号"
                        placeholder-class="input-placeholder"
                        class="input input-id-card"
                        :disabled="ifOCR"
                        :value="idNumber"
                        @input="handleInput"
                    />
                </view>
            </view>
        </view>
        <view class="button-wrap safe-padding-bottom">
            <view class="button button-primary" :class="{ disabled: isDisabled }" @click="onConfirm">提交值机</view>
        </view>
        <!-- 登录弹框 -->
        <popup-login ref="popupLoginRef" />
    </view>
</template>

<script setup lang="ts">
import config from '@/../config/config';
import { checkIn, getServeOrderDetail } from '@/services/api-order';
import { subscribeMessage } from '@/utils/BusinessUtils';
import { getCheckInStorage } from '@/utils/Storage';
import { uploadFile } from '@/utils/UpLoadUtils';
import { computed, onMounted, onUnmounted, ref, toRaw } from 'vue';
import { useStore } from 'vuex';
import { goTabBarPage, goFlyGuidePage } from '@/utils/goPage';
const TOP_BG_PNG = `${config.assetPath}/images/fly/guide-bg.png`;

const store = useStore();
let certNoLimit = store?.state?.configStore?.globalConfig?.certNoLimit !== 'APP_CONTEXT_CERT_NO_LIMIT_OFF'; // APP_CONTEXT_CERT_NO_LIMIT_OFF 隐藏证件号,APP_CONTEXT_CERT_NO_LIMIT_ON 不隐藏证件号
let isInputShow = ref(false);
let idNumber = ref();
let oCRInfo = ref({});
let viewCertImgUrl = ref('');
let serviceOrderInfo = ref({});
let serverOrderId = ref('');
let ifOCR = ref(false);
const isDisabled = computed(() => {
    return !oCRInfo.value?.certNo && !idNumber.value;
});

const handleUploadClick = () => {
    // console.log('点击了上传按钮');
};

const handleSuccessTop = async (e) => {
    const info = e.detail;
    if (!info?.id?.text) {
        uni.showToast({
            title: '验证失败,请保证照片的清晰重新上传',
            icon: 'none',
        });
        return;
    }
    let certImg;
    uploadFile({
        tempFilePath: info?.image_path,
        moduleName: 'certImg',
    })
        .then((res) => {
            certImg = res;
            viewCertImgUrl.value = info?.image_path;
            oCRInfo.value = {
                certImg,
                name: info?.name?.text,
                certNo: info?.id?.text,
                sex: info?.gender?.text === '男' ? 1 : info?.gender?.text === '女' ? 2 : 0,
                birthday: info?.birth?.text,
                nation: info?.nationality?.text,
                address: info?.address?.text,
            };
            idNumber.value = info?.id?.text;
            ifOCR.value = true;
            store.dispatch('setCertInfo', { ...toRaw(oCRInfo.value) });
            uni.showToast({
                title: '验证通过',
                icon: 'success',
            });
        })
        .catch((error) => {
            uni.showToast({
                title: error || '验证失败,请保证照片的清晰重新上传',
                icon: 'none',
            });
        });
};

/** 身份证号输入 */
const handleInput = (event) => {
    if (isInputShow.value) {
        const { value } = event.detail;
        idNumber.value = value;
        store.dispatch('setCertInfoCerNo', idNumber.value);
    }
};

/** 展示文本输入框 */
const showInput = () => {
    isInputShow.value = true;
};
let reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
/** 提交值机 */
const onConfirm = async () => {
    const checkInInfo = await getCheckInStorage();
    if (!reg.test(checkInInfo?.certInfo?.certNo)) {
        uni.showToast({
            title: '请输入正确的身份证号',
            icon: 'none',
        });
        return;
    }
    const res = await checkIn({
        serverOrderId: checkInInfo?.serverOrderId,
        inviteTicket: checkInInfo?.inviteTicket,
        checkCode: checkInInfo?.checkCode,
        noticeSign: checkInInfo?.noticeSign,
        ...checkInInfo?.certInfo,
    });
    const config = store?.state?.configStore?.globalConfig;
    const tempIds = [config.orderCompleteTemplateId];
    await subscribeMessage(tempIds)
        .catch((err) => {
            uni.showToast({
                title: '订阅消息失败',
                icon: 'none',
            });
        })
        .finally(() => {
            //是否有值机完成图片/值机完成视频
            if (serviceOrderInfo.value?.checkedInImages?.length || serviceOrderInfo.value?.checkedInVideos?.length) {

                const type = 'CHECKIN';
                goFlyGuidePage(type, serverOrderId.value);
            } else {
                goTabBarPage('fly');
               
            }
        });
};

const loadCheckInGuide = async () => {
    const { data } = await getServeOrderDetail({ id: serverOrderId.value });
    serviceOrderInfo.value = data || {};
};
const init = async () => {
    const info = await getCheckInStorage();
    serverOrderId.value = info?.serverOrderId;
    loadCheckInGuide();
};
onMounted(() => {
    init();
});

onUnmounted(() => {
    //清除值机信息
    store.dispatch('setCheckInInfo', null);
});
</script>

<style lang="scss">
@import './index.scss';
</style>

方案一:使用chooseMedia上传身份证安卓机只需要确认一次才能上传(推荐)

<!--
 * @Date: 2024-02-22 18:53:33
 * @LastEditTime: 2025-07-29 13:49:25
 * @Description: 上传身份证
-->
<template>
    <view class="wrap">
        <image class="bg-img" :src="TOP_BG_PNG" mode="widthFix" />
        <view class="main-content">
            <!-- 1-1.验证和识别通过,提示“验证通过”,并覆盖页面的身份证信息缓存(照片信息、格式化信息)
                 1-2.验证不通过,报错“验证失败,请保证照片的清晰重新上传” -->
            <view class="upload-wrap box-shadow">
                <view class="upload-label">
                    <view class="label-title">头像面</view>
                    <view class="label-subtitle">拍摄或上传</view>
                </view>
                <view class="ocr-area">
  
                        <view class="upload-area">
                            <view class="angle-wrap">
                                <view class="angle top-left" />
                                <view class="angle top-right" />
                                <view class="angle bottom-right" />
                                <view class="angle bottom-left" />
                            </view>
                            <!-- 微信OCR -->
                            <view v-if="!viewCertImgUrl" class="inner-area" @click="chooseImage">
                                <view class="upload-icon cross" />
                                <view class="upload-text">拍照或上传身份证头像面</view>
                            </view>
                            <!-- 身份证照片 -->
                            <image v-else class="cert-image" :src="viewCertImgUrl" />
                        </view>
              
                </view>
            </view>
            <view v-if="!isInputShow && certNoLimit" class="forget-text" @click="showInput">忘记带身份证?</view>
            <view v-else-if="isInputShow && certNoLimit" class="id-card-input-wrap box-shadow">
                <view class="input-label">请输入身份证号</view>
                <view class="input-wrap">
                    <input
                        type="text"
                        focus
                        placeholder="请输入身份证号"
                        placeholder-class="input-placeholder"
                        class="input input-id-card"
                        :disabled="ifOCR"
                        :value="idNumber"
                        @input="handleInput"
                    />
                </view>
            </view>
        </view>
        <view class="button-wrap safe-padding-bottom">
            <view class="button button-primary" :class="{ disabled: isDisabled }" @click="onConfirm">提交值机</view>
        </view>
        <!-- 登录弹框 -->
        <popup-login ref="popupLoginRef" />
    </view>
</template>

<script setup lang="ts">
import config from '@/../config/config';
import { checkIn, getServeOrderDetail,idCardOCR } from '@/services/api-order';
import { subscribeMessage } from '@/utils/BusinessUtils';
import { getCheckInStorage } from '@/utils/Storage';
import { uploadFile } from '@/utils/UpLoadUtils';
import { computed, onMounted, onUnmounted, ref, toRaw } from 'vue';
import { useStore } from 'vuex';
import { goTabBarPage, goFlyGuidePage } from '@/utils/goPage';
const TOP_BG_PNG = `${config.assetPath}/images/fly/guide-bg.png`;

const store = useStore();
let certNoLimit = store?.state?.configStore?.globalConfig?.certNoLimit !== 'APP_CONTEXT_CERT_NO_LIMIT_OFF'; // APP_CONTEXT_CERT_NO_LIMIT_OFF 隐藏证件号,APP_CONTEXT_CERT_NO_LIMIT_ON 不隐藏证件号
let isInputShow = ref(false);
let idNumber = ref();
let oCRInfo = ref({});
let viewCertImgUrl = ref('');
let serviceOrderInfo = ref({});
let serverOrderId = ref('');
let ifOCR = ref(false);
const isDisabled = computed(() => {
    return !oCRInfo.value?.certNo && !idNumber.value;
});

const chooseImage = async () => {
    uni.chooseMedia({
        count: 1,
        sizeType: ['original', 'compressed'],
        mediaType: ['image'],
        sourceType: ['album', 'camera'],
        success: async (res) => {
            const tempFilePath = res.tempFiles[0].tempFilePath;
            viewCertImgUrl.value = tempFilePath; // 预览本地图片
            try {
                // 1. 上传图片到自己的服务器(示例:调用项目的上传方法)
                const uploadRes = await uploadFile({
                    tempFilePath,
                    moduleName: 'certImg',
                });
                const imageUrl = uploadRes; // 服务器返回的图片URL
                // 2. 调用后端OCR接口(需自行实现后端)
                const ocrRes = await idCardOCR({
                    imageUrl,
                    cardSide: 'FRONT', //FRONT:身份证有照片的一面(人像面),BACK:身份证有国徽的一面(国徽面),
                });
                // 3. 处理OCR结果(与原逻辑对齐)
                const info = ocrRes.data;
                oCRInfo.value = {
                    certImg: imageUrl,
                    name: info.name,
                    certNo: info.idNum,
                    sex: info?.sex === '男' ? 1 : info?.sex === '女' ? 2 : 0,
                    birthday: info.birth,
                    nation: info.nation,
                    address: info.address,
                };
                idNumber.value = info.idNum;
                ifOCR.value = true;
                store.dispatch('setCertInfo', { ...toRaw(oCRInfo.value) });
                uni.showToast({
                    title: '验证通过',
                    icon: 'success',
                });
            } catch (error) {
                if (error?.retcode !== 0) {
                    await nextTick(() => {
                        oCRInfo.value = ''; //清空信息,置灰按钮
                        idNumber.value = ''; //清空信息,置灰按钮
                        viewCertImgUrl.value = ''; // 清空预览图
                    });
                }
            }
        },
        fail: (err) => {
            uni.showToast({
                title: '选择图片失败,请重试',
                icon: 'none',
            });
        },
    });
};

/** 身份证号输入 */
const handleInput = (event) => {
    if (isInputShow.value) {
        const { value } = event.detail;
        idNumber.value = value;
        store.dispatch('setCertInfoCerNo', idNumber.value);
    }
};

/** 展示文本输入框 */
const showInput = () => {
    isInputShow.value = true;
};
let reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
/** 提交值机 */
const onConfirm = async () => {
    const checkInInfo = await getCheckInStorage();
    if (!reg.test(checkInInfo?.certInfo?.certNo)) {
        uni.showToast({
            title: '请输入正确的身份证号',
            icon: 'none',
        });
        return;
    }
    const res = await checkIn({
        serverOrderId: checkInInfo?.serverOrderId,
        inviteTicket: checkInInfo?.inviteTicket,
        checkCode: checkInInfo?.checkCode,
        noticeSign: checkInInfo?.noticeSign,
        ...checkInInfo?.certInfo,
    });
    const config = store?.state?.configStore?.globalConfig;
    const tempIds = [config.orderCompleteTemplateId];
    await subscribeMessage(tempIds)
        .catch((err) => {
            uni.showToast({
                title: '订阅消息失败',
                icon: 'none',
            });
        })
        .finally(() => {
            //是否有值机完成图片/值机完成视频
            if (serviceOrderInfo.value?.checkedInImages?.length || serviceOrderInfo.value?.checkedInVideos?.length) {

                const type = 'CHECKIN';
                goFlyGuidePage(type, serverOrderId.value);
            } else {
                goTabBarPage('fly');

            }
        });
};

const loadCheckInGuide = async () => {
    const { data } = await getServeOrderDetail({ id: serverOrderId.value });
    serviceOrderInfo.value = data || {};
};
const init = async () => {
    const info = await getCheckInStorage();
    serverOrderId.value = info?.serverOrderId;
    loadCheckInGuide();
};
onMounted(() => {
    init();
});

onUnmounted(() => {
    //清除值机信息
    store.dispatch('setCheckInInfo', null);
});
</script>

<style lang="scss">
@import './index.scss';
</style>

方案二:使用chooseImage上传身份证安卓机需要两次确认才能上传

<!--
 * @Date: 2024-02-22 18:53:33
 * @LastEditTime: 2025-07-29 13:49:25
 * @Description: 上传身份证
-->
<template>
    <view class="wrap">
        <image class="bg-img" :src="TOP_BG_PNG" mode="widthFix" />
        <view class="main-content">
            <!-- 1-1.验证和识别通过,提示“验证通过”,并覆盖页面的身份证信息缓存(照片信息、格式化信息)
                 1-2.验证不通过,报错“验证失败,请保证照片的清晰重新上传” -->
            <view class="upload-wrap box-shadow">
                <view class="upload-label">
                    <view class="label-title">头像面</view>
                    <view class="label-subtitle">拍摄或上传</view>
                </view>
                <view class="ocr-area">
  
                        <view class="upload-area">
                            <view class="angle-wrap">
                                <view class="angle top-left" />
                                <view class="angle top-right" />
                                <view class="angle bottom-right" />
                                <view class="angle bottom-left" />
                            </view>
                            <!-- 微信OCR -->
                            <view v-if="!viewCertImgUrl" class="inner-area" @click="chooseImage">
                                <view class="upload-icon cross" />
                                <view class="upload-text">拍照或上传身份证头像面</view>
                            </view>
                            <!-- 身份证照片 -->
                            <image v-else class="cert-image" :src="viewCertImgUrl" />
                        </view>
              
                </view>
            </view>
            <view v-if="!isInputShow && certNoLimit" class="forget-text" @click="showInput">忘记带身份证?</view>
            <view v-else-if="isInputShow && certNoLimit" class="id-card-input-wrap box-shadow">
                <view class="input-label">请输入身份证号</view>
                <view class="input-wrap">
                    <input
                        type="text"
                        focus
                        placeholder="请输入身份证号"
                        placeholder-class="input-placeholder"
                        class="input input-id-card"
                        :disabled="ifOCR"
                        :value="idNumber"
                        @input="handleInput"
                    />
                </view>
            </view>
        </view>
        <view class="button-wrap safe-padding-bottom">
            <view class="button button-primary" :class="{ disabled: isDisabled }" @click="onConfirm">提交值机</view>
        </view>
        <!-- 登录弹框 -->
        <popup-login ref="popupLoginRef" />
    </view>
</template>

<script setup lang="ts">
import config from '@/../config/config';
import { checkIn, getServeOrderDetail,idCardOCR } from '@/services/api-order';
import { subscribeMessage } from '@/utils/BusinessUtils';
import { getCheckInStorage } from '@/utils/Storage';
import { uploadFile } from '@/utils/UpLoadUtils';
import { computed, onMounted, onUnmounted, ref, toRaw } from 'vue';
import { useStore } from 'vuex';
import { goTabBarPage, goFlyGuidePage } from '@/utils/goPage';
const TOP_BG_PNG = `${config.assetPath}/images/fly/guide-bg.png`;

const store = useStore();
let certNoLimit = store?.state?.configStore?.globalConfig?.certNoLimit !== 'APP_CONTEXT_CERT_NO_LIMIT_OFF'; // APP_CONTEXT_CERT_NO_LIMIT_OFF 隐藏证件号,APP_CONTEXT_CERT_NO_LIMIT_ON 不隐藏证件号
let isInputShow = ref(false);
let idNumber = ref();
let oCRInfo = ref({});
let viewCertImgUrl = ref('');
let serviceOrderInfo = ref({});
let serverOrderId = ref('');
let ifOCR = ref(false);
const isDisabled = computed(() => {
    return !oCRInfo.value?.certNo && !idNumber.value;
});

const chooseImage = async () => {
    uni.chooseImage({
        count: 1,
        sizeType: ['original', 'compressed'],
        sourceType: ['album', 'camera'],
        success: async (res) => {
            const tempFilePath = res.tempFilePaths[0];
            viewCertImgUrl.value = tempFilePath; // 预览本地图片
            try {
                // 1. 上传图片到自己的服务器(示例:调用项目的上传方法)
                const uploadRes = await uploadFile({
                    tempFilePath,
                    moduleName: 'certImg',
                });
                const imageUrl = uploadRes; // 服务器返回的图片URL

                // 2. 调用后端OCR接口(需自行实现后端)
                const ocrRes = await idCardOCR({
                    imageUrl,
                    cardSide: 'FRONT', //FRONT:身份证有照片的一面(人像面),BACK:身份证有国徽的一面(国徽面),
                });
                // 3. 处理OCR结果(与原逻辑对齐)
                const info = ocrRes.data;
                oCRInfo.value = {
                    certImg: imageUrl,
                    name: info.name,
                    certNo: info.idNum,
                    sex: info?.sex === '男' ? 1 : info?.sex === '女' ? 2 : 0,
                    birthday: info.birth,
                    nation: info.nation,
                    address: info.address,
                };
                idNumber.value = info.idNum;
                ifOCR.value = true;
                store.dispatch('setCertInfo', { ...toRaw(oCRInfo.value) });
                uni.showToast({
                    title: '验证通过',
                    icon: 'success',
                });
            } catch (error) {
                if (error?.retcode !== 0) {
                    await nextTick(() => {
                        oCRInfo.value = ''; //清空信息,置灰按钮
                        idNumber.value = ''; //清空信息,置灰按钮
                        viewCertImgUrl.value = ''; // 清空预览图
                    });
                }
            }
        },
        fail: (err) => {
            uni.showToast({
                title: '选择图片失败,请重试',
                icon: 'none',
            });
        },
    });
};

/** 身份证号输入 */
const handleInput = (event) => {
    if (isInputShow.value) {
        const { value } = event.detail;
        idNumber.value = value;
        store.dispatch('setCertInfoCerNo', idNumber.value);
    }
};

/** 展示文本输入框 */
const showInput = () => {
    isInputShow.value = true;
};
let reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
/** 提交值机 */
const onConfirm = async () => {
    const checkInInfo = await getCheckInStorage();
    if (!reg.test(checkInInfo?.certInfo?.certNo)) {
        uni.showToast({
            title: '请输入正确的身份证号',
            icon: 'none',
        });
        return;
    }
    const res = await checkIn({
        serverOrderId: checkInInfo?.serverOrderId,
        inviteTicket: checkInInfo?.inviteTicket,
        checkCode: checkInInfo?.checkCode,
        noticeSign: checkInInfo?.noticeSign,
        ...checkInInfo?.certInfo,
    });
    const config = store?.state?.configStore?.globalConfig;
    const tempIds = [config.orderCompleteTemplateId];
    await subscribeMessage(tempIds)
        .catch((err) => {
            uni.showToast({
                title: '订阅消息失败',
                icon: 'none',
            });
        })
        .finally(() => {
            //是否有值机完成图片/值机完成视频
            if (serviceOrderInfo.value?.checkedInImages?.length || serviceOrderInfo.value?.checkedInVideos?.length) {

                const type = 'CHECKIN';
                goFlyGuidePage(type, serverOrderId.value);
            } else {
                goTabBarPage('fly');

            }
        });
};

const loadCheckInGuide = async () => {
    const { data } = await getServeOrderDetail({ id: serverOrderId.value });
    serviceOrderInfo.value = data || {};
};
const init = async () => {
    const info = await getCheckInStorage();
    serverOrderId.value = info?.serverOrderId;
    loadCheckInGuide();
};
onMounted(() => {
    init();
});

onUnmounted(() => {
    //清除值机信息
    store.dispatch('setCheckInInfo', null);
});
</script>

<style lang="scss">
@import './index.scss';
</style>


网站公告

今日签到

点亮在社区的每一天
去签到