浏览代码

feat: 增加基于图片拼图切片平移的验证码

chenweiran 2 月之前
父节点
当前提交
bbd8a53d9d

+ 1 - 0
packages/effects/common-ui/src/components/captcha/index.ts

@@ -3,4 +3,5 @@ export { default as PointSelectionCaptchaCard } from './point-selection-captcha/
 
 export { default as SliderCaptcha } from './slider-captcha/index.vue';
 export { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue';
+export { default as SliderTranslateCaptcha } from './slider-translate-captcha/index.vue';
 export type * from './types';

+ 16 - 3
packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue

@@ -127,7 +127,12 @@ function resetCanvas() {
   if (!puzzleCanvas || !pieceCanvas) return;
   pieceCanvas.width = canvasWidth;
   const puzzleCanvasCtx = puzzleCanvas.getContext('2d');
-  const pieceCanvasCtx = pieceCanvas.getContext('2d');
+  // Canvas2D: Multiple readback operations using getImageData
+  // are faster with the willReadFrequently attribute set to true.
+  // See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently (anonymous)
+  const pieceCanvasCtx = pieceCanvas.getContext('2d', {
+    willReadFrequently: true,
+  });
   if (!puzzleCanvasCtx || !pieceCanvasCtx) return;
   puzzleCanvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
   pieceCanvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
@@ -139,9 +144,16 @@ function initCanvas() {
   const pieceCanvas = unref(pieceCanvasRef);
   if (!puzzleCanvas || !pieceCanvas) return;
   const puzzleCanvasCtx = puzzleCanvas.getContext('2d');
-  const pieceCanvasCtx = pieceCanvas.getContext('2d');
+  // Canvas2D: Multiple readback operations using getImageData
+  // are faster with the willReadFrequently attribute set to true.
+  // See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently (anonymous)
+  const pieceCanvasCtx = pieceCanvas.getContext('2d', {
+    willReadFrequently: true,
+  });
   if (!puzzleCanvasCtx || !pieceCanvasCtx) return;
   const img = new Image();
+  // 解决跨域
+  img.crossOrigin = 'Anonymous';
   img.src = src;
   img.addEventListener('load', () => {
     draw(puzzleCanvasCtx, pieceCanvasCtx);
@@ -158,6 +170,7 @@ function initCanvas() {
     );
     pieceCanvas.width = pieceLength;
     pieceCanvasCtx.putImageData(imageData, 0, sy);
+    setLeft('0');
   });
 }
 
@@ -265,7 +278,7 @@ onMounted(() => {
         @click="resume"
       ></canvas>
       <div
-        class="absolute bottom-3 left-0 z-10 block h-7 w-full text-center text-xs leading-[30px] text-white"
+        class="h-15 absolute bottom-3 left-0 z-10 block w-full text-center text-xs leading-[30px] text-white"
       >
         <div
           v-if="state.showTip"

+ 36 - 0
packages/effects/common-ui/src/components/captcha/types.ts

@@ -159,6 +159,42 @@ export interface SliderRotateCaptchaProps {
   defaultTip?: string;
 }
 
+export interface SliderTranslateCaptchaProps {
+  /**
+   * @description 拼图的宽度
+   * @default 420
+   */
+  canvasWidth?: number;
+  /**
+   * @description 拼图的高度
+   * @default 280
+   */
+  canvasHeight?: number;
+  /**
+   * @description 切块上正方形的长度
+   * @default 42
+   */
+  squareLength?: number;
+  /**
+   * @description 切块上圆形的半径
+   * @default 10
+   */
+  circleRadius?: number;
+  /**
+   * @description 图片的地址
+   */
+  src?: string;
+  /**
+   * @description 允许的最大差距
+   * @default 3
+   */
+  diffDistance?: number;
+  /**
+   * @description 默认提示文本
+   */
+  defaultTip?: string;
+}
+
 export interface CaptchaVerifyPassingData {
   isPassing: boolean;
   time: number | string;

+ 1 - 0
playground/src/locales/langs/en-US/examples.json

@@ -41,6 +41,7 @@
     "pointSelection": "Point Selection Captcha",
     "sliderCaptcha": "Slider Captcha",
     "sliderRotateCaptcha": "Rotate Captcha",
+    "sliderTranslateCaptcha": "Translate Captcha",
     "captchaCardTitle": "Please complete the security verification",
     "pageDescription": "Verify user identity by clicking on specific locations in the image.",
     "pageTitle": "Captcha Component Example",

+ 1 - 0
playground/src/locales/langs/zh-CN/examples.json

@@ -44,6 +44,7 @@
     "pointSelection": "点选验证",
     "sliderCaptcha": "滑块验证",
     "sliderRotateCaptcha": "旋转验证",
+    "sliderTranslateCaptcha": "拼图滑块验证",
     "captchaCardTitle": "请完成安全验证",
     "pageDescription": "通过点击图片中的特定位置来验证用户身份。",
     "pageTitle": "验证码组件示例",

+ 9 - 0
playground/src/router/routes/modules/examples.ts

@@ -196,6 +196,15 @@ const routes: RouteRecordRaw[] = [
               title: $t('examples.captcha.sliderRotateCaptcha'),
             },
           },
+          {
+            name: 'TranslateVerifyExample',
+            path: '/examples/captcha/slider-translate',
+            component: () =>
+              import('#/views/examples/captcha/slider-translate-captcha.vue'),
+            meta: {
+              title: $t('examples.captcha.sliderTranslateCaptcha'),
+            },
+          },
           {
             name: 'CaptchaPointSelectionExample',
             path: '/examples/captcha/point-selection',