@hzab/form-render 1.6.8 → 1.6.10
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 +7 -0
- package/package.json +1 -1
- package/src/common/formily-utils.ts +119 -7
- package/src/components/PersonnelSelect/index.less +21 -0
- package/src/components/PersonnelSelect/index.module.less +33 -0
- package/src/components/PersonnelSelect/index.tsx +248 -0
- package/src/components/PersonnelSelect/type.ts +76 -0
- package/src/components/RichEditor/index.tsx +38 -35
- package/src/components/index.tsx +1 -0
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
|
}
|
@@ -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,248 @@
|
|
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 { debounce, isObject } from "lodash";
|
5
|
+
import type { LableValue, Person, ScrollPagination, RemoteSelectProps } from "./type";
|
6
|
+
|
7
|
+
import "./index.less";
|
8
|
+
|
9
|
+
const defaultListItemConfigs = [
|
10
|
+
{ label: "姓名:", key: "userName" },
|
11
|
+
{ label: "主部门:", key: "parentName" },
|
12
|
+
{ label: "主驻点:", key: "orgName" },
|
13
|
+
{ label: "手机号:", key: "phoneNumber" },
|
14
|
+
{ label: "身份证号:", key: "idnumber" },
|
15
|
+
];
|
16
|
+
|
17
|
+
const transfromLabelInValueData = (list: Person[], labelKey: string, valueKey: string): Person[] => {
|
18
|
+
try {
|
19
|
+
return list.map((item) => ({ ...item, label: item?.[labelKey], value: item?.[valueKey] }));
|
20
|
+
} catch (e) {
|
21
|
+
console.error(e);
|
22
|
+
return list;
|
23
|
+
}
|
24
|
+
};
|
25
|
+
|
26
|
+
const RemoteSelect: React.FC<RemoteSelectProps> = ({
|
27
|
+
loadOptions,
|
28
|
+
renderItem,
|
29
|
+
listItemConfigs,
|
30
|
+
labelKey = "userName",
|
31
|
+
valueKey = "userId",
|
32
|
+
disabledKey = "disabled",
|
33
|
+
initialPagination = {},
|
34
|
+
avataProps = {},
|
35
|
+
disabledStyle,
|
36
|
+
customItemNode,
|
37
|
+
...selectProps
|
38
|
+
}) => {
|
39
|
+
const [search, setSearch] = useState("");
|
40
|
+
const [loading, setLoading] = useState(false);
|
41
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
42
|
+
const [list, setList] = useState<Person[]>([]);
|
43
|
+
const [pagination, setPagination] = useState<ScrollPagination>({
|
44
|
+
pageNum: 1,
|
45
|
+
pageSize: 10,
|
46
|
+
...initialPagination,
|
47
|
+
});
|
48
|
+
const selectInstance = useRef<any>(null);
|
49
|
+
const total = useRef(0);
|
50
|
+
|
51
|
+
const debounceLoadData = debounce(async (isScrollLoad = false) => {
|
52
|
+
try {
|
53
|
+
isScrollLoad ? setLoadingMore(true) : setLoading(true);
|
54
|
+
|
55
|
+
const result = await loadOptions(search, {
|
56
|
+
...pagination,
|
57
|
+
pageNum: isScrollLoad ? pagination.pageNum + 1 : 1,
|
58
|
+
});
|
59
|
+
const { list, pagination: resPagination } = result;
|
60
|
+
|
61
|
+
total.current = resPagination.total;
|
62
|
+
|
63
|
+
const transformatList: Person[] = transfromLabelInValueData(list, labelKey, valueKey);
|
64
|
+
|
65
|
+
setList((prev) => (isScrollLoad ? [...prev, ...transformatList] : transformatList));
|
66
|
+
} finally {
|
67
|
+
isScrollLoad ? setLoadingMore(false) : setLoading(false);
|
68
|
+
}
|
69
|
+
}, 500);
|
70
|
+
|
71
|
+
const debounceHandleScroll = async (e: React.UIEvent<HTMLDivElement>) => {
|
72
|
+
const { scrollTop, clientHeight, scrollHeight } = e.currentTarget;
|
73
|
+
const isNearBottom = scrollHeight - scrollTop - clientHeight < 30;
|
74
|
+
|
75
|
+
if (isNearBottom && !loadingMore && list.length <= total.current) {
|
76
|
+
await loadData(true);
|
77
|
+
setPagination((pre) => ({ ...pre, pageNum: pagination.pageNum + 1 }));
|
78
|
+
}
|
79
|
+
};
|
80
|
+
|
81
|
+
const loadData = useCallback(debounceLoadData, [search, pagination, loadOptions]);
|
82
|
+
|
83
|
+
// 处理滚动加载
|
84
|
+
const handleScroll = useCallback(debounceHandleScroll, [loadingMore, loadData]);
|
85
|
+
|
86
|
+
// 重置list滚动条
|
87
|
+
const resetListScroll = () => {
|
88
|
+
const listNode = document.getElementById("abt-list");
|
89
|
+
listNode && listNode.scrollTo({ top: 0, behavior: "auto" });
|
90
|
+
};
|
91
|
+
|
92
|
+
// 处理搜索
|
93
|
+
const handleSearch = debounce((value: string) => {
|
94
|
+
resetListScroll();
|
95
|
+
setSearch(value);
|
96
|
+
setPagination((prev) => ({ ...prev, pageNum: 1 }));
|
97
|
+
}, 1000);
|
98
|
+
|
99
|
+
useEffect(() => {
|
100
|
+
loadData();
|
101
|
+
}, [search]);
|
102
|
+
|
103
|
+
// 处理选项点击事件
|
104
|
+
const handleItemClick = (item: Person) => {
|
105
|
+
const { value: currentValue, mode, onChange } = selectProps;
|
106
|
+
const itemId = item.userId;
|
107
|
+
|
108
|
+
// 多选模式处理
|
109
|
+
const handleMultipleSelection = () => {
|
110
|
+
const selectedOptions = Array.isArray(currentValue) ? currentValue : [];
|
111
|
+
const isSelected = selectedOptions.some((opt) => opt.value === itemId);
|
112
|
+
|
113
|
+
return isSelected
|
114
|
+
? selectedOptions.filter((opt) => opt.value !== itemId) // 移除已选项
|
115
|
+
: [...selectedOptions, item]; // 添加新选项
|
116
|
+
};
|
117
|
+
|
118
|
+
// 单选模式处理
|
119
|
+
const handleSingleSelection = () => {
|
120
|
+
const isCurrentSelected = (currentValue as LableValue)?.value === itemId;
|
121
|
+
return isCurrentSelected ? undefined : item;
|
122
|
+
};
|
123
|
+
|
124
|
+
// 执行模式对应处理
|
125
|
+
const newValue = mode === "multiple" ? handleMultipleSelection() : handleSingleSelection();
|
126
|
+
|
127
|
+
// 触发变更回调
|
128
|
+
onChange?.(newValue, currentValue);
|
129
|
+
|
130
|
+
// 单选模式自动关闭下拉框
|
131
|
+
if (mode !== "multiple" && selectInstance.current) selectInstance.current.blur();
|
132
|
+
};
|
133
|
+
|
134
|
+
// 合并配置
|
135
|
+
const itemConfigs = useMemo(() => {
|
136
|
+
if (Array.isArray(listItemConfigs) && listItemConfigs?.length) return listItemConfigs;
|
137
|
+
return defaultListItemConfigs;
|
138
|
+
}, [listItemConfigs]);
|
139
|
+
|
140
|
+
// 默认渲染项(增加选中状态和点击处理)
|
141
|
+
const defaultRenderItem = useCallback(
|
142
|
+
(item: Person) => {
|
143
|
+
const { mode, value: selectedValue } = selectProps;
|
144
|
+
const { userId, [disabledKey]: isDisabled = false } = item;
|
145
|
+
// 选中状态判断
|
146
|
+
const getSelectionStatus = () => {
|
147
|
+
if (mode === "multiple") {
|
148
|
+
if (!Array.isArray(selectedValue)) return false;
|
149
|
+
const selectedValues = (selectedValue || [])?.map((opt) => (isObject(opt) ? opt?.value ?? opt?.userId : opt));
|
150
|
+
return selectedValues.includes(userId);
|
151
|
+
}
|
152
|
+
return isObject(selectedValue) ? (selectedValue as LableValue)?.value === userId : false;
|
153
|
+
};
|
154
|
+
|
155
|
+
// 禁用样式生成
|
156
|
+
const generateStyle = (disabled: boolean): React.CSSProperties => {
|
157
|
+
if (typeof disabledStyle === "function") {
|
158
|
+
return disabledStyle(disabled);
|
159
|
+
}
|
160
|
+
|
161
|
+
return {
|
162
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
163
|
+
backgroundColor: disabled ? "#f0f0f0" : getSelectionStatus() ? "#e6f7ff" : "inherit",
|
164
|
+
color: disabled ? "#999" : "inherit",
|
165
|
+
filter: disabled ? "grayscale(100%)" : "none",
|
166
|
+
};
|
167
|
+
};
|
168
|
+
|
169
|
+
// 头像组件
|
170
|
+
const avatarComponent = <Avatar alt={item.userName} size={60} {...avataProps} src={item.avatar} />;
|
171
|
+
|
172
|
+
// 用户信息列表
|
173
|
+
const userInfoContent = (
|
174
|
+
<ul className="abt-user-item-info">
|
175
|
+
{itemConfigs.map((configItem, index) => (
|
176
|
+
<li key={configItem?.key || index} className="abt-user-item-info__item">
|
177
|
+
<span>{configItem?.label}</span>
|
178
|
+
<span>{item?.[configItem.key]}</span>
|
179
|
+
</li>
|
180
|
+
))}
|
181
|
+
</ul>
|
182
|
+
);
|
183
|
+
|
184
|
+
return (
|
185
|
+
<List.Item key={userId} onClick={() => !isDisabled && handleItemClick(item)} style={generateStyle(isDisabled)}>
|
186
|
+
{customItemNode || (
|
187
|
+
<div className="abt-user-item">
|
188
|
+
<div className="abt-user-item-avatar">{avatarComponent}</div>
|
189
|
+
{userInfoContent}
|
190
|
+
</div>
|
191
|
+
)}
|
192
|
+
</List.Item>
|
193
|
+
);
|
194
|
+
},
|
195
|
+
[selectProps.value],
|
196
|
+
);
|
197
|
+
|
198
|
+
// 自定义下拉内容
|
199
|
+
const dropdownRender = () => (
|
200
|
+
<div>
|
201
|
+
<Spin spinning={loading}>
|
202
|
+
<List
|
203
|
+
id={"abt-list"}
|
204
|
+
loading={loadingMore}
|
205
|
+
dataSource={list}
|
206
|
+
renderItem={renderItem ? renderItem : defaultRenderItem}
|
207
|
+
/*@ts-ignore*/
|
208
|
+
onScroll={handleScroll}
|
209
|
+
style={{ maxHeight: 250, overflowY: "auto" }}
|
210
|
+
/>
|
211
|
+
</Spin>
|
212
|
+
</div>
|
213
|
+
);
|
214
|
+
|
215
|
+
return (
|
216
|
+
<Select
|
217
|
+
{...selectProps}
|
218
|
+
ref={selectInstance}
|
219
|
+
showSearch
|
220
|
+
labelInValue
|
221
|
+
onSearch={handleSearch}
|
222
|
+
filterOption={false}
|
223
|
+
dropdownRender={dropdownRender}
|
224
|
+
placeholder="请选择人员"
|
225
|
+
options={list.map((item) => ({
|
226
|
+
value: item.userId,
|
227
|
+
label: item.userName,
|
228
|
+
...item,
|
229
|
+
}))}
|
230
|
+
/>
|
231
|
+
);
|
232
|
+
};
|
233
|
+
|
234
|
+
const PersonnelSelect = connect(
|
235
|
+
RemoteSelect,
|
236
|
+
mapProps(
|
237
|
+
{
|
238
|
+
loading: true,
|
239
|
+
},
|
240
|
+
(props) => {
|
241
|
+
return {
|
242
|
+
...props,
|
243
|
+
};
|
244
|
+
},
|
245
|
+
),
|
246
|
+
);
|
247
|
+
|
248
|
+
export { RemoteSelect, PersonnelSelect };
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import type { SelectProps } from "antd/lib/select";
|
2
|
+
import React from "react";
|
3
|
+
import type { AvatarProps } from "antd/lib/avatar";
|
4
|
+
|
5
|
+
export interface Person {
|
6
|
+
userId: string | number;
|
7
|
+
userName: string;
|
8
|
+
avatar?: string;
|
9
|
+
email?: string;
|
10
|
+
description?: string;
|
11
|
+
disabled?: boolean;
|
12
|
+
[key: string]: any;
|
13
|
+
}
|
14
|
+
|
15
|
+
export interface ScrollPagination {
|
16
|
+
pageNum: number;
|
17
|
+
pageSize: number;
|
18
|
+
current?: number;
|
19
|
+
total?: number;
|
20
|
+
}
|
21
|
+
|
22
|
+
export type LableValue = {
|
23
|
+
label: string;
|
24
|
+
value: string | number;
|
25
|
+
[key: string]: any;
|
26
|
+
};
|
27
|
+
|
28
|
+
export interface RemoteSelectProps extends Omit<SelectProps<any>, "options" | "children"> {
|
29
|
+
/**
|
30
|
+
* @title 请求函数
|
31
|
+
* */
|
32
|
+
loadOptions: (
|
33
|
+
search: string,
|
34
|
+
pagination: ScrollPagination,
|
35
|
+
) => Promise<{ list: Person[]; pagination: ScrollPagination }>;
|
36
|
+
/**
|
37
|
+
* @title 自定义列表项
|
38
|
+
* */
|
39
|
+
renderItem?: (item: Person) => React.ReactNode;
|
40
|
+
/**
|
41
|
+
* @title 分页参数
|
42
|
+
* */
|
43
|
+
initialPagination?: Partial<ScrollPagination>;
|
44
|
+
/**
|
45
|
+
* @title antd头像组件props
|
46
|
+
* */
|
47
|
+
avataProps?: AvatarProps;
|
48
|
+
/**
|
49
|
+
* @title 下拉框值
|
50
|
+
* */
|
51
|
+
value: LableValue[] | LableValue;
|
52
|
+
/**
|
53
|
+
* @title 列表项内部展示的字段内容
|
54
|
+
* */
|
55
|
+
listItemConfigs?: { label: string; key: string }[];
|
56
|
+
/**
|
57
|
+
* @title 动态disabledKey
|
58
|
+
* */
|
59
|
+
disabledKey?: string;
|
60
|
+
/**
|
61
|
+
* @title 动态labelKey
|
62
|
+
* */
|
63
|
+
labelKey?: string;
|
64
|
+
/**
|
65
|
+
* @title 动态valueKey
|
66
|
+
* */
|
67
|
+
valueKey?: string;
|
68
|
+
/**
|
69
|
+
* @title disabledStyle
|
70
|
+
* */
|
71
|
+
disabledStyle?: (disabled: boolean) => React.CSSProperties;
|
72
|
+
/**
|
73
|
+
* @title 自定义列表项中的样式
|
74
|
+
* */
|
75
|
+
customItemNode?: React.ReactNode;
|
76
|
+
}
|
@@ -5,8 +5,8 @@ import { IDomEditor, IEditorConfig, IToolbarConfig } from "@wangeditor/editor";
|
|
5
5
|
import { getOssUploadRequest, getOfflineUploadRequest } from "../Upload/common/customRequest";
|
6
6
|
import { handlePreviewUrls } from "../Upload/common/OfflineUpload";
|
7
7
|
import "./index.less";
|
8
|
-
import { Spin, message } from
|
9
|
-
import { connect, mapProps, observer } from "@formily/react";
|
8
|
+
import { Spin, message } from "antd";
|
9
|
+
import { connect, mapProps, observer, useField } from "@formily/react";
|
10
10
|
|
11
11
|
interface AnyObject {
|
12
12
|
[key: string]: any;
|
@@ -15,18 +15,18 @@ interface AnyObject {
|
|
15
15
|
interface PropsType {
|
16
16
|
toolbarConfig?: Partial<IToolbarConfig>;
|
17
17
|
editorConfig?: Partial<IEditorConfig>;
|
18
|
-
ossOpt: AnyObject
|
19
|
-
uploadParams: AnyObject
|
20
|
-
height: string
|
21
|
-
ossServerUrl: string
|
22
|
-
ossUrl: string
|
23
|
-
offlinePreviewUrl: string
|
24
|
-
offlineServerUrl: string
|
25
|
-
value: string
|
26
|
-
onChange: any
|
27
|
-
customRequest: any
|
28
|
-
uploadMode: string
|
29
|
-
zIndex: number
|
18
|
+
ossOpt: AnyObject;
|
19
|
+
uploadParams: AnyObject;
|
20
|
+
height: string;
|
21
|
+
ossServerUrl: string;
|
22
|
+
ossUrl: string;
|
23
|
+
offlinePreviewUrl: string;
|
24
|
+
offlineServerUrl: string;
|
25
|
+
value: string;
|
26
|
+
onChange: any;
|
27
|
+
customRequest: any;
|
28
|
+
uploadMode: string;
|
29
|
+
zIndex: number;
|
30
30
|
}
|
31
31
|
|
32
32
|
export const RichEditor = observer(function (props: PropsType, parentRef) {
|
@@ -44,7 +44,7 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
44
44
|
// ossUrl = "https://test-abt.hzabjt.com:18091/api/v1/user/oss/getWebOssConfig",
|
45
45
|
ossServerUrl = "/api/v1/user/oss/getWebOssConfig",
|
46
46
|
ossUrl = "/api/v1/user/oss/getWebOssConfig",
|
47
|
-
zIndex = 9999
|
47
|
+
zIndex = 9999,
|
48
48
|
} = props;
|
49
49
|
const isPrivateStore =
|
50
50
|
props.uploadParams?.fileAcl === "private" || props.ossOpt?.signatureParams?.fileAcl === "private";
|
@@ -54,15 +54,16 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
54
54
|
getEditorContent,
|
55
55
|
}));
|
56
56
|
|
57
|
+
const field = useField<any>();
|
58
|
+
|
57
59
|
// editor 实例
|
58
60
|
const [editor, setEditor] = useState<IDomEditor | null>(null); // TS 语法
|
59
61
|
|
60
62
|
// 编辑器内容
|
61
|
-
const [html, setHtml] = useState<string>(
|
63
|
+
const [html, setHtml] = useState<string>("");
|
62
64
|
|
63
65
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
64
66
|
|
65
|
-
|
66
67
|
const setEditorContent = (content) => {
|
67
68
|
setHtml(content);
|
68
69
|
};
|
@@ -71,7 +72,6 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
71
72
|
return html;
|
72
73
|
};
|
73
74
|
|
74
|
-
|
75
75
|
const customUpload = async (file: File, insertFn) => {
|
76
76
|
let customRequest = props.customRequest;
|
77
77
|
if (uploadMode === "oss") {
|
@@ -95,15 +95,15 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
95
95
|
file,
|
96
96
|
onSuccess: (res) => {
|
97
97
|
setIsLoading(false);
|
98
|
-
console.log(res)
|
99
|
-
insertFn(res.url,
|
98
|
+
console.log(res);
|
99
|
+
insertFn(res.url, "", "");
|
100
100
|
},
|
101
101
|
onError: () => {
|
102
102
|
setIsLoading(false);
|
103
103
|
message.error(`${file.name} 上传失败`);
|
104
|
-
}
|
105
|
-
})
|
106
|
-
}
|
104
|
+
},
|
105
|
+
});
|
106
|
+
};
|
107
107
|
|
108
108
|
const config: Partial<IEditorConfig> = {
|
109
109
|
// TS 语法
|
@@ -111,13 +111,13 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
111
111
|
MENU_CONF: {
|
112
112
|
uploadImage: {
|
113
113
|
// form-data fieldName ,默认值 'wangeditor-uploaded-image'
|
114
|
-
fieldName:
|
114
|
+
fieldName: "wangeditor-uploaded-image",
|
115
115
|
// 单个文件的最大体积限制,默认为 2M
|
116
116
|
maxFileSize: 1 * 1024 * 1024, // 1M
|
117
117
|
// 最多可上传几个文件,默认为 100
|
118
118
|
maxNumberOfFiles: 1,
|
119
119
|
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
|
120
|
-
allowedFileTypes: [
|
120
|
+
allowedFileTypes: ["image/*"],
|
121
121
|
// 跨域是否传递 cookie ,默认为 false
|
122
122
|
withCredentials: false,
|
123
123
|
// 超时时间,默认为 10 秒
|
@@ -126,11 +126,11 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
126
126
|
},
|
127
127
|
uploadVideo: {
|
128
128
|
// form-data fieldName ,默认值 'wangeditor-uploaded-image'
|
129
|
-
fieldName:
|
129
|
+
fieldName: "wangeditor-uploaded-video",
|
130
130
|
// 最多可上传几个文件,默认为 100
|
131
131
|
maxNumberOfFiles: 1,
|
132
132
|
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
|
133
|
-
allowedFileTypes: [
|
133
|
+
allowedFileTypes: ["video/*"],
|
134
134
|
// 跨域是否传递 cookie ,默认为 false
|
135
135
|
withCredentials: false,
|
136
136
|
customUpload,
|
@@ -139,12 +139,11 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
139
139
|
...editorConfig,
|
140
140
|
};
|
141
141
|
|
142
|
-
|
143
142
|
const onEditorChange = (e) => {
|
144
143
|
const content = e.getHtml();
|
145
144
|
setHtml(content);
|
146
145
|
onChange && onChange(content);
|
147
|
-
}
|
146
|
+
};
|
148
147
|
|
149
148
|
// 及时销毁 editor ,重要!
|
150
149
|
useEffect(() => {
|
@@ -156,15 +155,17 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
156
155
|
}, [editor]);
|
157
156
|
|
158
157
|
useEffect(() => {
|
159
|
-
setEditorContent(value)
|
158
|
+
setEditorContent(value);
|
160
159
|
}, [value]);
|
161
160
|
|
162
161
|
return (
|
163
162
|
<>
|
164
163
|
<div className="wang-editor-warpper" style={{ zIndex }}>
|
165
|
-
{isLoading &&
|
166
|
-
<
|
167
|
-
|
164
|
+
{isLoading && (
|
165
|
+
<div className="spin-content">
|
166
|
+
<Spin tip="Loading..." />
|
167
|
+
</div>
|
168
|
+
)}
|
168
169
|
<Toolbar
|
169
170
|
editor={editor}
|
170
171
|
defaultConfig={toolbarConfig}
|
@@ -174,7 +175,10 @@ export const RichEditor = observer(function (props: PropsType, parentRef) {
|
|
174
175
|
<WangEditor
|
175
176
|
defaultConfig={config}
|
176
177
|
value={html}
|
177
|
-
onCreated={
|
178
|
+
onCreated={(e) => {
|
179
|
+
setEditor(e);
|
180
|
+
field.editor = e;
|
181
|
+
}}
|
178
182
|
onChange={(e) => onEditorChange(e)}
|
179
183
|
mode="default"
|
180
184
|
style={{ height }}
|
@@ -204,4 +208,3 @@ export default connect(
|
|
204
208
|
);
|
205
209
|
|
206
210
|
export const Editor = WangEditor;
|
207
|
-
|