更新最新代码

This commit is contained in:
luob
2025-12-24 23:48:38 +08:00
parent e728cf2c5e
commit 1fd17ef73a
1320 changed files with 83513 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
import type { Component } from 'vue';
import type {
BaseFormComponentType,
FormCommonConfig,
VbenFormAdapterOptions,
} from './types';
import { h } from 'vue';
import {
VbenButton,
VbenCheckbox,
Input as VbenInput,
VbenInputPassword,
VbenPinInput,
VbenSelect,
} from '@vben-core/shadcn-ui';
import { globalShareState } from '@vben-core/shared/global-state';
import { defineRule } from 'vee-validate';
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),
VbenCheckbox,
VbenInput,
VbenInputPassword,
VbenPinInput,
VbenSelect,
};
export const COMPONENT_BIND_EVENT_MAP: Partial<
Record<BaseFormComponentType, string>
> = {
VbenCheckbox: 'checked',
};
export function setupVbenForm<
T extends BaseFormComponentType = BaseFormComponentType,
>(options: VbenFormAdapterOptions<T>) {
const { config, defineRules } = options;
const {
disabledOnChangeListener = true,
disabledOnInputListener = true,
emptyStateValue = undefined,
} = (config || {}) as FormCommonConfig;
Object.assign(DEFAULT_FORM_COMMON_CONFIG, {
disabledOnChangeListener,
disabledOnInputListener,
emptyStateValue,
});
if (defineRules) {
for (const key of Object.keys(defineRules)) {
defineRule(key, defineRules[key as never]);
}
}
const baseModelPropName =
config?.baseModelPropName ?? DEFAULT_MODEL_PROP_NAME;
const modelPropNameMap = config?.modelPropNameMap as
| Record<BaseFormComponentType, string>
| undefined;
const components = globalShareState.getComponents();
for (const component of Object.keys(components)) {
const key = component as BaseFormComponentType;
COMPONENT_MAP[key] = components[component as never];
if (baseModelPropName !== DEFAULT_MODEL_PROP_NAME) {
COMPONENT_BIND_EVENT_MAP[key] = baseModelPropName;
}
// 覆盖特殊组件的modelPropName
if (modelPropNameMap && modelPropNameMap[key]) {
COMPONENT_BIND_EVENT_MAP[key] = modelPropNameMap[key];
}
}
}

View File

@@ -0,0 +1,24 @@
import type { FormRenderProps } from '../types';
import { computed } from 'vue';
import { createContext } from '@vben-core/shadcn-ui';
export const [injectRenderFormProps, provideFormRenderProps] =
createContext<FormRenderProps>('FormRenderProps');
export const useFormContext = () => {
const formRenderProps = injectRenderFormProps();
const isVertical = computed(() => formRenderProps.layout === 'vertical');
const componentMap = computed(() => formRenderProps.componentMap);
const componentBindEventMap = computed(
() => formRenderProps.componentBindEventMap,
);
return {
componentBindEventMap,
componentMap,
isVertical,
};
};

View File

