【宁渡课堂】宁渡课堂vue3+vite+pinia+element-plus+apifox搭建的通用后台管理

视频介绍 威辛=>web8931拿所有的内容

项目介绍

项目目录
在这里插入图片描述

创建项目

vite官网地址

https://cn.vitejs.dev/guide/#scaffolding-your-first-vite-project

创建vue3+vite的项目

要求:

Vite 需要 Node.js 版本 18+ 

创建命令:

# npm 7+, 需要额外加 --:
npm create vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app --template vue

下载依赖

# npm:
npm i

# yarn
yarn 

启动项目

yarn dev

删减不需要的东西

在main.js中,把默认引入的样式去掉

import './style.css'

删除components下的HelloWorld.vue

删除app.vue中的代码改成以下这样

<script setup>
    
</script>

<template>

</template>

<style scoped>

</style>

下载必备的依赖

yarn add less

yarn add vue-router

yarn add element-plus

yarn add @element-plus/icons-vue

配置@别名

在vite.config.js下

export default defineConfig({
   
   
  plugins: [vue()],
  //这个resolve是添加的别名
  resolve:{
   
   
    alias:[
      {
   
   
        find: "@", replacement: "/src" 
      }
    ]
  }
})

引入重置样式文件和图片资源

把课件中的less文件夹和images文件夹,都放到src下的assets中

在main.js中引入

import '@/assets/less/index.less'

#路由的创建

1.在src下创建router文件夹,在其中创建index.js文件

//引入两个方法,第一个创建路由器对象,第二个是开启hash模式的方法
import {
   
    createRouter, createWebHashHistory } from 'vue-router'

//路由规则
const routes = [
    {
   
   
      path: '/',
      name: 'main',
      component: () => import('@/views/Main.vue')
    }
  ]

const router = createRouter({
   
   
    //history设置路由模式
    history: createWebHashHistory(),
    routes
})

//把路由器暴露出去
export default  router

2.在src下创建views文件夹,并在其中创建Main.vue(组件需要默认的代码,不然会报错)

<template>
<div>
	我是main组件
</div>
</template>

<script setup>
</script>

<style lang="less" scoped >
</style>

3.在main.js中 使用路由,这里我们把createApp(App)用一个变量接收

import router from './router'

const app =createApp(App)
app.use(router).mount('#app')

4.在app.vue组件中放置路由出口

<template>
  <router-view />      
</template>

//这个是app的样式,设置全屏展示,防止滚动条的出现
//注意,style上不要使用scoped
<style>
#app{
   
   
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>

#整体布局的实现

引入element-plus和@element-plus/icons-vue

文档:

element-plus:https://element-plus.org/zh-CN/guide/quickstart.html#%E5%AE%8C%E6%95%B4%E5%BC%95%E5%85%A5

@element-plus/icons-vue:https://element-plus.org/zh-CN/component/icon.html#%E6%B3%A8%E5%86%8C%E6%89%80%E6%9C%89%E5%9B%BE%E6%A0%87

//这里ElementPlus我们使用完整引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)

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

1.main组件的实现

<template>
    <div class="common-layout">
        
      <el-container class="lay-container">
            //这个是自定义的菜单组件
            <common-aside />
          
            <el-container>
                <el-header>
                    //这个是自定义的头部组件,需要用el-header包裹
                    <common-header />
                </el-header>

                <el-main class="right-main">
                    main
                </el-main>
            </el-container>
          
      </el-container>
        
    </div>
  </template>

<script setup>
//这三个组件后面定义,我们统一暴露出来
import {CommonAside,CommonHeader} from "@/components"

</script>

<style lang="less" scoped>
.common-layout,.lay-container {
  height: 100%;
}
.el-header{
  background-color: #333;
}

</style>

2.创建菜单和头部组件

在components下创建CommonAside.vue和CommonHeader.vue组件。还有index.js文件

在index.js中,把这两个组件统一暴露出去

export {
   
   default as CommonAside} from "./CommonAside.vue"
export {
   
   default as CommonHeader} from "./CommonHeader.vue"

