完善后端联调链路与模拟器多通道支持

This commit is contained in:
2026-04-01 18:48:59 +08:00
parent 94a1f0ba78
commit a70dc8d5d0
51 changed files with 4037 additions and 197 deletions

View File

@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "结果"
}

View File

@@ -0,0 +1,134 @@
import { loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
import { getMyResults, getSessionResult, type BackendSessionResultView } from '../../utils/backendApi'
type ResultPageData = {
sessionId: string
statusText: string
sessionTitleText: string
sessionSubtitleText: string
rows: Array<{ label: string; value: string }>
recentResults: BackendSessionResultView[]
}
function getAccessToken(): string | null {
const app = getApp<IAppOption>()
const tokens = app.globalData && app.globalData.backendAuthTokens
? app.globalData.backendAuthTokens
: loadBackendAuthTokens()
return tokens && tokens.accessToken ? tokens.accessToken : null
}
function formatValue(value: unknown): string {
if (value === null || value === undefined || value === '') {
return '--'
}
return String(value)
}
Page({
data: {
sessionId: '',
statusText: '准备加载结果',
sessionTitleText: '结果页',
sessionSubtitleText: '未加载',
rows: [],
recentResults: [],
} as ResultPageData,
onLoad(query: { sessionId?: string }) {
const sessionId = query && query.sessionId ? decodeURIComponent(query.sessionId) : ''
this.setData({ sessionId })
if (sessionId) {
this.loadSingleResult(sessionId)
return
}
this.loadRecentResults()
},
async loadSingleResult(sessionId: string) {
const accessToken = getAccessToken()
if (!accessToken) {
wx.redirectTo({ url: '/pages/login/login' })
return
}
this.setData({
statusText: '正在加载单局结果',
})
try {
const result = await getSessionResult({
baseUrl: loadBackendBaseUrl(),
accessToken,
sessionId,
})
this.setData({
statusText: '单局结果加载完成',
sessionTitleText: result.session.eventName || result.session.eventDisplayName || result.session.eventId || result.session.id || result.session.sessionId,
sessionSubtitleText: `${result.session.status || result.session.sessionStatus} / ${result.result.status}`,
rows: [
{ label: '最终得分', value: formatValue(result.result.finalScore) },
{ label: '最终用时(秒)', value: formatValue(result.result.finalDurationSec) },
{ label: '完成点数', value: formatValue(result.result.completedControls) },
{ label: '总点数', value: formatValue(result.result.totalControls) },
{ label: '累计里程(m)', value: formatValue(result.result.distanceMeters) },
{ label: '平均速度(km/h)', value: formatValue(result.result.averageSpeedKmh) },
{ label: '最大心率', value: formatValue(result.result.maxHeartRateBpm) },
],
})
} catch (error) {
const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
this.setData({
statusText: `结果加载失败:${message}`,
})
}
},
async loadRecentResults() {
const accessToken = getAccessToken()
if (!accessToken) {
wx.redirectTo({ url: '/pages/login/login' })
return
}
this.setData({
statusText: '正在加载最近结果',
})
try {
const results = await getMyResults({
baseUrl: loadBackendBaseUrl(),
accessToken,
limit: 20,
})
this.setData({
statusText: '最近结果加载完成',
sessionSubtitleText: '最近结果列表',
recentResults: results,
})
} catch (error) {
const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
this.setData({
statusText: `结果加载失败:${message}`,
})
}
},
handleOpenResult(event: WechatMiniprogram.TouchEvent) {
const sessionId = event.currentTarget.dataset.sessionId as string | undefined
if (!sessionId) {
return
}
wx.redirectTo({
url: `/pages/result/result?sessionId=${encodeURIComponent(sessionId)}`,
})
},
handleBackToList() {
this.setData({
sessionId: '',
rows: [],
})
this.loadRecentResults()
},
})

View File

@@ -0,0 +1,33 @@
<scroll-view class="page" scroll-y>
<view class="shell">
<view class="hero">
<view class="hero__eyebrow">Result</view>
<view class="hero__title">{{sessionTitleText}}</view>
<view class="hero__desc">{{sessionSubtitleText}}</view>
</view>
<view class="panel">
<view class="panel__title">当前状态</view>
<view class="summary">{{statusText}}</view>
<button wx:if="{{sessionId}}" class="btn btn--ghost" bindtap="handleBackToList">返回最近结果</button>
</view>
<view wx:if="{{rows.length}}" class="panel">
<view class="panel__title">单局摘要</view>
<view wx:for="{{rows}}" wx:key="label" class="row">
<view class="row__label">{{item.label}}</view>
<view class="row__value">{{item.value}}</view>
</view>
</view>
<view wx:if="{{!sessionId}}" class="panel">
<view class="panel__title">最近结果</view>
<view wx:if="{{!recentResults.length}}" class="summary">当前没有结果记录</view>
<view wx:for="{{recentResults}}" wx:key="session.id" class="result-card" bindtap="handleOpenResult" data-session-id="{{item.session.id}}">
<view class="result-card__title">{{item.session.eventName || item.session.id}}</view>
<view class="result-card__meta">{{item.result.status}} / {{item.session.status}}</view>
<view class="result-card__meta">得分 {{item.result.finalScore || '--'}} / 用时 {{item.result.finalDurationSec || '--'}}s</view>
</view>
</view>
</view>
</scroll-view>

View File

@@ -0,0 +1,114 @@
page {
min-height: 100vh;
background: linear-gradient(180deg, #eff4fb 0%, #e8eff7 100%);
}
.page {
min-height: 100vh;
}
.shell {
display: grid;
gap: 24rpx;
padding: 28rpx 24rpx 40rpx;
}
.hero,
.panel {
display: grid;
gap: 16rpx;
padding: 24rpx;
border-radius: 24rpx;
}
.hero {
background: linear-gradient(135deg, #163a66 0%, #1f5da1 100%);
color: #ffffff;
}
.hero__eyebrow {
font-size: 22rpx;
letter-spacing: 0.16em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.72);
}
.hero__title {
font-size: 40rpx;
font-weight: 700;
}
.hero__desc {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.84);
}
.panel {
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 14rpx 32rpx rgba(40, 63, 95, 0.08);
}
.panel__title {
font-size: 30rpx;
font-weight: 700;
color: #17345a;
}
.summary,
.row__label,
.row__value,
.result-card__meta {
font-size: 24rpx;
line-height: 1.6;
color: #30465f;
}
.row {
display: flex;
justify-content: space-between;
gap: 16rpx;
padding: 10rpx 0;
border-bottom: 2rpx solid #edf2f7;
}
.row:last-child {
border-bottom: 0;
}
.row__value {
font-weight: 700;
color: #17345a;
}
.result-card {
display: grid;
gap: 8rpx;
padding: 18rpx;
border-radius: 18rpx;
background: #f6f9fc;
}
.result-card__title {
font-size: 28rpx;
font-weight: 700;
color: #17345a;
}
.btn {
margin: 0;
min-height: 76rpx;
padding: 0 24rpx;
line-height: 76rpx;
border-radius: 18rpx;
font-size: 26rpx;
}
.btn::after {
border: 0;
}
.btn--ghost {
background: #ffffff;
color: #52657d;
border: 2rpx solid #d8e2ec;
}