Skip to content
0

技能页面

前言

Firefly 主题移植了一个专业的技能展示(Skills)页面,用于展示您掌握的技术栈、工具和专业能力。这个页面可以帮助访客了解您的技术专长和专业水平。

  • 本文档详细介绍如何将技能页面功能移植到 Firefly-Hyde 主题中。

  • 在开始之前,请确保你已经按照文件结构创建好对应的目录和文件。

📁 文件结构

src/
├── components/
│   ├── atoms/
│   │   ├── Icon/
│   │       └── Icon.astro          ✨ 新增 - 图标组件
│   │       └── index.ts                  ✨ 新增 - 组件导出
│   │   └── FilterTabs.astro          ✨ 新增 - 筛选标签组件
│   │   └── index.ts                  ✨ 新增 - 组件导出
│   └── features/
│       └── skills/
│           ├── index.ts               ✨ 新增 - 组件导出
│           ├── types.ts               ✨ 新增 - 类型定义
│           └── skills.astro       ✨ 新增 - 技能卡片组件
│   └── misc/
│       └── Icon.astro.astro          ✨ 新增 -  图标组件
├── config/
│   └── siteConfig.ts                  ✏️ 修改 - 添加页面开关配置
│   └── navBarConfig.ts               ✏️ 修改 - 导航栏配置
├── constants/
│   └── link-skills.ts               ✏️ 修改 - 添加导航链接
├── data/
│   └── skills.ts                      ✨ 新增 - 技能数据管理
├── i18n/
│   ├── i18nKey.ts                    ✏️ 修改 - 添加翻译键
│   └── languages/
│       ├── en.ts                     ✏️ 修改 - 英文翻译
│       ├── zh_CN.ts                  ✏️ 修改 - 中文翻译
│       ├── zh_TW.ts                  ✏️ 修改 - 繁体翻译
│       ├── ja.ts                     ✏️ 修改 - 日文翻译
│       └── ru.ts                     ✏️ 修改 - 俄文翻译
├── pages/
│   └── projects.astro                   ✨ 新增 - skills页面
├── types/
│   └── config.ts                      ✏️ 修改 - 添加类型定义
├── utils/
│   └── icon-loader.ts                      ✏️ 修改 - 图标加载器

创建图标组件

文件路径src/components/atoms/Icon/Icon.astrosrc/components/atoms/Icon/types.ts

文件路径src/components/misc/Icon.astro

astro
---
import type { IconProps } from "./types";

const {
	icon,
	class: className = "",
	style = "",
	size = "md",
	color,
	fallback = "●",
	loading = "lazy",
} = Astro.props as IconProps;

const sizeClasses = {
	xs: "text-xs",
	sm: "text-sm",
	md: "text-base",
	lg: "text-lg",
	xl: "text-xl",
	"2xl": "text-2xl",
};

const sizeClass = sizeClasses[size] || sizeClasses.md;
const colorStyle = color ? `color: ${color};` : "";
const combinedStyle = `${colorStyle}${style}`;
const combinedClass = `${sizeClass} ${className}`.trim();

const iconId = `icon-${Math.random().toString(36).substring(2, 9)}`;
---

<span
	class={`inline-flex items-center justify-center ${combinedClass}`}
	style={combinedStyle}
	data-icon-container={iconId}
>
	<span class="icon-loading animate-pulse opacity-50" data-loading-indicator>
		{fallback}
	</span>

	<iconify-icon
		icon={icon}
		class="icon-content opacity-0 transition-opacity duration-200"
		data-icon-element
		loading={loading}></iconify-icon>
</span>

<script is:inline define:vars={{ iconId, icon }}>
	(function () {
		const container = document.querySelector(
			`[data-icon-container="${iconId}"]`,
		);
		if (!container) {
			return;
		}

		const loadingIndicator = container.querySelector(
			"[data-loading-indicator]",
		);
		const iconElement = container.querySelector("[data-icon-element]");

		if (!loadingIndicator || !iconElement) {
			return;
		}

		function checkIconLoaded() {
			const hasContent =
				iconElement.shadowRoot &&
				iconElement.shadowRoot.children.length > 0;

			if (hasContent) {
				showIcon();
				return true;
			}
			return false;
		}

		function showIcon() {
			loadingIndicator.style.display = "none";
			iconElement.classList.remove("opacity-0");
			iconElement.classList.add("opacity-100");
		}

		function showLoading() {
			loadingIndicator.style.display = "inline-flex";
			iconElement.classList.remove("opacity-100");
			iconElement.classList.add("opacity-0");
		}

		showLoading();

		iconElement.addEventListener("load", () => {
			showIcon();
		});

		iconElement.addEventListener("error", () => {
			console.warn(`Failed to load icon: ${icon}`);
		});

		if (window.MutationObserver) {
			const observer = new MutationObserver(() => {
				if (checkIconLoaded()) {
					observer.disconnect();
				}
			});

			observer.observe(iconElement, {
				childList: true,
				subtree: true,
				attributes: true,
			});

			setTimeout(() => {
				observer.disconnect();
				if (!checkIconLoaded()) {
					console.warn(`Icon load timeout: ${icon}`);
				}
			}, 5000);
		}

		setTimeout(() => {
			checkIconLoaded();
		}, 100);
	})();
</script>

<style>
	.icon-loading {
		min-width: 1em;
		min-height: 1em;
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}

	.icon-content {
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}

	[data-icon-container] {
		position: relative;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 1em;
		min-height: 1em;
	}

	[data-icon-container] .icon-loading,
	[data-icon-container] .icon-content {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
	}
</style>
ts
export interface IconProps {
	icon: string;
	class?: string;
	style?: string;
	size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
	color?: string;
	fallback?: string;
	loading?: "lazy" | "eager";
}
astro
---
/**
 * Icon 组件 - 向后兼容包装器
 *
 * 此文件包装 atoms/Icon/ 以保持向后兼容性
 * 新代码应直接从 @components/atoms/Icon 导入
 *
 * @deprecated 请使用 import from "@components/atoms/Icon"
 */

import BaseIcon from "../atoms/Icon/Icon.astro";
import type { IconProps } from "../atoms/Icon/types";

export interface Props extends IconProps {}

const {
	icon,
	class: className = "",
	style = "",
	size = "md",
	color,
	fallback = "●",
	loading = "lazy",
} = Astro.props;
---

<BaseIcon
	icon={icon}
	class={className}
	style={style}
	size={size}
	color={color}
	fallback={fallback}
	loading={loading}
/>

创建筛选标签组件

文件路径src/components/atoms/FilterTabs.astrosrc/components/atoms/index.ts

