vue3 + ant-design-vue 简易入门

20,845 阅读3分钟

最近忙里偷闲,看了下vue3的相关文档,想着写点东西熟悉一下新版的代码。恰好当前时间节点上你能在市面上成熟使用的vue3的UI框架放眼望去,也就只有阿里旗下的Ant Design Vue 2.0.0测试版。正好以前都是用element-ui的,所以就萌生了用这个框架搭一个非常简易的后台管理页面框架,主要目的是学习,用于实际生产估计还差点,主要是记录一下学习过程。最终项目在这里

父子组件通信

先来看下页面整体布局

<!-- App.vue -->
<a-config-provider :locale="locale">
	<a-spin :spinning="spinning" tip="Loading...">
	  <a-layout id="components-layout">
		<Sider :collapsed="collapsed" />

		<a-layout>
		  <Header :collapsed="collapsed" @toggle-collapsed="collapsed = !collapsed" />
		  <Breadcrumb />
		  <a-layout-content :style="{
		  margin: '24px 16px',
		  padding: '24px',
		  background: '#fff',
		  minHeight: '280px',
		}">
			<router-view></router-view>
		  </a-layout-content>
		</a-layout>
	  </a-layout>
	</a-spin>
</a-config-provider>

这里直接把官网的样例拷过来,然后按照头部,侧边栏,面包屑分别拆成组件。那么在拆分之后我需要点击头部的按钮控制侧边栏的收缩与展开,这里既可以使用父子组件通过最上层的App.vue作为桥梁传值,也可以通过Vuex传值,我采用第一种办法。

先在Header.vue中添加方法

<!-- Header.vue -->
 <a-layout-header class="header">
    <menu-unfold-outlined v-if="collapsed" class="trigger" @click="toggleCollapsed" />
    <menu-fold-outlined v-else class="trigger" @click="toggleCollapsed" />
	...
</a-layout-header>

在vue2.x的版本中子传父需要通过this.$emit传递,但是在vue3.x中如果代码写在setup中是不能使用this的,官方文档的办法是在setup的第二个参数中有一个emit方法,通过它实现传值。

// Header.vue
setup(props, { emit }) {
    const toggleCollapsed = () => {
      emit("toggle-collapsed");
    };

    return {
      toggleCollapsed,
    };
  },

我这里其实不需要传值,只是需要触一下点击事件,之后在父组件中直接切换一下true/false

// App.vue
<Header :collapsed="collapsed" @toggle-collapsed="collapsed = !collapsed" />

然后在侧边栏组件中获取到改变的collpased

<Sider :collapsed="collapsed" />

之后在侧边栏组件内获取父层带过来的数据与vue2.x的方式差不多,直接获取使用即可,如果你需要在setup中获取别忘了在setup方法上添加props参数就行。

路由的监听

在侧边栏中如果路由改变我希望可以高亮当前的导航,同时我希望面包屑也要展示到的对应的路由,这里就需要用到vue3.x的watch方法,另外vue3.x还提供了一个watchEffect方法,官方文档说区别在于watchEffect可以做到立即触发监听,我这里使用的是watch。 如果你是直接使用官方脚手架生成的项目代码,那么vue-router的版本应该是4.0,这也是为了配合使用vue3.x的原因,在2.x中我们使用this.routethis.route**和**this.router获取路由信息和操作路由,在4.0的路由版本中这两个方法被放在了useRouteuseRouter里,所以要监听路由的变化则需要监听useRoute的变化,以面包屑为例

// Breadcrumb.vue
import { watch, reactive, toRefs } from "vue";
import { useRoute, useRouter } from "vue-router";

export default {
  name: "Breadcrumb",
  setup() {
    const route = useRoute();
    const router = useRouter();

    const state = reactive({
      matched: route.matched,
    });

	// 监听路由变化
    watch(
      () => route,
      (route) => {
        state.matched = route.matched;
      },
      {
        immediate: true,
        deep: true,
      }
    );

    const jumped = (path) => {
      router.push({
        path: path,
      });
    };

    return { ...toRefs(state), jumped };
  },
};

在watch的第一个参数中构建一个函数获取useRoute(),第二个参数则是监听变动之后的操作,这里我获取路由的matched数组并显示到面包屑上

// Breadcrumb.vue
 <a-breadcrumb :style="{
      padding: '24px',
      paddingBottom: '0',
    }">
    <template v-if="matched.length > 0">
      <a-breadcrumb-item v-for="item in matched" :key="item.path"><a href="javascript:;" @click="jumped(item.path)">{{
          item.meta.title
        }}</a></a-breadcrumb-item>
    </template>
  </a-breadcrumb>

