From 03ac351930a06175ff31712a65a54055292c69b5 Mon Sep 17 00:00:00 2001 From: hex2077 Date: Tue, 26 Aug 2025 23:01:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=A8=AA=E5=B9=85=E7=BB=84=E4=BB=B6=E5=B9=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4UI=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加全局通知横幅组件,支持多语言和多种状态类型。同时调整Toast组件和开发指示器的位置。 --- web/next.config.ts | 2 +- web/public/locales/en/components.json | 4 + web/public/locales/ja/components.json | 4 + web/public/locales/zh-CN/components.json | 4 + web/src/app/[lang]/page.tsx | 6 ++ web/src/components/NotificationBanner.tsx | 113 ++++++++++++++++++++++ web/src/components/Toast.tsx | 2 +- 7 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 web/src/components/NotificationBanner.tsx diff --git a/web/next.config.ts b/web/next.config.ts index 919e3c8..d8f4629 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -10,7 +10,7 @@ const nextConfig = { }, output: 'standalone', devIndicators: { - position: 'top-right', // 将挂件移动到右下角 + position: 'top-left', // 将挂件移动到右下角 }, }; diff --git a/web/public/locales/en/components.json b/web/public/locales/en/components.json index 571111b..734de58 100644 --- a/web/public/locales/en/components.json +++ b/web/public/locales/en/components.json @@ -266,5 +266,9 @@ "initialBonusDescription": "New user registration, initial points bonus", "initError": "Failed to initialize user {{userId}} points account or record transaction: {{error}}", "pointsAccountExists": "User {{userId}} already has a points account, no initialization required." + }, + "notificationBanner": { + "notificationBannerMessage": "Limited time free, come and experience it now.", + "close": "Close" } } \ No newline at end of file diff --git a/web/public/locales/ja/components.json b/web/public/locales/ja/components.json index c9fbff3..433190f 100644 --- a/web/public/locales/ja/components.json +++ b/web/public/locales/ja/components.json @@ -266,5 +266,9 @@ "initialBonusDescription": "新規ユーザー登録、初回ポイントボーナス", "initError": "ユーザー {{userId}} のポイントアカウントの初期化またはトランザクションの記録に失敗しました: {{error}}", "pointsAccountExists": "ユーザー {{userId}} はすでにポイントアカウントを持っています。初期化は不要です。" + }, + "notificationBanner": { + "notificationBannerMessage": "期間限定無料です、今すぐ体験してください。", + "close": "閉じる" } } \ No newline at end of file diff --git a/web/public/locales/zh-CN/components.json b/web/public/locales/zh-CN/components.json index 0c7fdaa..e938a74 100644 --- a/web/public/locales/zh-CN/components.json +++ b/web/public/locales/zh-CN/components.json @@ -266,5 +266,9 @@ "initialBonusDescription": "新用户注册,初始积分奖励", "initError": "初始化用户 {{userId}} 积分账户或记录流水失败: {{error}}", "pointsAccountExists": "用户 {{userId}} 已存在积分账户,无需初始化。" + }, + "notificationBanner": { + "notificationBannerMessage": "限时免费中,赶快来体验吧。", + "close": "关闭" } } \ No newline at end of file diff --git a/web/src/app/[lang]/page.tsx b/web/src/app/[lang]/page.tsx index 1d01500..7314d12 100644 --- a/web/src/app/[lang]/page.tsx +++ b/web/src/app/[lang]/page.tsx @@ -9,6 +9,7 @@ import AudioPlayer from '@/components/AudioPlayer'; import SettingsForm from '@/components/SettingsForm'; import PointsOverview from '@/components/PointsOverview'; // 导入 PointsOverview import LoginModal from '@/components/LoginModal'; // 导入 LoginModal +import NotificationBanner from '@/components/NotificationBanner'; // 导入 NotificationBanner import { ToastContainer, useToast } from '@/components/Toast'; import { usePreventDuplicateCall } from '@/hooks/useApiCall'; import { trackedFetch } from '@/utils/apiCallTracker'; @@ -418,6 +419,11 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> return (
+ {/* 侧边栏 */} void; + lang: string; +} + +const NotificationBanner: React.FC = ({ + messageKey, + type = 'info', + onClose, + lang +}) => { + const { t } = useTranslation(lang, 'components'); + const [isVisible, setIsVisible] = useState(true); + const [isClosing, setIsClosing] = useState(false); + + // 从本地存储获取通知状态,避免重复显示 + useEffect(() => { + const hasClosed = localStorage.getItem('notificationBannerClosed'); + if (hasClosed) { + setIsVisible(false); + } + }, []); + + const handleClose = () => { + setIsClosing(true); + // 添加关闭动画 + setTimeout(() => { + setIsVisible(false); + localStorage.setItem('notificationBannerClosed', 'true'); + if (onClose) onClose(); + }, 300); + }; + + if (!isVisible) return null; + + // 根据类型设置样式 + const getTypeStyles = () => { + switch (type) { + case 'success': + return 'bg-green-50 text-green-800 border-green-200'; + case 'warning': + return 'bg-yellow-50 text-yellow-800 border-yellow-200'; + case 'error': + return 'bg-red-50 text-red-800 border-red-200'; + default: + return 'bg-blue-50 text-blue-800 border-blue-200'; + } + }; + + return ( +
+
+
+
+
+
+
+ {type === 'success' && ( + + + + )} + {type === 'warning' && ( + + + + )} + {type === 'error' && ( + + + + )} + {type === 'info' && ( + + + + )} +
+
+

{t(messageKey)}

+
+
+
+
+ +
+
+
+
+
+ ); +}; + +export default NotificationBanner; \ No newline at end of file diff --git a/web/src/components/Toast.tsx b/web/src/components/Toast.tsx index 22dac83..b20f2c4 100644 --- a/web/src/components/Toast.tsx +++ b/web/src/components/Toast.tsx @@ -121,7 +121,7 @@ const Toast: React.FC = ({ onRemove, }) => { return ( -
{/* 定位到顶部水平居中,并限制宽度,使用flex布局垂直居中,增加间距 */} +
{/* 定位到顶部水平居中,并限制宽度,使用flex布局垂直居中,增加间距 */} {toasts.map((toast) => (