diff --git a/README.md b/README.md index 33895ab..faa147f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,148 @@ -# Vue 3 + TypeScript + Vite +# Vue Dicts - 法语词典学习平台 -This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` \ No newline at end of file diff --git a/src/components/SearchBar.vue b/src/components/SearchBar.vue index 452e8c4..96a5667 100644 --- a/src/components/SearchBar.vue +++ b/src/components/SearchBar.vue @@ -1,15 +1,46 @@ \ No newline at end of file + + + \ No newline at end of file diff --git a/src/composables/useAuth.ts b/src/composables/useAuth.ts new file mode 100644 index 0000000..506344c --- /dev/null +++ b/src/composables/useAuth.ts @@ -0,0 +1,88 @@ +import { ref, computed } from 'vue' +import { useRouter } from 'vue-router' +import { login as apiLogin, logout as apiLogout, register as apiRegister, type LoginRequest, type RegisterRequest } from '../api/auth' + +// 全局状态 +const user = ref(null) +const token = ref(null) + +// 从localStorage恢复状态 +const initAuth = () => { + const savedToken = localStorage.getItem('access_token') + const savedUser = localStorage.getItem('user') + + if (savedToken) { + token.value = savedToken + } + + if (savedUser) { + try { + user.value = JSON.parse(savedUser) + } catch (e) { + console.error('Failed to parse saved user:', e) + localStorage.removeItem('user') + } + } +} + +export const useAuth = () => { + const router = useRouter() + + const isAuthenticated = computed(() => !!token.value && !!user.value) + + const login = async (credentials: LoginRequest) => { + try { + const response = await apiLogin(credentials) + + // 保存到状态 + token.value = response.access_token + user.value = response.user + + // 保存到localStorage + localStorage.setItem('access_token', response.access_token) + localStorage.setItem('user', JSON.stringify(response.user)) + + return response + } catch (error) { + console.error('Login failed:', error) + throw error + } + } + + const register = async (userData: RegisterRequest) => { + try { + const response = await apiRegister(userData) + return response + } catch (error) { + console.error('Registration failed:', error) + throw error + } + } + + const logout = async () => { + try { + await apiLogout() + } catch (error) { + console.error('Logout error:', error) + } finally { + // 无论API调用是否成功,都清除本地状态 + token.value = null + user.value = null + localStorage.removeItem('access_token') + localStorage.removeItem('user') + router.push('/login') + } + } + + return { + user: computed(() => user.value), + token: computed(() => token.value), + isAuthenticated, + login, + register, + logout + } +} + +// 初始化认证状态 +initAuth() diff --git a/src/router/index.ts b/src/router/index.ts index 0dbe6b8..b0f4d11 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,10 +1,30 @@ import { createRouter, createWebHistory } from 'vue-router' -export default createRouter({ +const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', name: 'Home', component: () => import('../views/HomePage.vue') }, { path: '/dict', name: 'Dict', component: () => import('../views/DictPage.vue') }, + { path: '/trans', name: 'Translation', component: () => import('../views/TranslationPage.vue') }, + { path: '/write', name: 'Writing', component: () => import('../views/WritingPage.vue') }, { path: '/login', name: 'Login', component: () => import('../views/LoginPage.vue') }, ], -}) \ No newline at end of file +}) + +// 路由守卫 +router.beforeEach((to, from, next) => { + const token = localStorage.getItem('access_token') + + // 需要认证的页面 + const requiresAuth = ['Dict', 'Translation', 'Writing'] + + if (requiresAuth.includes(to.name as string) && !token) { + next('/login') + } else if (to.name === 'Login' && token) { + next('/') + } else { + next() + } +}) + +export default router \ No newline at end of file diff --git a/src/views/DictPage.vue b/src/views/DictPage.vue index 8f39cd9..1341236 100644 --- a/src/views/DictPage.vue +++ b/src/views/DictPage.vue @@ -1,50 +1,84 @@ - \ No newline at end of file diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue index ae73b9a..7f19ade 100644 --- a/src/views/HomePage.vue +++ b/src/views/HomePage.vue @@ -1,27 +1,27 @@ + + \ No newline at end of file diff --git a/src/views/TranslationPage.vue b/src/views/TranslationPage.vue new file mode 100644 index 0000000..43d3c60 --- /dev/null +++ b/src/views/TranslationPage.vue @@ -0,0 +1,217 @@ + + + diff --git a/src/views/WritingPage.vue b/src/views/WritingPage.vue new file mode 100644 index 0000000..caba9a3 --- /dev/null +++ b/src/views/WritingPage.vue @@ -0,0 +1,234 @@ + + + diff --git a/start-backend.bat b/start-backend.bat new file mode 100644 index 0000000..d85227e --- /dev/null +++ b/start-backend.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting backend server... +cd backend/dict-server +python main.py +pause diff --git a/start-frontend.bat b/start-frontend.bat new file mode 100644 index 0000000..b2a8e8d --- /dev/null +++ b/start-frontend.bat @@ -0,0 +1,4 @@ +@echo off +echo Starting frontend development server... +npm run dev +pause diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..eb6f07d --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,20 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + primary: '#2563eb', // blue-700 + muted: '#6b7280', // gray-500 + }, + fontFamily: { + 'deserta': ['Deserta', 'serif'], + 'inter': ['Inter', 'sans-serif'], + }, + }, + }, + plugins: [], +}