watch的第三个参数则用来配置是否立即执行监听和深度变动。这样路由变化,面包屑的信息会跟着变动,侧边栏类同,不再赘述。

全局配置axios

另一项需求就是怎样在项目中全局引入axios,说实话我一开始确实不知道,官方文档也没看太懂,后来在其他的文章中找到了使用方法。 首先引入自己封装过的axio方法,然后在createApp的全局配置中有一个globalProperties,将自己写的axios方法挂在到这个属性上就好了

// main.js
import $http from "./api/axios"

const app = createApp(App);
app.config.globalProperties.$http = $http;

接着在实际使用中与vue2.x略微有些不同,你需要在需要使用axios的组件中先引入vue提供的getCurrentInstance方法,其中有一个ctx,获取全局变量,这就相当于2.x中的this了。然后在ctx中使用我自定义的$http方法

import { ref, getCurrentInstance } from "vue";

export default {
  setup() {
  	...
    const { ctx } = getCurrentInstance();

    ctx.$http({ url: "/test", loading: true });

    return {
      ...
    };
  },
  created() {
    // this.$http({ url: "/test" });
  },

顺便一提,如果你按照老方法,在createdmounted使用this.$http也是可以正常调用的。

结合ant-design 添加全局loading

我原来的$http方法结合了element-ui在请求之前的拦截里写了一个loading的效果,用过element-ui的朋友应该知道你可以用一个服务的方式添加一个loading非常方便

const loading = this.$loading({
  lock: true,
  text: 'Loading',
  spinner: 'el-icon-loading',
  background: 'rgba(0, 0, 0, 0.7)'
});
setTimeout(() => {
  loading.close();
}, 2000)

但是ant-design-vue没有这样的方法,要想实现全局loading就必须在最外层添加一个spin组件。

// App.vue
<a-spin :spinning="spinning" tip="Loading...">
  <a-layout id="components-layout">
		...
  </a-layout>
</a-spin>

这样的话,就需要spinning作为一个全局变量,在任何组件的任何地方,请求发起与结束之后改变该变量,vuex最适合这样的功能

// strore/index.js
export default createStore({
  state: {
    // 全局loading
    spinning: false,
  },
  mutations: {
  	// 全局loading
    changeSpinning(state, load) {
      state.spinning = load
    },
  }
 }

接着,改造我原来的axios代码,另外,我希望在成功或失败之后有一个提示,那么,只需要将element-ui的message组件换成ant-design-vue的就好了

...
import store from "@/store";
import { message } from "ant-design-vue";
...

function $http(options, success, failure) {
  if (!!options.loading && options.loading) {
    store.commit("changeSpinning", true);
  }
  ...
  
  return $axios({...})
  	.then(...)
	.catch(err => {
		...
		message.error({
			content: err.message,
			duration: 5
	  	});
	})
}

这里我很想吐槽一下,element-ui的提示框有关闭按钮的配置,为什么ant-design-vue的提示框居然不提供这样的配置啊,我觉得这是一个很普遍的需求吧。然后在组件中调用$http方法看到错误提示说明添加成功了。

关于侧边栏的一点遗憾

关于侧边栏,这里我说实话,卡住了。侧边栏的图标我希望使用ant-design原生的图标,但是2.0的ant-design的图标全部改成了组件引入的调用形式,我想了半天不知道怎样把组件图标添加到路由中带过来。所以退而求其次,我这里使用了font-awesome了。但是如果你直接使用第三方图标你就会掉到另一个坑里,就是收缩侧边栏的时候,文字还是在的

这一点其实比较好办,你只需要F12看一下官网demo的效果就会发现,在生成的html中图标这里添加了一个anticon 的样式类名 所以,你只需要给自己添加的font-awesome图标上也加一个这样的类名就达到了“欺骗ant-design”的效果了/逃

// sider/SubMenu.vue
<template v-slot:title>
  <span v-if="menuInfo.meta.icon" class="anticon"><i :class="menuInfo.meta.icon"></i></span>
  <span>{{ menuInfo.meta.title }}</span>
</template>

如果有朋友知道这里怎样把ant-design-vue的原生图标加进去的方法欢迎告诉我,多谢,多谢。

总结

大体上这就是我粗略学习一下vue3.0和ant-design-vue2.0的感触吧,主要还是在于熟悉语法和掌握基础使用上。等过段时间,生态成熟了,世面上就会有很多不错的vue3.0的UI框架和后台管理脚手架任我们选择了,共同期待吧。