拓冰建站拓冰建站
首页 / 资讯中心 / 正文

uniapp-x 生成二维码与条形码:UTS 原生插件封装实践

1. 先说结论uniapp-x 里生成码为什么绕开了纯 JS 方案先交代下背景。我最近在做一个仓储管理 App需求很直白把货架上的货品编号生成条形码贴在箱子上同时生成二维码里面存 SKU 和批次信息方便仓管员扫一下就能看到货品详情。项目用的就是 uniapp-x也就是 DCloud 新一代的跨平台开发框架很多人习惯写成 uniapp-x官方文档里叫 uni-app x。这个需求听起来简单但真做起来比想象中麻烦。原因在于 uniapp-x 和传统的 uni-app 不完全是一回事。旧版 uni-app 的 App 端本质上是 WebView 套壳前端跑的是 Web 那套环境所以网上大量 qrcode.js、bwip-js 这类纯 JS 库可以直接拿过来用canvas 画一画就出图了。但 uniapp-x 的逻辑层和视图层都是自研或原生编译的不再天然兼容浏览器 API。它支持 Vue3 和 TS 语法但底层没有 DOM、没有 window你指望拿一段“在浏览器里跑得好好的”二维码生成脚本直接塞进去大概率会报各种稀奇古怪的错。我一开始没意识到这个问题图省事直接 npm 装了一个 qrcode 库结果一编译就发现 canvas 相关的 API 拿不到。后来换思路试过用 canvas 组件手动画码uniapp-x 的 canvas 接口和 Web 端的 Canvas API 有很多细节不一样画一个二维码需要把矩阵点一个个绘制出来性能先不说光是逐点控制坐标就很折磨人而且条形码的编码规则比二维码复杂得多手写 Code128 的编码表是个大工程完全不划算。所以如果你现在问我 uniapp-x 生成条形码和二维码到底怎么搞我会直接建议别在纯前端层面硬碰硬最稳的是用 UTS 插件封装原生代码Android 端走 ZxingiOS 端走 CoreImage 的 CIFilter。这两套都是各自平台系统级的成熟方案性能和兼容性都有保障。当然我知道不是所有人都愿意碰原生代码所以备选方案我也会在后面展开。2. 方案选型对比UTS 原生插件、web-view 绕路、后端出图到底怎么选在动手之前我把市面上常见做法列了一个对比表这里直接贴出来方便你做决策。方案实现思路优点缺点适合场景UTS 原生插件方案封装 Android Zxing / iOS CoreImage通过 uni_modules 暴露给前端双端原生能力性能好支持二维码和条码多种编码图片质量可控需要写 Kotlin / Swift 代码对纯前端同学有门槛大多数正式项目尤其是对图片尺寸、清晰度、批量生成有要求的场景web-view 绕路方案在 uniapp-x 页面里嵌 web-view加载本地 HTMLHTML 里用 JS 库生成二维码图片再通过 postMessage 或图片回传前端代码量少JS 库生态丰富不用碰原生web-view 加载和通信有延迟生成大量码时体验一般条形码的清晰度受限于渲染尺寸临时演示、工具类小应用、不想引入原生依赖的场景后端生成图片方案服务端用 Zxing 或 Java 库生成 Base64 图片App 直接请求拿到图片展示前端最简单逻辑全部在后端方便后期统一样式和水印依赖网络弱网环境体验差批量场景请求量大而且本质上没解决“App 端自主生成”的问题网络稳定、有现成后端的内部系统Canvas 纯前端绘制解析二维码矩阵或条码编码规则用 uniapp-x 的 canvas 组件逐格绘制不依赖原生也不依赖网络编码规则实现复杂Canvas 接口适配成本高性能堪忧练手项目或者只生成最简单的 QRCode 且能容忍性能问题我最终选了 UTS 原生插件方案原因有三点。第一这条链路是 DCloud 官方推荐的扩展方式后续 HBuilderX 升级、uniapp-x 版本迭代兼容性不会突然断裂。UTS 插件编译后直接打进 App 原生工程不经过 WebView 的中间层稳定性最强。第二Zxing 和 CoreImage 对二维码和条形码的支持都非常完整。Zxing 支持 QRCode、Code128、Code39、EAN-13、UPC-A 等几乎所有常见码制iOS 的 CIFilter 虽然只原生支持 CIQRCodeGenerator二维码和 CICode128BarcodeGeneratorCode128 条码但已经覆盖了大多数业务场景。如果你在某个平台遇到不支持的码制UTS 插件里也方便单独扩展。第三团队里虽然也有人不擅长原生但 UTS 的语法跟 TypeScript 很像Kotlin 和 Swift 的代码量也就几十行花一晚上看基础语法完全能看懂。封装完之后前端调用就是一个简单函数后面谁都能接手维护。我在做之前也下载过几个插件市场的现成二维码插件但发现很多要么只支持 Android要么生成的图片带水印要么需要付费解锁高清尺寸还不如自己封装省心。这个你们根据自己的实际情况取舍。3. UTS 插件封装 Zxing 生成二维码与条形码Android 端3.1 创建 UTS 插件目录结构和接口定义先说项目环境。我用的是 HBuilderX 4.x 版本创建的项目类型是“uni-app x”空模板编译目标包含 Android 和 iOS。UTS 插件有两种存在方式一种是在uni_modules目录下建共享插件方便多项目复用另一种是放在项目根目录utssdk下只能当前项目用。我建议直接放uni_modules因为生成的码工具类太常用了以后别的项目也能直接导入。插件目录结构长这样uni_modules/ └── uts-codegen/ ├── package.json ├── index.uts // 前端引用的接口定义 ├── utssdk/ │ ├── app-android/ │ │ ├── build.gradle // Android 依赖声明 │ │ └── CodeGenerator.kt │ ├── app-ios/ │ │ ├── CodeGenerator.swift │ │ └── Info.plist // 如果不需要权限可以省略 │ └── app-harmony/ // 如果暂时只做双端这里可以先留着不写 └── common/ // 公共类型index.uts是前端唯一需要关心的接口文件我在里面定义了两个方法/** * 生成二维码 * param content 二维码内容可以是文本或 URL * param size 图片尺寸单位 px * returns Base64 字符串不含 data:image/png;base64, 前缀 */ export function generateQRCode(content: string, size: number): string { return Platform.OS android ? androidGenerateQRCode(content, size) : iosGenerateQRCode(content, size); } /** * 生成条形码Code128 * param content 条码内容 * param width 图片宽度 * param height 图片高度 * returns Base64 字符串 */ export function generateBarcode(content: string, width: number, height: number): string { return Platform.OS android ? androidGenerateBarcode(content, width, height) : iosGenerateBarcode(content, width, height); }Android 端的实际实现挂在androidGenerateQRCode和androidGenerateBarcode这两个函数上iOS 端挂到对应的 iOS 函数上。这里我用了Platform.OS做运行时判断逻辑很简单但方便以后扩展到鸿蒙等平台时只改这一个文件。3.2 Android 端 Kotlin 实现Zxing 的矩阵转 BitmapAndroid 端我选 Zxing 3.5.2 版本这个版本比较稳定在 Maven Central 上可以直接拉取。先在build.gradle里加上依赖dependencies { implementation com.google.zxing:core:3.5.2 }这里有个很关键的细节Zxing 本身分core包和javase包。javase里才有MatrixToImageWriter这类把矩阵转成图片的快捷工具类但它在 Android 上用不了因为它依赖 Java 标准库里的ImageIO。Android 端需要自己写一个矩阵转 Bitmap 的逻辑。代码如下package uts.sdk.modules.codegen import android.graphics.Bitmap import android.graphics.Color import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.common.BitMatrix import com.google.zxing.oned.Code128Writer import com.google.zxing.oned.Code39Writer import com.google.zxing.oned.EAN13Writer import com.google.zxing.oned.ITFWriter import java.io.ByteArrayOutputStream import java.util.Base64 import kotlin.math.roundToInt object CodeGenerator { private fun bitMatrixToBitmap(matrix: BitMatrix, width: Int, height: Int): Bitmap { val pixels IntArray(width * height) val stepX matrix.width / width.toFloat() val stepY matrix.height / height.toFloat() for (y in 0 until height) { val matrixY (y * stepY).roundToInt().coerceIn(0, matrix.height - 1) for (x in 0 until width) { val matrixX (x * stepX).roundToInt().coerceIn(0, matrix.width - 1) pixels[y * width x] if (matrix.get(matrixX, matrixY)) Color.BLACK else Color.WHITE } } return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565).apply { setPixels(pixels, 0, width, 0, 0, width, height) } } private fun bitmapToBase64(bitmap: Bitmap): String { val stream ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) return Base64.getEncoder().encodeToString(stream.toByteArray()) } fun generateQRCode(content: String, size: Int): String { val hints mapOf( EncodeHintType.CHARACTER_SET to UTF-8, EncodeHintType.MARGIN to 1, EncodeHintType.ERROR_CORRECTION to H ) val matrix QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size, hints) val bitmap bitMatrixToBitmap(matrix, size, size) return bitmapToBase64(bitmap) } fun generateBarcode(content: String, width: Int, height: Int): String { val hints mapOf( EncodeHintType.CHARACTER_SET to UTF-8, EncodeHintType.MARGIN to 2 ) val writer: Code128Writer Code128Writer() val matrix writer.encode(content, BarcodeFormat.CODE_128, width, height, hints) val bitmap bitMatrixToBitmap(matrix, width, height) return bitmapToBase64(bitmap) } }这里我踩过一个坑需要单独说一下bitMatrixToBitmap里为什么有stepX/stepY这种缩放逻辑。Zxing 的encode方法传入了目标尺寸但它生成的 BitMatrix 内部实际尺寸并非严格等于传入值尤其是条形码高度方向会做修正宽度方向会根据内容动态计算。如果你直接拿 BitMatrix 的 getWidth 和 getHeight 去遍历再创建对应尺寸的 Bitmap经常会出现图片比例不对或者边缘留白很多的问题。所以正确做法是固定按你想要的width * height像素数组去遍历把矩阵坐标映射过去。这样输出的图片尺寸是可控的。另外Base64我用的java.util.Base64这是 Android API 26Android 8.0之后才有的。如果你的 App 还要兼容更低版本记得换成android.util.Base64用法是android.util.Base64.encodeToString(byteArray, android.util.Base64.NO_WRAP)。我的项目 minSdk 设置在当前已经普遍高于 26所以直接用了java.util版本。3.3 Android 端 UTS 封装怎么把 Kotlin 对象暴露给前端Kotlin 代码写完之后需要在utssdk/app-android目录下写一个 UTS 的 Android 实现入口文件一般名字叫UTSAndroid.kt或者直接用index.uts里声明的函数名一一对应。具体写法是这样的package uts.sdk.modules.codegen import uts.sdk.modules.codegen.CodeGenerator // UTS 插件要求暴露一个 UTSAndroid 对象前端 index.uts 里的函数对接这里的同名方法 Suppress(unused) object UTSAndroid { fun androidGenerateQRCode(content: String, size: Number): String { return CodeGenerator.generateQRCode(content, size.toInt()) } fun androidGenerateBarcode(content: String, width: Number, height: Number): String { return CodeGenerator.generateBarcode(content, width.toInt(), height.toInt()) } }注意 UTS 里前端传过来的size在 Kotlin 侧会表现为Number类型不是Int所以这里要做一次toInt()转换。我最早没转直接拿 Number 传给 Zxing 的encode方法编译报错报得人一头雾水后来查文档才明白 UTS 对跨语言类型映射有自己的一套规则这个细节非常容易踩。写完之后在 HBuilderX 里对 uni_modules 目录右键“重新编译”或者直接运行到 Android 真机HBuilderX 会自动把 UTS 插件编进 App 原生工程。如果语法或类型有问题编译期会直接报错不会拖到运行时。4. iOS 端使用 CoreImage 原生生成不改一行前端代码4.1 iOS 端 Swift 实现CIFilter 的两行核心代码iOS 端其实比 Android 简单很多因为系统自带的 CoreImage 框架就内置了二维码和条形码的生成器不需要引入任何第三方依赖。先看二维码。CIQRCodeGenerator这个滤镜在 iOS 7 之后就一直存在用法很简单import CoreImage import UIKit func generateQRCode(content: String, size: CGFloat) - String? { let data Data(content.utf8) let filter CIFilter(name: CIQRCodeGenerator)! filter.setValue(data, forKey: inputMessage) filter.setValue(H, forKey: inputCorrectionLevel) guard let output filter.outputImage else { return nil } // CIFilter 默认生成的是一个 23x23 的小图需要放大 let scale size / output.extent.width let scaledImage output.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) let context CIContext() guard let cgImage context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil } let uiImage UIImage(cgImage: cgImage) guard let pngData uiImage.pngData() else { return nil } return pngData.base64EncodedString() }注意output.transformed(by:)这一步不能省。CIQRCodeGenerator 默认输出的 CIImage 尺寸非常小只有 23x23 个像素点如果直接转图片放到 App 里会模糊得没法扫。很多人第一次用这个滤镜都会漏掉缩放步骤然后跑来问为什么生成的二维码这么糊。条形码用的是CICode128BarcodeGenerator它生成 Code128 格式的条码。这个滤镜默认输出的尺寸也是一小块只是长宽比例跟二维码不同同样需要按目标尺寸缩放func generateBarcode(content: String, width: CGFloat, height: CGFloat) - String? { let data Data(content.utf8) let filter CIFilter(name: CICode128BarcodeGenerator)! filter.setValue(data, forKey: inputMessage) filter.setValue(0.5, forKey: inputQuietSpace) // 左右留白宽度 guard let output filter.outputImage else { return nil } let scaleX width / output.extent.width let scaleY height / output.extent.height let scaledImage output.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY)) let context CIContext() guard let cgImage context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil } let uiImage UIImage(cgImage: cgImage) guard let pngData uiImage.pngData() else { return nil } return pngData.base64EncodedString() }条形码这边有个参数非常有用就是inputQuietSpace。它控制条码左右两侧的空白区域宽度。如果你生成的条形码是给扫描枪扫的这个静区一定要留够否则很多扫码设备会识别不了。我测试下来取值 0.5 到 1.0 之间是安全的太小容易识别失败太大又浪费纸面空间。你们可以根据实际打印效果微调。4.2 iOS 端 UTS 封装Swift 文件的接入方式iOS 端的 UTS 封装逻辑和 Android 端类似也是建一个UTSiOS.swift文件放在utssdk/app-ios目录里import Foundation import CoreImage import UIKit objc(UTSiOS) public class UTSiOS: NSObject { objc public static func iosGenerateQRCode(content: String, size: Double) - String { return generateQRCode(content: content, size: CGFloat(size)) ?? } objc public static func iosGenerateBarcode(content: String, width: Double, height: Double) - String { return generateBarcode(content: content, width: CGFloat(width), height: CGFloat(height)) ?? } }注意几个点Swift 里暴露给 UTS 调用的类必须是objc暴露方法必须是objc且是类方法用static。返回值如果可能为 nil需要在 UTS 侧给一个兜底空字符串不然前端拿到 nil 处理起来很麻烦。还有一点CIContext()的创建是有开销的。如果你在列表页一次性生成几十个二维码每次调用都创建新的 CIContext 会有明显卡顿。我的做法是在类里定义一个静态的懒加载 context复用它private static let ciContext: CIContext CIContext()然后在方法里直接用Self.ciContext.createCGImage(...)。这个优化虽然小但在批量生成场景下体感差异很大建议保留。4.3 双端接口保持一致的好处你可能会问Android 和 iOS 实现完全不一样前端怎么统一调用这就是 UTS 插件设计的好处了。前端index.uts里我已经通过Platform.OS做了分发所以 Vue 页面里的业务代码不需要关心当前跑在什么平台只需要调用generateQRCode和generateBarcode。这就是前面说的“不改一行前端代码”。而且 Base64 返回的格式在双端是一致的一个不含前缀的纯 Base64 字符串。前端展示图片的时候根据自己的需求拼不拼data:image/png;base64,前缀都可以如果要保存到相册或者发给后端直接用纯 Base64 传输更干净。5. 前端页面集成组件化封装、图片保存和常见坑5.1 把插件包一层前端工具类UTS 插件写好后我不建议业务页面直接去调index.uts里的函数因为你还可能要做尺寸单位转换、错误处理、缓存等逻辑。我在项目的utils目录下包了一层codeUtils.tsimport { generateQRCode, generateBarcode } from /uni_modules/uts-codegen; const MARGIN 8; // 给图片留一点边距 /** * 生成二维码图片临时路径 * 返回的是本地文件路径可以直接用于 image 组件展示 */ export async function createQRCodeTempFile(content: string, sizePx: number): Promisestring { if (!content) { return ; } const base64 generateQRCode(content, sizePx); if (!base64) { return ; } // 将 base64 写入临时文件具体函数看下面 const filePath await base64ToTempFile(base64, qr_${Date.now()}.png); return filePath; } /** * 生成条形码图片临时路径 */ export async function createBarcodeTempFile(content: string, widthPx: number, heightPx: number): Promisestring { if (!content) { return ; } const base64 generateBarcode(content, widthPx, heightPx); if (!base64) { return ; } const filePath await base64ToTempFile(base64, bar_${Date.now()}.png); return filePath; } /** * Base64 字符串写入临时文件 * 不同平台的文件系统 API 略有区别这里封装一层 */ async function base64ToTempFile(base64: string, fileName: string): Promisestring { // 使用 uni-app x 提供的文件写入能力 // 具体 API 以你的项目依赖为准核心思路是 // 1. 拿到临时目录路径 // 2. 把 base64 转成 ArrayBuffer // 3. 写入文件 // 4. 返回文件路径 // 我这里按项目里封装好的 writeBase64ToTempFile 处理 return writeBase64ToTempFile(base64, fileName); }这里多说一句为什么要把 Base64 转成临时文件再给 image 组件展示而不是直接用 Data URI 塞给image的 src。我测试下来uniapp-x 的 image 组件在 App 端对超长 Data URI 的支持不太稳定尤其是 Android 端偶尔会出现图片加载不出来的情况。转成本地临时文件路径是最稳妥的而且后续如果要保存到相册也直接有文件可操作。5.2 页面里的使用示例在业务页面里用起来就很简单了。比如一个典型的出库单页面需要展示一个二维码和一个条形码template view classcontainer text classlabel出库单号{{ orderId }}/text image v-ifqrCodePath :srcqrCodePath classcode-image modewidthFix / image v-ifbarcodePath :srcbarcodePath classcode-image bar-height modewidthFix / button clicksaveToAlbum保存到相册/button /view /template script setup import { ref } from vue; import { onLoad } from dcloudio/uni-app; import { createQRCodeTempFile, createBarcodeTempFile } from /utils/codeUtils; const orderId ref(PO20250500123); const qrCodePath ref(); const barcodePath ref(); const tempFilePath ref(); onLoad(async () { // 生成二维码内容里带中文也支持因为底层指定了 UTF-8 qrCodePath.value await createQRCodeTempFile( JSON.stringify({ orderId: orderId.value, ts: Date.now() }), 300 ); // 生成条形码宽度 600高度 200 barcodePath.value await createBarcodeTempFile(orderId.value, 600, 200); }); async function saveToAlbum() { // 把当前展示的图片保存到系统相册用临时文件路径即可 // 需要在 App 配置相册权限 if (!tempFilePath.value) { uni.showToast({ title: 图片还没有生成, icon: none }); return; } uni.saveImageToPhotosAlbum({ filePath: tempFilePath.value, success: () uni.showToast({ title: 已保存, icon: success }), fail: (err) { console.error(保存失败, err); uni.showToast({ title: 保存失败请检查相册权限, icon: none }); } }); } /script这里有两个细节想单独提一下。第一个是二维码内容编码。仓储场景里很多订单号是纯数字但也不排除有字母、中文字符甚至 JSON 字符串。Zxing 和 CoreImage 都支持 UTF-8 编码前端不用做特殊的 encodeURIComponent 处理直接把原始字符串传给生成函数就行。但要注意如果二维码内容太长比如超过 1000 个字符二维码的密度会急剧上升扫描识别的成功率会下降。所以我的建议是内容能精简就精简太长的数据走后端存取二维码里只放一个索引 ID。第二个是生成时机。上面的代码是onLoad里生成一次这是最简单的方案。但如果是长列表页面每一项都要生成一个码我强烈建议做懒加载滚动到可视区域再去生成不然一次性几十个原生生成任务同时跑卡顿还是小事内存占用可能会把低端机压垮。我在列表页里用的是 IntersectionObserver 或者滚动事件 可视区判断的简易懒加载生成完的图片路径缓存在 Map 里避免重复生成。5.3 无法绕开的几个坑尺寸、留白、单位和缓存在这个环节我把实际项目里踩过的几个比较隐蔽的坑集中列一下你们做的时候能少走很多弯路。第一单位的坑。UTS 插件里接收的size、width、height我按 px 处理。但 uniapp-x 里给用户设置界面尺寸时通常用的是 rpx。如果前端直接把 rpx 数值传进来生成出来的图片实际显示尺寸和期望尺寸会差很多。解决办法很简单在调用工具函数之前把 rpx 转成 px。uniapp-x 里可以用uni.upx2px()来做转换或者根据uni.getSystemInfoSync()拿到窗口宽度后自己换算。第二二维码的留白问题。Zxing 生成时我设置了EncodeHintType.MARGIN为 1CoreImage 生成时默认会带一圈静区。这里有个反直觉的现象留白太小扫码机器反而识别不了。二维码最外圈必须有至少 4 个模块宽度的白色区域这是 QRCode 规范里明确规定的。所以我在测试时特意对比了 MARGIN0、MARGIN1、MARGIN4 三种情况用扫码枪实测MARGIN0 时部分扫码枪确实识别失败。最后我统一用 1因为如果前端展示时外面还要套一层圆角卡片卡片背景本身就是白色等于额外加了静区识别完全没问题。如果你要把生成的图片直接贴在非白色背景上记得把 MARGIN 调大一些。第三图片缓存。Base64 转临时文件如果每次都生成新文件临时目录会越积越多最终触发系统清理或者磁盘满。我封装工具类时生成文件名里带了时间戳就是为了避免覆盖但同时我也加了一个“按内容哈希命名”的逻辑同一内容的码只生成一次下次直接复用已有文件。这样既避免了重复生成的开销也不会无限堆积文件。如果你们有清理需求可以在 App 启动时把临时目录里超过 7 天的文件删掉。第四条形码的内容限制。Code128 虽然支持全部 ASCII 字符但某些特殊字符比如中文是编不了的。Zxing 的 Code128Writer 遇到中文字符会直接抛异常CoreImage 的 CICode128BarcodeGenerator 遇到不支持的内容也会返回空。所以条形码内容最好限制为数字、字母、连字符、空格这类纯 ASCII 字符。如果业务上一定要用中文存条码那就只能改用二维码或者先把中文映射成拼音/编号。6. 补充方案web-view 本地 HTML 生成二维码的绕路实现6.1 什么时候才会想起这个方案很多同学可能不想碰 UTS 插件或者项目只是做个内部小工具连“原生工程”的概念都不太想了解。这种时候web-view 绕路方案反而是一个性价比很高的选择。我最初也是先试了这个方案虽然最后因为性能和图片传输的繁琐程度换成了原生方案但它的实现思路还是有参考价值的尤其是对于那些只需要在某个页面偶尔生成一下、不追求极致性能的场景。这个方案的核心思路是uniapp-x 页面里嵌一个 web-viewweb-view 加载本地 HTML 文件HTML 里用前端 JS 库比如 qrcodejs生成二维码图片然后把图片的 Base64 数据通过 postMessage 传回 uniapp-x 侧。6.2 实现步骤和关键代码第一步在项目static/html目录下建一个qrgen.html!DOCTYPE html html head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title二维码生成器/title script src./qrcode.min.js/script script src./bwip-js.js/script /head body script // 接收 uniapp-x 传来的参数 document.addEventListener(message, function (e) { var payload JSON.parse(e.data || {}); if (payload.type qr) { var qr new QRCode(document.getElementById(code), { text: payload.content, width: payload.size || 256, height: payload.size || 256, correctLevel: QRCode.CorrectLevel.H }); var img document.querySelector(#code canvas); var base64 img.toDataURL(image/png); window.postMessage(base64); } else if (payload.type barcode) { try { var canvas document.createElement(canvas); bwipjs.toCanvas(canvas, { bcid: code128, text: payload.content, scale: 3, height: Math.round(payload.height / 2), includetext: true, textxalign: center }); window.postMessage(canvas.toDataURL(image/png)); } catch (err) { window.postMessage(ERROR: err.message); } } }); /script div idcode/div /body /html注意web-view 里加载本地 HTMLqrcode.min.js 和 bwip-js 这些 JS 库也要一起放进static/html目录下。不要在 HTML 里用 CDN 链接因为本地 web-view 可能没有网络权限或者网络加载有延迟。第二步uniapp-x 页面里放一个 web-view 组件并处理通信template web-view :srchtmlPath messageonWebViewMessage /web-view /template script setup import { ref } from vue; import { onLoad } from dcloudio/uni-app; const htmlPath ref(/static/html/qrgen.html); onLoad(() { // 页面加载后给 web-view 发送消息 setTimeout(() { // 这里通过 web-view 的 context 发消息不同版本 API 名称可能不同 uni.postMessageToWebView({ type: qr, content: PO20250500123, size: 256 }); }, 500); }); function onWebViewMessage(e) { const data e.detail.data; if (data data[0]) { const base64 data[0]; // 拿到 base64 后转成临时文件展示 console.log(web-view 返回的二维码图片, base64); } } /script6.3 这个方案的真实痛点我必须说实话这个方案我最终没有采用主要因为它有几个绕不开的痛点。第一个是加载时序问题。web-view 加载本地 HTML 也是异步的你不能保证onLoad一触发 HTML 里的 JS 就已经准备好了。我上面用了 500ms 的 setTimeout这是很糙的做法实际项目里应该用更可靠的“就绪握手”比如 HTML 加载完后主动向 uniapp-x 发一条 ready 消息uniapp-x 收到后再下发生成请求。我图省事用了延时在低端安卓机上就出现过发消息太早、HTML 还没监听 message 的情况。第二个是图片传回效率。HTML 侧生成的图片 Base64 通过 postMessage 传回数据量动辄几十 KB 到几百 KB在 web-view 和原生层之间走这么一大段字符串近实时展示还行批量生成几十个码的时候就会明显感到卡顿和内存飙升。第三个是可维护性。web-view 里的代码是独立的 HTML/JS 世界调试要靠 H5 那套 devtools报错信息也不容易冒泡到 uniapp-x 侧。有一次用户反馈说某个条码生成失败我 debug 了很久才发现是 bwip-js 不支持那串特殊字符。如果走原生方案异常会在 UTS 层直接抛出问题定位会快很多。所以我的建议是web-view 方案只适合“临时顶上”或者“快速验证”的场景。如果你在做正式的仓储、物流、零售类 App还是老老实实走原生插件路线一次封装长期受益。从我个人角度看这次 uniapp-x 项目里最值回票价的决策就是把码图生成从“前端 JS 库思路”切换到“原生能力封装思路”。虽然前期多花了一天写 Kotlin 和 Swift但后面所有页面调用都变得极其干净而且打印出来的条码用工业扫码枪实测识别率非常高。后面如果你们要扩展别的码制比如 EAN-13 商品码或 PDF417 堆叠码也只需要在 UTS 插件里对应平台各加一个方法即可前端接口完全不受影响。最后再分享一个小技巧如果你需要在多端做灰度切换比如 iOS 先走 CoreImageAndroid 临时走 web-viewUTS 的Platform.OS判断可以让两端随时各走各的路调试起来特别方便。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门