feat: refine guide explain UX and QA docs

This commit is contained in:
lyf
2026-06-12 17:20:39 +08:00
parent 2055b13b90
commit a7c1879f60
27 changed files with 2831 additions and 1362 deletions

View File

@@ -4,32 +4,62 @@ import type {
SearchIndexItem
} from '@/domain/museum'
export interface ExplainExhibitViewModel {
export type ExplainContentType = 'hall' | 'zone' | 'exhibit' | 'theme'
export type ExplainAudioStatus = 'playable' | 'unavailable' | 'none'
export interface ExplainCardViewModel {
id: string
name: string
hall?: string
floor?: string
image?: string
hasAudio: boolean
audioUnavailableReason?: string
title: string
subtitle?: string
summary?: string
coverImage?: string
contentType: ExplainContentType
hallName?: string
floorLabel?: string
durationLabel?: string
languageLabel?: string
audioStatus: ExplainAudioStatus
audioStatusText: string
badges: string[]
progress?: number
poiId?: string
locationActionText?: string
}
export interface ExplainDetailViewModel {
export interface ExplainDetailPageViewModel {
id: string
name: string
artist?: string
year?: string
material?: string
size?: string
hall?: string
image: string
description: string
hasAudio: boolean
audioUnavailableReason?: string
poiId?: string
title: string
subtitle?: string
contentType: ExplainContentType
coverImages: string[]
hallName?: string
floorLabel?: string
summary: string
body: string
audio: {
status: ExplainAudioStatus
url?: string
durationLabel?: string
language?: string
unavailableReason?: string
}
chapters: Array<{
id: string
title: string
startTime?: number
}>
transcript?: string
location?: {
poiId: string
actionText: string
previewOnly: boolean
}
relatedItems: ExplainCardViewModel[]
}
export type ExplainExhibitViewModel = ExplainCardViewModel
export type ExplainDetailViewModel = ExplainDetailPageViewModel
export interface HallDetailViewModel {
id: string
name: string
@@ -41,36 +71,76 @@ export interface HallDetailViewModel {
poiId?: string
}
export const toExplainExhibitViewModel = (exhibit: MuseumExhibit): ExplainExhibitViewModel => ({
const contentTypeFor = (exhibit: MuseumExhibit): ExplainContentType => {
if (exhibit.tags?.some((tag) => /主题/.test(tag))) return 'theme'
if (exhibit.hallName && exhibit.tags?.some((tag) => /展厅/.test(tag))) return 'hall'
return 'exhibit'
}
const buildSubtitle = (exhibit: MuseumExhibit) => [
exhibit.hallName,
exhibit.floorLabel
].filter(Boolean).join(' · ')
const buildBadges = (exhibit: MuseumExhibit) => {
const badges = [
exhibit.hallName ? '展厅讲解' : '讲解点',
exhibit.floorLabel || '',
...(exhibit.tags || [])
].filter(Boolean)
return Array.from(new Set(badges)).slice(0, 3)
}
export const toExplainCardViewModel = (exhibit: MuseumExhibit): ExplainCardViewModel => ({
id: exhibit.id,
name: exhibit.name,
hall: exhibit.hallName && exhibit.floorLabel
? `${exhibit.hallName} ${exhibit.floorLabel}`
: exhibit.hallName || exhibit.floorLabel,
floor: exhibit.floorLabel,
image: exhibit.image,
hasAudio: false,
audioUnavailableReason: '正式讲解音频尚未接入媒体仓储',
poiId: exhibit.poiId
title: exhibit.name,
subtitle: buildSubtitle(exhibit),
summary: exhibit.description || '讲解内容待正式内容库补充。',
coverImage: exhibit.image,
contentType: contentTypeFor(exhibit),
hallName: exhibit.hallName,
floorLabel: exhibit.floorLabel,
durationLabel: undefined,
languageLabel: undefined,
audioStatus: 'unavailable',
audioStatusText: '图文讲解',
badges: buildBadges(exhibit),
progress: undefined,
poiId: exhibit.poiId,
locationActionText: '查看位置'
})
export const toExplainDetailViewModel = (exhibit: MuseumExhibit): ExplainDetailViewModel => ({
export const toExplainExhibitViewModel = (exhibit: MuseumExhibit): ExplainExhibitViewModel => toExplainCardViewModel(exhibit)
export const toExplainDetailPageViewModel = (exhibit: MuseumExhibit): ExplainDetailPageViewModel => ({
id: exhibit.id,
name: exhibit.name,
artist: exhibit.artist,
year: exhibit.year,
material: exhibit.material,
size: exhibit.size,
hall: exhibit.hallName && exhibit.floorLabel
? `${exhibit.hallName} ${exhibit.floorLabel}`
: exhibit.hallName || exhibit.floorLabel,
image: exhibit.image || '/static/exhibit-placeholder.jpg',
description: exhibit.description || '该展项介绍待正式内容库补充。',
hasAudio: false,
audioUnavailableReason: '正式讲解音频尚未接入媒体仓储',
poiId: exhibit.poiId
title: exhibit.name,
subtitle: buildSubtitle(exhibit),
contentType: contentTypeFor(exhibit),
coverImages: [exhibit.image || '/static/exhibit-placeholder.jpg'].filter(Boolean),
hallName: exhibit.hallName,
floorLabel: exhibit.floorLabel,
summary: exhibit.description || '该讲解内容待正式内容库补充。',
body: exhibit.description || '该讲解内容待正式内容库补充。',
audio: {
status: 'unavailable',
unavailableReason: '正式讲解音频尚未接入媒体仓储'
},
chapters: [],
transcript: undefined,
location: exhibit.poiId
? {
poiId: exhibit.poiId,
actionText: '查看三维位置',
previewOnly: true
}
: undefined,
relatedItems: []
})
export const toExplainDetailViewModel = (exhibit: MuseumExhibit): ExplainDetailViewModel => toExplainDetailPageViewModel(exhibit)
export const toHallDetailViewModel = (hall: MuseumHall): HallDetailViewModel => ({
id: hall.id,
name: hall.name,