deepchart后台管理系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

475 lines
13 KiB

<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>