VUE3写后台管理(3)

2023-11-07


项目写完代码已经放在了 仓库

1.环境

1.node

1.电脑已安装nvm,node不会的可以自行搜索,或者看我的vue系列的第一篇。

2.vite

2.使用vite快速构建vue项目: npm create vite@latest 或者 npm create vite@latest my-vue-app -- --template vue ;在设置里面关掉eslint
在这里插入图片描述
启动程序:npm run dev

3.Element-plus

3.1.使用Element-plus设置UI,先安装:npm install element-plus --save,然后全局引入:在mian.ts中

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

在组件中使用时就可以直接用:<el-button type="primary">Primary</el-button>
3.2使用Element-plus进行按需引入,首先需要额外再下载插件:npm install -D unplugin-vue-components unplugin-auto-import,因为使用vite创建的工程,所以工程的配置文件是vite.config.ts(如果使用的是vue cli脚手架则打包工具就是webpack),在配置文件里面配置插件:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

然后就和全局引入一样,在组件中使用时就可以直接用。
3.3Element-plus手动导入,首先需要下载插件:npm install unplugin-element-plus -S,然后在配置文件里面引入:

import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'
export default defineConfig({
  plugins: [
    vue(),
    ElementPlus()
  ],
})

在组件里面导入并使用:import {ElButton} from 'element-plus';

4.vue-router

使用vue-router进行路由配置,首先下载:npm install vue-router -S,然后在src/router/index.js里面进行工程的路由配置:

import {createRouter,createWebHashHistory} from 'vue-router'//1.引入vue-router里创造路由和映射

const routes=[//2.进行路由和视图的映射关系
   {
    path:"/",
    component:()=>import("../views/Main.vue"),
    children:[
        {
            path:'/',
            name:"home",
            component:()=>import("../views/home/Home.vue"),
        }
    ]
   }
]

const router=createRouter({//3.用vue-router的方法,将2的映射关系添加进去
    history:createWebHashHistory(),
    routes,
})

export default router;//4.将项目的处理好的路由映射暴露在外

然后在main.js里面将配置好的router挂载到根组件App上:

const app = createApp(App)
app.use(router)

最后在根组件App.vue的template和父组件的根据路由变化的子组件部分里面使用这个组件:<router-view />,就会在根据路由变化的部分做出相应的变化。

5.element icon

使用element做ui肯定是会用到icon的,首先是下载:npm install @element-plus/icons-vue,然后在main.ts里全局注册到App上:

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

然后就可以在子组件里使用svg方式使用。

6.less

样式使用到了less,所以首先下载npm install -D less,然后使用时:lang='less'

7.vuex

跨组件信息传递用到了vuex,首先是下载:npm install vuex -S,然后在src/store/index.js里面使用vuex的createstore方法设置组件之间传递的变量和方法:

import {createStore} from 'vuex'
export default createStore({
    state:{//值
        isCollapse:true
    },
    mutations:{
        updataIsCollapse(state,payload){//方法
            state.isCollapse = !state.isCollapse
        }
    }
})

然后再将store这个组挂载到app上,在src的main.ts里面:

import store from './store/index.js'
app.use(store)

现在就可以跨组件使用了,在header组件里面用这个变量isCollapse:

<el-aside width="$store.state.isCollapse ? '64px' :'180px'">
<el-menu class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" :collapse="$store.state.isCollapse" :collapse-transition="false">     

在left组件里面用这个方法updataIsCollapse:

<el-button size="small" plain @click="handleCollapse">
<script>
import {defineComponent} from "vue-demi";
import {useStore} from "vuex";
export default defineComponent({
    setup(){
        let store=useStore();
        let handleCollapse=()=>{
            store.commit("updataIsCollapse");
        };
        return {
            handleCollapse,
        };
},
})
</script>

8.vue-demi

vue-demi可以让你不用担心vue2还是vue3,安装:npm install vue-demi -S,然后在script里面数据和方法中使用。

9.mockjs

mockjs模拟后端,生成随机数据,拦截 Ajax 请求,首先下载:npm install mockjs -S
1.本地mock拦截请求:
首先在src/api/mockData/xxx.js里面写前端方法请求的后端返回(包括访问此路由时调用的视图函数,后端返回的code和data);
然后在src/api/mock.js里面将上面的数据通过mock拦截请求的路由地址和视图函数,拦截到这些数据:Mock.mock('/home/getData',homeApi.getHomeData)
最后在main.ts里引入配置好的mock.js
2.线上fastmock拦截请求:
线上写好要返回的数据,然后复制访问链接,前端发送请求时访问这个链接拿到数据。

