join-form.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <template>
  2. <view class="join-form-container">
  3. <view class="header">
  4. <text class="title">加入家庭</text>
  5. <text class="subtitle">请填写您的真实姓名和家庭身份,提交后等待管理员审批</text>
  6. </view>
  7. <view class="form-section">
  8. <!-- 真实姓名(必填) -->
  9. <view class="form-item">
  10. <text class="label">真实姓名 <text class="required">*</text></text>
  11. <input type="text" v-model="form.realName" placeholder="请输入真实姓名" class="input" maxlength="20" />
  12. </view>
  13. <!-- 家庭身份(必填) -->
  14. <view class="form-item">
  15. <text class="label">家庭身份 <text class="required">*</text></text>
  16. <picker mode="selector" :range="familyRoleOptions" :value="familyRoleIndex" @change="onFamilyRoleChange">
  17. <view class="picker-input">{{ form.familyRole || '请选择(如:爸爸、妈妈)' }}</view>
  18. </picker>
  19. </view>
  20. <!-- 昵称(选填,默认用户+手机尾号) -->
  21. <view class="form-item">
  22. <text class="label">昵称</text>
  23. <input type="text" v-model="form.nickname" placeholder="选填,默认:用户+手机尾号" class="input" maxlength="20" />
  24. </view>
  25. <!-- 头像(选填) -->
  26. <view class="form-item">
  27. <text class="label">头像</text>
  28. <view class="avatar-upload" @click="chooseAvatar">
  29. <image v-if="form.avatar" :src="form.avatar" class="avatar-preview"></image>
  30. <view v-else class="avatar-placeholder">
  31. <text class="plus-icon">+</text>
  32. </view>
  33. </view>
  34. </view>
  35. </view>
  36. <view class="actions">
  37. <button class="btn-primary" @click="submit" :loading="submitting" :disabled="submitting">
  38. <text v-if="submitting">提交中...</text>
  39. <text v-else>提交加入申请</text>
  40. </button>
  41. </view>
  42. </view>
  43. </template>
  44. <script>
  45. import { updateUserInfo, requestJoinByCode } from '@/utils/api.js'
  46. export default {
  47. data() {
  48. return {
  49. familyInviteCode: '',
  50. familyRoleOptions: ['爸爸', '妈妈', '爷爷', '奶奶', '姥爷', '姥姥', '其他'],
  51. form: {
  52. realName: '',
  53. familyRole: '',
  54. nickname: '',
  55. avatar: ''
  56. },
  57. submitting: false
  58. }
  59. },
  60. computed: {
  61. familyRoleIndex: {
  62. get() {
  63. return this.familyRoleOptions.indexOf(this.form.familyRole)
  64. },
  65. set(val) {
  66. this.form.familyRole = this.familyRoleOptions[val] || ''
  67. }
  68. }
  69. },
  70. onLoad(options) {
  71. // 接收家庭邀请码:join.vue 传 familyInviteCode;登录页衔接传 pendingFamilyInviteCode 对应值
  72. var code = (options && options.familyInviteCode) || uni.getStorageSync('pendingFamilyInviteCode') || ''
  73. this.familyInviteCode = code
  74. if (code) {
  75. uni.setStorageSync('pendingFamilyInviteCode', code)
  76. }
  77. // 已有资料预填(如有昵称/头像/真实姓名则带出)
  78. this.prefill()
  79. },
  80. methods: {
  81. prefill() {
  82. var userInfo = uni.getStorageSync('userInfo') || {}
  83. if (userInfo.realName) this.form.realName = userInfo.realName
  84. if (userInfo.nickname) this.form.nickname = userInfo.nickname
  85. if (userInfo.avatar) this.form.avatar = userInfo.avatar
  86. },
  87. chooseAvatar() {
  88. var self = this
  89. uni.chooseImage({
  90. count: 1,
  91. success: function(res) {
  92. self.form.avatar = res.tempFilePaths[0]
  93. }
  94. })
  95. },
  96. onFamilyRoleChange(e) {
  97. this.form.familyRole = this.familyRoleOptions[e.detail.value] || ''
  98. },
  99. async submit() {
  100. if (this.submitting) return
  101. if (!this.form.realName || !this.form.realName.trim()) {
  102. uni.showToast({ title: '请输入真实姓名', icon: 'none' })
  103. return
  104. }
  105. if (!this.form.familyRole) {
  106. uni.showToast({ title: '请选择家庭身份', icon: 'none' })
  107. return
  108. }
  109. if (!this.familyInviteCode) {
  110. uni.showToast({ title: '缺少家庭邀请码', icon: 'none' })
  111. return
  112. }
  113. this.submitting = true
  114. try {
  115. // 先保存基本信息(真实姓名/昵称/头像),失败不阻塞加入申请
  116. try {
  117. await updateUserInfo({
  118. realName: this.form.realName.trim(),
  119. nickname: this.form.nickname ? this.form.nickname.trim() : '',
  120. avatar: this.form.avatar
  121. })
  122. } catch (e) {
  123. console.log('[join-form] 保存基本信息失败,继续提交加入申请', e)
  124. }
  125. // 提交加入家庭申请
  126. await requestJoinByCode(this.familyInviteCode, this.form.familyRole)
  127. uni.removeStorageSync('pendingFamilyInviteCode')
  128. uni.showToast({ title: '加入家庭申请已提交,等待管理员审批', icon: 'success' })
  129. setTimeout(function() {
  130. uni.reLaunch({ url: '/pages/index-home/index' })
  131. }, 1500)
  132. } catch (e) {
  133. uni.showToast({ title: (e && e.message) || '提交失败', icon: 'none' })
  134. } finally {
  135. this.submitting = false
  136. }
  137. }
  138. }
  139. }
  140. </script>
  141. <style scoped>
  142. .join-form-container {
  143. min-height: 100vh;
  144. background: #f5f5f5;
  145. padding: 30rpx;
  146. box-sizing: border-box;
  147. }
  148. .header {
  149. margin-bottom: 40rpx;
  150. text-align: center;
  151. }
  152. .title {
  153. font-size: 40rpx;
  154. font-weight: bold;
  155. color: #333;
  156. display: block;
  157. margin-bottom: 10rpx;
  158. }
  159. .subtitle {
  160. font-size: 26rpx;
  161. color: #999;
  162. line-height: 1.6;
  163. display: block;
  164. }
  165. .form-section {
  166. background: #fff;
  167. border-radius: 16rpx;
  168. padding: 30rpx;
  169. margin-bottom: 30rpx;
  170. }
  171. .form-item {
  172. margin-bottom: 30rpx;
  173. }
  174. .form-item:last-child {
  175. margin-bottom: 0;
  176. }
  177. .label {
  178. display: block;
  179. font-size: 28rpx;
  180. color: #333;
  181. margin-bottom: 16rpx;
  182. }
  183. .required {
  184. color: #f44336;
  185. }
  186. .input, .picker-input {
  187. padding: 20rpx;
  188. background: #f5f5f5;
  189. border-radius: 12rpx;
  190. font-size: 28rpx;
  191. box-sizing: border-box;
  192. width: 100%;
  193. }
  194. .avatar-upload {
  195. width: 150rpx;
  196. height: 150rpx;
  197. }
  198. .avatar-preview {
  199. width: 150rpx;
  200. height: 150rpx;
  201. border-radius: 50%;
  202. }
  203. .avatar-placeholder {
  204. width: 150rpx;
  205. height: 150rpx;
  206. background: #f5f5f5;
  207. border-radius: 50%;
  208. display: flex;
  209. align-items: center;
  210. justify-content: center;
  211. }
  212. .plus-icon {
  213. font-size: 60rpx;
  214. color: #ccc;
  215. }
  216. .actions {
  217. padding-bottom: 40rpx;
  218. }
  219. .btn-primary {
  220. background: #4CAF50;
  221. color: #fff;
  222. border-radius: 40rpx;
  223. font-size: 30rpx;
  224. display: flex;
  225. align-items: center;
  226. justify-content: center;
  227. padding: 24rpx 0;
  228. }
  229. </style>