astro
---
import { Icon } from "astro-icon/components";

interface FilterTab {
	value: string;
	label: string;
	icon?: string;
	count?: number;
}

interface Props {
	tabs: FilterTab[];
	dataAttr: string;
	activeValue?: string;
	class?: string;
}

const {
	tabs,
	dataAttr,
	activeValue = "all",
	class: className = "",
} = Astro.props;
---

<div class:list={["filter-tabs", className]}>
	{
		tabs.map((tab) => (
			<button
				class:list={[
					"filter-tabs-item",
					{ active: tab.value === activeValue },
				]}
				data-filter-value={tab.value}
				data-filter-attr={dataAttr}
			>
				{tab.icon && <Icon name={tab.icon} class="text-base w-4 h-4" />}
				<span>{tab.label}</span>
				{tab.count !== undefined && (
					<span class="filter-tabs-count">({tab.count})</span>
				)}
			</button>
		))
	}
</div>

<style>
	.filter-tabs {
		display: flex;
		flex-wrap: wrap;
		gap: 0.5rem;
	}

	.filter-tabs-item {
		display: inline-flex;
		align-items: center;
		gap: 0.375rem;
		padding: 0.5rem 1rem;
		border: 1px solid var(--line-divider);
		border-radius: var(--radius-large);
		background: var(--btn-regular-bg);
		color: var(--btn-content);
		font-size: 0.875rem;
		font-weight: 500;
		cursor: pointer;
		transition: all 0.2s ease;
		white-space: nowrap;
	}

	.filter-tabs-item iconify-icon {
		flex-shrink: 0;
		opacity: 0.7;
		transition: opacity 0.2s ease;
	}

	.filter-tabs-item:hover:not(.active) {
		background: var(--btn-regular-bg-hover);
		border-color: var(--primary);
	}

	.filter-tabs-item:hover:not(.active) iconify-icon {
		opacity: 1;
	}

	.filter-tabs-item.active {
		background: var(--primary);
		color: white;
		border-color: var(--primary);
	}

	.filter-tabs-item.active iconify-icon {
		opacity: 1;
	}

	.filter-tabs-count {
		opacity: 0.6;
		font-size: 0.8rem;
	}

	@media (max-width: 768px) {
		.filter-tabs {
			gap: 0.375rem;
		}

		.filter-tabs-item {
			padding: 0.4rem 0.8rem;
			font-size: 0.8rem;
		}

		.filter-tabs-count {
			display: none;
		}
	}
</style>
ts
export { default as FilterTabs } from "./FilterTabs.astro";

创建技能卡片组件

  • 文件路径src/components/features/skills/index.ts 组件导出

  • 文件路径src/components/features/skills/types.ts 类型定义

  • 文件路径src/components/features/skills/SkillCard.astro 技能卡片组件

ts
export { default as SkillCard } from "./SkillCard.astro";
export * from "./types";
ts
import type { Skill } from "../../../data/skills";

export interface SkillCardProps {
	skill: Skill;
}
astro
---
import I18nKey from "../../../i18n/i18nKey";
import { i18n } from "../../../i18n/translation";
import Icon from "../../misc/Icon.astro";
import type { SkillCardProps } from "./types";

const { skill } = Astro.props as SkillCardProps;

const skillColor = skill.color || "#3B82F6";

const getLevelText = (level: string) => {
	switch (level) {
		case "expert":
			return i18n(I18nKey.skillsExpert);
		case "advanced":
			return i18n(I18nKey.skillsAdvanced);
		case "intermediate":
			return i18n(I18nKey.skillsIntermediate);
		case "beginner":
			return i18n(I18nKey.skillsBeginner);
		default:
			return level;
	}
};

const getLevelWidth = (level: string) => {
	switch (level) {
		case "expert":
			return "100%";
		case "advanced":
			return "80%";
		case "intermediate":
			return "60%";
		case "beginner":
			return "40%";
		default:
			return "20%";
	}
};

const formatExperience = (exp: { years: number; months: number }) => {
	const parts: string[] = [];
	if (exp.years > 0) {
		parts.push(`${exp.years} ${i18n(I18nKey.skillYears)}`);
	}
	if (exp.months > 0) {
		parts.push(`${exp.months} ${i18n(I18nKey.skillMonths)}`);
	}
	return parts.join(" ");
};
---

<div
	class="skill-card group relative bg-transparent rounded-xl border border-black/10 dark:border-white/10 overflow-hidden transition-all duration-300 hover:shadow-xl hover:-translate-y-1"
	data-category={skill.category}
>
	<div class="p-5">
		<div class="flex items-start gap-4 mb-3">
			<div
				class="w-12 h-12 flex-shrink-0 rounded-lg flex items-center justify-center"
				style={`background-color: ${skillColor}20`}
			>
				<Icon
					icon={skill.icon}
					class="text-xl"
					color={skillColor}
					fallback={skill.name.charAt(0)}
					loading="eager"
				/>
			</div>

			<div class="flex-1 min-w-0">
				<div class="flex items-center justify-between mb-1">
					<h3
						class="text-lg font-bold text-black/90 dark:text-white/90 truncate group-hover:text-[var(--primary)] transition-colors duration-200"
					>
						{skill.name}
					</h3>
					<span
						class="shrink-0 ml-2 px-2 py-0.5 text-xs rounded-md bg-[var(--primary)]/10 text-[var(--primary)] font-medium"
					>
						{getLevelText(skill.level)}
					</span>
				</div>
				<p class="text-xs text-black/50 dark:text-white/50">
					{formatExperience(skill.experience)}
				</p>
			</div>
		</div>

		<p
			class="text-sm text-black/60 dark:text-white/60 line-clamp-2 min-h-[2.5rem]"
		>
			{skill.description}
		</p>

		<div class="mt-3 w-full bg-[var(--btn-regular-bg)] rounded-full h-1.5">
			<div
				class="h-1.5 rounded-full transition-all duration-500"
				style={`width: ${getLevelWidth(skill.level)}; background-color: ${skillColor}`}
			>
			</div>
		</div>
	</div>

	<div
		class="absolute inset-0 bg-gradient-to-br from-[var(--primary)]/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"
	>
	</div>
</div>

