@hzab/form-render 0.0.2 → 0.1.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.
@@ -0,0 +1,151 @@
1
+ import { useState, useRef, useEffect, useMemo } from "react";
2
+ import { Button, Modal, message } from "antd";
3
+
4
+ import ItemList from "./components/ItemList";
5
+
6
+ import { handleMaxCount, handleInputFileList } from "./common/utils";
7
+ import { handleOssUpload } from "./common/ossUpload";
8
+
9
+ import "./uploader.less";
10
+
11
+ const _ = require("lodash");
12
+
13
+ function Uploader(props) {
14
+ const {
15
+ name,
16
+ multiple,
17
+ // 模式字符串: all | select | image | video 或 数组 select | image | video 组合:如:['select', 'image']
18
+ mode = "select",
19
+ onChange,
20
+ accept,
21
+ disabled,
22
+ readOnly,
23
+ value,
24
+ baseUrl = "",
25
+ // 文件大小限制
26
+ maxSize,
27
+ maxCount = 1,
28
+ // 是否使用 oss 上传文件
29
+ isOssUpload,
30
+ // 是否使用字符串结果
31
+ isStrRes,
32
+ // maxCount === 1 时,结果是否自动去除数组嵌套
33
+ isResRemoveArr = true,
34
+ ossUrl,
35
+ ossOpt,
36
+ onCountExceed,
37
+ } = props;
38
+ const [loading, setLoading] = useState(false);
39
+ const [fileList, setFileList] = useState(handleInputFileList(value, maxCount));
40
+ useEffect(() => {
41
+ setFileList(handleInputFileList(value, maxCount));
42
+ }, [value]);
43
+
44
+ async function onFileChange(e) {
45
+ e.persist();
46
+ const { files: rawFiles } = e.target;
47
+ let files = [...fileList, ...[].slice.call(rawFiles)];
48
+ // 超出个数限制只保留前面的 maxCount 个
49
+ if (maxCount > 0 && files.length > maxCount) {
50
+ onCountExceed && onCountExceed(files.length - maxCount);
51
+ files.length = maxCount;
52
+ message.error("文件个数超出限制");
53
+ }
54
+ files = files.filter((it) => {
55
+ if (maxSize && it.size > maxSize) {
56
+ message.error("文件大小超出限制:" + it.size);
57
+ return false;
58
+ }
59
+ return it;
60
+ });
61
+ // 处理 oss 逻辑
62
+ if (isOssUpload) {
63
+ setLoading(true);
64
+ files = await handleOssUpload(files, {
65
+ axios: props.axios,
66
+ axiosConf: props?.axiosConf,
67
+ ...ossOpt,
68
+ ossUrl,
69
+ });
70
+ setLoading(false);
71
+ }
72
+
73
+ e.target.value = ""; // HACK: fix the same file doesn't trigger onChange
74
+ handleChange(files);
75
+ }
76
+
77
+ function handleChange(files, optType) {
78
+ let _files = files;
79
+ if (optType !== "del" && maxCount > 1) {
80
+ _files = [...fileList, ..._files];
81
+ }
82
+ _files = handleMaxCount(files, maxCount);
83
+ setFileList(_files);
84
+ if (isOssUpload && isStrRes) {
85
+ _files = _files?.map((file) => {
86
+ return file.url || file.ossUrl;
87
+ });
88
+ }
89
+ if (!_files || _files.length <= 0) {
90
+ _files = undefined;
91
+ } else if (isResRemoveArr && maxCount === 1) {
92
+ // maxCount 为1的时候返回结果去除数组
93
+ _files = _files[0];
94
+ }
95
+ onChange && onChange(_files);
96
+ }
97
+
98
+ const uploaderRef = useRef();
99
+
100
+ function onItemDel(idx) {
101
+ Modal.confirm({
102
+ content: "确认删除?",
103
+ onOk: () => {
104
+ const middleValue = _.cloneDeep(fileList);
105
+ middleValue.splice(idx, 1);
106
+ handleChange(middleValue, "del");
107
+ setFileList(middleValue);
108
+ },
109
+ });
110
+ }
111
+
112
+ const inputProps = {
113
+ className: "aria-hidden",
114
+ style: { display: "none" },
115
+ id: name,
116
+ onChange: onFileChange,
117
+ type: "file",
118
+ name: name,
119
+ multiple: multiple,
120
+ accept: accept,
121
+ capture: props.capture,
122
+ };
123
+
124
+ return (
125
+ <div className="uploader">
126
+ <input {...inputProps} ref={uploaderRef}></input>
127
+ <ItemList
128
+ fileList={fileList}
129
+ baseUrl={isOssUpload ? baseUrl : ""}
130
+ disabled={disabled}
131
+ readOnly={readOnly}
132
+ onItemDel={onItemDel}
133
+ />
134
+ {disabled || readOnly || (maxCount > 0 && fileList.length >= maxCount) ? null : (
135
+ <>
136
+ <Button
137
+ className="uploader-add-btn"
138
+ onClick={() => {
139
+ uploaderRef.current.click();
140
+ }}
141
+ loading={loading}
142
+ >
143
+ {loading ? "" : "+"}
144
+ </Button>
145
+ </>
146
+ )}
147
+ </div>
148
+ );
149
+ }
150
+
151
+ export default Uploader;
@@ -0,0 +1,200 @@
1
+ import {
2
+ UploadOutlined,
3
+ PaperClipOutlined,
4
+ FilePdfTwoTone,
5
+ VideoCameraTwoTone,
6
+ FileExcelTwoTone,
7
+ FileWordTwoTone,
8
+ } from "@ant-design/icons";
9
+ import { Button, Upload as AUpload, message } from "antd";
10
+ import React, { useEffect, useState } from "react";
11
+ import { nanoid } from "nanoid";
12
+
13
+ import { handleInputFileList, getFileName, getFileType } from "./common/utils";
14
+ import UploadOss from "./common/ossUpload";
15
+
16
+ import "./uploader.less";
17
+
18
+ export function Uploader({ onChange, ...props }) {
19
+ const {
20
+ name = "file",
21
+ multiple = false,
22
+ accept,
23
+ value,
24
+ maxCount = 1,
25
+ // 文件大小限制
26
+ maxSize,
27
+ listType = "text",
28
+ disabled = false,
29
+ readOnly = false,
30
+ ossUrl = "/api/v1/user/oss/getWebOssConfig",
31
+ // 是否使用 oss 上传文件
32
+ isOssUpload,
33
+ // 是否使用字符串结果
34
+ isStrRes,
35
+ // maxCount === 1 时,结果是否自动去除数组嵌套
36
+ isResRemoveArr = true,
37
+ ossOpt,
38
+ onCountExceed,
39
+ templateUrl,
40
+ templateDownloadText = "模板下载",
41
+ } = props;
42
+
43
+ const [fileList, setFileList] = useState([]);
44
+
45
+ const handleChange = async ({ fileList }) => {
46
+ setFileList(fileList);
47
+ let _files = [...fileList];
48
+ // 处理提交结果为字符串的情况
49
+ if (isOssUpload && isStrRes) {
50
+ for (let i = 0; i < _files.length; i++) {
51
+ let _file = _files[i];
52
+ if (_file?.originFileObj) {
53
+ _file = _file?.originFileObj;
54
+ }
55
+ // 从文件对象中获取 url,ossPromise 为上传中的文件,兼容 file 为字符串的情况
56
+ let str = (await _file.ossPromise) || _file.url || _file.ossUrl || _file;
57
+ _files[i] = str;
58
+ }
59
+ }
60
+ // 处理空数据情况
61
+ if (!_files || _files.length <= 0) {
62
+ _files = undefined;
63
+ } else if (isResRemoveArr && maxCount === 1) {
64
+ // maxCount 为 1 的时候返回结果去除数组
65
+ _files = _files[0];
66
+ }
67
+ onChange && onChange?.(_files);
68
+ };
69
+
70
+ useEffect(() => {
71
+ if (!value) {
72
+ return;
73
+ }
74
+ let _list = value;
75
+ if (typeof _list === "string") {
76
+ _list = value.split(",");
77
+ }
78
+ if (Array.isArray(_list)) {
79
+ _list = _list.map((res) => {
80
+ if (typeof res === "string") {
81
+ return {
82
+ name: getFileName(res),
83
+ status: "done",
84
+ uid: `rc-upload-${Date.now()}-${nanoid()}`,
85
+ url: res,
86
+ type: getFileType(res),
87
+ };
88
+ }
89
+ return res;
90
+ });
91
+ }
92
+ setFileList(handleInputFileList(_list, maxCount));
93
+ }, [value]);
94
+
95
+ const onRemove = (file) => {
96
+ const files = (fileList || []).filter((v) => v.url !== file.url);
97
+ onChange && onChange(files);
98
+ };
99
+
100
+ const _uploadProps = {
101
+ iconRender: (e) => {
102
+ if (e?.type == "application/pdf") {
103
+ return <FilePdfTwoTone twoToneColor={"#DD4B43"} />;
104
+ } else if (e?.type?.startsWith("video/")) {
105
+ return <VideoCameraTwoTone twoToneColor={"#3C87F7"} />;
106
+ } else if (
107
+ e?.type == "application/vnd.ms-excel" ||
108
+ e?.type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
109
+ ) {
110
+ return <FileExcelTwoTone twoToneColor={"#68B27C"} />;
111
+ } else if (
112
+ e?.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
113
+ e?.type == "application/msword"
114
+ ) {
115
+ return <FileWordTwoTone twoToneColor={"#88CCF5"} />;
116
+ } else {
117
+ return <PaperClipOutlined />;
118
+ }
119
+ },
120
+ name: name,
121
+ accept: accept,
122
+ multiple: multiple,
123
+ listType: listType,
124
+ maxCount: maxCount,
125
+ fileList: fileList,
126
+ onChange: handleChange,
127
+ disabled,
128
+ readOnly,
129
+ onRemove,
130
+ beforeUpload(file, _fileList) {
131
+ // 文件大小限制
132
+ let hasOverSize = false;
133
+ let overSizeTips = "";
134
+ _fileList.forEach((it) => {
135
+ if (maxSize && it.size > maxSize) {
136
+ message.error(`${it.name} 文件大小超出限制:${it.size}`);
137
+ overSizeTips += `${it.name} 文件大小超出限制:${it.size}; `;
138
+ hasOverSize = true;
139
+ }
140
+ });
141
+ if (hasOverSize) {
142
+ return AUpload.LIST_IGNORE;
143
+ }
144
+
145
+ // 超出个数限制只保留前面的 maxCount 个
146
+ if (maxCount > 0 && fileList.length >= maxCount) {
147
+ onCountExceed && onCountExceed(fileList.length - maxCount);
148
+ fileList.length = maxCount;
149
+ message.error("文件个数超出限制");
150
+ return AUpload.LIST_IGNORE;
151
+ }
152
+ return true;
153
+ },
154
+ customRequest: isOssUpload
155
+ ? function customRequest({ action, data, file, filename, headers, method, onSuccess, onProgress, onError }) {
156
+ const ossUpload = new UploadOss({
157
+ serverUrl: ossUrl,
158
+ ...ossOpt,
159
+ });
160
+ file.ossPromise = ossUpload
161
+ .upload(file, {
162
+ params: {
163
+ isPublic: 1,
164
+ ...(props.params || {}),
165
+ },
166
+ })
167
+ .then((res) => {
168
+ file.url = res?.data?.data?.fileUrl;
169
+ file.ossUrl = file.url;
170
+ onSuccess(file);
171
+ return file.ossUrl;
172
+ })
173
+ .catch(onError);
174
+ return;
175
+ }
176
+ : null,
177
+ ...(props || {}),
178
+ };
179
+
180
+ return (
181
+ <>
182
+ <AUpload className="oss-uploader" {..._uploadProps}>
183
+ {maxCount && maxCount > fileList.length ? (
184
+ listType === "picture-card" ? (
185
+ props.btnText || "上传"
186
+ ) : (
187
+ <Button icon={<UploadOutlined />}>{props.btnText || "上传"}</Button>
188
+ )
189
+ ) : null}
190
+ </AUpload>
191
+ {templateUrl ? (
192
+ <a className="uploader-template-download-btn" href={templateUrl}>
193
+ {templateDownloadText}
194
+ </a>
195
+ ) : null}
196
+ </>
197
+ );
198
+ }
199
+
200
+ export default Uploader;
@@ -0,0 +1,3 @@
1
+ .uploader-template-download-btn {
2
+ color: #1890ff;
3
+ }
@@ -1,3 +1,5 @@
1
1
  import Text from "./Text";
