fix: 复习详情返回按钮改为返回总览

This commit is contained in:
2026-09-13 23:12:23 +08:00
parent 66c3c68394
commit 132cb706d7
4 changed files with 89 additions and 7 deletions
+14
View File
@@ -82,4 +82,18 @@ test.describe('Review page', () => {
await expect(page.getByRole('button', { name: '重新加载' })).toBeVisible();
await expect(page.getByText('暂无复习任务')).toHaveCount(0);
});
test('详情页返回按钮为「返回总览」并回到复习总览', async ({ page }) => {
await page.route('**/api/review/task/**', (route) =>
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: [] }) }),
);
await page.locator('.task-card').first().click();
await expect(page).toHaveURL(/\/review\/detail\/task\/T001/, { timeout: 5000 });
const backBtn = page.locator('.head .back-btn');
await expect(backBtn).toHaveText('← 返回总览');
await backBtn.click();
await expect(page).toHaveURL(/\/review$/, { timeout: 5000 });
});
});
+51 -2
View File
@@ -22,6 +22,13 @@ async function mountAt(path: string) {
})
}
// 真实挂载:shallow 会存根化 el-button 且不渲染插槽,无法断言按钮文案与点击行为
async function mountRealAt(path: string) {
await router.push(path)
await router.isReady()
return mount(MyHead, { global: { plugins: [ElementPlus, router] } })
}
describe('MyHead.vue', () => {
beforeEach(async () => {
vi.clearAllMocks()
@@ -30,6 +37,49 @@ describe('MyHead.vue', () => {
await router.isReady()
})
it('默认返回首页,复习详情按路由 meta 返回总览', async () => {
localStorage.setItem('isLoggedIn', 'true')
const studyWrapper = await mountRealAt('/study')
expect(studyWrapper.find('.back-btn').text()).toBe('← 返回首页')
await studyWrapper.find('.back-btn').trigger('click')
await vi.waitFor(() => expect(router.currentRoute.value.path).toBe('/welcome'))
studyWrapper.unmount()
const taskDetailWrapper = await mountRealAt('/review/detail/task/T1')
expect(taskDetailWrapper.find('.back-btn').text()).toBe('← 返回总览')
await taskDetailWrapper.find('.back-btn').trigger('click')
await vi.waitFor(() => expect(router.currentRoute.value.path).toBe('/review'))
taskDetailWrapper.unmount()
})
it('多入口详情页优先返回来路,无历史时兜底到上级', async () => {
localStorage.setItem('isLoggedIn', 'true')
const backSpy = vi.spyOn(router, 'back').mockImplementation(() => {})
// 从回忆复习页进入:存在历史,回退到实际来路
await mountRealAt('/review/recall/T1')
const fromRecall = await mountRealAt('/review/detail/report/9')
expect(fromRecall.find('.back-btn').text()).toBe('← 返回上一页')
await fromRecall.find('.back-btn').trigger('click')
expect(backSpy).toHaveBeenCalledTimes(1)
fromRecall.unmount()
// 直接深链或刷新进入:没有历史,回退到该页声明的上级
window.history.replaceState(
{ back: null, current: '/review/detail/report/9', forward: null, position: 0 },
'',
)
const deepLink = mount(MyHead, { global: { plugins: [ElementPlus, router] } })
expect(deepLink.find('.back-btn').text()).toBe('← 返回上一页')
await deepLink.find('.back-btn').trigger('click')
expect(backSpy).toHaveBeenCalledTimes(1)
await vi.waitFor(() => expect(router.currentRoute.value.path).toBe('/review'))
deepLink.unmount()
backSpy.mockRestore()
})
it('derives route-based computed properties from real route', async () => {
const loginWrapper = await mountAt('/login')
expect(loginWrapper.vm.isLoginRoute).toBe(true)
@@ -46,8 +96,7 @@ describe('MyHead.vue', () => {
expect(welcomeWrapper.vm.pageTitle).toBeUndefined()
})
it('clears login state and navigates to login on logout', async () => {
vi.mocked(logout).mockResolvedValue({ code: 200 } as any)
it('clears login state and navigates to login on logout', async () => { vi.mocked(logout).mockResolvedValue({ code: 200 } as any)
localStorage.setItem('isLoggedIn', 'true')
const wrapper = await mountAt('/study')
+16 -3
View File
@@ -4,9 +4,9 @@
v-if="showBackButton"
text
class="back-btn"
@click="router.push('/welcome')"
@click="goBack"
>
返回首页
{{ backLabel }}
</el-button>
<template v-if="pageTitle">
<h1 class="head-title">{{ pageTitle }}</h1>
@@ -39,8 +39,21 @@ const route = useRoute();
const isLoginRoute = computed(() => route.path === "/login");
const showBackButton = computed(() => route.path !== "/login" && route.path !== "/welcome");
const pageTitle = computed(() => route.meta?.title as string | undefined);
// 返回目标由路由 meta 声明,未声明时回首页
const backTo = computed(() => (route.meta?.backTo as string | undefined) || "/welcome");
const backLabel = computed(() => (route.meta?.backLabel as string | undefined) || "返回首页");
// 有多个入口的页面(如复习详情)优先回到用户来路,无历史时回退到声明的上级
const backUseHistory = computed(() => route.meta?.backUseHistory === true);
const loggingOut = ref(false);
const goBack = () => {
if (backUseHistory.value && window.history.state?.back) {
router.back();
return;
}
router.push(backTo.value);
};
const handleLogout = async () => {
if (loggingOut.value) return;
loggingOut.value = true;
@@ -58,7 +71,7 @@ const handleLogout = async () => {
}
};
defineExpose({ isLoginRoute, showBackButton, pageTitle, loggingOut, handleLogout });
defineExpose({ isLoginRoute, showBackButton, pageTitle, backTo, backLabel, loggingOut, goBack, handleLogout });
</script>
<style scoped>
+8 -2
View File
@@ -30,12 +30,18 @@ const routes: RouteRecordRaw[] = [
{
path: "/review/detail/task/:taskNum",
component: () => import("@/components/ReviewDetail.vue"),
meta: { requiresAuth: true, title: "复习详情" },
meta: { requiresAuth: true, title: "复习详情", backTo: "/review", backLabel: "返回总览" },
},
{
path: "/review/detail/:type/:id",
component: () => import("@/components/ReviewDetail.vue"),
meta: { requiresAuth: true, title: "复习详情" },
meta: {
requiresAuth: true,
title: "复习详情",
backTo: "/review",
backLabel: "返回上一页",
backUseHistory: true,
},
},
{
path: "/review/recall/:taskNum",