From fd456c63d3210cef4f176155df39c1924294d310 Mon Sep 17 00:00:00 2001 From: wangyupei <2311595895@qq.com> Date: Sat, 16 Apr 2022 17:22:37 +0800 Subject: [PATCH 1/3] =?UTF-8?q?refactor(Form):=20=E5=8F=82=E8=80=83Rem?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form-control/form-control-types.ts | 18 + .../components/form-control/form-control.scss | 16 +- .../components/form-control/form-control.tsx | 92 +- .../form-control/use-form-control.ts | 20 + .../components/form-item/form-item-types.ts | 19 + .../src/components/form-item/form-item.scss | 19 +- .../src/components/form-item/form-item.tsx | 45 +- .../src/components/form-item/use-form-item.ts | 23 + .../components/form-label/form-label-types.ts | 23 + .../src/components/form-label/form-label.scss | 35 +- .../src/components/form-label/form-label.tsx | 67 +- .../components/form-label/use-form-label.ts | 23 + .../devui-vue/devui/form/src/form-types.ts | 103 +- packages/devui-vue/devui/form/src/form.tsx | 28 +- packages/devui-vue/devui/input/src/input.tsx | 26 +- .../devui-vue/docs/components/form/index.md | 1007 ++++++++--------- .../docs/en-US/components/form/index.md | 838 +++++++------- 17 files changed, 1176 insertions(+), 1226 deletions(-) create mode 100644 packages/devui-vue/devui/form/src/components/form-control/form-control-types.ts create mode 100644 packages/devui-vue/devui/form/src/components/form-control/use-form-control.ts create mode 100644 packages/devui-vue/devui/form/src/components/form-item/form-item-types.ts create mode 100644 packages/devui-vue/devui/form/src/components/form-item/use-form-item.ts create mode 100644 packages/devui-vue/devui/form/src/components/form-label/form-label-types.ts create mode 100644 packages/devui-vue/devui/form/src/components/form-label/use-form-label.ts 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 new file mode 100644 index 0000000000..79335480fe --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-control/form-control-types.ts @@ -0,0 +1,18 @@ +import type { PropType, ExtractPropTypes, ComputedRef } from 'vue'; + +export const formControlProps = { + feedbackStatus: { + type: String as PropType<'success' | 'error' | 'pending' | ''>, + default: '', + }, + extraInfo: { + type: String, + default: '', + }, +}; + +export type FormControlProps = ExtractPropTypes; + +export interface UseFormControl { + controlContainerClasses: ComputedRef>; +} diff --git a/packages/devui-vue/devui/form/src/components/form-control/form-control.scss b/packages/devui-vue/devui/form/src/components/form-control/form-control.scss index 7e246ad1c8..775ad5791d 100644 --- a/packages/devui-vue/devui/form/src/components/form-control/form-control.scss +++ b/packages/devui-vue/devui/form/src/components/form-control/form-control.scss @@ -1,4 +1,4 @@ -.devui-form-control { +.devui-form__control { position: relative; width: 100%; @@ -6,10 +6,10 @@ color: red; } - .devui-form-control-container { + .devui-form__control-container { position: relative; - .devui-feedback-status { + .devui-form__control-feedback { position: absolute; top: 50%; right: 0; @@ -30,7 +30,7 @@ } } - .devui-form-control-container-horizontal { + .devui-form__control-container--horizontal { display: flex; width: 100%; @@ -57,11 +57,11 @@ } } - .devui-control-content-wrapper { + .devui-form__control-content { width: 100%; } - .devui-has-feedback { + .devui-form__control-container--has-feedback { display: flex; align-items: center; @@ -70,7 +70,7 @@ } } - .devui-feedback-error { + .devui-form__control-container--feedback-error { border: 1px solid #f66f6a; border-radius: 2px; @@ -92,7 +92,7 @@ } } - .devui-form-control-extra-info { + .devui-form__control-extra { font-size: 12px; color: #8a8e99; min-height: 20px; diff --git a/packages/devui-vue/devui/form/src/components/form-control/form-control.tsx b/packages/devui-vue/devui/form/src/components/form-control/form-control.tsx index bd41466419..c1d1d6cbb7 100644 --- a/packages/devui-vue/devui/form/src/components/form-control/form-control.tsx +++ b/packages/devui-vue/devui/form/src/components/form-control/form-control.tsx @@ -1,12 +1,15 @@ -import { defineComponent, inject, ref, computed, reactive, onMounted, Teleport } from 'vue'; +import { defineComponent, ref, computed, onMounted, Teleport } from 'vue'; +import type { SetupContext } from 'vue'; import { uniqueId } from 'lodash'; -import { IForm, formControlProps, formInjectionKey } from '../../form-types'; +import { formControlProps, FormControlProps } from './form-control-types'; import { ShowPopoverErrorMessageEventData } from '../../directives/d-validate-rules'; import clickoutsideDirective from '../../../../shared/devui-directive/clickoutside'; import { EventBus, getElOffset } from '../../utils'; import Icon from '../../../../icon/src/icon'; import Popover from '../../../../popover/src/popover'; +import { useNamespace } from '../../../../shared/hooks/use-namespace'; import './form-control.scss'; +import { useFormControl } from './use-form-control'; type positionType = 'top' | 'right' | 'bottom' | 'left'; @@ -16,16 +19,15 @@ export default defineComponent({ clickoutside: clickoutsideDirective, }, props: formControlProps, - setup(props, ctx) { + setup(props: FormControlProps, ctx: SetupContext) { const formControl = ref(); - const dForm = reactive(inject(formInjectionKey, {} as IForm)); - const labelData = reactive(dForm.labelData); - const isHorizontal = labelData.layout === 'horizontal'; const uid = uniqueId('dfc-'); const showPopover = ref(false); const updateOn = ref('change'); const tipMessage = ref(''); const popPosition = ref('bottom'); + const ns = useNamespace('form'); + const { controlContainerClasses } = useFormControl(props); let rectInfo: Partial = { width: 0, height: 0, @@ -74,50 +76,44 @@ export default defineComponent({ } }; - return () => { - const { feedbackStatus, extraInfo } = props; - return ( -
- {showPopover.value && ( - -
- -
-
- )} -
-
- {ctx.slots.default?.()} + return () => ( +
+ {showPopover.value && ( + +
+
- {(feedbackStatus || ctx.slots.suffixTemplate?.()) && ( - - {ctx.slots.suffixTemplate?.() ? ( - ctx.slots.suffixTemplate?.() - ) : ( - - )} - - )} +
+ )} +
+
+ {ctx.slots.default?.()}
- {extraInfo &&
{extraInfo}
} + {(props.feedbackStatus || ctx.slots.suffixTemplate?.()) && ( + + {ctx.slots.suffixTemplate?.() ? ( + ctx.slots.suffixTemplate?.() + ) : ( + + )} + + )}
- ); - }; + {props.extraInfo &&
{props.extraInfo}
} +
+ ); }, }); diff --git a/packages/devui-vue/devui/form/src/components/form-control/use-form-control.ts b/packages/devui-vue/devui/form/src/components/form-control/use-form-control.ts new file mode 100644 index 0000000000..652497f64d --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-control/use-form-control.ts @@ -0,0 +1,20 @@ +import { computed, reactive, inject, toRefs } from 'vue'; +import { FORM_TOKEN, IForm } from '../../form-types'; +import { FormControlProps, UseFormControl } from './form-control-types'; +import { useNamespace } from '../../../../shared/hooks/use-namespace'; + +export function useFormControl(props: FormControlProps): UseFormControl { + const Form = reactive(inject(FORM_TOKEN) as IForm); + const labelData = reactive(Form.labelData); + const ns = useNamespace('form'); + const { feedbackStatus } = toRefs(props); + + const controlContainerClasses = computed(() => ({ + [`${ns.e('control-container')}`]: true, + [`${ns.em('control-container', 'horizontal')}`]: labelData.layout === 'horizontal', + [`${ns.em('control-container', 'has-feedback')}`]: Boolean(feedbackStatus.value), + [`${ns.em('control-container', 'feedback-error')}`]: Boolean(feedbackStatus.value === 'error'), + })); + + return { controlContainerClasses }; +} diff --git a/packages/devui-vue/devui/form/src/components/form-item/form-item-types.ts b/packages/devui-vue/devui/form/src/components/form-item/form-item-types.ts new file mode 100644 index 0000000000..69c5f6aefc --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-item/form-item-types.ts @@ -0,0 +1,19 @@ +import type { ComputedRef, ExtractPropTypes } from 'vue'; + +export const formItemProps = { + field: { + type: String, + default: '', + }, + dHasFeedback: { + type: Boolean, + default: false, + }, +}; + +export type FormItemProps = ExtractPropTypes; + +export interface UseFormItem { + itemClasses: ComputedRef>; + tipClasses: ComputedRef>; +} diff --git a/packages/devui-vue/devui/form/src/components/form-item/form-item.scss b/packages/devui-vue/devui/form/src/components/form-item/form-item.scss index 581ae36c1c..19c66a5aa5 100644 --- a/packages/devui-vue/devui/form/src/components/form-item/form-item.scss +++ b/packages/devui-vue/devui/form/src/components/form-item/form-item.scss @@ -1,25 +1,22 @@ -.devui-form-item { +.devui-form__item { display: flex; - // align-items: center; margin-bottom: 20px; } -.devui-form-item-vertical { +.devui-form__item--vertical { flex-direction: column; } -.devui-form-item-columns { +.devui-form__item--columns { flex-direction: column; display: inline-block !important; } -// .u-1-3 { -// width: 33.3%; -// } -.devui-column-item { + +/* .devui-form__item--columns { margin-bottom: 20px; -} +} */ -.devui-validate-tip { +.devui-form__validate-tip { display: flex; justify-content: center; align-items: center; @@ -27,6 +24,6 @@ color: #f66f6a; } -.devui-validate-tip-horizontal { +.devui-form__validate-tip--horizontal { margin-left: 10px; } diff --git a/packages/devui-vue/devui/form/src/components/form-item/form-item.tsx b/packages/devui-vue/devui/form/src/components/form-item/form-item.tsx index 52783de339..5df931e728 100644 --- a/packages/devui-vue/devui/form/src/components/form-item/form-item.tsx +++ b/packages/devui-vue/devui/form/src/components/form-item/form-item.tsx @@ -1,46 +1,46 @@ import { defineComponent, reactive, inject, onMounted, onBeforeUnmount, provide, ref } from 'vue'; +import type { SetupContext } from 'vue'; import AsyncValidator, { Rules } from 'async-validator'; import mitt from 'mitt'; -import { dFormEvents, dFormItemEvents, IForm, formItemProps, formInjectionKey, formItemInjectionKey } from '../../form-types'; +import { dFormEvents, dFormItemEvents, IForm, FORM_TOKEN, FORM_ITEM_TOKEN } from '../../form-types'; +import { formItemProps, FormItemProps } from './form-item-types'; +import { useNamespace } from '../../../../shared/hooks/use-namespace'; +import { useFormItem } from './use-form-item'; import './form-item.scss'; +import item from '../../../../carousel/src/item'; export default defineComponent({ name: 'DFormItem', props: formItemProps, - setup(props, ctx) { + setup(props: FormItemProps, ctx: SetupContext) { const formItemMitt = mitt(); - const dForm = reactive(inject(formInjectionKey, {} as IForm)); + const dForm = reactive(inject(FORM_TOKEN, {} as IForm)); const formData = reactive(dForm.formData); - const columnsClass = ref(dForm.columnsClass); - const initFormItemData = formData[props.prop]; - const labelData = reactive(dForm.labelData); + const initFormItemData = formData[props.field]; const rules = reactive(dForm.rules); + const { itemClasses, tipClasses } = useFormItem(); const resetField = () => { if (Array.isArray(initFormItemData)) { - formData[props.prop] = [...initFormItemData]; + formData[props.field] = [...initFormItemData]; } else { - formData[props.prop] = initFormItemData; + formData[props.field] = initFormItemData; } }; const formItem = reactive({ dHasFeedback: props.dHasFeedback, - prop: props.prop, + field: props.field, formItemMitt, resetField, }); - provide(formItemInjectionKey, formItem); - - const isHorizontal = labelData.layout === 'horizontal'; - const isVertical = labelData.layout === 'vertical'; - const isColumns = labelData.layout === 'columns'; + provide(FORM_ITEM_TOKEN, formItem); const showMessage = ref(false); const tipMessage = ref(''); const validate = (trigger: string) => { - const ruleKey = props.prop; + const ruleKey = props.field; const ruleItem = rules[ruleKey]; const descriptor: Rules = {}; descriptor[ruleKey] = ruleItem; @@ -61,8 +61,8 @@ export default defineComponent({ const validateEvents = []; const addValidateEvents = () => { - if (rules && rules[props.prop]) { - const ruleItem = rules[props.prop]; + if (rules && rules[props.field]) { + const ruleItem = rules[props.field]; let eventName = ruleItem['trigger']; if (Array.isArray(ruleItem)) { @@ -81,7 +81,7 @@ export default defineComponent({ }; const removeValidateEvents = () => { - if (rules && rules[props.prop] && validateEvents.length > 0) { + if (rules && rules[props.field] && validateEvents.length > 0) { validateEvents.forEach((item) => { formItem.formItemMitt.off(item.eventName, item.cb); }); @@ -99,14 +99,9 @@ export default defineComponent({ }); return () => { return ( -
+
{ctx.slots.default?.()} -
- {showMessage.value && tipMessage.value} -
+
{showMessage.value && tipMessage.value}
); }; diff --git a/packages/devui-vue/devui/form/src/components/form-item/use-form-item.ts b/packages/devui-vue/devui/form/src/components/form-item/use-form-item.ts new file mode 100644 index 0000000000..c11bb99855 --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-item/use-form-item.ts @@ -0,0 +1,23 @@ +import { computed, reactive, inject } from 'vue'; +import { FORM_TOKEN, IForm } from '../../form-types'; +import { UseFormItem } from './form-item-types'; +import { useNamespace } from '../../../../shared/hooks/use-namespace'; + +export function useFormItem(): UseFormItem { + const Form = reactive(inject(FORM_TOKEN) as IForm); + const labelData = reactive(Form.labelData); + const ns = useNamespace('form'); + + const itemClasses = computed(() => ({ + [`${ns.e('item')}`]: true, + [`${ns.em('item', 'vertical')}`]: labelData.layout === 'vertical', + [`${ns.em('item', 'columns')}`]: labelData.layout === 'columns', + })); + + const tipClasses = computed(() => ({ + [`${ns.e('validate-tip')}`]: true, + [`${ns.em('validate-tip', 'horizontal')}`]: labelData.layout === 'horizontal', + })); + + return { itemClasses, tipClasses }; +} 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 new file mode 100644 index 0000000000..23a6e51184 --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-label/form-label-types.ts @@ -0,0 +1,23 @@ +import type { ComputedRef, ExtractPropTypes } from 'vue'; + +export const formLabelProps = { + required: { + type: Boolean, + default: false, + }, + hasHelp: { + type: Boolean, + default: false, + }, + helpTips: { + type: String, + default: '', + }, +}; + +export type FormLabelProps = ExtractPropTypes; + +export interface UseFormLabel { + labelClasses: ComputedRef>; + labelInnerClasses: ComputedRef>; +} 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 87d693b7d5..83ccda57d3 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,13 +1,7 @@ -.devui-form-label { - // flex: 1 1 auto; - -moz-box-flex: 1; - text-align: left; - padding-bottom: 8px; - justify-content: flex-start; +.devui-form__label { align-self: flex-start; - margin-right: 16px; - .devui-required { + .devui-form__label--required { display: inline-flex; align-items: center; @@ -21,30 +15,31 @@ } } -.devui-form-label_sm { - width: 80px; - min-width: 80px; +.devui-form__label--sm { + flex: 0 0 80px; +} + +.devui-form__label--md { + flex: 0 0 100px; } -.devui-form-label_sd { - width: 100px; - min-width: 100px; +.devui-form__label--lg { + flex: 0 0 150px; } -.devui-form-label_lg { - width: 150px; - min-width: 150px; +.devui-form__label--start { + text-align: left; } -.devui-form-label_center { +.devui-form__label--center { text-align: center; } -.devui-form-label_end { +.devui-form__label--end { text-align: end; } -.devui-form-label-help { +.devui-form__label-help { border-radius: 50%; display: inline-flex; justify-content: center; 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 c20b88e35b..d1a078af35 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,50 +1,39 @@ -import { defineComponent, inject, reactive, computed } from 'vue'; -import { IForm, formLabelProps, FormLabelProps, formInjectionKey } from '../../form-types'; +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 { useNamespace } from '../../../../shared/hooks/use-namespace'; +import { useFormLabel } from './use-form-label'; import './form-label.scss'; export default defineComponent({ name: 'DFormLabel', props: formLabelProps, - setup(props: FormLabelProps, ctx) { - const dForm = reactive(inject(formInjectionKey, {} as IForm)); - const labelData = reactive(dForm.labelData); + setup(props: FormLabelProps, ctx: SetupContext) { + const ns = useNamespace('form'); + const { labelClasses, labelInnerClasses } = useFormLabel(props); - const isHorizontal = computed(() => labelData.layout === 'horizontal').value; - const isLg = computed(() => labelData.labelSize === 'lg').value; - const isSm = computed(() => labelData.labelSize === 'sm').value; - const isCenter = computed(() => labelData.labelAlign === 'center').value; - const isEnd = computed(() => labelData.labelAlign === 'end').value; - - const wrapperCls = `devui-form-label${ - isHorizontal ? (isSm ? ' devui-form-label_sm' : isLg ? ' devui-form-label_lg' : ' devui-form-label_sd') : '' - }${isCenter ? ' devui-form-label_center' : isEnd ? ' devui-form-label_end' : ''}`; - const className = `${props.required ? ' devui-required' : ''}`; - const style = { display: isHorizontal ? 'inline' : 'inline-block' }; - - return () => { - return ( - - - {ctx.slots.default?.()} - {props.hasHelp && props.helpTips && ( - ( - - - - ), - }}> - )} - + return () => ( + + + {ctx.slots.default?.()} + {props.hasHelp && props.helpTips && ( + ( + + + + ), + }}> + )} - ); - }; + + ); }, }); 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 new file mode 100644 index 0000000000..b980929918 --- /dev/null +++ b/packages/devui-vue/devui/form/src/components/form-label/use-form-label.ts @@ -0,0 +1,23 @@ +import { computed, reactive, 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 ns = useNamespace('form'); + const { required } = toRefs(props); + + const labelClasses = computed(() => ({ + [`${ns.e('label')}`]: true, + [`${ns.em('label', labelData.labelSize)}`]: labelData.layout === 'horizontal', + [`${ns.em('label', labelData.labelAlign)}`]: labelData.layout === 'horizontal', + })); + + const labelInnerClasses = computed(() => ({ + [`${ns.em('label', 'required')}`]: required.value, + })); + + return { labelClasses, labelInnerClasses }; +} diff --git a/packages/devui-vue/devui/form/src/form-types.ts b/packages/devui-vue/devui/form/src/form-types.ts index db5d54f051..5b3c61e2e2 100644 --- a/packages/devui-vue/devui/form/src/form-types.ts +++ b/packages/devui-vue/devui/form/src/form-types.ts @@ -1,18 +1,22 @@ import { Emitter } from 'mitt'; import type { PropType, ExtractPropTypes, InjectionKey, Ref } from 'vue'; +export type Layout = 'horizontal' | 'vertical' | 'columns'; +export type LabelSize = 'sm' | 'md' | 'lg'; +export type FormData = Record; + export const formProps = { - formData: { - type: Object, - default: {} + data: { + type: Object as PropType, + default: () => ({}), }, layout: { - type: String as PropType<'horizontal' | 'vertical' | 'columns'>, + type: String as PropType, default: 'horizontal', }, labelSize: { - type: String as PropType<'sm' | '' | 'lg'>, - default: '', + type: String as PropType, + default: 'md', }, labelAlign: { type: String as PropType<'start' | 'center' | 'end'>, @@ -22,10 +26,6 @@ export const formProps = { type: Object, default: {}, }, - columnsClass: { - type: String as PropType<'u-1-3'>, - default: '', - }, name: { type: String, default: '', @@ -36,43 +36,6 @@ export const formProps = { }, } as const; -export const formItemProps = { - dHasFeedback: { - type: Boolean, - default: false - }, - prop: { - type: String, - default: '' - } -} as const; - -export const formLabelProps = { - required: { - type: Boolean, - default: false - }, - hasHelp: { - type: Boolean, - default: false - }, - helpTips: { - type: String, - default: '' - } -} as const; - -export const formControlProps = { - feedbackStatus: { - type: String as PropType<'success' | 'error' | 'pending' | ''>, - default: '' - }, - extraInfo: { - type: String, - default: '' - } -} as const; - export const dFormEvents = { addField: 'd.form.addField', removeField: 'd.form.removeField', @@ -84,8 +47,7 @@ type LabelData = { labelAlign: string; }; -export const formInjectionKey: InjectionKey = Symbol('dForm'); -export const formItemInjectionKey: InjectionKey = Symbol('dFormItem'); +export const FORM_ITEM_TOKEN: InjectionKey = Symbol('dFormItem'); export const dFormItemEvents = { blur: 'd.form.blur', @@ -93,16 +55,16 @@ export const dFormItemEvents = { input: 'd.form.input', } as const; - export interface IForm { formData: any; labelData: IFormLabel; formMitt: Emitter; rules: any; - columnsClass: string; messageShowType: string; } +export const FORM_TOKEN: InjectionKey = Symbol('dForm'); + export interface IFormLabel { layout: string; labelSize: string; @@ -124,10 +86,6 @@ export interface IFormControl { } export type FormProps = ExtractPropTypes; -export type FormItemProps = ExtractPropTypes; -export type FormLabelProps = ExtractPropTypes; -export type FormControlProps = ExtractPropTypes; - export interface IValidators { required: boolean; @@ -150,23 +108,34 @@ const Validators: IValidators = { requiredTrue: false, email: false, pattern: undefined, - whiteSpace: false + whiteSpace: false, }; export const dDefaultValidators = { - 'required': Validators.required, // 配置不能为空限制,rule中使用:{ required: true } - 'minlength': Validators.minlength, // 配置最小长度限制,rule中使用:{ minlength: 5 } - 'maxlength': Validators.maxlength, // 配置最大长度限制,rule中使用:{ maxlength: 128 } - 'min': Validators.min, // 配置最小值限制,rule中使用:{ min: 0 } - 'max': Validators.max, // 配置最大值限制,rule中使用:{ max: 100 } - 'requiredTrue': Validators.requiredTrue, // 配置需要为true,rule中使用:{ requiredTrue: true } - 'email': Validators.email, // 配置邮箱校验,rule中使用:{ email: true } - 'pattern': Validators.pattern, // 配置正则校验,rule中使用:{ pattern: RegExp } - 'whitespace': Validators.whiteSpace, // 配置输入不能全为空格限制,rule中使用:{ whitespace: true } + required: Validators.required, // 配置不能为空限制,rule中使用:{ required: true } + minlength: Validators.minlength, // 配置最小长度限制,rule中使用:{ minlength: 5 } + maxlength: Validators.maxlength, // 配置最大长度限制,rule中使用:{ maxlength: 128 } + min: Validators.min, // 配置最小值限制,rule中使用:{ min: 0 } + max: Validators.max, // 配置最大值限制,rule中使用:{ max: 100 } + requiredTrue: Validators.requiredTrue, // 配置需要为true,rule中使用:{ requiredTrue: true } + email: Validators.email, // 配置邮箱校验,rule中使用:{ email: true } + pattern: Validators.pattern, // 配置正则校验,rule中使用:{ pattern: RegExp } + whitespace: Validators.whiteSpace, // 配置输入不能全为空格限制,rule中使用:{ whitespace: true } }; - -export type positionType = 'top' | 'right' | 'bottom' | 'left' | 'left-top' | 'left-bottom' | 'top-left' | 'top-right' | 'right-top' | 'right-bottom' | 'bottom-left' | 'bottom-right'; +export type positionType = + | 'top' + | 'right' + | 'bottom' + | 'left' + | 'left-top' + | 'left-bottom' + | 'top-left' + | 'top-right' + | 'right-top' + | 'right-bottom' + | 'bottom-left' + | 'bottom-right'; export interface DValidateResult { errors: any; diff --git a/packages/devui-vue/devui/form/src/form.tsx b/packages/devui-vue/devui/form/src/form.tsx index 59c03f7e35..6d8c528891 100644 --- a/packages/devui-vue/devui/form/src/form.tsx +++ b/packages/devui-vue/devui/form/src/form.tsx @@ -1,16 +1,18 @@ -import { defineComponent, provide } from 'vue'; +import { defineComponent, provide, SetupContext } from 'vue'; import mitt from 'mitt'; -import { formProps, FormProps, IFormItem, dFormEvents, formInjectionKey, IForm } from './form-types'; +import { formProps, FormProps, IFormItem, dFormEvents, FORM_TOKEN } from './form-types'; import { EventBus } from './utils'; +import { useNamespace } from '../../shared/hooks/use-namespace'; import './form.scss'; export default defineComponent({ name: 'DForm', props: formProps, emits: ['submit'], - setup(props: FormProps, ctx) { + setup(props: FormProps, ctx: SetupContext) { const formMitt = mitt(); const fields: IFormItem[] = []; + const ns = useNamespace('form'); const resetFormFields = () => { fields.forEach((field: IFormItem) => { field.resetField(); @@ -29,8 +31,8 @@ export default defineComponent({ } }); - provide(formInjectionKey, { - formData: props.formData, + provide(FORM_TOKEN, { + formData: props.data, formMitt, labelData: { layout: props.layout, @@ -38,7 +40,6 @@ export default defineComponent({ labelAlign: props.labelAlign, }, rules: props.rules, - columnsClass: props.columnsClass, messageShowType: 'popover', }); @@ -48,18 +49,9 @@ export default defineComponent({ EventBus.emit(`formSubmit:${props.name}`); }; - return { - fields, - formMitt, - onSubmit, - resetFormFields, - }; - }, - render() { - const { onSubmit } = this; - return ( -
- {this.$slots.default?.()} + return () => ( + + {ctx.slots.default?.()}
); }, diff --git a/packages/devui-vue/devui/input/src/input.tsx b/packages/devui-vue/devui/input/src/input.tsx index 509393835d..694ace998e 100644 --- a/packages/devui-vue/devui/input/src/input.tsx +++ b/packages/devui-vue/devui/input/src/input.tsx @@ -1,7 +1,7 @@ import { defineComponent, computed, ref, watch, inject } from 'vue'; import { inputProps, InputType } from './input-types'; import './input.scss'; -import { dFormItemEvents, IFormItem, formItemInjectionKey } from '../../form/src/form-types'; +import { dFormItemEvents, IFormItem, FORM_ITEM_TOKEN } from '../../form/src/form-types'; export default defineComponent({ name: 'DInput', @@ -11,13 +11,13 @@ export default defineComponent({ if (binding.value) { el.focus(); } - } - } + }, + }, }, props: inputProps, emits: ['update:modelValue', 'focus', 'blur', 'change', 'keydown'], setup(props, ctx) { - const formItem = inject(formItemInjectionKey, {} as IFormItem); + const formItem = inject(FORM_ITEM_TOKEN, {} as IFormItem); const hasFormItem = Object.keys(formItem).length > 0; const sizeCls = computed(() => `devui-input-${props.size}`); const showPwdIcon = ref(false); @@ -27,7 +27,7 @@ export default defineComponent({ error: props.error, [props.cssClass]: true, 'devui-input-restore': showPwdIcon.value, - [sizeCls.value]: props.size !== '' + [sizeCls.value]: props.size !== '', }; }); const showPreviewIcon = computed(() => inputType.value === 'password'); @@ -71,7 +71,7 @@ export default defineComponent({ onBlur, onChange, onKeydown, - onChangeInputType + onChangeInputType, }; }, render() { @@ -90,10 +90,10 @@ export default defineComponent({ onBlur, onChange, onKeydown, - onChangeInputType + onChangeInputType, } = this; return ( -
+
{showPwdIcon && ( -
- {showPreviewIcon ? ( - - ) : ( - - )} +
+ {showPreviewIcon ? : }
)}
); - } + }, }); diff --git a/packages/devui-vue/docs/components/form/index.md b/packages/devui-vue/docs/components/form/index.md index af6f44529a..e33d600e73 100644 --- a/packages/devui-vue/docs/components/form/index.md +++ b/packages/devui-vue/docs/components/form/index.md @@ -6,37 +6,32 @@ 需要进行数据收集、数据校验、数据提交功能时。 - - ### 基础用法 -基本用法当中,Label是在数据框的上面。 - - -:::demo +:::demo 默认提供水平布局,`data`参数用于设置表单数据。 ```vue - - ``` ::: - ### 横向排列 -Label左右布局方式。 - +Label 左右布局方式。 :::demo ```vue - - ``` ::: - ### 弹框表单 > todo
-> 待替换为Modal组件 - -弹框表单,弹框建议是400px,550px,700px,900px,建议宽高比是16: 9、3: 2。 +> 待替换为 Modal 组件 +弹框表单,弹框建议是 400px,550px,700px,900px,建议宽高比是 16: 9、3: 2。 :::demo @@ -278,25 +258,25 @@ export default defineComponent({
- + 姓名 - + 年龄 - + 城市 - + 喜欢的水果 - + 性别 - + 下班了吗 - + 兴趣领域 @@ -340,11 +320,10 @@ export default defineComponent({
- - - ``` ::: - ### 多列表单 -多列表单。layout的属性为`columns`,同时搭配columnsClass属性,值为"u-[row]-[col]",例如`u-1-3`为1行3列。 - +多列表单。layout 的属性为`columns`。 :::demo ```vue - ``` ::: - - ### 模板驱动表单验证 在`d-form`、`d-input`等表单类组件上使用`v-d-validate-rules`指令,配置校验规则。 +#### 验证单个元素,使用内置校验器,配置 error message -#### 验证单个元素,使用内置校验器,配置error message - -当前DevUI支持的内置校验器有:`required`、`minlength`、`maxlength`、`min`、`max`、`requiredTrue`、`email`、`pattern`、`whitespace`。 +当前 DevUI 支持的内置校验器有:`required`、`minlength`、`maxlength`、`min`、`max`、`requiredTrue`、`email`、`pattern`、`whitespace`。 - 若需限制用户输入不能全为空格,可使用`whitespace`内置校验器 @@ -557,37 +524,45 @@ export default defineComponent({ - 除`pattern`外,其他内置校验器我们也提供了内置的错误提示信息,在你未自定义提示消息时,我们将使用默认的提示信息。 -- message配置支持string与object两种形式(支持国际化词条配置,如`'zh-cn'`,默认将取`'default'`)。 +- message 配置支持 string 与 object 两种形式(支持国际化词条配置,如`'zh-cn'`,默认将取`'default'`)。 :::demo ```vue - - ``` ::: @@ -627,35 +600,40 @@ export default defineComponent({ ```vue - - ``` ::: - -#### 验证单个元素,配置错误更新策略errorStrategy、校验时机updateOn +#### 验证单个元素,配置错误更新策略 errorStrategy、校验时机 updateOn - 设置`errorStrategy`属性初始化时是否进行校验 + - 默认配置为`dirty`,校验不通过进行错误提示 - 若需要在初始化时将错误抛出,可配置为`pristine` @@ -721,61 +697,67 @@ export default defineComponent({ ```vue - - ``` ::: - #### 验证单个元素,自定义管理消息提示 配置`messageShowType`可选择消息自动提示的方式,默认为`popover`。 @@ -843,52 +822,53 @@ export default defineComponent({ - 设置为`none`错误信息将不会自动呈现到视图, 可在模板中获取`message`或通过监听`messageChange`事件获取错误`message`, 或在模板中直接通过引用获取。 -- 在 `options`中配置 `popPosition`可在消息提示方式为`popover`时,自定义`popover`内容弹出方向, 默认为`['right', 'bottom']`。更多取值参考popover组件。 +- 在 `options`中配置 `popPosition`可在消息提示方式为`popover`时,自定义`popover`内容弹出方向, 默认为`['right', 'bottom']`。更多取值参考 popover 组件。 :::demo ```vue - - ``` ::: +#### 验证单个元素,自定义 asyncDebounceTime - -#### 验证单个元素,自定义asyncDebounceTime - - -对于异步校验器,提供默认300ms debounce time。在options中设置`asyncDebounceTime`显示设置(单位ms)。 - +对于异步校验器,提供默认 300ms debounce time。在 options 中设置`asyncDebounceTime`显示设置(单位 ms)。 :::demo ```vue - - ``` ::: +#### Form 验证与提交 - -#### Form验证与提交 - -点击提交按钮时进行验证,需指定name属性,并同时绑定d-form标签的submit事件才能生效。 +点击提交按钮时进行验证,需指定 name 属性,并同时绑定 d-form 标签的 submit 事件才能生效。 :::demo ```vue - - ``` ::: +#### Form 验证与提交,用户注册场景 -#### Form验证与提交,用户注册场景 - -对于自动错误提示的方式,在form中, 建议在dForm层统一设置`messageShowType`,需同时设置ref属性才能生效。 - +对于自动错误提示的方式,在 form 中, 建议在 dForm 层统一设置`messageShowType`,需同时设置 ref 属性才能生效。 :::demo ```vue - - ``` ::: ### 响应式表单验证 -在`d-form`标签中指定校验规则rules,同时在`d-form-item`中指定`prop`的值为校验字段名。 - +在`d-form`标签中指定校验规则 rules,同时在`d-form-item`中指定`field`的值为校验字段名。 :::demo ```vue - - ``` ::: +### 指定表单 Feedback 状态 -### 指定表单Feedback状态 - -你可通过对d-form-control设置feedbackStatus手动指定反馈状态。当前已支持状态:`success`、`error`、`pending`。 - +你可通过对 d-form-control 设置 feedbackStatus 手动指定反馈状态。当前已支持状态:`success`、`error`、`pending`。 :::demo ```vue - - ``` ::: - -可通过对具名插槽suffixTemplate在d-form-control中自定义反馈状态icon。 - +可通过对具名插槽 suffixTemplate 在 d-form-control 中自定义反馈状态 icon。 :::demo ```vue - - ``` ::: - ### Label horizontal arrangement Left-right layout of labels. - :::demo ```vue @@ -206,11 +195,10 @@ Left-right layout of labels. Reset - - - ``` ::: - ### Pop-up form > todo
@@ -269,7 +252,6 @@ export default defineComponent({ Pop-up form. The recommended pop-up box size is 400px, 550px, 700px, and 900px. The recommended aspect ratio is 16:9 or 3:2. - :::demo ```vue @@ -340,11 +322,10 @@ Pop-up form. The recommended pop-up box size is 400px, 550px, 700px, and 900px.
- - - ``` ::: - ### Multiple Lists Multiple lists.The value of layout should be `columns` ,together with the columnsclass attribute, and the value should be "u - [row] - [column]".For example, 'u-1-3' is 1 row and 3 columns. - :::demo ```vue - ``` ::: - - ### Template driven form validation Use the `v-d-validate-rules` derective on form components such as `d-form` and `d-input` to configure verification rules. - #### Verify a single element, use the built-in validator, and configure error message The following built-in validators are currently supported by DevUI:`required`,`minlength`,`maxlength`,`min`,`max`,`requiredTrue`,`email`,`pattern`,`whitespace`. - To limit user input to not all spaces, use the `whitespace`built-in validator -- Setting the maximum limit to the actual check value `+1` is a good way to limit the length of user input. -- In addition to pattern, other built-in validators also provide built-in error alerts, which are used by default when you do not customize them. +- Setting the maximum limit to the actual check value `+1` is a good way to limit the length of user input. +- In addition to pattern, other built-in validators also provide built-in error alerts, which are used by default when you do not customize them. - The message configuration supports both string and object forms (supports internationalized term configurations such as `'zh-cn'`, which defaults to `'default'`). @@ -561,31 +530,33 @@ The following built-in validators are currently supported by DevUI:`required`, ```vue - - ``` ::: @@ -625,35 +594,40 @@ Custom validators, which pass in `validators` field configuration checking rules ```vue - - ``` ::: @@ -706,6 +678,7 @@ export default defineComponent({ #### Verify a single element. The update policy errorStrategy is incorrectly configured and the validation moment updateOn is incorrectly configured. - Set whether the `errorStrategy` property is checked when it is initialized + - Default configuration is `dirty`, check does not pass error prompt - If you need to throw an error at initialization, configure it as `pristine` @@ -718,61 +691,71 @@ export default defineComponent({ ```vue - - ``` ::: @@ -842,46 +823,47 @@ Configure `messageShowType` to choose how messages are automatically prompted, d ```vue - - ``` ::: - - #### Verify a single element and customize asyncDebounceTime - For asynchronous validators, a default of 300ms debounce time is provided. Set `asyncDebounceTime`(ms) display settings in options. - :::demo ```vue - - ``` ::: - - #### Form Validation and Submission To validate when you click the submit button, you need to specify the name property and also bind the submit event of the `d-form` component to take effect. @@ -1007,23 +980,29 @@ To validate when you click the submit button, you need to specify the name prope Name - + Age - + @@ -1034,7 +1013,7 @@ To validate when you click the submit button, you need to specify the name prope - - ``` ::: @@ -1081,34 +1058,47 @@ export default defineComponent({ For automatic error prompting, it is recommended that `messageShowType` be set uniformly at the `d-form` component in the form, and ref property must be set at the same time to take effect. - :::demo ```vue - - ``` ::: @@ -1166,30 +1154,28 @@ export default defineComponent({ Specify the validate rules in the `d-form` component and the value of `prop` in the `d-form-item` is the validate field name. - :::demo ```vue - - ``` ::: - ### Feedback status of a specified form You can manually specify the feedback status by setting `feedbackStatus` for `d-form-control`. Currently, the following statuses are supported: `success`, `error`, and `pending`. - :::demo ```vue - - ``` ::: - You can customize the feedback status icon in `d-form-control` by `suffixTemplate` to a named slot. - :::demo ```vue