Browse Source

用户类登录统计页面完成;

zhaowenkang/feature-20260206140254-后台AI复盘二期
songjie 1 month ago
parent
commit
cdc4fc9192
  1. 594
      src/views/PlatformData/UserLoginStats.vue

594
src/views/PlatformData/UserLoginStats.vue

@ -1,14 +1,606 @@
<template>
<div class="user-login-stats-container">
<h1>用户类登录统计</h1>
<div class="tab-header">
<div
class="tab-item"
:class="{ active: activeTab === 'loginData' }"
@click="activeTab = 'loginData'"
>
登录数据
</div>
<div
class="tab-item"
:class="{ active: activeTab === 'regionalData' }"
@click="activeTab = 'regionalData'"
>
各地区登录数据
</div>
</div>
<!-- 登录数据 Tab -->
<div v-show="activeTab === 'loginData'" class="tab-content">
<!-- 搜索栏 -->
<div class="search-bar">
<div class="search-label">地区</div>
<el-select v-model="selectedRegion" placeholder="请选择所属地区" style="width: 200px; margin-right: 20px;">
<el-option label="全部" value="all" />
<el-option label="中国" value="cn" />
<el-option label="美国" value="us" />
</el-select>
<div class="search-label">时间段查询</div>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
size="default"
/>
<el-button type="primary" class="search-btn">搜索</el-button>
<el-button type="primary" class="reset-btn">重置</el-button>
<el-button type="danger" class="export-btn">数据导出</el-button>
</div>
<!-- 统计卡片 -->
<div class="stats-row">
<!-- 今日登录用户数 -->
<div class="stat-card purple-gradient big-card">
<div class="card-title">
<el-icon><UserFilled /></el-icon>
</div>
<div class="big-card-content">
<div class="card-value">154,838</div>
<div class="card-tag up">
较昨日增加 5.22%
</div>
</div>
</div>
<div class="right-stats-col">
<!-- 今日登录会员用户数 -->
<div class="stat-card orange-gradient small-card">
<div class="top-row">
<div class="card-title">
<el-icon><Trophy /></el-icon>
</div>
<div class="card-value-small">112,265</div>
</div>
<div class="card-tag-wrapper">
<div class="card-tag up">较昨日增加 15.22%</div>
</div>
</div>
<!-- 今日登录非网用户数 -->
<div class="stat-card blue-gradient small-card">
<div class="top-row">
<div class="card-title">
<el-icon><User /></el-icon>
</div>
<div class="card-value-small">42,567</div>
</div>
<div class="card-tag-wrapper">
<div class="card-tag down">较昨日减少 1.22%</div>
</div>
</div>
</div>
</div>
<!-- 近7天登录趋势 -->
<div class="chart-section">
<div class="section-title"><el-icon><TrendCharts /></el-icon> 7</div>
<div ref="chartTrendRef" class="chart-box-large"></div>
</div>
<!-- 表格1: 今日登录数据 -->
<div class="detail-section">
<div class="section-title"><el-icon><DataLine /></el-icon> </div>
<el-table :data="loginTableData1" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="channel" label="来源渠道" />
<el-table-column prop="total" label="今日登录总数" />
<el-table-column prop="dailyNew" label="较昨日新增" />
<el-table-column prop="percent" label="占比" />
</el-table>
</div>
<!-- 表格2: 会员登录数据 -->
<div class="detail-section">
<div class="section-title"><el-icon><Trophy /></el-icon> </div>
<el-table :data="loginTableData2" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="channel" label="来源渠道" />
<el-table-column prop="total" label="今日登录会员数" />
<el-table-column prop="dailyNew" label="较昨日新增" />
<el-table-column prop="rate" label="会员登录率" />
</el-table>
</div>
<!-- 表格3: 非网登录数据 -->
<div class="detail-section">
<div class="section-title"><el-icon><User /></el-icon> </div>
<el-table :data="loginTableData3" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="channel" label="来源渠道" />
<el-table-column prop="total" label="今日登录会员数" />
<el-table-column prop="dailyNew" label="较昨日新增" />
<el-table-column prop="rate" label="非网登录率" />
</el-table>
</div>
</div>
<!-- 各地区登录数据 Tab -->
<div v-show="activeTab === 'regionalData'" class="tab-content">
<!-- 搜索栏 -->
<div class="search-bar">
<div class="search-label">地区查询</div>
<el-input v-model="searchRegion" placeholder="请输入地区" style="width: 200px; margin-right: 20px;" />
<div class="search-label">时间段查询</div>
<el-date-picker
v-model="dateRangeRegion"
type="daterange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
size="default"
/>
<el-button type="primary" class="search-btn">搜索</el-button>
<el-button type="primary" class="reset-btn">重置</el-button>
<el-button type="danger" class="export-btn">数据导出</el-button>
</div>
<!-- 表格1: 各地区活跃数据 -->
<div class="detail-section">
<div class="section-title"><el-icon><Location /></el-icon> </div>
<el-table :data="regionalTableData1" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="region" label="地区" />
<el-table-column prop="dailyActive" label="日活跃用户" />
<el-table-column prop="weeklyActive" label="周活跃用户" />
<el-table-column prop="monthlyActive" label="月活跃用户" />
<el-table-column prop="periodActive" label="区间活跃用户" />
<el-table-column prop="percent" label="活跃度占比" />
</el-table>
</div>
<!-- 表格2: 各地区会员活跃数据 -->
<div class="detail-section">
<div class="section-title"><el-icon><Trophy /></el-icon> </div>
<el-table :data="regionalTableData2" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="region" label="地区" />
<el-table-column prop="dailyActive" label="日活跃用户" />
<el-table-column prop="weeklyActive" label="周活跃用户" />
<el-table-column prop="monthlyActive" label="月活跃用户" />
<el-table-column prop="periodActive" label="区间活跃用户" />
<el-table-column prop="percent" label="活跃度占比" />
</el-table>
</div>
<!-- 表格3: 各地区非网活跃数据 -->
<div class="detail-section">
<div class="section-title"><el-icon><User /></el-icon> </div>
<el-table :data="regionalTableData3" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="region" label="地区" />
<el-table-column prop="dailyActive" label="日活跃用户" />
<el-table-column prop="weeklyActive" label="周活跃用户" />
<el-table-column prop="monthlyActive" label="月活跃用户" />
<el-table-column prop="periodActive" label="区间活跃用户" />
<el-table-column prop="percent" label="活跃度占比" />
</el-table>
</div>
<!-- 图表: 各地区日活跃用户 -->
<div class="chart-section">
<div class="section-title"><el-icon><BarChart /></el-icon>
<el-button size="small" style="margin-left: auto;">切换</el-button>
</div>
<div ref="chartRegionBarRef" class="chart-box-large"></div>
</div>
<!-- 图表: 地区分布 -->
<div class="chart-section">
<div class="section-title"><el-icon><PieChart /></el-icon> </div>
<div ref="chartRegionPieRef" class="chart-box-large" style="height: 400px;"></div>
</div>
<div class="charts-row">
<div class="chart-section half-width">
<div class="section-title">各地区会员用户分布</div>
<div ref="chartRegionMemberPieRef" class="chart-box-medium"></div>
</div>
<div class="chart-section half-width">
<div class="section-title">各地区非网用户分布</div>
<div ref="chartRegionNonMemberPieRef" class="chart-box-medium"></div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import * as echarts from 'echarts';
const route = useRoute();
const router = useRouter();
const activeTab = ref(route.query.tab || 'loginData');
const dateRange = ref('');
const selectedRegion = ref('');
const searchRegion = ref('');
const dateRangeRegion = ref('');
const chartTrendRef = ref(null);
const chartRegionBarRef = ref(null);
const chartRegionPieRef = ref(null);
const chartRegionMemberPieRef = ref(null);
const chartRegionNonMemberPieRef = ref(null);
// Tab 1
const loginTableData1 = [
{ channel: 'App Store', total: '154,832', dailyNew: '', percent: '38%' },
{ channel: 'Play Store', total: '42,567', dailyNew: '', percent: '30%' },
{ channel: 'H5', total: '112,265', dailyNew: '', percent: '17%' },
{ channel: 'APK', total: '68,420', dailyNew: '', percent: '10%' },
{ channel: '总计', total: '68,420', dailyNew: '', percent: '100%' },
];
const loginTableData2 = [
{ channel: 'App Store', total: '1,245', dailyNew: '', rate: '38%' },
{ channel: 'Play Store', total: '987', dailyNew: '', rate: '30%' },
{ channel: 'H5', total: '543', dailyNew: '', rate: '17%' },
{ channel: 'APK', total: '321', dailyNew: '', rate: '10%' },
{ channel: '总计', total: '3,096', dailyNew: '', rate: '100%' },
];
const loginTableData3 = [
{ channel: 'App Store', total: '1,245', dailyNew: '', rate: '38%' },
{ channel: 'Play Store', total: '987', dailyNew: '', rate: '30%' },
{ channel: 'H5', total: '543', dailyNew: '', rate: '17%' },
{ channel: 'APK', total: '321', dailyNew: '', rate: '10%' },
{ channel: '总计', total: '3,096', dailyNew: '', rate: '100%' },
];
// Tab 2
const regionalTableData1 = [
{ region: '香港', dailyActive: '1,245', weeklyActive: '1,245', monthlyActive: '1,245', periodActive: '', percent: '38%' },
{ region: '新加坡', dailyActive: '987', weeklyActive: '987', monthlyActive: '807', periodActive: '', percent: '30%' },
{ region: '泰国', dailyActive: '543', weeklyActive: '543', monthlyActive: '543', periodActive: '', percent: '17%' },
{ region: '越南', dailyActive: '321', weeklyActive: '321', monthlyActive: '321', periodActive: '', percent: '10%' },
{ region: '马来西亚', dailyActive: '321', weeklyActive: '321', monthlyActive: '321', periodActive: '', percent: '10%' },
{ region: '加拿大', dailyActive: '321', weeklyActive: '321', monthlyActive: '321', periodActive: '', percent: '10%' },
{ region: '其他', dailyActive: '321', weeklyActive: '321', monthlyActive: '321', periodActive: '', percent: '10%' },
{ region: '总计', dailyActive: '3,096', weeklyActive: '3,096', monthlyActive: '3,096', periodActive: '', percent: '100%' },
];
const regionalTableData2 = JSON.parse(JSON.stringify(regionalTableData1)); //
const regionalTableData3 = JSON.parse(JSON.stringify(regionalTableData1)); //
const headerCellStyle = {
background: '#fff0f0',
color: '#333',
fontWeight: 'bold'
};
const initCharts = () => {
nextTick(() => {
if (activeTab.value === 'loginData') {
if (chartTrendRef.value) {
const chart = echarts.init(chartTrendRef.value);
chart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: ['新用户', '老用户'], top: 'top' },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: ['六天前', '五天前', '四天前', '三天前', '两天前', '昨天', '今天']
},
yAxis: { type: 'value' },
series: [
{
name: '新用户',
type: 'line',
data: [1350, 1250, 1100, 1050, 1050, 1300, 1000],
smooth: true,
itemStyle: { color: '#40a9ff' }
},
{
name: '老用户',
type: 'line',
data: [300, 600, 320, 320, 400, 550, 650],
smooth: true,
itemStyle: { color: '#52c41a' }
}
]
});
}
} else if (activeTab.value === 'regionalData') {
//
if (chartRegionBarRef.value) {
const chart = echarts.init(chartRegionBarRef.value);
chart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: ['日活跃用户', '周活跃用户', '月活跃用户'], top: 'top' },
xAxis: {
type: 'category',
data: ['香港', '新加坡', '泰国', '越南', '马来西亚', '加拿大', '美国']
},
yAxis: { type: 'value' },
series: [
{ name: '日活跃用户', type: 'bar', data: [400, 400, 400, 500, 400, 400, 400], itemStyle: { color: '#40a9ff' } },
{ name: '周活跃用户', type: 'bar', data: [500, 500, 500, 600, 500, 500, 500], itemStyle: { color: '#52c41a' } },
{ name: '月活跃用户', type: 'bar', data: [600, 600, 600, 700, 600, 600, 600], itemStyle: { color: '#b37feb' } }
]
});
}
// -
if (chartRegionPieRef.value) {
const chart = echarts.init(chartRegionPieRef.value);
chart.setOption({
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', right: '10%', top: 'center' },
series: [
{
type: 'pie',
radius: '80%',
center: ['40%', '50%'],
data: [
{ value: 1048, name: '香港地区' },
{ value: 735, name: '新加坡地区' },
{ value: 580, name: '泰国地区' },
{ value: 484, name: '越南地区' },
{ value: 300, name: '马来西亚地区' },
{ value: 300, name: '其他地区' }
],
itemStyle: {
borderRadius: 0,
borderColor: '#fff',
borderWidth: 2
},
label: { show: false }
}
]
});
}
// -
if (chartRegionMemberPieRef.value) {
const chart = echarts.init(chartRegionMemberPieRef.value);
chart.setOption({
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', right: '0%', top: 'center', itemWidth: 10, itemHeight: 10, textStyle: { fontSize: 10 } },
series: [
{
type: 'pie',
radius: '70%',
center: ['30%', '50%'],
data: [
{ value: 1048, name: '香港地区' },
{ value: 735, name: '新加坡地区' },
{ value: 580, name: '泰国地区' },
{ value: 484, name: '越南地区' },
{ value: 300, name: '马来西亚地区' },
{ value: 300, name: '其他地区' }
],
label: { show: false }
}
]
});
}
// -
if (chartRegionNonMemberPieRef.value) {
const chart = echarts.init(chartRegionNonMemberPieRef.value);
chart.setOption({
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', right: '0%', top: 'center', itemWidth: 10, itemHeight: 10, textStyle: { fontSize: 10 } },
series: [
{
type: 'pie',
radius: '70%',
center: ['30%', '50%'],
data: [
{ value: 1048, name: '香港地区' },
{ value: 735, name: '新加坡地区' },
{ value: 580, name: '泰国地区' },
{ value: 484, name: '越南地区' },
{ value: 300, name: '马来西亚地区' },
{ value: 300, name: '其他地区' }
],
label: { show: false }
}
]
});
}
}
});
};
watch(activeTab, (newVal) => {
router.replace({ query: { ...route.query, tab: newVal } });
initCharts();
});
onMounted(() => {
initCharts();
});
</script>
<style scoped>
.user-login-stats-container {
padding: 20px;
background-color: #fee6e6;
min-height: calc(100vh - 40px);
}
/* Tabs */
.tab-header {
display: flex;
margin-bottom: 20px;
}
.tab-item {
padding: 6px 16px;
margin-right: 10px;
background-color: #fff;
border: 1px solid #ffcccc;
border-radius: 4px;
cursor: pointer;
color: #ff4d4f;
font-size: 14px;
}
.tab-item.active {
background-color: #ff4d4f;
color: #fff;
}
/* Search Bar */
.search-bar {
background: #fff;
padding: 15px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
border: 1px solid #f0f0f0;
}
.search-label {
font-weight: bold;
margin-right: 5px;
}
.search-btn, .reset-btn {
width: 80px;
}
.search-btn {
background-color: #409eff;
}
.reset-btn {
background-color: #a0cfff;
border-color: #a0cfff;
}
.export-btn {
margin-left: auto;
background-color: #ff7875;
border-color: #ff7875;
}
/* Cards */
.stats-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.big-card {
flex: 1;
height: 360px;
border-radius: 12px;
padding: 24px;
display: flex;
flex-direction: column;
justify-content: flex-start;
color: #fff;
position: relative;
}
.right-stats-col {
flex: 2;
display: flex;
flex-direction: column;
gap: 20px;
}
.small-card {
flex: 1;
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: flex-start;
color: #fff;
position: relative;
}
.purple-gradient { background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%); }
.orange-gradient { background: linear-gradient(90deg, #ff8c6d 0%, #ffcba4 100%); }
.blue-gradient { background: linear-gradient(135deg, #9BB7FC 0%, #66a6ff 100%); }
.card-title {
font-size: 24px;
font-weight: bold;
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
width: 100%;
}
.big-card-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
}
.card-value {
font-size: 64px;
font-weight: bold;
margin: 0;
text-align: center;
}
.card-value-small {
font-size: 48px;
font-weight: bold;
margin-left: auto;
}
.top-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-tag-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.card-tag {
background-color: #fff;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
display: inline-block;
}
.big-card .card-tag { font-size: 18px; }
.card-tag.up { color: #52c41a; }
.card-tag.down { color: #ff4d4f; }
/* Sections */
.chart-section, .detail-section {
background: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
border: 1px solid #f0f0f0;
}
.section-title {
color: #409eff;
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 5px;
}
.chart-box-large {
width: 100%;
height: 400px;
}
.charts-row {
display: flex;
gap: 20px;
}
.half-width {
flex: 1;
}
.chart-box-medium {
width: 100%;
height: 300px;
}
</style>
Loading…
Cancel
Save