参考源码地址:https://gitee.com/xiaoqiang001/online-retailers

知识点:

Vue、Vuex、Koa2、WebSocket

一、Koa2

koa2是继express之后,node的又一个主流的web框架,相比于express,koa只保留了核心的中间件处理逻辑,去掉了路由、模版以及一些其他的功能,是一个基于node实现的web框架,它所有的功能都是通过插件的形式来实现的。

安装koa

npm init -y
npm install koa

编写app.js

// 1、创建koa对象
const Koa = require('koa')
const app = new Koa()// 2、编写响应函数(中间件)
// ctx:上下文,web容器,ctx.request ctx.response
// next:下一个中间件,下一层中间件是否能够得到执行,取决于next这个函数有没有被调用
//注意:这里的next函数得到的是Promise对象,拿到对象里面的内容需要用async和await
app.use(async(ctx,next) => {console.log("第一层中间件")ctx.response.body = 'hello world'await next()
})
app.use(async(ctx,next) => {console.log("第二层中间件")await next()
})
app.use(async(ctx,next) => {console.log("第三层中间件")
})
// 3、绑定端口号,node.js里面有
app.listen(3000)//终端输出
第一层中间件
第二层中间件
第三层中间件
//浏览器输出
hello world

启动服务器

node app.js

二、后台开始

1、计算服务器消耗时长的中间件

module.exports > exports
module.exports对象是由模块系统创建的,自己写模块,需要在模块最后写好模块接口,
声明这个模块对外暴露什么内容,module.exports 提供了暴露接口的方法。

// 计算服务器消耗时长的中间件
// 该中间件必须是第一层,因为要计算服务器消耗时长module.exports = async (ctx, next) => {/*1、记录开始时间2、内层中间件执行完3、记录结束时间4、ctx.set设置响应头 X-Response-Time*/const start = Date.now()await next()const end = Date.now()let duration = end - startctx.set('X-Response-Time', duration + 'ms')
}

2、用来设置响应头的中间件

// 用来设置响应头的中间件
module.exports = async (ctx, next) => {//告诉浏览器,给它的是json格式的字符串,编码是UTF-8const contentType = 'application/json; charset=UTF-8'ctx.set('Content-Type', contentType)//ctx.response.body = '{"success": true}'// 允许跨域ctx.set('Access-Control-Allow-Origin', '*')ctx.set('Access-Control-Allow-Methods', 'OPTIONS, GET, PUT, POST, DELETE')await next()
}

3、处理业务逻辑的中间件,读取某个json文件并返回

// 处理业务逻辑的中间件,读取某个json文件并返回
const fs = require('fs')
const path = require('path')
const {getFileJsonData
} = require('../utils/file_utils')
module.exports = async (ctx, next) => {const url = ctx.request.urllet filePath = url.replace('/api', '')filePath = `../data/${filePath}.json`let fullPath = path.join(__dirname, filePath) // 保证动态的绝对路径// 保证后台不崩溃,就需要用try-catchtry {let data = await getFileJsonData(fullPath)ctx.response.body = data} catch (error) {// ctx.response.body = {//     message: '文件不存在'// }const errorMsg = {message: '读取文件内容失败,文件资源不存在',status: 404}ctx.response.body = JSON.stringify(errorMsg)} await next()
}

…/utils/file_utils.js

// 读取文件的工具
const fs = require('fs')
module.exports.getFileJsonData = (fullPath) => {// 异步任务需要通过promise拿到数据,无法直接返回fs.readFile结果的return new Promise((resolve, reject) => {fs.readFile(fullPath, 'UTF-8', (err, data) => {if (err) {reject('文件不存在')} else {resolve(data)}})})
}

三、前端开始

1、devServer对默认服务器端口号进行修改

