diff --git a/examples/sites/demos/apis/form.js b/examples/sites/demos/apis/form.js index 56d4cddbf..0bb72ce0c 100644 --- a/examples/sites/demos/apis/form.js +++ b/examples/sites/demos/apis/form.js @@ -385,6 +385,17 @@ export default { mode: ['pc'], pcDemo: '' }, + { + name: 'extra', + type: 'string', + defaultValue: '', + desc: { + 'zh-CN': '表单项额外提示', + 'en-US': 'Form item extra tip' + }, + mode: ['pc'], + pcDemo: 'extra-tip' + }, { name: 'inline-message', type: 'boolean', @@ -581,6 +592,16 @@ export default { }, mode: ['pc'], pcDemo: 'slot-label' + }, + { + name: 'error', + defaultValue: '', + desc: { + 'zh-CN': '错误提示内容', + 'en-US': 'Error content' + }, + mode: ['pc'], + pcDemo: 'error-label' } ] } diff --git a/examples/sites/demos/pc/app/form/error-slot-composition-api.vue b/examples/sites/demos/pc/app/form/error-slot-composition-api.vue new file mode 100644 index 000000000..06cf6c5a2 --- /dev/null +++ b/examples/sites/demos/pc/app/form/error-slot-composition-api.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/examples/sites/demos/pc/app/form/error-slot.spec.ts b/examples/sites/demos/pc/app/form/error-slot.spec.ts new file mode 100644 index 000000000..3853d257f --- /dev/null +++ b/examples/sites/demos/pc/app/form/error-slot.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test' + +test('错误提示插槽', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('form#error-slot') + + const demo = page.locator('#error-slot') + const getTooltipByText = (text: string) => page.locator('.tiny-tooltip').getByText(text) + const validBtn = demo.getByRole('button', { name: '校验' }).first() + + await validBtn.click() + await expect(getTooltipByText('必填')).toBeVisible() + await expect(getTooltipByText('错误提示内容插槽')).toBeVisible() + await expect(page.locator('.error-slot')).toBeVisible() +}) diff --git a/examples/sites/demos/pc/app/form/error-slot.vue b/examples/sites/demos/pc/app/form/error-slot.vue new file mode 100644 index 000000000..cfe99a48f --- /dev/null +++ b/examples/sites/demos/pc/app/form/error-slot.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/examples/sites/demos/pc/app/form/extra-tip-composition-api.vue b/examples/sites/demos/pc/app/form/extra-tip-composition-api.vue new file mode 100644 index 000000000..1548f57a3 --- /dev/null +++ b/examples/sites/demos/pc/app/form/extra-tip-composition-api.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/examples/sites/demos/pc/app/form/extra-tip.spec.ts b/examples/sites/demos/pc/app/form/extra-tip.spec.ts new file mode 100644 index 000000000..fd3a570ab --- /dev/null +++ b/examples/sites/demos/pc/app/form/extra-tip.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test' + +test('错误提示插槽', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('form#extra-tip') + + const demo = page.locator('#extra-tip') + const validBtn = demo.getByRole('button', { name: '提交' }).first() + + await expect(demo.getByText('需要填写真实姓名')).toBeVisible() + await expect(demo.getByText('需要填写真实年龄')).toBeVisible() + await validBtn.click() + await expect(demo.getByText('需要填写真实姓名')).toBeVisible() + await expect(demo.getByText('需要填写真实年龄')).toBeVisible() +}) diff --git a/examples/sites/demos/pc/app/form/extra-tip.vue b/examples/sites/demos/pc/app/form/extra-tip.vue new file mode 100644 index 000000000..9e333af4d --- /dev/null +++ b/examples/sites/demos/pc/app/form/extra-tip.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/examples/sites/demos/pc/app/form/webdoc/form.js b/examples/sites/demos/pc/app/form/webdoc/form.js index 1033a26cc..073d77f37 100644 --- a/examples/sites/demos/pc/app/form/webdoc/form.js +++ b/examples/sites/demos/pc/app/form/webdoc/form.js @@ -78,10 +78,16 @@ export default { 'en-US': 'Form Validation/Clear Validation' }, desc: { - 'zh-CN': - '

通过 rules 设置校验规则,\n 调用 clearValidate 方法移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果,\n 调用 resetFields 方法重置表单并移除校验结果。\n

', - 'en-US': - '

Includes common verification rules such as mandatory fields, date, time, URL, and email. Use trigger to configure the mode of triggering the validation rule. If change is used, the validation is triggered when the value in the text box changes. If blur is used, the validation is triggered after the focus is lost. \n
\n Use clearValidate method to clear the validation result. \n

\n ' + 'zh-CN': `

通过 rules 设置校验规则, + 调用 clearValidate 方法移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果, + 调用 resetFields 方法重置表单并移除校验结果 +

`, + 'en-US': `

Includes common verification rules such as mandatory fields, date, time, URL, and email. + Use trigger to configure the mode of triggering the validation rule. + If change is used, the validation is triggered when the value in the text box changes. + If blur is used, the validation is triggered after the focus is lost. +
+ Use clearValidate method to clear the validation result.

