From 3ddb5e9b38c0d07a50aa2957c201553478fa25fc Mon Sep 17 00:00:00 2001 From: RuoYi <yzz_ivy@163.com> Date: 星期三, 01 十二月 2021 17:36:12 +0800 Subject: [PATCH] 文件夹目录大写 --- src/components/RuoYi/Git/index.vue | 13 + src/layout/components/TagsView/ScrollPane.vue | 103 ++++++++++++ src/layout/components/TagsView/index.vue | 330 +++++++++++++++++++++++++++++++++++++++++ src/components/RuoYi/Doc/index.vue | 13 + 4 files changed, 459 insertions(+), 0 deletions(-) diff --git a/src/components/RuoYi/Doc/index.vue b/src/components/RuoYi/Doc/index.vue new file mode 100644 index 0000000..ace6d47 --- /dev/null +++ b/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,13 @@ +<template> + <div> + <svg-icon icon-class="question" @click="goto" /> + </div> +</template> + +<script setup> +const url = ref('http://doc.ruoyi.vip/ruoyi-vue'); + +function goto() { + window.open(url.value) +} +</script> \ No newline at end of file diff --git a/src/components/RuoYi/Git/index.vue b/src/components/RuoYi/Git/index.vue new file mode 100644 index 0000000..3fe77d0 --- /dev/null +++ b/src/components/RuoYi/Git/index.vue @@ -0,0 +1,13 @@ +<template> + <div> + <svg-icon icon-class="github" @click="goto" /> + </div> +</template> + +<script setup> +const url = ref('https://gitee.com/y_project/RuoYi-Vue'); + +function goto() { + window.open(url.value) +} +</script> diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..36171fa --- /dev/null +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,103 @@ +<template> + <el-scrollbar + ref="scrollContainer" + :vertical="false" + class="scroll-container" + @wheel.prevent="handleScroll" + > + <slot /> + </el-scrollbar> +</template> + +<script setup> +const tagAndTagSpacing = ref(4); +const { proxy } = getCurrentInstance(); + +const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrap); + +onMounted(() => { + scrollWrapper.value.addEventListener('scroll', emitScroll, true) +}) +onBeforeUnmount(() => { + scrollWrapper.value.removeEventListener('scroll', emitScroll) +}) + +function handleScroll(e) { + const eventDelta = e.wheelDelta || -e.deltaY * 40 + const $scrollWrapper = scrollWrapper.value; + $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4 +} +const emits = defineEmits() +const emitScroll = () => { + emits('scroll') +} + +const store = useStore(); +const visitedViews = computed(() => store.state.tagsView.visitedViews); + +function moveToTarget(currentTag) { + const $container = proxy.$refs.scrollContainer.$el + const $containerWidth = $container.offsetWidth + const $scrollWrapper = scrollWrapper.value; + + let firstTag = null + let lastTag = null + + // find first tag and last tag + if (visitedViews.value.length > 0) { + firstTag = visitedViews.value[0] + lastTag = visitedViews.value[visitedViews.value.length - 1] + } + + if (firstTag === currentTag) { + $scrollWrapper.scrollLeft = 0 + } else if (lastTag === currentTag) { + $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth + } else { + const tagListDom = document.getElementsByClassName('tags-view-item'); + const currentIndex = visitedViews.value.findIndex(item => item === currentTag) + let prevTag = null + let nextTag = null + for (const k in tagListDom) { + if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) { + if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) { + prevTag = tagListDom[k]; + } + if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) { + nextTag = tagListDom[k]; + } + } + } + + // the tag's offsetLeft after of nextTag + const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value + + // the tag's offsetLeft before of prevTag + const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value + if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) { + $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth + } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) { + $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft + } + } +} + +defineExpose({ + moveToTarget, +}) +</script> + +<style lang='scss' scoped> +.scroll-container { + white-space: nowrap; + position: relative; + overflow: hidden; + width: 100%; + :deep(.el-scrollbar__bar) { + bottom: 0px; + } + :deep(.el-scrollbar__wrap) { + height: 49px; + } +} +</style> \ No newline at end of file diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..eb150d9 --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,330 @@ +<template> + <div id="tags-view-container" class="tags-view-container"> + <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)" + > + {{ 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;" /> + </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 v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"> + <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' + +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 store = useStore(); +const route = useRoute(); +const router = useRouter(); + +const visitedViews = computed(() => store.state.tagsView.visitedViews); +const routes = computed(() => store.state.permission.routes); +const theme = computed(() => store.state.settings.theme); + +watch(route, () => { + addTags() + moveToCurrentTag() +}) +watch(visible, (value) => { + if (value) { + document.body.addEventListener('click', closeMenu) + } else { + document.body.removeEventListener('click', closeMenu) + } +}) +onMounted(() => { + initTags() + addTags() +}) + +function isActive(r) { + return r.path === route.path +} +function activeStyle(tag) { + if (!isActive(tag)) return {}; + return { + "background-color": theme.value, + "border-color": theme.value + }; +} +function isAffix(tag) { + return tag.meta && tag.meta.affix +} +function isFirstView() { + try { + return selectedTag.value.fullPath === visitedViews.value[1].fullPath || selectedTag.value.fullPath === '/index' + } catch (err) { + return false + } +} +function isLastView() { + try { + return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath + } catch (err) { + return false + } +} +function filterAffixTags(routes, basePath = '') { + let tags = [] + routes.forEach(route => { + if (route.meta && route.meta.affix) { + const tagPath = getNormalPath(basePath + '/' + route.path) + tags.push({ + fullPath: tagPath, + path: tagPath, + name: route.name, + meta: { ...route.meta } + }) + } + if (route.children) { + const tempTags = filterAffixTags(route.children, route.path) + if (tempTags.length >= 1) { + tags = [...tags, ...tempTags] + } + } + }) + return tags +} +function initTags() { + const res = filterAffixTags(routes.value); + affixTags.value = res; + for (const tag of res) { + // Must have tag name + if (tag.name) { + store.dispatch('tagsView/addVisitedView', tag) + } + } +} +function addTags() { + const { name } = route + if (name) { + store.dispatch('tagsView/addView', route) + } + return false +} +function moveToCurrentTag() { + nextTick(() => { + for (const r of visitedViews.value) { + if (r.path === route.path) { + scrollPaneRef.value.moveToTarget(r); + // when query is different then update + if (r.fullPath !== route.fullPath) { + store.dispatch('tagsView/updateVisitedView', route) + } + } + } + }) +} +function refreshSelectedTag(view) { + proxy.$tab.refreshPage(view); +} +function closeSelectedTag(view) { + proxy.$tab.closePage(view).then(({ visitedViews }) => { + if (isActive(view)) { + toLastView(visitedViews, view) + } + }) +} +function closeRightTags() { + 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)) { + toLastView(visitedViews) + } + }) +} +function closeOthersTags() { + 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)) { + return + } + toLastView(visitedViews, view) + }) +} +function 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('/') + } + } +} +function openMenu(tag, e) { + const menuMinWidth = 105 + const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left + const offsetWidth = proxy.$el.offsetWidth // container width + const maxLeft = offsetWidth - menuMinWidth // left boundary + const l = e.clientX - offsetLeft + 15 // 15: margin right + + if (l > maxLeft) { + left.value = maxLeft + } else { + left.value = l + } + + top.value = e.clientY + visible.value = true + selectedTag.value = tag +} +function closeMenu() { + visible.value = false +} +function handleScroll() { + closeMenu() +} +</script> + +<style lang='scss' scoped> +.tags-view-container { + height: 34px; + width: 100%; + 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: 26px; + line-height: 26px; + border: 1px solid #d8dce5; + color: #495060; + background: #fff; + padding: 0 8px; + font-size: 12px; + margin-left: 5px; + margin-top: 4px; + &: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: 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> + +<style lang="scss"> +//reset element css of el-icon-close +.tags-view-wrapper { + .tags-view-item { + .el-icon-close { + width: 16px; + height: 16px; + vertical-align: 2px; + border-radius: 50%; + text-align: center; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + transform-origin: 100% 50%; + &:before { + transform: scale(0.6); + display: inline-block; + vertical-align: -3px; + } + &:hover { + background-color: #b4bccc; + color: #fff; + width: 12px !important; + height: 12px !important; + } + } + } +} +</style> \ No newline at end of file -- Gitblit v1.9.3