技术栈: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 组件进行支持
1、安装
npm install element-plus --save
2、按需引入:添加 vue.config.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') // 挂载到对应的位置
npm install sass
npm install sass-loader
sass-loader 是将scss文件转为css文件,因为浏览器只解析html、css、js
四:处理全局样式
1、高度的继承
2、盒子模型
3、项目的公共样式
4、处理icon,阿里巴巴矢量图标库
1)在 src 目录下创建一个style文件夹 => 处理项目的样式 => 进行模块化(把其他的scss文件引入到index.scss文件中)
2) 在main.ts进行引入
import './style/index.scss'
1、安装axios
npm install axios
2、在scr目录下创建一个文件夹https/index.js
3、封装axios请求,并添加请求拦截和响应拦截
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); }
六、路由的处理
npm install vue-router
2、怎么在项目中使用路由?
步骤一:在src目录下创建一个文件夹router,创建index.ts
a:引入路由hooks:import {createRouter,createWebHashHistory} from 'vue-router'
// 路由主文件 // 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;
作用:app.use(router)
项目中的所有组件都可以访问到$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
npm i @types/nprogress -D
打开进度条: NProgress.start()
关闭进度条:NProgress.done()
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)刷新浏览器的时候=》我这个面包屑应展示的是最后 面包屑的数据结构
<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>
实现 功能实现:
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 通过画布画到对应的位置
<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>
作用: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>
不想在模板中使用 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>
//项目的权限表=》当前权限很多时候=》数据是后端给的并且,按钮的权限, // 项目的权限表 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>
全局自定义注册指令
const app = createApp({}) // 使 v-focus 在所有组件中都可用 app.directive('focus', { /* ... */ mounted(el){ console.log(el,"全局注册自定义指令") el.focus() } }) // 直接在其他组件中使用 v-focus指令 <input type="text" v-focus>
// 统一处理项目中按钮权限:自定义指令 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; // // 为什么需要将这个仓库暴露出去=>因为我这个仓库是一个 插件