推进活动列表第一刀与联调回归
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
"pages/index/index",
|
||||
"pages/login/login",
|
||||
"pages/home/home",
|
||||
"pages/events/events",
|
||||
"pages/event/event",
|
||||
"pages/event-prepare/event-prepare",
|
||||
"pages/result/result",
|
||||
|
||||
@@ -13,6 +13,7 @@ const DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY = 'cmr.debug.autoConnectMockSources.v1
|
||||
type EventPreparePageData = {
|
||||
eventId: string
|
||||
loading: boolean
|
||||
canLaunch: boolean
|
||||
titleText: string
|
||||
summaryText: string
|
||||
releaseText: string
|
||||
@@ -29,6 +30,8 @@ type EventPreparePageData = {
|
||||
runtimeRouteCodeText: string
|
||||
selectedVariantId: string
|
||||
selectedVariantText: string
|
||||
showVariantSelector: boolean
|
||||
variantSelectorEmptyText: string
|
||||
selectableVariants: Array<{
|
||||
id: string
|
||||
name: string
|
||||
@@ -54,6 +57,27 @@ type EventPreparePageData = {
|
||||
mockSourceStatusText: string
|
||||
}
|
||||
|
||||
function detectMultiVariantContext(result: BackendEventPlayResult): boolean {
|
||||
const assignmentMode = result.play.assignmentMode
|
||||
if (assignmentMode === 'manual' || assignmentMode === 'random' || assignmentMode === 'server-assigned') {
|
||||
return true
|
||||
}
|
||||
|
||||
const variants = result.play.courseVariants || []
|
||||
if (variants.length > 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
const haystacks = [
|
||||
result.event.displayName,
|
||||
result.event.summary,
|
||||
result.release ? result.release.configLabel : '',
|
||||
result.resolvedRelease ? result.resolvedRelease.configLabel : '',
|
||||
]
|
||||
|
||||
return haystacks.some((item) => typeof item === 'string' && item.indexOf('多赛道') >= 0)
|
||||
}
|
||||
|
||||
function formatAssignmentMode(mode?: string | null): string {
|
||||
if (mode === 'manual') {
|
||||
return '手动选择'
|
||||
@@ -77,6 +101,7 @@ function formatVariantSummary(result: BackendEventPlayResult): string {
|
||||
const title = item.routeCode || item.name
|
||||
return item.selectable === false ? `${title}(固定)` : title
|
||||
}).join(' / ')
|
||||
const selectableCount = variants.filter((item) => item.selectable !== false).length
|
||||
|
||||
if (result.play.assignmentMode === 'manual') {
|
||||
return `当前活动支持 ${variants.length} 条赛道。本阶段前端先展示赛道信息,最终绑定以后端 launch 返回为准:${preview}`
|
||||
@@ -90,13 +115,17 @@ function formatVariantSummary(result: BackendEventPlayResult): string {
|
||||
return `当前活动赛道由后台预先指定:${preview}`
|
||||
}
|
||||
|
||||
if (selectableCount > 1) {
|
||||
return `当前活动支持 ${variants.length} 条赛道。后端当前未明确返回赛道模式,前端先按手动选择兼容显示:${preview}`
|
||||
}
|
||||
|
||||
return preview
|
||||
}
|
||||
|
||||
function formatPresentationSummary(result: BackendEventPlayResult): string {
|
||||
const currentPresentation = result.currentPresentation
|
||||
if (!currentPresentation) {
|
||||
return '当前未声明展示版本'
|
||||
return '当前发布 release 未绑定展示版本,或当前尚未发布'
|
||||
}
|
||||
|
||||
return `${currentPresentation.presentationId || '--'} / ${currentPresentation.templateKey || '--'} / ${currentPresentation.version || '--'}`
|
||||
@@ -105,7 +134,7 @@ function formatPresentationSummary(result: BackendEventPlayResult): string {
|
||||
function formatContentBundleSummary(result: BackendEventPlayResult): string {
|
||||
const currentContentBundle = result.currentContentBundle
|
||||
if (!currentContentBundle) {
|
||||
return '当前未声明内容包版本'
|
||||
return '当前发布 release 未绑定内容包版本,或当前尚未发布'
|
||||
}
|
||||
|
||||
return `${currentContentBundle.bundleId || '--'} / ${currentContentBundle.bundleType || '--'} / ${currentContentBundle.version || '--'}`
|
||||
@@ -115,12 +144,13 @@ function resolveSelectedVariantId(
|
||||
currentVariantId: string,
|
||||
assignmentMode?: string | null,
|
||||
variants?: BackendCourseVariantSummary[] | null,
|
||||
forceVisible?: boolean,
|
||||
): string {
|
||||
if (assignmentMode !== 'manual' || !variants || !variants.length) {
|
||||
if (!shouldShowVariantSelector(assignmentMode, variants, forceVisible)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const selectable = variants.filter((item) => item.selectable !== false)
|
||||
const selectable = (variants || []).filter((item) => item.selectable !== false)
|
||||
if (!selectable.length) {
|
||||
return ''
|
||||
}
|
||||
@@ -137,8 +167,9 @@ function buildSelectableVariants(
|
||||
selectedVariantId: string,
|
||||
assignmentMode?: string | null,
|
||||
variants?: BackendCourseVariantSummary[] | null,
|
||||
forceVisible?: boolean,
|
||||
) {
|
||||
if (assignmentMode !== 'manual' || !variants || !variants.length) {
|
||||
if (!shouldShowVariantSelector(assignmentMode, variants, forceVisible) || !variants || !variants.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -153,6 +184,32 @@ function buildSelectableVariants(
|
||||
}))
|
||||
}
|
||||
|
||||
function shouldShowVariantSelector(
|
||||
assignmentMode?: string | null,
|
||||
variants?: BackendCourseVariantSummary[] | null,
|
||||
forceVisible?: boolean,
|
||||
): boolean {
|
||||
if (forceVisible) {
|
||||
return true
|
||||
}
|
||||
|
||||
const normalizedVariants = variants || []
|
||||
|
||||
if (!normalizedVariants.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (assignmentMode === 'manual') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (assignmentMode === 'random' || assignmentMode === 'server-assigned') {
|
||||
return false
|
||||
}
|
||||
|
||||
return normalizedVariants.filter((item) => item.selectable !== false).length > 1
|
||||
}
|
||||
|
||||
let prepareHeartRateController: HeartRateController | null = null
|
||||
|
||||
function getAccessToken(): string | null {
|
||||
@@ -202,6 +259,7 @@ Page({
|
||||
data: {
|
||||
eventId: '',
|
||||
loading: false,
|
||||
canLaunch: false,
|
||||
titleText: '开始前准备',
|
||||
summaryText: '未加载',
|
||||
releaseText: '--',
|
||||
@@ -218,6 +276,8 @@ Page({
|
||||
runtimeRouteCodeText: '待 launch 确认',
|
||||
selectedVariantId: '',
|
||||
selectedVariantText: '当前无需手动指定赛道',
|
||||
showVariantSelector: false,
|
||||
variantSelectorEmptyText: '当前无需手动指定赛道',
|
||||
selectableVariants: [],
|
||||
locationStatusText: '待进入地图后校验定位权限与实时精度',
|
||||
heartRateStatusText: '局前心率带连接入口待接入,本轮先保留骨架',
|
||||
@@ -286,17 +346,25 @@ Page({
|
||||
},
|
||||
|
||||
applyEventPlay(result: BackendEventPlayResult) {
|
||||
const multiVariantContext = detectMultiVariantContext(result)
|
||||
const selectedVariantId = resolveSelectedVariantId(
|
||||
this.data.selectedVariantId,
|
||||
result.play.assignmentMode,
|
||||
result.play.courseVariants,
|
||||
multiVariantContext,
|
||||
)
|
||||
const assignmentMode = result.play.assignmentMode ? result.play.assignmentMode : null
|
||||
const showVariantSelector = shouldShowVariantSelector(
|
||||
result.play.assignmentMode,
|
||||
result.play.courseVariants,
|
||||
multiVariantContext,
|
||||
)
|
||||
const logVariantId = assignmentMode === 'manual' && selectedVariantId ? selectedVariantId : null
|
||||
const selectableVariants = buildSelectableVariants(
|
||||
selectedVariantId,
|
||||
result.play.assignmentMode,
|
||||
result.play.courseVariants,
|
||||
multiVariantContext,
|
||||
)
|
||||
const selectedVariant = selectableVariants.find((item) => item.id === selectedVariantId) || null
|
||||
reportBackendClientLog({
|
||||
@@ -315,10 +383,20 @@ Page({
|
||||
resultEventId: result.event.id || '',
|
||||
selectedVariantId: logVariantId,
|
||||
assignmentMode,
|
||||
variantCount: result.play.courseVariants ? result.play.courseVariants.length : 0,
|
||||
selectableVariantCount: result.play.courseVariants
|
||||
? result.play.courseVariants.filter((item) => item.selectable !== false).length
|
||||
: 0,
|
||||
showVariantSelector,
|
||||
multiVariantContext,
|
||||
},
|
||||
})
|
||||
const variantSelectorEmptyText = multiVariantContext
|
||||
? '当前活动按多赛道处理,但后端暂未返回可选赛道,请稍后刷新或联系后台。'
|
||||
: '当前无需手动指定赛道'
|
||||
this.setData({
|
||||
loading: false,
|
||||
canLaunch: result.play.canLaunch,
|
||||
titleText: `${result.event.displayName} / 开始前准备`,
|
||||
summaryText: result.event.summary || '暂无活动简介',
|
||||
releaseText: result.resolvedRelease
|
||||
@@ -327,7 +405,9 @@ Page({
|
||||
actionText: formatBackendPlayActionText(result.play.primaryAction, result.play.reason),
|
||||
statusText: formatBackendPlayStatusText(result.play.canLaunch, result.play.primaryAction, result.play.reason),
|
||||
assignmentMode: result.play.assignmentMode || '',
|
||||
variantModeText: formatAssignmentMode(result.play.assignmentMode),
|
||||
variantModeText: result.play.assignmentMode
|
||||
? formatAssignmentMode(result.play.assignmentMode)
|
||||
: (showVariantSelector ? '手动选择' : '默认单赛道'),
|
||||
variantSummaryText: formatVariantSummary(result),
|
||||
presentationText: formatPresentationSummary(result),
|
||||
contentBundleText: formatContentBundleSummary(result),
|
||||
@@ -346,7 +426,9 @@ Page({
|
||||
selectedVariantId,
|
||||
selectedVariantText: selectedVariant
|
||||
? `${selectedVariant.name} / ${selectedVariant.routeCodeText}`
|
||||
: '当前无需手动指定赛道',
|
||||
: variantSelectorEmptyText,
|
||||
showVariantSelector,
|
||||
variantSelectorEmptyText,
|
||||
selectableVariants,
|
||||
})
|
||||
},
|
||||
@@ -591,6 +673,17 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.data.canLaunch) {
|
||||
this.setData({
|
||||
statusText: '当前发布状态不可进入地图',
|
||||
})
|
||||
wx.showToast({
|
||||
title: '当前发布状态不可进入地图',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.data.locationPermissionGranted) {
|
||||
this.setData({
|
||||
statusText: '进入地图前请先完成定位授权',
|
||||
@@ -608,7 +701,7 @@ Page({
|
||||
|
||||
try {
|
||||
const assignmentMode = this.data.assignmentMode ? this.data.assignmentMode : null
|
||||
const selectedVariantId = assignmentMode === 'manual' && this.data.selectedVariantId
|
||||
const selectedVariantId = this.data.showVariantSelector && this.data.selectedVariantId
|
||||
? this.data.selectedVariantId
|
||||
: null
|
||||
reportBackendClientLog({
|
||||
@@ -641,7 +734,7 @@ Page({
|
||||
baseUrl: loadBackendBaseUrl(),
|
||||
eventId: this.data.eventId,
|
||||
accessToken,
|
||||
variantId: this.data.assignmentMode === 'manual' ? this.data.selectedVariantId : undefined,
|
||||
variantId: this.data.showVariantSelector ? this.data.selectedVariantId : undefined,
|
||||
clientType: 'wechat',
|
||||
deviceKey: 'mini-dev-device-001',
|
||||
})
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
|
||||
<view class="panel">
|
||||
<view class="panel__title">活动运营摘要</view>
|
||||
<view class="summary">当前阶段先展示活动运营对象摘要,不展开复杂 schema。</view>
|
||||
<view class="summary">当前阶段先展示当前发布 release 绑定的活动运营对象摘要,不展开复杂 schema。</view>
|
||||
<view class="row">
|
||||
<view class="row__label">展示版本</view>
|
||||
<view class="row__label">当前发布展示版本</view>
|
||||
<view class="row__value">{{presentationText}}</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view class="row__label">内容包版本</view>
|
||||
<view class="row__label">当前发布内容包版本</view>
|
||||
<view class="row__value">{{contentBundleText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -50,10 +50,11 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel" wx:if="{{assignmentMode === 'manual' && selectableVariants.length}}">
|
||||
<view class="panel" wx:if="{{showVariantSelector}}">
|
||||
<view class="panel__title">赛道选择</view>
|
||||
<view class="summary">当前活动要求手动指定赛道。这里的选择会随 launch 一起带给后端,最终绑定以后端返回为准。</view>
|
||||
<view class="variant-list">
|
||||
<view wx:if="{{!selectableVariants.length}}" class="summary">{{variantSelectorEmptyText}}</view>
|
||||
<view wx:if="{{selectableVariants.length}}" class="variant-list">
|
||||
<view wx:for="{{selectableVariants}}" wx:key="id" class="variant-card {{item.selected ? 'variant-card--active' : ''}}" data-variant-id="{{item.id}}" bindtap="handleSelectVariant">
|
||||
<view class="variant-card__main">
|
||||
<view class="variant-card__title-row">
|
||||
@@ -109,7 +110,7 @@
|
||||
<view class="actions">
|
||||
<button class="btn btn--secondary" bindtap="handleBack">返回活动页</button>
|
||||
<button class="btn btn--ghost" bindtap="handleRefresh">刷新</button>
|
||||
<button class="btn btn--primary" bindtap="handleLaunch">进入地图</button>
|
||||
<button class="btn btn--primary" bindtap="handleLaunch" disabled="{{!canLaunch}}">进入地图</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -45,7 +45,7 @@ function formatVariantSummary(result: BackendEventPlayResult): string {
|
||||
function formatPresentationSummary(result: BackendEventPlayResult): string {
|
||||
const currentPresentation = result.currentPresentation
|
||||
if (!currentPresentation) {
|
||||
return '当前未声明展示版本'
|
||||
return '当前发布 release 未绑定展示版本,或当前尚未发布'
|
||||
}
|
||||
|
||||
const presentationId = currentPresentation.presentationId || '--'
|
||||
@@ -57,7 +57,7 @@ function formatPresentationSummary(result: BackendEventPlayResult): string {
|
||||
function formatContentBundleSummary(result: BackendEventPlayResult): string {
|
||||
const currentContentBundle = result.currentContentBundle
|
||||
if (!currentContentBundle) {
|
||||
return '当前未声明内容包版本'
|
||||
return '当前发布 release 未绑定内容包版本,或当前尚未发布'
|
||||
}
|
||||
|
||||
const bundleId = currentContentBundle.bundleId || '--'
|
||||
@@ -147,6 +147,14 @@ Page({
|
||||
pageEventId: this.data.eventId || '',
|
||||
resultEventId: result.event.id || '',
|
||||
primaryAction: result.play.primaryAction || '',
|
||||
detailStatus: result.play.reason || '',
|
||||
detailCanLaunch: result.play.canLaunch,
|
||||
detailCurrentPresentation: result.currentPresentation
|
||||
? `${result.currentPresentation.presentationId || '--'} / ${result.currentPresentation.templateKey || '--'} / ${result.currentPresentation.version || '--'}`
|
||||
: '',
|
||||
detailCurrentContentBundle: result.currentContentBundle
|
||||
? `${result.currentContentBundle.bundleId || '--'} / ${result.currentContentBundle.bundleType || '--'} / ${result.currentContentBundle.version || '--'}`
|
||||
: '',
|
||||
assignmentMode,
|
||||
variantCount: result.play.courseVariants ? result.play.courseVariants.length : 0,
|
||||
},
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<view class="summary">状态:{{statusText}}</view>
|
||||
<view class="summary">赛道模式:{{variantModeText}}</view>
|
||||
<view class="summary">赛道摘要:{{variantSummaryText}}</view>
|
||||
<view class="summary">展示版本:{{presentationText}}</view>
|
||||
<view class="summary">内容包版本:{{contentBundleText}}</view>
|
||||
<view class="summary">当前发布展示版本:{{presentationText}}</view>
|
||||
<view class="summary">当前发布内容包版本:{{contentBundleText}}</view>
|
||||
<view class="actions">
|
||||
<button class="btn btn--secondary" bindtap="handleRefresh">刷新</button>
|
||||
<button class="btn btn--primary" bindtap="handleLaunch">前往准备页</button>
|
||||
|
||||
220
miniprogram/pages/events/events.ts
Normal file
220
miniprogram/pages/events/events.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import { loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
|
||||
import {
|
||||
getEntryHome,
|
||||
type BackendCardResult,
|
||||
type BackendContentBundleSummary,
|
||||
type BackendPresentationSummary,
|
||||
} from '../../utils/backendApi'
|
||||
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
||||
|
||||
const DEFAULT_CHANNEL_CODE = 'mini-demo'
|
||||
const DEFAULT_CHANNEL_TYPE = 'wechat_mini'
|
||||
|
||||
type EventListFilter = 'all' | 'experience'
|
||||
|
||||
type EventCardView = {
|
||||
id: string
|
||||
eventId: string
|
||||
titleText: string
|
||||
subtitleText: string
|
||||
summaryText: string
|
||||
statusText: string
|
||||
timeWindowText: string
|
||||
ctaText: string
|
||||
badgeText: string
|
||||
eventTypeText: string
|
||||
presentationText: string
|
||||
contentBundleText: string
|
||||
coverUrl: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
type EventsPageData = {
|
||||
loading: boolean
|
||||
statusText: string
|
||||
currentFilter: EventListFilter
|
||||
cards: EventCardView[]
|
||||
}
|
||||
|
||||
function requireAuthToken(): 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 formatPresentationSummary(summary?: BackendPresentationSummary | null): string {
|
||||
if (!summary) {
|
||||
return '当前未声明展示版本'
|
||||
}
|
||||
|
||||
return summary.version || summary.templateKey || summary.presentationId || '当前未声明展示版本'
|
||||
}
|
||||
|
||||
function formatContentBundleSummary(summary?: BackendContentBundleSummary | null): string {
|
||||
if (!summary) {
|
||||
return '当前未声明内容包版本'
|
||||
}
|
||||
|
||||
return summary.version || summary.bundleType || summary.bundleId || '当前未声明内容包版本'
|
||||
}
|
||||
|
||||
function buildCardView(card: BackendCardResult): EventCardView {
|
||||
const eventId = card.event && card.event.id ? card.event.id : ''
|
||||
const statusText = card.status || card.statusCode || '状态待确认'
|
||||
const badgeText = card.isDefaultExperience ? '体验' : '活动'
|
||||
const eventTypeText = card.eventType || '类型待确认'
|
||||
const subtitleText = card.subtitle || (card.event && card.event.displayName ? card.event.displayName : '')
|
||||
|
||||
return {
|
||||
id: card.id,
|
||||
eventId,
|
||||
titleText: card.title || '未命名活动',
|
||||
subtitleText,
|
||||
summaryText: card.summary || (card.event && card.event.summary ? card.event.summary : '当前暂无活动摘要'),
|
||||
statusText,
|
||||
timeWindowText: card.timeWindow || '时间待公布',
|
||||
ctaText: card.ctaText || '查看详情',
|
||||
badgeText,
|
||||
eventTypeText,
|
||||
presentationText: formatPresentationSummary(card.currentPresentation),
|
||||
contentBundleText: formatContentBundleSummary(card.currentContentBundle),
|
||||
coverUrl: card.coverUrl || '',
|
||||
disabled: !eventId,
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilter(cards: BackendCardResult[], filter: EventListFilter): EventCardView[] {
|
||||
const filtered = filter === 'experience'
|
||||
? cards.filter((item) => item.isDefaultExperience === true)
|
||||
: cards
|
||||
|
||||
return filtered
|
||||
.slice()
|
||||
.sort((left, right) => {
|
||||
const leftPriority = typeof left.displayPriority === 'number' ? left.displayPriority : 0
|
||||
const rightPriority = typeof right.displayPriority === 'number' ? right.displayPriority : 0
|
||||
return rightPriority - leftPriority
|
||||
})
|
||||
.map(buildCardView)
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: false,
|
||||
statusText: '准备加载活动列表',
|
||||
currentFilter: 'all',
|
||||
cards: [],
|
||||
} as EventsPageData,
|
||||
|
||||
onLoad() {
|
||||
this.loadCards()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadCards()
|
||||
},
|
||||
|
||||
async loadCards() {
|
||||
const accessToken = requireAuthToken()
|
||||
if (!accessToken) {
|
||||
wx.redirectTo({ url: '/pages/login/login' })
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({
|
||||
loading: true,
|
||||
statusText: '正在加载活动列表',
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await getEntryHome({
|
||||
baseUrl: loadBackendBaseUrl(),
|
||||
accessToken,
|
||||
channelCode: DEFAULT_CHANNEL_CODE,
|
||||
channelType: DEFAULT_CHANNEL_TYPE,
|
||||
})
|
||||
this.applyCards(result.cards || [])
|
||||
} catch (error) {
|
||||
const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
|
||||
this.setData({
|
||||
loading: false,
|
||||
statusText: `活动列表加载失败:${message}`,
|
||||
cards: [],
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
applyCards(cards: BackendCardResult[]) {
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'event-cards',
|
||||
message: 'event cards loaded',
|
||||
details: {
|
||||
filter: this.data.currentFilter,
|
||||
cardCount: cards.length,
|
||||
experienceCount: cards.filter((item) => item.isDefaultExperience === true).length,
|
||||
cardEventIds: cards.map((item) => (item.event && item.event.id ? item.event.id : '')),
|
||||
},
|
||||
})
|
||||
|
||||
const filteredCards = applyFilter(cards, this.data.currentFilter)
|
||||
this.setData({
|
||||
loading: false,
|
||||
statusText: filteredCards.length ? '活动列表加载完成' : '当前没有可显示活动',
|
||||
cards: filteredCards,
|
||||
})
|
||||
const pageInstance = this as unknown as WechatMiniprogram.Page.Instance<EventsPageData, Record<string, never>> & {
|
||||
rawCards?: BackendCardResult[]
|
||||
}
|
||||
pageInstance.rawCards = cards
|
||||
},
|
||||
|
||||
handleSwitchFilter(event: WechatMiniprogram.TouchEvent) {
|
||||
const filter = event.currentTarget.dataset.filter as EventListFilter | undefined
|
||||
if (!filter || filter === this.data.currentFilter) {
|
||||
return
|
||||
}
|
||||
|
||||
const pageInstance = this as unknown as WechatMiniprogram.Page.Instance<EventsPageData, Record<string, never>> & {
|
||||
rawCards?: BackendCardResult[]
|
||||
}
|
||||
const rawCards = pageInstance.rawCards || []
|
||||
const filteredCards = applyFilter(rawCards, filter)
|
||||
this.setData({
|
||||
currentFilter: filter,
|
||||
statusText: filteredCards.length ? '活动列表加载完成' : '当前筛选下没有活动',
|
||||
cards: filteredCards,
|
||||
})
|
||||
},
|
||||
|
||||
handleRefresh() {
|
||||
this.loadCards()
|
||||
},
|
||||
|
||||
handleOpenCard(event: WechatMiniprogram.TouchEvent) {
|
||||
const eventId = event.currentTarget.dataset.eventId as string | undefined
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'event-cards',
|
||||
message: 'event card clicked',
|
||||
eventId: eventId || '',
|
||||
details: {
|
||||
clickedEventId: eventId || '',
|
||||
filter: this.data.currentFilter,
|
||||
},
|
||||
})
|
||||
if (!eventId) {
|
||||
wx.showToast({
|
||||
title: '该卡片暂无活动入口',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.navigateTo({
|
||||
url: `/pages/event/event?eventId=${encodeURIComponent(eventId)}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
47
miniprogram/pages/events/events.wxml
Normal file
47
miniprogram/pages/events/events.wxml
Normal file
@@ -0,0 +1,47 @@
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="shell">
|
||||
<view class="hero">
|
||||
<view class="hero__eyebrow">Activity List</view>
|
||||
<view class="hero__title">活动列表</view>
|
||||
<view class="hero__desc">当前阶段先做独立列表页第一刀,不重构首页入口区。</view>
|
||||
</view>
|
||||
|
||||
<view class="panel">
|
||||
<view class="panel__title">筛选</view>
|
||||
<view class="summary">{{statusText}}</view>
|
||||
<view class="filters">
|
||||
<view class="filter-chip {{currentFilter === 'all' ? 'filter-chip--active' : ''}}" data-filter="all" bindtap="handleSwitchFilter">全部</view>
|
||||
<view class="filter-chip {{currentFilter === 'experience' ? 'filter-chip--active' : ''}}" data-filter="experience" bindtap="handleSwitchFilter">体验</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn btn--secondary" bindtap="handleRefresh">刷新列表</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel">
|
||||
<view class="panel__title">活动卡片</view>
|
||||
<view wx:if="{{!cards.length}}" class="summary">当前筛选下没有活动卡片</view>
|
||||
<view wx:for="{{cards}}" wx:key="id" class="card {{item.disabled ? 'card--disabled' : ''}}" bindtap="handleOpenCard" data-event-id="{{item.eventId}}">
|
||||
<image wx:if="{{item.coverUrl}}" class="card__cover" src="{{item.coverUrl}}" mode="aspectFill"></image>
|
||||
<view class="card__top">
|
||||
<text class="card__badge">{{item.badgeText}}</text>
|
||||
<text class="card__type">{{item.eventTypeText}}</text>
|
||||
</view>
|
||||
<view class="card__title">{{item.titleText}}</view>
|
||||
<view wx:if="{{item.subtitleText}}" class="card__subtitle">{{item.subtitleText}}</view>
|
||||
<view class="card__summary">{{item.summaryText}}</view>
|
||||
<view class="card__meta-row">
|
||||
<text class="card__meta">{{item.statusText}}</text>
|
||||
<text class="card__meta">{{item.timeWindowText}}</text>
|
||||
</view>
|
||||
<view class="card__meta-row">
|
||||
<text class="card__meta">展示:{{item.presentationText}}</text>
|
||||
</view>
|
||||
<view class="card__meta-row">
|
||||
<text class="card__meta">内容:{{item.contentBundleText}}</text>
|
||||
</view>
|
||||
<view class="card__cta">{{item.ctaText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
186
miniprogram/pages/events/events.wxss
Normal file
186
miniprogram/pages/events/events.wxss
Normal file
@@ -0,0 +1,186 @@
|
||||
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 {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #30465f;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-chip {
|
||||
min-width: 120rpx;
|
||||
padding: 14rpx 20rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #eef3f8;
|
||||
color: #50677f;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.filter-chip--active {
|
||||
background: #173d73;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
gap: 12rpx;
|
||||
padding: 22rpx;
|
||||
border-radius: 22rpx;
|
||||
background: #f6f9fc;
|
||||
}
|
||||
|
||||
.card--disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.card__cover {
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #d7e4f2;
|
||||
}
|
||||
|
||||
.card__top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.card__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 40rpx;
|
||||
padding: 0 14rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #dce9fb;
|
||||
color: #173d73;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card__type {
|
||||
font-size: 22rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #17345a;
|
||||
}
|
||||
|
||||
.card__subtitle,
|
||||
.card__summary,
|
||||
.card__meta,
|
||||
.card__cta {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card__subtitle {
|
||||
color: #4f627a;
|
||||
}
|
||||
|
||||
.card__summary {
|
||||
color: #30465f;
|
||||
}
|
||||
|
||||
.card__meta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.card__meta {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.card__cta {
|
||||
color: #173d73;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -153,6 +153,12 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
handleOpenEventList() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/events/events',
|
||||
})
|
||||
},
|
||||
|
||||
handleLogout() {
|
||||
clearBackendAuthTokens()
|
||||
setGlobalMockDebugBridgeEnabled(false)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<view class="summary">最近一局运行对象:{{recentRuntimeText}}</view>
|
||||
<view class="actions">
|
||||
<button class="btn btn--secondary" bindtap="handleRefresh">刷新首页</button>
|
||||
<button class="btn btn--ghost" bindtap="handleOpenEventList">活动列表</button>
|
||||
<button class="btn btn--ghost" bindtap="handleOpenRecentResult">查看结果</button>
|
||||
<button class="btn btn--ghost" bindtap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
|
||||
@@ -94,6 +94,15 @@ export interface BackendCardResult {
|
||||
type: string
|
||||
title: string
|
||||
subtitle?: string | null
|
||||
summary?: string | null
|
||||
status?: string | null
|
||||
statusCode?: string | null
|
||||
timeWindow?: string | null
|
||||
ctaText?: string | null
|
||||
isDefaultExperience?: boolean
|
||||
eventType?: string | null
|
||||
currentPresentation?: BackendPresentationSummary | null
|
||||
currentContentBundle?: BackendContentBundleSummary | null
|
||||
coverUrl?: string | null
|
||||
displaySlot: string
|
||||
displayPriority: number
|
||||
|
||||
Reference in New Issue
Block a user