@cqsjjb/jjb-react-admin-component 3.3.1-beta.5 → 3.3.1-beta.6
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/package.json +1 -1
- package/AMap/index.js +0 -372
- package/AdaptiveTree/index.js +0 -185
- package/BMap/index.js +0 -167
- package/ControlWrapper/index.js +0 -65
- package/ControlWrapper/index.less +0 -38
- package/Editor/index.js +0 -310
- package/ErrorBoundary/index.js +0 -35
- package/FileUploader/index.js +0 -312
- package/FormilyDescriptions/index.js +0 -313
- package/FormilyRenderer/index.js +0 -58
- package/ImageCropper/index.js +0 -116
- package/ImageUploader/index.js +0 -310
- package/ListDataContainer/index.js +0 -211
- package/MediaQuery/index.js +0 -42
- package/PageLayout/index.js +0 -60
- package/SearchForm/index.js +0 -95
- package/Table/index.js +0 -118
- package/TableAction/index.js +0 -30
package/ImageCropper/index.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import MD5 from 'spark-md5';
|
|
2
|
-
import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
|
3
|
-
import Cropper from 'react-cropper';
|
|
4
|
-
import 'cropperjs/dist/cropper.css';
|
|
5
|
-
import { Empty } from 'antd';
|
|
6
|
-
import { tools } from '@cqsjjb/jjb-common-lib';
|
|
7
|
-
import { isHttpUrl } from '../tools/index.js';
|
|
8
|
-
const ImageCropper = /*#__PURE__*/forwardRef((props, ref) => {
|
|
9
|
-
const {
|
|
10
|
-
width = 400,
|
|
11
|
-
height = 400,
|
|
12
|
-
aspect = 16 / 9,
|
|
13
|
-
locked = false,
|
|
14
|
-
resource
|
|
15
|
-
} = props;
|
|
16
|
-
const cropRef = useRef(null);
|
|
17
|
-
const [src, setSrc] = useState(undefined);
|
|
18
|
-
|
|
19
|
-
// 更新 src
|
|
20
|
-
const onUpdateSrc = () => {
|
|
21
|
-
if (resource instanceof File) {
|
|
22
|
-
tools.getFileToBase64(resource).then(setSrc);
|
|
23
|
-
} else if (/^data:image\/(png|jpg|jpeg);base64/.test(resource) || isHttpUrl(resource)) {
|
|
24
|
-
setSrc(resource);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// 初始化或资源变化时更新
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
onUpdateSrc();
|
|
31
|
-
}, [resource]);
|
|
32
|
-
|
|
33
|
-
// 暴露给父组件的方法
|
|
34
|
-
useImperativeHandle(ref, () => ({
|
|
35
|
-
toDataURL: (quality = 1) => {
|
|
36
|
-
const md5 = new MD5();
|
|
37
|
-
try {
|
|
38
|
-
const base64 = cropRef.current.cropper.getCroppedCanvas().toDataURL('image/png', quality);
|
|
39
|
-
md5.appendBinary(base64);
|
|
40
|
-
return {
|
|
41
|
-
md5: md5.end(),
|
|
42
|
-
base64
|
|
43
|
-
};
|
|
44
|
-
} catch (e) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
toDataFile: () => {
|
|
49
|
-
try {
|
|
50
|
-
const {
|
|
51
|
-
md5,
|
|
52
|
-
base64
|
|
53
|
-
} = ref.current.toDataURL();
|
|
54
|
-
return {
|
|
55
|
-
md5,
|
|
56
|
-
file: tools.getBase64ToFile(base64, `${tools.createOnlyKey()}.png`)
|
|
57
|
-
};
|
|
58
|
-
} catch (e) {
|
|
59
|
-
return {
|
|
60
|
-
file: null,
|
|
61
|
-
md5: null
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
toBlobURL: () => {
|
|
66
|
-
try {
|
|
67
|
-
return window.URL.createObjectURL(ref.current.toDataFile().file);
|
|
68
|
-
} catch (e) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
toDataImageText: () => {
|
|
73
|
-
try {
|
|
74
|
-
return `<img alt="img" src="${ref.current.toDataURL().base64}"/>`;
|
|
75
|
-
} catch (e) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
toDataImageInstance: () => {
|
|
80
|
-
return new Promise(resolve => {
|
|
81
|
-
const image = new Image();
|
|
82
|
-
image.src = ref.current.toDataURL().base64;
|
|
83
|
-
image.onload = () => resolve({
|
|
84
|
-
src: image.src,
|
|
85
|
-
width: image.width,
|
|
86
|
-
height: image.height,
|
|
87
|
-
instance: image
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}));
|
|
92
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
93
|
-
style: {
|
|
94
|
-
width,
|
|
95
|
-
height,
|
|
96
|
-
position: 'relative'
|
|
97
|
-
}
|
|
98
|
-
}, src ? /*#__PURE__*/React.createElement(Cropper, {
|
|
99
|
-
ref: cropRef,
|
|
100
|
-
src: src,
|
|
101
|
-
style: {
|
|
102
|
-
width,
|
|
103
|
-
height
|
|
104
|
-
},
|
|
105
|
-
aspectRatio: aspect,
|
|
106
|
-
cropBoxResizable: !locked
|
|
107
|
-
}) : /*#__PURE__*/React.createElement(Empty, {
|
|
108
|
-
image: Empty.PRESENTED_IMAGE_SIMPLE,
|
|
109
|
-
style: {
|
|
110
|
-
margin: 0,
|
|
111
|
-
paddingTop: (height - 70) / 2 - 40
|
|
112
|
-
},
|
|
113
|
-
description: "\u672A\u5BFC\u5165\u56FE\u7247\u8D44\u6E90\uFF0C\u65E0\u6CD5\u88C1\u526A"
|
|
114
|
-
}));
|
|
115
|
-
});
|
|
116
|
-
export default ImageCropper;
|
package/ImageUploader/index.js
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import MD5 from 'spark-md5';
|
|
2
|
-
import React, { useState, useRef, useCallback } from 'react';
|
|
3
|
-
import { tools } from '@cqsjjb/jjb-common-lib';
|
|
4
|
-
import { Button, Col, Image as ImageFunc, Modal, Row, message } from 'antd';
|
|
5
|
-
import { PlusOutlined, EyeOutlined, DeleteOutlined, SyncOutlined, ScissorOutlined } from '@ant-design/icons';
|
|
6
|
-
import ImageCropper from '../ImageCropper';
|
|
7
|
-
import './index.less';
|
|
8
|
-
import { getToken, getViewAsp, getAppKey, isHttpUrl } from '../tools/index.js';
|
|
9
|
-
const {
|
|
10
|
-
toObject,
|
|
11
|
-
isPromise,
|
|
12
|
-
isFunction,
|
|
13
|
-
parseObject
|
|
14
|
-
} = tools;
|
|
15
|
-
const prefixCls = window.process?.env?.app?.antd['ant-prefix'] || 'ant';
|
|
16
|
-
const FILE_TYPE = ['image/png', 'image/jpg', 'image/jpeg'];
|
|
17
|
-
const FILE_TYPE_POWER = [...FILE_TYPE, 'image/svg+xml'];
|
|
18
|
-
const ImageUploader = props => {
|
|
19
|
-
const [loading, setLoading] = useState(false);
|
|
20
|
-
const [preview, setPreview] = useState(false);
|
|
21
|
-
const [visible, setVisible] = useState(false);
|
|
22
|
-
const [resource, setResource] = useState(undefined);
|
|
23
|
-
const cropperRef = useRef(null);
|
|
24
|
-
const fileInputRef = useRef(null);
|
|
25
|
-
const algorithm = (() => {
|
|
26
|
-
const value = document.documentElement.style.getPropertyValue('--saas-algorithm');
|
|
27
|
-
return value ? value === '#FFF' ? 'default' : 'dark' : 'default';
|
|
28
|
-
})();
|
|
29
|
-
const onLoadFile = useCallback(file => {
|
|
30
|
-
const md5 = new MD5();
|
|
31
|
-
const {
|
|
32
|
-
svg = false
|
|
33
|
-
} = props;
|
|
34
|
-
return new Promise(resolve => {
|
|
35
|
-
const reader = new FileReader();
|
|
36
|
-
reader.onload = e => {
|
|
37
|
-
md5.appendBinary(e.target.result);
|
|
38
|
-
const isImage = (svg ? FILE_TYPE_POWER : FILE_TYPE).includes(file.type);
|
|
39
|
-
if (isImage) {
|
|
40
|
-
const image = new Image();
|
|
41
|
-
const base64 = e.target.result;
|
|
42
|
-
image.src = base64;
|
|
43
|
-
image.onload = () => {
|
|
44
|
-
resolve({
|
|
45
|
-
file,
|
|
46
|
-
base64,
|
|
47
|
-
md5: md5.end(),
|
|
48
|
-
size: file.size,
|
|
49
|
-
type: file.type,
|
|
50
|
-
name: file.name,
|
|
51
|
-
width: image.width,
|
|
52
|
-
height: image.height
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
|
-
} else {
|
|
56
|
-
resolve({
|
|
57
|
-
file,
|
|
58
|
-
md5: md5.end(),
|
|
59
|
-
size: file.size,
|
|
60
|
-
type: file.type,
|
|
61
|
-
name: file.name,
|
|
62
|
-
width: 0,
|
|
63
|
-
height: 0,
|
|
64
|
-
base64: undefined
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
reader.readAsDataURL(file);
|
|
69
|
-
});
|
|
70
|
-
}, [props]);
|
|
71
|
-
const onCompression = useCallback(data => {
|
|
72
|
-
const {
|
|
73
|
-
compressionConfig
|
|
74
|
-
} = props;
|
|
75
|
-
return new Promise(resolve => {
|
|
76
|
-
const URL = window.URL || window.webkitURL;
|
|
77
|
-
const {
|
|
78
|
-
quality = 0.8
|
|
79
|
-
} = toObject(compressionConfig);
|
|
80
|
-
const img = new Image();
|
|
81
|
-
const canvas = document.createElement('canvas');
|
|
82
|
-
const context = canvas.getContext('2d');
|
|
83
|
-
onLoadFile(data.file).then(res => {
|
|
84
|
-
img.width = res.width;
|
|
85
|
-
img.height = res.height;
|
|
86
|
-
img.src = URL.createObjectURL(data.file);
|
|
87
|
-
img.onload = () => {
|
|
88
|
-
img.onload = null;
|
|
89
|
-
canvas.width = img.width;
|
|
90
|
-
canvas.height = img.height;
|
|
91
|
-
context.drawImage(img, 0, 0, img.width, img.height);
|
|
92
|
-
canvas.toBlob(blob => {
|
|
93
|
-
const newFile = new File([blob], data.name, {
|
|
94
|
-
type: blob.type
|
|
95
|
-
});
|
|
96
|
-
resolve({
|
|
97
|
-
...data,
|
|
98
|
-
file: newFile,
|
|
99
|
-
size: newFile.size
|
|
100
|
-
});
|
|
101
|
-
}, 'image/png', quality);
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}, [onLoadFile, props]);
|
|
106
|
-
const onUploadHttp = useCallback(transfer => {
|
|
107
|
-
const {
|
|
108
|
-
API_HOST
|
|
109
|
-
} = window.process.env.app;
|
|
110
|
-
const {
|
|
111
|
-
data = {},
|
|
112
|
-
action,
|
|
113
|
-
headers: $headers = {},
|
|
114
|
-
needToken = true,
|
|
115
|
-
customRequest,
|
|
116
|
-
fieldName = 'file',
|
|
117
|
-
onChange
|
|
118
|
-
} = props;
|
|
119
|
-
const requestURL = isHttpUrl(action) ? action : `${API_HOST}${action}`;
|
|
120
|
-
const xhr = new XMLHttpRequest();
|
|
121
|
-
const form = new FormData();
|
|
122
|
-
const headers = toObject($headers);
|
|
123
|
-
headers.appKey = getAppKey();
|
|
124
|
-
if (needToken) {
|
|
125
|
-
headers.token = getToken();
|
|
126
|
-
}
|
|
127
|
-
transfer.md5 && form.append('md5', transfer.md5);
|
|
128
|
-
setLoading(true);
|
|
129
|
-
xhr.open('POST', requestURL);
|
|
130
|
-
Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key]));
|
|
131
|
-
xhr.onreadystatechange = () => {
|
|
132
|
-
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
133
|
-
setLoading(false);
|
|
134
|
-
const res = parseObject(xhr.responseText);
|
|
135
|
-
if (isFunction(customRequest)) {
|
|
136
|
-
const fn = customRequest(res);
|
|
137
|
-
if (isPromise(fn)) {
|
|
138
|
-
fn.then(value => onChange && onChange(value));
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
const {
|
|
142
|
-
success,
|
|
143
|
-
errMessage
|
|
144
|
-
} = res;
|
|
145
|
-
if (success) {
|
|
146
|
-
const result = toObject(res.data);
|
|
147
|
-
if (typeof onChange === 'undefined') {
|
|
148
|
-
console.warn('ImageUploader警告:缺少必要的onChange回调,可能无法正确显示图片!');
|
|
149
|
-
}
|
|
150
|
-
onChange && onChange({
|
|
151
|
-
fileId: result.fileId,
|
|
152
|
-
fileUrl: result.fileUrl
|
|
153
|
-
});
|
|
154
|
-
message.success('上传成功!');
|
|
155
|
-
} else {
|
|
156
|
-
message.error(errMessage || '未知错误,上传失败,请检查!');
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
form.append(fieldName, transfer.file);
|
|
162
|
-
Object.keys(data).forEach(key => form.append(key, data[key]));
|
|
163
|
-
xhr.send(form);
|
|
164
|
-
}, [props]);
|
|
165
|
-
const onUpload = useCallback(e => {
|
|
166
|
-
const target = e.target;
|
|
167
|
-
const files = target.files;
|
|
168
|
-
const file = files[0];
|
|
169
|
-
const {
|
|
170
|
-
wh = '50*50',
|
|
171
|
-
svg = false,
|
|
172
|
-
size = 5120,
|
|
173
|
-
cropper = false,
|
|
174
|
-
compression = false
|
|
175
|
-
} = props;
|
|
176
|
-
onLoadFile(file).then(load => {
|
|
177
|
-
const [w, h] = getViewAsp(wh);
|
|
178
|
-
const fileSize = load.size;
|
|
179
|
-
const fileType = load.type;
|
|
180
|
-
if (!(svg ? FILE_TYPE_POWER : FILE_TYPE).includes(fileType)) {
|
|
181
|
-
message.error(`文件格式错误,不是图片类型,支持.PNG、.JPG、.JPEG${svg ? '、.SVG' : ''}`);
|
|
182
|
-
target.value = null;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (fileSize / 1024 > size) {
|
|
186
|
-
message.error(`图片大小超过最大值(${size}KB),请重新选择`);
|
|
187
|
-
target.value = null;
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (cropper) {
|
|
191
|
-
setResource(load.file);
|
|
192
|
-
setVisible(true);
|
|
193
|
-
} else {
|
|
194
|
-
if (load.width < w || load.height < h) {
|
|
195
|
-
message.error(`图片尺寸不能小于${w}x${h}`);
|
|
196
|
-
target.value = null;
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (compression && fileSize / 1024 > 200) {
|
|
200
|
-
onCompression(load).then(res => onUploadHttp(res));
|
|
201
|
-
target.value = null;
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
onUploadHttp(load);
|
|
205
|
-
}
|
|
206
|
-
target.value = null;
|
|
207
|
-
});
|
|
208
|
-
}, [props, onLoadFile, onCompression, onUploadHttp]);
|
|
209
|
-
const {
|
|
210
|
-
tip = '点击上传',
|
|
211
|
-
svg = false,
|
|
212
|
-
value,
|
|
213
|
-
disabled = false,
|
|
214
|
-
width = 120,
|
|
215
|
-
height = 120,
|
|
216
|
-
preview: _preview = true,
|
|
217
|
-
cropper = false,
|
|
218
|
-
borderStyle = 'dashed',
|
|
219
|
-
borderColor = '#d9d9d9',
|
|
220
|
-
borderWidth = 1,
|
|
221
|
-
borderRadius = 2,
|
|
222
|
-
backgroundColor = '#fafafa',
|
|
223
|
-
cropperConfig: $cropperConfig = {},
|
|
224
|
-
onChange
|
|
225
|
-
} = props;
|
|
226
|
-
const cropperConfig = toObject($cropperConfig);
|
|
227
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
228
|
-
style: {
|
|
229
|
-
width,
|
|
230
|
-
height,
|
|
231
|
-
borderStyle,
|
|
232
|
-
borderWidth,
|
|
233
|
-
borderColor,
|
|
234
|
-
borderRadius,
|
|
235
|
-
backgroundColor
|
|
236
|
-
},
|
|
237
|
-
className: `${prefixCls}_image-file-uploader_ ${prefixCls}_image-file-uploader-${algorithm}_`
|
|
238
|
-
}, value ? /*#__PURE__*/React.createElement("div", {
|
|
239
|
-
className: `${prefixCls}_image-file-uploader-preview_`
|
|
240
|
-
}, value && /*#__PURE__*/React.createElement(ImageFunc, {
|
|
241
|
-
src: toObject(value).fileUrl,
|
|
242
|
-
preview: !disabled ? {
|
|
243
|
-
visible: preview,
|
|
244
|
-
onVisibleChange: () => setPreview(false)
|
|
245
|
-
} : true
|
|
246
|
-
}), !disabled && /*#__PURE__*/React.createElement("div", {
|
|
247
|
-
className: `${prefixCls}_image-file-uploader-preview-control_`
|
|
248
|
-
}, _preview && /*#__PURE__*/React.createElement(EyeOutlined, {
|
|
249
|
-
title: "\u9884\u89C8",
|
|
250
|
-
onClick: () => setPreview(true)
|
|
251
|
-
}), /*#__PURE__*/React.createElement(DeleteOutlined, {
|
|
252
|
-
title: "\u5220\u9664",
|
|
253
|
-
onClick: () => onChange && onChange(undefined)
|
|
254
|
-
}))) : /*#__PURE__*/React.createElement("label", {
|
|
255
|
-
className: `${prefixCls}_image-file-uploader-select_`
|
|
256
|
-
}, loading ? /*#__PURE__*/React.createElement(SyncOutlined, {
|
|
257
|
-
spin: true
|
|
258
|
-
}) : /*#__PURE__*/React.createElement(PlusOutlined, null), /*#__PURE__*/React.createElement("div", {
|
|
259
|
-
style: {
|
|
260
|
-
maxWidth: width
|
|
261
|
-
},
|
|
262
|
-
className: `${prefixCls}_image-file-uploader-select-tip_`
|
|
263
|
-
}, loading ? '上传中...' : tip), !loading && !disabled && /*#__PURE__*/React.createElement("input", {
|
|
264
|
-
ref: fileInputRef,
|
|
265
|
-
type: "file",
|
|
266
|
-
style: {
|
|
267
|
-
display: 'none'
|
|
268
|
-
},
|
|
269
|
-
accept: `image/png,image/jpg,image/jpeg${svg ? ',image/svg+xml' : ''}`,
|
|
270
|
-
onChange: onUpload
|
|
271
|
-
})), cropper && visible && !svg && /*#__PURE__*/React.createElement(Modal, {
|
|
272
|
-
open: true,
|
|
273
|
-
title: "\u56FE\u7247\u88C1\u526A",
|
|
274
|
-
width: (cropperConfig.width || 400) + 48,
|
|
275
|
-
footer: /*#__PURE__*/React.createElement(Row, {
|
|
276
|
-
align: "middle",
|
|
277
|
-
justify: "space-between"
|
|
278
|
-
}, /*#__PURE__*/React.createElement(Col, {
|
|
279
|
-
style: {
|
|
280
|
-
color: 'red'
|
|
281
|
-
}
|
|
282
|
-
}, "\u5F53\u524D\u88C1\u526A\u7684\u7EB5\u6A2A\u6BD4\uFF1A16 / ", cropperConfig.aspect ? Math.floor(16 / cropperConfig.aspect) : 9), /*#__PURE__*/React.createElement(Col, null, /*#__PURE__*/React.createElement(Button, {
|
|
283
|
-
type: "primary",
|
|
284
|
-
icon: /*#__PURE__*/React.createElement(ScissorOutlined, null),
|
|
285
|
-
onClick: () => {
|
|
286
|
-
onUploadHttp(cropperRef.current.toDataFile());
|
|
287
|
-
setTimeout(() => setVisible(false), 300);
|
|
288
|
-
}
|
|
289
|
-
}, "\u786E\u5B9A"))),
|
|
290
|
-
maskClosable: false,
|
|
291
|
-
destroyOnClose: false,
|
|
292
|
-
onCancel: () => setVisible(false)
|
|
293
|
-
}, /*#__PURE__*/React.createElement(ImageCropper, {
|
|
294
|
-
ref: cropperRef,
|
|
295
|
-
width: cropperConfig.width || 400,
|
|
296
|
-
height: cropperConfig.height || 400,
|
|
297
|
-
locked: cropperConfig.locked,
|
|
298
|
-
aspect: cropperConfig.aspect,
|
|
299
|
-
resource: resource
|
|
300
|
-
})));
|
|
301
|
-
};
|
|
302
|
-
export default ImageUploader;
|
|
303
|
-
export const getValueProps = value => ({
|
|
304
|
-
value: value ? {
|
|
305
|
-
fileUrl: value
|
|
306
|
-
} : undefined
|
|
307
|
-
});
|
|
308
|
-
export const getValueFromEvent = file => {
|
|
309
|
-
return file?.fileUrl;
|
|
310
|
-
};
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
-
import React, { useState, useEffect, useCallback, useImperativeHandle, forwardRef } from 'react';
|
|
3
|
-
import { Table, Row, Col, Form } from 'antd';
|
|
4
|
-
import { isEqual } from 'lodash';
|
|
5
|
-
import SearchForm from '@cqsjjb/jjb-react-admin-component/SearchForm';
|
|
6
|
-
import ProTable from '@cqsjjb/jjb-react-admin-component/Table';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @component ListDataContainer
|
|
10
|
-
* @description 通用列表数据容器组件,提供搜索表单、数据表格、分页等功能的完整列表页面解决方案
|
|
11
|
-
*
|
|
12
|
-
* @props {Array} columns - 表格列配置,遵循Ant Design Table组件的columns规范
|
|
13
|
-
* @props {Function} fetchDataApi - 数据获取API函数,接收分页和搜索参数,返回包含list和total的对象
|
|
14
|
-
* @props {Array} searchFormConfig - 搜索表单配置数组
|
|
15
|
-
* @param {React.Component} searchFormConfig[].field - 表单项组件(如ControlWrapper.Select)
|
|
16
|
-
* @param {string} searchFormConfig[].name - 表单项字段名
|
|
17
|
-
* @param {string} searchFormConfig[].label - 表单项标签
|
|
18
|
-
* @param {Object} searchFormConfig[].fieldProps - 传递给字段组件的属性
|
|
19
|
-
* @param {Object} searchFormConfig[].itemProps - 传递给Form.Item的属性
|
|
20
|
-
* @param {React.ReactNode} searchFormConfig[].content - 字段组件的子元素(如Select.Option列表)
|
|
21
|
-
* @param {boolean} searchFormConfig[].required - 是否必填
|
|
22
|
-
* @props {string} rowKey - 表格行键值,默认为'id'
|
|
23
|
-
* @props {Object} initialPagination - 初始分页配置,包含current、pageSize等分页属性
|
|
24
|
-
* @props {Object} tableProps - 传递给Table组件的属性
|
|
25
|
-
* @props {boolean} proTable - 是否使用ProTable组件,默认为false
|
|
26
|
-
* @example
|
|
27
|
-
* // 基础用法
|
|
28
|
-
* <ListDataContainer
|
|
29
|
-
* columns={tableColumns}
|
|
30
|
-
* fetchDataApi={getCourseList}
|
|
31
|
-
* searchFormConfig={[
|
|
32
|
-
* {
|
|
33
|
-
* field: Select,
|
|
34
|
-
* name: 'status',
|
|
35
|
-
* label: '状态',
|
|
36
|
-
* }
|
|
37
|
-
* {
|
|
38
|
-
* field: Input,
|
|
39
|
-
* name: 'name',
|
|
40
|
-
* label: '名称',
|
|
41
|
-
* required: true,
|
|
42
|
-
* }
|
|
43
|
-
* ]}
|
|
44
|
-
* />
|
|
45
|
-
*
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
const SELECT_COMPONENTS = ['Select', 'TreeSelect', 'Cascader', 'DatePicker', 'TimePicker', 'Checkbox', 'Radio', 'Switch', 'Slider', 'Upload'];
|
|
49
|
-
const INPUT_COMPONENTS = ['Input', 'InputNumber'];
|
|
50
|
-
const ListDataContainer = /*#__PURE__*/forwardRef(({
|
|
51
|
-
columns,
|
|
52
|
-
fetchDataApi,
|
|
53
|
-
searchFormConfig = [],
|
|
54
|
-
rowKey = 'id',
|
|
55
|
-
initialPagination = {
|
|
56
|
-
pageIndex: 1,
|
|
57
|
-
pageSize: 10,
|
|
58
|
-
showSizeChanger: true,
|
|
59
|
-
showQuickJumper: true
|
|
60
|
-
},
|
|
61
|
-
tableProps = {},
|
|
62
|
-
proTable = false
|
|
63
|
-
}, ref) => {
|
|
64
|
-
const [form] = Form.useForm();
|
|
65
|
-
const [tableData, setTableData] = useState([]);
|
|
66
|
-
const [loading, setLoading] = useState(false);
|
|
67
|
-
const [pagination, setPagination] = useState(initialPagination);
|
|
68
|
-
const [totalCount, setTotalCount] = useState(0);
|
|
69
|
-
const getComponentName = Component => {
|
|
70
|
-
return Component.displayName || Component.constructor.name;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 数据加载与刷新方法
|
|
75
|
-
* @param {Object} [overrideParams={}] - 可选,用于覆盖内部参数的键值对
|
|
76
|
-
* 若传入,会覆盖同名的表单参数和分页参数(如page、pageSize或搜索字段)
|
|
77
|
-
*/
|
|
78
|
-
const loadDataSource = useCallback(async (overrideParams = {}) => {
|
|
79
|
-
if (!fetchDataApi) return;
|
|
80
|
-
setLoading(true);
|
|
81
|
-
try {
|
|
82
|
-
// 1. 获取组件内部的表单参数
|
|
83
|
-
const formValues = await form.validateFields();
|
|
84
|
-
|
|
85
|
-
// 2. 构造基础参数:分页参数 + 表单参数
|
|
86
|
-
const baseParams = {
|
|
87
|
-
pageIndex: pagination.pageIndex,
|
|
88
|
-
pageSize: pagination.pageSize,
|
|
89
|
-
...formValues
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// 3. 用外部传入的参数覆盖基础参数(同名参数以外部为准)
|
|
93
|
-
const requestParams = {
|
|
94
|
-
...baseParams,
|
|
95
|
-
...overrideParams
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// 4. 发起请求
|
|
99
|
-
const response = await fetchDataApi(requestParams);
|
|
100
|
-
setTableData(response.data || []);
|
|
101
|
-
setTotalCount(response.totalCount || 0);
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.error('列表数据请求失败:', error);
|
|
104
|
-
setTableData([]);
|
|
105
|
-
setTotalCount(0);
|
|
106
|
-
} finally {
|
|
107
|
-
setLoading(false);
|
|
108
|
-
}
|
|
109
|
-
}, [fetchDataApi, pagination, form]);
|
|
110
|
-
|
|
111
|
-
// 暴露刷新方法给父组件
|
|
112
|
-
useImperativeHandle(ref, () => ({
|
|
113
|
-
// 外部调用时可传入覆盖参数
|
|
114
|
-
loadDataSource: (overrideParams = {}) => loadDataSource(overrideParams)
|
|
115
|
-
}));
|
|
116
|
-
const handleFinish = values => {
|
|
117
|
-
// 搜索时重置到第一页,并用表单值覆盖(values会合并到baseParams中)
|
|
118
|
-
setPagination(prev => ({
|
|
119
|
-
...prev,
|
|
120
|
-
pageIndex: 1
|
|
121
|
-
}));
|
|
122
|
-
loadDataSource({
|
|
123
|
-
...values,
|
|
124
|
-
pageIndex: 1
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
const handleReset = () => {
|
|
128
|
-
// 清空表单
|
|
129
|
-
form.setFieldsValue(searchFormConfig.reduce((obj, item) => {
|
|
130
|
-
obj[item.name] = undefined;
|
|
131
|
-
return obj;
|
|
132
|
-
}, {}));
|
|
133
|
-
// 重置分页并使用内部默认参数查询
|
|
134
|
-
setPagination(initialPagination);
|
|
135
|
-
loadDataSource();
|
|
136
|
-
};
|
|
137
|
-
const handlePaginationChange = newPagination => {
|
|
138
|
-
console.warn("ssssssssssss", newPagination);
|
|
139
|
-
// 更新分页后,用新分页参数查询
|
|
140
|
-
setPagination({
|
|
141
|
-
...newPagination,
|
|
142
|
-
pageIndex: newPagination.current
|
|
143
|
-
});
|
|
144
|
-
loadDataSource({
|
|
145
|
-
pageIndex: newPagination.current,
|
|
146
|
-
pageSize: newPagination.pageSize
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
loadDataSource();
|
|
151
|
-
}, []);
|
|
152
|
-
const generateFormItems = () => {
|
|
153
|
-
return searchFormConfig.map((item, index) => {
|
|
154
|
-
const {
|
|
155
|
-
field: FieldComponent,
|
|
156
|
-
name,
|
|
157
|
-
label,
|
|
158
|
-
required,
|
|
159
|
-
fieldProps = {},
|
|
160
|
-
itemProps = {},
|
|
161
|
-
content,
|
|
162
|
-
...restProps
|
|
163
|
-
} = item;
|
|
164
|
-
return /*#__PURE__*/React.createElement(Form.Item, _extends({
|
|
165
|
-
key: `search-item-${name}-${index}`,
|
|
166
|
-
name: name,
|
|
167
|
-
label: label
|
|
168
|
-
}, required ? {
|
|
169
|
-
required: true,
|
|
170
|
-
rules: [{
|
|
171
|
-
required: true,
|
|
172
|
-
message: `${SELECT_COMPONENTS.includes(getComponentName(FieldComponent)) ? '请选择' : '请输入'}${label}`
|
|
173
|
-
}]
|
|
174
|
-
} : {}, itemProps, restProps), /*#__PURE__*/React.createElement(FieldComponent, _extends({
|
|
175
|
-
allowClear: true,
|
|
176
|
-
placeholder: `${SELECT_COMPONENTS.includes(getComponentName(FieldComponent)) ? '请选择' : '请输入'}${label}`
|
|
177
|
-
}, fieldProps), content || null));
|
|
178
|
-
}).filter(Boolean);
|
|
179
|
-
};
|
|
180
|
-
const TableComponent = proTable ? ProTable : Table;
|
|
181
|
-
return /*#__PURE__*/React.createElement(Row, {
|
|
182
|
-
gutter: [16, 24]
|
|
183
|
-
}, /*#__PURE__*/React.createElement(Col, {
|
|
184
|
-
span: 24
|
|
185
|
-
}, /*#__PURE__*/React.createElement(SearchForm, {
|
|
186
|
-
expand: true,
|
|
187
|
-
form: form,
|
|
188
|
-
loading: loading,
|
|
189
|
-
formLine: generateFormItems(),
|
|
190
|
-
onReset: handleReset,
|
|
191
|
-
onFinish: handleFinish
|
|
192
|
-
})), /*#__PURE__*/React.createElement(Col, {
|
|
193
|
-
span: 24
|
|
194
|
-
}, /*#__PURE__*/React.createElement(TableComponent, _extends({
|
|
195
|
-
columns: columns,
|
|
196
|
-
dataSource: tableData,
|
|
197
|
-
rowKey: rowKey,
|
|
198
|
-
loading: loading,
|
|
199
|
-
pagination: {
|
|
200
|
-
...pagination,
|
|
201
|
-
current: pagination.pageIndex,
|
|
202
|
-
total: totalCount
|
|
203
|
-
},
|
|
204
|
-
onChange: handlePaginationChange,
|
|
205
|
-
scroll: {
|
|
206
|
-
x: 'max-content'
|
|
207
|
-
},
|
|
208
|
-
bordered: true
|
|
209
|
-
}, tableProps))));
|
|
210
|
-
});
|
|
211
|
-
export default ListDataContainer;
|
package/MediaQuery/index.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState, useMemo } from 'react';
|
|
2
|
-
const MEDIA_QUERY_LIST = {
|
|
3
|
-
MAX_WIDTH_479: '(max-width: 479px)',
|
|
4
|
-
MIN_WIDTH_480_AND_MAX_WIDTH_599: '(min-width: 480px) and (max-width: 599px)',
|
|
5
|
-
MIN_WIDTH_600_AND_MAX_WIDTH_899: '(min-width: 600px) and (max-width: 899px)',
|
|
6
|
-
MIN_WIDTH_900_AND_MAX_WIDTH_1199: '(min-width: 900px) and (max-width: 1199px)',
|
|
7
|
-
MIN_WIDTH_1200_AND_MAX_WIDTH_1439: '(min-width: 1200px) and (max-width: 1439px)',
|
|
8
|
-
MIN_WIDTH_1440: '(min-width: 1440px)'
|
|
9
|
-
};
|
|
10
|
-
export default function MediaQuery({
|
|
11
|
-
children,
|
|
12
|
-
disabled
|
|
13
|
-
}) {
|
|
14
|
-
const [media, setMedia] = useState(undefined);
|
|
15
|
-
const mediaQueries = useMemo(() => {
|
|
16
|
-
if (disabled) return {};
|
|
17
|
-
return Object.fromEntries(Object.entries(MEDIA_QUERY_LIST).map(([key, query]) => [key, window.matchMedia(query)]));
|
|
18
|
-
}, [disabled]);
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (disabled) return;
|
|
21
|
-
const handleChange = () => {
|
|
22
|
-
for (const [key, mql] of Object.entries(mediaQueries)) {
|
|
23
|
-
if (mql.matches) {
|
|
24
|
-
setMedia(key);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
setMedia(undefined);
|
|
29
|
-
};
|
|
30
|
-
Object.values(mediaQueries).forEach(mql => mql.addEventListener('change', handleChange));
|
|
31
|
-
|
|
32
|
-
// 初始化
|
|
33
|
-
handleChange();
|
|
34
|
-
return () => {
|
|
35
|
-
Object.values(mediaQueries).forEach(mql => mql.removeEventListener('change', handleChange));
|
|
36
|
-
};
|
|
37
|
-
}, [mediaQueries, disabled]);
|
|
38
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, (Array.isArray(children) ? children : [children]).map((item, index) => /*#__PURE__*/React.isValidElement(item) ? /*#__PURE__*/React.cloneElement(item, {
|
|
39
|
-
key: index,
|
|
40
|
-
'data-media-query': media
|
|
41
|
-
}) : item));
|
|
42
|
-
}
|