improve: instance activity chart (#1144)

* improve: instance activity chart

* wip: ?

* Wrong commit

* add prev/next day btn, bar width setting, i18n

* add show solo, no friend instance btn, add friend icon, i18n

* add favorite friend icon, tips, improve the chart display effect, and i18n
This commit is contained in:
pa
2025-02-22 18:30:42 +09:00
committed by GitHub
parent f7530303f6
commit f221b43a10
13 changed files with 852 additions and 177 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div style="width: 100%">
<div style="height: 25px; margin-top: 60px">
<transition name="el-fade-in-linear">
<location
@@ -30,13 +30,18 @@
type: Array,
required: true
},
activityData: {
type: Array,
required: true
},
isDarkMode: {
type: Boolean,
required: true
},
dtHour12: {
type: Boolean,
required: true
},
barWidth: {
type: Number,
required: true,
default: 10
}
},
data() {
@@ -48,16 +53,30 @@
},
computed: {
startTimeStamp() {
return this.activityData
.find((item) => item.location === this.activityDetailData[0].location)
return this.activityDetailData
.find((item) => item.user_id === this.API.currentUser.id)
?.joinTime.valueOf();
},
endTimeStamp() {
return this.activityData
.findLast((item) => item.location === this.activityDetailData[0].location)
return this.activityDetailData
.find((item) => item.user_id === this.API.currentUser.id)
?.leaveTime.valueOf();
}
},
watch: {
isDarkMode() {
if (this.echartsInstance) {
this.echartsInstance.dispose();
this.echartsInstance = null;
this.initEcharts();
}
},
dtHour12() {
if (this.echartsInstance) {
this.initEcharts();
}
}
},
created() {
this.resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
@@ -70,38 +89,46 @@
}
});
},
mounted() {
this.initEcharts(true);
},
deactivated() {
// prevent switch tab play resize animation
this.resizeObserver.disconnect();
},
methods: {
async initEcharts() {
async initEcharts(isFirstLoad = false) {
// TODO: unnecessary import, import from individual js file
await import('echarts').then((echartsModule) => {
echarts = echartsModule;
});
const chartsHeight = this.activityDetailData.length * (this.barWidth + 10) + 200;
const chartDom = this.$refs.activityDetailChart;
if (!this.echartsInstance) {
this.echartsInstance = echarts.init(chartDom, `${this.isDarkMode ? 'dark' : null}`, {
height: this.activityDetailData.length * 40 + 200,
useDirtyRect: this.activityDetailData.length > 30
height: chartsHeight,
useDirtyRect: this.activityDetailData.length > 80
});
this.resizeObserver.observe(chartDom);
}
this.echartsInstance.resize({
height: this.activityDetailData.length * 40 + 200,
height: chartsHeight,
animation: {
duration: 300
}
});
this.echartsInstance.setOption(this.getNewOption(), { lazyUpdate: true });
this.echartsInstance.setOption(isFirstLoad ? {} : this.getNewOption(), { lazyUpdate: true });
this.echartsInstance.on('click', 'yAxis', this.handleClickYAxisLabel);
setTimeout(() => {
this.isLoading = false;
this.echartsInstance.dispatchAction({
type: 'highlight',
seriesIndex: 3 // 对于 seriesLayoutBy: 'row'seriesIndex 对应行索引
});
}, 200);
},
handleClickYAxisLabel(params) {
@@ -111,6 +138,22 @@
}
},
getNewOption() {
const friendOrFavIcon = (display_name) => {
const foundItem = this.activityDetailData.find((item) => item.display_name === display_name);
if (!foundItem) {
return '';
}
if (foundItem.isFavorite) {
return '⭐';
}
if (foundItem.isFriend) {
return '💚';
}
return '';
};
const getTooltip = (params) => {
const activityDetailData = this.activityDetailData;
const param = params[1];
@@ -121,8 +164,9 @@
const instanceData = activityDetailData[param.dataIndex];
const formattedLeftDateTime = dayjs(instanceData.leaveTime).format('HH:mm:ss');
const formattedJoinDateTime = dayjs(instanceData.joinTime).format('HH:mm:ss');
const format = this.dtHour12 ? 'hh:mm:ss A' : 'HH:mm:ss';
const formattedLeftDateTime = dayjs(instanceData.leaveTime).format(format);
const formattedJoinDateTime = dayjs(instanceData.joinTime).format(format);
const timeString = utils.timeToText(instanceData.time, true);
const color = param.color;
@@ -131,7 +175,7 @@
<div style="display: flex; align-items: center;">
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 5px;"></div>
<div>
<div>${instanceData.display_name}</div>
<div>${instanceData.display_name} ${friendOrFavIcon(instanceData.display_name)}</div>
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
<div>${timeString}</div>
</div>
@@ -139,6 +183,8 @@
`;
};
const format = this.dtHour12 ? 'hh:mm A' : 'HH:mm';
const echartsOption = {
tooltip: {
trigger: 'axis',
@@ -148,7 +194,7 @@
formatter: getTooltip
},
grid: {
top: 60,
top: 50,
left: 160,
right: 90
},
@@ -156,7 +202,11 @@
type: 'category',
axisLabel: {
interval: 0,
formatter: (value) => (value.length > 20 ? `${value.slice(0, 20)}...` : value)
formatter: (value) => {
const MAX_LENGTH = 20;
const len = value.length;
return `${friendOrFavIcon(value)} ${len > MAX_LENGTH ? `${value.substring(0, MAX_LENGTH)}...` : value}`;
}
},
inverse: true,
data: this.activityDetailData.map((item) => item.display_name),
@@ -168,7 +218,7 @@
max: this.endTimeStamp - this.startTimeStamp,
axisLine: { show: true },
axisLabel: {
formatter: (value) => dayjs(value + this.startTimeStamp).format('HH:mm')
formatter: (value) => dayjs(value + this.startTimeStamp).format(format)
},
splitLine: { lineStyle: { type: 'dashed' } }
},
@@ -194,7 +244,10 @@
type: 'bar',
stack: 'Total',
colorBy: 'data',
barWidth: 30,
barWidth: this.barWidth,
emphasis: {
focus: 'self'
},
itemStyle: {
borderRadius: 2,
shadowBlur: 2,
@@ -203,12 +256,10 @@
},
data: this.activityDetailData.map((item) => item.time)
}
]
],
backgroundColor: 'rgba(0, 0, 0, 0)'
};
if (this.isDarkMode) {
echartsOption.backgroundColor = 'rgba(0, 0, 0, 0)';
}
return echartsOption;
}
}