阅读视图

完全免费的网站状态监控服务 - UptimeFlare

完全免费的网站状态监控服务 - UptimeFlare ​

UptimeFlare 是一个基于 Cloudflare Workers 的 Serverless 网站状态监控服务,完全免费且易于部署。本文将详细介绍其技术架构、核心功能实现以及开发部署流程。项目 fork 自 lyc8503/UptimeFlare,在原有基础上进行了界面美化和功能优化。在线演示:status.weizwz.com,项目地址:weizwz/UptimeFlare。预览图如下:

技术栈概览 ​

系统架构 ​

整体架构 ​

数据流 ​

  1. 监控流程

    Cron Trigger → Worker 检查 → 更新 D1 → 记录状态/故障
  2. 页面渲染流程

    用户访问 → Pages 请求 → 读取 D1 → SSR 渲染 → 返回 HTML
  3. 实时更新流程

    Worker 定时检查 → 状态变化 → 更新数据库 → 前端轮询/刷新

项目结构 ​

UptimeFlare/
├── components/              # React 组件
│   ├── MonitorCard.tsx     # 监控卡片
│   ├── MonitorDetail.tsx   # 监控详情
│   ├── DetailBar.tsx       # 数据可视化
│   ├── MaintenanceAlert.tsx # 维护通知
│   ├── IncidentsDrawer.tsx # 历史故障侧边栏
│   └── OverallStatus.tsx   # 整体状态

├── pages/                   # Next.js 页面
│   ├── index.tsx           # 主页
│   ├── incidents.tsx       # 历史故障页面
│   └── api/                # API 路由
│       └── data.ts         # 数据接口

├── worker/                  # Cloudflare Worker
│   └── src/
│       ├── index.ts        # Worker 入口
│       ├── monitor.ts      # 监控逻辑
│       ├── store.ts        # 数据存储
│       └── util.ts         # 工具函数

├── locales/                 # 国际化
│   ├── en/                 # 英文
│   ├── zh-CN/              # 简体中文
│   └── zh-TW/              # 繁体中文

├── types/                   # TypeScript 类型
│   └── config.ts           # 配置类型定义

├── uptime.config.ts        # 监控配置
├── wrangler.toml           # Worker 配置
├── next.config.js          # Next.js 配置
└── package.json            # 依赖管理

开发指南 ​

1. 安装依赖 ​

bash
npm install

2. 监控配置项 ​

重要

uptime.config.ts 里的监控服务 monitors 建议首次就配置好,尤其是里面的 id,多次修改会造成脏数据。所有配置项都可以参考 uptime.config.full.ts 文件

uptime.config.ts
typescript
const pageConfig: PageConfig = {
  // 标题
  title: '服务状态 - weizwz',
  // 分组,根据 monitors 里的服务 id
  group: {
    基础服务: ['weiwz', 'blog', 'picture'],
    实用工具: ['nav', 'cover'],
    演示项目: ['fun_animation', 'charts']
  }
}
export const workerConfig = {
  monitors: [
    {
      id: 'blog', // 唯一标识,不要随意修改
      name: '博客',
      method: 'HEAD',
      target: 'https://note.weizwz.com/', // 必需,监控 url
      statusPageLink: 'https://note.weizwz.com/', // 项目点击链接,有了之后界面可以点击跳转
      preview: 'https://p.weizwz.com/siteshot_note.webp' // 监控项目主页预览图,非必须
    }
  ],
  notification: {
    webhook: {
      // 通知必需,建议去 `https://resend.com/` 注册,免费。后面有具体教程
      url: 'https://api.resend.com/emails',
      payload: {
        from: '系统状态更新 <uptimeflare@updates.weizwz.com>', // 你的线上监控服务域名
        to: ['xxxxxx@gmial.com'], // 你的邮箱
        subject: 'UptimeFlare 状态更新',
        text: '$MSG'
      }
    }
  }
}
// 公告,非必须
const maintenances: MaintenanceConfig[] = [
  // {
  //   title: '图床服务维护',
  //   body: '正在进行图床服务器升级维护,预计2小时完成',
  //   monitors: ['picture'],
  //   start: '2026-02-01T18:00:00+08:00',
  //   end: '2026-02-21T23:00:00+08:00',
  // },
]

3. 本地数据库初始化 &ZeroWidthSpace;

重要说明

  • 数据库文件自动创建:首次运行 npm run dev 时,Wrangler 会自动在 .wrangler/state/v3/d1/ 目录创建 SQLite 数据库文件
  • ⚠️ 表结构需要手动创建:数据库文件虽然自动创建,但 uptimeflare 表需要手动执行 SQL 创建
  • ⚠️ 数据需要手动导入:表创建后,需要手动插入初始数据或从生产环境导入

步骤 3.1:启动开发服务器

bash
npm run dev
# 此时会自动创建数据库文件,但表结构未创建,页面无数据
# 访问 http://localhost:3000

步骤 3.2:创建表结构

bash
# 使用 init.sql 初始化表结构
npx wrangler d1 execute UPTIMEFLARE_D1 --local --file=./init.sql

步骤 3.3:初始化数据

选择以下任一方式:

  1. 手动插入空数据(首次部署)
bash
# 插入空的初始状态
npx wrangler d1 execute UPTIMEFLARE_D1 --local \
  --command "INSERT INTO uptimeflare (key, value) VALUES ('state', '{\"overallUp\":0,\"overallDown\":0,\"lastUpdate\":0,\"incident\":{},\"latency\":{}}')"
  1. 从生产环境导入(推荐)
bash
# 1. 从生产环境导出数据
npx wrangler d1 execute UPTIMEFLARE_D1 \
  --command "SELECT value FROM uptimeflare WHERE key = 'state'" \
  --json > .wrangler/backup_data.json
# 2. 让 AI 将数据导入到本地,或者手动使用脚本
python3 .wrangler/import_local_data.py
附导入脚本
py
#!/usr/bin/env python3
"""
UptimeFlare - 本地数据导入脚本

从生产环境导出的 JSON 数据导入到本地 D1 数据库

使用方法:
1. 先从生产环境导出数据:
   npx wrangler d1 execute UPTIMEFLARE_D1 \
     --command "SELECT value FROM uptimeflare WHERE key = 'state'" \
     --json > backup_data.json

2. 运行此脚本导入到本地:
   python3 scripts/import_local_data.py
"""

import json
import subprocess
import sys
import os

def main():
    # 检查备份文件是否存在
    backup_file = "backup_data.json"
    if not os.path.exists(backup_file):
        print(f"❌ 错误: 找不到备份文件 '{backup_file}'")
        print("\n请先从生产环境导出数据:")
        print('  npx wrangler d1 execute UPTIMEFLARE_D1 \\')
        print('    --command "SELECT value FROM uptimeflare WHERE key = \'state\'" \\')
        print('    --json > backup_data.json')
        sys.exit(1)

    # 读取备份数据
    print(f"📖 读取备份文件: {backup_file}")
    try:
        with open(backup_file, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except json.JSONDecodeError as e:
        print(f"❌ 错误: JSON 格式错误 - {e}")
        sys.exit(1)
    except Exception as e:
        print(f"❌ 错误: 读取文件失败 - {e}")
        sys.exit(1)

    # 提取 state 数据
    if not data or len(data) == 0:
        print("❌ 错误: 备份文件为空")
        sys.exit(1)

    # Wrangler 导出的格式通常是数组,取第一条记录
    if isinstance(data, list) and len(data) > 0:
        state_value = data[0].get('value')
    elif isinstance(data, dict):
        state_value = data.get('value')
    else:
        print("❌ 错误: 无法识别的数据格式")
        sys.exit(1)

    if not state_value:
        print("❌ 错误: 找不到 'value' 字段")
        sys.exit(1)

    print(f"✅ 成功读取数据 ({len(state_value)} 字符)")

    # 转义 JSON 字符串中的特殊字符
    # 将单引号替换为两个单引号(SQLite 转义方式)
    escaped_value = state_value.replace("'", "''")

    # 构建 SQL 命令
    sql_command = f"INSERT OR REPLACE INTO uptimeflare (key, value) VALUES ('state', '{escaped_value}')"

    print("\n🔄 导入数据到本地 D1 数据库...")

    # 执行 wrangler 命令
    try:
        result = subprocess.run(
            [
                'npx', 'wrangler', 'd1', 'execute', 'UPTIMEFLARE_D1',
                '--local',
                '--command', sql_command
            ],
            capture_output=True,
            text=True,
            check=True
        )

        print("✅ 数据导入成功!")
        print("\n📊 Wrangler 输出:")
        if result.stdout:
            print(result.stdout)

        # 验证导入
        print("\n🔍 验证导入结果...")
        verify_result = subprocess.run(
            [
                'npx', 'wrangler', 'd1', 'execute', 'UPTIMEFLARE_D1',
                '--local',
                '--command', "SELECT key, LENGTH(value) as value_length FROM uptimeflare WHERE key = 'state'"
            ],
            capture_output=True,
            text=True,
            check=True
        )

        print(verify_result.stdout)
        print("\n✨ 完成! 现在可以重启开发服务器:")
        print("  npm run dev")

    except subprocess.CalledProcessError as e:
        print(f"\n❌ 错误: 导入失败")
        print(f"错误信息: {e.stderr}")
        sys.exit(1)
    except FileNotFoundError:
        print("\n❌ 错误: 找不到 npx 命令")
        print("请确保已安装 Node.js 和 npm")
        sys.exit(1)

if __name__ == "__main__":
    main()

步骤 3.4:重启开发服务器

bash
# 停止之前的服务器(Ctrl+C)
# 重新启动
npm run dev
# 现在应该可以正常访问了

生产部署 &ZeroWidthSpace;

1. 创建 Cloudflare API 令牌 &ZeroWidthSpace;

创建步骤如图示,注意第 5 步的时候,要添加对数据库 D1 的编辑权限,是这个默认模板里没有的,之后就是继续确定等操作了。

创建完成后记得复制 API,然后将该 API 绑定到你的 Github 仓库,key 名:CLOUDFLARE_API_TOKEN

2. 创建通知 API &ZeroWidthSpace;

https://resend.com/,注册并创建一个 API,API 复制备用。然后再创建一个 domain 来绑定你的 API。如果你的域名直接在 CloudFlare 上托管的话,直接会提示自动修改 CloudFlare 的 DNS 解析,如果是其他服务商的域名,需要你按他的要求去修改。

同 Cloudflare API 一样,这个 API 也去 GitHub 仓库添加并绑定,key 名:RESEND_API_KEY

3. 创建部署到 CloudFlare 的工作流 &ZeroWidthSpace;

在 GitHub 仓库的 Action 里,直接运行已有的工作流 .github/workflows/deploy.yml

4. 在 CloudFlare 里查看服务并检查 &ZeroWidthSpace;

运行正常的话,你的 Workers 和 Pages 里会多出两项服务,一个 uptimeflare page 和一个 uptimeflare_worker,数据库里会多出一个 uptimeflare_d1 数据库。

uptimeflare page里设置一下自定义域,即可访问你的状态监控服务

注意事项 &ZeroWidthSpace;

  1. D1 数据库限制

    • ✅ 免费版:每天 100,000 次读取
    • ⚠️ 单次查询最大 1MB
    • ⚠️ 写入有延迟(最终一致性)
  2. Worker 限制

    • ✅ CPU 时间:10ms (免费) / 50ms (付费)
    • ⚠️ 内存:128MB
    • ⚠️ 请求大小:100MB
  3. Next.js Edge Runtime

    • ⚠️ 不支持 Node.js API
    • ⚠️ 不支持某些 npm 包
    • ✅ 使用 runtime = 'experimental-edge'
  4. 本地数据库

    • ⚠️ D1 本地数据库与生产环境完全独立
    • ⚠️ 本地调试需要导入数据
    • ✅ 数据库文件位于 .wrangler/state/v3/d1/ 目录
    • ✅ 重置数据库:删除 .wrangler/state 目录后重新执行步骤3

参考资料 &ZeroWidthSpace;

  •  

VSCode Snippets 配置指南:打造高效写作与开发环境

VSCode Snippets 配置指南:打造高效写作与开发环境 &ZeroWidthSpace;

在日常的博客写作和代码开发中,重复性的输入往往占据了大量时间。VSCode 的用户代码片段(User Snippets)功能是一个强大的工具,它允许我们定义简写(Prefix),通过 Tab 键快速展开为预设的代码块(Body)。

本文将结合 .vscode 目录下的实际配置,分享一套专为 VitePress 写作和 Vue 开发打造的高效 Snippets 方案。

说明 &ZeroWidthSpace;

VSCode 支持全局 Snippets 和项目级 Snippets。一般我们配置 项目级 Snippets,将配置放在项目的 .vscode 目录下(如 .vscode/markdown.code-snippets)有以下好处:

  1. 团队共享:配置随代码仓库提交,团队成员开箱即用。
  2. 针对性强:只在当前项目中生效,避免污染全局环境。
  3. 易于维护:集中管理,修改方便。

如何配置 &ZeroWidthSpace;

  1. 在项目根目录创建 .vscode 文件夹。
  2. 新建 *.code-snippets 文件(如 global.code-snippets 为全局文件配置,markdown.code-snippets 专为 markdown 文件配置)。
  3. 官方配置文档参见 VSCode - Snippets in Visual Studio Code

下面分享个人的一些常用配置:

1. 全局通用配置 (global.code-snippets) &ZeroWidthSpace;

有些代码片段在多种语言中都可能用到,比如代码区域标记。

  • 通用代码 region 区域以便引用
json
{
  "通用代码 region 区域以便引用 /* #region */ 代码片段 /* #endregion */": {
    "prefix": "!region",
    "body": ["/* #region $1 */", "$2", "/* #endregion $1 */"],
    "description": "引用方式 ``` @文件路径#区域名 或者 <!--@include 文件路径#区域名-->>"
  }
}

使用效果:输入 !region -> Tab,生成 #region 注释块,方便 VitePress 的代码片段引用功能。

2. Vue 开发提效 (vue3.code-snippets) &ZeroWidthSpace;

在 Vue 文件或 Markdown 中编写 Vue 组件时,基础模版必不可少。

  • Vue3 基础模版 (!vue3),生成包含 <template>, <script setup>, <style scoped> 的标准结构。
json
{
  "vue3 基础模版": {
    "scope": "vue",
    "prefix": "!vue3",
    "body": ["<template>", "\t<div>$1</div>", "</template>\n", "<script setup lang='ts'>", "$2", "</script>\n", "<style lang='scss' scoped>\n", "</style>", ""],
    "description": "vue3 基础模版"
  }
}

3. Markdown 写作增强 (markdown.code-snippets) &ZeroWidthSpace;

这是提升博客写作效率的重头戏,涵盖了从新建文档到复杂排版的方方面面。

  • 快速新建文档 (!newmd),一键生成包含 Frontmatter 的标准文档结构。
json
"vitepress 新建markdown文档": {
  "scope": "markdown",
  "prefix": "!newmd",
  "body": ["---", "title: $1", "description: $2", "tags: ", " - $3", "---", "", "# $1", "", "$4"],
  "description": "vitepress 新建markdown文档"
}
  • 折叠容器 (!details)
json
"vitepress 折叠容器 details": {
  "scope": "markdown",
  "prefix": "!details",
  "body": ["::: details $1", "$2", ":::"],
  "description": "vitepress 折叠容器 details"
},
  • 代码组 (!group)
json
"vitepress 自定义代码块组 code-group": {
  "scope": "markdown",
  "prefix": "!group",
  "body": ["::: code-group", "", "```$1 [$2]", "$3", "```", "", ":::"],
  "description": "vitepress 自定义代码块组 code-group"
}
  • 快速插入 GitHub 风格的警告块,支持 NOTE, TIP, IMPORTANT, WARNING, CAUTION
json
"vitepress github风格提示 tip": {
  "scope": "markdown",
  "prefix": "!tip",
  "body": ["> [!TIP] $1", "> $2"],
  "description": "github风格提示 tip"
}
  • 代码块快捷指令,为常用的语言代码块设置快捷键,避免手动输入 ```,比如:
json
"vitepress 常用代码块 js": {
  "scope": "markdown",
  "prefix": "!js",
  "body": ["```js", "$1", "```"],
  "description": "vitepress 常用代码块 js"
}
  • 代码引用 (!include, `!````),配合 VitePress 的代码引入功能,快速生成引用语法。
