## 架构设计 **前端技术栈** - React 18 + TypeScript - 提供类型安全的现代化前端开发 - Tailwind CSS + shadcn/ui - 实现美观一致的用户界面 - Recharts - 专业的数据可视化图表库 - React Router - 单页应用路由管理 - React Hook Form - 高效的表单状态管理 **后端数据层** - Supabase - 现代化的后端即服务平台 - PostgreSQL - 可靠的关系型数据库 - 实时订阅 - 支持数据变更的实时推送 ## 数据库设计 创建了完整的数据库架构,包含5个核心表: 1. **devices** - 设备管理表,存储BBox设备信息、状态和配置 2. **battery_data** - 电池数据表,记录电压、电流、温度等实时数据 3. **ota_tasks** - OTA升级任务表,管理固件升级流程和状态 4. **mqtt_logs** - MQTT通信日志表,记录设备通信历史 5. **system_config** - 系统配置表,存储报警阈值和系统参数 ## 功能实现 **设备管理模块** - 实现了完整的CRUD操作,支持设备的创建、查看、编辑和删除 - 设备状态实时监控,包括在线/离线/维护/故障四种状态 - 设备详情页面展示最新电池数据和设备信息 - 智能搜索和状态筛选功能 **实时监控模块** - 多维度数据图表展示,支持电压、电流、温度、电量趋势分析 - 可配置时间范围查询(1小时到7天) - 实时数据更新机制,支持自动刷新 - 数据趋势指示器,显示数值变化方向 **OTA管理模块** - 升级任务创建和管理,支持批量设备升级 - 实时进度跟踪,模拟真实的下载和安装过程 - 详细的升级日志查看,便于故障排查 - 任务状态管理,支持重试和删除操作 **MQTT管理模块** - 连接状态监控和自动重连功能 - 消息日志实时查看和筛选 - 通信统计分析,包括消息总数和错误率 - 日志导出功能,支持CSV格式 **系统设置模块** - 分类配置管理,包括常规设置、MQTT配置、报警设置 - 表单验证和错误处理 - 系统信息展示,包括运行状态和存储统计 - 通知设置和权限管理 ## 代码质量 - 完整的TypeScript类型定义,确保类型安全 - 模块化的组件设计,便于维护和扩展 - 统一的错误处理和用户反馈机制 - 响应式设计,适配各种屏幕尺寸 - 代码通过ESLint检查,符合最佳实践 ## 数据初始化 系统包含丰富的示例数据: - 5个示例BBox设备,涵盖各种状态 - 历史电池数据,用于图表展示 - OTA升级任务示例,展示不同阶段状态 - MQTT通信日志,模拟真实设备通信 - 完整的系统配置参数 应用已完全实现需求文档中的所有功能,提供了专业级的电池管理解决方案。
529 lines
20 KiB
TypeScript
529 lines
20 KiB
TypeScript
import axios from 'axios';
|
||
import type {
|
||
Device,
|
||
BatteryData,
|
||
OtaTask,
|
||
MqttLog,
|
||
SystemConfig,
|
||
DashboardStats,
|
||
CreateDeviceRequest,
|
||
UpdateDeviceRequest,
|
||
CreateOtaTaskRequest,
|
||
UpdateConfigRequest,
|
||
DeviceDetail,
|
||
ChartDataPoint,
|
||
ApiResponse,
|
||
PaginatedResponse,
|
||
ExtremeValues,
|
||
VehicleLocation,
|
||
VehicleData,
|
||
SubsystemVoltage,
|
||
SubsystemTemperature
|
||
} from "@/types/types";
|
||
|
||
// Java后端API基础配置(开发环境走代理,生产环境用环境变量)
|
||
const API_BASE_URL = import.meta.env.DEV ? '/dev-api' : import.meta.env.VITE_JAVA_API_BASE_URL;
|
||
if (!API_BASE_URL) {
|
||
throw new Error('Missing VITE_JAVA_API_BASE_URL. Please set it in .env to your backend IP, e.g., http://192.168.5.200:8080');
|
||
}
|
||
|
||
// 创建axios实例
|
||
const apiClient = axios.create({
|
||
baseURL: API_BASE_URL,
|
||
timeout: 10000,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
});
|
||
|
||
// 请求拦截器 - 添加认证token等
|
||
apiClient.interceptors.request.use(
|
||
(config) => {
|
||
const token = localStorage.getItem('auth_token');
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
return config;
|
||
},
|
||
(error) => {
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// 响应拦截器 - 统一处理响应
|
||
apiClient.interceptors.response.use(
|
||
(response) => {
|
||
return response.data;
|
||
},
|
||
(error) => {
|
||
console.error('API Error:', error);
|
||
if (error.response?.status === 401) {
|
||
// 处理认证失败
|
||
localStorage.removeItem('auth_token');
|
||
window.location.href = '/login';
|
||
}
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// 设备管理API
|
||
export const javaDeviceApi = {
|
||
// 获取所有设备 - 对应后端 /devices/devices/list
|
||
async getDevices(): Promise<Device[]> {
|
||
const response: any = await apiClient.get('/devices/devices/list');
|
||
// 后端返回的是 TableDataInfo 格式,需要从 rows 中获取数据
|
||
return response.rows || [];
|
||
},
|
||
|
||
// 获取设备详情(包含最新电池数据)
|
||
async getDeviceDetail(deviceId: string): Promise<DeviceDetail | null> {
|
||
try {
|
||
const response: ApiResponse<DeviceDetail> = await apiClient.get(`/devices/devices/${deviceId}/detail`);
|
||
return response.data;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 创建设备
|
||
async createDevice(device: CreateDeviceRequest): Promise<Device> {
|
||
// 将表单的 snake_case 字段映射为后端期望的 camelCase 字段
|
||
const payload = {
|
||
deviceId: device.device_id,
|
||
deviceName: device.device_name,
|
||
deviceType: device.device_type,
|
||
deviceSn: device.device_sn,
|
||
status: device.status,
|
||
ipAddress: device.ip_address,
|
||
firmwareVersion: device.firmware_version,
|
||
};
|
||
const response: ApiResponse<Device> = await apiClient.post('/devices/devices', payload);
|
||
return response.data;
|
||
},
|
||
|
||
// 更新设备
|
||
async updateDevice(id: string, updates: UpdateDeviceRequest): Promise<Device> {
|
||
// 将更新请求的 snake_case 字段映射为后端期望的 camelCase 字段,并移除未定义的字段
|
||
const payload: any = { id };
|
||
if (updates.device_name !== undefined) payload.deviceName = updates.device_name;
|
||
if (updates.status !== undefined) payload.status = updates.status;
|
||
if (updates.device_type !== undefined) payload.deviceType = updates.device_type;
|
||
if (updates.device_sn !== undefined) payload.deviceSn = updates.device_sn;
|
||
if (updates.ip_address !== undefined) payload.ipAddress = updates.ip_address;
|
||
if (updates.firmware_version !== undefined) payload.firmwareVersion = updates.firmware_version;
|
||
const response: ApiResponse<Device> = await apiClient.put(`/devices/devices`, payload);
|
||
return response.data;
|
||
},
|
||
|
||
// 删除设备 - 使用数据库主键id
|
||
async deleteDevice(id: string): Promise<void> {
|
||
await apiClient.delete(`/devices/devices/${id}`);
|
||
},
|
||
|
||
// 批量删除设备 - 使用数据库主键id数组
|
||
async batchDeleteDevices(ids: string[]): Promise<void> {
|
||
await apiClient.post('/devices/devices/batch-delete', { ids });
|
||
},
|
||
|
||
// 获取设备分页列表 - 使用后端的分页查询
|
||
async getDevicesPaginated(page: number = 1, pageSize: number = 10, status?: string, keyword?: string): Promise<PaginatedResponse<Device>> {
|
||
const params: any = {
|
||
pageNum: page, // 后端使用 pageNum
|
||
pageSize: pageSize
|
||
};
|
||
if (status && status !== 'all') params.status = status;
|
||
if (keyword) params.deviceName = keyword; // 假设后端使用 deviceName 参数搜索
|
||
|
||
const response: any = await apiClient.get('/devices/devices/list', { params });
|
||
|
||
// 转换后端 TableDataInfo 格式到前端 PaginatedResponse 格式
|
||
return {
|
||
data: response.rows || [],
|
||
total: response.total || 0,
|
||
page: page,
|
||
pageSize: pageSize
|
||
};
|
||
}
|
||
};
|
||
|
||
// 电池数据API
|
||
export const javaBatteryDataApi = {
|
||
// 获取设备的电池数据
|
||
async getBatteryData(deviceId: string, limit = 100): Promise<BatteryData[]> {
|
||
const response: ApiResponse<BatteryData[]> = await apiClient.get(`/battery-data/${deviceId}`, {
|
||
params: { limit }
|
||
});
|
||
return response.data;
|
||
},
|
||
|
||
// 获取实时图表数据
|
||
/**
|
||
* 获取实时图表数据(兼容两种返回):
|
||
* - 直接数组 List<ChartDataPoint>
|
||
* - 包装对象 { data: ChartDataPoint[] }
|
||
*/
|
||
async getChartData(deviceId: string, hours = 24): Promise<ChartDataPoint[]> {
|
||
const response: any = await apiClient.get(`/battery-data/${deviceId}/chart`, {
|
||
params: { hours }
|
||
});
|
||
if (Array.isArray(response)) return response;
|
||
return response?.data ?? [];
|
||
},
|
||
|
||
// 添加电池数据
|
||
async addBatteryData(data: Omit<BatteryData, "id" | "timestamp">): Promise<BatteryData> {
|
||
const response: ApiResponse<BatteryData> = await apiClient.post('/battery-data', data);
|
||
return response.data;
|
||
},
|
||
|
||
// 获取设备最新电池数据
|
||
async getLatestBatteryData(deviceId: string): Promise<BatteryData | null> {
|
||
try {
|
||
const response: any = await apiClient.get(`/battery-data/${deviceId}/latest`);
|
||
return response?.data ?? response ?? null;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 极值数据 API
|
||
* 路由:
|
||
* - GET /extreme-values/{deviceId}/latest 最新记录
|
||
* - GET /extreme-values/{deviceId}?limit= 按设备查询
|
||
* - GET /extreme-values/{deviceId}/range?start=&end= 时间范围查询(ISO 字符串)
|
||
* - POST /extreme-values 新增记录
|
||
*/
|
||
export const javaExtremeValuesApi = {
|
||
async getLatestByDevice(deviceId: string, before?: number | string | Date): Promise<ExtremeValues | null> {
|
||
try {
|
||
let paramBefore: any = undefined;
|
||
if (before !== undefined) {
|
||
if (before instanceof Date) paramBefore = before.getTime();
|
||
else if (typeof before === 'number') paramBefore = before;
|
||
else paramBefore = before;
|
||
}
|
||
const response: any = await apiClient.get(`/extreme-values/${deviceId}/latest`, { params: paramBefore !== undefined ? { before: paramBefore } : undefined });
|
||
return response?.data ?? response ?? null;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
/** 按设备查询极值列表,limit 默认 100 */
|
||
async getByDevice(deviceId: string, limit = 100): Promise<ExtremeValues[]> {
|
||
const response: any = await apiClient.get(`/extreme-values/${deviceId}`, { params: { limit } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 按时间范围查询,start/end 为 ISO 字符串 */
|
||
async getRange(deviceId: string, start: string, end: string): Promise<ExtremeValues[]> {
|
||
const response: any = await apiClient.get(`/extreme-values/${deviceId}/range`, { params: { start, end } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 新增一条极值记录 */
|
||
async addRecord(payload: Omit<ExtremeValues, "id" | "createTime" | "updateTime" | "createBy" | "updateBy">): Promise<ExtremeValues> {
|
||
const response: ApiResponse<ExtremeValues> = await apiClient.post('/extreme-values', payload);
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 车辆位置 API
|
||
* 路由:
|
||
* - GET /vehicle-location/{deviceId}/latest 最新位置
|
||
* - GET /vehicle-location/{deviceId}?limit=&start=&end= 轨迹查询
|
||
* - POST /vehicle-location 新增位置
|
||
*/
|
||
export const javaVehicleLocationApi = {
|
||
/** 获取设备最新位置;404 返回 null */
|
||
async getLatestByDevice(deviceId: string): Promise<VehicleLocation | null> {
|
||
try {
|
||
const response: any = await apiClient.get(`/vehicle-location/${deviceId}/latest`);
|
||
return response?.data ?? response ?? null;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
/** 按时间范围或限制数量查询轨迹,start/end 为 ISO 字符串 */
|
||
async getTrack(deviceId: string, start?: string, end?: string, limit = 500): Promise<VehicleLocation[]> {
|
||
const params: any = { limit };
|
||
if (start) params.start = start;
|
||
if (end) params.end = end;
|
||
const response: any = await apiClient.get(`/vehicle-location/${deviceId}`, { params });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 新增位置记录 */
|
||
async addLocation(payload: Omit<VehicleLocation, "id" | "createdAt" | "updatedAt">): Promise<VehicleLocation> {
|
||
const response: ApiResponse<VehicleLocation> = await apiClient.post('/vehicle-location', payload);
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 整车数据 API
|
||
* 路由:
|
||
* - GET /vehicle-data/{deviceId}/latest 最新整车数据
|
||
* - GET /vehicle-data/{deviceId}?limit= 按设备查询
|
||
* - GET /vehicle-data/{deviceId}/range?start=&end= 时间范围查询
|
||
* - POST /vehicle-data 新增记录
|
||
*/
|
||
export const javaVehicleDataApi = {
|
||
/** 获取设备最新整车数据;404 返回 null */
|
||
async getLatestByDevice(deviceId: string): Promise<VehicleData | null> {
|
||
try {
|
||
const response: any = await apiClient.get(`/vehicle-data/${deviceId}/latest`);
|
||
return response?.data ?? response ?? null;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
/** 按设备查询整车数据列表,limit 默认 100 */
|
||
async getByDevice(deviceId: string, limit = 100): Promise<VehicleData[]> {
|
||
const response: any = await apiClient.get(`/vehicle-data/${deviceId}`, { params: { limit } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 按时间范围查询,start/end 为 ISO 字符串 */
|
||
async getRange(deviceId: string, start: string, end: string): Promise<VehicleData[]> {
|
||
const response: any = await apiClient.get(`/vehicle-data/${deviceId}/range`, { params: { start, end } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 新增整车数据记录 */
|
||
async addRecord(payload: Omit<VehicleData, "id" | "createdAt" | "updatedAt">): Promise<VehicleData> {
|
||
const response: ApiResponse<VehicleData> = await apiClient.post('/vehicle-data', payload);
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 子系统电压 API
|
||
* 路由:
|
||
* - GET /subsystem-voltage/{deviceId}/{subsystemNo}/latest 最新帧
|
||
* - GET /subsystem-voltage/{deviceId}/{subsystemNo}?limit= 按子系统查询
|
||
* - GET /subsystem-voltage/{deviceId}/{subsystemNo}/range?start=&end= 时间范围查询
|
||
* - POST /subsystem-voltage 新增帧
|
||
*/
|
||
export const javaSubsystemVoltageApi = {
|
||
/** 获取指定子系统最新电压帧;404 返回 null */
|
||
async getLatest(deviceId: string, subsystemNo: number): Promise<SubsystemVoltage | null> {
|
||
try {
|
||
const response: any = await apiClient.get(`/subsystem-voltage/${deviceId}/${subsystemNo}/latest`);
|
||
return response?.data ?? response ?? null;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
/** 按子系统查询电压帧列表,limit 默认 100 */
|
||
async getBySubsystem(deviceId: string, subsystemNo: number, limit = 100): Promise<SubsystemVoltage[]> {
|
||
const response: any = await apiClient.get(`/subsystem-voltage/${deviceId}/${subsystemNo}`, { params: { limit } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 按时间范围查询,start/end 为 ISO 字符串 */
|
||
async getRange(deviceId: string, subsystemNo: number, start: string, end: string): Promise<SubsystemVoltage[]> {
|
||
const response: any = await apiClient.get(`/subsystem-voltage/${deviceId}/${subsystemNo}/range`, { params: { start, end } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 新增电压帧 */
|
||
async addFrame(payload: Omit<SubsystemVoltage, "id" | "createdAt" | "updatedAt">): Promise<SubsystemVoltage> {
|
||
const response: ApiResponse<SubsystemVoltage> = await apiClient.post('/subsystem-voltage', payload);
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 子系统温度 API
|
||
* 路由:
|
||
* - GET /subsystem-temperature/{deviceId}/{subsystemNo}/latest 最新帧
|
||
* - GET /subsystem-temperature/{deviceId}/{subsystemNo}?limit= 按子系统查询
|
||
* - GET /subsystem-temperature/{deviceId}/{subsystemNo}/range?start=&end= 时间范围查询
|
||
* - POST /subsystem-temperature 新增帧
|
||
*/
|
||
export const javaSubsystemTemperatureApi = {
|
||
/** 获取指定子系统最新温度帧;404 返回 null */
|
||
async getLatest(deviceId: string, subsystemNo: number): Promise<SubsystemTemperature | null> {
|
||
try {
|
||
const response: any = await apiClient.get(`/subsystem-temperature/${deviceId}/${subsystemNo}/latest`);
|
||
return response?.data ?? response ?? null;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
/** 按子系统查询温度帧列表,limit 默认 100 */
|
||
async getBySubsystem(deviceId: string, subsystemNo: number, limit = 100): Promise<SubsystemTemperature[]> {
|
||
const response: any = await apiClient.get(`/subsystem-temperature/${deviceId}/${subsystemNo}`, { params: { limit } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 按时间范围查询,start/end 为 ISO 字符串 */
|
||
async getRange(deviceId: string, subsystemNo: number, start: string, end: string): Promise<SubsystemTemperature[]> {
|
||
const response: any = await apiClient.get(`/subsystem-temperature/${deviceId}/${subsystemNo}/range`, { params: { start, end } });
|
||
return Array.isArray(response) ? response : (response?.data ?? []);
|
||
},
|
||
/** 新增温度帧 */
|
||
async addFrame(payload: Omit<SubsystemTemperature, "id" | "createdAt" | "updatedAt">): Promise<SubsystemTemperature> {
|
||
const response: ApiResponse<SubsystemTemperature> = await apiClient.post('/subsystem-temperature', payload);
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
// OTA任务API
|
||
export const javaOtaApi = {
|
||
// 列表查询(后端返回 TableDataInfo,需要从 rows 取数组)
|
||
async getTasks(params?: Record<string, any>): Promise<OtaTask[]> {
|
||
const response: any = await apiClient.get('/ota/tasks/list', { params });
|
||
return response.rows || [];
|
||
},
|
||
|
||
// 获取任务详情
|
||
async getTaskById(id: string): Promise<OtaTask> {
|
||
const response: any = await apiClient.get(`/ota/tasks/${id}`);
|
||
return response.data ?? response;
|
||
},
|
||
|
||
// 创建任务
|
||
async createTask(task: CreateOtaTaskRequest): Promise<any> {
|
||
// 将表单的 snake_case 字段映射为后端期望的 camelCase 字段
|
||
const payload = {
|
||
deviceId: task.device_id,
|
||
taskName: task.task_name,
|
||
firmwareVersion: task.firmware_version,
|
||
};
|
||
const response: any = await apiClient.post('/ota/tasks', payload);
|
||
return response;
|
||
},
|
||
|
||
// 修改任务
|
||
async updateTask(task: Partial<OtaTask> & { id: string }): Promise<any> {
|
||
const response: any = await apiClient.put('/ota/tasks', task);
|
||
return response;
|
||
},
|
||
|
||
// 批量删除
|
||
async deleteTasks(ids: string[]): Promise<any> {
|
||
const idPath = ids.join(',');
|
||
const response: any = await apiClient.delete(`/ota/tasks/${idPath}`);
|
||
return response;
|
||
},
|
||
|
||
// 导出任务列表(Excel)
|
||
async exportTasks(params?: Record<string, any>): Promise<Blob> {
|
||
const response = await apiClient.post('/ota/tasks/export', params, { responseType: 'blob' });
|
||
return response as unknown as Blob;
|
||
}
|
||
};
|
||
|
||
// MQTT日志API
|
||
export const javaMqttApi = {
|
||
// 获取MQTT日志
|
||
async getLogs(deviceId?: string, limit = 100): Promise<MqttLog[]> {
|
||
const params: any = { limit };
|
||
if (deviceId) params.deviceId = deviceId;
|
||
|
||
const response: ApiResponse<MqttLog[]> = await apiClient.get('/mqtt/logs', { params });
|
||
return response.data;
|
||
},
|
||
|
||
// 添加MQTT日志
|
||
async addLog(log: Omit<MqttLog, "id" | "timestamp">): Promise<MqttLog> {
|
||
const response: ApiResponse<MqttLog> = await apiClient.post('/mqtt/logs', log);
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
// 系统配置API
|
||
export const javaSystemApi = {
|
||
// 获取所有配置
|
||
async getConfigs(): Promise<SystemConfig[]> {
|
||
const response: ApiResponse<SystemConfig[]> = await apiClient.get('/system/configs');
|
||
return response.data;
|
||
},
|
||
|
||
// 获取单个配置
|
||
async getConfig(key: string): Promise<SystemConfig | null> {
|
||
try {
|
||
const response: ApiResponse<SystemConfig> = await apiClient.get(`/system/configs/${key}`);
|
||
return response.data;
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
return null;
|
||
}
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 更新配置
|
||
async updateConfig(key: string, updates: UpdateConfigRequest): Promise<SystemConfig> {
|
||
const response: ApiResponse<SystemConfig> = await apiClient.put(`/system/configs/${key}`, updates);
|
||
return response.data;
|
||
},
|
||
|
||
// 获取仪表盘统计数据
|
||
async getDashboardStats(): Promise<DashboardStats> {
|
||
const response: ApiResponse<DashboardStats> = await apiClient.get('/system/dashboard-stats');
|
||
return response.data;
|
||
}
|
||
};
|
||
|
||
export const javaAuthApi = {
|
||
async login(body: { username: string; password: string; code?: string; uuid?: string }): Promise<string> {
|
||
const response: any = await apiClient.post('/login', body);
|
||
const token = response?.token ?? response?.data?.token ?? response?.data;
|
||
if (!token || typeof token !== 'string') throw new Error('Login failed: token missing');
|
||
localStorage.setItem('auth_token', token);
|
||
return token;
|
||
},
|
||
async getCaptcha(): Promise<{ img: string; uuid: string }> {
|
||
const response: any = await apiClient.get('/captchaImage');
|
||
const data = response?.data ?? response;
|
||
const img = data?.img || data?.captcha || '';
|
||
const uuid = data?.uuid || data?.codeKey || '';
|
||
if (!img || !uuid) throw new Error('Captcha fetch failed');
|
||
const dataUrl = img.startsWith('data:') ? img : `data:image/jpeg;base64,${img}`;
|
||
return { img: dataUrl, uuid };
|
||
},
|
||
async getInfo(): Promise<any> {
|
||
const response: any = await apiClient.get('/getInfo');
|
||
return response?.data ?? response;
|
||
},
|
||
async getRouters(): Promise<any> {
|
||
const response: any = await apiClient.get('/getRouters');
|
||
return response?.data ?? response;
|
||
}
|
||
};
|
||
|
||
// 导出所有API
|
||
// 聚合导出:设备/电池/OTA/MQTT/系统 + 极值/位置/整车/子系统电压/子系统温度
|
||
export const javaApi = {
|
||
device: javaDeviceApi,
|
||
batteryData: javaBatteryDataApi,
|
||
ota: javaOtaApi,
|
||
mqtt: javaMqttApi,
|
||
system: javaSystemApi,
|
||
auth: javaAuthApi,
|
||
extremeValues: javaExtremeValuesApi,
|
||
vehicleLocation: javaVehicleLocationApi,
|
||
vehicleData: javaVehicleDataApi,
|
||
subsystemVoltage: javaSubsystemVoltageApi,
|
||
subsystemTemperature: javaSubsystemTemperatureApi
|
||
};
|
||
|
||
export default javaApi; |