| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- <template>
- <view class="report-container">
- <!-- 顶部标题 -->
- <view class="header">
- <text class="header-title">数据统计</text>
- </view>
- <view v-if="loading" class="loading-state">
- <text>加载中...</text>
- </view>
- <scroll-view v-else scroll-y class="scroll-area">
- <!-- 概览卡片 -->
- <view class="overview-grid">
- <view class="overview-item">
- <text class="ov-num">{{ overview.totalPoints || 0 }}</text>
- <text class="ov-label">总积分</text>
- </view>
- <view class="overview-item">
- <text class="ov-num">{{ (overview.taskStats && overview.taskStats.completed) || 0 }}/{{ (overview.taskStats && overview.taskStats.total) || 0 }}</text>
- <text class="ov-label">任务完成</text>
- </view>
- <view class="overview-item">
- <text class="ov-num">{{ (overview.gameStats && overview.gameStats.total) || 0 }}</text>
- <text class="ov-label">游戏次数</text>
- </view>
- <view class="overview-item" @click="navTo('/pages/stats/completion-rate')">
- <text class="ov-num">{{ (overview.taskStats && overview.taskStats.completionRate) || 0 }}%</text>
- <text class="ov-label">完成率 ›</text>
- </view>
- </view>
- <!-- 积分趋势 -->
- <view class="section">
- <view class="section-header">
- <text class="section-title">积分趋势(近7天)</text>
- </view>
- <view class="trend-chart">
- <view class="bar-item" v-for="(item, idx) in overview.pointsTrend || []" :key="idx">
- <view class="bar-wrapper">
- <view class="bar" :style="{ height: barHeight(item.points) + 'rpx' }"></view>
- </view>
- <text class="bar-label">{{ formatDay(item.date) }}</text>
- </view>
- </view>
- </view>
- <!-- 任务统计 -->
- <view class="section">
- <view class="section-header">
- <text class="section-title">任务统计</text>
- <text class="section-more" @click="navTo('/pages/stats/completion-rate')">查看详情 ›</text>
- </view>
- <view class="stat-cards">
- <view class="stat-row">
- <view class="stat-chip pending">
- <text class="chip-num">{{ (overview.taskStats && overview.taskStats.pending) || 0 }}</text>
- <text class="chip-label">待完成</text>
- </view>
- <view class="stat-chip review">
- <text class="chip-num">{{ (overview.taskStats && overview.taskStats.inReview) || 0 }}</text>
- <text class="chip-label">待审核</text>
- </view>
- <view class="stat-chip done">
- <text class="chip-num">{{ (overview.taskStats && overview.taskStats.completed) || 0 }}</text>
- <text class="chip-label">已完成</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 游戏统计 -->
- <view class="section">
- <view class="section-header">
- <text class="section-title">专注训练统计</text>
- <text class="section-more" @click="navTo('/pages/games/stats')">查看详情 ›</text>
- </view>
- <view class="stat-cards">
- <view class="stat-row">
- <view class="stat-chip game">
- <text class="chip-num">{{ (overview.gameStats && overview.gameStats.total) || 0 }}</text>
- <text class="chip-label">总次数</text>
- </view>
- <view class="stat-chip game">
- <text class="chip-num">{{ (overview.gameStats && overview.gameStats.bestScore) || 0 }}</text>
- <text class="chip-label">最高分</text>
- </view>
- <view class="stat-chip game">
- <text class="chip-num">{{ (overview.gameStats && overview.gameStats.totalPoints) || 0 }}</text>
- <text class="chip-label">获得积分</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 快捷入口 -->
- <view class="section">
- <view class="section-header">
- <text class="section-title">更多报表</text>
- </view>
- <view class="quick-links">
- <view class="quick-link" @click="navTo('/pages/games/records')">
- <text class="ql-icon">📋</text>
- <text class="ql-text">训练记录</text>
- </view>
- <view class="quick-link" @click="navTo('/pages/games/stats')">
- <text class="ql-icon">📊</text>
- <text class="ql-text">成绩统计</text>
- </view>
- <view class="quick-link" @click="navTo('/pages/rewards/badge')">
- <text class="ql-icon">🏅</text>
- <text class="ql-text">勋章墙</text>
- </view>
- <view class="quick-link" @click="navTo('/pages/stats/completion-rate')">
- <text class="ql-icon">✅</text>
- <text class="ql-text">完成率</text>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
- <script>
- import { getChildOverview } from '../../utils/api.js'
- export default {
- data() {
- return {
- loading: true,
- overview: {}
- }
- },
- onShow() {
- this.loadData()
- },
- methods: {
- async loadData() {
- this.loading = true
- try {
- const memberId = uni.getStorageSync('currentChildId')
- if (memberId) {
- const res = await getChildOverview(memberId)
- this.overview = res.data || {}
- }
- } catch (e) {
- console.error('加载报表数据失败', e)
- } finally {
- this.loading = false
- }
- },
- barHeight(points) {
- const max = Math.max(...(this.overview.pointsTrend || []).map(i => i.points), 1)
- return Math.max(8, (points / max) * 160)
- },
- formatDay(dateStr) {
- if (!dateStr) return ''
- const parts = dateStr.split('-')
- return (parts[1] || '') + '/' + (parts[2] || '')
- },
- navTo(path) {
- uni.navigateTo({ url: path })
- }
- }
- }
- </script>
- <style scoped>
- .report-container {
- min-height: 100vh;
- background: #f5f7fa;
- }
- .header {
- padding: 20rpx 30rpx;
- background: #fff;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .header-title {
- font-size: 34rpx;
- font-weight: bold;
- color: #333;
- }
- .loading-state {
- text-align: center;
- padding: 200rpx 30rpx;
- color: #999;
- font-size: 28rpx;
- }
- .scroll-area {
- padding: 0 20rpx 40rpx;
- }
- /* 概览 */
- .overview-grid {
- display: flex;
- margin: 20rpx 0;
- gap: 16rpx;
- }
- .overview-item {
- flex: 1;
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx 16rpx;
- text-align: center;
- box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
- }
- .ov-num {
- display: block;
- font-size: 36rpx;
- font-weight: bold;
- color: #F97316;
- }
- .ov-label {
- display: block;
- font-size: 22rpx;
- color: #999;
- margin-top: 6rpx;
- }
- /* 积分趋势 */
- .section {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
- }
- .section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16rpx;
- }
- .section-title {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- }
- .section-more {
- font-size: 24rpx;
- color: #F97316;
- }
- .trend-chart {
- display: flex;
- align-items: flex-end;
- height: 200rpx;
- gap: 12rpx;
- padding-top: 20rpx;
- }
- .bar-item {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- height: 100%;
- }
- .bar-wrapper {
- flex: 1;
- width: 100%;
- display: flex;
- align-items: flex-end;
- justify-content: center;
- }
- .bar {
- width: 32rpx;
- background: linear-gradient(180deg, #F97316, #fb923c);
- border-radius: 8rpx 8rpx 0 0;
- min-height: 8rpx;
- transition: height 0.3s;
- }
- .bar-label {
- font-size: 20rpx;
- color: #999;
- margin-top: 8rpx;
- }
- /* 统计卡片 */
- .stat-cards {
- margin-top: 8rpx;
- }
- .stat-row {
- display: flex;
- gap: 16rpx;
- }
- .stat-chip {
- flex: 1;
- text-align: center;
- padding: 20rpx;
- border-radius: 12rpx;
- }
- .stat-chip.pending { background: #fef3c7; }
- .stat-chip.review { background: #dbeafe; }
- .stat-chip.done { background: #d1fae5; }
- .stat-chip.game { background: #f3e8ff; }
- .chip-num {
- display: block;
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- .chip-label {
- display: block;
- font-size: 22rpx;
- color: #666;
- margin-top: 4rpx;
- }
- /* 快捷入口 */
- .quick-links {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- }
- .quick-link {
- width: calc(25% - 12rpx);
- text-align: center;
- padding: 20rpx 0;
- }
- .ql-icon {
- display: block;
- font-size: 48rpx;
- margin-bottom: 8rpx;
- }
- .ql-text {
- display: block;
- font-size: 22rpx;
- color: #666;
- }
- </style>
|