vue项目架构

技术栈:vue3、vue-router 、vuex(和pinia)、element plus 、axios、ts、sass

1、安装vue3 脚手架+ ts

vue create admin

2、分析目录结构

node_modules =>依赖包

public => 静态文件

src => 核心代码

assets => 静态方法

components => 公共组件

app.vue => 根组件

mian.ts =>入口文件

shims-vue.d=>对vue 组件进行支持

二、安装Element Plus组件库

1、安装

npm install element-plus --save

2、按需引入:添加 vue.config.ts => 报错了,没有效果

解决方法:自己写一个插件,按需导入要使用的组件 或 手动导入(这里选择手动导入)

步骤1:对象的形式(在scr新建一个文件夹useHooks.ts,再创建一个elementPlus.ts)

import { ElButton } from "element-plus";
import { ElInput } from "element-plus";

let arrs = [{ com: ElButton }, { com: ElInput }];

export let installPlus = {
  install(app:any) {
    //app 当前的vue 实例
    arrs.forEach((item) => {
      app.use(item.com);
    });
  },
};

//总结我们自己写 插件按需引入 ui组件库

 //实现的步骤
 //1 自己写插件 =>我会在 src文件夹下创建一个项目文件夹  useHooks,出来 element ui 插件

步骤2:在入口文件main.ts中导入,并引入Element Plus的css样式

import { createApp } from 'vue'
import App from './App.vue'

import {installPlus} from "./useHooks/ElementPlus" // 引入手动注册的ElementPlus
import 'element-plus/dist/index.css'    // 引入element plus css

// 全局注册installPlus

let app=createApp(App)  // 创建app实例对象

app.use(installPlus)
app.mount('#app')   // 挂载到对应的位置

三:在项目中安装sass

npm install sass 
npm install sass-loader

sass-loader 是将scss文件转为css文件,因为浏览器只解析html、css、js

在style添加scss文件

四:处理全局样式

1、高度的继承

2、盒子模型

3、项目的公共样式

4、处理icon,阿里巴巴矢量图标库

1)在 src 目录下创建一个style文件夹 => 处理项目的样式 => 进行模块化(把其他的scss文件引入到index.scss文件中)

2) 在main.ts进行引入

import './style/index.scss'

五:封装axios请求

1、安装axios

npm install axios

2、在scr目录下创建一个文件夹https/index.js

3、封装axios请求,并添加请求拦截和响应拦截

4、添加nprogress,显示进度条(显示请求与响应)

import axios from "axios";

// 创建axios实例
let service=axios.create({
    baseURL:'https://www.fastmock.site/mock/c005af6795f70b326c9eb6acd3bcb042/admin',
    timeout:6000
})

// 创建请求拦截器
service.interceptors.request.use(
    (config:any)=>{ //config 是发送请求的配置对象,是给后端服务器的
        // 注意:发送请求给后端,想要让后端知道我这个请求有什么东西,都可以在这里进行配置。比如 1 让后端知道我这个接口是内部网页的发送请求,2 下载文件 或者是 上传图片等接口

        return config
    },
    (err:any)=>{// 请求失败的回调
        return Promise.reject(err); //  axios底层代码通过 promise 和ajax

    }
)

// 创建响应拦截
service.interceptors.response.use(
    (response:any)=>{   //  响应成功的对象 =》  规定一些状态吗
        return response.data
    },
    (err:any){  // 响应失败的回调
        return Promise.reject(err)
    }
)

export default service;

5、在scr目录下创建一个文件夹api,文件夹下创建login.ts实现页面请求一一对应

// 登录页面的接口
import service from '../index'


export function Login(data={}){
    
    return service.post('/login',data);
}

六、路由的处理

1、项目安装路由  

npm install vue-router

2、怎么在项目中使用路由?

步骤一:在src目录下创建一个文件夹router,创建index.ts

a:引入路由hooks:import {createRouter,createWebHashHistory} from 'vue-router'

b:创建路由实例:

// 路由主文件

// 1 引入路由 路由hooks
import {createRouter,createWebHashHistory} from 'vue-router'
    /* 
        createRouter:创建路由实例
        createWebHashHistory:选择路由模式
        createWebHistory:历史模式
    */

// 创建路由表
// 1 引入组件
import Login from '@/views/Login/index.vue'

let routes:any=[
    {
        path:'/login',
        component:Login
    },
    {
        path:'/',
        redirect:'login',
        component:Login
    }
    {
        path:'/home',
        name:'首页',
        component:()=>import('@/view/Home/index.vue')

    }
]

//  创建路由实例 =VueRouter.createRouter  => 返回值就是:路由实例对象
let router=createRouter({
    history:createWebHashHistory(),
    routes:routes,
})

// 让这个路由实例对象和 vue项目进行关联 ,在入口文件main.ts中进行注册
export default router;

步骤二:将上面的路由实力对象注册到入口文件main.ts中

作用:app.use(router)

    挂载了全局组件 <RouterLink></RouterLink>~<RouterView></RouterView>

项目中的所有组件都可以访问到$router和$route

