|
@@ -0,0 +1,536 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
|
+ <nav class="bg-white dark:bg-gray-800 shadow-sm">
|
|
|
|
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
|
+ <div class="flex justify-between h-16">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <h1 class="text-xl font-bold text-gray-800 dark:text-white">用户行为分析</h1>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
|
+ <button
|
|
|
|
+ @click="handleExportReport"
|
|
|
|
+ class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 flex items-center space-x-2"
|
|
|
|
+ >
|
|
|
|
+ <DownloadIcon class="h-5 w-5" />
|
|
|
|
+ <span>导出报表</span>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </nav>
|
|
|
|
+
|
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
|
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
+ <!-- 搜索和筛选区域 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
+ <div class="relative">
|
|
|
|
+ <input
|
|
|
|
+ v-model="searchText"
|
|
|
|
+ @input="handleSearch"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="搜索用户ID/名称..."
|
|
|
|
+ class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
|
|
|
+ />
|
|
|
|
+ <SearchIcon class="absolute left-3 top-2.5 h-5 w-5 text-gray-400 dark:text-gray-500" />
|
|
|
|
+ </div>
|
|
|
|
+ <select
|
|
|
|
+ v-model="userType"
|
|
|
|
+ @change="handleUserTypeChange"
|
|
|
|
+ class="border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有用户类型</option>
|
|
|
|
+ <option value="new">新用户</option>
|
|
|
|
+ <option value="regular">普通用户</option>
|
|
|
|
+ <option value="vip">VIP用户</option>
|
|
|
|
+ </select>
|
|
|
|
+ <select
|
|
|
|
+ v-model="behaviorType"
|
|
|
|
+ @change="handleBehaviorTypeChange"
|
|
|
|
+ class="border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有行为类型</option>
|
|
|
|
+ <option value="browse">浏览</option>
|
|
|
|
+ <option value="search">搜索</option>
|
|
|
|
+ <option value="purchase">购买</option>
|
|
|
|
+ <option value="comment">评论</option>
|
|
|
|
+ </select>
|
|
|
|
+ <select
|
|
|
|
+ v-model="timeRange"
|
|
|
|
+ @change="handleTimeRangeChange"
|
|
|
|
+ class="border border-gray-300 dark:border-gray-600 rounded-md px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
|
|
|
+ >
|
|
|
|
+ <option value="">所有时间</option>
|
|
|
|
+ <option value="today">今天</option>
|
|
|
|
+ <option value="week">本周</option>
|
|
|
|
+ <option value="month">本月</option>
|
|
|
|
+ <option value="quarter">本季度</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 用户行为统计卡片 -->
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-blue-100 dark:bg-blue-900">
|
|
|
|
+ <UsersIcon class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-gray-600 dark:text-gray-400">活跃用户数</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">12,345</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-green-100 dark:bg-green-900">
|
|
|
|
+ <ActivityIcon class="h-6 w-6 text-green-600 dark:text-green-400" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-gray-600 dark:text-gray-400">平均访问时长</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">8.5分钟</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-yellow-100 dark:bg-yellow-900">
|
|
|
|
+ <ShoppingCartIcon class="h-6 w-6 text-yellow-600 dark:text-yellow-400" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-gray-600 dark:text-gray-400">转化率</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">3.2%</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="p-3 rounded-full bg-purple-100 dark:bg-purple-900">
|
|
|
|
+ <RepeatIcon class="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <p class="text-sm font-medium text-gray-600 dark:text-gray-400">复购率</p>
|
|
|
|
+ <p class="text-2xl font-semibold text-gray-900 dark:text-white">45.6%</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 用户行为分析图表 -->
|
|
|
|
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">用户行为趋势</h3>
|
|
|
|
+ <div ref="trendChartRef" class="h-64"></div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">行为类型分布</h3>
|
|
|
|
+ <div ref="distributionChartRef" class="h-64"></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 用户行为列表 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
|
|
|
+ <div class="overflow-x-auto">
|
|
|
|
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
+ <thead class="bg-gray-50 dark:bg-gray-700">
|
|
|
|
+ <tr>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">用户信息</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">用户类型</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">行为类型</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">行为详情</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">时间</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">操作</th>
|
|
|
|
+ </tr>
|
|
|
|
+ </thead>
|
|
|
|
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
+ <tr v-for="behavior in paginatedBehaviors" :key="behavior.id" class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
|
|
+ <td class="px-6 py-4">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gray-200 dark:bg-gray-700 rounded-full">
|
|
|
|
+ <UserIcon class="h-6 w-6 text-gray-500 dark:text-gray-400" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-4">
|
|
|
|
+ <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>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4">
|
|
|
|
+ <span :class="['user-type-tag', `type-${behavior.userType}`]">
|
|
|
|
+ {{ behavior.userType === 'new' ? '新用户' :
|
|
|
|
+ behavior.userType === 'normal' ? '普通用户' : 'VIP用户' }}
|
|
|
|
+ </span>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4">
|
|
|
|
+ <span :class="['behavior-type-tag', `type-${behavior.behaviorType}`]">
|
|
|
|
+ {{ behavior.behaviorType === 'browse' ? '浏览' :
|
|
|
|
+ behavior.behaviorType === 'search' ? '搜索' :
|
|
|
|
+ behavior.behaviorType === 'purchase' ? '购买' :
|
|
|
|
+ behavior.behaviorType === 'add_to_cart' ? '加入购物车' : '评论' }}
|
|
|
|
+ </span>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ behavior.targetName }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ behavior.timestamp }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 text-sm font-medium">
|
|
|
|
+ <button
|
|
|
|
+ @click="handleViewBehavior(behavior)"
|
|
|
|
+ class="action-button view-button"
|
|
|
|
+ >
|
|
|
|
+ <EyeIcon class="h-4 w-4 inline-block mr-1" />
|
|
|
|
+ 查看
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handleExportBehavior(behavior)"
|
|
|
|
+ class="action-button export-button"
|
|
|
|
+ >
|
|
|
|
+ <DownloadIcon class="h-4 w-4 inline-block mr-1" />
|
|
|
|
+ 导出
|
|
|
|
+ </button>
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ </tbody>
|
|
|
|
+ </table>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-gray-700 sm:px-6 mt-4">
|
|
|
|
+ <div class="flex-1 flex justify-between sm:hidden">
|
|
|
|
+ <button
|
|
|
|
+ @click="handlePageChange(currentPage - 1)"
|
|
|
|
+ :disabled="currentPage === 1"
|
|
|
|
+ class="pagination-button"
|
|
|
|
+ >
|
|
|
|
+ 上一页
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handlePageChange(currentPage + 1)"
|
|
|
|
+ :disabled="currentPage === totalPages"
|
|
|
|
+ class="pagination-button"
|
|
|
|
+ >
|
|
|
|
+ 下一页
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
|
|
|
+ <div>
|
|
|
|
+ <p class="text-sm text-gray-700 dark:text-gray-300">
|
|
|
|
+ 显示 <span class="font-medium">{{ (currentPage - 1) * pageSize + 1 }}</span> 到
|
|
|
|
+ <span class="font-medium">{{ Math.min(currentPage * pageSize, filteredBehaviors.length) }}</span> 条,共
|
|
|
|
+ <span class="font-medium">{{ filteredBehaviors.length }}</span> 条
|
|
|
|
+ </p>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
|
|
|
+ <button
|
|
|
|
+ @click="handlePageChange(currentPage - 1)"
|
|
|
|
+ :disabled="currentPage === 1"
|
|
|
|
+ class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-600"
|
|
|
|
+ >
|
|
|
|
+ <ChevronLeftIcon class="h-5 w-5" />
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ v-for="page in totalPages"
|
|
|
|
+ :key="page"
|
|
|
|
+ @click="handlePageChange(page)"
|
|
|
|
+ :class="[
|
|
|
|
+ 'relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium',
|
|
|
|
+ currentPage === page ? 'bg-blue-600 text-white border-blue-600' : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600'
|
|
|
|
+ ]"
|
|
|
|
+ >
|
|
|
|
+ {{ page }}
|
|
|
|
+ </button>
|
|
|
|
+ <button
|
|
|
|
+ @click="handlePageChange(currentPage + 1)"
|
|
|
|
+ :disabled="currentPage === totalPages"
|
|
|
|
+ class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-600"
|
|
|
|
+ >
|
|
|
|
+ <ChevronRightIcon class="h-5 w-5" />
|
|
|
|
+ </button>
|
|
|
|
+ </nav>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+import { ref, computed, onMounted } from 'vue'
|
|
|
|
+import {
|
|
|
|
+ SearchIcon,
|
|
|
|
+ ChevronLeftIcon,
|
|
|
|
+ ChevronRightIcon,
|
|
|
|
+ UsersIcon,
|
|
|
|
+ ActivityIcon,
|
|
|
|
+ ShoppingCartIcon,
|
|
|
|
+ RepeatIcon,
|
|
|
|
+ DownloadIcon,
|
|
|
|
+ EyeIcon,
|
|
|
|
+ UserIcon
|
|
|
|
+} from 'lucide-vue-next'
|
|
|
|
+
|
|
|
|
+// 状态管理
|
|
|
|
+const isLoading = ref(false)
|
|
|
|
+const searchText = ref('')
|
|
|
|
+const userType = ref('')
|
|
|
|
+const behaviorType = ref('')
|
|
|
|
+const timeRange = ref('')
|
|
|
|
+const currentPage = ref(1)
|
|
|
|
+const pageSize = ref(10)
|
|
|
|
+
|
|
|
|
+// 图表相关
|
|
|
|
+const trendChartRef = ref(null)
|
|
|
|
+const distributionChartRef = ref(null)
|
|
|
|
+
|
|
|
|
+// 模拟数据
|
|
|
|
+const mockUserBehaviors = ref([
|
|
|
|
+ {
|
|
|
|
+ id: 'B1001',
|
|
|
|
+ userId: 'U1001',
|
|
|
|
+ userName: '张三',
|
|
|
|
+ userType: 'vip',
|
|
|
|
+ behaviorType: 'browse',
|
|
|
|
+ targetId: 'P1001',
|
|
|
|
+ targetName: '智能手表 Pro',
|
|
|
|
+ targetType: 'product',
|
|
|
|
+ duration: 300,
|
|
|
|
+ timestamp: '2024-03-20 10:30:00',
|
|
|
|
+ location: '首页',
|
|
|
|
+ device: 'iPhone 13',
|
|
|
|
+ ip: '192.168.1.1'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 'B1002',
|
|
|
|
+ userId: 'U1002',
|
|
|
|
+ userName: '李四',
|
|
|
|
+ userType: 'normal',
|
|
|
|
+ behaviorType: 'search',
|
|
|
|
+ targetId: null,
|
|
|
|
+ targetName: '无线耳机',
|
|
|
|
+ targetType: 'keyword',
|
|
|
|
+ duration: 60,
|
|
|
|
+ timestamp: '2024-03-20 11:15:00',
|
|
|
|
+ location: '搜索页',
|
|
|
|
+ device: 'Huawei P40',
|
|
|
|
+ ip: '192.168.1.2'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 'B1003',
|
|
|
|
+ userId: 'U1003',
|
|
|
|
+ userName: '王五',
|
|
|
|
+ userType: 'vip',
|
|
|
|
+ behaviorType: 'add_to_cart',
|
|
|
|
+ targetId: 'P1002',
|
|
|
|
+ targetName: '无线耳机 Air',
|
|
|
|
+ targetType: 'product',
|
|
|
|
+ duration: 120,
|
|
|
|
+ timestamp: '2024-03-20 14:20:00',
|
|
|
|
+ location: '商品详情页',
|
|
|
|
+ device: 'iPad Pro',
|
|
|
|
+ ip: '192.168.1.3'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 'B1004',
|
|
|
|
+ userId: 'U1004',
|
|
|
|
+ userName: '赵六',
|
|
|
|
+ userType: 'normal',
|
|
|
|
+ behaviorType: 'purchase',
|
|
|
|
+ targetId: 'P1003',
|
|
|
|
+ targetName: '智能音箱 Mini',
|
|
|
|
+ targetType: 'product',
|
|
|
|
+ duration: 180,
|
|
|
|
+ timestamp: '2024-03-20 16:45:00',
|
|
|
|
+ location: '购物车',
|
|
|
|
+ device: 'Xiaomi 12',
|
|
|
|
+ ip: '192.168.1.4'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 'B1005',
|
|
|
|
+ userId: 'U1005',
|
|
|
|
+ userName: '钱七',
|
|
|
|
+ userType: 'vip',
|
|
|
|
+ behaviorType: 'review',
|
|
|
|
+ targetId: 'P1004',
|
|
|
|
+ targetName: '运动相机 4K',
|
|
|
|
+ targetType: 'product',
|
|
|
|
+ duration: 240,
|
|
|
|
+ timestamp: '2024-03-20 19:30:00',
|
|
|
|
+ location: '商品评价页',
|
|
|
|
+ device: 'MacBook Pro',
|
|
|
|
+ ip: '192.168.1.5'
|
|
|
|
+ }
|
|
|
|
+])
|
|
|
|
+
|
|
|
|
+// 计算属性
|
|
|
|
+const filteredBehaviors = computed(() => {
|
|
|
|
+ let result = [...mockUserBehaviors.value]
|
|
|
|
+
|
|
|
|
+ // 搜索过滤
|
|
|
|
+ if (searchText.value) {
|
|
|
|
+ result = result.filter(behavior =>
|
|
|
|
+ behavior.userName.includes(searchText.value) ||
|
|
|
|
+ behavior.userId.includes(searchText.value)
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 用户类型过滤
|
|
|
|
+ if (userType.value) {
|
|
|
|
+ result = result.filter(behavior => behavior.userType === userType.value)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 行为类型过滤
|
|
|
|
+ if (behaviorType.value) {
|
|
|
|
+ result = result.filter(behavior => behavior.behaviorType === behaviorType.value)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 时间范围过滤
|
|
|
|
+ if (timeRange.value) {
|
|
|
|
+ const now = new Date()
|
|
|
|
+ result = result.filter(behavior => {
|
|
|
|
+ // 这里可以根据实际需求添加时间过滤逻辑
|
|
|
|
+ return true
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const paginatedBehaviors = computed(() => {
|
|
|
|
+ const start = (currentPage.value - 1) * pageSize.value
|
|
|
|
+ const end = start + pageSize.value
|
|
|
|
+ return filteredBehaviors.value.slice(start, end)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const totalPages = computed(() => {
|
|
|
|
+ return Math.ceil(filteredBehaviors.value.length / pageSize.value)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 方法
|
|
|
|
+const handleSearch = () => {
|
|
|
|
+ currentPage.value = 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handleUserTypeChange = () => {
|
|
|
|
+ currentPage.value = 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handleBehaviorTypeChange = () => {
|
|
|
|
+ currentPage.value = 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handleTimeRangeChange = () => {
|
|
|
|
+ currentPage.value = 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handlePageChange = (page) => {
|
|
|
|
+ currentPage.value = page
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handleViewBehavior = (behavior) => {
|
|
|
|
+ console.log('查看行为:', behavior)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handleExportBehavior = (behavior) => {
|
|
|
|
+ console.log('导出行为:', behavior)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const handleExportReport = () => {
|
|
|
|
+ console.log('导出报表')
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 生命周期钩子
|
|
|
|
+onMounted(() => {
|
|
|
|
+ // 初始化数据
|
|
|
|
+ console.log('用户行为分析页面已加载')
|
|
|
|
+
|
|
|
|
+ // 这里可以添加图表初始化代码
|
|
|
|
+ // 由于我们不能直接使用echarts,这里只是占位
|
|
|
|
+ // 实际项目中可以根据需要引入图表库
|
|
|
|
+})
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+@media (max-width: 475px) {
|
|
|
|
+ .grid {
|
|
|
|
+ @apply grid-cols-1;
|
|
|
|
+ }
|
|
|
|
+ table {
|
|
|
|
+ @apply block overflow-x-auto whitespace-nowrap;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 用户类型标签样式 */
|
|
|
|
+.user-type-tag {
|
|
|
|
+ @apply px-2 inline-flex text-xs leading-5 font-semibold rounded-full;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-new {
|
|
|
|
+ @apply bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-normal {
|
|
|
|
+ @apply bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-vip {
|
|
|
|
+ @apply bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 行为类型标签样式 */
|
|
|
|
+.behavior-type-tag {
|
|
|
|
+ @apply px-2 inline-flex text-xs leading-5 font-semibold rounded-full;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-browse {
|
|
|
|
+ @apply bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-search {
|
|
|
|
+ @apply bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-purchase {
|
|
|
|
+ @apply bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-add_to_cart {
|
|
|
|
+ @apply bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.type-review {
|
|
|
|
+ @apply bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-400;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 操作按钮样式 */
|
|
|
|
+.action-button {
|
|
|
|
+ @apply text-sm font-medium mr-3;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.view-button {
|
|
|
|
+ @apply text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.export-button {
|
|
|
|
+ @apply text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 分页样式 */
|
|
|
|
+.pagination-button {
|
|
|
|
+ @apply relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.pagination-button-active {
|
|
|
|
+ @apply bg-blue-600 text-white border-blue-600;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 加载动画 */
|
|
|
|
+.loading-spinner {
|
|
|
|
+ @apply animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600;
|
|
|
|
+}
|
|
|
|
+</style>
|