Files
cpzs-frontend-new/src/views/dlt/UsageStats.vue

716 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="usage-stats-page">
<!-- 现代化页面头部 -->
<div class="page-header-modern">
<div class="header-content">
<button class="back-btn" @click="goBack">
<svg viewBox="0 0 24 24" class="back-icon">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path>
</svg>
</button>
<div class="header-info">
<div class="header-text">
<h1 class="header-title">使用统计</h1>
<p class="header-subtitle">查看您的大乐透预测使用情况</p>
</div>
</div>
<!-- 彩票类型筛选 -->
<div class="lottery-type-filter">
<span class="filter-label">请选择彩票类型</span>
<div class="select-wrapper">
<el-select v-model="currentLotteryType" placeholder="选择彩票类型" @change="handleLotteryTypeChange" class="lottery-select">
<el-option
v-for="item in lotteryTypes"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</div>
</div>
<!-- 主要内容 -->
<div class="stats-content">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<div class="loading-spinner">
<div class="spinner"></div>
<p class="loading-text">正在加载数据...</p>
</div>
</div>
<!-- 数据展示 -->
<div v-else class="data-container">
<!-- 使用统计卡片 -->
<div>
<h2 class="section-title">
<svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11l3 3 8-8"/>
<path d="M21 12c0 4.97-4.03 9-9 9s-9-4.03-9-9 4.03-9 9-9c1.39 0 2.7.32 3.87.9"/>
</svg>
使用统计
</h2>
<div class="stats-card">
<div class="stats-grid">
<div class="stat-item">
<div class="stat-number">{{ userStats.predictCount }}</div>
<div class="stat-label">推测次数</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ userStats.pendingCount }}</div>
<div class="stat-label">待开奖次数</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ userStats.hitCount }}</div>
<div class="stat-label">中奖次数</div>
</div>
<div class="stat-item">
<div class="stat-number highlight">{{ formatRate(userStats.hitRate) }}</div>
<div class="stat-label">中奖率</div>
</div>
</div>
</div>
</div>
<!-- 数据说明卡片 -->
<div class="info-card">
<div class="info-header">
<svg class="info-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
</svg>
<span class="info-title">数据说明</span>
</div>
<div class="info-content">
<ul class="info-list">
<li>推测次数您进行大乐透号码预测的总次数</li>
<li>待开奖次数已预测但尚未开奖的次数</li>
<li>命中次数预测准确的总次数</li>
<li>命中率命中次数与总预测次数的比率</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import dltLotteryApi from '../../api/dlt/index.js'
import { userStore } from '../../store/user.js'
import { ElMessage } from 'element-plus'
export default {
name: 'DltUsageStats',
data() {
return {
loading: false,
currentLotteryType: 'dlt',
lotteryTypes: [
{ value: 'ssq', label: '双色球' },
{ value: 'dlt', label: '大乐透' }
// { value: '3d', label: '福彩3D' },
// { value: 'qlc', label: '七乐彩' },
// { value: 'qxc', label: '七星彩' },
// { value: 'pl3', label: '排列3' },
// { value: 'pl5', label: '排列5' },
// { value: 'kl8', label: '快乐8' }
],
userStats: {
userId: '',
predictCount: 0,
pendingCount: 0,
hitCount: 0,
drawnCount: 0,
hitRate: 0
}
}
},
async mounted() {
await this.loadUserStats()
},
methods: {
handleLotteryTypeChange() {
// 如果选择双色球,跳转到双色球使用统计页面
if (this.currentLotteryType === 'ssq') {
this.$router.push('/usage-stats')
return
}
// 其他彩票类型暂时不支持,提示用户
if (this.currentLotteryType !== 'dlt') {
ElMessage.info('该彩票类型的使用统计功能正在开发中')
this.currentLotteryType = 'dlt'
return
}
this.loadUserStats()
},
async loadUserStats() {
this.loading = true
try {
// 优先从路由参数获取userId管理员查看指定用户否则使用当前登录用户ID
const routeUserId = this.$route.query.userId
const userId = routeUserId || userStore.user?.id
if (!userId) {
this.$router.push('/login')
return
}
const response = await this.getUserPredictStats(userId)
if (response && response.success) {
this.userStats = response.data
} else {
ElMessage.error('获取使用统计失败:' + (response?.message || '未知错误'))
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败,请重试')
} finally {
this.loading = false
}
},
async getUserPredictStats(userId) {
try {
const response = await dltLotteryApi.getUserPredictStats(userId)
return response
} catch (error) {
console.error('获取用户预测统计失败:', error)
return null
}
},
formatRate(rate) {
if (typeof rate !== 'number') return '0.00%'
return (rate * 100).toFixed(2) + '%'
},
goBack() {
// 直接跳转到我的页面
this.$router.push('/profile')
}
}
}
</script>
<style scoped>
.usage-stats-page {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 0;
}
/* 页面头部 */
.page-header-modern {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
padding: 20px 0;
position: relative;
overflow: hidden;
}
.page-header-modern::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 200' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='grad1' x1='0%25' y1='0%25' x2='100%25' y2='0%25'%3E%3Cstop offset='0%25' style='stop-color:rgba(255,255,255,0.15);stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:rgba(255,255,255,0.05);stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3C!-- Background waves --%3E%3Cpath d='M0,100 Q200,60 400,100 T800,100 L800,200 L0,200 Z' fill='rgba(255,255,255,0.08)'/%3E%3Cpath d='M0,120 Q200,80 400,120 T800,120 L800,200 L0,200 Z' fill='rgba(255,255,255,0.05)'/%3E%3C!-- Chart bars animated --%3E%3Crect x='120' y='90' width='18' height='60' rx='3' fill='rgba(255,255,255,0.25)'%3E%3Canimate attributeName='height' values='60;80;60' dur='2.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='y' values='90;70;90' dur='2.5s' repeatCount='indefinite'/%3E%3C/rect%3E%3Crect x='150' y='75' width='18' height='75' rx='3' fill='rgba(255,255,255,0.3)'%3E%3Canimate attributeName='height' values='75;95;75' dur='2.2s' repeatCount='indefinite'/%3E%3Canimate attributeName='y' values='75;55;75' dur='2.2s' repeatCount='indefinite'/%3E%3C/rect%3E%3Crect x='180' y='85' width='18' height='65' rx='3' fill='rgba(255,255,255,0.28)'%3E%3Canimate attributeName='height' values='65;85;65' dur='2.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='y' values='85;65;85' dur='2.8s' repeatCount='indefinite'/%3E%3C/rect%3E%3Crect x='210' y='70' width='18' height='80' rx='3' fill='rgba(255,255,255,0.35)'%3E%3Canimate attributeName='height' values='80;100;80' dur='2.3s' repeatCount='indefinite'/%3E%3Canimate attributeName='y' values='70;50;70' dur='2.3s' repeatCount='indefinite'/%3E%3C/rect%3E%3C!-- Trend line with dots --%3E%3Cpolyline points='300,110 380,85 460,95 540,70 620,80' stroke='rgba(255,255,255,0.45)' stroke-width='3.5' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Canimate attributeName='points' values='300,110 380,85 460,95 540,70 620,80;300,105 380,80 460,90 540,65 620,75;300,110 380,85 460,95 540,70 620,80' dur='4s' repeatCount='indefinite'/%3E%3C/polyline%3E%3C!-- Data points on line --%3E%3Ccircle cx='300' cy='110' r='6' fill='rgba(255,255,255,0.7)'%3E%3Canimate attributeName='cy' values='110;105;110' dur='4s' repeatCount='indefinite'/%3E%3Canimate attributeName='r' values='6;8;6' dur='4s' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='380' cy='85' r='6' fill='rgba(255,255,255,0.7)'%3E%3Canimate attributeName='cy' values='85;80;85' dur='4s' repeatCount='indefinite'/%3E%3Canimate attributeName='r' values='6;8;6' dur='4s' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='460' cy='95' r='6' fill='rgba(255,255,255,0.7)'%3E%3Canimate attributeName='cy' values='95;90;95' dur='4s' repeatCount='indefinite'/%3E%3Canimate attributeName='r' values='6;8;6' dur='4s' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='540' cy='70' r='6' fill='rgba(255,255,255,0.8)'%3E%3Canimate attributeName='cy' values='70;65;70' dur='4s' repeatCount='indefinite'/%3E%3Canimate attributeName='r' values='6;9;6' dur='4s' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='620' cy='80' r='6' fill='rgba(255,255,255,0.7)'%3E%3Canimate attributeName='cy' values='80;75;80' dur='4s' repeatCount='indefinite'/%3E%3Canimate attributeName='r' values='6;8;6' dur='4s' repeatCount='indefinite'/%3E%3C/circle%3E%3C!-- Percentage symbols --%3E%3Ctext x='680' y='85' font-size='28' fill='rgba(255,255,255,0.3)' font-weight='bold'%3E%25%3Canimate attributeName='y' values='85;70;85' dur='3.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='opacity' values='0.3;0.5;0.3' dur='3.5s' repeatCount='indefinite'/%3E%3C/text%3E%3Ctext x='730' y='105' font-size='22' fill='rgba(255,255,255,0.25)' font-weight='bold'%3E%25%3Canimate attributeName='y' values='105;90;105' dur='3s' repeatCount='indefinite'/%3E%3Canimate attributeName='opacity' values='0.25;0.4;0.25' dur='3s' repeatCount='indefinite'/%3E%3C/text%3E%3C!-- Sparkle stars --%3E%3Ccircle cx='260' cy='60' r='3' fill='rgba(255,255,255,0.7)'%3E%3Canimate attributeName='opacity' values='0;1;0' dur='2.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='570' cy='50' r='3' fill='rgba(255,255,255,0.7)'%3E%3Canimate attributeName='opacity' values='0;1;0' dur='3s' repeatCount='indefinite' begin='0.8s'/%3E%3C/circle%3E%3Ccircle cx='700' cy='130' r='2' fill='rgba(255,255,255,0.6)'%3E%3Canimate attributeName='opacity' values='0;1;0' dur='2.8s' repeatCount='indefinite' begin='1.2s'/%3E%3C/circle%3E%3Ccircle cx='100' cy='70' r='2' fill='rgba(255,255,255,0.6)'%3E%3Canimate attributeName='opacity' values='0;1;0' dur='2.2s' repeatCount='indefinite' begin='0.5s'/%3E%3C/circle%3E%3C!-- Number symbols --%3E%3Ctext x='60' y='100' font-size='32' fill='rgba(255,255,255,0.2)' font-weight='bold'%3E0%3C/text%3E%3Ctext x='760' y='95' font-size='24' fill='rgba(255,255,255,0.18)' font-weight='bold'%3E100%3Canimate attributeName='opacity' values='0.18;0.3;0.18' dur='4s' repeatCount='indefinite'/%3E%3C/text%3E%3C!-- Circular progress indicator --%3E%3Ccircle cx='80' cy='140' r='15' stroke='rgba(255,255,255,0.25)' stroke-width='3' fill='none' stroke-dasharray='31.4 62.8'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 80 140' to='360 80 140' dur='6s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/svg%3E");
background-size: cover;
background-position: center;
opacity: 1;
z-index: 0;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
padding: 0 20px;
flex-wrap: wrap;
position: relative;
z-index: 1;
}
.back-btn {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
color: white;
margin-right: 16px;
}
.back-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
}
.back-icon {
width: 24px;
height: 24px;
fill: currentColor;
}
.header-info {
flex: 1;
}
.header-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 4px 0;
color: white;
}
.header-subtitle {
font-size: 14px;
opacity: 0.8;
margin: 0;
}
/* 彩票类型筛选 */
.lottery-type-filter {
margin-left: 16px;
display: flex;
align-items: center;
}
.filter-label {
color: white;
font-size: 14px;
margin-right: 10px;
white-space: nowrap;
font-weight: 500;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.select-wrapper {
position: relative;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
}
.lottery-select {
width: 140px;
}
/* 自定义Element UI下拉框样式 */
:deep(.el-select .el-input__wrapper) {
background: rgba(255, 255, 255, 0.9);
box-shadow: none;
border-radius: 20px;
padding: 0 15px;
border: 2px solid rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
}
:deep(.el-select .el-input__wrapper:hover) {
background: white;
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
:deep(.el-select .el-input__inner) {
color: #333;
font-size: 14px;
font-weight: 500;
height: 36px;
}
:deep(.el-select .el-select__caret) {
color: #4facfe;
transition: transform 0.3s;
}
:deep(.el-select-dropdown__item.selected) {
color: #4facfe;
font-weight: bold;
position: relative;
}
:deep(.el-select-dropdown__item.selected::after) {
content: "";
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #4facfe;
}
:deep(.el-select-dropdown__item:hover) {
background-color: #f0f9ff;
}
:deep(.el-popper.is-light) {
border-radius: 15px;
border: none;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
:deep(.el-select-dropdown__wrap) {
max-height: 280px;
}
:deep(.el-select-dropdown__list) {
padding: 8px 0;
}
:deep(.el-scrollbar__bar.is-horizontal) {
display: none;
}
/* 主要内容 */
.stats-content {
max-width: 850px;
margin: 0 auto;
padding: 16px 16px;
}
/* 加载状态 */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #4facfe;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: #666;
font-size: 14px;
margin: 0;
}
/* 数据容器 */
.data-container {
display: flex;
flex-direction: column;
gap: 16px;
padding: 0 5px;
}
/* 标题样式 */
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 16px 0;
display: flex;
align-items: center;
gap: 10px;
padding-left: 5px;
}
.section-icon {
width: 20px;
height: 20px;
stroke: #4facfe;
stroke-width: 2;
}
/* 统计卡片 */
.stats-card {
background: white;
border-radius: 12px;
border: 1px solid #e9ecef;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
margin-bottom: 16px;
overflow: hidden;
}
.stats-grid {
padding: 16px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 12px;
}
.stat-item {
text-align: center;
padding: 24px 12px;
background: transparent;
transition: all 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.stat-item:not(:last-child)::after {
content: '';
position: absolute;
right: 0;
top: 20%;
bottom: 20%;
width: 1px;
background-color: #e9ecef;
}
.stat-item:nth-child(1)::before,
.stat-item:nth-child(2)::before {
content: '';
position: absolute;
left: 20%;
right: 20%;
bottom: 0;
height: 1px;
background-color: #e9ecef;
}
.stat-item:hover {
background: rgba(0, 0, 0, 0.02);
}
.stat-number {
font-size: 28px;
font-weight: 600;
color: #333;
margin-bottom: 5px;
line-height: 1;
}
.stat-number.highlight {
color: #FF6B6B;
}
.stat-label {
font-size: 13px;
color: #666;
font-weight: 500;
}
/* 信息卡片 */
.info-card {
background: white;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
overflow: hidden;
border: 1px solid #e9ecef;
}
.info-header {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
padding: 16px 24px;
display: flex;
align-items: center;
gap: 12px;
}
.info-icon {
width: 24px;
height: 24px;
stroke: white;
stroke-width: 2;
flex-shrink: 0;
}
.info-title {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.info-content {
padding: 20px 24px;
}
.info-list {
list-style: none;
padding: 0;
margin: 0;
}
.info-list li {
font-size: 14px;
color: #555;
line-height: 1.6;
margin-bottom: 12px;
padding-left: 20px;
position: relative;
}
.info-list li::before {
content: '•';
color: #4facfe;
font-size: 16px;
position: absolute;
left: 0;
top: 0;
}
.info-list li:last-child {
margin-bottom: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.page-header-modern {
padding: 16px 0;
}
.header-content {
padding: 0 16px;
flex-wrap: wrap;
}
.lottery-type-filter {
margin-left: 0;
margin-top: 12px;
width: 100%;
justify-content: flex-start;
}
.filter-label {
font-size: 13px;
margin-right: 8px;
}
.lottery-select {
width: 130px;
}
.back-btn {
width: 40px;
height: 40px;
margin-right: 12px;
}
.back-icon {
width: 20px;
height: 20px;
}
.header-title {
font-size: 20px;
}
.header-subtitle {
font-size: 12px;
}
.stats-content {
padding: 16px;
}
.stats-card {
border-radius: 10px;
}
.stats-grid {
padding: 15px;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 10px;
}
.stat-item {
padding: 20px 10px;
}
.stat-number {
font-size: 22px;
}
.stat-label {
font-size: 12px;
}
.info-header {
padding: 16px 20px;
}
.info-content {
padding: 20px;
}
}
@media (max-width: 480px) {
.header-content {
padding: 0 12px;
}
.stats-content {
padding: 10px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
padding: 10px;
gap: 8px;
}
.stat-item {
padding: 16px 6px;
}
.stat-number {
font-size: 20px;
margin-bottom: 3px;
font-weight: 600;
}
.stat-label {
font-size: 12px;
}
.section-title {
font-size: 16px;
margin-bottom: 10px;
}
.info-header {
padding: 12px 16px;
}
.info-content {
padding: 16px;
}
.info-list li {
font-size: 13px;
}
}
</style>