$router => 总路由实例对象,可以使用push,replace,go,back,forward...

$route => 当前路由对象,可以query,params,matched,path,fullpath...

在组合式API:const router = useRouter()

import { createApp } from 'vue'
import App from './App.vue'
// 引入路由实例
import router from './Route/index'

console.log(router,"router")
app.use(router);    //为什么需要将路由对象实例,使用路由hooks在每一个路由分布中
    //     如果不注册
      //  1 不能使用  router 提高 两个全局组件 ,
     //  2 不能在项目中,使用 vue -router 和vuex  
app.mount('#app')   // 挂载到对应的位置

步骤三:在App组件中显示路由组件<RouterView></RouterView> 

<template>
  <div class="App">
    <RouterView></RouterView>
  </div>
</template>

 

七、项目的业务组件

  1、暗黑模式

    在src/views/login目录下创建一个文件StyleChange

<template>
    <div class="styleChange">
        <!-- 引入element -->
        <el-switch v-model="value" @change="changeS" 
        inline-prompt 
        :active-icon="Sunny" :inactive-icon="Moon" />
    </div>
</template>
   
<script setup lang='ts'>
import { ref } from 'vue'
import { Sunny, Moon } from '@element-plus/icons-vue'
import { useDark, useToggle } from '@vueuse/core';// 切换动态使用

// const isDark = useDark()
// const toggleDark = useToggle(isDark)

let value = ref(false)


// 切换
const changeS = () => {
    if (value.value) {
        const isDark = useDark({   // 定义主题颜色
            valueDark: 'dark',  // 黑暗

        });
        const toggleDark = useToggle(isDark); //定义主题颜色 =》执行这个主题样式
        toggleDark();
    }
    else{
        const isDark=useDark({
            // 定义主题颜色
           valueLight:"light",
        })
        // 定义主体颜色,执行这个主题样式
        const toggleDark=useToggle(isDark);
        toggleDark();
    }
}
</script>

<style lang='scss' scoped>

</style>

2、登录组件

<template>
    <div class="loginBox">
        <div class="logintop  dis-flex2">
        <!--  实现样式的切换 -->
        <div>国际化</div>
        <StyleChange></StyleChange>
        </div>
        <Contents></Contents>
    </div>
</template>
   
<script setup lang='ts'>
import StyleChange from './StyleChange.vue'
import Contents from './Contets.vue';

</script>

<style scoped lang="scss">
.loginBox{
    height: 100px;
}
</style>

3、自己封装一个组件(验证码输入框+验证图片)

<template>
    <!-- 自定义组件 -->
    <div class="VerInput dis-flex3">
        <div class="dis-flex3"
        :class="show?'leftBoxA':'leftBox'">
            <i class="iconfont icon-yanzheng"></i>

            <input v-model="verCon" type="text" @blur="blur" placeholder="验证码" />

            <i v-if="show" @click="deleteC" class="iconfont icon-shanchu2"></i>
        </div>
        <!-- 验证码数据 -->
        <img :src="verImg" @click="getImage" />

        <div v-if="show" class="showRule">验证码错误</div>
    </div>
</template>
   
<script setup lang='ts'>
import { ref } from 'vue';
import {LoginVerData} from '@/https/api/login'

let verCon = ref('')
let verImg=ref('')
let verContent=ref('')
let show=ref(false);

// 子组件给父组件数据 => 是否验证通过
const emits=defineEmits(['getVerify']);

// 脱标事件
const blur = () => {
    if(verContent.value==verCon.value){
        show.value=false;
        emits('getVerify',true)
    }
    else{
        show.value=true;
        emits('getVerify',false)
    }
}

// 清空验证码中的内容
const deleteC = () => {
    verCon.value='';
}

// 获取到请求
LoginVerData().then((res:any)=>{
    console.log(res)
    verImg.value=res.img;
    verContent.value = res.content
})

// 点击更换验证码图片
const getImage=()=>{
    // 发送请求获取图片
    LoginVerData().then((res:any)=>{
        verImg.value=res.img;
    verContent.value = res.content 
    })
}

</script>

<style lang='scss' scoped>
.VerInput {
    position: relative;
    margin-bottom: 20px;
    .leftBox {
        width: 50%;
        height:32px;
        padding-left: 12px;
        border: 1px solid #ccc;
        border-radius: 4px 0 0 4px;
        
        i {
            font-size: 14px;
        }
    }

    .leftBoxA {
        width: 50%;
        height:32px;
        padding-left: 12px;
        border: 1px solid red;
        border-radius: 4px 0 0 4px;

        i {
            font-size: 14px;
        }
    }

    img{
        height:32px;
        width: 50%;
    }
    .showRule{
        position: absolute;
        width: 100%;
        font-size: 12px;
        color: red;
        top:32px;
        left:8px;
    }
}

input {
    width: 80%;
    outline: none;
    border: none;
    font-size: 12px;
}
</style>

 

八、动态生成侧边导航栏

1、页面布局:

处理侧边栏导航 =》动态生成=》根据后端给的数据动态的生成

