@cqsjjb/jjb-react-admin-component 3.1.4 → 3.2.0-beta.1

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.
@@ -0,0 +1,55 @@
1
+ import { ReactNode, Ref } from 'react';
2
+
3
+ interface ImageCropperProps {
4
+ width?: number; // 宽度,默认400
5
+ height?: number; // 高度,默认400
6
+ locked?: boolean; // 是否锁定裁剪框大小,默认false
7
+ aspect?: number; // 裁剪框纵横比,默认16/9
8
+ resource: File | string; // 裁剪的资源
9
+ }
10
+
11
+ interface ToDataURLResult {
12
+ // md5
13
+ md5: string;
14
+ // base64
15
+ base64: string;
16
+ }
17
+
18
+ interface ToDataFileResult {
19
+ // file
20
+ file: File | null;
21
+ // md5
22
+ md5: string | null;
23
+ }
24
+
25
+ interface ToDataImageInstanceResult {
26
+ // 资源
27
+ src: string;
28
+ // 宽度
29
+ width: number;
30
+ // 高度
31
+ height: number;
32
+ // 实例
33
+ instance: HTMLImageElement;
34
+ }
35
+
36
+ export interface ImageCropperRef {
37
+ // 转换为dataURL
38
+ toDataURL: (quality?: number) => ToDataURLResult | null;
39
+ // 转换为dataFile
40
+ toDataFile: () => ToDataFileResult;
41
+ // 转换为blobURL
42
+ toBlobURL: () => string | null;
43
+ // 转换为dataImageText
44
+ toDataImageText: () => string | null;
45
+ // 转换为dataImageInstance
46
+ toDataImageInstance: () => Promise<ToDataImageInstanceResult>;
47
+ }
48
+
49
+ declare const ImageCropper: {
50
+ (props: ImageCropperProps & { ref?: Ref<ImageCropperRef> }): ReactNode;
51
+ defaultProps: Partial<ImageCropperProps>;
52
+ propTypes: any;
53
+ };
54
+
55
+ export default ImageCropper;
@@ -0,0 +1,131 @@
1
+ import MD5 from 'spark-md5';
2
+ import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
3
+ import Cropper from 'react-cropper';
4
+ import PropTypes from 'prop-types';
5
+ import 'cropperjs/dist/cropper.css';
6
+ import { Empty } from 'antd';
7
+ import { tools } from '@cqsjjb/jjb-common-lib';
8
+ import { isHttpUrl } from '../tools/index.js';
9
+ const ImageCropper = /*#__PURE__*/forwardRef((props, ref) => {
10
+ const {
11
+ width,
12
+ height,
13
+ aspect,
14
+ locked,
15
+ resource
16
+ } = props;
17
+ const cropRef = useRef(null);
18
+ const [src, setSrc] = useState(undefined);
19
+ console.log('props', locked);
20
+
21
+ // 更新 src
22
+ const onUpdateSrc = () => {
23
+ if (resource instanceof File) {
24
+ tools.getFileToBase64(resource).then(setSrc);
25
+ } else if (/^data:image\/(png|jpg|jpeg);base64/.test(resource) || isHttpUrl(resource)) {
26
+ setSrc(resource);
27
+ }
28
+ };
29
+
30
+ // 初始化或资源变化时更新
31
+ useEffect(() => {
32
+ onUpdateSrc();
33
+ }, [resource]);
34
+
35
+ // 暴露给父组件的方法
36
+ useImperativeHandle(ref, () => ({
37
+ toDataURL: (quality = 1) => {
38
+ const md5 = new MD5();
39
+ try {
40
+ const base64 = cropRef.current.cropper.getCroppedCanvas().toDataURL('image/png', quality);
41
+ md5.appendBinary(base64);
42
+ return {
43
+ md5: md5.end(),
44
+ base64
45
+ };
46
+ } catch (e) {
47
+ return null;
48
+ }
49
+ },
50
+ toDataFile: () => {
51
+ try {
52
+ const {
53
+ md5,
54
+ base64
55
+ } = ref.current.toDataURL();
56
+ return {
57
+ md5,
58
+ file: tools.getBase64ToFile(base64, `${tools.createOnlyKey()}.png`)
59
+ };
60
+ } catch (e) {
61
+ return {
62
+ file: null,
63
+ md5: null
64
+ };
65
+ }
66
+ },
67
+ toBlobURL: () => {
68
+ try {
69
+ return window.URL.createObjectURL(ref.current.toDataFile().file);
70
+ } catch (e) {
71
+ return null;
72
+ }
73
+ },
74
+ toDataImageText: () => {
75
+ try {
76
+ return `<img alt="img" src="${ref.current.toDataURL().base64}"/>`;
77
+ } catch (e) {
78
+ return null;
79
+ }
80
+ },
81
+ toDataImageInstance: () => {
82
+ return new Promise(resolve => {
83
+ const image = new Image();
84
+ image.src = ref.current.toDataURL().base64;
85
+ image.onload = () => resolve({
86
+ src: image.src,
87
+ width: image.width,
88
+ height: image.height,
89
+ instance: image
90
+ });
91
+ });
92
+ }
93
+ }));
94
+ return /*#__PURE__*/React.createElement("div", {
95
+ style: {
96
+ width,
97
+ height,
98
+ position: 'relative'
99
+ }
100
+ }, src ? /*#__PURE__*/React.createElement(Cropper, {
101
+ ref: cropRef,
102
+ src: src,
103
+ style: {
104
+ width,
105
+ height
106
+ },
107
+ aspectRatio: aspect,
108
+ cropBoxResizable: !locked
109
+ }) : /*#__PURE__*/React.createElement(Empty, {
110
+ image: Empty.PRESENTED_IMAGE_SIMPLE,
111
+ style: {
112
+ margin: 0,
113
+ paddingTop: (height - 70) / 2 - 40
114
+ },
115
+ description: "\u672A\u5BFC\u5165\u56FE\u7247\u8D44\u6E90\uFF0C\u65E0\u6CD5\u88C1\u526A"
116
+ }));
117
+ });
118
+ ImageCropper.defaultProps = {
119
+ width: 400,
120
+ height: 400,
121
+ aspect: 16 / 9,
122
+ locked: false
123
+ };
124
+ ImageCropper.propTypes = {
125
+ width: PropTypes.number,
126
+ height: PropTypes.number,
127
+ locked: PropTypes.bool,
128
+ aspect: PropTypes.number,
129
+ resource: PropTypes.any.isRequired
130
+ };
131
+ export default ImageCropper;
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+
3
+ export interface ImageUploaderProps {
4
+ // 版本 v1-才俊java v2-杨飞java 默认-v2
5
+ version?: 'v1' | 'v2';
6
+ // 宽高 默认-50*50
7
+ wh?: string;
8
+ // 是否是svg 默认-false
9
+ svg?: boolean;
10
+ // 提示文本 默认-点击上传
11
+ tip?: string;
12
+ // 大小 单位:KB 默认-5120
13
+ size?: number;
14
+ // 额外的数据 默认-{}
15
+ data?: Record<string, any>;
16
+ // 值 默认-undefined
17
+ value?: any;
18
+ // 组件宽度 默认-120
19
+ width?: number;
20
+ // 组件高度 默认-120
21
+ height?: number;
22
+ // 请求地址 默认-undefined
23
+ action: string;
24
+ // 是否支持预览 默认-true
25
+ preview?: boolean;
26
+ // 是否支持裁剪 默认-false
27
+ cropper?: boolean;
28
+ // 额外的请求头 默认-{}
29
+ headers?: Record<string, any>;
30
+ // 是否禁用 默认-false
31
+ disabled?: boolean;
32
+ // 提交的字段名 默认-file
33
+ fieldName?: string;
34
+ // 边框样式 默认-dashed
35
+ borderStyle?: string;
36
+ // 边框颜色 默认:#d9d9d9
37
+ borderColor?: string;
38
+ // 边框宽度 默认-1
39
+ borderWidth?: number;
40
+ // 边框圆角 默认-2
41
+ borderRadius?: number;
42
+ // 裁剪配置,cropper为true时有效 默认:{}
43
+ cropperConfig?: {
44
+ // 宽度 默认-400
45
+ width?: number;
46
+ // 高度 默认-400
47
+ height?: number;
48
+ // 锁定不可缩放裁剪框 默认-false
49
+ locked?: boolean;
50
+ // 纵横比 默认-16 / 9, 若不知如何计算纵横比,可使用tools.calcAspectRadio函数
51
+ aspect?: number;
52
+ [key: string]: any;
53
+ };
54
+ // 背景颜色 默认-#fafafa
55
+ backgroundColor?: string;
56
+ // 自定义请求 默认-undefined
57
+ customRequest?: (res: {data: any}) => void | Promise<{fileId: string, fileUrl: string}>;
58
+ // 受控值改变回调 默认-undefined
59
+ onChange?: (value: any) => void;
60
+ // 是否支持压缩 默认-false
61
+ compression?: boolean;
62
+ // 压缩配置,compression为true时有效 默认-{}
63
+ compressionConfig?: {
64
+ // 压缩质量 默认-0.6
65
+ quality?: number;
66
+ // 压缩类型 默认-file
67
+ backType?: 'file' | 'base64';
68
+ [key: string]: any;
69
+ };
70
+ // 是否需要token 默认-true
71
+ needToken?: boolean;
72
+ }
73
+
74
+ declare const ImageUploader: React.FC<ImageUploaderProps>;
75
+
76
+ export default ImageUploader;
77
+
78
+ export function getValueProps(value: any): { value: { fileUrl: any } | undefined };
79
+ export function getValueFromEvent(event: any): any;
@@ -0,0 +1,362 @@
1
+ import MD5 from 'spark-md5';
2
+ import React, { useState, useRef, useCallback } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import { tools } from '@cqsjjb/jjb-common-lib';
5
+ import { Button, Col, Image as ImageFunc, Modal, Row, message } from 'antd';
6
+ import { PlusOutlined, EyeOutlined, DeleteOutlined, SyncOutlined, ScissorOutlined } from '@ant-design/icons';
7
+ import ImageCropper from '../ImageCropper';
8
+ import './index.less';
9
+ import { getTenantCode, getToken, getViewAsp, isVersion1, isVersion2, getAppKey, isHttpUrl } from '../tools/index.js';
10
+ const {
11
+ toObject,
12
+ isPromise,
13
+ isFunction,
14
+ parseObject
15
+ } = tools;
16
+ const prefixCls = window.process?.env?.app?.antd['ant-prefix'] || 'ant';
17
+ const FILE_TYPE = ['image/png', 'image/jpg', 'image/jpeg'];
18
+ const FILE_TYPE_POWER = [...FILE_TYPE, 'image/svg+xml'];
19
+ const ImageUploader = props => {
20
+ const [loading, setLoading] = useState(false);
21
+ const [preview, setPreview] = useState(false);
22
+ const [visible, setVisible] = useState(false);
23
+ const [resource, setResource] = useState(undefined);
24
+ const cropperRef = useRef(null);
25
+ const fileInputRef = useRef(null);
26
+ const version = props.version;
27
+ const algorithm = (() => {
28
+ const value = document.documentElement.style.getPropertyValue('--saas-algorithm');
29
+ return value ? value === '#FFF' ? 'default' : 'dark' : 'default';
30
+ })();
31
+ const onLoadFile = useCallback(file => {
32
+ const md5 = new MD5();
33
+ const {
34
+ svg
35
+ } = props;
36
+ return new Promise(resolve => {
37
+ const reader = new FileReader();
38
+ reader.onload = e => {
39
+ if (version === 'v2') md5.appendBinary(e.target.result);
40
+ const isImage = (svg ? FILE_TYPE_POWER : FILE_TYPE).includes(file.type);
41
+ if (isImage) {
42
+ const image = new Image();
43
+ const base64 = e.target.result;
44
+ image.src = base64;
45
+ image.onload = () => {
46
+ resolve({
47
+ file,
48
+ base64,
49
+ md5: isVersion2(version) ? md5.end() : null,
50
+ size: file.size,
51
+ type: file.type,
52
+ name: file.name,
53
+ width: image.width,
54
+ height: image.height
55
+ });
56
+ };
57
+ } else {
58
+ resolve({
59
+ file,
60
+ md5: isVersion2(version) ? md5.end() : null,
61
+ size: file.size,
62
+ type: file.type,
63
+ name: file.name,
64
+ width: 0,
65
+ height: 0,
66
+ base64: undefined
67
+ });
68
+ }
69
+ };
70
+ reader.readAsDataURL(file);
71
+ });
72
+ }, [props, version]);
73
+ const onCompression = useCallback(data => {
74
+ const {
75
+ compressionConfig
76
+ } = props;
77
+ return new Promise(resolve => {
78
+ const URL = window.URL || window.webkitURL;
79
+ const {
80
+ quality = 0.8
81
+ } = toObject(compressionConfig);
82
+ const img = new Image();
83
+ const canvas = document.createElement('canvas');
84
+ const context = canvas.getContext('2d');
85
+ onLoadFile(data.file).then(res => {
86
+ img.width = res.width;
87
+ img.height = res.height;
88
+ img.src = URL.createObjectURL(data.file);
89
+ img.onload = () => {
90
+ img.onload = null;
91
+ canvas.width = img.width;
92
+ canvas.height = img.height;
93
+ context.drawImage(img, 0, 0, img.width, img.height);
94
+ canvas.toBlob(blob => {
95
+ const newFile = new File([blob], data.name, {
96
+ type: blob.type
97
+ });
98
+ resolve({
99
+ ...data,
100
+ file: newFile,
101
+ size: newFile.size
102
+ });
103
+ }, 'image/png', quality);
104
+ };
105
+ });
106
+ });
107
+ }, [onLoadFile, props]);
108
+ const onUploadHttp = useCallback(transfer => {
109
+ const {
110
+ API_HOST
111
+ } = window.process.env.app;
112
+ const {
113
+ data,
114
+ action,
115
+ headers: $headers,
116
+ needToken,
117
+ customRequest,
118
+ fieldName,
119
+ onChange
120
+ } = props;
121
+ const requestURL = isHttpUrl(action) ? action : `${API_HOST}${action}`;
122
+ const xhr = new XMLHttpRequest();
123
+ const form = new FormData();
124
+ const headers = toObject($headers);
125
+ headers.appKey = getAppKey();
126
+ if (needToken) headers.token = getToken();
127
+ if (isVersion1(version)) headers.tenantCode = getTenantCode();
128
+ if (isVersion2(version) && transfer.md5) form.append('md5', transfer.md5);
129
+ setLoading(true);
130
+ xhr.open('POST', requestURL);
131
+ Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key]));
132
+ xhr.onreadystatechange = () => {
133
+ if (xhr.readyState === 4 && xhr.status === 200) {
134
+ setLoading(false);
135
+ const res = parseObject(xhr.responseText);
136
+ if (isFunction(customRequest)) {
137
+ const fn = customRequest(res);
138
+ if (isPromise(fn)) fn.then(value => onChange && onChange(value));
139
+ } else {
140
+ const {
141
+ success,
142
+ respCode,
143
+ errMessage
144
+ } = res;
145
+ if (respCode === '0000' || success) {
146
+ const result = toObject(res.data);
147
+ if (typeof onChange === 'undefined') console.warn('ImageUploader警告:缺少必要的onChange回调,可能无法正确显示图片!');
148
+ onChange && onChange(isVersion1(version) ? result.url : {
149
+ fileId: result.fileId || result.url,
150
+ fileUrl: result.fileUrl || result.fileName
151
+ });
152
+ message.success('上传成功!');
153
+ } else {
154
+ message.error(errMessage || '未知错误,上传失败,请检查!');
155
+ }
156
+ }
157
+ }
158
+ };
159
+ form.append(fieldName, transfer.file);
160
+ Object.keys(data).forEach(key => form.append(key, data[key]));
161
+ xhr.send(form);
162
+ }, [props, version]);
163
+ const onUpload = useCallback(e => {
164
+ const target = e.target;
165
+ const files = target.files;
166
+ const file = files[0];
167
+ const {
168
+ wh,
169
+ svg,
170
+ size,
171
+ cropper,
172
+ compression
173
+ } = props;
174
+ onLoadFile(file).then(load => {
175
+ const [w, h] = getViewAsp(wh);
176
+ const fileSize = load.size;
177
+ const fileType = load.type;
178
+ if (!(svg ? FILE_TYPE_POWER : FILE_TYPE).includes(fileType)) {
179
+ message.error(`文件格式错误,不是图片类型,支持.PNG、.JPG、.JPEG${svg ? '、.SVG' : ''}`);
180
+ target.value = null;
181
+ return;
182
+ }
183
+ if (fileSize / 1024 > size) {
184
+ message.error(`图片大小超过最大值(${size}KB),请重新选择`);
185
+ target.value = null;
186
+ return;
187
+ }
188
+ if (cropper) {
189
+ setResource(load.file);
190
+ setVisible(true);
191
+ } else {
192
+ if (load.width < w || load.height < h) {
193
+ message.error(`图片尺寸不能小于${w}x${h}`);
194
+ target.value = null;
195
+ return;
196
+ }
197
+ if (compression && fileSize / 1024 > 200) {
198
+ onCompression(load).then(res => onUploadHttp(res));
199
+ target.value = null;
200
+ return;
201
+ }
202
+ onUploadHttp(load);
203
+ }
204
+ target.value = null;
205
+ });
206
+ }, [props, onLoadFile, onCompression, onUploadHttp]);
207
+ const {
208
+ tip,
209
+ svg,
210
+ value,
211
+ disabled,
212
+ width,
213
+ height,
214
+ cropper,
215
+ borderStyle,
216
+ borderColor,
217
+ borderWidth,
218
+ borderRadius,
219
+ backgroundColor,
220
+ cropperConfig: $cropperConfig,
221
+ onChange
222
+ } = props;
223
+ const cropperConfig = toObject($cropperConfig);
224
+ return /*#__PURE__*/React.createElement("div", {
225
+ style: {
226
+ width,
227
+ height,
228
+ borderStyle,
229
+ borderWidth,
230
+ borderColor,
231
+ borderRadius,
232
+ backgroundColor
233
+ },
234
+ className: `${prefixCls}_image-file-uploader_ ${prefixCls}_image-file-uploader-${algorithm}_`
235
+ }, value ? /*#__PURE__*/React.createElement("div", {
236
+ className: `${prefixCls}_image-file-uploader-preview_`
237
+ }, value && /*#__PURE__*/React.createElement(ImageFunc, {
238
+ src: version === 'v1' ? value : toObject(value).fileUrl,
239
+ preview: !disabled ? {
240
+ visible: preview,
241
+ onVisibleChange: () => setPreview(false)
242
+ } : true
243
+ }), !disabled && /*#__PURE__*/React.createElement("div", {
244
+ className: `${prefixCls}_image-file-uploader-preview-control_`
245
+ }, props.preview && /*#__PURE__*/React.createElement(EyeOutlined, {
246
+ title: "\u9884\u89C8",
247
+ onClick: () => setPreview(true)
248
+ }), /*#__PURE__*/React.createElement(DeleteOutlined, {
249
+ title: "\u5220\u9664",
250
+ onClick: () => onChange && onChange(undefined)
251
+ }))) : /*#__PURE__*/React.createElement("label", {
252
+ className: `${prefixCls}_image-file-uploader-select_`
253
+ }, loading ? /*#__PURE__*/React.createElement(SyncOutlined, {
254
+ spin: true
255
+ }) : /*#__PURE__*/React.createElement(PlusOutlined, null), /*#__PURE__*/React.createElement("div", {
256
+ style: {
257
+ maxWidth: width
258
+ },
259
+ className: `${prefixCls}_image-file-uploader-select-tip_`
260
+ }, loading ? '上传中...' : tip), !loading && !disabled && /*#__PURE__*/React.createElement("input", {
261
+ ref: fileInputRef,
262
+ type: "file",
263
+ style: {
264
+ display: 'none'
265
+ },
266
+ accept: `image/png,image/jpg,image/jpeg${svg ? ',image/svg+xml' : ''}`,
267
+ onChange: onUpload
268
+ })), cropper && visible && !svg && /*#__PURE__*/React.createElement(Modal, {
269
+ open: true,
270
+ title: "\u56FE\u7247\u88C1\u526A",
271
+ width: (cropperConfig.width || 400) + 48,
272
+ footer: /*#__PURE__*/React.createElement(Row, {
273
+ align: "middle",
274
+ justify: "space-between"
275
+ }, /*#__PURE__*/React.createElement(Col, {
276
+ style: {
277
+ color: 'red'
278
+ }
279
+ }, "\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, {
280
+ type: "primary",
281
+ icon: /*#__PURE__*/React.createElement(ScissorOutlined, null),
282
+ onClick: () => {
283
+ onUploadHttp(cropperRef.current.toDataFile());
284
+ setTimeout(() => setVisible(false), 300);
285
+ }
286
+ }, "\u786E\u5B9A"))),
287
+ maskClosable: false,
288
+ destroyOnClose: false,
289
+ onCancel: () => setVisible(false)
290
+ }, /*#__PURE__*/React.createElement(ImageCropper, {
291
+ ref: cropperRef,
292
+ width: cropperConfig.width || 400,
293
+ height: cropperConfig.height || 400,
294
+ locked: cropperConfig.locked,
295
+ aspect: cropperConfig.aspect,
296
+ resource: resource
297
+ })));
298
+ };
299
+ ImageUploader.defaultProps = {
300
+ wh: '50*50',
301
+ tip: '点击上传',
302
+ svg: false,
303
+ size: 5120,
304
+ data: {},
305
+ width: 120,
306
+ height: 120,
307
+ headers: {},
308
+ preview: true,
309
+ cropper: false,
310
+ version: 'v2',
311
+ disabled: false,
312
+ fieldName: 'file',
313
+ borderColor: '#d9d9d9',
314
+ borderWidth: 1,
315
+ borderStyle: 'dashed',
316
+ borderRadius: 2,
317
+ cropperConfig: {},
318
+ backgroundColor: '#fafafa',
319
+ compression: false,
320
+ compressionConfig: {
321
+ quality: 0.6,
322
+ backType: 'file'
323
+ },
324
+ needToken: true
325
+ };
326
+ ImageUploader.propTypes = {
327
+ version: PropTypes.string,
328
+ wh: PropTypes.string,
329
+ svg: PropTypes.bool,
330
+ tip: PropTypes.string,
331
+ size: PropTypes.number,
332
+ data: PropTypes.object,
333
+ value: PropTypes.any,
334
+ width: PropTypes.number,
335
+ height: PropTypes.number,
336
+ action: PropTypes.string.isRequired,
337
+ preview: PropTypes.bool,
338
+ cropper: PropTypes.bool,
339
+ headers: PropTypes.object,
340
+ disabled: PropTypes.bool,
341
+ fieldName: PropTypes.string,
342
+ borderStyle: PropTypes.string,
343
+ borderColor: PropTypes.string,
344
+ borderWidth: PropTypes.number,
345
+ borderRadius: PropTypes.number,
346
+ cropperConfig: PropTypes.object,
347
+ backgroundColor: PropTypes.string,
348
+ customRequest: PropTypes.func,
349
+ onChange: PropTypes.func,
350
+ compression: PropTypes.bool,
351
+ compressionConfig: PropTypes.object,
352
+ needToken: PropTypes.bool
353
+ };
354
+ export default ImageUploader;
355
+ export const getValueProps = value => ({
356
+ value: value ? {
357
+ fileUrl: value
358
+ } : undefined
359
+ });
360
+ export const getValueFromEvent = file => {
361
+ return file?.fileUrl;
362
+ };