<style>
	.skill-card {
		animation: fadeInUp 0.5s ease-out forwards;
		opacity: 0;
	}

	.skill-card.filtered-out {
		display: none;
	}

	@keyframes fadeInUp {
		from {
			opacity: 0;
			transform: translateY(20px);
		}
		to {
			opacity: 1;
			transform: translateY(0);
		}
	}

	.skill-card:nth-child(1) {
		animation-delay: 0.03s;
	}
	.skill-card:nth-child(2) {
		animation-delay: 0.06s;
	}
	.skill-card:nth-child(3) {
		animation-delay: 0.09s;
	}
	.skill-card:nth-child(4) {
		animation-delay: 0.12s;
	}
	.skill-card:nth-child(5) {
		animation-delay: 0.15s;
	}
	.skill-card:nth-child(6) {
		animation-delay: 0.18s;
	}
	.skill-card:nth-child(7) {
		animation-delay: 0.21s;
	}
	.skill-card:nth-child(8) {
		animation-delay: 0.24s;
	}
	.skill-card:nth-child(9) {
		animation-delay: 0.27s;
	}
	.skill-card:nth-child(10) {
		animation-delay: 0.3s;
	}
	.skill-card:nth-child(11) {
		animation-delay: 0.33s;
	}
	.skill-card:nth-child(12) {
		animation-delay: 0.36s;
	}

	.line-clamp-2 {
		display: -webkit-box;
		-webkit-line-clamp: 2;
		-webkit-box-orient: vertical;
		overflow: hidden;
	}
</style>

技能开关导航栏配置

  • SiteConfig 类型中添加页面开关配置,文件路径src/config/siteConfig.ts

  • navBarConfig 类型中更新导航栏配置,文件路径src/config/navBarConfig.ts

ts
// 页面开关配置
pages: {
  // ... 其他配置
  // 技能页面开关
	skills: true,  
},
ts
// 我的及其子菜单
	links.push({
		name: "我的",
		url: "/my/",
		icon: "material-symbols:person",
		children: [
			// 根据配置决定是否添加技能,在siteConfig关闭pages.skills时导航栏不显示技能
			...(siteConfig.pages.skills ? [LinkPreset.Skills] : []),
		],
	});

更新类型配置链接

  • types 类型的 config 中添加类型定义,文件路径src/types/config.ts

  • constants 类型的 link-presets 添加到导航链接,文件路径src/constants/link-presets.ts

ts
export type SiteConfig = {
	// 页面开关配置
	pages: {
		skills?: boolean; // 技能页面开关
	};
}

export enum LinkPreset {
		Skills = 12, // ✨ 新增
}
ts
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
	[LinkPreset.Skills]: {
		name: i18n(I18nKey.skills),
		url: "/skills/",
		icon: "material-symbols:psychology",
	},
}

技能数据管理

文件路径src/data/skills.ts

skills.ts
ts
// Skill data configuration file
// Used to manage data for the skill display page

export interface Skill {
	id: string;
	name: string;
	description: string;
	icon: string; // Iconify icon name
	category: "frontend" | "backend" | "database" | "tools" | "other";
	level: "beginner" | "intermediate" | "advanced" | "expert";
	experience: {
		years: number;
		months: number;
	};
	projects?: string[]; // Related project IDs
	certifications?: string[];
	color?: string; // Skill card theme color
}

