echarts练手项目部署上线

376 阅读4分钟

项目展示 在线地址

前言 首先声明这不是一个原创项目,这个项目是我在B站上看见的,之所以选它是因为它有文档!,虽然有视频教程,但我觉得看视频学习效率太慢了,不如看文章方便。 本文主要讲我修改的样式的思路,以及部署上线的坑

👉原项目地址

👉B站视频教程

项目简介

项目的技术栈: vue2 + koa2 + websocket + echarts 后端 后端的数据是静态的json文件,通过koa处理请求,运用websocket实现前后端的长链接 前端 前端主要由vue框架构建的,主要通过ScreenPage.vue将六个组件对应的路由合并,每个图都是一个组件 在这里插入图片描述 项目特色

  • 主题切换 在这里插入图片描述

  • 全屏切换 在这里插入图片描述

原项目有详细介绍,本文不在赘述构建过程

在这里插入图片描述

修改样式

热销产品

原项目效果:

在这里插入图片描述

修改过后的样式效果:

在这里插入图片描述

主要实现的思路就是将饼图设置环形图,然后在原始数据的之间添加一个固定的value,并隐藏掉其label,这样做的作用就是在绘制的时候让每个数据之间有一个间隙,发光的效果是将阴影设置为对应颜色的效果

//初始化echarts实例
initChart() {
      const color = [
        "#00ffff",
        "#00cfff",
        "#006ced",
        "#ffe000",
        "#ffa800",
        "#ff5b00",
        "#ff3000",
        "#00FF7F"
      ];

      this.chartInstance = this.$echarts.init(this.$refs.hot_ref, this.theme);
      const initOption = {
        color: color,
        title: {
          text: "▎ 热销商品的占比",
          left: 20,
          top: 20
        },
        series: [ 
          { // 外形坏图
            type: "pie",
            center: ["50%", "50%"],
            label: {
              show: false
            },
            emphasis: {
              label: {
                show: true
              },
              labelLine: {
                show: false
              }
            }
          },
          {// 内圆动画
            type: "pie",
            // radius: ['44%', '45%'],
            center: ["50%", "50%"],
            hoverAnimation: false,
            clockWise: false,
            itemStyle: {
              normal: {
                color: "#0C355E"
              }
            },
            label: {
              show: false
            },
            data: this._dashed()
          }
        ]
      };
      this.chartInstance.setOption(initOption);
    },
updateChart() {
      const color = [
        "#00ffff",
        "#00cfff",
        "#006ced",
        "#ffe000",
        "#ffa800",
        "#ff5b00",
        "#ff3000"
      ];
      // 处理图表需要的数据
      const legendData = this.allData[this.currentIndex].children.map(item => {
        return item.name;
      });
      const totalValue = this.allData[this.currentIndex].value;
      const seriesData = this.allData[this.currentIndex].children.map(item => {
        return {
          name: item.name,
          value: item.value,
          children: item.children 
        };
      });
      let data = [];
      for (var i = 0; i < seriesData.length; i++) {
        data.push(
          {
            value: seriesData[i].value,
            name: seriesData[i].name,
            itemStyle: {
              normal: {
                borderWidth: 5,
                shadowBlur: 20,
                borderColor: color[i],
                shadowColor: color[i]
              }
            }
          },
          {//添加定值
            value: 10000,
            name: "",
            itemStyle: {
              normal: {
                label: {
                  show: false
                },
                labelLine: {
                  show: false
                },
                color: "rgba(0, 0, 0, 0)",
                borderColor: "rgba(0, 0, 0, 0)",
                borderWidth: 0
              }
            }
          }
        );
      }
      const dataOption = {
        // legend: {
        //   data: legendData
        // },
        series: [
          // { data: this._dashed() },
          {
            data: data,
            clockWise: false,
            hoverAnimation: false,
            itemStyle: {
              normal: {
                label: { //标签
                  show: true,
                  position: "outside",
                  // color: "#D3D3D3",
                  formatter: function(params) {
                    let percent = 0;
                    let total = 0;
                    for (let i = 0; i < seriesData.length; i++) {
                      total += seriesData[i].value;
                    }
                    percent = ((params.value / total) * 100).toFixed(0);
                    if (params.name !== "") {
                      return params.name + "\n" + "\n" + percent + "%";
                    } else {
                      return "";
                    }
                  }
                }
              }
            }
            // radius: [55, 70]
          }
        ]
      };
      this.chartInstance.setOption(dataOption);
    },

