child-index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. <template>
  2. <view class="container">
  3. <!-- 加载状态 -->
  4. <view class="loading-state" v-if="loading">
  5. <view class="loading-spinner"></view>
  6. <text class="loading-text">加载中...</text>
  7. </view>
  8. <!-- 空状态 - 无孩子 -->
  9. <view class="empty-state" v-if="!loading && !hasChildren">
  10. <view class="empty-illustration">🌈</view>
  11. <text class="empty-title">欢迎来到知行益家!</text>
  12. <text class="empty-desc">让爸爸妈妈帮你添加,开始快乐成长之旅</text>
  13. <button class="btn-add" @click="goToAddChild">添加我</button>
  14. </view>
  15. <!-- 有孩子时 -->
  16. <view class="main-content" v-if="!loading && hasChildren">
  17. <!-- 欢迎区 -->
  18. <view class="welcome-section">
  19. <view class="welcome-left">
  20. <text class="hello">你好呀!{{ nickname || '小朋友' }}</text>
  21. <text class="greeting">{{ getGreeting() }}</text>
  22. </view>
  23. <view class="streak-badge" v-if="streakDays > 0" @click="showStreakDetail">
  24. <text class="fire">🔥</text>
  25. <view class="streak-info">
  26. <text class="streak-days">{{ streakDays }}天</text>
  27. <text class="streak-label">坚持</text>
  28. </view>
  29. </view>
  30. </view>
  31. <!-- 积分卡片 -->
  32. <view class="points-card" @click="goToRewards">
  33. <view class="points-bg"></view>
  34. <view class="points-main">
  35. <view class="star-icon">⭐</view>
  36. <view class="points-content">
  37. <text class="points-label">我的星星</text>
  38. <text class="points-value">{{ totalPoints }}</text>
  39. </view>
  40. <view class="points-arrow">→</view>
  41. </view>
  42. <view class="points-level">
  43. <text class="level-name">{{ getLevelName() }}</text>
  44. <view class="level-bar">
  45. <view class="level-fill" :style="{ width: levelProgress + '%' }"></view>
  46. </view>
  47. <text class="level-next" v-if="pointsToNext > 0">还差 {{ pointsToNext }} 颗星星升级</text>
  48. </view>
  49. </view>
  50. <!-- 任务进度卡片 -->
  51. <view class="task-card">
  52. <view class="task-header">
  53. <text class="task-title">📝 今日任务</text>
  54. <text class="task-count">{{ completedCount }}/{{ totalTaskCount }}</text>
  55. </view>
  56. <view class="task-progress">
  57. <view class="progress-track">
  58. <view class="progress-fill" :style="{ width: taskProgress + '%' }"></view>
  59. </view>
  60. </view>
  61. <view class="task-status all-done" v-if="taskProgress >= 100 && totalTaskCount > 0">
  62. <text class="status-icon">🎉</text>
  63. <text class="status-text">太棒啦!任务全部完成!</text>
  64. </view>
  65. <view class="task-status pending" v-else-if="totalTaskCount > 0">
  66. <text class="status-icon">💪</text>
  67. <text class="status-text">再完成 {{ totalTaskCount - completedCount }} 项任务</text>
  68. </view>
  69. <view class="task-status empty" v-else>
  70. <text class="status-icon">🌸</text>
  71. <text class="status-text">今天没有任务,放松一下吧</text>
  72. </view>
  73. </view>
  74. <!-- 任务列表 -->
  75. <view class="tasks-section">
  76. <view class="section-header">
  77. <text class="section-title">今天要做的事</text>
  78. <text class="section-more" @click="goToTasks">查看全部 →</text>
  79. </view>
  80. <view class="task-list" v-if="todayTasks.length > 0">
  81. <view class="task-item" v-for="task in todayTasks" :key="task.id" :class="{ completed: task.status === 'completed' }">
  82. <view class="task-check" @click="toggleTask(task)">
  83. <text v-if="task.status === 'completed'">✅</text>
  84. <text v-else>⭕</text>
  85. </view>
  86. <view class="task-content">
  87. <text class="task-name">{{ task.title }}</text>
  88. <text class="task-reward" v-if="task.points">+{{ task.points }}⭐</text>
  89. </view>
  90. </view>
  91. </view>
  92. <view class="task-list-empty" v-else>
  93. <text class="empty-small-icon">🎯</text>
  94. <text class="empty-small-text">暂无今日任务</text>
  95. </view>
  96. </view>
  97. <!-- 成就徽章 -->
  98. <view class="badges-section">
  99. <view class="section-header">
  100. <text class="section-title">🏆 我的成就</text>
  101. </view>
  102. <view class="badges-row">
  103. <view class="badge-item" v-for="badge in badges" :key="badge.id" :class="{ locked: !badge.unlocked }">
  104. <text class="badge-icon">{{ badge.icon }}</text>
  105. <text class="badge-name">{{ badge.name }}</text>
  106. </view>
  107. </view>
  108. </view>
  109. <!-- 快捷操作 -->
  110. <view class="quick-section">
  111. <view class="quick-item" @click="goToRewards">
  112. <view class="quick-icon-wrap reward-icon-bg">
  113. <text class="quick-icon">🎁</text>
  114. </view>
  115. <text class="quick-label">换礼物</text>
  116. </view>
  117. <view class="quick-item" @click="goToTasks">
  118. <view class="quick-icon-wrap task-icon-bg">
  119. <text class="quick-icon">📋</text>
  120. </view>
  121. <text class="quick-label">做任务</text>
  122. </view>
  123. <view class="quick-item" @click="showAchievements">
  124. <view class="quick-icon-wrap badge-icon-bg">
  125. <text class="quick-icon">🏅</text>
  126. </view>
  127. <text class="quick-label">成就</text>
  128. </view>
  129. <view class="quick-item" @click="switchMode">
  130. <view class="quick-icon-wrap switch-icon-bg">
  131. <text class="quick-icon">👨‍👩‍👧</text>
  132. </view>
  133. <text class="quick-label">切换</text>
  134. </view>
  135. </view>
  136. </view>
  137. </view>
  138. </template>
  139. <script>
  140. import { getChildren, getTodayTasks } from '../../utils/api.js'
  141. export default {
  142. data() {
  143. return {
  144. loading: true,
  145. hasChildren: false,
  146. nickname: '',
  147. childId: '',
  148. totalPoints: 0,
  149. streakDays: 0,
  150. completedCount: 0,
  151. totalTaskCount: 0,
  152. todayTasks: [],
  153. badges: [
  154. { id: 1, icon: '🌟', name: '早起鸟', unlocked: false },
  155. { id: 2, icon: '📚', name: '阅读小达人', unlocked: false },
  156. { id: 3, icon: '🏃', name: '运动健将', unlocked: false },
  157. { id: 4, icon: '🎨', name: '小艺术家', unlocked: false },
  158. { id: 5, icon: '🧹', name: '家务小能手', unlocked: false }
  159. ],
  160. _mounted: false
  161. }
  162. },
  163. computed: {
  164. taskProgress() {
  165. if (this.totalTaskCount === 0) return 0
  166. return Math.floor((this.completedCount / this.totalTaskCount) * 100)
  167. },
  168. levelProgress() {
  169. return (this.totalPoints % 200) / 200 * 100
  170. },
  171. pointsToNext() {
  172. return this.totalPoints > 0 ? 200 - (this.totalPoints % 200) : 200
  173. }
  174. },
  175. mounted() {
  176. this.loadData()
  177. },
  178. onShow() {
  179. if (this._mounted) {
  180. this.loadData()
  181. }
  182. this._mounted = true
  183. },
  184. methods: {
  185. async loadData() {
  186. this.loading = true
  187. try {
  188. const userInfo = uni.getStorageSync('userInfo')
  189. const role = uni.getStorageSync('currentRole') || 'child'
  190. if (role !== 'child') {
  191. uni.reLaunch({ url: '/pages/index/parent-index' })
  192. return
  193. }
  194. this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : '小朋友'
  195. const childrenRes = await getChildren()
  196. const children = childrenRes.data || []
  197. if (children.length === 0) {
  198. this.hasChildren = false
  199. this.loading = false
  200. return
  201. }
  202. this.hasChildren = true
  203. const child = children[0] || {}
  204. this.childId = child.id || ''
  205. this.totalPoints = child.totalPoints || 0
  206. this.streakDays = child.streakDays || 0
  207. const tasksRes = await getTodayTasks(this.childId)
  208. const rawTasks = tasksRes.data || []
  209. this.todayTasks = rawTasks
  210. this.completedCount = rawTasks.filter(function(t) { return t.status === 'completed' }).length
  211. this.totalTaskCount = rawTasks.length
  212. // 解锁成就
  213. if (this.streakDays >= 7) {
  214. this.unlockBadge(1)
  215. }
  216. if (this.totalPoints >= 1000) {
  217. this.unlockBadge(2)
  218. }
  219. } catch (e) {
  220. console.error('加载数据失败', e)
  221. uni.showToast({ title: '加载失败', icon: 'none' })
  222. } finally {
  223. this.loading = false
  224. }
  225. },
  226. unlockBadge(id) {
  227. const badge = this.badges.find(function(b) { return b.id === id })
  228. if (badge) badge.unlocked = true
  229. },
  230. getGreeting() {
  231. const hour = new Date().getHours()
  232. if (hour < 9) return '新的一天要加油哦!'
  233. if (hour < 12) return '上午好棒棒的!'
  234. if (hour < 14) return '午餐时间到~'
  235. if (hour < 18) return '下午也要继续努力!'
  236. return '一天过得好快呀~'
  237. },
  238. getLevelName() {
  239. const level = Math.floor(this.totalPoints / 200)
  240. const names = ['星星宝宝', '月亮小朋友', '太阳小天使', '宇宙小英雄']
  241. return names[Math.min(level, names.length - 1)]
  242. },
  243. toggleTask(task) {
  244. if (task.status === 'completed') return
  245. if (!task.id) return
  246. task.status = 'completed'
  247. this.completedCount = this.todayTasks.filter(function(t) { return t.status === 'completed' }).length
  248. if (task.points) {
  249. uni.showToast({ title: '获得 ' + task.points + ' 颗星星!', icon: 'success' })
  250. }
  251. },
  252. goToRewards() {
  253. uni.switchTab({ url: '/pages/rewards/rewards' })
  254. },
  255. goToTasks() {
  256. uni.switchTab({ url: '/pages/tasks/tasks' })
  257. },
  258. showAchievements() {
  259. uni.showToast({ title: '成就页面开发中', icon: 'none' })
  260. },
  261. switchMode() {
  262. uni.showModal({
  263. title: '切换模式',
  264. content: '确定要切换回家长模式吗?',
  265. success: function(res) {
  266. if (res.confirm) {
  267. uni.switchTab({ url: '/pages/index/parent-index' })
  268. }
  269. }
  270. })
  271. },
  272. showStreakDetail() {
  273. uni.showToast({ title: '连续打卡' + this.streakDays + '天啦!太棒了!', icon: 'none' })
  274. },
  275. goToAddChild() {
  276. uni.navigateTo({ url: '/pages/profile/create-child' })
  277. }
  278. }
  279. }
  280. </script>
  281. <style scoped>
  282. /* ========================================
  283. Claymorphism Design System - 暖橙黏土
  284. ======================================== */
  285. :root {
  286. --clay-br: 20rpx;
  287. --clay-br-lg: 26rpx;
  288. --clay-bw: 4rpx;
  289. --clay-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.6), 6rpx 6rpx 16rpx rgba(255,140,66,0.12);
  290. --clay-shadow-sm: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5), 4rpx 4rpx 10rpx rgba(255,140,66,0.10);
  291. --clay-transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  292. }
  293. .container {
  294. padding: 32rpx;
  295. background: #FFF0E6;
  296. min-height: 100vh;
  297. }
  298. /* ========================================
  299. 加载状态
  300. ======================================== */
  301. .loading-state {
  302. display: flex;
  303. flex-direction: column;
  304. align-items: center;
  305. padding: 200rpx 0;
  306. }
  307. .loading-spinner {
  308. width: 64rpx;
  309. height: 64rpx;
  310. border: 8rpx solid #FFE0CC;
  311. border-top-color: #FF8C42;
  312. border-radius: 50%;
  313. animation: spin 0.8s linear infinite;
  314. box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.4), 2rpx 2rpx 6rpx rgba(255,140,66,0.08);
  315. }
  316. @keyframes spin {
  317. to { transform: rotate(360deg); }
  318. }
  319. .loading-text {
  320. font-size: 26rpx;
  321. color: #B2BEC3;
  322. margin-top: 20rpx;
  323. }
  324. /* ========================================
  325. 空状态
  326. ======================================== */
  327. .empty-state {
  328. display: flex;
  329. flex-direction: column;
  330. align-items: center;
  331. justify-content: center;
  332. padding: 80rpx 40rpx;
  333. text-align: center;
  334. }
  335. .empty-illustration {
  336. font-size: 120rpx;
  337. margin-bottom: 32rpx;
  338. }
  339. .empty-title {
  340. font-size: 40rpx;
  341. font-weight: 700;
  342. color: #2D3436;
  343. display: block;
  344. margin-bottom: 16rpx;
  345. }
  346. .empty-desc {
  347. font-size: 28rpx;
  348. color: #636E72;
  349. display: block;
  350. margin-bottom: 40rpx;
  351. }
  352. .btn-add {
  353. background: linear-gradient(135deg, #FF8C42, #FF6B6B);
  354. color: #FFFFFF;
  355. font-size: 32rpx;
  356. font-weight: 600;
  357. padding: 24rpx 64rpx;
  358. border-radius: 50rpx;
  359. border: 4rpx solid #E07A3A;
  360. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.3), 6rpx 6rpx 18rpx rgba(255,107,107,0.35);
  361. transition: var(--clay-transition);
  362. }
  363. .btn-add:active {
  364. box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.15), 2rpx 2rpx 6rpx rgba(255,107,107,0.2);
  365. transform: scale(0.97);
  366. }
  367. /* ========================================
  368. 欢迎区
  369. ======================================== */
  370. .welcome-section {
  371. display: flex;
  372. justify-content: space-between;
  373. align-items: center;
  374. margin-bottom: 28rpx;
  375. }
  376. .welcome-left .hello {
  377. font-size: 40rpx;
  378. font-weight: 700;
  379. color: #2D3436;
  380. display: block;
  381. }
  382. .welcome-left .greeting {
  383. font-size: 26rpx;
  384. color: #636E72;
  385. display: block;
  386. margin-top: 8rpx;
  387. }
  388. .streak-badge {
  389. display: flex;
  390. align-items: center;
  391. background: linear-gradient(135deg, #FFF3CD, #FFE08C);
  392. padding: 12rpx 22rpx;
  393. border-radius: 30rpx;
  394. border: 4rpx solid #F5D97A;
  395. box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5), 4rpx 4rpx 12rpx rgba(255,215,0,0.3);
  396. flex-shrink: 0;
  397. }
  398. .streak-badge .fire {
  399. font-size: 32rpx;
  400. margin-right: 8rpx;
  401. }
  402. .streak-info {
  403. display: flex;
  404. flex-direction: column;
  405. }
  406. .streak-days {
  407. font-size: 28rpx;
  408. font-weight: 700;
  409. color: #92400E;
  410. }
  411. .streak-label {
  412. font-size: 20rpx;
  413. color: #B45309;
  414. }
  415. /* ========================================
  416. 积分卡片 - warm orange claymorphism
  417. ======================================== */
  418. .points-card {
  419. background: linear-gradient(135deg, #FF8C42, #FF6B6B);
  420. border-radius: 30rpx;
  421. padding: 32rpx;
  422. margin-bottom: 28rpx;
  423. border: 4rpx solid #E07A3A;
  424. box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.25), 8rpx 8rpx 24rpx rgba(255,107,107,0.3);
  425. position: relative;
  426. overflow: hidden;
  427. transition: var(--clay-transition);
  428. }
  429. .points-card:active {
  430. box-shadow: inset 3rpx 3rpx 10rpx rgba(0,0,0,0.12), 4rpx 4rpx 12rpx rgba(255,107,107,0.2);
  431. transform: scale(0.98);
  432. }
  433. .points-bg {
  434. position: absolute;
  435. top: -50rpx;
  436. right: -50rpx;
  437. width: 200rpx;
  438. height: 200rpx;
  439. background: rgba(255, 255, 255, 0.1);
  440. border-radius: 50%;
  441. }
  442. .points-main {
  443. display: flex;
  444. align-items: center;
  445. }
  446. .star-icon {
  447. font-size: 56rpx;
  448. margin-right: 16rpx;
  449. }
  450. .points-content {
  451. flex: 1;
  452. }
  453. .points-label {
  454. font-size: 24rpx;
  455. color: rgba(255, 255, 255, 0.85);
  456. display: block;
  457. }
  458. .points-value {
  459. font-size: 56rpx;
  460. font-weight: 800;
  461. color: #FFFFFF;
  462. }
  463. .points-arrow {
  464. font-size: 36rpx;
  465. color: rgba(255, 255, 255, 0.6);
  466. }
  467. .points-level {
  468. margin-top: 20rpx;
  469. padding-top: 20rpx;
  470. border-top: 1rpx solid rgba(255, 255, 255, 0.2);
  471. }
  472. .level-name {
  473. font-size: 24rpx;
  474. color: #FFD700;
  475. font-weight: 600;
  476. }
  477. .level-bar {
  478. height: 12rpx;
  479. background: rgba(255, 255, 255, 0.2);
  480. border-radius: 6rpx;
  481. margin: 12rpx 0;
  482. overflow: hidden;
  483. }
  484. .level-fill {
  485. height: 100%;
  486. background: linear-gradient(90deg, #FFD700, #FFA500);
  487. border-radius: 6rpx;
  488. transition: width 0.5s ease;
  489. }
  490. .level-next {
  491. font-size: 22rpx;
  492. color: rgba(255, 255, 255, 0.7);
  493. }
  494. /* ========================================
  495. 任务进度卡片 - claymorphism
  496. ======================================== */
  497. .task-card {
  498. background: #FFFFFF;
  499. border-radius: 26rpx;
  500. padding: 28rpx;
  501. margin-bottom: 28rpx;
  502. border: 4rpx solid #FFE0CC;
  503. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.6), 6rpx 6rpx 18rpx rgba(255,140,66,0.10);
  504. transition: var(--clay-transition);
  505. }
  506. .task-header {
  507. display: flex;
  508. justify-content: space-between;
  509. align-items: center;
  510. margin-bottom: 16rpx;
  511. }
  512. .task-title {
  513. font-size: 32rpx;
  514. font-weight: 700;
  515. color: #2D3436;
  516. }
  517. .task-count {
  518. font-size: 28rpx;
  519. color: #FF8C42;
  520. font-weight: 700;
  521. }
  522. .task-progress {
  523. margin-bottom: 16rpx;
  524. }
  525. .progress-track {
  526. height: 18rpx;
  527. background: #FFF0E6;
  528. border-radius: 9rpx;
  529. overflow: hidden;
  530. border: 2rpx solid #FFE0CC;
  531. }
  532. .progress-fill {
  533. height: 100%;
  534. background: linear-gradient(90deg, #4ECDC4, #44B09E);
  535. border-radius: 8rpx;
  536. transition: width 0.4s ease;
  537. }
  538. .task-status {
  539. display: flex;
  540. align-items: center;
  541. padding: 14rpx 18rpx;
  542. border-radius: 16rpx;
  543. border: 3rpx solid transparent;
  544. }
  545. .task-status.all-done {
  546. background: #ECFDF5;
  547. border-color: #A7F3D0;
  548. }
  549. .task-status.pending {
  550. background: #FEF3C7;
  551. border-color: #FDE68A;
  552. }
  553. .task-status.empty {
  554. background: #F0F7FF;
  555. border-color: #BFDBFE;
  556. }
  557. .status-icon {
  558. font-size: 28rpx;
  559. margin-right: 10rpx;
  560. }
  561. .status-text {
  562. font-size: 26rpx;
  563. }
  564. .task-status.all-done .status-text { color: #059669; }
  565. .task-status.pending .status-text { color: #D97706; }
  566. .task-status.empty .status-text { color: #3B82F6; }
  567. /* ========================================
  568. 任务列表
  569. ======================================== */
  570. .tasks-section {
  571. margin-bottom: 28rpx;
  572. }
  573. .section-header {
  574. display: flex;
  575. justify-content: space-between;
  576. align-items: center;
  577. margin-bottom: 16rpx;
  578. }
  579. .section-title {
  580. font-size: 32rpx;
  581. font-weight: 700;
  582. color: #2D3436;
  583. }
  584. .section-more {
  585. font-size: 24rpx;
  586. color: #FF8C42;
  587. font-weight: 500;
  588. }
  589. .task-list {
  590. background: #FFFFFF;
  591. border-radius: 22rpx;
  592. padding: 8rpx 0;
  593. border: 4rpx solid #FFE0CC;
  594. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 16rpx rgba(255,140,66,0.10);
  595. }
  596. .task-item {
  597. display: flex;
  598. align-items: center;
  599. padding: 22rpx 24rpx;
  600. border-bottom: 1rpx solid #FFE8D0;
  601. }
  602. .task-item:last-child {
  603. border-bottom: none;
  604. }
  605. .task-item.completed .task-name {
  606. text-decoration: line-through;
  607. color: #B2BEC3;
  608. }
  609. .task-check {
  610. font-size: 40rpx;
  611. margin-right: 16rpx;
  612. flex-shrink: 0;
  613. transition: var(--clay-transition);
  614. }
  615. .task-check:active {
  616. transform: scale(1.2);
  617. }
  618. .task-content {
  619. flex: 1;
  620. display: flex;
  621. justify-content: space-between;
  622. align-items: center;
  623. }
  624. .task-name {
  625. font-size: 28rpx;
  626. color: #2D3436;
  627. }
  628. .task-reward {
  629. font-size: 24rpx;
  630. color: #FF8C42;
  631. font-weight: 600;
  632. flex-shrink: 0;
  633. }
  634. .task-list-empty {
  635. background: #FFFFFF;
  636. border-radius: 22rpx;
  637. padding: 48rpx;
  638. display: flex;
  639. flex-direction: column;
  640. align-items: center;
  641. border: 4rpx solid #FFE0CC;
  642. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 16rpx rgba(255,140,66,0.10);
  643. }
  644. .empty-small-icon {
  645. font-size: 48rpx;
  646. margin-bottom: 12rpx;
  647. }
  648. .empty-small-text {
  649. font-size: 26rpx;
  650. color: #B2BEC3;
  651. }
  652. /* ========================================
  653. 成就徽章
  654. ======================================== */
  655. .badges-section {
  656. margin-bottom: 28rpx;
  657. }
  658. .badges-row {
  659. display: flex;
  660. gap: 16rpx;
  661. overflow-x: auto;
  662. padding: 8rpx 0;
  663. }
  664. .badge-item {
  665. display: flex;
  666. flex-direction: column;
  667. align-items: center;
  668. padding: 20rpx 28rpx;
  669. background: #FFFFFF;
  670. border-radius: 20rpx;
  671. min-width: 130rpx;
  672. border: 4rpx solid #FFE0CC;
  673. box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5), 4rpx 4rpx 12rpx rgba(255,140,66,0.08);
  674. transition: var(--clay-transition);
  675. }
  676. .badge-item.locked {
  677. opacity: 0.45;
  678. }
  679. .badge-icon {
  680. font-size: 44rpx;
  681. margin-bottom: 8rpx;
  682. }
  683. .badge-name {
  684. font-size: 22rpx;
  685. color: #636E72;
  686. white-space: nowrap;
  687. }
  688. /* ========================================
  689. 快捷操作 - claymorphism
  690. ======================================== */
  691. .quick-section {
  692. display: flex;
  693. justify-content: space-around;
  694. background: #FFFFFF;
  695. border-radius: 26rpx;
  696. padding: 28rpx 16rpx;
  697. margin-bottom: 48rpx;
  698. border: 4rpx solid #FFE0CC;
  699. box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5), 6rpx 6rpx 18rpx rgba(255,140,66,0.10);
  700. }
  701. .quick-item {
  702. display: flex;
  703. flex-direction: column;
  704. align-items: center;
  705. transition: var(--clay-transition);
  706. }
  707. .quick-item:active {
  708. transform: scale(0.92);
  709. }
  710. .quick-icon-wrap {
  711. width: 88rpx;
  712. height: 88rpx;
  713. border-radius: 24rpx;
  714. display: flex;
  715. align-items: center;
  716. justify-content: center;
  717. margin-bottom: 10rpx;
  718. border: 3rpx solid transparent;
  719. box-shadow: inset -2rpx -2rpx 5rpx rgba(255,255,255,0.4), 3rpx 3rpx 8rpx rgba(0,0,0,0.06);
  720. transition: var(--clay-transition);
  721. }
  722. .quick-item:active .quick-icon-wrap {
  723. box-shadow: inset 2rpx 2rpx 6rpx rgba(0,0,0,0.08), 1rpx 1rpx 3rpx rgba(0,0,0,0.04);
  724. }
  725. .quick-icon {
  726. font-size: 40rpx;
  727. }
  728. .reward-icon-bg { background: rgba(255, 217, 61, 0.25); border-color: #FDE68A; }
  729. .task-icon-bg { background: rgba(78, 205, 196, 0.2); border-color: #A7F3D0; }
  730. .badge-icon-bg { background: rgba(255, 140, 66, 0.2); border-color: #FED7AA; }
  731. .switch-icon-bg { background: rgba(116, 185, 255, 0.2); border-color: #BFDBFE; }
  732. .quick-label {
  733. font-size: 24rpx;
  734. color: #636E72;
  735. font-weight: 500;
  736. }
  737. /* ========================================
  738. Soft Press 通用交互
  739. ======================================== */
  740. .badge-item:active {
  741. transform: scale(0.95);
  742. box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.06), 2rpx 2rpx 6rpx rgba(255,140,66,0.06);
  743. }
  744. .task-item:active .task-check {
  745. transform: scale(1.15);
  746. }
  747. .section-more:active {
  748. opacity: 0.6;
  749. }
  750. </style>