You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

242 lines
8.5 KiB

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 } | 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`
}
} else if (applyInfo.reason != 0) {
// 状态码映射表 - 包含状态文本和颜色类型
const statusMap: Record<string, { text: string; colorType: string }> = {
'-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<T extends { id: number | string }, K extends keyof Omit<T, 'id'>>(
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];
}