学习进度条
今日所花时间:一小时
今日代码量:100行
博客量:一篇
了解到的知识点:
前几天发布的博客,将团队项目的后端代码基本完成,接下来要进行前端页面的开发
首先是登录页面
Vue3 + Element Plus 实现运维管理系统前端架构
项目背景
我深入学习了 Vue3 的组合式 API、Pinia 状态管理、Element Plus UI 组件库以及前端路由控制等技术。
核心代码实现
1. 登录页面 Login.vue
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { userLoginService } from '@/api/user.js'
import { useTokenStore } from '@/stores/token.js'
import { useRouter } from 'vue-router'
// 数据模型
const loginData = ref({
username: '',
password: ''
})
// 表单校验规则
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 5, max: 16, message: '长度在 5 到 16 个非空字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 16, message: '长度在 5 到 16 个字符', trigger: 'blur' }
]
}
const tokenStore = useTokenStore();
const router = useRouter();
const form = ref(null)
const loading = ref(false)
// 登录函数
const login = async () => {
try {
loading.value = true
await form.value.validate()
const response = await userLoginService({
username: loginData.value.username,
password: loginData.value.password
})
console.log('登录响应:', response)
// 检查响应状态
if (response && response.code === 1) {
// 存储token
if (response.data) {
tokenStore.setToken(response.data)
// 保存用户信息到 localStorage
localStorage.setItem('username', loginData.value.username)
if (response.data.userId) {
localStorage.setItem('userId', response.data.userId)
}
ElMessage.success('登录成功')
router.push('/')
} else {
throw new Error('登录响应中没有token数据')
}
} else {
throw new Error(response?.message || '登录失败')
}
} catch (error) {
console.error('登录错误:', error)
// 显示具体的错误信息
if (error.response?.data?.message) {
ElMessage.error(error.response.data.message)
} else if (error.message) {
ElMessage.error(error.message)
} else {
ElMessage.error('登录失败,请重试')
}
} finally {
loading.value = false
}
}
</script>
<template>
<el-row class="login-page">
<el-col :span="12" class="bg"></el-col>
<el-col :span="6" :offset="3" class="form">
<el-form ref="form" size="large" autocomplete="off" :model="loginData" :rules="rules">
<el-form-item>
<h1>登录</h1>
</el-form-item>
<el-form-item prop="username">
<el-input :prefix-icon="User" placeholder="请输入用户名" v-model="loginData.username" clearable></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="loginData.password"
show-password></el-input>
</el-form-item>
<el-form-item class="flex">
<div class="flex">
<el-checkbox>记住我</el-checkbox>
<el-link type="primary" :underline="false">忘记密码?</el-link>
</div>
</el-form-item>
<el-form-item>
<el-button class="button" type="primary" auto-insert-space @click="login" :loading="loading">
登录
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<style lang="scss" scoped>
.login-page {
height: 100vh;
background-color: #fff;
.bg::after {
content: "运维管理子系统";
/* 添加文字内容 */
position: absolute;
top: 50%;
left: 30%;
transform: translate(-50%, -50%);
color: #333;
font-size: 24px;
font-weight: bold;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
.form {
display: flex;
flex-direction: column;
justify-content: center;
user-select: none;
.title {
margin: 0 auto;
}
.button {
width: 100%;
}
.flex {
width: 100%;
display: flex;
justify-content: space-between;
}
}
}
</style>
技术要点:
- 使用 Vue3 的
<script setup>语法糖简化代码 - 采用 Element Plus 的表单组件实现登录界面
- 实现表单验证功能,包括必填项检查和长度限制
- 使用 Pinia 管理登录状态和 token
- 处理登录过程中的加载状态和错误提示
- 响应式设计,适配不同屏幕尺寸
2. 路由配置 index.js
import { createRouter, createWebHistory } from 'vue-router'
import LoginVue from '@/views/Login.vue'
import LayoutVue from '@/views/Layout.vue'
import { useTokenStore } from '@/stores/token'
import ManagerDeviceManageVue from '@/views/manager/ManagerDeviceManage.vue'
import EngineerRepairOrderVue from '@/views/engineer/EngineerRepairOrder.vue'
import EngineerCheckVue from '@/views/engineer/EngineerCheck.vue'
import MaintenancePlanVue from '@/views/engineer/MaintenancePlan.vue'
import InspectionPlanVue from '@/views/engineer/InspectionPlan.vue'
import UserAvatarVue from '@/views/center/UserAvatar.vue'
import UserInfoVue from '@/views/center/UserInfo.vue'
import UserResetPasswordVue from '@/views/center/UserResetPassword.vue'
import InspectionOrderVue from '@/views/engineer/InspectionOrder.vue'
import MaintenanceOrderVue from '@/views/engineer/MaintenanceOrder.vue'
import CheckOrderVue from '@/views/engineer/CheckOrder.vue'
const routes = [
{
path: '/login',
name: 'login', // 添加路由名称
component: LoginVue
},
{
path: '/',
component: LayoutVue,
redirect: '/managerDeviceManage',
children: [
{
path: '/managerDeviceManage',
name: 'managerDeviceManage',
component: ManagerDeviceManageVue
},
{
path: '/engineerRepairOrder',
name: 'engineerRepairOrder',
component: EngineerRepairOrderVue
},
{
path: '/EngineerCheck',
name: 'EngineerCheck',
component: EngineerCheckVue
},
{
path: '/MaintenancePlan',
name: 'MaintenancePlan',
component: MaintenancePlanVue
},
{
path: '/InspectionPlan',
name: 'InspectionPlan',
component: InspectionPlanVue
},
{
path: '/inspectionOrder',
name: 'inspectionOrder',
component: InspectionOrderVue
},
{
path: '/maintenanceOrder',
name: 'maintenanceOrder',
component: MaintenanceOrderVue
},
{
path: '/checkOrder',
name: 'checkOrder',
component: CheckOrderVue
},
{
path: '/user/avatar',
name: 'UserAvatar',
component: UserAvatarVue
},
{
path: '/user/info',
name: 'UserInfo',
component: UserInfoVue
},
{
path: '/user/resetPassword',
name: 'UserResetPassword',
component: UserResetPasswordVue
},
]
},
]
const router = createRouter({
history: createWebHistory(),
routes: routes
})
export default router
技术要点:
- 使用 Vue Router 4 的路由配置
- 实现嵌套路由,Layout 作为主框架包含子路由
- 采用路由懒加载提高性能
- 使用命名路由方便编程式导航
- 基于 HTML5 History 模式的路由
3. API 请求模块 user.js
import request from '@/utils/request'
import { useTokenStore } from '@/stores/token.js'
import useUserInfoStore from '@/stores/userInfo'
// 用户登录
export const userLoginService = (loginData) => {
return request({
url: '/login',
method: 'post',
data: loginData
})
}
// 获取用户信息
export const userInfoService = () => {
return request({
url: '/userInfo',
method: 'get'
})
}
// 重置密码
export const userPasswordUpdateService = (data) => {
// 直接使用前端表单数据,因为字段名已经匹配后端要求
return request({
url: '/updatePwd',
method: 'post',
data: {
old_pwd: data.old_pwd,
new_pwd: data.new_pwd,
re_pwd: data.re_pwd
}
})
}
技术要点:
- 封装 API 请求模块,统一管理接口
- 使用 async/await 处理异步请求
- 接口参数规范化
- 与后端 RESTful API 对接
4. 主布局组件 Layout.vue
<template>
<el-container class="layout-container">
<!-- 左侧菜单 -->
<el-aside width="200px">
<div class="el-aside__logo">运维管理子系统</div>
<el-menu active-text-color="#ffd04b" background-color="#232323" text-color="#fff" router>
<el-menu-item index="/managerDeviceManage">
<el-icon>
<Management />
</el-icon>
<span>设备管理</span>
</el-menu-item>
<el-menu-item index="/engineerRepairOrder">
<el-icon>
<Tools />
</el-icon>
<span>维修管理</span>
</el-menu-item>
<!-- 计划管理子菜单 -->
<el-sub-menu>
<template #title>
<el-icon>
<Calendar />
</el-icon>
<span>计划管理</span>
</template>
<el-menu-item index="/EngineerCheck">
<el-icon>
<Promotion />
</el-icon>
<span>检测计划管理</span>
</el-menu-item>
<el-menu-item index="/MaintenancePlan">
<el-icon>
<Calendar />
</el-icon>
<span>保养计划管理</span>
</el-menu-item>
<el-menu-item index="/InspectionPlan">
<el-icon>
<List />
</el-icon>
<span>巡检计划管理</span>
</el-menu-item>
</el-sub-menu>
<!-- 工单管理子菜单 -->
<el-sub-menu>
<template #title>
<el-icon>
<Document />
</el-icon>
<span>工单管理</span>
</template>
<el-menu-item index="/inspectionOrder">
<el-icon>
<Document />
</el-icon>
<span>巡检工单管理</span>
</el-menu-item>
<el-menu-item index="/maintenanceOrder">
<el-icon>
<Document />
</el-icon>
<span>保养工单管理</span>
</el-menu-item>
<el-menu-item index="/checkOrder">
<el-icon>
<Document />
</el-icon>
<span>检测工单管理</span>
</el-menu-item>
</el-sub-menu>
<!-- 子菜单 -->
<el-sub-menu>
<template #title>
<el-icon>
<UserFilled />
</el-icon>
<span>个人中心</span>
</template>
<el-menu-item index="/user/resetPassword">
<el-icon>
<EditPen />
</el-icon>
<span>重置密码</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<!-- 右侧主区域 -->
<el-container>
<!-- 头部区域 -->
<el-header>
<div>运维管理:<strong>{{ userInfoStore.info?.username || '未登录' }}</strong></div>
<!-- 下拉菜单 -->
<el-dropdown placement="bottom-end" @command="handleCommand" v-if="userInfoStore.info">
<span class="el-dropdown__box">
<!-- avatar头像 -->
<el-avatar :src="userInfoStore.info?.userPic || avatar" />
<el-icon>
<CaretBottom />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="resetPassword" :icon="EditPen">重置密码</el-dropdown-item>
<el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-header>
<!-- 中间区域 -->
<el-main>
<router-view></router-view>
</el-main>
<!-- 底部区域 -->
<el-footer>运维管理 ©2023 Created by 石家庄铁道大学</el-footer>
</el-container>
</el-container>
</template>
<script setup>
import {
Management,
Promotion,
Tools,
UserFilled,
User,
Crop,
EditPen,
SwitchButton,
CaretBottom,
Calendar,
List,
Setting,
Document
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
import { userInfoService } from '@/api/user.js'
import useUserInfoStore from '@/stores/userInfo.js'
import { useTokenStore } from '@/stores/token.js'
import { useRouter } from 'vue-router'
import { ElMessageBox, ElMessage } from 'element-plus'
const tokenStore = useTokenStore();
const userInfoStore = useUserInfoStore();
const router = useRouter();
// 调用函数获取用户信息
const getUserInfo = async () => {
try {
// 调用接口
let result = await userInfoService();
if (result && result.data) {
// 数据存储到pinia中
userInfoStore.setInfo(result.data);
} else {
console.warn('获取用户信息失败:返回数据为空');
// 如果获取用户信息失败,跳转到登录页
router.push('/login');
}
} catch (error) {
console.error('获取用户信息失败:', error);
// 如果获取用户信息失败,跳转到登录页
router.push('/login');
}
}
// 检查是否有token,有则获取用户信息
if (tokenStore.token) {
getUserInfo();
} else {
// 如果没有token,直接跳转到登录页
router.push('/login');
}
// 条目被点击后,调用的函数
const handleCommand = (command) => {
// 判断指令
if (command === 'logout') {
// 退出登录,清除token,跳转到登录页面
ElMessageBox.confirm(
'你确认要退出吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
// 退出登录
// 1.清空pinia中存储的token以及个人信息
tokenStore.removeToken()
userInfoStore.removeInfo();
// 2.跳转到登录页面
router.push('/login')
ElMessage({
type: 'success',
message: '退出登录成功',
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '用户取消退出登录',
})
})
} else {
// 路由
router.push('/user/' + command)
}
}
</script>
<style lang="scss" scoped>
.layout-container {
height: 100vh;
.el-aside {
background-color: #232323;
position: relative;
min-height: auto;
padding-bottom: 10px; // 添加底部内边距控制黑色区域
&__logo {
height: 100px;
margin: 0 auto;
padding: 5px 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff; // 白色文字
font-size: 18px; // 字体大小
font-weight: bold; // 加粗
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); // 文字阴影增加可读性
letter-spacing: 2px; // 字间距
border-radius: 4px; // 圆角
margin: 10px; // 边距
}
.el-menu {
border-right: none;
margin-top: -30px; // 上移菜单减少间距
padding: 5px 0; // 调整菜单内边距
}
}
.el-header {
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.el-dropdown__box {
display: flex;
align-items: center;
cursor: pointer;
.el-avatar {
width: 40px;
height: 40px;
border: 2px solid #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.el-icon {
color: #999;
margin-left: 10px;
transition: transform 0.3s;
}
&:hover .el-icon {
transform: rotate(180deg);
}
&:active,
&:focus {
outline: none;
}
}
}
.el-footer {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #666;
background-color: #f5f5f5;
padding: 15px 0;
}
}
</style>
技术要点:
- 使用 Element Plus 的布局组件构建整体框架
- 实现动态导航菜单,支持多级菜单
- 用户头像和下拉菜单交互
- 路由视图渲染
- 用户登录状态管理和权限控制
- 响应式设计和样式优化
学到的关键技术
1. Vue3 组合式 API
- 使用
<script setup>语法简化组件代码 - 响应式数据管理 (ref, reactive)
- 生命周期钩子的使用
- 组合式函数的封装和复用
2. Pinia 状态管理
- 集中管理应用状态 (token, 用户信息)
- 状态持久化 (localStorage)
- 模块化的 store 设计
- 与 Vue 组件的高效集成
3. Element Plus UI 组件库
- 表单组件和验证规则
- 消息提示和弹窗组件
- 布局系统和菜单组件
- 图标系统的使用
- 主题定制和样式覆盖
4. Vue Router 4
- 动态路由配置
- 导航守卫实现权限控制
- 嵌套路由布局
- 编程式导航
- 路由懒加载优化性能
5. 前端工程化
- API 请求的封装和错误处理
- 组件化开发思想
- SCSS 预处理器的使用
- 响应式设计原则
- 代码组织和模块划分
开发心得
通过这个项目的开发,我深入理解了 Vue3 的现代前端开发模式,掌握了组合式 API 的使用技巧,学会了如何构建一个完整的前端架构。特别是 Pinia 状态管理的使用,让我对前端状态管理有了更清晰的认识。
在开发过程中,我遇到了路由权限控制、表单验证、API 错误处理等挑战,通过查阅文档和实践,最终都得到了解决。Element Plus 组件库的使用大大提高了开发效率,同时也让我认识到 UI 组件库标准化的重要性。
这个项目让我对前端工程化有了更深的理解,特别是在项目结构组织、状态管理和组件设计方面积累了宝贵经验。未来我还计划加入更多的功能,如动态路由权限、主题切换等,进一步完善这个系统。

浙公网安备 33010602011771号