Taro实现微信小程序自定义拍照截图识别

效果图:

代码:

html 复制代码
<template>
    <view class="lary-top" :style="{ height: `${topBarHight}px` }"></view>
    <Camera
        v-show="!canvasShow"
        class="camera-photo"
        :style="{
            width: `${info.windowWidth}px`,
            height: `${info.windowHeight}px`,
        }"
    >
        <view
            class="page-flex"
            :style="{
                width: `${info.windowWidth}px`,
                height: `${info.windowHeight}px`,
            }"
        >
            <view
                :style="{
                    height: `${info.windowHeight}px`,
                    width: `${convasXL}px`,
                }"
                class="page-mask page-mask-lr take-photo"
            >
                <view
                    class="colose-x"
                    :style="{ marginTop: `${convasY - 75}px` }"
                >
                    <cover-image
                        src="@/assets/imgs/icon-x.png"
                        style="height: 24px; width: 24px"
                        @tap="evtBack"
                    >
                    </cover-image>
                </view>
            </view>
            <view
                class="page-content"
                :style="{ height: `${info.windowHeight}px` }"
            >
                <view
                    class="page-mask"
                    :style="{ height: `${convasY - 40}px` }"
                ></view>
                <view
                    class="camera-frame"
                    :style="{
                        width: `100%`,
                        height: `${canvasHeight}px`,
                    }"
                >
                    <view class="corner top-left"></view>
                    <view class="corner top-right"></view>
                    <view class="corner bottom-left"></view>
                    <view class="corner bottom-right"></view>
                </view>
                <view
                    class="page-mask tackPhoto"
                    :style="{ height: `${convasY + 40}px` }"
                >
                    <view class="confirm-photo" @tap="takePhoto">
                        >
                        <view class="in-box"></view>
                    </view>
                </view>
            </view>
            <view
                :style="{
                    height: `${info.windowHeight}px`,
                    width: `${convasXL}px`,
                }"
                class="page-mask page-mask-lr"
            ></view>
        </view>
        <view class="id-card">
            请对准框内拍摄
            <view class="id-card-text">题目</view>
        </view>
    </Camera>
    <view
        v-show="canvasShow"
        class="canvas-main"
        :style="{
            height: `${info.windowHeight}px`,
            width: `${info.windowWidth}px`,
        }"
    >
        <TopNavbar ref="topbarRef" :navbar-props="{ title: '' }">
            <template #left>
                <Left @click="evtBack" />
            </template>
            <template #right>
                <Left color="transparent" />
            </template>
        </TopNavbar>
        <view
            style="flex-grow: 1; background-color: rgba(1, 1, 1, 0.9)"
            class="canvas-wrepper"
        >
            <view :style="{ width: `${canvasWidth + 40}px` }">
                <Canvas
                    class="canvas-style"
                    canvas-id="myCanvas"
                    :style="{
                        width: `${canvasWidth}px`,
                        height: `${canvasHeight}px`,
                    }"
                ></Canvas>
                <!-- 二侧底部按钮 -->
                <view
                    style="
                        display: flex;
                        justify-content: space-between;
                        margin-top: 20px;
                    "
                >
                    <view
                        style="color: #fff; font-size: 16px"
                        @tap="reTakePhoto"
                        >重新拍照</view
                    >
                    <view
                        style="color: #fff; font-size: 16px"
                        v-if="isImgLoading"
                        >文件处理中...</view
                    >
                    <view
                        style="color: #fff; font-size: 16px"
                        @tap="startOcr"
                        v-if="!isImgLoading"
                        >开始识别</view
                    >
                </view>
            </view>
        </view>
    </view>
</template>

<script>
definePageConfig({
    navigationStyle: "custom",
    navigationBarTitleText: "",
    //启用页面分享
    //enableShareAppMessage:true,
    //启动朋友圈分享
    //enableShareTimeline:true
});
import { reactive, toRefs, ref, onMounted, nextTick, watch } from "vue";
import Taro from "@tarojs/taro";

