• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

vue3后台管理系统

武飞扬头像
miaomiao吖
帮助1

vite构建vue3项目

  1. npm create vite@latest,再回车
  2. 按要求写下项目名;manage-app
  3. 选择vue,再回车
  4. 选择javascript,在回车
  5. cd manage-app,到该项目目录下,回车
  6. 安装依赖:npm install,再回车
  7. 启动项目:npm dev
  8. main.js文件的改变:
createApp(App).mount('#app')
//上面那行改为下面这行,一样的,
const app=createApp(App)
app.mount('#app')

项目中其他需求的引入

1. element-plus引入

1. 全部引入:

  • 在终端安转:npm install element-plus --save
  • main.js文件引入:
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

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

  • 使用:直接在组件里编写element代码即可

2. 按需引入:

  • 在终端安转:npm install element-plus --save
  • 在终端安装插件:npm install -D unplugin-vue-components unplugin-auto-import
  • 在 vite.config.ts文件引入:
//引入部分
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

//plugins数组部分加入
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  • 使用:直接在组件里编写element代码即可

3. 手动引入:

  • 在终端安转:npm install element-plus --save
  • 终端安装:npm install unplugin-element-plus
  • 在 vite.config.ts文件引入:
//引入部分
import ElementPlus from 'unplugin-element-plus/vite'

//plugins数组部分加入
   plugins: [ElementPlus()],
  • 使用:在使用element的组件里引入具体需要的插件,比如需要引入按钮插件则需要加这段代码:
<script>
import { ElButton } from 'element-plus'

export default defineComponent({
    component:{ ElButton}
})
</script>

2. vue3引入路由

  1. 终端安装:npm install vue-router -S
  2. 新建文件:src / router / index.js:文件配置如下
import {createRouter,createWebHashHistory} from 'vue-router' 

const routes=[
    {
        path:'/',
        redirect:'/home',
        component:()=>import('../views/MainApp.vue'),
        children:[
            {
                path:'/home',
                nsme:'home',
                component:()=>import('../views/home/HomeApp.vue')
            }
        ]
    }
]

const router=createRouter({
    history:createWebHashHistory(),
    routes
})

export default router
学新通
  1. main.js文件引入
import router from './router'

const app=createApp(App)
app.use(router)
app.mount('#app')
  1. 使用:在具体的组件中需要导入
import {useRouter} from 'vue-router' //导入1
export default {
  setup() {
     let router=useRouter()//声明2
//下面可以配置方法进行路由跳转
     }
}
  1. 需要显示路由组件的地方添加<router-view></router-view>

3. element-plus图标的引入和使用

  • 在终端安转:npm install element-plus --save
  • 终端安转:npm install @element-plus/icons-vue
  • main.js引入:
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

app.mount('#app')

静态引入图标

  • 直接点击想要的图标图案,就可以复制相关代码
//例如加号图标
<el-icon><Plus /></el-icon>

动态引入图标

 <!-- 遍历菜单栏-->
    <el-menu-item :index="item.path" v-for="item in noChildren()" :key="item.path">
    
        <!-- 根据遍历得到的item,动态引入图标 -->
       <component class="icons" :is="item.icon"></component>
       
    </el-menu-item>

4. 引入less

  • 终端引入less:npm install less-loader less --save-dev

5. 基础样式引入

  • 在src/assets新建文件夹less
  • 在less文件夹新建reset.less文件,这个是全局样式
  • 在less文件夹新建index.less文件,里面只写@import './reset.less';
  • 在main.js中引入index.js文件 ,import './assets/less/index.less'

6. vuex的引入

    1. 终端安装:npm install vuex -S
    1. 新建文件:src / store / index.js,文件配置如下
import {createStore} from 'vuex'
export default createStore({
   //里面配置数据方法等
    state:{//数据
    
    } ,
    mutations:{//修改数据的方法

    },
})
    1. 在main.js文件引入
import store from './store/index.js'

const app=createApp(App)
app.use(store)
app.mount('#app')
    1. 使用:使用vuex数据和方法的组件需要引入
<script>
import { useStore } from "vuex";
export default defineComponent ({
  setup() {
  //定义store
  let store = useStore();
  
  function handleCollapse(){
    //调用vuex中的mutations中的updateIsCollapse方法
     store.commit("updateIsCollapse")
  }
    return {
       handleCollapse
    };
  },
});
</script>
学新通

工具类的使用

一、mock的使用

  • 本地mock 的使用

  1. 终端安转:npm install mockjs
  2. 新建文件:src / api / mockData / home.js(home.js表示的是home组件的mock数据)---------文件配置如下:
export default{
   getHomeData:()=>{    //导出home 的数据
       return {
           code:200,
          data:{
           tableData :[
               {
                 name: "oppo",
                 todayBuy: 500,
                 monthBuy: 3500,
                 totalBuy: 22000,
               },
               {
                 name: "vivo",
                 todayBuy: 300,
                 monthBuy: 2200,
                 totalBuy: 24000,
               },
               {
                 name: "苹果",
                 todayBuy: 800,
                 monthBuy: 4500,
                 totalBuy: 65000,
               },
               {
                 name: "小米",
                 todayBuy: 1200,
                 monthBuy: 6500,
                 totalBuy: 45000,
               },
               {
                 name: "三星",
                 todayBuy: 300,
                 monthBuy: 2000,
                 totalBuy: 34000,
               },
               {
                 name: "魅族",
                 todayBuy: 350,
                 monthBuy: 3000,
                 totalBuy: 22000,
               },
             ]
          }
       }
   }
}
学新通
  1. 再建文件:src / api / mock.js ,这个文件引入所有的mock数据,并且全部导出。----------- 文件配置如下
//导入mockjs
import Mock from 'mockjs'  

//导入home的数据
import homeApi from './mockData/home' 

//拦截请求,两个参数,第一个参数是设置的拦截请求数据的路径,第二个参数是对应数据文件里该数据的方法a
Mock.mock('/home/getData',homeApi.getHomeData)
  1. 引入:在mian.js文件里引入mock--------如下引入
