This commit is contained in:
ChuXun
2026-01-29 02:55:05 +08:00
parent 33fb5639c7
commit c856666f22
19 changed files with 197 additions and 53 deletions

View File

@@ -1,7 +1,6 @@
import apiClient from './index';
import type { Page, AssigneeInfoDTO, UserInfoDTO, TaskInfoDTO, AttachmentDTO } from './types';
import { PollutionType } from './types';
import type { AxiosResponse } from 'axios';
// 导出必要的类型
export { PollutionType };
@@ -42,11 +41,17 @@ export interface TaskDetail {
title: string;
description: string;
status: string;
assignee: AssigneeDTO;
assignee?: AssigneeDTO;
assignedAt: string | null;
completedAt: string | null;
history: TaskHistoryDTO[];
submissionInfo: SubmissionInfoDTO | null;
gridX?: number | null;
gridY?: number | null;
assignment?: {
assignmentTime?: string;
remarks?: string;
};
}
// --- End: Copied from TaskDetailView.vue ---
@@ -97,6 +102,7 @@ export interface FeedbackResponse {
user: UserInfoDTO;
task: TaskDetail | null; // Task can be null if not assigned
attachments: AttachmentDTO[];
reporterPhone?: string;
}
/**
@@ -126,6 +132,7 @@ export interface FeedbackFilters {
startDate?: string;
endDate?: string;
keyword?: string;
submitterId?: number;
page?: number;
size?: number;
}
@@ -183,7 +190,7 @@ export function getFeedbackList(params?: FeedbackFilters): Promise<Page<Feedback
* @param id 反馈ID
* @returns 反馈详情
*/
export function getFeedbackDetail(id: number): Promise<AxiosResponse<FeedbackDetail>> {
export function getFeedbackDetail(id: number): Promise<FeedbackDetail> {
return apiClient.get(`/feedback/${id}`);
}
@@ -193,7 +200,7 @@ export function getFeedbackDetail(id: number): Promise<AxiosResponse<FeedbackDet
* @param data 处理数据
* @returns 处理结果
*/
export function processFeedback(id: number, request: { status: string; notes?: string }): Promise<AxiosResponse<FeedbackDetail>> {
export function processFeedback(id: number, request: { status: string; notes?: string }): Promise<FeedbackDetail> {
return apiClient.post(`/feedback/${id}/process`, request);
}
@@ -287,6 +294,6 @@ export const getFeedbackStats = async () => {
* 获取待处理的反馈列表
* @returns 待处理的反馈列表
*/
export function getPendingFeedback(): Promise<AxiosResponse<FeedbackResponse[]>> {
export function getPendingFeedback(): Promise<FeedbackResponse[]> {
return apiClient.get('/feedback/pending');
}
}

View File

@@ -1,5 +1,13 @@
import apiClient from './index';
import type { Grid } from './types';
import type { Grid, UserAccount } from './types';
export interface GridData extends Grid {}
export type GridWorker = UserAccount;
export interface GridWorkersQuery {
unassigned?: boolean;
}
/**
* DTO for updating a grid's core properties.
@@ -16,8 +24,49 @@ export interface GridUpdateRequest {
* @param data - 包含更新数据的对象
* @returns 更新后的网格对象
*/
export const updateGrid = (gridId: number, data: GridUpdateRequest): Promise<Grid> => {
return apiClient.patch(`/grids/${gridId}`, data);
export function updateGrid(gridId: number, data: GridUpdateRequest): Promise<Grid>;
export function updateGrid(grid: GridData & GridUpdateRequest): Promise<Grid>;
export function updateGrid(
gridOrId: number | (GridData & GridUpdateRequest),
data?: GridUpdateRequest
): Promise<Grid> {
if (typeof gridOrId === 'number') {
return apiClient.patch(`/grids/${gridOrId}`, data ?? {});
}
const payload: GridUpdateRequest = {
isObstacle: gridOrId.isObstacle,
description: gridOrId.description,
};
return apiClient.patch(`/grids/${gridOrId.id}`, payload);
}
/**
* 获取所有网格
*/
export const getGrids = (): Promise<GridData[]> => {
return apiClient.get('/grids');
};
/**
* 获取网格员列表(可选:仅未分配)
*/
export const getGridWorkers = async (params?: GridWorkersQuery): Promise<GridWorker[]> => {
const response = await apiClient.get('/personnel/users', {
params: { role: 'GRID_WORKER' },
});
let workers: GridWorker[] = Array.isArray(response) ? response : (response as any).content || [];
if (params?.unassigned) {
workers = workers.filter(worker =>
worker.gridX === null ||
worker.gridY === null ||
worker.gridX === undefined ||
worker.gridY === undefined ||
worker.gridX === -1 ||
worker.gridY === -1
);
}
return workers;
};
/**
@@ -39,4 +88,8 @@ export const assignWorkerByCoordinates = (gridX: number, gridY: number, userId:
*/
export const unassignWorkerByCoordinates = (gridX: number, gridY: number): Promise<void> => {
return apiClient.post(`/grids/coordinates/${gridX}/${gridY}/unassign`);
};
};
// Aliases for compatibility with existing usage
export const assignGridWorkerByCoordinates = assignWorkerByCoordinates;
export const removeGridWorkerByCoordinates = unassignWorkerByCoordinates;

View File

@@ -1,13 +1,24 @@
import axios from 'axios';
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios';
import { useAuthStore } from '@/stores/auth';
interface ApiClient extends AxiosInstance {
request<T = any, R = T>(config: AxiosRequestConfig): Promise<R>;
get<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
delete<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
head<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
options<T = any, R = T>(url: string, config?: AxiosRequestConfig): Promise<R>;
post<T = any, R = T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
put<T = any, R = T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
patch<T = any, R = T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
}
// 创建 Axios 实例
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量或默认值设置基础URL
headers: {
'Content-Type': 'application/json',
},
});
}) as ApiClient;
// 添加请求拦截器
apiClient.interceptors.request.use(
@@ -47,4 +58,4 @@ apiClient.interceptors.response.use(
}
);
export default apiClient;
export default apiClient;

