feat(admin): demo account rebuild button + user asset registry (photo keys cascade-deleted with user)
This commit is contained in:
@@ -211,6 +211,8 @@ export const adminCreateInvite = (code: string, remark: string, baseUrl?: string
|
||||
adminCall<{ code: string }>('/admin/invites/create', { code, remark }, baseUrl);
|
||||
export const adminUpdateInvite = (id: number, patch: { remark?: string; valid?: boolean }, baseUrl?: string) =>
|
||||
adminCall<{ id: number }>('/admin/invites/update', { id, ...patch }, baseUrl);
|
||||
export const adminDemoRebuild = (baseUrl?: string) =>
|
||||
adminCall<{ ok: true; seeded: { seeded: boolean; footprints: number; semesters: number } }>('/admin/demo-rebuild', {}, baseUrl);
|
||||
export const adminDeleteUser = (userId: number | string, baseUrl?: string) =>
|
||||
adminCall<{ ok: true; username: string; remote_deleted: number }>('/admin/users/delete', { user_id: userId }, baseUrl);
|
||||
export const adminDeleteInvite = (id: number, baseUrl?: string) =>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
||||
import { AlertTriangle, Check, Copy, Pencil, Plus, RefreshCw, TicketCheck, Trash2, Users2, X } from 'lucide-react';
|
||||
import {
|
||||
adminCreateInvite, adminDeleteInvite, adminDeleteUser, adminGenerateInvites, adminListInvites,
|
||||
adminCreateInvite, adminDeleteInvite, adminDeleteUser, adminDemoRebuild, adminGenerateInvites, adminListInvites,
|
||||
adminListUsers, adminUpdateInvite, getServerUrl, getToken,
|
||||
type InviteRow,
|
||||
} from '@/lib/api';
|
||||
@@ -37,10 +37,22 @@ export function AdminPage() {
|
||||
const [filter, setFilter] = useState<Filter>('all');
|
||||
const [delUser, setDelUser] = useState<{ id: number | string; username: string } | null>(null);
|
||||
const [delConfirm, setDelConfirm] = useState('');
|
||||
const [rebuildOpen, setRebuildOpen] = useState(false);
|
||||
|
||||
|
||||
|
||||
const invalidate = () => { void qc.invalidateQueries({ queryKey: ['admin-invites'] }); };
|
||||
const onErr = (e: unknown) => setActionError((e as Error).message || '操作失败');
|
||||
const rebuildMut = useMutation({
|
||||
mutationFn: () => adminDemoRebuild(base),
|
||||
onSuccess: (d) => {
|
||||
setRebuildOpen(false); setActionError('');
|
||||
toast('success', '演示账号已重建:' + (d.seeded?.footprints ?? 0) + ' 条记录 + ' + (d.seeded?.semesters ?? 0) + ' 学期');
|
||||
void qc.invalidateQueries({ queryKey: ['admin-users'] });
|
||||
},
|
||||
onError: onErr,
|
||||
});
|
||||
|
||||
const delUserMut = useMutation({
|
||||
mutationFn: (id: number | string) => adminDeleteUser(id, base),
|
||||
onSuccess: (d) => {
|
||||
@@ -96,9 +108,14 @@ export function AdminPage() {
|
||||
<div className="mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-[15px] font-semibold text-ink-1">管理</h2>
|
||||
<Button variant="secondary" onClick={() => { void qc.invalidateQueries(); }}>
|
||||
<RefreshCw size={13} /> 刷新
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" onClick={() => setRebuildOpen(true)}>
|
||||
<Users2 size={13} /> 重建演示账号
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => { void qc.invalidateQueries(); }}>
|
||||
<RefreshCw size={13} /> 刷新
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{actionError && (
|
||||
@@ -309,6 +326,23 @@ export function AdminPage() {
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={rebuildOpen}
|
||||
onClose={() => setRebuildOpen(false)}
|
||||
width={440}
|
||||
title="重建演示账号"
|
||||
footer={<>
|
||||
<Button variant="ghost" onClick={() => setRebuildOpen(false)}>取消</Button>
|
||||
<Button variant="primary" disabled={rebuildMut.isPending} onClick={() => rebuildMut.mutate()}>
|
||||
重建并重新播种
|
||||
</Button>
|
||||
</>}>
|
||||
<div className="text-[12.5px] leading-relaxed text-ink-2">
|
||||
将<b>删除现有 demo 账号及其全部数据</b>,然后重新创建并播种全套虚拟演示数据
|
||||
(个人信息档案、16 条足迹、4 学期成绩、简历内容)。
|
||||
<div className="mt-2 text-ink-3">demo / demo1234 · 仅影响演示账号,其他用户不受影响。</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user