Table of contents
(2), install and use the svg plug-in
(3) Write the framework code of the main interface
(7) Modify the package menu bar
I. Introduction:
First, the screenshot of the finished project template:
There are many open source vue management background templates. If you need to quickly develop a background management, you can search for "vue management background template". Students who only want the code can directly click on the source code . Through this chapter blog you can learn:
(1), the use of element UI component library.
(2) How to customize components.
(3) Skilled use of routing.
(4) Improvement of Vue project development ideas.
(5)、……
This project is developed on the basis of the integration project in the previous chapter, please click the link below to read first. (If you read this chapter without barriers, please ignore it)
2. Website page analysis
The website consists of a login page, a main interface, and a content page.
The overall module of the main interface is composed of:
(1) Navigation bar;
(2), the navigation menu bar on the left side;
(3), page tab bar;
(4), content column (display page).
composition.
The click switch of the menu bar only changes the content of the content bar, which leads to:
The login page and the main interface are first-level routes, and the content page is a nested route under the main interface.
3. Development steps
(1), install element
Official website: A Vue 3 UI framework | Element Plus .
First install the domestic npm mirror, so that the download speed of the resource package will be faster
npm install cnpm -g --registry=https://registry.npmmirror.com
Then, install element
npm install element-plus --save
Import the project and add the following code to the main.js file
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)
Test whether the introduction is successful, and add the button component to home.vue
<el-button type="primary">Primary</el-button>
Run the project: npm run dev
The running effect is as follows, indicating that the import is successful:
(2), install and use the svg plug-in
- Install:
npm i vite-plugin-svg-icons -D
- Create a new storage svg directory under src:
- Create a new component in the components directory: SvgIcon.vue
<template>
<svg aria-hidden="true">
<use :xlink:href="symbolId" />
</svg>
</template>
<script>
import { defineComponent, computed } from 'vue';
export default defineComponent({
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
},
setup(props) {
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
return { symbolId };
},
});
</script>
- Configure in vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
// 指定symbolId格式
symbolId: '[name]',
})
]
})
- Introduced in main.js, add code:
//Import Svg image plug-in, you can display Svg images on the page
import 'virtual:svg-icons-register'
import SvgIcon from "./components/SvgIcon.vue";
app.component('SvgIcon',SvgIcon)
- At this point, an error occurs when running the project: no fast-glob found. You need to install fast-glob.
npm i fast-glob
- test use
Click the icon to copy the svg content
Create a new svg file in the newly created svg directory, name format: icon-"icon name", paste the content and save
In the page use:
<SvgIcon name="aim" class="icon-svg" />
(3) Write the framework code of the main interface
- First build an overall framework, home.vue code
<template v-slot:default>
<div :class="['content',isCollapse?'menu--fold':'menu--unfold']">
<!-- 侧边菜单栏 -->
<div class="menuLeft">
<div class="menu-nav-header">
<span>{
{isCollapse?'控制台':'管理控制台'}}</span>
</div>
<!--todo 菜单栏组件 -->
</div>
<!-- 右边内容 -->
<div class="content-main">
<div class="navTop horizontalView">
<div class="nav_tools horizontalView">
<SvgIcon :name="isCollapse?'expand':'fold'" class="icon-svg" @click="isCollapse=!isCollapse" />
</div>
</div>
<!-- todo 内容组件 -->
</div>
</div>
</template>
<script>
export default {
components: {
},
data: function() {
return {
isCollapse: false
}
}
}
</script>
<style>
@import url('../assets/css/home.css');
</style>
- write css style
Common ones are placed in base.css, and page-specific ones are placed in home.css
- base.css code:
.content {
width: 100%;
height: 100%;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
/* 水平布局 居中*/
.horizontalView {
position: relative;
flex-direction: row;
display: flex;
align-items: center;
}
/* 垂直布局居中 */
.verticalView {
position: relative;
flex-direction: column;
display: flex;
align-items: center;
}
/* 居中 */
.center {
position: absolute;
top: 50%;
left: 50%;
font-size: 28px;
transform: translate(-50%, -50%);
}
.w100 {
width: 100%;
}
.h100 {
height: 100%;
}
.icon-svg {
width: 1.4rem;
height: 1.4rem;
fill: currentColor;
overflow: hidden;
}
- home.css code:
/* -------侧边栏 折叠 */
.menu--fold .menuLeft {
width: 64px;
}
.menu--fold .content-main {
margin-left: 64px;
}
/* --------------------- */
/* ---------侧边栏 展开 */
.menu--unfold .menuLeft {
width: 230px;
}
.menu--unfold .content-main {
margin-left: 230px;
}
/* ------------- */
.navTop {
position: relative;
width: 100%;
height: 50px;
z-index: 100;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
box-sizing: border-box;
background: white;
}
.nav_tools {
height: 100%;
}
.nav_tools .icon-svg {
margin-left: 10px;
color: #5b5b5b;
}
.menuLeft {
position: fixed;
top: 0;
left: 0;
bottom: 0;
z-index: 1020;
overflow: hidden;
background-color: #263238;
}
.content-main {
position: relative;
background: #f1f4f5;
height: 100%;
}
.menu-nav-header {
color: white;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 20px;
font-weight: bold;
/* background-color: #9fbea7; */
background-color: #566f7e;
}
/* 动画 */
.nav_tools,
.menuLeft,
.content-main {
transition: inline-block 0.3s, left 0.3s, width 0.3s, margin-left 0.3s, font-size 0.3s;
}
- base.css is placed in app.vue
@import url("./assets/css/base.css");
- Take a look at the page effect:
- Click the Collapse button above
(4) Writing the menu bar
Please go to understand the component usage documentation first: Menu menu | Element Plus
Copy the example code to customize the content properties and styles, close the folding animation provided by the component, and customize the animation style
- Add and modify the menu component code in home.vue
<!--todo 菜单栏组件 -->
<el-menu active-text-color="#fff" background-color="#263238" class="el-menu-vertical-demo"
:collapse-transition="false" default-active="2" text-color="#96a4ab " @open="handleOpen"
@close="handleClose" :collapse="isCollapse">
<el-menu-item index="1">
<SvgIcon name="home" class="icon-svg" />
<span slot=""> 首页</span>
</el-menu-item>
<el-sub-menu index="2">
<template #title>
<SvgIcon name="img" class="icon-svg" />
<span> 图片管理</span>
</template>
<el-menu-item index="1-1">
<SvgIcon name="img" class="icon-svg" />
<span> 图片1</span>
</el-menu-item>
<el-menu-item index="1-2">
<SvgIcon name="img" class="icon-svg" />
<span> 图片2</span>
</el-menu-item>
<el-sub-menu index="1-4">
<template #title>
<SvgIcon name="img" class="icon-svg" />
<span> 图片3</span>
</template>
<el-menu-item index="1-4-1">
<SvgIcon name="img" class="icon-svg" />
<span> 图片三级子菜单</span>
</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>
<SvgIcon name="collection" class="icon-svg" />
<span> 收藏管理</span>
</template>
<el-menu-item index="3">
<SvgIcon name="collection" class="icon-svg" />
<span class="icon-text"> 收藏</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="4">
<SvgIcon name="about" class="icon-svg" />
<span> 设置</span>
</el-menu-item>
</el-menu>
- Add modified style code to home.css
/* 修改菜单栏样式样式 */
.menuLeft .el-menu {
border-right: none;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
border-right: none;
width: 230px;
}
.el-menu .icon-text {
margin-left: 10px;
}
- Page effect:
The writing of the menu bar is not finished yet. The above method of writing is that every time a menu is added, modified or deleted, it is a bit cumbersome to find the location on the page and then modify it. It is even more troublesome to edit and modify when there are too many page codes or menu items. Therefore, we will optimize the code later, encapsulate the menu into a menu data set, and then display it in a for loop on the page.
(5), new page and routing
- New page:
index.vue, img1.vue, collect.vue, set.vue. And add the page identification text in the page.
- Configure routing:
The index.js code in the router directory:
// import Vue from 'vue' //引入Vue
import {
createRouter,
createWebHashHistory
} from 'vue-router' //引入vue-router
// Vue.use(Router) //Vue全局使用Router
import home from '../views/home.vue'
import login from '../views/login.vue'
import index from '../views/index.vue'
import collect from '../views/collect.vue'
import set from '../views/set.vue'
import img1 from '../views/img1.vue'
const routes = [{
path: '',
redirect: "home"
}, {
path: '/',
redirect: "home"
},
{
path: '/login',
name: 'login',
component: login,
meta: {
title: '登录'
}
},
{
path: '/home',
name: 'home',
component: home,
/* 子路由 */
children: [{
path: '/',
redirect: "index"
},{
path: '',
redirect: "index"
}, {
path: '/index',
name: 'index',
component: index,
meta: {
title: '首页',
}
},
{
path: '/collect',
name: 'collect',
component: collect,
meta: {
title: '收藏',
isTab: true
}
},
{
path: '/img1',
name: 'img1',
component: img1,
meta: {
title: '图片1',
isTab: true
}
},
{
path: '/set',
name: 'set',
component: set,
meta: {
title: '设置',
isTab: true
}
}
]
}
];
// 导航守卫
// 使用 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();
}
}
}); */
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router;
- Add a routing component to home.vue to test the routing jump:
<router-view />
- Add the jump routing code to the menu item
Add the click redirect routing code to the "Home" menu item: @click="$router.push({ name: 'index' })"
Similarly, add corresponding codes to other menu items.
- Test effect
Click "Settings"
Click "Favorites"
ok, routing configuration is successful!
(6), custom page label bar
Two steps:
(1) Monitor route switching, store the name (or path) collection of the redirected route, and store the current route name.
(2) Use the el-tabs tab page component Tabs tab | Element Plus to customize the style and write content components.
first step:
- Monitor route changes, watch and data are at the same layer:
watch: {
$route: {
handler(to, from) {
if (to.path != from.path) {
// 处理路由
this.routeHandle(to);
}
}
}
},
- Forget it, just put all the js code directly:
<script>
export default {
components: {
},
data: function() {
return {
isCollapse: false,
mainTabs: [],
mainTabsActiveName: '',
menuActiveName: '',
menus: []
}
},
created() {
let that = this;
that.routeHandle(that.$route);
},
// 监听路由变化
watch: {
$route: {
handler(to, from) {
if (to.path != from.path) {
// 处理路由
this.routeHandle(to);
}
}
}
},
methods: {
resetDocumentClientHeight: function() {
this.documentClientHeight = document.documentElement['clientHeight'];
window.onresize = () => {
this.documentClientHeight = document.documentElement['clientHeight'];
this.loadSiteContentViewHeight();
};
},
loadSiteContentViewHeight: function() {
let height = this.documentClientHeight - 52; //减去导航栏高度50
console.log(this.$route.meta.isTab)
if (this.$route.meta.isTab) {
height -= 70; //减去tab栏高度40,减去上下边距30
/* this.siteContentViewHeight = {
'min-height': height + 'px'
}; */
this.siteContentViewHeight = height;
} else {
height -= 30;
//给内容区设置高度
this.siteContentViewHeight = height;
}
},
routeHandle: function(route) {
//每次切换页面,重新计算页面高度和内容区高度
this.resetDocumentClientHeight();
this.loadSiteContentViewHeight();
if (route.meta.isTab) {
// tab选中, 不存在先添加
var tab = this.mainTabs.filter(item => item.name === route.name)[0];
if (!tab) {
if (route.meta.isDynamic) {
route = this.dynamicMenuRoutes.filter(item => item.name === route.name)[0];
if (!route) {
return console.error('未能找到可用标签页!');
}
}
tab = {
menuId: route.meta.menuId || route.name,
name: route.name,
title: route.meta.title,
iframeUrl: route.meta.iframeUrl || '',
params: route.params,
query: route.query
};
this.mainTabs = this.mainTabs.concat(tab);
}
this.menuActiveName = tab.menuId + '';
this.mainTabsActiveName = tab.name;
}
},
mounted: function() {
let that = this;
that.resetDocumentClientHeight();
that.loadSiteContentViewHeight();
}
}
}
</script>
Step two:
- Write components
<!-- todo 内容组件 -->
<el-tabs v-if="$route.meta.isTab" v-model="mainTabsActiveName" :closable="true"
@tab-click="selectedTabHandle" @tab-remove="removeTabHandle">
<el-scrollbar ref="scroll" :height="siteContentViewHeight+32+'px'" @scroll="scroll">
<el-tab-pane v-for="item in mainTabs" :label="item.title" :name="item.name">
<el-card :style="'min-height:'+siteContentViewHeight + 'px'">
<router-view v-if="item.name === mainTabsActiveName" />
</el-card>
</el-tab-pane>
</el-scrollbar>
</el-tabs>
<div v-else>
<el-scrollbar ref="scroll" :height="siteContentViewHeight+32+'px'" @scroll="scroll">
<!-- 主入口标签页 e -->
<el-card :style="'min-height:'+ siteContentViewHeight + 'px'">
<router-view />
</el-card>
</el-scrollbar>
</div>
- Modify the style:
/* 修改标签栏样式 */
.content-main .el-tabs .el-tabs__header {
z-index: 90;
padding: 0 55px 0 15px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);
background-color: #fff;
}
.content-main .el-tabs .el-tabs__nav-wrap::after {
width: 0px;
}
.content-main .el-scrollbar .el-card {
margin: 15px 15px;
}
.content-main .el-tabs .el-tabs__header{
margin: unset;
}
.content-main .el-tabs .el-tab-pane{
}
- Add function method in methods
selectedTabHandle: function(tab, e) {
tab = this.mainTabs.filter(item => item.name === tab.paneName);
if (tab.length >= 1) {
this.$router.push({
name: tab[0].name,
query: tab[0].query,
params: tab[0].params
});
}
},
removeTabHandle: function(tabName) {
this.mainTabs = this.mainTabs.filter(item => item.name !== tabName);
if (this.mainTabs.length >= 1) {
// 当前选中tab被删除
if (tabName === this.mainTabsActiveName) {
var tab = this.mainTabs[this.mainTabs.length - 1];
this.$router.push({
name: tab.name,
query: tab.query,
params: tab.params
},
() => {
this.mainTabsActiveName = this.$route.name;
}
);
}
} else {
this.menuActiveName = '';
this.$router.push({
name: 'Home'
});
}
},
- Effect:
(7) Modify the package menu bar
- Create a new file under the router file
- menu.js file code:
var mu = {
longTitle: '管理控制台',
littleTitle: '控制台',
items: [{
iconName: 'home',
name: '首页',
routerName: 'index',
disabled: false
}, {
iconName: 'img',
name: '图片管理',
submenu: [{
iconName: 'img',
name: '图片一',
routerName: 'img1',
disabled: false
}, {
iconName: 'img',
name: '图片二',
routerName: 'img2',
disabled: false
}, {
iconName: 'img',
name: '图片三管理',
submenu: [{
iconName: 'img',
name: '图片三',
routerName: 'img1',
disabled: true
}]
}]
},
{
iconName: 'collection',
name: '收藏管理',
submenu: [{
iconName: 'collection',
name: '收藏',
routerName: 'collect',
disabled: false
}]
},
{
iconName: 'about',
name: '设置',
routerName: 'set',
disabled: false
}
]
}
export default mu;
- Rewrite the menu component:
<div class="menu-nav-header">
<span>{
{isCollapse?littleTitle:longTitle}}</span>
</div>
<el-menu active-text-color="#fff" background-color="#263238" class="el-menu-vertical-demo"
:collapse-transition="false" text-color="#96a4ab " @open="handleOpen"
@close="handleClose" :collapse="isCollapse">
<template v-for="(item,index) in menus">
<el-menu-item v-if="!item.submenu" :index="index" @click="$router.push({ name: item.routerName })" :disabled="item.disabled">
<SvgIcon :name="item.iconName" class="icon-svg" />
<span slot=""> {
{item.name}}</span>
</el-menu-item>
<el-sub-menu v-else :index="index">
<template #title>
<SvgIcon :name="item.iconName" class="icon-svg" />
<span slot=""> {
{item.name}}</span>
</template>
<template v-for="(submenuItem,submenuIndex) in item.submenu">
<el-menu-item v-if="!submenuItem.submenu" :index="index+'-'+submenuIndex" :disabled="submenuItem.disabled"
@click="$router.push({ name: submenuItem.routerName })">
<SvgIcon :name="submenuItem.iconName" class="icon-svg" />
<span slot=""> {
{submenuItem.name}}</span>
</el-menu-item>
<el-sub-menu v-else :index="index+'-'+submenuIndex">
<template #title>
<SvgIcon :name="submenuItem.iconName" class="icon-svg" />
<span slot=""> {
{submenuItem.name}}</span>
</template>
<el-menu-item v-for="(item3,index3) in submenuItem.submenu" :index="index" :disabled="item3.disabled"
@click="$router.push({ name: item3.routerName })">
<SvgIcon :name="item3.iconName" class="icon-svg" />
<span slot=""> {
{item3.name}}</span>
</el-menu-item>
</el-sub-menu>
</template>
</el-sub-menu>
</template>
</el-menu>
Only three levels of submenus are nested. If there are more submenus, multiple for loops need to be nested in the component.
- The script code first imports the menu
import mu from '../router/menu/menu.js';
- Called in created
In this way, to modify the menu bar, you only need to edit it in menu.js, and you no longer need to modify the page code.
(8) Add bread crumbs
- Add the breadcrumb component to the page:
<el-breadcrumb separator="/">
<el-breadcrumb-item v-if="!breadcrumbList.size && breadcrumbList[0]!='首页'" :to="{ name: 'index' }">
首页
</el-breadcrumb-item>
<el-breadcrumb-item v-for="it in breadcrumbList">{
{it}}</el-breadcrumb-item>
</el-breadcrumb>
- In created, process the menu bar collection into the following format:
{
"首页":["首页"],
"图片一":["图片管理","图片一"],
......
}
- code:
//菜单项层级处理,做一个面包屑集合保存
var mus=that.menus
for (let i1 of mus) {
if (i1.submenu) {
for (let i2 of i1.submenu) {
if (i2.submenu) {
for (let i3 of i2.submenu) {
if (!i3.submenu) {
that.breadcrumbObj[i3.name] = [i1.name, i2.name, i3.name];
}
}
} else {
that.breadcrumbObj[i2.name] = [i1.name, i2.name];
console.log(i2.name)
}
}
} else {
that.breadcrumbObj[i1.name] = [i1.name];
console.log(i1.name)
}
}
Assign a value when the route changes, and add it to the watch:
this.breadcrumbList = this.breadcrumbObj[to.meta.title]
Note: The name in the route must be consistent with the name in the menu.
4. End
At this point, a simple management background template is completed. The source code is not appreciated, and the code words are not easy. Now that you have seen this, please like it before leaving.