优化退出登录, cookie 可以复制

This commit is contained in:
yml2213
2026-06-22 14:54:32 +08:00
parent 627f06d0a0
commit 5fa91c1bb4
2 changed files with 149 additions and 75 deletions
+29 -6
View File
@@ -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,19 +60,31 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
menuItems.push({ key: '/users', label: '用户管理', icon: <TeamOutlined /> });
}
const handleLogout = async () => {
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 (
<Layout style={{ height: '100vh', overflow: 'hidden' }}>
<Sider trigger={null} collapsed={collapsed} onCollapse={setCollapsed}
style={{ display: 'flex', flexDirection: 'column' }}>
<Sider trigger={null} collapsed={collapsed} onCollapse={setCollapsed}>
<div style={{
display: 'flex', flexDirection: 'column', height: '100%',
}}>
<div style={{
height: 48, margin: '12px 12px 0', color: '#fff', fontSize: 16,
textAlign: 'center', lineHeight: '48px', fontWeight: 'bold',
@@ -114,18 +126,29 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
</>
)}
</Space>
{!collapsed && (
{collapsed ? (
<Button
block
type="text"
size="small"
icon={<LogoutOutlined />}
onClick={handleLogout}
style={{ textAlign: 'left' }}
style={{ color: 'rgba(255,255,255,0.65)' }}
/>
) : (
<Button
block
type="text"
size="small"
icon={<LogoutOutlined />}
onClick={handleLogout}
style={{ color: 'rgba(255,255,255,0.65)', justifyContent: 'flex-start' }}
>
退
</Button>
)}
</div>
</div>
</Sider>
<Layout>
<Content style={{ margin: 16, padding: 24, background: '#fff', borderRadius: 8, overflow: 'auto' }}>
+62 -11
View File
@@ -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<any[]>([]);
const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
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 <span style={{ fontFamily: 'monospace', fontSize: 12 }}>{val}</span>;
},
},
{ 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) => (
<Space size={4}>
<Button
size="small"
icon={<CopyOutlined />}
onClick={() => handleCopyCookie(record.cookie, record.account_username)}
>
</Button>
{canExport && (
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
<Button danger size="small" icon={<DeleteOutlined />}></Button>
<Button danger size="small" icon={<DeleteOutlined />} />
</Popconfirm>
)}
</Space>
),
});
}
},
];
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
<h2>Cookie </h2>
<Space>
<Button
icon={<CopyOutlined />}
onClick={handleCopySelected}
disabled={selectedRowKeys.length === 0}
>
{selectedRowKeys.length > 0 && `(${selectedRowKeys.length})`}
</Button>
{canExport && (
<Button type="primary" icon={<DownloadOutlined />} onClick={handleExport}>
CSV
</Button>
)}
</Space>
</div>
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={6}>
@@ -96,6 +143,10 @@ export default function CookiePage() {
</Col>
</Row>
<Table
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
}}
columns={columns}
dataSource={cookies}
rowKey="id"