mine.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <template>
  2. <view class="mine-page">
  3. <view class="section-card">
  4. <text class="section-title">我的沙龙</text>
  5. <text class="card-desc">查看报名、现场签到与会后反馈</text>
  6. <view v-if="loading" class="empty-tip">加载中…</view>
  7. <view v-else-if="list.length === 0" class="empty-tip">暂无报名记录</view>
  8. <view v-else>
  9. <view class="salon-card" v-for="item in list" :key="item.enrollmentId">
  10. <view class="salon-head">
  11. <text class="salon-theme">{{ mapTheme(item.theme) }}</text>
  12. <view class="head-right">
  13. <text class="salon-state" :class="stateClass(item.displayStatus)">{{ stateText(item.displayStatus) }}</text>
  14. <text class="salon-status" v-if="item.checkedIn">已签到</text>
  15. </view>
  16. </view>
  17. <text class="salon-title">{{ item.title }}</text>
  18. <view class="salon-info">
  19. <view class="info-item">
  20. <text class="info-label">时间:</text>
  21. <text class="info-value">{{ item.time }}</text>
  22. </view>
  23. <view class="info-item">
  24. <text class="info-label">地点:</text>
  25. <text class="info-value">{{ item.place }}</text>
  26. </view>
  27. <view class="info-item">
  28. <text class="info-label">报名:</text>
  29. <text class="info-value">{{ statusText(item.status) }}</text>
  30. </view>
  31. </view>
  32. <view class="salon-foot">
  33. <button
  34. v-if="canCheckin(item)"
  35. class="mini-btn primary"
  36. :loading="checking"
  37. :disabled="checking"
  38. @click="handleCheckin(item)"
  39. >现场签到</button>
  40. <text v-else-if="item.checkedIn" class="foot-tip">已完成签到</text>
  41. <button
  42. v-if="canFeedback(item)"
  43. class="mini-btn plain"
  44. :disabled="submitting"
  45. @click="goFeedback(item)"
  46. >{{ item.feedbackSubmitted ? '查看反馈' : '去反馈' }}</button>
  47. <button
  48. v-if="isUnpaid(item)"
  49. class="mini-btn plain"
  50. @click="goPay(item)"
  51. >去支付</button>
  52. </view>
  53. </view>
  54. </view>
  55. </view>
  56. </view>
  57. </template>
  58. <script>
  59. import { getMySalons, salonCheckin } from '@/utils/api.js'
  60. import { guardPage } from '@/utils/guard.js'
  61. export default {
  62. data() {
  63. return {
  64. list: [],
  65. loading: false,
  66. checking: false,
  67. submitting: false,
  68. themeMap: {
  69. ai_basic: 'AI 认知基础',
  70. wealth: '家庭财富管理',
  71. health: '健康管理',
  72. growth: '孩子成长陪伴'
  73. }
  74. }
  75. },
  76. onShow() {
  77. if (!guardPage()) return
  78. this.loadMine()
  79. },
  80. methods: {
  81. mapTheme: function(theme) {
  82. return this.themeMap[theme] || theme || '通用主题'
  83. },
  84. statusText: function(status) {
  85. var map = {
  86. pending: '待支付',
  87. paid: '已支付',
  88. confirmed: '已确认',
  89. rejected: '已拒绝',
  90. cancelled: '已取消'
  91. }
  92. return map[status] || status || ''
  93. },
  94. stateText: function(s) {
  95. var map = { ended: '已结束', ongoing: '进行中', pending_start: '未开始', active: '进行中', draft: '草稿' }
  96. return map[s] || s || ''
  97. },
  98. stateClass: function(s) {
  99. if (s === 'ended' || s === 'draft') return 'state-end'
  100. if (s === 'pending_start') return 'state-wait'
  101. return 'state-go'
  102. },
  103. canCheckin: function(item) {
  104. // 已支付/已确认 + 时间窗内(displayStatus=ongoing)+ 未签到
  105. var inTime = item.displayStatus === 'ongoing'
  106. if (!inTime && !item.displayStatus) { inTime = item.salonStatus === 'active' }
  107. return (item.status === 'paid' || item.status === 'confirmed') && inTime && !item.checkedIn
  108. },
  109. canFeedback: function(item) {
  110. // 已支付/已确认即可反馈(已提交则入口改为查看)
  111. return item.status === 'paid' || item.status === 'confirmed'
  112. },
  113. isUnpaid: function(item) {
  114. return item.status === 'pending' && item.orderNo
  115. },
  116. loadMine: function() {
  117. var self = this
  118. self.loading = true
  119. getMySalons().then(function(resp) {
  120. self.list = resp.data || []
  121. }).catch(function() {
  122. uni.showToast({ title: '加载失败', icon: 'none' })
  123. }).finally(function() {
  124. self.loading = false
  125. })
  126. },
  127. handleCheckin: function(item) {
  128. var self = this
  129. if (self.checking) return
  130. self.checking = true
  131. salonCheckin(item.salonId).then(function(resp) {
  132. var data = resp && resp.data ? resp.data : {}
  133. var pts = data.pointsEarned || 0
  134. var msg = pts > 0 ? '签到成功,获得 ' + pts + ' 积分' : '签到成功'
  135. uni.showToast({ title: msg, icon: 'success' })
  136. item.checkedIn = true
  137. self.loadMine()
  138. }).catch(function(err) {
  139. uni.showToast({ title: (err && err.message) || '签到失败', icon: 'none' })
  140. }).finally(function() {
  141. self.checking = false
  142. })
  143. },
  144. goFeedback: function(item) {
  145. uni.navigateTo({
  146. url: '/pages/salon/feedback?salonId=' + item.salonId +
  147. '&title=' + encodeURIComponent(item.title)
  148. })
  149. },
  150. goPay: function(item) {
  151. uni.navigateTo({
  152. url: '/pages/pay/index?enrollmentId=' + item.enrollmentId
  153. })
  154. }
  155. }
  156. }
  157. </script>
  158. <style scoped>
  159. .mine-page { min-height: 100vh; background: #F5F5F5; padding: 32rpx; }
  160. .section-card { background: #FFF; border-radius: 16rpx; padding: 32rpx; }
  161. .section-title { display: block; font-size: 32rpx; font-weight: 700; color: #1E293B; margin-bottom: 8rpx; border-left: 8rpx solid #F97316; padding-left: 16rpx; }
  162. .card-desc { display: block; font-size: 26rpx; color: #64748B; margin-bottom: 24rpx; }
  163. .empty-tip { font-size: 26rpx; color: #94A3B8; padding: 32rpx 0; text-align: center; }
  164. .salon-card { border: 2rpx solid #E2E8F0; border-radius: 12rpx; padding: 24rpx; margin-bottom: 20rpx; }
  165. .salon-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8rpx; }
  166. .head-right { display: flex; align-items: center; gap: 12rpx; }
  167. .salon-state { font-size: 22rpx; font-weight: 600; padding: 2rpx 12rpx; border-radius: 4rpx; }
  168. .state-go { background: #DCFCE7; color: #16A34A; }
  169. .state-wait { background: #FFF7ED; color: #F97316; }
  170. .state-end { background: #F1F5F9; color: #94A3B8; }
  171. .salon-theme { display: block; background: #FFF7ED; color: #F97316; font-size: 22rpx; font-weight: 600; padding: 4rpx 12rpx; border-radius: 4rpx; }
  172. .salon-status { font-size: 22rpx; color: #22C55E; font-weight: 600; }
  173. .salon-title { display: block; font-size: 30rpx; font-weight: 600; color: #1E293B; margin-bottom: 12rpx; }
  174. .salon-info { margin-bottom: 16rpx; }
  175. .info-item { display: flex; align-items: center; margin-bottom: 4rpx; }
  176. .info-label { font-size: 24rpx; color: #64748B; width: 70rpx; flex-shrink: 0; }
  177. .info-value { font-size: 26rpx; color: #475569; }
  178. .salon-foot { display: flex; align-items: center; justify-content: flex-end; gap: 16rpx; }
  179. .mini-btn { height: 60rpx; line-height: 60rpx; font-size: 26rpx; border-radius: 30rpx; border: none; margin: 0; padding: 0 32rpx; }
  180. .mini-btn.primary { background: #F97316; color: #FFF; }
  181. .mini-btn.plain { background: #FFF; color: #F97316; border: 2rpx solid #F97316; }
  182. .mini-btn:active { opacity: 0.85; }
  183. .foot-tip { font-size: 24rpx; color: #94A3B8; }
  184. </style>