2026-04-17 10:49:14 +08:00
|
|
|
|
const request = require('../../utils/request')
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const { getApiBaseUrl } = require('../../utils/constants')
|
|
|
|
|
|
|
|
|
|
|
|
function addCacheVersion(url, version) {
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
const joiner = url.includes('?') ? '&' : '?'
|
|
|
|
|
|
return `${url}${joiner}v=${encodeURIComponent(version)}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 21:28:56 +08:00
|
|
|
|
function isLocalDevUrl(url) {
|
|
|
|
|
|
return /^https?:\/\/(127\.0\.0\.1|localhost|0\.0\.0\.0)(:\d+)?\//i.test(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function upgradeToHttpsIfNeeded(url) {
|
|
|
|
|
|
if (/^http:\/\//i.test(url) && !isLocalDevUrl(url)) {
|
|
|
|
|
|
return url.replace(/^http:\/\//i, 'https://')
|
|
|
|
|
|
}
|
|
|
|
|
|
return url
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveImageUrl(url) {
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
if (/^https?:\/\//i.test(url)) {
|
|
|
|
|
|
return upgradeToHttpsIfNeeded(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const baseUrl = getApiBaseUrl().replace(/\/api\/v1\/?$/, '')
|
|
|
|
|
|
const resolved = url.startsWith('/') ? `${baseUrl}${url}` : `${baseUrl}/${url}`
|
|
|
|
|
|
return upgradeToHttpsIfNeeded(resolved)
|
2026-04-17 19:26:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 21:28:56 +08:00
|
|
|
|
function normalizeAvatarState(userInfo) {
|
|
|
|
|
|
const avatarUrl = resolveImageUrl(userInfo.avatar_url || '')
|
|
|
|
|
|
const avatarBlurUrl = resolveImageUrl(userInfo.avatar_blur_url || '')
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
...userInfo,
|
|
|
|
|
|
avatar_url: avatarUrl,
|
|
|
|
|
|
avatar_blur_url: avatarBlurUrl
|
|
|
|
|
|
}
|
2026-04-17 19:26:32 +08:00
|
|
|
|
}
|
2026-04-17 10:49:14 +08:00
|
|
|
|
|
|
|
|
|
|
const AUDIT_REQUIRED_FIELDS = ['personality_tags', 'hobbies', 'wechat_id', 'phone']
|
|
|
|
|
|
const PROFILE_LIMITS = {
|
|
|
|
|
|
nickname: 50,
|
|
|
|
|
|
real_name: 20,
|
|
|
|
|
|
city: 30,
|
|
|
|
|
|
job_industry: 50,
|
|
|
|
|
|
job_company: 100,
|
|
|
|
|
|
prefer_city: 30,
|
|
|
|
|
|
self_intro: 500,
|
|
|
|
|
|
wechat_id: 64,
|
|
|
|
|
|
phone: 20,
|
|
|
|
|
|
tagItem: 20,
|
|
|
|
|
|
listItems: 20
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const MAX_AVATAR_SIZE = 5 * 1024 * 1024
|
|
|
|
|
|
|
|
|
|
|
|
function cloneProfileCache() {
|
|
|
|
|
|
return wx.getStorageSync('profile_page_cache') || null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveProfileCache(userInfo) {
|
2026-04-17 21:28:56 +08:00
|
|
|
|
wx.setStorageSync('profile_page_cache', normalizeAvatarState(userInfo))
|
2026-04-17 19:26:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 10:49:14 +08:00
|
|
|
|
Page({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
form: {
|
|
|
|
|
|
nickname: '',
|
|
|
|
|
|
real_name: '',
|
|
|
|
|
|
gender: null,
|
|
|
|
|
|
birth_year: null,
|
|
|
|
|
|
city: '',
|
|
|
|
|
|
height: null,
|
|
|
|
|
|
education: null,
|
|
|
|
|
|
job_industry: '',
|
|
|
|
|
|
job_company: '',
|
|
|
|
|
|
income_range: null,
|
|
|
|
|
|
prefer_city: '',
|
|
|
|
|
|
self_intro: '',
|
|
|
|
|
|
personality_tags: [],
|
|
|
|
|
|
hobbies: [],
|
|
|
|
|
|
wechat_id: '',
|
|
|
|
|
|
phone: ''
|
|
|
|
|
|
},
|
|
|
|
|
|
tagsText: '',
|
|
|
|
|
|
hobbiesText: '',
|
2026-04-17 19:26:32 +08:00
|
|
|
|
avatarUrl: '',
|
|
|
|
|
|
avatarVersion: '',
|
2026-04-17 10:49:14 +08:00
|
|
|
|
genderOptions: [
|
|
|
|
|
|
{ label: '男', value: 1 },
|
|
|
|
|
|
{ label: '女', value: 2 }
|
|
|
|
|
|
],
|
|
|
|
|
|
educationOptions: [
|
|
|
|
|
|
{ label: '高中', value: 1 },
|
|
|
|
|
|
{ label: '大专', value: 2 },
|
|
|
|
|
|
{ label: '本科', value: 3 },
|
|
|
|
|
|
{ label: '硕士', value: 4 },
|
|
|
|
|
|
{ label: '博士', value: 5 }
|
|
|
|
|
|
],
|
|
|
|
|
|
incomeOptions: [
|
|
|
|
|
|
{ label: '5k以下', value: 1 },
|
|
|
|
|
|
{ label: '5k-10k', value: 2 },
|
|
|
|
|
|
{ label: '10k-20k', value: 3 },
|
|
|
|
|
|
{ label: '20k以上', value: 4 }
|
|
|
|
|
|
],
|
|
|
|
|
|
genderText: '请选择',
|
|
|
|
|
|
educationText: '请选择',
|
|
|
|
|
|
incomeText: '请选择',
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
saving: false
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onLoad() {
|
|
|
|
|
|
this.syncPickerTexts(this.data.form)
|
|
|
|
|
|
this.syncExtraTexts(this.data.form)
|
|
|
|
|
|
this.loadProfile()
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
syncExtraTexts(form) {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
tagsText: (form.personality_tags || []).join(','),
|
|
|
|
|
|
hobbiesText: (form.hobbies || []).join(',')
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
parseListText(text) {
|
|
|
|
|
|
return (text || '')
|
|
|
|
|
|
.split(/[,,\n]/)
|
|
|
|
|
|
.map((item) => item.trim())
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async loadProfile() {
|
|
|
|
|
|
this.setData({ loading: true })
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await request({ url: '/users/me' })
|
|
|
|
|
|
const form = {
|
|
|
|
|
|
...this.data.form,
|
|
|
|
|
|
...data,
|
|
|
|
|
|
gender: data.gender == null ? null : Number(data.gender),
|
|
|
|
|
|
birth_year: data.birth_year == null ? null : Number(data.birth_year),
|
|
|
|
|
|
height: data.height == null ? null : Number(data.height),
|
|
|
|
|
|
education: data.education == null ? null : Number(data.education),
|
|
|
|
|
|
income_range: data.income_range == null ? null : Number(data.income_range),
|
|
|
|
|
|
personality_tags: data.personality_tags || [],
|
|
|
|
|
|
hobbies: data.hobbies || [],
|
|
|
|
|
|
wechat_id: data.wechat_id || '',
|
2026-04-17 19:26:32 +08:00
|
|
|
|
phone: data.phone || '',
|
2026-04-17 21:28:56 +08:00
|
|
|
|
avatar_url: resolveImageUrl(data.avatar_url || ''),
|
|
|
|
|
|
avatar_blur_url: resolveImageUrl(data.avatar_blur_url || '')
|
2026-04-17 10:49:14 +08:00
|
|
|
|
}
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const avatarVersion = Date.now().toString()
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
form,
|
|
|
|
|
|
avatarUrl: addCacheVersion(form.avatar_url, avatarVersion),
|
|
|
|
|
|
avatarVersion
|
|
|
|
|
|
})
|
2026-04-17 10:49:14 +08:00
|
|
|
|
this.syncPickerTexts(form)
|
|
|
|
|
|
this.syncExtraTexts(form)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Load profile edit data failed:', err)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.setData({ loading: false })
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleInput(e) {
|
|
|
|
|
|
const field = e.currentTarget.dataset.field
|
|
|
|
|
|
const value = e.detail.value
|
|
|
|
|
|
|
|
|
|
|
|
if (field === 'personality_tags') {
|
|
|
|
|
|
const tags = this.parseListText(value)
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
form: { ...this.data.form, personality_tags: tags },
|
|
|
|
|
|
tagsText: value
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (field === 'hobbies') {
|
|
|
|
|
|
const hobbies = this.parseListText(value)
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
form: { ...this.data.form, hobbies },
|
|
|
|
|
|
hobbiesText: value
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({ [`form.${field}`]: value })
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handlePickerChange(e) {
|
|
|
|
|
|
const field = e.currentTarget.dataset.field
|
|
|
|
|
|
const optionKeyMap = {
|
|
|
|
|
|
income_range: 'incomeOptions'
|
|
|
|
|
|
}
|
|
|
|
|
|
const options = this.data[optionKeyMap[field] || `${field}Options`]
|
|
|
|
|
|
const index = Number(e.detail.value)
|
|
|
|
|
|
if (!options || !options[index]) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const form = {
|
|
|
|
|
|
...this.data.form,
|
|
|
|
|
|
[field]: options[index].value
|
|
|
|
|
|
}
|
|
|
|
|
|
this.setData({ form })
|
|
|
|
|
|
this.syncPickerTexts(form)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
syncPickerTexts(form) {
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
genderText: this.getOptionLabel(this.data.genderOptions, form.gender, '请选择'),
|
|
|
|
|
|
educationText: this.getOptionLabel(this.data.educationOptions, form.education, '请选择'),
|
|
|
|
|
|
incomeText: this.getOptionLabel(this.data.incomeOptions, form.income_range, '请选择')
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
getOptionLabel(options, value, fallback) {
|
|
|
|
|
|
const normalizedValue = value === null || value === undefined || value === '' ? value : Number(value)
|
|
|
|
|
|
const matched = (options || []).find((item) => item.value === normalizedValue)
|
|
|
|
|
|
return matched ? matched.label : fallback
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
sanitizeText(value, maxLength) {
|
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return String(value).trim().slice(0, maxLength)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
sanitizeNumber(value) {
|
|
|
|
|
|
if (value === null || value === undefined || value === '') {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const num = Number(value)
|
|
|
|
|
|
return Number.isFinite(num) ? num : null
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
buildProfilePayload() {
|
|
|
|
|
|
const payload = {
|
|
|
|
|
|
nickname: this.sanitizeText(this.data.form.nickname, PROFILE_LIMITS.nickname) || null,
|
|
|
|
|
|
real_name: this.sanitizeText(this.data.form.real_name, PROFILE_LIMITS.real_name) || null,
|
|
|
|
|
|
gender: this.sanitizeNumber(this.data.form.gender),
|
|
|
|
|
|
birth_year: this.sanitizeNumber(this.data.form.birth_year),
|
|
|
|
|
|
city: this.sanitizeText(this.data.form.city, PROFILE_LIMITS.city) || null,
|
|
|
|
|
|
height: this.sanitizeNumber(this.data.form.height),
|
|
|
|
|
|
education: this.sanitizeNumber(this.data.form.education),
|
|
|
|
|
|
job_industry: this.sanitizeText(this.data.form.job_industry, PROFILE_LIMITS.job_industry) || null,
|
|
|
|
|
|
job_company: this.sanitizeText(this.data.form.job_company, PROFILE_LIMITS.job_company) || null,
|
|
|
|
|
|
income_range: this.sanitizeNumber(this.data.form.income_range),
|
|
|
|
|
|
prefer_city: this.sanitizeText(this.data.form.prefer_city, PROFILE_LIMITS.prefer_city) || null,
|
|
|
|
|
|
self_intro: this.sanitizeText(this.data.form.self_intro, PROFILE_LIMITS.self_intro) || null,
|
|
|
|
|
|
personality_tags: Array.isArray(this.data.form.personality_tags)
|
|
|
|
|
|
? this.data.form.personality_tags.map((item) => this.sanitizeText(item, PROFILE_LIMITS.tagItem)).filter(Boolean).slice(0, PROFILE_LIMITS.listItems)
|
|
|
|
|
|
: [],
|
|
|
|
|
|
hobbies: Array.isArray(this.data.form.hobbies)
|
|
|
|
|
|
? this.data.form.hobbies.map((item) => this.sanitizeText(item, PROFILE_LIMITS.tagItem)).filter(Boolean).slice(0, PROFILE_LIMITS.listItems)
|
|
|
|
|
|
: [],
|
|
|
|
|
|
wechat_id: this.sanitizeText(this.data.form.wechat_id, PROFILE_LIMITS.wechat_id) || null,
|
|
|
|
|
|
phone: this.sanitizeText(this.data.form.phone, PROFILE_LIMITS.phone) || null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return payload
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
getValidationLabel(field) {
|
|
|
|
|
|
const labels = {
|
|
|
|
|
|
nickname: '昵称',
|
|
|
|
|
|
real_name: '真实姓名',
|
|
|
|
|
|
city: '所在城市',
|
|
|
|
|
|
job_industry: '行业',
|
|
|
|
|
|
job_company: '公司',
|
|
|
|
|
|
prefer_city: '期望城市',
|
|
|
|
|
|
self_intro: '个人介绍',
|
|
|
|
|
|
wechat_id: '微信号',
|
|
|
|
|
|
phone: '手机号',
|
|
|
|
|
|
personality_tags: '性格标签',
|
|
|
|
|
|
hobbies: '兴趣爱好',
|
|
|
|
|
|
gender: '性别',
|
|
|
|
|
|
birth_year: '出生年份',
|
|
|
|
|
|
height: '身高',
|
|
|
|
|
|
education: '学历',
|
|
|
|
|
|
income_range: '收入范围'
|
|
|
|
|
|
}
|
|
|
|
|
|
return labels[field] || field
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-17 19:26:32 +08:00
|
|
|
|
chooseAvatar() {
|
|
|
|
|
|
wx.chooseMedia({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
mediaType: ['image'],
|
|
|
|
|
|
sourceType: ['album', 'camera'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
const tempFilePath = res.tempFiles && res.tempFiles[0] && res.tempFiles[0].tempFilePath
|
|
|
|
|
|
if (tempFilePath) {
|
|
|
|
|
|
this.uploadAvatar(tempFilePath)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
if (err && !String(err.errMsg || '').includes('cancel')) {
|
|
|
|
|
|
wx.showToast({ title: '请选择头像图片', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
uploadAvatar(tempFilePath) {
|
|
|
|
|
|
wx.getFileInfo({
|
|
|
|
|
|
filePath: tempFilePath,
|
|
|
|
|
|
success: (info) => {
|
|
|
|
|
|
if (info.size > MAX_AVATAR_SIZE) {
|
|
|
|
|
|
wx.showToast({ title: '头像不能超过 5MB', icon: 'none' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
wx.uploadFile({
|
|
|
|
|
|
url: `${getApiBaseUrl()}/upload/avatar`,
|
|
|
|
|
|
filePath: tempFilePath,
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
header: {
|
|
|
|
|
|
Authorization: wx.getStorageSync('token') ? `Bearer ${wx.getStorageSync('token')}` : ''
|
|
|
|
|
|
},
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const body = JSON.parse(res.data)
|
|
|
|
|
|
if (body.code !== 0) {
|
|
|
|
|
|
throw new Error(body.message || '上传失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
const rawAvatarUrl = body.data.avatar_url || ''
|
2026-04-17 21:28:56 +08:00
|
|
|
|
const rawAvatarBlurUrl = body.data.avatar_blur_url || ''
|
|
|
|
|
|
const avatarUrl = resolveImageUrl(rawAvatarUrl)
|
|
|
|
|
|
const avatarBlurUrl = resolveImageUrl(rawAvatarBlurUrl)
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const avatarVersion = String(Date.now())
|
2026-04-17 21:28:56 +08:00
|
|
|
|
const form = { ...this.data.form, avatar_url: avatarUrl, avatar_blur_url: avatarBlurUrl }
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const cachedProfile = cloneProfileCache() || {}
|
2026-04-17 21:28:56 +08:00
|
|
|
|
const nextProfile = normalizeAvatarState({
|
2026-04-17 19:26:32 +08:00
|
|
|
|
...cachedProfile,
|
|
|
|
|
|
...form,
|
|
|
|
|
|
avatar_url: avatarUrl,
|
2026-04-17 21:28:56 +08:00
|
|
|
|
avatar_blur_url: avatarBlurUrl,
|
2026-04-17 19:26:32 +08:00
|
|
|
|
nicknameText: form.nickname || cachedProfile.nicknameText || '未设置昵称',
|
|
|
|
|
|
auditStatusText: cachedProfile.auditStatusText || '待审核'
|
2026-04-17 21:28:56 +08:00
|
|
|
|
})
|
2026-04-17 19:26:32 +08:00
|
|
|
|
saveProfileCache(nextProfile)
|
|
|
|
|
|
this.setData({ form, avatarUrl: addCacheVersion(avatarUrl, avatarVersion), avatarVersion })
|
|
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
const prevPage = pages[pages.length - 2]
|
|
|
|
|
|
if (prevPage && typeof prevPage.applyUserInfo === 'function') {
|
|
|
|
|
|
prevPage.applyUserInfo(nextProfile)
|
|
|
|
|
|
}
|
|
|
|
|
|
wx.showToast({ title: '头像已更新', icon: 'success' })
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
wx.showToast({ title: err.message || '上传失败', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
wx.showToast({ title: '上传失败,请重试', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
wx.showToast({ title: '无法读取图片大小', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
validateProfilePayload(payload) {
|
|
|
|
|
|
const errors = []
|
|
|
|
|
|
const checkTextLength = (field, maxLength) => {
|
|
|
|
|
|
const value = payload[field]
|
|
|
|
|
|
if (typeof value === 'string' && value.length > maxLength) {
|
|
|
|
|
|
errors.push(`${this.getValidationLabel(field)}不能超过${maxLength}个字符`)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
checkTextLength('nickname', PROFILE_LIMITS.nickname)
|
|
|
|
|
|
checkTextLength('real_name', PROFILE_LIMITS.real_name)
|
|
|
|
|
|
checkTextLength('city', PROFILE_LIMITS.city)
|
|
|
|
|
|
checkTextLength('job_industry', PROFILE_LIMITS.job_industry)
|
|
|
|
|
|
checkTextLength('job_company', PROFILE_LIMITS.job_company)
|
|
|
|
|
|
checkTextLength('prefer_city', PROFILE_LIMITS.prefer_city)
|
|
|
|
|
|
checkTextLength('self_intro', PROFILE_LIMITS.self_intro)
|
|
|
|
|
|
checkTextLength('wechat_id', PROFILE_LIMITS.wechat_id)
|
|
|
|
|
|
checkTextLength('phone', PROFILE_LIMITS.phone)
|
|
|
|
|
|
|
|
|
|
|
|
;['personality_tags', 'hobbies'].forEach((field) => {
|
|
|
|
|
|
const list = payload[field]
|
|
|
|
|
|
const label = this.getValidationLabel(field)
|
|
|
|
|
|
if (!Array.isArray(list)) {
|
|
|
|
|
|
errors.push(`${label}格式不正确`)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (list.length > PROFILE_LIMITS.listItems) {
|
|
|
|
|
|
errors.push(`${label}最多${PROFILE_LIMITS.listItems}项`)
|
|
|
|
|
|
}
|
|
|
|
|
|
list.forEach((item) => {
|
|
|
|
|
|
if (item.length > PROFILE_LIMITS.tagItem) {
|
|
|
|
|
|
errors.push(`${label}单项不能超过${PROFILE_LIMITS.tagItem}个字符`)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
;['gender', 'birth_year', 'height', 'education', 'income_range'].forEach((field) => {
|
|
|
|
|
|
const value = payload[field]
|
|
|
|
|
|
if (value !== null && value !== undefined && !Number.isFinite(value)) {
|
|
|
|
|
|
errors.push(`${this.getValidationLabel(field)}格式不正确`)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return errors
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-17 10:49:14 +08:00
|
|
|
|
async saveProfile() {
|
|
|
|
|
|
this.setData({ saving: true })
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = this.buildProfilePayload()
|
|
|
|
|
|
const validationErrors = this.validateProfilePayload(payload)
|
|
|
|
|
|
if (validationErrors.length > 0) {
|
|
|
|
|
|
wx.showModal({
|
|
|
|
|
|
title: '资料有误',
|
|
|
|
|
|
content: validationErrors.slice(0, 3).join(';'),
|
|
|
|
|
|
showCancel: false
|
|
|
|
|
|
})
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await request({
|
|
|
|
|
|
url: '/users/me',
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
data: payload
|
|
|
|
|
|
})
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const nextForm = { ...this.data.form, ...payload }
|
|
|
|
|
|
const avatarVersion = this.data.avatarVersion || Date.now().toString()
|
|
|
|
|
|
const cachedProfile = cloneProfileCache() || {}
|
2026-04-17 21:28:56 +08:00
|
|
|
|
const nextProfile = normalizeAvatarState({
|
2026-04-17 19:26:32 +08:00
|
|
|
|
...cachedProfile,
|
|
|
|
|
|
...nextForm,
|
|
|
|
|
|
avatar_url: nextForm.avatar_url || cachedProfile.avatar_url || '',
|
|
|
|
|
|
avatar_blur_url: nextForm.avatar_blur_url || cachedProfile.avatar_blur_url || ''
|
2026-04-17 21:28:56 +08:00
|
|
|
|
})
|
2026-04-17 19:26:32 +08:00
|
|
|
|
saveProfileCache(nextProfile)
|
2026-04-17 10:49:14 +08:00
|
|
|
|
this.setData({
|
2026-04-17 19:26:32 +08:00
|
|
|
|
form: nextForm,
|
2026-04-17 21:28:56 +08:00
|
|
|
|
avatarUrl: addCacheVersion(nextProfile.avatar_url || '', avatarVersion),
|
2026-04-17 19:26:32 +08:00
|
|
|
|
avatarVersion
|
2026-04-17 10:49:14 +08:00
|
|
|
|
})
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
const prevPage = pages[pages.length - 2]
|
|
|
|
|
|
if (prevPage && typeof prevPage.applyUserInfo === 'function') {
|
|
|
|
|
|
prevPage.applyUserInfo(nextProfile)
|
|
|
|
|
|
}
|
|
|
|
|
|
this.syncPickerTexts(nextForm)
|
2026-04-17 10:49:14 +08:00
|
|
|
|
wx.showToast({ title: '保存成功', icon: 'success' })
|
|
|
|
|
|
return true
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Save profile failed:', err)
|
|
|
|
|
|
wx.showToast({ title: err.message || '保存失败', icon: 'none' })
|
|
|
|
|
|
return false
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.setData({ saving: false })
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async submitAudit() {
|
|
|
|
|
|
const saveOk = await this.saveProfile()
|
|
|
|
|
|
if (!saveOk) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const payload = this.buildProfilePayload()
|
|
|
|
|
|
const validationErrors = this.validateProfilePayload(payload)
|
|
|
|
|
|
if (validationErrors.length > 0) {
|
|
|
|
|
|
wx.showModal({
|
|
|
|
|
|
title: '资料有误',
|
|
|
|
|
|
content: validationErrors.slice(0, 3).join(';'),
|
|
|
|
|
|
showCancel: false
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const missingFields = AUDIT_REQUIRED_FIELDS.filter((field) => {
|
|
|
|
|
|
const value = payload[field]
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
return value.length === 0
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value && typeof value === 'object') {
|
|
|
|
|
|
return Object.keys(value).length === 0
|
|
|
|
|
|
}
|
|
|
|
|
|
return value === null || value === undefined || value === ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (missingFields.length > 0) {
|
|
|
|
|
|
const labelMap = {
|
|
|
|
|
|
personality_tags: '性格标签',
|
|
|
|
|
|
hobbies: '兴趣爱好',
|
|
|
|
|
|
wechat_id: '微信号',
|
|
|
|
|
|
phone: '手机号'
|
|
|
|
|
|
}
|
|
|
|
|
|
wx.showModal({
|
|
|
|
|
|
title: '资料未完善',
|
|
|
|
|
|
content: `提交审核前请先补充:${missingFields.map((field) => labelMap[field] || field).join('、')}`,
|
|
|
|
|
|
showCancel: false
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await request({
|
|
|
|
|
|
url: '/users/submit-audit',
|
|
|
|
|
|
method: 'POST'
|
|
|
|
|
|
})
|
|
|
|
|
|
wx.showToast({ title: '已提交审核', icon: 'success' })
|
2026-04-17 19:26:32 +08:00
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
const prevPage = pages[pages.length - 2]
|
|
|
|
|
|
if (prevPage && typeof prevPage.loadProfile === 'function') {
|
|
|
|
|
|
prevPage.loadProfile()
|
|
|
|
|
|
}
|
2026-04-17 10:49:14 +08:00
|
|
|
|
setTimeout(() => {
|
2026-04-17 19:26:32 +08:00
|
|
|
|
wx.navigateBack({ delta: 1 })
|
2026-04-17 10:49:14 +08:00
|
|
|
|
}, 300)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Submit audit failed:', err)
|
|
|
|
|
|
wx.showToast({ title: err.message || '提交审核失败', icon: 'none' })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|