|
|
@@ -6,13 +6,14 @@
|
|
6
|
6
|
* @module components/Editor/RichTextEditor
|
|
7
|
7
|
*/
|
|
8
|
8
|
|
|
9
|
|
-import React, { useEffect, useRef } from 'react';
|
|
10
|
|
-import { Button, Tooltip, Space, Divider } from 'antd';
|
|
|
9
|
+import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|
|
10
|
+import { Button, Tooltip, Space, Divider, Select, Popover } from 'antd';
|
|
11
|
11
|
import {
|
|
12
|
12
|
BoldOutlined,
|
|
13
|
13
|
ItalicOutlined,
|
|
14
|
14
|
UnderlineOutlined,
|
|
15
|
15
|
FontSizeOutlined,
|
|
|
16
|
+ BgColorsOutlined,
|
|
16
|
17
|
} from '@ant-design/icons';
|
|
17
|
18
|
import './RichTextToolbar.css';
|
|
18
|
19
|
|
|
|
@@ -44,6 +45,139 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
44
|
45
|
onFormat,
|
|
45
|
46
|
}) => {
|
|
46
|
47
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
|
|
48
|
+ const [currentFontSize, setCurrentFontSize] = useState<string>('12');
|
|
|
49
|
+ const [currentColor, setCurrentColor] = useState<string>('#000000');
|
|
|
50
|
+ const [colorPickerOpen, setColorPickerOpen] = useState(false);
|
|
|
51
|
+
|
|
|
52
|
+ // 保存选区的引用(使用更可靠的方式)
|
|
|
53
|
+ const savedSelectionRef = useRef<{
|
|
|
54
|
+ range: Range;
|
|
|
55
|
+ text: string;
|
|
|
56
|
+ } | null>(null);
|
|
|
57
|
+
|
|
|
58
|
+ // ── 保存选区 ───────────────────────────────────────────────────────────────
|
|
|
59
|
+ const saveSelection = useCallback(() => {
|
|
|
60
|
+ const selection = window.getSelection();
|
|
|
61
|
+ if (selection && selection.rangeCount > 0) {
|
|
|
62
|
+ const range = selection.getRangeAt(0);
|
|
|
63
|
+ savedSelectionRef.current = {
|
|
|
64
|
+ range: range.cloneRange(),
|
|
|
65
|
+ text: range.toString()
|
|
|
66
|
+ };
|
|
|
67
|
+ }
|
|
|
68
|
+ }, []);
|
|
|
69
|
+
|
|
|
70
|
+ // ── 恢复选区 ───────────────────────────────────────────────────────────────
|
|
|
71
|
+ const restoreSelection = useCallback(() => {
|
|
|
72
|
+ if (savedSelectionRef.current) {
|
|
|
73
|
+ const selection = window.getSelection();
|
|
|
74
|
+ if (selection) {
|
|
|
75
|
+ try {
|
|
|
76
|
+ selection.removeAllRanges();
|
|
|
77
|
+ selection.addRange(savedSelectionRef.current.range);
|
|
|
78
|
+ } catch (error) {
|
|
|
79
|
+ console.error('Failed to restore selection:', error);
|
|
|
80
|
+ }
|
|
|
81
|
+ }
|
|
|
82
|
+ }
|
|
|
83
|
+ }, []);
|
|
|
84
|
+
|
|
|
85
|
+ // ── 检测当前选区的格式 ─────────────────────────────────────────────────────
|
|
|
86
|
+ useEffect(() => {
|
|
|
87
|
+ const updateCurrentFormat = () => {
|
|
|
88
|
+ const selection = window.getSelection();
|
|
|
89
|
+ if (!selection || selection.rangeCount === 0) return;
|
|
|
90
|
+
|
|
|
91
|
+ // 保存选区
|
|
|
92
|
+ console.log('[updateCurrentFormat] Saving selection');
|
|
|
93
|
+ saveSelection();
|
|
|
94
|
+
|
|
|
95
|
+ const range = selection.getRangeAt(0);
|
|
|
96
|
+ const container = range.commonAncestorContainer;
|
|
|
97
|
+ const parentElement = container.nodeType === Node.TEXT_NODE
|
|
|
98
|
+ ? container.parentElement
|
|
|
99
|
+ : container as HTMLElement;
|
|
|
100
|
+
|
|
|
101
|
+ if (parentElement) {
|
|
|
102
|
+ // 检测字号
|
|
|
103
|
+ const computedStyle = window.getComputedStyle(parentElement);
|
|
|
104
|
+ if (computedStyle.fontSize) {
|
|
|
105
|
+ const fontSizeStr = computedStyle.fontSize;
|
|
|
106
|
+ let fontSize: number;
|
|
|
107
|
+
|
|
|
108
|
+ if (fontSizeStr.endsWith('pt')) {
|
|
|
109
|
+ fontSize = Math.round(parseFloat(fontSizeStr));
|
|
|
110
|
+ } else if (fontSizeStr.endsWith('px')) {
|
|
|
111
|
+ fontSize = Math.round(parseFloat(fontSizeStr) * 0.75);
|
|
|
112
|
+ } else {
|
|
|
113
|
+ fontSize = Math.round(parseFloat(fontSizeStr) * 0.75);
|
|
|
114
|
+ }
|
|
|
115
|
+
|
|
|
116
|
+ if (fontSize && !isNaN(fontSize)) {
|
|
|
117
|
+ setCurrentFontSize(String(fontSize));
|
|
|
118
|
+ }
|
|
|
119
|
+ }
|
|
|
120
|
+
|
|
|
121
|
+ // 检测颜色
|
|
|
122
|
+ if (computedStyle.color) {
|
|
|
123
|
+ const rgb = computedStyle.color;
|
|
|
124
|
+ // 将 rgb(r, g, b) 转换为十六进制
|
|
|
125
|
+ const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
|
|
126
|
+ if (match) {
|
|
|
127
|
+ const r = parseInt(match[1]).toString(16).padStart(2, '0');
|
|
|
128
|
+ const g = parseInt(match[2]).toString(16).padStart(2, '0');
|
|
|
129
|
+ const b = parseInt(match[3]).toString(16).padStart(2, '0');
|
|
|
130
|
+ const hexColor = `#${r}${g}${b}`;
|
|
|
131
|
+ console.log('[updateCurrentFormat] Detected color:', hexColor);
|
|
|
132
|
+ setCurrentColor(hexColor);
|
|
|
133
|
+ }
|
|
|
134
|
+ }
|
|
|
135
|
+ }
|
|
|
136
|
+ };
|
|
|
137
|
+
|
|
|
138
|
+ // 工具栏显示时更新格式
|
|
|
139
|
+ updateCurrentFormat();
|
|
|
140
|
+ }, [position, saveSelection]); // position 变化表示工具栏重新显示
|
|
|
141
|
+
|
|
|
142
|
+ // 字号选项
|
|
|
143
|
+ const fontSizeOptions = [
|
|
|
144
|
+ { label: '8', value: '8' },
|
|
|
145
|
+ { label: '9', value: '9' },
|
|
|
146
|
+ { label: '10', value: '10' },
|
|
|
147
|
+ { label: '11', value: '11' },
|
|
|
148
|
+ { label: '12', value: '12' },
|
|
|
149
|
+ { label: '14', value: '14' },
|
|
|
150
|
+ { label: '16', value: '16' },
|
|
|
151
|
+ { label: '18', value: '18' },
|
|
|
152
|
+ { label: '20', value: '20' },
|
|
|
153
|
+ { label: '22', value: '22' },
|
|
|
154
|
+ { label: '24', value: '24' },
|
|
|
155
|
+ { label: '26', value: '26' },
|
|
|
156
|
+ { label: '28', value: '28' },
|
|
|
157
|
+ { label: '36', value: '36' },
|
|
|
158
|
+ { label: '48', value: '48' },
|
|
|
159
|
+ { label: '72', value: '72' },
|
|
|
160
|
+ ];
|
|
|
161
|
+
|
|
|
162
|
+ // 预设颜色
|
|
|
163
|
+ const presetColors = [
|
|
|
164
|
+ '#000000', // 黑色
|
|
|
165
|
+ '#FFFFFF', // 白色
|
|
|
166
|
+ '#FF0000', // 红色
|
|
|
167
|
+ '#00FF00', // 绿色
|
|
|
168
|
+ '#0000FF', // 蓝色
|
|
|
169
|
+ '#FFFF00', // 黄色
|
|
|
170
|
+ '#FF00FF', // 洋红
|
|
|
171
|
+ '#00FFFF', // 青色
|
|
|
172
|
+ '#808080', // 灰色
|
|
|
173
|
+ '#800000', // 栗色
|
|
|
174
|
+ '#008000', // 深绿
|
|
|
175
|
+ '#000080', // 海军蓝
|
|
|
176
|
+ '#808000', // 橄榄色
|
|
|
177
|
+ '#800080', // 紫色
|
|
|
178
|
+ '#008080', // 青色(暗)
|
|
|
179
|
+ '#C0C0C0', // 银色
|
|
|
180
|
+ ];
|
|
47
|
181
|
|
|
48
|
182
|
// ── 点击外部关闭 ───────────────────────────────────────────────────────────
|
|
49
|
183
|
useEffect(() => {
|
|
|
@@ -63,14 +197,181 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
63
|
197
|
|
|
64
|
198
|
// ── 格式化命令 ─────────────────────────────────────────────────────────────
|
|
65
|
199
|
const execCommand = (command: string, value?: string) => {
|
|
66
|
|
- document.execCommand(command, false, value);
|
|
|
200
|
+ const selection = window.getSelection();
|
|
|
201
|
+ if (!selection || selection.rangeCount === 0) return;
|
|
|
202
|
+
|
|
|
203
|
+ const range = selection.getRangeAt(0);
|
|
|
204
|
+ if (range.collapsed) return;
|
|
|
205
|
+
|
|
|
206
|
+ // 创建一个包装元素
|
|
|
207
|
+ const span = document.createElement('span');
|
|
|
208
|
+
|
|
|
209
|
+ // 根据命令类型应用样式
|
|
|
210
|
+ switch (command) {
|
|
|
211
|
+ case 'bold':
|
|
|
212
|
+ span.style.fontWeight = isActive('bold') ? 'normal' : 'bold';
|
|
|
213
|
+ break;
|
|
|
214
|
+ case 'italic':
|
|
|
215
|
+ span.style.fontStyle = isActive('italic') ? 'normal' : 'italic';
|
|
|
216
|
+ break;
|
|
|
217
|
+ case 'underline':
|
|
|
218
|
+ span.style.textDecoration = isActive('underline') ? 'none' : 'underline';
|
|
|
219
|
+ break;
|
|
|
220
|
+ case 'foreColor':
|
|
|
221
|
+ if (value) span.style.color = value;
|
|
|
222
|
+ break;
|
|
|
223
|
+ default:
|
|
|
224
|
+ return;
|
|
|
225
|
+ }
|
|
|
226
|
+
|
|
|
227
|
+ // 提取内容并包装
|
|
|
228
|
+ const contents = range.extractContents();
|
|
|
229
|
+ span.appendChild(contents);
|
|
|
230
|
+ range.insertNode(span);
|
|
|
231
|
+
|
|
|
232
|
+ // 重新选中
|
|
|
233
|
+ const newRange = document.createRange();
|
|
|
234
|
+ newRange.selectNodeContents(span);
|
|
|
235
|
+ selection.removeAllRanges();
|
|
|
236
|
+ selection.addRange(newRange);
|
|
|
237
|
+
|
|
|
238
|
+ onFormat();
|
|
|
239
|
+ };
|
|
|
240
|
+
|
|
|
241
|
+ // ── 设置字号 ───────────────────────────────────────────────────────────────
|
|
|
242
|
+ const handleFontSizeChange = (size: string) => {
|
|
|
243
|
+ setCurrentFontSize(size);
|
|
|
244
|
+
|
|
|
245
|
+ // 恢复选区
|
|
|
246
|
+ restoreSelection();
|
|
|
247
|
+
|
|
|
248
|
+ const selection = window.getSelection();
|
|
|
249
|
+ if (!selection || selection.rangeCount === 0) return;
|
|
|
250
|
+
|
|
|
251
|
+ const range = selection.getRangeAt(0);
|
|
|
252
|
+ if (range.collapsed) return;
|
|
|
253
|
+
|
|
|
254
|
+ // 创建一个 span 元素来包装选中的文本
|
|
|
255
|
+ const span = document.createElement('span');
|
|
|
256
|
+ span.style.fontSize = `${size}pt`;
|
|
|
257
|
+
|
|
|
258
|
+ // 提取选中的内容
|
|
|
259
|
+ const contents = range.extractContents();
|
|
|
260
|
+ span.appendChild(contents);
|
|
|
261
|
+
|
|
|
262
|
+ // 插入新的 span
|
|
|
263
|
+ range.insertNode(span);
|
|
|
264
|
+
|
|
|
265
|
+ // 重新选中内容
|
|
|
266
|
+ const newRange = document.createRange();
|
|
|
267
|
+ newRange.selectNodeContents(span);
|
|
|
268
|
+ selection.removeAllRanges();
|
|
|
269
|
+ selection.addRange(newRange);
|
|
|
270
|
+
|
|
67
|
271
|
onFormat();
|
|
68
|
272
|
};
|
|
69
|
273
|
|
|
|
274
|
+ // ── 设置颜色(简化版,直接操作 DOM)───────────────────────────────────────
|
|
|
275
|
+ const handleColorChange = (color: string) => {
|
|
|
276
|
+ console.log('[handleColorChange] Start - color:', color);
|
|
|
277
|
+ setCurrentColor(color);
|
|
|
278
|
+
|
|
|
279
|
+ // 获取编辑器元素
|
|
|
280
|
+ const editor = document.querySelector('.rich-text-editor') as HTMLElement;
|
|
|
281
|
+ if (!editor) {
|
|
|
282
|
+ console.error('[handleColorChange] Editor element not found');
|
|
|
283
|
+ return;
|
|
|
284
|
+ }
|
|
|
285
|
+
|
|
|
286
|
+ // 确保编辑器有焦点
|
|
|
287
|
+ if (document.activeElement !== editor) {
|
|
|
288
|
+ console.log('[handleColorChange] Editor not focused, focusing...');
|
|
|
289
|
+ editor.focus();
|
|
|
290
|
+ }
|
|
|
291
|
+
|
|
|
292
|
+ // 恢复保存的选区
|
|
|
293
|
+ restoreSelection();
|
|
|
294
|
+
|
|
|
295
|
+ // 再次确认选区
|
|
|
296
|
+ const selection = window.getSelection();
|
|
|
297
|
+ if (!selection) {
|
|
|
298
|
+ console.error('[handleColorChange] No selection object');
|
|
|
299
|
+ return;
|
|
|
300
|
+ }
|
|
|
301
|
+
|
|
|
302
|
+ if (selection.rangeCount === 0) {
|
|
|
303
|
+ console.error('[handleColorChange] No range in selection');
|
|
|
304
|
+ return;
|
|
|
305
|
+ }
|
|
|
306
|
+
|
|
|
307
|
+ const range = selection.getRangeAt(0);
|
|
|
308
|
+ if (range.collapsed) {
|
|
|
309
|
+ console.error('[handleColorChange] Selection is collapsed');
|
|
|
310
|
+ return;
|
|
|
311
|
+ }
|
|
|
312
|
+
|
|
|
313
|
+ console.log('[handleColorChange] Selection valid, applying color...');
|
|
|
314
|
+ console.log('[handleColorChange] Selected text:', range.toString());
|
|
|
315
|
+
|
|
|
316
|
+ // 使用现代方法直接操作 DOM
|
|
|
317
|
+ try {
|
|
|
318
|
+ const span = document.createElement('span');
|
|
|
319
|
+ span.style.color = color;
|
|
|
320
|
+
|
|
|
321
|
+ const contents = range.extractContents();
|
|
|
322
|
+ span.appendChild(contents);
|
|
|
323
|
+ range.insertNode(span);
|
|
|
324
|
+
|
|
|
325
|
+ // 重新选中
|
|
|
326
|
+ const newRange = document.createRange();
|
|
|
327
|
+ newRange.selectNodeContents(span);
|
|
|
328
|
+ selection.removeAllRanges();
|
|
|
329
|
+ selection.addRange(newRange);
|
|
|
330
|
+
|
|
|
331
|
+ console.log('[handleColorChange] Color applied successfully');
|
|
|
332
|
+ } catch (error) {
|
|
|
333
|
+ console.error('[handleColorChange] Failed to apply color:', error);
|
|
|
334
|
+ return;
|
|
|
335
|
+ }
|
|
|
336
|
+
|
|
|
337
|
+ // 触发 input 事件
|
|
|
338
|
+ console.log('[handleColorChange] Triggering input event');
|
|
|
339
|
+ const inputEvent = new Event('input', { bubbles: true, cancelable: false });
|
|
|
340
|
+ editor.dispatchEvent(inputEvent);
|
|
|
341
|
+
|
|
|
342
|
+ // 调用 onFormat
|
|
|
343
|
+ console.log('[handleColorChange] Calling onFormat');
|
|
|
344
|
+ onFormat();
|
|
|
345
|
+
|
|
|
346
|
+ console.log('[handleColorChange] Complete');
|
|
|
347
|
+ };
|
|
|
348
|
+
|
|
70
|
349
|
// ── 检查当前格式状态 ───────────────────────────────────────────────────────
|
|
71
|
350
|
const isActive = (command: string): boolean => {
|
|
72
|
351
|
try {
|
|
73
|
|
- return document.queryCommandState(command);
|
|
|
352
|
+ const selection = window.getSelection();
|
|
|
353
|
+ if (!selection || selection.rangeCount === 0) return false;
|
|
|
354
|
+
|
|
|
355
|
+ const range = selection.getRangeAt(0);
|
|
|
356
|
+ const container = range.commonAncestorContainer;
|
|
|
357
|
+ const parentElement = container.nodeType === Node.TEXT_NODE
|
|
|
358
|
+ ? container.parentElement
|
|
|
359
|
+ : container as HTMLElement;
|
|
|
360
|
+
|
|
|
361
|
+ if (!parentElement) return false;
|
|
|
362
|
+
|
|
|
363
|
+ const computedStyle = window.getComputedStyle(parentElement);
|
|
|
364
|
+
|
|
|
365
|
+ switch (command) {
|
|
|
366
|
+ case 'bold':
|
|
|
367
|
+ return parseInt(computedStyle.fontWeight) >= 600 || computedStyle.fontWeight === 'bold';
|
|
|
368
|
+ case 'italic':
|
|
|
369
|
+ return computedStyle.fontStyle === 'italic';
|
|
|
370
|
+ case 'underline':
|
|
|
371
|
+ return computedStyle.textDecoration.includes('underline');
|
|
|
372
|
+ default:
|
|
|
373
|
+ return false;
|
|
|
374
|
+ }
|
|
74
|
375
|
} catch {
|
|
75
|
376
|
return false;
|
|
76
|
377
|
}
|
|
|
@@ -121,39 +422,112 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
121
|
422
|
|
|
122
|
423
|
<Divider type="vertical" style={{ margin: '0 4px' }} />
|
|
123
|
424
|
|
|
124
|
|
- {/* 字号 */}
|
|
|
425
|
+ {/* 字号选择器 */}
|
|
125
|
426
|
<Tooltip title="字号">
|
|
126
|
|
- <Button
|
|
127
|
|
- type="text"
|
|
|
427
|
+ <Select
|
|
|
428
|
+ value={currentFontSize}
|
|
128
|
429
|
size="small"
|
|
129
|
|
- icon={<FontSizeOutlined />}
|
|
130
|
|
- onClick={() => {
|
|
131
|
|
- const size = prompt('输入字号(磅):', '12');
|
|
132
|
|
- if (size) {
|
|
133
|
|
- execCommand('fontSize', '7');
|
|
134
|
|
- // 查找刚创建的font标签并修改
|
|
135
|
|
- const selection = window.getSelection();
|
|
136
|
|
- if (selection && selection.anchorNode) {
|
|
137
|
|
- const parent = selection.anchorNode.parentElement;
|
|
138
|
|
- if (parent?.tagName === 'FONT') {
|
|
139
|
|
- parent.removeAttribute('size');
|
|
140
|
|
- parent.style.fontSize = `${size}pt`;
|
|
141
|
|
- }
|
|
142
|
|
- }
|
|
143
|
|
- onFormat();
|
|
144
|
|
- }
|
|
145
|
|
- }}
|
|
|
430
|
+ style={{ width: 65 }}
|
|
|
431
|
+ onChange={handleFontSizeChange}
|
|
|
432
|
+ options={fontSizeOptions}
|
|
|
433
|
+ suffixIcon={<FontSizeOutlined />}
|
|
146
|
434
|
/>
|
|
147
|
435
|
</Tooltip>
|
|
148
|
436
|
|
|
149
|
|
- {/* 颜色 */}
|
|
|
437
|
+ {/* 颜色选择器 */}
|
|
150
|
438
|
<Tooltip title="文字颜色">
|
|
151
|
|
- <input
|
|
152
|
|
- type="color"
|
|
153
|
|
- className="color-picker"
|
|
154
|
|
- onChange={(e) => execCommand('foreColor', e.target.value)}
|
|
155
|
|
- title="文字颜色"
|
|
156
|
|
- />
|
|
|
439
|
+ <Popover
|
|
|
440
|
+ open={colorPickerOpen}
|
|
|
441
|
+ onOpenChange={(open) => {
|
|
|
442
|
+ if (open) {
|
|
|
443
|
+ // 打开时保存选区
|
|
|
444
|
+ saveSelection();
|
|
|
445
|
+ }
|
|
|
446
|
+ setColorPickerOpen(open);
|
|
|
447
|
+ }}
|
|
|
448
|
+ content={
|
|
|
449
|
+ <div
|
|
|
450
|
+ style={{ width: 200 }}
|
|
|
451
|
+ onMouseDown={(e) => {
|
|
|
452
|
+ // 阻止 mousedown 事件,防止失去焦点
|
|
|
453
|
+ e.preventDefault();
|
|
|
454
|
+ e.stopPropagation();
|
|
|
455
|
+ }}
|
|
|
456
|
+ onClick={(e) => {
|
|
|
457
|
+ // 阻止 click 事件冒泡
|
|
|
458
|
+ e.stopPropagation();
|
|
|
459
|
+ }}
|
|
|
460
|
+ >
|
|
|
461
|
+ <div style={{ marginBottom: 8 }}>
|
|
|
462
|
+ <div style={{
|
|
|
463
|
+ display: 'grid',
|
|
|
464
|
+ gridTemplateColumns: 'repeat(8, 1fr)',
|
|
|
465
|
+ gap: 4
|
|
|
466
|
+ }}>
|
|
|
467
|
+ {presetColors.map((color) => (
|
|
|
468
|
+ <div
|
|
|
469
|
+ key={color}
|
|
|
470
|
+ onMouseDown={(e) => {
|
|
|
471
|
+ // 阻止失去焦点
|
|
|
472
|
+ e.preventDefault();
|
|
|
473
|
+ e.stopPropagation();
|
|
|
474
|
+ }}
|
|
|
475
|
+ onClick={(e) => {
|
|
|
476
|
+ e.preventDefault();
|
|
|
477
|
+ e.stopPropagation();
|
|
|
478
|
+ console.log('Color clicked:', color);
|
|
|
479
|
+ handleColorChange(color);
|
|
|
480
|
+ // 延迟关闭,确保颜色应用完成
|
|
|
481
|
+ setTimeout(() => setColorPickerOpen(false), 100);
|
|
|
482
|
+ }}
|
|
|
483
|
+ style={{
|
|
|
484
|
+ width: 20,
|
|
|
485
|
+ height: 20,
|
|
|
486
|
+ backgroundColor: color,
|
|
|
487
|
+ border: color === '#FFFFFF' ? '1px solid #d9d9d9' : 'none',
|
|
|
488
|
+ borderRadius: 2,
|
|
|
489
|
+ cursor: 'pointer',
|
|
|
490
|
+ boxShadow: currentColor === color ? '0 0 0 2px #1890ff' : 'none',
|
|
|
491
|
+ }}
|
|
|
492
|
+ title={color}
|
|
|
493
|
+ />
|
|
|
494
|
+ ))}
|
|
|
495
|
+ </div>
|
|
|
496
|
+ </div>
|
|
|
497
|
+ <div style={{ marginTop: 8 }}>
|
|
|
498
|
+ <input
|
|
|
499
|
+ type="color"
|
|
|
500
|
+ value={currentColor}
|
|
|
501
|
+ onChange={(e) => {
|
|
|
502
|
+ e.preventDefault();
|
|
|
503
|
+ e.stopPropagation();
|
|
|
504
|
+ console.log('Color input changed:', e.target.value);
|
|
|
505
|
+ handleColorChange(e.target.value);
|
|
|
506
|
+ }}
|
|
|
507
|
+ onMouseDown={(e) => {
|
|
|
508
|
+ e.stopPropagation();
|
|
|
509
|
+ }}
|
|
|
510
|
+ onClick={(e) => {
|
|
|
511
|
+ e.stopPropagation();
|
|
|
512
|
+ }}
|
|
|
513
|
+ style={{ width: '100%', height: 32, cursor: 'pointer' }}
|
|
|
514
|
+ />
|
|
|
515
|
+ </div>
|
|
|
516
|
+ </div>
|
|
|
517
|
+ }
|
|
|
518
|
+ trigger="click"
|
|
|
519
|
+ placement="bottom"
|
|
|
520
|
+ >
|
|
|
521
|
+ <Button
|
|
|
522
|
+ type="text"
|
|
|
523
|
+ size="small"
|
|
|
524
|
+ icon={<BgColorsOutlined style={{ color: currentColor }} />}
|
|
|
525
|
+ onMouseDown={(e) => {
|
|
|
526
|
+ // 打开颜色选择器前保存选区
|
|
|
527
|
+ saveSelection();
|
|
|
528
|
+ }}
|
|
|
529
|
+ />
|
|
|
530
|
+ </Popover>
|
|
157
|
531
|
</Tooltip>
|
|
158
|
532
|
</Space>
|
|
159
|
533
|
</div>
|