Procházet zdrojové kódy

fix(mobile): 修复 MCP 管理页移动端布局和 Hydration 错误

1. Header 移动端两行布局 - 标题和导航在第一行,MCP 状态在第二行
2. 聊天页输入框固定底部 - 使用 100dvh 动态视口高度解决浏览器地址栏遮挡
3. MCP 管理页移动端优化:
   - 统计文字响应式 (text-xs md:text-sm)
   - 按钮增大点击区域 (min-h-[44px])
   - URL 截断显示 (truncate max-w-full)
   - 禁用状态灰色圆点
4. Hydration 错误修复 - 添加 mounted state 和 suppressHydrationWarning
5. MCP 计数同步修复 - Header 从 MCP_SERVERS 获取总数 (4 个)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude AI před 2 dny
rodič
revize
d62ffe5e37

+ 68 - 32
frontend-v2/app/mcp/page.tsx

@@ -30,11 +30,17 @@ interface ServerEnabledStatus {
 }
 
 export default function McpPage() {
+  const [mounted, setMounted] = useState(false);
   const [connectionStatuses, setConnectionStatuses] = useState<Record<string, ServerConnectionStatus>>({});
   const [enabledStatuses, setEnabledStatuses] = useState<Record<string, boolean>>({});
 
   const totalCount = Object.keys(MCP_SERVERS).length;
 
+  // 客户端挂载后设置 mounted 标志
+  useEffect(() => {
+    setMounted(true);
+  }, []);
+
   // 计算已启用数量
   const enabledCount = Object.values(enabledStatuses).filter(enabled => enabled).length;
 
@@ -85,50 +91,80 @@ export default function McpPage() {
 
   return (
     <div className="min-h-screen bg-gray-50 dark:bg-gray-900">
-      {/* Header */}
-      <header className="bg-white dark:bg-gray-800 border-b dark:border-gray-700 px-6 py-4">
-        <div className="flex items-center justify-between">
-          <div className="flex items-center space-x-4">
-            <h1 className="text-xl font-bold text-gray-800 dark:text-white">
+      {/* Header - 移动端两行布局 */}
+      <header className="bg-white dark:bg-gray-800 border-b dark:border-gray-700 px-3 py-2 md:px-6 md:py-4 safe-area-inset-top">
+        <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-2 md:gap-0">
+          {/* 第一行:标题和导航 */}
+          <div className="flex items-center justify-between">
+            <h1 className="text-lg md:text-xl font-bold text-gray-800 dark:text-white">
               AI MCP Web UI
             </h1>
-            <nav className="flex space-x-4">
-              <Link href="/" className="text-gray-600 dark:text-gray-300 hover:text-blue-500">
+            <nav className="flex space-x-2 md:space-x-4">
+              <Link href="/" className="text-sm md:text-base text-gray-600 dark:text-gray-300 hover:text-blue-500 px-2 py-1">
                 聊天
               </Link>
-              <Link href="/mcp" className="text-blue-500 dark:text-blue-400 font-medium">
+              <Link href="/mcp" className="text-sm md:text-base text-blue-500 dark:text-blue-400 font-medium px-2 py-1">
                 MCP 管理
               </Link>
             </nav>
           </div>
-          <Link
-            href="/"
-            className="px-4 py-2 text-sm bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
-          >
-            开始聊天
-          </Link>
+
+          {/* 第二行(移动端)/ 右侧(桌面端):状态和按钮 */}
+          <div className="flex items-center justify-between md:justify-end space-x-2 md:space-x-4">
+            {/* MCP 统计 - 使用 suppressHydrationWarning 避免 SSR/CSR 不匹配 */}
+            <div className="flex items-center space-x-1 md:space-x-2 text-xs md:text-sm" suppressHydrationWarning>
+              <span className="font-medium text-purple-600 dark:text-purple-400">
+                启用 {mounted ? enabledCount : 0}/{totalCount}
+              </span>
+              {mounted && loggedInCount > 0 && (
+                <span className="text-green-600 dark:text-green-400">
+                  · 已登录 {loggedInCount}
+                </span>
+              )}
+            </div>
+
+            <Link
+              href="/"
+              className="text-xs md:text-sm px-3 md:px-4 py-1.5 md:py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
+            >
+              开始聊天
+            </Link>
+          </div>
         </div>
       </header>
 
       {/* Main Content */}
-      <main className="max-w-6xl mx-auto px-4 py-8">
-        {/* Page Header */}
-        <div className="mb-8">
-          <h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-2">
+      <main className="max-w-6xl mx-auto px-3 md:px-4 py-4 md:py-8">
+        {/* Page Header - 移动端优化 */}
+        <div className="mb-4 md:mb-8">
+          <h2 className="text-xl md:text-2xl font-bold text-gray-800 dark:text-white mb-2">
             MCP 服务器管理
           </h2>
-          <p className="text-gray-600 dark:text-gray-400">
-            管理和配置您的 MCP 服务器连接。
-            <span className="font-medium text-purple-600 dark:text-purple-400"> 已启用 {enabledCount}/{totalCount}</span>
-            <span className="font-medium text-green-600 dark:text-green-400 ml-2"> | 已连接 {connectedCount}/{totalCount}</span>
-            {loggedInCount > 0 && (
-              <span className="font-medium text-blue-600 dark:text-blue-400 ml-2"> | 已登录 {loggedInCount}/{totalCount}</span>
-            )}
+          <p className="text-sm md:text-base text-gray-600 dark:text-gray-400">
+            管理和配置您的 MCP 服务器连接
           </p>
+          {/* 统计信息 - 移动端分两行,使用 suppressHydrationWarning 避免 SSR/CSR 不匹配 */}
+          <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 mt-2 text-xs md:text-sm" suppressHydrationWarning>
+            <span className="font-medium text-purple-600 dark:text-purple-400">
+              已启用 {mounted ? enabledCount : 0}/{totalCount}
+            </span>
+            <span className="hidden md:inline text-gray-400">|</span>
+            <span className="font-medium text-green-600 dark:text-green-400">
+              已连接 {mounted ? connectedCount : 0}/{totalCount}
+            </span>
+            {mounted && loggedInCount > 0 && (
+              <>
+                <span className="hidden md:inline text-gray-400">|</span>
+                <span className="font-medium text-blue-600 dark:text-blue-400">
+                  已登录 {loggedInCount}/{totalCount}
+                </span>
+              </>
+            )}
+          </div>
         </div>
 
-        {/* MCP Server Cards */}
-        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+        {/* MCP Server Cards - 移动端增加间距 */}
+        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
           {Object.entries(MCP_SERVERS).map(([mcpType, config]) => (
             <McpServerCard
               key={mcpType}
@@ -140,18 +176,18 @@ export default function McpPage() {
           ))}
         </div>
 
-        {/* Info Section */}
-        <div className="mt-8 p-6 bg-blue-50 dark:bg-blue-900/20 rounded-lg border dark:border-blue-800">
-          <h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-3">
+        {/* Info Section - 移动端优化 */}
+        <div className="mt-6 md:mt-8 p-4 md:p-6 bg-blue-50 dark:bg-blue-900/20 rounded-lg border dark:border-blue-800">
+          <h3 className="text-base md:text-lg font-semibold text-blue-900 dark:text-blue-200 mb-3">
             关于 MCP 认证
           </h3>
-          <ul className="space-y-2 text-sm text-blue-800 dark:text-blue-300">
+          <ul className="space-y-2 text-xs md:text-sm text-blue-800 dark:text-blue-300">
             <li>• <strong>Novel Translator MCP</strong>: 无需登录,可直接使用</li>
             <li>• <strong>Platform User MCP</strong>: 需要读者/作者账号登录(部分工具无需登录)</li>
             <li>• <strong>Platform Admin MCP</strong>: 需要管理员账号登录</li>
             <li>• <strong>Template 241 MCP App</strong>: 无需登录,36 个 shadcn/ui 组件架构</li>
           </ul>
-          <p className="mt-3 text-sm text-blue-700 dark:text-blue-400">
+          <p className="mt-3 text-xs md:text-sm text-blue-700 dark:text-blue-400">
             <strong>已连接</strong> 表示服务器在线,<strong>已登录</strong> 表示可以调用需要权限的工具。
             登录后,对应的 MCP Token 会自动在聊天请求中携带,无需额外配置。
           </p>

+ 3 - 4
frontend-v2/components/Header.tsx

@@ -10,20 +10,19 @@
 
 import { useState, useEffect } from 'react';
 import Link from 'next/link';
-import { mcpTokenManager } from '@/lib/mcp-token-manager';
+import { MCP_SERVERS, mcpTokenManager } from '@/lib/mcp-token-manager';
 
 export default function Header() {
   const [loggedInCount, setLoggedInCount] = useState(0);
   const [enabledCount, setEnabledCount] = useState(0);
-  const [total, setTotal] = useState(0);
+  // 从 MCP_SERVERS 配置获取总数
+  const total = Object.keys(MCP_SERVERS).length;
 
   useEffect(() => {
     // 更新 MCP 登录状态
     const updateStatus = () => {
-      const servers = Object.keys(mcpTokenManager as any).filter(k => k.startsWith('novel-'));
       setLoggedInCount(mcpTokenManager.getLoggedInMcpList().length);
       setEnabledCount(mcpTokenManager.getEnabledMcpList().length);
-      setTotal(servers.length || 3);
     };
 
     updateStatus();

+ 82 - 52
frontend-v2/components/McpServerCard.tsx

@@ -43,26 +43,26 @@ function DevTestAccounts({ mcpType }: { mcpType: string }) {
         🛠️ 开发环境测试账号
       </p>
       <div className="space-y-2 text-xs">
-        <div className="flex items-center justify-between gap-2">
+        <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
           <span className="text-gray-700 dark:text-gray-300">
             邮箱: <span className="font-mono font-medium">{account.email}</span>
           </span>
           <button
             type="button"
             onClick={() => copyToClipboard(account.email, 'email')}
-            className="px-2 py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap"
+            className="px-3 py-2 sm:px-2 sm:py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap text-center min-h-[44px] sm:min-h-0"
           >
             {copied === 'email' ? '已复制 ✓' : '复制邮箱'}
           </button>
         </div>
-        <div className="flex items-center justify-between gap-2">
+        <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
           <span className="text-gray-700 dark:text-gray-300">
             密码: <span className="font-mono font-medium">••••••••••••</span>
           </span>
           <button
             type="button"
             onClick={() => copyToClipboard(account.password, 'password')}
-            className="px-2 py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap"
+            className="px-3 py-2 sm:px-2 sm:py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap text-center min-h-[44px] sm:min-h-0"
           >
             {copied === 'password' ? '已复制 ✓' : '复制密码'}
           </button>
@@ -89,15 +89,24 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
   const [isLoading, setIsLoading] = useState(false);
   const [error, setError] = useState('');
   const [updateTrigger, setUpdateTrigger] = useState(0);
+  // 新增:客户端挂载标志
+  const [mounted, setMounted] = useState(false);
   // 新增:健康检查状态
   const [isHealthy, setIsHealthy] = useState<boolean | null>(null); // null=未检查, true=健康, false=不健康
   const [isCheckingHealth, setIsCheckingHealth] = useState(false);
   const [latency, setLatency] = useState<number | null>(null);
-  // 新增:启用/禁用状态
-  const [isEnabled, setIsEnabled] = useState(() => mcpTokenManager.isEnabled(mcpType));
+  // 新增:启用/禁用状态 - 初始值设为 true 避免闪烁,useEffect 中会更新真实值
+  const [isEnabled, setIsEnabled] = useState(true);
 
   // 监听 localStorage 变化和组件挂载
   useEffect(() => {
+    // 设置客户端挂载标志
+    setMounted(true);
+
+    // 初始化启用状态 - 从 localStorage 读取真实值
+    const realEnabledState = mcpTokenManager.isEnabled(mcpType);
+    setIsEnabled(realEnabledState);
+
     // 初始化时检查登录状态
     const checkLoginStatus = () => {
       const token = localStorage.getItem(`mcp_token_${mcpType}`);
@@ -182,10 +191,15 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
   }, [isHealthy, isLoggedIn, mcpType, onConnectionStatusChange]);
 
   // 计算连接状态显示
+  // - 禁用 → 灰色圆点
   // - 健康 + 已登录 → 已连接 ✓ | 已登录 ✓
   // - 健康 + 未登录 → 已连接 ✓ | 未登录
   // - 不健康 → 离线
   const getConnectionStatus = () => {
+    // 禁用状态优先,显示灰色
+    if (!isEnabled) {
+      return { text: '已禁用', className: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400', dotColor: 'bg-gray-400' };
+    }
     if (isHealthy === null) {
       return { text: '检查中...', className: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400', dotColor: 'bg-gray-400' };
     }
@@ -247,48 +261,60 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
   };
 
   return (
-    <div className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border dark:border-gray-700 transition-all ${!isEnabled ? 'opacity-50 grayscale' : ''}`}>
-      <div className="flex items-start justify-between mb-4">
-        <div className="flex-1">
-          <div className="flex items-center gap-3">
-            <h3 className="text-lg font-semibold text-gray-800 dark:text-white">
+    <div
+      className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 md:p-6 border dark:border-gray-700 transition-all ${!isEnabled ? 'opacity-50 grayscale' : ''}`}
+      suppressHydrationWarning
+    >
+      {/* 头部区域 - 移动端优化 */}
+      <div className="flex flex-col gap-3 mb-4">
+        {/* 第一行:名称 + 启用/禁用按钮 */}
+        <div className="flex items-start justify-between gap-2">
+          <div className="flex-1 min-w-0">
+            <h3 className="text-base md:text-lg font-semibold text-gray-800 dark:text-white truncate">
               {config.name}
             </h3>
-            {/* 启用/禁用开关 */}
-            <button
-              type="button"
-              onClick={handleToggleEnabled}
-              className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium transition-all ${
-                isEnabled
-                  ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-800'
-                  : 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600'
-              }`}
-              title={isEnabled ? '点击禁用此 MCP 服务器' : '点击启用此 MCP 服务器'}
-            >
-              <span className={`w-1.5 h-1.5 rounded-full ${isEnabled ? 'bg-green-500' : 'bg-gray-400'}`} />
-              {isEnabled ? '禁用' : '启用'}
-            </button>
-          </div>
-          <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
-            {config.authType === 'none' ? '无需认证' : '需要登录'}
-            {latency !== null && isHealthy && isEnabled && (
-              <span className="ml-2 text-xs text-gray-400">
-                ({latency}ms)
-              </span>
-            )}
-          </p>
-          {config.description && (
-            <p className="text-xs text-gray-400 dark:text-gray-500 mt-1">
-              {config.description}
+            <p className="text-xs md:text-sm text-gray-500 dark:text-gray-400 mt-0.5">
+              {config.authType === 'none' ? '无需认证' : '需要登录'}
+              {latency !== null && isHealthy && isEnabled && (
+                <span className="ml-2 text-xs text-gray-400">
+                  ({latency}ms)
+                </span>
+              )}
             </p>
-          )}
+          </div>
+          {/* 启用/禁用按钮 - 移动端加大点击区域 */}
+          <button
+            type="button"
+            onClick={handleToggleEnabled}
+            className={`flex items-center gap-1.5 px-3 py-2 md:px-2.5 md:py-1 rounded-full text-xs md:text-sm font-medium transition-all min-h-[44px] md:min-h-0 ${
+              isEnabled
+                ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-800'
+                : 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600'
+            }`}
+            title={isEnabled ? '点击禁用此 MCP 服务器' : '点击启用此 MCP 服务器'}
+          >
+            <span className={`w-2 h-2 rounded-full ${isEnabled ? 'bg-green-500' : 'bg-gray-400'}`} />
+            {isEnabled ? '禁用' : '启用'}
+          </button>
         </div>
-        <div className={`flex items-center gap-2 px-3 py-1 rounded-full text-sm ${connectionStatus.className}`}>
-          <span className={`w-2 h-2 rounded-full ${connectionStatus.dotColor} ${isCheckingHealth ? 'animate-pulse' : ''}`} />
-          {connectionStatus.text}
+
+        {/* 第二行:连接状态 */}
+        <div className="flex items-center">
+          <div className={`flex items-center gap-2 px-3 py-1.5 md:py-1 rounded-full text-xs md:text-sm ${connectionStatus.className}`}>
+            <span className={`w-2 h-2 rounded-full ${connectionStatus.dotColor} ${isCheckingHealth ? 'animate-pulse' : ''}`} />
+            {connectionStatus.text}
+          </div>
         </div>
+
+        {/* 描述(如果有) */}
+        {config.description && (
+          <p className="text-xs text-gray-400 dark:text-gray-500">
+            {config.description}
+          </p>
+        )}
       </div>
 
+      {/* 已登录状态显示 - 移动端优化 */}
       {isLoggedIn && (
         <div className="mb-4 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
           <p className="text-sm text-green-800 dark:text-green-200">
@@ -300,9 +326,10 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
         </div>
       )}
 
+      {/* 禁用提示 - 移动端优化 */}
       {!isEnabled && (
         <div className="mb-4 p-3 bg-gray-100 dark:bg-gray-700/50 rounded-lg border border-gray-200 dark:border-gray-600">
-          <p className="text-sm text-gray-600 dark:text-gray-400">
+          <p className="text-xs md:text-sm text-gray-600 dark:text-gray-400">
             此 MCP 服务器已禁用。点击右上角的"启用"按钮来启用它。
           </p>
         </div>
@@ -343,7 +370,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
             <button
               type="submit"
               disabled={isLoading}
-              className="flex-1 py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
+              className="flex-1 py-3 md:py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors text-sm min-h-[44px] md:min-h-0"
             >
               {isLoading ? '登录中...' : '登录'}
             </button>
@@ -355,7 +382,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
                 setEmail('');
                 setPassword('');
               }}
-              className="py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
+              className="py-3 md:py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm min-h-[44px] md:min-h-0"
             >
               取消
             </button>
@@ -400,7 +427,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
           <div className="flex gap-2">
             <button
               onClick={handleSetToken}
-              className="flex-1 py-2 px-4 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors"
+              className="flex-1 py-3 md:py-2 px-4 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm min-h-[44px] md:min-h-0"
             >
               设置 Token
             </button>
@@ -411,7 +438,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
                 setManualToken('');
                 setManualUsername('');
               }}
-              className="py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
+              className="py-3 md:py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm min-h-[44px] md:min-h-0"
             >
               取消
             </button>
@@ -419,24 +446,27 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
         </div>
       )}
 
-      <div className="flex items-center justify-between">
-        <p className="text-xs text-gray-500 dark:text-gray-400">
+      {/* 底部操作区 - 移动端优化 */}
+      <div className="flex flex-col gap-3 pt-3 border-t dark:border-gray-700">
+        {/* URL 显示 - 移动端截断 */}
+        <p className="text-xs text-gray-400 dark:text-gray-500 truncate max-w-full overflow-hidden" title={config.url}>
           {config.url}
         </p>
+        {/* 操作按钮 - 移动端加大点击区域,按钮靠左对齐 */}
         {config.authType === 'jwt' && isEnabled && (
-          <div className="flex gap-2">
+          <div className="flex gap-3 md:gap-2">
             {!isLoggedIn ? (
               !showLoginForm && !showTokenForm && (
                 <>
                   <button
                     onClick={() => setShowLoginForm(true)}
-                    className="text-sm text-blue-500 hover:text-blue-600 font-medium"
+                    className="text-sm text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 font-medium px-4 py-2.5 md:px-0 md:py-0 rounded-lg md:rounded-none bg-blue-50 dark:bg-blue-900/30 md:bg-transparent min-h-[44px] md:min-h-0 md:inline"
                   >
                     登录
                   </button>
                   <button
                     onClick={() => setShowTokenForm(true)}
-                    className="text-sm text-green-500 hover:text-green-600 font-medium"
+                    className="text-sm text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300 font-medium px-4 py-2.5 md:px-0 md:py-0 rounded-lg md:rounded-none bg-green-50 dark:bg-green-900/30 md:bg-transparent min-h-[44px] md:min-h-0 md:inline"
                   >
                     设置 Token
                   </button>
@@ -445,7 +475,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
             ) : (
               <button
                 onClick={handleLogout}
-                className="text-sm text-red-500 hover:text-red-600 font-medium"
+                className="text-sm text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300 font-medium px-4 py-2.5 md:px-0 md:py-0 rounded-lg md:rounded-none bg-red-50 dark:bg-red-900/30 md:bg-transparent min-h-[44px] md:min-h-0 md:inline"
               >
                 登出
               </button>