UNPKG

@fesjs/fes-design

Version:
235 lines (221 loc) 10.4 kB
import _defineProperty from '@babel/runtime/helpers/esm/defineProperty'; import { defineComponent, inject, computed, getCurrentInstance, ref, onBeforeUnmount, provide, nextTick, openBlock, createElementBlock, normalizeClass, normalizeStyle, renderSlot, createTextVNode, toDisplayString, createCommentVNode, createElementVNode, createVNode, Transition, withCtx } from 'vue'; import Schema from 'async-validator'; import { get, cloneDeep, isNil, isArray, set } from 'lodash-es'; import { pxfy } from '../_util/utils'; import getPrefixCls from '../_util/getPrefixCls'; import { FORM_ITEM_INJECTION_KEY } from '../_util/constants'; import { FORM_ITEM_NAME, provideKey, VALIDATE_STATUS, VALIDATE_MESSAGE_DEFAULT, FORM_LAYOUT, LABEL_POSITION, FORM_ITEM_ALIGN, RULE_TYPE_DEFAULT, TRIGGER_TYPE_DEFAULT } from './const'; import { wrapValidator } from './utils'; import { formItemProps } from './interface'; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } const prefixCls = getPrefixCls('form-item'); var script = defineComponent({ name: FORM_ITEM_NAME, props: formItemProps, setup(props) { const { model, rules, layout, span, inlineItemWidth, showMessage, labelWidth, labelClass, labelPosition, disabled, align, addField, removeField } = inject(provideKey); const formItemProp = computed(() => { return props.prop || `${prefixCls}_${getCurrentInstance().uid}`; }); const fieldValue = computed(() => { // 优先获取 value 的值 if (props.value !== undefined) { return props.value; } // 不存在时获取 model[prop] 的值 if (!model.value || !formItemProp.value) { return; } return get(model.value, formItemProp.value); }); const initialValue = cloneDeep(fieldValue.value); const formItemRules = computed(() => { const _rules = [].concat(props.rules || []).concat(get(rules === null || rules === void 0 ? void 0 : rules.value, formItemProp.value) || []); return _rules; }); /** * 规则校验结果逻辑: 仅存最后一条校验规则的逻辑 * 存在问题: 如果同时触发两个规则 A|B,规则 A 先触发校验且不通过,接着规则 B 触发校验且通过,规则 A 结果会不展示 */ const validateDisabled = ref(false); // 是否触发校验的标志 const validateStatus = ref(VALIDATE_STATUS.DEFAULT); const validateMessage = ref(VALIDATE_MESSAGE_DEFAULT); const setValidateInfo = function () { let status = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : VALIDATE_STATUS.DEFAULT; let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : VALIDATE_MESSAGE_DEFAULT; validateStatus.value = status; validateMessage.value = message; }; /** 错误展示逻辑: 就近原则【formItem 权重更高】 */ const formItemShowMessage = computed(() => (props.showMessage === null ? showMessage.value : props.showMessage) && validateStatus.value === VALIDATE_STATUS.ERROR); const formItemDisabled = computed(() => { if (!isNil(props.disabled)) { return props.disabled; } return disabled.value; }); const formItemRequired = computed(() => formItemRules.value.length > 0 && formItemRules.value.some(_ => _.required)); const formItemClass = computed(() => [prefixCls, // inlineFormItem 定宽情况: Form 传入 inlineItemWidth, 此时 inlineItemWidth 优先级最高 // inlineFormItem 自适应情况: 同时支持 form、formItem 传入 span, 此时 formItem 优先级更高 layout.value === FORM_LAYOUT.INLINE && !inlineItemWidth.value && `${prefixCls}-span-${Math.ceil(props.span || span.value)}`, labelPosition.value !== LABEL_POSITION.LEFT && `${prefixCls}-${labelPosition.value}`, validateStatus.value === VALIDATE_STATUS.ERROR && 'is-error', // 校验错误: is-error FORM_ITEM_ALIGN.includes(props.align || align.value) && `${prefixCls}-align-${props.align || align.value}`].filter(Boolean)); const formItemLabelClass = computed(() => [`${prefixCls}-label`, formItemRequired.value && 'is-required', // 必填校验: is-required labelClass.value, props.labelClass].filter(Boolean)); const formItemLabelStyle = computed(() => ({ width: pxfy(props.labelWidth || labelWidth.value) })); /** 校验规则的类型: 默认 String */ let ruleDefaultType = RULE_TYPE_DEFAULT; const setRuleDefaultType = function () { let val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : RULE_TYPE_DEFAULT; ruleDefaultType = val; }; const validateRules = async function () { let trigger = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : TRIGGER_TYPE_DEFAULT; // fieldValue.value computed 执行后再进行校验 await nextTick(); if (validateDisabled.value) { validateDisabled.value = false; return; } /** * 过滤符合条件的 triggersRules * * 未指定具体 trigger 类型,则直接返回 rule 规则 * 指定具体 trigger 类型: * 当 rule.trigger 未填写时, 则直接返回 rule 规则【没写 trigger 默认各种类型都可以触发该规则】 * 当 rule.trigger 填写时, 需要按指定类型过滤【分 Array| String】 */ const triggersRules = !trigger ? formItemRules.value : formItemRules.value.filter(rule => !rule.trigger || (isArray(rule.trigger) ? rule.trigger.includes(trigger) : rule.trigger === trigger)); // 处理 rule 规则里面是自定义 validator const activeRules = triggersRules.map(rule => { const shallowClonedRule = Object.assign({}, rule); if (shallowClonedRule.validator) { shallowClonedRule.validator = wrapValidator(shallowClonedRule.validator, false); } if (shallowClonedRule.asyncValidator) { shallowClonedRule.asyncValidator = wrapValidator(shallowClonedRule.asyncValidator, true); } if (!shallowClonedRule.type) { shallowClonedRule.type = ruleDefaultType; } return shallowClonedRule; }); if (!activeRules.length) { return Promise.resolve(); } // 开始规则校验 const descriptor = {}; descriptor[formItemProp.value] = activeRules; const validatorModel = {}; validatorModel[formItemProp.value] = fieldValue.value; const validator = new Schema(descriptor); try { await Promise.resolve(validator.validate(validatorModel)); setValidateInfo(VALIDATE_STATUS.SUCCESS); } catch (errObj) { if (errObj.errors) { const error = errObj.errors[0]; setValidateInfo(VALIDATE_STATUS.ERROR, error.message); return Promise.reject([_objectSpread(_objectSpread({}, error), {}, { message: error.message, descriptor: descriptor[formItemProp.value] || null })]); } } }; // 验证表单项 const validate = async function () { let trigger = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : TRIGGER_TYPE_DEFAULT; try { await validateRules(trigger); } catch (err) {} }; const clearValidate = () => { setValidateInfo(); validateDisabled.value = false; }; const resetField = () => { setValidateInfo(); validateDisabled.value = true; // 在表单重置行为,不应触发校验 // reset initialValue set(model.value, formItemProp.value, cloneDeep(initialValue)); // reset validateDisabled after onFieldChange triggered nextTick(() => { validateDisabled.value = false; }); }; addField(formItemProp.value, { prop: formItemProp.value, value: fieldValue.value, rules: formItemRules.value, validateRules, clearValidate, resetField }); onBeforeUnmount(() => { removeField(formItemProp.value); }); provide(FORM_ITEM_INJECTION_KEY, { validate, setRuleDefaultType, isError: computed(() => { return validateStatus.value === VALIDATE_STATUS.ERROR; }), isFormDisabled: formItemDisabled }); return { prefixCls, formItemClass, formItemLabelClass, formItemLabelStyle, formItemShowMessage, validateMessage, validate, clearValidate, formItemRules }; } }); function render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.formItemClass) }, [_ctx.label || _ctx.$slots.label ? (openBlock(), createElementBlock("span", { key: 0, class: normalizeClass(_ctx.formItemLabelClass), style: normalizeStyle(_ctx.formItemLabelStyle) }, [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(_ctx.label), 1 /* TEXT */)])], 6 /* CLASS, STYLE */)) : createCommentVNode("v-if", true), createElementVNode("div", { class: normalizeClass(`${_ctx.prefixCls}-content`), style: normalizeStyle(_ctx.contentStyle) }, [renderSlot(_ctx.$slots, "default"), createVNode(Transition, { name: "fes-fade" }, { default: withCtx(() => [_ctx.formItemShowMessage ? (openBlock(), createElementBlock("div", { key: 0, class: normalizeClass(`${_ctx.prefixCls}-error`) }, toDisplayString(_ctx.validateMessage), 3 /* TEXT, CLASS */)) : createCommentVNode("v-if", true)]), _: 1 /* STABLE */ })], 6 /* CLASS, STYLE */)], 2 /* CLASS */); } script.render = render; script.__file = "components/form/formItem.vue"; export { script as default };