home.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { clearBackendAuthTokens, loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
  2. import { finishSession, getEntryHome, type BackendCardResult, type BackendEntryHomeResult } from '../../utils/backendApi'
  3. import { reportBackendClientLog } from '../../utils/backendClientLogs'
  4. import { setGlobalMockDebugBridgeEnabled } from '../../utils/globalMockDebugBridge'
  5. import { clearSessionRecoverySnapshot, loadSessionRecoverySnapshot } from '../../game/core/sessionRecovery'
  6. import { getBackendSessionContextFromLaunchEnvelope, prepareMapPageUrlForRecovery } from '../../utils/gameLaunch'
  7. const DEFAULT_CHANNEL_CODE = 'mini-demo'
  8. const DEFAULT_CHANNEL_TYPE = 'wechat_mini'
  9. type HomePageData = {
  10. loading: boolean
  11. statusText: string
  12. userNameText: string
  13. tenantText: string
  14. channelText: string
  15. ongoingSessionText: string
  16. recentSessionText: string
  17. ongoingRuntimeText: string
  18. recentRuntimeText: string
  19. ongoingActionHintText: string
  20. showOngoingPanel: boolean
  21. canRecoverOngoing: boolean
  22. canAbandonOngoing: boolean
  23. cards: BackendCardResult[]
  24. }
  25. function formatSessionSummary(session?: BackendEntryHomeResult['ongoingSession'] | null): string {
  26. if (!session) {
  27. return '无'
  28. }
  29. const title = session.eventName || session.eventDisplayName || session.eventId || session.id || session.sessionId
  30. const status = session.status || session.sessionStatus || '--'
  31. const route = session.routeCode || session.variantName || '默认赛道'
  32. return `${title} / ${status} / ${route}`
  33. }
  34. function formatRuntimeSummary(session?: BackendEntryHomeResult['ongoingSession'] | null): string {
  35. if (!session || !session.runtime) {
  36. return '运行对象 --'
  37. }
  38. const runtime = session.runtime
  39. const placeText = runtime.placeName || runtime.placeId || '--'
  40. const mapText = runtime.mapName || runtime.mapId || '--'
  41. const variantText = runtime.courseVariantId || session.variantName || session.variantId || '--'
  42. return `地点 ${placeText} / 地图 ${mapText} / 赛道 ${variantText}`
  43. }
  44. function requireAuthToken(): string | null {
  45. const app = getApp<IAppOption>()
  46. const tokens = app.globalData && app.globalData.backendAuthTokens
  47. ? app.globalData.backendAuthTokens
  48. : loadBackendAuthTokens()
  49. return tokens && tokens.accessToken ? tokens.accessToken : null
  50. }
  51. function getRecoverySnapshotSessionId(): string {
  52. const snapshot = loadSessionRecoverySnapshot()
  53. if (!snapshot) {
  54. return ''
  55. }
  56. const context = getBackendSessionContextFromLaunchEnvelope(snapshot.launchEnvelope)
  57. return context && context.sessionId ? context.sessionId : ''
  58. }
  59. Page({
  60. data: {
  61. loading: false,
  62. statusText: '准备加载首页',
  63. userNameText: '--',
  64. tenantText: '--',
  65. channelText: '--',
  66. ongoingSessionText: '无',
  67. recentSessionText: '无',
  68. ongoingRuntimeText: '运行对象 --',
  69. recentRuntimeText: '运行对象 --',
  70. ongoingActionHintText: '当前没有可恢复的进行中对局',
  71. showOngoingPanel: false,
  72. canRecoverOngoing: false,
  73. canAbandonOngoing: false,
  74. cards: [],
  75. } as HomePageData,
  76. onLoad() {
  77. this.loadEntryHome()
  78. },
  79. onShow() {
  80. this.loadEntryHome()
  81. },
  82. async loadEntryHome() {
  83. const accessToken = requireAuthToken()
  84. if (!accessToken) {
  85. wx.redirectTo({ url: '/pages/login/login' })
  86. return
  87. }
  88. this.setData({
  89. loading: true,
  90. statusText: '正在加载首页聚合',
  91. })
  92. try {
  93. const result = await getEntryHome({
  94. baseUrl: loadBackendBaseUrl(),
  95. accessToken,
  96. channelCode: DEFAULT_CHANNEL_CODE,
  97. channelType: DEFAULT_CHANNEL_TYPE,
  98. })
  99. this.applyEntryHomeResult(result)
  100. } catch (error) {
  101. const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
  102. this.setData({
  103. loading: false,
  104. statusText: `首页加载失败:${message}`,
  105. })
  106. }
  107. },
  108. applyEntryHomeResult(result: BackendEntryHomeResult) {
  109. const ongoingSession = result.ongoingSession || null
  110. const recoverySnapshotSessionId = getRecoverySnapshotSessionId()
  111. const canRecoverOngoing = !!ongoingSession && !!recoverySnapshotSessionId
  112. && ongoingSession.id === recoverySnapshotSessionId
  113. const canAbandonOngoing = canRecoverOngoing
  114. const ongoingActionHintText = !ongoingSession
  115. ? '当前没有可恢复的进行中对局'
  116. : canRecoverOngoing
  117. ? '检测到本机仍保留这局的恢复记录,你可以继续恢复或主动放弃。'
  118. : '检测到后端存在进行中对局,但本机当前没有匹配的恢复快照。'
  119. reportBackendClientLog({
  120. level: 'info',
  121. category: 'entry-home',
  122. message: 'entry home loaded',
  123. details: {
  124. ongoingSessionId: result.ongoingSession && result.ongoingSession.id ? result.ongoingSession.id : '',
  125. ongoingEventId: result.ongoingSession && result.ongoingSession.eventId ? result.ongoingSession.eventId : '',
  126. recentSessionId: result.recentSession && result.recentSession.id ? result.recentSession.id : '',
  127. recentEventId: result.recentSession && result.recentSession.eventId ? result.recentSession.eventId : '',
  128. cardEventIds: (result.cards || []).map((item) => (item.event && item.event.id ? item.event.id : '')),
  129. hasOngoingSession: !!ongoingSession,
  130. recoverySnapshotSessionId,
  131. canRecoverOngoing,
  132. },
  133. })
  134. this.setData({
  135. loading: false,
  136. statusText: '首页加载完成',
  137. userNameText: result.user.nickname || result.user.publicId || result.user.id,
  138. tenantText: `${result.tenant.name} (${result.tenant.code})`,
  139. channelText: `${result.channel.displayName} / ${result.channel.code}`,
  140. ongoingSessionText: formatSessionSummary(result.ongoingSession),
  141. recentSessionText: formatSessionSummary(result.recentSession),
  142. ongoingRuntimeText: formatRuntimeSummary(result.ongoingSession),
  143. recentRuntimeText: formatRuntimeSummary(result.recentSession),
  144. ongoingActionHintText,
  145. showOngoingPanel: !!ongoingSession,
  146. canRecoverOngoing,
  147. canAbandonOngoing,
  148. cards: result.cards || [],
  149. })
  150. },
  151. handleRefresh() {
  152. this.loadEntryHome()
  153. },
  154. handleOpenCard(event: WechatMiniprogram.TouchEvent) {
  155. const eventId = event.currentTarget.dataset.eventId as string | undefined
  156. if (!eventId) {
  157. wx.showToast({
  158. title: '该卡片暂无活动入口',
  159. icon: 'none',
  160. })
  161. return
  162. }
  163. wx.navigateTo({
  164. url: `/pages/event/event?eventId=${encodeURIComponent(eventId)}`,
  165. })
  166. },
  167. handleOpenRecentResult() {
  168. wx.navigateTo({
  169. url: '/pages/results/results',
  170. })
  171. },
  172. handleOpenEventList() {
  173. wx.navigateTo({
  174. url: '/pages/events/events',
  175. })
  176. },
  177. handleOpenExperienceMaps() {
  178. wx.navigateTo({
  179. url: '/pages/experience-maps/experience-maps',
  180. })
  181. },
  182. handleResumeOngoing() {
  183. const snapshot = loadSessionRecoverySnapshot()
  184. if (!snapshot) {
  185. wx.showToast({
  186. title: '本机未找到恢复快照',
  187. icon: 'none',
  188. })
  189. this.loadEntryHome()
  190. return
  191. }
  192. wx.navigateTo({
  193. url: prepareMapPageUrlForRecovery(snapshot.launchEnvelope),
  194. })
  195. },
  196. handleAbandonOngoing() {
  197. const snapshot = loadSessionRecoverySnapshot()
  198. if (!snapshot) {
  199. wx.showToast({
  200. title: '本机未找到恢复快照',
  201. icon: 'none',
  202. })
  203. this.loadEntryHome()
  204. return
  205. }
  206. const sessionContext = getBackendSessionContextFromLaunchEnvelope(snapshot.launchEnvelope)
  207. if (!sessionContext) {
  208. clearSessionRecoverySnapshot()
  209. wx.showToast({
  210. title: '已清理本机恢复记录',
  211. icon: 'none',
  212. })
  213. this.loadEntryHome()
  214. return
  215. }
  216. wx.showModal({
  217. title: '放弃进行中的游戏',
  218. content: '放弃后,这局游戏会记为已取消,且不会再出现在“进行中”。',
  219. confirmText: '确认放弃',
  220. cancelText: '先保留',
  221. success: (result) => {
  222. if (!result.confirm) {
  223. return
  224. }
  225. finishSession({
  226. baseUrl: loadBackendBaseUrl(),
  227. sessionId: sessionContext.sessionId,
  228. sessionToken: sessionContext.sessionToken,
  229. status: 'cancelled',
  230. summary: {},
  231. }).catch(() => {
  232. wx.showToast({
  233. title: '取消上报失败,请稍后重试',
  234. icon: 'none',
  235. })
  236. }).finally(() => {
  237. clearSessionRecoverySnapshot()
  238. this.loadEntryHome()
  239. })
  240. },
  241. })
  242. },
  243. handleLogout() {
  244. clearBackendAuthTokens()
  245. setGlobalMockDebugBridgeEnabled(false)
  246. const app = getApp<IAppOption>()
  247. if (app.globalData) {
  248. app.globalData.backendAuthTokens = null
  249. }
  250. wx.redirectTo({
  251. url: '/pages/login/login',
  252. })
  253. },
  254. })