文件
jw-beauty/miniprogram/pages/profile/profile.js
T
Guoguo 92416261ee feat: production readiness — feature gaps + config hardening
Miniprogram:
- Add BLE reconnect button on home page when disconnected
- Add loading states to index, profile, subscribe-plans pages
- Add profile editing (avatar + nickname) with COS upload
- Enable pull-to-refresh on history page
- Fix auto-scan self.options → self.data inconsistency
- Add console.error to silent catch blocks
- Gate 'Simple Peripheral' BLE scan behind __DEV__ flag
- Add production config comment to env.js

Admin console:
- Add empty state '暂无数据' to all 5 list views
- Replace plain text plan input with select dropdown
- Add type="date" to record date filters
- Add production config comment

Server:
- CORS origin restricted in production (env CORS_ORIGIN)
- DB pool size configurable via DB_POOL_SIZE env var
- .env.example updated with WeChat Pay + production fields
2026-05-15 09:07:41 -07:00

194 行
5.0 KiB
JavaScript

var api = require('../../utils/api')
var config = require('../../config/env')
var app = getApp()
Page({
data: {
userInfo: null,
subscription: null,
deviceCount: 0,
subRemaining: 0,
loading: true,
editing: false,
editNickname: '',
editAvatarUrl: '',
saving: false
},
onShow: function () {
this.loadProfile()
},
loadProfile: function () {
var self = this
self.setData({ loading: true })
var p1 = api.getProfile().then(function (profile) {
self.setData({
userInfo: profile,
deviceCount: profile.device_count || 0
})
}).catch(function (err) {
console.error('getProfile failed', err)
})
var p2 = api.getSubscription().then(function (sub) {
self.setData({
subscription: sub,
subRemaining: sub.remaining_days || 0
})
}).catch(function (err) {
console.error('getSubscription failed', err)
})
Promise.all([p1, p2]).then(function () {
self.setData({ loading: false })
})
},
onManageDevice: function () {
var self = this
if (this.data.deviceCount > 0) {
wx.showActionSheet({
itemList: ['解绑当前设备'],
success: function (res) {
if (res.tapIndex === 0) {
wx.showModal({
title: '确认解绑',
content: '解绑后将无法使用该设备,确定要解绑吗?',
success: function (modalRes) {
if (modalRes.confirm) {
api.unbindDevice().then(function () {
wx.showToast({ title: '已解绑', icon: 'success' })
self.loadProfile()
}).catch(function (err) {
wx.showToast({ title: err.message || '解绑失败', icon: 'none' })
})
}
}
})
}
}
})
} else {
wx.navigateTo({ url: '/pages/scan/scan' })
}
},
onViewSubscription: function () {
wx.navigateTo({ url: '/pages/subscribe-plans/subscribe-plans' })
},
onViewHistory: function () {
wx.switchTab({ url: '/pages/history/history' })
},
onHelp: function () {
wx.navigateTo({ url: '/pages/help/help' })
},
onContact: function () {
wx.navigateTo({ url: '/pages/contact/contact' })
},
onEditProfile: function () {
var info = this.data.userInfo || {}
this.setData({
editing: true,
editNickname: info.nickname || '',
editAvatarUrl: info.avatar || ''
})
},
onCancelEdit: function () {
this.setData({ editing: false })
},
onEditNicknameInput: function (e) {
this.setData({ editNickname: e.detail.value || '' })
},
onPickAvatar: function () {
var self = this
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: function (res) {
if (res.tempFiles && res.tempFiles[0]) {
self.setData({ editAvatarUrl: res.tempFiles[0].tempFilePath })
}
}
})
},
uploadAvatar: function (tempPath) {
return new Promise(function (resolve, reject) {
var token = wx.getStorageSync('token')
wx.uploadFile({
url: config.API_BASE + '/api/v1/user/avatar',
filePath: tempPath,
name: 'file',
header: { Authorization: 'Bearer ' + token },
success: function (res) {
try {
var data = JSON.parse(res.data)
if (data.code === 0 && data.data && data.data.avatar) {
resolve(data.data.avatar)
} else {
reject(new Error(data.message || '上传失败'))
}
} catch (e) {
reject(new Error('上传失败'))
}
},
fail: function () { reject(new Error('上传失败')) }
})
})
},
onSaveProfile: function () {
var self = this
var nickname = (self.data.editNickname || '').trim()
if (!nickname) {
wx.showToast({ title: '昵称不能为空', icon: 'none' })
return
}
self.setData({ saving: true })
var avatarUrl = self.data.editAvatarUrl
var isLocalFile = avatarUrl && (avatarUrl.indexOf('wxfile://') === 0 || avatarUrl.indexOf('http://tmp') === 0)
var avatarPromise = isLocalFile
? self.uploadAvatar(avatarUrl)
: Promise.resolve(avatarUrl || '')
avatarPromise.then(function (permanentUrl) {
return api.updateProfile({
nickname: nickname,
avatar: permanentUrl || ''
})
}).then(function () {
self.setData({ saving: false, editing: false })
wx.showToast({ title: '保存成功', icon: 'success' })
self.loadProfile()
}).catch(function (err) {
self.setData({ saving: false })
wx.showToast({ title: err.message || '保存失败', icon: 'none' })
})
},
onLogout: function () {
wx.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
success: function (res) {
if (res.confirm) {
wx.removeStorageSync('token')
wx.reLaunch({ url: '/pages/login/login' })
}
}
})
}
})