本质:二次封装 菜单栏组件,使用递归的方式

1)先创建一个文件夹Layout/NavList/index.vue => 引入菜单组件和处理后端给的数据

<template>
    <div class="navList">
        
        <!-- 侧边栏导航的内容 -->
        <div class="navListContent">
<div class="navListTop">内部管理系统</div>
            <el-menu background-color="pink" default-active="2" class="el-menu-vertical-demo" :collapse="isCollapse">

                <!-- 侧边栏展示的数据  模块化抽离 -->
                <LeftNav :navList="navList"></LeftNav>
            </el-menu>

            <!-- 改变菜单栏的展开于收缩 -->
<div class="navBottom" :style="{ width: showshous ? '240' + 'px' : '64' + 'px' }">
            <i @click="changeS" v-if="showshous" class="iconfont icon-shousuocaidan"></i>
            <i @click="changeS" v-else class="iconfont icon-shousuocaidan-copy"></i>
        </div>
        </div>
        
    </div>
</template>
   
<script setup lang='ts'>
import LeftNav from './LeftNav.vue';
import { navLisData } from "@/https/api/Layout";
import { ref } from 'vue'

// 1 动态的数据(后端) => 嵌套  不嵌套
//根据不同的用户名称发送请求
let navList = ref([])
const getNavList = () => {
    navLisData({ token: sessionStorage.getItem('token') }).then((res: any) => {
        navList.value = res.navList;
    })
}
getNavList();

// 改变菜单栏的展开与收缩
const isCollapse = ref(false)
let showshous = ref(true)
const changeS = () => {
    showshous.value = !showshous.value;
    if (showshous.value) {
        isCollapse.value = false;
    }
    else {
        isCollapse.value = true;
    }
}

</script>

<style lang='scss' scoped>
.navList {
    height: 100%;
    background-color: #545c64;

    .navListContent {
        height: 100%;

        .navListTop {
            text-align: center;
            font-size: 24px;
            margin: 10px 0;
        }

        .navBottom {
            height: 40px;
            line-height: 40px;
            position: fixed;
            bottom: 0;
            left: 0px;
            background-color: #545c64;
            box-shadow: 0 0 6px -2px var(--el-color-primary);
            transition: width 0.3s ease-in-out;

            i {
                font-size: 22px;
                color: greenyellow
            }
        }
    }
}

.el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 240px;
    min-height: 400px;
}
</style>

2)再创建一个文件夹Layout/NavList/LeftNav.vue => 处理模块抽离的文件,

用递归的方式动态生成菜单栏

<template>
    <!-- [{ },{children:}] -->
    <div v-for="(item,index) in navList" :key="index">
        <el-sub-menu :index="item.path" v-if="item.children">
            <template #title>
                <el-icon>
                    <House />
                </el-icon>
                <span>{{ item.name }}</span>
            </template>

            <LeftNav :navList="item.children"></LeftNav>
        </el-sub-menu>
        <!-- 不嵌套 -->
        <el-menu-item :index="item.path" v-else>
            <el-icon><icon-menu /></el-icon>
            <template #title>{{ item.name }}</template>
        </el-menu-item>
    </div>
</template>
   
<script setup lang='ts'>
import {
    Document,
    Menu as IconMenu,
    Location,
    Setting,
House,
} from '@element-plus/icons-vue'


let props=defineProps({
    navList:{
        type:Array,
    }
})

</script>
<style lang='scss' scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
}

</style>

九、创建路由守卫文件、并添加nprogress进度条

1、下载NProgress

npm i nprogress

如果是ts,则需要另外下载

npm i @types/nprogress -D

引入nprogress的css样式

npm i @types/nprogress -D

打开进度条: NProgress.start()

关闭进度条:NProgress.done()

改变nprogress样式:#nprogress .bar{ background: skyblue !important; }

2、创建路由前置守卫

创建完成需要早入口文件main中引入:引入全局前置守卫 import './Route/BeforeRouter'

// 创建路由守卫文件

// 1 基本的权限处理
import router from './index'
import NProgress from 'nprogress' 
import 'nprogress/nprogress.css'

// 创建路由前置守卫
router.beforeEach((to,from,next)=>{
    /* 
        to:去到哪个页面
        from:来自哪个页面
        next:继续向下执行,执行根据路径匹配组件
    */
        NProgress.start();
    //区分内容内部页面与外部页面,做基本的权限处理
    if(to.fullPath!='/login'){
        if(sessionStorage.getItem('token')){// 如果有登录过
            next()
        }
        else{ //没有登录
            next('login')
        }
    }
    else{
        next()
    }
})

// 全局后置守卫
router.afterEach(()=>{
    // from,to
    NProgress.done();
})

 

 十、封装业务组件

1、处理面包屑组件的封装

用来展示当前用户点击侧边导航的谁,或者在展示当前的浏览器上的路径对应的组件

实现 =》

1) 处理基本的样式

2)功能的分析

(1):用户进入到后台管理系统默认第一个显示 首页

(2)发现这个面包屑组件 数据结构 =》 数组结构

