first commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Card, Space, Divider, Typography, message } from 'antd';
|
||||
import XinForm, { type XinFormRef } from '@/components/XinForm';
|
||||
import type { FormColumn } from '@/components/XinFormField/FieldRender/typings';
|
||||
import IconSelect from '@/components/XinFormField/IconSelector';
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
/**
|
||||
* 图标选择器组件使用示例页面
|
||||
*/
|
||||
const IconSelectExample: React.FC = () => {
|
||||
const [icon, setIcon] = useState<string>('');
|
||||
const formRef = useRef<XinFormRef>(undefined);
|
||||
|
||||
// 表单列配置
|
||||
const columns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'systemName',
|
||||
title: '系统名称',
|
||||
valueType: 'text',
|
||||
rules: [{ required: true, message: '请输入系统名称' }],
|
||||
fieldProps: {
|
||||
placeholder: '请输入系统名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'systemIcon',
|
||||
title: '系统图标',
|
||||
rules: [{ required: true, message: '请选择系统图标' }],
|
||||
fieldRender: () => <IconSelect placeholder="请选择系统图标" />,
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '系统描述',
|
||||
valueType: 'textarea',
|
||||
fieldProps: {
|
||||
placeholder: '请输入系统描述',
|
||||
rows: 4,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography style={{ margin: '12px 0 24px 0' }}>
|
||||
<Title level={2}>图标选择器组件示例</Title>
|
||||
<Paragraph>
|
||||
基于 Ant Design Select + Modal + Tabs 封装的图标选择器组件,支持多分类图标选择。
|
||||
</Paragraph>
|
||||
</Typography>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<Card title="独立使用" bordered>
|
||||
|
||||
<Text>基础用法:</Text>
|
||||
<div className="mt-2">
|
||||
<IconSelect
|
||||
value={icon}
|
||||
onChange={(value) => {
|
||||
setIcon(value || '');
|
||||
if (value) {
|
||||
message.success(`选中图标: ${value}`);
|
||||
} else {
|
||||
message.info('已清空图标');
|
||||
}
|
||||
}}
|
||||
placeholder="请选择图标"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Text>禁用状态:</Text>
|
||||
<div className="mt-2">
|
||||
<IconSelect
|
||||
value="HomeOutlined"
|
||||
disabled={true}
|
||||
placeholder="禁用状态"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Text>只读状态:</Text>
|
||||
<div className="mt-2">
|
||||
<IconSelect
|
||||
value="SettingOutlined"
|
||||
readonly={true}
|
||||
placeholder="只读状态"
|
||||
/>
|
||||
</div>
|
||||
<Text type="secondary" className="mt-2 block text-sm">
|
||||
只读模式下不能打开选择弹窗,但可以清空
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<Card title="在 XinForm 中使用" bordered>
|
||||
<XinForm
|
||||
formRef={formRef}
|
||||
columns={columns}
|
||||
onFinish={async (values) => {
|
||||
console.log('XinForm 提交:', values);
|
||||
message.success('提交成功!');
|
||||
message.info(`系统图标: ${values.systemIcon}`);
|
||||
return true;
|
||||
}}
|
||||
submitter={{
|
||||
submitText: '提交表单',
|
||||
render: (dom) => dom.submit,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconSelectExample;
|
||||
@@ -0,0 +1,156 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Form, Button, Space, Typography } from 'antd';
|
||||
import ImageUploader from '@/components/XinFormField/ImageUploader';
|
||||
import type { ISysFileInfo } from '@/domain/iSysFile';
|
||||
const { Title, Paragraph } = Typography;
|
||||
/**
|
||||
* 图片上传器示例页面
|
||||
*/
|
||||
const ImageUploaderExample: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [singleValue, setSingleValue] = useState<ISysFileInfo | null>(null);
|
||||
const [multipleValue, setMultipleValue] = useState<ISysFileInfo[]>([]);
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
console.log('Form values:', values);
|
||||
console.log('Single image:', singleValue);
|
||||
console.log('Multiple images:', multipleValue);
|
||||
} catch (error) {
|
||||
console.error('Validation failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
form.resetFields();
|
||||
setSingleValue(null);
|
||||
setMultipleValue([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography style={{ margin: '12px 0 24px 0' }}>
|
||||
<Title level={2}>图片上传组件示例</Title>
|
||||
<Paragraph>
|
||||
基于 Ant Design Upload 封装的图片上传组件,支持图片剪裁、多张图片上传、尺寸限制、禁用状态等。
|
||||
</Paragraph>
|
||||
</Typography>
|
||||
<Card title="基础用法" style={{ marginBottom: 24 }}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
label="单张图片上传"
|
||||
name="avatar"
|
||||
extra="支持单张图片上传,最大 5MB,尺寸不超过 1920x1080"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
value={singleValue}
|
||||
onChange={(value) => setSingleValue(value as ISysFileInfo)}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="多张图片上传"
|
||||
name="gallery"
|
||||
extra="支持最多 5 张图片上传"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="multiple"
|
||||
maxCount={5}
|
||||
value={multipleValue}
|
||||
onChange={(value) => setMultipleValue(value as ISysFileInfo[])}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
提交
|
||||
</Button>
|
||||
<Button onClick={handleReset}>重置</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Card title="高级配置" style={{ marginBottom: 24 }}>
|
||||
<Form layout="vertical">
|
||||
<Form.Item
|
||||
label="自定义尺寸限制"
|
||||
extra="限制图片尺寸为 800x600,大小不超过 2MB"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
maxWidth={800}
|
||||
maxHeight={600}
|
||||
maxSize={2}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="裁剪功能 - 自由裁剪"
|
||||
extra="启用裁剪功能,可自由调整裁剪区域"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
croppable
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="裁剪功能 - 1:1 正方形"
|
||||
extra="固定 1:1 比例裁剪,适合头像上传"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
croppable
|
||||
cropperOptions={{ aspect: 1 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="裁剪功能 - 圆形裁剪"
|
||||
extra="圆形裁剪模式,适合头像上传"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
croppable
|
||||
cropperOptions={{ cropShape: 'round', aspect: 1 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="裁剪功能 - 16:9 宽屏"
|
||||
extra="固定 16:9 比例裁剪,适合横幅图片"
|
||||
>
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
croppable
|
||||
cropperOptions={{ aspect: 16 / 9 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="禁用状态">
|
||||
<ImageUploader
|
||||
action="/sys/file/list/upload/image"
|
||||
mode="single"
|
||||
disabled
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageUploaderExample;
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Card, Space, Divider, Typography, message } from 'antd';
|
||||
import XinForm, { type XinFormRef } from '@/components/XinForm';
|
||||
import type { FormColumn } from '@/components/XinFormField/FieldRender/typings';
|
||||
import UserSelector from '@/components/XinFormField/UserSelector';
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
/**
|
||||
* 用户选择器组件使用示例页面
|
||||
*/
|
||||
const UserSelectorExample: React.FC = () => {
|
||||
const [singleUser, setSingleUser] = useState<number | null>(null);
|
||||
const [multipleUsers, setMultipleUsers] = useState<number[]>([]);
|
||||
const formRef = useRef<XinFormRef>(undefined);
|
||||
|
||||
// 表单列配置
|
||||
const columns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'taskName',
|
||||
title: '任务名称',
|
||||
valueType: 'text',
|
||||
rules: [{ required: true, message: '请输入任务名称' }],
|
||||
fieldProps: {
|
||||
placeholder: '请输入任务名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'owner_id',
|
||||
title: '任务负责人',
|
||||
rules: [{ required: true, message: '请选择负责人' }],
|
||||
fieldRender: () => <UserSelector placeholder="请选择负责人" />,
|
||||
},
|
||||
{
|
||||
dataIndex: 'participant_ids',
|
||||
title: '参与人员',
|
||||
rules: [
|
||||
{ required: true, message: '请至少选择一个参与人员' },
|
||||
{
|
||||
validator: (_: any, value: number[]) => {
|
||||
if (value && value.length > 10) {
|
||||
return Promise.reject('最多选择10个参与人员');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
fieldRender: () => <UserSelector mode="multiple" maxTagCount={3} placeholder="请选择参与人员" />,
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '任务描述',
|
||||
valueType: 'textarea',
|
||||
fieldProps: {
|
||||
placeholder: '请输入任务描述',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography style={{ margin: '12px 0 24px 0' }}>
|
||||
<Title level={2}>用户选择器组件示例</Title>
|
||||
<Paragraph>
|
||||
基于 AntDesign 封装的用户选择器表单组件,支持单选和多选模式。
|
||||
</Paragraph>
|
||||
</Typography>
|
||||
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<Card title="独立使用" bordered>
|
||||
|
||||
<Text>单选模式:</Text>
|
||||
<div className="mt-2">
|
||||
<UserSelector
|
||||
value={singleUser}
|
||||
onChange={(value) => {
|
||||
setSingleUser(value as number);
|
||||
message.success(`选中用户ID: ${value}`);
|
||||
}}
|
||||
placeholder="请选择用户"
|
||||
/>
|
||||
</div>
|
||||
<Text type="secondary" className="mt-2 block">
|
||||
当前选中: {singleUser || '未选择'}
|
||||
</Text>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Text>多选模式:</Text>
|
||||
<div className="mt-2">
|
||||
<UserSelector
|
||||
mode="multiple"
|
||||
value={multipleUsers}
|
||||
onChange={(value) => {
|
||||
setMultipleUsers(value as number[]);
|
||||
message.success(`选中${(value as number[]).length}个用户`);
|
||||
}}
|
||||
placeholder="请选择多个用户"
|
||||
maxTagCount={3}
|
||||
/>
|
||||
</div>
|
||||
<Text type="secondary" className="mt-2 block">
|
||||
当前选中: {multipleUsers.length > 0 ? multipleUsers.join(', ') : '未选择'}
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<Card title="在 XinForm 中使用" bordered>
|
||||
<XinForm
|
||||
formRef={formRef}
|
||||
columns={columns}
|
||||
onFinish={async (values) => {
|
||||
console.log('表单提交:', values);
|
||||
message.success('提交成功!');
|
||||
message.info(`负责人ID: ${values.owner_id}, 参与人IDs: ${values.participant_ids?.join(', ')}`);
|
||||
return true;
|
||||
}}
|
||||
submitter={{
|
||||
submitText: '提交表单',
|
||||
render: (dom) => dom.submit,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSelectorExample;
|
||||
@@ -0,0 +1,710 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Card, Space, Typography, message, Button, Divider, Input, Tag } from 'antd';
|
||||
import XinForm from '@/components/XinForm';
|
||||
import type { XinFormRef } from '@/components/XinForm';
|
||||
import type { FormColumn } from '@/components/XinFormField/FieldRender/typings';
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
// 基础表单字段配置
|
||||
const basicColumns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'username',
|
||||
title: '用户名',
|
||||
valueType: 'text',
|
||||
rules: [{ required: true, message: '请输入用户名' }],
|
||||
fieldProps: {
|
||||
placeholder: '请输入用户名',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'password',
|
||||
title: '密码',
|
||||
valueType: 'password',
|
||||
rules: [{ required: true, message: '请输入密码' }],
|
||||
fieldProps: {
|
||||
placeholder: '请输入密码',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'email',
|
||||
title: '邮箱',
|
||||
valueType: 'text',
|
||||
rules: [
|
||||
{ required: true, message: '请输入邮箱' },
|
||||
{ type: 'email', message: '请输入有效的邮箱地址' },
|
||||
],
|
||||
fieldProps: {
|
||||
placeholder: '请输入邮箱地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '个人简介',
|
||||
valueType: 'textarea',
|
||||
fieldProps: {
|
||||
placeholder: '请输入个人简介',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Grid 布局表单字段配置
|
||||
const gridColumns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '姓名',
|
||||
valueType: 'text',
|
||||
colProps: { span: 12 },
|
||||
rules: [{ required: true, message: '请输入姓名' }],
|
||||
fieldProps: { placeholder: '请输入姓名' },
|
||||
},
|
||||
{
|
||||
dataIndex: 'age',
|
||||
title: '年龄',
|
||||
valueType: 'digit',
|
||||
colProps: { span: 12 },
|
||||
fieldProps: { placeholder: '请输入年龄', min: 0, max: 150, style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
dataIndex: 'gender',
|
||||
title: '性别',
|
||||
valueType: 'radio',
|
||||
colProps: { span: 12 },
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '男', value: 'male' },
|
||||
{ label: '女', value: 'female' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
valueType: 'switch',
|
||||
colProps: { span: 12 },
|
||||
valuePropName: 'checked',
|
||||
},
|
||||
{
|
||||
dataIndex: 'birthDate',
|
||||
title: '出生日期',
|
||||
valueType: 'date',
|
||||
colProps: { span: 12 },
|
||||
fieldProps: { style: { width: '100%' } },
|
||||
},
|
||||
{
|
||||
dataIndex: 'salary',
|
||||
title: '薪资',
|
||||
valueType: 'money',
|
||||
colProps: { span: 12 },
|
||||
fieldProps: { style: { width: '100%' }, placeholder: '请输入薪资' },
|
||||
},
|
||||
];
|
||||
|
||||
// 完整表单字段配置 - 展示所有字段类型
|
||||
const fullColumns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'text',
|
||||
title: '文本输入',
|
||||
valueType: 'text',
|
||||
tooltip: '这是一个帮助提示',
|
||||
fieldProps: { placeholder: '普通文本输入' },
|
||||
},
|
||||
{
|
||||
dataIndex: 'password',
|
||||
title: '密码输入',
|
||||
valueType: 'password',
|
||||
fieldProps: { placeholder: '密码输入' },
|
||||
},
|
||||
{
|
||||
dataIndex: 'digit',
|
||||
title: '数字输入',
|
||||
valueType: 'digit',
|
||||
extra: '请输入0-100之间的数字',
|
||||
fieldProps: {
|
||||
placeholder: '数字输入',
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'money',
|
||||
title: '金额输入',
|
||||
valueType: 'money',
|
||||
fieldProps: {
|
||||
placeholder: '金额输入',
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'select',
|
||||
title: '下拉选择',
|
||||
valueType: 'select',
|
||||
fieldProps: {
|
||||
placeholder: '请选择',
|
||||
options: [
|
||||
{ label: '选项一', value: 'option1' },
|
||||
{ label: '选项二', value: 'option2' },
|
||||
{ label: '选项三', value: 'option3' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'treeSelect',
|
||||
title: '树形选择',
|
||||
valueType: 'treeSelect',
|
||||
fieldProps: {
|
||||
placeholder: '请选择',
|
||||
treeData: [
|
||||
{ title: '父节点1', value: 'parent1', children: [
|
||||
{ title: '子节点1-1', value: 'child1-1' },
|
||||
{ title: '子节点1-2', value: 'child1-2' },
|
||||
]},
|
||||
{ title: '父节点2', value: 'parent2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'cascader',
|
||||
title: '级联选择',
|
||||
valueType: 'cascader',
|
||||
fieldProps: {
|
||||
placeholder: '请选择',
|
||||
options: [
|
||||
{ label: '浙江', value: 'zhejiang', children: [
|
||||
{ label: '杭州', value: 'hangzhou' },
|
||||
{ label: '宁波', value: 'ningbo' },
|
||||
]},
|
||||
{ label: '江苏', value: 'jiangsu', children: [
|
||||
{ label: '南京', value: 'nanjing' },
|
||||
{ label: '苏州', value: 'suzhou' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'radio',
|
||||
title: '单选框',
|
||||
valueType: 'radio',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '选项A', value: 'a' },
|
||||
{ label: '选项B', value: 'b' },
|
||||
{ label: '选项C', value: 'c' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'radioButton',
|
||||
title: '单选按钮',
|
||||
valueType: 'radioButton',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '按钮A', value: 'a' },
|
||||
{ label: '按钮B', value: 'b' },
|
||||
{ label: '按钮C', value: 'c' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'checkbox',
|
||||
title: '多选框',
|
||||
valueType: 'checkbox',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '选项1', value: '1' },
|
||||
{ label: '选项2', value: '2' },
|
||||
{ label: '选项3', value: '3' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'switch',
|
||||
title: '开关',
|
||||
valueType: 'switch',
|
||||
valuePropName: 'checked',
|
||||
},
|
||||
{
|
||||
dataIndex: 'rate',
|
||||
title: '评分',
|
||||
valueType: 'rate',
|
||||
},
|
||||
{
|
||||
dataIndex: 'slider',
|
||||
title: '滑动条',
|
||||
valueType: 'slider',
|
||||
},
|
||||
{
|
||||
dataIndex: 'date',
|
||||
title: '日期选择',
|
||||
valueType: 'date',
|
||||
},
|
||||
{
|
||||
dataIndex: 'dateTime',
|
||||
title: '日期时间',
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
dataIndex: 'dateRange',
|
||||
title: '日期范围',
|
||||
valueType: 'dateRange',
|
||||
},
|
||||
{
|
||||
dataIndex: 'time',
|
||||
title: '时间选择',
|
||||
valueType: 'time',
|
||||
},
|
||||
{
|
||||
dataIndex: 'color',
|
||||
title: '颜色选择',
|
||||
valueType: 'color',
|
||||
},
|
||||
{
|
||||
dataIndex: 'icon',
|
||||
title: '图标选择',
|
||||
valueType: 'icon',
|
||||
},
|
||||
{
|
||||
dataIndex: 'image',
|
||||
title: '图片上传',
|
||||
valueType: 'image',
|
||||
fieldProps: {
|
||||
action: '/file/upload', // 假设的上传接口
|
||||
maxCount: 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'user',
|
||||
title: '用户选择',
|
||||
valueType: 'user',
|
||||
fieldProps: {
|
||||
placeholder: '请选择用户',
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'textarea',
|
||||
title: '多行文本',
|
||||
valueType: 'textarea',
|
||||
fieldProps: { placeholder: '请输入多行文本', rows: 3 },
|
||||
},
|
||||
];
|
||||
|
||||
// 字段分组配置
|
||||
const groupColumns: FormColumn<any>[] = [
|
||||
{
|
||||
valueType: 'text',
|
||||
colProps: { span: 24 },
|
||||
fieldRender: () => <Divider titlePlacement={'left'}>基本信息</Divider>
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '姓名',
|
||||
valueType: 'text',
|
||||
colProps: { span: 12 },
|
||||
rules: [{ required: true, message: '请输入姓名' }],
|
||||
},
|
||||
{
|
||||
dataIndex: 'phone',
|
||||
title: '电话',
|
||||
valueType: 'text',
|
||||
colProps: { span: 12 },
|
||||
tooltip: '请输入11位手机号',
|
||||
},
|
||||
{
|
||||
valueType: 'text',
|
||||
colProps: { span: 24 },
|
||||
fieldRender: () => <Divider titlePlacement={'left'}>部门信息</Divider>
|
||||
},
|
||||
{
|
||||
dataIndex: 'department',
|
||||
title: '部门',
|
||||
valueType: 'select',
|
||||
colProps: { span: 12 },
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '技术部', value: 'tech' },
|
||||
{ label: '产品部', value: 'product' },
|
||||
{ label: '运营部', value: 'operation' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'position',
|
||||
title: '职位',
|
||||
valueType: 'text',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
dataIndex: 'remark',
|
||||
title: '备注',
|
||||
valueType: 'textarea',
|
||||
extra: '可选填写',
|
||||
},
|
||||
];
|
||||
|
||||
// 依赖联动配置
|
||||
interface DependencyFormData {
|
||||
hasDiscount: boolean;
|
||||
discount: number;
|
||||
productType: string;
|
||||
subType: string;
|
||||
}
|
||||
|
||||
const dependencyColumns: FormColumn<DependencyFormData>[] = [
|
||||
{
|
||||
dataIndex: 'hasDiscount',
|
||||
title: '是否有折扣',
|
||||
valueType: 'switch',
|
||||
valuePropName: 'checked',
|
||||
tooltip: '开启后可设置折扣比例',
|
||||
},
|
||||
{
|
||||
dataIndex: 'discount',
|
||||
title: '折扣比例',
|
||||
valueType: 'slider',
|
||||
dependency: {
|
||||
dependencies: ['hasDiscount'],
|
||||
visible: (values) => values.hasDiscount === true,
|
||||
},
|
||||
fieldProps: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
marks: { 0: '0%', 50: '50%', 100: '100%' },
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'productType',
|
||||
title: '产品类型',
|
||||
valueType: 'select',
|
||||
fieldProps: {
|
||||
placeholder: '请选择产品类型',
|
||||
options: [
|
||||
{ label: '电子产品', value: 'electronics' },
|
||||
{ label: '服装', value: 'clothing' },
|
||||
{ label: '食品', value: 'food' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'subType',
|
||||
title: '子类型',
|
||||
valueType: 'select',
|
||||
dependency: {
|
||||
dependencies: ['productType'],
|
||||
visible: (values) => !!values.productType,
|
||||
disabled: (values) => !values.productType,
|
||||
fieldProps: (values) => {
|
||||
const subTypeOptions: Record<string, { label: string; value: string }[]> = {
|
||||
electronics: [
|
||||
{ label: '手机', value: 'phone' },
|
||||
{ label: '电脑', value: 'computer' },
|
||||
{ label: '平板', value: 'tablet' },
|
||||
],
|
||||
clothing: [
|
||||
{ label: '上衣', value: 'top' },
|
||||
{ label: '裤子', value: 'pants' },
|
||||
{ label: '鞋子', value: 'shoes' },
|
||||
],
|
||||
food: [
|
||||
{ label: '零食', value: 'snack' },
|
||||
{ label: '饮料', value: 'drink' },
|
||||
{ label: '水果', value: 'fruit' },
|
||||
],
|
||||
};
|
||||
return {
|
||||
placeholder: '请先选择产品类型',
|
||||
options: subTypeOptions[values.productType as string] || [],
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 自定义渲染配置
|
||||
const customColumns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'username',
|
||||
title: '用户名',
|
||||
valueType: 'text',
|
||||
rules: [{ required: true, message: '请输入用户名' }],
|
||||
},
|
||||
{
|
||||
dataIndex: 'tags',
|
||||
title: '标签',
|
||||
valueType: 'text',
|
||||
fieldRender: (form) => (
|
||||
<Space>
|
||||
<Input
|
||||
placeholder="输入标签后按回车"
|
||||
onPressEnter={(e) => {
|
||||
const value = (e.target as HTMLInputElement).value;
|
||||
const tags = form.getFieldValue('tags') || [];
|
||||
if (value && !tags.includes(value)) {
|
||||
form.setFieldValue('tags', [...tags, value]);
|
||||
}
|
||||
(e.target as HTMLInputElement).value = '';
|
||||
}}
|
||||
/>
|
||||
<Space>
|
||||
{(form.getFieldValue('tags') || []).map((tag: string, index: number) => (
|
||||
<Tag
|
||||
key={index}
|
||||
closable
|
||||
onClose={() => {
|
||||
const tags = form.getFieldValue('tags') || [];
|
||||
form.setFieldValue('tags', tags.filter((_: string, i: number) => i !== index));
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'hiddenField',
|
||||
title: '隐藏字段',
|
||||
valueType: 'text',
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
// Modal/Drawer 表单字段配置
|
||||
const modalColumns: FormColumn<any>[] = [
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: '标题',
|
||||
valueType: 'text',
|
||||
rules: [{ required: true, message: '请输入标题' }],
|
||||
fieldProps: { placeholder: '请输入标题' },
|
||||
},
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: '类型',
|
||||
valueType: 'select',
|
||||
rules: [{ required: true, message: '请选择类型' }],
|
||||
fieldProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: [
|
||||
{ label: '类型一', value: 'type1' },
|
||||
{ label: '类型二', value: 'type2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'priority',
|
||||
title: '优先级',
|
||||
valueType: 'radioButton',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '低', value: 'low' },
|
||||
{ label: '中', value: 'medium' },
|
||||
{ label: '高', value: 'high' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'content',
|
||||
title: '内容',
|
||||
valueType: 'textarea',
|
||||
fieldProps: { placeholder: '请输入内容', rows: 4 },
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* XinForm 组件使用示例页面
|
||||
*/
|
||||
const XinFormExample: React.FC = () => {
|
||||
const modalFormRef = useRef<XinFormRef>(undefined);
|
||||
const drawerFormRef = useRef<XinFormRef>(undefined);
|
||||
|
||||
const handleFinish = async (values: any) => {
|
||||
console.log('表单提交:', values);
|
||||
message.success('表单提交成功!');
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography style={{ margin: '12px 0 24px 0' }}>
|
||||
<Title level={2}>XinForm 动态表单组件示例</Title>
|
||||
<Paragraph>
|
||||
基于 Ant Design Form 封装的 JSON 配置动态表单组件,支持多种布局和丰富的表单字段类型。
|
||||
</Paragraph>
|
||||
</Typography>
|
||||
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
{/* 基础表单 */}
|
||||
<Card title="基础表单" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
最简单的表单用法,通过 columns 配置表单字段。
|
||||
</Paragraph>
|
||||
<XinForm
|
||||
columns={basicColumns}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Grid 布局表单 */}
|
||||
<Card title="Grid 布局表单" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
使用 grid 属性开启栅格布局,通过 colProps 控制每列宽度。
|
||||
</Paragraph>
|
||||
<XinForm
|
||||
columns={gridColumns}
|
||||
layout="vertical"
|
||||
grid={true}
|
||||
rowProps={{ gutter: 16 }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 所有字段类型 */}
|
||||
<Card title="所有字段类型" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
展示 XinForm 支持的所有字段类型:text、password、digit、money、select、treeSelect、cascader、radio、checkbox、switch、rate、slider、date、time、color、icon、textarea 等。
|
||||
<br />
|
||||
新增功能:<Text code>tooltip</Text> 提示信息、<Text code>extra</Text> 额外说明、<Text code>width</Text> 组件宽度。
|
||||
</Paragraph>
|
||||
<XinForm
|
||||
columns={fullColumns}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 字段分组 */}
|
||||
<Card title="字段分组" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
使用 <Text code>group</Text> 属性对表单字段进行分组,自动显示分割线和标题。
|
||||
</Paragraph>
|
||||
<XinForm
|
||||
columns={groupColumns}
|
||||
layout="vertical"
|
||||
grid={true}
|
||||
rowProps={{ gutter: 16 }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 依赖联动 */}
|
||||
<Card title="依赖联动" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
使用 <Text code>dependency</Text> 属性实现字段间的依赖联动:
|
||||
<br />
|
||||
- <Text code>visible</Text>:根据其他字段值控制显示/隐藏
|
||||
<br />
|
||||
- <Text code>disabled</Text>:根据其他字段值控制禁用状态
|
||||
<br />
|
||||
- <Text code>fieldProps</Text>:根据其他字段值动态设置组件属性
|
||||
</Paragraph>
|
||||
<XinForm<DependencyFormData>
|
||||
columns={dependencyColumns}
|
||||
layout="vertical"
|
||||
initialValues={{ hasDiscount: false }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 自定义渲染 */}
|
||||
<Card title="自定义渲染" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
使用 <Text code>valueType="custom"</Text> + <Text code>renderField</Text> 实现完全自定义的表单字段渲染。
|
||||
<br />
|
||||
使用 <Text code>hidden</Text> 属性隐藏字段(支持布尔值或函数)。
|
||||
</Paragraph>
|
||||
<XinForm
|
||||
columns={customColumns}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* ModalForm 弹窗表单 */}
|
||||
<Card title="ModalForm 弹窗表单" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
使用 layoutType="ModalForm" 实现弹窗表单,支持 trigger 触发器或 formRef 控制打开/关闭。
|
||||
</Paragraph>
|
||||
<Space>
|
||||
<XinForm
|
||||
columns={modalColumns}
|
||||
layoutType="ModalForm"
|
||||
layout="vertical"
|
||||
trigger={<Button type="primary">使用 Trigger 打开</Button>}
|
||||
modalProps={{ title: '新建任务', width: 520 }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
<Button onClick={() => modalFormRef.current?.open()}>
|
||||
使用 Ref 打开
|
||||
</Button>
|
||||
<XinForm
|
||||
columns={modalColumns}
|
||||
layoutType="ModalForm"
|
||||
layout="vertical"
|
||||
formRef={modalFormRef}
|
||||
modalProps={{ title: '新建任务 (Ref)', width: 520 }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* DrawerForm 抽屉表单 */}
|
||||
<Card title="DrawerForm 抽屉表单" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
使用 layoutType="DrawerForm" 实现抽屉表单,适用于复杂表单或需要更大空间的场景。
|
||||
</Paragraph>
|
||||
<Space>
|
||||
<XinForm
|
||||
columns={modalColumns}
|
||||
layoutType="DrawerForm"
|
||||
layout="vertical"
|
||||
trigger={<Button type="primary">使用 Trigger 打开</Button>}
|
||||
drawerProps={{ title: '新建任务', width: 520 }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
<Button onClick={() => drawerFormRef.current?.open()}>
|
||||
使用 Ref 打开
|
||||
</Button>
|
||||
<XinForm
|
||||
columns={modalColumns}
|
||||
layoutType="DrawerForm"
|
||||
layout="vertical"
|
||||
formRef={drawerFormRef}
|
||||
drawerProps={{ title: '新建任务 (Ref)', width: 520 }}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 自定义提交按钮 */}
|
||||
<Card title="自定义提交按钮" variant={'borderless'}>
|
||||
<Paragraph type="secondary">
|
||||
通过 submitter 配置项自定义提交按钮文本、样式,或完全自定义渲染。
|
||||
</Paragraph>
|
||||
<Divider titlePlacement={'left'}>自定义按钮文本</Divider>
|
||||
<XinForm
|
||||
columns={basicColumns.slice(0, 2)}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
submitter={{
|
||||
submitText: '确认提交',
|
||||
resetText: '清空表单',
|
||||
}}
|
||||
/>
|
||||
<Divider titlePlacement={'left'}>隐藏提交按钮</Divider>
|
||||
<Text type="secondary">设置 submitter.render = false 隐藏提交按钮</Text>
|
||||
<XinForm
|
||||
columns={basicColumns.slice(0, 2)}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
submitter={{ render: false }}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default XinFormExample;
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Button, Tag } from 'antd';
|
||||
import { ExportOutlined } from '@ant-design/icons';
|
||||
import type {XinTableColumn, XinTableProps} from '@/components/XinTable/typings';
|
||||
import XinTable from '@/components/XinTable';
|
||||
|
||||
// 模拟用户数据类型
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
age: number;
|
||||
status: number;
|
||||
role: string;
|
||||
department: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// 模拟数据
|
||||
const mockData: User[] = Array.from({ length: 50 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
name: `用户${i + 1}`,
|
||||
email: `user${i + 1}@example.com`,
|
||||
age: Math.floor(Math.random() * 40) + 20,
|
||||
status: Math.random() > 0.3 ? 1 : 0,
|
||||
role: ['管理员', '编辑', '访客'][Math.floor(Math.random() * 3)],
|
||||
department: ['技术部', '产品部', '运营部', '市场部'][Math.floor(Math.random() * 4)],
|
||||
createdAt: new Date(Date.now() - Math.random() * 10000000000).toLocaleDateString(),
|
||||
}));
|
||||
|
||||
/**
|
||||
* XinTable 示例页面
|
||||
*/
|
||||
const XinTableExample = () => {
|
||||
|
||||
// 表格列配置
|
||||
const columns: XinTableColumn<User>[] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 60,
|
||||
valueType: 'text',
|
||||
dataIndex: 'id',
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '用户名',
|
||||
width: 120,
|
||||
valueType: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
dataIndex: 'email',
|
||||
title: '邮箱',
|
||||
valueType: 'text',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
dataIndex: 'age',
|
||||
title: '年龄',
|
||||
width: 80,
|
||||
valueType: 'digit',
|
||||
sorter: (a: User, b: User) => a.age - b.age,
|
||||
hideInSearch: true
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
valueType: 'select',
|
||||
// 自定义渲染 - 用户外理显示内容
|
||||
render: (value: number) => {
|
||||
const enumMap: Record<number, { text: string; color: string }> = {
|
||||
1: { text: '启用', color: 'green' },
|
||||
0: { text: '禁用', color: 'red' },
|
||||
};
|
||||
const item = enumMap[value];
|
||||
return item ? <Tag color={item.color}>{item.text}</Tag> : '-';
|
||||
},
|
||||
filters: [
|
||||
{ text: '启用', value: 1 },
|
||||
{ text: '禁用', value: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
dataIndex: 'role',
|
||||
title: '角色',
|
||||
width: 100,
|
||||
valueType: 'select',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '管理员', value: '管理员' },
|
||||
{ label: '编辑', value: '编辑' },
|
||||
{ label: '访客', value: '访客' },
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'department',
|
||||
title: '部门',
|
||||
width: 120,
|
||||
valueType: 'select',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ label: '技术部', value: '技术部' },
|
||||
{ label: '产品部', value: '产品部' },
|
||||
{ label: '运营部', value: '运营部' },
|
||||
{ label: '市场部', value: '市场部' },
|
||||
],
|
||||
},
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
dataIndex: 'createdAt',
|
||||
title: '创建时间',
|
||||
width: 120,
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
},
|
||||
];
|
||||
|
||||
// 自定义工具栏
|
||||
const customToolbar: XinTableProps['toolBarRender'] = (dom) => [
|
||||
<Button key="export" icon={<ExportOutlined />}>
|
||||
导出
|
||||
</Button>,
|
||||
dom.columnSetting,
|
||||
dom.hideBorder,
|
||||
dom.reload,
|
||||
dom.columnHeight
|
||||
];
|
||||
|
||||
return (
|
||||
<XinTable
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={mockData}
|
||||
accessName='system.user.list'
|
||||
api={'/system-user/list'}
|
||||
toolBarRender={customToolbar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default XinTableExample;
|
||||
|
||||
Reference in New Issue
Block a user