diff --git a/web/frontend/src/layouts/MainLayout.tsx b/web/frontend/src/layouts/MainLayout.tsx index 82b9fd8..f4fd506 100644 --- a/web/frontend/src/layouts/MainLayout.tsx +++ b/web/frontend/src/layouts/MainLayout.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Layout, Menu, Avatar, Space, Typography, Button } from 'antd'; +import { Layout, Menu, Avatar, Space, Typography, Button, Modal } from 'antd'; import { DashboardOutlined, UserOutlined, LogoutOutlined, CloudServerOutlined, TeamOutlined, ApiOutlined, KeyOutlined, @@ -60,71 +60,94 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) { menuItems.push({ key: '/users', label: '用户管理', icon: }); } - const handleLogout = async () => { - try { - await authApi.logout(); - } catch {} - clearAuth(); - onLogout?.(); - navigate('/login', { replace: true }); + const handleLogout = () => { + Modal.confirm({ + title: '确认退出', + content: '确定要退出登录吗?', + okText: '退出', + cancelText: '取消', + okButtonProps: { type: 'primary' }, + centered: true, + onOk: async () => { + try { + await authApi.logout(); + } catch {} + clearAuth(); + onLogout?.(); + navigate('/login', { replace: true }); + }, + }); }; return ( - +
- - {collapsed ? '鱼' : '斗鱼登录后台'} - -
- navigate(key)} - style={{ flex: 1, overflow: 'auto', marginTop: 8 }} - /> -
- - } size="small" /> - {!collapsed && ( - <> - {user.username} - - ({ROLE_LABELS[user.role] || user.role}) - - - )} - - {!collapsed && ( +
+ + {collapsed ? '鱼' : '斗鱼登录后台'} + - )} + icon={collapsed ? : } + onClick={() => setCollapsed(!collapsed)} + style={{ color: '#fff', flexShrink: 0 }} + /> +
+ navigate(key)} + style={{ flex: 1, overflow: 'auto', marginTop: 8 }} + /> +
+ + } size="small" /> + {!collapsed && ( + <> + {user.username} + + ({ROLE_LABELS[user.role] || user.role}) + + + )} + + {collapsed ? ( + + )} +
diff --git a/web/frontend/src/pages/CookiePage.tsx b/web/frontend/src/pages/CookiePage.tsx index 465ad8b..d85abee 100644 --- a/web/frontend/src/pages/CookiePage.tsx +++ b/web/frontend/src/pages/CookiePage.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from 'react'; -import { Table, Button, Card, Row, Col, Statistic, message, Tag, Popconfirm } from 'antd'; -import { DownloadOutlined, DeleteOutlined } from '@ant-design/icons'; +import { Table, Button, Card, Row, Col, Statistic, message, Tag, Popconfirm, Space } from 'antd'; +import { DownloadOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons'; import { cookieApi } from '../api/modules'; import { getUser, hasPerm } from '../store/auth'; export default function CookiePage() { const [cookies, setCookies] = useState([]); const [loading, setLoading] = useState(false); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); const user = getUser(); const canView = hasPerm(user, 'cookie:view'); @@ -43,10 +44,39 @@ export default function CookiePage() { } }; + const handleCopyCookie = (cookie: string, username: string) => { + if (!cookie) { + message.warning('Cookie 为空'); + return; + } + navigator.clipboard.writeText(cookie).then(() => { + message.success(`已复制 ${username} 的 Cookie`); + }).catch(() => { + message.error('复制失败'); + }); + }; + + const handleCopySelected = () => { + if (selectedRowKeys.length === 0) { + message.warning('请先选择 Cookie'); + return; + } + const selected = cookies.filter((c) => selectedRowKeys.includes(c.id)); + const text = selected + .map((c) => `${c.account_username}: ${c.cookie || '(空)'}`) + .join('\n'); + navigator.clipboard.writeText(text).then(() => { + message.success(`已复制 ${selected.length} 条 Cookie`); + }).catch(() => { + message.error('复制失败'); + }); + }; + const handleDelete = async (id: number) => { try { await cookieApi.delete(id); message.success('已删除'); + setSelectedRowKeys((prev) => prev.filter((k) => k !== id)); loadCookies(); } catch (e: any) { message.error(e.message); @@ -65,30 +95,47 @@ export default function CookiePage() { return {val}; }, }, - { title: '时间', dataIndex: 'created_at', width: 180 }, - ]; - - if (canExport) { - columns.push({ + { title: '时间', dataIndex: 'created_at', width: 170 }, + { title: '操作', - width: 80, + width: 160, render: (_: any, record: any) => ( - handleDelete(record.id)}> - - + + + {canExport && ( + handleDelete(record.id)}> + - )} + {canExport && ( + + )} + @@ -96,6 +143,10 @@ export default function CookiePage() { setSelectedRowKeys(keys), + }} columns={columns} dataSource={cookies} rowKey="id"