json
"vitepress 代码块,内部引用其他文件内的代码 ```": {
  "scope": "markdown",
  "prefix": "!```",
  "body": ["``` @$1#$2"],
  "description": "vitepress代码块,内部引用其他文件内的代码 ```"
}

注:在 Markdown 文件中,我们也也可以配置类似的 !vue3 指令,方便直接在文档中演示组件。

附录 &ZeroWidthSpace;

分享个人配置的具体文件,供大家参考:

json
{
  // 代码region区域
  "通用 代码region区域以便引用 /* #region */ 代码片段 /* #endregion */": {
    "prefix": "!region",
    "body": ["/* #region $1 */", "$2", "/* #endregion $1 */"],
    "description": "引用方式 ``` @文件路径#区域名 或者 <!--@include 文件路径#区域名-->>"
  }
}
json
{
  // 新建markdown文档
  "vitepress 新建markdown文档": {
    "scope": "markdown",
    "prefix": "!newmd",
    "body": ["---", "title: $1", "description: $2", "tags: ", " - $3", "---", "", "# $1", "", "$4"],
    "description": "vitepress 新建markdown文档"
  },
  // mrkdown中快速插入Vue3模版
  "vitepress markdown中插入vue3模版": {
    "scope": "markdown",
    "prefix": "!vue3",
    "body": ["<template>", "\t<div>$1</div>", "</template>\n", "<script setup lang='ts'>", "$2", "</script>\n", "<style lang='scss' scoped>\n", "</style>", ""],
    "description": "vitepress markdown中插入vue3模版"
  },
  // 折叠容器
  "vitepress 折叠容器 details": {
    "scope": "markdown",
    "prefix": "!details",
    "body": ["::: details $1", "$2", ":::"],
    "description": "vitepress 折叠容器 details"
  },
  // 代码块组
  "vitepress 自定义代码块组 code-group": {
    "scope": "markdown",
    "prefix": "!group",
    "body": ["::: code-group", "", "```$1 [$2]", "$3", "```", "", ":::"],
    "description": "vitepress 自定义代码块组 code-group"
  },
  // 代码引用 include
  "vitepress 代码块,内部引用其他文件内的代码 @include": {
    "scope": "markdown",
    "prefix": "!include",
    "body": ["```$1 [.vitepress/$2.$1]", "", "```"],
    "description": "vitepress代码块,内部引用其他文件内的代码 @include"
  },
  // 代码引用 ```
  "vitepress 代码块,内部引用其他文件内的代码 ```": {
    "scope": "markdown",
    "prefix": "!```",
    "body": ["``` @$1#$2"],
    "description": "vitepress代码块,内部引用其他文件内的代码 ```"
  },
  // github风格提示
  "vitepress github风格提示 note": {
    "scope": "markdown",
    "prefix": "!note",
    "body": ["> [!NOTE] $1", "> $2"],
    "description": "github 风格提示 note"
  },
  "vitepress github风格提示 tip": {
    "scope": "markdown",
    "prefix": "!tip",
    "body": ["> [!TIP] $1", "> $2"],
    "description": "github风格提示 tip"
  },
  "vitepress github风格提示 important": {
    "scope": "markdown",
    "prefix": "!important",
    "body": ["> [!IMPORTANT] $1", "> $2"],
    "description": "github风格提示 important"
  },
  "vitepress github风格提示 warning": {
    "scope": "markdown",
    "prefix": "!warning",
    "body": ["> [!WARNING] $1", "> $2"],
    "description": "github风格提示 warning"
  },
  "vitepress github风格提示 caution": {
    "scope": "markdown",
    "prefix": "!caution",
    "body": ["> [!CAUTION] $1", "> $2"],
    "description": "github风格提示 caution"
  },
  // 常用代码块
  "vitepress 常用代码块 markdown": {
    "scope": "markdown",
    "prefix": "!md",
    "body": ["```md", "$1", "```"],
    "description": "vitepress 常用代码块 markdown"
  },
  "vitepress 常用代码块 shell": {
    "scope": "markdown",
    "prefix": "!sh",
    "body": ["```sh", "$1", "```"],
    "description": "vitepress 常用代码块 shell"
  },
  "vitepress 常用代码块 vue": {
    "scope": "markdown",
    "prefix": "!vue",
    "body": ["```vue", "$1", "```"],
    "description": "vitepress 常用代码块 vue"
  },
  "vitepress 常用代码块 js": {
    "scope": "markdown",
    "prefix": "!js",
    "body": ["```js", "$1", "```"],
    "description": "vitepress 常用代码块 js"
  },
  "vitepress 常用代码块 ts": {
    "scope": "markdown",
    "prefix": "!ts",
    "body": ["```ts", "$1", "```"],
    "description": "vitepress 常用代码块 ts"
  },
  "vitepress 常用代码块 html": {
    "scope": "markdown",
    "prefix": "!html",
    "body": ["```html", "$1", "```"],
    "description": "vitepress 常用代码块 html"
  },
  "vitepress 常用代码块 css": {
    "scope": "markdown",
    "prefix": "!css",
    "body": ["```css", "$1", "```"],
    "description": "vitepress 常用代码块 css"
  },
  "vitepress 常用代码块 scss": {
    "scope": "markdown",
    "prefix": "!scss",
    "body": ["```scss", "$1", "```"],
    "description": "vitepress 常用代码块 scss"
  }
}
json
{
  "vue3 基础模版": {
    "scope": "vue",
    "prefix": "!vue3",
    "body": ["<template>", "\t<div>$1</div>", "</template>\n", "<script setup lang='ts'>", "$2", "</script>\n", "<style lang='scss' scoped>\n", "</style>", ""],
    "description": "vue3 基础模版"
  }
}
  •  

Antigravity Skills 全局安装与配置指南

Antigravity Skills 全局安装与配置指南 &ZeroWidthSpace;

本文将指导你如何为 Antigravity 编辑器安装全局 Skills(技能),特别是强大的 ui-ux-pro-max 设计系统,并演示如何在具体项目中调用它们。

1. 核心概念 &ZeroWidthSpace;

在 Antigravity 中,技能系统分为两层:

  • Skills (全局库):实际的代码、脚本和指南,存储在系统级目录(如 ~/.gemini/antigravity/skills)。它们是“能力”的本体。
  • Workflows (项目级):存储在项目根目录的 .agent/workflows 中。它们是“遥控器”,定义了如何在当前项目中调用全局的 Skills。

这种分离设计确保了你的项目代码库保持轻量,同时又能复用强大的全局能力。

2. 全局安装 Skills &ZeroWidthSpace;

2.1 准备目录 &ZeroWidthSpace;

首先,确保全局 Skills 目录存在。Antigravity 通常使用以下路径:

bash
mkdir -p ~/.gemini/antigravity/skills
cd ~/.gemini/antigravity/skills

2.2 安装 Anthropic 官方 Skills &ZeroWidthSpace;

这是基础技能库,包含前端设计、文档编写、测试等通用能力。

bash
git clone https://github.com/anthropics/skills.git
# 此时目录结构应为 ~/.gemini/antigravity/skills/skills/...

2.3 安装 UI-UX-Pro-Max &ZeroWidthSpace;

这是进阶的前端设计技能,包含智能配色、排版和反模式检查。

bash
git clone https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git
# 此时目录结构应为 ~/.gemini/antigravity/skills/ui-ux-pro-max-skill/...

全部安装完成后,目录如下:

3. 项目级配置 (Workflows) &ZeroWidthSpace;

要在某个项目中使用这些技能,你需要创建 Workflow 文件。建议将 .agent/ 添加到 .gitignore 中,以免污染代码库。

3.1 配置 UI-UX-Pro-Max &ZeroWidthSpace;

在项目根目录下创建 .agent/workflows/ui-ux-pro-max.md

.agent/workflows/ui-ux-pro-max.md
markdown
---
description: Generate a professional design system using UI UX Pro Max skill.
---

# UI UX Pro Max Workflow

This workflow uses the globally installed `ui-ux-pro-max-skill` to generate a comprehensive design system and implementation guidelines.

## 1. Identify Requirements

- **Query**: What is the user trying to build? (e.g., "SaaS landing page", "Crypto dashboard").
- **Stack**: What is the tech stack? (Default: `vue` or `html-tailwind` based on project).

## 2. Generate Design System

- Run the search script to generate the design system.
- **Command**:
  ```bash
  python3 /Users/weiz/.gemini/antigravity/skills/ui-ux-pro-max-skill/src/ui-ux-pro-max/scripts/search.py "[User Query]" --design-system --format markdown --stack vue
  ```
  _(Replace `[User Query]` with the actual request)_

## 3. Apply Design

- Read the output from the script.
- Use the generated colors, typography, and patterns to implement the user's request.
- **Critical**: Follow the "Anti-patterns" section to avoid generic AI mistakes.

3.2 配置通用 Skills (如 Frontend Design) &ZeroWidthSpace;

创建 .agent/workflows/frontend-design.md

.agent/workflows/frontend-design.md
markdown
---
description: Apply the frontend-design skill to create distinctive interfaces.
---

# Frontend Design Workflow

This workflow guides the creation of distinctive, production-grade frontend interfaces, avoiding generic "AI aesthetics".

## 1. Analyze Requirements

- **Context**: Understand the user's request (component, page, app).
- **Goal**: Identify the problem to solve and the target audience.

## 2. Adopt Design Persona

- **Tone**: Choose a bold aesthetic (e.g., Brutalist, Glassmorphism, Neobrutalism, Minimalist Luxury).
- **Differentiation**: What makes this design unforgettable?
- **Constraint**: Respect technical constraints (Vue/VitePress in this project).

## 3. Aesthetic Guidelines (The "Pro Max" Standard)

- **Typography**: Use distinctive font weights and tracking. Avoid generic system font stacks if possible, or style them uniquely.
- **Color**: Use cohesive, bold palettes. High contrast or subtle sophisticated gradients.
- **Motion**:
  - Use `transition-all duration-xxx` for smooth state changes.
  - Add entrance animations (e.g., `animate-fade-in`).
  - Micro-interactions on hover/active states.
- **Composition**:
  - Use generous negative space.
  - Break the grid where appropriate.
  - Use depth (shadows, layers, blur).
- **Details**:
  - Add texture (noise, gradients).
  - Custom cursors or scrollbars if appropriate.
  - Glassmorphism (`backdrop-blur`, `bg-opacity`).

## 4. Implementation Steps

1.  **Scaffold**: Create the file structure.
2.  **Style**: Apply Tailwind classes for the chosen aesthetic.
3.  **Interact**: Add Vue logic for state and animations.
4.  **Refine**: Review against the "Generic AI" trap. Is it too boring? Add more "juice".

## 5. Review Checklist

- [ ] Is the typography distinct?
- [ ] Are there micro-interactions?
- [ ] Is the layout responsive?
- [ ] Does it feel "Premium"?

3.3 配置技能列表查询 &ZeroWidthSpace;

创建 .agent/workflows/list-skills.md

.agent/workflows/list-skills.md
markdown
---
description: List all available agent skills installed in the global workspace.
---

# List Available Skills (Global)

1.  **Check Global Skills Directory**:
    - List directories in `/Users/weiz/.gemini/antigravity/skills/skills/skills`.
2.  **Display Skills**:
    - Show the list of available skills to the user.
    - Provide a brief description if possible (by reading `SKILL.md`).

// turbo 3. **Run Command**:
`bash ls /Users/weiz/.gemini/antigravity/skills/skills/skills`

4. 如何在项目中使用 &ZeroWidthSpace;

配置完成后,你可以通过 Slash Commands自然语言 来调用这些能力。

场景 A:设计一个高大上的落地页 &ZeroWidthSpace;

指令

/ui-ux-pro-max 为我的 SaaS 产品设计一个落地页,风格要科技感强

Agent 行为

  1. Agent 会自动运行全局的 Python 脚本。
  2. 脚本分析需求,生成一套包含配色(如深空蓝+霓虹绿)、排版(如 Inter + JetBrains Mono)和布局建议的设计系统。
  3. Agent 读取这些建议,并直接编写 Vue/React 代码实现页面。

场景 B:查看有哪些能力可用 &ZeroWidthSpace;

指令

/list-skills

Agent 行为

  1. 列出所有已安装的全局技能包。

场景 C:日常开发辅助 &ZeroWidthSpace;

指令

使用 frontend-design skill 帮我优化这个按钮的交互

Agent 行为

  1. Agent 会参考 frontend-design 工作流中的“反平庸”指南。
  2. 它不会只写一个简单的 :hover,而是可能会添加 transform: scale(1.05)、光影流光效果或磁吸效果,确保符合“Pro Max”标准。

5. 总结 &ZeroWidthSpace;

通过这种 全局存储 + 本地引用 的方式,你可以:

  1. 节省空间:不需要在每个项目中重复下载几百 MB 的技能文件。
  2. 保持整洁:项目代码库中只有轻量级的 Workflow 配置文件。
  3. 能力复用:一次安装,所有项目受益。
  •  

2026 年度自用 Mac 软件推荐

2026 年度自用 Mac 软件推荐 &ZeroWidthSpace;

过去一年,新发现了好多好用的软件,也卸载了不少之前使用的软件,永远走在卸载安装的路上……
过度追求完美的软件,往往会让你失望,也没有永恒的软件,就像没有不变的需求。遇到一款刚好满足你需求的软件,就快使用它!

AI 相关 &ZeroWidthSpace;

千问 - AI小助手 &ZeroWidthSpace;

千问 - 网页版:简洁,精准,支持常用工具分类。无论是生活类问题,还是代码相关,都能给出不错的回答。手机端有千问App,同步对话内容。

Kiro & Antigravity - AI编码 &ZeroWidthSpace;

第一次使用 KiroSpec 模式,满心期待,结果一言难尽。详细过程可以看我这篇记录 挑战无手写代码用 Kiro 开发一个导航网站。然后到 11 月的时候,因为它的免费额度还不错,又试用了一次,结果相当之好,可以查看我用它开发的一个 前端导航网站。当然也得益于编码模型的大跃进 Claude Sonnet 4.5,不过 Kiro 的 Spec 模式目前来看还是值得一用

试用 Antigravity,主要是冲他的 Gemini 3 模型,结果它在前端方面的表现非常令人惊喜。生成的界面样式基本去 AI 化了,对前端 UI 方面的一些简单描述理解的也很到位。尤其是,Google 很大方,免费额度较大。当然最近由于使用的人暴增,额度有所限制了,要每周刷新。如果你有学生优惠的话,可以免费使用一年,建议上某鱼试试。

Antigravity 应该是我今年的主力编辑器之一!

Readdy - AI设计 &ZeroWidthSpace;

Readdy:AI 产品设计工具,一键生成界面与代码。免费额度较少(每月刷新),但是简洁无广,设计效果还可以,适合偶尔使用的人群。

日常工具 &ZeroWidthSpace;

PixPin - 截图软件 &ZeroWidthSpace;

详见我之前的介绍 PixPin - 新截图工具。我一般设置快捷键 F1 为截图,F2 为贴图。

MacCy - 剪贴板管理 &ZeroWidthSpace;

MacCy:轻量开源 macOS 剪贴板管理器,支持搜索、图片预览。直接在菜单栏就能访问,很方便。我直接设置打开快捷键为 Option ⌥+C,用起来很丝滑。

eul - 状态监控 &ZeroWidthSpace;

eul:Mac 系统状态监控工具,可以监控 CPU、内存、磁盘、网络等状态,小巧高效,占用内存极低。

