index.vue 28 KB

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