profile.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. <template>
  2. <view class="container">
  3. <tab-transition v-if="showTabTransition" dimCode="wealth" ref="tabTransition" @close="showTabTransition = false" />
  4. <!-- 未登录 -->
  5. <view v-if="!isLoggedIn" class="login-prompt">
  6. <text class="prompt-icon">👤</text>
  7. <text class="prompt-title">登录浠艾福</text>
  8. <text class="prompt-desc">登录后查看个人资料、积分记录等</text>
  9. <button class="login-btn" @click="goLogin">登录 / 注册</button>
  10. </view>
  11. <!-- 已登录 -->
  12. <template v-else>
  13. <!-- 退出切换 -->
  14. <view class="exit-switch" v-if="isSwitchedChild" @click="onExitSwitch">
  15. <text>🔄 退出切换</text>
  16. </view>
  17. <!-- 用户身份卡片 -->
  18. <view class="user-card">
  19. <view class="user-card-left">
  20. <view class="avatar-wrap" @click="goToEditInfo">
  21. <image v-if="userInfo.avatar" class="avatar-img" :src="userInfo.avatar" mode="aspectFill" />
  22. <text v-else class="avatar-text">{{ nickname.charAt(0) || '👤' }}</text>
  23. </view>
  24. <view class="user-meta">
  25. <view class="user-name-row">
  26. <text class="nickname">{{ nickname || '未设置昵称' }}</text>
  27. <text class="role-tag">{{ roleLabel }}</text>
  28. </view>
  29. <view class="user-sub" v-if="memberLevel">
  30. <text class="level-badge" :class="'level-' + memberLevel">{{ memberLevelText }}</text>
  31. <text v-if="membershipDays > 0 && membershipDays <= 30" class="days-left">剩余 {{ membershipDays }} 天</text>
  32. </view>
  33. <view class="user-sub" v-else>
  34. <text class="days-left">开通会员解锁更多权益</text>
  35. </view>
  36. </view>
  37. </view>
  38. <view class="user-card-right">
  39. <view class="points-box" @click="goToPoints">
  40. <text class="points-num">{{ totalPoints }}</text>
  41. <text class="points-label">积分</text>
  42. </view>
  43. <button v-if="!isSwitchedChild" class="btn-switch" @click="onSwitchMode">切换</button>
  44. </view>
  45. </view>
  46. <!-- 核心数据概览 -->
  47. <view class="stats-bar">
  48. <view class="stat-item" @click="goToTasks">
  49. <text class="stat-num">{{ pendingTasks }}</text>
  50. <text class="stat-label">待完成任务</text>
  51. </view>
  52. <view class="stat-divider"></view>
  53. <view class="stat-item" @click="goToCheckin">
  54. <text class="stat-num">{{ streakDays }}</text>
  55. <text class="stat-label">连续打卡</text>
  56. </view>
  57. <view class="stat-divider"></view>
  58. <view class="stat-item" @click="goToFamily">
  59. <text class="stat-num">{{ familyCount }}</text>
  60. <text class="stat-label">家庭成员</text>
  61. </view>
  62. <view class="stat-divider"></view>
  63. <view class="stat-item" @click="goToChallenges">
  64. <text class="stat-num">{{ activeChallenges }}</text>
  65. <text class="stat-label">进行中挑战</text>
  66. </view>
  67. </view>
  68. <!-- 五维能量 -->
  69. <view class="energy-section" v-if="sandboxData">
  70. <view class="section-header">
  71. <text class="section-title">🌟 五维能量</text>
  72. <text class="section-more" @click="goToEnergySandbox">详情 ›</text>
  73. </view>
  74. <view class="energy-row">
  75. <view class="energy-item" v-for="dim in energyDims" :key="dim.code">
  76. <view class="energy-circle" :style="{ background: dim.color }">
  77. <text class="energy-val">{{ dim.score || 0 }}</text>
  78. </view>
  79. <text class="energy-label">{{ dim.label }}</text>
  80. </view>
  81. </view>
  82. </view>
  83. <!-- 待办提醒 -->
  84. <view class="reminders-section" v-if="hasReminders">
  85. <view class="section-header">
  86. <text class="section-title">📌 待办提醒</text>
  87. </view>
  88. <view class="reminder-item" v-if="pendingTasks > 0" @click="goToTasks">
  89. <text class="reminder-icon">📋</text>
  90. <text class="reminder-text">有 {{ pendingTasks }} 个任务待完成</text>
  91. <text class="reminder-arrow">›</text>
  92. </view>
  93. <view class="reminder-item" v-if="activeChallenges > 0" @click="goToChallenges">
  94. <text class="reminder-icon">🔥</text>
  95. <text class="reminder-text">{{ activeChallenges }} 个挑战进行中</text>
  96. <text class="reminder-arrow">›</text>
  97. </view>
  98. <view class="reminder-item" v-if="profileProgress < 14" @click="goToEditInfo">
  99. <text class="reminder-icon">📝</text>
  100. <text class="reminder-text">个人信息待完善({{ profileProgress }}/14)</text>
  101. <text class="reminder-arrow">›</text>
  102. </view>
  103. </view>
  104. <!-- 功能菜单 -->
  105. <view class="menu-section">
  106. <!-- 家庭 -->
  107. <view class="menu-group">
  108. <view class="menu-group-title">家庭</view>
  109. <view class="menu-item" @click="goToFamily">
  110. <text class="menu-icon">👨‍👩‍👧‍👦</text>
  111. <text class="menu-text">家庭成员</text>
  112. <text class="arrow">›</text>
  113. </view>
  114. <view class="menu-item" @click="goToChildren">
  115. <text class="menu-icon">👶</text>
  116. <text class="menu-text">孩子管理</text>
  117. <text class="arrow">›</text>
  118. </view>
  119. <view class="menu-item" v-if="role === 'parent'" @click="goToAppointments">
  120. <text class="menu-icon">📅</text>
  121. <text class="menu-text">测评预约</text>
  122. <text class="arrow">›</text>
  123. </view>
  124. <view class="menu-item" @click="goToChallenges">
  125. <text class="menu-icon">🔥</text>
  126. <text class="menu-text">家庭挑战</text>
  127. <text class="arrow">›</text>
  128. </view>
  129. </view>
  130. <!-- 成长 -->
  131. <view class="menu-group">
  132. <view class="menu-group-title">成长</view>
  133. <view class="menu-item" @click="goToTasks">
  134. <text class="menu-icon">📋</text>
  135. <text class="menu-text">每日任务</text>
  136. <text class="arrow">›</text>
  137. </view>
  138. <view class="menu-item" @click="goToGrowth">
  139. <text class="menu-icon">📈</text>
  140. <text class="menu-text">成长档案</text>
  141. <text class="arrow">›</text>
  142. </view>
  143. <view class="menu-item" @click="goToReport">
  144. <text class="menu-icon">📊</text>
  145. <text class="menu-text">健康报告</text>
  146. <text class="arrow">›</text>
  147. </view>
  148. </view>
  149. <!-- 商城 -->
  150. <view class="menu-group" v-if="showServices">
  151. <view class="menu-group-title">商城</view>
  152. <view class="menu-item" @click="goToMembership">
  153. <text class="menu-icon">👑</text>
  154. <text class="menu-text">会员中心</text>
  155. <text class="arrow">›</text>
  156. </view>
  157. <view class="menu-item" @click="goToOrders">
  158. <text class="menu-icon">🛒</text>
  159. <text class="menu-text">我的订单</text>
  160. <text class="arrow">›</text>
  161. </view>
  162. <view class="menu-item" @click="goToCoupons">
  163. <text class="menu-icon">🎫</text>
  164. <text class="menu-text">优惠券</text>
  165. <text class="arrow">›</text>
  166. </view>
  167. </view>
  168. <!-- 更多 -->
  169. <view class="menu-group">
  170. <view class="menu-group-title">更多</view>
  171. <view class="menu-item" v-if="role === 'parent'" @click="showInviteActionSheet">
  172. <text class="menu-icon">📨</text>
  173. <text class="menu-text">邀请加入</text>
  174. <text class="arrow">›</text>
  175. </view>
  176. <view class="menu-item" @click="goToSettings">
  177. <text class="menu-icon">⚙️</text>
  178. <text class="menu-text">设置</text>
  179. <text class="arrow">›</text>
  180. </view>
  181. <view class="menu-item" @click="logout">
  182. <text class="menu-icon">🚪</text>
  183. <text class="menu-text">退出登录</text>
  184. <text class="arrow">›</text>
  185. </view>
  186. </view>
  187. </view>
  188. </template>
  189. <AIFloatingAvatar />
  190. </view>
  191. </template>
  192. <script>
  193. import TabTransition from '../../components/tab-transition.vue'
  194. import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
  195. import {
  196. getChildren, getVisibleFamilyMembers, getFamilyEnergySandbox,
  197. getTodayTasksByCategory, getChallengeList, getMyMembership,
  198. verifyPassword, getPointsBalance, getUserInfo
  199. } from '../../utils/api.js'
  200. export default {
  201. components: { TabTransition, AIFloatingAvatar },
  202. data() {
  203. return {
  204. showTabTransition: true,
  205. nickname: '',
  206. role: 'parent',
  207. isSwitchedChild: false,
  208. userInfo: {},
  209. memberLevel: null,
  210. membershipDays: 0,
  211. totalPoints: 0,
  212. pendingTasks: 0,
  213. streakDays: 0,
  214. familyCount: 0,
  215. activeChallenges: 0,
  216. profileProgress: 0,
  217. sandboxData: null,
  218. energyDims: []
  219. }
  220. },
  221. computed: {
  222. isLoggedIn() {
  223. return !!uni.getStorageSync('token')
  224. },
  225. roleLabel() {
  226. var map = { parent: '家长', child: '孩子', teacher: '规划师' }
  227. return map[this.role] || '用户'
  228. },
  229. memberLevelText() {
  230. var map = { GOLD: '金卡', PLATINUM: '白金', DIAMOND: '钻石' }
  231. return map[this.memberLevel] || this.memberLevel || ''
  232. },
  233. hasReminders() {
  234. return this.pendingTasks > 0 || this.activeChallenges > 0 || this.profileProgress < 14
  235. }
  236. },
  237. onShow() {
  238. this.showTabTransition = true
  239. if (!this.isLoggedIn) {
  240. this._watchPageReady()
  241. return
  242. }
  243. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
  244. if (role === 'teacher') {
  245. uni.redirectTo({ url: '/pages/teacher/teacher-profile' })
  246. return
  247. }
  248. this.role = role
  249. this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
  250. this.nickname = (uni.getStorageSync('userInfo') || {}).nickname || ''
  251. this.loadAllData()
  252. this._watchPageReady()
  253. },
  254. methods: {
  255. _watchPageReady() {
  256. var self = this
  257. this.$nextTick(function() {
  258. if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
  259. })
  260. },
  261. loadAllData() {
  262. this.loadUserInfo()
  263. this.loadMembership()
  264. this.loadPoints()
  265. this.loadFamily()
  266. this.loadSandbox()
  267. this.loadTasks()
  268. this.loadChallenges()
  269. },
  270. loadUserInfo() {
  271. var self = this
  272. getUserInfo().then(function(res) {
  273. if (res && res.code === 200 && res.data) {
  274. self.userInfo = res.data
  275. if (!self.nickname) self.nickname = res.data.nickname || ''
  276. }
  277. }).catch(function() {})
  278. },
  279. loadMembership() {
  280. var self = this
  281. getMyMembership().then(function(res) {
  282. if (res && res.data) {
  283. self.memberLevel = res.data.memberLevel || res.data.levelCode || null
  284. self.membershipDays = res.data.membershipDays || res.data.daysRemaining || 0
  285. }
  286. }).catch(function() {})
  287. },
  288. loadPoints() {
  289. var self = this
  290. var memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('userId')
  291. if (!memberId) return
  292. getPointsBalance(memberId).then(function(res) {
  293. if (res && res.data) {
  294. self.totalPoints = res.data.totalPoints || res.data.points || 0
  295. }
  296. }).catch(function() {})
  297. },
  298. loadFamily() {
  299. var self = this
  300. var childrenRes = getChildren()
  301. var membersRes = getVisibleFamilyMembers()
  302. Promise.all([childrenRes, membersRes]).then(function(results) {
  303. var children = results[0].data || []
  304. var members = results[1].data || []
  305. self.familyCount = members.length || children.length
  306. self.streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
  307. }).catch(function() {
  308. self.familyCount = 0
  309. })
  310. },
  311. loadSandbox() {
  312. var self = this
  313. getFamilyEnergySandbox().then(function(res) {
  314. if (res && res.data) {
  315. self.sandboxData = res.data
  316. var dims = res.data.dimensions || []
  317. self.energyDims = dims.map(function(d) {
  318. return {
  319. code: d.code,
  320. label: d.label || d.code,
  321. score: d.score || 0,
  322. color: self.getDimColor(d.code)
  323. }
  324. })
  325. }
  326. }).catch(function() {})
  327. },
  328. loadTasks() {
  329. var self = this
  330. var memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('userId')
  331. if (!memberId) return
  332. if (self.role === 'child' && memberId) {
  333. getTodayTasksByCategory(memberId, 'action').then(function(res) {
  334. if (res && res.data) {
  335. self.pendingTasks = Array.isArray(res.data) ? res.data.length : 0
  336. }
  337. }).catch(function() {})
  338. }
  339. self.pendingTasks = parseInt(uni.getStorageSync('pendingTaskCount') || 0)
  340. },
  341. loadChallenges() {
  342. var self = this
  343. getChallengeList({}).then(function(res) {
  344. if (res && res.data) {
  345. self.activeChallenges = Array.isArray(res.data) ? res.data.length : 0
  346. }
  347. }).catch(function() {})
  348. },
  349. getDimColor: function(code) {
  350. var map = {
  351. body: '#FF8C42', mind: '#FF6B9D', wisdom: '#6366F1',
  352. action: '#10B981', wealth: '#F59E0B'
  353. }
  354. return map[code] || '#999'
  355. },
  356. goLogin: function() {
  357. uni.navigateTo({ url: '/pages/login/login' })
  358. },
  359. goToEditInfo: function() {
  360. uni.navigateTo({ url: '/pages/user-edit/user-edit' })
  361. },
  362. goToPoints: function() {
  363. uni.navigateTo({ url: '/pages/points/points' })
  364. },
  365. goToTasks: function() {
  366. uni.navigateTo({ url: '/pages/tasks/tasks' })
  367. },
  368. goToCheckin: function() {
  369. uni.navigateTo({ url: '/pages/health/daily-checkin' })
  370. },
  371. goToFamily: function() {
  372. uni.navigateTo({ url: '/pages/profile-extra/family-members' })
  373. },
  374. goToChildren: function() {
  375. uni.navigateTo({ url: '/pages/profile-extra/children' })
  376. },
  377. goToAppointments: function() {
  378. uni.navigateTo({ url: '/pages/parent/appointments' })
  379. },
  380. goToChallenges: function() {
  381. uni.navigateTo({ url: '/pages/health/challenge-manage' })
  382. },
  383. goToGrowth: function() {
  384. uni.navigateTo({ url: '/pages/growth-main/index' })
  385. },
  386. goToReport: function() {
  387. uni.navigateTo({ url: '/pages/profile/report-management' })
  388. },
  389. goToMembership: function() {
  390. uni.navigateTo({ url: '/pages/membership/index' })
  391. },
  392. goToOrders: function() {
  393. uni.navigateTo({ url: '/pages/shop/order-list/order-list' })
  394. },
  395. goToCoupons: function() {
  396. uni.navigateTo({ url: '/pages/profile-extra/coupons' })
  397. },
  398. goToEnergySandbox: function() {
  399. uni.switchTab({ url: '/pages/index-home/index' })
  400. },
  401. goToSettings: function() {
  402. uni.showActionSheet({
  403. itemList: ['设置密码', '隐私设置', '关于浠艾福'],
  404. success: function(res) {
  405. if (res.tapIndex === 0) {
  406. uni.navigateTo({ url: '/pages/profile/settings' })
  407. }
  408. }
  409. })
  410. },
  411. onSwitchMode: function() {
  412. var self = this
  413. if (self.role === 'child') {
  414. uni.showModal({
  415. title: '验证密码',
  416. content: '请输入家长密码',
  417. editable: true,
  418. success: function(res) {
  419. if (res.confirm && res.content) {
  420. verifyPassword(res.content).then(function(result) {
  421. if (result.code === 200) {
  422. self.doSwitchMode()
  423. } else {
  424. uni.showToast({ title: '密码错误', icon: 'none' })
  425. }
  426. }).catch(function() {
  427. uni.showToast({ title: '验证失败', icon: 'none' })
  428. })
  429. }
  430. }
  431. })
  432. } else {
  433. this.doSwitchMode()
  434. }
  435. },
  436. doSwitchMode: function() {
  437. var self = this
  438. getChildren().then(function(res) {
  439. var children = res.data || []
  440. if (children.length === 0) {
  441. uni.showModal({
  442. title: '提示',
  443. content: '您还没有添加孩子,请先在家庭成员中添加',
  444. success: function(r) { if (r.confirm) uni.navigateTo({ url: '/pages/profile-extra/family-members' }) }
  445. })
  446. return
  447. }
  448. if (children.length === 1) {
  449. self.$store.commit('switchToChild', children[0].id)
  450. self.role = 'child'
  451. self.isSwitchedChild = true
  452. uni.showToast({ title: '已切换为孩子模式', icon: 'success' })
  453. return
  454. }
  455. uni.showActionSheet({
  456. itemList: children.map(function(c) { return c.nickname }),
  457. success: function(res) {
  458. var sel = children[res.tapIndex]
  459. self.$store.commit('switchToChild', sel.id)
  460. self.role = 'child'
  461. self.isSwitchedChild = true
  462. uni.showToast({ title: '已切换为' + sel.nickname + '模式', icon: 'success' })
  463. }
  464. })
  465. }).catch(function() {})
  466. },
  467. onExitSwitch: function() {
  468. var self = this
  469. uni.showModal({
  470. title: '退出切换',
  471. content: '请输入家长密码以确认退出',
  472. editable: true,
  473. success: function(res) {
  474. if (res.confirm && res.content) {
  475. verifyPassword(res.content).then(function(result) {
  476. if (result.code === 200) {
  477. self.$store.commit('switchBackToParent')
  478. self.isSwitchedChild = false
  479. self.role = 'parent'
  480. uni.showToast({ title: '已退出切换', icon: 'success' })
  481. uni.reLaunch({ url: '/pages/index-home/index' })
  482. } else {
  483. uni.showToast({ title: '密码错误', icon: 'none' })
  484. }
  485. }).catch(function() {
  486. uni.showToast({ title: '验证失败', icon: 'none' })
  487. })
  488. }
  489. }
  490. })
  491. },
  492. showInviteActionSheet: function() {
  493. var self = this
  494. var ui = uni.getStorageSync('userInfo') || {}
  495. var items = ['👨‍👩‍👧‍👦 邀请家人', '👨‍🏫 邀请成长规划师']
  496. uni.showActionSheet({
  497. itemList: items,
  498. success: function(res) {
  499. var type = res.tapIndex === 0 ? 'family' : 'guide'
  500. var title = (ui.nickname || '家人') + ' 邀请你加入家庭'
  501. var path = type === 'guide'
  502. ? '/pages/login/login?invite_type=guide'
  503. : '/pages/login/login?invite_type=family'
  504. self.$emit('invite-generate', { title: title, path: path, imageUrl: '/static/invite-card.png' })
  505. uni.showToast({ title: '点击右上角转发', icon: 'none' })
  506. }
  507. })
  508. },
  509. logout: function() {
  510. uni.showModal({
  511. title: '确认退出',
  512. content: '确定要退出登录吗?',
  513. success: function(res) {
  514. if (res.confirm) {
  515. this.$store.commit('logout')
  516. uni.reLaunch({ url: '/pages/index-home/index' })
  517. }
  518. }.bind(this)
  519. })
  520. }
  521. }
  522. }
  523. </script>
  524. <style scoped>
  525. .container {
  526. min-height: 100vh;
  527. background: #f5f7fa;
  528. padding-bottom: 120rpx;
  529. }
  530. /* 登录 */
  531. .login-prompt {
  532. display: flex;
  533. flex-direction: column;
  534. align-items: center;
  535. padding: 200rpx 60rpx;
  536. }
  537. .prompt-icon { font-size: 100rpx; margin-bottom: 30rpx; }
  538. .prompt-title { font-size: 40rpx; font-weight: bold; color: #333; margin-bottom: 16rpx; }
  539. .prompt-desc { font-size: 26rpx; color: #999; margin-bottom: 60rpx; }
  540. .login-btn {
  541. background: linear-gradient(135deg, #667eea, #764ba2);
  542. color: #fff;
  543. border-radius: 50rpx;
  544. font-size: 30rpx;
  545. padding: 24rpx 80rpx;
  546. }
  547. /* 退出切换 */
  548. .exit-switch {
  549. margin: 0 30rpx 16rpx;
  550. background: linear-gradient(135deg, #667eea, #764ba2);
  551. color: #fff;
  552. padding: 16rpx;
  553. border-radius: 12rpx;
  554. text-align: center;
  555. font-size: 26rpx;
  556. }
  557. /* 用户卡片 */
  558. .user-card {
  559. margin: 30rpx;
  560. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  561. border-radius: 24rpx;
  562. padding: 36rpx 32rpx;
  563. display: flex;
  564. justify-content: space-between;
  565. align-items: center;
  566. box-shadow: 0 8rpx 32rpx rgba(102,126,234,0.3);
  567. }
  568. .user-card-left {
  569. display: flex;
  570. align-items: center;
  571. flex: 1;
  572. }
  573. .avatar-wrap {
  574. width: 110rpx;
  575. height: 110rpx;
  576. border-radius: 50%;
  577. background: rgba(255,255,255,0.2);
  578. display: flex;
  579. align-items: center;
  580. justify-content: center;
  581. margin-right: 24rpx;
  582. overflow: hidden;
  583. border: 4rpx solid rgba(255,255,255,0.3);
  584. }
  585. .avatar-img {
  586. width: 110rpx;
  587. height: 110rpx;
  588. border-radius: 50%;
  589. }
  590. .avatar-text {
  591. font-size: 48rpx;
  592. color: #fff;
  593. }
  594. .user-meta { flex: 1; }
  595. .user-name-row {
  596. display: flex;
  597. align-items: center;
  598. gap: 12rpx;
  599. margin-bottom: 8rpx;
  600. }
  601. .nickname {
  602. font-size: 36rpx;
  603. font-weight: bold;
  604. color: #fff;
  605. }
  606. .role-tag {
  607. font-size: 22rpx;
  608. background: rgba(255,255,255,0.2);
  609. color: #fff;
  610. padding: 4rpx 16rpx;
  611. border-radius: 20rpx;
  612. }
  613. .user-sub {
  614. display: flex;
  615. align-items: center;
  616. gap: 12rpx;
  617. }
  618. .level-badge {
  619. font-size: 22rpx;
  620. padding: 4rpx 16rpx;
  621. border-radius: 20rpx;
  622. font-weight: 600;
  623. }
  624. .level-GOLD { background: #F59E0B; color: #fff; }
  625. .level-PLATINUM { background: #6366F1; color: #fff; }
  626. .level-DIAMOND { background: #EC4899; color: #fff; }
  627. .days-left {
  628. font-size: 24rpx;
  629. color: rgba(255,255,255,0.8);
  630. }
  631. .user-card-right {
  632. display: flex;
  633. flex-direction: column;
  634. align-items: center;
  635. gap: 16rpx;
  636. }
  637. .points-box {
  638. text-align: center;
  639. background: rgba(255,255,255,0.15);
  640. padding: 16rpx 24rpx;
  641. border-radius: 16rpx;
  642. }
  643. .points-num {
  644. font-size: 44rpx;
  645. font-weight: bold;
  646. color: #fff;
  647. display: block;
  648. }
  649. .points-label {
  650. font-size: 22rpx;
  651. color: rgba(255,255,255,0.7);
  652. }
  653. .btn-switch {
  654. background: rgba(255,255,255,0.2);
  655. color: #fff;
  656. font-size: 24rpx;
  657. padding: 10rpx 32rpx;
  658. border-radius: 30rpx;
  659. border: none;
  660. }
  661. /* 数据概览 */
  662. .stats-bar {
  663. margin: 0 30rpx 20rpx;
  664. background: #fff;
  665. border-radius: 20rpx;
  666. display: flex;
  667. align-items: center;
  668. padding: 28rpx 0;
  669. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  670. }
  671. .stat-item {
  672. flex: 1;
  673. display: flex;
  674. flex-direction: column;
  675. align-items: center;
  676. gap: 8rpx;
  677. }
  678. .stat-num {
  679. font-size: 40rpx;
  680. font-weight: bold;
  681. color: #667eea;
  682. }
  683. .stat-label {
  684. font-size: 24rpx;
  685. color: #999;
  686. }
  687. .stat-divider {
  688. width: 1rpx;
  689. height: 60rpx;
  690. background: #f0f0f0;
  691. }
  692. /* 五维能量 */
  693. .energy-section {
  694. margin: 0 30rpx 20rpx;
  695. background: #fff;
  696. border-radius: 20rpx;
  697. padding: 28rpx;
  698. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  699. }
  700. .section-header {
  701. display: flex;
  702. justify-content: space-between;
  703. align-items: center;
  704. margin-bottom: 20rpx;
  705. }
  706. .section-title {
  707. font-size: 30rpx;
  708. font-weight: bold;
  709. color: #333;
  710. }
  711. .section-more {
  712. font-size: 24rpx;
  713. color: #999;
  714. }
  715. .energy-row {
  716. display: flex;
  717. justify-content: space-around;
  718. }
  719. .energy-item {
  720. display: flex;
  721. flex-direction: column;
  722. align-items: center;
  723. gap: 10rpx;
  724. }
  725. .energy-circle {
  726. width: 88rpx;
  727. height: 88rpx;
  728. border-radius: 50%;
  729. display: flex;
  730. align-items: center;
  731. justify-content: center;
  732. box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.15);
  733. }
  734. .energy-val {
  735. font-size: 28rpx;
  736. font-weight: bold;
  737. color: #fff;
  738. }
  739. .energy-label {
  740. font-size: 24rpx;
  741. color: #666;
  742. }
  743. /* 待办提醒 */
  744. .reminders-section {
  745. margin: 0 30rpx 20rpx;
  746. }
  747. .reminder-item {
  748. background: #fff;
  749. border-radius: 16rpx;
  750. padding: 24rpx 28rpx;
  751. margin-bottom: 12rpx;
  752. display: flex;
  753. align-items: center;
  754. gap: 16rpx;
  755. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  756. }
  757. .reminder-icon { font-size: 36rpx; }
  758. .reminder-text { flex: 1; font-size: 28rpx; color: #333; }
  759. .reminder-arrow { font-size: 32rpx; color: #ccc; }
  760. /* 菜单 */
  761. .menu-section {
  762. margin: 0 30rpx;
  763. }
  764. .menu-group {
  765. background: #fff;
  766. border-radius: 20rpx;
  767. margin-bottom: 20rpx;
  768. overflow: hidden;
  769. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  770. }
  771. .menu-group-title {
  772. padding: 16rpx 28rpx 8rpx;
  773. font-size: 22rpx;
  774. color: #999;
  775. }
  776. .menu-item {
  777. display: flex;
  778. align-items: center;
  779. padding: 26rpx 28rpx;
  780. border-bottom: 1rpx solid #f5f5f5;
  781. }
  782. .menu-item:last-child { border-bottom: none; }
  783. .menu-icon { font-size: 36rpx; margin-right: 16rpx; }
  784. .menu-text { flex: 1; font-size: 28rpx; color: #333; }
  785. .arrow { font-size: 32rpx; color: #ccc; }
  786. </style>