Files
shaguabijia-admin-web/src/components/DeleteUserModal.tsx
T
liujiahui b1bdb66a76 feat(users): 用户管理加注销账号入口(super_admin) (#35)
## 用户管理:加注销账号入口(super_admin)

配合后端注销级联清理 + 删除端点(app-server PR #115),后台加删除入口。

### 改动

- **新增 `DeleteUserModal`**:受控弹窗,必填「注销原因(入审计)」,红字说明删什么/保留什么;`api.delete` 带 body 调 `DELETE /admin/api/users/{id}`;后端 `409`(有未提现现金/邀请金 / 在审提现单)经 `errMsg` 原样透出。列表页与详情页共用一套(同 `AdjustBalanceModals`)。
- **用户详情页**:顶部加红色「注销账号」按钮(`canDo(['super_admin'])`,`deleted` 状态隐藏)。
- **用户列表页**:操作列加红色「注销」内联链接(同门控),列宽 300→340。

### 门控

前端 `canDo(['super_admin'])` 与后端 `require_role("super_admin")` 对齐;门控被绕过后端亦兜底(403)。

### 验证

`next build` ✓ Compiled successfully(tsc + eslint + 21 路由静态生成)。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: no_gen_mu <liujianhishen@gmail.com>
Reviewed-on: #35
Co-authored-by: liujiahui <liujiahui@wonderable.ai>
Co-committed-by: liujiahui <liujiahui@wonderable.ai>
2026-07-06 00:25:47 +08:00

62 lines
2.3 KiB
TypeScript

'use client';
// 注销/删除账号弹窗(受控)。列表页与用户详情页共用,避免两处各写一套(同 AdjustBalanceModals)。
// 复用 C 端自助注销同一套后端清理:物理删除个人内容/行为/PII 表 + 清零余额 + 匿名化 user 行,
// 保留资金流水/邀请关系/广告幂等+对账表。仅 super_admin 可见入口;后端亦 require_role("super_admin") 兜底。
// 后端资金前置闸(有未提现现金/邀请金 / 在审提现单 → 409)带明确 detail,errMsg 原样透出。
import { App, Form, Input, Modal } from 'antd';
import { api, errMsg } from '@/lib/api';
export type DeleteTargetUser = { id: number; phone: string };
interface Props {
user: DeleteTargetUser | null;
open: boolean;
onClose: () => void;
onDone?: () => void; // 删除成功后回调(刷新详情/列表)
}
export function DeleteUserModal({ user, open, onClose, onDone }: Props) {
const { message } = App.useApp();
const [form] = Form.useForm();
const submit = async () => {
if (!user) return;
const v = await form.validateFields(); // 原因必填,缺失即红字阻断
try {
// axios 的 DELETE 带 body 要放在 config.data 里
await api.delete(`/admin/api/users/${user.id}`, { data: { reason: v.reason } });
message.success('账号已注销');
onClose();
onDone?.();
} catch (e) {
message.error(errMsg(e));
}
};
return (
<Modal
title={`注销账号 - ${user?.phone ?? ''}`}
open={open}
onOk={submit}
onCancel={onClose}
okText="确认注销"
okButtonProps={{ danger: true }}
afterOpenChange={(o) => {
if (o) form.resetFields();
}}
destroyOnHidden
>
<p style={{ color: '#cf1322', marginBottom: 12 }}>
(//////),
<strong></strong>//广
</p>
<Form form={form} layout="vertical">
<Form.Item name="reason" label="注销原因(入审计)" rules={[{ required: true, message: '请填写注销原因' }]}>
<Input.TextArea rows={2} />
</Form.Item>
</Form>
</Modal>
);
}