@cqsjjb/jjb-react-admin-component 3.1.3 → 3.2.0-beta.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.
@@ -1,268 +1,93 @@
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); }
1
2
  import React from 'react';
2
3
  import PropTypes from 'prop-types';
3
- import { Cascader as OriginCascader, Col, DatePicker as OriginDatePicker, Input as OriginInput, Row, Select as OriginSelect, TreeSelect as OriginTreeSelect } from 'antd';
4
+ import { Row, Col, Input as OriginInput, Select as OriginSelect, Cascader as OriginCascader, DatePicker as OriginDatePicker, TreeSelect as OriginTreeSelect } from 'antd';
5
+ import { tools } from '@cqsjjb/jjb-common-lib';
6
+ import { getAlgorithm, getPrefixCls } from '../tools/index.js';
4
7
  import './index.less';
5
- const prefixCls = process?.env?.app?.antd['ant-prefix'] || 'ant';
8
+ const algorithm = getAlgorithm();
9
+ const prefixCls = getPrefixCls();
6
10
 
7
11
  /**
8
- * @param classList {string[]}
9
- * @return {string}
12
+ * 通用包裹组件
13
+ * @param {*} WrappedComponent antd 原生组件
14
+ * @param {*} extraProps 额外的透传配置
10
15
  */
