zy-react-library 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/components/FormBuilder/FormBuilder.d.ts +31 -0
- package/components/FormBuilder/FormBuilder.js +41 -0
- package/components/FormBuilder/FormItemsRenderer.d.ts +107 -0
- package/components/FormBuilder/FormItemsRenderer.js +345 -0
- package/components/FormBuilder/index.d.ts +3 -0
- package/components/FormBuilder/index.js +3 -0
- package/components/PreviewImg/index.d.ts +12 -0
- package/components/PreviewImg/index.js +26 -0
- package/components/Search/index.d.ts +41 -0
- package/components/Search/index.js +125 -0
- package/components/Table/index.d.ts +23 -0
- package/components/Table/index.js +19 -0
- package/components/TooltipPreviewImg/index.d.ts +6 -0
- package/components/TooltipPreviewImg/index.js +22 -0
- package/components/Upload/index.d.ts +28 -0
- package/components/Upload/index.js +161 -0
- package/enum/formItemRender/index.d.ts +35 -0
- package/enum/formItemRender/index.js +35 -0
- package/hooks/useDownloadBlob/index.d.ts +16 -0
- package/hooks/useDownloadBlob/index.js +52 -0
- package/hooks/useDownloadFile/index.d.ts +4 -0
- package/hooks/useDownloadFile/index.js +36 -0
- package/hooks/useIsExistenceDuplicateSelection/index.d.ts +18 -0
- package/hooks/useIsExistenceDuplicateSelection/index.js +18 -0
- package/hooks/useTable/index.d.ts +75 -0
- package/hooks/useTable/index.js +162 -0
- package/package.json +33 -0
- package/regular/index.d.ts +49 -0
- package/regular/index.js +56 -0
- package/utils/index.d.ts +265 -0
- package/utils/index.js +402 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { DownOutlined, UpOutlined } from "@ant-design/icons";
|
|
2
|
+
import { Button, Col, Form, Row } from "antd";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import FormItemsRenderer from "../FormBuilder/FormItemsRenderer";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 搜索表单组件
|
|
8
|
+
*/
|
|
9
|
+
const Search = (props) => {
|
|
10
|
+
const {
|
|
11
|
+
labelCol = { span: 4 },
|
|
12
|
+
options = [],
|
|
13
|
+
values,
|
|
14
|
+
onFinish,
|
|
15
|
+
onSubmit,
|
|
16
|
+
onReset,
|
|
17
|
+
searchText = "搜索",
|
|
18
|
+
resetText = "重置",
|
|
19
|
+
showSearchButton = true,
|
|
20
|
+
showResetButton = true,
|
|
21
|
+
extraButtons,
|
|
22
|
+
form,
|
|
23
|
+
...restProps
|
|
24
|
+
} = props;
|
|
25
|
+
|
|
26
|
+
const [collapse, setCollapse] = useState(true);
|
|
27
|
+
const [span, setSpan] = useState(6);
|
|
28
|
+
const [showCollapseButton, setShowCollapseButton] = useState(false);
|
|
29
|
+
const classNameRef = useRef(`search-${Date.now()}`);
|
|
30
|
+
|
|
31
|
+
// 计算是否需要显示展开/收起按钮
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!options || options.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
|
|
36
|
+
const calculateLayout = () => {
|
|
37
|
+
const colEl = document.querySelectorAll(
|
|
38
|
+
`.${classNameRef.current}>.${window.process.env.app.antd["ant-prefix"]}-col`,
|
|
39
|
+
);
|
|
40
|
+
const colElLength = colEl.length;
|
|
41
|
+
const excludeLast = colElLength - (extraButtons ? 2 : 1);
|
|
42
|
+
|
|
43
|
+
const spanMap = { 0: 24, 1: 18, 2: 12, 3: 6 };
|
|
44
|
+
setSpan(spanMap[excludeLast % 4] || 6);
|
|
45
|
+
|
|
46
|
+
setShowCollapseButton(excludeLast > 3);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 延迟执行以确保 DOM 已渲染
|
|
50
|
+
setTimeout(calculateLayout, 0);
|
|
51
|
+
}, [options, extraButtons]);
|
|
52
|
+
|
|
53
|
+
// 处理表单提交
|
|
54
|
+
const handleSubmit = () => {
|
|
55
|
+
const values = form.getFieldsValue();
|
|
56
|
+
onFinish?.(values, "submit");
|
|
57
|
+
onSubmit?.(values);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// 处理重置
|
|
61
|
+
const handleReset = () => {
|
|
62
|
+
form.resetFields();
|
|
63
|
+
const values = form.getFieldsValue();
|
|
64
|
+
onFinish?.(values, "reset");
|
|
65
|
+
onReset?.(values);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// 切换展开/收起
|
|
69
|
+
const toggleCollapse = () => {
|
|
70
|
+
setCollapse(!collapse);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Form
|
|
75
|
+
form={form}
|
|
76
|
+
labelCol={labelCol}
|
|
77
|
+
initialValues={values}
|
|
78
|
+
{...restProps}
|
|
79
|
+
>
|
|
80
|
+
<Row className={classNameRef.current}>
|
|
81
|
+
<FormItemsRenderer
|
|
82
|
+
options={options}
|
|
83
|
+
span={6}
|
|
84
|
+
collapse={collapse}
|
|
85
|
+
useAutoGenerateRequired={false}
|
|
86
|
+
/>
|
|
87
|
+
<Col span={showCollapseButton ? (collapse ? 6 : span) : span}>
|
|
88
|
+
<Form.Item label=" " colon={false} style={{ textAlign: "right" }}>
|
|
89
|
+
{showSearchButton && (
|
|
90
|
+
<Button type="primary" onClick={handleSubmit}>
|
|
91
|
+
{searchText}
|
|
92
|
+
</Button>
|
|
93
|
+
)}
|
|
94
|
+
{showResetButton && (
|
|
95
|
+
<Button style={{ marginLeft: 8 }} onClick={handleReset}>
|
|
96
|
+
{resetText}
|
|
97
|
+
</Button>
|
|
98
|
+
)}
|
|
99
|
+
{showCollapseButton && (
|
|
100
|
+
<Button
|
|
101
|
+
type="link"
|
|
102
|
+
icon={collapse ? <DownOutlined /> : <UpOutlined />}
|
|
103
|
+
onClick={toggleCollapse}
|
|
104
|
+
style={{ marginLeft: 8 }}
|
|
105
|
+
>
|
|
106
|
+
{collapse ? "展开" : "收起"}
|
|
107
|
+
</Button>
|
|
108
|
+
)}
|
|
109
|
+
</Form.Item>
|
|
110
|
+
</Col>
|
|
111
|
+
{extraButtons && (
|
|
112
|
+
<Col span={24}>
|
|
113
|
+
<Form.Item label=" " colon={false} labelCol={{ span: 0 }}>
|
|
114
|
+
{extraButtons}
|
|
115
|
+
</Form.Item>
|
|
116
|
+
</Col>
|
|
117
|
+
)}
|
|
118
|
+
</Row>
|
|
119
|
+
</Form>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
Search.displayName = "Search";
|
|
124
|
+
|
|
125
|
+
export default Search;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ProTableProps } from "@ant-design/pro-table";
|
|
2
|
+
import type { TableProps } from "antd";
|
|
3
|
+
import type { FC } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TablePro 组件属性
|
|
7
|
+
*/
|
|
8
|
+
export type TableProProps<DataSource, U, ValueType> = Omit<TableProps, 'columns'> & ProTableProps<DataSource, U, ValueType> & {
|
|
9
|
+
/** 当一个路由下存在多个表格的情况下 需要给每一个表格设置一个唯一存储索引 若没有设置则使用默认索引,请注意缓存数据会被覆盖 */
|
|
10
|
+
storeIndex?: string;
|
|
11
|
+
/** 是否禁用内容区滚动,默认 false */
|
|
12
|
+
disabledResizer?: boolean;
|
|
13
|
+
/** 是否显示索引列,默认 true */
|
|
14
|
+
showIndex?: boolean;
|
|
15
|
+
/** 是否使用居中布局,默认 true */
|
|
16
|
+
useAlignCenter?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 表格组件
|
|
21
|
+
*/
|
|
22
|
+
declare const TablePro: <DataSource, U, ValueType = "text">(props: TableProProps<DataSource, U, ValueType>) => ReturnType<FC>;
|
|
23
|
+
export default TablePro;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Table from "@cqsjjb/jjb-react-admin-component/Table";
|
|
2
|
+
import { getIndexColumn } from "../../utils/index";
|
|
3
|
+
|
|
4
|
+
function TablePro(props) {
|
|
5
|
+
const {
|
|
6
|
+
columns = [],
|
|
7
|
+
showIndex = true,
|
|
8
|
+
useAlignCenter = true,
|
|
9
|
+
...restProps
|
|
10
|
+
} = props;
|
|
11
|
+
const storeIndex = props.storeIndex || `${window.process.env.app.antd["ant-prefix"]}_${Math.random().toString(36).substring(2)}`;
|
|
12
|
+
function calcColumns() {
|
|
13
|
+
showIndex && columns.unshift(getIndexColumn(props.pagination));
|
|
14
|
+
return columns.map(item => ({ ...item, align: useAlignCenter ? "center" : "left" }));
|
|
15
|
+
}
|
|
16
|
+
return <Table storeIndex={storeIndex} columns={calcColumns()} {...restProps} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default TablePro;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Tag, Tooltip } from "antd";
|
|
2
|
+
import PreviewImg from "~/components/PreviewImg";
|
|
3
|
+
|
|
4
|
+
const TooltipPreviewImg = (props) => {
|
|
5
|
+
const { files = [], fileUrlKey = "filePath" } = props;
|
|
6
|
+
|
|
7
|
+
const renderContent = () => {
|
|
8
|
+
return (
|
|
9
|
+
files.length > 0
|
|
10
|
+
? <PreviewImg files={files} fileUrlKey={fileUrlKey} />
|
|
11
|
+
: <span>暂无图片</span>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Tooltip placement="top" title={renderContent()}>
|
|
17
|
+
<Tag>预览</Tag>
|
|
18
|
+
</Tooltip>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default TooltipPreviewImg;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { UploadProps as AntUploadProps, UploadFile } from "antd/es/upload";
|
|
2
|
+
import type { FC, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Upload 组件属性
|
|
6
|
+
*/
|
|
7
|
+
export interface UploadProps extends Omit<AntUploadProps, "fileList"> {
|
|
8
|
+
/** 文件列表 */
|
|
9
|
+
value?: UploadFile[];
|
|
10
|
+
/** 图片分辨率限制,如 "1920*1080" */
|
|
11
|
+
ratio?: `${number}*${number}`;
|
|
12
|
+
/** 是否显示提示,默认 true */
|
|
13
|
+
showTip?: boolean;
|
|
14
|
+
/** 文件大小限制(单位:MB),默认 0(不限制) */
|
|
15
|
+
size?: number;
|
|
16
|
+
/** 自定义提示内容 */
|
|
17
|
+
tipContent?: ReactNode;
|
|
18
|
+
/** listType 为 text 时上传按钮文本,默认 "点击选择文件上传" */
|
|
19
|
+
uploadButtonText?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 文件上传组件
|
|
24
|
+
* 支持文件格式、大小、分辨率验证,支持图片预览
|
|
25
|
+
*/
|
|
26
|
+
declare const Upload: FC<UploadProps>;
|
|
27
|
+
|
|
28
|
+
export default Upload;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { PlusOutlined } from "@ant-design/icons";
|
|
2
|
+
import { Upload as AntUpload, Button, message, Modal } from "antd";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 文件上传组件
|
|
7
|
+
*/
|
|
8
|
+
const Upload = (props) => {
|
|
9
|
+
const {
|
|
10
|
+
value = [],
|
|
11
|
+
onChange,
|
|
12
|
+
onPreview,
|
|
13
|
+
maxCount = 1,
|
|
14
|
+
listType = "text",
|
|
15
|
+
accept = "",
|
|
16
|
+
ratio = "",
|
|
17
|
+
showTip = true,
|
|
18
|
+
multiple = true,
|
|
19
|
+
size = 0,
|
|
20
|
+
tipContent,
|
|
21
|
+
uploadButtonText = "点击选择文件上传",
|
|
22
|
+
...restProps
|
|
23
|
+
} = props;
|
|
24
|
+
|
|
25
|
+
const [previewVisible, setPreviewVisible] = useState(false);
|
|
26
|
+
const [previewImage, setPreviewImage] = useState("");
|
|
27
|
+
|
|
28
|
+
// 生成提示信息
|
|
29
|
+
const getTipText = () => {
|
|
30
|
+
if (tipContent)
|
|
31
|
+
return tipContent;
|
|
32
|
+
|
|
33
|
+
const tips = [
|
|
34
|
+
`最多上传${maxCount}个文件`,
|
|
35
|
+
accept
|
|
36
|
+
? `并且只能上传${accept
|
|
37
|
+
.replace(/\./g, "")
|
|
38
|
+
.split(",")
|
|
39
|
+
.join("、")}格式的文件`
|
|
40
|
+
: "可以上传任意格式的文件",
|
|
41
|
+
size ? `文件大小不能超过${size}M` : "",
|
|
42
|
+
ratio ? `只能上传${ratio}分辨率的图片` : "",
|
|
43
|
+
].filter(Boolean);
|
|
44
|
+
|
|
45
|
+
return `${tips.join(",")}。`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleBeforeUpload = () => {
|
|
49
|
+
return false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 文件状态改变
|
|
53
|
+
const handleChange = ({ file, fileList }) => {
|
|
54
|
+
const acceptList = accept ? accept.split(",") : [];
|
|
55
|
+
const ratioArr = ratio ? ratio.split("*") : [];
|
|
56
|
+
const suffix = file.name.substring(
|
|
57
|
+
file.name.lastIndexOf("."),
|
|
58
|
+
file.name.length,
|
|
59
|
+
);
|
|
60
|
+
const maxSize = size * 1024 * 1024;
|
|
61
|
+
|
|
62
|
+
// 验证文件格式
|
|
63
|
+
if (acceptList.length > 0 && !acceptList.includes(suffix)) {
|
|
64
|
+
message.warning(`只能上传${accept}格式的文件`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 验证文件大小
|
|
69
|
+
if (maxSize && file.size > maxSize) {
|
|
70
|
+
message.warning(`文件大小不能超过${size}M`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 验证图片分辨率
|
|
75
|
+
if (ratioArr.length === 2 && file.type?.startsWith("image/")) {
|
|
76
|
+
const img = new Image();
|
|
77
|
+
img.src = file.url || file.thumbUrl;
|
|
78
|
+
img.onload = () => {
|
|
79
|
+
if (img.width !== +ratioArr[0] || img.height !== +ratioArr[1]) {
|
|
80
|
+
message.warning(`只能上传${ratio}分辨率的图片`);
|
|
81
|
+
const filtered = fileList.filter(item => item.uid !== file.uid);
|
|
82
|
+
onChange?.(filtered);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
onChange?.(fileList);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
onChange?.(fileList);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 预览文件
|
|
94
|
+
const handlePreview = (file) => {
|
|
95
|
+
if (["picture-card", "picture-circle", "picture"].includes(listType)) {
|
|
96
|
+
setPreviewImage(file.url || file.thumbUrl);
|
|
97
|
+
setPreviewVisible(true);
|
|
98
|
+
}
|
|
99
|
+
onPreview?.(file);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// 关闭预览
|
|
103
|
+
const handleCancel = () => {
|
|
104
|
+
setPreviewVisible(false);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// 上传按钮
|
|
108
|
+
const uploadButton
|
|
109
|
+
= ["picture-card", "picture-circle", "picture"].includes(listType)
|
|
110
|
+
? (
|
|
111
|
+
<div>
|
|
112
|
+
<PlusOutlined style={{ fontSize: 32 }} />
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
: (
|
|
116
|
+
<Button type="primary">{uploadButtonText}</Button>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<>
|
|
121
|
+
<AntUpload
|
|
122
|
+
fileList={value}
|
|
123
|
+
multiple={multiple}
|
|
124
|
+
maxCount={maxCount}
|
|
125
|
+
listType={listType}
|
|
126
|
+
accept={accept}
|
|
127
|
+
onChange={handleChange}
|
|
128
|
+
onPreview={handlePreview}
|
|
129
|
+
beforeUpload={handleBeforeUpload}
|
|
130
|
+
{...restProps}
|
|
131
|
+
>
|
|
132
|
+
{value.length >= maxCount ? null : uploadButton}
|
|
133
|
+
</AntUpload>
|
|
134
|
+
{
|
|
135
|
+
showTip
|
|
136
|
+
? (tipContent || getTipText()) && (
|
|
137
|
+
<div style={{ marginTop: 10, color: "#ff4d4f" }}>
|
|
138
|
+
{tipContent || getTipText()}
|
|
139
|
+
</div>
|
|
140
|
+
)
|
|
141
|
+
: null
|
|
142
|
+
}
|
|
143
|
+
<Modal
|
|
144
|
+
open={previewVisible}
|
|
145
|
+
title="查看图片"
|
|
146
|
+
footer={null}
|
|
147
|
+
onCancel={handleCancel}
|
|
148
|
+
>
|
|
149
|
+
<img
|
|
150
|
+
alt="preview"
|
|
151
|
+
style={{ width: "100%", objectFit: "scale-down" }}
|
|
152
|
+
src={previewImage}
|
|
153
|
+
/>
|
|
154
|
+
</Modal>
|
|
155
|
+
</>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
Upload.displayName = "Upload";
|
|
160
|
+
|
|
161
|
+
export default Upload;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表单项类型枚举
|
|
3
|
+
*/
|
|
4
|
+
export declare const FORM_ITEM_RENDER_ENUM: {
|
|
5
|
+
/** 映射为 antd Input */
|
|
6
|
+
INPUT: "input";
|
|
7
|
+
/** 映射为 antd Input.TextArea */
|
|
8
|
+
TEXTAREA: "textarea";
|
|
9
|
+
/** 映射为 antd InputNumber */
|
|
10
|
+
INPUT_NUMBER: "inputNumber";
|
|
11
|
+
/** 映射为 antd InputNumber */
|
|
12
|
+
NUMBER: "number";
|
|
13
|
+
/** 映射为 antd Select */
|
|
14
|
+
SELECT: "select";
|
|
15
|
+
/** 映射为 antd Radio.Group */
|
|
16
|
+
RADIO: "radio";
|
|
17
|
+
/** 映射为 antd Checkbox.Group */
|
|
18
|
+
CHECKBOX: "checkbox";
|
|
19
|
+
/** 映射为 antd DatePicker,日期格式为YYYY-MM-DD */
|
|
20
|
+
DATE: "date";
|
|
21
|
+
/** 映射为 antd DatePicker.MonthPicker,日期格式为YYYY-MM */
|
|
22
|
+
DATE_MONTH: "dateMonth";
|
|
23
|
+
/** 映射为 antd DatePicker.YearPicker,日期格式为YYYY */
|
|
24
|
+
DATE_YEAR: "dateYear";
|
|
25
|
+
/** 映射为 antd DatePicker.WeekPicker,日期格式为YYYY-wo */
|
|
26
|
+
DATE_WEEK: "dateWeek";
|
|
27
|
+
/** 映射为 antd DatePicker.RangePicker,日期格式为YYYY-MM-DD */
|
|
28
|
+
DATE_RANGE: "dateRange";
|
|
29
|
+
/** 映射为 antd DatePicker,日期格式为YYYY-MM-DD HH:mm:ss */
|
|
30
|
+
DATETIME: "datetime";
|
|
31
|
+
/** 映射为 antd DatePicker.RangePicker,日期格式为YYYY-MM-DD HH:mm:ss */
|
|
32
|
+
DATETIME_RANGE: "datetimeRange";
|
|
33
|
+
/** 映射为 antd Divider */
|
|
34
|
+
DIVIDER: "divider";
|
|
35
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表单项类型枚举
|
|
3
|
+
*/
|
|
4
|
+
export const FORM_ITEM_RENDER_ENUM = {
|
|
5
|
+
/** 映射为 antd Input */
|
|
6
|
+
INPUT: "input",
|
|
7
|
+
/** 映射为 antd Input.TextArea */
|
|
8
|
+
TEXTAREA: "textarea",
|
|
9
|
+
/** 映射为 antd InputNumber */
|
|
10
|
+
INPUT_NUMBER: "inputNumber",
|
|
11
|
+
/** 映射为 antd InputNumber */
|
|
12
|
+
NUMBER: "number",
|
|
13
|
+
/** 映射为 antd Select */
|
|
14
|
+
SELECT: "select",
|
|
15
|
+
/** 映射为 antd Radio.Group */
|
|
16
|
+
RADIO: "radio",
|
|
17
|
+
/** 映射为 antd Checkbox.Group */
|
|
18
|
+
CHECKBOX: "checkbox",
|
|
19
|
+
/** 映射为 antd DatePicker,日期格式为YYYY-MM-DD */
|
|
20
|
+
DATE: "date",
|
|
21
|
+
/** 映射为 antd DatePicker.MonthPicker,日期格式为YYYY-MM */
|
|
22
|
+
DATE_MONTH: "dateMonth",
|
|
23
|
+
/** 映射为 antd DatePicker.YearPicker,日期格式为YYYY */
|
|
24
|
+
DATE_YEAR: "dateYear",
|
|
25
|
+
/** 映射为 antd DatePicker.WeekPicker,日期格式为YYYY-wo */
|
|
26
|
+
DATE_WEEK: "dateWeek",
|
|
27
|
+
/** 映射为 antd DatePicker.RangePicker,日期格式为YYYY-MM-DD */
|
|
28
|
+
DATE_RANGE: "dateRange",
|
|
29
|
+
/** 映射为 antd DatePicker,日期格式为YYYY-MM-DD HH:mm:ss */
|
|
30
|
+
DATETIME: "datetime",
|
|
31
|
+
/** 映射为 antd DatePicker.RangePicker,日期格式为YYYY-MM-DD HH:mm:ss */
|
|
32
|
+
DATETIME_RANGE: "datetimeRange",
|
|
33
|
+
/** 映射为 antd Divider */
|
|
34
|
+
DIVIDER: "divider",
|
|
35
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface UseDownloadBlobOptions {
|
|
2
|
+
/** 下载文件的自定义文件名(不含后缀),默认为当前时间戳 */
|
|
3
|
+
name?: string;
|
|
4
|
+
/** Blob 对象的 MIME 类型,默认为 Excel 类型 */
|
|
5
|
+
type?: string;
|
|
6
|
+
/** 请求时携带的查询参数对象 */
|
|
7
|
+
params?: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 下载Blob流文件
|
|
12
|
+
*/
|
|
13
|
+
export default function useDownloadBlob(
|
|
14
|
+
url: string,
|
|
15
|
+
options?: UseDownloadBlobOptions
|
|
16
|
+
): Promise<any>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { message } from "antd";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import { getFileUrl } from "../../utils/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 下载Blob流文件
|
|
7
|
+
*/
|
|
8
|
+
export default function useDownloadBlob(
|
|
9
|
+
url,
|
|
10
|
+
options = { name: "", type: "", params: {} },
|
|
11
|
+
) {
|
|
12
|
+
const fileUrl = getFileUrl();
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const finalUrl = !url.includes(fileUrl) ? fileUrl + url : url;
|
|
15
|
+
Object.entries(options.params).forEach(([key, value]) => {
|
|
16
|
+
finalUrl.searchParams.append(key, value);
|
|
17
|
+
});
|
|
18
|
+
fetch(finalUrl, {
|
|
19
|
+
method: "GET",
|
|
20
|
+
mode: "cors",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
.then((response) => {
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error("Network response was not ok");
|
|
28
|
+
}
|
|
29
|
+
return response.blob();
|
|
30
|
+
})
|
|
31
|
+
.then((blob) => {
|
|
32
|
+
const finalBlob = new Blob([blob], {
|
|
33
|
+
type: options.type || "application/vnd.ms-excel",
|
|
34
|
+
});
|
|
35
|
+
const downloadElement = document.createElement("a");
|
|
36
|
+
const href = window.URL.createObjectURL(finalBlob);
|
|
37
|
+
downloadElement.style.display = "none";
|
|
38
|
+
downloadElement.href = href;
|
|
39
|
+
downloadElement.download
|
|
40
|
+
= options.name || dayjs().format("YYYY-MM-DD HH:mm:ss");
|
|
41
|
+
document.body.appendChild(downloadElement);
|
|
42
|
+
downloadElement.click();
|
|
43
|
+
document.body.removeChild(downloadElement);
|
|
44
|
+
window.URL.revokeObjectURL(href);
|
|
45
|
+
resolve({ data: finalBlob });
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
message.error("导出失败");
|
|
49
|
+
reject(err);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { message, Modal } from "antd";
|
|
2
|
+
import { getFileName, getFileSuffix, getFileUrl } from "../../utils/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 下载文件
|
|
6
|
+
*/
|
|
7
|
+
export default function useDownloadFile(url, name) {
|
|
8
|
+
if (!url)
|
|
9
|
+
throw new Error("没有下载地址");
|
|
10
|
+
Modal.confirm({ title: "提示", content: "确定要下载此文件吗?", onOk: () => {
|
|
11
|
+
const fileUrl = getFileUrl();
|
|
12
|
+
if (name) {
|
|
13
|
+
if (!getFileSuffix(url))
|
|
14
|
+
name = name + getFileSuffix(url);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
name = getFileName(url);
|
|
18
|
+
}
|
|
19
|
+
fetch(!url.includes(fileUrl) ? fileUrl + url : url)
|
|
20
|
+
.then(res => res.blob())
|
|
21
|
+
.then((blob) => {
|
|
22
|
+
const a = document.createElement("a");
|
|
23
|
+
document.body.appendChild(a);
|
|
24
|
+
a.style.display = "none";
|
|
25
|
+
const url = window.URL.createObjectURL(blob);
|
|
26
|
+
a.href = url;
|
|
27
|
+
a.download = `${name}`;
|
|
28
|
+
a.click();
|
|
29
|
+
document.body.removeChild(a);
|
|
30
|
+
window.URL.revokeObjectURL(url);
|
|
31
|
+
})
|
|
32
|
+
.catch(() => {
|
|
33
|
+
message.error("下载失败");
|
|
34
|
+
});
|
|
35
|
+
} });
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useIsExistenceDuplicateSelection 钩子的选项参数
|
|
3
|
+
*/
|
|
4
|
+
interface UseIsExistenceDuplicateSelectionOptions<T> {
|
|
5
|
+
/** 需要检查重复项的目标数组 */
|
|
6
|
+
data: T[];
|
|
7
|
+
/** 用于去重判断的对象属性名 */
|
|
8
|
+
key: keyof T;
|
|
9
|
+
/** 可选的错误提示信息 */
|
|
10
|
+
message?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 检查数组中是否存在重复项
|
|
15
|
+
*/
|
|
16
|
+
export default function useIsExistenceDuplicateSelection<T>(
|
|
17
|
+
options: UseIsExistenceDuplicateSelectionOptions<T>
|
|
18
|
+
): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { message as antdMessage } from "antd";
|
|
2
|
+
import { uniqBy } from "lodash-es";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 检查数组中是否存在重复项
|
|
6
|
+
*/
|
|
7
|
+
export default function useIsExistenceDuplicateSelection(options) {
|
|
8
|
+
const { data, key, message = "存在重复项,请勿重复选择" } = options;
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
if (uniqBy(data, key).length !== data.length) {
|
|
11
|
+
antdMessage.error(message);
|
|
12
|
+
reject(new Error(message));
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
resolve();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { AntdTableOptions, AntdTableResult, Data, Params, Service } from "ahooks/lib/useAntdTable/types";
|
|
2
|
+
import type { FormInstance } from "antd/es/form";
|
|
3
|
+
|
|
4
|
+
type FormValues = Record<string, any>;
|
|
5
|
+
/**
|
|
6
|
+
* useTable 钩子的选项参数
|
|
7
|
+
*/
|
|
8
|
+
export interface UseTableOptions<TData extends Data, TParams extends Params> extends Omit<AntdTableOptions<TData, TParams>, "defaultParams" | "form"> {
|
|
9
|
+
/** 是否使用分页,默认是 */
|
|
10
|
+
usePagination?: boolean;
|
|
11
|
+
/** 默认分页参数,默认 { current: 1; pageSize: 10 } */
|
|
12
|
+
defaultPagination?: { current: number; pageSize: number };
|
|
13
|
+
/** 是否使用存储查询条件,默认是 */
|
|
14
|
+
useStorageQueryCriteria?: boolean;
|
|
15
|
+
/** 额外参数 */
|
|
16
|
+
params?: FormValues | (() => FormValues);
|
|
17
|
+
/** 表单数据转换函数,在每次请求之前调用,接收当前搜索的表单项,要求返回一个对象 */
|
|
18
|
+
transform?: (formData: FormValues) => FormValues;
|
|
19
|
+
/** 回调函数 */
|
|
20
|
+
callback?: (list: any[], data: any) => void;
|
|
21
|
+
/** 表单实例(通过 Form.useForm() 创建) */
|
|
22
|
+
form?: FormInstance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 基础分页配置接口
|
|
27
|
+
*/
|
|
28
|
+
export interface BasePaginationConfig {
|
|
29
|
+
/** 当前页码 */
|
|
30
|
+
current: number;
|
|
31
|
+
/** 每页数量 */
|
|
32
|
+
pageSize: number;
|
|
33
|
+
/** 总数 */
|
|
34
|
+
total: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 扩展分页配置接口
|
|
39
|
+
*/
|
|
40
|
+
export interface ExtendedPaginationConfig extends BasePaginationConfig {
|
|
41
|
+
/** 显示快速跳转 */
|
|
42
|
+
showQuickJumper: boolean;
|
|
43
|
+
/** 显示页码选择器 */
|
|
44
|
+
showSizeChanger: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* useTable 钩子返回的结果
|
|
49
|
+
*/
|
|
50
|
+
export interface UseTableResult<TData extends Data, TParams extends Params> extends AntdTableResult<TData, TParams> {
|
|
51
|
+
/** 表格属性 */
|
|
52
|
+
tableProps: {
|
|
53
|
+
/** 表格数据 */
|
|
54
|
+
dataSource: TData["list"];
|
|
55
|
+
/** 表格加载状态 */
|
|
56
|
+
loading: boolean;
|
|
57
|
+
/** 表格改变 */
|
|
58
|
+
onChange: (pagination: any, filters?: any, sorter?: any) => void;
|
|
59
|
+
/** 分页属性 */
|
|
60
|
+
pagination: false | ExtendedPaginationConfig;
|
|
61
|
+
[key: string]: any;
|
|
62
|
+
};
|
|
63
|
+
/** 查询方法,等于直接调用 search.submit */
|
|
64
|
+
getData: () => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 自定义 useTable,继承 ahooks 的 useAntdTable,根据需求进行扩展
|
|
69
|
+
*/
|
|
70
|
+
declare function useTable<TData extends Data, TParams extends Params>(
|
|
71
|
+
service: Service<TData, TParams>,
|
|
72
|
+
options?: UseTableOptions<TData, TParams>
|
|
73
|
+
): UseTableResult<TData, TParams>;
|
|
74
|
+
|
|
75
|
+
export default useTable;
|