前端
Vue 入门与进阶
Vue ElementPlus 组件库
K8s管理系统项目实战[前端开发]-1
项目概述、框架搭建
Vue前端开发:整体布局
Vue前端开发:工作流
Vue前端开发:登录登出、部署、总结
一、项目慨述
本节是k8s管理系统项目实战的前端开发部分,在完成API接口的整体开发后,开始看手于前端部分,构建一个个功能页面,将管理系统平台化。
前端部分使用vue3框架以及element-plus组件完成,开发过程中,会使用到以下依赖:
(1) xterm 命令行终端模拟器
(2) nprogress 浏览器顶部的进度条
(3) jsonwebtoken jwt token的生成与校验组件
(4) json-editor-vue3/codemirror-editor-vue3 代码编辑器,用于编辑k8s资源YAML
(5) echarts 画图组件,如柱状图、饼图等
二、Vue目录结构及启动
1、目录结构
node_modules:存放npm下载的依赖包
public: 站点图标和主页
package.json/package-lock.json:存放依赖版本及项目描述信息
babel.config.js: babel的配置文件,babel是js的编译器
vue.config.js:vue的配置文件
src/下:
views/common/Config,js: 存放后端接口路径、编辑器配置等公共属性
assets:存放图片等静态资源
components: 存放自定义的公共组件
layout: 存放布局视图文件
router: 定义路由配置及规则
utils: 工具类,用于常用方法的封装
views:存放各个页面的视图文件
App.vue:主组件,所有页面都是在App.vue下进行切换,可以理解为所有的路由都是App.vue的子组件
main.js:入口文件,主要作用是初始化vue实例,并引入所需插件
2、启动过程
三、开发&响应流程
index.html:public目录下的项目html的入口文件
App.vue: 主组件,所有views下的页面都是在App.vue下进行切换
router/index.js:定义路由配置及规则
src/views/xxvue:各个页面的视图文件
四、框架搭建
1、初始化Vue项目
(1)初始化vue3项目
安装vue
npm install @vue/cli -g
vue create k8s-platform-fe
(2)关闭语法检查配置文件,关闭语法检测,设置端口号
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
devServer:{
host: '0.0.0.0', // 监听地址
port: 7707, // 监听端口
open: true // 启动后是否打开网页
},
transpileDependencies: true,
// 关闭语法检测
lintOnSave: false
})
(3)初始化main.js以及安装插件
main.js
import { createApp } from 'vue'
import App from './App.vue'
// 代码引入element plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//引入图标视图
import * as ELIcons from '@element-plus/icons-vue'
//引入路由配置和规则
import router from "./router"
// 创建app实例
const app = createApp(App)
// 图标注册为全局组件
for (let iconName in ELIcons) {
app.component(iconName, ELIcons[iconName])
}
app.use(ElementPlus)
app.use(router)
// 挂载
app.mount('#app')
安装插件
npm install element-plus \
npm install vue-router \
npm install nprogress \
npm install axios \
npm install json2yaml \
npm install js-yaml
(4)初始化App.vue
<template>
<span>我是App.....</span>
<!-- 路由占位符-->
<router-view></router-view>
</template>
<style>
.html, body{
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#nprogress .bar {
/* 自定义进度条 */
background: #2186c0 !important;
}
</style>
创建目录views、router、layout、utils
创建router/index.js
启动vue项目
npm run serve
2、封装路由
src/view/home/Home.vue
<template>
<div class="home">
我是Home.vue
</div>
</template>
src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'
//定义路由规则
const routes = [
{
path: '/home', //视图
component: () => import('@/views/home/Home.vue'),
icon: "odometer", //图标
meta: {title:"概要", requireAuth: false}, //定义meta元数据
},
]
//创建路由实例
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
3、添加进度条
src/router/index.js
// 导入进度条
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
//进度条配置
NProgress.inc(0.2) //设置进度条递增
NProgress.configure({easing: 'ease', speed: 600, showSpinner: false})//动画效果、动画速度、进度环是否显示
//路由守卫,路由拦截
router.beforeEach((to, from, next) => {
//启动进度条
NProgress.start()
//设置头部
if (to.meta.title) {
document.title = to.meta.title
} else {
document.title = "Kubernetes"
}
// 放行
next()
})
//关闭进度条
router.afterEach(() => {
NProgress.done()
})
4、启动/测试
npm run serve
5、封装axios
封装axios请求,添加自定义配置,如超时、重试、header等
utils/request.js
import axios from 'axios'
//新建个axios对象
const httpClient = axios.create({
validateStatus(status) {
return status >= 200 && status < 504 //设置默认的合法状态码,不合法的话不接受response
},
timeout: 10000 //超时时间10秒
})
httpClient.defaults.retry = 3 // 请求重试次数
httpClient.defaults.retryDelay = 1000 //请求重试时间间隔
httpClient.defaults.shouldRetry = true //是否重试
//添加请求拦截器
httpClient.interceptors.request.use(
config => {
//添加header
config.headers['Content-Type'] = 'application/json'
config.headers['Accept-Language'] = 'zh-CN'
config.headers['Authorization'] = localStorage.getItem("token")
//处理post请求
if(config.method == 'post') {
if (!config.data) {
config.data = {}
}
}
return config
},
err => {
return Promise.reject(err)
}
)
//添加响应拦截器
httpClient.interceptors.response.use(
response => {
//处理状态码
if (response.status !== 200) {
return Promise.reject(response.data)
} else {
return response.data
}
},
err => {
return Promise.reject(err)
}
)
export default httpClient
6、处理404页面
(1)404页面
src/views/common/404.vue
<template>
<div class="main-body-div">
<el-row>
<!-- 图片 -->
<el-col :span="24">
<div>
<img class="main-body-img" src="../../assets/img/404.png" />
</div>
</el-col>
<!-- 描述 -->
<el-col :span="24">
<div>
<p class="status-code">404</p>
<p class="status-describe">你所访问的页面不存在······</p>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
/* 图片属性 */
.main-body-img {
margin-top: 150px
}
/* 整体位置 */
.main-body-div {
text-align: center;
height: 100vh;
width: 100vw;
}
/* 状态码 */
.status-code {
margin-top: 20px;
margin-bottom: 10px;
font-size: 95px;
font-weight: bold;
color: rgb(54, 95, 230);
}
/* 描述 */
.status-describe {
color: rgb(145, 143, 143);
}
</style>
添加路由规则
src/router/index.js
//定义路由规则
const routes = [
{
path: '/home', //视图
component: () => import('@/views/home/Home.vue'),
icon: "odometer", //图标
meta: {title:"概要", requireAuth: false}, //定义meta元数据
},
{
path: '/404',
component: () => import('@/views/common/404.vue'),
meta: {title:"404",requiredAuth:true},
},
{
path: '/:pathMatch(.*)',
redirect: '/404',
}
]
(2)403页面
common/403.vue
<template>
<div class="main-body-div">
<el-row>
<!-- 图片 -->
<el-col :span="24">
<div>
<img class="main-body-img" src="../../assets/img/403.png" />
</div>
</el-col>
<el-col :span="24">
<!-- 描述 -->
<div>
<p class="status-code">403</p>
<p class="status-describe">你暂时无权限访问该页面······</p>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
/* 图片属性 */
.main-body-img {
margin-top: 15%
}
/* 整体位置 */
.main-body-div {
text-align: center;
height: 100vh;
width: 100vw;
}
/* 状态码 */
.status-code {
margin: 20px 0 20px 0;
font-size: 95px;
font-weight: bold;
color: rgb(54, 95, 230);
}
/* 描述 */
.status-describe {
color: rgb(145, 143, 143);
}
</style>
(3)路由规则
src/router/index.js
//定义路由规则
const routes = [
{
path: '/home', //视图
component: () => import('@/views/home/Home.vue'),
icon: "odometer", //图标
meta: {title:"概要", requireAuth: false}, //定义meta元数据
},
{
path: '/404',
component: () => import('@/views/common/404.vue'),
meta: {title:"404",requiredAuth:true},
},
{
path: '/403',
component: () => import('@/views/common/403.vue'),
meta: {title:"403",requiredAuth:true},
},
{
path: '/:pathMatch(.*)',
redirect: '/404',
}
]
五、前端开发
1、整体布局
(1)Container布局框架
src/layout/Layout.vue
<template>
<div class="common-layout">
<el-container>
<el-side width="200">Aside</el-side>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</div>
</template>
(2)添加路由规则
src/router/index.js
{
path: '/layout',
component: () => import('@/layout/Layout.vue'),
icon: "odometer", //图标
meta: {title:"Layout", requireAuth: false}, //定义meta元数据
},
将home放到main中
<template>
<div class="common-layout">
<el-container>
<el-side width="200">Aside</el-side>
<el-container>
<el-header>Header</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</div>
</template>
(3)菜单导航栏
功能:固钉、vuerouter模式的menu、折叠
Aside实现原理
上传2个图片
- 固钉的k8s图片logo,src/assets/img/k8s-metris.png
- 登录用户的头像,src/assets/img/avator.jpg
实现固定和menu循环
src/layout/Layout.vue
<template>
<div class="common-layout">
<el-container style="height:100vh">
<el-aside class="aside" :width="asideWidth">
<!-- 固钉,将平台logo和名字固钉在侧边栏最上方 -->
<!-- z-index是显示优先级,为了让固钉保持显示 -->
<el-affix class="aside-affix" :z-index="1200">
<div class="aside-logo">
<!-- logo图片 -->
<el-image class="logo-image" :src="logo" />
<!-- 平台名,折叠后不显示 -->
<span :class="[isCollapse ? 'is-collapse' : '']">
<span class="logo-name">Kubernetes</span>
</span>
</div>
</el-affix>
<!-- 菜单导航栏 -->
<!-- router 使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 -->
<!-- default-active 当前激活菜单的index,将菜单栏与路径做了对应关系 -->
<!-- collapse 是否折叠 -->
<el-menu class="aside-menu"
router
:default-active="$route.path"
:collapse="isCollapse"
background-color="#131b27"
text-color="#bfcbd9"
active-text-color="#20a0ff">
<!-- for循环路由规则 -->
<div v-for="menu in routers" :key="menu">
<!-- 处理子路由只有1个的情况,如概要、工作流 -->
<el-menu-item class="aside-menu-item" v-if="menu.children && menu.children.length == 1" :index="menu.children[0].path">
<!-- 引入图标的方式 -->
<el-icon><component :is="menu.children[0].icon" /></el-icon>
<template #title>
{{menu.children[0].name}}
</template>
</el-menu-item>
<!-- 处理有多个子路由的情况,如集群、工作负载、负载均衡等 -->
<!-- 父菜单 -->
<!-- 注意el-menu-item在折叠后,title的部分会自动消失,但el-sub-menu不会,需要自己控制 -->
<el-sub-menu class="aside-submenu" v-else-if="menu.children && menu.children.length > 1" :index="menu.path">
<template #title>
<el-icon><component :is="menu.icon" /></el-icon>
<span :class="[isCollapse ? 'is-collapse' : '']">{{menu.name}}</span>
</template>
<!-- 子菜单 -->
<el-menu-item class="aside-menu-childitem" v-for="child in menu.children" :key="child" :index="child.path">
<template #title>
{{child.name}}
</template>
</el-menu-item>
</el-sub-menu>
</div>
</el-menu>
</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</div>
</template>
<script>
import {useRouter} from 'vue-router'
export default {
data() {
return {
//导入头像图片
avator: require('@/assets/img/avator.png'),
//导入logo图片
logo: require('@/assets/img/k8s-metrics.png'),
//控制导航栏折叠
isCollapse: false,
//导航栏宽度
asideWidth: '220px',
//路由规则
routers: [],
}
},
computed: {
//获取用户名
username() {
let username = localStorage.getItem('username');
//三元运算
return username ? username : '未知';
},
},
methods: {
//控制折叠
onCollapse() {
if (this.isCollapse) {
this.isCollapse = false
this.asideWidth = '220px'
} else {
this.isCollapse = true
this.asideWidth = '64px'
}
},
//登出
logout() {
//移除用户名
localStorage.removeItem('username');
//移除token
localStorage.removeItem('token');
//跳转至/login页面
this.$router.push('/login');
}
},
beforeMount() {
//使用useRouter().options.routes方法获取路由规则
this.routers = useRouter().options.routes
}
}
</script>
<style scoped>
/* 侧边栏折叠速度,背景色 */
.aside{
transition: all .5s;
background-color: #131b27;
}
/* 固钉,以及logo图片和平台名的属性 */
.aside-logo{
background-color: #131b27;
height: 60px;
color: white;
}
.logo-image {
width: 40px;
height: 40px;
top: 12px;
padding-left: 12px;
}
.logo-name{
font-size: 20px;
font-weight: bold;
padding: 10px;
}
/* 滚动条不展示 */
.aside::-webkit-scrollbar {
display: none;
}
/* 修整边框,让边框不要有溢出 */
.aside-affix {
border-bottom-width: 0;
}
.aside-menu {
border-right-width: 0;
}
/* 菜单栏的位置以及颜色 */
.aside-menu-item.is-active {
background-color: #1f2a3a ;
}
.aside-menu-item {
padding-left: 20px !important;
}
.aside-menu-item:hover {
background-color: #142c4e ;
}
.aside-menu-childitem {
padding-left: 40px !important;
}
.aside-menu-childitem.is-active {
background-color: #1f2a3a ;
}
.aside-menu-childitem:hover {
background-color: #142c4e ;
}
</style>
( 4 ) Header
功能:面包屑、下拉框、登出按钮
- 面包屑
- 展开关闭按钮
- 用户信息(退出按钮)
src/layout/Layout.vue
在之前预留header位置填上
<!-- header -->
<el-header class="header">
<el-row :gutter="20">
<el-col :span="1">
<!-- 折叠按钮 -->
<div class="header-collapse" @click="onCollapse">
<el-icon><component :is="isCollapse ? 'expand':'fold'" /></el-icon>
</div>
</el-col>
<el-col :span="10">
<!-- 面包屑 -->
<div class="header-breadcrumb">
<!-- separator 分隔符 -->
<el-breadcrumb separator="/">
<!-- :to="{ path: '/' }"表示跳转到/路径 -->
<el-breadcrumb-item :to="{ path: '/' }">工作台</el-breadcrumb-item>
<!-- this.$route.matched 可以拿到当前页面的路由信息 -->
<template v-for="(matched,m) in this.$route.matched" :key="m">
<el-breadcrumb-item v-if="matched.name != undefined">
{{ matched.name }}
</el-breadcrumb-item>
</template>
</el-breadcrumb>
</div>
</el-col>
<el-col class="header-menu" :span="13">
<!-- 用户信息 -->
<el-dropdown>
<!-- 头像及用户名 -->
<div class="header-dropdown">
<el-image class="avator-image" :src="avator" />
<span>{{ username }}</span>
</div>
<!-- 下拉框内容 -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="logout()">退出</el-dropdown-item>
<el-dropdown-item >修改密码</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</el-header>
style追加
<style scoped> </style>
/* header的属性 */
.header{
z-index: 1200;
line-height: 60px;
font-size: 24px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
}
/* 折叠按钮 */
.header-collapse{
cursor: pointer;
}
/* 面包屑 */
.header-breadcrumb{
padding-top: 0.9em;
}
/* 用户信息靠右 */
.header-menu{
text-align: right;
}
/* 折叠属性 */
.is-collapse {
display: none;
}
/* 用户信息下拉框 */
.header-dropdown {
line-height: 60px;
cursor: pointer;
}
/* 头像 */
.avator-image {
top: 12px;
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 8px;
}
折叠部分是使用改变asideWidth值实现
methods: {
//控制折叠
onCollapse() {
if (this.isCollapse) {
this.isCollapse = false
this.asideWidth = '220px'
} else {
this.isCollapse = true
this.asideWidth = '64px'
}
},
( 5) Main
功能:路由占位符
( 6 ) Footer
2、工作负载
创建src/workload目录
2.1 Deployment
src/views/workload/Deployment.vue
<template>
<div class="home">
我是Deployment.vue
</div>
</template>
添加路由规则
src/router/index.js
{
path: '/workload',
component: Layout,
icon: "menu", //图标
meta: {title:"工作负载", requireAuth: false},
children: [
{
path: '/workload/deployment',
name: 'Deployment',
icon: "el-icons-s-data", //图标
meta: {title:"Deployment", requireAuth: true}, //定义meta元数据
component: () => import('@/views/workload/Deployment.vue')
},
{
path: '/workload/pod',
name: 'Pod',
icon: "el-icons-document-add", //图标
meta: {title:"Pod", requireAuth: true}, //定义meta元数据
component: () => import('@/views/workload/Pod.vue')
},
]
},
测试
(1)功能
列表、详情、新增、更新、删除、重启、副本数
(2)Main布局
(3)引入codemirror编辑器
main.js
2.2 Pod
src/views/workload/Pod.vue
<template>
<div class="home">
我是Pod.vue
</div>
</template>
添加路由规则
(1)功能
列表、详情、更新、删除、日志、终端
(2)布局
(3)头部工具栏
(4)数据表格
展开Expand
容器
日志
2.3 DaemonSet
2.4 StatefulSet
3、集群
3.1 Node
3.2 Namespace
3.3 PV
4、负载均衡
4.1 Service
4.2 Ingress
5、存储与配置
5.1 ConfigMap
5.2 Secret
5.3 Pvc
6、概要
7、工作流
(1)功能
(2)布局
(3)头部工具栏
(4)步骤条
抽屉弹出框1
抽屉弹出框2
(5)数据表格
workflow信息
8、登录/登出
(1)登录
(2)JWT校验
router/index.js
(3)登出
六、部署前后端代码
1、前端
(1) 进入k8s-platform-fe项目根目录
(2) 删除/node_modules
(3) 执行 npm install
( 4) 运行 npm run serve
(5) 浏览器打开 localhost:8080
(6) 默认登录账号密码admin 123456
2、后端
(1) 要求golang版本1.13及以上
(2) 进入k8s-platform项目根目录
(3) 执行go mod tidy
( 4) 运行 go run main.go
(5)测试接口响应 curl --location --request GET --X GET 'http://0.0.0.0:9090/api/k8s/podsnamespace=kube-system'
Ps:由于启动了jwt验证,请求后端接口时需要携带Authorization头,故直接请求后端地址会报错。解决方式:打开main.go文件,注销第21行
r.Use(middle.JWTAuth())
七、总结
整个项目的前端页面开发完成,发现开发前端页面也就是固定的几个流程,布局->小视图->axios请求。