profile.vue 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504
  1. <template>
  2. <view class="container">
  3. <!-- 未登录 -->
  4. <view v-if="!isLoggedIn">
  5. <view 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. <text class="skip-link" @click="goSkip">暂不登录,先逛逛 ›</text>
  11. </view>
  12. <view class="philosophy-card">
  13. <text class="philosophy-title">照顾家庭,从照顾好自己开始</text>
  14. <text class="philosophy-desc">你的状态,就是家的状态。先安顿好自己,才有余力温暖家人。</text>
  15. </view>
  16. </view>
  17. <!-- 已登录 -->
  18. <template v-else>
  19. <!-- 退出切换 -->
  20. <view class="exit-switch" v-if="isSwitchedChild" @click="onExitSwitch">
  21. <text>🔄 退出切换</text>
  22. </view>
  23. <!-- 身份卡 -->
  24. <view class="user-card">
  25. <view class="user-card-left">
  26. <view class="avatar-wrap" @click="goToEditInfo">
  27. <image v-if="userInfo.avatar" class="avatar-img" :src="userInfo.avatar" mode="aspectFill" />
  28. <text v-else class="avatar-text">{{ nickname.charAt(0) || '👤' }}</text>
  29. </view>
  30. <view class="user-meta">
  31. <view class="user-name-row">
  32. <text class="nickname">{{ nickname || '未设置昵称' }}</text>
  33. <text v-if="roleLabel" class="role-tag">{{ roleLabel }}</text>
  34. </view>
  35. <view class="user-sub" v-if="memberLevel">
  36. <view class="level-badge" :class="'level-' + memberLevel">{{ memberLevelText }}</view>
  37. <text v-if="isLifetime" class="days-left">永久有效</text>
  38. <text v-else-if="membershipDays > 0 && membershipDays <= 30" class="days-left">剩余 {{ membershipDays }} 天</text>
  39. <view v-if="hasUpgradeSpace" class="upgrade-btn" @click="goUpgrade">
  40. <text class="upgrade-btn-text">{{ upgradeBtnText }}</text>
  41. </view>
  42. </view>
  43. <view class="user-sub" v-else>
  44. <text class="days-left">开通会员解锁更多权益</text>
  45. <view class="upgrade-btn" @click="goUpgrade">
  46. <text class="upgrade-btn-text">立即开通</text>
  47. </view>
  48. </view>
  49. </view>
  50. </view>
  51. <view class="user-card-right" @click="goToPoints">
  52. <text class="points-num">{{ totalPoints }}</text>
  53. <text class="points-label">积分</text>
  54. </view>
  55. </view>
  56. <!-- 我的分享海报浮动按钮(纯 CSS 二维码图标,避免 emoji 渲染成计算器) -->
  57. <view class="share-qr-float" @click="showShareQr">
  58. <view class="qr-glyph">
  59. <view class="qr-eye qr-eye-tl"></view>
  60. <view class="qr-eye qr-eye-tr"></view>
  61. <view class="qr-eye qr-eye-bl"></view>
  62. <view class="qr-mid"></view>
  63. <view class="qr-cluster">
  64. <view class="qr-pip"></view>
  65. <view class="qr-pip"></view>
  66. <view class="qr-pip"></view>
  67. <view class="qr-pip"></view>
  68. </view>
  69. </view>
  70. </view>
  71. <!-- 家族成员速览 -->
  72. <view class="family-strip" v-if="familyMembers.length > 0">
  73. <view class="family-strip-header">
  74. <text class="family-strip-title">我的家庭</text>
  75. <text class="family-strip-count">{{ familyMembers.length }} 位成员</text>
  76. </view>
  77. <view class="family-avatars">
  78. <view
  79. v-for="member in familyMembers"
  80. :key="member.id"
  81. class="family-avatar-item"
  82. @click="goMemberDetail(member)"
  83. >
  84. <view class="family-avatar-wrap">
  85. <image v-if="member.avatar" class="family-avatar-img" :src="member.avatar" mode="aspectFill" />
  86. <text v-else class="family-avatar-text">{{ member.nickname ? member.nickname.charAt(0) : '?' }}</text>
  87. <view class="family-avatar-tag" :class="'tag-' + (member.effectiveRole || member.source || 'member')">{{ member.roleLabel || memberRoleLabel(member.effectiveRole) }}</view>
  88. </view>
  89. <text class="family-avatar-name">{{ member.nickname || member.realName || '成员' }}</text>
  90. </view>
  91. <view class="family-avatar-item" @click="goToFamily">
  92. <view class="family-avatar-wrap family-avatar-add">
  93. <text class="family-avatar-plus">+</text>
  94. </view>
  95. <text class="family-avatar-name">添加</text>
  96. </view>
  97. </view>
  98. </view>
  99. <!-- 用户画像 - 五维能量概览 -->
  100. <view class="portrait-section" v-if="portrait">
  101. <view class="section-header-row">
  102. <text class="section-title">🧭 用户画像</text>
  103. <text class="section-sub">{{ portrait.tierLabel }}</text>
  104. </view>
  105. <view class="portrait-summary">
  106. <text class="portrait-desc">{{ portrait.summary }}</text>
  107. </view>
  108. <view class="portrait-dimensions">
  109. <view class="portrait-dim-item" v-for="dim in portrait.dims" :key="dim.code">
  110. <view class="portrait-dim-header">
  111. <view class="portrait-dim-icon" :style="{ background: dim.color }">{{ dim.icon }}</view>
  112. <text class="portrait-dim-name">{{ dim.name }}</text>
  113. <text class="portrait-dim-score">{{ dim.score }}</text>
  114. </view>
  115. <view class="portrait-dim-bar-track">
  116. <view class="portrait-dim-bar-fill" :style="{ width: dim.score + '%', background: dim.color }"></view>
  117. </view>
  118. <text class="portrait-dim-desc">{{ dim.desc }}</text>
  119. </view>
  120. </view>
  121. <view class="portrait-tip" v-if="portrait.tip">
  122. <text class="portrait-tip-text">{{ portrait.tip }}</text>
  123. </view>
  124. </view>
  125. <!-- 个人成就统计 -->
  126. <view class="achievement-section">
  127. <view class="section-header-row">
  128. <text class="section-title">🏆 我的成就</text>
  129. <text class="section-sub">持续成长,看见改变</text>
  130. </view>
  131. <view class="achievement-grid">
  132. <view class="ach-item" @click="goToPoints">
  133. <view class="ach-icon-wrap" style="background:rgba(249,115,22,0.12)">
  134. <text class="ach-icon">💰</text>
  135. </view>
  136. <text class="ach-num">{{ totalPoints }}</text>
  137. <text class="ach-label">累计积分</text>
  138. </view>
  139. <view class="ach-item" @click="goToStreak">
  140. <view class="ach-icon-wrap" style="background:rgba(255,140,66,0.12)">
  141. <text class="ach-icon">🔥</text>
  142. </view>
  143. <text class="ach-num">{{ streakDays }}</text>
  144. <text class="ach-label">连续打卡</text>
  145. </view>
  146. <view class="ach-item" @click="goToTasks">
  147. <view class="ach-icon-wrap" style="background:rgba(99,102,241,0.12)">
  148. <text class="ach-icon">✅</text>
  149. </view>
  150. <text class="ach-num">{{ completedTasks }}</text>
  151. <text class="ach-label">完成任务</text>
  152. </view>
  153. <view class="ach-item" @click="goToWishes">
  154. <view class="ach-icon-wrap" style="background:rgba(245,158,11,0.12)">
  155. <text class="ach-icon">🎁</text>
  156. </view>
  157. <text class="ach-num">{{ rewardsEarned }}</text>
  158. <text class="ach-label">获得奖励</text>
  159. </view>
  160. <view class="ach-item" @click="goToBadges">
  161. <view class="ach-icon-wrap" style="background:rgba(236,72,153,0.12)">
  162. <text class="ach-icon">🏅</text>
  163. </view>
  164. <text class="ach-num">{{ badgesCollected }}</text>
  165. <text class="ach-label">收集勋章</text>
  166. </view>
  167. <view class="ach-item" @click="goToGrowth">
  168. <view class="ach-icon-wrap" style="background:rgba(16,185,129,0.12)">
  169. <text class="ach-icon">📈</text>
  170. </view>
  171. <text class="ach-num">{{ growthRecords }}</text>
  172. <text class="ach-label">成长记录</text>
  173. </view>
  174. <view class="ach-item" @click="goToChallenges">
  175. <view class="ach-icon-wrap" style="background:rgba(255,107,157,0.12)">
  176. <text class="ach-icon">🎯</text>
  177. </view>
  178. <text class="ach-num">{{ challengesJoined }}</text>
  179. <text class="ach-label">参加挑战</text>
  180. </view>
  181. <view class="ach-item" @click="goToHealthReports">
  182. <view class="ach-icon-wrap" style="background:rgba(14,165,233,0.12)">
  183. <text class="ach-icon">📋</text>
  184. </view>
  185. <text class="ach-num">{{ healthReports }}</text>
  186. <text class="ach-label">上传报告</text>
  187. </view>
  188. <view class="ach-item" @click="goToWealthCenter">
  189. <view class="ach-icon-wrap" style="background:rgba(245,158,11,0.12)">
  190. <text class="ach-icon">💰</text>
  191. </view>
  192. <text class="ach-num">{{ referralEarnings }}</text>
  193. <text class="ach-label">推广收益</text>
  194. </view>
  195. <view class="ach-item" @click="goTeamTree">
  196. <view class="ach-icon-wrap" style="background:rgba(16,185,129,0.12)">
  197. <text class="ach-icon">🌳</text>
  198. </view>
  199. <text class="ach-num">{{ teamMembers }}</text>
  200. <text class="ach-label">我的团队</text>
  201. </view>
  202. </view>
  203. </view>
  204. <!-- 我的订单(电商风格) -->
  205. <view class="order-section">
  206. <view class="order-section-header">
  207. <text class="section-title">🛒 我的订单</text>
  208. <text class="order-all-link" @click="goToOrders">全部订单 ›</text>
  209. </view>
  210. <view class="order-card">
  211. <view class="order-grid">
  212. <view class="order-grid-item" @click="goToOrderStatus('pending')">
  213. <view class="order-icon-wrap">
  214. <text class="order-icon">💳</text>
  215. <text class="order-badge" v-if="orderPending > 0">{{ orderPending }}</text>
  216. </view>
  217. <text class="order-grid-text">待付款</text>
  218. </view>
  219. <view class="order-grid-item" @click="goToOrderStatus('paid')">
  220. <view class="order-icon-wrap">
  221. <text class="order-icon">📦</text>
  222. <text class="order-badge" v-if="orderUnpaid > 0">{{ orderUnpaid }}</text>
  223. </view>
  224. <text class="order-grid-text">待发货</text>
  225. </view>
  226. <view class="order-grid-item" @click="goToOrderStatus('shipped')">
  227. <view class="order-icon-wrap">
  228. <text class="order-icon">🚚</text>
  229. <text class="order-badge" v-if="orderShipped > 0">{{ orderShipped }}</text>
  230. </view>
  231. <text class="order-grid-text">待收货</text>
  232. </view>
  233. <view class="order-grid-item" @click="goToOrderStatus('refunding')">
  234. <view class="order-icon-wrap">
  235. <text class="order-icon">🔧</text>
  236. <text class="order-badge" v-if="orderAfterSale > 0">{{ orderAfterSale }}</text>
  237. </view>
  238. <text class="order-grid-text">售后</text>
  239. </view>
  240. <view class="order-grid-item" @click="goToOrders">
  241. <view class="order-icon-wrap">
  242. <text class="order-icon">📋</text>
  243. </view>
  244. <text class="order-grid-text">全部</text>
  245. </view>
  246. </view>
  247. <view class="order-extra-row">
  248. <view class="order-extra-item" @click="goToCart">
  249. <text class="order-extra-icon">🛒</text>
  250. <text class="order-extra-text">购物车</text>
  251. <text class="order-extra-badge" v-if="cartCount > 0">{{ cartCount > 99 ? '99+' : cartCount }}</text>
  252. </view>
  253. <view class="order-extra-divider"></view>
  254. <view class="order-extra-item" @click="goToAddress">
  255. <text class="order-extra-icon">📍</text>
  256. <text class="order-extra-text">收货地址</text>
  257. </view>
  258. </view>
  259. </view>
  260. </view>
  261. <!-- 规划师服务中心入口(仅规划师可见) -->
  262. <view class="service-center-section" v-if="role === 'teacher' && teacherStatus === 'approved'">
  263. <view class="service-center-card" @click="goToServiceCenter">
  264. <view class="service-center-icon">👨‍🏫</view>
  265. <view class="service-center-info">
  266. <text class="service-center-title">服务中心</text>
  267. <text class="service-center-desc">待处理任务:{{ pendingTasks }} 条 · 消息:{{ unreadMessages }} 条</text>
  268. </view>
  269. <text class="service-center-arrow">›</text>
  270. </view>
  271. </view>
  272. <!-- 规划师待审核提示 -->
  273. <view class="service-center-section" v-if="role === 'teacher' && teacherStatus !== 'approved'">
  274. <view class="service-center-card disabled-card">
  275. <view class="service-center-icon">👨‍🏫</view>
  276. <view class="service-center-info">
  277. <text class="service-center-title">服务中心</text>
  278. <text class="service-center-desc">{{ teacherStatus === 'pending' ? '申请审核中,暂未开放' : teacherStatus === 'rejected' ? '申请未通过,请联系管理员' : '规划师服务' }}</text>
  279. </view>
  280. </view>
  281. </view>
  282. <!-- 设置 + 退出 -->
  283. <view class="bottom-menu-section">
  284. <view class="menu-row" @click="goToSettings">
  285. <text class="menu-row-icon">⚙️</text>
  286. <text class="menu-row-text">设置</text>
  287. <text class="menu-row-arrow">›</text>
  288. </view>
  289. <view class="logout-btn" @click="logout">
  290. <text class="logout-text">退出登录</text>
  291. </view>
  292. </view>
  293. </template>
  294. <!-- 我的分享海报:复用文章/活动/商品同一套 ContentSharePoster 组件(canvas 绘制 + 保存到相册) -->
  295. <ContentSharePoster
  296. :show="showSharePoster"
  297. :title="shareHeadline"
  298. :energyData="posterEnergyData"
  299. :brandFallback="true"
  300. :qrCodeBase64="posterQrCode || ''"
  301. typeLabel="邀友加入"
  302. :summary="posterSummary"
  303. ctaText="长按识别二维码 · 加入浠艾福"
  304. :inviteText="posterInviteText"
  305. @close="closeSharePoster"
  306. />
  307. <FloatingAvatar />
  308. </view>
  309. </template>
  310. <script>
  311. import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
  312. import ContentSharePoster from '../../components/ContentSharePoster.vue'
  313. import {
  314. getChildren, getFamilyMemberList,
  315. getTodayTasksByCategory, getChallengeList, getMyMembership,
  316. verifyPassword, getPointsBalance, getUserInfo,
  317. cartCount, getOrderCounts, getReferralSummary,
  318. getProfileStats, getMyReferrer, getTeamConsumption,
  319. getTeacherInfo, getTeacherPendingTasks, getTeacherUnreadCount,
  320. getReferralCode, extractReferralCode, getShareQrCode, getEnergySandbox
  321. } from '../../utils/api.js'
  322. export default {
  323. components: { FloatingAvatar, ContentSharePoster },
  324. data() {
  325. return {
  326. nickname: '',
  327. role: 'parent',
  328. isSwitchedChild: false,
  329. userInfo: {},
  330. memberLevel: null,
  331. membershipDays: 0,
  332. totalPoints: 0,
  333. referralEarnings: 0,
  334. teamMembers: 0,
  335. // 个人成就
  336. completedTasks: 0,
  337. streakDays: 0,
  338. rewardsEarned: 0,
  339. badgesCollected: 0,
  340. growthRecords: 0,
  341. challengesJoined: 0,
  342. healthReports: 0,
  343. // 家庭
  344. familyMembers: [],
  345. // 商城
  346. orderPending: 0,
  347. orderUnpaid: 0,
  348. orderShipped: 0,
  349. orderAfterSale: 0,
  350. cartCount: 0,
  351. // 待办
  352. pendingTasks: 0,
  353. unreadMessages: 0,
  354. teacherStatus: 'pending',
  355. profileProgress: 0,
  356. // 五维能量
  357. dimensionScores: { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 },
  358. portrait: null,
  359. myReferrer: null,
  360. // 分享推广码
  361. showSharePoster: false,
  362. referralCode: '',
  363. posterQrCode: '',
  364. posterEnergyData: null,
  365. referralCodeLoaded: false,
  366. }
  367. },
  368. computed: {
  369. isLoggedIn() {
  370. return !!uni.getStorageSync('token')
  371. },
  372. roleLabel() {
  373. var map = { parent: '', child: '成员', teacher: '规划师' }
  374. return map[this.role] || '用户'
  375. },
  376. memberLevelText() {
  377. var map = { FREE: '免费用户', FAMILY: '家庭会员', PREMIUM: '高级会员', LIFETIME: '终身会员' }
  378. return map[this.memberLevel] || this.memberLevel || ''
  379. },
  380. isLifetime() {
  381. return this.memberLevel === 'LIFETIME'
  382. },
  383. hasUpgradeSpace() {
  384. return this.memberLevel === 'FREE' || this.memberLevel === 'FAMILY'
  385. },
  386. upgradeBtnText() {
  387. return this.hasUpgradeSpace ? '升级' : '立即开通'
  388. },
  389. // 分享海报标题:XXX邀请你关注家庭健康(昵称过长自动截断)
  390. shareHeadline() {
  391. var name = (this.nickname || '').trim()
  392. if (!name) return '邀请你关注家庭健康'
  393. if (name.length > 8) name = name.substring(0, 8) + '…'
  394. return name + '邀请你关注家庭健康'
  395. },
  396. posterSummary() {
  397. return '身体 · 心理 · 智慧 · 行动 · 财富,一家专注全家健康的成长平台'
  398. },
  399. posterInviteText() {
  400. var name = (this.nickname || '').trim()
  401. if (!name) return '扫码加入浠艾福,一起照顾好这个家'
  402. if (name.length > 8) name = name.substring(0, 8) + '…'
  403. return '「' + name + '」邀请你加入浠艾福'
  404. }
  405. },
  406. onShow() {
  407. if (!this.isLoggedIn) {
  408. return
  409. }
  410. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
  411. this.role = role
  412. this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
  413. this.nickname = (uni.getStorageSync('userInfo') || {}).nickname || ''
  414. // 规划师加载额外数据
  415. if (role === 'teacher') {
  416. this.loadTeacherStatus()
  417. this.loadTeacherData()
  418. } else {
  419. this.loadAllData()
  420. this.loadMyReferrer()
  421. }
  422. },
  423. methods: {
  424. loadTeacherStatus() {
  425. var self = this
  426. getTeacherInfo().then(function(res) {
  427. if (res && res.code === 200 && res.data) {
  428. self.teacherStatus = res.data.verificationStatus || res.data.status || 'pending'
  429. }
  430. }).catch(function() {})
  431. },
  432. loadTeacherData() {
  433. var self = this
  434. // 并行加载待处理任务和未读消息
  435. getTeacherPendingTasks().then(function(res) {
  436. if (res && res.data && Array.isArray(res.data)) {
  437. self.pendingTasks = res.data.length
  438. } else if (res && res.data && res.data.pendingCount != null) {
  439. self.pendingTasks = res.data.pendingCount
  440. }
  441. }).catch(function() {})
  442. getTeacherUnreadCount().then(function(res) {
  443. if (res && res.data != null) {
  444. self.unreadMessages = res.data
  445. }
  446. }).catch(function() {})
  447. },
  448. loadAllData() {
  449. this.loadUserInfo()
  450. this.loadMembership()
  451. this.loadPoints()
  452. this.loadFamily()
  453. this.loadTasks()
  454. this.loadChallenges()
  455. this.loadOrderCounts()
  456. this.loadCartCount()
  457. this.loadProfileStats()
  458. this.loadReferralEarnings()
  459. this.loadTeamInfo()
  460. this.loadDimensionEnergy()
  461. },
  462. loadUserInfo() {
  463. var self = this
  464. getUserInfo().then(function(res) {
  465. if (res && res.code === 200 && res.data) {
  466. self.userInfo = res.data
  467. if (!self.nickname) self.nickname = res.data.nickname || ''
  468. }
  469. }).catch(function() {})
  470. },
  471. loadMembership() {
  472. var self = this
  473. getMyMembership().then(function(res) {
  474. if (res && res.data) {
  475. self.memberLevel = res.data.memberLevel || res.data.levelCode || null
  476. self.membershipDays = res.data.membershipDays || res.data.daysRemaining || 0
  477. }
  478. }).catch(function() {})
  479. },
  480. loadPoints() {
  481. // 累计积分由 loadProfileStats 汇总家庭成员积分,此处不再单独覆盖
  482. },
  483. loadFamily() {
  484. var self = this
  485. getFamilyMemberList({ visibleOnly: true }).then(function(res) {
  486. var data = res && res.data ? res.data : []
  487. self.familyMembers = Array.isArray(data) ? data : []
  488. }).catch(function() {
  489. self.familyMembers = []
  490. })
  491. },
  492. loadTasks() {
  493. var self = this
  494. var memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('userId')
  495. if (!memberId) return
  496. getTodayTasksByCategory(memberId, 'action').then(function(res) {
  497. if (res && res.data) {
  498. self.pendingTasks = Array.isArray(res.data) ? res.data.length : 0
  499. }
  500. }).catch(function() {})
  501. // fallback to storage
  502. if (!self.pendingTasks) {
  503. self.pendingTasks = parseInt(uni.getStorageSync('pendingTaskCount') || 0)
  504. }
  505. },
  506. loadChallenges() {
  507. var self = this
  508. getChallengeList({}).then(function(res) {
  509. if (res && res.data) {
  510. // 不在此处使用,仅确保请求
  511. }
  512. }).catch(function() {})
  513. },
  514. loadOrderCounts() {
  515. var self = this
  516. getOrderCounts().then(function(res) {
  517. if (res && res.data) {
  518. self.orderPending = res.data.pending || 0
  519. self.orderUnpaid = res.data.paid || 0
  520. self.orderShipped = res.data.shipped || 0
  521. self.orderAfterSale = res.data.refunding || 0
  522. }
  523. }).catch(function() {})
  524. },
  525. loadCartCount() {
  526. var self = this
  527. cartCount().then(function(res) {
  528. if (res && res.data != null) {
  529. self.cartCount = res.data || 0
  530. }
  531. }).catch(function() {})
  532. },
  533. loadProfileStats() {
  534. var self = this
  535. getProfileStats().then(function(res) {
  536. if (res && res.code === 200 && res.data) {
  537. var d = res.data
  538. if (d.totalPoints != null) self.totalPoints = d.totalPoints
  539. if (d.streakDays != null) self.streakDays = d.streakDays
  540. if (d.completedTasks != null) self.completedTasks = d.completedTasks
  541. if (d.rewardsEarned != null) self.rewardsEarned = d.rewardsEarned
  542. if (d.badgesCollected != null) self.badgesCollected = d.badgesCollected
  543. if (d.growthRecords != null) self.growthRecords = d.growthRecords
  544. if (d.challengesJoined != null) self.challengesJoined = d.challengesJoined
  545. if (d.healthReports != null) self.healthReports = d.healthReports
  546. }
  547. }).catch(function() {})
  548. },
  549. loadDimensionEnergy() {
  550. var self = this
  551. getEnergySandbox('family').then(function(res) {
  552. if (res && res.code === 200 && res.data) {
  553. var d = res.data.familyData || null
  554. if (!d) {
  555. if (res.data.personalData) {
  556. // 个人数据模式:使用个人数据
  557. d = res.data.personalData
  558. } else {
  559. self.portrait = null
  560. return
  561. }
  562. }
  563. if (!d || typeof d !== 'object') return
  564. // 设置五维能量分数
  565. self.dimensionScores = {
  566. body: d.bodyScore ?? d.body_score ?? 0,
  567. mind: d.mindScore ?? d.mind_score ?? 0,
  568. wisdom: d.wisdomScore ?? d.wisdom_score ?? 0,
  569. action: d.actionScore ?? d.action_score ?? 0,
  570. wealth: d.wealthScore ?? d.wealth_score ?? 0
  571. }
  572. // 生成用户画像
  573. self.portrait = self.mapToPortrait(d)
  574. }
  575. }).catch(function(e) {
  576. console.error('获取五维能量沙盘失败', e)
  577. self.portrait = null
  578. })
  579. },
  580. loadReferralEarnings() {
  581. var self = this
  582. getReferralSummary().then(function(res) {
  583. if (res && res.code === 200 && res.data) {
  584. self.referralEarnings = res.data.totalEarnings || 0
  585. }
  586. }).catch(function() {})
  587. },
  588. loadTeamInfo() {
  589. var self = this
  590. getTeamConsumption(3).then(function(res) {
  591. if (res && res.data && res.data.totalMembers != null) {
  592. self.teamMembers = res.data.totalMembers
  593. }
  594. }).catch(function() {})
  595. },
  596. async loadMyReferrer() {
  597. try {
  598. var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || 0
  599. var res = await getMyReferrer(memberId)
  600. if (res && res.code === 200 && res.data && res.data.hasReferrer) {
  601. this.myReferrer = res.data
  602. }
  603. } catch (e) {
  604. console.error('获取推荐人失败', e)
  605. }
  606. },
  607. // ===== 分享海报(复用 ContentSharePoster,与文章/活动/商品同一套生成逻辑)=====
  608. async showShareQr() {
  609. uni.showLoading({ title: '生成海报中...' })
  610. try {
  611. if (!this.referralCodeLoaded) {
  612. await this.loadReferralCode()
  613. }
  614. var scene = 'ref=' + (this.referralCode || '')
  615. if (scene.length > 32) {
  616. scene = scene.substring(0, 32)
  617. }
  618. // 首页路径,扫码后进入首页并绑定推荐人
  619. // 注意:微信 getwxacodeunlimit 的 page 不能带前导斜杠,否则报 errcode 41030 invalid page rid
  620. var res = await getShareQrCode('pages/index-home/index', scene)
  621. if (res && res.code === 200 && res.data && res.data.qrCodeBase64) {
  622. this.posterQrCode = res.data.qrCodeBase64
  623. // 必须在 showSharePoster=true 之前取到数据:组件 watch 到 show 后 300ms 即开始绘制
  624. await this.loadPosterEnergy()
  625. this.showSharePoster = true
  626. } else {
  627. uni.showToast({ title: '生成失败,请重试', icon: 'none' })
  628. }
  629. } catch (e) {
  630. console.error('生成分享海报失败', e)
  631. uni.showToast({ title: '生成失败,请重试', icon: 'none' })
  632. } finally {
  633. uni.hideLoading()
  634. }
  635. },
  636. closeSharePoster() {
  637. this.showSharePoster = false
  638. },
  639. // 五维能量数据卡数据(方案C):家庭聚合视角;未解锁全景/取数失败时置空,海报降级为品牌卡
  640. loadPosterEnergy() {
  641. var self = this
  642. return getEnergySandbox('family').then(function(res) {
  643. self.posterEnergyData = self.mapPosterEnergy(res)
  644. }).catch(function(e) {
  645. console.error('获取五维能量失败,海报降级为品牌卡', e)
  646. self.posterEnergyData = null
  647. })
  648. },
  649. mapPosterEnergy(res) {
  650. if (!res || res.code !== 200 || !res.data) return null
  651. // locked=true 表示用户未解锁全景,此时不展示任何数据,改为品牌卡
  652. if (res.data.locked) return null
  653. // 只认家庭聚合视角;仅当接口返回的是裸 DTO(无 personalData/familyData 包装)时才直接取 res.data
  654. var d = res.data.familyData || null
  655. if (!d) {
  656. if (res.data.personalData) return null
  657. d = res.data
  658. }
  659. if (!d || typeof d !== 'object') return null
  660. var defs = [
  661. { key: 'body', label: '身', color: '#FF8C42' },
  662. { key: 'mind', label: '心', color: '#FF6B9D' },
  663. { key: 'wisdom', label: '智', color: '#6366F1' },
  664. { key: 'action', label: '行', color: '#10B981' },
  665. { key: 'wealth', label: '富', color: '#F59E0B' }
  666. ]
  667. var dims = []
  668. var sum = 0
  669. for (var i = 0; i < defs.length; i++) {
  670. var raw = d[defs[i].key + 'Score']
  671. if (raw === null || raw === undefined || raw === '' || isNaN(raw)) continue
  672. var n = Number(raw)
  673. if (n < 0) n = 0
  674. if (n > 100) n = 100
  675. sum += n
  676. dims.push({ key: defs[i].key, label: defs[i].label, score: n, color: defs[i].color })
  677. }
  678. if (dims.length === 0) return null
  679. var overall = d.overallScore
  680. if (overall === null || overall === undefined || overall === '' || isNaN(overall)) {
  681. overall = Math.round(sum / dims.length)
  682. }
  683. return { label: '我们家的能量', overall: Number(overall), dims: dims }
  684. },
  685. mapToPortrait(d) {
  686. // 计算总分和平均分
  687. var scores = { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
  688. var dims = []
  689. var total = 0
  690. var count = 0
  691. var defs = [
  692. { key: 'body', label: '身', color: '#FF8C42', desc: '身体健康状况,体质好坏的综合反映' },
  693. { key: 'mind', label: '心', color: '#FF6B9D', desc: '情绪与心理状态,情绪波动与心理平衡' },
  694. { key: 'wisdom', label: '智', color: '#6366F1', desc: '智力与知识水平,认知能力与知识储备' },
  695. { key: 'action', label: '行', color: '#10B981', desc: '行动力与执行力,计划与行动的落实情况' },
  696. { key: 'wealth', label: '富', color: '#F59E0B', desc: '财富与资源,物质丰富程度与创富能力' }
  697. ]
  698. for (var i = 0; i < defs.length; i++) {
  699. var raw = d[defs[i].key + 'Score']
  700. var n = 0
  701. if (raw != null && !isNaN(raw)) {
  702. n = Math.max(0, Math.min(100, Number(raw)))
  703. }
  704. scores[defs[i].key] = n
  705. total += n
  706. count++
  707. dims.push({
  708. code: defs[i].key,
  709. name: defs[i].label,
  710. score: n,
  711. color: defs[i].color,
  712. desc: defs[i].desc
  713. })
  714. }
  715. var overall = count > 0 ? Math.round(total / count) : 0
  716. // 确定等级和描述
  717. var tierLabel = ''
  718. var summary = ''
  719. var tip = ''
  720. if (overall >= 80) {
  721. tierLabel = '卓越守护者'
  722. summary = '你的五维能量处于卓越水平,身心健康,知行合一。家庭成员都受益于你的正向能量场。'
  723. tip = '保持当前的良好习惯,继续关注家庭成员的需求'
  724. } else if (overall >= 60) {
  725. tierLabel = '进取者'
  726. summary = '你的五维能量处于良好水平,各方面有所提升,但仍有提升空间。'
  727. tip = '可以尝试在其中一个维度上突破瓶颈,带动全家进步'
  728. } else if (overall >= 40) {
  729. tierLabel = '探索者'
  730. summary = '你的五维能量处于起步水平,正在探索和学习中。这是成长的起点。'
  731. tip = '从关注最容易改进的维度开始,每一点进步都很重要'
  732. } else {
  733. tierLabel = '起步者'
  734. summary = '你的五维能量处于起始水平,正处于开始关注全家健康的阶段。'
  735. tip = '从现在开始,每天关注一项小健康行动,逐步建立五维能量基础'
  736. }
  737. return {
  738. overall: overall,
  739. tierLabel: tierLabel,
  740. summary: summary,
  741. dims: dims,
  742. tip: tip
  743. }
  744. },
  745. async loadReferralCode() {
  746. if (this.referralCodeLoaded) return
  747. var self = this
  748. try {
  749. var res = await getReferralCode()
  750. self.referralCode = extractReferralCode(res) || ''
  751. } catch (e) {
  752. console.error('获取推广码失败', e)
  753. } finally {
  754. self.referralCodeLoaded = true
  755. }
  756. },
  757. goToServiceCenter() {
  758. uni.navigateTo({ url: '/pages/service-center/index' })
  759. },
  760. goTeamTree() {
  761. uni.navigateTo({ url: '/pages/profile/team-tree' })
  762. },
  763. goLogin: function() {
  764. uni.navigateTo({ url: '/pages/login/login' })
  765. },
  766. goSkip: function() {
  767. uni.switchTab({ url: '/pages/index-home/index' })
  768. },
  769. goToEditInfo: function() {
  770. uni.navigateTo({ url: '/pages/user-edit/user-edit' })
  771. },
  772. goToPoints: function() {
  773. uni.navigateTo({ url: '/pages/points/points' })
  774. },
  775. goToStreak: function() {
  776. var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId')
  777. var url = '/pages/stats/completion-rate'
  778. if (memberId) {
  779. url += '?memberId=' + memberId
  780. } else {
  781. url += '?familyView=1'
  782. }
  783. uni.navigateTo({ url: url })
  784. },
  785. goToTasks: function() {
  786. uni.navigateTo({ url: '/pages/tasks/tasks' })
  787. },
  788. goToWishes: function() {
  789. uni.navigateTo({ url: '/pages/wishes/index' })
  790. },
  791. goToBadges: function() {
  792. uni.navigateTo({ url: '/pages/rewards/badge' })
  793. },
  794. goToGrowth: function() {
  795. uni.navigateTo({ url: '/pages/growth-main/index' })
  796. },
  797. goToChallenges: function() {
  798. uni.navigateTo({ url: '/pages/health/challenge-manage' })
  799. },
  800. goToHealthReports: function() {
  801. uni.navigateTo({ url: '/pages/health/report-list' })
  802. },
  803. goToWealthCenter: function() {
  804. uni.navigateTo({ url: '/pages/promotion/index' })
  805. },
  806. goUpgrade: function() {
  807. uni.navigateTo({ url: '/pages/membership/upgrade' })
  808. },
  809. goToFamily: function() {
  810. uni.navigateTo({ url: '/pages/profile-extra/family-members' })
  811. },
  812. goMemberDetail: function(member) {
  813. uni.navigateTo({ url: '/pages/home-pages/member-home-detail?memberId=' + member.id })
  814. },
  815. goToAppointments: function() {
  816. uni.navigateTo({ url: '/pages/parent/appointments' })
  817. },
  818. goToOrders: function() {
  819. uni.navigateTo({ url: '/pages/shop/order-list/order-list' })
  820. },
  821. goToOrderStatus: function(status) {
  822. uni.navigateTo({ url: '/pages/shop/order-list/order-list?status=' + status })
  823. },
  824. goToCart: function() {
  825. uni.navigateTo({ url: '/pages/shop/cart/cart' })
  826. },
  827. goToAddress: function() {
  828. uni.navigateTo({ url: '/pages/shop/address/address' })
  829. },
  830. goToSettings: function() {
  831. uni.showActionSheet({
  832. itemList: ['完善信息', '家庭成员', '隐私政策', '用户协议', '我的画像', '提醒设置'],
  833. success: function(res) {
  834. var urls = {
  835. 0: '/pages/user-edit/user-edit',
  836. 1: '/pages/profile-extra/family-members',
  837. 2: '/pages/policy/privacy',
  838. 3: '/pages/policy/agreement',
  839. 4: '/pages/profile/portrait-edit',
  840. 5: '/pages/reminder-config/index'
  841. }
  842. if (urls[res.tapIndex]) {
  843. uni.navigateTo({ url: urls[res.tapIndex] })
  844. }
  845. }
  846. })
  847. },
  848. memberRoleLabel: function(role) {
  849. var map = { parent: '家长', child: '成员', teacher: '规划师' }
  850. return map[role] || '成员'
  851. },
  852. onExitSwitch: function() {
  853. var self = this
  854. uni.showModal({
  855. title: '退出切换',
  856. content: '请输入家长密码以确认退出',
  857. editable: true,
  858. success: function(res) {
  859. if (res.confirm && res.content) {
  860. verifyPassword(res.content).then(function(result) {
  861. if (result.code === 200) {
  862. self.$store.commit('switchBackToParent')
  863. self.isSwitchedChild = false
  864. self.role = 'parent'
  865. uni.showToast({ title: '已退出切换', icon: 'success' })
  866. uni.reLaunch({ url: '/pages/index-home/index' })
  867. } else {
  868. uni.showToast({ title: '密码错误', icon: 'none' })
  869. }
  870. }).catch(function() {
  871. uni.showToast({ title: '验证失败', icon: 'none' })
  872. })
  873. }
  874. }
  875. })
  876. },
  877. logout: function() {
  878. uni.showModal({
  879. title: '确认退出',
  880. content: '确定要退出登录吗?',
  881. success: function(res) {
  882. if (res.confirm) {
  883. this.$store.commit('logout')
  884. uni.reLaunch({ url: '/pages/index-home/index' })
  885. }
  886. }.bind(this)
  887. })
  888. }
  889. }
  890. }
  891. </script>
  892. <style scoped>
  893. .container {
  894. min-height: 100vh;
  895. background: #F8F4EF;
  896. padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
  897. }
  898. /* 登录引导 */
  899. .login-prompt {
  900. display: flex;
  901. flex-direction: column;
  902. align-items: center;
  903. padding: 200rpx 60rpx;
  904. }
  905. .prompt-icon { font-size: 100rpx; margin-bottom: 30rpx; }
  906. .prompt-title { font-size: 40rpx; font-weight: bold; color: #333; margin-bottom: 16rpx; }
  907. .prompt-desc { font-size: 26rpx; color: #999; margin-bottom: 20rpx; }
  908. .login-btn {
  909. background: linear-gradient(135deg, #667eea, #764ba2);
  910. color: #fff;
  911. border-radius: 50rpx;
  912. font-size: 30rpx;
  913. padding: 24rpx 80rpx;
  914. }
  915. .skip-link {
  916. font-size: 26rpx;
  917. color: #999;
  918. margin-top: 24rpx;
  919. text-decoration: underline;
  920. }
  921. .philosophy-card {
  922. margin: 0 40rpx 48rpx;
  923. background: linear-gradient(135deg, #FFF7ED, rgba(249, 115, 22, 0.06));
  924. border-radius: 20rpx;
  925. padding: 28rpx 32rpx;
  926. border-left: 6rpx solid #F97316;
  927. }
  928. .philosophy-title {
  929. font-size: 28rpx;
  930. font-weight: bold;
  931. color: #333;
  932. display: block;
  933. margin-bottom: 8rpx;
  934. }
  935. .philosophy-desc {
  936. font-size: 24rpx;
  937. color: #888;
  938. line-height: 1.6;
  939. display: block;
  940. }
  941. /* 退出切换 */
  942. .exit-switch {
  943. margin: 0 30rpx 16rpx;
  944. background: linear-gradient(135deg, #667eea, #764ba2);
  945. color: #fff;
  946. padding: 16rpx;
  947. border-radius: 12rpx;
  948. text-align: center;
  949. font-size: 26rpx;
  950. }
  951. /* 身份卡 */
  952. .user-card {
  953. margin: 24rpx 30rpx 20rpx;
  954. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  955. border-radius: 24rpx;
  956. padding: 32rpx 32rpx;
  957. display: flex;
  958. justify-content: space-between;
  959. align-items: center;
  960. box-shadow: 0 8rpx 32rpx rgba(102,126,234,0.3);
  961. }
  962. .user-card-left {
  963. display: flex;
  964. align-items: center;
  965. flex: 1;
  966. }
  967. .avatar-wrap {
  968. width: 100rpx;
  969. height: 100rpx;
  970. border-radius: 50%;
  971. background: rgba(255,255,255,0.2);
  972. display: flex;
  973. align-items: center;
  974. justify-content: center;
  975. margin-right: 20rpx;
  976. overflow: hidden;
  977. border: 3rpx solid rgba(255,255,255,0.3);
  978. }
  979. .avatar-img {
  980. width: 100rpx;
  981. height: 100rpx;
  982. border-radius: 50%;
  983. }
  984. .avatar-text {
  985. font-size: 44rpx;
  986. color: #fff;
  987. }
  988. .user-meta { flex: 1; }
  989. .user-name-row {
  990. display: flex;
  991. align-items: center;
  992. gap: 12rpx;
  993. margin-bottom: 8rpx;
  994. }
  995. .nickname {
  996. font-size: 34rpx;
  997. font-weight: bold;
  998. color: #fff;
  999. }
  1000. .role-tag {
  1001. font-size: 20rpx;
  1002. background: rgba(255,255,255,0.2);
  1003. color: #fff;
  1004. padding: 4rpx 14rpx;
  1005. border-radius: 20rpx;
  1006. }
  1007. .referrer-line {
  1008. font-size: 24rpx;
  1009. color: rgba(255,255,255,0.7);
  1010. margin-bottom: 6rpx;
  1011. }
  1012. .user-sub {
  1013. display: flex;
  1014. align-items: center;
  1015. gap: 12rpx;
  1016. }
  1017. .level-badge {
  1018. font-size: 22rpx;
  1019. padding: 6rpx 20rpx;
  1020. border-radius: 22rpx;
  1021. font-weight: bold;
  1022. border: 2rpx solid rgba(255,255,255,0.35);
  1023. display: flex;
  1024. align-items: center;
  1025. gap: 6rpx;
  1026. box-sizing: border-box;
  1027. height: 40rpx;
  1028. }
  1029. .level-badge::before {
  1030. content: '👑';
  1031. font-size: 18rpx;
  1032. }
  1033. .level-FREE { background: linear-gradient(135deg, #9CA3AF, #6B7280); color: #fff; }
  1034. .level-FREE::before { content: ''; }
  1035. .level-FAMILY { background: linear-gradient(135deg, #F59E0B, #D97706); color: #fff; }
  1036. .level-PREMIUM { background: linear-gradient(135deg, #6366F1, #4F46E5); color: #fff; }
  1037. .level-LIFETIME {
  1038. background: linear-gradient(135deg, #EC4899, #DB2777);
  1039. color: #fff;
  1040. box-shadow: 0 4rpx 16rpx rgba(236,72,153,0.45);
  1041. border-color: rgba(255,255,255,0.55);
  1042. }
  1043. .days-left {
  1044. font-size: 20rpx;
  1045. color: rgba(255,255,255,0.85);
  1046. }
  1047. .upgrade-btn {
  1048. background: rgba(255,255,255,0.22);
  1049. border: 2rpx solid rgba(255,255,255,0.6);
  1050. border-radius: 20rpx;
  1051. padding: 6rpx 20rpx;
  1052. flex-shrink: 0;
  1053. box-sizing: border-box;
  1054. height: 40rpx;
  1055. display: flex;
  1056. align-items: center;
  1057. }
  1058. .upgrade-btn-text {
  1059. font-size: 20rpx;
  1060. font-weight: bold;
  1061. color: #fff;
  1062. line-height: 1;
  1063. }
  1064. .user-card-right {
  1065. display: flex;
  1066. flex-direction: column;
  1067. align-items: center;
  1068. gap: 6rpx;
  1069. background: rgba(255,255,255,0.15);
  1070. padding: 12rpx 24rpx;
  1071. border-radius: 14rpx;
  1072. }
  1073. .points-num {
  1074. font-size: 36rpx;
  1075. font-weight: bold;
  1076. color: #fff;
  1077. }
  1078. .points-label {
  1079. font-size: 20rpx;
  1080. color: rgba(255,255,255,0.7);
  1081. }
  1082. /* 家庭成员速览 */
  1083. .family-strip {
  1084. margin: 0 30rpx 20rpx;
  1085. background: #fff;
  1086. border-radius: 20rpx;
  1087. padding: 20rpx 24rpx 16rpx;
  1088. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1089. }
  1090. .family-strip-header {
  1091. display: flex;
  1092. justify-content: space-between;
  1093. align-items: center;
  1094. margin-bottom: 14rpx;
  1095. }
  1096. .family-strip-title {
  1097. font-size: 26rpx;
  1098. font-weight: bold;
  1099. color: #333;
  1100. }
  1101. .family-strip-count {
  1102. font-size: 22rpx;
  1103. color: #999;
  1104. }
  1105. .family-avatars {
  1106. display: flex;
  1107. flex-direction: row;
  1108. align-items: center;
  1109. gap: 16rpx;
  1110. overflow-x: auto;
  1111. padding-bottom: 4rpx;
  1112. }
  1113. .family-avatar-item {
  1114. display: flex;
  1115. flex-direction: column;
  1116. align-items: center;
  1117. gap: 6rpx;
  1118. flex-shrink: 0;
  1119. }
  1120. .family-avatar-wrap {
  1121. width: 72rpx;
  1122. height: 72rpx;
  1123. border-radius: 50%;
  1124. background: #f0f0f0;
  1125. display: flex;
  1126. align-items: center;
  1127. justify-content: center;
  1128. overflow: hidden;
  1129. position: relative;
  1130. }
  1131. .family-avatar-img {
  1132. width: 72rpx;
  1133. height: 72rpx;
  1134. border-radius: 50%;
  1135. }
  1136. .family-avatar-text {
  1137. font-size: 28rpx;
  1138. color: #666;
  1139. }
  1140. .family-avatar-wrap.family-avatar-add {
  1141. background: #F8F4EF;
  1142. border: 2rpx dashed #ccc;
  1143. }
  1144. .family-avatar-plus {
  1145. font-size: 36rpx;
  1146. color: #999;
  1147. }
  1148. .family-avatar-tag {
  1149. position: absolute;
  1150. bottom: -4rpx;
  1151. right: -4rpx;
  1152. font-size: 14rpx;
  1153. padding: 2rpx 6rpx;
  1154. border-radius: 8rpx;
  1155. color: #fff;
  1156. }
  1157. .family-avatar-tag.tag-parent { background: #6366F1; }
  1158. .family-avatar-tag.tag-child { background: #F97316; }
  1159. .family-avatar-tag.tag-teacher { background: #10B981; }
  1160. .family-avatar-tag.tag-member { background: #9CA3AF; }
  1161. .family-avatar-name {
  1162. font-size: 20rpx;
  1163. color: #666;
  1164. white-space: nowrap;
  1165. }
  1166. /* 个人成就 */
  1167. .achievement-section {
  1168. margin: 0 30rpx 20rpx;
  1169. background: #fff;
  1170. border-radius: 20rpx;
  1171. padding: 24rpx;
  1172. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1173. }
  1174. .section-header-row {
  1175. display: flex;
  1176. justify-content: space-between;
  1177. align-items: baseline;
  1178. margin-bottom: 16rpx;
  1179. }
  1180. .section-title {
  1181. font-size: 28rpx;
  1182. font-weight: bold;
  1183. color: #333;
  1184. }
  1185. .section-sub {
  1186. font-size: 22rpx;
  1187. color: #999;
  1188. }
  1189. .achievement-grid {
  1190. display: flex;
  1191. flex-wrap: wrap;
  1192. gap: 0;
  1193. }
  1194. .ach-item {
  1195. width: 25%;
  1196. display: flex;
  1197. flex-direction: column;
  1198. align-items: center;
  1199. gap: 6rpx;
  1200. padding: 10rpx 0;
  1201. }
  1202. .ach-icon-wrap {
  1203. width: 64rpx;
  1204. height: 64rpx;
  1205. border-radius: 50%;
  1206. display: flex;
  1207. align-items: center;
  1208. justify-content: center;
  1209. margin-bottom: 4rpx;
  1210. }
  1211. .ach-icon {
  1212. font-size: 32rpx;
  1213. line-height: 1;
  1214. }
  1215. .ach-num {
  1216. font-size: 28rpx;
  1217. font-weight: bold;
  1218. color: #333;
  1219. line-height: 1;
  1220. }
  1221. .ach-label {
  1222. font-size: 20rpx;
  1223. color: #999;
  1224. line-height: 1;
  1225. }
  1226. /* 我的订单(电商风格) */
  1227. .order-section {
  1228. margin: 0 30rpx 20rpx;
  1229. }
  1230. .order-section-header {
  1231. display: flex;
  1232. align-items: center;
  1233. justify-content: space-between;
  1234. margin-bottom: 14rpx;
  1235. }
  1236. .order-all-link {
  1237. font-size: 24rpx;
  1238. color: #999;
  1239. }
  1240. .order-card {
  1241. background: #fff;
  1242. border-radius: 20rpx;
  1243. padding: 24rpx 12rpx;
  1244. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1245. }
  1246. .order-grid {
  1247. display: flex;
  1248. flex-wrap: wrap;
  1249. }
  1250. .order-grid-item {
  1251. width: 20%;
  1252. display: flex;
  1253. flex-direction: column;
  1254. align-items: center;
  1255. gap: 10rpx;
  1256. padding: 8rpx 0;
  1257. }
  1258. .order-icon-wrap {
  1259. position: relative;
  1260. width: 64rpx;
  1261. height: 64rpx;
  1262. display: flex;
  1263. align-items: center;
  1264. justify-content: center;
  1265. }
  1266. .order-icon {
  1267. font-size: 40rpx;
  1268. line-height: 1;
  1269. }
  1270. .order-badge {
  1271. position: absolute;
  1272. top: -8rpx;
  1273. right: -14rpx;
  1274. min-width: 30rpx;
  1275. height: 30rpx;
  1276. line-height: 30rpx;
  1277. border-radius: 15rpx;
  1278. background: #FF4D4F;
  1279. color: #fff;
  1280. font-size: 18rpx;
  1281. text-align: center;
  1282. padding: 0 6rpx;
  1283. }
  1284. .order-grid-text {
  1285. font-size: 22rpx;
  1286. color: #666;
  1287. line-height: 1;
  1288. }
  1289. .order-extra-row {
  1290. display: flex;
  1291. align-items: center;
  1292. margin-top: 20rpx;
  1293. padding-top: 20rpx;
  1294. border-top: 1rpx solid #F2F2F2;
  1295. }
  1296. .order-extra-item {
  1297. flex: 1;
  1298. display: flex;
  1299. align-items: center;
  1300. justify-content: center;
  1301. gap: 8rpx;
  1302. }
  1303. .order-extra-icon {
  1304. font-size: 28rpx;
  1305. }
  1306. .order-extra-text {
  1307. font-size: 24rpx;
  1308. color: #333;
  1309. }
  1310. .order-extra-badge {
  1311. min-width: 30rpx;
  1312. height: 30rpx;
  1313. line-height: 30rpx;
  1314. border-radius: 15rpx;
  1315. background: #F97316;
  1316. color: #fff;
  1317. font-size: 18rpx;
  1318. text-align: center;
  1319. padding: 0 6rpx;
  1320. }
  1321. .order-extra-divider {
  1322. width: 1rpx;
  1323. height: 30rpx;
  1324. background: #F2F2F2;
  1325. }
  1326. /* 规划师服务中心入口 */
  1327. .service-center-section {
  1328. margin: 0 30rpx 20rpx;
  1329. }
  1330. .service-center-card {
  1331. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  1332. border-radius: 20rpx;
  1333. padding: 24rpx;
  1334. display: flex;
  1335. align-items: center;
  1336. gap: 20rpx;
  1337. box-shadow: 0 4rpx 16rpx rgba(102,126,234,0.25);
  1338. }
  1339. .service-center-card.disabled-card {
  1340. background: linear-gradient(135deg, #e2e8f0, #94a3b8);
  1341. box-shadow: none;
  1342. opacity: 0.8;
  1343. }
  1344. .service-center-icon {
  1345. font-size: 48rpx;
  1346. }
  1347. .service-center-info {
  1348. flex: 1;
  1349. }
  1350. .service-center-title {
  1351. font-size: 30rpx;
  1352. font-weight: bold;
  1353. color: #fff;
  1354. display: block;
  1355. }
  1356. .service-center-desc {
  1357. font-size: 22rpx;
  1358. color: rgba(255,255,255,0.8);
  1359. margin-top: 6rpx;
  1360. display: block;
  1361. }
  1362. .service-center-card.disabled-card .service-center-title {
  1363. color: #fff;
  1364. }
  1365. .service-center-card.disabled-card .service-center-desc {
  1366. color: rgba(255,255,255,0.9);
  1367. }
  1368. .service-center-arrow {
  1369. font-size: 40rpx;
  1370. color: #fff;
  1371. }
  1372. /* 设置 + 退出 */
  1373. .bottom-menu-section {
  1374. margin: 0 30rpx;
  1375. padding-bottom: 20rpx;
  1376. }
  1377. .menu-row {
  1378. width: 100%;
  1379. background: #fff;
  1380. border-radius: 16rpx;
  1381. padding: 24rpx;
  1382. display: flex;
  1383. align-items: center;
  1384. gap: 14rpx;
  1385. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  1386. margin-bottom: 20rpx;
  1387. }
  1388. .menu-row-icon {
  1389. font-size: 30rpx;
  1390. }
  1391. .menu-row-text {
  1392. flex: 1;
  1393. font-size: 26rpx;
  1394. color: #333;
  1395. }
  1396. .menu-row-arrow {
  1397. font-size: 28rpx;
  1398. color: #ccc;
  1399. }
  1400. .logout-btn {
  1401. width: 100%;
  1402. background: #fff;
  1403. border-radius: 16rpx;
  1404. padding: 24rpx;
  1405. text-align: center;
  1406. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  1407. }
  1408. .logout-text {
  1409. font-size: 26rpx;
  1410. color: #FF4D4F;
  1411. font-weight: 500;
  1412. }
  1413. /* 分享推广码浮动按钮 */
  1414. .share-qr-float {
  1415. position: fixed;
  1416. top: 180rpx;
  1417. right: 30rpx;
  1418. width: 80rpx;
  1419. height: 80rpx;
  1420. border-radius: 50%;
  1421. background: #fff;
  1422. box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.15);
  1423. display: flex;
  1424. align-items: center;
  1425. justify-content: center;
  1426. z-index: 99;
  1427. }
  1428. /* 二维码图标(纯 CSS 绘制,不依赖 emoji 字体) */
  1429. .qr-glyph {
  1430. position: relative;
  1431. width: 46rpx;
  1432. height: 46rpx;
  1433. }
  1434. .qr-eye {
  1435. position: absolute;
  1436. width: 16rpx;
  1437. height: 16rpx;
  1438. border: 4rpx solid #1E293B;
  1439. border-radius: 3rpx;
  1440. box-sizing: border-box;
  1441. }
  1442. .qr-eye-tl { left: 0; top: 0; }
  1443. .qr-eye-tr { right: 0; top: 0; }
  1444. .qr-eye-bl { left: 0; bottom: 0; }
  1445. .qr-mid {
  1446. position: absolute;
  1447. left: 20rpx;
  1448. top: 20rpx;
  1449. width: 7rpx;
  1450. height: 7rpx;
  1451. background: #1E293B;
  1452. border-radius: 1rpx;
  1453. }
  1454. .qr-cluster {
  1455. position: absolute;
  1456. right: 0;
  1457. bottom: 0;
  1458. width: 18rpx;
  1459. display: flex;
  1460. flex-wrap: wrap;
  1461. }
  1462. .qr-pip {
  1463. width: 7rpx;
  1464. height: 7rpx;
  1465. margin: 1rpx;
  1466. background: #1E293B;
  1467. border-radius: 1rpx;
  1468. }
  1469. </style>