feat: 多tag实现

dev_1.0.0
xiangcongshuai 1 year ago
parent 4aa406a531
commit 7fac7c019d

@ -4,7 +4,7 @@
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,
"MultiTagsCache": false, "MultiTagsCache": false,
"KeepAlive": false, "KeepAlive": true,
"Layout": "vertical", "Layout": "vertical",
"Theme": "default", "Theme": "default",
"DarkMode": false, "DarkMode": false,
@ -12,7 +12,7 @@
"Weak": false, "Weak": false,
"HideTabs": true, "HideTabs": true,
"SidebarStatus": true, "SidebarStatus": true,
"EpThemeColor": "#4287ff", "EpThemeColor": "#0052D9",
"ShowLogo": true, "ShowLogo": true,
"ShowModel": "smart", "ShowModel": "smart",
"MenuArrowIconNoTransition": true, "MenuArrowIconNoTransition": true,

@ -0,0 +1,5 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="guanbi">
<path id="guanbi_2" d="M6.07143 5L9.78571 1.28571C10.0714 1 10.0714 0.5 9.78571 0.214286C9.5 -0.0714286 9 -0.0714286 8.71429 0.214286L5 3.92857L1.28571 0.214286C1 -0.0714286 0.5 -0.0714286 0.214286 0.214286C-0.0714286 0.5 -0.0714286 1 0.214286 1.28571L3.92857 5L0.214286 8.71429C-0.0714286 9 -0.0714286 9.5 0.214286 9.78571C0.5 10.0714 1 10.0714 1.28571 9.78571L5 6.07143L8.71429 9.78571C9 10.0714 9.5 10.0714 9.78571 9.78571C10.0714 9.5 10.0714 9 9.78571 8.71429L6.07143 5Z" fill="#999999"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 617 B

