Sfoglia il codice sorgente

feat: Add Qrcode component and add qrcode demo

陈凯龙 3 anni fa
parent
commit
535a31b35e

+ 2 - 0
package.json

@@ -38,6 +38,7 @@
     "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "pinia": "^2.0.9",
+    "qrcode": "^1.5.0",
     "qs": "^6.10.3",
     "vue": "3.2.26",
     "vue-i18n": "9.1.9",
@@ -55,6 +56,7 @@
     "@types/lodash-es": "^4.17.5",
     "@types/node": "^17.0.10",
     "@types/nprogress": "^0.2.0",
+    "@types/qrcode": "^1.4.2",
     "@types/qs": "^6.9.7",
     "@typescript-eslint/eslint-plugin": "^5.10.0",
     "@typescript-eslint/parser": "^5.10.0",

+ 168 - 34
pnpm-lock.yaml

@@ -11,6 +11,7 @@ specifiers:
   '@types/lodash-es': ^4.17.5
   '@types/node': ^17.0.10
   '@types/nprogress': ^0.2.0
+  '@types/qrcode': ^1.4.2
   '@types/qs': ^6.9.7
   '@typescript-eslint/eslint-plugin': ^5.10.0
   '@typescript-eslint/parser': ^5.10.0
@@ -43,6 +44,7 @@ specifiers:
   postcss-less: ^6.0.0
   prettier: ^2.5.1
   pretty-quick: ^3.1.3
+  qrcode: ^1.5.0
   qs: ^6.10.3
   rimraf: ^3.0.2
   stylelint: ^14.2.0
@@ -82,6 +84,7 @@ dependencies:
   mockjs: registry.npmmirror.com/mockjs/1.1.0
   nprogress: registry.npmmirror.com/nprogress/0.2.0
   pinia: registry.npmmirror.com/pinia/2.0.9_typescript@4.5.5+vue@3.2.26
+  qrcode: registry.npmmirror.com/qrcode/1.5.0
   qs: registry.npmmirror.com/qs/6.10.3
   vue: registry.npmmirror.com/vue/3.2.26
   vue-i18n: registry.npmmirror.com/vue-i18n/9.1.9_vue@3.2.26
@@ -99,6 +102,7 @@ devDependencies:
   '@types/lodash-es': registry.npmmirror.com/@types/lodash-es/4.17.5
   '@types/node': registry.npmmirror.com/@types/node/17.0.10
   '@types/nprogress': registry.npmmirror.com/@types/nprogress/0.2.0
+  '@types/qrcode': registry.npmmirror.com/@types/qrcode/1.4.2
   '@types/qs': registry.npmmirror.com/@types/qs/6.9.7
   '@typescript-eslint/eslint-plugin': registry.npmmirror.com/@typescript-eslint/eslint-plugin/5.10.0_706fb07ce74b1db611f19a02ad2ce784
   '@typescript-eslint/parser': registry.npmmirror.com/@typescript-eslint/parser/5.10.0_eslint@8.7.0+typescript@4.5.5
@@ -469,7 +473,6 @@ packages:
     name: ansi-regex
     version: 5.0.1
     engines: { node: '>=8' }
-    dev: true
 
   registry.nlark.com/ansi-regex/6.0.1:
     resolution:
@@ -483,20 +486,6 @@ packages:
     engines: { node: '>=12' }
     dev: true
 