import './api/mock.js'
  1. 使用:在响应的组件上使用
async function getTableList(){
  await axios.get("/home/getData").then((res)=>{
          tableData.value=res.data.data.tableData
   })
}
onMounted(()=>{
  //调用getTableList()方法
getTableList()
})
  • 线上fastmock 的使用

  1. 点击“ ”,创建项目,
    学新通
  2. 得到项目的根路径
    学新通
  3. 点击新增接口
    学新通
  4. 编辑接口路径以及返回的数据
    学新通
  5. 最终得到请求拦截地址,与请求得到的数据
    学新通
  6. 在组件里的使用:
//axios请求table列表的数据,并且将请求来的数据赋值给tableData
 async function getTableList(){
    await axios
    //该路径为线上mock的接口根路径与对应数据请求的路径的拼接。
    .get("https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api/home/getTableData")
    .then((res)=>{
        if (res.data.code == 200){
            //请求成功之后在进行渲染
             tableData.value=res.data.data
           }
     })
}
onMounted(()=>{
    //调用getTableList()方法
getTableList()
})
学新通

二、二次封装axios

  1. 二次封装axios的原因:处理接口请求之前或接口请求之后的公共部分

  2. 终端安装:npm install axios -S

  3. 在新建文件(环境配置文件):src /config / index.js------文件如下

/**
 * 环境配置文件
 * 一般在企业级项目里面有三个环境
 * 开发环境
 * 测试环境
 * 线上环境
 */
// 当前的环境赋值给变量env
const env = import.meta.env.MODE || 'prod'

const EnvConfig = {
  //1、开发环境
  development: {
    baseApi: '/api',
    //线上mock的根路径地址
    mockApi: 'https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api',
  },
  //2、测试环境
  test: {
    baseApi: '//test.future.com/api',
    mockApi: 'https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api',
  },
  //3、线上环境,企业才会用,
  pro: {
    baseApi: '//future.com/api',
    mockApi: 'https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api',
  },
}

export default {
  env,
  // mock的总开关,true则项目的所有接口调用的是mock数据
  mock: true,
  ...EnvConfig[env]//结构
}
学新通
  1. 新建文件:src / api /request.js-------文件如下
import axios from 'axios'
import config from '../config'
//错误提示
import { ElMessage } from 'element-plus'
const NETWORK_ERROR = '网络请求异常,请稍后重试.....'


//创建axios实例对象service
const service=axios.create({
    //根路径为config里的index.js文件里的开发环境的baseApi
    baseURL:config.baseApi
})


//在请求之前做的一些事情,request
service.interceptors.request.use((req)=>{
    //可以自定义header
    //jwt-token认证的时候
    return req//需要return出去,否则会阻塞程序
})
 


//在请求之后做的一些事情,response
service.interceptors.response.use((res)=>{
    console.log(res)
    //解构res的数据
    const { code, data, msg } = res.data
     // 根据后端协商,视情况而定
    if (code == 200) {
    //返回请求的数据
    return data
  } else {
    // 网络请求错误
    ElMessage.error(msg || NETWORK_ERROR)
     // 对响应错误做点什么
    return Promise.reject(msg || NETWORK_ERROR)
  }
})



//封装的核心函数
function request(options){
    //默认为get请求
    options.methods=options.methods || 'get'
    if (options.method.toLowerCase() == 'get') {
        options.params = options.data
      }

            // 对mock的处理
  let isMock = config.mock  //config的mock总开关赋值给isMock
  if (typeof options.mock !== 'undefined') { //若组件传来的options.mock 有值,单独对mock定义开关
    isMock = options.mock  //就把options.mock 的值赋给isMock
  }
   // 对线上环境做处理
   if (config.env == 'prod') {
    // 如果是线上环境,就不用mock环境,不给你用到mock的机会
    service.defaults.baseURL = config.baseApi
  } else {
    //ismock中开关是否为true,若为真,则说明事由mock环境,那么跟路径就要事由mock的根路径
    service.defaults.baseURL = isMock ? config.mockApi : config.baseApi
  }

  return service(options)   //函数的返回值
}

export default request
学新通
  1. 新建文件 src / api / api.js-----文件如下:
// 整个项目api的管理
import request  from "./request";
 //导出
export default{
    //home组件左侧表格数据获取
    getTableData(params){
        //request就是reuest.js文件中封装的核心函数,里面的对象就是options参数
        return request({
            url:'/home/getTableData',
            method:'get',
            data:params,//通过getTableData(params)方法的形参传过来
            mock:true
        })
    },
}
  1. 如果要进行axios请求数据,直接引入api.js即可
  2. 将api.js的方法挂载到全局,在main.js的文件配置如下
import api from './api/api'  //引入api.js

const app=createApp(App)
app.config.globalProperties.$api = api  //全局挂载,将api赋值给$api
app.mount('#app')

  1. 使用:
import { defineComponent ,getCurrentInstance,onMounted ,ref} from "vue";

export default defineComponent({
  setup() {
    //proxy类似于vue2的this
    const {proxy}=getCurrentInstance()
    //左侧表格的tableData数据
    let tableData =ref([])
   
//getTableList()这个方法里面使用了api.js里面的getTableData方法来请求table里的数据
async function getTableList(){
    let res=await proxy.$api.getTableData()  //通过proxy拿到api的请求数据的方法
    tableData.value=res
}


onMounted(()=>{
    //调用getTableList()方法
getTableList()
})
  },
});
</script>
学新通
  1.  

普通component组价

CommonHeader.vue(布局里的头部组件)

  1. 效果图
    学新通

  2. 左侧引入图标,并且图标嵌套在el-button里

  3. 右侧个人头像,点击出现下拉菜单,个人中心和退出

    <img class="user" :src="getImageUrl('user')" alt="" />  //src前面加冒号,表示动态
    
     // 动态引入图片路径,参数为图片名字,
    function getImageUrl(user) {
      return new URL(`../assets/images/${user}.jpg`, import.meta.url).href;
      //../assets/images/${user}.jpg是图片相对路径,import.meta.url表示的是当前组件路径,两者进行拼接。
    }
    
    return {
        getImageUrl,
    };
    
    • 下拉菜单的实现:使用elment-plus的Dropdown 下拉菜单

