From 970a439fabc6d75446b6ec5e5586e2c259e96ff2 Mon Sep 17 00:00:00 2001 From: chunyu <12914@example.com> Date: Fri, 28 Aug 2026 13:53:30 +0800 Subject: [PATCH] fix: update - dark mode, i18n, bug fixes across frontend pages --- src/App.tsx | 3 +- .../MobileBottomNav/MobileBottomNav.tsx | 2 - src/components/MobileTopNav/MobileTopNav.tsx | 5 - src/components/Navbar/Navbar.tsx | 4 +- .../ToolFavoriteButton/ToolFavoriteButton.tsx | 3 +- src/contexts/LoginModalContext.tsx | 2 +- src/global.css | 250 ++++++++++++++++++ src/pages/AQIDetails/AQIDetails.tsx | 4 +- src/pages/AQIDetails/AQIDetailsMobile.tsx | 2 +- src/pages/ApiDirectory/ApiDirectory.tsx | 12 - .../ApiDirectory/ApiDirectoryDesktop.tsx | 1 + src/pages/ApiDirectory/ApiDirectoryMobile.tsx | 1 + src/pages/ArticleDetail/ArticleDetail.tsx | 4 +- src/pages/ArticleEditor/ArticleEditor.tsx | 3 +- src/pages/Articles/Articles.css | 2 + src/pages/Articles/Articles.tsx | 7 - src/pages/BaiduTranslate/BaiduTranslate.tsx | 2 +- .../BaiduTranslate/BaiduTranslateMobile.tsx | 6 +- .../BaiduTranslateDetail.tsx | 2 +- src/pages/Changelog/ChangelogMobile.css | 27 +- src/pages/Chat/Chat.tsx | 81 +++--- src/pages/Chat/ChatMobile.tsx | 10 +- src/pages/CodeFormatter/CodeFormatter.tsx | 1 + src/pages/CurrencyDetail/CurrencyDetail.tsx | 6 +- .../EncodingConverter/EncodingConverter.tsx | 1 - src/pages/History/History.tsx | 1 - src/pages/Home/HomeMobile.tsx | 4 +- .../IPLocationApiDetail.tsx | 1 - src/pages/ImageEditor/ImageEditor.tsx | 4 +- src/pages/Messages/MessagesMobile.css | 8 +- src/pages/NotFound/NotFound.css | 155 +++++++++++ src/pages/NotFound/NotFound.tsx | 29 +- .../PasswordGeneratorApiDetail.tsx | 4 +- src/pages/Profile/components/UserInfoCard.tsx | 3 - .../QRCodeGeneratorApiDetail.tsx | 2 +- src/pages/RegexTool/RegexTool.tsx | 2 +- src/pages/Settings/Settings.css | 11 +- src/pages/Settings/Settings.tsx | 6 +- src/pages/ShortUrlDetail/ShortUrlDetail.tsx | 13 +- .../ShortUrlDetail/ShortUrlDetailMobile.tsx | 9 +- src/pages/SystemMessage/SystemMessage.tsx | 12 +- src/pages/TaskCenter/TaskCenter.tsx | 6 +- .../TimestampTool/TimestampToolMobile.tsx | 1 + src/pages/UserHome/UserHome.tsx | 1 + .../components/CourseProgressCard.tsx | 2 +- src/pages/WeatherDetail/WeatherDetail.tsx | 2 +- .../WeatherDetail/WeatherDetailMobile.tsx | 2 +- src/utils/htmlSanitizer.ts | 17 ++ src/utils/request.ts | 17 +- 49 files changed, 597 insertions(+), 156 deletions(-) create mode 100644 src/pages/NotFound/NotFound.css create mode 100644 src/utils/htmlSanitizer.ts diff --git a/src/App.tsx b/src/App.tsx index 2ffbad2..efe98ea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { Spin, App as AntdApp } from "antd"; import Navbar from "@/components/Navbar/Navbar"; import MobileGuard from "@/components/MobileGuard/MobileGuard"; import AuthGuard from "@/components/AuthGuard/AuthGuard"; +import MobileTopNav from "@/components/MobileTopNav/MobileTopNav"; import useIsMobile from "@/hooks/useIsMobile"; import { useRouteTracker } from "@/hooks/useRouteTracker"; @@ -120,7 +121,7 @@ const App: React.FC = () => { useEffect(() => { import("@/utils/message").then(({ setMessageInstance }) => { - setMessageInstance(message); + setMessageInstance(message as any); }); }, [message]); diff --git a/src/components/MobileBottomNav/MobileBottomNav.tsx b/src/components/MobileBottomNav/MobileBottomNav.tsx index a1d3b73..ca2b886 100644 --- a/src/components/MobileBottomNav/MobileBottomNav.tsx +++ b/src/components/MobileBottomNav/MobileBottomNav.tsx @@ -6,9 +6,7 @@ import { ApiOutlined, ToolOutlined, ReadOutlined, - FolderOutlined, UserOutlined, - SearchOutlined, } from "@ant-design/icons"; import "./MobileBottomNav.css"; diff --git a/src/components/MobileTopNav/MobileTopNav.tsx b/src/components/MobileTopNav/MobileTopNav.tsx index 23c8d81..3a1ff36 100644 --- a/src/components/MobileTopNav/MobileTopNav.tsx +++ b/src/components/MobileTopNav/MobileTopNav.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import { SearchOutlined, @@ -10,19 +9,15 @@ import { import { Avatar, Badge } from "antd"; import { selectIsLogin, - selectUser, } from "@/features/login"; import { api_request } from "@/utils/request"; import { useLoginModal } from "@/contexts/LoginModalContext"; import ThemeToggle from "../ThemeToggle/ThemeToggle"; -import LanguageSwitcher from "../LanguageSwitcher/LanguageSwitcher"; import "./MobileTopNav.css"; const MobileTopNav: React.FC = () => { - const { t } = useTranslation(); const navigate = useNavigate(); const isLogin = useSelector(selectIsLogin); - const user = useSelector(selectUser); const { openLoginModal } = useLoginModal(); const [unreadCount, setUnreadCount] = useState(0); diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index f858113..d917c6b 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -532,7 +532,7 @@ const Navbar: React.FC = () => { } else if (info.key === "wallet-points") { navigation("/wallet/points"); } else if (info.key === "wallet-recharge") { - navigation("/wallet/recharge"); + navigation("/wallet/coins/recharge"); } else if (info.key === "wallet-invite") { navigation("/wallet/invite"); } else if (info.key === "task-center") { @@ -561,7 +561,7 @@ const Navbar: React.FC = () => { onClick={() => navigation("/user")} style={{ cursor: "pointer" }} > - 用户10086 + {user?.username || t("nav.user")} ) : ( diff --git a/src/components/ToolFavoriteButton/ToolFavoriteButton.tsx b/src/components/ToolFavoriteButton/ToolFavoriteButton.tsx index 5274786..0736922 100644 --- a/src/components/ToolFavoriteButton/ToolFavoriteButton.tsx +++ b/src/components/ToolFavoriteButton/ToolFavoriteButton.tsx @@ -62,7 +62,7 @@ const fetchToolUrlPathMap = (): Promise> => { const ToolFavoriteButton: React.FC = ({ toolId: propToolId, urlPath, - size = "default", + size: _size = "default", className = "", variant = "default", }) => { @@ -134,6 +134,7 @@ const ToolFavoriteButton: React.FC = ({ return; } if (loading) return; + if (!toolId) return; setLoading(true); try { const res: any = await api_request.tool.toggleFavorite(toolId); diff --git a/src/contexts/LoginModalContext.tsx b/src/contexts/LoginModalContext.tsx index 3dd4b46..9ad1788 100644 --- a/src/contexts/LoginModalContext.tsx +++ b/src/contexts/LoginModalContext.tsx @@ -12,7 +12,7 @@ import { } from "@/features/login"; import { useAppDispatch } from "@/app/store"; import { useSelector } from "react-redux"; -import { useNavigate, useSearchParams } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { api_request } from "@/utils/request"; const getInviteCodeFromUrl = (): string | null => { diff --git a/src/global.css b/src/global.css index 25e3188..7795025 100644 --- a/src/global.css +++ b/src/global.css @@ -120,6 +120,7 @@ body { /* Modal and dialog utilities */ .modal-width { width: 852px; + max-width: 100%; } /* Text and color utilities */ @@ -788,3 +789,252 @@ html { [data-theme="dark"] .ant-dropdown-menu-item-divider { background-color: var(--border-color) !important; } + +/* ============ 暗色模式 - Ant Design 全局组件覆盖 ============ */ + +/* Modal 弹窗 */ +[data-theme="dark"] .ant-modal-content, +[data-theme="dark"] .ant-modal-header { + background-color: #1e293b !important; + border-color: var(--border-color) !important; +} + +[data-theme="dark"] .ant-modal-title, +[data-theme="dark"] .ant-modal-close-icon { + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-modal-close-icon:hover { + color: #818cf8 !important; +} + +[data-theme="dark"] .ant-modal-footer { + border-top-color: var(--border-color) !important; +} + +[data-theme="dark"] .ant-modal-body { + color: var(--text-primary) !important; +} + +/* Table 表格 */ +[data-theme="dark"] .ant-table, +[data-theme="dark"] .ant-table-container { + background-color: transparent !important; +} + +[data-theme="dark"] .ant-table-thead > tr > th, +[data-theme="dark"] .ant-table-thead > tr > td { + background-color: #1e293b !important; + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-table-tbody > tr > td { + border-color: var(--border-color) !important; + color: var(--text-secondary) !important; +} + +[data-theme="dark"] .ant-table-tbody > tr:hover > td { + background-color: rgba(129, 140, 248, 0.08) !important; +} + +[data-theme="dark"] .ant-table-cell-fix-left, +[data-theme="dark"] .ant-table-cell-fix-right { + background-color: #1e293b !important; +} + +/* Input 输入框 */ +[data-theme="dark"] .ant-input, +[data-theme="dark"] .ant-input-affix-wrapper, +[data-theme="dark"] .ant-input-group-addon { + background-color: #1e293b !important; + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-input:focus, +[data-theme="dark"] .ant-input-affix-wrapper:focus, +[data-theme="dark"] .ant-input-affix-wrapper-focused { + border-color: var(--primary-color) !important; + box-shadow: 0 0 0 2px rgba(129, 140, 248, 0.2) !important; +} + +[data-theme="dark"] .ant-input::placeholder, +[data-theme="dark"] .ant-input-affix-wrapper .ant-input::placeholder { + color: var(--text-muted) !important; +} + +[data-theme="dark"] .ant-input-prefix { + color: var(--text-muted) !important; +} + +/* Select 选择器 */ +[data-theme="dark"] .ant-select-selector { + background-color: #1e293b !important; + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-select-arrow { + color: var(--text-muted) !important; +} + +[data-theme="dark"] .ant-select-selection-placeholder { + color: var(--text-muted) !important; +} + +[data-theme="dark"] .ant-select-dropdown { + background-color: #1e293b !important; + border: 1px solid var(--border-color) !important; + box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.4) !important; +} + +[data-theme="dark"] .ant-select-item { + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-select-item-option-selected { + background-color: rgba(129, 140, 248, 0.2) !important; +} + +[data-theme="dark"] .ant-select-item-option-active { + background-color: rgba(129, 140, 248, 0.1) !important; +} + +/* Card 卡片 */ +[data-theme="dark"] .ant-card { + background-color: #1e293b !important; + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-card-head { + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-card-body { + color: var(--text-primary) !important; +} + +/* Tabs 标签页 */ +[data-theme="dark"] .ant-tabs-tab { + color: var(--text-secondary) !important; +} + +[data-theme="dark"] .ant-tabs-tab-active .ant-tabs-tab-btn { + color: var(--primary-color) !important; +} + +[data-theme="dark"] .ant-tabs-ink-bar { + background: var(--primary-color) !important; +} + +[data-theme="dark"] .ant-tabs-nav::before { + border-color: var(--border-color) !important; +} + +/* Spin 加载 */ +[data-theme="dark"] .ant-spin-text { + color: var(--text-secondary) !important; +} + +/* Empty 空状态 */ +[data-theme="dark"] .ant-empty-description { + color: var(--text-secondary) !important; +} + +/* Pagination 分页 */ +[data-theme="dark"] .ant-pagination-item, +[data-theme="dark"] .ant-pagination-prev .ant-pagination-item-link, +[data-theme="dark"] .ant-pagination-next .ant-pagination-item-link { + background-color: transparent !important; + border-color: var(--border-color) !important; +} + +[data-theme="dark"] .ant-pagination-item a { + color: var(--text-secondary) !important; +} + +[data-theme="dark"] .ant-pagination-item-active { + background-color: rgba(129, 140, 248, 0.15) !important; + border-color: var(--primary-color) !important; +} + +[data-theme="dark"] .ant-pagination-item-active a { + color: var(--primary-color) !important; +} + +/* Tooltip */ +[data-theme="dark"] .ant-tooltip-inner { + background-color: #334155 !important; + color: var(--text-primary) !important; +} + +/* Popover 气泡 */ +[data-theme="dark"] .ant-popover-inner { + background-color: #1e293b !important; + border-color: var(--border-color) !important; +} + +[data-theme="dark"] .ant-popover-title { + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-popover-inner-content { + color: var(--text-secondary) !important; +} + +/* Badge 徽标 */ +[data-theme="dark"] .ant-badge-count { + box-shadow: 0 0 0 2px #1e293b !important; +} + +/* Divider 分割线 */ +[data-theme="dark"] .ant-divider { + border-color: var(--border-color) !important; +} + +[data-theme="dark"] .ant-divider-inner-text { + color: var(--text-secondary) !important; +} + +/* List 列表 */ +[data-theme="dark"] .ant-list-item { + border-color: var(--border-color) !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-list-item-meta-title { + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-list-item-meta-description { + color: var(--text-secondary) !important; +} + +/* Drawer 抽屉 */ +[data-theme="dark"] .ant-drawer-content { + background-color: #1e293b !important; +} + +[data-theme="dark"] .ant-drawer-header { + background-color: #1e293b !important; + border-color: var(--border-color) !important; +} + +[data-theme="dark"] .ant-drawer-title { + color: var(--text-primary) !important; +} + +[data-theme="dark"] .ant-drawer-body { + color: var(--text-primary) !important; +} + +/* Tag 标签 */ +[data-theme="dark"] .ant-tag { + background-color: transparent !important; + border-color: var(--border-color) !important; +} + diff --git a/src/pages/AQIDetails/AQIDetails.tsx b/src/pages/AQIDetails/AQIDetails.tsx index 11e8498..be5342e 100644 --- a/src/pages/AQIDetails/AQIDetails.tsx +++ b/src/pages/AQIDetails/AQIDetails.tsx @@ -101,7 +101,7 @@ const AQIDetails: React.FC = () => { urlObj.searchParams.forEach((value, key) => { params[key] = value; }); - res = await request.public.get(url, params); + res = await api_request.public.get(url, params); } else { let data: Record = {}; try { @@ -112,7 +112,7 @@ const AQIDetails: React.FC = () => { data[key] = value; }); } - res = await request.public.post(url, data); + res = await api_request.public.post(url, data); } const elapsed = Date.now() - start; setResponse({ diff --git a/src/pages/AQIDetails/AQIDetailsMobile.tsx b/src/pages/AQIDetails/AQIDetailsMobile.tsx index e791c9b..c5f5977 100644 --- a/src/pages/AQIDetails/AQIDetailsMobile.tsx +++ b/src/pages/AQIDetails/AQIDetailsMobile.tsx @@ -111,7 +111,7 @@ const AQIDetailsMobile: React.FC = () => { } } - const res: any = await request.public.get(url, params); + const res: any = await api_request.public.get(url, params); const elapsed = Date.now() - start; setResponse({ status: res?.code === 200 ? 200 : (res?.code || 500), diff --git a/src/pages/ApiDirectory/ApiDirectory.tsx b/src/pages/ApiDirectory/ApiDirectory.tsx index a3ede3d..8c19e01 100644 --- a/src/pages/ApiDirectory/ApiDirectory.tsx +++ b/src/pages/ApiDirectory/ApiDirectory.tsx @@ -23,7 +23,6 @@ import { LoadingOutlined, ArrowRightOutlined, AppstoreOutlined, - ThunderboltOutlined, ApiOutlined, } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; @@ -60,13 +59,6 @@ interface ApiCategory { item_count: number; } -const methodColors: Record = { - GET: "success", - POST: "processing", - PUT: "warning", - DELETE: "error", -}; - const methodTagColors: Record = { GET: "#10b981", POST: "#f59e0b", @@ -185,10 +177,6 @@ const ApiDirectory: React.FC = () => { })) .filter((cat) => cat.items.length > 0); - const totalApis = items.length; - const enabledApis = items.filter((item) => item.is_enabled).length; - const newApis = items.filter((item) => item.is_new).length; - const handleApiClick = (item: ApiItem) => { if (item.is_coming_soon) { message.info(t("apiDirectory.comingSoon")); diff --git a/src/pages/ApiDirectory/ApiDirectoryDesktop.tsx b/src/pages/ApiDirectory/ApiDirectoryDesktop.tsx index 4053b27..4e3c894 100644 --- a/src/pages/ApiDirectory/ApiDirectoryDesktop.tsx +++ b/src/pages/ApiDirectory/ApiDirectoryDesktop.tsx @@ -42,6 +42,7 @@ interface ApiItem { sort_order: number; favorites_count: number; is_favorited: boolean; + [key: string]: any; } const methodColors: Record = { diff --git a/src/pages/ApiDirectory/ApiDirectoryMobile.tsx b/src/pages/ApiDirectory/ApiDirectoryMobile.tsx index 435e726..a903a77 100644 --- a/src/pages/ApiDirectory/ApiDirectoryMobile.tsx +++ b/src/pages/ApiDirectory/ApiDirectoryMobile.tsx @@ -38,6 +38,7 @@ interface ApiItem { sort_order: number; favorites_count: number; is_favorited: boolean; + [key: string]: any; } const ApiDirectoryMobile: React.FC = () => { diff --git a/src/pages/ArticleDetail/ArticleDetail.tsx b/src/pages/ArticleDetail/ArticleDetail.tsx index 25a8322..e02e39f 100644 --- a/src/pages/ArticleDetail/ArticleDetail.tsx +++ b/src/pages/ArticleDetail/ArticleDetail.tsx @@ -21,10 +21,12 @@ import { useNavigate, useParams } from "react-router-dom"; import { api_request } from "@/utils/request"; import { useRecordHistory } from "@/hooks/useRecordHistory"; import { formatDateTime } from "@/utils/time"; +import { sanitizeHtml } from "@/utils/htmlSanitizer"; const ArticleDetail: React.FC = () => { const navigate = useNavigate(); const { id } = useParams(); + const { t, i18n } = useTranslation(); const [article, setArticle] = useState(null); const [comments, setComments] = useState([]); const [liked, setLiked] = useState(false); @@ -407,7 +409,7 @@ const ArticleDetail: React.FC = () => {
-
+
{article.related_articles && article.related_articles.length > 0 && (
diff --git a/src/pages/ArticleEditor/ArticleEditor.tsx b/src/pages/ArticleEditor/ArticleEditor.tsx index 7af8c37..27a3c8b 100644 --- a/src/pages/ArticleEditor/ArticleEditor.tsx +++ b/src/pages/ArticleEditor/ArticleEditor.tsx @@ -63,6 +63,7 @@ import rehypeKatex from "rehype-katex"; import "katex/dist/katex.min.css"; import { useNavigate, useSearchParams } from "react-router-dom"; import { api_request } from "@/utils/request"; +import { sanitizeHtml } from "@/utils/htmlSanitizer"; import { useTheme } from "@/contexts/ThemeContext"; import "./ArticleEditor.css"; @@ -1118,7 +1119,7 @@ const ArticleEditor: React.FC = () => { {editorMode === "rich" ? (
) : (
diff --git a/src/pages/Articles/Articles.css b/src/pages/Articles/Articles.css index b913d71..a64861d 100644 --- a/src/pages/Articles/Articles.css +++ b/src/pages/Articles/Articles.css @@ -644,6 +644,8 @@ .articles-filter-clear:hover { background: rgba(102, 126, 234, 0.2); } + +.articles-list-title { font-size: 16px; font-weight: 700; color: #1e293b; diff --git a/src/pages/Articles/Articles.tsx b/src/pages/Articles/Articles.tsx index 2cd9023..0601f2f 100644 --- a/src/pages/Articles/Articles.tsx +++ b/src/pages/Articles/Articles.tsx @@ -84,13 +84,6 @@ const categoryColors: Record = { other: "#94a3b8", }; -const categoryLabels: Record = { - tech: "技术教程", - design: "设计前沿", - productivity: "效率方法", - industry: "行业洞察", -}; - const Articles: React.FC = () => { const { t } = useTranslation(); const [sortBy, setSortBy] = useState(1); diff --git a/src/pages/BaiduTranslate/BaiduTranslate.tsx b/src/pages/BaiduTranslate/BaiduTranslate.tsx index 2439042..22ba125 100644 --- a/src/pages/BaiduTranslate/BaiduTranslate.tsx +++ b/src/pages/BaiduTranslate/BaiduTranslate.tsx @@ -274,7 +274,7 @@ const BaiduTranslate: React.FC = () => { api_request.baidu.translate.getAllLangType(), api_request.baidu.translate.getPictureLangType(), api_request.baidu.translate.getSpeechLangType("mp3"), - ]); + ]) as [any, any, any]; if (textLangRes) { setLanguages(textLangRes); diff --git a/src/pages/BaiduTranslate/BaiduTranslateMobile.tsx b/src/pages/BaiduTranslate/BaiduTranslateMobile.tsx index 4550066..824ff8a 100644 --- a/src/pages/BaiduTranslate/BaiduTranslateMobile.tsx +++ b/src/pages/BaiduTranslate/BaiduTranslateMobile.tsx @@ -232,7 +232,7 @@ const BaiduTranslateMobile: React.FC = () => { api_request.baidu.translate.getAllLangType(), api_request.baidu.translate.getPictureLangType(), api_request.baidu.translate.getSpeechLangType("mp3"), - ]); + ]) as [any, any, any]; if (textLangRes) { setLanguages(textLangRes); const textOptions = sortLangOptions(buildLangOptions(textLangRes).filter(opt => opt.value !== "auto")); @@ -601,8 +601,8 @@ const BaiduTranslateMobile: React.FC = () => { {textLoading ? t("baiduTranslate.common.translating") : t("baiduTranslate.common.translate")}
- - + +
diff --git a/src/pages/BaiduTranslateDetail/BaiduTranslateDetail.tsx b/src/pages/BaiduTranslateDetail/BaiduTranslateDetail.tsx index 0d45cba..efc16a9 100644 --- a/src/pages/BaiduTranslateDetail/BaiduTranslateDetail.tsx +++ b/src/pages/BaiduTranslateDetail/BaiduTranslateDetail.tsx @@ -221,7 +221,7 @@ const BaiduTranslateDetail: React.FC = () => { } catch { // ignore parse error } - res = await api_request.baidu.translate.recognizeLangType(params); + res = await api_request.baidu.translate.recognizeLangType(params as any); } else { message.warning(t('baiduTranslateDetail.test.fileUploadWarning')); setLoading(false); diff --git a/src/pages/Changelog/ChangelogMobile.css b/src/pages/Changelog/ChangelogMobile.css index f2d701e..97bc71e 100644 --- a/src/pages/Changelog/ChangelogMobile.css +++ b/src/pages/Changelog/ChangelogMobile.css @@ -79,4 +79,29 @@ .changelog-mobile-pagination { margin-top: 16px; text-align: center; -} \ No newline at end of file +} + +[data-theme="dark"] .changelog-mobile { + background: #0f172a; +} + +[data-theme="dark"] .changelog-mobile-item { + background: #1e293b; + box-shadow: 0 1px 3px rgba(0,0,0,0.3); +} + +[data-theme="dark"] .changelog-mobile-item-version { + color: #f1f5f9; +} + +[data-theme="dark"] .changelog-mobile-item-time { + color: #64748b; +} + +[data-theme="dark"] .changelog-mobile-item-title { + color: #f1f5f9; +} + +[data-theme="dark"] .changelog-mobile-item-desc { + color: #94a3b8; +} diff --git a/src/pages/Chat/Chat.tsx b/src/pages/Chat/Chat.tsx index f3dd4b2..56f2130 100644 --- a/src/pages/Chat/Chat.tsx +++ b/src/pages/Chat/Chat.tsx @@ -192,7 +192,7 @@ const Chat: React.FC = () => { } } catch (error) { console.error("加载会话列表失败:", error); - antMessage.error(t('chat.loadConversationsError')); + message.error(t('chat.loadConversationsError')); } finally { setLoading(false); } @@ -221,7 +221,7 @@ const Chat: React.FC = () => { })); setFriends(mapped); } catch { - antMessage.error(t('chat.loadFriendsError')); + message.error(t('chat.loadFriendsError')); } finally { setFriendsLoading(false); } @@ -237,17 +237,17 @@ const Chat: React.FC = () => { loadConversations(); } } catch { - antMessage.error(t('chat.createConversationError')); + message.error(t('chat.createConversationError')); } }; const handleDeleteFriend = async (userId: number) => { try { await api_request.chat.friends.remove(userId); - antMessage.success(t('chat.friendDeleted')); + message.success(t('chat.friendDeleted')); loadFriends(); } catch { - antMessage.error(t('chat.deleteFriendError')); + message.error(t('chat.deleteFriendError')); } }; @@ -280,7 +280,7 @@ const Chat: React.FC = () => { const data = res.data?.data || res.data || []; setSearchResults(Array.isArray(data) ? data : []); } catch { - antMessage.error(t('chat.searchError')); + message.error(t('chat.searchError')); } finally { setSearchLoading(false); } @@ -289,10 +289,10 @@ const Chat: React.FC = () => { const handleAddFriend = async (userId: number) => { try { await api_request.chat.friendRequests.send({ to_user_id: userId }); - antMessage.success(t('chat.friendRequestSent')); + message.success(t('chat.friendRequestSent')); setSearchResults(prev => prev.map(r => r.id === userId ? { ...r, is_friend: false } : r)); } catch (err: any) { - antMessage.error(err?.response?.data?.message || t('chat.sendFriendRequestError')); + message.error(err?.response?.data?.message || t('chat.sendFriendRequestError')); } }; @@ -309,7 +309,7 @@ const Chat: React.FC = () => { } } catch (error) { console.error("加载消息失败:", error); - antMessage.error(t('chat.loadMessagesError')); + message.error(t('chat.loadMessagesError')); } finally { setLoading(false); } @@ -336,10 +336,10 @@ const Chat: React.FC = () => { ...prev, [selectedConversation]: [], })); - antMessage.success(t('chat.clearSuccess')); + message.success(t('chat.clearSuccess')); setClearModalOpen(false); } catch { - antMessage.error(t('chat.clearFailed')); + message.error(t('chat.clearFailed')); } }, [selectedConversation, t]); @@ -393,7 +393,7 @@ const Chat: React.FC = () => { case "typing": if (data.conversation_id) { - antMessage.info(t('chat.typing', { name: data.user?.nickname || t('chat.other') }), 1); + message.info(t('chat.typing', { name: data.user?.nickname || t('chat.other') }), 1); } break; @@ -472,7 +472,7 @@ const Chat: React.FC = () => { if (!inputValue.trim() || !selectedConversation) return; if (isBlocked) { - antMessage.error(t('chat.blockedWarning')); + message.error(t('chat.blockedWarning')); return; } @@ -502,7 +502,7 @@ const Chat: React.FC = () => { } } catch (error) { console.error("发送消息失败:", error); - antMessage.error(t('chat.sendMessageError')); + message.error(t('chat.sendMessageError')); setInputValue(content); } } @@ -643,10 +643,10 @@ const Chat: React.FC = () => { const handleRecallMessage = async () => { if (!contextMenu.messageId || !selectedConversation) return; const messages = chatMessages[selectedConversation] || []; - const message = messages.find(m => m.id === contextMenu.messageId); - - if (!message || !message.isOwn) { - antMessage.error(t('chat.cannotRecallOthersMessage')); + const targetMsg = messages.find(m => m.id === contextMenu.messageId); + + if (!targetMsg || !targetMsg.isOwn) { + message.error(t('chat.cannotRecallOthersMessage')); setContextMenu(prev => ({ ...prev, visible: false })); return; } @@ -662,11 +662,11 @@ const Chat: React.FC = () => { : m ), })); - antMessage.success(t('chat.messageRecalled')); + message.success(t('chat.messageRecalled')); } } catch (error) { console.error("撤回消息失败:", error); - antMessage.error(t('chat.recallMessageError')); + message.error(t('chat.recallMessageError')); } setContextMenu(prev => ({ ...prev, visible: false })); @@ -682,16 +682,16 @@ const Chat: React.FC = () => { })); setContextMenu(prev => ({ ...prev, visible: false })); - antMessage.success(t('chat.messageDeleted')); + message.success(t('chat.messageDeleted')); }; const handleSaveSticker = async (msgId: string, _content: string) => { try { await (api_request as any).chat.favoriteStickers.fromMessage(msgId); - antMessage.success(t('chat.stickerSaved')); + message.success(t('chat.stickerSaved')); loadFavoriteStickers(); } catch { - antMessage.error(t('chat.saveStickerError')); + message.error(t('chat.saveStickerError')); } setContextMenu({ visible: false, x: 0, y: 0, messageId: null }); }; @@ -699,23 +699,23 @@ const Chat: React.FC = () => { const handleDownload = () => { if (!contextMenu.messageId || !selectedConversation) return; const messages = chatMessages[selectedConversation] || []; - const message = messages.find(m => m.id === contextMenu.messageId); - - if (!message) { + const targetMsg = messages.find(m => m.id === contextMenu.messageId); + + if (!targetMsg) { setContextMenu(prev => ({ ...prev, visible: false })); return; } - if (message.type === "image" || message.type === "sticker") { + if (targetMsg.type === "image" || targetMsg.type === "sticker") { const link = document.createElement("a"); - link.href = message.content; + link.href = targetMsg.content; link.download = `image_${Date.now()}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); - antMessage.success(t('chat.imageDownloading')); - } else if (message.type === "file") { - antMessage.info(t('chat.fileDownloadNotSupported')); + message.success(t('chat.imageDownloading')); + } else if (targetMsg.type === "file") { + message.info(t('chat.fileDownloadNotSupported')); } setContextMenu(prev => ({ ...prev, visible: false })); @@ -724,13 +724,13 @@ const Chat: React.FC = () => { const handleCopyMessage = () => { if (!contextMenu.messageId || !selectedConversation) return; const messages = chatMessages[selectedConversation] || []; - const message = messages.find(m => m.id === contextMenu.messageId); - - if (message && message.type === "text") { - navigator.clipboard.writeText(message.content).then(() => { - antMessage.success(t('chat.copiedToClipboard')); + const targetMsg = messages.find(m => m.id === contextMenu.messageId); + + if (targetMsg && targetMsg.type === "text") { + navigator.clipboard.writeText(targetMsg.content).then(() => { + message.success(t('chat.copiedToClipboard')); }).catch(() => { - antMessage.error(t('chat.copyError')); + message.error(t('chat.copyError')); }); } @@ -1030,10 +1030,10 @@ const Chat: React.FC = () => { blocked_user_id: otherUserId, reason: '', }); - antMessage.success(t('chat.userBlocked')); + message.success(t('chat.userBlocked')); setIsBlocked(true); } catch { - antMessage.error(t('chat.blockUserFailed')); + message.error(t('chat.blockUserFailed')); } }, }); @@ -1197,7 +1197,7 @@ const Chat: React.FC = () => { setInputValue(e.target.value)} - onKeyPress={handleKeyPress} + onKeyDown={handleKeyPress} placeholder={quotedMessage ? t('chat.replyPlaceholder', {name: quotedMessage.senderName}) : t('chat.inputPlaceholder')} className="chat-input" /> @@ -1330,3 +1330,4 @@ const Chat: React.FC = () => { }; export default Chat; + diff --git a/src/pages/Chat/ChatMobile.tsx b/src/pages/Chat/ChatMobile.tsx index 7e6452b..f600e4b 100644 --- a/src/pages/Chat/ChatMobile.tsx +++ b/src/pages/Chat/ChatMobile.tsx @@ -206,7 +206,7 @@ const ChatMobile: React.FC = () => { const handleRecall = async () => { if (!contextMenu?.msg.id || !conversationId) return; try { - await api_request.chat.messages.recall(contextMenu.msg.id); + await api_request.chat.messages.recall(Number(contextMenu.msg.id)); setMessages(prev => prev.map(m => m.id === contextMenu!.msg.id ? { ...m, isRecalled: true, content: t('chat.messageRecalled') } : m)); message.success(t('chat.recallSuccess')); } catch { @@ -218,7 +218,7 @@ const ChatMobile: React.FC = () => { const handleDelete = async () => { if (!contextMenu?.msg.id) return; try { - await api_request.chat.messages.delete(contextMenu.msg.id); + await api_request.chat.messages.recall(Number(contextMenu.msg.id)); setMessages(prev => prev.filter(m => m.id !== contextMenu!.msg.id)); message.success(t('chat.deleteSuccess')); } catch { @@ -289,7 +289,7 @@ const ChatMobile: React.FC = () => { {contextMenu && (
e.stopPropagation()} >
@@ -337,7 +337,7 @@ const ChatMobile: React.FC = () => { className="chat-mobile__input" value={inputValue} onChange={(e) => setInputValue(e.target.value)} - onKeyPress={handleKeyPress} + onKeyDown={handleKeyPress} placeholder={t('chat.inputPlaceholder')} />
@@ -232,4 +232,4 @@ const HomeMobile: React.FC = () => { ); }; -export default HomeMobile; \ No newline at end of file +export default HomeMobile; diff --git a/src/pages/IPLocationApiDetail/IPLocationApiDetail.tsx b/src/pages/IPLocationApiDetail/IPLocationApiDetail.tsx index 25a812c..27708b2 100644 --- a/src/pages/IPLocationApiDetail/IPLocationApiDetail.tsx +++ b/src/pages/IPLocationApiDetail/IPLocationApiDetail.tsx @@ -18,7 +18,6 @@ import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { api_request } from "@/utils/request"; -const { TextArea } = Input; const { Text } = Typography; const methodColors: Record = { diff --git a/src/pages/ImageEditor/ImageEditor.tsx b/src/pages/ImageEditor/ImageEditor.tsx index 2bb8c1f..09d7891 100644 --- a/src/pages/ImageEditor/ImageEditor.tsx +++ b/src/pages/ImageEditor/ImageEditor.tsx @@ -190,7 +190,9 @@ const ImageEditor: React.FC = () => { return; } const img = new Image(); + const objectUrl = URL.createObjectURL(file); img.onload = () => { + URL.revokeObjectURL(objectUrl); setImage(img); setRotation(0); setFlipH(false); @@ -218,7 +220,7 @@ const ImageEditor: React.FC = () => { } } }; - img.src = URL.createObjectURL(file); + img.src = objectUrl; }; const handleFileInput = (e: React.ChangeEvent) => { diff --git a/src/pages/Messages/MessagesMobile.css b/src/pages/Messages/MessagesMobile.css index 8ea2358..fa94766 100644 --- a/src/pages/Messages/MessagesMobile.css +++ b/src/pages/Messages/MessagesMobile.css @@ -19,9 +19,13 @@ font-size: 18px; font-weight: 600; color: #333333; +} + .messages-mobile__badge { position: absolute; right: 16px; + top: 50%; + transform: translateY(-50%); } .messages-mobile__toolbar { @@ -54,8 +58,6 @@ .messages-mobile__clear-btn:disabled { opacity: 0.5; cursor: not-allowed; -} top: 50%; - transform: translateY(-50%); } .messages-mobile__tabs { @@ -241,4 +243,4 @@ [data-theme="dark"] .messages-mobile__item-preview { color: #94a3b8; -} \ No newline at end of file +} diff --git a/src/pages/NotFound/NotFound.css b/src/pages/NotFound/NotFound.css new file mode 100644 index 0000000..bfa9399 --- /dev/null +++ b/src/pages/NotFound/NotFound.css @@ -0,0 +1,155 @@ +.not-found-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: var(--background-color); + position: relative; + overflow: hidden; +} + +.not-found-bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); + opacity: 0.92; +} + +.not-found-bg::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px); + background-size: 50px 50px; +} + +.not-found-bg::after { + content: ''; + position: absolute; + top: -30%; + left: -30%; + width: 160%; + height: 160%; + background: + radial-gradient(circle at 30% 40%, rgba(255, 255, 255, 0.15) 0%, transparent 40%), + radial-gradient(circle at 70% 60%, rgba(255, 255, 255, 0.1) 0%, transparent 35%); + animation: notFoundGlow 12s ease-in-out infinite alternate; +} + +@keyframes notFoundGlow { + 0% { transform: translate(0, 0) rotate(0deg); } + 100% { transform: translate(-20px, -10px) rotate(5deg); } +} + +.not-found-content { + position: relative; + z-index: 1; + text-align: center; + color: white; + padding: 40px 20px; + max-width: 600px; +} + +.not-found-code { + font-size: 120px; + font-weight: 900; + line-height: 1; + letter-spacing: -4px; + margin-bottom: 16px; + text-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); + animation: notFoundFloat 4s ease-in-out infinite; +} + +@keyframes notFoundFloat { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.not-found-title { + font-size: 28px; + font-weight: 700; + margin-bottom: 12px; + opacity: 0.95; +} + +.not-found-desc { + font-size: 16px; + opacity: 0.85; + margin-bottom: 40px; + line-height: 1.8; +} + +.not-found-actions { + display: flex; + gap: 16px; + justify-content: center; + flex-wrap: wrap; +} + +.not-found-btn-home { + background: white; + color: #667eea; + border: none; + padding: 14px 36px; + border-radius: 12px; + font-weight: 600; + font-size: 16px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); +} + +.not-found-btn-home:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); +} + +.not-found-btn-back { + background: transparent; + color: white; + border: 2px solid rgba(255, 255, 255, 0.6); + padding: 14px 36px; + border-radius: 12px; + font-weight: 600; + font-size: 16px; + cursor: pointer; + transition: all 0.3s ease; +} + +.not-found-btn-back:hover { + background: rgba(255, 255, 255, 0.15); + border-color: white; +} + +@media (max-width: 768px) { + .not-found-code { + font-size: 80px; + } + + .not-found-title { + font-size: 22px; + } + + .not-found-desc { + font-size: 14px; + } + + .not-found-actions { + flex-direction: column; + align-items: center; + } + + .not-found-btn-home, + .not-found-btn-back { + width: 100%; + max-width: 280px; + } +} diff --git a/src/pages/NotFound/NotFound.tsx b/src/pages/NotFound/NotFound.tsx index 8c84bc8..b6465be 100644 --- a/src/pages/NotFound/NotFound.tsx +++ b/src/pages/NotFound/NotFound.tsx @@ -1,22 +1,25 @@ -import React from "react"; -import { Button, Result } from "antd"; +import "./NotFound.css"; import { useNavigate } from "react-router-dom"; -const NotFound: React.FC = () => { +const NotFound = () => { const navigate = useNavigate(); return ( -
- navigate("/")}> +
+
+
+
404
+

页面走丢了

+

您访问的页面可能已被移除、重命名或暂时不可用

+
+ - } - /> + + +
+
); }; diff --git a/src/pages/PasswordGenerator/PasswordGeneratorApiDetail.tsx b/src/pages/PasswordGenerator/PasswordGeneratorApiDetail.tsx index 0e97bd4..db716de 100644 --- a/src/pages/PasswordGenerator/PasswordGeneratorApiDetail.tsx +++ b/src/pages/PasswordGenerator/PasswordGeneratorApiDetail.tsx @@ -1,12 +1,12 @@ import "./PasswordGeneratorApiDetail.css"; import React, { useState } from "react"; -import { Tabs, Tag, Button, Input, Select, InputNumber, Spin, Typography, Switch, Slider } from "antd"; +import { Tabs, Tag, Button, Input, Select, Spin, Typography } from "antd"; import { message } from "@/utils/message"; import type { TabsProps } from "antd"; import { LockOutlined, SendOutlined, CopyOutlined, BookOutlined, ThunderboltOutlined, CodeOutlined, CheckCircleOutlined, SettingOutlined, - ReloadOutlined, SafetyOutlined, + SafetyOutlined, } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/src/pages/Profile/components/UserInfoCard.tsx b/src/pages/Profile/components/UserInfoCard.tsx index 525b28d..58b2d8d 100644 --- a/src/pages/Profile/components/UserInfoCard.tsx +++ b/src/pages/Profile/components/UserInfoCard.tsx @@ -7,8 +7,6 @@ import { Avatar, message, Button, - Tag, - Tooltip, } from "antd"; import { UserOutlined, @@ -261,7 +259,6 @@ const UserInfoCard: React.FC = ({ onEditField }) => { style={{ width: "100%" }} changeOnSelect dropdownClassName="region-cascader-dropdown" - popupProps={{ closeOnScroll: false }} /> {detectedLocation && (