This commit is contained in:
star7th
2025-07-27 22:42:44 +08:00
parent cc5c5f7028
commit 3cb25118ab
14 changed files with 1998 additions and 10 deletions
+22
View File
@@ -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>
);
}
+583
View File
@@ -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>
);
}
+2 -1
View File
@@ -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
}
};
+111
View File
@@ -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
};
+111
View File
@@ -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: '下载就绪'
};
+3 -1
View File
@@ -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;
+2 -1
View File
@@ -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
View File
@@ -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[];