本篇工作总结,如果对您有帮助,看下就行,半公开。请不要转载,当然我写的一般都是很基础的内容,也没什么转载的必要
最近写代码过程中遇到了好多的bug和警告,本来写的还不成气候,不太适合记录。可是五一假期实在是太近了,再不记,恐怕假期后会忘光光。所以还是简单记录下吧。代码可能会有错误或疏漏,如有发现,还请指正。
下面一段是废话,可以略过。
下面我写的后台管理系统的页面标签功能,就是点击导航栏出现页签,点击页签会切换页面。这个后台管理系统大部分的代码都是抄的(咳,借鉴??!)若依的vue2,vue3以及Arco Design(UI框架)的内容,所以可能有的人会看上去好眼熟的。一开始不知道也没查若依有vue3版本,后来知道了,发现适配的是vue 3.0。虽然vue 3.0挺好的,但是现在vue 3.2版本毕竟出来了,而且简化了composition API的写法,因此我还是打算继续攒一套自己的后台管理系统。本项目是vue3+vue-router4+pinia2+Arco Design UI等构成。
总结:
1、一定要注意各种属性的正确拼写,不然就会产生暴击。
2、router-link去掉tag标签,采用v-slot的写法,具体的请参照下方代码
3、这是一个警告:Extraneous non-emits event listeners (contextmenu,mouseup) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option.
这里主要出了两个问题,我先总结下:一是:vue3 @click.属性=function,属性被移除了一些。二是router-link的@contextmenu.prevent也不生效,这个我是采用UI的右键事件和window监听右键事件的方式修改bug的。
4、这个问题是部署的时候出现的,访问首页的时候,页面空白,并且控制台一直在循环输出找不到refs的属性。于是开始苦逼的找原因,后来发现问题出在router-link标签的ref="tag"这里,再进一步锁定moveToCurrentTag方法。最后解决方式是将获取tags挪到了nextTick里。详见下方代码
下面是啰嗦的一些废话,可以跳过。
具体什么意思,自行百度翻译或者谷歌翻译或者自己翻译下吧。但我看这个的时候是有点崩的。开玩笑,我搜遍了整个项目都没有用mouseup方法!!!后来将@click.后面的关键字去掉了,(具体是哪个我忘了),然后mouseup的警告消失。这是因为vue3拿掉了一些关键字,具体的我只记得一个native,因为在vue3中获取。。。。。(后面的读API吧,我忘记了)
下方代码是vue2版本,可参考比对。
<router-link v-for="tag in visitedViews" ref="tag" :key="tag.path" :class="isActive(tag)?'active':''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" tag="span" class="tags-view-item" :style="activeStyle(tag)"
@click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''" @contextmenu.prevent.native="openMenu(tag,$event)">
{{ setTitle (tag) }}
<span v-if="!isAffix(tag)" class="el-icon-close"
@click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
接下来就是contextmenu,这个是鼠标右击事件。我遇到的问题就是@contextmenu.prevent无效,阻止不了浏览器的原始事件。当然如果您在使用的时候可以生效,就可以略过本文了。在网上搜了好久,知道有一个插件可以解决。但是我发现我抄的的代码中没有这个依赖。于是我就开始了为什么,为什么别人的可以,我不可以,为什么,为什么。然后开始google和百度,结果都是一份答案各种传抄。
最后的我发现我用的UI框架支持右键点击自定义菜单,于是我被救了。
很快,我就又被淹了。我发现我的右键菜单是有判断条件的,不一定要显示全部,但我的按钮又是循环出来的。于是我又开始了各种失败的尝试。其实这个一直都有一个方法,把右键菜单放进循环里也可,但是我不愿意这样做。即使可以增加判断条件,也不愿意的那种!!!,倔强girl在线不服。我就又想了想,我为什么不试下监听contextmenu事件呢,最后bug解决了。
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper">
<a-dropdown trigger="contextMenu" align-point>
<div class="tags-view-wrapper">
<router-link v-for="tag in visitedViews" ref="tag" v-slot="{ isActive, navigate }" :key="tag.path" custom :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }">
<span class="arco-tag arco-tag-size-medium arco-tag-checked" :class="isActive ? 'active' : ''" @click="navigate()">
<!-- <span v-if="!tagsView.isAffix(tag)" class="starBtn">
<icon-star-fill />
</span> -->
{{ tagsView.setTitle(tag) }}
<span v-if="!tagsView.isAffix(tag)" class="closeBtn" @click.prevent.stop="tagsView.closeSelectedTag(tag)">
<icon-close-circle />
</span>
</span>
</router-link>
</div>
<template #content>
<a-doption @click="tagsView.refreshSelectedTag(tagsView.selectedTag)">刷新页面</a-doption>
<a-doption v-if="!tagsView.isAffix(tagsView.selectedTag)" @click="tagsView.closeSelectedTag(tagsView.selectedTag)">关闭当前</a-doption>
<a-doption @click="tagsView.closeOthersTags">关闭其他</a-doption>
<a-doption v-if="!tagsView.isLastView()" @click="tagsView.closeRightTags">关闭右侧</a-doption>
<a-doption @click="tagsView.closeAllTags(tagsView.selectedTag)">关闭所有</a-doption>
</template>
</a-dropdown>
</scroll-pane>
</div>
</template>
<script setup>
import { reactive, ref, computed, watch, provide, nextTick, onMounted, onBeforeUnmount } from 'vue';
import path from 'path';
import { useRoute, useRouter } from 'vue-router';
import { useTagStore, useRouterStore } from '@/store';
import ScrollPane from './ScrollPane';
import { getLog } from '@/api/tagsView';
const $route = useRoute();
const $router = useRouter();
const tagStore = useTagStore();
const routerStore = useRouterStore();
const routeList = routerStore.routes;
const visitedViews = computed(() => tagStore.visitedViews);
const scrollPaneRef = ref(null);
const tag = ref(null);
const PARENT_PROVIDE = 'parentProvide';
provide(PARENT_PROVIDE, tag);
const tagsView = reactive({
visible: false,
selectedTag: {},
affixTags: [],
isActive(route) {
return route.path === $route.path;
// return route.fullPath === this.$route.fullPath
},
setTitle(tag) {
if (tag.title.includes('新建策略')) {
if (tag.query.type && tag.query.type == '编辑') {
return '编辑策略-' + tag.query.id;
} else {
return tag.title;
}
}
return tag.title;
},
isAffix(tag) {
return tag.meta && tag.meta.affix;
},
filterAffixTags(routes, basePath = '/') {
let tags = [];
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta },
});
}
if (route.children) {
const tempTags = tagsView.filterAffixTags(route.children, route.path);
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags];
}
}
});
return tags;
},
initTags() {
const affixTags = (tagsView.affixTags = tagsView.filterAffixTags(routeList));
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
tagStore.addVisitedView(tag);
}
}
},
addTags() {
const { name } = $route;
if (name) {
getLog({ menuName: $route.meta.title }).then((res) => {
if (res.code == 200) return;
});
tagStore.addView($route);
}
return false;
},
moveToCurrentTag() {
nextTick(() => {
const tags = tag.value;
if (tags === null || tags === undefined) {
return;
}
for (const tag of tags) {
if (tag.to.path === $route.path) {
scrollPaneRef.value.moveToTarget(tag);
// when query is different then update
if (tag.to.fullPath !== $route.fullPath) {
tagStore.updateVisitedView($route);
}
break;
}
}
});
},
refreshSelectedTag(view) {
tagStore.delCachedView(view).then(() => {
const { fullPath } = view;
nextTick(() => {
$router.replace({
path: '/redirect' + fullPath,
});
});
});
},
closeSelectedTag(view) {
tagStore.delView(view).then(({ visitedViews }) => {
if (tagsView.isActive(view)) {
tagsView.toLastView(visitedViews, view);
}
});
},
closeRightTags() {
tagStore.delRightTags(tagsView.selectedTag).then((visitedViews) => {
// eslint-disable-next-line arrow-parens
if (!visitedViews.find((i) => i.fullPath === $route.fullPath)) {
tagsView.toLastView(visitedViews);
}
});
},
closeOthersTags() {
$router.push(tagsView.selectedTag).catch(() => {});
tagStore.delOthersViews(tagsView.selectedTag).then(() => {
tagsView.moveToCurrentTag();
});
},
closeAllTags(view) {
tagStore.delAllViews().then(({ visitedViews }) => {
// eslint-disable-next-line arrow-parens
if (tagsView.affixTags.some((tag) => tag.path === $route.path)) {
return;
}
tagsView.toLastView(visitedViews, view);
});
},
toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0];
if (latestView) {
$router.push(latestView.fullPath);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
$router.replace({ path: '/redirect' + view.fullPath });
} else {
$router.push('/');
}
}
},
isLastView() {
try {
return tagsView.selectedTag.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
} catch (err) {
return false;
}
},
openMenu(tag) {
// eslint-disable-next-line arrow-parens
tagsView.selectedTag = visitedViews.value.filter((item) => item.fullPath === tag)[0];
},
closeMenu() {
tagsView.visible = false;
},
handleScroll() {
tagsView.closeMenu();
},
});
tagsView.initTags();
tagsView.addTags();
watch(
() => $route.fullPath,
() => {
tagsView.addTags();
tagsView.moveToCurrentTag();
},
{ immediate: true }
);
watch(
() => tagsView.visible,
(val) => {
if (val) {
window.addEventListener('click', tagsView.closeMenu);
} else {
window.removeEventListener('click', tagsView.closeMenu);
}
},
{ immediate: true }
);
const menuRightClick = (e) => {
const vueDom = e.target.__vueParentComponent;
if (vueDom && vueDom.ctx && vueDom.ctx.to) {
console.log('取消/监听鼠标右击事件,关闭功已实现,滚动条未测试,页数还不够');
const fullPath = e.target.__vueParentComponent.ctx.to.fullPath;
tagsView.openMenu(fullPath);
}
};
onMounted(() => {
window.addEventListener('contextmenu', menuRightClick, false);
window.addEventListener('scroll', tagsView.handleScroll, false);
});
onBeforeUnmount(() => {
window.removeEventListener('contextmenu', menuRightClick, false);
window.removeEventListener('scroll', tagsView.handleScroll, false);
});
</script>
<style lang="less" scoped>
.tags-view-container {
width: 100%;
padding: 6px 0;
background-color: var(--color-bg-2);
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 12%), 0 0 3px 0 rgb(0 0 0 / 12%);
position: fixed;
top: 60px;
z-index: 100;
.tags-view-wrapper {
.arco-tag {
margin-left: 10px;
position: relative;
cursor: pointer;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: rgb(var(--primary-6));
color: var(--color-white);
border-color: rgb(var(--primary-6));
.starBtn {
position: relative;
left: 0;
top: 0;
}
.closeBtn {
position: relative;
right: -2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>