删除.
This commit is contained in:
parent
9e45704c66
commit
2894fd6aad
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -230,7 +230,9 @@ export default {
|
|||
wechat: '微信',
|
||||
alipay: '支付宝',
|
||||
bankCard: '银行卡',
|
||||
uploadQRCode: '请上传微信收款码',
|
||||
uploadQRCode: '请上传收款码',
|
||||
uploadWechatQRCode: '请上传微信收款码',
|
||||
uploadAlipayQRCode: '请上传支付宝收款码',
|
||||
enterBankInfo: '请填写银行卡信息',
|
||||
bankCardNumber: '银行卡号',
|
||||
enterBankCardNumber: '请输入银行卡号',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user