接入编号讲解后端查询
Some checks failed
CI / verify (push) Has been cancelled

- 使用真实讲解编号解析接口替换本地演示映射
- 成功后复用现有详情页并自动播放讲解
- 增加查询状态、业务错误提示和相关测试

Made-with: Proma
This commit is contained in:
lyf
2026-08-25 18:00:52 +08:00
parent 00585a7b48
commit 130f155bdf
13 changed files with 490 additions and 13 deletions

View File

@@ -68,6 +68,58 @@ describe('DefaultAudioPlayInfoRepository unified stop detail', () => {
expect(request).toHaveBeenCalledTimes(2)
})
it('resolves a museum code through info-by-code and preserves the code string', async () => {
const requestUrls: string[] = []
vi.stubGlobal('uni', {
request: ({ url, success }: { url: string, success: (response: unknown) => void }) => {
requestUrls.push(url)
success({
statusCode: 200,
data: {
code: 0,
data: {
available: true,
targetType: 'STOP',
targetId: '865546652298080256',
resolvedStopId: '865546652298080256',
lang: 'zh-CN',
title: '手持折射望远镜',
imageStatus: 'READY',
playTargetType: 'STOP',
playTargetId: '865546652298080256',
hasAudio: true,
supportedLanguages: ['zh-CN', 'yue-HK'],
audioStatus: 'READY'
}
}
})
}
})
const repository = new DefaultAudioPlayInfoRepository()
const result = await repository.getStopInfoByCode('0101', 'zh-CN')
expect(requestUrls).toEqual([
'/app-api/gis/guide/stop/info-by-code?code=0101&lang=zh-CN'
])
expect(result.resolvedStopId).toBe('865546652298080256')
expect(result.title).toBe('手持折射望远镜')
})
it('preserves info-by-code business error codes', async () => {
vi.stubGlobal('uni', {
request: ({ success }: { success: (response: unknown) => void }) => {
success({ statusCode: 200, data: { code: 1020007001, msg: '馆方讲解点编号不存在', data: null } })
}
})
const repository = new DefaultAudioPlayInfoRepository()
await expect(repository.getStopInfoByCode('9999')).rejects.toMatchObject({
message: '馆方讲解点编号不存在',
code: 1020007001
})
})
it('surfaces a business missing-detail response as a detail error', async () => {
vi.stubGlobal('uni', {
request: ({ success }: { success: (response: unknown) => void }) => {

View File

@@ -256,6 +256,26 @@ describe('讲解详情音频优先布局', () => {
}), expect.objectContaining({ displayMode: 'mini' }))
})
it('编号入口加载详情后自动发起一次播放', async () => {
mount(ExhibitDetail, {
global: { stubs: { GuidePageFrame: GuidePageFrameStub } }
})
await mocks.onLoadHandler?.({
id: exhibit.id,
stopId: 'stop-1',
autoplay: '1',
entry: 'code'
})
await flushPromises()
expect(mocks.play).toHaveBeenCalledTimes(1)
expect(mocks.play).toHaveBeenCalledWith(
expect.objectContaining({ audioUrl: exhibit.audioUrl }),
expect.objectContaining({ displayMode: 'mini' })
)
})
it('加载中使用同一高度底栏和不确定进度', async () => {
audioState.currentAudio.value = { id: 'audio-1' }
audioState.currentSource.value = { targetType: 'STOP', targetId: 'stop-1', lang: 'en-US' }

View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from 'vitest'
import {
explainCodeDetailUrl,
explainCodeErrorMessage,
normalizeExplainCode
} from '@/utils/explainCodeEntry'
describe('讲解编号入口', () => {
it('清理首尾空格但保留编号前导零', () => {
expect(normalizeExplainCode(' 0231 ')).toBe('0231')
})
it.each([
[1020007000, '请输入有效的 4 位讲解编号'],
[1020007001, '未找到该讲解编号,请确认后重试'],
[1020007002, '该编号的讲解内容暂不可用'],
[429, '查询过于频繁,请稍后再试']
])('将业务错误码 %s 转换为明确提示', (code, message) => {
expect(explainCodeErrorMessage({ code })).toBe(message)
})
it('未知错误使用网络重试提示', () => {
expect(explainCodeErrorMessage(new Error('offline'))).toBe('编号查询失败,请检查网络后重试')
})
it('详情路由携带讲解点和自动播放意图', () => {
const url = explainCodeDetailUrl({ code: '1001', stopId: '865546652298080256' })
expect(url.split('?')[0]).toBe('/pages/exhibit/detail')
expect(url).toContain('stopId=865546652298080256')
expect(url).toContain('autoplay=1')
expect(url).toContain('entry=code')
expect(url).toContain('guideCode=1001')
})
})

View File

@@ -67,6 +67,51 @@ describe('讲解展厅选择列表', () => {
expect(wrapper.emitted('hallClick')).toEqual([['hall-evolution']])
})
it('展厅阶段可输入讲解编号并保留前导零', async () => {
const wrapper = mount(ExplainHallSelect, {
props: { stage: 'hall' }
})
expect(wrapper.get('.code-entry-title').text()).toBe('编号讲解')
expect(wrapper.get('.hall-section-title').text()).toBe('按展厅选择')
expect(wrapper.find('.code-entry-arrow').exists()).toBe(false)
await wrapper.get('.code-entry-button').trigger('tap')
expect(wrapper.get('.header-title').text()).toBe('编号讲解')
expect(wrapper.find('.code-entry-close').exists()).toBe(false)
expect(wrapper.get('.code-entry-sheet-title').text()).toBe('输入讲解编号')
expect(wrapper.findAll('.code-digit-cell')).toHaveLength(4)
expect(wrapper.text()).toContain('请输入展品标签上的四位编号')
await wrapper.get('.code-entry-native-input').trigger('input', { detail: { value: '02a' } })
expect(wrapper.get('.code-entry-action.primary').attributes('disabled')).toBeDefined()
await wrapper.get('.code-entry-native-input').trigger('confirm')
expect(wrapper.get('.code-entry-error').text()).toBe('请输入完整的 4 位讲解编号')
await wrapper.get('.code-entry-native-input').trigger('input', { detail: { value: '0231' } })
expect(wrapper.findAll('.code-digit-value').map((item) => item.text())).toEqual(['0', '2', '3', '1'])
await wrapper.get('.code-entry-action.primary').trigger('tap')
expect(wrapper.emitted('codeSubmit')).toEqual([['0231']])
expect(wrapper.find('.code-entry-sheet').exists()).toBe(true)
await wrapper.setProps({ codeSubmitting: true })
expect(wrapper.get('.code-entry-native-input').attributes('disabled')).toBeDefined()
expect(wrapper.get('.code-entry-action.primary').text()).toBe('正在查询')
wrapper.vm.showCodeEntryError('未找到该讲解编号,请确认后重试')
await wrapper.vm.$nextTick()
expect(wrapper.get('.code-entry-error').text()).toBe('未找到该讲解编号,请确认后重试')
await wrapper.get('.header-back').trigger('tap')
expect(wrapper.find('.code-entry-sheet').exists()).toBe(false)
expect(wrapper.get('.header-title').text()).toBe('免费讲解')
expect(wrapper.emitted('back')).toBeUndefined()
await wrapper.get('.header-back').trigger('tap')
expect(wrapper.emitted('back')).toEqual([[]])
})
it('展厅阶段使用完整展厅卡片资源', () => {
const wrapper = mount(ExplainHallSelect, {
props: {
@@ -84,9 +129,9 @@ describe('讲解展厅选择列表', () => {
const card = wrapper.get('.hall-overview-card')
expect(wrapper.get('.header-title').text()).toBe('免费讲解')
expect(card.attributes('style')).toContain('--hall-card-color: #d9b06c')
expect(card.attributes('style')).toContain('--hall-card-color: #bb965a')
expect(card.get('.hall-full-card-image').attributes('src'))
.toBe('/static/icons/halls/custom-cards/dinosaur.png')
.toBe('/static/icons/halls/portal-icons/dinosaur.png')
expect(card.find('.hall-overview-copy').exists()).toBe(false)
expect(card.find('.hall-go-entry').exists()).toBe(false)
})

View File

@@ -33,11 +33,28 @@ const createUseCase = (detail: GuideStopDetail) => {
getExhibitById: vi.fn().mockResolvedValue(null),
listExplainExhibits: vi.fn().mockResolvedValue([])
} as unknown as ExplainRepository
const audio = { getStopDetail: vi.fn().mockResolvedValue(detail) } as unknown as AudioPlayInfoRepository
const audio = {
getStopDetail: vi.fn().mockResolvedValue(detail),
getStopInfoByCode: vi.fn().mockResolvedValue({
available: true,
resolvedStopId: 'resolved-stop-1',
playTargetId: 'resolved-stop-1',
targetId: 'resolved-stop-1'
})
} as unknown as AudioPlayInfoRepository
return { useCase: new ExplainUseCase(explain, audio), explain, audio }
}
describe('ExplainUseCase unified stop detail', () => {
it('resolves the museum code to the backend stop ID', async () => {
const { useCase, audio } = createUseCase(createDetail())
const result = await useCase.resolveExplainCode('1001', 'zh-CN')
expect(audio.getStopInfoByCode).toHaveBeenCalledWith('1001', 'zh-CN')
expect(result).toEqual({ code: '1001', stopId: 'resolved-stop-1' })
})
it('uses stopId once in remote mode without requesting a catalog fallback', async () => {
const originalMode = dataSourceConfig.explainContentMode
Object.assign(dataSourceConfig, { explainContentMode: 'remote' })