内圆也是一个饼图,然后定时更新起始角度startAngle的值,从而达到动画的效果

    mounted() {
    this.startTimer();
    }
    doing() {//修改起始角度
      let option = this.chartInstance.getOption();
      option.series[1].startAngle = option.series[1].startAngle - 1;
      this.chartInstance.setOption(option);
    },
    startTimer() {
      setInterval(this.doing, 500);
    },
    _dashed() {//内圆划分60份
      let dataArr = [];
      for (var i = 0; i < 60; i++) {
        if (i % 2 === 0) {
          dataArr.push({
            name: (i + 1).toString(),
            value: 20,
            itemStyle: {
              normal: {
                color: "#23E5E5"
              }
            }
          });
        } else {
          dataArr.push({
            name: (i + 1).toString(),
            value: 20,
            itemStyle: {
              normal: {
                color: "rgb(0,0,0,0)",
                borderWidth: 1,
                borderColor: "#2E72BF"
              }
            }
          });
        }
      }
      return dataArr;
    },

由于它是可以放大的,所以在设置相关图和字的大小的时候,它是通过计算组件的宽度 其他组件也是如此,所以后面就不在讲了

screenAdapter() {
		//设置相对大小
      this.titleFontSize = (this.$refs.hot_ref.offsetWidth / 100) * 3.3;
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: this.titleFontSize
          }
        },

        series: [
          {//外环的相对大小
            radius: [4 * this.titleFontSize, 5 * this.titleFontSize],
            center: ["50%", "50%"],
            itemStyle: {
              normal: {
                label: {
                  fontSize: this.titleFontSize / 1.3
                },
                labelLine: {
                  show: true,
                  color: "#00ffff"
                }
              }
            }
          },
          {//内圆相对大小
            radius: [3.2 * this.titleFontSize, 3.5 * this.titleFontSize]
          }
        ]
      };
      this.chartInstance.setOption(adapterOption);
      this.chartInstance.resize();
      
    }

地图样式

原项目效果:

在这里插入图片描述

修改后的项目效果:

在这里插入图片描述

修改地图的边框样式border的颜色,并设置阴影颜色

async initChart() {
      this.chartInstance = this.$echarts.init(this.$refs.map_ref, this.theme);
      // 获取中国地图的矢量数据
      // http://localhost:8999/static/map/china.json
      // 由于我们现在获取的地图矢量数据并不是位于KOA2的后台, 所以咱们不能使用this.$http
      const ret = await axios.get(
        
         "http://localhost:8999/static/map/china.json"
      );
      this.$echarts.registerMap("china", ret.data);
      const initOption = {
        title: {
          text: "▎ 商家分布",
          left: 20,
          top: 20
        },
        geo: {
          type: "map",
          map: "china",
          top: "5%",
          bottom: "5%",
          itemStyle: {
            normal: {
              areaColor: "#013C62",
              roam: false,
              label: {
                emphasis: {
                  show: false
                }
              },
              layoutSize: "100%",
              // borderColor: "#333",
              borderColor: new echarts.graphic.LinearGradient(
                0,
                0,
                0,
                1,
                [
                  {
                    offset: 0,
                    color: "#00F6FF"
                  }
                  // {
                  //   offset: 1,
                  //   color: "#53D9FF"
                  // }
                ],
                false
              ),
              borderWidth: 2,
              shadowColor: "rgba(10,76,139,1)",
              // shadowOffsetY: 0,
              shadowBlur: 10
            }
          }
        },
        legend: {
          left: "5%",
          bottom: "5%",
          orient: "vertical"
        }
      };
      this.chartInstance.setOption(initOption);
      this.chartInstance.on("click", async arg => {
        // arg.name 得到所点击的省份, 这个省份他是中文
        const provinceInfo = getProvinceMapInfo(arg.name);
        console.log(provinceInfo);
        // 需要获取这个省份的地图矢量数据
        // 判断当前所点击的这个省份的地图矢量数据在mapData中是否存在
        //http://localhost:8999/static/map/china.json
        if (!this.mapData["http://localhost:8999" + provinceInfo.key]) {
          const ret = await axios.get(provinceInfo.path);
          this.mapData[provinceInfo.key] = ret.data;
          this.$echarts.registerMap(provinceInfo.key, ret.data);
        }
        const changeOption = {
          geo: {
            map: provinceInfo.key
           
          }
        };
        this.chartInstance.setOption(changeOption);
      });
    },

