Browse Source

feat: 🎸 新增二维码组件

chenkl 4 years ago
parent
commit
85555eef7d

+ 9 - 1
mock/role/admin-role.ts

@@ -46,6 +46,10 @@ export const checkedNodes = [{
     'path': '/components-demo/detail',
     'title': '详情组件',
     'name': 'DetailDemo'
+  }, {
+    'path': '/components-demo/qrcode',
+    'title': '二维码组件',
+    'name': 'QrcodeDemo'
   }]
 }, {
   'path': '/components-demo/echarts',
@@ -91,6 +95,10 @@ export const checkedNodes = [{
   'path': '/components-demo/detail',
   'title': '详情组件',
   'name': 'DetailDemo'
+}, {
+  'path': '/components-demo/qrcode',
+  'title': '二维码组件',
+  'name': 'QrcodeDemo'
 }, {
   'path': '/table-demo',
   'title': '表格',
@@ -407,7 +415,7 @@ export const checkedNodes = [{
 export const checkedkeys = ['/components-demo', '/components-demo/echarts', '/components-demo/preview',
   '/components-demo/button', '/components-demo/message', '/components-demo/count-to', '/components-demo/search',
   '/components-demo/editor', '/components-demo/markdown', '/components-demo/dialog', '/components-demo/more',
-  '/components-demo/detail', '/table-demo', '/table-demo/basic-table', '/table-demo/page-table',
+  '/components-demo/detail', '/components-demo/qrcode', '/table-demo', '/table-demo/basic-table', '/table-demo/page-table',
   '/table-demo/stripe-table', '/table-demo/border-table', '/table-demo/state-table', '/table-demo/fixed-header',
   '/table-demo/fixed-column', '/table-demo/fixed-column-header', '/table-demo/fluid-height',
   '/table-demo/multi-header', '/table-demo/single-choice', '/table-demo/multiple-choice', '/table-demo/sort-table',

+ 8 - 0
mock/role/test-role.ts

@@ -97,6 +97,14 @@ export const checkedRoleNodes = [
         meta: {
           title: '详情组件'
         }
+      },
+      {
+        path: 'qrcode',
+        component: 'pages/index/views/components-demo/qrcode/index',
+        name: 'QrcodeDemo',
+        meta: {
+          title: '二维码组件'
+        }
       }
     ]
   },

+ 2 - 0
package.json

@@ -28,6 +28,7 @@
     "normalize.css": "^8.0.1",
     "nprogress": "^0.2.0",
     "path-to-regexp": "^6.2.0",
+    "qrcode": "^1.4.4",
     "qs": "^6.9.4",
     "screenfull": "^5.0.2",
     "vditor": "^3.7.0",
@@ -46,6 +47,7 @@
     "@types/lodash-es": "^4.17.3",
     "@types/mockjs": "^1.0.3",
     "@types/nprogress": "^0.2.0",
+    "@types/qrcode": "^1.3.5",
     "@typescript-eslint/eslint-plugin": "^4.5.0",
     "@typescript-eslint/parser": "^4.5.0",
     "@vue/cli-plugin-babel": "^4.5.9",

+ 120 - 1
public/index.html

@@ -11,7 +11,126 @@
     <noscript>
       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
     </noscript>
-    <div id="app"></div>
+    <div id="app">
+      <style>
+        .app-loading {
+          display: flex;
+          width: 100%;
+          height: 100%;
+          justify-content: center;
+          align-items: center;
+          flex-direction: column;
+          background: #f0f2f5;
+        }
+        .app-loading .app-loading-wrap {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          display: flex;
+          -webkit-transform: translate3d(-50%, -50%, 0);
+          transform: translate3d(-50%, -50%, 0);
+          justify-content: center;
+          align-items: center;
+          flex-direction: column;
+        }
+        .app-loading .app-loading-title {
+          font-size: 20px;
+          font-weight: bold;
+          text-align: center;
+          margin-bottom: 30px;
+        }
+        .app-loading .app-loading-logo {
+          width: 100px;
+          margin-bottom: 15px;
+        }
+        .app-loading .app-loading-item {
+          position: relative;
+          width: 60px;
+          height: 60px;
+          border-radius: 50%;
+          display: inline-block;
+          vertical-align: middle;
+        }
+        .app-loading .app-loading-outter {
+          position: absolute;
+          border: 4px solid #2d8cf0;
+          border-left-color: transparent;
+          border-bottom: 0;
+          width: 100%;
+          height: 100%;
+          border-radius: 50%;
+          animation: loader-outter 1s cubic-bezier(.42, .61, .58, .41) infinite;
+        }
+        .app-loading .app-loading-inner {
+          position: absolute;
+          border: 4px solid #87bdff;
+          border-radius: 50%;
+          width: 40px;
+          height: 40px;
+          left: calc(50% - 20px);
+          top: calc(50% - 20px);
+          border-right: 0;
+          border-top-color: transparent;
+          animation: loader-inner 1s cubic-bezier(.42, .61, .58, .41) infinite;
+        }
+      
+        @-webkit-keyframes loader-outter {
+          0% {
+            -webkit-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          100% {
+            -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+          }
+        }
+        
+        @keyframes loader-outter {
+          0% {
+            -webkit-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          100% {
+            -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+          }
+        }
+        
+        @-webkit-keyframes loader-inner {
+          0% {
+            -webkit-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          100% {
+            -webkit-transform: rotate(-360deg);
+            transform: rotate(-360deg);
+          }
+        }
+        
+        @keyframes loader-inner {
+          0% {
+            -webkit-transform: rotate(0deg);
+            transform: rotate(0deg);
+          }
+          100% {
+            -webkit-transform: rotate(-360deg);
+            transform: rotate(-360deg);
+          }
+        }
+      </style>
+      <div class="app-loading">
+        <div class="app-loading-wrap">
+          <div class="app-loading-title">
+            <img src="./logo.png" class="app-loading-logo" alt="Logo" />
+            <div class="app-loading-title"><%= htmlWebpackPlugin.options.title %></div>
+          </div>
+          <div class="app-loading-item">
+            <div class="app-loading-outter"></div>
+            <div class="app-loading-inner"></div>
+          </div>
+        </div>
+      </div>
+    </div>
     <!-- built files will be auto injected -->
   </body>
 </html>

BIN
public/logo.png


+ 274 - 0
src/components/Qrcode/index.vue

@@ -0,0 +1,274 @@
+<template>
+  <div v-loading="loading" class="qrcode__wrap" :style="wrapStyle">
+    <component :is="tag" ref="wrapRef" @click="clickCode" />
+    <div v-if="disabled" class="disabled__wrap" @click="disabledClick">
+      <div>
+        <i class="el-icon-refresh-right" />
+        <div>{{ disabledText }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType, nextTick, ref, watch, computed, unref } from 'vue'
+import type { LogoTypes } from './types'
+import QRCode from 'qrcode'
+import type { QRCodeRenderersOptions } from 'qrcode'
+import { deepClone } from '@/utils'
+import { isString } from '@/utils/is'
+const { toCanvas, toDataURL } = QRCode
+export default defineComponent({
+  name: 'Qrcode',
+  props: {
+    // img 或者 canvas,img不支持logo嵌套
+    tag: {
+      type: String as PropType<'canvas' | 'img'>,
+      default: 'canvas',
+      validator: (v: string) => ['canvas', 'img'].includes(v)
+    },
+    // 二维码内容
+    text: {
+      type: [String, Array] as PropType<string | any[]>,
+      default: null
+    },
+    // qrcode.js配置项
+    options: {
+      type: Object as PropType<QRCodeRenderersOptions>,
+      default: null
+    },
+    // 宽度
+    width: {
+      type: Number as PropType<number>,
+      default: 200
+    },
+    // logo
+    logo: {
+      type: [String, Object] as PropType<Partial<LogoTypes> | string>,
+      default: ''
+    },
+    // 是否过期
+    disabled: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    // 过期提示内容
+    disabledText: {
+      type: String as PropType<string>,
+      default: '二维码已失效'
+    }
+  },
+  emits: ['done', 'click', 'disabled-click'],
+  setup(props, { emit }) {
+    const loading = ref<boolean>(true)
+    const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null)
+    const renderText = computed(() => String(props.text))
+    const wrapStyle = computed(() => {
+      return {
+        width: props.width + 'px',
+        height: props.width + 'px'
+      }
+    })
+
+    watch(
+      () => renderText.value,
+      (val) => {
+        if (!val) return
+        initQrcode()
+      },
+      {
+        deep: true,
+        immediate: true
+      }
+    )
+
+    // 初始化
+    function initQrcode() {
+      nextTick(async() => {
+        const options = deepClone(props.options || {})
+        if (props.tag === 'canvas') {
+          // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
+          options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(renderText.value)
+          getOriginWidth(renderText.value, options).then(async(_width) => {
+            options.scale = props.width === 0 ? undefined : (props.width / _width) * 4
+            const canvasRef: any = await toCanvas(unref(wrapRef as any), renderText.value, 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 any).src = url
+          emit('done', url)
+          loading.value = false
+        }
+      })
+    }
+
+    // 生成logo
+    function createLogoCode(canvasRef: HTMLCanvasElement) {
+      const canvasWidth = canvasRef.width
+      const logoOptions: LogoTypes = 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大小
+    function getOriginWidth(content: string, options: QRCodeRenderersOptions) {
+      const _canvas = document.createElement('canvas')
+      return toCanvas(_canvas, content, options).then(() => _canvas.width)
+    }
+
+    // 对于内容少的QrCode,增大容错率
+    function getErrorCorrectionLevel(content: string) {
+      if (content.length > 36) {
+        return 'M'
+      } else if (content.length > 16) {
+        return 'Q'
+      } else {
+        return 'H'
+      }
+    }
+
+    // 点击二维码
+    function clickCode() {
+      emit('click')
+    }
+
+    // 失效点击事件
+    function disabledClick() {
+      emit('disabled-click')
+    }
+
+    // copy来的方法,用于绘制圆角
+    function 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
+      }
+    }
+
+    return {
+      loading,
+      wrapRef,
+      renderText,
+      wrapStyle,
+      clickCode,
+      disabledClick
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.qrcode__wrap {
+  display: inline-block;
+  position: relative;
+  .disabled__wrap {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: rgba(255,255,255,0.95);
+    top: 0;
+    left: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    &>div {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-weight: bold;
+      i {
+        font-size: 30px;
+        margin-bottom: 10px;
+      }
+    }
+  }
+}
+</style>

+ 9 - 0
src/components/Qrcode/types.ts

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

+ 8 - 0
src/pages/index/router/index.ts

@@ -197,6 +197,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: '详情组件'
         }
+      },
+      {
+        path: 'qrcode',
+        component: () => import('_p/index/views/components-demo/qrcode/index.vue'),
+        name: 'QrcodeDemo',
+        meta: {
+          title: '二维码组件'
+        }
       }
     ]
   },

+ 106 - 0
src/pages/index/views/components-demo/qrcode/index.vue

@@ -0,0 +1,106 @@
+<template>
+  <div>
+    <el-row :gutter="20">
+      <el-col :span="6">
+        <div class="title-item">基础用法,默认canvas</div>
+        <qrcode text="vue-element-admin2.0" />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">img标签</div>
+        <qrcode text="vue-element-admin2.0" tag="img" />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">样式配置</div>
+        <qrcode
+          text="vue-element-admin2.0"
+          :options="{
+            color: {
+              dark: '#55D187',
+              light: '#2d8cf0'
+            }
+          }"
+        />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">点击</div>
+        <qrcode
+          text="vue-element-admin2.0"
+          @click="codeClick"
+        />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">异步内容</div>
+        <qrcode :text="text" />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">二维码失效</div>
+        <qrcode text="vue-element-admin2.0" :disabled="true" @disabled-click="disabledClick" />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">logo配置</div>
+        <qrcode
+          text="vue-element-admin2.0"
+          :logo="require('@/assets/img/logo.png')"
+        />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">logo样式配置</div>
+        <qrcode
+          text="vue-element-admin2.0"
+          :logo="{
+            src: require('@/assets/img/logo.png'),
+            logoSize: 0.2,
+            borderSize: 0.05,
+            borderRadius: 50,
+            bgColor: 'blue'
+          }"
+        />
+      </el-col>
+      <el-col :span="6">
+        <div class="title-item">大小配置</div>
+        <qrcode text="vue-element-admin2.0" :width="300" />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+import Qrcode from '_c/Qrcode/index.vue'
+import { Message } from '_c/Message'
+export default defineComponent({
+  // name: 'QrcodeDemo',
+  components: {
+    Qrcode
+  },
+  setup() {
+    const text = ref<string>('')
+    setTimeout(() => {
+      text.value = '我是异步生成的内容'
+    }, 3000)
+
+    function codeClick() {
+      Message.info('我被点击了。')
+    }
+    function disabledClick() {
+      Message.info('我失效被点击了。')
+    }
+    return {
+      text,
+      codeClick,
+      disabledClick
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.el-col {
+  text-align: center;
+  margin-bottom: 20px;
+  .title-item {
+    font-weight: bold;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 1 - 1
src/pages/index/views/login/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="login-wrap">
+  <div class="login-wrap" @keydown.enter="login">
     <div class="login-con">
       <el-card class="box-card">
         <template #header>

+ 65 - 4
yarn.lock

@@ -1258,6 +1258,13 @@
   resolved "https://registry.npm.taobao.org/@types/q/download/@types/q-1.5.4.tgz?cache=0&sync_timestamp=1605055096527&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fq%2Fdownload%2F%40types%2Fq-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
   integrity sha1-FZJUFOCtLNdlv+9YhC9+JqesyyQ=
 
+"@types/qrcode@^1.3.5":
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.3.5.tgz#9c97cc2875f03e2b16a0d89856fc48414e380c38"
+  integrity sha512-92QMnMb9m0ErBU20za5Eqtf4lzUcSkk5w/Cz30q5qod0lWHm2loztmFs2EnCY06yT51GY1+m/oFq2D8qVK2Bjg==
+  dependencies:
+    "@types/node" "*"
+
 "@types/qs@*":
   version "6.9.5"
   resolved "https://registry.npm.taobao.org/@types/qs/download/@types/qs-6.9.5.tgz?cache=0&sync_timestamp=1605055106687&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fqs%2Fdownload%2F%40types%2Fqs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b"
@@ -2324,7 +2331,7 @@ balanced-match@^1.0.0:
   resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
   integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
 
-base64-js@^1.0.2:
+base64-js@^1.0.2, base64-js@^1.3.1:
   version "1.5.1"
   resolved "https://registry.npm.taobao.org/base64-js/download/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   integrity sha1-GxtEAWClv3rUC2UPCVljSBkDkwo=
@@ -2547,7 +2554,25 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4
     escalade "^3.1.1"
     node-releases "^1.1.67"
 
-buffer-from@^1.0.0:
+buffer-alloc-unsafe@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
+  integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
+
+buffer-alloc@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
+  integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
+  dependencies:
+    buffer-alloc-unsafe "^1.1.0"
+    buffer-fill "^1.0.0"
+
+buffer-fill@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
+  integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
+
+buffer-from@^1.0.0, buffer-from@^1.1.1:
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
   integrity sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=
@@ -2576,6 +2601,14 @@ buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
+buffer@^5.4.3:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+  dependencies:
+    base64-js "^1.3.1"
+    ieee754 "^1.1.13"
+
 builtin-modules@^1.1.1:
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/builtin-modules/download/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@@ -3788,6 +3821,11 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
+dijkstrajs@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
+  integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs=
+
 dir-glob@^2.0.0, dir-glob@^2.2.2:
   version "2.2.2"
   resolved "https://registry.npm.taobao.org/dir-glob/download/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
@@ -5369,7 +5407,7 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
   dependencies:
     postcss "^7.0.14"
 
-ieee754@^1.1.4:
+ieee754@^1.1.13, ieee754@^1.1.4:
   version "1.2.1"
   resolved "https://registry.npm.taobao.org/ieee754/download/ieee754-1.2.1.tgz?cache=0&sync_timestamp=1603838209136&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fieee754%2Fdownload%2Fieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha1-jrehCmP/8l0VpXsAFYbRd9Gw01I=
@@ -5844,6 +5882,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
 
+isarray@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
+  integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -7404,6 +7447,11 @@ please-upgrade-node@^3.1.1:
   dependencies:
     semver-compare "^1.0.0"
 
+pngjs@^3.3.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
+  integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
+
 pnp-webpack-plugin@^1.6.4:
   version "1.6.4"
   resolved "https://registry.npm.taobao.org/pnp-webpack-plugin/download/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
@@ -7966,6 +8014,19 @@ q@^1.1.2, q@^1.5.1:
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
   integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
 
+qrcode@^1.4.4:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
+  integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
+  dependencies:
+    buffer "^5.4.3"
+    buffer-alloc "^1.2.0"
+    buffer-from "^1.1.1"
+    dijkstrajs "^1.0.1"
+    isarray "^2.0.1"
+    pngjs "^3.3.0"
+    yargs "^13.2.4"
+
 qs@6.7.0:
   version "6.7.0"
   resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@@ -10259,7 +10320,7 @@ yargs-parser@^20.2.3:
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
   integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
 
-yargs@^13.3.2:
+yargs@^13.2.4, yargs@^13.3.2:
   version "13.3.2"
   resolved "https://registry.npm.taobao.org/yargs/download/yargs-13.3.2.tgz?cache=0&sync_timestamp=1607207963779&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
   integrity sha1-rX/+/sGqWVZayRX4Lcyzipwxot0=