` }, codeFiles: ['form-validation.vue'] }, @@ -284,6 +290,30 @@ export default { }, codeFiles: ['popper-options.vue'] }, + { + demoId: 'error-slot', + name: { + 'zh-CN': '错误提示插槽', + 'en-US': 'Error content' + }, + desc: { + 'zh-CN': '

通过 error 插槽,自定义标签文本的内容。

', + 'en-US': '

Use the error slot to customize the content of the label text.

' + }, + codeFiles: ['error-slot.vue'] + }, + { + demoId: 'extra-tip', + name: { + 'zh-CN': '额外提示信息', + 'en-US': 'Extra tip' + }, + desc: { + 'zh-CN': '

通过 extra 配置额外提示信息。

', + 'en-US': '

Configure additional prompt information through extra.

' + }, + codeFiles: ['extra-tip.vue'] + }, { demoId: 'events', name: { diff --git a/examples/sites/src/tools/useTheme.js b/examples/sites/src/tools/useTheme.js index b1e098c59..442d320d4 100644 --- a/examples/sites/src/tools/useTheme.js +++ b/examples/sites/src/tools/useTheme.js @@ -64,7 +64,8 @@ const changeTheme = (themeKey) => { router.push({ params: { theme: THEME_ROUTE_MAP[themeKey] - } + }, + hash: router?.currentRoute.value.hash }) } diff --git a/packages/theme/src/form-item/index.less b/packages/theme/src/form-item/index.less index a940dd40f..710d82a35 100644 --- a/packages/theme/src/form-item/index.less +++ b/packages/theme/src/form-item/index.less @@ -66,6 +66,13 @@ width: 100%; } + .@{form-item-prefix-cls}__extra-tip { + line-height: var(--ti-form-item-extra-tip-line-height); + margin-top: var(--ti-form-item-extra-tip-margin-top); + color: var(--ti-form-item-extra-tip-font-color); + font-size: var(--ti-form-item-extra-tip-font-size); + } + & & { margin-bottom: 0; } diff --git a/packages/theme/src/form-item/vars.less b/packages/theme/src/form-item/vars.less index 39e0eb127..26bcae670 100644 --- a/packages/theme/src/form-item/vars.less +++ b/packages/theme/src/form-item/vars.less @@ -55,4 +55,12 @@ --ti-form-item-error-inline-margin-left: calc(var(--ti-common-space-base, 4px) * 2.5); // 表单校验块级错误消息上侧内边距 --ti-form-item-error-block-padding-top: var(--ti-common-space-2x, 8px); + // 表单额外提示信息上侧外边距 + --ti-form-item-extra-tip-margin-top: var(--ti-common-space-2x, 8px); + // 表单额外提示信息字体颜色 + --ti-form-item-extra-tip-font-color: var(--ti-common-color-text-weaken, #8a8e99); + // 表单额外提示信息字体大小 + --ti-form-item-extra-tip-font-size: var(--ti-common-font-size-1, 14px); + // 表单额外提示信息字体行高 + --ti-form-item-extra-tip-line-height: var(--ti-common-line-height-number, 1.5); } diff --git a/packages/vue/src/form-item/src/index.ts b/packages/vue/src/form-item/src/index.ts index 52e9a616e..18d90ddec 100644 --- a/packages/vue/src/form-item/src/index.ts +++ b/packages/vue/src/form-item/src/index.ts @@ -58,7 +58,8 @@ export const formItemProps = { vertical: { type: Boolean, default: false - } + }, + extra: String } export default defineComponent({ diff --git a/packages/vue/src/form-item/src/pc.vue b/packages/vue/src/form-item/src/pc.vue index dcf1906d5..ba842595a 100644 --- a/packages/vue/src/form-item/src/pc.vue +++ b/packages/vue/src/form-item/src/pc.vue @@ -79,7 +79,8 @@ export default defineComponent({ vertical: { type: Boolean, default: false - } + }, + extra: String }, setup(props, context) { return setup({ props, context, renderless, api }) as unknown as IFormItemApi @@ -181,8 +182,8 @@ export default defineComponent({ typeof this.appendToBody === 'boolean' ? this.appendToBody : typeof formAppendToBody === 'boolean' - ? formAppendToBody - : true + ? formAppendToBody + : true const validatePosition = this.validatePosition || (state.formInstance && state.formInstance.validatePosition) || 'top-end' @@ -221,10 +222,17 @@ export default defineComponent({ modelValue: isShowError ? state.canShowTip : false, zIndex: 'relative', renderContent() { - return [ - validateIconNode, - {state.validateMessage} - ] + let tooltipContent + if (errorSlot) { + tooltipContent = [errorSlot] + } else { + tooltipContent = [ + validateIconNode, + {state.validateMessage} + ] + } + + return tooltipContent } }, on: { @@ -298,6 +306,19 @@ export default defineComponent({ : null ] ) + + const ExtraTip = this.extra + ? h( + 'div', + { + class: { + [`${classPrefix}form-item__extra-tip`]: true + } + }, + this.extra + ) + : null + return h( 'div', { @@ -336,7 +357,8 @@ export default defineComponent({ } }, [ErrorContent] - ) + ), + isMobile ? null : ExtraTip ] ) ]