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` | - | diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index 7abb3051..a6d45525 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -324,6 +324,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | submitOnEnter | 按下回车健时提交表单 | `boolean` | false | | submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false | | compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false | +| scrollToFirstError | 表单验证失败时是否自动滚动到第一个错误字段 | `boolean` | false | ::: tip handleValuesChange diff --git a/docs/src/demos/vben-form/rules/index.vue b/docs/src/demos/vben-form/rules/index.vue index 7abcc6f6..78e59813 100644 --- a/docs/src/demos/vben-form/rules/index.vue +++ b/docs/src/demos/vben-form/rules/index.vue @@ -15,6 +15,7 @@ const [Form] = useVbenForm({ handleSubmit: onSubmit, // 垂直布局,label和input在不同行,值为vertical // 水平布局,label和input在同一行 + scrollToFirstError: true, layout: 'horizontal', schema: [ { diff --git a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue index c5a3f9c2..18975596 100644 --- a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue +++ b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue @@ -48,13 +48,18 @@ const queryFormStyle = computed(() => { async function handleSubmit(e: Event) { e?.preventDefault(); e?.stopPropagation(); - const { valid } = await form.validate(); + const props = unref(rootProps); + if (!props.formApi) { + return; + } + + const { valid } = await props.formApi.validate(); if (!valid) { return; } - const values = toRaw(await unref(rootProps).formApi?.getValues()); - await unref(rootProps).handleSubmit?.(values); + const values = toRaw(await props.formApi.getValues()); + await props.handleSubmit?.(values); } async function handleReset(e: Event) { diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index e097a465..bdc44de7 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -39,6 +39,7 @@ function getDefaultState(): VbenFormProps { layout: 'horizontal', resetButtonOptions: {}, schema: [], + scrollToFirstError: false, showCollapseButton: false, showDefaultActions: true, submitButtonOptions: {}, @@ -253,6 +254,41 @@ export class FormApi { }); } + /** + * 滚动到第一个错误字段 + * @param errors 验证错误对象 + */ + scrollToFirstError(errors: Record | string) { + // https://github.com/logaretm/vee-validate/discussions/3835 + const firstErrorFieldName = + typeof errors === 'string' ? errors : Object.keys(errors)[0]; + + if (!firstErrorFieldName) { + return; + } + + let el = document.querySelector( + `[name="${firstErrorFieldName}"]`, + ) as HTMLElement; + + // 如果通过 name 属性找不到,尝试通过组件引用查找, 正常情况下不会走到这,怕哪天 vee-validate 改了 name 属性有个兜底的 + if (!el) { + const componentRef = this.getFieldComponentRef(firstErrorFieldName); + if (componentRef && componentRef.$el instanceof HTMLElement) { + el = componentRef.$el; + } + } + + if (el) { + // 滚动到错误字段,添加一些偏移量以确保字段完全可见 + el.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'nearest', + }); + } + } + async setFieldValue(field: string, value: any, shouldValidate?: boolean) { const form = await this.getForm(); form.setFieldValue(field, value, shouldValidate); @@ -377,14 +413,21 @@ export class FormApi { if (Object.keys(validateResult?.errors ?? {}).length > 0) { console.error('validate error', validateResult?.errors); + + if (this.state?.scrollToFirstError) { + this.scrollToFirstError(validateResult.errors); + } } return validateResult; } async validateAndSubmitForm() { const form = await this.getForm(); - const { valid } = await form.validate(); + const { valid, errors } = await form.validate(); if (!valid) { + if (this.state?.scrollToFirstError) { + this.scrollToFirstError(errors); + } return; } return await this.submitForm(); @@ -396,6 +439,10 @@ export class FormApi { if (Object.keys(validateResult?.errors ?? {}).length > 0) { console.error('validate error', validateResult?.errors); + + if (this.state?.scrollToFirstError) { + this.scrollToFirstError(fieldName); + } } return validateResult; } diff --git a/packages/@core/ui-kit/form-ui/src/types.ts b/packages/@core/ui-kit/form-ui/src/types.ts index 34312ae7..ccfe8dd8 100644 --- a/packages/@core/ui-kit/form-ui/src/types.ts +++ b/packages/@core/ui-kit/form-ui/src/types.ts @@ -387,6 +387,12 @@ export interface VbenFormProps< */ resetButtonOptions?: ActionButtonOptions; + /** + * 验证失败时是否自动滚动到第一个错误字段 + * @default false + */ + scrollToFirstError?: boolean; + /** * 是否显示默认操作按钮 * @default true diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue index 955ae67e..c6c4df7d 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue @@ -287,7 +287,11 @@ defineExpose({ class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded px-2 py-1 outline-none focus:ring-2" > +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(), { + 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({ default: false }); + +const slideBarRef = useTemplateRef('slideBarRef'); +const puzzleCanvasRef = useTemplateRef('puzzleCanvasRef'); +const pieceCanvasRef = useTemplateRef('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(); +}); + + + diff --git a/packages/effects/common-ui/src/components/captcha/types.ts b/packages/effects/common-ui/src/components/captcha/types.ts index ddc190d6..8baa2539 100644 --- a/packages/effects/common-ui/src/components/captcha/types.ts +++ b/packages/effects/common-ui/src/components/captcha/types.ts @@ -158,6 +158,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; diff --git a/packages/effects/common-ui/src/components/index.ts b/packages/effects/common-ui/src/components/index.ts index 0e422bf8..6b0df201 100644 --- a/packages/effects/common-ui/src/components/index.ts +++ b/packages/effects/common-ui/src/components/index.ts @@ -20,6 +20,7 @@ export { VbenAvatar, VbenButton, VbenButtonGroup, + VbenCheckbox, VbenCheckButtonGroup, VbenCountToAnimator, VbenFullScreen, @@ -27,6 +28,7 @@ export { VbenLoading, VbenLogo, VbenPinInput, + VbenSelect, VbenSpinner, VbenTree, } from '@vben-core/shadcn-ui'; diff --git a/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue b/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue index 736d09d5..6fb7a54a 100644 --- a/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue +++ b/packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue @@ -85,7 +85,7 @@ useScrollLock();
{{ $t('ui.widgets.lockScreen.unlock') }}
-
-
- - {{ meridiem }} - - {{ hour }} -
-
- {{ minute }} +
+
+
+ + {{ meridiem }} + + {{ hour }} +
+
+ {{ minute }} +
@@ -117,9 +121,8 @@ useScrollLock(); class="flex-center size-full" @keydown.enter.prevent="handleSubmit" > -
+
-
@@ -145,12 +148,13 @@ useScrollLock();
-
- {{ hour }}:{{ minute }} {{ meridiem }} +
+ {{ hour }}:{{ minute }} + {{ meridiem }}
-
{{ date }}
+
{{ date }}
diff --git a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue index d3043b1e..3867b065 100644 --- a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue +++ b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue @@ -303,7 +303,7 @@ async function init() { if (enableProxyConfig && autoLoad) { // 第一次拿到的是readonly的数据 如果需要修改 需要cloneDeep props.api.grid.commitProxy?.( - '_init', + 'initial', cloneDeep(formOptions.value) ? (cloneDeep(await formApi.getValues()) ?? {}) : {}, diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json index 4d2f08ce..645563d2 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 ef2e507c..e4f855ca 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": "确认选择", diff --git a/playground/src/locales/langs/en-US/examples.json b/playground/src/locales/langs/en-US/examples.json index 9335b28b..2b9c23db 100644 --- a/playground/src/locales/langs/en-US/examples.json +++ b/playground/src/locales/langs/en-US/examples.json @@ -19,6 +19,7 @@ "custom": "Custom Component", "api": "Api", "merge": "Merge Form", + "scrollToError": "Scroll to Error Field", "upload-error": "Partial file upload failed", "upload-urls": "Urls after file upload", "file": "file", @@ -41,6 +42,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", diff --git a/playground/src/locales/langs/zh-CN/examples.json b/playground/src/locales/langs/zh-CN/examples.json index ff11d7fd..aa0b00f9 100644 --- a/playground/src/locales/langs/zh-CN/examples.json +++ b/playground/src/locales/langs/zh-CN/examples.json @@ -22,6 +22,7 @@ "custom": "自定义组件", "api": "Api", "merge": "合并表单", + "scrollToError": "滚动到错误字段", "upload-error": "部分文件上传失败", "upload-urls": "文件上传后的网址", "file": "文件", @@ -44,6 +45,7 @@ "pointSelection": "点选验证", "sliderCaptcha": "滑块验证", "sliderRotateCaptcha": "旋转验证", + "sliderTranslateCaptcha": "拼图滑块验证", "captchaCardTitle": "请完成安全验证", "pageDescription": "通过点击图片中的特定位置来验证用户身份。", "pageTitle": "验证码组件示例", diff --git a/playground/src/router/routes/modules/examples.ts b/playground/src/router/routes/modules/examples.ts index c91303c7..2ca6b80d 100644 --- a/playground/src/router/routes/modules/examples.ts +++ b/playground/src/router/routes/modules/examples.ts @@ -85,6 +85,15 @@ const routes: RouteRecordRaw[] = [ title: $t('examples.form.merge'), }, }, + { + name: 'FormScrollToErrorExample', + path: '/examples/form/scroll-to-error-test', + component: () => + import('#/views/examples/form/scroll-to-error-test.vue'), + meta: { + title: $t('examples.form.scrollToError'), + }, + }, ], }, { @@ -196,6 +205,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', 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 @@ + + + diff --git a/playground/src/views/examples/form/scroll-to-error-test.vue b/playground/src/views/examples/form/scroll-to-error-test.vue new file mode 100644 index 00000000..61e8815c --- /dev/null +++ b/playground/src/views/examples/form/scroll-to-error-test.vue @@ -0,0 +1,183 @@ + + + diff --git a/playground/src/views/examples/vxe-table/remote.vue b/playground/src/views/examples/vxe-table/remote.vue index 907c1e2c..7042019f 100644 --- a/playground/src/views/examples/vxe-table/remote.vue +++ b/playground/src/views/examples/vxe-table/remote.vue @@ -55,7 +55,7 @@ const gridOptions: VxeGridProps = { custom: true, export: true, // import: true, - refresh: { code: 'query' }, + refresh: true, zoom: true, }, }; diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 73c34012..fb0ef90e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -187,8 +187,8 @@ catalog: vue-router: ^4.5.1 vue-tippy: ^6.7.1 vue-tsc: 2.2.10 - vxe-pc-ui: ^4.6.42 - vxe-table: 4.13.53 + vxe-pc-ui: ^4.7.12 + vxe-table: ^4.14.4 watermark-js-plus: ^1.6.2 zod: ^3.25.67 zod-defaults: ^0.1.3