dev #5
@@ -17,15 +17,30 @@ export class AdminUserPage {
|
||||
}
|
||||
|
||||
async createUser(data: UserData) {
|
||||
await this.goto();
|
||||
console.log('开始创建用户:', data.email);
|
||||
|
||||
await this.goto();
|
||||
await this.page.waitForLoadState('domcontentloaded');
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
console.log('页面加载完成,准备点击添加用户按钮');
|
||||
|
||||
const addButton = this.page.locator('button:has-text("添加用户")');
|
||||
await addButton.waitFor({ timeout: 10000, state: 'visible' });
|
||||
await addButton.click();
|
||||
|
||||
console.log('已点击添加用户按钮,等待模态框打开');
|
||||
|
||||
await this.page.waitForTimeout(500);
|
||||
|
||||
await this.page.waitForSelector('.fixed.inset-0.bg-black.bg-opacity-50', {
|
||||
timeout: 5000,
|
||||
state: 'visible'
|
||||
});
|
||||
|
||||
console.log('模态框已打开,等待表单加载');
|
||||
|
||||
await this.page.waitForTimeout(300);
|
||||
|
||||
await this.page.waitForSelector('input[name="email"]', { timeout: 5000, state: 'visible' });
|
||||
await this.page.fill('input[name="email"]', data.email);
|
||||
await this.page.fill('input[name="password"]', data.password);
|
||||
@@ -38,12 +53,40 @@ export class AdminUserPage {
|
||||
await this.page.selectOption('select[name="role"]', data.role);
|
||||
}
|
||||
|
||||
console.log('表单填写完成,准备提交');
|
||||
|
||||
await this.page.click('button:has-text("创建")');
|
||||
|
||||
console.log('用户创建请求已提交');
|
||||
}
|
||||
|
||||
async expectUserInList(email: string) {
|
||||
console.log(`检查用户是否在列表中: ${email}`);
|
||||
|
||||
await this.goto();
|
||||
const row = this.page.locator(`tr:has-text("${email}")`);
|
||||
await expect(row).toBeVisible();
|
||||
await this.page.waitForLoadState('domcontentloaded');
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
let row = this.page.locator(`tr:has-text("${email}")`);
|
||||
let isVisible = await row.count() > 0;
|
||||
|
||||
if (!isVisible) {
|
||||
console.log('用户不在列表中,尝试刷新页面');
|
||||
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await this.page.waitForSelector('table tbody tr', { timeout: 5000 });
|
||||
await this.page.waitForTimeout(1000);
|
||||
|
||||
row = this.page.locator(`tr:has-text("${email}")`);
|
||||
isVisible = await row.count() > 0;
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
const allRows = await this.page.locator('table tbody tr').allTextContents();
|
||||
console.log('当前列表中的用户:');
|
||||
allRows.forEach((text, i) => console.log(`行 ${i + 1}: ${text}`));
|
||||
}
|
||||
|
||||
await expect(row).toBeVisible({ timeout: 10000 });
|
||||
console.log(`✅ 用户已在列表中: ${email}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@ export default function UsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -271,6 +272,7 @@ export default function UsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">姓名</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -280,6 +282,7 @@ export default function UsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">密码</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -288,6 +291,7 @@ export default function UsersPage() {
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">角色</label>
|
||||
<select
|
||||
name="role"
|
||||
value={formData.role}
|
||||
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -330,6 +334,7 @@ export default function UsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -339,6 +344,7 @@ export default function UsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">姓名</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -348,6 +354,7 @@ export default function UsersPage() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">新密码(留空则不修改)</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
@@ -356,6 +363,7 @@ export default function UsersPage() {
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">角色</label>
|
||||
<select
|
||||
name="role"
|
||||
value={formData.role}
|
||||
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
|
||||
|
||||
@@ -2,8 +2,10 @@ import { NextRequest } from 'next/server';
|
||||
import { db } from '@/db';
|
||||
import { users } from '@/db/schema';
|
||||
import { checkIsAdmin } from '@/lib/auth/check-permission';
|
||||
import { forbidden, success, handleApiError } from '@/lib/api-response';
|
||||
import { forbidden, success, handleApiError, badRequest } from '@/lib/api-response';
|
||||
import { desc } from 'drizzle-orm';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export async function GET(_request: NextRequest) {
|
||||
try {
|
||||
@@ -31,3 +33,40 @@ export async function GET(_request: NextRequest) {
|
||||
return handleApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { isAdmin } = await checkIsAdmin();
|
||||
|
||||
if (!isAdmin) {
|
||||
return forbidden();
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { email, name, password, role } = body;
|
||||
|
||||
if (!email || !name || !password || !role) {
|
||||
return badRequest('缺少必填字段');
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const [newUser] = await db
|
||||
.insert(users)
|
||||
.values({
|
||||
id: nanoid(),
|
||||
email,
|
||||
name,
|
||||
password: hashedPassword,
|
||||
isAdmin: role === 'admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.returning();
|
||||
|
||||
return success({ user: newUser });
|
||||
} catch (error) {
|
||||
console.error('创建用户失败:', error);
|
||||
return handleApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user