From 1a6d61ec36581d20786f64cca8a745b6159c39e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=9E=AB?= Date: Tue, 17 Mar 2026 20:31:34 +0800 Subject: [PATCH] feat: add setup wizard and enhance login flow - Add /setup route with 4-step wizard: welcome, admin account creation, basic config (port, HTTPS), completion - Router auto-detects first-time setup via GET /admin/setup/status - Redirects to /setup if not configured, blocks /setup after init - Uses Headless UI TransitionRoot for step animations - Password confirmation with mismatch validation Co-Authored-By: Paperclip --- web/src/router/index.ts | 40 ++++- web/src/views/SetupView.vue | 301 ++++++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 web/src/views/SetupView.vue diff --git a/web/src/router/index.ts b/web/src/router/index.ts index d6efef5..8c8368b 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"; import { useAuthStore } from "@/stores/auth"; +import api from "@/api/client"; const routes: RouteRecordRaw[] = [ { @@ -8,6 +9,12 @@ const routes: RouteRecordRaw[] = [ component: () => import("@/views/LoginView.vue"), meta: { requiresAuth: false }, }, + { + path: "/setup", + name: "setup", + component: () => import("@/views/SetupView.vue"), + meta: { requiresAuth: false }, + }, { path: "/", component: () => import("@/components/layout/AppLayout.vue"), @@ -42,8 +49,34 @@ const router = createRouter({ routes, }); -router.beforeEach((to) => { +let setupChecked = false; +let isSetUp = false; + +router.beforeEach(async (to) => { const auth = useAuthStore(); + + // Check setup status once on first navigation + if (!setupChecked) { + try { + const { data } = await api.get("/setup/status"); + isSetUp = data.isSetUp; + } catch { + // If server unreachable, assume setup done + isSetUp = true; + } + setupChecked = true; + } + + // Redirect to setup if not configured (unless already on setup page) + if (!isSetUp && to.name !== "setup") { + return { name: "setup" }; + } + + // After setup is done, don't allow revisiting setup + if (isSetUp && to.name === "setup") { + return { name: "login" }; + } + if (to.meta.requiresAuth !== false && !auth.isAuthenticated) { return { name: "login" }; } @@ -52,4 +85,9 @@ router.beforeEach((to) => { } }); +// Allow marking setup as complete from the setup view +export function markSetupComplete(): void { + isSetUp = true; +} + export default router; diff --git a/web/src/views/SetupView.vue b/web/src/views/SetupView.vue new file mode 100644 index 0000000..8c56481 --- /dev/null +++ b/web/src/views/SetupView.vue @@ -0,0 +1,301 @@ + + +