菜单组件的实现

在components下的CommonAside.vue中

html

<template>

  <el-aside width="180px">
      
    <el-menu
        background-color="#545c64"
        text-color="#fff"
        :collapse-transition="false"
        :collapse="false"
      >
        
      <h3 >后台管理</h3>
        //这个是没有子选项的菜单,注意这个index必须是item.path,后面有用
      <el-menu-item
        :index="item.path"
        v-for="item in noChildren"
        :key="item.path"
        @click="clickMenu(item)"
      >
      <component class="icons" :is="item.icon"></component>
      <span>{
  
  { item.label }}</span>
      </el-menu-item>
        
        //这个是有子选项的菜单,注意这个index必须是item.path,后面有用
      <el-sub-menu
        v-for="item in hasChildren"
        :key="item.path"
        :index="item.path"
      >
        <template #title>
            <component class="icons" :is="item.icon"></component>
            <span>{
  
  { item.label }}</span>
        </template>

        <el-menu-item-group>
            
          <el-menu-item
            :index="subItem.path"
            v-for="(subItem, subIndex) in item.children"
            :key="subIndex"
            @click="clickMenu(subItem)"
          >
            <component class="icons" :is="subItem.icon"></component>
            <span>{
  
  { subItem.label }}</span>
          </el-menu-item>
            
        </el-menu-item-group>

      </el-sub-menu>
      </el-menu>

</el-aside>

</template>

js

<script setup>
import {
   
   ref,computed} from 'vue'
import {
   
    useRouter } from 'vue-router';
const router=useRouter()

const list =ref([
      	{
   
   
          path: '/home',
          name: 'home',
          label: '首页',
          icon: 'house',
          url: 'Home'
      	},
        {
   
   
            path: '/mall',
            name: 'mall',
            label: '商品管理',
            icon: 'video-play',
            url: 'Mall'
        },
        {
   
   
            path: '/user',
            name: 'user',
            label: '用户管理',
            icon: 'user',
            url: 'User'
        },
        {
   
   
            path: 'other',
            label: '其他',
            icon: 'location',
            children: [
                {
   
   
                    path: '/page1',
                    name: 'page1',
                    label: '页面1',
                    icon: 'setting',
                    url: 'Page1'
                },
                {
   
   
                    path: '/page2',
                    name: 'page2',
                    label: '页面2',
                    icon: 'setting',
                    url: 'Page2'
                }
            ]
        }
])
const noChildren = computed(() => list.value.filter(item => !item.children))
const hasChildren =computed(() => list.value.filter(item => item.children))

const clickMenu=(item)=>{
   
   
    router.push(item.path)
}
</script>

样式

<style lang="less" scoped >

.icons{
  width: 18px;
  height: 18px;
  margin-right: 5px;
}
.el-menu {
  border-right: none;
  h3 {
    line-height: 48px;
    color: #fff;
    text-align: center;
  }
}
.el-aside{
    height: 100%;
    background-color: #545c64;
}

</style>

编写header页面

在components下的CommonHeader.vue中

html

<template>

    <div class="header">
        
      <div class="l-content">
          
        <!-- 这个点击事件是控制菜单组件的收缩的-->
        <el-button size="small" @click="handleCollapse">
          <!-- 图标的展示,这里我们使用动态组件来展示图标-->
         <component class="icons" is="menu"></component>
        </el-button>
			
            <!-- 面包屑,separator是分隔符-->
        <el-breadcrumb separator="/" class="bread">
          <!-- 首页是一定存在的所以直接写死 -->
          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
            <!-- if判断一定要加-->
          <el-breadcrumb-item v-if="current" :to="current.path" >{
  
  {
            current.label
          }}</el-breadcrumb-item>

        </el-breadcrumb>
      </div>
      
        <!--右侧用户头像-->
      <div class="r-content">
        <el-dropdown>
          <span>
              <!--我们定义一个URl对象地址,这里是传入图片的名称-->
            <img :src="getImageUrl('user')"  class="user" />
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item>个人中心</el-dropdown-item>
              <el-dropdown-item @click="handleLoginOut">退出</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>

    </div>
    
  </template>