-  registry.nlark.com/ansi-styles/4.3.0:
-    resolution:
-      {
-        integrity: sha1-7dgDYornHATIWuegkG7a00tkiTc=,
-        registry: https://registry.npm.taobao.org/,
-        tarball: https://registry.nlark.com/ansi-styles/download/ansi-styles-4.3.0.tgz
-      }
-    name: ansi-styles
-    version: 4.3.0
-    engines: { node: '>=8' }
-    dependencies:
-      color-convert: registry.npmmirror.com/color-convert/2.0.1
-    dev: true
-
   registry.nlark.com/anymatch/3.1.2:
     resolution:
       {
@@ -1038,6 +1027,21 @@ packages:
     version: 2.2.1
     dev: true
 
+  registry.nlark.com/cliui/6.0.0:
+    resolution:
+      {
+        integrity: sha1-UR1wLAxOQcoVbX0OlgIfI+EyJbE=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/cliui/download/cliui-6.0.0.tgz
+      }
+    name: cliui
+    version: 6.0.0
+    dependencies:
+      string-width: registry.npmmirror.com/string-width/4.2.3
+      strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1
+      wrap-ansi: registry.nlark.com/wrap-ansi/6.2.0
+    dev: false
+
   registry.nlark.com/cliui/7.0.4:
     resolution:
       {
@@ -1114,7 +1118,6 @@ packages:
       }
     name: color-name
     version: 1.1.4
-    dev: true
 
   registry.nlark.com/compare-func/2.0.0:
     resolution:
@@ -1498,6 +1501,17 @@ packages:
     engines: { node: '>=0.3.1' }
     dev: true
 
+  registry.nlark.com/dijkstrajs/1.0.2:
+    resolution:
+      {
+        integrity: sha1-LkjA07glRir+datK1egpyOzjYlc=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/dijkstrajs/download/dijkstrajs-1.0.2.tgz
+      }
+    name: dijkstrajs
+    version: 1.0.2
+    dev: false
+
   registry.nlark.com/dir-glob/3.0.1:
     resolution:
       {
@@ -2343,7 +2357,6 @@ packages:
     name: get-caller-file
     version: 2.0.5
     engines: { node: 6.* || 8.* || >= 10.* }
-    dev: true
 
   registry.nlark.com/get-intrinsic/1.1.1:
     resolution:
@@ -3051,7 +3064,6 @@ packages:
     name: is-fullwidth-code-point
     version: 3.0.0
     engines: { node: '>=8' }
-    dev: true
 
   registry.nlark.com/is-fullwidth-code-point/4.0.0:
     resolution:
@@ -3511,7 +3523,6 @@ packages:
     engines: { node: '>=8' }
     dependencies:
       p-locate: registry.nlark.com/p-locate/4.1.0
-    dev: true
 
   registry.nlark.com/locate-path/6.0.0:
     resolution:
@@ -4163,7 +4174,6 @@ packages:
     engines: { node: '>=6' }
     dependencies:
       p-try: registry.npmmirror.com/p-try/2.2.0
-    dev: true
 
   registry.nlark.com/p-limit/3.1.0:
     resolution:
@@ -4191,7 +4201,6 @@ packages:
     engines: { node: '>=8' }
     dependencies:
       p-limit: registry.nlark.com/p-limit/2.3.0
-    dev: true
 
   registry.nlark.com/p-locate/5.0.0:
     resolution:
@@ -4307,7 +4316,6 @@ packages:
     name: path-exists
     version: 4.0.0
     engines: { node: '>=8' }
-    dev: true
 
   registry.nlark.com/path-is-absolute/1.0.1:
     resolution:
@@ -4381,6 +4389,18 @@ packages:
     dev: true
     optional: true
 
+  registry.nlark.com/pngjs/5.0.0:
+    resolution:
+      {
+        integrity: sha1-553SshV2f9nARWHAEjbflgvOf7s=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/pngjs/download/pngjs-5.0.0.tgz
+      }
+    name: pngjs
+    version: 5.0.0
+    engines: { node: '>=10.13.0' }
+    dev: false
+
   registry.nlark.com/posix-character-classes/0.1.1:
     resolution:
       {
@@ -4906,6 +4926,17 @@ packages:
     engines: { node: '>=0.10.0' }
     dev: true
 
+  registry.nlark.com/require-main-filename/2.0.0:
+    resolution:
+      {
+        integrity: sha1-0LMp7MfMD2Fkn2IhW+aa9UqomJs=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/require-main-filename/download/require-main-filename-2.0.0.tgz
+      }
+    name: require-main-filename
+    version: 2.0.0
+    dev: false
+
   registry.nlark.com/resolve-dir/1.0.1:
     resolution:
       {
@@ -5166,6 +5197,17 @@ packages:
       upper-case-first: registry.nlark.com/upper-case-first/2.0.2
     dev: true
 
+  registry.nlark.com/set-blocking/2.0.0:
+    resolution:
+      {
+        integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/set-blocking/download/set-blocking-2.0.0.tgz
+      }
+    name: set-blocking
+    version: 2.0.0
+    dev: false
+
   registry.nlark.com/set-value/2.0.1:
     resolution:
       {
@@ -6107,6 +6149,17 @@ packages:
     version: 5.0.0
     dev: true
 
+  registry.nlark.com/which-module/2.0.0:
+    resolution:
+      {
+        integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/which-module/download/which-module-2.0.0.tgz
+      }
+    name: which-module
+    version: 2.0.0
+    dev: false
+
   registry.nlark.com/which/1.3.1:
     resolution:
       {
@@ -6179,7 +6232,6 @@ packages:
       ansi-styles: registry.npmmirror.com/ansi-styles/4.3.0
       string-width: registry.npmmirror.com/string-width/4.2.3
       strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1
-    dev: true
 
   registry.nlark.com/wrap-ansi/7.0.0:
     resolution:
@@ -6224,6 +6276,17 @@ packages:
       typedarray-to-buffer: registry.nlark.com/typedarray-to-buffer/3.1.5
     dev: true
 
+  registry.nlark.com/y18n/4.0.3:
+    resolution:
+      {
+        integrity: sha1-tfJZyCzW4zaSHv17/Yv1YN6e7t8=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.nlark.com/y18n/download/y18n-4.0.3.tgz
+      }
+    name: y18n
+    version: 4.0.3
+    dev: false
+
   registry.nlark.com/y18n/5.0.8:
     resolution:
       {
@@ -7680,6 +7743,19 @@ packages:
     version: 4.0.0
     dev: true
 
+  registry.npmmirror.com/@types/qrcode/1.4.2:
+    resolution:
+      {
+        integrity: sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ==,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.npmmirror.com/@types/qrcode/download/@types/qrcode-1.4.2.tgz
+      }
+    name: '@types/qrcode'
+    version: 1.4.2
+    dependencies:
+      '@types/node': registry.npmmirror.com/@types/node/17.0.10
+    dev: true
+
   registry.npmmirror.com/@types/qs/6.9.7:
     resolution:
       {
@@ -8592,7 +8668,6 @@ packages:
     engines: { node: '>=8' }
     dependencies:
       color-convert: registry.npmmirror.com/color-convert/2.0.1
-    dev: true
 
   registry.npmmirror.com/ansi-styles/6.1.0:
     resolution:
@@ -8797,7 +8872,6 @@ packages:
     name: camelcase
     version: 5.3.1
     engines: { node: '>=6' }
-    dev: true
 
   registry.npmmirror.com/camelcase/6.2.1:
     resolution:
@@ -8867,7 +8941,7 @@ packages:
     version: 3.0.0
     engines: { node: '>=8' }
     dependencies:
-      ansi-styles: registry.nlark.com/ansi-styles/4.3.0
+      ansi-styles: registry.npmmirror.com/ansi-styles/4.3.0
       supports-color: registry.npmmirror.com/supports-color/7.2.0
     dev: true
 
@@ -8987,7 +9061,6 @@ packages:
     engines: { node: '>=7.0.0' }
     dependencies:
       color-name: registry.nlark.com/color-name/1.1.4
-    dev: true
 
   registry.npmmirror.com/colord/2.9.2:
     resolution:
@@ -9339,7 +9412,6 @@ packages:
     name: decamelize
     version: 1.2.0
     engines: { node: '>=0.10.0' }
-    dev: true
 
   registry.npmmirror.com/declass/0.0.1:
     resolution:
@@ -9483,7 +9555,6 @@ packages:
       }
     name: emoji-regex
     version: 8.0.0
-    dev: true
 
   registry.npmmirror.com/emoji-regex/9.2.2:
     resolution:
@@ -9496,6 +9567,17 @@ packages:
     version: 9.2.2
     dev: true
 
+  registry.npmmirror.com/encode-utf8/1.0.3:
+    resolution:
+      {
+        integrity: sha1-8w/dMdoH+1lvKBvrL2sCeFGZTNo=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.npmmirror.com/encode-utf8/download/encode-utf8-1.0.3.tgz
+      }
+    name: encode-utf8
+    version: 1.0.3
+    dev: false
+
   registry.npmmirror.com/error-ex/1.3.2:
     resolution:
       {
@@ -10247,7 +10329,6 @@ packages:
     dependencies:
       locate-path: registry.nlark.com/locate-path/5.0.0
       path-exists: registry.nlark.com/path-exists/4.0.0
-    dev: true
 
   registry.npmmirror.com/find-up/5.0.0:
     resolution:
@@ -11310,7 +11391,6 @@ packages:
     name: p-try
     version: 2.2.0
     engines: { node: '>=6' }
-    dev: true
 
   registry.npmmirror.com/parent-module/1.0.1:
     resolution:
@@ -11609,6 +11689,24 @@ packages:
     version: 2.0.0
     dev: true
 
+  registry.npmmirror.com/qrcode/1.5.0:
+    resolution:
+      {
+        integrity: sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ==,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.npmmirror.com/qrcode/download/qrcode-1.5.0.tgz
+      }
+    name: qrcode
+    version: 1.5.0
+    engines: { node: '>=10.13.0' }
+    hasBin: true
+    dependencies:
+      dijkstrajs: registry.nlark.com/dijkstrajs/1.0.2
+      encode-utf8: registry.npmmirror.com/encode-utf8/1.0.3
+      pngjs: registry.nlark.com/pngjs/5.0.0
+      yargs: registry.npmmirror.com/yargs/15.4.1
+    dev: false
+
   registry.npmmirror.com/qs/6.10.3:
     resolution:
       {
@@ -11661,7 +11759,6 @@ packages:
     name: require-directory
     version: 2.1.1
     engines: { node: '>=0.10.0' }
-    dev: true
 
   registry.npmmirror.com/resolve-global/1.0.0:
     resolution:
@@ -11885,7 +11982,6 @@ packages:
       emoji-regex: registry.npmmirror.com/emoji-regex/8.0.0
       is-fullwidth-code-point: registry.nlark.com/is-fullwidth-code-point/3.0.0
       strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1
-    dev: true
 
   registry.npmmirror.com/string-width/5.0.1:
     resolution:
@@ -11957,7 +12053,6 @@ packages:
     engines: { node: '>=8' }
     dependencies:
       ansi-regex: registry.nlark.com/ansi-regex/5.0.1
-    dev: true
 
   registry.npmmirror.com/strip-ansi/7.0.1:
     resolution:
@@ -12972,6 +13067,21 @@ packages:
     engines: { node: '>= 6' }
     dev: true
 
+  registry.npmmirror.com/yargs-parser/18.1.3:
+    resolution:
+      {
+        integrity: sha1-vmjEl1xrKr9GkjawyHA2L6sJp7A=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.npmmirror.com/yargs-parser/download/yargs-parser-18.1.3.tgz
+      }
+    name: yargs-parser
+    version: 18.1.3
+    engines: { node: '>=6' }
+    dependencies:
+      camelcase: registry.npmmirror.com/camelcase/5.3.1
+      decamelize: registry.npmmirror.com/decamelize/1.2.0
+    dev: false
+
   registry.npmmirror.com/yargs-parser/20.2.9:
     resolution:
       {
@@ -12996,6 +13106,30 @@ packages:
     engines: { node: '>=12' }
     dev: true
 
+  registry.npmmirror.com/yargs/15.4.1:
+    resolution:
+      {
+        integrity: sha1-DYehbeAa7p2L7Cv7909nhRcw9Pg=,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.npmmirror.com/yargs/download/yargs-15.4.1.tgz
+      }
+    name: yargs
+    version: 15.4.1
+    engines: { node: '>=8' }
+    dependencies:
+      cliui: registry.nlark.com/cliui/6.0.0
+      decamelize: registry.npmmirror.com/decamelize/1.2.0
+      find-up: registry.npmmirror.com/find-up/4.1.0
+      get-caller-file: registry.nlark.com/get-caller-file/2.0.5
+      require-directory: registry.npmmirror.com/require-directory/2.1.1
+      require-main-filename: registry.nlark.com/require-main-filename/2.0.0
+      set-blocking: registry.nlark.com/set-blocking/2.0.0
+      string-width: registry.npmmirror.com/string-width/4.2.3
+      which-module: registry.nlark.com/which-module/2.0.0
+      y18n: registry.nlark.com/y18n/4.0.3
+      yargs-parser: registry.npmmirror.com/yargs-parser/18.1.3
+    dev: false
+
   registry.npmmirror.com/yargs/17.3.1:
     resolution:
       {

+ 3 - 0
src/components/Qrcode/index.ts

@@ -0,0 +1,3 @@
+import Qrcode from './src/Qrcode.vue'
+
+export { Qrcode }

+ 251 - 0
src/components/Qrcode/src/Qrcode.vue

@@ -0,0 +1,251 @@
+<script setup lang="ts">
+import { PropType, nextTick, ref, watch, computed, unref } from 'vue'
+import QRCode from 'qrcode'
+import { QRCodeRenderersOptions } from 'qrcode'
+import { cloneDeep } from 'lodash-es'
+import { propTypes } from '@/utils/propTypes'
+import { useDesign } from '@/hooks/web/useDesign'
+import { isString } from '@/utils/is'
+
+const props = defineProps({
+  // img 或者 canvas,img不支持logo嵌套
+  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<boolean>(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 = 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())
+    }
+  })
+}
+
+// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
+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 top-0 left-0 flex w-full h-full items-center justify-center"
+      @click="disabledClick"
+    >
+      <div class="absolute top-[50%] left-[50%] font-bold">
+        <Icon icon="ep:refresh-right" :size="30" color="var(--el-color-primary)" />
+        <div>{{ disabledText }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="less" scoped>
+@prefix-cls: ~'@{namespace}-qrcode';
+
+.@{prefix-cls} {
+  &--disabled {
+    background: rgba(255, 255, 255, 0.95);
+
+    & > div {
+      transform: translate(-50%, -50%);
+    }
+  }
+}
+</style>

+ 15 - 1
src/locales/en.ts

@@ -90,7 +90,8 @@ export default {
     icon: 'Icon',
     echart: 'Echart',
     countTo: 'Count to',
-    watermark: 'Watermark'
+    watermark: 'Watermark',
+    qrcode: 'Qrcode'
   },
   analysis: {
     newUser: 'New user',
@@ -231,5 +232,18 @@ export default {
     createdWatermark: 'Created watermark',
     clearWatermark: 'Clear watermark',
     resetWatermark: 'Reset watermark'
+  },
+  qrcodeDemo: {
+    qrcode: 'Qrcode',
+    qrcodeDes: 'Secondary packaging based on qrcode',
+    basicUsage: 'Basic usage',
+    imgTag: 'Img tag',
+    style: 'Style config',
+    click: 'Click event',
+    asynchronousContent: 'Asynchronous content',
+    invalid: 'Invalid',
+    logoConfig: 'Logo config',
+    logoStyle: 'Logo style',
+    size: 'size config'
   }
 }

+ 15 - 1
src/locales/zh-CN.ts

@@ -90,7 +90,8 @@ export default {
     icon: '图标',
     echart: '图表',
     countTo: '数字动画',
-    watermark: '水印'
+    watermark: '水印',
+    qrcode: '二维码'
   },
   analysis: {
     newUser: '新增用户',
@@ -230,5 +231,18 @@ export default {
     createdWatermark: '创建水印',
     clearWatermark: '清除水印',
     resetWatermark: '重置水印'
+  },
+  qrcodeDemo: {
+    qrcode: '二维码',
+    qrcodeDes: '基于 qrcode 二次封装',
+    basicUsage: '基础用法',
+    imgTag: 'img标签',
+    style: '样式配置',
+    click: '点击事件',
+    asynchronousContent: '异步内容',
+    invalid: '失效',
+    logoConfig: 'logo配置',
+    logoStyle: 'logo样式',
+    size: '大小配置'
   }
 }

+ 8 - 0
src/router/index.ts

@@ -127,6 +127,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: t('router.watermark')
         }
+      },
+      {
+        path: 'qrcode',
+        component: () => import('@/views/Components/Qrcode.vue'),
+        name: 'Qrcode',
+        meta: {
+          title: t('router.qrcode')
+        }
       }
     ]
   },

+ 108 - 0
src/views/Components/Qrcode.vue

@@ -0,0 +1,108 @@
+<script setup lang="ts">
+import { Qrcode } from '@/components/Qrcode'
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { computed, ref, unref } from 'vue'
+import { useAppStore } from '@/store/modules/app'
+import { ElRow, ElCard, ElCol, ElMessage } from 'element-plus'
+// @ts-ignore
+import logoImg from '@/assets/imgs/logo.png'
+
+const appStore = useAppStore()
+
+const { t } = useI18n()
+
+const title = computed(() => appStore.getTitle)
+
+const asyncTitle = ref('')
+
+setTimeout(() => {
+  asyncTitle.value = unref(title)
+}, 3000)
+
+const codeClick = () => {
+  ElMessage.info(t('qrcodeDemo.click'))
+}
+
+const disabledClick = () => {
+  ElMessage.info(t('qrcodeDemo.invalid'))
+}
+</script>
+
+<template>
+  <ContentWrap :title="t('qrcodeDemo.qrcode')" :message="t('qrcodeDemo.qrcodeDes')">
+    <ElRow :gutter="20" justify="space-between">
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.basicUsage') }}</div>
+          <Qrcode :text="title" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.imgTag') }}</div>
+          <Qrcode :text="title" tag="img" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.style') }}</div>
+          <Qrcode
+            :text="title"
+            :options="{
+              color: {
+                dark: '#55D187',
+                light: '#2d8cf0'
+              }
+            }"
+          />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.click') }}</div>
+          <Qrcode :text="title" @click="codeClick" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.asynchronousContent') }}</div>
+          <Qrcode :text="asyncTitle" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.invalid') }}</div>
+          <Qrcode :text="title" disabled @disabled-click="disabledClick" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.logoConfig') }}</div>
+          <Qrcode :text="title" :logo="logoImg" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.logoStyle') }}</div>
+          <Qrcode
+            :text="title"
+            :logo="{
+              src: logoImg,
+              logoSize: 0.2,
+              borderSize: 0.05,
+              borderRadius: 50,
+              bgColor: 'blue'
+            }"
+          />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-10px text-center">
+          <div class="font-bold mb-10px">{{ t('qrcodeDemo.size') }}</div>
+          <Qrcode :text="title" :width="250" />
+        </ElCard>
+      </ElCol>
+    </ElRow>
+  </ContentWrap>
+</template>

+ 2 - 1
tsconfig.json

@@ -27,7 +27,8 @@
       "@intlify/vite-plugin-vue-i18n/client",
       "vite/client",
       "element-plus/global",
-      "@types/intro.js"
+      "@types/intro.js",
+      "@types/qrcode"
     ],
     "typeRoots": ["./node_modules/@types/", "./types"]
   },

+ 9 - 0
types/componentType/qrcode.d.ts

@@ -0,0 +1,9 @@
+declare interface QrcodeLogo {
+  src?: string
+  logoSize?: number
+  bgColor?: string
+  borderSize?: number
+  crossOrigin?: string
+  borderRadius?: number
+  logoRadius?: number
+}

+ 2 - 1
vite.config.ts

@@ -133,7 +133,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         'qs',
         'echarts',
         'echarts-wordcloud',
-        'intro.js'
+        'intro.js',
+        'qrcode'
       ]
     }
   }