parent
320e27802d
commit
72ae11aec3
|
|
@ -1,4 +1,4 @@
|
||||||
import type { FormInstance, FormProps } from "antd/es/form";
|
import type { FormProps } from "antd/es/form";
|
||||||
import type { Gutter } from "antd/es/grid/row";
|
import type { Gutter } from "antd/es/grid/row";
|
||||||
import type { FC, ReactNode } from "react";
|
import type { FC, ReactNode } from "react";
|
||||||
import type { FormOption, FormValues } from "./FormItemsRenderer";
|
import type { FormOption, FormValues } from "./FormItemsRenderer";
|
||||||
|
|
@ -6,7 +6,7 @@ import type { FormOption, FormValues } from "./FormItemsRenderer";
|
||||||
/**
|
/**
|
||||||
* FormBuilder 组件属性
|
* FormBuilder 组件属性
|
||||||
*/
|
*/
|
||||||
export interface FormBuilderProps extends Omit<FormProps, "form"> {
|
export interface FormBuilderProps extends FormProps {
|
||||||
/** 表单初始值 */
|
/** 表单初始值 */
|
||||||
values?: FormValues;
|
values?: FormValues;
|
||||||
/** 表单配置项数组 */
|
/** 表单配置项数组 */
|
||||||
|
|
@ -15,24 +15,22 @@ export interface FormBuilderProps extends Omit<FormProps, "form"> {
|
||||||
gutter?: Gutter | [Gutter, Gutter];
|
gutter?: Gutter | [Gutter, Gutter];
|
||||||
/** 占据栅格列数,默认 12 */
|
/** 占据栅格列数,默认 12 */
|
||||||
span?: number | string;
|
span?: number | string;
|
||||||
/** 表单实例(通过 Form.useForm() 创建) */
|
|
||||||
form?: FormInstance;
|
|
||||||
/** 自动生成必填规则,默认 true */
|
/** 自动生成必填规则,默认 true */
|
||||||
useAutoGenerateRequired?: boolean;
|
useAutoGenerateRequired?: boolean;
|
||||||
/** 表单提交回调 */
|
|
||||||
onFinish?: (values: FormValues) => void;
|
|
||||||
/** 是否显示操作按钮区域,默认 true */
|
/** 是否显示操作按钮区域,默认 true */
|
||||||
showActionButtons?: boolean;
|
showActionButtons?: boolean;
|
||||||
/** 提交按钮文字,默认为"提交" */
|
/** 提交按钮文字,默认为"提交" */
|
||||||
submitButtonText?: string;
|
submitButtonText?: string;
|
||||||
/** 重置按钮文字,默认为"重置" */
|
/** 取消按钮文字,默认为"取消" */
|
||||||
resetButtonText?: string;
|
cancelButtonText?: string;
|
||||||
/** 是否显示提交按钮,默认 true */
|
/** 是否显示提交按钮,默认 true */
|
||||||
showSubmitButton?: boolean;
|
showSubmitButton?: boolean;
|
||||||
/** 是否显示重置按钮,默认 true */
|
/** 是否显示取消按钮,默认 true */
|
||||||
showResetButton?: boolean;
|
showCancelButton?: boolean;
|
||||||
/** 自定义操作按钮组 */
|
/** 自定义操作按钮组 */
|
||||||
customActionButtons?: ReactNode;
|
customActionButtons?: ReactNode;
|
||||||
|
/** 额外操作按钮组 */
|
||||||
|
extraActionButtons?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,19 @@ const FormBuilder = (props) => {
|
||||||
gutter = 24,
|
gutter = 24,
|
||||||
span = 12,
|
span = 12,
|
||||||
labelCol = { span: 4 },
|
labelCol = { span: 4 },
|
||||||
onFinish,
|
|
||||||
useAutoGenerateRequired = true,
|
useAutoGenerateRequired = true,
|
||||||
showActionButtons = true,
|
showActionButtons = true,
|
||||||
submitButtonText = "提交",
|
submitButtonText = "提交",
|
||||||
resetButtonText = "重置",
|
cancelButtonText = "取消",
|
||||||
showSubmitButton = true,
|
showSubmitButton = true,
|
||||||
showResetButton = true,
|
showCancelButton = true,
|
||||||
customActionButtons,
|
customActionButtons,
|
||||||
form: externalForm,
|
extraActionButtons,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [internalForm] = Form.useForm();
|
const handleCancel = () => {
|
||||||
const form = externalForm || internalForm;
|
window.history.back();
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
form.resetFields();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -35,9 +31,7 @@ const FormBuilder = (props) => {
|
||||||
labelCol={labelCol}
|
labelCol={labelCol}
|
||||||
scrollToFirstError
|
scrollToFirstError
|
||||||
wrapperCol={{ span: 24 - labelCol.span }}
|
wrapperCol={{ span: 24 - labelCol.span }}
|
||||||
onFinish={onFinish}
|
|
||||||
initialValues={values}
|
initialValues={values}
|
||||||
form={form}
|
|
||||||
style={{ width: `calc(100% - ${gutter * 2}px)`, margin: `0 auto` }}
|
style={{ width: `calc(100% - ${gutter * 2}px)`, margin: `0 auto` }}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
|
|
@ -60,11 +54,12 @@ const FormBuilder = (props) => {
|
||||||
{submitButtonText}
|
{submitButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{showResetButton && (
|
{showCancelButton && (
|
||||||
<Button onClick={handleReset} style={{ marginRight: 8 }}>
|
<Button onClick={handleCancel} style={{ marginRight: 8 }}>
|
||||||
{resetButtonText}
|
{cancelButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{extraActionButtons}
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const FormItemsRenderer = ({
|
||||||
collapse = false,
|
collapse = false,
|
||||||
useAutoGenerateRequired = true,
|
useAutoGenerateRequired = true,
|
||||||
initialValues,
|
initialValues,
|
||||||
}) => {
|
}) => {
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
|
|
||||||
// 获取表单值,优先使用 initialValues
|
// 获取表单值,优先使用 initialValues
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ const ImportFile = (props) => {
|
||||||
>
|
>
|
||||||
{children && typeof children === "function" ? children({ form }) : children}
|
{children && typeof children === "function" ? children({ form }) : children}
|
||||||
<Form.Item label="附件" name="file" rules={[{ required: true, message: "附件不能为空" }]}>
|
<Form.Item label="附件" name="file" rules={[{ required: true, message: "附件不能为空" }]}>
|
||||||
<Upload accept=".xls,.xlsx" listType="text" />
|
<Upload accept=".xls,.xlsx" listType="text" maxCount={1} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,14 @@ export interface UploadProps extends Omit<AntUploadProps, "fileList"> {
|
||||||
ratio?: `${number}*${number}`;
|
ratio?: `${number}*${number}`;
|
||||||
/** 是否显示提示,默认 true */
|
/** 是否显示提示,默认 true */
|
||||||
showTip?: boolean;
|
showTip?: boolean;
|
||||||
/** 文件大小限制(单位:MB),默认 0(不限制) */
|
/** 文件大小限制(单位:MB) */
|
||||||
size?: number;
|
size?: number;
|
||||||
/** 自定义提示内容 */
|
/** 自定义提示内容 */
|
||||||
tipContent?: ReactNode;
|
tipContent?: ReactNode;
|
||||||
/** listType 为 text 时上传按钮文本,默认 "点击选择文件上传" */
|
/** listType 为 text 时上传按钮文本 */
|
||||||
uploadButtonText?: string;
|
uploadButtonText?: string;
|
||||||
|
/** 要上传的文件类型,默认为 image */
|
||||||
|
fileType?: "image" | "video" | "document";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,3 +28,7 @@ export interface UploadProps extends Omit<AntUploadProps, "fileList"> {
|
||||||
declare const Upload: FC<UploadProps>;
|
declare const Upload: FC<UploadProps>;
|
||||||
|
|
||||||
export default Upload;
|
export default Upload;
|
||||||
|
|
||||||
|
// 视频:数量默认1个,且只支持mp4格式,单个文件大小默认100M
|
||||||
|
// 文件:数量默认4个,且只支持pdf、doc、docx格式
|
||||||
|
// 图片:数量默认4个,且只支持jpg、jpeg、png格式
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { PlusOutlined, UploadOutlined } from "@ant-design/icons";
|
import { PlusOutlined, UploadOutlined, VideoCameraAddOutlined } from "@ant-design/icons";
|
||||||
import { Upload as AntUpload, Button, message, Modal } from "antd";
|
import { Upload as AntUpload, Button, message, Modal } from "antd";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|
@ -10,15 +10,16 @@ const Upload = (props) => {
|
||||||
value = [],
|
value = [],
|
||||||
onChange,
|
onChange,
|
||||||
onPreview,
|
onPreview,
|
||||||
maxCount = 1,
|
maxCount: externalMaxCount,
|
||||||
listType = "picture-card",
|
listType: externalListType,
|
||||||
accept = ["picture-card", "picture-circle", "picture"].includes(listType) ? ".jpg,.jpeg,.png" : "",
|
accept: externalAccept,
|
||||||
ratio = "",
|
ratio = "",
|
||||||
showTip = true,
|
showTip = true,
|
||||||
multiple = true,
|
multiple = true,
|
||||||
size = 0,
|
size: externalSize,
|
||||||
tipContent,
|
tipContent,
|
||||||
uploadButtonText = "点击选择文件上传",
|
uploadButtonText: externalUploadButtonText,
|
||||||
|
fileType: externalFileType,
|
||||||
formValues,
|
formValues,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -26,6 +27,90 @@ const Upload = (props) => {
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
const [previewVisible, setPreviewVisible] = useState(false);
|
||||||
const [previewImage, setPreviewImage] = useState("");
|
const [previewImage, setPreviewImage] = useState("");
|
||||||
|
|
||||||
|
// 预设的文件格式
|
||||||
|
const imageAccept = ".jpg,.jpeg,.png";
|
||||||
|
const documentAccept = ".pdf,.doc,.docx";
|
||||||
|
const videoAccept = ".mp4";
|
||||||
|
|
||||||
|
// 根据accept自动判断文件类型
|
||||||
|
const getAutoFileType = () => {
|
||||||
|
if (externalAccept) {
|
||||||
|
if (externalAccept === "*")
|
||||||
|
return "document";
|
||||||
|
const acceptList = externalAccept.split(",");
|
||||||
|
if (acceptList.some(format => videoAccept.split(",").includes(format)))
|
||||||
|
return "video";
|
||||||
|
if (acceptList.some(format => documentAccept.split(",").includes(format)))
|
||||||
|
return "document";
|
||||||
|
if (acceptList.some(format => imageAccept.split(",").includes(format)))
|
||||||
|
return "image";
|
||||||
|
return "document";
|
||||||
|
}
|
||||||
|
return "image";
|
||||||
|
};
|
||||||
|
const fileType = externalFileType || getAutoFileType();
|
||||||
|
|
||||||
|
// 文件类型判断
|
||||||
|
const isImageType = fileType === "image";
|
||||||
|
const isVideoType = fileType === "video";
|
||||||
|
const isDocumentType = fileType === "document";
|
||||||
|
|
||||||
|
// 获取listType
|
||||||
|
const getListType = () => {
|
||||||
|
if (externalListType)
|
||||||
|
return externalListType;
|
||||||
|
if (externalAccept === "*")
|
||||||
|
return "text";
|
||||||
|
if (fileType === "image")
|
||||||
|
return "picture-card";
|
||||||
|
return "text";
|
||||||
|
};
|
||||||
|
const listType = getListType();
|
||||||
|
|
||||||
|
// 获取文件格式
|
||||||
|
const getAccept = () => {
|
||||||
|
if (externalAccept)
|
||||||
|
return externalAccept === "*" ? "" : externalAccept;
|
||||||
|
if (isImageType)
|
||||||
|
return imageAccept;
|
||||||
|
if (isVideoType)
|
||||||
|
return videoAccept;
|
||||||
|
if (isDocumentType)
|
||||||
|
return documentAccept;
|
||||||
|
return imageAccept;
|
||||||
|
};
|
||||||
|
const accept = getAccept();
|
||||||
|
|
||||||
|
// 获取默认上传数量
|
||||||
|
const getMaxCount = () => {
|
||||||
|
if (externalMaxCount)
|
||||||
|
return externalMaxCount;
|
||||||
|
if (isVideoType)
|
||||||
|
return 1;
|
||||||
|
if (isImageType)
|
||||||
|
return 4;
|
||||||
|
if (isDocumentType)
|
||||||
|
return 4;
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
const maxCount = getMaxCount();
|
||||||
|
|
||||||
|
// 获取默认文件大小
|
||||||
|
const getSize = () => {
|
||||||
|
if (externalSize)
|
||||||
|
return externalSize;
|
||||||
|
if (isVideoType)
|
||||||
|
return 100;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
const size = getSize();
|
||||||
|
|
||||||
|
// 上传按钮文字
|
||||||
|
const uploadButtonText = externalUploadButtonText || (isVideoType ? "上传视频" : "上传附件");
|
||||||
|
|
||||||
|
// 文件格式提示
|
||||||
|
const acceptTip = accept.replace(/\./g, "").split(",").join("、");
|
||||||
|
|
||||||
// 生成提示信息
|
// 生成提示信息
|
||||||
const getTipText = () => {
|
const getTipText = () => {
|
||||||
if (tipContent)
|
if (tipContent)
|
||||||
|
|
@ -34,10 +119,7 @@ const Upload = (props) => {
|
||||||
const tips = [
|
const tips = [
|
||||||
`最多上传${maxCount}个文件`,
|
`最多上传${maxCount}个文件`,
|
||||||
accept
|
accept
|
||||||
? `并且只能上传${accept
|
? `并且只能上传${acceptTip}格式的文件`
|
||||||
.replace(/\./g, "")
|
|
||||||
.split(",")
|
|
||||||
.join("、")}格式的文件`
|
|
||||||
: "可以上传任意格式的文件",
|
: "可以上传任意格式的文件",
|
||||||
size ? `文件大小不能超过${size}M` : "",
|
size ? `文件大小不能超过${size}M` : "",
|
||||||
ratio ? `只能上传${ratio}分辨率的图片` : "",
|
ratio ? `只能上传${ratio}分辨率的图片` : "",
|
||||||
|
|
@ -62,7 +144,7 @@ const Upload = (props) => {
|
||||||
|
|
||||||
// 验证文件格式
|
// 验证文件格式
|
||||||
if (acceptList.length > 0 && !acceptList.includes(suffix)) {
|
if (acceptList.length > 0 && !acceptList.includes(suffix)) {
|
||||||
message.warning(`只能上传${accept}格式的文件`);
|
message.warning(`只能上传${acceptTip}格式的文件`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +189,7 @@ const Upload = (props) => {
|
||||||
|
|
||||||
// 预览文件
|
// 预览文件
|
||||||
const handlePreview = (file) => {
|
const handlePreview = (file) => {
|
||||||
if (["picture-card", "picture-circle", "picture"].includes(listType)) {
|
if (isImageType) {
|
||||||
setPreviewImage(file.url || file.thumbUrl);
|
setPreviewImage(file.url || file.thumbUrl);
|
||||||
setPreviewVisible(true);
|
setPreviewVisible(true);
|
||||||
}
|
}
|
||||||
|
|
@ -121,14 +203,19 @@ const Upload = (props) => {
|
||||||
|
|
||||||
// 上传按钮
|
// 上传按钮
|
||||||
const uploadButton
|
const uploadButton
|
||||||
= ["picture-card", "picture-circle", "picture"].includes(listType)
|
= isImageType
|
||||||
? (
|
? (
|
||||||
<div>
|
<div>
|
||||||
<PlusOutlined style={{ fontSize: 32 }} />
|
<PlusOutlined style={{ fontSize: 32 }} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<Button type="primary" icon={<UploadOutlined />}>{uploadButtonText}</Button>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={isVideoType ? <VideoCameraAddOutlined /> : <UploadOutlined />}
|
||||||
|
>
|
||||||
|
{uploadButtonText}
|
||||||
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -148,9 +235,9 @@ const Upload = (props) => {
|
||||||
</AntUpload>
|
</AntUpload>
|
||||||
{
|
{
|
||||||
showTip
|
showTip
|
||||||
? (tipContent || getTipText()) && (
|
? (getTipText()) && (
|
||||||
<div style={{ marginTop: 10, color: "#ff4d4f" }}>
|
<div style={{ marginTop: 10, color: "#ff4d4f" }}>
|
||||||
{tipContent || getTipText()}
|
{getTipText()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,24 @@ type DataType
|
||||||
| "HTMLDocument"
|
| "HTMLDocument"
|
||||||
| string; // 允许其他可能的类型
|
| string; // 允许其他可能的类型
|
||||||
|
|
||||||
|
// 定义 getFileSuffix 函数可能返回的常见类型
|
||||||
|
type FileSuffix
|
||||||
|
= | "jpg"
|
||||||
|
| "jpeg"
|
||||||
|
| "png"
|
||||||
|
| "mp4"
|
||||||
|
| "mp3"
|
||||||
|
| "pdf"
|
||||||
|
| "doc"
|
||||||
|
| "docx"
|
||||||
|
| "xls"
|
||||||
|
| "xlsx"
|
||||||
|
| "txt"
|
||||||
|
| "zip"
|
||||||
|
| "rar"
|
||||||
|
| "tar"
|
||||||
|
| string; // 允许其他可能的类型
|
||||||
|
|
||||||
// 为 findCharIndex 函数定义接口类型
|
// 为 findCharIndex 函数定义接口类型
|
||||||
interface FindCharIndexOptions {
|
interface FindCharIndexOptions {
|
||||||
/** 查找的字符串 */
|
/** 查找的字符串 */
|
||||||
|
|
@ -186,7 +204,7 @@ export function paging<T>(options: PagingOptions): T[];
|
||||||
/**
|
/**
|
||||||
* 获取文件后缀
|
* 获取文件后缀
|
||||||
*/
|
*/
|
||||||
export function getFileSuffix(name: string): string;
|
export function getFileSuffix(name: string): FileSuffix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件名称
|
* 获取文件名称
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue