kydc/me/me.js

306 lines
10 KiB
JavaScript
Raw Normal View History

2026-04-18 17:16:49 +08:00
const app = getApp()
2026-05-12 20:27:47 +08:00
function normalizeAvatarUrl(avatarUrl, baseUrl) {
if (!avatarUrl) return ''
return avatarUrl.startsWith('http') ? avatarUrl : `${baseUrl}${avatarUrl}`
}
function requestFailHint(errMsg) {
const m = String(errMsg || '').toLowerCase()
if (m.includes('domain')) return '域名未配置或未开启 HTTPS'
if (m.includes('ssl') || m.includes('certificate')) return 'HTTPS 证书异常,请检查服务端配置'
2026-05-12 20:27:47 +08:00
if (m.includes('timeout') || m.includes('timed out')) return '连接超时,请稍后重试'
return '网络异常,请稍后重试'
2026-05-12 20:27:47 +08:00
}
2026-05-07 10:19:07 +08:00
const DEFAULT_AVATAR = 'data:image/svg+xml;utf8,' + encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#c19cff"/>
<stop offset="100%" stop-color="#00e3fd"/>
</linearGradient>
</defs>
<rect width="320" height="320" rx="160" fill="#1d2025"/>
<circle cx="160" cy="160" r="154" fill="url(#bg)" opacity="0.18"/>
<circle cx="160" cy="126" r="54" fill="#f6f6fc" opacity="0.92"/>
<path d="M72 268c18-44 52-70 88-70s70 26 88 70" fill="#f6f6fc" opacity="0.92"/>
</svg>
`)
2026-04-18 17:16:49 +08:00
Page({
data: {
name: '',
2026-04-23 11:34:54 +08:00
nickname: '',
username: '',
2026-04-18 17:16:49 +08:00
days: 0,
badges: 0,
2026-04-23 11:34:54 +08:00
avatarUrl: '',
2026-05-07 10:19:07 +08:00
defaultAvatarUrl: DEFAULT_AVATAR,
2026-04-19 11:56:49 +08:00
loading: false,
error: '',
2026-04-23 11:34:54 +08:00
showNicknameModal: false,
nicknameDraft: '',
authMode: 'login',
authForm: {
username: '',
password: '',
nickname: '',
},
2026-04-18 17:16:49 +08:00
},
onShow() {
this.loadProfile()
},
isLoggedIn() {
return !!app.globalData.authToken
},
onAuthModeChange(e) {
this.setData({ authMode: e.currentTarget.dataset.mode || 'login', error: '' })
},
onAuthInput(e) {
const { field } = e.currentTarget.dataset
const value = e.detail.value || ''
this.setData({ [`authForm.${field}`]: value })
},
submitAuth() {
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
const mode = this.data.authMode
const username = (this.data.authForm.username || '').trim()
const password = (this.data.authForm.password || '').trim()
const nickname = (this.data.authForm.nickname || '').trim()
if (!username || !password) {
wx.showToast({ title: '请输入账号和密码', icon: 'none' })
return
}
if (mode === 'register' && !nickname) {
wx.showToast({ title: '请输入昵称', icon: 'none' })
2026-04-19 11:56:49 +08:00
return
}
2026-05-12 20:27:47 +08:00
this.setData({ loading: true, error: '' })
2026-04-23 11:34:54 +08:00
wx.request({
url: `${baseUrl}/api/v1/auth/${mode === 'register' ? 'register' : 'login'}`,
method: 'POST',
header: { 'Content-Type': 'application/json' },
data: mode === 'register' ? { username, password, nickname } : { username, password },
2026-04-23 11:34:54 +08:00
success: (res) => {
const payload = res.data?.data || null
if (res.statusCode < 200 || res.statusCode >= 300 || !payload?.auth_token) {
const message = res.data?.detail || res.data?.message || `${mode === 'register' ? '注册' : '登录'}失败`
this.setData({ error: message })
wx.showToast({ title: message, icon: 'none' })
2026-05-12 20:27:47 +08:00
return
}
app.setLoginSession(payload)
2026-04-23 11:34:54 +08:00
this.setData({
authForm: { username: '', password: '', nickname: '' },
username: payload.username || '',
2026-04-23 11:34:54 +08:00
})
wx.showToast({ title: mode === 'register' ? '注册成功' : '登录成功', icon: 'success' })
this.loadProfile()
},
fail: (err) => {
const message = requestFailHint(err.errMsg)
this.setData({ error: message })
wx.showToast({ title: message, icon: 'none' })
2026-04-23 11:34:54 +08:00
},
complete: () => this.setData({ loading: false }),
})
},
loadProfile() {
const token = app.ensureLogin()
Promise.resolve(token).then((authToken) => {
if (!authToken) {
2026-05-07 10:19:07 +08:00
this.setData({
loading: false,
name: '未登录',
nickname: '未登录',
username: '',
avatarUrl: '',
nicknameDraft: '',
badges: 0,
days: 0,
error: '',
2026-05-07 10:19:07 +08:00
})
return
}
this.setData({ loading: true, error: '' })
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
wx.request({
url: `${baseUrl}/api/v1/auth/profile`,
method: 'GET',
header: app.getAuthHeader(),
success: (res) => {
const body = res.data || {}
const profile = body.data
const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0 && profile
if (!ok) {
const message = body.detail || body.message || '个人信息加载失败'
this.setData({ error: message })
return
}
const identity = profile.username || app.globalData.currentUser?.username || ''
const avatarUrl = wx.getStorageSync(`avatarUrl:${identity}`) || profile.avatar_url || ''
const normalizedAvatar = normalizeAvatarUrl(avatarUrl, baseUrl)
const nickname = profile.nickname || identity || '考研战士'
app.globalData.currentUser = {
...(app.globalData.currentUser || {}),
id: profile.id,
username: profile.username || '',
nickname,
loginType: profile.login_type || 'account',
}
wx.setStorageSync('currentUser', app.globalData.currentUser)
this.setData({
name: nickname,
nickname,
username: profile.username || '',
avatarUrl: normalizedAvatar,
nicknameDraft: nickname,
days: Number(profile.days || 0),
error: '',
})
this.loadAchievements()
},
fail: (err) => {
this.setData({ error: requestFailHint(err.errMsg) })
},
complete: () => this.setData({ loading: false }),
})
2026-04-23 11:34:54 +08:00
})
},
openNicknameModal() {
2026-05-07 10:19:07 +08:00
this.setData({ showNicknameModal: true, nicknameDraft: this.data.nickname || this.data.name || '考研战士' })
2026-04-23 11:34:54 +08:00
},
onAvatarError() {
2026-05-12 20:27:47 +08:00
this.setData({ avatarUrl: this.data.defaultAvatarUrl })
2026-04-23 11:34:54 +08:00
},
closeNicknameModal() {
2026-05-07 10:19:07 +08:00
this.setData({ showNicknameModal: false, nicknameDraft: this.data.nickname || this.data.name || '考研战士' })
2026-04-23 11:34:54 +08:00
},
onNicknameInput(e) {
this.setData({ nicknameDraft: e.detail.value })
},
saveNickname() {
if (!this.isLoggedIn()) return
2026-04-23 11:34:54 +08:00
const nickname = (this.data.nicknameDraft || '').trim()
if (!nickname) {
2026-05-07 10:19:07 +08:00
wx.showToast({ title: '请输入昵称', icon: 'none' })
2026-04-23 11:34:54 +08:00
return
}
wx.request({
2026-05-12 20:27:47 +08:00
url: `${app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl}/api/v1/auth/profile`,
2026-04-23 11:34:54 +08:00
method: 'PUT',
header: app.getAuthHeader({ 'Content-Type': 'application/json' }),
data: { nickname },
2026-04-23 11:34:54 +08:00
success: (res) => {
const profile = res.data?.data || {}
const nextNickname = profile.nickname || nickname
const currentUser = { ...(app.globalData.currentUser || {}), nickname: nextNickname }
app.globalData.currentUser = currentUser
wx.setStorageSync('currentUser', currentUser)
2026-04-23 11:34:54 +08:00
this.setData({ nickname: nextNickname, name: nextNickname, nicknameDraft: nextNickname, showNicknameModal: false })
2026-05-07 10:19:07 +08:00
wx.showToast({ title: '保存成功', icon: 'success' })
2026-04-23 11:34:54 +08:00
},
2026-05-07 10:19:07 +08:00
fail: () => wx.showToast({ title: '保存失败', icon: 'none' }),
2026-04-23 11:34:54 +08:00
})
},
2026-05-12 20:27:47 +08:00
onChooseAvatar(e) {
const tempPath = e.detail?.avatarUrl || ''
if (!this.isLoggedIn()) {
2026-05-07 10:19:07 +08:00
wx.showToast({ title: '请先登录', icon: 'none' })
2026-04-23 11:34:54 +08:00
return
}
2026-05-12 20:27:47 +08:00
if (!tempPath) {
wx.showToast({ title: '未选择头像', icon: 'none' })
return
}
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
wx.uploadFile({
url: `${baseUrl}/api/v1/auth/avatar`,
2026-05-12 20:27:47 +08:00
filePath: tempPath,
name: 'file',
header: app.getAuthHeader(),
2026-05-12 20:27:47 +08:00
success: (uploadRes) => {
try {
const payload = JSON.parse(uploadRes.data || '{}')
const ok = uploadRes.statusCode >= 200 && uploadRes.statusCode < 300 && payload.code === 0 && payload.data
2026-05-12 20:27:47 +08:00
if (!ok) {
wx.showToast({ title: payload.message || '头像上传失败', icon: 'none' })
return
}
const rel = payload.data.avatar_url || ''
const full = normalizeAvatarUrl(rel, baseUrl)
const key = this.data.username || app.globalData.currentUser?.username || ''
if (key) wx.setStorageSync(`avatarUrl:${key}`, full)
2026-05-12 20:27:47 +08:00
this.setData({ avatarUrl: full })
wx.showToast({ title: '头像已更新', icon: 'success' })
} catch {
wx.showToast({ title: '头像上传结果异常', icon: 'none' })
}
2026-04-23 11:34:54 +08:00
},
2026-05-12 20:27:47 +08:00
fail: (err) => wx.showToast({ title: requestFailHint(err.errMsg), icon: 'none' }),
2026-04-23 11:34:54 +08:00
})
2026-04-19 11:56:49 +08:00
},
loadAchievements() {
if (!this.isLoggedIn()) return
2026-05-12 20:27:47 +08:00
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
wx.request({
2026-05-12 20:27:47 +08:00
url: `${baseUrl}/api/v1/achievements`,
header: app.getAuthHeader(),
success: (res) => {
2026-05-12 20:27:47 +08:00
const body = res.data || {}
const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0 && Array.isArray(body.data)
if (!ok) return
const badges = body.data.filter((i) => i.is_unlocked).length
this.setData({ badges })
2026-05-12 20:27:47 +08:00
},
})
},
logout() {
if (!this.isLoggedIn()) return
wx.request({
url: `${app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl}/api/v1/auth/logout`,
method: 'POST',
header: app.getAuthHeader(),
complete: () => {
app.clearLoginSession()
this.setData({
name: '未登录',
nickname: '未登录',
username: '',
avatarUrl: '',
nicknameDraft: '',
badges: 0,
days: 0,
error: '',
2026-05-12 20:27:47 +08:00
})
wx.showToast({ title: '已退出登录', icon: 'success' })
},
2026-04-18 17:16:49 +08:00
})
},
goTo(e) {
const { url } = e.currentTarget.dataset
if (!this.isLoggedIn()) {
2026-05-07 10:19:07 +08:00
wx.showToast({ title: '请先登录', icon: 'none' })
2026-04-19 11:56:49 +08:00
return
}
2026-04-18 17:16:49 +08:00
wx.navigateTo({ url })
},
goHome() {
2026-04-19 16:39:02 +08:00
wx.reLaunch({ url: '/pages/index/index' })
2026-04-18 17:16:49 +08:00
},
2026-04-19 16:39:02 +08:00
goToReview() {
wx.navigateTo({ url: '/pages/review-config/review-config' })
},
goToMe() {},
2026-04-23 11:34:54 +08:00
noop() {},
2026-04-18 17:16:49 +08:00
})