diff --git a/README.md b/README.md index ee1b7e5..fa89ddc 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ PostgreSQL 18 兼容工作继续冻结。 先在宝塔面板停止并删除当前错误的 Java 项目,再执行: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.29/install.sh | sudo env KAIDI_APP_PORT=18080 bash +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.30/install.sh | sudo env KAIDI_APP_PORT=18080 bash ``` 该命令只安装程序运行所需的 systemd 单元,自动创建 `kaidi` 用户并检测现有 Java 17(包括 diff --git a/frontend/e2e/setup-wizard.e2e.ts b/frontend/e2e/setup-wizard.e2e.ts index 167c50f..1b8cff3 100644 --- a/frontend/e2e/setup-wizard.e2e.ts +++ b/frontend/e2e/setup-wizard.e2e.ts @@ -137,3 +137,69 @@ test('confirms completion after the setup response is interrupted by a service r await expect(page.getByText('网络请求失败')).toHaveCount(0); await expect(page).toHaveURL(/\/login$/, { timeout: 15_000 }); }); + +test('leaves the completed wizard when post-completion status polling is unavailable', async ({ page }) => { + let completionStarted = false; + await page.route('**/api/v1/setup/**', async (route) => { + const request = route.request(); + const pathname = new URL(request.url()).pathname; + if (pathname === '/api/v1/setup/status') { + if (completionStarted) { + await route.abort('connectionreset'); + return; + } + await route.fulfill({ + json: { + data: { + required: true, + locked: false, + ready: false, + supportedDatabaseTypes: ['MYSQL'], + message: '请完成安装', + }, + }, + }); + return; + } + const body = request.postDataJSON() as Record; + if (pathname.endsWith('/test-connection')) { + await route.fulfill({ + json: { + data: { + successful: true, + databaseType: 'MYSQL', + serverVersion: '8.4.6', + schemaReady: true, + message: 'MySQL 8.4 连接、排序规则和完整迁移权限验证通过', + }, + }, + }); + return; + } + expect(body.adminUsername).toBe('admin'); + completionStarted = true; + await route.fulfill({ + json: { + data: { + completed: true, + username: 'admin', + message: '安装完成,系统正在切换到正式模式', + restartAfterSeconds: 1, + }, + }, + }); + }); + + await page.goto('/setup'); + await page.getByLabel('数据库密码').fill('fixture-db-password'); + await page.getByLabel('安装码').fill('fixture-setup-code'); + await page.getByRole('button', { name: '测试连接' }).click(); + await page.getByRole('button', { name: '下一步' }).click(); + await page.getByLabel('管理员密码').fill('SetupAdmin@2026Strong'); + await page.getByLabel('确认密码').fill('SetupAdmin@2026Strong'); + await page.getByRole('button', { name: '完成安装' }).click(); + await page.getByRole('button', { name: '确定', exact: true }).click(); + + await expect(page.getByText('安装完成', { exact: true })).toBeVisible(); + await expect(page).toHaveURL(/\/login$/, { timeout: 5_000 }); +}); diff --git a/frontend/src/pages/setup/index.vue b/frontend/src/pages/setup/index.vue index eb6e1e5..f349ce2 100644 --- a/frontend/src/pages/setup/index.vue +++ b/frontend/src/pages/setup/index.vue @@ -378,19 +378,18 @@ function confirmSetup() { function startCountdown() { window.clearInterval(countdownTimer); countdownTimer = window.setInterval(() => { - countdown.value -= 1; - if (countdown.value <= 0) void openLogin(); + countdown.value = Math.max(0, countdown.value - 1); + if (countdown.value === 0) openLogin(); }, 1000); } -async function openLogin() { +function openLogin() { window.clearInterval(countdownTimer); - completionMessage.value = '系统正在启动,请稍候。'; - if (await waitForSetupCompletion()) { - await router.replace('/login'); - return; - } - completionMessage.value = '系统启动时间较长,请稍后刷新页面。'; + countdown.value = 0; + completionMessage.value = '正在进入登录页。'; + // A full navigation clears the setup-mode SPA state retained across the + // service restart. The login route guard performs the final setup check. + window.location.replace('/login'); } onMounted(async () => {