浏览代码

feat: add qrcode demo (#256)

一万 3 年之前
父节点
当前提交
6c6d520dcb

+ 1 - 0
locales/en.yaml

@@ -79,3 +79,4 @@ menus:
   hsDebounce: Debounce & Throttle
   hsFormDesign: Form Design
   hsBarcode: Barcode
+  hsQarcode: Qarcode

+ 1 - 0
locales/zh-CN.yaml

@@ -79,3 +79,4 @@ menus:
   hsDebounce: 防抖节流
   hsFormDesign: 表单设计器
   hsBarcode: 条形码
+  hsQarcode: 二维码

+ 2 - 0
package.json

@@ -56,6 +56,7 @@
     "nprogress": "^0.2.0",
     "path": "^0.12.7",
     "pinia": "^2.0.13",
+    "qrcode": "^1.5.0",
     "qs": "^6.10.1",
     "resize-observer-polyfill": "^1.5.1",
     "responsive-storage": "^1.0.11",
@@ -93,6 +94,7 @@
     "@types/mockjs": "1.0.3",
     "@types/node": "14.14.14",
     "@types/nprogress": "0.2.0",
+    "@types/qrcode": "^1.4.2",
     "@types/qs": "^6.9.7",
     "@typescript-eslint/eslint-plugin": "^5.10.2",
     "@typescript-eslint/parser": "^5.10.2",

+ 113 - 18
pnpm-lock.yaml

@@ -26,6 +26,7 @@ specifiers:
   "@types/mockjs": 1.0.3
   "@types/node": 14.14.14
   "@types/nprogress": 0.2.0
+  "@types/qrcode": ^1.4.2
   "@types/qs": ^6.9.7
   "@typescript-eslint/eslint-plugin": ^5.10.2
   "@typescript-eslint/parser": ^5.10.2
@@ -73,6 +74,7 @@ specifiers:
   postcss-scss: ^4.0.3
   prettier: ^2.5.1
   pretty-quick: 3.1.1
+  qrcode: ^1.5.0
   qs: ^6.10.1
   resize-observer-polyfill: ^1.5.1
   responsive-storage: ^1.0.11
@@ -137,6 +139,7 @@ dependencies:
   nprogress: 0.2.0
   path: 0.12.7
   pinia: 2.0.13_typescript@4.6.3+vue@3.2.33
+  qrcode: 1.5.0
   qs: 6.10.3
   resize-observer-polyfill: 1.5.1
   responsive-storage: 1.0.11_vue@3.2.33
@@ -174,6 +177,7 @@ devDependencies:
   "@types/mockjs": 1.0.3
   "@types/node": 14.14.14
   "@types/nprogress": 0.2.0
+  "@types/qrcode": 1.4.2
   "@types/qs": 6.9.7
   "@typescript-eslint/eslint-plugin": 5.16.0_bc68a9cd5bf604202498b1a9faaf9387
   "@typescript-eslint/parser": 5.16.0_eslint@8.11.0+typescript@4.6.3
@@ -1493,6 +1497,15 @@ packages:
       }
     dev: true
 
+  /@types/qrcode/1.4.2:
+    resolution:
+      {
+        integrity: sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ==
+      }
+    dependencies:
+      "@types/node": 14.14.14
+    dev: true
+
   /@types/qs/6.9.7:
     resolution:
       {
@@ -2476,7 +2489,6 @@ packages:
         integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
       }
     engines: { node: ">=8" }
-    dev: true
 
   /ansi-styles/3.2.1:
     resolution:
@@ -2495,7 +2507,6 @@ packages:
     engines: { node: ">=8" }
     dependencies:
       color-convert: 2.0.1
-    dev: true
 
   /ant-design-vue/3.2.0_vue@3.2.33:
     resolution:
@@ -2763,7 +2774,6 @@ packages:
         integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
       }
     engines: { node: ">=6" }
-    dev: true
 
   /camelcase/6.3.0:
     resolution:
@@ -2872,6 +2882,17 @@ packages:
       string-width: 4.2.3
     dev: true
 
