var ble = require('../../services/ble') var i18n = require('../../i18n/index') // Manual care page: user directly picks a care function (-> single light // wavelength), regions and duration, then starts — skipping the smart scan. // // Care function -> wavelength (protocol WAVELENGTH: IR=1, R=2, UV=3, Y=4). // SINGLE light per session: buildVendorCommand only lights one wavelength. // FUTURE EXTENSION: a composite/multi-light care mode would require the vendor // command builder to accept several wavelengths at once — not implemented here. Page({ data: { statusBarHeight: 44, // 治疗亮度统一 204 = 80%(果果 2026-07-28 定案,与智能推荐一致)。 // Do NOT expose a brightness slider (safety); builder 层另有 ≤204 硬上限兜底。 brightness: 204, functions: [ { id: 'rejuvenate', wavelength: 2, wl: 'red', color: '#E8503A' }, { id: 'acne', wavelength: 3, wl: 'uv', color: '#7C4DFF' }, { id: 'eventone', wavelength: 4, wl: 'yellow', color: '#F5B841' }, { id: 'revitalize', wavelength: 1, wl: 'infrared', color: '#8B2E3C' } ], selectedFunctionId: 'rejuvenate', selectedWavelength: 2, // 掩码 = IO↔物理位置(实测):右=0x01 左=0x02 上=0x04 中=0x08 下=0x10 regions: [ { key: 'right', mask: 0x01, checked: true }, { key: 'left', mask: 0x02, checked: true }, { key: 'top', mask: 0x04, checked: true }, { key: 'middle', mask: 0x08, checked: true }, { key: 'bottom', mask: 0x10, checked: true } ], // safety: a single session must NOT exceed 10 minutes (hardware caps at 10). durations: [5, 10], selectedMinutes: 10, durationOptions: [], submitting: false, error: '' }, onLoad: function () { var app = getApp() if (app && app.globalData && app.globalData.statusBarHeight) { this.setData({ statusBarHeight: app.globalData.statusBarHeight }) } }, onShow: function () { i18n.bind(this) this.refreshDurationOptions() this._checkAccess() }, // 手动选择护理模式为智能模式专属(果果 2026-07-28 定案)。普通模式用户不可用—— // 入口链接已按订阅隐藏,此处兜底拦截直达(smart_mode_free 开启时后端返回 active,同样放行)。 _checkAccess: function () { var http = require('../../utils/request') http.get('/api/v1/subscription').then(function (sub) { if (sub && sub.status === 'active') return wx.showModal({ content: i18n.t('manual.smartOnly'), showCancel: false, complete: function () { wx.navigateBack({ fail: function () { wx.reLaunch({ url: '/pages/index/index' }) } }) } }) }).catch(function () {}) // 网络失败不拦(避免误伤),实际下发仍有 BLE/后端校验 }, // Duration labels carry a dynamic number, which WXML cannot substitute, so // build the final strings in JS. Re-run on every onShow to follow locale. refreshDurationOptions: function () { var options = this.data.durations.map(function (min) { return { min: min, label: i18n.t('manual.durationUnit', { min: min }) } }) this.setData({ durationOptions: options }) }, onBack: function () { wx.navigateBack({ fail: function () { wx.reLaunch({ url: '/pages/index/index' }) } }) }, onSelectFunction: function (e) { var id = e.currentTarget.dataset.id var funcs = this.data.functions var wavelength = this.data.selectedWavelength for (var i = 0; i < funcs.length; i++) { if (funcs[i].id === id) { wavelength = funcs[i].wavelength break } } this.setData({ selectedFunctionId: id, selectedWavelength: wavelength }) }, onToggleRegion: function (e) { var idx = e.currentTarget.dataset.idx var regions = this.data.regions regions[idx].checked = !regions[idx].checked this.setData({ regions: regions }) }, onSelectDuration: function (e) { var min = parseInt(e.currentTarget.dataset.min, 10) this.setData({ selectedMinutes: min }) }, onStart: function () { var self = this if (!ble.isConnected()) { self.setData({ error: i18n.t('common.deviceNotConnected') }) return } var mask = 0 self.data.regions.forEach(function (r) { if (r.checked) mask |= r.mask }) if (mask === 0) { wx.showToast({ title: i18n.t('manual.noRegionSelected'), icon: 'none' }) return } var wavelength = self.data.selectedWavelength var durationMs = self.data.selectedMinutes * 60000 self.setData({ submitting: true, error: '' }) ble.setParams({ region_mask: mask, wavelength: wavelength, brightness: self.data.brightness, duration_ms: durationMs, mode: 0, control: 0x02 }).then(function () { return ble.startTreatment(mask) }).then(function () { self.setData({ submitting: false }) wx.redirectTo({ url: '/pages/treating/treating?regions=' + mask + '&wavelength=' + wavelength + '&duration=' + durationMs + '&mode=0' }) }).catch(function (err) { self.setData({ submitting: false, error: (err && err.error_msg) || i18n.t('manual.startFailed') }) }) } })