全国图书资源数据可视化大屏-vue纯前端静态页面项目练习

80 阅读6分钟

学习前端还是非常有意思的,因为前端真的是可见即所得,可以做出来非常好看漂亮的页面,最近我就在使用前端技术 做一些大屏报表,在制作这些大屏报表过程中,又熟练的练习了自己的学到的相关的前端技术,接下来把做出来的效果 分享给大家。主要做的是一个图书资源相关的数据大屏展示。

使用的技术是vue2 + element-ui

· Vue2

· Element-UI

· ECharts

· Node版本 16.20

安装依赖:npm i

运行项目:npm run serve

打包项目:npm run build

介绍了使用的技术后,接下来给大家看一下页面效果

image.png

因为是练习项目,为了熟练的对ECharts插件的使用。只做了一个页面。

部分代码

<template>
  <div class="home">
    <Header />
    <div class="dashboard-container">
      <!-- 左侧面板 -->
      <div class="left-panel">
        <div class="panel-item">
          <div class="panel-header">图书分类统计</div>
          <div class="chart-container" ref="categoryChart"></div>
        </div>
        <div class="panel-item">
          <div class="panel-header">读者年龄分布</div>
          <div class="chart-container" ref="ageChart"></div>
        </div>
      </div>
      
      <!-- 中间面板 -->
      <div class="center-panel">
        <div class="panel-item map-container">
          <div class="panel-header">全国图书馆分布</div>
          <div class="chart-container" ref="mapChart"></div>
        </div>
        <div class="data-overview">
          <div class="data-card" v-for="(item, index) in dataCards" :key="index">
            <div class="data-icon" :style="{backgroundColor: item.color}">
              <i :class="item.icon"></i>
            </div>
            <div class="data-info">
              <div class="data-title">{{item.title}}</div>
              <div class="data-value">{{item.value}}</div>
            </div>
          </div>
        </div>
      </div>
      
      <!-- 右侧面板 -->
      <div class="right-panel">
        <div class="panel-item">
          <div class="panel-header">借阅趋势分析</div>
          <div class="chart-container" ref="trendChart"></div>
        </div>
        <div class="panel-item">
          <div class="panel-header">热门排行榜</div>
          <div class="rank-list-container">
            <div class="rank-list-scroll" ref="rankList">
              <div class="rank-item" v-for="(book, index) in hotBooks" :key="index">
                <div class="rank-number" :class="{'top-three': index < 3}">{{index + 1}}</div>
                <div class="book-info">
                  <div class="book-name">{{book.name}}</div>
                  <div class="book-author">{{book.author}}</div>
                </div>
                <div class="book-borrow-count">{{book.count}}次</div>
              </div>
              <!-- 复制一份列表用于无缝滚动 -->
              <div class="rank-item" v-for="(book, index) in hotBooks" :key="'copy-'+index">
                <div class="rank-number" :class="{'top-three': index < 3}">{{index + 1}}</div>
                <div class="book-info">
                  <div class="book-name">{{book.name}}</div>
                  <div class="book-author">{{book.author}}</div>
                </div>
                <div class="book-borrow-count">{{book.count}}次</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <Footer />
  </div>
</template>

<script>
/**
 * 作者:json
 */
import Header from '../components/Header.vue'
import Footer from '../components/Footer.vue'
import { echarts, createChart, getCommonOptions, getGradientColor } from '../utils/echarts-helper'
import 'echarts/map/js/china'

export default {
  name: 'Home',
  components: {
    Header,
    Footer
  },
  data() {
    return {
      // 数据卡片
      dataCards: [
        { title: '图书总量', value: '2,568,321', icon: 'el-icon-reading', color: '#1890ff' },
        { title: '读者总数', value: '1,245,689', icon: 'el-icon-user', color: '#52c41a' },
        { title: '今日借阅', value: '12,456', icon: 'el-icon-date', color: '#faad14' },
        { title: '电子资源', value: '856,932', icon: 'el-icon-document', color: '#f5222d' }
      ],
      // 热门排行
      hotBooks: [
        { name: '001', author: 'xxx作者1', count: 12568 },
        { name: '002', author: 'xxx作者2', count: 10234 },
        { name: '003', author: 'xxx作者3', count: 9872 },
        { name: '004', author: 'xxx作者4', count: 8765 },
        { name: '005', author: 'xxx作者5', count: 7654 },
        { name: '006', author: 'xxx作者6', count: 6543 },
        { name: '007', author: 'xxx作者7', count: 5432 },
        { name: '008', author: 'xxx作者8', count: 4321 }
      ],
      // 图表实例
      charts: {}
    }
  },
  mounted() {
    this.initCharts()
    // 窗口大小变化时重新调整图表大小
    window.addEventListener('resize', this.resizeCharts)
    
    // 初始化排行榜滚动暂停功能
    this.$nextTick(() => {
      this.initRankListScroll()
    })
  },
  beforeDestroy() {
    // 组件销毁前移除事件监听
    window.removeEventListener('resize', this.resizeCharts)
    
    // 移除排行榜滚动相关的事件监听
    if (this.$refs.rankList) {
      this.$refs.rankList.removeEventListener('mouseenter', this.pauseRankScroll)
      this.$refs.rankList.removeEventListener('mouseleave', this.resumeRankScroll)
    }
    
    // 销毁图表实例
    Object.keys(this.charts).forEach(key => {
      if (this.charts[key]) {
        this.charts[key].dispose()
      }
    })
  },
  methods: {
    // 初始化所有图表
    initCharts() {
      this.$nextTick(() => {
        this.initCategoryChart()
        this.initAgeChart()
        this.initMapChart()
        this.initTrendChart()
      })
    },
    // 图书分类统计图表
    initCategoryChart() {
      const { chart } = createChart(this.$refs.categoryChart)
      this.charts.category = chart
      
      const option = {
        ...getCommonOptions(),
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          right: 10,
          top: 'center',
          textStyle: { color: '#fff' }
        },
        series: [
          {
            name: '图书分类',
            type: 'pie',
            radius: ['50%', '70%'],
            avoidLabelOverlap: false,
            label: {
              show: false,
              position: 'center'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: '18',
                fontWeight: 'bold'
              }
            },
            labelLine: {
              show: false
            },
            data: [
              { value: 1048, name: '文学' },
              { value: 735, name: '历史' },
              { value: 580, name: '科技' },
              { value: 484, name: '艺术' },
              { value: 300, name: '教育' },
              { value: 200, name: '经济' }
            ],
            itemStyle: {
              borderWidth: 2,
              borderColor: 'rgba(255, 255, 255, 0.1)'
            }
          }
        ]
      }
      
      chart.setOption(option)
    },
    // 读者年龄分布图表
    initAgeChart() {
      const { chart } = createChart(this.$refs.ageChart)
      this.charts.age = chart
      
      const option = {
        ...getCommonOptions(),
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        xAxis: [
          {
            type: 'category',
            data: ['<18岁', '18-25岁', '26-35岁', '36-45岁', '46-60岁', '>60岁'],
            axisTick: {
              alignWithLabel: true
            }
          }
        ],
        yAxis: [
          {
            type: 'value'
          }
        ],
        series: [
          {
            name: '读者人数',
            type: 'bar',
            barWidth: '60%',
            data: [15000, 42000, 35000, 28000, 16000, 9000],
            itemStyle: {
              color: getGradientColor('rgba(131, 191, 246, 1)', 'rgba(24, 141, 240, 1)')
            }
          }
        ]
      }
      
      chart.setOption(option)
    },
    // 全国图书馆分布地图
    initMapChart() {
      const { chart } = createChart(this.$refs.mapChart)
      this.charts.map = chart
      
      // 模拟数据
      const data = [
        { name: '北京', value: 156 },
        { name: '天津', value: 89 },
        { name: '上海', value: 178 },
        { name: '重庆', value: 95 },
        { name: '河北', value: 125 },
        { name: '河南', value: 132 },
        { name: '云南', value: 86 },
        { name: '辽宁', value: 93 },
        { name: '黑龙江', value: 78 },
        { name: '湖南', value: 112 },
        { name: '安徽', value: 96 },
        { name: '山东', value: 145 },
        { name: '新疆', value: 56 },
        { name: '江苏', value: 158 },
        { name: '浙江', value: 168 },
        { name: '江西', value: 82 },
        { name: '湖北', value: 105 },
        { name: '广西', value: 76 },
        { name: '甘肃', value: 58 },
        { name: '山西', value: 72 },
        { name: '内蒙古', value: 63 },
        { name: '陕西', value: 89 },
        { name: '吉林', value: 65 },
        { name: '福建', value: 98 },
        { name: '贵州', value: 71 },
        { name: '广东', value: 172 },
        { name: '青海', value: 42 },
        { name: '西藏', value: 35 },
        { name: '四川', value: 125 },
        { name: '宁夏', value: 38 },
        { name: '海南', value: 59 },
        { name: '台湾', value: 86 },
        { name: '香港', value: 32 },
        { name: '澳门', value: 12 }
      ]
      
      const option = {
        ...getCommonOptions(),
        tooltip: {
          trigger: 'item',
          formatter: '{b}<br/>图书馆数量:{c}'
        },
        visualMap: {
          min: 0,
          max: 200,
          left: 'left',
          top: 'bottom',
          text: ['高', '低'],
          inRange: {
            color: ['#e0f7fa', '#4fc3f7', '#0288d1']
          },
          textStyle: {
            color: '#fff'
          }
        },
        series: [
          {
            name: '图书馆数量',
            type: 'map',
            map: 'china',
            roam: true,
            emphasis: {
              label: {
                show: true
              }
            },
            itemStyle: {
              areaColor: '#0f1c3c',
              borderColor: 'rgba(65, 120, 255, 0.5)',
              borderWidth: 1
            },
            data: data
          }
        ]
      }
      
      chart.setOption(option)
    },
    // 借阅趋势分析图表
    initTrendChart() {
      const { chart } = createChart(this.$refs.trendChart)
      this.charts.trend = chart
      
      const option = {
        ...getCommonOptions(),
        tooltip: {
          trigger: 'axis'
        },
        legend: {
          data: ['实体图书', '电子图书'],
          textStyle: {
            color: '#fff'
          }
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '实体图书',
            type: 'line',
            stack: '总量',
            smooth: true,
            symbol: 'circle',
            symbolSize: 5,
            showSymbol: false,
            lineStyle: {
              width: 2
            },
            areaStyle: {
              color: getGradientColor('rgba(80, 141, 255, 0.8)', 'rgba(80, 141, 255, 0.1)')
            },
            emphasis: {
              focus: 'series'
            },
            data: [12000, 13200, 15100, 14500, 13800, 14900, 18000, 21000, 20500, 19200, 18300, 23500]
          },
          {
            name: '电子图书',
            type: 'line',
            stack: '总量',
            smooth: true,
            symbol: 'circle',
            symbolSize: 5,
            showSymbol: false,
            lineStyle: {
              width: 2
            },
            areaStyle: {
              color: getGradientColor('rgba(255, 180, 0, 0.8)', 'rgba(255, 180, 0, 0.1)')
            },
            emphasis: {
              focus: 'series'
            },
            data: [8200, 9320, 9010, 10340, 12800, 14300, 15200, 16500, 17800, 18100, 19500, 22000]
          }
        ]
      }
      
      chart.setOption(option)
    },
    // 调整图表大小
    resizeCharts() {
      Object.keys(this.charts).forEach(key => {
        this.charts[key].resize()
      })
    },
    
    // 初始化排行榜滚动
    initRankListScroll() {
      if (this.$refs.rankList) {
        // 添加鼠标悬停暂停滚动的事件
        this.$refs.rankList.addEventListener('mouseenter', this.pauseRankScroll)
        this.$refs.rankList.addEventListener('mouseleave', this.resumeRankScroll)
      }
    },
    
    // 暂停排行榜滚动
    pauseRankScroll() {
      if (this.$refs.rankList) {
        this.$refs.rankList.style.animationPlayState = 'paused'
      }
    },
    
    // 恢复排行榜滚动
    resumeRankScroll() {
      if (this.$refs.rankList) {
        this.$refs.rankList.style.animationPlayState = 'running'
      }
    }
  }
}
</script>

