13 changed files with 799 additions and 572 deletions
@ -0,0 +1,88 @@ |
|||||
|
<template> |
||||
|
<div class="company-shore-power"> |
||||
|
<div class="left"> |
||||
|
<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"> |
||||
|
<BarChart :chart-data="companyComparisonData" title="企业岸电使用对比" color="#4CAF50" /> |
||||
|
</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="company-selector"> |
||||
|
<el-select v-model="selectedCompany" placeholder="请选择公司" @change="handleCompanyChange" |
||||
|
style="width: 100%; margin-bottom: 20px;"> |
||||
|
<el-option v-for="item in companyComparisonData" :key="item.name" :label="item.name" :value="item.name" /> |
||||
|
</el-select> |
||||
|
|
||||
|
<PieChart :chart-data="pieChartData" :title="`${selectedCompany}子项占比`" style="height: 300px;" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, computed } from 'vue' |
||||
|
import BarChart from './charts/BarChart.vue' |
||||
|
import PieChart from './charts/PieChart.vue' |
||||
|
|
||||
|
// 定义组件属性 |
||||
|
interface ChartDataItem { |
||||
|
name: string; |
||||
|
value: number; |
||||
|
} |
||||
|
|
||||
|
interface Props { |
||||
|
companyComparisonData: ChartDataItem[]; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps<Props>() |
||||
|
|
||||
|
// 定义事件 |
||||
|
const emit = defineEmits<{ |
||||
|
(e: 'company-change', company: string): void; |
||||
|
}>() |
||||
|
|
||||
|
// 组件内部状态 |
||||
|
const selectedCompany = ref<string>(props.companyComparisonData[0]?.name || '') |
||||
|
|
||||
|
// 模拟的饼图数据 |
||||
|
const pieChartData = computed(() => { |
||||
|
// 根据选中的公司生成模拟的子项数据 |
||||
|
return [ |
||||
|
{ name: '码头1', value: Math.floor(Math.random() * 100) + 50 }, |
||||
|
{ name: '码头2', value: Math.floor(Math.random() * 100) + 50 }, |
||||
|
{ name: '码头3', value: Math.floor(Math.random() * 100) + 50 }, |
||||
|
{ name: '码头4', value: Math.floor(Math.random() * 100) + 50 }, |
||||
|
] |
||||
|
}) |
||||
|
|
||||
|
// 处理公司选择变化 |
||||
|
const handleCompanyChange = (company: string) => { |
||||
|
emit('company-change', company) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.company-shore-power { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.company-selector { |
||||
|
width: 100%; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,140 @@ |
|||||
|
<template> |
||||
|
<div class="port-overview"> |
||||
|
<div class="left" style="width: 500px;"> |
||||
|
<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="搜索船舶" /> |
||||
|
</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> |
||||
|
<div class="ship-table-body"> |
||||
|
<div v-for="ship in shipStatusData" :key="ship.id" class="ship-table-row" @click="handleSwitch(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('正常')"> |
||||
|
正常 |
||||
|
</div> |
||||
|
<div class="status-tag" :class="getStatusClass('空闲')"> |
||||
|
空闲 |
||||
|
</div> |
||||
|
<div class="status-tag" :class="getStatusClass('故障')"> |
||||
|
故障 |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="right" style="width: 1000px;"> |
||||
|
<div v-if="selectedShip" 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">{{ selectedShip.shipBasicInfo.name }}</span> |
||||
|
</div> |
||||
|
<div class="card-content"> |
||||
|
<div class="overview-grid"> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ selectedShip.shorePowerAndShip?.powerUsage || 0 }}</div> |
||||
|
<div class="overview-label">功率(kW)</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ shorePowerStatusMap[selectedShip.shorePowerAndShip?.status || ''] }}</div> |
||||
|
<div class="overview-label">岸电状态</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> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { computed } from 'vue' |
||||
|
|
||||
|
// 定义组件属性 |
||||
|
interface ShipItem { |
||||
|
id: string; |
||||
|
shipBasicInfo: { |
||||
|
name: string; |
||||
|
}; |
||||
|
shorePowerAndShip?: { |
||||
|
status?: string; |
||||
|
powerUsage?: number; |
||||
|
}; |
||||
|
modelInstance?: any; |
||||
|
} |
||||
|
|
||||
|
interface Props { |
||||
|
shipStatusData: ShipItem[]; |
||||
|
selectedShip: ShipItem | null; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps<Props>() |
||||
|
|
||||
|
// 定义事件 |
||||
|
const emit = defineEmits<{ |
||||
|
(e: 'switch-ship', ship: ShipItem): void; |
||||
|
}>() |
||||
|
|
||||
|
// 处理船舶选择 |
||||
|
const handleSwitch = (ship: ShipItem) => { |
||||
|
emit('switch-ship', ship) |
||||
|
} |
||||
|
|
||||
|
// 状态样式映射 |
||||
|
const shorePowerStatusMap = { |
||||
|
'on': '使用中', |
||||
|
'off': '未使用', |
||||
|
'fault': '故障', |
||||
|
'': '未知' |
||||
|
} |
||||
|
|
||||
|
// 获取状态样式类 |
||||
|
const getStatusClass = (status: string) => { |
||||
|
return `status-${status === '使用中' ? 'shorepower' : status === '故障' ? 'fault' : 'default'}` |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.port-overview { |
||||
|
display: flex; |
||||
|
gap: 10px; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.search-container { |
||||
|
height: 100%; |
||||
|
width: 200px; |
||||
|
border-radius: 4px; |
||||
|
border: 1px solid rgb(10, 130, 170); |
||||
|
padding: 0 10px; |
||||
|
margin-bottom: 8px; |
||||
|
background-color: rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,189 @@ |
|||||
|
<template> |
||||
|
<div class="ship-shore-power"> |
||||
|
<div class="left" style="width: 800px;"> |
||||
|
<div 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="overview-grid"> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ berthingShips }}</div> |
||||
|
<div class="overview-label">在泊船舶数量</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ shorePowerShips }}</div> |
||||
|
<div class="overview-label">使用岸电船舶数量</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ noShorePowerShips }}</div> |
||||
|
<div class="overview-label">未使用岸电船舶数量</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="card digital-twin-card--deep-blue " style="flex: 3;"> |
||||
|
<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-data-table-container" ref="scrollContainerRef"> |
||||
|
<div class="ship-data-table"> |
||||
|
<div v-for="(ship, index) in shipData" :key="index" class="ship-data-row"> |
||||
|
<div class="ship-data-cell ship-name">{{ ship.name }}</div> |
||||
|
<div class="ship-data-cell wharf-name">{{ ship.wharf }}</div> |
||||
|
<div class="ship-data-cell berth-number">{{ ship.berth }}</div> |
||||
|
<div class="ship-data-cell shore-power-status" :class="ship.statusClass">{{ ship.status }}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue' |
||||
|
|
||||
|
// 定义组件属性 |
||||
|
interface ShipDataItem { |
||||
|
name: string; |
||||
|
wharf: string; |
||||
|
berth: string; |
||||
|
status: string; |
||||
|
statusClass: string; |
||||
|
} |
||||
|
|
||||
|
interface Props { |
||||
|
berthingShips: number; |
||||
|
shorePowerShips: number; |
||||
|
noShorePowerShips: number; |
||||
|
shipData: ShipDataItem[]; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps<Props>() |
||||
|
|
||||
|
// 滚动容器引用 |
||||
|
const scrollContainerRef = ref<HTMLElement | null>(null) |
||||
|
|
||||
|
// 滚动相关变量 |
||||
|
let scrollInterval: NodeJS.Timeout | null = null |
||||
|
const scrollSpeed = ref<number>(20) // 滚动速度,越小越快 |
||||
|
|
||||
|
// 开始滚动 |
||||
|
const startScroll = () => { |
||||
|
if (!scrollContainerRef.value) return |
||||
|
|
||||
|
const container = scrollContainerRef.value |
||||
|
const content = container.querySelector('.ship-data-table') |
||||
|
if (!content) return |
||||
|
|
||||
|
// 计算滚动距离 |
||||
|
const scrollDistance = content.offsetHeight - container.clientHeight |
||||
|
if (scrollDistance <= 0) return // 内容高度小于容器高度,不需要滚动 |
||||
|
|
||||
|
let currentScroll = 0 |
||||
|
let direction = 1 // 1 向下滚动,-1 向上滚动 |
||||
|
|
||||
|
// 清除可能存在的旧定时器 |
||||
|
if (scrollInterval) { |
||||
|
clearInterval(scrollInterval) |
||||
|
} |
||||
|
|
||||
|
// 创建新的滚动定时器 |
||||
|
scrollInterval = setInterval(() => { |
||||
|
if (!scrollContainerRef.value) return |
||||
|
|
||||
|
currentScroll += direction * scrollSpeed.value |
||||
|
|
||||
|
// 检查是否到达边界 |
||||
|
if (currentScroll >= scrollDistance) { |
||||
|
currentScroll = scrollDistance |
||||
|
direction = -1 |
||||
|
} else if (currentScroll <= 0) { |
||||
|
currentScroll = 0 |
||||
|
direction = 1 |
||||
|
} |
||||
|
|
||||
|
// 应用滚动 |
||||
|
scrollContainerRef.value.scrollTop = currentScroll |
||||
|
}, 100) |
||||
|
} |
||||
|
|
||||
|
// 停止滚动 |
||||
|
const stopScroll = () => { |
||||
|
if (scrollInterval) { |
||||
|
clearInterval(scrollInterval) |
||||
|
scrollInterval = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 监听activeHeadGroup变化 |
||||
|
watch( |
||||
|
() => props.shipData, |
||||
|
() => { |
||||
|
// 数据变化时重新开始滚动 |
||||
|
stopScroll() |
||||
|
startScroll() |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
) |
||||
|
|
||||
|
// 组件挂载时开始滚动 |
||||
|
onMounted(() => { |
||||
|
// 延迟启动滚动,确保DOM已经渲染完成 |
||||
|
setTimeout(() => { |
||||
|
startScroll() |
||||
|
}, 500) |
||||
|
}) |
||||
|
|
||||
|
// 组件销毁前停止滚动 |
||||
|
onBeforeUnmount(() => { |
||||
|
stopScroll() |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.ship-shore-power { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.ship-data-table-container { |
||||
|
height: 100%; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.ship-data-table { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.ship-data-row { |
||||
|
display: flex; |
||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
|
||||
|
.ship-data-cell { |
||||
|
flex: 1; |
||||
|
padding: 10px; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.shore-power-status { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.shore-power-status.status-on { |
||||
|
color: #4CAF50; |
||||
|
} |
||||
|
|
||||
|
.shore-power-status.status-off { |
||||
|
color: #F44336; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,130 @@ |
|||||
|
<template> |
||||
|
<div class="shore-power-usage"> |
||||
|
<div class="left" style="width: 40%;"> |
||||
|
<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="overview-grid"> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ totalPower }}</div> |
||||
|
<div class="overview-label">累计用电(千瓦时)</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ fuelReduction }}</div> |
||||
|
<div class="overview-label">减少燃油(吨)</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ co2Reduction }}</div> |
||||
|
<div class="overview-label">减少二氧化碳排放(千克)</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ pm25Reduction }}</div> |
||||
|
<div class="overview-label">减少PM2.5排放(千克)</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ noxReduction }}</div> |
||||
|
<div class="overview-label">减少氮氧化物(千克)</div> |
||||
|
</div> |
||||
|
<div class="overview-item"> |
||||
|
<div class="overview-value">{{ so2Reduction }}</div> |
||||
|
<div class="overview-label">减少二氧化硫(千克)</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</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"> |
||||
|
<LineChart :chart-data="fuelReductionData" title="减少燃油趋势" color="#4CAF50" /> |
||||
|
</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">减少CO₂排放(千克)</span> |
||||
|
</div> |
||||
|
<div class="card-content"> |
||||
|
<LineChart :chart-data="co2ReductionData" title="减少CO₂排放趋势" color="#F44336" /> |
||||
|
</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">减少PM2.5排放(千克)</span> |
||||
|
</div> |
||||
|
<div class="card-content"> |
||||
|
<LineChart :chart-data="pm25ReductionData" title="减少PM2.5排放趋势" color="#FF9800" /> |
||||
|
</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">减少NOₓ排放(千克)</span> |
||||
|
</div> |
||||
|
<div class="card-content"> |
||||
|
<LineChart :chart-data="noxReductionData" title="减少NOₓ排放趋势" color="#9C27B0" /> |
||||
|
</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">减少SO₂排放(千克)</span> |
||||
|
</div> |
||||
|
<div class="card-content"> |
||||
|
<LineChart :chart-data="so2ReductionData" title="减少SO₂排放趋势" color="#00BCD4" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import LineChart from './charts/LineChart.vue' |
||||
|
|
||||
|
// 定义组件属性 |
||||
|
interface ChartDataItem { |
||||
|
name: string; |
||||
|
value: number; |
||||
|
} |
||||
|
|
||||
|
interface Props { |
||||
|
totalPower: number; |
||||
|
fuelReduction: number; |
||||
|
co2Reduction: number; |
||||
|
pm25Reduction: number; |
||||
|
noxReduction: number; |
||||
|
so2Reduction: number; |
||||
|
fuelReductionData: ChartDataItem[]; |
||||
|
co2ReductionData: ChartDataItem[]; |
||||
|
pm25ReductionData: ChartDataItem[]; |
||||
|
noxReductionData: ChartDataItem[]; |
||||
|
so2ReductionData: ChartDataItem[]; |
||||
|
} |
||||
|
|
||||
|
defineProps<Props>() |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.shore-power-usage { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,182 @@ |
|||||
|
<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: '#1296db' |
||||
|
}) |
||||
|
|
||||
|
// 图表容器引用和实例 |
||||
|
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: '10%', |
||||
|
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: props.color |
||||
|
}, |
||||
|
{ |
||||
|
offset: 1, |
||||
|
color: props.color + '80' |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
borderRadius: [1, 1, 0, 0] |
||||
|
}, |
||||
|
emphasis: { |
||||
|
itemStyle: { |
||||
|
color: props.color |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
chartInstance.setOption(option) |
||||
|
} |
||||
|
|
||||
|
// 监听数据变化 |
||||
|
watch( |
||||
|
() => props.chartData, |
||||
|
() => { |
||||
|
updateChart() |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
) |
||||
|
|
||||
|
// 窗口大小改变时重绘图表 |
||||
|
const handleResize = () => { |
||||
|
if (chartInstance) { |
||||
|
chartInstance.resize() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 组件挂载时初始化图表 |
||||
|
onMounted(() => { |
||||
|
initChart() |
||||
|
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> |
||||
@ -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> |
|
||||
Loading…
Reference in new issue