效果图:
代码:
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>