Bob - 快捷翻译 &ZeroWidthSpace;

Bob:Mac 翻译与 OCR 工具,支持多应用即用。界面简洁,功能强大,支持输入翻译、划词翻译、截图翻译、OCR 等功能。可支持调用系统词典翻译,也支持接入各类 AI大模型 的 API。设置打开快捷键为 Option ⌥+E,划词快捷键为 Option ⌥+D后,在网页中划词,然后翻译,使用非常丝滑。

超级右键 - 快捷菜单 &ZeroWidthSpace;

超级右键:强大的macOS右键菜单工具,支持自定义菜单项,支持系统菜单项。早买早享受

FastZip - 压缩工具 &ZeroWidthSpace;

FastZip:强大的macOS压缩工具,免费。主要是可以集成在 超级右键 里,UI统一,日常够用。

App Cleaner & Uninstaller &ZeroWidthSpace;

App Cleaner & Uninstaller:Mac 应用卸载与垃圾清理工具。可以设置启动项,卸载 APP 比较干净,还可以清理残留文件,占用系统资源低。

其他 &ZeroWidthSpace;

PicList - 图床工具 &ZeroWidthSpace;

PicList:高效的云存储和图床平台管理工具,基于PicGo深度二次开发。非常好用,非常丝滑,支持上传自动压缩、自动转格式、自动加水印、上传后自动复制地址,有时间单开一篇介绍。

notepad-- - 文本编辑器 &ZeroWidthSpace;

notepad-- 是一个国产跨平台、轻量级的文本编辑器,是替换notepad++的一种选择。其内置强大的代码对比功能,让你丢掉付费的 beyond compare

Caesium Image Compressor - 图片压缩 &ZeroWidthSpace;

Caesium Image Compressor:免费批量图片压缩工具,多平台。支持压缩级别、输入目录、压缩后的图片格式等。

BlueStacks Air - 安卓模拟器 &ZeroWidthSpace;

BlueStacks Air:专为 Mac M 系列芯片打造的免费安卓模拟器,日常使用很稳定。

OmniPlayer - 播放器 &ZeroWidthSpace;

OmniPlayer:Mac 播放器,支持 macOS 上几乎所有音频和视频格式。

SwitchHosts - hosts 管理工具 &ZeroWidthSpace;

SwitchHosts:跨平台 Hosts 文件管理与快速切换工具。方便你快速屏蔽系统更新,软件更新等。

……

更多有趣好玩的在线工具,请关注 唯知导航

  •  

VitePress 集成 Tailwind CSS

VitePress 集成 Tailwind CSS &ZeroWidthSpace;

随着 AI模型 对 Tailwind CSS 的高频率使用和适配,当你使用 AI 编程的时候,不使用 Tailwind CSS 都感觉有点吃亏。这算是一个很好的的理由吧,😜。 说说优点:

  1. 使用 TailwindCSS 让 AI 更懂你,也可以更好的使用 AI
  2. 可以遵循 TailwindCSS 的样式规范,代码量减少,省心省力

1. 安装依赖 &ZeroWidthSpace;

bash
pnpm install tailwindcss @tailwindcss/vite
pnpm install -D autoprefixer

2. 初始化和配置 &ZeroWidthSpace;

初始化 Tailwind CSS,然后在主题配置中导入。

友情提示

@theme 里配置好 Tailwind CSS 的变量,然后就可以直接在 class 里使用了,比如: border-border,相当于 border-(--vp-c-border)

css
@import 'tailwindcss';
/* 适配主题 */
@variant dark (.dark &);

/* 定义 css 变量和使用Tailwind 变量 */
:root {
  --border-color: var(--vp-c-border);
  --shadow-color: theme('colors.slate.200');
  --icon-color: theme('colors.neutral.900');
}

/* Tailwind 主题配置 */
@theme {
  --color-border: var(--border-color);
  --color-shadow: var(--shadow-color);
  --color-icon: var(--icon-color);
}
ts
import './style/tailwind.css'

同时,在 VitePress 的 config 中配置 Tailwind CSS。

.vitepress/config/index.ts
ts
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  vite: {
    plugins: [tailwindcss()]
  }
})

以上就是所有的集成步骤了,是不是很简单!

分享我的配置 &ZeroWidthSpace;

这里分享下我的配置,因为我后续的组件都或多或少使用到了这部分 CSS 变量

.vitepress/theme/style/tailwind.css
css
  •  

VitePress 添加 Vercount 统计

VitePress 添加 Vercount 统计 &ZeroWidthSpace;

由于 busuanzi 统计插件今年曾罢工了一段时间,然后就转到了 Vercount 统计。使用 Vercount 统计插件,发觉它功能更丰富,自定义程度高,兼容 busuanzi,还有统计面板,后续就一直使用了。这里说一下集成 Vercount 统计插件的过程,其实是跟 busuanzi 高度类似的。

提示

项目使用 Tailwind 进行了重构,本文组件样式都对此有所依赖,参见 我的 Tailwind 配置。如果不想使用 Tailwind,可以直接把代码扔给 AI,让其转化为一般 css

1. 引入 Vercount 的 JS 文件并调用 &ZeroWidthSpace;

由于 Vercount 暂不支持 npm 安装,所以这里我们对引入稍作处理。

新建 useVisitData.ts 文件,然后再主题中心调用

ts
ts
import DefaultTheme from 'vitepress/theme'
import { EnhanceAppContext, inBrowser } from 'vitepress'
import useVisitData from './hooks/useVisitData' // 网站访问统计 vercount

export default {
  extends: DefaultTheme,
  enhanceApp({ app, router }: EnhanceAppContext) {
    if (inBrowser) {
      // 访问量统计,路由加载完成,在加载页面组件后(在更新页面组件之前)调用
      router.onAfterPageLoad = () => {
        useVisitData()
      }
    }
  }
}

2. 显示网站统计 &ZeroWidthSpace;

使用方式跟 busuanzi 一致,对应 ID 显示对应统计量。而且 Vercount 兼容 Busuanzi 的 span 标签,数据会在首次访问时自动同步。

也就是说你仍然可以使用原来 busuanzi 的 ID,并且数据可以继承过来。

Vercount ID busaunzi ID 说明
vercount_value_site_pv busuanzi_value_site_pv 全站访问量
vercount_value_site_uv busuanzi_value_site_uv 全站访客量
vercount_value_page_pv busuanzi_value_page_pv 单个网页访问量

3. 定制统计组件 &ZeroWidthSpace;

对于我们之前的统计组件,这里也做了代码更新和优化。注意事项:

  1. 样式使用了 tailwind4
  2. 使用 setTimeout 是防止页面加载未完成时,不蒜子脚本已执行成功,从而无法获取统计数据
  3. WStatistics.vue 组件注册到主题配置 .vitepress/theme/index.ts 中去,就可以全局使用了,要么就局部引用局部使用

1. 组件创建 &ZeroWidthSpace;

vue
ts

2. 全局注册和使用 &ZeroWidthSpace;

ts
import DefaultTheme from 'vitepress/theme'
import WStatistics from './components/WStatistics.vue'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    // 注册自定义全局组件
    app.component('weiz-statistics', WStatistics)
  }
}
vue
<template>
  <div>
    <weiz-statistics />
  </div>
</template>

4. 单个网页统计 &ZeroWidthSpace;

参见 busuanzi - 单个网页统计,为单个文章显示统计信息

1. 组件创建 &ZeroWidthSpace;

vue
ts

2. 全局注册 &ZeroWidthSpace;

.vitepress/theme/index.ts
ts
import DefaultTheme from 'vitepress/theme'
import WDocTitleMeta from './components/WDocTitleMeta.vue' //文章顶部

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    // 注册自定义全局组件
    app.component('weiz-title-meta', WDocTitleMeta)
  }
}

3. 调用方法 &ZeroWidthSpace;

参考 VitePress 的高级配置,我们在 Markdown 渲染器 里进行拦截,监听到有 h1 标签时,将此组件插入在 h1 后面

.vitepress/config/index.ts
ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  //markdown配置
  markdown: {
    // 对markdown中的内容进行替换或者批量处理
    config: (md) => {
      // 创建 markdown-it 插件
      md.use((md) => {
        // 组件插入h1标题下
        md.renderer.rules.heading_close = (tokens, idx, options, env, slf) => {
          let htmlResult = slf.renderToken(tokens, idx, options)
          if (tokens[idx].tag === 'h1') htmlResult += `<weiz-title-meta />`
          return htmlResult
        }
      })
    }
  }
})
  •  

常用图标资源汇总

常用图标资源汇总 &ZeroWidthSpace;

整理常用图标资源

Font Awesome - 最早使用Web字体的流行图标库 &ZeroWidthSpace;

https://fontawesome.com/

Font Awesome 是一个广泛使用的图标库,包含数千个矢量图标,覆盖了从基础符号到专业用途的各方面需求,支持实心(solid)、常规(regular)、品牌(brands)等多种样式,满足不同设计场景。它还提供响应式设计支持,图标能够根据父容器自动调整大小。需要注意的是 Font Awesome 分为免费版和Pro版(付费)。

Iconfont-阿里巴巴矢量图标库 &ZeroWidthSpace;

https://www.iconfont.cn/

Iconfont 是由阿里巴巴推出的矢量图标管理网站,它允许用户从丰富的图标库中选择图标,或者上传自己的图标进行管理。通过 Iconfont,用户可以方便地将图标以多种格式(如 SVG、PNG、CSS 等)下载下来使用,也可以直接在网页开发中使用其提供的 CDN 服务或通过 @font-face 方式引用图标字体。

需要注意的是,Iconfont 的图标许可协议主要取决于上传图标的设计师或团队所设置的具体授权条款。如果需要更灵活的权限(如无需署名或允许再分发),可以选择明确标注为“完全开源”或“无版权限制”的图标资源。

Iconify Design - 最丰富的图标库之一 &ZeroWidthSpace;

https://iconify.design/

Iconify Design 是一个集成多种流行图标集的框架,提供超过20万个开源矢量图标。它旨在为开发者和设计师提供一个便捷的平台,让用户能够轻松访问和使用各种高质量的图标资源。

重点,Iconify Design 的图标可以 在线引用,也支持 UnoCSSTailwind CSS在线查找 Iconify Design 图标,随便选一个图标点击即可查看它的使用方式

Material Icons - Google 开源图标库 &ZeroWidthSpace;

https://fonts.google.com/icons

Material Icons 是由 Google 提供的一套图标库,基于其 Material Design 设计语言。这套图标库提供了大量美观且一致的图标资源,非常适合用于 Android 应用、网站以及其他遵循 Material Design 规范的项目中。

Lucide Icon - 简洁、高效的图标库之一 &ZeroWidthSpace;

https://lucide.dev/

Lucide Icon 是一个开源的图标库,它提供了一系列简洁且高效的图标。最主要的是它支持各种方式集成到项目中,也支持众多主流框架,比如 Vue 、React、 Angular等。当前 Lucide Icon 也是被 shadcn/ui 建议和使用的,也是我目前在使用 React 框架时选择的图标库之一。

CSS*GG - 可使用纯CSS绘制的图标库 &ZeroWidthSpace;

https://css.gg/

CSS*GG 是一个全面的开源 CSS 图标库,具有 Vanilla CSS、SVG 和 Figma UI 图标。它最大的特点,是你可以用 纯CSS 代码去绘制出它的图标,而不依赖于 web字体或svg或图片资源。

Remix Icon - 极简风格图标库 &ZeroWidthSpace;

https://remixicon.com/

Remix Icon 是一个开源、免费的极简风格图标库,专为设计师和开发者设计。它提供了一套风格统一、简洁现代的高质量矢量图标,非常适合各种应用和网站项目。

Bootstrap Icons - 从Bootstrap中独立出来的图标库 &ZeroWidthSpace;

https://icons.getbootstrap.com/

Bootstrap曾是最流行的UI库之一,随着Bootstrap 4.3及之后版本推出的官方图标库,包含众多与Bootstrap组件相匹配的图标。这些图标最初是为配合Bootstrap的样式和组件设计的,但现在也可以独立使用。

macOSicons - 最丰富的 MacOS 图标集 &ZeroWidthSpace;

https://macosicons.com/

macOS Icons 专注于为Mac操作系统提供高质量、风格统一的图标集。它包含 5000+ MacOS 高质量图标,且数量在逐年增加。它支持用户自由的下载或上传自己的图标,目前已涵盖 MacOS 上大部分的应用。

Simple Icons - 品牌图标大全 &ZeroWidthSpace;

https://simpleicons.org/

Simple Icons 是一个收集了众多品牌和项目的单一颜色图标库。这些图标以 SVG 格式提供,非常适合用于网站、应用等场合的品牌展示。Simple Icons 的特色在于其简洁性和统一性,所有图标都只使用一种颜色,并尽力保持官方品牌的识别元素。Simple Icons 是开源的,允许用户免费使用并在遵守相应许可协议的前提下进行修改和分享。

Shields.io - 图标徽章集 &ZeroWidthSpace;

https://shields.io/

Shields.io 是一个广泛使用的在线服务,用于生成小巧且易于理解的徽章(badges),这些徽章通常用于展示项目的状态、版本、构建通过率、代码覆盖率等信息。开发者经常在 GitHub、GitLab 等代码托管平台上使用这些徽章来直观地反映项目的健康状况和最新动态。

具体使用方法可参考我的这篇文章 Github 项目徽标

其他UI库中自带的图标 &ZeroWidthSpace;

大多数的 UI 库都会自带图标,比如:

Element UI 图标

Ant Design 图标

……等

绘制图标网站 &ZeroWidthSpace;

https://yqnn.github.io/svg-path-editor/ ,一个可以自由绘制svg图标的简易网站

  •  

VitePress 建站资源汇总

VitePress 建站资源汇总 &ZeroWidthSpace;

整理下使用 VitePress 搭建博客过程中使用过的一些资源和方案

主要参考站点 &ZeroWidthSpace;

VitePress 官方文档

VitePress快速上手中文教程,这个站点扩展很全,包括静态部署选择,样式美化,第三方插件等,都是手把手教程,很细

XaviDocs个人技术文档,借鉴了部分 VitePress 的使用经验

VitePress主题 vitepress-theme-async 源码,借鉴了部分对 VitePress 的配置和操作逻辑,阅读这些代码对你理解 VitePress 会有很大帮助

晚阳Crown,借鉴了使用 Cloudflare R2 搭建图床以及使用 Cloudflare 管理域名的经验

基础配置和样式美化 &ZeroWidthSpace;

暗黑模式切换动画 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/basic/dark-animation

链接前加图标 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/basic/link-icon

优化代码块为mac风格 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/basic/code-block

优化自定义容器 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/basic/custom-block

使用 DocSearch 搜索 &ZeroWidthSpace;

官方教程 https://vitepress.dev/zh/reference/default-theme-search#algolia-search

详细教程 https://vitepress.yiov.top/docsearch.html

点击查看我的配置
ts
import type { DefaultTheme } from 'vitepress'

export const algolia: DefaultTheme.AlgoliaSearchOptions = {
  appId: 'appid',
  apiKey: 'apikey',
  indexName: 'weizwz',
  placeholder: '搜索文档',
  translations: {
    button: {
      buttonText: '搜索文档',
      buttonAriaLabel: '搜索文档'
    },
    modal: {
      searchBox: {
        resetButtonTitle: '清除查询条件',
        resetButtonAriaLabel: '清除查询条件',
        cancelButtonText: '取消',
        cancelButtonAriaLabel: '取消'
      },
      startScreen: {
        recentSearchesTitle: '搜索历史',
        noRecentSearchesText: '没有搜索历史',
        saveRecentSearchButtonTitle: '保存至搜索历史',
        removeRecentSearchButtonTitle: '从搜索历史中移除',
        favoriteSearchesTitle: '收藏',
        removeFavoriteSearchButtonTitle: '从收藏中移除'
      },
      errorScreen: {
        titleText: '无法获取结果',
        helpText: '你可能需要检查你的网络连接'
      },
      footer: {
        selectText: '选择',
        navigateText: '切换',
        closeText: '关闭',
        searchByText: '搜索提供者'
      },
      noResultsScreen: {
        noResultsText: '无法找到相关结果',
        suggestedQueryText: '你可以尝试查询',
        reportMissingResultsText: '你认为该查询应该有结果?',
        reportMissingResultsLinkText: '点击反馈'
      }
    }
  }
}

