@hzab/form-render 1.6.7 → 1.6.8-beta1
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/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/common/formily-utils.ts +119 -7
- package/src/components/LocationListPicker/index.tsx +1 -1
- package/src/components/PersonnelSelect/index.less +21 -0
- package/src/components/PersonnelSelect/index.module.less +33 -0
- package/src/components/PersonnelSelect/index.tsx +257 -0
- package/src/components/PersonnelSelect/type.ts +0 -0
- package/src/components/RichEditor/index.tsx +53 -34
- package/src/components/index.tsx +1 -0
- package/src/components/RichEditor/common/ossUpload.js +0 -155
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
@@ -1,13 +1,125 @@
|
|
1
|
+
import _ from "lodash";
|
2
|
+
|
3
|
+
export interface ISetTargetLevelValOpt {
|
4
|
+
/**
|
5
|
+
* 相对当前的层级数
|
6
|
+
*/
|
7
|
+
level?: number;
|
8
|
+
/**
|
9
|
+
* 目标数据为数组是否使用 push
|
10
|
+
*/
|
11
|
+
isArrPush?: boolean;
|
12
|
+
}
|
13
|
+
|
1
14
|
/**
|
2
|
-
*
|
3
|
-
*
|
4
|
-
* @param field
|
15
|
+
* 获取父级的数据(包括当前项的对象)
|
16
|
+
* @param field formily field 对象 const field: any = useField();
|
5
17
|
* @returns
|
6
18
|
*/
|
7
19
|
export function getParentValue(field) {
|
8
|
-
|
9
|
-
|
10
|
-
|
20
|
+
return getTargetLevelVal(field, -1);
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* 设置当前 field 同层级数据值。
|
25
|
+
* @param field formily field 对象 const field: any = useField();
|
26
|
+
* @param key 目标 key
|
27
|
+
* @param val 目标值
|
28
|
+
* @example
|
29
|
+
* 如当前的 field name 为 test, 设置同层级 testA
|
30
|
+
* // 初始值:
|
31
|
+
* {
|
32
|
+
* test: 1
|
33
|
+
* }
|
34
|
+
* // 设置同层级 key 为 testA 的值为 222
|
35
|
+
* setParentValue(field, 'testA', 222);
|
36
|
+
* // 设置结果
|
37
|
+
* {
|
38
|
+
* test: 1,
|
39
|
+
* testA: 222
|
40
|
+
* }
|
41
|
+
*/
|
42
|
+
export function setParentValue(field, key, val) {
|
43
|
+
setTargetLevelVal(field, key, val, { level: -1 });
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* 获取相对当前层级的数据对象(包括当前项的对象)
|
48
|
+
* @param field formily field 对象 const field: any = useField();
|
49
|
+
* @param key 目标 key
|
50
|
+
* @param val 目标 value
|
51
|
+
* @param level 相对的层级 负数
|
52
|
+
* @returns
|
53
|
+
*/
|
54
|
+
export function getTargetLevelVal(field, level) {
|
55
|
+
if (typeof field !== "object" || !field.form || !field.path) {
|
56
|
+
console.warn("Warn setCurLevelData: field 入参不是一个正确的 formily field 数据");
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
// 设置目标参数的路径
|
60
|
+
let path = [...field.path.segments];
|
61
|
+
// 层级超出路径长度不进行设置
|
62
|
+
if (Math.abs(level) > path.length) {
|
63
|
+
console.info("Info setCurLevelData: level 层级超出路径长度不进行设置");
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
if (typeof level === "number") {
|
67
|
+
path = path.slice(0, level);
|
68
|
+
}
|
69
|
+
// 设置目标数据
|
70
|
+
return _.get(field.form.values, path.join("."));
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* 设置相对当前层级的数据对象(包括当前项的对象)
|
75
|
+
* @param field formily field 对象 const field: any = useField();
|
76
|
+
* @param key 目标 key
|
77
|
+
* @param val 目标 value
|
78
|
+
* @param level 相对的层级
|
79
|
+
* @returns
|
80
|
+
*/
|
81
|
+
export function setTargetLevelVal(
|
82
|
+
field,
|
83
|
+
key,
|
84
|
+
val,
|
85
|
+
opt: ISetTargetLevelValOpt = { level: undefined, isArrPush: false },
|
86
|
+
) {
|
87
|
+
if (typeof field !== "object" || !field.form || !field.path) {
|
88
|
+
console.warn("Warn setCurLevelData: field 入参不是一个正确的 formily field 数据");
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
const {
|
92
|
+
/**
|
93
|
+
* 数组是否进行 push 曹组
|
94
|
+
*/
|
95
|
+
isArrPush,
|
96
|
+
/**
|
97
|
+
* 相对的层级
|
98
|
+
*/
|
99
|
+
level,
|
100
|
+
} = opt || {};
|
101
|
+
// 设置目标参数的路径
|
102
|
+
let path = [...field.path.segments];
|
103
|
+
// 层级超出路径长度不进行设置
|
104
|
+
if (Math.abs(level) > path.length) {
|
105
|
+
console.info("Info setCurLevelData: level 层级超出路径长度不进行设置");
|
106
|
+
return;
|
107
|
+
}
|
108
|
+
if (typeof level === "number") {
|
109
|
+
path = path.slice(0, -Math.abs(level));
|
110
|
+
}
|
111
|
+
const parentPath = path.join(".");
|
112
|
+
const parentType = _.get(field.form.values, path);
|
113
|
+
// 添加目标 key,得到目标完整 path
|
114
|
+
path.push(key);
|
115
|
+
// 克隆 form 数据,用于修改目标数据 ()
|
116
|
+
const data = _.cloneDeep(field.form.values);
|
117
|
+
// 设置目标数据
|
118
|
+
if (isArrPush && Array.isArray(parentType)) {
|
119
|
+
const _d = _.get(data, parentPath);
|
120
|
+
Array.isArray(_d) && _d.push({ [key]: val });
|
121
|
+
} else {
|
122
|
+
_.set(data, path.join("."), val);
|
11
123
|
}
|
12
|
-
|
124
|
+
field.form.setValues(data);
|
13
125
|
}
|
@@ -63,13 +63,13 @@ export const LocationListPicker = observer((props) => {
|
|
63
63
|
if (!Array.isArray(value) || value?.length == 0) {
|
64
64
|
return;
|
65
65
|
}
|
66
|
-
listRef.current = value || [];
|
67
66
|
// 处理 id
|
68
67
|
value?.forEach((it) => {
|
69
68
|
if (!it.id) {
|
70
69
|
it.id = nanoid();
|
71
70
|
}
|
72
71
|
});
|
72
|
+
listRef.current = value || [];
|
73
73
|
if (!mapUtilsRef.current) {
|
74
74
|
return;
|
75
75
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
.abt-user-item {
|
2
|
+
display: flex;
|
3
|
+
align-items: center;
|
4
|
+
padding: 0 16px;
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
.abt-user-item-info {
|
9
|
+
list-style: none;
|
10
|
+
flex: 1;
|
11
|
+
display: flex;
|
12
|
+
flex-direction: column;
|
13
|
+
padding-left: 16px;
|
14
|
+
|
15
|
+
.abt-user-item-info__item {
|
16
|
+
& > span:last-of-type {
|
17
|
+
color: #666;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
.user-select {
|
2
|
+
|
3
|
+
&-option {
|
4
|
+
:global {
|
5
|
+
.ant-select-item-option-content {
|
6
|
+
display: flex;
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
10
|
+
|
11
|
+
&__avatar {
|
12
|
+
margin-right: 10px;
|
13
|
+
}
|
14
|
+
|
15
|
+
.user-item {
|
16
|
+
display: flex;
|
17
|
+
flex-direction: column;
|
18
|
+
|
19
|
+
&-name {
|
20
|
+
margin-bottom: 4px;
|
21
|
+
color: #000000d9;
|
22
|
+
font-size: 14px;
|
23
|
+
display: flex;
|
24
|
+
align-items: center;
|
25
|
+
}
|
26
|
+
|
27
|
+
&__idCard {
|
28
|
+
color: #00000073;
|
29
|
+
font-size: 14px;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,257 @@
|
|
1
|
+
import React, { useEffect, useState, useRef, useCallback, useMemo } from "react";
|
2
|
+
import { Select, List, Avatar, Spin } from "antd";
|
3
|
+
import { connect, mapProps } from "@formily/react";
|
4
|
+
import type { SelectProps } from "antd/lib/select";
|
5
|
+
import type { AvatarProps } from "antd/lib/avatar";
|
6
|
+
import { debounce, isObject, uniq } from "lodash";
|
7
|
+
|
8
|
+
import "./index.less";
|
9
|
+
|
10
|
+
export interface Person {
|
11
|
+
id: string | number;
|
12
|
+
name: string;
|
13
|
+
avatar?: string;
|
14
|
+
email?: string;
|
15
|
+
description?: string;
|
16
|
+
disabled?: boolean;
|
17
|
+
[key: string]: any;
|
18
|
+
}
|
19
|
+
|
20
|
+
interface ScrollPagination {
|
21
|
+
pageNum: number;
|
22
|
+
pageSize: number;
|
23
|
+
current?: number;
|
24
|
+
total?: number;
|
25
|
+
}
|
26
|
+
|
27
|
+
type LableValue = {
|
28
|
+
label: string;
|
29
|
+
value: string | number;
|
30
|
+
[key: string]: any;
|
31
|
+
};
|
32
|
+
|
33
|
+
interface RemoteSelectProps extends Omit<SelectProps<any>, "options" | "children"> {
|
34
|
+
loadOptions: (
|
35
|
+
search: string,
|
36
|
+
pagination: ScrollPagination,
|
37
|
+
) => Promise<{ list: Person[]; pagination: ScrollPagination }>;
|
38
|
+
renderItem?: (item: Person) => React.ReactNode;
|
39
|
+
initialPagination?: Partial<ScrollPagination>;
|
40
|
+
avataProps?: AvatarProps;
|
41
|
+
value: LableValue[] | LableValue;
|
42
|
+
listItemConfigs?: { label: string; key: string }[];
|
43
|
+
disabledKey?: string;
|
44
|
+
disabledStyle?: (disabled: boolean) => React.CSSProperties;
|
45
|
+
customItemNode?: React.ReactNode;
|
46
|
+
}
|
47
|
+
|
48
|
+
const defaultListItemConfigs = [
|
49
|
+
{ label: "姓名:", key: "userName" },
|
50
|
+
{ label: "主部门:", key: "orgName" },
|
51
|
+
{ label: "主驻点:", key: "userName" },
|
52
|
+
{ label: "手机号:", key: "phoneNumber" },
|
53
|
+
{ label: "身份证号:", key: "idnumber" },
|
54
|
+
];
|
55
|
+
|
56
|
+
const RemoteSelect: React.FC<RemoteSelectProps> = ({
|
57
|
+
loadOptions,
|
58
|
+
renderItem,
|
59
|
+
listItemConfigs,
|
60
|
+
disabledKey = "disabled",
|
61
|
+
initialPagination = {},
|
62
|
+
avataProps = {},
|
63
|
+
disabledStyle,
|
64
|
+
customItemNode,
|
65
|
+
...selectProps
|
66
|
+
}) => {
|
67
|
+
const [search, setSearch] = useState("");
|
68
|
+
const [loading, setLoading] = useState(false);
|
69
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
70
|
+
const [list, setList] = useState<Person[]>([]);
|
71
|
+
const [pagination, setPagination] = useState<ScrollPagination>({
|
72
|
+
pageNum: 1,
|
73
|
+
pageSize: 10,
|
74
|
+
...initialPagination,
|
75
|
+
});
|
76
|
+
const listRef = useRef<HTMLDivElement>(null);
|
77
|
+
const selectInstance = useRef<any>(null);
|
78
|
+
|
79
|
+
const debounceLoadData = debounce(async (isScrollLoad = false) => {
|
80
|
+
try {
|
81
|
+
isScrollLoad ? setLoadingMore(true) : setLoading(true);
|
82
|
+
|
83
|
+
const result = await loadOptions(search, {
|
84
|
+
...pagination,
|
85
|
+
pageNum: isScrollLoad ? pagination.pageNum + 1 : 1,
|
86
|
+
});
|
87
|
+
const { list } = result;
|
88
|
+
|
89
|
+
setList((prev) => (isScrollLoad ? [...prev, ...list] : list));
|
90
|
+
} finally {
|
91
|
+
isScrollLoad ? setLoadingMore(false) : setLoading(false);
|
92
|
+
}
|
93
|
+
}, 500);
|
94
|
+
const debounceHandleScroll = async (e: React.UIEvent<HTMLDivElement>) => {
|
95
|
+
const { scrollTop, clientHeight, scrollHeight } = e.currentTarget;
|
96
|
+
const isNearBottom = scrollHeight - scrollTop - clientHeight < 30;
|
97
|
+
|
98
|
+
if (isNearBottom && !loadingMore) {
|
99
|
+
await loadData(true);
|
100
|
+
setPagination((pre) => ({ ...pre, pageNum: pagination.pageNum + 1 }));
|
101
|
+
}
|
102
|
+
};
|
103
|
+
|
104
|
+
const loadData = useCallback(debounceLoadData, [search, pagination, loadOptions]);
|
105
|
+
|
106
|
+
// 处理滚动加载
|
107
|
+
const handleScroll = useCallback(debounceHandleScroll, [loadingMore, loadData]);
|
108
|
+
|
109
|
+
// 处理搜索
|
110
|
+
const handleSearch = debounce((value: string) => {
|
111
|
+
setSearch(value);
|
112
|
+
setPagination((prev) => ({ ...prev, pageNum: 1 }));
|
113
|
+
}, 1000);
|
114
|
+
|
115
|
+
useEffect(() => {
|
116
|
+
loadData();
|
117
|
+
}, [search]);
|
118
|
+
|
119
|
+
// 处理选项点击
|
120
|
+
const handleItemClick = (itemId: string | number) => {
|
121
|
+
const currentValue = selectProps.value;
|
122
|
+
const isMultiple = selectProps.mode === "multiple";
|
123
|
+
|
124
|
+
// 更新值
|
125
|
+
if (isMultiple) {
|
126
|
+
const isValueArr = Array.isArray(currentValue);
|
127
|
+
let newValue = null;
|
128
|
+
|
129
|
+
if (isValueArr) {
|
130
|
+
const currentValueIds = currentValue.map((it) => it.value);
|
131
|
+
newValue = currentValueIds.includes(itemId)
|
132
|
+
? currentValueIds.filter((v) => v !== itemId)
|
133
|
+
: uniq([...currentValue, itemId]);
|
134
|
+
} else {
|
135
|
+
newValue = [itemId];
|
136
|
+
}
|
137
|
+
|
138
|
+
selectProps.onChange?.(newValue, currentValue);
|
139
|
+
} else {
|
140
|
+
const newValue = itemId === (currentValue as LableValue)?.value ? undefined : itemId;
|
141
|
+
selectProps.onChange?.(newValue, currentValue);
|
142
|
+
|
143
|
+
// 单选模式下关闭下拉框
|
144
|
+
if (selectInstance.current) {
|
145
|
+
selectInstance.current.blur();
|
146
|
+
}
|
147
|
+
}
|
148
|
+
};
|
149
|
+
|
150
|
+
const itemConfigs = useMemo(() => {
|
151
|
+
if (Array.isArray(listItemConfigs) && listItemConfigs?.length) return listItemConfigs;
|
152
|
+
return defaultListItemConfigs;
|
153
|
+
}, [listItemConfigs]);
|
154
|
+
|
155
|
+
// 默认渲染项(增加选中状态和点击处理)
|
156
|
+
const defaultRenderItem = useCallback(
|
157
|
+
(item: Person) => {
|
158
|
+
let isSelected;
|
159
|
+
|
160
|
+
if (selectProps.mode === "multiple") {
|
161
|
+
isSelected = (selectProps.value || [])
|
162
|
+
.map((it) => (isObject(it) ? it?.value || it?.userId : it))
|
163
|
+
.includes(item.userId);
|
164
|
+
} else {
|
165
|
+
isSelected = isObject(selectProps.value) ? (selectProps.value as LableValue)?.value === item.userId : false;
|
166
|
+
}
|
167
|
+
|
168
|
+
const disabled = item?.[disabledKey] || false;
|
169
|
+
const getDisabledStyle: React.CSSProperties =
|
170
|
+
typeof disabledStyle === "function"
|
171
|
+
? disabledStyle(disabled)
|
172
|
+
: {
|
173
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
174
|
+
backgroundColor: disabled ? "#f0f0f0" : isSelected ? "#e6f7ff" : "inherit",
|
175
|
+
color: disabled ? "#999" : "inherit",
|
176
|
+
filter: disabled ? "grayscale(100%)" : "none",
|
177
|
+
};
|
178
|
+
|
179
|
+
return (
|
180
|
+
<List.Item
|
181
|
+
key={item.userId}
|
182
|
+
onClick={() => (disabled ? null : handleItemClick(item.userId))}
|
183
|
+
style={getDisabledStyle}
|
184
|
+
>
|
185
|
+
{customItemNode ? (
|
186
|
+
customItemNode
|
187
|
+
) : (
|
188
|
+
<div className="abt-user-item">
|
189
|
+
<div className="abt-user-item-avatar">
|
190
|
+
<Avatar alt={item?.userName} size={60} {...avataProps} src={item.avatar} />
|
191
|
+
</div>
|
192
|
+
<ul className="abt-user-item-info">
|
193
|
+
{itemConfigs.map((configItem) => (
|
194
|
+
<li className="abt-user-item-info__item">
|
195
|
+
<span>{configItem.label}</span>
|
196
|
+
<span>{item?.[configItem.key]}</span>
|
197
|
+
</li>
|
198
|
+
))}
|
199
|
+
</ul>
|
200
|
+
</div>
|
201
|
+
)}
|
202
|
+
</List.Item>
|
203
|
+
);
|
204
|
+
},
|
205
|
+
[selectProps.value],
|
206
|
+
);
|
207
|
+
|
208
|
+
// 自定义下拉内容
|
209
|
+
const dropdownRender = (menu: React.ReactNode) => (
|
210
|
+
<div ref={listRef}>
|
211
|
+
<Spin spinning={loading}>
|
212
|
+
<List
|
213
|
+
loading={loadingMore}
|
214
|
+
dataSource={list}
|
215
|
+
renderItem={renderItem ? renderItem : defaultRenderItem}
|
216
|
+
/*@ts-ignore*/
|
217
|
+
onScroll={handleScroll}
|
218
|
+
style={{ maxHeight: 250, overflowY: "auto" }}
|
219
|
+
/>
|
220
|
+
</Spin>
|
221
|
+
</div>
|
222
|
+
);
|
223
|
+
|
224
|
+
return (
|
225
|
+
<Select
|
226
|
+
{...selectProps}
|
227
|
+
ref={selectInstance}
|
228
|
+
showSearch
|
229
|
+
onSearch={handleSearch}
|
230
|
+
filterOption={false}
|
231
|
+
dropdownRender={dropdownRender}
|
232
|
+
placeholder="请选择人员"
|
233
|
+
// 增加虚拟的options用于显示选中标签
|
234
|
+
options={list.map((item) => ({
|
235
|
+
value: item.userId,
|
236
|
+
label: item.userName,
|
237
|
+
}))}
|
238
|
+
/>
|
239
|
+
);
|
240
|
+
};
|
241
|
+
|
242
|
+
// Formily集成
|
243
|
+
const PersonnelSelect = connect(
|
244
|
+
RemoteSelect,
|
245
|
+
mapProps(
|
246
|
+
{
|
247
|
+
loading: true,
|
248
|
+
},
|
249
|
+
(props, field: any) => {
|
250
|
+
return {
|
251
|
+
...props,
|
252
|
+
};
|
253
|
+
},
|
254
|
+
),
|
255
|
+
);
|
256
|
+
|
257
|
+
export { RemoteSelect, PersonnelSelect };
|
File without changes
|
@@ -2,7 +2,8 @@ import "@wangeditor/editor/dist/css/style.css"; // 引入 css
|
|
2
2
|
import React, { useState, useEffect, useImperativeHandle, forwardRef } from "react";
|
3
3
|
import { Editor as WangEditor, Toolbar } from "@wangeditor/editor-for-react";
|
4
4
|
import { IDomEditor, IEditorConfig, IToolbarConfig } from "@wangeditor/editor";
|
5
|
-
import
|
5
|
+
import { getOssUploadRequest, getOfflineUploadRequest } from "../Upload/common/customRequest";
|
6
|
+
import { handlePreviewUrls } from "../Upload/common/OfflineUpload";
|
6
7
|
import "./index.less";
|
7
8
|
import { Spin, message } from 'antd';
|
8
9
|
import { connect, mapProps, observer } from "@formily/react";
|
@@ -15,11 +16,16 @@ interface PropsType {
|
|
15
16
|
toolbarConfig?: Partial<IToolbarConfig>;
|
16
17
|
editorConfig?: Partial<IEditorConfig>;
|
17
18
|
ossOpt: AnyObject,
|
18
|
-
|
19
|
+
uploadParams: AnyObject,
|
19
20
|
height: string,
|
21
|
+
ossServerUrl: string,
|
20
22
|
ossUrl: string,
|
23
|
+
offlinePreviewUrl: string,
|
24
|
+
offlineServerUrl: string,
|
21
25
|
value: string,
|
22
26
|
onChange: any,
|
27
|
+
customRequest: any,
|
28
|
+
uploadMode: string,
|
23
29
|
zIndex: number
|
24
30
|
}
|
25
31
|
|
@@ -31,10 +37,18 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
31
37
|
onChange,
|
32
38
|
value,
|
33
39
|
ossOpt,
|
40
|
+
uploadMode = "oss",
|
41
|
+
offlinePreviewUrl = "",
|
42
|
+
offlineServerUrl = "",
|
43
|
+
uploadParams = {},
|
34
44
|
// ossUrl = "https://test-abt.hzabjt.com:18091/api/v1/user/oss/getWebOssConfig",
|
45
|
+
ossServerUrl = "/api/v1/user/oss/getWebOssConfig",
|
35
46
|
ossUrl = "/api/v1/user/oss/getWebOssConfig",
|
36
47
|
zIndex = 9999
|
37
48
|
} = props;
|
49
|
+
const isPrivateStore =
|
50
|
+
props.uploadParams?.fileAcl === "private" || props.ossOpt?.signatureParams?.fileAcl === "private";
|
51
|
+
|
38
52
|
useImperativeHandle(parentRef, () => ({
|
39
53
|
setEditorContent,
|
40
54
|
getEditorContent,
|
@@ -57,6 +71,40 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
57
71
|
return html;
|
58
72
|
};
|
59
73
|
|
74
|
+
|
75
|
+
const customUpload = async (file: File, insertFn) => {
|
76
|
+
let customRequest = props.customRequest;
|
77
|
+
if (uploadMode === "oss") {
|
78
|
+
customRequest = getOssUploadRequest({
|
79
|
+
...props,
|
80
|
+
isPrivateStore,
|
81
|
+
params: uploadParams,
|
82
|
+
ossServerUrl: ossServerUrl || ossUrl,
|
83
|
+
});
|
84
|
+
} else if (uploadMode === "offline") {
|
85
|
+
customRequest = getOfflineUploadRequest({
|
86
|
+
...props,
|
87
|
+
isPrivateStore,
|
88
|
+
params: uploadParams,
|
89
|
+
offlineServerUrl: offlineServerUrl || ossUrl,
|
90
|
+
offlinePreviewUrl: offlinePreviewUrl,
|
91
|
+
});
|
92
|
+
}
|
93
|
+
setIsLoading(true);
|
94
|
+
customRequest({
|
95
|
+
file,
|
96
|
+
onSuccess: (res) => {
|
97
|
+
setIsLoading(false);
|
98
|
+
console.log(res)
|
99
|
+
insertFn(res.url, '', '');
|
100
|
+
},
|
101
|
+
onError: () => {
|
102
|
+
setIsLoading(false);
|
103
|
+
message.error(`${file.name} 上传失败`);
|
104
|
+
}
|
105
|
+
})
|
106
|
+
}
|
107
|
+
|
60
108
|
const config: Partial<IEditorConfig> = {
|
61
109
|
// TS 语法
|
62
110
|
placeholder: "请输入内容...",
|
@@ -74,19 +122,7 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
74
122
|
withCredentials: false,
|
75
123
|
// 超时时间,默认为 10 秒
|
76
124
|
timeout: 5 * 1000, // 5 秒
|
77
|
-
|
78
|
-
const ossUpload = new UploadOss({
|
79
|
-
serverUrl: ossUrl,
|
80
|
-
...ossOpt,
|
81
|
-
});
|
82
|
-
const res = await ossUpload.upload(file, {
|
83
|
-
params: {
|
84
|
-
isPublic: 1,
|
85
|
-
...(props.params || {}),
|
86
|
-
},
|
87
|
-
})
|
88
|
-
insertFn(res?.data?.data?.fileUrl, '', '')
|
89
|
-
},
|
125
|
+
customUpload,
|
90
126
|
},
|
91
127
|
uploadVideo: {
|
92
128
|
// form-data fieldName ,默认值 'wangeditor-uploaded-image'
|
@@ -97,30 +133,13 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
97
133
|
allowedFileTypes: ['video/*'],
|
98
134
|
// 跨域是否传递 cookie ,默认为 false
|
99
135
|
withCredentials: false,
|
100
|
-
customUpload
|
101
|
-
setIsLoading(true)
|
102
|
-
const ossUpload = new UploadOss({
|
103
|
-
serverUrl: ossUrl,
|
104
|
-
...ossOpt,
|
105
|
-
});
|
106
|
-
ossUpload.upload(file, {
|
107
|
-
params: {
|
108
|
-
isPublic: 1,
|
109
|
-
...(props.params || {}),
|
110
|
-
},
|
111
|
-
}).then((res) => {
|
112
|
-
setIsLoading(false)
|
113
|
-
insertFn(res?.data?.data?.fileUrl, '', '')
|
114
|
-
}).catch(() => {
|
115
|
-
setIsLoading(false)
|
116
|
-
message.error(`${file.name} 上传失败`);
|
117
|
-
})
|
118
|
-
},
|
136
|
+
customUpload,
|
119
137
|
},
|
120
138
|
},
|
121
139
|
...editorConfig,
|
122
140
|
};
|
123
141
|
|
142
|
+
|
124
143
|
const onEditorChange = (e) => {
|
125
144
|
const content = e.getHtml();
|
126
145
|
setHtml(content);
|
package/src/components/index.tsx
CHANGED
@@ -1,155 +0,0 @@
|
|
1
|
-
import { axios } from "@hzab/data-model";
|
2
|
-
import { nanoid } from "nanoid";
|
3
|
-
|
4
|
-
export function getSignature(opt = {}) {
|
5
|
-
const { serverUrl = "/api/v1/user/oss/getWebOssConfig" } = opt;
|
6
|
-
// 减 10 秒,避免发起请求时 刚好过期的情况
|
7
|
-
if (
|
8
|
-
window.__ossSignatureRes &&
|
9
|
-
serverUrl === window.__ossSignatureRes.serverUrl &&
|
10
|
-
Date.now() - window.__ossSignatureRes.__saveTime < window.__ossSignatureRes.expireTimeMillis - 10000
|
11
|
-
) {
|
12
|
-
return Promise.resolve(window.__ossSignatureRes);
|
13
|
-
}
|
14
|
-
const { axios: _ax = axios, params = {}, axiosConf } = opt;
|
15
|
-
return _ax
|
16
|
-
.get(serverUrl, {
|
17
|
-
...axiosConf,
|
18
|
-
params: {
|
19
|
-
isPublic: 1,
|
20
|
-
...params,
|
21
|
-
},
|
22
|
-
})
|
23
|
-
.then((res) => {
|
24
|
-
window.__ossSignatureRes = res?.data?.data;
|
25
|
-
if (window.__ossSignatureRes) {
|
26
|
-
window.__ossSignatureRes.__saveTime = Date.now();
|
27
|
-
window.__ossSignatureRes.serverUrl = serverUrl;
|
28
|
-
}
|
29
|
-
return window.__ossSignatureRes;
|
30
|
-
});
|
31
|
-
}
|
32
|
-
|
33
|
-
class OssUpload {
|
34
|
-
constructor(props = {}) {
|
35
|
-
this.axios = props.axios || axios;
|
36
|
-
this.axiosConf = props.axiosConf || {};
|
37
|
-
this.serverUrl = props.serverUrl || "/api/v1/user/oss/getWebOssConfig";
|
38
|
-
this.signatureParams = props.signatureParams || {};
|
39
|
-
}
|
40
|
-
|
41
|
-
getSignature(serverUrl = this.serverUrl, opt) {
|
42
|
-
return getSignature({
|
43
|
-
...opt,
|
44
|
-
serverUrl,
|
45
|
-
axios: opt?.axios || this.axios,
|
46
|
-
axiosConf: { ...this.axiosConf, ...opt?.axiosConf },
|
47
|
-
});
|
48
|
-
}
|
49
|
-
|
50
|
-
upload(file, opt = {}) {
|
51
|
-
return new Promise(async (resolve, reject) => {
|
52
|
-
const ossParams = await this.getSignature(opt.serverUrl || this.serverUrl, {
|
53
|
-
...opt,
|
54
|
-
params: { ...this.signatureParams, ...opt.signatureParams },
|
55
|
-
});
|
56
|
-
|
57
|
-
const { ossParams: propOssParams } = opt || {};
|
58
|
-
const formData = new FormData();
|
59
|
-
// key 表示上传到 Bucket 内的 Object 的完整路径,例如 exampledir/exampleobject.txtObject,完整路径中不能包含 Bucket 名称。
|
60
|
-
// filename 表示待上传的本地文件名称。
|
61
|
-
let filename = file?.name;
|
62
|
-
if (file?.name) {
|
63
|
-
const nameArr = file?.name.match(/^(.+)\.(.+)$/);
|
64
|
-
if (nameArr && nameArr.length > 2) {
|
65
|
-
filename = `${nameArr[1]}_${Date.now()}_${nanoid()}.${nameArr[2]}`;
|
66
|
-
}
|
67
|
-
}
|
68
|
-
if (!filename) {
|
69
|
-
filename = `${Date.now()}_${nanoid()}.${file.type?.replace(/\w+\/, ''/)}`;
|
70
|
-
}
|
71
|
-
const key = `${ossParams?.dir}${filename}`;
|
72
|
-
formData.set("key", key);
|
73
|
-
formData.set("OSSAccessKeyId", ossParams.accessid);
|
74
|
-
formData.set("policy", ossParams.policy);
|
75
|
-
formData.set("Signature", ossParams.signature);
|
76
|
-
if (ossParams.callback) {
|
77
|
-
formData.set("callback", ossParams.callback);
|
78
|
-
}
|
79
|
-
formData.set("success_action_status", 200);
|
80
|
-
formData.set("file", file);
|
81
|
-
|
82
|
-
if (propOssParams) {
|
83
|
-
for (const key in propOssParams) {
|
84
|
-
if (Object.hasOwnProperty.call(propOssParams, key)) {
|
85
|
-
formData.set(key, propOssParams[key]);
|
86
|
-
}
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
const _axios = opt?.axios || this.axios;
|
91
|
-
|
92
|
-
return _axios
|
93
|
-
.post(ossParams.host, formData, { ...this.axiosConf, ...opt?.axiosConf })
|
94
|
-
.then((res) => {
|
95
|
-
resolve(res);
|
96
|
-
return res;
|
97
|
-
})
|
98
|
-
.catch((err) => {
|
99
|
-
console.error("oss upload err", err);
|
100
|
-
reject(err);
|
101
|
-
return Promise.reject(err);
|
102
|
-
});
|
103
|
-
});
|
104
|
-
}
|
105
|
-
}
|
106
|
-
|
107
|
-
/**
|
108
|
-
* 处理文件上传逻辑
|
109
|
-
* @param {Array} files
|
110
|
-
* @param {Object} opt
|
111
|
-
* @returns
|
112
|
-
*/
|
113
|
-
export async function handleOssUpload(files, opt) {
|
114
|
-
const _files = files;
|
115
|
-
const { ossUrl, signatureParams, ossParams, axiosConf } = opt || {};
|
116
|
-
const ossUpload = new OssUpload({
|
117
|
-
axios: opt.axios,
|
118
|
-
axiosConf: axiosConf,
|
119
|
-
serverUrl: ossUrl || "/api/v1/user/oss/getWebOssConfig",
|
120
|
-
});
|
121
|
-
|
122
|
-
const promise = [];
|
123
|
-
_files?.forEach((file) => {
|
124
|
-
// 数据已经是 url 的情况
|
125
|
-
if (typeof file === "string" || file.ossUrl) {
|
126
|
-
promise.push(Promise.resolve(file));
|
127
|
-
} else {
|
128
|
-
promise.push(
|
129
|
-
ossUpload
|
130
|
-
.upload(file, {
|
131
|
-
signatureParams: {
|
132
|
-
isPublic: 1,
|
133
|
-
...(signatureParams || {}),
|
134
|
-
},
|
135
|
-
ossParams,
|
136
|
-
axiosConf,
|
137
|
-
})
|
138
|
-
.then((res) => {
|
139
|
-
return Promise.resolve(res?.data?.data?.fileUrl);
|
140
|
-
}),
|
141
|
-
);
|
142
|
-
}
|
143
|
-
});
|
144
|
-
|
145
|
-
return Promise.all(promise).then((filePromises) => {
|
146
|
-
filePromises?.forEach((fileUrl, idx) => {
|
147
|
-
_files[idx].ossUrl = fileUrl;
|
148
|
-
});
|
149
|
-
return Promise.resolve(_files);
|
150
|
-
});
|
151
|
-
}
|
152
|
-
|
153
|
-
export { axios };
|
154
|
-
|
155
|
-
export default OssUpload;
|