export const skillsData: Skill[] = [
	// Frontend Skills
	{
		id: "javascript",
		name: "JavaScript",
		description:
			"Modern JavaScript development, including ES6+ syntax, asynchronous programming, and modular development.",
		icon: "logos:javascript",
		category: "frontend",
		level: "advanced",
		experience: { years: 3, months: 6 },
		projects: [
			"mizuki-blog",
			"portfolio-website",
			"data-visualization-tool",
		],
		color: "#F7DF1E",
	},
	{
		id: "typescript",
		name: "TypeScript",
		description:
			"A type-safe superset of JavaScript that enhances code quality and development efficiency.",
		icon: "logos:typescript-icon",
		category: "frontend",
		level: "advanced",
		experience: { years: 2, months: 8 },
		projects: ["mizuki-blog", "portfolio-website", "task-manager-app"],
		color: "#3178C6",
	},
	{
		id: "react",
		name: "React",
		description:
			"A JavaScript library for building user interfaces, including Hooks, Context, and state management.",
		icon: "logos:react",
		category: "frontend",
		level: "advanced",
		experience: { years: 2, months: 10 },
		projects: ["portfolio-website", "task-manager-app"],
		color: "#61DAFB",
	},
	{
		id: "vue",
		name: "Vue.js",
		description:
			"A progressive JavaScript framework that is easy to learn and use, suitable for rapid development.",
		icon: "logos:vue",
		category: "frontend",
		level: "intermediate",
		experience: { years: 1, months: 8 },
		projects: ["data-visualization-tool"],
		color: "#4FC08D",
	},
	{
		id: "angular",
		name: "Angular",
		description:
			"An enterprise-level frontend framework developed by Google, a complete single-page application solution.",
		icon: "logos:angular-icon",
		category: "frontend",
		level: "beginner",
		experience: { years: 0, months: 9 },
		projects: ["enterprise-dashboard"],
		color: "#DD0031",
	},
	{
		id: "nextjs",
		name: "Next.js",
		description:
			"A production-level React framework supporting SSR, SSG, and full-stack development.",
		icon: "logos:nextjs-icon",
		category: "frontend",
		level: "intermediate",
		experience: { years: 1, months: 4 },
		projects: ["e-commerce-frontend", "blog-platform"],
		color: "#616161", // 更改为深灰色,避免纯黑色
	},
	{
		id: "nuxtjs",
		name: "Nuxt.js",
		description:
			"An intuitive Vue.js framework supporting server-side rendering and static site generation.",
		icon: "logos:nuxt-icon",
		category: "frontend",
		level: "beginner",
		experience: { years: 0, months: 6 },
		projects: ["vue-ssr-app"],
		color: "#00DC82",
	},
	{
		id: "astro",
		name: "Astro",
		description:
			"A modern static site generator supporting multi-framework integration and excellent performance.",
		icon: "logos:astro-icon",
		category: "frontend",
		level: "advanced",
		experience: { years: 1, months: 2 },
		projects: ["mizuki-blog"],
		color: "#FF5D01",
	},
	{
		id: "tailwindcss",
		name: "Tailwind CSS",
		description:
			"A utility-first CSS framework for rapidly building modern user interfaces.",
		icon: "logos:tailwindcss-icon",
		category: "frontend",
		level: "advanced",
		experience: { years: 2, months: 0 },
		projects: ["mizuki-blog", "portfolio-website"],
		color: "#06B6D4",
	},
	{
		id: "sass",
		name: "Sass/SCSS",
		description:
			"A CSS preprocessor providing advanced features like variables, nesting, and mixins.",
		icon: "logos:sass",
		category: "frontend",
		level: "intermediate",
		experience: { years: 2, months: 3 },
		projects: ["legacy-website", "component-library"],
		color: "#CF649A",
	},
	{
		id: "webpack",
		name: "Webpack",
		description:
			"A static module bundler for modern JavaScript applications.",
		icon: "logos:webpack",
		category: "frontend",
		level: "intermediate",
		experience: { years: 1, months: 10 },
		projects: ["custom-build-tool", "spa-application"],
		color: "#8DD6F9",
	},
	{
		id: "vite",
		name: "Vite",
		description:
			"Next-generation frontend build tool with fast cold starts and hot updates.",
		icon: "logos:vitejs",
		category: "frontend",
		level: "intermediate",
		experience: { years: 1, months: 2 },
		projects: ["vue-project", "react-project"],
		color: "#646CFF",
	},

	// Backend Skills
	{
		id: "nodejs",
		name: "Node.js",
		description:
			"A JavaScript runtime based on Chrome V8 engine, used for server-side development.",
		icon: "logos:nodejs-icon",
		category: "backend",
		level: "intermediate",
		experience: { years: 2, months: 3 },
		projects: ["data-visualization-tool", "e-commerce-platform"],
		color: "#339933",
	},
	{
		id: "python",
		name: "Python",
		description:
			"A general-purpose programming language suitable for web development, data analysis, machine learning, and more.",
		icon: "logos:python",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 10 },
		color: "#3776AB",
	},
	{
		id: "java",
		name: "Java",
		description:
			"A mainstream programming language for enterprise application development, cross-platform and object-oriented.",
		icon: "logos:java",
		category: "backend",
		level: "intermediate",
		experience: { years: 2, months: 0 },
		projects: ["enterprise-system", "microservices-api"],
		color: "#ED8B00",
	},
	{
		id: "csharp",
		name: "C#",
		description:
			"A modern object-oriented programming language developed by Microsoft, suitable for the .NET ecosystem.",
		icon: "devicon:csharp",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 6 },
		projects: ["desktop-application", "web-api"],
		color: "#239120",
	},
	{
		id: "go",
		name: "Go",
		description:
			"An efficient programming language developed by Google, suitable for cloud-native and microservices development.",
		icon: "logos:go",
		category: "backend",
		level: "beginner",
		experience: { years: 0, months: 8 },
		projects: ["microservice-demo"],
		color: "#00ADD8",
	},
	{
		id: "rust",
		name: "Rust",
		description:
			"A systems programming language focusing on safety, speed, and concurrency, with no garbage collector.",
		icon: "logos:rust",
		category: "backend",
		level: "beginner",
		experience: { years: 0, months: 6 },
		projects: ["system-tool", "performance-critical-app"],
		color: "#CE422B",
	},
	{
		id: "cpp",
		name: "C++",
		description:
			"A high-performance systems programming language widely used in game development, system software, and embedded development.",
		icon: "logos:c-plusplus",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 4 },
		projects: ["game-engine", "system-optimization"],
		color: "#00599C",
	},
	{
		id: "c",
		name: "C",
		description:
			"A low-level systems programming language, the foundation for operating systems and embedded systems development.",
		icon: "logos:c",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 2 },
		projects: ["embedded-system", "kernel-module"],
		color: "#A8B9CC",
	},
	{
		id: "kotlin",
		name: "Kotlin",
		description:
			"A modern programming language developed by JetBrains, fully compatible with Java, the preferred choice for Android development.",
		icon: "logos:kotlin-icon",
		category: "backend",
		level: "beginner",
		experience: { years: 0, months: 8 },
		projects: ["android-app", "kotlin-backend"],
		color: "#7F52FF",
	},
	{
		id: "swift",
		name: "Swift",
		description:
			"A modern programming language developed by Apple for iOS, macOS, watchOS, and tvOS development.",
		icon: "logos:swift",
		category: "backend",
		level: "beginner",
		experience: { years: 0, months: 6 },
		projects: ["ios-app", "macos-tool"],
		color: "#FA7343",
	},
	{
		id: "ruby",
		name: "Ruby",
		description:
			"A dynamic, open-source programming language focusing on simplicity and productivity, the foundation of the Rails framework.",
		icon: "logos:ruby",
		category: "backend",
		level: "beginner",
		experience: { years: 0, months: 4 },
		projects: ["web-prototype"],
		color: "#CC342D",
	},
	{
		id: "php",
		name: "PHP",
		description:
			"A widely-used server-side scripting language, particularly suitable for web development.",
		icon: "logos:php",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 6 },
		projects: ["cms-system", "e-commerce-backend"],
		color: "#777BB4",
	},
	{
		id: "express",
		name: "Express.js",
		description: "A fast, minimalist Node.js web application framework.",
		icon: "simple-icons:express",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 8 },
		projects: ["data-visualization-tool"],
		color: "#616161", // 更改为深灰色,避免纯黑色
	},
	{
		id: "spring",
		name: "Spring Boot",
		description:
			"The most popular enterprise application development framework in the Java ecosystem.",
		icon: "logos:spring-icon",
		category: "backend",
		level: "intermediate",
		experience: { years: 1, months: 4 },
		projects: ["enterprise-system", "rest-api"],
		color: "#6DB33F",
	},
	{
		id: "django",
		name: "Django",
		description:
			"A high-level Python web framework with rapid development and clean, pragmatic design.",
		icon: "logos:django-icon",
		category: "backend",
		level: "beginner",
		experience: { years: 0, months: 6 },
		projects: ["blog-backend"],
		color: "#092E20",
	},

	// Database Skills
	{
		id: "mysql",
		name: "MySQL",
		description:
			"The world's most popular open-source relational database management system, widely used in web applications.",
		icon: "logos:mysql-icon",
		category: "database",
		level: "advanced",
		experience: { years: 2, months: 6 },
		projects: ["e-commerce-platform", "blog-system"],
		color: "#4479A1",
	},
	{
		id: "postgresql",
		name: "PostgreSQL",
		description:
			"A powerful open-source relational database management system.",
		icon: "logos:postgresql",
		category: "database",
		level: "intermediate",
		experience: { years: 1, months: 5 },
		projects: ["e-commerce-platform"],
		color: "#336791",
	},
	{
		id: "redis",
		name: "Redis",
		description:
			"A high-performance in-memory data structure store, used as a database, cache, and message broker.",
		icon: "logos:redis",
		category: "database",
		level: "intermediate",
		experience: { years: 1, months: 3 },
		projects: ["e-commerce-platform", "real-time-chat"],
		color: "#DC382D",
	},
	{
		id: "mongodb",
		name: "MongoDB",
		description:
			"A document-oriented NoSQL database with a flexible data model.",
		icon: "logos:mongodb-icon",
		category: "database",
		level: "intermediate",
		experience: { years: 1, months: 2 },
		color: "#47A248",
	},
	{
		id: "sqlite",
		name: "SQLite",
		description:
			"A lightweight embedded relational database, suitable for mobile applications and small projects.",
		icon: "simple-icons:sqlite",
		category: "database",
		level: "intermediate",
		experience: { years: 1, months: 8 },
		projects: ["mobile-app", "desktop-tool"],
		color: "#003B57",
	},
	{
		id: "firebase",
		name: "Firebase",
		description:
			"Google's mobile and web application development platform providing real-time database and authentication services.",
		icon: "simple-icons:firebase",
		category: "database",
		level: "intermediate",
		experience: { years: 0, months: 10 },
		projects: ["task-manager-app"],
		color: "#FFCA28",
	},

	// Tools
	{
		id: "git",
		name: "Git",
		description:
			"A distributed version control system, an essential tool for code management and team collaboration.",
		icon: "logos:git-icon",
		category: "tools",
		level: "advanced",
		experience: { years: 3, months: 0 },
		color: "#F05032",
	},
	{
		id: "vscode",
		name: "VS Code",
		description:
			"A lightweight but powerful code editor with a rich plugin ecosystem.",
		icon: "logos:visual-studio-code",
		category: "tools",
		level: "expert",
		experience: { years: 3, months: 6 },
		color: "#007ACC",
	},
	{
		id: "webstorm",
		name: "WebStorm",
		description:
			"A professional JavaScript and web development IDE developed by JetBrains with intelligent code assistance.",
		icon: "logos:webstorm",
		category: "tools",
		level: "advanced",
		experience: { years: 2, months: 0 },
		projects: ["react-project", "vue-project"],
		color: "#00CDD7",
	},
	{
		id: "intellij",
		name: "IntelliJ IDEA",
		description:
			"JetBrains flagship IDE, the preferred tool for Java development with powerful intelligent coding assistance.",
		icon: "logos:intellij-idea",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 8 },
		projects: ["java-enterprise", "spring-boot-app"],
		color: "#616161", // 更改为深灰色,避免纯黑色
	},
	{
		id: "pycharm",
		name: "PyCharm",
		description:
			"A professional Python IDE by JetBrains providing intelligent code analysis and debugging features.",
		icon: "logos:pycharm",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 4 },
		projects: ["python-web-app", "data-analysis"],
		color: "#21D789",
	},
	{
		id: "rider",
		name: "Rider",
		description:
			"A cross-platform .NET IDE by JetBrains supporting development in C#, VB.NET, F#, and other languages.",
		icon: "logos:rider",
		category: "tools",
		level: "beginner",
		experience: { years: 0, months: 8 },
		projects: ["dotnet-api", "desktop-app"],
		color: "#616161", // 更改为深灰色,避免纯黑色
	},
	{
		id: "goland",
		name: "GoLand",
		description:
			"A professional Go language IDE by JetBrains providing intelligent coding assistance and debugging tools.",
		icon: "logos:goland",
		category: "tools",
		level: "beginner",
		experience: { years: 0, months: 6 },
		projects: ["go-microservice"],
		color: "#3D7BF7",
	},
	{
		id: "docker",
		name: "Docker",
		description:
			"A containerization platform that simplifies application deployment and environment management.",
		icon: "logos:docker-icon",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 0 },
		color: "#2496ED",
	},
	{
		id: "kubernetes",
		name: "Kubernetes",
		description:
			"A container orchestration platform for automating deployment, scaling, and management of containerized applications.",
		icon: "logos:kubernetes",
		category: "tools",
		level: "beginner",
		experience: { years: 0, months: 4 },
		projects: ["microservices-deployment"],
		color: "#326CE5",
	},
	{
		id: "nginx",
		name: "Nginx",
		description: "A high-performance web server and reverse proxy server.",
		icon: "logos:nginx",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 2 },
		projects: ["web-server-config", "load-balancer"],
		color: "#009639",
	},
	{
		id: "apache",
		name: "Apache HTTP Server",
		description:
			"The world's most popular web server software, a stable and reliable HTTP server.",
		icon: "logos:apache",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 6 },
		projects: ["traditional-web-server", "php-hosting"],
		color: "#D22128",
	},
	{
		id: "openresty",
		name: "OpenResty",
		description:
			"A high-performance web platform based on Nginx and LuaJIT, supporting dynamic web application development.",
		icon: "simple-icons:nginx",
		category: "tools",
		level: "beginner",
		experience: { years: 0, months: 8 },
		projects: ["api-gateway", "dynamic-routing"],
		color: "#00A693",
	},
	{
		id: "tomcat",
		name: "Apache Tomcat",
		description:
			"A Java Servlet container and web server, the standard deployment environment for Java web applications.",
		icon: "logos:tomcat",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 4 },
		projects: ["java-web-app", "servlet-container"],
		color: "#F8DC75",
	},
	{
		id: "aws",
		name: "AWS",
		description:
			"Amazon's cloud platform providing comprehensive cloud computing solutions.",
		icon: "logos:aws",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 0 },
		projects: ["cloud-deployment", "serverless-app"],
		color: "#FF9900",
	},
	{
		id: "linux",
		name: "Linux",
		description:
			"An open-source operating system, the preferred choice for server deployment and development environments.",
		icon: "logos:linux-tux",
		category: "tools",
		level: "intermediate",
		experience: { years: 2, months: 0 },
		projects: ["server-management", "shell-scripting"],
		color: "#FCC624",
	},
	{
		id: "postman",
		name: "Postman",
		description:
			"An API development and testing tool that simplifies API design, testing, and documentation.",
		icon: "logos:postman-icon",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 8 },
		projects: ["api-testing", "api-documentation"],
		color: "#FF6C37",
	},
	{
		id: "figma",
		name: "Figma",
		description:
			"A collaborative interface design tool for UI/UX design and prototyping.",
		icon: "logos:figma",
		category: "tools",
		level: "intermediate",
		experience: { years: 1, months: 6 },
		color: "#F24E1E",
	},
	{
		id: "photoshop",
		name: "Photoshop",
		description: "Professional image editing and design software.",
		icon: "logos:adobe-photoshop",
		category: "tools",
		level: "intermediate",
		experience: { years: 2, months: 6 },
		projects: ["ui-design", "image-processing"],
		color: "#31A8FF",
	},

	// Other Skills
	{
		id: "graphql",
		name: "GraphQL",
		description:
			"An API query language and runtime providing a more efficient, powerful, and flexible way to fetch data.",
		icon: "logos:graphql",
		category: "other",
		level: "beginner",
		experience: { years: 0, months: 6 },
		projects: ["modern-api"],
		color: "#E10098",
	},
	{
		id: "elasticsearch",
		name: "Elasticsearch",
		description:
			"A distributed search and analytics engine used for full-text search and data analysis.",
		icon: "logos:elasticsearch",
		category: "other",
		level: "beginner",
		experience: { years: 0, months: 4 },
		projects: ["search-system"],
		color: "#005571",
	},
	{
		id: "jest",
		name: "Jest",
		description:
			"A JavaScript testing framework focused on simplicity and ease of use.",
		icon: "logos:jest",
		category: "other",
		level: "intermediate",
		experience: { years: 1, months: 2 },
		projects: ["unit-testing", "integration-testing"],
		color: "#C21325",
	},
	{
		id: "cypress",
		name: "Cypress",
		description:
			"A modern end-to-end testing framework for web applications.",
		icon: "logos:cypress-icon",
		category: "other",
		level: "beginner",
		experience: { years: 0, months: 8 },
		projects: ["e2e-testing"],
		color: "#17202C",
	},
];

