This commit is contained in:
ChuXun
2025-10-25 19:18:43 +08:00
parent 4ce487588a
commit 02a830145e
3971 changed files with 1549956 additions and 2 deletions

View File

@@ -0,0 +1,99 @@
// @/api/auth.ts
import apiClient from './index';
import type {
LoginRequest,
LoginResponse,
SignUpRequest,
ResetPasswordWithCodeRequest
} from './types';
/**
* 用户登录(使用 Fetch API
* @param credentials - 登录凭据 (邮箱和密码)
* @returns Promise<LoginResponse>
*/
export const loginWithFetch = async (credentials: LoginRequest): Promise<LoginResponse> => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if (!response.ok) {
let errorMessage = `HTTP error ${response.status}: ${response.statusText}`;
const responseText = await response.text();
try {
const errorBody = JSON.parse(responseText);
if (errorBody && errorBody.message) {
errorMessage = errorBody.message;
} else if (responseText) {
errorMessage = responseText;
}
} catch (e) {
if (responseText) {
errorMessage = responseText;
}
}
const error: any = new Error(errorMessage);
error.status = response.status;
throw error;
}
// If response is OK, we need to parse it as JSON.
// The stream hasn't been read if response.ok is true.
const data = await response.json();
return data;
};
/**
* 用户登录
* @param credentials - 登录凭据 (邮箱和密码)
* @returns Promise<AxiosResponse<LoginResponse>>
*/
export const login = (credentials: LoginRequest) => {
return apiClient.post<LoginResponse>('/auth/login', credentials);
};
/**
* 用户注册
* @param data - 注册所需信息
*/
export const signup = (data: SignUpRequest) => {
return apiClient.post('/auth/signup', data);
};
/**
* 发送【注册】验证码
* @param email - 目标邮箱
*/
export const sendVerificationCode = (email: string) => {
return apiClient.post(`/auth/send-verification-code?email=${email}`);
};
/**
* 发送【密码重置】验证码
* @param email - 目标邮箱
*/
export const sendPasswordResetCode = (email: string) => {
return apiClient.post(`/auth/send-password-reset-code?email=${email}`);
};
/**
* 使用验证码重置密码
* @param data - 包含邮箱、验证码和新密码的对象
*/
export const resetPasswordWithCode = (data: ResetPasswordWithCodeRequest) => {
return apiClient.post('/auth/reset-password-with-code', data);
};
/**
* 用户登出
* 通知后端记录登出操作
*/
export const logout = () => {
return apiClient.post('/auth/logout');
};
// ... 其他认证相关的API调用

View File

