优化编号讲解布局与返回流程
- 将讲解编号置于标题上方并增强辨识度 - 编号详情返回后保留输入界面并清空原编号 - 增加页面栈丢失时恢复编号输入页的兜底逻辑 - 补充编号布局和返回流程回归测试 Made-with: Proma
This commit is contained in:
@@ -30,8 +30,8 @@
|
||||
<text class="stop-cover-fallback">{{ stop.name.slice(0, 1) }}</text>
|
||||
</view>
|
||||
<view class="stop-copy">
|
||||
<text class="stop-name">{{ stop.name }}</text>
|
||||
<text v-if="stop.externalCode" class="stop-code">编号:{{ stop.externalCode }}</text>
|
||||
<text class="stop-name">{{ stop.name }}</text>
|
||||
</view>
|
||||
<view class="stop-status"><text class="stop-status-text">{{ availabilityLabel(stop) }}</text></view>
|
||||
</view>
|
||||
@@ -155,6 +155,6 @@ const handleScroll = (event: Event) => { const target = event.target as HTMLElem
|
||||
.header-back { position: absolute; left: 12px; top: 10px; width: 44px; height: 44px; display: flex; align-items: center; }.header-back-icon { width: 20px; height: 20px; position: relative; } .header-back-icon::before { content: ''; position: absolute; left: 3px; top: 4px; width: 10px; height: 10px; border-left: 1.5px solid #262421; border-bottom: 1.5px solid #262421; transform: rotate(45deg); }
|
||||
.header-back-text { display: none; }.header-title { max-width: calc(100% - 152px); padding: 0 8px; font-size: 18px; line-height: 26px; font-weight: 700; color: #262421; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.explain-scroll { height: 100%; padding: 80px 16px calc(16px + env(safe-area-inset-bottom)); box-sizing: border-box; background: #f7f9f2; }.host-navigation .explain-scroll { padding-top: 16px; }
|
||||
.stop-list { display: flex; width: 100%; min-width: 0; flex-direction: column; gap: 4px; }.stop-card { width: 100%; min-width: 0; min-height: 80px; padding: 8px 0; display: flex; align-items: center; gap: 12px; box-sizing: border-box; overflow: hidden; border: 0; border-bottom: 1px solid #dbded4; border-radius: 0; background: transparent; }.stop-card:active { background: rgba(227, 235, 201, 0.4); }.stop-cover { flex: 0 0 64px; width: 64px; height: 64px; overflow: hidden; border-radius: 6px; background: #f5f5ed; }.stop-cover-fallback { font-size: 22px; line-height: 28px; color: #a9b29b; }.stop-copy { min-width: 0; display: flex; flex: 1; flex-direction: column; justify-content: center; gap: 3px; overflow: hidden; }.stop-code { display: block; overflow: hidden; font-size: 11px; line-height: 16px; font-weight: 600; color: #68725d; text-overflow: ellipsis; white-space: nowrap; }.stop-name { display: -webkit-box; max-height: 40px; font-size: 14px; line-height: 20px; font-weight: 700; color: #262421; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }.stop-name + .stop-code { margin-top: 1px; }.stop-status { flex: 0 0 44px; width: 44px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 12px; background: #e3ebc9; }.stop-status-text { font-size: 11px; line-height: 16px; color: #262421; white-space: nowrap; }
|
||||
.stop-list { display: flex; width: 100%; min-width: 0; flex-direction: column; gap: 4px; }.stop-card { width: 100%; min-width: 0; min-height: 80px; padding: 8px 0; display: flex; align-items: center; gap: 12px; box-sizing: border-box; overflow: hidden; border: 0; border-bottom: 1px solid #dbded4; border-radius: 0; background: transparent; }.stop-card:active { background: rgba(227, 235, 201, 0.4); }.stop-cover { flex: 0 0 64px; width: 64px; height: 64px; overflow: hidden; border-radius: 6px; background: #f5f5ed; }.stop-cover-fallback { font-size: 22px; line-height: 28px; color: #a9b29b; }.stop-copy { min-width: 0; display: flex; flex: 1; flex-direction: column; justify-content: center; gap: 3px; overflow: hidden; }.stop-code { display: block; overflow: hidden; font-size: 13px; line-height: 18px; font-weight: 700; color: #68725d; text-overflow: ellipsis; white-space: nowrap; }.stop-name { display: -webkit-box; max-height: 40px; font-size: 14px; line-height: 20px; font-weight: 700; color: #262421; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }.stop-code + .stop-name { margin-top: 1px; }.stop-status { flex: 0 0 44px; width: 44px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 12px; background: #e3ebc9; }.stop-status-text { font-size: 11px; line-height: 16px; color: #262421; white-space: nowrap; }
|
||||
.state-block { margin: 34px 0; padding: 22px 16px; box-sizing: border-box; text-align: center; border: 1px solid #e4e6df; border-radius: 8px; background: #fff; }.state-block.error { border-color: #e5c2c2; background: #fff7f7; }.state-title,.state-desc { display: block; }.state-title { font-size: 15px; line-height: 21px; font-weight: 700; color: #141412; }.state-desc { margin-top: 6px; font-size: 12px; line-height: 18px; color: #68725d; }.state-retry { margin-top: 12px; padding: 0 14px; font-size: 13px; line-height: 32px; color: #141412; background: #e0df00; border: 0; border-radius: 6px; }.load-more-state { min-height: 52px; padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); text-align: center; }
|
||||
</style>
|
||||
|
||||
@@ -453,10 +453,11 @@ const showCodeEntryError = (message: string) => {
|
||||
}
|
||||
|
||||
const completeCodeEntry = () => {
|
||||
closeCodeEntry()
|
||||
codeEntryValue.value = ''
|
||||
codeEntryError.value = ''
|
||||
}
|
||||
|
||||
defineExpose({ showCodeEntryError, completeCodeEntry })
|
||||
defineExpose({ openCodeEntry, showCodeEntryError, completeCodeEntry })
|
||||
|
||||
const handleBusinessUnitClick = (unitId: string) => {
|
||||
emit('businessUnitClick', unitId)
|
||||
|
||||
@@ -247,6 +247,7 @@ const detailTextLoadedByLanguage = ref<Record<AudioLanguage, boolean>>({
|
||||
})
|
||||
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||
const shouldAutoplayOnEntry = ref(false)
|
||||
const enteredByGuideCode = ref(false)
|
||||
|
||||
const isAudioLanguage = (value: unknown): value is AudioLanguage => (
|
||||
value === 'zh-CN' || value === 'yue-HK' || value === 'en-US'
|
||||
@@ -533,7 +534,9 @@ onLoad(async (options: any = {}) => {
|
||||
}
|
||||
const rawLang = Array.isArray(options.lang) ? options.lang[0] : options.lang
|
||||
const rawAutoplay = Array.isArray(options.autoplay) ? options.autoplay[0] : options.autoplay
|
||||
const rawEntry = Array.isArray(options.entry) ? options.entry[0] : options.entry
|
||||
shouldAutoplayOnEntry.value = rawAutoplay === '1' || rawAutoplay === 'true'
|
||||
enteredByGuideCode.value = rawEntry === 'code'
|
||||
const lang = typeof rawLang === 'string'
|
||||
? normalizeGuideAudioLanguage(rawLang)
|
||||
: 'zh-CN'
|
||||
@@ -935,6 +938,16 @@ const decodeRouteParam = (value: string) => {
|
||||
return decoded
|
||||
}
|
||||
|
||||
const fallbackToCodeEntry = () => {
|
||||
const url = '/pages/explain/list?codeEntry=1'
|
||||
uni.redirectTo({
|
||||
url,
|
||||
fail: () => {
|
||||
uni.reLaunch({ url })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const fallbackToExplainObjectList = () => {
|
||||
const hallId = detailEntryRequest.value?.hallId || exhibit.value.hallId
|
||||
const hallName = decodeRouteParam(detailEntryRequest.value?.hallName || exhibit.value.hallName || '讲解')
|
||||
@@ -957,15 +970,27 @@ type ExplainDetailPageStackEntry = {
|
||||
const returnToExplainObjectList = () => {
|
||||
closeDetailAudioOnExit()
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const previousPage = pages[pages.length - 2] as ExplainDetailPageStackEntry | undefined
|
||||
|
||||
if (enteredByGuideCode.value) {
|
||||
if (previousPage?.route === 'pages/explain/list' || previousPage?.route === 'pages/index/index') {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: fallbackToCodeEntry
|
||||
})
|
||||
return
|
||||
}
|
||||
fallbackToCodeEntry()
|
||||
return
|
||||
}
|
||||
|
||||
// H5 history can omit intermediate uni-app pages after a refresh or deep link.
|
||||
if (typeof window !== 'undefined' && window.location.hash.startsWith('#/')) {
|
||||
fallbackToExplainObjectList()
|
||||
return
|
||||
}
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const previousPage = pages[pages.length - 2] as ExplainDetailPageStackEntry | undefined
|
||||
|
||||
if (previousPage?.route === 'pages/explain/guide-stop-list') {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { onBackPress, onLoad } from '@dcloudio/uni-app'
|
||||
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
|
||||
import ExplainHallSelect, {
|
||||
@@ -242,10 +242,14 @@ const loadExplainHalls = async () => {
|
||||
return explainLoadPromise
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
onLoad((options: any = {}) => {
|
||||
if (shouldUseHostNavigation.value) syncHostNavigationTitle('免费讲解')
|
||||
pushExplainListHistory()
|
||||
void loadExplainHalls()
|
||||
const codeEntry = Array.isArray(options.codeEntry) ? options.codeEntry[0] : options.codeEntry
|
||||
if (codeEntry === '1' || codeEntry === 'true') {
|
||||
nextTick(() => hallSelectRef.value?.openCodeEntry())
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@@ -281,6 +285,7 @@ const handleExplainCodeSubmit = async (code: string) => {
|
||||
codeSubmitting.value = true
|
||||
try {
|
||||
const target = await explainUseCase.resolveExplainCode(code)
|
||||
// 保留编号输入页,详情返回时让用户可以继续输入下一个编号。
|
||||
hallSelectRef.value?.completeCodeEntry()
|
||||
uni.navigateTo({ url: explainCodeDetailUrl(target) })
|
||||
} catch (error) {
|
||||
|
||||
@@ -2585,6 +2585,7 @@ const handleExplainCodeSubmit = async (code: string) => {
|
||||
codeSubmitting.value = true
|
||||
try {
|
||||
const target = await explainUseCase.resolveExplainCode(code)
|
||||
// 保留编号输入页,详情返回时让用户可以继续输入下一个编号。
|
||||
explainHallSelectRef.value?.completeCodeEntry()
|
||||
uni.navigateTo({ url: explainCodeDetailUrl(target) })
|
||||
} catch (error) {
|
||||
|
||||
@@ -465,6 +465,40 @@ describe('讲解详情音频优先布局', () => {
|
||||
expect(mocks.toggleMute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('编号入口详情返回时回到原编号输入页', async () => {
|
||||
vi.stubGlobal('getCurrentPages', vi.fn(() => [
|
||||
{ route: 'pages/explain/list', options: {} },
|
||||
{ route: 'pages/exhibit/detail', options: { entry: 'code' } }
|
||||
]))
|
||||
const wrapper = mount(ExhibitDetail, {
|
||||
global: { stubs: { GuidePageFrame: GuidePageFrameStub } }
|
||||
})
|
||||
await mocks.onLoadHandler?.({ id: exhibit.id, stopId: 'stop-1', entry: 'code' })
|
||||
await flushPromises()
|
||||
|
||||
wrapper.getComponent(GuidePageFrameStub).vm.$emit('back')
|
||||
await flushPromises()
|
||||
|
||||
expect(uni.navigateBack).toHaveBeenCalledWith(expect.objectContaining({ delta: 1 }))
|
||||
expect(uni.redirectTo).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('编号入口详情丢失页面栈时回退到自动打开的编号输入页', async () => {
|
||||
const wrapper = mount(ExhibitDetail, {
|
||||
global: { stubs: { GuidePageFrame: GuidePageFrameStub } }
|
||||
})
|
||||
await mocks.onLoadHandler?.({ id: exhibit.id, stopId: 'stop-1', entry: 'code' })
|
||||
await flushPromises()
|
||||
|
||||
wrapper.getComponent(GuidePageFrameStub).vm.$emit('back')
|
||||
await flushPromises()
|
||||
|
||||
expect(uni.navigateBack).not.toHaveBeenCalled()
|
||||
expect(uni.redirectTo).toHaveBeenCalledWith(expect.objectContaining({
|
||||
url: '/pages/explain/list?codeEntry=1'
|
||||
}))
|
||||
})
|
||||
|
||||
it('上一页是讲解对象列表时使用页面栈返回', async () => {
|
||||
vi.stubGlobal('getCurrentPages', vi.fn(() => [
|
||||
{ route: 'pages/explain/guide-stop-list', options: {} },
|
||||
|
||||
@@ -24,6 +24,9 @@ describe('ExplainGuideStopCatalog', () => {
|
||||
expect(wrapper.text()).toContain('图文')
|
||||
expect(wrapper.get('.stop-code').text()).toBe('编号:0231')
|
||||
expect(wrapper.findAll('.stop-code')).toHaveLength(1)
|
||||
const readyCopy = wrapper.get('.stop-card .stop-copy')
|
||||
expect(readyCopy.element.firstElementChild?.classList.contains('stop-code')).toBe(true)
|
||||
expect(readyCopy.element.lastElementChild?.classList.contains('stop-name')).toBe(true)
|
||||
expect(wrapper.text()).not.toContain('暂无内容')
|
||||
expect(wrapper.find('.hall-arrow').exists()).toBe(false)
|
||||
expect(wrapper.find('.stop-name').classes()).toContain('stop-name')
|
||||
|
||||
@@ -95,6 +95,11 @@ describe('讲解展厅选择列表', () => {
|
||||
expect(wrapper.emitted('codeSubmit')).toEqual([['0231']])
|
||||
expect(wrapper.find('.code-entry-sheet').exists()).toBe(true)
|
||||
|
||||
wrapper.vm.completeCodeEntry()
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.find('.code-entry-sheet').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.code-digit-value').map((item) => item.text())).toEqual(['', '', '', ''])
|
||||
|
||||
await wrapper.setProps({ codeSubmitting: true })
|
||||
expect(wrapper.get('.code-entry-native-input').attributes('disabled')).toBeDefined()
|
||||
expect(wrapper.get('.code-entry-action.primary').text()).toBe('正在查询')
|
||||
|
||||
Reference in New Issue
Block a user