js

<script setup>
import {
   
   computed} from 'vue'
import {
   
   useRouter} from 'vue-router'
const router =useRouter()

const getImageUrl = (user) => {
   
   
      return new URL(`../assets/images/${
     
     user}.png`, import.meta.url).href;
    };

const current = computed(() => {
   
   
    //这个是当前选中菜单的面包屑,但是我们需要和菜单联动,先写成null
    return null
})
const handleCollapse=()=>{
   
   
  
}
const handleLoginOut=()=>{
   
   
  router.push("/login")
}

</script>

样式

<style lang="less" scoped>
.header {
   
   
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  height: 100%;
  background-color: #333;
}
.icons{
   
   
    width: 20px;
    height: 20px;
}
.r-content {
   
   
  .user {
   
   
    width: 40px;
    height: 40px;
    border-radius: 50%;
  }
}
.l-content {
   
   
  display: flex;
  align-items: center;
  .el-button {
   
   
    margin-right: 20px;
  }
}

:deep(.bread span) {
   
   
  color: #fff !important;
  cursor: pointer !important;
}
</style>

使用pinia

pinia官网:https://pinia.vuejs.org/zh/core-concepts/

下载

yarn add pinia

在main.js中使用

import {
   
    createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)

在src下创建stores文件夹,在其中创建index.js

import {
   
    defineStore } from 'pinia'
import {
   
    ref } from 'vue'

//初始化state数据,这里我们使用一个函数来返回
function initState(){
   
   
  return {
   
   
    
  }
}
//第一个参数要求是一个独一无二的名字
//第二个参数可接受两类值:Setup 函数或 Option 对象。
export const useAllDataStore = defineStore('allData', (a) => {
   
   
   	  //在 Setup Store 中:
      //ref() 就是 state 属性
	  //computed() 就是 getters
	  //function() 就是 actions	
      const state=ref(initState())



      //需要把所有定义的state,getters,actions返回出去
      return {
   
   
        state
      }
})

菜单组件和头部组件联动

components下的CommonAside.vue

html中

		//这个width也改成动态的
<el-aside :width="width">
    //在el-menu上有一个配置collapse,表示菜单是否收缩,这里我们在pinia中存放这个变量
    <el-menu  :collapse="isCollapse">
        //这个标题,我们使用v-show来动态展示,!isCollapse表示不收缩,isCollapse表示收缩
       <h3 v-show="!isCollapse">后台管理</h3>
       <h3 v-show="isCollapse">后台</h3>
    </el-menu>
</el-aside>   

js中

//引入定义的pinia
import {
   
    useAllDataStore } from '@/stores'

//这个store和vuex的差不多
const store=useAllDataStore()

//从pinia中取出isCollapse
const isCollapse =computed(() => store.state.isCollapse)
//width也是通过判断isCollapse动态决定
const width =computed(() => store.state.isCollapse? "64px" : "180px")

const clickMenu=(item)=>{
   
   
    //这个selectMenu 相当于vuex的action方法,传入item,用于头部的面包屑
    store.selectMenu(item)
    router.push(item.path)
}

在pinia中定义属性和方法

//改变初始化state的值
function initState(){
   
   
  return {
   
   
    isCollapse: false,
    currentMenu:null, 
  }
}
export const useAllDataStore = defineStore('allData', (a) => {
   
   
 	 //新添加的方法
    function selectMenu(val){
   
   
        //如果是点击的首页,那就不需要设置currentMenu
    if (val.name == 'home') {
   
   
      state.value.currentMenu = null
    }else {
   
   
        //如果点击的不是首页,那就把这个设置为第二个面包屑
      state.value.currentMenu = val
    }
  }

   //返回出去
  return {
   
   
    selectMenu
  }
})

components下的CommonHeader.vue

js

import {
   
    useAllDataStore } from '@/stores'
const store = useAllDataStore()

const current = computed(() => {
   
   
    //返回pinia中的当前点击的菜单
    return store.state.currentMenu
})