@@ -0,0 +1,147 @@
import apiClient from './index';
import type {
DashboardStatsDTO,
AqiDistributionDTO,
TaskStatsDTO,
TrendDataPointDTO,
GridCoverageDTO,
HeatmapPointDTO,
PollutionStatsDTO,
AqiHeatmapPointDTO,
PollutantThresholdDTO,
Grid
} from './types';
/**
* Fetches the main dashboard statistics.
* @returns A promise that resolves to the dashboard stats.
*/
export const getDashboardStats = (): Promise<DashboardStatsDTO> => {
return apiClient.get('/dashboard/stats');
};
/**
* Fetches the AQI level distribution.
* @returns A promise that resolves to an array of AQI distribution data.
*/
export const getAqiDistribution = (): Promise<AqiDistributionDTO[]> => {
return apiClient.get('/dashboard/reports/aqi-distribution');
};
/**
* Fetches the monthly trend of exceedance events.
* @returns A promise that resolves to an array of trend data points.
*/
export const getMonthlyExceedanceTrend = (): Promise<TrendDataPointDTO[]> => {
return apiClient.get('/dashboard/reports/monthly-exceedance-trend')
.then(data => {
console.log('原始趋势数据:', data);
// 确保数据格式正确
if (Array.isArray(data)) {
return data.map(item => ({
yearMonth: item.yearMonth || '',
count: item.count || 0
}));
}
return [];
});
};
/**
* Fetches the grid coverage statistics by city.
* @returns A promise that resolves to an array of grid coverage data.
*/
export const getGridCoverageByCity = (): Promise<GridCoverageDTO[]> => {
return apiClient.get('/dashboard/reports/grid-coverage');
};
/**
* Fetches the heatmap data for pollution incidents.
* @returns A promise that resolves to an array of heatmap points.
*/
export const getHeatmapData = (): Promise<HeatmapPointDTO[]> => {
return apiClient.get('/dashboard/map/heatmap');
};
/**
* Fetches the pollution statistics.
* @returns A promise that resolves to an array of pollution stats.
*/
export const getPollutionStats = (): Promise<PollutionStatsDTO[]> => {
return apiClient.get('/dashboard/reports/pollution-stats');
};
/**
* Fetches the task completion statistics.
* @returns A promise that resolves to the task stats.
*/
export const getTaskCompletionStats = (): Promise<TaskStatsDTO> => {
return apiClient.get('/dashboard/reports/task-completion-stats');
};
/**
* Fetches the AQI heatmap data.
* @returns A promise that resolves to an array of AQI heatmap points.
*/
export const getAqiHeatmapData = (): Promise<AqiHeatmapPointDTO[]> => {
return apiClient.get('/dashboard/map/aqi-heatmap');
};
/**
* Fetches all pollutant thresholds.
* @returns A promise that resolves to an array of pollutant thresholds.
*/
export const getPollutantThresholds = (): Promise<PollutantThresholdDTO[]> => {
return apiClient.get('/dashboard/thresholds');
};
/**
* Fetches a specific pollutant threshold.
* @param pollutantName The name of the pollutant.
* @returns A promise that resolves to the pollutant threshold.
*/
export const getPollutantThreshold = (pollutantName: string): Promise<PollutantThresholdDTO> => {
return apiClient.get(`/dashboard/thresholds/${pollutantName}`);
};
/**
* Saves a pollutant threshold.
* @param threshold The pollutant threshold to save.
* @returns A promise that resolves to the saved pollutant threshold.
*/
export const savePollutantThreshold = (threshold: PollutantThresholdDTO): Promise<PollutantThresholdDTO> => {
return apiClient.post('/dashboard/thresholds', threshold);
};
/**
* 获取每种污染物的月度趋势数据
* @returns 每种污染物的月度趋势数据
*/
export const getPollutantMonthlyTrends = (value: string): Promise<Record<string, TrendDataPointDTO[]>> => {
return apiClient.get('/dashboard/reports/pollutant-monthly-trends')
.then(data => {
console.log('原始污染物趋势数据:', data);
// 确保数据格式正确
if (data && typeof data === 'object') {
const result: Record<string, TrendDataPointDTO[]> = {};
Object.entries(data).forEach(([key, value]) => {
if (Array.isArray(value)) {
result[key] = value.map(item => ({
yearMonth: item.yearMonth || '',
count: item.count || 0
}));
}
});
return result;
}
return {};
});
};
/**
* Fetches all grid data from the server.
* @returns A promise that resolves to an array of Grid objects.
*/
export const getGrids = (): Promise<Grid[]> => {
return apiClient.get('/grids');
};

View File

