修复讲解详情接口并展示展厅统计
This commit is contained in:
@@ -47,7 +47,7 @@
|
|||||||
<view class="floor-badge">
|
<view class="floor-badge">
|
||||||
<text class="floor-badge-text">{{ hall.floorLabel || '楼层待补充' }}</text>
|
<text class="floor-badge-text">{{ hall.floorLabel || '楼层待补充' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="hall-meta-text">{{ hall.explainCount > 0 ? `${hall.explainCount} 条讲解` : '暂无讲解' }}</text>
|
<text class="hall-meta-text">{{ hallMetaText(hall) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -130,6 +130,8 @@ export interface ExplainHallSelectItem {
|
|||||||
floorLabel?: string
|
floorLabel?: string
|
||||||
image?: string
|
image?: string
|
||||||
explainCount: number
|
explainCount: number
|
||||||
|
businessUnitCount?: number
|
||||||
|
guideStopCount?: number
|
||||||
searchText: string
|
searchText: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +229,12 @@ const hallIconUrl = (hall: ExplainHallSelectItem) => {
|
|||||||
|
|
||||||
const hallIconText = (name: string) => name.trim().slice(0, 1) || '讲'
|
const hallIconText = (name: string) => name.trim().slice(0, 1) || '讲'
|
||||||
|
|
||||||
|
const hallMetaText = (hall: ExplainHallSelectItem) => (
|
||||||
|
typeof hall.businessUnitCount === 'number' || typeof hall.guideStopCount === 'number'
|
||||||
|
? `${hall.businessUnitCount || 0} 个业务单元 · ${hall.guideStopCount || 0} 个讲解点`
|
||||||
|
: hall.explainCount > 0 ? `${hall.explainCount} 个展项` : '展厅'
|
||||||
|
)
|
||||||
|
|
||||||
const handleHallClick = (hallId: string) => {
|
const handleHallClick = (hallId: string) => {
|
||||||
emit('hallClick', hallId)
|
emit('hallClick', hallId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,17 +294,14 @@ export const toBackendGuideStop = (source: BackendGuideStop): ExplainGuideStop |
|
|||||||
const id = stringifyId(source.id)
|
const id = stringifyId(source.id)
|
||||||
if (!id) return null
|
if (!id) return null
|
||||||
|
|
||||||
const targetType = normalizeAudioTargetType(source.targetType) || 'STOP'
|
|
||||||
const targetId = stringifyId(source.targetId) || id
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: firstText(source.name, `讲解点${id}`),
|
name: firstText(source.name, `讲解点${id}`),
|
||||||
hallId: stringifyId(source.hallId) || undefined,
|
hallId: stringifyId(source.hallId) || undefined,
|
||||||
hallName: firstText(source.hallName) || undefined,
|
hallName: firstText(source.hallName) || undefined,
|
||||||
floorId: stringifyId(source.floorId) || undefined,
|
floorId: stringifyId(source.floorId) || undefined,
|
||||||
targetType,
|
targetType: 'STOP',
|
||||||
targetId,
|
targetId: id,
|
||||||
coverImageUrl: normalizeSameOriginPublicUrl(firstText(source.coverImageUrl)) || undefined,
|
coverImageUrl: normalizeSameOriginPublicUrl(firstText(source.coverImageUrl)) || undefined,
|
||||||
description: firstText(source.description) || undefined,
|
description: firstText(source.description) || undefined,
|
||||||
hasAudio: source.hasAudio === true,
|
hasAudio: source.hasAudio === true,
|
||||||
|
|||||||
15
src/domain/explainDetailTarget.ts
Normal file
15
src/domain/explainDetailTarget.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type {
|
||||||
|
ExplainGuideStop
|
||||||
|
} from './museum'
|
||||||
|
|
||||||
|
export interface ExplainDetailTarget {
|
||||||
|
targetType: 'STOP'
|
||||||
|
targetId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const normalizeExplainDetailTargetFromGuideStop = (
|
||||||
|
guideStop: Pick<ExplainGuideStop, 'id'>
|
||||||
|
): ExplainDetailTarget => ({
|
||||||
|
targetType: 'STOP',
|
||||||
|
targetId: String(guideStop.id)
|
||||||
|
})
|
||||||
@@ -185,11 +185,15 @@ onLoad(async (options: any = {}) => {
|
|||||||
if (!exhibitId) return
|
if (!exhibitId) return
|
||||||
|
|
||||||
const rawTargetType = Array.isArray(options.targetType) ? options.targetType[0] : options.targetType
|
const rawTargetType = Array.isArray(options.targetType) ? options.targetType[0] : options.targetType
|
||||||
const targetType: AudioPlayTargetType | undefined = rawTargetType === 'ITEM' || rawTargetType === 'STOP'
|
const targetType: AudioPlayTargetType | undefined = rawTargetType === 'ITEM'
|
||||||
? rawTargetType
|
? 'ITEM'
|
||||||
: undefined
|
: rawTargetType === 'STOP' || rawTargetType === 'GUIDE_STOP'
|
||||||
|
? 'STOP'
|
||||||
|
: undefined
|
||||||
const rawTargetId = Array.isArray(options.targetId) ? options.targetId[0] : options.targetId
|
const rawTargetId = Array.isArray(options.targetId) ? options.targetId[0] : options.targetId
|
||||||
const targetId = rawTargetId ? String(rawTargetId) : undefined
|
const targetId = rawTargetType === 'GUIDE_STOP'
|
||||||
|
? String(exhibitId)
|
||||||
|
: rawTargetId ? String(rawTargetId) : undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exhibitData = await explainUseCase.enterExplainDetail({
|
const exhibitData = await explainUseCase.enterExplainDetail({
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ import ExplainHallSelect, {
|
|||||||
import {
|
import {
|
||||||
explainUseCase
|
explainUseCase
|
||||||
} from '@/usecases/explainUseCase'
|
} from '@/usecases/explainUseCase'
|
||||||
|
import {
|
||||||
|
normalizeExplainDetailTargetFromGuideStop
|
||||||
|
} from '@/domain/explainDetailTarget'
|
||||||
import type {
|
import type {
|
||||||
ExplainBusinessUnit,
|
ExplainBusinessUnit,
|
||||||
MuseumHall
|
MuseumHall
|
||||||
@@ -63,13 +66,18 @@ let explainLoadPromise: Promise<void> | null = null
|
|||||||
|
|
||||||
const normalizeExplainKeyword = (value?: string) => (value || '').trim().toLowerCase()
|
const normalizeExplainKeyword = (value?: string) => (value || '').trim().toLowerCase()
|
||||||
|
|
||||||
const buildExplainHallItems = (halls: MuseumHall[]): ExplainHallSelectItem[] => (
|
const buildExplainHallItems = (
|
||||||
|
halls: MuseumHall[],
|
||||||
|
summaries: Awaited<ReturnType<typeof explainUseCase.loadExplainHallSummaries>> = {}
|
||||||
|
): ExplainHallSelectItem[] => (
|
||||||
halls.map((hall) => ({
|
halls.map((hall) => ({
|
||||||
id: hall.id,
|
id: hall.id,
|
||||||
name: hall.name,
|
name: hall.name,
|
||||||
floorLabel: hall.floorLabel,
|
floorLabel: hall.floorLabel,
|
||||||
image: hall.image,
|
image: hall.image,
|
||||||
explainCount: hall.exhibitCount || 0,
|
explainCount: hall.exhibitCount || 0,
|
||||||
|
businessUnitCount: summaries[hall.id]?.businessUnitCount,
|
||||||
|
guideStopCount: summaries[hall.id]?.guideStopCount,
|
||||||
searchText: normalizeExplainKeyword([
|
searchText: normalizeExplainKeyword([
|
||||||
hall.name,
|
hall.name,
|
||||||
hall.floorLabel,
|
hall.floorLabel,
|
||||||
@@ -115,7 +123,8 @@ const loadExplainHalls = async () => {
|
|||||||
explainLoadPromise = (async () => {
|
explainLoadPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
const halls = await explainUseCase.loadExplainHalls()
|
const halls = await explainUseCase.loadExplainHalls()
|
||||||
explainHallItems.value = buildExplainHallItems(halls)
|
const summaries = await explainUseCase.loadExplainHallSummaries(halls.map((hall) => hall.id))
|
||||||
|
explainHallItems.value = buildExplainHallItems(halls, summaries)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载讲解展厅失败:', error)
|
console.error('加载讲解展厅失败:', error)
|
||||||
explainError.value = '讲解展厅加载失败,请稍后重试'
|
explainError.value = '讲解展厅加载失败,请稍后重试'
|
||||||
@@ -178,11 +187,12 @@ const handleExplainBusinessUnitClick = (unitId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleExplainGuideStopClick = (stopId: string) => {
|
const handleExplainGuideStopClick = (stopId: string) => {
|
||||||
|
const target = normalizeExplainDetailTargetFromGuideStop({ id: stopId })
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
id: stopId,
|
id: target.targetId,
|
||||||
tab: 'explain',
|
tab: 'explain',
|
||||||
targetType: 'STOP',
|
targetType: target.targetType,
|
||||||
targetId: stopId
|
targetId: target.targetId
|
||||||
})
|
})
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/exhibit/detail?${params.toString()}`
|
url: `/pages/exhibit/detail?${params.toString()}`
|
||||||
|
|||||||
@@ -250,6 +250,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
explainUseCase
|
explainUseCase
|
||||||
} from '@/usecases/explainUseCase'
|
} from '@/usecases/explainUseCase'
|
||||||
|
import {
|
||||||
|
normalizeExplainDetailTargetFromGuideStop
|
||||||
|
} from '@/domain/explainDetailTarget'
|
||||||
import type {
|
import type {
|
||||||
GuideRenderPoi
|
GuideRenderPoi
|
||||||
} from '@/domain/guideModel'
|
} from '@/domain/guideModel'
|
||||||
@@ -604,7 +607,8 @@ const loadExplainHalls = async () => {
|
|||||||
explainLoadPromise = (async () => {
|
explainLoadPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
const halls = await explainUseCase.loadExplainHalls()
|
const halls = await explainUseCase.loadExplainHalls()
|
||||||
explainHallItems.value = buildExplainHallItems(halls)
|
const summaries = await explainUseCase.loadExplainHallSummaries(halls.map((hall) => hall.id))
|
||||||
|
explainHallItems.value = buildExplainHallItems(halls, summaries)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载讲解内容失败:', error)
|
console.error('加载讲解内容失败:', error)
|
||||||
explainError.value = '讲解内容加载失败,请稍后重试'
|
explainError.value = '讲解内容加载失败,请稍后重试'
|
||||||
@@ -621,7 +625,8 @@ const loadExplainHalls = async () => {
|
|||||||
const normalizeExplainKeyword = (value?: string) => (value || '').trim().toLowerCase()
|
const normalizeExplainKeyword = (value?: string) => (value || '').trim().toLowerCase()
|
||||||
|
|
||||||
const buildExplainHallItems = (
|
const buildExplainHallItems = (
|
||||||
halls: Awaited<ReturnType<typeof explainUseCase.loadExplainHalls>>
|
halls: Awaited<ReturnType<typeof explainUseCase.loadExplainHalls>>,
|
||||||
|
summaries: Awaited<ReturnType<typeof explainUseCase.loadExplainHallSummaries>> = {}
|
||||||
): ExplainHallSelectItem[] => {
|
): ExplainHallSelectItem[] => {
|
||||||
return halls.map((hall) => {
|
return halls.map((hall) => {
|
||||||
const keywordParts = [
|
const keywordParts = [
|
||||||
@@ -637,6 +642,8 @@ const buildExplainHallItems = (
|
|||||||
floorLabel: hall.floorLabel,
|
floorLabel: hall.floorLabel,
|
||||||
image: hall.image,
|
image: hall.image,
|
||||||
explainCount: hall.exhibitCount || 0,
|
explainCount: hall.exhibitCount || 0,
|
||||||
|
businessUnitCount: summaries[hall.id]?.businessUnitCount,
|
||||||
|
guideStopCount: summaries[hall.id]?.guideStopCount,
|
||||||
searchText: normalizeExplainKeyword(keywordParts.join(' '))
|
searchText: normalizeExplainKeyword(keywordParts.join(' '))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1251,11 +1258,12 @@ const handleExplainBusinessUnitClick = (unitId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleExplainGuideStopClick = (stopId: string) => {
|
const handleExplainGuideStopClick = (stopId: string) => {
|
||||||
|
const target = normalizeExplainDetailTargetFromGuideStop({ id: stopId })
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
id: stopId,
|
id: target.targetId,
|
||||||
tab: 'explain',
|
tab: 'explain',
|
||||||
targetType: 'STOP',
|
targetType: target.targetType,
|
||||||
targetId: stopId
|
targetId: target.targetId
|
||||||
})
|
})
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/exhibit/detail?${params.toString()}`
|
url: `/pages/exhibit/detail?${params.toString()}`
|
||||||
|
|||||||
@@ -131,9 +131,16 @@ const requestJson = <T>(url: string): Promise<T> => new Promise((resolve, reject
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let guideAudioApiUnavailable = false
|
const unavailableAudioApiEndpoints = new Set<'stopInfo' | 'playInfo' | 'textInfo'>()
|
||||||
|
|
||||||
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '')
|
const normalizeBaseUrl = (baseUrl: string) => {
|
||||||
|
const trimmed = baseUrl.trim().replace(/\/+$/, '')
|
||||||
|
if (!trimmed || /^https?:\/\//i.test(trimmed) || trimmed.startsWith('/')) {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/${trimmed}`
|
||||||
|
}
|
||||||
|
|
||||||
const resolveAudioApiBaseUrl = () => {
|
const resolveAudioApiBaseUrl = () => {
|
||||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.audioApiBaseUrl)
|
const baseUrl = normalizeBaseUrl(dataSourceConfig.audioApiBaseUrl)
|
||||||
@@ -160,9 +167,12 @@ const isMissingRouteResponse = (response: { code: number; msg?: string }) => (
|
|||||||
response.code === 404 && /请求地址不存在/.test(response.msg || '')
|
response.code === 404 && /请求地址不存在/.test(response.msg || '')
|
||||||
)
|
)
|
||||||
|
|
||||||
const markGuideAudioApiUnavailable = (response: { code: number; msg?: string }) => {
|
const markGuideAudioApiUnavailable = (
|
||||||
|
endpoint: 'stopInfo' | 'playInfo' | 'textInfo',
|
||||||
|
response: { code: number; msg?: string }
|
||||||
|
) => {
|
||||||
if (isMissingRouteResponse(response)) {
|
if (isMissingRouteResponse(response)) {
|
||||||
guideAudioApiUnavailable = true
|
unavailableAudioApiEndpoints.add(endpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +186,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
private readonly textCache = new Map<string, AudioTextInfo>()
|
private readonly textCache = new Map<string, AudioTextInfo>()
|
||||||
|
|
||||||
async getStopInfo(request: GuideStopInfoRequest): Promise<GuideStopInfo> {
|
async getStopInfo(request: GuideStopInfoRequest): Promise<GuideStopInfo> {
|
||||||
if (guideAudioApiUnavailable) {
|
if (unavailableAudioApiEndpoints.has('stopInfo')) {
|
||||||
throw new Error('讲解展示信息接口暂不可用')
|
throw new Error('讲解展示信息接口暂不可用')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +211,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
const response = await requestJson<GuideStopInfoResponse>(url)
|
const response = await requestJson<GuideStopInfoResponse>(url)
|
||||||
|
|
||||||
if (response.code !== 0 || !response.data) {
|
if (response.code !== 0 || !response.data) {
|
||||||
markGuideAudioApiUnavailable(response)
|
markGuideAudioApiUnavailable('stopInfo', response)
|
||||||
throw new Error(response.msg || '讲解点展示信息加载失败')
|
throw new Error(response.msg || '讲解点展示信息加载失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +227,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||||
}
|
}
|
||||||
const key = audioKey(normalizedRequest)
|
const key = audioKey(normalizedRequest)
|
||||||
if (guideAudioApiUnavailable) {
|
if (unavailableAudioApiEndpoints.has('playInfo')) {
|
||||||
return {
|
return {
|
||||||
playable: false,
|
playable: false,
|
||||||
targetType: normalizedRequest.targetType,
|
targetType: normalizedRequest.targetType,
|
||||||
@@ -242,7 +252,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
const response = await requestJson<AudioPlayInfoResponse>(url)
|
const response = await requestJson<AudioPlayInfoResponse>(url)
|
||||||
|
|
||||||
if (response.code !== 0 || !response.data) {
|
if (response.code !== 0 || !response.data) {
|
||||||
markGuideAudioApiUnavailable(response)
|
markGuideAudioApiUnavailable('playInfo', response)
|
||||||
throw new Error(response.msg || '语音播放解析失败')
|
throw new Error(response.msg || '语音播放解析失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +272,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||||
}
|
}
|
||||||
const key = audioTextKey(normalizedRequest)
|
const key = audioTextKey(normalizedRequest)
|
||||||
if (guideAudioApiUnavailable) {
|
if (unavailableAudioApiEndpoints.has('textInfo')) {
|
||||||
return {
|
return {
|
||||||
available: false,
|
available: false,
|
||||||
targetType: normalizedRequest.targetType,
|
targetType: normalizedRequest.targetType,
|
||||||
@@ -287,7 +297,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
const response = await requestJson<AudioTextInfoResponse>(url)
|
const response = await requestJson<AudioTextInfoResponse>(url)
|
||||||
|
|
||||||
if (response.code !== 0 || !response.data) {
|
if (response.code !== 0 || !response.data) {
|
||||||
markGuideAudioApiUnavailable(response)
|
markGuideAudioApiUnavailable('textInfo', response)
|
||||||
throw new Error(response.msg || '讲解词正文解析失败')
|
throw new Error(response.msg || '讲解词正文解析失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +313,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
|||||||
this.stopInfoCache.clear()
|
this.stopInfoCache.clear()
|
||||||
this.cache.clear()
|
this.cache.clear()
|
||||||
this.textCache.clear()
|
this.textCache.clear()
|
||||||
guideAudioApiUnavailable = false
|
unavailableAudioApiEndpoints.clear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
dataSourceConfig
|
dataSourceConfig
|
||||||
} from '@/config/dataSource'
|
} from '@/config/dataSource'
|
||||||
|
import {
|
||||||
|
normalizeExplainDetailTargetFromGuideStop
|
||||||
|
} from '@/domain/explainDetailTarget'
|
||||||
|
|
||||||
export interface ExplainAudioSelection {
|
export interface ExplainAudioSelection {
|
||||||
exhibit: MuseumExhibit
|
exhibit: MuseumExhibit
|
||||||
@@ -63,6 +66,12 @@ export interface ExhibitDetailAudioOptions {
|
|||||||
includeText?: boolean
|
includeText?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExplainHallSummary {
|
||||||
|
hallId: string
|
||||||
|
businessUnitCount: number
|
||||||
|
guideStopCount: number
|
||||||
|
}
|
||||||
|
|
||||||
export class ExplainUseCase {
|
export class ExplainUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly explain: ExplainRepository = explainRepository,
|
private readonly explain: ExplainRepository = explainRepository,
|
||||||
@@ -355,14 +364,17 @@ export class ExplainUseCase {
|
|||||||
|
|
||||||
async enterExplainDetail(request: ExplainDetailEntryRequest) {
|
async enterExplainDetail(request: ExplainDetailEntryRequest) {
|
||||||
const entryTarget = await this.resolveDetailEntryTarget(request)
|
const entryTarget = await this.resolveDetailEntryTarget(request)
|
||||||
|
const shouldLoadFallbackExhibit = entryTarget.targetType === 'ITEM'
|
||||||
const [stopInfoResult, fallbackExhibit] = await Promise.all([
|
const [stopInfoResult, fallbackExhibit] = await Promise.all([
|
||||||
this.audioPlayInfo.getStopInfo(entryTarget)
|
this.audioPlayInfo.getStopInfo(entryTarget)
|
||||||
.then((stopInfo) => ({ stopInfo, error: null }))
|
.then((stopInfo) => ({ stopInfo, error: null }))
|
||||||
.catch((error) => ({ stopInfo: null, error })),
|
.catch((error) => ({ stopInfo: null, error })),
|
||||||
this.explain.getExhibitById(request.exhibitId)
|
shouldLoadFallbackExhibit
|
||||||
.catch(() => this.explain.listExplainExhibits()
|
? this.explain.getExhibitById(request.exhibitId)
|
||||||
.then((items) => items.find((item) => item.id === request.exhibitId) || null)
|
.catch(() => this.explain.listExplainExhibits()
|
||||||
.catch(() => null))
|
.then((items) => items.find((item) => item.id === request.exhibitId) || null)
|
||||||
|
.catch(() => null))
|
||||||
|
: Promise.resolve(null)
|
||||||
])
|
])
|
||||||
|
|
||||||
if (stopInfoResult.stopInfo) {
|
if (stopInfoResult.stopInfo) {
|
||||||
@@ -419,6 +431,30 @@ export class ExplainUseCase {
|
|||||||
return this.listHalls()
|
return this.listHalls()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadExplainHallSummaries(hallIds: string[]): Promise<Record<string, ExplainHallSummary>> {
|
||||||
|
const uniqueHallIds = Array.from(new Set(hallIds.map((id) => id.trim()).filter(Boolean)))
|
||||||
|
const entries = await Promise.all(uniqueHallIds.map(async (hallId) => {
|
||||||
|
try {
|
||||||
|
const units = await this.loadTemporaryBusinessUnitsByHall(hallId)
|
||||||
|
const guideStopCount = units.reduce((total, unit) => total + unit.guideStopCount, 0)
|
||||||
|
return [hallId, {
|
||||||
|
hallId,
|
||||||
|
businessUnitCount: units.length,
|
||||||
|
guideStopCount
|
||||||
|
}] as const
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('讲解展厅统计加载失败:', hallId, error)
|
||||||
|
return [hallId, {
|
||||||
|
hallId,
|
||||||
|
businessUnitCount: 0,
|
||||||
|
guideStopCount: 0
|
||||||
|
}] as const
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return Object.fromEntries(entries)
|
||||||
|
}
|
||||||
|
|
||||||
getHallById(id: string) {
|
getHallById(id: string) {
|
||||||
return this.explain.getHallById(id)
|
return this.explain.getHallById(id)
|
||||||
}
|
}
|
||||||
@@ -447,10 +483,11 @@ export class ExplainUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openGuideStopDetail(stopId: string) {
|
openGuideStopDetail(stopId: string) {
|
||||||
|
const target = normalizeExplainDetailTargetFromGuideStop({ id: stopId })
|
||||||
return this.enterExplainDetail({
|
return this.enterExplainDetail({
|
||||||
exhibitId: stopId,
|
exhibitId: target.targetId,
|
||||||
targetType: 'STOP',
|
targetType: target.targetType,
|
||||||
targetId: stopId
|
targetId: target.targetId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user