initial commit

This commit is contained in:
ken
2026-02-16 12:46:37 +08:00
commit e10747feac
190 changed files with 49119 additions and 0 deletions

73
mock/agent.ts Normal file
View File

@@ -0,0 +1,73 @@
import { Request, Response } from 'express';
const agents = {
team: {
name: 'team',
description: '管理复杂开发任务的项目经理和团队协调者',
mode: 'primary',
prompt: './.opencode/agents/team.md',
temperature: 0.3,
tools: { write: false, edit: false, bash: true },
},
planning: {
name: 'planning',
description: '专注于深度分析、需求拆解和实施路线图的技术架构师',
mode: 'subagent',
prompt: './.opencode/agents/planning.md',
temperature: 0.2,
tools: { write: false, edit: false, bash: false },
},
frontend: {
name: 'frontend',
description: '资深前端与 UmiJS 专家,负责从服务层到 UI/UX 的全栈实施',
mode: 'subagent',
prompt: './.opencode/agents/frontend.md',
temperature: 0.3,
tools: { write: true, edit: true, bash: true },
},
'code-spec': {
name: 'code-spec',
description: '强制执行 Ant Design 和 ProComponents 最佳实践的代码规范专家',
mode: 'subagent',
prompt: './.opencode/agents/code-spec.md',
temperature: 0.1,
tools: { write: true, edit: true, bash: false },
},
'qa-tester': {
name: 'qa-tester',
description: '进行功能测试和 i18n 验证的资深 QA 工程师',
mode: 'subagent',
prompt: './.opencode/agents/qa-tester.md',
temperature: 0.2,
tools: { write: false, edit: false, bash: true },
},
};
export default {
'GET /api/agents': (req: Request, res: Response) => {
const list = Object.keys(agents).map((key) => ({
key,
...agents[key],
promptPath: agents[key].prompt,
}));
res.send({ data: list, success: true });
},
'GET /api/agents/:key/prompt': (req: Request, res: Response) => {
res.send({
data: `这是一个模拟的 ${req.params.key} Prompt 内容...\n# ${req.params.key.toUpperCase()} Agent\n...`,
success: true,
});
},
'POST /api/agents/update': (req: Request, res: Response) => {
res.send({ success: true, message: '配置已更新' });
},
'GET /api/skills': (req: Request, res: Response) => {
res.send({
data: [
{ id: '1', name: 'Ant Design Skill', description: '提供标准组件模式和样式指南', isEnabled: true },
{ id: '2', name: 'Security Audit', description: '代码安全与 XSS 检查脚本', isEnabled: false },
],
success: true,
});
},
};

83
mock/article.mock.ts Normal file
View File

