七天从0到1基于vue实现docker管理平台之第2至7天
从本期文章开始,我将带大家从0开始基于前端vue框架实现一个docker管理平台。因为我是后端开发出身,对于前端技术完全是零基础,所以本次边做边学习,完成任务的同时,提高自己的技术能力!在前一篇文章中,我们完成了环境搭建以及登陆等相关功能,在这篇文章中,我们将是实现token的保存、页面的跳转及列表页的展示等工作。首先,我们先封装一个axios文件,定义一个拦截器,对接口的请求及响应进行过滤,如将
从本期文章开始,我将带大家从0开始基于前端vue框架实现一个docker管理平台。因为我是后端开发出身,对于前端技术完全是零基础,所以本次边做边学习,完成任务的同时,提高自己的技术能力!
在前一篇文章中,我们完成了环境搭建以及登陆等相关功能,在这篇文章中,我们将是实现token的保存、页面的跳转及列表页的展示等工作。
首先,我们先封装一个axios文件,定义一个拦截器,对接口的请求及响应进行过滤,如将token设置到header中,并且基于接口响应结果,判断token是否过期等,如果过期,则跳转到登录页面。
import axios from 'axios'
const axiosService = axios.create({
timeout: 5000 // 设置超时时间为5s
});
// 请求前设置header
axiosService.interceptors.request.use(
config => {
if (localStorage.getItem('Authorization')) {
config.headers.Authorization = localStorage.getItem('Authorization')
}
return config
},
error => {
return Promise.reject(error)
})
// 请求完成后 拦截器
axiosService.interceptors.response.use(
response => {
console.log(response.status)
if (response.data.code === 403) {
router.replace({
path: '/login'
})
localStorage.removeItem('Authorization')
}
return response
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
localStorage.removeItem('Authorization')
router.replace({
path: '/login',
query: {redirect: router.currentRoute.fullPath} // 登录成功后 跳转当前页面
})
}
}
}
)
// 这句一定要写
export default axiosService
接着实现token保存操作,修改Login.vue文件:
<template>
<div class="login-wrap">
<el-form class="login-container">
<h1 class="title">docker镜像管理平台</h1>
<el-form-item label="账号">
<el-input v-model="uname" placeholder="请输入账号"></el-input>
</el-form-item>
<el-form-item label="登录密码">
<el-input placeholder="请输入密码" v-model="password" show-password></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width: 100%;" @click="dosubmit()">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import axiosService from '@/utils/intercept';
import qs from 'qs';
export default {
name: 'login',
data: function () {
return {
uname: '',
password: '',
token: ''
}
},
methods: {
dosubmit: function () {
let _this = this;
if (this.uname === '' || this.password === '') {
alert('用户名或密码不能为空');
} else {
let params = {
uname: this.uname,
password: this.password
};
var url = "http://localhost:8088/web/login";
var str = qs.stringify(params);
axiosService.post(url, str)
.then(res => {
console.log(res);
console.log(_this);
_this.token = res.data
localStorage.setItem('Authorization', _this.token);
localStorage.setItem('Uname', _this.uname);
console.log(localStorage.getItem("Authorization"));
_this.$router.push('/home');
}).catch(function (error) {
console.log(error);
});
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.login-wrap {
box-sizing: border-box;
width: 100%;
height: 100%;
padding-top: 10%;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjEzNjFweCIgaGVpZ2h0PSI2MDlweCIgdmlld0JveD0iMCAwIDEzNjEgNjA5IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA0Ni4yICg0NDQ5NikgLSBodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2ggLS0+CiAgICA8dGl0bGU+R3JvdXAgMjE8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iQW50LURlc2lnbi1Qcm8tMy4wIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0i6LSm5oi35a+G56CB55m75b2VLeagoemqjCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTc5LjAwMDAwMCwgLTgyLjAwMDAwMCkiPgogICAgICAgICAgICA8ZyBpZD0iR3JvdXAtMjEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc3LjAwMDAwMCwgNzMuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtMTgiIG9wYWNpdHk9IjAuOCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzQuOTAxNDE2LCA1NjkuNjk5MTU4KSByb3RhdGUoLTcuMDAwMDAwKSB0cmFuc2xhdGUoLTc0LjkwMTQxNiwgLTU2OS42OTkxNTgpIHRyYW5zbGF0ZSg0LjkwMTQxNiwgNTI1LjE5OTE1OCkiPgogICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlIGlkPSJPdmFsLTExIiBmaWxsPSIjQ0ZEQUU2IiBvcGFjaXR5PSIwLjI1IiBjeD0iNjMuNTc0ODc5MiIgY3k9IjMyLjQ2ODM2NyIgcng9IjIxLjc4MzA0NzkiIHJ5PSIyMS43NjYwMDgiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0zIiBmaWxsPSIjQ0ZEQUU2IiBvcGFjaXR5PSIwLjU5OTk5OTk2NCIgY3g9IjUuOTg3NDY0NzkiIGN5PSIxMy44NjY4NjAxIiByeD0iNS4yMTczOTEzIiByeT0iNS4yMTMzMDk5NyI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0zOC4xMzU0NTE0LDg4LjM1MjAyMTUgQzQzLjg5ODQyMjcsODguMzUyMDIxNSA0OC41NzAyMzQsODMuNjgzODY0NyA0OC41NzAyMzQsNzcuOTI1NDAxNSBDNDguNTcwMjM0LDcyLjE2NjkzODMgNDMuODk4NDIyNyw2Ny40OTg3ODE2IDM4LjEzNTQ1MTQsNjcuNDk4NzgxNiBDMzIuMzcyNDgwMSw2Ny40OTg3ODE2IDI3LjcwMDY2ODgsNzIuMTY2OTM4MyAyNy43MDA2Njg4LDc3LjkyNTQwMTUgQzI3LjcwMDY2ODgsODMuNjgzODY0NyAzMi4zNzI0ODAxLDg4LjM1MjAyMTUgMzguMTM1NDUxNCw4OC4zNTIwMjE1IFoiIGlkPSJPdmFsLTMtQ29weSIgZmlsbD0iI0NGREFFNiIgb3BhY2l0eT0iMC40NSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02NC4yNzc1NTgyLDMzLjE3MDQ5NjMgTDExOS4xODU4MzYsMTYuNTY1NDkxNSIgaWQ9IlBhdGgtMTIiIHN0cm9rZT0iI0NGREFFNiIgc3Ryb2tlLXdpZHRoPSIxLjczOTEzMDQzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNDIuMTQzMTcwOCwyNi41MDAyNjgxIEw3LjcxMTkwMTYyLDE0LjU2NDA3MDIiIGlkPSJQYXRoLTE2IiBzdHJva2U9IiNFMEI0QjciIHN0cm9rZS13aWR0aD0iMC43MDI2Nzg5NjQiIG9wYWNpdHk9IjAuNyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtZGFzaGFycmF5PSIxLjQwNTM1Nzg5OTg3MzE1MywyLjEwODAzNjk1MzQ2OTk4MSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02My45MjYyMTg3LDMzLjUyMTU2MSBMNDMuNjcyMTMyNiw2OS4zMjUwOTUxIiBpZD0iUGF0aC0xNSIgc3Ryb2tlPSIjQkFDQUQ5IiBzdHJva2Utd2lkdGg9IjAuNzAyNjc4OTY0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1kYXNoYXJyYXk9IjEuNDA1MzU3ODk5ODczMTUzLDIuMTA4MDM2OTUzNDY5OTgxIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTE3IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMjYuODUwOTIyLCAxMy41NDM2NTQpIHJvdGF0ZSgzMC4wMDAwMDApIHRyYW5zbGF0ZSgtMTI2Ljg1MDkyMiwgLTEzLjU0MzY1NCkgdHJhbnNsYXRlKDExNy4yODU3MDUsIDQuMzgxODg5KSIgZmlsbD0iI0NGREFFNiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlIGlkPSJPdmFsLTQiIG9wYWNpdHk9IjAuNDUiIGN4PSI5LjEzNDgyNjUzIiBjeT0iOS4xMjc2ODA3NiIgcng9IjkuMTM0ODI2NTMiIHJ5PSI5LjEyNzY4MDc2Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xOC4yNjk2NTMxLDE4LjI1NTM2MTUgQzE4LjI2OTY1MzEsMTMuMjE0MjgyNiAxNC4xNzk4NTE5LDkuMTI3NjgwNzYgOS4xMzQ4MjY1Myw5LjEyNzY4MDc2IEM0LjA4OTgwMTE0LDkuMTI3NjgwNzYgMCwxMy4yMTQyODI2IDAsMTguMjU1MzYxNSBMMTguMjY5NjUzMSwxOC4yNTUzNjE1IFoiIGlkPSJPdmFsLTQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkuMTM0ODI3LCAxMy42OTE1MjEpIHNjYWxlKC0xLCAtMSkgdHJhbnNsYXRlKC05LjEzNDgyNywgLTEzLjY5MTUyMSkgIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTE0IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMTYuMjk0NzAwLCAxMjMuNzI1NjAwKSByb3RhdGUoLTUuMDAwMDAwKSB0cmFuc2xhdGUoLTIxNi4yOTQ3MDAsIC0xMjMuNzI1NjAwKSB0cmFuc2xhdGUoMTA2LjI5NDcwMCwgMzUuMjI1NjAwKSI+CiAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtMiIgZmlsbD0iI0NGREFFNiIgb3BhY2l0eT0iMC4yNSIgY3g9IjI5LjExNzY0NzEiIGN5PSIyOS4xNDAyNDM5IiByeD0iMjkuMTE3NjQ3MSIgcnk9IjI5LjE0MDI0MzkiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0yIiBmaWxsPSIjQ0ZEQUU2IiBvcGFjaXR5PSIwLjMiIGN4PSIyOS4xMTc2NDcxIiBjeT0iMjkuMTQwMjQzOSIgcng9IjIxLjU2ODYyNzUiIHJ5PSIyMS41ODUzNjU5Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtMi1Db3B5IiBzdHJva2U9IiNDRkRBRTYiIG9wYWNpdHk9IjAuNCIgY3g9IjE3OS4wMTk2MDgiIGN5PSIxMzguMTQ2MzQxIiByeD0iMjMuNzI1NDkwMiIgcnk9IjIzLjc0MzkwMjQiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0yIiBmaWxsPSIjQkFDQUQ5IiBvcGFjaXR5PSIwLjUiIGN4PSIyOS4xMTc2NDcxIiBjeT0iMjkuMTQwMjQzOSIgcng9IjEwLjc4NDMxMzciIHJ5PSIxMC43OTI2ODI5Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTI5LjExNzY0NzEsMzkuOTMyOTI2OCBMMjkuMTE3NjQ3MSwxOC4zNDc1NjEgQzIzLjE2MTYzNTEsMTguMzQ3NTYxIDE4LjMzMzMzMzMsMjMuMTc5NjA5NyAxOC4zMzMzMzMzLDI5LjE0MDI0MzkgQzE4LjMzMzMzMzMsMzUuMTAwODc4MSAyMy4xNjE2MzUxLDM5LjkzMjkyNjggMjkuMTE3NjQ3MSwzOS45MzI5MjY4IFoiIGlkPSJPdmFsLTIiIGZpbGw9IiNCQUNBRDkiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtOSIgb3BhY2l0eT0iMC40NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTcyLjAwMDAwMCwgMTMxLjAwMDAwMCkiIGZpbGw9IiNFNkExQTYiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0yLUNvcHktMiIgY3g9IjcuMDE5NjA3ODQiIGN5PSI3LjE0NjM0MTQ2IiByeD0iNi40NzA1ODgyNCIgcnk9IjYuNDc1NjA5NzYiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTAuNTQ5MDE5NjA4LDEzLjYyMTk1MTIgQzQuMTIyNjI2ODEsMTMuNjIxOTUxMiA3LjAxOTYwNzg0LDEwLjcyMjcyMiA3LjAxOTYwNzg0LDcuMTQ2MzQxNDYgQzcuMDE5NjA3ODQsMy41Njk5NjA5NSA0LjEyMjYyNjgxLDAuNjcwNzMxNzA3IDAuNTQ5MDE5NjA4LDAuNjcwNzMxNzA3IEwwLjU0OTAxOTYwOCwxMy42MjE5NTEyIFoiIGlkPSJPdmFsLTItQ29weS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzLjc4NDMxNCwgNy4xNDYzNDEpIHNjYWxlKC0xLCAxKSB0cmFuc2xhdGUoLTMuNzg0MzE0LCAtNy4xNDYzNDEpICI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0xMCIgZmlsbD0iI0NGREFFNiIgY3g9IjIxOC4zODIzNTMiIGN5PSIxMzguNjg1OTc2IiByeD0iMS42MTc2NDcwNiIgcnk9IjEuNjE4OTAyNDQiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC0xMC1Db3B5LTIiIGZpbGw9IiNFMEI0QjciIG9wYWNpdHk9IjAuMzUiIGN4PSIxNzkuNTU4ODI0IiBjeT0iMTc1LjM4MTA5OCIgcng9IjEuNjE3NjQ3MDYiIHJ5PSIxLjYxODkwMjQ0Ij48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtMTAtQ29weSIgZmlsbD0iI0UwQjRCNyIgb3BhY2l0eT0iMC4zNSIgY3g9IjE4MC4wOTgwMzkiIGN5PSIxMDIuNTMwNDg4IiByeD0iMi4xNTY4NjI3NSIgcnk9IjIuMTU4NTM2NTkiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjguOTk4NTM4MSwyOS45NjcxNTk4IEwxNzEuMTUxMDE4LDEzMi44NzYwMjQiIGlkPSJQYXRoLTExIiBzdHJva2U9IiNDRkRBRTYiIG9wYWNpdHk9IjAuOCI+PC9wYXRoPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTEwIiBvcGFjaXR5PSIwLjc5OTk5OTk1MiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTA1NC4xMDA2MzUsIDM2LjY1OTMxNykgcm90YXRlKC0xMS4wMDAwMDApIHRyYW5zbGF0ZSgtMTA1NC4xMDA2MzUsIC0zNi42NTkzMTcpIHRyYW5zbGF0ZSgxMDI2LjYwMDYzNSwgNC42NTkzMTcpIj4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbC03IiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMC45NDExNzY0NzEiIGN4PSI0My44MTM1NTkzIiBjeT0iMzIiIHJ4PSIxMS4xODY0NDA3IiByeT0iMTEuMjk0MTE3NiI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC0xMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzQuNTk2Nzc0LCAyMy4xMTExMTEpIiBmaWxsPSIjQkFDQUQ5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPGVsbGlwc2UgaWQ9Ik92YWwtNyIgb3BhY2l0eT0iMC40NSIgY3g9IjkuMTg1MzQ3MTgiIGN5PSI4Ljg4ODg4ODg5IiByeD0iOC40NzQ1NzYyNyIgcnk9IjguNTU2MTQ5NzMiPjwvZWxsaXBzZT4KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTkuMTg1MzQ3MTgsMTcuNDQ1MDM4NiBDMTMuODY1NzI2NCwxNy40NDUwMzg2IDE3LjY1OTkyMzUsMTMuNjE0MzE5OSAxNy42NTk5MjM1LDguODg4ODg4ODkgQzE3LjY1OTkyMzUsNC4xNjM0NTc4NyAxMy44NjU3MjY0LDAuMzMyNzM5MTU2IDkuMTg1MzQ3MTgsMC4zMzI3MzkxNTYgTDkuMTg1MzQ3MTgsMTcuNDQ1MDM4NiBaIiBpZD0iT3ZhbC03Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0zNC42NTk3Mzg1LDI0LjgwOTY5NCBMNS43MTY2NjA4NCw0Ljc2ODc4OTQ1IiBpZD0iUGF0aC0yIiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMC45NDExNzY0NzEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8ZWxsaXBzZSBpZD0iT3ZhbCIgc3Ryb2tlPSIjQ0ZEQUU2IiBzdHJva2Utd2lkdGg9IjAuOTQxMTc2NDcxIiBjeD0iMy4yNjI3MTE4NiIgY3k9IjMuMjk0MTE3NjUiIHJ4PSIzLjI2MjcxMTg2IiByeT0iMy4yOTQxMTc2NSI+PC9lbGxpcHNlPgogICAgICAgICAgICAgICAgICAgIDxlbGxpcHNlIGlkPSJPdmFsLUNvcHkiIGZpbGw9IiNGN0UxQUQiIGN4PSIyLjc5NjYxMDE3IiBjeT0iNjEuMTc2NDcwNiIgcng9IjIuNzk2NjEwMTciIHJ5PSIyLjgyMzUyOTQxIj48L2VsbGlwc2U+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTM0LjYzMTI0NDMsMzkuMjkyMjcxMiBMNS4wNjM2NjY2Myw1OS43ODUwODIiIGlkPSJQYXRoLTEwIiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMC45NDExNzY0NzEiPjwvcGF0aD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cC0xOSIgb3BhY2l0eT0iMC4zMyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI4Mi41MzcyMTksIDQ0Ni41MDI4NjcpIHJvdGF0ZSgtMTAuMDAwMDAwKSB0cmFuc2xhdGUoLTEyODIuNTM3MjE5LCAtNDQ2LjUwMjg2NykgdHJhbnNsYXRlKDExNDIuNTM3MjE5LCAzMjcuNTAyODY3KSI+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTE3IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNDEuMzMzNTM5LCAxMDQuNTAyNzQyKSByb3RhdGUoMjc1LjAwMDAwMCkgdHJhbnNsYXRlKC0xNDEuMzMzNTM5LCAtMTA0LjUwMjc0MikgdHJhbnNsYXRlKDEyOS4zMzM1MzksIDkyLjUwMjc0MikiIGZpbGw9IiNCQUNBRDkiPgogICAgICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLTQiIG9wYWNpdHk9IjAuNDUiIGN4PSIxMS42NjY2NjY3IiBjeT0iMTEuNjY2NjY2NyIgcj0iMTEuNjY2NjY2NyI+PC9jaXJjbGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMy4zMzMzMzMzLDIzLjMzMzMzMzMgQzIzLjMzMzMzMzMsMTYuODkwMDExMyAxOC4xMDk5ODg3LDExLjY2NjY2NjcgMTEuNjY2NjY2NywxMS42NjY2NjY3IEM1LjIyMzM0NDU5LDExLjY2NjY2NjcgMCwxNi44OTAwMTEzIDAsMjMuMzMzMzMzMyBMMjMuMzMzMzMzMywyMy4zMzMzMzMzIFoiIGlkPSJPdmFsLTQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExLjY2NjY2NywgMTcuNTAwMDAwKSBzY2FsZSgtMSwgLTEpIHRyYW5zbGF0ZSgtMTEuNjY2NjY3LCAtMTcuNTAwMDAwKSAiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC01LUNvcHktNiIgZmlsbD0iI0NGREFFNiIgY3g9IjIwMS44MzMzMzMiIGN5PSI4Ny41IiByPSI1LjgzMzMzMzMzIj48L2NpcmNsZT4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQzLjUsODguODEyNjY4NSBMMTU1LjA3MDUwMSwxNy42MDM4NTQ0IiBpZD0iUGF0aC0xNyIgc3Ryb2tlPSIjQkFDQUQ5IiBzdHJva2Utd2lkdGg9IjEuMTY2NjY2NjciPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTcuNSwzNy4zMzMzMzMzIEwxMjcuNDY2MjUyLDk3LjY0NDk3MzUiIGlkPSJQYXRoLTE4IiBzdHJva2U9IiNCQUNBRDkiIHN0cm9rZS13aWR0aD0iMS4xNjY2NjY2NyI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwb2x5bGluZSBpZD0iUGF0aC0xOSIgc3Ryb2tlPSIjQ0ZEQUU2IiBzdHJva2Utd2lkdGg9IjEuMTY2NjY2NjciIHBvaW50cz0iMTQzLjkwMjU5NyAxMjAuMzAyMjgxIDE3NC45MzU0NTUgMjMxLjU3MTM0MiAzOC41IDE0Ny41MTA4NDcgMTI2LjM2Njk0MSAxMTAuODMzMzMzIj48L3BvbHlsaW5lPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNTkuODMzMzMzLDk5Ljc0NTM4NDIgTDE5NS40MTY2NjcsODkuMjUiIGlkPSJQYXRoLTIwIiBzdHJva2U9IiNFMEI0QjciIHN0cm9rZS13aWR0aD0iMS4xNjY2NjY2NyIgb3BhY2l0eT0iMC42Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTIwNS4zMzMzMzMsODIuMTM3MjEwNSBMMjM4LjcxOTQwNiwzNi4xNjY2NjY3IiBpZD0iUGF0aC0yNCIgc3Ryb2tlPSIjQkFDQUQ5IiBzdHJva2Utd2lkdGg9IjEuMTY2NjY2NjciPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjY2LjcyMzQyNCwxMzIuMjMxOTg4IEwyMDcuMDgzMzMzLDkwLjQxNjY2NjciIGlkPSJQYXRoLTI1IiBzdHJva2U9IiNDRkRBRTYiIHN0cm9rZS13aWR0aD0iMS4xNjY2NjY2NyI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwtNSIgZmlsbD0iI0MxRDFFMCIgY3g9IjE1Ni45MTY2NjciIGN5PSI4Ljc1IiByPSI4Ljc1Ij48L2NpcmNsZT4KICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLTUtQ29weS0zIiBmaWxsPSIjQzFEMUUwIiBjeD0iMzkuMDgzMzMzMyIgY3k9IjE0OC43NSIgcj0iNS4yNSI+PC9jaXJjbGU+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC01LUNvcHktMiIgZmlsbC1vcGFjaXR5PSIwLjYiIGZpbGw9IiNEMURFRUQiIGN4PSI4Ljc1IiBjeT0iMzMuMjUiIHI9IjguNzUiPjwvY2lyY2xlPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwtNS1Db3B5LTQiIGZpbGwtb3BhY2l0eT0iMC42IiBmaWxsPSIjRDFERUVEIiBjeD0iMjQzLjgzMzMzMyIgY3k9IjMwLjMzMzMzMzMiIHI9IjUuODMzMzMzMzMiPjwvY2lyY2xlPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwtNS1Db3B5LTUiIGZpbGw9IiNFMEI0QjciIGN4PSIxNzUuNTgzMzMzIiBjeT0iMjMyLjc1IiByPSI1LjI1Ij48L2NpcmNsZT4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
/* background-color: #112346; */
background-repeat: no-repeat;
background-position: center right;
background-size: 100%;
}
.login-container {
border-radius: 10px;
margin: 0px auto;
width: 350px;
padding: 30px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
text-align: left;
box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.1);
}
.title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
</style>
在登录时,将获取到的token,设置在localStorage中,然后路由跳转到home页面。
新建Home.vue组件,作为我们系统的主页面,定义菜单、页头等元素:
先修改index.js,定义路由信息:
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Home from '@/components/Home'
import Image from '@/components/Image'
import Container from '@/components/Container'
import SystemSetting from '@/components/SystemSetting'
import Detail from '@/components/Detail'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
redirect: '/home'
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/home',
name: 'Home',
component: Home,
redirect: '/systemSetting',
children:[
{
path: '/image',
name: 'Image',
component: Image
},
{
path: '/container',
name: 'Container',
component: Container
},
{
path: '/systemSetting',
name: 'SystemSetting',
component: SystemSetting
},
{
path: '/detail',
name: 'Detail',
component: Detail
}
]
}
]
})
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('Authorization');
if (token === null || token === '') {
next('/login');
} else {
next();
}
}
});
export default router;
Home.vue:
<template>
<el-container style="height: 100vh">
<el-aside width="200px">
<el-menu :router="true" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose"
:default-active="this.$router.path">
<el-submenu index="">
<template slot="title"><i class="el-icon-message"></i>容器管理</template>
<el-menu-item index="/container">容器列表</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>镜像管理</template>
<el-menu-item index="/image">镜像列表</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>系统设置</template>
<el-menu-item index="/systemSetting">账号管理</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; height: 80px; font-size: 12px; border: 1px solid #F2F6FC">
<el-page-header @back="goBack" content="详情页面"></el-page-header>
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="logout">注销</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>{{ uname }}</span>
</el-header>
<el-main style="border: 1px solid #F2F6FC">
<!-- 切换不同的组件,显示对应的内容 -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<style>
.el-header {
background-color: #5aceb7;
color: #333;
line-height: 60px;
}
.el-aside {
background-color: #FFFFFF;
color: #333;
}
.el-main {
background-color: #F2F6FC;
color: #333;
line-height: 60px;
}
</style>
<script>
export default {
data() {
const uname = localStorage.getItem('Uname')
return {
tableData: [],
uname
}
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
queryMenus() {
console.log("菜单初始化完成");
},
goBack() {
console.log("点击返回!");
this.$router.push('/home');
},
logout() {
console.log("点击注销!");
localStorage.removeItem('Authorization');
this.$router.push('/login');
}
},
mounted() {
this.queryMenus();
}
};
</script>
列表页展示页面如下:
新建文件Image.vue
<template>
<div>
<el-table :data="tableData.slice((currentPage - 1) * pagesize, currentPage * pagesize)" height="500" border
:row-style="{ height: '0' }" :cell-style="{ padding: '3px' }"
:header-cell-style="{ background: '#eef1f6', color: '#606266' }">
<el-table-column prop="repository" label="仓库" width="200" fixed>
</el-table-column>
<el-table-column prop="tag" label="标签" width="200" fit="true">
</el-table-column>
<el-table-column prop="imageId" label="镜像ID" width="300">
</el-table-column>
<el-table-column prop="imageName" label="镜像名称" width="200">
</el-table-column>
<el-table-column prop="date" label="构建日期" width="200">
</el-table-column>
<el-table-column prop="size" label="大小" width="200">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="text-align: right;" @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[5, 10, 15, 20]" :page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper" :total="tableData.length">
</el-pagination>
</div>
</template>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
import axiosService from '@/utils/intercept';
export default {
data() {
return {
tableData: [],
currentPage: 1, //初始页
pagesize: 5 // 每页的数据
}
},
mounted() {
this.listData();
},
methods: {
handleSizeChange(size) {
console.log(size, 'size');
this.pagesize = size;
console.log(this.pagesize); //每页下拉显示数据
},
handleCurrentChange(currentPage) {
console.log(currentPage, 'currentPage');
this.currentPage = currentPage;
console.log(this.currentPage); //点击第几页
},
handleClick(row) {
let _this = this;
console.log(row.imageId)
_this.$router.push({name:'Detail',query: {imageId: row.imageId}});
},
listData() {
let _this = this;
axiosService.get("http://localhost:8088/web/list").then(res => {
let arr = res.data;
_this.tableData = arr;
}).catch(function (error) {
console.log(error);
localStorage.removeItem('Authorization');
_this.$router.push('/login');
});
}
}
};
</script>
详情页Detail.vue:
<template>
<div>
<el-radio-group v-model="size">
<el-radio label="">默认</el-radio>
<el-radio label="medium">中等</el-radio>
<el-radio label="small">小型</el-radio>
<el-radio label="mini">超小</el-radio>
</el-radio-group>
<el-descriptions class="margin-top" title="镜像信息" :column="3" :size="size" border>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-user"></i>
仓库
</template>
{{ row.repository }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-mobile-phone"></i>
标签
</template>
{{ row.tag }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-location-outline"></i>
镜像ID
</template>
{{ row.imageId }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-tickets"></i>
镜像名称
</template>
<el-tag size="small">{{ row.imageName }}</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-office-building"></i>
构建日期
</template>
{{ row.date }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-office-building"></i>
大小
</template>
{{ row.size }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script>
import axiosService from '@/utils/intercept';
export default {
data() {
return {
imageId: {},
size: '',
row: {}
}
},
mounted() {
this.imageId = this.$route.query.imageId
console.log(this.imageId)
axiosService.get("http://localhost:8088/web/getById", {
params: {
imageId: this.imageId
}
}).then(res => {
this.row = res.data
}).catch(function (error) {
console.log(error);
localStorage.removeItem('Authorization');
_this.$router.push('/login');
});
},
}
</script>
查询列表及详情的后端接口如下:
package com.zxm.plugin.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
@RestController
@RequestMapping("web")
@Slf4j
public class PluginController {
private static String token = "";
private static long expire = 0;
@PostMapping(value = "/login", produces = "application/x-www-form-urlencoded;charset=UTF-8")
public String login(String uname, String password){
log.info("用户名:{},密码:{}", uname, password);
if("zhangxiaomin".equals(uname)&&"123456".equals(password)){
token = UUID.randomUUID().toString().replace("-", "").substring(0,15);
expire = System.currentTimeMillis() + 10*60*1000L;
return token;
}
return "登陆失败";
}
@GetMapping(value = "/list", produces = "application/x-www-form-urlencoded;charset=UTF-8")
public String list(HttpServletRequest request){
String as = request.getHeader("Authorization");
if(!token.equals(as) || System.currentTimeMillis() > expire){
throw new RuntimeException("token 已过期,请重新登录");
}
JSONArray result = new JSONArray();
for(int i=0;i<50;i++){
JSONObject object = new JSONObject();
object.put("repository", UUID.randomUUID().toString().replace("-", "").substring(0, 8));
object.put("tag", "v1.2."+i);
object.put("imageId", UUID.randomUUID().toString().replace("-", ""));
object.put("imageName", "ubuntu-"+i);
object.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
object.put("size", 65536);
result.add(object);
}
return result.toJSONString();
}
@GetMapping(value = "/getById", produces = "application/x-www-form-urlencoded;charset=UTF-8")
public String getById(HttpServletRequest request, @RequestParam("imageId") String imageId){
String as = request.getHeader("Authorization");
if(!token.equals(as) || System.currentTimeMillis() > expire){
throw new RuntimeException("token 已过期,请重新登录");
}
JSONObject object = new JSONObject();
object.put("repository", UUID.randomUUID().toString().replace("-", "").substring(0, 8));
object.put("tag", "v1.2");
object.put("imageId", imageId);
object.put("imageName", "ubuntu-1");
object.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
object.put("size", 65536);
return object.toJSONString();
}
}
后端接口临时造了假数据,仅供展示!
接下来,启动系统,打开页面,验证我们的开发成果吧!
复盘:
第一天:
搭环境
实现登录界面,并请求后端接口,生成token
第二天:
实现token保存于localStorage
实现home主页
实现登录成功后页面跳转至home页
第三天:
实现home页请求后端接口,并展示列表数据
实现http请求添加拦截器,将token放在header中
实现token过期判断,token过期,清空localStorage,并跳转至登录页面
第四天:
实现axios拦截器全局配置(请求及响应的拦截),拦截器的拦截内容待优化
实现页面子路由的跳转功能
第五天:
调整布局细节问题,增加页头
增加列表分页功能
增加注销功能
第六天:
优化列表页
增加详情页,实现了列表页跳详情页
这篇文章有点长,本来是打算拆解为几篇文章分开写的,内容尽量详细一些。到这里,我们的任务基本完成,在实现期间,我自己也遇到很多困难,都是一步步查阅资料解决的,作为初学者,属实不容易。整体实现的功能基本包括了登录注销、路由跳转、列表、分页、详情页、菜单等等基础功能。文章词藻一般,主要是分享源码及实现思路为主,感兴趣的朋友多多交流!
请关注我!谢谢大家!
代码后续会上传到github中,请大家持续关注我!
更多推荐
所有评论(0)