+  /cliui/6.0.0:
+    resolution:
+      {
+        integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
+      }
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 6.2.0
+    dev: false
+
   /cliui/7.0.4:
     resolution:
       {
@@ -2914,7 +2935,6 @@ packages:
     engines: { node: ">=7.0.0" }
     dependencies:
       color-name: 1.1.4
-    dev: true
 
   /color-name/1.1.3:
     resolution: { integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= }
@@ -3394,7 +3414,6 @@ packages:
   /decamelize/1.2.0:
     resolution: { integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= }
     engines: { node: ">=0.10.0" }
-    dev: true
 
   /deep-is/0.1.4:
     resolution:
@@ -3427,6 +3446,13 @@ packages:
     engines: { node: ">=0.3.1" }
     dev: true
 
+  /dijkstrajs/1.0.2:
+    resolution:
+      {
+        integrity: sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
+      }
+    dev: false
+
   /dir-glob/3.0.1:
     resolution:
       {
@@ -3603,7 +3629,13 @@ packages:
       {
         integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
       }
-    dev: true
+
+  /encode-utf8/1.0.3:
+    resolution:
+      {
+        integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
+      }
+    dev: false
 
   /encodeurl/1.0.2:
     resolution: { integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= }
@@ -4435,7 +4467,6 @@ packages:
     dependencies:
       locate-path: 5.0.0
       path-exists: 4.0.0
-    dev: true
 
   /find-up/5.0.0:
     resolution:
@@ -4590,7 +4621,6 @@ packages:
         integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
       }
     engines: { node: 6.* || 8.* || >= 10.* }
-    dev: true
 
   /get-intrinsic/1.1.1:
     resolution:
@@ -5029,7 +5059,6 @@ packages:
         integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
       }
     engines: { node: ">=8" }
-    dev: true
 
   /is-glob/4.0.3:
     resolution:
@@ -5373,7 +5402,6 @@ packages:
     engines: { node: ">=8" }
     dependencies:
       p-locate: 4.1.0
-    dev: true
 
   /locate-path/6.0.0:
     resolution:
@@ -5937,7 +5965,6 @@ packages:
     engines: { node: ">=6" }
     dependencies:
       p-try: 2.2.0
-    dev: true
 
   /p-limit/3.1.0:
     resolution:
@@ -5957,7 +5984,6 @@ packages:
     engines: { node: ">=8" }
     dependencies:
       p-limit: 2.3.0
-    dev: true
 
   /p-locate/5.0.0:
     resolution:
@@ -5985,7 +6011,6 @@ packages:
         integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
       }
     engines: { node: ">=6" }
-    dev: true
 
   /parent-module/1.0.1:
     resolution:
@@ -6024,7 +6049,6 @@ packages:
         integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
       }
     engines: { node: ">=8" }
-    dev: true
 
   /path-is-absolute/1.0.1:
     resolution: { integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= }
@@ -6117,6 +6141,14 @@ packages:
       semver-compare: 1.0.0
     dev: true
 