View File

@@ -1,12 +1,10 @@
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) => {
.catch((error: any) => {
console.error('获取可用网格员API错误:', error);
// 如果API调用失败返回空数组而不是模拟数据
return [];
@@ -34,4 +32,4 @@ export const approveTask = (taskId: number): Promise<any> => {
*/
export const rejectTask = (taskId: number, reason: string): Promise<any> => {
return apiClient.post(`/management/tasks/${taskId}/reject`, { reason });
};
};

View File

@@ -263,6 +263,7 @@ export interface UserInfoDTO {
name: string;
phone: string;
email: string;
role?: string;
}
export interface TaskInfoDTO {
@@ -304,4 +305,4 @@ export interface OperationLog {
/**
* Task Summary DTO for supervisor view.
* This should match `TaskSummaryDTO` from the backend.
*/
*/

View File

@@ -50,8 +50,8 @@ export function updateUserProfile(userId: number, data: UserUpdateRequest): Prom
console.log(`API调用: 更新用户资料, userId=${userId}`, data);
return apiClient.patch(`/personnel/users/${userId}`, data)
.then(response => {
console.log('更新用户资料API调用成功:', response.data);
return response.data;
console.log('更新用户资料API调用成功:', response);
return response;
})
.catch(error => {
console.error('更新用户资料API调用失败:', error);
@@ -70,5 +70,5 @@ export function updateUserProfile(userId: number, data: UserUpdateRequest): Prom
*/
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);
}
return apiClient.put(`/personnel/users/${userId}/role`, data);
}

View File

@@ -425,7 +425,7 @@ const formatDate = (dateStr: string) => {
}
};
const formatRole = (role: string) => {
const formatRole = (role?: string) => {
const map: Record<string, string> = {
'ADMIN': '管理员',
'DECISION_MAKER': '决策人',
@@ -433,6 +433,7 @@ const formatRole = (role: string) => {
'GRID_WORKER': '网格员',
'PUBLIC_SUPERVISOR': '公众监督员'
};
if (!role) return '未知';
return map[role] || role;
};
@@ -604,4 +605,4 @@ const handleRejectFeedback = async () => {
border-radius: 4px;
cursor: pointer;
}
</style>
</style>

View File

@@ -13,7 +13,7 @@ const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus, {
const elementPlusOptions = {
locale: zhCn,
zIndex: 3000,
popperOptions: {
@@ -26,6 +26,8 @@ app.use(ElementPlus, {
}
]
}
})
} as any;
app.use(ElementPlus as any, elementPlusOptions)
app.mount('#app')

View File

@@ -0,0 +1,4 @@
declare module 'element-plus/dist/locale/zh-cn.mjs' {
const locale: any;
export default locale;
}

View File

@@ -15,8 +15,7 @@ import {
import type {
FeedbackResponse,
FeedbackDetail,
FeedbackFilters,
Page
FeedbackFilters
} from '@/api/feedback';
import { ElMessage } from 'element-plus';
import { useAuthStore } from '../stores/auth';
@@ -121,4 +120,4 @@ export const useFeedbackStore = defineStore('feedback', {
// async process(id: number, data: ProcessFeedbackRequest) { ... }
// async assign(id: number, data: AssignFeedbackRequest) { ... }
}
});
});

View File

@@ -80,13 +80,13 @@
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import type { FormInstance, FormRules, UploadFile } from 'element-plus';
import { PollutionType, SeverityLevel } from '@/api/feedback';
import { submitPublicFeedback } from '@/api/public'; // Assuming the function will be in a new api/public.ts
const feedbackForm = ref<FormInstance>();
const loading = ref(false);
const fileList = ref([]);
const fileList = ref<UploadFile[]>([]);
const rating = ref(2);
interface PublicFeedbackForm {
@@ -131,11 +131,11 @@ const rules = reactive<FormRules>({
gridY: [{ required: true, type: 'number', message: '网格Y必须为数字' }],
});
const handleRemove = (file, fileListUpdated) => {
const handleRemove = (file: UploadFile, fileListUpdated: UploadFile[]) => {
fileList.value = fileListUpdated;
};
const handleChange = (file, fileListUpdated) => {
const handleChange = (file: UploadFile, fileListUpdated: UploadFile[]) => {
fileList.value = fileListUpdated;
}
@@ -156,7 +156,9 @@ const submitForm = async () => {
formData.append('feedback', new Blob([JSON.stringify(submissionData)], { type: 'application/json' }));
fileList.value.forEach(file => {
formData.append('files', file.raw);
if (file.raw) {
formData.append('files', file.raw);
}
});
await submitPublicFeedback(formData);
@@ -201,4 +203,4 @@ const resetForm = () => {
color: #666;
margin-top: 5px;
}
</style>
</style>

View File

@@ -76,7 +76,7 @@
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import type { FormInstance, FormRules, UploadFile } from 'element-plus';
import { useFeedbackStore } from '@/store/feedback';
import { PollutionType } from '@/api/types';
import { SeverityLevel } from '@/api/feedback';
@@ -84,7 +84,7 @@ import type { FeedbackSubmissionRequest, LocationInfo } from '@/api/feedback';
const feedbackForm = ref<FormInstance>();
const loading = ref(false);
const fileList = ref([]);
const fileList = ref<UploadFile[]>([]);
const rating = ref(2); // For el-rate which uses a number. 1=LOW, 2=MEDIUM, 3=HIGH
const form = reactive<Omit<FeedbackSubmissionRequest, 'severityLevel'>>({
@@ -121,15 +121,15 @@ const rules = reactive<FormRules>({
const feedbackStore = useFeedbackStore();
const handleRemove = (file, fileListUpdated) => {
const handleRemove = (file: UploadFile, fileListUpdated: UploadFile[]) => {
fileList.value = fileListUpdated;
};
const handlePreview = (file) => {
const handlePreview = (file: UploadFile) => {
console.log(file);
};
const handleChange = (file, fileListUpdated) => {
const handleChange = (file: UploadFile, fileListUpdated: UploadFile[]) => {
fileList.value = fileListUpdated;
}
@@ -157,7 +157,9 @@ const submitForm = async () => {
formData.append('feedback', new Blob([JSON.stringify(submissionData)], { type: 'application/json' }));
fileList.value.forEach(file => {
formData.append('files', file.raw);
if (file.raw) {
formData.append('files', file.raw);
}
});
await feedbackStore.submitFeedback(formData);
@@ -187,4 +189,4 @@ const resetForm = () => {
font-size: 1.2em;
font-weight: bold;
}
</style>
</style>

View File

@@ -33,18 +33,18 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { Feedback } from '@/api/types';
import type { FeedbackResponse } from '@/api/feedback';
import { getPendingFeedback, processFeedback } from '@/api/feedback'; // Assuming processFeedback exists
import { formatDateTime } from '@/utils/formatter';
const pendingFeedback = ref<Feedback[]>([]);
const pendingFeedback = ref<FeedbackResponse[]>([]);
const loading = ref(false);
const fetchPendingFeedback = async () => {
loading.value = true;
try {
const response = await getPendingFeedback();
pendingFeedback.value = response.data;
pendingFeedback.value = response;
} catch (error) {
ElMessage.error('获取待审核反馈失败');
console.error(error);
@@ -53,13 +53,13 @@ const fetchPendingFeedback = async () => {
}
};
const handleCreateTask = (feedback: Feedback) => {
const handleCreateTask = (feedback: FeedbackResponse) => {
// TODO: Implement task creation logic
console.log('Create task from feedback:', feedback.id);
ElMessage.info(`准备为反馈 #${feedback.id} 创建任务`);
};
const handleRejectFeedback = async (feedback: Feedback) => {
const handleRejectFeedback = async (feedback: FeedbackResponse) => {
try {
await ElMessageBox.confirm(`确定要拒绝这条反馈吗? (ID: ${feedback.id})`, '确认操作', {
confirmButtonText: '确定',

View File

@@ -171,8 +171,8 @@ const isSubmitDialogVisible = ref(false);
const fetchTaskDetails = async () => {
loading.value = true;
try {
const response = await apiClient.get(`/worker/${taskId.value}`);
task.value = response as TaskDetail;
const response = await apiClient.get<TaskDetail>(`/worker/${taskId.value}`);
task.value = response;
} catch (error) {
console.error(`获取任务详情失败 (ID: ${taskId.value}):`, error);
ElMessage.error('获取任务详情失败');
@@ -286,4 +286,4 @@ onMounted(() => {
margin-top: 20px;
text-align: right;
}
</style>
</style>

View File

@@ -110,7 +110,7 @@ const fetchAllGrids = async () => {
gridData.value = Array.isArray(response) ? response : (response as any).content || [];
} catch (e: any) {
error.value = e.message || '获取网格数据时发生未知错误';
ElMessage.error(error.value);
ElMessage.error(error.value || '获取网格数据时发生未知错误');
} finally {
loading.value = false;
}
@@ -334,4 +334,4 @@ const selectGrid = (grid: DisplayGrid) => {
color: #303133;
margin-right: 8px;
}
</style>
</style>