添加国际化翻译

  • 添加翻译键,文件路径src/i18n/i18nKey.ts

  • 添加英文翻译,文件路径src/i18n/languages/en.ts

  • 添加中文翻译,文件路径src/i18n/languages/zh_CN.ts

  • 添加繁体中文翻译,文件路径src/i18n/languages/zh_TW.ts

  • 添加日文翻译,文件路径src/i18n/languages/ja.ts

  • 添加俄文翻译,文件路径src/i18n/languages/ru.ts

ts
export enum I18nKey {
  // 技能页面
	skills = "skills",
	skillsSubtitle = "skillsSubtitle",
	skillsFrontend = "skillsFrontend",
	skillsBackend = "skillsBackend",
	skillsDatabase = "skillsDatabase",
	skillsTools = "skillsTools",
	skillsOther = "skillsOther",
	skillLevel = "skillLevel",
	skillLevelBeginner = "skillLevelBeginner",
	skillLevelIntermediate = "skillLevelIntermediate",
	skillLevelAdvanced = "skillLevelAdvanced",
	skillLevelExpert = "skillLevelExpert",
	skillExperience = "skillExperience",
	skillYears = "skillYears",
	skillMonths = "skillMonths",
	skillsTotal = "skillsTotal",
	skillsExpert = "skillsExpert",
	skillsAdvanced = "skillsAdvanced",
	skillsIntermediate = "skillsIntermediate",
	skillsBeginner = "skillsBeginner",
	skillsAdvancedTitle = "skillsAdvancedTitle",
	skillsProjects = "skillsProjects",
	skillsDistribution = "skillsDistribution",
	skillsByLevel = "skillsByLevel",
	skillsByCategory = "skillsByCategory",
	noData = "noData",
}
ts
	// Skills Page
	[Key.skills]: "Skills",
	[Key.skillsSubtitle]: "My technical skills and expertise",
	[Key.skillsFrontend]: "Frontend Development",
	[Key.skillsBackend]: "Backend Development",
	[Key.skillsDatabase]: "Database",
	[Key.skillsTools]: "Development Tools",
	[Key.skillsOther]: "Other Skills",
	[Key.skillLevel]: "Proficiency",
	[Key.skillLevelBeginner]: "Beginner",
	[Key.skillLevelIntermediate]: "Intermediate",
	[Key.skillLevelAdvanced]: "Advanced",
	[Key.skillLevelExpert]: "Expert",
	[Key.skillExperience]: "Experience",
	[Key.skillYears]: "years",
	[Key.skillMonths]: "months",
	[Key.skillsTotal]: "Total Skills",
	[Key.skillsExpert]: "Expert Level",
	[Key.skillsAdvanced]: "Advanced",
	[Key.skillsIntermediate]: "Intermediate",
	[Key.skillsBeginner]: "Beginner",
	[Key.skillsAdvancedTitle]: "Professional Skills",
	[Key.skillsProjects]: "Related Projects",
	[Key.skillsDistribution]: "Skill Distribution",
	[Key.skillsByLevel]: "By Level",
	[Key.skillsByCategory]: "By Category",
	[Key.noData]: "No data",