11
- function classnames(classList) {
12
- return classList.filter(i => i).join(' ');
13
- }
14
- function algorithm() {
15
- const value = document.documentElement.style.getPropertyValue('--saas-algorithm');
16
- return value ? value === '#FFF' ? 'default' : 'dark' : 'default';
17
- }
18
- class Input extends React.Component {
19
- get algorithm() {
20
- return algorithm();
21
- }
22
- render() {
23
- return /*#__PURE__*/React.createElement("div", {
24
- style: this.props.style,
25
- className: classnames([`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${this.algorithm}`, this.props.label && `${prefixCls}-form-item-control-label-has`])
26
- }, /*#__PURE__*/React.createElement(Row, {
27
- align: "middle",
28
- wrap: false
29
- }, this.props.label && /*#__PURE__*/React.createElement(Col, {
30
- span: this.props.labelSpan,
31
- className: `${prefixCls}-form-item-control-label-span`
32
- }, this.props.label), /*#__PURE__*/React.createElement(Col, {
33
- flex: 1
34
- }, /*#__PURE__*/React.createElement(OriginInput, {
35
- style: {
36
- width: '100%'
37
- },
38
- value: this.props.value,
39
- maxLength: this.props.maxLength,
40
- allowClear: this.props.allowClear,
41
- placeholder: this.props.placeholder,
42
- onChange: e => this.props.onChange(e.target.value || undefined)
43
- }))));
44
- }
45
- }
46
- Input.propTypes = {
47
- value: PropTypes.any,
48
- label: PropTypes.string,
49
- labelSpan: PropTypes.number,
50
- maxLength: PropTypes.number,
51
- allowClear: PropTypes.bool,
52
- placeholder: PropTypes.string,
53
- onChange: PropTypes.func
54
- };
55
- class Select extends React.Component {
56
- get algorithm() {
57
- return algorithm();
58
- }
59
- render() {
60
- return /*#__PURE__*/React.createElement("div", {
61
- style: this.props.style,
62
- className: classnames([`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${this.algorithm}`, this.props.label && `${prefixCls}-form-item-control-label-has`])
63
- }, /*#__PURE__*/React.createElement(Row, {
64
- align: "middle",
65
- wrap: false
66
- }, this.props.label && /*#__PURE__*/React.createElement(Col, {
67
- span: this.props.labelSpan,
68
- className: `${prefixCls}-form-item-control-label-span`
69
- }, this.props.label), /*#__PURE__*/React.createElement(Col, {
70
- flex: 1
71
- }, /*#__PURE__*/React.createElement(OriginSelect, {
72
- mode: this.props.mode,
73
- style: {
74
- width: '100%'
75
- },
76
- value: this.props.value,
77
- allowClear: this.props.allowClear,
78
- maxTagCount: this.props.maxTagCount || 3,
79
- placeholder: this.props.placeholder,
80
- onChange: value => this.props.onChange(value || undefined)
81
- }, this.props.options ? this.props.options : this.props.children))));
82
- }
83
- }
84
- Select.propTypes = {
85
- mode: PropTypes.string,
86
- value: PropTypes.any,
87
- label: PropTypes.string,
88
- labelSpan: PropTypes.number,
89
- allowClear: PropTypes.bool,
90
- maxTagCount: PropTypes.number,
91
- placeholder: PropTypes.string,
92
- onChange: PropTypes.func
93
- };
94
- class Cascader extends React.Component {
95
- get algorithm() {
96
- return algorithm();
97
- }
98
- render() {
99
- return /*#__PURE__*/React.createElement("div", {
100
- style: this.props.style,
101
- className: classnames([`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${this.algorithm}`, this.props.label && `${prefixCls}-form-item-control-label-has`])
102
- }, /*#__PURE__*/React.createElement(Row, {
103
- align: "middle",
104
- wrap: false
105
- }, this.props.label && /*#__PURE__*/React.createElement(Col, {
106
- span: this.props.labelSpan,
107
- className: `${prefixCls}-form-item-control-label-span`
108
- }, this.props.label), /*#__PURE__*/React.createElement(Col, {
109
- flex: 1
110
- }, /*#__PURE__*/React.createElement(OriginCascader, {
111
- style: {
112
- width: '100%'
113
- },
114
- value: this.props.value,
115
- options: this.props.options,
116
- multiple: this.props.multiple,
117
- allowClear: this.props.allowClear,
118
- fieldNames: this.props.fieldNames,
119
- maxTagCount: this.props.maxTagCount || 3,
120
- placeholder: this.props.placeholder,
121
- showCheckedStrategy: this.props.showCheckedStrategy,
122
- onChange: value => this.props.onChange(value || undefined)
123
- }))));
124
- }
125
- }
126
- Cascader.propTypes = {
127
- mode: PropTypes.string,
128
- value: PropTypes.any,
129
- label: PropTypes.string,
130
- options: PropTypes.array,
131
- multiple: PropTypes.bool,
132
- labelSpan: PropTypes.number,
133
- allowClear: PropTypes.bool,
134
- fieldNames: PropTypes.object,
135
- maxTagCount: PropTypes.number,
136
- placeholder: PropTypes.string,
137
- showCheckedStrategy: PropTypes.string,
138
- onChange: PropTypes.func
139
- };
140
- class RangePicker extends React.Component {
141
- get algorithm() {
142
- return algorithm();
143
- }
144
- render() {
145
- return /*#__PURE__*/React.createElement("div", {
146
- style: this.props.style,
147
- className: classnames([`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${this.algorithm}`, this.props.label && `${prefixCls}-form-item-control-label-has`])
148
- }, /*#__PURE__*/React.createElement(Row, {
149
- align: "middle",
150
- wrap: false
151
- }, this.props.label && /*#__PURE__*/React.createElement(Col, {
152
- span: this.props.labelSpan,
153
- className: `${prefixCls}-form-item-control-label-span`
154
- }, this.props.label), /*#__PURE__*/React.createElement(Col, {
155
- style: {
156
- flex: 1
16
+ function withLabel(WrappedComponent, extraProps = {}) {
17
+ return function Wrapper(props) {
18
+ const {
19
+ label,
20
+ labelSpan,
21
+ style,
22
+ onChange,
23
+ ...restProps
24
+ } = props;
25
+ const handleChange = (val, ...args) => {
26
+ if (onChange) {
27
+ // 统一处理空字符串 -> undefined
28
+ onChange(val === '' ? undefined : val, ...args);
157
29
  }
158
- }, /*#__PURE__*/React.createElement(OriginDatePicker.RangePicker, {
159
- style: {
160
- width: '100%'
161
- },
162
- value: this.props.value,
163
- format: this.props.format,
164
- showTime: this.props.showTime || undefined,
165
- placeholder: this.props.placeholder,
166
- onChange: value => this.props.onChange(value || undefined)
167
- }))));
168
- }
169
- }
170
- RangePicker.propTypes = {
171
- value: PropTypes.any,
172
- label: PropTypes.string,
173
- format: PropTypes.string,
174
- labelSpan: PropTypes.number,
175
- placeholder: PropTypes.string,
176
- onChange: PropTypes.func
177
- };
178
- class DatePicker extends React.Component {
179
- static RangePicker = RangePicker;
180
- get algorithm() {
181
- return algorithm();
182
- }
183
- render() {
30
+ };
184
31
  return /*#__PURE__*/React.createElement("div", {
185
- style: this.props.style,
186
- className: classnames([`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${this.algorithm}`, this.props.label && `${prefixCls}-form-item-control-label-has`])
32
+ style: style,
33
+ className: tools.classNames(`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${algorithm}`, label && `${prefixCls}-form-item-control-label-has`)
187
34
  }, /*#__PURE__*/React.createElement(Row, {
188
- align: "middle",
189
- wrap: false
190
- }, this.props.label && /*#__PURE__*/React.createElement(Col, {
191
- span: this.props.labelSpan,
35
+ align: "middle"
36
+ }, label && /*#__PURE__*/React.createElement(Col, {
37
+ span: labelSpan,
192
38
  className: `${prefixCls}-form-item-control-label-span`
193
- }, this.props.label), /*#__PURE__*/React.createElement(Col, {
39
+ }, label), /*#__PURE__*/React.createElement(Col, {
194
40
  flex: 1
195
- }, /*#__PURE__*/React.createElement(OriginDatePicker, {
41
+ }, /*#__PURE__*/React.createElement(WrappedComponent, _extends({
196
42
  style: {
197
43
  width: '100%'
198
- },
199
- value: this.props.value,
200
- format: this.props.format,
201
- picker: this.props.picker || undefined,
202
- showTime: this.props.showTime || undefined,
203
- placeholder: this.props.placeholder,
204
- onChange: value => this.props.onChange(value || undefined)
205
- }))));
206
- }
207
- }
208
- DatePicker.propTypes = {
209
- value: PropTypes.any,
210
- label: PropTypes.string,
211
- format: PropTypes.string,
212
- labelSpan: PropTypes.number,
213
- placeholder: PropTypes.string,
214
- onChange: PropTypes.func
215
- };
216
- class TreeSelect extends React.Component {
217
- get algorithm() {
218
- return algorithm();
219
- }
220
- render() {
221
- return /*#__PURE__*/React.createElement("div", {
222
- style: this.props.style,
223
- className: classnames([`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${this.algorithm}`, this.props.label && `${prefixCls}-form-item-control-label-has`])
224
- }, /*#__PURE__*/React.createElement(Row, {
225
- align: "middle",
226
- wrap: false
227
- }, this.props.label && /*#__PURE__*/React.createElement(Col, {
228
- span: this.props.labelSpan,
229
- className: `${prefixCls}-form-item-control-label-span`
230
- }, this.props.label), /*#__PURE__*/React.createElement(Col, {
231
- flex: 1
232
- }, /*#__PURE__*/React.createElement(OriginTreeSelect, {
233
- showSearch: true,
234
- allowClear: true,
235
- style: {
236
- width: '100%'
237
- },
238
- treeData: this.props.treeData,
239
- fieldNames: this.props.fieldNames,
240
- dropdownStyle: this.props.dropdownStyle,
241
- maxTagCount: 2,
242
- value: this.props.value,
243
- placeholder: this.props.placeholder ? this.props.placeholder : '请选择分类',
244
- defaultValue: this.props.defaultValue ? this.props.defaultValue : undefined,
245
- treeCheckable: this.props.checkbox,
246
- treeNodeFilterProp: "title",
247
- showCheckedStrategy: OriginTreeSelect.SHOW_PARENT,
248
- onChange: value => this.props.onChange(value)
249
- }))));
250
- }
44
+ }
45
+ }, extraProps, restProps, {
46
+ onChange: handleChange
47
+ })))));
48
+ };
251
49
  }