这个地图加载的是本地的,如果要部署到服务器上的记得将"http://localhost:8999"直接改为域名

库存销量

原效果:

在这里插入图片描述

修改后的效果:

在这里插入图片描述

这个效果我其实觉得就比原来强点,本来想做下面的效果

在这里插入图片描述

但是这个项目有5个数据要画,如果全部都是这样的话,缩在右下角的时候有点拥挤,所以偷了点懒,只在中间加了个饼图修改了一下原样式和颜色

updateChart() {
      const centerArr = [
        ["18%", "40%"],
        ["50%", "40%"],
        ["82%", "40%"],
        ["34%", "75%"],
        ["66%", "75%"]
      ];
      const colorArr = [
        ["#ffccff", "#ff00ff"],
        ["#99ff99", "#00ff00"],
        ["#ffff99", "#ff9900"],
        ["#FFC0CB", "#FF3E96"],
        ["#23E5E5", "#2E72BF"]
      ];
      // 处理图表需要的数据
      const start = this.currentIndex * 5;
      const end = (this.currentIndex + 1) * 5;
      const showData = this.allData.slice(start, end);
      let tdata = [];
      for (var i = 0; i < showData.length; i++) {
        let item = showData[i];
        tdata.push(
          {// 内圆
            type: "pie",
            center: centerArr[i],
            data: [
              {
                name: item.name + "\n\n" + item.sales,
                value: item.sales,
                label: {
                  position: "center",
                  fontFamily: "DINAlternate-Bold",
                  color: "#000000"
                },
                // value: 100,
                itemStyle: {
                  normal: {
                  //阴影 发光效果
                    shadowBlur: 20,
                    shadowColor: colorArr[i][1],
                    color: colorArr[i][0]
                  }
                }
              }
            ]
          },
          {// 外圆
            type: "pie",
            center: centerArr[i],
            hoverAnimation: false, // 关闭鼠标移入到饼图时的动画效果
            labelLine: {
              show: false // 隐藏指示线
            },
            label: {
              show: false,
              position: "center",
              fontFamily: "DINAlternate-Bold"
              // color: colorArr[i][1]
            },
            data: [
              {
                name: item.name + "\n\n" + item.sales,
                value: item.sales,
                itemStyle: {
                  borderWidth: 5,
                  color: new this.$echarts.graphic.LinearGradient(0, 1, 0, 0, [
                    {
                      offset: 0,
                      color: colorArr[i][0]
                    },
                    {
                      offset: 1,
                      color: colorArr[i][1]
                    }
                  ])
                }
              }
              // { 
              //   value: item.stock,
              //   itemStyle: {
              //     color: "#333843"
              //   }
              // }
            ]
          }
        );
      }
      const dataOption = {
        series: tdata
      };
      this.chartInstance.setOption(dataOption);
    },