ts
	// 技能展示页面
	[Key.skills]: "技能展示",
	[Key.skillsSubtitle]: "我的技术技能和专业知识",
	[Key.skillsFrontend]: "前端开发",
	[Key.skillsBackend]: "后端开发",
	[Key.skillsDatabase]: "数据库",
	[Key.skillsTools]: "开发工具",
	[Key.skillsOther]: "其他技能",
	[Key.skillLevel]: "熟练度",
	[Key.skillLevelBeginner]: "初学者",
	[Key.skillLevelIntermediate]: "中级",
	[Key.skillLevelAdvanced]: "高级",
	[Key.skillLevelExpert]: "专家",
	[Key.skillExperience]: "经验",
	[Key.skillYears]: "年",
	[Key.skillMonths]: "个月",
	[Key.skillsTotal]: "总技能数",
	[Key.skillsExpert]: "专家级",
	[Key.skillsAdvanced]: "高级",
	[Key.skillsIntermediate]: "中级",
	[Key.skillsBeginner]: "初级",
	[Key.skillsAdvancedTitle]: "专业技能",
	[Key.skillsProjects]: "相关项目",
	[Key.skillsDistribution]: "技能分布",
	[Key.skillsByLevel]: "按等级分布",
	[Key.skillsByCategory]: "按分类分布",
	[Key.noData]: "暂无数据",
ts
	// 技能展示頁面
	[Key.skills]: "技能展示",
	[Key.skillsSubtitle]: "我的技術技能和專業知識",
	[Key.skillsFrontend]: "前端開發",
	[Key.skillsBackend]: "後端開發",
	[Key.skillsDatabase]: "資料庫",
	[Key.skillsTools]: "開發工具",
	[Key.skillsOther]: "其他技能",
	[Key.skillLevel]: "熟練度",
	[Key.skillLevelBeginner]: "初學者",
	[Key.skillLevelIntermediate]: "中級",
	[Key.skillLevelAdvanced]: "高級",
	[Key.skillLevelExpert]: "專家",
	[Key.skillExperience]: "經驗",
	[Key.skillYears]: "年",
	[Key.skillMonths]: "個月",
	[Key.skillsTotal]: "總技能數",
	[Key.skillsExpert]: "專家級",
	[Key.skillsAdvanced]: "高級",
	[Key.skillsIntermediate]: "中級",
	[Key.skillsBeginner]: "初級",
	[Key.skillsAdvancedTitle]: "專業技能",
	[Key.skillsProjects]: "相關專案",
	[Key.skillsDistribution]: "技能分布",
	[Key.skillsByLevel]: "按等級分布",
	[Key.skillsByCategory]: "按分類分布",
