feat(i18n): 剩余15页全量接入中英(登录/注册/扫码/绑定/佩戴/扫描/治疗/订阅/帮助等)

- 3 个并行 agent 各管 5 页,互不重叠命名空间
- 全 App 18 页均 i18n.bind,22 命名空间/400 键中英对齐
- 动态文案(时长/天数/价格/区域)JS 内 i18n.t 计算;区域标签复用 common.regions
这个提交包含在:
Guoguo
2026-07-19 06:00:04 -07:00
父节点 f5eaf05067
当前提交 4466c53c7b
修改 45 个文件,包含 842 行新增268 行删除
+9 -4
查看文件
@@ -1,6 +1,7 @@
var ble = require('../../services/ble')
var api = require('../../utils/api')
var diagnosis = require('../../services/diagnosis')
var i18n = require('../../i18n/index')
var SCAN_TIMEOUT = 180000
Page({
@@ -31,6 +32,10 @@ Page({
})
},
onShow: function () {
i18n.bind(this)
},
_cleanup: function () {
if (this._timeoutTimer) { clearTimeout(this._timeoutTimer); this._timeoutTimer = null }
if (this._onAdc) { ble.off('adc', this._onAdc); this._onAdc = null }
@@ -42,7 +47,7 @@ Page({
var self = this
if (!ble.isConnected()) {
self.setData({ error: '设备未连接' })
self.setData({ error: i18n.t('common.deviceNotConnected') })
return
}
@@ -90,7 +95,7 @@ Page({
console.log('[SCAN] 未贴合,检测失败')
self._completed = true
self._cleanup()
self.setData({ phase: 'ready', error: '面膜未佩戴好,请重新佩戴后再试' })
self.setData({ phase: 'ready', error: i18n.t('autoScan.notWorn') })
return
}
@@ -129,7 +134,7 @@ Page({
}).catch(function (err) {
console.error('[SCAN] 检测命令发送失败:', err)
self._cleanup()
self.setData({ phase: 'ready', error: '检测命令发送失败' })
self.setData({ phase: 'ready', error: i18n.t('autoScan.cmdFailed') })
})
self._timeoutTimer = setTimeout(function () {
@@ -140,7 +145,7 @@ Page({
} else {
self._completed = true
self._cleanup()
self.setData({ phase: 'ready', error: '检测超时,未收到数据,请重试' })
self.setData({ phase: 'ready', error: i18n.t('autoScan.timeout') })
}
}, SCAN_TIMEOUT)
},
+8 -8
查看文件
@@ -1,15 +1,15 @@
<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">{{phase === 'ready' ? '准备就绪' : phase === 'waiting_fit' ? '等待佩戴贴合...' : phase === 'collecting' ? '采集数据中 (' + frameCount + '帧)...' : '检测完成,正在启动...'}}</view>
<view class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.autoScan.title}}</view>
<view class="page-header-subtitle">{{phase === 'ready' ? i18n.autoScan.subReady : phase === 'waiting_fit' ? i18n.autoScan.subWaiting : phase === 'collecting' ? i18n.autoScan.subCollectingPre + frameCount + i18n.autoScan.subCollectingPost : i18n.autoScan.subDone}}</view>
</view>
<view class="page-content">
<!-- 待开始 -->
<view wx:if="{{phase === 'ready'}}">
<view class="scan-hint">设备已佩戴,点击下方按钮开始智能检测</view>
<button class="btn-primary mt-30" bindtap="onStartScan">开始检测</button>
<view class="scan-hint">{{i18n.autoScan.readyHint}}</view>
<button class="btn-primary mt-30" bindtap="onStartScan">{{i18n.autoScan.startBtn}}</button>
</view>
<!-- 等待贴合 -->
@@ -19,7 +19,7 @@
<view class="scan-line"></view>
</view>
</view>
<view class="scan-status">正在检测面膜贴合状态,请确保设备正确佩戴...</view>
<view class="scan-status">{{i18n.autoScan.waitingStatus}}</view>
</view>
<!-- 采集中 -->
@@ -29,12 +29,12 @@
<view class="scan-line"></view>
</view>
</view>
<view class="scan-status">已贴合,正在采集光谱数据... 已收到 {{frameCount}} 帧</view>
<view class="scan-status">{{i18n.autoScan.collectingStatusPre}}{{frameCount}}{{i18n.autoScan.collectingStatusPost}}</view>
</view>
<!-- 完成,自动跳转 -->
<view wx:if="{{phase === 'done'}}">
<view class="scan-status">检测完成(共 {{frameCount}} 帧),正在启动治疗...</view>
<view class="scan-status">{{i18n.autoScan.doneStatusPre}}{{frameCount}}{{i18n.autoScan.doneStatusPost}}</view>
</view>
<view class="scan-error" wx:if="{{error}}">{{error}}</view>
+21 -2
查看文件
@@ -1,9 +1,24 @@
var i18n = require('../../i18n/index')
Page({
data: {
statusBarHeight: 44,
deviceId: '',
trialDays: 7,
isFreeMode: false
isFreeMode: false,
tipTitle: ''
},
onShow: function () {
i18n.bind(this)
},
updateTip: function () {
this.setData({
tipTitle: this.data.isFreeMode
? i18n.t('bindSuccess.freeModeOpen')
: i18n.t('bindSuccess.trialGranted', { days: this.data.trialDays })
})
},
onBack: function () {
@@ -18,10 +33,14 @@ Page({
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.setData({ deviceId: options.device_id || '' })
this.updateTip()
var self = this
var http = require('../../utils/request')
http.get('/api/v1/subscription').then(function (sub) {
if (sub && sub.plan === 'free') self.setData({ isFreeMode: true })
if (sub && sub.plan === 'free') {
self.setData({ isFreeMode: true })
self.updateTip()
}
}).catch(function () {})
},
@@ -1,25 +1,25 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.bindSuccess.title}}</view>
<view class="page-header-subtitle">{{i18n.bindSuccess.subtitle}}</view>
</view>
<view class="page-content">
<view class="success-area">
<view class="success-icon">✓</view>
<view class="success-title">绑定成功!</view>
<view class="success-title">{{i18n.bindSuccess.successTitle}}</view>
</view>
<view class="tip-card">
<text class="tip-card-icon">🎁</text>
<view class="tip-card-body">
<view class="tip-card-title">{{isFreeMode ? '智能模式已开放' : '恭喜获得7天智能模式试用'}}</view>
<view class="tip-card-sub" wx:if="{{!isFreeMode}}">体验结束后可订阅继续使用</view>
<view class="tip-card-title">{{tipTitle}}</view>
<view class="tip-card-sub" wx:if="{{!isFreeMode}}">{{i18n.bindSuccess.trialSub}}</view>
</view>
</view>
<button class="btn-primary" bindtap="onStart">开始使用</button>
<button class="btn-secondary" bindtap="onGoHome">返回首页</button>
<button class="btn-primary" bindtap="onStart">{{i18n.bindSuccess.startUsing}}</button>
<button class="btn-secondary" bindtap="onGoHome">{{i18n.bindSuccess.goHome}}</button>
</view>
</view>
+10 -5
查看文件
@@ -1,5 +1,6 @@
var ble = require('../../services/ble')
var http = require('../../utils/request')
var i18n = require('../../i18n/index')
var app = getApp()
var SCAN_TIMEOUT = 15000
@@ -12,6 +13,10 @@ Page({
error: '',
},
onShow: function () {
i18n.bind(this)
},
onBack: function () {
wx.navigateBack({
fail: function () {
@@ -48,7 +53,7 @@ Page({
self._scanTimer = setTimeout(function () {
ble.stopScan()
self.setData({ state: 'error', error: '搜索超时,请确认设备已开机并在附近' })
self.setData({ state: 'error', error: i18n.t('bleConnect.scanTimeout') })
}, SCAN_TIMEOUT)
ble.startScan({
@@ -64,7 +69,7 @@ Page({
},
onError: function (err) {
clearTimeout(self._scanTimer)
self.setData({ state: 'error', error: err.msg || '连接失败' })
self.setData({ state: 'error', error: err.msg || i18n.t('bleConnect.failed') })
}
})
},
@@ -83,16 +88,16 @@ Page({
url: '/pages/bind-success/bind-success?device_id=' + self.data.deviceId
})
}).catch(function (err) {
self.setData({ state: 'error', error: err.message || '后台确认绑定失败' })
self.setData({ state: 'error', error: err.message || i18n.t('bleConnect.confirmFailed') })
})
} else {
self.setData({ state: 'error', error: '设备绑定失败' })
self.setData({ state: 'error', error: i18n.t('bleConnect.bindFailed') })
}
}
ble.on('bind_result', this._onBindResult)
ble.bindDevice(userId, self.data.bindToken).catch(function (err) {
self.setData({ state: 'error', error: err.error_msg || '绑定命令失败' })
self.setData({ state: 'error', error: err.error_msg || i18n.t('bleConnect.bindCmdFailed') })
})
},
@@ -1,31 +1,31 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.bleConnect.title}}</view>
<view class="page-header-subtitle">{{state === 'scanning' ? i18n.bleConnect.searchingSub : state === 'connecting' ? i18n.bleConnect.connectingSub : state === 'binding' ? i18n.bleConnect.bindingSub : i18n.bleConnect.failedSub}}</view>
</view>
<view class="page-content">
<view class="info-tip">
<text>📡</text>
<text>请确保手机蓝牙已开启</text>
<text>{{i18n.bleConnect.bluetoothTip}}</text>
</view>
<view class="search-card" wx:if="{{state === 'scanning' || state === 'connecting'}}">
<view class="search-icon pulsing">⏳</view>
<view class="search-text">搜索中...</view>
<view class="search-text">{{i18n.bleConnect.searching}}</view>
</view>
<view class="found-card" wx:if="{{state === 'binding'}}">
<view class="found-icon">💆</view>
<view class="found-name">LumiFlow-{{deviceId}}</view>
<text class="status-badge status-badge-green">正在配对...</text>
<text class="status-badge status-badge-green">{{i18n.bleConnect.pairing}}</text>
</view>
<view class="found-card" wx:if="{{state === 'error'}}">
<view class="search-icon">⚠️</view>
<view class="found-name">{{error || '连接失败'}}</view>
<button class="retry-btn" bindtap="onRetry">重试</button>
<view class="found-name">{{error || i18n.bleConnect.failed}}</view>
<button class="retry-btn" bindtap="onRetry">{{i18n.common.retry}}</button>
</view>
</view>
+6
查看文件
@@ -1,8 +1,14 @@
var i18n = require('../../i18n/index')
Page({
data: {
statusBarHeight: 44
},
onShow: function () {
i18n.bind(this)
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
+4 -4
查看文件
@@ -1,13 +1,13 @@
<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="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.contact.title}}</view>
</view>
<view class="page-content">
<view class="placeholder-container">
<view class="placeholder-icon">📬</view>
<view class="placeholder-text">内容建设中</view>
<view class="placeholder-sub">联系方式正在整理,敬请期待</view>
<view class="placeholder-text">{{i18n.contact.placeholderText}}</view>
<view class="placeholder-sub">{{i18n.contact.placeholderSub}}</view>
</view>
</view>
</view>
+6
查看文件
@@ -1,8 +1,14 @@
var i18n = require('../../i18n/index')
Page({
data: {
statusBarHeight: 44
},
onShow: function () {
i18n.bind(this)
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
+4 -4
查看文件
@@ -1,13 +1,13 @@
<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="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.help.title}}</view>
</view>
<view class="page-content">
<view class="placeholder-container">
<view class="placeholder-icon">📖</view>
<view class="placeholder-text">内容建设中</view>
<view class="placeholder-sub">帮助文档正在编写,敬请期待</view>
<view class="placeholder-text">{{i18n.help.placeholderText}}</view>
<view class="placeholder-sub">{{i18n.help.placeholderSub}}</view>
</view>
</view>
</view>
+7 -2
查看文件
@@ -1,4 +1,5 @@
var app = getApp()
var i18n = require('../../i18n/index')
Page({
data: {
@@ -6,6 +7,10 @@ Page({
loading: false
},
onShow: function () {
i18n.bind(this)
},
onLoad: function () {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
},
@@ -23,11 +28,11 @@ Page({
}
}).catch(function (err) {
self.setData({ loading: false })
wx.showToast({ title: err.message || '登录失败', icon: 'none' })
wx.showToast({ title: err.message || i18n.t('login.loginFailed'), icon: 'none' })
})
},
onAgreement: function () {
wx.showModal({ title: '提示', content: '用户协议和隐私政策内容建设中', showCancel: false })
wx.showModal({ title: i18n.t('login.agreementTitle'), content: i18n.t('login.agreementContent'), showCancel: false })
}
})
+5 -5
查看文件
@@ -1,21 +1,21 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="page-header-title">LumiFlow</view>
<view class="page-header-subtitle">微信登录</view>
<view class="page-header-subtitle">{{i18n.login.wechatLogin}}</view>
</view>
<view class="page-content">
<view class="welcome-text">欢迎使用LumiFlow</view>
<view class="welcome-text">{{i18n.login.welcome}}</view>
<view class="flower-area">
<text class="flower">💎</text>
<view class="desc">登录后即可使用全部功能</view>
<view class="desc">{{i18n.login.desc}}</view>
</view>
<button class="login-btn" loading="{{loading}}" bindtap="onLogin" disabled="{{loading}}">
微信登录
{{i18n.login.wechatLogin}}
</button>
<view class="agreement" bindtap="onAgreement">登录即表示同意《用户协议》和《隐私政策》</view>
<view class="agreement" bindtap="onAgreement">{{i18n.login.agreement}}</view>
</view>
</view>
+17 -12
查看文件
@@ -1,6 +1,7 @@
var app = getApp()
var http = require('../../utils/request')
var config = require('../../config/env')
var i18n = require('../../i18n/index')
var DEFAULT_AVATAR = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI9t2NHfLvvMiaiaCJQn3KP2P0vAHp1QBP0piavhGfhKCARlGd6z1EbR58ibJXKCp5WVlNnojwA/640'
@@ -9,7 +10,7 @@ function randomNickname() {
for (var i = 0; i < 4; i++) {
digits += Math.floor(Math.random() * 10)
}
return '用户' + digits
return i18n.t('register.nicknamePrefix') + digits
}
Page({
@@ -21,6 +22,10 @@ Page({
phone: ''
},
onShow: function () {
i18n.bind(this)
},
onLoad: function () {
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
@@ -58,19 +63,19 @@ Page({
onGetPhoneNumber: function (e) {
var self = this
if (!e.detail || !e.detail.code) {
wx.showToast({ title: '需要授权手机号才能完成注册', icon: 'none' })
wx.showToast({ title: i18n.t('register.phoneRequired'), icon: 'none' })
return
}
self.setData({ loading: true })
http.post('/api/v1/user/phone', { code: e.detail.code }).then(function (data) {
self.setData({
loading: false,
phone: '已授权'
phone: i18n.t('register.authorized')
})
wx.showToast({ title: '手机号授权成功', icon: 'success' })
wx.showToast({ title: i18n.t('register.phoneAuthSuccess'), icon: 'success' })
}).catch(function (err) {
self.setData({ loading: false })
wx.showToast({ title: err.message || '手机号授权失败', icon: 'none' })
wx.showToast({ title: err.message || i18n.t('register.phoneAuthFailed'), icon: 'none' })
})
},
@@ -88,13 +93,13 @@ Page({
if (data.code === 0 && data.data && data.data.avatar) {
resolve(data.data.avatar)
} else {
reject(new Error(data.message || '上传失败'))
reject(new Error(data.message || i18n.t('register.uploadFailed')))
}
} catch (e) {
reject(new Error('上传失败'))
reject(new Error(i18n.t('register.uploadFailed')))
}
},
fail: function () { reject(new Error('上传失败')) }
fail: function () { reject(new Error(i18n.t('register.uploadFailed'))) }
})
})
},
@@ -105,12 +110,12 @@ Page({
var avatarUrl = self.data.avatarUrl
if (!self.data.phone) {
wx.showToast({ title: '请先授权手机号', icon: 'none' })
wx.showToast({ title: i18n.t('register.phoneFirst'), icon: 'none' })
return
}
if (!nickname) {
wx.showToast({ title: '请输入昵称', icon: 'none' })
wx.showToast({ title: i18n.t('register.nicknameRequired'), icon: 'none' })
return
}
@@ -132,11 +137,11 @@ Page({
wx.switchTab({ url: '/pages/index/index' })
}).catch(function (err) {
self.setData({ loading: false })
wx.showToast({ title: err.message || '保存失败', icon: 'none' })
wx.showToast({ title: err.message || i18n.t('common.saveFailed'), icon: 'none' })
})
},
onAgreement: function () {
wx.showModal({ title: '提示', content: '用户协议和隐私政策内容建设中', showCancel: false })
wx.showModal({ title: i18n.t('register.agreementTitle'), content: i18n.t('register.agreementContent'), showCancel: false })
}
})
+10 -10
查看文件
@@ -1,24 +1,24 @@
<view class="page">
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="page-header-title">LumiFlow</view>
<view class="page-header-subtitle">完善个人资料</view>
<view class="page-header-subtitle">{{i18n.register.subtitle}}</view>
</view>
<view class="page-content">
<view class="section-title">设置头像</view>
<view class="section-title">{{i18n.register.avatarSection}}</view>
<view class="avatar-area">
<view class="avatar-wrap" bindtap="onPickAvatar">
<image class="avatar-img" src="{{avatarUrl}}" mode="aspectFill"></image>
<view class="avatar-edit-hint">点击更换</view>
<view class="avatar-edit-hint">{{i18n.register.changeAvatar}}</view>
</view>
</view>
<view class="section-title">设置昵称</view>
<view class="section-title">{{i18n.register.nicknameSection}}</view>
<view class="input-wrap">
<input type="nickname" class="weui-input nickname-input" value="{{nickname}}" bindinput="onNicknameInput" bindblur="onNicknameBlur" placeholder="请输入昵称" />
<input type="nickname" class="weui-input nickname-input" value="{{nickname}}" bindinput="onNicknameInput" bindblur="onNicknameBlur" placeholder="{{i18n.register.nicknamePlaceholder}}" />
</view>
<view class="section-title">授权手机号</view>
<view class="section-title">{{i18n.register.authorizePhone}}</view>
<view class="phone-area">
<block wx:if="{{phone}}">
<view class="phone-done">
@@ -28,16 +28,16 @@
</block>
<block wx:else>
<button class="phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" disabled="{{loading}}">
授权手机号
{{i18n.register.authorizePhone}}
</button>
<view class="phone-hint">手机号用于接收服务通知,必须授权</view>
<view class="phone-hint">{{i18n.register.phoneHint}}</view>
</block>
</view>
<button class="submit-btn" bindtap="onSubmit" loading="{{loading}}" disabled="{{loading}}">
完成注册
{{i18n.register.submit}}
</button>
<view class="agreement" bindtap="onAgreement">注册即表示同意《用户协议》和《隐私政策》</view>
<view class="agreement" bindtap="onAgreement">{{i18n.register.agreement}}</view>
</view>
</view>
+11 -6
查看文件
@@ -1,4 +1,5 @@
var http = require('../../utils/request')
var i18n = require('../../i18n/index')
Page({
data: {
@@ -8,6 +9,10 @@ Page({
error: ''
},
onShow: function () {
i18n.bind(this)
},
onBack: function () {
wx.navigateBack({
fail: function () {
@@ -31,7 +36,7 @@ Page({
success: function (res) {
var deviceId = self.parseDeviceId(res.result)
if (!deviceId) {
self.setData({ scanning: false, error: '无效的设备二维码' })
self.setData({ scanning: false, error: i18n.t('scan.invalidQr') })
return
}
self.bindDevice(deviceId)
@@ -66,12 +71,12 @@ Page({
}).catch(function (err) {
self.setData({ scanning: false })
if (err && err.code === 2001) {
wx.showToast({ title: '已绑定设备', icon: 'none' })
wx.showToast({ title: i18n.t('scan.alreadyBound'), icon: 'none' })
setTimeout(function () {
wx.navigateBack()
}, 1500)
} else {
self.setData({ error: err.message || '绑定失败' })
self.setData({ error: err.message || i18n.t('scan.bindFailed') })
}
})
},
@@ -79,16 +84,16 @@ Page({
onManualInput: function () {
var self = this
wx.showModal({
title: '手动输入设备号',
title: i18n.t('scan.manualInput'),
editable: true,
placeholderText: '请输入设备ID,如 672B6D5A 4DCD861',
placeholderText: i18n.t('scan.manualPlaceholder'),
success: function (res) {
if (res.confirm && res.content) {
var deviceId = self.parseDeviceId(res.content)
if (deviceId) {
self.bindDevice(deviceId)
} else {
wx.showToast({ title: '设备号格式不正确', icon: 'none' })
wx.showToast({ title: i18n.t('scan.invalidFormat'), icon: 'none' })
}
}
}
+6 -6
查看文件
@@ -1,20 +1,20 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.scan.title}}</view>
<view class="page-header-subtitle">{{i18n.scan.subtitle}}</view>
</view>
<view class="page-content">
<view class="scan-area">
<text class="scan-icon">📷</text>
<text class="scan-text">扫描设备二维码</text>
<text class="scan-text">{{i18n.scan.scanText}}</text>
</view>
<button class="btn-primary" bindtap="onScan" disabled="{{scanning}}">
{{scanning ? '扫描中...' : '扫码'}}
{{scanning ? i18n.scan.scanning : i18n.scan.scan}}
</button>
<button class="btn-secondary" bindtap="onManualInput">手动输入设备号</button>
<button class="btn-secondary" bindtap="onManualInput">{{i18n.scan.manualInput}}</button>
<view class="error-text" wx:if="{{error}}">{{error}}</view>
</view>
@@ -1,4 +1,5 @@
var api = require('../../utils/api')
var i18n = require('../../i18n/index')
Page({
data: {
@@ -8,6 +9,7 @@ Page({
purchasing: false,
subscription: null,
subRemaining: 0,
subRemainingText: '',
loadingPlans: true
},
@@ -25,10 +27,18 @@ Page({
},
onShow: function () {
i18n.bind(this)
this.loadPlans()
this.loadSubscription()
},
planName: function (key) {
if (key === 'trial') return i18n.t('subPlans.planTrial')
if (key === 'monthly') return i18n.t('subPlans.planMonthly')
if (key === 'yearly') return i18n.t('subPlans.planYearly')
return key
},
loadPlans: function () {
var self = this
self.setData({ loadingPlans: true })
@@ -41,16 +51,16 @@ Page({
var plans = serverPlans.map(function (p) {
var daily = p.days > 0 ? (p.price / p.days).toFixed(1) : 0
var desc = ''
if (p.key === 'trial') desc = p.days + '天免费体验'
else if (p.key === 'yearly') desc = '省¥' + Math.round(monthlyPrice * 12 - p.price)
else desc = '约' + daily + '元/天'
if (p.key === 'trial') desc = i18n.t('subPlans.descTrial', { days: p.days })
else if (p.key === 'yearly') desc = i18n.t('subPlans.descSave', { amount: Math.round(monthlyPrice * 12 - p.price) })
else desc = i18n.t('subPlans.descDaily', { price: daily })
return {
key: p.key,
name: p.name,
name: self.planName(p.key),
price: p.price,
priceLabel: p.key === 'trial' ? '免费' : '¥' + p.price,
priceLabel: p.key === 'trial' ? i18n.t('subPlans.free') : '¥' + p.price,
desc: desc,
tag: p.key === 'yearly' ? '推荐' : '',
tag: p.key === 'yearly' ? i18n.t('subPlans.tagRecommend') : '',
disabled: false
}
})
@@ -61,9 +71,9 @@ Page({
self.setData({
loadingPlans: false,
plans: [
{ key: 'trial', name: '试用', price: 0, priceLabel: '免费', desc: '7天免费体验', disabled: false },
{ key: 'monthly', name: '月卡', price: 99, priceLabel: '¥99', desc: '约3.3元/天' },
{ key: 'yearly', name: '年卡', price: 899, priceLabel: '¥899', desc: '省¥289', tag: '推荐' }
{ key: 'trial', name: self.planName('trial'), price: 0, priceLabel: i18n.t('subPlans.free'), desc: i18n.t('subPlans.descTrial', { days: 7 }), disabled: false },
{ key: 'monthly', name: self.planName('monthly'), price: 99, priceLabel: '¥99', desc: i18n.t('subPlans.descDaily', { price: '3.3' }) },
{ key: 'yearly', name: self.planName('yearly'), price: 899, priceLabel: '¥899', desc: i18n.t('subPlans.descSave', { amount: 289 }), tag: i18n.t('subPlans.tagRecommend') }
]
})
self.checkTrialUsed()
@@ -75,7 +85,7 @@ Page({
var sub = self.data.subscription
if (sub && sub.trial_used) {
var plans = self.data.plans.map(function (p) {
if (p.key === 'trial') return Object.assign({}, p, { disabled: true, tag: '已使用' })
if (p.key === 'trial') return Object.assign({}, p, { disabled: true, tag: i18n.t('subPlans.tagUsed') })
return p
})
self.setData({ plans: plans })
@@ -86,16 +96,18 @@ Page({
loadSubscription: function () {
var self = this
api.getSubscription().then(function (sub) {
var remaining = sub.remaining_days || 0
self.setData({
subscription: sub,
subRemaining: sub.remaining_days || 0
subRemaining: remaining,
subRemainingText: i18n.t('subPlans.daysValue', { days: remaining })
})
self.checkTrialUsed()
}).catch(function () {})
},
onAgreement: function () {
wx.showModal({ title: '提示', content: '订阅协议内容建设中', showCancel: false })
wx.showModal({ title: i18n.t('subPlans.tipTitle'), content: i18n.t('subPlans.agreementBuilding'), showCancel: false })
},
onSelect: function (e) {
@@ -116,8 +128,8 @@ Page({
if (plan.key === 'trial') {
wx.showModal({
title: '激活试用',
content: '免费试用 ' + plan.desc + '?',
title: i18n.t('subPlans.activateTrialTitle'),
content: i18n.t('subPlans.activateTrialConfirm', { desc: plan.desc }),
success: function (res) {
if (res.confirm) self.doActivateTrial()
}
@@ -127,10 +139,10 @@ Page({
var isRenew = self.data.subscription && self.data.subscription.status === 'active' && self.data.subRemaining > 0
var planDays = plan.key === 'yearly' ? 365 : 30
var title = isRenew ? '确认续费' : '确认支付'
var title = isRenew ? i18n.t('subPlans.confirmRenewTitle') : i18n.t('subPlans.confirmPayTitle')
var content = isRenew
? '在现有订阅基础上延长' + planDays + '天,支付 ¥' + plan.price
: '支付 ¥' + plan.price
? i18n.t('subPlans.renewContent', { days: planDays, price: plan.price })
: i18n.t('subPlans.payContent', { price: plan.price })
wx.showModal({
title: title,
@@ -148,13 +160,13 @@ Page({
self.setData({ purchasing: true })
api.activateTrial().then(function () {
self.setData({ purchasing: false })
wx.showToast({ title: '试用已激活', icon: 'success' })
wx.showToast({ title: i18n.t('subPlans.trialActivated'), icon: 'success' })
setTimeout(function () {
wx.redirectTo({ url: '/pages/subscribe-success/subscribe-success?plan=trial' })
}, 1000)
}).catch(function (err) {
self.setData({ purchasing: false })
wx.showToast({ title: err.message || '激活失败', icon: 'none' })
wx.showToast({ title: err.message || i18n.t('subPlans.activateFailed'), icon: 'none' })
})
},
@@ -164,7 +176,7 @@ Page({
api.purchase(plan.key).then(function (data) {
if (!data.payment_params) {
self.setData({ purchasing: false })
wx.showToast({ title: '支付服务暂不可用', icon: 'none' })
wx.showToast({ title: i18n.t('subPlans.payUnavailable'), icon: 'none' })
return
}
@@ -182,13 +194,13 @@ Page({
},
fail: function (err) {
self.setData({ purchasing: false })
var msg = (err.errMsg || '').indexOf('cancel') > -1 ? '已取消支付' : '支付失败'
var msg = (err.errMsg || '').indexOf('cancel') > -1 ? i18n.t('subPlans.payCancelled') : i18n.t('subPlans.payFailed')
wx.showToast({ title: msg, icon: 'none' })
}
})
}).catch(function (err) {
self.setData({ purchasing: false })
wx.showToast({ title: err.message || '创建订单失败', icon: 'none' })
wx.showToast({ title: err.message || i18n.t('subPlans.createOrderFailed'), icon: 'none' })
})
},
@@ -201,7 +213,7 @@ Page({
api.syncPaymentOrder(orderId).then(function (result) {
if (result.status === 'paid') {
self.setData({ purchasing: false })
wx.showToast({ title: '支付成功', icon: 'success' })
wx.showToast({ title: i18n.t('subPlans.paySuccess'), icon: 'success' })
setTimeout(function () {
wx.redirectTo({ url: '/pages/subscribe-success/subscribe-success?plan=' + planKey })
}, 1000)
@@ -210,7 +222,7 @@ Page({
setTimeout(pollSync, 2000)
} else {
self.setData({ purchasing: false })
wx.showToast({ title: '支付处理中,请稍后在订阅页查看', icon: 'none' })
wx.showToast({ title: i18n.t('subPlans.payProcessing'), icon: 'none' })
}
}).catch(function () {
if (retryCount < maxRetries) {
@@ -218,7 +230,7 @@ Page({
setTimeout(pollSync, 2000)
} else {
self.setData({ purchasing: false })
wx.showToast({ title: '支付处理中,请稍后在订阅页查看', icon: 'none' })
wx.showToast({ title: i18n.t('subPlans.payProcessing'), icon: 'none' })
}
})
}
@@ -1,35 +1,35 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.subPlans.title}}</view>
<view class="page-header-subtitle">{{i18n.subPlans.subtitle}}</view>
</view>
<view class="page-content">
<!-- Current subscription status -->
<view class="current-sub" wx:if="{{subscription && subscription.status === 'active' && subRemaining > 0}}">
<view class="current-sub-row">
<text class="current-sub-label">当前套餐</text>
<text class="current-sub-value">{{subscription.plan === 'trial' ? '试用' : subscription.plan === 'monthly' ? '月卡' : subscription.plan === 'yearly' ? '年卡' : subscription.plan}}</text>
<text class="current-sub-label">{{i18n.subPlans.currentPlanLabel}}</text>
<text class="current-sub-value">{{subscription.plan === 'trial' ? i18n.subPlans.planTrial : subscription.plan === 'monthly' ? i18n.subPlans.planMonthly : subscription.plan === 'yearly' ? i18n.subPlans.planYearly : subscription.plan}}</text>
</view>
<view class="current-sub-row">
<text class="current-sub-label">剩余天数</text>
<text class="current-sub-value">{{subRemaining}}</text>
<text class="current-sub-label">{{i18n.subPlans.remainDaysLabel}}</text>
<text class="current-sub-value">{{subRemainingText}}</text>
</view>
</view>
<view class="page-title">选择订阅套餐</view>
<view class="page-title">{{i18n.subPlans.choosePlan}}</view>
<view class="loading-plans" wx:if="{{loadingPlans}}">
<view class="loading-spinner"></view>
<text class="loading-text">加载套餐中...</text>
<text class="loading-text">{{i18n.subPlans.loadingPlans}}</text>
</view>
<view class="service-card" wx:if="{{!loadingPlans}}">
<view class="service-icon">✨</view>
<view class="service-info">
<view class="service-name">智能模式</view>
<view class="service-desc">智能自动调节</view>
<view class="service-name">{{i18n.subPlans.smartMode}}</view>
<view class="service-desc">{{i18n.subPlans.smartModeDesc}}</view>
</view>
</view>
@@ -44,9 +44,9 @@
<block wx:if="{{!loadingPlans}}">
<button class="btn-primary btn-primary-gold" bindtap="onPurchase" disabled="{{purchasing}}" loading="{{purchasing}}">
{{subscription && subscription.status === 'active' && subRemaining > 0 ? '续费' : '确认支付'}}
{{subscription && subscription.status === 'active' && subRemaining > 0 ? i18n.subPlans.renew : i18n.subPlans.confirmPay}}
</button>
<view class="agreement" bindtap="onAgreement">支付即表示同意《订阅协议》</view>
<view class="agreement" bindtap="onAgreement">{{i18n.subPlans.agreement}}</view>
</block>
</view>
</view>
@@ -1,8 +1,14 @@
var i18n = require('../../i18n/index')
Page({
data: {
statusBarHeight: 44
},
onShow: function () {
i18n.bind(this)
},
onLoad: function () {
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
@@ -1,35 +1,35 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.subPrompt.title}}</view>
<view class="page-header-subtitle">{{i18n.subPrompt.subtitle}}</view>
</view>
<view class="page-content">
<view class="page-title">选择模式</view>
<view class="page-title">{{i18n.subPrompt.selectMode}}</view>
<view class="mode-selector">
<view class="mode-option active">
<text class="mode-icon">🔄</text>
<view class="mode-label">普通模式</view>
<view class="mode-sub">6色循环</view>
<view class="mode-label">{{i18n.subPrompt.normalMode}}</view>
<view class="mode-sub">{{i18n.subPrompt.normalModeSub}}</view>
</view>
<view class="mode-option locked">
<text class="mode-icon">🔒</text>
<view class="mode-label">智能模式</view>
<view class="mode-sub">订阅解锁</view>
<view class="mode-label">{{i18n.subPrompt.smartMode}}</view>
<view class="mode-sub">{{i18n.subPrompt.smartModeSub}}</view>
</view>
</view>
<view class="tip-card">
<text class="tip-card-icon">⏰</text>
<view class="tip-card-body">
<view class="tip-card-title">试用已结束</view>
<view class="tip-card-sub">订阅后继续使用智能模式</view>
<view class="tip-card-title">{{i18n.subPrompt.trialEndedTitle}}</view>
<view class="tip-card-sub">{{i18n.subPrompt.trialEndedSub}}</view>
</view>
</view>
<button class="btn-primary btn-primary-gold" bindtap="onViewPlans">立即订阅</button>
<button class="btn-secondary" bindtap="onUseFreeMode">先使用普通模式</button>
<button class="btn-primary btn-primary-gold" bindtap="onViewPlans">{{i18n.subPrompt.subscribeNow}}</button>
<button class="btn-secondary" bindtap="onUseFreeMode">{{i18n.subPrompt.useNormalFirst}}</button>
</view>
</view>
@@ -1,9 +1,10 @@
var api = require('../../utils/api')
var i18n = require('../../i18n/index')
Page({
data: {
statusBarHeight: 44,
planName: '年卡会员',
planName: '',
expiryDate: ''
},
@@ -15,33 +16,56 @@ Page({
})
},
onShow: function () {
i18n.bind(this)
this.render()
},
// Recompute locale-dependent display strings from stored raw values.
render: function () {
this.setData({
planName: this.planNameFor(this._plan),
expiryDate: this._expireTime ? this.formatDate(this._expireTime) : ''
})
},
planNameFor: function (plan) {
if (plan === 'trial') return i18n.t('subSuccess.planTrial')
if (plan === 'monthly') return i18n.t('subSuccess.planMonthly')
if (plan === 'yearly') return i18n.t('subSuccess.planYearly')
return i18n.t('subSuccess.planDefault')
},
formatDate: function (raw) {
var date = new Date(raw)
var y = date.getFullYear()
var m = ('0' + (date.getMonth() + 1)).slice(-2)
var d = ('0' + date.getDate()).slice(-2)
if (i18n.getLocale() === 'en') return y + '-' + m + '-' + d
return y + '年' + m + '月' + d + '日'
},
onLoad: function (options) {
var self = this
var app = getApp()
self.setData({ statusBarHeight: app.globalData.statusBarHeight })
var planMap = { yearly: '年卡会员', monthly: '月卡会员', trial: '试用会员' }
var plan = options.plan || 'yearly'
self.setData({ planName: planMap[plan] || '会员' })
self._plan = plan
// Fetch actual subscription expiry from server instead of computing client-side
api.getSubscription().then(function (res) {
if (res && res.expire_time) {
var date = new Date(res.expire_time)
var y = date.getFullYear()
var m = ('0' + (date.getMonth() + 1)).slice(-2)
var d = ('0' + date.getDate()).slice(-2)
self.setData({ expiryDate: y + '年' + m + '月' + d + '日' })
self._expireTime = res.expire_time
}
self.render()
}).catch(function () {
// Fallback: compute from current date if server call fails
var durationMap = { yearly: 365, monthly: 30, trial: 7 }
var now = new Date()
now.setDate(now.getDate() + (durationMap[plan] || 365))
var y = now.getFullYear()
var m = ('0' + (now.getMonth() + 1)).slice(-2)
var d = ('0' + now.getDate()).slice(-2)
self.setData({ expiryDate: y + '年' + m + '月' + d + '日' })
self._expireTime = now.getTime()
self.render()
})
},
@@ -1,25 +1,25 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.subSuccess.title}}</view>
<view class="page-header-subtitle">{{i18n.subSuccess.subtitle}}</view>
</view>
<view class="page-content">
<view class="success-area">
<view class="success-icon">✓</view>
<view class="success-title">订阅成功!</view>
<view class="success-title">{{i18n.subSuccess.successTitle}}</view>
</view>
<view class="sub-card">
<view class="sub-header">
<text class="sub-name">✨ {{planName}}</text>
<text class="sub-status status-badge status-badge-green">已订阅</text>
<text class="sub-status status-badge status-badge-green">{{i18n.subSuccess.subscribed}}</text>
</view>
<view class="sub-info">有效期至:{{expiryDate}}</view>
<view class="sub-info">{{i18n.subSuccess.validUntil}}{{expiryDate}}</view>
</view>
<button class="btn-primary btn-primary-gold" bindtap="onStartSmart">立即体验智能模式</button>
<button class="btn-secondary btn-secondary-gold" bindtap="onBackHome">返回首页</button>
<button class="btn-primary btn-primary-gold" bindtap="onStartSmart">{{i18n.subSuccess.startSmart}}</button>
<button class="btn-secondary btn-secondary-gold" bindtap="onBackHome">{{i18n.subSuccess.backHome}}</button>
</view>
</view>
+33 -20
查看文件
@@ -1,6 +1,7 @@
var ble = require('../../services/ble')
var api = require('../../utils/api')
var commandSync = require('../../services/command-sync')
var i18n = require('../../i18n/index')
Page({
data: {
statusBarHeight: 44,
@@ -16,19 +17,31 @@ Page({
paused: false,
completed: false,
startedAt: 0,
regionText: '全区域',
regionText: '',
fittingLost: false,
deviceFault: false
},
_getRegionText: function (regionMask) {
var REGION_MAP = {1:'右区', 2:'左区', 4:'上区', 8:'中区', 16:'下区', 32:'左上区', 64:'右上区'}
if (regionMask === 0x7F) return '全区域'
var REGION_MAP = {
1: 'common.regions.right',
2: 'common.regions.left',
4: 'common.regions.top',
8: 'common.regions.middle',
16: 'common.regions.bottom',
32: 'treating.regionTopLeft',
64: 'treating.regionTopRight'
}
if (regionMask === 0x7F) return i18n.t('treating.allRegions')
var names = []
for (var bit in REGION_MAP) {
if (regionMask & parseInt(bit)) names.push(REGION_MAP[bit])
if (regionMask & parseInt(bit)) names.push(i18n.t(REGION_MAP[bit]))
}
return names.length > 0 ? names.join('+') : '全区域'
return names.length > 0 ? names.join('+') : i18n.t('treating.allRegions')
},
onShow: function () {
i18n.bind(this)
},
onLoad: function (options) {
@@ -91,10 +104,10 @@ Page({
if (this.data.completed) return
if (data.fitting === 0 && !this.data.fittingLost) {
this.setData({ fittingLost: true, paused: true })
wx.showToast({ title: '检测到离肤,请重新佩戴设备', icon: 'none', duration: 3000 })
wx.showToast({ title: i18n.t('treating.fittingLost'), icon: 'none', duration: 3000 })
} else if (data.fitting === 1 && this.data.fittingLost) {
this.setData({ fittingLost: false, paused: false })
wx.showToast({ title: '已重新贴合,继续使用', icon: 'success', duration: 2000 })
wx.showToast({ title: i18n.t('treating.fittingRestored'), icon: 'success', duration: 2000 })
}
},
@@ -112,8 +125,8 @@ Page({
this.setData({ deviceFault: true })
var self = this
wx.showModal({
title: '设备故障',
content: '设备上报故障,已自动停止。',
title: i18n.t('treating.faultTitle'),
content: i18n.t('treating.faultContent'),
showCancel: false,
complete: function () { self.finishAsComplete() }
})
@@ -136,8 +149,8 @@ Page({
if (self.data.battery > 0 && self.data.battery <= 5) {
clearInterval(timer)
wx.showModal({
title: '电量过低',
content: '设备电量不足5%,已自动停止。请及时充电。',
title: i18n.t('treating.lowBatteryTitle'),
content: i18n.t('treating.lowBatteryContent'),
showCancel: false,
complete: function () { self.finishAsComplete() }
})
@@ -146,7 +159,7 @@ Page({
if (self.data.battery > 0 && self.data.battery <= 10 && !self._lowBatteryWarned) {
self._lowBatteryWarned = true
wx.showToast({ title: '电量低,请及时充电', icon: 'none', duration: 3000 })
wx.showToast({ title: i18n.t('treating.lowBatteryWarn'), icon: 'none', duration: 3000 })
}
if (remaining <= 0) {
@@ -262,8 +275,8 @@ Page({
onException: function (err) {
wx.showModal({
title: '设备异常',
content: err.error_msg || '使用过程中出现异常',
title: i18n.t('treating.exceptionTitle'),
content: err.error_msg || i18n.t('treating.exceptionContent'),
showCancel: false,
complete: function () {
wx.navigateBack()
@@ -274,7 +287,7 @@ Page({
onDisconnect: function () {
this.setData({ paused: true })
wx.showToast({
title: '设备连接断开,正在重连...',
title: i18n.t('treating.disconnected'),
icon: 'none',
duration: 3000
})
@@ -283,7 +296,7 @@ Page({
onReconnect: function () {
this.setData({ paused: false })
wx.showToast({
title: '设备已重新连接',
title: i18n.t('treating.reconnected'),
icon: 'success',
duration: 2000
})
@@ -296,8 +309,8 @@ Page({
this._localTimer = null
}
wx.showModal({
title: '连接丢失',
content: '设备蓝牙连接已断开,无法恢复。本次使用数据将保存。',
title: i18n.t('treating.reconnectFailedTitle'),
content: i18n.t('treating.reconnectFailedContent'),
showCancel: false,
complete: function () {
self.finishAsComplete()
@@ -312,8 +325,8 @@ Page({
onStop: function () {
var self = this
wx.showModal({
title: '结束使用',
content: '确定要提前结束本次使用吗?',
title: i18n.t('treating.stopTitle'),
content: i18n.t('treating.stopContent'),
success: function (res) {
if (res.confirm) {
ble.stopTreatment().then(function (res) {
+8 -8
查看文件
@@ -1,14 +1,14 @@
<view class="page">
<view class="page-header page-header-blue" style="padding-top: {{statusBarHeight + 24}}px;">
<view class="nav-back nav-back-light" bindtap="onBack"> 返回</view>
<view class="page-header-title">使用中</view>
<view class="page-header-subtitle">正在使用...</view>
<view class="nav-back nav-back-light" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.treating.title}}</view>
<view class="page-header-subtitle">{{i18n.treating.subtitle}}</view>
</view>
<view class="page-content">
<view class="countdown-area">
<text class="countdown-time">{{remainingText}}</text>
<view class="countdown-label">剩余时间</view>
<view class="countdown-label">{{i18n.treating.remainingLabel}}</view>
</view>
<view class="progress-bar">
@@ -16,17 +16,17 @@
</view>
<view class="mode-tag-area">
<text class="mode-tag">{{mode === 1 ? '✨ 智能模式' : '🔄 普通模式'}} · {{regionText}}</text>
<text class="mode-tag">{{mode === 1 ? i18n.treating.smartMode : i18n.treating.normalMode}} · {{regionText}}</text>
</view>
<view class="relax-card">
<text class="relax-icon">💆</text>
<view class="relax-text">
<text>请放松心情</text>
<text>享受美好时光</text>
<text>{{i18n.treating.relax1}}</text>
<text>{{i18n.treating.relax2}}</text>
</view>
</view>
<button class="btn-primary btn-primary-red" bindtap="onStop">停止使用</button>
<button class="btn-primary btn-primary-red" bindtap="onStop">{{i18n.treating.stopBtn}}</button>
</view>
</view>
@@ -1,5 +1,6 @@
var api = require('../../utils/api')
var ble = require('../../services/ble')
var i18n = require('../../i18n/index')
Page({
data: {
@@ -15,6 +16,10 @@ Page({
synced: false
},
onShow: function () {
i18n.bind(this)
},
onBack: function () {
wx.reLaunch({ url: '/pages/index/index' })
},
@@ -33,7 +38,7 @@ Page({
regions: parseInt(options.regions) || 0,
duration: durationMs,
avgPd: options.avg_pd || 0,
durationText: mins > 0 ? mins + '分钟' : secs + '秒',
durationText: mins > 0 ? i18n.t('treatmentDone.durationMin', { min: mins }) : i18n.t('treatmentDone.durationSec', { sec: secs }),
mode: parseInt(options.mode) || 0,
regionNames: ble.getRegionName(parseInt(options.regions) || 0)
})
@@ -1,30 +1,30 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.treatmentDone.title}}</view>
<view class="page-header-subtitle">{{i18n.treatmentDone.headerSub}}</view>
</view>
<view class="page-content">
<view class="result-icon">✓</view>
<view class="result-title">使用完成!</view>
<view class="result-title">{{i18n.treatmentDone.resultTitle}}</view>
<view class="result-data">
<view class="result-item">
<view class="result-value">{{durationText}}</view>
<view class="result-label">使用时长</view>
<view class="result-label">{{i18n.treatmentDone.durationLabel}}</view>
</view>
<view class="result-item">
<view class="result-value">{{regionNames.length}}</view>
<view class="result-label">使用区域</view>
<view class="result-label">{{i18n.treatmentDone.regionLabel}}</view>
</view>
</view>
<view class="result-tip">
<text class="result-tip-icon">✨</text>
<text class="result-tip-text">本次使用已记录</text>
<text class="result-tip-text">{{i18n.treatmentDone.recorded}}</text>
</view>
<button class="btn-primary" bindtap="onBackHome">返回首页</button>
<button class="btn-primary" bindtap="onBackHome">{{i18n.treatmentDone.backHome}}</button>
</view>
</view>
@@ -4,11 +4,11 @@ Page({
data: {
statusBarHeight: 44,
regions: [
{ name: '右区', mask: 0x01, checked: true },
{ name: '左区', mask: 0x02, checked: true },
{ name: '上区', mask: 0x04, checked: true },
{ name: '中区', mask: 0x08, checked: true },
{ name: '下区', mask: 0x10, checked: true }
{ name: '', mask: 0x01, checked: true },
{ name: '', mask: 0x02, checked: true },
{ name: '', mask: 0x04, checked: true },
{ name: '', mask: 0x08, checked: true },
{ name: '', mask: 0x10, checked: true }
],
selectedMode: 0,
subExpired: false,
@@ -34,6 +34,15 @@ Page({
var app = getApp()
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
i18n.bind(this)
var regionKeys = ['right', 'left', 'top', 'middle', 'bottom']
var regions = this.data.regions
regions.forEach(function (r, i) {
r.name = i18n.t('common.regions.' + regionKeys[i])
})
this.setData({
regions: regions,
subDaysText: i18n.t('setup.remainDays', { days: this.data.subDays })
})
this.checkSubscription()
},
@@ -43,7 +52,8 @@ Page({
http.get('/api/v1/subscription').then(function (sub) {
self.setData({
subExpired: sub.status !== 'active',
subDays: sub.remaining_days || 0
subDays: sub.remaining_days || 0,
subDaysText: i18n.t('setup.remainDays', { days: sub.remaining_days || 0 })
})
}).catch(function () {})
},
@@ -79,7 +89,7 @@ Page({
})
if (mask === 0) {
wx.showToast({ title: '请至少选择一个区域', icon: 'none' })
wx.showToast({ title: i18n.t('setup.noRegion'), icon: 'none' })
return
}
@@ -109,7 +119,7 @@ Page({
'&mode=' + self.data.selectedMode
})
}).catch(function (err) {
self.setData({ submitting: false, error: err.error_msg || '启动失败' })
self.setData({ submitting: false, error: err.error_msg || i18n.t('setup.startFailed') })
})
},
@@ -1,40 +1,40 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.setup.title}}</view>
<view class="page-header-subtitle">{{i18n.setup.subtitle}}</view>
</view>
<view class="page-content">
<view class="page-title">选择模式</view>
<view class="page-title">{{i18n.setup.selectMode}}</view>
<view class="mode-selector">
<view class="mode-option {{selectedMode === 0 ? 'active' : ''}}" bindtap="onSelectMode" data-value="0">
<text class="mode-icon">🔄</text>
<view class="mode-label">普通模式</view>
<view class="mode-sub">6色循环</view>
<view class="mode-label">{{i18n.setup.normalMode}}</view>
<view class="mode-sub">{{i18n.setup.normalModeSub}}</view>
</view>
<view class="mode-option {{selectedMode === 1 ? 'active' : ''}} {{subExpired ? 'locked' : ''}}" bindtap="onSelectMode" data-value="1">
<text class="mode-icon">✨</text>
<view class="mode-label">智能模式</view>
<view class="mode-sub" wx:if="{{!subExpired}}">剩余{{subDays}}</view>
<view class="mode-sub" wx:else>订阅解锁</view>
<view class="mode-label">{{i18n.setup.smartMode}}</view>
<view class="mode-sub" wx:if="{{!subExpired}}">{{subDaysText}}</view>
<view class="mode-sub" wx:else>{{i18n.setup.subscribeUnlock}}</view>
</view>
</view>
<view class="page-title" style="font-size:28rpx;">选择区域</view>
<view class="mode-tip" wx:if="{{selectedMode === 0}}">普通模式固定 10 分钟,使用区域默认全区域,不可调整</view>
<view class="page-title" style="font-size:28rpx;">{{i18n.setup.selectRegion}}</view>
<view class="mode-tip" wx:if="{{selectedMode === 0}}">{{i18n.setup.normalTip}}</view>
<view class="face-map">
<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[1].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="1">左区</view>
<view class="face-region right-cheek {{regions[0].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="0">右区</view>
<view class="face-region nose {{regions[3].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="3">中区</view>
<view class="face-region chin {{regions[4].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="4">下区</view>
<view class="face-region forehead {{regions[2].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="2">{{i18n.common.regions.top}}</view>
<view class="face-region left-cheek {{regions[1].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="1">{{i18n.common.regions.left}}</view>
<view class="face-region right-cheek {{regions[0].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="0">{{i18n.common.regions.right}}</view>
<view class="face-region nose {{regions[3].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="3">{{i18n.common.regions.middle}}</view>
<view class="face-region chin {{regions[4].checked ? 'active' : ''}} {{selectedMode === 0 ? 'disabled' : ''}}" bindtap="onToggleRegion" data-idx="4">{{i18n.common.regions.bottom}}</view>
</view>
<button class="btn-primary" bindtap="onStart" disabled="{{submitting}}" loading="{{submitting}}">
开始使用
{{i18n.setup.startBtn}}
</button>
<view class="manual-entry" bindtap="onManual" style="text-align:center;color:#E8503A;font-size:26rpx;margin-top:28rpx;">{{i18n.manual.entryLink}}</view>
+9 -4
查看文件
@@ -1,4 +1,5 @@
var ble = require('../../services/ble')
var i18n = require('../../i18n/index')
Page({
data: {
@@ -8,6 +9,10 @@ Page({
reconnecting: false
},
onShow: function () {
i18n.bind(this)
},
onBack: function () {
wx.navigateBack({
fail: function () {
@@ -28,7 +33,7 @@ Page({
onNext: function () {
if (!ble.isConnected()) {
wx.showToast({ title: '设备未连接', icon: 'none' })
wx.showToast({ title: i18n.t('common.deviceNotConnected'), icon: 'none' })
return
}
wx.redirectTo({
@@ -51,18 +56,18 @@ Page({
onConnected: function () {
clearTimeout(self._reconnectTimer)
self.setData({ connected: true, reconnecting: false })
wx.showToast({ title: '连接成功', icon: 'success' })
wx.showToast({ title: i18n.t('wearCheck.connectSuccess'), icon: 'success' })
},
onError: function (err) {
clearTimeout(self._reconnectTimer)
self.setData({ reconnecting: false })
wx.showToast({ title: err.msg || '连接失败', icon: 'none' })
wx.showToast({ title: err.msg || i18n.t('wearCheck.connectFailed'), icon: 'none' })
}
})
self._reconnectTimer = setTimeout(function () {
ble.stopScan()
self.setData({ reconnecting: false })
wx.showToast({ title: '搜索超时', icon: 'none' })
wx.showToast({ title: i18n.t('wearCheck.searchTimeout'), icon: 'none' })
}, 5000)
}, 500)
},
+13 -13
查看文件
@@ -1,8 +1,8 @@
<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 class="nav-back" bindtap="onBack"> {{i18n.common.back}}</view>
<view class="page-header-title">{{i18n.wearCheck.title}}</view>
<view class="page-header-subtitle">{{i18n.wearCheck.subtitle}}</view>
</view>
<view class="page-content">
@@ -11,24 +11,24 @@
<view class="device-row-info">
<view class="device-row-name">
LumiFlow
<text class="status-badge status-badge-green" wx:if="{{connected}}">已连接</text>
<text class="status-badge" style="background:#ccc;" wx:else>未连接</text>
<text class="status-badge status-badge-green" wx:if="{{connected}}">{{i18n.wearCheck.connected}}</text>
<text class="status-badge" style="background:#ccc;" wx:else>{{i18n.wearCheck.disconnected}}</text>
</view>
<view class="device-row-battery">电量 {{battery}}%</view>
<view class="device-row-battery">{{i18n.wearCheck.battery}} {{battery}}%</view>
</view>
</view>
<view class="guide-card">
<view class="guide-title">佩戴指引</view>
<view class="guide-img">设备佩戴示意图</view>
<view class="guide-title">{{i18n.wearCheck.guideTitle}}</view>
<view class="guide-img">{{i18n.wearCheck.guideImg}}</view>
<view class="guide-steps">
<view class="guide-step">1. 取出设备,展开弹性绑带</view>
<view class="guide-step">2. 将设备正确佩戴</view>
<view class="guide-step">3. 调节绑带至舒适位置</view>
<view class="guide-step">{{i18n.wearCheck.step1}}</view>
<view class="guide-step">{{i18n.wearCheck.step2}}</view>
<view class="guide-step">{{i18n.wearCheck.step3}}</view>
</view>
</view>
<button class="btn-primary" wx:if="{{connected}}" bindtap="onNext">确认已佩戴,开始检测</button>
<button class="btn-secondary" wx:if="{{!connected}}" bindtap="onRetry" disabled="{{reconnecting}}" loading="{{reconnecting}}">{{reconnecting ? '正在连接...' : '重新连接'}}</button>
<button class="btn-primary" wx:if="{{connected}}" bindtap="onNext">{{i18n.wearCheck.confirmBtn}}</button>
<button class="btn-secondary" wx:if="{{!connected}}" bindtap="onRetry" disabled="{{reconnecting}}" loading="{{reconnecting}}">{{reconnecting ? i18n.wearCheck.connecting : i18n.wearCheck.reconnect}}</button>
</view>
</view>