<style scoped>
.home {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #0f1c3c;
  color: white;
  overflow: hidden;
}

.dashboard-container {
  flex: 1;
  display: flex;
  padding: 10px;
  overflow: hidden;
}

.left-panel, .right-panel {
  width: 25%;
  display: flex;
  flex-direction: column;
}

.center-panel {
  width: 50%;
  display: flex;
  flex-direction: column;
}

.panel-item {
  flex: 1;
  margin: 5px;
  background-color: rgba(13, 28, 61, 0.7);
  border: 1px solid rgba(65, 120, 255, 0.3);
  border-radius: 4px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.panel-header {
  height: 40px;
  line-height: 40px;
  padding: 0 15px;
  font-size: 16px;
  font-weight: bold;
  background: linear-gradient(to right, rgba(15, 67, 132, 0.8), rgba(15, 67, 132, 0.2));
  border-bottom: 1px solid rgba(65, 120, 255, 0.3);
}

.chart-container {
  flex: 1;
  width: 100%;
}

.map-container {
  flex: 2;
}

.data-overview {
  display: flex;
  flex-wrap: wrap;
  margin: 5px;
}

.data-card {
  flex: 1;
  min-width: calc(50% - 10px);
  height: 100px;
  margin: 5px;
  background-color: rgba(13, 28, 61, 0.7);
  border: 1px solid rgba(65, 120, 255, 0.3);
  border-radius: 4px;
  display: flex;
  align-items: center;
  padding: 0 15px;
}

.data-icon {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 15px;
  font-size: 24px;
  color: white;
}

.data-info {
  flex: 1;
}

.data-title {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
  margin-bottom: 5px;
}

.data-value {
  font-size: 22px;
  font-weight: bold;
}

.rank-list-container {
  flex: 1;
  padding: 10px 15px;
  overflow: hidden;
  position: relative;
}

.rank-list-scroll {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  padding: 5px 0;
  animation: scrollRank 30s linear infinite;
}

@keyframes scrollRank {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-50%);
  }
}

/* 当鼠标悬停在容器上时,添加阴影效果 */
.rank-list-container:hover {
  box-shadow: inset 0 0 10px rgba(65, 120, 255, 0.3);
}

.rank-item {
  display: flex;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  transition: background-color 0.3s;
}

.rank-item:hover {
  background-color: rgba(65, 120, 255, 0.1);
}

.rank-number {
  width: 24px;
  height: 24px;
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 10px;
  font-size: 14px;
}

.top-three {
  background-color: #f5a623;
  color: #fff;
}

.book-info {
  flex: 1;
}

.book-name {
  font-size: 14px;
  margin-bottom: 3px;
}

.book-author {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
}

.book-borrow-count {
  font-size: 14px;
  color: #f5a623;
}

/* 自定义滚动条 */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.1);
  border-radius: 3px;
}

::-webkit-scrollbar-thumb {
  background: rgba(65, 120, 255, 0.5);
  border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
  background: rgba(65, 120, 255, 0.8);
}
</style>

如果对这个源码有兴趣可以去看看~ 源码地址:

wwwoop.com/home/Index/…