import { RealtimeDeviceData, ShipRespVo } from "@/types/shorepower"; import dayjs from "dayjs"; import { MapApi } from "@/api/shorepower/map"; /** * 将毫秒时间戳格式化为 'YYYY/MM/DD HH:mm:ss' 格式 * @param timestamp 毫秒时间戳 * @returns 格式化后的时间字符串 */ export const formatTimestamp = ( timestamp: number | null | undefined, format = 'YYYY/MM/DD HH:mm:ss' ): string => { if (!timestamp) return ''; // 处理秒级时间戳(10位数字) if (timestamp < 1e12) { timestamp *= 1000; } return dayjs(timestamp).format(format); }; /** * 将两个时间戳之间的差值格式化为 "X小时Y分钟Z秒" * @param {number} startTime - 开始时间戳(毫秒) * @param {number} endTime - 结束时间戳(毫秒) * @returns {string} 格式化后的时间字符串,如 "2小时30分钟15秒" */ export function formatDuration(startTime?: number, endTime?: number): string { if (!startTime || !endTime) return '--'; const diffMs = Math.max(0, endTime - startTime); const totalSeconds = Math.floor(diffMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; let result = ''; if (hours > 0) result += `${hours}小时`; if (minutes > 0) result += `${minutes}分钟`; if (seconds > 0 || result === '') result += `${seconds}秒`; return result || '0秒'; } // 岸电使用文本构建 export function showStatus(ship: ShipRespVo, realtimeDeviceData?: RealtimeDeviceData[]): { statusClass: string, status: string, useTime: string, useValue: string } | null { const { usageRecordInfo, applyInfo } = ship; if (!applyInfo || !usageRecordInfo || !usageRecordInfo.beginTime) { return null; } if (applyInfo.reason == 0 && usageRecordInfo && usageRecordInfo.beginTime) { const start = new Date(usageRecordInfo.beginTime); const end = usageRecordInfo.endTime ? new Date(usageRecordInfo.endTime) : new Date(); // 校验日期有效性 if (isNaN(start.getTime()) || isNaN(end.getTime())) { return null; } // 校验时间顺序 if (end < start) { return null; // 或抛出错误、记录日志等 } // 计算总毫秒差 const diffMs = end.getTime() - start.getTime(); // 转换为秒 const totalSeconds = Math.floor(diffMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; let useValue = 0; // 检查是否有结束读数 const hasEndReading = usageRecordInfo.powerEndManualReading || usageRecordInfo.powerEndSystemReading; if (hasEndReading) { // 有结束读数,使用常规计算方式 // 优先计算人工差值 const manualDiff = usageRecordInfo.powerStartManualReading !== undefined && usageRecordInfo.powerEndManualReading !== undefined ? usageRecordInfo.powerEndManualReading - usageRecordInfo.powerStartManualReading : null; // 然后计算系统差值 const systemDiff = usageRecordInfo.powerStartSystemReading !== undefined && usageRecordInfo.powerEndSystemReading !== undefined ? usageRecordInfo.powerEndSystemReading - usageRecordInfo.powerStartSystemReading : null; // 使用人工差值优先,然后系统差值,最后默认0 useValue = manualDiff ?? systemDiff ?? 0; } else { // 没有结束读数,使用实时数据计算 const deviceId = ship.shorePower?.totalPowerDeviceId; if (deviceId) { try { // 调用API获取实时数据 const measureValueStr = getValueById(realtimeDeviceData || [], deviceId, 'measureValue') if (measureValueStr !== undefined) { // 优先使用人工开始读数,然后系统开始读数 const startReading = usageRecordInfo.powerStartManualReading ?? usageRecordInfo.powerStartSystemReading ?? 0; const measureValue = typeof measureValueStr === 'string' ? parseFloat(measureValueStr) : measureValueStr; useValue = measureValue - startReading; } } catch (error) { console.error('获取实时数据失败:', error); useValue = 0; } } } return { statusClass: 'status-using', status: `使用岸电${hours}小时${minutes}分钟${seconds}秒,${useValue.toFixed(2)}kWH`, useTime: `${hours}小时${minutes}分钟${seconds}秒`, useValue: useValue.toFixed(2) + 'kWH' } } else if (applyInfo.reason != 0) { // 状态码映射表 - 包含状态文本和颜色类型 const statusMap: Record = { '-1': { text: '未知错误', colorType: 'default' }, '1': { text: '岸电用电接口不匹配', colorType: 'default' }, '2': { text: '岸电设施电压/频率不匹配', colorType: 'success' }, '3': { text: '电缆长度不匹配', colorType: 'success' }, '4': { text: '气象因素禁止作业', colorType: 'success' }, '5': { text: '船电设施损坏', colorType: 'warning' }, '6': { text: '岸电设施维护中', colorType: 'warning' }, '7': { text: '无受电设备', colorType: 'danger' }, '8': { text: '拒绝使用岸电', colorType: 'danger' }, '9': { text: '其他', colorType: 'danger' } } const reasonKey = applyInfo.reason?.toString() || '-1' const statusInfo = statusMap[reasonKey] || { text: applyInfo.reason, colorType: 'default' } // 根据颜色类型设置对应的状态类 let statusClass = 'status-cable' switch (statusInfo.colorType) { case 'danger': statusClass = 'status-danger' break case 'warning': statusClass = 'status-warning' break case 'success': statusClass = 'status-success' break default: statusClass = 'status-cable' } return { statusClass: statusClass, status: statusInfo.text } } else { return { statusClass: '', status: 'unknown' } } } /** * 根据 id 查找对象数组中的指定字段值,并保留两位小数 * @param list - 对象数组,每个对象必须包含 id 字段 * @param id - 要查找的 id 值 * @param valueField - 要返回的字段名(必须是对象中除 id 外的 key) * @returns 字段值,未找到则返回 undefined */ export function getValueById>( list: T[], id: T['id'], valueField: K ): T[K] extends number | string ? string | undefined : T[K] | undefined { const item = list.find(item => item.id === id); if (!item) return undefined; const value = item[valueField]; // 如果值是数字或可以转换为数字的字符串,则保留两位小数 if (typeof value === 'number') { return Number(value).toFixed(2) as any; } if (typeof value === 'string' && !isNaN(Number(value))) { return Number(value).toFixed(2) as any; } // 否则返回原始值 return value as any; } export function parseRangeToTimestamp(range: string[], granularity: 'year' | 'month' | 'week' | 'day'): [number, number] | null { if (!range || range.length !== 2) return null; const [startStr, endStr] = range; let startDate: Date; let endDate: Date; switch (granularity) { case 'year': // '2023' → 2023-01-01 00:00:00 ~ 2023-12-31 23:59:59 startDate = new Date(`${startStr}-01-01T00:00:00`); endDate = new Date(`${endStr}-12-31T23:59:59`); break; case 'month': // '2023-05' → 2023-05-01 00:00:00 ~ 2023-05-31 23:59:59 startDate = new Date(`${startStr}-01T00:00:00`); // 获取该月最后一天 const yearMonth = startStr.split('-'); const nextMonth = new Date(+yearMonth[0], +yearMonth[1], 0); // 本月最后一天 endDate = new Date(nextMonth.getFullYear(), nextMonth.getMonth(), nextMonth.getDate(), 23, 59, 59); // 同样处理 endStr const endYearMonth = endStr.split('-'); const endNextMonth = new Date(+endYearMonth[0], +endYearMonth[1], 0); endDate = new Date(endNextMonth.getFullYear(), endNextMonth.getMonth(), endNextMonth.getDate(), 23, 59, 59); break; case 'week': case 'day': default: // '2023-05-01' → 2023-05-01 00:00:00 ~ 2023-05-01 23:59:59(week 实际也是日期) startDate = new Date(`${startStr}T00:00:00`); endDate = new Date(`${endStr}T23:59:59`); break; } const start = startDate.getTime(); const end = endDate.getTime(); if (isNaN(start) || isNaN(end)) { console.error('日期解析失败:', range, granularity); return null; } return [start, end]; }