为专心模式添加去杂色功能,新增画布颜色面板以管理颜色,优化颜色移除和恢复逻辑,提升用户操作体验。
This commit is contained in:
@@ -30,6 +30,7 @@ import CompletionCard from '../../components/CompletionCard';
|
||||
import PreviewToolbar from '../../components/PreviewToolbar';
|
||||
import EditToolbar from '../../components/EditToolbar';
|
||||
import ColorSystemPanel from '../../components/ColorSystemPanel';
|
||||
import CanvasColorPanel from '../../components/CanvasColorPanel';
|
||||
import { getColorKeyByHex, ColorSystem, getMardToHexMapping, getAllHexValues } from '../../utils/colorSystemUtils';
|
||||
|
||||
// 定义编辑模式类型
|
||||
@@ -140,6 +141,11 @@ export default function FocusMode() {
|
||||
const [history, setHistory] = useState<MappedPixel[][][]>([]);
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
|
||||
// 去杂色模式状态
|
||||
const [showCanvasColorPanel, setShowCanvasColorPanel] = useState(false);
|
||||
const [canvasPalette, setCanvasPalette] = useState<Set<string>>(new Set());
|
||||
const [removedColors, setRemovedColors] = useState<string[]>([]);
|
||||
|
||||
// 计算状态
|
||||
const hasSelection = selectedCells.size > 0;
|
||||
const canUndo = historyIndex > 0;
|
||||
@@ -1011,6 +1017,148 @@ export default function FocusMode() {
|
||||
}
|
||||
}, [history, historyIndex]);
|
||||
|
||||
// 获取画布中的所有颜色信息
|
||||
const getCanvasColors = useCallback(() => {
|
||||
if (!mappedPixelData) return [];
|
||||
|
||||
const colorCounts: { [hex: string]: number } = {};
|
||||
|
||||
mappedPixelData.forEach(row => {
|
||||
row.forEach(pixel => {
|
||||
if (pixel.color && pixel.color !== 'transparent' && !pixel.isExternal) {
|
||||
colorCounts[pixel.color] = (colorCounts[pixel.color] || 0) + 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.entries(colorCounts).map(([hex, count]) => ({
|
||||
hex,
|
||||
key: getColorKeyByHex(hex, selectedColorSystem),
|
||||
count
|
||||
}));
|
||||
}, [mappedPixelData, selectedColorSystem]);
|
||||
|
||||
// 去杂色:启动模式
|
||||
const handleRemoveNoise = useCallback(() => {
|
||||
if (!mappedPixelData) return;
|
||||
|
||||
// 获取当前画布颜色
|
||||
const canvasColors = getCanvasColors();
|
||||
setCanvasPalette(new Set(canvasColors.map(c => c.hex)));
|
||||
setRemovedColors([]); // 重置已移除颜色列表
|
||||
setShowCanvasColorPanel(true);
|
||||
}, [mappedPixelData, getCanvasColors]);
|
||||
|
||||
// 去杂色:移除颜色
|
||||
const handleRemoveCanvasColor = useCallback((hexToRemove: string) => {
|
||||
if (!mappedPixelData) return;
|
||||
|
||||
// 更新画布色板
|
||||
const newCanvasPalette = new Set(canvasPalette);
|
||||
newCanvasPalette.delete(hexToRemove);
|
||||
setCanvasPalette(newCanvasPalette);
|
||||
|
||||
// 添加到已移除颜色列表
|
||||
setRemovedColors(prev => [...prev, hexToRemove]);
|
||||
|
||||
// 保存历史
|
||||
saveToHistory();
|
||||
|
||||
// 获取剩余的颜色
|
||||
const remainingColors = Array.from(newCanvasPalette);
|
||||
|
||||
if (remainingColors.length === 0) {
|
||||
console.warn('无法移除所有颜色');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新的像素数据
|
||||
const newPixelData = mappedPixelData.map(row =>
|
||||
row.map(pixel => {
|
||||
if (pixel.color === hexToRemove && !pixel.isExternal) {
|
||||
// 找到最接近的替换颜色
|
||||
let closestColor = remainingColors[0];
|
||||
let minDistance = Infinity;
|
||||
|
||||
const removedRgb = hexToRgb(hexToRemove);
|
||||
if (removedRgb) {
|
||||
remainingColors.forEach(hex => {
|
||||
const rgb = hexToRgb(hex);
|
||||
if (rgb) {
|
||||
const distance = colorDistance(removedRgb, rgb);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestColor = hex;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...pixel,
|
||||
color: closestColor,
|
||||
key: closestColor
|
||||
};
|
||||
}
|
||||
return pixel;
|
||||
})
|
||||
);
|
||||
|
||||
setMappedPixelData(newPixelData);
|
||||
}, [mappedPixelData, canvasPalette, saveToHistory]);
|
||||
|
||||
// 去杂色:恢复颜色
|
||||
const handleRestoreCanvasColor = useCallback((hexToRestore: string) => {
|
||||
if (!mappedPixelData) return;
|
||||
|
||||
// 从已移除列表中移除
|
||||
setRemovedColors(prev => prev.filter(hex => hex !== hexToRestore));
|
||||
|
||||
// 添加回画布色板
|
||||
const newCanvasPalette = new Set(canvasPalette);
|
||||
newCanvasPalette.add(hexToRestore);
|
||||
setCanvasPalette(newCanvasPalette);
|
||||
|
||||
// 保存历史
|
||||
saveToHistory();
|
||||
|
||||
// 重新计算整个画布的颜色映射
|
||||
const allAvailableColors = Array.from(newCanvasPalette);
|
||||
|
||||
const newPixelData = mappedPixelData.map(row =>
|
||||
row.map(pixel => {
|
||||
if (!pixel.isExternal && pixel.color !== 'transparent') {
|
||||
// 为每个像素找到最接近的可用颜色
|
||||
let closestColor = allAvailableColors[0];
|
||||
let minDistance = Infinity;
|
||||
|
||||
const pixelRgb = hexToRgb(pixel.color);
|
||||
if (pixelRgb) {
|
||||
allAvailableColors.forEach(hex => {
|
||||
const rgb = hexToRgb(hex);
|
||||
if (rgb) {
|
||||
const distance = colorDistance(pixelRgb, rgb);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestColor = hex;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...pixel,
|
||||
color: closestColor,
|
||||
key: closestColor
|
||||
};
|
||||
}
|
||||
return pixel;
|
||||
})
|
||||
);
|
||||
|
||||
setMappedPixelData(newPixelData);
|
||||
}, [mappedPixelData, canvasPalette, saveToHistory]);
|
||||
|
||||
// 编辑模式:执行操作
|
||||
const handleEditOperation = useCallback((operation: 'fill' | 'clear' | 'invert') => {
|
||||
if (!mappedPixelData) return;
|
||||
@@ -1274,22 +1422,30 @@ export default function FocusMode() {
|
||||
{/* 编辑模式工具栏 */}
|
||||
{focusState.editMode === 'edit' && (
|
||||
<EditToolbar
|
||||
editTool={editTool}
|
||||
hasSelection={hasSelection}
|
||||
canUndo={canUndo}
|
||||
canRedo={canRedo}
|
||||
selectedColor={selectedColor}
|
||||
availableColors={availableColors}
|
||||
selectedCells={selectedCells}
|
||||
onEditToolChange={setEditTool}
|
||||
onEditOperation={handleEditOperation}
|
||||
onUndo={handleUndo}
|
||||
onRedo={handleRedo}
|
||||
onRemoveNoise={handleRemoveNoise}
|
||||
onManualColoring={() => {
|
||||
// TODO: 实现手动上色功能
|
||||
console.log('手动上色功能');
|
||||
}}
|
||||
onColorSelect={setSelectedColor}
|
||||
onShowColorPanel={() => setFocusState(prev => ({ ...prev, showColorPanel: true }))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 画布颜色面板 - 去杂色模式 */}
|
||||
{showCanvasColorPanel && (
|
||||
<CanvasColorPanel
|
||||
canvasColors={getCanvasColors()}
|
||||
removedColors={removedColors}
|
||||
selectedColorSystem={selectedColorSystem}
|
||||
onColorRemove={handleRemoveCanvasColor}
|
||||
onColorRestore={handleRestoreCanvasColor}
|
||||
onClose={() => setShowCanvasColorPanel(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 底部模式切换栏 */}
|
||||
<div className="bg-white border-t border-gray-200 px-4 py-3">
|
||||
<div className="flex bg-gray-100 rounded-lg p-1 max-w-md mx-auto">
|
||||
|
||||
129
src/components/CanvasColorPanel.tsx
Normal file
129
src/components/CanvasColorPanel.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
import { getColorKeyByHex, ColorSystem } from '../utils/colorSystemUtils';
|
||||
|
||||
interface CanvasColorInfo {
|
||||
hex: string;
|
||||
key: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface CanvasColorPanelProps {
|
||||
canvasColors: CanvasColorInfo[];
|
||||
removedColors: string[];
|
||||
selectedColorSystem: ColorSystem;
|
||||
onColorRemove: (hex: string) => void;
|
||||
onColorRestore: (hex: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const CanvasColorPanel: React.FC<CanvasColorPanelProps> = ({
|
||||
canvasColors,
|
||||
removedColors,
|
||||
selectedColorSystem,
|
||||
onColorRemove,
|
||||
onColorRestore,
|
||||
onClose
|
||||
}) => {
|
||||
// 按数量从少到多排序
|
||||
const sortedColors = [...canvasColors].sort((a, b) => a.count - b.count);
|
||||
|
||||
return (
|
||||
<div className="bg-white/95 backdrop-blur-lg border-t border-gray-200/30 px-4 py-3 max-h-64 overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium text-gray-700">画布颜色</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{/* 当前画布颜色 */}
|
||||
{sortedColors.map(color => (
|
||||
<button
|
||||
key={color.hex}
|
||||
onClick={() => onColorRemove(color.hex)}
|
||||
className="flex items-center gap-3 p-2 hover:bg-red-50/50 rounded-lg transition-all duration-200 active:bg-red-100/60 group"
|
||||
>
|
||||
{/* 颜色块 */}
|
||||
<div
|
||||
className="w-6 h-6 rounded-lg border border-gray-200 flex-shrink-0"
|
||||
style={{ backgroundColor: color.hex }}
|
||||
/>
|
||||
|
||||
{/* 色号名称 */}
|
||||
<span className="text-sm font-mono text-gray-700 min-w-[3rem]">
|
||||
{color.key}
|
||||
</span>
|
||||
|
||||
{/* 数量 */}
|
||||
<span className="text-xs text-gray-500 ml-auto">
|
||||
{color.count}粒
|
||||
</span>
|
||||
|
||||
{/* 删除图标 */}
|
||||
<svg className="w-4 h-4 text-gray-400 group-hover:text-red-500 transition-colors opacity-0 group-hover:opacity-100" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
))}
|
||||
|
||||
{/* 已移除颜色区域 */}
|
||||
{removedColors.length > 0 && (
|
||||
<>
|
||||
<div className="border-t border-gray-300/50 pt-3 mt-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xs font-medium text-gray-500">已移除</span>
|
||||
<div className="flex-1 h-px bg-gray-200"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{removedColors.map(hex => {
|
||||
const key = getColorKeyByHex(hex, selectedColorSystem);
|
||||
return (
|
||||
<button
|
||||
key={hex}
|
||||
onClick={() => onColorRestore(hex)}
|
||||
className="flex items-center gap-3 p-2 bg-gray-50/80 hover:bg-green-50/50 rounded-lg transition-all duration-200 active:bg-green-100/60 group opacity-60 hover:opacity-100"
|
||||
>
|
||||
{/* 颜色块 */}
|
||||
<div
|
||||
className="w-6 h-6 rounded-lg border border-gray-300 flex-shrink-0 relative"
|
||||
style={{ backgroundColor: hex }}
|
||||
>
|
||||
{/* 删除线标识 */}
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-full h-0.5 bg-red-400 rotate-45"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 色号名称 */}
|
||||
<span className="text-sm font-mono text-gray-500 min-w-[3rem] line-through">
|
||||
{key}
|
||||
</span>
|
||||
|
||||
{/* 恢复图标 */}
|
||||
<svg className="w-4 h-4 text-gray-400 group-hover:text-green-500 transition-colors opacity-0 group-hover:opacity-100 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{sortedColors.length === 0 && removedColors.length === 0 && (
|
||||
<div className="text-center py-4 text-gray-500 text-sm">
|
||||
暂无颜色数据
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CanvasColorPanel;
|
||||
@@ -8,162 +8,46 @@ interface ColorInfo {
|
||||
}
|
||||
|
||||
interface EditToolbarProps {
|
||||
editTool: 'select' | 'wand';
|
||||
hasSelection: boolean;
|
||||
canUndo: boolean;
|
||||
canRedo: boolean;
|
||||
selectedColor: string;
|
||||
availableColors: ColorInfo[];
|
||||
selectedCells: Set<string>;
|
||||
onEditToolChange: (tool: 'select' | 'wand') => void;
|
||||
onEditOperation: (operation: 'fill' | 'clear' | 'invert') => void;
|
||||
onUndo: () => void;
|
||||
onRedo: () => void;
|
||||
onRemoveNoise: () => void;
|
||||
onManualColoring: () => void;
|
||||
onColorSelect: (color: string) => void;
|
||||
onShowColorPanel: () => void;
|
||||
}
|
||||
|
||||
const EditToolbar: React.FC<EditToolbarProps> = ({
|
||||
editTool,
|
||||
hasSelection,
|
||||
canUndo,
|
||||
canRedo,
|
||||
selectedColor,
|
||||
availableColors,
|
||||
selectedCells,
|
||||
onEditToolChange,
|
||||
onEditOperation,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onRemoveNoise,
|
||||
onManualColoring,
|
||||
onColorSelect,
|
||||
onShowColorPanel
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-white border-t border-gray-200 px-3 py-2">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* 选择工具区域 */}
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span className="text-[9px] text-gray-400">选择</span>
|
||||
<div className="flex bg-gray-100 rounded p-0.5">
|
||||
<button
|
||||
onClick={() => onEditToolChange('select')}
|
||||
className={`p-1 rounded ${editTool === 'select' ? 'bg-white shadow-sm' : ''}`}
|
||||
title="矩形选择"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onEditToolChange('wand')}
|
||||
className={`p-1 rounded ${editTool === 'wand' ? 'bg-white shadow-sm' : ''}`}
|
||||
title="魔棒选择"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 编辑操作区域 */}
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span className="text-[9px] text-gray-400">操作</span>
|
||||
<div className="flex gap-0.5">
|
||||
<button
|
||||
onClick={() => onEditOperation('fill')}
|
||||
className={`p-1 rounded ${!hasSelection ? 'opacity-50' : 'active:bg-gray-100'}`}
|
||||
disabled={!hasSelection}
|
||||
title="填充"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21V5a2 2 0 012-2h11l-5 7h4l-5.5 8" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onEditOperation('clear')}
|
||||
className={`p-1 rounded ${!hasSelection ? 'opacity-50' : 'active:bg-gray-100'}`}
|
||||
disabled={!hasSelection}
|
||||
title="清除"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onEditOperation('invert')}
|
||||
className="p-1 rounded active:bg-gray-100"
|
||||
title="反选"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 历史记录区域 */}
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span className="text-[9px] text-gray-400">历史</span>
|
||||
<div className="flex gap-0.5">
|
||||
<button
|
||||
onClick={onUndo}
|
||||
className={`p-1 rounded ${!canUndo ? 'opacity-50' : 'active:bg-gray-100'}`}
|
||||
disabled={!canUndo}
|
||||
title="撤销"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={onRedo}
|
||||
className={`p-1 rounded ${!canRedo ? 'opacity-50' : 'active:bg-gray-100'}`}
|
||||
disabled={!canRedo}
|
||||
title="重做"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 10h-10a8 8 0 00-8 8v2m18-10l-6 6m6-6l-6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/90 backdrop-blur-lg px-4 py-3">
|
||||
<div className="flex items-center justify-center gap-4 max-w-sm mx-auto">
|
||||
{/* 去杂色功能 */}
|
||||
<button
|
||||
onClick={onRemoveNoise}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl transition-all duration-200 hover:bg-blue-50/50 active:bg-blue-100/60 active:shadow-inner active:shadow-blue-200/40"
|
||||
>
|
||||
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-gray-700">去杂色</span>
|
||||
</button>
|
||||
|
||||
<div className="h-6 w-px bg-gray-200"></div>
|
||||
|
||||
{/* 颜色选择区域 */}
|
||||
<div className="flex flex-col gap-0.5 flex-1">
|
||||
<span className="text-[9px] text-gray-400">颜色</span>
|
||||
<div className="flex gap-0.5 overflow-x-auto">
|
||||
{availableColors.slice(0, 10).map(color => (
|
||||
<button
|
||||
key={color.color}
|
||||
onClick={() => onColorSelect(color.color)}
|
||||
className={`w-5 h-5 rounded border flex-shrink-0 ${
|
||||
selectedColor === color.color ? 'border-blue-500 border-2' : 'border-gray-300'
|
||||
}`}
|
||||
style={{ backgroundColor: color.color }}
|
||||
/>
|
||||
))}
|
||||
{availableColors.length > 10 && (
|
||||
<button
|
||||
onClick={onShowColorPanel}
|
||||
className="w-5 h-5 rounded border border-gray-300 flex items-center justify-center text-gray-500"
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 选择状态区域 */}
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span className="text-[9px] text-gray-400">状态</span>
|
||||
<div className="text-[10px] text-gray-600">
|
||||
{hasSelection ? `${selectedCells.size}格` : '未选'}
|
||||
</div>
|
||||
</div>
|
||||
{/* 手动上色功能 */}
|
||||
<button
|
||||
onClick={onManualColoring}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl transition-all duration-200 hover:bg-purple-50/50 active:bg-purple-100/60 active:shadow-inner active:shadow-purple-200/40"
|
||||
>
|
||||
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-gray-700">手动上色</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user