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 行删除
+38 -3
查看文件
@@ -1,5 +1,40 @@
// Strings for the autoScan page. Fill zh/en with identical key structure.
// Strings for the auto-scan (smart scan) page. Keep zh/en with identical key structure.
// Pre/Post pairs are concatenated around a frame count in WXML (WXML cannot do {param}).
module.exports = {
zh: {},
en: {}
zh: {
title: '智能检测',
subReady: '准备就绪',
subWaiting: '等待佩戴贴合...',
subCollectingPre: '采集数据中 (',
subCollectingPost: '帧)...',
subDone: '检测完成,正在启动...',
readyHint: '设备已佩戴,点击下方按钮开始智能检测',
startBtn: '开始检测',
waitingStatus: '正在检测面膜贴合状态,请确保设备正确佩戴...',
collectingStatusPre: '已贴合,正在采集光谱数据... 已收到 ',
collectingStatusPost: ' 帧',
doneStatusPre: '检测完成(共 ',
doneStatusPost: ' 帧),正在启动治疗...',
notWorn: '面膜未佩戴好,请重新佩戴后再试',
timeout: '检测超时,未收到数据,请重试',
cmdFailed: '检测命令发送失败'
},
en: {
title: 'Smart Scan',
subReady: 'Ready',
subWaiting: 'Waiting for fit...',
subCollectingPre: 'Collecting data (',
subCollectingPost: ' frames)...',
subDone: 'Scan complete, starting...',
readyHint: 'Device is on. Tap the button below to start the smart scan',
startBtn: 'Start Scan',
waitingStatus: 'Checking mask fit, please make sure the device is worn correctly...',
collectingStatusPre: 'Fitted. Collecting spectral data... received ',
collectingStatusPost: ' frames',
doneStatusPre: 'Scan complete (',
doneStatusPost: ' frames), starting treatment...',
notWorn: 'The mask is not worn properly. Please re-fit and try again',
timeout: 'Scan timed out with no data. Please try again',
cmdFailed: 'Failed to send scan command'
}
}
+20 -2
查看文件
@@ -1,5 +1,23 @@
// Strings for the bindSuccess page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '绑定成功',
subtitle: '设备已成功绑定',
successTitle: '绑定成功!',
freeModeOpen: '智能模式已开放',
trialGranted: '恭喜获得{days}天智能模式试用',
trialSub: '体验结束后可订阅继续使用',
startUsing: '开始使用',
goHome: '返回首页'
},
en: {
title: 'Bound Successfully',
subtitle: 'Device bound successfully',
successTitle: 'Bound Successfully!',
freeModeOpen: 'Smart mode unlocked',
trialGranted: 'You got a {days}-day Smart mode trial',
trialSub: 'Subscribe to keep using it after the trial ends',
startUsing: 'Get Started',
goHome: 'Back to Home'
}
}
+30 -2
查看文件
@@ -1,5 +1,33 @@
// Strings for the bleConnect page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '蓝牙连接',
searchingSub: '正在搜索设备...',
connectingSub: '正在连接设备...',
bindingSub: '正在绑定...',
failedSub: '连接失败',
bluetoothTip: '请确保手机蓝牙已开启',
searching: '搜索中...',
pairing: '正在配对...',
failed: '连接失败',
scanTimeout: '搜索超时,请确认设备已开机并在附近',
confirmFailed: '后台确认绑定失败',
bindFailed: '设备绑定失败',
bindCmdFailed: '绑定命令失败'
},
en: {
title: 'Bluetooth Connection',
searchingSub: 'Searching for device...',
connectingSub: 'Connecting to device...',
bindingSub: 'Binding...',
failedSub: 'Connection failed',
bluetoothTip: 'Please make sure Bluetooth is enabled on your phone',
searching: 'Searching...',
pairing: 'Pairing...',
failed: 'Connection failed',
scanTimeout: 'Search timed out. Please make sure the device is powered on and nearby',
confirmFailed: 'Failed to confirm binding on the server',
bindFailed: 'Device binding failed',
bindCmdFailed: 'Binding command failed'
}
}
+10 -2
查看文件
@@ -1,5 +1,13 @@
// Strings for the contact page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '联系我们',
placeholderText: '内容建设中',
placeholderSub: '联系方式正在整理,敬请期待'
},
en: {
title: 'Contact Us',
placeholderText: 'Coming soon',
placeholderSub: 'Contact details are being prepared. Stay tuned.'
}
}
+10 -2
查看文件
@@ -1,5 +1,13 @@
// Strings for the help page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '使用帮助',
placeholderText: '内容建设中',
placeholderSub: '帮助文档正在编写,敬请期待'
},
en: {
title: 'Help',
placeholderText: 'Coming soon',
placeholderSub: 'The help guide is being written. Stay tuned.'
}
}
+18 -2
查看文件
@@ -1,5 +1,21 @@
// Strings for the login page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
wechatLogin: '微信登录',
welcome: '欢迎使用LumiFlow',
desc: '登录后即可使用全部功能',
agreement: '登录即表示同意《用户协议》和《隐私政策》',
loginFailed: '登录失败',
agreementTitle: '提示',
agreementContent: '用户协议和隐私政策内容建设中'
},
en: {
wechatLogin: 'WeChat Login',
welcome: 'Welcome to LumiFlow',
desc: 'Log in to unlock all features',
agreement: 'By logging in, you agree to the Terms of Service and Privacy Policy',
loginFailed: 'Login failed',
agreementTitle: 'Notice',
agreementContent: 'Terms of Service and Privacy Policy are coming soon'
}
}
+42 -2
查看文件
@@ -1,5 +1,45 @@
// Strings for the register page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
subtitle: '完善个人资料',
avatarSection: '设置头像',
changeAvatar: '点击更换',
nicknameSection: '设置昵称',
nicknamePlaceholder: '请输入昵称',
authorizePhone: '授权手机号',
phoneHint: '手机号用于接收服务通知,必须授权',
submit: '完成注册',
agreement: '注册即表示同意《用户协议》和《隐私政策》',
nicknamePrefix: '用户',
phoneRequired: '需要授权手机号才能完成注册',
phoneAuthSuccess: '手机号授权成功',
phoneAuthFailed: '手机号授权失败',
authorized: '已授权',
uploadFailed: '上传失败',
phoneFirst: '请先授权手机号',
nicknameRequired: '请输入昵称',
agreementTitle: '提示',
agreementContent: '用户协议和隐私政策内容建设中'
},
en: {
subtitle: 'Complete Your Profile',
avatarSection: 'Set Avatar',
changeAvatar: 'Tap to change',
nicknameSection: 'Set Nickname',
nicknamePlaceholder: 'Enter a nickname',
authorizePhone: 'Authorize Phone Number',
phoneHint: 'Your phone number is used to receive service notifications and is required',
submit: 'Complete Registration',
agreement: 'By registering, you agree to the Terms of Service and Privacy Policy',
nicknamePrefix: 'User',
phoneRequired: 'Phone number authorization is required to complete registration',
phoneAuthSuccess: 'Phone number authorized',
phoneAuthFailed: 'Phone authorization failed',
authorized: 'Authorized',
uploadFailed: 'Upload failed',
phoneFirst: 'Please authorize your phone number first',
nicknameRequired: 'Please enter a nickname',
agreementTitle: 'Notice',
agreementContent: 'Terms of Service and Privacy Policy are coming soon'
}
}
+26 -2
查看文件
@@ -1,5 +1,29 @@
// Strings for the scan page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '扫码绑定',
subtitle: '扫描设备底部二维码',
scanText: '扫描设备二维码',
scan: '扫码',
scanning: '扫描中...',
manualInput: '手动输入设备号',
manualPlaceholder: '请输入设备ID,如 672B6D5A 4DCD861',
invalidQr: '无效的设备二维码',
alreadyBound: '已绑定设备',
bindFailed: '绑定失败',
invalidFormat: '设备号格式不正确'
},
en: {
title: 'Scan to Bind',
subtitle: 'Scan the QR code on the bottom of the device',
scanText: 'Scan device QR code',
scan: 'Scan',
scanning: 'Scanning...',
manualInput: 'Enter Device ID Manually',
manualPlaceholder: 'Enter device ID, e.g. 672B6D5A 4DCD861',
invalidQr: 'Invalid device QR code',
alreadyBound: 'Device already bound',
bindFailed: 'Binding failed',
invalidFormat: 'Invalid device ID format'
}
}
+31 -3
查看文件
@@ -1,5 +1,33 @@
// Strings for the setup page. Fill zh/en with identical key structure.
// Strings for the treatment-setup page. Keep zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '使用设置',
subtitle: '选择模式和区域',
selectMode: '选择模式',
normalMode: '普通模式',
normalModeSub: '6色循环',
smartMode: '智能模式',
remainDays: '剩余{days}天',
subscribeUnlock: '订阅解锁',
selectRegion: '选择区域',
normalTip: '普通模式固定 10 分钟,使用区域默认全区域,不可调整',
startBtn: '开始使用',
noRegion: '请至少选择一个区域',
startFailed: '启动失败'
},
en: {
title: 'Session Setup',
subtitle: 'Choose mode and regions',
selectMode: 'Select Mode',
normalMode: 'Normal Mode',
normalModeSub: '6-color cycle',
smartMode: 'Smart Mode',
remainDays: '{days} days left',
subscribeUnlock: 'Subscribe to unlock',
selectRegion: 'Select Regions',
normalTip: 'Normal mode runs a fixed 10 minutes over all regions and cannot be adjusted',
startBtn: 'Start',
noRegion: 'Please select at least one region',
startFailed: 'Failed to start'
}
}
+84 -2
查看文件
@@ -1,5 +1,87 @@
// Strings for the subPlans page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '订阅服务',
subtitle: '解锁智能模式',
currentPlanLabel: '当前套餐',
remainDaysLabel: '剩余天数',
daysValue: '{days}天',
choosePlan: '选择订阅套餐',
loadingPlans: '加载套餐中...',
smartMode: '智能模式',
smartModeDesc: '智能自动调节',
renew: '续费',
confirmPay: '确认支付',
agreement: '支付即表示同意《订阅协议》',
// plan names (also used as fallback plan labels)
planTrial: '试用',
planMonthly: '月卡',
planYearly: '年卡',
// computed plan descriptions
free: '免费',
descTrial: '{days}天免费体验',
descSave: '省¥{amount}',
descDaily: '约{price}元/天',
tagRecommend: '推荐',
tagUsed: '已使用',
// modals / toasts
tipTitle: '提示',
agreementBuilding: '订阅协议内容建设中',
activateTrialTitle: '激活试用',
activateTrialConfirm: '免费试用 {desc}?',
confirmRenewTitle: '确认续费',
confirmPayTitle: '确认支付',
renewContent: '在现有订阅基础上延长{days}天,支付 ¥{price}',
payContent: '支付 ¥{price}',
trialActivated: '试用已激活',
activateFailed: '激活失败',
payUnavailable: '支付服务暂不可用',
payCancelled: '已取消支付',
payFailed: '支付失败',
createOrderFailed: '创建订单失败',
paySuccess: '支付成功',
payProcessing: '支付处理中,请稍后在订阅页查看'
},
en: {
title: 'Subscription',
subtitle: 'Unlock Smart Mode',
currentPlanLabel: 'Current plan',
remainDaysLabel: 'Days remaining',
daysValue: '{days} days',
choosePlan: 'Choose a plan',
loadingPlans: 'Loading plans...',
smartMode: 'Smart Mode',
smartModeDesc: 'Automatic smart adjustment',
renew: 'Renew',
confirmPay: 'Pay Now',
agreement: 'By paying you agree to the Subscription Agreement',
// plan names (also used as fallback plan labels)
planTrial: 'Trial',
planMonthly: 'Monthly',
planYearly: 'Yearly',
// computed plan descriptions
free: 'Free',
descTrial: '{days}-day free trial',
descSave: 'Save ¥{amount}',
descDaily: 'About ¥{price}/day',
tagRecommend: 'Best value',
tagUsed: 'Used',
// modals / toasts
tipTitle: 'Notice',
agreementBuilding: 'The subscription agreement is being prepared',
activateTrialTitle: 'Activate trial',
activateTrialConfirm: 'Start free trial: {desc}?',
confirmRenewTitle: 'Confirm renewal',
confirmPayTitle: 'Confirm payment',
renewContent: 'Extend your current subscription by {days} days for ¥{price}',
payContent: 'Pay ¥{price}',
trialActivated: 'Trial activated',
activateFailed: 'Activation failed',
payUnavailable: 'Payment service is unavailable',
payCancelled: 'Payment cancelled',
payFailed: 'Payment failed',
createOrderFailed: 'Failed to create order',
paySuccess: 'Payment successful',
payProcessing: 'Payment is processing. Please check the subscription page later.'
}
}
+26 -2
查看文件
@@ -1,5 +1,29 @@
// Strings for the subPrompt page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '使用设置',
subtitle: '试用期已结束',
selectMode: '选择模式',
normalMode: '普通模式',
normalModeSub: '6色循环',
smartMode: '智能模式',
smartModeSub: '订阅解锁',
trialEndedTitle: '试用已结束',
trialEndedSub: '订阅后继续使用智能模式',
subscribeNow: '立即订阅',
useNormalFirst: '先使用普通模式'
},
en: {
title: 'Settings',
subtitle: 'Your free trial has ended',
selectMode: 'Choose a mode',
normalMode: 'Standard Mode',
normalModeSub: '6-color cycle',
smartMode: 'Smart Mode',
smartModeSub: 'Unlock with subscription',
trialEndedTitle: 'Trial ended',
trialEndedSub: 'Subscribe to keep using Smart Mode',
subscribeNow: 'Subscribe Now',
useNormalFirst: 'Use Standard Mode for now'
}
}
+26 -2
查看文件
@@ -1,5 +1,29 @@
// Strings for the subSuccess page. Fill zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '订阅成功',
subtitle: '欢迎使用智能模式',
successTitle: '订阅成功!',
subscribed: '已订阅',
validUntil: '有效期至:',
startSmart: '立即体验智能模式',
backHome: '返回首页',
planTrial: '试用会员',
planMonthly: '月卡会员',
planYearly: '年卡会员',
planDefault: '会员'
},
en: {
title: 'Subscription Successful',
subtitle: 'Welcome to Smart Mode',
successTitle: 'Subscription successful!',
subscribed: 'Subscribed',
validUntil: 'Valid until: ',
startSmart: 'Try Smart Mode now',
backHome: 'Back to Home',
planTrial: 'Trial Member',
planMonthly: 'Monthly Member',
planYearly: 'Yearly Member',
planDefault: 'Member'
}
}
+57 -3
查看文件
@@ -1,5 +1,59 @@
// Strings for the treating page. Fill zh/en with identical key structure.
// Strings for the treating (live treatment) page. Keep zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '使用中',
subtitle: '正在使用...',
remainingLabel: '剩余时间',
smartMode: '✨ 智能模式',
normalMode: '🔄 普通模式',
allRegions: '全区域',
regionTopLeft: '左上区',
regionTopRight: '右上区',
relax1: '请放松心情',
relax2: '享受美好时光',
stopBtn: '停止使用',
fittingLost: '检测到离肤,请重新佩戴设备',
fittingRestored: '已重新贴合,继续使用',
faultTitle: '设备故障',
faultContent: '设备上报故障,已自动停止。',
lowBatteryTitle: '电量过低',
lowBatteryContent: '设备电量不足5%,已自动停止。请及时充电。',
lowBatteryWarn: '电量低,请及时充电',
exceptionTitle: '设备异常',
exceptionContent: '使用过程中出现异常',
disconnected: '设备连接断开,正在重连...',
reconnected: '设备已重新连接',
reconnectFailedTitle: '连接丢失',
reconnectFailedContent: '设备蓝牙连接已断开,无法恢复。本次使用数据将保存。',
stopTitle: '结束使用',
stopContent: '确定要提前结束本次使用吗?'
},
en: {
title: 'In Progress',
subtitle: 'Treatment in progress...',
remainingLabel: 'Time Left',
smartMode: '✨ Smart Mode',
normalMode: '🔄 Normal Mode',
allRegions: 'All regions',
regionTopLeft: 'Top-left',
regionTopRight: 'Top-right',
relax1: 'Relax and unwind',
relax2: 'Enjoy your moment',
stopBtn: 'Stop',
fittingLost: 'Device lifted off skin. Please put it back on',
fittingRestored: 'Re-fitted, continuing',
faultTitle: 'Device Fault',
faultContent: 'The device reported a fault and stopped automatically.',
lowBatteryTitle: 'Battery Too Low',
lowBatteryContent: 'Battery is below 5%. Stopped automatically. Please charge soon.',
lowBatteryWarn: 'Low battery, please charge soon',
exceptionTitle: 'Device Error',
exceptionContent: 'Something went wrong during the session',
disconnected: 'Device disconnected, reconnecting...',
reconnected: 'Device reconnected',
reconnectFailedTitle: 'Connection Lost',
reconnectFailedContent: 'The Bluetooth connection was lost and cannot be restored. This session will be saved.',
stopTitle: 'End Session',
stopContent: 'Are you sure you want to end this session early?'
}
}
+23 -3
查看文件
@@ -1,5 +1,25 @@
// Strings for the treatmentDone page. Fill zh/en with identical key structure.
// Strings for the treatment-done page. Keep zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '使用完成',
headerSub: '本次使用已结束',
resultTitle: '使用完成!',
durationLabel: '使用时长',
regionLabel: '使用区域',
recorded: '本次使用已记录',
backHome: '返回首页',
durationMin: '{min}分钟',
durationSec: '{sec}秒'
},
en: {
title: 'Complete',
headerSub: 'This session has ended',
resultTitle: 'Session Complete!',
durationLabel: 'Duration',
regionLabel: 'Regions',
recorded: 'This session has been recorded',
backHome: 'Back to Home',
durationMin: '{min} min',
durationSec: '{sec} s'
}
}
+37 -3
查看文件
@@ -1,5 +1,39 @@
// Strings for the wearCheck page. Fill zh/en with identical key structure.
// Strings for the wear-check page. Keep zh/en with identical key structure.
module.exports = {
zh: {},
en: {}
zh: {
title: '确认佩戴',
subtitle: '请确保设备正确佩戴',
connected: '已连接',
disconnected: '未连接',
battery: '电量',
guideTitle: '佩戴指引',
guideImg: '设备佩戴示意图',
step1: '1. 取出设备,展开弹性绑带',
step2: '2. 将设备正确佩戴',
step3: '3. 调节绑带至舒适位置',
confirmBtn: '确认已佩戴,开始检测',
reconnect: '重新连接',
connecting: '正在连接...',
connectSuccess: '连接成功',
connectFailed: '连接失败',
searchTimeout: '搜索超时'
},
en: {
title: 'Confirm Fit',
subtitle: 'Make sure the device is worn correctly',
connected: 'Connected',
disconnected: 'Not connected',
battery: 'Battery',
guideTitle: 'How to Wear',
guideImg: 'Wearing diagram',
step1: '1. Take out the device and unfold the strap',
step2: '2. Put the device on correctly',
step3: '3. Adjust the strap to a comfortable fit',
confirmBtn: 'Confirm Fit & Continue',
reconnect: 'Reconnect',
connecting: 'Connecting...',
connectSuccess: 'Connected',
connectFailed: 'Connection failed',
searchTimeout: 'Search timed out'
}
}
+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>