diff --git a/.env.dev b/.env.dev index 4436bae..f9e9a5f 100644 --- a/.env.dev +++ b/.env.dev @@ -22,7 +22,7 @@ VITE_DROP_CONSOLE=false VITE_SOURCEMAP=true # 打包路径 -VITE_BASE_PATH='http://server.ayaojies.com.cn:48080' +VITE_BASE_PATH='http://106.118.88.15:48080' # 输出路径 VITE_OUT_DIR=dist diff --git a/.env.local b/.env.local index 4cfb0f3..2662574 100644 --- a/.env.local +++ b/.env.local @@ -4,7 +4,7 @@ NODE_ENV=development VITE_DEV=true # 请求路径 -VITE_BASE_URL='http://server.ayaojies.com.cn:48080' +VITE_BASE_URL='http://106.118.88.15:48080' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 VITE_UPLOAD_TYPE=server diff --git a/.env.prod b/.env.prod index 7bf2746..3eee7d1 100644 --- a/.env.prod +++ b/.env.prod @@ -4,7 +4,7 @@ NODE_ENV=production VITE_DEV=false # 请求路径 -VITE_BASE_URL='http://server.ayaojies.com.cn:48080' +VITE_BASE_URL='http://106.118.88.15:48080' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 VITE_UPLOAD_TYPE=server diff --git a/.env.stage b/.env.stage index 380e3ca..a910ed0 100644 --- a/.env.stage +++ b/.env.stage @@ -4,7 +4,7 @@ NODE_ENV=production VITE_DEV=false # 请求路径 -VITE_BASE_URL='http://server.ayaojies.com.cn:48080' +VITE_BASE_URL='http://106.118.88.15:48080' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 VITE_UPLOAD_TYPE=server diff --git a/.env.test b/.env.test index 2364857..3cdb19d 100644 --- a/.env.test +++ b/.env.test @@ -4,7 +4,7 @@ NODE_ENV=production VITE_DEV=false # 请求路径 -VITE_BASE_URL='http://server.ayaojies.com.cn:48080' +VITE_BASE_URL='http://106.118.88.15:48080' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 VITE_UPLOAD_TYPE=server diff --git a/.eslintrc.js b/.eslintrc.js index b28255c..496cfb9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -70,6 +70,7 @@ module.exports = defineConfig({ 'vue/no-v-html': 'off', 'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件 '@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐 - '@unocss/order-attributify': 'off' // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐 + '@unocss/order-attributify': 'off', // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐 + 'vue/first-attribute-linebreak': 'off' // 禁用属性换行检查,解决与格式化工具的冲突 } }) diff --git a/public.zip b/public.zip deleted file mode 100644 index 467acd8..0000000 Binary files a/public.zip and /dev/null differ diff --git a/public/img/未连接.png b/public/img/未连接.png new file mode 100644 index 0000000..5c8736b Binary files /dev/null and b/public/img/未连接.png differ diff --git a/public/map/.index.vue.swp b/public/map/.index.vue.swp deleted file mode 100644 index 05064c4..0000000 Binary files a/public/map/.index.vue.swp and /dev/null differ diff --git a/public/map/components/CompanyShorePower.vue b/public/map/components/CompanyShorePower.vue new file mode 100644 index 0000000..e7f88c9 --- /dev/null +++ b/public/map/components/CompanyShorePower.vue @@ -0,0 +1,256 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/HistoryRecordDialog.vue b/public/map/components/HistoryRecordDialog.vue new file mode 100644 index 0000000..18f5260 --- /dev/null +++ b/public/map/components/HistoryRecordDialog.vue @@ -0,0 +1,174 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/PieChart.vue b/public/map/components/PieChart.vue deleted file mode 100644 index e26879c..0000000 --- a/public/map/components/PieChart.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - - - diff --git a/public/map/components/PortOverview.vue b/public/map/components/PortOverview.vue new file mode 100644 index 0000000..6250084 --- /dev/null +++ b/public/map/components/PortOverview.vue @@ -0,0 +1,506 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/ShipHistoryDialog.vue b/public/map/components/ShipHistoryDialog.vue new file mode 100644 index 0000000..5bcccfd --- /dev/null +++ b/public/map/components/ShipHistoryDialog.vue @@ -0,0 +1,426 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/ShipShorePower.vue b/public/map/components/ShipShorePower.vue new file mode 100644 index 0000000..9d767a3 --- /dev/null +++ b/public/map/components/ShipShorePower.vue @@ -0,0 +1,1356 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/ShorePowerHistoryDialog.vue b/public/map/components/ShorePowerHistoryDialog.vue new file mode 100644 index 0000000..edcdbdc --- /dev/null +++ b/public/map/components/ShorePowerHistoryDialog.vue @@ -0,0 +1,343 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/ShorePowerUsage.vue b/public/map/components/ShorePowerUsage.vue new file mode 100644 index 0000000..e6e90c9 --- /dev/null +++ b/public/map/components/ShorePowerUsage.vue @@ -0,0 +1,1272 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/_index.vue b/public/map/components/bk/components_index.vue similarity index 100% rename from public/map/components/_index.vue rename to public/map/components/bk/components_index.vue diff --git a/public/map/_index.vue b/public/map/components/bk/map_index.vue similarity index 97% rename from public/map/_index.vue rename to public/map/components/bk/map_index.vue index 370a8c9..08ebdcb 100644 --- a/public/map/_index.vue +++ b/public/map/components/bk/map_index.vue @@ -366,7 +366,7 @@ const buildDlData = () => { } const updateDataDate = (number: number) => { if (number == 5) { - window.open("http://server.ayaojies.com.cn:801/login", '_blank'); + window.open("http://106.118.88.15:801/login", '_blank'); } else { dataDate.value = number usedDl.value = 835229.9 * number diff --git a/public/map/components/cesiumMap.vue b/public/map/components/cesiumMap.vue new file mode 100644 index 0000000..0c76e36 --- /dev/null +++ b/public/map/components/cesiumMap.vue @@ -0,0 +1,1398 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/BarChart.vue b/public/map/components/charts/BarChart.vue similarity index 100% rename from public/map/components/BarChart.vue rename to public/map/components/charts/BarChart.vue diff --git a/public/map/components/charts/BarChartMinute.vue b/public/map/components/charts/BarChartMinute.vue new file mode 100644 index 0000000..b25dc2f --- /dev/null +++ b/public/map/components/charts/BarChartMinute.vue @@ -0,0 +1,184 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/LineChart.vue b/public/map/components/charts/LineChart.vue similarity index 92% rename from public/map/components/LineChart.vue rename to public/map/components/charts/LineChart.vue index 1f3b849..3b3cd79 100644 --- a/public/map/components/LineChart.vue +++ b/public/map/components/charts/LineChart.vue @@ -37,12 +37,12 @@ const updateChart = () => { const option = { title: { - text: props.title, + // text: props.title, textStyle: { color: '#fff', fontSize: 14 }, - left: 'center' + // left: 'center' }, tooltip: { trigger: 'axis', @@ -113,10 +113,10 @@ const updateChart = () => { } ], grid: { - left: '5%', - right: '5%', - top: '15%', - bottom: '10%', + left: '1%', + right: '1%', + top: '5%', + bottom: '1%', containLabel: true } } @@ -142,7 +142,10 @@ const handleResize = () => { // 组件挂载时初始化图表 onMounted(() => { - initChart() + // 延迟初始化,确保DOM已经完全渲染 + setTimeout(() => { + initChart() + }, 200) window.addEventListener('resize', handleResize) }) @@ -160,6 +163,6 @@ onBeforeUnmount(() => { .line-chart-container { width: 100%; height: 100%; - min-height: 200px; + min-height: 0; } \ No newline at end of file diff --git a/public/map/components/charts/PieChart.vue b/public/map/components/charts/PieChart.vue new file mode 100644 index 0000000..dc0a908 --- /dev/null +++ b/public/map/components/charts/PieChart.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/public/map/components/charts/WaveLineChart.vue b/public/map/components/charts/WaveLineChart.vue new file mode 100644 index 0000000..b241d3b --- /dev/null +++ b/public/map/components/charts/WaveLineChart.vue @@ -0,0 +1,254 @@ + + + + + \ No newline at end of file diff --git a/public/map/components/dictionaryTable.ts b/public/map/components/dictionaryTable.ts new file mode 100644 index 0000000..8e8ec49 --- /dev/null +++ b/public/map/components/dictionaryTable.ts @@ -0,0 +1,120 @@ +import { MapApi } from "@/api/shorepower/map"; + +export const getOperationTypeLabel = (value: string | number | undefined | null, options: { label: string, value: string | number }[]) => { + try { + const item = options.find(opt => opt.value === value); + return item ? item.label : ''; + } catch (error) { + return '' + } + +}; + +// 装货/卸货 +export const OPERATION_TYPE = [ + { label: '装货', value: 1 }, + { label: '卸货', value: 2 }, + { label: '卸货并装货', value: 2 }, +] + +// 获取类型 +export const CARGO_CATEGORY = [ + { label: '空船', value: 0 }, + { label: '散货', value: 1 }, + { label: '集装箱', value: 2 }, + { label: '液体货物', value: 3 }, + { label: '件杂货', value: 4 }, + { label: '危险品', value: 5 }, + { label: '冷藏货物', value: 6 }, +] + +// 未使用岸电原因 +export const UNUSED_SHORE_POWER_REASON = [ + { label: '岸电用电接口不匹配', value: 1 }, + { label: '岸电设施电压/频率不匹配', value: 2 }, + { label: '电缆长度不匹配', value: 3 }, + { label: '气象因素禁止作业', value: 4 }, + { label: '船电设施损坏', value: 5 }, + { label: '岸电设施维护中', value: 6 }, + { label: '无受电设备', value: 7 }, + { label: '拒绝使用岸电', value: 8 }, + { label: '其他', value: 9 }, +] + +// 港区map +export const HARBOR_DISTRICT = async () => { + const res = await MapApi.getHarborDistrictIdAndNameList() + return res.map(item => ({ + ...item, + label: item.name, + value: item.id + })) +} + +// doc 码头map +export const DOCK_DISTRICT = async () => { + const res = await MapApi.getDockIdAndNameList() + return res.map(item => ({ + ...item, + label: item.name, + value: item.id + })) +} + +// 岸电状态 +export const SHORE_POWER_STATUS = [ + { label: '待靠泊', value: 1 }, + { label: '靠泊中', value: 2 }, + { label: '岸电接入中', value: 3 }, + { label: '用电中', value: 4 }, + { label: '岸电卸载中', value: 5 }, + { label: '岸电卸载完成', value: 6 }, + { label: '离泊', value: 7 }, + { label: '未使用岸电', value: 9 } +] + +export const BERTH_TYPE = [ + { label: '左舷停舶', value: 'left' }, + { label: '右舷停舶', value: 'right' }, +] + +// 设施类型 +export const FACILITY_TYPE = [ + { label: '港区', value: 1 }, + { label: '码头', value: 2 }, + { label: '岸电设施', value: 3 }, + { label: '泊位', value: 4 }, + { label: '用电接口', value: 5 }, +] + +// 贸易类型 +export const TRADE_TYPE = [ + { label: '内贸', value: 1 }, + { label: '外贸', value: 2 }, +] + +// 作业状态 +export const WORK_STATUS = [ + { label: '待作业', value: 1 }, + { label: '前往作业中', value: 2 }, + { label: '接电中', value: 3 }, + { label: '待拆除岸电', value: 4 }, + { label: '岸电卸载中', value: 5 }, + { label: '待上报数据', value: 6 }, + { label: '作业完成', value: 7 }, + { label: '未成功接入', value: 9 }, +] + +// 申请状态 +export const APPLY_STATUS = [ + { label: '待签合同', value: 2 }, + { label: '待付款', value: 3 }, + { label: '待确认收款', value: 4 }, + { label: '待送电', value: 5 }, + { label: '用电中', value: 6 }, + { label: '信息收集中', value: 7 }, + { label: '待退款', value: 8 }, + { label: '用电完成', value: 9 }, + { label: '申请完成', value: 10 }, + { label: '已取消', value: 11 }, +] \ No newline at end of file diff --git a/public/map/components/index.html b/public/map/components/index.html deleted file mode 100644 index 0305cf7..0000000 --- a/public/map/components/index.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - CesiumJS 天地图纯影像示例 - - - - - - - -
- - - -
- - - - - -
- - - - \ No newline at end of file diff --git a/public/map/components/index.vue b/public/map/components/index.vue deleted file mode 100644 index e3c5aba..0000000 --- a/public/map/components/index.vue +++ /dev/null @@ -1,529 +0,0 @@ - - - - - \ No newline at end of file diff --git a/public/map/components/utils.ts b/public/map/components/utils.ts new file mode 100644 index 0000000..1c0c147 --- /dev/null +++ b/public/map/components/utils.ts @@ -0,0 +1,244 @@ +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]; +} \ No newline at end of file diff --git a/public/map/index.vue b/public/map/index.vue index 87d8ce3..18c3182 100644 --- a/public/map/index.vue +++ b/public/map/index.vue @@ -1,13 +1,17 @@