import router from "@/router";
import useSystemInfoStore from "@/stores/systemInfo";
import constants from "@/common/constants";
import store from "@/stores";
import useLoginUserStore from "@/stores/loginUser";
import TopNavbar from "@/components/TopNavbar.vue";
import { Left } from "@nutui/icons-vue-taro";
import { Camera, Canvas } from "@tarojs/components";
export default {
    name: "Ocr",
    components: { TopNavbar, Left, Camera, Canvas },
    setup() {
        //camera实例
        const camera = Taro.createCameraContext();
        //系统信息
        const systemInfo = useSystemInfoStore();
        //系统文件服务
        const fileManager = Taro.getFileSystemManager();
        //登录用户信息
        const loginUserStore = useLoginUserStore(store);
        //头部ref
        const topbarRef = ref(null);
        const state = reactive({
            //识别中
            isImgLoading: true,
            //是否正在识别
            isOcrLoading: false,
            //转存图片的定时器
            timer: null,
            //cor识别内容
            ocrContent: "",
            //头部导航栏
            topBaRefHight: 0,
            //ocr识别组件的展示
            img: require("@/assets/imgs/icon-x.png"),
            //状态栏高度
            topBarHight: systemInfo.getState.statusBarHeight,
            //底部导航栏高度
            bottomBarHight: systemInfo.getState.bottomBarHeight
                ? systemInfo.getState.bottomBarHeight
                : 0,
            //是否显示canvas
            canvasShow: false,
            //canvas相关计算
            info: {
                windowWidth: 0,
                windowHeight: 0,
            },
            //canvas相关计算
            canvasWidth: 0,
            canvasHeight: 0,
            convasX: 0,
            convasXL: 0,
            convasY: 0,
            //图片相关
            baseImg: "",
            isBaseImg: false,
            isCanvas: false,
            //识别的照相路径
            srcCanvasPath: "",
        });

        //初始化数据
        const initData = () => {
            var statusBarHeight = 0;
            Taro.getSystemInfo({
                success: (res) => {
                    statusBarHeight = res.statusBarHeight; //状态栏高度
                    state.info.windowHeight = res.windowHeight;
                    state.info.windowWidth = res.windowWidth;
                    state.convasX = res.screenWidth / 4; //遮罩层上下的高度(生成canvas的起始横坐标)
                    state.convasY = res.screenHeight / 5; //遮罩层左右的宽度(生成canvas的起始纵坐标)
                    state.canvasWidth = state.convasX * 3; //中间裁剪部位的宽度
                    state.canvasHeight = state.convasY * 3; //中间裁剪部位的高度
                    state.convasXL = state.convasX / 2;
                },
            });

            //获取胶囊对象
            var menuButtonObject = Taro.getMenuButtonBoundingClientRect();

            let menuBottonHeight = menuButtonObject.height; //胶囊高度
            let menuBottonTop = menuButtonObject.top; //胶囊距离顶部距离
            state.topBaRefHight =
                statusBarHeight +
                menuBottonHeight +
                (menuBottonTop - statusBarHeight) * 2; //胶囊距离顶部距离
        };

        //回退路由 携带好识别信息
        const evtBack = () => {
            state.isImgLoading = true;
            router.back({ data: state.ocrContent });
        };

        // Retrieve system info and set top and bottom bar heights
        onMounted(async () => {
            //state.topBarHight = statusBarHeight;
            //state.bottomBarHight = bottomBarHeight;
            initData();
        });

        //重新拍照
        const reTakePhoto = () => {
            //清除定时器停止缓存
            clearTimeout(state.timer);
            const canvaCtx = Taro.createCanvasContext("myCanvas", this);
            canvaCtx.clearRect(0, 0, state.canvasWidth, state.canvasHeight);
            canvaCtx.draw();
            state.isOcrLoading = false;
            state.isImgLoading = true;
            state.canvasShow = false;
        };

        //剪切图片
        const drawImage = (filepath) => {
            const ctx = Taro.createCanvasContext("myCanvas", this);

            Taro.getImageInfo({
                src: filepath,
                success: (imgInfo) => {
                    // 我这里宽度和高度都计算了设备比,其实两个值是一样的 ,计算一个就够了
                    let prxHeight = state.info.windowHeight / imgInfo.height; //计算设备比
                    let prxWidth = state.info.windowWidth / imgInfo.width; //计算设备比
                    let canvasWidth = state.canvasWidth;
                    let canvasHeight = state.canvasHeight;
                    //生成
                    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
                    ctx.drawImage(
                        filepath,
                        state.convasXL / prxWidth,
                        (state.convasY - 40) / prxHeight,
                        canvasWidth / prxWidth,
                        canvasHeight / prxHeight,
                        0,
                        0,
                        canvasWidth,
                        canvasHeight
                    );

                    ctx.draw(false, () => {
                        // 增加延迟,等待图片资源加载完成
                       state.timer = setTimeout(() => {
                            Taro.canvasToTempFilePath(
                                {
                                    canvasId: "myCanvas",
                                    success: (res) => {
                                        const tempFilePath = res.tempFilePath;
                                        //uploadFile(tempFilePath);
                                        console.log(tempFilePath);
                                        state.isBaseImg = false;
                                        state.isCanvas = false;
                                        state.baseImg = tempFilePath;
                                        state.srcCanvasPath = tempFilePath;
                                        state.isImgLoading = false;
                                    },
                                    fail: (res) => {
                                        // 转换为临时文件失败
                                        state.isImgLoading = false;
                                        Taro.showToast({
                                            title: "转换为临时文件失败",
                                            duration: 2000,
                                            icon: "none",
                                        });
                                    },
                                },
                                this
                            );
                        }, 2000); // 增加延迟时间,单位为毫秒
                    });
                },
                fail: (res) => {
                    // Handle getImageInfo failure
                    Taro.showToast({
                        title: "获取图片信息失败",
                        duration: 2000,
                        icon: "none",
                    });
                },
            });
        };

        //开始ocr识别
        const startOcr = () => {
            //防止连续识别
            if(state.isOcrLoading){
                return;
            }
            state.isOcrLoading = true;
            //识别中
            Taro.showToast({
                title: "识别中",
                icon: "loading",
                duration: 100000,
            });
            //上传文件然后识别
            uploadFile(state.srcCanvasPath);
        };

        //文件上传服务  l is not a constructor
        const uploadFile = (filepath) => {
            let file;
            fileManager.readFile({
                filePath: filepath,
                success: (res) => {
                    //读取文件成功
                    file = res.data;
                    //上传给后端 MultipartFile photo 进行解析
                    //上传文件之前剪切照片 将照片按中心剪切然后上传

                    Taro.uploadFile({
                        //TODO constants.taroReqUrl.ocr
                        url: constants.taroReqUrl.ocr,
                        filePath: filepath,
                        name: "photo",
                        header: {
                            "content-type": "multipart/form-data",
                            Authorization: loginUserStore.token,
                        },
                        success: (res) => {
                            //上传成功
                            //解析成功
                            try {
                                const data = JSON.parse(res.data);
                                state.ocrContent = data.data.content;
                                state.isOcrLoading = false;
                                Taro.hideToast();
                                //识别完成跳转回去
                                evtBack();
                            } catch (e) {
                                state.isOcrLoading = false;
                                Taro.hideToast();
                                //上传失败
                                Taro.showToast({
                                    title: "Ocr服务异常",
                                    duration: 2000,
                                    icon: "none",
                                });
                            }
                        },
                        fail: (res) => {
                            state.isOcrLoading = false;
                            Taro.hideToast();
                            //上传失败
                            Taro.showToast({
                                title: "Ocr服务异常",
                                duration: 2000,
                                icon: "none",
                            });
                        },
                    });
                },
                fail: (res) => {
                    Taro.hideToast();
                    //读取文件失败
                    Taro.showToast({
                        title: "文件处理中,请稍后重试",
                        duration: 2000,
                        icon: "none",
                    });
                },
            });
        };
        //拍照
        const takePhoto = () => {
            camera.takePhoto({
                quality: "high",
                success: (res) => {
                    //拍照成功
                    const filepath = res.tempImagePath;
                    state.canvasShow = true;
                    drawImage(filepath);
                    state.baseImg = filepath;
                    state.isBaseImg = true;
                    state.isCanvas = true;
                    //上传文件
                    //uploadFile(filepath);
                },
                fail: (res) => {
                    //拍照失败 弹出提示框拍照失败 请检查权限
                    Taro.showToast({
                        title: "拍照失败",
                        duration: 2000,
                        icon: "none",
                    });
                },
            });
        };

        return {
            ...toRefs(state),
            takePhoto,
            evtBack,
            topbarRef,
            reTakePhoto,
            startOcr,
        };
    },
};
</script>

