|
@@ -0,0 +1,221 @@
|
|
|
+<template>
|
|
|
+ <div class="visitor-trend">
|
|
|
+ <div ref="chartRef" style="width: 100%; height: 300px;"></div>
|
|
|
+ <div style="position: absolute;top: 20px;right: 0;">
|
|
|
+ <el-select v-model="value" style="width: 84px;">
|
|
|
+ <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+
|
|
|
+const value = ref('7');
|
|
|
+const options = [
|
|
|
+ {
|
|
|
+ value: '7',
|
|
|
+ label: '7天',
|
|
|
+ selected: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: '30',
|
|
|
+ label: '30天',
|
|
|
+ },
|
|
|
+]
|
|
|
+const chartRef = ref(null);
|
|
|
+let chartInstance = null;
|
|
|
+
|
|
|
+// 示例数据
|
|
|
+const data = [
|
|
|
+ { date: '2025-01-01', visit: 12, visitor: 80, user: 50, duration: 3.2, bounceRate: 32 },
|
|
|
+ { date: '2025-01-02', visit: 13, visitor: 92, user: 62, duration: 4.1, bounceRate: 28 },
|
|
|
+ { date: '2025-01-03', visit: 10, visitor: 71, user: 41, duration: 3.8, bounceRate: 35 },
|
|
|
+ { date: '2025-01-04', visit: 13, visitor: 94, user: 64, duration: 4.5, bounceRate: 26 },
|
|
|
+ { date: '2025-01-05', visit: 9, visitor: 60, user: 30, duration: 2.9, bounceRate: 42 },
|
|
|
+ { date: '2025-01-06', visit: 23, visitor: 30, user: 60, duration: 5.2, bounceRate: 22 },
|
|
|
+ { date: '2025-01-07', visit: 21, visitor: 10, user: 70, duration: 4.8, bounceRate: 25 },
|
|
|
+];
|
|
|
+
|
|
|
+// 配置颜色
|
|
|
+const colorList = [
|
|
|
+ 'rgba(255, 154, 95, 1)', // 访问量
|
|
|
+ 'rgba(91, 88, 225, 1)', // 访客量
|
|
|
+ 'rgba(88, 206, 82, 1)', // 用户管理
|
|
|
+ 'rgba(237, 49, 168, 1)', // 平均停留时长
|
|
|
+ 'rgba(22, 122, 240, 1)' // 跳出率
|
|
|
+];
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ chartInstance = echarts.init(chartRef.value);
|
|
|
+
|
|
|
+ // 准备数据
|
|
|
+ const dateList = data.map(item => item.date);
|
|
|
+
|
|
|
+ const WEEK_DAYS = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.5)',
|
|
|
+ borderColor: 'rgba(204, 204, 204, 1)',
|
|
|
+ borderWidth: 1,
|
|
|
+ borderRadius: 0,
|
|
|
+ formatter: function (params) {
|
|
|
+ const date = params[0].axisValue;
|
|
|
+ let result = `<div style="font-weight: bold; font-size: 12px; color: rgba(18, 18, 18, 1); margin-bottom: 5px;">${dayjs(date).format('YYYY年MM月DD日')} ${WEEK_DAYS[dayjs(date).day()]}</div>`;
|
|
|
+ params.forEach(item => {
|
|
|
+ let value = item.value;
|
|
|
+ // // 特殊处理不同指标的单位
|
|
|
+ // if (item.seriesName === '平均停留时长') {
|
|
|
+ // value = value + ' 分钟';
|
|
|
+ // } else if (item.seriesName === '跳出率') {
|
|
|
+ // value = value + '%';
|
|
|
+ // }
|
|
|
+ result += `<div style="font-size: 12px; color: #666; margin-top: 2px;">${item.marker} <span style="font-weight: bold; color: #000000;">${value}</span> ${item.seriesName}</div>`;
|
|
|
+ });
|
|
|
+ return result;
|
|
|
+ },
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['访问量', '访客量', '用户管理', '平均停留时间(秒)', '跳出率'],
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '0%',
|
|
|
+ right: '5%',
|
|
|
+ bottom: '0%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: dateList,
|
|
|
+ axisLabel: {
|
|
|
+ formatter: function (value) {
|
|
|
+ // 格式化日期显示
|
|
|
+ return value.split('-').slice(1).join('-');
|
|
|
+ },
|
|
|
+ // rotate: 30 // 日期标签旋转30度防止重叠
|
|
|
+ color: 'rgba(100, 100, 100, 1)',
|
|
|
+ },
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ // color: 'rgba(22, 122, 240, 1)',
|
|
|
+ // width: 2,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '访问量',
|
|
|
+ type: 'line',
|
|
|
+ data: data.map(item => item.visit),
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 2,
|
|
|
+ itemStyle: {
|
|
|
+ color: colorList[0]
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '访客量',
|
|
|
+ type: 'line',
|
|
|
+ data: data.map(item => item.visitor),
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 2,
|
|
|
+ itemStyle: {
|
|
|
+ color: colorList[1]
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '用户管理',
|
|
|
+ type: 'line',
|
|
|
+ data: data.map(item => item.user),
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 2,
|
|
|
+ itemStyle: {
|
|
|
+ color: colorList[2]
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '平均停留时间(秒)',
|
|
|
+ type: 'line',
|
|
|
+ data: data.map(item => item.duration),
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 2,
|
|
|
+ itemStyle: {
|
|
|
+ color: colorList[3]
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '跳出率',
|
|
|
+ type: 'line',
|
|
|
+ data: data.map(item => item.bounceRate),
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 2,
|
|
|
+ itemStyle: {
|
|
|
+ color: colorList[4]
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ color: colorList
|
|
|
+ };
|
|
|
+
|
|
|
+ chartInstance.setOption(option);
|
|
|
+
|
|
|
+ // 响应式调整
|
|
|
+ const handleResize = () => chartInstance?.resize();
|
|
|
+ window.addEventListener('resize', handleResize);
|
|
|
+});
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('resize', handleResize);
|
|
|
+ chartInstance?.dispose();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+.visitor-trend {
|
|
|
+ position: relative;
|
|
|
+ padding-top: 20px;
|
|
|
+ height: 340px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 下拉框整体样式 */
|
|
|
+.visitor-trend .el-select__wrapper {
|
|
|
+ border: 1px solid rgba(22, 122, 240, 1);
|
|
|
+}
|
|
|
+
|
|
|
+.visitor-trend .el-select__selected-item {
|
|
|
+ color: rgba(22, 122, 240, 1); /* 文字颜色 */
|
|
|
+}
|
|
|
+
|
|
|
+.visitor-trend .el-select__caret {
|
|
|
+ color: rgba(22, 122, 240, 1); /* 三角形颜色 */
|
|
|
+}
|
|
|
+</style>
|