(3)当浏览器上的地址不在是首页的情况的时候

1)在面包屑组件 数据首页后面添加 地址对应的 组件名称=》嵌套 不嵌套的处理逻辑

3)点击面包屑的首页=》路由的跳转

4)刷新浏览器的时候=》我这个面包屑应展示的是最后 面包屑的数据结构

步骤:先创建一个文件夹components/WBreadcrumb/index.vue 创建面包屑组件,并在Layout/TopCom中引入该组件

<template>
<!--
二次封装 element ui ,比如 侧边栏导航,面包屑组件,下拉选择框等等
实现:
1:在 项目中我会单独创建一个文件.vue文件,element ui组件的面包屑,放在在components 下
方便相同的技术栈进行复用
-->

<div class="breadBox">
<!--
分析 => 面包屑 => 数组结构
1 用户进入到后台管理系统默认首页
2 当用户点击侧边导航 => 实现路由跳转
1)没有嵌套的:在面包屑上直接显示,如果不是首页,在首页后面添加
2)有嵌套的:先显示父组件,再显示自己
:to="{path:'/'}"
-->

<el-breadcrumb separator="/">

<!-- 变成空标签tempate,导入数据 -->
<template v-for="(item,index) in beradList" :key="index">
<el-breadcrumb-item
v-if="item.path" :to="{path:'/layout/index'}"
>{{ item.name }}</el-breadcrumb-item>
<el-breadcrumb-item v-else>{{ item.name }}</el-breadcrumb-item>
</template>
</el-breadcrumb>
</div>

</template>

<script setup lang='ts'>
import {ref,watch} from 'vue';
import {useRoute} from 'vue-router';

// 1 动态数据
// 2 定义数据结构
let route=useRoute();
// console.log(route,1234)

// 解决面包屑因为刷新而导致的数据丢失的问题
const getBreadListData=()=>{
let datas=sessionStorage.getItem("beradList");
if(datas){
let beradLists=ref(JSON.parse(datas));
return beradLists;
}
else{
let breadLists=ref([{name:"首页",path:"/layout/index"}]);
return breadLists;
}
}

let beradList=getBreadListData();

// 分析: 没有嵌套的 => 在面包屑上直接显示 => 如果不是首页,就在首页后面添加
// 1:知道当前的路由信息,通过路由插件在 当前项目所有的.vue的文件中都可以通过hooks
// 2:监听路由信息的改变:逻辑处理

// 如果这个监听的数据是一个对象,则需要监听这个对象李米娜的某个数据的改变
// 如果过你要监听这个对象,只要这个对象中有一个数据改变,就会触发watch 处理数据,写这个数据
//{ fullPath:'',meta:"",name:''}

watch(route,(newVal,oldVal)=>{
// 逻辑处理
BreadChange(newVal);
})

const BreadChange=(newVal:any)=>{
if(newVal.fullPath=='/layout/index'){
beradList.value=[{name:"首页",path:'/layout/index'}]
}
else{
// 点击侧边栏导航没有嵌套
// 1 需要获取到meta
// [{name:"首页",path:'/layout/index'}] 数组结构
let arrs=[{name:"首页",path:'/layout/index'}];

beradList.value=arrs.concat(newVal.meta.nameList);
// console.log(beradList.value,525252)

// 把每次点击的面包屑存储起来
sessionStorage.setItem("beradList",JSON.stringify(beradList.value));
}
}

</script>

<style lang='scss' scoped>
//修改 element plus
::v-deep .el-breadcrumb__inner a,
.el-breadcrumb__inner.is-link {
font-weight: 0 !important;
}
</style>

2、封装路由标签组件

作用:记录用户点击侧边栏导航

实现 功能实现:

1:用户进入到后台管理系统 默认进行首页

2:用户点击其他侧边栏导航的时候,在路由标签上添加记录 =》 数组对象

3:显示 删除图标:(1) 除了 首页标签,其他的都可以显示

4:点击删除图标:(1)如果这个路由标签不是和浏览器的地址对应的标签,直接删除

        (2)如果这个路由标签是和浏览器的地址对应的标签,点击删除的时候,路由跳转=》路由标签的最后一项的路径

5:点击路由标签实现路由跳转

6:浏览器的地址和路由标签的对象的地址一样,路由标签组件高亮效果

7:浏览器刷新=》路由标签展示 之前的最新的数据

8:点路由标签的实现页面跳转=》让侧边栏导航 一一对应

步骤:先创建一个文件夹components/WRouterTags/index.vue 创建路由标签组件,并在Layout/index.vue中引入该组件

<template>

<!-- 自定义的组件 -->
    <div class="wuInput wukong-flex3">
         <div class=" wukong-flex3" :class="show?'leftBoxA':'leftBox' ">
            <i class="iconfont icon-yanzheng" ></i>
         <input v-model="verCon" type="text" @blur="blur" placeholder="验证码">
         <i  @click="deleteC" v-if="verCon" class="">x</i>
         </div>
        <!-- 验证数据 -->
        <img :src="verImg" @click="getImage" alt="">
        <div  v-if="show" class="showguiz">dfdfdf</div>
    </div>
