初步增加 web 界面
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Table, Button, Modal, Input, Select, message, Popconfirm, Typography,
|
||||
} from 'antd';
|
||||
import { ImportOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { accountApi, userApi } from '../api/modules';
|
||||
import { getUser, hasPerm } from '../store/auth';
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function AccountsPage() {
|
||||
const [accounts, setAccounts] = useState<any[]>([]);
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [importOpen, setImportOpen] = useState(false);
|
||||
const [importText, setImportText] = useState('');
|
||||
const [importing, setImporting] = useState(false);
|
||||
const user = getUser();
|
||||
|
||||
const canViewAll = hasPerm(user, 'account:view_all');
|
||||
const canImport = hasPerm(user, 'account:import');
|
||||
const canAssign = hasPerm(user, 'account:assign');
|
||||
const canDelete = hasPerm(user, 'account:delete');
|
||||
|
||||
const loadAccounts = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await accountApi.list();
|
||||
setAccounts(data);
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
const data = await userApi.list();
|
||||
setUsers(data.filter((u: any) => u.role === 'support'));
|
||||
} catch {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadAccounts();
|
||||
if (canAssign) loadUsers();
|
||||
}, []);
|
||||
|
||||
const handleImport = async () => {
|
||||
if (!importText.trim()) {
|
||||
message.warning('请输入账号数据');
|
||||
return;
|
||||
}
|
||||
setImporting(true);
|
||||
try {
|
||||
const result = await accountApi.import(importText);
|
||||
message.success(result.message);
|
||||
setImportOpen(false);
|
||||
setImportText('');
|
||||
loadAccounts();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssign = async (accountId: number, assignedTo: number | null) => {
|
||||
try {
|
||||
await accountApi.assign(accountId, assignedTo);
|
||||
message.success('已分配');
|
||||
loadAccounts();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await accountApi.delete(id);
|
||||
message.success('已删除');
|
||||
loadAccounts();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: any[] = [
|
||||
{ title: 'ID', dataIndex: 'id', width: 60 },
|
||||
{ title: '用户名', dataIndex: 'username' },
|
||||
];
|
||||
|
||||
if (canViewAll) {
|
||||
columns.push(
|
||||
{ title: '密码', dataIndex: 'password', width: 120 },
|
||||
{ title: '邮箱', dataIndex: 'email' },
|
||||
{ title: '邮箱密码', dataIndex: 'email_password', width: 120 },
|
||||
);
|
||||
}
|
||||
|
||||
columns.push({
|
||||
title: '分配给',
|
||||
dataIndex: 'assigned_username',
|
||||
render: (_: any, record: any) => {
|
||||
if (canAssign) {
|
||||
return (
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
allowClear
|
||||
placeholder="未分配"
|
||||
value={record.assigned_to}
|
||||
onChange={(val) => handleAssign(record.id, val ?? null)}
|
||||
options={users.map((u: any) => ({ value: u.id, label: u.username }))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return record.assigned_username || <Text type="secondary">未分配</Text>;
|
||||
},
|
||||
});
|
||||
|
||||
if (canDelete) {
|
||||
columns.push({
|
||||
title: '操作',
|
||||
width: 80,
|
||||
render: (_: any, record: any) => (
|
||||
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
|
||||
<Button danger size="small" icon={<DeleteOutlined />}>删除</Button>
|
||||
</Popconfirm>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
||||
<h2>账号管理</h2>
|
||||
{canImport && (
|
||||
<Button type="primary" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>
|
||||
批量导入
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={accounts}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
size="small"
|
||||
pagination={{ pageSize: 20 }}
|
||||
/>
|
||||
<Modal
|
||||
title="批量导入账号"
|
||||
open={importOpen}
|
||||
onCancel={() => setImportOpen(false)}
|
||||
onOk={handleImport}
|
||||
confirmLoading={importing}
|
||||
okText="导入"
|
||||
width={600}
|
||||
>
|
||||
<Text type="secondary">
|
||||
格式:用户名|密码|邮箱|邮箱密码(每行一个)
|
||||
</Text>
|
||||
<TextArea
|
||||
rows={10}
|
||||
value={importText}
|
||||
onChange={(e) => setImportText(e.target.value)}
|
||||
placeholder="用户名|密码|邮箱|邮箱密码 用户名|密码|邮箱|邮箱密码"
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user