@@ -0,0 +1,83 @@
import { Request, Response } from 'express';
import type { ArticleItem } from '../src/pages/Article/data';
const articles: ArticleItem[] = [
{
id: '1',
title: '深入浅出 UmiJS 4 核心实战',
cover: 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800&auto=format&fit=crop&q=60',
summary: '本文将带你深入了解 UmiJS 4 的核心特性,包括路由加载、插件机制以及与 ProComponents 的深度集成。',
htmlContent: '<p>UmiJS 是可扩展的企业级前端框架,集成了 React 核心能力...</p>',
category: '技术框架',
tags: ['UmiJS', 'React', 'Frontend'],
status: 'published',
publishTime: '2024-02-14 10:00:00',
createdAt: '2024-02-14 09:00:00',
updatedAt: '2024-02-14 10:00:00',
},
{
id: '2',
title: 'Ant Design 5.0 设计趋势解析',
cover: 'https://images.unsplash.com/photo-1586717791821-3f44a563eb4c?w=800&auto=format&fit=crop&q=60',
summary: 'Ant Design 5.0 带来了全新的设计语言:快乐工作,本文解析了其背后的设计哲学。',
htmlContent: '<p>快乐工作是 Ant Design 5.0 的核心理念...</p>',
category: '设计规范',
tags: ['Ant Design', 'Design', 'UI'],
status: 'draft',
createdAt: '2024-02-14 11:00:00',
updatedAt: '2024-02-14 11:00:00',
},
];
export default {
'GET /api/articles': (req: Request, res: Response) => {
const { title } = req.query;
let filteredData = [...articles];
if (title) {
filteredData = filteredData.filter(item => item.title.includes(title as string));
}
res.json({
data: filteredData,
total: filteredData.length,
success: true,
});
},
'GET /api/articles/:id': (req: Request, res: Response) => {
const article = articles.find((i) => i.id === req.params.id);
res.json({
data: article,
success: !!article,
});
},
'POST /api/articles': (req: Request, res: Response) => {
const newArticle = {
...req.body,
id: Math.random().toString(36).substr(2, 9),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
articles.unshift(newArticle);
res.json({
data: newArticle,
success: true,
});
},
'PUT /api/articles/:id': (req: Request, res: Response) => {
const index = articles.findIndex((item) => item.id === req.params.id);
if (index !== -1) {
articles[index] = { ...articles[index], ...req.body, updatedAt: new Date().toISOString() };
res.json({ data: articles[index], success: true });
} else {
res.status(404).json({ success: false, message: 'Article not found' });
}
},
'DELETE /api/articles/:id': (req: Request, res: Response) => {
const index = articles.findIndex((item) => item.id === req.params.id);
if (index !== -1) {
articles.splice(index, 1);
res.json({ success: true });
} else {
res.status(404).json({ success: false, message: 'Article not found' });
}
},
};

454
mock/interface.mock.ts Normal file
View File

@@ -0,0 +1,454 @@
import type {
InterfaceChangeLogItem,
InterfaceDocInfo,
InterfaceExportResponse,
InterfaceFormValues,
InterfaceGatewayFormValues,
InterfaceGatewayPolicy,
InterfaceIntegrationInfo,
InterfaceItem,
InterfaceListParams,
InterfaceMockInfo,
InterfaceRoutePreview,
} from '@/pages/InterfaceManagement/data';
import type { Request, Response } from 'express';
import type { ParamsDictionary } from 'express-serve-static-core';
type InterfaceListQuery = Omit<InterfaceListParams, 'current' | 'pageSize'> & {
current?: string;
pageSize?: string;
};
const interfaceList: InterfaceItem[] = [
{
id: 'api-1001',
name: '用户列表查询',
method: 'GET',
path: '/api/users',
owner: '李娜',
status: 'active',
version: 'v1',
description: '分页获取用户列表',
createdAt: '2024-10-01 09:30:00',
updatedAt: '2024-10-10 18:20:00',
},
{
id: 'api-1002',
name: '新增订单',
method: 'POST',
path: '/api/orders',
owner: '王彬',
status: 'active',
version: 'v2',
description: '创建新的订单记录',
createdAt: '2024-09-18 13:10:00',
updatedAt: '2024-10-05 11:05:00',
},
{
id: 'api-1003',
name: '库存调整',
method: 'PUT',
path: '/api/inventory/adjust',
owner: '赵敏',
status: 'deprecated',
version: 'v1',
description: '旧库存调整接口,计划下线',
createdAt: '2024-08-02 08:45:00',
updatedAt: '2024-09-21 16:40:00',
},
{
id: 'api-1004',
name: '删除文章',
method: 'DELETE',
path: '/api/articles/:id',
owner: '陈曦',
status: 'disabled',
version: 'v1',
description: '文章删除功能已暂时关闭',
createdAt: '2024-07-12 10:20:00',
updatedAt: '2024-08-20 19:10:00',
},
];
const interfaceDocMap: Record<string, InterfaceDocInfo> = {
'api-1001': {
docUrl: 'https://docs.example.com/interfaces/api-1001',
exampleText:
'curl -X GET https://api.example.com/api/users?page=1&pageSize=10',
updatedAt: '2024-10-10 18:20:00',
owner: '李娜',
},
'api-1002': {
docUrl: 'https://docs.example.com/interfaces/api-1002',
exampleText: 'curl -X POST https://api.example.com/api/orders -d "{}"',
updatedAt: '2024-10-05 11:05:00',
owner: '王彬',
},
};
const gatewayPolicyMap: Record<string, InterfaceGatewayPolicy> = {
'api-1001': {
routeId: 'route-api-1001',
rateLimit: '2000 次/分钟',
authType: 'OAuth2 + 签名校验',
timeoutMs: 3000,
},
'api-1002': {
routeId: 'route-api-1002',
rateLimit: '1200 次/分钟',
authType: 'API Key',
timeoutMs: 5000,
},
};
const mockInfoMap: Record<string, InterfaceMockInfo> = {
'api-1001': {
requestSample:
'curl -X GET https://api.example.com/api/users?page=1&pageSize=10',
responseSample: '{"success":true,"data":[{"id":"1"}],"total":1}',
latency: '120ms',
mockEnabled: true,
mockUrl: 'https://mock.example.com/interfaces/api-1001',
},
'api-1002': {
requestSample: 'curl -X POST https://api.example.com/api/orders -d "{}"',
responseSample: '{"success":true,"data":{"id":"100"}}',
latency: '210ms',
mockEnabled: false,
mockUrl: 'https://mock.example.com/interfaces/api-1002',
},
};
const integrationInfoMap: Record<string, InterfaceIntegrationInfo> = {
'api-1001': {
partner: '物流平台 A',
channel: 'API Key + IP 白名单',
status: '已接入',
consoleUrl: 'https://console.example.com/integrations/api-1001',
},
'api-1002': {
partner: '支付平台 B',
channel: 'JWT + 双向 TLS',
status: '对接中',
consoleUrl: 'https://console.example.com/integrations/api-1002',
},
};
const changeLogMap: Record<string, InterfaceChangeLogItem[]> = {
'api-1001': [
{
id: 'log-1',
time: '2024-10-10 18:20',
description: '调整响应字段,新增可选字段 note',
operator: '李娜',
},
{
id: 'log-2',
time: '2024-09-18 13:10',
description: '接口首次发布',
operator: '王彬',
},
],
'api-1002': [
{
id: 'log-3',
time: '2024-10-05 11:05',
description: '补充校验规则与异常码',
operator: '王彬',
},
],
};
const parseNumber = (value: string | undefined, fallback: number) => {
const parsed = Number(value);
return Number.isNaN(parsed) ? fallback : parsed;
};
const getInterfaceItem = (id: string) =>
interfaceList.find((item) => item.id === id);
const ensureGatewayPolicy = (id: string): InterfaceGatewayPolicy => {
if (!gatewayPolicyMap[id]) {
gatewayPolicyMap[id] = {
routeId: `route-${id}`,
rateLimit: '2000 次/分钟',
authType: 'OAuth2 + 签名校验',
timeoutMs: 3000,
};
}
return gatewayPolicyMap[id];
};
const ensureMockInfo = (id: string): InterfaceMockInfo => {
if (!mockInfoMap[id]) {
const item = getInterfaceItem(id);
mockInfoMap[id] = {
requestSample: `curl -X ${item?.method ?? 'GET'} https://api.example.com${
item?.path ?? '/api/example'
}`,
responseSample: '{"success":true}',
latency: '120ms',
mockEnabled: false,
mockUrl: `https://mock.example.com/interfaces/${id}`,
};
}
return mockInfoMap[id];
};
export default {
'GET /api/interfaces': (
req: Request<ParamsDictionary, unknown, unknown, InterfaceListQuery>,
res: Response,
) => {
const { current, pageSize, name, method, path, owner, status, version } =
req.query;
const currentPage = parseNumber(current, 1);
const size = parseNumber(pageSize, 10);
let dataSource = [...interfaceList];
if (name) {
dataSource = dataSource.filter((item) => item.name.includes(name));
}
if (method) {
dataSource = dataSource.filter((item) => item.method === method);
}
if (path) {
dataSource = dataSource.filter((item) => item.path.includes(path));
}
if (owner) {
dataSource = dataSource.filter((item) => item.owner.includes(owner));
}
if (status) {
dataSource = dataSource.filter((item) => item.status === status);
}
if (version) {
dataSource = dataSource.filter((item) => item.version.includes(version));
}
const start = (currentPage - 1) * size;
const end = start + size;
res.json({
success: true,
data: dataSource.slice(start, end),
total: dataSource.length,
});
},
'POST /api/interfaces': (
req: Request<ParamsDictionary, InterfaceItem, InterfaceFormValues>,
res: Response,
) => {
const now = new Date().toISOString();
const newItem: InterfaceItem = {
id: `api-${Date.now()}`,
createdAt: now,
updatedAt: now,
...req.body,
};
interfaceList.unshift(newItem);
res.json(newItem);
},
'PUT /api/interfaces/:id': (
req: Request<{ id: string }, InterfaceItem, InterfaceFormValues>,
res: Response,
) => {
const { id } = req.params;
const index = interfaceList.findIndex((item) => item.id === id);
if (index >= 0) {
interfaceList[index] = {
...interfaceList[index],
...req.body,
updatedAt: new Date().toISOString(),
};
res.json(interfaceList[index]);
return;
}
res.status(404).json({
success: false,
data: interfaceList,
total: interfaceList.length,
});
},
'DELETE /api/interfaces/:id': (
req: Request<{ id: string }>,
res: Response,
) => {
const { id } = req.params;
const index = interfaceList.findIndex((item) => item.id === id);
if (index >= 0) {
interfaceList.splice(index, 1);
res.json({ success: true });
return;
}
res.status(404).json({ success: false });
},
'GET /api/interfaces/:id/doc': (
req: Request<{ id: string }>,
res: Response<InterfaceDocInfo>,
) => {
const { id } = req.params;
const item = getInterfaceItem(id);
if (!item) {
res.status(404).end();
return;
}
const docInfo: InterfaceDocInfo = interfaceDocMap[id] ?? {
docUrl: `https://docs.example.com/interfaces/${id}`,
exampleText: `curl -X ${item.method} https://api.example.com${item.path}`,
updatedAt: item.updatedAt,
owner: item.owner,
};
interfaceDocMap[id] = docInfo;
res.json(docInfo);
},
'GET /api/interfaces/:id/gateway': (
req: Request<{ id: string }>,
res: Response<InterfaceGatewayPolicy>,
) => {
const { id } = req.params;
res.json(ensureGatewayPolicy(id));
},
'PUT /api/interfaces/:id/gateway': (
req: Request<
{ id: string },
InterfaceGatewayPolicy,
InterfaceGatewayFormValues
>,
res: Response<InterfaceGatewayPolicy>,
) => {
const { id } = req.params;
const currentPolicy = ensureGatewayPolicy(id);
const nextPolicy: InterfaceGatewayPolicy = {
...currentPolicy,
rateLimit: req.body.rateLimit,
authType: req.body.authType,
timeoutMs: req.body.timeoutMs,
};
gatewayPolicyMap[id] = nextPolicy;
res.json(nextPolicy);
},
'GET /api/interfaces/:id/gateway/preview': (
req: Request<{ id: string }>,
res: Response<InterfaceRoutePreview>,
) => {
const { id } = req.params;
const item = getInterfaceItem(id);
if (!item) {
res.status(404).end();
return;
}
const policy = ensureGatewayPolicy(id);
const preview: InterfaceRoutePreview = {
routeId: policy.routeId,
upstream: 'https://gateway.example.com',
path: item.path,
method: item.method,
timeoutMs: policy.timeoutMs,
rateLimit: policy.rateLimit,
authType: policy.authType,
};
res.json(preview);
},
'GET /api/interfaces/:id/mock': (
req: Request<{ id: string }>,
res: Response<InterfaceMockInfo>,
) => {
const { id } = req.params;
res.json(ensureMockInfo(id));
},
'POST /api/interfaces/:id/debug': (
req: Request<{ id: string }>,
res: Response<{ success: boolean; message: string }>,
) => {
const { id } = req.params;
const mockInfo = ensureMockInfo(id);
mockInfo.mockEnabled = true;
mockInfo.latency = '98ms';
res.json({ success: true, message: 'debug-started' });
},
'GET /api/interfaces/:id/integrations': (
req: Request<{ id: string }>,
res: Response<InterfaceIntegrationInfo>,
) => {
const { id } = req.params;
const info =
integrationInfoMap[id] ??
({
partner: '待配置',
channel: '未配置',
status: '未接入',
consoleUrl: 'https://console.example.com/integrations',
} as InterfaceIntegrationInfo);
integrationInfoMap[id] = info;
res.json(info);
},
'POST /api/interfaces/:id/integrations': (
req: Request<
{ id: string },
InterfaceIntegrationInfo,
Pick<InterfaceIntegrationInfo, 'partner' | 'channel'>
>,
res: Response<InterfaceIntegrationInfo>,
) => {
const { id } = req.params;
const info: InterfaceIntegrationInfo = {
partner: req.body.partner,
channel: req.body.channel,
status: '已接入',
consoleUrl: `https://console.example.com/integrations/${id}`,
};
integrationInfoMap[id] = info;
res.json(info);
},
'POST /api/interfaces/:id/integrations/key': (
req: Request<{ id: string }>,
res: Response<InterfaceIntegrationInfo>,
) => {
const { id } = req.params;
const info = integrationInfoMap[id] ?? {
partner: '待配置',
channel: '未配置',
status: '未接入',
consoleUrl: `https://console.example.com/integrations/${id}`,
};
res.json(info);
},
'GET /api/interfaces/:id/changelog': (
req: Request<{ id: string }>,
res: Response<InterfaceChangeLogItem[]>,
) => {
const { id } = req.params;
res.json(changeLogMap[id] ?? []);
},
'GET /api/interfaces/:id/changelog/export': (
req: Request<{ id: string }>,
res: Response<InterfaceExportResponse>,
) => {
const { id } = req.params;
res.json({
downloadUrl: `https://download.example.com/interfaces/${id}/changelog.csv`,
fileName: `interface-${id}-changelog.csv`,
});
},
};

166
mock/inventory.mock.ts Normal file
View File

@@ -0,0 +1,166 @@
import { Request, Response } from 'express';
// 初始数据
const genInventoryList = (current: number, pageSize: number) => {
const tableListDataSource: any[] = [];
const warehouses = ['上海一号仓', '北京东区仓', '深圳前海仓'];
const units = ['个', '箱', '台'];
const statusList = ['normal', 'low_stock', 'out_of_stock'];
for (let i = 0; i < pageSize; i += 1) {
const quantity = Math.floor(Math.random() * 200);
let status = 'normal';
if (quantity === 0) status = 'out_of_stock';
else if (quantity < 10) status = 'low_stock';
tableListDataSource.push({
key: i,
id: `inv-${i}`,
sku: `SKU-2024-${String(i).padStart(3, '0')}`,
name: `高性能组件-${i}`,
quantity,
unit: units[i % 3],
warehouse: warehouses[i % 3],
status,
minStock: 10,
maxStock: 500,
lastUpdated: new Date().toISOString(),
category: '电子元器件',
description: '关键零部件,需定期盘点',
});
}
return tableListDataSource;
};
let tableListDataSource = genInventoryList(1, 40);
const logs: Record<string, any[]> = {}; // Map<InventoryId, LogItem[]>
export default {
'GET /api/inventory': (req: Request, res: Response) => {
const { current = 1, pageSize = 20, name, sku, status, warehouse } = req.query as any;
let dataSource = [...tableListDataSource];
if (name) {
dataSource = dataSource.filter(item => item.name.includes(name));
}
if (sku) {
dataSource = dataSource.filter(item => item.sku.includes(sku));
}
if (status) {
dataSource = dataSource.filter(item => item.status === status);
}
if (warehouse) {
dataSource = dataSource.filter(item => item.warehouse === warehouse);
}
const total = dataSource.length;
const startIndex = ((current as number) - 1) * (pageSize as number);
const endIndex = (current as number) * (pageSize as number);
const list = dataSource.slice(startIndex, endIndex);
res.json({
data: list,
total,
success: true,
pageSize,
current: parseInt(`${current}`, 10) || 1,
});
},
'POST /api/inventory': (req: Request, res: Response) => {
const newData = {
...req.body,
id: `inv-${Date.now()}`,
lastUpdated: new Date().toISOString(),
createdAt: new Date().toISOString(),
status: req.body.quantity > 0 ? 'normal' : 'out_of_stock',
};
tableListDataSource.unshift(newData);
res.json(newData);
},
'PUT /api/inventory/:id': (req: Request, res: Response) => {
const { id } = req.params;
const index = tableListDataSource.findIndex(item => item.id === id);
if (index !== -1) {
tableListDataSource[index] = { ...tableListDataSource[index], ...req.body, lastUpdated: new Date().toISOString() };
res.json(tableListDataSource[index]);
} else {
res.status(404).json({ success: false });
}
},
'POST /api/inventory/:id/adjust': (req: Request, res: Response) => {
const { id } = req.params;
const { type, quantity, remark } = req.body; // type: 'in' | 'out' | 'adjust'
const index = tableListDataSource.findIndex(item => item.id === id);
if (index !== -1) {
const item = tableListDataSource[index];
let newQuantity = item.quantity;
let changeAmount = 0;
if (type === 'in') {
newQuantity += parseInt(quantity, 10);
changeAmount = parseInt(quantity, 10);
} else if (type === 'out') {
newQuantity -= parseInt(quantity, 10);
changeAmount = -parseInt(quantity, 10);
} else if (type === 'adjust') {
changeAmount = parseInt(quantity, 10) - item.quantity; // 盘点调整:直接设为新值
newQuantity = parseInt(quantity, 10);
}
if (newQuantity < 0) {
res.status(400).json({ success: false, message: '库存不足,无法扣减' });
return;
}
// Update Status
let status = 'normal';
if (newQuantity === 0) status = 'out_of_stock';
else if (newQuantity < (item.minStock || 10)) status = 'low_stock';
tableListDataSource[index] = { ...item, quantity: newQuantity, status, lastUpdated: new Date().toISOString() };
// Log
const logId = String(id);
if (!logs[logId]) logs[logId] = [];
logs[logId].unshift({
id: `log-${Date.now()}`,
inventoryId: logId,
type,
quantity: changeAmount,
beforeQuantity: item.quantity,
afterQuantity: newQuantity,
operator: 'Admin', // MOCKED
remark,
createdAt: new Date().toISOString(),
});
res.json({ success: true, data: tableListDataSource[index] });
} else {
res.status(404).json({ success: false, message: 'Item not found' });
}
},
'GET /api/inventory/:id/logs': (req: Request, res: Response) => {
const { id } = req.params;
const logId = String(id);
const list = logs[logId] || [];
res.json({
data: list,
total: list.length,
success: true,
});
},
'DELETE /api/inventory/:id': (req: Request, res: Response) => {
const { id } = req.params;
tableListDataSource = tableListDataSource.filter(item => item.id !== id);
delete logs[String(id)];
res.json({ success: true });
}
};

572
mock/log.mock.ts Normal file
View File

@@ -0,0 +1,572 @@
import { Request, Response } from 'express';
import type {
LogItem,
OperationModule,
OperationStatus,
OperationType,
} from '../src/pages/LogAudit/data.d';
// 操作人姓名列表
const operatorNames = [
'张伟',
'李娜',
'王强',
'刘洋',
'陈静',
'杨帆',
'赵敏',
'黄涛',
'周丽',
'吴刚',
'徐静',
'孙鹏',
'马丽',
'朱杰',
'胡军',
'林娜',
'郭明',
'何伟',
'高峰',
'梁雨',
'system',
'admin',
'root',
];
// 角色列表
const operatorRoles = [
'超级管理员',
'系统管理员',
'运营专员',
'内容编辑',
'普通用户',
'审计员',
];
// 操作类型
const operationTypes: OperationType[] = [
'CREATE',
'UPDATE',
'DELETE',
'QUERY',
'LOGIN',
'LOGOUT',
'EXPORT',
'IMPORT',
'ENABLE',
'DISABLE',
'OTHER',
];
// 操作模块
const operationModules: OperationModule[] = [
'USER',
'PRODUCT',
'ORDER',
'ARTICLE',
'SYSTEM',
'AUTH',
'OTHER',
];
// 操作描述模板
const operationDescMap: Record<
OperationModule,
Record<OperationType, string[]>
> = {
USER: {
CREATE: ['创建新用户账号', '批量导入用户数据', '注册新会员'],
UPDATE: ['修改用户信息', '更新用户头像', '修改用户邮箱'],
DELETE: ['删除用户账号', '批量删除用户', '注销用户'],
QUERY: ['查询用户列表', '查看用户详情', '搜索用户'],
LOGIN: ['用户登录系统', '扫码登录', '短信验证码登录'],
LOGOUT: ['用户退出登录', '会话超时退出', '强制退出'],
EXPORT: ['导出用户列表', '导出用户报表'],
IMPORT: ['导入用户数据', '批量导入用户'],
ENABLE: ['启用用户账号', '激活用户'],
DISABLE: ['禁用用户账号', '冻结用户'],
OTHER: ['重置用户密码', '发送验证邮件'],
},
PRODUCT: {
CREATE: ['创建商品', '新增商品信息', '批量添加商品'],
UPDATE: ['修改商品信息', '更新商品价格', '修改商品库存'],
DELETE: ['删除商品', '批量删除商品'],
QUERY: ['查询商品列表', '查看商品详情', '搜索商品'],
LOGIN: [],
LOGOUT: [],
EXPORT: ['导出商品列表', '导出商品库存报表'],
IMPORT: ['导入商品数据', '批量导入商品信息'],
ENABLE: ['上架商品', '启用商品'],
DISABLE: ['下架商品', '禁用商品'],
OTHER: ['修改商品图片', '更新商品分类'],
},
ORDER: {
CREATE: ['创建订单', '生成新订单'],
UPDATE: ['修改订单状态', '更新订单信息', '修改收货地址'],
DELETE: ['删除订单', '取消订单'],
QUERY: ['查询订单列表', '查看订单详情', '搜索订单'],
LOGIN: [],
LOGOUT: [],
EXPORT: ['导出订单列表', '导出销售报表'],
IMPORT: ['导入订单数据'],
ENABLE: [],
DISABLE: [],
OTHER: ['订单退款', '订单发货', '订单备注'],
},
ARTICLE: {
CREATE: ['发布文章', '创建新文章', '保存草稿'],
UPDATE: ['修改文章内容', '更新文章标题', '编辑文章'],
DELETE: ['删除文章', '批量删除文章'],
QUERY: ['查询文章列表', '查看文章详情', '搜索文章'],
LOGIN: [],
LOGOUT: [],
EXPORT: ['导出文章内容'],
IMPORT: ['导入文章'],
ENABLE: ['发布文章', '启用文章'],
DISABLE: ['隐藏文章', '禁用文章'],
OTHER: ['文章审核', '文章置顶'],
},
SYSTEM: {
CREATE: ['创建系统配置', '添加系统参数'],
UPDATE: ['修改系统配置', '更新系统参数'],
DELETE: ['删除系统配置', '清除缓存'],
QUERY: ['查询系统日志', '查看系统状态'],
LOGIN: [],
LOGOUT: [],
EXPORT: ['导出系统日志', '导出配置备份'],
IMPORT: ['导入系统配置'],
ENABLE: ['启用功能模块'],
DISABLE: ['禁用功能模块'],
OTHER: ['系统备份', '系统恢复', '清理日志'],
},
AUTH: {
CREATE: ['创建角色', '添加权限', '新建菜单'],
UPDATE: ['修改角色权限', '更新菜单', '编辑权限'],
DELETE: ['删除角色', '移除权限', '删除菜单'],
QUERY: ['查询角色列表', '查看权限树'],
LOGIN: ['用户认证', '令牌刷新'],
LOGOUT: ['令牌失效'],
EXPORT: ['导出权限配置'],
IMPORT: ['导入权限配置'],
ENABLE: ['启用角色'],
DISABLE: ['禁用角色'],
OTHER: ['密码修改', '权限分配'],
},
OTHER: {
CREATE: ['创建数据'],
UPDATE: ['更新数据'],
DELETE: ['删除数据'],
QUERY: ['查询数据'],
LOGIN: [],
LOGOUT: [],
EXPORT: ['导出数据'],
IMPORT: ['导入数据'],
ENABLE: ['启用功能'],
DISABLE: ['禁用功能'],
OTHER: ['其他操作'],
},
};
// 请求方法
const requestMethods: Array<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'> = [
'GET',
'POST',
'PUT',
'DELETE',
'PATCH',
];
// 请求 URL 模板
const urlTemplates: Record<OperationModule, string[]> = {
USER: [
'/api/users',
'/api/users/list',
'/api/users/detail',
'/api/auth/login',
'/api/auth/logout',
],
PRODUCT: [
'/api/products',
'/api/products/list',
'/api/products/detail',
'/api/products/stock',
],
ORDER: [
'/api/orders',
'/api/orders/list',
'/api/orders/detail',
'/api/orders/status',
],
ARTICLE: [
'/api/articles',
'/api/articles/list',
'/api/articles/detail',
'/api/articles/publish',
],
SYSTEM: [
'/api/system/config',
'/api/system/logs',
'/api/system/status',
'/api/system/backup',
],
AUTH: [
'/api/auth/roles',
'/api/auth/permissions',
'/api/auth/menus',
'/api/auth/login',
],
OTHER: ['/api/misc', '/api/utils', '/api/common'],
};
// IP 地址池
const ipPrefixes = [
'192.168.1',
'192.168.0',
'10.0.0',
'172.16.0',
'113.45',
'36.110',
'223.5.5',
];
// User Agent 列表
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (Linux; Android 13; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
];
// 错误信息模板
const errorMessages = [
'参数校验失败:必填字段为空',
'权限不足:当前用户无操作权限',
'数据不存在:记录已被删除或不存在',
'数据库连接超时',
'系统繁忙,请稍后再试',
'请求参数格式错误',
'接口限流,请求过于频繁',
'Token 已过期,请重新登录',
'密码错误,请重新输入',
'账号已被锁定',
];
// 生成随机整数
function randomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 生成随机日期近30天
function randomDate(): string {
const now = new Date();
const daysAgo = randomInt(0, 30);
const hoursAgo = randomInt(0, 23);
const minutesAgo = randomInt(0, 59);
const secondsAgo = randomInt(0, 59);
const date = new Date(
now.getTime() -
daysAgo * 24 * 60 * 60 * 1000 -
hoursAgo * 60 * 60 * 1000 -
minutesAgo * 60 * 1000 -
secondsAgo * 1000,
);
return date.toISOString();
}
// 生成随机 IP
function randomIP(): string {
const prefix = ipPrefixes[randomInt(0, ipPrefixes.length - 1)];
return `${prefix}.${randomInt(1, 254)}`;
}
// 生成单条日志数据
function generateLog(id: number): LogItem {
const module = operationModules[randomInt(0, operationModules.length - 1)];
const type = operationTypes[randomInt(0, operationTypes.length - 1)];
const descList = operationDescMap[module][type];
const desc =
descList.length > 0
? descList[randomInt(0, descList.length - 1)]
: `${type}操作`;
// 90% 成功10% 失败
const isSuccess = Math.random() > 0.1;
const status: OperationStatus = isSuccess ? 'success' : 'failure';
const operatorName = operatorNames[randomInt(0, operatorNames.length - 1)];
const operatorId =
operatorName === 'system' ? '0' : randomInt(1000, 9999).toString();
return {
id: id.toString(),
operatorId,
operatorName,
operatorRole: operatorRoles[randomInt(0, operatorRoles.length - 1)],
operationType: type,
operationModule: module,
operationDesc: desc,
requestMethod: requestMethods[randomInt(0, requestMethods.length - 1)],
requestUrl:
urlTemplates[module][randomInt(0, urlTemplates[module].length - 1)],
requestParams:
Math.random() > 0.5
? JSON.stringify({ id: randomInt(1, 1000), page: 1, size: 20 })
: undefined,
responseData:
isSuccess && Math.random() > 0.7
? JSON.stringify({ code: 200, message: 'success', data: {} })
: undefined,
ipAddress: randomIP(),
userAgent:
Math.random() > 0.3
? userAgents[randomInt(0, userAgents.length - 1)]
: undefined,
status,
errorMessage: !isSuccess
? errorMessages[randomInt(0, errorMessages.length - 1)]
: undefined,
executionTime: randomInt(10, 5000),
createdAt: randomDate(),
};
}
// 生成 150 条日志数据
const logs: LogItem[] = Array.from({ length: 150 }, (_, i) =>
generateLog(i + 1),
);
// 按时间倒序排序
logs.sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
);
export default {
// 获取日志列表 GET /api/logs
'GET /api/logs': (req: Request, res: Response) => {
const {
current = 1,
pageSize = 20,
operatorName,
operationType,
operationModule,
status,
startTime,
endTime,
ipAddress,
sortField = 'createdAt',
sortOrder = 'descend',
} = req.query;
let filteredData = [...logs];
// 操作人姓名筛选
if (operatorName) {
filteredData = filteredData.filter((item) =>
item.operatorName.includes(operatorName as string),
);
}
// 操作类型筛选
if (operationType) {
filteredData = filteredData.filter(
(item) => item.operationType === operationType,
);
}
// 操作模块筛选
if (operationModule) {
filteredData = filteredData.filter(
(item) => item.operationModule === operationModule,
);
}
// 状态筛选
if (status) {
filteredData = filteredData.filter((item) => item.status === status);
}
// 时间范围筛选
if (startTime) {
const start = new Date(startTime as string).getTime();
filteredData = filteredData.filter(
(item) => new Date(item.createdAt).getTime() >= start,
);
}
if (endTime) {
const end = new Date(endTime as string).getTime();
filteredData = filteredData.filter(
(item) => new Date(item.createdAt).getTime() <= end,
);
}
// IP 地址筛选
if (ipAddress) {
filteredData = filteredData.filter((item) =>
item.ipAddress.includes(ipAddress as string),
);
}
// 排序
if (sortField && sortOrder) {
filteredData.sort((a, b) => {
const aValue = a[sortField as keyof LogItem];
const bValue = b[sortField as keyof LogItem];
const isAscend = sortOrder === 'ascend';
if (typeof aValue === 'string' && typeof bValue === 'string') {
return isAscend
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
}
if (typeof aValue === 'number' && typeof bValue === 'number') {
return isAscend ? aValue - bValue : bValue - aValue;
}
return 0;
});
}
// 分页
const currentNum = parseInt(current as string, 10);
const pageSizeNum = parseInt(pageSize as string, 10);
const startIndex = (currentNum - 1) * pageSizeNum;
const endIndex = startIndex + pageSizeNum;
const pagedData = filteredData.slice(startIndex, endIndex);
// 模拟网络延迟
setTimeout(() => {
res.json({
data: pagedData,
total: filteredData.length,
success: true,
});
}, 300);
},
// 获取日志详情 GET /api/logs/:id
'GET /api/logs/:id': (req: Request, res: Response) => {
const log = logs.find((item) => item.id === req.params.id);
if (!log) {
res.status(404).json({
success: false,
message: '日志记录不存在',
});
return;
}
res.json({
data: log,
success: true,
});
},
// 导出日志 POST /api/logs/export
'POST /api/logs/export': (req: Request, res: Response) => {
const {
operatorName,
operationType,
operationModule,
status,
startTime,
endTime,
ipAddress,
} = req.body;
let filteredData = [...logs];
// 应用相同的筛选逻辑
if (operatorName) {
filteredData = filteredData.filter((item) =>
item.operatorName.includes(operatorName),
);
}
if (operationType) {
filteredData = filteredData.filter(
(item) => item.operationType === operationType,
);
}
if (operationModule) {
filteredData = filteredData.filter(
(item) => item.operationModule === operationModule,
);
}
if (status) {
filteredData = filteredData.filter((item) => item.status === status);
}
if (startTime) {
const start = new Date(startTime).getTime();
filteredData = filteredData.filter(
(item) => new Date(item.createdAt).getTime() >= start,
);
}
if (endTime) {
const end = new Date(endTime).getTime();
filteredData = filteredData.filter(
(item) => new Date(item.createdAt).getTime() <= end,
);
}
if (ipAddress) {
filteredData = filteredData.filter((item) =>
item.ipAddress.includes(ipAddress),
);
}
// 生成 CSV 内容
const headers = [
'ID',
'操作人',
'操作类型',
'操作模块',
'操作描述',
'请求方法',
'请求URL',
'IP地址',
'状态',
'执行时间(ms)',
'操作时间',
];
const rows = filteredData.map((log) => [
log.id,
log.operatorName,
log.operationType,
log.operationModule,
log.operationDesc,
log.requestMethod,
log.requestUrl,
log.ipAddress,
log.status,
log.executionTime,
new Date(log.createdAt).toLocaleString('zh-CN'),
]);
const csvContent = [
headers.join(','),
...rows.map((row) => row.map((cell) => `"${cell}"`).join(',')),
].join('\n');
// 设置响应头,触发浏览器下载
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader(
'Content-Disposition',
`attachment; filename=logs_export_${new Date()
.toISOString()
.slice(0, 10)}.csv`,
);
// 添加 BOM 以支持中文显示
const bom = '\uFEFF';
res.send(bom + csvContent);
},
};

