项目展示 在线地址
前言 首先声明这不是一个原创项目,这个项目是我在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
,所以只要在安装Nginx
的conf.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