技能页面
前言
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.astro和src/components/atoms/Icon/types.ts
文件路径:src/components/misc/Icon.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>export interface IconProps {
icon: string;
class?: string;
style?: string;
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
color?: string;
fallback?: string;
loading?: "lazy" | "eager";
}---
/**
* 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.astro和src/components/atoms/index.ts
---
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>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技能卡片组件
export { default as SkillCard } from "./SkillCard.astro";
export * from "./types";import type { Skill } from "../../../data/skills";
export interface SkillCardProps {
skill: Skill;
}---
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
// 页面开关配置
pages: {
// ... 其他配置
// 技能页面开关
skills: true,
},// 我的及其子菜单
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
export type SiteConfig = {
// 页面开关配置
pages: {
skills?: boolean; // 技能页面开关
};
}
export enum LinkPreset {
Skills = 12, // ✨ 新增
}export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
[LinkPreset.Skills]: {
name: i18n(I18nKey.skills),
url: "/skills/",
icon: "material-symbols:psychology",
},
}技能数据管理
文件路径:src/data/skills.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
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",
} // 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", // 技能展示页面
[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]: "暂无数据", // 技能展示頁面
[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.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]: "データなし", // Страница навыков
[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
---
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
// 图标加载工具类
// 提供可靠的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);