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

571 lines
17 KiB

<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>
<!-- 返回上个页面 -->
<el-button type="primary" @click.stop="handleGoBack()">返回</el-button>
<!-- <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="data-update-time">起始时间: {{ getStartTimeByRange(getCompanyTimeRange(company.id)) }} 更新时间: {{
realtimeDeviceDataTime }}</div>
<div class="company-header">
<div class="company-info">
<div class="company-name">{{ company.name }}</div>
<div class="company-berth-count">泊位数: {{ company.children?.length || 0 }}</div>
</div>
<div class="time-range-container">
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: getCompanyTimeRange(company.id) === option.value }]"
@click.stop="() => handleTimeRangeChange(company.id, option.value)">
{{ option.label }}
</button>
<div class="company-total">总量: {{ calculateTotal(company.children).toFixed(2) }}</div>
</div>
</div>
<div class="overview-grid">
<div v-for="(berth, index) in (company.children || [])" :key="index" class="overview-item"
@click="handleClickBerthItem(berth)">
<div class="overview-value">{{ berth.measureValue?.toFixed(2)
|| 0 }}</div>
<div class="overview-label">{{ berth.name }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="right" style="width: 800px;" :style="{ width: selectedShorePowerItem ? '800px' : '420px' }">
<div style="display: flex; gap: 12px; height: 100%;">
<div v-if="selectedShorePowerItem" 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>
<el-button class="close-btn" type="text" @click="closeShorePowerDetail">×</el-button>
<!-- <div style="ma">
<el-button type="primary" @click="handleOpenHistory">查看历史记录</el-button>
</div> -->
</div>
<div class="card-content">
<div class="ship-detail">
<div class="detail-item">
<span class="label">名称:</span>
<span class="value">{{ selectedShorePowerItem?.name }}</span>
</div>
<div class="detail-item">
<span class="label">位置:</span>
<span class="value">{{ selectedShorePowerItem?.position }}</span>
</div>
<div class="detail-item">
<span class="label">电压:</span>
<span class="value">{{ selectedShorePowerItem?.shorePowerEquipmentInfo?.voltage }}</span>
</div>
<div class="detail-item">
<span class="label">频率:</span>
<span class="value">{{ selectedShorePowerItem?.shorePowerEquipmentInfo?.frequency }} </span>
</div>
<div class="detail-item">
<span class="label">容量:</span>
<span class="value">{{ selectedShorePowerItem?.shorePowerEquipmentInfo?.capacity }}</span>
</div>
<div class="detail-item">
<span class="label">当前电压:</span>
<span class="value">{{ getValueById(realtimeDeviceData, selectedShorePowerItem.voltageDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">当前电流:</span>
<span class="value">{{ getValueById(realtimeDeviceData, selectedShorePowerItem.currentDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">当前频率:</span>
<span class="value">{{ getValueById(realtimeDeviceData, selectedShorePowerItem.frequencyDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">当前总用量:</span>
<span class="value">{{ getValueById(realtimeDeviceData, selectedShorePowerItem.totalPowerDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">当前状态:</span>
<span class="value">{{ selectedShorePowerItem.storePowerStatus }}</span>
</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="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>
</div>
<ship-history-dialog 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, ShipRespVo, ShorePowerBerth } from '@/types/shorepower';
import { getValueById } from './utils';
// 定义组件属性
interface ChartDataItem {
id: number;
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
}
})
interface Props {
realtimeDeviceData: RealtimeDeviceData[];
realtimeDeviceDataTime: string;
shorePowerList: ShorePowerBerth[];
handleGoBack: () => void;
}
const props = defineProps<Props>()
const selectedShorePowerItem = ref<ShorePowerBerth>()
const shorepowerSelectedItem = ref<ShorePowerBerth & { position: string; } | null>(null)
const storeSearchKeyword = ref('')
// 时间范围选项定义
interface TimeRangeOption {
value: 'day' | 'month' | 'year' | 'realtime';
label: string;
}
const timeRangeOptions: TimeRangeOption[] = [
{ value: 'day', label: '本日' },
{ value: 'month', label: '本月' },
{ value: 'year', label: '本年' },
{ value: 'realtime', label: '总' },
]
const closeShorePowerDetail = () => {
selectedShorePowerItem.value = undefined
}
// 处理岸电箱搜索输入
const handlStoreSearch = () => {
// 输入处理逻辑(如果需要额外处理可以在这里添加)
}
const getStatusClass = (status: string | undefined) => {
switch (status) {
case '正常':
return 'status-normal'
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-normal'
}
}
// 过滤后的岸电箱数据
const filteredShorePowerList = computed(() => {
if (!storeSearchKeyword.value) {
return props.shorePowerList
}
return props.shorePowerList.filter(shorepower =>
shorepower.name.toLowerCase().includes(storeSearchKeyword.value.toLowerCase())
)
})
// 定义事件
const emit = defineEmits<{
(e: 'switch-ship', ship: ShipRespVo): void;
(e: 'item-click', item: any): void;
// handleSelectItem(ship)
}>()
const handleSelectShorePower = async (shorepower: ShorePowerBerth & { position: string }) => {
// selectedItem.value = shorepower
shorepowerSelectedItem.value = shorepower
selectedShorePowerItem.value = shorepower
emit('item-click', {
type: 'shorepower_box',
item: shorepower
})
// const data = await MapApi.getRealtimeDataByIdList({ ids: shorepower.totalPowerDeviceId })
// console.log('voltageDeviceId', data)
}
// 为每个公司维护独立的时间范围状态
const companyTimeRanges = ref<Record<number, 'realtime' | 'day' | 'month' | 'year'>>({})
// 获取指定公司的时间范围设置,默认为'day'
const getCompanyTimeRange = (companyId: number) => {
return companyTimeRanges.value[companyId] || 'day'
}
// 根据时间粒度计算起始时间
const getStartTimeByRange = (range: 'realtime' | 'day' | 'month' | 'year') => {
const now = new Date();
switch (range) {
case 'day':
// 当天的 00:00:00
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
return formatDate(today);
case 'month':
// 当月第一天的 00:00:00
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
return formatDate(firstDayOfMonth);
case 'year':
// 当年第一天的 00:00:00
const firstDayOfYear = new Date(now.getFullYear(), 0, 1);
return formatDate(firstDayOfYear);
case 'realtime':
// 固定起始时间,可以根据实际需求修改
return '2020-01-01 00:00:00';
default:
return formatDate(new Date(now.getFullYear(), now.getMonth(), now.getDate()));
}
}
// 格式化日期为 YYYY-MM-DD HH:mm:ss 格式
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day} 00:00:00`;
}
// 处理时间范围选择
const handleTimeRangeChange = (companyId: number, range: 'realtime' | 'day' | 'month' | 'year') => {
companyTimeRanges.value[companyId] = range
// 这里可以添加根据时间范围切换数据源的逻辑
}
// 计算总量的函数
const calculateTotal = (children?: ChartDataItem[]) => {
if (!children || children.length === 0) return 0;
return children.reduce((total, item) => {
return total + (item.measureValue || 0);
}, 0);
};
/* 点击泊位box */
const handleClickBerthItem = (berth: ChartDataItem) => {
console.log(props.shorePowerList)
console.log(berth)
const shorePower = props.shorePowerList.find(item => item.id === berth.id)
selectedShorePowerItem.value = shorePower
console.log(shorePower)
}
const handleOpenHistory = () => {
if (selectedShorePowerItem.value) {
showShorePowerHistory(selectedShorePowerItem.value)
}
}
// 显示岸电箱历史记录
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,
}
}
}
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')))
}))
}))
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-info {
display: flex;
align-items: baseline;
gap: 2px
}
.company-name {
font-size: 24px;
font-weight: bold;
color: #fff;
}
.company-berth-count {
font-size: 16px;
color: #aaa;
margin-top: 4px;
}
.company-total {
font-size: 24px;
font-weight: bold;
color: #1296db;
background-color: rgba(18, 150, 219, 0.1);
padding: 2px 8px;
border-radius: 4px;
}
.data-update-time {
margin-left: 6px;
font-size: 16px;
color: #aaa;
white-space: nowrap;
}
/* 自定义滚动条样式 */
.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);
}
.time-range-container {
display: flex;
align-items: center;
gap: 4px;
}
.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);
}
.close-btn {
width: 36px;
height: 36px;
margin-bottom: 8px;
}
</style>