补齐讲解字段映射并优先展示接口展厅图
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-07 01:31:06 +08:00
parent 0ce38b390e
commit 4b03cc281c
9 changed files with 199 additions and 17 deletions

View File

@@ -160,6 +160,9 @@ export interface ExplainGuideStopSelectItem {
hallId?: string
hallName?: string
floorId?: string
poiId?: string
mapX?: number
mapY?: number
coverImageUrl?: string
description?: string
hasAudio?: boolean
@@ -239,7 +242,7 @@ const emptyDescription = computed(() => {
})
const hallIconUrl = (hall: ExplainHallSelectItem) => {
return hallIconMap[hall.name.trim()] || hall.image || ''
return hall.image || hallIconMap[hall.name.trim()] || ''
}
const hallIconText = (name: string) => name.trim().slice(0, 1) || '讲'

View File

@@ -83,6 +83,8 @@ export interface BackendGuideStop {
id?: string | number | null
name?: string | null
floorId?: string | number | null
mapX?: number | string | null
mapY?: number | string | null
targetType?: string | null
targetId?: string | number | null
coverImageUrl?: string | null
@@ -128,6 +130,7 @@ export interface BackendCatalogLinkedExhibitItem {
code?: string | null
exhibitCode?: string | null
coverImageUrl?: string | null
galleryUrls?: string | string[] | null
sortOrder?: number | null
}
@@ -135,6 +138,10 @@ export interface BackendCatalogStopItem {
stopId?: string | number | null
id?: string | number | null
outlineId?: string | number | null
poiId?: string | number | null
floorId?: string | number | null
mapX?: number | string | null
mapY?: number | string | null
name?: string | null
guideLevel?: string | null
description?: string | null
@@ -168,7 +175,7 @@ const firstText = (...values: Array<string | number | null | undefined>) => (
.find(Boolean) || ''
)
const parseGalleryUrls = (value: BackendExhibit['galleryUrls']) => {
const parseGalleryUrls = (value: BackendExhibit['galleryUrls'] | string[]) => {
if (!value) return []
if (Array.isArray(value)) return value.map(String).filter(Boolean)
@@ -308,8 +315,11 @@ const toCatalogLinkedExhibitSummary = (source: BackendCatalogLinkedExhibitItem)
id,
name: firstText(source.name, source.exhibitCode, source.code, `展品 ${id}`),
nameEn: firstText(source.nameEn) || undefined,
code: firstText(source.code, source.exhibitCode) || undefined,
exhibitCode: firstText(source.exhibitCode, source.code) || undefined,
coverImageUrl: normalizeSameOriginPublicUrl(firstText(source.coverImageUrl)) || undefined
coverImageUrl: normalizeSameOriginPublicUrl(firstText(source.coverImageUrl)) || undefined,
galleryUrls: parseGalleryUrls(source.galleryUrls),
sortOrder: normalizeNumber(source.sortOrder)
}
}
@@ -331,7 +341,7 @@ export const toCatalogGuideStop = (
name: firstText(source.name, `讲解点${id}`),
hallId: hall?.id,
hallName: hall?.name,
floorId: hall?.floorId,
floorId: stringifyId(source.floorId) || hall?.floorId,
targetType: playTargetType,
targetId: playTargetId,
coverImageUrl: canUseStopImage
@@ -342,6 +352,9 @@ export const toCatalogGuideStop = (
audioStatus: normalizeCatalogAudioStatus(source.audioStatus),
supportedLanguages: normalizeSupportedLanguages(source.supportedLanguages),
hasTextRecord: source.hasTextRecord === true,
poiId: stringifyId(source.poiId) || undefined,
mapX: normalizeNumber(source.mapX),
mapY: normalizeNumber(source.mapY),
outlineId: stringifyId(source.outlineId) || outline?.id,
outlineName: outline?.name,
sort: typeof source.sort === 'number' ? source.sort : undefined,
@@ -374,6 +387,10 @@ export const toCatalogMuseumExhibitFromStop = (
image: stop.coverImageUrl || undefined,
description: stop.description || linkedPrimary?.name || '该讲解点暂无简介。',
tags: [stop.outlineName, linkedPrimary?.exhibitCode].filter(Boolean) as string[],
poiId: stop.poiId,
sourcePoiId: stop.poiId,
mapX: stop.mapX,
mapY: stop.mapY,
guideTitle: stop.name,
guideText: stop.description,
audioAvailable: audioReady,
@@ -611,6 +628,8 @@ export const toBackendGuideStop = (source: BackendGuideStop): ExplainGuideStop |
description: firstText(source.description) || undefined,
hasAudio: source.hasAudio === true,
poiId: stringifyId(source.poiId) || undefined,
mapX: normalizeNumber(source.mapX),
mapY: normalizeNumber(source.mapY),
outlineId: stringifyId(source.outlineId) || undefined,
outlineName: firstText(source.outlineName) || undefined,
sort: typeof source.sort === 'number' ? source.sort : undefined

View File

@@ -9,8 +9,11 @@ export interface BackendGuideStopLinkedExhibit {
id?: string | number | null
name?: string | null
nameEn?: string | null
code?: string | null
exhibitCode?: string | null
coverImageUrl?: string | null
galleryUrls?: string | string[] | null
sortOrder?: number | string | null
}
export interface BackendGuideStopInfo {
@@ -19,6 +22,10 @@ export interface BackendGuideStopInfo {
targetId?: string | number | null
resolvedStopId?: string | number | null
stopId?: string | number | null
poiId?: string | number | null
floorId?: string | number | null
mapX?: number | string | null
mapY?: number | string | null
lang?: string | null
title?: string | null
description?: string | null
@@ -74,8 +81,11 @@ export interface GuideStopLinkedExhibit {
id: string
name: string
nameEn?: string
code?: string
exhibitCode?: string
coverImageUrl?: string
galleryUrls?: string[]
sortOrder?: number
}
export interface GuideStopInfo {
@@ -83,6 +93,10 @@ export interface GuideStopInfo {
targetType: AudioPlayTargetType
targetId: string
resolvedStopId?: string
poiId?: string
floorId?: string
mapX?: number
mapY?: number
lang: 'zh-CN' | 'en-US'
title: string
description?: string
@@ -156,7 +170,7 @@ const normalizeLanguage = (value: string | null | undefined): 'zh-CN' | 'en-US'
value === 'en-US' ? 'en-US' : 'zh-CN'
)
const parseGalleryUrls = (value: BackendGuideStopInfo['galleryUrls']) => {
const parseGalleryUrls = (value: BackendGuideStopInfo['galleryUrls'] | BackendGuideStopLinkedExhibit['galleryUrls']) => {
if (!value) return []
if (Array.isArray(value)) {
return value.map((entry) => normalizeSameOriginPublicUrl(String(entry))).filter(Boolean)
@@ -192,10 +206,13 @@ const normalizeLinkedExhibits = (items: BackendGuideStopLinkedExhibit[] | null |
return {
id,
name: item.name?.trim() || item.exhibitCode?.trim() || `展品 ${id}`,
name: item.name?.trim() || item.exhibitCode?.trim() || item.code?.trim() || `展品 ${id}`,
nameEn: item.nameEn?.trim() || undefined,
exhibitCode: item.exhibitCode?.trim() || undefined,
coverImageUrl: normalizeSameOriginPublicUrl(item.coverImageUrl) || undefined
code: item.code?.trim() || item.exhibitCode?.trim() || undefined,
exhibitCode: item.exhibitCode?.trim() || item.code?.trim() || undefined,
coverImageUrl: normalizeSameOriginPublicUrl(item.coverImageUrl) || undefined,
galleryUrls: parseGalleryUrls(item.galleryUrls),
sortOrder: normalizeNumber(item.sortOrder)
}
})
.filter(Boolean) as GuideStopLinkedExhibit[]
@@ -225,6 +242,10 @@ export const toGuideStopInfo = (
targetType,
targetId,
resolvedStopId: stringifyId(source.resolvedStopId) || stringifyId(source.stopId) || undefined,
poiId: stringifyId(source.poiId) || undefined,
floorId: stringifyId(source.floorId) || undefined,
mapX: normalizeNumber(source.mapX),
mapY: normalizeNumber(source.mapY),
lang: normalizeLanguage(source.lang || fallback.lang),
title: source.title?.trim() || '讲解内容',
description: source.description?.trim() || undefined,

View File

@@ -152,6 +152,8 @@ export interface MuseumExhibit {
description?: string
poiId?: string
sourcePoiId?: string
mapX?: number
mapY?: number
location?: GuideLocationResolution
artist?: string
year?: string
@@ -183,8 +185,11 @@ export interface MuseumExhibit {
id: string
name: string
nameEn?: string
code?: string
exhibitCode?: string
coverImageUrl?: string
galleryUrls?: string[]
sortOrder?: number
}>
stopInfoAvailable?: boolean
stopInfoReason?: string
@@ -208,6 +213,8 @@ export interface ExplainGuideStop {
supportedLanguages?: string[]
hasTextRecord?: boolean
poiId?: string
mapX?: number
mapY?: number
outlineId?: string
outlineName?: string
sort?: number
@@ -219,8 +226,11 @@ export interface ExplainGuideStop {
id: string
name: string
nameEn?: string
code?: string
exhibitCode?: string
coverImageUrl?: string
galleryUrls?: string[]
sortOrder?: number
}>
playTargetType?: AudioPlayTargetType
playTargetId?: string

View File

@@ -27,10 +27,13 @@
<view class="detail-body">
<text class="detail-title">{{ exhibit.title }}</text>
<text v-if="detailMeta" class="detail-meta">{{ detailMeta }}</text>
<view v-if="exhibit.linkedExhibitStatusText" class="detail-chip-row">
<view class="detail-chip" :class="{ shared: exhibit.isSharedStop }">
<view v-if="exhibit.linkedExhibitStatusText || exhibit.galleryStatusText" class="detail-chip-row">
<view v-if="exhibit.linkedExhibitStatusText" class="detail-chip" :class="{ shared: exhibit.isSharedStop }">
<text class="detail-chip-text">{{ exhibit.linkedExhibitStatusText }}</text>
</view>
<view v-if="exhibit.galleryStatusText" class="detail-chip">
<text class="detail-chip-text">{{ exhibit.galleryStatusText }}</text>
</view>
</view>
<view
@@ -67,6 +70,36 @@
</view>
<text v-if="textError" class="text-error">{{ textError }}</text>
</view>
<view v-if="visibleLinkedExhibits.length" class="content-section">
<text class="section-title">关联展品</text>
<view class="linked-exhibit-list">
<view
v-for="linked in visibleLinkedExhibits"
:key="linked.id"
class="linked-exhibit-item"
>
<image
v-if="linked.coverImageUrl"
class="linked-exhibit-image"
:src="linked.coverImageUrl"
mode="aspectFill"
/>
<view v-else class="linked-exhibit-image placeholder">
<text class="linked-exhibit-placeholder">展品</text>
</view>
<view class="linked-exhibit-main">
<text class="linked-exhibit-name">{{ linked.name }}</text>
<text v-if="linked.exhibitCode || linked.code" class="linked-exhibit-code">
{{ linked.exhibitCode || linked.code }}
</text>
<text v-if="linked.galleryUrls?.length" class="linked-exhibit-gallery">
图集 {{ linked.galleryUrls.length }}
</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
@@ -168,6 +201,10 @@ const canExpandText = computed(() => (
&& !textExpanded.value
&& !textLoading.value
))
const visibleLinkedExhibits = computed(() => (
[...(exhibit.value.linkedExhibits || [])]
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
))
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
const resolveHallGuidePoi = async () => {
@@ -641,6 +678,76 @@ const handleBack = () => {
color: #8a4a31;
}
.linked-exhibit-list {
margin-top: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.linked-exhibit-item {
min-height: 72px;
padding: 10px;
display: flex;
align-items: center;
gap: 12px;
box-sizing: border-box;
background: #ffffff;
border: 1px solid #e1e5dc;
border-radius: 8px;
}
.linked-exhibit-image {
flex-shrink: 0;
width: 52px;
height: 52px;
border-radius: 6px;
background: #eef1e8;
}
.linked-exhibit-image.placeholder {
display: flex;
align-items: center;
justify-content: center;
}
.linked-exhibit-placeholder,
.linked-exhibit-name,
.linked-exhibit-code,
.linked-exhibit-gallery {
display: block;
}
.linked-exhibit-placeholder {
font-size: 12px;
line-height: 16px;
font-weight: 700;
color: #68725d;
}
.linked-exhibit-main {
min-width: 0;
flex: 1;
}
.linked-exhibit-name {
font-size: 15px;
line-height: 21px;
font-weight: 800;
color: #151713;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.linked-exhibit-code,
.linked-exhibit-gallery {
margin-top: 3px;
font-size: 12px;
line-height: 17px;
color: #666d61;
}
.action-bar {
position: absolute;
left: 0;

View File

@@ -107,6 +107,9 @@ const explainGuideStopItems = computed<ExplainGuideStopSelectItem[]>(() => (
hallId: stop.hallId,
hallName: stop.hallName,
floorId: stop.floorId,
poiId: stop.poiId,
mapX: stop.mapX,
mapY: stop.mapY,
coverImageUrl: stop.coverImageUrl,
description: stop.description,
hasAudio: stop.hasAudio,

View File

@@ -723,6 +723,9 @@ const explainGuideStopItems = computed<ExplainGuideStopSelectItem[]>(() => (
hallId: stop.hallId,
hallName: stop.hallName,
floorId: stop.floorId,
poiId: stop.poiId,
mapX: stop.mapX,
mapY: stop.mapY,
coverImageUrl: stop.coverImageUrl,
description: stop.description,
hasAudio: stop.hasAudio,

View File

@@ -135,12 +135,14 @@ export class ExplainUseCase {
name: stopInfo.title || fallback?.name || linkedPrimary?.name || '讲解内容',
hallId: fallback?.hallId,
hallName: fallback?.hallName,
floorId: fallback?.floorId,
floorId: stopInfo.floorId || fallback?.floorId,
floorLabel: fallback?.floorLabel,
image: coverImage,
description,
poiId: fallback?.poiId,
sourcePoiId: fallback?.sourcePoiId,
poiId: stopInfo.poiId || fallback?.poiId,
sourcePoiId: stopInfo.poiId || fallback?.sourcePoiId,
mapX: stopInfo.mapX ?? fallback?.mapX,
mapY: stopInfo.mapY ?? fallback?.mapY,
location: fallback?.location,
year: fallback?.year,
material: fallback?.material,

View File

@@ -46,7 +46,11 @@ export interface ExplainDetailPageViewModel {
coverImages: string[]
hallId?: string
hallName?: string
floorId?: string
floorLabel?: string
poiId?: string
mapX?: number
mapY?: number
summary: string
body: string
audio: {
@@ -61,6 +65,7 @@ export interface ExplainDetailPageViewModel {
statusText?: string
}
imageStatus?: string
galleryStatusText?: string
linkedExhibitCount?: number
isSharedStop?: boolean
linkedExhibitStatusText?: string
@@ -68,8 +73,11 @@ export interface ExplainDetailPageViewModel {
id: string
name: string
nameEn?: string
code?: string
exhibitCode?: string
coverImageUrl?: string
galleryUrls?: string[]
sortOrder?: number
}>
chapters: Array<{
id: string
@@ -171,6 +179,10 @@ export const toExplainExhibitViewModel = (exhibit: MuseumExhibit): ExplainExhibi
export const toExplainDetailPageViewModel = (exhibit: MuseumExhibit): ExplainDetailPageViewModel => {
const hasPlayableAudio = isAudioReady(exhibit)
const coverImages = [
exhibit.image || EXPLAIN_DETAIL_PLACEHOLDER_IMAGE,
...(exhibit.galleryUrls || [])
].filter(Boolean)
const linkedExhibitCount = typeof exhibit.linkedExhibitCount === 'number'
? exhibit.linkedExhibitCount
: exhibit.linkedExhibits?.length
@@ -195,13 +207,14 @@ export const toExplainDetailPageViewModel = (exhibit: MuseumExhibit): ExplainDet
title: exhibit.name,
subtitle: buildSubtitle(exhibit),
contentType: contentTypeFor(exhibit),
coverImages: [
exhibit.image || EXPLAIN_DETAIL_PLACEHOLDER_IMAGE,
...(exhibit.galleryUrls || [])
].filter(Boolean),
coverImages,
hallId: exhibit.hallId,
hallName: exhibit.hallName,
floorId: exhibit.floorId,
floorLabel: exhibit.floorLabel,
poiId: exhibit.poiId,
mapX: exhibit.mapX,
mapY: exhibit.mapY,
summary: transcript || '该展项暂无讲解文稿。',
body,
audio: {
@@ -218,6 +231,7 @@ export const toExplainDetailPageViewModel = (exhibit: MuseumExhibit): ExplainDet
statusText: hasPlayableAudio ? '可播放' : '当前语言暂无语音讲解'
},
imageStatus: exhibit.imageStatus,
galleryStatusText: coverImages.length > 1 ? `${coverImages.length} 张讲解图片` : undefined,
linkedExhibitCount,
isSharedStop: exhibit.isSharedStop,
linkedExhibitStatusText,