目录
一、效果展示
二、说明
2.1 销售额统计图表
2.2 全国热榜模块
三、边框图片【border-image】
3.1 使用场景
3.2 边框图片切图原理
3.3 边框图片语法【重要】
四、代码展示
4.1 展示页面
4.2 数据概览区域
4.3 监控区域
4.4 点位分布统计区域
4.5 设备数据统计区域
4.6 全国用户总量统计区域
4.7 订单区域
4.8 销售统计区域
4.9 渠道分布
4.10 季度销售进度
4.11 排行榜
一、效果展示
二、说明
此文章只是展示如何使用Echarts实现大屏可视化,数据都是固定的,如果想要看使用websocket实现的可以看这篇文章Echarts实现大屏可视化_echart 大屏-CSDN博客文章浏览阅读896次,点赞19次,收藏20次。的时候一个人的操作所有人都可以看见,可以实现全局图表主题的切换,主题的切换也是可以实现多个浏览器之间联动的。并且随着浏览器窗口的变化,所有的图表都可以自动适配屏幕的大小。该项目是使用websocket实现数据的实时推送,每一个图表就是一个单独的组件,该组件可以进行全屏和取消全屏的操作,可以实现多个浏览器同时访问。该组件显示的图表并不是一次性将所有的地区数据进行展示,而是按照销售数量从大到小进行排序,然后每次先显示10条数据,每隔2s将数据向左移动一个。三个图表之间的来回切换。三个图表之间的来回切换。_echart 大屏https://blog.csdn.net/qq_45569925/article/details/144676550?fromshare=blogdetail&sharetype=blogdetail&sharerId=144676550&sharerefer=PC&sharesource=qq_45569925&sharefrom=from_link 该页面设置到的图表有:
👉 南丁格尔玫瑰图
👉 中国地图+飞机航线
👉 折线图
👉 雷达图
👉 仪表盘
👉 柱状图
2.1 销售额统计图表
是按照年、季、月、周进行划分的,点击不同的按钮即可显示不同的数据,且此图是按照2s的时间间隔进行自动切换的;鼠标移入的时候图表的自动切换暂停,当鼠标移开的时候自动切换重新启动。
2.2 全国热榜模块
此处的数据也是按照2s的时间间隔自动进行切换的,最右边那一列展示的是各省热销商品的具体数据,当前选中的哪一个就将哪一个的数据显示出来,鼠标移入到哪一个省份,就显示哪个省份的数据且自动切换暂停,当鼠标移开之后自动切换重新启动。
三、边框图片【border-image】
3.1 使用场景
盒子大小不一,但是边框样式相同,此时就需要边框图片来完成。
3.2 边框图片切图原理
把四个角切出去(九宫格的由来),中间部分可以铺排、拉伸或者环绕。
按照上右下左顺序进行切割
3.3 边框图片语法【重要】
属性 | 描述 |
border-image-source | 用在边框的图片的路径 |
border-image-slice | 图片边框向内偏移【裁剪的尺寸,一定不加单位,上右下左顺序】 |
border-image-width | 图片边框的宽度【需要加单位,不是边框的宽度而是边框图片的宽度】 |
border-image-repeat | 图像边框是否应
|
示例
.element {border: 10px solid transparent; /* 定义边框宽度 */border-image-source: url('path/to/border-image.png'); /* 边框图片路径 */border-image-slice: 30; /* 切割图片,确保边角不被拉伸 */border-image-width: 20px; /* 设置边框图片宽度 */border-image-outset: 10px; /* 边框图片超出边框的量 */border-image-repeat: round; /* 确保图像平滑地填充边框 */
}
border-image-source
指定了边框图像的来源。border-image-slice
设为30,意味着从原始图像的每个边缘向内切割30个单位,形成四个角部图像和四条边的图像。如果图像的尺寸是60x60像素,那么每个角部图像将是30x30像素,而边上的图像则会是30x60像素。border-image-width
设置了边框图像的实际显示宽度。border-image-outset
使得边框图像稍微超出实际的边框。border-image-repeat
使用round
值确保图像平铺时不会出现不连续的情况,而是尽可能均匀分布。请注意,
border-image
的效果依赖于border
的存在。如果未定义边框,则border-image
将不会生效。此外,为了确保兼容性,您可能需要添加浏览器前缀(例如-webkit-border-image
),尽管现代浏览器大多已经支持标准的border-image
语法。
四、代码展示
4.1 展示页面
<template><div class="box"><div class="container"><div class="main"><div class="left"><!-- 数据概览区域 --><Overview /><!-- 监控区域 --><Monitor /><!-- 点位分布统计区域 --><Point /></div><div class="middle"><!-- 设备数据统计区域 --><Map /><!-- 全国用户总量统计区域 --><Users /></div><div class="right"><!-- 订单区域 --><Order /><!-- 销售统计区域 --><Sales /><div class="wrap"><!-- 渠道分布 --><Channel /><!-- 一季度销售进度 --><Quarter /></div><!-- 排行榜 --><Rank /></div></div></div></div>
</template><script setup>
import Overview from '@/components/Overview.vue'
import Monitor from '@/components/Monitor.vue'
import Point from '@/components/Point.vue'
import Map from '@/components/Map.vue'
import Users from '@/components/Users.vue'
import Order from '@/components/Order.vue'
import Sales from '@/components/Sales.vue'
import Channel from '@/components/Channel.vue'
import Quarter from '@/components/Quarter.vue'
import Rank from '@/components/Rank.vue'
</script><style lang="scss">
.box {min-height: 100%;background-image: url('../assets/images/bg.png');background-size: cover;.container {padding: 1.25rem 0.25rem 0;min-width: 1024px;max-width: 1920px;min-height: 100vh;background-image: url('../assets/images/logo.png');background-size: contain;background-repeat: no-repeat;.main {height: 100%;display: flex;.middle {flex: 4;margin: .4rem .25rem 0;}.left,.right {flex: 3;.wrap {display: flex;}}.dot {display: inline-block;width: .1rem;height: .1625rem;border-radius: 0.05rem;background: #006cff;}// 公共面板样式.panel {margin-bottom: 0.25rem;position: relative;border: 15px solid transparent;// border-width: 1.25rem 1rem .5625rem 3.375rem;border-width: .6375rem .475rem .25rem 1.65rem;border-image-source: url('../assets//images/border.png');border-image-slice: 100 80 45 270;.inner {padding: .3rem .45rem;position: absolute;top: -.6375rem;right: -.475rem;bottom: -.25rem;left: -1.65rem;h3 {font-size: .25rem;color: #fff;}}}}}
}
</style>
4.2 数据概览区域
<template><!-- 数据概览区域 --><div class="panel overview"><div class="inner"><ul><li><h4>2190</h4><span><i class="dot"></i>设备总数</span></li><li><h4>190</h4><span><i class="dot dot2"></i>季度新增</span></li><li><h4>3,001</h4><span><i class="dot dot2"></i>运营设备</span></li><li><h4>108</h4><span><i class="dot dot3"></i>异常设备</span></li></ul></div></div>
</template><script setup>
import { ref } from 'vue'
// 当前是异常还是故障
const isAbnorma = ref(false)
</script><style lang="scss" scoped>
// 概览区域
.overview {height: 1.375rem;padding: 0 0 .1rem 0.06rem;ul {display: flex;justify-content: space-between;h4 {font-size: .35rem;color: #fff;}span {padding: 0 0 0.1rem 0.06rem;font-size: 0.2rem;color: #4c9bfd;.dot2 {background: #6acca3 !important;}.dot3 {background: #ed3f35 !important;}}}
}
</style>
4.3 监控区域
<template><!-- 监控区域 --><div class="panel monitor"><div class="inner"><div class="tab"><divclass="tab-item":class="{ active: isAbnorma === item.isAbnorma }"v-for="item in tabs":key="item.id"@click="isAbnorma = item.isAbnorma">{{ item.text }}设备监控</div></div><div class="content"><div class="c-top"><div>{{ isAbnorma ? '异常' : '故障' }}时间</div><div>设备地址</div><div>异常代码</div></div><div class="c-bottom"><!-- 内容滚动区域 --><div class="marquee"><divclass="row"v-for="item in 20":key="item"><i class="dot"></i><span>20241225</span><span>北京市昌平区建材西路金燕龙写卡萨丁金卡三等奖</span><span>1000002</span></div></div></div></div></div></div>
</template><script setup>
import { ref } from 'vue'
const tabs = [{ id: 1, text: '故障', isAbnorma: false },{ id: 2, text: '异常', isAbnorma: true },
]
// 当前是异常还是故障
const isAbnorma = ref(false)
</script><style lang="scss" scoped>
.monitor {height: 6rem;.inner {padding: .3rem 0 !important;display: flex;flex-direction: column;}.tab {padding: 0 .45rem .25rem;display: flex;align-items: center;font-size: 0.225rem;color: #1950c4;&-item {padding: 0 .3375rem;&:first-child {padding-left: 0;border-right: .0375rem solid #00f2f1;}&.active {color: #fff;}}}.content {flex: 1;position: relative;font-size: 0.175rem;overflow: hidden;.c-top {margin: 0 0.1rem;padding: 0.15rem 0.45rem;display: flex;justify-content: space-between;line-height: 1.05;// background: rgba(255, 255, 255, 0.1);background: #1A1F2A;position: relative;z-index: 1;color: #68d8fe;div {width: 1rem;&:nth-child(2) {width: 2.5rem;}}}.c-bottom {position: relative;z-index: 0;// 动画@keyframes scroll-top {0% {transform: translateY(0);}100% {transform: translateY(-50%);}}.marquee {animation: scroll-top 15s linear infinite;.row {padding: 0.15rem 0.45rem;display: flex;align-items: center;justify-content: space-between;line-height: 1.05;color: #516CBA;.dot {position: absolute;left: 0.2rem;display: inline-block;background: #00f2f1;opacity: 0;}&:hover {background: rgba(255, 255, 255, 0.1);.dot {opacity: 1;}}span {width: 1rem;&:nth-child(3) {width: 2.5rem;// 单行省略overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}}}}}}
}
</style>
4.4 点位分布统计区域
<template><div class="panel point"><div class="inner"><h3>点位分布统计</h3><div class="chart"><!-- 饼图 --><divclass="cleft"ref="pieRef"></div><!-- 数据 --><div class="cright"><divclass="item"v-for="item in data":key="item.id"><h4>{{ item.value }}</h4><iclass="dot":style="{ background: item.color }"></i>{{ item.name }}</div></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
const data = ref([{ id: 1, name: '点位总数', value: '320,11', color: '#ed3f35' },{ id: 2, name: '本月新增', value: '418', color: '#eacf19' }
])
const pieRef = ref(null)
const instance = shallowRef(null)
// 初始化图表
const initChart = () => {// 初始化图表实例instance.value = echarts.init(pieRef.value)// 配置图标参数const option = {color: ['#006cff', '#60cda0', '#ed8884', '#ff9f7f', '#0096ff', '#9fe6b8', '#32c5e9', '#1d9dff'],// 提示框tooltip: {//触发时机,非轴图形,使用item的意思是放到数据对应图形上时触发提示trigger: 'item',/* 格式化提示内容:a:提示框中的系列名称b:提示框中的数据项名称c:提示框中的数据项值d:提示框中的数据项百分比*/formatter: '{a} <br/>{b} : {c} ({d}%)'},series: [{name: '点位统计',//图表名称type: 'pie',//图标类型// 南丁格尔玫瑰图有两个圈,内圈半径10%,外圈半径70%radius: ['10%', '60%'],// 图表中心位置,left50%,top50% 距离图表DOM容器的距离center: ['50%', '50%'],roseType: 'radius',//radius半径模式,area面积模式// 文字相关的样式label: {fontSize: 10},// 引导线样式lineStyle: {length: 6,//连接扇形图形线长length2: 8//连接文字线长},data: [{ value: 20, name: '云南' },{ value: 26, name: '北京' },{ value: 24, name: '山东' },{ value: 25, name: '河北' },{ value: 20, name: '江苏' },{ value: 25, name: '浙江' },{ value: 30, name: '四川' },{ value: 42, name: '湖北' },]}]};// 配置图表instance.value.setOption(option)
}
onMounted(() => {initChart()// 监听窗口大小变化事件window.addEventListener('resize', () => {instance.value.resize()})
})
onUnmounted(() => {// 在组件销毁的时候注销注册的回调函数window.removeEventListener('resize', () => {instance.value.resize()})
})
</script><style lang="scss" scoped>
.point {height: 4.25rem;.chart {margin-top: .3rem;display: flex;justify-content: space-between;.cleft {flex: 1;// width: 3.9rem;height: 3rem;margin-left: -0.1875rem;}.cright {padding: .45rem .375rem;display: flex;flex-direction: column;justify-content: space-between;width: 1.71rem;border-radius: 0 .5rem 0 .5rem;background: rgba(3, 10, 40, .8);.item {font-size: .2rem;color: #4c9bfd;h4 {margin-bottom: .15rem;font-size: .35rem;color: #fff;}}}}
}
</style>
4.5 设备数据统计区域
<template><div class="map"><h3><i class="iconfont icon-cube"></i>设备数据统计</h3><!-- 中国地图 --><divclass="chart"ref="geoRef"> </div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
import axios from 'axios'
import option from '@/utils/mapOptions'const geoRef = ref(null)
const instance = shallowRef(null)
// 初始化图表
const initChart = async () => {instance.value = echarts.init(geoRef.value)// 获取中国地图的矢量数据const chinaMap = await axios.get('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')echarts.registerMap('china', chinaMap.data)// 对图标初始化的配置instance.value.setOption(option)
}
onMounted(() => {initChart()// 监听窗口大小变化事件window.addEventListener('resize', () => {instance.value.resize()})
})
onUnmounted(() => {// 在组件销毁的时候注销注册的回调函数window.removeEventListener('resize', () => {instance.value.resize()})
})
</script><style lang="scss" scoped>
.map {height: 7.225rem;margin-bottom: 0.25rem;display: flex;flex-direction: column;h3 {padding: 0.2rem 0;margin: 0;font-size: 0.25rem;line-height: 1;color: #fff;.icon-cube {color: #68d8fe;}}.chart {flex: 1;background: rgba(255, 255, 255, 0.05);}
}
</style>
4.6 全国用户总量统计区域
<template><div class="panel users"><div class="inner"><h3>全国用户总量统计</h3><div class="chart"><!-- 柱状图 --><divclass="cleft"ref="barRef"></div><!-- 数据 --><div class="cright"><divclass="item"v-for="item in data":key="item.id"><h4>{{ item.value }}</h4><iclass="dot":style="{ background: item.color }"></i>{{ item.name }}</div></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
const data = ref([{ id: 1, name: '用户总数', value: '120,899', color: '#ed3f35' },{ id: 2, name: '本月新增', value: '248', color: '#eacf19' }
])
const barRef = ref(null)
const instance = shallowRef(null)
// 初始化图表
const initChart = async () => {if (!barRef.value) return// 初始化图表实例instance.value = echarts.init(barRef.value)const obj = {itemStyle: {color: '#254065'},emphasis: {// 鼠标放到柱子上,不高亮显示itemStyle: {color: '#254065'}},tooltip: {//鼠标经过柱子时不显示提示框show: false}}// 配置图标参数const option = {// 从上到下渐变color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#00fffb' },// 渐变开始颜色{ offset: 1, color: '#0061ce' }// 渐变结束颜色]),// 提示框配置tooltip: {trigger: 'item',//触发方式},// 配置图表位置grid: {top: '3%',right: '3%',bottom: '3%',left: '0%',// 图表位置津贴画布边缘是否显示刻度以及label文字,防止坐标轴标签溢出,跟grid区域有关系containLabel: true,show: true,//是否显示直角坐标系网格borderColor: 'rgba(255,255,255,0.3)'//边框颜色},xAxis: [{type: 'category',data: ['上海', '广州', '北京', '深圳', '合肥', '', '......', '', '杭州', '厦门', '成都', '重庆'],axisTick: {show: false,//把x轴的刻度线隐藏},axisLabel: {//刻度标签color: '#4c9bfd'//刻度标签颜色},axisLine: {//坐标轴轴线lineStyle: {//x轴的颜色color: 'rgba(255,255,255,0.3)'}}}],yAxis: [{type: 'value',axisTick: {show: false,//把x轴的刻度线隐藏},axisLabel: {//刻度标签color: '#4c9bfd'//刻度标签颜色},axisLine: {//坐标轴轴线lineStyle: {//x轴的颜色color: 'rgba(255,255,255,0.3)'}},splitLine: {//网格线lineStyle: {//网格线的颜色color: 'rgba(255,255,255,0.3)'}}}],series: [{name: '用户统计',type: 'bar',barWidth: '60%',data: [2100, 1900, 1700, 1560, 1400, 1200, 1200, 1200,{ value: 1200, ...obj },{ value: 1200, ...obj },{ value: 1200, ...obj },900, 750, 600, 400, 240]}]};// 配置图表instance.value.setOption(option)
}
const resizeHandler = () => {instance.value && instance.value.resize()
}
onMounted(() => {initChart()// 监听窗口大小变化事件window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {// 在组件销毁的时候注销注册的回调函数window.removeEventListener('resize', resizeHandler)instance.value && instance.value.dispose()
})
</script><style lang="scss" scoped>
.users {height: 4.25rem;.chart {margin-top: .3rem;display: flex;justify-content: space-between;.cleft {flex: 1;height: 3rem;// margin-left: -0.1875rem;}.cright {padding: .45rem .375rem;display: flex;flex-direction: column;justify-content: space-between;width: 1.72rem;border-radius: 0 .5rem 0 .5rem;background: #030A28;.item {font-size: .2rem;color: #4c9bfd;h4 {margin-bottom: .15rem;font-size: .35rem;color: #fff;}}}}
}
</style>
4.7 订单区域
<template><!-- 数据概览区域 --><div class="panel order"><div class="inner"><div class="filter"><divclass="fitem"v-for="item in filters":key="item.id":class="{ active: activeFilter === item.id }"@click="handleChange(item)">{{ item.text }}</div></div><ul><li><h4>{{ data.orders }}</h4><span><i class="dot"></i>订单量</span></li><li><h4>{{ data.sales }}</h4><span><i class="dot dot2"></i>销售额(万元)</span></li></ul></div></div>
</template><script setup>
import { ref, reactive } from 'vue'const filters = ref([{ id: 1, text: '365天' },{ id: 2, text: '90天' },{ id: 3, text: '30天' },{ id: 4, text: '24小时' }
])
let data = reactive({ orders: '20,301,987', sales: '99834' },)
// 当前的筛选条件
const activeFilter = ref(1)
// 切换筛选条件
const handleChange = (item) => {activeFilter.value = item.idswitch (item.id) {case 1:data.orders = '20,301,987'data.sales = '99834'breakcase 2:data.orders = '301.987'data.sales = '9834'breakcase 3:data.orders = '1,987'data.sales = '3834'breakcase 4:data.orders = '987'data.sales = '834'break}
}
</script><style lang="scss" scoped>
// 概览区域
.order {height: 1.875rem;padding: 0 0 .1rem 0.06rem;.filter {display: flex;align-items: center;.fitem {margin-bottom: 0.25rem;padding: 0 0.225rem;height: 0.225rem;line-height: 1;font-size: 0.225rem;border-right: .05rem solid #00f2f1;color: #1950c4;&:first-child {padding-left: 0;}&:last-child {padding-right: 0;border: none;}&.active {color: #fff;}}}ul {display: flex;justify-content: space-between;li {flex: 1;h4 {font-size: .35rem;color: #fff;}span {padding: 0 0 0.1rem 0.06rem;font-size: 0.2rem;color: #4c9bfd;.dot {background: #ed3f35 !important;}.dot2 {background: #eacf19 !important;}}}}
}
</style>
4.8 销售统计区域
<template><divclass="panel sales"ref="salesRef"><div class="inner"><div class="header"><h3>销售额统计</h3><spanv-for="(item, index) in filter":key="index":class="{ active: item.type === currentFilter }"@click="handleChange(item)">{{ item.name }}</span></div><div class="chart"><div class="label">单位:万</div><!-- 柱状图 --><divclass="cleft"ref="lineRef"></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
const filter = [{ name: "年", type: 'year' },{ name: "季", type: 'quarter' },{ name: "月", type: 'month' },{ name: "周", type: 'week' },
]
const currentFilter = ref('year')
const lineRef = ref(null)
const instance = shallowRef(null)
const salesRef = ref(null)const seriesData = {year: [[24, 40, 101, 134, 90, 230, 210, 230, 120, 230, 210, 120],[40, 64, 191, 324, 290, 330, 310, 213, 180, 200, 180, 79]],quarter: [[23, 75, 12, 97, 21, 67, 98, 21, 43, 64, 76, 38],[43, 31, 65, 23, 78, 21, 82, 64, 43, 60, 19, 34]],month: [[34, 87, 32, 76, 98, 12, 32, 97, 39, 36, 29, 36],[56, 43, 21, 56, 87, 43, 12, 43, 43, 54, 12, 98]],week: [[43, 73, 62, 54, 91, 54, 84, 43, 86, 43, 54, 53],[32, 54, 34, 87, 32, 45, 62, 68, 93, 54, 54, 24]],
}
// 切换筛选条件
const handleChange = (item) => {currentFilter.value = item.typeconst option = {series: [{data: seriesData[currentFilter.value][0],},{data: seriesData[currentFilter.value][1],}]}instance.value.setOption(option)
}
const timer = ref(null)
const curIndex = ref(0)
// 设置tab栏自动切换
const startInterval = () => {clearInterval(timer.value)timer.value = setInterval(() => {curIndex.value++if (curIndex.value > filter.length - 1) {curIndex.value = 0}handleChange(filter[curIndex.value])}, 2000)
}
// 初始化图表
const initChart = async () => {if (!lineRef.value) return// 初始化图表实例instance.value = echarts.init(lineRef.value)// 配置图标参数const option = {color: ['#00f2f1', '#ed3f35'],tooltip: {trigger: 'axis'},legend: {data: ['预期销售额', '实际销售额'],right: '10%',textStyle: {color: '#4c9bfd'}},grid: {top: '20%',left: '3%',right: '4%',bottom: '3%',show: true,//显示边距borderColor: '#012f4a',//边框颜色containLabel: true//包含刻度文字在内},xAxis: {type: 'category',boundaryGap: false,//去除轴内间距data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],axisTick: {show: false//去除刻度线},axisLabel: {color: '#4c9bfd'//刻度标签颜色},axisLine: {show: false//去除x坐标轴的颜色}},yAxis: {type: 'value',axisTick: {show: false//去除刻度线},axisLabel: {color: '#4c9bfd'//刻度标签颜色},splitLine: {lineStyle: {color: '#012f4a'//分割线颜色}}},series: [{name: '预期销售额',type: 'line',data: seriesData[currentFilter.value][0],smooth: true},{name: '实际销售额',type: 'line',data: seriesData[currentFilter.value][1],smooth: true}]};// 配置图表instance.value.setOption(option)
}
const resizeHandler = () => {instance.value && instance.value.resize()
}
onMounted(() => {initChart()startInterval()// 监听窗口大小变化事件window.addEventListener('resize', resizeHandler)// 监听鼠标移入事件salesRef.value.addEventListener('mouseover', () => {clearInterval(timer.value)//清除定时器})// 监听鼠标移出事件salesRef.value.addEventListener('mouseout', () => {curIndex.value = filter.findIndex(item => item.type === currentFilter.value)startInterval()//重新启动定时器})
})
onUnmounted(() => {// 在组件销毁的时候注销注册的回调函数window.removeEventListener('resize', resizeHandler)instance.value && instance.value.dispose()// 清除定时器timer.value && clearInterval(timer.value)
})
</script><style lang="scss" scoped>
.sales {height: 3.1rem;.inner {display: flex;flex-direction: column;}.header {display: flex;font-size: 0.25rem;color: #4c9bfd;h3 {height: 0.025rem;padding-right: .225rem;border-right: 0.025rem solid #00f2f1;}span {// margin-left: .4rem;margin: -0.0375rem 0 0 0.2625rem;padding: 0.05rem;font-size: 0.2rem;border-radius: 0.0375rem;&.active {background: #4c9bfd;color: #fff;}}}.chart {flex: 1;position: relative;padding-top: 0.1875rem;.label {position: absolute;left: 0.525rem;top: 0.225rem;font-size: 0.175rem;color: #4c9bfd;}.cleft {width: 100%;height: 100%;}.cright {padding: .45rem .375rem;display: flex;flex-direction: column;justify-content: space-between;width: 1.72rem;border-radius: 0 .5rem 0 .5rem;background: #030A28;.item {font-size: .2rem;color: #4c9bfd;h4 {margin-bottom: .15rem;font-size: .35rem;color: #fff;}}}}
}
</style>
4.9 渠道分布
<template><divclass="panel channel"ref="salesRef"><div class="inner"><h3>渠道分布</h3><div class="chart"><!-- 柱状图 --><divclass="cleft"ref="radarRef"></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
const radarRef = ref(null)
const instance = shallowRef(null)
// 初始化图表
const initChart = async () => {// 初始化图表实例instance.value = echarts.init(radarRef.value)// 配置图标参数const option = {radar: {shape: 'circle',center: ['50%', '60%'],radius: '60%',//外半径占据容器大小splitArea: {show: false},splitNumber: 4,//指示器轴的分割段数// 坐标轴在grid区域的分割线(圆圈)splitLine: {lineStyle: {color: 'rgba(255, 255, 255, 0.3)'}},// 坐标轴轴线相关设置(竖线)axisLine: {show: true,lineStyle: {color: 'rgba(255, 255, 255, 0.3)'}},axisName: {color: '#4c9bfd',//雷达图坐标轴文本颜色},axisNameGap: 10,//指示器名称和指示器轴的距离indicator: [{ name: '机场', max: 100 },{ name: '商场', max: 100 },{ name: '火车站', max: 100 },{ name: '汽车站', max: 100 },{ name: '地铁', max: 100 },]},tooltip: {//提示框show: true,position: ['60%', '10%']//提示框位置},series: [{name: '北京',type: 'radar',areaStyle: {//区域填充样式color: 'rgba(238, 197, 102, 0.6)'},lineStyle: {//区域填充的线条样式width: 1,opacity: 0.5,color: '#fff'},//拐点样式,取值:rect(长方形),circle(圆形),triangle(三角形),diamond(菱形),none(无)// symbol: 'circle',//默认是圆点symbolSize: 4,//拐点大小itemStyle: {//拐点样式color: '#fff'},label: {//在圆点上显示相关数据show: true,color: '#fff',fontSize: 10},data: [[55, 19, 56, 11, 34]]}]};// 配置图表instance.value.setOption(option)
}
const resizeHandler = () => {instance.value && instance.value.resize()
}
onMounted(() => {initChart()// 监听窗口大小变化事件window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {// 在组件销毁的时候注销注册的回调函数window.removeEventListener('resize', resizeHandler)instance.value && instance.value.dispose()
})
</script><style lang="scss" scoped>
.channel {margin-right: 0.25rem;flex: 1;height: 2.9rem;.inner {display: flex;flex-direction: column;}h3 {height: 0.025rem;padding-right: .225rem;border-right: 0.025rem solid #00f2f1;}h4 {margin-bottom: 0.0625rem;font-size: 0.4rem;color: #fff;}.chart {flex: 1;padding-top: 0.1875rem;.cleft {height: 100%;}}
}
</style>
4.10 季度销售进度
<template><divclass="panel quarter"ref="salesRef"><div class="inner"><h3>一季度销售进度</h3><div class="chart"><!-- 柱状图 --><divclass="cleft"ref="gaugeRef"></div><div class="data"><div class="ditem"><h4>1,321</h4><span><i class="dot dot2"></i>销售额(万元)</span></div><div class="ditem"><h4>150%</h4><span><i class="dot dot3"></i>同比增长</span></div></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
import * as echarts from 'echarts'
const gaugeRef = ref(null)
const instance = shallowRef(null)
// 初始化图表
const initChart = async () => {// 初始化图表实例instance.value = echarts.init(gaugeRef.value)// 配置图标参数const option = {series: [{type: 'gauge',startAngle: 180,//刻度起始角度endAngle: 0,//刻度结束角度min: 0,//刻度最小值max: 100,//刻度最大值progress: {//进度条show: true,width: 8,//刻度线宽度itemStyle: {// 渐变方向:从上到下color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#00c9e0' },{ offset: 1, color: '#005fc1' }])}},axisLine: { // 背景条的宽度和颜色lineStyle: {width: 8,color: [[1, '#12274d']] // 背景条的颜色}},pointer: {show: false // 隐藏指针},axisTick: {show: false // 隐藏刻度线},axisLabel: {show: false // 隐藏刻度标签},splitLine: {show: false},data: [{ value: 50 }],detail: {valueAnimation: true,formatter: '{value}%',color: '#fff',fontSize: '0.3rem',fontWeight: 400,offsetCenter: [0, '-30%'] // 调整数值显示的位置},radius: '145%',center: ['50%', '90%']}]};// 配置图表instance.value.setOption(option)
}
const resizeHandler = () => {instance.value && instance.value.resize()
}
onMounted(() => {initChart()// 监听窗口大小变化事件window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {// 在组件销毁的时候注销注册的回调函数window.removeEventListener('resize', resizeHandler)instance.value && instance.value.dispose()
})
</script><style lang="scss" scoped>
.quarter {flex: 1;height: 2.9rem;.inner {display: flex;flex-direction: column;margin: 0 -0.075rem;}h3 {height: 0.025rem;padding-right: .225rem;border-right: 0.025rem solid #00f2f1;}.chart {padding-top: 0.225rem;flex: 1;display: flex;flex-direction: column;justify-content: space-between;.cleft {width: 100%;height: 1.05rem;}.data {display: flex;justify-content: space-between;.ditem {display: flex;flex-direction: column;h4 {margin-bottom: 0.125rem;font-size: 0.3rem;color: #fff;}span {font-size: 0.175rem;color: #4c9bfd;.dot2 {background: #6acca3 !important;}.dot3 {background: #ed3f35 !important;}}}}}
}
</style>
4.11 排行榜
<template><div class="panel rank"><div class="inner"><div class="rleft"><h3>全国热榜</h3><div class="country"><ul><liv-for="(item, index) in data.country":key="index"><iclass="iconfont":class="`icon-${index}`"></i><span>{{ item }}</span></li></ul></div></div><div class="rright"><h3><i>各省热销</i><i>//近30日//</i></h3><divclass="province"ref="proRef"><div class="pleft"><ul><liv-for="(item, index) in data.province":key="item.city":class="{ active: index === curIndex }"@mouseover="curIndex = index"><span>{{ item.city }}</span><span>{{ item.sales }}<iclass="iconfont":class="`icon-${item.isUp ? 'up' : 'down'}`"></i></span></li></ul></div><div class="pright"><ul v-if="data.province"><liv-for="item in data.province[curIndex]?.brands":key="item.name"><span>{{ item.name }}</span><span>{{ item.num }}<iclass="iconfont":class="`icon-${item.isUp ? 'up' : 'down'}`"></i></span></li></ul></div></div></div></div></div>
</template><script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import axios from 'axios'
const data = reactive({country: { one: '可爱多', tow: '哇哈哈', three: '喜之郎' },province: []
})
// 获取数据
const getData = async () => {const res = await axios.get('/data/index.json')data.province = res.data
}
const curIndex = ref(0)
const proRef = ref(null)const timer = ref(null)
const starInterval = () => {clearInterval(timer.value)timer.value = setInterval(() => {curIndex.value++if (curIndex.value > data.province?.length - 1) {curIndex.value = 0}}, 2000)
}
// 初始化图表
onMounted(() => {getData()starInterval()// 鼠标移入的时候清空定时器proRef.value.addEventListener('mouseover', () => {clearInterval(timer.value)})// 鼠标移除的时候重启定时器proRef.value.addEventListener('mouseout', () => {starInterval()})
})
onUnmounted(() => {clearInterval(timer.value)proRef.value.removeEventListener('mouseover', () => {clearInterval(timer.value)})proRef.value.removeEventListener('mouseout', () => {starInterval()})
})
</script><style lang="scss" scoped>
.rank {height: 3.5rem;.inner {display: flex;justify-content: space-between;color: #728AFC;font-size: 0.175rem;.rleft {flex: 1;display: flex;flex-direction: column;.country {flex: 1;ul {padding-left: 0.15rem;margin-top: 0.15rem;display: flex;flex-direction: column;justify-content: space-around;height: 100%;li {display: flex;align-items: center;.iconfont {margin-right: 0.15rem;font-size: 0.45rem;}.icon-one {color: #E44A39;}.icon-tow {color: #77CCFF;}.icon-three {color: #728AFC;}}}}}.rright {flex: 2;display: flex;flex-direction: column;h3 {margin-bottom: 0.2rem;display: flex;justify-content: space-between;i:last-child {font-size: 0.2rem;color: #4c9bfd;}}.province {flex: 1;display: flex;justify-content: space-between;overflow: hidden;.pleft {flex: 1;}ul {display: flex;flex-direction: column;justify-content: space-around;height: 100%;li {padding: 0.13rem;display: flex;justify-content: space-between;&.active {background: rgb(43 54 101 / 50%);}span {i {&.iconfont {font-size: 0.175rem;}&.icon-up {color: #E44A39;}&.icon-down {color: #6acca3;}}}}}.pright {flex: 1;background: rgb(43 54 101 / 50%);li {padding: 0 0.13rem;color: #68d8fe;}}}}}
}
</style>