</template>

<script lang="ts" setup>
       import {LoginVerData} from '@/https/api/login'
          import {ref} from 'vue'
          let verCon =ref('')
          let verImg =ref('')
          let verContent =ref('')
          let show =ref(false)
       //获取到请求
       LoginVerData().then((res:any)=>{
             verImg.value = res.img
             verContent.value =res.content
       })

// 子组件给父组件数据

let  emits = defineEmits(['getVerify'])
       const blur = ()=>{
           if( verContent.value==verCon.value){
             //
             show.value = false
               emits('getVerify',true)
           }else{
               show.value = true
                 emits('getVerify',false)
           }
       }

       const deleteC = ()=>{
        verCon.value = ''
         emits('getVerify',false)
       }

     const  getImage = ()=>{
        //发送请求 =》获取到验证
          LoginVerData().then((res:any)=>{
             verImg.value = res.img
             verContent.value =res.content
       })
     }

    //  我只需要告诉父亲我验证通过
</script>

<style scoped lang="scss">
   .wuInput{
     position: relative;
     .leftBox{
        width:50%;
       padding-left:12px;
       border:1px solid #ccc;
       border-radius: 4px 0 0 4px;
        i{
             font-size: 12px;
        }

     }
     .leftBox:hover{
         border:1px solid rgb(99, 95, 95);
          border-radius: 4px 0 0 4px;
     }
     .leftBoxA{
         width:50%;
       padding-left:12px;
       border:1px solid red;
       border-radius: 4px 0 0 4px;
        i{
             font-size: 12px;
        }
     }

    img{
      height:33px;
       width:50%;
    }

    .showguiz{
          position: absolute;
          width:100%;
          font-size: 12px;
          color:red;

          top:24px;
          left:12px;
    }
   }
      input{
        outline: none;
        border:none;
        font-size: 12px;
    }
</style>

3、退出登录组件

清空本地数据 =》跳转到登录页面

<template>
  <div>
    <div class="outLogin" @click="showClick">
      <img class="images" :src="users.img" alt="">
      <div>{{users.name}}</div>
    </div>
    <el-dropdown
      ref="dropdown1"
      trigger="contextmenu"
      style="margin-right: 30px"
      @command="outLogin"
    >
    <!-- 必须需要一个 内容 -->
     <span class="el-dropdown-link">  </span>
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item >退出登录</el-dropdown-item>
        </el-dropdown-menu>
      </template>
    </el-dropdown>
  </div>
</template>

<script lang="ts" setup >
import { ref,reactive } from "vue";
import {useRouter} from 'vue-router'
const dropdown1 = ref(); // vue doM操作
let users = reactive({
    name: JSON.parse(sessionStorage.getItem('users')|| '').name,
    img:JSON.parse(sessionStorage.getItem('users')|| '').img,
})
let router = useRouter()
function showClick() {
   
    
  dropdown1.value.handleOpen();
}

const outLogin = ()=>{
    console.log(55555);
    
    //  (1)清空本地数据
     sessionStorage.clear()
     //(2) 跳转到登录页面
     router.replace('/login')
}
</script>

<style scoped lang="scss">
.outLogin {
  align-items: center;
  color: #000000d9;
  cursor: pointer;
  display: flex;
  height: 48px;
  justify-content: space-around;
  padding: 10px;
  .images{
      height:36px;
      width:36px;
  }
}
.outLogin:hover {
  background: #ccc;
}

.example-showcase .el-dropdown-link {
  cursor: pointer;
  color: var(--el-color-primary);
  display: flex;
  align-items: center;
}
</style>

4、二次封装echarts组件

1:创建一个单独文件.vue文件=》实现echarts

<template>
  <!-- 
   1:在vue项目中 安装 ecahrts
   // 1安装
   //2配置

   //3 确定 echarts 柱状图 动态属性 =》多少个=》公司项目 ui图 柱状图
  //处理动态属性
 -->
  <div class="bar">
    <div id="main" :style="{ width: 700 + 'px', height: 300 + 'px' }"></div>
  </div>
</template>

<script lang="ts" setup>
import * as echarts from "echarts";
import { onMounted } from "vue";
  

  
onMounted(() => {
  let dom: any = document.getElementById("main");
  var myChart = echarts.init(dom);
  // 绘制图表
  myChart.setOption({
    // title: {
    //   text: "ECharts 入门示例",
    // },
    tooltip: {},
    xAxis: {
      data: ["1月", "2月", "3月", "4月", "5月", "6月"],
    },
    yAxis: {},
    series: [
      {
        name: "销售量",
        type: "bar",
        data: [5, 20, 36, 10, 10, 20],
      },
    ],
  });
});
</script>

<style>
</style>

2:echarts 封装 常用的版本=》 50%

<template>
  <!-- 
   1:在vue项目中 安装 ecahrts =>
   // 1安装
   //2配置

   //3 确定 echarts 柱状图 动态属性 =》多少个=》公司项目 ui图 柱状图
  //处理动态属性
 -->
  <div class="bar">
    <div :id="ids" :style="{ width: 1000 + 'px', height: 380 + 'px' }"></div>
  </div>
