* !44 comment
* !39 添加下行日志打印
* !36 扩展计价领域模型
* !35 webui 初步成型
* !34 webui 初步成型
This commit is contained in:
三丙
2025-09-09 08:23:59 +00:00
parent 921045af8f
commit 58580ca11e
372 changed files with 37900 additions and 1206 deletions

View File

@@ -0,0 +1,176 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
import axios, {AxiosError, AxiosResponse} from 'axios';
import {message} from 'antd';
// 创建axios实例
const api = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080',
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Accept': 'application/json;charset=UTF-8'
}
});
// 请求拦截器
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
console.log('Request interceptor - Token:', token ? 'exists' : 'missing'); // 调试日志
if (token) {
config.headers.Authorization = `Bearer ${token}`;
console.log('Authorization header set:', config.headers.Authorization); // 调试日志
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 根据HTTP状态码返回友好的错误提示
const getErrorMessage = (error: AxiosError): string => {
if (!error.response) {
return '网络连接失败,请检查网络连接';
}
const status = error.response.status;
const data = error.response.data as any;
// 优先使用ApiResponse格式的错误信息
const apiErrorMessage = data?.message;
const errorCode = data?.errorCode;
// 根据错误码提供特殊处理
if (errorCode) {
switch (errorCode) {
// 通用错误码
case 'BUSINESS_ERROR':
return apiErrorMessage || '业务处理失败';
case 'SYSTEM_ERROR':
return apiErrorMessage || '系统错误';
// 参数校验相关
case 'VALIDATION_ERROR':
return apiErrorMessage || '数据验证失败';
case 'BINDING_ERROR':
return apiErrorMessage || '数据绑定失败';
case 'ILLEGAL_ARGUMENT':
return apiErrorMessage || '参数错误';
case 'ILLEGAL_STATE':
return apiErrorMessage || '状态错误';
// 认证授权相关
case 'UNAUTHORIZED':
return apiErrorMessage || '用户未认证';
case 'AUTH_FAILED':
return apiErrorMessage || '用户名或密码错误';
case 'JWT_AUTH_FAILED':
return apiErrorMessage || 'Token认证失败';
case 'FORBIDDEN':
return apiErrorMessage || '权限不足';
// 资源相关
case 'NOT_FOUND':
return apiErrorMessage || '资源不存在';
case 'CONFLICT':
return apiErrorMessage || '资源冲突';
// 业务特定错误码
case 'PILE_CODE_EXISTS':
return apiErrorMessage || '充电桩编码已存在';
case 'STATION_NAME_EXISTS':
return apiErrorMessage || '充电站名称已存在';
case 'GUN_CODE_EXISTS':
return apiErrorMessage || '充电枪编号已存在';
case 'PILE_NOT_FOUND':
return apiErrorMessage || '充电桩不存在';
case 'STATION_NOT_FOUND':
return apiErrorMessage || '充电站不存在';
case 'GUN_NOT_FOUND':
return apiErrorMessage || '充电枪不存在';
default:
// 对于未知错误码,继续使用消息内容
break;
}
}
// 根据HTTP状态码提供后备错误信息
switch (status) {
case 400:
return apiErrorMessage || '请求参数错误,请检查输入信息';
case 401:
return apiErrorMessage || '未授权,请重新登录';
case 403:
return apiErrorMessage || '没有权限执行此操作';
case 404:
return apiErrorMessage || '请求的资源不存在';
case 409:
return apiErrorMessage || '数据冲突,请刷新后重试';
case 422:
return apiErrorMessage || '数据验证失败,请检查输入信息';
case 500:
return apiErrorMessage || '服务器内部错误,请稍后重试';
case 502:
return '服务器网关错误,请稍后重试';
case 503:
return '服务不可用,请稍后重试';
case 504:
return '服务器响应超时,请稍后重试';
default:
return apiErrorMessage || `操作失败(状态码: ${status}`;
}
};
// 响应拦截器
api.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
(error: AxiosError) => {
if (error.response) {
const { status } = error.response;
const config = error.config;
const data = error.response.data as any;
const apiErrorMessage = data?.message;
// const errorCode = data?.errorCode; // 暂时不在拦截器中使用errorCode
// 如果是登录接口的401错误不进行全局处理让组件自己处理
if (status === 401 && config?.url?.includes('/api/auth/login')) {
return Promise.reject(error);
}
switch (status) {
case 401:
message.error(apiErrorMessage || '未授权,请重新登录');
localStorage.removeItem('token');
window.location.href = '/login';
break;
case 403:
message.error(apiErrorMessage || '没有权限访问');
break;
case 404:
message.error(apiErrorMessage || '请求的资源不存在');
break;
default:
// 对于其他错误包括500不在拦截器中显示消息让组件自己处理
break;
}
} else if (error.request) {
message.error('网络错误,请检查网络连接');
} else {
message.error('请求配置错误');
}
return Promise.reject(error);
}
);
export { getErrorMessage, api };
export default api;

View File

