08ea5fbe98
添加用户管理视图、API和状态管理文件
304 lines
6.5 KiB
TypeScript
304 lines
6.5 KiB
TypeScript
import { Page, Route } from '@playwright/test';
|
|
|
|
/**
|
|
* Mock管理器
|
|
* 提供统一的API Mock功能
|
|
*/
|
|
|
|
export interface MockConfig {
|
|
url: string | RegExp;
|
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
status?: number;
|
|
body?: unknown;
|
|
headers?: Record<string, string>;
|
|
delay?: number;
|
|
}
|
|
|
|
export class MockManager {
|
|
private page: Page;
|
|
private mocks: Map<string, MockConfig> = new Map();
|
|
private isEnabled: boolean = false;
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
}
|
|
|
|
/**
|
|
* 启用Mock
|
|
*/
|
|
enable(): void {
|
|
this.isEnabled = true;
|
|
}
|
|
|
|
/**
|
|
* 禁用Mock
|
|
*/
|
|
disable(): void {
|
|
this.isEnabled = false;
|
|
}
|
|
|
|
/**
|
|
* 检查Mock是否启用
|
|
*/
|
|
isMockEnabled(): boolean {
|
|
return this.isEnabled;
|
|
}
|
|
|
|
/**
|
|
* 添加Mock配置
|
|
*/
|
|
addMock(config: MockConfig): void {
|
|
const key = this.getMockKey(config.url, config.method || 'GET');
|
|
this.mocks.set(key, config);
|
|
}
|
|
|
|
/**
|
|
* 移除Mock配置
|
|
*/
|
|
removeMock(url: string | RegExp, method: string = 'GET'): void {
|
|
const key = this.getMockKey(url, method);
|
|
this.mocks.delete(key);
|
|
}
|
|
|
|
/**
|
|
* 清除所有Mock
|
|
*/
|
|
clearMocks(): void {
|
|
this.mocks.clear();
|
|
}
|
|
|
|
/**
|
|
* 应用所有Mock
|
|
*/
|
|
async applyMocks(): Promise<void> {
|
|
if (!this.isEnabled) {
|
|
return;
|
|
}
|
|
|
|
await this.page.route('**/*', async (route: Route) => {
|
|
const request = route.request();
|
|
const url = request.url();
|
|
const method = request.method();
|
|
|
|
// 查找匹配的Mock配置
|
|
for (const config of this.mocks.values()) {
|
|
if (this.matchesUrl(url, config.url) && method === (config.method || 'GET')) {
|
|
// 模拟延迟
|
|
if (config.delay) {
|
|
await new Promise(resolve => setTimeout(resolve, config.delay));
|
|
}
|
|
|
|
// 返回Mock响应
|
|
await route.fulfill({
|
|
status: config.status || 200,
|
|
contentType: 'application/json',
|
|
headers: config.headers || {},
|
|
body: JSON.stringify(config.body || {}),
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 没有匹配的Mock,继续正常请求
|
|
await route.continue();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock登录API
|
|
*/
|
|
mockLogin(success: boolean = true, delay: number = 500): void {
|
|
this.addMock({
|
|
url: /.*\/auth\/login/,
|
|
method: 'POST',
|
|
status: success ? 200 : 401,
|
|
delay,
|
|
body: success
|
|
? {
|
|
token: 'mock-token-' + Date.now(),
|
|
refreshToken: 'mock-refresh-token',
|
|
user: {
|
|
id: 1,
|
|
username: 'admin',
|
|
realName: '管理员',
|
|
email: 'admin@example.com',
|
|
avatar: '',
|
|
status: 'active',
|
|
},
|
|
permissions: ['*'],
|
|
}
|
|
: {
|
|
message: '用户名或密码错误',
|
|
code: 401,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock用户列表API
|
|
*/
|
|
mockUserList(count: number = 10, delay: number = 300): void {
|
|
const users = Array.from({ length: count }, (_, i) => ({
|
|
id: i + 1,
|
|
username: `user${i + 1}`,
|
|
realName: `用户${i + 1}`,
|
|
email: `user${i + 1}@example.com`,
|
|
phone: `138001380${String(i).padStart(2, '0')}`,
|
|
status: i % 3 === 0 ? 'inactive' : 'active',
|
|
createTime: new Date().toISOString(),
|
|
}));
|
|
|
|
this.addMock({
|
|
url: /.*\/user\/list/,
|
|
method: 'GET',
|
|
status: 200,
|
|
delay,
|
|
body: {
|
|
data: users,
|
|
total: count,
|
|
page: 1,
|
|
pageSize: 10,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock角色列表API
|
|
*/
|
|
mockRoleList(delay: number = 300): void {
|
|
const roles = [
|
|
{ id: 1, roleName: '超级管理员', roleKey: 'admin', status: 'active' },
|
|
{ id: 2, roleName: '普通用户', roleKey: 'user', status: 'active' },
|
|
{ id: 3, roleName: '访客', roleKey: 'guest', status: 'inactive' },
|
|
];
|
|
|
|
this.addMock({
|
|
url: /.*\/role\/list/,
|
|
method: 'GET',
|
|
status: 200,
|
|
delay,
|
|
body: {
|
|
data: roles,
|
|
total: roles.length,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock菜单列表API
|
|
*/
|
|
mockMenuList(delay: number = 300): void {
|
|
const menus = [
|
|
{ id: 1, menuName: '仪表盘', path: '/dashboard', icon: 'DashboardOutlined', status: 'active' },
|
|
{ id: 2, menuName: '系统管理', path: '/sys', icon: 'SettingOutlined', status: 'active' },
|
|
{ id: 3, menuName: '用户管理', path: '/sys/user', parentId: 2, status: 'active' },
|
|
{ id: 4, menuName: '角色管理', path: '/sys/role', parentId: 2, status: 'active' },
|
|
];
|
|
|
|
this.addMock({
|
|
url: /.*\/menu\/list/,
|
|
method: 'GET',
|
|
status: 200,
|
|
delay,
|
|
body: {
|
|
data: menus,
|
|
total: menus.length,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock创建操作
|
|
*/
|
|
mockCreate(resource: string, delay: number = 500): void {
|
|
this.addMock({
|
|
url: new RegExp(`.*\\/${resource}$`),
|
|
method: 'POST',
|
|
status: 200,
|
|
delay,
|
|
body: {
|
|
message: '创建成功',
|
|
code: 200,
|
|
data: { id: Date.now() },
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock更新操作
|
|
*/
|
|
mockUpdate(resource: string, delay: number = 500): void {
|
|
this.addMock({
|
|
url: new RegExp(`.*\\/${resource}\\/.*`),
|
|
method: 'PUT',
|
|
status: 200,
|
|
delay,
|
|
body: {
|
|
message: '更新成功',
|
|
code: 200,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock删除操作
|
|
*/
|
|
mockDelete(resource: string, delay: number = 500): void {
|
|
this.addMock({
|
|
url: new RegExp(`.*\\/${resource}\\/.*`),
|
|
method: 'DELETE',
|
|
status: 200,
|
|
delay,
|
|
body: {
|
|
message: '删除成功',
|
|
code: 200,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock错误响应
|
|
*/
|
|
mockError(url: string | RegExp, status: number = 500, message: string = '服务器错误'): void {
|
|
this.addMock({
|
|
url,
|
|
method: 'GET',
|
|
status,
|
|
body: {
|
|
message,
|
|
code: status,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock网络延迟
|
|
*/
|
|
mockDelay(url: string | RegExp, delay: number = 2000): void {
|
|
this.addMock({
|
|
url,
|
|
method: 'GET',
|
|
delay,
|
|
body: {},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取Mock键
|
|
*/
|
|
private getMockKey(url: string | RegExp, method: string): string {
|
|
const urlStr = url instanceof RegExp ? url.source : url;
|
|
return `${method}:${urlStr}`;
|
|
}
|
|
|
|
/**
|
|
* 检查URL是否匹配
|
|
*/
|
|
private matchesUrl(actualUrl: string, configUrl: string | RegExp): boolean {
|
|
if (configUrl instanceof RegExp) {
|
|
return configUrl.test(actualUrl);
|
|
}
|
|
return actualUrl.includes(configUrl);
|
|
}
|
|
}
|