QueryFormItem
A decorator component built on
QueryFormthat requests and updates the current field'sdataSourcebased on conditions.
SelectTable with Pagination Example
<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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>export interface UserRecord {
id: string
name: string
department: string
}
export interface PermissionRecord {
key: string
label: string
module: string
}
export const userSource: UserRecord[] = Array.from({ length: 80 }, (_, index) => ({
id: `${index + 1}`,
name: `User-${index + 1}`,
department: index % 2 === 0 ? 'R&D' : 'Product',
}))
export const permissionSource: PermissionRecord[] = [
{ key: 'user:read', label: 'View Users', module: 'user' },
{ key: 'user:edit', label: 'Edit Users', module: 'user' },
{ key: 'order:read', label: 'View Orders', module: 'order' },
{ key: 'order:refund', label: 'Order Refunds', module: 'order' },
{ key: 'finance:read', label: 'View Bills', module: 'finance' },
{ key: 'finance:export', label: 'Export Bills', module: 'finance' },
]
export function createUserRequest(delay = 200) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const department = `${params.department ?? ''}`.trim()
const filtered = userSource.filter((item) => {
const keywordMatched = keyword ? item.name.toLowerCase().includes(keyword) : true
const departmentMatched = department ? item.department === department : true
return keywordMatched && departmentMatched
})
const current = Number(params.current) || 1
const pageSize = Number(params.pageSize) || 10
const start = (current - 1) * pageSize
const end = current * pageSize
return {
data: filtered.slice(start, end),
success: true,
total: filtered.length,
}
}
}
export function createPermissionRequest(delay = 150) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const module = `${params.module ?? ''}`.trim()
const filtered = permissionSource.filter((item) => {
const keywordMatched = !keyword
|| item.label.toLowerCase().includes(keyword)
|| item.key.includes(keyword)
const moduleMatched = !module || item.module === module
return keywordMatched && moduleMatched
})
return {
data: filtered,
success: true,
total: filtered.length,
}
}
}<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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>- 1
Tree with Light Mode and No Pagination Example
<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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>Custom Component Registration Example (Segmented)
<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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: 'After registering Segmented through queryFormProps.components, you can use it directly in 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>export interface UserRecord {
id: string
name: string
department: string
}
export interface PermissionRecord {
key: string
label: string
module: string
}
export const userSource: UserRecord[] = Array.from({ length: 80 }, (_, index) => ({
id: `${index + 1}`,
name: `User-${index + 1}`,
department: index % 2 === 0 ? 'R&D' : 'Product',
}))
export const permissionSource: PermissionRecord[] = [
{ key: 'user:read', label: 'View Users', module: 'user' },
{ key: 'user:edit', label: 'Edit Users', module: 'user' },
{ key: 'order:read', label: 'View Orders', module: 'order' },
{ key: 'order:refund', label: 'Order Refunds', module: 'order' },
{ key: 'finance:read', label: 'View Bills', module: 'finance' },
{ key: 'finance:export', label: 'Export Bills', module: 'finance' },
]
export function createUserRequest(delay = 200) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const department = `${params.department ?? ''}`.trim()
const filtered = userSource.filter((item) => {
const keywordMatched = keyword ? item.name.toLowerCase().includes(keyword) : true
const departmentMatched = department ? item.department === department : true
return keywordMatched && departmentMatched
})
const current = Number(params.current) || 1
const pageSize = Number(params.pageSize) || 10
const start = (current - 1) * pageSize
const end = current * pageSize
return {
data: filtered.slice(start, end),
success: true,
total: filtered.length,
}
}
}
export function createPermissionRequest(delay = 150) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const module = `${params.module ?? ''}`.trim()
const filtered = permissionSource.filter((item) => {
const keywordMatched = !keyword
|| item.label.toLowerCase().includes(keyword)
|| item.key.includes(keyword)
const moduleMatched = !module || item.module === module
return keywordMatched && moduleMatched
})
return {
data: filtered,
success: true,
total: filtered.length,
}
}
}<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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: 'After registering Segmented through queryFormProps.components, you can use it directly in 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>- 1
External Form Initial Values Example
Note
If you need to pass form inside the decorator, use a function that returns the props object. That is because props inside decorators go through toJS, which would otherwise cause repeated component re-renders. See the example below for the exact pattern.
<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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>- 1
Transfer with Clearing on Condition Changes Example
<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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': 'Keyword',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
clearable: true,
placeholder: 'Filter by permission name',
},
},
module: {
'type': 'string',
'title': 'Module',
'enum': [
{ label: 'All', value: '' },
{ label: 'Users', value: 'user' },
{ label: 'Orders', value: 'order' },
{ label: 'Finance', 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('Please select at least one permission first')
}
}
const schema: ISchema = {
type: 'object',
properties: {
selectedPermissions: {
'type': 'array',
'x-validator': [
{
required: true,
message: 'Please select at least one permission',
},
],
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
label: '',
required: true,
querySchema,
request,
pagination: false,
clearOnDataChange: true,
extra: 'Changing the filter criteria and clicking Search will automatically clear the selected data.',
},
'x-component': 'Transfer',
'x-component-props': {
titles: ['Available Permissions', 'Selected Permissions'],
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>export interface UserRecord {
id: string
name: string
department: string
}
export interface PermissionRecord {
key: string
label: string
module: string
}
export const userSource: UserRecord[] = Array.from({ length: 80 }, (_, index) => ({
id: `${index + 1}`,
name: `User-${index + 1}`,
department: index % 2 === 0 ? 'R&D' : 'Product',
}))
export const permissionSource: PermissionRecord[] = [
{ key: 'user:read', label: 'View Users', module: 'user' },
{ key: 'user:edit', label: 'Edit Users', module: 'user' },
{ key: 'order:read', label: 'View Orders', module: 'order' },
{ key: 'order:refund', label: 'Order Refunds', module: 'order' },
{ key: 'finance:read', label: 'View Bills', module: 'finance' },
{ key: 'finance:export', label: 'Export Bills', module: 'finance' },
]
export function createUserRequest(delay = 200) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const department = `${params.department ?? ''}`.trim()
const filtered = userSource.filter((item) => {
const keywordMatched = keyword ? item.name.toLowerCase().includes(keyword) : true
const departmentMatched = department ? item.department === department : true
return keywordMatched && departmentMatched
})
const current = Number(params.current) || 1
const pageSize = Number(params.pageSize) || 10
const start = (current - 1) * pageSize
const end = current * pageSize
return {
data: filtered.slice(start, end),
success: true,
total: filtered.length,
}
}
}
export function createPermissionRequest(delay = 150) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const module = `${params.module ?? ''}`.trim()
const filtered = permissionSource.filter((item) => {
const keywordMatched = !keyword
|| item.label.toLowerCase().includes(keyword)
|| item.key.includes(keyword)
const moduleMatched = !module || item.module === module
return keywordMatched && moduleMatched
})
return {
data: filtered,
success: true,
total: filtered.length,
}
}
}<script setup lang="ts">
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-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': 'Keyword',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
clearable: true,
placeholder: 'Filter by permission name',
},
},
module: {
'type': 'string',
'title': 'Module',
'enum': [
{ label: 'All', value: '' },
{ label: 'Users', value: 'user' },
{ label: 'Orders', value: 'order' },
{ label: 'Finance', 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('Please select at least one permission first')
}
}
const schema: ISchema = {
type: 'object',
properties: {
selectedPermissions: {
'type': 'array',
'x-validator': [
{
required: true,
message: 'Please select at least one permission',
},
],
'x-decorator': 'QueryFormItem',
'x-decorator-props': {
label: '',
required: true,
querySchema,
request,
pagination: false,
clearOnDataChange: true,
extra: 'Changing the filter criteria and clicking Search will automatically clear the selected data.',
},
'x-component': 'Transfer',
'x-component-props': {
titles: ['Available Permissions', 'Selected Permissions'],
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>Inject selected-list into extra with decorator-content Example
<script setup lang="ts">
import type { QueryFormItemSelectedListItem } from '@silver-formily/element-plus'
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-formily/core'
import { QueryFormItem, QueryFormItemSelectedList, SelectTable } from '@silver-formily/element-plus'
import { Field, FormProvider } from '@silver-formily/vue'
import { defineComponent, h } from 'vue'
import { createUserRequest } from './mock-user-request'
const form = createForm()
const request = createUserRequest()
function getSelectedUserText(item: QueryFormItemSelectedListItem) {
return item.record?.name ?? String(item.value)
}
const SelectedListExtra = defineComponent({
name: 'SelectedListExtra',
setup() {
return () => h(QueryFormItemSelectedList, {
itemText: getSelectedUserText,
})
},
})
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,
},
},
},
}
</script>
<template>
<FormProvider :form="form">
<Field
name="selectedUsers"
:decorator="[
QueryFormItem,
{
label: 'Target Users',
querySchema,
request,
paginationProps: {
pageSize: 8,
},
queryFormProps: {
submitText: 'Search',
resetText: 'Reset',
},
},
]"
:decorator-content="{
extra: SelectedListExtra,
}"
:component="[
SelectTable,
{
mode: 'multiple',
rowKey: 'id',
optionAsValue: true,
showAlertToolbar: false,
columns: [
{ prop: 'name', label: 'Name' },
{ prop: 'department', label: 'Department' },
],
},
]"
/>
</FormProvider>
</template>export interface UserRecord {
id: string
name: string
department: string
}
export interface PermissionRecord {
key: string
label: string
module: string
}
export const userSource: UserRecord[] = Array.from({ length: 80 }, (_, index) => ({
id: `${index + 1}`,
name: `User-${index + 1}`,
department: index % 2 === 0 ? 'R&D' : 'Product',
}))
export const permissionSource: PermissionRecord[] = [
{ key: 'user:read', label: 'View Users', module: 'user' },
{ key: 'user:edit', label: 'Edit Users', module: 'user' },
{ key: 'order:read', label: 'View Orders', module: 'order' },
{ key: 'order:refund', label: 'Order Refunds', module: 'order' },
{ key: 'finance:read', label: 'View Bills', module: 'finance' },
{ key: 'finance:export', label: 'Export Bills', module: 'finance' },
]
export function createUserRequest(delay = 200) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const department = `${params.department ?? ''}`.trim()
const filtered = userSource.filter((item) => {
const keywordMatched = keyword ? item.name.toLowerCase().includes(keyword) : true
const departmentMatched = department ? item.department === department : true
return keywordMatched && departmentMatched
})
const current = Number(params.current) || 1
const pageSize = Number(params.pageSize) || 10
const start = (current - 1) * pageSize
const end = current * pageSize
return {
data: filtered.slice(start, end),
success: true,
total: filtered.length,
}
}
}
export function createPermissionRequest(delay = 150) {
return async function request(params: Record<string, any>) {
await new Promise(resolve => setTimeout(resolve, delay))
const keyword = `${params.keyword ?? ''}`.trim().toLowerCase()
const module = `${params.module ?? ''}`.trim()
const filtered = permissionSource.filter((item) => {
const keywordMatched = !keyword
|| item.label.toLowerCase().includes(keyword)
|| item.key.includes(keyword)
const moduleMatched = !module || item.module === module
return keywordMatched && moduleMatched
})
return {
data: filtered,
success: true,
total: filtered.length,
}
}
}<script setup lang="ts">
import type { QueryFormItemSelectedListItem } from '@silver-formily/element-plus'
import type { ISchema } from '@silver-formily/json-schema'
import { createForm } from '@silver-formily/core'
import { QueryFormItem, QueryFormItemSelectedList, SelectTable } from '@silver-formily/element-plus'
import { Field, FormProvider } from '@silver-formily/vue'
import { defineComponent, h } from 'vue'
import { createUserRequest } from './mock-user-request'
const form = createForm()
const request = createUserRequest()
function getSelectedUserText(item: QueryFormItemSelectedListItem) {
return item.record?.name ?? String(item.value)
}
const SelectedListExtra = defineComponent({
name: 'SelectedListExtra',
setup() {
return () => h(QueryFormItemSelectedList, {
itemText: getSelectedUserText,
})
},
})
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,
},
},
},
}
</script>
<template>
<FormProvider :form="form">
<Field
name="selectedUsers"
:decorator="[
QueryFormItem,
{
label: 'Target Users',
querySchema,
request,
paginationProps: {
pageSize: 8,
},
queryFormProps: {
submitText: 'Search',
resetText: 'Reset',
},
},
]"
:decorator-content="{
extra: SelectedListExtra,
}"
:component="[
SelectTable,
{
mode: 'multiple',
rowKey: 'id',
optionAsValue: true,
showAlertToolbar: false,
columns: [
{ prop: 'name', label: 'Name' },
{ prop: 'department', label: 'Department' },
],
},
]"
/>
</FormProvider>
</template>- 1
API
QueryFormItem Props
The component inherits most FormItem props. To avoid validation-error styles from breaking the internal QueryForm layout, it changes the FormItem class name, so a few FormItem-related props may not behave exactly the same. The following are QueryFormItem-specific props.
| Prop | Description | Type | Default |
|---|---|---|---|
mode | Query mode | enum | 'default' |
request | Query function. See Request Contract. | Function | - |
clearOnDataChange | Whether to clear the current field value after a successful query | boolean | false |
querySchema | Equivalent to queryFormProps.schema | object | - |
queryFormProps | Query form configuration | object | See QueryForm defaults |
pagination | Whether to enable pagination | boolean | true |
paginationProps | Pagination props forwarded to ElPagination | See Element Plus documentation | - |
paginationMap | Pagination key mapping used when building request params | object | Pagination Parameter Mapping |
immediate | Whether to run the query immediately after mount | boolean | true |
Events
| Prop | Type | Description | Default |
|---|---|---|---|
requestSuccess | Function | Triggered after a successful query. See QueryFormItemRequestSuccessPayload. | - |
requestFailed | Function | Triggered when the query fails | - |
QueryFormItemRequestSuccessPayload
requestSuccess is emitted after the request succeeds with result.success set to true. Its payload contains:
| Prop | Type | Description |
|---|---|---|
values | object | Query values collected by QueryForm, excluding pagination parameters |
pagination | object | undefined | Current page and page size; undefined when pagination is disabled |
dataSource | array | The value of result.data, also synchronized to the current field's dataSource |
total | number | undefined | Total item count; falls back to result.data.length when result.total is omitted |
result | object | Original value returned by request; its total is not replaced with the fallback shown above |
Request Contract
When pagination is enabled, the request function receives current and pageSize in addition to values collected from QueryForm. You can remap those keys through paginationMap.
request must return data in the following shape, similar to ProTable:
interface QueryResult {
data: any[]
success: boolean
total?: number
}successmust betruebeforedatais written into the fielddataSource.- When
totalis omitted,data.lengthis used by default. In paginated scenarios, returningtotalexplicitly is recommended.
Pagination Parameter Mapping
The default pagination keys are current and pageSize. If your backend expects different names, configure them through paginationMap:
const props = {
paginationMap: {
current: 'pageNum',
pageSize: 'pageSize',
},
}SelectedList API
QueryFormItemSelectedList displays the items selected by the current field. It is usually injected into the extra area of QueryFormItem through decoratorContent.extra. The component reads the current field value automatically and includes interactions for clearing all selections and removing a single selected item.
SelectedList Props
| Prop | Description | Type | Default |
|---|---|---|---|
itemText | Required. Returns the display text for each selected item | Function | - |
width | List width | number | 250 |
maxHeight | Max list height before scrolling. Defaults to the content height of QueryFormItem | number | string | - |
selectionText | Selected-count text. When a string is provided, {count} is replaced by the count | string | ((count: number) => string) | Generated from the current locale |
clearSelectionText | Text for the clear-all action | string | Generated from the current locale |
QueryFormItemSelectedListItem
itemText receives the following item shape:
| Prop | Description | Type |
|---|---|---|
value | Current item value. When a record can be resolved by rowKey, this is the record key value | any |
rawValue | Original item stored in the field value | any |
record | Full record matched from dataSource; in object-value mode, resolves the full data source record by identity and falls back to the form object | object | undefined |
index | Original index of the item in the field value array | number |