refactor: 项目重构
This commit is contained in:
parent
cf8a76c203
commit
56f5ac3802
|
|
@ -3,6 +3,7 @@ node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
.history
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,14 @@ module.exports = {
|
||||||
"property-no-unknown": [
|
"property-no-unknown": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
ignoreProperties: ["menuBg", "menuText", "menuActiveText"],
|
ignoreProperties: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 允许未知规则
|
||||||
|
"at-rule-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
ignoreAtRules: ["apply"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<el-breadcrumb class="h-[50px] flex items-center">
|
<el-breadcrumb class="h-[50px] flex items-center">
|
||||||
<transition-group name="breadcrumb">
|
<transition-group name="breadcrumb-transition">
|
||||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,25 @@
|
||||||
<script setup lang="ts">
|
<template>
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
<div ref="rightPanel" :class="{ show: show }">
|
||||||
|
<div class="right-panel-overlay"></div>
|
||||||
|
<div class="right-panel-container">
|
||||||
|
<div
|
||||||
|
class="right-panel-btn"
|
||||||
|
:style="{
|
||||||
|
top: buttonTop + 'px',
|
||||||
|
}"
|
||||||
|
@click="show = !show"
|
||||||
|
>
|
||||||
|
<i-ep-close v-show="show" />
|
||||||
|
<i-ep-setting v-show="!show" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
import { addClass, removeClass } from "@/utils/index";
|
import { addClass, removeClass } from "@/utils/index";
|
||||||
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
|
|
@ -52,27 +71,6 @@ onBeforeUnmount(() => {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="rightPanel" :class="{ show: show }">
|
|
||||||
<div class="right-panel-overlay"></div>
|
|
||||||
<div class="right-panel-container">
|
|
||||||
<div
|
|
||||||
class="right-panel-btn"
|
|
||||||
:style="{
|
|
||||||
top: buttonTop + 'px',
|
|
||||||
}"
|
|
||||||
@click="show = !show"
|
|
||||||
>
|
|
||||||
<i-ep-close v-show="show" />
|
|
||||||
<i-ep-setting v-show="!show" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.showRightPanel {
|
.showRightPanel {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -91,7 +89,7 @@ onBeforeUnmount(() => {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 999;
|
z-index: 1000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<section class="app-main">
|
|
||||||
<router-view>
|
|
||||||
<template #default="{ Component, route }">
|
|
||||||
<transition name="fade-slide" mode="out-in">
|
|
||||||
<keep-alive :include="tagsViewStore.cachedViews">
|
|
||||||
<component :is="Component" :key="route.fullPath" />
|
|
||||||
</keep-alive>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
</router-view>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-main {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
/* 50= navbar 50 */
|
|
||||||
min-height: calc(100vh - 50px);
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--el-bg-color-page);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hasTagsView {
|
|
||||||
.app-main {
|
|
||||||
/* 84 = navbar + tags-view = 50 + 34 */
|
|
||||||
min-height: calc(100vh - 84px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-top: 84px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.isMix {
|
|
||||||
.app-main {
|
|
||||||
height: calc(100vh - 50px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hasTagsView {
|
|
||||||
.app-main {
|
|
||||||
/* 84 = navbar + tags-view = 50 + 34 */
|
|
||||||
height: calc(100vh - 84px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
min-height: calc(100vh - 50px);
|
|
||||||
padding-top: 34px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.isTop {
|
|
||||||
.hasTagsView {
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: 34px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<section class="app-main">
|
||||||
|
<router-view>
|
||||||
|
<template #default="{ Component, route }">
|
||||||
|
<transition name="fade-translate" mode="out-in">
|
||||||
|
<keep-alive :include="cachedViews">
|
||||||
|
<component :is="Component" :key="route.path" />
|
||||||
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
</router-view>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTagsViewStore } from "@/store";
|
||||||
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
|
||||||
|
const cachedViews = computed(() => tagsViewStore.cachedViews); // 缓存页面集合
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-main {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100vh - $navbar-height);
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--el-bg-color-page);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header + .app-main {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-top: $navbar-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasTagsView {
|
||||||
|
.app-main {
|
||||||
|
/* 84 = navbar + tags-view = 50 + 34 */
|
||||||
|
min-height: calc(100vh - $navbar-height - $tags-view-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header + .app-main {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-top: $navbar-height + $tags-view-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mix {
|
||||||
|
.app-main {
|
||||||
|
height: calc(100vh - $navbar-height);
|
||||||
|
min-height: calc(100vh - $navbar-height);
|
||||||
|
padding-top: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header + .app-main {
|
||||||
|
min-height: calc(100vh - $navbar-height);
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasTagsView {
|
||||||
|
.app-main {
|
||||||
|
height: calc(100vh - $navbar-height - $tags-view-height);
|
||||||
|
min-height: calc(100vh - $navbar-height - $tags-view-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header + .app-main {
|
||||||
|
min-height: calc(100vh - $navbar-height);
|
||||||
|
padding-top: $tags-view-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- 导航栏设置(窄屏隐藏)-->
|
|
||||||
<div v-if="device !== 'mobile'" class="setting-container">
|
|
||||||
<!--全屏 -->
|
|
||||||
<div class="setting-item" @click="toggle">
|
|
||||||
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" />
|
|
||||||
</div>
|
|
||||||
<!-- 布局大小 -->
|
|
||||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
|
||||||
<size-select class="setting-item" />
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 用户头像 -->
|
|
||||||
<el-dropdown trigger="click">
|
|
||||||
<div class="avatar-container">
|
|
||||||
<img :src="userStore.user.avatar + '?imageView2/1/w/80/h/80'" />
|
|
||||||
<i-ep-caret-bottom class="w-3 h-3" />
|
|
||||||
</div>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<router-link to="/">
|
|
||||||
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
|
|
||||||
</router-link>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/youlaitech/vue3-element-admin"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://juejin.cn/post/7228990409909108793">
|
|
||||||
<el-dropdown-item>{{ $t("navbar.document") }}</el-dropdown-item>
|
|
||||||
</a>
|
|
||||||
<el-dropdown-item divided @click="logout">
|
|
||||||
{{ $t("navbar.logout") }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { useRoute, useRouter } from "vue-router";
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
|
||||||
import { useUserStore } from "@/store/modules/user";
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { device } = storeToRefs(appStore); // 设备类型:desktop-宽屏设备 || mobile-窄屏设备
|
|
||||||
|
|
||||||
/**
|
|
||||||
* vueUse 全屏
|
|
||||||
*/
|
|
||||||
const { isFullscreen, toggle } = useFullscreen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注销
|
|
||||||
*/
|
|
||||||
function logout() {
|
|
||||||
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
lockScroll: false,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
userStore
|
|
||||||
.logout()
|
|
||||||
.then(() => {
|
|
||||||
tagsViewStore.delAllViews();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
router.push(`/login?redirect=${route.fullPath}`);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.setting-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.setting-item {
|
|
||||||
display: inline-block;
|
|
||||||
width: 30px;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--el-disabled-bg-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-container {
|
|
||||||
display: flex;
|
|
||||||
place-items: center center;
|
|
||||||
margin: 0 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex">
|
||||||
|
<hamburger
|
||||||
|
:is-active="appStore.sidebar.opened"
|
||||||
|
@toggle-click="toggleSideBar"
|
||||||
|
/>
|
||||||
|
<breadcrumb />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
function toggleSideBar() {
|
||||||
|
appStore.toggleSidebar();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex">
|
||||||
|
<div v-if="device !== 'mobile'" class="flex-center">
|
||||||
|
<!--全屏 -->
|
||||||
|
<div class="navbar-item" @click="toggle">
|
||||||
|
<svg-icon
|
||||||
|
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 布局大小 -->
|
||||||
|
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
||||||
|
<size-select class="navbar-item" />
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户头像 -->
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<div class="flex-center ml-1">
|
||||||
|
<img
|
||||||
|
:src="userStore.user.avatar + '?imageView2/1/w/80/h/80'"
|
||||||
|
width="40px"
|
||||||
|
height="40px"
|
||||||
|
class="rounded-md cursor-pointer"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-icon class="cursor-pointer">
|
||||||
|
<CaretBottom />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<router-link to="/">
|
||||||
|
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
|
||||||
|
</router-link>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/youlaitech/vue3-element-admin"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://juejin.cn/post/7228990409909108793">
|
||||||
|
<el-dropdown-item>{{ $t("navbar.document") }}</el-dropdown-item>
|
||||||
|
</a>
|
||||||
|
<el-dropdown-item divided @click="logout">
|
||||||
|
{{ $t("navbar.logout") }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppStore, useTagsViewStore, useUserStore } from "@/store";
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 设备类型:desktop-宽屏设备 || mobile-窄屏设备
|
||||||
|
const device = computed(() => appStore.device);
|
||||||
|
|
||||||
|
const { isFullscreen, toggle } = useFullscreen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销
|
||||||
|
*/
|
||||||
|
function logout() {
|
||||||
|
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
lockScroll: false,
|
||||||
|
}).then(() => {
|
||||||
|
userStore
|
||||||
|
.logout()
|
||||||
|
.then(() => {
|
||||||
|
tagsViewStore.delAllViews();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
router.push(`/login?redirect=${route.fullPath}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.navbar-item {
|
||||||
|
display: inline-block;
|
||||||
|
width: 30px;
|
||||||
|
height: $navbar-height;
|
||||||
|
line-height: $navbar-height;
|
||||||
|
color: var(--el-text-color);
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgb(0 0 0 / 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-top,
|
||||||
|
.layout-mix {
|
||||||
|
.navbar-item,
|
||||||
|
.el-icon {
|
||||||
|
color: var(--el-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .navbar-item:hover {
|
||||||
|
background: rgb(255 255 255 / 20%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,42 +1,18 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 左侧菜单栏显示/隐藏
|
|
||||||
*/
|
|
||||||
function toggleSideBar() {
|
|
||||||
appStore.toggleSidebar();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<div class="navbar">
|
<div class="navbar-container">
|
||||||
<!-- 左侧面包屑 -->
|
<!-- 导航栏左侧 -->
|
||||||
<div class="flex">
|
<NavbarLeft />
|
||||||
<hamburger
|
<!-- 导航栏右侧 -->
|
||||||
:is-active="appStore.sidebar.opened"
|
<NavbarRight />
|
||||||
@toggle-click="toggleSideBar"
|
|
||||||
/>
|
|
||||||
<breadcrumb />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧导航设置 -->
|
|
||||||
<div class="flex">
|
|
||||||
<NavRight />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.navbar {
|
.navbar-container {
|
||||||
display: flex;
|
@apply flex-x-between;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
height: $navbar-height;
|
||||||
height: 50px;
|
background: var(--el-bg-color);
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 0 0 1px #0003;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-container">
|
<div class="setting-container">
|
||||||
<h3 class="text-base font-bold">项目配置</h3>
|
<h3 class="text-base font-bold">项目配置</h3>
|
||||||
<el-divider>主题设置</el-divider>
|
<el-divider>主题设置</el-divider>
|
||||||
|
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="isDark"
|
v-model="isDark"
|
||||||
:active-icon="IconEpMoon"
|
:active-icon="Moon"
|
||||||
:inactive-icon="IconEpSunny"
|
:inactive-icon="Sunny"
|
||||||
@change="handleThemeChange"
|
@change="handleThemeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-divider>界面设置</el-divider>
|
<el-divider>界面设置</el-divider>
|
||||||
<div class="py-[8px] flex justify-between">
|
<div class="py-[8px] flex-x-between">
|
||||||
<el-text>开启 Tags-View</el-text>
|
<el-text>开启 Tags-View</el-text>
|
||||||
<el-switch v-model="settingsStore.tagsView" />
|
<el-switch v-model="settingsStore.tagsView" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<li
|
<li
|
||||||
v-for="(color, index) in themeColors"
|
v-for="(color, index) in themeColors"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="inline-block w-[30px] h-[30px] cursor-pointer theme-wrap"
|
class="w-[30px] h-[30px] cursor-pointer flex-center color-white"
|
||||||
:style="{ background: color }"
|
:style="{ background: color }"
|
||||||
@click="changeThemeColor(color)"
|
@click="changeThemeColor(color)"
|
||||||
>
|
>
|
||||||
|
|
@ -86,12 +86,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
import { useSettingsStore, usePermissionStore, useAppStore } from "@/store";
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
import { Sunny, Moon } from "@element-plus/icons-vue";
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import IconEpSunny from "~icons/ep/sunny";
|
|
||||||
import IconEpMoon from "~icons/ep/moon";
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
|
@ -129,7 +125,7 @@ function findOutermostParent(tree: any[], findName: string) {
|
||||||
const againActiveTop = (newVal: string) => {
|
const againActiveTop = (newVal: string) => {
|
||||||
const parent = findOutermostParent(permissionStore.routes, newVal);
|
const parent = findOutermostParent(permissionStore.routes, newVal);
|
||||||
if (appStore.activeTopMenu !== parent.path) {
|
if (appStore.activeTopMenu !== parent.path) {
|
||||||
appStore.changeTopActive(parent.path);
|
appStore.activeTopMenu(parent.path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -138,7 +134,6 @@ const againActiveTop = (newVal: string) => {
|
||||||
*/
|
*/
|
||||||
function changeLayout(layout: string) {
|
function changeLayout(layout: string) {
|
||||||
settingsStore.changeSetting({ key: "layout", value: layout });
|
settingsStore.changeSetting({ key: "layout", value: layout });
|
||||||
window.document.body.setAttribute("layout", settingsStore.layout);
|
|
||||||
if (layout === "mix") {
|
if (layout === "mix") {
|
||||||
route.name && againActiveTop(route.name as string);
|
route.name && againActiveTop(route.name as string);
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +188,7 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.settings-container {
|
.setting-container {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
|
|
@ -257,12 +252,5 @@ onMounted(() => {
|
||||||
box-shadow: 0 0 1px #888;
|
box-shadow: 0 0 1px #888;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-wrap {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { isExternal } from "@/utils/index";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const sidebar = computed(() => appStore.sidebar);
|
|
||||||
const device = computed(() => appStore.device);
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
to: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
function push() {
|
|
||||||
if (device.value === "mobile" && sidebar.value.opened == true) {
|
|
||||||
appStore.closeSideBar(false);
|
|
||||||
}
|
|
||||||
router.push(props.to).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
|
||||||
<slot></slot>
|
|
||||||
</a>
|
|
||||||
<div v-else @click="push">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import defaultSettings from "@/settings";
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
collapse: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const logo = ref(new URL(`../../../assets/logo.png`, import.meta.url).href);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="w-full h-[50px] bg-gray-800 dark:bg-[var(--el-bg-color-overlay)] logo-wrap"
|
|
||||||
>
|
|
||||||
<transition name="sidebarLogoFade">
|
|
||||||
<router-link
|
|
||||||
v-if="collapse"
|
|
||||||
key="collapse"
|
|
||||||
class="h-full w-full flex items-center justify-center"
|
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
<img v-if="settingsStore.sidebarLogo" :src="logo" class="w-5 h-5" />
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
|
||||||
v-else
|
|
||||||
key="expand"
|
|
||||||
class="h-full w-full flex items-center justify-center"
|
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
<img v-if="settingsStore.sidebarLogo" :src="logo" class="w-5 h-5" />
|
|
||||||
<span class="ml-3 text-white text-sm font-bold">
|
|
||||||
{{ defaultSettings.title }}</span
|
|
||||||
>
|
|
||||||
</router-link>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
// https://cn.vuejs.org/guide/built-ins/transition.html#the-transition-component
|
|
||||||
.sidebarLogoFade-enter-active {
|
|
||||||
transition: opacity 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarLogoFade-leave-active,
|
|
||||||
.sidebarLogoFade-enter-from,
|
|
||||||
.sidebarLogoFade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
|
||||||
import variables from "@/styles/variables.module.scss";
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
import { translateRouteTitle } from "@/utils/i18n";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const activePath = computed(() => appStore.activeTopMenu);
|
|
||||||
const router = useRouter();
|
|
||||||
// 递归跳转
|
|
||||||
const goFirst = (menu: any[]) => {
|
|
||||||
if (!menu.length) return;
|
|
||||||
const [first] = menu;
|
|
||||||
if (first.children) {
|
|
||||||
goFirst(first.children);
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
name: first.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const selectMenu = (index: string) => {
|
|
||||||
appStore.changeTopActive(index);
|
|
||||||
permissionStore.getMixLeftMenu(index);
|
|
||||||
const { mixLeftMenu } = permissionStore;
|
|
||||||
goFirst(mixLeftMenu);
|
|
||||||
};
|
|
||||||
const permissionStore = usePermissionStore();
|
|
||||||
const topMenu = ref<any[]>([]);
|
|
||||||
onMounted(() => {
|
|
||||||
topMenu.value = permissionStore.routes.filter(
|
|
||||||
(item) => !item.meta || !item.meta.hidden
|
|
||||||
);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<el-scrollbar>
|
|
||||||
<el-menu
|
|
||||||
mode="horizontal"
|
|
||||||
:default-active="activePath"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
@select="selectMenu"
|
|
||||||
>
|
|
||||||
<el-menu-item
|
|
||||||
v-for="route in topMenu"
|
|
||||||
:key="route.path"
|
|
||||||
:index="route.path"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
<svg-icon
|
|
||||||
v-if="route.meta && route.meta.icon"
|
|
||||||
:icon-class="route.meta.icon"
|
|
||||||
/>
|
|
||||||
<span v-if="route.path === '/'"> 首页 </span>
|
|
||||||
<template v-else>
|
|
||||||
<span v-if="route.meta && route.meta.title">
|
|
||||||
{{ translateRouteTitle(route.meta.title) }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-menu-item>
|
|
||||||
</el-menu>
|
|
||||||
</el-scrollbar>
|
|
||||||
</template>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.el-menu {
|
|
||||||
height: 50px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="logo-container">
|
||||||
|
<transition name="logo-transition">
|
||||||
|
<router-link v-if="collapse" class="wh-full flex-center" to="/">
|
||||||
|
<img v-if="settingsStore.sidebarLogo" :src="logo" class="logo-image" />
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link v-else class="wh-full flex-center" to="/">
|
||||||
|
<img v-if="settingsStore.sidebarLogo" :src="logo" class="logo-image" />
|
||||||
|
<span class="logo-title"> {{ defaultSettings.title }}</span>
|
||||||
|
</router-link>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import defaultSettings from "@/settings";
|
||||||
|
import { useSettingsStore } from "@/store";
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
collapse: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const logo = ref(new URL(`../../../../assets/logo.png`, import.meta.url).href);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.logo-container {
|
||||||
|
width: 100%;
|
||||||
|
height: $navbar-height;
|
||||||
|
background-color: $sidebar-logo-background;
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-title {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-top,
|
||||||
|
.layout-mix {
|
||||||
|
.logo-container {
|
||||||
|
width: $sidebar-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .logo-container {
|
||||||
|
width: $sidebar-width-collapsed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,12 +1,30 @@
|
||||||
<script lang="ts" setup>
|
<!-- 侧边菜单:包括左侧布局(all)、顶部布局(all)、混合布局(left) -->
|
||||||
import { useRoute } from "vue-router";
|
<template>
|
||||||
import SidebarItem from "./SidebarItem.vue";
|
<el-menu
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
:default-active="currRoute.path"
|
||||||
import { useAppStore } from "@/store/modules/app";
|
:collapse="!appStore.sidebar.opened"
|
||||||
import variables from "@/styles/variables.module.scss";
|
:background-color="variables['menu-background']"
|
||||||
|
:text-color="variables['menu-text']"
|
||||||
|
:active-text-color="variables['menu-active-text']"
|
||||||
|
:unique-opened="false"
|
||||||
|
:collapse-transition="false"
|
||||||
|
:mode="layout === 'top' ? 'horizontal' : 'vertical'"
|
||||||
|
>
|
||||||
|
<SidebarMenuItem
|
||||||
|
v-for="route in menuList"
|
||||||
|
:key="route.path"
|
||||||
|
:item="route"
|
||||||
|
:base-path="resolvePath(route.path)"
|
||||||
|
:is-collapse="!appStore.sidebar.opened"
|
||||||
|
/>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
import path from "path-browserify";
|
<script lang="ts" setup>
|
||||||
|
import { useSettingsStore, useAppStore } from "@/store";
|
||||||
import { isExternal } from "@/utils/index";
|
import { isExternal } from "@/utils/index";
|
||||||
|
import path from "path-browserify";
|
||||||
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
@ -44,23 +62,3 @@ function resolvePath(routePath: string) {
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
|
||||||
<el-menu
|
|
||||||
:default-active="currRoute.path"
|
|
||||||
:collapse="!appStore.sidebar.opened"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
:unique-opened="false"
|
|
||||||
:collapse-transition="false"
|
|
||||||
:mode="layout === 'top' ? 'horizontal' : 'vertical'"
|
|
||||||
>
|
|
||||||
<sidebar-item
|
|
||||||
v-for="route in menuList"
|
|
||||||
:key="route.path"
|
|
||||||
:item="route"
|
|
||||||
:base-path="resolvePath(route.path)"
|
|
||||||
:is-collapse="!appStore.sidebar.opened"
|
|
||||||
/>
|
|
||||||
</el-menu>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<component :is="type" v-bind="linkProps(to)">
|
||||||
|
<slot></slot>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: "AppLink",
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
import { isExternal } from "@/utils/index";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isExternalLink = computed(() => isExternal(props.to));
|
||||||
|
|
||||||
|
const type = computed(() => {
|
||||||
|
return isExternalLink.value ? "a" : "router-link";
|
||||||
|
});
|
||||||
|
|
||||||
|
const linkProps = (to: string) => {
|
||||||
|
if (isExternalLink.value) {
|
||||||
|
return {
|
||||||
|
href: to,
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noopener noreferrer",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
to: to,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<el-icon v-if="icon && icon.includes('el-icon')" class="sub-el-icon" />
|
<el-icon v-if="icon && icon.includes('el-icon')" class="sub-el-icon" />
|
||||||
<SvgIcon v-else-if="icon" :icon-class="icon" />
|
<SvgIcon v-else-if="icon" :icon-class="icon" />
|
||||||
<span v-if="title">{{ translateRouteTitle(title) }}</span>
|
<span v-if="title" class="ml-1">{{ translateRouteTitle(title) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -19,10 +19,19 @@ defineProps({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss" scoped>
|
||||||
.sub-el-icon {
|
.sub-el-icon {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
color: currentcolor;
|
color: currentcolor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideSidebar {
|
||||||
|
.el-sub-menu,
|
||||||
|
.el-menu-item {
|
||||||
|
.svg-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,11 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="!item.meta || !item.meta.hidden">
|
||||||
|
<!-- 显示具有单个子路由的菜单项或没有子路由的父路由 -->
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
hasOneShowingChild(item.children, item as RouteRecordRaw) &&
|
||||||
|
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
||||||
|
!item.meta?.alwaysShow
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<AppLink v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
||||||
|
<el-menu-item
|
||||||
|
:index="resolvePath(onlyOneChild.path)"
|
||||||
|
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||||
|
>
|
||||||
|
<MenuIconTitle
|
||||||
|
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
|
||||||
|
:title="onlyOneChild.meta.title"
|
||||||
|
/>
|
||||||
|
</el-menu-item>
|
||||||
|
</AppLink>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 显示具有多个子路由的父菜单项 -->
|
||||||
|
<el-sub-menu v-else :index="resolvePath(item.path)" teleported>
|
||||||
|
<template #title>
|
||||||
|
<MenuIconTitle
|
||||||
|
v-if="item.meta"
|
||||||
|
:icon="item.meta && item.meta.icon"
|
||||||
|
:title="item.meta.title"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<SidebarMenuItem
|
||||||
|
v-for="child in item.children"
|
||||||
|
:key="child.path"
|
||||||
|
:is-nest="true"
|
||||||
|
:item="child"
|
||||||
|
:base-path="resolvePath(child.path)"
|
||||||
|
/>
|
||||||
|
</el-sub-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: "SidebarMenuItem",
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
import path from "path-browserify";
|
import path from "path-browserify";
|
||||||
import { isExternal } from "@/utils/index";
|
import { isExternal } from "@/utils/index";
|
||||||
import AppLink from "./Link.vue";
|
|
||||||
import { RouteRecordRaw } from "vue-router";
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
import Item from "./Item.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**
|
/**
|
||||||
* 路由(eg:user)
|
* 路由(eg:user)
|
||||||
|
|
@ -87,52 +132,25 @@ function resolvePath(routePath: string) {
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
|
||||||
<div v-if="!item.meta || !item.meta.hidden">
|
|
||||||
<!-- 无子路由 || 目录只有一个子路由并配置始终显示为否(alwaysShow=false) -->
|
|
||||||
<template
|
|
||||||
v-if="
|
|
||||||
hasOneShowingChild(item.children, item as RouteRecordRaw) &&
|
|
||||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
|
||||||
!item.meta?.alwaysShow
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
|
||||||
<el-menu-item
|
|
||||||
:index="resolvePath(onlyOneChild.path)"
|
|
||||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
|
||||||
>
|
|
||||||
<item
|
|
||||||
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
|
|
||||||
:title="onlyOneChild.meta.title"
|
|
||||||
/>
|
|
||||||
</el-menu-item>
|
|
||||||
</app-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 有子路由 -->
|
|
||||||
<el-sub-menu v-else :index="resolvePath(item.path)" teleported>
|
|
||||||
<template #title>
|
|
||||||
<item
|
|
||||||
v-if="item.meta"
|
|
||||||
:icon="item.meta && item.meta.icon"
|
|
||||||
:title="item.meta.title"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<sidebar-item
|
|
||||||
v-for="child in item.children"
|
|
||||||
:key="child.path"
|
|
||||||
:is-nest="true"
|
|
||||||
:item="child"
|
|
||||||
:base-path="resolvePath(child.path)"
|
|
||||||
/>
|
|
||||||
</el-sub-menu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.el-menu-item .el-menu-tooltip__trigger) {
|
.submenu-title-noDropdown {
|
||||||
width: auto !important;
|
position: relative;
|
||||||
|
|
||||||
|
.el-tooltip {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-left: 19px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<!-- 混合布局菜单(top) -->
|
||||||
|
<template>
|
||||||
|
<el-scrollbar>
|
||||||
|
<el-menu
|
||||||
|
mode="horizontal"
|
||||||
|
:default-active="activePath"
|
||||||
|
:background-color="variables['menu-background']"
|
||||||
|
:text-color="variables['menu-text']"
|
||||||
|
:active-text-color="variables['menu-active-text']"
|
||||||
|
@select="handleMenuSelect"
|
||||||
|
>
|
||||||
|
<el-menu-item
|
||||||
|
v-for="route in mixTopMenus"
|
||||||
|
:key="route.path"
|
||||||
|
:index="route.path"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<svg-icon
|
||||||
|
v-if="route.meta && route.meta.icon"
|
||||||
|
:icon-class="route.meta.icon"
|
||||||
|
/>
|
||||||
|
<span v-if="route.path === '/'"> 首页 </span>
|
||||||
|
<template v-else>
|
||||||
|
<span v-if="route.meta && route.meta.title" class="ml-1">
|
||||||
|
{{ translateRouteTitle(route.meta.title) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
import { usePermissionStore, useAppStore } from "@/store";
|
||||||
|
import { translateRouteTitle } from "@/utils/i18n";
|
||||||
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const activePath = computed(() => appStore.activeTopMenu);
|
||||||
|
|
||||||
|
// 顶部菜单集合
|
||||||
|
const mixTopMenus = ref<RouteRecordRaw[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单选择事件
|
||||||
|
*/
|
||||||
|
const handleMenuSelect = (selectedRoutePath: string) => {
|
||||||
|
appStore.activeTopMenu(selectedRoutePath);
|
||||||
|
permissionStore.setMixLeftMenus(selectedRoutePath);
|
||||||
|
// 获取左侧菜单集合,并跳转到第一个菜单
|
||||||
|
const mixLeftMenus = permissionStore.mixLeftMenus;
|
||||||
|
goToFirstMenu(mixLeftMenus);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认跳转到左侧第一个菜单
|
||||||
|
*/
|
||||||
|
const goToFirstMenu = (menus: RouteRecordRaw[]) => {
|
||||||
|
if (menus.length === 0) return;
|
||||||
|
|
||||||
|
const [first] = menus;
|
||||||
|
|
||||||
|
if (first.children && first.children.length > 0) {
|
||||||
|
goToFirstMenu(first.children as RouteRecordRaw[]);
|
||||||
|
} else if (first.name) {
|
||||||
|
router.push({
|
||||||
|
name: first.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化顶部菜单
|
||||||
|
onMounted(() => {
|
||||||
|
mixTopMenus.value = permissionStore.routes.filter(
|
||||||
|
(item) => !item.meta || !item.meta.hidden
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -1,111 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{ 'has-logo': sidebarLogo }">
|
||||||
|
<!--混合布局-->
|
||||||
|
<div class="flex w-full" v-if="layout == 'mix'">
|
||||||
|
<SidebarLogo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
||||||
|
<SidebarMixTopMenu class="flex-1" />
|
||||||
|
<NavbarRight />
|
||||||
|
</div>
|
||||||
|
<!--左侧布局 || 顶部布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<SidebarLogo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
||||||
|
<el-scrollbar>
|
||||||
|
<SidebarMenu :menu-list="permissionStore.routes" base-path="" />
|
||||||
|
</el-scrollbar>
|
||||||
|
<NavbarRight v-if="layout === 'top'" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TopMenu from "./TopMenu.vue";
|
import { useSettingsStore, usePermissionStore, useAppStore } from "@/store";
|
||||||
import LeftMenu from "./LeftMenu.vue";
|
|
||||||
import Logo from "./Logo.vue";
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
const { sidebarLogo } = settingsStore;
|
||||||
const layout = computed(() => settingsStore.layout);
|
const layout = computed(() => settingsStore.layout);
|
||||||
const showContent = ref(true);
|
|
||||||
watch(
|
|
||||||
() => layout.value,
|
|
||||||
() => {
|
|
||||||
showContent.value = false;
|
|
||||||
nextTick(() => {
|
|
||||||
showContent.value = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:class="{ 'has-logo': sidebarLogo }"
|
|
||||||
class="menu-wrap"
|
|
||||||
v-if="layout !== 'mix'"
|
|
||||||
>
|
|
||||||
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
|
||||||
<el-scrollbar v-if="showContent">
|
|
||||||
<LeftMenu :menu-list="permissionStore.routes" base-path="" />
|
|
||||||
</el-scrollbar>
|
|
||||||
<NavRight v-if="layout === 'top'" />
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div :class="{ 'has-logo': sidebarLogo }" class="menu-wrap">
|
|
||||||
<div class="header">
|
|
||||||
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
|
||||||
<TopMenu />
|
|
||||||
<NavRight />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
:deep(.setting-container) {
|
|
||||||
.setting-item {
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.isMix {
|
|
||||||
.menu-wrap {
|
|
||||||
z-index: 99;
|
|
||||||
width: 100% !important;
|
|
||||||
height: 50px;
|
|
||||||
background-color: $menuBg;
|
|
||||||
|
|
||||||
:deep(.header) {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
// 顶部模式全局变量修改
|
|
||||||
--el-menu-item-height: 50px;
|
|
||||||
|
|
||||||
.logo-wrap {
|
|
||||||
width: $sideBarWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu {
|
|
||||||
background-color: $menuBg;
|
|
||||||
|
|
||||||
.el-menu-item {
|
|
||||||
color: $menuText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-menu {
|
|
||||||
display: inline-block;
|
|
||||||
width: $sideBarWidth;
|
|
||||||
background-color: $menuBg;
|
|
||||||
|
|
||||||
:deep(.el-menu) {
|
|
||||||
background-color: $menuBg;
|
|
||||||
|
|
||||||
.el-menu-item {
|
|
||||||
color: $menuText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
|
||||||
|
|
||||||
const tagAndTagSpacing = ref(4);
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
|
|
||||||
const emits = defineEmits(["scroll"]);
|
|
||||||
const emitScroll = () => {
|
|
||||||
emits("scroll");
|
|
||||||
};
|
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
|
||||||
|
|
||||||
const scrollWrapper = computed(
|
|
||||||
() => proxy?.$refs.scrollContainer.$refs.wrapRef
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveToTarget(currentTag: TagView) {
|
|
||||||
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 (tagsViewStore.visitedViews.length > 0) {
|
|
||||||
firstTag = tagsViewStore.visitedViews[0];
|
|
||||||
lastTag = tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstTag === currentTag) {
|
|
||||||
$scrollWrapper.scrollLeft = 0;
|
|
||||||
} else if (lastTag === currentTag) {
|
|
||||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
|
||||||
} else {
|
|
||||||
const tagListDom = document.getElementsByClassName("tags-item");
|
|
||||||
const currentIndex = tagsViewStore.visitedViews.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 ===
|
|
||||||
tagsViewStore.visitedViews[currentIndex - 1].path
|
|
||||||
) {
|
|
||||||
prevTag = tagListDom[k];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(tagListDom[k] as any).dataset.path ===
|
|
||||||
tagsViewStore.visitedViews[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;
|
|
||||||
|
|
||||||
// the tag's offsetLeft before of prevTag
|
|
||||||
const beforePrevTagOffsetLeft =
|
|
||||||
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
|
|
||||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
|
||||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
|
|
||||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
|
||||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
moveToTarget,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<el-scrollbar
|
|
||||||
ref="scrollContainer"
|
|
||||||
class="scroll-container"
|
|
||||||
:vertical="false"
|
|
||||||
@wheel.prevent="handleScroll"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
</el-scrollbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scroll-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
.el-scrollbar__bar {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar__wrap {
|
|
||||||
height: 49px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tags-container">
|
<div class="tags-container">
|
||||||
<scroll-pane ref="scrollPaneRef" @scroll="handleScroll">
|
<el-scrollbar
|
||||||
|
class="scroll-container"
|
||||||
|
:vertical="false"
|
||||||
|
@wheel.prevent="handleScroll"
|
||||||
|
>
|
||||||
<router-link
|
<router-link
|
||||||
ref="tagRef"
|
ref="tagRef"
|
||||||
v-for="tag in visitedViews"
|
v-for="tag in visitedViews"
|
||||||
:key="tag.fullPath"
|
:key="tag.fullPath"
|
||||||
:class="'tags-item ' + (isActive(tag) ? 'active' : '')"
|
:class="'tags-item ' + (isActive(tag) ? 'active' : '')"
|
||||||
:to="{ path: tag.fullPath, query: tag.query }"
|
:to="{ path: tag.path, query: tag.query }"
|
||||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||||
@contextmenu.prevent="openTagMenu(tag, $event)"
|
@contextmenu.prevent="openContentMenu(tag, $event)"
|
||||||
>
|
>
|
||||||
{{ translateRouteTitle(tag.title) }}
|
{{ translateRouteTitle(tag.title) }}
|
||||||
|
|
||||||
<i-ep-close
|
<i-ep-close
|
||||||
size="12px"
|
size="12px"
|
||||||
v-if="!isAffix(tag)"
|
v-if="!isAffix(tag)"
|
||||||
@click.prevent.stop="closeSelectedTag(tag)"
|
@click.prevent.stop="closeSelectedTag(tag)"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</scroll-pane>
|
</el-scrollbar>
|
||||||
|
|
||||||
<!-- tag标签操作菜单 -->
|
<!-- tag标签操作菜单 -->
|
||||||
<ul
|
<ul
|
||||||
v-show="tagMenuVisible"
|
v-show="contentMenuVisible"
|
||||||
class="tag-menu"
|
class="contextmenu"
|
||||||
:style="{ left: left + 'px', top: top + 'px' }"
|
:style="{ left: left + 'px', top: top + 'px' }"
|
||||||
>
|
>
|
||||||
<li @click="refreshSelectedTag(selectedTag)">
|
<li @click="refreshSelectedTag(selectedTag)">
|
||||||
|
|
@ -55,18 +58,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { useRoute, useRouter, RouteRecordRaw } from "vue-router";
|
import { useRoute, useRouter, RouteRecordRaw } from "vue-router";
|
||||||
import { resolve } from "path-browserify";
|
import { resolve } from "path-browserify";
|
||||||
|
|
||||||
import { translateRouteTitle } from "@/utils/i18n";
|
import { translateRouteTitle } from "@/utils/i18n";
|
||||||
|
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
import {
|
||||||
import { useTagsViewStore } from "@/store/modules/tagsView";
|
usePermissionStore,
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
useTagsViewStore,
|
||||||
import { useAppStore } from "@/store/modules/app";
|
useSettingsStore,
|
||||||
|
useAppStore,
|
||||||
import ScrollPane from "./ScrollPane.vue";
|
} from "@/store";
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()!;
|
const { proxy } = getCurrentInstance()!;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -90,7 +91,6 @@ const selectedTag = ref<TagView>({
|
||||||
});
|
});
|
||||||
|
|
||||||
const affixTags = ref<TagView[]>([]);
|
const affixTags = ref<TagView[]>([]);
|
||||||
const scrollPaneRef = ref();
|
|
||||||
const left = ref(0);
|
const left = ref(0);
|
||||||
const top = ref(0);
|
const top = ref(0);
|
||||||
|
|
||||||
|
|
@ -105,40 +105,39 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const tagMenuVisible = ref(false); // 标签操作菜单显示状态
|
const contentMenuVisible = ref(false); // 右键菜单是否显示
|
||||||
watch(tagMenuVisible, (value) => {
|
watch(contentMenuVisible, (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
document.body.addEventListener("click", closeTagMenu);
|
document.body.addEventListener("click", closeContentMenu);
|
||||||
} else {
|
} else {
|
||||||
document.body.removeEventListener("click", closeTagMenu);
|
document.body.removeEventListener("click", closeContentMenu);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤出需要固定的标签
|
||||||
|
*/
|
||||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
|
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
|
||||||
const processRoute = (route: RouteRecordRaw) => {
|
let tags: TagView[] = [];
|
||||||
const fullPath = resolve(basePath, route.path);
|
routes.forEach((route: RouteRecordRaw) => {
|
||||||
|
const tagPath = resolve(basePath, route.path);
|
||||||
const tag: TagView = {
|
if (route.meta?.affix) {
|
||||||
path: route.path,
|
tags.push({
|
||||||
fullPath,
|
path: tagPath,
|
||||||
|
fullPath: tagPath,
|
||||||
name: String(route.name),
|
name: String(route.name),
|
||||||
title: route.meta?.title || "no-name",
|
title: route.meta?.title || "no-name",
|
||||||
affix: route.meta?.affix,
|
affix: route.meta?.affix,
|
||||||
keepAlive: route.meta?.keepAlive,
|
keepAlive: route.meta?.keepAlive,
|
||||||
};
|
});
|
||||||
|
|
||||||
if (tag.affix) {
|
|
||||||
tags.push(tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
route.children.forEach(processRoute);
|
const tempTags = filterAffixTags(route.children, basePath + route.path);
|
||||||
|
if (tempTags.length >= 1) {
|
||||||
|
tags = [...tags, ...tempTags];
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
});
|
||||||
let tags: TagView[] = [];
|
|
||||||
routes.forEach(processRoute);
|
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +170,6 @@ function moveToCurrentTag() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
for (const tag of visitedViews.value) {
|
for (const tag of visitedViews.value) {
|
||||||
if (tag.path === route.path) {
|
if (tag.path === route.path) {
|
||||||
scrollPaneRef.value.moveToTarget(tag);
|
|
||||||
// when query is different then update
|
// when query is different then update
|
||||||
// route.query = { ...route.query, ...tag.query };
|
// route.query = { ...route.query, ...tag.query };
|
||||||
if (tag.fullPath !== route.fullPath) {
|
if (tag.fullPath !== route.fullPath) {
|
||||||
|
|
@ -190,7 +188,7 @@ function moveToCurrentTag() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActive(tag: TagView) {
|
function isActive(tag: TagView) {
|
||||||
return tag.fullPath === route.fullPath;
|
return tag.path === route.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAffix(tag: TagView) {
|
function isAffix(tag: TagView) {
|
||||||
|
|
@ -200,7 +198,7 @@ function isAffix(tag: TagView) {
|
||||||
function isFirstView() {
|
function isFirstView() {
|
||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
selectedTag.value.fullPath === "/dashboard" ||
|
selectedTag.value.path === "/dashboard" ||
|
||||||
selectedTag.value.fullPath === tagsViewStore.visitedViews[1].fullPath
|
selectedTag.value.fullPath === tagsViewStore.visitedViews[1].fullPath
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -253,18 +251,14 @@ function closeSelectedTag(view: TagView) {
|
||||||
|
|
||||||
function closeLeftTags() {
|
function closeLeftTags() {
|
||||||
tagsViewStore.delLeftViews(selectedTag.value).then((res: any) => {
|
tagsViewStore.delLeftViews(selectedTag.value).then((res: any) => {
|
||||||
if (
|
if (!res.visitedViews.find((item: any) => item.path === route.path)) {
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
|
||||||
) {
|
|
||||||
toLastView(res.visitedViews);
|
toLastView(res.visitedViews);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function closeRightTags() {
|
function closeRightTags() {
|
||||||
tagsViewStore.delRightViews(selectedTag.value).then((res: any) => {
|
tagsViewStore.delRightViews(selectedTag.value).then((res: any) => {
|
||||||
if (
|
if (!res.visitedViews.find((item: any) => item.path === route.path)) {
|
||||||
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
|
|
||||||
) {
|
|
||||||
toLastView(res.visitedViews);
|
toLastView(res.visitedViews);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -283,7 +277,10 @@ function closeAllTags(view: TagView) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTagMenu(tag: TagView, e: MouseEvent) {
|
/**
|
||||||
|
* 打开右键菜单
|
||||||
|
*/
|
||||||
|
function openContentMenu(tag: TagView, e: MouseEvent) {
|
||||||
const menuMinWidth = 105;
|
const menuMinWidth = 105;
|
||||||
|
|
||||||
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
|
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
|
||||||
|
|
@ -304,17 +301,24 @@ function openTagMenu(tag: TagView, e: MouseEvent) {
|
||||||
top.value = e.clientY;
|
top.value = e.clientY;
|
||||||
}
|
}
|
||||||
|
|
||||||
tagMenuVisible.value = true;
|
contentMenuVisible.value = true;
|
||||||
selectedTag.value = tag;
|
selectedTag.value = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeTagMenu() {
|
/**
|
||||||
tagMenuVisible.value = false;
|
* 关闭右键菜单
|
||||||
|
*/
|
||||||
|
function closeContentMenu() {
|
||||||
|
contentMenuVisible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动事件
|
||||||
|
*/
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
closeTagMenu();
|
closeContentMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOutermostParent(tree: any[], findName: string) {
|
function findOutermostParent(tree: any[], findName: string) {
|
||||||
let parentMap: any = {};
|
let parentMap: any = {};
|
||||||
|
|
||||||
|
|
@ -342,11 +346,12 @@ function findOutermostParent(tree: any[], findName: string) {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const againActiveTop = (newVal: string) => {
|
const againActiveTop = (newVal: string) => {
|
||||||
if (layout.value !== "mix") return;
|
if (layout.value !== "mix") return;
|
||||||
const parent = findOutermostParent(permissionStore.routes, newVal);
|
const parent = findOutermostParent(permissionStore.routes, newVal);
|
||||||
if (appStore.activeTopMenu !== parent.path) {
|
if (appStore.activeTopMenu !== parent.path) {
|
||||||
appStore.changeTopActive(parent.path);
|
appStore.activeTopMenu(parent.path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 如果是混合模式,更改selectedTag,需要对应高亮的activeTop
|
// 如果是混合模式,更改selectedTag,需要对应高亮的activeTop
|
||||||
|
|
@ -412,7 +417,7 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-menu {
|
.contextmenu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
@ -429,4 +434,19 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.el-scrollbar__bar {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__wrap {
|
||||||
|
height: 49px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export { default as Navbar } from "./NavBar/index.vue";
|
|
||||||
export { default as AppMain } from "./AppMain.vue";
|
|
||||||
export { default as Settings } from "./Settings/index.vue";
|
|
||||||
export { default as TagsView } from "./TagsView/index.vue";
|
|
||||||
|
|
@ -1,42 +1,79 @@
|
||||||
<script setup lang="ts">
|
<template>
|
||||||
import Main from "./main.vue";
|
<div class="wh-full" :class="classObj">
|
||||||
import { computed, watchEffect } from "vue";
|
<!-- 遮罩层 -->
|
||||||
import { useWindowSize } from "@vueuse/core";
|
<div
|
||||||
import Sidebar from "./components/Sidebar/index.vue";
|
v-if="classObj.mobile && classObj.openSidebar"
|
||||||
import LeftMenu from "./components/Sidebar/LeftMenu.vue";
|
class="fixed z-1000 bg-black bg-opacity-20"
|
||||||
|
@click="handleOutsideClick"
|
||||||
|
></div>
|
||||||
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
<Sidebar class="sidebar-container" />
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
<!-- 混合布局 -->
|
||||||
|
<div v-if="layout === 'mix'" class="mix-container">
|
||||||
|
<div class="mix-container__left">
|
||||||
|
<SidebarMenu :menu-list="mixLeftMenus" :base-path="activeTopMenu" />
|
||||||
|
<div class="sidebar-toggle">
|
||||||
|
<hamburger
|
||||||
|
:is-active="appStore.sidebar.opened"
|
||||||
|
@toggle-click="toggleSidebar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
|
<div :class="{ 'fixed-header': fixedHeader }">
|
||||||
|
<TagsView v-if="showTagsView" />
|
||||||
|
</div>
|
||||||
|
<AppMain />
|
||||||
|
<RightPanel v-if="showSettings">
|
||||||
|
<Settings />
|
||||||
|
</RightPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 左侧布局|| 顶部布局 -->
|
||||||
|
<div v-else :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
|
<div :class="{ 'fixed-header': fixedHeader }">
|
||||||
|
<Navbar v-if="layout === 'left'" />
|
||||||
|
<TagsView v-if="showTagsView" />
|
||||||
|
</div>
|
||||||
|
<AppMain />
|
||||||
|
<RightPanel v-if="showSettings">
|
||||||
|
<Settings />
|
||||||
|
</RightPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppStore, useSettingsStore, usePermissionStore } from "@/store";
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
/**
|
|
||||||
* 响应式布局容器固定宽度
|
const WIDTH = 992; // 响应式布局容器固定宽度 大屏(>=1200px) 中屏(>=992px) 小屏(>=768px)
|
||||||
*
|
|
||||||
* 大屏(>=1200px)
|
|
||||||
* 中屏(>=992px)
|
|
||||||
* 小屏(>=768px)
|
|
||||||
*/
|
|
||||||
const WIDTH = 992;
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||||
|
const showTagsView = computed(() => settingsStore.tagsView);
|
||||||
|
const showSettings = computed(() => settingsStore.showSettings);
|
||||||
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
|
||||||
const activeTopMenu = computed(() => {
|
const activeTopMenu = computed(() => {
|
||||||
return appStore.activeTopMenu;
|
return appStore.activeTopMenu;
|
||||||
});
|
});
|
||||||
// 混合模式左侧菜单
|
// 混合模式左侧菜单
|
||||||
const mixLeftMenu = computed(() => {
|
const mixLeftMenus = computed(() => {
|
||||||
return permissionStore.mixLeftMenu;
|
return permissionStore.mixLeftMenus;
|
||||||
});
|
});
|
||||||
const layout = computed(() => settingsStore.layout);
|
|
||||||
const watermarkEnabled = computed(() => settingsStore.watermark.enabled);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => activeTopMenu.value,
|
() => activeTopMenu.value,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (layout.value !== "mix") return;
|
if (layout.value !== "mix") return;
|
||||||
permissionStore.getMixLeftMenu(newVal);
|
permissionStore.setMixLeftMenus(newVal);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
|
|
@ -49,8 +86,8 @@ const classObj = computed(() => ({
|
||||||
openSidebar: appStore.sidebar.opened,
|
openSidebar: appStore.sidebar.opened,
|
||||||
withoutAnimation: appStore.sidebar.withoutAnimation,
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
mobile: appStore.device === "mobile",
|
mobile: appStore.device === "mobile",
|
||||||
isTop: layout.value === "top",
|
"layout-top": layout.value === "top",
|
||||||
isMix: layout.value === "mix",
|
"layout-mix": layout.value === "mix",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
|
@ -61,7 +98,6 @@ watchEffect(() => {
|
||||||
appStore.toggleDevice("desktop");
|
appStore.toggleDevice("desktop");
|
||||||
|
|
||||||
if (width.value >= 1200) {
|
if (width.value >= 1200) {
|
||||||
//大屏
|
|
||||||
appStore.openSideBar(true);
|
appStore.openSideBar(true);
|
||||||
} else {
|
} else {
|
||||||
appStore.closeSideBar(true);
|
appStore.closeSideBar(true);
|
||||||
|
|
@ -73,123 +109,117 @@ function handleOutsideClick() {
|
||||||
appStore.closeSideBar(false);
|
appStore.closeSideBar(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSideBar() {
|
function toggleSidebar() {
|
||||||
appStore.toggleSidebar();
|
appStore.toggleSidebar();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="classObj" class="app-wrapper">
|
|
||||||
<!-- 手机设备侧边栏打开遮罩层 -->
|
|
||||||
<div
|
|
||||||
v-if="classObj.mobile && classObj.openSidebar"
|
|
||||||
class="drawer__background"
|
|
||||||
@click="handleOutsideClick"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<Sidebar class="sidebar-container" />
|
|
||||||
|
|
||||||
<div v-if="layout === 'mix'" class="mix-wrapper">
|
|
||||||
<div class="mix-wrapper__left">
|
|
||||||
<LeftMenu :menu-list="mixLeftMenu" :base-path="activeTopMenu" />
|
|
||||||
<!-- 展开/收缩侧边栏菜单 -->
|
|
||||||
<div class="toggle-sidebar">
|
|
||||||
<hamburger
|
|
||||||
:is-active="appStore.sidebar.opened"
|
|
||||||
@toggle-click="toggleSideBar"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Main />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Main v-else />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.app-wrapper {
|
.sidebar-container {
|
||||||
&::after {
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&.mobile.openSidebar {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 999;
|
||||||
|
width: $sidebar-width;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: $menu-background;
|
||||||
|
transition: width 0.28s;
|
||||||
|
|
||||||
|
:deep(.el-menu) {
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__background {
|
.fixed-header {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 999;
|
right: 0;
|
||||||
width: 100%;
|
z-index: 9;
|
||||||
height: 100%;
|
width: calc(100% - $sidebar-width);
|
||||||
background: #000;
|
transition: width 0.28s;
|
||||||
opacity: 0.3;
|
|
||||||
}
|
}
|
||||||
// 导航栏顶部显示
|
|
||||||
.isTop {
|
.main-container {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100%;
|
||||||
|
margin-left: $sidebar-width;
|
||||||
|
transition: margin-left 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-top {
|
||||||
|
.fixed-header {
|
||||||
|
top: $navbar-height;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
z-index: 800;
|
z-index: 999;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 50px;
|
height: $navbar-height;
|
||||||
|
|
||||||
:deep(.logo-wrap) {
|
|
||||||
width: $sideBarWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-scrollbar) {
|
:deep(.el-scrollbar) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
height: $navbar-height;
|
||||||
height: 50px;
|
}
|
||||||
|
|
||||||
|
:deep(.el-menu-item),
|
||||||
|
:deep(.el-sub-menu__title) {
|
||||||
|
height: $navbar-height;
|
||||||
|
line-height: $navbar-height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
padding-top: 50px;
|
|
||||||
margin-left: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 顶部模式全局变量修改
|
|
||||||
--el-menu-item-height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile.isTop {
|
|
||||||
:deep(.logo-wrap) {
|
|
||||||
width: 63px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.isMix {
|
|
||||||
:deep(.main-container) {
|
|
||||||
display: inline-block;
|
|
||||||
width: calc(100% - #{$sideBarWidth});
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mix-wrapper {
|
.layout-mix {
|
||||||
|
.sidebar-container {
|
||||||
|
width: 100% !important;
|
||||||
|
height: $navbar-height;
|
||||||
|
|
||||||
|
:deep(.el-scrollbar) {
|
||||||
|
flex: 1;
|
||||||
|
height: $navbar-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-menu-item),
|
||||||
|
:deep(.el-sub-menu__title),
|
||||||
|
:deep(.el-menu--horizontal) {
|
||||||
|
height: $navbar-height;
|
||||||
|
line-height: $navbar-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-menu--horizontal.el-menu) {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header {
|
||||||
|
top: $navbar-height;
|
||||||
|
width: calc(100% - $sidebar-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mix-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-top: 50px;
|
padding-top: $navbar-height;
|
||||||
|
|
||||||
.mix-wrapper__left {
|
&__left {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: $sidebar-width;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.el-menu {
|
:deep(.el-menu) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-sidebar {
|
.sidebar-toggle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -201,11 +231,11 @@ function toggleSideBar() {
|
||||||
box-shadow: 0 0 6px -2px var(--el-color-primary);
|
box-shadow: 0 0 6px -2px var(--el-color-primary);
|
||||||
|
|
||||||
div:hover {
|
div:hover {
|
||||||
background-color: var(--menuBg);
|
background-color: var(--menu-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(svg) {
|
:deep(svg) {
|
||||||
color: #409eff !important;
|
color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,23 +243,47 @@ function toggleSideBar() {
|
||||||
.main-container {
|
.main-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.openSidebar {
|
.hideSidebar {
|
||||||
.mix-wrapper {
|
.mix-container__left {
|
||||||
.mix-wrapper__left {
|
width: $sidebar-width-collapsed;
|
||||||
width: $sideBarWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.svg-icon) {
|
.fixed-header {
|
||||||
margin-top: -1px;
|
width: calc(100% - $sidebar-width-collapsed);
|
||||||
margin-right: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu {
|
&.mobile {
|
||||||
border: none;
|
.fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-top {
|
||||||
|
.sidebar-container {
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
width: 100% !important;
|
||||||
|
height: $navbar-height;
|
||||||
|
|
||||||
|
:deep(.el-scrollbar) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: $navbar-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
padding-top: $navbar-height;
|
||||||
|
margin-left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶部模式全局变量修改
|
||||||
|
--el-menu-item-height: $navbar-height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
<template>
|
|
||||||
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
|
||||||
<div :class="{ 'fixed-header': fixedHeader, device: device }">
|
|
||||||
<navbar v-if="layout === 'left'" />
|
|
||||||
<tags-view v-if="showTagsView" />
|
|
||||||
</div>
|
|
||||||
<!--主页面-->
|
|
||||||
<app-main />
|
|
||||||
<!-- 设置面板 -->
|
|
||||||
<RightPanel v-if="showSettings">
|
|
||||||
<settings />
|
|
||||||
</RightPanel>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watchEffect } from "vue";
|
|
||||||
import { useWindowSize } from "@vueuse/core";
|
|
||||||
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
|
||||||
import RightPanel from "@/components/RightPanel/index.vue";
|
|
||||||
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
|
||||||
const { width } = useWindowSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应式布局容器固定宽度
|
|
||||||
*
|
|
||||||
* 大屏(>=1200px)
|
|
||||||
* 中屏(>=992px)
|
|
||||||
* 小屏(>=768px)
|
|
||||||
*/
|
|
||||||
const WIDTH = 992;
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
|
||||||
const showTagsView = computed(() => settingsStore.tagsView);
|
|
||||||
const showSettings = computed(() => settingsStore.showSettings);
|
|
||||||
const layout = computed(() => settingsStore.layout);
|
|
||||||
const device = computed(() => appStore.device);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (width.value < WIDTH) {
|
|
||||||
appStore.toggleDevice("mobile");
|
|
||||||
appStore.closeSideBar(true);
|
|
||||||
} else {
|
|
||||||
appStore.toggleDevice("desktop");
|
|
||||||
|
|
||||||
if (width.value >= 1200) {
|
|
||||||
//大屏
|
|
||||||
appStore.openSideBar(true);
|
|
||||||
} else {
|
|
||||||
appStore.closeSideBar(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 9;
|
|
||||||
width: calc(100% - #{$sideBarWidth});
|
|
||||||
transition: width 0.28s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
|
||||||
width: calc(100% - 54px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar.mobile .fixed-header {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body[layout="top"] .fixed-header {
|
|
||||||
top: 50px;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body[layout="mix"] .fixed-header {
|
|
||||||
top: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { setupDirective } from "@/directive";
|
||||||
|
|
||||||
import "@/permission";
|
import "@/permission";
|
||||||
|
|
||||||
|
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||||
|
|
||||||
// 本地SVG图标
|
// 本地SVG图标
|
||||||
import "virtual:svg-icons-register";
|
import "virtual:svg-icons-register";
|
||||||
|
|
||||||
|
|
@ -23,4 +25,8 @@ setupDirective(app);
|
||||||
// 全局注册 状态管理(store)
|
// 全局注册 状态管理(store)
|
||||||
setupStore(app);
|
setupStore(app);
|
||||||
|
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component);
|
||||||
|
}
|
||||||
|
|
||||||
app.use(router).use(i18n).mount("#app");
|
app.use(router).use(i18n).mount("#app");
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const whiteList = ["/login"];
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
const hasToken = localStorage.getItem("accessToken");
|
const hasToken = localStorage.getItem("token");
|
||||||
if (hasToken) {
|
if (hasToken) {
|
||||||
if (to.path === "/login") {
|
if (to.path === "/login") {
|
||||||
// 如果已登录,跳转首页
|
// 如果已登录,跳转首页
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,9 @@ export function setupStore(app: App<Element>) {
|
||||||
app.use(store);
|
app.use(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export * from "./modules/app";
|
||||||
|
export * from "./modules/permission";
|
||||||
|
export * from "./modules/settings";
|
||||||
|
export * from "./modules/tagsView";
|
||||||
|
export * from "./modules/user";
|
||||||
export { store };
|
export { store };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import { useStorage } from "@vueuse/core";
|
|
||||||
import defaultSettings from "@/settings";
|
import defaultSettings from "@/settings";
|
||||||
|
|
||||||
// 导入 Element Plus 中英文语言包
|
// 导入 Element Plus 中英文语言包
|
||||||
|
|
@ -19,7 +17,7 @@ export const useAppStore = defineStore("app", () => {
|
||||||
opened: sidebarStatus.value !== "closed",
|
opened: sidebarStatus.value !== "closed",
|
||||||
withoutAnimation: false,
|
withoutAnimation: false,
|
||||||
});
|
});
|
||||||
const activeTopMenu = useStorage("activeTop", "");
|
const activeTopMenuPath = useStorage("activeTopMenuPath", "");
|
||||||
/**
|
/**
|
||||||
* 根据语言标识读取对应的语言包
|
* 根据语言标识读取对应的语言包
|
||||||
*/
|
*/
|
||||||
|
|
@ -72,8 +70,8 @@ export const useAppStore = defineStore("app", () => {
|
||||||
/**
|
/**
|
||||||
* 混合模式顶部切换
|
* 混合模式顶部切换
|
||||||
*/
|
*/
|
||||||
function changeTopActive(val: string) {
|
function activeTopMenu(val: string) {
|
||||||
activeTopMenu.value = val;
|
activeTopMenuPath.value = val;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
device,
|
device,
|
||||||
|
|
@ -88,6 +86,6 @@ export const useAppStore = defineStore("app", () => {
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
closeSideBar,
|
closeSideBar,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
changeTopActive,
|
activeTopMenuPath,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -100,17 +100,24 @@ export const usePermissionStore = defineStore("permission", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 混合模式左侧菜单
|
* 获取与激活的顶部菜单项相关的混合模式左侧菜单集合
|
||||||
*/
|
*/
|
||||||
const mixLeftMenu = ref<RouteRecordRaw[]>([]);
|
const mixLeftMenus = ref<RouteRecordRaw[]>([]);
|
||||||
function getMixLeftMenu(activeTop: string) {
|
function setMixLeftMenus(activeTopMenu: string) {
|
||||||
routes.value.forEach((item) => {
|
const matchedItem = routes.value.find(
|
||||||
if (item.path === activeTop) {
|
(item) => item.path === activeTopMenu
|
||||||
mixLeftMenu.value = item.children || [];
|
);
|
||||||
|
if (matchedItem && matchedItem.children) {
|
||||||
|
mixLeftMenus.value = matchedItem.children;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return { routes, setRoutes, generateRoutes, getMixLeftMenu, mixLeftMenu };
|
return {
|
||||||
|
routes,
|
||||||
|
setRoutes,
|
||||||
|
generateRoutes,
|
||||||
|
mixLeftMenus,
|
||||||
|
setMixLeftMenus,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 非setup
|
// 非setup
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
|
|
||||||
export const useTagsViewStore = defineStore("tagsView", () => {
|
export const useTagsViewStore = defineStore("tagsView", () => {
|
||||||
const visitedViews = ref<TagView[]>([]);
|
const visitedViews = ref<TagView[]>([]);
|
||||||
const cachedViews = ref<string[]>([]);
|
const cachedViews = ref<string[]>([]);
|
||||||
|
|
@ -9,7 +7,7 @@ export const useTagsViewStore = defineStore("tagsView", () => {
|
||||||
*/
|
*/
|
||||||
function addVisitedView(view: TagView) {
|
function addVisitedView(view: TagView) {
|
||||||
// 如果已经存在于已访问的视图列表中,则不再添加
|
// 如果已经存在于已访问的视图列表中,则不再添加
|
||||||
if (visitedViews.value.some((v) => v.fullPath === view.fullPath)) {
|
if (visitedViews.value.some((v) => v.path === view.path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 如果视图是固定的(affix),则在已访问的视图列表的开头添加
|
// 如果视图是固定的(affix),则在已访问的视图列表的开头添加
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
|
|
||||||
import { loginApi, logoutApi } from "@/api/auth";
|
import { loginApi, logoutApi } from "@/api/auth";
|
||||||
import { getUserInfoApi } from "@/api/user";
|
import { getUserInfoApi } from "@/api/user";
|
||||||
import { resetRouter } from "@/router";
|
import { resetRouter } from "@/router";
|
||||||
|
|
@ -8,16 +6,12 @@ import { store } from "@/store";
|
||||||
import { LoginData } from "@/api/auth/types";
|
import { LoginData } from "@/api/auth/types";
|
||||||
import { UserInfo } from "@/api/user/types";
|
import { UserInfo } from "@/api/user/types";
|
||||||
|
|
||||||
import { useStorage } from "@vueuse/core";
|
|
||||||
|
|
||||||
export const useUserStore = defineStore("user", () => {
|
export const useUserStore = defineStore("user", () => {
|
||||||
const user: UserInfo = {
|
const user: UserInfo = {
|
||||||
roles: [],
|
roles: [],
|
||||||
perms: [],
|
perms: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const token = useStorage("accessToken", "");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
*
|
*
|
||||||
|
|
@ -29,7 +23,7 @@ export const useUserStore = defineStore("user", () => {
|
||||||
loginApi(loginData)
|
loginApi(loginData)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const { tokenType, accessToken } = response.data;
|
const { tokenType, accessToken } = response.data;
|
||||||
token.value = tokenType + " " + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
|
localStorage.setItem("token", tokenType + " " + accessToken); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
@ -65,7 +59,7 @@ export const useUserStore = defineStore("user", () => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
logoutApi()
|
logoutApi()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
token.value = "";
|
localStorage.setItem("token", "");
|
||||||
location.reload(); // 清空路由
|
location.reload(); // 清空路由
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
|
|
@ -77,15 +71,15 @@ export const useUserStore = defineStore("user", () => {
|
||||||
|
|
||||||
// remove token
|
// remove token
|
||||||
function resetToken() {
|
function resetToken() {
|
||||||
|
console.log("resetToken");
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
token.value = "";
|
localStorage.setItem("token", "");
|
||||||
resetRouter();
|
resetRouter();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
|
||||||
user,
|
user,
|
||||||
login,
|
login,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
html.dark {
|
html.dark {
|
||||||
--menuBg: var(--el-bg-color-overlay);
|
--menu-background: var(--el-bg-color-overlay);
|
||||||
--menuText: #fff;
|
--menu-text: #fff;
|
||||||
--menuActiveText: var(--el-menu-active-color);
|
--menuActiveText: var(--el-menu-active-color);
|
||||||
--menuHover: rgb(0 0 0 / 20%);
|
--menuHover: rgb(0 0 0 / 20%);
|
||||||
--subMenuBg: var(--el-menu-bg-color);
|
--sub-menu-background: var(--el-menu-bg-color);
|
||||||
--subMenuActiveText: var(--el-menu-active-color);
|
--subMenuActiveText: var(--el-menu-active-color);
|
||||||
--subMenuHover: rgb(0 0 0 / 20%);
|
--subMenuHover: rgb(0 0 0 / 20%);
|
||||||
|
|
||||||
|
|
@ -38,10 +38,4 @@ html.dark {
|
||||||
.right-panel-btn {
|
.right-panel-btn {
|
||||||
background-color: var(--el-color-primary-dark);
|
background-color: var(--el-color-primary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
.el-menu-item.is-active .svg-icon {
|
|
||||||
fill: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,89 +1,4 @@
|
||||||
.app {
|
.app {
|
||||||
.main-container {
|
|
||||||
position: relative;
|
|
||||||
min-height: 100%;
|
|
||||||
margin-left: $sideBarWidth;
|
|
||||||
transition: margin-left 0.28s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1001;
|
|
||||||
width: $sideBarWidth !important;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: $menuBg;
|
|
||||||
transition: width 0.28s;
|
|
||||||
|
|
||||||
// reset element-ui css
|
|
||||||
.horizontal-collapse-transition {
|
|
||||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out,
|
|
||||||
0s padding-right ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollbar-wrapper {
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar__bar.is-vertical {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-scrollbar {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-logo {
|
|
||||||
.el-scrollbar {
|
|
||||||
height: calc(100% - 50px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-horizontal {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu {
|
|
||||||
width: 100% !important;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// menu hover
|
|
||||||
.submenu-title-noDropdown,
|
|
||||||
.el-sub-menu__title {
|
|
||||||
&:hover {
|
|
||||||
background-color: $menuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-active > .el-sub-menu__title {
|
|
||||||
color: $subMenuActiveText !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
|
||||||
& .el-sub-menu .el-menu-item {
|
|
||||||
min-width: $sideBarWidth !important;
|
|
||||||
background-color: $subMenuBg !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $subMenuHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar {
|
.hideSidebar {
|
||||||
.mix-wrapper__left {
|
.mix-wrapper__left {
|
||||||
width: 54px;
|
width: 54px;
|
||||||
|
|
@ -111,20 +26,11 @@
|
||||||
.el-tooltip {
|
.el-tooltip {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
.sub-el-icon {
|
||||||
margin-left: 19px;
|
margin-left: 19px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
& > .svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
@ -140,10 +46,6 @@
|
||||||
& > .el-sub-menu__title {
|
& > .el-sub-menu__title {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
.sub-el-icon {
|
||||||
margin-left: 19px;
|
margin-left: 19px;
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +72,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu--collapse .el-menu .el-sub-menu {
|
.el-menu--collapse .el-menu .el-sub-menu {
|
||||||
min-width: $sideBarWidth !important;
|
min-width: $sidebar-width !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mobile responsive
|
// mobile responsive
|
||||||
|
|
@ -180,15 +82,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
width: $sideBarWidth !important;
|
width: $sidebar-width !important;
|
||||||
transition: transform 0.28s;
|
transition: transform 0.28s;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hideSidebar:not(.isMix, .isTop) {
|
&.hideSidebar:not(.layout-mix, .layout-top) {
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition-duration: 0.3s;
|
transition-duration: 0.3s;
|
||||||
transform: translate3d(-$sideBarWidth, 0, 0);
|
transform: translate3d(-$sidebar-width, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -204,10 +106,6 @@
|
||||||
// when menu collapsed
|
// when menu collapsed
|
||||||
.el-menu--vertical {
|
.el-menu--vertical {
|
||||||
& > .el-menu {
|
& > .el-menu {
|
||||||
.svg-icon {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-el-icon {
|
.sub-el-icon {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
margin-left: -2px;
|
margin-left: -2px;
|
||||||
|
|
@ -218,7 +116,7 @@
|
||||||
.el-menu-item {
|
.el-menu-item {
|
||||||
&:hover {
|
&:hover {
|
||||||
// you can use $subMenuHover
|
// you can use $subMenuHover
|
||||||
background-color: $menuHover !important;
|
background-color: $menu-hover !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,29 @@
|
||||||
// global transition css
|
/* 简化和统一后的命名示例 */
|
||||||
|
|
||||||
/* fade */
|
/* 淡入淡出 */
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
.fade-leave-active {
|
.fade-leave-active {
|
||||||
transition: opacity 0.28s;
|
transition: opacity 0.28s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter,
|
.fade-enter-from,
|
||||||
.fade-leave-active {
|
.fade-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fade-transform */
|
/* 平移和淡入淡出 */
|
||||||
.fade-transform-leave-active,
|
.fade-translate-enter-active,
|
||||||
.fade-transform-enter-active {
|
.fade-translate-leave-active {
|
||||||
transition: all 0.5s;
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-transform-enter {
|
.fade-translate-enter-from,
|
||||||
|
.fade-translate-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(-30px);
|
transform: translateX(-30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-transform-leave-to {
|
/* 缩放 */
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* breadcrumb transition */
|
|
||||||
.breadcrumb-enter-active,
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
transition: all 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-enter,
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-move {
|
|
||||||
transition: all 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 缩放过渡 */
|
|
||||||
.fade-scale-enter-active,
|
.fade-scale-enter-active,
|
||||||
.fade-scale-leave-active {
|
.fade-scale-leave-active {
|
||||||
transition: transform 0.3s ease-in-out;
|
transition: transform 0.3s ease-in-out;
|
||||||
|
|
@ -58,22 +34,7 @@
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-slide-leave-active,
|
/* 旋转 */
|
||||||
.fade-slide-enter-active {
|
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-slide-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-slide-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 旋转过渡 */
|
|
||||||
.fade-rotate-enter-active,
|
.fade-rotate-enter-active,
|
||||||
.fade-rotate-leave-active {
|
.fade-rotate-leave-active {
|
||||||
transition: transform 0.3s ease-in-out;
|
transition: transform 0.3s ease-in-out;
|
||||||
|
|
@ -83,3 +44,28 @@
|
||||||
.fade-rotate-leave-to {
|
.fade-rotate-leave-to {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 面包屑 */
|
||||||
|
.breadcrumb-transition-enter-active,
|
||||||
|
.breadcrumb-transition-leave-active {
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-transition-enter-from,
|
||||||
|
.breadcrumb-transition-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo动画 */
|
||||||
|
|
||||||
|
// 进入动画,2s内过渡到完全不透明(opacity默认值为1,不需要显示声明)
|
||||||
|
.logo-transition-enter-active {
|
||||||
|
transition: opacity 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进入动画开始时和离开动画的样式状态(opacity)
|
||||||
|
.logo-transition-enter-from,
|
||||||
|
.logo-transition-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
// 导出 variables.module.scss 变量提供给TypeScript使用
|
/* stylelint-disable property-no-unknown */
|
||||||
:export {
|
:export {
|
||||||
menuBg: $menuBg;
|
sidebar-width: $sidebar-width;
|
||||||
menuText: $menuText;
|
navbar-height: $navbar-height;
|
||||||
menuActiveText: $menuActiveText;
|
menu-background: $menu-background;
|
||||||
|
menu-text: $menu-text;
|
||||||
|
menu-active-text: $menu-active-text;
|
||||||
|
menu-hover: $menu-hover;
|
||||||
|
sub-menu-background: $sub-menu-background;
|
||||||
|
sub-menu-active-text: $sub-menu-active-text;
|
||||||
|
sub-menu-hover: $sub-menu-hover;
|
||||||
}
|
}
|
||||||
|
/* stylelint-enable property-no-unknown */
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,37 @@
|
||||||
// 全局SCSS变量
|
/* 全局SCSS变量 */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--menuBg: #304156;
|
--menu-background: #304156;
|
||||||
--menuText: #bfcbd9;
|
--menu-text: #bfcbd9;
|
||||||
--menuActiveText: #409eff;
|
--menu-active-text: #409eff;
|
||||||
--menuHover: #263445;
|
--menu-hover: #263445;
|
||||||
--subMenuBg: #1f2d3d;
|
--sub-menu-background: #1f2d3d;
|
||||||
--subMenuActiveText: #f4f4f5;
|
--sub-menu-active-text: #f4f4f5;
|
||||||
--subMenuHover: #001528;
|
--sub-menu-hover: #001528;
|
||||||
|
--sidebar-logo-background: #2d3748;
|
||||||
// wang-editor textarea
|
|
||||||
--w-e-textarea-slight-border-color: var(--el-color-primary);
|
|
||||||
--w-e-textarea-slight-bg-color: rgb(var(--el-color-primary-rgb) 0.1);
|
|
||||||
--w-e-textarea-selected-border-color: var(--el-color-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$menuBg: var(--menuBg);
|
html.dark {
|
||||||
$menuText: var(--menuText);
|
--menu-background: var(--el-bg-color-overlay);
|
||||||
$menuActiveText: var(--menuActiveText);
|
--menu-text: #fff;
|
||||||
$menuHover: var(--menuHover);
|
--menu-active-text: var(--el-menu-active-color);
|
||||||
|
--menu-hover: rgb(0 0 0 / 20%);
|
||||||
|
--sub-menu-background: var(--el-menu-bg-color);
|
||||||
|
--sub-menu-active-text: var(--el-menu-active-color);
|
||||||
|
--sub-menu-hover: rgb(0 0 0 / 20%);
|
||||||
|
--sidebar-logo-background: rgb(0 0 0 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
$subMenuBg: var(--subMenuBg);
|
$menu-background: var(--menu-background);
|
||||||
$subMenuActiveText: var(--subMenuActiveText);
|
$menu-text: var(--menu-text);
|
||||||
$subMenuHover: var(--subMenuHover);
|
$menu-active-text: var(--menu-active-text);
|
||||||
|
$menu-hover: var(--menu-hover);
|
||||||
|
$sub-menu-background: var(--sub-menu-background);
|
||||||
|
$sub-menu-active-text: var(--sub-menu-active-text);
|
||||||
|
$sub-menu-hover: var(--sub-menu-hover);
|
||||||
|
$sidebar-logo-background: var(--sidebar-logo-background); // 侧边栏 Logo 背景色
|
||||||
|
|
||||||
$sideBarWidth: 210px;
|
$sidebar-width: 210px; // 侧边栏宽度
|
||||||
|
$sidebar-width-collapsed: 54px; // 侧边栏收缩宽度
|
||||||
|
$navbar-height: 50px; // 导航栏高度
|
||||||
|
$tags-view-height: 34px; // TagsView 高度
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ declare global {
|
||||||
const useFullscreen: typeof import("@vueuse/core")["useFullscreen"];
|
const useFullscreen: typeof import("@vueuse/core")["useFullscreen"];
|
||||||
const useGamepad: typeof import("@vueuse/core")["useGamepad"];
|
const useGamepad: typeof import("@vueuse/core")["useGamepad"];
|
||||||
const useGeolocation: typeof import("@vueuse/core")["useGeolocation"];
|
const useGeolocation: typeof import("@vueuse/core")["useGeolocation"];
|
||||||
|
const useI18n: typeof import("vue-i18n")["useI18n"];
|
||||||
const useIdle: typeof import("@vueuse/core")["useIdle"];
|
const useIdle: typeof import("@vueuse/core")["useIdle"];
|
||||||
const useImage: typeof import("@vueuse/core")["useImage"];
|
const useImage: typeof import("@vueuse/core")["useImage"];
|
||||||
const useInfiniteScroll: typeof import("@vueuse/core")["useInfiniteScroll"];
|
const useInfiniteScroll: typeof import("@vueuse/core")["useInfiniteScroll"];
|
||||||
|
|
@ -741,6 +742,7 @@ declare module "vue" {
|
||||||
readonly useGeolocation: UnwrapRef<
|
readonly useGeolocation: UnwrapRef<
|
||||||
typeof import("@vueuse/core")["useGeolocation"]
|
typeof import("@vueuse/core")["useGeolocation"]
|
||||||
>;
|
>;
|
||||||
|
readonly useI18n: UnwrapRef<typeof import("vue-i18n")["useI18n"]>;
|
||||||
readonly useIdle: UnwrapRef<typeof import("@vueuse/core")["useIdle"]>;
|
readonly useIdle: UnwrapRef<typeof import("@vueuse/core")["useIdle"]>;
|
||||||
readonly useImage: UnwrapRef<typeof import("@vueuse/core")["useImage"]>;
|
readonly useImage: UnwrapRef<typeof import("@vueuse/core")["useImage"]>;
|
||||||
readonly useInfiniteScroll: UnwrapRef<
|
readonly useInfiniteScroll: UnwrapRef<
|
||||||
|
|
@ -1427,6 +1429,7 @@ declare module "@vue/runtime-core" {
|
||||||
readonly useGeolocation: UnwrapRef<
|
readonly useGeolocation: UnwrapRef<
|
||||||
typeof import("@vueuse/core")["useGeolocation"]
|
typeof import("@vueuse/core")["useGeolocation"]
|
||||||
>;
|
>;
|
||||||
|
readonly useI18n: UnwrapRef<typeof import("vue-i18n")["useI18n"]>;
|
||||||
readonly useIdle: UnwrapRef<typeof import("@vueuse/core")["useIdle"]>;
|
readonly useIdle: UnwrapRef<typeof import("@vueuse/core")["useIdle"]>;
|
||||||
readonly useImage: UnwrapRef<typeof import("@vueuse/core")["useImage"]>;
|
readonly useImage: UnwrapRef<typeof import("@vueuse/core")["useImage"]>;
|
||||||
readonly useInfiniteScroll: UnwrapRef<
|
readonly useInfiniteScroll: UnwrapRef<
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ export {};
|
||||||
|
|
||||||
declare module "@vue/runtime-core" {
|
declare module "@vue/runtime-core" {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AppMain: typeof import("./../layout/components/AppMain.vue")["default"];
|
AppLink: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/AppLink.vue")["default"];
|
||||||
|
AppMain: typeof import("./../layout/components/AppMain/index.vue")["default"];
|
||||||
BarChart: typeof import("./../views/dashboard/components/BarChart.vue")["default"];
|
BarChart: typeof import("./../views/dashboard/components/BarChart.vue")["default"];
|
||||||
Breadcrumb: typeof import("./../components/Breadcrumb/index.vue")["default"];
|
Breadcrumb: typeof import("./../components/Breadcrumb/index.vue")["default"];
|
||||||
|
Components: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/index.vue")["default"];
|
||||||
DeptTree: typeof import("./../views/system/user/components/dept-tree.vue")["default"];
|
DeptTree: typeof import("./../views/system/user/components/dept-tree.vue")["default"];
|
||||||
Dictionary: typeof import("./../components/Dictionary/index.vue")["default"];
|
Dictionary: typeof import("./../components/Dictionary/index.vue")["default"];
|
||||||
DictItem: typeof import("./../views/system/dict/components/dict-item.vue")["default"];
|
DictItem: typeof import("./../views/system/dict/components/dict-item.vue")["default"];
|
||||||
|
|
@ -23,6 +25,7 @@ declare module "@vue/runtime-core" {
|
||||||
ElCheckbox: typeof import("element-plus/es")["ElCheckbox"];
|
ElCheckbox: typeof import("element-plus/es")["ElCheckbox"];
|
||||||
ElCheckboxGroup: typeof import("element-plus/es")["ElCheckboxGroup"];
|
ElCheckboxGroup: typeof import("element-plus/es")["ElCheckboxGroup"];
|
||||||
ElCol: typeof import("element-plus/es")["ElCol"];
|
ElCol: typeof import("element-plus/es")["ElCol"];
|
||||||
|
ElConfigProvider: typeof import("element-plus/es")["ElConfigProvider"];
|
||||||
ElDatePicker: typeof import("element-plus/es")["ElDatePicker"];
|
ElDatePicker: typeof import("element-plus/es")["ElDatePicker"];
|
||||||
ElDialog: typeof import("element-plus/es")["ElDialog"];
|
ElDialog: typeof import("element-plus/es")["ElDialog"];
|
||||||
ElDivider: typeof import("element-plus/es")["ElDivider"];
|
ElDivider: typeof import("element-plus/es")["ElDivider"];
|
||||||
|
|
@ -48,6 +51,7 @@ declare module "@vue/runtime-core" {
|
||||||
ElRow: typeof import("element-plus/es")["ElRow"];
|
ElRow: typeof import("element-plus/es")["ElRow"];
|
||||||
ElScrollbar: typeof import("element-plus/es")["ElScrollbar"];
|
ElScrollbar: typeof import("element-plus/es")["ElScrollbar"];
|
||||||
ElSelect: typeof import("element-plus/es")["ElSelect"];
|
ElSelect: typeof import("element-plus/es")["ElSelect"];
|
||||||
|
ElStatistic: typeof import("element-plus/es")["ElStatistic"];
|
||||||
ElSubMenu: typeof import("element-plus/es")["ElSubMenu"];
|
ElSubMenu: typeof import("element-plus/es")["ElSubMenu"];
|
||||||
ElSwitch: typeof import("element-plus/es")["ElSwitch"];
|
ElSwitch: typeof import("element-plus/es")["ElSwitch"];
|
||||||
ElTable: typeof import("element-plus/es")["ElTable"];
|
ElTable: typeof import("element-plus/es")["ElTable"];
|
||||||
|
|
@ -55,6 +59,7 @@ declare module "@vue/runtime-core" {
|
||||||
ElTabPane: typeof import("element-plus/es")["ElTabPane"];
|
ElTabPane: typeof import("element-plus/es")["ElTabPane"];
|
||||||
ElTabs: typeof import("element-plus/es")["ElTabs"];
|
ElTabs: typeof import("element-plus/es")["ElTabs"];
|
||||||
ElTag: typeof import("element-plus/es")["ElTag"];
|
ElTag: typeof import("element-plus/es")["ElTag"];
|
||||||
|
ElText: typeof import("element-plus/es")["ElText"];
|
||||||
ElTooltip: typeof import("element-plus/es")["ElTooltip"];
|
ElTooltip: typeof import("element-plus/es")["ElTooltip"];
|
||||||
ElTree: typeof import("element-plus/es")["ElTree"];
|
ElTree: typeof import("element-plus/es")["ElTree"];
|
||||||
ElTreeSelect: typeof import("element-plus/es")["ElTreeSelect"];
|
ElTreeSelect: typeof import("element-plus/es")["ElTreeSelect"];
|
||||||
|
|
@ -64,9 +69,11 @@ declare module "@vue/runtime-core" {
|
||||||
GithubCorner: typeof import("./../components/GithubCorner/index.vue")["default"];
|
GithubCorner: typeof import("./../components/GithubCorner/index.vue")["default"];
|
||||||
Hamburger: typeof import("./../components/Hamburger/index.vue")["default"];
|
Hamburger: typeof import("./../components/Hamburger/index.vue")["default"];
|
||||||
IconSelect: typeof import("./../components/IconSelect/index.vue")["default"];
|
IconSelect: typeof import("./../components/IconSelect/index.vue")["default"];
|
||||||
|
IconTitle: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/IconTitle.vue")["default"];
|
||||||
IEpArrowDown: typeof import("~icons/ep/arrow-down")["default"];
|
IEpArrowDown: typeof import("~icons/ep/arrow-down")["default"];
|
||||||
IEpCaretBottom: typeof import("~icons/ep/caret-bottom")["default"];
|
IEpCaretBottom: typeof import("~icons/ep/caret-bottom")["default"];
|
||||||
IEpCaretTop: typeof import("~icons/ep/caret-top")["default"];
|
IEpCaretTop: typeof import("~icons/ep/caret-top")["default"];
|
||||||
|
IEpCheck: typeof import("~icons/ep/check")["default"];
|
||||||
IEpClose: typeof import("~icons/ep/close")["default"];
|
IEpClose: typeof import("~icons/ep/close")["default"];
|
||||||
IEpCollection: typeof import("~icons/ep/collection")["default"];
|
IEpCollection: typeof import("~icons/ep/collection")["default"];
|
||||||
IEpDelete: typeof import("~icons/ep/delete")["default"];
|
IEpDelete: typeof import("~icons/ep/delete")["default"];
|
||||||
|
|
@ -75,6 +82,7 @@ declare module "@vue/runtime-core" {
|
||||||
IEpPicture: typeof import("~icons/ep/picture")["default"];
|
IEpPicture: typeof import("~icons/ep/picture")["default"];
|
||||||
IEpPlus: typeof import("~icons/ep/plus")["default"];
|
IEpPlus: typeof import("~icons/ep/plus")["default"];
|
||||||
IEpPosition: typeof import("~icons/ep/position")["default"];
|
IEpPosition: typeof import("~icons/ep/position")["default"];
|
||||||
|
IEpQuestionFilled: typeof import("~icons/ep/question-filled")["default"];
|
||||||
IEpRefresh: typeof import("~icons/ep/refresh")["default"];
|
IEpRefresh: typeof import("~icons/ep/refresh")["default"];
|
||||||
IEpRefreshLeft: typeof import("~icons/ep/refresh-left")["default"];
|
IEpRefreshLeft: typeof import("~icons/ep/refresh-left")["default"];
|
||||||
IEpSearch: typeof import("~icons/ep/search")["default"];
|
IEpSearch: typeof import("~icons/ep/search")["default"];
|
||||||
|
|
@ -83,14 +91,25 @@ declare module "@vue/runtime-core" {
|
||||||
IEpSortUp: typeof import("~icons/ep/sort-up")["default"];
|
IEpSortUp: typeof import("~icons/ep/sort-up")["default"];
|
||||||
IEpTop: typeof import("~icons/ep/top")["default"];
|
IEpTop: typeof import("~icons/ep/top")["default"];
|
||||||
IEpUploadFilled: typeof import("~icons/ep/upload-filled")["default"];
|
IEpUploadFilled: typeof import("~icons/ep/upload-filled")["default"];
|
||||||
Item: typeof import("./../layout/components/Sidebar/Item.vue")["default"];
|
Item: typeof import("./../layout/components/Sidebar/components/Item.vue")["default"];
|
||||||
LangSelect: typeof import("./../components/LangSelect/index.vue")["default"];
|
LangSelect: typeof import("./../components/LangSelect/index.vue")["default"];
|
||||||
LeftMenu: typeof import("./../layout/components/Sidebar/LeftMenu.vue")["default"];
|
LeftMenu: typeof import("./../layout/components/Sidebar/components/LeftMenu.vue")["default"];
|
||||||
Link: typeof import("./../layout/components/Sidebar/Link.vue")["default"];
|
Link: typeof import("./../layout/components/Sidebar/Link.vue")["default"];
|
||||||
Logo: typeof import("./../layout/components/Sidebar/Logo.vue")["default"];
|
Logo: typeof import("./../layout/components/Sidebar/components/Logo.vue")["default"];
|
||||||
|
Menu: typeof import("./../layout/components/Sidebar/components/Menu/index.vue")["default"];
|
||||||
|
MenuIconTitle: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/MenuIconTitle.vue")["default"];
|
||||||
|
MenuItem: typeof import("./../layout/components/Sidebar/components/MenuItem/index.vue")["default"];
|
||||||
|
MenuItemContent: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/MenuItemContent.vue")["default"];
|
||||||
|
MenuItemLink: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/MenuItemLink.vue")["default"];
|
||||||
|
MenuItemTitle: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/MenuItemTitle.vue")["default"];
|
||||||
|
MenuLink: typeof import("./../components/AppLink/MenuLink.vue")["default"];
|
||||||
|
MenuTitle: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/components/MenuTitle.vue")["default"];
|
||||||
|
MixLayoutTopMenu: typeof import("./../layout/components/Sidebar/components/MixLayoutTopMenu.vue")["default"];
|
||||||
MultiUpload: typeof import("./../components/Upload/MultiUpload.vue")["default"];
|
MultiUpload: typeof import("./../components/Upload/MultiUpload.vue")["default"];
|
||||||
|
Navbar: typeof import("./../layout/components/Navbar/index.vue")["default"];
|
||||||
NavBar: typeof import("./../layout/components/NavBar/index.vue")["default"];
|
NavBar: typeof import("./../layout/components/NavBar/index.vue")["default"];
|
||||||
NavRight: typeof import("./../layout/components/NavBar/NavRight.vue")["default"];
|
NavbarLeft: typeof import("./../layout/components/Navbar/components/NavbarLeft.vue")["default"];
|
||||||
|
NavbarRight: typeof import("./../layout/components/Navbar/components/NavbarRight.vue")["default"];
|
||||||
Pagination: typeof import("./../components/Pagination/index.vue")["default"];
|
Pagination: typeof import("./../components/Pagination/index.vue")["default"];
|
||||||
PieChart: typeof import("./../views/dashboard/components/PieChart.vue")["default"];
|
PieChart: typeof import("./../views/dashboard/components/PieChart.vue")["default"];
|
||||||
RadarChart: typeof import("./../views/dashboard/components/RadarChart.vue")["default"];
|
RadarChart: typeof import("./../views/dashboard/components/RadarChart.vue")["default"];
|
||||||
|
|
@ -98,16 +117,27 @@ declare module "@vue/runtime-core" {
|
||||||
RouterLink: typeof import("vue-router")["RouterLink"];
|
RouterLink: typeof import("vue-router")["RouterLink"];
|
||||||
RouterView: typeof import("vue-router")["RouterView"];
|
RouterView: typeof import("vue-router")["RouterView"];
|
||||||
ScrollPane: typeof import("./../layout/components/TagsView/ScrollPane.vue")["default"];
|
ScrollPane: typeof import("./../layout/components/TagsView/ScrollPane.vue")["default"];
|
||||||
|
Setting: typeof import("./../layout/components/Setting/index.vue")["default"];
|
||||||
Settings: typeof import("./../layout/components/Settings/index.vue")["default"];
|
Settings: typeof import("./../layout/components/Settings/index.vue")["default"];
|
||||||
Sidebar: typeof import("./../layout/components/Sidebar/index.vue")["default"];
|
Sidebar: typeof import("./../layout/components/Sidebar/index.vue")["default"];
|
||||||
SidebarItem: typeof import("./../layout/components/Sidebar/SidebarItem.vue")["default"];
|
SidebarItem: typeof import("./../layout/components/Sidebar/components/SidebarItem/index.vue")["default"];
|
||||||
|
SidebarLeft: typeof import("./../layout/components/Sidebar/components/SidebarLeft.vue")["default"];
|
||||||
|
SidebarLeftMenu: typeof import("./../layout/components/Sidebar/components/SidebarLeftMenu.vue")["default"];
|
||||||
|
SidebarLogo: typeof import("./../layout/components/Sidebar/components/SidebarLogo.vue")["default"];
|
||||||
|
SidebarMenu: typeof import("./../layout/components/Sidebar/components/SidebarMenu.vue")["default"];
|
||||||
|
SidebarMenuItem: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/index.vue")["default"];
|
||||||
|
SidebarMenuItemLink: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/SidebarMenuItemLink.vue")["default"];
|
||||||
|
SidebarMenuItemTitle: typeof import("./../layout/components/Sidebar/components/SidebarMenuItem/SidebarMenuItemTitle.vue")["default"];
|
||||||
|
SidebarMixTopMenu: typeof import("./../layout/components/Sidebar/components/SidebarMixTopMenu.vue")["default"];
|
||||||
|
SidebarTop: typeof import("./../layout/components/Sidebar/components/SidebarTop.vue")["default"];
|
||||||
|
SidebarTopMenu: typeof import("./../layout/components/Sidebar/components/SidebarTopMenu.vue")["default"];
|
||||||
SingleUpload: typeof import("./../components/Upload/SingleUpload.vue")["default"];
|
SingleUpload: typeof import("./../components/Upload/SingleUpload.vue")["default"];
|
||||||
SizeSelect: typeof import("./../components/SizeSelect/index.vue")["default"];
|
SizeSelect: typeof import("./../components/SizeSelect/index.vue")["default"];
|
||||||
SvgIcon: typeof import("./../components/SvgIcon/index.vue")["default"];
|
SvgIcon: typeof import("./../components/SvgIcon/index.vue")["default"];
|
||||||
SwitchRoles: typeof import("./../views/demo/permission/components/SwitchRoles.vue")["default"];
|
SwitchRoles: typeof import("./../views/demo/permission/components/SwitchRoles.vue")["default"];
|
||||||
TagInput: typeof import("./../components/TagInput/index.vue")["default"];
|
TagInput: typeof import("./../components/TagInput/index.vue")["default"];
|
||||||
TagsView: typeof import("./../layout/components/TagsView/index.vue")["default"];
|
TagsView: typeof import("./../layout/components/TagsView/index.vue")["default"];
|
||||||
TopMenu: typeof import("./../layout/components/Sidebar/TopMenu.vue")["default"];
|
TopMenu: typeof import("./../layout/components/Sidebar/components/TopMenu.vue")["default"];
|
||||||
UnfixedThead: typeof import("./../views/demo/table/dynamic-table/components/UnfixedThead.vue")["default"];
|
UnfixedThead: typeof import("./../views/demo/table/dynamic-table/components/UnfixedThead.vue")["default"];
|
||||||
WangEditor: typeof import("./../components/WangEditor/index.vue")["default"];
|
WangEditor: typeof import("./../components/WangEditor/index.vue")["default"];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ const service = axios.create({
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config: InternalAxiosRequestConfig) => {
|
(config: InternalAxiosRequestConfig) => {
|
||||||
const userStore = useUserStoreHook();
|
const accessToken = localStorage.getItem("token");
|
||||||
if (userStore.token) {
|
if (accessToken) {
|
||||||
config.headers.Authorization = userStore.token;
|
config.headers.Authorization = accessToken;
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|
@ -44,6 +44,7 @@ service.interceptors.response.use(
|
||||||
if (code === "A0230") {
|
if (code === "A0230") {
|
||||||
ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", {
|
ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
const userStore = useUserStoreHook();
|
const userStore = useUserStoreHook();
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<!-- 验证码 -->
|
<!-- 验证码 -->
|
||||||
<el-form-item prop="captchaCode" class="flex justify-between">
|
<el-form-item prop="captchaCode" class="flex-x-between">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<span class="p-2">
|
<span class="p-2">
|
||||||
<svg-icon icon-class="captcha" />
|
<svg-icon icon-class="captcha" />
|
||||||
|
|
@ -85,14 +85,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="flex-x-end w-[120px] cursor-pointer" @click="getCaptcha">
|
||||||
class="flex justify-end h-full items-center !w-[128px] cursor-pointer"
|
|
||||||
@click="getCaptcha"
|
|
||||||
>
|
|
||||||
<el-image
|
<el-image
|
||||||
:src="captchaBase64"
|
:src="captchaBase64"
|
||||||
height="48px"
|
class="rounded-tr-md rounded-br-md h-[48px]"
|
||||||
class="rounded-tr-md rounded-br-md"
|
|
||||||
>
|
>
|
||||||
<template #error>
|
<template #error>
|
||||||
<el-icon><Picture /></el-icon>
|
<el-icon><Picture /></el-icon>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ import {
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
"flex-center": "flex justify-center items-center",
|
"flex-center": "flex justify-center items-center",
|
||||||
|
"flex-x-center": "flex justify-center",
|
||||||
|
"flex-y-center": "flex items-center",
|
||||||
|
"wh-full": "w-full h-full",
|
||||||
|
"flex-x-between": "flex items-center justify-between",
|
||||||
|
"flex-x-end": "flex items-center justify-end",
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,9 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
[env.VITE_APP_BASE_API]: {
|
[env.VITE_APP_BASE_API]: {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// 线上接口地址
|
// 线上接口地址
|
||||||
target: "http://vapi.youlai.tech",
|
// target: "http://vapi.youlai.tech",
|
||||||
// 开发接口地址
|
// 开发接口地址
|
||||||
//target: "http://localhost:8989",
|
target: "http://localhost:8989",
|
||||||
rewrite: (path) =>
|
rewrite: (path) =>
|
||||||
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
|
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
|
||||||
},
|
},
|
||||||
|
|
@ -71,7 +71,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
// 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
|
// 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
|
||||||
AutoImport({
|
AutoImport({
|
||||||
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
|
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
|
||||||
imports: ["vue", "@vueuse/core", "pinia", "vue-router"],
|
imports: ["vue", "@vueuse/core", "pinia", "vue-router", "vue-i18n"],
|
||||||
// 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
|
// 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
|
||||||
resolvers: [ElementPlusResolver(), IconsResolver({})],
|
resolvers: [ElementPlusResolver(), IconsResolver({})],
|
||||||
eslintrc: {
|
eslintrc: {
|
||||||
|
|
@ -81,8 +81,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
},
|
},
|
||||||
vueTemplate: true,
|
vueTemplate: true,
|
||||||
// 配置文件生成位置(false:关闭自动生成)
|
// 配置文件生成位置(false:关闭自动生成)
|
||||||
dts: false,
|
//dts: false,
|
||||||
// dts: "src/typings/auto-imports.d.ts",
|
dts: "src/typings/auto-imports.d.ts",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Components({
|
Components({
|
||||||
|
|
@ -95,8 +95,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
// 指定自定义组件位置(默认:src/components)
|
// 指定自定义组件位置(默认:src/components)
|
||||||
dirs: ["src/components", "src/**/components"],
|
dirs: ["src/components", "src/**/components"],
|
||||||
// 配置文件位置 (false:关闭自动生成)
|
// 配置文件位置 (false:关闭自动生成)
|
||||||
dts: false,
|
//dts: false,
|
||||||
// dts: "src/typings/components.d.ts",
|
dts: "src/typings/components.d.ts",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Icons({
|
Icons({
|
||||||
|
|
@ -172,6 +172,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
"@wangeditor/editor",
|
"@wangeditor/editor",
|
||||||
"@wangeditor/editor-for-vue",
|
"@wangeditor/editor-for-vue",
|
||||||
"vue-i18n",
|
"vue-i18n",
|
||||||
|
"element-plus/es/components/text/style/css",
|
||||||
|
"path-browserify",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 构建配置
|
// 构建配置
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue