20260526152617

This commit is contained in:
oneao committed 2026-05-26 15:26:17 +08:00
1 parent 843529e57a
commit 223435efa6
10 files changed
+1143 -1

No files matched your search

@@ -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. 启动开发服务器验证显示效果
@@ -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. 验证样式是否正确显示
+333
View File
@@ -0,0 +1,333 @@
---
id: resources
title: 资源导航
sidebar_position: false
hide_table_of_contents: true
---
import SearchableLinkGrid from '@site/src/components/Link/SearchableLinkGrid';
<SearchableLinkGrid
groups={[
{
groupName: 'AI 助手',
items: [
{
title: 'ChatGPT',
description: 'OpenAI 推出的智能对话助手',
href: 'https://chatgpt.com/',
},
{
title: '腾讯元宝',
description: '腾讯混元大模型智能助手',
href: 'https://yuanbao.tencent.com/chat/naQivTmsDa',
},
{
title: 'v0.dev',
description: 'AI 代码生成工具',
href: 'http://v0.dev/',
},
],
},
{
groupName: '大模型与AI',
items: [
{
title: 'Ollama',
description: '本地大模型运行工具',
href: 'https://ollama.com/',
},
{
title: 'OpenCompass',
description: '大模型评测榜单',
href: 'https://rank.opencompass.org.cn/leaderboard-multimodal/',
},
{
title: 'InternVL',
description: '多模态大模型',
href: 'https://internvl.readthedocs.io/en/latest/internvl3.0/quick_start.html',
},
{
title: 'Qwen',
description: '阿里云通义千问',
href: 'https://mcp.csdn.net/683559a9606a8318e85a74e7.html',
},
{
title: '腾讯混元',
description: '腾讯大模型',
href: 'https://github.com/Tencent-Hunyuan/Tencent-Hunyuan-Large',
},
{
title: 'MinerU',
description: '文档解析工具',
href: 'https://opendatalab.github.io/MinerU/zh/',
},
{
title: 'PaddleOCR',
description: 'OCR 识别工具',
href: 'https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html',
},
{
title: 'PaddleNLP',
description: '自然语言处理框架',
href: 'https://paddlenlp.readthedocs.io/zh/latest/advanced_guide/distributed_training.html',
},
{
title: 'PaddleServing',
description: '模型部署服务',
href: 'https://github.com/PaddlePaddle/Serving/blob/v0.9.0/examples/Pipeline/PaddleOCR/ocr/README_CN.md',
},
{
title: '免费 AI 接口整理',
description: '4种免费 AI 接口',
href: 'https://www.cnblogs.com/vipstone/p/19142030',
},
{
title: 'HuggingFace 模型转换',
description: 'GGUF 格式转换教程',
href: 'https://zhuanlan.zhihu.com/p/721617987',
},
],
},
{
groupName: '计算机视觉',
items: [
{
title: 'YOLOv11',
description: '目标检测模型',
href: 'https://modelers.csdn.net/683fe614606a8318e85b0e13.html',
},
{
title: 'X-AnyLabeling',
description: '标注工具',
href: 'https://github.com/CVHub520/X-AnyLabeling',
},
{
title: 'GeoLayoutLM',
description: '文档智能模型',
href: 'https://mp.weixin.qq.com/s?__biz=Mzg4NjI0NDg0Ng==',
},
{
title: 'LayoutReader',
description: '文档阅读顺序模型',
href: 'https://developer.volcengine.com/articles/7385010087594721319',
},
{
title: 'PubLayNet',
description: '文档布局数据集',
href: 'https://opendatalab.com/OpenDataLab/PubLayNet/tree/main',
},
],
},
{
groupName: '前端开发',
items: [
{
title: 'Tauri',
description: '跨平台桌面应用框架',
href: 'https://v2.tauri.app/zh-cn/',
},
{
title: 'Arco Design Vue',
description: '字节跳动前端组件库',
href: 'https://arco.design/vue/docs/start',
},
{
title: 'gluestack-ui',
description: 'React Native UI 套件',
href: 'https://gluestack.io/ui/docs/home/overview/quick-start',
},
{
title: 'React Native Directory',
description: '第三方库目录',
href: 'https://reactnative.directory/packages',
},
{
title: 'Awesome RN',
description: '学习资源精选',
href: 'https://github.com/wabg/awesome-react-native',
},
{
title: 'RN UI Datepicker',
description: '日期选择组件',
href: 'https://farhoudshapouran.github.io/react-native-ui-datepicker/',
},
{
title: 'Victory Charts',
description: '数据可视化库',
href: 'https://nearform.com/open-source/victory/docs/introduction/',
},
{
title: 'React Native 第三方库',
description: '常用库汇总',
href: 'https://www.cnblogs.com/youhui/articles/11771865.html',
},
{
title: 'Expo 热更新',
description: '不使用 EAS 的热更新方案',
href: 'https://juejin.cn/post/7390659669113929779',
},
{
title: '原生插件集成指南',
description: 'uni-app 原生插件文档',
href: 'https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android.html',
},
{
title: '离线打包教程',
description: 'Android 离线打包制作',
href: 'https://ask.dcloud.net.cn/article/35482',
},
],
},
{
groupName: 'UI 资源',
items: [
{
title: 'Expo Vector Icons',
description: 'React Native 图标库',
href: 'https://icons.expo.fyi/Index',
},
{
title: 'IconFont',
description: '阿里巴巴矢量图标库',
href: 'https://www.iconfont.cn/',
},
{
title: 'Icônes',
description: '开源图标库集合',
href: 'https://icones.js.org/collection/all',
},
{
title: 'Lucide',
description: '精美的开源图标库',
href: 'https://lucide.dev/icons/',
},
{
title: 'SVG Repo',
description: '免费 SVG 矢量图标',
href: 'https://www.svgrepo.com/',
},
{
title: 'UIverse',
description: 'CSS & Tailwind UI 组件',
href: 'https://uiverse.io/elements',
},
{
title: 'CodePen',
description: '前端代码分享社区',
href: 'https://codepen.io/trending',
},
{
title: 'HTML Rev',
description: '免费 HTML 网站模板',
href: 'https://htmlrev.com/',
},
{
title: 'LottieFiles',
description: '动画资源平台',
href: 'https://app.lottiefiles.com/search',
},
{
title: 'MockUPhone',
description: '设备样机展示',
href: 'https://mockuphone.com/model/iphone-15/',
},
{
title: 'unDraw',
description: '开源插画库',
href: 'https://undraw.co/',
},
{
title: 'Storyset',
description: '可自定义插画',
href: 'https://storyset.com/',
},
{
title: 'ManyPixels',
description: '免费插画素材',
href: 'https://www.manypixels.co/gallery',
},
],
},
{
groupName: '开发工具',
items: [
{
title: 'NestJS',
description: 'Node.js 企业级框架',
href: 'https://nest.nodejs.cn/',
},
{
title: 'JDK 8',
description: 'Java 开发工具包',
href: 'https://adoptium.net/zh-CN/temurin/releases?version=8',
},
{
title: 'Go',
description: 'Golang 下载',
href: 'https://go.dev/dl/',
},
{
title: 'nvm-windows',
description: 'Node 版本管理',
href: 'https://github.com/coreybutler/nvm-windows/releases',
},
{
title: 'URL Encoder',
description: 'SVG URL 编码工具',
href: 'https://yoksel.github.io/url-encoder/zh-cn/',
},
{
title: 'Dependency Walker',
description: '依赖分析工具',
href: 'https://www.dependencywalker.com/',
},
{
title: '天地图 API',
description: '地理编码服务',
href: 'http://lbs.tianditu.gov.cn/server/geocoding.html',
},
{
title: '天地图示例',
description: 'JS API 示例',
href: 'http://lbs.tianditu.gov.cn/api/js4.0/examples.html',
},
{
title: '天地图 UniApp',
description: 'UniApp 插件',
href: 'https://ext.dcloud.net.cn/plugin?id=22630',
},
{
title: 'Ollama 完整教程',
description: '本地 LLM 管理',
href: 'https://www.cnblogs.com/obullxl/p/18295202',
},
],
},
{
groupName: '效率工具',
items: [
{
title: 'DeepL翻译',
description: 'AI 驱动的翻译工具',
href: 'https://www.deepl.com/zh/translator',
},
{
title: 'Snipaste',
description: '截图工具',
href: 'https://www.snipaste.com/',
},
{
title: '腾讯桌面整理',
description: '桌面文件整理工具',
href: 'https://guanjia.qq.com/product/zmzl/',
},
{
title: 'Twinkle Tray',
description: '屏幕亮度调节',
href: 'https://twinkletray.com/',
},
],
},
]}
/>
+8
View File
@@ -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
+3 -1
View File
@@ -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",
+245
View File
@@ -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: {}
@@ -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 (
<a
className={styles.card}
href={href}
target="_blank"
rel="noopener noreferrer"
>
<div className={styles.header}>
{favicon
? (
<img
src={favicon}
alt={title}
className={styles.icon}
/>
)
: (
<div className={styles.placeholder}>
{title.charAt(0)}
</div>
)}
<span className={styles.title}>
{title}
</span>
</div>
<div className={styles.description}>
{description}
</div>
</a>
)
}
@@ -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 (
<div className={styles.grid}>
{children}
</div>
)
}
@@ -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 (
<div>
<div className={styles.searchContainer}>
<div className={styles.searchWrapper}>
<svg
className={styles.searchIcon}
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
<input
type="text"
placeholder="搜索资源..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
className={styles.searchInput}
/>
</div>
<span className={styles.searchCount}>
{filteredTotalItems} / {totalItems} 项
</span>
</div>
{filteredGroups.length > 0 ? (
filteredGroups.map((group, groupIndex) => (
<div key={groupIndex} className={styles.group}>
<h3 className={styles.groupTitle}>{group.groupName}</h3>
<div className={styles.grid}>
{group.items.map((item, itemIndex) => (
<LinkCard key={itemIndex} {...item} />
))}
</div>
</div>
))
) : (
<div className={styles.noResults}>
<svg
className={styles.noResultsIcon}
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
<path d="M8 11s1.5-2 4-2 4 2 4 2" />
</svg>
<p>未找到匹配的资源</p>
</div>
)}
</div>
)
}
@@ -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;
}