新建页面和注册组件 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/basic/pages-components

功能扩展 &ZeroWidthSpace;

添加图片查看器 Fancybox &ZeroWidthSpace;

Fancybox 是一款非常流行且功能强大的 JavaScript 图片查看库

集成参考 https://note.weizwz.com/vitepress/extend/vitepress-fancybox

添加不蒜子统计 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/extend/busuanzi

添加 Vercount 统计 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/extend/vercount

文章统计,自定义首页,归档页,标签页 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/extend/post-data

其他参考资料:

vitepress-theme-async 主题文章统计

添加友链界面 &ZeroWidthSpace;

参考 https://note.weizwz.com/vitepress/extend/links

部署相关 &ZeroWidthSpace;

静态部署 &ZeroWidthSpace;

部署到 GitHub Pages ,参考文章

https://vitepress.yiov.top/assets.html#部署

基本流程: 项目根目录建立文件 .github/workflows/main.yml,增加自动部署配置,代码提交, GitHub 监测到工作流后自动执行

点击查看我的配置
yml
# 将静态内容部署到 GitHub Pages 的简易工作流程
name: Deploy static content to Pages

# env:
#   VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
#   VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

on:
  # 仅在推送到默认分支时运行。
  push:
    branches: ['main'],暂停部署到 GitHub pages

  # 这个选项可以使你手动在 Action tab 页面触发工作流
  workflow_dispatch:

# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages。
permissions:
  contents: read
  pages: write
  id-token: write

# 允许一个并发的部署
concurrency:
  group: 'pages'
  cancel-in-progress: true

jobs:
  # 构建
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          fetch-depth: 0
  # github page deploy
      - name: Set up pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: Setup Pages
        uses: actions/configure-pages@v4
      - name: Install dependencies
        run: pnpm install
      - name: Build
        run: pnpm run build
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          # Upload dist repository
          path: './dist'

  # 部署
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

      # vercel deploy
      # - name: Restore file modification time 🕒
        # run: find docs/post -name '*.md' | while read file; do touch -d "$(git log -1 --format="@%ct" "$file")" "$file"; done
        # run: "git ls-files -z | while read -d '' path; do touch -d \"$(git log -1 --format=\"@%ct\" \"$path\")\" \"$path\"; done"
      # - name: Install Vercel-cli🔧
      #   run: npm install --global vercel@latest
      # - name: Pull Vercel Environment Information
      #   run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      # - name: Build Project Artifacts
      #   run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      # - name: Deploy Project Artifacts to Vercel
      #   run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

这个配置,也是可以部署到 Vercel 的,注释掉 github-pages 部分,放开后面的 vercel deploy 部分即可。需要注意构建命令和输出目录,原始构建命令为 npm run docs:build,输出目录 docs/.vitepress/dist

添加 Github Giscus 评论系统 &ZeroWidthSpace;

Github Giscus 的好处简单易用,限制的地方在于需要仓库公开,代码必须托管在 Github 上。可参考我的这篇文章

https://note.weizwz.com/vitepress/extend/vitepress-giscus

其他参考链接:

Github Giscus官网

VitePress 添加评论功能

使用 Cloudflare 管理域名 &ZeroWidthSpace;

参考这篇文章

https://www.oneyangcrown.top/posts/cloudflare-hosted-domain-cdn-ssl-guide/

如果你的域名在阿里云或者腾讯云,找到域名解析,修改DNS服务器地址为 Cloudflare 上的即可

使用 Cloudflare R2 配置图床 &ZeroWidthSpace;

参考这篇文章,先配置 R2,然后使用自定义域名。

https://www.oneyangcrown.top/posts/cloudflare-r2-free-image-hosting-guide/

注意,配置过程中创建的 API令牌,访问密钥等,需要我们储存起来,方便后续在 PicList 中使用;

第二个需要注意的是,图床我们一般使用 子域名。比如,你在 Cloudflare 上管理的域名为 weizwz.com,R2 的自定义域名你可以输入 p.weizwz.com,然后 Cloudflare 会自动给你添加 子域名解析,实在是在贴心了。

关于图床API的使用,就是上传和删除等操作,这里建议使用工具 PicList,官网文档很详细,包括下载和配置等

https://piclist.cn/app.html

由于我们使用的是 Cloudflare R2,重点查看这里 PicList 内置AWS S3 配置

上传配置里,我们可以设置水印,上传时压缩图片,还有图片转换为 webp 格式,进一步减小图片体积

还可以上传配置到代码仓,方便切换设备后同步配置。在 github/gitee/gitea 中创建一个私有仓库,然后将 PicList 配置上传,参考 设置配置同步

配置同步需要的 GitHub Token,点击 GitHub 上的个人头像,Settings,拉到最下面点击 Developer settings,然后会跳转到新界面,按下图创建

创建好后,复制 Token,粘贴到 PicList 配置里,然后就可以上传你的配置到仓库了。

使用 Cloudflare Pages 托管网站 &ZeroWidthSpace;

参考这篇文章

https://www.oneyangcrown.top/posts/cloudflare-pages-deploy-hexo-blog-guide/

文章中的博客使用的是 Hexo 框架 不是 Vitepress,因此我们需要修改构建命令。VitePress 默认构建命令默认是 npm i && vitepress build docs,输入目录 docs/.vitepress/dist

我对 VitePress 原始构建配置有修改,我的构建命令是 pnpm i && pnpm run build,构建输出 ./dist。如果你也有修改的话,需要修改为你自己的配置。这个配置在 package.json 中的 scripts 中可以看到。

构建监听分支默认为主分支 main,当 main 分支有修改时,Cloudflare 将自动构建部署。

使用 Twikoo 评论系统 &ZeroWidthSpace;

Twikoo 评论,需要部署前后端,包括数据库,操作略有复杂,但是不限制代码托管和服务托管,自定义程度更高

官方文档 https://twikoo.js.org/ ,非常详细,部署步骤如下:

1. 先建立 MongoDB Atlas 数据库

2. 然后选择云厂商部署,我这里选择的 Vercel 部署,因为操作相对简单,使用 Cloudflare workers 部署 限制较多,部署成功后,修改默认域名为你的子域名,需要去你的域名管理那上修改 DNS 解析。

以 Cloudflare 为例,添加 CNAME 类型,子域名 twikoo,内容 cname.vercel-dns.com

3. 后端部署完成后,再集成到前端项目

修改我们的 VitePress 项目,参考这篇 VitePress 集成 Twikoo 评论

前端集成基本步骤:pnpm install twikoo -> 封装 Twikoo.vue 组件 -> 插入 Layout 插槽

4.配置邮件。前后端都处理好后,界面就能正常展示了,但是我们还要处理下邮件功能

首次打开设置按钮后,会有设置密码框,设置一个复杂密码并记住。然后进入配置管理,选择邮件通知

按照提示输入你的邮箱,邮箱授权码等即可。最后有个邮件测试,测试后,你能收到一封邮件,说明功能可用了

  •  

VitePress 添加不蒜子统计

VitePress 添加不蒜子统计 &ZeroWidthSpace;

不蒜子 是一个极简的网页计数器,支持统计全站访客和访问量,支持单个网页访问量,免费稳定。先上定制效果图:

1. 安装插件 &ZeroWidthSpace;

sh
pnpm add -D busuanzi.pure.js

2. 调用统计 &ZeroWidthSpace;

要触发不蒜子统计,需要我们在项目路由切换后,手动调用它的方法,从而向不蒜子后台发送请求,增加计数。所以我们修改主题配置 .vitepress/theme/index.ts

ts
import DefaultTheme from 'vitepress/theme'
import { inBrowser } from 'vitepress'
import busuanzi from 'busuanzi.pure.js'

export default {
  extends: DefaultTheme,
  enhanceApp({ app, router }) {
    if (inBrowser) {
      // 访问量统计,路由加载完成,在加载页面组件后(在更新页面组件之前)调用
      router.onAfterRouteChanged = () => {
        busuanzi.fetch()
      }
    }
  }
}

3. 显示统计量 &ZeroWidthSpace;

按不蒜子的使用说明,只要页面中出现它配套的 id,js 就会自动填充数字到对应id的元素中。

ID 说明
busuanzi_value_site_pv 全站访问量
busuanzi_value_site_uv 全站访客量
busuanzi_value_page_pv 单个网页访问量

简单示例,页面中有如下内容即可

html
本站总访问量 <span id="busuanzi_value_site_pv" /> 本站访客数 <span id="busuanzi_value_site_uv" />

4. 定制统计组件 &ZeroWidthSpace;

以上使用简单,但是不好看,没有什么设计可言。这里我们设计一个统计卡片,数字可以跳动,有逐渐增长的进度条;并且我们将统计数据存储在 sessionStorage 中,当切换页面后,数据有增长时,数字还会跳动,进度条也会增长。

直接贴代码吧,注意:

  1. 其中 scss 中的一些变量基本使用 VitePress 自带变量,无需担心
  2. 使用 setTimeout 是防止页面加载未完成时,不蒜子脚本已执行成功,从而无法获取统计数据
  3. WStatistics.vue 组件注册到主题配置 .vitepress/theme/index.ts 中去,就可以全局使用了,要么就局部引用局部使用
vue
<template>
  <div class="statistics">
    <div class="title-wrapper">
      <div class="title">
        <span>访问统计</span>
        <span class="title-hover">访问统计</span>
      </div>
      <i class="weiz-icon weiz-icon-chart-line main"></i>
    </div>
    <div class="statistics-main">
      <div class="statistics-wrapper">
        <span class="statistics-title">总访问量</span>
        <span class="statistics-pv" id="pv">{{ pv }}</span>
      </div>
      <div class="chart pv-wrapper">
        <div class="pv-num" id="pvProgress" style="width: 70%"></div>
      </div>
      <div class="statistics-wrapper">
        <span class="statistics-title">独立访客</span>
        <span class="statistics-uv" id="uv">{{ uv }}</span>
      </div>
      <div class="chart uv-wrapper">
        <div class="uv-num" id="uvProgress" style="width: 45%"></div>
      </div>
    </div>
    <span id="busuanzi_value_site_pv" style="display: none" />
    <span id="busuanzi_value_site_uv" style="display: none" />
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getSessionStorage, setSessionStorage, numberWithCommas } from '../../utils/tools'

let sessionPv = getSessionStorage('pv')
let sessionUv = getSessionStorage('uv')
const pv = ref<string | number>(sessionPv ? numberWithCommas(parseInt(sessionPv)) : 'loading')
const uv = ref<string | number>(sessionUv ? numberWithCommas(parseInt(sessionUv)) : 'loading')

let timeoutPV = 0
const getPV = () => {
  if (timeoutPV) clearTimeout(timeoutPV)
  timeoutPV = window.setTimeout(() => {
    const $PV = document.querySelector('#busuanzi_value_site_pv')
    const text = $PV?.innerHTML
    if ($PV && text) {
      const start = getSessionStorage('pv') || '1000'
      pv.value = numberWithCommas(parseInt(text))
      setSessionStorage('pv', text)
      // 调用封装的函数
      animateNumberAndProgressBar({
        counterSelector: '#pv',
        fillBarSelector: '#pvProgress',
        start: parseFloat(start),
        end: parseInt(text),
        totalDuration: 2000,
        minPercentage: 5,
        targetPercentage: 75
      })
    } else {
      getPV()
    }
  }, 500)
}

let timeoutUV = 0
const getUV = () => {
  if (timeoutUV) clearTimeout(timeoutUV)
  timeoutUV = window.setTimeout(() => {
    const $UV = document.querySelector('#busuanzi_value_site_uv')
    const text = $UV?.innerHTML
    if ($UV && text) {
      const text = $UV.innerHTML
      const start = getSessionStorage('uv') || '1000'
      uv.value = numberWithCommas(parseInt(text))
      setSessionStorage('uv', text)
      // 调用封装的函数
      animateNumberAndProgressBar({
        counterSelector: '#uv',
        fillBarSelector: '#uvProgress',
        start: parseFloat(start),
        end: parseInt(text),
        totalDuration: 2000,
        minPercentage: 5,
        targetPercentage: 50
      })
    } else {
      getUV()
    }
  }, 500)
}

// 统计数字动画
const animateNumberAndProgressBar = ({ counterSelector, fillBarSelector, start = 0, end, totalDuration = 2000, minPercentage = 5, targetPercentage = 75 }) => {
  // 如果开始和结束的数字相同,直接返回
  if (start == end) {
    return
  }
  // 调整进度条起始位置,要基本符合进度条的长度
  const maxNum = (end * 100) / targetPercentage
  let startPercentage = (start / maxNum) * 100

  const counterElement = document.querySelector(counterSelector)
  const fillBarElement = document.querySelector(fillBarSelector)

  let startTime = null
  const totalSteps = end - start

  function animateCounter(timestamp) {
    if (!startTime) startTime = timestamp
    const elapsed = timestamp - (startTime ?? timestamp)

    const progress = Math.min(elapsed / totalDuration, 1)
    const currentNumber = Math.floor(start + progress * totalSteps)
    let stepPercentage = progress * (targetPercentage - startPercentage)
    // 保证肉眼能看到至少5%的变化
    if (targetPercentage - startPercentage < minPercentage) {
      stepPercentage = progress * minPercentage
      startPercentage = targetPercentage - minPercentage
    }

    const currentProgress = startPercentage + stepPercentage

    counterElement.textContent = numberWithCommas(currentNumber)
    fillBarElement.style.width = currentProgress + '%'

    if (fillBarElement.style.display !== 'block') {
      fillBarElement.style.display = 'block'
    }

    if (progress < 1) {
      requestAnimationFrame(animateCounter)
    }
  }

  fillBarElement.style.width = startPercentage + '%'
  fillBarElement.style.display = 'block'

  requestAnimationFrame(animateCounter)
}

onMounted(() => {
  getUV()
  getPV()
})
</script>

<style lang="scss" scoped>
.statistics {
  width: 100%;
  display: inline-block;
  border-radius: 16px;
  background-color: var(--vp-c-bg);
  color: var(--vp-c-text-1);
  font-weight: 500;
  padding: 24px;
  box-shadow: 0 1px 2px 0 rgba(25, 26, 28, 0.05);
  transition: all cubic-bezier(0.175, 0.885, 0.32, 1.275) 0.6s;
  &:hover {
    color: var(--vp-c-text-1);
    transform: scale(1.03);
    box-shadow:
      0 10px 15px -3px rgba(25, 26, 28, 0.1),
      0 4px 6px -4px rgba(25, 26, 28, 0.1);
    .title .title-hover {
      width: 100%;
    }
  }
  .title-wrapper {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 32px;
  }
  .title {
    font-size: 16px;
    line-height: 1.5;
    font-weight: 600;
    white-space: nowrap;
    position: relative;
    overflow: hidden;
    .title-hover {
      position: absolute;
      left: 0;
      top: 0;
      width: 0;
      overflow: hidden;
      background-color: var(--vp-c-bg);
      color: #409eff;
      transition: width 0.4s ease-in-out;
    }
  }
  .statistics-wrapper {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
  }
  .chart {
    height: 8px;
    border-radius: 8px;
    background-color: var(--vp-c-gray-3);
    > div {
      height: 100%;
      border-radius: 8px;
      background-color: #409eff;
    }
  }
  .pv-wrapper {
    margin-bottom: 16px;
  }
  .statistics-title {
    & + span {
      font-size: 16px;
      line-height: 1.5;
      font-weight: 600;
    }
  }
}
</style>
ts

