跳至主要內容

说明

zfh大约 5 分钟约 1646 字...

vite 项目index.html中使用变量

vite-plugin-html 插件open in new window

$响应式语法糖

一般的我们通过 ref 声明响应式数据,在 js 中访问需要加.value

通过响应式语法糖,我们可以省去 value

通过配置开启:

// vite.config.js
export default {
  plugins: [
    vue({
      reactivityTransform: true,
    }),
  ],
}

更多细节open in new window

css 中绑定变量

注意

如果需要动态变更,请创建响应式数据

let leftNavWidth = $ref("220px")
 .left-nav {
    width: v-bind(leftNavWidth);
    height: 100vh;
    background-color: #ffffff;
    overflow-y: auto;
    transition: width .5s;
 }

路径别名

vite3.0 支持路径别名open in new window

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参数确保先执行离开动画,然后在其完成之后再执行元素的进入动画,这样使得切换显得更“正常”

vue3admin-路由切换动画
路由切换动画

按钮权限控制实现

// 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 页面开发

前置知识

导航守卫open in new window

Vue Router 3 Vue Router 4的处理逻辑稍有不同open in new window。指的是next

现在不使用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' }
})

实现全局标签页

vue3-admin-全局标签页
全局标签页效果图

监听路由变化,利用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))
}

利用elementUIel-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没有实现,后面看看有没有其他拖拽库

上次编辑于:
本站勉强运行 小时
本站总访问量
網站計數器