2
2
 
3
+ export * from "./Upload";
4
+
3
5
  export { Text };
package/src/index.tsx CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  TimePicker,
22
22
  Transfer,
23
23
  TreeSelect,
24
- Upload,
24
+ // Upload,
25
25
  FormGrid,
26
26
  FormLayout,
27
27
  FormTab,
@@ -32,6 +32,7 @@ import {
32
32
  import { Card, Slider, Rate } from "antd";
33
33
  // 自定义组件
34
34
  import * as customComponents from "./components/index";
35
+ import { GlobalPropsContext } from "./common/global-props-context";
35
36
 
36
37
  import "./index.less";
37
38
 
@@ -63,7 +64,7 @@ const FormRender = forwardRef((props: any, parentRef) => {
63
64
  TimePicker,
64
65
  Transfer,
65
66
  TreeSelect,
66
- Upload,
67
+ // Upload,
67
68
  Card,
68
69
  Slider,
69
70
  Rate,
@@ -102,19 +103,37 @@ const FormRender = forwardRef((props: any, parentRef) => {
102
103
  props.init && props.init(formRender);
103
104
  }, []);
104
105
 
106
+ const footer = useMemo(() => {
107
+ const { Slots } = props;
108
+ if (typeof Slots === "function") {
109
+ return <Slots />;
110
+ }
111
+ if (typeof Slots?.footer === "function") {
112
+ return <Slots.footer />;
113
+ }
114
+ }, [props.Slots]);
115
+
116
+ const formLayoutProps = {
117
+ ...props.schema?.form,
118
+ };
119
+
105
120
  return (
106
- <Form
107
- className={`form-render ${props.className}`}
108
- layout={props.layout}
109
- form={formRender}
110
- labelCol={props.schema.form.labelCol}
111
- wrapperCol={props.schema.form.wrapperCol}
112
- onAutoSubmit={autoSubmit}
113
- onAutoSubmitFailed={autoSubmitFailed}
114
- >
115
- <SchemaField schema={props.schema.schema}></SchemaField>
116
- <div className="form-render-footer xxm">{props.Slots && <props.Slots />}</div>
117
- </Form>
121
+ <GlobalPropsContext.Provider value={props}>
122
+ <Form
123
+ className={`form-render ${props.className}`}
124
+ layout={props.layout}
125
+ form={formRender}
126
+ labelCol={props.schema.form.labelCol}
127
+ wrapperCol={props.schema.form.wrapperCol}
128
+ onAutoSubmit={autoSubmit}
129
+ onAutoSubmitFailed={autoSubmitFailed}
130
+ >
131
+ <FormLayout {...formLayoutProps}>
132
+ <SchemaField schema={props.schema.schema}></SchemaField>
133
+ <div className="form-render-footer xxm">{footer}</div>
134
+ </FormLayout>
135
+ </Form>
136
+ </GlobalPropsContext.Provider>
118
137
  );
119
138
  });
120
139
 
package/CHANGELOG DELETED
@@ -1,2 +0,0 @@
1
- # @hzab/form-render0.0.1
2
- 组件初始化