update
This commit is contained in:
@@ -249,6 +249,28 @@ body {
|
||||
filter: drop-shadow(0 0 3px rgba(var(--color-primary), 0.3));
|
||||
}
|
||||
|
||||
/* 选项按钮样式 */
|
||||
.btn-option {
|
||||
@apply px-3 py-1.5 rounded-md border text-sm font-medium transition-all;
|
||||
background-color: rgb(var(--color-bg-secondary));
|
||||
color: rgb(var(--color-text-secondary));
|
||||
border-color: rgba(var(--color-primary), 0.1);
|
||||
}
|
||||
|
||||
.btn-option:hover {
|
||||
border-color: rgba(var(--color-primary), 0.5);
|
||||
color: rgb(var(--color-text-primary));
|
||||
background-color: rgba(var(--color-primary), 0.1);
|
||||
}
|
||||
|
||||
.btn-option-active {
|
||||
@apply px-3 py-1.5 rounded-md border text-sm font-medium transition-all;
|
||||
background: linear-gradient(to right, rgb(var(--color-primary)), rgb(var(--color-primary-hover)));
|
||||
color: white;
|
||||
border-color: rgba(var(--color-primary), 0.7);
|
||||
box-shadow: 0 2px 4px rgba(var(--color-primary), 0.4);
|
||||
}
|
||||
|
||||
/* 圆角按钮 */
|
||||
.rounded-button {
|
||||
@apply rounded-md;
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BackgroundType, IconType, ShapeType } from './EnhancedIconPreview';
|
||||
|
||||
interface EnhancedIconCanvasProps {
|
||||
// 图标相关
|
||||
iconType: IconType;
|
||||
icon?: IconDefinition;
|
||||
customText?: string;
|
||||
iconColor: string;
|
||||
iconSize: number;
|
||||
iconRotation: number;
|
||||
fontFamily?: string;
|
||||
fontWeight?: string;
|
||||
|
||||
// 背景相关
|
||||
backgroundType: BackgroundType;
|
||||
backgroundColor: string;
|
||||
gradientStartColor?: string;
|
||||
gradientEndColor?: string;
|
||||
gradientDirection?: number;
|
||||
shape: ShapeType;
|
||||
|
||||
canvasSize?: number;
|
||||
}
|
||||
|
||||
export interface EnhancedIconCanvasRef {
|
||||
getCanvas: () => HTMLCanvasElement | null;
|
||||
generateIcon: (size: number) => Promise<string>;
|
||||
}
|
||||
|
||||
const EnhancedIconCanvas = forwardRef<EnhancedIconCanvasRef, EnhancedIconCanvasProps>(({
|
||||
iconType,
|
||||
icon,
|
||||
customText,
|
||||
iconColor,
|
||||
iconSize,
|
||||
iconRotation,
|
||||
fontFamily = 'Arial, sans-serif',
|
||||
fontWeight = 'normal',
|
||||
backgroundType,
|
||||
backgroundColor,
|
||||
gradientStartColor,
|
||||
gradientEndColor,
|
||||
gradientDirection = 45,
|
||||
shape,
|
||||
canvasSize = 200
|
||||
}, ref) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
// 创建渐变
|
||||
const createGradient = (ctx: CanvasRenderingContext2D, size: number) => {
|
||||
if (!gradientStartColor || !gradientEndColor) return null;
|
||||
|
||||
if (backgroundType === 'linear-gradient') {
|
||||
const angle = (gradientDirection * Math.PI) / 180;
|
||||
const x1 = size / 2 - (Math.cos(angle) * size) / 2;
|
||||
const y1 = size / 2 - (Math.sin(angle) * size) / 2;
|
||||
const x2 = size / 2 + (Math.cos(angle) * size) / 2;
|
||||
const y2 = size / 2 + (Math.sin(angle) * size) / 2;
|
||||
|
||||
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
||||
gradient.addColorStop(0, gradientStartColor);
|
||||
gradient.addColorStop(1, gradientEndColor);
|
||||
return gradient;
|
||||
} else if (backgroundType === 'radial-gradient') {
|
||||
const gradient = ctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
|
||||
gradient.addColorStop(0, gradientStartColor);
|
||||
gradient.addColorStop(1, gradientEndColor);
|
||||
return gradient;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// 独立的绘制函数
|
||||
const drawIconToContext = React.useCallback(async (ctx: CanvasRenderingContext2D, size: number) => {
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
|
||||
// 绘制背景
|
||||
const padding = (size * 5) / 200;
|
||||
const backgroundSize = size - padding * 2;
|
||||
|
||||
// 设置背景颜色或渐变
|
||||
const gradient = createGradient(ctx, size);
|
||||
ctx.fillStyle = gradient || backgroundColor;
|
||||
|
||||
switch (shape) {
|
||||
case 'circle':
|
||||
ctx.beginPath();
|
||||
ctx.arc(size / 2, size / 2, backgroundSize / 2, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
break;
|
||||
case 'square':
|
||||
ctx.fillRect(padding, padding, backgroundSize, backgroundSize);
|
||||
break;
|
||||
case 'rounded-square':
|
||||
ctx.beginPath();
|
||||
const radius = backgroundSize * 0.15;
|
||||
ctx.roundRect(padding, padding, backgroundSize, backgroundSize, radius);
|
||||
ctx.fill();
|
||||
break;
|
||||
case 'hexagon':
|
||||
const centerX = size / 2;
|
||||
const centerY = size / 2;
|
||||
const hexRadius = backgroundSize / 2;
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const angle = (i * Math.PI) / 3;
|
||||
const x = centerX + hexRadius * Math.cos(angle);
|
||||
const y = centerY + hexRadius * Math.sin(angle);
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
break;
|
||||
}
|
||||
|
||||
// 绘制图标或文字
|
||||
const iconDrawSize = (size * iconSize) / 100;
|
||||
const iconX = size / 2;
|
||||
const iconY = size / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(iconX, iconY);
|
||||
ctx.rotate((iconRotation * Math.PI) / 180);
|
||||
|
||||
if (iconType === 'text' && customText) {
|
||||
// 绘制自定义文字
|
||||
ctx.fillStyle = iconColor;
|
||||
ctx.font = `${fontWeight} ${iconDrawSize}px ${fontFamily}`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(customText, 0, 0);
|
||||
} else if (iconType === 'fontawesome' && icon) {
|
||||
// 绘制FontAwesome图标
|
||||
try {
|
||||
const svgString = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 ${icon.icon[0]} ${icon.icon[1]}">
|
||||
<path fill="${iconColor}" d="${icon.icon[4]}"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const svgUrl = URL.createObjectURL(svgBlob);
|
||||
const img = new Image();
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, -iconDrawSize / 2, -iconDrawSize / 2, iconDrawSize, iconDrawSize);
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
// 降级方案:绘制一个圆点
|
||||
ctx.fillStyle = iconColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, iconDrawSize / 4, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.src = svgUrl;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('绘制图标失败:', error);
|
||||
// 降级方案
|
||||
ctx.fillStyle = iconColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, iconDrawSize / 4, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}, [iconType, icon, customText, iconColor, iconSize, iconRotation, fontFamily, fontWeight, backgroundType, backgroundColor, gradientStartColor, gradientEndColor, gradientDirection, shape]);
|
||||
|
||||
// 绘制到当前画布
|
||||
const drawIcon = React.useCallback(async () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
canvas.width = canvasSize;
|
||||
canvas.height = canvasSize;
|
||||
|
||||
await drawIconToContext(ctx, canvasSize);
|
||||
}, [drawIconToContext, canvasSize]);
|
||||
|
||||
// 生成指定尺寸的图标
|
||||
const generateIcon = React.useCallback(async (size: number): Promise<string> => {
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
|
||||
if (!tempCtx) {
|
||||
throw new Error('无法创建画布');
|
||||
}
|
||||
|
||||
tempCanvas.width = size;
|
||||
tempCanvas.height = size;
|
||||
|
||||
tempCtx.imageSmoothingEnabled = true;
|
||||
tempCtx.imageSmoothingQuality = 'high';
|
||||
|
||||
await drawIconToContext(tempCtx, size);
|
||||
|
||||
return tempCanvas.toDataURL('image/png');
|
||||
}, [drawIconToContext]);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
getCanvas: () => canvasRef.current,
|
||||
generateIcon
|
||||
}), [generateIcon]);
|
||||
|
||||
// 当参数变化时重新绘制
|
||||
useEffect(() => {
|
||||
drawIcon().catch(console.error);
|
||||
}, [drawIcon]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="rounded-lg shadow-lg border border-purple-glow/20"
|
||||
style={{
|
||||
maxWidth: `${canvasSize}px`,
|
||||
maxHeight: `${canvasSize}px`,
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
EnhancedIconCanvas.displayName = 'EnhancedIconCanvas';
|
||||
|
||||
export default EnhancedIconCanvas;
|
||||
@@ -0,0 +1,177 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export type ShapeType = 'circle' | 'square' | 'rounded-square' | 'hexagon';
|
||||
export type BackgroundType = 'solid' | 'linear-gradient' | 'radial-gradient';
|
||||
export type IconType = 'fontawesome' | 'text';
|
||||
|
||||
interface EnhancedIconPreviewProps {
|
||||
// 图标相关
|
||||
iconType: IconType;
|
||||
icon?: IconDefinition;
|
||||
customText?: string;
|
||||
iconColor: string;
|
||||
iconSize: number;
|
||||
iconRotation: number;
|
||||
fontFamily?: string;
|
||||
fontWeight?: string;
|
||||
|
||||
// 背景相关
|
||||
backgroundType: BackgroundType;
|
||||
backgroundColor: string;
|
||||
gradientStartColor?: string;
|
||||
gradientEndColor?: string;
|
||||
gradientDirection?: number; // 角度
|
||||
shape: ShapeType;
|
||||
|
||||
// 其他
|
||||
previewSize?: number;
|
||||
}
|
||||
|
||||
export default function EnhancedIconPreview({
|
||||
iconType,
|
||||
icon,
|
||||
customText,
|
||||
iconColor,
|
||||
iconSize,
|
||||
iconRotation,
|
||||
fontFamily = 'Arial, sans-serif',
|
||||
fontWeight = 'normal',
|
||||
backgroundType,
|
||||
backgroundColor,
|
||||
gradientStartColor,
|
||||
gradientEndColor,
|
||||
gradientDirection = 45,
|
||||
shape,
|
||||
previewSize = 200
|
||||
}: EnhancedIconPreviewProps) {
|
||||
|
||||
// 获取背景样式
|
||||
const getBackgroundStyle = () => {
|
||||
switch (backgroundType) {
|
||||
case 'linear-gradient':
|
||||
if (gradientStartColor && gradientEndColor) {
|
||||
return {
|
||||
background: `linear-gradient(${gradientDirection}deg, ${gradientStartColor}, ${gradientEndColor})`
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'radial-gradient':
|
||||
if (gradientStartColor && gradientEndColor) {
|
||||
return {
|
||||
background: `radial-gradient(circle, ${gradientStartColor}, ${gradientEndColor})`
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'solid':
|
||||
default:
|
||||
return {
|
||||
backgroundColor: backgroundColor
|
||||
};
|
||||
}
|
||||
return { backgroundColor: backgroundColor };
|
||||
};
|
||||
|
||||
// 根据形状生成CSS类和额外样式
|
||||
const getShapeStyle = () => {
|
||||
const baseStyle = {
|
||||
width: `${previewSize}px`,
|
||||
height: `${previewSize}px`,
|
||||
position: 'relative' as const,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
...getBackgroundStyle()
|
||||
};
|
||||
|
||||
switch (shape) {
|
||||
case 'circle':
|
||||
return {
|
||||
...baseStyle,
|
||||
borderRadius: '50%'
|
||||
};
|
||||
case 'square':
|
||||
return baseStyle;
|
||||
case 'rounded-square':
|
||||
return {
|
||||
...baseStyle,
|
||||
borderRadius: `${previewSize * 0.15}px`
|
||||
};
|
||||
case 'hexagon':
|
||||
return {
|
||||
...baseStyle,
|
||||
clipPath: 'polygon(25% 6.7%, 75% 6.7%, 100% 50%, 75% 93.3%, 25% 93.3%, 0% 50%)'
|
||||
};
|
||||
default:
|
||||
return baseStyle;
|
||||
}
|
||||
};
|
||||
|
||||
// 计算图标/文字大小
|
||||
const calculateIconSize = () => {
|
||||
return (previewSize * iconSize) / 100;
|
||||
};
|
||||
|
||||
// 渲染图标内容
|
||||
const renderIconContent = () => {
|
||||
const iconStyle = {
|
||||
color: iconColor,
|
||||
fontSize: `${calculateIconSize()}px`,
|
||||
transform: `rotate(${iconRotation}deg)`,
|
||||
transition: 'all 0.3s ease',
|
||||
userSelect: 'none' as const
|
||||
};
|
||||
|
||||
if (iconType === 'text' && customText) {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
...iconStyle,
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
lineHeight: 1,
|
||||
textAlign: 'center' as const
|
||||
}}
|
||||
>
|
||||
{customText}
|
||||
</span>
|
||||
);
|
||||
} else if (iconType === 'fontawesome' && icon) {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
style={iconStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<div style={getShapeStyle()}>
|
||||
{renderIconContent()}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<p className="text-sm text-secondary">
|
||||
{iconType === 'text'
|
||||
? `"${customText || 'Text'}" • ${shape} • ${iconSize}% • ${iconRotation}°`
|
||||
: `${icon?.iconName || 'Icon'} • ${shape} • ${iconSize}% • ${iconRotation}°`
|
||||
}
|
||||
</p>
|
||||
<p className="text-xs text-tertiary mt-1">
|
||||
{backgroundType === 'solid'
|
||||
? `背景: ${backgroundColor}`
|
||||
: `背景: ${backgroundType} (${gradientStartColor} → ${gradientEndColor})`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
// 添加所有FontAwesome图标到库中
|
||||
library.add(fas);
|
||||
|
||||
export type ShapeType = 'circle' | 'square' | 'rounded-square' | 'hexagon';
|
||||
|
||||
interface IconCanvasProps {
|
||||
icon: IconDefinition;
|
||||
backgroundColor: string;
|
||||
iconColor: string;
|
||||
shape: ShapeType;
|
||||
iconSize: number;
|
||||
canvasSize?: number;
|
||||
}
|
||||
|
||||
export interface IconCanvasRef {
|
||||
getCanvas: () => HTMLCanvasElement | null;
|
||||
generateIcon: (size: number) => Promise<string>;
|
||||
}
|
||||
|
||||
const IconCanvas = forwardRef<IconCanvasRef, IconCanvasProps>(({
|
||||
icon,
|
||||
backgroundColor,
|
||||
iconColor,
|
||||
shape,
|
||||
iconSize,
|
||||
canvasSize = 200
|
||||
}, ref) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
// 独立的绘制函数,可以绘制到任意context和尺寸
|
||||
const drawIconToContext = React.useCallback(async (ctx: CanvasRenderingContext2D, size: number) => {
|
||||
// 启用抗锯齿设置
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
|
||||
// 绘制背景
|
||||
ctx.fillStyle = backgroundColor;
|
||||
|
||||
const padding = (size * 5) / 200; // 按比例调整padding
|
||||
const backgroundSize = size - padding * 2;
|
||||
|
||||
switch (shape) {
|
||||
case 'circle':
|
||||
ctx.beginPath();
|
||||
ctx.arc(size / 2, size / 2, backgroundSize / 2, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
break;
|
||||
case 'square':
|
||||
ctx.fillRect(padding, padding, backgroundSize, backgroundSize);
|
||||
break;
|
||||
case 'rounded-square':
|
||||
ctx.beginPath();
|
||||
const radius = backgroundSize * 0.15; // 15% 圆角
|
||||
ctx.roundRect(padding, padding, backgroundSize, backgroundSize, radius);
|
||||
ctx.fill();
|
||||
break;
|
||||
case 'hexagon':
|
||||
const centerX = size / 2;
|
||||
const centerY = size / 2;
|
||||
const hexRadius = backgroundSize / 2;
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const angle = (i * Math.PI) / 3;
|
||||
const x = centerX + hexRadius * Math.cos(angle);
|
||||
const y = centerY + hexRadius * Math.sin(angle);
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
break;
|
||||
}
|
||||
|
||||
// 绘制FontAwesome图标
|
||||
try {
|
||||
// 创建SVG字符串
|
||||
const svgString = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 ${icon.icon[0]} ${icon.icon[1]}">
|
||||
<path fill="${iconColor}" d="${icon.icon[4]}"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// 将SVG转换为blob URL
|
||||
const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const svgUrl = URL.createObjectURL(svgBlob);
|
||||
|
||||
// 创建图片对象
|
||||
const img = new Image();
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
img.onload = () => {
|
||||
// 计算图标绘制位置和大小
|
||||
const iconDrawSize = (size * iconSize) / 100;
|
||||
const iconX = (size - iconDrawSize) / 2;
|
||||
const iconY = (size - iconDrawSize) / 2;
|
||||
|
||||
// 绘制图标
|
||||
ctx.drawImage(img, iconX, iconY, iconDrawSize, iconDrawSize);
|
||||
|
||||
// 清理URL
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
// 如果SVG加载失败,使用降级方案
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
|
||||
// 降级方案:使用文字符号
|
||||
ctx.fillStyle = iconColor;
|
||||
const fontSize = (size * iconSize) / 300; // 调整字体大小
|
||||
ctx.font = `${fontSize}px Arial`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// 使用emoji符号作为降级
|
||||
const fallbackSymbols: Record<string, string> = {
|
||||
'heart': '♥',
|
||||
'star': '★',
|
||||
'home': '⌂',
|
||||
'user': '👤',
|
||||
'envelope': '✉',
|
||||
'phone': '📞',
|
||||
'shopping-cart': '🛒',
|
||||
'play': '▶',
|
||||
'music': '♪',
|
||||
'camera': '📷',
|
||||
'gift': '🎁',
|
||||
'check': '✓',
|
||||
'bookmark': '🔖',
|
||||
'coffee': '☕',
|
||||
};
|
||||
|
||||
const symbol = fallbackSymbols[icon.iconName] || '●';
|
||||
ctx.fillText(symbol, size / 2, size / 2);
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.src = svgUrl;
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('绘制图标失败:', error);
|
||||
|
||||
// 最终降级方案:绘制一个简单的圆点
|
||||
ctx.fillStyle = iconColor;
|
||||
ctx.beginPath();
|
||||
const dotRadius = (size * iconSize) / 400;
|
||||
ctx.arc(size / 2, size / 2, dotRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
}, [icon, backgroundColor, iconColor, shape, iconSize]);
|
||||
|
||||
// 绘制图标到画布
|
||||
const drawIcon = React.useCallback(async () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
canvas.width = canvasSize;
|
||||
canvas.height = canvasSize;
|
||||
|
||||
await drawIconToContext(ctx, canvasSize);
|
||||
}, [drawIconToContext, canvasSize]);
|
||||
|
||||
// 生成指定尺寸的图标
|
||||
const generateIcon = React.useCallback(async (size: number): Promise<string> => {
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
|
||||
if (!tempCtx || !canvasRef.current) {
|
||||
throw new Error('无法创建画布');
|
||||
}
|
||||
|
||||
tempCanvas.width = size;
|
||||
tempCanvas.height = size;
|
||||
|
||||
// 启用高质量抗锯齿
|
||||
tempCtx.imageSmoothingEnabled = true;
|
||||
tempCtx.imageSmoothingQuality = 'high';
|
||||
|
||||
// 始终重新绘制以获得最佳质量
|
||||
await drawIconToContext(tempCtx, size);
|
||||
|
||||
return tempCanvas.toDataURL('image/png');
|
||||
}, [canvasSize, drawIconToContext]);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
getCanvas: () => canvasRef.current,
|
||||
generateIcon
|
||||
}), [generateIcon]);
|
||||
|
||||
// 当参数变化时重新绘制
|
||||
useEffect(() => {
|
||||
drawIcon().catch(console.error);
|
||||
}, [drawIcon]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="rounded-lg shadow-lg border border-purple-glow/20"
|
||||
style={{
|
||||
maxWidth: `${canvasSize}px`,
|
||||
maxHeight: `${canvasSize}px`,
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
IconCanvas.displayName = 'IconCanvas';
|
||||
|
||||
export default IconCanvas;
|
||||
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export type ShapeType = 'circle' | 'square' | 'rounded-square' | 'hexagon';
|
||||
|
||||
interface IconPreviewProps {
|
||||
icon: IconDefinition;
|
||||
backgroundColor: string;
|
||||
iconColor: string;
|
||||
shape: ShapeType;
|
||||
iconSize: number;
|
||||
previewSize?: number;
|
||||
}
|
||||
|
||||
export default function IconPreview({
|
||||
icon,
|
||||
backgroundColor,
|
||||
iconColor,
|
||||
shape,
|
||||
iconSize,
|
||||
previewSize = 200
|
||||
}: IconPreviewProps) {
|
||||
|
||||
// 根据形状生成CSS类
|
||||
const getShapeClass = () => {
|
||||
switch (shape) {
|
||||
case 'circle':
|
||||
return 'rounded-full';
|
||||
case 'square':
|
||||
return '';
|
||||
case 'rounded-square':
|
||||
return 'rounded-xl';
|
||||
case 'hexagon':
|
||||
return 'rounded-lg transform rotate-45'; // 简化为圆角,真正的六边形需要clip-path
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 计算图标大小
|
||||
const calculateIconSize = () => {
|
||||
return (previewSize * iconSize) / 100;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<div
|
||||
className={`relative flex items-center justify-center ${getShapeClass()}`}
|
||||
style={{
|
||||
width: `${previewSize}px`,
|
||||
height: `${previewSize}px`,
|
||||
backgroundColor: backgroundColor,
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
style={{
|
||||
color: iconColor,
|
||||
fontSize: `${calculateIconSize()}px`,
|
||||
}}
|
||||
className={shape === 'hexagon' ? 'transform -rotate-45' : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<p className="text-sm text-secondary">
|
||||
{icon.iconName} • {shape} • {iconSize}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
// 常用图标
|
||||
faHeart, faStar, faHome, faUser, faEnvelope, faPhone, faShoppingCart, faPlay,
|
||||
faMusic, faCamera, faGift, faCheck, faBookmark, faCoffee, faGamepad, faBell,
|
||||
faSearch, faDownload, faUpload, faShare, faCog, faThumbsUp, faThumbsDown,
|
||||
faFire, faEye, faEyeSlash, faVolumeMute, faVolumeUp, faMicrophone, faMicrophoneSlash,
|
||||
|
||||
// 商务图标
|
||||
faChartBar, faFile, faFolder, faCogs, faWrench, faRocket, faLightbulb,
|
||||
faDatabase, faCode, faBug, faEdit, faCopy, faTrash, faSave, faPrint,
|
||||
faBriefcase, faBuilding, faIndustry, faHandshake, faMoneyBill, faCreditCard,
|
||||
faChartLine, faChartPie, faCalculator, faClipboard, faFileAlt, faFilePdf,
|
||||
faFileWord, faFileExcel, faFilePowerpoint, faFileImage, faFileVideo, faFileAudio,
|
||||
|
||||
// 科技图标
|
||||
faCloud, faServer, faLaptop, faDesktop, faMobile, faTablet,
|
||||
faShieldAlt, faLock, faKey, faGlobe, faWifi,
|
||||
faHdd, faSdCard, faMemory, faBatteryFull, faBatteryHalf, faBatteryEmpty,
|
||||
faPlug, faPowerOff, faSignal, faRss, faQrcode,
|
||||
|
||||
// 社交图标
|
||||
faUsers, faUserFriends, faUserPlus, faUserMinus, faComments, faComment,
|
||||
faCommentDots, faReply, faRetweet, faHashtag, faAt, faQuoteLeft, faQuoteRight,
|
||||
faPaperPlane, faInbox, faEnvelopeOpen, faEnvelopeOpenText, faAddressBook,
|
||||
faIdCard, faIdBadge, faUserTag, faUserCheck, faUserTimes,
|
||||
|
||||
// 界面图标
|
||||
faPlus, faMinus, faTimes, faArrowRight, faArrowLeft, faArrowUp, faArrowDown,
|
||||
faSync, faRedo, faUndo, faRefresh, faExpand, faCompress, faMaximize, faMinimize,
|
||||
faAngleUp, faAngleDown, faAngleLeft, faAngleRight, faChevronUp, faChevronDown,
|
||||
faChevronLeft, faChevronRight, faCaretUp, faCaretDown, faCaretLeft, faCaretRight,
|
||||
faSort, faSortUp, faSortDown, faFilter, faBars, faEllipsisH, faEllipsisV,
|
||||
faGripHorizontal, faGripVertical, faGripLines, faGripLinesVertical,
|
||||
|
||||
// 媒体图标
|
||||
faPause, faStop, faStepForward, faStepBackward, faFastForward, faFastBackward,
|
||||
faRandom, faRepeat, faVolumeDown, faVolumeOff, faHeadphones,
|
||||
faVideo, faVideoSlash, faImage, faImages, faPhotoVideo, faFilm,
|
||||
faCameraRetro, faRecordVinyl, faCompactDisc, faTv, faRadio, faPodcast,
|
||||
|
||||
// 交通出行
|
||||
faCar, faTruck, faBus, faTaxi, faMotorcycle, faBicycle, faPlane, faTrain,
|
||||
faShip, faSubway, faWalking, faRunning, faMapMarkerAlt, faMap, faRoute,
|
||||
faCompass, faLocationArrow, faStreetView, faRoad, faParking, faGasPump,
|
||||
|
||||
// 购物电商
|
||||
faShoppingBag, faShoppingBasket, faStoreAlt, faStore, faReceipt, faBarcode,
|
||||
faTags, faPercent, faGem, faCrown, faAward, faMedal, faTrophy, faRibbon,
|
||||
faGifts, faBox, faBoxOpen, faBoxes, faWarehouse, faShippingFast,
|
||||
|
||||
// 健康医疗
|
||||
faHeart as faHeartSolid, faHeartbeat, faStethoscope, faUserMd, faHospital,
|
||||
faAmbulance, faPills, faSyringe, faThermometer, faBandAid, faFirstAid,
|
||||
faDna, faMicroscope, faXRay, faTeeth, faEye as faEyeMedical, faBrain,
|
||||
|
||||
// 食物饮料
|
||||
faUtensils, faUtensilSpoon, faCocktail, faWineGlass,
|
||||
faBeer, faPizzaSlice, faHamburger, faHotdog, faIceCream, faCake,
|
||||
faAppleAlt, faCarrot, faCheese, faFish, faEgg, faBacon, faBreadSlice,
|
||||
|
||||
// 运动休闲
|
||||
faFutbol, faBasketballBall, faBaseballBall, faFootballBall, faVolleyballBall,
|
||||
faTableTennis, faGolfBall, faBowlingBall, faHockeyPuck, faDumbbell,
|
||||
faSwimmer, faSkiing, faBiking, faHiking, faCampground,
|
||||
|
||||
// 天气自然
|
||||
faSun, faMoon, faCloudSun, faCloudMoon, faCloudRain, faCloudShowersHeavy,
|
||||
faSnowflake, faBolt, faWind, faTemperatureHigh, faTemperatureLow,
|
||||
faTree, faLeaf, faSeedling, faMountain, faWater, faFire as faFireWeather,
|
||||
|
||||
// 时间日期
|
||||
faCalendar, faCalendarAlt, faCalendarCheck, faCalendarTimes, faCalendarPlus,
|
||||
faClock, faStopwatch, faHourglass, faHourglassHalf, faHistory,
|
||||
faBusinessTime, faCalendarWeek, faCalendarDay,
|
||||
|
||||
// 安全保护
|
||||
faShield, faShieldVirus, faUserShield, faLockOpen,
|
||||
faUnlock, faKeyboard, faFingerprint, faEyeDropper, faMask, faHardHat,
|
||||
faLifeRing, faExclamationTriangle, faInfoCircle, faQuestionCircle,
|
||||
|
||||
// 文件格式
|
||||
faFileCode, faFileArchive, faFileContract, faFileInvoice, faFileSignature,
|
||||
faFileDownload, faFileUpload, faFileImport, faFileExport, faFileCsv, faFileText
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
|
||||
// 图标数据定义
|
||||
interface IconCategory {
|
||||
key: string;
|
||||
name: string;
|
||||
icons: IconDefinition[];
|
||||
}
|
||||
|
||||
const iconCategories: IconCategory[] = [
|
||||
{
|
||||
key: 'popular',
|
||||
name: 'popular_icons',
|
||||
icons: [
|
||||
faHeart, faStar, faHome, faUser, faEnvelope, faPhone, faShoppingCart, faPlay,
|
||||
faMusic, faCamera, faGift, faCheck, faBookmark, faCoffee, faGamepad, faBell,
|
||||
faSearch, faDownload, faUpload, faShare, faCog, faThumbsUp, faThumbsDown,
|
||||
faFire, faEye, faEyeSlash, faVolumeMute, faVolumeUp, faMicrophone, faMicrophoneSlash
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
name: 'business_icons',
|
||||
icons: [
|
||||
faChartBar, faFile, faFolder, faCogs, faWrench, faRocket, faLightbulb,
|
||||
faDatabase, faCode, faBug, faEdit, faCopy, faTrash, faSave, faPrint,
|
||||
faBriefcase, faBuilding, faIndustry, faHandshake, faMoneyBill, faCreditCard,
|
||||
faChartLine, faChartPie, faCalculator, faClipboard, faFileAlt, faFilePdf,
|
||||
faFileWord, faFileExcel, faFilePowerpoint, faFileImage, faFileVideo, faFileAudio
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'tech',
|
||||
name: 'tech_icons',
|
||||
icons: [
|
||||
faCloud, faDatabase, faServer, faLaptop, faDesktop, faMobile, faTablet,
|
||||
faShieldAlt, faLock, faKey, faGlobe, faWifi,
|
||||
faHdd, faSdCard, faMemory, faBatteryFull, faBatteryHalf, faBatteryEmpty,
|
||||
faPlug, faPowerOff, faSignal, faRss, faQrcode,
|
||||
faCode, faBug, faWrench, faDownload, faUpload
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'social',
|
||||
name: 'social_icons',
|
||||
icons: [
|
||||
faUsers, faUserFriends, faUserPlus, faUserMinus, faComments, faComment,
|
||||
faCommentDots, faReply, faRetweet, faHashtag, faAt, faQuoteLeft, faQuoteRight,
|
||||
faPaperPlane, faInbox, faEnvelopeOpen, faEnvelopeOpenText, faAddressBook,
|
||||
faIdCard, faIdBadge, faUserTag, faUserCheck, faUserTimes,
|
||||
faUser, faEnvelope, faPhone, faHeart, faShare, faBell
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'ui',
|
||||
name: 'ui_icons',
|
||||
icons: [
|
||||
faPlus, faMinus, faTimes, faArrowRight, faArrowLeft, faArrowUp, faArrowDown,
|
||||
faSync, faRedo, faUndo, faRefresh, faExpand, faCompress, faMaximize, faMinimize,
|
||||
faAngleUp, faAngleDown, faAngleLeft, faAngleRight, faChevronUp, faChevronDown,
|
||||
faChevronLeft, faChevronRight, faCaretUp, faCaretDown, faCaretLeft, faCaretRight,
|
||||
faSort, faSortUp, faSortDown, faFilter, faBars, faEllipsisH, faEllipsisV,
|
||||
faGripHorizontal, faGripVertical, faGripLines, faGripLinesVertical,
|
||||
faSearch, faCog, faCheck
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'media',
|
||||
name: 'media_icons',
|
||||
icons: [
|
||||
faPlay, faPause, faStop, faStepForward, faStepBackward, faFastForward, faFastBackward,
|
||||
faRandom, faRepeat, faVolumeDown, faVolumeOff, faHeadphones,
|
||||
faVideo, faVideoSlash, faImage, faImages, faPhotoVideo, faFilm,
|
||||
faCameraRetro, faRecordVinyl, faCompactDisc, faTv, faRadio, faPodcast,
|
||||
faMusic, faCamera, faVolumeMute, faVolumeUp, faMicrophone, faMicrophoneSlash
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'transport',
|
||||
name: 'transport_icons',
|
||||
icons: [
|
||||
faCar, faTruck, faBus, faTaxi, faMotorcycle, faBicycle, faPlane, faTrain,
|
||||
faShip, faSubway, faWalking, faRunning, faMapMarkerAlt, faMap, faRoute,
|
||||
faCompass, faLocationArrow, faStreetView, faRoad, faParking, faGasPump
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'shopping',
|
||||
name: 'shopping_icons',
|
||||
icons: [
|
||||
faShoppingCart, faShoppingBag, faShoppingBasket, faStoreAlt, faStore, faReceipt, faBarcode,
|
||||
faTags, faPercent, faGem, faCrown, faAward, faMedal, faTrophy, faRibbon,
|
||||
faGifts, faBox, faBoxOpen, faBoxes, faWarehouse, faShippingFast,
|
||||
faMoneyBill, faCreditCard, faGift
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'health',
|
||||
name: 'health_icons',
|
||||
icons: [
|
||||
faHeartSolid, faHeartbeat, faStethoscope, faUserMd, faHospital,
|
||||
faAmbulance, faPills, faSyringe, faThermometer, faBandAid, faFirstAid,
|
||||
faDna, faMicroscope, faXRay, faTeeth, faEyeMedical, faBrain
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'food',
|
||||
name: 'food_icons',
|
||||
icons: [
|
||||
faUtensils, faUtensilSpoon, faCocktail, faWineGlass,
|
||||
faBeer, faPizzaSlice, faHamburger, faHotdog, faIceCream, faCake,
|
||||
faAppleAlt, faCarrot, faCheese, faFish, faEgg, faBacon, faBreadSlice, faCoffee
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'sports',
|
||||
name: 'sports_icons',
|
||||
icons: [
|
||||
faFutbol, faBasketballBall, faBaseballBall, faFootballBall, faVolleyballBall,
|
||||
faTableTennis, faGolfBall, faBowlingBall, faHockeyPuck, faDumbbell,
|
||||
faSwimmer, faSkiing, faBiking, faHiking, faCampground
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'weather',
|
||||
name: 'weather_icons',
|
||||
icons: [
|
||||
faSun, faMoon, faCloudSun, faCloudMoon, faCloudRain, faCloudShowersHeavy,
|
||||
faSnowflake, faBolt, faWind, faTemperatureHigh, faTemperatureLow,
|
||||
faTree, faLeaf, faSeedling, faMountain, faWater, faFireWeather, faCloud
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
name: 'time_icons',
|
||||
icons: [
|
||||
faCalendar, faCalendarAlt, faCalendarCheck, faCalendarTimes, faCalendarPlus,
|
||||
faClock, faStopwatch, faHourglass, faHourglassHalf, faHistory,
|
||||
faBusinessTime, faCalendarWeek, faCalendarDay
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'security',
|
||||
name: 'security_icons',
|
||||
icons: [
|
||||
faShield, faShieldVirus, faUserShield, faLockOpen,
|
||||
faUnlock, faKeyboard, faFingerprint, faEyeDropper, faMask, faHardHat,
|
||||
faLifeRing, faExclamationTriangle, faInfoCircle, faQuestionCircle,
|
||||
faShieldAlt, faLock, faKey
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'files',
|
||||
name: 'files_icons',
|
||||
icons: [
|
||||
faFileCode, faFileArchive, faFileContract, faFileInvoice, faFileSignature,
|
||||
faFileDownload, faFileUpload, faFileImport, faFileExport, faFileCsv, faFileText,
|
||||
faFile, faFolder, faFileAlt, faFilePdf, faFileWord, faFileExcel,
|
||||
faFilePowerpoint, faFileImage, faFileVideo, faFileAudio
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 为图标创建搜索关键词映射
|
||||
const iconKeywords: Record<string, string[]> = {
|
||||
[faHeart.iconName]: ['heart', 'love', 'like', '心', '爱心', '喜欢'],
|
||||
[faStar.iconName]: ['star', 'favorite', 'rating', '星', '收藏', '评分'],
|
||||
[faHome.iconName]: ['home', 'house', '家', '首页'],
|
||||
[faUser.iconName]: ['user', 'person', 'profile', '用户', '人', '个人资料'],
|
||||
[faEnvelope.iconName]: ['mail', 'email', 'message', '邮件', '消息'],
|
||||
[faPhone.iconName]: ['phone', 'call', 'contact', '电话', '联系'],
|
||||
[faShoppingCart.iconName]: ['cart', 'shop', 'buy', '购物车', '商店', '购买'],
|
||||
[faPlay.iconName]: ['play', 'start', 'video', '播放', '开始', '视频'],
|
||||
[faMusic.iconName]: ['music', 'audio', 'sound', '音乐', '音频', '声音'],
|
||||
[faCamera.iconName]: ['camera', 'photo', 'picture', '相机', '照片', '图片'],
|
||||
[faGift.iconName]: ['gift', 'present', 'reward', '礼物', '奖励'],
|
||||
[faCheck.iconName]: ['check', 'ok', 'done', '检查', '确认', '完成'],
|
||||
[faBookmark.iconName]: ['bookmark', 'save', 'mark', '书签', '保存', '标记'],
|
||||
[faCoffee.iconName]: ['coffee', 'drink', 'cafe', '咖啡', '饮料'],
|
||||
[faGamepad.iconName]: ['game', 'play', 'gaming', '游戏', '娱乐'],
|
||||
[faChartBar.iconName]: ['chart', 'graph', 'analytics', '图表', '分析'],
|
||||
[faFile.iconName]: ['file', 'document', 'paper', '文件', '文档'],
|
||||
[faFolder.iconName]: ['folder', 'directory', '文件夹', '目录'],
|
||||
[faCogs.iconName]: ['settings', 'config', 'gear', '设置', '配置'],
|
||||
[faWrench.iconName]: ['tool', 'fix', 'repair', '工具', '修复'],
|
||||
[faRocket.iconName]: ['rocket', 'fast', 'launch', '火箭', '快速', '启动'],
|
||||
[faLightbulb.iconName]: ['idea', 'light', 'innovation', '想法', '创新'],
|
||||
[faDatabase.iconName]: ['database', 'data', 'storage', '数据库', '数据'],
|
||||
[faCode.iconName]: ['code', 'programming', 'developer', '代码', '编程'],
|
||||
[faBug.iconName]: ['bug', 'error', 'debug', '错误', '调试'],
|
||||
[faEdit.iconName]: ['edit', 'write', 'modify', '编辑', '修改'],
|
||||
[faCopy.iconName]: ['copy', 'duplicate', '复制'],
|
||||
[faTrash.iconName]: ['delete', 'remove', 'trash', '删除', '垃圾桶'],
|
||||
[faCloud.iconName]: ['cloud', 'online', 'storage', '云', '在线'],
|
||||
[faShieldAlt.iconName]: ['security', 'protect', 'safe', '安全', '保护'],
|
||||
[faLock.iconName]: ['lock', 'secure', 'private', '锁', '安全', '私有'],
|
||||
[faKey.iconName]: ['key', 'password', 'access', '钥匙', '密码', '访问'],
|
||||
[faGlobe.iconName]: ['world', 'global', 'internet', '世界', '全球', '网络'],
|
||||
[faWifi.iconName]: ['wifi', 'wireless', 'internet', '无线网络'],
|
||||
[faShare.iconName]: ['share', 'send', 'forward', '分享', '发送'],
|
||||
[faDownload.iconName]: ['download', 'save', '下载', '保存'],
|
||||
[faUpload.iconName]: ['upload', 'send', '上传', '发送'],
|
||||
[faBell.iconName]: ['notification', 'alert', 'bell', '通知', '提醒'],
|
||||
[faSearch.iconName]: ['search', 'find', 'look', '搜索', '查找'],
|
||||
[faCog.iconName]: ['setting', 'config', 'gear', '设置', '配置'],
|
||||
[faPlus.iconName]: ['add', 'plus', 'new', '添加', '新增'],
|
||||
[faMinus.iconName]: ['minus', 'remove', 'subtract', '减少', '删除'],
|
||||
[faTimes.iconName]: ['close', 'cancel', 'exit', '关闭', '取消'],
|
||||
[faSync.iconName]: ['refresh', 'reload', 'sync', '刷新', '同步'],
|
||||
[faSave.iconName]: ['save', 'store', 'keep', '保存', '存储'],
|
||||
[faPrint.iconName]: ['print', 'printer', '打印'],
|
||||
[faCalendar.iconName]: ['calendar', 'date', 'schedule', '日历', '日期'],
|
||||
[faClock.iconName]: ['time', 'clock', 'schedule', '时间', '时钟'],
|
||||
[faMapMarkerAlt.iconName]: ['location', 'place', 'map', '位置', '地点']
|
||||
};
|
||||
|
||||
interface IconSelectorProps {
|
||||
selectedIcon: IconDefinition;
|
||||
onIconSelect: (icon: IconDefinition) => void;
|
||||
}
|
||||
|
||||
export default function IconSelector({ selectedIcon, onIconSelect }: IconSelectorProps) {
|
||||
const { t } = useLanguage();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [activeCategory, setActiveCategory] = useState('popular');
|
||||
|
||||
// 搜索过滤图标
|
||||
const filteredCategories = useMemo(() => {
|
||||
if (!searchTerm.trim()) {
|
||||
return iconCategories;
|
||||
}
|
||||
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
|
||||
return iconCategories.map(category => ({
|
||||
...category,
|
||||
icons: category.icons.filter(icon => {
|
||||
const keywords = iconKeywords[icon.iconName] || [];
|
||||
return keywords.some(keyword =>
|
||||
keyword.toLowerCase().includes(searchLower)
|
||||
) || icon.iconName.toLowerCase().includes(searchLower);
|
||||
})
|
||||
})).filter(category => category.icons.length > 0);
|
||||
}, [searchTerm]);
|
||||
|
||||
const styles = {
|
||||
container: "space-y-4",
|
||||
searchInput: "search-input w-full",
|
||||
categoryTabs: "flex flex-wrap gap-2 mb-4",
|
||||
categoryTab: "px-3 py-1 text-sm rounded-md cursor-pointer transition-colors",
|
||||
categoryTabActive: "px-3 py-1 text-sm rounded-md cursor-pointer transition-colors bg-purple-600 text-white",
|
||||
categoryTabInactive: "px-3 py-1 text-sm rounded-md cursor-pointer transition-colors bg-gray-700 text-gray-300 hover:bg-gray-600",
|
||||
iconGrid: "grid grid-cols-6 sm:grid-cols-8 md:grid-cols-10 gap-2",
|
||||
iconButton: "btn-option p-3 flex items-center justify-center text-lg hover:scale-110 transition-transform",
|
||||
iconButtonActive: "btn-option-active p-3 flex items-center justify-center text-lg scale-110",
|
||||
categorySection: "mb-6",
|
||||
categoryTitle: "text-sm font-medium mb-3 text-secondary",
|
||||
noResults: "text-center text-gray-500 py-8"
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('tools.icon_designer.icon_search_placeholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className={styles.searchInput}
|
||||
/>
|
||||
|
||||
{!searchTerm && (
|
||||
<div className={styles.categoryTabs}>
|
||||
{iconCategories.map((category) => (
|
||||
<button
|
||||
key={category.key}
|
||||
className={activeCategory === category.key ? styles.categoryTabActive : styles.categoryTabInactive}
|
||||
onClick={() => setActiveCategory(category.key)}
|
||||
>
|
||||
{t(`tools.icon_designer.${category.name}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredCategories.length === 0 ? (
|
||||
<div className={styles.noResults}>
|
||||
没有找到匹配的图标
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{searchTerm ? (
|
||||
// 搜索模式:显示所有匹配的分类
|
||||
filteredCategories.map((category) => (
|
||||
<div key={category.key} className={styles.categorySection}>
|
||||
<h4 className={styles.categoryTitle}>
|
||||
{t(`tools.icon_designer.${category.name}`)} ({category.icons.length})
|
||||
</h4>
|
||||
<div className={styles.iconGrid}>
|
||||
{category.icons.map((icon, index) => (
|
||||
<button
|
||||
key={`${category.key}-${index}`}
|
||||
className={selectedIcon === icon ? styles.iconButtonActive : styles.iconButton}
|
||||
onClick={() => onIconSelect(icon)}
|
||||
title={icon.iconName}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
// 分类模式:只显示当前激活的分类
|
||||
(() => {
|
||||
const currentCategory = iconCategories.find(cat => cat.key === activeCategory);
|
||||
return currentCategory ? (
|
||||
<div className={styles.iconGrid}>
|
||||
{currentCategory.icons.map((icon, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={selectedIcon === icon ? styles.iconButtonActive : styles.iconButton}
|
||||
onClick={() => onIconSelect(icon)}
|
||||
title={icon.iconName}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null;
|
||||
})()
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faCube, faDownload, faStar
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import ToolHeader from '@/components/ToolHeader';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
import IconSelector from './components/IconSelector';
|
||||
import EnhancedIconPreview, { ShapeType, BackgroundType, IconType } from './components/EnhancedIconPreview';
|
||||
import EnhancedIconCanvas, { EnhancedIconCanvasRef } from './components/EnhancedIconCanvas';
|
||||
|
||||
// 样式定义
|
||||
const styles = {
|
||||
container: "min-h-screen flex flex-col max-w-[1440px] mx-auto p-4 md:p-6",
|
||||
card: "card p-6 mb-6",
|
||||
section: "mb-6",
|
||||
sectionTitle: "text-lg font-semibold mb-3 text-primary",
|
||||
grid: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
||||
leftPanel: "lg:col-span-2 space-y-6",
|
||||
rightPanel: "lg:col-span-1",
|
||||
iconGrid: "grid grid-cols-6 sm:grid-cols-8 md:grid-cols-10 gap-2",
|
||||
iconButton: "btn-option p-3 flex items-center justify-center text-lg hover:scale-110 transition-transform",
|
||||
iconButtonActive: "btn-option-active p-3 flex items-center justify-center text-lg scale-110",
|
||||
shapeGrid: "grid grid-cols-2 sm:grid-cols-4 gap-2",
|
||||
shapeButton: "btn-option p-4 flex flex-col items-center justify-center",
|
||||
shapeButtonActive: "btn-option-active p-4 flex flex-col items-center justify-center",
|
||||
colorGrid: "grid grid-cols-4 sm:grid-cols-6 gap-2",
|
||||
colorButton: "w-12 h-12 rounded-lg border-2 border-white/20 hover:border-white/60 transition-all cursor-pointer",
|
||||
colorButtonActive: "w-12 h-12 rounded-lg border-2 border-purple-500 scale-110 shadow-lg shadow-purple-500/30",
|
||||
slider: "w-full accent-[rgb(var(--color-primary))]",
|
||||
previewArea: "card p-8 flex flex-col items-center justify-center min-h-80",
|
||||
previewIcon: "mb-4 transition-all duration-300",
|
||||
templateGrid: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
||||
templateButton: "btn-option p-4 text-left",
|
||||
exportGrid: "grid grid-cols-2 gap-3",
|
||||
searchInput: "search-input w-full mb-4",
|
||||
iconCategory: "mb-4",
|
||||
categoryTitle: "text-sm font-medium mb-2 text-secondary",
|
||||
label: "block text-sm font-medium mb-2 text-secondary",
|
||||
input: "w-full px-3 py-2 bg-card border border-gray-600 rounded-lg text-primary placeholder-tertiary focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 transition-all",
|
||||
select: "w-full px-3 py-2 bg-card border border-gray-600 rounded-lg text-primary focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20 transition-all",
|
||||
colorInput: "w-12 h-10 border-0 rounded-lg cursor-pointer",
|
||||
};
|
||||
|
||||
// 移除原有的图标数据,现在由IconSelector组件管理
|
||||
|
||||
// 颜色预设
|
||||
const colorPresets = [
|
||||
'#000000', '#FFFFFF', '#6B7280', '#3B82F6', '#10B981',
|
||||
'#EF4444', '#F59E0B', '#8B5CF6', '#EC4899', '#14B8A6'
|
||||
];
|
||||
|
||||
// ShapeType现在从IconCanvas组件导入
|
||||
|
||||
// 预设模板
|
||||
interface Template {
|
||||
name: string;
|
||||
backgroundType: BackgroundType;
|
||||
backgroundColor: string;
|
||||
gradientStartColor?: string;
|
||||
gradientEndColor?: string;
|
||||
gradientDirection?: number;
|
||||
iconColor: string;
|
||||
shape: ShapeType;
|
||||
iconSize: number;
|
||||
iconRotation?: number;
|
||||
}
|
||||
|
||||
const templates: Template[] = [
|
||||
{
|
||||
name: 'iOS Style',
|
||||
backgroundType: 'solid',
|
||||
backgroundColor: '#000000',
|
||||
iconColor: '#FFFFFF',
|
||||
shape: 'rounded-square',
|
||||
iconSize: 60
|
||||
},
|
||||
{
|
||||
name: 'Material',
|
||||
backgroundType: 'solid',
|
||||
backgroundColor: '#4CAF50',
|
||||
iconColor: '#FFFFFF',
|
||||
shape: 'circle',
|
||||
iconSize: 55
|
||||
},
|
||||
{
|
||||
name: 'Minimal',
|
||||
backgroundType: 'solid',
|
||||
backgroundColor: '#FFFFFF',
|
||||
iconColor: '#000000',
|
||||
shape: 'square',
|
||||
iconSize: 50
|
||||
},
|
||||
{
|
||||
name: 'Gradient',
|
||||
backgroundType: 'linear-gradient',
|
||||
backgroundColor: '#8B5CF6',
|
||||
gradientStartColor: '#6366F1',
|
||||
gradientEndColor: '#8B5CF6',
|
||||
gradientDirection: 45,
|
||||
iconColor: '#FFFFFF',
|
||||
shape: 'rounded-square',
|
||||
iconSize: 65
|
||||
},
|
||||
{
|
||||
name: 'Neon',
|
||||
backgroundType: 'radial-gradient',
|
||||
backgroundColor: '#000000',
|
||||
gradientStartColor: '#FF006E',
|
||||
gradientEndColor: '#8338EC',
|
||||
iconColor: '#FFFFFF',
|
||||
shape: 'circle',
|
||||
iconSize: 70,
|
||||
iconRotation: 15
|
||||
},
|
||||
{
|
||||
name: 'Retro',
|
||||
backgroundType: 'linear-gradient',
|
||||
backgroundColor: '#F72585',
|
||||
gradientStartColor: '#F72585',
|
||||
gradientEndColor: '#B5179E',
|
||||
gradientDirection: 135,
|
||||
iconColor: '#FFE66D',
|
||||
shape: 'square',
|
||||
iconSize: 65
|
||||
},
|
||||
{
|
||||
name: 'Glassmorphism',
|
||||
backgroundType: 'linear-gradient',
|
||||
backgroundColor: 'rgba(255,255,255,0.1)',
|
||||
gradientStartColor: 'rgba(255,255,255,0.2)',
|
||||
gradientEndColor: 'rgba(255,255,255,0.05)',
|
||||
iconColor: '#FFFFFF',
|
||||
shape: 'rounded-square',
|
||||
iconSize: 60
|
||||
},
|
||||
{
|
||||
name: 'Neumorphism',
|
||||
backgroundType: 'solid',
|
||||
backgroundColor: '#E0E5EC',
|
||||
iconColor: '#9BAACF',
|
||||
shape: 'rounded-square',
|
||||
iconSize: 55
|
||||
}
|
||||
];
|
||||
|
||||
export default function IconDesigner() {
|
||||
const { t } = useLanguage();
|
||||
const canvasRef = useRef<EnhancedIconCanvasRef>(null);
|
||||
|
||||
// 图标相关状态
|
||||
const [iconType, setIconType] = useState<IconType>('fontawesome');
|
||||
const [selectedIcon, setSelectedIcon] = useState(faStar);
|
||||
const [customText, setCustomText] = useState('ABC');
|
||||
const [iconColor, setIconColor] = useState('#FFFFFF');
|
||||
const [iconSize, setIconSize] = useState(60);
|
||||
const [iconRotation, setIconRotation] = useState(0);
|
||||
const [fontFamily, setFontFamily] = useState('Arial, sans-serif');
|
||||
const [fontWeight, setFontWeight] = useState('bold');
|
||||
const [fontSize, setFontSize] = useState(40); // 独立的字体大小,用于文字模式
|
||||
|
||||
// 背景相关状态
|
||||
const [backgroundType, setBackgroundType] = useState<BackgroundType>('solid');
|
||||
const [backgroundColor, setBackgroundColor] = useState('#000000');
|
||||
const [gradientStartColor, setGradientStartColor] = useState('#6366F1');
|
||||
const [gradientEndColor, setGradientEndColor] = useState('#8B5CF6');
|
||||
const [gradientDirection, setGradientDirection] = useState(45);
|
||||
const [shape, setShape] = useState<ShapeType>('rounded-square');
|
||||
|
||||
// 导出相关状态
|
||||
const [exportSize, setExportSize] = useState(256);
|
||||
const [exportFormat, setExportFormat] = useState('png');
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
|
||||
// 应用模板
|
||||
const applyTemplate = (template: Template) => {
|
||||
setBackgroundType(template.backgroundType);
|
||||
setBackgroundColor(template.backgroundColor);
|
||||
if (template.gradientStartColor) setGradientStartColor(template.gradientStartColor);
|
||||
if (template.gradientEndColor) setGradientEndColor(template.gradientEndColor);
|
||||
if (template.gradientDirection) setGradientDirection(template.gradientDirection);
|
||||
setIconColor(template.iconColor);
|
||||
setShape(template.shape);
|
||||
setIconSize(template.iconSize);
|
||||
if (template.iconRotation) setIconRotation(template.iconRotation);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 下载图标
|
||||
const downloadIcon = async () => {
|
||||
if (!canvasRef.current) return;
|
||||
|
||||
setIsGenerating(true);
|
||||
|
||||
try {
|
||||
const dataUrl = await canvasRef.current.generateIcon(exportSize);
|
||||
|
||||
// 下载
|
||||
const link = document.createElement('a');
|
||||
link.download = `icon-${Date.now()}.${exportFormat}`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error);
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ToolHeader
|
||||
icon={faCube}
|
||||
toolCode="icon_designer"
|
||||
title=""
|
||||
description=""
|
||||
/>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{/* 左侧控制面板 */}
|
||||
<div className={styles.leftPanel}>
|
||||
{/* 图标选择 */}
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.sectionTitle}>{t('tools.icon_designer.icon_selection')}</h3>
|
||||
|
||||
{/* 图标类型选择 */}
|
||||
<div className="mb-4">
|
||||
<label className={styles.label}>{t('tools.icon_designer.icon_type')}</label>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setIconType('fontawesome')}
|
||||
className={`px-4 py-2 rounded-lg border transition-all ${
|
||||
iconType === 'fontawesome'
|
||||
? 'bg-gradient-to-r from-indigo-500 to-purple-500 text-white border-purple-500 shadow-lg shadow-purple-500/25'
|
||||
: 'bg-card border-gray-600 text-secondary hover:border-purple-500/50'
|
||||
}`}
|
||||
>
|
||||
{t('tools.icon_designer.icon_type_fontawesome')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIconType('text')}
|
||||
className={`px-4 py-2 rounded-lg border transition-all ${
|
||||
iconType === 'text'
|
||||
? 'bg-gradient-to-r from-indigo-500 to-purple-500 text-white border-purple-500 shadow-lg shadow-purple-500/25'
|
||||
: 'bg-card border-gray-600 text-secondary hover:border-purple-500/50'
|
||||
}`}
|
||||
>
|
||||
{t('tools.icon_designer.icon_type_text')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FontAwesome图标选择器 */}
|
||||
{iconType === 'fontawesome' && (
|
||||
<IconSelector
|
||||
selectedIcon={selectedIcon}
|
||||
onIconSelect={setSelectedIcon}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 自定义文字输入 */}
|
||||
{iconType === 'text' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className={styles.label}>{t('tools.icon_designer.text_input')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={customText}
|
||||
onChange={(e) => setCustomText(e.target.value)}
|
||||
placeholder={t('tools.icon_designer.text_input_placeholder')}
|
||||
className={styles.input}
|
||||
maxLength={10}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={styles.label}>{t('tools.icon_designer.font_family')}</label>
|
||||
<select
|
||||
value={fontFamily}
|
||||
onChange={(e) => setFontFamily(e.target.value)}
|
||||
className={styles.select}
|
||||
>
|
||||
<option value="Arial, sans-serif">Arial</option>
|
||||
<option value="Helvetica, sans-serif">Helvetica</option>
|
||||
<option value="Times, serif">Times</option>
|
||||
<option value="Courier, monospace">Courier</option>
|
||||
<option value="Georgia, serif">Georgia</option>
|
||||
<option value="Verdana, sans-serif">Verdana</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className={styles.label}>{t('tools.icon_designer.font_weight')}</label>
|
||||
<select
|
||||
value={fontWeight}
|
||||
onChange={(e) => setFontWeight(e.target.value)}
|
||||
className={styles.select}
|
||||
>
|
||||
<option value="normal">Normal</option>
|
||||
<option value="bold">Bold</option>
|
||||
<option value="100">Thin</option>
|
||||
<option value="300">Light</option>
|
||||
<option value="500">Medium</option>
|
||||
<option value="700">Bold</option>
|
||||
<option value="900">Black</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className={styles.label}>{t('tools.icon_designer.font_size')}: {fontSize}%</label>
|
||||
<input
|
||||
type="range"
|
||||
min="20"
|
||||
max="90"
|
||||
value={fontSize}
|
||||
onChange={(e) => setFontSize(Number(e.target.value))}
|
||||
className={styles.slider}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 图标设置 */}
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.sectionTitle}>{t('tools.icon_designer.icon_settings')}</h3>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.icon_color')}
|
||||
</label>
|
||||
<div className={styles.colorGrid}>
|
||||
{colorPresets.map((color) => (
|
||||
<button
|
||||
key={color}
|
||||
className={iconColor === color ? styles.colorButtonActive : styles.colorButton}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => setIconColor(color)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<input
|
||||
type="color"
|
||||
value={iconColor}
|
||||
onChange={(e) => setIconColor(e.target.value)}
|
||||
className="mt-2 w-full h-10 rounded border-0 cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{iconType === 'fontawesome' && (
|
||||
<div>
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.icon_size')}: {iconSize}%
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="30"
|
||||
max="80"
|
||||
value={iconSize}
|
||||
onChange={(e) => setIconSize(Number(e.target.value))}
|
||||
className={styles.slider}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.icon_rotation')}: {iconRotation}°
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="360"
|
||||
value={iconRotation}
|
||||
onChange={(e) => setIconRotation(Number(e.target.value))}
|
||||
className={styles.slider}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 背景设置 */}
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.sectionTitle}>{t('tools.icon_designer.background_settings')}</h3>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.background_shape')}
|
||||
</label>
|
||||
<div className={styles.shapeGrid}>
|
||||
{(['circle', 'square', 'rounded-square', 'hexagon'] as ShapeType[]).map((shapeType) => (
|
||||
<button
|
||||
key={shapeType}
|
||||
className={shape === shapeType ? styles.shapeButtonActive : styles.shapeButton}
|
||||
onClick={() => setShape(shapeType)}
|
||||
>
|
||||
<div className={`w-8 h-8 bg-current ${
|
||||
shapeType === 'circle' ? 'rounded-full' :
|
||||
shapeType === 'rounded-square' ? 'rounded-lg' :
|
||||
shapeType === 'hexagon' ? 'rounded-md transform rotate-45' :
|
||||
''
|
||||
}`} />
|
||||
<span className="text-xs mt-1">{t(`tools.icon_designer.shape_${shapeType.replace('-', '_')}`)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.background_color')}
|
||||
</label>
|
||||
<div className={styles.colorGrid}>
|
||||
{colorPresets.map((color) => (
|
||||
<button
|
||||
key={color}
|
||||
className={backgroundColor === color ? styles.colorButtonActive : styles.colorButton}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => setBackgroundColor(color)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<input
|
||||
type="color"
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="mt-2 w-full h-10 rounded border-0 cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 预设模板 */}
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.sectionTitle}>{t('tools.icon_designer.preset_templates')}</h3>
|
||||
<div className={styles.templateGrid}>
|
||||
{templates.map((template, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={styles.templateButton}
|
||||
onClick={() => applyTemplate(template)}
|
||||
>
|
||||
<div className="flex items-center mb-2">
|
||||
<div
|
||||
className="w-6 h-6 rounded mr-2"
|
||||
style={{ backgroundColor: template.backgroundColor }}
|
||||
/>
|
||||
<span className="font-medium">{t(`tools.icon_designer.template_${template.name.toLowerCase().replace(' ', '_')}`)}</span>
|
||||
</div>
|
||||
<span className="text-xs text-secondary">
|
||||
{template.backgroundType === 'solid' ? '纯色' : '渐变'} • {template.shape} • {template.iconSize}%
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧预览和导出 */}
|
||||
<div className={styles.rightPanel}>
|
||||
{/* 预览 */}
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.sectionTitle}>{t('tools.icon_designer.preview')}</h3>
|
||||
<div className={styles.previewArea}>
|
||||
<EnhancedIconPreview
|
||||
iconType={iconType}
|
||||
icon={selectedIcon}
|
||||
customText={customText}
|
||||
iconColor={iconColor}
|
||||
iconSize={iconType === 'text' ? fontSize : iconSize}
|
||||
iconRotation={iconRotation}
|
||||
fontFamily={fontFamily}
|
||||
fontWeight={fontWeight}
|
||||
backgroundType={backgroundType}
|
||||
backgroundColor={backgroundColor}
|
||||
gradientStartColor={gradientStartColor}
|
||||
gradientEndColor={gradientEndColor}
|
||||
gradientDirection={gradientDirection}
|
||||
shape={shape}
|
||||
previewSize={200}
|
||||
/>
|
||||
|
||||
{/* 隐藏的Canvas用于导出 */}
|
||||
<div style={{ position: 'absolute', left: '-9999px' }}>
|
||||
<EnhancedIconCanvas
|
||||
ref={canvasRef}
|
||||
iconType={iconType}
|
||||
icon={selectedIcon}
|
||||
customText={customText}
|
||||
iconColor={iconColor}
|
||||
iconSize={iconType === 'text' ? fontSize : iconSize}
|
||||
iconRotation={iconRotation}
|
||||
fontFamily={fontFamily}
|
||||
fontWeight={fontWeight}
|
||||
backgroundType={backgroundType}
|
||||
backgroundColor={backgroundColor}
|
||||
gradientStartColor={gradientStartColor}
|
||||
gradientEndColor={gradientEndColor}
|
||||
gradientDirection={gradientDirection}
|
||||
shape={shape}
|
||||
canvasSize={256}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 导出设置 */}
|
||||
<div className={styles.card}>
|
||||
<h3 className={styles.sectionTitle}>{t('tools.icon_designer.export_settings')}</h3>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.export_size')}
|
||||
</label>
|
||||
<div className={styles.exportGrid}>
|
||||
{[64, 128, 256, 512].map((size) => (
|
||||
<button
|
||||
key={size}
|
||||
className={exportSize === size ? 'btn-option-active' : 'btn-option'}
|
||||
onClick={() => setExportSize(size)}
|
||||
>
|
||||
{size}x{size}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<label className="block text-secondary text-sm font-bold mb-2">
|
||||
{t('tools.icon_designer.export_format')}
|
||||
</label>
|
||||
<div className={styles.exportGrid}>
|
||||
{['png', 'svg'].map((format) => (
|
||||
<button
|
||||
key={format}
|
||||
className={exportFormat === format ? 'btn-option-active' : 'btn-option'}
|
||||
onClick={() => setExportFormat(format)}
|
||||
>
|
||||
{format.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn-primary w-full"
|
||||
onClick={downloadIcon}
|
||||
disabled={isGenerating}
|
||||
>
|
||||
<FontAwesomeIcon icon={faDownload} className="mr-2" />
|
||||
{isGenerating ? t('tools.icon_designer.generating_icon') : t('tools.icon_designer.download_icon')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 使用说明 */}
|
||||
<div className="bg-block p-4 rounded-lg border border-purple-glow/15">
|
||||
<h3 className="text-lg font-semibold mb-2 text-primary">
|
||||
{t('tools.icon_designer.usage_guide')}
|
||||
</h3>
|
||||
<ul className="list-disc pl-5 space-y-1 text-secondary text-sm">
|
||||
<li>{t('tools.icon_designer.guide_1')}</li>
|
||||
<li>{t('tools.icon_designer.guide_2')}</li>
|
||||
<li>{t('tools.icon_designer.guide_3')}</li>
|
||||
<li>{t('tools.icon_designer.guide_4')}</li>
|
||||
<li>{t('tools.icon_designer.guide_5')}</li>
|
||||
<li>{t('tools.icon_designer.guide_6')}</li>
|
||||
</ul>
|
||||
|
||||
<div className="mt-4">
|
||||
<h4 className="font-medium mb-2 text-primary">{t('tools.icon_designer.tips')}</h4>
|
||||
<ul className="list-disc pl-5 space-y-1 text-secondary text-sm">
|
||||
<li>{t('tools.icon_designer.tip_1')}</li>
|
||||
<li>{t('tools.icon_designer.tip_2')}</li>
|
||||
<li>{t('tools.icon_designer.tip_3')}</li>
|
||||
<li>{t('tools.icon_designer.tip_4')}</li>
|
||||
<li>{t('tools.icon_designer.tip_5')}</li>
|
||||
<li>{t('tools.icon_designer.tip_6')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -31,7 +31,8 @@ export const en = {
|
||||
base64_to_image: tools.base64_to_image.en,
|
||||
image_watermark: tools.image_watermark.en,
|
||||
image_to_ico: tools.image_to_ico.en,
|
||||
cron_generator: tools.cron_generator.en
|
||||
cron_generator: tools.cron_generator.en,
|
||||
icon_designer: tools.icon_designer.en
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
export const iconDesignerEn = {
|
||||
title: 'Icon Designer',
|
||||
description: 'Quickly design clean and beautiful app icons with various background shapes and color combinations',
|
||||
icon_selection: 'Icon Selection',
|
||||
choose_icon: 'Choose Icon',
|
||||
icon_search_placeholder: 'Search icons...',
|
||||
popular_icons: 'Popular Icons',
|
||||
business_icons: 'Business Icons',
|
||||
tech_icons: 'Tech Icons',
|
||||
social_icons: 'Social Icons',
|
||||
ui_icons: 'UI Icons',
|
||||
media_icons: 'Media Icons',
|
||||
transport_icons: 'Transport Icons',
|
||||
shopping_icons: 'Shopping Icons',
|
||||
health_icons: 'Health Icons',
|
||||
food_icons: 'Food Icons',
|
||||
sports_icons: 'Sports Icons',
|
||||
weather_icons: 'Weather Icons',
|
||||
time_icons: 'Time Icons',
|
||||
security_icons: 'Security Icons',
|
||||
files_icons: 'File Icons',
|
||||
|
||||
background_settings: 'Background Settings',
|
||||
background_shape: 'Background Shape',
|
||||
background_color: 'Background Color',
|
||||
background_type: 'Background Type',
|
||||
background_solid: 'Solid',
|
||||
background_gradient: 'Gradient',
|
||||
gradient_direction: 'Gradient Direction',
|
||||
gradient_start: 'Start Color',
|
||||
gradient_end: 'End Color',
|
||||
gradient_linear: 'Linear Gradient',
|
||||
gradient_radial: 'Radial Gradient',
|
||||
|
||||
shape_circle: 'Circle',
|
||||
shape_square: 'Square',
|
||||
shape_rounded_square: 'Rounded Square',
|
||||
shape_hexagon: 'Hexagon',
|
||||
|
||||
icon_settings: 'Icon Settings',
|
||||
icon_type: 'Icon Type',
|
||||
icon_type_fontawesome: 'FontAwesome Icon',
|
||||
icon_type_text: 'Custom Text',
|
||||
icon_color: 'Icon Color',
|
||||
icon_size: 'Icon Size',
|
||||
icon_position: 'Icon Position',
|
||||
icon_rotation: 'Icon Rotation',
|
||||
text_input: 'Enter Text',
|
||||
text_input_placeholder: 'Enter your text...',
|
||||
font_family: 'Font Family',
|
||||
font_weight: 'Font Weight',
|
||||
font_size: 'Font Size',
|
||||
|
||||
color_black: 'Black',
|
||||
color_white: 'White',
|
||||
color_gray: 'Gray',
|
||||
color_blue: 'Blue',
|
||||
color_green: 'Green',
|
||||
color_red: 'Red',
|
||||
color_orange: 'Orange',
|
||||
color_purple: 'Purple',
|
||||
color_custom: 'Custom',
|
||||
|
||||
preset_templates: 'Preset Templates',
|
||||
template_ios_style: 'iOS Style',
|
||||
template_material: 'Material Style',
|
||||
template_minimal: 'Minimal Style',
|
||||
template_gradient: 'Gradient Style',
|
||||
template_neon: 'Neon Style',
|
||||
template_retro: 'Retro Style',
|
||||
template_glassmorphism: 'Glassmorphism',
|
||||
template_neumorphism: 'Neumorphism',
|
||||
|
||||
export_settings: 'Export Settings',
|
||||
export_size: 'Export Size',
|
||||
export_format: 'Export Format',
|
||||
download_icon: 'Download Icon',
|
||||
|
||||
size_small: 'Small (64x64)',
|
||||
size_medium: 'Medium (128x128)',
|
||||
size_large: 'Large (256x256)',
|
||||
size_xlarge: 'X-Large (512x512)',
|
||||
|
||||
format_png: 'PNG Format',
|
||||
format_svg: 'SVG Format',
|
||||
format_ico: 'ICO Format',
|
||||
|
||||
preview: 'Preview',
|
||||
no_icon_selected: 'No Icon Selected',
|
||||
select_icon_first: 'Please select an icon first',
|
||||
|
||||
usage_guide: 'Usage Guide',
|
||||
guide_1: '1. Choose icon type: FontAwesome icon or custom text',
|
||||
guide_2: '2. Configure icon content, color, size and rotation',
|
||||
guide_3: '3. Select background shape and type (solid or gradient)',
|
||||
guide_4: '4. Adjust background color or gradient settings',
|
||||
guide_5: '5. Apply preset templates for quick design',
|
||||
guide_6: '6. Select export size and format, then download',
|
||||
|
||||
tips: 'Tips',
|
||||
tip_1: 'Recommend using high contrast color combinations, like black background with white icon',
|
||||
tip_2: 'For mobile apps, rounded square background is recommended',
|
||||
tip_3: 'Gradient backgrounds can add depth and modern feel to icons',
|
||||
tip_4: 'Custom text feature supports creating brand initials or short word icons',
|
||||
tip_5: 'Appropriate rotation angles can add dynamic feel to icons',
|
||||
tip_6: 'Neumorphism and glassmorphism templates suit modern UI design',
|
||||
|
||||
applying_template: 'Applying template...',
|
||||
generating_icon: 'Generating icon...',
|
||||
download_ready: 'Download ready'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { iconDesignerZh } from './zh';
|
||||
import { iconDesignerEn } from './en';
|
||||
|
||||
export const iconDesigner = {
|
||||
zh: iconDesignerZh,
|
||||
en: iconDesignerEn
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
export const iconDesignerZh = {
|
||||
title: '图标设计器',
|
||||
description: '快速设计简洁精美的应用图标,支持多种形状背景和颜色搭配',
|
||||
icon_selection: '图标选择',
|
||||
choose_icon: '选择图标',
|
||||
icon_search_placeholder: '搜索图标...',
|
||||
popular_icons: '常用图标',
|
||||
business_icons: '商务图标',
|
||||
tech_icons: '科技图标',
|
||||
social_icons: '社交图标',
|
||||
ui_icons: '界面图标',
|
||||
media_icons: '媒体图标',
|
||||
transport_icons: '交通出行',
|
||||
shopping_icons: '购物电商',
|
||||
health_icons: '健康医疗',
|
||||
food_icons: '食物饮料',
|
||||
sports_icons: '运动休闲',
|
||||
weather_icons: '天气自然',
|
||||
time_icons: '时间日期',
|
||||
security_icons: '安全保护',
|
||||
files_icons: '文件格式',
|
||||
|
||||
background_settings: '背景设置',
|
||||
background_shape: '背景形状',
|
||||
background_color: '背景颜色',
|
||||
background_type: '背景类型',
|
||||
background_solid: '纯色',
|
||||
background_gradient: '渐变',
|
||||
gradient_direction: '渐变方向',
|
||||
gradient_start: '起始颜色',
|
||||
gradient_end: '结束颜色',
|
||||
gradient_linear: '线性渐变',
|
||||
gradient_radial: '径向渐变',
|
||||
|
||||
shape_circle: '圆形',
|
||||
shape_square: '正方形',
|
||||
shape_rounded_square: '圆角正方形',
|
||||
shape_hexagon: '六边形',
|
||||
|
||||
icon_settings: '图标设置',
|
||||
icon_type: '图标类型',
|
||||
icon_type_fontawesome: 'FontAwesome图标',
|
||||
icon_type_text: '自定义文字',
|
||||
icon_color: '图标颜色',
|
||||
icon_size: '图标大小',
|
||||
icon_position: '图标位置',
|
||||
icon_rotation: '图标旋转',
|
||||
text_input: '输入文字',
|
||||
text_input_placeholder: '输入您的文字...',
|
||||
font_family: '字体',
|
||||
font_weight: '字体粗细',
|
||||
font_size: '字体大小',
|
||||
|
||||
color_black: '黑色',
|
||||
color_white: '白色',
|
||||
color_gray: '灰色',
|
||||
color_blue: '蓝色',
|
||||
color_green: '绿色',
|
||||
color_red: '红色',
|
||||
color_orange: '橙色',
|
||||
color_purple: '紫色',
|
||||
color_custom: '自定义',
|
||||
|
||||
preset_templates: '预设模板',
|
||||
template_ios_style: 'iOS风格',
|
||||
template_material: 'Material风格',
|
||||
template_minimal: '极简风格',
|
||||
template_gradient: '渐变风格',
|
||||
template_neon: '霓虹风格',
|
||||
template_retro: '复古风格',
|
||||
template_glassmorphism: '玻璃拟态',
|
||||
template_neumorphism: '新拟态',
|
||||
|
||||
export_settings: '导出设置',
|
||||
export_size: '导出尺寸',
|
||||
export_format: '导出格式',
|
||||
download_icon: '下载图标',
|
||||
|
||||
size_small: '小 (64x64)',
|
||||
size_medium: '中 (128x128)',
|
||||
size_large: '大 (256x256)',
|
||||
size_xlarge: '超大 (512x512)',
|
||||
|
||||
format_png: 'PNG格式',
|
||||
format_svg: 'SVG格式',
|
||||
format_ico: 'ICO格式',
|
||||
|
||||
preview: '预览',
|
||||
no_icon_selected: '未选择图标',
|
||||
select_icon_first: '请先选择一个图标',
|
||||
|
||||
usage_guide: '使用说明',
|
||||
guide_1: '1. 选择图标类型:FontAwesome图标或自定义文字',
|
||||
guide_2: '2. 配置图标内容、颜色、大小和旋转',
|
||||
guide_3: '3. 选择背景形状和类型(纯色或渐变)',
|
||||
guide_4: '4. 调整背景颜色或渐变设置',
|
||||
guide_5: '5. 应用预设模板快速设计',
|
||||
guide_6: '6. 选择导出尺寸和格式并下载',
|
||||
|
||||
tips: '小贴士',
|
||||
tip_1: '建议使用高对比度的颜色搭配,如黑底白图标或白底黑图标',
|
||||
tip_2: '对于移动应用,推荐使用圆角正方形背景',
|
||||
tip_3: '渐变背景可以让图标更有层次感和现代感',
|
||||
tip_4: '自定义文字功能支持创建品牌首字母或简短词汇图标',
|
||||
tip_5: '适当的旋转角度可以增加图标的动感',
|
||||
tip_6: '新拟态和玻璃拟态模板适合现代UI设计',
|
||||
|
||||
applying_template: '正在应用模板...',
|
||||
generating_icon: '正在生成图标...',
|
||||
download_ready: '下载就绪'
|
||||
};
|
||||
@@ -26,6 +26,7 @@ import base64ToImage from './base64_to_image';
|
||||
import imageWatermark from './image_watermark';
|
||||
import imageToIco from './image_to_ico';
|
||||
import cronGenerator from './cron_generator';
|
||||
import { iconDesigner } from './icon_designer';
|
||||
|
||||
export const tools = {
|
||||
json_formatter: jsonFormatter,
|
||||
@@ -55,7 +56,8 @@ export const tools = {
|
||||
base64_to_image: base64ToImage,
|
||||
image_watermark: imageWatermark,
|
||||
image_to_ico: imageToIco,
|
||||
cron_generator: cronGenerator
|
||||
cron_generator: cronGenerator,
|
||||
icon_designer: iconDesigner
|
||||
};
|
||||
|
||||
export default tools;
|
||||
|
||||
@@ -31,7 +31,8 @@ export const zh = {
|
||||
base64_to_image: tools.base64_to_image.zh,
|
||||
image_watermark: tools.image_watermark.zh,
|
||||
image_to_ico: tools.image_to_ico.zh,
|
||||
cron_generator: tools.cron_generator.zh
|
||||
cron_generator: tools.cron_generator.zh,
|
||||
icon_designer: tools.icon_designer.zh
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+7
-7
@@ -2,7 +2,7 @@ import {
|
||||
faCode, faExchangeAlt, faClock, faGlobe, faLink, faLock,
|
||||
faImage, faCogs, faFileCode, faKey, faFont,
|
||||
faCalendarAlt, faPalette, faEdit, faRuler, faNetworkWired,
|
||||
faEraser, faCalculator, faFileAlt
|
||||
faEraser, faCalculator, faFileAlt, faCube
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { Tool } from '@/types/tools';
|
||||
|
||||
@@ -166,17 +166,17 @@ const tools: Tool[] = [
|
||||
category: ['image'],
|
||||
keywords: ['图片水印', '水印', '水印添加', '图片', '文字水印', '图片水印', 'watermark', 'image watermark', 'tupian shuiyin', 'tpshuiyin', 'shuiyin', 'sy', 'tp']
|
||||
},
|
||||
{
|
||||
code: 'image_to_ico',
|
||||
icon: faImage,
|
||||
category: ['image'],
|
||||
keywords: ['图标', 'ico', '图片转ico', 'icon', '图标生成', '图标转换', 'favicon', '网站图标', 'tubiao', 'tb', 'zhuanicon', 'icon转换', 'icon生成']
|
||||
},
|
||||
{
|
||||
code: 'cron_generator',
|
||||
icon: faCalendarAlt,
|
||||
category: ['datetime'],
|
||||
keywords: ['cron', 'cron表达式', '定时任务', '调度', '表达式生成', '执行时间', 'crontab', 'quartz', 'schedule', 'dingshi', 'dingshibiaodashi', 'dsrw', 'bds', 'cronbds']
|
||||
},
|
||||
{
|
||||
code: 'icon_designer',
|
||||
icon: faCube,
|
||||
category: ['common', 'image'],
|
||||
keywords: ['图标设计', '图标生成', 'icon设计', '图标制作', 'app图标', 'logo设计', 'favicon制作', '图标工具', 'icon designer', 'icon generator', 'tubiao', 'tb', 'sheji', 'sj', 'zhizuo', 'zz']
|
||||
}
|
||||
] as Tool[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user