fix: improve UX and fix admin console display issues

Miniprogram:
- Add back buttons to all custom-nav pages
- Add debug/mock buttons (BLE connect, wear check, treatment)
  controlled by __DEV__ flag (hidden in prod)

Admin console:
- Fix log page using nonexistent fields (operator_type, target_type)
  now correctly reads admin_id/user_id/action/detail from API
- Fix subscription date fields (started_at→start_time, expired_at→expire_time)
- Wire up subscription detail/extend/renew action buttons
- Wire up device detail "查看完整日志" button
- Add "创建订阅" button to subscription toolbar
- Fix subscription status mapping (2=expired, 3=cancelled)

Docs:
- Add detailed architecture docs for server, miniprogram, admin console
这个提交包含在:
Guoguo
2026-04-28 18:32:26 -07:00
父节点 91d5937d8e
当前提交 c5f6033ccf
修改 30 个文件,包含 1509 行新增59 行删除
+25 -2
查看文件
@@ -1,4 +1,5 @@
var ble = require('../../services/ble')
var config = require('../../config/env')
Page({
data: {
@@ -8,14 +9,24 @@ Page({
regions: 0x7F,
regionData: [],
timeout: false,
error: ''
error: '',
devMode: false
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function (options) {
var app = getApp()
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
regions: parseInt(options.regions) || 0x7F
regions: parseInt(options.regions) || 0x7F,
devMode: config.__DEV__ || false
})
this.startScan()
},
@@ -80,5 +91,17 @@ Page({
}).catch(function (err) {
wx.showToast({ title: err.error_msg || '启动失败', icon: 'none' })
})
},
onMockScanDone: function () {
var self = this
if (self._progressTimer) clearInterval(self._progressTimer)
var mask = parseInt(self.options.regions) || 0x7F
wx.redirectTo({
url: '/pages/treating/treating?regions=' + mask +
'&wavelength=2' +
'&duration=600000' +
'&mode=1'
})
}
})
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">自动扫描</view>
<view class="page-header-subtitle">正在识别面部...</view>
</view>
@@ -15,6 +16,11 @@
<view class="scan-hint">请将面罩对准面部</view>
<view class="debug-actions" wx:if="{{devMode}}">
<view class="debug-label">🔧 调试工具</view>
<button class="btn-secondary btn-sm" bindtap="onMockScanDone">模拟扫描完成</button>
</view>
<view class="scan-progress" wx:if="{{scanning}}">
正在扫描... <text class="highlight">{{regionData.length || 3}}</text> 个区域
</view>
@@ -5,6 +5,14 @@ Page({
trialDays: 7
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function (options) {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-green" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">绑定成功</view>
<view class="page-header-subtitle">设备已成功绑定</view>
</view>
+48 -2
查看文件
@@ -1,5 +1,6 @@
var ble = require('../../services/ble')
var http = require('../../utils/request')
var config = require('../../config/env')
var app = getApp()
var SCAN_TIMEOUT = 15000
@@ -9,14 +10,24 @@ Page({
deviceId: '',
bindToken: '',
state: 'scanning',
error: ''
error: '',
devMode: false
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function (options) {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.setData({
deviceId: options.device_id || '',
bindToken: options.bind_token || ''
bindToken: options.bind_token || '',
devMode: config.__DEV__ || false
})
this.startBleConnect()
},
@@ -83,5 +94,40 @@ Page({
onRetry: function () {
ble.disconnect()
this.startBleConnect()
},
onMockConnect: function () {
var self = this
clearTimeout(this._scanTimer)
ble.stopScan()
self.setData({ state: 'binding' })
// Skip BLE bind, confirm directly with server
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 || '模拟确认失败' })
})
},
onMockBindSuccess: function () {
var self = this
clearTimeout(this._scanTimer)
ble.stopScan()
http.post('/api/v1/device/bind/confirm', {
device_id: self.data.deviceId,
bind_token: self.data.bindToken
}).then(function () {
wx.redirectTo({
url: '/pages/bind-success/bind-success?device_id=' + self.data.deviceId
})
}).catch(function (err) {
self.setData({ state: 'error', error: err.message || '模拟确认失败' })
})
}
})
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">蓝牙连接</view>
<view class="page-header-subtitle">{{state === 'scanning' ? '正在搜索设备...' : state === 'connecting' ? '正在连接设备...' : state === 'binding' ? '正在绑定...' : '连接失败'}}</view>
</view>
@@ -26,5 +27,11 @@
<view class="found-name">{{error || '连接失败'}}</view>
<button class="retry-btn" bindtap="onRetry">重试</button>
</view>
<view class="debug-actions" wx:if="{{devMode}}">
<view class="debug-label">🔧 调试工具</view>
<button class="btn-secondary btn-sm" bindtap="onMockConnect">模拟连接成功</button>
<button class="btn-secondary btn-sm" bindtap="onMockBindSuccess">模拟绑定成功</button>
</view>
</view>
</view>
+8
查看文件
@@ -8,6 +8,14 @@ Page({
error: ''
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
+1
查看文件
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">扫码绑定</view>
<view class="page-header-subtitle">扫描设备底部二维码</view>
</view>
@@ -11,6 +11,14 @@ Page({
purchasing: false
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-gold" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">订阅服务</view>
<view class="page-header-subtitle">解锁智能模式</view>
</view>
@@ -13,6 +13,10 @@ Page({
},
onBack: function () {
wx.switchTab({ url: '/pages/index/index' })
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
}
})
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">护理设置</view>
<view class="page-header-subtitle">试用期已结束</view>
</view>
@@ -5,6 +5,14 @@ Page({
expiryDate: ''
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function (options) {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-green" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">订阅成功</view>
<view class="page-header-subtitle">欢迎使用智能模式</view>
</view>
+7 -1
查看文件
@@ -1,5 +1,6 @@
var ble = require('../../services/ble')
var commandSync = require('../../services/command-sync')
var config = require('../../config/env')
Page({
data: {
@@ -16,11 +17,12 @@ Page({
paused: false,
completed: false,
startedAt: 0,
devMode: false,
},
onLoad: function (options) {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.setData({ statusBarHeight: app.globalData.statusBarHeight, devMode: config.__DEV__ || false })
this.setData({
regions: parseInt(options.regions) || 0x7F,
wavelength: parseInt(options.wavelength) || 2,
@@ -187,5 +189,9 @@ Page({
}
}
})
},
onMockComplete: function () {
this.finishAsComplete()
}
})
+5
查看文件
@@ -26,6 +26,11 @@
</view>
</view>
<view class="debug-actions" wx:if="{{devMode}}">
<view class="debug-label">🔧 调试工具</view>
<button class="btn-secondary btn-sm" bindtap="onMockComplete">模拟护理完成</button>
</view>
<button class="btn-primary btn-primary-red" bindtap="onStop">停止护理</button>
</view>
</view>
@@ -16,6 +16,10 @@ Page({
synced: false
},
onBack: function () {
wx.reLaunch({ url: '/pages/index/index' })
},
onLoad: function (options) {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
var durationMs = parseInt(options.duration) || 0
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-green" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">护理完成</view>
<view class="page-header-subtitle">本次护理已结束</view>
</view>
@@ -1,4 +1,5 @@
var ble = require('../../services/ble')
var config = require('../../config/env')
Page({
data: {
@@ -15,12 +16,21 @@ Page({
subDays: 6,
fixedDurationMs: 600000,
submitting: false,
error: ''
error: '',
devMode: false
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.setData({ statusBarHeight: app.globalData.statusBarHeight, devMode: config.__DEV__ || false })
this.checkSubscription()
},
@@ -97,5 +107,19 @@ Page({
}).catch(function (err) {
self.setData({ submitting: false, error: err.error_msg || '启动失败' })
})
},
onMockStart: function () {
var mask = 0
this.data.regions.forEach(function (r) {
if (r.checked) mask |= r.mask
})
if (mask === 0) mask = 0x7F
wx.redirectTo({
url: '/pages/treating/treating?regions=' + mask +
'&wavelength=2' +
'&duration=' + this.data.fixedDurationMs +
'&mode=' + this.data.selectedMode
})
}
})
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">护理设置</view>
<view class="page-header-subtitle">选择模式和区域</view>
</view>
@@ -37,5 +38,10 @@
</button>
<view class="error-text" wx:if="{{error}}">{{error}}</view>
<view class="debug-actions" wx:if="{{devMode}}">
<view class="debug-label">🔧 调试工具</view>
<button class="btn-secondary btn-sm" bindtap="onMockStart">模拟开始护理(跳过BLE</button>
</view>
</view>
</view>
+16 -2
查看文件
@@ -1,16 +1,26 @@
var ble = require('../../services/ble')
var config = require('../../config/env')
Page({
data: {
statusBarHeight: 44,
checking: false,
result: null,
error: ''
error: '',
devMode: false
},
onBack: function () {
wx.navigateBack({
fail: function () {
wx.reLaunch({ url: '/pages/index/index' })
}
})
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.setData({ statusBarHeight: app.globalData.statusBarHeight, devMode: config.__DEV__ || false })
},
onShow: function () {
@@ -47,5 +57,9 @@ Page({
onRetry: function () {
this.checkWearing()
},
onMockWearOk: function () {
this.setData({ checking: false, result: 'ok' })
}
})
@@ -1,5 +1,6 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back" bindtap="onBack"> 返回</view>
<view class="page-header-title">确认佩戴</view>
<view class="page-header-subtitle">请确保面罩贴合面部</view>
</view>
@@ -39,5 +40,10 @@
</view>
<button wx:if="{{result === 'ok'}}" class="btn-primary mt-30" bindtap="onNext">设置护理参数</button>
<view class="debug-actions" wx:if="{{devMode}}">
<view class="debug-label">🔧 调试工具</view>
<button class="btn-secondary btn-sm" bindtap="onMockWearOk">模拟佩戴确认</button>
</view>
</view>
</view>