diff --git a/docker-compose.hub.yml b/docker-compose.hub.yml new file mode 100644 index 0000000..fc395d5 --- /dev/null +++ b/docker-compose.hub.yml @@ -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: diff --git a/ems-frontend/ems-monitoring-system/nginx.conf b/ems-frontend/ems-monitoring-system/nginx.conf index d788fe9..17e1495 100644 --- a/ems-frontend/ems-monitoring-system/nginx.conf +++ b/ems-frontend/ems-monitoring-system/nginx.conf @@ -8,7 +8,7 @@ server { client_max_body_size 50m; location /api/ { - proxy_pass http://backend:8080/; + proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/ems-frontend/ems-monitoring-system/package-lock.json b/ems-frontend/ems-monitoring-system/package-lock.json index 1b0d798..a29c1ed 100644 --- a/ems-frontend/ems-monitoring-system/package-lock.json +++ b/ems-frontend/ems-monitoring-system/package-lock.json @@ -12,7 +12,9 @@ "animate.css": "^4.1.1", "axios": "^1.10.0", "date-fns": "^4.1.0", + "echarts": "^6.0.0", "element-plus": "^2.10.2", + "jwt-decode": "^4.0.0", "pinia": "^3.0.1", "vue": "^3.5.13", "vue-router": "^4.5.0" @@ -2157,6 +2159,16 @@ "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": { "version": "1.5.170", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz", @@ -2772,6 +2784,15 @@ "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": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -3408,6 +3429,12 @@ "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": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -3747,6 +3774,15 @@ "funding": { "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" + } } } } diff --git a/ems-frontend/ems-monitoring-system/package.json b/ems-frontend/ems-monitoring-system/package.json index 73f54bf..db14c1e 100644 --- a/ems-frontend/ems-monitoring-system/package.json +++ b/ems-frontend/ems-monitoring-system/package.json @@ -16,7 +16,9 @@ "animate.css": "^4.1.1", "axios": "^1.10.0", "date-fns": "^4.1.0", + "echarts": "^6.0.0", "element-plus": "^2.10.2", + "jwt-decode": "^4.0.0", "pinia": "^3.0.1", "vue": "^3.5.13", "vue-router": "^4.5.0" diff --git a/ems-frontend/ems-monitoring-system/src/api/feedback.ts b/ems-frontend/ems-monitoring-system/src/api/feedback.ts index e06df4a..d9e42c2 100644 --- a/ems-frontend/ems-monitoring-system/src/api/feedback.ts +++ b/ems-frontend/ems-monitoring-system/src/api/feedback.ts @@ -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> { +export function getFeedbackDetail(id: number): Promise { return apiClient.get(`/feedback/${id}`); } @@ -193,7 +200,7 @@ export function getFeedbackDetail(id: number): Promise> { +export function processFeedback(id: number, request: { status: string; notes?: string }): Promise { return apiClient.post(`/feedback/${id}/process`, request); } @@ -287,6 +294,6 @@ export const getFeedbackStats = async () => { * 获取待处理的反馈列表 * @returns 待处理的反馈列表 */ -export function getPendingFeedback(): Promise> { +export function getPendingFeedback(): Promise { return apiClient.get('/feedback/pending'); -} \ No newline at end of file +} diff --git a/ems-frontend/ems-monitoring-system/src/api/grid.ts b/ems-frontend/ems-monitoring-system/src/api/grid.ts index 96b4f77..2c318f5 100644 --- a/ems-frontend/ems-monitoring-system/src/api/grid.ts +++ b/ems-frontend/ems-monitoring-system/src/api/grid.ts @@ -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 => { - return apiClient.patch(`/grids/${gridId}`, data); +export function updateGrid(gridId: number, data: GridUpdateRequest): Promise; +export function updateGrid(grid: GridData & GridUpdateRequest): Promise; +export function updateGrid( + gridOrId: number | (GridData & GridUpdateRequest), + data?: GridUpdateRequest +): Promise { + 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 => { + return apiClient.get('/grids'); +}; + +/** + * 获取网格员列表(可选:仅未分配) + */ +export const getGridWorkers = async (params?: GridWorkersQuery): Promise => { + 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 => { return apiClient.post(`/grids/coordinates/${gridX}/${gridY}/unassign`); -}; \ No newline at end of file +}; + +// Aliases for compatibility with existing usage +export const assignGridWorkerByCoordinates = assignWorkerByCoordinates; +export const removeGridWorkerByCoordinates = unassignWorkerByCoordinates; diff --git a/ems-frontend/ems-monitoring-system/src/api/index.ts b/ems-frontend/ems-monitoring-system/src/api/index.ts index 8eae36d..b1bbcbe 100644 --- a/ems-frontend/ems-monitoring-system/src/api/index.ts +++ b/ems-frontend/ems-monitoring-system/src/api/index.ts @@ -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(config: AxiosRequestConfig): Promise; + get(url: string, config?: AxiosRequestConfig): Promise; + delete(url: string, config?: AxiosRequestConfig): Promise; + head(url: string, config?: AxiosRequestConfig): Promise; + options(url: string, config?: AxiosRequestConfig): Promise; + post(url: string, data?: any, config?: AxiosRequestConfig): Promise; + put(url: string, data?: any, config?: AxiosRequestConfig): Promise; + patch(url: string, data?: any, config?: AxiosRequestConfig): Promise; +} + // 创建 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; \ No newline at end of file +export default apiClient; diff --git a/ems-frontend/ems-monitoring-system/src/api/tasks.ts b/ems-frontend/ems-monitoring-system/src/api/tasks.ts index 9e05f1e..8c00e34 100644 --- a/ems-frontend/ems-monitoring-system/src/api/tasks.ts +++ b/ems-frontend/ems-monitoring-system/src/api/tasks.ts @@ -1,12 +1,10 @@ -import type { AxiosResponse, AxiosError } from 'axios'; import apiClient from './index'; import type { UserAccount } from './types'; export const getAvailableGridWorkers = (): Promise => { // 根据TaskAssignmentController的路径应为/tasks/grid-workers return apiClient.get('/tasks/grid-workers') - .then((response: AxiosResponse) => response.data) - .catch((error: AxiosError) => { + .catch((error: any) => { console.error('获取可用网格员API错误:', error); // 如果API调用失败,返回空数组而不是模拟数据 return []; @@ -34,4 +32,4 @@ export const approveTask = (taskId: number): Promise => { */ export const rejectTask = (taskId: number, reason: string): Promise => { return apiClient.post(`/management/tasks/${taskId}/reject`, { reason }); -}; \ No newline at end of file +}; diff --git a/ems-frontend/ems-monitoring-system/src/api/types.ts b/ems-frontend/ems-monitoring-system/src/api/types.ts index f97fde0..b133cb6 100644 --- a/ems-frontend/ems-monitoring-system/src/api/types.ts +++ b/ems-frontend/ems-monitoring-system/src/api/types.ts @@ -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. - */ \ No newline at end of file + */ diff --git a/ems-frontend/ems-monitoring-system/src/api/user.ts b/ems-frontend/ems-monitoring-system/src/api/user.ts index 0fbfc64..e898ab5 100644 --- a/ems-frontend/ems-monitoring-system/src/api/user.ts +++ b/ems-frontend/ems-monitoring-system/src/api/user.ts @@ -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 { const data = { role, gridX, gridY }; - return apiClient.put(`/personnel/users/${userId}/role`, data).then(response => response.data); -} \ No newline at end of file + return apiClient.put(`/personnel/users/${userId}/role`, data); +} diff --git a/ems-frontend/ems-monitoring-system/src/components/FeedbackDetailDialog.vue b/ems-frontend/ems-monitoring-system/src/components/FeedbackDetailDialog.vue index 7b591a2..2205cd2 100644 --- a/ems-frontend/ems-monitoring-system/src/components/FeedbackDetailDialog.vue +++ b/ems-frontend/ems-monitoring-system/src/components/FeedbackDetailDialog.vue @@ -425,7 +425,7 @@ const formatDate = (dateStr: string) => { } }; -const formatRole = (role: string) => { +const formatRole = (role?: string) => { const map: Record = { '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; } - \ No newline at end of file + diff --git a/ems-frontend/ems-monitoring-system/src/main.ts b/ems-frontend/ems-monitoring-system/src/main.ts index 82f8333..8a35c2b 100644 --- a/ems-frontend/ems-monitoring-system/src/main.ts +++ b/ems-frontend/ems-monitoring-system/src/main.ts @@ -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') diff --git a/ems-frontend/ems-monitoring-system/src/shims-element-plus.d.ts b/ems-frontend/ems-monitoring-system/src/shims-element-plus.d.ts new file mode 100644 index 0000000..e253808 --- /dev/null +++ b/ems-frontend/ems-monitoring-system/src/shims-element-plus.d.ts @@ -0,0 +1,4 @@ +declare module 'element-plus/dist/locale/zh-cn.mjs' { + const locale: any; + export default locale; +} diff --git a/ems-frontend/ems-monitoring-system/src/store/feedback.ts b/ems-frontend/ems-monitoring-system/src/store/feedback.ts index 6fcce4c..d9e11e5 100644 --- a/ems-frontend/ems-monitoring-system/src/store/feedback.ts +++ b/ems-frontend/ems-monitoring-system/src/store/feedback.ts @@ -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) { ... } } -}); \ No newline at end of file +}); diff --git a/ems-frontend/ems-monitoring-system/src/views/PublicFeedbackSubmitView.vue b/ems-frontend/ems-monitoring-system/src/views/PublicFeedbackSubmitView.vue index d13ddb7..015cf97 100644 --- a/ems-frontend/ems-monitoring-system/src/views/PublicFeedbackSubmitView.vue +++ b/ems-frontend/ems-monitoring-system/src/views/PublicFeedbackSubmitView.vue @@ -80,13 +80,13 @@