10.axios

异步调用请求,首先下载npm install axios -S
1.基础使用
使用axios进行异步访问,最后挂载到onmounted或者别的周期函数。
2.二次封装
首先在src/config/index.js里面写项目的环境配置文件,项目的三种环境(开发环境,测试环境,线上环境)

const env=import.meta.env.MODE || 'prod'
const EnvConfig={
    development:{
        baseApi:"/api",
        mockApi:"https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api",
    },
    test:{
        baseApi:"//test.future.com/api",
        mockApi:"https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api",
    },
    pro:{
        baseApi:"//future.com/api",
        mockApi:"https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api",
    },
}
export default{
    env,
    mock:true,//mock的总开关
    ...EnvConfig[env]//ES6语法的解构
}

然后在src/api/request.js里面对接口请求进行二次封装

import axios from 'axios'
import config from '../config'
import {ElMessage} from 'element-plus'
const NETWORK_ERROR='网络请求错误,请稍后重试...'
//1.创建一个axios实例对象
const service=axios.create({baseURL:config.baseApi})
//2.请求之前要做的一些事儿,比如自定义header,jwt-token认证
service.interceptors.request.use((req)=>{
    return req;
})
//3.请求之后要做的事儿,和后端协商状态码
service.interceptors.response.use((res)=>{
    const {code,data,msg} =res.data
    if (code == 200){
        return data;
    }else{
        ElMessage.error(msg || NETWORK_ERROR)
        return Promise.reject(msg || NETWORK_ERROR)
    }
});
//4.二次封装核心函数,
function request(options){
    options.method=options.method || 'get';
    //请求方法
    if (options.method.toLowerCase()=='get'){
        options.params=options.data;
    }
    //对mock的处理
    let isMock=config.mock;//全局mock
    if (typeof options.mock !=='undefined'){
        isMock=options.mock;//这个请求的mock
    }
    //对线上环境的处理
    if (config.env=='prod'){
        service.defaults.baseURL=config.baseApi
    }else{
        service.defaults.baseURL=isMock?config.mockApi:config.baseApi
    }
    return service(options)
}
export default request;

然后在src/api/api.js里面对整个项目的api进行管理,然后在main.ts里面挂载到整个项目的后在组件里面使用。

11.echarts

echarts画图,首先是下载:npm install echarts -S,然后导入使用:import * as echarts from "echarts";

2.首页

1.布局Main

首先在Main.vue父组件里面完成布局,然后编写各个组件。这里面包含了三个静态子组件CommonHeader、CommonLeft和CommonTab以及动态路由子组件router-view.

<template>
    <div class="common-layout">
      <el-container class="lay-content">
        <el-aside >
            <CommonLeft/>
        </el-aside>
        <el-container class="r-container">
          <el-header >
            <CommonHeader/>
            <CommonTab/>
          </el-header>
          <el-main>
            <router-view/>
          </el-main>
        </el-container>
      </el-container>
    </div>
  </template>
<script>
import {defineComponent} from 'vue';
import CommonHeader from '../components/CommonHeader.vue';
import CommonLeft from '../components/CommonLeft.vue';
import CommonTab from '../components/CommonTab.vue';
export default defineComponent({
    components:{
        CommonHeader,
        CommonLeft,
        CommonTab,
    }
})
</script>

2.头部导航栏CommonHeader

这里有一个静态引入静态资源:

<img class="user" src="../assets/vue.svg" alt="用户头像">

也可以动态引入静态资源

<img class="user" :src="getImgSrc('vue')" alt="用户头像">
import {defineComponent} from "vue-demi";
export default defineComponent({
    setup(){
        let getImgSrc = (user)=>{
            return new URL(`../assets/${user}.svg`,import.meta.url).href;//相对地址拼接成绝对地址****这里是反引号不是单引号****
        };
        return {getImgSrc,};
},
})

组件信息传递的面包屑功能:
首先是CommonLeft点击左侧菜单按钮时要触发vuex里面自定义的selectMenu方法,并给这个方法传递一个参数item

import {useStore} from 'vuex';
export default{
    setup(){
        const store=useStore();
        const clickMenu=(item)=>{
            router.push({
                name:item.name,
            });
            //vuex管理面包屑
            store.commit('selectMenu',item);
        };
        return {//函数外部暴露
            noChildren,
            hasChildren,
            clickMenu,
        };
        };
    }

然后在store/index.js里面定义这个方法,并绑定一个值:

