refactor: pinia整合优化重构
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component ,route}">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="router-fade" mode="out-in">
|
||||
<keep-alive :include="cachedViews">
|
||||
<component :is="Component" :key="route.path"/>
|
||||
<component :is="Component" :key="route.path" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
@@ -12,11 +12,12 @@
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed} from "vue";
|
||||
import {useTagsViewStoreHook} from '@/store/modules/tagsView'
|
||||
import { computed } from "vue";
|
||||
import useStore from "@/store";
|
||||
|
||||
const cachedViews = computed(() => useTagsViewStoreHook().cachedViews);
|
||||
const { tagsView } = useStore();
|
||||
|
||||
const cachedViews = computed(() => tagsView.cachedViews);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -28,7 +29,7 @@ const cachedViews = computed(() => useTagsViewStoreHook().cachedViews);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fixed-header+.app-main {
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ const cachedViews = computed(() => useTagsViewStoreHook().cachedViews);
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
|
||||
.fixed-header+.app-main {
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 84px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,124 @@
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
|
||||
@toggleClick="toggleSideBar"/>
|
||||
<hamburger
|
||||
id="hamburger-container"
|
||||
:is-active="sidebar.opened"
|
||||
class="hamburger-container"
|
||||
@toggleClick="toggleSideBar"
|
||||
/>
|
||||
|
||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container"/>
|
||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
|
||||
|
||||
<div class="right-menu">
|
||||
<template v-if="device!=='mobile'">
|
||||
<template v-if="device !== 'mobile'">
|
||||
<!-- <search id="header-search" class="right-menu-item" />
|
||||
<error-log class="errLog-container right-menu-item hover-effect" />-->
|
||||
<screenfull id="screenfull" class="right-menu-item hover-effect"/>
|
||||
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
||||
<size-select id="size-select" class="right-menu-item hover-effect"/>
|
||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
<lang-select class="right-menu-item hover-effect" />
|
||||
</template>
|
||||
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||
<el-dropdown
|
||||
class="avatar-container right-menu-item hover-effect"
|
||||
trigger="click"
|
||||
>
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
|
||||
<CaretBottom style="width: .6em; height: .6em;margin-left: 5px"/>
|
||||
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
|
||||
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
|
||||
</div>
|
||||
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<router-link to="/">
|
||||
<el-dropdown-item>{{$t('navbar.dashboard')}}</el-dropdown-item>
|
||||
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
|
||||
</router-link>
|
||||
<a target="_blank" href="https://github.com/hxrui">
|
||||
<el-dropdown-item>Github</el-dropdown-item>
|
||||
</a>
|
||||
<a target="_blank" href="https://gitee.com/haoxr">
|
||||
<el-dropdown-item>{{$t('navbar.gitee')}}</el-dropdown-item>
|
||||
<el-dropdown-item>{{ $t("navbar.gitee") }}</el-dropdown-item>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
|
||||
<el-dropdown-item>{{$t('navbar.document')}}</el-dropdown-item>
|
||||
<el-dropdown-item>{{ $t("navbar.document") }}</el-dropdown-item>
|
||||
</a>
|
||||
<el-dropdown-item divided @click="logout">
|
||||
{{$t('navbar.logout')}}
|
||||
{{ $t("navbar.logout") }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {computed} from "vue"
|
||||
import {useRoute, useRouter} from "vue-router"
|
||||
import {ElMessageBox} from 'element-plus'
|
||||
import { computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
import {useAppStoreHook} from '@/store/modules/app'
|
||||
import {useUserStoreHook} from '@/store/modules/user'
|
||||
import useStore from "@/store";
|
||||
|
||||
// 组件依赖
|
||||
import Breadcrumb from '@/components/Breadcrumb/index.vue'
|
||||
import Hamburger from '@/components/Hamburger/index.vue'
|
||||
import Screenfull from '@/components/Screenfull/index.vue'
|
||||
import SizeSelect from '@/components/SizeSelect/index.vue'
|
||||
import LangSelect from '@/components/LangSelect/index.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import Breadcrumb from "@/components/Breadcrumb/index.vue";
|
||||
import Hamburger from "@/components/Hamburger/index.vue";
|
||||
import Screenfull from "@/components/Screenfull/index.vue";
|
||||
import SizeSelect from "@/components/SizeSelect/index.vue";
|
||||
import LangSelect from "@/components/LangSelect/index.vue";
|
||||
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||
|
||||
// 图标依赖
|
||||
import {CaretBottom} from '@element-plus/icons-vue'
|
||||
import { CaretBottom } from "@element-plus/icons-vue";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { app, user } = useStore();
|
||||
|
||||
const sidebar = computed(() => useAppStoreHook().sidebar)
|
||||
const device = computed(() => useAppStoreHook().device)
|
||||
const avatar = computed(() => useUserStoreHook().avatar)
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const sidebar = computed(() => app.sidebar);
|
||||
const device = computed(() => app.device);
|
||||
const avatar = computed(() => user.avatar);
|
||||
|
||||
function toggleSideBar() {
|
||||
useAppStoreHook().toggleSidebar()
|
||||
app.toggleSidebar();
|
||||
}
|
||||
|
||||
function logout() {
|
||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
}).then(() => {
|
||||
useUserStoreHook().logout().then(() => {
|
||||
router.push(`/login?redirect=${route.fullPath}`)
|
||||
})
|
||||
})
|
||||
user.logout().then(() => {
|
||||
router.push(`/login?redirect=${route.fullPath}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
ul { list-style: none; margin: 0; padding: 0; }
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.navbar {
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
transition: background 0.3s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,10 +145,10 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,71 @@
|
||||
<template>
|
||||
<div class="drawer-container">
|
||||
<h3 class="drawer-title">系统布局配置</h3>
|
||||
<div class="drawer-item">
|
||||
<span>主题颜色</span>
|
||||
<div style="float: right;height: 26px;margin: -3px 8px 0 0;">
|
||||
<theme-picker @change="themeChange"/>
|
||||
</div>
|
||||
<h3 class="drawer-title">系统布局配置</h3>
|
||||
<div class="drawer-item">
|
||||
<span>主题颜色</span>
|
||||
<div style="float: right; height: 26px; margin: -3px 8px 0 0">
|
||||
<theme-picker @change="themeChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启 Tags-View</span>
|
||||
<el-switch v-model="tagsView" class="drawer-switch"/>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<span>开启 Tags-View</span>
|
||||
<el-switch v-model="tagsView" class="drawer-switch" />
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>固定 Header</span>
|
||||
<el-switch v-model="fixedHeader" class="drawer-switch"/>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<span>固定 Header</span>
|
||||
<el-switch v-model="fixedHeader" class="drawer-switch" />
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>侧边栏 Logo</span>
|
||||
<el-switch v-model="sidebarLogo" class="drawer-switch"/>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<span>侧边栏 Logo</span>
|
||||
<el-switch v-model="sidebarLogo" class="drawer-switch" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {reactive, toRefs, watch} from "vue";
|
||||
import {useSettingStoreHook} from "@/store/modules/settings";
|
||||
import ThemePicker from '@/components/ThemePicker/index.vue';
|
||||
import { reactive, toRefs, watch } from "vue";
|
||||
|
||||
import ThemePicker from "@/components/ThemePicker/index.vue";
|
||||
|
||||
import useStore from "@/store";
|
||||
|
||||
const { setting } = useStore();
|
||||
|
||||
const state = reactive({
|
||||
fixedHeader: useSettingStoreHook().fixedHeader,
|
||||
tagsView: useSettingStoreHook().tagsView,
|
||||
sidebarLogo: useSettingStoreHook().sidebarLogo
|
||||
})
|
||||
fixedHeader: setting.fixedHeader,
|
||||
tagsView: setting.tagsView,
|
||||
sidebarLogo: setting.sidebarLogo,
|
||||
});
|
||||
|
||||
const {fixedHeader, tagsView, sidebarLogo} = toRefs(state)
|
||||
const { fixedHeader, tagsView, sidebarLogo } = toRefs(state);
|
||||
|
||||
function themeChange(val: any) {
|
||||
useSettingStoreHook().changeSetting({key: 'theme', value: val})
|
||||
setting.changeSetting({ key: "theme", value: val });
|
||||
}
|
||||
|
||||
watch(() => state.fixedHeader, (value) => {
|
||||
useSettingStoreHook().changeSetting({key: 'fixedHeader', value: value})
|
||||
})
|
||||
watch(
|
||||
() => state.fixedHeader,
|
||||
(value) => {
|
||||
setting.changeSetting({ key: "fixedHeader", value: value });
|
||||
}
|
||||
);
|
||||
|
||||
watch(() => state.tagsView, (value) => {
|
||||
useSettingStoreHook().changeSetting({key: 'tagsView', value: value})
|
||||
})
|
||||
|
||||
watch(() => state.sidebarLogo, (value) => {
|
||||
useSettingStoreHook().changeSetting({key: 'sidebarLogo', value: value})
|
||||
})
|
||||
watch(
|
||||
() => state.tagsView,
|
||||
(value) => {
|
||||
setting.changeSetting({ key: "tagsView", value: value });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.sidebarLogo,
|
||||
(value) => {
|
||||
setting.changeSetting({ key: "sidebarLogo", value: value });
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -65,19 +77,19 @@ watch(() => state.sidebarLogo, (value) => {
|
||||
|
||||
.drawer-title {
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.drawer-item {
|
||||
color: rgba(0, 0, 0, .65);
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.drawer-switch {
|
||||
float: right
|
||||
float: right;
|
||||
}
|
||||
|
||||
.job-link {
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
import {computed, defineComponent} from 'vue'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {useAppStoreHook} from "@/store/modules/app";
|
||||
|
||||
const sidebar = computed(() => useAppStoreHook().sidebar);
|
||||
const device = computed(() => useAppStoreHook().device);
|
||||
import useStore from "@/store";
|
||||
|
||||
const {app}=useStore()
|
||||
|
||||
const sidebar = computed(() => app.sidebar);
|
||||
const device = computed(() => app.device);
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -35,7 +38,7 @@ export default defineComponent({
|
||||
const router = useRouter()
|
||||
const push = () => {
|
||||
if (device.value === 'mobile' && sidebar.value.opened == true) {
|
||||
useAppStoreHook().closeSideBar(false)
|
||||
app.closeSideBar(false)
|
||||
}
|
||||
router.push(props.to).catch((err) => {
|
||||
console.log(err)
|
||||
|
||||
@@ -39,8 +39,8 @@ import {isExternal} from '@/utils/validate'
|
||||
import AppLink from './Link.vue'
|
||||
import {RouteRecordRaw} from "vue-router";
|
||||
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
|
||||
@@ -24,20 +24,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import {computed, defineComponent} from "vue";
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
import SidebarItem from './SidebarItem.vue'
|
||||
import Logo from './Logo.vue'
|
||||
import variables from '@/styles/variables.module.scss'
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import {useRoute} from 'vue-router'
|
||||
import useStore from "@/store";
|
||||
|
||||
const {permission,setting,app} =useStore();
|
||||
|
||||
const route =useRoute()
|
||||
const routes =computed(() => usePermissionStoreHook().routes)
|
||||
const showLogo = computed(() => useSettingStoreHook().sidebarLogo)
|
||||
const isCollapse = computed(() => !useAppStoreHook().sidebar.opened)
|
||||
const routes =computed(() => permission.routes)
|
||||
const showLogo = computed(() => setting.sidebarLogo)
|
||||
const isCollapse = computed(() => !app.sidebar.opened)
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const {meta, path} = route
|
||||
|
||||
@@ -1,95 +1,119 @@
|
||||
<template>
|
||||
<el-scrollbar
|
||||
ref="scrollContainerRef"
|
||||
:vertical="false"
|
||||
class="scroll-container"
|
||||
@wheel.prevent="handleScroll">
|
||||
<slot/>
|
||||
ref="scrollContainerRef"
|
||||
:vertical="false"
|
||||
class="scroll-container"
|
||||
@wheel.prevent="handleScroll"
|
||||
>
|
||||
<slot />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onMounted, onBeforeUnmount, getCurrentInstance} from "vue";
|
||||
import {useTagsViewStoreHook} from "@/store/modules/tagsView"
|
||||
import {TagView} from "@/store/interface";
|
||||
const emits = defineEmits()
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
getCurrentInstance,
|
||||
} from "vue";
|
||||
import { TagView } from "@/store/interface";
|
||||
import useStore from "@/store";
|
||||
|
||||
const tagAndTagSpacing = ref(4)
|
||||
const scrollContainerRef = ref(null)
|
||||
const visitedViews = computed(() => useTagsViewStoreHook().visitedViews)
|
||||
const emits = defineEmits();
|
||||
|
||||
const tagAndTagSpacing = ref(4);
|
||||
const scrollContainerRef = ref(null);
|
||||
|
||||
const { tagsView } = useStore();
|
||||
|
||||
const visitedViews = computed(() => tagsView.visitedViews);
|
||||
|
||||
const emitScroll = () => {
|
||||
(emits as any)('scroll')
|
||||
}
|
||||
(emits as any)("scroll");
|
||||
};
|
||||
|
||||
const {ctx} = getCurrentInstance() as any
|
||||
const { ctx } = getCurrentInstance() as any;
|
||||
const scrollWrapper = computed(() => {
|
||||
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement
|
||||
})
|
||||
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
|
||||
})
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
|
||||
})
|
||||
});
|
||||
|
||||
function handleScroll(e: WheelEvent) {
|
||||
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40
|
||||
scrollWrapper.value.scrollLeft = scrollWrapper.value.scrollLeft + eventDelta / 4
|
||||
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
|
||||
scrollWrapper.value.scrollLeft =
|
||||
scrollWrapper.value.scrollLeft + eventDelta / 4;
|
||||
}
|
||||
|
||||
function moveToTarget(currentTag:TagView) {
|
||||
const $container = ctx.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
function moveToTarget(currentTag: TagView) {
|
||||
const $container = ctx.$refs.scrollContainer.$el;
|
||||
const $containerWidth = $container.offsetWidth;
|
||||
const $scrollWrapper = scrollWrapper.value;
|
||||
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
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]
|
||||
firstTag = visitedViews.value[0];
|
||||
lastTag = visitedViews.value[visitedViews.value.length - 1];
|
||||
}
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
$scrollWrapper.scrollLeft = 0;
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
$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
|
||||
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] as any).dataset.path === visitedViews.value[currentIndex - 1].path) {
|
||||
prevTag = tagListDom[k] ;
|
||||
if (k !== "length" && Object.hasOwnProperty.call(tagListDom, k)) {
|
||||
if (
|
||||
(tagListDom[k] as any).dataset.path ===
|
||||
visitedViews.value[currentIndex - 1].path
|
||||
) {
|
||||
prevTag = tagListDom[k];
|
||||
}
|
||||
if ((tagListDom[k] as any).dataset.path === visitedViews.value[currentIndex + 1].path) {
|
||||
if (
|
||||
(tagListDom[k] as any).dataset.path ===
|
||||
visitedViews.value[currentIndex + 1].path
|
||||
) {
|
||||
nextTag = tagListDom[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft = (nextTag as any).offsetLeft + (nextTag as any).offsetWidth + tagAndTagSpacing.value
|
||||
const afterNextTagOffsetLeft =
|
||||
(nextTag as any).offsetLeft +
|
||||
(nextTag as any).offsetWidth +
|
||||
tagAndTagSpacing.value;
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = (prevTag as any).offsetLeft - tagAndTagSpacing.value
|
||||
const beforePrevTagOffsetLeft =
|
||||
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
moveToTarget
|
||||
})
|
||||
moveToTarget,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,44 +1,59 @@
|
||||
<template>
|
||||
<div id="tags-view-container" class="tags-view-container">
|
||||
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
|
||||
<scroll-pane
|
||||
ref="scrollPaneRef"
|
||||
class="tags-view-wrapper"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<router-link
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:class="isActive(tag)?'active':''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
@click.middle="!isAffix(tag)?closeSelectedTag(tag):''"
|
||||
@contextmenu.prevent="openMenu(tag,$event)"
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ generateTitle(tag.meta.title) }}
|
||||
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;"/>
|
||||
<span
|
||||
v-if="!isAffix(tag)"
|
||||
class="el-icon-close"
|
||||
@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">
|
||||
<ul
|
||||
v-show="visible"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
class="contextmenu"
|
||||
>
|
||||
<li @click="refreshSelectedTag(selectedTag)">
|
||||
<refresh-right style="width: 1em; height: 1em;"/>
|
||||
<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="closeOtherTags">
|
||||
<circle-close style="width: 1em; height: 1em;"/>
|
||||
<circle-close style="width: 1em; height: 1em" />
|
||||
关闭其它
|
||||
</li>
|
||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
||||
<back style="width: 1em; height: 1em;"/>
|
||||
<back style="width: 1em; height: 1em" />
|
||||
关闭左侧
|
||||
</li>
|
||||
<li v-if="!isLastView()" @click="closeRightTags">
|
||||
<right style="width: 1em; height: 1em;"/>
|
||||
<right style="width: 1em; height: 1em" />
|
||||
关闭右侧
|
||||
</li>
|
||||
<li @click="closeAllTags(selectedTag)">
|
||||
<circle-close style="width: 1em; height: 1em;"/>
|
||||
<circle-close style="width: 1em; height: 1em" />
|
||||
关闭所有
|
||||
</li>
|
||||
</ul>
|
||||
@@ -46,31 +61,39 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" >
|
||||
|
||||
import {useTagsViewStoreHook} from '@/store/modules/tagsView'
|
||||
import {usePermissionStoreHook} from '@/store/modules/permission'
|
||||
import path from 'path-browserify'
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
ref,
|
||||
watch,
|
||||
onMounted
|
||||
onMounted,
|
||||
} from "vue";
|
||||
import {RouteRecordRaw, useRoute, useRouter} from 'vue-router'
|
||||
import {TagView} from "@/store/interface";
|
||||
|
||||
import ScrollPane from './ScrollPane.vue'
|
||||
import {Close,RefreshRight,CircleClose,Back,Right} from '@element-plus/icons-vue'
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
import path from "path-browserify";
|
||||
|
||||
const {ctx} = getCurrentInstance() as any
|
||||
const router = useRouter()
|
||||
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||||
import { TagView } from "@/store/interface";
|
||||
|
||||
import ScrollPane from "./ScrollPane.vue";
|
||||
import {
|
||||
Close,
|
||||
RefreshRight,
|
||||
CircleClose,
|
||||
Back,
|
||||
Right,
|
||||
} from "@element-plus/icons-vue";
|
||||
import { generateTitle } from "@/utils/i18n";
|
||||
import useStore from "@/store";
|
||||
|
||||
const { tagsView, permission } = useStore();
|
||||
|
||||
const { ctx } = getCurrentInstance() as any;
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const visitedViews = computed<any>(() => useTagsViewStoreHook().visitedViews)
|
||||
const routes = computed<any>(() => usePermissionStoreHook().routes)
|
||||
const visitedViews = computed<any>(() => tagsView.visitedViews);
|
||||
const routes = computed<any>(() => permission.routes);
|
||||
|
||||
const affixTags = ref([]);
|
||||
const visible = ref(false);
|
||||
@@ -80,197 +103,207 @@ const left = ref(0);
|
||||
const top = ref(0);
|
||||
|
||||
watch(route, () => {
|
||||
addTags()
|
||||
moveToCurrentTag()
|
||||
})
|
||||
addTags();
|
||||
moveToCurrentTag();
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = '/') {
|
||||
let tags: TagView[] = []
|
||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
|
||||
let tags: TagView[] = [];
|
||||
|
||||
routes.forEach(route => {
|
||||
routes.forEach((route) => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
const tagPath = path.resolve(basePath, route.path)
|
||||
const tagPath = path.resolve(basePath, route.path);
|
||||
tags.push({
|
||||
fullPath: tagPath,
|
||||
path: tagPath,
|
||||
name: route.name,
|
||||
meta: {...route.meta}
|
||||
})
|
||||
meta: { ...route.meta },
|
||||
});
|
||||
}
|
||||
|
||||
if (route.children) {
|
||||
const childTags = filterAffixTags(route.children, route.path)
|
||||
const childTags = filterAffixTags(route.children, route.path);
|
||||
if (childTags.length >= 1) {
|
||||
tags = tags.concat(childTags)
|
||||
tags = tags.concat(childTags);
|
||||
}
|
||||
}
|
||||
})
|
||||
return tags
|
||||
});
|
||||
return tags;
|
||||
}
|
||||
|
||||
function initTags() {
|
||||
const res = filterAffixTags(routes.value) as []
|
||||
affixTags.value = res
|
||||
const res = filterAffixTags(routes.value) as [];
|
||||
affixTags.value = res;
|
||||
for (const tag of res) {
|
||||
// Must have tag name
|
||||
if ((tag as TagView).name) {
|
||||
useTagsViewStoreHook().addVisitedView(tag)
|
||||
tagsView.addVisitedView(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addTags() {
|
||||
if (route.name) {
|
||||
useTagsViewStoreHook().addView(route)
|
||||
tagsView.addView(route);
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
function moveToCurrentTag() {
|
||||
const tags = getCurrentInstance()?.refs.tag as any[]
|
||||
const tags = getCurrentInstance()?.refs.tag as any[];
|
||||
nextTick(() => {
|
||||
if (tags === null || tags === undefined || !Array.isArray(tags)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
for (const tag of tags) {
|
||||
if ((tag.to as TagView).path === route.path) {
|
||||
(scrollPaneRef.value as any).value.moveToTarget(tag)
|
||||
(scrollPaneRef.value as any).value.moveToTarget(tag);
|
||||
// when query is different then update
|
||||
if ((tag.to as TagView).fullPath !== route.fullPath) {
|
||||
useTagsViewStoreHook().updateVisitedView(route)
|
||||
tagsView.updateVisitedView(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function isActive(tag: TagView) {
|
||||
return tag.path === route.path
|
||||
return tag.path === route.path;
|
||||
}
|
||||
|
||||
function isAffix(tag: TagView) {
|
||||
return tag.meta && tag.meta.affix
|
||||
return tag.meta && tag.meta.affix;
|
||||
}
|
||||
|
||||
function isFirstView() {
|
||||
try {
|
||||
return (selectedTag.value as TagView).fullPath === visitedViews.value[1].fullPath || (selectedTag.value as TagView).fullPath === '/index'
|
||||
return (
|
||||
(selectedTag.value as TagView).fullPath ===
|
||||
visitedViews.value[1].fullPath ||
|
||||
(selectedTag.value as TagView).fullPath === "/index"
|
||||
);
|
||||
} catch (err) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isLastView() {
|
||||
try {
|
||||
return (selectedTag.value as TagView).fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||
return (
|
||||
(selectedTag.value as TagView).fullPath ===
|
||||
visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||
);
|
||||
} catch (err) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSelectedTag(view: TagView) {
|
||||
useTagsViewStoreHook().delCachedView(view)
|
||||
const {fullPath} = view
|
||||
tagsView.delCachedView(view);
|
||||
const { fullPath } = view;
|
||||
nextTick(() => {
|
||||
router.replace({path: '/redirect' + fullPath}).catch(err => {
|
||||
console.warn(err)
|
||||
})
|
||||
})
|
||||
router.replace({ path: "/redirect" + fullPath }).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toLastView(visitedViews: TagView[], view?: any) {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
const latestView = visitedViews.slice(-1)[0];
|
||||
if (latestView && latestView.fullPath) {
|
||||
router.push(latestView.fullPath)
|
||||
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') {
|
||||
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("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeSelectedTag(view: TagView) {
|
||||
useTagsViewStoreHook().delView(view).then((res: any) => {
|
||||
tagsView.delView(view).then((res: any) => {
|
||||
if (isActive(view)) {
|
||||
toLastView(res.visitedViews, view)
|
||||
toLastView(res.visitedViews, view);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function closeLeftTags() {
|
||||
useTagsViewStoreHook().delLeftViews(selectedTag.value).then((res: any) => {
|
||||
if (!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)) {
|
||||
toLastView(res.visitedViews)
|
||||
tagsView.delLeftViews(selectedTag.value).then((res: any) => {
|
||||
if (
|
||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
||||
) {
|
||||
toLastView(res.visitedViews);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
function closeRightTags() {
|
||||
useTagsViewStoreHook().delRightViews(selectedTag.value).then((res:any) => {
|
||||
if (!res.visitedViews.find((item:any) => item.fullPath === route.fullPath)) {
|
||||
toLastView(res.visitedViews)
|
||||
tagsView.delRightViews(selectedTag.value).then((res: any) => {
|
||||
if (
|
||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
||||
) {
|
||||
toLastView(res.visitedViews);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function closeOtherTags() {
|
||||
useTagsViewStoreHook().delOtherViews(selectedTag.value).then(() => {
|
||||
moveToCurrentTag()
|
||||
})
|
||||
tagsView.delOtherViews(selectedTag.value).then(() => {
|
||||
moveToCurrentTag();
|
||||
});
|
||||
}
|
||||
|
||||
function closeAllTags(view: TagView) {
|
||||
useTagsViewStoreHook().delRightViews(selectedTag.value).then((res:any) => {
|
||||
if (affixTags.value.some((tag:any) => tag.path === route.path)) {
|
||||
return
|
||||
tagsView.delRightViews(selectedTag.value).then((res: any) => {
|
||||
if (affixTags.value.some((tag: any) => tag.path === route.path)) {
|
||||
return;
|
||||
}
|
||||
toLastView(res.visitedViews, view)
|
||||
})
|
||||
toLastView(res.visitedViews, view);
|
||||
});
|
||||
}
|
||||
|
||||
function openMenu(tag: TagView, e: MouseEvent) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = ctx.$el.getBoundingClientRect().left // container margin left
|
||||
const offsetWidth = ctx.$el.offsetWidth // container width
|
||||
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
||||
const l = e.clientX - offsetLeft + 15 // 15: margin right
|
||||
const menuMinWidth = 105;
|
||||
const offsetLeft = ctx.$el.getBoundingClientRect().left; // container margin left
|
||||
const offsetWidth = ctx.$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
|
||||
left.value = maxLeft;
|
||||
} else {
|
||||
left.value = l
|
||||
left.value = l;
|
||||
}
|
||||
|
||||
top.value = e.clientY
|
||||
visible.value = true
|
||||
selectedTag.value = tag
|
||||
top.value = e.clientY;
|
||||
visible.value = true;
|
||||
selectedTag.value = tag;
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
visible.value = false
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
closeMenu()
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initTags()
|
||||
addTags()
|
||||
})
|
||||
|
||||
initTags();
|
||||
addTags();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -1,57 +1,62 @@
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper">
|
||||
<div v-if="device==='mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<sidebar class="sidebar-container"/>
|
||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||
<div :class="{'fixed-header':fixedHeader}">
|
||||
<navbar/>
|
||||
<tags-view v-if="needTagsView"/>
|
||||
<div
|
||||
v-if="device === 'mobile' && sidebar.opened"
|
||||
class="drawer-bg"
|
||||
@click="handleClickOutside"
|
||||
/>
|
||||
<sidebar class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main/>
|
||||
<app-main />
|
||||
<right-panel v-if="showSettings">
|
||||
<settings/>
|
||||
<settings />
|
||||
</right-panel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, watchEffect} from "vue"
|
||||
import {useWindowSize} from '@vueuse/core'
|
||||
import {AppMain, Navbar, Settings, TagsView} from './components/index'
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import RightPanel from '@/components/RightPanel/index.vue'
|
||||
import { computed, watchEffect } from "vue";
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
||||
import Sidebar from "./components/Sidebar/index.vue";
|
||||
import RightPanel from "@/components/RightPanel/index.vue";
|
||||
|
||||
import {useAppStoreHook} from "@/store/modules/app"
|
||||
import {useSettingStoreHook} from "@/store/modules/settings"
|
||||
import useStore from "@/store";
|
||||
|
||||
const {width, height} = useWindowSize();
|
||||
const WIDTH = 992
|
||||
const { width, height } = useWindowSize();
|
||||
const WIDTH = 992;
|
||||
|
||||
const sidebar = computed(() => useAppStoreHook().sidebar);
|
||||
const device = computed(() => useAppStoreHook().device);
|
||||
const needTagsView = computed(() => useSettingStoreHook().tagsView);
|
||||
const fixedHeader = computed(() => useSettingStoreHook().fixedHeader);
|
||||
const showSettings = computed(() => useSettingStoreHook().showSettings);
|
||||
const { app, setting } = useStore();
|
||||
|
||||
const sidebar = computed(() => app.sidebar);
|
||||
const device = computed(() => app.device);
|
||||
const needTagsView = computed(() => setting.tagsView);
|
||||
const fixedHeader = computed(() => setting.fixedHeader);
|
||||
const showSettings = computed(() => setting.showSettings);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === 'mobile'
|
||||
}))
|
||||
mobile: device.value === "mobile",
|
||||
}));
|
||||
|
||||
watchEffect(() => {
|
||||
if (width.value < WIDTH) {
|
||||
useAppStoreHook().toggleDevice("mobile")
|
||||
useAppStoreHook().closeSideBar(true)
|
||||
app.toggleDevice("mobile");
|
||||
app.closeSideBar(true);
|
||||
} else {
|
||||
useAppStoreHook().toggleDevice("desktop")
|
||||
app.toggleDevice("desktop");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function handleClickOutside() {
|
||||
useAppStoreHook().closeSideBar(false)
|
||||
app.closeSideBar(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -91,7 +96,7 @@ function handleClickOutside() {
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px)
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
|
||||
Reference in New Issue
Block a user