33 changed files with 7875 additions and 1478 deletions
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
@ -0,0 +1,256 @@ |
|||
<template> |
|||
<div class="company-shore-power"> |
|||
<div class="left" style="width: 1000px;"> |
|||
<!-- 码头信息卡片 --> |
|||
<div class="card digital-twin-card--deep-blue"> |
|||
<div class="card-title" 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">码头与泊位信息</span> |
|||
</div> |
|||
<!-- <div> |
|||
<el-date-picker type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" |
|||
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" /> |
|||
</div> --> |
|||
|
|||
</div> |
|||
|
|||
<div class="card-content"> |
|||
<div v-for="company in companyComparisonData" :key="company.name" class="company-section"> |
|||
<div class="company-header"> |
|||
<div class="company-name">{{ company.name }}</div> |
|||
<div class="company-total">总量: {{ calculateTotal(company.children).toFixed(2) }}</div> |
|||
</div> |
|||
<div class="overview-grid"> |
|||
<div v-for="(berth, index) in (company.children || [])" :key="index" class="overview-item" |
|||
@click="showShorePowerHistory(berth)"> |
|||
<div class="overview-value">{{ berth.measureValue?.toFixed(2) |
|||
|| 0 }}</div> |
|||
|
|||
<div class="overview-label">{{ berth.name }}</div> |
|||
|
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<ship-history-dialog v-if="historyVisible.visible" v-model="historyVisible.visible" |
|||
:ship-param="historyVisible.searchParams" :realtime-device-data="realtimeDeviceData" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue' |
|||
import { MapApi } from "@/api/shorepower/map"; |
|||
import ShipHistoryDialog from './ShipHistoryDialog.vue'; |
|||
import { RealtimeDeviceData } from '@/types/shorepower'; |
|||
// 定义组件属性 |
|||
interface ChartDataItem { |
|||
name: string; |
|||
value?: number; |
|||
measureValue?: number; |
|||
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 { |
|||
companyComparisonData?: ChartDataItem[]; |
|||
realtimeDeviceData: RealtimeDeviceData[]; |
|||
} |
|||
|
|||
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> |
|||
|
|||
<style scoped> |
|||
.company-shore-power { |
|||
display: flex; |
|||
height: 100%; |
|||
gap: 10px; |
|||
} |
|||
|
|||
.left { |
|||
/* display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
gap: 10px; */ |
|||
} |
|||
|
|||
.card { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
} |
|||
|
|||
.card-content { |
|||
flex: 1; |
|||
height: 100%; |
|||
min-height: 0; |
|||
padding-bottom: 50px; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
/* 总览网格样式 */ |
|||
.overview-grid { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr 1fr 1fr; |
|||
gap: 8px; |
|||
padding: 4px; |
|||
height: 100%; |
|||
} |
|||
|
|||
.overview-item { |
|||
cursor: pointer; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 4px; |
|||
background-color: rgba(0, 0, 0, 0.2); |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
.overview-label { |
|||
text-align: center; |
|||
font-size: 24px; |
|||
color: #ccc; |
|||
margin-bottom: 2px; |
|||
} |
|||
|
|||
.overview-value { |
|||
font-size: 36px; |
|||
font-weight: bold; |
|||
color: #1296db; |
|||
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5); |
|||
margin-bottom: 2px; |
|||
} |
|||
|
|||
.overview-text { |
|||
font-size: 10px; |
|||
color: #999; |
|||
text-align: center; |
|||
} |
|||
|
|||
/* 企业区块样式 */ |
|||
.company-section { |
|||
margin-bottom: 16px; |
|||
padding-bottom: 16px; |
|||
border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
|||
} |
|||
|
|||
.company-section:last-child { |
|||
border-bottom: none; |
|||
margin-bottom: 0; |
|||
padding-bottom: 0; |
|||
} |
|||
|
|||
/* 企业头部样式 */ |
|||
.company-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 8px; |
|||
padding: 0 4px; |
|||
} |
|||
|
|||
.company-name { |
|||
font-size: 24px; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
} |
|||
|
|||
.company-total { |
|||
font-size: 24px; |
|||
font-weight: bold; |
|||
color: #1296db; |
|||
background-color: rgba(18, 150, 219, 0.1); |
|||
padding: 2px 8px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
/* 自定义滚动条样式 */ |
|||
.card-content::-webkit-scrollbar { |
|||
width: 6px; |
|||
} |
|||
|
|||
.card-content::-webkit-scrollbar-track { |
|||
background: rgba(255, 255, 255, 0.1); |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.card-content::-webkit-scrollbar-thumb { |
|||
background: rgba(17, 138, 237, 0.7); |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.card-content::-webkit-scrollbar-thumb:hover { |
|||
background: rgba(17, 138, 237, 1); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,174 @@ |
|||
<template> |
|||
<el-dialog v-model="visible" :title="title" width="1200px" :before-close="handleClose"> |
|||
<!-- 搜索和筛选区域 --> |
|||
<div class="filter-container"> |
|||
<el-form :model="queryParams" label-width="80px" inline> |
|||
<el-form-item label="关键字"> |
|||
<el-input v-model="queryParams.keyword" placeholder="请输入关键字搜索" clearable @keyup.enter="handleSearch" /> |
|||
</el-form-item> |
|||
<el-form-item label="时间范围"> |
|||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" |
|||
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleDateChange" /> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|||
<el-button @click="resetQuery">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<el-table :data="tableData" border stripe style="width: 100%" v-loading="loading"> |
|||
<el-table-column v-for="column in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label" |
|||
:width="column.width" :formatter="column.formatter" /> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<el-pagination v-model:current-page="queryParams.pageNo" v-model:page-size="queryParams.pageSize" |
|||
:page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper" |
|||
@size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
|||
</div> |
|||
|
|||
<template #footer> |
|||
<span class="dialog-footer"> |
|||
<el-button @click="handleClose">关闭</el-button> |
|||
</span> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, computed, watch } from 'vue' |
|||
import { ElDialog, ElForm, ElFormItem, ElInput, ElDatePicker, ElButton, ElTable, ElTableColumn, ElPagination } from 'element-plus' |
|||
|
|||
// 定义组件属性 |
|||
interface Props { |
|||
modelValue: boolean |
|||
title?: string |
|||
columns: Array<{ |
|||
prop: string |
|||
label: string |
|||
width?: string | number |
|||
formatter?: (row: any, column: any, cellValue: any) => string |
|||
}> |
|||
data: any[] |
|||
} |
|||
|
|||
// 定义事件 |
|||
const emit = defineEmits<{ |
|||
(e: 'update:modelValue', value: boolean): void |
|||
(e: 'search', params: any): void |
|||
}>() |
|||
|
|||
// 属性定义 |
|||
const props = withDefaults(defineProps<Props>(), { |
|||
modelValue: false, |
|||
title: '历史记录', |
|||
columns: () => [], |
|||
data: () => [] |
|||
}) |
|||
|
|||
// 响应式数据 |
|||
const visible = computed({ |
|||
get: () => props.modelValue, |
|||
set: (val) => emit('update:modelValue', val) |
|||
}) |
|||
|
|||
const queryParams = ref({ |
|||
keyword: '', |
|||
startTime: '', |
|||
endTime: '', |
|||
pageNo: 1, |
|||
pageSize: 10 |
|||
}) |
|||
|
|||
const dateRange = ref<[string, string]>(['', '']) |
|||
const loading = ref(false) |
|||
const total = ref(0) |
|||
|
|||
// 表格数据计算属性 |
|||
const tableData = computed(() => { |
|||
// 在实际应用中,这里应该是从服务器获取的数据 |
|||
// 当前为了演示,我们使用传入的模拟数据 |
|||
return props.data.slice( |
|||
(queryParams.value.pageNo - 1) * queryParams.value.pageSize, |
|||
queryParams.value.pageNo * queryParams.value.pageSize |
|||
) |
|||
}) |
|||
|
|||
// 表格列 |
|||
const tableColumns = computed(() => props.columns) |
|||
|
|||
// 处理关闭 |
|||
const handleClose = () => { |
|||
visible.value = false |
|||
} |
|||
|
|||
// 处理搜索 |
|||
const handleSearch = () => { |
|||
// 发送搜索事件给父组件 |
|||
emit('search', { ...queryParams.value }) |
|||
} |
|||
|
|||
// 重置查询条件 |
|||
const resetQuery = () => { |
|||
queryParams.value.keyword = '' |
|||
queryParams.value.startTime = '' |
|||
queryParams.value.endTime = '' |
|||
dateRange.value = ['', ''] |
|||
queryParams.value.pageNo = 1 |
|||
handleSearch() |
|||
} |
|||
|
|||
// 处理日期变化 |
|||
const handleDateChange = (val: [string, string] | null) => { |
|||
if (val && val[0] && val[1]) { |
|||
queryParams.value.startTime = val[0] |
|||
queryParams.value.endTime = val[1] |
|||
} else { |
|||
queryParams.value.startTime = '' |
|||
queryParams.value.endTime = '' |
|||
} |
|||
} |
|||
|
|||
// 处理分页大小变化 |
|||
const handleSizeChange = (val: number) => { |
|||
queryParams.value.pageSize = val |
|||
queryParams.value.pageNo = 1 |
|||
handleSearch() |
|||
} |
|||
|
|||
// 处理当前页变化 |
|||
const handleCurrentChange = (val: number) => { |
|||
queryParams.value.pageNo = val |
|||
handleSearch() |
|||
} |
|||
|
|||
// 监听数据变化并更新总数 |
|||
watch( |
|||
() => props.data, |
|||
(newData) => { |
|||
total.value = newData.length |
|||
}, |
|||
{ immediate: true } |
|||
) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.filter-container { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.pagination-container { |
|||
margin-top: 20px; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
.dialog-footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 10px; |
|||
} |
|||
</style> |
|||
@ -1,126 +0,0 @@ |
|||
<template> |
|||
<div ref="chartContainer" class="pie-chart-container"></div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue' |
|||
import * as echarts from 'echarts' |
|||
|
|||
// 定义组件props |
|||
interface Props { |
|||
chartData?: Array<{ name: string; value: number }> |
|||
title?: string |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
chartData: () => [], |
|||
title: '' |
|||
}) |
|||
|
|||
const chartContainer = ref<HTMLDivElement | null>(null) |
|||
let chartInstance: echarts.ECharts | null = null |
|||
|
|||
// 初始化图表 |
|||
const initChart = () => { |
|||
if (chartContainer.value) { |
|||
chartInstance = echarts.init(chartContainer.value) |
|||
updateChart() |
|||
} |
|||
} |
|||
|
|||
// 更新图表 |
|||
const updateChart = () => { |
|||
if (!chartInstance || !props.chartData) return |
|||
|
|||
const option = { |
|||
title: { |
|||
// text: props.title, |
|||
left: 'center', |
|||
textStyle: { |
|||
color: '#FFF' |
|||
} |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
show: true, |
|||
type: 'scroll', // 👈 开启可滚动图例 |
|||
orient: 'vertical', // 👈 垂直排列 |
|||
right: 10, |
|||
top: 'center', |
|||
textStyle: { |
|||
color: '#FFF' |
|||
} |
|||
}, |
|||
series: [ |
|||
{ |
|||
name: props.title, |
|||
type: 'pie', |
|||
radius: ['30%', '50%'], |
|||
center: ['35%', '50%'], // 👈 左移饼图避免与图例重叠 |
|||
avoidLabelOverlap: true, |
|||
itemStyle: { |
|||
borderRadius: 0, |
|||
borderColor: '#fff', |
|||
borderWidth: 1 |
|||
}, |
|||
label: { |
|||
show: true, |
|||
formatter: '{b}\n{d}%', |
|||
fontSize: 10 |
|||
}, |
|||
emphasis: { |
|||
label: { |
|||
show: true, |
|||
fontSize: 14, |
|||
fontWeight: 'bold' |
|||
} |
|||
}, |
|||
labelLine: { |
|||
show: true |
|||
}, |
|||
data: props.chartData |
|||
} |
|||
] |
|||
} |
|||
|
|||
chartInstance.setOption(option, true) |
|||
} |
|||
|
|||
// 窗口大小改变时重置图表大小 |
|||
const handleResize = () => { |
|||
if (chartInstance) { |
|||
chartInstance.resize() |
|||
} |
|||
} |
|||
|
|||
// 监听数据变化 |
|||
watch( |
|||
() => props.chartData, |
|||
() => { |
|||
updateChart() |
|||
}, |
|||
{ deep: true } |
|||
) |
|||
|
|||
// 初始化和销毁 |
|||
onMounted(() => { |
|||
initChart() |
|||
window.addEventListener('resize', handleResize) |
|||
}) |
|||
|
|||
onBeforeUnmount(() => { |
|||
if (chartInstance) { |
|||
chartInstance.dispose() |
|||
} |
|||
window.removeEventListener('resize', handleResize) |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.pie-chart-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,506 @@ |
|||
<template> |
|||
<div class="port-overview"> |
|||
<div class="left" style="width:45%;"> |
|||
<div style="height: 100%; width: 100%; display: flex; gap: 8px; "> |
|||
<div 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">岸电箱状态</span> |
|||
</div> |
|||
<input class="search-container" type="text" placeholder="搜索岸电设备" v-model="storeSearchKeyword" |
|||
@input="handlStoreSearch" /> |
|||
</div> |
|||
|
|||
<div class=" card-content"> |
|||
<div class="ship-table"> |
|||
<div class="ship-table-header"> |
|||
<div class="ship-table-column ship-name-header">岸电箱名称</div> |
|||
<div class="ship-table-column ship-status-header">状态</div> |
|||
<div class="ship-table-column ship-status-header">历史</div> |
|||
</div> |
|||
<div class="ship-table-body"> |
|||
<div v-for="shorepower in filteredShorePowerList" :key="shorepower.id" class="ship-table-row" |
|||
@click="handleSelectShorePower(shorepower)"> |
|||
<div class="ship-table-column ship-name"> |
|||
<div class="ship-icon">⚡</div> |
|||
<span class="ship-name-text">{{ shorepower.name }}</span> |
|||
</div> |
|||
<div class="ship-table-column ship-status"> |
|||
<div class="status-tag" :class="getStatusClass(shorepower.storePowerStatus)"> |
|||
{{ shorepower.storePowerStatus }} |
|||
</div> |
|||
<!-- <div class="status-tag" :class="getStatusClass('空闲')"> |
|||
空闲 |
|||
</div> |
|||
<div class="status-tag" :class="getStatusClass('故障')"> |
|||
故障 |
|||
</div> --> |
|||
</div> |
|||
<div class="ship-table-column ship-status-header"> |
|||
<el-button type="primary" link @click.stop="showShorePowerHistory(shorepower)">查看</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div 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">船舶状态</span> |
|||
</div> |
|||
<input class="search-container" type="text" placeholder="搜索船舶" v-model="searchKeyword" |
|||
@input="handleSearch" /> |
|||
</div> |
|||
|
|||
<div class=" card-content"> |
|||
<div class="ship-table"> |
|||
<div class="ship-table-header"> |
|||
<div class="ship-table-column ship-name-header">轮船名称</div> |
|||
<div class="ship-table-column ship-status-header">状态</div> |
|||
<div class="ship-table-column ship-status-header">历史</div> |
|||
</div> |
|||
<div class="ship-table-body"> |
|||
<div v-for="ship in filteredShipStatusData" :key="ship.id" class="ship-table-row" |
|||
@click="handleSelectItem(ship)"> |
|||
<div class="ship-table-column ship-name"> |
|||
<div class="ship-icon">🚢</div> |
|||
<span class="ship-name-text">{{ ship.shipBasicInfo.name }}</span> |
|||
</div> |
|||
<div class="ship-table-column ship-status"> |
|||
<div class="status-tag" :class="getStatusClass(ship.shipStatus)"> |
|||
{{ ship.shipStatus }} |
|||
</div> |
|||
<!-- <div class="status-tag" :class="getStatusClass('空闲')"> |
|||
空闲 |
|||
</div> |
|||
<div class="status-tag" :class="getStatusClass('故障')"> |
|||
故障 |
|||
</div> --> |
|||
</div> |
|||
<div class="ship-table-column ship-status-header"> |
|||
<el-button type="primary" link @click.stop="showShipHistory(ship)">查看</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div v-if="shipSelectedItem || shorepowerSelectedItem" class="right" style="width: 20%"> |
|||
<div v-if="shipSelectedItem && show_type === 'ship'" class="right-two-row"> |
|||
<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">{{ shipSelectedItem.shipBasicInfo.name + '-' + shipSelectedItem.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">{{ shipSelectedItem.shipBasicInfo.length }} 米</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">宽度:</span> |
|||
<span class="value">{{ shipSelectedItem.shipBasicInfo.width }} 米</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">吨位:</span> |
|||
<span class="value">{{ shipSelectedItem.shipBasicInfo.tonnage }} 吨</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">满载吃水深度:</span> |
|||
<span class="value">{{ shipSelectedItem.shipBasicInfo.fullLoadDraft }} 米</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">电压:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shipSelectedItem.shorePower.voltageDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">电流:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shipSelectedItem.shorePower.currentDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">频率:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shipSelectedItem.shorePower.frequencyDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">靠泊状态:</span> |
|||
<span class="value">{{ getOperationTypeLabel(shipSelectedItem.shorePowerAndShip.status, |
|||
SHORE_POWER_STATUS, |
|||
) }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">靠泊类型:</span> |
|||
<span class="value">{{ getOperationTypeLabel(shipSelectedItem.shorePowerAndShip.type, BERTH_TYPE) |
|||
}}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">靠泊时间:</span> |
|||
<span class="value">{{ formatTimestamp(shipSelectedItem?.usageRecordInfo?.actualBerthTime) }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">当前状态:</span> |
|||
<span class="value">{{ shipSelectedItem.shipStatus }}</span> |
|||
</div> |
|||
<div v-if="shipSelectedItem.applyInfo.reason === 0" class="detail-item"> |
|||
<span class="label">岸电使用时长:</span> |
|||
<span class="value">{{ showStatus(shipSelectedItem, realtimeDeviceData)?.useTime }}</span> |
|||
</div> |
|||
<div v-if="shipSelectedItem.applyInfo.reason === 0" class="detail-item"> |
|||
<span class="label">岸电使用用量:</span> |
|||
<span class="value">{{ showStatus(shipSelectedItem, realtimeDeviceData)?.useValue }}</span> |
|||
</div> |
|||
<div v-if="shipSelectedItem.applyInfo.reason != 0" class="detail-item"> |
|||
<span class="label">未使用岸电原因:</span> |
|||
<span class="value">{{ getOperationTypeLabel(shipSelectedItem?.applyInfo?.reason, |
|||
UNUSED_SHORE_POWER_REASON) }}</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.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> |
|||
</div> |
|||
</div> --> |
|||
</div> |
|||
<div v-if="shorepowerSelectedItem && show_type === 'shorepower'" class="right-two-row"> |
|||
<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">{{ shorepowerSelectedItem?.name }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">位置:</span> |
|||
<span class="value">{{ shorepowerSelectedItem?.position }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">电压:</span> |
|||
<span class="value">{{ shorepowerSelectedItem?.shorePowerEquipmentInfo?.voltage }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">频率:</span> |
|||
<span class="value">{{ shorepowerSelectedItem?.shorePowerEquipmentInfo?.frequency }} </span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">容量:</span> |
|||
<span class="value">{{ shorepowerSelectedItem?.shorePowerEquipmentInfo?.capacity }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">当前电压:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shorepowerSelectedItem.voltageDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">当前电流:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shorepowerSelectedItem.currentDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">当前频率:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shorepowerSelectedItem.frequencyDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">当前总用量:</span> |
|||
<span class="value">{{ getValueById(realtimeDeviceData, shorepowerSelectedItem.totalPowerDeviceId, |
|||
'measureValue') }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="label">当前状态:</span> |
|||
<span class="value">{{ shorepowerSelectedItem.storePowerStatus }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- <div v-else class="card digital-twin-card--deep-blue" style="flex: 1;"> |
|||
<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="no-selection">请选择设备以查看详细信息</div> |
|||
</div> |
|||
</div> --> |
|||
</div> |
|||
<ShipHistoryDialog v-model="shipHistoryVisible.visible" :ship-param="shipHistoryVisible.searchParams" |
|||
:realtime-device-data="realtimeDeviceData" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, computed } from 'vue' |
|||
import ShipHistoryDialog from './ShipHistoryDialog.vue' |
|||
import { MapApi } from "@/api/shorepower/map"; |
|||
import { RealtimeDeviceData, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'; |
|||
import { BERTH_TYPE, getOperationTypeLabel, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './dictionaryTable'; |
|||
import { formatDuration, formatTimestamp, getValueById, showStatus } from './utils'; |
|||
// 定义组件属性 |
|||
type ShipItem = ShipRespVo & { id: string; modelInstance?: any; }; |
|||
|
|||
interface Props { |
|||
shipStatusData: ShipItem[]; |
|||
shorePowerStatusData: ShipItem[]; |
|||
realtimeDeviceData: RealtimeDeviceData[]; |
|||
shorePowerList: (ShorePowerBerth & { position: string; })[]; |
|||
shipDataList: ShipRespVo[]; |
|||
// selectedItem: ShipItem | null; |
|||
} |
|||
|
|||
const props = defineProps<Props>() |
|||
|
|||
// 搜索关键字 |
|||
const searchKeyword = ref('') |
|||
const storeSearchKeyword = ref('') |
|||
const show_type = ref('ship') |
|||
const shipSelectedItem = ref<ShipItem | null>(null) |
|||
const shorepowerSelectedItem = ref<ShorePowerBerth & { position: string; } | null>(null) |
|||
|
|||
|
|||
const shipHistoryVisible = ref({ |
|||
visible: false, |
|||
searchParams: { |
|||
shipId: 0 as number | null, |
|||
ids: [] as number[] | null, |
|||
type: 1 as number |
|||
} |
|||
}) |
|||
const currentShorePower = ref<ShorePowerBerth | null>(null) |
|||
const currentShip = ref<ShipItem | null>(null) |
|||
|
|||
// const ShorePowerList = ref<(ShorePowerBerth & { position: string; })[]>([]) |
|||
|
|||
// 过滤后的船舶数据 |
|||
const filteredShipStatusData = computed(() => { |
|||
if (!searchKeyword.value) { |
|||
return props.shipDataList |
|||
} |
|||
return props.shipDataList.filter(ship => |
|||
ship.shipBasicInfo.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) |
|||
) |
|||
}) |
|||
|
|||
// 过滤后的岸电箱数据 |
|||
const filteredShorePowerList = computed(() => { |
|||
if (!storeSearchKeyword.value) { |
|||
return props.shorePowerList |
|||
} |
|||
return props.shorePowerList.filter(shorepower => |
|||
shorepower.name.toLowerCase().includes(storeSearchKeyword.value.toLowerCase()) |
|||
) |
|||
}) |
|||
|
|||
const handleSelectItem = async (item: ShipRespVo) => { |
|||
show_type.value = 'ship' |
|||
const deviceId = item.shorePower?.totalPowerDeviceId; |
|||
const res = await MapApi.getRealtimeDataByIdList({ ids: deviceId }) |
|||
console.log(res) |
|||
shipSelectedItem.value = { |
|||
...item, |
|||
shorePowerEquipment: { |
|||
...item.shorePowerEquipment, |
|||
'measureValue': res[0].measureValue || 'N/A', |
|||
} |
|||
} |
|||
emit('item-click', { |
|||
type: 'ship', |
|||
item: item |
|||
}) |
|||
} |
|||
|
|||
// 处理搜索输入 |
|||
const handleSearch = () => { |
|||
// 输入处理逻辑(如果需要额外处理可以在这里添加) |
|||
} |
|||
|
|||
// 处理岸电箱搜索输入 |
|||
const handlStoreSearch = () => { |
|||
// 输入处理逻辑(如果需要额外处理可以在这里添加) |
|||
} |
|||
|
|||
// 显示岸电箱历史记录 |
|||
const showShorePowerHistory = (shorepower: ShorePowerBerth) => { |
|||
console.log('shorepower', shorepower) |
|||
currentShorePower.value = shorepower |
|||
/* shorePowerHistoryVisible.value = { |
|||
visible: true, |
|||
shorePowerId: shorepower.id || 0 |
|||
} */ |
|||
shipHistoryVisible.value = { |
|||
visible: true, |
|||
searchParams: { |
|||
shipId: null, |
|||
ids: [shorepower.id], |
|||
type: 5, |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 显示船舶历史记录 |
|||
const showShipHistory = (ship: ShipItem) => { |
|||
console.log('ship', ship) |
|||
currentShip.value = ship |
|||
shipHistoryVisible.value = { |
|||
visible: true, |
|||
searchParams: { |
|||
shipId: ship.shipBasicInfo.id, |
|||
ids: null, |
|||
type: 1, |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 定义事件 |
|||
const emit = defineEmits<{ |
|||
(e: 'switch-ship', ship: ShipItem): void; |
|||
(e: 'item-click', item: any): void; |
|||
// handleSelectItem(ship) |
|||
|
|||
}>() |
|||
|
|||
// 处理船舶选择 |
|||
const handleSwitch = (ship: ShipItem, type: string) => { |
|||
// console.log(ship) |
|||
show_type.value = type |
|||
emit('switch-ship', ship) |
|||
handleSelectItem(ship) |
|||
} |
|||
|
|||
const handleSelectShorePower = async (shorepower: ShorePowerBerth & { position: string }) => { |
|||
show_type.value = 'shorepower' |
|||
// selectedItem.value = shorepower |
|||
shorepowerSelectedItem.value = shorepower |
|||
emit('item-click', { |
|||
type: 'shorepower_box', |
|||
item: shorepower |
|||
}) |
|||
// const data = await MapApi.getRealtimeDataByIdList({ ids: shorepower.totalPowerDeviceId }) |
|||
// console.log('voltageDeviceId', data) |
|||
} |
|||
|
|||
const getStatusClass = (status: string | undefined) => { |
|||
switch (status) { |
|||
case '正常': |
|||
return 'status-normal' |
|||
case '在线': |
|||
return 'status-normal' |
|||
case '空闲': |
|||
return 'status-idle' |
|||
case '故障': |
|||
return 'status-fault' |
|||
case '超容': |
|||
return 'status-maintenance' |
|||
case '异常': |
|||
return 'status-abnormal' |
|||
case '维修中': |
|||
return 'status-maintenance' |
|||
case '岸电使用中': |
|||
return 'status-shorepower' |
|||
// case '岸电故障': |
|||
// return 'status-fault' |
|||
default: |
|||
return 'status-default' |
|||
} |
|||
} |
|||
|
|||
watch(() => props.shipDataList, (newVal) => { |
|||
console.log('newVal', newVal) |
|||
}) |
|||
|
|||
onMounted(() => { |
|||
// console.log(props.shorePowerStatusData) |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.port-overview { |
|||
display: flex; |
|||
gap: 10px; |
|||
height: 100%; |
|||
} |
|||
|
|||
.right-two-row { |
|||
display: flex; |
|||
gap: 10px; |
|||
height: 100%; |
|||
} |
|||
|
|||
.search-container { |
|||
height: 100%; |
|||
width: 200px; |
|||
border-radius: 4px; |
|||
border: 1px solid rgb(10, 130, 170); |
|||
color: #FFF; |
|||
padding: 0 10px; |
|||
margin-bottom: 8px; |
|||
background-color: rgba(255, 255, 255, 0.1); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,426 @@ |
|||
<template> |
|||
<el-dialog v-model="visible" title="历史记录" width="1200px" :before-close="handleClose"> |
|||
<!-- <el-tabs v-model="activeTab"> --> |
|||
<!-- 船舶靠泊历史记录 tab --> |
|||
<!-- <el-tab-pane label="船舶靠泊历史记录" name="shipBerthing"> --> |
|||
<!-- 搜索和筛选区域 --> |
|||
<div class="filter-container"> |
|||
<el-form :model="berthingQueryParams" label-width="80px" inline> |
|||
<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 |
|||
@keyup.enter="handleBerthingSearch" /> |
|||
</el-form-item> --> |
|||
<el-form-item label="时间范围"> |
|||
<el-date-picker v-model="berthingDateRange" type="daterange" range-separator="至" start-placeholder="开始日期" |
|||
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleBerthingDateChange" /> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleShorePowerConnectionSearch">搜索</el-button> |
|||
<el-button @click="resetBerthingQuery">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<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" :label="column.label" |
|||
:width="column.width" :formatter="column.formatter" /> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<el-pagination v-model:current-page="berthingQueryParams.pageNo" v-model:page-size="berthingQueryParams.pageSize" |
|||
:page-sizes="[10, 20, 50, 100]" :total="berthingTotal" layout="total, sizes, prev, pager, next, jumper" |
|||
@size-change="handleBerthingSizeChange" @current-change="handleBerthingCurrentChange" /> |
|||
</div> |
|||
<!-- </el-tab-pane> --> |
|||
|
|||
|
|||
<!-- <el-tab-pane label="船舶连接岸电箱历史记录" name="shorePowerConnection"> |
|||
|
|||
<div class="filter-container"> |
|||
<el-form :model="shorePowerConnectionQueryParams" label-width="80px" inline> |
|||
<el-form-item label="关键字"> |
|||
<el-input v-model="shorePowerConnectionQueryParams.keyword" placeholder="请输入关键字搜索" clearable |
|||
@keyup.enter="handleShorePowerConnectionSearch" /> |
|||
</el-form-item> |
|||
<el-form-item label="时间范围"> |
|||
<el-date-picker v-model="shorePowerConnectionDateRange" type="daterange" range-separator="至" |
|||
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" |
|||
@change="handleShorePowerConnectionDateChange" /> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleShorePowerConnectionSearch">搜索</el-button> |
|||
<el-button @click="resetShorePowerConnectionQuery">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
|
|||
<el-table :data="shorePowerConnectionTableData" border stripe style="width: 100%" |
|||
v-loading="shorePowerConnectionLoading"> |
|||
<el-table-column v-for="column in shorePowerConnectionColumns" :key="column.prop" :prop="column.prop" |
|||
:label="column.label" :width="column.width" :formatter="column.formatter" /> |
|||
</el-table> |
|||
|
|||
|
|||
<div class="pagination-container"> |
|||
<el-pagination v-model:current-page="shorePowerConnectionQueryParams.pageNo" |
|||
v-model:page-size="shorePowerConnectionQueryParams.pageSize" :page-sizes="[10, 20, 50, 100]" |
|||
:total="shorePowerConnectionTotal" layout="total, sizes, prev, pager, next, jumper" |
|||
@size-change="handleShorePowerConnectionSizeChange" |
|||
@current-change="handleShorePowerConnectionCurrentChange" /> |
|||
</div> --> |
|||
<!-- </el-tab-pane> --> |
|||
<!-- </el-tabs> --> |
|||
|
|||
<template #footer> |
|||
<span class="dialog-footer"> |
|||
<el-button @click="handleClose">关闭</el-button> |
|||
</span> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, computed, watch } from 'vue' |
|||
import { MapApi } from "@/api/shorepower/map"; |
|||
import { |
|||
ElDialog, |
|||
ElTabs, |
|||
ElTabPane, |
|||
ElForm, |
|||
ElFormItem, |
|||
ElInput, |
|||
ElDatePicker, |
|||
ElButton, |
|||
ElTable, |
|||
ElTableColumn, |
|||
ElPagination |
|||
} from 'element-plus' |
|||
import { CARGO_CATEGORY, FACILITY_TYPE, HARBOR_DISTRICT, getOperationTypeLabel, OPERATION_TYPE } from './dictionaryTable'; |
|||
import { formatDuration, formatTimestamp, showStatus } from './utils'; |
|||
import { RealtimeDeviceData } from '@/types/shorepower'; |
|||
|
|||
// 定义组件属性 |
|||
interface Props { |
|||
modelValue: boolean |
|||
berthingData?: any[] |
|||
shipParam?: { type: number, ids: number[] | null, shipId: number | null } |
|||
shorePowerConnectionData?: any[] |
|||
realtimeDeviceData: RealtimeDeviceData[]; |
|||
} |
|||
|
|||
// 定义事件 |
|||
const emit = defineEmits<{ |
|||
(e: 'update:modelValue', value: boolean): void |
|||
(e: 'berthing-search', params: any): void |
|||
(e: 'shore-power-connection-search', params: any): void |
|||
}>() |
|||
|
|||
// 属性定义 |
|||
const props = withDefaults(defineProps<Props>(), { |
|||
modelValue: false, |
|||
shipId: 0, |
|||
berthingData: () => [], |
|||
shorePowerConnectionData: () => [] |
|||
}) |
|||
|
|||
// 响应式数据 |
|||
const visible = computed({ |
|||
get: () => props.modelValue, |
|||
set: (val) => emit('update:modelValue', val) |
|||
}) |
|||
|
|||
// 船舶靠泊历史记录相关数据 |
|||
const berthingQueryParams = ref({ |
|||
// keyword: '', |
|||
startTime: '', |
|||
endTime: '', |
|||
ids: null as number[] | null, |
|||
shipId: null as number | null, |
|||
type: 1 as number | null, |
|||
pageNo: 1, |
|||
pageSize: 10 |
|||
}) |
|||
|
|||
const berthingDateRange = ref<[string, string]>(['', '']) |
|||
const berthingLoading = ref(false) |
|||
const berthingTotal = ref(0) |
|||
|
|||
// 船舶靠泊历史记录表格数据 |
|||
const berthingTableData = ref<any[]>([]) |
|||
|
|||
// 船舶靠泊历史记录表格列 |
|||
const berthingColumns = ref([ |
|||
{ |
|||
prop: 'shipBasicInfo', |
|||
label: '船舶名称-英文名称', |
|||
width: 200, |
|||
formatter: (row) => { |
|||
const name = row.shipBasicInfo?.name || ''; |
|||
const nameEn = row.shipBasicInfo?.nameEn || ''; |
|||
return `${name}${name && nameEn ? '-' : ''}${nameEn}`; |
|||
} |
|||
}, |
|||
{ prop: 'shipBasicInfo.length', label: '船长', width: 200 }, |
|||
{ prop: 'shipBasicInfo.width', label: '船宽', width: 150 }, |
|||
{ prop: 'shipBasicInfo.tonnage', label: '最大载重', width: 150 }, |
|||
{ prop: 'shipBasicInfo.voyage', label: '航线', width: 150 }, |
|||
{ prop: 'applyInfo.departureHarborDistrict', label: '起运港', width: 150 }, |
|||
{ prop: 'arrivalHarborDistrict', label: '到达港', width: 150 }, |
|||
{ prop: 'applyInfo.operationType', label: '靠泊类型', width: 150, formatter: (_row, _column, cellValue) => getOperationTypeLabel(cellValue, OPERATION_TYPE) }, |
|||
{ prop: '', label: '作业内容', width: 180 }, |
|||
/* { |
|||
prop: 'applyInfo.loadingCargoCategory', label: '货物类型', width: 180, |
|||
formatter: (row) => { |
|||
if (row.applyInfo.operationType === 1) { |
|||
return getOperationTypeLabel(row.applyInfo.loadingCargoCategory, CARGO_CATEGORY); |
|||
} else if (row.applyInfo.operationType === 2) { |
|||
return row.applyInfo.unloadingCargoCategory; |
|||
} else if (row.applyInfo.operationType === 3) { |
|||
return '装' + row.applyInfo.loadingCargoCategory + '-卸' + row.applyInfo.unloadingCargoCategory |
|||
} |
|||
return ''; |
|||
} |
|||
}, */ |
|||
|
|||
{ |
|||
prop: 'applyInfo.loadingCargoCategory', label: '货物名称', width: 180, |
|||
formatter: (row) => { |
|||
if (row.applyInfo.operationType === 1) { |
|||
const label = getOperationTypeLabel(row.applyInfo.loadingCargoCategory, CARGO_CATEGORY); |
|||
return label; |
|||
} else if (row.applyInfo.operationType === 2) { |
|||
const label = getOperationTypeLabel(row.applyInfo.unloadingCargoCategory, CARGO_CATEGORY); |
|||
return label; |
|||
} else if (row.applyInfo.operationType === 3) { |
|||
return '装' + getOperationTypeLabel(row.applyInfo.loadingCargoCategory, CARGO_CATEGORY) + '-卸' + getOperationTypeLabel(row.applyInfo.unloadingCargoCategory, CARGO_CATEGORY) |
|||
} |
|||
return ''; |
|||
} |
|||
}, |
|||
{ |
|||
prop: 'applyInfo.loadingCargoCategory', label: '货物吨数', width: 180, |
|||
formatter: (row) => { |
|||
if (row.applyInfo.operationType === 1) { |
|||
return row.applyInfo.loadingCargoTonnage; |
|||
} else if (row.applyInfo.operationType === 2) { |
|||
return row.applyInfo.unloadingCargoTonnage; |
|||
} else if (row.applyInfo.operationType === 3) { |
|||
return '装' + row.applyInfo.loadingCargoTonnage + '-卸' + row.applyInfo.unloadingCargoTonnage |
|||
} |
|||
return ''; |
|||
} |
|||
}, |
|||
{ prop: 'usageRecordInfo.actualBerthTime', label: '靠泊时间', width: 180, formatter: (row) => formatTimestamp(row.usageRecordInfo.actualBerthTime) }, |
|||
{ prop: 'usageRecordInfo.actualDepartureTime', label: '离泊时间', width: 180, formatter: (row) => formatTimestamp(row.usageRecordInfo.actualDepartureTime) }, |
|||
{ |
|||
prop: 'useStatus.status', label: '岸电使用情况', width: 180, |
|||
formatter: (row) => { |
|||
const status = showStatus(row, props.realtimeDeviceData); |
|||
return status?.status || ''; |
|||
} |
|||
}, |
|||
|
|||
]) |
|||
|
|||
const shorePowerConnectionTotal = ref(0) |
|||
|
|||
// 处理日期变化 |
|||
const handleBerthingDateChange = (val: [string, string]) => { |
|||
if (val && val.length === 2) { |
|||
berthingQueryParams.value.startTime = val[0] + ' 00:00:00' |
|||
berthingQueryParams.value.endTime = val[1] + ' 23:59:59' |
|||
} else { |
|||
berthingQueryParams.value.startTime = '' |
|||
berthingQueryParams.value.endTime = '' |
|||
} |
|||
handleShorePowerConnectionSearch() |
|||
} |
|||
|
|||
// 处理关闭 |
|||
const handleClose = () => { |
|||
berthingQueryParams.value.ids = null |
|||
berthingQueryParams.value.shipId = null |
|||
berthingQueryParams.value.type = 1 |
|||
visible.value = false |
|||
} |
|||
|
|||
// 船舶靠泊历史记录相关方法 |
|||
const handleBerthingSearch = () => { |
|||
// 发送搜索事件给父组件 |
|||
emit('berthing-search', { ...berthingQueryParams.value }) |
|||
} |
|||
|
|||
const resetBerthingQuery = () => { |
|||
// berthingQueryParams.value.keyword = '' |
|||
berthingQueryParams.value.startTime = '' |
|||
berthingQueryParams.value.endTime = '' |
|||
berthingDateRange.value = ['', ''] |
|||
berthingQueryParams.value.pageNo = 1 |
|||
handleShorePowerConnectionSearch() |
|||
} |
|||
|
|||
|
|||
const handleBerthingSizeChange = (val: number) => { |
|||
berthingQueryParams.value.pageSize = val |
|||
berthingQueryParams.value.pageNo = 1 |
|||
handleShorePowerConnectionSearch() |
|||
} |
|||
|
|||
const handleBerthingCurrentChange = (val: number) => { |
|||
berthingQueryParams.value.pageNo = val |
|||
handleShorePowerConnectionSearch() |
|||
} |
|||
|
|||
// 船舶连接岸电箱历史记录相关方法 |
|||
const handleShorePowerConnectionSearch = () => { |
|||
// const |
|||
const params = { |
|||
// shipId: berthingQueryParams.value.shipId, |
|||
// ids: berthingQueryParams.value.ids, |
|||
...(berthingQueryParams.value.ids ? { ids: berthingQueryParams.value.ids } : {}), |
|||
...(berthingQueryParams.value.shipId ? { shipId: berthingQueryParams.value.shipId } : {}), |
|||
type: berthingQueryParams.value.type, |
|||
pageNo: 1, |
|||
pageSize: 9999 // 请求足够多的数据,以便在前端进行筛选和分页 |
|||
} |
|||
handleGetShipHistortyList(params) |
|||
// 发送搜索事件给父组件 |
|||
// emit('shore-power-connection-search', { ...shorePowerConnectionQueryParams.value }) |
|||
} |
|||
|
|||
// 监听数据变化并更新总数 |
|||
watch( |
|||
() => props.berthingData, |
|||
(newData) => { |
|||
berthingTotal.value = newData?.length || 0 |
|||
}, |
|||
{ immediate: true } |
|||
) |
|||
|
|||
watch( |
|||
() => props.shorePowerConnectionData, |
|||
(newData) => { |
|||
shorePowerConnectionTotal.value = newData?.length || 0 |
|||
}, |
|||
{ immediate: true } |
|||
) |
|||
|
|||
watch( |
|||
() => props.shipParam, |
|||
(newShipParam) => { |
|||
if (newShipParam) { |
|||
// 当船舶ID变化时,触发搜索 |
|||
berthingTableData.value = [] |
|||
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 (param) => { |
|||
console.log('handleGetShipHistortyList', param) |
|||
if (!param) return; |
|||
// try { |
|||
const res = await MapApi.getShipHistoryPage({ |
|||
/* shipId: param.id, // 将字符串转换为数字 |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
type: param.type, */ |
|||
// ids: [parseInt(id, 10)] |
|||
...param |
|||
}) |
|||
console.log(res); |
|||
|
|||
// 本地日期筛选逻辑 |
|||
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 harborDistructList = await HARBOR_DISTRICT() |
|||
const buildData = await Promise.all(paginatedList.map(async item => ({ |
|||
...item, |
|||
arrivalHarborDistrict: getOperationTypeLabel(item.applyInfo.arrivalHarborDistrict, harborDistructList), |
|||
}))) |
|||
berthingTableData.value = buildData |
|||
berthingTotal.value = totalFiltered |
|||
} |
|||
|
|||
onMounted(() => { |
|||
console.log('页面加载了!') |
|||
// console.log('props.shipId', props.shipId) |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.filter-container { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.pagination-container { |
|||
margin-top: 20px; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
.dialog-footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 10px; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
@ -0,0 +1,343 @@ |
|||
<template> |
|||
<el-dialog v-model="visible" title="岸电箱历史记录" width="1200px" :before-close="handleClose"> |
|||
<el-tabs v-model="activeTab"> |
|||
<!-- 岸电箱历史记录 tab --> |
|||
<el-tab-pane label="岸电箱历史记录" name="shorePower"> |
|||
<!-- 搜索和筛选区域 --> |
|||
<div class="filter-container"> |
|||
<el-form :model="queryParams" label-width="80px" inline> |
|||
<el-form-item label="关键字"> |
|||
<el-input v-model="queryParams.keyword" placeholder="请输入关键字搜索" clearable @keyup.enter="handleSearch" /> |
|||
</el-form-item> |
|||
<el-form-item label="时间范围"> |
|||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" |
|||
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleDateChange" /> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch">搜索</el-button> |
|||
<el-button @click="resetQuery">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<el-table :data="shorePowerTableData" border stripe style="width: 100%" v-loading="loading"> |
|||
<el-table-column v-for="column in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label" |
|||
:width="column.width" :formatter="column.formatter" /> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<el-pagination v-model:current-page="queryParams.pageNo" v-model:page-size="queryParams.pageSize" |
|||
:page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper" |
|||
@size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
|||
</div> |
|||
</el-tab-pane> |
|||
|
|||
<!-- 岸电箱连接船舶记录 tab --> |
|||
<el-tab-pane label="岸电箱连接船舶记录" name="shipConnection"> |
|||
<!-- 搜索和筛选区域 --> |
|||
<div class="filter-container"> |
|||
<el-form :model="shipConnectionQueryParams" label-width="80px" inline> |
|||
<el-form-item label="关键字"> |
|||
<el-input v-model="shipConnectionQueryParams.keyword" placeholder="请输入关键字搜索" clearable |
|||
@keyup.enter="handleShipConnectionSearch" /> |
|||
</el-form-item> |
|||
<el-form-item label="时间范围"> |
|||
<el-date-picker v-model="shipConnectionDateRange" type="daterange" range-separator="至" |
|||
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" |
|||
@change="handleShipConnectionDateChange" /> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleShipConnectionSearch">搜索</el-button> |
|||
<el-button @click="resetShipConnectionQuery">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<el-table :data="shipConnectionTableData" border stripe style="width: 100%" v-loading="shipConnectionLoading"> |
|||
<el-table-column v-for="column in shipConnectionColumns" :key="column.prop" :prop="column.prop" |
|||
:label="column.label" :width="column.width" :formatter="column.formatter" /> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<el-pagination v-model:current-page="shipConnectionQueryParams.pageNo" |
|||
v-model:page-size="shipConnectionQueryParams.pageSize" :page-sizes="[10, 20, 50, 100]" |
|||
:total="shipConnectionTotal" layout="total, sizes, prev, pager, next, jumper" |
|||
@size-change="handleShipConnectionSizeChange" @current-change="handleShipConnectionCurrentChange" /> |
|||
</div> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
|
|||
<template #footer> |
|||
<span class="dialog-footer"> |
|||
<el-button @click="handleClose">关闭</el-button> |
|||
</span> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, computed, watch } from 'vue' |
|||
import { |
|||
ElDialog, |
|||
ElTabs, |
|||
ElTabPane, |
|||
ElForm, |
|||
ElFormItem, |
|||
ElInput, |
|||
ElDatePicker, |
|||
ElButton, |
|||
ElTable, |
|||
ElTableColumn, |
|||
ElPagination |
|||
} from 'element-plus' |
|||
import { MapApi } from "@/api/shorepower/map"; |
|||
|
|||
// 定义组件属性 |
|||
interface Props { |
|||
modelValue: boolean |
|||
data?: any[] |
|||
shipConnectionData?: any[] |
|||
shorePowerId: number |
|||
} |
|||
|
|||
// 定义事件 |
|||
const emit = defineEmits<{ |
|||
(e: 'update:modelValue', value: boolean): void |
|||
(e: 'search', params: any): void |
|||
(e: 'ship-connection-search', params: any): void |
|||
}>() |
|||
|
|||
// 属性定义 |
|||
const props = withDefaults(defineProps<Props>(), { |
|||
modelValue: false, |
|||
shorePowerId: 0, |
|||
data: () => [], |
|||
shipConnectionData: () => [] |
|||
}) |
|||
|
|||
// 响应式数据 |
|||
const visible = computed({ |
|||
get: () => props.modelValue, |
|||
set: (val) => emit('update:modelValue', val) |
|||
}) |
|||
|
|||
const shorePowerTableData = ref<any[]>([]) |
|||
const activeTab = ref('shorePower') |
|||
|
|||
// 岸电箱历史记录相关数据 |
|||
const queryParams = ref({ |
|||
keyword: '', |
|||
startTime: '', |
|||
endTime: '', |
|||
pageNo: 1, |
|||
pageSize: 10 |
|||
}) |
|||
|
|||
const dateRange = ref<[string, string]>(['', '']) |
|||
const loading = ref(false) |
|||
const total = ref(0) |
|||
|
|||
// 表格数据计算属性 |
|||
const tableData = computed(() => { |
|||
// 在实际应用中,这里应该是从服务器获取的数据 |
|||
// 当前为了演示,我们使用传入的模拟数据 |
|||
return props.data?.slice( |
|||
(queryParams.value.pageNo - 1) * queryParams.value.pageSize, |
|||
queryParams.value.pageNo * queryParams.value.pageSize |
|||
) || [] |
|||
}) |
|||
|
|||
// 表格列 |
|||
const tableColumns = ref([ |
|||
{ prop: 'id', label: '记录ID', width: 80 }, |
|||
{ prop: 'time', label: '时间', width: 180 }, |
|||
{ prop: 'operation', label: '操作类型' }, |
|||
{ prop: 'voltage', label: '电压(V)' }, |
|||
{ prop: 'current', label: '电流(A)' }, |
|||
{ prop: 'power', label: '功率(kW)' }, |
|||
{ prop: 'energy', label: '用电量(kWh)' } |
|||
]) |
|||
|
|||
// 岸电箱连接船舶记录相关数据 |
|||
const shipConnectionQueryParams = ref({ |
|||
keyword: '', |
|||
startTime: '', |
|||
endTime: '', |
|||
pageNo: 1, |
|||
pageSize: 10 |
|||
}) |
|||
|
|||
const shipConnectionDateRange = ref<[string, string]>(['', '']) |
|||
const shipConnectionLoading = ref(false) |
|||
const shipConnectionTotal = ref(0) |
|||
|
|||
// 岸电箱连接船舶记录表格数据 |
|||
const shipConnectionTableData = computed(() => { |
|||
// 在实际应用中,这里应该是从服务器获取的数据 |
|||
// 当前为了演示,我们使用传入的模拟数据 |
|||
return props.shipConnectionData?.slice( |
|||
(shipConnectionQueryParams.value.pageNo - 1) * shipConnectionQueryParams.value.pageSize, |
|||
shipConnectionQueryParams.value.pageNo * shipConnectionQueryParams.value.pageSize |
|||
) || [] |
|||
}) |
|||
|
|||
// 岸电箱连接船舶记录表格列 |
|||
const shipConnectionColumns = ref([ |
|||
{ prop: 'id', label: '记录ID', width: 80 }, |
|||
{ prop: 'shipName', 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 = () => { |
|||
visible.value = false |
|||
} |
|||
|
|||
// 岸电箱历史记录相关方法 |
|||
const handleSearch = () => { |
|||
// 发送搜索事件给父组件 |
|||
emit('search', { ...queryParams.value }) |
|||
} |
|||
|
|||
const resetQuery = () => { |
|||
queryParams.value.keyword = '' |
|||
queryParams.value.startTime = '' |
|||
queryParams.value.endTime = '' |
|||
dateRange.value = ['', ''] |
|||
queryParams.value.pageNo = 1 |
|||
handleSearch() |
|||
} |
|||
|
|||
const handleDateChange = (val: [string, string] | null) => { |
|||
if (val && val[0] && val[1]) { |
|||
queryParams.value.startTime = val[0] |
|||
queryParams.value.endTime = val[1] |
|||
} else { |
|||
queryParams.value.startTime = '' |
|||
queryParams.value.endTime = '' |
|||
} |
|||
} |
|||
|
|||
const handleSizeChange = (val: number) => { |
|||
queryParams.value.pageSize = val |
|||
queryParams.value.pageNo = 1 |
|||
handleSearch() |
|||
} |
|||
|
|||
const handleCurrentChange = (val: number) => { |
|||
queryParams.value.pageNo = val |
|||
handleSearch() |
|||
} |
|||
|
|||
// 岸电箱连接船舶记录相关方法 |
|||
const handleShipConnectionSearch = () => { |
|||
// 发送搜索事件给父组件 |
|||
emit('ship-connection-search', { ...shipConnectionQueryParams.value }) |
|||
} |
|||
|
|||
const resetShipConnectionQuery = () => { |
|||
shipConnectionQueryParams.value.keyword = '' |
|||
shipConnectionQueryParams.value.startTime = '' |
|||
shipConnectionQueryParams.value.endTime = '' |
|||
shipConnectionDateRange.value = ['', ''] |
|||
shipConnectionQueryParams.value.pageNo = 1 |
|||
handleShipConnectionSearch() |
|||
} |
|||
|
|||
const handleShipConnectionDateChange = (val: [string, string] | null) => { |
|||
if (val && val[0] && val[1]) { |
|||
shipConnectionQueryParams.value.startTime = val[0] |
|||
shipConnectionQueryParams.value.endTime = val[1] |
|||
} else { |
|||
shipConnectionQueryParams.value.startTime = '' |
|||
shipConnectionQueryParams.value.endTime = '' |
|||
} |
|||
} |
|||
|
|||
const handleShipConnectionSizeChange = (val: number) => { |
|||
shipConnectionQueryParams.value.pageSize = val |
|||
shipConnectionQueryParams.value.pageNo = 1 |
|||
handleShipConnectionSearch() |
|||
} |
|||
|
|||
const handleShipConnectionCurrentChange = (val: number) => { |
|||
shipConnectionQueryParams.value.pageNo = val |
|||
handleShipConnectionSearch() |
|||
} |
|||
|
|||
// 监听数据变化并更新总数 |
|||
watch( |
|||
() => props.data, |
|||
(newData) => { |
|||
total.value = newData?.length || 0 |
|||
}, |
|||
{ immediate: true } |
|||
) |
|||
|
|||
watch( |
|||
() => props.shipConnectionData, |
|||
(newData) => { |
|||
shipConnectionTotal.value = newData?.length || 0 |
|||
}, |
|||
{ immediate: true } |
|||
) |
|||
|
|||
watch( |
|||
() => props.shorePowerId, |
|||
(newShorePowerId) => { |
|||
if (newShorePowerId) { |
|||
// 当岸电箱ID变化时,触发搜索 |
|||
shorePowerTableData.value = [] |
|||
handleGetShorePowerHistoryList(newShorePowerId) |
|||
} |
|||
} |
|||
) |
|||
|
|||
const handleGetShorePowerHistoryList = async (id) => { |
|||
console.log('handleGetShorePowerHistoryList', id) |
|||
if (!id) return; |
|||
// try { |
|||
const res = await MapApi.getShipHistoryPage({ |
|||
// shipId: parseInt(id, 10), // 将字符串转换为数字 |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
type: 5, |
|||
ids: [parseInt(id, 10)] |
|||
}) |
|||
console.log(res); |
|||
const buildData = await Promise.all(res.list.map(async item => ({ |
|||
...item, |
|||
}))) |
|||
shorePowerTableData.value = buildData |
|||
} |
|||
|
|||
onMounted(() => { |
|||
console.log('页面加载了!') |
|||
console.log('props.shipId', props.shorePowerId) |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.filter-container { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.pagination-container { |
|||
margin-top: 20px; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
.dialog-footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 10px; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,184 @@ |
|||
<template> |
|||
<div ref="chartContainer" class="bar-chart-minute-container"></div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue' |
|||
import * as echarts from 'echarts' |
|||
|
|||
// 定义组件属性 |
|||
interface Props { |
|||
chartData?: Array<{ name: string; value: number }> |
|||
title?: string |
|||
color?: string |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
chartData: () => [], |
|||
title: '', |
|||
color: '#4CAF50' // 固定使用绿色 |
|||
}) |
|||
|
|||
// 图表容器引用和实例 |
|||
const chartContainer = ref<HTMLDivElement | null>(null) |
|||
let chartInstance: echarts.ECharts | null = null |
|||
|
|||
// 初始化图表 |
|||
const initChart = () => { |
|||
if (chartContainer.value) { |
|||
chartInstance = echarts.init(chartContainer.value) |
|||
updateChart() |
|||
} |
|||
} |
|||
|
|||
// 更新图表 |
|||
const updateChart = () => { |
|||
if (!chartInstance || !props.chartData.length) return |
|||
|
|||
const option = { |
|||
title: { |
|||
// text: props.title, |
|||
textStyle: { |
|||
color: '#fff', |
|||
fontSize: 14 |
|||
}, |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis', |
|||
axisPointer: { |
|||
type: 'shadow' |
|||
}, |
|||
backgroundColor: 'rgba(0, 0, 0, 0.7)', |
|||
borderColor: 'rgba(30, 120, 255, 0.4)', |
|||
borderWidth: 1, |
|||
textStyle: { |
|||
color: '#fff' |
|||
} |
|||
}, |
|||
grid: { |
|||
left: '1%', |
|||
right: '1%', |
|||
top: '5%', |
|||
bottom: '1%', |
|||
containLabel: true |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: props.chartData.map(item => item.name), |
|||
axisLine: { |
|||
lineStyle: { |
|||
color: 'rgba(255, 255, 255, 0.3)' |
|||
} |
|||
}, |
|||
axisLabel: { |
|||
color: 'rgba(255, 255, 255, 0.7)', |
|||
rotate: 0, |
|||
interval: Math.ceil(props.chartData.length / 24), // 每小时显示一个标签 |
|||
formatter: (value: string) => { |
|||
// 只显示小时:00:00的标签 |
|||
if (value.endsWith(':00:00')) { |
|||
return value.split(':')[0] + '时' |
|||
} |
|||
return '' |
|||
}, |
|||
margin: 10 |
|||
}, |
|||
axisTick: { |
|||
interval: Math.ceil(props.chartData.length / 24) |
|||
} |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
axisLine: { |
|||
lineStyle: { |
|||
color: 'rgba(255, 255, 255, 0.3)' |
|||
} |
|||
}, |
|||
splitLine: { |
|||
lineStyle: { |
|||
color: 'rgba(255, 255, 255, 0.1)' |
|||
} |
|||
}, |
|||
axisLabel: { |
|||
color: 'rgba(255, 255, 255, 0.7)' |
|||
} |
|||
}, |
|||
series: [ |
|||
{ |
|||
type: 'bar', |
|||
data: props.chartData.map(item => item.value), |
|||
barWidth: 1, // 设置柱子宽度为1px |
|||
itemStyle: { |
|||
color: { |
|||
type: 'linear', |
|||
x: 0, |
|||
y: 0, |
|||
x2: 0, |
|||
y2: 1, |
|||
colorStops: [ |
|||
{ |
|||
offset: 0, |
|||
color: '#4CAF50' // 固定使用绿色 |
|||
}, |
|||
{ |
|||
offset: 1, |
|||
color: '#4CAF5080' // 绿色带透明度 |
|||
} |
|||
] |
|||
}, |
|||
borderRadius: [1, 1, 0, 0] |
|||
}, |
|||
emphasis: { |
|||
itemStyle: { |
|||
color: '#4CAF50' // 固定使用绿色高亮 |
|||
} |
|||
} |
|||
} |
|||
] |
|||
} |
|||
|
|||
chartInstance.setOption(option) |
|||
} |
|||
|
|||
// 监听数据变化 |
|||
watch( |
|||
() => props.chartData, |
|||
() => { |
|||
updateChart() |
|||
}, |
|||
{ deep: true } |
|||
) |
|||
|
|||
// 窗口大小改变时重绘图表 |
|||
const handleResize = () => { |
|||
if (chartInstance) { |
|||
chartInstance.resize() |
|||
} |
|||
} |
|||
|
|||
// 组件挂载时初始化图表 |
|||
onMounted(() => { |
|||
setTimeout(() => { |
|||
initChart() |
|||
}, 200) |
|||
window.addEventListener('resize', handleResize) |
|||
}) |
|||
|
|||
// 组件销毁前清理资源 |
|||
onBeforeUnmount(() => { |
|||
window.removeEventListener('resize', handleResize) |
|||
if (chartInstance) { |
|||
chartInstance.dispose() |
|||
chartInstance = null |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.bar-chart-minute-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
/* min-height: 200px; */ |
|||
} |
|||
</style> |
|||
@ -0,0 +1,183 @@ |
|||
<template> |
|||
<div ref="chartContainer" class="pie-chart-container"></div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue' |
|||
import * as echarts from 'echarts' |
|||
|
|||
// 定义组件props |
|||
interface Props { |
|||
chartData?: Array<{ name: string; value: number }> |
|||
title?: string |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
chartData: () => [], |
|||
title: '' |
|||
}) |
|||
|
|||
const chartContainer = ref<HTMLDivElement | null>(null) |
|||
let chartInstance: echarts.ECharts | null = null |
|||
|
|||
// 初始化图表 |
|||
const initChart = () => { |
|||
if (chartContainer.value) { |
|||
chartInstance = echarts.init(chartContainer.value) |
|||
updateChart() |
|||
} |
|||
} |
|||
|
|||
// 计算数据总和 |
|||
const getTotalValue = () => { |
|||
if (!props.chartData) return 0 |
|||
const total = props.chartData.reduce((sum, item) => sum + item.value, 0) |
|||
return Math.round(total * 100) / 100 // 保留两位小数 |
|||
} |
|||
|
|||
// 更新图表 |
|||
const updateChart = () => { |
|||
if (!chartInstance || !props.chartData) return |
|||
|
|||
const totalValue = getTotalValue() |
|||
|
|||
const option = { |
|||
title: { |
|||
// text: props.title, |
|||
left: 'center', |
|||
textStyle: { |
|||
color: '#FFF' |
|||
} |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
show: true, |
|||
// type: 'scroll', // 👈 开启可滚动图例 |
|||
orient: 'horizontal', // 👈 水平排列 |
|||
top: 10, // 👈 放在顶部 |
|||
left: 'center', // 👈 居中对齐 |
|||
textStyle: { |
|||
color: '#FFF' |
|||
} |
|||
}, |
|||
series: [ |
|||
{ |
|||
name: props.title, |
|||
type: 'pie', |
|||
radius: ['35%', '55%'], // 调整内外半径,增加厚度 |
|||
center: ['50%', '40%'], // 👈 调整饼图位置,因为图例已移到顶部 |
|||
padAngle: 5, // 添加间隙角度 |
|||
avoidLabelOverlap: true, |
|||
itemStyle: { |
|||
borderRadius: 0, |
|||
borderColor: 'transparent', // 取消边框 |
|||
borderWidth: 0 // 边框宽度设为0 |
|||
}, |
|||
label: { |
|||
show: true, |
|||
formatter: (params) => { |
|||
return params.name + '\n' + params.value.toFixed(2) |
|||
}, |
|||
fontSize: 16, |
|||
color: '#FFFFFF', // 设置文本颜色为白色 |
|||
textBorderColor: 'transparent', // 取消文本边缘的发光效果 |
|||
textBorderWidth: 0 |
|||
}, |
|||
emphasis: { |
|||
label: { |
|||
show: true, |
|||
formatter: (params) => { |
|||
return params.name + '\n' + params.value.toFixed(2) |
|||
}, |
|||
fontSize: 14, |
|||
fontWeight: 'bold', |
|||
color: '#FFFFFF', // 设置强调时文本颜色为白色 |
|||
textBorderColor: 'transparent', // 取消文本边缘的发光效果 |
|||
textBorderWidth: 0 |
|||
} |
|||
}, |
|||
labelLine: { |
|||
show: true |
|||
}, |
|||
data: props.chartData |
|||
}, |
|||
{ |
|||
name: '总量', |
|||
type: 'pie', |
|||
radius: ['0%', '35%'], // 内部圆环用于显示总数,与外环内半径匹配 |
|||
center: ['50%', '40%'], |
|||
hoverAnimation: false, // 禁用悬停动画 |
|||
label: { |
|||
show: true, |
|||
position: 'center', |
|||
formatter: [`{title|总数}`, `{value|${totalValue.toFixed(2)}}`].join('\n'), |
|||
rich: { |
|||
title: { |
|||
color: '#FFF', |
|||
fontSize: 14, |
|||
lineHeight: 20 |
|||
}, |
|||
value: { |
|||
color: '#FFF', |
|||
fontSize: 20, |
|||
fontWeight: 'bold', |
|||
lineHeight: 30 |
|||
} |
|||
} |
|||
}, |
|||
labelLine: { |
|||
show: false |
|||
}, |
|||
data: [ |
|||
{ |
|||
value: totalValue, |
|||
itemStyle: { |
|||
color: 'rgba(0, 0, 0, 0)' // 透明色 |
|||
} |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
|
|||
chartInstance.setOption(option, true) |
|||
} |
|||
|
|||
// 窗口大小改变时重置图表大小 |
|||
const handleResize = () => { |
|||
if (chartInstance) { |
|||
chartInstance.resize() |
|||
} |
|||
} |
|||
|
|||
// 监听数据变化 |
|||
watch( |
|||
() => props.chartData, |
|||
() => { |
|||
updateChart() |
|||
}, |
|||
{ deep: true } |
|||
) |
|||
|
|||
// 初始化和销毁 |
|||
onMounted(() => { |
|||
initChart() |
|||
window.addEventListener('resize', handleResize) |
|||
}) |
|||
|
|||
onBeforeUnmount(() => { |
|||
if (chartInstance) { |
|||
chartInstance.dispose() |
|||
} |
|||
window.removeEventListener('resize', handleResize) |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.pie-chart-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,254 @@ |
|||
<template> |
|||
<div ref="chartContainer" class="wave-line-chart-container"></div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue' |
|||
import * as echarts from 'echarts' |
|||
|
|||
// 定义组件属性 |
|||
interface Props { |
|||
chartData?: Array<{ name: string; value: number }> |
|||
title?: string |
|||
color?: string |
|||
magnifyMode?: boolean |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
chartData: () => [], |
|||
title: '', |
|||
color: '#1296db', |
|||
magnifyMode: false |
|||
}) |
|||
|
|||
// 图表容器引用和实例 |
|||
const chartContainer = ref<HTMLDivElement | null>(null) |
|||
let chartInstance: echarts.ECharts | null = null |
|||
|
|||
// 初始化图表 |
|||
const initChart = () => { |
|||
if (chartContainer.value) { |
|||
chartInstance = echarts.init(chartContainer.value) |
|||
updateChart() |
|||
} |
|||
} |
|||
|
|||
// 更新图表 |
|||
const updateChart = () => { |
|||
if (!chartInstance || !props.chartData.length) return |
|||
|
|||
// 创建波浪效果的数据点 |
|||
const waveData = props.chartData.map(item => { |
|||
return { |
|||
...item, |
|||
value: item.value |
|||
} |
|||
}) |
|||
|
|||
const option = { |
|||
title: { |
|||
// text: props.title, |
|||
textStyle: { |
|||
color: '#fff', |
|||
fontSize: props.magnifyMode ? 24 : 14 |
|||
}, |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis', |
|||
backgroundColor: 'rgba(0, 0, 0, 0.7)', |
|||
borderColor: 'rgba(30, 120, 255, 0.4)', |
|||
borderWidth: 1, |
|||
textStyle: { |
|||
color: '#fff', |
|||
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: { |
|||
type: 'category', |
|||
data: props.chartData.map(item => item.name), |
|||
axisLine: { |
|||
lineStyle: { |
|||
color: 'rgba(255, 255, 255, 0.3)' |
|||
} |
|||
}, |
|||
axisLabel: { |
|||
color: 'rgba(255, 255, 255, 0.7)', |
|||
fontSize: props.magnifyMode ? 18 : 10, |
|||
rotate: 0, |
|||
interval: Math.ceil(props.chartData.length / 12), // 减少间隔以显示更多标签 |
|||
formatter: (value: string) => { |
|||
// 更灵活的时间格式化 |
|||
if (value.includes(':')) { |
|||
const parts = value.split(':'); |
|||
if (parts.length >= 2) { |
|||
// 如果是完整的时间格式 HH:mm:ss |
|||
if (parts[2] && parts[2] === '00') { |
|||
return parts[0] + ':' + parts[1]; |
|||
} |
|||
// 如果是 HH:mm 格式 |
|||
return parts[0] + ':' + parts[1]; |
|||
} |
|||
} |
|||
return value; |
|||
}, |
|||
margin: 10 |
|||
} |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
// 设置 Y 轴最小值略低于数据中的最小值,以提供更好的可视化效果 |
|||
min: (value: any) => { |
|||
if (props.chartData && props.chartData.length > 0) { |
|||
// 查找数据中的最小值 |
|||
const minValue = Math.min(...props.chartData.map(item => item.value)); |
|||
// 计算一个合适的最小值,略低于实际最小值 |
|||
// const range = value.max - minValue; |
|||
return minValue; // 最小值不低于0 |
|||
} |
|||
return undefined; |
|||
}, |
|||
axisLine: { |
|||
lineStyle: { |
|||
color: 'rgba(255, 255, 255, 0.3)' |
|||
} |
|||
}, |
|||
splitLine: { |
|||
lineStyle: { |
|||
color: 'rgba(255, 255, 255, 0.1)' |
|||
} |
|||
}, |
|||
axisLabel: { |
|||
color: 'rgba(255, 255, 255, 0.7)', |
|||
fontSize: props.magnifyMode ? 18 : 10 |
|||
} |
|||
}, |
|||
series: [ |
|||
{ |
|||
data: waveData.map(item => item.value), |
|||
type: 'line', |
|||
smooth: true, |
|||
showSymbol: true, |
|||
symbol: 'circle', |
|||
symbolSize: props.magnifyMode ? 8 : 4, |
|||
lineStyle: { |
|||
color: props.color, |
|||
width: props.magnifyMode ? 4 : 2 |
|||
}, |
|||
areaStyle: { |
|||
color: { |
|||
type: 'linear', |
|||
x: 0, |
|||
y: 0, |
|||
x2: 0, |
|||
y2: 1, |
|||
colorStops: [ |
|||
{ |
|||
offset: 0, |
|||
color: props.color + '80' // 添加透明度 |
|||
}, |
|||
{ |
|||
offset: 1, |
|||
color: props.color + '00' // 透明 |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
// 添加波浪效果的特殊配置 |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
// 显示数据点和数值 |
|||
label: { |
|||
show: true, |
|||
position: 'top', |
|||
color: '#fff', |
|||
fontSize: props.magnifyMode ? 16 : 10, |
|||
formatter: '{c}', |
|||
distance: props.magnifyMode ? 10 : 5 |
|||
} |
|||
}, |
|||
// 添加一个辅助系列来增强波浪视觉效果 |
|||
{ |
|||
data: props.chartData.map(item => item.value), |
|||
type: 'line', |
|||
smooth: true, |
|||
showSymbol: false, |
|||
lineStyle: { |
|||
color: props.color + '60', |
|||
width: props.magnifyMode ? 2 : 1, |
|||
type: 'dashed' |
|||
}, |
|||
// 辅助系列不显示在tooltip中 |
|||
tooltip: { |
|||
show: false |
|||
}, |
|||
// 辅助系列不显示数据标签以避免重叠 |
|||
label: { |
|||
show: false |
|||
} |
|||
} |
|||
], |
|||
grid: { |
|||
left: '1%', |
|||
right: '1%', |
|||
top: props.title ? (props.magnifyMode ? '10%' : '15%') : '5%', |
|||
bottom: '1%', |
|||
containLabel: true |
|||
} |
|||
} |
|||
|
|||
chartInstance.setOption(option) |
|||
} |
|||
|
|||
// 监听数据变化 |
|||
watch( |
|||
() => props.chartData, |
|||
() => { |
|||
updateChart() |
|||
}, |
|||
{ deep: true } |
|||
) |
|||
|
|||
// 窗口大小改变时重绘图表 |
|||
const handleResize = () => { |
|||
if (chartInstance) { |
|||
chartInstance.resize() |
|||
} |
|||
} |
|||
|
|||
// 组件挂载时初始化图表 |
|||
onMounted(() => { |
|||
// 延迟初始化,确保DOM已经完全渲染 |
|||
setTimeout(() => { |
|||
initChart() |
|||
}, 200) |
|||
window.addEventListener('resize', handleResize) |
|||
}) |
|||
|
|||
// 组件销毁前清理资源 |
|||
onBeforeUnmount(() => { |
|||
window.removeEventListener('resize', handleResize) |
|||
if (chartInstance) { |
|||
chartInstance.dispose() |
|||
chartInstance = null |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.wave-line-chart-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
min-height: 0; |
|||
} |
|||
</style> |
|||
@ -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 }, |
|||
] |
|||
@ -1,240 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>CesiumJS 天地图纯影像示例</title> |
|||
|
|||
<link href="https://cesium.com/downloads/cesiumjs/releases/1.123/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> |
|||
<script src="https://cesium.com/downloads/cesiumjs/releases/1.123/Build/Cesium/Cesium.js"></script> |
|||
|
|||
<style> |
|||
/* 确保 Cesium 容器占满整个浏览器窗口 */ |
|||
body { margin: 0; overflow: hidden; } |
|||
#cesiumContainer { width: 100%; height: 100vh; } |
|||
|
|||
/* 悬浮按钮样式 */ |
|||
.control-btn { |
|||
position: absolute; |
|||
z-index: 1000; |
|||
padding: 10px 15px; |
|||
background-color: rgba(0, 0, 0, 0.7); |
|||
color: white; |
|||
border: none; |
|||
border-radius: 5px; |
|||
cursor: pointer; |
|||
font-size: 14px; |
|||
box-shadow: 0 2px 5px rgba(0,0,0,0.3); |
|||
transition: background-color 0.3s; |
|||
} |
|||
|
|||
.control-btn:hover { |
|||
background-color: rgba(0, 0, 0, 0.9); |
|||
} |
|||
|
|||
#cameraInfoBtn { |
|||
top: 20px; |
|||
right: 20px; |
|||
} |
|||
|
|||
/* 视角切换按钮 */ |
|||
.view-buttons { |
|||
position: absolute; |
|||
bottom: 20px; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
display: flex; |
|||
gap: 10px; |
|||
z-index: 1000; |
|||
} |
|||
|
|||
.view-btn { |
|||
padding: 8px 16px; |
|||
background-color: rgba(0, 0, 0, 0.7); |
|||
color: white; |
|||
border: none; |
|||
border-radius: 5px; |
|||
cursor: pointer; |
|||
font-size: 14px; |
|||
box-shadow: 0 2px 5px rgba(0,0,0,0.3); |
|||
transition: background-color 0.3s; |
|||
} |
|||
|
|||
.view-btn:hover { |
|||
background-color: rgba(0, 0, 0, 0.9); |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div id="cesiumContainer"></div> |
|||
<button id="cameraInfoBtn" class="control-btn">获取当前视角参数</button> |
|||
|
|||
<!-- 视角切换按钮 --> |
|||
<div class="view-buttons"> |
|||
<button class="view-btn" data-view="overview">全局视角</button> |
|||
<button class="view-btn" data-view="ship1">船1视角</button> |
|||
<button class="view-btn" data-view="ship2">船2视角</button> |
|||
<button class="view-btn" data-view="ship3">船3视角</button> |
|||
<button class="view-btn" data-view="ship4">船4视角</button> |
|||
</div> |
|||
|
|||
<script type="module"> |
|||
// ---------------------------------------------------------------------- |
|||
// ** 请替换为您申请的天地图密钥 ** |
|||
// ---------------------------------------------------------------------- |
|||
const TDT_KEY = 'b19d3ad72716d1a28cf77836dfa19a71'; |
|||
|
|||
// 步骤 1: 初始化 Viewer 并配置为使用天地图影像和平坦地形 |
|||
const viewer = new Cesium.Viewer('cesiumContainer', { |
|||
// **核心设置:禁用所有默认影像,我们将手动添加天地图** |
|||
imageryProvider: false, |
|||
|
|||
// **使用平坦地形,避免依赖 Cesium Ion** |
|||
terrainProvider: new Cesium.EllipsoidTerrainProvider(), |
|||
|
|||
// 禁用所有不必要的UI控件 |
|||
timeline: false, |
|||
animation: false, |
|||
baseLayerPicker: false, |
|||
geocoder: false, |
|||
sceneModePicker: false, |
|||
navigationHelpButton: false, |
|||
infoBox: false, |
|||
fullscreenButton: false, |
|||
homeButton: false, |
|||
|
|||
// 启用抗锯齿和其他渲染优化 |
|||
contextOptions: { |
|||
webgl: { |
|||
antialias: true |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// 禁用所有鼠标和键盘的相机控制操作 |
|||
viewer.scene.screenSpaceCameraController.enableRotate = false; |
|||
viewer.scene.screenSpaceCameraController.enableTranslate = false; |
|||
viewer.scene.screenSpaceCameraController.enableZoom = false; |
|||
viewer.scene.screenSpaceCameraController.enableTilt = false; |
|||
viewer.scene.screenSpaceCameraController.enableLook = false; |
|||
|
|||
// 渲染优化设置 |
|||
// 启用抗锯齿 |
|||
viewer.scene.postProcessStages.fxaa.enabled = true; |
|||
|
|||
// 启用深度测试,确保模型能够被地形正确遮挡 |
|||
viewer.scene.globe.depthTestAgainstTerrain = true; |
|||
|
|||
// 步骤 2: 添加天地图卫星影像底图 (img: 卫星图层, w: WGS84坐标系) |
|||
const tdtImage = new Cesium.WebMapTileServiceImageryProvider({ |
|||
// URL 中 LAYER=img 表示卫星影像图层 |
|||
url: "http://t{s}.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=" + TDT_KEY, |
|||
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], |
|||
layer: 'tdtImgLayer', |
|||
style: 'default', |
|||
format: 'tiles', |
|||
tileMatrixSetID: 'w', // 确保使用 WGS84 坐标系 |
|||
maximumLevel: 18 |
|||
}); |
|||
|
|||
// 步骤 3: 将图层添加到 Viewer 中 |
|||
viewer.imageryLayers.addImageryProvider(tdtImage); |
|||
|
|||
// **注意:由于您要求不添加标注层,因此这里不添加 tdtCVA 或 tdtCIA** |
|||
|
|||
// 引入我们自定义的标记和定位功能 |
|||
import { initMarkerAndPosition } from './src/cesium-utils.js'; |
|||
|
|||
// 初始化标记和定位 |
|||
initMarkerAndPosition(viewer); |
|||
|
|||
// 定义预设视角参数 |
|||
const presetViews = { |
|||
overview: { |
|||
destination: Cesium.Cartesian3.fromDegrees(118.4603328835826, 38.953967794772765, 560.0105923892418), |
|||
orientation: { |
|||
heading: Cesium.Math.toRadians(231.0260194269599), |
|||
pitch: Cesium.Math.toRadians(-24.749471814600415), |
|||
roll: Cesium.Math.toRadians(0.005508519138937096) |
|||
} |
|||
}, |
|||
ship1: { |
|||
destination: Cesium.Cartesian3.fromDegrees(118.45467237056748 + 0.005, 38.94345692673452, 150), // 在船的前方 |
|||
orientation: { |
|||
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32) |
|||
pitch: Cesium.Math.toRadians(-15), |
|||
roll: 0 |
|||
} |
|||
}, |
|||
ship2: { |
|||
destination: Cesium.Cartesian3.fromDegrees(118.45183602217774 + 0.005, 38.94485094840323, 150), // 在船的前方 |
|||
orientation: { |
|||
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32) |
|||
pitch: Cesium.Math.toRadians(-15), |
|||
roll: 0 |
|||
} |
|||
}, |
|||
ship3: { |
|||
destination: Cesium.Cartesian3.fromDegrees(118.4468305964142 + 0.005, 38.947237470602076, 150), // 在船的前方 |
|||
orientation: { |
|||
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32) |
|||
pitch: Cesium.Math.toRadians(-15), |
|||
roll: 0 |
|||
} |
|||
}, |
|||
ship4: { |
|||
destination: Cesium.Cartesian3.fromDegrees(118.44446808752532 + 0.005, 38.94835610136433, 150), // 在船的前方 |
|||
orientation: { |
|||
heading: Cesium.Math.toRadians(212), // 朝向船的位置 (180+32) |
|||
pitch: Cesium.Math.toRadians(-15), |
|||
roll: 0 |
|||
} |
|||
} |
|||
}; |
|||
|
|||
// 设置初始视角 |
|||
viewer.camera.setView(presetViews.overview); |
|||
|
|||
// 添加按钮点击事件监听器 |
|||
document.getElementById('cameraInfoBtn').addEventListener('click', function() { |
|||
// 获取当前相机位置 |
|||
const camera = viewer.camera; |
|||
const position = camera.position; |
|||
const cartographic = Cesium.Cartographic.fromCartesian(position); |
|||
|
|||
// 转换为度数 |
|||
const longitude = Cesium.Math.toDegrees(cartographic.longitude); |
|||
const latitude = Cesium.Math.toDegrees(cartographic.latitude); |
|||
const height = cartographic.height; |
|||
|
|||
// 获取相机方向 |
|||
const heading = Cesium.Math.toDegrees(camera.heading); |
|||
const pitch = Cesium.Math.toDegrees(camera.pitch); |
|||
const roll = Cesium.Math.toDegrees(camera.roll); |
|||
|
|||
// 输出到控制台 |
|||
console.log('当前相机视角参数:'); |
|||
console.log('位置 - 经度:', longitude, '纬度:', latitude, '高度:', height); |
|||
console.log('方向 - 航向角:', heading, '俯仰角:', pitch, '翻滚角:', roll); |
|||
|
|||
// 显示提示信息 |
|||
alert('相机参数已输出到控制台,请打开开发者工具查看。'); |
|||
}); |
|||
|
|||
// 添加视角切换按钮事件监听器 |
|||
document.querySelectorAll('.view-btn').forEach(button => { |
|||
button.addEventListener('click', function() { |
|||
const viewName = this.getAttribute('data-view'); |
|||
const viewParams = presetViews[viewName]; |
|||
|
|||
if (viewParams) { |
|||
viewer.camera.flyTo({ |
|||
destination: viewParams.destination, |
|||
orientation: viewParams.orientation, |
|||
duration: 2.0 // 动画持续时间(秒) |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -1,529 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<div ref="cesiumContainerRef" class="cesium-container"></div> |
|||
<div class="btn-group"> |
|||
<!-- <el-button class="get-view-btn" size="small" type="primary" @click="getCurrentViewInfo" |
|||
style="margin-right: 10px;">获取当前视角</el-button> --> |
|||
<!-- <el-button class="view-btn" size="small" type="success" @click="switchView('overview')" |
|||
style="margin-right: 10px;">概览视角</el-button> --> |
|||
<!-- <el-button class="view-btn" size="small" type="warning" @click="switchView('view1')" |
|||
style="margin-right: 10px;">视角1</el-button> |
|||
<el-button class="view-btn" size="small" type="warning" @click="switchView('view2')" |
|||
style="margin-right: 10px;">视角2</el-button> --> |
|||
</div> |
|||
</div> |
|||
|
|||
</template> |
|||
|
|||
<script setup> |
|||
import { onMounted, onBeforeUnmount, ref } from 'vue'; |
|||
import coordtransform from 'coordtransform'; |
|||
import { MapApi } from "@/api/shorepower/map"; |
|||
import { toRaw } from 'vue' |
|||
// ⚠️ 注意:通过 window 访问全局 Cesium 对象 |
|||
const Cesium = window.Cesium; |
|||
|
|||
let viewer = null; |
|||
const cesiumContainerRef = ref(null); // 引用 DOM 元素 |
|||
const history = ref(null); // 历史数据 |
|||
const dataWithModels = ref([]); // 存储带有模型实例的数据列表 |
|||
|
|||
// 定义预设视角参数 |
|||
const presetViews = { |
|||
overview: { |
|||
"longitude": 118.5132903711312, |
|||
"latitude": 38.84648563549342, |
|||
"height": 12675.768680075478, |
|||
"heading": 353.6789759103708, |
|||
"pitch": -36.657258995301596, |
|||
"roll": 0.0343469697692715 |
|||
} |
|||
}; |
|||
|
|||
const createView = (obj) => { |
|||
return { |
|||
destination: Cesium.Cartesian3.fromDegrees(obj.longitude, obj.latitude, obj.height), |
|||
orientation: { |
|||
heading: Cesium.Math.toRadians(obj.heading), |
|||
pitch: Cesium.Math.toRadians(obj.pitch), |
|||
roll: Cesium.Math.toRadians(obj.roll) |
|||
} |
|||
} |
|||
} |
|||
|
|||
onMounted(async () => { |
|||
if (!Cesium) { |
|||
console.error("Cesium 对象未找到,请检查 index.html 文件!"); |
|||
return; |
|||
} |
|||
|
|||
// 步骤 1: 初始化 Viewer 并配置为使用高德地图影像和平坦地形 |
|||
try { |
|||
viewer = new Cesium.Viewer(cesiumContainerRef.value, { |
|||
// **核心设置:禁用所有默认影像,我们将手动添加高德地图** |
|||
imageryProvider: false, |
|||
|
|||
// **使用平坦地形,避免依赖 Cesium Ion** |
|||
terrainProvider: new Cesium.EllipsoidTerrainProvider(), |
|||
// sceneMode: Cesium.SceneMode.COLUMBUS_VIEW, |
|||
// 禁用所有不必要的UI控件 |
|||
timeline: false, |
|||
animation: false, |
|||
baseLayerPicker: false, |
|||
geocoder: false, |
|||
sceneModePicker: false, |
|||
navigationHelpButton: false, |
|||
infoBox: false, |
|||
fullscreenButton: false, |
|||
homeButton: false, |
|||
|
|||
// 启用抗锯齿和其他渲染优化 |
|||
contextOptions: { |
|||
webgl: { |
|||
antialias: true |
|||
} |
|||
} |
|||
}); |
|||
|
|||
|
|||
|
|||
// 禁用所有鼠标和键盘的相机控制操作 |
|||
/* viewer.scene.screenSpaceCameraController.enableRotate = false; |
|||
viewer.scene.screenSpaceCameraController.enableTranslate = false; |
|||
viewer.scene.screenSpaceCameraController.enableZoom = false; |
|||
viewer.scene.screenSpaceCameraController.enableTilt = false; |
|||
viewer.scene.screenSpaceCameraController.enableLook = false; */ |
|||
|
|||
// 渲染优化设置 |
|||
// 启用抗锯齿 |
|||
viewer.scene.postProcessStages.fxaa.enabled = true; |
|||
|
|||
// 启用深度测试,确保模型能够被地形正确遮挡 |
|||
viewer.scene.globe.depthTestAgainstTerrain = true; |
|||
|
|||
// 启用时钟动画,确保模型能够根据时间更新 |
|||
viewer.clock.shouldAnimate = true; |
|||
|
|||
viewer.scene.screenSpaceCameraController.minimumZoomDistance = 200; |
|||
// 步骤 2: 添加高德地图卫星影像底图 |
|||
const gaodeImage = new Cesium.UrlTemplateImageryProvider({ |
|||
url: "https://webst0{s}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}", |
|||
subdomains: ['1', '2', '3', '4'], |
|||
layer: 'gaodeImgLayer', |
|||
style: 'default', |
|||
format: 'image/png', |
|||
maximumLevel: 16 |
|||
}); |
|||
const dataInfo = history.value ? history.value : await MapApi.getAllData() |
|||
const shipData = await MapApi.getShipInfo({ |
|||
harborDistrictId: 1 |
|||
}) |
|||
console.log('shipData', shipData) |
|||
|
|||
// const unitedData = |
|||
|
|||
// 通用添加函数,遍历dataInfo并创建标记 |
|||
const addMarkersFromDataInfo = (dataInfo) => { |
|||
// 更严格的数组检查 |
|||
if (!dataInfo || !Array.isArray(dataInfo) || dataInfo.length === 0) { |
|||
console.log('No valid dataInfo array provided'); |
|||
return []; |
|||
} |
|||
|
|||
// 创建新数组存储带有模型实例的数据 |
|||
const dataWithModelsArray = []; |
|||
|
|||
console.log(`Processing ${dataInfo.length} items`); |
|||
|
|||
dataInfo.forEach((item, index) => { |
|||
// 检查基本数据结构 |
|||
if (!item) { |
|||
console.warn(`Item at index ${index} is null or undefined`); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
// 创建包含模型实例的数据项副本 |
|||
let itemWithModel = { ...item }; |
|||
|
|||
// 解析data字段为JSON对象 |
|||
let dataObj; |
|||
if (typeof item.data === 'string') { |
|||
try { |
|||
dataObj = JSON.parse(item.data); |
|||
} catch (parseError) { |
|||
console.warn(`Failed to parse data for item ${item.id || index}:`, parseError); |
|||
dataWithModelsArray.push(itemWithModel); // 添加未解析成功的数据项 |
|||
return; |
|||
} |
|||
} else { |
|||
dataObj = item.data; |
|||
} |
|||
|
|||
// 检查dataObj是否有效 |
|||
if (!dataObj) { |
|||
console.warn(`No data object found for item ${item.id || index}`); |
|||
dataWithModelsArray.push(itemWithModel); // 添加无数据对象的数据项 |
|||
return; |
|||
} |
|||
|
|||
// 获取坐标信息 - 更严格的验证 |
|||
let longitude, latitude; |
|||
let wgsLon, wgsLat; |
|||
if (dataObj.xy && Array.isArray(dataObj.xy) && dataObj.xy.length >= 2) { |
|||
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]); |
|||
wgsLon = wgsCoords[0]; |
|||
wgsLat = wgsCoords[1]; |
|||
// 确保坐标是有效数字 |
|||
latitude = wgsLat; |
|||
longitude = wgsLon; |
|||
// 检查坐标是否为有效数字 |
|||
if (isNaN(latitude) || isNaN(longitude)) { |
|||
console.warn(`Invalid coordinates for item ${item.id || index}:`, dataObj.xy); |
|||
dataWithModelsArray.push(itemWithModel); // 添加坐标无效的数据项 |
|||
return; |
|||
} |
|||
|
|||
// 检查坐标范围(简单验证) |
|||
if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) { |
|||
console.warn(`Coordinates out of range for item ${item.id || index}:`, dataObj.xy[1], dataObj.xy[0]); |
|||
dataWithModelsArray.push(itemWithModel); // 添加坐标超出范围的数据项 |
|||
return; |
|||
} |
|||
} else { |
|||
console.warn('无效的坐标信息:', item); |
|||
dataWithModelsArray.push(itemWithModel); // 添加无有效坐标的数据项 |
|||
return; |
|||
} |
|||
|
|||
if (dataObj.icon === 'ship_green') { |
|||
const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5) |
|||
const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 15); |
|||
const statusPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1); |
|||
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35); |
|||
// 投放船模型 |
|||
const shipModel = viewer.entities.add({ |
|||
name: 'Cargo Ship Model', |
|||
position: position, |
|||
orientation: Cesium.Transforms.headingPitchRollQuaternion( |
|||
position, |
|||
new Cesium.HeadingPitchRoll( |
|||
Cesium.Math.toRadians(dataObj.rotationAngle), // 航向角 (绕Z轴旋转) |
|||
Cesium.Math.toRadians(0), // 俯仰角 (绕Y轴旋转) |
|||
Cesium.Math.toRadians(0) // 翻滚角 (绕X轴旋转) |
|||
) |
|||
), |
|||
model: { |
|||
uri: '/model/cargo_ship_07.glb', |
|||
scale: 1.5, // 调整模型大小 |
|||
// minimumPixelSize: 100, // 确保模型至少显示为50像素大小 |
|||
// 启用深度测试,使模型能够被地形遮挡 |
|||
enableDepthTest: true, |
|||
// 启用背光剔除,提高渲染性能 |
|||
backFaceCulling: true |
|||
} |
|||
}); |
|||
|
|||
// 保存模型实例到数据项 |
|||
itemWithModel.modelInstance = shipModel; |
|||
itemWithModel.modelType = 'ship'; |
|||
itemWithModel = { ...itemWithModel, ...itemShipInfo }; |
|||
|
|||
viewer.entities.add({ |
|||
position: labelPosition, // 船上方约10米 |
|||
label: { |
|||
text: itemShipInfo.shipBasicInfo.name || `Marker-${item.id || index}`, |
|||
font: '12px sans-serif', |
|||
fillColor: Cesium.Color.WHITE, |
|||
outlineColor: Cesium.Color.BLACK, |
|||
outlineWidth: 2, |
|||
style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
|||
pixelOffset: new Cesium.Cartesian2(0, -30), // 偏移量,避免与模型重叠 |
|||
disableDepthTestDistance: Number.POSITIVE_INFINITY // 始终可见 |
|||
} |
|||
}); |
|||
const overlayBillboard = viewer.entities.add({ |
|||
position: statusPosition, |
|||
billboard: { |
|||
image: '/img/故障.png', |
|||
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|||
verticalOrigin: Cesium.VerticalOrigin.CENTER, |
|||
disableDepthTestDistance: Number.POSITIVE_INFINITY, |
|||
scale: new Cesium.CallbackProperty(function (time, result) { |
|||
// t 会随着时间不断变化 |
|||
const t = Cesium.JulianDate.toDate(time).getTime() / 1000; |
|||
const pulse = 0.6 + Math.sin(t * 4) * 0.2; // (基准0.6, 幅度±0.2) |
|||
return pulse; |
|||
}, false) |
|||
} |
|||
}); |
|||
} |
|||
if (dataObj.type === 'text') { |
|||
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35); |
|||
viewer.entities.add({ |
|||
position: labelPosition, // 船上方约10米 |
|||
label: { |
|||
text: dataObj.name || `Marker-${item.id || index}`, |
|||
font: '20px sans-serif', |
|||
fillColor: Cesium.Color.WHITE, |
|||
outlineColor: Cesium.Color.BLACK, |
|||
outlineWidth: 2, |
|||
style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
|||
pixelOffset: new Cesium.Cartesian2(0, -30), // 偏移量,避免与模型重叠 |
|||
disableDepthTestDistance: Number.POSITIVE_INFINITY // 始终可见 |
|||
} |
|||
}); |
|||
} |
|||
if (dataObj.type === 'icon' && (dataObj.icon === 'interface_blue' || dataObj.icon === 'interface_red')) { |
|||
const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1); |
|||
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 5); |
|||
// 投放岸电箱模型 |
|||
const electricalBoxModel = viewer.entities.add({ |
|||
name: 'Electrical Box Model', |
|||
position: position, |
|||
orientation: Cesium.Transforms.headingPitchRollQuaternion( |
|||
position, |
|||
new Cesium.HeadingPitchRoll( |
|||
Cesium.Math.toRadians(dataObj.rotationAngle), // 航向角 (绕Z轴旋转) |
|||
Cesium.Math.toRadians(0), // 俯仰角 (绕Y轴旋转) |
|||
Cesium.Math.toRadians(0) // 翻滚角 (绕X轴旋转) |
|||
) |
|||
), |
|||
model: { |
|||
uri: '/model/electrical_box.glb', |
|||
scale: 1, // 调整模型大小 |
|||
// minimumPixelSize: 10, // 确保模型至少显示为50像素大小 |
|||
// 启用深度测试,使模型能够被地形遮挡 |
|||
enableDepthTest: true, |
|||
// 启用背光剔除,提高渲染性能 |
|||
backFaceCulling: true |
|||
} |
|||
}); |
|||
|
|||
// 保存模型实例到数据项 |
|||
itemWithModel.modelInstance = electricalBoxModel; |
|||
itemWithModel.modelType = 'electrical_box'; |
|||
|
|||
viewer.entities.add({ |
|||
position: labelPosition, // 船上方约10米 |
|||
label: { |
|||
text: '岸电箱' || `Marker-${item.id || index}`, |
|||
font: '10px sans-serif', |
|||
fillColor: Cesium.Color.WHITE, |
|||
outlineColor: Cesium.Color.BLACK, |
|||
outlineWidth: 2, |
|||
style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
|||
pixelOffset: new Cesium.Cartesian2(0, -30), // 偏移量,避免与模型重叠 |
|||
disableDepthTestDistance: Number.POSITIVE_INFINITY // 始终可见 |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// 创建红点标记 |
|||
/* const entity = viewer.entities.add({ |
|||
name: item.name || `Marker-${item.id || index}`, |
|||
position: Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1), |
|||
point: { |
|||
pixelSize: 10, |
|||
color: Cesium.Color.RED, |
|||
outlineColor: Cesium.Color.WHITE, |
|||
outlineWidth: 2 |
|||
}, |
|||
// 可以添加标签显示名称 |
|||
label: { |
|||
text: item.name || `Marker-${item.id || index}`, |
|||
font: '14px sans-serif', |
|||
fillColor: Cesium.Color.WHITE, |
|||
outlineColor: Cesium.Color.BLACK, |
|||
outlineWidth: 2, |
|||
style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
|||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
|||
pixelOffset: new Cesium.Cartesian2(0, -15) |
|||
} |
|||
}); |
|||
// 保存点标记实例到数据项 |
|||
itemWithModel.modelInstance = entity; |
|||
itemWithModel.modelType = 'point'; |
|||
*/ |
|||
|
|||
// 将处理后的数据项添加到新数组 |
|||
dataWithModelsArray.push(itemWithModel); |
|||
} catch (error) { |
|||
console.error('Error processing item:', item, error); |
|||
// 创建一个基本的数据项副本并添加到数组 |
|||
dataWithModelsArray.push({ ...item }); |
|||
} |
|||
}); |
|||
|
|||
return dataWithModelsArray; |
|||
}; |
|||
|
|||
// 步骤 3: 将图层添加到 Viewer 中 |
|||
viewer.imageryLayers.addImageryProvider(gaodeImage); |
|||
|
|||
// 调用通用函数创建标记并获取带有模型实例的数据 |
|||
dataWithModels.value = addMarkersFromDataInfo(dataInfo); |
|||
|
|||
// 设置初始视角 |
|||
viewer.camera.flyTo(createView(presetViews.overview)); |
|||
// 设置显示高度阈值 |
|||
const SHOW_HEIGHT = 30000; // 高度 > 5000m 才显示模型等信息 |
|||
// const targets = []; // 把需要控制的 entities push 进来 |
|||
const targets = dataWithModels.value |
|||
.filter(item => item.modelInstance) |
|||
.map(item => toRaw(item.modelInstance)); |
|||
console.log('targets', targets) |
|||
viewer.camera.changed.addEventListener(() => { |
|||
// 这个函数会在相机的任何属性发生变化后立即被调用。 |
|||
const height = viewer.camera.positionCartographic.height; |
|||
// console.log("相机高度(即缩放级别)已变化:", height.toFixed(2), "米"); |
|||
|
|||
// 控制模型显示/隐藏 |
|||
const visible = height <= SHOW_HEIGHT; // 1000m内才显示模型等信息 |
|||
targets.forEach(entity => { |
|||
if (entity.show !== visible) { |
|||
entity.show = visible; |
|||
} |
|||
}); |
|||
|
|||
// 其他缩放级别逻辑 |
|||
if (height < 100000) { |
|||
// 放大到街景级别 |
|||
console.log("当前处于近距离缩放级别。"); |
|||
} else if (height > 5000000) { |
|||
// 缩小到全球级别 |
|||
console.log("当前处于远距离缩放级别。"); |
|||
} |
|||
}); |
|||
|
|||
// 添加地图点击事件 |
|||
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); |
|||
handler.setInputAction(function (movement) { |
|||
console.log('地图点击事件触发'); |
|||
|
|||
// 尝试多种方式获取点击位置 |
|||
const ray = viewer.camera.getPickRay(movement.position); |
|||
|
|||
// 方法1: 从地球表面拾取 |
|||
let cartesian = viewer.scene.globe.pick(ray, viewer.scene); |
|||
|
|||
// 如果从地球表面拾取失败,尝试从场景中拾取 |
|||
if (!cartesian) { |
|||
cartesian = viewer.scene.pickPosition(movement.position); |
|||
console.log('使用场景拾取方法'); |
|||
} |
|||
|
|||
// 如果仍然失败,尝试使用相机计算位置 |
|||
if (!cartesian) { |
|||
// 获取屏幕中心点作为替代方案(仅用于调试) |
|||
console.log('无法从点击位置获取精确坐标'); |
|||
return; |
|||
} |
|||
|
|||
if (cartesian) { |
|||
const cartographic = Cesium.Cartographic.fromCartesian(cartesian); |
|||
if (cartographic) { |
|||
const longitude = Cesium.Math.toDegrees(cartographic.longitude); |
|||
const latitude = Cesium.Math.toDegrees(cartographic.latitude); |
|||
const gcj02 = coordtransform.gcj02towgs84(longitude, latitude); |
|||
console.log('点击位置经纬度:', { |
|||
longitude: gcj02[0], |
|||
latitude: gcj02[1] |
|||
}); |
|||
} else { |
|||
console.log('无法从笛卡尔坐标转换为大地坐标'); |
|||
} |
|||
} else { |
|||
console.log('未能获取到地球表面的点击位置'); |
|||
} |
|||
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|||
|
|||
// 确保启用场景的鼠标事件 |
|||
viewer.scene.screenSpaceCameraController.enableInputs = true; |
|||
|
|||
// 启用拾取透明物体 |
|||
viewer.scene.useDepthPicking = true; |
|||
|
|||
console.log('Cesium 高德地图 Viewer 初始化成功'); |
|||
} catch (error) { |
|||
console.error('Cesium Viewer 初始化失败:', error); |
|||
} |
|||
}); |
|||
|
|||
// 视角切换方法 |
|||
const switchView = (viewName) => { |
|||
const viewParams = presetViews[viewName]; |
|||
if (viewParams && viewer) { |
|||
viewer.camera.flyTo({ |
|||
...createView(viewParams), |
|||
duration: 2.0 // 动画持续时间(秒) |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
// 视角切换方法 |
|||
const switchModelView = (view) => { |
|||
const modelInstance = toRaw(view); |
|||
console.log(modelInstance) |
|||
if (viewer && view) { |
|||
viewer.flyTo(modelInstance); |
|||
} |
|||
}; |
|||
|
|||
// 获取当前视角信息的方法 |
|||
const getCurrentViewInfo = () => { |
|||
if (!viewer) { |
|||
console.warn('Viewer 尚未初始化'); |
|||
return null; |
|||
} |
|||
const camera = viewer.camera; |
|||
|
|||
const position = camera.position; // 世界笛卡尔坐标系 x,y,z |
|||
const cartographic = Cesium.Cartographic.fromCartesian(position); |
|||
console.log( |
|||
{ |
|||
longitude: Cesium.Math.toDegrees(cartographic.longitude), |
|||
latitude: Cesium.Math.toDegrees(cartographic.latitude), |
|||
height: cartographic.height, |
|||
heading: Cesium.Math.toDegrees(camera.heading), |
|||
pitch: Cesium.Math.toDegrees(camera.pitch), |
|||
roll: Cesium.Math.toDegrees(camera.roll) |
|||
} |
|||
) |
|||
|
|||
}; |
|||
|
|||
// 暴露方法和数据给父组件使用 |
|||
defineExpose({ |
|||
switchView, |
|||
dataWithModels, |
|||
switchModelView |
|||
}); |
|||
|
|||
onBeforeUnmount(() => { |
|||
// 销毁 Viewer,释放 WebGL 资源 |
|||
if (viewer) { |
|||
viewer.destroy(); |
|||
viewer = null; |
|||
console.log('Cesium Viewer 已销毁'); |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.cesium-container { |
|||
width: 100%; |
|||
height: 100vh; |
|||
margin: 0; |
|||
padding: 0; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.btn-group { |
|||
position: absolute; |
|||
top: 10px; |
|||
z-index: 1000; |
|||
display: flex; |
|||
gap: 12rpx; |
|||
} |
|||
</style> |
|||
@ -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<string, { text: string; colorType: string }> = { |
|||
'-1': { text: '未知错误', colorType: 'default' }, |
|||
'1': { text: '岸电用电接口不匹配', colorType: 'default' }, |
|||
'2': { text: '岸电设施电压/频率不匹配', colorType: 'success' }, |
|||
'3': { text: '电缆长度不匹配', colorType: 'success' }, |
|||
'4': { text: '气象因素禁止作业', colorType: 'success' }, |
|||
'5': { text: '船电设施损坏', colorType: 'warning' }, |
|||
'6': { text: '岸电设施维护中', colorType: 'warning' }, |
|||
'7': { text: '无受电设备', colorType: 'danger' }, |
|||
'8': { text: '拒绝使用岸电', colorType: 'danger' }, |
|||
'9': { text: '其他', colorType: 'danger' } |
|||
} |
|||
|
|||
const reasonKey = applyInfo.reason?.toString() || '-1' |
|||
const statusInfo = statusMap[reasonKey] || { text: applyInfo.reason, colorType: 'default' } |
|||
|
|||
// 根据颜色类型设置对应的状态类
|
|||
let statusClass = 'status-cable' |
|||
switch (statusInfo.colorType) { |
|||
case 'danger': |
|||
statusClass = 'status-danger' |
|||
break |
|||
case 'warning': |
|||
statusClass = 'status-warning' |
|||
break |
|||
case 'success': |
|||
statusClass = 'status-success' |
|||
break |
|||
default: |
|||
statusClass = 'status-cable' |
|||
} |
|||
|
|||
return { |
|||
statusClass: statusClass, |
|||
status: statusInfo.text |
|||
} |
|||
} else { |
|||
return { |
|||
statusClass: '', |
|||
status: 'unknown' |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 根据 id 查找对象数组中的指定字段值,并保留两位小数 |
|||
* @param list - 对象数组,每个对象必须包含 id 字段 |
|||
* @param id - 要查找的 id 值 |
|||
* @param valueField - 要返回的字段名(必须是对象中除 id 外的 key) |
|||
* @returns 字段值,未找到则返回 undefined |
|||
*/ |
|||
export function getValueById<T extends { id: number | string }, K extends keyof Omit<T, 'id'>>( |
|||
list: T[], |
|||
id: T['id'], |
|||
valueField: K |
|||
): T[K] extends number | string ? string | undefined : T[K] | undefined { |
|||
const item = list.find(item => item.id === id); |
|||
|
|||
if (!item) return undefined; |
|||
|
|||
const value = item[valueField]; |
|||
|
|||
// 如果值是数字或可以转换为数字的字符串,则保留两位小数
|
|||
if (typeof value === 'number') { |
|||
return Number(value).toFixed(2) as any; |
|||
} |
|||
|
|||
if (typeof value === 'string' && !isNaN(Number(value))) { |
|||
return Number(value).toFixed(2) as any; |
|||
} |
|||
|
|||
// 否则返回原始值
|
|||
return value as any; |
|||
} |
|||
|
|||
export function parseRangeToTimestamp(range: string[], granularity: 'year' | 'month' | 'week' | 'day'): [number, number] | null { |
|||
if (!range || range.length !== 2) return null; |
|||
|
|||
const [startStr, endStr] = range; |
|||
|
|||
let startDate: Date; |
|||
let endDate: Date; |
|||
|
|||
switch (granularity) { |
|||
case 'year': |
|||
// '2023' → 2023-01-01 00:00:00 ~ 2023-12-31 23:59:59
|
|||
startDate = new Date(`${startStr}-01-01T00:00:00`); |
|||
endDate = new Date(`${endStr}-12-31T23:59:59`); |
|||
break; |
|||
|
|||
case 'month': |
|||
// '2023-05' → 2023-05-01 00:00:00 ~ 2023-05-31 23:59:59
|
|||
startDate = new Date(`${startStr}-01T00:00:00`); |
|||
// 获取该月最后一天
|
|||
const yearMonth = startStr.split('-'); |
|||
const nextMonth = new Date(+yearMonth[0], +yearMonth[1], 0); // 本月最后一天
|
|||
endDate = new Date(nextMonth.getFullYear(), nextMonth.getMonth(), nextMonth.getDate(), 23, 59, 59); |
|||
// 同样处理 endStr
|
|||
const endYearMonth = endStr.split('-'); |
|||
const endNextMonth = new Date(+endYearMonth[0], +endYearMonth[1], 0); |
|||
endDate = new Date(endNextMonth.getFullYear(), endNextMonth.getMonth(), endNextMonth.getDate(), 23, 59, 59); |
|||
break; |
|||
|
|||
case 'week': |
|||
case 'day': |
|||
default: |
|||
// '2023-05-01' → 2023-05-01 00:00:00 ~ 2023-05-01 23:59:59(week 实际也是日期)
|
|||
startDate = new Date(`${startStr}T00:00:00`); |
|||
endDate = new Date(`${endStr}T23:59:59`); |
|||
break; |
|||
} |
|||
|
|||
const start = startDate.getTime(); |
|||
const end = endDate.getTime(); |
|||
|
|||
if (isNaN(start) || isNaN(end)) { |
|||
console.error('日期解析失败:', range, granularity); |
|||
return null; |
|||
} |
|||
|
|||
return [start, end]; |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,479 @@ |
|||
interface ShorePowerBerth { |
|||
id: number |
|||
berthId: number |
|||
equipmentId: number |
|||
name: string |
|||
totalPowerDeviceId: number |
|||
frequencyDeviceId: number |
|||
voltageDeviceId: number |
|||
currentDeviceId: number |
|||
status: number |
|||
createTime: number // Unix timestamp in milliseconds
|
|||
shorePowerEquipmentInfo: ShorePowerEquipmentInfo |
|||
storePowerStatus?: string |
|||
} |
|||
|
|||
interface ShorePowerEquipmentInfo { |
|||
id: number |
|||
dockId: number |
|||
name: string |
|||
capacity: string // 可考虑转为 number,但原始数据为字符串 "2000"
|
|||
voltage: string // 如 "0.4/0.44"
|
|||
frequency: string // 如 "50/60"
|
|||
createTime: number // Unix timestamp in milliseconds
|
|||
} |
|||
|
|||
interface shipHistoryPageRequest { |
|||
/** |
|||
* 查询编号 |
|||
*/ |
|||
ids?: number[] |
|||
/** |
|||
* 页码,从 1 开始 |
|||
*/ |
|||
pageNo: number |
|||
/** |
|||
* 每页条数,最大值为 100 |
|||
*/ |
|||
pageSize: number |
|||
/** |
|||
* 船舶编号 |
|||
*/ |
|||
shipId?: number |
|||
/** |
|||
* 查询类型 |
|||
*/ |
|||
type?: number |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* CommonResultPageResultShipRespVo |
|||
*/ |
|||
interface apiResponse<T> { |
|||
code?: number |
|||
data?: T |
|||
msg?: string |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* PageResultShipRespVo,分页结果 |
|||
*/ |
|||
interface PageResultShipRespVo<T> { |
|||
/** |
|||
* 数据 |
|||
*/ |
|||
list: T[] |
|||
/** |
|||
* 总量 |
|||
*/ |
|||
total: number |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* ShipRespVo,管理后台 - 船舶岸电使用情况 Response VO |
|||
*/ |
|||
export interface ShipRespVo { |
|||
/** |
|||
* 船舶状态 |
|||
*/ |
|||
shipStatus?: string |
|||
/** |
|||
* 用电申请信息 |
|||
*/ |
|||
applyInfo: ApplyRespVO |
|||
/** |
|||
* 船舶基础信息 |
|||
*/ |
|||
shipBasicInfo: ShipBasicInfoRespVO |
|||
/** |
|||
* 用电接口信息 |
|||
*/ |
|||
shorePower: ShorePowerRespVO |
|||
/** |
|||
* 岸电接口与船舶关系 |
|||
*/ |
|||
shorePowerAndShip: ShorePowerAndShipRespVO |
|||
/** |
|||
* 岸电设施信息 |
|||
*/ |
|||
shorePowerEquipment: ShorePowerEquipmentRespVO |
|||
/** |
|||
* 船舶用电信息 |
|||
*/ |
|||
usageRecordInfo: UsageRecordRespVO |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* 用电申请信息 |
|||
* |
|||
* ApplyRespVO,管理后台 - 用电申请 Response VO |
|||
*/ |
|||
export interface ApplyRespVO { |
|||
/** |
|||
* 船方代理单位 |
|||
*/ |
|||
agentCompany: string |
|||
/** |
|||
* 代理人联系方式 |
|||
*/ |
|||
agentContact: string |
|||
/** |
|||
* 是否申请使用岸电 |
|||
*/ |
|||
applyShorePower: boolean |
|||
/** |
|||
* 到达泊位 |
|||
*/ |
|||
arrivalBerth: number |
|||
/** |
|||
* 到达码头 |
|||
*/ |
|||
arrivalDock: number |
|||
/** |
|||
* 到达港 |
|||
*/ |
|||
arrivalHarborDistrict: number |
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
createTime: Date |
|||
/** |
|||
* 起运港 |
|||
*/ |
|||
departureHarborDistrict: string |
|||
/** |
|||
* 编号 |
|||
*/ |
|||
id: number |
|||
/** |
|||
* 货物类型(装货) |
|||
*/ |
|||
loadingCargoCategory: number |
|||
/** |
|||
* 货物名称(装货) |
|||
*/ |
|||
loadingCargoName: string |
|||
/** |
|||
* 货物吨数(装货) |
|||
*/ |
|||
loadingCargoTonnage: number |
|||
/** |
|||
* 装货/卸货 |
|||
*/ |
|||
operationType: number |
|||
/** |
|||
* 计划靠泊时间 |
|||
*/ |
|||
plannedBerthTime: Date |
|||
/** |
|||
* 计划离泊时间 |
|||
*/ |
|||
plannedDepartureTime: Date |
|||
/** |
|||
* 未使用岸电原因 |
|||
*/ |
|||
reason?: number |
|||
/** |
|||
* 备注 |
|||
*/ |
|||
remark?: string |
|||
/** |
|||
* 船舶编号 |
|||
*/ |
|||
shipId: number |
|||
/** |
|||
* 申请状态 |
|||
*/ |
|||
status: number |
|||
/** |
|||
* 内贸/外贸 |
|||
*/ |
|||
tradeType: number |
|||
/** |
|||
* 货物类型(卸货) |
|||
*/ |
|||
unloadingCargoCategory: number |
|||
/** |
|||
* 货物名称(卸货) |
|||
*/ |
|||
unloadingCargoName: string |
|||
/** |
|||
* 货物吨数(卸货) |
|||
*/ |
|||
unloadingCargoTonnage: number |
|||
/** |
|||
* 航次 |
|||
*/ |
|||
voyage: string |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* 船舶基础信息 |
|||
* |
|||
* ShipBasicInfoRespVO,管理后台 - 船舶基础信息 Response VO |
|||
*/ |
|||
export interface ShipBasicInfoRespVO { |
|||
/** |
|||
* 船舶呼号 |
|||
*/ |
|||
callSign: string |
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
createTime: Date |
|||
/** |
|||
* 满载吃水深度 |
|||
*/ |
|||
fullLoadDraft: number |
|||
/** |
|||
* 编号 |
|||
*/ |
|||
id: number |
|||
/** |
|||
* 船检登记号 |
|||
*/ |
|||
inspectionNo: string |
|||
/** |
|||
* 船舶长度 |
|||
*/ |
|||
length: number |
|||
/** |
|||
* 船名 |
|||
*/ |
|||
name: string |
|||
/** |
|||
* 英文船名 |
|||
*/ |
|||
nameEn: string |
|||
/** |
|||
* 航运单位名称 |
|||
*/ |
|||
shippingCompany: string |
|||
/** |
|||
* 岸电负责人 |
|||
*/ |
|||
shorePowerContact: string |
|||
/** |
|||
* 联系方式 |
|||
*/ |
|||
shorePowerContactPhone: string |
|||
/** |
|||
* 船舶吨位 |
|||
*/ |
|||
tonnage: number |
|||
/** |
|||
* 船舶宽度 |
|||
*/ |
|||
width: number |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* 用电接口信息 |
|||
* |
|||
* ShorePowerRespVO,管理后台 - 岸电 Response VO |
|||
*/ |
|||
export interface ShorePowerRespVO { |
|||
/** |
|||
* 泊位编号 |
|||
*/ |
|||
berthId: number |
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
createTime: Date |
|||
/** |
|||
* 岸电设备编号 |
|||
*/ |
|||
equipmentId: number |
|||
/** |
|||
* 频率设备编号 |
|||
*/ |
|||
frequencyDeviceId: number |
|||
/** |
|||
* 编号 |
|||
*/ |
|||
id: number |
|||
/** |
|||
* 名称 |
|||
*/ |
|||
name: string |
|||
/** |
|||
* 总电量设备编号 |
|||
*/ |
|||
totalPowerDeviceId: number |
|||
/** |
|||
* 电压设备编号 |
|||
*/ |
|||
voltageDeviceId: number |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* 岸电接口与船舶关系 |
|||
* |
|||
* ShorePowerAndShipRespVO,管理后台 - 岸电接口与船舶关系 Response VO |
|||
*/ |
|||
export interface ShorePowerAndShipRespVO { |
|||
/** |
|||
* 申请单编号 |
|||
*/ |
|||
applyId: number |
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
createTime: Date |
|||
/** |
|||
* 编号 |
|||
*/ |
|||
id: number |
|||
/** |
|||
* 船舶编号 |
|||
*/ |
|||
shipId: number |
|||
/** |
|||
* 岸电接口编号 |
|||
*/ |
|||
shorePowerId: number |
|||
/** |
|||
* 靠泊状态 |
|||
*/ |
|||
status: number |
|||
/** |
|||
* 靠泊类型 |
|||
*/ |
|||
type: string |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* 岸电设施信息 |
|||
* |
|||
* ShorePowerEquipmentRespVO,管理后台 - 岸电设施 Response VO |
|||
*/ |
|||
export interface ShorePowerEquipmentRespVO { |
|||
/** |
|||
* 装机容量 |
|||
*/ |
|||
capacity: string |
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
createTime: Date |
|||
/** |
|||
* 码头编号 |
|||
*/ |
|||
dockId: number |
|||
/** |
|||
* 频率 |
|||
*/ |
|||
frequency: string |
|||
/** |
|||
* 编号 |
|||
*/ |
|||
id: number |
|||
/** |
|||
* 名称 |
|||
*/ |
|||
name: string |
|||
/** |
|||
* 电压 |
|||
*/ |
|||
voltage: string |
|||
[property: string]: any |
|||
} |
|||
|
|||
/** |
|||
* 船舶用电信息 |
|||
* |
|||
* UsageRecordRespVO,管理后台 - 用电记录 Response VO |
|||
*/ |
|||
export interface UsageRecordRespVO { |
|||
/** |
|||
* 实际靠泊时间 |
|||
*/ |
|||
actualBerthTime?: Date |
|||
/** |
|||
* 实际离泊时间 |
|||
*/ |
|||
actualDepartureTime?: number | null | undefined |
|||
/** |
|||
* 用电申请编号 |
|||
*/ |
|||
applyId: number |
|||
/** |
|||
* 用电开始时供电方操作人 |
|||
*/ |
|||
beginPowerSupplyOperator?: string |
|||
/** |
|||
* 用电开始时用电方操作人 |
|||
*/ |
|||
beginPowerUsageOperator?: string |
|||
/** |
|||
* 用电开始时间 |
|||
*/ |
|||
beginTime?: number |
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
createTime: Date |
|||
/** |
|||
* 用电结束时间 |
|||
*/ |
|||
endTime?: number |
|||
/** |
|||
* 编号 |
|||
*/ |
|||
id: number |
|||
/** |
|||
* 用电结束时供电方操作人 |
|||
*/ |
|||
overPowerSupplyOperator?: string |
|||
/** |
|||
* 用电结束时用电方操作人 |
|||
*/ |
|||
overPowerUsageOperator?: string |
|||
/** |
|||
* 用电结束时间人工读数 |
|||
*/ |
|||
powerEndManualReading?: number |
|||
/** |
|||
* 用电结束时间系统读数 |
|||
*/ |
|||
powerEndSystemReading?: number |
|||
/** |
|||
* 用电开始时间人工读数 |
|||
*/ |
|||
powerStartManualReading?: number |
|||
/** |
|||
* 用电开始时间系统读数 |
|||
*/ |
|||
powerStartSystemReading?: number |
|||
/** |
|||
* 船舶编号 |
|||
*/ |
|||
shipId: number |
|||
/** |
|||
* 作业单状态 |
|||
*/ |
|||
status: number |
|||
[property: string]: any |
|||
} |
|||
|
|||
interface RealtimeDeviceData { |
|||
createTime: number // 时间戳(毫秒)
|
|||
deviceCode: string |
|||
deviceId: number |
|||
deviceName: string |
|||
deviceStatus: number |
|||
id: number |
|||
incrementCost: number |
|||
incrementValue: number |
|||
measureTime: number // 时间戳(毫秒)
|
|||
measureValue: number |
|||
} |
|||
Loading…
Reference in new issue