| | |
| | | <template> |
| | | <div id="tags-view-container" class="tags-view-container"> |
| | | <div id="tags-view-container" class="tags-view-container" :class="{'theme-dark': sideTheme === 'theme-dark', 'theme-light': sideTheme === 'theme-light'}"> |
| | | <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll"> |
| | | <router-link v-for="tag in visitedViews" :key="tag.path" :data-path="tag.path" :class="isActive(tag) ? 'active' : ''" |
| | | :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" class="tags-view-item" :style="activeStyle(tag)" |
| | | @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @contextmenu.prevent="openMenu(tag, $event)"> |
| | | <router-link |
| | | v-for="tag in visitedViews" |
| | | :key="tag.path" |
| | | :data-path="tag.path" |
| | | :class="isActive(tag) ? 'active' : ''" |
| | | :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" |
| | | class="tags-view-item" |
| | | :style="activeStyle(tag)" |
| | | @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" |
| | | @contextmenu.prevent="openMenu(tag, $event)" |
| | | > |
| | | {{ tag.title }} |
| | | <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)"> |
| | | <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" /> |
| | | <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" /> |
| | | </span> |
| | | </router-link> |
| | | </scroll-pane> |
| | | <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu"> |
| | | <li @click="refreshSelectedTag(selectedTag)"> |
| | | <refresh-right style="width: 1em; height: 1em;" /> 刷新页面 |
| | | </li> |
| | | <li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em" /> 刷新页面</li> |
| | | <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"> |
| | | <close style="width: 1em; height: 1em;" /> 关闭当前 |
| | | <close style="width: 1em; height: 1em" /> 关闭当前 |
| | | </li> |
| | | <li @click="closeOthersTags"> |
| | | <circle-close style="width: 1em; height: 1em;" /> 关闭其他 |
| | | </li> |
| | | <li v-if="!isFirstView()" @click="closeLeftTags"> |
| | | <back style="width: 1em; height: 1em;" /> 关闭左侧 |
| | | </li> |
| | | <li v-if="!isLastView()" @click="closeRightTags"> |
| | | <right style="width: 1em; height: 1em;" /> 关闭右侧 |
| | | </li> |
| | | <li @click="closeAllTags(selectedTag)"> |
| | | <circle-close style="width: 1em; height: 1em;" /> 全部关闭 |
| | | </li> |
| | | <li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em" /> 关闭其他</li> |
| | | <li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em" /> 关闭左侧</li> |
| | | <li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em" /> 关闭右侧</li> |
| | | <li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em" /> 全部关闭</li> |
| | | </ul> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import ScrollPane from './ScrollPane' |
| | | import { getNormalPath } from '@/utils/ruoyi' |
| | | import useTagsViewStore from '@/store/modules/tagsView' |
| | | import useSettingsStore from '@/store/modules/settings' |
| | | import usePermissionStore from '@/store/modules/permission' |
| | | import ScrollPane from "./ScrollPane" |
| | | import { getNormalPath } from "@/utils/ruoyi" |
| | | import useTagsViewStore from "@/store/modules/tagsView" |
| | | import useSettingsStore from "@/store/modules/settings" |
| | | import usePermissionStore from "@/store/modules/permission" |
| | | |
| | | const visible = ref(false); |
| | | const top = ref(0); |
| | | const left = ref(0); |
| | | const selectedTag = ref({}); |
| | | const affixTags = ref([]); |
| | | const scrollPaneRef = ref(null); |
| | | const visible = ref(false) |
| | | const top = ref(0) |
| | | const left = ref(0) |
| | | const selectedTag = ref({}) |
| | | const affixTags = ref([]) |
| | | const scrollPaneRef = ref(null) |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const { proxy } = getCurrentInstance() |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | |
| | | const visitedViews = computed(() => useTagsViewStore().visitedViews); |
| | | const routes = computed(() => usePermissionStore().routes); |
| | | const theme = computed(() => useSettingsStore().theme); |
| | | const visitedViews = computed(() => useTagsViewStore().visitedViews) |
| | | const routes = computed(() => usePermissionStore().routes) |
| | | const theme = computed(() => useSettingsStore().theme) |
| | | const sideTheme = computed(() => useSettingsStore().sideTheme) |
| | | |
| | | watch(route, () => { |
| | | addTags() |
| | |
| | | }) |
| | | watch(visible, (value) => { |
| | | if (value) { |
| | | document.body.addEventListener('click', closeMenu) |
| | | document.body.addEventListener("click", closeMenu) |
| | | } else { |
| | | document.body.removeEventListener('click', closeMenu) |
| | | document.body.removeEventListener("click", closeMenu) |
| | | } |
| | | }) |
| | | onMounted(() => { |
| | |
| | | return r.path === route.path |
| | | } |
| | | function activeStyle(tag) { |
| | | if (!isActive(tag)) return {}; |
| | | if (!isActive(tag)) return {} |
| | | return { |
| | | "background-color": theme.value, |
| | | "border-color": theme.value |
| | | }; |
| | | "border-color": theme.value, |
| | | } |
| | | } |
| | | function isAffix(tag) { |
| | | return tag.meta && tag.meta.affix |
| | | } |
| | | function isFirstView() { |
| | | try { |
| | | return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath |
| | | return selectedTag.value.fullPath === "/index" || selectedTag.value.fullPath === visitedViews.value[1].fullPath |
| | | } catch (err) { |
| | | return false |
| | | } |
| | |
| | | return false |
| | | } |
| | | } |
| | | function filterAffixTags(routes, basePath = '') { |
| | | function filterAffixTags(routes, basePath = "") { |
| | | let tags = [] |
| | | routes.forEach(route => { |
| | | routes.forEach((route) => { |
| | | if (route.meta && route.meta.affix) { |
| | | const tagPath = getNormalPath(basePath + '/' + route.path) |
| | | const tagPath = getNormalPath(basePath + "/" + route.path) |
| | | tags.push({ |
| | | fullPath: tagPath, |
| | | path: tagPath, |
| | | name: route.name, |
| | | meta: { ...route.meta } |
| | | meta: { ...route.meta }, |
| | | }) |
| | | } |
| | | if (route.children) { |
| | |
| | | return tags |
| | | } |
| | | function initTags() { |
| | | const res = filterAffixTags(routes.value); |
| | | affixTags.value = res; |
| | | const res = filterAffixTags(routes.value) |
| | | affixTags.value = res |
| | | for (const tag of res) { |
| | | // Must have tag name |
| | | if (tag.name) { |
| | |
| | | if (name) { |
| | | useTagsViewStore().addView(route) |
| | | if (route.meta.link) { |
| | | useTagsViewStore().addIframeView(route); |
| | | useTagsViewStore().addIframeView(route) |
| | | } |
| | | } |
| | | return false |
| | |
| | | nextTick(() => { |
| | | for (const r of visitedViews.value) { |
| | | if (r.path === route.path) { |
| | | scrollPaneRef.value.moveToTarget(r); |
| | | scrollPaneRef.value.moveToTarget(r) |
| | | // when query is different then update |
| | | if (r.fullPath !== route.fullPath) { |
| | | useTagsViewStore().updateVisitedView(route) |
| | |
| | | }) |
| | | } |
| | | function refreshSelectedTag(view) { |
| | | proxy.$tab.refreshPage(view); |
| | | proxy.$tab.refreshPage(view) |
| | | if (route.meta.link) { |
| | | useTagsViewStore().delIframeView(route); |
| | | useTagsViewStore().delIframeView(route) |
| | | } |
| | | } |
| | | function closeSelectedTag(view) { |
| | |
| | | }) |
| | | } |
| | | function closeRightTags() { |
| | | proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => { |
| | | if (!visitedViews.find(i => i.fullPath === route.fullPath)) { |
| | | proxy.$tab.closeRightPage(selectedTag.value).then((visitedViews) => { |
| | | if (!visitedViews.find((i) => i.fullPath === route.fullPath)) { |
| | | toLastView(visitedViews) |
| | | } |
| | | }) |
| | | } |
| | | function closeLeftTags() { |
| | | proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => { |
| | | if (!visitedViews.find(i => i.fullPath === route.fullPath)) { |
| | | proxy.$tab.closeLeftPage(selectedTag.value).then((visitedViews) => { |
| | | if (!visitedViews.find((i) => i.fullPath === route.fullPath)) { |
| | | toLastView(visitedViews) |
| | | } |
| | | }) |
| | | } |
| | | function closeOthersTags() { |
| | | router.push(selectedTag.value).catch(() => { }); |
| | | router.push(selectedTag.value).catch(() => {}) |
| | | proxy.$tab.closeOtherPage(selectedTag.value).then(() => { |
| | | moveToCurrentTag() |
| | | }) |
| | | } |
| | | function closeAllTags(view) { |
| | | proxy.$tab.closeAllPage().then(({ visitedViews }) => { |
| | | if (affixTags.value.some(tag => tag.path === route.path)) { |
| | | if (affixTags.value.some((tag) => tag.path === route.path)) { |
| | | return |
| | | } |
| | | toLastView(visitedViews, view) |
| | |
| | | } 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') { |
| | | if (view.name === "Dashboard") { |
| | | // to reload home page |
| | | router.replace({ path: '/redirect' + view.fullPath }) |
| | | router.replace({ path: "/redirect" + view.fullPath }) |
| | | } else { |
| | | router.push('/') |
| | | router.push("/") |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | </script> |
| | | |
| | | <style lang='scss' scoped> |
| | | .themeDark { |
| | | .tags-view-container { |
| | | height: 50px; |
| | | width: 100%; |
| | | background: #1a235d; |
| | | // border-bottom: 1px solid #d8dce5; |
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); |
| | | .tags-view-wrapper { |
| | | .tags-view-item { |
| | | display: inline-block; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 34px; |
| | | line-height: 34px; |
| | | border: 1px solid #203a81; |
| | | color: #c1c1c1; |
| | | background: #3271eb; |
| | | padding: 0 8px; |
| | | font-size: 15px; |
| | | margin-left: 5px; |
| | | margin-top: 6px; |
| | | border-radius: 5px; |
| | | font-family: OPPOSans, OPPOSans; |
| | | &:first-of-type { |
| | | margin-left: 15px; |
| | | } |
| | | &:last-of-type { |
| | | margin-right: 15px; |
| | | } |
| | | &.active { |
| | | background-color: #42b983; |
| | | color: #fff; |
| | | border-color: #42b983; |
| | | &::before { |
| | | content: ""; |
| | | background: #fff; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | position: relative; |
| | | margin-right: 5px; |
| | | } |
| | | } |
| | | <style lang="scss" scoped> |
| | | .tags-view-container { |
| | | height: 34px; |
| | | width: calc(100% - 42px); |
| | | margin-top: 10px; |
| | | margin-left: 14px; |
| | | box-sizing: border-box; |
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); |
| | | |
| | | &.theme-dark { |
| | | background: #0A3465; |
| | | .tags-view-item { |
| | | color: #fff; |
| | | border: 1px solid #0c4685; |
| | | background: rgba(10, 52, 101, .48); |
| | | border-radius: 5px; |
| | | |
| | | &.active { |
| | | background-color: var(--el-color-primary) !important; |
| | | color: #fff !important; |
| | | } |
| | | } |
| | | .contextmenu { |
| | | margin: 0; |
| | | } |
| | | |
| | | &.theme-light { |
| | | background: #fff; |
| | | .tags-view-item { |
| | | color: #495060; |
| | | background: #fff; |
| | | z-index: 3000; |
| | | position: absolute; |
| | | list-style-type: none; |
| | | padding: 5px 0; |
| | | border-radius: 4px; |
| | | border: 1px solid #d8dce5; |
| | | |
| | | &.active { |
| | | background-color: var(--el-color-primary) !important; |
| | | color: #fff !important; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .tags-view-wrapper { |
| | | .tags-view-item { |
| | | display: inline-block; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 26px; |
| | | line-height: 26px; |
| | | border-radius: 3px; |
| | | padding: 0 10px; |
| | | 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; |
| | | margin-left: 5px; |
| | | margin-top: 4px; |
| | | &:first-of-type { |
| | | margin-left: 5px; |
| | | } |
| | | &:last-of-type { |
| | | margin-right: 5px; |
| | | } |
| | | &.active { |
| | | background-color: #42b983; |
| | | color: #fff; |
| | | border-color: #42b983; |
| | | &::before { |
| | | content: ""; |
| | | background: #fff; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | position: relative; |
| | | margin-right: 2px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .themeLight { |
| | | .tags-view-container { |
| | | height: 50px; |
| | | width: 100%; |
| | | .contextmenu { |
| | | margin: 0; |
| | | background: #fff; |
| | | // border-bottom: 1px solid #d8dce5; |
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); |
| | | .tags-view-wrapper { |
| | | .tags-view-item { |
| | | display: inline-block; |
| | | position: relative; |
| | | cursor: pointer; |
| | | height: 34px; |
| | | line-height: 34px; |
| | | border: 1px solid #d8dce5; |
| | | color: #495060; |
| | | background: #fff; |
| | | padding: 0 8px; |
| | | font-size: 15px; |
| | | margin-left: 5px; |
| | | margin-top: 6px; |
| | | border-radius: 5px; |
| | | font-family: OPPOSans, OPPOSans; |
| | | &:first-of-type { |
| | | margin-left: 15px; |
| | | } |
| | | &:last-of-type { |
| | | margin-right: 15px; |
| | | } |
| | | &.active { |
| | | background-color: #42b983; |
| | | color: #fff; |
| | | border-color: #42b983; |
| | | &::before { |
| | | content: ""; |
| | | background: #fff; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | position: relative; |
| | | margin-right: 5px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .contextmenu { |
| | | 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; |
| | | 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; |
| | | } |
| | | padding: 7px 16px; |
| | | cursor: pointer; |
| | | &:hover { |
| | | background: #eee; |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | .scroll-container .el-scrollbar__wrap { |
| | | height: 50px !important; |
| | | height: 34px !important; |
| | | } |
| | | </style> |
| | | </style> |