切换路由的效果展示

5. 单个网页统计 &ZeroWidthSpace;

单个网页只显示访问量太单薄,因此我们还增加了额外信息,如文章创建更新时间,文章字数等

直接贴代码,注意事项同上

vue
<template>
  <div class="weiz-title-meta">
    <div class="tags">
      <div class="created" title="发表于">
        <i class="weiz-icon weiz-icon-created gray" />
        <span>发表于 {{ firstCommit }}</span>
      </div>
      <div class="updated" title="更新于">
        <i class="weiz-icon weiz-icon-updated gray" />
        <span>更新于 {{ lastUpdated }}</span>
      </div>
      <div class="word" title="字数">
        <i class="weiz-icon weiz-icon-word gray" />
        <span>字数 {{ wordCount }}</span>
      </div>
      <div class="reader" title="阅读量">
        <i class="weiz-icon weiz-icon-user gray"></i>
        <span>阅读量 {{ pv }}<span id="busuanzi_value_page_pv" style="display: none" /></span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useData } from 'vitepress'
import { ref, onMounted } from 'vue'
import { countWord, countTransK, formatDate } from '../../utils/tools'

const { frontmatter, page } = useData()
const wordCount = ref('')
const firstCommit = ref('')
const lastUpdated = ref('')
const pv = ref('')

let timeoutPV = 0
const getPV = () => {
  if (timeoutPV) clearTimeout(timeoutPV)
  timeoutPV = window.setTimeout(() => {
    const $PV = document.querySelector('#busuanzi_value_page_pv')
    const text = $PV?.innerHTML
    if ($PV && text) {
      pv.value = countTransK(parseInt(text))
    } else {
      getPV()
    }
  }, 500)
}

onMounted(() => {
  const dateOption = formatDate()
  firstCommit.value = dateOption.format(new Date(frontmatter.value.firstCommit!)).replace(/\//g, '-')
  lastUpdated.value = dateOption.format(new Date(frontmatter.value.lastUpdated || page.value.lastUpdated!)).replace(/\//g, '-')

  const docDomContainer = window.document.querySelector('#VPContent')
  const words = docDomContainer?.querySelector('.content-container .main')?.textContent || ''
  wordCount.value = countTransK(countWord(words))

  getPV()
})
</script>

<style lang="scss" scoped>
.weiz-title-meta {
  .tags {
    display: flex;
    flex-wrap: wrap;
    margin: 0 0 32px;
    color: var(--vp-c-text-2);
    font-weight: 500;
    line-height: 18px;
    word-break: keep-all;
    > div {
      display: flex;
      align-items: center;
      margin-top: 16px;
      margin-right: 6px;
      &:last-child {
        margin-right: 0;
      }
    }
  }
  .weiz-icon {
    margin-right: 2px;
  }
}

@media (min-width: 768px) {
  .weiz-title-meta .tags > div {
    margin-right: 16px;
  }
}
</style>
ts

组件创建好后,还需要在全局注册为组件,方便我们使用

.vitepress/theme/index.ts
ts
import DefaultTheme from 'vitepress/theme'
import WDocTitleMeta from './components/WDocTitleMeta.vue' //文章顶部

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    // 注册自定义全局组件
    app.component('weiz-title-meta', WDocTitleMeta)
  }
}

WDocTitleMeta.vue 组件需要注册到配置中心后,怎么插入到单个文章页面中去呢。这就需要用到 VitePress 的高级配置,我们在 Markdown 渲染器 里进行拦截,监听到有 h1 标签时,将此组件插入在 h1 后面

.vitepress/config/index.ts
ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  //markdown配置
  markdown: {
    // 对markdown中的内容进行替换或者批量处理
    config: (md) => {
      // 创建 markdown-it 插件
      md.use((md) => {
        // 组件插入h1标题下
        md.renderer.rules.heading_close = (tokens, idx, options, env, slf) => {
          let htmlResult = slf.renderToken(tokens, idx, options)
          if (tokens[idx].tag === 'h1') htmlResult += `<weiz-title-meta />`
          return htmlResult
        }
      })
    }
  }
})
  •  

VitePress 切换暗黑模式丝滑动画

VitePress 切换暗黑模式丝滑动画 &ZeroWidthSpace;

移植之前 Hexo 博客中使用的暗黑切换动画,主要用到的原理就是 设置动态css变量原生视图过渡 - startViewTransition 以及 css的clip-path属性。先上效果图:

1. 设置全局css &ZeroWidthSpace;

新建 dark.css,并注入到主题配置 docs/.vitepress/theme/index.ts

.vitepress/theme/style/dark.css
css

切换动画速度,通过 animation 中的时间控制

2. 设置动态css变量 &ZeroWidthSpace;

新建 Dark.ts,并在 Layout 插槽中使用

ts
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { toggleDark } from './Dark'

const { isDark } = useData()
// 实现切换主题过渡动画
toggleDark(isDark)
</script>

最后将Layout 插槽组件,注入到 主题配置 docs/.vitepress/theme/index.ts

.vitepress/theme/index.ts
ts
import DefaultTheme from 'vitepress/theme'
import Layout from './Layout.vue'
// 暗黑样式
import './style/dark.css'

export default {
  extends: DefaultTheme,
  // 使用注入插槽的包装组件覆盖 Layout
  Layout: Layout
}
  •  

VitePress 统计文章,新建归档页和标签页

VitePress 统计文章,新建归档页和标签页 &ZeroWidthSpace;

VitePress 虽然没有直接提供统计文章的方法和页面,但是有提供相关 API 帮助我们完成这项工作。详情可以查看 构建时数据加载

明确文章字段 &ZeroWidthSpace;

首先我们要明确我们需要哪些文章字段,这取决于我们的文章卡片要显示哪些内容。我们能想到的必不可少的,有 文章标题,创建时间,标签,url链接。以下是一个简单的示例:

.vitepress/theme/type/WPost.ts
ts

获取文章时间 &ZeroWidthSpace;

基于以上字段,标题 、url等可以在 VitePress 内部提供,摘要、标签等,我们在写博客时可以在 frontmatter 中直接写入,剩下只有一个时间字段,我们无法获取。这就需要我们在服务端根据文章创建时间或者git提交时间来获取。

我们创建一个 fileTime.ts 文件来获取文章时间,这里利用 node 的方法和 git 的命令

.vitepress/utils/fileTime.ts
ts

统计文章数据 &ZeroWidthSpace;

根据 VitePress构建时数据加载 - 基本用法 的要求,我们新建 post.data.ts 文件

ts
ts

这里需要注意的点:

  1. post/**/!(*-demo).md' 中的 !(-demo).md 是排除以 -demo结尾的文件,如果你没有的话,可以直接写 post/**/*.md

  2. Post 类型来自于我们之前的文章类型定义

  3. formatDate 是一个格式化时间的方法,见上文代码 .vitepress/utils/tools.ts

  4. 获取文章数据主要方法还是利用官方提供的 createContentLoader

  5. 最后需要对文章按照发布时间来排序,以便我们显示最新文章

使用文章数据 &ZeroWidthSpace;

直接在 .md 页面和 .vue 组件中使用从post.data.ts 导出的 data 数据

vue
<script setup>
import { data } from './post.data.ts'
</script>

<pre>{{ data }}</pre>

归档页和标签页数据处理 &ZeroWidthSpace;

归档文章我们一般按年份展示,标签则需要根据选中标签展示对于文章。这里就需要对文章数据进行处理。

新建一个 post.ts 文件,专门用来处理文章数据

.vitepress/utils/post.ts
ts

新建归档页 &ZeroWidthSpace;

新建页面可以查看另一篇文章 VitePress 新建页面和注册组件

这里以归档页面简单做一个示例:

新建一个 post.vue 组件,内容如下:

.vitepress/theme/components/WPost/index.vue
vue

其中的 weiz-post-list 文章列表组件,你可以自由发挥

然后将此组件注册到全局主题中

.vitepress/theme/index.ts
ts
import Post from './Post/index.vue'

export default {
  enhanceApp({ app }) {
    // 注册全局组件
    app.component('post', Post)
  }
}

最后我们新建一个 posts.md 页面,应用此组件即可

docs/pages/posts.md
md
---
layout: post
title: 归档页
description: 这是唯知笔记网站的文章归档界面……
---

标签页类似,只是多一个选择标签过滤数据的事件,这里不再赘述

另一种思路:生成 json 文件 &ZeroWidthSpace;

根据官方文档描述,还有一种思路就是在 项目构建时,服务端生成 json 文件,前端请求 json 文件获取数据

警告

这样会把你的文章接口 API 暴露在客户端,如果文章数据量很大,或者文章数据敏感,不建议使用此方式。

新建文件 loadPosts.ts,注意这里的文件目录是在 config 下

.vitepress/config/loadPosts.ts
ts
// 代码和 docs/.vitepress/utils/post.data.ts  类似,细节不太一样
import { createContentLoader } from 'vitepress'
import fs from 'fs'
import path from 'path'
import { sep, normalize } from 'path'
import { formatDate } from '../utils/tools'
import { getGitTimestamp } from '../utils/fileTime'
import { Post } from '../utils/post'

export const loadPosts = async (mode) => {
  // 使用 createContentLoader 加载 Markdown 文件
  const posts = await createContentLoader('post/**/!(*-demo).md', {
    // 包含原始 markdown 源
    includeSrc: false,
    // 包含摘录
    excerpt: false
  }).load()

  //
  const promises: Promise<any>[] = []
  const _post: Post[] = []
  posts.forEach(({ frontmatter, src, url }) => {
    const title = frontmatter.title,
      _tags = frontmatter?.tags,
      category = frontmatter?.category,
      abstract = frontmatter?.description,
      // 获取手动设置的更新时间
      createdDate = frontmatter?.firstCommit ? +new Date(frontmatter.firstCommit) : '',
      updatedDate = frontmatter?.lastUpdated ? +new Date(frontmatter.lastUpdated) : '',
      // 日期格式
      dateOption = formatDate(),
      // 链接去掉项目名
      link = normalize(url)
        .split(sep)
        .filter((item) => item)
        .join(sep)
    // 没有时间的文章根据git时间戳获取
    if (createdDate && updatedDate) {
      _post.push({
        title,
        url: link.replace(/post\//, ''),
        date: [createdDate, updatedDate],
        dateText: [dateOption.format(createdDate), dateOption.format(updatedDate)],
        abstract: abstract,
        category: category,
        tags: _tags
      })
    } else {
      // https://vitepress.dev/zh/guide/getting-started#file-structure
      // 如果你的文档在docs目录下,路径开头需要拼接 docs/ ,末尾需要拼接 .md
      const task = getGitTimestamp(`docs/${link.replace(/.html/, '')}.md`).then((date) => ({
        title,
        url: link.replace(/post\//, ''), // 由于使用了rewrites重定向,这里也对url作处理
        date: [date[0], date[1]],
        dateText: [dateOption.format(date[0]), dateOption.format(date[1])],
        abstract: abstract,
        category: category,
        tags: _tags
      }))
      promises.push(task)
    }
  })
  let formattedPosts = _post.concat(await Promise.all(promises))
  // 发布时间降序排列
  formattedPosts = formattedPosts.sort((a, b) => b.date[0] - a.date[0])

  // 定义输出路径
  const outputPath = path.resolve(mode === 'production' ? './dist/posts.json' : './docs/posts.json')

  // 确保目标目录存在,如果不存在则创建,否则首次构建会报错
  const outputDir = path.dirname(outputPath)
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true }) // 递归创建目录
  }

  // 将数据写入 JSON 文件
  fs.writeFileSync(outputPath, JSON.stringify(formattedPosts, null, 2))

  console.log('Generated posts.json successfully!')
}

然后修改 config 配置,在开发和生产构建阶段,分别调用此方法

docs/.vitepress/config/index.ts
ts
import { loadPosts } from './loadPosts'

export default async ({ mode }) => {
  // https://vitepress.dev/zh/reference/site-config
  return defineConfig({
    // 其他配置省略
    vite: {
      plugins: [
        // 开发环境执行
        {
          name: 'load-posts-plugin',
          async configResolved() {
            await loadPosts(mode)
          }
        }
      ]
    },
    // 构建时执行
    async buildEnd() {
      try {
        // 加载文章
        loadPosts(mode)
      } catch (error) {
        console.error('Error during buildEnd:', error)
      }
    }
  })
}

这样本地项目启动或者项目生产构建时,就会生成一个包含文章数据的json文件。

对应的,在客户端我们发送一个请求去获取json文件,从而拿到数据。定义以下方法

ts
export const postsData = async () => {
  const response = await fetch(window.location.origin + '/posts.json')
  const posts: Post[] = await response.json()
  return posts
}

在归档页或者标签页

vue
<script setup lang="ts">
// 省略其他
onMounted(async () => {
  posts.value = await postsData()
})
</script>

通过以上方式我们也能拿到文章数据,正常展示在页面上。

部署问题 &ZeroWidthSpace;

Git 时间显示异常 &ZeroWidthSpace;

由于我们的时间依赖于 git,所以脱离 github pages 部署的话,新建和更新文章时间可能显示是最近部署的时间。

这里只列举下 cloudflare worker 部署的解决方案,其他的可以自定搜索:

  1. package.json 中新增命令 build:cf
package.json
json
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "vitepress dev docs",
  "build": "vitepress build docs",
  "build:cf": "git fetch --unshallow && vitepress build docs",  
  "preview": "vitepress preview docs"
},
  1. 修改 cloudflare 上的构建命令为 pnpm i && pnpm run build:cf
  •  

VitePress 新建页面和注册组件

VitePress 新建页面和注册组件 &ZeroWidthSpace;

VitePress 默认只有一个首页,而且内容区域基本都是固定的,如果你想自定义首页,或者新增归档、标签等页面,那么注册组件和新建页面就是必不可少的步骤

注册组件 &ZeroWidthSpace;

如果只使用普通组件,不需要注册,直接导入使用即可。一般注册组件,都是我们要在很多地方使用(比如文章列表,文章卡片),或者直接当一个页面使用(比如首页,归档页),注册后就不用再导入了。

1. 创建组件。这里假设我们创建了一个名为 Home.vue 的组件,并且它位于 .vitepress/theme/ 目录下。

.vitepress/theme/Home.vue
vue
<template>
  <div>这是我的首页</div>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped></style>

2. 在主题配置中注册这个组件

.vitepress/theme/index.ts
ts
import Home from './MyComponent.vue'

export default {
  // 为自定义界面添加class,非必须
  Layout: () => {
    const props: Record<string, any> = {}
    const { frontmatter } = useData()
    props.class = frontmatter.value?.layout || ''

    return h(WLayout, props)
  },
  enhanceApp({ app }) {
    // 注册全局组件
    app.component('home', Home)
  }
}

组件注册后,我们就可以使用了。如果是在 vue 组件中,我们不需导入,直接使用就可 <home />,如果是在 md 页面中,我们可以直接修改 layout 字段值即可。

新建页面 &ZeroWidthSpace;

注册完 Home 组件后,我们就可以新建一个页面使用它了。这里我们修改首页 index.mdlayout 字段,使用我们注册的组件名称 home。(首页比较特殊,默认入口就是 docs/index.md,VitePress 已经提供了这个文件,不需要创建)

docs/index.md
md
---
layout: home
title: 唯知笔记
titleTemplate: 唯知笔记

site:
    title: 唯知笔记
    author: weizwz
    #  其他字段省略
---

同时 index.md 里的字段 site.title 等,我们可以在 Home.vue 组件里通过以下方式获取到

.vitepress/theme/Home.vue
vue
<script setup lang="ts">
import { useData } from 'vitepress'

const { frontmatter: fm } = useData()

console.log(fm.value.site)
</script>

除了首页的其他页面都需要我们新建对应的 md 文件,比如我们想要一个归档页面:

