Initial commit: tkmind admin frontend (React + Vite).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { createAdminUser, updateAdminUser } from '../../api/client';
|
||||
import { useAdminUsers } from '../hooks/useAdminUsers';
|
||||
import { formatYuan } from '../utils/format';
|
||||
|
||||
export function UsersPage() {
|
||||
const { users, loading, error, reload, setError } = useAdminUsers();
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [newUser, setNewUser] = useState({
|
||||
username: '',
|
||||
password: '',
|
||||
displayName: '',
|
||||
workspaceRoot: '',
|
||||
balanceCents: 500,
|
||||
});
|
||||
|
||||
const handleCreate = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setMessage(null);
|
||||
setError(null);
|
||||
try {
|
||||
await createAdminUser({
|
||||
username: newUser.username,
|
||||
password: newUser.password,
|
||||
displayName: newUser.displayName || undefined,
|
||||
workspaceRoot: newUser.workspaceRoot || undefined,
|
||||
balanceCents: Number(newUser.balanceCents),
|
||||
});
|
||||
setNewUser({
|
||||
username: '',
|
||||
password: '',
|
||||
displayName: '',
|
||||
workspaceRoot: '',
|
||||
balanceCents: 500,
|
||||
});
|
||||
setMessage('用户已创建');
|
||||
await reload();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '创建失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<div className="admin-page-head">
|
||||
<h2>用户管理</h2>
|
||||
<p className="muted">创建账号、查看列表、进入用户详情配置权限</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="banner banner-error">{error}</p>}
|
||||
{message && <p className="banner banner-info">{message}</p>}
|
||||
|
||||
<section className="admin-card">
|
||||
<h2>创建用户</h2>
|
||||
<form className="admin-form" onSubmit={handleCreate}>
|
||||
<input
|
||||
placeholder="用户名"
|
||||
autoComplete="username"
|
||||
value={newUser.username}
|
||||
onChange={(e) => setNewUser((s) => ({ ...s, username: e.target.value }))}
|
||||
/>
|
||||
<input
|
||||
placeholder="密码"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={newUser.password}
|
||||
onChange={(e) => setNewUser((s) => ({ ...s, password: e.target.value }))}
|
||||
/>
|
||||
<input
|
||||
placeholder="显示名称"
|
||||
value={newUser.displayName}
|
||||
onChange={(e) => setNewUser((s) => ({ ...s, displayName: e.target.value }))}
|
||||
/>
|
||||
<input
|
||||
placeholder="工作目录(可选)"
|
||||
value={newUser.workspaceRoot}
|
||||
onChange={(e) => setNewUser((s) => ({ ...s, workspaceRoot: e.target.value }))}
|
||||
/>
|
||||
<input
|
||||
placeholder="初始余额(分)"
|
||||
type="number"
|
||||
value={newUser.balanceCents}
|
||||
onChange={(e) =>
|
||||
setNewUser((s) => ({ ...s, balanceCents: Number(e.target.value) }))
|
||||
}
|
||||
/>
|
||||
<button type="submit" className="send-btn">
|
||||
创建
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="admin-card">
|
||||
<div className="admin-card-head">
|
||||
<h2>用户列表</h2>
|
||||
<button type="button" className="ghost-btn" onClick={() => void reload()}>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
{loading ? (
|
||||
<p className="muted">加载中…</p>
|
||||
) : (
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户</th>
|
||||
<th>角色</th>
|
||||
<th>状态</th>
|
||||
<th>余额</th>
|
||||
<th>工作目录</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td>
|
||||
<div>{user.displayName}</div>
|
||||
<div className="muted">@{user.username}</div>
|
||||
</td>
|
||||
<td>{user.role}</td>
|
||||
<td>{user.status}</td>
|
||||
<td>¥{formatYuan(user.balanceCents)}</td>
|
||||
<td className="mono">{user.workspaceRoot}</td>
|
||||
<td className="admin-actions">
|
||||
<Link to={`/admin/users/${user.id}`} className="ghost-btn admin-inline-link">
|
||||
详情
|
||||
</Link>
|
||||
{user.role === 'user' && (
|
||||
<button
|
||||
type="button"
|
||||
className="ghost-btn"
|
||||
onClick={() =>
|
||||
void updateAdminUser(user.id, {
|
||||
status: user.status === 'active' ? 'disabled' : 'active',
|
||||
}).then(reload)
|
||||
}
|
||||
>
|
||||
{user.status === 'active' ? '禁用' : '启用'}
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user