|
|
<template> <div class="user-activity-stats-container"> <!-- 用户活跃度趋势图 --> <div class="content-card"> <div class="card-header"> <el-icon class="header-icon"><TrendCharts /></el-icon> <span class="header-title">用户活跃度趋势图</span> </div> <div class="card-filter-row"> <div class="filter-left"> <span class="filter-label">时间段查询</span> <el-date-picker v-model="chartDateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" size="default" @change="handleChartDateChange" /> </div> <div class="filter-right"> <el-button-group> <el-button :type="chartMode === 'day' ? 'danger' : 'default'" @click="handleModeChange('day')">每日</el-button> <el-button :type="chartMode === 'week' ? 'danger' : 'default'" @click="handleModeChange('week')">近七日</el-button> <el-button :type="chartMode === 'month' ? 'danger' : 'default'" @click="handleModeChange('month')">近三十日</el-button> </el-button-group> </div> </div>
<div class="chart-container" ref="chartRef"></div> </div>
<!-- DeepChart活跃用户明细 --> <div class="content-card"> <div class="card-header"> <el-icon class="header-icon"><DataLine /></el-icon> <span class="header-title">DeepChart活跃用户明细</span> </div>
<div class="table-filter-row"> <el-input v-model="tableAccount" placeholder="请输入账号" style="width: 150px" /> <el-select v-model="tableRegion" placeholder="请选择所属地区" style="width: 150px"> <el-option label="全部" value="all" /> <el-option v-for="item in regionOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> <el-date-picker v-model="tableDateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" size="default" style="width: 240px" /> <el-button type="primary" class="search-btn-small" @click="handleTableSearch">搜索</el-button> <el-button type="primary" class="reset-btn-small" @click="handleTableReset">重置</el-button> <el-button type="danger" class="export-btn-small">数据导出</el-button> <el-button type="danger" class="export-list-btn">查看导出列表</el-button> </div>
<el-table :data="tableData" style="width: 100%" :header-cell-style="headerCellStyle"> <el-table-column prop="index" label="序号" width="80" align="center" /> <el-table-column prop="account" label="账号" min-width="120" /> <el-table-column prop="name" label="姓名" min-width="120" /> <el-table-column prop="region" label="地区" width="100" /> <el-table-column prop="loginCount" label="登录次数" width="100" align="center" /> <el-table-column prop="totalDuration" label="总停留时长" min-width="150" align="center" /> <el-table-column prop="avgDuration" label="平均停留时长" min-width="150" align="center" /> <el-table-column prop="deepMate" label="DeepMate" min-width="100" align="center" /> <el-table-column prop="deepExplore" label="深度探索" min-width="100" align="center" /> <el-table-column prop="marketInfo" label="行情" min-width="100" align="center" /> <el-table-column prop="updateTime" label="更新日期" min-width="150" align="center" /> </el-table>
<div class="pagination-container"> <div class="total-count">共{{ total }}条</div> <el-pagination background layout="sizes, prev, pager, next, jumper" :total="total" :page-sizes="[10, 20, 50, 100]" v-model:current-page="currentPage" v-model:page-size="pageSize" @current-change="handlePageChange" @size-change="handleSizeChange" /> </div> </div> </div></template>
<script setup>import { ref, onMounted, nextTick, watch } from 'vue';import { useRoute, useRouter } from 'vue-router';import * as echarts from 'echarts';import { getUserDeepChartTrend, getDeepChartActiveUserList, getRegionsList } from '../../api/platformData';
const route = useRoute();const router = useRouter();
// 图表筛选
const chartDateRange = ref('');const chartMode = ref(route.query.mode || 'day'); // day, week, month
const chartRef = ref(null);let chartInstance = null;
// 初始化 URL 参数
const initQueryParams = () => { const { mode, startTime, endTime } = route.query; if (mode) { chartMode.value = mode; } if (startTime && endTime) { chartDateRange.value = [new Date(startTime), new Date(endTime)]; }};
initQueryParams();
const updateUrlParams = () => { const query = { ...route.query }; query.mode = chartMode.value; if (chartDateRange.value && chartDateRange.value.length === 2) { query.startTime = formatDate(chartDateRange.value[0]); query.endTime = formatDate(chartDateRange.value[1]); } else { delete query.startTime; delete query.endTime; } router.replace({ query });};
const handleChartDateChange = () => { updateUrlParams(); fetchChartData();};
const handleModeChange = (mode) => { chartMode.value = mode; updateUrlParams(); fetchChartData();};
// 格式化日期
const formatDate = (date) => { if (!date) return ''; const d = new Date(date); const pad = (n) => n < 10 ? '0' + n : n; return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;};
const fetchChartData = async () => { const params = { mode: chartMode.value };
if (chartDateRange.value && chartDateRange.value.length === 2) { params.startTime = formatDate(chartDateRange.value[0]); params.endTime = formatDate(chartDateRange.value[1]); }
try { const res = await getUserDeepChartTrend(params); console.log("获取DeepChart趋势数据响应:", res); const data = res.data || res; // 兼容处理
if (Array.isArray(data)) { updateChart(data); } } catch (e) { console.error('获取DeepChart趋势数据失败:', e); }};
const updateChart = (data) => { if (!chartRef.value) return;
if (!chartInstance) { chartInstance = echarts.init(chartRef.value); }
const dates = data.map(item => item.time_point); const activeUsers = data.map(item => item.active_users); const useCounts = data.map(item => item.use_count);
const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } }, legend: { data: ['DeepChart活跃人数', 'DeepChart使用次数'], top: 'top', left: 'center', itemWidth: 35, itemHeight: 18, textStyle: { fontSize: 14 } }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: dates }, yAxis: [ { type: 'value', min: 0, position: 'left', axisLabel: { formatter: '{value}' } }, { type: 'value', min: 0, position: 'right', axisLabel: { formatter: '{value}' } } ], series: [ { name: 'DeepChart活跃人数', type: 'line', yAxisIndex: 0, data: activeUsers, itemStyle: { color: '#e74c3c' }, symbol: 'circle', symbolSize: 8, lineStyle: { width: 3 } }, { name: 'DeepChart使用次数', type: 'line', yAxisIndex: 1, data: useCounts, itemStyle: { color: '#2ecc71' }, symbol: 'circle', symbolSize: 8, lineStyle: { width: 3 } } ] }; chartInstance.setOption(option);};const tableAccount = ref('');const tableRegion = ref('');const tableDateRange = ref('');const currentPage = ref(1);const pageSize = ref(10);const total = ref(0);const regionOptions = ref([]);
// 获取地区列表
const fetchRegionOptions = async () => { try { const res = await getRegionsList(); console.log("获取地区列表响应:", res); const data = res.data || res; if (data && data.list) { regionOptions.value = data.list.map(region => ({ label: region, value: region })); } } catch (e) { console.error('获取地区列表失败:', e); }};
// 表格数据
const tableData = ref([]);
const handleTableSearch = () => { currentPage.value = 1; fetchTableData();};
const handleTableReset = () => { tableAccount.value = ''; tableRegion.value = ''; tableDateRange.value = ''; currentPage.value = 1; fetchTableData();};
const handlePageChange = (page) => { currentPage.value = page; fetchTableData();};
const handleSizeChange = (size) => { pageSize.value = size; currentPage.value = 1; fetchTableData();};
const fetchTableData = async () => { const params = { page: currentPage.value, page_size: pageSize.value };
if (tableAccount.value) params.jwcode = tableAccount.value; if (tableRegion.value && tableRegion.value !== 'all') params.region = tableRegion.value; if (tableDateRange.value && tableDateRange.value.length === 2) { params.startTime = formatDate(tableDateRange.value[0]); params.endTime = formatDate(tableDateRange.value[1]); }
try { const res = await getDeepChartActiveUserList(params); console.log("获取DeepChart活跃用户明细响应:", res); const data = res.data || res; // 兼容处理
if (data && data.list) { tableData.value = data.list.map((item, index) => ({ index: (currentPage.value - 1) * pageSize.value + index + 1, account: item.jwcode, name: item.username, region: item.region, loginCount: item.login_count, totalDuration: formatDuration(item.stay_time), avgDuration: formatDuration(item.avg_stay_time), deepMate: item.deepmate_count, deepExplore: item.dive_seek_count, marketInfo: item.market_info_count, // 行情登录次数
updateTime: item.update_time // 使用 update_time
})); total.value = data.total || 0; } else { tableData.value = []; total.value = 0; } } catch (e) { console.error('获取DeepChart活跃用户明细失败:', e); }};
// 格式化时长 (秒 -> 时分秒)
const formatDuration = (seconds) => { if (!seconds) return '0秒'; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; let str = ''; if (h > 0) str += `${h}小时`; if (m > 0) str += `${m}分钟`; if (s > 0) str += `${s}秒`; return str || '0秒';};
const headerCellStyle = { background: '#f5f7fa', color: '#333', fontWeight: 'bold'};
const initChart = () => { if (chartRef.value) { // 初始化图表实例
if (!chartInstance) { chartInstance = echarts.init(chartRef.value); } // 初始加载数据
fetchChartData(); }};
onMounted(() => { nextTick(() => { initChart(); fetchTableData(); fetchRegionOptions(); });});</script>
<style scoped>.user-activity-stats-container { padding: 20px; background-color: #fee6e6; min-height: calc(100vh - 40px);}
/* Content Cards */.content-card { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 20px; border: 1px solid #f0f0f0;}.card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 20px;}.header-icon { color: #409eff; font-size: 20px;}.header-title { font-size: 18px; font-weight: bold; color: #333;}
/* Chart Section */.card-filter-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; background: #fafafa; padding: 10px; border-radius: 4px;}.filter-left { display: flex; align-items: center; gap: 10px;}.filter-label { font-size: 14px; font-weight: bold;}.chart-container { width: 100%; height: 400px;}
/* Table Section */.table-filter-row { display: flex; align-items: center; gap: 10px; margin-bottom: 20px;}.search-btn-small { background-color: #409eff; width: 70px; }.reset-btn-small { background-color: #409eff; border-color: #409eff; width: 70px; }.export-btn-small { background-color: #ff7875; border-color: #ff7875; width: 90px; }.export-list-btn { background-color: #ff7875; border-color: #ff7875; width: 110px; }
/* Pagination */.pagination-container { display: flex; justify-content: center; align-items: center; margin-top: 20px;}.total-count { font-size: 14px; color: #606266; margin-right: 20px;}</style>
|