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 {// 使用相对路径引入图片// 直接将图片路径赋值给 backgroundImagesbackgroundImages.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-dialogtitle="修改密码"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; }