hookehuyr

feat(user-profile): 新增参数构建工具并完善用户资料提交逻辑

- 新建src/utils/userProfile.js工具文件,封装标准化的更新用户资料请求参数构建逻辑
- 重构AddProfile和EditProfile页面的保存逻辑,统一使用工具处理表单数据
- 为AddProfile新增路由参数解析,支持带入reg_source和reg_stage_id请求参数
- 完善EditProfile表单验证,补充性别和轮椅需求的校验逻辑
- 统一项目代码格式,修正多余分号和导入语句风格
1 /* 1 /*
2 * @Date: 2024-01-01 00:00:00 2 * @Date: 2024-01-01 00:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-09-02 20:33:00 4 + * @LastEditTime: 2026-05-20 11:23:02
5 * @FilePath: /lls_program/src/api/user.js 5 * @FilePath: /lls_program/src/api/user.js
6 * @Description: 用户相关接口 6 * @Description: 用户相关接口
7 */ 7 */
8 -import { fn, fetch } from './fn'; 8 +import { fn, fetch } from './fn'
9 9
10 const Api = { 10 const Api = {
11 GET_PROFILE: '/srv/?a=user&t=get_profile', 11 GET_PROFILE: '/srv/?a=user&t=get_profile',
...@@ -28,7 +28,7 @@ const Api = { ...@@ -28,7 +28,7 @@ const Api = {
28 * @returns {boolean} response.data.user.wheelchair_needed - 是否需要轮椅 28 * @returns {boolean} response.data.user.wheelchair_needed - 是否需要轮椅
29 * @returns {number} response.data.user.gender - 用户性别 (0=女, 1=男) 29 * @returns {number} response.data.user.gender - 用户性别 (0=女, 1=男)
30 */ 30 */
31 -export const getUserProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, params)); 31 +export const getUserProfileAPI = params => fn(fetch.get(Api.GET_PROFILE, params))
32 32
33 /** 33 /**
34 * @description: 更新个人信息 34 * @description: 更新个人信息
...@@ -38,5 +38,7 @@ export const getUserProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, param ...@@ -38,5 +38,7 @@ export const getUserProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, param
38 * @param {string} params.avatar_url - 用户头像URL 38 * @param {string} params.avatar_url - 用户头像URL
39 * @param {boolean} params.wheelchair_needed - 是否需要轮椅 39 * @param {boolean} params.wheelchair_needed - 是否需要轮椅
40 * @param {number} params.gender - 用户性别 (0=女, 1=男) 40 * @param {number} params.gender - 用户性别 (0=女, 1=男)
41 + * @param {string} [params.reg_source] - 注册来源
42 + * @param {number} [params.reg_stage_id] - 注册关卡ID
41 */ 43 */
42 -export const updateUserProfileAPI = (params) => fn(fetch.post(Api.UPDATE_PROFILE, params)); 44 +export const updateUserProfileAPI = params => fn(fetch.post(Api.UPDATE_PROFILE, params))
......
...@@ -2,8 +2,14 @@ ...@@ -2,8 +2,14 @@
2 <view class="min-h-screen bg-gray-50 p-5 pb-24"> 2 <view class="min-h-screen bg-gray-50 p-5 pb-24">
3 <!-- Avatar --> 3 <!-- Avatar -->
4 <view class="flex flex-col items-center py-8" @click="changeAvatar"> 4 <view class="flex flex-col items-center py-8" @click="changeAvatar">
5 - <view class="w-24 h-24 rounded-full bg-gray-100 flex items-center justify-center mb-2 overflow-hidden"> 5 + <view
6 - <image :src="formData.avatar_url || defaultAvatar" class="w-full h-full" mode="aspectFill" /> 6 + class="w-24 h-24 rounded-full bg-gray-100 flex items-center justify-center mb-2 overflow-hidden"
7 + >
8 + <image
9 + :src="formData.avatar_url || defaultAvatar"
10 + class="w-full h-full"
11 + mode="aspectFill"
12 + />
7 </view> 13 </view>
8 <view class="flex items-center justify-center"> 14 <view class="flex items-center justify-center">
9 <text class="text-gray-500 text-sm mr-1">上传头像</text> 15 <text class="text-gray-500 text-sm mr-1">上传头像</text>
...@@ -31,21 +37,38 @@ ...@@ -31,21 +37,38 @@
31 <label class="block text-sm font-medium text-gray-700 mb-2">出生年月</label> 37 <label class="block text-sm font-medium text-gray-700 mb-2">出生年月</label>
32 <view class="bg-white rounded-xl p-4 border border-gray-200" @click="showDatePicker = true"> 38 <view class="bg-white rounded-xl p-4 border border-gray-200" @click="showDatePicker = true">
33 <view class="flex justify-between items-center"> 39 <view class="flex justify-between items-center">
34 - <text :class="{'text-gray-400': !formData.birth_date, 'text-gray-900': formData.birth_date}" class="text-base"> 40 + <text
41 + :class="{
42 + 'text-gray-400': !formData.birth_date,
43 + 'text-gray-900': formData.birth_date,
44 + }"
45 + class="text-base"
46 + >
35 {{ formData.birth_date || '-/-/-/' }} 47 {{ formData.birth_date || '-/-/-/' }}
36 </text> 48 </text>
37 <DateIcon size="20" color="#888" /> 49 <DateIcon size="20" color="#888" />
38 </view> 50 </view>
39 </view> 51 </view>
40 - <text class="text-red-500 text-xs mt-1 block">注意:出生年月填写后不可修改,请仔细确认</text> 52 + <text class="text-red-500 text-xs mt-1 block"
53 + >注意:出生年月填写后不可修改,请仔细确认</text
54 + >
41 </view> 55 </view>
42 56
43 <!-- gender --> 57 <!-- gender -->
44 <view class="mb-6"> 58 <view class="mb-6">
45 <label class="block text-sm font-medium text-gray-700 mb-2">性别</label> 59 <label class="block text-sm font-medium text-gray-700 mb-2">性别</label>
46 - <view class="bg-white rounded-xl p-4 border border-gray-200" @click="showGenderPicker = true"> 60 + <view
61 + class="bg-white rounded-xl p-4 border border-gray-200"
62 + @click="showGenderPicker = true"
63 + >
47 <view class="flex justify-between items-center"> 64 <view class="flex justify-between items-center">
48 - <text :class="{'text-gray-400': !formData.gender_text, 'text-gray-900': formData.gender_text}" class="text-base"> 65 + <text
66 + :class="{
67 + 'text-gray-400': !formData.gender_text,
68 + 'text-gray-900': formData.gender_text,
69 + }"
70 + class="text-base"
71 + >
49 {{ formData.gender_text || '请选择' }} 72 {{ formData.gender_text || '请选择' }}
50 </text> 73 </text>
51 <Right size="16" color="#888" /> 74 <Right size="16" color="#888" />
...@@ -56,9 +79,18 @@ ...@@ -56,9 +79,18 @@
56 <!-- wheelchair_needed --> 79 <!-- wheelchair_needed -->
57 <view class="mb-6"> 80 <view class="mb-6">
58 <label class="block text-sm font-medium text-gray-700 mb-2">是否需要轮椅出行</label> 81 <label class="block text-sm font-medium text-gray-700 mb-2">是否需要轮椅出行</label>
59 - <view class="bg-white rounded-xl p-4 border border-gray-200" @click="showWheelchairPicker = true"> 82 + <view
83 + class="bg-white rounded-xl p-4 border border-gray-200"
84 + @click="showWheelchairPicker = true"
85 + >
60 <view class="flex justify-between items-center"> 86 <view class="flex justify-between items-center">
61 - <text :class="{'text-gray-400': !formData.wheelchair_text, 'text-gray-900': formData.wheelchair_text}" class="text-base"> 87 + <text
88 + :class="{
89 + 'text-gray-400': !formData.wheelchair_text,
90 + 'text-gray-900': formData.wheelchair_text,
91 + }"
92 + class="text-base"
93 + >
62 {{ formData.wheelchair_text || '请选择' }} 94 {{ formData.wheelchair_text || '请选择' }}
63 </text> 95 </text>
64 <Right size="16" color="#888" /> 96 <Right size="16" color="#888" />
...@@ -69,7 +101,15 @@ ...@@ -69,7 +101,15 @@
69 101
70 <!-- Save Button --> 102 <!-- Save Button -->
71 <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100"> 103 <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
72 - <nut-button type="primary" size="large" :color="isFormValid ? THEME_COLORS.PRIMARY : '#D8D8D8'" block @click="handleSave" :disabled="!isFormValid">保存</nut-button> 104 + <nut-button
105 + type="primary"
106 + size="large"
107 + :color="isFormValid ? THEME_COLORS.PRIMARY : '#D8D8D8'"
108 + block
109 + @click="handleSave"
110 + :disabled="!isFormValid"
111 + >保存</nut-button
112 + >
73 </view> 113 </view>
74 114
75 <!-- Popups --> 115 <!-- Popups -->
...@@ -107,10 +147,7 @@ ...@@ -107,10 +147,7 @@
107 <nut-image-preview v-model:show="previewVisible" :images="previewImages" /> 147 <nut-image-preview v-model:show="previewVisible" :images="previewImages" />
108 148
109 <!-- 头像上传须知对话框 --> 149 <!-- 头像上传须知对话框 -->
110 - <nut-dialog 150 + <nut-dialog v-model:visible="showAvatarTipDialog" title="头像上传须知">
111 - v-model:visible="showAvatarTipDialog"
112 - title="头像上传须知"
113 - >
114 <template #default> 151 <template #default>
115 <view class="text-gray-700 leading-loose text-sm text-left break-words"> 152 <view class="text-gray-700 leading-loose text-sm text-left break-words">
116 <view class="mb-2">1. 头像大小不能超过5MB</view> 153 <view class="mb-2">1. 头像大小不能超过5MB</view>
...@@ -134,16 +171,17 @@ ...@@ -134,16 +171,17 @@
134 </template> 171 </template>
135 172
136 <script setup> 173 <script setup>
137 -import { ref, reactive, onMounted, computed } from 'vue'; 174 +import { ref, reactive, computed } from 'vue'
138 -import Taro from '@tarojs/taro'; 175 +import Taro, { useLoad } from '@tarojs/taro'
139 -import { Date as DateIcon, Right, Ask } from '@nutui/icons-vue-taro'; 176 +import { Date as DateIcon, Right, Ask } from '@nutui/icons-vue-taro'
140 -import BASE_URL from '@/utils/config'; 177 +import BASE_URL from '@/utils/config'
141 // 默认头像 178 // 默认头像
142 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 179 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
143 // 获取接口信息 180 // 获取接口信息
144 -import { updateUserProfileAPI } from '@/api/user'; 181 +import { updateUserProfileAPI } from '@/api/user'
182 +import { buildUpdateUserProfilePayload } from '@/utils/userProfile'
145 // 导入主题颜色 183 // 导入主题颜色
146 -import { THEME_COLORS } from '@/utils/config'; 184 +import { THEME_COLORS } from '@/utils/config'
147 185
148 /** 186 /**
149 * @description 表单数据 187 * @description 表单数据
...@@ -152,116 +190,129 @@ const formData = reactive({ ...@@ -152,116 +190,129 @@ const formData = reactive({
152 avatar_url: '', 190 avatar_url: '',
153 nickname: '', 191 nickname: '',
154 birth_date: '', 192 birth_date: '',
155 - wheelchair_needed: null, // 0 for No, 1 for Yes 193 + wheelchair_needed: null,
156 wheelchair_text: '', 194 wheelchair_text: '',
157 - gender: null, // 0 for 女, 1 for 男 195 + gender: null,
158 gender_text: '', 196 gender_text: '',
159 qiniu_audit: '', // 图片审核信息 197 qiniu_audit: '', // 图片审核信息
160 -}); 198 +})
199 +
200 +/**
201 + * @description 注册来源补充参数
202 + */
203 +const registerSourceParams = reactive({
204 + reg_source: '',
205 + reg_stage_id: '',
206 +})
161 207
162 /** 208 /**
163 * @description 检查表单是否有效 209 * @description 检查表单是否有效
164 */ 210 */
165 const isFormValid = computed(() => { 211 const isFormValid = computed(() => {
166 - return formData.nickname && formData.birth_date && formData.wheelchair_needed !== null && formData.gender !== null; 212 + return (
167 -}); 213 + formData.nickname &&
214 + formData.birth_date &&
215 + formData.wheelchair_needed !== null &&
216 + formData.gender !== null
217 + )
218 +})
168 219
169 // --- Avatar Tip Dialog --- 220 // --- Avatar Tip Dialog ---
170 /** 221 /**
171 * @description 控制头像上传须知对话框显示 222 * @description 控制头像上传须知对话框显示
172 */ 223 */
173 -const showAvatarTipDialog = ref(false); 224 +const showAvatarTipDialog = ref(false)
174 225
175 // --- Date Picker --- 226 // --- Date Picker ---
176 /** 227 /**
177 * @description 控制日期选择器显示 228 * @description 控制日期选择器显示
178 */ 229 */
179 -const showDatePicker = ref(false); 230 +const showDatePicker = ref(false)
180 /** 231 /**
181 * @description 最小可选日期 232 * @description 最小可选日期
182 */ 233 */
183 -const minDate = new Date(1920, 0, 1); 234 +const minDate = new Date(1920, 0, 1)
184 /** 235 /**
185 * @description 最大可选日期 236 * @description 最大可选日期
186 */ 237 */
187 -const maxDate = new Date(); 238 +const maxDate = new Date()
188 /** 239 /**
189 * @description 当前选中的日期 240 * @description 当前选中的日期
190 */ 241 */
191 -const currentDate = ref(new Date()); 242 +const currentDate = ref(new Date())
192 243
193 /** 244 /**
194 * @description 确认选择日期 245 * @description 确认选择日期
195 * @param {object} param0 - 包含 selectedValue 的对象 246 * @param {object} param0 - 包含 selectedValue 的对象
196 */ 247 */
197 const onDateConfirm = ({ selectedValue }) => { 248 const onDateConfirm = ({ selectedValue }) => {
198 - formData.birth_date = selectedValue.join('-'); 249 + formData.birth_date = selectedValue.join('-')
199 - showDatePicker.value = false; 250 + showDatePicker.value = false
200 -}; 251 +}
201 252
202 // --- wheelchair_needed Picker --- 253 // --- wheelchair_needed Picker ---
203 /** 254 /**
204 * @description 控制轮椅选择器显示 255 * @description 控制轮椅选择器显示
205 */ 256 */
206 -const showWheelchairPicker = ref(false); 257 +const showWheelchairPicker = ref(false)
207 /** 258 /**
208 * @description 当前选中的轮椅选项 259 * @description 当前选中的轮椅选项
209 */ 260 */
210 -const wheelchairValue = ref([]); 261 +const wheelchairValue = ref([])
211 /** 262 /**
212 * @description 轮椅选择器选项 263 * @description 轮椅选择器选项
213 */ 264 */
214 const wheelchairColumns = ref([ 265 const wheelchairColumns = ref([
215 { text: '是', value: 1 }, 266 { text: '是', value: 1 },
216 { text: '否', value: 0 }, 267 { text: '否', value: 0 },
217 -]); 268 +])
218 269
219 // --- Gender Picker --- 270 // --- Gender Picker ---
220 /** 271 /**
221 * @description 控制性别选择器显示 272 * @description 控制性别选择器显示
222 */ 273 */
223 -const showGenderPicker = ref(false); 274 +const showGenderPicker = ref(false)
224 /** 275 /**
225 * @description 当前选中的性别选项 276 * @description 当前选中的性别选项
226 */ 277 */
227 -const genderValue = ref([]); 278 +const genderValue = ref([])
228 /** 279 /**
229 * @description 性别选择器选项 280 * @description 性别选择器选项
230 */ 281 */
231 const genderColumns = ref([ 282 const genderColumns = ref([
232 { text: '女', value: 0 }, 283 { text: '女', value: 0 },
233 { text: '男', value: 1 }, 284 { text: '男', value: 1 },
234 -]); 285 +])
235 286
236 /** 287 /**
237 * @description 确认选择性别选项 288 * @description 确认选择性别选项
238 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象 289 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
239 */ 290 */
240 const onGenderConfirm = ({ selectedValue, selectedOptions }) => { 291 const onGenderConfirm = ({ selectedValue, selectedOptions }) => {
241 - formData.gender = selectedValue[0]; 292 + formData.gender = selectedValue[0]
242 - formData.gender_text = selectedOptions.map((option) => option.text).join(''); 293 + formData.gender_text = selectedOptions.map(option => option.text).join('')
243 - showGenderPicker.value = false; 294 + showGenderPicker.value = false
244 -}; 295 +}
245 296
246 /** 297 /**
247 * @description 确认选择轮椅选项 298 * @description 确认选择轮椅选项
248 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象 299 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
249 */ 300 */
250 const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => { 301 const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => {
251 - formData.wheelchair_needed = selectedValue[0]; 302 + formData.wheelchair_needed = selectedValue[0]
252 - formData.wheelchair_text = selectedOptions.map((option) => option.text).join(''); 303 + formData.wheelchair_text = selectedOptions.map(option => option.text).join('')
253 - showWheelchairPicker.value = false; 304 + showWheelchairPicker.value = false
254 -}; 305 +}
255 306
256 // --- Avatar --- 307 // --- Avatar ---
257 /** 308 /**
258 * @description 控制图片预览显示 309 * @description 控制图片预览显示
259 */ 310 */
260 -const previewVisible = ref(false); 311 +const previewVisible = ref(false)
261 /** 312 /**
262 * @description 预览的图片列表 313 * @description 预览的图片列表
263 */ 314 */
264 -const previewImages = ref([]); 315 +const previewImages = ref([])
265 316
266 /** 317 /**
267 * @description 更换头像 318 * @description 更换头像
...@@ -271,57 +322,57 @@ const changeAvatar = () => { ...@@ -271,57 +322,57 @@ const changeAvatar = () => {
271 count: 1, 322 count: 1,
272 sizeType: ['compressed'], 323 sizeType: ['compressed'],
273 sourceType: ['album', 'camera'], 324 sourceType: ['album', 'camera'],
274 - success: (res) => { 325 + success: res => {
275 - const tempFile = res.tempFiles[0]; 326 + const tempFile = res.tempFiles[0]
276 if (tempFile.size > 5 * 1024 * 1024) { 327 if (tempFile.size > 5 * 1024 * 1024) {
277 Taro.showToast({ 328 Taro.showToast({
278 title: '图片大小不能超过5MB', 329 title: '图片大小不能超过5MB',
279 icon: 'none', 330 icon: 'none',
280 - }); 331 + })
281 - return; 332 + return
282 } 333 }
283 334
284 - Taro.showLoading({ title: '上传中...' }); 335 + Taro.showLoading({ title: '上传中...' })
285 336
286 Taro.uploadFile({ 337 Taro.uploadFile({
287 url: BASE_URL + '/admin/?m=srv&a=upload&image_audit=1', 338 url: BASE_URL + '/admin/?m=srv&a=upload&image_audit=1',
288 filePath: tempFile.path, 339 filePath: tempFile.path,
289 name: 'file', 340 name: 'file',
290 - success: (uploadRes) => { 341 + success: uploadRes => {
291 - Taro.hideLoading(); 342 + Taro.hideLoading()
292 - const data = JSON.parse(uploadRes.data); 343 + const data = JSON.parse(uploadRes.data)
293 - if (data.code == 0) { 344 + if (data.code === 0) {
294 // 检查是否为审核不通过 345 // 检查是否为审核不通过
295 - if (data.data.audit_code == -1) { 346 + if (data.data.audit_code === -1) {
296 Taro.showModal({ 347 Taro.showModal({
297 title: '温馨提示', 348 title: '温馨提示',
298 content: data.msg, 349 content: data.msg,
299 - showCancel: false 350 + showCancel: false,
300 }).then(res => { 351 }).then(res => {
301 if (res.confirm) { 352 if (res.confirm) {
302 // 点击了确认按钮 353 // 点击了确认按钮
303 } 354 }
304 - }); 355 + })
305 } else { 356 } else {
306 - formData.avatar_url = data.data.src; 357 + formData.avatar_url = data.data.src
307 - formData.qiniu_audit = data.data.audit_result; 358 + formData.qiniu_audit = data.data.audit_result
308 - Taro.showToast({ title: '上传成功', icon: 'success' }); 359 + Taro.showToast({ title: '上传成功', icon: 'success' })
309 } 360 }
310 } else { 361 } else {
311 - Taro.showToast({ title: data.msg || '上传失败', icon: 'none' }); 362 + Taro.showToast({ title: data.msg || '上传失败', icon: 'none' })
312 } 363 }
313 }, 364 },
314 fail: () => { 365 fail: () => {
315 - Taro.hideLoading(); 366 + Taro.hideLoading()
316 - Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' }); 367 + Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' })
317 }, 368 },
318 - }); 369 + })
319 }, 370 },
320 fail: () => { 371 fail: () => {
321 // Taro.showToast({ title: '选择图片失败', icon: 'none' }); 372 // Taro.showToast({ title: '选择图片失败', icon: 'none' });
322 }, 373 },
323 - }); 374 + })
324 -}; 375 +}
325 376
326 // --- Save --- 377 // --- Save ---
327 /** 378 /**
...@@ -332,25 +383,37 @@ const handleSave = async () => { ...@@ -332,25 +383,37 @@ const handleSave = async () => {
332 Taro.showToast({ 383 Taro.showToast({
333 title: '请填写所有必填项', 384 title: '请填写所有必填项',
334 icon: 'none', 385 icon: 'none',
335 - }); 386 + })
336 - return; 387 + return
337 } 388 }
338 389
339 - // 保存用户信息 390 + if (!formData.avatar_url) {
340 - const { code, data } = await updateUserProfileAPI(formData); 391 + formData.avatar_url = defaultAvatar
341 - if (code) { 392 + }
393 +
394 + const payload = buildUpdateUserProfilePayload(formData, registerSourceParams)
395 + const result = await updateUserProfileAPI(payload)
396 + if (result?.code === 1) {
342 Taro.showToast({ 397 Taro.showToast({
343 title: '保存成功', 398 title: '保存成功',
344 icon: 'success', 399 icon: 'success',
345 - }); 400 + })
346 setTimeout(() => { 401 setTimeout(() => {
347 - Taro.navigateBack(); 402 + Taro.navigateBack()
348 - }, 1500); 403 + }, 1500)
404 + return
349 } 405 }
350 -};
351 406
352 -onMounted(() => { 407 + Taro.showToast({
353 -}); 408 + title: result?.msg || '保存失败,请稍后重试',
409 + icon: 'none',
410 + })
411 +}
412 +
413 +useLoad(options => {
414 + registerSourceParams.reg_source = options?.reg_source || ''
415 + registerSourceParams.reg_stage_id = options?.reg_stage_id || ''
416 +})
354 </script> 417 </script>
355 418
356 <style> 419 <style>
......
...@@ -2,8 +2,14 @@ ...@@ -2,8 +2,14 @@
2 <view class="min-h-screen bg-gray-50 p-5 pb-24"> 2 <view class="min-h-screen bg-gray-50 p-5 pb-24">
3 <!-- Avatar --> 3 <!-- Avatar -->
4 <view class="flex flex-col items-center py-8" @tap="changeAvatar"> 4 <view class="flex flex-col items-center py-8" @tap="changeAvatar">
5 - <view class="w-24 h-24 rounded-full bg-gray-100 flex items-center justify-center mb-2 overflow-hidden"> 5 + <view
6 - <image :src="formData.avatar_url || defaultAvatar" class="w-full h-full" mode="aspectFill" /> 6 + class="w-24 h-24 rounded-full bg-gray-100 flex items-center justify-center mb-2 overflow-hidden"
7 + >
8 + <image
9 + :src="formData.avatar_url || defaultAvatar"
10 + class="w-full h-full"
11 + mode="aspectFill"
12 + />
7 </view> 13 </view>
8 <view class="flex items-center justify-center"> 14 <view class="flex items-center justify-center">
9 <text class="text-gray-500 text-sm mr-1">上传头像</text> 15 <text class="text-gray-500 text-sm mr-1">上传头像</text>
...@@ -43,9 +49,18 @@ ...@@ -43,9 +49,18 @@
43 <!-- gender --> 49 <!-- gender -->
44 <view class="mb-6"> 50 <view class="mb-6">
45 <label class="block text-sm font-medium text-gray-700 mb-2">性别</label> 51 <label class="block text-sm font-medium text-gray-700 mb-2">性别</label>
46 - <view class="bg-white rounded-xl p-4 border border-gray-200" @click="showGenderPicker = true"> 52 + <view
53 + class="bg-white rounded-xl p-4 border border-gray-200"
54 + @click="showGenderPicker = true"
55 + >
47 <view class="flex justify-between items-center"> 56 <view class="flex justify-between items-center">
48 - <text :class="{'text-gray-400': !formData.gender_text, 'text-gray-900': formData.gender_text}" class="text-base"> 57 + <text
58 + :class="{
59 + 'text-gray-400': !formData.gender_text,
60 + 'text-gray-900': formData.gender_text,
61 + }"
62 + class="text-base"
63 + >
49 {{ formData.gender_text || '请选择' }} 64 {{ formData.gender_text || '请选择' }}
50 </text> 65 </text>
51 <Right size="16" color="#888" /> 66 <Right size="16" color="#888" />
...@@ -56,9 +71,18 @@ ...@@ -56,9 +71,18 @@
56 <!-- wheelchair_needed --> 71 <!-- wheelchair_needed -->
57 <view class="mb-6"> 72 <view class="mb-6">
58 <label class="block text-sm font-medium text-gray-700 mb-2">是否需要轮椅出行</label> 73 <label class="block text-sm font-medium text-gray-700 mb-2">是否需要轮椅出行</label>
59 - <view class="bg-white rounded-xl p-4 border border-gray-200" @click="showWheelchairPicker = true"> 74 + <view
75 + class="bg-white rounded-xl p-4 border border-gray-200"
76 + @click="showWheelchairPicker = true"
77 + >
60 <view class="flex justify-between items-center"> 78 <view class="flex justify-between items-center">
61 - <text :class="{'text-gray-400': !formData.wheelchair_text, 'text-gray-900': formData.wheelchair_text}" class="text-base"> 79 + <text
80 + :class="{
81 + 'text-gray-400': !formData.wheelchair_text,
82 + 'text-gray-900': formData.wheelchair_text,
83 + }"
84 + class="text-base"
85 + >
62 {{ formData.wheelchair_text || '请选择' }} 86 {{ formData.wheelchair_text || '请选择' }}
63 </text> 87 </text>
64 <Right size="16" color="#888" /> 88 <Right size="16" color="#888" />
...@@ -69,7 +93,14 @@ ...@@ -69,7 +93,14 @@
69 93
70 <!-- Save Button --> 94 <!-- Save Button -->
71 <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100"> 95 <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
72 - <nut-button type="primary" size="large" :color="THEME_COLORS.PRIMARY" block @click="handleSave">保存</nut-button> 96 + <nut-button
97 + type="primary"
98 + size="large"
99 + :color="THEME_COLORS.PRIMARY"
100 + block
101 + @click="handleSave"
102 + >保存</nut-button
103 + >
73 </view> 104 </view>
74 105
75 <!-- Popups --> 106 <!-- Popups -->
...@@ -107,10 +138,7 @@ ...@@ -107,10 +138,7 @@
107 <nut-image-preview v-model:show="previewVisible" :images="previewImages" /> 138 <nut-image-preview v-model:show="previewVisible" :images="previewImages" />
108 139
109 <!-- 头像上传须知对话框 --> 140 <!-- 头像上传须知对话框 -->
110 - <nut-dialog 141 + <nut-dialog v-model:visible="showAvatarTipDialog" title="头像上传须知">
111 - v-model:visible="showAvatarTipDialog"
112 - title="头像上传须知"
113 - >
114 <template #default> 142 <template #default>
115 <view class="text-gray-700 leading-loose text-sm text-left break-words"> 143 <view class="text-gray-700 leading-loose text-sm text-left break-words">
116 <view class="mb-2">1. 头像大小不能超过5MB</view> 144 <view class="mb-2">1. 头像大小不能超过5MB</view>
...@@ -134,16 +162,17 @@ ...@@ -134,16 +162,17 @@
134 </template> 162 </template>
135 163
136 <script setup> 164 <script setup>
137 -import { ref, reactive, onMounted } from 'vue'; 165 +import { ref, reactive } from 'vue'
138 -import Taro from '@tarojs/taro'; 166 +import Taro, { useLoad } from '@tarojs/taro'
139 -import { My, Date as DateIcon, Right, Ask } from '@nutui/icons-vue-taro'; 167 +import { My, Date as DateIcon, Right, Ask } from '@nutui/icons-vue-taro'
140 -import BASE_URL from '@/utils/config'; 168 +import BASE_URL from '@/utils/config'
141 // 默认头像 169 // 默认头像
142 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 170 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
143 // 获取接口信息 171 // 获取接口信息
144 -import { getUserProfileAPI, updateUserProfileAPI } from '@/api/user'; 172 +import { getUserProfileAPI, updateUserProfileAPI } from '@/api/user'
173 +import { buildUpdateUserProfilePayload } from '@/utils/userProfile'
145 // 导入主题颜色 174 // 导入主题颜色
146 -import { THEME_COLORS } from '@/utils/config'; 175 +import { THEME_COLORS } from '@/utils/config'
147 176
148 /** 177 /**
149 * @description 表单数据 178 * @description 表单数据
...@@ -152,109 +181,109 @@ const formData = reactive({ ...@@ -152,109 +181,109 @@ const formData = reactive({
152 avatar_url: '', 181 avatar_url: '',
153 nickname: '', 182 nickname: '',
154 birth_date: '', 183 birth_date: '',
155 - wheelchair_needed: null, // 0 for No, 1 for Yes 184 + wheelchair_needed: null,
156 wheelchair_text: '', 185 wheelchair_text: '',
157 - gender: null, // 0 for 女, 1 for 男 186 + gender: null,
158 gender_text: '', 187 gender_text: '',
159 qiniu_audit: '', // 图片审核信息 188 qiniu_audit: '', // 图片审核信息
160 -}); 189 +})
161 190
162 // --- Avatar Tip Dialog --- 191 // --- Avatar Tip Dialog ---
163 /** 192 /**
164 * @description 控制头像上传须知对话框显示 193 * @description 控制头像上传须知对话框显示
165 */ 194 */
166 -const showAvatarTipDialog = ref(false); 195 +const showAvatarTipDialog = ref(false)
167 196
168 // --- Date Picker --- 197 // --- Date Picker ---
169 /** 198 /**
170 * @description 控制日期选择器显示 199 * @description 控制日期选择器显示
171 */ 200 */
172 -const showDatePicker = ref(false); 201 +const showDatePicker = ref(false)
173 /** 202 /**
174 * @description 最小可选日期 203 * @description 最小可选日期
175 */ 204 */
176 -const minDate = new Date(1920, 0, 1); 205 +const minDate = new Date(1920, 0, 1)
177 /** 206 /**
178 * @description 最大可选日期 207 * @description 最大可选日期
179 */ 208 */
180 -const maxDate = new Date(); 209 +const maxDate = new Date()
181 /** 210 /**
182 * @description 当前选中的日期 211 * @description 当前选中的日期
183 */ 212 */
184 -const currentDate = ref(new Date()); 213 +const currentDate = ref(new Date())
185 214
186 /** 215 /**
187 * @description 确认选择日期 216 * @description 确认选择日期
188 * @param {object} param0 - 包含 selectedValue 的对象 217 * @param {object} param0 - 包含 selectedValue 的对象
189 */ 218 */
190 const onDateConfirm = ({ selectedValue }) => { 219 const onDateConfirm = ({ selectedValue }) => {
191 - formData.birth_date = selectedValue.join('-'); 220 + formData.birth_date = selectedValue.join('-')
192 - showDatePicker.value = false; 221 + showDatePicker.value = false
193 -}; 222 +}
194 223
195 // --- wheelchair_needed Picker --- 224 // --- wheelchair_needed Picker ---
196 /** 225 /**
197 * @description 控制轮椅选择器显示 226 * @description 控制轮椅选择器显示
198 */ 227 */
199 -const showWheelchairPicker = ref(false); 228 +const showWheelchairPicker = ref(false)
200 /** 229 /**
201 * @description 当前选中的轮椅选项 230 * @description 当前选中的轮椅选项
202 */ 231 */
203 -const wheelchairValue = ref([]); 232 +const wheelchairValue = ref([])
204 /** 233 /**
205 * @description 轮椅选择器选项 234 * @description 轮椅选择器选项
206 */ 235 */
207 const wheelchairColumns = ref([ 236 const wheelchairColumns = ref([
208 { text: '是', value: true }, 237 { text: '是', value: true },
209 { text: '否', value: false }, 238 { text: '否', value: false },
210 -]); 239 +])
211 240
212 // --- Gender Picker --- 241 // --- Gender Picker ---
213 /** 242 /**
214 * @description 控制性别选择器显示 243 * @description 控制性别选择器显示
215 */ 244 */
216 -const showGenderPicker = ref(false); 245 +const showGenderPicker = ref(false)
217 /** 246 /**
218 * @description 当前选中的性别选项 247 * @description 当前选中的性别选项
219 */ 248 */
220 -const genderValue = ref([]); 249 +const genderValue = ref([])
221 /** 250 /**
222 * @description 性别选择器选项 251 * @description 性别选择器选项
223 */ 252 */
224 const genderColumns = ref([ 253 const genderColumns = ref([
225 { text: '女', value: 0 }, 254 { text: '女', value: 0 },
226 { text: '男', value: 1 }, 255 { text: '男', value: 1 },
227 -]); 256 +])
228 257
229 /** 258 /**
230 * @description 确认选择性别选项 259 * @description 确认选择性别选项
231 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象 260 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
232 */ 261 */
233 const onGenderConfirm = ({ selectedValue, selectedOptions }) => { 262 const onGenderConfirm = ({ selectedValue, selectedOptions }) => {
234 - formData.gender = selectedValue[0]; 263 + formData.gender = selectedValue[0]
235 - formData.gender_text = selectedOptions.map((option) => option.text).join(''); 264 + formData.gender_text = selectedOptions.map(option => option.text).join('')
236 - showGenderPicker.value = false; 265 + showGenderPicker.value = false
237 -}; 266 +}
238 267
239 /** 268 /**
240 * @description 确认选择轮椅选项 269 * @description 确认选择轮椅选项
241 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象 270 * @param {object} param0 - 包含 selectedValue 和 selectedOptions 的对象
242 */ 271 */
243 const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => { 272 const onWheelchairConfirm = ({ selectedValue, selectedOptions }) => {
244 - formData.wheelchair_needed = selectedValue[0]; 273 + formData.wheelchair_needed = selectedValue[0]
245 - formData.wheelchair_text = selectedOptions.map((option) => option.text).join(''); 274 + formData.wheelchair_text = selectedOptions.map(option => option.text).join('')
246 - showWheelchairPicker.value = false; 275 + showWheelchairPicker.value = false
247 -}; 276 +}
248 277
249 // --- Avatar --- 278 // --- Avatar ---
250 /** 279 /**
251 * @description 控制图片预览显示 280 * @description 控制图片预览显示
252 */ 281 */
253 -const previewVisible = ref(false); 282 +const previewVisible = ref(false)
254 /** 283 /**
255 * @description 预览的图片列表 284 * @description 预览的图片列表
256 */ 285 */
257 -const previewImages = ref([]); 286 +const previewImages = ref([])
258 287
259 /** 288 /**
260 * @description 更换头像 289 * @description 更换头像
...@@ -264,57 +293,57 @@ const changeAvatar = () => { ...@@ -264,57 +293,57 @@ const changeAvatar = () => {
264 count: 1, 293 count: 1,
265 sizeType: ['compressed'], 294 sizeType: ['compressed'],
266 sourceType: ['album', 'camera'], 295 sourceType: ['album', 'camera'],
267 - success: (res) => { 296 + success: res => {
268 - const tempFile = res.tempFiles[0]; 297 + const tempFile = res.tempFiles[0]
269 if (tempFile.size > 5 * 1024 * 1024) { 298 if (tempFile.size > 5 * 1024 * 1024) {
270 Taro.showToast({ 299 Taro.showToast({
271 title: '图片大小不能超过5MB', 300 title: '图片大小不能超过5MB',
272 icon: 'none', 301 icon: 'none',
273 - }); 302 + })
274 - return; 303 + return
275 } 304 }
276 305
277 - Taro.showLoading({ title: '上传中...' }); 306 + Taro.showLoading({ title: '上传中...' })
278 307
279 Taro.uploadFile({ 308 Taro.uploadFile({
280 url: BASE_URL + '/admin/?m=srv&a=upload&image_audit=1', 309 url: BASE_URL + '/admin/?m=srv&a=upload&image_audit=1',
281 filePath: tempFile.path, 310 filePath: tempFile.path,
282 name: 'file', 311 name: 'file',
283 - success: (uploadRes) => { 312 + success: uploadRes => {
284 - Taro.hideLoading(); 313 + Taro.hideLoading()
285 - const data = JSON.parse(uploadRes.data); 314 + const data = JSON.parse(uploadRes.data)
286 if (data.code === 0) { 315 if (data.code === 0) {
287 // 检查是否为审核不通过 316 // 检查是否为审核不通过
288 - if (data.data.audit_code == -1) { 317 + if (data.data.audit_code === -1) {
289 Taro.showModal({ 318 Taro.showModal({
290 title: '温馨提示', 319 title: '温馨提示',
291 content: data.msg, 320 content: data.msg,
292 - showCancel: false 321 + showCancel: false,
293 }).then(res => { 322 }).then(res => {
294 if (res.confirm) { 323 if (res.confirm) {
295 // 点击了确认按钮 324 // 点击了确认按钮
296 } 325 }
297 - }); 326 + })
298 } else { 327 } else {
299 - formData.avatar_url = data.data.src; 328 + formData.avatar_url = data.data.src
300 - formData.qiniu_audit = data.data.audit_result; 329 + formData.qiniu_audit = data.data.audit_result
301 - Taro.showToast({ title: '上传成功', icon: 'success' }); 330 + Taro.showToast({ title: '上传成功', icon: 'success' })
302 } 331 }
303 } else { 332 } else {
304 - Taro.showToast({ title: data.msg || '上传失败', icon: 'none' }); 333 + Taro.showToast({ title: data.msg || '上传失败', icon: 'none' })
305 } 334 }
306 }, 335 },
307 fail: () => { 336 fail: () => {
308 - Taro.hideLoading(); 337 + Taro.hideLoading()
309 - Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' }); 338 + Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' })
310 }, 339 },
311 - }); 340 + })
312 }, 341 },
313 fail: () => { 342 fail: () => {
314 // Taro.showToast({ title: '选择图片失败', icon: 'none' }); 343 // Taro.showToast({ title: '选择图片失败', icon: 'none' });
315 }, 344 },
316 - }); 345 + })
317 -}; 346 +}
318 347
319 // --- Save --- 348 // --- Save ---
320 /** 349 /**
...@@ -325,29 +354,37 @@ const validateForm = () => { ...@@ -325,29 +354,37 @@ const validateForm = () => {
325 if (!formData.nickname.trim()) { 354 if (!formData.nickname.trim()) {
326 Taro.showToast({ 355 Taro.showToast({
327 title: '请输入昵称', 356 title: '请输入昵称',
328 - icon: 'none' 357 + icon: 'none',
329 - }); 358 + })
330 - return false; 359 + return false
331 } 360 }
332 361
333 if (!formData.birth_date) { 362 if (!formData.birth_date) {
334 Taro.showToast({ 363 Taro.showToast({
335 title: '请选择出生年月', 364 title: '请选择出生年月',
336 - icon: 'none' 365 + icon: 'none',
337 - }); 366 + })
338 - return false; 367 + return false
368 + }
369 +
370 + if (formData.gender === null || formData.gender === undefined) {
371 + Taro.showToast({
372 + title: '请选择性别',
373 + icon: 'none',
374 + })
375 + return false
339 } 376 }
340 377
341 if (formData.wheelchair_needed === null || formData.wheelchair_needed === undefined) { 378 if (formData.wheelchair_needed === null || formData.wheelchair_needed === undefined) {
342 Taro.showToast({ 379 Taro.showToast({
343 title: '请选择是否需要轮椅出行', 380 title: '请选择是否需要轮椅出行',
344 - icon: 'none' 381 + icon: 'none',
345 - }); 382 + })
346 - return false; 383 + return false
347 } 384 }
348 385
349 - return true; 386 + return true
350 -}; 387 +}
351 388
352 /** 389 /**
353 * @description 保存用户信息 390 * @description 保存用户信息
...@@ -355,51 +392,57 @@ const validateForm = () => { ...@@ -355,51 +392,57 @@ const validateForm = () => {
355 const handleSave = async () => { 392 const handleSave = async () => {
356 // 验证表单 393 // 验证表单
357 if (!validateForm()) { 394 if (!validateForm()) {
358 - return; 395 + return
359 } 396 }
360 397
361 // 如果头像为空,使用默认头像 398 // 如果头像为空,使用默认头像
362 if (!formData.avatar_url) { 399 if (!formData.avatar_url) {
363 - formData.avatar_url = defaultAvatar; 400 + formData.avatar_url = defaultAvatar
364 } 401 }
365 402
366 - // 保存数据 403 + const payload = buildUpdateUserProfilePayload(formData)
367 - const { code, data } = await updateUserProfileAPI(formData); 404 + const result = await updateUserProfileAPI(payload)
368 - if (code) { 405 + if (result?.code === 1) {
369 Taro.showToast({ 406 Taro.showToast({
370 title: '保存成功', 407 title: '保存成功',
371 icon: 'success', 408 icon: 'success',
372 - }); 409 + })
373 setTimeout(() => { 410 setTimeout(() => {
374 - Taro.navigateBack(); 411 + Taro.navigateBack()
375 - }, 1500); 412 + }, 1500)
413 + return
376 } 414 }
377 -}; 415 +
416 + Taro.showToast({
417 + title: result?.msg || '保存失败,请稍后重试',
418 + icon: 'none',
419 + })
420 +}
378 421
379 /** 422 /**
380 * @description 页面加载时获取初始数据 423 * @description 页面加载时获取初始数据
381 */ 424 */
382 -onMounted(async () => { 425 +useLoad(async () => {
383 // 获取用户信息 426 // 获取用户信息
384 - const { code, data } = await getUserProfileAPI(); 427 + const result = await getUserProfileAPI()
385 - if (code && data?.user) { 428 + if (result?.code === 1 && result?.data?.user) {
386 - Object.assign(formData, data.user); 429 + Object.assign(formData, result.data.user)
387 // 初始化勾选数据 430 // 初始化勾选数据
388 if (formData.birth_date) { 431 if (formData.birth_date) {
389 - currentDate.value = new Date(formData.birth_date); 432 + currentDate.value = new Date(formData.birth_date)
390 } 433 }
391 // 设置轮椅选择器的值和显示文本 434 // 设置轮椅选择器的值和显示文本
392 if (formData.wheelchair_needed !== null && formData.wheelchair_needed !== undefined) { 435 if (formData.wheelchair_needed !== null && formData.wheelchair_needed !== undefined) {
393 - wheelchairValue.value = [formData.wheelchair_needed]; 436 + wheelchairValue.value = [formData.wheelchair_needed]
394 - formData.wheelchair_text = formData.wheelchair_needed ? '是' : '否'; 437 + formData.wheelchair_text = formData.wheelchair_needed ? '是' : '否'
395 } 438 }
396 // 设置性别选择器的值和显示文本 439 // 设置性别选择器的值和显示文本
397 if (formData.gender !== null && formData.gender !== undefined) { 440 if (formData.gender !== null && formData.gender !== undefined) {
398 - genderValue.value = [formData.gender]; 441 + genderValue.value = [formData.gender]
399 - formData.gender_text = formData.gender === 0 ? '女' : '男'; 442 + formData.gender_text = formData.gender === 0 ? '女' : '男'
400 } 443 }
401 } 444 }
402 -}); 445 +})
403 </script> 446 </script>
404 447
405 <style> 448 <style>
......
1 +/*
2 + * @Date: 2026-05-20 11:40:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2026-05-20 11:40:00
5 + * @FilePath: /lls_program/src/utils/userProfile.js
6 + * @Description: 用户资料相关工具函数
7 + */
8 +
9 +/**
10 + * @description 构建更新个人资料请求参数
11 + * @param {Object} formData - 页面表单数据
12 + * @param {Object} extraParams - 额外补充参数
13 + * @returns {Object} 仅包含接口需要的字段
14 + */
15 +export const buildUpdateUserProfilePayload = (formData, extraParams = {}) => {
16 + const payload = {
17 + nickname: (formData?.nickname || '').trim(),
18 + avatar_url: formData?.avatar_url || '',
19 + birth_date: formData?.birth_date || '',
20 + wheelchair_needed: Boolean(formData?.wheelchair_needed),
21 + gender: formData?.gender,
22 + }
23 +
24 + const optionalKeys = ['reg_source', 'reg_stage_id']
25 + optionalKeys.forEach(key => {
26 + const value = extraParams[key]
27 + if (value !== '' && value !== undefined && value !== null) {
28 + payload[key] = key === 'reg_stage_id' ? Number(value) : value
29 + }
30 + })
31 +
32 + return payload
33 +}