<style lang="less">
.camera-warpper {
    height: 100%;
    width: 100%;
}

//相机聚焦线
.camera-frame {
    position: relative;

    .corner {
        width: 120rpx;
        height: 120rpx;
        position: absolute;
    }

    .top-left {
        border-top: 2px solid #fff;
        border-left: 2px solid #fff;
        left: 0;
        top: 0;
    }

    .top-right {
        border-top: 2px solid #fff;
        border-right: 2px solid #fff;
        right: 0;
        top: 0;
    }

    .bottom-left {
        border-bottom: 2px solid #fff;
        border-left: 2px solid #fff;
        left: 0;
        bottom: 0;
    }

    .bottom-right {
        border-bottom: 2px solid #fff;
        border-right: 2px solid #fff;
        right: 0;
        bottom: 0;
    }
}
//canvas主盒子
.canvas-main {
    display: flex;
    flex-direction: column;
}
.canvas-wrepper {
    display: flex;
    justify-content: center;
    align-items: center;
}

.lary-top {
    position: fixed;
    z-index: 101;
    top: 0;
    width: 100%;
}
.camera-photo {
    position: fixed;
    z-index: 100;
    overflow: hidden;
}
.page-flex {
    display: flex;
    .take-photo {
    }
}
.page-mask {
    background-color: rgba(0, 0, 0, 0.6);
}
.page-mask-lr {
    display: flex;
    justify-content: center;

    .take-photo {
    }
}
.page-content {
    flex: 1;
}
.tackPhoto {
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
}
.confirm-photo {
    width: 130rpx;
    height: 130rpx;
    background-color: #4f4f4f;
    border: 1px solid #bbbbbb;
    border-radius: 50%;
    text-align: center;
    line-height: 130rpx;
    justify-content: center;
    display: flex;
    align-items: center;
    .in-box {
        height: 100rpx;
        width: 100rpx;
        background-color: #fff;
        border-radius: 50%;
    }
}
.cancel {
    position: absolute;
    right: 90rpx;
    color: #fff;
}
.id-card {
    position: absolute;
    font-size: 36rpx;
    color: #fff;
    top: 53%;
    right: 40rpx;
    display: flex;
    align-items: center;
    transform-origin: right;
    transform: rotate(90deg);
}
.id-card-text {
    color: #3b8bff;
}
.canvas-style {
    margin: auto;
    overflow: hidden;
    border: 2px solid #ffffff;
}
.base-img {
    position: fixed;
    z-index: 101;
}
.success-img {
    position: fixed;
    z-index: 101;
    background-color: black;
}
.after-img-tips {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    position: fixed;
    bottom: 75rpx;
    padding-left: 60rpx;
    padding-right: 60rpx;
    box-sizing: border-box;
}
.back {
    width: 141rpx;
    height: 141rpx;
}
</style>
相关推荐
qq_17448285752 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
wqq_9922502772 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
licy__8 小时前
微信小程序登录注册页面设计(小程序项目)
微信小程序·小程序
wqq_99225027717 小时前
springboot基于微信小程序的农产品交易平台
spring boot·后端·微信小程序
说私域1 天前
基于“开源 2+1 链动模式 S2B2C 商城小程序”的社区团购运作主体特征分析
大数据·人工智能·小程序
HUODUNYUN1 天前
小程序免备案:快速部署与优化的全攻略
服务器·网络·web安全·小程序·1024程序员节
guanpinkeji1 天前
二手手机回收小程序,一键便捷高效回收
微信小程序·小程序·软件开发·手机回收小程序·二手手机回收
paterWang1 天前
小程序-基于java+SpringBoot+Vue的小区服务管理系统设计与实现
java·spring boot·小程序
尘浮生1 天前
Java项目实战II基于微信小程序的私家车位共享系统(开发文档+数据库+源码)
java·开发语言·数据库·学习·微信小程序·小程序·maven
十幺卜入1 天前
基于xr-frame实现微信小程序的手部、手势识别3D模型叠加和石头剪刀布游戏功能
游戏·微信小程序·xr·手势识别·人手跟踪