first commit

This commit is contained in:
liu
2026-07-13 15:23:29 +08:00
commit 50885a98c8
473 changed files with 33772 additions and 0 deletions
@@ -0,0 +1,152 @@
import React, {useCallback, useMemo} from 'react';
import {
Form,
Button,
Space, Row, Col,
} from 'antd';
import { useTranslation } from 'react-i18next';
import type { SearchFormProps, SubmitterButton } from './typings';
import type { FormItemProps } from 'antd';
import type { FormColumn } from "@/components/XinFormField/FieldRender";
import FieldRender from '@/components/XinFormField/FieldRender';
import { pick } from 'lodash';
/**
* SearchForm - JSON 配置动态搜索表单组件
*/
function SearchForm<T extends Record<string, any> = any>(props: SearchFormProps<T>) {
const {
columns,
handleSearch,
submitter,
form: formRef,
grid = true,
rowProps = {
gutter: [20, 20],
wrap: true
},
colProps = {
xs: 24,
sm: 12,
md: 12,
lg: 8,
xl: 6,
xxl: 4
},
...formProps
} = props;
const { t } = useTranslation();
const [form] = Form.useForm<T>(formRef);
// 渲染表单项
const renderFormItem = useCallback((column: FormColumn<T>, index: number): React.ReactNode => {
const {
dataIndex,
valueType,
title = '',
fieldProps = {},
fieldRender
} = column;
// Form.Item 允许的属性列表
const formItemPropKeys = [
'colon', 'extra', 'getValueFromEvent', 'help', 'htmlFor',
'initialValue', 'labelAlign', 'labelCol', 'name', 'normalize',
'noStyle', 'tooltip', 'wrapperCol', 'layout'
];
const formItemProps = pick(column, formItemPropKeys);
const key = String(dataIndex) || `form-item-${index}`;
const defaultFieldRender = (
<FieldRender
valueType={valueType}
placeholder={title}
{...fieldProps}
/>
);
const formItemContent = (
<Form.Item
style={{marginBottom: 0}}
key={key}
name={key}
label={column.title}
{...formItemProps as FormItemProps}
required={false}
>
{fieldRender ? fieldRender(form) : defaultFieldRender}
</Form.Item>
);
return grid ? <Col {...colProps} {...column.colProps} key={key}>{formItemContent}</Col> : formItemContent;
}, [form, grid, colProps]);
// 渲染提交按钮
const renderSubmitter = useMemo(() => {
if (submitter?.render === false) return null;
const submitButton = (
<Button
type="primary"
onClick={() => form.submit()}
{...submitter?.submitButtonProps}
>
{submitter?.submitText || t('xin.table.search.search')}
</Button>
);
const resetButton = (
<Button
onClick={() => form.resetFields()}
{...submitter?.resetButtonProps}
>
{submitter?.resetText || t('xin.table.search.reset')}
</Button>
);
const buttons: SubmitterButton = {
search: submitButton,
reset: resetButton
};
if (typeof submitter?.render === 'function') {
return submitter.render(buttons);
}
return (
<Form.Item style={{marginBottom: 0}}>
<Space size={16}>
{buttons.reset}
{buttons.search}
</Space>
</Form.Item>
);
}, [form, submitter, t]);
// 表单内容
return (
<Form
{...formProps}
form={form}
onFinish={handleSearch}
style={{ width: '100%' }}
>
{grid ? (
<Row {...rowProps}>
{columns.map((column, index) => renderFormItem(column, index))}
<Col {...colProps}>{renderSubmitter}</Col>
</Row>
) : (
<>
{columns.map((column, index) => renderFormItem(column, index))}
{renderSubmitter}
</>
)}
</Form>
)
}
export default SearchForm;
export type { SearchFormProps };
@@ -0,0 +1,53 @@
import type { ButtonProps, ColProps, FormInstance, FormProps, RowProps } from "antd";
import type { ReactNode } from "react";
import type { FormColumn } from "@/components/XinFormField/FieldRender";
/**
* 操作栏按钮
*/
export type SubmitterButton = {
/** 查询按钮 */
search: ReactNode;
/** 重置按钮 */
reset: ReactNode;
/** 折叠按钮 */
collapse?: ReactNode;
}
/**
* 表单操作栏属性
*/
export interface SubmitterProps {
/** 操作栏渲染 */
render?: false | ((dom: SubmitterButton) => ReactNode);
/** 提交按钮文本 */
submitText?: string | ReactNode;
/** 重置按钮文本 */
resetText?: string | ReactNode;
/** 提交按钮属性 */
submitButtonProps?: Omit<ButtonProps, 'loading' | 'onClick'>;
/** 重置按钮属性 */
resetButtonProps?: Omit<ButtonProps, 'loading' | 'onClick'>;
/** 折叠按钮渲染 */
collapseRender?: (collapse: boolean) => ReactNode;
}
/**
* XinSearch 组件属性
*/
export type SearchFormProps<T = any> = Omit<FormProps<T>, 'onFinish' | 'form'> & {
/** 表单列配置 */
columns: FormColumn<T>[];
/** 表单实例引用 */
form?: FormInstance<T>;
/** 表单提交 */
handleSearch?: (values: T) => Promise<boolean | void>;
/** 渲染表单操作栏 */
submitter?: SubmitterProps;
/** 是否使用 Grid 布局 */
grid?: boolean;
/** 开启 grid 模式时传递给 Row */
rowProps?: RowProps;
/** 传递给表单项的 Col */
colProps?: ColProps;
}
+580
View File
@@ -0,0 +1,580 @@
import {
Button,
Tree,
Card,
Form,
Input,
Space,
Table,
Tooltip,
Popover,
Dropdown,
type TableProps,
type TableColumnType,
type TreeProps, ConfigProvider, Flex, Divider,
} from "antd";
import type {XinTableProps, XinTableInstance, RequestParams, FormMode} from "./typings";
import SearchForm from "./SearchForm";
import XinForm, {type XinFormRef} from "@/components/XinForm";
import {type ReactNode, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
import type {FormColumn} from "@/components/XinFormField/FieldRender/typings";
import {Delete, List, Create, Update} from "@/api/common/table.ts";
import {isArray, isEmpty, omit} from "lodash";
import type {SearchProps} from "antd/es/input";
import {
BorderlessTableOutlined,
BorderOutlined,
ColumnHeightOutlined,
DeleteOutlined,
EditOutlined,
PlusOutlined,
ReloadOutlined,
SearchOutlined,
SettingOutlined
} from "@ant-design/icons";
import {useTranslation} from "react-i18next";
import type {DataNode} from "antd/es/tree";
import AuthButton from "@/components/AuthButton";
// 默认分页大小
const DEFAULT_PAGE_SIZE: number = 10;
// 默认页数
const DEFAULT_PAGE: number = 1;
export default function XinTable<T extends Record<string, any> = any>(props: XinTableProps<T>) {
const {
api,
accessName,
rowKey,
columns,
tableRef,
formProps,
modalProps,
cardProps,
searchProps,
operateProps = {},
pagination: customPagination = {},
addShow = true,
editShow = true,
deleteShow = true,
searchShow = true,
operateShow = true,
paginationShow = true,
keywordSearchShow = true,
toolBarRender: customToolBalRender,
actionBarRender: customActionBarRender,
operateRender: customOperateRender,
handleRequest: customHandleRequest,
requestParams: customRequestParams,
handleFinish: customHandleFinish,
} = props;
const {t} = useTranslation();
const formRef = useRef<XinFormRef<T>>(null);
// 内部状态
const [loading, setLoading] = useState<boolean>(true);
const [dataSource, setDataSource] = useState<T[]>([]);
const [total, setTotal] = useState<number>(0);
const [requestParams, setRequestParams] = useState<RequestParams>({
page: DEFAULT_PAGE,
pageSize: DEFAULT_PAGE_SIZE,
});
const [density, setDensity] = useState<TableProps['size']>();
const [bordered, setBordered] = useState<boolean>();
const [searchRef] = Form.useForm<T>();
const [columnsChecked, setColumnsChecked] = useState<any[]>([]);
const [searchRender, setSearchRender] = useState<boolean>(false);
// 表单模式状态
const [formMode, setFormMode] = useState<FormMode>('create');
const [formDefaultValues, setFormDefaultValues] = useState<T | undefined>(undefined);
/** 表格请求 */
const handleRequest = useCallback(async (params?: RequestParams) => {
try {
setLoading(true);
const defaultParams: RequestParams = Object.assign({
page: DEFAULT_PAGE,
pageSize: DEFAULT_PAGE_SIZE,
}, params);
// 自定义参数处理
const finalParams: RequestParams = customRequestParams ? customRequestParams(defaultParams) : defaultParams;
// 自定义请求
let listData: { data: T[]; total: number };
if (customHandleRequest) {
listData = await customHandleRequest(finalParams);
} else {
const { data } = await List<T>(api, finalParams);
listData = data.data!;
}
setDataSource(listData.data);
setTotal(listData.total);
} finally {
setLoading(false);
}
}, [api, customHandleRequest, customRequestParams]);
/** 新增按钮点击 */
const handleCreate = useCallback(() => {
setFormMode('create');
setFormDefaultValues(undefined);
formRef.current?.resetFields();
formRef.current?.open();
}, []);
/** 修改按钮点击 */
const handleUpdate = useCallback((record: T) => {
setFormMode('update');
setFormDefaultValues(record);
formRef.current?.setFieldsValue(record);
formRef.current?.open();
}, []);
// 暴露方法到 tableRef
useImperativeHandle(tableRef, (): XinTableInstance<T> => ({
reload: async () => { await handleRequest(); },
reset: async () => {
searchRef.resetFields();
setRequestParams({ page: 1, pageSize: 10 });
await handleRequest({ page: 1, pageSize: 10 });
},
getDataSource: () => dataSource,
setDataSource,
getTotal: () => total,
getLoading: () => loading,
setLoading,
setPageInfo: (page?, pageSize?) => {
setRequestParams(prev => ({
...prev,
...(page !== undefined && { page }),
...(pageSize !== undefined && { pageSize }),
}));
},
getForm: () => formRef.current,
getSearchForm: () => searchRef,
}));
/** 初始化 */
useEffect(() => { void handleRequest(); }, []);
/** 处理表格变化 */
const handleTableChange: TableProps<T>['onChange'] = async (newPagination, newFilters, newSorter) => {
const params: RequestParams = {
...requestParams,
page: newPagination.current ?? requestParams.page,
pageSize: newPagination.pageSize ?? requestParams.pageSize,
};
// 处理筛选
if (!isEmpty(newFilters)) {
params.filterValues = newFilters;
}
// 处理排序
if (newSorter && !isArray(newSorter) && !isEmpty(newSorter) && newSorter.field) {
params.sorterValue = {
field: String(newSorter.field),
order: newSorter.order === 'ascend' ? 'asc' : 'desc',
};
} else {
delete params.sorterValue;
}
setRequestParams(params);
await handleRequest(params);
};
/** 快速搜索 */
const handleKeywordSearch: SearchProps['onSearch'] = async (value: string) => {
if( !value ) {
window.$message?.warning(t('xin.table.keywordEmpty'));
return;
}
const params: RequestParams = {
...requestParams,
page: 1,
keywordSearch: value,
}
setRequestParams(params);
await handleRequest(params);
};
/** 快速搜索 Change */
const keywordSearchChange: SearchProps['onChange'] = (e) => {
if( !e.target.value ) {
const params = { ...requestParams };
delete params.keywordSearch;
setRequestParams(params);
} else {
setRequestParams({
...requestParams,
keywordSearch: e.target.value,
});
}
};
/** 搜索表单提交 */
const handleSearch = async () => {
const searchValues: T = searchRef.getFieldsValue();
// 移除 空值
Object.keys(searchValues).forEach((key) => {
if (searchValues[key] === '' || searchValues[key] === undefined) {
delete searchValues[key];
}
});
await handleRequest({
page: 1,
...requestParams,
...searchValues,
});
};
/** 搜索列 */
const searchColumn: FormColumn<T>[] = useMemo(() => {
if(!searchShow) return [];
return columns.filter((column) => column.hideInSearch !== true);
}, [columns, searchShow]);
/** 表单列 */
const formColumn: FormColumn<T>[] = useMemo(() => {
return columns.filter((column) => {
if(formMode === 'update') {
return column.hideInForm !== true && column.hideInUpdate !== true;
} else {
return column.hideInForm !== true && column.hideInCreate !== true;
}
});
}, [columns, formMode]);
/** 默认表格列 */
const defaultTableColumns = useMemo(() => {
return columns
.filter((column) => column.hideInTable !== true && column.dataIndex)
.map(column => omit(column, ['hideInTable', 'hideInForm', 'hideInSearch', 'search']));
}, [columns]);
/** 初始化列设置树数据 */
useEffect(() => {
const dataIndexList = defaultTableColumns.map(item => item.dataIndex!);
setColumnsChecked(dataIndexList);
}, [defaultTableColumns]);
/** 表格操作列渲染 */
const operateRender = (record: T): ReactNode[] => {
// 编辑按钮渲染
const editRender = (): ReactNode => {
const show = typeof editShow === 'function' ? editShow(record) : editShow;
return show ? (
<AuthButton auth={props.accessName + '.update'} key={'update'}>
<Tooltip title={t('xin.table.edit')}>
<Button
type="primary"
icon={<EditOutlined />}
size={'small'}
onClick={() => handleUpdate(record)}
/>
</Tooltip>
</AuthButton>
) : null;
}
// 删除按钮渲染
const deleteRender = (): ReactNode => {
const show = typeof deleteShow === 'function' ? deleteShow(record) : deleteShow;
return show ? (
<AuthButton auth={props.accessName + '.delete'} key={'delete'}>
<Tooltip title={t('xin.table.delete')}>
<Button
danger
type="primary"
icon={<DeleteOutlined />}
size={'small'}
onClick={() => handleDelete(record)}
/>
</Tooltip>
</AuthButton>
) : null;
}
if (customOperateRender) {
return customOperateRender(record, {
del: deleteRender(),
edit: editRender()
});
}
return [deleteRender(), editRender()];
};
/** 删除记录 */
const handleDelete = async (record: T) => {
window.$modal?.confirm({
title: t('xin.table.deleteConfirm', { id: record[rowKey] }),
okText: t('xin.table.deleteOk'),
cancelText: t('xin.table.deleteCancel'),
onOk: async () => {
await Delete(api + `/${record[rowKey]}`);
window.$message?.success(t('xin.table.deleteSuccess'));
await handleRequest(requestParams);
}
})
};
/** 表单提交处理 */
const handleFinish = async (values: T) => {
try {
formRef.current?.setLoading(true);
// 如果有自定义 onFinish,优先调用
if (customHandleFinish) {
const result = await customHandleFinish(values, formMode, formRef, formDefaultValues);
if (result) {
await handleRequest(requestParams);
formRef.current?.close();
}
return;
}
if (formMode === 'create') {
await Create(api, values);
window.$message?.success(t('xin.table.form.createSuccess'));
} else {
if (formDefaultValues && rowKey) {
await Update(api + `/${formDefaultValues[rowKey]}`, values);
window.$message?.success(t('xin.table.form.updateSuccess'));
} else {
window.$message?.error(t('xin.table.form.updateKeyUndefined'));
return;
}
}
await handleRequest(requestParams);
formRef.current?.close();
} finally {
formRef.current?.setLoading(false);
}
};
/** 密度设置菜单 */
const densityMenu = {
items: [
{
key: 'large',
label: t('xin.table.density.default'),
onClick: () => setDensity('large'),
},
{
key: 'middle',
label: t('xin.table.density.middle'),
onClick: () => setDensity('middle'),
},
{
key: 'small',
label: t('xin.table.density.compact'),
onClick: () => setDensity('small'),
},
],
selectedKeys: [density || 'large'],
};
/** 列设置树数据 */
const columnTreeData: DataNode[] = useMemo(() => {
const filteredColumns = columns.filter(item => item.hideInTable !== true && item.dataIndex);
return filteredColumns
.map((item) => ({
key: String(item.dataIndex),
title: item.title,
}));
}, [columns]);
/** 列设置选中改变 */
const columnSettingCheck: TreeProps['onCheck'] = (keys) => {
if(isArray(keys)) {
setColumnsChecked(keys);
}
}
/** 最终表格列,计算排序以及显示状态 */
const tableColumns: TableColumnType<T>[] = useMemo(() => {
// 过滤出选中的列
const filteredColumns = defaultTableColumns.filter(column =>
columnsChecked.includes(column.dataIndex as any)
);
if(! operateShow) {
return filteredColumns;
}
return [
...filteredColumns,
{
title: t('xin.table.operate'),
key: 'operate',
align: 'center',
...operateProps,
render: (_, record: T) => (
<Space>{...operateRender(record)}</Space>
),
}
];
}, [defaultTableColumns, columnsChecked, operateShow]);
/** 分页配置 */
const paginationProps: TableProps['pagination'] = {
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => t('xin.table.total', { total }),
pageSize: requestParams.pageSize,
...customPagination,
current: requestParams.page,
total,
}
/** 顶部操作栏渲染 */
const actionBarRender = (): ReactNode[] => {
// 新增按钮渲染
const addButtonRender = addShow ? (
<AuthButton auth={accessName + '.create'}>
<Button type="primary" onClick={handleCreate} icon={<PlusOutlined />}>{t('xin.table.add')}</Button>
</AuthButton>
) : null;
// 搜索按钮渲染
const searchButtonRender = searchShow ? (
<Button type="primary" icon={<SearchOutlined/>} onClick={() => setSearchRender(!searchRender)}>
</Button>
) : null;
// 关键字搜索按钮渲染
const keywordSearchRender = keywordSearchShow ? (
<Input.Search
onChange={keywordSearchChange}
placeholder={t('xin.table.keywordPlaceholder')}
style={{ width: 200 }}
value={requestParams.keywordSearch}
onSearch={handleKeywordSearch}
/>
) : null;
// 自定义顶部操作栏渲染
if (customActionBarRender) {
return customActionBarRender({
add: addButtonRender,
search: searchButtonRender,
keywordSearch: keywordSearchRender
}).filter(Boolean);
} else {
return [addButtonRender, searchButtonRender, keywordSearchRender].filter(Boolean);
}
}
/** 顶部工具栏渲染 */
const toolBarRender = (): ReactNode[] => {
// 刷新
const reload = (
<Tooltip title={'刷新表格'}>
<Button
type="text"
icon={<ReloadOutlined />}
onClick={() => handleRequest()}
/>
</Tooltip>
);
// 密度设置
const columnHeight = (
<Dropdown menu={densityMenu} trigger={['click']}>
<Button type="text" icon={<ColumnHeightOutlined />} />
</Dropdown>
);
// 边框设置
const hideBorder = (
<Tooltip title={bordered ? t('xin.table.hideBorder') : t('xin.table.showBorder')}>
<Button
type="text"
icon={bordered ? <BorderOutlined /> : <BorderlessTableOutlined />}
onClick={() => setBordered(!bordered)}
/>
</Tooltip>
);
// 列设置
const columnSetting = (
<Popover
content={(
<ConfigProvider
theme={{
components: { Tree: { switcherSize: 12 } },
}}
>
<Tree
icon={false}
blockNode
checkable={true}
treeData={columnTreeData}
selectable={false}
checkedKeys={columnsChecked}
onCheck={columnSettingCheck}
/>
</ConfigProvider>
)}
trigger="click"
placement="bottomRight"
title={t('xin.table.columnSettings')}
>
<Tooltip title={t('xin.table.columnSettings')}>
<Button type="text" icon={<SettingOutlined />} />
</Tooltip>
</Popover>
);
// 自定义顶部操作栏渲染
if (customToolBalRender) {
return customToolBalRender({reload, columnHeight, hideBorder, columnSetting}).filter(Boolean);
} else {
return [reload, columnHeight, hideBorder, columnSetting];
}
}
return (
<>
<Card {...cardProps}>
{/* 搜索表单 */}
{ searchRender && (
<>
<SearchForm<T>
form={searchRef}
columns={searchColumn}
handleSearch={handleSearch}
{...searchProps}
/>
<Divider />
</>
)}
{/* 操作栏 */}
<Flex justify={'space-between'} align={'center'} style={{ marginBottom: 20 }}>
<Space>{ ...actionBarRender() }</Space>
<Space size={1}>{...toolBarRender()}</Space>
</Flex>
{/* 表格 */}
<Table
loading={loading}
dataSource={dataSource}
size={density}
bordered={bordered}
{...props}
columns={tableColumns}
rowKey={rowKey}
onChange={handleTableChange}
pagination={paginationShow ? paginationProps : false}
/>
</Card>
{/* 新增编辑表单 */}
<XinForm
{...formProps}
columns={formColumn}
formRef={formRef}
layoutType="ModalForm"
modalProps={{
title: formMode === 'update' ? t('xin.table.form.editTitle') : t('xin.table.form.createTitle'),
styles: { header: { marginBottom: 16 } },
...modalProps,
}}
onFinish={handleFinish}
/>
</>
);
}
+163
View File
@@ -0,0 +1,163 @@
import type {
TableProps,
TableColumnType,
CardProps,
FormInstance,
PaginationProps,
} from 'antd';
import type {ReactNode, RefObject, Dispatch, SetStateAction} from 'react';
import type { FormColumn } from '@/components/XinFormField/FieldRender/typings';
import type { SearchFormProps } from './SearchForm';
import type {XinFormProps, XinFormRef} from '@/components/XinForm/typings';
/**
* 表单模式
*/
export type FormMode = 'create' | 'update';
/**
* 表格列配置
*/
export type XinTableColumn<T = any> = Omit<TableColumnType<T>, 'dataIndex'> & {
hideInSearch?: boolean;
hideInForm?: boolean;
hideInTable?: boolean;
hideInUpdate?: boolean;
hideInCreate?: boolean;
search?: FormColumn<T>;
} & FormColumn<T>;
/**
* 表格操作栏按钮
*/
export type OperateNode = {
/** 删除按钮 */
del: ReactNode;
/** 编辑按钮 */
edit: ReactNode;
}
/**
* 顶部操作按钮渲染
*/
export type ActionNode = {
/** 新增按钮 */
add: ReactNode;
/** 搜索按钮 */
search: ReactNode;
/** 关键字搜索框 */
keywordSearch: ReactNode;
}
/**
* 顶部右侧工具栏渲染
*/
export type ToolBarNode = {
/** 刷新按钮 */
reload: ReactNode;
/** 密度设置按钮 */
columnHeight: ReactNode;
/** 边框设置按钮 */
hideBorder: ReactNode;
/** 列设置按钮 */
columnSetting: ReactNode;
}
/**
* XinTable 实例
*/
export interface XinTableInstance<T = any> {
/** 刷新表格(保持当前页) */
reload: (resetPage?: boolean) => Promise<void>;
/** 重置表格到第一页并刷新 */
reset: () => Promise<void>;
/** 获取当前数据源 */
getDataSource: () => T[];
/** 设置数据源 */
setDataSource: Dispatch<SetStateAction<T[]>>;
/** 获取数据总数 */
getTotal: () => number;
/** 获取加载状态 */
getLoading: () => boolean;
/** 设置加载状态 */
setLoading: Dispatch<SetStateAction<boolean>>;
/** 设置分页参数 */
setPageInfo: (page?: number, pageSize?: number) => void;
/** 获取表单实例 */
getForm: () => XinFormRef<T> | null | undefined;
/** 获取搜索表单实例 */
getSearchForm: () => FormInstance<T> | undefined;
}
export interface SorterParams {
field: string;
order: 'asc' | 'desc';
}
/** 请求参数类型 */
export interface RequestParams extends Record<string, any> {
page?: number;
pageSize?: number;
filter?: Record<string, any>;
sorter?: SorterParams;
keywordSearch?: string;
}
/**
* XinTable 组件属性
*/
export interface XinTableProps<T = any> extends Omit<TableProps<T>, 'columns' | 'rowKey' | 'onChange' | 'pagination'> {
/** API 地址 */
api: string;
/** 权限名称前缀 */
accessName: string;
/** 主键 */
rowKey: string;
/** 列配置 */
columns: XinTableColumn<T>[];
/** 表格 ref */
tableRef?: RefObject<XinTableInstance<T> | null>;
/** 新增按钮显示 */
addShow?: boolean;
/** 编辑按钮显示 */
editShow?: boolean | ((record: T) => boolean);
/** 删除按钮显示 */
deleteShow?: boolean | ((record: T) => boolean);
/** 搜索栏显示 */
searchShow?: boolean;
/** 表格操作列显示 */
operateShow?: boolean;
/** 分页显示 */
paginationShow?: boolean;
/** 快速搜索显示 */
keywordSearchShow?: boolean;
/** 表单属性 */
formProps?: Omit<XinFormProps<T>, 'onFinish' | 'modalProps' | 'columns' | 'formRef' | 'layoutType'> | false;
/** 表单属性 */
modalProps?: XinFormProps<T>['modalProps'];
/** 搜索栏属性 */
searchProps?: Omit<SearchFormProps<T>, 'form'> | false;
/** 操作栏属性 */
operateProps?: TableColumnType<T>;
/** 卡片属性 */
cardProps?: Pick<CardProps, 'variant' | 'hoverable' | 'size' | 'classNames' | 'styles'>;
/** 分页配置 */
pagination?: Omit<PaginationProps, 'total' | 'onChange' | 'current'>;
/** 顶部操作栏渲染 */
actionBarRender?: ((dom: ActionNode) => ReactNode[]);
/** 工具栏渲染 */
toolBarRender?: ((dom: ToolBarNode) => ReactNode[]);
/** 表格操作栏渲染 */
operateRender?: ((record: T, dom: OperateNode) => ReactNode[]);
/** 自定义请求 */
handleRequest?: (params: RequestParams) => Promise<{ data: T[]; total: number }>;
/** 请求参数处理 */
requestParams?: (params: RequestParams) => RequestParams;
/** 自定义表单请求 */
handleFinish?: (values: T, mode: FormMode, formRef: RefObject<XinFormRef<T> | null>, defaultValue?: T) => Promise<boolean>;
}