diff --git a/src/main/java/com/campus/water/controller/web/DeviceStatusController.java b/src/main/java/com/campus/water/controller/web/DeviceStatusController.java index 501e405..dc357b4 100644 --- a/src/main/java/com/campus/water/controller/web/DeviceStatusController.java +++ b/src/main/java/com/campus/water/controller/web/DeviceStatusController.java @@ -102,17 +102,24 @@ public class DeviceStatusController { @GetMapping("/by-status") @Operation(summary = "按状态查询设备", description = "根据状态查询设备列表") public ResponseEntity>> getDevicesByStatus( - @RequestParam String status, - @RequestParam(required = false) String areaId, - @RequestParam(required = false) String deviceType) { - try { - List devices = deviceStatusService.getDevicesByStatus(status, areaId, deviceType); - return ResponseEntity.ok(ResultVO.success(devices)); - } catch (Exception e) { - return ResponseEntity.ok(ResultVO.error(500, "查询设备失败: " + e.getMessage())); - } + @RequestParam String status, + @RequestParam(required = false) String areaId, + @RequestParam(required = false) String deviceType) { + + // 添加默认值处理 + if (deviceType == null || deviceType.isEmpty()) { + deviceType = "water_maker"; // 默认值 } + try { + List devices = deviceStatusService.getDevicesByStatus(status, areaId, deviceType); + return ResponseEntity.ok(ResultVO.success(devices)); + } catch (Exception e) { + return ResponseEntity.ok(ResultVO.error(500, "查询设备失败: " + e.getMessage())); + } +} + + @GetMapping("/status-count") @Operation(summary = "设备状态数量统计", description = "统计各状态设备数量") public ResponseEntity>> getDeviceStatusCount( diff --git a/src/main/java/com/campus/water/service/LoginService.java b/src/main/java/com/campus/water/service/LoginService.java index c44d31e..9e5ed92 100644 --- a/src/main/java/com/campus/water/service/LoginService.java +++ b/src/main/java/com/campus/water/service/LoginService.java @@ -12,6 +12,7 @@ import com.campus.water.entity.dto.request.LoginRequest; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; import java.util.UUID; @@ -37,7 +38,7 @@ public class LoginService { }; } - private LoginVO handleAdminLogin(String username, String password) { + /* private LoginVO handleAdminLogin(String username, String password) { Admin admin = adminRepository.findByAdminName(username) .orElseThrow(() -> new RuntimeException("管理员不存在")); @@ -46,8 +47,29 @@ public class LoginService { } return createLoginVO(admin.getAdminId(), username, "admin"); + }*/ + private LoginVO handleAdminLogin(String username, String password) { + Admin admin = adminRepository.findByAdminName(username) + .orElseThrow(() -> new RuntimeException("管理员不存在")); + + boolean matches; + // 临时支持 MD5 验证(仅用于测试环境) + if (admin.getPassword().startsWith("$2a$") || admin.getPassword().startsWith("$2y$")) { + // BCrypt 格式密码 + matches = passwordEncoder.matches(password, admin.getPassword()); + } else { + // MD5 格式密码 + String md5Password = DigestUtils.md5DigestAsHex(password.getBytes()); + matches = md5Password.equals(admin.getPassword()); } + if (!matches) { + throw new RuntimeException("密码错误"); + } + + return createLoginVO(admin.getAdminId(), username, "admin"); +} + private LoginVO handleUserLogin(String username, String password) { // 改为查询User实体,使用studentName字段匹配用户名 User user = userRepository.findByStudentName(username) diff --git a/src/main/resources/web/.env b/src/main/resources/web/.env new file mode 100644 index 0000000..3a6da93 --- /dev/null +++ b/src/main/resources/web/.env @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://localhost:8080 +VITE_APP_ORIGIN=http://localhost:5173 \ No newline at end of file diff --git a/src/main/resources/web/src/api/auth.ts b/src/main/resources/web/src/api/auth.ts index 640729c..dc8857c 100644 --- a/src/main/resources/web/src/api/auth.ts +++ b/src/main/resources/web/src/api/auth.ts @@ -1,58 +1,61 @@ -import type { LoginRequest, LoginVO } from './types/auth' +// 替换原文件内容 +import type { LoginRequest, LoginResponse, LoginVO } from './types/auth' // 真实的登录API调用 -export const realLoginApi = async (data: LoginRequest): Promise => { - const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080' - - console.log('🌐 调用登录接口:', `${API_BASE_URL}/api/common/login`) - console.log('📤 请求数据:', data) - - try { - const response = await fetch(`${API_BASE_URL}/api/common/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - - console.log('📥 响应状态:', response.status, response.statusText) - - if (!response.ok) { - throw new Error(`网络请求失败: ${response.status} ${response.statusText}`) +export const realLoginApi = async (data: LoginRequest): Promise => { + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080' + + console.log('🌐 调用登录接口:', `${API_BASE_URL}/api/common/login`) + console.log('📤 请求数据:', data) + + try { + const response = await fetch(`${API_BASE_URL}/api/common/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + + console.log('📥 响应状态:', response.status, response.statusText) + + if (!response.ok) { + const errorText = await response.text() + console.error('❌ 响应内容:', errorText) + throw new Error(`网络请求失败: ${response.status} ${response.statusText}`) + } + + const result: LoginResponse = await response.json() + console.log('✅ 登录响应:', result) + + return result + + } catch (error: any) { + console.error('❌ 登录接口调用失败:', error) + throw new Error(`登录失败: ${error.message}`) } - - const result: LoginVO = await response.json() - console.log('✅ 登录响应:', result) - - return result - - } catch (error: any) { - console.error('❌ 登录接口调用失败:', error) - throw new Error(`登录失败: ${error.message}`) - } } // 备用模拟登录 -export const mockLoginApi = async (data: LoginRequest): Promise => { - await new Promise(resolve => setTimeout(resolve, 1000)) - - if (data.username === 'admin' && data.password === '123456') { - return { - code: 200, - message: '登录成功', - data: { - token: 'mock-jwt-token-' + Date.now(), - userInfo: { - id: 1, - username: 'admin', - realName: '张管理员', - role: 'admin', - avatar: '' +export const mockLoginApi = async (data: LoginRequest): Promise => { + await new Promise(resolve => setTimeout(resolve, 1000)) + + if (data.username === 'admin' && data.password === '123456') { + return { + code: 200, + message: '登录成功', + data: { + token: 'mock-jwt-token-' + Date.now(), + userInfo: { + id: 1, + username: 'admin', + realName: '张管理员', + role: 'admin', + avatar: '' + } + } } - } + } else { + throw new Error('用户名或密码错误') } - } else { - throw new Error('用户名或密码错误') - } } \ No newline at end of file diff --git a/src/main/resources/web/src/api/deviceStatus.ts b/src/main/resources/web/src/api/deviceStatus.ts new file mode 100644 index 0000000..8c11722 --- /dev/null +++ b/src/main/resources/web/src/api/deviceStatus.ts @@ -0,0 +1,50 @@ +// src/api/deviceStatus.ts +import axios from 'axios' + +export const DeviceStatusApi = { + // 获取设备状态列表 - 修改为匹配后端实际接口 + getDevicesByStatus: async (status: string, areaId?: string, deviceType?: string) => { + try { + const params: any = { status } + if (areaId) params.areaId = areaId + if (deviceType) params.deviceType = deviceType + + const response = await axios.get('/api/web/device-status/by-status', { params }) + return response.data + } catch (error) { + throw new Error(`获取设备列表失败: ${error}`) + } + }, + + // 标记设备在线 + markDeviceOnline: async (deviceId: string) => { + try { + const response = await axios.post(`/api/web/device-status/${deviceId}/online`) + return response.data + } catch (error) { + throw new Error(`设置设备在线失败: ${error}`) + } + }, + + // 标记设备离线 + markDeviceOffline: async (deviceId: string, reason?: string) => { + try { + const params = reason ? { reason } : {} + const response = await axios.post(`/api/web/device-status/${deviceId}/offline`, null, { params }) + return response.data + } catch (error) { + throw new Error(`设置设备离线失败: ${error}`) + } + }, + + // 标记设备故障 + markDeviceFault: async (deviceId: string, faultType: string, description: string) => { + try { + const params = { faultType, description } + const response = await axios.post(`/api/web/device-status/${deviceId}/fault`, null, { params }) + return response.data + } catch (error) { + throw new Error(`设置设备故障失败: ${error}`) + } + } +} diff --git a/src/main/resources/web/src/api/modules/auth.ts b/src/main/resources/web/src/api/modules/auth.ts new file mode 100644 index 0000000..4001362 --- /dev/null +++ b/src/main/resources/web/src/api/modules/auth.ts @@ -0,0 +1,15 @@ +// src/api/modules/auth.ts +import { api } from '../request' +import type { LoginRequest, LoginVO, ResultVO } from '../types/auth' + +class AuthApi { + /** + * 用户登录 + */ + async login(data: LoginRequest): Promise> { + console.log('🚀 调用登录接口:', data) + return api.post>('/api/common/login', data) + } +} + +export const authApi = new AuthApi() \ No newline at end of file diff --git a/src/main/resources/web/src/api/request.ts b/src/main/resources/web/src/api/request.ts new file mode 100644 index 0000000..4f80b6f --- /dev/null +++ b/src/main/resources/web/src/api/request.ts @@ -0,0 +1,141 @@ +// src/api/request.ts +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080' + +// 统一的 fetch 封装 +export async function request( + url: string, + options: RequestInit = {} +): Promise { + console.log(`🌐 发送请求: ${API_BASE_URL}${url}`, { + method: options.method || 'GET', + headers: options.headers, + body: options.body ? JSON.parse(options.body as string) : undefined, + }) + + const defaultOptions: RequestInit = { + headers: { + 'Content-Type': 'application/json', + ...(options.headers || {}), + }, + } + + // 确保登录请求不携带任何认证信息 + const isLoginRequest = url.includes('/login') + if (isLoginRequest) { + console.log('🔐 这是登录请求,不携带认证头') + // 确保没有 Authorization header + const headers = new Headers(defaultOptions.headers) + headers.delete('Authorization') + headers.delete('authorization') + defaultOptions.headers = headers + } else { + // 非登录请求,从存储中获取 token + const token = localStorage.getItem('token') || sessionStorage.getItem('token') + if (token) { + const headers = new Headers(defaultOptions.headers) + headers.set('Authorization', `Bearer ${token}`) + defaultOptions.headers = headers + } + } + + try { + const response = await fetch(`${API_BASE_URL}${url}`, { + ...defaultOptions, + ...options, + }) + + console.log('📥 响应状态:', response.status, response.statusText) + + // 尝试读取响应文本(无论成功与否) + let responseText = '' + try { + responseText = await response.text() + console.log('📥 响应内容:', responseText) + } catch (e) { + console.log('📥 无法读取响应文本') + } + + if (!response.ok) { + let errorMessage = `HTTP ${response.status}: ${response.statusText}` + if (responseText) { + try { + const errorJson = JSON.parse(responseText) + errorMessage = errorJson.message || errorJson.error || errorMessage + } catch { + errorMessage = `${errorMessage}\n${responseText}` + } + } + + console.error('❌ 请求失败:', errorMessage) + throw new Error(errorMessage) + } + + // 尝试解析 JSON + if (responseText) { + try { + const data = JSON.parse(responseText) + console.log('✅ 解析成功的数据:', data) + return data + } catch (e) { + console.error('❌ JSON 解析失败:', e) + throw new Error(`响应不是有效的 JSON: ${responseText}`) + } + } else { + // 没有响应体的情况(如 204 No Content) + return {} as T + } + } catch (error: any) { + console.error('❌ 请求异常:', error) + + // 处理网络错误 + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('网络连接失败,请检查网络设置和后端服务') + } + + throw error + } +} + +// 封装常用 HTTP 方法 +export const api = { + get(url: string) { + return request(url, { method: 'GET' }) + }, + + post(url: string, data?: any) { + return request(url, { + method: 'POST', + body: data ? JSON.stringify(data) : undefined, + }) + }, + + put(url: string, data?: any) { + return request(url, { + method: 'PUT', + body: data ? JSON.stringify(data) : undefined, + }) + }, + + patch(url: string, data?: any) { + return request(url, { + method: 'PATCH', + body: data ? JSON.stringify(data) : undefined, + }) + }, + + delete(url: string) { + return request(url, { method: 'DELETE' }) + }, + + upload(url: string, formData: FormData) { + const headers = new Headers() + // 上传文件时不要设置 Content-Type,浏览器会自动设置 + headers.delete('Content-Type') + + return request(url, { + method: 'POST', + headers, + body: formData, + }) + }, +} \ No newline at end of file diff --git a/src/main/resources/web/src/api/types/auth.ts b/src/main/resources/web/src/api/types/auth.ts index 8734ac9..92b8e1a 100644 --- a/src/main/resources/web/src/api/types/auth.ts +++ b/src/main/resources/web/src/api/types/auth.ts @@ -1,22 +1,29 @@ -// 与后端 LoginRequest 对应的类型 +// src/api/types/auth.ts + +// 登录请求参数 - 匹配后端的 LoginRequest export interface LoginRequest { - username: string - password: string - rememberMe?: boolean + username: string + password: string + userType: string // 添加这个属性 + rememberMe?: boolean +} + +// 通用响应结构 +export interface ResultVO { + code: number + message: string + data: T } -// 与后端 LoginVO 对应的类型 +// 登录响应数据 - 匹配后端的 LoginVO export interface LoginVO { - code: number - message: string - data: { token: string userInfo: { - id: number - username: string - realName: string - role: string - avatar?: string + id: number + username: string + realName?: string // 根据后端字段调整 + role?: string // 根据后端字段调整 + userType?: string // 添加这个字段 + avatar?: string } - } } \ No newline at end of file diff --git a/src/main/resources/web/src/components/layout/AppHeader.vue b/src/main/resources/web/src/components/layout/AppHeader.vue index 6f2c653..f2fb007 100644 --- a/src/main/resources/web/src/components/layout/AppHeader.vue +++ b/src/main/resources/web/src/components/layout/AppHeader.vue @@ -4,7 +4,7 @@

校园矿化水系统

-