complete-info.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <CustomNav title="完善信息" leftType="back" />
  3. <view class="complete-container">
  4. <view class="form-section">
  5. <view class="avatar-section">
  6. <view class="avatar">
  7. <button
  8. class="avatar-wrapper"
  9. :class="{ disabled: isChoosing }"
  10. :disabled="isChoosing"
  11. open-type="chooseAvatar"
  12. @tap="startChooseAvatar"
  13. @chooseavatar="onChooseAvatar"
  14. >
  15. <view class="avatar-frame">
  16. <image v-if="form.avatar" class="avatar-img" :src="form.avatar" mode="aspectFill" />
  17. <text v-else class="avatar-placeholder">点击选择头像</text>
  18. </view>
  19. </button>
  20. </view>
  21. </view>
  22. <view class="form-item">
  23. <text class="label">姓名</text>
  24. <input class="input" type="nickname" v-model="form.nickname" placeholder="请输入姓名" />
  25. </view>
  26. <view class="form-item">
  27. <text class="label">手机号</text>
  28. <input class="input" v-model="form.phone" placeholder="请输入手机号" type="number" />
  29. </view>
  30. <view class="form-item">
  31. <text class="label">年龄</text>
  32. <input class="input" v-model="form.age" placeholder="请输入年龄" type="number" />
  33. </view>
  34. <view class="form-item">
  35. <text class="label">性别</text>
  36. <view class="radio-group">
  37. <label class="radio-item" @click="form.sex = 1">
  38. <radio :checked="form.sex === 1" color="#07C160" />
  39. <text>男</text>
  40. </label>
  41. <label class="radio-item" @click="form.sex = 2">
  42. <radio :checked="form.sex === 2" color="#07C160" />
  43. <text>女</text>
  44. </label>
  45. </view>
  46. </view>
  47. <view class="form-item">
  48. <text class="label">省/市</text>
  49. <view class="location-section">
  50. <picker mode="region" :value="region" @change="onRegionChange">
  51. <view class="picker">{{ region.join(' ') || '请选择省/市' }}</view>
  52. </picker>
  53. <button class="get-location-btn" @click="getCurrentLocation" :disabled="gettingLocation">
  54. {{ gettingLocation ? '获取中...' : '自动获取' }}
  55. </button>
  56. </view>
  57. </view>
  58. <view class="submit-section">
  59. <button class="submit-btn" @click="onSubmit" :disabled="submitting">{{ submitting ? '提交中...' : '提交' }}</button>
  60. </view>
  61. </view>
  62. </view>
  63. </template>
  64. <script setup lang="ts">
  65. import { ref } from 'vue'
  66. import CustomNav from '@/components/CustomNav.vue'
  67. const form = ref({
  68. avatar: '',
  69. nickname: '',
  70. phone: '',
  71. age: '',
  72. sex: 0,
  73. address: ''
  74. })
  75. const region = ref<string[]>([])
  76. const submitting = ref(false)
  77. const isChoosing = ref(false)
  78. const gettingLocation = ref(false)
  79. const onChooseAvatar = (e: any) => {
  80. console.log('onChooseAvatar called with event:', e)
  81. try {
  82. const detail = e?.detail
  83. console.log('Event detail:', detail)
  84. let url = ''
  85. if (!detail) {
  86. url = typeof e === 'string' ? e : ''
  87. console.log('No detail, using event as string:', url)
  88. } else if (typeof detail === 'string') {
  89. url = detail
  90. console.log('Detail is string:', url)
  91. } else if (detail.avatarUrl) {
  92. url = detail.avatarUrl
  93. console.log('Using detail.avatarUrl:', url)
  94. } else if (Array.isArray(detail) && detail[0]) {
  95. url = detail[0]
  96. console.log('Using first element of array:', url)
  97. }
  98. if (url) {
  99. form.value.avatar = url
  100. console.log('Avatar URL updated to:', form.value.avatar)
  101. }
  102. if (detail && detail.nickName && !form.value.nickname) {
  103. form.value.nickname = detail.nickName
  104. console.log('Nickname updated from detail:', form.value.nickname)
  105. }
  106. } catch (err) {
  107. console.error('Error in onChooseAvatar:', err)
  108. } finally {
  109. isChoosing.value = false
  110. console.log('isChoosing reset to false')
  111. }
  112. }
  113. const startChooseAvatar = () => {
  114. console.log('startChooseAvatar called, current isChoosing:', isChoosing.value)
  115. if (isChoosing.value) {
  116. console.log('Already choosing, ignoring')
  117. return
  118. }
  119. isChoosing.value = true
  120. console.log('isChoosing set to true')
  121. setTimeout(() => {
  122. isChoosing.value = false
  123. console.log('Timeout: isChoosing reset to false')
  124. }, 3000)
  125. }
  126. const onRegionChange = (e: any) => {
  127. region.value = e.detail.value
  128. form.value.address = region.value.join(' ')
  129. }
  130. const getCurrentLocation = async () => {
  131. if (gettingLocation.value) return
  132. gettingLocation.value = true
  133. try {
  134. // 获取经纬度
  135. const locationRes = await uni.getLocation({ type: 'wgs84' })
  136. const { latitude, longitude } = locationRes
  137. console.log('getLocation success:', locationRes)
  138. // 调用用户自己的地理编码接口
  139. const geocodeRes = await uni.request({
  140. url: `http://45.207.222.6/geo/nearest.php?latitude=${latitude}&longitude=${longitude}`,
  141. method: 'GET'
  142. })
  143. const data = geocodeRes.data as any
  144. if (data && data.province && data.city) {
  145. region.value = [data.province, data.city,data.district]
  146. form.value.address = region.value.join(' ')
  147. uni.showToast({ title: '位置获取成功', icon: 'success' })
  148. } else {
  149. throw new Error('Geocode failed')
  150. }
  151. } catch (err) {
  152. console.error('Get location error:', err)
  153. uni.showToast({ title: '获取位置失败,请检查定位权限和网络', icon: 'none' })
  154. } finally {
  155. gettingLocation.value = false
  156. }
  157. }
  158. const onSubmit = async () => {
  159. if (submitting.value) return
  160. // 检查所有必需字段
  161. if (!form.value.avatar) {
  162. uni.showToast({ title: '请选择头像', icon: 'none' })
  163. return
  164. }
  165. if (!form.value.nickname) {
  166. uni.showToast({ title: '请输入姓名', icon: 'none' })
  167. return
  168. }
  169. if (!form.value.phone) {
  170. uni.showToast({ title: '请输入手机号', icon: 'none' })
  171. return
  172. }
  173. if (!form.value.age) {
  174. uni.showToast({ title: '请输入年龄', icon: 'none' })
  175. return
  176. }
  177. if (!form.value.sex) {
  178. uni.showToast({ title: '请选择性别', icon: 'none' })
  179. return
  180. }
  181. if (!form.value.address) {
  182. uni.showToast({ title: '请选择或自动获取省/市', icon: 'none' })
  183. return
  184. }
  185. submitting.value = true
  186. try {
  187. const token = uni.getStorageSync('token')
  188. const response = await uni.request({
  189. url: 'http://10.20.30.111:8080/update_user_info',
  190. method: 'POST',
  191. header: {
  192. 'Content-Type': 'application/json',
  193. 'Authorization': `Bearer ${token}`
  194. },
  195. data: form.value
  196. })
  197. console.log('Update response:', response)
  198. const resp = response.data as any
  199. if (resp && resp.code === 200) {
  200. uni.showToast({ title: '信息更新成功', icon: 'success' })
  201. setTimeout(() => {
  202. uni.navigateBack()
  203. }, 1500)
  204. } else {
  205. throw new Error('Update failed')
  206. }
  207. } catch (err) {
  208. console.error('Update error:', err)
  209. uni.showToast({ title: '更新失败', icon: 'error' })
  210. } finally {
  211. submitting.value = false
  212. }
  213. }
  214. </script>
  215. <style>
  216. .complete-container {
  217. min-height: 100vh;
  218. background-color: #f5f5f5;
  219. padding-top: calc(var(--status-bar-height) + 44px);
  220. padding-left: 40rpx;
  221. padding-right: 40rpx;
  222. padding-bottom: 40rpx;
  223. }
  224. .form-section {
  225. background-color: #fff;
  226. border-radius: 20rpx;
  227. padding: 40rpx;
  228. }
  229. .avatar-section {
  230. display: flex;
  231. justify-content: center;
  232. margin-bottom: 40rpx;
  233. }
  234. .avatar {
  235. width: 120rpx;
  236. height: 120rpx;
  237. border-radius: 60rpx;
  238. background-color: #eee;
  239. display: flex;
  240. align-items: center;
  241. justify-content: center;
  242. }
  243. .avatar-wrapper {
  244. width: 120rpx;
  245. height: 120rpx;
  246. border-radius: 60rpx;
  247. overflow: hidden;
  248. display: flex;
  249. align-items: center;
  250. justify-content: center;
  251. padding: 0;
  252. border: 2rpx solid #07C160;
  253. background: #fff;
  254. }
  255. .avatar-wrapper.disabled {
  256. opacity: 0.5;
  257. }
  258. .avatar-frame {
  259. width: 100%;
  260. height: 100%;
  261. border-radius: 60rpx;
  262. overflow: hidden;
  263. display: flex;
  264. align-items: center;
  265. justify-content: center;
  266. }
  267. .avatar-img {
  268. width: 100%;
  269. height: 100%;
  270. object-fit: cover;
  271. }
  272. .avatar-placeholder {
  273. color: #999;
  274. font-size: 24rpx;
  275. }
  276. .form-item {
  277. margin-bottom: 30rpx;
  278. }
  279. .label {
  280. display: block;
  281. font-size: 32rpx;
  282. color: #333;
  283. margin-bottom: 10rpx;
  284. }
  285. .input {
  286. width: 100%;
  287. height: 80rpx;
  288. border: 1rpx solid #ddd;
  289. border-radius: 8rpx;
  290. padding: 0 20rpx;
  291. font-size: 32rpx;
  292. }
  293. .phone-section {
  294. display: flex;
  295. align-items: center;
  296. }
  297. .get-phone-btn {
  298. flex: 1;
  299. height: 80rpx;
  300. background: linear-gradient(135deg, #07C160 0%, #00A854 100%);
  301. color: #fff;
  302. border-radius: 8rpx;
  303. font-size: 32rpx;
  304. border: none;
  305. }
  306. .get-phone-btn:disabled {
  307. opacity: 0.5;
  308. }
  309. .radio-group {
  310. display: flex;
  311. gap: 40rpx;
  312. }
  313. .radio-item {
  314. display: flex;
  315. align-items: center;
  316. font-size: 32rpx;
  317. color: #333;
  318. }
  319. .picker {
  320. width: 100%;
  321. height: 80rpx;
  322. border: 1rpx solid #ddd;
  323. border-radius: 8rpx;
  324. padding: 0 20rpx;
  325. display: flex;
  326. align-items: center;
  327. font-size: 32rpx;
  328. color: #333;
  329. }
  330. .location-section {
  331. display: flex;
  332. align-items: center;
  333. gap: 20rpx;
  334. }
  335. .get-location-btn {
  336. flex-shrink: 0;
  337. height: 80rpx;
  338. background: linear-gradient(135deg, #07C160 0%, #00A854 100%);
  339. color: #fff;
  340. border-radius: 8rpx;
  341. font-size: 28rpx;
  342. padding: 0 30rpx;
  343. border: none;
  344. }
  345. .get-location-btn:disabled {
  346. opacity: 0.5;
  347. }
  348. .submit-section {
  349. margin-top: 60rpx;
  350. }
  351. .submit-btn {
  352. width: 100%;
  353. background: linear-gradient(135deg, #07C160 0%, #00A854 100%);
  354. color: #fff;
  355. border-radius: 12rpx;
  356. font-size: 32rpx;
  357. line-height: 80rpx;
  358. border: none;
  359. }
  360. .submit-btn:disabled {
  361. opacity: 0.5;
  362. }
  363. </style>