const handleCollapse=()=>{
   
   
    //在点击图标的时候,我们把isCollapse进行一个取反
    //pinia中可以直接修改state的值,省去了vuex中的Mutation的步骤
  store.state.isCollapse=!store.state.isCollapse
}

首页

1添加路由

打开路由文件

const routes = [
    {
   
   
      path: '/',
      name: 'main',
      component: () => import('@/views/Main.vue'),
      //添加重定向
      redirect: '/home',
        //添加子路由
      children:[
        {
   
   
        path: 'home',
        name: 'home',
        component: () => import('@/views/Home.vue')
        }
       
      ]
    }
  ]
  

2.在views下创建Home.vue

3.在Main.vue中放置路由出口

<el-main class="right-main">
    //在el-main中放置
    <router-view />
</el-main>

4.编写Home.vue

html,左侧的用户卡片和table表格

<template>

  <el-row class="home" :gutter="20">
    <el-col :span="8" style="margin-top: 20px">
        
      <el-card shadow="hover">
        <div class="user">
          <img :src="getImageUrl('user')"  class="user" />
          <div class="user-info">
            <p>Admin</p>
            <p>超级管理员</p>
          </div>
        </div>
        <div class="login-info">
          <p>上次登录时间:<span>2022-7-11</span></p>
          <p>上次登录的地点:<span>北京</span></p>
        </div>
      </el-card>
        
      <el-card shadow="hover" class="table" >
          
        <el-table :data="tableData">
          <el-table-column
            v-for="(val, key) in tableLabel"
            :key="key"
            :prop="key"
            :label="val"
          >
          </el-table-column>
        </el-table>
          
      </el-card>

    </el-col>
   
      
  </el-row>
</template>

js

<script setup>
import {
   
   ref} from 'vue'

const getImageUrl = (user) => {
   
   
      return new URL(`../assets/images/${
     
     user}.png`, import.meta.url).href;
}
//这个tableData是假数据,等会我们使用axios请求mock数据
const tableData = ref([
    {
   
   
      name: "Java",
      todayBuy: 100,
      monthBuy: 200,
      totalBuy: 300,
    },
    {
   
   
      name: "Python",
      todayBuy: 100,
      monthBuy: 200,
      totalBuy: 300,
    }
])

const tableLabel = ref({
   
   
    name: "课程",
    todayBuy: "今日购买",
    monthBuy: "本月购买",
    totalBuy: "总购买",
})




</script>

样式

<style lang="less" scoped >

.home {
    height: 100%;
    overflow: hidden;
    .user {
    display: flex;
    align-items: center;
    border-bottom: 1px solid #ccc;
    margin-bottom: 20px;
    img {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      margin-right: 40px;
    }
  }
  .login-info {
    p {
      line-height: 30px;
      font-size: 14px;
      color: #999;
      span {
        color: #666;
        margin-left: 60px;
      }
    }
  }
  .table{
    margin-top: 20px;
  }
  .num {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .el-card {
      width: 32%;
      margin-bottom: 20px;
    }
    .icons {
      width: 80px;
      height: 80px;
      font-size: 30px;
      text-align: center;
      line-height: 80px;
      color: #fff;
    }
    .detail {
      margin-left: 15px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      .num {
        font-size: 30px;
        margin-bottom: 10px;
      }
      .txt {
        font-size: 14px;
        text-align: center;
        color: #999;
      }
    }
  }
  .top-echart{
    height: 280px
  }
  .graph {
    margin-top: 20px;
    display: flex;
    justify-content: space-between;
    .el-card {
      width: 48%;
      height: 260px;
    }
  }
}

</style>

封装axios

axios官网:https://www.axios-http.cn/docs/intro

下载

yarn add axios

1.配置环境地址

在src下创建config文件夹,在其中创建index.js

/**
 * 环境配置文件
 * 一般在企业级项目里面有三个环境
 * 开发环境
 * 测试环境
 * 线上环境
 */


// 当前的环境
const env = import.meta.env.MODE || 'prod'

const EnvConfig = {
   
   
  development: {
   
   </
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宁渡课堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值