搭建前后端分离项目
1. vue-cli 创建 vue 工程
1.1 打开 cmd 命令提示符进入到存放项目的目录,在命令提示符上使用 “ vue create 项目名” 命令创建项目。
1.2 之后可以选择 vue 版本,虽然现在 vue2 已经停更,但是还是值得学习的。
1.3 等待初始化 vue 脚手架后,会出现蓝色的两条命令,依次执行它们即可启动 vue。vue脚手架启动成功后会出现蓝色的两个网址,无论是哪个网址都可以打开页面。
1.4 浏览器输入网址后界面如下
2. 使用 elementUI
2.1 在该项目的根目录安装 elementUI , 执行以下命令即可安装
npm i element-ui -S
2.2 在 src/main.js 中引入 elementUI 及 全局注册它:
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';Vue.use(ElementUI);new Vue({el: '#app',render: h => h(App)
});
2.3 之后我在 HelloWord.vue 组件中测试 elementUI 是否可以正常使用。这里简单添加一个按钮测试。
重启项目后看到按钮出现了,说明elementUI 成功引入进来了。
但是按钮还有一定的边距,我们可以 在assets 文件夹中添加一个全局样式清除默认样式。
3. 关闭 esline 语法检查
ESLint 是一个用于识别和报告 JavaScript 代码中问题的静态代码分析工具。它能够帮助开发者发现潜在的语法错误、代码风格问题以及不符合最佳实践的代码。但这也为我们开发带来诸多不便,如提前引入一个未使用的变量也会使得代码不能运行,因此在开发过程中我将其关闭。只需要在vue.config.js 中设置 lintOnSave: false 即可。
4. 登录模块
3.1 在 src/view 目录下新建一个 login 目录,创建login 组件
整体代码如下
<template><!-- 登录页面 --><div><el-form ref="form" :model="loginForm" :rules="loginRules" class="loginContainer"><h3 class="formTitle">系统登录</h3><el-form-item prop="username"><el-input type="text" autocomplate="false" v-model="loginForm.username" placeholder="请输入用户名"></el-input></el-form-item><el-form-item prop="password"><el-input type="password" autocomplate="false" v-model="loginForm.password" placeholder="请输入密码"></el-input></el-form-item><el-form-item prop="code"><el-input type="text" class="codeInput" autocomplate="false" v-model="loginForm.code" placeholder="点击图片更换验证码"></el-input><img :src="captchaUrl"></el-form-item><el-form-item prop="isAgree"><el-checkbox class="loginRember" v-model="loginForm.isAgree">用户平台使用协议</el-checkbox><el-button type="primary" class="loginBtn" @click="submitForm">登录</el-button></el-form-item></el-form></div>
</template><script>
export default {name: 'Login',data() {return {// 表单字段loginForm: {username: '', // 用户名password: '', // 密码code: '', // 验证码isAgree: false, // 用户平台使用协议},captchaUrl: '', // 验证码图片地址// 校验规则loginRules: {username: [{required: true, // required只能检测 null undefined ""message: '请输入用户名',trigger: 'blur'}],password: [{required: true,message: '请输入密码',trigger: 'blur'}],code: [{required: true,message: '请输入验证码',trigger: 'blur'}],isAgree: [{validator: (rule, value, callback) => {// rule校验规则// value 校验的值// callback 函数 - promise resolve reject// callback() callback(new Error(错误信息))value ? callback() : callback(new Error('您必须勾选用户的使用协议'))}}]}}},methods: {// 登录方法submitForm() {this.$refs.form.validate((valid) => {if (valid) {console.log('se')} else {this.$message.error('请输入所有字段')return false;}})}}
}
</script><style>
/* 登录表单 */
.loginContainer {width: 350px;padding: 15px 35px;margin: 80px auto;background-clip: padding-box;background-color: #fff;border-radius: 15px;border: 1px solid #eaeaea;box-shadow: 0 0 25px #cac6c6;
}
/* 表单标题 */
.formTitle {text-align: center;margin: 0 0 20px 0;
}
/* 验证码输入框 */
.codeInput {width: 65%;
}
/* 记住我 */
.loginRember {margin: 0 0 5px 0;
}
/* 登录按钮 */
.loginBtn {width: 100%
}
</style>
3.2 下载路由。如果是vue2项目,指定版本为3,因为现在脚手架默认的路由版本是适配vue3项目的,因此需要指定路由版本。
3.3 在src/router 目录下新建一个 index.js 文件,用于配置路由信息。
import Vue from 'vue'
import vueRouter from 'vue-router'Vue.use(vueRouter)// 静态路由
const constantRoutes = [{ path: '/login',name: 'Login',component: () => import('@/view/login/Login')}
]const router = new vueRouter({// mode: 'history', // 两种模式, hash 、 history// scrollBehavior: () => ({ y: 0 }),routes: constantRoutes // 默认引入静态路由
})export default router
运行效果如下:
5. 数据持久化
1. 在 src 目录下创建一个 modules 文件夹,是存放每个模块的仓库。然后在其 modules 目录下创建一个 user.js 文件。
2. 在src/store 目录下 新建一个index.js 文件作为主文件。将 user.js 导入到 index.js 中。
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import user from '@/store/modules/user'Vue.use(Vuex)const store = new Vuex.Store({modules: {user},getters
})export default store;
目录结构如下所示
在登录页调用派发登录接口
登录后token已经存入进来了,但是刷新后token又立马变为初始值。这就要用到数据持久化了。
只要一刷新token就会消失,这个没有做数据持久化处理,下面就来解决这个问题。
1. 首页下载 js-cookie
npm i js-cookie
2. 在src/utils 目录下创建一个 auth.js 文件,包含三个方法,分别是 获取、设置、移除token。
import Cookies from 'js-cookie'const TokenKey = 'userToken'; // token 的key值,移除、添加token需要用到// 获取 token
export function getToken() {return Cookies.get(TokenKey)
}// 设置token
export function setToken(token) {return Cookies.set(TokenKey, token)
}// 移除token
export function removeToken() {return Cookies.remove(TokenKey)
}
3. 在src/store/modules 目录下的user.js 文件引入 atuh.js 的三个方法(添加、获取、移除token)。
import { getToken, setToken, removeToken } from '@/utils/auth'const state = {token: getToken() // 从缓存中获取 token
}
const mutations = {// 设置tokensetToken(state, token) {state.token = token;setToken(token); // 把 token 设置到缓存中},removeToken() {// 删除vuex的tokenstate.token = null;removeToken()}
}
const actions = {// 登录async login(context, data) {// console.log(data)// const token = await reqLogin(data); // 调用登录接口context.commit('setToken', 'token123')}
}
export default {// 开启命名空间, 以后调用 actions 的方法需要带上文件名+方法名(如 user/login)namespaced: true, state,mutations,actions
}
登录后刷新发现vuex中的token还是存在,在浏览器控制台上的"应用"模块下找到 Cookie,发现token已经存入到本地了。这就已经完成了数据持久化。后续token如何得到,需要后端返回给前端,并不是和现在的代码一样固定写死的。
6. vue-cli 代理解决跨域
let proxyObj = {}proxyObj ['/'] = {// websocketws: false,// 目标地址target: 'http://localhost:8083',// 发送请求时host会被设置成targetchangeOrigin: true,// 不重写请求地址pathReWrite: {'^/':'/'}
}
module.exports = {lintOnSave: false, // 关闭 esline 语法检查devServer: {host: 'localhost',port: 8080,proxy: proxyObj}
}
7. 封装配置拦截器
上面已经做完了表单校验工作,接下来我们需要请求后端接口实现登录功能。这里使用 axios 发送请求。
1. 安装 axios。使用命令行进入到该项目的根目录。使用 npm i axios 命令安装axios
安装完毕后可以在 package.json 的 dependencies 中查看本项目安装的所有依赖
2. 在 src/
8. 主体布局
8.1 布局搭建
在 elementUI 中有常见的布局,我们可以直接拿来使用。
1. 直接把上面的框架复制到 src/view/home/index.vue 组件中
<el-container><el-aside width="200px">Aside</el-aside><el-container><el-header>Header</el-header><el-main>Main</el-main></el-container>
</el-container>
2. 为侧边栏添加菜单选项,其中@select 是选择菜单项后跳转到对应页面的方法。
<aside style="width: 200px"><el-menu @select="menuClick"><el-submenu index="1"><template slot="title"><i class="el-icon-location"></i>导航一</template><el-menu-item index="/test1">选项1</el-menu-item><el-menu-item index="/test2">选项2</el-menu-item></el-submenu></el-menu>
</aside>
methods: {// 选择侧边栏选项跳转到对应路由menuClick(index) {this.$router.push(index)}
}
3. 在<el-main>标签下设置路由出口,即使用 <router-view>标签
4. 在 src/view下创建两个页面,分别是test1,test2。接着在路由配置文件src/router/index.js 文件中配置路由信息。
5. 重新启动项目运行发现,路由可以跳转,但页面不是显示在home/index组件的 main 标签中,如图5.1。这是因为在 src/App.vue 中也设置了一个路由出口,有两个路由出口时,vue默认了App.vue中的路由出口如图 5.2 。
图 5.1
图 5.2
解决这个问题也简单。只需要将路由配置放在Home组件中,把这些路由当作Home组件的子路由即可。
8.2 侧边栏动态添加节点
上面这种方式虽然实现了路由跳转到对应界面,但是每添加一个菜单选项,都需要在侧边栏添加对应的菜单信息。我们可以利用路由信息遍历到菜单选项中,这样以后我们只要在路由配置信息src/router/index 中维护,侧边菜单栏选项会根据路由信息动态渲染生成。
8.2.1 改造 src/router/index.js 文件。
在path同级目录中添加一个meta元字段,目前先实现title(侧边导航栏名称)和 icon(侧边导航栏图标) 两个字段的功能。
8.2.2 将src/home/index 组件的 @select 方法移除。遍历路由信息。在$router的options.routes可以获取到路由的全部信息。在实现的时候可以在钩子函数中打印,方便查看。
为了可扩展性,先将页面拆分成多个组件,例如,侧边栏作为一个组件,导航栏作为一个组件。在src/home目录中新建一个components文件夹,在components文件夹下建一个LeftAside.vue组件做侧边栏组件。
注意:需要在 <el-muen>的根标签上添加 router属性,否则路由不能跳转。
<template><!-- 侧边栏菜单 --><div class="container"><el-menurouterdefault-active="/home"class="el-menu-vertical-demo"@open="handleOpen"@close="handleClose"background-color="#545c64"text-color="#fff"active-text-color="#ffd04b"><!-- 一级菜单(无子菜单) --><el-menu-itemv-for="(item, index1) in noChild" :key="index1"v-show="!item.hidden":index="item.path"><i v-if="item.meta" :class="item.meta.icon"></i><span class="firstMenu" v-if="item.meta" slot="title">{{ item.meta.title}}</span></el-menu-item><!-- 一级菜单 (有子菜单) --><el-submenu v-for="(hasChildrenItem, index2) in hasChild":key="`key` + index2":index="`key` + index2"><!-- 一级菜单 --><template slot="title"><i v-if="hasChildrenItem.meta" :class="hasChildrenItem.meta.icon"></i><span class="firstMenu" v-if="hasChildrenItem.meta">{{ hasChildrenItem.meta.title }}</span></template> <!-- 二级子菜单 --><el-menu-item v-for="(childrenItem) in hasChildrenItem.children":key="childrenItem.path":index="childrenItem.path"><i v-if="childrenItem.meta" :class="childrenItem.meta.icon"></i> <span class="secondMenu">{{ childrenItem.meta.title }}</span></el-menu-item></el-submenu></el-menu></div>
</template><script>
export default {name: 'left_aside',methods: {handleOpen(key, keyPath) {// console.log('open', key, keyPath);},handleClose(key, keyPath) {// console.log('close', key, keyPath);}, },computed: {// 没有子节点noChild() {return this.$router.options.routes.filter(item => !item.children)},// 有子节点hasChild() {return this.$router.options.routes.filter(items => items.children)}}
}
</script><style>
/* 侧边栏容器 */
.container {width: 200px;height: 100vh;background-color: #545c64;
}
/* 一级菜单名称 */
.firstMenu {position: absolute;top: 0;left: 45px;
}
/* 二级菜单 */
.secondMenu {position: absolute;top: 0;left: 65px;
}
/* 重写 侧边栏样式 */
.el-menu {border-right: none !important;
}
</style>
在src/home/index 中引入 leftAside 组件。index.vue 组件内容如下所示。
<template><!-- 系统整体布局 --><el-container><!-- 左侧侧边栏 --><el-aside width="200px"><LeftAside></LeftAside></el-aside><!-- 右侧 --><el-container><!-- 右侧导航栏 --><el-header>Header</el-header><!-- 主体内容 --><el-main><!-- 路由出口 --><router-view></router-view></el-main></el-container></el-container>
</template><script>
import LeftAside from './components/LeftAside.vue' // 侧边栏组件
export default {name: 'home',components: { LeftAside }, // 注册组件
}
</script><style>
/*设置距离屏幕的边距*/
body {margin: 0;padding: 0;/* 隐藏溢出 清除浮动*/overflow: hidden;}/*把所有的元素变成盒状模型:应用“box-sizing: border-box;”样式后,盒子border和padding的参数值是被包含在width和height之内的。*/
* {/*外边距不会额外占用1px的像素*/box-sizing: border-box;}
</style>
效果如下
8.3 优化侧边栏
虽然上边已完成基本布局,但会发现布局是放在首页的,但点击首页时如何加载首页的组件呢?下边将解决这一个问题,就是将系统布局作为一个一级路由隔离开(当作公共组件),其余的页面作为二级、三级路由显示。
1. 在 src/layout 目录下新建一个 index.vue 组件,将系统整体布局架构移动到 index.vue 组件中。
<template><!-- 系统整体布局 --><el-container><!-- 左侧侧边栏 --><el-aside width="200px"><LeftAside></LeftAside></el-aside><!-- 右侧 --><el-container><!-- 头部导航栏 --><el-header><Nav></Nav></el-header><!-- 主体内容 --><el-main><!-- 路由出口 --><router-view></router-view></el-main></el-container></el-container>
</template><script>
import LeftAside from '@/layout/components/LeftAside.vue' // 侧边栏组件
import Nav from '@/layout/components/Nav.vue' // 头部导航栏组件
export default {name: 'Layout',components: { LeftAside, Nav }, // 注册组件
}
</script><style></style>
2. 在 src/layout 目录下新建一个 components 文件夹, 再新建一个 LeftAside.vue 组件存放侧边栏信息。
<template><!-- 侧边栏菜单 --><div class="container"><el-menurouterdefault-active="/home"class="el-menu-vertical-demo"@open="handleOpen"@close="handleClose"background-color="#545c64"text-color="#fff"active-text-color="#ffd04b"><!-- 系统名称和图标 --><div class="asideHeader"><img class="sysLogo" src="@/assets/logo.png"><span class="sysTitle">后台管理系统</span></div><!-- 遍历路由信息 --><div v-for="route in filterRoutes" :key="route.path"><!-- 如果有子路由 --><div v-for="parentItem in route.children":key="parentItem.path":index="parentItem.path"><!-- 父节点 --><el-submenu v-if="parentItem.children" :index="parentItem.path" :key="parentItem.path"><template slot="title"><i v-if="parentItem.meta && parentItem.meta.icon" :class="parentItem.meta.icon"></i><span class="firstMenu">{{ parentItem.meta ? parentItem.meta.title : parentItem.name }}</span></template><!-- 子节点 --><el-menu-itemv-for="childItem in parentItem.children":index="childItem.path":key="childItem.path"><i v-if="childItem.meta && childItem.meta.icon" :class="childItem.meta.icon"></i><span class="secondMenu">{{ childItem.meta ? childItem.meta.title : childItem.name }}</span></el-menu-item></el-submenu><!-- 没有子路由 --><el-menu-item v-else :index="parentItem.path" :key="parentItem.path"><i v-if="parentItem.meta && parentItem.meta.icon" :class="parentItem.meta.icon"></i><span class="firstMenu">{{ parentItem.meta ? parentItem.meta.title : parentItem.name }}</span></el-menu-item></div></div></el-menu></div>
</template><script>
export default {name: 'left_aside',methods: {handleOpen(key, keyPath) {// console.log('open', key, keyPath);},handleClose(key, keyPath) {// console.log('close', key, keyPath);}, }, computed: {// 有子节点filterRoutes() {return this.$router.options.routes.filter(route => !route.hidden)}}
}
</script><style>
/* 侧边栏容器 */
.container {width: 200px;height: 100vh;background-color: #545c64;
}
/* 侧边栏头部 */
.asideHeader {background-color: #4c555e;display: flex;box-sizing: border-box;align-items: center;height: 60px;padding: 0 10px;
}
.sysLogo {width: 20px;height: 20px;padding-right: 5px;
}
.sysTitle {font-size: 24px;font-family: 华文楷体;color: #fff;font-weight: bold;
}
/* 一级菜单名称 */
.firstMenu {position: absolute;top: 0;left: 45px;
}
/* 二级菜单 */
.secondMenu {position: absolute;top: 0;left: 65px;
}
/* 重写 侧边栏样式 */
.el-menu {border-right: none !important;
}
.el-aside {overflow: hidden !important;
}
</style>
3. 在 src/router/index.js 重新调整路由配置。由于一级路由是公共的布局(Layout组件),二级路由仅仅只是作为三级路由的一个分组作用而已,并没有实质的组件。三级路由才是要显示在页面上的组件。那么二级路由如何渲染组件呢?如果二级渲染失败,三级路由也不可能渲染。这里是在二级路由上重新渲染路由出口,即
component: { render: h => h('router-view') },
import Vue from 'vue'
import vueRouter from 'vue-router'
import Layout from '@/layout/index' // 布局Vue.use(vueRouter)// 静态路由
const constantRoutes = [{ path: '/login',name: 'Login',hidden: true, // 在Home组件中判断了,如果属性hidden为true,则不显示在侧边栏上component: () => import('@/view/login/login')},{path: '/',component: Layout, // 一级路由redirect: '/home',children: [{path: '/home',name: 'Home',component: () => import('@/view/home/index'),meta: {title: '首页',// icon: 'el-icon-location'}}]},{ path: 'sysManger', // 系统管理name: 'sysManger',component: Layout,children: [{path: '', // 二级路由地址为空,表示显示一级路由 + 二级路由, /XXX + ''component: { render: h => h('router-view') },meta: {title: '系统设置',icon: 'el-icon-location'},children: [{ path: '/sysManger/pagg1', name: 'pagg1',component: () => import('@/view/sysManger/pagg1'),meta: {title: '测试1',icon: 'el-icon-location'}},{ path: '/sysManger/1', name: 'test1',component: () => import('@/view/sysManger/1'),meta: {title: '测试2',icon: 'el-icon-location'}},]}]},]const router = new vueRouter({// mode: 'history', // 两种模式, hash 、 history// scrollBehavior: () => ({ y: 0 }),routes: constantRoutes // 默认引入静态路由
})export default router
4. 在 src/asset 目录下新建一个全局样式css文件,命名为 global.css
/*设置距离屏幕的边距*/
body {margin: 0;padding: 0;/* 隐藏溢出 清除浮动*/overflow: hidden;
}
/*把所有的元素变成盒状模型:应用“box-sizing: border-box;”样式后,盒子border和padding的参数值是被包含在width和height之内的。*/
* {/*外边距不会额外占用1px的像素*/box-sizing: border-box;
}
.el-header {padding: 0;
}
5. 在 main.js 入口脚本文件中引入 全局 css 样式
8.4 导航栏设计
<template><div class="nav-header"><!-- 左侧 --><el-breadcrumb separator="/"><el-breadcrumb-item><span>{{ this.$route.meta.title }}</span></el-breadcrumb-item></el-breadcrumb><!-- 右侧 --><div class="nav-right"><span class="sys-dataTime">{{ currentTime }} </span><span class="username">王小虎</span><img class="my-avtar" src="@/assets/avtar.jpg"><el-dropdown><i class="el-icon-caret-bottom"></i><el-dropdown-menu slot="dropdown"><el-dropdown-item>个人中心</el-dropdown-item><el-dropdown-item>设置</el-dropdown-item><el-dropdown-item>退出</el-dropdown-item></el-dropdown-menu></el-dropdown></div></div>
</template><script>export default {name: 'Nav',data() {return {currentTime: ''}},mounted() {this.updeTime(); // 初始化时间setInterval(this.updeTime, 1000); // 每秒更新一次时间},methods: {// 显示时间updeTime() {this.currentTime = new Date().toLocaleString(); },}
}
</script><style scoped>
/* 头部导航栏 */
.nav-header {display: flex;align-items: center; /* 设置文字和图片垂直居中对齐 */justify-content: space-between;background-color: #fff;color: #333;height: 60px;line-height: 60px;padding: 20px;border-bottom: 1px solid #ccc ;box-shadow: 0 0 3px #a09f9f; /* x偏移, y偏移, 模糊半径, 颜色 */overflow: hidden; /* 防止图片溢出 */
}
.el-dropdown {cursor: pointer;
}/* 用户名 */
.username,
.sys-dataTime {font-family: 华文楷体;color: #333;font-weight: bold;font-size: 16px;
}
/* 用户头像 */
.my-avtar {width: 40px;height: 40px;border-radius: 50%;margin: -15px 5px;
}</style>
运行后样式如下
9. 使用 git 管理
1. 进入到项目的根目录,git 初始化项目
git init
2. 进入到 github创建一个仓库地址。
创建成功后就可以找到仓库地址,需要将仓库地址和项目关联起来,按照页面上的命令依次执行就可以将项目推送上去了。
注: 将整个项目都暂存 使用 add .
git add . #暂存所有文件
git add .
git commot -m "这是一个后台管理系统"
git branch -M main
git remote add origin https://github.com/aikumao/MangerSystem.git
git push -u origin main
执行完命令后刷新github 页面,即可看到上传的项目。