|
|
@@ -1,331 +1,409 @@
|
|
|
<template>
|
|
|
<view class="container">
|
|
|
- <PlayfulCard elevation="1">
|
|
|
+ <!-- 空状态:没有孩子 -->
|
|
|
+ <view class="empty-state" v-if="!hasChildren">
|
|
|
+ <view class="empty-icon">👶</view>
|
|
|
+ <text class="empty-title">还没有孩子信息</text>
|
|
|
+ <text class="empty-desc">请先添加孩子,开始任务管理之旅</text>
|
|
|
+ <button class="btn-add-child" @click="goToAddChild">添加孩子</button>
|
|
|
+ <button class="btn-switch" @click="switchMode">切换回家长模式</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 有孩子时显示正常内容 -->
|
|
|
+ <PlayfulCard v-else elevation="1">
|
|
|
<!-- 欢迎区域 -->
|
|
|
<view class="welcome-section">
|
|
|
+ <view class="avatar-container">
|
|
|
+ <view class="avatar-glow"></view>
|
|
|
+ <image class="avatar" :src="avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
|
|
|
+ <view class="level-badge" v-if="level">{{ level }}</view>
|
|
|
+ </view>
|
|
|
<view class="welcome-text">
|
|
|
- <text class="hello">你好,{{ nickname || '小朋友' }}</text>
|
|
|
- <text class="subtitle">今天也要加油完成任务哦!</text>
|
|
|
+ <text class="hello">你好,{{ nickname || '小朋友' }} 👋</text>
|
|
|
+ <text class="subtitle">{{ getGreeting() }}</text>
|
|
|
</view>
|
|
|
- <view class="streak-badge" v-if="streakDays > 0">
|
|
|
+ <view class="streak-badge" v-if="streakDays > 0" @click="showStreakDetail">
|
|
|
<text class="fire">🔥</text>
|
|
|
<text class="days">{{ streakDays }}天</text>
|
|
|
+ <text class="streak-label">连续打卡</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 打卡里程碑进度条 -->
|
|
|
+ <view class="milestone-bar" v-if="streakProgress && streakProgress.nextMilestone">
|
|
|
+ <view class="milestone-info">
|
|
|
+ <text class="milestone-label">距{{ streakProgress.nextMilestone }}天里程碑</text>
|
|
|
+ <text class="milestone-progress">{{ streakProgress.daysToNext }}天后达标</text>
|
|
|
</view>
|
|
|
+ <view class="milestone-progress-bar">
|
|
|
+ <view class="milestone-fill" :style="{ width: getMilestoneProgress() + '%' }"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 积分进度环 -->
|
|
|
+ <view class="ring-section">
|
|
|
+ <PointsProgressRing :points="totalPoints" :currentMilestone="totalPoints % 100" :nextMilestone="100" />
|
|
|
</view>
|
|
|
|
|
|
<!-- 积分卡片 -->
|
|
|
<view class="points-card">
|
|
|
- <view class="points-label">当前积分</view>
|
|
|
- <view class="points-value">{{ totalPoints }}</view>
|
|
|
- <view class="points-expire" v-if="expireDate">有效期至 {{ expireDate }}</view>
|
|
|
+ <view class="points-main">
|
|
|
+ <view class="points-label">我的积分</view>
|
|
|
+ <view class="points-value">{{ totalPoints }} <text class="unit">分</text></view>
|
|
|
+ </view>
|
|
|
+ <view class="points-actions">
|
|
|
+ <view class="action-btn" @click="goToRewards">
|
|
|
+ <text class="action-icon">🎁</text>
|
|
|
+ <text class="action-text">兑换</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-btn" @click="showPointsHistory">
|
|
|
+ <text class="action-icon">📊</text>
|
|
|
+ <text class="action-text">明细</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="points-expire" v-if="expireDate">📅 有效期至 {{ expireDate }}</view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 今日任务概览 -->
|
|
|
<view class="section">
|
|
|
- <view class="section-title">今日任务</view>
|
|
|
+ <view class="section-header">
|
|
|
+ <view class="section-title">📝 今日任务</view>
|
|
|
+ <view class="progress-bar">
|
|
|
+ <view class="progress-fill" :style="{ width: taskProgress + '%' }"></view>
|
|
|
+ </view>
|
|
|
+ <text class="progress-text">{{ completedCount }}/{{ totalTaskCount }}</text>
|
|
|
+ </view>
|
|
|
<view class="task-summary">
|
|
|
- <view class="summary-item">
|
|
|
+ <view class="summary-item completed">
|
|
|
+ <text class="emoji">✅</text>
|
|
|
<text class="num">{{ completedCount }}</text>
|
|
|
<text class="label">已完成</text>
|
|
|
</view>
|
|
|
- <view class="summary-item">
|
|
|
+ <view class="summary-item pending">
|
|
|
+ <text class="emoji">⏳</text>
|
|
|
<text class="num">{{ pendingCount }}</text>
|
|
|
<text class="label">待完成</text>
|
|
|
</view>
|
|
|
- <view class="summary-item">
|
|
|
- <text class="num">{{ todayPoints }}</text>
|
|
|
+ <view class="summary-item points-today">
|
|
|
+ <text class="emoji">⭐</text>
|
|
|
+ <text class="num">+{{ todayPoints }}</text>
|
|
|
<text class="label">今日获得</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <button class="btn-primary view-all-btn" @click="goToTasks">查看全部任务</button>
|
|
|
+ <button class="btn-primary view-all-btn" @click="goToTasks">
|
|
|
+ <text>🚀</text> 查看全部任务
|
|
|
+ </button>
|
|
|
</view>
|
|
|
|
|
|
<!-- 快捷操作 -->
|
|
|
<view class="section">
|
|
|
- <view class="section-title">快捷操作</view>
|
|
|
+ <view class="section-title">⚡ 快捷操作</view>
|
|
|
<view class="quick-actions">
|
|
|
<view class="action-item" @click="goToRewards">
|
|
|
- <text class="icon">🎁</text>
|
|
|
+ <view class="action-icon-wrapper reward">🎁</view>
|
|
|
<text class="label">心愿单</text>
|
|
|
</view>
|
|
|
<view class="action-item" @click="goToFocus">
|
|
|
- <text class="icon">⏱️</text>
|
|
|
+ <view class="action-icon-wrapper focus">⏱️</view>
|
|
|
<text class="label">专注训练</text>
|
|
|
</view>
|
|
|
+ <view class="action-item" @click="showAchievements">
|
|
|
+ <view class="action-icon-wrapper trophy">🏆</view>
|
|
|
+ <text class="label">成就</text>
|
|
|
+ </view>
|
|
|
<view class="action-item" @click="switchMode">
|
|
|
- <text class="icon">🔄</text>
|
|
|
- <text class="label">切换模式</text>
|
|
|
+ <view class="action-icon-wrapper switch">🔄</view>
|
|
|
+ <text class="label">切换</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+
|
|
|
+ <!-- 趣味提示 -->
|
|
|
+ <view class="tip-card" v-if="tip">
|
|
|
+ <text class="tip-icon">💡</text>
|
|
|
+ <text class="tip-text">{{ tip }}</text>
|
|
|
+ </view>
|
|
|
+ <ConfettiCelebration :show="showConfetti" :duration="3000" />
|
|
|
+ </PlayfulCard>
|
|
|
</view>
|
|
|
- </PlayfulCard>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import PlayfulCard from '@/components/PlayfulCard.vue'
|
|
|
import PlayfulButton from '@/components/PlayfulButton.vue'
|
|
|
-import { getTodayTasks, getPointsBalance, getChildren, switchBackToParent } from '../../utils/api.js'
|
|
|
+import ConfettiCelebration from '@/components/ConfettiCelebration.vue'
|
|
|
+import PointsProgressRing from '@/components/PointsProgressRing.vue'
|
|
|
+import { getTodayTasks, getPointsBalance, getChildren, switchBackToParent, verifyPassword, getStreakProgress } from '../../utils/api.js'
|
|
|
|
|
|
export default {
|
|
|
- components: { PlayfulCard, PlayfulButton },
|
|
|
+ components: { PlayfulCard, PlayfulButton, ConfettiCelebration, PointsProgressRing },
|
|
|
data() {
|
|
|
return {
|
|
|
+ hasChildren: true, // 默认有孩子,加载后更新
|
|
|
nickname: '',
|
|
|
+ avatar: '',
|
|
|
totalPoints: 0,
|
|
|
todayPoints: 0,
|
|
|
streakDays: 0,
|
|
|
+ streakProgress: null,
|
|
|
expireDate: '',
|
|
|
completedCount: 0,
|
|
|
pendingCount: 0,
|
|
|
- currentChildId: ''
|
|
|
+ currentChildId: '',
|
|
|
+ showConfetti: false,
|
|
|
+ level: '',
|
|
|
+ tip: ''
|
|
|
}
|
|
|
},
|
|
|
- onLoad() {
|
|
|
- this.checkLogin()
|
|
|
+ computed: {
|
|
|
+ totalTaskCount() { return this.completedCount + this.pendingCount },
|
|
|
+ taskProgress() {
|
|
|
+ if (this.totalTaskCount === 0) return 0
|
|
|
+ return (this.completedCount / this.totalTaskCount) * 100
|
|
|
+ }
|
|
|
},
|
|
|
+ onLoad() { this.checkLogin() },
|
|
|
onShow() {
|
|
|
- if (uni.getStorageSync('token')) {
|
|
|
- this.loadData()
|
|
|
- }
|
|
|
+ if (uni.getStorageSync('token')) this.loadData()
|
|
|
},
|
|
|
methods: {
|
|
|
checkLogin() {
|
|
|
if (!uni.getStorageSync('token')) {
|
|
|
- uni.reLaunch({
|
|
|
- url: '/pages/login/login'
|
|
|
- })
|
|
|
+ uni.reLaunch({ url: '/pages/login/login' })
|
|
|
}
|
|
|
},
|
|
|
async loadData() {
|
|
|
try {
|
|
|
- // 获取孩子列表
|
|
|
const childrenRes = await getChildren()
|
|
|
const children = childrenRes.data
|
|
|
if (children && children.length > 0) {
|
|
|
+ this.hasChildren = true
|
|
|
const child = children[0]
|
|
|
this.currentChildId = child.id
|
|
|
this.nickname = child.nickname
|
|
|
- this.totalPoints = child.totalPoints
|
|
|
- this.streakDays = child.streakDays
|
|
|
-
|
|
|
- // 获取积分余额
|
|
|
+ this.avatar = child.avatar
|
|
|
+ this.totalPoints = child.totalPoints || 0
|
|
|
+ this.streakDays = child.streakDays || 0
|
|
|
+
|
|
|
const balanceRes = await getPointsBalance(child.id)
|
|
|
this.totalPoints = balanceRes.data.balance
|
|
|
this.expireDate = this.formatDate(balanceRes.data.expireDate)
|
|
|
-
|
|
|
- // 获取今日任务
|
|
|
+
|
|
|
const tasksRes = await getTodayTasks(child.id)
|
|
|
const tasks = tasksRes.data
|
|
|
this.completedCount = tasks.filter(t => t.status === 'completed').length
|
|
|
this.pendingCount = tasks.filter(t => t.status === 'pending').length
|
|
|
-
|
|
|
- // 计算今日获得积分
|
|
|
- this.todayPoints = tasks
|
|
|
- .filter(t => t.status === 'completed')
|
|
|
- .reduce((sum, t) => sum + (t.points || 0), 0)
|
|
|
+
|
|
|
+ this.todayPoints = tasks.filter(t => t.status === 'completed').reduce((sum, t) => sum + (t.points || 0), 0)
|
|
|
+
|
|
|
+ this.calculateLevel()
|
|
|
+ this.setRandomTip()
|
|
|
+
|
|
|
+ // 加载打卡进度
|
|
|
+ this.loadStreakProgress(child.id)
|
|
|
+ } else {
|
|
|
+ // 没有孩子,显示空状态
|
|
|
+ this.hasChildren = false
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
+ } catch (e) {
|
|
|
console.error('加载数据失败', e)
|
|
|
+ this.hasChildren = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadStreakProgress(childId) {
|
|
|
+ try {
|
|
|
+ const res = await getStreakProgress(childId)
|
|
|
+ if (res.data) {
|
|
|
+ this.streakProgress = res.data
|
|
|
+ }
|
|
|
+ } catch (e) { console.error('加载打卡进度失败', e) }
|
|
|
+ },
|
|
|
+ getMilestoneProgress() {
|
|
|
+ if (!this.streakProgress || !this.streakProgress.nextMilestone) return 0
|
|
|
+ const current = this.streakDays || 0
|
|
|
+ const next = this.streakProgress.nextMilestone
|
|
|
+ // 计算距离下一个里程碑的进度
|
|
|
+ // 找到上一个已完成的里程碑
|
|
|
+ const milestones = this.streakProgress.milestones || []
|
|
|
+ let prevAchieved = 0
|
|
|
+ for (let m of milestones) {
|
|
|
+ if (m.achieved) prevAchieved = m.days
|
|
|
}
|
|
|
+ const total = next - prevAchieved
|
|
|
+ const progress = current - prevAchieved
|
|
|
+ return Math.min(100, Math.max(0, (progress / total) * 100))
|
|
|
+ },
|
|
|
+ calculateLevel() {
|
|
|
+ const levels = ['🌱', '🌿', '🌳', '⭐', '🌟', '💫', '🏆', '👑']
|
|
|
+ this.level = levels[Math.min(Math.floor(this.totalPoints / 100), levels.length - 1)]
|
|
|
+ },
|
|
|
+ setRandomTip() {
|
|
|
+ const tips = ['坚持就是胜利!今天也要加油哦 💪', '完成任务可以获得更多积分 🎯', '连续打卡可以获得额外奖励 🌟', '今天也要做一个自律的小朋友!', '加油!你是最棒的! 🚀']
|
|
|
+ this.tip = tips[Math.floor(Math.random() * tips.length)]
|
|
|
+ },
|
|
|
+ getGreeting() {
|
|
|
+ const hour = new Date().getHours()
|
|
|
+ if (hour < 12) return '早上好!今天也要元气满满 🌞'
|
|
|
+ if (hour < 18) return '下午好!继续加油哦 ⛅'
|
|
|
+ return '晚上好!辛苦啦 🎉'
|
|
|
},
|
|
|
formatDate(dateStr) {
|
|
|
if (!dateStr) return ''
|
|
|
const date = new Date(dateStr)
|
|
|
return `${date.getMonth() + 1}月${date.getDate()}日`
|
|
|
},
|
|
|
- goToTasks() {
|
|
|
- uni.switchTab({
|
|
|
- url: '/pages/tasks/tasks'
|
|
|
- })
|
|
|
- },
|
|
|
- goToRewards() {
|
|
|
- uni.switchTab({
|
|
|
- url: '/pages/rewards/rewards'
|
|
|
- })
|
|
|
- },
|
|
|
- goToFocus() {
|
|
|
- uni.navigateTo({
|
|
|
- url: '/pages/focus/focus'
|
|
|
+ goToTasks() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
|
|
|
+ goToRewards() { uni.switchTab({ url: '/pages/rewards/rewards' }) },
|
|
|
+ goToFocus() { uni.navigateTo({ url: '/pages/focus/focus' }) },
|
|
|
+ showPointsHistory() { uni.showToast({ title: '积分明细功能开发中', icon: 'none' }) },
|
|
|
+ showAchievements() { uni.showToast({ title: '成就功能开发中', icon: 'none' }) },
|
|
|
+ showStreakDetail() {
|
|
|
+ if (!this.streakProgress) {
|
|
|
+ uni.showModal({ title: '连续打卡', content: `你已经连续打卡 ${this.streakDays} 天了!太棒了!🎉`, showCancel: false })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const milestones = this.streakProgress.milestones || []
|
|
|
+ let content = `连续打卡 ${this.streakDays} 天\n\n📋 里程碑进度:\n`
|
|
|
+
|
|
|
+ milestones.forEach(m => {
|
|
|
+ const status = m.achieved ? '✅' : '⭕'
|
|
|
+ const reward = m.rewardPoints > 0 ? ` (+${m.rewardPoints}分)` : ''
|
|
|
+ content += `${status} ${m.days}天${reward}\n`
|
|
|
})
|
|
|
+
|
|
|
+ if (this.streakProgress.nextMilestone) {
|
|
|
+ content += `\n🎯 距下一里程碑: ${this.streakProgress.daysToNext} 天`
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showModal({ title: '🔥 连续打卡', content: content, showCancel: false })
|
|
|
},
|
|
|
switchMode() {
|
|
|
- // 孩子端点击切换模式时,直接切换回家长模式
|
|
|
+ // 弹出密码输入框
|
|
|
uni.showModal({
|
|
|
title: '切换模式',
|
|
|
- content: '确定要切换回家长模式吗?',
|
|
|
+ content: '请输入家长密码以确认切换',
|
|
|
+ editable: true,
|
|
|
success: async (res) => {
|
|
|
- if (res.confirm) {
|
|
|
- await this.switchBackToParentMode()
|
|
|
+ if (res.confirm && res.content) {
|
|
|
+ // 验证密码
|
|
|
+ try {
|
|
|
+ const verifyResult = await verifyPassword(res.content)
|
|
|
+ if (verifyResult.code === 200) {
|
|
|
+ await this.switchBackToParentMode()
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: '密码错误', icon: 'none' })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '密码验证失败', icon: 'none' })
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- // 切换回家长模式
|
|
|
async switchBackToParentMode() {
|
|
|
try {
|
|
|
const result = await switchBackToParent()
|
|
|
if (result.data) {
|
|
|
- this.$store.commit('switchRole', 'parent')
|
|
|
- uni.showToast({
|
|
|
- title: '已切换回家长模式',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- // 跳转到家长首页
|
|
|
- uni.reLaunch({
|
|
|
- url: '/pages/index/parent-index'
|
|
|
- })
|
|
|
+ uni.showToast({ title: '已切换回家长模式', icon: 'success' })
|
|
|
+ uni.reLaunch({ url: '/pages/index/parent-index' })
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({
|
|
|
- title: '切换失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
+ } catch (e) { uni.showToast({ title: '切换失败', icon: 'none' }) }
|
|
|
},
|
|
|
- navigateToHome(role) {
|
|
|
- if (role === 'parent') {
|
|
|
- uni.reLaunch({
|
|
|
- url: '/pages/index/parent-index'
|
|
|
- })
|
|
|
- } else {
|
|
|
- uni.reLaunch({
|
|
|
- url: '/pages/index/child-index'
|
|
|
- })
|
|
|
- }
|
|
|
+ triggerConfetti() {
|
|
|
+ this.showConfetti = true
|
|
|
+ setTimeout(() => { this.showConfetti = false }, 3000)
|
|
|
+ },
|
|
|
+ goToAddChild() {
|
|
|
+ uni.navigateTo({ url: '/pages/profile/create-child' })
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.container {
|
|
|
- padding: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.welcome-section {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.hello {
|
|
|
- font-size: 36rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.subtitle {
|
|
|
- font-size: 26rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-.streak-badge {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- background: linear-gradient(135deg, #FF6B6B, #FF8E53);
|
|
|
- padding: 10rpx 20rpx;
|
|
|
- border-radius: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.fire {
|
|
|
- font-size: 32rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.days {
|
|
|
- color: #fff;
|
|
|
- font-size: 24rpx;
|
|
|
- margin-left: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.points-card {
|
|
|
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 40rpx;
|
|
|
- text-align: center;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.points-label {
|
|
|
- color: rgba(255, 255, 255, 0.8);
|
|
|
- font-size: 26rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.points-value {
|
|
|
- color: #fff;
|
|
|
- font-size: 72rpx;
|
|
|
- font-weight: bold;
|
|
|
- margin: 20rpx 0;
|
|
|
-}
|
|
|
+.container { padding: 30rpx; }
|
|
|
+.welcome-section { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30rpx; flex-wrap: wrap; }
|
|
|
+.avatar-container { position: relative; width: 120rpx; height: 120rpx; }
|
|
|
+.avatar-glow { position: absolute; width: 120rpx; height: 120rpx; border-radius: 50%; background: linear-gradient(135deg, #FFD700, #FFA500); animation: pulse 2s infinite; }
|
|
|
+.avatar { width: 120rpx; height: 120rpx; border-radius: 50%; border: 4rpx solid #fff; position: relative; z-index: 1; }
|
|
|
+.level-badge { position: absolute; bottom: -10rpx; left: 50%; transform: translateX(-50%); font-size: 24rpx; }
|
|
|
+@keyframes pulse { 0%, 100% { opacity: 0.5; transform: scale(1); } 50% { opacity: 0.8; transform: scale(1.1); } }
|
|
|
+.welcome-text { flex: 1; margin-left: 20rpx; }
|
|
|
+.hello { font-size: 36rpx; font-weight: bold; color: #333; display: block; }
|
|
|
+.subtitle { font-size: 26rpx; color: #666; }
|
|
|
+.streak-badge { display: flex; align-items: center; background: linear-gradient(135deg, #FF6B6B, #FF8E53); padding: 10rpx 20rpx; border-radius: 30rpx; }
|
|
|
+.streak-label { color: #fff; font-size: 20rpx; margin-left: 8rpx; }
|
|
|
+.ring-section { display: flex; justify-content: center; margin: 30rpx 0; }
|
|
|
+.points-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20rpx; padding: 40rpx; text-align: center; margin-bottom: 30rpx; }
|
|
|
+.points-main { margin-bottom: 20rpx; }
|
|
|
+.points-label { color: rgba(255, 255, 255, 0.8); font-size: 26rpx; }
|
|
|
+.points-value { color: #fff; font-size: 72rpx; font-weight: bold; }
|
|
|
+.unit { font-size: 32rpx; }
|
|
|
+.points-actions { display: flex; justify-content: center; gap: 40rpx; margin-bottom: 20rpx; }
|
|
|
+.action-btn { display: flex; align-items: center; background: rgba(255,255,255,0.2); padding: 10rpx 30rpx; border-radius: 30rpx; }
|
|
|
+.action-icon { font-size: 32rpx; margin-right: 10rpx; }
|
|
|
+.action-text { color: #fff; font-size: 24rpx; }
|
|
|
+.points-expire { color: rgba(255, 255, 255, 0.6); font-size: 22rpx; }
|
|
|
+.section { margin-bottom: 30rpx; }
|
|
|
+.section-header { display: flex; align-items: center; margin-bottom: 20rpx; flex-wrap: wrap; }
|
|
|
+.section-title { font-size: 32rpx; font-weight: bold; color: #333; margin-right: 20rpx; }
|
|
|
+.progress-bar { flex: 1; height: 16rpx; background: #E8F4FD; border-radius: 8rpx; overflow: hidden; max-width: 200rpx; }
|
|
|
+.progress-fill { height: 100%; background: linear-gradient(90deg, #0EA5E9, #10B981); border-radius: 8rpx; transition: width 0.3s; }
|
|
|
+.progress-text { font-size: 24rpx; color: #666; margin-left: 20rpx; }
|
|
|
+.task-summary { display: flex; justify-content: space-around; background: #fff; border-radius: 20rpx; padding: 30rpx; margin-bottom: 20rpx; }
|
|
|
+.summary-item { text-align: center; }
|
|
|
+.emoji { font-size: 40rpx; display: block; margin-bottom: 10rpx; }
|
|
|
+.summary-item .num { font-size: 40rpx; font-weight: bold; display: block; }
|
|
|
+.summary-item.completed .num { color: #10B981; }
|
|
|
+.summary-item.pending .num { color: #F59E0B; }
|
|
|
+.summary-item.points-today .num { color: #0EA5E9; }
|
|
|
+.summary-item .label { font-size: 24rpx; color: #666; }
|
|
|
+.view-all-btn { width: 100%; }
|
|
|
+.quick-actions { display: flex; justify-content: space-around; background: #fff; border-radius: 20rpx; padding: 30rpx; }
|
|
|
+.action-item { display: flex; flex-direction: column; align-items: center; }
|
|
|
+.action-icon-wrapper { width: 100rpx; height: 100rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; font-size: 48rpx; margin-bottom: 10rpx; }
|
|
|
+.action-icon-wrapper.reward { background: linear-gradient(135deg, #FFD700, #FFA500); }
|
|
|
+.action-icon-wrapper.focus { background: linear-gradient(135deg, #0EA5E9, #06B6D4); }
|
|
|
+.action-icon-wrapper.trophy { background: linear-gradient(135deg, #8B5CF6, #A855F7); }
|
|
|
+.action-icon-wrapper.switch { background: linear-gradient(135deg, #10B981, #34D399); }
|
|
|
+.action-item .label { font-size: 24rpx; color: #666; }
|
|
|
+.tip-card { background: linear-gradient(135deg, #FEF3C7, #FDE68A); border-radius: 20rpx; padding: 20rpx; display: flex; align-items: center; }
|
|
|
+.tip-icon { font-size: 32rpx; margin-right: 16rpx; }
|
|
|
+.tip-text { font-size: 26rpx; color: #92400E; flex: 1; }
|
|
|
+.milestone-bar { background: linear-gradient(135deg, #FF6B6B, #FF8E53); border-radius: 16rpx; padding: 20rpx; margin-bottom: 20rpx; }
|
|
|
+.milestone-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
|
|
|
+.milestone-label { color: #fff; font-size: 24rpx; }
|
|
|
+.milestone-progress { color: rgba(255,255,255,0.8); font-size: 22rpx; }
|
|
|
+.milestone-progress-bar { height: 12rpx; background: rgba(255,255,255,0.3); border-radius: 6rpx; overflow: hidden; }
|
|
|
+.milestone-fill { height: 100%; background: #fff; border-radius: 6rpx; transition: width 0.3s; }
|
|
|
|
|
|
-.points-expire {
|
|
|
- color: rgba(255, 255, 255, 0.6);
|
|
|
- font-size: 22rpx;
|
|
|
+/* 空状态样式 */
|
|
|
+.empty-state {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 100rpx 60rpx;
|
|
|
+ text-align: center;
|
|
|
}
|
|
|
-
|
|
|
-.section {
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.task-summary {
|
|
|
- display: flex;
|
|
|
- justify-content: space-around;
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.summary-item {
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-
|
|
|
-.summary-item .num {
|
|
|
- font-size: 40rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #FF6B6B;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.summary-item .label {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-.view-all-btn {
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-.quick-actions {
|
|
|
- display: flex;
|
|
|
- justify-content: space-around;
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx;
|
|
|
+.empty-icon { font-size: 120rpx; margin-bottom: 30rpx; }
|
|
|
+.empty-title { font-size: 36rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
|
|
|
+.empty-desc { font-size: 28rpx; color: #666; margin-bottom: 40rpx; }
|
|
|
+.btn-add-child {
|
|
|
+ background: linear-gradient(135deg, #FF6B6B, #FF8E53);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ padding: 20rpx 60rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
}
|
|
|
-
|
|
|
-.action-item {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
-}
|
|
|
-
|
|
|
-.action-item .icon {
|
|
|
- font-size: 48rpx;
|
|
|
- margin-bottom: 10rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.action-item .label {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
+.btn-switch {
|
|
|
+ background: #fff;
|
|
|
+ color: #666;
|
|
|
+ border: 1rpx solid #ddd;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ padding: 20rpx 60rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
}
|
|
|
</style>
|