feat(admin): demo account rebuild button + user asset registry (photo keys cascade-deleted with user)

This commit is contained in:
root
2026-10-17 13:59:18 +08:00
parent 6a97590f90
commit b48c36fdb3
18 changed files with 156 additions and 88 deletions
+2
View File
@@ -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) =>
+38 -4
View File
@@ -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>
);
}