-
+
现代化的个人前端导航网站,为开发者提供高效、美观的资源导航体验
@@ -14,7 +14,7 @@
[](https://ant.design/)
[](./LICENSE)
-[在线演示](https://nav.weizwz.com) | [快速开始](#-快速开始) | [文档](./.kiro/specs/frontend-navigation-site/) | [部署指南](./.kiro/specs/frontend-navigation-site/QUICKSTART.md)
+
在线演示 | [快速开始](#-快速开始) | [文档](./.kiro/specs/frontend-navigation-site/) | [部署指南](./.kiro/specs/frontend-navigation-site/QUICKSTART.md)
diff --git a/app/register-sw.tsx b/app/register-sw.tsx
index 613c6c7..d536eb2 100644
--- a/app/register-sw.tsx
+++ b/app/register-sw.tsx
@@ -58,9 +58,21 @@ export default function RegisterServiceWorker() {
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
+
+ // 检查是否是首次加载(没有旧的 controller)
+ const isFirstLoad = !navigator.serviceWorker.controller;
+ if (isFirstLoad) {
+ console.log('首次加载 Service Worker,无需刷新');
+ return;
+ }
+
refreshing = true;
- console.log('Service Worker 已更新,刷新页面...');
- window.location.reload();
+ console.log('Service Worker 已更新,3秒后刷新页面...');
+
+ // 延迟刷新,给用户时间看到提示
+ setTimeout(() => {
+ window.location.reload();
+ }, 3000);
});
});
}
diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx
index 45d723b..c82b8d1 100644
--- a/components/layout/Header.tsx
+++ b/components/layout/Header.tsx
@@ -3,7 +3,7 @@
import React, { memo, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { Button, Tooltip } from 'antd';
-import { EditOutlined, MenuOutlined } from '@ant-design/icons';
+import { EditOutlined, MenuOutlined, GithubOutlined } from '@ant-design/icons';
import SearchBar from './SearchBar';
import ThemeToggle from './ThemeToggle';
@@ -75,6 +75,21 @@ const Header = memo(function Header({ onMenuClick }: HeaderProps) {
}}
/>
+ = ({ link, onEdit, onDelete, isDragg
// 获取 favicon URL 作为回退选项,使用 larger=true 获取更高质量的图标
const faviconUrl = getFaviconUrl(link.url, { larger: true });
const scale = link.iconScale || 0.7;
- const iconSize = Math.round(60 * scale);
+ const backgroundColor = link.backgroundColor;
+
+ // 判断是否为 favicon.im 的 URL
+ const isFaviconUrl = (url: string) => {
+ return url.includes('favicon.im/');
+ };
- // 情况1: 用户提供了自定义图标 URL
- if (link.icon && (link.icon.startsWith('http://') || link.icon.startsWith('https://') || link.icon.startsWith('/'))) {
+ // 情况1: 用户提供了自定义图标 URL(但不是 favicon.im 的 URL)
+ if (link.icon &&
+ (link.icon.startsWith('http://') || link.icon.startsWith('https://') || link.icon.startsWith('/')) &&
+ !isFaviconUrl(link.icon)) {
return (
);
}
// 情况2: 用户提供了 Ant Design 图标名称
- if (link.icon) {
+ if (link.icon && !link.icon.startsWith('http://') && !link.icon.startsWith('https://') && !link.icon.startsWith('/')) {
const IconComponent = (AntdIcons as any)[link.icon];
if (IconComponent) {
- return ;
+ const antdIconSize = Math.round(60 * scale);
+ return ;
}
}
- // 情况3: 没有自定义图标,尝试使用 favicon
+ // 情况3: 没有自定义图标,或者图标是 favicon.im URL,尝试使用 favicon
if (faviconUrl) {
return (
);
}
// 情况4: 所有方式都失败,显示默认图标
+ // 默认图标使用固定大小(48px),不受 iconScale 影响
+ // 如果背景是白色,使用主题色;否则使用白色
const DefaultIcon = AntdIcons.LinkOutlined;
- return ;
- }, [link.icon, link.name, link.url, link.iconScale]);
+ const defaultIconColor = isWhiteColor(backgroundColor) ? '#1890ff' : '#ffffff';
+ const defaultIconSize = 48;
+
+ return (
+
+ );
+ }, [link.icon, link.name, link.url, link.iconScale, link.backgroundColor]);
return (
void;
@@ -43,7 +44,7 @@ const LinkGridBase: React.FC = ({
}) => {
const dispatch = useAppDispatch();
const links = useAppSelector((state) => state.links.items);
- const currentCategory = useAppSelector((state) => state.settings.currentCategory || '主页');
+ const currentCategory = useAppSelector((state) => state.settings.currentCategory || getDefaultCategoryName());
const searchQuery = useAppSelector((state) => state.search.query);
const searchResults = useAppSelector((state) => state.search.results);
diff --git a/services/defaultData.ts b/services/defaultData.ts
index 735f17a..74a912b 100644
--- a/services/defaultData.ts
+++ b/services/defaultData.ts
@@ -187,3 +187,11 @@ export const defaultCategories: Category[] = (() => {
updatedAt: Date.now(),
}));
})();
+
+/**
+ * 获取默认分类名称
+ * 返回第一个分类的名称,如果没有分类则返回空字符串
+ */
+export const getDefaultCategoryName = (): string => {
+ return defaultCategories.length > 0 ? defaultCategories[0].name : '';
+};
diff --git a/store/slices/linksSlice.ts b/store/slices/linksSlice.ts
index 06b9210..5e5b098 100644
--- a/store/slices/linksSlice.ts
+++ b/store/slices/linksSlice.ts
@@ -59,9 +59,34 @@ const linksSlice = createSlice({
updateLink: (state, action: PayloadAction) => {
const index = state.items.findIndex(link => link.id === action.payload.id);
if (index !== -1) {
+ const oldLink = state.items[index];
+ const newCategory = action.payload.category;
+
+ // 检查分类是否改变
+ const categoryChanged = newCategory && newCategory !== oldLink.category;
+
+ // 如果分类改变,将链接移到新分类的最后
+ let newOrder = oldLink.order;
+ if (categoryChanged) {
+ // 找到新分类中所有链接的最大 order 值
+ const linksInNewCategory = state.items.filter(
+ link => link.category === newCategory && link.id !== action.payload.id
+ );
+
+ if (linksInNewCategory.length > 0) {
+ const maxOrder = Math.max(...linksInNewCategory.map(link => link.order));
+ newOrder = maxOrder + 1;
+ } else {
+ // 如果新分类中没有其他链接,使用当前所有链接的最大 order + 1
+ const maxOrder = Math.max(...state.items.map(link => link.order));
+ newOrder = maxOrder + 1;
+ }
+ }
+
state.items[index] = {
- ...state.items[index],
+ ...oldLink,
...action.payload,
+ order: newOrder,
updatedAt: Date.now(),
};
state.error = null;
diff --git a/store/slices/settingsSlice.ts b/store/slices/settingsSlice.ts
index 12b56e7..1848a37 100644
--- a/store/slices/settingsSlice.ts
+++ b/store/slices/settingsSlice.ts
@@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Settings, ThemeMode, LayoutMode, UpdateSettingsInput } from '@/types';
+import { getDefaultCategoryName } from '@/services/defaultData';
/**
* Settings 状态接口
@@ -18,7 +19,7 @@ const defaultSettings: Settings = {
theme: 'system',
searchEngine: 'google',
layout: 'grid',
- currentCategory: '常用工具',
+ currentCategory: getDefaultCategoryName(),
showDescription: true,
gridColumns: 6,
};