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

26
docker-compose.hub.yml Normal file
View File

@@ -0,0 +1,26 @@
services:
backend:
image: chuxunyu/ems-backend:latest
ports:
- "8080:8080"
environment:
APP_BASE_URL: ${APP_BASE_URL:-http://localhost:5173}
TOKEN_SIGNING_KEY: ${TOKEN_SIGNING_KEY:-}
JWT_SECRET: ${JWT_SECRET:-}
VOLCANO_API_KEY: ${VOLCANO_API_KEY:-}
SPRING_MAIL_USERNAME: ${SPRING_MAIL_USERNAME:-}
SPRING_MAIL_PASSWORD: ${SPRING_MAIL_PASSWORD:-}
volumes:
- ems_json_db:/app/json-db
- ems_uploads:/app/uploads
frontend:
image: chuxunyu/ems-frontend:latest
ports:
- "5173:80"
depends_on:
- backend
volumes:
ems_json_db:
ems_uploads:

View File

@@ -8,7 +8,7 @@ server {
client_max_body_size 50m; client_max_body_size 50m;
location /api/ { location /api/ {
proxy_pass http://backend:8080/; proxy_pass http://backend:8080;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;

View File

@@ -12,7 +12,9 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.10.0", "axios": "^1.10.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"echarts": "^6.0.0",
"element-plus": "^2.10.2", "element-plus": "^2.10.2",
"jwt-decode": "^4.0.0",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
@@ -2157,6 +2159,16 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/echarts": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "6.0.0"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.170", "version": "1.5.170",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz",
@@ -2772,6 +2784,15 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/kolorist": { "node_modules/kolorist": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
@@ -3408,6 +3429,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.3", "version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -3747,6 +3774,15 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zrender": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
} }
} }
} }

View File

@@ -16,7 +16,9 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.10.0", "axios": "^1.10.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"echarts": "^6.0.0",
"element-plus": "^2.10.2", "element-plus": "^2.10.2",
"jwt-decode": "^4.0.0",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"

View File

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

View File

@@ -1,5 +1,13 @@
import apiClient from './index'; 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. * DTO for updating a grid's core properties.
@@ -16,8 +24,49 @@ export interface GridUpdateRequest {
* @param data - 包含更新数据的对象 * @param data - 包含更新数据的对象
* @returns 更新后的网格对象 * @returns 更新后的网格对象
*/ */
export const updateGrid = (gridId: number, data: GridUpdateRequest): Promise<Grid> => { export function updateGrid(gridId: number, data: GridUpdateRequest): Promise<Grid>;
return apiClient.patch(`/grids/${gridId}`, data); 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;
}; };
/** /**
@@ -40,3 +89,7 @@ export const assignWorkerByCoordinates = (gridX: number, gridY: number, userId:
export const unassignWorkerByCoordinates = (gridX: number, gridY: number): Promise<void> => { export const unassignWorkerByCoordinates = (gridX: number, gridY: number): Promise<void> => {
return apiClient.post(`/grids/coordinates/${gridX}/${gridY}/unassign`); 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'; 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 实例 // 创建 Axios 实例
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量或默认值设置基础URL baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量或默认值设置基础URL
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); }) as ApiClient;
// 添加请求拦截器 // 添加请求拦截器
apiClient.interceptors.request.use( apiClient.interceptors.request.use(

View File

@@ -1,12 +1,10 @@
import type { AxiosResponse, AxiosError } from 'axios';
import apiClient from './index'; import apiClient from './index';
import type { UserAccount } from './types'; import type { UserAccount } from './types';
export const getAvailableGridWorkers = (): Promise<UserAccount[]> => { export const getAvailableGridWorkers = (): Promise<UserAccount[]> => {
// 根据TaskAssignmentController的路径应为/tasks/grid-workers // 根据TaskAssignmentController的路径应为/tasks/grid-workers
return apiClient.get<UserAccount[]>('/tasks/grid-workers') return apiClient.get<UserAccount[]>('/tasks/grid-workers')
.then((response: AxiosResponse<UserAccount[]>) => response.data) .catch((error: any) => {
.catch((error: AxiosError) => {
console.error('获取可用网格员API错误:', error); console.error('获取可用网格员API错误:', error);
// 如果API调用失败返回空数组而不是模拟数据 // 如果API调用失败返回空数组而不是模拟数据
return []; return [];

View File

@@ -263,6 +263,7 @@ export interface UserInfoDTO {
name: string; name: string;
phone: string; phone: string;
email: string; email: string;
role?: string;
} }
export interface TaskInfoDTO { export interface TaskInfoDTO {

View File

@@ -50,8 +50,8 @@ export function updateUserProfile(userId: number, data: UserUpdateRequest): Prom
console.log(`API调用: 更新用户资料, userId=${userId}`, data); console.log(`API调用: 更新用户资料, userId=${userId}`, data);
return apiClient.patch(`/personnel/users/${userId}`, data) return apiClient.patch(`/personnel/users/${userId}`, data)
.then(response => { .then(response => {
console.log('更新用户资料API调用成功:', response.data); console.log('更新用户资料API调用成功:', response);
return response.data; return response;
}) })
.catch(error => { .catch(error => {
console.error('更新用户资料API调用失败:', 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> { export function updateUserRole(userId: number, role: string, gridX?: number, gridY?: number): Promise<GridWorker> {
const data = { role, gridX, gridY }; 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> = { const map: Record<string, string> = {
'ADMIN': '管理员', 'ADMIN': '管理员',
'DECISION_MAKER': '决策人', 'DECISION_MAKER': '决策人',
@@ -433,6 +433,7 @@ const formatRole = (role: string) => {
'GRID_WORKER': '网格员', 'GRID_WORKER': '网格员',
'PUBLIC_SUPERVISOR': '公众监督员' 'PUBLIC_SUPERVISOR': '公众监督员'
}; };
if (!role) return '未知';
return map[role] || role; return map[role] || role;
}; };

View File

@@ -13,7 +13,7 @@ const app = createApp(App)
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(ElementPlus, { const elementPlusOptions = {
locale: zhCn, locale: zhCn,
zIndex: 3000, zIndex: 3000,
popperOptions: { popperOptions: {
@@ -26,6 +26,8 @@ app.use(ElementPlus, {
} }
] ]
} }
}) } as any;
app.use(ElementPlus as any, elementPlusOptions)
app.mount('#app') 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 { import type {
FeedbackResponse, FeedbackResponse,
FeedbackDetail, FeedbackDetail,
FeedbackFilters, FeedbackFilters
Page
} from '@/api/feedback'; } from '@/api/feedback';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { useAuthStore } from '../stores/auth'; import { useAuthStore } from '../stores/auth';

View File

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

View File

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

View File

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

View File

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

View File

@@ -110,7 +110,7 @@ const fetchAllGrids = async () => {
gridData.value = Array.isArray(response) ? response : (response as any).content || []; gridData.value = Array.isArray(response) ? response : (response as any).content || [];
} catch (e: any) { } catch (e: any) {
error.value = e.message || '获取网格数据时发生未知错误'; error.value = e.message || '获取网格数据时发生未知错误';
ElMessage.error(error.value); ElMessage.error(error.value || '获取网格数据时发生未知错误');
} finally { } finally {
loading.value = false; loading.value = false;
} }