result.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
  2. import { getSessionResult } from '../../utils/backendApi'
  3. import type { MapEngineResultSnapshot } from '../../engine/map/mapEngine'
  4. import type { GameLaunchEnvelope } from '../../utils/gameLaunch'
  5. type ResultPageData = {
  6. sessionId: string
  7. statusText: string
  8. sessionTitleText: string
  9. sessionSubtitleText: string
  10. rows: Array<{ label: string; value: string }>
  11. }
  12. function getAccessToken(): string | null {
  13. const app = getApp<IAppOption>()
  14. const tokens = app.globalData && app.globalData.backendAuthTokens
  15. ? app.globalData.backendAuthTokens
  16. : loadBackendAuthTokens()
  17. return tokens && tokens.accessToken ? tokens.accessToken : null
  18. }
  19. function formatValue(value: unknown): string {
  20. if (value === null || value === undefined || value === '') {
  21. return '--'
  22. }
  23. return String(value)
  24. }
  25. function formatRouteSummary(input: {
  26. variantName?: string | null
  27. routeCode?: string | null
  28. }): string {
  29. if (input.variantName && input.routeCode) {
  30. return `${input.variantName} / ${input.routeCode}`
  31. }
  32. if (input.variantName) {
  33. return input.variantName
  34. }
  35. if (input.routeCode) {
  36. return input.routeCode
  37. }
  38. return '默认赛道'
  39. }
  40. function formatRuntimeValue(...candidates: Array<string | null | undefined>): string {
  41. for (let index = 0; index < candidates.length; index += 1) {
  42. const value = candidates[index]
  43. if (typeof value === 'string' && value.trim().length > 0) {
  44. return value.trim()
  45. }
  46. }
  47. return '--'
  48. }
  49. function appendRuntimeRows(
  50. rows: Array<{ label: string; value: string }>,
  51. options: {
  52. runtime?: {
  53. runtimeBindingId?: string | null
  54. placeId?: string | null
  55. placeName?: string | null
  56. mapId?: string | null
  57. mapName?: string | null
  58. tileReleaseId?: string | null
  59. courseSetId?: string | null
  60. courseVariantId?: string | null
  61. routeCode?: string | null
  62. } | null
  63. variantName?: string | null
  64. routeCode?: string | null
  65. },
  66. ) {
  67. if (!options.runtime) {
  68. return rows
  69. }
  70. return rows.concat([
  71. { label: '运行绑定', value: formatRuntimeValue(options.runtime.runtimeBindingId) },
  72. { label: '地点', value: formatRuntimeValue(options.runtime.placeName, options.runtime.placeId) },
  73. { label: '地图', value: formatRuntimeValue(options.runtime.mapName, options.runtime.mapId) },
  74. { label: '赛道集', value: formatRuntimeValue(options.runtime.courseSetId) },
  75. { label: '赛道版本', value: formatRuntimeValue(options.runtime.courseVariantId, options.variantName) },
  76. { label: 'RouteCode', value: formatRuntimeValue(options.runtime.routeCode, options.routeCode) },
  77. { label: '瓦片版本', value: formatRuntimeValue(options.runtime.tileReleaseId) },
  78. ])
  79. }
  80. function loadPendingResultLaunchEnvelope(): GameLaunchEnvelope | null {
  81. const app = getApp<IAppOption>()
  82. return app.globalData && app.globalData.pendingResultLaunchEnvelope
  83. ? app.globalData.pendingResultLaunchEnvelope
  84. : null
  85. }
  86. Page({
  87. data: {
  88. sessionId: '',
  89. statusText: '准备加载结果',
  90. sessionTitleText: '结果页',
  91. sessionSubtitleText: '未加载',
  92. rows: [],
  93. } as ResultPageData,
  94. onLoad(query: { sessionId?: string }) {
  95. const sessionId = query && query.sessionId ? decodeURIComponent(query.sessionId) : ''
  96. this.setData({ sessionId })
  97. this.applyPendingResultSnapshot()
  98. if (sessionId) {
  99. this.loadSingleResult(sessionId)
  100. return
  101. }
  102. this.setData({
  103. statusText: '未提供单局会话,已跳转历史结果',
  104. })
  105. wx.redirectTo({
  106. url: '/pages/results/results',
  107. })
  108. },
  109. applyPendingResultSnapshot() {
  110. const app = getApp<IAppOption>()
  111. const snapshot = app.globalData && app.globalData.pendingResultSnapshot
  112. ? app.globalData.pendingResultSnapshot as MapEngineResultSnapshot
  113. : null
  114. if (!snapshot) {
  115. return
  116. }
  117. const pendingLaunchEnvelope = loadPendingResultLaunchEnvelope()
  118. this.setData({
  119. statusText: '正在加载结果',
  120. sessionTitleText: snapshot.title,
  121. sessionSubtitleText: snapshot.subtitle,
  122. rows: appendRuntimeRows([
  123. { label: snapshot.heroLabel, value: snapshot.heroValue },
  124. ...snapshot.rows.map((row) => ({
  125. label: row.label,
  126. value: row.value,
  127. })),
  128. ], {
  129. runtime: pendingLaunchEnvelope && pendingLaunchEnvelope.runtime ? pendingLaunchEnvelope.runtime : null,
  130. variantName: pendingLaunchEnvelope && pendingLaunchEnvelope.variant ? pendingLaunchEnvelope.variant.variantName : null,
  131. routeCode: pendingLaunchEnvelope && pendingLaunchEnvelope.variant ? pendingLaunchEnvelope.variant.routeCode : null,
  132. }),
  133. })
  134. if (app.globalData) {
  135. app.globalData.pendingResultSnapshot = null
  136. }
  137. },
  138. async loadSingleResult(sessionId: string) {
  139. const accessToken = getAccessToken()
  140. if (!accessToken) {
  141. wx.redirectTo({ url: '/pages/login/login' })
  142. return
  143. }
  144. this.setData({
  145. statusText: '正在加载单局结果',
  146. })
  147. try {
  148. const result = await getSessionResult({
  149. baseUrl: loadBackendBaseUrl(),
  150. accessToken,
  151. sessionId,
  152. })
  153. const pendingLaunchEnvelope = loadPendingResultLaunchEnvelope()
  154. this.setData({
  155. statusText: '单局结果加载完成',
  156. sessionTitleText: result.session.eventName || result.session.eventDisplayName || result.session.eventId || result.session.id || result.session.sessionId,
  157. sessionSubtitleText: `${result.session.status || result.session.sessionStatus} / ${result.result.status} / ${formatRouteSummary(result.session)}`,
  158. rows: appendRuntimeRows([
  159. { label: '赛道版本', value: formatRouteSummary(result.session) },
  160. { label: '最终得分', value: formatValue(result.result.finalScore) },
  161. { label: '最终用时(秒)', value: formatValue(result.result.finalDurationSec) },
  162. { label: '完成点数', value: formatValue(result.result.completedControls) },
  163. { label: '总点数', value: formatValue(result.result.totalControls) },
  164. { label: '累计里程(m)', value: formatValue(result.result.distanceMeters) },
  165. { label: '平均速度(km/h)', value: formatValue(result.result.averageSpeedKmh) },
  166. { label: '最大心率', value: formatValue(result.result.maxHeartRateBpm) },
  167. ], {
  168. runtime: result.session.runtime || (pendingLaunchEnvelope && pendingLaunchEnvelope.runtime ? pendingLaunchEnvelope.runtime : null),
  169. variantName: result.session.variantName || (pendingLaunchEnvelope && pendingLaunchEnvelope.variant ? pendingLaunchEnvelope.variant.variantName : null),
  170. routeCode: result.session.routeCode || (pendingLaunchEnvelope && pendingLaunchEnvelope.variant ? pendingLaunchEnvelope.variant.routeCode : null),
  171. }),
  172. })
  173. const app = getApp<IAppOption>()
  174. if (app.globalData) {
  175. app.globalData.pendingResultLaunchEnvelope = null
  176. }
  177. } catch (error) {
  178. const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
  179. this.setData({
  180. statusText: `结果加载失败:${message}`,
  181. })
  182. }
  183. },
  184. handleBackToList() {
  185. wx.redirectTo({
  186. url: '/pages/results/results',
  187. })
  188. },
  189. })