diff --git a/packages/devui-vue/devui/form/index.ts b/packages/devui-vue/devui/form/index.ts index ba7b235ffa70a54c5fd22ef3acc49c3e1763ad64..bdbb61bbf6e0ac8c1efc924b0cbf2356d5bf84fa 100644 --- a/packages/devui-vue/devui/form/index.ts +++ b/packages/devui-vue/devui/form/index.ts @@ -32,7 +32,7 @@ export { Form, FormLabel, FormItem, FormControl, FormOperation } export default { title: 'Form 表单', category: '数据录入', - status: '70%', + status: '75%', install(app: App): void { app.use(Form as any); app.use(FormLabel as any); diff --git a/packages/devui-vue/devui/form/src/directive/d-validate-rules.ts b/packages/devui-vue/devui/form/src/directive/d-validate-rules.ts index 658bd0c344c962f1f9d821fe2fbbc423e8d6ec1a..f8a59f2852df6e2c035be9278fcccc2714681fae 100644 --- a/packages/devui-vue/devui/form/src/directive/d-validate-rules.ts +++ b/packages/devui-vue/devui/form/src/directive/d-validate-rules.ts @@ -12,6 +12,8 @@ interface ValidateFnParam { isFormTag: boolean message: string messageShowType: MessageShowType + dfcUID: string + popPosition: PopPosition | Array } interface CustomValidatorRuleObject { @@ -24,6 +26,7 @@ interface DirectiveValidateRuleOptions { updateOn?: UpdateOn errorStrategy?: ErrorStrategy asyncDebounceTime?: number + popPosition?: PopPosition | Array } interface DirectiveBindingValue { @@ -38,9 +41,18 @@ interface DirectiveCustomRuleItem extends RuleItem { asyncValidators: CustomValidatorRuleObject[] } +export interface ShowPopoverErrorMessageEventData { + showPopover?: boolean + message?: string + uid?: string, + popPosition?: PopPosition +} + type MessageShowType = 'popover' | 'text' | 'none' | 'toast'; type UpdateOn = 'input' | 'focus' | 'change' | 'blur' | 'submit'; type ErrorStrategy = 'dirty' | 'pristine'; +type BasePopPosition = 'left' | 'right' | 'top' | 'bottom'; +type PopPosition = BasePopPosition | 'left-top' | 'left-bottom' | 'top-left' | 'top-right' | 'right-top' | 'right-bottom' | 'bottom-left' | 'bottom-right'; enum ErrorStrategyEnum { dirty = 'dirty', @@ -198,7 +210,7 @@ function handleErrorStrategyPass(el: HTMLElement): void { el.setAttribute('class', classList.join(' ')); } -function handleValidateError({el, tipEl, message, isFormTag, messageShowType}: Partial): void { +function handleValidateError({el, tipEl, message, isFormTag, messageShowType, dfcUID, popPosition = 'right-bottom'}: Partial): void { // 如果该指令用在form标签上,这里做特殊处理 if(isFormTag && messageShowType === MessageShowTypeEnum.toast) { // todo:待替换为toast @@ -206,6 +218,12 @@ function handleValidateError({el, tipEl, message, isFormTag, messageShowType}: P return; } + // messageShowType为popover时,设置popover + if(MessageShowTypeEnum.popover === messageShowType) { + EventBus.emit("showPopoverErrorMessage", {showPopover: true, message, uid: dfcUID, popPosition} as ShowPopoverErrorMessageEventData); + return; + } + tipEl.innerText = '' + message; tipEl.style.display = 'inline-flex'; tipEl.setAttribute('class', 'd-validate-tip'); @@ -225,7 +243,7 @@ function getFormName(binding: DirectiveBinding): string { } // 校验处理函数 -function validateFn({validator, modelValue, el, tipEl, isFormTag, messageShowType}: Partial) { +function validateFn({validator, modelValue, el, tipEl, isFormTag, messageShowType, dfcUID, popPosition}: Partial) { validator.validate({modelName: modelValue}).then(() => { handleValidatePass(el, tipEl); }).catch((err) => { @@ -240,13 +258,22 @@ function validateFn({validator, modelValue, el, tipEl, isFormTag, messageShowTyp msg = errors[0].message; } - handleValidateError({el, tipEl, message: msg, isFormTag, messageShowType}); + handleValidateError({el, tipEl, message: msg, isFormTag, messageShowType, dfcUID, popPosition}); }) } +// 检测popover的position是否是正确值 +function checkValidPopsition(positionStr: string): boolean { + const validPosition = ['left', 'right', 'top', 'bottom', 'left-top', 'left-bottom', 'top-left', 'top-right', 'right-top', 'right-bottom', 'bottom-left', 'bottom-right']; + const isValid = validPosition.includes(positionStr); + !isValid && console.warn(`invalid popPosition value '${positionStr}'.`); + return isValid +} + export default { mounted(el: HTMLElement, binding: DirectiveBinding, vnode: VNode): void { const isFormTag = el.tagName === 'FORM'; + const dfcUID = el.parentNode.parentNode.parentElement.dataset.uid; const hasOptions = isObject(binding.value) && hasKey(binding.value, 'options'); @@ -259,12 +286,25 @@ export default { let { errorStrategy }: DirectiveBindingValue = binding.value; // errorStrategy可配置在options对象中 - const { + let { updateOn = UpdateOnEnum.change, errorStrategy: ErrorStrategy = ErrorStrategyEnum.dirty, - asyncDebounceTime = 300 + asyncDebounceTime = 300, + popPosition = ['right', 'bottom'] }: DirectiveValidateRuleOptions = options; + // 设置popover的位置 + if(messageShowType === MessageShowTypeEnum.popover) { + if(Array.isArray(popPosition)) { + popPosition = (popPosition.length > 1 ? popPosition.join('-') : popPosition[0]) as PopPosition; + if(!checkValidPopsition(popPosition)) { + popPosition = 'right-bottom'; + } + }else if(!checkValidPopsition(popPosition)) { + popPosition = 'right-bottom'; + } + } + if(!errorStrategy) { errorStrategy = ErrorStrategy; } @@ -340,7 +380,10 @@ export default { const htmlEventValidateHandler = (e) => { const modelValue = e.target.value; - validateFn({validator, modelValue, el, tipEl, isFormTag: false, messageShowType}); + if(messageShowType === MessageShowTypeEnum.popover) { + EventBus.emit("showPopoverErrorMessage", {showPopover: false, message: "", uid: dfcUID} as ShowPopoverErrorMessageEventData); + } + validateFn({validator, modelValue, el, tipEl, isFormTag: false, messageShowType, dfcUID, popPosition}); } // 监听事件验证 diff --git a/packages/devui-vue/devui/form/src/form-control/form-control.scss b/packages/devui-vue/devui/form/src/form-control/form-control.scss index 993bae7c6654f75d29a904b6da6a99ffa5a4eaac..55b3f1bbdea89e121157fcdce5fd9d15d0f4ce90 100644 --- a/packages/devui-vue/devui/form/src/form-control/form-control.scss +++ b/packages/devui-vue/devui/form/src/form-control/form-control.scss @@ -60,6 +60,29 @@ } } + + .devui-control-content-wrapper { + position: relative; + & > div { + z-index: 10; + } + .devui-popover-wrapper { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + z-index: 9; + + & > div { + width: inherit; + height: 100%; + } + } + } + + + .has-feedback { display: flex; align-items: center; diff --git a/packages/devui-vue/devui/form/src/form-control/form-control.tsx b/packages/devui-vue/devui/form/src/form-control/form-control.tsx index 181509190d2fbe207180c93e93584449475898a0..a1c0cc9979f8693f308c59bd267bbc85f765aa2e 100644 --- a/packages/devui-vue/devui/form/src/form-control/form-control.tsx +++ b/packages/devui-vue/devui/form/src/form-control/form-control.tsx @@ -1,8 +1,14 @@ -import { defineComponent, inject, ref, computed, reactive } from 'vue'; +import { defineComponent, inject, ref, computed, reactive, onMounted } from 'vue'; +import { uniqueId } from 'lodash-es'; import {IForm, formControlProps, formInjectionKey} from '../form-types'; +import { ShowPopoverErrorMessageEventData } from '../directive/d-validate-rules' +import { EventBus } from '../util'; import Icon from '../../../icon/src/icon'; +import Popover from '../../../popover/src/popover'; import './form-control.scss'; +type positionType = 'top' | 'right' | 'bottom' | 'left'; + export default defineComponent({ name: 'DFormControl', props: formControlProps, @@ -11,6 +17,20 @@ export default defineComponent({ 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 tipMessage = ref(""); + const popPosition = ref("bottom"); + + onMounted(() => { + EventBus.on("showPopoverErrorMessage", (data: ShowPopoverErrorMessageEventData) => { + if(uid === data.uid) { + showPopover.value = data.showPopover; + tipMessage.value = data.message; + popPosition.value = data.popPosition as any; // todo: 待popover组件positionType完善类型之后再替换类型 + } + }); + }); const iconData = computed(() => { switch(props.feedbackStatus) { @@ -30,9 +50,14 @@ export default defineComponent({ feedbackStatus, extraInfo, } = props; - return
+ return
- {ctx.slots.default?.()} +
+ {ctx.slots.default?.()} +
+ { showPopover.value && } +
+
{ (feedbackStatus || ctx.slots.suffixTemplate?.()) &&