2025-12-18 19:07:21 +08:00
|
|
|
import { Request, Response } from 'express';
|
|
|
|
|
import { UserModel } from '../models/user';
|
|
|
|
|
import { QuizModel } from '../models/quiz';
|
|
|
|
|
|
|
|
|
|
export class AdminUserController {
|
|
|
|
|
static async getUsers(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
|
|
|
const limit = parseInt(req.query.limit as string) || 10;
|
|
|
|
|
|
|
|
|
|
const result = await UserModel.findAll(limit, (page - 1) * limit);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.users.map((u) => ({ ...u, password: u.password ?? '' })),
|
|
|
|
|
pagination: {
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
total: result.total,
|
|
|
|
|
pages: Math.ceil(result.total / limit)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '获取用户列表失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async deleteUser(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const { userId } = req.body;
|
|
|
|
|
if (!userId) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: 'userId不能为空'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const user = await UserModel.findById(userId);
|
|
|
|
|
if (!user) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '用户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await UserModel.delete(userId);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: '删除成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '删除用户失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async exportUsers(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const result = await UserModel.findAll(10000, 0);
|
|
|
|
|
const data = result.users.map((u) => ({
|
|
|
|
|
ID: u.id,
|
|
|
|
|
姓名: u.name,
|
|
|
|
|
手机号: u.phone,
|
|
|
|
|
密码: u.password ?? '',
|
|
|
|
|
注册时间: u.createdAt
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '导出用户失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 00:58:58 +08:00
|
|
|
// 更新用户信息
|
|
|
|
|
static async updateUser(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { name, phone, password } = req.body;
|
|
|
|
|
|
|
|
|
|
const user = await UserModel.findById(id);
|
|
|
|
|
if (!user) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '用户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 准备更新数据
|
|
|
|
|
const updateData: Partial<{ name: string; phone: string; password: string }> = {};
|
|
|
|
|
if (name !== undefined) updateData.name = name;
|
|
|
|
|
if (phone !== undefined) updateData.phone = phone;
|
|
|
|
|
if (password !== undefined) updateData.password = password;
|
|
|
|
|
|
|
|
|
|
// 更新用户
|
|
|
|
|
const updatedUser = await UserModel.update(id, updateData);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: updatedUser
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
// 处理手机号已存在的错误
|
|
|
|
|
if (error.message === '手机号已存在') {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '手机号已存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理SQLITE_CONSTRAINT_UNIQUE错误
|
|
|
|
|
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '手机号已存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理数据验证错误
|
|
|
|
|
if (error.message.includes('姓名长度必须在2-20个字符之间') ||
|
|
|
|
|
error.message.includes('手机号格式不正确')) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理其他错误
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '更新用户失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 19:07:21 +08:00
|
|
|
static async importUsers(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const file = (req as any).file;
|
|
|
|
|
if (!file) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '请上传Excel文件'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const XLSX = await import('xlsx');
|
|
|
|
|
const workbook = XLSX.read(file.buffer, { type: 'buffer' });
|
|
|
|
|
const sheetName = workbook.SheetNames[0];
|
|
|
|
|
const worksheet = workbook.Sheets[sheetName];
|
|
|
|
|
const rows = XLSX.utils.sheet_to_json(worksheet) as any[];
|
|
|
|
|
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
let imported = 0;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < rows.length; i++) {
|
|
|
|
|
const row = rows[i];
|
|
|
|
|
try {
|
|
|
|
|
const name = row['姓名'] || row['name'];
|
|
|
|
|
const phone = row['手机号'] || row['phone'];
|
|
|
|
|
const password = row['密码'] || row['password'] || '';
|
|
|
|
|
|
|
|
|
|
if (!name || !phone) {
|
|
|
|
|
errors.push(`第${i + 2}行:姓名或手机号缺失`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await UserModel.create({ name, phone, password });
|
|
|
|
|
imported++;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.message === '手机号已存在') {
|
|
|
|
|
errors.push(`第${i + 2}行:手机号重复`);
|
|
|
|
|
} else {
|
|
|
|
|
errors.push(`第${i + 2}行:${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
imported,
|
|
|
|
|
total: rows.length,
|
|
|
|
|
errors
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '导入用户失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async getUserRecords(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const { userId } = req.params;
|
|
|
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
|
|
|
const limit = parseInt(req.query.limit as string) || 10;
|
|
|
|
|
|
|
|
|
|
const result = await QuizModel.findRecordsByUserId(userId, limit, (page - 1) * limit);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.records,
|
|
|
|
|
pagination: {
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
total: result.total,
|
|
|
|
|
pages: Math.ceil(result.total / limit)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '获取用户答题记录失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async getRecordDetail(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const { recordId } = req.params;
|
|
|
|
|
|
|
|
|
|
const record = await QuizModel.findRecordById(recordId);
|
|
|
|
|
if (!record) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '答题记录不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const answers = await QuizModel.findAnswersByRecordId(recordId);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
record,
|
|
|
|
|
answers
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || '获取答题记录详情失败'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|