diff --git a/web/src/App.vue b/web/src/App.vue index c04d85a..ed746f3 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,9 +1,15 @@ diff --git a/web/src/components/layout/NavBar.vue b/web/src/components/layout/NavBar.vue new file mode 100644 index 0000000..9a3bd94 --- /dev/null +++ b/web/src/components/layout/NavBar.vue @@ -0,0 +1,36 @@ + + + diff --git a/web/src/components/layout/NotificationBell.vue b/web/src/components/layout/NotificationBell.vue new file mode 100644 index 0000000..c961f0b --- /dev/null +++ b/web/src/components/layout/NotificationBell.vue @@ -0,0 +1,7 @@ + + + diff --git a/web/src/layouts/AppShell.vue b/web/src/layouts/AppShell.vue new file mode 100644 index 0000000..d44e53c --- /dev/null +++ b/web/src/layouts/AppShell.vue @@ -0,0 +1,12 @@ + + + diff --git a/web/src/main.ts b/web/src/main.ts index 10b802c..f432751 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -2,10 +2,13 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' +import router, { bindAuthToApi } from './router' import './styles/tailwind.css' import './styles/tokens.css' const app = createApp(App) app.use(createPinia()) +app.use(router) +bindAuthToApi() app.mount('#app') diff --git a/web/src/router/index.ts b/web/src/router/index.ts new file mode 100644 index 0000000..bcbd635 --- /dev/null +++ b/web/src/router/index.ts @@ -0,0 +1,48 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { useAuthStore } from '@/stores/auth' +import { configure } from '@/api/client' + +const routes: RouteRecordRaw[] = [ + { + path: '/login', + name: 'login', + component: () => import('@/views/auth/LoginView.vue'), + meta: { layout: 'none' }, + }, + { + path: '/', + name: 'home', + component: () => import('@/views/HomeView.vue'), + meta: { layout: 'app', requiresAuth: true }, + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +router.beforeEach((to) => { + const auth = useAuthStore() + if (to.meta.requiresAuth && !auth.isAuthenticated) { + return { name: 'login', query: { next: to.fullPath } } + } + if (to.name === 'login' && auth.isAuthenticated) { + return { name: 'home' } + } + return true +}) + +export default router + +// 把 auth store 注入 api client(在 router 模块顶层晚于 pinia 安装可能未就绪,所以用懒访问) +export function bindAuthToApi() { + const auth = useAuthStore() + configure({ + tokenProvider: () => auth.token, + onUnauthorized: () => { + auth.clearSession() + router.replace({ name: 'login' }) + }, + }) +} diff --git a/web/src/views/HomeView.vue b/web/src/views/HomeView.vue new file mode 100644 index 0000000..8ceeaa7 --- /dev/null +++ b/web/src/views/HomeView.vue @@ -0,0 +1,14 @@ + + + diff --git a/web/src/views/auth/LoginView.vue b/web/src/views/auth/LoginView.vue new file mode 100644 index 0000000..216aad1 --- /dev/null +++ b/web/src/views/auth/LoginView.vue @@ -0,0 +1,51 @@ + + +