詳解Vite+JavaScript+Vue+Yarn實現(xiàn)登錄頁面實戰(zhàn)
本文將實現(xiàn)一個完整的登錄系統(tǒng)前端,包含登錄、注冊、登出和用戶管理功能。后端接口使用Mock.js模擬,不包含實際后端代碼。
項目結(jié)構(gòu)
login-demo/ ├── public/ ├── src/ │ ├── api/ # 接口定義 │ │ └── auth.js │ ├── components/ # 組件 │ │ ├── LoginForm.vue │ │ ├── RegisterForm.vue │ │ └── UserTable.vue │ ├── router/ # 路由 │ │ └── index.js │ ├── store/ # 狀態(tài)管理 │ │ └── auth.js │ ├── views/ # 頁面視圖 │ │ ├── Dashboard.vue │ │ ├── Login.vue │ │ └── Register.vue │ ├── App.vue │ ├── main.js │ └── mock.js # Mock數(shù)據(jù) ├── .gitignore ├── index.html ├── package.json ├── vite.config.js └── yarn.lock
完整代碼實現(xiàn)
1. 安裝依賴 (終端執(zhí)行)
yarn add vue-router@4 pinia axios mockjs
2. src/main.js
import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import router from './router' import './mock' // 引入Mock數(shù)據(jù) const app = createApp(App) app.use(createPinia()) app.use(router) app.mount('#app')
3. src/mock.js (模擬后端接口)
import Mock from 'mockjs' // 模擬用戶數(shù)據(jù) const users = [ { id: 1, username: 'admin', password: 'admin123', email: 'admin@example.com', role: 'admin' }, { id: 2, username: 'user1', password: 'user123', email: 'user1@example.com', role: 'user' } ] // 登錄接口 Mock.mock('/api/login', 'post', (options) => { const { username, password } = JSON.parse(options.body) const user = users.find(u => u.username === username && u.password === password) if (user) { return Mock.mock({ code: 200, message: '登錄成功', data: { token: '@guid', user: { id: user.id, username: user.username, email: user.email, role: user.role } } }) } return Mock.mock({ code: 401, message: '用戶名或密碼錯誤' }) }) // 登出接口 Mock.mock('/api/logout', 'post', { code: 200, message: '登出成功' }) // 注冊接口 Mock.mock('/api/register', 'post', (options) => { const data = JSON.parse(options.body) const existingUser = users.find(u => u.username === data.username || u.email === data.email) if (existingUser) { return Mock.mock({ code: 400, message: '用戶名或郵箱已存在' }) } const newUser = { id: users.length + 1, username: data.username, password: data.password, email: data.email, role: 'user' } users.push(newUser) return Mock.mock({ code: 200, message: '注冊成功', data: { user: { id: newUser.id, username: newUser.username, email: newUser.email, role: newUser.role } } }) }) // 獲取用戶列表 Mock.mock('/api/users', 'get', () => { return Mock.mock({ code: 200, message: '獲取用戶列表成功', data: { users: users.map(u => ({ id: u.id, username: u.username, email: u.email, role: u.role })) } }) }) // 刪除用戶 Mock.mock(/\/api\/users\/\d+/, 'delete', (options) => { const id = options.url.split('/').pop() const index = users.findIndex(u => u.id == id) if (index !== -1) { users.splice(index, 1) return Mock.mock({ code: 200, message: '用戶刪除成功' }) } return Mock.mock({ code: 404, message: '用戶不存在' }) })
4. src/api/auth.js (接口封裝)
import axios from 'axios' const api = axios.create({ baseURL: '/api', timeout: 5000 }) export default { // 登錄 login(credentials) { return api.post('/login', credentials) }, // 登出 logout() { return api.post('/logout') }, // 注冊 register(userData) { return api.post('/register', userData) }, // 獲取用戶列表 getUsers() { return api.get('/users') }, // 刪除用戶 deleteUser(id) { return api.delete(`/users/${id}`) } }
5. src/store/auth.js (Pinia狀態(tài)管理)
import { defineStore } from 'pinia' import authApi from '@/api/auth' export const useAuthStore = defineStore('auth', { state: () => ({ user: JSON.parse(localStorage.getItem('user')) || null, token: localStorage.getItem('token') || null, isAuthenticated: !!localStorage.getItem('token'), users: [] }), actions: { async login(credentials) { try { const response = await authApi.login(credentials) if (response.data.code === 200) { this.user = response.data.data.user this.token = response.data.data.token this.isAuthenticated = true // 保存到本地存儲 localStorage.setItem('user', JSON.stringify(this.user)) localStorage.setItem('token', this.token) return true } return false } catch (error) { console.error('登錄失敗:', error) return false } }, async logout() { try { await authApi.logout() this.user = null this.token = null this.isAuthenticated = false // 清除本地存儲 localStorage.removeItem('user') localStorage.removeItem('token') return true } catch (error) { console.error('登出失敗:', error) return false } }, async register(userData) { try { const response = await authApi.register(userData) return response.data.code === 200 } catch (error) { console.error('注冊失敗:', error) return false } }, async fetchUsers() { try { const response = await authApi.getUsers() if (response.data.code === 200) { this.users = response.data.data.users return true } return false } catch (error) { console.error('獲取用戶列表失敗:', error) return false } }, async deleteUser(id) { try { const response = await authApi.deleteUser(id) if (response.data.code === 200) { // 重新獲取用戶列表 await this.fetchUsers() return true } return false } catch (error) { console.error('刪除用戶失敗:', error) return false } } } })
6. src/router/index.js (路由配置)
import { createRouter, createWebHistory } from 'vue-router' import { useAuthStore } from '@/store/auth' const routes = [ { path: '/', redirect: '/login' }, { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { requiresGuest: true } }, { path: '/register', name: 'Register', component: () => import('@/views/Register.vue'), meta: { requiresGuest: true } }, { path: '/dashboard', name: 'Dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } } ] const router = createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) => { const authStore = useAuthStore() // 檢查路由是否需要認(rèn)證 if (to.meta.requiresAuth && !authStore.isAuthenticated) { return next({ name: 'Login' }) } // 檢查路由是否需要未登錄狀態(tài) if (to.meta.requiresGuest && authStore.isAuthenticated) { return next({ name: 'Dashboard' }) } next() }) export default router
7. src/views/Login.vue
<template> <div class="login-container"> <div class="login-card"> <h1>用戶登錄</h1> <LoginForm @login="handleLogin" /> <div class="links"> <router-link to="/register">注冊新賬號</router-link> </div> </div> </div> </template> <script> import LoginForm from '@/components/LoginForm.vue' import { useAuthStore } from '@/store/auth' import { useRouter } from 'vue-router' export default { components: { LoginForm }, setup() { const authStore = useAuthStore() const router = useRouter() const handleLogin = async (credentials) => { const success = await authStore.login(credentials) if (success) { router.push({ name: 'Dashboard' }) } else { alert('登錄失敗,請檢查用戶名和密碼') } } return { handleLogin } } } </script> <style scoped> .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); } .login-card { background: white; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); padding: 30px; width: 100%; max-width: 400px; text-align: center; } h1 { margin-bottom: 24px; color: #333; font-size: 24px; } .links { margin-top: 20px; font-size: 14px; } .links a { color: #2575fc; text-decoration: none; } .links a:hover { text-decoration: underline; } </style>
8. src/components/LoginForm.vue
<template> <form @submit.prevent="submitForm"> <div class="form-group"> <label for="username">用戶名</label> <input type="text" id="username" v-model="form.username" placeholder="請輸入用戶名" required /> </div> <div class="form-group"> <label for="password">密碼</label> <input type="password" id="password" v-model="form.password" placeholder="請輸入密碼" required /> </div> <button type="submit" class="login-btn">登錄</button> </form> </template> <script> import { ref } from 'vue' export default { emits: ['login'], setup(props, { emit }) { const form = ref({ username: '', password: '' }) const submitForm = () => { emit('login', { username: form.value.username, password: form.value.password }) } return { form, submitForm } } } </script> <style scoped> .form-group { margin-bottom: 20px; text-align: left; } label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; } input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 5px; font-size: 16px; transition: border-color 0.3s; } input:focus { border-color: #2575fc; outline: none; } .login-btn { width: 100%; padding: 12px; background: #2575fc; color: white; border: none; border-radius: 5px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.3s; } .login-btn:hover { background: #1c65e0; } </style>
9. src/views/Register.vue
<template> <div class="register-container"> <div class="register-card"> <h1>用戶注冊</h1> <RegisterForm @register="handleRegister" /> <div class="links"> <router-link to="/login">已有賬號?立即登錄</router-link> </div> </div> </div> </template> <script> import RegisterForm from '@/components/RegisterForm.vue' import { useAuthStore } from '@/store/auth' import { useRouter } from 'vue-router' export default { components: { RegisterForm }, setup() { const authStore = useAuthStore() const router = useRouter() const handleRegister = async (userData) => { const success = await authStore.register(userData) if (success) { alert('注冊成功!請登錄') router.push({ name: 'Login' }) } else { alert('注冊失敗,用戶名或郵箱可能已被使用') } } return { handleRegister } } } </script> <style scoped> .register-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: linear-gradient(135deg, #ff6b6b 0%, #ffa502 100%); } .register-card { background: white; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); padding: 30px; width: 100%; max-width: 400px; text-align: center; } h1 { margin-bottom: 24px; color: #333; font-size: 24px; } .links { margin-top: 20px; font-size: 14px; } .links a { color: #ff6b6b; text-decoration: none; } .links a:hover { text-decoration: underline; } </style>
10. src/components/RegisterForm.vue
<template> <form @submit.prevent="submitForm"> <div class="form-group"> <label for="username">用戶名</label> <input type="text" id="username" v-model="form.username" placeholder="請輸入用戶名" required /> </div> <div class="form-group"> <label for="email">郵箱</label> <input type="email" id="email" v-model="form.email" placeholder="請輸入郵箱" required /> </div> <div class="form-group"> <label for="password">密碼</label> <input type="password" id="password" v-model="form.password" placeholder="請輸入密碼" required /> </div> <div class="form-group"> <label for="confirmPassword">確認(rèn)密碼</label> <input type="password" id="confirmPassword" v-model="form.confirmPassword" placeholder="請再次輸入密碼" required /> </div> <button type="submit" class="register-btn">注冊</button> </form> </template> <script> import { ref } from 'vue' export default { emits: ['register'], setup(props, { emit }) { const form = ref({ username: '', email: '', password: '', confirmPassword: '' }) const submitForm = () => { if (form.value.password !== form.value.confirmPassword) { alert('兩次輸入的密碼不一致') return } emit('register', { username: form.value.username, email: form.value.email, password: form.value.password }) } return { form, submitForm } } } </script> <style scoped> .form-group { margin-bottom: 20px; text-align: left; } label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; } input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 5px; font-size: 16px; transition: border-color 0.3s; } input:focus { border-color: #ff6b6b; outline: none; } .register-btn { width: 100%; padding: 12px; background: #ff6b6b; color: white; border: none; border-radius: 5px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.3s; } .register-btn:hover { background: #ff5252; } </style>
11. src/views/Dashboard.vue
<template> <div class="dashboard-container"> <header class="header"> <div class="user-info"> <span>歡迎, {{ user.username }} ({{ user.role }})</span> </div> <button class="logout-btn" @click="handleLogout">登出</button> </header> <main class="main-content"> <h1>用戶管理系統(tǒng)</h1> <div class="actions"> <button class="refresh-btn" @click="fetchUsers">刷新用戶列表</button> </div> <UserTable :users="users" @delete-user="deleteUser" /> </main> </div> </template> <script> import UserTable from '@/components/UserTable.vue' import { useAuthStore } from '@/store/auth' import { useRouter } from 'vue-router' import { computed, onMounted, ref } from 'vue' export default { components: { UserTable }, setup() { const authStore = useAuthStore() const router = useRouter() const users = ref([]) const user = computed(() => authStore.user) const fetchUsers = async () => { await authStore.fetchUsers() users.value = authStore.users } const deleteUser = async (id) => { const confirmDelete = confirm('確定要刪除這個用戶嗎?') if (confirmDelete) { const success = await authStore.deleteUser(id) if (success) { await fetchUsers() } } } const handleLogout = async () => { await authStore.logout() router.push({ name: 'Login' }) } onMounted(() => { fetchUsers() }) return { user, users, fetchUsers, deleteUser, handleLogout } } } </script> <style scoped> .dashboard-container { min-height: 100vh; background-color: #f5f7fa; } .header { display: flex; justify-content: space-between; align-items: center; padding: 20px 40px; background-color: white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .user-info { font-weight: 500; color: #333; } .logout-btn { padding: 8px 16px; background-color: #ff4757; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; } .logout-btn:hover { background-color: #ff2e43; } .main-content { max-width: 1200px; margin: 30px auto; padding: 0 20px; } h1 { text-align: center; margin-bottom: 30px; color: #2c3e50; } .actions { margin-bottom: 20px; display: flex; justify-content: flex-end; } .refresh-btn { padding: 8px 16px; background-color: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; } .refresh-btn:hover { background-color: #2980b9; } </style>
12. src/components/UserTable.vue
<template> <div class="user-table"> <table> <thead> <tr> <th>ID</th> <th>用戶名</th> <th>郵箱</th> <th>角色</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="user in users" :key="user.id"> <td>{{ user.id }}</td> <td>{{ user.username }}</td> <td>{{ user.email }}</td> <td>{{ user.role }}</td> <td> <button class="delete-btn" @click="$emit('delete-user', user.id)" :disabled="user.id === currentUserId" > 刪除 </button> </td> </tr> </tbody> </table> </div> </template> <script> import { computed } from 'vue' import { useAuthStore } from '@/store/auth' export default { props: { users: { type: Array, required: true } }, emits: ['delete-user'], setup() { const authStore = useAuthStore() const currentUserId = computed(() => authStore.user?.id || null) return { currentUserId } } } </script> <style scoped> .user-table { overflow-x: auto; background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } table { width: 100%; border-collapse: collapse; } th, td { padding: 16px; text-align: left; border-bottom: 1px solid #eee; } th { background-color: #f8f9fa; font-weight: 600; color: #495057; } tbody tr:hover { background-color: #f8f9fa; } .delete-btn { padding: 6px 12px; background-color: #ff6b6b; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; } .delete-btn:hover:not(:disabled) { background-color: #ff5252; } .delete-btn:disabled { background-color: #cccccc; cursor: not-allowed; } </style>
13. src/App.vue
<template> <router-view /> </template> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background-color: #f8f9fa; } #app { min-height: 100vh; } </style>
項目啟動步驟
1)創(chuàng)建項目
yarn create vite login-demo --template vue cd login-demo
2)安裝依賴
yarn add vue-router@4 pinia axios mockjs
3)創(chuàng)建項目結(jié)構(gòu)
創(chuàng)建上述所有文件和目錄
4)啟動開發(fā)服務(wù)器
yarn dev
5)訪問應(yīng)用
打開瀏覽器訪問 http://localhost:5173
功能說明
1)登錄功能
- 使用用戶名和密碼登錄
- 模擬后端驗證
- 登錄成功后跳轉(zhuǎn)到儀表盤
2)注冊功能
- 創(chuàng)建新用戶賬號
- 驗證用戶名和郵箱唯一性
- 注冊成功后跳轉(zhuǎn)到登錄頁面
3)登出功能
- 清除用戶會話
- 重定向到登錄頁面
4)用戶管理
- 查看所有用戶列表
- 刪除用戶(不能刪除當(dāng)前登錄用戶)
- 刷新用戶列表
接口定義
POST /api/login
- 用戶登錄POST /api/logout
- 用戶登出POST /api/register
- 用戶注冊GET /api/users
- 獲取用戶列表DELETE /api/users/:id
- 刪除用戶
這個登錄系統(tǒng)前端包含了完整的用戶認(rèn)證流程和簡單的用戶管理功能,使用了Vite作為構(gòu)建工具,Vue3作為前端框架,Pinia進(jìn)行狀態(tài)管理,Vue Router處理路由,Mock.js模擬后端接口。
到此這篇關(guān)于詳解Vite+JavaScript+Vue+Yarn實現(xiàn)登錄頁面實戰(zhàn)的文章就介紹到這了,更多相關(guān)Vue Yarn登錄頁內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue實現(xiàn)省市區(qū)級聯(lián)下拉選擇框
這篇文章主要為大家詳細(xì)介紹了Vue實現(xiàn)省市區(qū)級聯(lián)下拉選擇框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03vue 頁面回退mounted函數(shù)不執(zhí)行的解決方案
這篇文章主要介紹了vue 頁面回退mounted函數(shù)不執(zhí)行的解決方案 ,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07Vue前端如何設(shè)置Cookie和鑒權(quán)問題詳解
這篇文章主要介紹了前端如何設(shè)置和使用Cookie,并對比了Cookie和Token在鑒權(quán)中的優(yōu)缺點,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02vue出現(xiàn)Uncaught SyntaxError:Unexpected token問題及解決
這篇文章主要介紹了vue出現(xiàn)Uncaught SyntaxError:Unexpected token問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04vue3開啟攝像頭并進(jìn)行拍照的實現(xiàn)示例
本文主要介紹了vue3開啟攝像頭并進(jìn)行拍照的實現(xiàn)示例,主要是使用navigator.mediaDevices.getUserMedia這個API來實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-01-01vue elementUI el-form 數(shù)據(jù)無法賦值且不報錯的問題及解決方法
vue項目中使用elementUI的el-form組件,里面有部分后端數(shù)據(jù)遍歷的字段和部分確定的字段,遇到個問題遍歷的字段可以修改值但是確定的幾個字段無法修改值,下面小編給大家分享vue elementUI el-form 數(shù)據(jù)無法賦值且不報錯的問題及解決方法,感興趣的朋友一起看看吧2023-12-12