feat(全局):增加 barcode 二维码组件

pull/345/head
YunaiV 2026-05-17 23:07:56 +08:00
parent 4933180560
commit 0e4012c623
7 changed files with 165 additions and 0 deletions

View File

@ -41,6 +41,7 @@
"@vben/types": "workspace:*",
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"jsbarcode": "catalog:",
"json-bigint": "catalog:",
"qrcode": "catalog:",
"tippy.js": "catalog:",

View File

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { QRCodeRenderersOptions } from 'qrcode';
import type { BarcodeFormat } from './types';
import { computed, nextTick, ref, unref, watch } from 'vue';
import JsBarcode from 'jsbarcode';
import QRCode from 'qrcode';
import { BARCODE_FORMAT_MAP, BarcodeFormatEnum } from './types';
defineOptions({ name: 'Barcode' });
const props = withDefaults(
defineProps<{
content?: string;
displayValue?: boolean;
format?: BarcodeFormat;
height?: number;
width?: number;
}>(),
{
content: '',
displayValue: true,
format: BarcodeFormatEnum.QR_CODE,
height: 100,
width: 200,
},
);
const emit = defineEmits<{
done: [base64: string];
}>();
const loading = ref(true);
const canvasRef = ref<HTMLCanvasElement>();
const imgRef = ref<HTMLImageElement>();
const isQRCode = computed(() => props.format === BarcodeFormatEnum.QR_CODE);
const wrapStyle = computed(() => {
if (isQRCode.value) {
return {
height: `${props.width}px`,
width: `${props.width}px`,
};
}
return {
width: `${props.width}px`,
};
});
async function generateQRCode() {
const canvas = unref(canvasRef);
if (!canvas) {
return;
}
const options: QRCodeRenderersOptions = {
errorCorrectionLevel: 'M',
width: props.width,
};
await QRCode.toCanvas(canvas, props.content, options);
emit('done', canvas.toDataURL());
}
function generateOneDimensionalBarcode() {
const img = unref(imgRef);
if (!img) {
return;
}
JsBarcode(img, props.content, {
displayValue: props.displayValue,
format: BARCODE_FORMAT_MAP[props.format] || 'CODE39',
height: props.height,
margin: 10,
width: 2,
});
emit('done', img.src);
}
async function generateBarcode() {
if (!props.content) {
loading.value = false;
return;
}
loading.value = true;
await nextTick();
try {
if (isQRCode.value) {
await generateQRCode();
return;
}
generateOneDimensionalBarcode();
} catch (error) {
console.error('生成条码失败:', error);
} finally {
loading.value = false;
}
}
watch(
() => [
props.content,
props.displayValue,
props.format,
props.height,
props.width,
],
() => {
generateBarcode();
},
{ immediate: true },
);
function getImageBase64() {
if (isQRCode.value) {
return unref(canvasRef)?.toDataURL() || '';
}
return unref(imgRef)?.src || '';
}
defineExpose({ getImageBase64 });
</script>
<template>
<div :aria-busy="loading" class="inline-block" :style="wrapStyle">
<canvas v-if="isQRCode" ref="canvasRef" class="block max-w-full"></canvas>
<img v-else ref="imgRef" alt="barcode" class="block max-w-full" />
</div>
</template>

View File

@ -0,0 +1,2 @@
export { default as Barcode } from './barcode.vue';
export * from './types';

View File

@ -0,0 +1,18 @@
/** 条码格式枚举 */
export const BarcodeFormatEnum = {
QR_CODE: 1,
EAN13: 2,
CODE39: 3,
UPC_A: 4,
} as const;
export type BarcodeFormat =
(typeof BarcodeFormatEnum)[keyof typeof BarcodeFormatEnum];
/** 条码格式映射表(枚举值 -> JsBarcode 格式名) */
export const BARCODE_FORMAT_MAP: Record<BarcodeFormat, string> = {
[BarcodeFormatEnum.QR_CODE]: 'QR_CODE',
[BarcodeFormatEnum.EAN13]: 'EAN13',
[BarcodeFormatEnum.CODE39]: 'CODE39',
[BarcodeFormatEnum.UPC_A]: 'UPC_A',
};

View File

@ -1,4 +1,5 @@
export * from './api-component';
export * from './barcode';
export * from './captcha';
export * from './card/comparison-card';
export * from './card/statistic-card';

View File

@ -357,6 +357,9 @@ catalogs:
is-ci:
specifier: ^4.1.0
version: 4.1.0
jsbarcode:
specifier: ^3.12.3
version: 3.12.3
jsencrypt:
specifier: ^3.5.4
version: 3.5.4
@ -1955,6 +1958,9 @@ importers:
'@vueuse/integrations':
specifier: 'catalog:'
version: 14.2.1(async-validator@4.2.5)(axios@1.15.0)(change-case@5.4.4)(focus-trap@8.0.1)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.7)(vue@3.5.32(typescript@6.0.2))
jsbarcode:
specifier: 'catalog:'
version: 3.12.3
json-bigint:
specifier: 'catalog:'
version: 1.0.0
@ -9196,6 +9202,9 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsbarcode@3.12.3:
resolution: {integrity: sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg==}
jsdoc-type-pratt-parser@7.1.1:
resolution: {integrity: sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==}
engines: {node: '>=20.0.0'}
@ -19810,6 +19819,8 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbarcode@3.12.3: {}
jsdoc-type-pratt-parser@7.1.1: {}
jsencrypt@3.5.4: {}

View File

@ -138,6 +138,7 @@ catalog:
highlight.js: ^11.11.1
html-minifier-terser: ^7.2.0
is-ci: ^4.1.0
jsbarcode: ^3.12.3
jsencrypt: ^3.5.4
json-bigint: ^1.0.0
lefthook: ^2.1.5