vue移动端统计分析echarts开发小结(多提示框渲染,下载适配,数据量大的时候不分页崩溃等)
前言
前段时间负责开发公司移动端的统计分析模块。其中晒选项包含了季度年度,月度具体还有二级筛选。因此晒选项我封装了一个新的组件。后续我会分享出来。接下来就是柱状图折线图以及表格之间的切换。我把各个图表都独立封装成了一个组件。这样然后一起放在父组件中。图表渲染确实不难很简单。但是在开发途中还是遇到了几个恶心的问题。在此我想分享一下
晒选项
根据ui稿我封装了一个组件
<template>
<div class="timeSelect">
<van-dropdown-menu :active-color="'#ff6f2f'">
<van-dropdown-item
v-model="selectType"
@change="typeChange"
:options="type"
/>
</van-dropdown-menu>
<div class="time" @click="showDataCheck">
<div class="time-title">{{ showTitle }}</div>
</div>
<van-popup v-model="dataCheck" round position="bottom" :duration="0">
<van-datetime-picker
v-model="currentDate"
:formatter="formatter"
type="year-month"
title=""
@confirm="confirmDate"
@cancel="onCancel"
:min-date="minDate"
:max-date="maxDate"
/></van-popup>
<van-popup v-model="yearCheck" round position="bottom" :duration="0">
<van-picker
ref="picker"
show-toolbar
:columns="selectType == 2 ? quarterColumns : yearColumns"
v-model="currentValue"
@change="yearChange"
@confirm="onConfirm"
@cancel="onCancel"
/></van-popup>
</div>
</template>
<script>
import dayjs from "dayjs";
export default {
data() {
return {
selectType: 1,
selectTime: "",
showTitle: "",
yearCheck: false,
type: [
{ text: "月度", value: 1 },
{ text: "季度", value: 2 },
{ text: "年度", value: 3 },
],
yearColumns: [],
quarterColumns: [
// 第一列
{
values: [],
defaultIndex: 2,
},
// 第二列
{
values: ["1季度", "2季度", "3季度", "4季度"],
defaultIndex: 1,
},
],
dataCheck: false,
minDate: new Date(2022, 0, 1),
maxDate: new Date(),
currentDate: new Date(),
currentValue: [],
};
},
mounted() {
this.onInit();
},
methods: {
updateTime(type, date) {
console.log(type, date);
},
onInit() {
if (this.$route.query.dateType) {
this.selectType = Number(this.$route.query.dateType);
this.selectTime = this.$route.query.date;
this.showTitle = this.$route.query.date;
} else {
this.showTitle = `${dayjs().year()}年${dayjs().month() + 1}月`;
}
console.log(this.selectType);
const years = [];
for (let year = dayjs().year(); year >= 2022; year--) {
years.unshift(`${year}年`);
}
this.yearColumns = years;
this.quarterColumns[0].values = years;
},
// 选中日期
confirmDate(value) {
this.selectTime = `${dayjs(value).year()}年${dayjs(value).month() + 1}月`;
this.showTitle = `${dayjs(value).year()}年${dayjs(value).month() + 1}月`;
this.currentDate = new Date(value);
this.dataCheck = false;
this.$emit("updateTime", this.selectType, this.selectTime);
console.log("改变", this.selectType, this.selectTime);
},
// 日期格式化
formatter(type, val) {
if (type === "year") {
return `${val}年`;
} else if (type === "month") {
return `${Number(val)}月`;
}
return val;
},
onConfirm(val) {
if (this.selectType == 3) {
this.showTitle = val;
this.selectTime = val;
} else {
this.showTitle = `${val[0]}${val[1]}`;
this.selectTime = `${val[0]}${val[1]}`;
}
this.currentValue = val;
this.$emit("updateTime", this.selectType, this.selectTime);
console.log("改变", this.selectType, this.selectTime);
this.yearCheck = false;
},
// 年或季度改变
yearChange(val, time) {
console.log(111, time);
if (time[0] == `${dayjs().year()}年`) {
let vals = JSON.parse(JSON.stringify(this.quarterColumns[1].values));
vals.length = Math.floor(dayjs().month() / 3) + 1;
this.$refs["picker"].setColumnValues(1, vals);
} else {
this.$refs["picker"].setColumnValues(1, [
"1季度",
"2季度",
"3季度",
"4季度",
]);
}
},
// 改变选择框类型
typeChange(value) {
// 选中年份
if (value == 3) {
this.showTitle = `${dayjs().year()}年`;
this.currentValue = [`${dayjs().year()}年`];
this.selectTime = `${dayjs().year()}年`;
} else if (value == 2) {
this.showTitle = `${dayjs().year()}年${
Math.floor(dayjs().month() / 3) + 1
}季度`;
this.selectTime = `${dayjs().year()}年${
Math.floor(dayjs().month() / 3) + 1
}季度`;
} else {
this.showTitle = `${dayjs().year()}年${dayjs().month() + 1}月`;
this.selectTime = `${dayjs().year()}年${dayjs().month() + 1}月`;
}
this.$emit("updateTime", this.selectType, this.selectTime);
console.log("改变", this.selectType, this.selectTime);
},
// 隐藏弹窗
onCancel() {
this.yearCheck = false;
this.dataCheck = false;
},
// 展示弹窗
showDataCheck() {
if (this.selectType == 1) {
this.dataCheck = true;
} else {
this.yearCheck = true;
// 第一次弹窗选中对应
this.$nextTick(() => {
if (this.selectType == 3) {
console.log(this.yearColumns);
this.$refs["picker"].setColumnIndex(
0,
this.yearColumns.indexOf(this.showTitle)
);
} else {
if (this.showTitle.split("年")[0] + "年" == `${dayjs().year()}年`) {
let vals = JSON.parse(
JSON.stringify(this.quarterColumns[1].values)
);
vals.length = Math.floor(dayjs().month() / 3) + 1;
this.$refs["picker"].setColumnValues(1, vals);
}
this.$refs["picker"].setColumnIndex(
0,
this.yearColumns.indexOf(this.showTitle.split("年")[0] + "年")
);
this.$refs["picker"].setColumnIndex(
1,
this.quarterColumns[1].values.indexOf(
this.showTitle.split("年")[1]
)
);
}
});
}
},
},
};
</script>
<style scoped lang="less">
.timeSelect {
font-size: 0.15rem;
box-sizing: border-box;
padding: 0 2%;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 9vh;
background-color: #f5f8fb;
font-size: 15px;
}
.time {
width: 48%;
background-color: #fff;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
margin-right: 4px;
}
.time-title {
position: relative;
}
.time-title::after {
position: absolute;
top: 50%;
right: -12px;
margin-top: -5px;
border: 3px solid;
border-color: transparent transparent #1f1f1f #1f1f1f;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
opacity: 0.8;
content: "";
}
/deep/ .van-dropdown-menu {
width: 48%;
}
/deep/ .van-dropdown-menu__bar {
border-radius: 4px;
margin-left: 5px;
}
/deep/ .van-ellipsis {
color: #1f1f1f;
}
/deep/ .van-dropdown-item {
margin-top: 10px;
font-size: 17px;
}
/deep/ .van-dropdown-menu__bar {
}
/deep/ .van-ellipsis {
font-size: 17px;
}
/deep/ .van-cell__title {
font-size: 16px;
}
/deep/ .time-title {
font-size: 17px;
}
/deep/ .van-dropdown-menu__title::after {
border-color: transparent transparent #1f1f1f #1f1f1f;
}
/deep/ .van-picker__confirm {
color: #ff6f2f;
}
/deep/ .van-icon-success:before {
font-family: "iconfont";
content: "\E625" !important;
}
</style>
直接暴露两个参数type和date就行了,因为我自己封装了转时间戳的函数,上文中也有提到。具体可以去查看。
柱状图
柱状图中后台给我数据之后我处理成我想要的数据格式。因为业务问题我特殊处理X轴和y轴上的数据。另外提示框我自己写了所需要的函数
formatter: function (params) {
let tooltip = "";
let xAxisValue = params[0].axisValue;
// 根据 xAxisValue 的格式进行处理
if (/^\d{4}$/.test(xAxisValue)) {
xAxisValue = `${xAxisValue}年`;
} else if (/^\d{4}-\d{1,2}月$/.test(xAxisValue)) {
const [year, month] = xAxisValue.split("-");
xAxisValue = `${year}年${parseInt(month)}月`;
} else if (/^\d{4}-[1-4]季度$/.test(xAxisValue)) {
const [year, quarter] = xAxisValue.split("-");
xAxisValue = `${year}年${parseInt(quarter)}季度`;
}
tooltip += `${xAxisValue}<br>`;
// 添加蓝色小圆圈代表会议量
tooltip += `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:#396EF3;"></span>${params[0].seriesName} ${params[0].value}<br>`;
// 添加黄色小圆圈代表会议量排名
tooltip += `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:#E6BB58;"></span>${params[1].seriesName} ${params[1].value}<br>`;
return tooltip;
},
在柱状图中发现了一个问题就是tooltip提示框他会出现一个偶发性问题。如果在一个页面放两个组件图表。当你点击一个组件图表的提示框的时候在点击另一个组件图表的提示框的时候两个提示框会同时出现(在你配置写对的情况下)安卓手机和苹果手机复现的频率还不一样。
第二个问题是:在我们项目开发中有一个需求是下载图表页面。根据ui设计下载好的图片是特定的样式有预览效果。刚开始我的方案是直接使用第三方包html2canvas来截图(上文都有讲过)这种情况下功能可以实现但是时间有点长(数据量100多条的时候安卓手机7秒左右,苹果手机3秒左右这很影响体验使用)下载的图表还需要加特定的样式和字段所以还得用canvs去拼接,所以整体下来时间生成时间就长了。所以很快就放弃这种方法。之后转变思路直接在cavas中加载图表生成图表这样速度很快,但是在调试途中要注意一个问题就是数据量大的情况下并且设计的是不让分页的时候这样加载图表就会出现一个图表崩溃问题,或者像素垮了的问题,或者就是在苹果手机上直接一个问号(崩溃)这都是因为echart图表加载崩溃的原因。
解决方法初始化的时候设置为svg格式(有疑问可以私信我)
this.barChart = echarts.init(this.$refs.bar, null, {
renderer: "svg",
devicePixelRatio: window.devicePixelRatio,
// devicePixelRatio: 1,
});
不要以为这样就结束了,记得还有适配问题,安卓手机上和苹果手机上出现的问题还是不一样。为综合考虑后在下载的时候根据数据量做了判断。当数据量小于200的时候直接使用图表加载下载图片(很快)数据量大于200的时候使用html2canvas截图来下载记得加一个loading。在这中间我封装了预览图片的函数。截图的函数。如果有需要可以私信我。