index.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. <template>
  2. <view class="container">
  3. <!-- ===== 未登录状态 ===== -->
  4. <view v-if="!isLoggedIn" class="login-prompt">
  5. <view class="prompt-icon">👤</view>
  6. <text class="prompt-title">登录浠艾福</text>
  7. <text class="prompt-desc">登录后可查看个人资料、积分记录等</text>
  8. <button class="login-btn" @click="goLogin">登录 / 注册</button>
  9. </view>
  10. <!-- ===== 已登录状态 ===== -->
  11. <template v-else>
  12. <!-- 品牌头部 -->
  13. <PageBanner theme="wealth"
  14. tagline="财富管理 · 价值创造"
  15. quote="富足源于利他与坚持" />
  16. <!-- 退出切换按钮(仅家长切换到孩子状态时显示) -->
  17. <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
  18. <text>🔄 退出切换</text>
  19. </view>
  20. <view class="user-card">
  21. <view class="avatar">👤</view>
  22. <view class="user-info">
  23. <view class="nickname">{{ nickname || '未设置昵称' }}</view>
  24. <view class="role">{{ role === 'parent' ? '家长模式' : (role === 'child' ? '孩子模式' : '成长规划师模式') }}</view>
  25. <view class="system-points" v-if="role === 'parent' && children.length > 0">🏅 系统积分: {{ children[0].systemPoints || 0 }}</view>
  26. </view>
  27. <!-- 切换按钮仅在非切换状态下显示 -->
  28. <button class="btn-switch" v-if="!isSwitchedChild" @click="switchMode">切换</button>
  29. </view>
  30. <!-- 行动数据 -->
  31. <view class="section">
  32. <view class="section-header">
  33. <text class="section-title">🎯 行动数据</text>
  34. </view>
  35. <view class="wealth-card">
  36. <view class="wealth-item">
  37. <text class="wealth-value">{{ actionData.taskCount }}</text>
  38. <text class="wealth-label">完成任务</text>
  39. </view>
  40. <view class="wealth-divider"></view>
  41. <view class="wealth-item">
  42. <text class="wealth-value">{{ actionData.purchaseCount }}</text>
  43. <text class="wealth-label">购买商品</text>
  44. </view>
  45. <view class="wealth-divider"></view>
  46. <view class="wealth-item">
  47. <text class="wealth-value">{{ actionData.activityCount }}</text>
  48. <text class="wealth-label">参加活动</text>
  49. </view>
  50. <view class="wealth-divider"></view>
  51. <view class="wealth-item">
  52. <text class="wealth-value">{{ actionData.courseCount }}</text>
  53. <text class="wealth-label">学习课程</text>
  54. </view>
  55. </view>
  56. </view>
  57. <!-- 财富数据(推广收益) -->
  58. <view class="section">
  59. <view class="section-header">
  60. <text class="section-title">💰 财富数据</text>
  61. <text class="section-more" @click="goToPromotion">全部 ›</text>
  62. </view>
  63. <view class="wealth-card">
  64. <view class="wealth-item">
  65. <text class="wealth-value">{{ formatPriceWithSymbol(wealthData.totalEarnings) }}</text>
  66. <text class="wealth-label">累计收益</text>
  67. </view>
  68. <view class="wealth-divider"></view>
  69. <view class="wealth-item">
  70. <text class="wealth-value">{{ formatPriceWithSymbol(wealthData.availableAmount) }}</text>
  71. <text class="wealth-label">可提现</text>
  72. </view>
  73. <view class="wealth-divider"></view>
  74. <view class="wealth-item">
  75. <text class="wealth-value">{{ wealthData.referralCount }}</text>
  76. <text class="wealth-label">邀请人数</text>
  77. </view>
  78. </view>
  79. <view class="referral-code-bar" @click="copyReferralCode" v-if="wealthData.referralCode">
  80. <text class="referral-code-label">邀请码</text>
  81. <text class="referral-code-value">{{ wealthData.referralCode }}</text>
  82. <text class="referral-code-copy">复制</text>
  83. </view>
  84. </view>
  85. <!-- 成就徽章 -->
  86. <view class="section">
  87. <view class="section-header">
  88. <text class="section-title">🏅 成就徽章</text>
  89. <text class="section-more" @click="goAllBadges">全部 ›</text>
  90. </view>
  91. <view class="badge-grid">
  92. <view class="badge-item" v-for="badge in badges" :key="badge.id">
  93. <view :class="['badge-icon-wrap', badge.unlocked ? 'unlocked' : 'locked']">
  94. <text class="badge-icon">{{ badge.icon }}</text>
  95. </view>
  96. <text class="badge-name">{{ badge.name }}</text>
  97. </view>
  98. </view>
  99. </view>
  100. <!-- 成长报告 -->
  101. <view class="section">
  102. <view class="section-header">
  103. <text class="section-title">📈 成长报告</text>
  104. <text class="section-more" @click="goGrowthReport">查看 ›</text>
  105. </view>
  106. <view class="report-card" @click="goGrowthReport">
  107. <view class="report-stat-item">
  108. <text class="report-stat">{{ growthReport.monthActiveDays || 0 }}天</text>
  109. <text class="report-label">本月活跃</text>
  110. </view>
  111. <view class="report-divider"></view>
  112. <view class="report-stat-item">
  113. <text class="report-stat">{{ growthReport.totalTasks || 0 }}</text>
  114. <text class="report-label">累计任务</text>
  115. </view>
  116. <view class="report-divider"></view>
  117. <view class="report-stat-item">
  118. <text class="report-stat">{{ growthReport.completionRate || 0 }}%</text>
  119. <text class="report-label">完成率</text>
  120. </view>
  121. </view>
  122. </view>
  123. <view class="menu-list">
  124. <!-- 修改个人资料:所有角色可见 -->
  125. <view class="menu-item" @click="goToEditInfo">
  126. <text>📝 修改个人资料</text>
  127. <text class="arrow">›</text>
  128. </view>
  129. <!-- 我的孩子:仅家长可见 -->
  130. <view class="menu-item" v-if="role === 'parent'" @click="goToChildren">
  131. <text>👶 我的孩子</text>
  132. <text class="arrow">›</text>
  133. </view>
  134. <!-- 家庭成员 -->
  135. <view class="menu-item" v-if="role === 'parent'" @click="goToFamilyMembers">
  136. <text>👨‍👩‍👧‍👦 家庭成员</text>
  137. <text class="arrow">›</text>
  138. </view>
  139. <view class="menu-item" @click="goToPointsLogs">
  140. <text>📊 积分记录</text>
  141. <text class="arrow">›</text>
  142. </view>
  143. <!-- 设置/重置密码:仅家长和成长规划师可见,孩子不可见 -->
  144. <view class="menu-item" v-if="role === 'parent'" @click="showPasswordModal = true">
  145. <text>🔐 {{ hasPassword ? '重置密码' : '设置密码' }}</text>
  146. <text class="arrow">›</text>
  147. </view>
  148. <!-- 对家庭成员可见:仅家长可见 -->
  149. <view class="menu-item menu-item-switch" v-if="role === 'parent'">
  150. <text>👁 对家庭成员可见</text>
  151. <switch :checked="showToFamily === 1" @change="onShowToFamilyChange" color="#5B9BD5" />
  152. </view>
  153. <button class="menu-item menu-item-btn" open-type="share" @click="inviteFamily" v-if="role === 'parent'">
  154. <text>👨‍👩‍👧‍👦 邀请家人</text>
  155. <text class="arrow">›</text>
  156. </button>
  157. <button class="menu-item menu-item-btn" open-type="share" @click="inviteTeacher" v-if="role === 'parent'">
  158. <text>👨‍🏫 邀请规划师</text>
  159. <text class="arrow">›</text>
  160. </button>
  161. <!-- 会员中心(浠艾福俱乐部) -->
  162. <view class="menu-item" @click="goToMembership">
  163. <text>👑 会员中心</text>
  164. <text class="arrow">›</text>
  165. </view>
  166. <!-- 服务商中心(仅已通过服务商可见) -->
  167. <view class="menu-item" v-if="isVendor" @click="goToVendorCenter">
  168. <text>🏪 服务商中心</text>
  169. <text class="arrow">›</text>
  170. </view>
  171. <view class="menu-item" @click="logout">
  172. <text>🚪 退出登录</text>
  173. <text class="arrow">›</text>
  174. </view>
  175. </view>
  176. <!-- 设置/重置密码弹窗 -->
  177. <view class="modal-mask" v-if="showPasswordModal" @click="showPasswordModal = false">
  178. <view class="modal" @click.stop>
  179. <view class="modal-title">{{ hasPassword ? '重置密码' : '设置密码' }}</view>
  180. <view class="form-item" v-if="hasPassword">
  181. <input v-model="oldPassword" type="number" password placeholder="输入旧密码" maxlength="6" />
  182. </view>
  183. <view class="form-item">
  184. <input v-model="newPassword" type="number" password placeholder="输入新密码" maxlength="6" />
  185. </view>
  186. <view class="form-item">
  187. <input v-model="confirmPassword" type="number" password placeholder="确认新密码" maxlength="6" />
  188. </view>
  189. <view class="modal-btns">
  190. <button @click="showPasswordModal = false">取消</button>
  191. <button class="btn-primary" @click="savePassword">确定</button>
  192. </view>
  193. </view>
  194. </view>
  195. </template>
  196. </view>
  197. </template>
  198. <script>
  199. import PageBanner from '../../components/PageBanner.vue'
  200. import { setPassword, verifyPassword, getChildren, vendorStatus, getEnergyOverview, getUserActionStats, getReferralCode, getReferralSummary, getCommissionSummary, updateMemberVisibility, getVisibleFamilyMembers } from '../../utils/api.js'
  201. export default {
  202. components: { PageBanner },
  203. data() {
  204. return {
  205. nickname: '',
  206. role: 'parent',
  207. hasPassword: false,
  208. showPasswordModal: false,
  209. oldPassword: '',
  210. newPassword: '',
  211. confirmPassword: '',
  212. children: [],
  213. isSwitchedChild: false,
  214. shareData: null,
  215. isVendor: false,
  216. showToFamily: 1,
  217. totalEnergy: null,
  218. badges: [
  219. { id: 1, icon: '\u{1F3C6}', name: '连续7天', unlocked: false },
  220. { id: 2, icon: '\u{2B50}', name: '积分达人', unlocked: false },
  221. { id: 3, icon: '\u{1F525}', name: '执行之星', unlocked: false },
  222. { id: 4, icon: '\u{1F31F}', name: '学习先锋', unlocked: false }
  223. ],
  224. actionData: {
  225. purchaseCount: 0,
  226. activityCount: 0,
  227. taskCount: 0,
  228. courseCount: 0
  229. },
  230. wealthData: {
  231. totalEarnings: '0.00',
  232. availableAmount: '0.00',
  233. referralCount: 0,
  234. referralCode: ''
  235. },
  236. growthReport: {
  237. monthActiveDays: 0,
  238. totalTasks: 0,
  239. completionRate: 0
  240. }
  241. }
  242. },
  243. computed: {
  244. isLoggedIn() {
  245. return !!uni.getStorageSync('token')
  246. }
  247. },
  248. onShareAppMessage() {
  249. if (this.shareData) {
  250. return {
  251. title: this.shareData.title,
  252. path: this.shareData.path,
  253. imageUrl: '/static/invite-card.png'
  254. }
  255. }
  256. },
  257. onShow() {
  258. if (!uni.getStorageSync('token')) return
  259. const currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
  260. if (currentRole === 'teacher') {
  261. uni.redirectTo({ url: '/pages/teacher/teacher-profile' })
  262. return
  263. }
  264. var userInfo = uni.getStorageSync('userInfo')
  265. this.nickname = this.$store.state.nickname || (userInfo && userInfo.nickname) || ''
  266. // 使用 currentRole(视图角色),而非 role(实际角色)
  267. this.role = currentRole
  268. this.hasPassword = !!uni.getStorageSync('passwordSet')
  269. // 获取切换标记
  270. this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
  271. this.loadChildren()
  272. this.loadShowToFamily()
  273. this.checkVendorStatus()
  274. this.loadEnergyData()
  275. this.loadBadges()
  276. this.loadActionData()
  277. this.loadWealthData()
  278. },
  279. methods: {
  280. goLogin() {
  281. uni.navigateTo({ url: '/pages/login/login' })
  282. },
  283. async loadChildren() {
  284. try {
  285. const res = await getChildren()
  286. this.children = res.data || []
  287. } catch (e) {
  288. console.error('获取孩子列表失败', e)
  289. }
  290. },
  291. async loadShowToFamily() {
  292. try {
  293. var uid = this.$store.state.userId || uni.getStorageSync('userId')
  294. var res = await getVisibleFamilyMembers()
  295. var members = res.data || []
  296. var self = members.find(function(m) { return m.id == uid })
  297. if (self) {
  298. this.showToFamily = self.showToFamily !== 0 ? 1 : 0
  299. }
  300. } catch (e) {
  301. console.error('获取可见性设置失败', e)
  302. }
  303. },
  304. goToEditInfo() {
  305. uni.navigateTo({ url: '/pages/user-edit/user-edit' })
  306. },
  307. goToChildren() {
  308. uni.navigateTo({ url: '/pages/profile/children' })
  309. },
  310. goToFamilyMembers() {
  311. uni.navigateTo({ url: '/pages/profile/family-members' })
  312. },
  313. goToPointsLogs() {
  314. uni.navigateTo({ url: '/pages/points/points' })
  315. },
  316. async onShowToFamilyChange(e) {
  317. var val = e.detail.value ? 1 : 0
  318. var uid = this.$store.state.userId || uni.getStorageSync('userId')
  319. try {
  320. await updateMemberVisibility(uid, 'parent', val)
  321. this.showToFamily = val
  322. uni.showToast({ title: val ? '已对家庭成员可见' : '已对家庭成员隐藏', icon: 'none' })
  323. } catch (e) {
  324. console.error('更新可见性失败', e)
  325. uni.showToast({ title: '操作失败', icon: 'none' })
  326. }
  327. },
  328. switchMode() {
  329. if (this.role === 'child') {
  330. this.verifyPasswordForSwitch(() => {
  331. this.doSwitchMode()
  332. })
  333. } else {
  334. this.doSwitchMode()
  335. }
  336. },
  337. doSwitchMode() {
  338. const newRole = this.role === 'parent' ? 'child' : 'parent'
  339. if (newRole === 'child') {
  340. if (this.children.length === 0) {
  341. this.loadChildren()
  342. }
  343. if (this.children.length === 0) {
  344. uni.showModal({
  345. title: '提示',
  346. content: '您还没有添加孩子,是否现在去添加?',
  347. success: (res) => {
  348. if (res.confirm) {
  349. uni.navigateTo({ url: '/pages/profile/create-child' })
  350. }
  351. }
  352. })
  353. return
  354. }
  355. if (this.children.length === 1) {
  356. this.$store.commit('switchToChild', this.children[0].id)
  357. this.role = newRole
  358. uni.showToast({ title: '已切换为孩子模式', icon: 'success' })
  359. return
  360. }
  361. uni.showActionSheet({
  362. itemList: this.children.map(c => c.nickname),
  363. success: (res) => {
  364. const selectedChild = this.children[res.tapIndex]
  365. this.$store.commit('switchToChild', selectedChild.id)
  366. this.role = newRole
  367. uni.showToast({ title: `已切换为${selectedChild.nickname}模式`, icon: 'success' })
  368. }
  369. })
  370. return
  371. }
  372. this.$store.commit('switchBackToParent')
  373. this.role = newRole
  374. uni.showToast({ title: `已切换为${newRole === 'parent' ? '家长' : '孩子'}模式`, icon: 'success' })
  375. },
  376. verifyPasswordForSwitch(callback) {
  377. uni.showModal({
  378. title: '验证密码',
  379. content: '请输入家长密码',
  380. editable: true,
  381. success: async (res) => {
  382. if (res.confirm && res.content) {
  383. try {
  384. const result = await verifyPassword(res.content)
  385. if (result.code === 200) {
  386. callback && callback()
  387. } else {
  388. uni.showToast({ title: '密码错误', icon: 'none' })
  389. }
  390. } catch (e) {
  391. uni.showToast({ title: '验证失败', icon: 'none' })
  392. }
  393. }
  394. }
  395. })
  396. },
  397. async savePassword() {
  398. if (this.newPassword.length !== 6) {
  399. uni.showToast({ title: '请输入6位密码', icon: 'none' })
  400. return
  401. }
  402. if (this.newPassword !== this.confirmPassword) {
  403. uni.showToast({ title: '两次密码不一致', icon: 'none' })
  404. return
  405. }
  406. if (this.hasPassword && this.oldPassword.length !== 6) {
  407. uni.showToast({ title: '请输入旧密码', icon: 'none' })
  408. return
  409. }
  410. try {
  411. if (this.hasPassword) {
  412. const verifyRes = await verifyPassword(this.oldPassword)
  413. if (verifyRes.code !== 200) {
  414. uni.showToast({ title: '旧密码错误', icon: 'none' })
  415. return
  416. }
  417. }
  418. await setPassword(this.newPassword)
  419. uni.setStorageSync('passwordSet', true)
  420. this.hasPassword = true
  421. uni.showToast({ title: '密码设置成功', icon: 'success' })
  422. this.showPasswordModal = false
  423. this.oldPassword = ''
  424. this.newPassword = ''
  425. this.confirmPassword = ''
  426. } catch (e) {
  427. console.error('设置密码失败', e)
  428. }
  429. },
  430. logout() {
  431. uni.showModal({
  432. title: '确认退出',
  433. content: '确定要退出登录吗?',
  434. success: (res) => {
  435. if (res.confirm) {
  436. this.$store.commit('logout')
  437. // 退出后返回首页 Tab(预登录状态,5 个底签可见)
  438. uni.reLaunch({ url: '/pages/index/index' })
  439. }
  440. }
  441. })
  442. },
  443. inviteFamily() {
  444. const userInfo = uni.getStorageSync('userInfo')
  445. const familyId = uni.getStorageSync('familyId')
  446. if (!familyId) {
  447. uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
  448. return
  449. }
  450. this.inviteFamilyGenerate(familyId, userInfo)
  451. },
  452. async inviteFamilyGenerate(familyId, userInfo) {
  453. try {
  454. const { generateInviteCard } = require('../../utils/api.js')
  455. const res = await generateInviteCard('family', familyId)
  456. const code = res.data.code
  457. this.shareData = {
  458. title: (userInfo && userInfo.nickname ? userInfo.nickname : '家人') + ' 邀请你加入家庭',
  459. path: '/pages/login/login?invite_code=' + code
  460. }
  461. uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
  462. } catch (e) {
  463. uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
  464. }
  465. },
  466. inviteTeacher() {
  467. const userInfo = uni.getStorageSync('userInfo')
  468. const familyId = uni.getStorageSync('familyId')
  469. if (!familyId) {
  470. uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
  471. return
  472. }
  473. this.inviteTeacherGenerate(familyId, userInfo)
  474. },
  475. async inviteTeacherGenerate(familyId, userInfo) {
  476. try {
  477. const { generateInviteCard } = require('../../utils/api.js')
  478. const res = await generateInviteCard('guide', familyId)
  479. const code = res.data.code
  480. this.shareData = {
  481. title: (userInfo && userInfo.nickname ? userInfo.nickname : '家长') + ' 邀请你成为家庭成长规划师',
  482. path: '/pages/login/login?invite_code=' + code
  483. }
  484. uni.showToast({ title: '点击右上角转发给规划师', icon: 'none' })
  485. } catch (e) {
  486. uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
  487. }
  488. },
  489. goToMembership() {
  490. uni.showToast({ title: '即将上线', icon: 'none' })
  491. },
  492. goToVendorCenter() {
  493. uni.navigateTo({ url: '/pages/vendor/center' })
  494. },
  495. goToPromotion() {
  496. uni.navigateTo({ url: '/pages/promotion/index' })
  497. },
  498. async loadEnergyData() {
  499. var childId = uni.getStorageSync('currentChildId') || null
  500. if (!childId) {
  501. // Try to get first child
  502. if (this.children.length > 0) {
  503. childId = this.children[0].id
  504. }
  505. }
  506. if (!childId) return
  507. try {
  508. var res = await getEnergyOverview(childId)
  509. if (res && res.data) {
  510. this.totalEnergy = res.data.totalEnergy || 0
  511. }
  512. } catch (e) { console.log('获取能量概览失败', e) }
  513. },
  514. loadBadges() {
  515. // Unlock based on stored stats
  516. var streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
  517. var totalPoints = parseInt(uni.getStorageSync('totalPoints') || 0)
  518. if (streakDays >= 7) this.badges[0].unlocked = true
  519. if (totalPoints >= 1000) this.badges[1].unlocked = true
  520. // Growth report mock
  521. this.growthReport.monthActiveDays = streakDays > 0 ? Math.min(streakDays, 30) : 0
  522. this.growthReport.totalTasks = parseInt(uni.getStorageSync('completedTasks') || 0)
  523. this.growthReport.completionRate = 85
  524. },
  525. async loadActionData() {
  526. try {
  527. const res = await getUserActionStats()
  528. if (res.code === 200 && res.data) {
  529. this.actionData.purchaseCount = res.data.purchaseCount || 0
  530. this.actionData.activityCount = res.data.activityCount || 0
  531. this.actionData.taskCount = res.data.taskCount || 0
  532. this.actionData.courseCount = res.data.courseCount || 0
  533. }
  534. } catch (e) {
  535. // Fallback to local computed stats
  536. this.actionData.taskCount = parseInt(uni.getStorageSync('completedTasks') || 0)
  537. console.log('获取行动统计失败,使用本地数据', e)
  538. }
  539. },
  540. async loadWealthData() {
  541. try {
  542. const codeRes = await getReferralCode()
  543. if (codeRes.data) {
  544. this.wealthData.referralCode = codeRes.data.referralCode || codeRes.data.code || ''
  545. }
  546. } catch (e) { console.log('获取邀请码失败', e) }
  547. try {
  548. const summaryRes = await getReferralSummary()
  549. if (summaryRes.data) {
  550. this.wealthData.totalEarnings = summaryRes.data.totalEarnings || '0.00'
  551. this.wealthData.referralCount = summaryRes.data.referredCount || summaryRes.data.totalCount || 0
  552. }
  553. } catch (e) { console.log('获取邀请统计失败', e) }
  554. try {
  555. const comRes = await getCommissionSummary()
  556. if (comRes.data) {
  557. this.wealthData.availableAmount = comRes.data.availableAmount || '0.00'
  558. }
  559. } catch (e) { console.log('获取佣金总览失败', e) }
  560. },
  561. copyReferralCode() {
  562. if (!this.wealthData.referralCode) return
  563. uni.setClipboardData({
  564. data: this.wealthData.referralCode,
  565. success: () => { uni.showToast({ title: '邀请码已复制', icon: 'success' }) }
  566. })
  567. },
  568. goAllBadges() {
  569. uni.showToast({ title: '即将上线', icon: 'none' })
  570. },
  571. goGrowthReport() {
  572. uni.showToast({ title: '即将上线', icon: 'none' })
  573. },
  574. async checkVendorStatus() {
  575. try {
  576. const res = await vendorStatus()
  577. this.isVendor = res.data && res.data.vendorStatus === 'approved'
  578. } catch (e) {
  579. this.isVendor = false
  580. }
  581. },
  582. exitSwitch() {
  583. if (!this.isSwitchedChild) return
  584. uni.showModal({
  585. title: '退出切换',
  586. content: '请输入家长密码以确认退出',
  587. editable: true,
  588. success: async (res) => {
  589. if (res.confirm && res.content) {
  590. try {
  591. const verifyResult = await verifyPassword(res.content)
  592. if (verifyResult.code === 200) {
  593. this.$store.commit('switchBackToParent')
  594. this.isSwitchedChild = false
  595. this.role = 'parent'
  596. uni.showToast({ title: '已退出切换', icon: 'success' })
  597. // 刷新首页
  598. uni.reLaunch({ url: '/pages/index/index' })
  599. } else {
  600. uni.showToast({ title: '密码错误', icon: 'none' })
  601. }
  602. } catch (e) {
  603. uni.showToast({ title: '验证失败', icon: 'none' })
  604. }
  605. }
  606. }
  607. })
  608. }
  609. }
  610. }
  611. </script>
  612. <style scoped>
  613. .container {
  614. min-height: 100vh;
  615. background: #f5f7fa;
  616. padding-bottom: 120rpx;
  617. }
  618. /* ===== 通用区块 ===== */
  619. .section {
  620. margin: 20rpx 30rpx;
  621. }
  622. .section-header {
  623. display: flex;
  624. justify-content: space-between;
  625. align-items: center;
  626. margin-bottom: 20rpx;
  627. }
  628. .section-title {
  629. font-size: 30rpx;
  630. font-weight: bold;
  631. color: #333;
  632. }
  633. .section-more {
  634. font-size: 24rpx;
  635. color: #999;
  636. }
  637. /* ===== 推广邀请码 ===== */
  638. .referral-code-bar {
  639. display: flex;
  640. align-items: center;
  641. background: #fff;
  642. border-radius: 16rpx;
  643. padding: 20rpx 24rpx;
  644. margin-top: 16rpx;
  645. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  646. }
  647. .referral-code-label {
  648. font-size: 26rpx;
  649. color: #999;
  650. margin-right: 16rpx;
  651. }
  652. .referral-code-value {
  653. flex: 1;
  654. font-size: 28rpx;
  655. font-weight: bold;
  656. color: #F97316;
  657. letter-spacing: 4rpx;
  658. }
  659. .referral-code-copy {
  660. font-size: 24rpx;
  661. color: #5B9BD5;
  662. padding: 4rpx 16rpx;
  663. border: 1rpx solid #5B9BD5;
  664. border-radius: 8rpx;
  665. }
  666. /* ===== 行动数据(推广收益) ===== */
  667. .wealth-card {
  668. background: #fff;
  669. border-radius: 20rpx;
  670. padding: 30rpx 20rpx;
  671. display: flex;
  672. align-items: center;
  673. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  674. }
  675. .wealth-item {
  676. flex: 1;
  677. display: flex;
  678. flex-direction: column;
  679. align-items: center;
  680. }
  681. .wealth-value {
  682. font-size: 40rpx;
  683. font-weight: bold;
  684. color: #F97316;
  685. }
  686. .wealth-label {
  687. font-size: 24rpx;
  688. color: #999;
  689. margin-top: 8rpx;
  690. }
  691. .wealth-divider {
  692. width: 1rpx;
  693. height: 60rpx;
  694. background: #f0f0f0;
  695. }
  696. /* ===== 成就徽章 ===== */
  697. .badge-grid {
  698. display: flex;
  699. background: #fff;
  700. border-radius: 20rpx;
  701. padding: 24rpx 16rpx;
  702. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  703. }
  704. .badge-item {
  705. flex: 1;
  706. display: flex;
  707. flex-direction: column;
  708. align-items: center;
  709. }
  710. .badge-icon-wrap {
  711. width: 72rpx;
  712. height: 72rpx;
  713. border-radius: 50%;
  714. display: flex;
  715. align-items: center;
  716. justify-content: center;
  717. margin-bottom: 8rpx;
  718. }
  719. .badge-icon-wrap.unlocked {
  720. background: linear-gradient(135deg, #FBBF24, #F97316);
  721. }
  722. .badge-icon-wrap.locked {
  723. background: #f0f0f0;
  724. opacity: 0.5;
  725. }
  726. .badge-icon {
  727. font-size: 36rpx;
  728. }
  729. .badge-name {
  730. font-size: 22rpx;
  731. color: #666;
  732. }
  733. /* ===== 成长报告 ===== */
  734. .report-card {
  735. background: #fff;
  736. border-radius: 20rpx;
  737. padding: 30rpx 20rpx;
  738. display: flex;
  739. align-items: center;
  740. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  741. }
  742. .report-stat-item {
  743. flex: 1;
  744. display: flex;
  745. flex-direction: column;
  746. align-items: center;
  747. }
  748. .report-stat {
  749. font-size: 36rpx;
  750. font-weight: bold;
  751. color: #5B9BD5;
  752. }
  753. .report-label {
  754. font-size: 22rpx;
  755. color: #999;
  756. margin-top: 6rpx;
  757. }
  758. .report-divider {
  759. width: 1rpx;
  760. height: 50rpx;
  761. background: #f0f0f0;
  762. }
  763. .user-card {
  764. background: linear-gradient(135deg, #5B9BD5 0%, #3A7CC4 100%);
  765. border-radius: 20rpx;
  766. padding: 40rpx;
  767. display: flex;
  768. align-items: center;
  769. color: #fff;
  770. }
  771. .avatar {
  772. font-size: 80rpx;
  773. margin-right: 30rpx;
  774. }
  775. .user-info {
  776. flex: 1;
  777. }
  778. .nickname {
  779. font-size: 36rpx;
  780. font-weight: bold;
  781. }
  782. .role {
  783. font-size: 24rpx;
  784. opacity: 0.8;
  785. }
  786. .system-points {
  787. font-size: 22rpx;
  788. color: #D6EAF8;
  789. margin-top: 8rpx;
  790. font-weight: 500;
  791. }
  792. .btn-switch {
  793. background: rgba(255, 255, 255, 0.2);
  794. color: #fff;
  795. font-size: 24rpx;
  796. padding: 10rpx 20rpx;
  797. border-radius: 20rpx;
  798. }
  799. .menu-list {
  800. margin-top: 30rpx;
  801. background: #fff;
  802. border-radius: 16rpx;
  803. }
  804. .menu-item {
  805. padding: 30rpx;
  806. border-bottom: 1rpx solid #f0f0f0;
  807. display: flex;
  808. justify-content: space-between;
  809. font-size: 28rpx;
  810. }
  811. .menu-item:last-child {
  812. border-bottom: none;
  813. }
  814. .arrow {
  815. color: #ccc;
  816. font-size: 32rpx;
  817. }
  818. .menu-item-btn {
  819. display: flex;
  820. justify-content: space-between;
  821. align-items: center;
  822. width: 100%;
  823. padding: 28rpx 24rpx;
  824. border: none;
  825. background: transparent;
  826. font-size: 28rpx;
  827. color: #333;
  828. }
  829. .menu-item-btn::after { border: none; }
  830. .menu-item-switch {
  831. align-items: center;
  832. }
  833. .modal-mask {
  834. position: fixed;
  835. top: 0;
  836. left: 0;
  837. right: 0;
  838. bottom: 0;
  839. background: rgba(0, 0, 0, 0.5);
  840. display: flex;
  841. align-items: center;
  842. justify-content: center;
  843. }
  844. .modal {
  845. background: #fff;
  846. border-radius: 20rpx;
  847. padding: 40rpx;
  848. width: 80%;
  849. }
  850. .modal-title {
  851. font-size: 32rpx;
  852. font-weight: bold;
  853. margin-bottom: 30rpx;
  854. text-align: center;
  855. }
  856. .form-item {
  857. margin-bottom: 20rpx;
  858. }
  859. .form-item input {
  860. border: 1rpx solid #ddd;
  861. border-radius: 10rpx;
  862. padding: 20rpx;
  863. font-size: 28rpx;
  864. }
  865. .modal-btns {
  866. display: flex;
  867. gap: 20rpx;
  868. margin-top: 30rpx;
  869. }
  870. .modal-btns button {
  871. flex: 1;
  872. }
  873. /* ===== 未登录提示 ===== */
  874. .login-prompt {
  875. display: flex;
  876. flex-direction: column;
  877. align-items: center;
  878. justify-content: center;
  879. min-height: 80vh;
  880. padding: 60rpx;
  881. }
  882. .prompt-icon {
  883. font-size: 120rpx;
  884. margin-bottom: 30rpx;
  885. width: 160rpx;
  886. height: 160rpx;
  887. line-height: 160rpx;
  888. text-align: center;
  889. background: #f5f5f5;
  890. border-radius: 50%;
  891. }
  892. .prompt-title {
  893. font-size: 36rpx;
  894. font-weight: bold;
  895. color: #333;
  896. margin-bottom: 16rpx;
  897. }
  898. .prompt-desc {
  899. font-size: 26rpx;
  900. color: #999;
  901. text-align: center;
  902. margin-bottom: 40rpx;
  903. }
  904. .login-prompt .login-btn {
  905. width: 60%;
  906. height: 80rpx;
  907. line-height: 80rpx;
  908. background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
  909. color: #fff;
  910. font-size: 30rpx;
  911. font-weight: bold;
  912. border-radius: 40rpx;
  913. text-align: center;
  914. border: none;
  915. }
  916. .login-prompt .login-btn::after {
  917. border: none;
  918. }
  919. </style>