import {createStore} from 'vuex'
export default createStore({
    state:{//值
        currentMenu:null,
    },
    mutations:{
        selectMenu(state,val){
            val.name=='home'?(state.currentMenu=null):(state.currentMenu=val)
        }
    }
})

最后在commonheader里面根据这个全局值计算显示属性:

el-breadcrumb separator="/" class="bread" >
            <el-breadcrumb-item :to="{ path: '/' }" >首页</el-breadcrumb-item>
            <el-breadcrumb-item :to="current.path" v-if="current" >{{ current.label }}</el-breadcrumb-item>
        </el-breadcrumb>
import {useStore} from "vuex";
import {computed} from "vue";
export default defineComponent({
    setup(){
        let store=useStore();
        //面包屑的计算属性
        const current=computed(()=>{
            return store.state.currentMenu;
        });
        return {
            current,
        };
        };
})
      

3.左侧菜单栏CommonLeft

1.icon
这里静态使用icon

 <el-icon><Menu /></el-icon>

动态使用icon

<component class="icons" :is="item.icon"></component>

2.点击menu后的路由跳转配置:
首先建立每个路由的视图函数vue文件。
然后在commonleft里面绑定点击事件,给跳转路由的name:

<el-menu-item v-for="item in noChildren()" :key="item.path" :index="item.path" @click="clickMenu(item)">
<el-menu-item v-for="(subItem,subIndex) in item.children " :key="subIndex" :index="subItem.path" @click="clickMenu(subItem)">
<script>
import {useRouter} from 'vue-router';
export default{
    setup(){
    const router =useRouter();
    const clickMenu=(item)=>{
            router.push({
                name:item.name,/给出要跳转的路由的name
            })
        };
    return {//函数外部暴露
            clickMenu,
        };
    }

在router/index.js里面对每个路由配置name、path和子组件路径component:

import {createRouter,createWebHashHistory} from 'vue-router'//1.引入vue-router里创造路由和映射

const routes=[//2.进行路由和视图的映射关系
   {
    path:"/",
    component:()=>import("../views/Main.vue"),
    redirect:'/home',
    children:[
        {
            path:'/home',
            name:"home",
            component:()=>import("../views/home/Home.vue"),
        },
        {
            path:'/mall',
            name:"mall",
            component:()=>import("../views/mall/mall.vue"),
        },
        {
            path:'/user',
            name:"user",
            component:()=>import("../views/user/user.vue"),
        },
        {
            path:'/other/page1',
            name:"page1",
            component:()=>import("../views/other/page1.vue"),
        },
        {
            path:'/other/page2',
            name:"page2",
            component:()=>import("../views/other/page2.vue"),
        },
    ],
   }
]

const router=createRouter({//3.用vue-router的方法,将2的映射关系添加进去
    history:createWebHashHistory(),
    routes,
})

export default router;//4.将项目的处理好的路由映射暴露在外

4.首页Home

1.从后端获取数据显示到前端table的三种方式

A假数据写在代码里,B假数据写在本地用mock模拟后端,C假数据写在线上fastmock模拟后端。
A:假数据写在代码里

<el-table :data="tableData">
  <el-table-column v-for="(val,key) in tableLabel" :key="key" :prop="key" :label="val">
  </el-table-column>
</el-table>
<script>
import {defineComponent} from "vue";
export default defineComponent({
    setup(){
        const tableData= [
          {
            name: 'oppo',
            todayBuy: 100,
            monthBuy: 300,
            totalBuy: 800
          },
      ...
        ];
        const tableLabel={
          name:"品牌",
          todayBuy: "今日购买",
          monthBuy: "本月购买",
          totalBuy: "总购买",

        };
        return {
        tableData,
        tableLabel,
        }
    }
});
</script>

B:假数据写在本地用mock模拟后端
先写本地返回数据src/api/mockData/home.js

export default{
    getHomeData:()=>{
        return{
            code:200,
            data:{
                tableData:[
                    {
                        name: 'oppo',
                        todayBuy: 100,
                        monthBuy: 300,
                        totalBuy: 800
                      },
                    ...
                ],
            }
        }
    }
}

然后拦截这个方法的访问路由到本地src/api/mock.js

import Mock from 'mockjs'
import homeApi from "./mockData/home"
Mock.mock('/home/getData',homeApi.getHomeData)//访问路由是:home/getData; 访问后端方法homeApi.getHomeData

最后在main.ts里引入配置好的mock.js

import './api/mock.js'

在home.vue里面使用axios对这个路径和方法进行异步请求并在onmounted时运行:

import {onMounted,ref} from "vue";
import axios from "axios";
setup(){
let tableData = ref([]);
const getTableList=async ()=>{
            await axios.get("/home/getData").then((res)=>{//运行这个方法就是访问这个路由
            if(res.data.code==200){
                     tableData.value=res.data.data.tableData;
               }
            });
        };
onMounted (()=>{getTableList();});
}

C:假数据写在线上fastmock模拟后端
在fastmcok里面写后台要返回的数据
在这里插入图片描述
然后复制预览接口的url,访问

import {onMounted,ref} from "vue";
import axios from "axios";
setup(){
let tableData = ref([]);
const getTableList=async ()=>{
            await axios.get("https://www.fastmock.site/mock/71f3f37d174c3eb17127909450c5479a/api/home/getData").then((res)=>{
            if(res.data.code==200){
                     tableData.value=res.data.data.tableData;
               }
            });
        };
onMounted (()=>{getTableList();});
}

2.axios的二次封装使用

src\api\request.js

import axios from 'axios'
import config from '../config'
import {ElMessage} from 'element-plus'
const NETWORK_ERROR='网络请求错误,请稍后重试...'
//1.创建一个axios实例对象
const service=axios.create({baseURL:config.baseApi})
//2.请求之前要做的一些事儿,比如自定义header,jwt-token认证
service.interceptors.request.use((req)=>{
    return req;
})
//3.请求之后要做的事儿,和后端协商状态码
service.interceptors.response.use((res)=>{
    const {code,data,msg} =res.data
    if (code == 200){
        return data;
    }else{
        ElMessage.error(msg || NETWORK_ERROR)
        return Promise.reject(msg || NETWORK_ERROR)
    }
});
//4.二次封装核心函数,
function request(options){
    options.method=options.method || 'get';
    //请求方法
    if (options.method.toLowerCase()=='get'){
        options.params=options.data;
    }
    //对mock的处理
    let isMock=config.mock;//全局mock
    if (typeof options.mock !=='undefined'){
        isMock=options.mock;//这个请求的mock
    }
    //对线上环境的处理
    if (config.env=='prod'){
        service.defaults.baseURL=config.baseApi
    }else{
        service.defaults.baseURL=isMock?config.mockApi:config.baseApi
    }
    return service(options)
}
export default request;

通过上面对项目axios的二次封装之后,在src/api/api.js里面对项目api统一管理(eg:当项目调用getTableData方法时就会访问/home/getData这个路由,并根据mock确定是线上还是本地去访问这个路由)。

```php
import request from './request';
export default {
    getTableData(params){
        return request({
            url:'/home/getData',
            method:'get',
            data:params,
            mock:true,
        })
    }
}

然后在main.ts里面挂载到全局

import api from './api/api.js'
app.config.globalProperties.$api = api

最后在Home.vue组件里面使用

import {getCurrentInstance} from "vue";
const {proxy}=getCurrentInstance();
const getTableList=async ()=>{
            let res=await proxy.$api.getTableData();
            tableData.value=res.tableData;
        };

3.使用封装后的axios和fastmock获取countdata数据

1.首先在fastmock里面写后端要返回的数据url方法(get)。

  1. url:/home/getCountData

在这里插入图片描述
2.在src/api/api.js里面添加这个路由对应的路由函数的配置

  1. 配置url对应的路由函数:getCountData
getCountData(params){
 return request({
 url:'/home/getCountData',
 method:'get',
 data:params,
 mock:true,
        });

3.在home子组件里访问这个路由,异步访问这个路由函数,最后将函数挂载到onmounted上,将拿到的数据返回:

3.调用这个路由函数,拿到数据

import {defineComponent,onMounted,ref,getCurrentInstance} from "vue";
import axios from "axios";
export default defineComponent({
    setup(){
        const {proxy}=getCurrentInstance();
        let countData = ref([]);
        const getCountList=async ()=>{
            let res=await proxy.$api.getCountData();
            console.log(res);
            countData.value=res;
        };
        onMounted (()=>{
            getCountList();
        });
        return {
        countData,
        }
    }
});

最后前端显示

 <el-col :span="16" style="margin-top:20px;align-self: flex-start;" >
            <div class="num">
                <el-card :body-style="{display:'flex',padding:0}" v-for="item in countData" :key="item.name">
                    <component class="icons" :is="item.icon" :style="{background:item.color}"></component>
                    <div class="details">
                        <p class="num">{{ item.value }}</p>
                        <p class="txt">{{ item.name }}</p>
                    </div>
                </el-card>
            </div>
        </el-col>

4.使用echarts画图

1.首先在fastmock里面给出url(/home/getChartData)后台数据

{
  code:200,
  data:{
    "orderData": {
          "date": ["20191001", "20191002", "20191003", "20191004", "20191005", "20191006", "20191007"],
          "data|7": [
          {
           "苹果":"@integer(1000,5000)",
           ...
          }
        ]
        },
    "videoData": [
          {
            name: '小米',
            value: 2999
          },
         ...
        ],
    "userData": [
          {
            date: '周一',
            new: 5,
            active: 200
          },
          ...
        ],    
  },
}

2.在src\api\api.js里面配置url和方法名( getChartData)

import request from './request';
export default {
...
    getChartData(params){
        return request({
            url:'/home/getChartData',
                method:'get',
                data:params,
                mock:true,
        });
    },
}

3.在Home.vue里面使用

<el-card style="height:320px">
                <div ref="orderchart" style="height:300px; width:auto;"></div>
            </el-card>
            <div class="graph">
                <el-card style="height:260px;">
                <div ref="userchart" style="height:240px; width:auto;"></div>
                </el-card>
                <el-card style="height:260px;">
                <div ref="videochart" style="height:240px;width:auto;"></div>
                </el-card>
            </div>
<script>
import {defineComponent,onMounted,ref,getCurrentInstance,reactive,} from "vue";
import axios from "axios";
import * as echarts from "echarts";
export default defineComponent({
    setup(){
        const {proxy}=getCurrentInstance();
        onMounted (()=>{
            getChartList();
        });
        //echats的渲染
        //x轴的配置
        let xOptions=reactive({
            legend: {
            // 图例文字颜色
            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: [],
  },)
        //饼状图的配置
        let pieOptions=reactive({
            tooltip: {
            trigger: "item",
          },
          color: [
            "#0f78f4",
            "#dd536b",
            "#9462e5",
            "#a6a6a6",
            "#e1bb22",
            "#39c362",
            "#3ed1cf",
          ],
          series: [],
        })
       //三个图数据的初始化
       let orderData=reactive({xData:[],series:[]});
       let userData=reactive({xData:[],series:[]});
       let videoData=reactive({series:[]});
       //通过faskmock获取数据
       const getChartList=async()=>{
            let res=await proxy.$api.getChartData();
            console.log(res);
            let orderRes=res.orderData;
            let userRes=res.userData;
            let videoRes=res.videoData;
            //折线图
            orderData.xData=orderRes.date;//折线图的横轴时间数据
            const keyArray = Object.keys(orderRes.data[0]);//提取每个品牌的销售数据,为每个品牌创建一个系列
            const series = keyArray.map(key => ({
                name: key,
                data: orderRes.data.map(item => item[key]),
                type: "line"
            }));

            // // 3. 将提取的数据放入xOptions和orderData中,然后传递给echarts
            orderData.series = series;
            xOptions.xAxis.data = orderData.xData; 
            xOptions.series = orderData.series; 
            console.log(xOptions);
            // // 折线图的渲染
            let oEcharts = echarts.init(proxy.$refs['orderchart']);
            oEcharts.setOption(xOptions);
            // //柱状图
            userData.xData=userRes.map((item) => item.date);
            userData.series = [
                {
                    name:"新增用户",
                    data:userRes.map((item) =>item.new),
                    type:"bar",
            },
            {
                    name:"活跃用户",
                    data:userRes.map((item) =>item.active),
                    type:"bar",
            },
            ];
            xOptions.xAxis.data = userData.xData;
            xOptions.series = userData.series;
            let uEcharts = echarts.init(proxy.$refs['userchart']);
            uEcharts.setOption(xOptions);
            // //饼状图
            videoData.series=[
                {data:videoRes,type:"pie"}
            ];
            pieOptions.series = videoData.series;
            let vEcharts = echarts.init(proxy.$refs['videochart']);
            vEcharts.setOption(pieOptions);
       };
        return {
        tableData,
        tableLabel,
        countData,
        }
    }
});
</script>

5.头部CommonTab

使用vuex绑定全局参数tagsList并在点击菜单栏的事件里面给这个参数添加val

import {createStore} from 'vuex'
export default createStore({
    state:{//值
        tabsList:[
            {
                path:"/",
                name:"home",
                label:"首页",
                icon:"home",
            }
        ],
    },
    mutations:{
        selectMenu(state,val){//这个事件已经绑定到了左侧菜单栏
            // val.name=='home'?(state.currentMenu=null):(state.currentMenu=val)
            if(val.name=='home'){
                state.currentMenu=null
            }else{
                state.currentMenu=val
                let result=state.tabsList.findIndex(item=>item.name==val.name)
                result==-1?state.tabsList.push(val):""
            }
        },
        closeTab(state,val){//这个事件会绑定在tag的事件里
            let res=state.tabsList.findIndex(item=>item.name==val.name)
            state.tabsList.splice(res,1)
        }
    },
})

然后在CommonTab里面拿到这个全局值进行循环并判断显示

<div class="tags">
        <el-tag v-for="(tag,index) in tags" :key="tag.name" :closable="tag.name!=='home'" :disable-transitions="false" 
        :effect="$route.name===tag.name ?'dark':'plain'" @click="changeMenu(tag)" @close="handleClose(tag,index)">
        {{ tag.label }}
        </el-tag>
    </div>
<script>
import {useStore} from "vuex";
import {useRouter,useRoute} from "vue-router";
export default{
    setup(){
        const router = useRouter();
        const route = useRoute();
        const store=useStore();
        const tags=store.state.tabsList;
        const changeMenu = (item)=>{
            router.push({name:item.name});
        };
        const handleClose=(tag,index)=>{
            let length=tags.length-1;
            store.commit("closeTab",tag);
            if (tag.name!==route.name){
                return;
            }
            if(index==length){
                router.push({name:tags[index-1].name});
            }else{
                router.push({name:tags[index].name});
            }
        };
        return {tags,changeMenu,handleClose};
    },
}
</script>

3.用户页

1.使用本地mock和Table 表格

首先在src\api\mockData\user.js里面写user页面的后端模拟数据;
然后在src\api\api.js里面写前后端访问的控制,当调用此方法函数时让它访问此路由,并设置是从线上还是本地获取数据。

 getUserData(params){
        return request({
            url:'/user/getUser',
                method:'get',
                data:params,
                mock:false,
        });
    },

然后在src\api\mock.js里面使用mock拦截前端请求,让它从本地user.js里面获取数据:Mock.mock(/user\/getUser/,'get',userApi.getUserList)
最后在user.vue里面拿到数据并通过el-table显示。

2.表单重置

表单重置时首先给表单一个ref<el-form :inline="true" :model="formUser" ref="userForm">
然后通过ref拿到表单,调用表单的resetFields方法:proxy.$refs.userForm.resetFields();

3.日期

提交有日期的表单时,先要对表单的日期数据进行处理:

const timeFormat=(time)=>{//对日期格式处理
            var time=new Date(time);
            var year=time.getFullYear();
            var month=time.getMonth()+1;
            var date=time.getDate();
            function add(m){
                return m<10?"0"+m:m;
            };
            console.log(year+"-"+add(month)+"-"+add(date));
            return year+"-"+add(month)+"-"+add(date);
        };
 const onSubmit=()=>{
            proxy.$refs.userForm.validate(async(valid)=>{
                if(valid){//如果表单验证成功,再提交到后端
                    formUser.birth=timeFormat(formUser.birth);
                    let res= await proxy.$api.addUser(formUser);
                    console.log(res);
                    if(res){
                        dialogVisible.value=false;
                        proxy.$refs.userForm.resetFields();//提交之后根据反向ref拿到表单,利用表单的resetFields方法,重置表单
                        getUserData(config);
                            }         
           }else{
            ElMessage({
                showClose:true,
                message:'请输入正确的内容',
            });
           }
        });
        };

4.表单校验

首先对每个表单数据进行校验:<el-form-item label="年龄" prop="age" :rules="[{required:true,message:'请输入年龄'},{type:'number', message: '年龄必须是数字'}]">

最后提交的时候,也要确定验证都通过了才能提交表单到后端:proxy.$refs.userForm.validate(async(valid)=>{if(valid){}})

5.表单编辑

首先需要拿到显示的数据到放到表单再去编辑,所以用到了scope 变量,它是一个插槽(slot)的作用域参数,用于在 Vue.js 中传递当前组件实例的数据给插槽的内容。

<template #default="scope">
  <el-button  size="small" @click="handleEdit(scope.row)">编辑</el-button>
  <el-button type="danger" size="small">删除</el-button>
 </template>

然后就可以通过row拿到这些表单值,然后通过浅拷贝,将数据显示到表单:

const handleEdit=(row)=>{
            console.log(row);
            dialogVisible.value=true;
            row.sex==0?(row.sex="男"):(row.sex="女");
            proxy.$nextTick(()=>{//异步操作函数, DOM 更新完成后执行
                Object.assign(formUser,row);//将row数据浅拷贝到表单formUser
            });
            action.value='edit';
            dialogVisible.value=true;
        };

4.other

1.权限管理

登录后会根据根据后台数据显示不同用户的左侧菜单,跨组件的信息传递用到了vuiex,所以在store/index.js里面定义全局变量和方法

import {createStore} from 'vuex'
export default createStore({
    state:{//值
        menu:[],
    },
    mutations:{
    setMenu(state,val){
        state.menu=val
        localStorage.setItem('menu',JSON.stringify(val))}
    },
})

然后在login里面将后台拿到的此用户的menu信息通过全局方法传递给全局值:store.commit("setMenu",res.menu);
最后在commonleft里面用这个变量显示不同的menu:const asyncList=store.state.menu;

2.数据持久化问题

因为menu给的是[],所以当用户刷新网页时,menu数据就会丢失,所以处理这个问题,就要在store里面拿到存到浏览器里面的localStorage.menu里面的值赋值给state.menu,并在main.ts里面使用这个方法刷新menu值。

addMenu(state){
            if (!localStorage.getItem('menu')){
                return
            }
            const menu=JSON.parse(localStorage.getItem('menu'))
            state.menu=menu
        }
//main.ts
store.commit("addMenu");

3.动态菜单路由的跳转

首先登录以后,通过addmenu方式,传递给全局的router:store.commit("addMenu",router);
然后在store里面给addmenu方法添加动态路由的列表获取:

addMenu(state,router){
            if (localStorage.getItem('menu')){
                // 如果menu里面有值就拿到她更新state里的menu
                const menu=JSON.parse(localStorage.getItem('menu'))
                state.menu=menu
                //动态路由添加到router
                const menuArray=[];
                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)
                    }else{
                        let url=`../views/${item.url}.vue`
                        item.component=()=>import(url)
                        menuArray.push(item)
                    }
                });
                menuArray.forEach(item=>{
                    router.addRoute('home1',item)
                })
            };
        },

最后在main.ts里面用addmenu方法时也要传递参数router:store.commit("addMenu",router);

4.路由守护

需要用到vuex存取全局token值,并用到js-cookie的Cookie,所以先下载:npm install js-cookie -S
然后在store/index里面定义值和方法:

import {createStore} from 'vuex'
import Cookie from 'js-cookie'
export default createStore({
    state:{//值
        token:'',
    },
    mutations:{//方法
    //1.设置token
        setToken(state,val){
            state.token=val;
            Cookie.set('token',val);
        },
        //2.获取token
        getToken(state){
            state.token=state.token||Cookie.get('token');
        },
        //3.清除token
        clearToken(state){
            state.token=''
            Cookie.remove('token');
        }
    },    
})

然后在登录login时,后端给一个token,通过settoken方法设置到全局token上:store.commit("setToken",res.token);
然后在main.ts里面每次去gettoken进行判断,并判断这个路由在路由列表吗,如果不在就跳转到home页面

function checkRouter(path) {
  let hasCheck = router.getRoutes().filter(route => route.path == path).length;
  return hasCheck;
}

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();
  }
});

退出时也clearToken。

5. 效果

1.首页
在这里插入图片描述
2.用户
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

VUE3写后台管理(3) 的相关文章

  • Arduino数字滤波设计方法(Python测试滤波示例)和异常值过滤算法

    数字滤波设计 数字信号处理的目的是在数字系统上执行 而不是在模拟电路上进行操作 通过这种方式 它基于软件执行相同的任务 而不需要电子材料或更换 这种方法确保设计简单且可升级 比如用RC元件制作的模拟滤波器 就需要改变材料来改变滤波器的截止频

随机推荐

  • Docker使用阿里云镜像加速器

    登录阿里云控制台 搜索容器镜像服务 界面上没有可以在搜索框中搜索 进入容器镜像服务管理控制台 如果需要开通就按照提示开通一下 如下图所示 在容器镜像服务控制台最底下有个镜像加速器 点进去之后有一个加速器地址 使用这个地址作为docker镜像
  • FreeRTOS ------- 任务(task)

    在学习RTOS的时候 个人觉得带着问题去学习 会了解到更多 1 什么是任务 在FreeRTOS中 每个执行线程都被称为 任务 每个任务都是在自己权限范围内的一个小程序 其具有程序入口每个任务都是在自己权限范围内的一个小程序 其具有程序入口通
  • 中断模式中中断和事件的区别_C中中断和继续之间的区别

    中断模式中中断和事件的区别 In this tutorial you will learn about difference between break and continue in C 在本教程中 您将了解C语言中break和conti
  • 前端websocket连接开发时devserver代理配置及nginx代理配置

    页面代码 socketUrl ws location host websocket ws uid 1 location host即前端服务部署地址 var socket new WebSocket socketUrl 打开事件 socket
  • HTML5+CSS3定位+导航二维码显示隐藏切换案例

  • Qt 样式表介绍

    概述 Qt Widget界面美化除了自己绘制外 有一个更简单高效的方式 就是Qt样式表 Qt样式表是一种强大的机制 通过QApplication setStyleSheet 设置整个应用程序的样式 通过QWidget setStyleShe
  • 分享几个图床网址,便于大家分享图片

    1 ImgURL 图床ImgURL ImgURL免费图床https https www imgurl org 又是一个比较老牌的免费图床服务 稳定性不错 无需注册 游客限制每日上传10张 单张图片不能超过 5M 支持 HTTPS 诞生于20
  • 数组的方法-sort()

    数组的sort 方法主要用于对数组的元素进行排序 其中 sort 方法有一个可选参数 但是 此参数必须是函数 数组在调用sort 方法时 如果没有传参将按字母顺序 字符编码顺序 对数组中的元素进行排序 如果想按照其他标准进行排序 就需要进行
  • Vue路由+NodeJs环境搭建

    Vue路由 NodeJs环境搭建 一 Vue路由 第一步 引入js文件 第二步 创建两个组件展示相关的功能 第三步 形成组件与请求路径的对应关系 第四步 将路由关系和路由对象绑定 第五步 将路由关系和vue实例绑定 第六步 发起请求 第七步
  • 1、docker 镜像操作

    1 创建 拉取镜像 docker hub https hub docker com 搜索对应的镜像 搜索 nvidia cuda 在tags里最终选择 10 2 cudnn7 devel ubuntu18 04版本 最终完整的镜像名 nvi
  • 域名,端口,IP总结

    前言 真正的房子只有几个门 但是一个IP地址的端口可以有65536 即 2 16 个之多 端口是通过端口号来标记的 端口号只有整数 范围是从0 到65535 2 16 1 我们知道 一台拥有IP地址的主机可以提供许多服务 比如Web服务 F
  • 智能交通技术(ITSTech)公号推广简介

    2023年9月14日更新 一 智能交通技术 微信公众号 简介 关注智能交通 车路协同 交通规划 交通改善 智慧城市等领域新理论和技术发展 交流大数据 云边端计算 人工智能 区块链等技术的应用经验 推广智能交通技术在交通规划 交通运输监管 交
  • hj212协议如何和php通讯,HJ212数据传输标准报文解析

    HJ212分为2005年 HJ T212 2005 和2017年 HJ212 2017 的版本 略有不同 网上没找到非常官方的渠道下载 在这贴一份2017年版本的下载地址 TCP IP通讯包组成 名称 类型 长度 描述 包头 字符 2 固定
  • 有符号数和无符号数

    1 数值 针对二进制来讲 有 有符号数 signed 和无符号数 unsigned 因为计算机无法区分一个二进制数是有符号数还是无符号数 因此我们在定义时要明确该数值是有符号数还是无符号数 无符号类型需要通过unsigned关键字指定 否则
  • 下载bison和flex的好地方

    http sourceforge net projects gnuwin32 这里的bison和flex我觉得是最好用的 捣乱的
  • vue el-form-item给多个表单项绑定rules校验规则

    vue el form item给多个表单项绑定rules规则 如图所示 我们想要在element的表单里 给多个input添加校验规则 只需要在内部添加el form item项 并在el form item项里绑定prop和rules规
  • 基于Prompt的MLM文本分类

    简介 常规NLP做文本分类时常用Transfer Learning的方式 在预训练bert上加一个分类层 哪个输出节点概率最大则划分到哪一类别 而基于Prompt的MLM文本分类是将文本分类任务转化为MLM Masked Language
  • 自动驾驶前沿综述:基于深度强化学习的自动驾驶算法

    作者 TIM 编辑 汽车人 原文链接 https zhuanlan zhihu com p 477372894 点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心技术交流群 后台回复 数据集下载 获
  • python弹窗程序教程(附源码解析)

    python弹窗程序教程 附源码解析 声明 此程序仅供娱乐整蛊 使用者后果自负 本人概不负责 代码可能有雷同 如有侵权 请联系小编 引言 弹窗是程序的一种显示内容的形式 例如警告 提示等 而弹窗还有一种特殊的呈现形式 恶意程序 有人说不就是
  • VUE3写后台管理(3)

    VUE3写后台管理 3 1 环境 1 node 2 vite 3 Element plus 4 vue router 5 element icon 6 less 7 vuex 8 vue demi 9 mockjs 10 axios 11