checkin.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <template>
  2. <!-- iOS 修复:scroll-view 内部的 position:fixed 元素在 iOS 上不生效,
  3. 需将 fixed 元素(.dialog-mask / .energy-animation)移出 scroll-view,
  4. 作为 scroll-view 的同级兄弟节点,参考 cart.vue / product-detail.vue 布局 -->
  5. <view class="page">
  6. <scroll-view class="container" scroll-y>
  7. <!-- 顶部能量提示 -->
  8. <view class="energy-tip">
  9. <text class="energy-tip-text">⚡ 每次打卡可获得 5 财富能量</text>
  10. <text class="energy-tip-sub">坚持打卡,养成理财好习惯</text>
  11. </view>
  12. <!-- 当月日历打卡 -->
  13. <view class="calendar-card">
  14. <view class="calendar-header">
  15. <text class="calendar-title">{{ currentYear }}年{{ currentMonth }}月</text>
  16. <view class="calendar-nav">
  17. <text class="nav-btn" @click="changeMonth(-1)">‹</text>
  18. <text class="nav-btn" @click="changeMonth(1)">›</text>
  19. </view>
  20. </view>
  21. <view class="calendar-weekdays">
  22. <text class="weekday" v-for="(w, i) in weekdays" :key="i">{{ w }}</text>
  23. </view>
  24. <view class="calendar-days">
  25. <view class="day-cell empty" v-for="(e, i) in emptyDays" :key="getEmptyKey(i)"></view>
  26. <view
  27. v-for="(day, i) in daysInMonth"
  28. :key="getDayKey(i)"
  29. class="day-cell"
  30. :class="{
  31. checked: checkinDates[day],
  32. today: day === currentDay && currentMonth === realMonth && currentYear === realYear
  33. }"
  34. @click="onDayClick(day)">
  35. <text class="day-num">{{ day }}</text>
  36. <text class="day-dot" v-if="checkinDates[day]">●</text>
  37. </view>
  38. </view>
  39. <view class="calendar-stats">
  40. <text>本月打卡 <text class="stat-highlight">{{ checkinDaysThisMonth }}</text> 天</text>
  41. <text>连续 <text class="stat-highlight">{{ streakDays }}</text> 天</text>
  42. </view>
  43. </view>
  44. <!-- 快速打卡 -->
  45. <view class="quick-checkin">
  46. <view class="section-title-row">
  47. <text class="section-title">快速打卡</text>
  48. <text class="section-subtitle">今日已打卡 {{ todayCount }} 次</text>
  49. </view>
  50. <view class="checkin-types">
  51. <view
  52. v-for="(item, index) in checkinTypes"
  53. :key="index"
  54. class="checkin-btn"
  55. :class="{ disabled: item.disabled }"
  56. @click="showCheckinDialog(item.type)">
  57. <text class="checkin-icon">{{ item.icon }}</text>
  58. <text class="checkin-label">{{ item.label }}</text>
  59. </view>
  60. </view>
  61. </view>
  62. <!-- 统计卡片 -->
  63. <view class="stats-card">
  64. <view class="stat-item">
  65. <text class="stat-value">{{ stats.totalCheckins || 0 }}</text>
  66. <text class="stat-label">总打卡</text>
  67. </view>
  68. <view class="stat-divider"></view>
  69. <view class="stat-item">
  70. <text class="stat-value">{{ stats.totalEnergy || 0 }}</text>
  71. <text class="stat-label">总能量</text>
  72. </view>
  73. <view class="stat-divider"></view>
  74. <view class="stat-item">
  75. <text class="stat-value">{{ stats.dailyPlanCount || 0 }}</text>
  76. <text class="stat-label">规划</text>
  77. </view>
  78. <view class="stat-divider"></view>
  79. <view class="stat-item">
  80. <text class="stat-value">{{ stats.savingCount || 0 }}</text>
  81. <text class="stat-label">储蓄</text>
  82. </view>
  83. </view>
  84. <!-- 打卡记录 -->
  85. <view class="record-section">
  86. <view class="section-title-row">
  87. <text class="section-title">打卡记录</text>
  88. <text class="section-subtitle" @click="toggleRecords">
  89. {{ showAllRecords ? '收起' : '查看全部' }}
  90. </text>
  91. </view>
  92. <view v-if="displayRecords.length === 0" class="empty-state">
  93. <text class="empty-icon">📝</text>
  94. <text class="empty-text">暂无打卡记录,开始打卡吧!</text>
  95. </view>
  96. <view
  97. v-for="(item, index) in displayRecords"
  98. :key="index"
  99. class="record-item"
  100. @longpress="confirmDelete(item)">
  101. <view class="record-left">
  102. <text class="record-icon">{{ getTypeIcon(item.checkinType) }}</text>
  103. <view class="record-info">
  104. <text class="record-type">{{ getTypeLabel(item.checkinType) }}</text>
  105. <text class="record-date">{{ formatDate(item.createdAt) }}</text>
  106. <text class="record-note" v-if="item.note">{{ item.note }}</text>
  107. </view>
  108. </view>
  109. <view class="record-right">
  110. <text class="record-energy">+{{ item.earnedEnergy || 5 }}</text>
  111. <text class="record-mood" v-if="item.mood">{{ item.mood }}</text>
  112. </view>
  113. </view>
  114. </view>
  115. </scroll-view>
  116. <!-- 打卡弹窗 -->
  117. <view class="dialog-mask" v-if="showDialog" @click="closeDialog">
  118. <view class="dialog-card" @click.stop>
  119. <text class="dialog-title">{{ dialogType ? getTypeLabel(dialogType) : '' }}打卡</text>
  120. <view class="dialog-form">
  121. <view class="form-item">
  122. <text class="form-label">金额(选填)</text>
  123. <input class="form-input" type="digit" v-model="formAmount" placeholder="输入金额" />
  124. </view>
  125. <view class="form-item">
  126. <text class="form-label">分类</text>
  127. <picker class="form-picker" mode="selector" :range="categoryOptions" @change="onCategoryChange">
  128. <text class="picker-text">{{ formCategory || '选择分类' }}</text>
  129. </picker>
  130. </view>
  131. <view class="form-item">
  132. <text class="form-label">备注</text>
  133. <input class="form-input" type="text" v-model="formNote" placeholder="备注信息" />
  134. </view>
  135. <view class="form-item">
  136. <text class="form-label">心情</text>
  137. <view class="mood-picker">
  138. <text
  139. v-for="(m, i) in moodOptions"
  140. :key="i"
  141. class="mood-item"
  142. :class="{ selected: formMood === m.value }"
  143. @click="selectMood(m.value)">{{ m.icon }}</text>
  144. </view>
  145. </view>
  146. </view>
  147. <view class="dialog-actions">
  148. <button class="dialog-btn cancel" @click="closeDialog">取消</button>
  149. <button class="dialog-btn confirm" @click="doCheckin">确认打卡</button>
  150. </view>
  151. </view>
  152. </view>
  153. <!-- 能量奖励动画 -->
  154. <view class="energy-animation" v-if="showEnergyAnim">
  155. <text class="energy-anim-text">+{{ earnedEnergy }} ⚡</text>
  156. </view>
  157. </view>
  158. </template>
  159. <script>
  160. import { getCheckinList, getCheckinStats, createCheckin, deleteCheckin } from '../../utils/api'
  161. import { parseDate } from '../../utils/format'
  162. export default {
  163. data() {
  164. return {
  165. checkinTypes: [
  166. { type: 'daily_plan', label: '记账规划', icon: '📋' },
  167. { type: 'saving', label: '储蓄记录', icon: '💰' },
  168. { type: 'expense', label: '消费记录', icon: '🛒' },
  169. { type: 'review', label: '财务回顾', icon: '📊' }
  170. ],
  171. checkins: [],
  172. stats: {},
  173. todayCount: 0,
  174. todayTimer: null,
  175. // 日历
  176. currentYear: 0,
  177. currentMonth: 0,
  178. realMonth: 0,
  179. realYear: 0,
  180. currentDay: 0,
  181. weekdays: ['日', '一', '二', '三', '四', '五', '六'],
  182. checkinDates: {},
  183. streakDays: 0,
  184. // 弹窗
  185. showDialog: false,
  186. dialogType: '',
  187. formAmount: '',
  188. formCategory: '',
  189. formNote: '',
  190. formMood: '',
  191. categoryOptions: ['日常', '餐饮', '交通', '购物', '娱乐', '教育', '医疗', '其他'],
  192. moodOptions: [
  193. { icon: '😊', value: 'happy' },
  194. { icon: '😐', value: 'neutral' },
  195. { icon: '😔', value: 'sad' },
  196. { icon: '😤', value: 'angry' },
  197. { icon: '🎉', value: 'excited' }
  198. ],
  199. // 能量动画
  200. showEnergyAnim: false,
  201. earnedEnergy: 5,
  202. showAllRecords: false,
  203. memberId: null
  204. }
  205. },
  206. computed: {
  207. daysInMonth: function() {
  208. return new Date(this.currentYear, this.currentMonth, 0).getDate()
  209. },
  210. emptyDays: function() {
  211. var firstDay = new Date(this.currentYear, this.currentMonth - 1, 1).getDay()
  212. return firstDay
  213. },
  214. checkinDaysThisMonth: function() {
  215. return Object.keys(this.checkinDates).length
  216. },
  217. displayRecords: function() {
  218. if (this.showAllRecords) return this.checkins
  219. return this.checkins.slice(0, 10)
  220. }
  221. },
  222. onShow() {
  223. var now = new Date()
  224. this.currentYear = now.getFullYear()
  225. this.currentMonth = now.getMonth() + 1
  226. this.realMonth = this.currentMonth
  227. this.realYear = this.currentYear
  228. this.currentDay = now.getDate()
  229. this.memberId = this.$route && this.$route.query && this.$route.query.memberId
  230. this.loadData()
  231. this.loadCalendarData()
  232. },
  233. methods: {
  234. async loadData() {
  235. try {
  236. var params = {}
  237. if (this.memberId) params.memberId = this.memberId
  238. var res = await getCheckinList(params)
  239. if (res && res.code === 200) {
  240. this.checkins = res.data || []
  241. }
  242. var statsRes = await getCheckinStats(params)
  243. if (statsRes && statsRes.code === 200) {
  244. this.stats = statsRes.data || {}
  245. }
  246. this.calcTodayCount()
  247. } catch (e) {
  248. console.error('加载打卡数据失败', e)
  249. }
  250. },
  251. async loadCalendarData() {
  252. try {
  253. var ym = this.currentYear + '-' + (this.currentMonth < 10 ? '0' : '') + this.currentMonth
  254. var params = { yearMonth: ym }
  255. if (this.memberId) params.memberId = this.memberId
  256. var res = await getCheckinList(params)
  257. if (res && res.code === 200) {
  258. var list = res.data || []
  259. var dates = {}
  260. for (var i = 0; i < list.length; i++) {
  261. var d = list[i]
  262. if (d.checkinDate) {
  263. var day = parseInt(d.checkinDate.substring(8, 10))
  264. dates[day] = true
  265. }
  266. }
  267. this.checkinDates = dates
  268. this.calcStreak(list)
  269. }
  270. } catch (e) {
  271. console.error('加载日历数据失败', e)
  272. }
  273. },
  274. calcStreak: function(list) {
  275. if (!list || list.length === 0) { this.streakDays = 0; return }
  276. var dates = []
  277. for (var i = 0; i < list.length; i++) {
  278. if (list[i].checkinDate) dates.push(list[i].checkinDate)
  279. }
  280. dates.sort().reverse()
  281. var streak = 0
  282. var today = new Date()
  283. var checkDate = new Date(today.getFullYear(), today.getMonth(), today.getDate())
  284. for (var j = 0; j < dates.length; j++) {
  285. var d = parseDate(dates[j])
  286. if (!d) continue
  287. var diff = Math.round((checkDate - d) / 86400000)
  288. if (diff === streak) {
  289. streak++
  290. } else if (diff > streak) {
  291. break
  292. }
  293. }
  294. this.streakDays = streak
  295. },
  296. calcTodayCount: function() {
  297. var today = new Date()
  298. var todayStr = today.toISOString().split('T')[0]
  299. this.todayCount = this.checkins.filter(function(c) {
  300. if (!c.createdAt) return false
  301. return c.createdAt.indexOf(todayStr) === 0
  302. }).length
  303. },
  304. changeMonth: function(delta) {
  305. this.currentMonth += delta
  306. if (this.currentMonth > 12) { this.currentMonth = 1; this.currentYear++ }
  307. if (this.currentMonth < 1) { this.currentMonth = 12; this.currentYear-- }
  308. this.loadCalendarData()
  309. },
  310. onDayClick: function(day) {
  311. // 点击日期跳转到当天或者查看记录
  312. },
  313. toggleRecords: function() {
  314. this.showAllRecords = !this.showAllRecords
  315. },
  316. selectMood: function(value) {
  317. this.formMood = value
  318. },
  319. closeDialog: function() {
  320. this.showDialog = false
  321. },
  322. showCheckinDialog: function(type) {
  323. this.dialogType = type
  324. this.formAmount = ''
  325. this.formCategory = ''
  326. this.formNote = ''
  327. this.formMood = ''
  328. this.showDialog = true
  329. },
  330. onCategoryChange: function(e) {
  331. this.formCategory = this.categoryOptions[e.detail.value]
  332. },
  333. async doCheckin() {
  334. if (this.todayTimer) {
  335. uni.showToast({ title: '操作太频繁,请稍后', icon: 'none' })
  336. return
  337. }
  338. this.todayTimer = setTimeout(function() { this.todayTimer = null }.bind(this), 3000)
  339. var params = {
  340. checkinType: this.dialogType,
  341. checkinDate: new Date().toISOString().split('T')[0],
  342. earnedEnergy: 5
  343. }
  344. if (this.memberId) params.memberId = this.memberId
  345. if (this.formAmount) params.amount = parseInt(this.formAmount)
  346. if (this.formCategory) params.category = this.formCategory
  347. if (this.formNote) params.note = this.formNote
  348. if (this.formMood) params.mood = this.formMood
  349. try {
  350. var res = await createCheckin(params)
  351. if (res && res.code === 200) {
  352. this.showDialog = false
  353. this.earnedEnergy = 5
  354. this.showEnergyAnim = true
  355. setTimeout(function() { this.showEnergyAnim = false }.bind(this), 1500)
  356. uni.showToast({ title: '打卡成功!+5能量', icon: 'success' })
  357. this.loadData()
  358. this.loadCalendarData()
  359. } else {
  360. uni.showToast({ title: res && res.message || '打卡失败', icon: 'none' })
  361. }
  362. } catch (e) {
  363. uni.showToast({ title: '网络错误', icon: 'none' })
  364. }
  365. },
  366. confirmDelete: function(item) {
  367. var self = this
  368. uni.showModal({
  369. title: '提示',
  370. content: '确定删除此打卡记录?',
  371. success: function(res) {
  372. if (res.confirm) self.deleteRecord(item)
  373. }
  374. })
  375. },
  376. async deleteRecord(item) {
  377. try {
  378. var res = await deleteCheckin(item.id)
  379. if (res && res.code === 200) {
  380. uni.showToast({ title: '已删除', icon: 'success' })
  381. this.loadData()
  382. this.loadCalendarData()
  383. }
  384. } catch (e) {
  385. console.error('删除失败', e)
  386. }
  387. },
  388. getTypeIcon: function(type) {
  389. var map = { daily_plan: '📋', saving: '💰', expense: '🛒', review: '📊' }
  390. return map[type] || '📌'
  391. },
  392. getTypeLabel: function(type) {
  393. var map = { daily_plan: '记账规划', saving: '储蓄记录', expense: '消费记录', review: '财务回顾' }
  394. return map[type] || type
  395. },
  396. formatDate: function(dateStr) {
  397. if (!dateStr) return ''
  398. return dateStr.substring(0, 16).replace('T', ' ')
  399. },
  400. getEmptyKey: function(i) {
  401. return 'e' + i
  402. },
  403. getDayKey: function(i) {
  404. return 'd' + i
  405. }
  406. }
  407. }
  408. </script>
  409. <style>
  410. .container { min-height: 100vh; background: #f5f7fa; padding: 20rpx 30rpx 120rpx; width: 100%; box-sizing: border-box; overflow-x: hidden; }
  411. /* 能量提示 */
  412. .energy-tip {
  413. background: linear-gradient(135deg, #F59E0B, #F97316);
  414. border-radius: 20rpx; padding: 24rpx 30rpx; margin-bottom: 24rpx;
  415. }
  416. .energy-tip-text { color: #fff; font-size: 28rpx; font-weight: 600; }
  417. .energy-tip-sub { color: rgba(255,255,255,0.8); font-size: 22rpx; margin-top: 6rpx; }
  418. /* 日历 */
  419. .calendar-card {
  420. background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 24rpx;
  421. }
  422. .calendar-header {
  423. display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx;
  424. }
  425. .calendar-title { font-size: 32rpx; font-weight: 700; color: #333; }
  426. .nav-btn { font-size: 40rpx; color: #F97316; padding: 0 16rpx; }
  427. .calendar-weekdays {
  428. display: flex; flex-direction: row; margin-bottom: 12rpx;
  429. }
  430. .weekday { width: 14.28%; text-align: center; font-size: 24rpx; color: #999; }
  431. .calendar-days { display: flex; flex-direction: row; flex-wrap: wrap; }
  432. .day-cell {
  433. width: 14.28%; height: 72rpx; display: flex; flex-direction: column;
  434. align-items: center; justify-content: center; position: relative;
  435. }
  436. .day-cell.today .day-num {
  437. background: #F97316; color: #fff; border-radius: 50%;
  438. width: 48rpx; height: 48rpx; text-align: center; line-height: 48rpx;
  439. }
  440. .day-num { font-size: 26rpx; color: #333; }
  441. .day-dot { font-size: 16rpx; color: #F97316; position: absolute; bottom: 4rpx; }
  442. .day-cell.empty { height: 72rpx; }
  443. .calendar-stats {
  444. display: flex; justify-content: space-between; margin-top: 16rpx;
  445. padding-top: 16rpx; border-top: 2rpx solid #f5f5f5;
  446. font-size: 24rpx; color: #999;
  447. }
  448. .stat-highlight { color: #F97316; font-weight: 700; font-size: 28rpx; }
  449. /* 快速打卡 */
  450. .quick-checkin { background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 24rpx; }
  451. .section-title-row {
  452. display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx;
  453. }
  454. .section-title { font-size: 30rpx; font-weight: 700; color: #333; }
  455. .section-subtitle { font-size: 22rpx; color: #999; }
  456. .checkin-types { display: flex; flex-direction: row; justify-content: space-between; }
  457. .checkin-btn {
  458. display: flex; flex-direction: column; align-items: center; justify-content: center;
  459. width: 150rpx; height: 130rpx; background: #FFFBEB; border-radius: 16rpx;
  460. border: 2rpx solid #FDE68A;
  461. }
  462. .checkin-btn:active { background: #FEF3C7; }
  463. .checkin-btn.disabled { opacity: 0.5; }
  464. .checkin-icon { font-size: 44rpx; margin-bottom: 8rpx; }
  465. .checkin-label { font-size: 24rpx; color: #92400E; font-weight: 500; }
  466. /* 统计 */
  467. .stats-card {
  468. background: #fff; border-radius: 20rpx; padding: 24rpx;
  469. display: flex; flex-direction: row; justify-content: space-around;
  470. align-items: center; margin-bottom: 24rpx;
  471. }
  472. .stat-item { display: flex; flex-direction: column; align-items: center; }
  473. .stat-value { font-size: 38rpx; font-weight: 800; color: #F59E0B; }
  474. .stat-label { font-size: 22rpx; color: #999; margin-top: 4rpx; }
  475. .stat-divider { width: 2rpx; height: 60rpx; background: #eee; }
  476. /* 记录 */
  477. .record-section { background: #fff; border-radius: 20rpx; padding: 24rpx; }
  478. .empty-state { text-align: center; padding: 48rpx 0; }
  479. .empty-icon { font-size: 48rpx; }
  480. .empty-text { color: #999; font-size: 26rpx; margin-top: 12rpx; }
  481. .record-item {
  482. display: flex; flex-direction: row; justify-content: space-between;
  483. align-items: center; padding: 20rpx 0; border-bottom: 2rpx solid #f5f5f5;
  484. }
  485. .record-item:last-child { border-bottom: none; }
  486. .record-left { display: flex; flex-direction: row; align-items: center; flex: 1; }
  487. .record-icon { font-size: 36rpx; margin-right: 16rpx; }
  488. .record-info { display: flex; flex-direction: column; }
  489. .record-type { font-size: 28rpx; color: #333; font-weight: 500; }
  490. .record-date { font-size: 22rpx; color: #999; margin-top: 2rpx; }
  491. .record-note { font-size: 22rpx; color: #666; margin-top: 2rpx; max-width: 300rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  492. .record-right { display: flex; flex-direction: column; align-items: flex-end; }
  493. .record-energy { font-size: 28rpx; color: #F59E0B; font-weight: 700; }
  494. .record-mood { font-size: 28rpx; margin-top: 4rpx; }
  495. /* 打卡弹窗 */
  496. .dialog-mask {
  497. position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  498. background: rgba(0,0,0,0.5); z-index: 999; display: flex;
  499. align-items: center; justify-content: center;
  500. }
  501. .dialog-card {
  502. background: #fff; border-radius: 24rpx; width: 620rpx; padding: 32rpx;
  503. }
  504. .dialog-title { font-size: 34rpx; font-weight: 700; color: #333; text-align: center; margin-bottom: 28rpx; }
  505. .form-item { margin-bottom: 24rpx; }
  506. .form-label { font-size: 26rpx; color: #666; margin-bottom: 8rpx; display: block; }
  507. .form-input {
  508. border: 2rpx solid #E5E7EB; border-radius: 12rpx; padding: 28rpx 20rpx;
  509. font-size: 28rpx; width: 100%; box-sizing: border-box; min-height: 80rpx;
  510. }
  511. .form-picker {
  512. border: 2rpx solid #E5E7EB; border-radius: 12rpx; padding: 28rpx 20rpx;
  513. font-size: 26rpx; color: #333; min-height: 80rpx; display: flex; align-items: center;
  514. }
  515. .picker-text { color: #333; }
  516. .mood-picker { display: flex; flex-direction: row; gap: 16rpx; }
  517. .mood-item { font-size: 44rpx; padding: 8rpx; border-radius: 12rpx; border: 2rpx solid transparent; }
  518. .mood-item.selected { border-color: #F59E0B; background: #FFFBEB; }
  519. .dialog-actions { display: flex; flex-direction: row; gap: 20rpx; margin-top: 32rpx; }
  520. .dialog-btn { flex: 1; padding: 20rpx; border-radius: 12rpx; font-size: 28rpx; text-align: center; }
  521. .dialog-btn.cancel { background: #f5f5f5; color: #666; }
  522. .dialog-btn.confirm { background: linear-gradient(135deg, #F59E0B, #F97316); color: #fff; }
  523. /* 能量动画 */
  524. .energy-animation {
  525. position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  526. z-index: 1000; pointer-events: none;
  527. }
  528. .energy-anim-text {
  529. font-size: 80rpx; font-weight: 900; color: #F59E0B;
  530. text-shadow: 0 4rpx 20rpx rgba(245,158,11,0.5);
  531. animation: floatUp 1.5s ease-out forwards;
  532. }
  533. @keyframes floatUp {
  534. 0% { opacity: 1; transform: scale(0.5); }
  535. 50% { opacity: 1; transform: scale(1.2); }
  536. 100% { opacity: 0; transform: translateY(-200rpx) scale(1); }
  537. }
  538. </style>