profile.vue 40 KB

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