详解Vite+JavaScript+Vue+Yarn实现登录页面实战
作者:alden_ygq
本文主要介绍了Vite+JavaScript+Vue+Yarn实现登录页面实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
本文将实现一个完整的登录系统前端,包含登录、注册、登出和用户管理功能。后端接口使用Mock.js模拟,不包含实际后端代码。
项目结构
login-demo/ ├── public/ ├── src/ │ ├── api/ # 接口定义 │ │ └── auth.js │ ├── components/ # 组件 │ │ ├── LoginForm.vue │ │ ├── RegisterForm.vue │ │ └── UserTable.vue │ ├── router/ # 路由 │ │ └── index.js │ ├── store/ # 状态管理 │ │ └── auth.js │ ├── views/ # 页面视图 │ │ ├── Dashboard.vue │ │ ├── Login.vue │ │ └── Register.vue │ ├── App.vue │ ├── main.js │ └── mock.js # Mock数据 ├── .gitignore ├── index.html ├── package.json ├── vite.config.js └── yarn.lock
完整代码实现
1. 安装依赖 (终端执行)
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数据
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')3. src/mock.js (模拟后端接口)
import Mock from 'mockjs'
// 模拟用户数据
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状态管理)
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()
// 检查路由是否需要认证
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
return next({ name: 'Login' })
}
// 检查路由是否需要未登录状态
if (to.meta.requiresGuest && authStore.isAuthenticated) {
return next({ name: 'Dashboard' })
}
next()
})
export default router7. 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">确认密码</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>用户管理系统</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)创建项目
yarn create vite login-demo --template vue cd login-demo
2)安装依赖
yarn add vue-router@4 pinia axios mockjs
3)创建项目结构
创建上述所有文件和目录
4)启动开发服务器
yarn dev
5)访问应用
打开浏览器访问 http://localhost:5173
功能说明
1)登录功能
- 使用用户名和密码登录
- 模拟后端验证
- 登录成功后跳转到仪表盘
2)注册功能
- 创建新用户账号
- 验证用户名和邮箱唯一性
- 注册成功后跳转到登录页面
3)登出功能
- 清除用户会话
- 重定向到登录页面
4)用户管理
- 查看所有用户列表
- 删除用户(不能删除当前登录用户)
- 刷新用户列表
接口定义
POST /api/login- 用户登录POST /api/logout- 用户登出POST /api/register- 用户注册GET /api/users- 获取用户列表DELETE /api/users/:id- 删除用户
这个登录系统前端包含了完整的用户认证流程和简单的用户管理功能,使用了Vite作为构建工具,Vue3作为前端框架,Pinia进行状态管理,Vue Router处理路由,Mock.js模拟后端接口。
到此这篇关于详解Vite+JavaScript+Vue+Yarn实现登录页面实战的文章就介绍到这了,更多相关Vue Yarn登录页内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
