This commit is contained in:
18631081161 2025-12-24 21:33:36 +08:00
parent 9e45704c66
commit 2894fd6aad
8 changed files with 232 additions and 5 deletions

View File

@ -82,7 +82,7 @@
{{ formatDate(row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right" align="center">
<el-table-column label="操作" width="220" fixed="right" align="center">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleViewDetails(row)">
<el-icon><View /></el-icon>
@ -97,6 +97,15 @@
<el-icon><component :is="row.status === 'active' ? 'Lock' : 'Unlock'" /></el-icon>
{{ row.status === 'active' ? '禁用' : '启用' }}
</el-button>
<el-button
type="danger"
link
size="small"
@click="handleDeleteUser(row)"
>
<el-icon><Delete /></el-icon>
删除
</el-button>
</template>
</el-table-column>
</el-table>
@ -530,6 +539,45 @@ async function handleToggleStatus(row) {
}
}
// Handle delete user
async function handleDeleteUser(row) {
try {
await ElMessageBox.confirm(
`确定要删除用户 "${row.nickname || row.uid || row.id}" 吗?\n\n此操作将同时删除该用户的所有相关数据包括\n• 预约记录\n• 提现记录\n• 佣金记录\n• 邀请记录\n• 登录历史\n• 通知消息\n\n此操作不可恢复`,
'危险操作',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'error',
dangerouslyUseHTMLString: false
}
)
// Second confirmation
await ElMessageBox.confirm(
`请再次确认:删除用户 "${row.nickname || row.uid}" 及其所有数据?`,
'最终确认',
{
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'error'
}
)
const response = await api.delete(`/api/v1/admin/users/${row.id}`)
if (response.data.code === 0) {
ElMessage.success('用户已删除')
fetchUsers()
}
} catch (error) {
if (error !== 'cancel') {
console.error('Failed to delete user:', error)
ElMessage.error(error.response?.data?.error?.message || '删除用户失败')
}
}
}
// Handle export CSV
async function handleExport() {
exporting.value = true

View File

@ -178,9 +178,47 @@ const exportUsersToCSV = async (req, res) => {
}
};
/**
* Delete user and all related data
* @route DELETE /api/v1/admin/users/:id
*/
const deleteUser = async (req, res) => {
try {
const { id } = req.params;
const result = await adminUserService.deleteUser(id);
res.json({
success: true,
data: result,
message: 'User and all related data deleted successfully',
});
} catch (error) {
if (error.message === 'User not found') {
return res.status(404).json({
success: false,
error: {
code: 'USER_NOT_FOUND',
message: 'User not found',
},
});
}
res.status(500).json({
success: false,
error: {
code: 'DELETE_USER_ERROR',
message: 'Failed to delete user',
details: error.message,
},
});
}
};
module.exports = {
getUserList,
getUserDetails,
updateUserStatus,
exportUsersToCSV,
deleteUser,
};

View File

@ -63,4 +63,17 @@ router.put(
adminUserController.updateUserStatus
);
/**
* @route DELETE /api/v1/admin/users/:id
* @desc Delete user and all related data
* @access Private (Super Admin only)
*/
router.delete(
'/:id',
authenticateAdmin,
requireRole(['super_admin']),
logAdminOperation,
adminUserController.deleteUser
);
module.exports = router;

View File

@ -5,8 +5,11 @@ const Withdrawal = require('../models/Withdrawal');
const LoginHistory = require('../models/LoginHistory');
const Commission = require('../models/Commission');
const PaymentOrder = require('../models/PaymentOrder');
const Notification = require('../models/Notification');
const { sequelize } = require('../config/database');
const { Op } = require('sequelize');
const { Parser } = require('json2csv');
const logger = require('../config/logger');
/**
* Admin User Service
@ -422,9 +425,128 @@ const exportUsersToCSV = async (options = {}) => {
return csv;
};
/**
* Delete user and all related data
* @param {String} userId - User ID
* @returns {Promise<Object>} Deletion result
*/
const deleteUser = async (userId) => {
const transaction = await sequelize.transaction();
try {
// Get user first
const user = await User.findByPk(userId, { transaction });
if (!user) {
throw new Error('User not found');
}
// Store user info for logging
const userInfo = {
id: user.id,
uid: user.uid,
nickname: user.nickname,
};
// Delete related data in order (respecting foreign key constraints)
// 1. Delete login history
const deletedLoginHistory = await LoginHistory.destroy({
where: { userId, userType: 'user' },
transaction,
});
// 2. Delete notifications
const deletedNotifications = await Notification.destroy({
where: { userId },
transaction,
});
// 3. Delete commissions (where user is inviter or invitee)
const deletedCommissions = await Commission.destroy({
where: {
[Op.or]: [
{ inviterId: userId },
{ inviteeId: userId },
],
},
transaction,
});
// 4. Delete withdrawals
const deletedWithdrawals = await Withdrawal.destroy({
where: { userId },
transaction,
});
// 5. Delete payment orders
const deletedPaymentOrders = await PaymentOrder.destroy({
where: { userId },
transaction,
});
// 6. Delete appointments
const deletedAppointments = await Appointment.destroy({
where: { userId },
transaction,
});
// 7. Delete invitations (where user is inviter or invitee)
const deletedInvitations = await Invitation.destroy({
where: {
[Op.or]: [
{ inviterId: userId },
{ inviteeId: userId },
],
},
transaction,
});
// 8. Clear invitedBy reference for users invited by this user
await User.update(
{ invitedBy: null },
{ where: { invitedBy: userId }, transaction }
);
// 9. Finally delete the user
await user.destroy({ transaction });
await transaction.commit();
logger.info(`User ${userInfo.uid} (${userInfo.nickname}) deleted with all related data`, {
userId: userInfo.id,
deletedLoginHistory,
deletedNotifications,
deletedCommissions,
deletedWithdrawals,
deletedPaymentOrders,
deletedAppointments,
deletedInvitations,
});
return {
user: userInfo,
deletedData: {
loginHistory: deletedLoginHistory,
notifications: deletedNotifications,
commissions: deletedCommissions,
withdrawals: deletedWithdrawals,
paymentOrders: deletedPaymentOrders,
appointments: deletedAppointments,
invitations: deletedInvitations,
},
};
} catch (error) {
await transaction.rollback();
logger.error('Delete user error:', error);
throw error;
}
};
module.exports = {
getUserList,
getUserDetails,
updateUserStatus,
exportUsersToCSV,
deleteUser,
};

View File

@ -230,7 +230,9 @@ If you have privacy questions, please contact us through the application.`
wechat: 'WeChat',
alipay: 'Alipay',
bankCard: 'Bank Card',
uploadQRCode: 'Please upload WeChat QR code',
uploadQRCode: 'Please upload QR code',
uploadWechatQRCode: 'Please upload WeChat QR code',
uploadAlipayQRCode: 'Please upload Alipay QR code',
enterBankInfo: 'Please enter bank card information',
bankCardNumber: 'Bank Card Number',
enterBankCardNumber: 'Please enter bank card number',

View File

@ -230,7 +230,9 @@ Si tiene preguntas sobre privacidad, contáctenos a través de la aplicación.`
wechat: 'WeChat',
alipay: 'Alipay',
bankCard: 'Tarjeta Bancaria',
uploadQRCode: 'Por favor, cargue el código QR de WeChat',
uploadQRCode: 'Por favor, cargue el código QR',
uploadWechatQRCode: 'Por favor, cargue el código QR de WeChat',
uploadAlipayQRCode: 'Por favor, cargue el código QR de Alipay',
enterBankInfo: 'Por favor, ingrese la información de la tarjeta bancaria',
bankCardNumber: 'Número de Tarjeta Bancaria',
enterBankCardNumber: 'Por favor, ingrese el número de tarjeta bancaria',

View File

@ -230,7 +230,9 @@ export default {
wechat: '微信',
alipay: '支付宝',
bankCard: '银行卡',
uploadQRCode: '请上传微信收款码',
uploadQRCode: '请上传收款码',
uploadWechatQRCode: '请上传微信收款码',
uploadAlipayQRCode: '请上传支付宝收款码',
enterBankInfo: '请填写银行卡信息',
bankCardNumber: '银行卡号',
enterBankCardNumber: '请输入银行卡号',

View File

@ -263,7 +263,7 @@
<!-- 微信/支付宝上传收款码 -->
<view v-if="paymentMethod !== 'bank'">
<text class="step-label">2.{{ $t('invite.uploadQRCode') }}</text>
<text class="step-label">2.{{ paymentMethod === 'alipay' ? $t('invite.uploadAlipayQRCode') : $t('invite.uploadWechatQRCode') }}</text>
<view class="upload-area" @click="uploadQRCode">
<image v-if="qrcodeImage" :src="qrcodeImage" class="uploaded-image" mode="aspectFit">
</image>