253
mock/order.mock.ts Normal file
View File

@@ -0,0 +1,253 @@
import type { Order } from '@/pages/Order/data';
// 模拟订单数据
const orders: Order[] = [
{
id: '1',
orderNo: 'ORD202402140001',
customerName: '张三',
customerPhone: '13800138001',
customerEmail: 'zhangsan@example.com',
items: [
{
id: '1-1',
orderId: '1',
productName: 'iPhone 15 Pro Max',
productImage: 'https://via.placeholder.com/80',
quantity: 1,
price: 9999,
subtotal: 9999,
},
{
id: '1-2',
orderId: '1',
productName: 'AirPods Pro',
productImage: 'https://via.placeholder.com/80',
quantity: 1,
price: 1899,
subtotal: 1899,
},
],
totalAmount: 11898,
status: 'completed',
paymentMethod: 'wechat',
paymentTime: '2024-02-14 10:30:00',
shippingAddress: '北京市朝阳区建国路88号',
shippingCompany: '顺丰速运',
shippingNo: 'SF1234567890',
shippedAt: '2024-02-14 14:00:00',
completedAt: '2024-02-15 16:30:00',
createdAt: '2024-02-14 10:00:00',
updatedAt: '2024-02-15 16:30:00',
},
{
id: '2',
orderNo: 'ORD202402140002',
customerName: '李四',
customerPhone: '13800138002',
items: [
{
id: '2-1',
orderId: '2',
productName: 'MacBook Pro 14',
productImage: 'https://via.placeholder.com/80',
quantity: 1,
price: 14999,
subtotal: 14999,
},
],
totalAmount: 14999,
status: 'shipped',
paymentMethod: 'alipay',
paymentTime: '2024-02-14 11:00:00',
shippingAddress: '上海市浦东新区陆家嘴环路100号',
shippingCompany: '京东物流',
shippingNo: 'JD0987654321',
shippedAt: '2024-02-14 15:30:00',
createdAt: '2024-02-14 10:45:00',
updatedAt: '2024-02-14 15:30:00',
},
{
id: '3',
orderNo: 'ORD202402140003',
customerName: '王五',
customerPhone: '13800138003',
customerEmail: 'wangwu@example.com',
items: [
{
id: '3-1',
orderId: '3',
productName: 'iPad Pro 12.9',
productImage: 'https://via.placeholder.com/80',
quantity: 2,
price: 8499,
subtotal: 16998,
},
],
totalAmount: 16998,
status: 'paid',
paymentMethod: 'bank',
paymentTime: '2024-02-14 13:00:00',
shippingAddress: '广州市天河区天河路385号',
createdAt: '2024-02-14 12:30:00',
updatedAt: '2024-02-14 13:00:00',
},
{
id: '4',
orderNo: 'ORD202402140004',
customerName: '赵六',
customerPhone: '13800138004',
items: [
{
id: '4-1',
orderId: '4',
productName: 'Apple Watch Ultra',
productImage: 'https://via.placeholder.com/80',
quantity: 1,
price: 5999,
subtotal: 5999,
},
],
totalAmount: 5999,
status: 'pending',
paymentMethod: 'wechat',
createdAt: '2024-02-14 14:00:00',
updatedAt: '2024-02-14 14:00:00',
},
{
id: '5',
orderNo: 'ORD202402140005',
customerName: '孙七',
customerPhone: '13800138005',
customerEmail: 'sunqi@example.com',
items: [
{
id: '5-1',
orderId: '5',
productName: 'AirPods Max',
productImage: 'https://via.placeholder.com/80',
quantity: 1,
price: 4399,
subtotal: 4399,
},
],
totalAmount: 4399,
status: 'cancelled',
paymentMethod: 'alipay',
cancelledAt: '2024-02-14 15:00:00',
cancelReason: '不想买了',
createdAt: '2024-02-14 09:00:00',
updatedAt: '2024-02-14 15:00:00',
},
];
export default {
// 查询订单列表
'GET /api/orders': (req: any, res: any) => {
const { current = 1, pageSize = 10, orderNo, customerName, status, paymentMethod } = req.query;
let filteredOrders = [...orders];
// 按订单号筛选
if (orderNo) {
filteredOrders = filteredOrders.filter((order) =>
order.orderNo.includes(orderNo),
);
}
// 按客户姓名筛选
if (customerName) {
filteredOrders = filteredOrders.filter((order) =>
order.customerName.includes(customerName),
);
}
// 按订单状态筛选
if (status) {
filteredOrders = filteredOrders.filter((order) => order.status === status);
}
// 按支付方式筛选
if (paymentMethod) {
filteredOrders = filteredOrders.filter(
(order) => order.paymentMethod === paymentMethod,
);
}
// 分页
const start = (current - 1) * pageSize;
const end = start + pageSize;
const paginatedOrders = filteredOrders.slice(start, end);
res.send({
success: true,
data: paginatedOrders,
total: filteredOrders.length,
});
},
// 查询订单详情
'GET /api/orders/:id': (req: any, res: any) => {
const { id } = req.params;
const order = orders.find((item) => item.id === id);
if (order) {
res.send({
success: true,
data: order,
});
} else {
res.status(404).send({
success: false,
message: '订单不存在',
});
}
},
// 订单发货
'POST /api/orders/:id/ship': (req: any, res: any) => {
const { id } = req.params;
const order = orders.find((item) => item.id === id);
if (order) {
order.status = 'shipped';
order.shippingCompany = req.body.shippingCompany;
order.shippingNo = req.body.shippingNo;
order.shippedAt = new Date().toISOString();
order.updatedAt = new Date().toISOString();
res.send({
success: true,
message: '发货成功',
});
} else {
res.status(404).send({
success: false,
message: '订单不存在',
});
}
},
// 取消订单
'POST /api/orders/:id/cancel': (req: any, res: any) => {
const { id } = req.params;
const order = orders.find((item) => item.id === id);
if (order) {
order.status = 'cancelled';
order.cancelReason = req.body.description || req.body.reason;
order.cancelledAt = new Date().toISOString();
order.updatedAt = new Date().toISOString();
res.send({
success: true,
message: '订单已取消',
});
} else {
res.status(404).send({
success: false,
message: '订单不存在',
});
}
},
};