ts
// スキルページ
	[Key.skills]: "スキル",
	[Key.skillsSubtitle]: "技術スキルと専門知識",
	[Key.skillsFrontend]: "フロントエンド開発",
	[Key.skillsBackend]: "バックエンド開発",
	[Key.skillsDatabase]: "データベース",
	[Key.skillsTools]: "開発ツール",
	[Key.skillsOther]: "その他のスキル",
	[Key.skillLevel]: "熟練度",
	[Key.skillLevelBeginner]: "初心者",
	[Key.skillLevelIntermediate]: "中級者",
	[Key.skillLevelAdvanced]: "上級者",
	[Key.skillLevelExpert]: "専門家",
	[Key.skillExperience]: "経験の合計",
	[Key.skillYears]: "年",
	[Key.skillMonths]: "ヶ月",
	[Key.skillsTotal]: "スキルの合計",
	[Key.skillsExpert]: "エキスパートレベル",
	[Key.skillsAdvanced]: "上級者",
	[Key.skillsIntermediate]: "中級者",
	[Key.skillsBeginner]: "初心者",
	[Key.skillsAdvancedTitle]: "プロフェッショナルスキル",
	[Key.skillsProjects]: "関連プロジェクト",
	[Key.skillsDistribution]: "スキル分布",
	[Key.skillsByLevel]: "レベル別",
	[Key.skillsByCategory]: "カテゴリー別",
	[Key.noData]: "データなし",
ts
	// Страница навыков
	[Key.skills]: "Навыки",
	[Key.skillsSubtitle]: "Мои технические навыки и профессиональные знания",
	[Key.skillsFrontend]: "Фронтенд разработка",
	[Key.skillsBackend]: "Бэкенд разработка",
	[Key.skillsDatabase]: "Базы данных",
	[Key.skillsTools]: "Инструменты разработки",
	[Key.skillsOther]: "Другие навыки",
	[Key.skillLevel]: "Уровень навыка",
	[Key.skillLevelBeginner]: "Начинающий",
	[Key.skillLevelIntermediate]: "Средний",
	[Key.skillLevelAdvanced]: "Продвинутый",
	[Key.skillLevelExpert]: "Эксперт",
	[Key.skillExperience]: "Опыт",
	[Key.skillYears]: "лет",
	[Key.skillMonths]: "месяцев",
	[Key.skillsTotal]: "Всего навыков",
	[Key.skillsExpert]: "Эксперт",
	[Key.skillsAdvanced]: "Продвинутый",
	[Key.skillsIntermediate]: "Средний",
	[Key.skillsBeginner]: "Начинающий",
	[Key.skillsAdvancedTitle]: "Профессиональные навыки",
	[Key.skillsProjects]: "Связанные проекты",
	[Key.skillsDistribution]: "Распределение навыков",
	[Key.skillsByLevel]: "По уровню",
	[Key.skillsByCategory]: "По категориям",
	[Key.noData]: "Нет данных",

新增项技能页面组件

文件路径src/pages/skills.astro

astro
---
import { FilterTabs } from "@components/atoms/";
import { PageHeader } from "@components/features/page-header";
import { SkillCard } from "@components/features/skills";
import MainGridLayout from "@layouts/MainGridLayout.astro";
import { Icon } from "astro-icon/components";

import { siteConfig } from "../config";
import { skillsData } from "../data/skills";
import I18nKey from "../i18n/i18nKey";
import { i18n } from "../i18n/translation";

if (!siteConfig.pages.skills) {
	return Astro.redirect("/404/");
}

const categories = [...new Set(skillsData.map((skill) => skill.category))];

const getCategoryText = (category: string) => {
	switch (category) {
		case "frontend":
			return i18n(I18nKey.skillsFrontend);
		case "backend":
			return i18n(I18nKey.skillsBackend);
		case "database":
			return i18n(I18nKey.skillsDatabase);
		case "tools":
			return i18n(I18nKey.skillsTools);
		case "other":
			return i18n(I18nKey.skillsOther);
		default:
			return category;
	}
};

const getCategoryIcon = (category: string) => {
	switch (category) {
		case "frontend":
			return "material-symbols:web";
		case "backend":
			return "material-symbols:dns";
		case "database":
			return "material-symbols:storage";
		case "tools":
			return "material-symbols:build";
		case "other":
			return "material-symbols:widgets";
		default:
			return "material-symbols:category";
	}
};

const filterTabs = [
	{
		value: "all",
		label: i18n(I18nKey.friendsFilterAll),
		icon: "material-symbols:apps",
		count: skillsData.length,
	},
	...categories.map((category) => ({
		value: category,
		label: getCategoryText(category),
		icon: getCategoryIcon(category),
		count: skillsData.filter((s) => s.category === category).length,
	})),
];

const title = i18n(I18nKey.skills);
const subtitle = i18n(I18nKey.skillsSubtitle);
---

<MainGridLayout title={title} description={subtitle}>
	<script>
		// import("../scripts/right-sidebar-layout.js");
		import { loadIconify } from "../utils/icon-loader";

		loadIconify().catch((error) => {
			console.error("Failed to load Iconify:", error);
		});
	</script>

	<script is:inline src="/js/filter-tabs-handler.js"></script>

	<div
		class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32"
	>
		<div class="card-base z-10 px-6 sm:px-9 py-6 relative w-full">
			<PageHeader title={title} subtitle={subtitle} />

			<div class="mb-8">
				<FilterTabs tabs={filterTabs} dataAttr="category" />
			</div>

			<div id="skills-grid" class="grid gap-6 items-start">
				{skillsData.map((skill) => <SkillCard skill={skill} />)}
			</div>

			<div id="no-results" class="hidden text-center py-16">
				<Icon
					name="material-symbols:search-off-rounded"
					class="text-6xl text-black/15 dark:text-white/15 mb-4"
				/>
				<p class="text-black/40 dark:text-white/40 text-lg">
					No matching skills
				</p>
			</div>
		</div>
	</div>
</MainGridLayout>

<style is:global>
	#skills-grid {
		grid-template-columns: 1fr;
	}

	@media (min-width: 640px) {
		#skills-grid {
			grid-template-columns: repeat(2, minmax(0, 1fr));
		}
	}

	#main-grid[data-layout-mode="grid"] #skills-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	@media (max-width: 639px) {
		#main-grid[data-layout-mode="grid"] #skills-grid {
			grid-template-columns: 1fr;
		}
	}
