• VUE3写后台管理(3)



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

    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')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在组件中使用时就可以直接用:Primary
    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()],
        }),
      ],
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后就和全局引入一样,在组件中使用时就可以直接用。
    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()
      ],
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在组件里面导入并使用: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.将项目的处理好的路由映射暴露在外
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    const app = createApp(App)
    app.use(router)
    
    • 1
    • 2

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

    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)
    }
    
    • 1
    • 2
    • 3
    • 4

    然后就可以在子组件里使用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
            }
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

    现在就可以跨组件使用了,在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">     
    
    • 1
    • 2

    在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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语法的解构
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    然后在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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    然后在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.头部导航栏CommonHeader

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

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

    也可以动态引入静态资源

    <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,};
    },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    组件信息传递的面包屑功能:
    首先是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,
            };
            };
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    然后在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)
            }
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最后在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,
            };
            };
    })
          
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.左侧菜单栏CommonLeft

    1.icon
    这里静态使用icon

     <el-icon><Menu /></el-icon>
    
    • 1

    动态使用icon

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

    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,
            };
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在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.将项目的处理好的路由映射暴露在外
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

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

    export default{
        getHomeData:()=>{
            return{
                code:200,
                data:{
                    tableData:[
                        {
                            name: 'oppo',
                            todayBuy: 100,
                            monthBuy: 300,
                            totalBuy: 800
                          },
                        ...
                    ],
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

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

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

    import './api/mock.js'
    
    • 1

    在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();});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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();});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    通过上面对项目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,
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

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

    import {getCurrentInstance} from "vue";
    const {proxy}=getCurrentInstance();
    const getTableList=async ()=>{
                let res=await proxy.$api.getTableData();
                tableData.value=res.tableData;
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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,
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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,
            }
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    最后前端显示

     <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
              },
              ...
            ],    
      },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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,
            });
        },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    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)
            }
        },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    然后在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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,
            });
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    2.表单重置

    表单重置时首先给表单一个ref
    然后通过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:'请输入正确的内容',
                });
               }
            });
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    4.表单校验

    首先对每个表单数据进行校验:

    最后提交的时候,也要确定验证都通过了才能提交表单到后端: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>
    
    • 1
    • 2
    • 3
    • 4

    然后就可以通过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;
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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))}
        },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后在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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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)
                    })
                };
            },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    最后在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');
            }
        },    
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    然后在登录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();
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    退出时也clearToken。

    5. 效果

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

  • 相关阅读:
    16.PyQt5中的事件系统之事件(QEvent)的传递(分发)和处理
    protolator简介
    Redux 中的函数概念
    如何实现基于场景的接口自动化测试用例?来看看大佬的方案
    一款GUI跨平台自动化测试工具分享——Squish,支持Qt框架
    Windows yarn 突然不能用了怎么解决
    WebGPU 导入[2] - 核心概念与重要机制解读
    蛋白激酶/磷酸酶 调控信号通路、细胞机制
    QFtp的使用
    DBA_IND_STATISTICS 字段含义
  • 原文地址:https://blog.csdn.net/weixin_38226321/article/details/132743089