252
- TreeSelect.propTypes = {
50
+
51
+ // ============ 输入类 ============
52
+ const Input = withLabel(OriginInput);
53
+
54
+ // ============ 选择类 ============
55
+ const Select = withLabel(OriginSelect, {
56
+ maxTagCount: 3
57
+ });
58
+ const Cascader = withLabel(OriginCascader, {
59
+ maxTagCount: 3
60
+ });
61
+ const TreeSelect = withLabel(OriginTreeSelect, {
62
+ showSearch: true,
63
+ allowClear: true,
64
+ showCheckedStrategy: OriginTreeSelect.SHOW_PARENT,
65
+ treeNodeFilterProp: 'title'
66
+ });
67
+
68
+ // ============ 日期类 ============
69
+ const DatePicker = withLabel(OriginDatePicker);
70
+ DatePicker.RangePicker = withLabel(OriginDatePicker.RangePicker);
71
+ DatePicker.TimePicker = withLabel(OriginDatePicker.TimePicker);
72
+ DatePicker.WeekPicker = withLabel(OriginDatePicker.WeekPicker);
73
+ DatePicker.MonthPicker = withLabel(OriginDatePicker.MonthPicker);
74
+ DatePicker.QuarterPicker = withLabel(OriginDatePicker.QuarterPicker);
75
+ DatePicker.YearPicker = withLabel(OriginDatePicker.YearPicker);
76
+
77
+ // ============ PropTypes 公共扩展 ============
78
+ const commonPropTypes = {
253
79
  label: PropTypes.string,
254
- checkbox: PropTypes.bool,
255
- treeData: PropTypes.array,
256
80
  labelSpan: PropTypes.number,
257
- fieldNames: PropTypes.object,
258
- defaultValue: PropTypes.array,
259
- dropdownStyle: PropTypes.object,
81
+ style: PropTypes.object,
260
82
  onChange: PropTypes.func
261
83
  };
84
+ [Input, Select, Cascader, TreeSelect, DatePicker, DatePicker.RangePicker, DatePicker.TimePicker, DatePicker.WeekPicker, DatePicker.MonthPicker, DatePicker.QuarterPicker, DatePicker.YearPicker].forEach(cmp => {
85
+ cmp.propTypes = commonPropTypes;
86
+ });
262
87
  export default {
263
88
  Input,
264
89
  Select,
265
90
  Cascader,
266
- DatePicker,
267
- TreeSelect
91
+ TreeSelect,
92
+ DatePicker
268
93
  };
@@ -0,0 +1,330 @@
1
+ import PropTypes from 'prop-types';
2
+ import '@wangeditor/editor/dist/css/style.css';
3
+ import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
4
+ import { message } from 'antd';
5
+ import { http, tools } from '@cqsjjb/jjb-common-lib';
6
+ import { createEditor, createToolbar } from '@wangeditor/editor';
7
+
8
+ /**
9
+ * 工具函数:标准化 HTML 内容为空值
10
+ */
11
+ function normalizeEmptyHtml(html) {
12
+ if (!html) return '';
13
+ const cleaned = html.replace(/\s+/g, '').toLowerCase();
14
+ return cleaned === '<p><br></p>' || cleaned === '<p></p>' || cleaned === '' ? '' : html;
15
+ }
16
+
17
+ /**
18
+ * 校验上传文件类型和大小
19
+ */
20
+ function validateFile(file, {
21
+ validTypes,
22
+ validExts,
23
+ maxSize,
24
+ typeName
25
+ }) {
26
+ const ext = file.name.split('.').pop().toLowerCase();
27
+ if (!validTypes.includes(file.type) && !validExts.includes(ext)) {
28
+ message.error(`只能上传${typeName}文件(${validExts.join(', ')})`);
29
+ return false;
30
+ }
31
+ if (file.size > maxSize) {
32
+ message.error(`${typeName}大小不能超过 ${maxSize / 1024 / 1024}MB`);
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * 通用上传函数
40
+ */
41
+ async function handleUpload(file, insertFn, options) {
42
+ const {
43
+ typeName,
44
+ validTypes,
45
+ validExts,
46
+ maxSize,
47
+ uploadServer,
48
+ uploadFileName,
49
+ uploadHeaders,
50
+ application,
51
+ uploadCustomRequest
52
+ } = options;
53
+ try {
54
+ if (!validateFile(file, {
55
+ validTypes,
56
+ validExts,
57
+ maxSize,
58
+ typeName
59
+ })) {
60
+ return;
61
+ }
62
+
63
+ // 自定义上传
64
+ if (typeof uploadCustomRequest === 'function') {
65
+ const url = await uploadCustomRequest(file);
66
+ insertFn(url, file.name);
67
+ return;
68
+ }
69
+
70
+ // 默认上传
71
+ const formData = new FormData();
72
+ formData.append(uploadFileName, file);
73
+ const resp = await fetch(uploadServer, {
74
+ method: 'POST',
75
+ headers: {
76
+ ...uploadHeaders,
77
+ ...(application ? {
78
+ tenantCode: application.tenantCode
79
+ } : {})
80
+ },
81
+ body: formData
82
+ });
83
+ const res = await resp.json();
84
+ if (res?.data?.fileUrl) {
85
+ insertFn(res.data.fileUrl, file.name);
86
+ } else if (Array.isArray(res?.data)) {
87
+ res.data.forEach(url => insertFn(url, file.name));
88
+ } else {
89
+ message.error(res?.respDesc || `${typeName}上传失败`);
90
+ }
91
+ } catch (e) {
92
+ console.error(e);
93
+ message.error(`${typeName}上传失败`);
94
+ }
95
+ }
96
+ const RCEditor = /*#__PURE__*/forwardRef(function RCEditor(props, ref) {
97
+ const {
98
+ value,
99
+ bordered = true,
100
+ mode = 'default',
101
+ height = 300,
102
+ disabled,
103
+ uploadUrl = `${window.process.env.app.API_HOST}/attachment/files/upload-speed-simple`,
104
+ uploadFileName = 'file',
105
+ uploadHeaders = {},
106
+ uploadCustomRequest,
107
+ placeholder = '请输入...',
108
+ uploadImageFileSize = 5 * 1024 * 1024,
109
+ uploadVideoFileSize = 50 * 1024 * 1024,
110
+ borderStyle = 'solid',
111
+ borderColor = '#e8e8e8',
112
+ borderWidth = 1,
113
+ borderRadius = 2,
114
+ onBlur,
115
+ onFocus,
116
+ onChange
117
+ } = props;
118
+
119
+ // 编辑器
120
+ const editorRef = useRef(null);
121
+ // 工具栏
122
+ const toolbarRef = useRef(null);
123
+ // 编辑器实例
124
+ const editorInstanceRef = useRef(null);
125
+ // 最后一次的html
126
+ const lastHtmlRef = useRef('');
127
+ // 是否是第一次聚焦
128
+ const isFirstFocusRef = useRef(true);
129
+ // 应用配置
130
+ const appConfig = tools.toObject(window.process?.env?.app);
131
+
132
+ // 上传地址
133
+ const uploadServer = useMemo(() => {
134
+ if (!appConfig) return uploadUrl;
135
+ return http.HTTP_URL_REG.test(uploadUrl) ? uploadUrl : `${appConfig.API_HOST}${uploadUrl}`;
136
+ }, [appConfig, uploadUrl]);
137
+
138
+ // 初始化编辑器
139
+ useEffect(() => {
140
+ if (!editorRef.current) return;
141
+ const editor = createEditor({
142
+ selector: editorRef.current,
143
+ config: {
144
+ placeholder,
145
+ readOnly: disabled,
146
+ onChange(editor) {
147
+ // 获取html
148
+ let html = editor.getHtml();
149
+ // 标准化html
150
+ html = normalizeEmptyHtml(html);
151
+ // 存储最后一次的html
152
+ lastHtmlRef.current = html;
153
+
154
+ // 如果不是第一次聚焦,并且有onChange函数,则调用onChange函数
155
+ if (!isFirstFocusRef.current && tools.isFunction(onChange)) {
156
+ onChange(html);
157
+ }
158
+ },
159
+ onFocus: () => {
160
+ // 如果是第一次聚焦,则设置为不是第一次聚焦
161
+ if (isFirstFocusRef.current) {
162
+ isFirstFocusRef.current = false;
163
+ return;
164
+ }
165
+ // 调用onFocus函数
166
+ tools.isFunction(onFocus) && onFocus();
167
+ },
168
+ onBlur: () => tools.isFunction(onBlur) && onBlur(),
169
+ // 菜单配置
170
+ MENU_CONF: {
171
+ uploadImage: {
172
+ server: uploadServer,
173
+ fieldName: uploadFileName,
174
+ maxFileSize: uploadImageFileSize,
175
+ headers: {
176
+ ...uploadHeaders,
177
+ ...(appConfig ? {
178
+ tenantCode: appConfig.tenantCode
179
+ } : {})
180
+ },
181
+ timeout: 60000,
182
+ customUpload: async (file, insertFn) => {
183
+ await handleUpload(file, insertFn, {
184
+ typeName: '图片',
185
+ validTypes: ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'],
186
+ validExts: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
187
+ maxSize: uploadImageFileSize,
188
+ uploadServer,
189
+ uploadFileName,
190
+ uploadHeaders,
191
+ appConfig,
192
+ uploadCustomRequest
193
+ });
194
+ }
195
+ },
196
+ uploadVideo: {
197
+ server: uploadServer,
198
+ fieldName: uploadFileName,
199
+ maxFileSize: uploadVideoFileSize,
200
+ headers: {
201
+ ...uploadHeaders,
202
+ ...(appConfig ? {
203
+ tenantCode: appConfig.tenantCode
204
+ } : {})
205
+ },
206
+ timeout: 120000,
207
+ customUpload: async (file, insertFn) => {
208
+ await handleUpload(file, insertFn, {
209
+ typeName: '视频',
210
+ validTypes: ['video/mp4', 'video/webm', 'video/ogg', 'video/mkv'],
211
+ validExts: ['mp4', 'webm', 'ogg', 'mkv'],
212
+ maxSize: uploadVideoFileSize,
213
+ uploadServer,
214
+ uploadFileName,
215
+ uploadHeaders,
216
+ appConfig,
217
+ uploadCustomRequest
218
+ });
219
+ }
220
+ }
221
+ }
222
+ },
223
+ mode
224
+ });
225
+ createToolbar({
226
+ editor,
227
+ selector: toolbarRef.current,
228
+ config: {},
229
+ mode
230
+ });
231
+
232
+ // 初始化内容
233
+ const initialHtml = normalizeEmptyHtml(value || '');
234
+ // 设置html
235
+ editor.setHtml(initialHtml);
236
+ // 存储最后一次的html
237
+ lastHtmlRef.current = initialHtml;
238
+ // 禁用编辑器
239
+ if (disabled) editor.disable();
240
+ // 存储编辑器实例
241
+ editorInstanceRef.current = editor;
242
+ // 销毁编辑器
243
+ return () => {
244
+ try {
245
+ editor.destroy();
246
+ } catch (e) {}
247
+ editorInstanceRef.current = null;
248
+ };
249
+ }, []);
250
+
251
+ // 受控 value
252
+ useEffect(() => {
253
+ // 获取编辑器实例
254
+ const editor = editorInstanceRef.current;
255
+ if (!editor) return;
256
+ // 标准化html
257
+ const safeHtml = normalizeEmptyHtml(value || '');
258
+ if (safeHtml !== lastHtmlRef.current) {
259
+ editor.setHtml(safeHtml);
260
+ // 存储最后一次的html
261
+ lastHtmlRef.current = safeHtml;
262
+ }
263
+ }, [value]);
264
+
265
+ // disabled 切换
266
+ useEffect(() => {
267
+ const editor = editorInstanceRef.current;
268
+ if (!editor) return;
269
+ // 禁用编辑器
270
+ if (disabled) editor.disable();
271
+ // 启用编辑器
272
+ else editor.enable();
273
+ }, [disabled]);
274
+ // 暴露编辑器实例
275
+ useImperativeHandle(ref, () => ({
276
+ // 获取编辑器实例
277
+ getEditorInstance: () => editorInstanceRef.current,
278
+ // 获取html
279
+ getHtml: () => normalizeEmptyHtml(editorInstanceRef.current?.getHtml?.() || ''),
280
+ // 设置html
281
+ setHtml: html => {
282
+ if (!editorInstanceRef.current) return;
283
+ const safeHtml = normalizeEmptyHtml(html || '');
284
+ editorInstanceRef.current.setHtml(safeHtml);
285
+ lastHtmlRef.current = safeHtml;
286
+ },
287
+ // 获取纯文本
288
+ getPlainText: () => editorInstanceRef.current?.getText?.() || ''
289
+ }));
290
+ return /*#__PURE__*/React.createElement("div", {
291
+ style: {
292
+ border: bordered ? `${borderWidth}px ${borderStyle} ${borderColor}` : 'none',
293
+ overflow: 'hidden',
294
+ borderRadius
295
+ }
296
+ }, /*#__PURE__*/React.createElement("div", {
297
+ ref: toolbarRef,
298
+ style: {
299
+ borderBottom: bordered ? `${borderWidth}px ${borderStyle} ${borderColor}` : 'none'
300
+ }
301
+ }), /*#__PURE__*/React.createElement("div", {
302
+ ref: editorRef,
303
+ style: {
304
+ height,
305
+ overflowY: 'auto'
306
+ }
307
+ }));
308
+ });
309
+ RCEditor.propTypes = {
310
+ bordered: PropTypes.bool,
311
+ borderStyle: PropTypes.string,
312
+ borderColor: PropTypes.string,
313
+ borderWidth: PropTypes.number,
314
+ borderRadius: PropTypes.number,
315
+ mode: PropTypes.string,
316
+ value: PropTypes.string,
317
+ height: PropTypes.number,
318
+ disabled: PropTypes.bool,
319
+ uploadUrl: PropTypes.string,
320
+ uploadFileName: PropTypes.string,
321
+ uploadImageFileSize: PropTypes.number,
322
+ uploadVideoFileSize: PropTypes.number,
323
+ uploadHeaders: PropTypes.object,
324
+ uploadCustomRequest: PropTypes.func,
325
+ placeholder: PropTypes.string,
326
+ onBlur: PropTypes.func,
327
+ onFocus: PropTypes.func,
328
+ onChange: PropTypes.func
329
+ };
330
+ export default RCEditor;