效果图
完整代码
<template>
<div class='city_box'>
<div class='map_dv'>
<div id='mapdv' class="map_box"></div>
<div :class="[!mapInfo.isShow ? 'bm_dr' : 'bm_dl']" @click="mapInfo.isShow = !mapInfo.isShow">
<img v-if="!mapInfo.isShow" class="img_l" src="@/assets/images/arr_l.svg">
<img v-else class="img_l" src="@/assets/images/arr_r.svg"/>
</div>
<div :class="['databox', mapInfo.isShow ? 'show_data' : 'hide_data']">
<table>
<thead>
<tr class="tb_head">
<td width="40px">
<div class="tb_line"></div>
<div class="tb_line line_dm"></div>
<div class="pont_t"></div>
<div class="pont_r"></div>
<div class="pont_b"></div>
<div class="pont_l"></div>
</td>
<td width="82px;">口岸</td>
<td>终点/起点</td>
<td width="55px;">耗时</td>
<td width="90px;">时间</td>
<td width="82px;">频次</td>
</tr>
</thead>
<tbody>
<tr class="tb_body" v-for="(v, i) in mapInfo.lineDatas" :key="i">
<td class="tb_h1">去<br/>程</td>
<td class="tb_y" width="82px;">{{ v.name }}</td>
<td class="tb_g">{{ v.line }}</td>
<td width="55px;">{{ v.day }}</td>
<td width="90px;">{{ v.week }}</td>
<td width="82px;">{{ v.time }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class='rt_num'>
<div class='s_box'>
<h3 class='inrer_tile'>国际通道</h3>
<div class='quar_box'>
<div class='row_box' v-for='(v, i) in dataInfo' :key='i'>
<img class='icon_img' :src='v.src' alt='火车'>
<div class='info_box'>
<div class='info_tile'>{{ v.name }}</div>
<div class='info_sbox'><span>{{ v.num }}</span>条</div>
</div>
</div>
</div>
</div>
<div class='line_bg'></div>
<div class='s_box'>
<h3 class='inrer_tile'>趟次</h3>
<div class='quar_box rot_box'>
<div class='rotate_box l1'>
<img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
<div class='txt_box'>
<div class='g_wh'>铁路</div>
<div class='g_yel'><span style='font-weight: 600;'>445</span>列</div>
</div>
</div>
<div class='rotate_box l2'>
<img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
<div class='txt_box'>
<div class='g_wh'>航线</div>
<div class='g_yel'><span style='font-weight: 600;'>4600</span>次</div>
</div>
</div>
<div class='rotate_box l3'>
<img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
<div class='txt_box'>
<div class='g_wh'>水运</div>
<div class='g_yel'><span style='font-weight: 600;'>84</span>次</div>
</div>
</div>
<div class='rotate_box l4'>
<img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
<div class='txt_box'>
<div class='g_wh'>多式</div>
<div class='g_yel'><span style='font-weight: 600;'>128</span>次</div>
</div>
</div>
</div>
</div>
<div class='line_bg'></div>
<div class='s_box'>
<h3 class='inrer_tile'>箱量</h3>
<div class='quar_box' style="padding-left: 15px;">
<Echarts :options='mapInfo.options' style='height: 100%;'/>
</div>
</div>
</div>
</div>
</template>
<script lang='ts' setup>
import { reactive, onMounted } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/charts/index.vue'
const dataInfo = [
{
name: '铁路货运班列',
num: 34,
src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2619.png'
},
{
name: '水运航线',
num: 4,
src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2620.png'
},
{
name: '航空航线',
num: 16,
src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2621.png'
},
{
name: '多式联运',
num: 3,
src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2622.png'
}
]
const nGs = []
const chartInfo = reactive({
geo: {
map: 'china',
roam: false,
emphasis: {
disabled: true
},
label: {
show: true,
color: '#fff',
formatter: (res) => {
let wname = ''
switch (res.name) {
case ('美国' || '墨西哥'):
wname = res.name
break
case '墨西哥':
wname = res.name
break
case '英国':
wname = res.name
break
case '芬兰':
wname = res.name
break
case '意大利':
wname = res.name
break
case '印度':
wname = res.name
break
case '中国':
wname = res.name
break
case '日本':
wname = res.name
break
case '越南':
wname = res.name
break
case '澳大利亚':
wname = res.name
break
}
return wname
}
},
top: 'center',
left: 'center',
zoom: 1,
itemStyle: {
areaColor: '#093551',
opacity: 0.8,
borderColor: '#0398C2',
borderWidth: 1
}
},
legend: {
orient: 'vertical',
top: 15,
left: 15,
data: [
{
name: '国际铁路货运班列',
icon: 'circle',
itemStyle: {
color: '#2E4F85',
borderColor: '#fff'
}
},
{
name: '国际水运航线',
icon: 'circle',
itemStyle: {
color: '#506E45',
borderColor: '#fff'
}
},
{
name: '国际航空航线',
icon: 'circle',
itemStyle: {
color: '#846C36',
borderColor: '#fff'
}
},
{
name: '国际多式联运',
icon: 'circle',
itemStyle: {
color: '#7E3B3D',
borderColor: '#fff'
}
}
],
textStyle: {
color: '#fff',
fontSize: 14,
height: 13,
rich: {
a: {
verticalAlign: 'middle'
}
}
},
itemWidth: 10,
itemHeight: 10,
itemGap: 15,
inactiveBorderColor: '#fff',
backgroundColor: '#062462',
padding: [10, 100, 10, 10],
selectedMode: 'multiple'
},
series: nGs,
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: (params) => {
// console.log(params)
if (params.seriesType === 'effectScatter') {
return '线路:' + params.data.fromName + '=>' + params.data.toName
} else if (params.seriesType === 'lines') {
return (
// '线路:' + params.data.fromName + '=>' + params.data.toName + '<br />'
'线路:' + params.data.fromName + '=>' + params.data.toName + '<br />'
)
} else {
return params.name
}
}
}
})
const mapInfo = reactive({
mychart: null,
area: null,
options: null,
isShow: false,
lineDatas: [
{
name: '二连浩特',
line: '马拉舍维奇/杜伊斯堡/汉堡',
day: '18天',
week: '每周周五',
time: '一周一班'
},
{
name: '阿拉山口',
line: '马拉舍维奇/汉堡/杜伊斯堡/里昂/伦敦',
day: '16天',
week: '每周周三',
time: '一周一班'
},
{
name: '阿拉山口',
line: '马拉舍维奇/杜伊斯堡/布达佩斯/布拉格',
day: '16天',
week: '每周周五',
time: '一周一班'
},
{
name: '阿拉山口',
line: '马拉舍维奇/杜伊斯堡/汉堡/里昂',
day: '18天',
week: '每周周六、周日',
time: '一周二班'
}
]
})
function loadData () {
const flays = [
{
name: '北京',
fromName: '中国',
toName: '芬兰',
coords: [[116.4551, 40.2539], [24.939374, 60.176573]]
},
{
name: '北京',
fromName: '中国',
toName: '日本',
coords: [[116.4551, 40.2539], [139.771786, 35.724056]]
},
{
name: '北京',
fromName: '中国',
toName: '英国',
coords: [[116.4551, 40.2539], [-0.128333, 51.513269]]
},
{
name: '北京',
fromName: '中国',
toName: '意大利',
coords: [[116.4551, 40.2539], [12.491766, 41.904716]]
},
{
name: '北京',
fromName: '中国',
toName: '美国',
coords: [[116.4551, 40.2539], [-73.865402, 40.847183]]
},
{
name: '北京',
fromName: '中国',
toName: '印度',
coords: [[116.4551, 40.2539], [77.102253, 28.711647]]
},
{
name: '北京',
fromName: '中国',
toName: '墨西哥',
coords: [[116.4551, 40.2539], [-99.139532, 19.445971]]
},
{
name: '北京',
fromName: '中国',
toName: '越南',
coords: [[116.4551, 40.2539], [105.698818, 20.971959]]
},
{
name: '北京',
fromName: '中国',
toName: '澳大利亚',
coords: [[116.4551, 40.2539], [149.131687, -35.261253]]
}
]
// 航线数据
const linesData = [
{
name: '国际铁路货运班列',
type: 'train',
data: flays.slice(0, 3)
},
{
name: '国际水运航线',
type: 'ship',
data: flays.slice(3, 5)
},
{
name: '国际航空航线',
type: 'plane',
data: flays.slice(5, 7)
},
{
name: '国际多式联运',
type: '',
data: flays.slice(7, 9)
}
]
const planePath = 'image://' + require('@/assets/images/plane.svg')
const trainPath = 'image://' + require('@/assets/images/train.svg')
const shipPath = 'image://' + require('@/assets/images/ship.svg')
const otherPath = 'image://' + require('@/assets/images/ufo.svg')
const pointPath = 'image://' + require('@/assets/images/piont.svg')
const ypointPath = 'image://' + require('@/assets/images/u311.svg')
const bpointPath = 'image://' + require('@/assets/images/u2536.svg')
const rpointPath = 'image://' + require('@/assets/images/u2533.svg')
const mpointPath = 'image://' + require('@/assets/images/u2539.svg')
const s = []
// 线路图
linesData.forEach(v => {
s.push(
{
type: 'lines',
name: v.name,
coordinateSystem: 'geo',
effect: {
show: true,
period: 5,
symbol: v.type === 'plane' ? planePath : v.type === 'ship' ? shipPath : v.type === 'train' ? trainPath : otherPath, // 飞机/轮船/火车/ufo
symbolSize: 35,
trailLength: 0,
// color: '#f00',
loop: true
},
lineStyle: {
width: 2,
curveness: 0.2,
type: 'dashed'
},
data: v.data
}
)
})
// 水波图
flays.forEach(item => {
s.push({
type: 'effectScatter',
name: '水波图',
coordinateSystem: 'geo',
symbol: item.toName === '芬兰' ? pointPath : item.toName === '日本' ? bpointPath : item.toName === '英国' ? mpointPath : ypointPath,
symbolSize: 60,
rippleEffect: {
color: '#f00',
number: 2,
scale: 4,
brushType: 'stroke'
},
data: [
// {
// fromName: item.fromName,
// toName: item.toName,
// value: item.coords[0]
// },
{
fromName: item.fromName,
toName: item.toName,
value: item.coords[1]
}
]
})
})
// 插入起始点
s.push({
type: 'effectScatter',
name: '水波图',
coordinateSystem: 'geo',
symbol: rpointPath,
symbolSize: 60,
rippleEffect: {
number: 2,
scale: 4,
brushType: 'stroke'
},
data: [
{
fromName: flays[0].fromName,
toName: flays[0].toName,
value: flays[0].coords[0]
}
]
})
return new Promise((resolve: any, reject: any) => {
resolve(s)
})
}
function initMap (area, zoom) {
mapInfo.mychart = null
const ct = echarts.init(document.querySelector('#mapdv'))
echarts.registerMap('china', area)
chartInfo.geo.zoom = zoom
ct.setOption(chartInfo, true)
mapInfo.mychart = ct
}
// 箱量
function initChat2 () {
const data = [
{ code: '600519', stock: '铁路', fundPost: '21.987691' },
{ code: '000858', stock: '水运', fundPost: '20.377176' },
{ code: '002475', stock: '航空', fundPost: '19.127404' },
{ code: '600276', stock: '多式联运', fundPost: '18.40882' }
]
const attaData = []
const attaName = []
const topName = []
data.forEach((it, index) => {
attaData[index] = parseFloat(it.fundPost).toFixed(2)
attaName[index] = it.stock
topName[index] = it.fundPost
})
const salvProMax = [] // 背景按最大值
for (let i = 0; i < attaData.length; i++) {
salvProMax.push(attaData[0])
}
const option = {
grid: {
top: '8%',
bottom: 0,
left: 0,
right: 0,
width: 'auto',
containLabel: true
},
xAxis: {
type: 'value',
splitLine: {
show: false
},
axisLabel: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
}
},
yAxis: [
{
type: 'category',
inverse: true,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
inside: true,
color: '#000'
}
},
{
inverse: true,
type: 'category',
offset: 6,
position: 'left',
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
inside: true,
color: '#fff',
align: 'left',
verticalAlign: 'bottom',
lineHeight: 25,
fontSize: 10,
formatter: (val, index) => {
return val
}
},
data: attaName
},
{
inverse: true,
type: 'category',
offset: -25,
position: 'right',
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
inside: true,
color: '#fff',
align: 'right',
verticalAlign: 'bottom',
lineHeight: 25,
fontSize: 10,
formatter: (val, index) => {
return val
}
},
data: topName
}
],
series: [
{
zlevel: 1,
name: '个人所得(亿元)',
type: 'bar',
barWidth: '10px',
animationDuration: 1500,
data: attaData,
align: 'center',
itemStyle: {
// barBorderRadius: 10
color: '#00DEDA'
},
label: {
show: false,
fontSize: 10,
color: '#fff',
textBorderWidth: 2
}
},
{
name: '个人所得(亿元)',
type: 'bar',
barWidth: '10px',
barGap: '-100%',
margin: '20',
data: salvProMax,
itemStyle: {
color: '#0B2C43'
// width: '100%',
// barBorderRadius: 30,
// fontSize: 10
}
}
]
}
return new Promise((resolve, reject) => {
resolve(option)
})
}
onMounted(() => {
loadData().then((res: any) => {
chartInfo.series = res
const world = require('@/assets/geoworld.json')
initMap(world, 1)
})
initChat2().then(res => {
mapInfo.options = res
})
})
</script>
<style lang='scss' scoped>
.city_box {
background-color: #0E1014;
width: 100vw;
height: 100vh;
display: flex;
.map_dv {
position: relative;
flex: 1;
overflow: hidden;
.map_box {
width: 100%;
height: 100%;
}
}
.rt_num {
width: 300px;
height: 100%;
display: flex;
flex-direction: column;
border-left: 20px solid #1C1F27;
.line_bg {
width: 100;
height: 20px;
background-color: #1C1F27;
}
.s_box {
padding: 15px;
flex: 1;
}
.inrer_tile {
color: #fff;
font-size: 18px;
position: relative;
padding-left: 15px;
&:before {
position: absolute;
content: '';
width: 5px;
height: 100%;
background-color: #66BBF9;
top: 0;
left: 0;
}
}
.quar_box {
position: relative;
height: calc(100% - 30px);
.row_box {
display: flex;
align-items: center;
background-color: #0A213D;
padding: 5px 0 5px 18px;
margin-bottom: 10px;
&:last-of-type {
margin-bottom: 0;
}
position: relative;
&::before {
position: absolute;
content: '';
width: 5px;
height: 100%;
background-color: #66BBF9;
top: 0;
left: 0;
}
.icon_img {
width: 35px;
margin-right: 10px;
}
.info_box {
.info_tile {
color: #fff;
}
.info_sbox {
color: #66BBF9;
> span {
font-size: 35px;
font-weight: 600;
padding: 0;
height: initial;
line-height: normal;
}
}
}
}
.l1 {
left: 5px;
top: 10px;
}
.l2 {
left: 100px;
top: 30px;
}
.l3 {
left: 50px;
top: 95px;
}
.l4 {
left: 155px;
top: 100px;
}
.rotate_box {
width: max-content;
position: absolute;
padding: 20px;
.rotate_img {
position: absolute;
width: 100%;
top: 0;
left: 0;
z-index: 1;
}
.txt_box {
position: relative;
z-index: 2;
text-align: center;
font-size: 18px;
.g_wh {
color: #fff;
}
.g_yel {
color: #FFCC00;
}
}
.gif {
animation: rotate 5s linear infinite;
}
}
}
.rot_box {
position: relative;
height: 200px;
}
}
.bm_dl {
border-width: 0px;
position: absolute;
right: 0;
bottom: 50px;
width: 22px;
height: 67px;
background: inherit;
background-color: rgba(1, 84, 120, 0.364705882352941);
border: none;
border-radius: 10px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
z-index: 1;
cursor: pointer;
.img_l {
position: absolute;
width: 100%;
top: 50%;
left: 2px;
z-index: 2;
transform: translateY(-50%);
}
}
.bm_dr {
border-width: 0px;
position: absolute;
right: 0;
bottom: 50px;
width: 22px;
height: 67px;
background: inherit;
background-color: rgba(1, 84, 120, 0.364705882352941);
border: none;
border-radius: 10px;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
z-index: 1;
cursor: pointer;
.img_l {
position: absolute;
width: 100%;
top: 50%;
left: 2px;
z-index: 2;
transform: translateY(-50%);
}
}
.show_data {
animation: showTb 0.5s linear forwards;
}
.hide_data {
animation: hideTb 0.5s linear forwards;
}
.databox {
position: absolute;
width: 570px;
bottom: 0;
transition: all 0.5s linear;
right: -323px;
color: #fff;
font-size: 14px;
table thead, tbody tr {
display: table;
width: 100%;
table-layout: fixed;
margin-bottom: 5px;
}
// table thead {
// width: calc(100% - 1em);
// }
tbody {
display: block;
max-height: 250px;
overflow-y: auto;
&:before {
content: "-";
display: block;
line-height: 1px;
color: transparent;
}
}
td {
padding: 5px 10px;
.bg_td {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
.tb_h1 {
width: 40px;
background: url('@/assets/images/bg_img.svg') no-repeat top center;
background-size: 100% 100%;
}
.tb_head {
position: relative;
background-color: rgba(0, 150, 255, 0.298039215686275);
.tb_line {
position: absolute;
height: 1px;
width: 100%;
background-color: #0A2C5A;
top: -5px;
left: 0;
}
.line_dm {
bottom: -5px;
top: inherit;
}
.pont_t {
background-color: #00AEFF;
width: 5px;
height: 3px;
position: absolute;
top: -6px;
left: -6px;
}
.pont_r {
background-color: #00AEFF;
width: 5px;
height: 3px;
position: absolute;
top: -6px;
right: -6px;
}
.pont_b {
background-color: #00AEFF;
width: 5px;
height: 3px;
position: absolute;
bottom: -6px;
right: -6px;
}
.pont_l {
background-color: #00AEFF;
width: 5px;
height: 3px;
position: absolute;
bottom: -6px;
left: -6px;
}
}
.tb_body {
cursor: default;
background: linear-gradient(180deg, rgba(0, 102, 153, 1) 0%, rgba(0, 102, 153, 1) 17%, rgba(9, 60, 106, 1) 100%, rgba(9, 60, 106, 1) 100%);
&:hover {
background: #F0AB33;
td {
color: #000;
}
.tb_h1 {
background: url('@/assets/images/bg_redimg.svg') no-repeat top center;
background-size: 100% 100%;
}
}
}
.tb_y {
color: #00FFBA;
}
.tb_g {
color: #FFEA00;
}
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes showTb {
0% {
right: -623px;
}
100% {
right: 23px;
}
}
@keyframes hideTb {
0% {
right: 23px;
}
100% {
right: -623px;
display: none;
}
}
</style>