sard-uniapp
Version:
sard-uniapp 是一套基于 Uniapp + Vue3 框架开发的兼容多端的 UI 组件库
210 lines (209 loc) • 7.14 kB
JavaScript
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, provide, reactive, ref, toRef, watch, } from 'vue';
import { formItemContextSymbol, useFormContext, } from '../form/common';
import { chainGet, chainSet, deepClone, getBoundingClientRect, getScrollIntoViewValue, getViewportScrollInfo, getWindowInfo, noop, toArray, uniqid, } from '../../utils';
export function useFormItem(props) {
// main
const formContext = useFormContext();
if (!formContext) {
throw new Error('FormItem must be included in Form.');
}
// 用于阻止验证
let isResetting = false;
const fieldValue = computed({
get() {
const model = formContext.model;
if (!model || !props.name) {
return;
}
return chainGet(model, toArray(props.name));
},
set(value) {
const model = formContext.model;
if (!model || !props.name) {
return;
}
chainSet(model, toArray(props.name), value);
},
});
let initialValue;
const validateMessage = ref('');
const validateState = ref('');
watch(() => props.error, () => {
validateMessage.value = props.error || '';
validateState.value = props.error ? 'error' : '';
});
watch(() => props.rules, () => {
if (validateMessage.value) {
validate().catch(noop);
}
}, {
deep: true,
flush: 'post',
});
const shouldShowError = computed(() => {
return (!!props.showError && !!formContext.showError && !!validateMessage.value);
});
const mergedValidateTrigger = computed(() => {
const trigger = props.validateTrigger ?? formContext.validateTrigger;
return trigger ? toArray(trigger) : undefined;
});
const mergedRules = computed(() => {
const rules = [];
if (props.rules) {
rules.push(...toArray(props.rules));
}
const formRules = formContext.rules;
if (formRules && props.name) {
const fRules = chainGet(formRules, toArray(props.name));
if (fRules) {
rules.push(...toArray(fRules));
}
}
const required = props.required;
if (required !== undefined) {
const requiredRules = rules
.map((rule, i) => [rule, i])
.filter(([rule]) => {
return Object.keys(rule).includes('required');
});
if (requiredRules.length > 0) {
for (const [rule, i] of requiredRules) {
if (rule.required !== required) {
rules[i] = { ...rule, required };
}
}
}
else {
rules.push({ required });
}
}
const trigger = mergedValidateTrigger.value;
if (trigger && trigger.length > 0) {
for (let i = 0, l = rules.length; i < l; i++) {
const rule = rules[i];
if (!rule.trigger) {
rules[i] = { ...rule, trigger: [...trigger] };
}
}
}
return rules;
});
const isRequired = computed(() => {
return mergedRules.value.some((rule) => rule.required);
});
const shouldShowStar = computed(() => {
return !formContext.hideStar && !props.hideStar && isRequired.value;
});
const validate = async (trigger) => {
if (isResetting || !props.name) {
return;
}
const validRules = formContext.validator.getValidTriggerRules(mergedRules.value, trigger);
if (validRules.length === 0) {
return;
}
validateState.value = 'validating';
try {
await formContext.validator.validate(mergedRules.value, {
validateFirst: true,
value: fieldValue.value,
name: props.name,
label: props.label,
trigger,
});
validateState.value = 'success';
validateMessage.value = '';
}
catch (messages) {
validateState.value = 'error';
validateMessage.value = messages[0];
const error = {
name: props.name,
value: fieldValue.value,
message: validateMessage.value,
};
throw error;
}
};
const clearValidate = () => {
validateState.value = '';
validateMessage.value = '';
isResetting = false;
};
const reset = async () => {
isResetting = true;
fieldValue.value = deepClone(initialValue);
await nextTick();
clearValidate();
};
const fieldId = uniqid();
const instance = getCurrentInstance();
const scrollToField = async () => {
const [scrollInfo, fieldRect, windowInfo] = await Promise.all([
getViewportScrollInfo(),
getBoundingClientRect(`.${fieldId}`, instance),
getWindowInfo(),
]);
const scrollTop = getScrollIntoViewValue(windowInfo.windowHeight, scrollInfo.scrollTop, fieldRect.height, fieldRect.top + scrollInfo.scrollTop, formContext.scrollIntoViewOptions);
uni.pageScrollTo({
scrollTop,
duration: formContext.scrollIntoViewOptions?.duration ??
formContext.scrollDuration,
});
};
const onBlur = () => {
validate('blur').catch(noop);
};
const onChange = () => {
validate('change').catch(noop);
};
const context = reactive({
name: toRef(() => props.name),
validateMessage,
validateState,
validate,
clearValidate,
reset,
scrollToField,
onBlur,
onChange,
});
onMounted(() => {
if (props.name) {
initialValue = deepClone(fieldValue.value);
formContext.addField(context);
}
});
onBeforeUnmount(() => {
formContext.removeField(context);
});
const direction = computed(() => props.direction || formContext.direction);
const labelAlign = computed(() => props.labelAlign || formContext.labelAlign);
const labelValign = computed(() => props.labelValign || formContext.labelValign);
const starPosition = computed(() => props.starPosition || formContext.starPosition);
const labelWidth = computed(() => props.labelWidth || formContext.labelWidth);
const contentPosition = computed(() => props.contentPosition ?? formContext.contentPosition);
provide(formItemContextSymbol, context);
const expose = {
validate,
reset,
clearValidate,
scrollToField,
validateMessage,
validateState,
};
return {
expose,
fieldId,
validateState,
shouldShowStar,
validateMessage,
shouldShowError,
direction,
labelAlign,
labelValign,
starPosition,
labelWidth,
contentPosition,
};
}