按照之前步骤,先创建一个名为 MyPost.vue 的组件,它位于 .vitepress/theme/ 目录下,然后在 .vitepress/theme/index.ts 中完成注册,注册名称是 my-post

最后我们新建一个 docs/pages/posts.md

docs/pages/posts.md
md
---
layout: my-post
title: 所有文章
description: 这是唯知笔记网站的文章归档界面……
---

这样归档页面就可以建好了,访问路径为 /pages/posts

  •  

VitePress 添加图片查看器 Fancybox

VitePress 添加图片查看器 Fancybox &ZeroWidthSpace;

Fancybox简介 &ZeroWidthSpace;

Fancybox 是一款非常流行且功能强大的 JavaScript 图片查看库。之前博客采用Hexo框架的时候,使用的 butterfly主题 就默认内置的 Fancybox。其在保持轻量化的同时,还支持丰富的功能,比如:缩放、旋转、全屏查看、手势操作等,并且可以展示视频、iframe 内容以及动态加载的内容;支持响应式布局,兼容所有主流浏览器。所以使用Vitepress切换博客框架后,第一时间就开始着手配置Fancybox。

封装组件 &ZeroWidthSpace;

官方有给出 Vue示例,但是并不完全适配 Vitepress,这里对照着封装下。

主要原理

根据官方示例,思路就是在 vue 组件被挂载时在 mounted 里完成绑定,在组件更新时在 update 里重新绑定,在组件被卸载时在 unmounted 里销毁。

那么在 VitePress 里,我们将在其全局主题配置 docs/.vitepress/theme/index.ts 中完成此设置,但是在 VitePress 中,切换页面后并不会触发 update,而是触发路由方法 onAfterRouteChange,所以我们用 onAfterRouteChange 代替 update,同时在切换路由之前 onBeforeRouteChange 中,销毁 Fancybox。

1. 封装组件 ImgViewer.ts &ZeroWidthSpace;

先封装一个组件 ImgViewer.ts,以便我们统一处理文章中的图片:

  1. 鉴于 Fancybox 的要求,不同图片的 data-fancybox 属性值将归于不同图库,所以我们对图片统一设置此属性
  2. 由于个人在文章图片中没有单独设置 alt 属性,所以统一设置此属性为离图片最近的标题文本

代码如下:

.vitepress/theme/components/ImgViewer.ts
ts

2. 在全局设置中启用组件 &ZeroWidthSpace;

我们在全局设置中根据不同生命周期和运行时API,启用不同的组件方法。代码如下:

.vitepress/theme/index.ts
ts
import type { Theme } from 'vitepress'
import { EnhanceAppContext, inBrowser } from 'vitepress'
import { h, onMounted, onUnmounted } from 'vue'
import DefaultTheme from 'vitepress/theme'
import { bindFancybox, destroyFancybox } from './components/ImgViewer' // 图片查看器

export default {
  extends: DefaultTheme,
  ...

  enhanceApp({ app, router }: EnhanceAppContext) {
    ...
 	   if (inBrowser) {
      router.onBeforeRouteChange = () => {
        destroyFancybox() // 销毁图片查看器
      }
      router.onAfterRouteChange = () => {
        bindFancybox() // 绑定图片查看器
      }
    }
  },
  setup() {
    onMounted(() => {
      bindFancybox()
    })
    onUnmounted(() => {
      destroyFancybox()
    })
  }
} satisfies Theme

3. 修改默认样式 &ZeroWidthSpace;

由于对Fancybox的纯黑背景不太感冒,修改为半透明高斯模糊遮罩层。
以下代码中使用的部分变量,参见 我的 Tailwind 配置

.vitepress/theme/style/custom.css
css

此 css 也是最终引入 docs/.vitepress/theme/index.ts 即可

最终效果 &ZeroWidthSpace;

封装并配置好后,我们查看界面效果,如图:

  •  

PS 安装教程翻译

PS 安装教程翻译 &ZeroWidthSpace;

appstorrent 上 ps 安装教程是俄文,特此翻译和矫正以便后续查看。此文档适用于 Adobe Photoshop v26.10.0+

1. 安装 Adobe Creative Cloud &ZeroWidthSpace;

要安装Adobe Photoshop,需要Adobe Creative Cloud(完整软件包)。如果已安装Adobe Creative Clo

ud,则可以跳过此步骤。

  • 导航到“Creative Cloud”文件夹,然后运行“Adobe Creative Cloud v6.7.0 Universal.pkg”。

  • 按照标准安装程序进行操作。您需要输入管理员密码(登录系统时使用的密码)。安装完成后,单击“关

闭”。

2. 安装 Adobe Photoshop v26.10.0 &ZeroWidthSpace;

安装之前,建议卸载任何旧版本的Adobe Photoshop。

  1. 打开Install文件夹并运行Install文件。

    错误:“安装程序无法继续,因为安装程序文件可能已损坏。请重新下载安装程序文件”?

    右键单击“安装”,然后从上下文菜单中选择“显示软件包内容”。导航至:目录> macOS。运行安装文件(一个带有黑色图标的Unix文件)。

    另一个错误?在本文中找到解决方案。

    要打开链接,请查看完整文档(不要使用预览)。

  2. 选择您的语言,然后单击“继续”。

    安装即将开始。

  3. 等待安装完成,然后单击“关闭”。

    如果遇到错误#501,说明您尚未安装Creative Cloud组件(步骤1)。

    如果遇到错误#195:您的Mac不符合系统要求,或者磁盘空间不足。

    如果遇到错误#133:可用磁盘空间不足。

3. 激活 Adobe Photoshop &ZeroWidthSpace;

  1. 有两种修补程序可供选择。请选择最适合您的修补程序:
  • Adobe激活工具v1.2.3——需要访问Adobe服务器。必须使用Adobe ID(免费账户)。此补丁程序不会破坏Photoshop中的神经滤镜功能(如果在该软件中使用相同补丁程序)。

  • Adobe激活工具v2.1.2–无法访问Adobe服务器。神经过滤器将无法运行。无需Adobe ID。

建议使用Adobe激活工具v1.2.3以获得更高的稳定性和更少的激活问题。

  • 如果选择Adobe Activation Tool v1.2.3:

    启动Adobe Creative Cloud或Adobe Photoshop并登录您的Adobe帐户。我们强烈建议使用新帐户(例如,使用临时电子邮件注册)。登录后,系统将提示您开始试用或订阅。单击“退出”并

    继续激活...

  • 如果选择Adobe Activation Tool v2.1.2:请勿启动Adobe Photoshop或登录到Adobe帐户。直接继续进行激活...

  1. 在“Crack”文件夹中,使用所选补丁程序打开.dmg映像,并运行Adobe激活工具。

    如果启动时遇到问题,请将Adobe Activation Tool拖到“应用程序”文件夹中,并使用图像中的“修复”文件。或者,可以查看本文中的解决方案。

  2. 在Adobe激活工具窗口中,单击“+”区域并选择已安装的Adobe Photoshop。

  3. 一旦补丁检测到Adobe Photoshop,点击“开始激活”并输入您的管理员密码(您登录系统时使用的密码)。

  4. 如果激活成功,将出现绿色消息:“日志:激活已成功完成”。

  5. 全部完成!启动Adobe Photoshop。

    启动时,您可能会看到此窗口。不必担心,只需单击“确定”即可。

    如果遇到错误#12013:证书丢失。请转到Certs文件夹并运行AddCerts文件。

    另一个错误?在本文中找到解决方案。

    要打开链接,请查看完整文档(请勿使用预览)。

提示 &ZeroWidthSpace;

您可以使用Adobe激活工具v1.2.3用于Adobe Creative Cloud。

(激活示例)。

Adobe Creative Cloud位于:

/Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app

使用键盘快捷键Cmd + Shift + G可以导航到指定文件夹。

激活Adobe Creative Cloud后,您可以直接从Adobe Creative Cloud下载应用程序——Adobe产品旁边

将出现一个下载按钮。

(示例)。

  •  

前端设计资源汇总

前端设计资源汇总 &ZeroWidthSpace;

整理前端常用设计资源

网页设计 &ZeroWidthSpace;

站酷(包含众多网页设计):https://www.zcool.com.cn/search/folder/fix?word=后台管理

UI世界(更全面的ui作品):http://www.uishijie.com/ui/

蓝湖(高效的产品设计协作平台):https://lanhuapp.com/

Figma(矢量图形编辑器和原型设计工具):https://www.figma.com/

Figma中文社区:https://www.figma.cool/

Readdy(ai网页设计):https://readdy.ai/

Pixso(产品设计协作一体化工具):https://pixso.cn/

美工吧(提供一站式电商美工导航服务):https://www.meigong8.com/

颜色设计 &ZeroWidthSpace;

Palette(调色板):https://arco.design/palette/list

ant-design(共计120个颜色,包含12个主色以及衍生色):https://ant-design.antgroup.com/docs/spec/colors-cn

tailwindcss(tailwind颜色):https://tailwindcss.com/docs/colors

字体资源 &ZeroWidthSpace;

100font(免费商用字体大全):https://www.100font.com/

开源字体整理:https://note.weizwz.com/resource/font/open-source-font

字图CDN(免费的中文字体公益 CDN 服务):https://chinese-font.netlify.app/zh-cn/cdn/

ZSFT(900+款免费商用字体):https://fonts.zeoseven.com/

字体天下(海量字体免费高速下载,商用需注意许可证):https://www.fonts.net.cn/

FONTSPACE(150,000+免费外文字体):https://www.fontspace.com/

图片资源 &ZeroWidthSpace;

Pexels(免费的图库,提供众多高质量图片和视频,查看许可证):https://www.pexels.com/

Unsplash(600W+免费图片,查看许可证):https://unsplash.com/

pngimg(包含众多免费的高清透明PNG素材资源):https://pngimg.com/

undraw(可调色彩的插画,支持svg和png格式):https://undraw.co/illustrations

PATTERN MONSTER(高度自定义svg底纹):https://pattern.monster/

Hero Patterns(免费的svg底纹合集):https://heropatterns.com/

图标资源 &ZeroWidthSpace;

汇总:https://note.weizwz.com/resource/image/icon-all

图片工具 &ZeroWidthSpace;

图片转BASE64编码:https://www.jyshare.com/front-end/59/

图片压缩和转化:https://tinypng.com/

图片转Excel:https://web.baimiaoapp.com/image-to-excel

在线抠图去背景:https://www.remove.bg/zh

带壳截图:https://mockuphone.com/

在线SVG转PNG工具:https://uutool.cn/svg2png/

动效设计 &ZeroWidthSpace;

LOADING.IO(加载动画设计):https://loading.io/

Motion(动画库,支持js、react、vue):https://motion.net.cn/

@vueuse/motion(vue动画库):https://motion.vueuse.org/

Inspira UI(使用Nuxt&Vue构建漂亮的动效组件):https://inspira-ui.com/

UIVERSE(最大的开源UI库):https://uiverse.io/

  •  

VitePress 添加友链界面

VitePress 添加友链界面 &ZeroWidthSpace;

提示:项目使用了 Tailwind 进行了重构,本文组件样式都对此有所依赖,参见 我的 Tailwind 配置。如果不想使用 Tailwind,可以直接把代码扔给 AI,让其转化为普通 CSS 样式

效果预览

友链数据存储 &ZeroWidthSpace;

友链数据通常是经常需要添加和修改的,所以我们不能直接写死到页面上。这里我们单独提一个文件去存储友链数据,并且友链数据的格式基本也是类似的。

新建 links.md 文件,内容如下:

docs/pages/links.md
md
---
layout: weiz-link
title: 我的友链
description: 这是唯知笔记网站的友链界面,xxxxxx

links:
  - title: 鸣谢
    desc: 建站中学习和使用了以下博客/网站的技术和分享,特别鸣谢!🫡
    list:
      - name: VitePress
        link: https://vitepress.dev/zh/
        avatar: https://vitepress.dev/vitepress-logo-mini.svg
        irregular: true
        descr: 由 Vite 和 Vue 驱动的静态站点生成器
      - name: VitePress 快速上手教程
        link: https://vitepress.yiov.top/
        avatar: https://vitepress.dev/vitepress-logo-mini.svg
        irregular: true
        descr: 如果你也想搭建它,那跟我一起做吧
  - title: 传送门
    desc: 聚集众多优秀独立博客,随机传送 🚀
    list:
      - name: 十年之约
        link: https://foreverblog.cn/go.html
        avatar: https://img.foreverblog.cn/logo_en_default.png
        descr: 十年之约,奔赴某个独立博客的十年
        irregular: true
      - name: Web Teleporter
        link: https://webteleporter.top/
        avatar: https://webteleporter.top/img/logo.png
        descr: 独立博客传送门
outLinks:
  - title: 去远方
    desc: 去了远方的伙伴,回来的时候请记得联络我📩
    list:
      - name: 随风起
        link: https://blog.bywind.xyz/
        avatar: https://blog.bywind.xyz/img/avatar.png
        descr: 爱意随风起,风止意难平
---

说明:

  1. irregular 字段是对特殊图片进行处理,非必须,你可以自由发挥
  2. outLinks 字段是网站断联的名单,非必须

友链页面创建 &ZeroWidthSpace;

Vitepress 中创建单独页面的方法,可以参考 VitePress 新建页面和注册组件

新建组件 &ZeroWidthSpace;

新建一个文件夹 docs/.vitepress/theme/components/WLink 用来存放友链组件

vue
vue
ts

组件注册 &ZeroWidthSpace;

新建的组件需要在主题配置中心进行注册,注册后就可以在 md页面 中使用了。比如我们 links.md 文件中的开头 layout: weiz-link

docs/.vitepress/theme/index.ts
ts
import WLink from './components/WLink/index.vue' // 友链页

export default {
  enhanceApp({ app }) {
    // 注册全局组件
    app.component('weiz-link', WLink)
  }
}

附带评论 &ZeroWidthSpace;

在组件内集成评论,直接使用评论组件,比如我的是 <Twikoo /> 评论。
Twikoo 评论的集成方式,可参考 VitePress 集成 Twikoo 评论

  •  

挑战无手写代码用 Kiro 开发一个导航网站

挑战无手写代码用 Kiro 开发一个导航网站 &ZeroWidthSpace;

Kiro 的 Spec 模式初始分三个阶段:Requirements 需求 -> Design 设计 -> Task list 任务。 这些文档都整理完后,既可以按 Task list 开始执行,中途也可以提出意见并进行修改。

梳理原始需求 &ZeroWidthSpace;

开始 Spec 模式之前,最好自己先整理下自己的原始需求。当然我的原始需求也是找通义的 Qwen3 梳理的:

经过通义的优化和最终的修改后,我的原始需求文档大概是这样

md
为了设计一个现代化的个人前端导航网站,我们需要详细规划文档需求,确保所有核心功能和技术特点都能得到充分实现。
以下是一个整理后的文档需求概览:

## 1. 项目概述

- **项目目标**:创建一个响应式、高效、用户友好的个人前端导航网站。
- **目标用户**:前端开发者、设计师、技术爱好者等。

## 2. 核心功能需求

### 2.1 响应式设计

- **支持设备**:桌面、平板、手机等不同尺寸屏幕。
- **设计原则**:确保在各种设备上都能提供一致且优化的用户体验。

### 2.2 站内实时搜索

- **搜索功能**:实现快速、准确的站内内容搜索。
- **搜索结果展示**:以清晰、直观的方式展示搜索结果。

### 2.3 可视化导航数据管理界面
- **界面设计**:提供易于操作的可视化界面,用于管理和编辑导航数据。
- **功能要求**:支持添加、删除、修改导航条目。

### 2.4 本地数据持久化存储
- **存储方式**:使用本地存储技术(如LocalStorage)保存用户数据。
- **数据安全**:确保数据的安全性和隐私性。

### 2.5 明暗主题切换
- **主题切换**:提供优雅的明暗主题切换功能,满足不同用户的视觉偏好。
- **过渡效果**:确保主题切换过程平滑无卡顿。

