From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/components/Qrcode/src/Qrcode.vue | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 253 insertions(+), 0 deletions(-)
diff --git a/src/components/Qrcode/src/Qrcode.vue b/src/components/Qrcode/src/Qrcode.vue
new file mode 100644
index 0000000..f0ce7b7
--- /dev/null
+++ b/src/components/Qrcode/src/Qrcode.vue
@@ -0,0 +1,253 @@
+<script lang="ts" setup>
+import { computed, nextTick, PropType, ref, unref, watch } from 'vue'
+import QRCode, { QRCodeRenderersOptions } from 'qrcode'
+import { cloneDeep } from 'lodash-es'
+import { propTypes } from '@/utils/propTypes'
+import { useDesign } from '@/hooks/web/useDesign'
+import { isString } from '@/utils/is'
+import { QrcodeLogo } from '@/types/qrcode'
+
+defineOptions({ name: 'Qrcode' })
+
+const props = defineProps({
+ // img 鎴栬�� canvas,img涓嶆敮鎸乴ogo宓屽
+ tag: propTypes.string.validate((v: string) => ['canvas', 'img'].includes(v)).def('canvas'),
+ // 浜岀淮鐮佸唴瀹�
+ text: {
+ type: [String, Array] as PropType<string | Recordable[]>,
+ default: null
+ },
+ // qrcode.js閰嶇疆椤�
+ options: {
+ type: Object as PropType<QRCodeRenderersOptions>,
+ default: () => ({})
+ },
+ // 瀹藉害
+ width: propTypes.number.def(200),
+ // logo
+ logo: {
+ type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
+ default: ''
+ },
+ // 鏄惁杩囨湡
+ disabled: propTypes.bool.def(false),
+ // 杩囨湡鎻愮ず鍐呭
+ disabledText: propTypes.string.def('')
+})
+
+const emit = defineEmits(['done', 'click', 'disabled-click'])
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('qrcode')
+
+const { toCanvas, toDataURL } = QRCode
+
+const loading = ref(true)
+
+const wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null)
+
+const renderText = computed(() => String(props.text))
+
+const wrapStyle = computed(() => {
+ return {
+ width: props.width + 'px',
+ height: props.width + 'px'
+ }
+})
+
+const initQrcode = async () => {
+ await nextTick()
+ const options = cloneDeep(props.options || {})
+ if (props.tag === 'canvas') {
+ // 瀹归敊鐜囷紝榛樿瀵瑰唴瀹瑰皯鐨勪簩缁寸爜閲囩敤楂樺閿欑巼锛屽唴瀹瑰鐨勪簩缁寸爜閲囩敤浣庡閿欑巼
+ options.errorCorrectionLevel =
+ options.errorCorrectionLevel || getErrorCorrectionLevel(unref(renderText))
+ const _width: number = await getOriginWidth(unref(renderText), options)
+ options.scale = props.width === 0 ? undefined : (props.width / _width) * 4
+ const canvasRef: HTMLCanvasElement | any = await toCanvas(
+ unref(wrapRef) as HTMLCanvasElement,
+ unref(renderText),
+ options
+ )
+ if (props.logo) {
+ const url = await createLogoCode(canvasRef)
+ emit('done', url)
+ loading.value = false
+ } else {
+ emit('done', canvasRef.toDataURL())
+ loading.value = false
+ }
+ } else {
+ const url = await toDataURL(renderText.value, {
+ errorCorrectionLevel: 'H',
+ width: props.width,
+ ...options
+ })
+ ;(unref(wrapRef) as HTMLImageElement).src = url
+ emit('done', url)
+ loading.value = false
+ }
+}
+
+watch(
+ () => renderText.value,
+ (val) => {
+ if (!val) return
+ initQrcode()
+ },
+ {
+ deep: true,
+ immediate: true
+ }
+)
+
+const createLogoCode = (canvasRef: HTMLCanvasElement) => {
+ const canvasWidth = canvasRef.width
+ const logoOptions: QrcodeLogo = Object.assign(
+ {
+ logoSize: 0.15,
+ bgColor: '#ffffff',
+ borderSize: 0.05,
+ crossOrigin: 'anonymous',
+ borderRadius: 8,
+ logoRadius: 0
+ },
+ isString(props.logo) ? {} : props.logo
+ )
+ const {
+ logoSize = 0.15,
+ bgColor = '#ffffff',
+ borderSize = 0.05,
+ crossOrigin = 'anonymous',
+ borderRadius = 8,
+ logoRadius = 0
+ } = logoOptions
+ const logoSrc = isString(props.logo) ? props.logo : props.logo.src
+ const logoWidth = canvasWidth * logoSize
+ const logoXY = (canvasWidth * (1 - logoSize)) / 2
+ const logoBgWidth = canvasWidth * (logoSize + borderSize)
+ const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2
+
+ const ctx = canvasRef.getContext('2d')
+ if (!ctx) return
+
+ // logo 搴曡壊
+ canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
+ ctx.fillStyle = bgColor
+ ctx.fill()
+
+ // logo
+ const image = new Image()
+ if (crossOrigin || logoRadius) {
+ image.setAttribute('crossOrigin', crossOrigin)
+ }
+ ;(image as any).src = logoSrc
+
+ // 浣跨敤image缁樺埗鍙互閬垮厤鏌愪簺璺ㄥ煙鎯呭喌
+ const drawLogoWithImage = (image: HTMLImageElement) => {
+ ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
+ }
+
+ // 浣跨敤canvas缁樺埗浠ヨ幏寰楁洿澶氱殑鍔熻兘
+ const drawLogoWithCanvas = (image: HTMLImageElement) => {
+ const canvasImage = document.createElement('canvas')
+ canvasImage.width = logoXY + logoWidth
+ canvasImage.height = logoXY + logoWidth
+ const imageCanvas = canvasImage.getContext('2d')
+ if (!imageCanvas || !ctx) return
+ imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
+
+ canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
+ if (!ctx) return
+ const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle
+ ctx.fill()
+ }
+ }
+
+ // 灏� logo缁樺埗鍒� canvas涓�
+ return new Promise((resolve: any) => {
+ image.onload = () => {
+ logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)
+ resolve(canvasRef.toDataURL())
+ }
+ })
+}
+
+// 寰楀埌鍘烸rCode鐨勫ぇ灏忥紝浠ヤ究缂╂斁寰楀埌姝g‘鐨凲rCode澶у皬
+const getOriginWidth = async (content: string, options: QRCodeRenderersOptions) => {
+ const _canvas = document.createElement('canvas')
+ await toCanvas(_canvas, content, options)
+ return _canvas.width
+}
+
+// 瀵逛簬鍐呭灏戠殑QrCode锛屽澶у閿欑巼
+const getErrorCorrectionLevel = (content: string) => {
+ if (content.length > 36) {
+ return 'M'
+ } else if (content.length > 16) {
+ return 'Q'
+ } else {
+ return 'H'
+ }
+}
+
+// copy鏉ョ殑鏂规硶锛岀敤浜庣粯鍒跺渾瑙�
+const canvasRoundRect = (ctx: CanvasRenderingContext2D) => {
+ return (x: number, y: number, w: number, h: number, r: number) => {
+ const minSize = Math.min(w, h)
+ if (r > minSize / 2) {
+ r = minSize / 2
+ }
+ ctx.beginPath()
+ ctx.moveTo(x + r, y)
+ ctx.arcTo(x + w, y, x + w, y + h, r)
+ ctx.arcTo(x + w, y + h, x, y + h, r)
+ ctx.arcTo(x, y + h, x, y, r)
+ ctx.arcTo(x, y, x + w, y, r)
+ ctx.closePath()
+ return ctx
+ }
+}
+
+const clickCode = () => {
+ emit('click')
+}
+
+const disabledClick = () => {
+ emit('disabled-click')
+}
+</script>
+
+<template>
+ <div v-loading="loading" :class="[prefixCls, 'relative inline-block']" :style="wrapStyle">
+ <component :is="tag" ref="wrapRef" @click="clickCode" />
+ <div
+ v-if="disabled"
+ :class="`${prefixCls}--disabled`"
+ class="absolute left-0 top-0 h-full w-full flex items-center justify-center"
+ @click="disabledClick"
+ >
+ <div class="absolute left-[50%] top-[50%] font-bold">
+ <Icon :size="30" color="var(--el-color-primary)" icon="ep:refresh-right" />
+ <div>{{ disabledText }}</div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-qrcode;
+
+.#{$prefix-cls} {
+ &--disabled {
+ background: rgb(255 255 255 / 95%);
+
+ & > div {
+ transform: translate(-50%, -50%);
+ }
+ }
+}
+</style>
--
Gitblit v1.8.0