56
mock/product.mock.ts Normal file
View File

@@ -0,0 +1,56 @@
const attributes = [
{
id: '1',
name: '主体颜色',
code: 'color',
type: 'select',
isRequired: true,
options: ['星空灰', '珍珠白', '午夜蓝'],
sort: 1,
status: 'active',
createdAt: '2024-02-14 10:00:00',
},
{
id: '2',
name: '重量 (g)',
code: 'weight',
type: 'number',
isRequired: false,
sort: 2,
status: 'active',
createdAt: '2024-02-14 10:05:00',
},
{
id: '3',
name: '无线连接',
code: 'wireless',
type: 'boolean',
isRequired: true,
sort: 3,
status: 'disabled',
createdAt: '2024-02-14 10:10:00',
},
];
export default {
'GET /api/product/attributes': (req: any, res: any) => {
res.send({
success: true,
data: attributes,
total: attributes.length,
});
},
'POST /api/product/attribute': (req: any, res: any) => {
res.send({
success: true,
message: '操作成功',
});
},
'DELETE /api/product/attribute/:id': (req: any, res: any) => {
res.send({
success: true,
message: '删除成功',
});
},
};

348
mock/product.ts Normal file
View File

@@ -0,0 +1,348 @@
import type { Request, Response } from 'express';
type ProductStatus =
| 'draft'
| 'pending_review'
| 'online'
| 'offline'
| 'rejected';
type ProductItem = {
id: string;
name: string;
category: string;
sku: string;
originalPrice: number;
salePrice: number;
costPrice?: number;
stock: number;
safetyStock: number;
status: ProductStatus;
description?: string;
imageUrl?: string;
createdAt: string;
updatedAt: string;
};
type ProductBody = Partial<ProductItem>;
const PRODUCT_STATUS_LIST: ProductStatus[] = [
'draft',
'pending_review',
'online',
'offline',
'rejected',
];
const now = () => new Date().toISOString();
let productList: ProductItem[] = [
{
id: 'prod-1001',
name: '智能降噪耳机 Pro',
category: 'audio',
sku: 'AUDIO-001',
originalPrice: 1599,
salePrice: 1299,
costPrice: 760,
stock: 45,
safetyStock: 20,
status: 'online',
description: '旗舰主动降噪,支持空间音频。',
imageUrl:
'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?q=80&w=200&auto=format&fit=crop',
createdAt: '2024-02-14T09:00:00.000Z',
updatedAt: '2024-02-14T09:00:00.000Z',
},
{
id: 'prod-1002',
name: '机械键盘无线版',
category: 'peripheral',
sku: 'KEYB-002',
originalPrice: 799,
salePrice: 599,
costPrice: 320,
stock: 0,
safetyStock: 10,
status: 'offline',
description: '三模连接,热插拔轴体。',
imageUrl:
'https://images.unsplash.com/photo-1511467687858-23d96c32e4ae?q=80&w=200&auto=format&fit=crop',
createdAt: '2024-02-14T10:30:00.000Z',
updatedAt: '2024-02-14T10:30:00.000Z',
},
{
id: 'prod-1003',
name: '4K 电竞显示器',
category: 'digital',
sku: 'DISP-003',
originalPrice: 3999,
salePrice: 3599,
costPrice: 2410,
stock: 8,
safetyStock: 12,
status: 'pending_review',
description: '144Hz 刷新率HDR 1000。',
imageUrl:
'https://images.unsplash.com/photo-1527443224154-c4a3942d3acf?q=80&w=200&auto=format&fit=crop',
createdAt: '2024-02-14T11:15:00.000Z',
updatedAt: '2024-02-14T11:15:00.000Z',
},
{
id: 'prod-1004',
name: '便携投影仪 Lite',
category: 'digital',
sku: 'PROJ-004',
originalPrice: 2999,
salePrice: 2599,
stock: 22,
safetyStock: 8,
status: 'draft',
description: '1080P 分辨率,自动梯形校正。',
createdAt: '2024-02-15T08:20:00.000Z',
updatedAt: '2024-02-15T08:20:00.000Z',
},
{
id: 'prod-1005',
name: '智能手环 X2',
category: 'wearable',
sku: 'WEAR-005',
originalPrice: 399,
salePrice: 299,
costPrice: 120,
stock: 15,
safetyStock: 15,
status: 'rejected',
description: '支持心率、血氧与睡眠监测。',
createdAt: '2024-02-15T10:00:00.000Z',
updatedAt: '2024-02-15T10:00:00.000Z',
},
];
const coreFieldChanged = (
product: ProductItem,
patch: ProductBody,
): boolean => {
return (
(typeof patch.name === 'string' && patch.name !== product.name) ||
(typeof patch.sku === 'string' && patch.sku !== product.sku) ||
(typeof patch.originalPrice === 'number' &&
patch.originalPrice !== product.originalPrice) ||
(typeof patch.salePrice === 'number' &&
patch.salePrice !== product.salePrice)
);
};
const isProductStatus = (value: unknown): value is ProductStatus => {
return (
typeof value === 'string' &&
PRODUCT_STATUS_LIST.includes(value as ProductStatus)
);
};
const isPositiveNumber = (value: unknown): value is number => {
return typeof value === 'number' && value > 0;
};
const hasCreatePriceError = (payload: ProductBody): boolean => {
if (!isPositiveNumber(payload.originalPrice)) {
return true;
}
if (!isPositiveNumber(payload.salePrice)) {
return true;
}
return payload.salePrice > payload.originalPrice;
};
const hasPriceError = (payload: ProductBody): boolean => {
if (typeof payload.originalPrice === 'number' && payload.originalPrice <= 0) {
return true;
}
if (typeof payload.salePrice === 'number' && payload.salePrice <= 0) {
return true;
}
if (
typeof payload.originalPrice === 'number' &&
typeof payload.salePrice === 'number' &&
payload.salePrice > payload.originalPrice
) {
return true;
}
return false;
};
const parseString = (value: unknown): string | undefined => {
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
};
export default {
'GET /api/products': (req: Request, res: Response) => {
const current = Number(req.query.current) || 1;
const pageSize = Number(req.query.pageSize) || 10;
const name = parseString(req.query.name);
const category = parseString(req.query.category);
const sku = parseString(req.query.sku);
const status = parseString(req.query.status) as ProductStatus | undefined;
const stockWarning = parseString(req.query.stockWarning);
let filtered = [...productList];
if (name) {
filtered = filtered.filter((item) => item.name.includes(name));
}
if (category) {
filtered = filtered.filter((item) => item.category === category);
}
if (sku) {
filtered = filtered.filter((item) => item.sku.includes(sku));
}
if (status) {
filtered = filtered.filter((item) => item.status === status);
}
if (stockWarning === 'warning') {
filtered = filtered.filter(
(item) => item.stock > 0 && item.stock < item.safetyStock,
);
}
if (stockWarning === 'empty') {
filtered = filtered.filter((item) => item.stock === 0);
}
const start = (current - 1) * pageSize;
const data = filtered.slice(start, start + pageSize);
res.json({
success: true,
data,
total: filtered.length,
});
},
'GET /api/products/:id': (req: Request, res: Response) => {
const { id } = req.params;
const item = productList.find((product) => product.id === id);
if (!item) {
res.status(404).json({ success: false, message: '商品不存在' });
return;
}
res.json(item);
},
'POST /api/products': (req: Request, res: Response) => {
const payload = req.body as ProductBody;
if (hasCreatePriceError(payload)) {
res.status(400).json({ success: false, message: '价格校验失败' });
return;
}
if (payload.status !== undefined && !isProductStatus(payload.status)) {
res.status(400).json({ success: false, message: '状态非法' });
return;
}
const created: ProductItem = {
id: `prod-${Date.now()}`,
name: String(payload.name || ''),
category: String(payload.category || 'digital'),
sku: String(payload.sku || ''),
originalPrice: Number(payload.originalPrice || 0),
salePrice: Number(payload.salePrice || 0),
costPrice:
typeof payload.costPrice === 'number'
? Number(payload.costPrice)
: undefined,
stock: Number(payload.stock || 0),
safetyStock: Number(payload.safetyStock || 0),
status: payload.status ?? 'draft',
description: payload.description,
imageUrl: payload.imageUrl,
createdAt: now(),
updatedAt: now(),
};
productList = [created, ...productList];
res.json(created);
},
'PUT /api/products/status': (req: Request, res: Response) => {
const payload = req.body as { ids?: string[]; status?: ProductStatus };
const ids = Array.isArray(payload.ids) ? payload.ids : [];
const status = payload.status;
if (!status || !['online', 'offline'].includes(status)) {
res.status(400).json({ success: false, message: '状态非法' });
return;
}
productList = productList.map((item) =>
ids.includes(item.id) ? { ...item, status, updatedAt: now() } : item,
);
res.json({ success: true });
},
'PUT /api/products/:id': (req: Request, res: Response) => {
const { id } = req.params;
const payload = req.body as ProductBody;
const index = productList.findIndex((product) => product.id === id);
if (index === -1) {
res.status(404).json({ success: false, message: '商品不存在' });
return;
}
const current = productList[index];
if (payload.status !== undefined && !isProductStatus(payload.status)) {
res.status(400).json({ success: false, message: '状态非法' });
return;
}
if (current.status === 'online' && coreFieldChanged(current, payload)) {
res.status(400).json({
success: false,
message: '已上架商品修改核心字段前需先下架',
});
return;
}
const merged = {
...current,
...payload,
updatedAt: now(),
} as ProductItem;
if (hasPriceError(merged)) {
res.status(400).json({ success: false, message: '价格校验失败' });
return;
}
productList[index] = merged;
res.json(productList[index]);
},
'DELETE /api/products/:id': (req: Request, res: Response) => {
const { id } = req.params;
const target = productList.find((item) => item.id === id);
if (!target) {
res.status(404).json({ success: false, message: '商品不存在' });
return;
}
if (!['draft', 'offline'].includes(target.status)) {
res.status(400).json({
success: false,
message: '仅草稿和已下架商品可删除',
});
return;
}
productList = productList.filter((item) => item.id !== id);
res.json({ success: true });
},
};