</template>

<script lang="ts" setup>
import * as echarts from "echarts";
import { onMounted,watch } from "vue";
  
//父组件给的数据 =>不是在template,而是在js 逻辑层
// 我们这个子组件
let props = defineProps({
     barList:{
      type:Number
     },
     ids:{
      type:String,
      default:'main'
     }
})
console.log('echarts创建');

//其实这个没有数据 =》是空的数据
//原因:组件的创建。会创建几次  =》一次
// 1)当这个 ehcarts组件第一次创建 ,父组件给子组件的数据 =》 barList =[]
// 2)  我需要获取到 barList 后端给的数据,后端给的数据是异步的


//组件创建速度比后端给的数据快
let dom: any  =''
let myChart:any ='' 
watch(()=>props.barList,(newVal,oldVal)=>{
  // console.log(newVal,oldVal,5555586);
     getEcharts(newVal)
})
onMounted(() => {

  //
     dom= document.getElementById("main");
  
  myChart = echarts.init(dom);
});

//生成 echart 图表
 
const getEcharts = (newVal:any)=>{
  console.log(newVal,2000);
  


  // 绘制图表
  myChart.setOption({
    // title: {
    //   text: "ECharts 入门示例",
    // },
    tooltip: {},
    xAxis: {
      data: ["1月", "2月", "3月", "4月", "5月", "6月"],
    },
    yAxis: {},
    series: [
      {
        name: "销售量",
        type: "bar",
        data: newVal,
      },
    ],
  });
}
</script>

<style>
</style>

3:echarts 封装 =》一个类型 =》当前的项目 echarts ,比如 柱状图

1)处理 id =>将echarts 通过画布画到对应的位置

2) 动态 柱状图 属性

<template>
    <!-- 
    1:在vue项目中 安装 echarts
        1)安装、
        2)配置
        3)确定echarts 柱状图 动态属性:多少个,公司项目 ui图 柱状图
        4)处理动态属性
 -->
    <div class="bar">
        <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
        <div :id="props.ids" :style="{ width: 1000 + 'px', height: 380 + 'px' }"></div>
    </div>
</template>
   
<script setup lang='ts'>

import * as echarts from 'echarts';
import { onMounted,watch } from 'vue';

// 接收父组件
let props=defineProps({
    barList:{
        type:Array,
    },
    ids:{
        type:String,
        default:"main",
    },
    showLine:{
        type:Boolean,
        default:false,
    }
})


// 基于准备好的dom,初始化echarts实例
let dom:any="";
let myChart:any="";
onMounted(() => {
    dom = document.getElementById(props.ids)
    myChart = echarts.init(dom);
})


// 第一次echart图表不显示数据 :因为他是空的数据
// 原因:组件创建的时候,只会创建一次
//  1)当这个echarts组件第一次创建的时候,父组件给子组件的数据为空  barList=[]; 
// 2) 需要获取到barList 后端给的数据,是异步的;
// 3) 组件创建速度比后端给的数据快,因为在服务器的数据还没返回,就父组件已经把默认的空数据传过来了

// 监听获取到的数据的变化
watch(()=>props.barList,(newVal,oldVal)=>{
    // console.log(newVal,oldVal,5555586);
    getEchartBar(newVal)
})

const showLine=(newVal:any)=>{
    return {
    name: "line",
    type: "line",
    data: newVal,
  };
}

// 生成echart 图标
const getEchartBar=(newVal:any)=>{
 
    // 绘制图表
    myChart.setOption({
        // title: {
        //     text: 'ECharts 入门示例'
        // },
        tooltip: {},
        xAxis: {
            data: ["1月", "2月", "3月", "4月", "5月", "6月","7月", "8月", "9月", "10月", "11月", "12月"],
        },
        yAxis: {},
        series: [
            {
                name: '销售量',
                type: 'bar',
                data: newVal,
            },
            // 显示折线图 => 是true才显示
            props.showLine?showLine(newVal):"",
        ]
    });
}

</script>

<style lang='scss' scoped>

</style>

5: 异步组件

作用:1 用户第一次进入到这个页面级组件的时候,添加加载效果,我这个页面的代码好没有加载出来。先加载,加载效果

2:实现分包的效果,提高用户首次加载项目的速度,=》将首次加载的页面变为异步组件

3:首次加载项目的时候,如果项目体积过大,提示加载效果

 

实现:

1)将用户访问我们这个项目,首次打开的页面,我们把这个页面变成我们这个异步组件

语法:

1:创建一个异步组件,在view/Login/AyLogin

创建异步组件写法1

<template>
  <!-- 创建异步组件
   分为  1:自身的内容   2:正在加载的效果
   

   实现:1=》创建异步组件 =》 把谁变成异步组件 =》把登录组件的代码
        2=》异步组件需要结合 Suspense
        3=》需要处理路由文件=》/login =>  Login/AyLoing.vue

 -->
  <Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <AsyncComp />

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    <Loading></Loading>
  </template>
