用户删除
This commit is contained in:
1 parent
0483b14e9b
commit
b75ce5b490
5 files changed
+155
-2
No files matched your search
@@ -16,6 +16,7 @@ interface UserRow {
|
||||
registered_at: string;
|
||||
last_login_at: string | null;
|
||||
entitlement_count: number;
|
||||
order_count: number;
|
||||
}
|
||||
|
||||
interface EntitlementRow {
|
||||
@@ -52,11 +53,14 @@ export default function UsersPage() {
|
||||
const [sortKey, setSortKey] = useState('id');
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
const [error, setError] = useState('');
|
||||
const [notice, setNotice] = useState('');
|
||||
const [editing, setEditing] = useState<UserRow | null>(null);
|
||||
const [form, setForm] = useState({ nickname: '', headicon_id: '0', status: '1' });
|
||||
const [courses, setCourses] = useState<CourseRow[]>([]);
|
||||
const [entitlements, setEntitlements] = useState<EntitlementRow[]>([]);
|
||||
const [grantCourseId, setGrantCourseId] = useState('');
|
||||
/** 待确认删除的用户 */
|
||||
const [deleteTarget, setDeleteTarget] = useState<UserRow | null>(null);
|
||||
|
||||
const load = useCallback((nextPage: number, nextKeyword: string) => {
|
||||
setError('');
|
||||
@@ -100,6 +104,7 @@ export default function UsersPage() {
|
||||
};
|
||||
|
||||
const openEdit = (user: UserRow) => {
|
||||
setError('');
|
||||
setEditing(user);
|
||||
setForm({
|
||||
nickname: user.nickname ?? '',
|
||||
@@ -110,6 +115,25 @@ export default function UsersPage() {
|
||||
loadEntitlements(user.id);
|
||||
};
|
||||
|
||||
/** 删除用户,连带它的订单和权益一起删(服务端在同一个事务里做)。 */
|
||||
const removeUser = () => {
|
||||
if (!deleteTarget) return;
|
||||
const target = deleteTarget;
|
||||
setError('');
|
||||
setNotice('');
|
||||
api
|
||||
.deleteUser(target.id)
|
||||
.then((res) => {
|
||||
setDeleteTarget(null);
|
||||
setNotice(
|
||||
`已删除用户 #${target.id}` +
|
||||
(res ? `(连带 ${res.orders} 笔订单、${res.entitlements} 条权益)` : ''),
|
||||
);
|
||||
load(page, keyword);
|
||||
})
|
||||
.catch((err: { message: string }) => setError(err.message));
|
||||
};
|
||||
|
||||
const saveUser = () => {
|
||||
if (!editing) return;
|
||||
setError('');
|
||||
@@ -161,6 +185,7 @@ export default function UsersPage() {
|
||||
「主动登录」指用户自己点过登录(下单的前置条件),跟打开小程序时的静默登录无关,也跟有没有昵称无关。
|
||||
</p>
|
||||
<Notice message={error} />
|
||||
{notice && <Notice message={notice} kind="success" />}
|
||||
|
||||
<Card
|
||||
title={`共 ${total} 位用户`}
|
||||
@@ -253,6 +278,15 @@ export default function UsersPage() {
|
||||
<td className="px-2 py-2 text-right">
|
||||
<Button variant="ghost" onClick={() => openEdit(u)}>
|
||||
编辑 / 权益
|
||||
</Button>{' '}
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => {
|
||||
setError('');
|
||||
setDeleteTarget(u);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -277,6 +311,38 @@ export default function UsersPage() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
open={deleteTarget !== null}
|
||||
title={`删除用户 #${deleteTarget?.id ?? ''}`}
|
||||
onClose={() => setDeleteTarget(null)}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={() => setDeleteTarget(null)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button variant="danger" onClick={removeUser}>
|
||||
确认删除
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{/* 删除失败(比如还有订单/权益)时把原因显示在弹窗里,否则提示会被弹窗挡住 */}
|
||||
<Notice message={error} />
|
||||
<div className="space-y-3 text-sm text-gray-600">
|
||||
<p>
|
||||
{deleteTarget?.nickname || '(无昵称)'}
|
||||
<span className="ml-2 font-mono text-xs text-gray-400">{deleteTarget?.openid}</span>
|
||||
</p>
|
||||
<p className="rounded-md bg-red-50 px-3 py-2 text-red-700">
|
||||
确认后会<span className="font-medium">连带删除</span>该用户的<b>全部订单和权益</b>
|
||||
(该用户当前有 {deleteTarget?.order_count ?? 0} 笔订单、
|
||||
{deleteTarget?.entitlement_count ?? 0} 条权益),然后删掉用户记录。
|
||||
<span className="font-medium">此操作不可撤销。</span>
|
||||
</p>
|
||||
<p>删除后,对方用同一个微信再次打开小程序会重新注册成新用户(昵称要重新设置)。</p>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={editing !== null}
|
||||
title={`用户 #${editing?.id ?? ''}`}
|
||||
@@ -290,6 +356,8 @@ export default function UsersPage() {
|
||||
</>
|
||||
}
|
||||
>
|
||||
{/* 保存 / 发放 / 回收失败的原因也显示在弹窗里,不然会被弹窗挡住看不到 */}
|
||||
<Notice message={error} />
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
|
||||
@@ -138,6 +138,10 @@ export const api = {
|
||||
updateUser: (id: number, payload: Record<string, unknown>) =>
|
||||
request<unknown>(`/admin/v1/users/${id}`, { method: 'PATCH', body: JSON.stringify(payload) }),
|
||||
|
||||
/** 删除用户,连带它的订单和权益一起删(返回连带删掉的行数) */
|
||||
deleteUser: (id: number) =>
|
||||
request<{ orders: number; entitlements: number }>(`/admin/v1/users/${id}`, { method: 'DELETE' }),
|
||||
|
||||
listResource: (resource: string, params: Record<string, string | number | undefined>) =>
|
||||
request<Paged<Record<string, unknown>>>(`/admin/v1/data/${resource}${qs(params)}`),
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"fileNames":["./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts",".Line truncated
|
||||
{"fileNames":["./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts",".Line truncated
|
||||
Reference in new issue
Block a user