删除.
This commit is contained in:
parent
9e45704c66
commit
2894fd6aad
|
|
@ -82,7 +82,7 @@
|
||||||
{{ formatDate(row.createdAt) }}
|
{{ formatDate(row.createdAt) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link size="small" @click="handleViewDetails(row)">
|
<el-button type="primary" link size="small" @click="handleViewDetails(row)">
|
||||||
<el-icon><View /></el-icon>
|
<el-icon><View /></el-icon>
|
||||||
|
|
@ -97,6 +97,15 @@
|
||||||
<el-icon><component :is="row.status === 'active' ? 'Lock' : 'Unlock'" /></el-icon>
|
<el-icon><component :is="row.status === 'active' ? 'Lock' : 'Unlock'" /></el-icon>
|
||||||
{{ row.status === 'active' ? '禁用' : '启用' }}
|
{{ row.status === 'active' ? '禁用' : '启用' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="handleDeleteUser(row)"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</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
|
// Handle export CSV
|
||||||
async function handleExport() {
|
async function handleExport() {
|
||||||
exporting.value = true
|
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 = {
|
module.exports = {
|
||||||
getUserList,
|
getUserList,
|
||||||
getUserDetails,
|
getUserDetails,
|
||||||
updateUserStatus,
|
updateUserStatus,
|
||||||
exportUsersToCSV,
|
exportUsersToCSV,
|
||||||
|
deleteUser,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -63,4 +63,17 @@ router.put(
|
||||||
adminUserController.updateUserStatus
|
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;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ const Withdrawal = require('../models/Withdrawal');
|
||||||
const LoginHistory = require('../models/LoginHistory');
|
const LoginHistory = require('../models/LoginHistory');
|
||||||
const Commission = require('../models/Commission');
|
const Commission = require('../models/Commission');
|
||||||
const PaymentOrder = require('../models/PaymentOrder');
|
const PaymentOrder = require('../models/PaymentOrder');
|
||||||
|
const Notification = require('../models/Notification');
|
||||||
|
const { sequelize } = require('../config/database');
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
const { Parser } = require('json2csv');
|
const { Parser } = require('json2csv');
|
||||||
|
const logger = require('../config/logger');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin User Service
|
* Admin User Service
|
||||||
|
|
@ -422,9 +425,128 @@ const exportUsersToCSV = async (options = {}) => {
|
||||||
return csv;
|
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 = {
|
module.exports = {
|
||||||
getUserList,
|
getUserList,
|
||||||
getUserDetails,
|
getUserDetails,
|
||||||
updateUserStatus,
|
updateUserStatus,
|
||||||
exportUsersToCSV,
|
exportUsersToCSV,
|
||||||
|
deleteUser,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,9 @@ If you have privacy questions, please contact us through the application.`
|
||||||
wechat: 'WeChat',
|
wechat: 'WeChat',
|
||||||
alipay: 'Alipay',
|
alipay: 'Alipay',
|
||||||
bankCard: 'Bank Card',
|
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',
|
enterBankInfo: 'Please enter bank card information',
|
||||||
bankCardNumber: 'Bank Card Number',
|
bankCardNumber: 'Bank Card Number',
|
||||||
enterBankCardNumber: 'Please enter 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',
|
wechat: 'WeChat',
|
||||||
alipay: 'Alipay',
|
alipay: 'Alipay',
|
||||||
bankCard: 'Tarjeta Bancaria',
|
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',
|
enterBankInfo: 'Por favor, ingrese la información de la tarjeta bancaria',
|
||||||
bankCardNumber: 'Número de Tarjeta Bancaria',
|
bankCardNumber: 'Número de Tarjeta Bancaria',
|
||||||
enterBankCardNumber: 'Por favor, ingrese el número de tarjeta bancaria',
|
enterBankCardNumber: 'Por favor, ingrese el número de tarjeta bancaria',
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,9 @@ export default {
|
||||||
wechat: '微信',
|
wechat: '微信',
|
||||||
alipay: '支付宝',
|
alipay: '支付宝',
|
||||||
bankCard: '银行卡',
|
bankCard: '银行卡',
|
||||||
uploadQRCode: '请上传微信收款码',
|
uploadQRCode: '请上传收款码',
|
||||||
|
uploadWechatQRCode: '请上传微信收款码',
|
||||||
|
uploadAlipayQRCode: '请上传支付宝收款码',
|
||||||
enterBankInfo: '请填写银行卡信息',
|
enterBankInfo: '请填写银行卡信息',
|
||||||
bankCardNumber: '银行卡号',
|
bankCardNumber: '银行卡号',
|
||||||
enterBankCardNumber: '请输入银行卡号',
|
enterBankCardNumber: '请输入银行卡号',
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@
|
||||||
|
|
||||||
<!-- 微信/支付宝:上传收款码 -->
|
<!-- 微信/支付宝:上传收款码 -->
|
||||||
<view v-if="paymentMethod !== 'bank'">
|
<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">
|
<view class="upload-area" @click="uploadQRCode">
|
||||||
<image v-if="qrcodeImage" :src="qrcodeImage" class="uploaded-image" mode="aspectFit">
|
<image v-if="qrcodeImage" :src="qrcodeImage" class="uploaded-image" mode="aspectFit">
|
||||||
</image>
|
</image>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user