feat: 添加MSW模拟服务和数据源集成
refactor: 重构页面组件移除冗余Layout组件 feat: 实现WebSocket和事件总线系统 feat: 添加队列和调度系统 docs: 更新架构文档和服务映射 style: 清理重复接口定义使用数据源 chore: 更新依赖项配置 feat: 添加运行时系统和领域引导 ci: 配置ESLint边界检查规则 build: 添加Redis和WebSocket依赖 test: 添加MSW浏览器环境入口 perf: 优化数据获取逻辑使用统一数据源 fix: 修复类型定义和状态管理问题
This commit is contained in:
393
dashboard/src/services/certificateDataSource.ts
Normal file
393
dashboard/src/services/certificateDataSource.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* [MOCK-002] 证书数据源抽象层
|
||||
* 通过环境变量自动切换Mock/真实API
|
||||
* AI注意: 这是唯一入口,业务代码必须调用此层
|
||||
*
|
||||
* @module services/certificateDataSource
|
||||
* @author AI-Frontend-Team
|
||||
* @created 2026-03-19
|
||||
*/
|
||||
|
||||
import { Certificate } from '@/types/certificate';
|
||||
import { IDataSource, CertificateQueryParams } from '@/types/datasource';
|
||||
|
||||
// ============================================
|
||||
// 真实API实现
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 证书API数据源
|
||||
* 调用真实后端API
|
||||
*/
|
||||
class ApiCertificateDataSource implements IDataSource<Certificate, CertificateQueryParams> {
|
||||
private baseUrl = '/api/v1/certificate';
|
||||
|
||||
async list(params?: CertificateQueryParams): Promise<Certificate[]> {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.status) query.append('status', params.status);
|
||||
if (params?.type) query.append('type', params.type);
|
||||
if (params?.keyword) query.append('keyword', params.keyword);
|
||||
if (params?.page) query.append('page', params.page.toString());
|
||||
if (params?.pageSize) query.append('pageSize', params.pageSize.toString());
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/certificates?${query.toString()}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result.data || [];
|
||||
}
|
||||
|
||||
async detail(id: string): Promise<Certificate | null> {
|
||||
const response = await fetch(`${this.baseUrl}/certificates/${id}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) return null;
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result.data || null;
|
||||
}
|
||||
|
||||
async create(data: Partial<Certificate>): Promise<Certificate> {
|
||||
const response = await fetch(`${this.baseUrl}/certificates`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 创建成功后获取完整数据
|
||||
if (result.data?.id) {
|
||||
const created = await this.detail(result.data.id);
|
||||
if (created) return created;
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<Certificate>): Promise<Certificate> {
|
||||
const response = await fetch(`${this.baseUrl}/certificates/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 更新成功后获取完整数据
|
||||
const updated = await this.detail(id);
|
||||
if (updated) return updated;
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
const response = await fetch(`${this.baseUrl}/certificates/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新证书状态(审核)
|
||||
* @param id 证书ID
|
||||
* @param status 新状态
|
||||
* @param approvedBy 审核人
|
||||
*/
|
||||
async updateStatus(id: string, status: string, approvedBy?: string): Promise<Certificate> {
|
||||
const response = await fetch(`${this.baseUrl}/certificates/${id}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ status, approvedBy }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 更新成功后获取完整数据
|
||||
const updated = await this.detail(id);
|
||||
if (updated) return updated;
|
||||
|
||||
return result.data;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Mock实现 (完全独立文件)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* [MOCK] 证书Mock数据源
|
||||
* AI注意: 这是Mock实现,不是真实业务逻辑
|
||||
* 仅在REACT_APP_USE_MOCK=true时启用
|
||||
*/
|
||||
class MockCertificateDataSource implements IDataSource<Certificate, CertificateQueryParams> {
|
||||
/** Mock标记 */
|
||||
readonly __MOCK__ = true as const;
|
||||
/** Mock数据源名称 */
|
||||
readonly __MOCK_NAME__ = 'MockCertificateDataSource';
|
||||
|
||||
private mockData: Certificate[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'CE认证证书',
|
||||
type: 'PRODUCT_CERT',
|
||||
status: 'APPROVED',
|
||||
fileUrl: '/files/ce-cert.pdf',
|
||||
fileName: 'CE-Certificate-2026.pdf',
|
||||
uploadDate: '2026-03-15',
|
||||
expiryDate: '2027-03-15',
|
||||
approvedBy: 'admin',
|
||||
approvedDate: '2026-03-16',
|
||||
productId: 'P001',
|
||||
productName: '工业温度传感器',
|
||||
notes: '欧盟市场准入认证',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '营业执照',
|
||||
type: 'BUSINESS_LICENSE',
|
||||
status: 'APPROVED',
|
||||
fileUrl: '/files/business-license.pdf',
|
||||
fileName: 'Business-License-2026.pdf',
|
||||
uploadDate: '2026-01-10',
|
||||
expiryDate: '2027-01-10',
|
||||
approvedBy: 'admin',
|
||||
approvedDate: '2026-01-11',
|
||||
notes: '企业营业执照',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'FCC认证',
|
||||
type: 'SAFETY_CERT',
|
||||
status: 'PENDING',
|
||||
fileUrl: '/files/fcc-cert.pdf',
|
||||
fileName: 'FCC-Certificate.pdf',
|
||||
uploadDate: '2026-03-18',
|
||||
expiryDate: '2027-03-18',
|
||||
productId: 'P002',
|
||||
productName: 'PLC控制器',
|
||||
notes: '美国FCC认证',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'ISO9001',
|
||||
type: 'QUALITY_CERT',
|
||||
status: 'EXPIRED',
|
||||
fileUrl: '/files/iso9001.pdf',
|
||||
fileName: 'ISO9001-2025.pdf',
|
||||
uploadDate: '2025-01-01',
|
||||
expiryDate: '2026-01-01',
|
||||
approvedBy: 'admin',
|
||||
approvedDate: '2025-01-02',
|
||||
notes: '质量管理体系认证,已过期需要更新',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'RoHS认证',
|
||||
type: 'PRODUCT_CERT',
|
||||
status: 'APPROVED',
|
||||
fileUrl: '/files/rohs-cert.pdf',
|
||||
fileName: 'RoHS-Certificate.pdf',
|
||||
uploadDate: '2026-02-20',
|
||||
expiryDate: '2027-02-20',
|
||||
approvedBy: 'admin',
|
||||
approvedDate: '2026-02-21',
|
||||
productId: 'P003',
|
||||
productName: '环保型传感器',
|
||||
notes: '欧盟RoHS环保认证',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
name: 'UL认证',
|
||||
type: 'SAFETY_CERT',
|
||||
status: 'PENDING',
|
||||
fileUrl: '/files/ul-cert.pdf',
|
||||
fileName: 'UL-Certificate.pdf',
|
||||
uploadDate: '2026-03-19',
|
||||
expiryDate: '2027-03-19',
|
||||
productId: 'P004',
|
||||
productName: '工业电源模块',
|
||||
notes: '美国UL安全认证',
|
||||
},
|
||||
];
|
||||
|
||||
private delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async list(params?: CertificateQueryParams): Promise<Certificate[]> {
|
||||
// 模拟网络延迟
|
||||
await this.delay(300);
|
||||
|
||||
let result = [...this.mockData];
|
||||
|
||||
// 状态筛选
|
||||
if (params?.status) {
|
||||
result = result.filter(item => item.status === params.status);
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
if (params?.type) {
|
||||
result = result.filter(item => item.type === params.type);
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (params?.keyword) {
|
||||
const keyword = params.keyword.toLowerCase();
|
||||
result = result.filter(
|
||||
item =>
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.productName?.toLowerCase().includes(keyword) ||
|
||||
item.notes?.toLowerCase().includes(keyword)
|
||||
);
|
||||
}
|
||||
|
||||
// 分页
|
||||
const page = params?.page || 1;
|
||||
const pageSize = params?.pageSize || 10;
|
||||
const start = (page - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
|
||||
return result.slice(start, end);
|
||||
}
|
||||
|
||||
async detail(id: string): Promise<Certificate | null> {
|
||||
await this.delay(200);
|
||||
return this.mockData.find(item => item.id === id) || null;
|
||||
}
|
||||
|
||||
async create(data: Partial<Certificate>): Promise<Certificate> {
|
||||
await this.delay(500);
|
||||
|
||||
const newCert: Certificate = {
|
||||
id: `${Date.now()}`,
|
||||
name: data.name || '',
|
||||
type: data.type || 'OTHER',
|
||||
status: 'PENDING',
|
||||
fileUrl: data.fileUrl || '/files/uploaded.pdf',
|
||||
fileName: data.fileName || 'uploaded.pdf',
|
||||
uploadDate: new Date().toISOString().split('T')[0],
|
||||
expiryDate: data.expiryDate || '',
|
||||
productId: data.productId,
|
||||
productName: data.productName,
|
||||
notes: data.notes,
|
||||
};
|
||||
|
||||
this.mockData.unshift(newCert);
|
||||
return newCert;
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<Certificate>): Promise<Certificate> {
|
||||
await this.delay(300);
|
||||
|
||||
const index = this.mockData.findIndex(item => item.id === id);
|
||||
if (index === -1) {
|
||||
throw new Error('Certificate not found');
|
||||
}
|
||||
|
||||
this.mockData[index] = { ...this.mockData[index], ...data };
|
||||
return this.mockData[index];
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.delay(200);
|
||||
this.mockData = this.mockData.filter(item => item.id !== id);
|
||||
}
|
||||
|
||||
async updateStatus(id: string, status: string, approvedBy?: string): Promise<Certificate> {
|
||||
await this.delay(300);
|
||||
|
||||
const index = this.mockData.findIndex(item => item.id === id);
|
||||
if (index === -1) {
|
||||
throw new Error('Certificate not found');
|
||||
}
|
||||
|
||||
const updates: Partial<Certificate> = {
|
||||
status: status as any,
|
||||
};
|
||||
|
||||
if (status === 'APPROVED') {
|
||||
updates.approvedBy = approvedBy || 'admin';
|
||||
updates.approvedDate = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
this.mockData[index] = { ...this.mockData[index], ...updates };
|
||||
return this.mockData[index];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 导出数据源实例 (环境变量控制)
|
||||
// ============================================
|
||||
|
||||
const useMock = process.env.REACT_APP_USE_MOCK === 'true';
|
||||
|
||||
/**
|
||||
* 证书数据源实例
|
||||
* 根据环境变量自动切换Mock/真实API
|
||||
*
|
||||
* 使用示例:
|
||||
* ```typescript
|
||||
* import { certificateDataSource } from '@/services/certificateDataSource';
|
||||
*
|
||||
* // 查询列表
|
||||
* const certificates = await certificateDataSource.list({ status: 'APPROVED' });
|
||||
*
|
||||
* // 获取详情
|
||||
* const cert = await certificateDataSource.detail('1');
|
||||
*
|
||||
* // 创建
|
||||
* const newCert = await certificateDataSource.create({ name: '新证书', ... });
|
||||
* ```
|
||||
*/
|
||||
export const certificateDataSource: IDataSource<Certificate, CertificateQueryParams> & {
|
||||
updateStatus?(id: string, status: string, approvedBy?: string): Promise<Certificate>;
|
||||
} = useMock ? new MockCertificateDataSource() : new ApiCertificateDataSource();
|
||||
|
||||
/**
|
||||
* Mock状态标记
|
||||
* 用于调试和开发环境识别
|
||||
*/
|
||||
export const __MOCK__ = useMock;
|
||||
|
||||
/**
|
||||
* 当前数据源类型
|
||||
*/
|
||||
export const __DATA_SOURCE_TYPE__ = useMock ? 'mock' : 'api';
|
||||
Reference in New Issue
Block a user