Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# 更新记录

## 未发布

- 修复管理后台手机端成功/错误提示遮挡顶部导航的问题:窄屏提示改为底部显示,桌面保留右上角,并支持手动关闭。新增提示显示期间的窄屏导航回归测试,覆盖 320/390px 与 100%/150% 字号。

## v0.3.4 — 2026-11-04

- 文字通知默认不朗读,只在大屏上显示;朗读次数可选 0–3 遍。原声通知播放 1–3 遍。大屏需升级到本版本,旧版大屏收到 0 遍时仍会朗读一遍。
Expand Down
43 changes: 41 additions & 2 deletions tests/e2e/admin-layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ const administrator = {
status: 'active',
};

async function mockAdmin(page: Page, siteName = settings.site_name, icpNumber = settings.icp_number) {
async function mockAdmin(
page: Page,
siteName = settings.site_name,
icpNumber = settings.icp_number,
uiScale = 100,
) {
const siteSettings = { ...settings, site_name: siteName, icp_number: icpNumber };
const responses: Record<string, unknown> = {
'/api/v1/config': siteSettings,
'/api/v1/auth/refresh': { access_token: 'test-token', user: administrator },
'/api/v1/auth/refresh': { access_token: 'test-token', user: { ...administrator, ui_scale: uiScale } },
'/api/v1/admin/settings': siteSettings,
'/api/v1/admin/schools': [],
'/api/v1/admin/classes': [],
'/api/v1/admin/users': [
{
id: 'teacher-1',
Expand Down Expand Up @@ -49,6 +56,38 @@ async function mockAdmin(page: Page, siteName = settings.site_name, icpNumber =
});
}

test('管理员成功提示显示时窄屏导航仍可点击', async ({ page }) => {
for (const width of [320, 390]) {
for (const scale of [100, 150]) {
await mockAdmin(page, settings.site_name, settings.icp_number, scale);
await page.route('**/api/v1/admin/schools', (route) =>
route.fulfill({
json: route.request().method() === 'POST' ? { id: 'school-toast', name: '提示验收学校' } : [],
}),
);
await page.setViewportSize({ width, height: 844 });
await page.goto(adminURL + 'schools');
await page.getByRole('button', { name: '新建学校' }).click();
const dialog = page.getByRole('dialog');
await dialog.getByLabel('学校名称').fill('提示验收学校');
await dialog.getByRole('button', { name: '创建学校' }).click();
await expect(dialog).toBeHidden();
const toast = page.locator('[data-sonner-toast]').filter({ hasText: '学校已创建' });
await expect(toast).toBeVisible();
// Hold the real toast open instead of waiting for it to expire or forcing the click.
await toast.hover();
const navigation = page.getByRole('navigation', { name: '管理后台导航' });
const toastBox = await toast.boundingBox();
const navigationBox = await navigation.boundingBox();
expect(toastBox!.y).toBeGreaterThan(navigationBox!.y + navigationBox!.height);
await navigation.getByRole('link', { name: '班级管理' }).click({ timeout: 2000 });
await expect(page.getByRole('heading', { name: '班级管理', exact: true })).toBeVisible();
await expect(toast).toBeVisible();
await noOverflow(page);
}
}
});

async function noOverflow(page: Page) {
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)).toBe(true);
}
Expand Down
9 changes: 8 additions & 1 deletion web/apps/admin/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { setAuthScope } from '@classding/api';
import { SiteSettingsProvider } from '@classding/ui/components/classding';
import { Toaster } from '@classding/ui/components/ui/sonner';
import { TooltipProvider } from '@classding/ui/components/ui/tooltip';
import { useIsMobile } from '@classding/ui/hooks/use-mobile';
import '@classding/ui/globals.css';
import App from './App';

Expand All @@ -13,13 +14,19 @@ const queryClient = new QueryClient({
defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 15_000 } },
});

function AdminToaster() {
const isMobile = useIsMobile();
// Keep success/error feedback clear of the narrow-screen header and navigation.
return <Toaster position={isMobile ? 'bottom-center' : 'top-right'} richColors closeButton />;
}

createRoot(document.getElementById('root')!).render(
<SiteSettingsProvider>
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<BrowserRouter basename="/admin">
<App />
<Toaster position="top-right" richColors />
<AdminToaster />
</BrowserRouter>
</TooltipProvider>
</QueryClientProvider>
Expand Down
Loading