|
|
@@ -1,516 +0,0 @@
|
|
|
-<template>
|
|
|
- <CustomNav title="完善基本信息" leftType="home" />
|
|
|
- <view class="complete-container">
|
|
|
- <view class="form-section">
|
|
|
- <view class="avatar-section">
|
|
|
- <view class="avatar">
|
|
|
- <button
|
|
|
- class="avatar-wrapper"
|
|
|
- :class="{ disabled: isChoosing }"
|
|
|
- :disabled="isChoosing"
|
|
|
- open-type="chooseAvatar"
|
|
|
- @tap="startChooseAvatar"
|
|
|
- @chooseavatar="onChooseAvatar"
|
|
|
- >
|
|
|
- <view class="avatar-frame">
|
|
|
- <image v-if="form.avatar" class="avatar-img" :src="form.avatar" mode="aspectFill" />
|
|
|
- <text v-else class="avatar-placeholder">点击选择头像</text>
|
|
|
- </view>
|
|
|
- </button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">姓名</text>
|
|
|
- <input class="input" type="nickname" v-model="form.nickname" placeholder="请输入姓名" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">手机号</text>
|
|
|
- <input class="input" v-model="form.phone" placeholder="请输入手机号" type="number" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">年龄</text>
|
|
|
- <input class="input" v-model="form.age" placeholder="请输入年龄" type="number" />
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">性别</text>
|
|
|
- <view class="radio-group">
|
|
|
- <label class="radio-item" @click="form.sex = 1">
|
|
|
- <radio :checked="form.sex === 1" color="#07C160" />
|
|
|
- <text>男</text>
|
|
|
- </label>
|
|
|
- <label class="radio-item" @click="form.sex = 2">
|
|
|
- <radio :checked="form.sex === 2" color="#07C160" />
|
|
|
- <text>女</text>
|
|
|
- </label>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="form-item">
|
|
|
- <text class="label">省/市</text>
|
|
|
- <view class="location-section">
|
|
|
- <picker mode="region" :value="region" @change="onRegionChange">
|
|
|
- <view class="picker">{{ region.join(' ') || '请选择省/市' }}</view>
|
|
|
- </picker>
|
|
|
- <button class="get-location-btn" @click="getCurrentLocation" :disabled="gettingLocation">
|
|
|
- {{ gettingLocation ? '获取中...' : '使用当前定位' }}
|
|
|
- </button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <view class="submit-section">
|
|
|
- <button class="submit-btn" @click="onSubmit" :disabled="submitting">{{ submitting ? '提交中...' : '提交' }}</button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup lang="ts">
|
|
|
-import { ref } from 'vue'
|
|
|
-import { onShow } from '@dcloudio/uni-app'
|
|
|
-import CustomNav from '@/components/custom-nav.vue'
|
|
|
-
|
|
|
-const form = ref({
|
|
|
- avatar: '',
|
|
|
- nickname: '',
|
|
|
- phone: '',
|
|
|
- age: '',
|
|
|
- sex: 0,
|
|
|
- address: ''
|
|
|
-})
|
|
|
-
|
|
|
-const region = ref<string[]>([])
|
|
|
-const submitting = ref(false)
|
|
|
-const isChoosing = ref(false)
|
|
|
-const gettingLocation = ref(false)
|
|
|
-
|
|
|
-// 从后端拉取已有用户信息并填充表单
|
|
|
-const fetchUserInfo = async () => {
|
|
|
- try {
|
|
|
- const token = uni.getStorageSync('token')
|
|
|
- if (!token) return
|
|
|
- uni.showLoading({ title: '加载中...' })
|
|
|
- const response = await uni.request({
|
|
|
- url: 'https://wx.baiyun.work/user_info',
|
|
|
- method: 'POST',
|
|
|
- header: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Authorization': `Bearer ${token}`
|
|
|
- },
|
|
|
- data: {}
|
|
|
- })
|
|
|
- uni.hideLoading()
|
|
|
- console.log('fetchUserInfo response:', response)
|
|
|
- if (response.statusCode === 401) {
|
|
|
- // 未授权,跳转登录
|
|
|
- uni.removeStorageSync('token')
|
|
|
- uni.reLaunch({ url: '/pages/public/login/index' })
|
|
|
- return
|
|
|
- }
|
|
|
- const resp = response.data as any
|
|
|
- if (resp && resp.code === 200 && resp.data) {
|
|
|
- const d = resp.data
|
|
|
- // 填充基本字段
|
|
|
- form.value.avatar = d.avatar || form.value.avatar
|
|
|
- form.value.nickname = d.nickname || form.value.nickname
|
|
|
- form.value.phone = d.phone || form.value.phone
|
|
|
- form.value.age = d.age || form.value.age
|
|
|
- // sex 可能为 'MALE'/'FEMALE' 或数字/1/2
|
|
|
- if (d.sex) {
|
|
|
- if (typeof d.sex === 'string') {
|
|
|
- const s = d.sex.toUpperCase()
|
|
|
- form.value.sex = s === 'MALE' ? 1 : s === 'FEMALE' ? 2 : form.value.sex
|
|
|
- } else if (typeof d.sex === 'number') {
|
|
|
- form.value.sex = d.sex
|
|
|
- }
|
|
|
- }
|
|
|
- // address 可能为 '重庆市 重庆市' 或包含空格,用空格或中文空格分割
|
|
|
- if (d.address) {
|
|
|
- const parts = String(d.address).split(/\s+/).filter(Boolean)
|
|
|
- region.value = parts
|
|
|
- form.value.address = parts.join(' ')
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- uni.hideLoading()
|
|
|
- console.error('fetchUserInfo error:', err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 在页面显示时尝试填充
|
|
|
-onShow(() => {
|
|
|
- fetchUserInfo()
|
|
|
-})
|
|
|
-
|
|
|
-
|
|
|
-const onChooseAvatar = (e: any) => {
|
|
|
- console.log('onChooseAvatar called with event:', e)
|
|
|
- try {
|
|
|
- const detail = e?.detail
|
|
|
- console.log('Event detail:', detail)
|
|
|
- let url = ''
|
|
|
- if (!detail) {
|
|
|
- url = typeof e === 'string' ? e : ''
|
|
|
- console.log('No detail, using event as string:', url)
|
|
|
- } else if (typeof detail === 'string') {
|
|
|
- url = detail
|
|
|
- console.log('Detail is string:', url)
|
|
|
- } else if (detail.avatarUrl) {
|
|
|
- url = detail.avatarUrl
|
|
|
- console.log('Using detail.avatarUrl:', url)
|
|
|
- } else if (Array.isArray(detail) && detail[0]) {
|
|
|
- url = detail[0]
|
|
|
- console.log('Using first element of array:', url)
|
|
|
- }
|
|
|
- if (url) {
|
|
|
- form.value.avatar = url
|
|
|
- console.log('Avatar URL updated to:', form.value.avatar)
|
|
|
- }
|
|
|
- if (detail && detail.nickName && !form.value.nickname) {
|
|
|
- form.value.nickname = detail.nickName
|
|
|
- console.log('Nickname updated from detail:', form.value.nickname)
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error('Error in onChooseAvatar:', err)
|
|
|
- } finally {
|
|
|
- isChoosing.value = false
|
|
|
- console.log('isChoosing reset to false')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const startChooseAvatar = () => {
|
|
|
- console.log('startChooseAvatar called, current isChoosing:', isChoosing.value)
|
|
|
- if (isChoosing.value) {
|
|
|
- console.log('Already choosing, ignoring')
|
|
|
- return
|
|
|
- }
|
|
|
- isChoosing.value = true
|
|
|
- console.log('isChoosing set to true')
|
|
|
- setTimeout(() => {
|
|
|
- isChoosing.value = false
|
|
|
- console.log('Timeout: isChoosing reset to false')
|
|
|
- }, 3000)
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-const onRegionChange = (e: any) => {
|
|
|
- region.value = e.detail.value
|
|
|
- form.value.address = region.value.join(' ')
|
|
|
-}
|
|
|
-
|
|
|
-const getCurrentLocation = async () => {
|
|
|
- if (gettingLocation.value) return
|
|
|
- gettingLocation.value = true
|
|
|
- try {
|
|
|
- // 获取经纬度
|
|
|
- const locationRes = await uni.getLocation({ type: 'wgs84' })
|
|
|
- const { latitude, longitude } = locationRes
|
|
|
- console.log('getLocation success:', locationRes)
|
|
|
-
|
|
|
- // 调用用户自己的地理编码接口
|
|
|
- const token = uni.getStorageSync('token')
|
|
|
- const geocodeRes = await uni.request({
|
|
|
- url: `https://wx.baiyun.work/geo/nearest?latitude=${latitude}&longitude=${longitude}`,
|
|
|
- method: 'GET',
|
|
|
- header: {
|
|
|
- 'Authorization': `Bearer ${token}`
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- const data = geocodeRes.data as any
|
|
|
- if (data && data.code === 200 && data.data) {
|
|
|
- // 解析 data 字段中的 JSON 字符串
|
|
|
- const addressData = JSON.parse(data.data)
|
|
|
- if (addressData && addressData.province && addressData.city) {
|
|
|
- region.value = [addressData.province, addressData.city, addressData.district].filter(Boolean)
|
|
|
- form.value.address = region.value.join(' ')
|
|
|
- uni.showToast({ title: '位置获取成功', icon: 'success' })
|
|
|
- } else {
|
|
|
- throw new Error('Invalid address data')
|
|
|
- }
|
|
|
- } else {
|
|
|
- throw new Error('Geocode failed')
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error('Get location error:', err)
|
|
|
- uni.showToast({ title: '获取位置失败,请检查定位权限和网络', icon: 'none' })
|
|
|
- } finally {
|
|
|
- gettingLocation.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const onSubmit = async () => {
|
|
|
- if (submitting.value) return
|
|
|
-
|
|
|
- // 检查所有必需字段
|
|
|
- if (!form.value.avatar) {
|
|
|
- uni.showToast({ title: '请选择头像', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!form.value.nickname) {
|
|
|
- uni.showToast({ title: '请输入姓名', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- // 姓名格式校验:2-30 个中文、字母、·或空格
|
|
|
- const nameRegex = /^[\u4e00-\u9fa5A-Za-z·\s]{2,10}$/
|
|
|
- if (!nameRegex.test(String(form.value.nickname))) {
|
|
|
- uni.showToast({ title: '姓名格式不正确,请输入2-10位中文或字母', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!form.value.phone) {
|
|
|
- uni.showToast({ title: '请输入手机号', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- // 手机号格式(中国)校验
|
|
|
- const phoneRegex = /^1[3-9]\d{9}$/
|
|
|
- if (!phoneRegex.test(String(form.value.phone))) {
|
|
|
- uni.showToast({ title: '手机号格式不正确', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!form.value.age) {
|
|
|
- uni.showToast({ title: '请输入年龄', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- // 年龄为 1-120 的整数
|
|
|
- const ageNum = Number(form.value.age)
|
|
|
- if (!Number.isInteger(ageNum) || ageNum < 1 || ageNum > 120) {
|
|
|
- uni.showToast({ title: '年龄请输入1到120之间的整数', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!form.value.sex) {
|
|
|
- uni.showToast({ title: '请选择性别', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- if (!form.value.address) {
|
|
|
- uni.showToast({ title: '请选择或自动获取省/市', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- submitting.value = true
|
|
|
- try {
|
|
|
- const token = uni.getStorageSync('token')
|
|
|
- const response = await uni.request({
|
|
|
- url: 'https://wx.baiyun.work/update_user_info',
|
|
|
- method: 'POST',
|
|
|
- header: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Authorization': `Bearer ${token}`
|
|
|
- },
|
|
|
- data: form.value
|
|
|
- })
|
|
|
- console.log('Update response:', response)
|
|
|
- const resp = response.data as any
|
|
|
- if (resp && resp.code === 200) {
|
|
|
- uni.showToast({ title: '信息更新成功', icon: 'success' })
|
|
|
- setTimeout(() => {
|
|
|
- uni.navigateBack()
|
|
|
- }, 1500)
|
|
|
- } else {
|
|
|
- throw new Error('Update failed')
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error('Update error:', err)
|
|
|
- uni.showToast({ title: '更新失败', icon: 'error' })
|
|
|
- } finally {
|
|
|
- submitting.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style>
|
|
|
-.complete-container {
|
|
|
- min-height: 100vh;
|
|
|
- /* 为固定在顶部的 CustomNav 留出空间(状态栏 + 导航栏 44px) */
|
|
|
- padding-top: calc(var(--status-bar-height) + 44px + 40rpx);
|
|
|
- /* 保留侧边与内部间距,使用 border-box 避免计算误差 */
|
|
|
- padding-right: 40rpx;
|
|
|
- padding-left: 40rpx;
|
|
|
- /* 底部安全区:使用项目中声明的 --window-bottom 或 fallback */
|
|
|
- padding-bottom: calc(var(--window-bottom, 0px) + 40rpx);
|
|
|
- box-sizing: border-box;
|
|
|
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.form-section {
|
|
|
- background-color: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 40rpx;
|
|
|
- width: 100%;
|
|
|
- max-width: 600rpx;
|
|
|
- box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-section {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- margin-bottom: 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar {
|
|
|
- width: 160rpx;
|
|
|
- height: 160rpx;
|
|
|
- border-radius: 90rpx;
|
|
|
- background-color: #eee;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-wrapper {
|
|
|
- width: 160rpx;
|
|
|
- height: 160rpx;
|
|
|
- border-radius: 90rpx;
|
|
|
- overflow: hidden;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- padding: 0;
|
|
|
- border: 2rpx solid #07C160;
|
|
|
- background: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-wrapper.disabled {
|
|
|
- opacity: 0.5;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-frame {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- border-radius: 60rpx;
|
|
|
- overflow: hidden;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-img {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- object-fit: cover;
|
|
|
-}
|
|
|
-
|
|
|
-.avatar-placeholder {
|
|
|
- color: #999;
|
|
|
- font-size: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.form-item {
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.label {
|
|
|
- display: block;
|
|
|
- font-size: 32rpx;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 10rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.input {
|
|
|
- width: 100%;
|
|
|
- height: 80rpx;
|
|
|
- border: 1rpx solid #ddd;
|
|
|
- border-radius: 8rpx;
|
|
|
- padding: 0 20rpx;
|
|
|
- font-size: 32rpx;
|
|
|
- box-sizing: border-box;
|
|
|
- max-width: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-.phone-section {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-}
|
|
|
-
|
|
|
-.get-phone-btn {
|
|
|
- flex: 1;
|
|
|
- height: 80rpx;
|
|
|
- background: linear-gradient(135deg, #07C160 0%, #00A854 100%);
|
|
|
- color: #fff;
|
|
|
- border-radius: 8rpx;
|
|
|
- font-size: 32rpx;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.get-phone-btn:disabled {
|
|
|
- opacity: 0.5;
|
|
|
-}
|
|
|
-
|
|
|
-.radio-group {
|
|
|
- display: flex;
|
|
|
- gap: 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.radio-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- font-size: 32rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-picker {
|
|
|
- width: 100%;
|
|
|
- height: 80rpx;
|
|
|
- border: 1rpx solid #ddd;
|
|
|
- border-radius: 8rpx;
|
|
|
- padding: 0 20rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- font-size: 32rpx;
|
|
|
- color: hsl(0, 0%, 20%);
|
|
|
- box-sizing: border-box;
|
|
|
- max-width: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-.form-item, .picker, .input, .get-location-btn, .submit-btn {
|
|
|
- box-sizing: border-box;
|
|
|
-}
|
|
|
-
|
|
|
-.location-section {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.get-location-btn {
|
|
|
- flex-shrink: 0;
|
|
|
- height: 80rpx;
|
|
|
- background: linear-gradient(135deg, #07C160 0%, #00A854 100%);
|
|
|
- color: #fff;
|
|
|
- border-radius: 8rpx;
|
|
|
- font-size: 28rpx;
|
|
|
- padding: 0 10rpx;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.get-location-btn:disabled {
|
|
|
- opacity: 0.5;
|
|
|
-}
|
|
|
-
|
|
|
-.submit-section {
|
|
|
- margin-top: 60rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.submit-btn {
|
|
|
- width: 100%;
|
|
|
- background: linear-gradient(135deg, #07C160 0%, #00A854 100%);
|
|
|
- color: #fff;
|
|
|
- border-radius: 12rpx;
|
|
|
- font-size: 32rpx;
|
|
|
- line-height: 80rpx;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.submit-btn:disabled {
|
|
|
- opacity: 0.5;
|
|
|
-}
|
|
|
-</style>
|