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; // // 为什么需要将这个仓库暴露出去=>因为我这个仓库是一个 插件