diff --git a/packages/devui-vue/devui/form/src/components/form-control/form-control-types.ts b/packages/devui-vue/devui/form/src/components/form-control/form-control-types.ts index a604e5487a..920b5b5c07 100644 --- a/packages/devui-vue/devui/form/src/components/form-control/form-control-types.ts +++ b/packages/devui-vue/devui/form/src/components/form-control/form-control-types.ts @@ -1,9 +1,10 @@ import type { PropType, ExtractPropTypes, ComputedRef } from 'vue'; +export type FeedbackStatus = 'success' | 'error' | 'pending'; + export const formControlProps = { feedbackStatus: { - type: String as PropType<'success' | 'error' | 'pending' | ''>, - default: '', + type: String as PropType, }, extraInfo: { type: String, diff --git a/packages/devui-vue/devui/form/src/components/form-icons.tsx b/packages/devui-vue/devui/form/src/components/form-icons.tsx new file mode 100644 index 0000000000..a3210c57e8 --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-icons.tsx @@ -0,0 +1,21 @@ +export function HelpTipsIcon(): JSX.Element { + return ( + + + + + + + + ); +} diff --git a/packages/devui-vue/devui/form/src/components/form-label/form-label-types.ts b/packages/devui-vue/devui/form/src/components/form-label/form-label-types.ts index 23a6e51184..a33688bfd6 100644 --- a/packages/devui-vue/devui/form/src/components/form-label/form-label-types.ts +++ b/packages/devui-vue/devui/form/src/components/form-label/form-label-types.ts @@ -5,10 +5,6 @@ export const formLabelProps = { type: Boolean, default: false, }, - hasHelp: { - type: Boolean, - default: false, - }, helpTips: { type: String, default: '', diff --git a/packages/devui-vue/devui/form/src/components/form-label/form-label.scss b/packages/devui-vue/devui/form/src/components/form-label/form-label.scss index dc6ea283b2..b7f55126a7 100644 --- a/packages/devui-vue/devui/form/src/components/form-label/form-label.scss +++ b/packages/devui-vue/devui/form/src/components/form-label/form-label.scss @@ -1,53 +1,54 @@ .devui-form__label { align-self: flex-start; - & > span { - line-height: 28px; + &--vertical { + padding-bottom: 8px; } - .devui-form__label--required { - display: inline-flex; - align-items: center; - - &::before { - content: '*'; - color: red; - display: inline-block; - margin-right: 8px; - margin-left: -12px; - } + &--sm { + flex: 0 0 80px; } -} -.devui-form__label--sm { - flex: 0 0 80px; -} + &--md { + flex: 0 0 100px; + } -.devui-form__label--md { - flex: 0 0 100px; -} + &--lg { + flex: 0 0 150px; + } -.devui-form__label--lg { - flex: 0 0 150px; -} + &--start { + text-align: left; + } -.devui-form__label--start { - text-align: left; -} + &--center { + text-align: center; + } -.devui-form__label--center { - text-align: center; + &--end { + text-align: end; + } } -.devui-form__label--end { - text-align: end; +.devui-form__label--required { + display: inline-flex; + align-items: center; + line-height: 28px; + + &::before { + content: '*'; + color: red; + display: inline-block; + margin-right: 8px; + margin-left: -12px; + } } .devui-form__label-help { - border-radius: 50%; - display: inline-flex; - justify-content: center; - align-items: center; position: relative; - margin-left: 10px; + top: -0.1em; + display: inline-block; + vertical-align: middle; + margin-left: 4px; + cursor: pointer; } diff --git a/packages/devui-vue/devui/form/src/components/form-label/form-label.tsx b/packages/devui-vue/devui/form/src/components/form-label/form-label.tsx index d1a078af35..5d7c666467 100644 --- a/packages/devui-vue/devui/form/src/components/form-label/form-label.tsx +++ b/packages/devui-vue/devui/form/src/components/form-label/form-label.tsx @@ -1,8 +1,8 @@ import { defineComponent } from 'vue'; import type { SetupContext } from 'vue'; import { formLabelProps, FormLabelProps } from './form-label-types'; -import Icon from '../../../../icon/src/icon'; import Popover from '../../../../popover/src/popover'; +import { HelpTipsIcon } from '../form-icons'; import { useNamespace } from '../../../../shared/hooks/use-namespace'; import { useFormLabel } from './use-form-label'; import './form-label.scss'; @@ -16,23 +16,14 @@ export default defineComponent({ return () => ( - - {ctx.slots.default?.()} - {props.hasHelp && props.helpTips && ( - ( - - - - ), - }}> - )} - + {ctx.slots.default?.()} + {props.helpTips && ( + + {{ + reference: () => , + }} + + )} ); }, diff --git a/packages/devui-vue/devui/form/src/components/form-label/use-form-label.ts b/packages/devui-vue/devui/form/src/components/form-label/use-form-label.ts index b980929918..ec3da25fae 100644 --- a/packages/devui-vue/devui/form/src/components/form-label/use-form-label.ts +++ b/packages/devui-vue/devui/form/src/components/form-label/use-form-label.ts @@ -1,16 +1,16 @@ -import { computed, reactive, inject, toRefs } from 'vue'; +import { computed, inject, toRefs } from 'vue'; import { FORM_TOKEN, IForm } from '../../form-types'; import { FormLabelProps, UseFormLabel } from './form-label-types'; import { useNamespace } from '../../../../shared/hooks/use-namespace'; export function useFormLabel(props: FormLabelProps): UseFormLabel { - const Form = reactive(inject(FORM_TOKEN) as IForm); - const labelData = reactive(Form.labelData); + const { labelData } = inject(FORM_TOKEN) as IForm; const ns = useNamespace('form'); const { required } = toRefs(props); const labelClasses = computed(() => ({ [`${ns.e('label')}`]: true, + [`${ns.em('label', 'vertical')}`]: labelData.layout === 'vertical', [`${ns.em('label', labelData.labelSize)}`]: labelData.layout === 'horizontal', [`${ns.em('label', labelData.labelAlign)}`]: labelData.layout === 'horizontal', })); diff --git a/packages/devui-vue/devui/form/src/form.tsx b/packages/devui-vue/devui/form/src/form.tsx index 6e2b7a282c..28347bc3cf 100644 --- a/packages/devui-vue/devui/form/src/form.tsx +++ b/packages/devui-vue/devui/form/src/form.tsx @@ -1,4 +1,4 @@ -import { defineComponent, provide, SetupContext } from 'vue'; +import { defineComponent, provide, reactive, SetupContext, toRefs } from 'vue'; import mitt from 'mitt'; import { formProps, FormProps, IFormItem, dFormEvents, FORM_TOKEN } from './form-types'; import { EventBus } from './utils'; @@ -17,6 +17,7 @@ export default defineComponent({ field.resetField(); }); }; + const { data, layout, labelSize, labelAlign } = toRefs(props); formMitt.on(dFormEvents.addField, (field: any) => { if (field) { @@ -30,17 +31,20 @@ export default defineComponent({ } }); - provide(FORM_TOKEN, { - formData: props.data, - formMitt, - labelData: { - layout: props.layout, - labelSize: props.labelSize, - labelAlign: props.labelAlign, - }, - rules: props.rules, - messageShowType: 'popover', - }); + provide( + FORM_TOKEN, + reactive({ + formData: data, + formMitt, + labelData: { + layout: layout, + labelSize: labelSize, + labelAlign: labelAlign, + }, + rules: props.rules, + messageShowType: 'popover', + }) + ); const onSubmit = (e) => { e.preventDefault(); diff --git a/packages/devui-vue/docs/components/form/index.md b/packages/devui-vue/docs/components/form/index.md index 2fd32c7af6..0c335f18d9 100644 --- a/packages/devui-vue/docs/components/form/index.md +++ b/packages/devui-vue/docs/components/form/index.md @@ -12,10 +12,10 @@ ```vue @@ -84,8 +84,7 @@ import { defineComponent, reactive, ref, nextTick } from 'vue'; export default defineComponent({ - setup(props, ctx) { - const dFormBasic = ref(null); + setup() { let formModel = reactive({ name: '', description: '', @@ -97,21 +96,10 @@ export default defineComponent({ }); const selectOptions = reactive(['Options1', 'Options2', 'Options3']); const tagLists = [{ name: 'Options1' }, { name: 'Options2' }, { name: 'Options3' }]; - const resetForm = () => { - console.log('formData reset before', dFormBasic.value.formData); - dFormBasic.value.resetFormFields(); - console.log('formData reset after', dFormBasic.value.formData); - }; - const onSubmitForm = () => { - console.log('onSubmitForm formModel', formModel); - }; return { - dFormBasic, formModel, selectOptions, tagLists, - resetForm, - onSubmitForm, }; }, }); @@ -126,13 +114,140 @@ export default defineComponent({ ::: +### 表单样式 + +:::demo 水平排列模式下,`label-size`可以设置`label`的宽度,提供`sm`、`md`、`lg`三种大小,分别对应`80px`、`100px`、`150px`,默认为`md`;`label-align`可以设置`label`的对齐方式,可选值为`start`、`center`、`end`,默认为`start`。 + +```vue + + + + + +``` + +::: + ### 垂直排列 :::demo 设置`layout`参数为`vertical`可启用垂直布局,即`label`在输入控件的上方。 ```vue @@ -204,8 +319,7 @@ export default defineComponent({ import { defineComponent, reactive, ref, nextTick } from 'vue'; export default defineComponent({ - setup(props, ctx) { - const dFormVertical = ref(null); + setup() { let formModel = reactive({ name: '', description: '', @@ -217,21 +331,11 @@ export default defineComponent({ }); const selectOptions = reactive(['Options1', 'Options2', 'Options3']); const tagLists = [{ name: 'Options1' }, { name: 'Options2' }, { name: 'Options3' }]; - const resetForm = () => { - console.log('formData reset before', dFormVertical.value.formData); - dFormVertical.value.resetFormFields(); - console.log('formData reset after', dFormVertical.value.formData); - }; - const onSubmitForm = () => { - console.log('onSubmitForm formModel', formModel); - }; + return { - dFormVertical, formModel, selectOptions, tagLists, - resetForm, - onSubmitForm, }; }, }); @@ -240,54 +344,81 @@ export default defineComponent({ ::: -### 模板驱动表单验证 - -在`d-form`、`d-input`等表单类组件上使用`v-d-validate-rules`指令,配置校验规则。 - -#### 验证单个元素,使用内置校验器,配置 error message - -当前 DevUI 支持的内置校验器有:`required`、`minlength`、`maxlength`、`min`、`max`、`requiredTrue`、`email`、`pattern`、`whitespace`。 - -- 若需限制用户输入不能全为空格,可使用`whitespace`内置校验器 +### 多列表单 -- 若需限制用户输入长度,将最大限制设置为实际校验值`+1`是一个好的办法。 - -- 除`pattern`外,其他内置校验器我们也提供了内置的错误提示信息,在你未自定义提示消息时,我们将使用默认的提示信息。 - -- message 配置支持 string 与 object 两种形式(支持国际化词条配置,如`'zh-cn'`,默认将取`'default'`)。 - -:::demo +:::demo 搭配`Grid`栅格布局方案,即可方便的实现多列表单布局效果。 ```vue @@ -295,1073 +426,109 @@ export default defineComponent({ import { defineComponent, reactive, ref } from 'vue'; export default defineComponent({ - setup(props, ctx) { - const dFormTemplateValidate1 = ref(null); - let formModel = reactive({ - username: 'AlanLee', + setup() { + const formModel = reactive({ + name: '', + select: 'Options2', + multiSelect: ref([]), + executionDay: [], + radio: '0', + switch: true, }); + const selectOptions = reactive(['Options1', 'Options2', 'Options3']); - return { - dFormTemplateValidate1, - formModel, - }; + return { formModel, selectOptions }; }, }); - - ``` ::: -#### 验证单个元素,自定义校验器 - -自定义校验器,可传入`validators`字段配置校验规则,你可以简单返回`true | false `来标识当前校验是否通过,来标识当前是否错误并返回错误消息,适用于动态错误提示。如果是异步校验器,可传入`asyncValidators`字段配置校验规则。 +### Form 参数 -:::demo +| 参数名 | 类型 | 默认值 | 说明 | 跳转 demo | +| :---------- | :------------------------ | :----------- | :----------------------------------------------------------------- | :-------------------- | +| name | `string` | '' | 可选,设置表单 name 属性,进行表单提交验证时必选 | [基础用法](#基础用法) | +| data | `object` | {} | 必选,表单数据 | [基础用法](#基础用法) | +| layout | [Layout](#layout) | 'horizontal' | 可选,设置表单的排列方式 | [垂直排列](#垂直排列) | +| label-size | [LabelSize](#labelsize) | 'md' | 可选,设置 label 的宽度,默认为 100px,sm 对应 80px,lg 对应 150px | [表单样式](#表单样式) | +| label-align | [LabelAlign](#labelalign) | 'start' | 可选,设置水平布局方式下,label 对齐方式 | [表单样式](#表单样式) | -```vue - +### Form 事件 - +### FormItem 参数 - -``` +| 参数名 | 类型 | 默认值 | 说明 | 跳转 demo | +| :----- | :------- | :----- | :--------------------------------------------------- | :-------------------- | +| field | `string` | '' | 可选,指定验证表单需验证的字段,验证表单时必选该属性 | [基础用法](#基础用法) | -::: +### FormItem 插槽 -#### 验证单个元素,配置错误更新策略 errorStrategy、校验时机 updateOn +| 插槽名 | 说明 | +| :------ | :------------- | +| default | 包裹单个表单项 | -- 设置`errorStrategy`属性初始化时是否进行校验 +### FormLabel 参数 - - 默认配置为`dirty`,校验不通过进行错误提示 - - 若需要在初始化时将错误抛出,可配置为`pristine` +| 参数名 | 类型 | 默认值 | 说明 | 跳转 demo | +| :-------- | :-------- | :----- | :--------------------------------------------------------- | :-------------------- | +| required | `boolean` | false | 可选,表单选项是否必填 | | +| help-tips | `string` | '' | 可选,表单项帮助指引提示内容,空字符串表示不设置提示内容。 | [基础用法](#基础用法) | -- 设置`updateOn`,指定校验的时机 - - 校验器`updateOn`基于你绑定的模型的`updateOn`设置, 你可以通过`options`来指定, 默认为`change` - - 可选值还有`blur` 、`input`、`submit` - - 设置为`submit`,则当元素所在表单进行提交时将触发校验 +### FormLabel 插槽 -:::demo +| 插槽名 | 说明 | +| :------ | :----------------------- | +| default | 包裹单个表单项的字段说明 | -```vue - +### FormControl 参数 - +#### Layout - +```ts +type Layout = 'horizontal' | 'vertical'; ``` -::: - -#### 验证单个元素,自定义管理消息提示 - -配置`messageShowType`可选择消息自动提示的方式,默认为`popover`。 - -- 设置为`popover`错误信息将在元素聚焦时以`popover`形式呈现。 - -- 设置为`text`错误信息将自动以文本方式显示在元素下方(需要与表单控件容器配合使用)。 - -- 设置为`none`错误信息将不会自动呈现到视图, 可在模板中获取`message`或通过监听`messageChange`事件获取错误`message`, 或在模板中直接通过引用获取。 - -- 在 `options`中配置 `popPosition`可在消息提示方式为`popover`时,自定义`popover`内容弹出方向, 默认为`['right', 'bottom']`。更多取值参考 popover 组件。 - -:::demo - -```vue - - - +#### LabelSize - +```ts +type LabelSize = 'sm' | 'md' | 'lg'; ``` -::: - -#### 验证单个元素,自定义 asyncDebounceTime - -对于异步校验器,提供默认 300ms debounce time。在 options 中设置`asyncDebounceTime`显示设置(单位 ms)。 - -:::demo - -```vue - +#### LabelAlign - - - -``` - -::: - -#### Form 验证与提交 - -点击提交按钮时进行验证,需指定 name 属性,并同时绑定 d-form 标签的 submit 事件才能生效。 - -:::demo - -```vue - - - - - -``` - -::: - -#### Form 验证与提交,用户注册场景 - -对于自动错误提示的方式,在 form 中, 建议在 dForm 层统一设置`messageShowType`,需同时设置 ref 属性才能生效。 - -:::demo - -```vue - - - - - -``` - -::: - -### 响应式表单验证 - -在`d-form`标签中指定校验规则 rules,同时在`d-form-item`中指定`field`的值为校验字段名。 - -:::demo - -```vue - - - - - -``` - -::: - -### 指定表单 Feedback 状态 - -你可通过对 d-form-control 设置 feedbackStatus 手动指定反馈状态。当前已支持状态:`success`、`error`、`pending`。 - -:::demo - -```vue - - - - - -``` - -::: - -可通过对具名插槽 suffixTemplate 在 d-form-control 中自定义反馈状态 icon。 - -:::demo - -```vue - - - -``` - -::: - -### 表单协同验证 - -在一些场景下,你的多个表单组件互相依赖,需共同校验(如注册场景中的密码输入与确认密码),通过自定义校验器实现校验规则(将密码输入与确认密码的值进行比较)。 - -:::demo - -```vue - - - - - -``` - -::: - -### 跨组件验证 - -> todo - -:::demo - -```vue - - - - - -``` - -::: - -### Form 参数 - -| 参数名 | 类型 | 默认值 | 说明 | 跳转 demo | -| :--------- | :------------------------- | :----------- | :----------------------------------------------------------------------- | :-------------------- | -| name | `string` | '' | 可选,设置表单 name 属性,进行表单提交验证时必选。 | [基础用法](#基础用法) | -| data | `object` | {} | 必选,表单数据 | [基础用法](#基础用法) | -| layout | [Layout](#layout) | 'horizontal' | 可选,设置表单的排列方式 | [基础用法](#基础用法) | -| label-size | [LabelSize](#labelsize) | 'md' | 可选,设置 label 的占宽,未设置默认为 100px,sm 对应 80px,lg 对应 150px | [基础用法](#基础用法) | -| labelAlign | 'start' \|'center' \|'end' | 'start' | 可选,设置水平布局方式下,label 对齐方式 | [基础用法](#基础用法) | - -| rules | object | | 可选,设置表单校验规则 | [响应式表单验证](#响应式表单验证) | - -### Form 事件 - -| 事件名 | 回调参数 | 说明 | 跳转 demo | -| :----- | :----------- | :----------------- | :--------------------------------- | -| submit | `() => void` | 可选,提交表单事件 | [Form 验证与提交](#Form验证与提交) | - -### FormItem 参数 - -| 参数名 | 类型 | 默认值 | 说明 | 跳转 demo | -| :----------- | :-------- | :----- | :--------------------------------------------------- | :---------------------------------------------- | -| field | `string` | '' | 可选,指定验证表单需验证的字段,验证表单时必选该属性 | [基础用法](#基础用法) | -| dHasFeedback | `boolean` | false | 可选,设置当前 formControl 是否显示反馈图标 | [指定表单 Feedback 状态](#指定表单Feedback状态) | - -d-form-label 参数 - -| 参数 | 类型 | 默认值 | 说明 | 跳转 demo | -| -------- | ------- | ------- | -------------------------------------------------------------------------------------------- | --------------------- | -| required | boolean | 'false' | 可选,表单选项是否必填 | [基础用法](#基础用法) | -| hasHelp | boolean | 'false' | 可选,表单项是否需要帮助指引 | [基础用法](#基础用法) | -| helpTips | string | | 可选,表单项帮助指引提示内容,需配合 `hasHelp`使用,且`helpTips`的值不能为空字符串才会生效。 | [基础用法](#基础用法) | - -d-form-control 参数 - -| 参数 | 类型 | 默认值 | 说明 | 跳转 demo | -| -------------- | ------- | ------- | ------------------------------------------------------------ | ----------------------------------------------- | -| extraInfo | string | | 可选,附件信息,一般用于补充表单选项的说明 | [基础用法](#基础用法) | -| feedbackStatus | boolean | 'false' | 可选,手动指定当前 control 状态反馈 | [基础用法](#基础用法) | -| suffixTemplate | string | | 可选,可传入图标模板作为输入框后缀(通过插槽传入 icon 组件) | [指定表单 Feedback 状态](#指定表单Feedback状态) | - -### Directives - -v-d-validate-rules - -| 参数 | 类型 | 默认值 | 说明 | 跳转 demo | -| ------- | ------ | ------ | ------------------------------------------------------------------------------------------------ | ------------------------------------- | -| rules | object | | 必选,表单校验规则,更多规则参考[async-validator](https://www.npmjs.com/package/async-validator) | [模板驱动表单验证](#模板驱动表单验证) | -| options | | | 可选,配置选项 | [模板驱动表单验证](#模板驱动表单验证) | - -> 该指令仅在`d-form`标签或`d-input`等表单类组件上使用有效。 - -- rules 格式如下 - -```js -{[validatorKey]: validatorValue, message: 'some tip messages.'} -``` - -当前 DevUI 支持的内置校验器 validatorKey 有:`required`、`minlength`、`maxlength`、`min`、`max`、`requiredTrue`、`email`、`pattern`、`whitespace`。更多规则参考[async-validator](https://www.npmjs.com/package/async-validator)。 - -
- -- options 支持以下字段 - - - errorStrategy,错误更新策略:`dirty`(默认)、`prestine` - - - updateOn,校验时机,可选值有:`change`(默认)、 `blur`、 `input` - - - popPosition,自定义`popover`内容弹出方向。 默认为`['right', 'bottom']`,更多取值参考 popover 组件。 - -### 类型定义 - -#### Layout +#### FeedbackStatus ```ts -type Layout = 'horizontal' | 'vertical'; -``` - -#### LabelSize - -```ts -type LabelSize = 'sm' | 'md' | 'lg'; -``` - -IForm - -```typescript -export interface IForm { - formData: any; - labelData: IFormLabel; - formMitt: Emitter; - rules: any; - messageShowType: string; -} -``` - -IFormLabel - -```typescript -export interface IFormLabel { - layout: string; - labelSize: string; - labelAlign: string; -} -``` - -IFormItem - -```typescript -export interface IFormItem { - dHasFeedback: boolean; - prop: string; - formItemMitt: Emitter; - resetField(): void; -} -``` - -IFormControl - -```typescript -export interface IFormControl { - feedbackStatus: string; - extraInfo: string; - formItemMitt: Emitter; - resetField(): void; -} +type FeedbackStatus = 'success' | 'error' | 'pending'; ```