背景
最近在开发一个项目时,需要实现图片文字识别功能。图片中主要包含中文和数字,我想到使用 Tesseract.js 这个开源的 OCR 库来实现。这篇文章记录了从功能实现到性能优化的完整过程。
一、技术选型
Tesseract.js 是一个基于 Tesseract OCR 引擎的 JavaScript 库,可以在浏览器端直接运行 OCR 识别,无需后端服务。它的优势包括:
- 纯前端实现,无需服务器
- 支持 100+ 种语言
- 支持图片预处理提升识别准确率
- API 简单易用
学习笔记
背景
最近在开发一个项目时,需要实现图片文字识别功能。图片中主要包含中文和数字,我想到使用 Tesseract.js 这个开源的 OCR 库来实现。这篇文章记录了从功能实现到性能优化的完整过程。
Tesseract.js 是一个基于 Tesseract OCR 引擎的 JavaScript 库,可以在浏览器端直接运行 OCR 识别,无需后端服务。它的优势包括:
背景
在给国家电网项目投标的时候,需要制作双层PDF文件,这里记录下普通PDF文件转换成双层PDF文件的方法。双层PDF文件有个特点,就是上边的文字是可以选中复制。其次,还要保留原始文件中的目录,转换完成后可以验证下。
首选需要下载并安装 Adobe Acrobat Pro DC。
背景
项目中需要查看一个零件图,需要实现图片的滚轮缩放,鼠标拖拽功能,类似百度地图的拖拽和缩放功能,实现方式如下:
<template>
<div class="box">
<ImageView ref="ImageViewRef" :imageUrl="url" />
<button class="btn-box" @click="reset">重置</button>
</div>
</template>
<script>
import ImageView from './ImageViewr.vue'
export default {
components: {
ImageView
},
data() {
return {
url: 'https://ts1.tc.mm.bing.net/th?id=ORMS.EBNm8u94OA0h0A&w=612&h=304&qlt=90&c=1&rs=1&dpr=1.5&p=0'
}
},
methods: {
reset() {
this.$refs.ImageViewRef.resetImage()
}
}
}
</script>
<style lang="scss" scoped>
.box {
width: 500px;
height: 400px;
border: 1px solid red;
.btn-box {
position: absolute;
top: 320px;
}
}
</style>
背景
用 vue3 + vite 开发一个移动端 h5 项目。开发完成后,又有新需求,需要将这个项目打包成安卓 APP。
import { resolve } from 'node:path'
import type { ConfigEnv, UserConfig } from 'vite'
import { loadEnv } from 'vite'
import { format } from 'date-fns'
import { wrapperEnv } from './build/utils'
import { createVitePlugins } from './build/vite/plugin'
import { OUTPUT_DIR } from './build/constant'
import { createProxy } from './build/vite/proxy'
import pkg from './package.json'
const { dependencies, devDependencies, name, version } = pkg
function pathResolve(dir: string) {
return resolve(process.cwd(), '.', dir)
}
const __APP_INFO__ = {
// APP 后台管理信息
pkg: { dependencies, devDependencies, name, version },
// 最后编译时间
lastBuildTime: format(new Date(), 'yyyy-MM-dd HH:mm:ss')
}
/** @type {import('vite').UserConfig} */
export default ({ command, mode }: ConfigEnv): UserConfig => {
const root = process.cwd()
const env = loadEnv(mode, root)
const viteEnv = wrapperEnv(env)
const { VITE_PUBLIC_PATH, VITE_DROP_CONSOLE, VITE_PORT, VITE_PROXY, VITE_GLOB_PROD_MOCK } = viteEnv
const prodMock = VITE_GLOB_PROD_MOCK
const isBuild = command === 'build'
// command === 'build'
return {
base: './', //重点在这个地方,打包的时候需要设置成相对路径,不然打包后安卓APP无法访问资源
// base: '/h5', //这个是h5版本的打包配置,因为项目访问地址是域名后面+/h5,所以需要设置成这个
root,
// 别名
resolve: {
alias: [
// @/xxxx => src/xxxx
{
find: /\@\//,
replacement: `${pathResolve('src')}/`
},
// #/xxxx => types/xxxx
{
find: /\#\//,
replacement: `${pathResolve('types')}/`
}
],
dedupe: ['vue']
},
// 定义全局常量替换方式
define: {
// 在生产中 启用/禁用 intlify-devtools 和 vue-devtools 支持,默认值 false
__INTLIFY_PROD_DEVTOOLS__: false,
__APP_INFO__: JSON.stringify(__APP_INFO__),
'process.env': {
// 将 Vite 的环境变量映射到 process.env
NODE_ENV: env.NODE_ENV,
// 可按需添加其他需要的变量(如第三方组件依赖的变量)
VITE_GLOB_API_URL_PREFIX: env.VITE_GLOB_API_URL_PREFIX,
VITE_APP_BASE_URL: env.VITE_APP_BASE_URL
// ...其他变量
}
},
esbuild: {
// 使用 esbuild 压缩 剔除 console.log
drop: VITE_DROP_CONSOLE ? ['debugger', 'console'] : []
// minify: true, // minify: true, 等于 minify: 'esbuild',
},
build: {
// 设置最终构建的浏览器兼容目标
target: 'es2015',
minify: 'esbuild',
// 构建后是否生成 source map 文件(用于线上报错代码报错映射对应代码)
sourcemap: false,
cssTarget: 'chrome80',
// 指定输出路径(相对于 项目根目录)
outDir: OUTPUT_DIR,
// 只有 minify 为 terser 的时候, 本配置项才能起作用
// terserOptions: {
// compress: {
// // 防止 Infinity 被压缩成 1/0,这可能会导致 Chrome 上的性能问题
// keep_infinity: true,
// // 打包是否自动删除 console
// drop_console: VITE_DROP_CONSOLE,
// },
// },
// 启用/禁用 gzip 压缩大小报告
// 压缩大型输出文件可能会很慢,因此禁用该功能可能会提高大型项目的构建性能
reportCompressedSize: true,
// chunk 大小警告的限制(以 kbs 为单位)
chunkSizeWarningLimit: 2000,
commonjsOptions: {
include: /node_modules|lib/ //这里记得把lib目录加进来,否则生产打包会报错!!
},
// 自定义底层的 Rollup 打包配置
rollupOptions: {
// 静态资源分类打包
output: {
chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称
entryFileNames: 'js/[name]-[hash].js', // 包的入口文件名称
assetFileNames: '[ext]/[name]-[hash].[ext]', // 资源文件像 字体,图片等
// 将 node_modules 三方依赖包最小化拆分
manualChunks(id) {
if (id.includes('node_modules') && !id.includes('@antv')) {
const paths = id.toString().split('node_modules/')
if (paths[2]) {
return paths[2].split('/')[0].toString()
}
return paths[1].split('/')[0].toString()
}
}
}
}
},
css: {
preprocessorOptions: {
less: {
modifyVars: {},
javascriptEnabled: true,
// 注入全局 less 变量
additionalData: `@import "src/styles/var.less";`
}
}
},
server: {
host: true,
// 服务启动时是否自动打开浏览器
open: true,
// 服务端口号
port: Number(VITE_PORT),
proxy: createProxy(VITE_PROXY),
// 预热文件以降低启动期间的初始页面加载时长
warmup: {
// 预热的客户端文件:首页、views、 components
clientFiles: ['./index.html', './src/{views,components}/*']
}
// proxy: {
// '/api': {
// target: '',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '/api/v1')
// }
// }
},
optimizeDeps: {
/**
* 依赖预构建,vite 启动时会将下面 include 里的模块,编译成 esm 格式并缓存到 node_modules/.vite 文件夹,
* 页面加载到对应模块时如果浏览器有缓存就读取浏览器缓存,如果没有会读取本地缓存并按需加载
* 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include 里,
* 否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),
* 因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存
* 温馨提示:如果你使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,
* 就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite
*/
include: ['pinia', 'lodash-es', 'axios', '@/../lib/vform/render.umd.js'],
// 打包时强制排除的依赖项
exclude: [
// https://www.mulingyuer.com/archives/928/
'vant',
'@vant/use'
]
},
// 加载插件
plugins: createVitePlugins(viteEnv, isBuild, prodMock)
}
}
背景
用 vue3 开发一个移动端项目,有个需求是,有一个 tab 人员列表页,点击其中的某个人名,可以跳转到该人员的详情页。为了优化体验,我使用了 keepAlive 来缓存人员列表页,这样在跳转详情页后,再返回人员列表页,不需要重新请求接口,直接从缓存中读取数据。
但是,偶尔会出现缓存失效的问题,导致返回人员列表页后,需要重新请求接口。
<!-- App.vue文件 -->
<template>
<vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()" theme-vars-scope="global">
<routerView v-slot="{ Component }">
<div class="absolute top-0 bottom-0 w-full overflow-hidden">
<transition :name="getTransitionName" mode="out-in" appear>
<!-- include后跟的变量用来存放需要缓存的组件name -->
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<!-- 下边component中,不能设置key,否则会导致缓存失效 -->
<component :is="Component" />
</keep-alive>
</transition>
</div>
</routerView>
</vanConfigProvider>
</template>
背景
最近在安装了 Windows24H2 的一个系统更新后,发现电脑上很多开发工具都变慢了。vscode 启动项目变得巨慢,git 上传代码也很慢。打开任务管理器,发现 CPU 在编译时只有一个核被占用,猜测可能是 Windows 的更新对多核编译的调度策略产生了影响,查询网络发现问题出在 MSPCManager Service 这个服务上面。
背景
项目中,对于结构复杂的树形数据选择,一般会使用懒加载的方式。但是懒加载的同时,又需要实现远程搜索,这时候就需要对 el-tree-select 进行一些处理了。
注意,当页面上有多个 el-tree-select 组件时,如果没有配置 value-key,对一个组件进行搜索选中操作,然后点击第二个组件时,可能会导致上一个组件的显示值变成一个 id 值。
背景
el-table 中,对于表格单元格的合并,文档中有介绍。原理就是通过设置span-method方法,返回一个二维数组,数组中的每一项表示一行中每个单元格的合并情况。
但是这种方法针对已知表格中的某些单元格需要合并的情况,如果需要合并的单元格位置是动态的,那么这种方法就需要改造下了。
<template>
<div>
<el-table :data="tableData" :span-method="objectSpanMethod" border style="width: 100%; margin-top: 20px">
<el-table-column prop="id" label="ID" width="180" />
<el-table-column prop="name" label="Name" />
<el-table-column prop="amount1" label="Amount 1" />
<el-table-column prop="amount2" label="Amount 2" />
<el-table-column prop="amount3" label="Amount 3" />
</el-table>
</div>
</template>
<script setup>
//重点就是这个方法,返回一个数组,用来描述每行每个单元格的合并情况
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
if (columnIndex === 0) {
if (rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
const tableData = [
{
id: '12987122',
name: 'Tom',
amount1: '234',
amount2: '3.2',
amount3: 10
},
{
id: '12987123',
name: 'Tom',
amount1: '165',
amount2: '4.43',
amount3: 12
},
{
id: '12987124',
name: 'Tom',
amount1: '324',
amount2: '1.9',
amount3: 9
},
{
id: '12987125',
name: 'Tom',
amount1: '621',
amount2: '2.2',
amount3: 17
},
{
id: '12987126',
name: 'Tom',
amount1: '539',
amount2: '4.1',
amount3: 15
}
]
</script>
背景
正在做一个考核管理系统,需要用到附件的上传以及回显。项目有 vue 做的 pc 端,以及 uniapp 做的 H5 移动端,做的时候发现二者的回显稍有不同,记录一下。因为附件只牵扯到 pdf 和图片,所以只写了这两种的回显。
function reviewFile (file) {
let fileType = ''
if (file.fileName.indexOf('.pdf') != -1) {
fileType = 'application/pdf'
} else {
fileType = 'image/png'
}
axios({
url: process.env.VUE_APP_BASE_URL + '/file/download/' + file.file,
method: 'get',
responseType: 'blob', // 一定要设置响应类型为 blob
headers: { token: token } // 对于有鉴权的接口,需要添加token
}).then(res => {
// 创建用于作为附件预览源的Blob
const blob = new Blob([res.data], { type: fileType });
// 创建用于作为PDF预览源的Blob的URL
let url = URL.createObjectURL(blob);
window.open(url)
})
},
背景
使用 vue3 + 若依框架,点击切换左侧菜单,页面偶尔白屏,并且不报错。刷新页面后可以正常显示。继续点击,还是偶尔会出现相同的问题。
网上查找资料,发现出现这种问题,有可能是页面组件嵌套,重复使用 transition 引起的。在 components 文件夹下的 layout 文件夹中,AppMain 中使用了 transition,而 keep-alive 包裹的组件中又使用了 transition,导致页面出现白屏。