完善多赛道联调与全局产品架构
This commit is contained in:
@@ -71,6 +71,7 @@ type MapPageData = MapEngineViewState & {
|
||||
showGameInfoPanel: boolean
|
||||
showResultScene: boolean
|
||||
showSystemSettingsPanel: boolean
|
||||
showHeartRateDevicePicker: boolean
|
||||
showCenterScaleRuler: boolean
|
||||
showPunchHintBanner: boolean
|
||||
punchHintFxClass: string
|
||||
@@ -92,6 +93,7 @@ type MapPageData = MapEngineViewState & {
|
||||
resultSceneHeroLabel: string
|
||||
resultSceneHeroValue: string
|
||||
resultSceneRows: MapEngineGameInfoRow[]
|
||||
resultSceneCountdownText: string
|
||||
panelTimerText: string
|
||||
panelTimerMode: 'elapsed' | 'countdown'
|
||||
panelMileageText: string
|
||||
@@ -157,6 +159,7 @@ const PUNCH_HINT_AUTO_HIDE_MS = 30000
|
||||
const PUNCH_HINT_FX_DURATION_MS = 420
|
||||
const PUNCH_HINT_HAPTIC_GAP_MS = 2400
|
||||
const SESSION_RECOVERY_PERSIST_INTERVAL_MS = 5000
|
||||
const RESULT_EXIT_REDIRECT_DELAY_MS = 3000
|
||||
let currentGameLaunchEnvelope: GameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
||||
let mapEngine: MapEngine | null = null
|
||||
let stageCanvasAttached = false
|
||||
@@ -172,6 +175,8 @@ let panelMileageFxTimer = 0
|
||||
let panelSpeedFxTimer = 0
|
||||
let panelHeartRateFxTimer = 0
|
||||
let sessionRecoveryPersistTimer = 0
|
||||
let resultExitRedirectTimer = 0
|
||||
let resultExitCountdownTimer = 0
|
||||
let lastPunchHintHapticAt = 0
|
||||
let currentSystemSettingsConfig: SystemSettingsConfig | undefined
|
||||
let currentRemoteMapConfig: RemoteMapConfig | undefined
|
||||
@@ -179,6 +184,8 @@ let systemSettingsLockLifetimeActive = false
|
||||
let syncedBackendSessionStartId = ''
|
||||
let syncedBackendSessionFinishId = ''
|
||||
let shouldAutoRestoreRecoverySnapshot = false
|
||||
let redirectedToResultPage = false
|
||||
let pendingHeartRateSwitchDeviceName: string | null = null
|
||||
const DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY = 'cmr.debug.mockChannelId.v1'
|
||||
const DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY = 'cmr.debug.autoConnectMockSources.v1'
|
||||
let lastCenterScaleRulerStablePatch: Pick<
|
||||
@@ -469,6 +476,34 @@ function clearSessionRecoveryPersistTimer() {
|
||||
}
|
||||
}
|
||||
|
||||
function clearResultExitRedirectTimer() {
|
||||
if (resultExitRedirectTimer) {
|
||||
clearTimeout(resultExitRedirectTimer)
|
||||
resultExitRedirectTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
function clearResultExitCountdownTimer() {
|
||||
if (resultExitCountdownTimer) {
|
||||
clearInterval(resultExitCountdownTimer)
|
||||
resultExitCountdownTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
function navigateAwayFromMapAfterCancel() {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
wx.navigateBack({
|
||||
delta: 1,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.redirectTo({
|
||||
url: '/pages/home/home',
|
||||
})
|
||||
}
|
||||
|
||||
function hasExplicitLaunchOptions(options?: MapPageLaunchOptions | null): boolean {
|
||||
if (!options) {
|
||||
return false
|
||||
@@ -776,11 +811,12 @@ function buildEmptyResultSceneSnapshot(): MapEngineResultSnapshot {
|
||||
|
||||
Page({
|
||||
data: {
|
||||
showDebugPanel: false,
|
||||
showGameInfoPanel: false,
|
||||
showResultScene: false,
|
||||
showSystemSettingsPanel: false,
|
||||
showCenterScaleRuler: false,
|
||||
showDebugPanel: false,
|
||||
showGameInfoPanel: false,
|
||||
showResultScene: false,
|
||||
showSystemSettingsPanel: false,
|
||||
showHeartRateDevicePicker: false,
|
||||
showCenterScaleRuler: false,
|
||||
statusBarHeight: 0,
|
||||
topInsetHeight: 12,
|
||||
hudPanelIndex: 0,
|
||||
@@ -798,6 +834,7 @@ Page({
|
||||
resultSceneHeroLabel: '本局用时',
|
||||
resultSceneHeroValue: '--',
|
||||
resultSceneRows: buildEmptyResultSceneSnapshot().rows,
|
||||
resultSceneCountdownText: '',
|
||||
panelTimerText: '00:00:00',
|
||||
panelTimerMode: 'elapsed',
|
||||
panelMileageText: '0m',
|
||||
@@ -927,8 +964,11 @@ Page({
|
||||
|
||||
onLoad(options: MapPageLaunchOptions) {
|
||||
clearSessionRecoveryPersistTimer()
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
syncedBackendSessionStartId = ''
|
||||
syncedBackendSessionFinishId = ''
|
||||
redirectedToResultPage = false
|
||||
shouldAutoRestoreRecoverySnapshot = options && options.recoverSession === '1'
|
||||
currentGameLaunchEnvelope = resolveGameLaunchEnvelope(options)
|
||||
if (!hasExplicitLaunchOptions(options)) {
|
||||
@@ -959,6 +999,7 @@ Page({
|
||||
const includeRulerFields = this.data.showCenterScaleRuler
|
||||
let shouldSyncRuntimeSystemSettings = false
|
||||
let nextLockLifetimeActive = isSystemSettingsLockLifetimeActive()
|
||||
let heartRateSwitchToastText = ''
|
||||
const nextData: Partial<MapPageData> = filterDebugOnlyPatch({
|
||||
...nextPatch,
|
||||
}, includeDebugFields, includeRulerFields)
|
||||
@@ -1054,6 +1095,8 @@ Page({
|
||||
: this.data.animationLevel
|
||||
let shouldSyncBackendSessionStart = false
|
||||
let backendSessionFinishStatus: 'finished' | 'failed' | null = null
|
||||
let shouldOpenResultExitPrompt = false
|
||||
let resultPageSnapshot: MapEngineResultSnapshot | null = null
|
||||
|
||||
if (nextAnimationLevel === 'lite') {
|
||||
clearHudFxTimer('timer')
|
||||
@@ -1112,13 +1155,24 @@ Page({
|
||||
shouldSyncRuntimeSystemSettings = true
|
||||
clearSessionRecoverySnapshot()
|
||||
clearSessionRecoveryPersistTimer()
|
||||
this.syncResultSceneSnapshot()
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
resultPageSnapshot = mapEngine ? mapEngine.getResultSceneSnapshot() : null
|
||||
nextData.showResultScene = true
|
||||
nextData.showDebugPanel = false
|
||||
nextData.showGameInfoPanel = false
|
||||
nextData.showSystemSettingsPanel = false
|
||||
clearGameInfoPanelSyncTimer()
|
||||
backendSessionFinishStatus = nextPatch.gameSessionStatus === 'finished' ? 'finished' : 'failed'
|
||||
shouldOpenResultExitPrompt = true
|
||||
if (resultPageSnapshot) {
|
||||
nextData.resultSceneTitle = resultPageSnapshot.title
|
||||
nextData.resultSceneSubtitle = resultPageSnapshot.subtitle
|
||||
nextData.resultSceneHeroLabel = resultPageSnapshot.heroLabel
|
||||
nextData.resultSceneHeroValue = resultPageSnapshot.heroValue
|
||||
nextData.resultSceneRows = resultPageSnapshot.rows
|
||||
}
|
||||
nextData.resultSceneCountdownText = '3 秒后自动进入成绩页'
|
||||
} else if (
|
||||
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
||||
&& nextPatch.gameSessionStatus === 'idle'
|
||||
@@ -1128,6 +1182,8 @@ Page({
|
||||
shouldSyncRuntimeSystemSettings = true
|
||||
clearSessionRecoverySnapshot()
|
||||
clearSessionRecoveryPersistTimer()
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
} else if (
|
||||
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
||||
&& nextPatch.gameSessionStatus === 'running'
|
||||
@@ -1138,6 +1194,19 @@ Page({
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
pendingHeartRateSwitchDeviceName
|
||||
&& nextPatch.heartRateConnected === true
|
||||
&& typeof nextPatch.heartRateDeviceText === 'string'
|
||||
) {
|
||||
const connectedDeviceName = nextPatch.heartRateDeviceText.trim()
|
||||
if (connectedDeviceName && connectedDeviceName === pendingHeartRateSwitchDeviceName) {
|
||||
heartRateSwitchToastText = `已切换到 ${connectedDeviceName}`
|
||||
nextData.statusText = `已切换心率带:${connectedDeviceName}`
|
||||
pendingHeartRateSwitchDeviceName = null
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(nextData).length || Object.keys(derivedPatch).length) {
|
||||
this.setData({
|
||||
...nextData,
|
||||
@@ -1152,9 +1221,20 @@ Page({
|
||||
if (backendSessionFinishStatus) {
|
||||
this.syncBackendSessionFinish(backendSessionFinishStatus)
|
||||
}
|
||||
if (shouldSyncRuntimeSystemSettings) {
|
||||
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
||||
}
|
||||
if (shouldOpenResultExitPrompt && resultPageSnapshot) {
|
||||
this.stashPendingResultSnapshot(resultPageSnapshot)
|
||||
this.presentResultExitPrompt()
|
||||
}
|
||||
if (heartRateSwitchToastText) {
|
||||
wx.showToast({
|
||||
title: `${heartRateSwitchToastText},并设为首选设备`,
|
||||
icon: 'none',
|
||||
duration: 1800,
|
||||
})
|
||||
}
|
||||
if (shouldSyncRuntimeSystemSettings) {
|
||||
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
||||
}
|
||||
if (this.data.showGameInfoPanel) {
|
||||
this.scheduleGameInfoPanelSnapshotSync()
|
||||
}
|
||||
@@ -1169,6 +1249,10 @@ Page({
|
||||
if (backendSessionFinishStatus) {
|
||||
this.syncBackendSessionFinish(backendSessionFinishStatus)
|
||||
}
|
||||
if (shouldOpenResultExitPrompt && resultPageSnapshot) {
|
||||
this.stashPendingResultSnapshot(resultPageSnapshot)
|
||||
this.presentResultExitPrompt()
|
||||
}
|
||||
if (shouldSyncRuntimeSystemSettings) {
|
||||
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
||||
}
|
||||
@@ -1209,6 +1293,7 @@ Page({
|
||||
...buildResolvedSystemSettingsPatch(systemSettingsState),
|
||||
showDebugPanel: false,
|
||||
showGameInfoPanel: false,
|
||||
showResultScene: false,
|
||||
showSystemSettingsPanel: false,
|
||||
statusBarHeight,
|
||||
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
|
||||
@@ -1218,6 +1303,12 @@ Page({
|
||||
gameInfoSubtitle: '未开始',
|
||||
gameInfoLocalRows: [],
|
||||
gameInfoGlobalRows: buildEmptyGameInfoSnapshot().globalRows,
|
||||
resultSceneTitle: '本局结果',
|
||||
resultSceneSubtitle: '未开始',
|
||||
resultSceneHeroLabel: '本局用时',
|
||||
resultSceneHeroValue: '--',
|
||||
resultSceneRows: buildEmptyResultSceneSnapshot().rows,
|
||||
resultSceneCountdownText: '',
|
||||
panelTimerText: '00:00:00',
|
||||
panelTimerMode: 'elapsed',
|
||||
panelTimerFxClass: '',
|
||||
@@ -1349,6 +1440,18 @@ Page({
|
||||
stageCanvasAttached = false
|
||||
this.measureStageAndCanvas()
|
||||
this.loadGameLaunchEnvelope(currentGameLaunchEnvelope)
|
||||
const app = getApp<IAppOption>()
|
||||
const pendingHeartRateAutoConnect = app.globalData ? app.globalData.pendingHeartRateAutoConnect : null
|
||||
if (pendingHeartRateAutoConnect && pendingHeartRateAutoConnect.enabled && mapEngine) {
|
||||
const pendingDeviceName = pendingHeartRateAutoConnect.deviceName || '心率带'
|
||||
app.globalData.pendingHeartRateAutoConnect = null
|
||||
mapEngine.handleConnectHeartRate()
|
||||
this.setData({
|
||||
statusText: `正在自动连接局前设备:${pendingDeviceName}`,
|
||||
heartRateStatusText: `正在自动连接 ${pendingDeviceName}`,
|
||||
heartRateDeviceText: pendingDeviceName,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -1360,6 +1463,8 @@ Page({
|
||||
|
||||
onHide() {
|
||||
this.persistSessionRecoverySnapshot()
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
if (mapEngine) {
|
||||
mapEngine.handleAppHide()
|
||||
}
|
||||
@@ -1368,6 +1473,8 @@ Page({
|
||||
onUnload() {
|
||||
this.persistSessionRecoverySnapshot()
|
||||
clearSessionRecoveryPersistTimer()
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
syncedBackendSessionStartId = ''
|
||||
syncedBackendSessionFinishId = ''
|
||||
clearGameInfoPanelSyncTimer()
|
||||
@@ -1388,6 +1495,7 @@ Page({
|
||||
systemSettingsLockLifetimeActive = false
|
||||
currentGameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
||||
shouldAutoRestoreRecoverySnapshot = false
|
||||
redirectedToResultPage = false
|
||||
stageCanvasAttached = false
|
||||
},
|
||||
|
||||
@@ -1528,6 +1636,57 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
stashPendingResultSnapshot(snapshot: MapEngineResultSnapshot) {
|
||||
const app = getApp<IAppOption>()
|
||||
if (app.globalData) {
|
||||
app.globalData.pendingResultSnapshot = snapshot
|
||||
}
|
||||
},
|
||||
|
||||
redirectToResultPage() {
|
||||
if (redirectedToResultPage) {
|
||||
return
|
||||
}
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
redirectedToResultPage = true
|
||||
const sessionContext = getCurrentBackendSessionContext()
|
||||
const resultUrl = sessionContext
|
||||
? `/pages/result/result?sessionId=${encodeURIComponent(sessionContext.sessionId)}`
|
||||
: '/pages/result/result'
|
||||
wx.redirectTo({
|
||||
url: resultUrl,
|
||||
})
|
||||
},
|
||||
|
||||
presentResultExitPrompt() {
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
|
||||
let remainingSeconds = Math.ceil(RESULT_EXIT_REDIRECT_DELAY_MS / 1000)
|
||||
this.setData({
|
||||
showResultScene: true,
|
||||
resultSceneCountdownText: `${remainingSeconds} 秒后自动进入成绩页`,
|
||||
})
|
||||
|
||||
resultExitCountdownTimer = setInterval(() => {
|
||||
remainingSeconds -= 1
|
||||
if (remainingSeconds <= 0) {
|
||||
clearResultExitCountdownTimer()
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({
|
||||
resultSceneCountdownText: `${remainingSeconds} 秒后自动进入成绩页`,
|
||||
})
|
||||
}, 1000) as unknown as number
|
||||
|
||||
resultExitRedirectTimer = setTimeout(() => {
|
||||
resultExitRedirectTimer = 0
|
||||
this.redirectToResultPage()
|
||||
}, RESULT_EXIT_REDIRECT_DELAY_MS) as unknown as number
|
||||
},
|
||||
|
||||
restoreRecoverySnapshot(snapshot: SessionRecoverySnapshot) {
|
||||
systemSettingsLockLifetimeActive = true
|
||||
this.applyRuntimeSystemSettings(true)
|
||||
@@ -2052,20 +2211,53 @@ Page({
|
||||
},
|
||||
|
||||
handleConnectHeartRate() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleConnectHeartRate()
|
||||
}
|
||||
},
|
||||
|
||||
handleDisconnectHeartRate() {
|
||||
if (mapEngine) {
|
||||
mapEngine.handleDisconnectHeartRate()
|
||||
if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
||||
return
|
||||
}
|
||||
if (mapEngine) {
|
||||
mapEngine.handleConnectHeartRate()
|
||||
}
|
||||
},
|
||||
|
||||
handleOpenHeartRateDevicePicker() {
|
||||
if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
showHeartRateDevicePicker: true,
|
||||
})
|
||||
if (mapEngine) {
|
||||
mapEngine.handleConnectHeartRate()
|
||||
}
|
||||
},
|
||||
|
||||
handleCloseHeartRateDevicePicker() {
|
||||
this.setData({
|
||||
showHeartRateDevicePicker: false,
|
||||
})
|
||||
},
|
||||
|
||||
handleDisconnectHeartRate() {
|
||||
if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
||||
return
|
||||
}
|
||||
if (mapEngine) {
|
||||
mapEngine.handleDisconnectHeartRate()
|
||||
}
|
||||
},
|
||||
|
||||
handleConnectHeartRateDevice(event: WechatMiniprogram.BaseEvent<{ deviceId?: string }>) {
|
||||
if (mapEngine && event.currentTarget && event.currentTarget.dataset && event.currentTarget.dataset.deviceId) {
|
||||
mapEngine.handleConnectHeartRateDevice(event.currentTarget.dataset.deviceId)
|
||||
const targetDeviceId = event.currentTarget.dataset.deviceId
|
||||
const targetDevice = this.data.heartRateDiscoveredDevices.find((item) => item.deviceId === targetDeviceId)
|
||||
pendingHeartRateSwitchDeviceName = targetDevice ? targetDevice.name : null
|
||||
mapEngine.handleConnectHeartRateDevice(targetDeviceId)
|
||||
this.setData({
|
||||
showHeartRateDevicePicker: false,
|
||||
statusText: targetDevice
|
||||
? `正在切换到 ${targetDevice.name}`
|
||||
: '正在切换心率带设备',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2174,9 +2366,21 @@ Page({
|
||||
cancelText: '取消',
|
||||
success: (result) => {
|
||||
if (result.confirm && mapEngine) {
|
||||
clearResultExitRedirectTimer()
|
||||
clearResultExitCountdownTimer()
|
||||
this.syncBackendSessionFinish('cancelled')
|
||||
clearSessionRecoverySnapshot()
|
||||
clearSessionRecoveryPersistTimer()
|
||||
systemSettingsLockLifetimeActive = false
|
||||
mapEngine.handleForceExitGame()
|
||||
wx.showToast({
|
||||
title: '已退出当前对局',
|
||||
icon: 'none',
|
||||
duration: 1000,
|
||||
})
|
||||
setTimeout(() => {
|
||||
navigateAwayFromMapAfterCancel()
|
||||
}, 180)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -2312,24 +2516,11 @@ Page({
|
||||
handleResultSceneTap() {},
|
||||
|
||||
handleCloseResultScene() {
|
||||
this.setData({
|
||||
showResultScene: false,
|
||||
})
|
||||
this.redirectToResultPage()
|
||||
},
|
||||
|
||||
handleRestartFromResult() {
|
||||
if (!mapEngine) {
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
showResultScene: false,
|
||||
}, () => {
|
||||
if (mapEngine) {
|
||||
systemSettingsLockLifetimeActive = true
|
||||
this.applyRuntimeSystemSettings(true)
|
||||
mapEngine.handleStartGame()
|
||||
}
|
||||
})
|
||||
this.redirectToResultPage()
|
||||
},
|
||||
|
||||
handleOpenSystemSettingsPanel() {
|
||||
|
||||
@@ -324,7 +324,7 @@
|
||||
|
||||
<view class="result-scene-modal" wx:if="{{showResultScene}}" bindtap="handleCloseResultScene">
|
||||
<view class="result-scene-modal__dialog" catchtap="handleResultSceneTap">
|
||||
<view class="result-scene-modal__eyebrow">RESULT</view>
|
||||
<view class="result-scene-modal__eyebrow">FINISH</view>
|
||||
<view class="result-scene-modal__title">{{resultSceneTitle}}</view>
|
||||
<view class="result-scene-modal__subtitle">{{resultSceneSubtitle}}</view>
|
||||
|
||||
@@ -340,9 +340,10 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="result-scene-modal__countdown">{{resultSceneCountdownText}}</view>
|
||||
|
||||
<view class="result-scene-modal__actions">
|
||||
<view class="result-scene-modal__action result-scene-modal__action--secondary" bindtap="handleCloseResultScene">返回地图</view>
|
||||
<view class="result-scene-modal__action result-scene-modal__action--primary" bindtap="handleRestartFromResult">再来一局</view>
|
||||
<view class="result-scene-modal__action result-scene-modal__action--primary" bindtap="handleRestartFromResult">查看成绩</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -726,13 +727,31 @@
|
||||
<view class="debug-section__header-row">
|
||||
<view class="debug-section__header-main">
|
||||
<view class="debug-section__title">16. 心率设备</view>
|
||||
<view class="debug-section__desc">清除已记住的首选心率带设备,下次重新选择</view>
|
||||
<view class="debug-section__desc">局内正式入口,可快速更换、重连或断开当前心率带</view>
|
||||
</view>
|
||||
<view class="debug-section__lock {{lockHeartRateDevice ? 'debug-section__lock--active' : ''}}">
|
||||
<text class="debug-section__lock-text">{{lockHeartRateDevice ? '配置锁定' : '允许调整'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-panel__row">
|
||||
<text class="info-panel__label">当前状态</text>
|
||||
<text class="info-panel__value">{{heartRateStatusText}}{{heartRateSourceMode !== 'real' ? ' · 当前为模拟模式' : ''}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row info-panel__row--stack">
|
||||
<text class="info-panel__label">当前设备</text>
|
||||
<text class="info-panel__value">{{heartRateDeviceText}}</text>
|
||||
</view>
|
||||
<view class="info-panel__row" wx:if="{{heartRateSourceMode === 'real'}}">
|
||||
<text class="info-panel__label">扫描状态</text>
|
||||
<text class="info-panel__value">{{heartRateScanText}}</text>
|
||||
</view>
|
||||
<view class="summary" wx:if="{{heartRateSourceMode !== 'real'}}">当前为模拟心率模式,如需连接真实心率带,请先在调试面板切回“真实心率”。</view>
|
||||
<view class="control-row" wx:if="{{heartRateSourceMode === 'real'}}">
|
||||
<view class="control-chip control-chip--secondary {{lockHeartRateDevice ? 'control-chip--disabled' : ''}}" bindtap="handleOpenHeartRateDevicePicker">更换心率带</view>
|
||||
<view class="control-chip {{heartRateConnected ? 'control-chip--active' : 'control-chip--secondary'}} {{lockHeartRateDevice ? 'control-chip--disabled' : ''}}" bindtap="handleConnectHeartRate">{{heartRateConnected ? '重新扫描' : '连接心率带'}}</view>
|
||||
<view class="control-chip control-chip--secondary {{lockHeartRateDevice ? 'control-chip--disabled' : ''}}" bindtap="handleDisconnectHeartRate">断开心率带</view>
|
||||
</view>
|
||||
<view class="control-row">
|
||||
<view class="control-chip control-chip--secondary {{lockHeartRateDevice ? 'control-chip--disabled' : ''}}" bindtap="handleClearPreferredHeartRateDevice">清除首选设备</view>
|
||||
</view>
|
||||
@@ -897,25 +916,10 @@
|
||||
<text class="info-panel__label">HR Scan</text>
|
||||
<text class="info-panel__value">{{heartRateScanText}}</text>
|
||||
</view>
|
||||
<view class="debug-device-list" wx:if="{{heartRateSourceMode === 'real' && heartRateDiscoveredDevices.length}}">
|
||||
<view class="debug-device-card" wx:for="{{heartRateDiscoveredDevices}}" wx:key="deviceId">
|
||||
<view class="debug-device-card__main">
|
||||
<view class="debug-device-card__title-row">
|
||||
<text class="debug-device-card__name">{{item.name}}</text>
|
||||
<text class="debug-device-card__badge" wx:if="{{item.preferred}}">首选</text>
|
||||
</view>
|
||||
<text class="debug-device-card__meta">{{item.rssiText}}</text>
|
||||
</view>
|
||||
<view class="debug-device-card__action {{item.connected ? 'debug-device-card__action--active' : ''}}" data-device-id="{{item.deviceId}}" bindtap="handleConnectHeartRateDevice">{{item.connected ? '已连接' : '连接'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="control-row" wx:if="{{heartRateSourceMode === 'real'}}">
|
||||
<view class="control-chip {{heartRateConnected ? 'control-chip--active' : 'control-chip--secondary'}}" bindtap="handleConnectHeartRate">{{heartRateConnected ? '心率带已连接' : '连接心率带'}}</view>
|
||||
<view class="control-chip control-chip--secondary" bindtap="handleDisconnectHeartRate">断开心率带</view>
|
||||
</view>
|
||||
<view class="control-row" wx:if="{{heartRateSourceMode === 'real'}}">
|
||||
<view class="control-chip control-chip--secondary" bindtap="handleClearPreferredHeartRateDevice">清除首选</view>
|
||||
<view class="control-chip {{heartRateConnected ? 'control-chip--active' : 'control-chip--secondary'}}" bindtap="handleConnectHeartRate">{{heartRateConnected ? '重新扫描' : '连接心率带'}}</view>
|
||||
</view>
|
||||
<view class="summary" wx:if="{{heartRateSourceMode === 'real'}}">正式用户入口已放到系统设置;这里仅保留心率源切换与开发调试能力。</view>
|
||||
<view class="info-panel__row info-panel__row--stack" wx:if="{{heartRateSourceMode === 'mock'}}">
|
||||
<text class="info-panel__label">心率模拟状态</text>
|
||||
<text class="info-panel__value">{{mockHeartRateBridgeStatusText}}</text>
|
||||
@@ -1169,9 +1173,32 @@
|
||||
<text class="info-panel__value">{{networkFetchCount}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{showHeartRateDevicePicker}}" class="picker-mask" bindtap="handleCloseHeartRateDevicePicker"></view>
|
||||
<view wx:if="{{showHeartRateDevicePicker}}" class="picker-sheet">
|
||||
<view class="picker-sheet__header">
|
||||
<view class="picker-sheet__title">选择心率带设备</view>
|
||||
<button class="picker-sheet__close" bindtap="handleCloseHeartRateDevicePicker">关闭</button>
|
||||
</view>
|
||||
<view class="summary">扫描状态:{{heartRateScanText}}</view>
|
||||
<view wx:if="{{!heartRateDiscoveredDevices.length}}" class="summary">当前还没有发现设备,可先点“重新扫描”。</view>
|
||||
<view wx:if="{{heartRateDiscoveredDevices.length}}" class="device-list">
|
||||
<view wx:for="{{heartRateDiscoveredDevices}}" wx:key="deviceId" class="device-card">
|
||||
<view class="device-card__main">
|
||||
<view class="device-card__title-row">
|
||||
<text class="device-card__name">{{item.name}}</text>
|
||||
<text class="device-card__badge" wx:if="{{item.preferred}}">首选</text>
|
||||
<text class="device-card__badge device-card__badge--active" wx:if="{{item.connected}}">已连接</text>
|
||||
</view>
|
||||
<text class="device-card__meta">{{item.rssiText}}</text>
|
||||
</view>
|
||||
<button class="btn {{item.connected ? 'btn--ghost' : 'btn--secondary'}} device-card__action" data-device-id="{{item.deviceId}}" bindtap="handleConnectHeartRateDevice">{{item.connected ? '已连接' : '连接'}}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
@@ -1458,6 +1458,14 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.result-scene-modal__countdown {
|
||||
margin-top: 18rpx;
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.4;
|
||||
color: #6a826f;
|
||||
}
|
||||
|
||||
.result-scene-modal__actions {
|
||||
margin-top: 28rpx;
|
||||
display: flex;
|
||||
@@ -1781,6 +1789,143 @@
|
||||
color: #f7fbf2;
|
||||
}
|
||||
|
||||
.picker-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(10, 22, 38, 0.42);
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.picker-sheet {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 91;
|
||||
display: grid;
|
||||
gap: 16rpx;
|
||||
padding: 24rpx 24rpx 36rpx;
|
||||
border-top-left-radius: 28rpx;
|
||||
border-top-right-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
box-shadow: 0 -14rpx 36rpx rgba(22, 43, 71, 0.18);
|
||||
}
|
||||
|
||||
.picker-sheet__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.picker-sheet__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #17345a;
|
||||
}
|
||||
|
||||
.picker-sheet__close {
|
||||
margin: 0;
|
||||
min-height: 60rpx;
|
||||
padding: 0 18rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
background: #eef3f8;
|
||||
color: #455a72;
|
||||
}
|
||||
|
||||
.picker-sheet__close::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #30465f;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
display: grid;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
align-items: center;
|
||||
padding: 18rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #f6f9fc;
|
||||
}
|
||||
|
||||
.device-card__main {
|
||||
display: grid;
|
||||
gap: 8rpx;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-card__title-row {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.device-card__name {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #17345a;
|
||||
}
|
||||
|
||||
.device-card__badge {
|
||||
padding: 4rpx 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #e1ecfa;
|
||||
color: #35567d;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.device-card__badge--active {
|
||||
background: #dff3e8;
|
||||
color: #1f6a45;
|
||||
}
|
||||
|
||||
.device-card__meta {
|
||||
font-size: 22rpx;
|
||||
color: #5c7288;
|
||||
}
|
||||
|
||||
.device-card__action {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0;
|
||||
min-height: 76rpx;
|
||||
padding: 0 24rpx;
|
||||
line-height: 76rpx;
|
||||
border-radius: 18rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.btn::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
background: #dfeaf8;
|
||||
color: #173d73;
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background: #ffffff;
|
||||
color: #52657d;
|
||||
border: 2rpx solid #d8e2ec;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
|
||||
Reference in New Issue
Block a user