效果图:
代码:
<template><view class="lary-top" :style="{ height: `${topBarHight}px` }"></view><Camerav-show="!canvasShow"class="camera-photo":style="{width: `${info.windowWidth}px`,height: `${info.windowHeight}px`,}"><viewclass="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"><viewclass="colose-x":style="{ marginTop: `${convasY - 75}px` }"><cover-imagesrc="@/assets/imgs/icon-x.png"style="height: 24px; width: 24px"@tap="evtBack"></cover-image></view></view><viewclass="page-content":style="{ height: `${info.windowHeight}px` }"><viewclass="page-mask":style="{ height: `${convasY - 40}px` }"></view><viewclass="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><viewclass="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><viewv-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><viewstyle="flex-grow: 1; background-color: rgba(1, 1, 1, 0.9)"class="canvas-wrepper"><view :style="{ width: `${canvasWidth + 40}px` }"><Canvasclass="canvas-style"canvas-id="myCanvas":style="{width: `${canvasWidth}px`,height: `${canvasHeight}px`,}"></Canvas><!-- 二侧底部按钮 --><viewstyle="display: flex;justify-content: space-between;margin-top: 20px;"><viewstyle="color: #fff; font-size: 16px"@tap="reTakePhoto">重新拍照</view><viewstyle="color: #fff; font-size: 16px"v-if="isImgLoading">文件处理中...</view><viewstyle="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);//头部refconst 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,//是否显示canvascanvasShow: 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 heightsonMounted(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 failureTaro.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 constructorconst uploadFile = (filepath) => {let file;fileManager.readFile({filePath: filepath,success: (res) => {//读取文件成功file = res.data;//上传给后端 MultipartFile photo 进行解析//上传文件之前剪切照片 将照片按中心剪切然后上传Taro.uploadFile({//TODO constants.taroReqUrl.ocrurl: 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>