child-index.vue 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  1. <template>
  2. <view class="container">
  3. <!-- 加载状态 -->
  4. <BaseLoading :loading="loading" text="正在加载..." />
  5. <!-- 撒花庆祝 -->
  6. <ConfettiCelebration :show="showConfetti" />
  7. <!-- 空状态 - 无孩子 -->
  8. <BaseEmpty
  9. v-if="!loading && !hasChildren"
  10. icon="🌈"
  11. title="欢迎来到知心益家!"
  12. description="让爸爸妈妈帮你添加,开始快乐成长之旅"
  13. >
  14. <template #action>
  15. <PlayfulButton variant="clay" size="lg" @click="goToAddChild">
  16. ➕ 添加我
  17. </PlayfulButton>
  18. </template>
  19. </BaseEmpty>
  20. <!-- ====== 主内容(有孩子时) ====== -->
  21. <view class="main-content" v-if="!loading && hasChildren">
  22. <!-- ============================================================
  23. A) 五维理念区
  24. ============================================================ -->
  25. <view class="philosophy-section animate-fade-in animate-stagger-1">
  26. <view class="philosophy-banner">
  27. <text class="philosophy-title">浠艾福</text>
  28. <view class="philosophy-five">
  29. <text class="five-item five-body">🌏 身泰</text>
  30. <text class="five-item five-wisdom">⚔️ 智达</text>
  31. <text class="five-item five-mind">🔥 心怡</text>
  32. <text class="five-item five-action">🌿 行远</text>
  33. <text class="five-item five-wealth">💧 富沛</text>
  34. </view>
  35. </view>
  36. </view>
  37. <!-- 五维能量沙盘 -->
  38. <view class="animate-fade-in animate-stagger-2">
  39. <wuxing-sandbox mode="live"
  40. :scores="{}"
  41. :overall-score="0"
  42. :members="[]"
  43. :dimensions="energyDimensions"
  44. :total-energy="totalEnergy"
  45. :total-health-index="totalHealthIndex"
  46. show-dimension-bar="false"
  47. star-height="400rpx"
  48. @point-click="goToEnergyDetail" />
  49. </view>
  50. <!-- 连续打卡徽章 -->
  51. <view
  52. class="streak-badge-wrap animate-slide-up animate-stagger-3"
  53. v-if="streakDays > 0"
  54. @click="showStreakDetail"
  55. hover-class="streak-pressed"
  56. >
  57. <text class="streak-fire">🔥</text>
  58. <view class="streak-info">
  59. <text class="streak-days">{{ streakDays }}</text>
  60. <text class="streak-unit">天坚持</text>
  61. </view>
  62. </view>
  63. <!-- ============================================================
  64. B) 积分卡片 — Claymorphism 暖橙黏土风格
  65. ============================================================ -->
  66. <view
  67. class="points-card animate-bounce-in"
  68. @click="goToRewards"
  69. hover-class="points-card-pressed"
  70. >
  71. <!-- 装饰圆点 -->
  72. <view class="points-deco points-deco--1"></view>
  73. <view class="points-deco points-deco--2"></view>
  74. <view class="points-top">
  75. <view class="points-left">
  76. <text class="points-label">🌟 我的星星</text>
  77. <text class="points-number">{{ totalPoints }}</text>
  78. </view>
  79. <view class="points-level-tag">
  80. <text class="level-emoji">{{ levelEmoji }}</text>
  81. <text class="level-name">{{ levelName }}</text>
  82. </view>
  83. </view>
  84. <view class="points-progress">
  85. <text class="progress-hint" v-if="pointsToNext > 0">
  86. 还差 <text class="progress-hint-num">{{ pointsToNext }}</text> 颗星星升到 {{ nextLevelName }}
  87. </text>
  88. <text class="progress-hint" v-else>
  89. 🎉 已经是最高等级啦!
  90. </text>
  91. <view class="progress-track">
  92. <view
  93. class="progress-fill"
  94. :style="'width: ' + levelProgress + '%'"
  95. ></view>
  96. </view>
  97. </view>
  98. </view>
  99. <!-- ============================================================
  100. C) 今日任务 — 可水平滑动的黏土卡片
  101. ============================================================ -->
  102. <view class="tasks-section animate-slide-up animate-stagger-3">
  103. <view class="section-header">
  104. <text class="section-title">📋 今日任务</text>
  105. <text class="section-link" @click="goToTasks">查看全部 →</text>
  106. </view>
  107. <scroll-view
  108. class="tasks-scroll"
  109. scroll-x
  110. enhanced
  111. :show-scrollbar="false"
  112. v-if="todayTasks.length > 0"
  113. >
  114. <view
  115. class="task-clay-card"
  116. v-for="(task, tIdx) in todayTasks"
  117. :key="task.id"
  118. @click="handleTaskClick(task)"
  119. hover-class="task-card-pressed"
  120. :style="'animation-delay: ' + (tIdx * 80) + 'ms'"
  121. >
  122. <view class="task-card-icon-wrap" :class="'task-cat--' + getTaskCategoryClass(task)">
  123. <text class="task-card-icon">{{ getTaskIcon(task) }}</text>
  124. </view>
  125. <text class="task-card-name">{{ task.title }}</text>
  126. <text class="task-card-points" v-if="task.points">+{{ task.points }}⭐</text>
  127. <view class="task-card-badge">
  128. <BaseBadge :variant="getBadgeVariant(task)" size="xs">
  129. {{ getStatusText(task) }}
  130. </BaseBadge>
  131. </view>
  132. </view>
  133. </scroll-view>
  134. <!-- 今日任务空状态 -->
  135. <view class="tasks-empty" v-else @click="goToTasks">
  136. <view class="tasks-empty-icon">🎯</view>
  137. <text class="tasks-empty-title">今天没有任务哦</text>
  138. <text class="tasks-empty-desc">让爸爸妈妈给你布置任务吧~</text>
  139. </view>
  140. </view>
  141. <!-- ============================================================
  142. D) 快捷操作 — 2×2 黏土按钮网格
  143. ============================================================ -->
  144. <view class="quick-section animate-slide-up animate-stagger-4">
  145. <view class="section-header">
  146. <text class="section-title">🚀 快捷入口</text>
  147. </view>
  148. <view class="quick-grid">
  149. <view
  150. class="quick-clay-btn"
  151. @click="goToTasks"
  152. hover-class="quick-btn-pressed"
  153. >
  154. <view class="quick-btn-icon quick-icon--tasks">
  155. <text class="quick-emoji">📋</text>
  156. </view>
  157. <text class="quick-btn-label">我的任务</text>
  158. </view>
  159. <view
  160. class="quick-clay-btn"
  161. @click="goToRewards"
  162. hover-class="quick-btn-pressed"
  163. >
  164. <view class="quick-btn-icon quick-icon--rewards">
  165. <text class="quick-emoji">⭐</text>
  166. </view>
  167. <text class="quick-btn-label">心愿商城</text>
  168. </view>
  169. <view
  170. class="quick-clay-btn"
  171. @click="goToGrowth"
  172. hover-class="quick-btn-pressed"
  173. >
  174. <view class="quick-btn-icon quick-icon--growth">
  175. <text class="quick-emoji">🏆</text>
  176. </view>
  177. <text class="quick-btn-label">成长档案</text>
  178. </view>
  179. <view
  180. class="quick-clay-btn"
  181. @click="goToGames"
  182. hover-class="quick-btn-pressed"
  183. >
  184. <view class="quick-btn-icon quick-icon--games">
  185. <text class="quick-emoji">🎮</text>
  186. </view>
  187. <text class="quick-btn-label">小游戏</text>
  188. </view>
  189. </view>
  190. </view>
  191. <!-- ============================================================
  192. E) 最近成就 — 水平滚动徽章
  193. ============================================================ -->
  194. <view
  195. class="achievement-section animate-slide-up animate-stagger-5"
  196. v-if="earnedBadges.length > 0"
  197. >
  198. <view class="section-header">
  199. <text class="section-title">🏅 最近成就</text>
  200. </view>
  201. <scroll-view
  202. class="achievement-scroll"
  203. scroll-x
  204. enhanced
  205. :show-scrollbar="false"
  206. >
  207. <view
  208. class="achievement-item"
  209. v-for="badge in earnedBadges"
  210. :key="badge.id"
  211. hover-class="achievement-pressed"
  212. >
  213. <view class="achievement-icon-wrap">
  214. <text class="achievement-icon">{{ badge.icon }}</text>
  215. </view>
  216. <text class="achievement-name">{{ badge.name }}</text>
  217. </view>
  218. <!-- 锁定占位徽章 -->
  219. <view
  220. class="achievement-item achievement-item--locked"
  221. v-for="lockedBadge in lockedBadges"
  222. :key="lockedKey(lockedBadge)"
  223. >
  224. <view class="achievement-icon-wrap achievement-icon-wrap--locked">
  225. <text class="achievement-icon achievement-icon--locked">🔒</text>
  226. </view>
  227. <text class="achievement-name achievement-name--locked">{{ lockedBadge.name }}</text>
  228. </view>
  229. </scroll-view>
  230. </view>
  231. <!-- 底部安全留白 -->
  232. <view class="bottom-spacer"></view>
  233. </view>
  234. </view>
  235. </template>
  236. <script>
  237. import { getChildren, getTodayTasks, getEnergyOverview } from '../../utils/api.js'
  238. import PlayfulCard from '../../components/PlayfulCard.vue'
  239. import PlayfulButton from '../../components/PlayfulButton.vue'
  240. import BaseBadge from '../../components/BaseBadge.vue'
  241. import BaseEmpty from '../../components/BaseEmpty.vue'
  242. import BaseLoading from '../../components/BaseLoading.vue'
  243. import ConfettiCelebration from '../../components/ConfettiCelebration.vue'
  244. import WuxingSandbox from '../../components/wuxing-sandbox.vue'
  245. /**
  246. * 等级配置表
  247. * 星星宝宝(0) → 月亮小朋友(200) → 太阳小天使(500) → 宇宙小英雄(1000)
  248. */
  249. var LEVELS = [
  250. { min: 0, max: 200, name: '星星宝宝', nextName: '月亮小朋友', emoji: '⭐' },
  251. { min: 200, max: 500, name: '月亮小朋友', nextName: '太阳小天使', emoji: '🌙' },
  252. { min: 500, max: 1000, name: '太阳小天使', nextName: '宇宙小英雄', emoji: '☀️' },
  253. { min: 1000, max: Infinity, name: '宇宙小英雄', nextName: '👑 MAX', emoji: '🦸' }
  254. ]
  255. /**
  256. * 任务分类 → 图标映射
  257. */
  258. var TASK_ICONS = {
  259. '学习类': '📚',
  260. '阅读类': '📖',
  261. '生活类': '🏠',
  262. '运动类': '⚽',
  263. '家务类': '🧹',
  264. '艺术类': '🎨',
  265. '其他': '⭐'
  266. }
  267. export default {
  268. components: {
  269. PlayfulCard,
  270. PlayfulButton,
  271. BaseBadge,
  272. BaseEmpty,
  273. BaseLoading,
  274. ConfettiCelebration,
  275. WuxingSandbox
  276. },
  277. data() {
  278. return {
  279. loading: true,
  280. hasChildren: false,
  281. nickname: '',
  282. childId: '',
  283. totalPoints: 0,
  284. streakDays: 0,
  285. completedCount: 0,
  286. totalTaskCount: 0,
  287. todayTasks: [],
  288. badges: [
  289. { id: 1, icon: '🌟', name: '早起鸟', unlocked: false },
  290. { id: 2, icon: '📚', name: '阅读小达人', unlocked: false },
  291. { id: 3, icon: '🏃', name: '运动健将', unlocked: false },
  292. { id: 4, icon: '🎨', name: '小艺术家', unlocked: false },
  293. { id: 5, icon: '🧹', name: '家务小能手', unlocked: false }
  294. ],
  295. _mounted: false,
  296. energyDimensions: [],
  297. totalEnergy: 0,
  298. totalHealthIndex: 0
  299. }
  300. },
  301. computed: {
  302. // ---------- 等级相关 ----------
  303. currentLevelIndex() {
  304. const pts = this.totalPoints
  305. if (pts >= 1000) return 3
  306. if (pts >= 500) return 2
  307. if (pts >= 200) return 1
  308. return 0
  309. },
  310. levelName() {
  311. return LEVELS[this.currentLevelIndex].name
  312. },
  313. nextLevelName() {
  314. return LEVELS[this.currentLevelIndex].nextName
  315. },
  316. levelEmoji() {
  317. return LEVELS[this.currentLevelIndex].emoji
  318. },
  319. currentLevelMin() {
  320. return LEVELS[this.currentLevelIndex].min
  321. },
  322. nextLevelMax() {
  323. return LEVELS[this.currentLevelIndex].max
  324. },
  325. levelProgress() {
  326. if (this.totalPoints >= 1000) return 100
  327. const range = this.nextLevelMax - this.currentLevelMin
  328. if (range <= 0) return 100
  329. const progress = ((this.totalPoints - this.currentLevelMin) / range) * 100
  330. return Math.min(100, Math.floor(progress))
  331. },
  332. pointsToNext() {
  333. if (this.totalPoints >= 1000) return 0
  334. return this.nextLevelMax - this.totalPoints
  335. },
  336. // ---------- 任务相关 ----------
  337. taskProgress() {
  338. if (this.totalTaskCount === 0) return 0
  339. return Math.floor((this.completedCount / this.totalTaskCount) * 100)
  340. },
  341. // ---------- 庆祝动画 ----------
  342. showConfetti() {
  343. return this.taskProgress >= 100 && this.totalTaskCount > 0
  344. },
  345. // ---------- 头像表情 ----------
  346. avatarEmoji() {
  347. const level = this.currentLevelIndex
  348. if (level >= 3) return '🦸'
  349. if (level >= 2) return '😇'
  350. if (level >= 1) return '🌙'
  351. return '🐣'
  352. },
  353. // ---------- 问候语 ----------
  354. timeGreeting() {
  355. const hour = new Date().getHours()
  356. if (hour < 6) return '🌛 夜深了,还没睡呀'
  357. if (hour < 9) return '🌅 早安!新的一天开始啦'
  358. if (hour < 12) return '☀️ 上午好!今天也要加油哦'
  359. if (hour < 14) return '🌤️ 中午好!吃饱了才有力气'
  360. if (hour < 18) return '🌻 下午好!继续保持好状态'
  361. if (hour < 21) return '🌆 晚上好!今天过得开心吗'
  362. return '🌛 该休息啦,明天继续加油'
  363. },
  364. // ---------- 成就徽章 ----------
  365. earnedBadges() {
  366. return this.badges.filter(function(b) { return b.unlocked })
  367. },
  368. lockedBadges() {
  369. return this.badges.filter(function(b) { return !b.unlocked })
  370. }
  371. },
  372. mounted() {
  373. this.loadData()
  374. },
  375. onShow() {
  376. if (this._mounted) {
  377. this.loadData()
  378. }
  379. this._mounted = true
  380. },
  381. methods: {
  382. // ======================== 数据加载 ========================
  383. async loadData() {
  384. this.loading = true
  385. try {
  386. const userInfo = uni.getStorageSync('userInfo')
  387. const role = uni.getStorageSync('currentRole') || 'child'
  388. if (role !== 'child') {
  389. uni.reLaunch({ url: '/pages/index/parent-index' })
  390. return
  391. }
  392. this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : '小朋友'
  393. const childrenRes = await getChildren()
  394. const children = childrenRes.data || []
  395. if (children.length === 0) {
  396. this.hasChildren = false
  397. this.loading = false
  398. return
  399. }
  400. this.hasChildren = true
  401. const child = children[0] || {}
  402. this.childId = child.id || ''
  403. this.totalPoints = child.totalPoints || 0
  404. this.streakDays = child.streakDays || 0
  405. const tasksRes = await getTodayTasks(this.childId)
  406. const rawTasks = tasksRes.data || []
  407. this.todayTasks = rawTasks
  408. this.completedCount = rawTasks.filter(function(t) { return t.status === 'completed' }).length
  409. this.totalTaskCount = rawTasks.length
  410. // 解锁成就
  411. if (this.streakDays >= 7) {
  412. this.unlockBadge(1)
  413. }
  414. if (this.totalPoints >= 1000) {
  415. this.unlockBadge(2)
  416. }
  417. // 加载五维能量概览
  418. var energyChildId = this.childId
  419. if (energyChildId) {
  420. try {
  421. var overviewRes = await getEnergyOverview(energyChildId)
  422. if (overviewRes && overviewRes.data) {
  423. this.energyDimensions = overviewRes.data.dimensions || []
  424. this.totalEnergy = overviewRes.data.totalEnergy || 0
  425. this.totalHealthIndex = overviewRes.data.totalHealthIndex || 0
  426. }
  427. } catch (e) {
  428. console.log('获取能量概览失败', e)
  429. }
  430. }
  431. } catch (e) {
  432. console.error('加载数据失败', e)
  433. uni.showToast({ title: '加载失败', icon: 'none' })
  434. } finally {
  435. this.loading = false
  436. }
  437. },
  438. unlockBadge(id) {
  439. var badge = this.badges.find(function(b) { return b.id === id })
  440. if (badge) badge.unlocked = true
  441. },
  442. // ======================== 导航方法 ========================
  443. goToRewards() {
  444. this.$emit('navigate', { page: 'rewards' })
  445. uni.switchTab({ url: '/pages/rewards/rewards' })
  446. },
  447. goToTasks() {
  448. this.$emit('navigate', { page: 'tasks' })
  449. uni.switchTab({ url: '/pages/tasks/tasks' })
  450. },
  451. goToGrowth() {
  452. this.$emit('navigate', { page: 'growth' })
  453. uni.navigateTo({ url: '/pages/growth/growth' })
  454. },
  455. goToGames() {
  456. this.$emit('navigate', { page: 'games' })
  457. uni.navigateTo({ url: '/pages/games/games' })
  458. },
  459. goToAddChild() {
  460. this.$emit('navigate', { page: 'add-child' })
  461. uni.navigateTo({ url: '/pages/profile/create-child' })
  462. },
  463. showStreakDetail() {
  464. this.$emit('navigate', { page: 'streak-detail' })
  465. uni.showToast({ title: '连续打卡 ' + this.streakDays + ' 天啦!太棒了!', icon: 'none' })
  466. },
  467. showAchievements() {
  468. uni.showToast({ title: '成就页面开发中', icon: 'none' })
  469. },
  470. switchMode() {
  471. uni.showModal({
  472. title: '切换模式',
  473. content: '确定要切换回家长模式吗?',
  474. success: function(res) {
  475. if (res.confirm) {
  476. uni.switchTab({ url: '/pages/index/parent-index' })
  477. }
  478. }
  479. })
  480. },
  481. goToDomain(domain) {
  482. uni.showToast({ title: '即将上线', icon: 'none' })
  483. },
  484. goToEnergyDetail(code) {
  485. // 跳转到能量详情页(T10 创建)
  486. var childId = this.childId
  487. if (!childId || !code) return
  488. uni.navigateTo({
  489. url: '/pages/energy/detail?childId=' + childId + '&code=' + code
  490. })
  491. },
  492. // ======================== 任务交互 ========================
  493. handleTaskClick(task) {
  494. if (!task || !task.id) return
  495. this.$emit('navigate', { page: 'task-detail', taskId: task.id })
  496. // 如果任务尚未完成,弹出确认完成
  497. if (task.status !== 'completed' && task.status !== 'overdue') {
  498. uni.showModal({
  499. title: '完成任务',
  500. content: '确定完成「' + task.title + '」吗?',
  501. success: function(res) {
  502. if (res.confirm) {
  503. task.status = 'completed'
  504. // 重新计算完成数
  505. this.completedCount = this.todayTasks.filter(function(t) { return t.status === 'completed' }).length
  506. if (task.points) {
  507. uni.showToast({ title: '🎉 获得 ' + task.points + ' 颗星星!', icon: 'success' })
  508. }
  509. }
  510. }.bind(this)
  511. })
  512. }
  513. },
  514. // ======================== 任务工具方法 ========================
  515. getTaskIcon(task) {
  516. if (!task) return '📝'
  517. var cat = task.category || '其他'
  518. return TASK_ICONS[cat] || '📝'
  519. },
  520. getTaskCategoryClass(task) {
  521. if (!task) return 'default'
  522. var cat = task.category || '其他'
  523. var map = {
  524. '学习类': 'study',
  525. '阅读类': 'reading',
  526. '生活类': 'life',
  527. '运动类': 'sports',
  528. '家务类': 'chores',
  529. '艺术类': 'art'
  530. }
  531. return map[cat] || 'default'
  532. },
  533. getTaskStatus(task) {
  534. if (!task || !task.status) return 'pending'
  535. return task.status
  536. },
  537. getStatusText(task) {
  538. var status = this.getTaskStatus(task)
  539. if (status === 'completed') return '✅ 已完成'
  540. if (status === 'overdue') return '⏰ 已超时'
  541. return '⏳ 待完成'
  542. },
  543. getBadgeVariant(task) {
  544. var status = this.getTaskStatus(task)
  545. if (status === 'completed') return 'success'
  546. if (status === 'overdue') return 'error'
  547. return 'warning'
  548. },
  549. lockedKey(item) {
  550. return 'locked-' + item.id
  551. }
  552. }
  553. }
  554. </script>
  555. <style scoped>
  556. /* ============================================================
  557. 知心益家 — 孩子端首页 Flat Rounded 设计系统
  558. 色彩体系:品牌蓝(#5B9BD5)+浅蓝(#8FC5E8) | 双重阴影 | 4rpx边框
  559. 参考:uni.scss --color-primary, --bg, --shadow-clay, --radius-md
  560. ============================================================ */
  561. /* ----------------------------------------------------------
  562. 容器 & 背景
  563. ---------------------------------------------------------- */
  564. .container {
  565. min-height: 100vh;
  566. background: var(--bg, #F5F9FC);
  567. padding: 0 32rpx 48rpx;
  568. }
  569. .main-content {
  570. padding-top: 16rpx;
  571. }
  572. .bottom-spacer {
  573. height: 48rpx;
  574. }
  575. /* ----------------------------------------------------------
  576. A) 五维理念区
  577. ---------------------------------------------------------- */
  578. .philosophy-section {
  579. margin-bottom: 32rpx;
  580. }
  581. .philosophy-banner {
  582. background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
  583. border-radius: 28rpx;
  584. padding: 32rpx 28rpx 24rpx;
  585. text-align: center;
  586. border: 3rpx solid rgba(147, 197, 253, 0.2);
  587. box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
  588. }
  589. .philosophy-title {
  590. font-size: 36rpx;
  591. font-weight: 800;
  592. color: #FFD700;
  593. letter-spacing: 8rpx;
  594. display: block;
  595. margin-bottom: 24rpx;
  596. text-shadow: 0 2rpx 12rpx rgba(255, 215, 0, 0.3);
  597. }
  598. .philosophy-five {
  599. display: flex;
  600. justify-content: space-around;
  601. align-items: center;
  602. flex-wrap: wrap;
  603. gap: 12rpx 8rpx;
  604. }
  605. .five-item {
  606. font-size: 26rpx;
  607. font-weight: 700;
  608. color: #FFFFFF;
  609. display: inline-flex;
  610. align-items: center;
  611. gap: 6rpx;
  612. padding: 10rpx 16rpx;
  613. border-radius: 24rpx;
  614. background: rgba(255, 255, 255, 0.08);
  615. border: 2rpx solid rgba(255, 255, 255, 0.12);
  616. }
  617. .five-body { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
  618. .five-wisdom { border-color: rgba(251, 191, 36, 0.4); color: #FDE68A; }
  619. .five-mind { border-color: rgba(251, 146, 60, 0.4); color: #FDBA74; }
  620. .five-action { border-color: rgba(134, 239, 172, 0.4); color: #BBF7D0; }
  621. .five-wealth { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
  622. /* 连续打卡徽章 — 独立行 */
  623. .streak-badge-wrap {
  624. display: flex;
  625. align-items: center;
  626. gap: 10rpx;
  627. justify-content: flex-end;
  628. margin-bottom: 24rpx;
  629. padding-top: 4rpx;
  630. }
  631. .streak-badge {
  632. display: flex;
  633. align-items: center;
  634. gap: 10rpx;
  635. background: linear-gradient(145deg, #F5F9FC, #E0ECF8);
  636. padding: 12rpx 24rpx 12rpx 18rpx;
  637. border-radius: 40rpx;
  638. border: 4rpx solid #8FC5E8;
  639. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
  640. 4rpx 4rpx 14rpx rgba(143,197,232,0.30);
  641. flex-shrink: 0;
  642. transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  643. }
  644. .streak-badge:active,
  645. .streak-pressed {
  646. transform: scale(0.94);
  647. }
  648. .streak-fire {
  649. font-size: 32rpx;
  650. }
  651. .streak-info {
  652. display: flex;
  653. flex-direction: column;
  654. align-items: center;
  655. line-height: 1.2;
  656. }
  657. .streak-days {
  658. font-size: 32rpx;
  659. font-weight: 800;
  660. color: #1E4A7A;
  661. line-height: 1.1;
  662. }
  663. .streak-unit {
  664. font-size: 18rpx;
  665. font-weight: 600;
  666. color: #2563EB;
  667. }
  668. /* ----------------------------------------------------------
  669. 积分卡片 — Flat Rounded 蓝色渐变
  670. ---------------------------------------------------------- */
  671. .points-card {
  672. position: relative;
  673. background: linear-gradient(145deg, #8FC5E8, #5B9BD5);
  674. border-radius: 32rpx;
  675. padding: 32rpx;
  676. margin-bottom: 28rpx;
  677. border: 4rpx solid #3A7CC4;
  678. box-shadow: inset -4rpx -4rpx 14rpx rgba(255,255,255,0.20),
  679. 8rpx 8rpx 28rpx rgba(91,155,213,0.25);
  680. overflow: hidden;
  681. transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  682. }
  683. .points-card:active,
  684. .points-card-pressed {
  685. transform: scale(0.97);
  686. box-shadow: inset 2rpx 2rpx 10rpx rgba(0,0,0,0.08),
  687. 4rpx 4rpx 16rpx rgba(91,155,213,0.18);
  688. }
  689. /* 装饰圆 */
  690. .points-deco {
  691. position: absolute;
  692. border-radius: 50%;
  693. background: rgba(255,255,255,0.08);
  694. pointer-events: none;
  695. }
  696. .points-deco--1 {
  697. top: -60rpx;
  698. right: -40rpx;
  699. width: 220rpx;
  700. height: 220rpx;
  701. }
  702. .points-deco--2 {
  703. bottom: -30rpx;
  704. left: -30rpx;
  705. width: 120rpx;
  706. height: 120rpx;
  707. }
  708. .points-top {
  709. display: flex;
  710. justify-content: space-between;
  711. align-items: flex-start;
  712. margin-bottom: 20rpx;
  713. position: relative;
  714. z-index: 1;
  715. }
  716. .points-left {
  717. display: flex;
  718. flex-direction: column;
  719. }
  720. .points-label {
  721. font-size: 24rpx;
  722. color: rgba(255,255,255,0.85);
  723. font-weight: 500;
  724. margin-bottom: 4rpx;
  725. }
  726. .points-number {
  727. font-size: 64rpx;
  728. font-weight: 800;
  729. color: #FFFFFF;
  730. line-height: 1.1;
  731. text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.10);
  732. }
  733. .points-level-tag {
  734. display: flex;
  735. flex-direction: column;
  736. align-items: center;
  737. background: rgba(255,255,255,0.15);
  738. padding: 12rpx 20rpx;
  739. border-radius: 24rpx;
  740. border: 2rpx solid rgba(255,255,255,0.20);
  741. min-width: 120rpx;
  742. }
  743. .level-emoji {
  744. font-size: 36rpx;
  745. line-height: 1;
  746. }
  747. .level-name {
  748. font-size: 22rpx;
  749. font-weight: 700;
  750. color: #FFD700;
  751. margin-top: 4rpx;
  752. }
  753. /* 进度条 */
  754. .points-progress {
  755. position: relative;
  756. z-index: 1;
  757. }
  758. .progress-hint {
  759. display: block;
  760. font-size: 22rpx;
  761. color: rgba(255,255,255,0.75);
  762. margin-bottom: 12rpx;
  763. }
  764. .progress-hint-num {
  765. font-weight: 700;
  766. color: #FFD700;
  767. font-size: 24rpx;
  768. }
  769. .progress-track {
  770. height: 14rpx;
  771. background: rgba(255,255,255,0.20);
  772. border-radius: 7rpx;
  773. overflow: hidden;
  774. border: 2rpx solid rgba(255,255,255,0.10);
  775. }
  776. .progress-fill {
  777. height: 100%;
  778. background: linear-gradient(90deg, #FFD700, #FFB800);
  779. border-radius: 6rpx;
  780. transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
  781. }
  782. /* ----------------------------------------------------------
  783. 任务区域 — 水平滚动黏土卡片
  784. ---------------------------------------------------------- */
  785. .tasks-section {
  786. margin-bottom: 28rpx;
  787. }
  788. .section-header {
  789. display: flex;
  790. justify-content: space-between;
  791. align-items: center;
  792. margin-bottom: 16rpx;
  793. }
  794. .section-title {
  795. font-size: 32rpx;
  796. font-weight: 700;
  797. color: var(--text, #1E293B);
  798. }
  799. .section-link {
  800. font-size: 24rpx;
  801. font-weight: 600;
  802. color: var(--color-primary, #5B9BD5);
  803. padding: 8rpx 4rpx;
  804. }
  805. .section-link:active {
  806. opacity: 0.6;
  807. }
  808. /* 任务水平滚动容器 */
  809. .tasks-scroll {
  810. display: flex;
  811. flex-direction: row;
  812. white-space: nowrap;
  813. overflow-x: auto;
  814. padding: 8rpx 0 16rpx;
  815. -webkit-overflow-scrolling: touch;
  816. }
  817. /* 单张任务黏土卡片 */
  818. .task-clay-card {
  819. display: inline-flex;
  820. flex-direction: column;
  821. align-items: center;
  822. width: 200rpx;
  823. min-height: 260rpx;
  824. background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
  825. border-radius: 28rpx;
  826. padding: 24rpx 16rpx 20rpx;
  827. margin-right: 20rpx;
  828. border: 4rpx solid var(--border, #CBD5E1);
  829. box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
  830. 6rpx 6rpx 20rpx rgba(91,155,213,0.08);
  831. transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  832. animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
  833. flex-shrink: 0;
  834. }
  835. .task-clay-card:active,
  836. .task-card-pressed {
  837. transform: scale(0.94);
  838. }
  839. /* 任务分类图标容器 */
  840. .task-card-icon-wrap {
  841. width: 88rpx;
  842. height: 88rpx;
  843. border-radius: 24rpx;
  844. display: flex;
  845. align-items: center;
  846. justify-content: center;
  847. margin-bottom: 12rpx;
  848. border: 3rpx solid var(--border-light, #E2E8F0);
  849. box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
  850. 3rpx 3rpx 8rpx rgba(91,155,213,0.06);
  851. }
  852. .task-card-icon {
  853. font-size: 42rpx;
  854. line-height: 1;
  855. }
  856. /* 分类图标底色 */
  857. .task-cat--study .task-card-icon-wrap { background: rgba(14,165,233,0.12); border-color: #BAE6FD; }
  858. .task-cat--reading .task-card-icon-wrap { background: rgba(34,197,94,0.12); border-color: #BBF7D0; }
  859. .task-cat--life .task-card-icon-wrap { background: rgba(91,155,213,0.12); border-color: #CBD5E1; }
  860. .task-cat--sports .task-card-icon-wrap { background: rgba(59,130,246,0.12); border-color: #BFDBFE; }
  861. .task-cat--chores .task-card-icon-wrap { background: rgba(245,158,11,0.12); border-color: #FDE68A; }
  862. .task-cat--art .task-card-icon-wrap { background: rgba(236,72,153,0.12); border-color: #FBCFE8; }
  863. .task-cat--default .task-card-icon-wrap { background: rgba(148,163,184,0.12); border-color: #E2E8F0; }
  864. .task-card-name {
  865. font-size: 26rpx;
  866. font-weight: 600;
  867. color: var(--text, #1E293B);
  868. text-align: center;
  869. line-height: 1.3;
  870. overflow: hidden;
  871. text-overflow: ellipsis;
  872. white-space: nowrap;
  873. max-width: 100%;
  874. margin-bottom: 6rpx;
  875. }
  876. .task-card-points {
  877. font-size: 24rpx;
  878. font-weight: 700;
  879. color: var(--color-primary, #5B9BD5);
  880. margin-bottom: 10rpx;
  881. }
  882. .task-card-badge {
  883. display: flex;
  884. align-items: center;
  885. justify-content: center;
  886. min-height: 36rpx;
  887. }
  888. /* 任务空状态 */
  889. .tasks-empty {
  890. display: flex;
  891. flex-direction: column;
  892. align-items: center;
  893. justify-content: center;
  894. padding: 48rpx 32rpx;
  895. background: var(--surface, #FFFFFF);
  896. border-radius: 28rpx;
  897. border: 4rpx solid var(--border, #CBD5E1);
  898. box-shadow: var(--shadow-clay);
  899. transition: transform 0.2s ease;
  900. }
  901. .tasks-empty:active {
  902. transform: scale(0.98);
  903. }
  904. .tasks-empty-icon {
  905. font-size: 64rpx;
  906. margin-bottom: 16rpx;
  907. }
  908. .tasks-empty-title {
  909. font-size: 30rpx;
  910. font-weight: 600;
  911. color: var(--text, #1E293B);
  912. margin-bottom: 8rpx;
  913. }
  914. .tasks-empty-desc {
  915. font-size: 24rpx;
  916. color: var(--text-secondary, #64748B);
  917. }
  918. /* ----------------------------------------------------------
  919. 快捷操作 — 2×2 黏土按钮网格
  920. ---------------------------------------------------------- */
  921. .quick-section {
  922. margin-bottom: 28rpx;
  923. }
  924. .quick-grid {
  925. display: flex;
  926. flex-wrap: wrap;
  927. gap: 20rpx;
  928. }
  929. .quick-grid > * {
  930. width: calc(50% - 10rpx);
  931. }
  932. .quick-clay-btn {
  933. display: flex;
  934. flex-direction: column;
  935. align-items: center;
  936. justify-content: center;
  937. padding: 28rpx 16rpx;
  938. background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
  939. border-radius: 28rpx;
  940. border: 4rpx solid var(--border, #CBD5E1);
  941. box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
  942. 6rpx 6rpx 20rpx rgba(91,155,213,0.08);
  943. transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  944. min-height: 180rpx;
  945. }
  946. .quick-clay-btn:active,
  947. .quick-btn-pressed {
  948. transform: scale(0.94);
  949. box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.06),
  950. 3rpx 3rpx 10rpx rgba(91,155,213,0.06);
  951. }
  952. .quick-btn-icon {
  953. width: 96rpx;
  954. height: 96rpx;
  955. border-radius: 28rpx;
  956. display: flex;
  957. align-items: center;
  958. justify-content: center;
  959. margin-bottom: 14rpx;
  960. border: 3rpx solid var(--border-light, #E2E8F0);
  961. box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
  962. 3rpx 3rpx 8rpx rgba(91,155,213,0.06);
  963. transition: transform 0.2s ease;
  964. }
  965. .quick-emoji {
  966. font-size: 44rpx;
  967. line-height: 1;
  968. }
  969. /* 各按钮图标色彩 */
  970. .quick-icon--tasks { background: rgba(14,165,233,0.15); border-color: #BAE6FD; }
  971. .quick-icon--rewards { background: rgba(245,158,11,0.15); border-color: #FDE68A; }
  972. .quick-icon--growth { background: rgba(91,155,213,0.15); border-color: #CBD5E1; }
  973. .quick-icon--games { background: rgba(236,72,153,0.12); border-color: #FBCFE8; }
  974. .quick-btn-label {
  975. font-size: 28rpx;
  976. font-weight: 700;
  977. color: var(--text, #1E293B);
  978. }
  979. /* ----------------------------------------------------------
  980. 成就区域 — 水平滚动徽章
  981. ---------------------------------------------------------- */
  982. .achievement-section {
  983. margin-bottom: 28rpx;
  984. }
  985. .achievement-scroll {
  986. display: flex;
  987. flex-direction: row;
  988. white-space: nowrap;
  989. overflow-x: auto;
  990. padding: 8rpx 0 8rpx;
  991. -webkit-overflow-scrolling: touch;
  992. }
  993. .achievement-item {
  994. display: inline-flex;
  995. flex-direction: column;
  996. align-items: center;
  997. width: 160rpx;
  998. margin-right: 20rpx;
  999. transition: transform 0.2s ease;
  1000. flex-shrink: 0;
  1001. }
  1002. .achievement-item:active,
  1003. .achievement-pressed {
  1004. transform: scale(0.92);
  1005. }
  1006. .achievement-icon-wrap {
  1007. width: 100rpx;
  1008. height: 100rpx;
  1009. border-radius: 50%;
  1010. background: linear-gradient(145deg, #DCE8F5, #C8DDF0);
  1011. border: 4rpx solid var(--border, #CBD5E1);
  1012. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
  1013. 4rpx 4rpx 14rpx rgba(91,155,213,0.08);
  1014. display: flex;
  1015. align-items: center;
  1016. justify-content: center;
  1017. margin-bottom: 10rpx;
  1018. }
  1019. .achievement-icon {
  1020. font-size: 44rpx;
  1021. line-height: 1;
  1022. }
  1023. .achievement-name {
  1024. font-size: 24rpx;
  1025. font-weight: 600;
  1026. color: var(--text, #1E293B);
  1027. text-align: center;
  1028. }
  1029. /* 锁定徽章 */
  1030. .achievement-item--locked {
  1031. opacity: 0.50;
  1032. }
  1033. .achievement-icon-wrap--locked {
  1034. background: var(--bg-grey, #EDF2F7);
  1035. border-color: var(--border-light, #E2E8F0);
  1036. box-shadow: none;
  1037. }
  1038. .achievement-icon--locked {
  1039. filter: grayscale(1);
  1040. }
  1041. .achievement-name--locked {
  1042. color: var(--muted, #94A3B8);
  1043. }
  1044. /* ----------------------------------------------------------
  1045. 加载 & 通用
  1046. ---------------------------------------------------------- */
  1047. .loading-state {
  1048. display: flex;
  1049. flex-direction: column;
  1050. align-items: center;
  1051. padding: 200rpx 0;
  1052. }
  1053. /* 连续打卡徽章的浮动动画 — 使用 uni.scss 全局 float keyframe */
  1054. .streak-badge {
  1055. animation: float 3s ease-in-out infinite;
  1056. }
  1057. /* 等级标签的金色光晕 — 使用 uni.scss 全局 pulseGlow keyframe */
  1058. .points-level-tag {
  1059. animation: pulseGlow 2.5s ease-in-out infinite;
  1060. }
  1061. </style>