child-index.vue 39 KB

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