+  /pngjs/5.0.0:
+    resolution:
+      {
+        integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
+      }
+    engines: { node: ">=10.13.0" }
+    dev: false
+
   /popmotion/11.0.3:
     resolution:
       {
@@ -6679,6 +6711,20 @@ packages:
     engines: { node: ">=0.6.0", teleport: ">=0.2.0" }
     dev: true
 
+  /qrcode/1.5.0:
+    resolution:
+      {
+        integrity: sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ==
+      }
+    engines: { node: ">=10.13.0" }
+    hasBin: true
+    dependencies:
+      dijkstrajs: 1.0.2
+      encode-utf8: 1.0.3
+      pngjs: 5.0.0
+      yargs: 15.4.1
+    dev: false
+
   /qs/6.10.3:
     resolution:
       {
@@ -6785,7 +6831,6 @@ packages:
   /require-directory/2.1.1:
     resolution: { integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I= }
     engines: { node: ">=0.10.0" }
-    dev: true
 
   /require-from-string/2.0.2:
     resolution:
@@ -6795,6 +6840,13 @@ packages:
     engines: { node: ">=0.10.0" }
     dev: true
 
+  /require-main-filename/2.0.0:
+    resolution:
+      {
+        integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+      }
+    dev: false
+
   /resize-observer-polyfill/1.5.1:
     resolution:
       {
@@ -7013,6 +7065,10 @@ packages:
       lru-cache: 6.0.0
     dev: true
 
+  /set-blocking/2.0.0:
+    resolution: { integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc= }
+    dev: false
+
   /shallow-equal/1.2.1:
     resolution:
       {
@@ -7274,7 +7330,6 @@ packages:
       emoji-regex: 8.0.0
       is-fullwidth-code-point: 3.0.0
       strip-ansi: 6.0.1
-    dev: true
 
   /string_decoder/1.3.0:
     resolution:
@@ -7305,7 +7360,6 @@ packages:
     engines: { node: ">=8" }
     dependencies:
       ansi-regex: 5.0.1
-    dev: true
 
   /strip-final-newline/2.0.0:
     resolution:
@@ -8119,6 +8173,10 @@ packages:
       loose-envify: 1.4.0
     dev: false
 
+  /which-module/2.0.0:
+    resolution: { integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= }
+    dev: false
+
   /which/1.3.1:
     resolution:
       {
@@ -8171,7 +8229,6 @@ packages:
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
-    dev: true
 
   /wrap-ansi/7.0.0:
     resolution:
@@ -8234,6 +8291,13 @@ packages:
       xgplayer-subtitles: 1.0.22
     dev: false
 
+  /y18n/4.0.3:
+    resolution:
+      {
+        integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
+      }
+    dev: false
+
   /y18n/5.0.8:
     resolution:
       {
@@ -8268,6 +8332,17 @@ packages:
     engines: { node: ">= 6" }
     dev: true
 
+  /yargs-parser/18.1.3:
+    resolution:
+      {
+        integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
+      }
+    engines: { node: ">=6" }
+    dependencies:
+      camelcase: 5.3.1
+      decamelize: 1.2.0
+    dev: false
+
   /yargs-parser/20.2.9:
     resolution:
       {
@@ -8284,6 +8359,26 @@ packages:
     engines: { node: ">=12" }
     dev: true
 
+  /yargs/15.4.1:
+    resolution:
+      {
+        integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
+      }
+    engines: { node: ">=8" }
+    dependencies:
+      cliui: 6.0.0
+      decamelize: 1.2.0
+      find-up: 4.1.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      require-main-filename: 2.0.0
+      set-blocking: 2.0.0
+      string-width: 4.2.3
+      which-module: 2.0.0
+      y18n: 4.0.3
+      yargs-parser: 18.1.3
+    dev: false
+
   /yargs/17.4.0:
     resolution:
       {

+ 10 - 0
src/components/ReQrcode/index.ts

@@ -0,0 +1,10 @@
+import { App } from "vue";
+import reQrcode from "./src/index";
+
+export const ReQrcode = Object.assign(reQrcode, {
+  install(app: App) {
+    app.component(reQrcode.name, reQrcode);
+  }
+});
+
+export default ReQrcode;

+ 8 - 0
src/components/ReQrcode/src/index.scss

@@ -0,0 +1,8 @@
+.qrcode {
+  &--disabled {
+    background: rgba(255, 255, 255, 0.95);
+    & > div {
+      transform: translate(-50%, -50%);
+    }
+  }
+}

+ 262 - 0
src/components/ReQrcode/src/index.tsx

@@ -0,0 +1,262 @@
+import {
+  ref,
+  unref,
+  watch,
+  nextTick,
+  computed,
+  PropType,
+  defineComponent
+} from "vue";
+import "./index.scss";
+import { isString } from "/@/utils/is";
+import { cloneDeep } from "lodash-unified";
+import { propTypes } from "/@/utils/propTypes";
+import { IconifyIconOffline } from "../../ReIcon";
+import QRCode, { QRCodeRenderersOptions } from "qrcode";
+
+interface QrcodeLogo {
+  src?: string;
+  logoSize?: number;
+  bgColor?: string;
+  borderSize?: number;
+  crossOrigin?: string;
+  borderRadius?: number;
+  logoRadius?: number;
+}
+
+const props = {
+  // 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: (): QRCodeRenderersOptions => ({})
+  },
+  // 宽度
+  width: propTypes.number.def(200),
+  // logo
+  logo: {
+    type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
+    default: (): QrcodeLogo | string => ""
+  },
+  // 是否过期
+  disabled: propTypes.bool.def(false),
+  // 过期提示内容
+  disabledText: propTypes.string.def("")
+};
+
+export default defineComponent({
+  name: "epTableProBar",
+  props,
+  emits: ["done", "click", "disabled-click"],
+  setup(props, { emit }) {
+    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 = 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";
+      }
+    };
+    // 用于绘制圆角
+    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");
+    };
+    return () => (
+      <>
+        <div
+          v-loading={unref(loading)}
+          class="qrcode relative inline-block"
+          style={unref(wrapStyle)}
+        >
+          {props.tag === "canvas" ? (
+            <canvas ref={wrapRef} onClick={clickCode}></canvas>
+          ) : (
+            <img ref={wrapRef} onClick={clickCode}></img>
+          )}
+          {props.disabled && (
+            <div
+              class="qrcode--disabled absolute top-0 left-0 flex w-full h-full items-center justify-center"
+              onClick={disabledClick}
+            >
+              <div class="absolute top-[50%] left-[50%] font-bold">
+                <IconifyIconOffline
+                  class="cursor-pointer outline-none"
+                  icon="refresh-right"
+                  width="30"
+                  color="var(--el-color-primary)"
+                />
+                <div>{props.disabledText}</div>
+              </div>
+            </div>
+          )}
+        </div>
+      </>
+    );
+  }
+});

+ 9 - 0
src/router/modules/able.ts

@@ -110,6 +110,15 @@ const ableRouter = {
         title: $t("menus.hsBarcode"),
         i18n: true
       }
+    },
+    {
+      path: "/able/qrcode",
+      name: "reQarcode",
+      component: () => import("/@/views/able/qrcode.vue"),
+      meta: {
+        title: $t("menus.hsQarcode"),
+        i18n: true
+      }
     }
   ]
 };

+ 113 - 0
src/views/able/qrcode.vue

@@ -0,0 +1,113 @@
+<script setup lang="ts">
+import { ref, unref } from "vue";
+import { ElMessage } from "element-plus";
+import avatars from "/@/assets/avatars.jpg";
+import ReQrcode from "/@/components/ReQrcode";
+
+const qrcodeText = "vue-pure-admin";
+
+const asyncTitle = ref("");
+setTimeout(() => {
+  asyncTitle.value = unref(qrcodeText);
+}, 3000);
+const codeClick = () => {
+  ElMessage.info("点击事件");
+};
+const disabledClick = () => {
+  ElMessage.info("失效");
+};
+</script>
+
+<template>
+  <div>
+    <el-card>
+      <template #header>
+        <div class="font-medium">
+          二维码(基于<el-link
+            href="https://github.com/soldair/node-qrcode"
+            target="_blank"
+            style="font-size: 16px; margin: 0 5px 4px 0"
+            >qrcode</el-link
+          >生成)
+        </div>
+      </template>
+      <el-row :gutter="20" justify="space-between">
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">基础用法</div>
+            <ReQrcode :text="qrcodeText" />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">img标签</div>
+            <ReQrcode :text="qrcodeText" tag="img" />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">样式配置</div>
+            <ReQrcode
+              :text="qrcodeText"
+              :options="{
+                color: {
+                  dark: '#55D187',
+                  light: '#2d8cf0'
+                }
+              }"
+            />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">点击事件</div>
+            <ReQrcode :text="qrcodeText" @click="codeClick" />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">异步内容</div>
+            <ReQrcode :text="asyncTitle" />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">失效</div>
+            <ReQrcode
+              :text="qrcodeText"
+              disabled
+              @disabled-click="disabledClick"
+            />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">logo配置</div>
+            <ReQrcode :text="qrcodeText" :logo="avatars" />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">logo样式</div>
+            <ReQrcode
+              :text="qrcodeText"
+              :logo="{
+                src: avatars,
+                logoSize: 0.2,
+                borderSize: 0.05,
+                borderRadius: 50,
+                bgColor: 'blue'
+              }"
+            />
+          </el-card>
+        </el-col>
+        <el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
+          <el-card shadow="hover" class="mb-10px text-center">
+            <div class="font-bold">大小配置</div>
+            <ReQrcode :text="qrcodeText" :width="100" />
+          </el-card>
+        </el-col>
+      </el-row>
+    </el-card>
+  </div>
+</template>