### 2.6 用户体验优化
- **UI设计**:采用现代化的UI设计风格,提升视觉吸引力。
- **页面加载**:优化页面加载速度,减少等待时间。
- **动画过渡**:使用流畅的动画过渡效果,增强交互体验。
- **数据编辑**:提供直观的数据编辑界面,简化操作流程。
- **数据重置确认**:设置安全的数据重置确认机制,防止误操作。

## 3. 技术实现方案
### 3.1 技术栈选择
- **框架**:基于Next.js 最新版本构建,利用其服务器端渲染和静态生成能力
- **样式**:使用Tailwind CSS最新版本实现灵活、高效的样式设计
- **组件库**:采用antdesign最新版本组件库,提供丰富的UI组件
- **动效库**:使用 React Bits 最新版,提供有趣高性能的动画特效
- **图标**:使用ant-design/icons 最新版本,满足项目需要的图标
- **状态管理**:使用redux最新版本进行全局状态管理,确保数据一致性
- **类型定义**:支持TypeScript,提高代码质量和可维护性

### 3.2 开发工具选择

- **编辑器**:Kiro 的 Spec 模式
- **AI 模型**:Claude Sonnet 4
- **开发环境**:Nodejs v20,pnpm v10

## 4. 开发与测试计划

- **开发阶段**:按照功能模块划分,逐步实现各项功能。
- **测试阶段**:进行全面的功能测试、性能测试和兼容性测试,确保网站稳定运行。

## 5. 部署与维护
- **部署方案**:选择合适的云服务提供商,进行网站部署。
- **维护计划**:定期更新网站内容,修复潜在问题,持续优化用户体验。

通过以上文档需求,帮我设计一个完整的项目。

让 Kiro 分解需求 &ZeroWidthSpace;

把原始需求扔给 kiro,让它先清醒清醒

Kiro 接着会创建三个文档,分别是 Requirements 需求、Design 设计、Task list 任务。这边也记录下:

md
# 需求文档

## 项目介绍

本项目旨在创建一个现代化的个人前端导航网站,为前端开发者、设计师和技术爱好者提供一个响应式、高效、用户友好的导航平台。该网站将集成实时搜索、可视化数据管理、本地数据持久化、主题切换等核心功能,采用Next.js + TypeScript + Tailwind CSS + Ant Design技术栈构建。

## 需求

### 需求 1 - 响应式设计

**用户故事:** 作为一个用户,我希望能在不同设备(桌面、平板、手机)上都能正常访问和使用导航网站,以便随时随地查找所需的前端资源。

#### 验收标准

1. WHEN 用户在桌面设备访问网站 THEN 系统 SHALL 显示完整的桌面版布局
2. WHEN 用户在平板设备访问网站 THEN 系统 SHALL 自动适配平板屏幕尺寸并优化布局
3. WHEN 用户在手机设备访问网站 THEN 系统 SHALL 显示移动端优化的布局
4. WHEN 用户旋转设备屏幕 THEN 系统 SHALL 实时调整布局以适应新的屏幕方向
5. WHEN 用户在任何设备上操作 THEN 系统 SHALL 保持一致的用户体验和功能可用性

### 需求 2 - 站内实时搜索

**用户故事:** 作为一个用户,我希望能够快速搜索站内的导航内容,以便高效地找到我需要的前端资源和工具。

#### 验收标准

1. WHEN 用户在搜索框输入关键词 THEN 系统 SHALL 实时显示匹配的搜索结果
2. WHEN 用户输入搜索关键词 THEN 系统 SHALL 在300毫秒内返回搜索结果
3. WHEN 搜索结果存在 THEN 系统 SHALL 高亮显示匹配的关键词
4. WHEN 用户点击搜索结果 THEN 系统 SHALL 导航到对应的资源页面或链接
5. WHEN 没有搜索结果 THEN 系统 SHALL 显示友好的"无结果"提示信息

### 需求 3 - 可视化导航数据管理

**用户故事:** 作为一个网站管理员,我希望能够通过可视化界面管理导航数据,以便轻松地添加、编辑和删除导航条目。

#### 验收标准

1. WHEN 管理员访问数据管理界面 THEN 系统 SHALL 显示所有现有的导航条目
2. WHEN 管理员点击添加按钮 THEN 系统 SHALL 显示添加新导航条目的表单
3. WHEN 管理员填写完整的导航信息并提交 THEN 系统 SHALL 成功添加新的导航条目
4. WHEN 管理员点击编辑按钮 THEN 系统 SHALL 显示预填充当前数据的编辑表单
5. WHEN 管理员点击删除按钮 THEN 系统 SHALL 显示确认对话框防止误删除
6. WHEN 管理员确认删除操作 THEN 系统 SHALL 永久删除选中的导航条目

### 需求 4 - 本地数据持久化存储

**用户故事:** 作为一个用户,我希望我的个人设置和自定义数据能够保存在本地,以便下次访问时能够保持我的个性化配置。

#### 验收标准

1. WHEN 用户修改导航数据 THEN 系统 SHALL 自动将数据保存到本地存储
2. WHEN 用户重新访问网站 THEN 系统 SHALL 从本地存储加载之前保存的数据
3. WHEN 本地存储数据损坏或不存在 THEN 系统 SHALL 加载默认的导航数据
4. WHEN 用户清除浏览器数据 THEN 系统 SHALL 能够检测到数据丢失并提供恢复选项
5. WHEN 数据保存失败 THEN 系统 SHALL 显示错误提示并提供重试选项

### 需求 5 - 明暗主题切换

**用户故事:** 作为一个用户,我希望能够在明亮和暗黑主题之间切换,以便根据我的使用环境和个人偏好选择合适的视觉主题。

#### 验收标准

1. WHEN 用户点击主题切换按钮 THEN 系统 SHALL 在明暗主题之间平滑切换
2. WHEN 主题切换完成 THEN 系统 SHALL 将用户的主题偏好保存到本地存储
3. WHEN 用户重新访问网站 THEN 系统 SHALL 自动应用用户之前选择的主题
4. WHEN 系统检测到用户的系统主题偏好 THEN 系统 SHALL 默认使用匹配的主题
5. WHEN 主题切换过程中 THEN 系统 SHALL 保持所有功能的正常可用性

### 需求 6 - 用户体验优化

**用户故事:** 作为一个用户,我希望网站具有现代化的设计和流畅的交互体验,以便获得愉悦的使用感受。

#### 验收标准

1. WHEN 用户访问网站 THEN 系统 SHALL 在2秒内完成首屏加载
2. WHEN 用户进行任何交互操作 THEN 系统 SHALL 提供即时的视觉反馈
3. WHEN 页面内容发生变化 THEN 系统 SHALL 使用平滑的动画过渡效果
4. WHEN 用户执行数据重置操作 THEN 系统 SHALL 显示明确的确认对话框
5. WHEN 用户确认重置操作 THEN 系统 SHALL 清除所有自定义数据并恢复默认状态
6. WHEN 网站在不同设备上显示 THEN 系统 SHALL 保持一致的视觉设计风格

### 需求 7 - 技术架构和性能

**用户故事:** 作为一个开发者,我希望网站采用现代化的技术栈并具有良好的性能表现,以便提供稳定可靠的服务。

#### 验收标准

1. WHEN 网站构建时 THEN 系统 SHALL 使用Next.js最新版本作为核心框架
2. WHEN 网站样式渲染时 THEN 系统 SHALL 使用Tailwind CSS提供响应式样式
3. WHEN 网站组件渲染时 THEN 系统 SHALL 使用Ant Design组件库确保UI一致性
4. WHEN 网站需要动画效果时 THEN 系统 SHALL 使用React Bits提供高性能动画
5. WHEN 网站进行状态管理时 THEN 系统 SHALL 使用Redux管理全局状态
6. WHEN 代码编写时 THEN 系统 SHALL 支持TypeScript提供类型安全
7. WHEN 网站部署后 THEN 系统 SHALL 在各主流浏览器中正常运行
md
# 设计文档

## 概述

本设计文档详细描述了现代化个人前端导航网站的技术架构、组件设计和实现方案。该网站采用Next.js 14+ 作为核心框架,结合TypeScript、Tailwind CSS、Ant Design等现代技术栈,构建一个高性能、响应式、用户友好的导航平台。

## 架构设计

### 整体架构

```mermaid
graph TB
    A[用户界面层] --> B[组件层]
    B --> C[状态管理层]
    C --> D[数据服务层]
    D --> E[本地存储层]

    A --> A1[响应式布局]
    A --> A2[主题系统]
    A --> A3[动画效果]

    B --> B1[页面组件]
    B --> B2[业务组件]
    B --> B3[通用组件]

    C --> C1[Redux Store]
    C --> C2[中间件]
    C --> C3[状态切片]

    D --> D1[搜索服务]
    D --> D2[数据管理服务]
    D --> D3[主题服务]

    E --> E1[LocalStorage]
    E --> E2[SessionStorage]
    E --> E3[IndexedDB]
```

### 技术栈架构

- **前端框架**: Next.js 14+ (App Router)
- **类型系统**: TypeScript 5+
- **样式框架**: Tailwind CSS 3+
- **组件库**: Ant Design 5+
- **状态管理**: Redux Toolkit + RTK Query
- **动画库**: React Bits + Framer Motion
- **图标库**: @ant-design/icons
- **构建工具**: Turbopack (Next.js内置)
- **包管理器**: pnpm 8+

## 组件和接口设计

### 核心组件架构

```mermaid
graph TD
    A[App Layout] --> B[Header]
    A --> C[Main Content]
    A --> D[Footer]

    B --> B1[Logo]
    B --> B2[Search Bar]
    B --> B3[Theme Toggle]
    B --> B4[Settings]

    C --> C1[Navigation Grid]
    C --> C2[Category Sidebar]
    C --> C3[Management Panel]

    C1 --> C1A[Navigation Card]
    C1 --> C1B[Category Section]

    C3 --> C3A[Add Form]
    C3 --> C3B[Edit Form]
    C3 --> C3C[Delete Confirmation]
```

### 主要组件接口

#### 1. NavigationCard 组件

```typescript
interface NavigationCardProps {
  id: string
  title: string
  description: string
  url: string
  icon?: string
  category: string
  tags: string[]
  isEditable?: boolean
  onEdit?: (id: string) => void
  onDelete?: (id: string) => void
}
```

#### 2. SearchBar 组件

```typescript
interface SearchBarProps {
  placeholder?: string
  onSearch: (query: string) => void
  onClear: () => void
  suggestions?: SearchSuggestion[]
  isLoading?: boolean
}

interface SearchSuggestion {
  id: string
  title: string
  category: string
  matchType: 'title' | 'description' | 'tag'
}
```

#### 3. ManagementPanel 组件

```typescript
interface ManagementPanelProps {
  isVisible: boolean
  mode: 'add' | 'edit' | 'view'
  currentItem?: NavigationItem
  onSave: (item: NavigationItem) => void
  onCancel: () => void
  onDelete?: (id: string) => void
}
```

#### 4. ThemeProvider 组件

```typescript
interface ThemeContextValue {
  theme: 'light' | 'dark' | 'auto'
  setTheme: (theme: 'light' | 'dark' | 'auto') => void
  isDark: boolean
}
```

## 数据模型

### 核心数据结构

#### NavigationItem 模型

```typescript
interface NavigationItem {
  id: string
  title: string
  description: string
  url: string
  icon?: string
  category: string
  tags: string[]
  createdAt: Date
  updatedAt: Date
  isActive: boolean
  sortOrder: number
}
```

#### Category 模型

```typescript
interface Category {
  id: string
  name: string
  description?: string
  icon?: string
  color?: string
  sortOrder: number
  isActive: boolean
}
```

#### UserSettings 模型

```typescript
interface UserSettings {
  theme: 'light' | 'dark' | 'auto'
  language: string
  gridColumns: number
  showDescriptions: boolean
  enableAnimations: boolean
  searchHistory: string[]
  favoriteCategories: string[]
}
```

#### AppState 模型

```typescript
interface AppState {
  navigation: {
    items: NavigationItem[]
    categories: Category[]
    filteredItems: NavigationItem[]
    searchQuery: string
    selectedCategory: string | null
    isLoading: boolean
  }
  ui: {
    theme: 'light' | 'dark' | 'auto'
    sidebarOpen: boolean
    managementPanelOpen: boolean
    currentEditItem: NavigationItem | null
  }
  settings: UserSettings
}
```

## 错误处理

### 错误类型定义

```typescript
enum ErrorType {
  NETWORK_ERROR = 'NETWORK_ERROR',
  STORAGE_ERROR = 'STORAGE_ERROR',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  PERMISSION_ERROR = 'PERMISSION_ERROR',
  UNKNOWN_ERROR = 'UNKNOWN_ERROR',
}

interface AppError {
  type: ErrorType
  message: string
  details?: any
  timestamp: Date
  recoverable: boolean
}
```

### 错误处理策略

1. **本地存储错误**
   - 检测存储空间不足
   - 提供数据导出功能
   - 自动降级到内存存储

2. **网络错误**
   - 实现重试机制
   - 显示离线状态提示
   - 缓存关键数据

3. **验证错误**
   - 实时表单验证
   - 友好的错误提示
   - 自动修复建议

4. **全局错误边界**
   - React Error Boundary
   - 错误日志收集
   - 优雅降级显示

## 性能优化策略

### 1. 代码分割和懒加载

```typescript
// 路由级别的代码分割
const ManagementPanel = lazy(() => import('./components/ManagementPanel'))
const SettingsModal = lazy(() => import('./components/SettingsModal'))

// 组件级别的条件加载
const HeavyComponent = lazy(() =>
  import('./components/HeavyComponent').then(module => ({
    default: module.HeavyComponent,
  }))
)
```

### 2. 数据优化

- 虚拟滚动处理大量导航项
- 搜索防抖和节流
- 智能缓存策略
- 增量数据更新

### 3. 渲染优化

- React.memo 优化组件重渲染
- useMemo 和 useCallback 缓存计算结果
- 图片懒加载和预加载
- CSS-in-JS 优化

### 4. 存储优化

- 数据压缩存储
- 分片存储大数据
- 定期清理过期数据
- 存储配额管理

## 响应式设计方案

### 断点设计

```css
/* Tailwind CSS 断点 */
sm: 640px   /* 小屏设备 */
md: 768px   /* 平板设备 */
lg: 1024px  /* 笔记本电脑 */
xl: 1280px  /* 桌面显示器 */
2xl: 1536px /* 大屏显示器 */
```

### 布局适配策略

1. **移动端优先设计**
   - 单列布局
   - 触摸友好的交互元素
   - 简化的导航菜单

2. **平板端适配**
   - 双列或三列网格布局
   - 侧边栏可折叠
   - 手势支持

3. **桌面端优化**
   - 多列网格布局
   - 固定侧边栏
   - 键盘快捷键支持

## 主题系统设计

### 主题配置

```typescript
interface ThemeConfig {
  colors: {
    primary: string
    secondary: string
    background: string
    surface: string
    text: {
      primary: string
      secondary: string
      disabled: string
    }
    border: string
    shadow: string
  }
  spacing: Record<string, string>
  typography: {
    fontFamily: string
    fontSize: Record<string, string>
    fontWeight: Record<string, number>
  }
  borderRadius: Record<string, string>
  animation: {
    duration: Record<string, string>
    easing: Record<string, string>
  }
}
```

### 主题切换实现

- CSS 变量动态切换
- 系统主题检测
- 平滑过渡动画
- 主题偏好持久化

## 搜索系统设计

### 搜索算法

1. **全文搜索**
   - 标题匹配(权重最高)
   - 描述内容匹配
   - 标签匹配
   - 分类匹配

2. **模糊搜索**
   - 拼音搜索支持
   - 容错匹配
   - 同义词扩展

3. **搜索优化**
   - 搜索结果排序
   - 搜索历史记录
   - 热门搜索推荐
   - 实时搜索建议

### 搜索索引结构

