Merge branch 'main' into feature/scroll_to_the_error_field
commit
d4786f3f75
|
@ -22,7 +22,7 @@ outline: deep
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
使用 `useVbenDrawer` 创建最基础的模态框。
|
使用 `useVbenDrawer` 创建最基础的抽屉。
|
||||||
|
|
||||||
<DemoPreview dir="demos/vben-drawer/basic" />
|
<DemoPreview dir="demos/vben-drawer/basic" />
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
|
||||||
|
|
||||||
::: info 注意
|
::: 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` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||||
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
||||||
- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
||||||
|
@ -77,7 +77,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
| connectedComponent | 连接另一个Drawer组件 | `Component` | - |
|
||||||
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
|
@ -96,7 +96,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
|
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
|
||||||
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
|
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
|
||||||
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
||||||
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
|
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
|
||||||
| class | modal的class,宽度通过这个配置 | `string` | - |
|
| class | modal的class,宽度通过这个配置 | `string` | - |
|
||||||
| contentClass | modal内容区域的class | `string` | - |
|
| contentClass | modal内容区域的class | `string` | - |
|
||||||
| footerClass | modal底部区域的class | `string` | - |
|
| footerClass | modal底部区域的class | `string` | - |
|
||||||
|
|
|
@ -3,4 +3,5 @@ export { default as PointSelectionCaptchaCard } from './point-selection-captcha/
|
||||||
|
|
||||||
export { default as SliderCaptcha } from './slider-captcha/index.vue';
|
export { default as SliderCaptcha } from './slider-captcha/index.vue';
|
||||||
export { default as SliderRotateCaptcha } from './slider-rotate-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';
|
export type * from './types';
|
||||||
|
|
|
@ -0,0 +1,311 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type {
|
||||||
|
CaptchaVerifyPassingData,
|
||||||
|
SliderCaptchaActionType,
|
||||||
|
SliderRotateVerifyPassingData,
|
||||||
|
SliderTranslateCaptchaProps,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
unref,
|
||||||
|
useTemplateRef,
|
||||||
|
watch,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import SliderCaptcha from '../slider-captcha/index.vue';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<SliderTranslateCaptchaProps>(), {
|
||||||
|
defaultTip: '',
|
||||||
|
canvasWidth: 420,
|
||||||
|
canvasHeight: 280,
|
||||||
|
squareLength: 42,
|
||||||
|
circleRadius: 10,
|
||||||
|
src: '',
|
||||||
|
diffDistance: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: [CaptchaVerifyPassingData];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const PI: number = Math.PI;
|
||||||
|
enum CanvasOpr {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
Clip = 'clip',
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
Fill = 'fill',
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalValue = defineModel<boolean>({ default: false });
|
||||||
|
|
||||||
|
const slideBarRef = useTemplateRef<SliderCaptchaActionType>('slideBarRef');
|
||||||
|
const puzzleCanvasRef = useTemplateRef<HTMLCanvasElement>('puzzleCanvasRef');
|
||||||
|
const pieceCanvasRef = useTemplateRef<HTMLCanvasElement>('pieceCanvasRef');
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
dragging: false,
|
||||||
|
startTime: 0,
|
||||||
|
endTime: 0,
|
||||||
|
pieceX: 0,
|
||||||
|
pieceY: 0,
|
||||||
|
moveDistance: 0,
|
||||||
|
isPassing: false,
|
||||||
|
showTip: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const left = ref('0');
|
||||||
|
|
||||||
|
const pieceStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
left: left.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function setLeft(val: string) {
|
||||||
|
left.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verifyTip = computed(() => {
|
||||||
|
return state.isPassing
|
||||||
|
? $t('ui.captcha.sliderTranslateSuccessTip', [
|
||||||
|
((state.endTime - state.startTime) / 1000).toFixed(1),
|
||||||
|
])
|
||||||
|
: $t('ui.captcha.sliderTranslateFailTip');
|
||||||
|
});
|
||||||
|
function handleStart() {
|
||||||
|
state.startTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragBarMove(data: SliderRotateVerifyPassingData) {
|
||||||
|
state.dragging = true;
|
||||||
|
const { moveX } = data;
|
||||||
|
state.moveDistance = moveX;
|
||||||
|
setLeft(`${moveX}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd() {
|
||||||
|
const { pieceX } = state;
|
||||||
|
const { diffDistance } = props;
|
||||||
|
|
||||||
|
if (Math.abs(pieceX - state.moveDistance) >= (diffDistance || 3)) {
|
||||||
|
setLeft('0');
|
||||||
|
state.moveDistance = 0;
|
||||||
|
} else {
|
||||||
|
checkPass();
|
||||||
|
}
|
||||||
|
state.showTip = true;
|
||||||
|
state.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPass() {
|
||||||
|
state.isPassing = true;
|
||||||
|
state.endTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.isPassing,
|
||||||
|
(isPassing) => {
|
||||||
|
if (isPassing) {
|
||||||
|
const { endTime, startTime } = state;
|
||||||
|
const time = (endTime - startTime) / 1000;
|
||||||
|
emit('success', { isPassing, time: time.toFixed(1) });
|
||||||
|
}
|
||||||
|
modalValue.value = isPassing;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function resetCanvas() {
|
||||||
|
const { canvasWidth, canvasHeight } = props;
|
||||||
|
const puzzleCanvas = unref(puzzleCanvasRef);
|
||||||
|
const pieceCanvas = unref(pieceCanvasRef);
|
||||||
|
if (!puzzleCanvas || !pieceCanvas) return;
|
||||||
|
pieceCanvas.width = canvasWidth;
|
||||||
|
const puzzleCanvasCtx = puzzleCanvas.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCanvas() {
|
||||||
|
const { canvasWidth, canvasHeight, squareLength, circleRadius, src } = props;
|
||||||
|
const puzzleCanvas = unref(puzzleCanvasRef);
|
||||||
|
const pieceCanvas = unref(pieceCanvasRef);
|
||||||
|
if (!puzzleCanvas || !pieceCanvas) return;
|
||||||
|
const puzzleCanvasCtx = puzzleCanvas.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);
|
||||||
|
puzzleCanvasCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
|
||||||
|
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 imageData = pieceCanvasCtx.getImageData(
|
||||||
|
sx,
|
||||||
|
sy,
|
||||||
|
pieceLength,
|
||||||
|
pieceLength,
|
||||||
|
);
|
||||||
|
pieceCanvas.width = pieceLength;
|
||||||
|
pieceCanvasCtx.putImageData(imageData, 0, sy);
|
||||||
|
setLeft('0');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomNumberByRange(start: number, end: number) {
|
||||||
|
return Math.round(Math.random() * (end - start) + start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制拼图
|
||||||
|
function draw(ctx1: CanvasRenderingContext2D, ctx2: CanvasRenderingContext2D) {
|
||||||
|
const { canvasWidth, canvasHeight, squareLength, circleRadius } = props;
|
||||||
|
state.pieceX = getRandomNumberByRange(
|
||||||
|
squareLength + 2 * circleRadius,
|
||||||
|
canvasWidth - (squareLength + 2 * circleRadius),
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制拼图切块
|
||||||
|
function drawPiece(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
opr: CanvasOpr,
|
||||||
|
) {
|
||||||
|
const { squareLength, circleRadius } = props;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
ctx.arc(
|
||||||
|
x + squareLength / 2,
|
||||||
|
y - circleRadius + 2,
|
||||||
|
circleRadius,
|
||||||
|
0.72 * PI,
|
||||||
|
2.26 * PI,
|
||||||
|
);
|
||||||
|
ctx.lineTo(x + squareLength, y);
|
||||||
|
ctx.arc(
|
||||||
|
x + squareLength + circleRadius - 2,
|
||||||
|
y + squareLength / 2,
|
||||||
|
circleRadius,
|
||||||
|
1.21 * PI,
|
||||||
|
2.78 * PI,
|
||||||
|
);
|
||||||
|
ctx.lineTo(x + squareLength, y + squareLength);
|
||||||
|
ctx.lineTo(x, y + squareLength);
|
||||||
|
ctx.arc(
|
||||||
|
x + circleRadius - 2,
|
||||||
|
y + squareLength / 2,
|
||||||
|
circleRadius + 0.4,
|
||||||
|
2.76 * PI,
|
||||||
|
1.24 * PI,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
|
||||||
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
|
||||||
|
ctx.stroke();
|
||||||
|
opr === CanvasOpr.Clip ? ctx.clip() : ctx.fill();
|
||||||
|
ctx.globalCompositeOperation = 'destination-over';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resume() {
|
||||||
|
state.showTip = false;
|
||||||
|
const basicEl = unref(slideBarRef);
|
||||||
|
if (!basicEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.dragging = false;
|
||||||
|
state.isPassing = false;
|
||||||
|
state.pieceX = 0;
|
||||||
|
state.pieceY = 0;
|
||||||
|
|
||||||
|
basicEl.resume();
|
||||||
|
resetCanvas();
|
||||||
|
initCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initCanvas();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative flex flex-col items-center">
|
||||||
|
<div
|
||||||
|
class="border-border relative flex cursor-pointer overflow-hidden border shadow-md"
|
||||||
|
>
|
||||||
|
<canvas
|
||||||
|
ref="puzzleCanvasRef"
|
||||||
|
:width="canvasWidth"
|
||||||
|
:height="canvasHeight"
|
||||||
|
@click="resume"
|
||||||
|
></canvas>
|
||||||
|
<canvas
|
||||||
|
ref="pieceCanvasRef"
|
||||||
|
:width="canvasWidth"
|
||||||
|
:height="canvasHeight"
|
||||||
|
:style="pieceStyle"
|
||||||
|
class="absolute"
|
||||||
|
@click="resume"
|
||||||
|
></canvas>
|
||||||
|
<div
|
||||||
|
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"
|
||||||
|
:class="{
|
||||||
|
'bg-success/80': state.isPassing,
|
||||||
|
'bg-destructive/80': !state.isPassing,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ verifyTip }}
|
||||||
|
</div>
|
||||||
|
<div v-if="!state.dragging" class="bg-black/30">
|
||||||
|
{{ defaultTip || $t('ui.captcha.sliderTranslateDefaultTip') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SliderCaptcha
|
||||||
|
ref="slideBarRef"
|
||||||
|
v-model="modalValue"
|
||||||
|
class="mt-5"
|
||||||
|
is-slot
|
||||||
|
@end="handleDragEnd"
|
||||||
|
@move="handleDragBarMove"
|
||||||
|
@start="handleStart"
|
||||||
|
>
|
||||||
|
<template v-for="(_, key) in $slots" :key="key" #[key]="slotProps">
|
||||||
|
<slot :name="key" v-bind="slotProps"></slot>
|
||||||
|
</template>
|
||||||
|
</SliderCaptcha>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -159,6 +159,42 @@ export interface SliderRotateCaptchaProps {
|
||||||
defaultTip?: string;
|
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 {
|
export interface CaptchaVerifyPassingData {
|
||||||
isPassing: boolean;
|
isPassing: boolean;
|
||||||
time: number | string;
|
time: number | string;
|
||||||
|
|
|
@ -32,8 +32,11 @@
|
||||||
"sliderDefaultText": "Slider and drag",
|
"sliderDefaultText": "Slider and drag",
|
||||||
"alt": "Supports img tag src attribute value",
|
"alt": "Supports img tag src attribute value",
|
||||||
"sliderRotateDefaultTip": "Click picture to refresh",
|
"sliderRotateDefaultTip": "Click picture to refresh",
|
||||||
|
"sliderTranslateDefaultTip": "Click picture to refresh",
|
||||||
"sliderRotateFailTip": "Validation failed",
|
"sliderRotateFailTip": "Validation failed",
|
||||||
"sliderRotateSuccessTip": "Validation successful, time {0} seconds",
|
"sliderRotateSuccessTip": "Validation successful, time {0} seconds",
|
||||||
|
"sliderTranslateFailTip": "Validation failed",
|
||||||
|
"sliderTranslateSuccessTip": "Validation successful, time {0} seconds",
|
||||||
"refreshAriaLabel": "Refresh captcha",
|
"refreshAriaLabel": "Refresh captcha",
|
||||||
"confirmAriaLabel": "Confirm selection",
|
"confirmAriaLabel": "Confirm selection",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
|
|
@ -31,8 +31,11 @@
|
||||||
"sliderSuccessText": "验证通过",
|
"sliderSuccessText": "验证通过",
|
||||||
"sliderDefaultText": "请按住滑块拖动",
|
"sliderDefaultText": "请按住滑块拖动",
|
||||||
"sliderRotateDefaultTip": "点击图片可刷新",
|
"sliderRotateDefaultTip": "点击图片可刷新",
|
||||||
|
"sliderTranslateDefaultTip": "点击图片可刷新",
|
||||||
"sliderRotateFailTip": "验证失败",
|
"sliderRotateFailTip": "验证失败",
|
||||||
"sliderRotateSuccessTip": "验证成功,耗时{0}秒",
|
"sliderRotateSuccessTip": "验证成功,耗时{0}秒",
|
||||||
|
"sliderTranslateFailTip": "验证失败",
|
||||||
|
"sliderTranslateSuccessTip": "验证成功,耗时{0}秒",
|
||||||
"alt": "支持img标签src属性值",
|
"alt": "支持img标签src属性值",
|
||||||
"refreshAriaLabel": "刷新验证码",
|
"refreshAriaLabel": "刷新验证码",
|
||||||
"confirmAriaLabel": "确认选择",
|
"confirmAriaLabel": "确认选择",
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"pointSelection": "Point Selection Captcha",
|
"pointSelection": "Point Selection Captcha",
|
||||||
"sliderCaptcha": "Slider Captcha",
|
"sliderCaptcha": "Slider Captcha",
|
||||||
"sliderRotateCaptcha": "Rotate Captcha",
|
"sliderRotateCaptcha": "Rotate Captcha",
|
||||||
|
"sliderTranslateCaptcha": "Translate Captcha",
|
||||||
"captchaCardTitle": "Please complete the security verification",
|
"captchaCardTitle": "Please complete the security verification",
|
||||||
"pageDescription": "Verify user identity by clicking on specific locations in the image.",
|
"pageDescription": "Verify user identity by clicking on specific locations in the image.",
|
||||||
"pageTitle": "Captcha Component Example",
|
"pageTitle": "Captcha Component Example",
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
"pointSelection": "点选验证",
|
"pointSelection": "点选验证",
|
||||||
"sliderCaptcha": "滑块验证",
|
"sliderCaptcha": "滑块验证",
|
||||||
"sliderRotateCaptcha": "旋转验证",
|
"sliderRotateCaptcha": "旋转验证",
|
||||||
|
"sliderTranslateCaptcha": "拼图滑块验证",
|
||||||
"captchaCardTitle": "请完成安全验证",
|
"captchaCardTitle": "请完成安全验证",
|
||||||
"pageDescription": "通过点击图片中的特定位置来验证用户身份。",
|
"pageDescription": "通过点击图片中的特定位置来验证用户身份。",
|
||||||
"pageTitle": "验证码组件示例",
|
"pageTitle": "验证码组件示例",
|
||||||
|
|
|
@ -205,6 +205,15 @@ const routes: RouteRecordRaw[] = [
|
||||||
title: $t('examples.captcha.sliderRotateCaptcha'),
|
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',
|
name: 'CaptchaPointSelectionExample',
|
||||||
path: '/examples/captcha/point-selection',
|
path: '/examples/captcha/point-selection',
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Page, SliderTranslateCaptcha } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
function handleSuccess() {
|
||||||
|
message.success('success!');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
description="用于前端简单的拼图滑块水平拖动校验场景"
|
||||||
|
title="拼图滑块校验"
|
||||||
|
>
|
||||||
|
<Card class="mb-5" title="基本示例">
|
||||||
|
<div class="flex items-center justify-center p-4">
|
||||||
|
<SliderTranslateCaptcha
|
||||||
|
src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/pro-avatar.webp"
|
||||||
|
:canvas-width="420"
|
||||||
|
:canvas-height="420"
|
||||||
|
@success="handleSuccess"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
Loading…
Reference in New Issue