</Suspense>
</template>

<script lang="ts" setup>
import Loading from '@/components/Loading/index.vue'
import { defineAsyncComponent } from 'vue'
  // defineAsyncComponent(处理方法) =>返回值就是一个异步组件


  //处理方法 相对于路由懒加载 =》()=>{return import('组件的地址')}
  //import('组件的地址') =》底层=》就是一个Promise
const AsyncComp = defineAsyncComponent(() => {
//   return new Promise((resolve, reject) => { //就是路由懒加载
//     // ...从服务器获取组件
//     resolve(/* 获取到的组件 */);
//   });

return import('./index.vue')
});
</script>

<style>
</style>

创建异步组件写法2

不想在模板中使用 Suspense 直接使用 异步组件,

//并且这个异步组件,已经处理好了加载效果

<template>
<!-- 创建异步组件
分为 1:自身的内容 2:正在加载的效果

实现:1=》创建异步组件 =》 把谁变成异步组件 =》把登录组件的代码
2=》异步组件需要结合 Suspense
3=》需要处理路由文件=》/login => Login/AyLoing.vue

-->

<AsyncComp></AsyncComp>

</template>

<script lang="ts" setup>
import Loading from '@/components/Loading/index.vue'
import { defineAsyncComponent } from 'vue'
// defineAsyncComponent(处理方法) =>返回值就是一个异步组件

//创建异步组件写法2 =》不想在模板中使用 Suspense 直接使用 异步组件,
//并且这个异步组件,已经处理好了加载效果

const AsyncComp = defineAsyncComponent({
// 加载组件
loader: () => import('./index.vue'),

// 加载异步组件时使用的组件
loadingComponent: Loading,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,

// 加载失败后展示的组件
// errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
</script>

<style>
</style>

异步组件的分包 =》 vue3 在项目打包时候体现, 将项目体积进行分包 =》打包工具,webpack 或者vite

 

6、权限 集成版本 =》页面级权限 和按钮级权限

项目中的权限 :页面级权限和 按钮级权限

按钮级权限:就是根据不同的权限的人,都可以进入到这个页面,但是这个页面显示的内容是不同的;

实现方法:1、最简单的方法,条件判断;2、最常用的自定义指令,处理按钮级权限;

<template>
  <div>
    <h2>home</h2>

    <!-- 有很多模块
       而这个首页 =》不同的用户都能进入到这个 页面
       但是不同的权限的人看到内容是不同的
       比如我这个项目有两个权限  主管  员工

       主管: 查看绩效按钮   
       员工:  不能的
    
     -->
       <div>所有的权限都能看到</div>
     <button  v-if="limit=='主管'||limit=='经理'">查看绩效</button>
     
  </div>
</template>

<script lang="ts" setup>
// 获取到权限
  import {ref,reactive} from 'vue'  //reactive =>vue2  data
  let limit = ref(sessionStorage.getItem('limit'))

</script>

<style>
</style>

if 语句=》 加强版处理:if变为函数调用,其实在函数内部已经做好了权限的处理了

<template>
  <div>
    <h2>home</h2>

     <button  v-if="limitJixiao()">查看绩效</button>
     
  </div>
</template>

<script lang="ts" setup>
// 获取到权限
  import {ref,reactive} from 'vue'  //reactive =>vue2  data
//定义一个方法
  import {limitJixiao} from '@/useHooks/useLimit/useHome'

 </script>

<style>
</style>

把limitJixiao方法抽离'@/useHooks/useLimit/useHome'

//项目的权限表=》当前权限很多时候=》数据是后端给的并且,按钮的权限,

// 项目的权限表

export let limitList = [
  { limit: "经理", wageMLimt: true },
  { limit: "boss", wageMLimt: true },
  { limit: "财务", wageMLimt: true },
  { limit: "主管", wageMLimt: true },
  { limit: "员工" },
];

export function limitJixiao() {
  //首页 绩效按钮 权限
  // 获取到权限=》当前用户的权限=>存放到本地
  let currentLimit = sessionStorage.getItem("limit");

  // 我项目有多个权限:固定有: 经理 ,boss ,财务 ,员工,主管

  //绩效按钮 权限 =》有多少个权限可以 显示这个 按钮   =》经理 ,boss ,财务

  let obj:any=limitList.find((item)=>{return item.limit==currentLimit});// 返回匹配的第一项
    // console.log(obj,"555666",obj.wageMLimt)
  if(obj.wageMLimt){
    return true;
  }
  else{
    return false;
  }
}

自定义指令 实现按钮级权限=》本质就是条件判断

什么是自定义指令=》就是自己定的指令,不是vue 内部提供的指令

局部注册自定义指令:语法

<template>
    <div class="directives">
        自定义指令
        <!-- 定义:全局 和 局部 
        写法: V
    -->
        <input type="text" v-focus="names">

        <!-- 使用全局自定义指令 -->
        使用全局自定义指令
        
    </div>
</template>
   
<script setup lang='ts'>
/* 局部注册 */
let names: any = "传递的参数";

const vFocus = {
    // 自定义指令的 实例对象(和vue 实例很像)
    // 生命周期 =》指令添加到元素上=》元素的本质组件=》这个自定指令就可以做操作
    mounted: (el: any, binding: any) => {
        // 参数1 就是这个元素的真实dom  参数 2:就是自定指令后面的数据,自定义值动态的
        el.focus();
        console.log(el, binding);
    }
}

</script>

<style lang='scss' scoped>

</style>

全局自定义注册指令

语法:在入口文件注册main.js:

const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
    mounted(el){
    console.log(el,"全局注册自定义指令")
    el.focus()
  }
})

