create.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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="form-item">
  7. <text class="form-label">班次</text>
  8. <picker mode="selector" :range="classNames" @change="onClassChange">
  9. <view class="picker-value" :class="{ placeholder: !classId }">
  10. {{ classId ? className : '请选择班次' }}
  11. </view>
  12. </picker>
  13. <view v-if="classId && unitAmount !== null" class="unit-tip">
  14. 单价 ¥{{ fenToYuan(unitAmount) }},共 {{ members.length }} 人,合计 ¥{{ fenToYuan(unitAmount * members.length) }}
  15. </view>
  16. </view>
  17. <view class="form-item">
  18. <text class="form-label">成员名单</text>
  19. <view class="member-item" v-for="(m, idx) in members" :key="idx">
  20. <input class="member-name" v-model="m.name" placeholder="姓名" />
  21. <input class="member-phone" v-model="m.phone" type="number" maxlength="11" placeholder="手机号" />
  22. <text class="member-del" v-if="members.length > 1" @click="removeMember(idx)">删除</text>
  23. </view>
  24. <text class="add-member" @click="addMember">+ 添加成员</text>
  25. </view>
  26. <view class="form-item">
  27. <text class="form-label">发票抬头(企业名称,选填)</text>
  28. <input class="form-input" v-model="invoice.invoiceTitle" placeholder="不填则不开票" />
  29. </view>
  30. <view class="form-item" v-if="invoice.invoiceTitle">
  31. <text class="form-label">税号</text>
  32. <input class="form-input" v-model="invoice.invoiceTaxNo" placeholder="统一社会信用代码" />
  33. </view>
  34. <view class="form-item" v-if="invoice.invoiceTitle">
  35. <text class="form-label">开票类型</text>
  36. <view class="check-group">
  37. <view class="check-item" @click="invoice.invoiceType = '普票'">
  38. <text class="check-square" :class="{ checked: invoice.invoiceType === '普票' }">✓</text>
  39. <text class="check-text">普票</text>
  40. </view>
  41. <view class="check-item" @click="invoice.invoiceType = '专票'">
  42. <text class="check-square" :class="{ checked: invoice.invoiceType === '专票' }">✓</text>
  43. <text class="check-text">专票</text>
  44. </view>
  45. </view>
  46. </view>
  47. <view class="form-item" v-if="invoice.invoiceTitle">
  48. <text class="form-label">接收邮箱(选填)</text>
  49. <input class="form-input" v-model="invoice.invoiceEmail" placeholder="电子发票接收邮箱" />
  50. </view>
  51. <view class="form-item" v-if="invoice.invoiceTitle">
  52. <text class="form-label">收票人电话(选填)</text>
  53. <input class="form-input" v-model="invoice.invoicePhone" type="number" maxlength="11" placeholder="收票联系电话" />
  54. </view>
  55. <button class="submit-btn" :loading="loading" :disabled="loading" @click="handleSubmit">提交团报</button>
  56. </view>
  57. </view>
  58. </template>
  59. <script>
  60. import { getEnrollClasses, createGroupOrder } from '@/utils/api.js'
  61. import { fenToYuan } from '@/utils/format.js'
  62. import { guardPage } from '@/utils/guard.js'
  63. export default {
  64. data() {
  65. return {
  66. classes: [],
  67. classNames: [],
  68. classId: '',
  69. className: '',
  70. unitAmount: null,
  71. members: [{ name: '', phone: '' }],
  72. invoice: {
  73. invoiceTitle: '',
  74. invoiceTaxNo: '',
  75. invoiceEmail: '',
  76. invoicePhone: '',
  77. invoiceType: '普票'
  78. },
  79. loading: false
  80. }
  81. },
  82. onLoad() {
  83. if (!guardPage()) return
  84. this.loadClasses()
  85. },
  86. methods: {
  87. fenToYuan: fenToYuan,
  88. loadClasses() {
  89. var self = this
  90. getEnrollClasses().then(function(resp) {
  91. self.classes = resp.data || []
  92. var names = []
  93. for (var i = 0; i < self.classes.length; i++) {
  94. names.push(self.classes[i].name || ('班次' + self.classes[i].id))
  95. }
  96. self.classNames = names
  97. }).catch(function() {
  98. uni.showToast({ title: '加载班次失败', icon: 'none' })
  99. })
  100. },
  101. onClassChange(e) {
  102. var idx = Number(e.detail.value)
  103. var item = this.classes[idx]
  104. if (!item) return
  105. this.classId = item.id
  106. this.className = item.name
  107. // 团报按会员价计价(与后端 create 一致)
  108. var price = item.memberPrice > 0 && item.memberPrice < item.price ? item.memberPrice : item.price
  109. this.unitAmount = price
  110. },
  111. addMember() {
  112. this.members.push({ name: '', phone: '' })
  113. },
  114. removeMember(idx) {
  115. this.members.splice(idx, 1)
  116. },
  117. validatePhone(phone) {
  118. return /^1\d{10}$/.test(phone)
  119. },
  120. handleSubmit() {
  121. var self = this
  122. if (!self.classId) {
  123. uni.showToast({ title: '请选择班次', icon: 'none' })
  124. return
  125. }
  126. var memberList = []
  127. for (var i = 0; i < self.members.length; i++) {
  128. var name = (self.members[i].name || '').trim()
  129. var phone = (self.members[i].phone || '').trim()
  130. if (!name || !phone) {
  131. uni.showToast({ title: '请填写第 ' + (i + 1) + ' 位成员的姓名和手机号', icon: 'none' })
  132. return
  133. }
  134. if (!self.validatePhone(phone)) {
  135. uni.showToast({ title: '第 ' + (i + 1) + ' 位成员手机号格式不正确', icon: 'none' })
  136. return
  137. }
  138. memberList.push({ name: name, phone: phone })
  139. }
  140. if (self.invoice.invoiceTitle && (self.invoice.invoiceTitle || '').trim()) {
  141. if (!(self.invoice.invoiceTaxNo || '').trim()) {
  142. uni.showToast({ title: '填写发票抬头后请一并填写税号', icon: 'none' })
  143. return
  144. }
  145. }
  146. if (self.loading) return
  147. self.loading = true
  148. var payload = {
  149. classId: self.classId,
  150. members: memberList,
  151. invoiceTitle: (self.invoice.invoiceTitle || '').trim(),
  152. invoiceTaxNo: (self.invoice.invoiceTaxNo || '').trim(),
  153. invoiceEmail: (self.invoice.invoiceEmail || '').trim(),
  154. invoicePhone: (self.invoice.invoicePhone || '').trim(),
  155. invoiceType: self.invoice.invoiceType
  156. }
  157. createGroupOrder(payload).then(function(resp) {
  158. var id = resp.data && resp.data.id ? resp.data.id : ''
  159. uni.showToast({ title: '团报单已创建', icon: 'success' })
  160. setTimeout(function() {
  161. if (id) {
  162. uni.redirectTo({ url: '/pages/group-order/detail?id=' + id })
  163. } else {
  164. uni.redirectTo({ url: '/pages/group-order/index' })
  165. }
  166. }, 1200)
  167. }).catch(function(err) {
  168. uni.showToast({ title: (err && err.message) || '提交失败', icon: 'none' })
  169. }).finally(function() {
  170. self.loading = false
  171. })
  172. }
  173. }
  174. }
  175. </script>
  176. <style scoped>
  177. .form-page { min-height: 100vh; background: #F5F5F5; padding: 32rpx; }
  178. .section-card { background: #FFF; border-radius: 16rpx; padding: 32rpx; }
  179. .section-title { display: block; font-size: 32rpx; font-weight: 700; color: #1E293B; margin-bottom: 8rpx; border-left: 8rpx solid #F97316; padding-left: 16rpx; }
  180. .card-desc { display: block; font-size: 26rpx; color: #64748B; margin-bottom: 32rpx; }
  181. .form-item { margin-bottom: 24rpx; }
  182. .form-label { display: block; font-size: 26rpx; color: #475569; font-weight: 500; margin-bottom: 12rpx; }
  183. .form-input { height: 72rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 0 16rpx; font-size: 28rpx; color: #1E293B; background: #F8FAFC; }
  184. .picker-value { height: 72rpx; line-height: 72rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 0 16rpx; font-size: 28rpx; color: #1E293B; background: #F8FAFC; }
  185. .picker-value.placeholder { color: #94A3B8; }
  186. .unit-tip { font-size: 24rpx; color: #F97316; margin-top: 12rpx; }
  187. .member-item { display: flex; align-items: center; margin-bottom: 16rpx; }
  188. .member-name { width: 200rpx; height: 64rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 0 12rpx; font-size: 26rpx; background: #F8FAFC; margin-right: 12rpx; }
  189. .member-phone { flex: 1; height: 64rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 0 12rpx; font-size: 26rpx; background: #F8FAFC; margin-right: 12rpx; }
  190. .member-del { font-size: 24rpx; color: #EF4444; padding: 8rpx; }
  191. .add-member { display: block; font-size: 26rpx; color: #F97316; font-weight: 600; margin-top: 8rpx; }
  192. .check-group { display: flex; }
  193. .check-item { display: flex; align-items: center; margin-right: 40rpx; }
  194. .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; }
  195. .check-square.checked { background: #F97316; border-color: #F97316; }
  196. .check-text { font-size: 26rpx; color: #1E293B; }
  197. .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; }
  198. .submit-btn:active { opacity: 0.85; }
  199. </style>