@@ -0,0 +1,124 @@
import type {
FormItemDependencies,
FormSchemaRuleType,
MaybeComponentProps,
} from '../types';
import { computed, ref, watch } from 'vue';
import { isBoolean, isFunction } from '@vben-core/shared/utils';
import { useFormValues } from 'vee-validate';
import { injectRenderFormProps } from './context';
export default function useDependencies(
getDependencies: () => FormItemDependencies | undefined,
) {
const values = useFormValues();
const formRenderProps = injectRenderFormProps();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const formApi = formRenderProps.form!;
if (!values) {
throw new Error('useDependencies should be used within <VbenForm>');
}
const isIf = ref(true);
const isDisabled = ref(false);
const isShow = ref(true);
const isRequired = ref(false);
const dynamicComponentProps = ref<MaybeComponentProps>({});
const dynamicRules = ref<FormSchemaRuleType>();
const triggerFieldValues = computed(() => {
// 该字段可能会被多个字段触发
const triggerFields = getDependencies()?.triggerFields ?? [];
return triggerFields.map((dep) => {
return values.value[dep];
});
});
const resetConditionState = () => {
isDisabled.value = false;
isIf.value = true;
isShow.value = true;
isRequired.value = false;
dynamicRules.value = undefined;
dynamicComponentProps.value = {};
};
watch(
[triggerFieldValues, getDependencies],
async ([_values, dependencies]) => {
if (!dependencies || !dependencies?.triggerFields?.length) {
return;
}
resetConditionState();
const {
componentProps,
disabled,
if: whenIf,
required,
rules,
show,
trigger,
} = dependencies;
// 1. 优先判断if如果if为false则不渲染dom后续判断也不再执行
const formValues = values.value;
if (isFunction(whenIf)) {
isIf.value = !!(await whenIf(formValues, formApi));
// 不渲染
if (!isIf.value) return;
} else if (isBoolean(whenIf)) {
isIf.value = whenIf;
if (!isIf.value) return;
}
// 2. 判断show如果show为false则隐藏
if (isFunction(show)) {
isShow.value = !!(await show(formValues, formApi));
if (!isShow.value) return;
} else if (isBoolean(show)) {
isShow.value = show;
if (!isShow.value) return;
}
if (isFunction(componentProps)) {
dynamicComponentProps.value = await componentProps(formValues, formApi);
}
if (isFunction(rules)) {
dynamicRules.value = await rules(formValues, formApi);
}
if (isFunction(disabled)) {
isDisabled.value = !!(await disabled(formValues, formApi));
} else if (isBoolean(disabled)) {
isDisabled.value = disabled;
}
if (isFunction(required)) {
isRequired.value = !!(await required(formValues, formApi));
}
if (isFunction(trigger)) {
await trigger(formValues, formApi);
}
},
{ deep: true, immediate: true },
);
return {
dynamicComponentProps,
dynamicRules,
isDisabled,
isIf,
isRequired,
isShow,
};
}

View File

@@ -0,0 +1,105 @@
import type { FormRenderProps } from '../types';
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
import {
breakpointsTailwind,
useBreakpoints,
useElementVisibility,
} from '@vueuse/core';
/**
* 动态计算行数
*/
export function useExpandable(props: FormRenderProps) {
const wrapperRef = useTemplateRef<HTMLElement>('wrapperRef');
const isVisible = useElementVisibility(wrapperRef);
const rowMapping = ref<Record<number, number>>({});
// 是否已经计算过一次
const isCalculated = ref(false);
const breakpoints = useBreakpoints(breakpointsTailwind);
const keepFormItemIndex = computed(() => {
const rows = props.collapsedRows ?? 1;
const mapping = rowMapping.value;
let maxItem = 0;
for (let index = 1; index <= rows; index++) {
maxItem += mapping?.[index] ?? 0;
}
// 保持一行
return maxItem - 1 || 1;
});
watch(
[
() => props.showCollapseButton,
() => breakpoints.active().value,
() => props.schema?.length,
() => isVisible.value,
],
async ([val]) => {
if (val) {
await nextTick();
rowMapping.value = {};
isCalculated.value = false;
await calculateRowMapping();
}
},
);
async function calculateRowMapping() {
if (!props.showCollapseButton) {
return;
}
await nextTick();
if (!wrapperRef.value) {
return;
}
// 小屏幕不计算
// if (breakpoints.smaller('sm').value) {
// // 保持一行
// rowMapping.value = { 1: 2 };
// return;
// }
const formItems = [...wrapperRef.value.children];
const container = wrapperRef.value;
const containerStyles = window.getComputedStyle(container);
const rowHeights = containerStyles
.getPropertyValue('grid-template-rows')
.split(' ');
const containerRect = container?.getBoundingClientRect();
formItems.forEach((el) => {
const itemRect = el.getBoundingClientRect();
// 计算元素在第几行
const itemTop = itemRect.top - containerRect.top;
let rowStart = 0;
let cumulativeHeight = 0;
for (const [i, rowHeight] of rowHeights.entries()) {
cumulativeHeight += Number.parseFloat(rowHeight);
if (itemTop < cumulativeHeight) {
rowStart = i + 1;
break;
}
}
if (rowStart > (props?.collapsedRows ?? 1)) {
return;
}
rowMapping.value[rowStart] = (rowMapping.value[rowStart] ?? 0) + 1;
isCalculated.value = true;
});
}
onMounted(() => {
calculateRowMapping();
});
return { isCalculated, keepFormItemIndex, wrapperRef };
}

