jiangAB 2 months ago
parent
commit
a05f704884
  1. 92
      public/map/components/CompanyShorePower.vue
  2. 234
      public/map/components/PortOverview.vue
  3. 242
      public/map/components/ShipHistoryDialog.vue
  4. 10
      public/map/components/ShipShorePower.vue
  5. 4
      public/map/components/ShorePowerHistoryDialog.vue
  6. 540
      public/map/components/ShorePowerUsage.vue
  7. 984
      public/map/components/cesiumMap.vue
  8. 13
      public/map/components/charts/WaveLineChart.vue
  9. 9
      public/map/components/dictionaryTable.ts
  10. 48
      public/map/components/utils.ts
  11. 283
      public/map/index.vue

92
public/map/components/CompanyShorePower.vue

@ -9,10 +9,10 @@
<img src="@/assets/svgs/data.svg" class="title-icon" /> <img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">码头与泊位信息</span> <span class="title-text">码头与泊位信息</span>
</div> </div>
<div> <!-- <div>
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" <el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" /> format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" />
</div> </div> -->
</div> </div>
@ -20,11 +20,13 @@
<div v-for="company in companyComparisonData" :key="company.name" class="company-section"> <div v-for="company in companyComparisonData" :key="company.name" class="company-section">
<div class="company-header"> <div class="company-header">
<div class="company-name">{{ company.name }}</div> <div class="company-name">{{ company.name }}</div>
<div class="company-total">总量: {{ company.value }}</div> <div class="company-total">总量: {{ calculateTotal(company.children).toFixed(2) }}</div>
</div> </div>
<div class="overview-grid"> <div class="overview-grid">
<div v-for="(berth, index) in (company.children || [])" :key="index" class="overview-item"> <div v-for="(berth, index) in (company.children || [])" :key="index" class="overview-item"
<div class="overview-value">{{ berth.value }}</div> @click="showShorePowerHistory(berth)">
<div class="overview-value">{{ berth.measureValue?.toFixed(2)
|| 0 }}</div>
<div class="overview-label">{{ berth.name }}</div> <div class="overview-label">{{ berth.name }}</div>
@ -34,24 +36,97 @@
</div> </div>
</div> </div>
</div> </div>
<ship-history-dialog v-model="historyVisible.visible" :ship-param="historyVisible.searchParams"
:realtime-device-data="realtimeDeviceData" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { MapApi } from "@/api/shorepower/map";
import ShipHistoryDialog from './ShipHistoryDialog.vue';
import { RealtimeDeviceData } from '@/types/shorepower';
// //
interface ChartDataItem { interface ChartDataItem {
name: string; name: string;
value: number; value?: number;
measureValue?: number;
children?: ChartDataItem[]; children?: ChartDataItem[];
} }
const historyVisible = ref({
visible: false,
searchParams: {
shipId: 0 as number | null,
ids: [] as number[] | null,
type: 1 as number
}
})
//
const calculateTotal = (children?: ChartDataItem[]) => {
if (!children || children.length === 0) return 0;
return children.reduce((total, item) => {
return total + (item.measureValue || 0);
}, 0);
};
//
const showShorePowerHistory = (berth: ChartDataItem) => {
console.log(berth)
// console.log('shorepower', shorepower)
// currentShorePower.value = shorepower
/* shorePowerHistoryVisible.value = {
visible: true,
shorePowerId: shorepower.id || 0
} */
historyVisible.value = {
visible: true,
searchParams: {
shipId: null,
ids: [berth.id],
type: 4,
}
}
}
interface Props { interface Props {
companyComparisonData: ChartDataItem[]; companyComparisonData?: ChartDataItem[];
realtimeDeviceData: RealtimeDeviceData[];
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const companyComparisonData = ref<ChartDataItem[]>([])
const handleGetBuildData = async () => {
const dockList = await MapApi.getDockIdAndNameList()
const berthList = await MapApi.getBerthIdAndNameList()
const buildData = dockList.map(dock => ({
...dock,
children: berthList.filter(berth => berth.dockId === dock.id).map(berth => ({
...berth,
...props.realtimeDeviceData.find(item => (item.id === berth.id) && (item.deviceCode.includes('Kwh')))
}))
}))
console.log('buildData', buildData)
console.log('props.realtimeDeviceData', props.realtimeDeviceData)
companyComparisonData.value = buildData
/* MapApi.getBerthIdAndNameList().then(res => {
console.log(res)
const buildData = res.map(item => ({
...item,
children: props.realtimeDeviceData.filter(item => item.id === item.id)
}))
}) */
}
watch(() => props.realtimeDeviceData, (newVal) => {
handleGetBuildData()
})
onMounted(() => {
// console.log(props.realtimeDeviceData)
handleGetBuildData()
})
</script> </script>
<style scoped> <style scoped>
@ -92,6 +167,7 @@ const props = defineProps<Props>()
} }
.overview-item { .overview-item {
cursor: pointer;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

234
public/map/components/PortOverview.vue

@ -10,7 +10,7 @@
<span class="title-text">岸电箱状态</span> <span class="title-text">岸电箱状态</span>
</div> </div>
<input class="search-container" type="text" placeholder="搜索岸电设备" v-model="storeSearchKeyword" <input class="search-container" type="text" placeholder="搜索岸电设备" v-model="storeSearchKeyword"
@input="handleSearch" /> @input="handlStoreSearch" />
</div> </div>
<div class=" card-content"> <div class=" card-content">
@ -21,7 +21,7 @@
<div class="ship-table-column ship-status-header">历史</div> <div class="ship-table-column ship-status-header">历史</div>
</div> </div>
<div class="ship-table-body"> <div class="ship-table-body">
<div v-for="shorepower in ShorePowerList" :key="shorepower.id" class="ship-table-row" <div v-for="shorepower in filteredShorePowerList" :key="shorepower.id" class="ship-table-row"
@click="handleSelectShorePower(shorepower)"> @click="handleSelectShorePower(shorepower)">
<div class="ship-table-column ship-name"> <div class="ship-table-column ship-name">
<div class="ship-icon"></div> <div class="ship-icon"></div>
@ -66,7 +66,7 @@
</div> </div>
<div class="ship-table-body"> <div class="ship-table-body">
<div v-for="ship in filteredShipStatusData" :key="ship.id" class="ship-table-row" <div v-for="ship in filteredShipStatusData" :key="ship.id" class="ship-table-row"
@click="handleSwitch(ship, 'ship')"> @click="handleSelectItem(ship)">
<div class="ship-table-column ship-name"> <div class="ship-table-column ship-name">
<div class="ship-icon">🚢</div> <div class="ship-icon">🚢</div>
<span class="ship-name-text">{{ ship.shipBasicInfo.name }}</span> <span class="ship-name-text">{{ ship.shipBasicInfo.name }}</span>
@ -92,7 +92,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="right" :style="`width: ${show_type === 'ship' ? '45%' : '20%'}`"> <div v-if="shipSelectedItem || shorepowerSelectedItem" class="right"
:style="`width: ${show_type === 'ship' ? '45%' : '20%'}`">
<div v-if="shipSelectedItem && show_type === 'ship'" class="right-two-row"> <div v-if="shipSelectedItem && show_type === 'ship'" class="right-two-row">
<div class="card digital-twin-card--deep-blue"> <div class="card digital-twin-card--deep-blue">
<div class="card-title"> <div class="card-title">
@ -263,27 +264,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="card digital-twin-card--deep-blue">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">岸电信息</span>
</div>
<div class="card-content">
<div class="ship-detail">
<div class="detail-item">
<span class="label">停泊状态:</span>
<span class="value">{{ selectedItem.shorePowerAndShip.type === 'left' ? '左舷停泊' :
selectedItem.shorePowerAndShip.type === 'right' ? '右舷停泊' : selectedItem.shorePowerAndShip.type
}}</span>
</div>
<div class="detail-item">
<span class="label">靠泊状态:</span>
<span class="value">{{ getShorePowerStatusText(selectedItem.shorePowerAndShip.status) }}</span>
</div>
</div>
</div>
</div> -->
</div> </div>
<!-- <div v-else class="card digital-twin-card--deep-blue" style="flex: 1;"> <!-- <div v-else class="card digital-twin-card--deep-blue" style="flex: 1;">
@ -297,28 +277,17 @@
</div> </div>
</div> --> </div> -->
</div> </div>
<ShipHistoryDialog v-model="shipHistoryVisible.visible" :ship-param="shipHistoryVisible.searchParams"
<!-- 岸电箱历史记录弹窗 -->
<ShorePowerHistoryDialog v-model="shorePowerHistoryVisible.visible" :data="shorePowerHistoryData"
:ship-connection-data="shorePowerConnectionData" @search="handleShorePowerHistorySearch"
@ship-connection-search="handleShorePowerConnectionSearch"
:shore-power-id="shorePowerHistoryVisible.shorePowerId" />
<!-- 船舶历史记录弹窗 -->
<ShipHistoryDialog v-model="shipHistoryVisible.visible" :berthing-data="shipHistoryData"
:shore-power-connection-data="shipShorePowerConnectionData" @berthing-search="handleShipHistorySearch"
@shore-power-connection-search="handleShipShorePowerConnectionSearch" :ship-id="shipHistoryVisible.shipId"
:realtime-device-data="realtimeDeviceData" /> :realtime-device-data="realtimeDeviceData" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import ShorePowerHistoryDialog from './ShorePowerHistoryDialog.vue'
import ShipHistoryDialog from './ShipHistoryDialog.vue' import ShipHistoryDialog from './ShipHistoryDialog.vue'
import { MapApi } from "@/api/shorepower/map"; import { MapApi } from "@/api/shorepower/map";
import { RealtimeDeviceData, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'; import { RealtimeDeviceData, ShipRespVo, ShorePowerBerth } from '@/types/shorepower';
import { BERTH_TYPE, DOCK_DISTRICT, getOperationTypeLabel, HARBOR_DISTRICT, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './dictionaryTable'; import { BERTH_TYPE, getOperationTypeLabel, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './dictionaryTable';
import { formatDuration, formatTimestamp, getValueById, showStatus } from './utils'; import { formatDuration, formatTimestamp, getValueById, showStatus } from './utils';
// //
type ShipItem = ShipRespVo & { id: string; modelInstance?: any; }; type ShipItem = ShipRespVo & { id: string; modelInstance?: any; };
@ -327,6 +296,8 @@ interface Props {
shipStatusData: ShipItem[]; shipStatusData: ShipItem[];
shorePowerStatusData: ShipItem[]; shorePowerStatusData: ShipItem[];
realtimeDeviceData: RealtimeDeviceData[]; realtimeDeviceData: RealtimeDeviceData[];
shorePowerList: (ShorePowerBerth & { position: string; })[];
shipDataList: ShipRespVo[];
// selectedItem: ShipItem | null; // selectedItem: ShipItem | null;
} }
@ -339,77 +310,41 @@ const show_type = ref('ship')
const shipSelectedItem = ref<ShipItem | null>(null) const shipSelectedItem = ref<ShipItem | null>(null)
const shorepowerSelectedItem = ref<ShorePowerBerth & { position: string; } | null>(null) const shorepowerSelectedItem = ref<ShorePowerBerth & { position: string; } | null>(null)
//
const shorePowerHistoryVisible = ref({
visible: false,
shorePowerId: 0
})
const shipHistoryVisible = ref({ const shipHistoryVisible = ref({
visible: false, visible: false,
shipId: 0 searchParams: {
shipId: 0 as number | null,
ids: [] as number[] | null,
type: 1 as number
}
}) })
const currentShorePower = ref<ShorePowerBerth | null>(null) const currentShorePower = ref<ShorePowerBerth | null>(null)
const currentShip = ref<ShipItem | null>(null) const currentShip = ref<ShipItem | null>(null)
const ShorePowerList = ref<(ShorePowerBerth & { position: string; })[]>([]) // const ShorePowerList = ref<(ShorePowerBerth & { position: string; })[]>([])
// //
const filteredShipStatusData = computed(() => { const filteredShipStatusData = computed(() => {
if (!searchKeyword.value) { if (!searchKeyword.value) {
return props.shipStatusData return props.shipDataList
} }
return props.shipStatusData.filter(ship => return props.shipDataList.filter(ship =>
ship.shipBasicInfo.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ship.shipBasicInfo.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
) )
}) })
// //
const shorePowerHistoryData = ref<any[]>([ const filteredShorePowerList = computed(() => {
{ id: 1, time: '2023-05-01 08:30:00', operation: '连接', voltage: '380', current: '120', power: '45.6', energy: '12.5' }, if (!storeSearchKeyword.value) {
{ id: 2, time: '2023-05-01 12:15:00', operation: '运行', voltage: '382', current: '118', power: '45.1', energy: '35.2' }, return props.shorePowerList
{ id: 3, time: '2023-05-01 16:45:00', operation: '断开', voltage: '0', current: '0', power: '0', energy: '0' }, }
{ id: 4, time: '2023-05-02 09:20:00', operation: '连接', voltage: '379', current: '122', power: '46.3', energy: '8.7' }, return props.shorePowerList.filter(shorepower =>
{ id: 5, time: '2023-05-02 14:30:00', operation: '运行', voltage: '381', current: '119', power: '45.4', energy: '28.9' }, shorepower.name.toLowerCase().includes(storeSearchKeyword.value.toLowerCase())
{ id: 6, time: '2023-05-02 18:50:00', operation: '断开', voltage: '0', current: '0', power: '0', energy: '0' }, )
{ id: 7, time: '2023-05-03 07:45:00', operation: '连接', voltage: '380', current: '121', power: '45.9', energy: '15.3' }, })
{ id: 8, time: '2023-05-03 13:20:00', operation: '运行', voltage: '382', current: '120', power: '45.8', energy: '42.1' },
{ id: 9, time: '2023-05-03 17:30:00', operation: '断开', voltage: '0', current: '0', power: '0', energy: '0' },
{ id: 10, time: '2023-05-04 10:15:00', operation: '连接', voltage: '378', current: '123', power: '46.5', energy: '9.8' }
])
//
const shipHistoryData = ref<any[]>([
{ id: 1, time: '2023-04-15 09:30:00', operation: '靠泊', berthPosition: '1号泊位', duration: '12.5', powerConsumption: '125.6' },
{ id: 2, time: '2023-04-16 14:20:00', operation: '离泊', berthPosition: '1号泊位', duration: '0', powerConsumption: '0' },
{ id: 3, time: '2023-04-20 08:45:00', operation: '靠泊', berthPosition: '3号泊位', duration: '8.2', powerConsumption: '89.3' },
{ id: 4, time: '2023-04-21 16:30:00', operation: '离泊', berthPosition: '3号泊位', duration: '0', powerConsumption: '0' },
{ id: 5, time: '2023-04-25 11:15:00', operation: '靠泊', berthPosition: '2号泊位', duration: '15.7', powerConsumption: '156.8' },
{ id: 6, time: '2023-04-26 18:40:00', operation: '离泊', berthPosition: '2号泊位', duration: '0', powerConsumption: '0' },
{ id: 7, time: '2023-05-01 07:20:00', operation: '靠泊', berthPosition: '1号泊位', duration: '18.3', powerConsumption: '189.2' },
{ id: 8, time: '2023-05-02 15:50:00', operation: '离泊', berthPosition: '1号泊位', duration: '0', powerConsumption: '0' },
{ id: 9, time: '2023-05-05 09:10:00', operation: '靠泊', berthPosition: '4号泊位', duration: '6.4', powerConsumption: '67.5' },
{ id: 10, time: '2023-05-06 13:45:00', operation: '离泊', berthPosition: '4号泊位', duration: '0', powerConsumption: '0' }
])
//
const shorePowerConnectionData = ref<any[]>([
{ id: 1, shipName: '远航号', time: '2023-05-01 08:30:00', duration: '4.5', powerConsumption: '125.6', status: '已断开' },
{ id: 2, shipName: '海鹰号', time: '2023-05-01 12:15:00', duration: '3.2', powerConsumption: '89.3', status: '已断开' },
{ id: 3, shipName: '巨轮号', time: '2023-05-02 09:20:00', duration: '6.8', powerConsumption: '189.2', status: '连接中' },
{ id: 4, shipName: '远洋号', time: '2023-05-03 07:45:00', duration: '2.1', powerConsumption: '67.5', status: '已断开' },
{ id: 5, shipName: '海豚号', time: '2023-05-04 10:15:00', duration: '5.3', powerConsumption: '156.8', status: '连接中' }
])
//
const shipShorePowerConnectionData = ref<any[]>([
{ id: 1, shorePowerName: '1号岸电箱', time: '2023-05-01 08:30:00', duration: '4.5', powerConsumption: '125.6', status: '已断开' },
{ id: 2, shorePowerName: '2号岸电箱', time: '2023-05-01 12:15:00', duration: '3.2', powerConsumption: '89.3', status: '已断开' },
{ id: 3, shorePowerName: '1号岸电箱', time: '2023-05-02 09:20:00', duration: '6.8', powerConsumption: '189.2', status: '连接中' },
{ id: 4, shorePowerName: '3号岸电箱', time: '2023-05-03 07:45:00', duration: '2.1', powerConsumption: '67.5', status: '已断开' },
{ id: 5, shorePowerName: '2号岸电箱', time: '2023-05-04 10:15:00', duration: '5.3', powerConsumption: '156.8', status: '连接中' }
])
const handleSelectItem = async (item: ShipItem) => { const handleSelectItem = async (item: ShipRespVo) => {
const deviceId = item.shorePower?.totalPowerDeviceId; const deviceId = item.shorePower?.totalPowerDeviceId;
const res = await MapApi.getRealtimeDataByIdList({ ids: deviceId }) const res = await MapApi.getRealtimeDataByIdList({ ids: deviceId })
console.log(res) console.log(res)
@ -420,29 +355,37 @@ const handleSelectItem = async (item: ShipItem) => {
'measureValue': res[0].measureValue || 'N/A', 'measureValue': res[0].measureValue || 'N/A',
} }
} }
} emit('item-click', {
type: 'ship',
const filteredShorePowerStatusData = computed(() => { item: item
if (!storeSearchKeyword.value) {
return props.shipStatusData
}
return props.shipStatusData.filter(ship =>
ship.shorePowerEquipment.name.toLowerCase().includes(storeSearchKeyword.value.toLowerCase())
)
}) })
}
// //
const handleSearch = () => { const handleSearch = () => {
// //
} }
//
const handlStoreSearch = () => {
//
}
// //
const showShorePowerHistory = (shorepower: ShorePowerBerth) => { const showShorePowerHistory = (shorepower: ShorePowerBerth) => {
console.log('shorepower', shorepower) console.log('shorepower', shorepower)
currentShorePower.value = shorepower currentShorePower.value = shorepower
shorePowerHistoryVisible.value = { /* shorePowerHistoryVisible.value = {
visible: true, visible: true,
shorePowerId: shorepower.id || 0 shorePowerId: shorepower.id || 0
} */
shipHistoryVisible.value = {
visible: true,
searchParams: {
shipId: null,
ids: [shorepower.id],
type: 5,
}
} }
} }
@ -452,53 +395,18 @@ const showShipHistory = (ship: ShipItem) => {
currentShip.value = ship currentShip.value = ship
shipHistoryVisible.value = { shipHistoryVisible.value = {
visible: true, visible: true,
shipId: ship.shipBasicInfo.id searchParams: {
shipId: ship.shipBasicInfo.id,
ids: null,
type: 1,
} }
} }
//
const handleShorePowerHistorySearch = (params: any) => {
console.log('岸电箱历史记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('岸电箱历史记录搜索完成')
}, 500)
}
//
const handleShipHistorySearch = (params: any) => {
console.log('船舶历史记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('船舶历史记录搜索完成')
}, 500)
}
//
const handleShorePowerConnectionSearch = (params: any) => {
console.log('岸电箱连接船舶记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('岸电箱连接船舶记录搜索完成')
}, 500)
}
//
const handleShipShorePowerConnectionSearch = (params: any) => {
console.log('船舶连接岸电箱记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('船舶连接岸电箱记录搜索完成')
}, 500)
} }
// //
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'switch-ship', ship: ShipItem): void; (e: 'switch-ship', ship: ShipItem): void;
(e: 'item-click', item: any): void;
// handleSelectItem(ship) // handleSelectItem(ship)
}>() }>()
@ -516,21 +424,14 @@ const handleSelectShorePower = async (shorepower: ShorePowerBerth & { position:
// selectedItem.value = shorepower // selectedItem.value = shorepower
shorepowerSelectedItem.value = shorepower shorepowerSelectedItem.value = shorepower
show_type.value = 'shorepower' show_type.value = 'shorepower'
emit('item-click', {
type: 'shorepower_box',
item: shorepower
})
// const data = await MapApi.getRealtimeDataByIdList({ ids: shorepower.totalPowerDeviceId }) // const data = await MapApi.getRealtimeDataByIdList({ ids: shorepower.totalPowerDeviceId })
// console.log('voltageDeviceId', data) // console.log('voltageDeviceId', data)
} }
const shorePowerStatusMap = {
1: '待靠泊',
2: '靠泊中',
3: '岸电接入中',
4: '用电中',
5: '岸电卸载中',
6: '岸电卸载完成',
7: '离泊',
9: '未使用岸电'
}
const StatusMap = { const StatusMap = {
1: '故障', 1: '故障',
2: '故障', 2: '故障',
@ -549,13 +450,6 @@ const StatusText = (status: number | string) => {
return StatusMap[statusNum] || status; return StatusMap[statusNum] || status;
} }
//
const getShorePowerStatusText = (status: number | string) => {
const statusNum = Number(status);
// console.log(status)
return shorePowerStatusMap[statusNum] || status;
}
const getStatusClass = (status: string) => { const getStatusClass = (status: string) => {
switch (status) { switch (status) {
case '正常': case '正常':
@ -579,22 +473,8 @@ const getStatusClass = (status: string) => {
} }
} }
const handleGetStorePower = async () => { watch(() => props.shipDataList, (newVal) => {
// console.log('loading') console.log('newVal', newVal)
const harborDistrictId = 1
const harborDistrictList = await HARBOR_DISTRICT()
const dockDistrictList = await DOCK_DISTRICT()
const res = await MapApi.getShorepowerIdAndNameListByHarborDistrictId(harborDistrictId)
ShorePowerList.value = res.map(item => ({
...item,
position: `${getOperationTypeLabel(harborDistrictId, harborDistrictList)}${getOperationTypeLabel(item.shorePowerEquipmentInfo.dockId, dockDistrictList)}${item.name} `,
}))
console.log('11', res)
}
onMounted(async () => {
await handleGetStorePower()
// console.log(props.shipStatusData)
}) })
onMounted(() => { onMounted(() => {

242
public/map/components/ShipHistoryDialog.vue

@ -1,21 +1,26 @@
<template> <template>
<el-dialog v-model="visible" title="船舶历史记录" width="1200px" :before-close="handleClose"> <el-dialog v-model="visible" title="历史记录" width="1200px" :before-close="handleClose">
<el-tabs v-model="activeTab"> <!-- <el-tabs v-model="activeTab"> -->
<!-- 船舶靠泊历史记录 tab --> <!-- 船舶靠泊历史记录 tab -->
<el-tab-pane label="船舶靠泊历史记录" name="shipBerthing"> <!-- <el-tab-pane label="船舶靠泊历史记录" name="shipBerthing"> -->
<!-- 搜索和筛选区域 --> <!-- 搜索和筛选区域 -->
<div class="filter-container"> <div class="filter-container">
<el-form :model="berthingQueryParams" label-width="80px" inline> <el-form :model="berthingQueryParams" label-width="80px" inline>
<el-form-item label="关键字"> <el-form-item label="类型">
<el-select v-model="berthingQueryParams.type" style="width: 120px" placeholder="请选择类型">
<el-option v-for="item in FACILITY_TYPE" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<!-- <el-form-item label="关键字">
<el-input v-model="berthingQueryParams.keyword" placeholder="请输入关键字搜索" clearable <el-input v-model="berthingQueryParams.keyword" placeholder="请输入关键字搜索" clearable
@keyup.enter="handleBerthingSearch" /> @keyup.enter="handleBerthingSearch" />
</el-form-item> </el-form-item> -->
<el-form-item label="时间范围"> <el-form-item label="时间范围">
<el-date-picker v-model="berthingDateRange" type="daterange" range-separator="" start-placeholder="开始日期" <el-date-picker v-model="berthingDateRange" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleBerthingDateChange" /> end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleBerthingDateChange" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleBerthingSearch">搜索</el-button> <el-button type="primary" @click="handleShorePowerConnectionSearch">搜索</el-button>
<el-button @click="resetBerthingQuery">重置</el-button> <el-button @click="resetBerthingQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -23,22 +28,21 @@
<!-- 数据表格 --> <!-- 数据表格 -->
<el-table :data="berthingTableData" border stripe style="width: 100%" v-loading="berthingLoading"> <el-table :data="berthingTableData" border stripe style="width: 100%" v-loading="berthingLoading">
<el-table-column v-for="column in berthingColumns" :key="column.prop" :prop="column.prop" <el-table-column v-for="column in berthingColumns" :key="column.prop" :prop="column.prop" :label="column.label"
:label="column.label" :width="column.width" :formatter="column.formatter" /> :width="column.width" :formatter="column.formatter" />
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<div class="pagination-container"> <div class="pagination-container">
<el-pagination v-model:current-page="berthingQueryParams.pageNo" <el-pagination v-model:current-page="berthingQueryParams.pageNo" v-model:page-size="berthingQueryParams.pageSize"
v-model:page-size="berthingQueryParams.pageSize" :page-sizes="[10, 20, 50, 100]" :total="berthingTotal" :page-sizes="[10, 20, 50, 100]" :total="berthingTotal" layout="total, sizes, prev, pager, next, jumper"
layout="total, sizes, prev, pager, next, jumper" @size-change="handleBerthingSizeChange" @size-change="handleBerthingSizeChange" @current-change="handleBerthingCurrentChange" />
@current-change="handleBerthingCurrentChange" />
</div> </div>
</el-tab-pane> <!-- </el-tab-pane> -->
<!-- <el-tab-pane label="船舶连接岸电箱历史记录" name="shorePowerConnection">
<!-- 船舶连接岸电箱历史记录 tab -->
<el-tab-pane label="船舶连接岸电箱历史记录" name="shorePowerConnection">
<!-- 搜索和筛选区域 -->
<div class="filter-container"> <div class="filter-container">
<el-form :model="shorePowerConnectionQueryParams" label-width="80px" inline> <el-form :model="shorePowerConnectionQueryParams" label-width="80px" inline>
<el-form-item label="关键字"> <el-form-item label="关键字">
@ -57,23 +61,23 @@
</el-form> </el-form>
</div> </div>
<!-- 数据表格 -->
<el-table :data="shorePowerConnectionTableData" border stripe style="width: 100%" <el-table :data="shorePowerConnectionTableData" border stripe style="width: 100%"
v-loading="shorePowerConnectionLoading"> v-loading="shorePowerConnectionLoading">
<el-table-column v-for="column in shorePowerConnectionColumns" :key="column.prop" :prop="column.prop" <el-table-column v-for="column in shorePowerConnectionColumns" :key="column.prop" :prop="column.prop"
:label="column.label" :width="column.width" :formatter="column.formatter" /> :label="column.label" :width="column.width" :formatter="column.formatter" />
</el-table> </el-table>
<!-- 分页 -->
<div class="pagination-container"> <div class="pagination-container">
<el-pagination v-model:current-page="shorePowerConnectionQueryParams.pageNo" <el-pagination v-model:current-page="shorePowerConnectionQueryParams.pageNo"
v-model:page-size="shorePowerConnectionQueryParams.pageSize" :page-sizes="[10, 20, 50, 100]" v-model:page-size="shorePowerConnectionQueryParams.pageSize" :page-sizes="[10, 20, 50, 100]"
:total="shorePowerConnectionTotal" layout="total, sizes, prev, pager, next, jumper" :total="shorePowerConnectionTotal" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleShorePowerConnectionSizeChange" @size-change="handleShorePowerConnectionSizeChange"
@current-change="handleShorePowerConnectionCurrentChange" /> @current-change="handleShorePowerConnectionCurrentChange" />
</div> </div> -->
</el-tab-pane> <!-- </el-tab-pane> -->
</el-tabs> <!-- </el-tabs> -->
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@ -99,7 +103,7 @@ import {
ElTableColumn, ElTableColumn,
ElPagination ElPagination
} from 'element-plus' } from 'element-plus'
import { CARGO_CATEGORY, getOperationTypeLabel, OPERATION_TYPE } from './dictionaryTable'; import { CARGO_CATEGORY, FACILITY_TYPE, getOperationTypeLabel, OPERATION_TYPE } from './dictionaryTable';
import { formatDuration, formatTimestamp, showStatus } from './utils'; import { formatDuration, formatTimestamp, showStatus } from './utils';
import { RealtimeDeviceData } from '@/types/shorepower'; import { RealtimeDeviceData } from '@/types/shorepower';
@ -107,7 +111,7 @@ import { RealtimeDeviceData } from '@/types/shorepower';
interface Props { interface Props {
modelValue: boolean modelValue: boolean
berthingData?: any[] berthingData?: any[]
shipId?: number shipParam?: { type: number, ids: number[] | null, shipId: number | null }
shorePowerConnectionData?: any[] shorePowerConnectionData?: any[]
realtimeDeviceData: RealtimeDeviceData[]; realtimeDeviceData: RealtimeDeviceData[];
} }
@ -133,13 +137,14 @@ const visible = computed({
set: (val) => emit('update:modelValue', val) set: (val) => emit('update:modelValue', val)
}) })
const activeTab = ref('shipBerthing')
// //
const berthingQueryParams = ref({ const berthingQueryParams = ref({
keyword: '', // keyword: '',
startTime: '', startTime: '',
endTime: '', endTime: '',
ids: null as number[] | null,
shipId: null as number | null,
type: 1 as number | null,
pageNo: 1, pageNo: 1,
pageSize: 10 pageSize: 10
}) })
@ -225,41 +230,25 @@ const berthingColumns = ref([
]) ])
//
const shorePowerConnectionQueryParams = ref({
keyword: '',
startTime: '',
endTime: '',
pageNo: 1,
pageSize: 10
})
const shorePowerConnectionDateRange = ref<[string, string]>(['', ''])
const shorePowerConnectionLoading = ref(false)
const shorePowerConnectionTotal = ref(0) const shorePowerConnectionTotal = ref(0)
// //
const shorePowerConnectionTableData = computed(() => { const handleBerthingDateChange = (val: [string, string]) => {
// if (val && val.length === 2) {
// 使 berthingQueryParams.value.startTime = val[0] + ' 00:00:00'
return props.shorePowerConnectionData?.slice( berthingQueryParams.value.endTime = val[1] + ' 23:59:59'
(shorePowerConnectionQueryParams.value.pageNo - 1) * shorePowerConnectionQueryParams.value.pageSize, } else {
shorePowerConnectionQueryParams.value.pageNo * shorePowerConnectionQueryParams.value.pageSize berthingQueryParams.value.startTime = ''
) || [] berthingQueryParams.value.endTime = ''
}) }
handleShorePowerConnectionSearch()
// }
const shorePowerConnectionColumns = ref([
{ prop: 'name', label: 'name', width: 80 },
/* { prop: 'shorePowerName', label: '', width: 150 },
{ prop: 'time', label: '连接时间', width: 180 },
{ prop: 'duration', label: '连接时长(小时)', width: 120 },
{ prop: 'powerConsumption', label: '用电量(kWh)', width: 120 },
{ prop: 'status', label: '状态', width: 100 } */
])
// //
const handleClose = () => { const handleClose = () => {
berthingQueryParams.value.ids = null
berthingQueryParams.value.shipId = null
berthingQueryParams.value.type = 1
visible.value = false visible.value = false
} }
@ -270,69 +259,41 @@ const handleBerthingSearch = () => {
} }
const resetBerthingQuery = () => { const resetBerthingQuery = () => {
berthingQueryParams.value.keyword = '' // berthingQueryParams.value.keyword = ''
berthingQueryParams.value.startTime = '' berthingQueryParams.value.startTime = ''
berthingQueryParams.value.endTime = '' berthingQueryParams.value.endTime = ''
berthingDateRange.value = ['', ''] berthingDateRange.value = ['', '']
berthingQueryParams.value.pageNo = 1 berthingQueryParams.value.pageNo = 1
handleBerthingSearch() handleShorePowerConnectionSearch()
} }
const handleBerthingDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
berthingQueryParams.value.startTime = val[0]
berthingQueryParams.value.endTime = val[1]
} else {
berthingQueryParams.value.startTime = ''
berthingQueryParams.value.endTime = ''
}
}
const handleBerthingSizeChange = (val: number) => { const handleBerthingSizeChange = (val: number) => {
berthingQueryParams.value.pageSize = val berthingQueryParams.value.pageSize = val
berthingQueryParams.value.pageNo = 1 berthingQueryParams.value.pageNo = 1
handleBerthingSearch() handleShorePowerConnectionSearch()
} }
const handleBerthingCurrentChange = (val: number) => { const handleBerthingCurrentChange = (val: number) => {
berthingQueryParams.value.pageNo = val berthingQueryParams.value.pageNo = val
handleBerthingSearch() handleShorePowerConnectionSearch()
} }
// //
const handleShorePowerConnectionSearch = () => { const handleShorePowerConnectionSearch = () => {
// // const
emit('shore-power-connection-search', { ...shorePowerConnectionQueryParams.value }) const params = {
} // shipId: berthingQueryParams.value.shipId,
// ids: berthingQueryParams.value.ids,
const resetShorePowerConnectionQuery = () => { ...(berthingQueryParams.value.ids ? { ids: berthingQueryParams.value.ids } : {}),
shorePowerConnectionQueryParams.value.keyword = '' ...(berthingQueryParams.value.shipId ? { shipId: berthingQueryParams.value.shipId } : {}),
shorePowerConnectionQueryParams.value.startTime = '' type: berthingQueryParams.value.type,
shorePowerConnectionQueryParams.value.endTime = '' pageNo: 1,
shorePowerConnectionDateRange.value = ['', ''] pageSize: 9999 // 便
shorePowerConnectionQueryParams.value.pageNo = 1
handleShorePowerConnectionSearch()
}
const handleShorePowerConnectionDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
shorePowerConnectionQueryParams.value.startTime = val[0]
shorePowerConnectionQueryParams.value.endTime = val[1]
} else {
shorePowerConnectionQueryParams.value.startTime = ''
shorePowerConnectionQueryParams.value.endTime = ''
}
}
const handleShorePowerConnectionSizeChange = (val: number) => {
shorePowerConnectionQueryParams.value.pageSize = val
shorePowerConnectionQueryParams.value.pageNo = 1
handleShorePowerConnectionSearch()
} }
handleGetShipHistortyList(params)
const handleShorePowerConnectionCurrentChange = (val: number) => { //
shorePowerConnectionQueryParams.value.pageNo = val // emit('shore-power-connection-search', { ...shorePowerConnectionQueryParams.value })
handleShorePowerConnectionSearch()
} }
// //
@ -353,36 +314,95 @@ watch(
) )
watch( watch(
() => props.shipId, () => props.shipParam,
(newShipId) => { (newShipParam) => {
if (newShipId) { if (newShipParam) {
// ID // ID
berthingTableData.value = [] berthingTableData.value = []
handleGetShipHistortyList(newShipId) if (newShipParam.shipId) {
berthingQueryParams.value.shipId = newShipParam.shipId
} else {
berthingQueryParams.value.shipId = null
}
if (newShipParam.ids && newShipParam.ids.length > 0) {
berthingQueryParams.value.ids = newShipParam.ids
} else {
berthingQueryParams.value.ids = null
}
berthingQueryParams.value.type = newShipParam.type
// handleGetShipHistortyList()
handleShorePowerConnectionSearch()
} }
} }
) )
const handleGetShipHistortyList = async (id) => { const handleGetShipHistortyList = async (param) => {
console.log('handleGetShipHistortyList', id) console.log('handleGetShipHistortyList', param)
if (!id) return; if (!param) return;
// try { // try {
const res = await MapApi.getShipHistoryPage({ const res = await MapApi.getShipHistoryPage({
shipId: parseInt(id, 10), // /* shipId: param.id, // 将字符串转换为数
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
type: 4, type: param.type, */
// ids: [parseInt(id, 10)] // ids: [parseInt(id, 10)]
...param
}) })
console.log(res); console.log(res);
const buildData = await Promise.all(res.list.map(async item => ({
//
let filteredList = [...res.list];
if (berthingQueryParams.value.startTime || berthingQueryParams.value.endTime) {
filteredList = filteredList.filter(item => {
const berthTime = item.usageRecordInfo?.actualBerthTime;
if (!berthTime) return false;
const berthTimestamp = new Date(berthTime).getTime();
const startTime = berthingQueryParams.value.startTime ? new Date(berthingQueryParams.value.startTime).getTime() : 0;
const endTime = berthingQueryParams.value.endTime ? new Date(berthingQueryParams.value.endTime).getTime() : Infinity;
//
if (berthTimestamp >= startTime && berthTimestamp <= endTime) {
return true;
}
//
if (item.usageRecordInfo?.actualDepartureTime) {
const departureTimestamp = new Date(item.usageRecordInfo.actualDepartureTime).getTime();
if (departureTimestamp >= startTime && departureTimestamp <= endTime) {
return true;
}
}
// -
if (item.usageRecordInfo?.actualDepartureTime) {
const departureTimestamp = new Date(item.usageRecordInfo.actualDepartureTime).getTime();
if (berthTimestamp <= endTime && departureTimestamp >= startTime) {
return true;
}
}
return false;
});
}
//
const totalFiltered = filteredList.length;
const pageNo = berthingQueryParams.value.pageNo;
const pageSize = berthingQueryParams.value.pageSize;
const startIndex = (pageNo - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedList = filteredList.slice(startIndex, endIndex);
const buildData = await Promise.all(paginatedList.map(async item => ({
...item, ...item,
}))) })))
berthingTableData.value = buildData berthingTableData.value = buildData
berthingTotal.value = totalFiltered
} }
onMounted(() => { onMounted(() => {
console.log('页面加载了!') console.log('页面加载了!')
console.log('props.shipId', props.shipId) // console.log('props.shipId', props.shipId)
}) })
</script> </script>

10
public/map/components/ShipShorePower.vue

@ -713,7 +713,7 @@ interface ShipDataItem {
} }
interface Props { interface Props {
shipData: ShipDataItem[]; shipData: ShipRespVo[];
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@ -864,7 +864,7 @@ const formatDateTime = (timestamp: string | number | Date | undefined) => {
const showStatus = async (ship: ShipDataItem) => { const showStatus = async (ship: ShipDataItem) => {
const { usageRecordInfo, applyInfo } = ship; const { usageRecordInfo, applyInfo } = ship;
if (applyInfo.reason == 0 && usageRecordInfo && usageRecordInfo.beginTime) { if (applyInfo && applyInfo.reason == 0 && usageRecordInfo && usageRecordInfo.beginTime) {
const start = new Date(usageRecordInfo.beginTime); const start = new Date(usageRecordInfo.beginTime);
const end = usageRecordInfo.endTime ? new Date(usageRecordInfo.endTime) : new Date(); const end = usageRecordInfo.endTime ? new Date(usageRecordInfo.endTime) : new Date();
@ -931,7 +931,7 @@ const showStatus = async (ship: ShipDataItem) => {
statusClass: 'status-using', statusClass: 'status-using',
status: `使用岸电${hours}小时${minutes}分钟${seconds}秒,${useValue.toFixed(2)}kWH` status: `使用岸电${hours}小时${minutes}分钟${seconds}秒,${useValue.toFixed(2)}kWH`
} }
} else if (applyInfo.reason != 0) { } else if (applyInfo && applyInfo.reason != 0) {
// - // -
const statusMap: Record<string, { text: string; colorType: string }> = { const statusMap: Record<string, { text: string; colorType: string }> = {
'1': { text: '岸电用电接口不匹配', colorType: 'default' }, '1': { text: '岸电用电接口不匹配', colorType: 'default' },
@ -984,7 +984,7 @@ const buildData = async (ship: ShipDataItem) => {
applyInfo: ship.applyInfo, applyInfo: ship.applyInfo,
usageRecordInfo: ship.usageRecordInfo, usageRecordInfo: ship.usageRecordInfo,
shipBasicInfo: ship.shipBasicInfo, shipBasicInfo: ship.shipBasicInfo,
name: ship.shipBasicInfo.name, name: ship?.shipBasicInfo?.name,
wharf: findNameById(ship.applyInfo?.arrivalDock.toString(), dockIdAndNameList.value), wharf: findNameById(ship.applyInfo?.arrivalDock.toString(), dockIdAndNameList.value),
berth: findNameById(ship.applyInfo?.arrivalBerth.toString(), berthIdAndNameList.value), berth: findNameById(ship.applyInfo?.arrivalBerth.toString(), berthIdAndNameList.value),
beginTime: ship.usageRecordInfo?.beginTime, beginTime: ship.usageRecordInfo?.beginTime,
@ -1007,7 +1007,7 @@ onMounted(async () => {
berthIdAndNameList.value = await MapApi.getBerthIdAndNameList() berthIdAndNameList.value = await MapApi.getBerthIdAndNameList()
dockIdAndNameList.value = await MapApi.getDockIdAndNameList() dockIdAndNameList.value = await MapApi.getDockIdAndNameList()
harborDistrictIdAndNameList.value = await MapApi.getHarborDistrictIdAndNameList() harborDistrictIdAndNameList.value = await MapApi.getHarborDistrictIdAndNameList()
console.log('props.shipData', props.shipData)
// 使Promise.all // 使Promise.all
localShipData.value = await Promise.all(props.shipData.map(ship => buildData(ship))); localShipData.value = await Promise.all(props.shipData.map(ship => buildData(ship)));
}) })

4
public/map/components/ShorePowerHistoryDialog.vue

@ -305,11 +305,11 @@ const handleGetShorePowerHistoryList = async (id) => {
if (!id) return; if (!id) return;
// try { // try {
const res = await MapApi.getShipHistoryPage({ const res = await MapApi.getShipHistoryPage({
shipId: parseInt(id, 10), // // shipId: parseInt(id, 10), //
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
type: 5, type: 5,
// ids: [parseInt(id, 10)] ids: [parseInt(id, 10)]
}) })
console.log(res); console.log(res);
const buildData = await Promise.all(res.list.map(async item => ({ const buildData = await Promise.all(res.list.map(async item => ({

540
public/map/components/ShorePowerUsage.vue

@ -1,5 +1,6 @@
<template> <template>
<div class="shore-power-usage"> <div class="shore-power-usage">
<template v-if="pageType === 'realtime'">
<!-- Average Mode --> <!-- Average Mode -->
<template v-if="displayMode === 'average'"> <template v-if="displayMode === 'average'">
<div class="left average" style="width: 40%;"> <div class="left average" style="width: 40%;">
@ -12,17 +13,27 @@
<span class="title-text">{{ card.title }}</span> <span class="title-text">{{ card.title }}</span>
</div> </div>
<div v-if="card.type === 'overview'" class="time-range-selector"> <div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" <el-date-picker v-if="historyTimeGranularity === 'year'" type="yearrange" range-separator=""
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" @click.stop /> start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
<button v-for="option in timeRangeOptions" :key="option.value" class="date-range-picker" @click.stop />
:class="['time-range-btn', { active: timeRange === option.value }]" <el-date-picker v-else-if="historyTimeGranularity === 'month'" type="monthrange" range-separator=""
@click.stop="handleTimeRangeChange(option.value)"> start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM"
class="date-range-picker" @click.stop />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" type="weekrange" range-separator=""
start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
class="date-range-picker" @click.stop /> -->
<el-date-picker v-else v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }} {{ option.label }}
</button> </button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div> </div>
<div v-if="card.type === 'chart'" class="show-value"> <div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label || <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}总量:</span> ''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> <div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div> </div>
</div> </div>
@ -67,17 +78,28 @@
<span class="title-text">{{ card.title }}</span> <span class="title-text">{{ card.title }}</span>
</div> </div>
<div v-if="card.type === 'overview'" class="time-range-selector"> <div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" <el-date-picker v-if="historyTimeGranularity === 'year'" type="yearrange" range-separator=""
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" @click.stop /> start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
<button v-for="option in timeRangeOptions" :key="option.value" class="date-range-picker" @click.stop />
:class="['time-range-btn', { active: timeRange === option.value }]" <el-date-picker v-else-if="historyTimeGranularity === 'month'" type="monthrange" range-separator=""
@click.stop="handleTimeRangeChange(option.value)"> start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM"
class="date-range-picker" @click.stop />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" type="weekrange" range-separator=""
start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
class="date-range-picker" @click.stop /> -->
<el-date-picker v-else type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker"
@click.stop />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }} {{ option.label }}
</button> </button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div> </div>
<div v-if="card.type === 'chart'" class="show-value"> <div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label || <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}总量:</span> ''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> <div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div> </div>
</div> </div>
@ -126,8 +148,7 @@
<span class="title-text">{{ card.title }}</span> <span class="title-text">{{ card.title }}</span>
</div> </div>
<div v-if="card.type === 'overview'" class="time-range-selector"> <div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" <el-button type="primary" @click.stop="pageType = 'history'">历史查询</el-button>
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" />
<button v-for="option in timeRangeOptions" :key="option.value" <button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]" :class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)"> @click.stop="handleTimeRangeChange(option.value)">
@ -136,7 +157,7 @@
</div> </div>
<div v-if="card.type === 'chart'" class="show-value"> <div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label || <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span> ''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> <div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div> </div>
</div> </div>
@ -190,7 +211,7 @@
</div> </div>
<div v-if="card.type === 'chart'" class="show-value"> <div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label || <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span> ''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> <div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div> </div>
</div> </div>
@ -226,6 +247,251 @@
</div> </div>
</div> </div>
</div> </div>
</template>
<template v-if="pageType === 'history'">
<!-- Average Mode -->
<template v-if="displayMode === 'average'">
<div class="left average" style="width: 40%;">
<div v-for="card in cards.slice(0, 3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
<div class="right average" style="width: 40%;">
<div v-for="card in cards.slice(3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" @click.stop />
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ fuelReduction }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ co2Reduction }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ pm25Reduction }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ noxReduction }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ so2Reduction }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
</template>
<!-- Magnify Mode -->
<div v-if="displayMode === 'magnify'" class="magnify">
<div class="big">
<div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker v-if="historyTimeGranularity === 'year'" v-model="dateRange" type="yearrange"
range-separator="至" start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
class="date-range-picker" @change="handleSelectDate" />
<el-date-picker v-else-if="historyTimeGranularity === 'month'" v-model="dateRange" type="monthrange"
range-separator="至" start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM"
value-format="YYYY-MM" class="date-range-picker" @change="handleSelectDate" />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" v-model="dateRange" type="week"
range-separator="至" start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" class="date-range-picker" @change="handleSelectDate" /> -->
<el-date-picker v-else v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker"
@change="handleSelectDate" />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="pageType = 'realtime'">实时显示</el-button>
<!-- <button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button> -->
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ historyData.totalPower }}</div>
<div class="overview-label">区间用电(千瓦时)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.fuel }}</div>
<div class="overview-label">减少燃油()</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.co2 }}</div>
<div class="overview-label">减少二氧化碳排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.pm25 }}</div>
<div class="overview-label">减少PM2.5排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.nox }}</div>
<div class="overview-label">减少氮氧化物(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.so2 }}</div>
<div class="overview-label">减少二氧化硫(千克)</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getHistoryChartData(card.id)" :title="card.title" :color="card.color"
:magnify-mode="true" />
</div>
</div>
</div>
<div class="one-row">
<div v-for="card in cards.filter(c => c.id !== selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<!-- <button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button> -->
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ historyData.totalPower }}</div>
<div class="overview-label">区间用电(千瓦时)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.fuel }}</div>
<div class="overview-label">减少燃油()</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.co2 }}</div>
<div class="overview-label">减少二氧化碳排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.pm25 }}</div>
<div class="overview-label">减少PM2.5排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.nox }}</div>
<div class="overview-label">减少氮氧化物(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.so2 }}</div>
<div class="overview-label">减少二氧化硫(千克)</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getHistoryChartData(card.id)" :title="card.title"
:color="card.color" />
</div>
</div>
</div>
</div>
</template>
</div> </div>
</template> </template>
@ -235,6 +501,7 @@ import WaveLineChart from './charts/WaveLineChart.vue'
import { MapApi } from "@/api/shorepower/map"; import { MapApi } from "@/api/shorepower/map";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { RealtimeDeviceData } from '@/types/shorepower'; import { RealtimeDeviceData } from '@/types/shorepower';
import { formatTimestamp, parseRangeToTimestamp } from './utils';
interface Props { interface Props {
@ -316,6 +583,8 @@ export interface deviceData {
// const props = defineProps<Props>() // const props = defineProps<Props>()
const pageType = ref<'realtime' | 'history'>('realtime')
const dateRange = ref<string[]>([])
const totalPower = ref<string>('0') const totalPower = ref<string>('0')
const fuelReduction = ref<string>('0') const fuelReduction = ref<string>('0')
const co2Reduction = ref<string>('0') const co2Reduction = ref<string>('0')
@ -336,6 +605,20 @@ const noxReductionChartData = ref<ChartDataItem[]>([])
const pm25ReductionChartData = ref<ChartDataItem[]>([]) const pm25ReductionChartData = ref<ChartDataItem[]>([])
const fuelReductionChartData = ref<ChartDataItem[]>([]) const fuelReductionChartData = ref<ChartDataItem[]>([])
const h_totalPowerChartData = ref<ChartDataItem[]>([])
const h_co2ReductionChartData = ref<ChartDataItem[]>([])
const h_so2ReductionChartData = ref<ChartDataItem[]>([])
const h_noxReductionChartData = ref<ChartDataItem[]>([])
const h_pm25ReductionChartData = ref<ChartDataItem[]>([])
const h_fuelReductionChartData = ref<ChartDataItem[]>([])
const historyData = ref({
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
})
const chartData = ref({ const chartData = ref({
'realtime': { 'realtime': {
'totalPower': 0, 'totalPower': 0,
@ -388,6 +671,144 @@ const chartData = ref({
}) })
function aggregateByIndex(data: RawItem[][]): ChartDataItem[] {
if (data.length === 0) return [];
//
const maxLength = Math.max(...data.map(group => group.length));
const result: ChartDataItem[] = [];
for (let i = 0; i < maxLength; i++) {
let totalValue = 0;
let timeLabel = '';
for (const group of data) {
const item = group[i];
if (item) {
totalValue += item.measureValue;
// group[i].measureTime
if (!timeLabel) {
timeLabel = formatTimestamp(item.measureTime);
}
}
}
//
if (timeLabel) {
result.push({
name: timeLabel,
value: totalValue
});
}
}
return result;
}
const calculateTotalDiff = (data) => {
return data.reduce((total, subArray) => {
if (subArray.length === 0) {
return total; //
}
const first = subArray[0].measureValue;
const last = subArray[subArray.length - 1].measureValue;
return total + (last - first);
}, 0);
}
const handleSelectDate = async (range: string[] | null) => {
if (!range || range.length !== 2) {
//
return;
}
const timestamps = parseRangeToTimestamp(range, historyTimeGranularity.value);
if (!timestamps) return;
const [start, end] = timestamps;
let timeTypeId = 0
switch (historyTimeGranularity.value) {
case 'day':
timeTypeId = 3
break
/* case 'week':
timeTypeId = 4
break */
case 'month':
timeTypeId = 4
break
case 'year':
timeTypeId = 5
break
default:
timeTypeId = 0
break
}
const params = {
start,
end,
timeType: timeTypeId // code
};
console.log(params)
const res = await MapApi.getByStartAndEndTimeAndTimeType(params)
const array = Object.values(res);
if (array.length === 0) {
historyData.value = {
totalPower: 0,
fuel: 0,
co2: 0,
pm25: 0,
nox: 0,
so2: 0,
}
h_fuelReductionChartData.value = [
{ name: '', value: 0 }
]
h_co2ReductionChartData.value = [
{ name: '', value: 0 }
]
h_so2ReductionChartData.value = [
{ name: '', value: 0 }
]
h_noxReductionChartData.value = [
{ name: '', value: 0 }
]
h_pm25ReductionChartData.value = [
{ name: '', value: 0 }
]
return;
}
console.log(array)
const totalDiff = calculateTotalDiff(array);
console.log(totalDiff)
historyData.value = {
totalPower: Number(totalDiff.toFixed(2)),
fuel: Number((totalDiff * 0.22 / 1).toFixed(2)), //
co2: Number((totalDiff * 670 / 1000).toFixed(2)), //
pm25: Number((totalDiff * 1.46 / 1000).toFixed(2)), //
nox: Number((totalDiff * 18.1 / 1000).toFixed(2)), //
so2: Number((totalDiff * 10.5 / 1000).toFixed(2)), //
}
const chartData = aggregateByIndex(array);
const fuelChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 0.22 / 1).toFixed(2)) }))
h_fuelReductionChartData.value = fuelChartData;
const co2ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 670 / 1000).toFixed(2)) }))
h_co2ReductionChartData.value = co2ChartData;
const pm25ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 1.46 / 1000).toFixed(2)) }))
h_pm25ReductionChartData.value = pm25ChartData;
const noxChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 18.1 / 1000).toFixed(2)) }))
h_noxReductionChartData.value = noxChartData;
const so2ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 10.5 / 1000).toFixed(2)) }))
h_so2ReductionChartData.value = so2ChartData;
};
// //
const getChartData = (cardId: string): ChartDataItem[] => { const getChartData = (cardId: string): ChartDataItem[] => {
switch (cardId) { switch (cardId) {
@ -407,18 +828,37 @@ const getChartData = (cardId: string): ChartDataItem[] => {
return commonChartData.value return commonChartData.value
} }
} }
//
const getHistoryChartData = (cardId: string): ChartDataItem[] => {
switch (cardId) {
case 'overview':
return totalPowerChartData.value
case 'fuel':
return h_fuelReductionChartData.value
case 'co2':
return h_co2ReductionChartData.value
case 'pm25':
return h_pm25ReductionChartData.value
case 'nox':
return h_noxReductionChartData.value
case 'so2':
return h_so2ReductionChartData.value
default:
return commonChartData.value
}
}
// //
const timeRange = ref<'realtime' | 'day' | 'week' | 'month' | 'quarter' | 'year'>('realtime') const timeRange = ref<'realtime' | 'day' | 'week' | 'month' | 'quarter' | 'year'>('realtime')
// //
const timeRangeOptions = [ const timeRangeOptions = [
{ value: 'realtime', label: '实时' }, { value: 'realtime', label: '' },
{ value: 'day', label: '日' }, { value: 'day', label: '日' },
{ value: 'week', label: '周' }, { value: 'week', label: '周' },
{ value: 'month', label: '月' }, { value: 'month', label: '月' },
// { value: 'quarter', label: '' }, // { value: 'quarter', label: '' },
{ value: 'year', label: '年' } { value: 'year', label: '年' }
] ]
// //
@ -427,6 +867,30 @@ const handleTimeRangeChange = (range: 'realtime' | 'day' | 'week' | 'month' | 'q
// //
} }
//
const historyTimeGranularity = ref<'year' | 'month' | 'week' | 'day'>('day')
//
const historyTimeGranularityOptions = [
{ value: 'year', label: '年' },
{ value: 'month', label: '月' },
// { value: 'week', label: '' },
{ value: 'day', label: '日' }
]
//
const handleHistoryTimeGranularityChange = (granularity: 'year' | 'month' | 'week' | 'day') => {
historyTimeGranularity.value = granularity
//
}
//
const handleDateRangeConfirm = () => {
//
console.log('日期范围已确认,当前颗粒度:', historyTimeGranularity.value)
// API
}
// //
const cards = ref<CardInfo[]>([ const cards = ref<CardInfo[]>([
{ {
@ -524,10 +988,20 @@ const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]
ids: ids.join(','), ids: ids.join(','),
// year: new Date().getFullYear() // year: new Date().getFullYear()
} }
const yearDataRes: RealtimeDeviceData[] = await MapApi.getYearDataByIdList(params); let yearDataRes: RealtimeDeviceData[] = []
const weekDataRes: RealtimeDeviceData[] = await MapApi.getWeekDataByIdList(params); let weekDataRes: RealtimeDeviceData[] = []
const monthDataRes: RealtimeDeviceData[] = await MapApi.getMonthDataByIdList(params); let monthDataRes: RealtimeDeviceData[] = []
const dayDataRes: RealtimeDeviceData[] = await MapApi.getDayDataByIdList(params); let dayDataRes: RealtimeDeviceData[] = []
try {
yearDataRes = await MapApi.getYearDataByIdList(params);
weekDataRes = await MapApi.getWeekDataByIdList(params);
monthDataRes = await MapApi.getMonthDataByIdList(params);
dayDataRes = await MapApi.getDayDataByIdList(params);
} catch (error) {
console.log(error);
}
// const quarterDataRes: deviceData = await MapApi.getQuarterDataByIdList(params); // const quarterDataRes: deviceData = await MapApi.getQuarterDataByIdList(params);
const realTimeSum = res.reduce((acc, item) => acc + item.measureValue, 0); const realTimeSum = res.reduce((acc, item) => acc + item.measureValue, 0);
@ -544,7 +1018,7 @@ const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]
// pm25Reduction.value = (realTimeSum * 1.46 / 1000000).toFixed(2) // // pm25Reduction.value = (realTimeSum * 1.46 / 1000000).toFixed(2) //
// fuelReduction.value = (realTimeSum * 0.22 / 1000).toFixed(2) // // fuelReduction.value = (realTimeSum * 0.22 / 1000).toFixed(2) //
const incrementRealTimeSum = Object.values(res).reduce((acc, item) => acc + item.incrementValue, 0); const incrementRealTimeSum = Object.values(res).reduce((acc, item) => acc + item.measureValue, 0);
chartData.value.realtime = { chartData.value.realtime = {
@ -629,26 +1103,20 @@ const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]
} }
} }
const handleGetAllDeviceDataByTimeRange = async () => { /* const handleGetAllDeviceDataByTimeRange = async () => {
try { try {
// //
const nowTimestamp = dayjs().valueOf().toString(); const nowTimestamp = dayjs().valueOf().toString();
// //
const twoDaysAgoTimestamp = dayjs().subtract(2, 'day').valueOf().toString(); const twoDaysAgoTimestamp = dayjs().subtract(2, 'day').valueOf().toString();
const params = {
start: twoDaysAgoTimestamp,
end: nowTimestamp,
timeType: '3'
}
const res = await MapApi.getByStartAndEndTimeAndTimeType(params)
console.log(res)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
} } */
watch(() => props.realtimeDeviceData, (newValue) => { watch(() => props.realtimeDeviceData, (newValue) => {
handleGetRealTimeAllData(newValue) handleGetRealTimeAllData(newValue)
@ -656,7 +1124,7 @@ watch(() => props.realtimeDeviceData, (newValue) => {
// //
onMounted(() => { onMounted(() => {
handleGetAllDeviceDataByTimeRange() // handleGetAllDeviceDataByTimeRange()
handleGetRealTimeAllData(props.realtimeDeviceData) handleGetRealTimeAllData(props.realtimeDeviceData)
}) })

984
public/map/components/cesiumMap.vue

File diff suppressed because it is too large

13
public/map/components/charts/WaveLineChart.vue

@ -62,6 +62,15 @@ const updateChart = () => {
textStyle: { textStyle: {
color: '#fff', color: '#fff',
fontSize: props.magnifyMode ? 20 : 12 fontSize: props.magnifyMode ? 20 : 12
},
formatter: (params: any) => {
// params
if (params && params.length > 0) {
const time = params[0].name; //
const value = params[0].value; //
return `时间: ${time}<br/>数值: ${value}`;
}
return '';
} }
}, },
xAxis: { xAxis: {
@ -179,6 +188,10 @@ const updateChart = () => {
width: props.magnifyMode ? 2 : 1, width: props.magnifyMode ? 2 : 1,
type: 'dashed' type: 'dashed'
}, },
// tooltip
tooltip: {
show: false
},
// //
label: { label: {
show: false show: false

9
public/map/components/dictionaryTable.ts

@ -72,3 +72,12 @@ export const BERTH_TYPE = [
{ label: '左舷停舶', value: 'left' }, { label: '左舷停舶', value: 'left' },
{ label: '右舷停舶', value: 'right' }, { label: '右舷停舶', value: 'right' },
] ]
// 设施类型
export const FACILITY_TYPE = [
{ label: '港区', value: 1 },
{ label: '码头', value: 2 },
{ label: '岸电设施', value: 3 },
{ label: '泊位', value: 4 },
{ label: '用电接口', value: 5 },
]

48
public/map/components/utils.ts

@ -192,3 +192,51 @@ export function getValueById<T extends { id: number | string }, K extends keyof
// 否则返回原始值 // 否则返回原始值
return value 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];
}

283
public/map/index.vue

@ -1,6 +1,12 @@
<template> <template>
<div> <div>
<PublicMapComponents ref="mapComponentRef" class="map-base" /> <PublicMapComponents ref="mapComponentRef" class="map-base" :shore-power-list="ShorePowerList"
:ship-data-list="shipDataList" v-if="dataLoad" :on-instance-click="handleElectricalBoxClick" />
<div v-else class="loading">
<div style="margin: 0 auto">
loading
</div>
</div>
<div class="head"> <div class="head">
<div class="head-title"> <div class="head-title">
<span>曹妃甸港区船舶岸电监管平台</span> <span>曹妃甸港区船舶岸电监管平台</span>
@ -20,7 +26,8 @@
<!-- 港区概览 --> <!-- 港区概览 -->
<template v-if="activeHeadGroup === 0"> <template v-if="activeHeadGroup === 0">
<PortOverview :ship-status-data="shipStatusData" :shore-power-status-data="shorePowerStatusData" <PortOverview :ship-status-data="shipStatusData" :shore-power-status-data="shorePowerStatusData"
@item-click="handleSwitch" :realtime-device-data="realtimeDeviceData" /> @item-click="handleSwitch" :realtime-device-data="realtimeDeviceData" :shore-power-list="ShorePowerList"
:ship-data-list="shipDataList" />
</template> </template>
<!-- 港口岸电使用情况 --> <!-- 港口岸电使用情况 -->
@ -31,64 +38,197 @@
<!-- 港口企业岸电使用 --> <!-- 港口企业岸电使用 -->
<template v-if="activeHeadGroup === 2"> <template v-if="activeHeadGroup === 2">
<CompanyShorePower :companyComparisonData="companyComparisonData" /> <CompanyShorePower :companyComparisonData="companyComparisonData" :realtime-device-data="realtimeDeviceData" />
</template> </template>
<!-- 船舶岸电使用情况 --> <!-- 船舶岸电使用情况 -->
<template v-if="activeHeadGroup === 3"> <template v-if="activeHeadGroup === 3">
<ShipShorePower :ship-data="shipStatusData" :selected-ship="selectedShip" @item-click="handleSwitch" /> <ShipShorePower :ship-data="shipDataList" :selected-ship="selectedShip" @item-click="handleSwitch" />
</template> </template>
<template v-if="activeHeadGroup === 4"> <template v-if="activeHeadGroup === 4">
<div class="right" style="width: 500px;"> <div v-if="selectedElectricalBox.type" class="right"
:style="`width: ${selectedElectricalBox.type === 'ship' ? '45%' : '20%'}`">
<div v-if="selectedElectricalBox.type === 'ship'" class="right-two-row">
<div class="card digital-twin-card--deep-blue">
<div class="card-title">
<el-button class="close-btn" size="small" type="text" @click="closeElectricalBoxInfo">×</el-button>
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">船舶详情</span>
</div>
<div class="card-content">
<div class="ship-detail">
<div class="detail-item">
<span class="label">名称:</span>
<span class="value">{{ selectedElectricalBox.data.shipBasicInfo.name + '-' +
selectedElectricalBox.data.shipBasicInfo.nameEn
}}</span>
</div>
<!-- <div class="detail-item">
<span class="label">英文船名:</span>
<span class="value">{{ selectedItem.shipBasicInfo.nameEn }}</span>
</div> -->
<!-- <div class="detail-item">
<span class="label">船舶呼号:</span>
<span class="value">{{ selectedItem.shipBasicInfo.callSign }}</span>
</div> -->
<div class="detail-item">
<span class="label">长度:</span>
<span class="value">{{ selectedElectricalBox.data.shipBasicInfo.length }} </span>
</div>
<div class="detail-item">
<span class="label">宽度:</span>
<span class="value">{{ selectedElectricalBox.data.shipBasicInfo.width }} </span>
</div>
<div class="detail-item">
<span class="label">吨位:</span>
<span class="value">{{ selectedElectricalBox.data.shipBasicInfo.tonnage }} </span>
</div>
<div class="detail-item">
<span class="label">满载吃水深度:</span>
<span class="value">{{ selectedElectricalBox.data.shipBasicInfo.fullLoadDraft }} </span>
</div>
<div class="detail-item">
<span class="label">电压:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
selectedElectricalBox.data.shorePower.voltageDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">电流:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
selectedElectricalBox.data.shorePower.currentDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">频率:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
selectedElectricalBox.data.shorePower.frequencyDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">靠泊状态:</span>
<span class="value">{{ getOperationTypeLabel(selectedElectricalBox.data.shorePowerAndShip.status,
SHORE_POWER_STATUS,
) }}</span>
</div>
<div class="detail-item">
<span class="label">靠泊类型:</span>
<span class="value">{{ getOperationTypeLabel(selectedElectricalBox.data.shorePowerAndShip.type,
BERTH_TYPE)
}}</span>
</div>
<div class="detail-item">
<span class="label">停泊时间:</span>
<span class="value">{{
formatTimestamp(selectedElectricalBox.data?.usageRecordInfo?.actualDepartureTime) }}</span>
</div>
<!-- <div class="detail-item">
<span class="label">航运单位:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.shippingCompany }}</span>
</div>
<div class="detail-item">
<span class="label">岸电联系人:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.shorePowerContact }}</span>
</div>
<div class="detail-item">
<span class="label">联系方式:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.shorePowerContactPhone }}</span>
</div>
<div class="detail-item">
<span class="label">船检登记号:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.inspectionNo }}</span>
</div>
<div class="detail-item">
<span class="label">创建时间:</span>
<span class="value">{{ new Date(shipSelectedItem.shipBasicInfo.createTime).toLocaleString() }}</span>
</div> -->
</div>
</div>
</div>
<div class="card digital-twin-card--deep-blue">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text"></span>
</div>
<div class="card-content">
<div class="ship-detail">
<div class="detail-item">
<span class="label">岸电使用时长:</span>
<span class="value">{{ formatDuration(selectedElectricalBox?.data?.usageRecordInfo?.beginTime,
selectedElectricalBox?.data?.usageRecordInfo?.endTime
) }}</span>
</div>
<div v-if="selectedElectricalBox?.data?.applyInfo?.reason === 0" class="detail-item">
<span class="label">岸电使用时长:</span>
<span class="value">{{ showStatus(selectedElectricalBox?.data, realtimeDeviceData)?.status }}</span>
</div>
<div v-if="selectedElectricalBox?.data?.applyInfo?.reason != 0" class="detail-item">
<span class="label">未使用岸电原因:</span>
<span class="value">{{ getOperationTypeLabel(selectedElectricalBox?.data?.applyInfo?.reason,
UNUSED_SHORE_POWER_REASON) }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-if="selectedElectricalBox.type === 'shorepower_box'" class="right-two-row">
<div class="card digital-twin-card--deep-blue"> <div class="card digital-twin-card--deep-blue">
<div class="card-title"> <div class="card-title">
<el-button class="close-btn" size="small" type="text" @click="closeElectricalBoxInfo">×</el-button>
<div class="vertical-line"></div> <div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" /> <img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">岸电箱详情</span> <span class="title-text">岸电箱详情</span>
<el-button class="close-btn" size="small" @click="closeElectricalBoxInfo"
style="margin-left: auto;">×</el-button>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="ship-detail"> <div class="ship-detail">
<div class="detail-item"> <div class="detail-item">
<span class="label">名称:</span> <span class="label">名称:</span>
<span class="value">{{ selectedElectricalBox?.name || '无' }}</span> <span class="value">{{ selectedElectricalBox.data?.name }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">位置:</span> <span class="label">位置:</span>
<span class="value">{{ selectedElectricalBox?.location || '曹妃甸港区华能码头' }}</span> <span class="value">{{ selectedElectricalBox.data?.position }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">状态:</span> <span class="label">电压:</span>
<span class="value">{{ selectedElectricalBox?.status || '在线' }}</span> <span class="value">{{ selectedElectricalBox.data?.shorePowerEquipmentInfo?.voltage }}</span>
</div> </div>
<!-- 当前使用数据 -->
<div class="section-title">当前使用数据</div>
<div class="detail-item"> <div class="detail-item">
<span class="label">当前使用功率:</span> <span class="label">频率:</span>
<span class="value">{{ selectedElectricalBox?.currentPower || '120' }} kW</span> <span class="value">{{ selectedElectricalBox.data?.shorePowerEquipmentInfo?.frequency }} </span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">当前使用电量:</span> <span class="label">:</span>
<span class="value">{{ selectedElectricalBox?.currentEnergy || '45.5' }} kWh</span> <span class="value">{{ selectedElectricalBox.data?.shorePowerEquipmentInfo?.capacity }}</span>
</div> </div>
<!-- 累计历史数据 -->
<div class="section-title">累计历史数据</div>
<div class="detail-item"> <div class="detail-item">
<span class="label">累计历史用电:</span> <span class="label">当前电压:</span>
<span class="value">{{ selectedElectricalBox?.totalEnergy || '1250.8' }} kWh</span> <span class="value">{{ getValueById(realtimeDeviceData, selectedElectricalBox.data?.voltageDeviceId,
'measureValue') }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">累计服务次数:</span> <span class="label">当前电流:</span>
<span class="value">{{ selectedElectricalBox?.serviceCount || '86' }} </span> <span class="value">{{ getValueById(realtimeDeviceData, selectedElectricalBox.data?.currentDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">当前频率:</span>
<span class="value">{{ getValueById(realtimeDeviceData, selectedElectricalBox.data?.frequencyDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">当前总用量:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
selectedElectricalBox.data?.totalPowerDeviceId,
'measureValue') }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
</div> </div>
@ -104,7 +244,9 @@ import ShipShorePower from './components/ShipShorePower.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { onMounted, onUnmounted, ref, computed, watch } from 'vue' import { onMounted, onUnmounted, ref, computed, watch } from 'vue'
import { MapApi } from "@/api/shorepower/map"; import { MapApi } from "@/api/shorepower/map";
import { RealtimeDeviceData } from '@/types/shorepower' import { RealtimeDeviceData, ShipBasicInfoRespVO, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'
import { BERTH_TYPE, DOCK_DISTRICT, getOperationTypeLabel, HARBOR_DISTRICT, SHORE_POWER_STATUS } from './components/dictionaryTable'
import { formatDuration, formatTimestamp, getValueById, showStatus } from './components/utils'
defineOptions({ name: 'PublicMap' }) defineOptions({ name: 'PublicMap' })
let getRealtimeIntervalId: NodeJS.Timeout | null = null let getRealtimeIntervalId: NodeJS.Timeout | null = null
const headGroupList = ref<{ value: number, label: string }[]>([ const headGroupList = ref<{ value: number, label: string }[]>([
@ -133,8 +275,8 @@ const selectHeadGroup = async (value: number) => {
} }
activeHeadGroup.value = value activeHeadGroup.value = value
if (value === 1) { if (value === 1) {
const deviceStatus = await MapApi.getDeviceStatusByIds(totalPowerDeviceId.value) // const deviceStatus = await MapApi.getDeviceStatusByIds(totalPowerDeviceId.value)
console.log('deviceStatus', deviceStatus) // console.log('deviceStatus', deviceStatus)
} }
} }
@ -145,6 +287,11 @@ const currentTime = ref<string>(dayjs().format('YYYY-MM-DD HH:mm:ss'))
// //
const realtimeDeviceData = ref<RealtimeDeviceData[]>([]) const realtimeDeviceData = ref<RealtimeDeviceData[]>([])
const ShorePowerList = ref<(ShorePowerBerth & { position: string; })[]>([])
const shipDataList = ref<ShipRespVo[]>([])
const dataLoad = ref<boolean>(false)
// //
const scrollContainerRef = ref<HTMLElement | null>(null) const scrollContainerRef = ref<HTMLElement | null>(null)
@ -159,7 +306,10 @@ const updateTime = () => {
} }
const selectedShip = ref(null) const selectedShip = ref(null)
const selectedElectricalBox = ref(null) const selectedElectricalBox = ref({
type: '',
data: null
})
/* 回到初始视角 */ /* 回到初始视角 */
const handleOverviewClick = () => { const handleOverviewClick = () => {
@ -170,22 +320,33 @@ const handleOverviewClick = () => {
const handleSwitch = (item) => { const handleSwitch = (item) => {
console.log(item) console.log(item)
// selectedShip.value = item // selectedShip.value = item
mapComponentRef.value?.switchModelView(item.modelInstance) mapComponentRef.value?.switchModelView(item)
} }
/* const handleFlyToitem = (item) => {
mapComponentRef.value?.switchModelView(item)
} */
// //
const handleElectricalBoxClick = (data) => { const handleElectricalBoxClick = (data) => {
activeHeadGroup.value = 4 activeHeadGroup.value = 4
console.log('岸电箱被点击:', data); console.log('岸电箱被点击:', data);
// //
selectedElectricalBox.value = data; // selectedElectricalBox.value = data;
selectedElectricalBox.value = {
type: data.type,
data: data.data
}
// //
}; };
// //
const closeElectricalBoxInfo = () => { const closeElectricalBoxInfo = () => {
activeHeadGroup.value = -1; activeHeadGroup.value = -1;
selectedElectricalBox.value = null; selectedElectricalBox.value = {
type: '',
data: null
};
}; };
// //
@ -195,6 +356,7 @@ onMounted(() => {
// //
if (mapComponentRef.value) { if (mapComponentRef.value) {
console.log('mapComponentRef.value', mapComponentRef.value)
mapComponentRef.value.setElectricalBoxClickCallback(handleElectricalBoxClick); mapComponentRef.value.setElectricalBoxClickCallback(handleElectricalBoxClick);
} }
}) })
@ -341,10 +503,33 @@ const handleGetRealtimeAllData = async () => {
} }
const handleGetStorePower = async () => {
// console.log('loading')
const harborDistrictId = 1
const harborDistrictList = await HARBOR_DISTRICT()
const dockDistrictList = await DOCK_DISTRICT()
const res = await MapApi.getShorepowerIdAndNameListByHarborDistrictId(harborDistrictId)
ShorePowerList.value = res.map(item => ({
...item,
position: `${getOperationTypeLabel(harborDistrictId, harborDistrictList)}${getOperationTypeLabel(item.shorePowerEquipmentInfo.dockId, dockDistrictList)}${item.name} `,
}))
// console.log('11', res)
}
const handleGetShipData = async () => {
const shipData = await MapApi.getShipInfo({
harborDistrictId: 1
})
shipDataList.value = shipData
}
// //
onMounted(() => { onMounted(async () => {
// DOM // DOM
handleGetRealtimeAllData() await handleGetRealtimeAllData()
await handleGetStorePower()
await handleGetShipData()
dataLoad.value = true
getRealtimeIntervalId = setInterval(() => { getRealtimeIntervalId = setInterval(() => {
handleGetRealtimeAllData() handleGetRealtimeAllData()
}, 5000) }, 5000)
@ -620,6 +805,27 @@ const shipStatusData = computed(() => {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 8px; margin-bottom: 8px;
position: relative;
.close-btn {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: #FFF;
font-size: 20px;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: #ff4d4f;
background-color: rgba(255, 77, 79, 0.1);
}
}
.vertical-line { .vertical-line {
width: 2px; width: 2px;
@ -707,6 +913,13 @@ const shipStatusData = computed(() => {
white-space: nowrap; white-space: nowrap;
} }
.right-two-row {
display: flex;
gap: 10px;
height: 100%;
}
.ship-name { .ship-name {
width: 20%; width: 20%;
} }

Loading…
Cancel
Save