From 2894fd6aad4fbb63b01878b9bd3e120cb9a55735 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Wed, 24 Dec 2025 21:33:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/views/users/index.vue | 50 ++++++- .../src/controllers/adminUserController.js | 38 ++++++ backend/src/routes/adminUserRoutes.js | 13 ++ backend/src/services/adminUserService.js | 122 ++++++++++++++++++ miniprogram/src/locale/en.js | 4 +- miniprogram/src/locale/es.js | 4 +- miniprogram/src/locale/zh.js | 4 +- .../src/pages/me/invite-reward-page.vue | 2 +- 8 files changed, 232 insertions(+), 5 deletions(-) diff --git a/admin/src/views/users/index.vue b/admin/src/views/users/index.vue index 450b59a..85c4210 100644 --- a/admin/src/views/users/index.vue +++ b/admin/src/views/users/index.vue @@ -82,7 +82,7 @@ {{ formatDate(row.createdAt) }} - + @@ -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 diff --git a/backend/src/controllers/adminUserController.js b/backend/src/controllers/adminUserController.js index 86b3bff..4bc6d75 100644 --- a/backend/src/controllers/adminUserController.js +++ b/backend/src/controllers/adminUserController.js @@ -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, }; diff --git a/backend/src/routes/adminUserRoutes.js b/backend/src/routes/adminUserRoutes.js index 7a9732d..32d96c2 100644 --- a/backend/src/routes/adminUserRoutes.js +++ b/backend/src/routes/adminUserRoutes.js @@ -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; diff --git a/backend/src/services/adminUserService.js b/backend/src/services/adminUserService.js index 9db5736..0beb139 100644 --- a/backend/src/services/adminUserService.js +++ b/backend/src/services/adminUserService.js @@ -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} 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, }; diff --git a/miniprogram/src/locale/en.js b/miniprogram/src/locale/en.js index ab62366..1ca42cc 100644 --- a/miniprogram/src/locale/en.js +++ b/miniprogram/src/locale/en.js @@ -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', diff --git a/miniprogram/src/locale/es.js b/miniprogram/src/locale/es.js index f52e3fc..1f8574e 100644 --- a/miniprogram/src/locale/es.js +++ b/miniprogram/src/locale/es.js @@ -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', diff --git a/miniprogram/src/locale/zh.js b/miniprogram/src/locale/zh.js index e76042a..70361e2 100644 --- a/miniprogram/src/locale/zh.js +++ b/miniprogram/src/locale/zh.js @@ -230,7 +230,9 @@ export default { wechat: '微信', alipay: '支付宝', bankCard: '银行卡', - uploadQRCode: '请上传微信收款码', + uploadQRCode: '请上传收款码', + uploadWechatQRCode: '请上传微信收款码', + uploadAlipayQRCode: '请上传支付宝收款码', enterBankInfo: '请填写银行卡信息', bankCardNumber: '银行卡号', enterBankCardNumber: '请输入银行卡号', diff --git a/miniprogram/src/pages/me/invite-reward-page.vue b/miniprogram/src/pages/me/invite-reward-page.vue index afd3689..1881ebf 100644 --- a/miniprogram/src/pages/me/invite-reward-page.vue +++ b/miniprogram/src/pages/me/invite-reward-page.vue @@ -263,7 +263,7 @@ - 2.{{ $t('invite.uploadQRCode') }} + 2.{{ paymentMethod === 'alipay' ? $t('invite.uploadAlipayQRCode') : $t('invite.uploadWechatQRCode') }}