|
@@ -149,25 +149,30 @@
|
|
<td class="px-6 py-4">
|
|
<td class="px-6 py-4">
|
|
<div class="flex items-center">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-10 w-10">
|
|
<div class="flex-shrink-0 h-10 w-10">
|
|
- <img class="h-10 w-10 rounded-full object-cover" :src="behavior.user.avatar" :alt="behavior.user.name">
|
|
|
|
|
|
+ <img
|
|
|
|
+ class="h-10 w-10 rounded-full object-cover"
|
|
|
|
+ :src="behavior.userAvatar"
|
|
|
|
+ :alt="behavior.userName"
|
|
|
|
+ @error="handleImageError"
|
|
|
|
+ >
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="ml-4">
|
|
- <div class="text-sm font-medium text-gray-900 dark:text-white">{{ behavior.user.name }}</div>
|
|
|
|
- <div class="text-sm text-gray-500 dark:text-gray-400">{{ behavior.user.id }}</div>
|
|
|
|
|
|
+ <div class="text-sm font-medium text-gray-900 dark:text-white">{{ behavior.userName }}</div>
|
|
|
|
+ <div class="text-sm text-gray-500 dark:text-gray-400">{{ behavior.userId }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<td class="px-6 py-4">
|
|
- <span :class="['user-type-tag', `type-${behavior.user.type}`]">
|
|
|
|
- {{ behavior.user.type === 'new' ? '新用户' :
|
|
|
|
- behavior.user.type === 'regular' ? '普通用户' : 'VIP用户' }}
|
|
|
|
|
|
+ <span :class="['user-type-tag', `type-${behavior.userType}`]">
|
|
|
|
+ {{ behavior.userType === 'new' ? '新用户' :
|
|
|
|
+ behavior.userType === 'regular' ? '普通用户' : 'VIP用户' }}
|
|
</span>
|
|
</span>
|
|
</td>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<td class="px-6 py-4">
|
|
- <span :class="['behavior-type-tag', `type-${behavior.type}`]">
|
|
|
|
- {{ behavior.type === 'browse' ? '浏览' :
|
|
|
|
- behavior.type === 'search' ? '搜索' :
|
|
|
|
- behavior.type === 'purchase' ? '购买' : '评论' }}
|
|
|
|
|
|
+ <span :class="['behavior-type-tag', `type-${behavior.behaviorType}`]">
|
|
|
|
+ {{ behavior.behaviorType === 'browse' ? '浏览' :
|
|
|
|
+ behavior.behaviorType === 'search' ? '搜索' :
|
|
|
|
+ behavior.behaviorType === 'purchase' ? '购买' : '评论' }}
|
|
</span>
|
|
</span>
|
|
</td>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<td class="px-6 py-4">
|
|
@@ -260,7 +265,7 @@
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
-import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
|
+import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
|
import * as echarts from 'echarts'
|
|
import * as echarts from 'echarts'
|
|
import {
|
|
import {
|
|
LineChart,
|
|
LineChart,
|
|
@@ -321,14 +326,13 @@ let trendChart = null
|
|
let distributionChart = null
|
|
let distributionChart = null
|
|
|
|
|
|
// Mock data
|
|
// Mock data
|
|
-// 模拟数据
|
|
|
|
const mockUserBehaviors = ref([
|
|
const mockUserBehaviors = ref([
|
|
{
|
|
{
|
|
id: 'B1001',
|
|
id: 'B1001',
|
|
userId: 'U1001',
|
|
userId: 'U1001',
|
|
userName: '张三',
|
|
userName: '张三',
|
|
userType: 'vip',
|
|
userType: 'vip',
|
|
- userAvatar: 'https://images.unsplash.com/photo-1695048133148-1e0e1b0b0b0b',
|
|
|
|
|
|
+ userAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=张三',
|
|
behaviorType: 'browse',
|
|
behaviorType: 'browse',
|
|
targetId: 'P1001',
|
|
targetId: 'P1001',
|
|
targetName: '智能手表 Pro',
|
|
targetName: '智能手表 Pro',
|
|
@@ -344,7 +348,7 @@ const mockUserBehaviors = ref([
|
|
userId: 'U1002',
|
|
userId: 'U1002',
|
|
userName: '李四',
|
|
userName: '李四',
|
|
userType: 'normal',
|
|
userType: 'normal',
|
|
- userAvatar: 'https://images.unsplash.com/photo-1695048133148-1e0e1b0b0b0c',
|
|
|
|
|
|
+ userAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=李四',
|
|
behaviorType: 'search',
|
|
behaviorType: 'search',
|
|
targetId: null,
|
|
targetId: null,
|
|
targetName: '无线耳机',
|
|
targetName: '无线耳机',
|
|
@@ -360,7 +364,7 @@ const mockUserBehaviors = ref([
|
|
userId: 'U1003',
|
|
userId: 'U1003',
|
|
userName: '王五',
|
|
userName: '王五',
|
|
userType: 'vip',
|
|
userType: 'vip',
|
|
- userAvatar: 'https://images.unsplash.com/photo-1695048133148-1e0e1b0b0b0d',
|
|
|
|
|
|
+ userAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=王五',
|
|
behaviorType: 'add_to_cart',
|
|
behaviorType: 'add_to_cart',
|
|
targetId: 'P1002',
|
|
targetId: 'P1002',
|
|
targetName: '无线耳机 Air',
|
|
targetName: '无线耳机 Air',
|
|
@@ -376,7 +380,7 @@ const mockUserBehaviors = ref([
|
|
userId: 'U1004',
|
|
userId: 'U1004',
|
|
userName: '赵六',
|
|
userName: '赵六',
|
|
userType: 'normal',
|
|
userType: 'normal',
|
|
- userAvatar: 'https://images.unsplash.com/photo-1695048133148-1e0e1b0b0b0e',
|
|
|
|
|
|
+ userAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=赵六',
|
|
behaviorType: 'purchase',
|
|
behaviorType: 'purchase',
|
|
targetId: 'P1003',
|
|
targetId: 'P1003',
|
|
targetName: '智能音箱 Mini',
|
|
targetName: '智能音箱 Mini',
|
|
@@ -392,7 +396,7 @@ const mockUserBehaviors = ref([
|
|
userId: 'U1005',
|
|
userId: 'U1005',
|
|
userName: '钱七',
|
|
userName: '钱七',
|
|
userType: 'vip',
|
|
userType: 'vip',
|
|
- userAvatar: 'https://images.unsplash.com/photo-1695048133148-1e0e1b0b0b0f',
|
|
|
|
|
|
+ userAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=钱七',
|
|
behaviorType: 'review',
|
|
behaviorType: 'review',
|
|
targetId: 'P1004',
|
|
targetId: 'P1004',
|
|
targetName: '运动相机 4K',
|
|
targetName: '运动相机 4K',
|
|
@@ -519,11 +523,145 @@ const handleExportReport = () => {
|
|
console.log('导出报表')
|
|
console.log('导出报表')
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// 初始化图表
|
|
|
|
+const initCharts = () => {
|
|
|
|
+ // 销毁已存在的图表实例
|
|
|
|
+ if (trendChart) {
|
|
|
|
+ trendChart.dispose()
|
|
|
|
+ }
|
|
|
|
+ if (distributionChart) {
|
|
|
|
+ distributionChart.dispose()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 初始化趋势图表
|
|
|
|
+ if (trendChartRef.value) {
|
|
|
|
+ trendChart = echarts.init(trendChartRef.value)
|
|
|
|
+ const trendOption = {
|
|
|
|
+ title: {
|
|
|
|
+ text: '用户行为趋势'
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'axis'
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ data: ['行为数', '用户数', '平均时长(分钟)']
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'category',
|
|
|
|
+ data: behaviorTrends.value.map(item => item.date)
|
|
|
|
+ },
|
|
|
|
+ yAxis: [
|
|
|
|
+ {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '数量'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ type: 'value',
|
|
|
|
+ name: '时长',
|
|
|
|
+ position: 'right'
|
|
|
|
+ }
|
|
|
|
+ ],
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ name: '行为数',
|
|
|
|
+ type: 'line',
|
|
|
|
+ data: behaviorTrends.value.map(item => item.behaviors)
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: '用户数',
|
|
|
|
+ type: 'line',
|
|
|
|
+ data: behaviorTrends.value.map(item => item.users)
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: '平均时长(分钟)',
|
|
|
|
+ type: 'line',
|
|
|
|
+ yAxisIndex: 1,
|
|
|
|
+ data: behaviorTrends.value.map(item => item.duration)
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+ trendChart.setOption(trendOption)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 初始化分布图表
|
|
|
|
+ if (distributionChartRef.value) {
|
|
|
|
+ distributionChart = echarts.init(distributionChartRef.value)
|
|
|
|
+ const distributionOption = {
|
|
|
|
+ title: {
|
|
|
|
+ text: '行为类型分布'
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'item'
|
|
|
|
+ },
|
|
|
|
+ legend: {
|
|
|
|
+ orient: 'vertical',
|
|
|
|
+ left: 'left'
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ name: '行为类型',
|
|
|
|
+ type: 'pie',
|
|
|
|
+ radius: '50%',
|
|
|
|
+ data: behaviorTypeDistribution.value.map(item => ({
|
|
|
|
+ name: item.type === 'browse' ? '浏览' :
|
|
|
|
+ item.type === 'search' ? '搜索' :
|
|
|
|
+ item.type === 'purchase' ? '购买' : '评论',
|
|
|
|
+ value: item.value
|
|
|
|
+ })),
|
|
|
|
+ emphasis: {
|
|
|
|
+ itemStyle: {
|
|
|
|
+ shadowBlur: 10,
|
|
|
|
+ shadowOffsetX: 0,
|
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+ distributionChart.setOption(distributionOption)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理图片加载错误
|
|
|
|
+const handleImageError = (event) => {
|
|
|
|
+ event.target.src = 'https://via.placeholder.com/40' // 使用默认头像
|
|
|
|
+}
|
|
|
|
+
|
|
// 生命周期钩子
|
|
// 生命周期钩子
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
// 初始化数据
|
|
// 初始化数据
|
|
console.log('用户行为分析页面已加载')
|
|
console.log('用户行为分析页面已加载')
|
|
|
|
+
|
|
|
|
+ // 初始化图表
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ initCharts()
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 监听窗口大小变化,重新调整图表大小
|
|
|
|
+ window.addEventListener('resize', handleResize)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ // 销毁图表实例
|
|
|
|
+ if (trendChart) {
|
|
|
|
+ trendChart.dispose()
|
|
|
|
+ }
|
|
|
|
+ if (distributionChart) {
|
|
|
|
+ distributionChart.dispose()
|
|
|
|
+ }
|
|
|
|
+ // 移除事件监听
|
|
|
|
+ window.removeEventListener('resize', handleResize)
|
|
})
|
|
})
|
|
|
|
+
|
|
|
|
+// 处理窗口大小变化
|
|
|
|
+const handleResize = () => {
|
|
|
|
+ if (trendChart) {
|
|
|
|
+ trendChart.resize()
|
|
|
|
+ }
|
|
|
|
+ if (distributionChart) {
|
|
|
|
+ distributionChart.resize()
|
|
|
|
+ }
|
|
|
|
+}
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|