refactor: 优化 SafeNFormItem 组件的类型定义和插槽处理
Some checks failed
CI/CD Pipeline / playwright (push) Successful in 2m38s
CI/CD Pipeline / build-and-deploy (push) Failing after 3m13s

This commit is contained in:
严浩
2025-10-31 11:14:13 +08:00
parent 4c7f052ea1
commit 18cb623730
2 changed files with 28 additions and 25 deletions

View File

@@ -63,10 +63,10 @@ function handleValidateClick() {
<SafeNFormItem #default="slotProps" path="user.name">
<div class="border">
<pre>{{ JSON.stringify({ slotProps }, null, 2) }}</pre>
<!-- <pre>{{ JSON.stringify({ slotProps: toRaw(slotProps) }, null, 2) }}</pre> -->
<div>v: {{ slotProps.value }}</div>
</div>
<NInput :value="slotProps.value" placeholder="SafeNFormItem" />
<NInput v-model:value="slotProps.value" placeholder="SafeNFormItem" />
</SafeNFormItem>
<n-form-item>

View File

@@ -1,7 +1,7 @@
// https://www.naiveui.com/zh-CN/os-theme/components/form
// initialFormValue
import { get } from 'lodash-es';
import { get, set } from 'lodash-es';
import type { FormInst } from 'naive-ui';
import type { Get, Paths } from 'type-fest';
import type { SlotsType } from 'vue';
@@ -48,45 +48,36 @@ export function useSafeNForm<T extends Record<string, any> = Record<string, unkn
// <<<<<
// >>>>> 创建类型安全的 FormItem 组件
type SafeNFormItemProps<P extends Paths<T> & string> = {
label?: string;
path: P;
placeholder?: string;
};
type SafeNFormItemSlotScope<P extends Paths<T> & string> = {
type SafeNFormItemDefaultSlot<P extends Paths<T> & string> = {
value: Get<T, P>;
};
type SafeNFormItemSlotsDefinition<P extends Paths<T> & string> = SlotsType<{
default: SafeNFormItemSlotScope<P>;
}>;
type SafeNFormItemSlotFns<P extends Paths<T> & string> = {
default?: (scope: SafeNFormItemSlotScope<P>) => any;
};
type SafeNFormItemComponent = new <P extends Paths<T> & string>(
props: SafeNFormItemProps<P>,
) => {
$props: SafeNFormItemProps<P>;
$slots: SafeNFormItemSlotFns<P>;
};
const SafeNFormItemImpl = defineComponent<
SafeNFormItemProps<Paths<T> & string>,
/* Emits */ [],
/* EE */ never,
SafeNFormItemSlotsDefinition<Paths<T> & string>
SlotsType<{ default: SafeNFormItemDefaultSlot<Paths<T> & string> }>
>(
(props, ctx) => {
return () => {
const value = get(formValue.value, props.path) as Get<T, typeof props.path>;
const slots = ctx.slots;
// const value = get(formValue.value, props.path) as Get<T, typeof props.path>;
const value = computed({
get() {
return get(formValue.value, props.path) as Get<T, typeof props.path>;
},
set(v) {
set(formValue.value, props.path, v);
},
});
return (
<NFormItem path={props.path} label={props.label}>
{slots.default?.({ value })}
{ctx.slots.default?.({ value: value.value })}
</NFormItem>
);
};
@@ -97,7 +88,19 @@ export function useSafeNForm<T extends Record<string, any> = Record<string, unkn
props: ['label', 'path', 'placeholder'],
},
);
const SafeNFormItem = SafeNFormItemImpl as unknown as SafeNFormItemComponent;
// Expose a generic constructor so template literals narrow `path`.
type SafeNFormItemComponent = {
new <P extends Paths<T> & string>(
props: SafeNFormItemProps<P>,
): {
$props: SafeNFormItemProps<P>;
$slots: {
default?: (scope: SafeNFormItemDefaultSlot<P>) => VNode[];
};
};
};
const SafeNFormItem = SafeNFormItemImpl as SafeNFormItemComponent;
// <<<<<
return {