// ============================ // API 基础封装 — 真实后端对接 // ============================ const API_BASE = '/v1'; /** * 通用请求方法 * @param {string} url - 请求URL * @param {object} options - fetch options * @returns {Promise} */ async function request(url, options = {}) { const token = getToken(); const headers = { 'Content-Type': 'application/json', ...options.headers, }; if (token) { headers['Authorization'] = 'Bearer ' + token; } try { const response = await fetch(url, { ...options, headers, }); if (!response.ok) { // 处理 401 未授权 if (response.status === 401) { clearToken(); location.href = 'login.html'; throw new Error('Unauthorized'); } // 尝试解析错误信息 const errorData = await response.json().catch(() => ({ error: 'Request failed' })); throw new Error(errorData.error || 'Request failed'); } return await response.json(); } catch (error) { console.error('API request error:', error); throw error; } } // 登录 function apiLogin(username, password) { return request(API_BASE + '/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); } // 获取设备列表 function apiGetDevices(params = {}) { return new Promise((resolve) => { setTimeout(() => { let items = [...MOCK_DEVICES]; if (params.type === 'tower_crane') { items = items.filter(d => d.type === 'tower_crane'); } else if (params.type === 'elevator') { items = items.filter(d => d.type === 'elevator'); } resolve({ code: 0, data: { total: items.length, items } }); }, 300); }); } // 获取设备实时数据 function apiGetDeviceRealtime(deviceId) { return new Promise((resolve) => { setTimeout(() => { const data = getRealtimeById(deviceId); resolve({ code: 0, data }); }, 200); }); } // 获取预警列表 function apiGetAlerts(params = {}) { return new Promise((resolve) => { setTimeout(() => { let items = [...MOCK_ALERTS]; if (params.level === 'danger') { items = items.filter(a => a.level === 'danger'); } else if (params.level === 'warning') { items = items.filter(a => a.level === 'warning'); } else if (params.status === 'handled') { items = items.filter(a => a.status === 'handled' || a.status === 'ignored'); } else if (params.status === 'unread') { items = items.filter(a => a.status === 'unread'); } const unreadCount = MOCK_ALERTS.filter(a => a.status === 'unread').length; resolve({ code: 0, data: { total: items.length, unreadCount, items } }); }, 300); }); } // 获取预警详情 function apiGetAlertDetail(alertId) { return new Promise((resolve) => { setTimeout(() => { const alert = getAlertById(alertId); resolve({ code: 0, data: alert }); }, 200); }); } // 处理预警 function apiHandleAlert(alertId, action, note) { return new Promise((resolve) => { setTimeout(() => { const alert = MOCK_ALERTS.find(a => a.id === alertId); if (alert) { alert.status = action === 'handled' ? 'handled' : 'ignored'; if (note) alert.handleNote = note; } resolve({ code: 0, message: '操作成功' }); }, 500); }); } // ============================ // 隐患管理 API // ============================ // 提交隐患随手拍 async function apiSubmitReport(formData) { // 处理照片上传 let photoUrls = []; if (formData.photos && formData.photos.length > 0) { for (let index = 0; index < formData.photos.length; index++) { const photo = formData.photos[index]; // 如果是 base64,转换为 Blob let blob; if (photo.file) { blob = photo.file; } else if (photo.dataUrl) { const response = await fetch(photo.dataUrl); blob = await response.blob(); } else { continue; } const contentType = blob.type || 'image/jpeg'; const filename = buildUploadFilename(photo, index, contentType); // 获取上传 token const tokenRes = await apiGetUploadToken(filename, contentType); // 上传文件 const uploadResponse = await fetch(tokenRes.uploadUrl, { method: 'PUT', body: blob, headers: { 'Content-Type': contentType, }, }); if (!uploadResponse.ok) { throw new Error('Upload failed'); } await apiConfirmUpload(tokenRes.objectKey, filename, contentType); photoUrls.push(tokenRes.objectKey); } } // 创建隐患 const payload = { category: formData.category, severity: mapSeverityToBackend(formData.severity), description: formData.description, photos: photoUrls, }; // 添加 GPS 数据 if (formData.location) { payload.gps_lat = formData.location.lat; payload.gps_lng = formData.location.lng; } return await request(API_BASE + '/hazards', { method: 'POST', body: JSON.stringify(payload), }); } // 获取随手拍记录 function apiGetReports(filters = {}) { return new Promise((resolve) => { setTimeout(() => { const sevMap = { '较大': 'serious', '一般': 'general', '重大': 'major' }; let items = MOCK_REPORTS.map(r => ({ ...r, reported_at: r.reportedAt ? r.reportedAt.replace(' ', 'T') : r.reported_at, reporter_id: r.reporter || r.reporter_id, severity: sevMap[r.severity] || r.severity, })); if (filters.status && filters.status !== 'all') { items = items.filter(r => r.status === filters.status); } if (filters.category) { items = items.filter(r => r.category === filters.category); } if (filters.severity) { items = items.filter(r => r.severity === filters.severity); } resolve({ code: 0, data: { total: items.length, items } }); }, 300); }); } // 获取随手拍详情 async function apiGetReportDetail(id) { return await request(API_BASE + '/hazards/' + id); } // 认领隐患 async function apiAssignReport(id) { return await request(API_BASE + '/hazards/' + id + '/assign', { method: 'POST', }); } // 处理完成隐患 async function apiResolveReport(id, resolveNote) { return await request(API_BASE + '/hazards/' + id + '/resolve', { method: 'POST', body: JSON.stringify({ resolve_note: resolveNote }), }); } // 辅助:映射前端严重程度到后端格式 function mapSeverityToBackend(frontendSeverity) { const map = { '一般': 'general', '较大': 'serious', '重大': 'major', }; return map[frontendSeverity] || 'general'; } // 辅助:映射后端严重程度到前端格式 function mapSeverityToFrontend(backendSeverity) { const map = { 'general': '一般', 'serious': '较大', 'major': '重大', }; return map[backendSeverity] || '一般'; } // 辅助:映射后端状态到前端格式 function mapStatusToFrontend(backendStatus) { const map = { 'pending': 'pending', 'assigned': 'processing', 'resolved': 'resolved', }; return map[backendStatus] || 'pending'; } // 获取日志列表 function apiGetLogs() { return new Promise((resolve) => { setTimeout(() => { resolve({ code: 0, data: { total: MOCK_LOGS.length, items: MOCK_LOGS } }); }, 300); }); } // 获取日志详情 function apiGetLogDetail(id) { return new Promise((resolve) => { setTimeout(() => { const item = MOCK_LOGS.find(l => l.id === id); resolve({ code: 0, data: item }); }, 200); }); } // 提交施工日志 function apiSubmitLog(formData) { return new Promise((resolve) => { setTimeout(() => { const newLog = { id: 'log' + Date.now(), ...formData, createdAt: new Date().toLocaleString('zh-CN'), }; MOCK_LOGS.unshift(newLog); resolve({ code: 0, data: newLog }); }, 800); }); } // OSS 预签名 URL async function apiGetUploadToken(filename, contentType) { return await request(API_BASE + '/files/upload-token', { method: 'POST', body: JSON.stringify({ filename: filename, contentType: contentType, category: 'hazard', }), }); } async function apiConfirmUpload(objectKey, filename, contentType) { return await request(API_BASE + '/files/confirm', { method: 'POST', body: JSON.stringify({ objectKey: objectKey, filename: filename, contentType: contentType, }), }); } function buildUploadFilename(photo, index, contentType) { if (photo.file && photo.file.name) { return photo.file.name; } return 'hazard-' + Date.now() + '-' + index + getExtensionForContentType(contentType); } function getExtensionForContentType(contentType) { const map = { 'image/jpeg': '.jpg', 'image/png': '.png', 'image/gif': '.gif', 'image/webp': '.webp', 'application/pdf': '.pdf', 'video/mp4': '.mp4', 'video/quicktime': '.mov', }; return map[contentType] || '.bin'; } // 获取 AI 分析列表 function apiGetAIAnalyses(params = {}) { return new Promise((resolve) => { setTimeout(() => { let items = [...MOCK_AI_ANALYSES]; if (params.type && params.type !== 'all') { items = items.filter(a => a.analysisType === params.type); } resolve({ code: 0, data: { total: items.length, items } }); }, 300); }); } // 获取 AI 分析详情 function apiGetAIAnalysisDetail(id) { return new Promise((resolve) => { setTimeout(() => { const item = MOCK_AI_ANALYSES.find(a => a.id === id); resolve({ code: 0, data: item }); }, 200); }); }