1.下载依赖
"@element-plus/icons": "^0.0.11", "@element-plus/icons-vue": "^2.3.1", "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/vue-fontawesome": "^3.0.8", "axios": "^1.7.9", "element-plus": "^2.9.3", "vue": "^3.5.13", "vue-router": "^4.5.0"
2.注册组件
import { createApp } from 'vue'; import App from './App.vue'; import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css'; import './assets/css/global.css'; import zhCn from 'element-plus/es/locale/lang/zh-cn'; // 导入所有需要的图标组件 import * as Icons from '@element-plus/icons-vue'; import {router} from "./router/router.js"; // 创建 Vue 应用实例 const app = createApp(App); // 使用路由 app.use(router); // 使用 Element Plus,并设置语言为中文 app.use(ElementPlus, { locale: zhCn, }); // 动态注册所有图标组件 Object.keys(Icons).forEach(key => { app.component(key, Icons[key]); }); // 挂载应用到 #app 元素上 app.mount('#app');
3.编写登陆界面
<template> <div class="login-container"> <!-- 背景轮播图 --> <el-carousel :interval="5000" indicator-position="none" class="background-carousel" height="100vh"> <el-carousel-item v-for="(image, index) in backgroundImages" :key="index"> <div class="carousel-image" :style="{ backgroundImage: `url(${image})` }"></div> </el-carousel-item> </el-carousel> <!-- 登录框 --> <div class="login-box"> <div class="title-container"> <h3 class="title">实验室信息管理系统</h3> </div> <el-form ref="formRef" :model="form" :rules="rules" class="login-form"> <el-form-item prop="number"> <el-input v-model="form.number" placeholder="请输入学工号" prefix-icon="User"></el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="form.password" placeholder="请输入密码" show-password prefix-icon="Lock"></el-input> </el-form-item> <div class="button-group"> <el-button type="primary" @click="handleLogin" class="login-btn">登 录</el-button> <el-button type="success" @click="goRegister" class="register-btn">注 册</el-button> </div> </el-form> </div> </div> </template> <script setup> import { ref, reactive } from 'vue'; import { ElMessage } from 'element-plus'; import { useRouter } from 'vue-router'; // 创建路由实例 const router = useRouter(); // 表单引用 const formRef = ref(null); // 表单数据 const form = reactive({ number: '', password: '' }); // 表单规则 const rules = reactive({ number: [ { required: true, message: '请输入学号/学工号', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' } ] }); // 背景图片数组 const backgroundImages = [ new URL('https://pic.meitukk.com/uploads/meitukk/dm/20230621/230621120150827.jpg').href, new URL('https://pic.meitukk.com/uploads/meitukk/dm/20230622/230622045223599.jpg').href, new URL('https://img.shetu66.com/2022/10/28/1666928035720956.jpg').href, new URL('https://n.sinaimg.cn/sinacn10116/600/w1920h1080/20190326/2c30-hutwezf6832339.jpg').href, new URL('https://c-ssl.duitang.com/uploads/blog/202303/15/20230315122347_db706.jpg').href ]; // 登录方法 const handleLogin = () => { formRef.value.validate((valid) => { if (valid) { // 这里添加登录逻辑,例如调用API进行验证 console.log('submit!'); ElMessage.success('登录成功'); // 假设登录成功后跳转到主页 router.push('/'); } else { console.log('error submit!!'); return false; } }); }; // 注册方法 const goRegister = () => { // 这里添加注册逻辑,例如跳转到注册页面 router.push('/register'); }; </script></script> <style scoped> .login-container { width: 100vw; /* 使用视口宽度 */ height: 100vh; /* 使用视口高度 */ overflow: hidden; position: relative; display: flex; justify-content: center; align-items: center; } .background-carousel { position: absolute; top: 0; left: 0; width: 100%; height: 100%; /* 修改为全屏高度 */ z-index: -1; /* 确保轮播图在最底层 */ overflow: hidden; } .carousel-image { width: 100%; height: 100%; background-size: cover; background-position: center; /* 确保背景图片居中 */ background-repeat: no-repeat; } .login-box { width: 400px; padding: 40px; background-color: rgba(255, 255, 255, 0.8); border-radius: 10px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); text-align: center; position: relative; z-index: 1; /* 确保登录框在轮播图之上 */ } .title-container { margin-bottom: 30px; } .title { color: #2c3e50; font-size: 26px; font-weight: bold; } .login-form { padding: 0 30px; } .button-group { display: flex; justify-content: space-between; } .login-btn, .register-btn { width: 48%; margin-top: 15px; } </style>
-
实现效果
4.编写注册页面
<template> <div class="register-container"> <!-- 背景轮播图 --> <transition name="fade" mode="out-in"> <el-carousel v-if="carouselLoaded" :interval="5000" indicator-position="none" class="background-carousel" height="100vh"> <el-carousel-item v-for="(image, index) in backgroundImages" :key="index"> <div class="carousel-image" :style="{ backgroundImage: `url(${image})` }"></div> </el-carousel-item> </el-carousel> </transition> <!-- 注册框 --> <div class="register-box"> <div class="title-container"> <h3 class="title">欢迎注册</h3> </div> <el-form ref="formRef" :model="form" :rules="rules" class="register-form" @submit.prevent> <el-form-item prop="number"> <el-input v-model="form.number" placeholder="请输入学工号"></el-input> </el-form-item> <el-form-item prop="nickname"> <el-input v-model="form.nickname" placeholder="请输入姓名"></el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="form.password" placeholder="请输入密码" show-password></el-input> </el-form-item> <el-form-item prop="confirm"> <el-input v-model="form.confirm" placeholder="请再次输入密码" show-password></el-input> </el-form-item> <div class="button-group"> <el-button type="primary" @click="handleRegister" class="register-btn">注 册</el-button> <el-button type="success" @click="goLogin" class="login-btn">登 录</el-button> </div> </el-form> </div> </div> </template><script setup> import {onMounted, reactive, ref} from 'vue'; import {useRouter} from 'vue-router'; import {ElMessage} from 'element-plus'; import axios from 'axios'; // 创建路由实例 const router = useRouter(); // 表单引用 const formRef = ref(null); // 表单数据 const form = reactive({ number: '', nickname: '', password: '', confirm: '' }); // 表单规则 const rules = reactive({ number: [ { required: true, message: '请输入学工号', trigger: 'blur' } ], nickname: [ { required: true, message: '请输入姓名', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' } ], confirm: [ { required: true, message: '请确认密码', trigger: 'blur' } ] }); // 背景图片加载状态 const carouselLoaded = ref(false); const backgroundImages = ref([]); onMounted(async () => { try { // 使用相对路径引入图片 // 直接将图片路径赋值给 backgroundImages backgroundImages.value = [ new URL('https://pic.meitukk.com/uploads/meitukk/dm/20230621/230621120150827.jpg').href, new URL('https://pic.meitukk.com/uploads/meitukk/dm/20230622/230622045223599.jpg').href, new URL('https://img.shetu66.com/2022/10/28/1666928035720956.jpg').href, new URL('https://n.sinaimg.cn/sinacn10116/600/w1920h1080/20190326/2c30-hutwezf6832339.jpg').href, new URL('https://c-ssl.duitang.com/uploads/blog/202303/15/20230315122347_db706.jpg').href ]; carouselLoaded.value = true; } catch (error) { console.error('Failed to load images:', error); } }); // 注册方法 const handleRegister = () => { if (form.password !== form.confirm) { ElMessage({ type: 'error', message: '密码不一致' }); return; } formRef.value.validate((valid) => { if (valid) { axios.post('http://localhost:9090/user/register', form) .then(res => { if (res.data.code === '0') { ElMessage({ type: 'success', message: '注册成功' }); router.push('/login'); } else { ElMessage({ type: 'error', message: res.data.msg }); } }) .catch(error => { console.error('There was an error!', error); ElMessage({ type: 'error', message: '服务器错误,请稍后再试' }); }); } }); }; // 登录方法 const goLogin = () => { router.push('/login'); }; </script><style scoped> .register-container { width: 100vw; /* 使用视口宽度 */ height: 100vh; /* 使用视口高度 */ overflow: hidden; position: relative; display: flex; justify-content: center; align-items: center; } .background-carousel { position: absolute; top: 0; left: 0; width: 100%; height: 100%; /* 修改为全屏高度 */ z-index: -1; /* 确保轮播图在最底层 */ overflow: hidden; } .carousel-image { width: 100%; height: 100%; background-size: cover; background-position: center; /* 确保背景图片居中 */ background-repeat: no-repeat; transition: opacity 0.5s ease; } .register-box { width: 400px; padding: 40px; background-color: rgba(255, 255, 255, 0.8); border-radius: 10px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); text-align: center; position: relative; z-index: 1; /* 确保注册框在轮播图之上 */ } .title-container { margin-bottom: 30px; } .title { color: #2c3e50; font-size: 30px; font-weight: bold; } .register-form { padding: 0 30px; } .button-group { display: flex; justify-content: space-between; margin-top: 20px; } .register-btn, .login-btn { width: 48%; } /* 隐藏前缀图标 */ .el-input__prefix { display: none; } </style>
-
实现效果
5.编写主页面
-
主界面父组件
<script setup> import Header from "../components/Header.vue"; import Aside from "../components/Aside.vue"; </script> <template> <div> <!-- 头部 --> <Header /> <!-- 主体 --> <div style="display: flex"> <!-- 侧边栏 --> <Aside /> <!-- 内容区域 --> <router-view style="flex: 1"/> </div> </div> </template> <style scoped> </style>
-
子组件侧边栏
<template> <div class="aside-container"> <el-menu default-active="1-1" class="el-menu-vertical-demo" :collapse="isCollapsed" @open="handleOpen" @close="handleClose"> <!-- 个人中心 --> <el-sub-menu index="1-1"> <template #title> <el-icon><User /></el-icon> <span>个人中心</span> </template> <el-menu-item index="1-1-1"> <el-icon><HomeFilled /></el-icon> <span>信息维护</span> </el-menu-item> </el-sub-menu> <!-- 系统管理 --> <el-sub-menu index="1-2"> <template #title> <el-icon><Setting /></el-icon> <span>系统管理</span> </template> <el-menu-item index="1-2-1"> <el-icon><VideoCamera /></el-icon> <span>设备管理</span> </el-menu-item> <el-menu-item index="1-2-2"> <el-icon><Mic /></el-icon> <span>学生管理</span> </el-menu-item> <el-menu-item index="1-2-3"> <el-icon><IceTea /></el-icon> <span>实验室管理</span> </el-menu-item> </el-sub-menu> <!-- 记录查询 --> <el-sub-menu index="1-3"> <template #title> <el-icon><MessageBox /></el-icon> <span>记录查询</span> </template> <el-menu-item index="1-3-1"> <el-icon><Document /></el-icon> <span>设备申请记录</span> </el-menu-item> <el-menu-item index="1-3-2"> <el-icon><DeleteFilled /></el-icon> <span>设备报废记录</span> </el-menu-item> <el-menu-item index="1-3-3"> <el-icon><TrendCharts /></el-icon> <span>设备借出记录</span> </el-menu-item> <el-menu-item index="1-3-4"> <el-icon><Calendar /></el-icon> <span>实验室预约记录</span> </el-menu-item> </el-sub-menu> <!-- 设备相关申请 --> <el-sub-menu index="1-4"> <template #title> <el-icon><SetUp /></el-icon> <span>设备相关申请</span> </template> <el-menu-item index="1-4-1"> <el-icon><Delete /></el-icon> <span>设备报废申请</span> </el-menu-item> <el-menu-item index="1-4-2"> <el-icon><Tickets /></el-icon> <span>设备使用申请</span> </el-menu-item> <el-menu-item index="1-4-3"> <el-icon><CircleCheckFilled /></el-icon> <span>设备归还申请</span> </el-menu-item> </el-sub-menu> <!-- 实验室相关申请 --> <el-sub-menu index="1-5"> <template #title> <el-icon><Plus /></el-icon> <span>实验室相关申请</span> </template> <el-menu-item index="1-5-1" > <el-icon><Ticket /></el-icon> <span>预约实验室</span> </el-menu-item> <el-menu-item index="1-5-2"> <el-icon><CircleCloseFilled /></el-icon> <span>归还实验室</span> </el-menu-item> </el-sub-menu> <!-- 审批进度 --> <el-sub-menu index="1-6"> <template #title> <el-icon><Check /></el-icon> <span>审批进度</span> </template> <el-menu-item index="1-6-1"> <el-icon><InfoFilled /></el-icon> <span>设备申请审批</span> </el-menu-item> <el-menu-item index="1-6-2"> <el-icon><DeleteFilled /></el-icon> <span>设备报废审批</span> </el-menu-item> <el-menu-item index="1-6-3"> <el-icon><Ticket /></el-icon> <span>实验室预约审批</span> </el-menu-item> </el-sub-menu> <!-- 数据可视化 --> <el-sub-menu index="1-8"> <template #title> <el-icon><PieChart /></el-icon> 设备数据可视化 </template> <el-menu-item index="1-8-1"> <el-icon><DataAnalysis /></el-icon> 设备数据可视化 </el-menu-item> </el-sub-menu> </el-menu> </div> </template> <script setup> import { ref } from 'vue'; import { Setting, User, HomeFilled, VideoCamera, Mic, IceTea, MessageBox, Document, DeleteFilled, TrendCharts, Calendar, SetUp, Delete, Tickets, CircleCheckFilled, Plus, Ticket, CircleCloseFilled, Check, InfoFilled } from '@element-plus/icons-vue'; // 控制菜单折叠状态 const isCollapsed = ref(false); // 处理子菜单打开事件 const handleOpen = (key, keyPath) => { console.log('opened sub menu:', key, keyPath); }; // 处理子菜单关闭事件 const handleClose = (key, keyPath) => { console.log('closed sub menu:', key, keyPath); }; </script> <style scoped> .aside-container { width: 200px; transition: width 0.3s ease; } .el-menu-vertical-demo:not(.el-menu--collapse) { width: 200px; min-height: 400px; } .el-menu { border-right: none; } .el-menu-item [class^=el-icon-]+span { margin-left: 8px; } .menu-item-group-title { padding: 8px 20px; color: #a8abb2; font-size: 12px; line-height: 24px; background-color: #f5f7fa; } .el-menu-item.is-active { background-color: #ecf5ff; color: #409eff; } .el-sub-menu__title:hover, .el-menu-item:hover { background-color: #eef1f6 !important; } @media (max-width: 768px) { .aside-container { width: 64px; } .el-menu-vertical-demo:not(.el-menu--collapse) { width: 64px; } .el-sub-menu__title span, .el-menu-item span { display: none; } .el-sub-menu__icon-arrow { display: none; } } </style>
-
子组件头部导航栏
<template> <header class="app-header"> <div class="header-content"> <div class="logo">实验室信息管理系统</div> <div class="user-action"> <el-dropdown trigger="click" @command="handleCommand"> <span class="el-dropdown-link user-profile" @click.stop> <el-avatar :size="30" :src="defaultAvatar" style="vertical-align: middle;"></el-avatar> <span class="username">{{ username }}</span> <el-icon class="el-icon--right"><arrow-down /></el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="changePassword">修改密码</el-dropdown-item> <el-dropdown-item command="logout">退出系统</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> <!-- 修改密码对话框 --> <el-dialog title="修改密码" v-model="dialogVisible" width="400px" center @close="resetForm" > <el-form ref="passwordForm" :model="form" :rules="rules" label-width="100px"> <el-form-item label="旧密码" prop="oldPassword"> <el-input v-model="form.oldPassword" type="password"></el-input> </el-form-item> <el-form-item label="新密码" prop="newPassword"> <el-input v-model="form.newPassword" type="password"></el-input> </el-form-item> <el-form-item label="确认密码" prop="confirmPassword"> <el-input v-model="form.confirmPassword" type="password"></el-input> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="submitForm('passwordForm')">确定</el-button> </span> </template> </el-dialog> </div> </div> </header> </template> <script setup> import { ref, reactive } from 'vue'; import { ArrowDown } from '@element-plus/icons-vue'; // 假定用户名称和默认头像链接 const username = ref('张三'); const defaultAvatar = ref('https://pic.meitukk.com/uploads/meitukk/dm/20230621/230621120150827.jpg'); // 对话框可见性控制 const dialogVisible = ref(false); // 表单数据和验证规则 const form = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' }); const rules = { oldPassword: [{ required: true, message: '请输入旧密码', trigger: 'blur' }], newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }], confirmPassword: [ { required: true, message: '请再次输入新密码', trigger: 'blur' }, ({ value }, callback) => { if (value !== form.newPassword) { callback(new Error('两次输入的密码不一致')); } else { callback(); } } ] }; // 提交表单的方法 const submitForm = (formName) => { // 这里可以添加提交逻辑,例如发送请求到服务器等 console.log('Submit:', form); dialogVisible.value = false; }; // 重置表单的方法 const resetForm = () => { form.oldPassword = ''; form.newPassword = ''; form.confirmPassword = ''; }; // 下拉菜单命令处理 const handleCommand = (command) => { if (command === 'changePassword') { dialogVisible.value = true; } else if (command === 'logout') { // 处理登出逻辑 console.log('Logout'); } }; </script> <style scoped> .app-header { height: 60px; line-height: 60px; background-color: #304156; color: #F8F8FF; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .header-content { width: 100%; display: flex; align-items: center; justify-content: space-between; } .logo { font-weight: bold; font-size: 18px; } .user-action { display: flex; align-items: center; } .user-profile { cursor: pointer; display: flex; align-items: center; color: #F8F8FF; } .username { margin-left: 8px; } .el-dropdown-link { display: flex; align-items: center; } @media (max-width: 768px) { .logo { font-size: 16px; } .user-action { flex-direction: column; align-items: flex-end; } .user-profile { flex-direction: column; align-items: flex-start; } .el-avatar { margin-bottom: 5px; } } /* 新增样式 */ .el-dropdown-menu__item { text-align: left; } .dialog-footer { text-align: right; } .el-dialog__header { background-color: #f5f7fa; padding: 16px 20px; border-bottom: 1px solid #ebeef5; } .el-dialog__body { padding: 20px; } .el-form-item { margin-bottom: 18px; } .el-input__inner { border-radius: 4px; } </style>
-
实现效果
6.编写路由
import { createRouter, createWebHistory } from 'vue-router' import LayOut from "../layout/LayOut.vue"; import Login from "../views/Login.vue"; import Register from "../views/Register.vue"; export const router = createRouter({ history: createWebHistory(), routes:[ { path: '/', name: 'Layout', component: LayOut, }, { path:'/login', name:'Login', component:Login }, { path:'/register', name:'Register', component:Register } ] })
7.全局样式
*{ margin: 0; padding: 0; box-sizing: border-box; }