```typescript
interface SearchIndex {
  id: string
  title: string
  titlePinyin: string
  description: string
  tags: string[]
  category: string
  searchableText: string
  weight: number
}
```

## 测试策略

### 1. 单元测试

- 组件渲染测试
- 工具函数测试
- Redux reducer 测试
- 自定义 Hook 测试

### 2. 集成测试

- 组件交互测试
- API 集成测试
- 状态管理集成测试
- 路由导航测试

### 3. 端到端测试

- 用户流程测试
- 跨浏览器兼容性测试
- 响应式设计测试
- 性能基准测试

### 4. 可访问性测试

- 键盘导航测试
- 屏幕阅读器兼容性
- 颜色对比度检查
- ARIA 标签验证

## 部署和构建策略

### 构建优化

- 静态资源优化
- 代码压缩和混淆
- Tree shaking
- 图片优化和格式转换

### 部署方案

- Vercel 部署(推荐)
- Netlify 部署
- 自托管方案
- CDN 加速配置

### 监控和分析

- 性能监控
- 错误追踪
- 用户行为分析
- 核心 Web 指标监控
md
# 实施计划

- [ ] 1. 项目初始化和基础配置
  - 创建Next.js 14项目并配置TypeScript
  - 安装和配置Tailwind CSS、Ant Design、Redux Toolkit等核心依赖
  - 设置项目目录结构和基础文件
  - 配置ESLint、Prettier和Git hooks
  - _需求: 7.1, 7.2, 7.3, 7.5, 7.6_

- [ ] 2. 核心类型定义和数据模型
  - 创建NavigationItem、Category、UserSettings等核心数据类型
  - 定义组件Props接口和API响应类型
  - 实现数据验证schemas
  - _需求: 7.6, 3.3_

- [ ] 3. Redux状态管理架构
  - 配置Redux store和中间件
  - 创建navigation、ui、settings等状态切片
  - 实现actions和reducers
  - 添加状态持久化中间件
  - _需求: 7.5, 4.1, 4.2_

- [ ] 4. 本地存储服务层
  - 实现LocalStorage封装类
  - 创建数据序列化和反序列化工具
  - 实现数据备份和恢复功能
  - 添加存储错误处理和降级策略
  - _需求: 4.1, 4.2, 4.3, 4.4, 4.5_

- [ ] 5. 主题系统实现
  - 创建ThemeProvider和ThemeContext
  - 实现明暗主题配置和CSS变量
  - 添加系统主题检测功能
  - 实现主题切换动画效果
  - _需求: 5.1, 5.2, 5.3, 5.4, 5.5_

- [ ] 6. 基础布局组件
  - 创建AppLayout主布局组件
  - 实现Header组件(Logo、搜索栏、主题切换)
  - 创建响应式侧边栏组件
  - 实现Footer组件
  - _需求: 1.1, 1.2, 1.3, 1.4, 1.5_

- [ ] 7. 导航卡片组件
  - 创建NavigationCard基础组件
  - 实现卡片悬停和点击效果
  - 添加编辑和删除操作按钮
  - 实现卡片响应式布局
  - _需求: 6.2, 6.3, 1.1, 1.2, 1.3_

- [ ] 8. 搜索功能实现
  - 创建SearchBar组件和搜索逻辑
  - 实现实时搜索和防抖处理
  - 添加搜索结果高亮显示
  - 实现搜索历史和建议功能
  - _需求: 2.1, 2.2, 2.3, 2.4, 2.5_

- [ ] 9. 导航网格和分类显示
  - 创建NavigationGrid组件
  - 实现分类筛选功能
  - 添加网格响应式布局
  - 实现无限滚动或分页
  - _需求: 1.1, 1.2, 1.3, 1.4, 1.5_

- [ ] 10. 数据管理界面
  - 创建ManagementPanel组件
  - 实现添加导航项表单
  - 创建编辑导航项功能
  - 添加删除确认对话框
  - _需求: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_

- [ ] 11. 表单验证和错误处理
  - 实现表单字段验证规则
  - 添加实时验证反馈
  - 创建错误边界组件
  - 实现全局错误处理机制
  - _需求: 3.3, 3.5, 6.4_

- [ ] 12. 动画和过渡效果
  - 集成React Bits动画库
  - 实现页面切换动画
  - 添加组件进入/退出动画
  - 创建加载状态动画
  - _需求: 6.3, 5.5, 6.2_

- [ ] 13. 响应式设计优化
  - 实现移动端导航菜单
  - 优化平板端布局
  - 添加触摸手势支持
  - 测试各种屏幕尺寸适配
  - _需求: 1.1, 1.2, 1.3, 1.4, 1.5_

- [ ] 14. 性能优化实现
  - 添加组件懒加载
  - 实现图片懒加载
  - 优化Bundle大小
  - 添加缓存策略
  - _需求: 6.1, 7.7_

- [ ] 15. 数据重置和备份功能
  - 实现数据重置确认机制
  - 创建数据导出功能
  - 添加数据导入功能
  - 实现数据备份提醒
  - _需求: 6.5, 4.4_

- [ ] 16. 键盘快捷键支持
  - 实现搜索快捷键(Ctrl+K)
  - 添加导航快捷键
  - 创建快捷键帮助面板
  - 实现可访问性键盘导航
  - _需求: 6.2_

- [ ] 17. 单元测试编写
  - 为核心组件编写单元测试
  - 测试Redux状态管理逻辑
  - 添加工具函数测试
  - 实现测试覆盖率报告
  - _需求: 7.7_

- [ ] 18. 集成测试实现
  - 创建组件交互测试
  - 测试搜索功能集成
  - 验证数据持久化功能
  - 测试主题切换功能
  - _需求: 2.1, 2.2, 4.1, 5.1_

- [ ] 19. 端到端测试
  - 编写用户流程测试
  - 测试跨浏览器兼容性
  - 验证响应式设计
  - 性能基准测试
  - _需求: 1.1, 1.2, 1.3, 6.1, 7.7_

- [ ] 20. 生产环境优化和部署
  - 配置生产环境构建
  - 优化静态资源
  - 设置环境变量
  - 配置部署流水线
  - _需求: 7.7_

让 Kiro 执行任务 &ZeroWidthSpace;

根据 Kiro 整理好的 Task list,让它一步步执行即可。

每执行完一个任务,Kiro 都会咋 Task List 里标记完成。遇到一些执行的未信任命令,需要我们手动执行,其他时候都不用处理。

这里说一个问题,遇到 pnpm dev 执行后,需要手动停止,Kiro 才会继续思考,不然它会一直卡在那.

第一天,执行到第 10 个任务后,Kiro 提示我超出限制,需要明天再试。截止目前为止,无手动代码,只能明天继续了

未完待续……

Kiro 的执行结果 &ZeroWidthSpace;

由于 Kiro 每天限额,历经 4 天,Task List 基本执行完了。界面如下:

执行过程中全程基本无干预,按照 Task List 一步一步执行。可以看到结果非常不理想:

  1. 界面很多多余的元素,如:主题系统演示卡片、布局系统演示卡片、悬停动画卡片等,没有任何功能,也不算新手引导,又比如标签卡片上名称下面的 frontend、design 等
  2. 设置元素位置不当,比如:设置网格数量、分页还是滚动模式等,本应出现在设置界面/弹窗里,现在是在首页
  3. 诸多操作无效,设置里的网格列数,右上角的设置图标(点击无反应)等

后续通过引导,对界面重新布局,把设置归纳到弹窗里,然后引发了更多的问题

由于问题太多,我这里已经放弃了此项目

Spec 模式总结 &ZeroWidthSpace;

Kiro 的 Spec 模式目前 完全不可信任,或者说没有非常详细的 开发需求+设计稿,使用 Kiro 帮你从头设计开发一个程序是不可信的,问题多多。

这里对此过程中遇到的问题也归纳下:

  1. 项目运作时,工作模式会卡住:当它执行 npm devpnpm dev 后,工作流会卡住,需要你手动停止终端,它才会继续工作

  2. 小任务完成/解决了 bug 后,总结文字太多:不知道这个会不会消耗额度,但是感觉很繁琐也没有什么意义

  3. 解决 bug 会创建总结性文档,感觉没有这个必要

  4. 项目是否正常运作,主要靠 npm build,对于 npm dev 运行后,在界面中的存在的错误捕获不到(其实在浏览器中打开页面是会报错的)

  5. 其他小问题,比如:我多次强调使用 pnpm,但是开始下一个任务后,它还是使用 npm;经常会使用一些废弃的 API,比如 Ant DesignTabs.TabPane;对于界面上的问题,截图丢给后大概率不能识别,即使加了说明文字;等等……

Spec 模式的可行性建议 &ZeroWidthSpace;

  1. 有大量操作的/复杂的项目,可以直接放弃这个模式,问题太多
  2. 需求需要更加细致,包括技术架构、界面风格、UI组件、功能逻辑
  3. 最好有设计图,不然它给出的界面可能与你想要的相差甚远
  4. 及时打断,对于它总结性的回答,可以直接让他停止,以免消耗额度
  •  

如何快速无缝的从 vscode 转向AI编辑器 cursor、kiro、trae 等

如何快速无缝的从 vscode 转向AI编辑器 cursor、kiro、trae 等 &ZeroWidthSpace;

如果你之前经常使用 vscode 的话,这篇文章会帮助你开始使用 AI 编辑器 kiro、cursor、trae 等,因为后者的配置和插件等都和 vscode 是共通的。甚至 kiro、cursor 等都是基于开源 vscode 的变种开发而来。

如果你是第一次使用类似 vscode 的编辑器,这篇文章也会帮到你,更快了解如何去安装插件和修改配置。

以下教程都是以 kiro 为例,其他编辑器类似

插件安装 &ZeroWidthSpace;

如图,1 即插件商店入口,在 2 所在的位置输入插件名称即可搜索。第一次使用这类编辑器的同学,可以安装中文插件 3,搜索 Chinese 后选择安装。如果你和我一样主攻前端的话,上图中是我建议的插件,直接搜索安装即可。

配置修改 &ZeroWidthSpace;

修改配置两种方式,一种是可视化界面修改,如图 1 处,一种是直接修改配置文件(json 文件),如图 2,使用快捷键 shift + command + p 打开

这里建议直接第二种方式,更快速。

如果你之前有使用 vscode 的话,直接打开 vscode 的配置文件,复制过来即可。首次使用的话,可以参考我的配置

json
{
  "workbench.colorTheme": "Atom One Dark",
  "workbench.startupEditor": "none",
  "workbench.iconTheme": "material-icon-theme",
  "material-icon-theme.activeIconPack": "vue",
  "git.autofetch": true,
  "git.enableSmartCommit": true,
  "git.confirmSync": false,
  "git.ignoreMissingGitWarning": true,
  "git.openRepositoryInParentFolders": "never",
  "editor.tabSize": 2,
  "editor.fontFamily": "FiraCode Nerd Font, Consolas, 'Courier New', monospace",
  "editor.fontSize": 12,
  "editor.formatOnSaveMode": "modificationsIfAvailable",
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "javascript.format.semicolons": "remove",
  // 定义如何处理可选分号。
  //  - ignore: 不要插入或删除任何分号。
  //  - insert: 在语句末尾插入分号。
  //  - remove: 删除不必要的分号。
  "javascript.updateImportsOnFileMove.enabled": "always",
  "typescript.updateImportsOnFileMove.enabled": "always",
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[css]": {
    "editor.defaultFormatter": "vscode.css-language-features"
    // "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "vscode.css-language-features"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "vscode.json-language-features"
  },
  "security.workspace.trust.untrustedFiles": "open",
  "[html]": {
    "editor.defaultFormatter": "vscode.html-language-features"
  },
  "[less]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[markdown]": {
    "editor.quickSuggestions": {
      "comments": "on",
      "strings": "on",
      "other": "on"
    },
    "diffEditor.ignoreTrimWhitespace": false
  },
  "prettier.bracketSameLine": true,
  "prettier.jsxSingleQuote": true,
  "prettier.semi": false,
  "prettier.singleQuote": true,
  "prettier.trailingComma": "none",
  "prettier.printWidth": 160,
  "html.format.wrapLineLength": 500,
  "html.format.wrapAttributes": "preserve-aligned",
  "files.associations": {
    "manifest.json": "jsonc",
    "pages.json": "jsonc"
  },
  "explorer.confirmDelete": false,
  "diffEditor.ignoreTrimWhitespace": false,
  "stylelint.packageManager": "pnpm",
  "terminal.integrated.env.windows": {},
  "extensions.ignoreRecommendations": true,
  "kiroAgent.agentModelSelection": "CLAUDE_SONNET_4_20250514_V1_0",
  "kiroAgent.trustedCommands": [
    "pnpm type-check",
    "pnpm build",
    "pnpm lint",
    "find . -name \"*.d.ts\" -not -path \"./node_modules/*\" -not -path \"./types/*\"",
    "pnpm dev",
    "npm run build",
    "npm run dev",
    "pnpm run build",
    "pnpm run dev"
  ],
}

其中部分配置是需要插件支持的,比如主题 Atom One Dark,比如格式化插件 prettier

其他 &ZeroWidthSpace;

两种模式 &ZeroWidthSpace;

kiro 有两种模式(图中 1 处):vibe 和 spec。

vibe 模式基本和 cursor 差不多,变下达指令边修改,遇到问题可以中止,提出并让他修复

spec 模式是一种全新的模式,它是从需求分析开始,帮你对需求进行整理分析,生成需求文档,然后再设计产品,最后还有任务计划文档,最后再开始执行。spec 模式尝试一种更拟人化的开发模式,对于全栈开发应该有很大的帮助

kiro 对话的快捷键如图中 1 处

命令执行 &ZeroWidthSpace;

首次使用 kiro,遇到命令执行,它会等待你的授权,需要手动允许。不过你也可以把类似的命令加入信任列表,之后它就会自动执行了。

如上图中 2 处,在设置里搜索 kiroAgent.trustedCommands 后,可以直接添加命令的信任列表

如上图中 1 处,点击 Run 直接执行,点击 Trust 后出现下图,需要继续点击,手动选择是信任 此命令的完全形式还是同类型的,点击后才会开始执行。

  •  

PS 快速替换证件照背景

PS 快速替换证件照背景 &ZeroWidthSpace;

由于替换背景照片的需求实在是太多,这里记录下一种使用 PS 快速更换证件照背景的方法,小白也可以轻松搞定

效果 &ZeroWidthSpace;

如下,可以看到人物边缘和发丝都很自然

步骤 &ZeroWidthSpace;

1. PS 中打开图片并解除锁定🔒 &ZeroWidthSpace;

2. 选择 菜单选择 主体 &ZeroWidthSpace;

3. 对选择到的主体右键,下拉选项中选择 选择并遮住 &ZeroWidthSpace;

执行后,会跳出一个新窗口,界面如下。此时人物边缘还带有一些背景,我们继续操作

4. 选择左侧第二个画笔,并根据图片边缘大小调整画笔大小,然后对着人物边缘进行涂抹 &ZeroWidthSpace;

涂抹后,看下图,人物已经比较清晰,杂色也去掉了

5. 拉动最右侧滚动条到最后,选择 净化颜色,然后点击 确定 &ZeroWidthSpace;

点击确定后,回到初始窗口

6. 初始窗口右下角有一个圆形半填充图标(新建涂层图标),点击后,选择 纯色 &ZeroWidthSpace;

此时新建了一个图层,并跳出一个颜色选择窗口,选择你需要的颜色后 确定 即可

7. 将新建的颜色图层往下拖动,拖到 背景 拷贝 这个图层的下方 &ZeroWidthSpace;

拖动完成后,效果如下。可以看到背景色已替换完成,且人物边缘和头发都很自然,没有锯齿和毛边

8. 选择 文件 -> 导出 -> 快速导出为png,如果是其他格式,选择 导出为,然后在导出选项中选择其他格式,最后确定即可 &ZeroWidthSpace;

  •