@@ -0,0 +1,69 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
import {api} from './api';
/**
* 总览统计
*/
export interface Overview {
totalStations: number; // 总充电站数
totalPiles: number; // 总充电桩数
totalGuns: number; // 总充电枪数
}
/**
* 充电桩在线状态分布
*/
export interface PileStatusDistribution {
onlinePiles: number; // 在线充电桩数
offlinePiles: number; // 离线充电桩数
totalPiles: number; // 总充电桩数
onlinePercentage: number; // 在线百分比
offlinePercentage: number; // 离线百分比
}
/**
* 充电枪运行状态分布
*/
export interface GunStatusDistribution {
idleGuns: number; // 空闲 (IDLE)
insertedGuns: number; // 已插枪未充电 (INSERTED)
chargingGuns: number; // 充电中 (CHARGING)
chargeCompleteGuns: number; // 充电完成 (CHARGE_COMPLETE)
dischargeReadyGuns: number; // 放电准备 (DISCHARGE_READY)
dischargingGuns: number; // 放电中 (DISCHARGING)
dischargeCompleteGuns: number; // 放电完成 (DISCHARGE_COMPLETE)
reservedGuns: number; // 预约 (RESERVED)
faultGuns: number; // 故障 (FAULT)
totalGuns: number; // 总充电枪数
idlePercentage: number; // 空闲百分比
insertedPercentage: number; // 已插枪百分比
chargingPercentage: number; // 充电中百分比
chargeCompletePercentage: number; // 充电完成百分比
dischargeReadyPercentage: number; // 放电准备百分比
dischargingPercentage: number; // 放电中百分比
dischargeCompletePercentage: number; // 放电完成百分比
reservedPercentage: number; // 预约百分比
faultPercentage: number; // 故障百分比
}
/**
* 仪表盘统计数据
*/
export interface DashboardStats {
overview: Overview;
pileStatusDistribution: PileStatusDistribution;
gunStatusDistribution: GunStatusDistribution;
}
/**
* 获取仪表盘统计数据
*/
export const getDashboardStats = async (): Promise<DashboardStats> => {
const response = await api.get('/api/dashboard/stats');
return response.data.data;
};

View File

@@ -0,0 +1,33 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
import {api} from './api';
import type {Gun, GunCreateRequest, PageResponse, QueryParams} from '../types';
export const getGuns = async (params: QueryParams): Promise<PageResponse<Gun>> => {
const response = await api.get('/api/guns', { params });
return response.data.data;
};
export const createGun = async (data: GunCreateRequest): Promise<Gun> => {
const response = await api.post('/api/guns', data);
return response.data.data;
};
export const updateGun = async (id: string, data: Partial<Gun>): Promise<Gun> => {
const response = await api.put(`/api/guns/${id}`, data);
return response.data.data;
};
export const deleteGun = async (id: string): Promise<void> => {
await api.delete(`/api/guns/${id}`);
};
export const getGun = async (id: string): Promise<Gun> => {
const response = await api.get(`/api/guns/${id}`);
return response.data.data;
};

View File

@@ -0,0 +1,71 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
import api from './api';
import {
ApiResponse,
PageResponse,
Pile,
PileCreateRequest,
PileOption,
PileQueryRequest,
PileUpdateRequest
} from '../types';
// 增强的API响应类型包含HTTP状态码
export interface EnhancedApiResponse<T> extends ApiResponse<T> {
httpStatus: number;
}
// 充电桩相关API
export const pileService = {
// 分页查询充电桩
async getPiles(params: PileQueryRequest): Promise<ApiResponse<PageResponse<Pile>>> {
console.log('🔍 前端发送的查询参数:', params); // 添加调试日志
const response = await api.get('/api/piles', { params });
console.log('📡 后端返回的响应:', response.data); // 添加调试日志
return response.data;
},
// 根据ID获取充电桩详情
async getPile(id: string): Promise<ApiResponse<Pile>> {
const response = await api.get(`/api/piles/${id}`);
return response.data;
},
// 创建充电桩
async createPile(data: PileCreateRequest): Promise<EnhancedApiResponse<Pile>> {
const response = await api.post('/api/piles', data);
return {
...response.data,
httpStatus: response.status
};
},
// 更新充电桩
async updatePile(id: string, data: PileUpdateRequest): Promise<EnhancedApiResponse<Pile>> {
const response = await api.put(`/api/piles/${id}`, data);
return {
...response.data,
httpStatus: response.status
};
},
// 删除充电桩
async deletePile(id: string): Promise<EnhancedApiResponse<void>> {
const response = await api.delete(`/api/piles/${id}`);
return {
...response.data,
httpStatus: response.status
};
},
// 获取充电桩选项列表
async getPileOptions(): Promise<ApiResponse<PileOption[]>> {
const response = await api.get('/api/piles/options');
return response.data;
}
};

View File

@@ -0,0 +1,22 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
import {api} from './api';
// 协议选项接口
export interface ProtocolOption {
value: string; // 协议标识符
label: string; // 显示名称
}
/**
* 获取所有支持的协议列表
* @returns 协议选项列表
*/
export const getSupportedProtocols = async (): Promise<ProtocolOption[]> => {
const response = await api.get('/api/protocols/supported');
return response.data.data || [];
};

View File

@@ -0,0 +1,42 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
import {api} from './api';
import type {PageResponse, QueryParams, Station, StationOption, StationSearchRequest} from '../types';
export const getStations = async (params: QueryParams): Promise<PageResponse<Station>> => {
const response = await api.get('/api/stations', { params });
return response.data.data;
};
export const createStation = async (data: Partial<Station>): Promise<Station> => {
const response = await api.post('/api/stations', data);
return response.data.data;
};
export const updateStation = async (id: string, data: Partial<Station>): Promise<Station> => {
const response = await api.put(`/api/stations/${id}`, data);
return response.data.data;
};
export const deleteStation = async (id: string): Promise<void> => {
await api.delete(`/api/stations/${id}`);
};
export const getStation = async (id: string): Promise<Station> => {
const response = await api.get(`/api/stations/${id}`);
return response.data.data;
};
export const searchStationOptions = async (params: StationSearchRequest): Promise<StationOption[]> => {
const response = await api.get('/api/stations/search', { params });
return response.data.data;
};
export const getStationOptions = async (): Promise<StationOption[]> => {
const response = await api.get('/api/stations/options');
return response.data.data;
};