说明
大约 5 分钟约 1646 字...
index.html
中使用变量
vite 项目$响应式语法糖
一般的我们通过 ref
声明响应式数据,在 js 中访问需要加.value
通过响应式语法糖,我们可以省去 value
通过配置开启:
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true,
}),
],
}
css 中绑定变量
注意
如果需要动态变更,请创建响应式数据
let leftNavWidth = $ref("220px")
.left-nav {
width: v-bind(leftNavWidth);
height: 100vh;
background-color: #ffffff;
overflow-y: auto;
transition: width .5s;
}
路径别名
props
<script setup>
const props = defineProps(['menuList', 'isCollapse'])
</script>
在使用 <script setup>
的单文件组件中,props 可以使用 defineProps()
来声明
模板中直接使用,无需props.xxx
动态组件
<component is='string | Component'>
- 当
is
是字符串,它既可以是HTML
标签名也可以是组件的注册名- 或者,
is
也可以直接绑定到组件的定义
- 或者,
递归组件
项目中的菜单列表并不是静态数据,而是从接口取数据进行动态渲染。
那么此时就需要递归组件,即自己调用自己
<template>
<el-menu :collapse="isCollapse" default-active="/system/menu" router>
<template v-for="menu in menuList">
<el-sub-menu
v-if="
menu.children &&
menu.children.length > 0 &&
menu.children[0].menuType == 1
"
:index="menu.path"
>
<template #title>
<el-icon>
<component :is="menu.icon.split('-')[2]" />
</el-icon>
<span>{{ menu.menuName }}</span>
</template>
<!-- 递归组件-->
<menu-tree :menuList="menu.children" />
</el-sub-menu>
<el-menu-item v-else-if="menu.menuType == 1" :index="menu.path"
>{{ menu.menuName }}
</el-menu-item>
</template>
</el-menu>
</template>
<script setup>
const props = defineProps(['menuList', 'isCollapse'])
</script>
面包屑导航
利用 vue router 的match
路由匹配数组和路由元数据title
进行渲染:
<template>
<el-breadcrumb :separator-icon="ArrowRight">
<el-breadcrumb-item v-for="(item, index) in matched">
<router-link
to="/welcome"
v-if="index === 0"
style="color:#fff !important;"
>{{ item.meta.title }}</router-link
>
<span v-else style="color:#fff">{{ item.meta.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { useRoute } from 'vue-router'
import { ArrowRight } from '@element-plus/icons-vue'
import { computed } from 'vue'
const router = useRoute()
const matched = computed(() => {
return router.matched
})
</script>
重置 elementPlus 颜色主题
新建一个 scss 文件:
/*
* element样式重置
*/
// 修改主题色
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #00cf74,
),
)
);
// 导入所有样式
@import 'element-plus/theme-chalk/src/index.scss';
然后在main.js
文件中引入:
import './style/elementReset.scss'
// import "element-plus/dist/index.css"
路由切换动画踩坑
想要给菜单切换(路由切换)时加一个动画,坑点很多记录下
vue-router 官网已经有示范代码:
<router-view v-slot="{ Component }">
<Transition name="fade">
<component :is="Component" />
</Transition>
</router-view>
特别注意
路由组件下必须只能有一个根节点,否则切换就会不正常,虽然 vue3 支持多个根节点的写法。
这里结合animate.css
实现过渡动画:
<router-view v-slot="{ Component }">
<Transition
mode="out-in"
enter-active-class="animate__animated animate__flipX animate__faster"
leave-active-class="animate__animated animate__flipOutX animate__faster"
>
<KeepAlive>
<component :is="Component"/>
</KeepAlive>
</Transition>
</router-view>
mode
参数确保先执行离开动画,然后在其完成之后再执行元素的进入动画,这样使得切换显得更“正常”

按钮权限控制实现
前置知识
// main.js
// 按钮权限判断自定义指令
app.directive('permission', {
// 在元素被插入到 DOM 前调用
beforeMount(el, binding) {
let userAction = storage.getItem('userAction')
if (!userAction.includes(binding.value)) {
// 隐藏元素
el.style.display = 'none'
// 变成宏任务 防止元素未插入 DOM 删除元素报错
setTimeout(() => {
el.parentNode.removeChild(el)
}, 0)
}
},
})
404 页面开发
现在不使用next
,只需要判断不符合条件的情况
// router/index.js
// 全局前置守卫
// 判断访问路径是否正确 不正确跳转404页面
// vue-router 4 建议不使用`next`写法
router.beforeEach((to, from) => {
let hasPermission =
router.getRoutes().filter((route) => route.path === to.path).length > 0
if (!hasPermission) return { name: '404' }
})
实现全局标签页