// 直接在其他组件中使用 v-focus指令
<input type="text" v-focus>

工作中的自定义指令=》处理按钮级权限

在src 文件夹下创建一个文件夹Directive=> 统一处理项目中的字定义指令

// 统一处理项目中按钮权限:自定义指令

import store from "@/store/index";

// app =>  vue 中 =》  在一个vue中使用 app 只能通过 插件机制

function AddDiretives(App: any) {
  //App 当前项目的

//   console.log(App, "app123"); //App 当前项目的
  App.directive("limit", {
    mounted(el: any) {
      console.log(el, "el123"); // 获取到真实dom

      //需要判断 =》 当前用户,是否可以显示这个组件
      // 思路:当前的用户权限,在去到按钮级权限表中,去找这个用户是否对这个按钮的权限

      let currentLimit = sessionStorage.getItem("limit");  

      // 这个权限表是后端给的,发送请求 => 用户成功的时候发送

    //   let limitS = [
    //     { limit: "经理", wageMLimt: true },
    //     { limit: "boss", wageMLimt: true },
    //     { limit: "财务", wageMLimt: true },
    //     { limit: "主管", wageMLimt: true },
    //     { limit: "员工" },
    //   ];

      let limitS=getButtonList(); // 获取的是权限表

    //   let limitS=store.state.buttonLists
      // 获取到vuex中的数据:之前在.vue文件中获取到仓库,使用的hooks中的useStore 只能在.vue文件中使用
      // 而在ts中 直接引入  import store from "@/store/index";

      let obj=limitS.find((item:any)=>{
        return item.limit==currentLimit
      })

      // 处理这个组件显示或不显示
      if(obj){
        return  true;
      }
      createElement(obj,el)
    },
  });
}


function getButtonList(){
    let list:any=sessionStorage.getItem('buttonLimits');
    if(store.state.buttonLists.length>0){
        return store.state.buttonLists
    }
    else{
        return JSON.parse(list);
    }
}


function createElement(val:any,el:any){
    // val.wageMLimt  有 =>按钮显示反之不显示
    if(val.wageMLimt){// 显示组件

    }
    else{   // 不显示组件    =>组件是谁 dom =>dom操作=>删除元素  lis.remove();
        el.remove()
    }
}

//总结一下自定义指令 处理按钮级权限=>
// 1 当前的用户权限 去 按钮权限列表中查找,就两种情况:显示或不显示
// 2 自定义指令 处理按钮级权限 本质:就是dom操作
export default AddDiretives;

 

对应的vue中的数据

//  创建 仓库 
import {createStore} from 'vuex';
import {getButtonLimits} from "@/https/api/limits"
// createStore 创建仓库

// 语法:createStore({})

 let store=createStore({
    state:{ // 存放数据 => 这个数据是响应式的,底层代码是通过vue reactvite实现的
        currentPath:'',
        age:20,
        limit:'员工',
        buttonLists:[],
    },
    getters:{   // 相对于vuex中的计算属性,就是state中的数据,在视图中使用,不是我需要的,在getters中把这个数据变为我想要的数据
        // 为什么会有计算属性 =》 就是  和vue 中的技术属性 一样的
        getSister(state){
            return state.age+5;
        }
    },
    mutations:{ // 定义方法修改state中的数据:同步方法, 底层代码就是一个发布订阅者模式;
        // 前端的设计模式:单例模式,工厂模式,订阅发布者模式,策略模式...26种设计模式
        changeCurrentPath(state,value){
            state.currentPath=value;
        },
        changeLimit(state,value){
            state.limit=value;
        },
        addButtonLists(state,val){
            state.buttonLists=val;
        }

    },
    actions:{   // 定义异步的方法 获取异步数据 => 再触发mutations中定义的方法
        AyAddButtonLists({commit},data){    // actions中定义的方法的:参数1:当前仓库实例对象;    参数2:dispatch('actions中方法',数据),中传递过来的数据
            let token=sessionStorage.getItem('token');
            // 发送请求获取到异步数据

            getButtonLimits({token:token}).then((res:any)=>{
                // 触发mutations中定义的方法
                commit('addButtonLists',res.limits)
                sessionStorage.setItem('buttonLimits',JSON.stringify(res.limits))
            })
        }

    }
})

export default store;   // // 为什么需要将这个仓库暴露出去=>因为我这个仓库是一个 插件