面包屑的实现

  1. 使用element-plus的Breadcrumb 面包屑
  2. 实现思路:点击用户管理,首页后面显示用户管理,点击页面1,首页后面出现页面1,由于点击的是commonAside组件,而显示的面包屑出现在commonHeader组件,是跨组件建的通信,所以vuex管理数据。
  3. 在store / index.js 写如下代码
import {createStore} from 'vuex'
export default createStore({
   
    state:{
     //当前菜单赋值为空
     currentMenu:'',
    } ,
    mutations:{
       //选择菜单,val是commonAside组件传过来的当前点击的菜单值
       selectMenu(state,val){
       //判断,如果当前点击的菜单名为home,home就是首页,就让当前菜单currentMenu还是赋值为空,
       //否则就让currentMenu赋值为当前菜单项item
       val.name=='home'?(state.currentMenu==null):(state.currentMenu==val)
       }
    },
})
学新通
  1. 在commonAside组件里通过store调用 selectMenu()方法,并且传入当前的菜单项item
        //在点击菜单进行路由跳转时调用vuex中的selectMenu(),并且传入当前的菜单项item
         // 点击菜单进行路由跳转方法
          function clickMenu(item){
         router.push({
            name:item.name
         })
         
       //vuex来管理路由跳转
       store.commit('selectMenu',item)
    }
  1. 在commonHeader组件里得到vuex的 currentMenu数据
     <!-- 面包屑 -->
 <el-breadcrumb separator="/">
     //永远显示首页
    <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> 
    
    //动态显示当前的菜单标签,并且有才current才显示
    <el-breadcrumb-item :to="current.path" v-if="current">{{current.label}}</el-breadcrumb-item>  
 </el-breadcrumb>
 
import { computed, defineComponent } from "vue";
import { useStore } from "vuex";
export default defineComponent({
     setup() {
    let store = useStore()
    
    //拿到vuex的 currentMenu数据,用计算属性,这样子菜单改变,页面也会跟着改变
    const current= computed(()=>{
          return store.state.currentMenu
    })
    }
    return {
      current,
    };
  },
})
</script>

学新通

CommonAside.vue(布局里的左侧菜单)

  1. 效果图:
    学新通

  2. 布局使用:elment-plus的Menu菜单的侧栏

  3. 具体菜单实现

    • 将菜单分为两组,有子菜单和无子菜单,分别遍历菜单数据并动态渲染文字、图标等。
  4. 功能:点击菜单,跳转到相应的路由组件。

<!-- 没有children的一级菜单 -->
      <el-menu-item
        :index="item.path"
        v-for="item in noChildren()"
        :key="item.path"
          @click="clickMenu(item)"  点击进行跳转的方法,并且传入item
      >
    // 点击菜单进行路由跳转方法
      function clickMenu(item){
        router.push({
           name:item.name,
        })
    }

CommonTab.vue -----(tag标签的展示及切换)

  1. 思路:
    • 首页的tag一开始就会存在,而且是不能进行删除的
    • 当点击左侧栏的时候,如果tag没有该菜单名称则新增,如果已经有了那么当前tag背景为蓝色。
    • 删除当前tag,如果是最后一个,那么路由调整到它前面那个标签并且背景为蓝色,如果不是最后一个,那么路由调整到它后面那个标签并且背景为蓝色。
    • 注意tag无论路由如何切换都会存在,所以这个tag一定存在mainApp.vue组件中。
  2. 使用elment-plus的Tag 标签
  3. 在store / index.js里存储首页tag数据
