From 8554924cb9dee1d8bb1fa9f5053dc656fb05f92c Mon Sep 17 00:00:00 2001 From: chenweiran Date: Thu, 3 Jul 2025 17:43:19 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E5=9B=BE=E7=89=87=E6=8B=BC=E5=9B=BE=E5=88=87=E7=89=87?= =?UTF-8?q?=E5=B9=B3=E7=A7=BB=E7=9A=84=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slider-translate-captcha/index.vue | 298 ++++++++++++++++++ .../captcha/slider-translate-captcha.vue | 27 ++ 2 files changed, 325 insertions(+) create mode 100644 packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue create mode 100644 playground/src/views/examples/captcha/slider-translate-captcha.vue diff --git a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue new file mode 100644 index 00000000..d340c201 --- /dev/null +++ b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue @@ -0,0 +1,298 @@ + + + diff --git a/playground/src/views/examples/captcha/slider-translate-captcha.vue b/playground/src/views/examples/captcha/slider-translate-captcha.vue new file mode 100644 index 00000000..78fbd86c --- /dev/null +++ b/playground/src/views/examples/captcha/slider-translate-captcha.vue @@ -0,0 +1,27 @@ + + + From bbd8a53d9d9c2d7d845501c8b60c76e6db85b4b9 Mon Sep 17 00:00:00 2001 From: chenweiran Date: Thu, 3 Jul 2025 18:20:20 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E5=9B=BE=E7=89=87=E6=8B=BC=E5=9B=BE=E5=88=87=E7=89=87?= =?UTF-8?q?=E5=B9=B3=E7=A7=BB=E7=9A=84=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common-ui/src/components/captcha/index.ts | 1 + .../slider-translate-captcha/index.vue | 19 ++++++++-- .../common-ui/src/components/captcha/types.ts | 36 +++++++++++++++++++ .../src/locales/langs/en-US/examples.json | 1 + .../src/locales/langs/zh-CN/examples.json | 1 + .../src/router/routes/modules/examples.ts | 9 +++++ 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/effects/common-ui/src/components/captcha/index.ts b/packages/effects/common-ui/src/components/captcha/index.ts index 6ad68c49..13634cd4 100644 --- a/packages/effects/common-ui/src/components/captcha/index.ts +++ b/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'; diff --git a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue index d340c201..ece15205 100644 --- a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue +++ b/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" >
+ import('#/views/examples/captcha/slider-translate-captcha.vue'), + meta: { + title: $t('examples.captcha.sliderTranslateCaptcha'), + }, + }, { name: 'CaptchaPointSelectionExample', path: '/examples/captcha/point-selection', From 8ccd01ade5929a0c49b28b6d684f5b018f42c5d3 Mon Sep 17 00:00:00 2001 From: chenweiran Date: Fri, 4 Jul 2025 10:23:47 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E5=9B=BE=E7=89=87=E6=8B=BC=E5=9B=BE=E5=88=87=E7=89=87?= =?UTF-8?q?=E5=B9=B3=E7=A7=BB=E7=9A=84=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slider-translate-captcha/index.vue | 20 +++++++++---------- packages/locales/src/langs/en-US/ui.json | 3 +++ packages/locales/src/langs/zh-CN/ui.json | 3 +++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue index ece15205..a1b15654 100644 --- a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue +++ b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue @@ -53,7 +53,7 @@ const state = reactive({ startTime: 0, endTime: 0, pieceX: 0, - PieceY: 0, + pieceY: 0, moveDistance: 0, isPassing: false, showTip: false, @@ -73,10 +73,10 @@ function setLeft(val: string) { const verifyTip = computed(() => { return state.isPassing - ? $t('ui.captcha.sliderRotateSuccessTip', [ + ? $t('ui.captcha.sliderTranslateSuccessTip', [ ((state.endTime - state.startTime) / 1000).toFixed(1), ]) - : $t('ui.captcha.sliderRotateFailTip'); + : $t('ui.captcha.sliderTranslateFailTip'); }); function handleStart() { state.startTime = Date.now(); @@ -93,7 +93,7 @@ function handleDragEnd() { const { pieceX } = state; const { diffDistance } = props; - if (Math.abs(pieceX - state.moveDistance) >= (diffDistance || 10)) { + if (Math.abs(pieceX - state.moveDistance) >= (diffDistance || 5)) { setLeft('0'); state.moveDistance = 0; } else { @@ -161,7 +161,7 @@ function initCanvas() { pieceCanvasCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight); const pieceLength = squareLength + 2 * circleRadius + 3; const sx = state.pieceX; - const sy = state.PieceY - 2 * circleRadius - 1; + const sy = state.pieceY - 2 * circleRadius - 1; const imageData = pieceCanvasCtx.getImageData( sx, sy, @@ -185,12 +185,12 @@ function draw(ctx1: CanvasRenderingContext2D, ctx2: CanvasRenderingContext2D) { squareLength + 2 * circleRadius, canvasWidth - (squareLength + 2 * circleRadius), ); - state.PieceY = getRandomNumberByRange( + state.pieceY = getRandomNumberByRange( 3 * circleRadius, canvasHeight - (squareLength + 2 * circleRadius), ); - drawPiece(ctx1, state.pieceX, state.PieceY, CanvasOpr.Fill); - drawPiece(ctx2, state.pieceX, state.PieceY, CanvasOpr.Clip); + drawPiece(ctx1, state.pieceX, state.pieceY, CanvasOpr.Fill); + drawPiece(ctx2, state.pieceX, state.pieceY, CanvasOpr.Clip); } // 绘制拼图切块 @@ -246,7 +246,7 @@ function resume() { state.dragging = false; state.isPassing = false; state.pieceX = 0; - state.PieceY = 0; + state.pieceY = 0; basicEl.resume(); resetCanvas(); @@ -290,7 +290,7 @@ onMounted(() => { {{ verifyTip }}
- {{ defaultTip || $t('ui.captcha.sliderRotateDefaultTip') }} + {{ defaultTip || $t('ui.captcha.sliderTranslateDefaultTip') }}
diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json index 5bfd5d07..0179c225 100644 --- a/packages/locales/src/langs/en-US/ui.json +++ b/packages/locales/src/langs/en-US/ui.json @@ -32,8 +32,11 @@ "sliderDefaultText": "Slider and drag", "alt": "Supports img tag src attribute value", "sliderRotateDefaultTip": "Click picture to refresh", + "sliderTranslateDefaultTip": "Click picture to refresh", "sliderRotateFailTip": "Validation failed", "sliderRotateSuccessTip": "Validation successful, time {0} seconds", + "sliderTranslateFailTip": "Validation failed", + "sliderTranslateSuccessTip": "Validation successful, time {0} seconds", "refreshAriaLabel": "Refresh captcha", "confirmAriaLabel": "Confirm selection", "confirm": "Confirm", diff --git a/packages/locales/src/langs/zh-CN/ui.json b/packages/locales/src/langs/zh-CN/ui.json index c0679581..da2dbeb0 100644 --- a/packages/locales/src/langs/zh-CN/ui.json +++ b/packages/locales/src/langs/zh-CN/ui.json @@ -31,8 +31,11 @@ "sliderSuccessText": "验证通过", "sliderDefaultText": "请按住滑块拖动", "sliderRotateDefaultTip": "点击图片可刷新", + "sliderTranslateDefaultTip": "点击图片可刷新", "sliderRotateFailTip": "验证失败", "sliderRotateSuccessTip": "验证成功,耗时{0}秒", + "sliderTranslateFailTip": "验证失败", + "sliderTranslateSuccessTip": "验证成功,耗时{0}秒", "alt": "支持img标签src属性值", "refreshAriaLabel": "刷新验证码", "confirmAriaLabel": "确认选择", From 1aafb43103c4041e998c5ec2939bc830773661dd Mon Sep 17 00:00:00 2001 From: chenweiran Date: Fri, 4 Jul 2025 10:26:02 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E5=9B=BE=E7=89=87=E6=8B=BC=E5=9B=BE=E5=88=87=E7=89=87?= =?UTF-8?q?=E5=B9=B3=E7=A7=BB=E7=9A=84=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/captcha/slider-translate-captcha/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue index a1b15654..81b7342b 100644 --- a/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue +++ b/packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue @@ -93,7 +93,7 @@ function handleDragEnd() { const { pieceX } = state; const { diffDistance } = props; - if (Math.abs(pieceX - state.moveDistance) >= (diffDistance || 5)) { + if (Math.abs(pieceX - state.moveDistance) >= (diffDistance || 3)) { setLeft('0'); state.moveDistance = 0; } else { From b333fd676d6854364e1096b984cccb679b131e66 Mon Sep 17 00:00:00 2001 From: xue-jn <40727429+xue-jn@users.noreply.github.com> Date: Sun, 6 Jul 2025 20:26:06 +0800 Subject: [PATCH 5/5] docs: update vben-drawer.md (#6478) * docs: update vben-drawer.md * docs: update vben-drawer.md --------- Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com> --- docs/src/components/common-ui/vben-drawer.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md index b66bd3a0..3a28cce7 100644 --- a/docs/src/components/common-ui/vben-drawer.md +++ b/docs/src/components/common-ui/vben-drawer.md @@ -22,7 +22,7 @@ outline: deep ## 基础用法 -使用 `useVbenDrawer` 创建最基础的模态框。 +使用 `useVbenDrawer` 创建最基础的抽屉。 @@ -52,7 +52,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra ::: info 注意 -- `VbenDrawer` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 +- `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 - 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。 - 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。 - 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。 @@ -77,7 +77,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ | 属性名 | 描述 | 类型 | 默认值 | | --- | --- | --- | --- | | appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` | -| connectedComponent | 连接另一个Modal组件 | `Component` | - | +| connectedComponent | 连接另一个Drawer组件 | `Component` | - | | destroyOnClose | 关闭时销毁 | `boolean` | `false` | | title | 标题 | `string\|slot` | - | | titleTooltip | 标题提示信息 | `string\|slot` | - | @@ -96,7 +96,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ | cancelText | 取消按钮文本 | `string\|slot` | `取消` | | placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` | | showCancelButton | 显示取消按钮 | `boolean` | `true` | -| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` | +| showConfirmButton | 显示确认按钮 | `boolean` | `true` | | class | modal的class,宽度通过这个配置 | `string` | - | | contentClass | modal内容区域的class | `string` | - | | footerClass | modal底部区域的class | `string` | - |