View File

@@ -0,0 +1,60 @@
import type {
AnyZodObject,
ZodDefault,
ZodEffects,
ZodNumber,
ZodString,
ZodTypeAny,
} from 'zod';
import { isObject, isString } from '@vben-core/shared/utils';
/**
* Get the lowest level Zod type.
* This will unpack optionals, refinements, etc.
*/
export function getBaseRules<
ChildType extends AnyZodObject | ZodTypeAny = ZodTypeAny,
>(schema: ChildType | ZodEffects<ChildType>): ChildType | null {
if (!schema || isString(schema)) return null;
if ('innerType' in schema._def)
return getBaseRules(schema._def.innerType as ChildType);
if ('schema' in schema._def)
return getBaseRules(schema._def.schema as ChildType);
return schema as ChildType;
}
/**
* Search for a "ZodDefault" in the Zod stack and return its value.
*/
export function getDefaultValueInZodStack(schema: ZodTypeAny): any {
if (!schema || isString(schema)) {
return;
}
const typedSchema = schema as unknown as ZodDefault<ZodNumber | ZodString>;
if (typedSchema._def.typeName === 'ZodDefault')
return typedSchema._def.defaultValue();
if ('innerType' in typedSchema._def) {
return getDefaultValueInZodStack(
typedSchema._def.innerType as unknown as ZodTypeAny,
);
}
if ('schema' in typedSchema._def) {
return getDefaultValueInZodStack(
(typedSchema._def as any).schema as ZodTypeAny,
);
}
return undefined;
}
export function isEventObjectLike(obj: any) {
if (!obj || !isObject(obj)) {
return false;
}
return Reflect.has(obj, 'target') && Reflect.has(obj, 'stopPropagation');
}

View File

@@ -0,0 +1,3 @@
export { default as FormField } from './form-field.vue';
export { default as FormLabel } from './form-label.vue';
export { default as Form } from './form.vue';

View File

@@ -0,0 +1,12 @@
export { setupVbenForm } from './config';
export type {
BaseFormComponentType,
ExtendedFormApi,
VbenFormProps,
FormSchema as VbenFormSchema,
} from './types';
export * from './use-vben-form';
// export { default as VbenForm } from './vben-form.vue';
export * as z from 'zod';

View File

@@ -0,0 +1,50 @@
import type {
BaseFormComponentType,
ExtendedFormApi,
VbenFormProps,
} from './types';
import { defineComponent, h, isReactive, onBeforeUnmount, watch } from 'vue';
import { useStore } from '@vben-core/shared/store';
import { FormApi } from './form-api';
import VbenUseForm from './vben-use-form.vue';
export function useVbenForm<
T extends BaseFormComponentType = BaseFormComponentType,
>(options: VbenFormProps<T>) {
const IS_REACTIVE = isReactive(options);
const api = new FormApi(options);
const extendedApi: ExtendedFormApi = api as never;
extendedApi.useStore = (selector) => {
return useStore(api.store, selector);
};
const Form = defineComponent(
(props: VbenFormProps, { attrs, slots }) => {
onBeforeUnmount(() => {
api.unmount();
});
api.setState({ ...props, ...attrs });
return () =>
h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);
},
{
name: 'VbenUseForm',
inheritAttrs: false,
},
);
// Add reactivity support
if (IS_REACTIVE) {
watch(
() => options.schema,
() => {
api.setState({ schema: options.schema });
},
{ immediate: true },
);
}
return [Form, extendedApi] as const;
}