推进活动系统最小成品闭环与游客体验
This commit is contained in:
195
miniprogram/pages/experience-map/experience-map.ts
Normal file
195
miniprogram/pages/experience-map/experience-map.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
|
||||
import {
|
||||
getExperienceMapDetail,
|
||||
getPublicExperienceMapDetail,
|
||||
type BackendContentBundleSummary,
|
||||
type BackendDefaultExperienceSummary,
|
||||
type BackendExperienceMapDetail,
|
||||
type BackendPresentationSummary,
|
||||
} from '../../utils/backendApi'
|
||||
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
||||
|
||||
type DefaultExperienceCardView = {
|
||||
eventId: string
|
||||
titleText: string
|
||||
subtitleText: string
|
||||
statusText: string
|
||||
ctaText: string
|
||||
eventTypeText: string
|
||||
presentationText: string
|
||||
contentBundleText: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
type ExperienceMapPageData = {
|
||||
mapId: string
|
||||
loading: boolean
|
||||
statusText: string
|
||||
placeText: string
|
||||
mapText: string
|
||||
summaryText: string
|
||||
tileInfoText: string
|
||||
cards: DefaultExperienceCardView[]
|
||||
}
|
||||
|
||||
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 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 buildDefaultExperienceCard(item: BackendDefaultExperienceSummary): DefaultExperienceCardView {
|
||||
const eventId = item.eventId || ''
|
||||
return {
|
||||
eventId,
|
||||
titleText: item.title || '未命名体验活动',
|
||||
subtitleText: item.subtitle || '当前暂无副标题',
|
||||
statusText: item.status || item.statusCode || '状态待确认',
|
||||
ctaText: item.ctaText || '查看体验',
|
||||
eventTypeText: item.eventType || '类型待确认',
|
||||
presentationText: formatPresentationSummary(item.currentPresentation),
|
||||
contentBundleText: formatContentBundleSummary(item.currentContentBundle),
|
||||
disabled: !eventId,
|
||||
}
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
mapId: '',
|
||||
loading: false,
|
||||
statusText: '准备加载地图详情',
|
||||
placeText: '地点待确认',
|
||||
mapText: '地图待确认',
|
||||
summaryText: '当前暂无地图摘要',
|
||||
tileInfoText: '瓦片信息待确认',
|
||||
cards: [],
|
||||
} as ExperienceMapPageData,
|
||||
|
||||
onLoad(query: { mapId?: string }) {
|
||||
const mapId = query && query.mapId ? decodeURIComponent(query.mapId) : ''
|
||||
if (!mapId) {
|
||||
this.setData({
|
||||
statusText: '缺少 mapId',
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setData({ mapId })
|
||||
this.loadMapDetail(mapId)
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.data.mapId) {
|
||||
this.loadMapDetail(this.data.mapId)
|
||||
}
|
||||
},
|
||||
|
||||
async loadMapDetail(mapId?: string) {
|
||||
const targetMapId = mapId || this.data.mapId
|
||||
const accessToken = getAccessToken()
|
||||
|
||||
this.setData({
|
||||
loading: true,
|
||||
statusText: '正在加载地图详情',
|
||||
})
|
||||
|
||||
try {
|
||||
const baseUrl = loadBackendBaseUrl()
|
||||
const result = accessToken
|
||||
? await getExperienceMapDetail({
|
||||
baseUrl,
|
||||
accessToken,
|
||||
mapAssetId: targetMapId,
|
||||
})
|
||||
: await getPublicExperienceMapDetail({
|
||||
baseUrl,
|
||||
mapAssetId: targetMapId,
|
||||
})
|
||||
this.applyDetail(result)
|
||||
} catch (error) {
|
||||
const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
|
||||
this.setData({
|
||||
loading: false,
|
||||
statusText: `地图详情加载失败:${message}`,
|
||||
cards: [],
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
applyDetail(result: BackendExperienceMapDetail) {
|
||||
const cards = (result.defaultExperiences || []).map(buildDefaultExperienceCard)
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'experience-map-detail',
|
||||
message: 'experience map detail loaded',
|
||||
details: {
|
||||
guestMode: !getAccessToken(),
|
||||
mapId: result.mapId || this.data.mapId || '',
|
||||
placeId: result.placeId || '',
|
||||
defaultExperienceCount: cards.length,
|
||||
defaultExperienceEventIds: (result.defaultExperiences || []).map((item) => item.eventId || ''),
|
||||
},
|
||||
})
|
||||
|
||||
const tileBase = result.tileBaseUrl || ''
|
||||
const tileMeta = result.tileMetaUrl || ''
|
||||
const tileInfoText = tileBase || tileMeta
|
||||
? `底图 ${tileBase || '--'} / Meta ${tileMeta || '--'}`
|
||||
: '当前未声明瓦片信息'
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
statusText: cards.length ? '地图详情加载完成' : '当前地图暂无默认体验活动',
|
||||
placeText: result.placeName || result.placeId || '地点待确认',
|
||||
mapText: result.mapName || result.mapId || '地图待确认',
|
||||
summaryText: result.summary || '当前暂无地图摘要',
|
||||
tileInfoText,
|
||||
cards,
|
||||
})
|
||||
},
|
||||
|
||||
handleRefresh() {
|
||||
this.loadMapDetail()
|
||||
},
|
||||
|
||||
handleOpenExperience(event: WechatMiniprogram.TouchEvent) {
|
||||
const eventId = event.currentTarget.dataset.eventId as string | undefined
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'experience-map-detail',
|
||||
message: 'default experience clicked',
|
||||
eventId: eventId || '',
|
||||
details: {
|
||||
mapId: this.data.mapId || '',
|
||||
clickedEventId: eventId || '',
|
||||
},
|
||||
})
|
||||
|
||||
if (!eventId) {
|
||||
wx.showToast({
|
||||
title: '该体验活动暂无入口',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.navigateTo({
|
||||
url: `/pages/event/event?eventId=${encodeURIComponent(eventId)}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
42
miniprogram/pages/experience-map/experience-map.wxml
Normal file
42
miniprogram/pages/experience-map/experience-map.wxml
Normal file
@@ -0,0 +1,42 @@
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="shell">
|
||||
<view class="hero">
|
||||
<view class="hero__eyebrow">Map Detail</view>
|
||||
<view class="hero__title">{{mapText}}</view>
|
||||
<view class="hero__desc">{{placeText}}</view>
|
||||
</view>
|
||||
|
||||
<view class="panel">
|
||||
<view class="panel__title">地图信息</view>
|
||||
<view class="summary">{{summaryText}}</view>
|
||||
<view class="summary">{{tileInfoText}}</view>
|
||||
<view class="summary">{{statusText}}</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="eventId" class="card {{item.disabled ? 'card--disabled' : ''}}" bindtap="handleOpenExperience" data-event-id="{{item.eventId}}">
|
||||
<view class="card__top">
|
||||
<text class="card__badge">体验</text>
|
||||
<text class="card__type">{{item.eventTypeText}}</text>
|
||||
</view>
|
||||
<view class="card__title">{{item.titleText}}</view>
|
||||
<view class="card__subtitle">{{item.subtitleText}}</view>
|
||||
<view class="card__meta-row">
|
||||
<text class="card__meta">{{item.statusText}}</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>
|
||||
153
miniprogram/pages/experience-map/experience-map.wxss
Normal file
153
miniprogram/pages/experience-map/experience-map.wxss
Normal file
@@ -0,0 +1,153 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.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__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__meta,
|
||||
.card__cta {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card__subtitle {
|
||||
color: #4f627a;
|
||||
}
|
||||
|
||||
.card__meta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.card__meta {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.card__cta {
|
||||
color: #173d73;
|
||||
font-weight: 700;
|
||||
}
|
||||
Reference in New Issue
Block a user