177
mock/project.mock.ts Normal file
View File

@@ -0,0 +1,177 @@
import { Request, Response } from 'express';
const appTypeList: string[] = ['pc', 'h5', 'miniapp', 'webapp'];
const statusList: string[] = ['developing', 'online', 'offline', 'maintenance'];
const genAppList = () => {
const appData = [
{
name: '企业官网',
icon: '🏢',
type: 'pc',
desc: '企业官方网站,展示品牌形象',
},
{ name: '管理后台', icon: '⚙️', type: 'pc', desc: '企业内部管理系统' },
{ name: '移动端官网', icon: '📱', type: 'h5', desc: '移动端展示网站' },
{ name: '微信小程序', icon: '💬', type: 'miniapp', desc: '微信生态小程序' },
{
name: '员工Portal',
icon: '👥',
type: 'webapp',
desc: '员工工作入口平台',
},
{ name: '客户管理系统', icon: '🤝', type: 'pc', desc: 'CRM客户关系管理' },
{ name: '微商城', icon: '🛒', type: 'miniapp', desc: '微信小程序商城' },
{
name: '数据分析平台',
icon: '📊',
type: 'pc',
desc: '企业数据可视化平台',
},
{ name: '移动OA', icon: '📋', type: 'h5', desc: '移动办公应用' },
{ name: '知识库', icon: '📚', type: 'webapp', desc: '企业知识管理平台' },
{ name: '招聘系统', icon: '👔', type: 'pc', desc: '在线招聘管理平台' },
{ name: '会议系统', icon: '🎥', type: 'webapp', desc: '视频会议管理' },
];
return appData.map((item, index) => ({
id: `app-${index + 1}`,
appName: item.name,
appDesc: item.desc,
appIcon: item.icon,
appType: item.type,
status: statusList[index % 4],
version: `v${Math.floor(Math.random() * 3) + 1}.${Math.floor(
Math.random() * 10,
)}.${Math.floor(Math.random() * 20)}`,
owner: ['张三', '李四', '王五', '赵六', '钱七'][index % 5],
url: `https://app${index + 1}.example.com`,
tags: [
['企业', '重要'],
['内部', '核心'],
['移动端', '新项目'],
['微信生态'],
][index % 4],
createdAt: new Date(
Date.now() - Math.random() * 90 * 24 * 60 * 60 * 1000,
).toISOString(),
updatedAt: new Date(
Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000,
).toISOString(),
}));
};
let appDataSource = genAppList();
// 计算统计数据
const calculateStats = () => {
const stats = {
total: appDataSource.length,
online: 0,
developing: 0,
offline: 0,
maintenance: 0,
};
appDataSource.forEach((app: { status: string }) => {
if (app.status === 'online') stats.online++;
else if (app.status === 'developing') stats.developing++;
else if (app.status === 'offline') stats.offline++;
else if (app.status === 'maintenance') stats.maintenance++;
});
return stats;
};
export default {
'GET /api/projects': (req: Request, res: Response) => {
const current = Number(req.query.current) || 1;
const pageSize = Number(req.query.pageSize) || 12;
const appName = req.query.appName as string | undefined;
const owner = req.query.owner as string | undefined;
const appType = req.query.appType as string | undefined;
const status = req.query.status as string | undefined;
let filtered = [...appDataSource];
// 关键词搜索
if (appName) {
filtered = filtered.filter((p) => p.appName.includes(appName));
}
if (owner) {
filtered = filtered.filter((p) => p.owner.includes(owner));
}
if (appType) {
filtered = filtered.filter((p) => p.appType === appType);
}
if (status) {
filtered = filtered.filter((p) => p.status === status);
}
// 分页
const start = (current - 1) * pageSize;
const data = filtered.slice(start, start + pageSize);
res.json({
data,
total: filtered.length,
success: true,
current,
pageSize,
});
},
'GET /api/projects/stats': (_req: Request, res: Response) => {
const stats = calculateStats();
res.json({
stats,
success: true,
});
},
'POST /api/projects': (req: Request, res: Response) => {
const newApp = {
...req.body,
id: `app-${Date.now()}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
appDataSource.unshift(newApp);
res.json(newApp);
},
'PUT /api/projects/:id': (req: Request, res: Response) => {
const { id } = req.params;
const index = appDataSource.findIndex((p) => p.id === id);
if (index !== -1) {
appDataSource[index] = {
...appDataSource[index],
...req.body,
updatedAt: new Date().toISOString(),
};
res.json(appDataSource[index]);
} else {
res.status(404).json({ success: false, message: '应用不存在' });
}
},
'PUT /api/projects/:id/status': (req: Request, res: Response) => {
const { id } = req.params;
const { status } = req.body;
const index = appDataSource.findIndex((p) => p.id === id);
if (index !== -1) {
appDataSource[index] = {
...appDataSource[index],
status,
updatedAt: new Date().toISOString(),
};
res.json(appDataSource[index]);
} else {
res.status(404).json({ success: false, message: '应用不存在' });
}
},
'DELETE /api/projects/:id': (req: Request, res: Response) => {
const { id } = req.params;
appDataSource = appDataSource.filter((p) => p.id !== id);
res.json({ success: true });
},
};

60
mock/ranking.mock.ts Normal file
View File

@@ -0,0 +1,60 @@
const rankingList = [
{
id: '1',
rank: 1,
title: '深夜的序章',
subTitle: 'Premium Edition 2024',
cover: 'https://images.unsplash.com/photo-1485846234645-a62644f84728?q=80&w=200&auto=format&fit=crop',
rating: 9.8,
category: '剧情',
trend: 'up',
viewCount: 1250000,
releaseDate: '2024-01-15',
},
{
id: '2',
rank: 2,
title: '霓虹之舞',
subTitle: 'Limited Director Cut',
cover: 'https://images.unsplash.com/photo-1478720568477-152d9b164e26?q=80&w=200&auto=format&fit=crop',
rating: 9.5,
category: '动作',
trend: 'stable',
viewCount: 890000,
releaseDate: '2024-02-01',
},
{
id: '3',
rank: 3,
title: '极速传说',
subTitle: '4K Ultra High Speed',
cover: 'https://images.unsplash.com/photo-1536440136628-849c177e76a1?q=80&w=200&auto=format&fit=crop',
rating: 9.2,
category: '竞技',
trend: 'down',
viewCount: 750000,
releaseDate: '2023-12-20',
},
{
id: '4',
rank: 4,
title: '蓝色海洋',
subTitle: 'Ocean Documentary Visuals',
cover: 'https://images.unsplash.com/photo-1439405326854-014607f694d7?q=80&w=200&auto=format&fit=crop',
rating: 9.0,
category: '纪录片',
trend: 'up',
viewCount: 620000,
releaseDate: '2024-02-10',
}
];
export default {
'GET /api/ranking/list': (req: any, res: any) => {
res.send({
success: true,
data: rankingList,
total: rankingList.length,
});
},
};

79
mock/server.mock.ts Normal file
View File

@@ -0,0 +1,79 @@
import { Request, Response } from 'express';
const genServerList = (current: number, pageSize: number) => {
const tableListDataSource: any[] = [];
const statusList = ['online', 'offline', 'maintenance'];
for (let i = 0; i < pageSize; i += 1) {
const status = statusList[i % 3];
tableListDataSource.push({
key: i,
id: `srv-${i}`,
name: `Server-${i}`,
ip: `192.168.1.${i}`,
status,
os: 'Ubuntu 22.04 LTS',
cpu: Math.floor(Math.random() * 100),
memory: Math.floor(Math.random() * 100),
tags: ['web', 'production'],
updatedAt: new Date().toISOString(),
createdAt: new Date().toISOString(),
});
}
return tableListDataSource;
};
let tableListDataSource = genServerList(1, 40);
export default {
'GET /api/servers': (req: Request, res: Response) => {
const { current = 1, pageSize = 20 } = req.query as any; // Cast to any to access pagination params
// Simulate pagination filter
let dataSource = [...tableListDataSource].slice(
((current as number) - 1) * (pageSize as number),
(current as number) * (pageSize as number),
);
res.json({
data: dataSource,
total: tableListDataSource.length,
success: true,
pageSize,
current: parseInt(`${current}`, 10) || 1,
});
},
'POST /api/servers': (req: Request, res: Response) => {
const newData = {
...req.body,
updatedAt: new Date().toISOString(),
createdAt: new Date().toISOString(),
id: `srv-${Math.floor(Math.random() * 1000)}`,
key: Math.floor(Math.random() * 1000),
};
tableListDataSource.unshift(newData);
res.json(newData);
},
'PUT /api/servers/:id': (req: Request, res: Response) => {
const { id } = req.params;
const index = tableListDataSource.findIndex(item => item.id === id);
if (index !== -1) {
tableListDataSource[index] = { ...tableListDataSource[index], ...req.body };
res.json(tableListDataSource[index]);
} else {
res.status(404).json({ success: false });
}
},
'DELETE /api/servers/:id': (req: Request, res: Response) => {
const { id } = req.params;
tableListDataSource = tableListDataSource.filter(item => item.id !== id);
res.json({ success: true });
},
'POST /api/servers/:id/restart': (req: Request, res: Response) => {
res.json({ success: true, status: 'restarting' });
},
};

274
mock/skill.ts Normal file
View File

@@ -0,0 +1,274 @@
import { SkillItem } from '@/pages/SkillManager/data';
// 类型映射函数
function getSkillType(skillName: string): SkillItem['type'] {
const typeMap: Record<string, string[]> = {
agent: ['agent-development', 'skill-creator', 'skill-creation-guide'],
design: [
'frontend-design',
'ui-design-system',
'ui-ux-pro-max',
'canvas-design',
'brand-guidelines',
],
document: ['docx', 'pdf', 'pptx', 'xlsx', 'doc-coauthoring'],
testing: [
'test',
'playwright',
'webapp-testing',
'test-driven-development',
],
integration: [
'mcp-builder',
'command-creator',
'command-development',
'figma',
],
workflow: [
'brainstorming',
'planning-with-files',
'subagent-driven-development',
'executing-plans',
'dispatching-parallel-agents',
'finishing-a-development-branch',
],
utility: [
'flags',
'fix',
'verify',
'extract-errors',
'flow',
'systematic-debugging',
'find-skills',
'feature-flags',
],
};
for (const [type, names] of Object.entries(typeMap)) {
if (names.includes(skillName)) {
return type as SkillItem['type'];
}
}
return 'development';
}
// 解析 frontmatter 的辅助函数
function parseFrontmatter(content: string): {
name: string;
description: string;
} {
const nameMatch = content.match(/^name:\s*(.+)$/m);
const descMatch = content.match(/^description:\s*(.+)$/m);
return {
name: nameMatch ? nameMatch[1].trim() : '',
description: descMatch ? descMatch[1].trim() : '',
};
}
// 模拟从文件系统读取 skills 数据
function getMockSkills(): SkillItem[] {
const skillsData = [
{ name: 'agent-development', source: 'agents' as const },
{ name: 'algorithmic-art', source: 'agents' as const },
{ name: 'brainstorming', source: 'agents' as const },
{ name: 'brand-guidelines', source: 'agents' as const },
{ name: 'canvas-design', source: 'agents' as const },
{ name: 'command-creator', source: 'agents' as const },
{ name: 'command-development', source: 'agents' as const },
{ name: 'context7', source: 'agents' as const },
{ name: 'context7-auto-research', source: 'agents' as const },
{ name: 'dispatching-parallel-agents', source: 'agents' as const },
{ name: 'doc-coauthoring', source: 'agents' as const },
{ name: 'docx', source: 'agents' as const },
{ name: 'executing-plans', source: 'agents' as const },
{ name: 'extract-errors', source: 'agents' as const },
{ name: 'feature-flags', source: 'agents' as const },
{ name: 'figma', source: 'agents' as const },
{ name: 'figma-implement-design', source: 'agents' as const },
{ name: 'find-skills', source: 'agents' as const },
{ name: 'finishing-a-development-branch', source: 'agents' as const },
{ name: 'fix', source: 'agents' as const },
{ name: 'flags', source: 'agents' as const },
{ name: 'flow', source: 'agents' as const },
{ name: 'frontend-design', source: 'agents' as const },
{ name: 'internal-comms', source: 'agents' as const },
{ name: 'mcp-builder', source: 'agents' as const },
{ name: 'pdf', source: 'agents' as const },
{ name: 'playwright', source: 'agents' as const },
{ name: 'pptx', source: 'agents' as const },
{ name: 'product-requirements', source: 'agents' as const },
{ name: 'prototype-prompt-generator', source: 'agents' as const },
{ name: 'receiving-code-review', source: 'agents' as const },
{ name: 'requesting-code-review', source: 'agents' as const },
{ name: 'skill-creation-guide', source: 'agents' as const },
{ name: 'skill-creator', source: 'agents' as const },
{ name: 'slack-gif-creator', source: 'agents' as const },
{ name: 'subagent-driven-development', source: 'agents' as const },
{ name: 'systematic-debugging', source: 'agents' as const },
{ name: 'template-skill', source: 'agents' as const },
{ name: 'test', source: 'agents' as const },
{ name: 'test-driven-development', source: 'agents' as const },
{ name: 'theme-factory', source: 'agents' as const },
{ name: 'ui-design-system', source: 'agents' as const },
{ name: 'ui-ux-pro-max', source: 'agents' as const },
{ name: 'using-git-worktrees', source: 'agents' as const },
{ name: 'using-superpowers', source: 'agents' as const },
{ name: 'ux-researcher-designer', source: 'agents' as const },
{ name: 'verification-before-completion', source: 'agents' as const },
{ name: 'verify', source: 'agents' as const },
{ name: 'web-artifacts-builder', source: 'agents' as const },
{ name: 'web-design-guidelines', source: 'agents' as const },
{ name: 'webapp-testing', source: 'agents' as const },
{ name: 'writing-plans', source: 'agents' as const },
{ name: 'writing-skills', source: 'agents' as const },
{ name: 'xlsx', source: 'agents' as const },
{ name: 'nodejs-backend-patterns', source: 'opencode' as const },
{ name: 'planning-with-files', source: 'opencode' as const },
];
const descriptions: Record<string, string> = {
'agent-development':
'Agent 开发技能,用于创建和管理 Claude Code 插件 agent',
'algorithmic-art': '使用 p5.js 创建算法艺术,支持生成艺术、流场和粒子系统',
brainstorming: '头脑风暴技能,在进行创造性工作前探索用户意图和需求',
'brand-guidelines': '应用 Anthropic 官方品牌颜色和字体设计规范',
'canvas-design': '使用设计哲学创建漂亮的视觉艺术作品',
'command-creator': '创建 Claude Code 斜杠命令的技能',
'command-development': '开发斜杠命令的完整指南',
context7: '通过 Context7 API 获取最新的库/框架文档',
'context7-auto-research': '自动获取 Claude Code 最新文档',
'dispatching-parallel-agents': '并行代理调度技能,处理独立任务',
'doc-coauthoring': '协作撰写文档的工作流程指南',
docx: 'Word 文档创建、编辑和分析工具包',
'executing-plans': '执行实现计划的技能',
'extract-errors': '提取和处理 React 错误消息',
'feature-flags': '功能开关管理技能',
figma: 'Figma MCP 服务器集成',
'figma-implement-design': '将 Figma 设计转换为生产代码',
'find-skills': '发现和安装 agent skills',
'finishing-a-development-branch': '完成开发分支的集成指导',
fix: '修复 lint 错误和格式化问题',
flags: '检查功能开关状态',
flow: 'Flow 类型检查技能',
'frontend-design': '创建生产级前端界面',
'internal-comms': '内部通信文档撰写',
'mcp-builder': 'MCP 服务器创建指南',
pdf: 'PDF 文档处理工具包',
playwright: '浏览器自动化测试工具',
pptx: '演示文稿创建和编辑工具',
'product-requirements': '产品需求文档生成',
'prototype-prompt-generator': 'UI/UX 原型提示生成器',
'receiving-code-review': '接收代码审查反馈',
'requesting-code-review': '请求代码审查',
'skill-creation-guide': '创建 skills 的完整指南',
'skill-creator': 'Skill 创建技能',
'slack-gif-creator': '创建 Slack 优化 GIF',
'subagent-driven-development': '子代理驱动开发',
'systematic-debugging': '系统化调试方法',
'template-skill': 'Skill 模板',
test: 'React 测试运行工具',
'test-driven-development': '测试驱动开发',
'theme-factory': '主题样式工具包',
'ui-design-system': 'UI 设计系统工具包',
'ui-ux-pro-max': 'UI/UX 设计智能工具',
'using-git-worktrees': 'Git worktree 隔离开发',
'using-superpowers': '使用技能系统',
'ux-researcher-designer': 'UX 研究和设计工具包',
'verification-before-completion': '完成前验证',
verify: '提交前验证检查',
'web-artifacts-builder': 'Web HTML artifacts 构建套件',
'web-design-guidelines': 'Web 界面指南合规性审查',
'webapp-testing': 'Web 应用测试工具包',
'writing-plans': '编写计划文档',
'writing-skills': '技能编写和验证',
xlsx: '电子表格创建和分析工具包',
'nodejs-backend-patterns': 'Node.js 后端服务开发模式',
'planning-with-files': '基于文件的复杂任务规划',
};
return skillsData.map((item, index) => ({
id: `skill_${index + 1}`,
name: item.name,
description: descriptions[item.name] || `${item.name} skill`,
path:
item.source === 'agents'
? `~/.agents/skills/${item.name}`
: `~/.config/opencode/skills/${item.name}`,
source: item.source,
type: getSkillType(item.name),
isEnabled: true,
fileCount: Math.floor(Math.random() * 10) + 1,
lastModified:
Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000),
}));
}
const mockSkills = getMockSkills();
export default {
'GET /api/skills': (req: any) => {
const { type, keyword, current = 1, pageSize = 10 } = req.query;
let filteredSkills = [...mockSkills];
// 按类型筛选
if (type && type !== 'all') {
filteredSkills = filteredSkills.filter((skill) => skill.type === type);
}
// 按关键词搜索
if (keyword) {
const kw = keyword.toLowerCase();
filteredSkills = filteredSkills.filter(
(skill) =>
skill.name.toLowerCase().includes(kw) ||
skill.description.toLowerCase().includes(kw),
);
}
const total = filteredSkills.length;
const start = (current - 1) * pageSize;
const data = filteredSkills.slice(start, start + pageSize);
return {
data,
total,
success: true,
};
},
'GET /api/skills/:id': (req: any) => {
const { id } = req.params;
const skill = mockSkills.find((s) => s.id === id);
if (!skill) {
return {
success: false,
errorMessage: 'Skill not found',
};
}
return {
data: {
...skill,
tags: [skill.type, skill.source],
content: `# ${skill.name}\n\n${skill.description}`,
},
success: true,
};
},
'POST /api/skills/:id/status': (req: any) => {
const { id } = req.params;
const { isEnabled } = req.body;
const skill = mockSkills.find((s) => s.id === id);
if (skill) {
skill.isEnabled = isEnabled;
}
return {
success: true,
};
},
};