import {createStore} from 'vuex'
export default createStore({
    // 数据
    state:{
        //tag数据,一开始只有首页
        tabsList:[
            {
                path:'/',
                name:'home',
                label:'首页',
                icon:'home'
            }
        ],
    },
  1. 在CommonTab.vue组件里拿到首页的tag数据
import { useStore } from "vuex";
export default {
  setup() {
    let store = useStore();
    const tags = store.state.tabsList;  //不需要计算属性拿到数据,因为这个值不会变化,永远都有首页这个tag
  1. 当点击不是首页的菜单时候,查看当前的 tabsList数组是否有该菜单,如果没有则向 tabsList数组添加该菜单项,如果有该菜单,就什么都不做。
 //点击菜单进行路由跳转
       selectMenu(state,val){
        if(val.name == 'home'){
            state.currentMenu = null
        }else{
            state.currentMenu=val
            //arr.findIndex 方法返回找到的元素的索引,而不是元素本身。如果没找到,则返回 -1
           let result= state.tabsList.findIndex(item=>item.name == val.name)//item表示的是数组的每一项
           result== -1? state.tabsList.push(val):''
        }
       },
  1. 点击对应tag标签进行路由跳转,所以需要tag标签里设置点击事件

<script>
import { useRouter, useRoute } from "vue-router";
export default {
  setup() {
    let router=useRouter()
    //点击tag标签进行路由跳转的方法
    function changeMenu(item){
         router.push({
            name:item.name
         })
    }
 return {
         changeMenu,
    };
};
</script>
学新通
  1. 点击关闭tag标签,设置点击事件
//点击tag标签关闭方法
   function handleClose(tag,index){
   //让长度与索引保持一致
      let length=tags.length - 1;
      //处理vuex的tablelist,就是删除当前的菜单项
      store.commit("closeTab",tag)
      //做第一个判断,如果当前显示的菜单tag与要删除的tag菜单不一致,不做处理,还是上面的closeTab方法
      if(tag.name !== route.name){
        return;
      }

      //如果当前显示的菜单tag与要删除的菜单tag相同
       //并且是最后一个菜单tag,则路由跳转到前面一个菜单tag
      if(index === length){
        router.push({
            name:tags[index-1].name
        })
    }else{ //不是最后一个菜单tag,则路由跳转到后面一个菜单tag,因为删除了该菜单,所以后面那个菜单的索引就是被删除菜单的索引。
        router.push({
            name:tags[index].name,
        })
      }
   }
学新通
  1. 在store文件里设置关闭标签的方法
   closeTab(state,val){
       //拿到当前菜单的索引
       let res= state.tabsList.findIndex(item=>item.name === val.name) 
      //在tabsList数组里,从当前菜单索引开始删除 1 个元素,就是删除当前的菜单项目
       state.tabsList.splice(res,1)  
       },

  1. 在mainApp.vue组件里引用该tab组件
    .

路由views组件

LoginApp.vue(登录页面)

功能:根据不同的用户角色,返回不一样的系统菜单

  1. UI效果图

  2. 先配置路由跳转,登录路由级别与首页路由同级

  3. 使用element-plus的form表单


  1. 创建本地mock的permission.js写不同用户登录得到不同菜单的逻辑
  2. 在api.js里创建接口
   //根据用户的用户名不同,返回不一样的菜单列表
        getMenu(params){  //点击登录时,用户信息会作为参数传过来
            return request({
                url:'/permission/getMenu',
                method:'post',
                mock: false,
                data: params
            })
          },  
  1. 在mock.js里导入permission.js并且拦截接口,调用permission.js的方法
//导入登录的数据
import permission from './mockData/permissin'
Mock.mock(/permission\/getMenu/, 'post', permission. getMenu)
  1. 用v-model双向绑定用户的账号和密码,当点击登录时,就调用api.js里的getMenu(params)方法,并把当前的用户信息作为参数传过去,判断用户返回不一样的菜单,如图所示。
    学新通
  2. 将返回的菜单数据存储到vuex中,aside组件显示出来。之前的菜单项是写死的,现在需要动态渲染。
  3. 在store / inde.js里定义方法得到当前用户的菜单,并且localStorage.setItem存储菜单数据
  //根据不同用户返回不一样的菜单
       setMenu(state,val){
              state.menu=val
              localStorage.setItem('menu',JSON.stringify(val))  //将val转化为JSON格式
       },
  1. CommonAside组件里通过store拿到当前的项。

  2. 在登录页面点击登录时通过store调用该方法,并且进行路由跳转到首页

  3. 每次刷新时,存储在vuex里的数据会丢失,所以动态得到的菜单也会消失,所以我们要解决数据持久化的问题。

  4. 所以在vuex里定义一个方法,用localStorage.getItem来获取当时localStorage.setItem存储的菜单数据,并且该方法在每次刷新的时候调用,每次刷新肯定会经过App.vue组件,所以在App.vue组件里调用该方法。


    //刷新获取 localStorage.setItem存储的菜单数据
    addMenu(state){
        if(!localStorage.getItem('menu')){
            return
        }
        const menu=JSON.parse(localStorage.getItem('menu'))
        state.menu=menu

    }

动态路由的实现

功能:根据后台返回的menu数据,动态的添加路由,而不是在router / index.js文件里直接配置要跳转的路由。除了首页和登陆注册页面,其他页面的路由都应该是动态添加的。
思路:

  1. 定义一个新的数组。用来存储菜单
const menuArray=[]
  1. 在store / index.js文件里的addmenu方法里遍历所得到的动态菜单,如果当前的菜单有子菜单,那么就用map对子菜单操作,返回当前菜单的路径url,并且使用懒加载的方式引入路由组件,并且把该子菜单项push到菜单空数组里
 menu.forEach(item => {
            if(item.children){
               item.children=item.children.map(item=>{
                let url=`../views/${item.url}.vue`
                item.component=()=>import(url)
                return item
               })
               menuArray.push(...item.children)//解构出子菜单
            }
  1. 如果当前菜单没有子菜单,依然返回路径,和上面操作一样,并且把该菜单项push到菜单空数组里
  2. 当menuArray里有菜单时,就遍历该菜单,并且使用addRoute方法添加一条新的路由记录作为首页路由(首页路由名为home1)的子路由。所以addmenu方法里还需要设置第二个参数router,当刷新时,需要传router过来。
  menuArray.forEach(item=>{
            router.addRoute('home1',item)
        })
  1. 则routre / index.js文件里的children路由应该设置为一个空数组。
  2. addmenu方法应该在main.js文件里使用,因为如果在app.vue里使用,此时,页面已经挂载完毕,此时在进行动态路由添加晚了。

登出功能的实现

思路:

  1. 点击退出时,添加点击事件。
  2. 清除掉当前菜单
  3. 在store / index.js里定义清除菜单的方法,用localstorage.remove清除掉当时存储的菜单,
    //    清除菜单
      clearMenu(state){
        state.menu=[]
        localStorage.removeItem('menu')
      },

  1. 然后在点击退出的时候调用该方法,并且跳转到登录页面。
//退出方法
    function handleLoginOut(){
      //清除菜单
      store.commit('clearMenu')
     router.push({
      name:'login'
     })
    }

路由守卫的实现

思路:

  1. 即使我们知道首页等其他页面地址,但是如果我们没有进行登录操作,就不能跳转到对应的地址,而是直接跳转到登录页面地址,进行登录操作。根据后端返回的token来进行路由守卫,
  2. 在vuex里进行管理。先设置token 为空,当我们登录时,就对拿到登录返回的token值,并且赋值给vuex力的token
  3. 并且要对Token进行持久化,需要下载cookie
  4. 终端安装cookie:npm install js-cookie --save
  5. 引用import jsCookie from 'js-cookie'
  6. 将登录时返回的token数据val赋值给当前vuex里的token,
    //设置token
      setToken(state,val){
              state.token=val
              Cookie.set('token',val)
      },



      //清除token
      clearToken(state){
        state.token=''
        Cookie.remove('token')
      },

      

      //获取token
      getToken(state){
      //如果当前的vuex里有Token的值,就获取vuex里的token值,如果当前vuex里token值为空。
     state.token=state.token || Cookie.get('token')
      }
学新通
  1. 当点击登录按钮时,就调用设置Token的方法,并且拿到当前的Token作为参数对vuex里的token进行赋值
  2. 在main.js中添加一个全局路由守卫,先调用获取Token的方法,并且的到store里的token值,如果没有token 并且即将要跳转到的路由页面不是登录页面,那就直接让它跳转到登录页面。如果有token值,还要进行判断,如果没有匹配到当前路径,就直接跳转到首页,如果能匹配到当前路径,就跳转到对应的路由页面。
router.beforeEach((to,from,next)=>{
   store.commit('getToken')
   const token=store.state.token
   if(!token && to.name !=='login'){
    next({name:'login'})
   }else if(!checkRouter(to.path)){  //如果没有检测到当前路径,就直接跳转到首页
    next({name:'home'})
   } else{
    next()
   }
})
  1. 在mian.js文件里要定义一个检查已有路由的方法,并且该方法要写在动态路由方法下面。
  2. getRoutes获取所有 路由记录的完整列表。并且对这些路由进行过滤,输出与输入的路径相同的已有路径的路由,如果该长度为0,说明输入的路径在已有的路由路径里不存在。就return false
  3. 在长度,则说明输入的路径在已有的路由路径里存在,就return true
function checkRouter(path){
  let hasCheck=router.getRoutes().filter(route=>route.path==path).length
  if(hasCheck){
    return true
  }else{
    return false
  }
}

mainApp.vue(总体的结构布局组件)

  1. layout整体布局实现:. 使用element-plus的Container 布局容器
    学新通
  2. 引入CommonHeader组件,并且在header区域导入<CommonHeader/>
  3. 引入CommonAside组件,并且在Aside区域导入<CommonAside/>

HomeApp.vue(布局里的主要展示区域)

  1. 总体使用elment-plus的 layout布局

    • layout布局的大概了解:

      一行:<el-row></el-row>, 整个页面可以用这个布局
      一列:<el-col></el-col>

  2. 用户信息展示使用使用elment-plus的card卡片

    • 代码:
      <el-card shadow="hover">
       <!-- 卡片的上部分具体的展示内容 -->
       <div class="user">
         <!-- 图片展示 -->
         <img src="../../assets/images/user.jpg" alt="" />
         <!-- 用户信息展示 -->
         <div class="user-info">
           <p class="name">Admin</p>
           <p class="role">超级管理员</p>
         </div>
       </div>
       <!-- 卡片的下部分具体的展示内容 -->
       <div class="login-info">
         <p>上次登录时间<span>2022-7-11</span></p>
         <p>上次登录地点<span>南昌</span></p>
       </div>
     </el-card>
    
    学新通
    • 效果:
      学新通
  3. 数据展示使用elment-plus的Table 表格

    • 代码:
    <el-card shadow="hover" style="margin-top: 20px" height="450px">
       <!-- 卡片里显示的是表格 -->
       <el-table :data="tableData">
         <!-- 表格的每列的标题<el-table-column/>,并且根据数据遍历每一列-->
         <el-table-column
           v-for="(val, key) in tableLabel"
           :key="key"
           :prop="key"
           :label="val"
         >
         </el-table-column>
       </el-table>
     </el-card>
    
    //数据
    <script>
    import { defineComponent } from "vue";
    export default defineComponent({
    setup() {
    //左侧表格的tableData数据
    const tableData = [
     {
       name: "oppo",
       todayBuy: 500,
       monthBuy: 3500,
       totalBuy: 22000,
     },
     {
     ......
     },
    ........
    ];
    //tableData数据的表头
    const tableLabel = {
     name: "品类",
     todayBuy: "今日购买",
     monthBuy: "本月购买",
     totalBuy: "总共购买",
    };
       return {
        tableData,
         tableLabel
       };
       },
       });
       </script>
    
    学新通
    • 效果:
      学新通
  4. home组件右侧上面的数据展示,也是用card卡片展示

    • 效果图:
      学新通

    • 数据来源于线上fastmock
      学新通

    • 在api.js文件中配置对应的请求数据的方法

    <script>
    import { defineComponent, getCurrentInstance, onMounted, ref } from "vue";
    export default defineComponent({
    setup() {
    //proxy类似于vue2的this
    const { proxy } = getCurrentInstance();
    
    let countData = ref([]);
    
    //getCountData()这个方法里面使用了api.js里面的getCountData方法来请求table里的数据
    async function getCountData() {
     let res = await proxy.$api.getCountData();
    console.log(res);
    countData.value = res;
    }
    
    onMounted(() => {
    getCountData();
    });
    return {
    countData,
      };
      },
      });
     </script>
    
    学新通
  5. homeApp组件右侧表格:使用echart表格

  • 折线图(echart表格)

具体可查看echart的配置项手册
* 效果:
学新通
* 终端下载echart:npm install echarts
* 页面引入echart:import * as echart from 'echarts'
* 装echart的盒子
xml <!-- 装echart折线表格的大卡片 --> <el-card style="height: 280px"> <!-- echart折线表格 --> <div ref="echart" style="height: 280px"></div> </el-card>

  • 图形的配置

      //折线图和柱状图的echarts配置
       let xOptions = reactive({
    // 图例文字颜色
    textStyle: {
    color: "#333",
    },
      grid: {
     left: "20%",
    },
    // 提示框
    tooltip: {
    trigger: "axis",
       },
       xAxis: {
      type: "category", // 类目轴
    data: [],
    axisLine: {
      lineStyle: {
        color: "#17b3a3",
      },
    },
    axisLabel: {
      interval: 0,
      color: "#333",
    },
    },
    yAxis: [
    {
      type: "value",
      axisLine: {
        lineStyle: {
          color: "#17b3a3",
        },
      },
    },
    ],
    color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
     series: [],
     });
    
      ```
    
    
    
    学新通
  • 设置echart表格的空数据

         //下面是rchart表格的数据,先设置为双向绑定数据,并且都为空值,后面通过接口获取数据,进行赋值
       let orderData = reactive({
      xData: [],
      series: [],
      });
       xData: [],
      series: [],
      });
      let videoData = reactive({
       series: [],
     });
    
    
    • 线上mock配置表格数据
      学新通

    • 在api.js文件中创建方法请求表格数据

     getEchartData(params){
     return request({
         url:'/home/getEchartData',
         method:'get',
         data:params,
         mock:true
     })
    },
    
  • 在homeApp.vue页面中获取数据,并且赋值给3个echart表格的空数据

         //通过接口获取echart表格的数据,api.js
    async function getEchartData(){
    let result=await proxy.$api.getEchartData()
    let orderData=result.orderData   //折线图数据
    let userData=result.userData    //柱状图数据
    let videoData=result.videoData   //饼状图数据
    orderData.xData=orderData.date     //将折线图的x坐标的数据进行赋值
    //每一条折线的数据是对象形式,每一条折线数据里的键相同,返回其中一条折线数据所有的键的数组。
    const keyArray=Object.keys(orderData.data[0])
    //定义一个空数组,折线数据的配置
    const series=[]
    //遍历每一个键,并向空数组里添加一个对象,该对象包括折线名字,折线纵坐标的值,还有折线的展现形式
    keyArray.forEach((key) => {
    series.push({
      name: key,  //折线的名字
      data:  orderData.data.map((item) => item[key]),  //得到该键的值,就是折线纵坐标的值
      type: "line",  //为折线展示
    });
    });
    }
    
     orderData.series=series    //对折线图的空数据进行赋值给orderData.series
    xOptions.xAxis.data=orderData.xData   //对折线图的xAxis.data横坐标空数据进行重新赋值
     xOptions.series=orderData.series  //对折线图的orderData.series赋值给空数组series
    
                        //对折线图进行渲染
    let hEcharts = echart.init(proxy.$refs['echart'])  //将echart进行赋值给hechart
    hEcharts.setOption(xOptions)  //开始建立折线图渲染
    onMounted(() => {
    
    getEchartData()
    });
    
    
    学新通
  • 柱状图(echart表格)
    • 效果:
      学新通
    • 页面的
      <!-- 左侧柱状图 -->
     <el-card style="height:260px">
       <div ref="userechart" style="height:240px"></div>
     </el-card>
    
    • 柱状图的配置
      //折线图和柱状图的echarts配置相同,只是横坐标,纵坐标,series不同,这些数据请求得到。
      let xOptions=reactive({
    // 图例文字颜色
    textStyle: {
      color: "#333",
    },
    grid: {
      left: "20%",
    },
    // 提示框
    tooltip: {
      trigger: "axis",
    },
    xAxis: {
      type: "category", // 类目轴
      data: [],
      axisLine: {
        lineStyle: {
          color: "#17b3a3",
        },
      },
      axisLabel: {
        interval: 0,
        color: "#333",
      },
    },
    yAxis: [
      {
        type: "value",
        axisLine: {
          lineStyle: {
            color: "#17b3a3",
          },
        },
      },
    ],
    color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
    series: [],
    
    })
    
    学新通
    • 请求柱状图的数据
    //通过接口获取echart表格的数据,api.js
    async function getEchartData(){
    let result = await proxy.$api.  getEchartData();
    let userData=result.userData    //柱状图数据
    }
    
    • 将请求来的数据赋值给当前的图形数据
      // 柱状图进行渲染的过程
    userData.xData =userData.map((item) => item.date);  //柱状图的横坐标的值
    userData.series = [
      {
        name: "新增用户",
        data: userData.map((item) => item.new),
        type: "bar",
      },
      {
        name: "活跃用户",
        data: userData.map((item) => item.active),
        type: "bar",
      },
    ];
    xOptions.xAxis.data=userData.xData
    xOptions.series=userData.series
    
    let uEcharts = echart.init(proxy.$refs['userechart'])
    uEcharts.setOption(xOptions)
    
    学新通
  • 饼状图(echart表格)
    • 效果:
      学新通
    • 页面的装饼状图的代码
     <!-- 右侧饼状图 -->
     <el-card style="height:260px">
       <div ref="videoechart" style="height:240px"></div>
     </el-card>
    
    • 饼状图的配置
        //饼状图的配置
       let pieOptions = reactive({
        tooltip: {
       trigger: "item",
       },
       color: [
       "#0f78f4",
       "#dd536b",
       "#9462e5",
       "#a6a6a6",
       "#e1bb22",
       "#39c362",
       "#3ed1cf",
       ],
       series: [],
       });                     
    
    学新通
    •  
    // 饼状图进行渲染的过程
    videoData.series = [
       {
         data: videoData,
         type: "pie",
       },
     ];
      pieOptions.series = videoData.series;
     let vEcharts = echart.init(proxy.$refs["videoechart"]);
     vEcharts.setOption(pieOptions);
    };
    
    •  

UserApp.vue(用户管理页面)

使用element-plus里的table表格的固定列

获取用户数据

  1. 使用本地mock来配置用户数据:在mockData / user.js中
import Mock from 'mockjs'

// get请求从config.url获取参数,post从config.body中获取参数
function param2Obj(url) {
  const search = url.split('?')[1]
  if (!search) {
    return {}
  }
  return JSON.parse(
    '{"'  
    decodeURIComponent(search)
      .replace(/"/g, '\\"')
      .replace(/&/g, '","')
      .replace(/=/g, '":"')  
    '"}'
  )
}

//创建了200条数据,
let List = []
const count = 200

for (let i = 0; i < count; i  ) {
  List.push(
    Mock.mock({
      id: Mock.Random.guid(),
      name: Mock.Random.cname(),
      addr: Mock.mock('@county(true)'),
      'age|18-60': 1,
      birth: Mock.Random.date(),
      sex: Mock.Random.integer(0, 1)
    })
  )
}

export default {
  /**
   * 获取列表
   * 要带参数 name, page, limt; name可以不填, page,limit有默认值。
   * @param name, page, limit
   * @return {{code: number, count: number, data: *[]}}
   */
  getUserList: config => {
  //每一页数据20条
    const { name, page = 1, limit = 20 } = param2Obj(config.url)
    const mockList = List.filter(user => {
      if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false
      return true
    })
    const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
    return {
      code: 200,
      data: {
        list: pageList, //返回当前页的数据
        count: mockList.length,   //返回整个数据的长度
      }
    }
  },
学新通
  1. 在mock.js文件里引入user.js文件,拦截请求,请求用户管理列表数据 ,
import userApi from './mockData/user'

//本地获取user的数据,第一个参数通过正则来匹配路径,第二个参数是请求方式,第三个参数是请求数据的方法
Mock.mock(/user\/getUser/, 'get', userApi.getUserList)
  1. 在api.js里利用二次封装的axios进行请求数据
//获取user的table数据
    getUserData(params) {
        return request({
          url: '/user/getUser',
          method: 'get',
          // 这个mock如果是true的话 用的就是线上fastmock的数据
          mock: false,
          data: params
          // 分页器的data:{total: 0,page: 1,}//page是1拿到第一页的数据
        })
      },
  1. 在用户管理页面上获取该数据,并赋值给定义的双向绑定的空数据,渲染到页面上。

  2. 自己设置表头数据

// table 表格表头的数据
//因为返回的性别数据不是我们需要的,后面进行处理
    const tableLabel = reactive([
      {
        prop: "name",
        label: "姓名",
      },
      {
        prop: "age",
        label: "年龄",
      },
      {
        prop: "sexLabel",
        label: "性别",
      },
      {
        prop: "birth",
        label: "出生日期",
        width: 200,
      },
      {
        prop: "addr",
        label: "地址",
        width: 320,
      },
    ]);
学新通
  1. 对拿到的性别数据进行处理,因为数据返回0和1,我们需要展示男女
//遍历每一条数据的sex,进行操作,将0,1该为男女,将值赋值给sexLabel
      list.value = res.list.map((item) => {
        item.sexLabel =(item.sex === 0 ? "女" : "男";)
        return item;  //并且每遍历一次就返回
      });
用户的分页实现
  1. 使用elment-plus的Pagination 分页
  2. 定义一个对象congig ,里面有tatol属性,和当前页page属性
//定义当前页和atol的响应数据对象config
   const config = reactive({
     page: 0,//默认为1,展示第一页的数据
     total: 1,  //默认为 1,会别请求得到的tatol覆盖
   });
  1. 分页器设置点击事件,并且每次点击时候都请求一次用户数据
<!-- 分页器 -->
    <el-pagination 
    background 
    layout="prev, pager, next" 
    :total=config.total     //分页器的总数
     class="pager mt-4"   //自己设置样式
    @current-change="changePage"  //点击事件,不用传参,默认点击的时候会得到该分页器的页数,
    />

//点击分页器跳转方法
   function changePage(page) {
     config.page = page;
     getUserData(config);
   }

增删改查用户数据

1. 搜索用户的实现
  • 搜索框使用element-plus的form表单的行内表单

      <!-- 搜索框 -->
      <el-form :inline="true" :model="formInline"> //
        <el-form-item label="请输入">
        //
          <el-input v-model="formInline.keyword" placeholder="请输入用户名" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSerch">搜索</el-button>
        </el-form-item>
      </el-form>
    
  • 在搜索按钮里定义点击事件,
//定义当前页和atol的响应数据对象config,这里新增name属性
    const config = reactive({
      page: 0,
      total: 1,
      name: "", //默认空值
    });

    //定义forminline,搜索框的关键字
    const formInline = reactive({
      keyword: "",
    });

 //点击搜索的方法,把keyword关键字赋值给config的name属性,作为params参数,拿到对应的数据
    function handleSerch() {
      config.name = formInline.keyword;
      getUserData(config);
    }
学新通
2. 新增用户的实现
  1. 新增用户ui界面实现
  • 效果图:
    学新通

  • 使用elmnt-plus的Dialog 对话框

  • Dialog 对话框添加用户信息新增的表单

  • 性别选择用表单里的选择框

  • 出生日期用表单的选择日期

  • 页面代码

 <!-- 用户新增布局dialog ,模态框,新增用户和编辑用户信息共用一个模态框,-->
  <el-button type="primary"  @click="dialogVisible = true"> 新增</el-button>
   <el-dialog
    v-model="dialogVisible"
    title="新增用户"
    width="40%"
    :before-close="handleClose"
  >
  <!-- 用户信息表单部分 -->
  <el-form :inline="true" :model="formUser" class="userForm">
    <!-- 一行包含两列 -->
    <el-row>
       <el-col :span="12">
           <el-form-item label="姓名">
               <el-input v-model="formUser.name" placeholder="请输入用户名" />
           </el-form-item>
        </el-col>
       <el-col :span="12">
            <el-form-item label="年龄">
                <el-input v-model="formUser.age" placeholder="请输入用户年龄" />
            </el-form-item>
        </el-col>
    </el-row>
     <!-- 一行包含两列 -->
    <el-row>
       <el-col :span="12">
            <el-form-item label="性别">
                <el-select v-model="formUser.sex" placeholder="请输入性别">
                    <el-option label="男" value="1" />
                    <el-option label="女" value="0" />
                </el-select>
           </el-form-item>
        </el-col>
       <el-col :span="12">
            <el-form-item label="出生日期">
             <el-date-picker
                v-model="formUser.birth"
                type="date"
                placeholder="请选择出生日期"
                style="width: 100%"
                 />
            </el-form-item>
        </el-col>
    </el-row>
      <!-- 最后只有一行只有一列地址 -->
    <el-row>
       <el-col :span="12">
           <el-form-item label="地址">
               <el-input v-model="formUser.addr" placeholder="请输入地址" />
           </el-form-item>
       </el-col>
    </el-row>
      <!-- 第四行是取消确定按钮,并且靠右 -->
    <el-row style="justify-content: flex-end">
          <el-button type="primary" @click="handleCancel">取消</el-button>
          <el-button type="primary" @click="onSubmit">确定</el-button>
    </el-row>
  </el-form>
 </el-dialog>

学新通
  • 定义用户对象数据,双向定
//添加用户的form数据
    const formUser = reactive({
      name: "", //添加的用户姓名
      age: "", //添加用户年龄
      sex: "",
      birth: "",
      addr: "",
    });

  1. 新增用户数据实现
  • 要将对话框里填写的用户 提交到用户数据里。
  • 本地mock的user.js中已经定义了添加用户的方法,
  • 在mock.js里拦截数据,当匹配到该路径的话,就调用该方法
 Mock.mock(/user\/add/, 'post', userApi.createUser)
  • api.js中利用二次封装的axios提交用户数据。
//新增用户接口
      addUser(params){
        return request({
            url:'/user/add',
            method:'post',
            mock: false,
            data: params
        })
      },
  • 点击确定提交用户数据成功之后,需要完成以下事情
    • 对话框要重置,里面的数据要消失
    • 对话框要消失
    • 还需要重新调用getuserlist方法 。拿到最新的用户数据
//点击确定提交用户数据
async function onSubmit(){
  let res = await proxy.$api.addUser(formUser);
  if(res){
    //重置表单,<el-form :inline="true" :model="formUser" ref="userForm">,userForm表单数据重置
    proxy.$refs.userForm.resetFields();
    //点击确定后关闭对话框
       dialogVisible.value = false;
    //重新请求用户数据
      getUserData(config)

  }
}
  •  
  1. 新增用户表单验证
:rules就是自己定义的规则

  <el-form-item label="姓名"  prop="name"  :rules="[{ required: true, message: '姓名是必填项' }]" >
  
  <el-form-item label="年龄"  prop="age" 
            :rules="[
                { required: true, message: '年龄是必填项' },
                { type: 'number', message: '年龄必须是数字' },
            ]">
  <el-input v-model.number="formUser.age" placeholder="请输入用户年龄" />
  v-model.number可以使输入的字符串数字转变为number类型。

  • 点击确定时
    • 每一个用户信息框都进行校验,当有信息框为填时,不能提交数据 , 用表单的validae方法来校验。
    
    //点击确定提交用户数据
    function onSubmit(){
     //如果能拿到形参,就是如果所有的必选框都填了,就提交数据
     proxy.$refs.userForm.validate(async (valid)=>{
      if(valid){
          //调用日期格式化方法
    formUser.birth = timeFormat(formUser.birth);
     let res = await proxy.$api.addUser(formUser);
     if(res){
     //重置表单
     proxy.$refs.userForm.resetFields();
     //点击确定后关闭对话框
        dialogVisible.value = false;
     //重新请求用户数据
       getUserData(config)
    
    }
      }
     })
    }
    
    学新通
  • 点击取消按钮时候,应该完成以下事情
    • 重置表单
    • 关闭对话框
  //点击取消,重置表单,关闭对话框
   function handleCancel() {
     proxy.$refs.userForm.resetFields();
     dialogVisible.value = false;
   }
  • 当有信息未填写完成,点击确定时,应该提示错误信息,使用element-plus的Message
3. 编辑用户的实现

思路:

  1. 点击编辑时候,显示对话框。并且复用新增用户的对话框,新增用户的对话框变成编辑用户的对话框
  2. 定义一个变量来区分当前是编辑用户还是新增用户。在点击新增和编辑的时候改变该变量。
  3. 点击编辑的时候要把拿到当前行的用户信息,可以用插槽的方法#default="scope"拿到当前表格的数据,再用scope.row 拿到当前行的数据。
//#default="scope"可以拿到当前表格的数据
 <template #default="scope">  
                                  //@click="handleEdit(scope.row)"点击编辑时候可以拿到当前行scope.row 的数据       
            <el-button type="primary" size="small" @click="handleEdit(scope.row)"  >编辑</el-button >
  </template>
  1. . 把拿到的这些数据赋值到编辑用户的对话框里。用浅拷贝object.assign(目标对象,源对象)
//把当前行的数据赋值当formUser用户对象上。
 Object.assign(formUser, row);   
  1. 点击取消,再点击新增时,对话框框一个为空,所以一个异步操作浅拷贝
 proxy.$nextTick(() => {
        //拿到每一行的用户数据,可以用浅拷贝
        Object.assign(formUser, row);
      });
  1. 点击确定时,需要用编辑用户的接口
  2. 在api.js里利用二次封装的axios调用编辑用户的接口
  //编辑用户接口
      aditUser(params){
        return request({
            url:'/user/edit',
            method:'post',
            mock: false,
            data: params
        })
      },
  1. 在mock.js里通过匹配路径拦截请求,调用本地mock的user.js文件的编辑用户方法。
Mock.mock(/user\/edit/, 'post', userApi.updateUser)
  1. 在点击确定的时候的点击事件需要进行判断,判断如果当前是编辑用户的对话框,就调用编辑用户的接口
4. 删除用户的实现

思路

  1. 在api.js里利用二次封装的axios调用删除用户的接口
 //删除用户接口
        deleteUser(params){
            return request({
                url:'/user/delete',
                method:'get',
                mock: false,
                data: params
            })
          },
  1. 在mock.js里通过匹配路径拦截请求,调用本地mock的user.js文件的删除用户方法。
Mock.mock(/user\/delete/, 'get', userApi. deleteUser)
  1. 点击删除时,设置点击事件,并且获取当前行的数据
 <template #default="scope">
           <el-button type="primary" size="small" @click="handleDelete(scope.row)"  >编辑</el-button >
 </template>
  1. 点击删除时,判断你确定删除吗,如果确定删除,就调用删除用户的接口,并且需要传入当前数据的id值,删除成功就message提示删除成功。在重新调用获取用户信息接口。
     //删除用户方法
    function handleDelete(row){
        //判断是否删除吗
       ElMessageBox.confirm("确定删除吗")
       //确认删除则以下做法
        .then(async() => {
        //调用删除用户接口
        await proxy.$api. deleteUser({  id:row.id })
        //弹出删除成功信息
         ElMessage({
          showClose:true,
          Message:'删除成功',
          type:"success"
         })
         //重新获取用户数据
         getUserData(config)
        })
        .catch(() => {
          // catch error
        });
    }
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhggaibe
系列文章
更多 icon
同类精品
更多 icon
继续加载