join-form.vue 6.1 KB

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