最近忙里偷闲,看了下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.router获取路由信息和操作路由,在4.0的路由版本中这两个方法被放在了useRoute和useRouter里,所以要监听路由的变化则需要监听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" });
},
顺便一提,如果你按照老方法,在created或mounted使用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框架和后台管理脚手架任我们选择了,共同期待吧。