因为新加了元素,所以在控制缩放的时候要更改

    screenAdapter() {
      const titleFontSize = (this.$refs.stock_ref.offsetWidth / 100) * 3.6;
      const innerRadius = titleFontSize * 2.6;
      const outterRadius = innerRadius * 1.2;
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize
          }
        },
        series: [
          {//内圆
            type: "pie",
            radius: [innerRadius * 0.9, 0],
            label: {
              fontSize: titleFontSize / 1.5
            },
            z: 1
          },
          { //外圆
            type: "pie",
            radius: [outterRadius, innerRadius]
          },
          ....//此处还有4套内外圆
      this.chartInstance.setOption(adapterOption);
      this.chartInstance.resize();

注意添加元素的时候是由内向外的,先加内圆再外圆

销售排行

原效果:

在这里插入图片描述

修改后的样式:

在这里插入图片描述

由于此处的样式和左边的商家销售的样式重复了,所以就修改了一下样式

updateChart() {
      const colorArr = [
        ["#0BA82C", "#4FF778"],
        ["#2E72BF", "#23E5E5"],
        ["#5052EE", "#AB6EE5"]
      ];
      // 处理图表需要的数据
      // 所有省份所形成的数组
      const provinceArr = this.allData.map(item => {
        return item.name;
      });
      // 所有省份对应的销售金额
      const valueArr = this.allData.map(item => {
        return item.value;
      });
      const dataOption = {
        xAxis: {
          data: provinceArr,
          axisLine: {
            show: false //不显示x轴
          },
          axisTick: {
            show: false //不显示刻度
          }
        },
        dataZoom: {
          show: false,
          startValue: this.startValue,
          endValue: this.endValue
        },
        series: [
          {
            //柱底圆片
            name: "",
            type: "pictorialBar",
            symbolSize: [30, 18], //调整截面形状
            symbolOffset: [0, 10],//调整截面位置
            z: 12,
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: "rgba(89,211,255,1)"
                  },
                  {
                    offset: 1,
                    color: "rgba(23,237,194,1)"
                  }
                ])
              }
            },
            data: valueArr
          },

          //柱体
          {
            name: "",
            type: "bar",
            barWidth: 30,
            barGap: "0%",
            itemStyle: {
              normal: {
                color: {
                  x: 0,
                  y: 0,
                  x2: 0,
                  y2: 1,
                  type: "linear",
                  global: false,
                  colorStops: [
                    {
                      //第一节下面
                      offset: 0,
                      color: "rgba(0,255,245,0.5)"
                    },
                    {
                      offset: 1,
                      color: "#43bafe"
                    }
                  ]
                }
              }
            },

            data: valueArr
          },

          //柱顶圆片
          {
            name: "",
            type: "pictorialBar",
            symbolSize: [30, 18], //调整截面形状
            symbolOffset: [0, -10],
            z: 12,
            symbolPosition: "end",
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(
                  0,
                  0,
                  0,
                  1,
                  [
                    {
                      offset: 0,
                      color: "rgba(89,211,255,1)"
                    },
                    {
                      offset: 1,
                      color: "rgba(23,237,194,1)"
                    }
                  ],
                  false
                )
              }
            },
            data: valueArr
          }
        ]
      };
      this.chartInstance.setOption(dataOption);
    },

部署上线

部署上线对于我这个小白可谓艰难啊,

本地运行

npm run build打包后的dist文件内是不能直接打开的需要服务器才可以

安装server
npm isntall -g server
切换到项目文件
serve -s dist

PM2

PM2是进程管理器,入门非常简单,在服务器上用起来非常方便

需要配置node的环境

//安装pm2
npm install pm2@latest -g

//启动node应用
pm2 start app.js

//查看pm2 的进程
pm2 list

// 暂停进程名为app的
pm2 stop app

// 删除
pm2 delete app

// 查看日志
pm2 logs

后端代码结构

在这里插入图片描述

将后端上传到服务器上,然后链接到服务器终端,启动后端程序

pm2 start app.js

在这里插入图片描述

Nignx 反向代理

我的服务器是阿里云的Centos7,之前装个人博客已经手动配置过Nignx,所以只要在安装Nginxconf.d的文件下新建一个mycharts.conf的文件

server {
    listen       80;
    server_name  myecharts.xrtning.cn;    #此处使用测试域名。实际配置中使用您的服务器域名。

    
    access_log  /var/log/nginx/b.access.log  main;

    location / {
        root   /online/vision/dist;    #测试站点路径。即的项目代码路径。
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection  “upgrade”;
    }

    #error_page  404              /404.html;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

重启ngnix服务

systemctl restart ngnix

bug处理

WebSocket connection to ‘ws://localhost:9998/‘ failed: Error in connection establish

首先在打包前,要将localhost改为服务器的ip地址,并且要给服务器添加安全组打开9998端口,但是还是链接不上,然后我关闭了服务器的防火墙,发现可以连上后台的数据

systemctl stop firewalld.service

在这里插入图片描述

关闭防火墙肯定是危险的行为,所以要重新打开防火墙,然后在服务器的终端上手动打开9998端口

systemctl start firewalld

添加端口9998:firewall-cmd --zone=public --add-port=9998/tcp --permanent

更新防火墙规则: firewall-cmd --reload

重启服务:systemctl restart firewalld.service

查看所有打开的端口: firewall-cmd --zone=public --list-ports