Skip to content

BasicForm 表单 ​

说明

BasicForm 位于 src/components/Form/src/BasicForm.vue,基于 NaiveUI 的 n-form 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。

组件架构 ​

src/components/Form/
├── src/
│ ├── BasicForm.vue    # 主组件
│ ├── props.ts         # 属性定义
│ ├── helper.ts        # 工具函数(placeholder 生成)
│ ├── types/form.ts    # 类型定义(FormSchema、FormProps)
│ └── hooks/
│ ├── useFormEvents.ts # 表单事件(submit、reset、validate)
│ └── useFormValues.ts # 表单值处理(默认值初始化)
└── index.ts           # 导出 useForm、FormSchema 等

查询表单(列表页搜索) ​

列表页的搜索区域使用 useForm 注册表单:

vue
<n-card :bordered="false" class="pt-3 mb-3 proCard">
    <BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
</n-card>

querySchemas 定义 ​

typescript
// src/views/system/position/querySchemas.ts

import { FormSchema } from '@/components/Form/index';

export const schemas: FormSchema[] = [
    {
        field: 'name',
        component: 'NInput',
        label: '岗位名称',
        componentProps: {
            placeholder: '请输入岗位名称',
        },
    },
    {
        field: 'status',
        component: 'NSelect',
        label: '状态',
        componentProps: {
            placeholder: '请选择状态',
            clearable: true,
            options: [
                { label: '正常', value: '1' },
                { label: '停用', value: '2' },
            ],
        },
    },
];

useForm 注册 ​

typescript
import { schemas } from './querySchemas';
import { useForm } from '@/components/Form/index';

const formParams = reactive({ name: '', status: '' });

const [register, {}] = useForm({
    gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
    labelWidth: 80,
    schemas,
});

function handleSubmit(values: Recordable) {
    for (const key in formParams) { formParams[key] = ''; }
    for (const key in values) { formParams[key] = values[key]; }
    reloadTable();
}

function handleReset(values: Recordable) {
    for (const key in formParams) { formParams[key] = ''; }
    for (const key in values) { formParams[key] = values[key]; }
    reloadTable();
}

useForm 返回 [register, formActionType],register 传给 @register 事件完成表单绑定。

业务表单(编辑弹窗) ​

编辑弹窗使用 basicModal + useModal + n-form 构建表单:

vue
<!-- src/views/system/position/edit.vue -->
<template>
    <basicModal
        @register="modalRegister"
        ref="modalRef"
        class="basicModal basicFormModal"
        @on-ok="handleSubmit"
        @on-close="handleClose"
    >
        <template #default>
            <n-form
                class="ls-form"
                ref="formRef"
                :model="formData"
                label-placement="left"
                label-width="85px"
            >
                <n-form-item
                    label="岗位名称"
                    path="name"
                    :rule="{ required: true, message: '请输入岗位名称', trigger: 'blur' }"
                >
                    <n-input v-model:value="formData.name" placeholder="请输入岗位名称" clearable />
                </n-form-item>
                <n-form-item label="岗位状态" path="status">
                    <n-radio-group v-model:value="formData.status" name="status">
                        <n-radio :value="1">正常</n-radio>
                        <n-radio :value="2">停用</n-radio>
                    </n-radio-group>
                </n-form-item>
                <n-form-item label="排序" path="sort">
                    <n-input-number v-model:value="formData.sort" />
                </n-form-item>
            </n-form>
        </template>
    </basicModal>
</template>

useModal 注册 ​

typescript
import { useModal } from '@/components/Modal';

const [modalRegister, { openModal, setSubLoading }] = useModal({
    title: props.positionId ? '编辑岗位' : '添加岗位',
    subBtuText: '确定',
    width: 600,
});

完整 script 示例 ​

typescript
import { getPositionDetail, positionAdd, positionUpdate } from '@/api/system/position';
import { onMounted, reactive, ref } from 'vue';
import { useMessage } from 'naive-ui';
import { useModal } from '@/components/Modal';

const emit = defineEmits(['success', 'update:visible']);
const formRef = ref();
const message = useMessage();

const formData = reactive({
    id: '',
    name: '',
    status: 1,
    sort: 0,
});

const props = defineProps({
    visible: { type: Boolean, required: true, default: false },
    positionId: { type: Number, required: true, default: 0 },
});

const [modalRegister, { openModal, setSubLoading }] = useModal({
    title: props.positionId ? '编辑岗位' : '添加岗位',
    subBtuText: '确定',
    width: 600,
});

const handleSubmit = async () => {
    formRef.value
        .validate()
        .then(async () => {
            props.positionId ? await positionUpdate(formData) : await positionAdd(formData);
            message.success('操作成功');
            setSubLoading(false);
            emit('update:visible', false);
            emit('success');
        })
        .catch(() => {
            setSubLoading(false);
        });
};

const handleClose = () => {
    emit('update:visible', false);
};

const setFormData = async () => {
    const data = await getPositionDetail(props.positionId);
    for (const key in formData) {
        if (data[key] != null && data[key] != undefined) {
            formData[key] = data[key];
        }
    }
};

onMounted(() => {
    if (props.positionId) setFormData();
});

defineExpose({ openModal });

BasicForm 内部渲染机制 ​

BasicForm.vue 根据 schema.component 动态渲染对应的 NaiveUI 组件:

vue
<!-- src/components/Form/src/BasicForm.vue 核心逻辑 -->
<template v-for="schema in getSchema" :key="schema.field">
    <!-- NCheckbox -->
    <template v-if="schema.component === 'NCheckbox'">
        <n-checkbox-group v-model:value="formModel[schema.field]">
            <n-space>
                <n-checkbox
                    v-for="item in schema.componentProps?.options"
                    :value="item.value"
                    :label="item.label"
                />
            </n-space>
        </n-checkbox-group>
    </template>

    <!-- NRadioGroup -->
    <template v-else-if="schema.component === 'NRadioGroup'">
        <n-radio-group v-model:value="formModel[schema.field]">
            <n-space>
                <n-radio v-for="item in schema.componentProps?.options" :value="item.value">
                    {{ item.label }}
                </n-radio>
            </n-space>
        </n-radio-group>
    </template>

    <!-- NDatePicker(带 valueFormat) -->
    <template v-else-if="schema.component === 'NDatePicker' && schema.componentProps?.valueFormat">
        <n-date-picker
            v-model:formatted-value="formModel[schema.field]"
            v-bind="schema.componentProps"
            clearable
        />
    </template>

    <!-- 动态渲染表单组件 -->
    <component
        v-else
        v-bind="getComponentProps(schema)"
        :is="schema.component"
        :type="schema.type"
        v-model:value="formModel[schema.field]"
    />
</template>

FormSchema 类型 ​

typescript
interface FormSchema {
    field: string;                                                    // 字段名
    label?: string;                                                  // 标签文本
    component?: ComponentType;                                       // 组件类型:NInput / NSelect / NRadioGroup / NCheckbox / NDatePicker 等
    componentProps?: any;                                            // 组件属性(placeholder、options、clearable 等)
    rules?: object | object[];                                       // 自定义校验规则
    defaultValue?: any;                                              // 默认值
    hidden?: boolean | (({ schema, values, model }) => boolean);     // 是否隐藏
    slot?: string;                                                   // 自定义插槽名
    suffix?: string;                                                 // 后缀插槽名
    labelMessage?: string;                                           // 标签提示信息
    isFull?: boolean;                                                // 是否全宽
    type?: string;                                                   // 组件子类型
    giProps?: GridItemProps;                                         // 栅格子项属性
    showFeedback?: boolean;                                          // 是否显示反馈
    labelWidth?: number | string;                                    // 标签宽度
}

表单校验 ​

typescript
// 手动校验
const handleSubmit = async () => {
    formRef.value
        .validate()
        .then(async () => {
            // 校验通过,提交数据
        })
        .catch(() => {
            // 校验失败
        });
};

// 清除校验
formRef.value?.clearValidate();

Schema 中的 required: true 会自动生成必填校验规则,也可以通过 rules 自定义:

typescript
{
    field: 'name',
    label: '案例名称',
    component: 'NInput',
    rules: [
        { required: true, message: '请输入案例名称', trigger: 'blur' },
        { min: 1, max: 150, message: '长度在 1 到 150 个字符', trigger: 'blur' },
    ],
}

在模板中使用 n-form-item 的 rule 属性也可以直接定义校验规则:

vue
<n-form-item
    label="案例名称"
    path="name"
    :rule="{ required: true, message: '请输入案例名称', trigger: 'blur' }"
>
    <n-input v-model:value="formData.name" placeholder="请输入案例名称" clearable />
</n-form-item>

表单赋值 ​

编辑时需要将行数据赋值给表单:

typescript
const formData = reactive({ id: '', name: '', status: 1, sort: 0 });

const setFormData = async () => {
    const data = await getPositionDetail(props.positionId);
    for (const key in formData) {
        if (data[key] != null && data[key] != undefined) {
            formData[key] = data[key];
        }
    }
};

onMounted(() => {
    if (props.positionId) setFormData();
});

n-select 用法 ​

NaiveUI 的 n-select 使用 :options 传递选项数据,无需嵌套 el-option:

vue
<n-select
    v-model:value="formData.type"
    placeholder="请选择案例类型"
    clearable
    filterable
    :options="optionsData.typeList"
/>

选项数据格式:

typescript
const optionsData = reactive({
    typeList: [
        { label: '类型1', value: 1 },
        { label: '类型2', value: 2 },
        { label: '类型3', value: 3 },
        { label: '类型4', value: 4 },
    ],
});

查询表单完整示例(带响应式栅格) ​

typescript
const [register, {}] = useForm({
    gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
    labelWidth: 80,
    schemas,
});

gridProps 使用 NaiveUI 的响应式栅格断点:

  • 1 — 默认每行 1 列
  • s:1 — 小屏 1 列
  • m:2 — 中屏 2 列
  • l:3 — 大屏 3 列
  • xl:4 — 超大屏 4 列
  • 2xl:4 — 超超大屏 4 列

温馨提示

查询表单的字段名需要与后端 page_like_fields 和 page_eq_fields 对应,确保查询参数能正确传递到后端。useForm 返回的 register 函数传给 @register 事件完成表单绑定,formActionType 提供 setFieldsValue、resetFields、validate 等方法。业务弹窗使用 basicModal + useModal 管理弹窗状态,通过 @on-ok 事件处理提交,通过 setSubLoading(false) 关闭提交按钮的 loading 状态。

总结 ​

BasicForm 通过 schemas 配置化定义表单字段,内部根据 component 动态渲染对应的 NaiveUI 组件(NInput、NSelect、NRadioGroup、NCheckbox、NDatePicker 等)。查询表单使用 useForm 注册并绑定 @submit/@reset 事件,业务表单使用 basicModal + useModal + n-form + reactive 管理数据。NaiveUI 的表单组件统一使用 v-model:value 进行双向绑定,n-form-item 使用 path 代替 prop 指定校验字段,使用 rule 代替 rules 指定校验规则。支持多种组件类型、表单校验、数据绑定、条件隐藏、响应式栅格等能力。

小蚂蚁云团队 · 提供技术支持