优化更新
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
`BasicForm` 位于 `src/components/Form/src/BasicForm.vue`,基于 ElementPlus 的 `el-form` 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。
|
||||
`BasicForm` 位于 `src/components/Form/src/BasicForm.vue`,基于 NaiveUI 的 `n-form` 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。
|
||||
:::
|
||||
|
||||
## 组件架构
|
||||
@@ -26,9 +26,9 @@ src/components/Form/
|
||||
列表页的搜索区域使用 `useForm` 注册表单:
|
||||
|
||||
```vue
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<n-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||
</el-card>
|
||||
</n-card>
|
||||
```
|
||||
|
||||
### querySchemas 定义
|
||||
@@ -41,7 +41,7 @@ import { FormSchema } from '@/components/Form/index';
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
component: 'Input',
|
||||
component: 'NInput',
|
||||
label: '岗位名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入岗位名称',
|
||||
@@ -49,14 +49,14 @@ export const schemas: FormSchema[] = [
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'Select',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
clearable: true,
|
||||
options: [
|
||||
{ label: '正常', value: '1' },
|
||||
{ label: '禁用', value: '2' },
|
||||
{ label: '停用', value: '2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -68,26 +68,25 @@ export const schemas: FormSchema[] = [
|
||||
```typescript
|
||||
import { schemas } from './querySchemas';
|
||||
import { useForm } from '@/components/Form/index';
|
||||
import { ColProps } from 'element-plus';
|
||||
|
||||
const formParams = reactive({ name: '', status: '' });
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
layout: 'horizontal',
|
||||
colProps: { span: 6 } as ColProps,
|
||||
submitOnReset: true,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
handleReset();
|
||||
for (const key in formParams) { formParams[key] = ''; }
|
||||
for (const key in values) { formParams[key] = values[key]; }
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
function handleReset(values: Recordable) {
|
||||
for (const key in formParams) { formParams[key] = ''; }
|
||||
for (const key in values) { formParams[key] = values[key]; }
|
||||
reloadTable();
|
||||
}
|
||||
```
|
||||
|
||||
@@ -95,73 +94,173 @@ function handleReset() {
|
||||
|
||||
## 业务表单(编辑弹窗)
|
||||
|
||||
编辑弹窗中直接使用 `el-form` + `el-form-item` 构建表单:
|
||||
编辑弹窗使用 `basicModal` + `useModal` + `n-form` 构建表单:
|
||||
|
||||
```vue
|
||||
<!-- src/views/system/position/edit.vue -->
|
||||
<template>
|
||||
<el-dialog v-model="props.visible" :title="props.positionId ? '编辑' : '新增'" width="500">
|
||||
<el-form class="ls-form" ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item
|
||||
label="岗位名称"
|
||||
prop="name"
|
||||
:rules="{ required: true, message: '请输入岗位名称', trigger: 'blur' }"
|
||||
<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"
|
||||
>
|
||||
<el-input v-model="formData.name" placeholder="请输入岗位名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">正常</el-radio>
|
||||
<el-radio :value="2">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<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` 动态渲染对应的 ElementPlus 组件:
|
||||
`BasicForm.vue` 根据 `schema.component` 动态渲染对应的 NaiveUI 组件:
|
||||
|
||||
```vue
|
||||
<!-- src/components/Form/src/BasicForm.vue 核心逻辑 -->
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<!-- Checkbox -->
|
||||
<template v-if="schema.component === 'Checkbox'">
|
||||
<el-checkbox-group v-model="formModel[schema.field]">
|
||||
<el-checkbox v-for="item in schema.componentProps?.options" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<!-- 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>
|
||||
|
||||
<!-- RadioGroup -->
|
||||
<template v-else-if="schema.component === 'RadioGroup'">
|
||||
<el-radio-group v-model="formModel[schema.field]">
|
||||
<el-radio v-for="item in schema.componentProps?.options" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Select -->
|
||||
<template v-else-if="schema.component === 'Select'">
|
||||
<el-select v-model="formModel[schema.field]">
|
||||
<el-option v-for="item in schema.componentProps?.options" :value="item.value" :label="item.label" />
|
||||
</el-select>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Daterange -->
|
||||
<template v-else-if="schema.component === 'Daterange'">
|
||||
<el-date-picker v-model="formModel[schema.field]" type="daterange" value-format="YYYY-MM-DD" />
|
||||
</template>
|
||||
|
||||
<!-- 其他组件:动态渲染 el-{component} -->
|
||||
<component v-else :is="`el-${schema.component}`" v-model="formModel[schema.field]" />
|
||||
<!-- 动态渲染表单组件 -->
|
||||
<component
|
||||
v-else
|
||||
v-bind="getComponentProps(schema)"
|
||||
:is="schema.component"
|
||||
:type="schema.type"
|
||||
v-model:value="formModel[schema.field]"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -170,11 +269,10 @@ function handleReset() {
|
||||
```typescript
|
||||
interface FormSchema {
|
||||
field: string; // 字段名
|
||||
label: string; // 标签文本
|
||||
component: string; // 组件类型:Input / Select / RadioGroup / Checkbox / DatePicker 等
|
||||
label?: string; // 标签文本
|
||||
component?: ComponentType; // 组件类型:NInput / NSelect / NRadioGroup / NCheckbox / NDatePicker 等
|
||||
componentProps?: any; // 组件属性(placeholder、options、clearable 等)
|
||||
required?: boolean; // 是否必填
|
||||
rules?: any[]; // 自定义校验规则
|
||||
rules?: object | object[]; // 自定义校验规则
|
||||
defaultValue?: any; // 默认值
|
||||
hidden?: boolean | (({ schema, values, model }) => boolean); // 是否隐藏
|
||||
slot?: string; // 自定义插槽名
|
||||
@@ -182,6 +280,9 @@ interface FormSchema {
|
||||
labelMessage?: string; // 标签提示信息
|
||||
isFull?: boolean; // 是否全宽
|
||||
type?: string; // 组件子类型
|
||||
giProps?: GridItemProps; // 栅格子项属性
|
||||
showFeedback?: boolean; // 是否显示反馈
|
||||
labelWidth?: number | string; // 标签宽度
|
||||
}
|
||||
```
|
||||
|
||||
@@ -190,8 +291,14 @@ interface FormSchema {
|
||||
```typescript
|
||||
// 手动校验
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
// 校验通过,提交数据
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
// 校验通过,提交数据
|
||||
})
|
||||
.catch(() => {
|
||||
// 校验失败
|
||||
});
|
||||
};
|
||||
|
||||
// 清除校验
|
||||
@@ -203,15 +310,27 @@ Schema 中的 `required: true` 会自动生成必填校验规则,也可以通
|
||||
```typescript
|
||||
{
|
||||
field: 'name',
|
||||
label: '岗位名称',
|
||||
component: 'Input',
|
||||
label: '案例名称',
|
||||
component: 'NInput',
|
||||
rules: [
|
||||
{ required: true, message: '请输入岗位名称', trigger: 'blur' },
|
||||
{ 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>
|
||||
```
|
||||
|
||||
## 表单赋值
|
||||
|
||||
编辑时需要将行数据赋值给表单:
|
||||
@@ -233,10 +352,55 @@ onMounted(() => {
|
||||
});
|
||||
```
|
||||
|
||||
## 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 列
|
||||
|
||||
:::tip 温馨提示
|
||||
查询表单的字段名需要与后端 `page_like_fields` 和 `page_eq_fields` 对应,确保查询参数能正确传递到后端。`useForm` 返回的 `register` 函数传给 `@register` 事件完成表单绑定,`formActionType` 提供 `setFieldsValue`、`resetFields`、`validate` 等方法。
|
||||
查询表单的字段名需要与后端 `page_like_fields` 和 `page_eq_fields` 对应,确保查询参数能正确传递到后端。`useForm` 返回的 `register` 函数传给 `@register` 事件完成表单绑定,`formActionType` 提供 `setFieldsValue`、`resetFields`、`validate` 等方法。业务弹窗使用 `basicModal` + `useModal` 管理弹窗状态,通过 `@on-ok` 事件处理提交,通过 `setSubLoading(false)` 关闭提交按钮的 loading 状态。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
BasicForm 通过 schemas 配置化定义表单字段,内部根据 `component` 动态渲染对应的 ElementPlus 组件。查询表单使用 `useForm` 注册并绑定 `@submit`/`@reset` 事件,业务表单直接使用 `el-form` + `reactive` 管理数据。支持多种组件类型、表单校验、数据绑定、条件隐藏等能力。
|
||||
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` 指定校验规则。支持多种组件类型、表单校验、数据绑定、条件隐藏、响应式栅格等能力。
|
||||
|
||||
Reference in New Issue
Block a user