module.exports = {devServer: {port: 3000, //端口号配置open: true, //自动打开浏览器}
}

2、echarts.js使用

1、在index.html里面引入echarts.js
<!-- 一旦通过script标签引入echarts文件,window.echarts,在window下面就有一个echarts对象了--><script src="./static/lib/echarts.min.js"></script>2、在main.js中,将全局的echarts对象挂载到Vue的原型对象上
//使用的时候 this.$echarts
Vue.prototype.$echarts = window.echarts

3、axios请求基准路径的配置

import axios from 'axios'//http://localhost:3000这个就是后台端口
axios.defaults.baseURL = 'http://localhost:3000/api/'
// 将axios挂载到vue的原型对象上
// 使用 this.$http
Vue.prototype.$http = axios

4、主题切换

在index.html里面引入chalk.js
<script src="./static/theme/chalk.js"></script>1、数据的存储,在vuex文件中
export default new Vuex.Store({state: {theme: 'chalk'},mutations: {changeTheme(state ) {if(state.theme === 'chalk') {state.theme = 'vintage'} else {state.theme = 'chalk'}}},
})2、点击切换按钮,修改vuex中的theme数据changeTheme() {// 修改vuex中的数据this.$store.commit('changeTheme')}3、各个组件监听theme的变化,将vuex中的theme数据映射为每一个组件的计算属性
import { mapState } from "vuex";
computed: {...mapState(['theme'])
},//通过watch来监听属性是否发生了变化
watch: {theme () {this.chartInstance.dispose() //销毁当前图表this.initChart()//重新初始化图表对象this.screenAdapter() //完成屏幕适配this.updateChart() //更新图表的展示}
}
修改每个组件里面的initChart()
this.chart = this.$echarts.init(this.$refs.rankRef, this.theme);4、特殊处理
import { getTheme } from "@/utils/themeUtils";computed: {...mapState(["theme"]),themeStyle() {return getTheme(this.theme);},}

5、全屏效果展示

点击事件:
expandChart(chartName) {1、改变fullScreenStatus 的数据this.fullScreenStatus[chartName] = true2、调用一个组件的screenAdapter方法this.$next(() => {this.$refs[chartName].screenAdapter() // 是因为屏幕没适应才导致的})}

四、板块分解(一)

1、商家销售统计部分

<template><!-- 商家销售统计 --><div class="com-container"><div class="com-chart" ref="sellerRef"></div></div>
</template><script>
export default {data() {return {chart: null,allData: null, //服务器返回的数据currentPage: 1,pageSize: 5,totalPage: 0,timer: null, //定时器的标识};},mounted() {this.initChart();// 监听窗口大小的变化,界面加载完成,this.screenAdapter 主动进行屏幕适配window.addEventListener("resize", this.screenAdapter);this.screenAdapter(); //在进入之前先完成自适应this.getData();},//   清除定时器destroyed() {clearInterval(this.timer);// 习惯性问题,这样就不会有内存泄漏window.removeEventListener("resize", this.screenAdapter);},methods: {// 初始化echartsInstance对象initChart() {this.chart = this.$echarts.init(this.$refs.sellerRef);// 设置初始化的optionconst initOption = {title: {text: "▎商家销售统计",left: 20,top: 20,},grid: {// 控制坐标轴显示的大小top: "15%",right: "6%",bottom: "3%",left: "3%",containLabel: true, // 让坐标轴文字也有间隔,不然就只控制了坐标轴大小},xAxis: {type: "value",},yAxis: {type: "category",},tooltip: {trigger: "axis",axisPointer: {// 设置鼠标移入条目背景样式type: "line",z: 0,lineStyle: {color: "#2D3443",},},},series: [{type: "bar",label: {show: true, // 显示文字position: "right",textStyle: {color: "#fff",},},itemStyle: {// 每个柱状图条目// 上右下左坐标分别是(0,0) (1,0) (1,1) (0,1) 分别指明方向和各百分比颜色color: new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [{offset: 0,color: "#5052EE",},{offset: 1,color: "#AB6EE5",},]),},},],};this.chart.setOption(initOption);// 监听鼠标移入移出事件this.chart.on("mouseover", () => {clearInterval(this.timer);});this.chart.on("mouseout", () => {this.startInterval();});},// 异步获取服务器的数据async getData() {//    http://localhost:3000/api/seller使用get请求这个接口const { data: ret } = await this.$http.get("seller");this.allData = ret;this.allData.sort((a, b) => {return a.value - b.value; // 数据从小到大排序});// 每五个元素显示一页this.totalPage =this.allData.length % this.pageSize === 0? this.allData.length / this.pageSize: this.allData.length / this.pageSize + 1;this.updateChart();this.startInterval();},// 更新图表updateChart() {// echarts图表展示区let start = (this.currentPage - 1) * this.pageSize;let end = this.currentPage * this.pageSize;const showData = this.allData.slice(start, end); //截取数据const yNames = showData.map((item) => item.name);const xValues = showData.map((item) => item.value);let dataOption = {yAxis: {data: yNames,},series: [{data: xValues,},],};this.chart.setOption(dataOption);},// 定时器,每3秒执行一次startInterval() {// 编程小技巧,一开始判断是否开启的定时器,开启了停止之后再重新启动if (this.timer) {window.clearInterval(this.timer);}this.timer = setInterval(() => {this.currentPage++;if (this.currentPage > this.totalPage) {this.currentPage = 1;}this.updateChart();}, 3000);},// 窗口大小调整screenAdapter() {// 浏览器大小发生改变,进行屏幕适配const titleFontSize = (this.$refs.sellerRef.offsetWidth / 100) * 3.6;//  和分辨率大小相关的配置项const adapterOption = {title: {textStyle: {fontSize: titleFontSize,},},tooltip: {axisPointer: {// 设置鼠标移入条目背景样式lineStyle: {width: titleFontSize,},},},series: [{barWidth: titleFontSize,itemStyle: {// 每个柱状图条目barBorderRadius: [0, titleFontSize / 2, titleFontSize / 2, 0], // 上右下左圆角,33是barWidth的一半},},],};this.chart.setOption(adapterOption);this.chart.resize(); // 必须手动调用图表对象的resize 才有效果},},
};
</script>


2、商家销量趋势部分

<template><div class="com-container"><div class="title" :style="curOptionStyle"><span>▎{{ curTypeName }}</span><!-- 下拉图标 --><spanclass="iconfont icon-arrow-down"@click="showOption = !showOption":style="curOptionStyle"></span><!-- 下拉框 --><div class="select-con" v-show="showOption"><divclass="select-option"v-for="item in selectTypes":key="item.key"@click="selectOption(item.key)">{{ item.text }}</div></div></div><div class="com-chart" ref="trendRef"></div></div>
</template><script>
export default {data() {return {chart: null,allData: null,curType: "map", // 当前类型showOption: false,titleFontSize: 0, // 指明标题大小};},computed: {// 计算属性依赖其他selectTypes() {if (!this.allData) {return [];} else {// 把当前已选项给去掉return this.allData.type.filter((item) => item.key !== this.curType);}},curTypeName() {if (!this.allData) {return "";} else {return this.allData[this.curType].title;}},curOptionStyle() {// 设置给当前选项的样式return {fontSize: this.titleFontSize + "px",// color: getTheme(this.theme).titleColor,};},},mounted() {this.initChart();this.getData();window.addEventListener("resize", this.screenAdapter);this.screenAdapter();},destroyed() {window.removeEventListener("resize", this.screenAdapter);},methods: {initChart() {this.chart = this.$echarts.init(this.$refs.trendRef);const initOption = {grid: {// 控制坐标轴大小left: "3%",top: "35%",right: "4%",bottom: "1%",containLabel: true,},tooltip: {trigger: "axis", //文字提示},legend: {left: 20,top: "15%",icon: "circle",},xAxis: {type: "category",boundaryGap: false, // 紧挨边缘},yAxis: {type: "value",},};this.chart.setOption(initOption);},async getData() {const { data: ret } = await this.$http.get("trend");this.allData = ret;this.updateChart();},updateChart() {const colorArr1 = ["rgba(11,168,44,.5)","rgba(44,110,255,.5)","rgba(22,242,217,.5)","rgba(254,33,30,.5)","rgba(250,105,0,.5)",]; // 半透明颜色数组const colorArr2 = ["rgba(11,168,44,0)","rgba(44,110,255,0)","rgba(22,242,217,0)","rgba(254,33,30,0)","rgba(250,105,0,0)",]; // 透明颜色数组const xValues = this.allData.common.month; // x轴数据const values = this.allData[this.curType].data;const yValues = values.map((item, index) => {return {name: item.name,type: "line",data: item.data,stack: this.curType, //堆叠形式存在areaStyle: {color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [{offset: 0,color: colorArr1[index],},{offset: 1,color: colorArr2[index],},]),},};});const legendArr = values.map((item) => item.name); //图例数据,上海,北京等等const dataOption = {legend: {data: legendArr,},xAxis: {data: xValues,},series: yValues,};this.chart.setOption(dataOption);},screenAdapter() {this.titleFontSize = (this.$refs.trendRef.offsetWidth / 100) * 3.6;const adapterOption = {// 更改图例legend: {itemWidth: this.titleFontSize,itemHeight: this.titleFontSize,itemGap: this.titleFontSize,textStyle: {fontSize: this.titleFontSize - 20,},},};this.chart.setOption(adapterOption);this.chart.resize(); // 手动调用图表自适应},// 下拉选择框selectOption(type) {this.curType = type;this.showOption = false;this.updateChart();},},
};
</script>

3、商家分布地图部分

<script>
import axios from "axios";
import { getProvinceMapInfo, isValid } from "@/utils/mapUtils.js";
export default {data() {return {chart: null,allData: null,allMapData: [], // 所有省份地图 ['hubei','henan']};},mounted() {this.initChart();this.updateChart();this.screenAdapter();this.getData();window.addEventListener("resize", this.screenAdapter);},destroyed() {window.removeEventListener("resize", this.screenAdapter);},methods: {async initChart() {this.chart = this.$echarts.init(this.$refs.mapRef);const initOption = {title: {text: "▎商家分布",left: 20,top: 20,},legend: {left: "5%",bottom: "5%",orient: "vertical",},geo: {type: "map",map: "China",top: "5%",bottom: "5%",itemStyle: {// 各省份样式areaColor: "#2E72BF",borderColor: "#333", // 边界样式},},};this.chart.setOption(initOption);// 点击事件的监听this.chart.on("click", async (item) => {// 这里可以优化:数据存在vuex中,那样让地图点进来还是home-map的地图而不用重新发请求// 为地图添加点击事件 -- 这段还是使用axios,因为跟后台数据关系不大if (isValid(item.name)) {const provinceObj = getProvinceMapInfo(item.name); // 有效的话,就去获取其路径和英文名if (!this.isSavedMapData(provinceObj.key)) {// 没有保存到它的数据,使用axios来获取地图矢量数据const { data: provinceMap } = await axios.get(`http://localhost:8888${provinceObj.path}`);// 将地图矢量数据进行注册this.$echarts.registerMap(provinceObj.key, provinceMap);this.allMapData.push(provinceObj.key);}// 配置geoconst mapOption = {geo: {map: provinceObj.key,},};this.chart.setOption(mapOption);}});},async getData() {const { data: ret } = await this.$http.get("map");this.allData = ret;// 图例的数据const legendArr = this.allData.map((item) => item.name);const seriesArr = this.allData.map((item) => {// return 的这个对象就代表一个类别下面的所有散点数据// 如果想在地图中显示散点的数据,所以需要给散点图表增加一个配置coordinateSystem:georeturn {// 要让数据在地图上显示,就要设置coordinate systemtype: "effectScatter",rippleEffect: {// 涟漪效果程度scale: 5,brushType: "stroke", // 空心涟漪效果},name: item.name,data: item.children,coordinateSystem: "geo",};});const dataOption = {legend: {data: legendArr,},series: seriesArr,};this.chart.setOption(dataOption);},updateChart() {const dataOption = {};this.chart.setOption(dataOption);},screenAdapter() {const titleFontSize = (this.$refs.mapRef.offsetWidth / 100) * 3.6;const adapterOption = {title: {textStyle: {fontSize: titleFontSize,},},legend: {itemWidth: titleFontSize / 2,itemHeight: titleFontSize / 2,itemGap: titleFontSize / 2,textStyle: {fontSize: titleFontSize / 2,},},};this.chart.setOption(adapterOption);this.chart.resize();},isSavedMapData(name) {// 查找是否已经保存了英文name的地图if (this.allMapData.length > 0) {return this.allMapData.indexOf(name) !== -1;}return false;},},
};
</script>

电商数据可视化实时监控系统(二):https://blog.csdn.net/rookie_0_0/article/details/119619525

电商数据可视化实时监控系统(一)相关推荐

  1. 「ECharts」电商平台数据可视化实时监控系统之后台开发

    此项目后台采用 Koa2 进行开发配置,相关配置整理如下. 1. Koa2 概述 Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领 ...

  2. 电商平台数据可视化实时监控系统(开发目录)

    目录 项目地址github 1.koa2快速上手 2.后台项目初步 3.前端项目的创建和准备 4.单独图表组件的开发---商家销售统计(横向柱状图) 5.单独图表组件的开发---销量趋势图表(折线图) ...

  3. 可视化实时监控系统echarts图

    1.躺着的柱状图 // 初始化echartInstance对象: this.$echarts.init(this.$refs.seller_ref, "chalk");  //第二 ...

  4. 电商平台实时监控系统丨前端项目的准备

    项目最终的效果如图所示: 最终效果涉及到6个图表, 5种图表类型,它们分别是折线图,柱状图,地图,散点图,饼图 每个图表的数据都是从后端推送到前端来的, 不过在项目的初期,我们会先使用 ajax 由前 ...

  5. CAT ----分布式实时监控系统

    2011年底,我加入大众点评网,出于很偶然的机会,决定开发CAT,为各个业务线打造分布式实时监控系统,CAT的核心概念源自eBay闭源系统CAL----eBay的几大法宝之一. 在当今互联网时代,业务 ...

  6. 5分钟完成业务实时监控系统搭建,是一种什么样的体验?

    简介:道旅需要构建一个全面的指标监控系统,既包括系统的业务指标:如各类业务类型的请求数变化,不同供应商信息的变化,客户请求的明细大盘,各酒店请求量的排名变化,不同城市的订单转换率分析报表等:也包括系统 ...

  7. 服务器可视化监控系统,可视化服务器监控系统

    可视化服务器监控系统 内容精选 换一换 精简视图提供了云服务器资源概况和状态的可视化统计结果,帮助您直观的了解云服务器资源.在精简视图中,您可以快速获取弹性云服务器基本信息.登录信息.配置信息.监控信 ...

  8. 浅谈Telegraf+InfluxDB+Grafana快速搭建简易实时监控系统

    监控从来都是一个很宽泛的问题,任何可能出问题的地方都需要加入监控.全量监控的确是监控的终极目标.在搭建一套监控系统前,需要结合实际的系统情况和发展趋势进行考量.在作者看来,一套监控系统应主要由数据采集 ...

  9. 五分钟搭建基于 Prometheus + Grafana 实时监控系统

    文章目录 Prometheus + Grafana 实时监控系统 Prometheus + Grafana 实时监控系统 依赖镜像包 启动 node-exporter check 端口 node_ex ...

最新文章

  1. shell脚本判断输入参数个数
  2. 4.12 总结-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  3. uboot流程——uboot启动流程
  4. flask mysql 版本_Flask mysql
  5. centos系统php环境配置,CentOS 6.4系统下编译安装LNMP和配置PHP环境具体步骤
  6. tensorflowgpu利用率为0_直流电压利用率的提高方法-梯形波调制法
  7. mysql 指定数字排序_Mysql数据排序
  8. 最简单的jQuery程序
  9. QNetworkAccessManager的异步与线程
  10. 第 10 章 容器监控 - 080 - Weave Scope 容器地图
  11. 生物信息学三大数据库NCBI-ENSEMBL-UCSC
  12. 使用python实现嵌套压缩包解压
  13. 在Swift中使用dispatch_once单例模型
  14. 亚马逊显示在售商品为0怎么办?亚马逊新品货还没到就在售了
  15. matlab 有限元分析
  16. 电脑软件测试英雄联盟,lol电脑配置检测,如何测试自己的网络玩lol的具体情况?...
  17. lmdb文件的读取和保存
  18. mitm 和嗅探攻击_中间人攻击(MITM)第2部分-数据包嗅探器
  19. 顺顺网页电子表格控件开源下载
  20. ESL第十七章 无向图模型 学习/推断/成对马尔可夫独立/全局马尔可夫性、协方差图/高斯图/修改回归算法/图结构估计/图lasso、【受限】玻尔兹曼机/泊松对数线性建模/迭代比例过滤/对比散度

热门文章

  1. 图书管理系统可行性分析报告
  2. getRealPath
  3. ARS408毫米波雷达使用记录
  4. 面试总结(四):消息队列
  5. Python+Requests接口测试教程(1):Fiddler抓包工具
  6. 图解如何计算结构体大小
  7. Playbook机密
  8. HTML层叠性优先级!important用法
  9. 【Windows系统】查询特定进程TCP端口
  10. 关于TensorFlow-gpu出现nan原因