监听路由变化,利用Pinia
存储当前路由路径和页面标题
// 监听路由变化
watch([() => route.path, () => route.meta.title], async (newValue) => {
tabsStore.changeTab(newValue[0])
tabsStore.saveTab({ path: newValue[0], title: newValue[1] })
})
路由变化时,改变当前路径,保存当前路数组(需要过滤,防止路径数组存在重复的路径):
// store/tabs.js
/**
* 路由tab信息
*/
import { defineStore, acceptHMRUpdate } from 'pinia'
// 第一个参数是应用程序中 store 的唯一 id
export const useTabsStore = defineStore('tabs', {
state: () => {
return {
tabs: [],
currentTab: '',
number: 1,
}
},
actions: {
clearTabs() {
this.tabs = []
},
changeTab(tab) {
this.currentTab = tab
},
//保存
saveTab(tab) {
const uniqueFunc = (arr, uniId) => {
const res = new Map()
return arr.filter(
(item) => !res.has(item[uniId]) && res.set(item[uniId], 1)
)
}
if (tab.path !== '/login') this.tabs.push(tab)
this.tabs = uniqueFunc(this.tabs, 'path')
},
// 删除
removeTab(tabPath) {
this.tabs.forEach((item, index) => {
if (tabPath === item.path) {
this.tabs.splice(index, 1)
}
})
},
},
})
// 热更新支持
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useTabsStore, import.meta.hot))
}
利用elementUI
的el-tabs
组件完成标签页组件:
<template>
<el-tabs
v-model="editableTabsValue"
type="card"
closable
class="demo-tabs"
@tab-remove="removeTab"
@tab-click="clickTab"
>
<el-tab-pane
v-for="item in tabs"
class="tab-pane"
:key="item.path"
:label="item.title"
:name="item.path"
>
</el-tab-pane>
</el-tabs>
</template>
<!--路由tab切换组件-->
<script setup>
import Sortable from 'sortablejs'
import { useTabsStore } from '@/store/tabs.js'
import { computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const tabsStore = useTabsStore()
const router = useRouter()
let editableTabsValue = computed(() => tabsStore.currentTab)
let tabs = computed(() => tabsStore.tabs)
// 实现拖拽
const rowDrop = async () => {
const el = document.querySelector('.el-tabs__nav')
Sortable.create(el, {
onEnd({ newIndex, oldIndex }) {
//oldIIndex拖放前的位置, newIndex拖放后的位置
const currRow = tabsStore.tabs.splice(oldIndex, 1)[0] //鼠标拖拽当前的el-tabs-pane
tabsStore.tabs.splice(newIndex, 0, currRow) //tableData 是存放所以el-tabs-pane的数组
},
})
}
onMounted(() => {
rowDrop()
})
const removeTab = (targetName) => {
if (targetName === '/welcome' && tabsStore.tabs.length === 1) return
tabsStore.tabs.forEach((item, index) => {
if (
item.path === targetName &&
tabsStore.tabs[index - 1] &&
tabsStore.currentTab === targetName
) {
router.push(tabsStore.tabs[index - 1].path)
}
})
tabsStore.removeTab(targetName)
if (tabsStore.tabs.length === 0) router.push('/welcome')
}
const clickTab = (v) => {
router.push(v.props.name)
}
</script>
<style scoped lang="scss">
.demo-tabs {
height: 40px;
}
</style>
给el-tab
绑定当前路径,点击标签 🏷️ 进行路径跳转。
删除需要进行判断:
- 欢迎页(首页)不允许删除
- 不符合第一个条件,遍历当前路径数组,当遍历到要删除的数组时候进行判断:如果要删除的路径与当前路径相同且路径数组中它的上一个元素存在,删除当前路径,并跳转到它的上一个路径
利用sortablejs
实现标签页的位置调换功能。这部分需求是第一次做所以还是有瑕疵的,动画比较僵硬,拖拽的时候标签是透明的,好在完成了功能。开始使用vuedraggable
没有实现,后面看看有没有其他拖拽库