fix: 复习详情返回按钮改为返回总览
This commit is contained in:
@@ -82,4 +82,18 @@ test.describe('Review page', () => {
|
|||||||
await expect(page.getByRole('button', { name: '重新加载' })).toBeVisible();
|
await expect(page.getByRole('button', { name: '重新加载' })).toBeVisible();
|
||||||
await expect(page.getByText('暂无复习任务')).toHaveCount(0);
|
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 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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', () => {
|
describe('MyHead.vue', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@@ -30,6 +37,49 @@ describe('MyHead.vue', () => {
|
|||||||
await router.isReady()
|
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 () => {
|
it('derives route-based computed properties from real route', async () => {
|
||||||
const loginWrapper = await mountAt('/login')
|
const loginWrapper = await mountAt('/login')
|
||||||
expect(loginWrapper.vm.isLoginRoute).toBe(true)
|
expect(loginWrapper.vm.isLoginRoute).toBe(true)
|
||||||
@@ -46,8 +96,7 @@ describe('MyHead.vue', () => {
|
|||||||
expect(welcomeWrapper.vm.pageTitle).toBeUndefined()
|
expect(welcomeWrapper.vm.pageTitle).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clears login state and navigates to login on logout', async () => {
|
it('clears login state and navigates to login on logout', async () => { vi.mocked(logout).mockResolvedValue({ code: 200 } as any)
|
||||||
vi.mocked(logout).mockResolvedValue({ code: 200 } as any)
|
|
||||||
localStorage.setItem('isLoggedIn', 'true')
|
localStorage.setItem('isLoggedIn', 'true')
|
||||||
const wrapper = await mountAt('/study')
|
const wrapper = await mountAt('/study')
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
v-if="showBackButton"
|
v-if="showBackButton"
|
||||||
text
|
text
|
||||||
class="back-btn"
|
class="back-btn"
|
||||||
@click="router.push('/welcome')"
|
@click="goBack"
|
||||||
>
|
>
|
||||||
← 返回首页
|
← {{ backLabel }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<template v-if="pageTitle">
|
<template v-if="pageTitle">
|
||||||
<h1 class="head-title">{{ pageTitle }}</h1>
|
<h1 class="head-title">{{ pageTitle }}</h1>
|
||||||
@@ -39,8 +39,21 @@ const route = useRoute();
|
|||||||
const isLoginRoute = computed(() => route.path === "/login");
|
const isLoginRoute = computed(() => route.path === "/login");
|
||||||
const showBackButton = computed(() => route.path !== "/login" && route.path !== "/welcome");
|
const showBackButton = computed(() => route.path !== "/login" && route.path !== "/welcome");
|
||||||
const pageTitle = computed(() => route.meta?.title as string | undefined);
|
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 loggingOut = ref(false);
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
if (backUseHistory.value && window.history.state?.back) {
|
||||||
|
router.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push(backTo.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
if (loggingOut.value) return;
|
if (loggingOut.value) return;
|
||||||
loggingOut.value = true;
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
+8
-2
@@ -30,12 +30,18 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: "/review/detail/task/:taskNum",
|
path: "/review/detail/task/:taskNum",
|
||||||
component: () => import("@/components/ReviewDetail.vue"),
|
component: () => import("@/components/ReviewDetail.vue"),
|
||||||
meta: { requiresAuth: true, title: "复习详情" },
|
meta: { requiresAuth: true, title: "复习详情", backTo: "/review", backLabel: "返回总览" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/review/detail/:type/:id",
|
path: "/review/detail/:type/:id",
|
||||||
component: () => import("@/components/ReviewDetail.vue"),
|
component: () => import("@/components/ReviewDetail.vue"),
|
||||||
meta: { requiresAuth: true, title: "复习详情" },
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: "复习详情",
|
||||||
|
backTo: "/review",
|
||||||
|
backLabel: "返回上一页",
|
||||||
|
backUseHistory: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/review/recall/:taskNum",
|
path: "/review/recall/:taskNum",
|
||||||
|
|||||||
Reference in New Issue
Block a user