最后案例:

 

 

 

 

一:创建一个 Vue 应用

打开控制台:

npm init vue@latest

输入你需要创建的项目名称,一路回车

 

 

 

下载需要的包,如下:

"dependencies": {
    "@element-plus/icons-vue": "^2.1.0",
    "axios": "^1.3.5",
    "element-plus": "^2.3.3",
    "qs": "^6.11.1",
    "vue": "^3.2.47",
    "vue-router": "^4.1.6",
    "vuex": "^4.0.2"
  },

 

 

 

main.js:

import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App) //创建VUE对象
//导入路由配置
import router from './router'
app.use(router)

// import VueWechatTitle from 'vue-wechat-title';
// app.use(VueWechatTitle)

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)

import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

app.mount('#app')

 

App.vue

<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

<template>
  <!--<header>
    <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
    </div>
  </header>

  <main>
    <TheWelcome />
  </main>-->
  <router-view v-wechat-title="$route.meta.title" />
</template>

<style scoped>
  @import url("./assets/base.css");
header {
  line-height: 1.5;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }
}
</style>

 

router / index.js

// 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,
        component: () => import('../views/login.vue'),
        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;

menu.js

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;

utils / api.js

/**
 * api接口的统一封装
 */
import axios from './request.js';
import Qs from 'qs';
const urlApi = 'http://localhost:3000/';//本地测试


// 登录
export function UserLogin(query) {
    return axios({
        url: urlApi + 'loginData',//模拟数据接口
        method: 'post',
        data: Qs.stringify(query)
    })
}

request.js

import axios from 'axios'

import router from '../router/index.js'

let loading

// 请求拦截
axios.interceptors.request.use(
    (confing) => {
        //设置请求头
        if (localStorage.Authorization) { //判断本地缓存的token是否存在
            confing.headers.Authorization = localStorage.Authorization
        }

        return confing
    },
    (error) => { //token不存在,设置为网络报错
        return Promise.reject(error)
    }
)

//响应拦截
axios.interceptors.response.use(
    (res) => { //响应处理

        if (res.status === 200) { //响应码200请求成功

            if (res.data.code == '200') { //接口请求成功
                return Promise.resolve(res.data)
            } else if (res.data.code == '10000' || res.data.code == '10001') {//token验证失败,根据自己实际的修改

                //清除token
                localStorage.removeItem('Authorization')
                //跳转到登录页面
                router.push('/login')
            } else {
                //Message.error(res.data.msg);
            }
            return Promise.reject(res)
        } else {
            return Promise.reject(res)
        }
        // return res
    },
    (error) => {
        Message.error('网络出错')
        // endLoading()

        // 获取状态码
        const {
            status
        } = error.response

        if (status === 401) {
            //Message.error('请重新登录')
            //清楚token
            localStorage.removeItem('Authorization')
            //跳转到登录页面
            router.push('/login')
        }
        return Promise.reject(error)
    }
)
export default axios

 

hemo.vue

<template v-slot:default>
    <div :class="['content',isCollapse?'menu--fold':'menu--unfold']">
        <!-- 侧边菜单栏 -->
        <div class="menuLeft">
            <!--<div class="menu-nav-header">
                <span>{{isCollapse?'控制台':'管理控制台'}}</span>
            </div>-->
            <div class="menu-nav-header">
                <span>{{isCollapse?littleTitle:longTitle}}</span>
            </div>
            <!--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" @click="$router.push({ name: 'index' })">
                    <!--<SvgIcon name="home" class="icon-svg" />-->
                    <el-icon :size="size" :color="color">
                        <Edit />
                    </el-icon>
                    <span slot="">&nbsp;&nbsp;首页</span>
                </el-menu-item>
                <el-sub-menu index="2">
                    <template #title>
                        <!--<SvgIcon name="img" class="icon-svg" />-->
                        <span>&nbsp;&nbsp;图片管理</span>
                    </template>
                    <el-menu-item index="1-1" @click="$router.push({ name: 'img1' })">
                        <SvgIcon name="img" class="icon-svg" />
                        <span>&nbsp;&nbsp;图片1</span>
                    </el-menu-item>
                    <el-menu-item index="1-2">
                        <SvgIcon name="img" class="icon-svg" />
                        <span>&nbsp;&nbsp;图片2</span>
                    </el-menu-item>
                    <el-sub-menu index="1-4">
                        <template #title>
                            <SvgIcon name="img" class="icon-svg" />
                            <span>&nbsp;&nbsp;图片3</span>
                        </template>
                        <el-menu-item index="1-4-1">
                            <SvgIcon name="img" class="icon-svg" />
                            <span>&nbsp;&nbsp;图片三级子菜单</span>
                        </el-menu-item>
                    </el-sub-menu>
                </el-sub-menu>
                <el-sub-menu index="3">
                    <template #title>
                        <SvgIcon name="collection" class="icon-svg" />
                        <span>&nbsp;&nbsp;收藏管理</span>
                    </template>
                    <el-menu-item index="3" @click="$router.push({ name: 'collect' })">
                        <SvgIcon name="collection" class="icon-svg" />
                        <span class="icon-text">&nbsp;&nbsp;收藏</span>
                    </el-menu-item>
                </el-sub-menu>

                <el-menu-item index="4" @click="$router.push({ name: 'set' })">
                    <SvgIcon name="about" class="icon-svg" />
                    <span>&nbsp;&nbsp;设置</span>
                </el-menu-item>

            </el-menu>
        </div>
        <!-- 右边内容 -->
        <div class="content-main">
            <div class="navTop horizontalView">
                <div class="nav_tools horizontalView">
                    <el-icon :name="isCollapse?'expand':'fold'" class="icon-svg" @click="isCollapse=!isCollapse" />
                </div>
                <!--<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>-->
            </div>
            <!-- 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>
            <router-view />
        </div>
    </div>
</template>

<script>
    import mu from '../router/menu';

    export default {
        components: {

        },
        data: function() {
            return {
                isCollapse: false,
                mainTabs: [],
                mainTabsActiveName: '',
                menuActiveName: '',
                menus: [],
                longTitle: '',
                littleTitle: '',
                breadcrumbObj: {},
                breadcrumbList:[]
            }
        },
        created() {
            let that = this;
            that.routeHandle(that.$route);

            that.menus = mu.items;
            that.longTitle = mu.longTitle;
            that.littleTitle = mu.littleTitle;

            that.breadcrumbList = [
                that.$route.meta.title
            ]

            //菜单项层级处理,做一个面包屑集合保存
            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)
                }
            }


        },
        // 监听路由变化
        watch: {
            $route: {
                handler(to, from) {
                    if (to.path != from.path) {
                        // 处理路由
                        this.routeHandle(to);
                        this.breadcrumbList = this.breadcrumbObj[to.meta.title]
                    }
                }
            }
        },
        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();
            },

            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'
                    });
                }
            },
        }
    }
</script>

<style>
    /*@import url('../assets/css/home.css');*/
    /* -------侧边栏 折叠 */
    .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%;
        /* width: 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;
    }

    /* 修改菜单栏样式 */
    .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;
    }

    /* 修改标签栏样式 */
    .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{
    }







</style>