@ -8,6 +8,8 @@ import MeunIcon from "./meunIcon.vue";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { getParentPaths } from "@/router/utils"; import { getParentPaths } from "@/router/utils";
import { useTabsStore } from "@/store/modules/tabs";
import { da } from "element-plus/es/locale";
const { route } = useNav(); const { route } = useNav();
const router = useRouter(); const router = useRouter();
const defaultActive = computed(() => const defaultActive = computed(() =>
@ -109,8 +111,23 @@ watch(
deep: true deep: true
} }
); );
const changeRouter = val => { const tabs = useTabsStore();
router.push(val); const changeRouter = item => {
router.push(item.path);
if (item.path === "/home") return;
let flag = false;
for (const data of tabs.list) {
if (item.path === data.path) {
flag = true;
}
}
if (!flag) {
tabs.setTabsItem({
name: item.name,
title: item.meta.title,
path: item.path
});
}
}; };
</script> </script>
@ -138,7 +155,7 @@ const changeRouter = val => {
<template #dropdown> <template #dropdown>
<div class="drop_meun"> <div class="drop_meun">
<div <div
@click="changeRouter(items.path)" @click="changeRouter(items)"
class="drop_meun_item" class="drop_meun_item"
v-for="(items, i) in item.children" v-for="(items, i) in item.children"
:key="i" :key="i"
@ -149,7 +166,7 @@ const changeRouter = val => {
</template> </template>
</el-dropdown> </el-dropdown>
<div v-else> <div v-else>
<div class="meun_item" @click="changeRouter(item.path)"> <div class="meun_item" @click="changeRouter(item)">
<MeunIcon <MeunIcon
:isActived="getIsActived(item.path)" :isActived="getIsActived(item.path)"
:meunIconIndex="item.meta.icon" :meunIconIndex="item.meta.icon"

@ -64,22 +64,13 @@ const transitionMain = defineComponent({
<backTop /> <backTop />
</el-backtop> </el-backtop>
<transitionMain :route="route"> <transitionMain :route="route">
<keep-alive <keep-alive>
v-if="keepAlive"
:include="usePermissionStoreHook().cachePageList"
>
<component <component
:is="Component" :is="Component"
:key="route.fullPath" :key="route.fullPath"
class="main-content" class="main-content"
/> />
</keep-alive> </keep-alive>
<component
v-else
:is="Component"
:key="route.fullPath"
class="main-content"
/>
</transitionMain> </transitionMain>
</el-scrollbar> </el-scrollbar>
</template> </template>
@ -91,7 +82,7 @@ const transitionMain = defineComponent({
.app-main { .app-main {
position: relative; position: relative;
width: 100%; width: 100%;
height: calc(100vh - 96px); /* height: calc(100vh - 96px); */
overflow-x: hidden; overflow-x: hidden;
/* background: #fff; */ /* background: #fff; */

@ -1,295 +0,0 @@
@keyframes schedule-in-width {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes schedule-out-width {
from {
width: 100%;
}
to {
width: 0;
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes close {
from {
transform: translate(-50%, -50%);
}
to {
transform: translate(0, -50%);
}
}
.tags-view {
position: relative;
display: flex;
align-items: center;
width: 100%;
font-size: 14px;
color: var(--el-text-color-primary);
background: #fff;
box-shadow: 0 0 1px #888;
.scroll-item {
position: relative;
display: inline-block;
height: 28px;
padding: 0 6px;
margin-right: 4px;
line-height: 28px;
cursor: pointer;
border-radius: 3px 3px 0 0;
box-shadow: 0 0 1px #888;
transition: all 0.4s;
.el-icon-close {
position: absolute;
top: 50%;
font-size: 10px;
color: var(--el-color-primary);
cursor: pointer;
transition: font-size 0.2s;
transform: translate(-50%, -50%);
&:hover {
font-size: 13px;
color: #fff;
background: #b4bccc;
border-radius: 50%;
}
}
&.is-closable:not(:first-child) {
&:hover {
padding-right: 18px;
&:not(.is-active) {
.el-icon-close {
animation: close 200ms ease-in forwards;
}
}
}
}
}
a {
padding: 0 4px;
color: var(--el-text-color-primary);
text-decoration: none;
}
.scroll-container {
position: relative;
flex: 1;
padding: 5px 0;
overflow: hidden;
white-space: nowrap;
.tab {
position: relative;
float: left;
overflow: visible;
white-space: nowrap;
list-style: none;
transition: transform 0.5s ease-in-out;
.scroll-item {
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:nth-child(1) {
margin-left: 5px;
}
}
}
}
/* 右键菜单 */
.contextmenu {
position: absolute;
padding: 5px 0;
margin: 0;
font-size: 13px;
font-weight: normal;
color: var(--el-text-color-primary);
white-space: nowrap;
list-style-type: none;
background: #fff;
border-radius: 4px;
outline: 0;
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
li {
display: flex;
align-items: center;
width: 100%;
padding: 7px 12px;
margin: 0;
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
svg {
display: block;
margin-right: 0.5em;
}
}
}
}
.el-dropdown-menu {
li {
display: flex;
align-items: center;
width: 100%;
margin: 0;
cursor: pointer;
svg {
display: block;
margin-right: 0.5em;
}
}
}
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
:deep(.el-dropdown-menu__item) i {
margin-right: 10px;
}
:deep(.el-dropdown-menu__item--divided) {
margin: 1px 0;
}
.el-dropdown-menu__item--divided::before {
margin: 0;
}
.el-dropdown-menu__item.is-disabled {
cursor: not-allowed;
}
.scroll-item.is-active {
position: relative;
color: #fff;
&:not(:first-child) {
padding-right: 18px;
}
.el-icon-close {
transform: translate(0, -50%);
}
a {
color: var(--el-color-primary) !important;
}
}
.arrow-left,
.arrow-right,
.arrow-down {
position: relative;
width: 40px;
height: 38px;
color: var(--el-text-color-primary);
svg {
position: absolute;
left: 50%;
width: 20px;
height: 20px;
transform: translate(-50%, 50%);
}
}
.arrow-left {
box-shadow: 5px 0 5px -6px #ccc;
&:hover {
cursor: w-resize;
}
}
.arrow-right {
border-right: 0.5px solid #ccc;
box-shadow: -5px 0 5px -6px #ccc;
&:hover {
cursor: e-resize;
}
}
/* 卡片模式下鼠标移入显示蓝色边框 */
.card-in {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
/* 卡片模式下鼠标移出隐藏蓝色边框 */
.card-out {
color: #666;
border: none;
a {
color: #666;
}
}
/* 灵动模式 */
.schedule-active {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--el-color-primary);
}
/* 灵动模式下鼠标移入显示蓝色进度条 */
.schedule-in {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--el-color-primary);
animation: schedule-in-width 200ms ease-in;
}
/* 灵动模式下鼠标移出隐藏蓝色进度条 */
.schedule-out {
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background: var(--el-color-primary);
animation: schedule-out-width 200ms ease-in;
}

@ -1,611 +1,143 @@
<script setup lang="ts"> <template>
import { emitter } from "@/utils/mitt"; <div class="tabs-container">
import { RouteConfigs } from "../../types"; <div class="tabs-content">
import { useTags } from "../../hooks/useTag"; <div @click="goHome" class="home">
import { routerArrays } from "@/layout/types"; <homeIcon />
import { handleAliveRoute, getTopMenu } from "@/router/utils"; </div>
import { useSettingStoreHook } from "@/store/modules/settings"; <div
import { useResizeObserver, useFullscreen } from "@vueuse/core"; @click="clickTabls(item)"
import { isEqual, isAllEmpty, debounce } from "@pureadmin/utils"; class="tabs-item"
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; :class="[activePath === item.path ? 'actived' : '']"
import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue"; v-for="(item, index) in tabs.list"
:key="index"
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill"; >
import Fullscreen from "@iconify-icons/ri/fullscreen-fill"; <span>{{ item.title }}</span>
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line"; <closeIcon @click.stop="closeTabs(item.path)" />
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line"; </div>
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line"; </div>
import CloseBold from "@iconify-icons/ep/close-bold"; </div>
</template>
const {
route,
router,
visible,
showTags,
instance,
multiTags,
tagsViews,
buttonTop,
buttonLeft,
showModel,
translateX,
pureSetting,
activeIndex,
getTabStyle,
iconIsActive,
linkIsActive,
currentSelect,
scheduleIsActive,
getContextMenuStyle,
closeMenu,
onMounted,
onMouseenter,
onMouseleave,
onContentFullScreen
} = useTags();
const tabDom = ref();
const containerDom = ref();
const scrollbarDom = ref();
const isShowArrow = ref(false);
const topPath = getTopMenu()?.path;
const { VITE_HIDE_HOME } = import.meta.env;
const { isFullscreen, toggle } = useFullscreen();
const dynamicTagView = async () => { <script setup lang="ts">
await nextTick(); import { ref, watch } from "vue";
const index = multiTags.value.findIndex(item => { import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
if (!isAllEmpty(route.query)) { import { useTabsStore } from "@/store/modules/tabs";
return isEqual(route.query, item.query); import homeIcon from "@/assets/svg/home/home.svg";
} else if (!isAllEmpty(route.params)) { import closeIcon from "@/assets/svg/home/close.svg";
return isEqual(route.params, item.params); import { onMounted } from "vue";
} else { const route = useRoute();
return route.path === item.path; const router = useRouter();
} const activePath = ref(route.fullPath);
const tabs = useTabsStore();
//
const setTags = (route: any) => {
const isExist = tabs.list.some(item => {
return item.path === route.fullPath;
}); });
moveToView(index); if (!isExist) {
}; tabs.setTabsItem({
name: route.name,
const moveToView = async (index: number): Promise<void> => { title: route.meta.title,
await nextTick(); path: route.fullPath
const tabNavPadding = 10; });
if (!instance.refs["dynamic" + index]) return;
const tabItemEl = instance.refs["dynamic" + index][0];
const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
//
const scrollbarDomWidth = scrollbarDom.value
? scrollbarDom.value?.offsetWidth
: 0;
//
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
scrollbarDomWidth <= tabDomWidth
? (isShowArrow.value = true)
: (isShowArrow.value = false);
if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
translateX.value = 0;
} else if (tabItemElOffsetLeft < -translateX.value) {
//
translateX.value = -tabItemElOffsetLeft + tabNavPadding;
} else if (
tabItemElOffsetLeft > -translateX.value &&
tabItemElOffsetLeft + tabItemOffsetWidth <
-translateX.value + scrollbarDomWidth
) {
//
translateX.value = Math.min(
0,
scrollbarDomWidth -
tabItemOffsetWidth -
tabItemElOffsetLeft -
tabNavPadding
);
} else {
//
translateX.value = -(
tabItemElOffsetLeft -
(scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth)
);
} }
}; };
setTags(route);
const handleScroll = (offset: number): void => { onBeforeRouteUpdate(to => {
const scrollbarDomWidth = scrollbarDom.value setTags(to);
? scrollbarDom.value?.offsetWidth });
: 0; const goHome = () => {
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0; router.push("/home");
if (offset > 0) {
translateX.value = Math.min(0, translateX.value + offset);
} else {
if (scrollbarDomWidth < tabDomWidth) {
if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
translateX.value = Math.max(
translateX.value + offset,
scrollbarDomWidth - tabDomWidth
);
}
} else {
translateX.value = 0;
}
}
}; };
//
function dynamicRouteTag(value: string): void { const closeAll = () => {
const hasValue = multiTags.value.some(item => { tabs.clearTabs();
return item.path === value; router.push("/");
}); };
//
function concatPath(arr: object[], value: string) { const closeOther = () => {
if (!hasValue) { const curItem = tabs.list.filter(item => {
arr.forEach((arrItem: any) => { return item.path === route.fullPath;
if (arrItem.path === value || arrItem.path === value) {
useMultiTagsStoreHook().handleTags("push", {
path: value,
meta: arrItem.meta,
name: arrItem.name
});
} else {
if (arrItem.children && arrItem.children.length > 0) {
concatPath(arrItem.children, value);
}
}
});
}
}
concatPath(router.options.routes as any, value);
}
/** 刷新路由 */
function onFresh() {
const { fullPath, query } = unref(route);
router.replace({
path: "/redirect" + fullPath,
query
});
handleAliveRoute(route as ToRouteType, "refresh");
}
function deleteDynamicTag(obj: any, current: any, tag?: string) {
const valueIndex: number = multiTags.value.findIndex((item: any) => {
if (item.query) {
if (item.path === obj.path) {
return item.query === obj.query;
}
} else if (item.params) {
if (item.path === obj.path) {
return item.params === obj.params;
}
} else {
return item.path === obj.path;
}
}); });
tabs.closeTabsOther(curItem);
const spliceRoute = ( };
startIndex?: number, const handleTags = (command: string) => {
length?: number, switch (command) {
other?: boolean case "current":
): void => { //
if (other) { tabs.closeCurrentTag({
useMultiTagsStoreHook().handleTags("equal", [ $router: router,
VITE_HIDE_HOME === "false" ? routerArrays[0] : toRaw(getTopMenu()), $route: route
obj
]);
} else {
useMultiTagsStoreHook().handleTags("splice", "", {
startIndex,
length
}) as any;
}
dynamicTagView();
};
if (tag === "other") {
spliceRoute(1, 1, true);
} else if (tag === "left") {
spliceRoute(1, valueIndex - 1);
} else if (tag === "right") {
spliceRoute(valueIndex + 1, multiTags.value.length);
} else {
//
spliceRoute(valueIndex, 1);
}
const newRoute = useMultiTagsStoreHook().handleTags("slice");
if (current === route.path) {
// tagtag
if (tag === "left") return;
if (newRoute[0]?.query) {
router.push({ name: newRoute[0].name, query: newRoute[0].query });
} else if (newRoute[0]?.params) {
router.push({ name: newRoute[0].name, params: newRoute[0].params });
} else {
router.push({ path: newRoute[0].path });
}
} else {
if (!multiTags.value.length) return;
if (multiTags.value.some(item => item.path === route.path)) return;
if (newRoute[0]?.query) {
router.push({ name: newRoute[0].name, query: newRoute[0].query });
} else if (newRoute[0]?.params) {
router.push({ name: newRoute[0].name, params: newRoute[0].params });
} else {
router.push({ path: newRoute[0].path });
}
}
}
function deleteMenu(item, tag?: string) {
deleteDynamicTag(item, item.path, tag);
handleAliveRoute(route as ToRouteType);
}
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
if (item && item.disabled) return;
let selectTagRoute;
if (selectRoute) {
selectTagRoute = {
path: selectRoute.path,
meta: selectRoute.meta,
name: selectRoute.name,
query: selectRoute?.query,
params: selectRoute?.params
};
} else {
selectTagRoute = { path: route.path, meta: route.meta };
}
//
switch (key) {
case 0:
//
onFresh();
break;
case 1:
//
deleteMenu(selectTagRoute);
break;
case 2:
//
deleteMenu(selectTagRoute, "left");
break;
case 3:
//
deleteMenu(selectTagRoute, "right");
break;
case 4:
//
deleteMenu(selectTagRoute, "other");
break;
case 5:
//
useMultiTagsStoreHook().handleTags("splice", "", {
startIndex: 1,
length: multiTags.value.length
}); });
router.push(topPath);
handleAliveRoute(route as ToRouteType);
break; break;
case 6: case "all":
// closeAll();
toggle();
setTimeout(() => {
if (isFullscreen.value) {
tagsViews[6].icon = ExitFullscreen;
tagsViews[6].text = "退出全屏";
} else {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = "全屏";
}
}, 100);
break; break;
case 7:
//
onContentFullScreen();
setTimeout(() => {
if (pureSetting.hiddenSideBar) {
tagsViews[7].icon = ExitFullscreen;
tagsViews[7].text = "内容区退出全屏";
} else {
tagsViews[7].icon = Fullscreen;
tagsViews[7].text = "内容区全屏";
}
}, 100);
break;
}
setTimeout(() => {
showMenuModel(route.fullPath, route.query);
});
}
function handleCommand(command: any) {
const { key, item } = command;
onClickDrop(key, item);
}
/** 触发右键中菜单的点击事件 */
function selectTag(key, item) {
onClickDrop(key, item, currentSelect.value);
}
function showMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews[v].show = value;
});
}
function disabledMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews[v].disabled = value;
});
}
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
function showMenuModel(
currentPath: string,
query: object = {},
refresh = false
) {
const allRoute = multiTags.value;
const routeLength = multiTags.value.length;
let currentIndex = -1;
if (isAllEmpty(query)) {
currentIndex = allRoute.findIndex(v => v.path === currentPath);
} else {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
}
showMenus(true);
if (refresh) {
tagsViews[0].show = true;
}
/** case "other":
* currentIndex为1时左侧的菜单顶级菜单则不显示关闭左侧标签页 closeOther();
* 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页 break;
*/
if (currentIndex === 1 && routeLength !== 2) {
//
tagsViews[2].show = false;
Array.of(1, 3, 4, 5).forEach(v => {
tagsViews[v].disabled = false;
});
tagsViews[2].disabled = true;
} else if (currentIndex === 1 && routeLength === 2) {
disabledMenus(false);
//
Array.of(2, 3, 4).forEach(v => {
tagsViews[v].show = false;
tagsViews[v].disabled = true;
});
} else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
//
tagsViews[3].show = false;
Array.of(1, 2, 4, 5).forEach(v => {
tagsViews[v].disabled = false;
});
tagsViews[3].disabled = true;
} else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
//
disabledMenus(true);
} else {
disabledMenus(false);
} }
} };
function openMenu(tag, e) { const clickTabls = (item: any) => {
closeMenu(); router.push(item.path);
if (tag.path === topPath) { };
// const closeTabs = (path: string) => {
showMenus(false); const index = tabs.list.findIndex(item => item.path === path);
tagsViews[0].show = true; tabs.delTabsItem(index);
} else if (route.path !== tag.path && route.name !== tag.name) { const item = tabs.list[index] || tabs.list[index - 1];
//
tagsViews[0].show = false;
showMenuModel(tag.path, tag.query);
} else if (
// eslint-disable-next-line no-dupe-else-if
multiTags.value.length === 2 &&
route.path !== tag.path
) {
showMenus(true);
//
tagsViews[4].show = false;
} else if (route.path === tag.path) {
//
showMenuModel(tag.path, tag.query, true);
}
currentSelect.value = tag; router.push(item ? item.path : "/home");
const menuMinWidth = 105; };
const offsetLeft = unref(containerDom).getBoundingClientRect().left;
const offsetWidth = unref(containerDom).offsetWidth;
const maxLeft = offsetWidth - menuMinWidth;
const left = e.clientX - offsetLeft + 5;
if (left > maxLeft) {
buttonLeft.value = maxLeft;
} else {
buttonLeft.value = left;
}
useSettingStoreHook().hiddenSideBar
? (buttonTop.value = e.clientY)
: (buttonTop.value = e.clientY - 40);
nextTick(() => {
visible.value = true;
});
}
/** 触发tags标签切换 */ watch(
function tagOnClick(item) { () => route.fullPath,
const { name, path } = item; (newVal, oldVal) => {
if (name) { activePath.value = newVal;
if (item.query) {
router.push({
name,
query: item.query
});
} else if (item.params) {
router.push({
name,
params: item.params
});
} else {
router.push({ name });
}
} else {
router.push({ path });
} }
// showMenuModel(item?.path, item?.query); );
}
watch(route, () => {
activeIndex.value = -1;
dynamicTagView();
});
watch(isFullscreen, () => {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = "全屏";
});
onMounted(() => { onMounted(() => {
if (!instance) return; console.log("222", tabs.list);
//
showMenuModel(route.fullPath);
//
emitter.on("tagViewsChange", (key: any) => {
if (unref(showTags as any) === key) return;
(showTags as any).value = key;
});
//
emitter.on("tagViewsShowModel", key => {
showModel.value = key;
});
//
emitter.on("changLayoutRoute", indexPath => {
dynamicRouteTag(indexPath);
setTimeout(() => {
showMenuModel(indexPath);
});
});
useResizeObserver(
scrollbarDom,
debounce(() => dynamicTagView())
);
});
onBeforeUnmount(() => {
// `tagViewsChange``tagViewsShowModel``changLayoutRoute`
emitter.off("tagViewsChange");
emitter.off("tagViewsShowModel");
emitter.off("changLayoutRoute");
}); });
</script> </script>
<template>
<div ref="containerDom" class="tags-view" v-if="!showTags">
<span v-show="isShowArrow" class="arrow-left">
<IconifyIconOffline :icon="ArrowLeftSLine" @click="handleScroll(200)" />
</span>
<div ref="scrollbarDom" class="scroll-container">
<div class="tab select-none" ref="tabDom" :style="getTabStyle">
<div
:ref="'dynamic' + index"
v-for="(item, index) in multiTags"
:key="index"
:class="[
'scroll-item is-closable',
linkIsActive(item),
route.path === item.path && showModel === 'card'
? 'card-active'
: ''
]"
@contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(index)"
@mouseleave.prevent="onMouseleave(index)"
@click="tagOnClick(item)"
>
<router-link
:to="item.path"
class="dark:!text-text_color_primary dark:hover:!text-primary"
>
{{ item.meta.title }}
</router-link>
<span
v-if="
iconIsActive(item, index) ||
(index === activeIndex && index !== 0)
"
class="el-icon-close"
@click.stop="deleteMenu(item)"
>
<IconifyIconOffline :icon="CloseBold" />
</span>
<div
:ref="'schedule' + index"
v-if="showModel !== 'card'"
:class="[scheduleIsActive(item)]"
/>
</div>
</div>
</div>
<span v-show="isShowArrow" class="arrow-right">
<IconifyIconOffline :icon="ArrowRightSLine" @click="handleScroll(-200)" />
</span>
<!-- 右键菜单按钮 -->
<transition name="el-zoom-in-top">
<ul
v-show="visible"
:key="Math.random()"
:style="getContextMenuStyle"
class="contextmenu"
>
<div
v-for="(item, key) in tagsViews.slice(0, 6)"
:key="key"
style="display: flex; align-items: center"
>
<li v-if="item.show" @click="selectTag(key, item)">
<IconifyIconOffline :icon="item.icon" />
{{ item.text }}
</li>
</div>
</ul>
</transition>
<!-- 右侧功能按钮 -->
<el-dropdown
trigger="click"
placement="bottom-end"
@command="handleCommand"
>
<span class="arrow-down">
<IconifyIconOffline :icon="ArrowDown" class="dark:text-white" />
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(item, key) in tagsViews"
:key="key"
:command="{ key, item }"
:divided="item.divided"
:disabled="item.disabled"
>
<IconifyIconOffline :icon="item.icon" />
{{ item.text }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
@import url("./index.scss"); .tabs-container {
height: 53px;
padding: 0 80px;
width: 100%;
margin-bottom: 16px;
.tabs-content {
height: 53px;
width: 100%;
background: #ffffff;
border-radius: 8px 8px 8px 8px;
display: flex;
.home {
display: flex;
align-items: center;
justify-content: center;
width: 68px;
cursor: pointer;
border-right: 1px solid #eaeaea;
}
.tabs-item {
display: flex;
align-items: center;
padding: 0 24px;
border-right: 1px solid #eaeaea;
font-size: 16px;
color: #333333;
cursor: pointer;
span {
margin-right: 16px;
}
}
.actived {
background: #f5f5f5;
border-bottom: 1px solid #0052d9;
}
}
}
</style> </style>

@ -11,7 +11,7 @@ import { ref, reactive, computed, onMounted, onBeforeMount } from "vue";
import AppHeader from "./components/appHeader/index.vue"; import AppHeader from "./components/appHeader/index.vue";
import appMain from "./components/appMain.vue"; import appMain from "./components/appMain.vue";
import tag from "./components/tag/index.vue";
const appWrapperRef = ref(); const appWrapperRef = ref();
const pureSetting = useSettingStoreHook(); const pureSetting = useSettingStoreHook();
const { $storage } = useGlobal<GlobalPropertiesApi>(); const { $storage } = useGlobal<GlobalPropertiesApi>();
@ -56,6 +56,7 @@ onBeforeMount(() => {
<div> <div>
<!-- 主体内容 --> <!-- 主体内容 -->
<AppHeader /> <AppHeader />
<tag />
<app-main /> <app-main />
</div> </div>
</div> </div>
@ -94,4 +95,18 @@ onBeforeMount(() => {
.re-screen { .re-screen {
margin-top: 12px; margin-top: 12px;
} }
.main-container {
position: relative;
min-width: 1500px;
height: 100vh;
min-height: 100%;
/* main-content 属性动画 */
transition: margin-left var(--pure-transition-duration);
.el-scrollbar__wrap {
height: 100%;
overflow: auto;
}
}
</style> </style>

@ -4,13 +4,13 @@ const { VITE_HIDE_HOME } = import.meta.env;
export const routerArrays: Array<RouteConfigs> = export const routerArrays: Array<RouteConfigs> =
VITE_HIDE_HOME === "false" VITE_HIDE_HOME === "false"
? [ ? [
{ // {
path: "/welcome", // path: "/welcome",
meta: { // meta: {
title: "首页", // title: "首页",
icon: "homeFilled" // icon: "homeFilled"
} // }
} // }
] ]
: []; : [];

@ -129,8 +129,9 @@ router.beforeEach((to: ToRouteType, _from, next) => {
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) { if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
next({ path: "/error/403" }); next({ path: "/error/403" });
} }
// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面 // 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") { if (VITE_HIDE_HOME === "true" && to.fullPath === "/home") {
next({ path: "/error/404" }); next({ path: "/error/404" });
} }
if (_from?.name) { if (_from?.name) {

@ -0,0 +1,23 @@
export default {
path: "/knowledgeCentre",
redirect: "/knowledgeCentre/submission",
meta: {
title: "知识中心",
icon: 1,
rank: 5
},
children: [
{
path: "/knowledgeCentre/submission",
name: "submission",
component: () => import("@/views/knowledgeCentre/submission/index.vue"),
meta: {
title: "知识报送",
showLink: true,
showParent: true,
roles: ["admin", "common"]
}
}
]
} as RouteConfigsTable;

@ -0,0 +1,53 @@
import { defineStore } from "pinia";
interface ListItem {
name: string;
path: string;
title: string;
}
export const useTabsStore = defineStore("tabs", {
state: () => {
return {
list: <ListItem[]>[]
};
},
getters: {
show: state => {
return state.list.length > 0;
},
nameList: state => {
return state.list.map(item => item.name);
}
},
actions: {
delTabsItem(index: number) {
this.list.splice(index, 1);
},
setTabsItem(data: ListItem) {
this.list.push(data);
},
clearTabs() {
this.list = [];
},
closeTabsOther(data: ListItem[]) {
this.list = data;
},
closeCurrentTag(data: any) {
for (let i = 0, len = this.list.length; i < len; i++) {
const item = this.list[i];
if (item.path === data.$route.fullPath) {
if (i < len - 1) {
data.$router.push(this.list[i + 1].path);
} else if (i > 0) {
data.$router.push(this.list[i - 1].path);
} else {
data.$router.push("/");
}
this.list.splice(i, 1);
break;
}
}
}
}
});

@ -18,3 +18,32 @@
.html-weakness { .html-weakness {
filter: invert(80%); filter: invert(80%);
} }
.app-main-content {
margin: 0 80px;
.seach {
margin-bottom: 16px;
padding: 24px;
background-color: #fff;
border-radius: 6px 6px 6px 6px;
.seach-title {
font-size: 24px;
color: #333333;
margin-bottom: 24px;
span {
padding-left: 8px;
border-left: 6px solid #0052D9;
}
}
}
}
.el-form-item__label {
font-weight: 400;
font-size: 14px;
color: #666666;
}
.main-table {
background-color: #fff;
padding: 24px;
}

@ -0,0 +1,227 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { ArrowDown } from "@element-plus/icons-vue";
const loading = ref(false);
const seachForm = reactive({
title: "",
publishDeptName: "",
status: ""
});
const tabList = ref([
{
title: "全部",
id: ""
},
{
title: "草稿",
id: "1"
},
{
title: "待审批",
id: "2"
},
{
title: "驳回",
id: "3"
},
{
title: "通过",
id: "4"
},
{
title: "失效",
id: "5"
}
]);
const pagination = reactive({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
const columns: TableColumnList = [
{
type: "selection",
width: 55
},
{
label: "ID",
prop: "diseaseName"
},
{
label: "知识标题",
prop: "diseaseName"
},
{
label: "发文部门",
prop: "diseaseName"
},
{
label: "状态",
prop: "diseaseName"
},
{
label: "最新时间",
prop: "diseaseName"
},
{
label: "操作",
fixed: "right",
width: 350,
slot: "operation"
}
];
const dataList = ref([{}]);
const getData = async () => {
// const params = {
// pageNum: pagination.currentPage,
// pageSize: pagination.pageSize,
// diseaseName: seachForm.diseaseName,
// diseaseType: seachForm.diseaseType
// };
// const res: any = await queryPageList(params);
// dataList.value = res.data.records;
// pagination.total = res.data.total;
};
function handleSizeChange(val: number) {
pagination.pageSize = val;
getData();
}
function handleCurrentChange(val: number) {
pagination.currentPage = val;
getData();
}
const search = () => {
pagination.currentPage = 1;
pagination.pageSize = 10;
getData();
};
const reset = () => {
seachForm.diseaseName = "";
seachForm.diseaseType = "";
search();
};
const handleCommand = command => {
console.log("Command", command);
};
const changeStatus = item => {
seachForm.status = item.id;
};
</script>
<template>
<div class="submission app-main-content">
<div class="seach">
<div class="seach-title"><span>全量知识</span></div>
<el-form :model="seachForm">
<el-row>
<el-form-item label="知识标题">
<el-input v-model="seachForm.title" />
</el-form-item>
<el-form-item class="ml-4" label="发文部门">
<el-input v-model="seachForm.publishDeptName" />
</el-form-item>
<el-button class="ml-8" @click="search" type="primary"
>搜索</el-button
>
<el-button @click="reset"></el-button>
</el-row>
</el-form>
</div>
<div class="main-table">
<div class="main-table-header">
<div class="tab-list">
<div
class="tab-list-item"
:class="[seachForm.status === item.id ? 'actived' : '']"
v-for="(item, index) in tabList"
:key="index"
@click="changeStatus(item)"
>
<span>{{ item.title }}</span>
</div>
</div>
<div class="header-btn">
<el-button @click="search" type="primary">新建报送</el-button>
<el-dropdown trigger="click" class="ml-6">
<div class="main-btn">
<span class="mr-4">批量操作</span>
<el-icon><ArrowDown style="color: #ffffff" /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="del"> </el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<pure-table
showOverflowTooltip
:data="dataList"
:columns="columns"
:header-cell-style="{
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}"
>
<template #operation="{ row }">
<el-button link type="danger" @click="handleDelete(row)">
删除
</el-button>
</template></pure-table
>
</div>
</div>
</template>
<style lang="scss" scoped>
.submission {
.main-table-header {
display: flex;
margin-bottom: 24px;
justify-content: space-between;
border-bottom: 1px solid #dfe1e2;
.tab-list {
display: flex;
align-items: center;
position: relative;
top: 1px;
.tab-list-item {
padding: 16px 34px;
border: 1px solid #dfe1e2;
border-bottom: 0;
background: #f5f7f9;
font-size: 18px;
color: #333333;
cursor: pointer;
}
.actived {
background: #ffffff;
color: #0052d9;
border: 0;
border-top: 3px solid #0052d9;
}
}
.header-btn {
display: flex;
align-items: center;
}
.header-tabs {
}
.main-btn {
width: 142px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: #0052d9;
border-radius: 6px 6px 6px 6px;
font-size: 14px;
color: #ffffff;
}
}
}
</style>
Loading…
Cancel
Save