QueryFormItem
基于
QueryForm的装饰器组件,用于按条件请求并更新当前字段的dataSource。
SelectTable + 分页
<script setup lang="ts">
import type { ISchema } from '@formily/json-schema'
import { createForm } from '@formily/core'
import { QueryFormItem, SelectTable } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton, ElMessage } from 'element-plus'
import { createUserRequest } from './mock-user-request'
const form = createForm()
const request = createUserRequest()
const querySchema: ISchema = {
type: 'object',
properties: {
keyword: {
'type': 'string',
'title': 'Keyword',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
clearable: true,
placeholder: 'Search by name',
},
},
department: {
'type': 'string',
'title': 'Department',
'enum': [
{ label: 'All', value: '' },
{ label: 'R&D', value: 'R&D' },
{ label: 'Product', value: 'Product' },
],
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
clearable: true,
},
},
},
}
async function handleSubmit() {
try {
const values = await form.submit()
ElMessage.success(`Submit: ${JSON.stringify(values)}`)
}
catch {
ElMessage.error('Please select at least one user before submit')
}
}
const schema: ISchema = {
type: 'object',
properties: {
selectedUsers: {
'type': 'array',
'x-validator': [
{
required: true,
message: 'Please select at least one user',
},
],
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
label: 'Target Users',
required: true,
tooltip: 'Select at least one user from the table',
extra: 'Use the query area to filter the table before selecting.',
querySchema,
request,
paginationProps: {
pageSize: 8,
},
queryFormProps: {
submitText: 'Search',
resetText: 'Reset',
},
},
'x-component': 'SelectTable',
'x-component-props': {
mode: 'multiple',
rowKey: 'id',
columns: [
{ prop: 'name', label: 'Name' },
{ prop: 'department', label: 'Department' },
],
},
},
},
}
const { SchemaField } = createSchemaField({
components: {
QueryFormItem,
SelectTable,
},
})
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<ElButton type="primary" style="margin-top: 12px;" @click="handleSubmit">
Submit
</ElButton>
</FormProvider>
</template>Target Users:
No Data
Total 0
- 1
Use the query area to filter the table before selecting.
查看源码
Tree + 轻量模式(无分页)
<script setup lang="ts">
import type { ISchema } from '@formily/json-schema'
import { createForm } from '@formily/core'
import { QueryFormItem, Tree } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton, ElMessage } from 'element-plus'
interface TreeNode {
id: number
label: string
children?: TreeNode[]
}
const form = createForm()
const source: TreeNode[] = [
{
id: 1,
label: 'East Region',
children: [
{ id: 11, label: 'Shanghai' },
{ id: 12, label: 'Hangzhou' },
],
},
{
id: 2,
label: 'South Region',
children: [
{ id: 21, label: 'Shenzhen' },
{ id: 22, label: 'Guangzhou' },
],
},
]
function filterTree(nodes: TreeNode[], keyword: string): TreeNode[] {
if (!keyword)
return nodes
return nodes
.map((node) => {
const children = node.children ? filterTree(node.children, keyword) : undefined
if (node.label.includes(keyword) || (children && children.length > 0)) {
return { ...node, children }
}
return null
})
.filter(Boolean) as TreeNode[]
}
const querySchema: ISchema = {
type: 'object',
properties: {
keyword: {
'type': 'string',
'title': 'Keyword',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
clearable: true,
placeholder: 'Filter node name',
},
},
},
}
async function request(values: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, 150))
const keyword = `${values.keyword ?? ''}`.trim()
const data = filterTree(source, keyword)
return {
data,
success: true,
total: data.length,
}
}
async function handleSubmit() {
try {
const values = await form.submit()
ElMessage.success(`Submit: ${JSON.stringify(values)}`)
}
catch {
ElMessage.error('Please select at least one node before submit')
}
}
const schema: ISchema = {
type: 'object',
properties: {
selectedNodes: {
'type': 'array',
'x-validator': [
{
required: true,
message: 'Please select at least one node',
},
],
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
label: 'Target Nodes',
required: true,
tooltip: 'Select at least one node from the tree',
extra: 'Light mode still supports FormItem label/required/feedback.',
querySchema,
request,
mode: 'light',
pagination: false,
queryFormProps: {
throttleWait: 200,
},
},
'x-component': 'Tree',
'x-component-props': {
nodeKey: 'id',
maxHeight: 260,
},
},
},
}
const { SchemaField } = createSchemaField({
components: {
QueryFormItem,
Tree,
},
})
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<ElButton type="primary" style="margin-top: 12px;" @click="handleSubmit">
Submit
</ElButton>
</FormProvider>
</template>Target Nodes:
No Data
Light mode still supports FormItem label/required/feedback.
查看源码
注册自定义组件(Segmented)
<script setup lang="ts">
import type { ISchema } from '@formily/json-schema'
import { createForm } from '@formily/core'
import { QueryFormItem, Segmented, SelectTable } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton, ElMessage } from 'element-plus'
import { createUserRequest } from './mock-user-request'
const form = createForm()
const request = createUserRequest()
const querySchema: ISchema = {
type: 'object',
properties: {
department: {
'type': 'string',
'x-decorator': 'FormItem',
'x-component': 'Segmented',
'enum': [
{ label: 'All', value: '' },
{ label: 'R&D', value: 'R&D' },
{ label: 'Product', value: 'Product' },
],
'default': '',
},
},
}
async function handleSubmit() {
try {
const values = await form.submit()
ElMessage.success(`Submit: ${JSON.stringify(values)}`)
}
catch {
ElMessage.error('Please select at least one user before submit')
}
}
const schema: ISchema = {
type: 'object',
properties: {
selectedUsers: {
'type': 'array',
'x-validator': [
{
required: true,
message: 'Please select at least one user',
},
],
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
label: 'Target Users',
required: true,
extra: '通过 queryFormProps.components 注册 Segmented 后,可在 querySchema 中直接使用。',
querySchema,
request,
mode: 'light',
paginationProps: {
pageSize: 8,
},
queryFormProps: {
throttleWait: 200,
components: {
Segmented,
},
},
},
'x-component': 'SelectTable',
'x-component-props': {
mode: 'multiple',
rowKey: 'id',
columns: [
{ prop: 'name', label: 'Name' },
{ prop: 'department', label: 'Department' },
],
},
},
},
}
const { SchemaField } = createSchemaField({
components: {
QueryFormItem,
SelectTable,
},
})
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<ElButton type="primary" style="margin-top: 12px;" @click="handleSubmit">
Submit
</ElButton>
</FormProvider>
</template>Target Users:
No Data
Total 0
- 1
通过 queryFormProps.components 注册 Segmented 后,可在 querySchema 中直接使用。
查看源码
通过外部传入 Form 设置初始值
注意
在Decorator中如果需要传入form需要使用函数返回的写法,这是因为Decorator中的props会经过toJS处理,会引起组件循环渲染。具体写法见下。
<script setup lang="ts">
import type { ISchema } from '@formily/json-schema'
import { createForm } from '@formily/core'
import { QueryFormItem, SelectTable } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton, ElMessage } from 'element-plus'
import { createUserRequest } from './mock-user-request'
const form = createForm()
const request = createUserRequest()
// External query form instance with initial query params.
const queryForm = createForm({
initialValues: {
keyword: 'User-1',
department: 'R&D',
},
})
const querySchema: ISchema = {
type: 'object',
properties: {
keyword: {
'type': 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
clearable: true,
placeholder: 'Keyword',
},
},
department: {
'type': 'string',
'enum': [
{ label: 'All', value: '' },
{ label: 'R&D', value: 'R&D' },
{ label: 'Product', value: 'Product' },
],
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
clearable: true,
placeholder: 'Department',
style: 'width: 120px;',
},
},
},
}
async function handleSubmit() {
const values = await form.submit()
ElMessage.success(`Submit: ${JSON.stringify(values)}`)
}
const schema: ISchema = {
type: 'object',
properties: {
selectedUsers: {
'type': 'array',
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
mode: 'light',
label: '',
extra: 'Light mode query area uses an external form with initial values.',
request,
querySchema,
queryFormProps: {
form: () => queryForm,
throttleWait: 200,
},
},
'x-component': 'SelectTable',
'x-component-props': {
mode: 'multiple',
rowKey: 'id',
columns: [
{ prop: 'name', label: 'Name' },
{ prop: 'department', label: 'Department' },
],
},
},
},
}
const { SchemaField } = createSchemaField({
components: {
QueryFormItem,
SelectTable,
},
})
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<ElButton type="primary" style="margin-top: 12px;" @click="handleSubmit">
Submit
</ElButton>
</FormProvider>
</template>No Data
Total 0
- 1
Light mode query area uses an external form with initial values.
查看源码
Transfer + 条件变化清空已选值
<script setup lang="ts">
import type { ISchema } from '@formily/json-schema'
import { createForm } from '@formily/core'
import { QueryFormItem, Transfer } from '@silver-formily/element-plus'
import { createSchemaField, FormProvider } from '@silver-formily/vue'
import { ElButton, ElMessage } from 'element-plus'
import { createPermissionRequest } from './mock-user-request'
const form = createForm()
const request = createPermissionRequest()
const querySchema: ISchema = {
type: 'object',
properties: {
keyword: {
'type': 'string',
'title': '关键词',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
clearable: true,
placeholder: '按权限名称过滤',
},
},
module: {
'type': 'string',
'title': '模块',
'enum': [
{ label: '全部', value: '' },
{ label: '用户', value: 'user' },
{ label: '订单', value: 'order' },
{ label: '财务', value: 'finance' },
],
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
clearable: true,
style: 'width: 130px;',
},
},
},
}
async function handleSubmit() {
try {
const values = await form.submit()
ElMessage.success(`Submit: ${JSON.stringify(values)}`)
}
catch {
ElMessage.error('请先选择至少一项权限')
}
}
const schema: ISchema = {
type: 'object',
properties: {
selectedPermissions: {
'type': 'array',
'x-validator': [
{
required: true,
message: '请选择至少一项权限',
},
],
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
label: '',
required: true,
querySchema,
request,
pagination: false,
clearOnDataChange: true,
extra: '修改过滤条件并点击查询后,会自动清空已选择的数据。',
},
'x-component': 'Transfer',
'x-component-props': {
titles: ['可选权限', '已选权限'],
filterable: true,
},
},
},
}
const { SchemaField } = createSchemaField({
components: {
QueryFormItem,
Transfer,
},
})
</script>
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<ElButton type="primary" style="margin-top: 12px;" @click="handleSubmit">
Submit
</ElButton>
</FormProvider>
</template>No data
No data
修改过滤条件并点击查询后,会自动清空已选择的数据。
查看源码
API
QueryFormItem Props
基本继承了所有 FormItem 的配置项。为了避免校验失败时报错的样式影响到内部的QueryForm,组件修改了FormItem的class名,因此可能会出现部分配置项不生效的情况。下面的这些配置项是 QueryFormItem 独有的。
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
mode | 查询模式 | 'default' | 'light' | 'default' |
request | 查询函数; | Request 约定 | - |
clearOnDataChange | 查询成功后是否清空当前字段值 | boolean | false |
querySchema | 等价于queryFormProps.schema | ISchema | - |
queryFormProps | 查询表单配置 | QueryFormItemQueryProps | 参考QueryForm默认值 |
pagination | 是否启用分页 | boolean | true |
paginationProps | 分页配置,透传给 ElPagination | 参考Element-plus 官方文档 | 略 |
paginationMap | 分页参数映射(用于请求入参键名) | QueryFormItemPaginationMap | 分页参数映射 |
immediate | 挂载后是否立即执行一次查询 | boolean | true |
事件
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
requestSuccess | (payload: QueryFormItemRequestSuccessPayload) => void | 查询成功后触发 | - |
requestFailed | (error: any) => void | 查询失败后触发 | - |
Request 约定
如果启用分页,那么request的入参除了QueryForm中获取的值之外还会额外传入 current 与 pageSize。可以通过paginationMap参数配置映射。
request 必须返回以下格式(参考 ProTable):
ts
interface QueryResult {
data: any[]
success: boolean
total?: number
}success必须为true才会解析data到字段dataSource。total不传时默认使用data.length,分页场景建议显式返回。
分页参数映射
默认分页参数键名为 current 和 pageSize。如果后端需要其他键名,可通过 paginationMap 配置:
ts
const props = {
paginationMap: {
current: 'pageNum',
pageSize: 'pageSize',
},
}