index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <template>
  2. <view class="page">
  3. <view class="nav-bar">
  4. <view class="nav-back" @tap="goBack">
  5. <text class="back-text">‹ 返回</text>
  6. </view>
  7. </view>
  8. <scroll-view class="content" scroll-y>
  9. <!-- 头部横幅 -->
  10. <view class="hero">
  11. <text class="hero-title">健康启航计划</text>
  12. <text class="hero-desc">199元解锁全年健康管理</text>
  13. <view class="hero-price">
  14. <text class="price-original">¥199</text>
  15. <text class="price-note">7天全功能体验</text>
  16. </view>
  17. </view>
  18. <!-- 权益对比 -->
  19. <view class="card">
  20. <view class="card-header">
  21. <text class="card-title">您的权益</text>
  22. </view>
  23. <view class="benefit-list">
  24. <view class="benefit-item" v-for="(item, idx) in benefits" :key="idx">
  25. <text class="benefit-icon">{{ item.icon }}</text>
  26. <text class="benefit-text">{{ item.text }}</text>
  27. <text class="benefit-status" :class="item.active ? 'active' : 'inactive'">{{ item.active ? '✓ 已包含' : '✗ 需升级' }}</text>
  28. </view>
  29. </view>
  30. </view>
  31. <!-- CF奖励计算器 -->
  32. <view class="card">
  33. <view class="card-header">
  34. <text class="card-title">打卡返CF值</text>
  35. <text class="card-subtitle">完成任务最高可得19,900 CF = 199元</text>
  36. </view>
  37. <view class="cf-calculator">
  38. <view class="cf-row">
  39. <text class="cf-label">基础奖励</text>
  40. <text class="cf-value">300 CF</text>
  41. </view>
  42. <view class="cf-row">
  43. <text class="cf-label">连续打卡加成</text>
  44. <text class="cf-value">+420 CF</text>
  45. </view>
  46. <view class="cf-row">
  47. <text class="cf-label">个性化任务</text>
  48. <text class="cf-value">+180 CF</text>
  49. </view>
  50. <view class="cf-divider"></view>
  51. <view class="cf-total">
  52. <text class="cf-total-label">累计可得</text>
  53. <text class="cf-total-value">19,900 CF</text>
  54. </view>
  55. <text class="cf-equivalent">= ¥199 全额返还</text>
  56. </view>
  57. </view>
  58. <!-- 21天挑战任务 -->
  59. <view class="card">
  60. <view class="card-header">
  61. <text class="card-title">21天挑战</text>
  62. </view>
  63. <view class="challenge-preview">
  64. <view class="task-item" v-for="(task, idx) in challengeTasks" :key="idx">
  65. <text class="task-num">{{ idx + 1 }}</text>
  66. <text class="task-content">{{ task }}</text>
  67. </view>
  68. </view>
  69. </view>
  70. <!-- 购买按钮 -->
  71. <view class="purchase-wrap">
  72. <view class="purchase-btn" :class="{ 'loading': submitting }" @tap="handlePurchase">
  73. <text class="btn-text">{{ submitting ? '处理中...' : '立即开启体验' }}</text>
  74. </view>
  75. <text class="purchase-tip">7天内可随时取消,首周打卡可获199元CF返还</text>
  76. </view>
  77. </scroll-view>
  78. </view>
  79. </template>
  80. <script>
  81. import { checkPurchase, createPurchaseOrder, getPurchasePayParams } from '../../utils/api.js'
  82. import { requestVirtualPayment } from '../../utils/api.js'
  83. export default {
  84. data() {
  85. return {
  86. submitting: false,
  87. benefits: [
  88. { icon: '📋', text: '无限报告上传', active: true },
  89. { icon: '🤖', text: 'AI报告解析', active: true },
  90. { icon: '📝', text: '每日方案生成', active: true },
  91. { icon: '🏆', text: '21天挑战', active: true },
  92. { icon: '💰', text: 'CF值奖励', active: true },
  93. { icon: '👨‍👩‍👧', text: '家庭会员权益', active: false }
  94. ],
  95. challengeTasks: [
  96. '完成方案制定',
  97. '根据方案践行任务',
  98. '任务完成率不低于70%',
  99. '21天连续打卡',
  100. '上传其他报告'
  101. ]
  102. }
  103. },
  104. onLoad() {
  105. this.checkStatus()
  106. },
  107. methods: {
  108. goBack() { uni.navigateBack() },
  109. async checkStatus() {
  110. try {
  111. var res = await checkPurchase()
  112. if (res.data && res.data.hasPurchased && res.data.isActive) {
  113. // 从方案制定页来购买:返回方案页,由 onShow 自动继续生成
  114. if (uni.getStorageSync('PLAN_RESUME_AFTER_PURCHASE') === '1') {
  115. uni.navigateBack()
  116. return
  117. }
  118. // 已有有效购买,跳转闯关进度
  119. uni.redirectTo({ url: '/pages/growth/challenge-progress' })
  120. }
  121. } catch (e) {
  122. console.log('检查购买状态失败', e)
  123. }
  124. },
  125. handlePurchase() {
  126. var self = this
  127. self.submitting = true
  128. createPurchaseOrder().then(function(res) {
  129. if (res.data && res.data.success && res.data.orderNo) {
  130. var orderNo = res.data.orderNo
  131. // 获取虚拟支付参数并拉起支付
  132. getPurchasePayParams(orderNo).then(function(payRes) {
  133. var dto = payRes.data || {}
  134. if (dto.orderInfo && dto.sign) {
  135. requestVirtualPayment(dto, {
  136. success: function() {
  137. self.submitting = false
  138. uni.showToast({ title: '支付成功,开通中…', icon: 'none' })
  139. setTimeout(function() {
  140. if (uni.getStorageSync('PLAN_RESUME_AFTER_PURCHASE') === '1') {
  141. // 从方案制定页来购买:返回方案页,由 onShow 自动继续生成
  142. uni.navigateBack()
  143. } else {
  144. uni.redirectTo({ url: '/pages/growth/challenge-progress' })
  145. }
  146. }, 800)
  147. },
  148. fail: function(err) {
  149. self.submitting = false
  150. var errCode = err && err.errCode
  151. var msg = '支付取消'
  152. if (errCode === -15007 || errCode === -15005) msg = '登录态已过期,请重新登录'
  153. else if (err && err.errMsg) msg = err.errMsg
  154. uni.showToast({ title: msg, icon: 'none' })
  155. }
  156. })
  157. } else {
  158. self.submitting = false
  159. uni.showToast({ title: payRes.message || '支付参数获取失败', icon: 'none' })
  160. }
  161. }).catch(function(err) {
  162. self.submitting = false
  163. if (err && err.code === 46001) {
  164. uni.showToast({ title: '该商品暂未开放购买', icon: 'none' })
  165. } else {
  166. uni.showToast({ title: (err && err.message) || '支付参数获取失败', icon: 'none' })
  167. }
  168. })
  169. } else {
  170. self.submitting = false
  171. uni.showToast({ title: (res.data && res.data.message) || '创建订单失败', icon: 'none' })
  172. }
  173. }).catch(function(err) {
  174. self.submitting = false
  175. uni.showToast({ title: '创建订单失败', icon: 'none' })
  176. })
  177. }
  178. }
  179. }
  180. </script>
  181. <style scoped>
  182. .page { min-height: 100vh; background: #FFF7ED; }
  183. .nav-bar { display: flex; align-items: center; height: 88rpx; padding-top: env(safe-area-inset-top); background: #fff; border-bottom: 1rpx solid #f0f0f0; position: relative; }
  184. .nav-back { position: absolute; left: 24rpx; top: 0; bottom: 0; display: flex; align-items: center; }
  185. .back-text { font-size: 28rpx; color: #F97316; }
  186. .content { height: calc(100vh - 88rpx); }
  187. .hero { background: linear-gradient(135deg, #FF8C42, #F97316); padding: 40rpx 32rpx; text-align: center; }
  188. .hero-title { font-size: 40rpx; font-weight: bold; color: #fff; display: block; }
  189. .hero-desc { font-size: 26rpx; color: rgba(255,255,255,0.9); margin-top: 8rpx; display: block; }
  190. .hero-price { margin-top: 24rpx; }
  191. .price-original { font-size: 56rpx; font-weight: bold; color: #fff; }
  192. .price-note { font-size: 22rpx; color: rgba(255,255,255,0.8); margin-left: 12rpx; }
  193. .card { margin: 16rpx 24rpx; background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
  194. .card-header { padding: 24rpx 24rpx 16rpx; border-bottom: 1rpx solid #f5f5f5; }
  195. .card-title { font-size: 28rpx; font-weight: bold; color: #333; }
  196. .card-subtitle { font-size: 22rpx; color: #999; margin-left: 12rpx; }
  197. .benefit-list { padding: 16rpx 24rpx; }
  198. .benefit-item { display: flex; align-items: center; gap: 16rpx; padding: 16rpx 0; border-bottom: 1rpx solid #f5f5f5; }
  199. .benefit-item:last-child { border-bottom: none; }
  200. .benefit-icon { font-size: 36rpx; }
  201. .benefit-text { flex: 1; font-size: 26rpx; color: #333; }
  202. .benefit-status { font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 12rpx; }
  203. .benefit-status.active { background: #E8F5E9; color: #4CAF50; }
  204. .benefit-status.inactive { background: #F5F5F5; color: #999; }
  205. .cf-calculator { padding: 16rpx 24rpx; }
  206. .cf-row { display: flex; justify-content: space-between; padding: 8rpx 0; }
  207. .cf-label { font-size: 24rpx; color: #666; }
  208. .cf-value { font-size: 24rpx; color: #F97316; font-weight: bold; }
  209. .cf-divider { height: 1rpx; background: #e0e0e0; margin: 12rpx 0; }
  210. .cf-total { display: flex; justify-content: space-between; padding: 8rpx 0; }
  211. .cf-total-label { font-size: 26rpx; color: #333; font-weight: bold; }
  212. .cf-total-value { font-size: 32rpx; color: #F97316; font-weight: bold; }
  213. .cf-equivalent { font-size: 22rpx; color: #999; text-align: center; margin-top: 8rpx; display: block; }
  214. .challenge-preview { padding: 16rpx 24rpx; }
  215. .task-item { display: flex; align-items: center; gap: 16rpx; padding: 16rpx 0; border-bottom: 1rpx solid #f5f5f5; }
  216. .task-item:last-child { border-bottom: none; }
  217. .task-num { width: 40rpx; height: 40rpx; line-height: 40rpx; text-align: center; border-radius: 50%; background: #FFF3E0; color: #F97316; font-size: 22rpx; font-weight: bold; flex-shrink: 0; }
  218. .task-content { flex: 1; font-size: 26rpx; color: #333; }
  219. .purchase-wrap { padding: 32rpx 24rpx 40rpx; text-align: center; }
  220. .purchase-btn { background: linear-gradient(135deg, #FF8C42, #F97316); color: #fff; text-align: center; padding: 28rpx 0; border-radius: 16rpx; font-size: 32rpx; font-weight: bold; }
  221. .purchase-btn.loading { opacity: 0.6; }
  222. .purchase-tip { font-size: 22rpx; color: #999; margin-top: 16rpx; display: block; }
  223. </style>