@@ -0,0 +1,292 @@
import apiClient from './index';
import type { Page, AssigneeInfoDTO, UserInfoDTO, TaskInfoDTO, AttachmentDTO } from './types';
import { PollutionType } from './types';
import type { AxiosResponse } from 'axios';
// 导出必要的类型
export { PollutionType };
// API URL
const API_URL = '/feedback';
// --- Start: Copied from TaskDetailView.vue for now ---
// This is technical debt and should be refactored into a central types file.
interface FeedbackDTO {
feedbackId: number;
eventId: string;
title: string;
}
interface AssigneeDTO {
id: number;
name: string;
phone: string;
}
interface TaskHistoryDTO {
id: number;
oldStatus: string | null;
newStatus: string;
comments: string;
changedAt: string;
changedBy: {
id: number;
name:string;
};
}
interface SubmissionInfoDTO {
comments: string;
attachments: AttachmentDTO[];
}
export interface TaskDetail {
id: number;
feedback: FeedbackDTO;
title: string;
description: string;
status: string;
assignee: AssigneeDTO;
assignedAt: string | null;
completedAt: string | null;
history: TaskHistoryDTO[];
submissionInfo: SubmissionInfoDTO | null;
}
// --- End: Copied from TaskDetailView.vue ---
/**
* 反馈状态枚举
*/
export enum FeedbackStatus {
AI_REVIEWING = 'AI_REVIEWING', // AI审核中
PENDING_REVIEW = 'PENDING_REVIEW', // 待人工审核
REJECTED = 'REJECTED', // 已拒绝
CONFIRMED = 'CONFIRMED', // 已确认
ASSIGNED = 'ASSIGNED', // 已分配
IN_PROGRESS = 'IN_PROGRESS', // 处理中
RESOLVED = 'RESOLVED', // 已解决
CLOSED = 'CLOSED', // 已关闭
PENDING_ASSIGNMENT = 'PENDING_ASSIGNMENT', // 待分配
PROCESSED = 'PROCESSED' // 已处理
}
/**
* 严重程度枚举
*/
export enum SeverityLevel {
LOW = 'LOW', // 低
MEDIUM = 'MEDIUM', // 中
HIGH = 'HIGH' // 高
}
/**
* 反馈列表项接口 (现在匹配后端的 FeedbackResponseDTO)
*/
export interface FeedbackResponse {
id: number;
eventId: string;
title: string;
description: string;
pollutionType: PollutionType;
severityLevel: SeverityLevel;
status: FeedbackStatus;
textAddress: string;
gridX: number;
gridY: number;
latitude: number;
longitude: number;
createdAt: string; // ISO date string
updatedAt: string; // ISO date string
submitterId: number;
user: UserInfoDTO;
task: TaskDetail | null; // Task can be null if not assigned
attachments: AttachmentDTO[];
}
/**
* 反馈详情接口 (为了简化,我们可以让它继承自 FeedbackResponse)
*/
export interface FeedbackDetail extends FeedbackResponse {
aiAnalysis?: string;
reviewNotes?: string;
assignmentId?: number;
assignmentMethod?: string;
assignmentTime?: string;
assignedWorkerId?: number;
resolutionNotes?: string;
resolutionTime?: string;
// `images` is now `attachments`, which is already in FeedbackResponse
}
/**
* 反馈筛选参数接口
*/
export interface FeedbackFilters {
status?: FeedbackStatus;
pollutionType?: PollutionType;
severityLevel?: SeverityLevel;
cityName?: string;
districtName?: string;
startDate?: string;
endDate?: string;
keyword?: string;
page?: number;
size?: number;
}
/**
* 反馈处理请求接口
*/
export interface ProcessFeedbackRequest {
status: FeedbackStatus;
notes?: string;
}
/**
* 反馈分配请求接口
*/
export interface AssignFeedbackRequest {
gridWorkerId: number;
notes?: string;
}
/**
* 反馈拒绝请求接口
*/
export interface RejectFeedbackRequest {
notes: string;
}
export interface LocationInfo {
latitude: number;
longitude: number;
textAddress: string;
gridX: number;
gridY: number;
}
export interface FeedbackSubmissionRequest {
title: string;
description: string;
pollutionType: PollutionType;
severityLevel: SeverityLevel;
location: LocationInfo;
}
/**
* 获取反馈列表
* @param params 筛选参数
* @returns 分页反馈列表
*/
export function getFeedbackList(params?: FeedbackFilters): Promise<Page<FeedbackResponse>> {
return apiClient.get(API_URL, { params });
}
/**
* 获取反馈详情
* @param id 反馈ID
* @returns 反馈详情
*/
export function getFeedbackDetail(id: number): Promise<AxiosResponse<FeedbackDetail>> {
return apiClient.get(`/feedback/${id}`);
}
/**
* 处理反馈
* @param id 反馈ID
* @param data 处理数据
* @returns 处理结果
*/
export function processFeedback(id: number, request: { status: string; notes?: string }): Promise<AxiosResponse<FeedbackDetail>> {
return apiClient.post(`/feedback/${id}/process`, request);
}
/**
* 分配反馈给网格员
* @param id 反馈ID
* @param data 分配数据
* @returns 分配结果
*/
export function assignFeedback(id: number, data: AssignFeedbackRequest): Promise<any> {
return apiClient.post(`${API_URL}/${id}/assign`, data);
}
/**
* 标记反馈为已解决
* @param id 反馈ID
* @param notes 解决说明
* @returns 操作结果
*/
export function resolveFeedback(id: number, notes?: string): Promise<any> {
return apiClient.post(`${API_URL}/${id}/resolve`, { notes });
}
/**
* 关闭反馈
* @param id 反馈ID
* @param notes 关闭说明
* @returns 操作结果
*/
export function closeFeedback(id: number, notes?: string): Promise<any> {
return apiClient.post(`${API_URL}/${id}/close`, { notes });
}
/**
* 拒绝反馈
* @param id 反馈ID
* @param data 拒绝数据
* @returns 操作结果
*/
export function rejectFeedback(id: number, data: RejectFeedbackRequest): Promise<any> {
return apiClient.post(`/supervisor/reviews/${id}/reject`, data);
}
/**
* 确认反馈
* @param id 反馈ID
* @returns 响应体
*/
export function confirmFeedback(id: number): Promise<any> {
return apiClient.post(`${API_URL}/${id}/confirm`);
}
/**
* 提交新的反馈
* @param formData 包含反馈数据和文件的表单
* @returns 提交结果
*/
export function submitFeedback(formData: FormData): Promise<any> {
return apiClient.post(`${API_URL}/submit`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
/**
* 获取反馈统计数据
* @returns 反馈统计数据
*/
export const getFeedbackStats = async () => {
try {
const response = await apiClient.get(`${API_URL}/stats`);
return response || {}; // 如果响应为空,返回一个空对象
} catch (error) {
console.error('获取反馈统计数据API错误:', error);
// 即使API失败也返回一个默认的统计对象结构防止UI崩溃
return {
total: 0,
pending: 0,
confirmed: 0,
assigned: 0,
inProgress: 0,
resolved: 0,
closed: 0,
rejected: 0,
};
}
};
/**
* 获取待处理的反馈列表
* @returns 待处理的反馈列表
*/
export function getPendingFeedback(): Promise<AxiosResponse<FeedbackResponse[]>> {
return apiClient.get('/feedback/pending');
}

View File

@@ -0,0 +1,42 @@
import apiClient from './index';
import type { Grid } from './types';
/**
* DTO for updating a grid's core properties.
* This should align with GridUpdateRequest in the backend.
*/
export interface GridUpdateRequest {
isObstacle?: boolean;
description?: string;
}
/**
* 更新网格信息
* @param gridId - 要更新的网格ID
* @param data - 包含更新数据的对象
* @returns 更新后的网格对象
*/
export const updateGrid = (gridId: number, data: GridUpdateRequest): Promise<Grid> => {
return apiClient.patch(`/grids/${gridId}`, data);
};
/**
* 通过坐标将网格员分配到网格
* @param gridX - 网格X坐标
* @param gridY - 网格Y坐标
* @param userId - 要分配的用户ID
* @returns 成功则返回void
*/
export const assignWorkerByCoordinates = (gridX: number, gridY: number, userId: number): Promise<void> => {
return apiClient.post(`/grids/coordinates/${gridX}/${gridY}/assign`, { userId });
};
/**
* 通过坐标从网格中移除网格员
* @param gridX - 网格X坐标
* @param gridY - 网格Y坐标
* @returns 成功则返回void
*/
export const unassignWorkerByCoordinates = (gridX: number, gridY: number): Promise<void> => {
return apiClient.post(`/grids/coordinates/${gridX}/${gridY}/unassign`);
};

View File

@@ -0,0 +1,50 @@
import axios from 'axios';
import { useAuthStore } from '@/stores/auth';
// 创建 Axios 实例
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量或默认值设置基础URL
headers: {
'Content-Type': 'application/json',
},
});
// 添加请求拦截器
apiClient.interceptors.request.use(
(config) => {
// 这个拦截器会在每个请求发送前执行
const token = localStorage.getItem('token');
if (token) {
// 为请求头添加Authorization字段
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 响应拦截器
apiClient.interceptors.response.use(
(response) => {
// 对响应数据做点什么
return response.data;
},
(error) => {
// 对响应错误做点什么
if (error.response && error.response.status === 401) {
// 如果是401错误说明token无效或过期
// 我们需要调用auth store的logout方法
// 为了避免循环依赖我们在函数内部获取store实例
const authStore = useAuthStore();
authStore.logout();
// 在这里重定向到登录页
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default apiClient;

View File

@@ -0,0 +1,10 @@
import apiClient from '.';
import type { OperationLog } from './types';
export function getOperationLogs(params: any): Promise<OperationLog[]> {
return apiClient.get('/logs', { params });
}
export function getMyOperationLogs(): Promise<OperationLog[]> {
return apiClient.get('/logs/my-logs');
}

View File

@@ -0,0 +1,30 @@
import apiClient from './index';
/**
* Represents a point with x and y coordinates.
* Matches the backend Point DTO.
*/
export interface Point {
x: number;
y: number;
}
/**
* Represents the request payload for finding a path.
* Matches the backend PathfindingRequest DTO.
*/
export interface PathfindingRequest {
startX: number;
startY: number;
endX: number;
endY: number;
}
/**
* Calls the backend API to find a path between two points.
* @param request The pathfinding request containing start and end coordinates.
* @returns A promise that resolves to a list of points representing the path.
*/
export const findPath = (request: PathfindingRequest): Promise<Point[]> => {
return apiClient.post('/pathfinding/find', request);
};

View File

@@ -0,0 +1,40 @@
import apiClient from './index';
import type { Page, Pageable, UserAccount, UserUpdateRequest } from './types';
/**
* 获取用户列表(可分页、可筛选)
* @param params - 包含分页和筛选条件的参数
* @returns 用户数据分页对象
*/
export const getUsers = (params: { role?: string; name?: string; } & Pageable): Promise<Page<UserAccount>> => {
return apiClient.get('/personnel/users', { params });
};
/**
* 获取所有网格员信息
* @returns 所有网格员的列表
*/
export const getAllGridWorkers = async (): Promise<UserAccount[]> => {
// We fetch with a large size to get all workers, assuming the number is manageable.
// A more robust solution might involve paginating through all results if the number of workers is very large.
const response = await getUsers({ role: 'GRID_WORKER', page: 0, size: 1000 });
return response.content;
};
/**
* 根据ID更新用户信息
* @param userId - 用户ID
* @param data - 需要更新的用户信息
* @returns 更新后的用户信息
*/
export const updateUser = (userId: number, data: UserUpdateRequest): Promise<UserAccount> => {
return apiClient.patch(`/personnel/users/${userId}`, data);
};
/**
* 根据ID删除用户
* @param userId - 用户ID
*/
export const deleteUser = (userId: number): Promise<void> => {
return apiClient.delete(`/personnel/users/${userId}`);
};

View File

@@ -0,0 +1,15 @@
import apiClient from './index';
/**
* Submits public feedback.
* This endpoint is for unauthenticated users (guests).
* @param formData The form data containing the feedback details and any files.
* @returns A promise that resolves on successful submission.
*/
export const submitPublicFeedback = (formData: FormData): Promise<any> => {
return apiClient.post('/public/feedback', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
};

View File

@@ -0,0 +1,37 @@
import type { AxiosResponse, AxiosError } from 'axios';
import apiClient from './index';
import type { UserAccount } from './types';
export const getAvailableGridWorkers = (): Promise<UserAccount[]> => {
// 根据TaskAssignmentController的路径应为/tasks/grid-workers
return apiClient.get<UserAccount[]>('/tasks/grid-workers')
.then((response: AxiosResponse<UserAccount[]>) => response.data)
.catch((error: AxiosError) => {
console.error('获取可用网格员API错误:', error);
// 如果API调用失败返回空数组而不是模拟数据
return [];
});
};
export const assignTask = (feedbackId: number, assigneeId: number): Promise<any> => {
return apiClient.post('/tasks/assign', { feedbackId, assigneeId });
};
/**
* Approves a submitted task.
* @param taskId The ID of the task to approve.
* @returns A promise that resolves with the updated task details.
*/
export const approveTask = (taskId: number): Promise<any> => {
return apiClient.post(`/management/tasks/${taskId}/approve`);
};
/**
* Rejects a submitted task.
* @param taskId The ID of the task to reject.
* @param reason The reason for rejection.
* @returns A promise that resolves with the updated task details.
*/
export const rejectTask = (taskId: number, reason: string): Promise<any> => {
return apiClient.post(`/management/tasks/${taskId}/reject`, { reason });
};

View File

@@ -0,0 +1,307 @@
/**
* 通用用户信息类型, 反映了JWT中包含的声明
*/
export interface User {
id: number;
name: string;
email: string;
role: string;
phone?: string;
status?: string;
region?: string;
skills?: string[];
gridX?: number;
gridY?: number;
}
/**
* 用户登录请求体
*/
export interface LoginRequest {
email: string;
password: string;
}
/**
* 登录成功响应体, 与后端完全匹配
*/
export interface LoginResponse {
accessToken: string;
}
/**
* 用户注册请求体
*/
export interface SignUpRequest {
name: string;
email: string;
phone: string;
password: string;
verificationCode: string;
role: string;
}
/**
* 使用验证码重置密码请求体, 对应后端的 PasswordResetWithCodeDto
*/
export interface ResetPasswordWithCodeRequest {
email: string;
code: string;
newPassword: string;
}
/**
* 分页请求参数
*/
export interface Pageable {
page?: number;
size?: number;
sort?: string;
}
/**
* 分页响应体
*/
export interface Page<T> {
content: T[];
totalPages: number;
totalElements: number;
size: number;
number: number;
}
/**
* 后端 UserAccount 实体类的完整映射
*/
export interface UserAccount {
id: number;
name: string;
email: string;
phone: string;
role: string;
status: string;
gender?: string;
region?: string;
gridX?: number;
gridY?: number;
level?: string;
skills?: string[];
currentLatitude?: number;
currentLongitude?: number;
createdAt: string; // Assuming ISO date string
updatedAt: string; // Assuming ISO date string
}
/**
* 用户更新请求体, 对应后端的 UserUpdateRequest
*/
export interface UserUpdateRequest {
name?: string;
phone?: string;
region?: string;
role?: string;
status?: string;
gender?: 'MALE' | 'FEMALE' | 'OTHER';
gridX?: number;
gridY?: number;
level?: string;
skills?: string[];
currentLatitude?: number;
currentLongitude?: number;
}
// --- Dashboard DTOs ---
/**
* Key statistics for the main dashboard.
* @param totalFeedbacks - Total number of feedbacks.
* @param confirmedFeedbacks - Number of confirmed feedbacks.
* @param totalAqiRecords - Total number of AQI records.
* @param activeGridWorkers - Number of active grid workers.
*/
export type DashboardStatsDTO = {
totalFeedbacks: number;
confirmedFeedbacks: number;
totalAqiRecords: number;
activeGridWorkers: number;
};
/**
* Data for AQI level distribution chart.
* @param level - The AQI descriptive level (e.g., "Good", "Moderate").
* @param count - The number of monitoring points at this level.
*/
export type AqiDistributionDTO = {
level: string;
count: number;
};
/**
* A single data point for a time-series trend chart.
* @param yearMonth - The date for the data point in "YYYY-MM" format.
* @param count - The value for that date (e.g., number of exceedances).
*/
export type TrendDataPointDTO = {
yearMonth: string;
count: number;
};
/**
* Grid coverage statistics for a specific city.
* @param city - The name of the city.
* @param totalGrids - The total number of grids in the city.
* @param coveredGrids - The number of grids with assigned personnel.
* @param coverageRate - The calculated coverage percentage.
*/
export type GridCoverageDTO = {
city: string;
totalGrids: number;
coveredGrids: number;
coverageRate: number;
};
/**
* A single point for a heatmap visualization.
* @param lat - Latitude of the data point.
* @param lng - Longitude of the data point.
* @param value - The intensity value for the heatmap.
*/
export type HeatmapPointDTO = {
lat: number;
lng: number;
value: number;
};
/**
* Detailed statistics for a specific pollutant.
* @param pollutantName - The name of the pollutant (e.g., "PM2.5").
* @param averageValue - The average value over a period.
* @param maxValue - The maximum recorded value.
* @param unit - The measurement unit (e.g., "µg/m³").
*/
export type PollutionStatsDTO = {
pollutantName: string;
averageValue: number;
maxValue: number;
unit: string;
};
/**
* Statistics on task completion status.
* @param totalTasks - Total number of tasks.
* @param completedTasks - Number of completed tasks.
* @param completionRate - Completion rate of tasks.
*/
export type TaskStatsDTO = {
totalTasks: number;
completedTasks: number;
completionRate: number;
};
/**
* A single point for the AQI heatmap, including pollutant info.
* Extends HeatmapPointDTO with additional details.
*/
export type AqiHeatmapPointDTO = HeatmapPointDTO & {
primaryPollutant: string;
aqi: number;
};
/**
* 污染物类型枚举
*/
export enum PollutionType {
PM25 = 'PM25',
O3 = 'O3',
NO2 = 'NO2',
SO2 = 'SO2',
OTHER = 'OTHER'
}
/**
* 污染物阈值设置
* @param pollutionType - 污染物类型
* @param pollutantName - 污染物名称
* @param threshold - 阈值
* @param unit - 单位
* @param description - 描述
*/
export type PollutantThresholdDTO = {
pollutionType: PollutionType;
pollutantName: string;
threshold: number;
unit: string;
description?: string;
};
/**
* Represents a geographical grid cell.
* Matches the backend Grid entity.
*/
export interface Grid {
id: number;
gridX: number;
gridY: number;
cityName: string;
districtName?: string;
description?: string;
isObstacle: boolean;
}
// ... 您可以根据API文档继续添加其他类型定义
// --- Task and Assignment DTOs ---
export interface AssigneeInfoDTO {
id: number;
name: string;
phone: string;
}
export interface UserInfoDTO {
id: number;
name: string;
phone: string;
email: string;
}
export interface TaskInfoDTO {
id: number;
status: string; // Should match TaskStatus enum from backend
assignee: AssigneeInfoDTO;
submissionInfo: SubmissionInfoDTO | null;
}
export interface AttachmentDTO {
id: number;
fileName: string;
fileType: string;
url: string;
uploadedAt: string; // ISO date string
}
export interface SubmissionInfoDTO {
comments: string;
attachments: AttachmentDTO[];
}
/**
* 操作日志类型
*/
export interface OperationLog {
id: number;
userId: number;
userName: string;
operationType: string;
operationTypeDesc: string;
description: string;
targetId: string;
targetType: string;
ipAddress: string;
createdAt: string;
}
/**
* Task Summary DTO for supervisor view.
* This should match `TaskSummaryDTO` from the backend.
*/

View File

@@ -0,0 +1,74 @@
import apiClient from './index';
import type { GridWorker } from './grid';
/**
* 用户信息接口
*/
export interface UserInfo {
id: number;
name: string;
email: string;
phone: string;
role: string;
avatar?: string;
permissions?: string[];
}
/**
* 用户更新请求接口
*/
export interface UserUpdateRequest {
name?: string;
phone?: string;
email?: string;
region?: string;
level?: string;
gridX?: number | null;
gridY?: number | null;
currentLatitude?: number | null;
currentLongitude?: number | null;
skills?: string[];
status?: string;
gender?: string;
}
/**
* 获取当前登录用户信息
* @returns 用户信息
*/
export function getCurrentUser(): Promise<UserInfo> {
return apiClient.get('/auth/profile');
}
/**
* 更新用户资料
* @param userId 用户ID
* @param data 更新数据
* @returns 更新后的用户信息
*/
export function updateUserProfile(userId: number, data: UserUpdateRequest): Promise<GridWorker> {
console.log(`API调用: 更新用户资料, userId=${userId}`, data);
return apiClient.patch(`/personnel/users/${userId}`, data)
.then(response => {
console.log('更新用户资料API调用成功:', response.data);
return response.data;
})
.catch(error => {
console.error('更新用户资料API调用失败:', error);
console.error('错误详情:', error.response?.data || error.message);
throw error;
});
}
/**
* 更新用户角色
* @param userId 用户ID
* @param role 角色名称
* @param gridX 网格X坐标可选
* @param gridY 网格Y坐标可选
* @returns 更新后的用户信息
*/
export function updateUserRole(userId: number, role: string, gridX?: number, gridY?: number): Promise<GridWorker> {
const data = { role, gridX, gridY };
return apiClient.put(`/personnel/users/${userId}/role`, data).then(response => response.data);
}