| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315 |
- <template>
- <view class="container">
- <!-- 加载状态 -->
- <BaseLoading :loading="loading" text="正在加载..." />
- <!-- 撒花庆祝 -->
- <ConfettiCelebration :show="showConfetti" />
- <!-- 首次登录引导浮层 -->
- <OnboardingGuide
- role="child"
- :show="showOnboarding"
- @close="showOnboarding = false" />
- <!-- 空状态 - 无孩子 -->
- <BaseEmpty
- v-if="!loading && !hasChildren"
- icon="🌈"
- title="欢迎来到知心益家!"
- description="让爸爸妈妈帮你添加,开始快乐成长之旅"
- >
- <template #action>
- <PlayfulButton variant="clay" size="lg" @click="goToAddChild">
- ➕ 添加我
- </PlayfulButton>
- </template>
- </BaseEmpty>
- <!-- ====== 主内容(有孩子时) ====== -->
- <view class="main-content" v-if="!loading && hasChildren">
- <!-- ===== A) 顶部 Banner ===== -->
- <PageBanner theme="home"
- tagline="家庭健康成长俱乐部"
- quote="给全家的一站式幸福提案" />
- <!-- 五维能量沙盘 -->
- <WuxingSandbox
- v-if="sandboxData"
- mode="live"
- :scores="sandboxScores"
- :overallScore="sandboxData.overallScore || 0"
- :members="sandboxData.members || []"
- :dimensions="energyDimensions"
- :totalEnergy="totalEnergy"
- :totalHealthIndex="totalHealthIndex"
- starHeight="400rpx"
- showDimensionBar
- @point-click="onDomainClick" />
- <!-- 家庭能量横条 -->
- <FamilyEnergyBar
- v-if="hasChildren"
- dimensionCode="child"
- :sandboxData="sandboxData" />
- <!-- 连续打卡徽章 -->
- <view
- class="streak-badge-wrap animate-slide-up animate-stagger-3"
- v-if="streakDays > 0"
- @click="showStreakDetail"
- hover-class="streak-pressed"
- >
- <text class="streak-fire">🔥</text>
- <view class="streak-info">
- <text class="streak-days">{{ streakDays }}</text>
- <text class="streak-unit">天坚持</text>
- </view>
- </view>
- <!-- ============================================================
- B) 积分卡片 — Claymorphism 暖橙黏土风格
- ============================================================ -->
- <view
- class="points-card animate-bounce-in"
- @click="goToRewards"
- hover-class="points-card-pressed"
- >
- <!-- 装饰圆点 -->
- <view class="points-deco points-deco--1"></view>
- <view class="points-deco points-deco--2"></view>
- <view class="points-top">
- <view class="points-left">
- <text class="points-label">🌟 我的星星</text>
- <text class="points-number">{{ totalPoints }}</text>
- </view>
- <view class="points-level-tag">
- <text class="level-emoji">{{ levelEmoji }}</text>
- <text class="level-name">{{ levelName }}</text>
- </view>
- </view>
- <view class="points-progress">
- <text class="progress-hint" v-if="pointsToNext > 0">
- 还差 <text class="progress-hint-num">{{ pointsToNext }}</text> 颗星星升到 {{ nextLevelName }}
- </text>
- <text class="progress-hint" v-else>
- 🎉 已经是最高等级啦!
- </text>
- <view class="progress-track">
- <view
- class="progress-fill"
- :style="'width: ' + levelProgress + '%'"
- ></view>
- </view>
- </view>
- </view>
- <!-- ============================================================
- C) 今日任务 — 可水平滑动的黏土卡片
- ============================================================ -->
- <view class="tasks-section animate-slide-up animate-stagger-3">
- <view class="section-header">
- <text class="section-title">📋 今日任务</text>
- <text class="section-link" @click="goToTasks">查看全部 →</text>
- </view>
- <scroll-view
- class="tasks-scroll"
- scroll-x
- enhanced
- :show-scrollbar="false"
- v-if="todayTasks.length > 0"
- >
- <view
- class="task-clay-card"
- v-for="(task, tIdx) in todayTasks"
- :key="task.id"
- @click="handleTaskClick(task)"
- hover-class="task-card-pressed"
- :style="'animation-delay: ' + (tIdx * 80) + 'ms'"
- >
- <view class="task-card-icon-wrap" :class="'task-cat--' + getTaskCategoryClass(task)">
- <text class="task-card-icon">{{ getTaskIcon(task) }}</text>
- </view>
- <text class="task-card-name">{{ task.title }}</text>
- <text class="task-card-points" v-if="task.points">+{{ task.points }}⭐</text>
- <view class="task-card-badge">
- <BaseBadge :variant="getBadgeVariant(task)" size="xs">
- {{ getStatusText(task) }}
- </BaseBadge>
- </view>
- </view>
- </scroll-view>
- <!-- 今日任务空状态 -->
- <view class="tasks-empty" v-else @click="goToTasks">
- <view class="tasks-empty-icon">🎯</view>
- <text class="tasks-empty-title">今天没有任务哦</text>
- <text class="tasks-empty-desc">让爸爸妈妈给你布置任务吧~</text>
- </view>
- </view>
- <!-- ============================================================
- D) 快捷操作 — 2×2 黏土按钮网格
- ============================================================ -->
- <view class="quick-section animate-slide-up animate-stagger-4">
- <view class="section-header">
- <text class="section-title">🚀 快捷入口</text>
- </view>
- <view class="quick-grid">
- <view
- class="quick-clay-btn"
- @click="goToTasks"
- hover-class="quick-btn-pressed"
- >
- <view class="quick-btn-icon quick-icon--tasks">
- <text class="quick-emoji">📋</text>
- </view>
- <text class="quick-btn-label">我的任务</text>
- </view>
- <view
- class="quick-clay-btn"
- @click="goToRewards"
- hover-class="quick-btn-pressed"
- >
- <view class="quick-btn-icon quick-icon--rewards">
- <text class="quick-emoji">⭐</text>
- </view>
- <text class="quick-btn-label">心愿商城</text>
- </view>
- <view
- class="quick-clay-btn"
- @click="goToGrowth"
- hover-class="quick-btn-pressed"
- >
- <view class="quick-btn-icon quick-icon--growth">
- <text class="quick-emoji">🏆</text>
- </view>
- <text class="quick-btn-label">成长档案</text>
- </view>
- <view
- class="quick-clay-btn"
- @click="goToGames"
- hover-class="quick-btn-pressed"
- >
- <view class="quick-btn-icon quick-icon--games">
- <text class="quick-emoji">🎮</text>
- </view>
- <text class="quick-btn-label">小游戏</text>
- </view>
- </view>
- </view>
- <!-- ============================================================
- E) 最近成就 — 水平滚动徽章
- ============================================================ -->
- <view
- class="achievement-section animate-slide-up animate-stagger-5"
- v-if="earnedBadges.length > 0"
- >
- <view class="section-header">
- <text class="section-title">🏅 最近成就</text>
- </view>
- <scroll-view
- class="achievement-scroll"
- scroll-x
- enhanced
- :show-scrollbar="false"
- >
- <view
- class="achievement-item"
- v-for="badge in earnedBadges"
- :key="badge.id"
- hover-class="achievement-pressed"
- >
- <view class="achievement-icon-wrap">
- <text class="achievement-icon">{{ badge.icon }}</text>
- </view>
- <text class="achievement-name">{{ badge.name }}</text>
- </view>
- <!-- 锁定占位徽章 -->
- <view
- class="achievement-item achievement-item--locked"
- v-for="lockedBadge in lockedBadges"
- :key="lockedKey(lockedBadge)"
- >
- <view class="achievement-icon-wrap achievement-icon-wrap--locked">
- <text class="achievement-icon achievement-icon--locked">🔒</text>
- </view>
- <text class="achievement-name achievement-name--locked">{{ lockedBadge.name }}</text>
- </view>
- </scroll-view>
- </view>
- <!-- ============================================================
- F) 最近能量变动
- ============================================================ -->
- <view class="energy-log-section animate-slide-up animate-stagger-6" v-if="energyLogs.length > 0">
- <view class="section-header">
- <text class="section-title">💧 最近能量变动</text>
- <text class="section-link" @click="goToEnergyDetail">查看详情 →</text>
- </view>
- <view class="energy-log-list">
- <view class="energy-log-item" v-for="log in energyLogs" :key="log.id">
- <view class="log-icon-wrap" :class="'log-icon--' + ((log.amount || 0) >= 0 ? 'up' : 'down')">
- <text class="log-icon">{{ (log.amount || 0) >= 0 ? '⬆' : '⬇' }}</text>
- </view>
- <view class="log-info">
- <text class="log-title">{{ log.reason || log.description }}</text>
- <text class="log-time">{{ log.createdAt || log.time }}</text>
- </view>
- <text class="log-amount" :class="(log.amount || 0) >= 0 ? 'log-amount--up' : 'log-amount--down'">
- {{ (log.amount || 0) >= 0 ? '+' : '' }}{{ log.amount }}
- </text>
- </view>
- </view>
- </view>
- <!-- ===== 推荐活动 ===== -->
- <DimensionActivities
- v-if="hasChildren"
- dimensionCode="child"
- :activities="dimensionActivities"
- @activityClick="goActivityDetail"
- @moreActivities="goMoreActivities" />
- <!-- ===== 推荐商品 ===== -->
- <DimensionProducts
- v-if="hasChildren"
- dimensionCode="child"
- :products="dimensionProducts"
- @productClick="goProductDetail"
- @moreProducts="goMoreProducts" />
- <!-- 底部安全留白 -->
- <view class="bottom-spacer"></view>
- </view>
- </view>
- </template>
- <script>
- import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs, getActivityList, getProductsByDomain, getFamilyEnergySandbox } from '../../utils/api.js'
- import PageBanner from '../../components/PageBanner.vue'
- import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
- import WuxingSandbox from '../../components/wuxing-sandbox.vue'
- import DimensionActivities from '../../components/DimensionActivities.vue'
- import DimensionProducts from '../../components/DimensionProducts.vue'
- import PlayfulCard from '../../components/PlayfulCard.vue'
- import PlayfulButton from '../../components/PlayfulButton.vue'
- import BaseBadge from '../../components/BaseBadge.vue'
- import BaseEmpty from '../../components/BaseEmpty.vue'
- import BaseLoading from '../../components/BaseLoading.vue'
- import OnboardingGuide from '../../components/OnboardingGuide.vue'
- import ConfettiCelebration from '../../components/ConfettiCelebration.vue'
- /**
- * 等级配置表
- * 星星宝宝(0) → 月亮小朋友(200) → 太阳小天使(500) → 宇宙小英雄(1000)
- */
- var LEVELS = [
- { min: 0, max: 200, name: '星星宝宝', nextName: '月亮小朋友', emoji: '⭐' },
- { min: 200, max: 500, name: '月亮小朋友', nextName: '太阳小天使', emoji: '🌙' },
- { min: 500, max: 1000, name: '太阳小天使', nextName: '宇宙小英雄', emoji: '☀️' },
- { min: 1000, max: Infinity, name: '宇宙小英雄', nextName: '👑 MAX', emoji: '🦸' }
- ]
- /**
- * 任务分类 → 图标映射
- */
- var TASK_ICONS = {
- '学习类': '📚',
- '阅读类': '📖',
- '生活类': '🏠',
- '运动类': '⚽',
- '家务类': '🧹',
- '艺术类': '🎨',
- '其他': '⭐'
- }
- export default {
- components: {
- PageBanner,
- FamilyEnergyBar,
- WuxingSandbox,
- DimensionActivities,
- DimensionProducts,
- PlayfulCard,
- PlayfulButton,
- BaseBadge,
- BaseEmpty,
- BaseLoading,
- OnboardingGuide,
- ConfettiCelebration
- },
- data() {
- return {
- loading: true,
- showOnboarding: false,
- hasChildren: false,
- nickname: '',
- childId: '',
- totalPoints: 0,
- streakDays: 0,
- completedCount: 0,
- totalTaskCount: 0,
- todayTasks: [],
- badges: [
- { id: 1, icon: '🌟', name: '早起鸟', unlocked: false },
- { id: 2, icon: '📚', name: '阅读小达人', unlocked: false },
- { id: 3, icon: '🏃', name: '运动健将', unlocked: false },
- { id: 4, icon: '🎨', name: '小艺术家', unlocked: false },
- { id: 5, icon: '🧹', name: '家务小能手', unlocked: false }
- ],
- _mounted: false,
- energyDimensions: [],
- totalEnergy: 0,
- totalHealthIndex: 0,
- energyLogs: [],
- sandboxData: null,
- dimensionActivities: [],
- dimensionProducts: []
- }
- },
- computed: {
- // ---------- 能量沙盘 ----------
- sandboxScores() {
- if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
- return {
- body: this.sandboxData.bodyScore || 0,
- mind: this.sandboxData.mindScore || 0,
- wisdom: this.sandboxData.wisdomScore || 0,
- action: this.sandboxData.actionScore || 0,
- wealth: this.sandboxData.wealthScore || 0
- }
- },
- // ---------- 等级相关 ----------
- currentLevelIndex() {
- const pts = this.totalPoints
- if (pts >= 1000) return 3
- if (pts >= 500) return 2
- if (pts >= 200) return 1
- return 0
- },
- levelName() {
- return LEVELS[this.currentLevelIndex].name
- },
- nextLevelName() {
- return LEVELS[this.currentLevelIndex].nextName
- },
- levelEmoji() {
- return LEVELS[this.currentLevelIndex].emoji
- },
- currentLevelMin() {
- return LEVELS[this.currentLevelIndex].min
- },
- nextLevelMax() {
- return LEVELS[this.currentLevelIndex].max
- },
- levelProgress() {
- if (this.totalPoints >= 1000) return 100
- const range = this.nextLevelMax - this.currentLevelMin
- if (range <= 0) return 100
- const progress = ((this.totalPoints - this.currentLevelMin) / range) * 100
- return Math.min(100, Math.floor(progress))
- },
- pointsToNext() {
- if (this.totalPoints >= 1000) return 0
- return this.nextLevelMax - this.totalPoints
- },
- // ---------- 任务相关 ----------
- taskProgress() {
- if (this.totalTaskCount === 0) return 0
- return Math.floor((this.completedCount / this.totalTaskCount) * 100)
- },
- // ---------- 庆祝动画 ----------
- showConfetti() {
- return this.taskProgress >= 100 && this.totalTaskCount > 0
- },
- // ---------- 头像表情 ----------
- avatarEmoji() {
- const level = this.currentLevelIndex
- if (level >= 3) return '🦸'
- if (level >= 2) return '😇'
- if (level >= 1) return '🌙'
- return '🐣'
- },
- // ---------- 问候语 ----------
- timeGreeting() {
- const hour = new Date().getHours()
- if (hour < 6) return '🌛 夜深了,还没睡呀'
- if (hour < 9) return '🌅 早安!新的一天开始啦'
- if (hour < 12) return '☀️ 上午好!今天也要加油哦'
- if (hour < 14) return '🌤️ 中午好!吃饱了才有力气'
- if (hour < 18) return '🌻 下午好!继续保持好状态'
- if (hour < 21) return '🌆 晚上好!今天过得开心吗'
- return '🌛 该休息啦,明天继续加油'
- },
- // ---------- 成就徽章 ----------
- earnedBadges() {
- return this.badges.filter(function(b) { return b.unlocked })
- },
- lockedBadges() {
- return this.badges.filter(function(b) { return !b.unlocked })
- }
- },
- mounted() {
- // 首次登录引导
- var onboardingDone = uni.getStorageSync('_onboardingDone')
- if (!onboardingDone) {
- this.showOnboarding = true
- }
- this.loadData()
- },
- onShow() {
- if (this._mounted) {
- this.loadData()
- }
- this._mounted = true
- },
- methods: {
- // ======================== 数据加载 ========================
- async loadData() {
- this.loading = true
- try {
- const userInfo = uni.getStorageSync('userInfo')
- const role = uni.getStorageSync('currentRole') || 'child'
- if (role !== 'child') {
- uni.switchTab({ url: '/pages/index/index' })
- return
- }
- this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : '小朋友'
- const childrenRes = await getChildren()
- const children = childrenRes.data || []
- if (children.length === 0) {
- this.hasChildren = false
- this.loading = false
- return
- }
- this.hasChildren = true
- const child = children[0] || {}
- this.childId = child.id || ''
- this.totalPoints = child.totalPoints || 0
- this.streakDays = child.streakDays || 0
- const tasksRes = await getTodayTasks(this.childId)
- const rawTasks = tasksRes.data || []
- this.todayTasks = rawTasks
- this.completedCount = rawTasks.filter(function(t) { return t.status === 'completed' }).length
- this.totalTaskCount = rawTasks.length
- // 解锁成就
- if (this.streakDays >= 7) {
- this.unlockBadge(1)
- }
- if (this.totalPoints >= 1000) {
- this.unlockBadge(2)
- }
- // 按类别统计已完成任务次数,解锁对应徽章
- var categoryCompletedCount = { '运动类': 0, '艺术类': 0, '家务类': 0 }
- for (var i = 0; i < rawTasks.length; i++) {
- var task = rawTasks[i]
- if (task.status === 'completed') {
- var cat = task.category || '其他'
- if (cat === '运动类') {
- categoryCompletedCount['运动类']++
- } else if (cat === '艺术类') {
- categoryCompletedCount['艺术类']++
- } else if (cat === '家务类') {
- categoryCompletedCount['家务类']++
- }
- }
- }
- if (categoryCompletedCount['运动类'] >= 5) {
- this.unlockBadge(3)
- }
- if (categoryCompletedCount['艺术类'] >= 5) {
- this.unlockBadge(4)
- }
- if (categoryCompletedCount['家务类'] >= 5) {
- this.unlockBadge(5)
- }
- // 加载五维能量概览
- var energyChildId = this.childId
- if (energyChildId) {
- try {
- var overviewRes = await getEnergyOverview(energyChildId)
- if (overviewRes && overviewRes.data) {
- this.energyDimensions = overviewRes.data.dimensions || []
- this.totalEnergy = overviewRes.data.totalEnergy || 0
- this.totalHealthIndex = overviewRes.data.totalHealthIndex || 0
- }
- } catch (e) {
- console.log('获取能量概览失败', e)
- }
- }
- // 加载最近能量变动
- if (energyChildId) {
- try {
- var logsRes = await getEnergyLogs(energyChildId, '', 1, 5)
- if (logsRes && logsRes.data) {
- this.energyLogs = logsRes.data.records || logsRes.data || []
- }
- } catch (e) {
- console.log('获取能量日志失败', e)
- this.energyLogs = []
- }
- }
- // 加载沙盘数据
- if (energyChildId) {
- try {
- var sandboxRes = await getFamilyEnergySandbox()
- if (sandboxRes && sandboxRes.data) {
- this.sandboxData = sandboxRes.data
- }
- } catch (e) {
- console.log('获取沙盘数据失败', e)
- }
- }
- // 加载维度活动
- try {
- var actRes = await getActivityList({ dimensionCode: 'child', page: 1, size: 3 })
- if (actRes && actRes.data) {
- var actList = Array.isArray(actRes.data) ? actRes.data : (actRes.data.records || [])
- this.dimensionActivities = actList.slice(0, 3)
- }
- } catch (e) {
- console.log('获取活动数据失败', e)
- }
- // 加载维度商品
- try {
- var prodRes = await getProductsByDomain('child', 1, 4)
- if (prodRes && prodRes.data) {
- var prodList = Array.isArray(prodRes.data) ? prodRes.data : (prodRes.data.records || [])
- this.dimensionProducts = prodList.slice(0, 4)
- }
- } catch (e) {
- console.log('获取商品数据失败', e)
- }
- } catch (e) {
- console.error('加载数据失败', e)
- uni.showToast({ title: '加载失败', icon: 'none' })
- } finally {
- this.loading = false
- }
- },
- unlockBadge(id) {
- var badge = this.badges.find(function(b) { return b.id === id })
- if (badge) badge.unlocked = true
- },
- // ======================== 导航方法 ========================
- onDomainClick(evt) {
- var code = evt && evt.code
- if (!code || !this.childId) return
- uni.navigateTo({
- url: '/pages/energy/detail?childId=' + this.childId + '&code=' + code
- })
- },
- goToRewards() {
- this.$emit('navigate', { page: 'rewards' })
- uni.switchTab({ url: '/pages/rewards/rewards' })
- },
- goToTasks() {
- this.$emit('navigate', { page: 'tasks' })
- uni.switchTab({ url: '/pages/tasks/tasks' })
- },
- goToGrowth() {
- this.$emit('navigate', { page: 'growth' })
- uni.navigateTo({ url: '/pages/growth/growth' })
- },
- goToGames() {
- this.$emit('navigate', { page: 'games' })
- uni.navigateTo({ url: '/pages/games/games' })
- },
- goToAddChild() {
- this.$emit('navigate', { page: 'add-child' })
- uni.navigateTo({ url: '/pages/profile/create-child' })
- },
- showStreakDetail() {
- this.$emit('navigate', { page: 'streak-detail' })
- uni.showToast({ title: '连续打卡 ' + this.streakDays + ' 天啦!太棒了!', icon: 'none' })
- },
- goToEnergyDetail() {
- if (this.childId) {
- uni.navigateTo({ url: '/pages/energy/detail?childId=' + this.childId + '&code=all' })
- }
- },
- showAchievements() {
- uni.showToast({ title: '成就页面开发中', icon: 'none' })
- },
- switchMode() {
- uni.showModal({
- title: '切换模式',
- content: '确定要切换回家长模式吗?',
- success: function(res) {
- if (res.confirm) {
- uni.switchTab({ url: '/pages/index/index' })
- }
- }
- })
- },
- goToDomain(domain) {
- uni.showToast({ title: '即将上线', icon: 'none' })
- },
- goToEnergyDetail(code) {
- // 跳转到能量详情页(T10 创建)
- var childId = this.childId
- if (!childId || !code) return
- uni.navigateTo({
- url: '/pages/energy/detail?childId=' + childId + '&code=' + code
- })
- },
- // ======================== 维度导航 ========================
- goActivityDetail: function(act) {
- if (act && act.id) uni.navigateTo({ url: '/pages/discover/activity-detail/activity-detail?id=' + act.id })
- },
- goMoreActivities: function() {
- uni.navigateTo({ url: '/pages/activity/index' })
- },
- goProductDetail: function(prod) {
- if (prod && prod.id) uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + prod.id })
- },
- goMoreProducts: function() {
- uni.navigateTo({ url: '/pages/shop/index' })
- },
- // ======================== 任务交互 ========================
- handleTaskClick(task) {
- if (!task || !task.id) return
- this.$emit('navigate', { page: 'task-detail', taskId: task.id })
- // 如果任务尚未完成,弹出确认完成
- if (task.status !== 'completed' && task.status !== 'overdue') {
- uni.showModal({
- title: '完成任务',
- content: '确定完成「' + task.title + '」吗?',
- success: function(res) {
- if (res.confirm) {
- task.status = 'completed'
- // 重新计算完成数
- this.completedCount = this.todayTasks.filter(function(t) { return t.status === 'completed' }).length
- if (task.points) {
- uni.showToast({ title: '🎉 获得 ' + task.points + ' 颗星星!', icon: 'success' })
- }
- }
- }.bind(this)
- })
- }
- },
- // ======================== 任务工具方法 ========================
- getTaskIcon(task) {
- if (!task) return '📝'
- var cat = task.category || '其他'
- return TASK_ICONS[cat] || '📝'
- },
- getTaskCategoryClass(task) {
- if (!task) return 'default'
- var cat = task.category || '其他'
- var map = {
- '学习类': 'study',
- '阅读类': 'reading',
- '生活类': 'life',
- '运动类': 'sports',
- '家务类': 'chores',
- '艺术类': 'art'
- }
- return map[cat] || 'default'
- },
- getTaskStatus(task) {
- if (!task || !task.status) return 'pending'
- return task.status
- },
- getStatusText(task) {
- var status = this.getTaskStatus(task)
- if (status === 'completed') return '✅ 已完成'
- if (status === 'overdue') return '⏰ 已超时'
- return '⏳ 待完成'
- },
- getBadgeVariant(task) {
- var status = this.getTaskStatus(task)
- if (status === 'completed') return 'success'
- if (status === 'overdue') return 'error'
- return 'warning'
- },
- lockedKey(item) {
- return 'locked-' + item.id
- }
- }
- }
- </script>
- <style scoped>
- /* ============================================================
- 知心益家 — 孩子端首页 Flat Rounded 设计系统
- 色彩体系:品牌蓝(#5B9BD5)+浅蓝(#8FC5E8) | 双重阴影 | 4rpx边框
- 参考:uni.scss --color-primary, --bg, --shadow-clay, --radius-md
- ============================================================ */
- /* ----------------------------------------------------------
- 容器 & 背景
- ---------------------------------------------------------- */
- .container {
- min-height: 100vh;
- background: var(--bg, #F5F9FC);
- padding: 0 32rpx 48rpx;
- }
- .main-content {
- padding-top: 16rpx;
- }
- .bottom-spacer {
- height: 48rpx;
- }
- /* 连续打卡徽章 — 独立行 */
- .streak-badge-wrap {
- display: flex;
- align-items: center;
- gap: 10rpx;
- justify-content: flex-end;
- margin-bottom: 24rpx;
- padding-top: 4rpx;
- }
- .streak-badge {
- display: flex;
- align-items: center;
- gap: 10rpx;
- background: linear-gradient(145deg, #F5F9FC, #E0ECF8);
- padding: 12rpx 24rpx 12rpx 18rpx;
- border-radius: 40rpx;
- border: 4rpx solid #8FC5E8;
- box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
- 4rpx 4rpx 14rpx rgba(143,197,232,0.30);
- flex-shrink: 0;
- transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
- }
- .streak-badge:active,
- .streak-pressed {
- transform: scale(0.94);
- }
- .streak-fire {
- font-size: 32rpx;
- }
- .streak-info {
- display: flex;
- flex-direction: column;
- align-items: center;
- line-height: 1.2;
- }
- .streak-days {
- font-size: 32rpx;
- font-weight: 800;
- color: #1E4A7A;
- line-height: 1.1;
- }
- .streak-unit {
- font-size: 18rpx;
- font-weight: 600;
- color: #2563EB;
- }
- /* ----------------------------------------------------------
- 积分卡片 — Flat Rounded 蓝色渐变
- ---------------------------------------------------------- */
- .points-card {
- position: relative;
- background: linear-gradient(145deg, #8FC5E8, #5B9BD5);
- border-radius: 32rpx;
- padding: 32rpx;
- margin-bottom: 28rpx;
- border: 4rpx solid #3A7CC4;
- box-shadow: inset -4rpx -4rpx 14rpx rgba(255,255,255,0.20),
- 8rpx 8rpx 28rpx rgba(91,155,213,0.25);
- overflow: hidden;
- transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
- }
- .points-card:active,
- .points-card-pressed {
- transform: scale(0.97);
- box-shadow: inset 2rpx 2rpx 10rpx rgba(0,0,0,0.08),
- 4rpx 4rpx 16rpx rgba(91,155,213,0.18);
- }
- /* 装饰圆 */
- .points-deco {
- position: absolute;
- border-radius: 50%;
- background: rgba(255,255,255,0.08);
- pointer-events: none;
- }
- .points-deco--1 {
- top: -60rpx;
- right: -40rpx;
- width: 220rpx;
- height: 220rpx;
- }
- .points-deco--2 {
- bottom: -30rpx;
- left: -30rpx;
- width: 120rpx;
- height: 120rpx;
- }
- .points-top {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 20rpx;
- position: relative;
- z-index: 1;
- }
- .points-left {
- display: flex;
- flex-direction: column;
- }
- .points-label {
- font-size: 24rpx;
- color: rgba(255,255,255,0.85);
- font-weight: 500;
- margin-bottom: 4rpx;
- }
- .points-number {
- font-size: 64rpx;
- font-weight: 800;
- color: #FFFFFF;
- line-height: 1.1;
- text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.10);
- }
- .points-level-tag {
- display: flex;
- flex-direction: column;
- align-items: center;
- background: rgba(255,255,255,0.15);
- padding: 12rpx 20rpx;
- border-radius: 24rpx;
- border: 2rpx solid rgba(255,255,255,0.20);
- min-width: 120rpx;
- }
- .level-emoji {
- font-size: 36rpx;
- line-height: 1;
- }
- .level-name {
- font-size: 22rpx;
- font-weight: 700;
- color: #FFD700;
- margin-top: 4rpx;
- }
- /* 进度条 */
- .points-progress {
- position: relative;
- z-index: 1;
- }
- .progress-hint {
- display: block;
- font-size: 22rpx;
- color: rgba(255,255,255,0.75);
- margin-bottom: 12rpx;
- }
- .progress-hint-num {
- font-weight: 700;
- color: #FFD700;
- font-size: 24rpx;
- }
- .progress-track {
- height: 14rpx;
- background: rgba(255,255,255,0.20);
- border-radius: 7rpx;
- overflow: hidden;
- border: 2rpx solid rgba(255,255,255,0.10);
- }
- .progress-fill {
- height: 100%;
- background: linear-gradient(90deg, #FFD700, #FFB800);
- border-radius: 6rpx;
- transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
- }
- /* ----------------------------------------------------------
- 任务区域 — 水平滚动黏土卡片
- ---------------------------------------------------------- */
- .tasks-section {
- margin-bottom: 28rpx;
- }
- .section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16rpx;
- }
- .section-title {
- font-size: 32rpx;
- font-weight: 700;
- color: var(--text, #1E293B);
- }
- .section-link {
- font-size: 24rpx;
- font-weight: 600;
- color: var(--color-primary, #5B9BD5);
- padding: 8rpx 4rpx;
- }
- .section-link:active {
- opacity: 0.6;
- }
- /* 任务水平滚动容器 */
- .tasks-scroll {
- display: flex;
- flex-direction: row;
- white-space: nowrap;
- overflow-x: auto;
- padding: 8rpx 0 16rpx;
- -webkit-overflow-scrolling: touch;
- }
- /* 单张任务黏土卡片 */
- .task-clay-card {
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- width: 200rpx;
- min-height: 260rpx;
- background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
- border-radius: 28rpx;
- padding: 24rpx 16rpx 20rpx;
- margin-right: 20rpx;
- border: 4rpx solid var(--border, #CBD5E1);
- box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
- 6rpx 6rpx 20rpx rgba(91,155,213,0.08);
- transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
- animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
- flex-shrink: 0;
- }
- .task-clay-card:active,
- .task-card-pressed {
- transform: scale(0.94);
- }
- /* 任务分类图标容器 */
- .task-card-icon-wrap {
- width: 88rpx;
- height: 88rpx;
- border-radius: 24rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 12rpx;
- border: 3rpx solid var(--border-light, #E2E8F0);
- box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
- 3rpx 3rpx 8rpx rgba(91,155,213,0.06);
- }
- .task-card-icon {
- font-size: 42rpx;
- line-height: 1;
- }
- /* 分类图标底色 */
- .task-cat--study .task-card-icon-wrap { background: rgba(14,165,233,0.12); border-color: #BAE6FD; }
- .task-cat--reading .task-card-icon-wrap { background: rgba(34,197,94,0.12); border-color: #BBF7D0; }
- .task-cat--life .task-card-icon-wrap { background: rgba(91,155,213,0.12); border-color: #CBD5E1; }
- .task-cat--sports .task-card-icon-wrap { background: rgba(59,130,246,0.12); border-color: #BFDBFE; }
- .task-cat--chores .task-card-icon-wrap { background: rgba(245,158,11,0.12); border-color: #FDE68A; }
- .task-cat--art .task-card-icon-wrap { background: rgba(236,72,153,0.12); border-color: #FBCFE8; }
- .task-cat--default .task-card-icon-wrap { background: rgba(148,163,184,0.12); border-color: #E2E8F0; }
- .task-card-name {
- font-size: 26rpx;
- font-weight: 600;
- color: var(--text, #1E293B);
- text-align: center;
- line-height: 1.3;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 100%;
- margin-bottom: 6rpx;
- }
- .task-card-points {
- font-size: 24rpx;
- font-weight: 700;
- color: var(--color-primary, #5B9BD5);
- margin-bottom: 10rpx;
- }
- .task-card-badge {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 36rpx;
- }
- /* 任务空状态 */
- .tasks-empty {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 48rpx 32rpx;
- background: var(--surface, #FFFFFF);
- border-radius: 28rpx;
- border: 4rpx solid var(--border, #CBD5E1);
- box-shadow: var(--shadow-clay);
- transition: transform 0.2s ease;
- }
- .tasks-empty:active {
- transform: scale(0.98);
- }
- .tasks-empty-icon {
- font-size: 64rpx;
- margin-bottom: 16rpx;
- }
- .tasks-empty-title {
- font-size: 30rpx;
- font-weight: 600;
- color: var(--text, #1E293B);
- margin-bottom: 8rpx;
- }
- .tasks-empty-desc {
- font-size: 24rpx;
- color: var(--text-secondary, #64748B);
- }
- /* ----------------------------------------------------------
- 快捷操作 — 2×2 黏土按钮网格
- ---------------------------------------------------------- */
- .quick-section {
- margin-bottom: 28rpx;
- }
- .quick-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- }
- .quick-grid > * {
- width: calc(50% - 10rpx);
- }
- .quick-clay-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 28rpx 16rpx;
- background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
- border-radius: 28rpx;
- border: 4rpx solid var(--border, #CBD5E1);
- box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
- 6rpx 6rpx 20rpx rgba(91,155,213,0.08);
- transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
- min-height: 180rpx;
- }
- .quick-clay-btn:active,
- .quick-btn-pressed {
- transform: scale(0.94);
- box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.06),
- 3rpx 3rpx 10rpx rgba(91,155,213,0.06);
- }
- .quick-btn-icon {
- width: 96rpx;
- height: 96rpx;
- border-radius: 28rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 14rpx;
- border: 3rpx solid var(--border-light, #E2E8F0);
- box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
- 3rpx 3rpx 8rpx rgba(91,155,213,0.06);
- transition: transform 0.2s ease;
- }
- .quick-emoji {
- font-size: 44rpx;
- line-height: 1;
- }
- /* 各按钮图标色彩 */
- .quick-icon--tasks { background: rgba(14,165,233,0.15); border-color: #BAE6FD; }
- .quick-icon--rewards { background: rgba(245,158,11,0.15); border-color: #FDE68A; }
- .quick-icon--growth { background: rgba(91,155,213,0.15); border-color: #CBD5E1; }
- .quick-icon--games { background: rgba(236,72,153,0.12); border-color: #FBCFE8; }
- .quick-btn-label {
- font-size: 28rpx;
- font-weight: 700;
- color: var(--text, #1E293B);
- }
- /* ----------------------------------------------------------
- 成就区域 — 水平滚动徽章
- ---------------------------------------------------------- */
- .achievement-section {
- margin-bottom: 28rpx;
- }
- .achievement-scroll {
- display: flex;
- flex-direction: row;
- white-space: nowrap;
- overflow-x: auto;
- padding: 8rpx 0 8rpx;
- -webkit-overflow-scrolling: touch;
- }
- .achievement-item {
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- width: 160rpx;
- margin-right: 20rpx;
- transition: transform 0.2s ease;
- flex-shrink: 0;
- }
- .achievement-item:active,
- .achievement-pressed {
- transform: scale(0.92);
- }
- .achievement-icon-wrap {
- width: 100rpx;
- height: 100rpx;
- border-radius: 50%;
- background: linear-gradient(145deg, #DCE8F5, #C8DDF0);
- border: 4rpx solid var(--border, #CBD5E1);
- box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
- 4rpx 4rpx 14rpx rgba(91,155,213,0.08);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 10rpx;
- }
- .achievement-icon {
- font-size: 44rpx;
- line-height: 1;
- }
- .achievement-name {
- font-size: 24rpx;
- font-weight: 600;
- color: var(--text, #1E293B);
- text-align: center;
- }
- /* 锁定徽章 */
- .achievement-item--locked {
- opacity: 0.50;
- }
- .achievement-icon-wrap--locked {
- background: var(--bg-grey, #EDF2F7);
- border-color: var(--border-light, #E2E8F0);
- box-shadow: none;
- }
- .achievement-icon--locked {
- filter: grayscale(1);
- }
- .achievement-name--locked {
- color: var(--muted, #94A3B8);
- }
- /* ----------------------------------------------------------
- 加载 & 通用
- ---------------------------------------------------------- */
- .loading-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 200rpx 0;
- }
- /* ----------------------------------------------------------
- 能量变动日志
- ---------------------------------------------------------- */
- .energy-log-section { margin-bottom: 28rpx; }
- .energy-log-list { background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
- .energy-log-item {
- display: flex; align-items: center;
- padding: 20rpx 24rpx;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .energy-log-item:last-child { border-bottom: none; }
- .log-icon-wrap {
- width: 56rpx; height: 56rpx;
- border-radius: 50%;
- display: flex; align-items: center; justify-content: center;
- margin-right: 16rpx; flex-shrink: 0;
- }
- .log-icon--up { background: rgba(16,185,129,0.12); }
- .log-icon--down { background: rgba(239,68,68,0.12); }
- .log-icon { font-size: 24rpx; }
- .log-info { flex: 1; min-width: 0; }
- .log-title { font-size: 26rpx; color: #333; display: block; }
- .log-time { font-size: 20rpx; color: #999; margin-top: 4rpx; display: block; }
- .log-amount { font-size: 28rpx; font-weight: 700; margin-left: 12rpx; }
- .log-amount--up { color: #10B981; }
- .log-amount--down { color: #EF4444; }
- /* 连续打卡徽章的浮动动画 — 使用 uni.scss 全局 float keyframe */
- .streak-badge {
- animation: float 3s ease-in-out infinite;
- }
- /* 等级标签的金色光晕 — 使用 uni.scss 全局 pulseGlow keyframe */
- .points-level-tag {
- animation: pulseGlow 2.5s ease-in-out infinite;
- }
- </style>
|