form.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <template>
  2. <view class="form-page">
  3. <view class="section-card">
  4. <text class="section-title">报名信息</text>
  5. <text class="card-desc">填写联系方式与准备情况,缴费后由助教分配班次</text>
  6. <view class="summary-box" v-if="courseName">
  7. <view class="summary-row">
  8. <text class="summary-label">报名课程</text>
  9. <text class="summary-value">{{ courseName }}</text>
  10. </view>
  11. </view>
  12. <view class="form-item">
  13. <text class="form-label">姓名</text>
  14. <input class="form-input" v-model="form.name" placeholder="请输入真实姓名" />
  15. </view>
  16. <view class="form-item">
  17. <text class="form-label">手机号</text>
  18. <input class="form-input" v-model="form.phone" type="number" maxlength="11" placeholder="请输入手机号" />
  19. </view>
  20. <view class="form-item switch-item">
  21. <text class="form-label">是否携带笔记本</text>
  22. <switch :checked="form.bringLaptop === 1" color="#F97316" @change="onLaptopChange" />
  23. </view>
  24. <view class="form-item">
  25. <text class="form-label">已备数据</text>
  26. <view class="check-group">
  27. <view class="check-item" @click="toggleData('wealth')">
  28. <text class="check-square" :class="{ checked: dataReadyList.indexOf('wealth') > -1 }">✓</text>
  29. <text class="check-text">券商持仓</text>
  30. </view>
  31. <view class="check-item" @click="toggleData('health')">
  32. <text class="check-square" :class="{ checked: dataReadyList.indexOf('health') > -1 }">✓</text>
  33. <text class="check-text">体检/血压报告</text>
  34. </view>
  35. <view class="check-item" @click="toggleData('growth')">
  36. <text class="check-square" :class="{ checked: dataReadyList.indexOf('growth') > -1 }">✓</text>
  37. <text class="check-text">孩子成绩/日程</text>
  38. </view>
  39. </view>
  40. </view>
  41. <view class="form-item">
  42. <text class="form-label">最想解决的问题</text>
  43. <textarea class="form-textarea" v-model="form.topProblems" placeholder="如:如何用AI管理家庭资产、孩子学习计划…" />
  44. </view>
  45. <view class="form-item" v-if="form.inviteCode">
  46. <text class="form-label">邀请码</text>
  47. <input class="form-input" v-model="form.inviteCode" disabled placeholder="微信好友邀请码" />
  48. </view>
  49. <button class="submit-btn" @click="handleSubmit" :loading="loading" :disabled="loading">提交报名</button>
  50. </view>
  51. </view>
  52. </template>
  53. <script>
  54. import { createEnrollment } from '@/utils/api.js'
  55. import { guardPage } from '@/utils/guard.js'
  56. export default {
  57. data() {
  58. return {
  59. form: {
  60. courseId: '',
  61. name: '',
  62. phone: '',
  63. bringLaptop: 0,
  64. dataReady: '',
  65. topProblems: '',
  66. inviteCode: ''
  67. },
  68. courseName: '',
  69. dataReadyList: [],
  70. loading: false
  71. }
  72. },
  73. onLoad(options) {
  74. if (!guardPage()) return
  75. this.form.courseId = options && options.courseId ? options.courseId : ''
  76. this.courseName = options && options.courseName ? decodeURIComponent(options.courseName) : ''
  77. this.form.inviteCode = options && options.inviteCode ? decodeURIComponent(options.inviteCode) : ''
  78. this.form.name = options && options.name ? decodeURIComponent(options.name) : ''
  79. this.form.phone = options && options.phone ? decodeURIComponent(options.phone) : ''
  80. if (!this.form.courseId) {
  81. uni.showToast({ title: '参数错误', icon: 'none' })
  82. setTimeout(function() {
  83. uni.navigateBack({ delta: 1 })
  84. }, 1500)
  85. }
  86. },
  87. methods: {
  88. onLaptopChange(e) {
  89. this.form.bringLaptop = e.detail.value ? 1 : 0
  90. },
  91. toggleData(key) {
  92. var idx = this.dataReadyList.indexOf(key)
  93. if (idx > -1) {
  94. this.dataReadyList.splice(idx, 1)
  95. } else {
  96. this.dataReadyList.push(key)
  97. }
  98. this.form.dataReady = this.dataReadyList.join(',')
  99. },
  100. handleSubmit() {
  101. var name = (this.form.name || '').trim()
  102. var phone = (this.form.phone || '').trim()
  103. if (!name) {
  104. uni.showToast({ title: '请填写姓名', icon: 'none' })
  105. return
  106. }
  107. if (!/^1\d{10}$/.test(phone)) {
  108. uni.showToast({ title: '请填写正确的手机号', icon: 'none' })
  109. return
  110. }
  111. if (this.loading) return
  112. this.loading = true
  113. var self = this
  114. var payload = {
  115. courseId: self.form.courseId,
  116. name: name,
  117. phone: phone,
  118. bringLaptop: self.form.bringLaptop,
  119. dataReady: self.form.dataReady,
  120. topProblems: (self.form.topProblems || '').trim(),
  121. inviteCode: self.form.inviteCode
  122. }
  123. createEnrollment(payload).then(function(resp) {
  124. uni.showToast({ title: '报名成功', icon: 'success' })
  125. setTimeout(function() {
  126. var enrollmentId = resp.data && resp.data.id ? resp.data.id : ''
  127. if (enrollmentId) {
  128. // 跳转支付页(test-mode 下自动模拟支付成功)
  129. uni.redirectTo({ url: '/pages/pay/index?enrollmentId=' + enrollmentId })
  130. } else {
  131. uni.redirectTo({ url: '/pages/enroll/list' })
  132. }
  133. }, 1200)
  134. }).catch(function(err) {
  135. uni.showToast({ title: (err && err.message) || '报名失败', icon: 'none' })
  136. }).finally(function() {
  137. self.loading = false
  138. })
  139. }
  140. }
  141. }
  142. </script>
  143. <style scoped>
  144. .form-page { min-height: 100vh; background: #F5F5F5; padding: 32rpx; }
  145. .section-card { background: #FFF; border-radius: 16rpx; padding: 32rpx; }
  146. .section-title { display: block; font-size: 32rpx; font-weight: 700; color: #1E293B; margin-bottom: 8rpx; border-left: 8rpx solid #F97316; padding-left: 16rpx; }
  147. .card-desc { display: block; font-size: 26rpx; color: #64748B; margin-bottom: 32rpx; }
  148. .summary-box { background: #FFF7ED; border-radius: 8rpx; padding: 20rpx 24rpx; margin-bottom: 24rpx; border: 2rpx solid #FED7AA; }
  149. .summary-row { display: flex; align-items: center; margin-bottom: 8rpx; }
  150. .summary-row:last-child { margin-bottom: 0; }
  151. .summary-label { font-size: 24rpx; color: #64748B; width: 140rpx; flex-shrink: 0; }
  152. .summary-value { font-size: 26rpx; color: #1E293B; font-weight: 600; flex: 1; }
  153. .form-item { margin-bottom: 24rpx; }
  154. .form-label { display: block; font-size: 26rpx; color: #475569; font-weight: 500; margin-bottom: 12rpx; }
  155. .form-input { height: 72rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 0 16rpx; font-size: 28rpx; color: #1E293B; background: #F8FAFC; }
  156. .form-textarea { width: 100%; height: 160rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 16rpx; font-size: 28rpx; color: #1E293B; background: #F8FAFC; box-sizing: border-box; }
  157. .check-group { display: flex; flex-wrap: wrap; }
  158. .check-item { display: flex; align-items: center; width: 50%; margin-bottom: 16rpx; }
  159. .check-square { width: 36rpx; height: 36rpx; border: 2rpx solid #CBD5E1; border-radius: 6rpx; display: flex; align-items: center; justify-content: center; font-size: 24rpx; color: #FFF; margin-right: 12rpx; flex-shrink: 0; }
  160. .check-square.checked { background: #F97316; border-color: #F97316; }
  161. .check-text { font-size: 26rpx; color: #1E293B; }
  162. .switch-item { display: flex; align-items: center; justify-content: space-between; }
  163. .switch-item .form-label { margin-bottom: 0; }
  164. .submit-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: #F97316; color: #FFF; font-size: 30rpx; font-weight: 600; border-radius: 44rpx; border: none; margin-top: 24rpx; }
  165. .submit-btn:active { opacity: 0.85; }
  166. </style>