</style>

创建图标加载器

  • utils 添加页图标加载器,文件路径src/utils/icon-loader.ts
ts
// 图标加载工具类
// 提供可靠的Iconify图标加载解决方案

interface IconifyLoadOptions {
	timeout?: number;
	retryCount?: number;
	retryDelay?: number;
}

class IconLoader {
	private static instance: IconLoader;
	private isLoaded = false;
	private isLoading = false;
	private loadPromise: Promise<void> | null = null;
	private observers = new Set<() => void>();

	private constructor() {}

	static getInstance(): IconLoader {
		if (!IconLoader.instance) {
			IconLoader.instance = new IconLoader();
		}
		return IconLoader.instance;
	}

	/**
	 * 加载Iconify图标库
	 */
	async loadIconify(options: IconifyLoadOptions = {}): Promise<void> {
		const { timeout = 10000, retryCount = 3, retryDelay = 1000 } = options;

		// 如果已经加载完成,直接返回
		if (this.isLoaded) {
			return Promise.resolve();
		}

		// 如果正在加载,返回现有的Promise
		if (this.isLoading && this.loadPromise) {
			return this.loadPromise;
		}

		this.isLoading = true;
		this.loadPromise = this.loadWithRetry(timeout, retryCount, retryDelay);

		try {
			await this.loadPromise;
			this.isLoaded = true;
			this.notifyObservers();
		} catch (error) {
			console.error("Failed to load Iconify after all retries:", error);
			throw error;
		} finally {
			this.isLoading = false;
		}
	}

	/**
	 * 带重试机制的加载
	 */
	private async loadWithRetry(
		timeout: number,
		retryCount: number,
		retryDelay: number,
	): Promise<void> {
		for (let attempt = 1; attempt <= retryCount; attempt++) {
			try {
				await this.loadScript(timeout);
				return;
			} catch (error) {
				console.warn(`Iconify load attempt ${attempt} failed:`, error);

				if (attempt === retryCount) {
					throw new Error(
						`Failed to load Iconify after ${retryCount} attempts`,
					);
				}

				// 等待后重试
				await new Promise((resolve) => setTimeout(resolve, retryDelay));
			}
		}
	}

	/**
	 * 加载脚本
	 */
	private loadScript(timeout: number): Promise<void> {
		return new Promise((resolve, reject) => {
			// 检查是否已经存在脚本
			const existingScript = document.querySelector(
				'script[src*="iconify-icon"]',
			);
			if (existingScript) {
				// 检查Iconify是否已经可用
				if (this.isIconifyReady()) {
					resolve();
					return;
				}
			}

			const script = document.createElement("script");
			script.src =
				"https://code.iconify.design/iconify-icon/3-latest/iconify-icon.min.js";
			script.async = true;
			script.defer = true;

			const timeoutId = setTimeout(() => {
				script.remove();
				reject(new Error("Iconify script load timeout"));
			}, timeout);

			script.onload = () => {
				clearTimeout(timeoutId);
				// 等待Iconify完全初始化
				this.waitForIconifyReady().then(resolve).catch(reject);
			};

			script.onerror = () => {
				clearTimeout(timeoutId);
				script.remove();
				reject(new Error("Failed to load Iconify script"));
			};

			document.head.appendChild(script);
		});
	}

	/**
	 * 等待Iconify完全准备就绪
	 */
	private waitForIconifyReady(maxWait = 5000): Promise<void> {
		return new Promise((resolve, reject) => {
			const startTime = Date.now();

			const checkReady = () => {
				if (this.isIconifyReady()) {
					resolve();
					return;
				}

				if (Date.now() - startTime > maxWait) {
					reject(new Error("Iconify initialization timeout"));
					return;
				}

				setTimeout(checkReady, 100);
			};

			checkReady();
		});
	}

	/**
	 * 检查Iconify是否准备就绪
	 */
	private isIconifyReady(): boolean {
		return (
			typeof window !== "undefined" &&
			"customElements" in window &&
			customElements.get("iconify-icon") !== undefined
		);
	}

	/**
	 * 添加加载完成观察者
	 */
	onLoad(callback: () => void): void {
		if (this.isLoaded) {
			callback();
		} else {
			this.observers.add(callback);
		}
	}

	/**
	 * 移除观察者
	 */
	offLoad(callback: () => void): void {
		this.observers.delete(callback);
	}

	/**
	 * 通知所有观察者
	 */
	private notifyObservers(): void {
		this.observers.forEach((callback) => {
			try {
				callback();
			} catch (error) {
				console.error("Error in icon load observer:", error);
			}
		});
		this.observers.clear();
	}

	/**
	 * 获取加载状态
	 */
	getLoadState(): { isLoaded: boolean; isLoading: boolean } {
		return {
			isLoaded: this.isLoaded,
			isLoading: this.isLoading,
		};
	}

	/**
	 * 预加载指定图标
	 */
	async preloadIcons(icons: string[]): Promise<void> {
		if (!this.isLoaded) {
			await this.loadIconify();
		}

		// 等待图标加载
		return new Promise((resolve) => {
			let loadedCount = 0;
			const totalIcons = icons.length;

			if (totalIcons === 0) {
				resolve();
				return;
			}

			const checkComplete = () => {
				loadedCount++;
				if (loadedCount >= totalIcons) {
					resolve();
				}
			};

			// 创建临时图标元素来触发加载
			icons.forEach((icon) => {
				const tempIcon = document.createElement("iconify-icon");
				tempIcon.setAttribute("icon", icon);
				tempIcon.style.display = "none";
				tempIcon.onload = checkComplete;
				tempIcon.onerror = checkComplete; // 即使加载失败也要继续
				document.body.appendChild(tempIcon);

				// 清理临时元素
				setTimeout(() => {
					if (tempIcon.parentNode) {
						tempIcon.parentNode.removeChild(tempIcon);
					}
				}, 1000);
			});

			// 设置超时
			setTimeout(() => {
				resolve();
			}, 5000);
		});
	}
}

// 导出单例实例
export const iconLoader = IconLoader.getInstance();

// 导出便捷函数
export const initIconLoader = (options?: IconifyLoadOptions) =>
	iconLoader.loadIconify(options);
export const loadIconify = (options?: IconifyLoadOptions) =>
	iconLoader.loadIconify(options);
export const preloadIcons = (icons: string[]) => iconLoader.preloadIcons(icons);
export const onIconsReady = (callback: () => void) =>
	iconLoader.onLoad(callback);
最近更新