diff --git a/code/docusaurus/.trae/documents/bookmarks_import_plan.md b/code/docusaurus/.trae/documents/bookmarks_import_plan.md
new file mode 100644
index 00000000..200b3178
--- /dev/null
+++ b/code/docusaurus/.trae/documents/bookmarks_import_plan.md
@@ -0,0 +1,48 @@
+# 书签导入与整理计划
+
+## 需求分析
+用户希望将浏览器书签文件 `D:\bookmarks_2026_5_26.html` 中的内容导入并整理到 `docs/resources.mdx` 文件中。
+
+## 书签文件结构分析
+
+从读取的书签文件中识别出以下主要分组:
+
+### 第一层分组
+1. **AI 智能助手** - 包含 ChatGPT、腾讯元宝、v0 等
+2. **前端开发** - 包含多个子文件夹:
+ - 图标库 (expo icons, iconfont, icones, lucide)
+ - 前端模板库 (uiverse, codepen, htmlrev, lottiefiles, mockuphone)
+ - TinyMCE 相关
+ - Uniapp 相关
+ - 前端组件库 (Arco Design)
+ - React Native 第三方库
+ - 插画库 (undraw, storyset, manypixels)
+ - React Native 开发相关
+3. **Python** - PaddlePaddle 相关
+
+## 实现方案
+
+### 步骤
+1. 创建一个 Node.js 脚本来解析书签 HTML 文件
+2. 将解析结果转换为资源导航所需的分组格式
+3. 更新 `docs/resources.mdx` 文件
+
+### 修改文件
+- `docs/resources.mdx` - 更新为解析后的书签内容
+
+## 输出格式
+使用现有的 `SearchableLinkGrid` 组件格式,保持分组结构清晰。
+
+## 过滤规则
+1. 跳过重复链接
+2. 清理过长的标题(超过50字符截断)
+3. 提取有意义的描述信息
+
+## 风险评估
+- 文件较大(50KB+),需要处理大量数据
+- 部分链接可能失效,建议用户后续验证
+
+## 测试计划
+1. 解析书签文件
+2. 生成新的 resources.mdx
+3. 启动开发服务器验证显示效果
\ No newline at end of file
diff --git a/code/docusaurus/.trae/documents/search_filter_plan.md b/code/docusaurus/.trae/documents/search_filter_plan.md
new file mode 100644
index 00000000..32f28c17
--- /dev/null
+++ b/code/docusaurus/.trae/documents/search_filter_plan.md
@@ -0,0 +1,102 @@
+# 资源导航搜索过滤功能实现计划
+
+## 需求分析
+
+用户希望在 `docs/resources.mdx` 文件中添加一个搜索框,实现本地过滤功能,可以根据关键词过滤显示的资源卡片。
+
+## 现有代码结构分析
+
+### 当前文件结构
+
+* `docs/resources.mdx` - 资源导航页面,使用 LinkGrid 和 LinkCard 组件
+
+* `src/components/Link/LinkCard.tsx` - 单个链接卡片组件
+
+* `src/components/Link/LinkGrid.tsx` - 卡片网格容器组件
+
+### 当前实现方式
+
+`LinkGrid` 组件直接渲染所有子元素,没有过滤功能。
+
+## 实现方案
+
+### 方案选择
+
+创建一个新的带搜索功能的组件 `SearchableLinkGrid`,包含:
+
+1. 搜索输入框
+2. 过滤逻辑(基于 title 和 description)
+3. 集成现有 LinkCard 组件
+
+### 修改文件列表
+
+| 文件 | 操作 | 说明 |
+| -------------------------------------------- | -- | ------------ |
+| `src/components/Link/SearchableLinkGrid.tsx` | 新建 | 带搜索过滤的网格组件 |
+| `docs/resources.mdx` | 修改 | 引入新组件并重构内容结构 |
+
+## 实现步骤
+
+### 步骤 1: 创建 SearchableLinkGrid 组件
+
+* 使用 React useState 管理搜索关键词
+
+* 使用 useMemo 进行过滤计算
+
+* 支持按 title 和 description 进行模糊搜索
+
+### 步骤 2: 修改 resources.mdx
+
+* 引入新组件
+
+* 将现有 LinkCard 包装在 SearchableLinkGrid 中
+
+## 技术实现细节
+
+### 搜索过滤逻辑
+
+```typescript
+// 过滤逻辑
+const filteredCards = useMemo(() => {
+ if (!searchTerm) return children
+ const lowerTerm = searchTerm.toLowerCase()
+ return // 过滤匹配的卡片
+}, [searchTerm, children])
+```
+
+### 组件设计
+
+* 搜索框位于网格上方
+
+* 实时过滤,无需点击搜索按钮
+
+* 支持中英文搜索
+
+## 风险评估
+
+### 潜在风险
+
+1. **性能问题**:如果卡片数量很大,过滤可能有性能问题
+
+ * 缓解:使用 useMemo 缓存过滤结果
+
+2. **样式冲突**:新组件可能与现有样式冲突
+
+ * 缓解:使用独立的 CSS 模块
+
+3. **React 版本兼容性**:需要确保使用 React 19 兼容的写法
+
+ * 缓解:使用标准 React hooks
+
+## 依赖检查
+
+* 项目已安装 React 19 和相关类型定义
+
+* 使用现有样式系统 (CSS Modules)
+
+## 测试计划
+
+1. 启动开发服务器验证功能
+2. 测试搜索功能是否正常工作
+3. 验证样式是否正确显示
+
diff --git a/code/docusaurus/docs/resources.mdx b/code/docusaurus/docs/resources.mdx
new file mode 100644
index 00000000..c008365c
--- /dev/null
+++ b/code/docusaurus/docs/resources.mdx
@@ -0,0 +1,333 @@
+---
+id: resources
+title: 资源导航
+sidebar_position: false
+hide_table_of_contents: true
+---
+
+import SearchableLinkGrid from '@site/src/components/Link/SearchableLinkGrid';
+
+
\ No newline at end of file
diff --git a/code/docusaurus/docusaurus.config.ts b/code/docusaurus/docusaurus.config.ts
index 9cc56f74..0ecc2c62 100644
--- a/code/docusaurus/docusaurus.config.ts
+++ b/code/docusaurus/docusaurus.config.ts
@@ -108,6 +108,11 @@ const config: Config = {
position: 'left',
sidebarId: 'DatabaseSidebar',
},
+ {
+ to: '/docs/resources',
+ label: '资源导航',
+ position: 'left',
+ },
{
to: '/blog',
label: '博客',
@@ -160,6 +165,9 @@ const config: Config = {
maxHeadingLevel: 5,
},
} satisfies Preset.ThemeConfig,
+ plugins: [
+ 'docusaurus-plugin-sass',
+ ],
}
export default config
diff --git a/code/docusaurus/package.json b/code/docusaurus/package.json
index d720c95b..e8d5b6f2 100644
--- a/code/docusaurus/package.json
+++ b/code/docusaurus/package.json
@@ -25,9 +25,11 @@
"@docusaurus/theme-common": "^3.9.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
+ "docusaurus-plugin-sass": "^0.2.6",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "sass": "^1.100.0"
},
"devDependencies": {
"@antfu/eslint-config": "^6.7.3",
diff --git a/code/docusaurus/pnpm-lock.yaml b/code/docusaurus/pnpm-lock.yaml
index 22fc19e5..6dc9a63c 100644
--- a/code/docusaurus/pnpm-lock.yaml
+++ b/code/docusaurus/pnpm-lock.yaml
@@ -29,6 +29,9 @@ importers:
clsx:
specifier: ^2.0.0
version: 2.1.1
+ docusaurus-plugin-sass:
+ specifier: ^0.2.6
+ version: 0.2.6(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.6.3))(sass@1.100.0)(webpack@5.104.1)
prism-react-renderer:
specifier: ^2.3.0
version: 2.4.1(react@19.2.3)
@@ -38,6 +41,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.2.3(react@19.2.3)
+ sass:
+ specifier: ^1.100.0
+ version: 1.100.0
devDependencies:
'@antfu/eslint-config':
specifier: ^6.7.3
@@ -1556,6 +1562,94 @@ packages:
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
+ '@parcel/watcher-android-arm64@2.5.6':
+ resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.6':
+ resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.6':
+ resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.6':
+ resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.6':
+ resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-arm-musl@2.5.6':
+ resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.6':
+ resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.6':
+ resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.6':
+ resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-x64-musl@2.5.6':
+ resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-win32-arm64@2.5.6':
+ resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.6':
+ resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.6':
+ resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.6':
+ resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
+ engines: {node: '>= 10.0.0'}
+
'@pkgr/core@0.2.9':
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -2346,6 +2440,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
+ chokidar@5.0.0:
+ resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
+ engines: {node: '>= 20.19.0'}
+
chrome-trace-event@1.0.4:
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
engines: {node: '>=6.0'}
@@ -2727,6 +2825,10 @@ packages:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
detect-node@2.1.0:
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
@@ -2750,6 +2852,12 @@ packages:
resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
engines: {node: '>=6'}
+ docusaurus-plugin-sass@0.2.6:
+ resolution: {integrity: sha512-2hKQQDkrufMong9upKoG/kSHJhuwd+FA3iAe/qzS/BmWpbIpe7XKmq5wlz4J5CJaOPu4x+iDJbgAxZqcoQf0kg==}
+ peerDependencies:
+ '@docusaurus/core': ^2.0.0-beta || ^3.0.0-alpha
+ sass: ^1.30.0
+
dom-converter@0.2.0:
resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==}
@@ -3622,6 +3730,9 @@ packages:
engines: {node: '>=16.x'}
hasBin: true
+ immutable@5.1.5:
+ resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==}
+
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@@ -4312,6 +4423,9 @@ packages:
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
node-emoji@2.2.0:
resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==}
engines: {node: '>=18'}
@@ -5082,6 +5196,10 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
+ readdirp@5.0.0:
+ resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
+ engines: {node: '>= 20.19.0'}
+
recma-build-jsx@1.0.0:
resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==}
@@ -5241,6 +5359,32 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ sass-loader@16.0.8:
+ resolution: {integrity: sha512-hcov4ZwZJIGbEuyNr9EmiTmZueyrxSToE6GOzoZnq5JM7ecRO7ttyvilPn+VmRsqiP16+VYZzVnGZj/hzZgKBA==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0
+ node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+ sass: ^1.3.0
+ sass-embedded: '*'
+ webpack: ^5.0.0
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+ node-sass:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ webpack:
+ optional: true
+
+ sass@1.100.0:
+ resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
+ engines: {node: '>=20.19.0'}
+ hasBin: true
+
sax@1.4.3:
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
@@ -8344,6 +8488,67 @@ snapshots:
'@opentelemetry/api@1.9.0': {}
+ '@parcel/watcher-android-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-darwin-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-freebsd-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-x64-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-x64-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-ia32@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher@2.5.6':
+ dependencies:
+ detect-libc: 2.1.2
+ is-glob: 4.0.3
+ node-addon-api: 7.1.1
+ picomatch: 4.0.3
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.6
+ '@parcel/watcher-darwin-arm64': 2.5.6
+ '@parcel/watcher-darwin-x64': 2.5.6
+ '@parcel/watcher-freebsd-x64': 2.5.6
+ '@parcel/watcher-linux-arm-glibc': 2.5.6
+ '@parcel/watcher-linux-arm-musl': 2.5.6
+ '@parcel/watcher-linux-arm64-glibc': 2.5.6
+ '@parcel/watcher-linux-arm64-musl': 2.5.6
+ '@parcel/watcher-linux-x64-glibc': 2.5.6
+ '@parcel/watcher-linux-x64-musl': 2.5.6
+ '@parcel/watcher-win32-arm64': 2.5.6
+ '@parcel/watcher-win32-ia32': 2.5.6
+ '@parcel/watcher-win32-x64': 2.5.6
+ optional: true
+
'@pkgr/core@0.2.9': {}
'@pnpm/config.env-replace@1.1.0': {}
@@ -9301,6 +9506,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ chokidar@5.0.0:
+ dependencies:
+ readdirp: 5.0.0
+
chrome-trace-event@1.0.4: {}
ci-info@3.9.0: {}
@@ -9652,6 +9861,9 @@ snapshots:
destroy@1.2.0: {}
+ detect-libc@2.1.2:
+ optional: true
+
detect-node@2.1.0: {}
detect-port@1.6.1:
@@ -9675,6 +9887,17 @@ snapshots:
dependencies:
'@leichtgewicht/ip-codec': 2.0.5
+ docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.6.3))(sass@1.100.0)(webpack@5.104.1):
+ dependencies:
+ '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.6.3)
+ sass: 1.100.0
+ sass-loader: 16.0.8(sass@1.100.0)(webpack@5.104.1)
+ transitivePeerDependencies:
+ - '@rspack/core'
+ - node-sass
+ - sass-embedded
+ - webpack
+
dom-converter@0.2.0:
dependencies:
utila: 0.4.0
@@ -10804,6 +11027,8 @@ snapshots:
image-size@2.0.2: {}
+ immutable@5.1.5: {}
+
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@@ -11703,6 +11928,9 @@ snapshots:
lower-case: 2.0.2
tslib: 2.8.1
+ node-addon-api@7.1.1:
+ optional: true
+
node-emoji@2.2.0:
dependencies:
'@sindresorhus/is': 4.6.0
@@ -12532,6 +12760,8 @@ snapshots:
dependencies:
picomatch: 2.3.1
+ readdirp@5.0.0: {}
+
recma-build-jsx@1.0.0:
dependencies:
'@types/estree': 1.0.8
@@ -12743,6 +12973,21 @@ snapshots:
safer-buffer@2.1.2: {}
+ sass-loader@16.0.8(sass@1.100.0)(webpack@5.104.1):
+ dependencies:
+ neo-async: 2.6.2
+ optionalDependencies:
+ sass: 1.100.0
+ webpack: 5.104.1
+
+ sass@1.100.0:
+ dependencies:
+ chokidar: 5.0.0
+ immutable: 5.1.5
+ source-map-js: 1.2.1
+ optionalDependencies:
+ '@parcel/watcher': 2.5.6
+
sax@1.4.3: {}
scheduler@0.27.0: {}
diff --git a/code/docusaurus/src/components/Link/LinkCard.tsx b/code/docusaurus/src/components/Link/LinkCard.tsx
new file mode 100644
index 00000000..f372e7bf
--- /dev/null
+++ b/code/docusaurus/src/components/Link/LinkCard.tsx
@@ -0,0 +1,61 @@
+import React from 'react'
+import styles from './styles.module.css'
+
+export interface LinkCardProps {
+ title: string
+ description: string
+ href: string
+ icon?: string
+}
+
+export default function LinkCard({
+ title,
+ description,
+ href,
+ icon,
+}: LinkCardProps) {
+ let favicon = icon
+
+ if (!favicon) {
+ try {
+ const hostname = new URL(href).hostname
+ favicon = `https://www.google.com/s2/favicons?domain=${hostname}&sz=128`
+ }
+ catch {
+ favicon = undefined
+ }
+ }
+
+ return (
+
+
+ {favicon
+ ? (
+

+ )
+ : (
+
+ {title.charAt(0)}
+
+ )}
+
+
+ {title}
+
+
+
+
+ {description}
+
+
+ )
+}
diff --git a/code/docusaurus/src/components/Link/LinkGrid.tsx b/code/docusaurus/src/components/Link/LinkGrid.tsx
new file mode 100644
index 00000000..561b40e6
--- /dev/null
+++ b/code/docusaurus/src/components/Link/LinkGrid.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import styles from './styles.module.css'
+
+interface Props {
+ children: React.ReactNode
+}
+
+export default function LinkGrid({ children }: Props) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx b/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx
new file mode 100644
index 00000000..e78578ac
--- /dev/null
+++ b/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx
@@ -0,0 +1,98 @@
+import React, { useState, useMemo } from 'react'
+import LinkCard, { LinkCardProps } from './LinkCard'
+import styles from './styles.module.css'
+
+export interface GroupedLinkCardProps {
+ groupName: string
+ items: LinkCardProps[]
+}
+
+interface SearchableLinkGridProps {
+ groups: GroupedLinkCardProps[]
+}
+
+export default function SearchableLinkGrid({ groups }: SearchableLinkGridProps) {
+ const [searchTerm, setSearchTerm] = useState('')
+
+ const filteredGroups = useMemo(() => {
+ if (!searchTerm.trim()) return groups
+
+ const lowerTerm = searchTerm.toLowerCase()
+ return groups.map(group => ({
+ ...group,
+ items: group.items.filter(
+ item =>
+ item.title.toLowerCase().includes(lowerTerm) ||
+ item.description.toLowerCase().includes(lowerTerm),
+ ),
+ })).filter(group => group.items.length > 0)
+ }, [searchTerm, groups])
+
+ const totalItems = groups.reduce((sum, group) => sum + group.items.length, 0)
+ const filteredTotalItems = filteredGroups.reduce((sum, group) => sum + group.items.length, 0)
+
+ return (
+
+
+
+
+
setSearchTerm(e.target.value)}
+ className={styles.searchInput}
+ />
+
+
+ {filteredTotalItems} / {totalItems} 项
+
+
+
+ {filteredGroups.length > 0 ? (
+ filteredGroups.map((group, groupIndex) => (
+
+
{group.groupName}
+
+ {group.items.map((item, itemIndex) => (
+
+ ))}
+
+
+ ))
+ ) : (
+
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/code/docusaurus/src/components/Link/styles.module.css b/code/docusaurus/src/components/Link/styles.module.css
new file mode 100644
index 00000000..790f618e
--- /dev/null
+++ b/code/docusaurus/src/components/Link/styles.module.css
@@ -0,0 +1,231 @@
+.grid {
+ display: grid;
+ gap: 16px;
+ margin-top: 16px;
+
+ /* 默认桌面4列 */
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+/* 平板 */
+@media (max-width: 1200px) {
+ .grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
+
+/* 小平板 */
+@media (max-width: 996px) {
+ .grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+/* 手机 */
+@media (max-width: 576px) {
+ .grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* 卡片 */
+
+.card {
+ display: flex;
+ flex-direction: column;
+
+ padding: 18px;
+
+ border-radius: 14px;
+ border: 1px solid var(--ifm-color-emphasis-200);
+
+ background: var(--ifm-card-background-color);
+
+ color: inherit;
+ text-decoration: none;
+
+ transition:
+ transform 0.2s ease,
+ border-color 0.2s ease,
+ box-shadow 0.2s ease;
+}
+
+.card:hover {
+ color: inherit;
+ text-decoration: none;
+
+ transform: translateY(-3px);
+
+ border-color: var(--ifm-color-primary);
+
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
+}
+
+/* 标题区域 */
+
+.header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+/* 图标 */
+
+.icon {
+ width: 32px;
+ height: 32px;
+
+ object-fit: contain;
+
+ flex-shrink: 0;
+}
+
+.placeholder {
+ width: 40px;
+ height: 40px;
+
+ border-radius: 10px;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ background: var(--ifm-color-primary);
+ color: white;
+
+ font-size: 18px;
+ font-weight: 700;
+
+ flex-shrink: 0;
+}
+
+/* 标题 */
+
+.title {
+ font-size: 17px;
+ font-weight: 600;
+ line-height: 1.4;
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* 描述 */
+
+.description {
+ margin-top: 14px;
+
+ font-size: 14px;
+ line-height: 1.6;
+
+ color: var(--ifm-color-emphasis-700);
+
+ overflow: hidden;
+
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+/* 搜索容器 */
+
+.searchContainer {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 24px;
+ width: 100%;
+}
+
+.searchWrapper {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ background: var(--ifm-card-background-color);
+ border: 1px solid var(--ifm-color-emphasis-200);
+ border-radius: 12px;
+ padding: 2px 2px 2px 16px;
+ transition:
+ border-color 0.2s ease,
+ box-shadow 0.2s ease;
+}
+
+.searchWrapper:focus-within {
+ border-color: var(--ifm-color-primary);
+ box-shadow: 0 0 0 3px var(--ifm-color-primary-alpha-10);
+}
+
+.searchIcon {
+ color: var(--ifm-color-emphasis-400);
+ margin-right: 12px;
+ flex-shrink: 0;
+}
+
+.searchInput {
+ flex: 1;
+ width: 100%;
+ padding: 12px 16px 12px 0;
+ border: none;
+ border-radius: 0 10px 10px 0;
+ font-size: 15px;
+ background: transparent;
+ color: var(--ifm-font-color-base);
+}
+
+.searchInput:focus {
+ outline: none;
+}
+
+.searchInput::placeholder {
+ color: var(--ifm-color-emphasis-500);
+}
+
+.searchCount {
+ font-size: 13px;
+ color: var(--ifm-color-emphasis-500);
+ white-space: nowrap;
+ padding: 6px 12px;
+ background: var(--ifm-color-emphasis-100);
+ border-radius: 20px;
+}
+
+/* 分组样式 */
+
+.group {
+ margin-bottom: 32px;
+}
+
+.group:last-child {
+ margin-bottom: 0;
+}
+
+.groupTitle {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--ifm-color-emphasis-800);
+ margin-bottom: 20px;
+ padding-left: 14px;
+ border-left: 4px solid var(--ifm-color-primary);
+}
+
+/* 无结果状态 */
+
+.noResults {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+ color: var(--ifm-color-emphasis-600);
+}
+
+.noResultsIcon {
+ color: var(--ifm-color-emphasis-400);
+ margin-bottom: 16px;
+}
+
+.noResults p {
+ margin: 0;
+ font-size: 15px;
+}