115
mock/user.ts Normal file
View File

@@ -0,0 +1,115 @@
import { Request, Response } from 'express';
const genUserList = (current: number, pageSize: number) => {
const tableListDataSource: any[] = [];
for (let i = 0; i < pageSize; i += 1) {
const index = (current - 1) * 10 + i;
tableListDataSource.push({
id: `${index}`,
username: `user_${index}`,
realName: `用户 ${index}`,
mobile: `138001380${index.toString().padStart(2, '0')}`,
email: `user_${index}@antgravity.com`,
role: index % 2 === 0 ? 'admin' : 'user',
status: ['active', 'disabled', 'pending'][index % 3],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
}
return tableListDataSource;
};
let tableListDataSource = genUserList(1, 20);
function getUserList(req: Request, res: Response, u: string) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const { current = 1, pageSize = 10, username, realName, role, status } = req.query;
let dataSource = [...tableListDataSource];
if (username) {
dataSource = dataSource.filter((item) => item.username.includes(username as string));
}
if (realName) {
dataSource = dataSource.filter((item) => item.realName.includes(realName as string));
}
if (role) {
dataSource = dataSource.filter((item) => item.role === role);
}
if (status) {
dataSource = dataSource.filter((item) => item.status === status);
}
const result = {
data: dataSource,
total: dataSource.length,
success: true,
pageSize,
current: parseInt(`${current}`, 10) || 1,
};
return res.json(result);
}
function postUser(req: Request, res: Response, u: string, b: Request) {
const body = (b && b.body) || req.body;
const { method, id } = req;
switch (method) {
case 'POST':
const i = Math.ceil(Math.random() * 10000);
const newUser = {
id: `${i}`,
...body,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
tableListDataSource.unshift(newUser);
return res.json(newUser);
case 'PUT':
// The id in URL is usually available as req.params.id for Express routes,
// but Umi mock matches implementation might vary.
// Assuming RESTful style: /api/users/:id
// We need to parse ID from URL if not provided directly.
// Simplification for mock: assume ID is passed or parsed.
let updateId = id;
if (!updateId) {
// rough parsing for /api/users/123
const parts = req.url.split('/');
updateId = parts[parts.length - 1];
}
tableListDataSource = tableListDataSource.map((item) => {
if (item.id === updateId) {
return { ...item, ...body, updatedAt: new Date().toISOString() };
}
return item;
});
return res.json({ id: updateId, ...body });
case 'DELETE':
let deleteId = id;
if (!deleteId) {
const parts = req.url.split('/');
deleteId = parts[parts.length - 1];
}
tableListDataSource = tableListDataSource.filter((item) => item.id !== deleteId);
return res.json({ success: true });
default:
break;
}
return res.json({ result: 'Error' });
}
export default {
'GET /api/users': getUserList,
'POST /api/users': postUser,
'PUT /api/users/:id': postUser,
'DELETE /api/users/:id': postUser,
};

20
mock/userAPI.ts Normal file
View File

@@ -0,0 +1,20 @@
const users = [
{ id: 0, name: 'Umi', nickName: 'U', gender: 'MALE' },
{ id: 1, name: 'Fish', nickName: 'B', gender: 'FEMALE' },
];
export default {
'GET /api/v1/queryUserList': (req: any, res: any) => {
res.json({
success: true,
data: { list: users },
errorCode: 0,
});
},
'PUT /api/v1/user/': (req: any, res: any) => {
res.json({
success: true,
errorCode: 0,
});
},
};

74
mock/workflow.ts Normal file
View File

@@ -0,0 +1,74 @@
import { Request, Response } from 'express';
import { WorkflowTask } from '../src/pages/WorkflowOrchestrator/data';
// 模拟内存数据库
let workflowData: WorkflowTask[] = [
{
id: 'wf-001',
name: '自动化 UI 部署任务',
status: 'executing',
progress: 30,
currentStepId: 'step-2',
createTime: '2024-03-20 10:00:00',
steps: [
{ id: 'step-1', agentName: 'Planning Agent', status: 'success', startTime: '10:00', logs: '已完成架构设计与需求拆解。' },
{ id: 'step-2', agentName: 'Frontend Agent', status: 'executing', startTime: '10:05', logs: '正在编写 ProTable 组件代码,应用 Design Tokens...' },
{ id: 'step-3', agentName: 'QA Agent', status: 'thinking', startTime: '10:10', logs: '待执行回归测试。' },
],
},
{
id: 'wf-002',
name: '后端 API 重构',
status: 'success',
progress: 100,
currentStepId: 'step-2',
createTime: '2024-03-20 09:30:00',
steps: [
{ id: 'step-1', agentName: 'Architect', status: 'success', startTime: '09:30', logs: 'API 路径规范化已通过。' },
{ id: 'step-2', agentName: 'Backend Dev', status: 'success', startTime: '09:45', logs: '完成 Swagger 契约自动生成。' },
],
},
{
id: 'wf-003',
name: '性能压力测试',
status: 'failed',
progress: 65,
currentStepId: 'step-2',
createTime: '2024-03-20 11:00:00',
steps: [
{ id: 'step-1', agentName: 'DevOps Agent', status: 'success', startTime: '11:00', logs: '环境已就绪。' },
{ id: 'step-2', agentName: 'QA Agent', status: 'failed', startTime: '11:15', logs: '内存溢出错误JVM heap size exceeded.' },
],
},
];
export default {
'GET /api/workflow/list': (req: Request, res: Response) => {
// 动态更新逻辑:每次列表请求都模拟进度步进
workflowData = workflowData.map((task) => {
if (task.status === 'executing' && task.progress < 100) {
const nextProgress = Math.min(task.progress + 5, 100);
return {
...task,
progress: nextProgress,
status: nextProgress === 100 ? 'success' : 'executing',
};
}
return task;
});
res.send({ data: workflowData, total: workflowData.length });
},
'POST /api/workflow/:id/control': (req: Request, res: Response) => {
const { id } = req.params;
const { action } = req.body;
workflowData = workflowData.map((task) => {
if (task.id === id) {
if (action === 'retry') return { ...task, status: 'executing', progress: 0 };
if (action === 'stop') return { ...task, status: 'failed' };
}
return task;
});
res.send({ success: true });
},
};