refactor: migrate to Tencent Cloud backend

这个提交包含在:
Guoguo
2026-04-28 22:56:47 +08:00
父节点 267c75718b
当前提交 444c91c0b0
修改 102 个文件,包含 17927 行新增1997 行删除
+29 -3
查看文件
@@ -5,13 +5,17 @@ Page({
statusBarHeight: 44,
scanning: true,
scanProgress: 0,
regions: 0x7F,
regionData: [],
error: ''
},
onLoad: function () {
onLoad: function (options) {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
regions: parseInt(options.regions) || 0x7F
})
this.startScan()
},
@@ -38,6 +42,13 @@ Page({
})
ble.queryStatus().catch(function () {})
setTimeout(function () {
clearInterval(progressTimer)
if (!self.data.scanning) return
self.setData({ scanning: false, scanProgress: 100 })
self.parseScanResults({ region_mask: self.data.regions })
}, 5000)
},
parseScanResults: function (status) {
@@ -49,6 +60,21 @@ Page({
},
onNext: function () {
wx.navigateTo({ url: '/pages/treatment-setup/treatment-setup' })
var mask = this.data.regions || 0x7F
ble.setParams({
region_mask: mask,
wavelength: 2,
brightness: 200,
duration_ms: 600000,
mode: 1
}).then(function () {
return ble.startTreatment(mask)
}).then(function () {
wx.redirectTo({
url: '/pages/treating/treating?regions=' + mask + '&wavelength=2&duration=600000&mode=1'
})
}).catch(function (err) {
wx.showToast({ title: err.error_msg || '启动失败', icon: 'none' })
})
}
})
+11 -3
查看文件
@@ -1,4 +1,5 @@
var ble = require('../../services/ble')
var http = require('../../utils/request')
var app = getApp()
var SCAN_TIMEOUT = 15000
@@ -56,9 +57,16 @@ Page({
var userId = app.globalData.userId || ''
ble.on('bind_result', function (result) {
if (result.success) {
self.setData({ state: 'done' })
wx.redirectTo({
url: '/pages/bind-success/bind-success?device_id=' + self.data.deviceId
http.post('/api/v1/device/bind/confirm', {
device_id: self.data.deviceId,
bind_token: self.data.bindToken
}).then(function () {
self.setData({ state: 'done' })
wx.redirectTo({
url: '/pages/bind-success/bind-success?device_id=' + self.data.deviceId
})
}).catch(function (err) {
self.setData({ state: 'error', error: err.message || '后台确认绑定失败' })
})
} else {
self.setData({ state: 'error', error: '设备绑定失败' })
+1 -1
查看文件
@@ -1,5 +1,4 @@
var ble = require('../../services/ble')
var mqtt = require('../../services/mqtt')
var http = require('../../utils/request')
var app = getApp()
@@ -43,6 +42,7 @@ Page({
var devices = data.devices || []
self.setData({ hasDevice: devices.length > 0 })
if (devices.length > 0) {
app.globalData.currentDevice = devices[0]
self.setData({
deviceName: devices[0].name || devices[0].device_id || '我的光面膜',
deviceInfo: devices[0]
+86 -2
查看文件
@@ -1,9 +1,11 @@
var app = getApp()
var http = require('../../utils/request')
Page({
data: {
statusBarHeight: 44,
loading: false
loading: false,
userProfile: null
},
onLoad: function () {
@@ -15,12 +17,94 @@ Page({
var self = this
self.setData({ loading: true })
app.doLogin().then(function () {
self.getWechatProfile().then(function (profile) {
self.setData({ userProfile: profile })
return app.doLogin()
}).then(function () {
return self.saveProfile()
}).then(function () {
self.setData({ loading: false })
wx.switchTab({ url: '/pages/index/index' })
}).catch(function (err) {
self.setData({ loading: false })
wx.showToast({ title: err.message || '登录失败', icon: 'none' })
})
},
getWechatProfile: function () {
return new Promise(function (resolve) {
if (!wx.getUserProfile) {
resolve(null)
return
}
wx.getUserProfile({
desc: '用于完善会员资料',
success: function (res) {
resolve(res.userInfo || null)
},
fail: function () {
resolve(null)
}
})
})
},
saveProfile: function () {
var profile = this.data.userProfile || {}
if (!profile.nickName && !profile.avatarUrl) return Promise.resolve()
return this.updateProfile({
nickname: profile.nickName || '',
avatar: profile.avatarUrl || '',
gender: profile.gender || 0
})
},
updateProfile: function (payload) {
return http.put('/api/v1/user/profile', payload).then(function () {
return app.loadProfile()
})
},
uploadAvatar: function (filePath) {
var ext = 'jpg'
var match = String(filePath).match(/\.([a-zA-Z0-9]+)$/)
if (match && match[1]) ext = match[1].toLowerCase()
var contentType = ext === 'png' ? 'image/png' : 'image/jpeg'
return http.post('/api/v1/user/avatar/upload-url', {
ext: ext,
content_type: contentType
}).then(function (data) {
return new Promise(function (resolve, reject) {
wx.uploadFile({
url: data.upload_url,
filePath: filePath,
name: 'file',
header: { 'Content-Type': contentType },
success: function (res) {
if (res.statusCode >= 200 && res.statusCode < 300) resolve(data.public_url)
else reject({ message: '头像上传失败' })
},
fail: function () { reject({ message: '头像上传失败' }) }
})
})
})
},
onGetPhoneNumber: function (e) {
var self = this
if (!e.detail || !e.detail.code) {
wx.showToast({ title: '已跳过手机号授权', icon: 'none' })
return
}
self.setData({ loading: true })
app.doLogin().then(function () {
return http.post('/api/v1/user/phone', { code: e.detail.code })
}).then(function () {
self.setData({ loading: false })
wx.switchTab({ url: '/pages/index/index' })
}).catch(function (err) {
self.setData({ loading: false })
wx.showToast({ title: err.message || '手机号授权失败', icon: 'none' })
})
}
})
+7 -2
查看文件
@@ -9,14 +9,19 @@
<view class="flower-area">
<text class="flower">🌸</text>
<view class="desc">获取你的手机号</view>
<view class="desc">提供更好的服务</view>
<view class="desc">授权获取微信昵称和头像</view>
<view class="desc">用于完善会员资料</view>
</view>
<button class="login-btn" loading="{{loading}}" bindtap="onLogin" disabled="{{loading}}">
微信授权登录
</button>
<!-- 手机号授权已预留。需要时展示该按钮并隐藏上面的普通登录按钮。 -->
<button class="phone-btn disabled-phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" disabled="{{loading}}">
授权手机号登录(预留)
</button>
<view class="agreement">登录即表示同意《用户协议》和《隐私政策》</view>
</view>
</view>
+21
查看文件
@@ -46,6 +46,27 @@
border: none;
}
.phone-btn {
display: block;
width: 100%;
padding: 28rpx;
background: #ffffff;
color: #E6508C;
border: 2rpx solid #E6508C;
border-radius: 20rpx;
font-size: 30rpx;
text-align: center;
margin-top: 24rpx;
}
.phone-btn::after {
border: none;
}
.disabled-phone-btn {
display: none;
}
.agreement {
text-align: center;
font-size: 24rpx;
+1 -1
查看文件
@@ -51,7 +51,7 @@ Page({
http.post('/api/v1/device/bind', { device_id: deviceId }).then(function (data) {
self.setData({ scanning: false })
wx.redirectTo({
url: '/pages/bind-success/bind-success?device_id=' + deviceId
url: '/pages/ble-connect/ble-connect?device_id=' + encodeURIComponent(deviceId) + '&bind_token=' + encodeURIComponent(data.bind_token || '')
})
}).catch(function (err) {
self.setData({ scanning: false })
+54 -10
查看文件
@@ -1,4 +1,5 @@
var ble = require('../../services/ble')
var commandSync = require('../../services/command-sync')
Page({
data: {
@@ -13,7 +14,9 @@ Page({
temperature: 0,
progress: 0,
paused: false,
completed: false
completed: false,
startedAt: 0,
localTimer: null
},
onLoad: function (options) {
@@ -24,36 +27,67 @@ Page({
wavelength: parseInt(options.wavelength) || 2,
duration: parseInt(options.duration) || 600000,
mode: parseInt(options.mode) || 0,
remainingMs: parseInt(options.duration) || 600000
remainingMs: parseInt(options.duration) || 600000,
startedAt: Date.now()
})
ble.on('status', this.onStatus.bind(this))
ble.on('treatment_complete', this.onComplete.bind(this))
ble.on('exception', this.onException.bind(this))
this.startLocalTimer()
this.syncCommands()
},
onUnload: function () {
ble.off('status')
ble.off('treatment_complete')
ble.off('exception')
if (this.data.localTimer) clearInterval(this.data.localTimer)
},
onStatus: function (status) {
var remaining = status.remaining_ms || 0
startLocalTimer: function () {
var self = this
var timer = setInterval(function () {
if (self.data.completed || self.data.paused) return
var elapsed = Date.now() - self.data.startedAt
var remaining = Math.max(0, self.data.duration - elapsed)
self.updateProgress(remaining)
if (remaining <= 0) {
clearInterval(timer)
self.finishAsComplete()
}
}, 1000)
this.setData({ localTimer: timer })
},
updateProgress: function (remaining) {
var total = this.data.duration
var progress = total > 0 ? Math.round(((total - remaining) / total) * 100) : 0
var mins = Math.floor(remaining / 60000)
var secs = Math.floor((remaining % 60000) / 1000)
var text = ('0' + mins).slice(-2) + ':' + ('0' + secs).slice(-2)
this.setData({
remainingMs: remaining,
remainingText: text,
progress: progress,
remainingText: ('0' + mins).slice(-2) + ':' + ('0' + secs).slice(-2),
progress: progress
})
},
onStatus: function (status) {
var remaining = status.remaining_ms || 0
this.updateProgress(remaining)
this.setData({
battery: status.battery,
temperature: status.temperature,
paused: status.mode_state === 0x03
})
this.syncCommands()
},
syncCommands: function () {
var app = getApp()
var device = app.globalData.currentDevice || {}
var deviceId = device.device_id || this.options.device_id
if (deviceId) commandSync.sync(deviceId)
},
onComplete: function (result) {
@@ -71,6 +105,16 @@ Page({
}, 1000)
},
finishAsComplete: function () {
var elapsed = Math.min(this.data.duration, Date.now() - this.data.startedAt)
this.onComplete({
session_id: 'SESS' + Date.now(),
regions: this.data.regions,
total_duration_ms: elapsed,
avg_pd: 0
})
},
onException: function (err) {
wx.showModal({
title: '设备异常',
@@ -89,8 +133,8 @@ Page({
content: '确定要提前结束本次护理吗?',
success: function (res) {
if (res.confirm) {
ble.stopTreatment().then(function () {
wx.navigateBack()
ble.stopTreatment().catch(function () {}).then(function () {
self.finishAsComplete()
})
}
}
@@ -13,6 +13,7 @@ Page({
selectedMode: 0,
subExpired: false,
subDays: 6,
fixedDurationMs: 600000,
submitting: false,
error: ''
},
@@ -26,7 +27,7 @@ Page({
checkSubscription: function () {
var http = require('../../utils/request')
var self = this
http.get('/api/v1/subscription/status').then(function (sub) {
http.get('/api/v1/subscription').then(function (sub) {
self.setData({
subExpired: sub.status !== 'active',
subDays: sub.remaining_days || 0
@@ -35,6 +36,7 @@ Page({
},
onToggleRegion: function (e) {
if (this.data.selectedMode === 0) return
var idx = e.currentTarget.dataset.idx
var regions = this.data.regions
regions[idx].checked = !regions[idx].checked
@@ -47,7 +49,13 @@ Page({
wx.navigateTo({ url: '/pages/subscribe-prompt/subscribe-prompt' })
return
}
this.setData({ selectedMode: mode })
var data = { selectedMode: mode }
if (mode === 0) {
var regions = this.data.regions
regions.forEach(function (r) { r.checked = true })
data.regions = regions
}
this.setData(data)
},
onStart: function () {
@@ -66,7 +74,7 @@ Page({
if (self.data.selectedMode === 1) {
self.setData({ submitting: false })
wx.navigateTo({ url: '/pages/auto-scan/auto-scan' })
wx.navigateTo({ url: '/pages/auto-scan/auto-scan?regions=' + mask })
return
}
@@ -74,7 +82,7 @@ Page({
region_mask: mask,
wavelength: 2,
brightness: 200,
duration_ms: 600000,
duration_ms: self.data.fixedDurationMs,
mode: self.data.selectedMode
}).then(function () {
return ble.startTreatment(mask)
@@ -83,7 +91,7 @@ Page({
wx.redirectTo({
url: '/pages/treating/treating?regions=' + mask +
'&wavelength=2' +
'&duration=600000' +
'&duration=' + self.data.fixedDurationMs +
'&mode=' + self.data.selectedMode
})
}).catch(function (err) {
@@ -22,13 +22,14 @@
</view>
<view class="page-title" style="font-size:28rpx;">选择护理区域</view>
<view class="mode-tip" wx:if="{{selectedMode === 0}}">普通模式固定 10 分钟,护理区域默认全脸,不可调整</view>
<view class="face-map">
<view class="face-region forehead {{regions[2].checked ? 'active' : ''}}" bindtap="onToggleRegion" data-idx="2">额头</view>
<view class="face-region left-cheek {{regions[0].checked ? 'active' : ''}}" bindtap="onToggleRegion" data-idx="0">左脸</view>
<view class="face-region right-cheek {{regions[1].checked ? 'active' : ''}}" bindtap="onToggleRegion" data-idx="1">右脸</view>
<view class="face-region nose {{regions[4].checked ? 'active' : ''}}" bindtap="onToggleRegion" data-idx="4">鼻唇</view>
<view class="face-region chin {{regions[3].checked ? 'active' : ''}}" bindtap="onToggleRegion" data-idx="3">下巴</view>
<view class="face-region forehead {{regions[2].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="2">额头</view>
<view class="face-region left-cheek {{regions[0].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="0">左脸</view>
<view class="face-region right-cheek {{regions[1].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="1">右脸</view>
<view class="face-region nose {{regions[4].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="4">鼻唇</view>
<view class="face-region chin {{regions[3].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="3">下巴</view>
</view>
<button class="btn-primary" bindtap="onStart" disabled="{{submitting}}" loading="{{submitting}}">
@@ -74,6 +74,19 @@
color: #E6508C;
}
.face-region.disabled {
border-color: #d8d8d8;
background: #eeeeee;
color: #999999;
}
.mode-tip {
color: #999999;
font-size: 24rpx;
text-align: center;
margin: -8rpx 0 20rpx;
}
.face-region.forehead {
top: 5%;
left: 25%;