mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-12 20:27:19 +00:00
style: 移除多余空行并统一代码格式
修复代码中的多余空行问题,统一字符串引号使用双引号,调整对象和数组的格式,优化代码缩进和换行,提升代码可读性和一致性
This commit is contained in:
@@ -58,51 +58,53 @@ const topMenus = ref<RouteRecordRaw[]>([]);
|
||||
|
||||
// 处理后的顶部菜单列表 - 智能显示唯一子菜单的标题
|
||||
const processedTopMenus = computed(() => {
|
||||
return topMenus.value
|
||||
.map((route) => {
|
||||
// 如果路由本身设置了hidden=true,直接返回null
|
||||
if (route.meta?.hidden === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果路由设置了 alwaysShow=true,或者没有子菜单,直接返回原路由
|
||||
if (route.meta?.alwaysShow || !route.children || route.children.length === 0) {
|
||||
return route;
|
||||
}
|
||||
|
||||
// 过滤出非隐藏的子菜单
|
||||
const visibleChildren = route.children.filter((child) => !child.meta?.hidden);
|
||||
|
||||
// 如果没有可见子菜单,返回null
|
||||
if (visibleChildren.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果只有一个非隐藏的子菜单,显示子菜单的信息
|
||||
if (visibleChildren.length === 1) {
|
||||
const onlyChild = visibleChildren[0];
|
||||
// 检查子菜单是否应该显示在顶部菜单
|
||||
if (onlyChild.meta?.hidden !== true) {
|
||||
return {
|
||||
...route,
|
||||
meta: {
|
||||
...route.meta,
|
||||
title: onlyChild.meta?.title || route.meta?.title,
|
||||
icon: onlyChild.meta?.icon || route.meta?.icon,
|
||||
},
|
||||
// 使用子菜单的path确保路由匹配正确
|
||||
path: onlyChild.path
|
||||
};
|
||||
return (
|
||||
topMenus.value
|
||||
.map((route) => {
|
||||
// 如果路由本身设置了hidden=true,直接返回null
|
||||
if (route.meta?.hidden === true) {
|
||||
return null;
|
||||
}
|
||||
// 如果子菜单应该隐藏,则不显示该顶级菜单
|
||||
return null;
|
||||
}
|
||||
|
||||
// 其他情况返回原路由
|
||||
return route;
|
||||
})
|
||||
// 过滤掉null值
|
||||
.filter((route) => route !== null);
|
||||
// 如果路由设置了 alwaysShow=true,或者没有子菜单,直接返回原路由
|
||||
if (route.meta?.alwaysShow || !route.children || route.children.length === 0) {
|
||||
return route;
|
||||
}
|
||||
|
||||
// 过滤出非隐藏的子菜单
|
||||
const visibleChildren = route.children.filter((child) => !child.meta?.hidden);
|
||||
|
||||
// 如果没有可见子菜单,返回null
|
||||
if (visibleChildren.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果只有一个非隐藏的子菜单,显示子菜单的信息
|
||||
if (visibleChildren.length === 1) {
|
||||
const onlyChild = visibleChildren[0];
|
||||
// 检查子菜单是否应该显示在顶部菜单
|
||||
if (onlyChild.meta?.hidden !== true) {
|
||||
return {
|
||||
...route,
|
||||
meta: {
|
||||
...route.meta,
|
||||
title: onlyChild.meta?.title || route.meta?.title,
|
||||
icon: onlyChild.meta?.icon || route.meta?.icon,
|
||||
},
|
||||
// 使用子菜单的path确保路由匹配正确
|
||||
path: onlyChild.path,
|
||||
};
|
||||
}
|
||||
// 如果子菜单应该隐藏,则不显示该顶级菜单
|
||||
return null;
|
||||
}
|
||||
|
||||
// 其他情况返回原路由
|
||||
return route;
|
||||
})
|
||||
// 过滤掉null值
|
||||
.filter((route) => route !== null)
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- <span class="lock-dialog-name">{{ t('navbar.lock') }}</span> -->
|
||||
<span class="lock-dialog-name">{{ userStore.basicInfo.name }}</span>
|
||||
</div>
|
||||
<el-form ref="lockFormRef" :model="form" :rules="rules" >
|
||||
<el-form ref="lockFormRef" :model="form" :rules="rules">
|
||||
<el-form-item :label="t('lock.lockPassword')" prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
@@ -25,63 +25,61 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleLock">{{ t('navbar.lock') }}</el-button>
|
||||
<el-button type="primary" @click="handleLock">{{ t("navbar.lock") }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { useLockStore } from '@/store'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { useLockStore } from "@/store";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { t } = useI18n()
|
||||
const lockStore = useLockStore()
|
||||
const { t } = useI18n();
|
||||
const lockStore = useLockStore();
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean
|
||||
}
|
||||
})
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => {
|
||||
emit('update:modelValue', val)
|
||||
}
|
||||
})
|
||||
emit("update:modelValue", val);
|
||||
},
|
||||
});
|
||||
|
||||
const dialogTitle = ref(t('lock.lockScreen'))
|
||||
const lockFormRef = ref<FormInstance>()
|
||||
const dialogTitle = ref(t("lock.lockScreen"));
|
||||
const lockFormRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
password: ''
|
||||
})
|
||||
password: "",
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
password: [
|
||||
{ required: true, message: t('lock.required'), trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
password: [{ required: true, message: t("lock.required"), trigger: "blur" }],
|
||||
};
|
||||
|
||||
// 优化后的锁定逻辑
|
||||
const handleLock = async () => {
|
||||
try {
|
||||
await lockFormRef.value?.validate()
|
||||
dialogVisible.value = false
|
||||
await lockFormRef.value?.validate();
|
||||
dialogVisible.value = false;
|
||||
lockStore.setLockInfo({
|
||||
isLock: true,
|
||||
password: form.password
|
||||
})
|
||||
password: form.password,
|
||||
});
|
||||
} catch {
|
||||
// 验证失败时不执行任何操作
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
<template>
|
||||
<div class="lockpage">
|
||||
<div
|
||||
v-show="showDate"
|
||||
class="unlock-container"
|
||||
@click="handleShowForm(false)"
|
||||
>
|
||||
<div v-show="showDate" class="unlock-container" @click="handleShowForm(false)">
|
||||
<el-icon><Lock /></el-icon>
|
||||
<span>{{ t('lock.unlock') }}</span>
|
||||
<span>{{ t("lock.unlock") }}</span>
|
||||
</div>
|
||||
|
||||
<div class="time-container w-screen h-screen">
|
||||
<div class="hour-container mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5">
|
||||
<span>{{ hour }}</span>
|
||||
<span v-show="showDate" class="meridiem absolute left-5 top-5 text-md xl:text-xl">{{ meridiem }}</span>
|
||||
<span v-show="showDate" class="meridiem absolute left-5 top-5 text-md xl:text-xl">
|
||||
{{ meridiem }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="minute-container w-2/5 h-2/5 md:h-4/5">
|
||||
<span>{{ minute }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<transition name="fade-slide">
|
||||
<div v-show="!showDate" class="entry-wrapper">
|
||||
<div class="entry-content">
|
||||
@@ -36,7 +34,7 @@
|
||||
@keydown.enter="unLock"
|
||||
/>
|
||||
<span v-if="errMsg" class="error-message">
|
||||
{{ t('lock.message') }}
|
||||
{{ t("lock.message") }}
|
||||
</span>
|
||||
<div class="button-group">
|
||||
<el-button
|
||||
@@ -47,7 +45,7 @@
|
||||
:disabled="loading"
|
||||
@click="handleShowForm(true)"
|
||||
>
|
||||
{{ t('common.back') }}
|
||||
{{ t("common.back") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -57,7 +55,7 @@
|
||||
:disabled="loading"
|
||||
@click="goLogin"
|
||||
>
|
||||
{{ t('lock.backToLogin') }}
|
||||
{{ t("lock.backToLogin") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -67,7 +65,7 @@
|
||||
:disabled="loading"
|
||||
@click="unLock()"
|
||||
>
|
||||
{{ t('lock.entrySystem') }}
|
||||
{{ t("lock.entrySystem") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,78 +74,73 @@
|
||||
|
||||
<div class="date-container">
|
||||
<div v-show="!showDate" class="time-display">
|
||||
{{ hour }}:{{ minute }} <span class="meridiem-display">{{ meridiem }}</span>
|
||||
{{ hour }}:{{ minute }}
|
||||
<span class="meridiem-display">{{ meridiem }}</span>
|
||||
</div>
|
||||
<div class="full-date">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useUserStore, useLockStore } from "@/store";
|
||||
import { ElInput } from 'element-plus';
|
||||
import { useNow } from '@/utils/dateUtil'
|
||||
import { useTagsViewStore } from '@/store'
|
||||
|
||||
import { ElInput } from "element-plus";
|
||||
import { useNow } from "@/utils/dateUtil";
|
||||
import { useTagsViewStore } from "@/store";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
|
||||
const { replace } = useRouter();
|
||||
|
||||
const { replace } = useRouter()
|
||||
const password = ref("");
|
||||
const loading = ref(false);
|
||||
const errMsg = ref(false);
|
||||
const showDate = ref(true);
|
||||
|
||||
const password = ref('')
|
||||
const loading = ref(false)
|
||||
const errMsg = ref(false)
|
||||
const showDate = ref(true)
|
||||
const lockStore = useLockStore();
|
||||
|
||||
const { hour, month, minute, meridiem, year, day, week } = useNow(true);
|
||||
|
||||
const lockStore = useLockStore()
|
||||
|
||||
const { hour, month, minute, meridiem, year, day, week } = useNow(true)
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n();
|
||||
|
||||
// 解锁
|
||||
async function unLock() {
|
||||
if (!password.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const pwd = password.value
|
||||
const pwd = password.value;
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await lockStore.unLock(pwd)
|
||||
errMsg.value = !res
|
||||
loading.value = true;
|
||||
const res = await lockStore.unLock(pwd);
|
||||
errMsg.value = !res;
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回登录
|
||||
async function goLogin() {
|
||||
await userStore.logout().catch(() => {})
|
||||
userStore.resetAllState()
|
||||
tagsViewStore.delAllViews()
|
||||
await userStore.logout().catch(() => {});
|
||||
userStore.resetAllState();
|
||||
tagsViewStore.delAllViews();
|
||||
router.push(`/login?redirect=${route.fullPath}`);
|
||||
lockStore.resetLockInfo()
|
||||
replace('/login')
|
||||
lockStore.resetLockInfo();
|
||||
replace("/login");
|
||||
}
|
||||
|
||||
|
||||
|
||||
const passwordInputRef = ref<InstanceType<typeof ElInput>>()
|
||||
const passwordInputRef = ref<InstanceType<typeof ElInput>>();
|
||||
|
||||
function handleShowForm(show = false) {
|
||||
showDate.value = show
|
||||
showDate.value = show;
|
||||
if (!show) {
|
||||
requestAnimationFrame(() => {
|
||||
passwordInputRef.value?.focus()
|
||||
})
|
||||
passwordInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -160,7 +153,7 @@ function handleShowForm(show = false) {
|
||||
left: 0;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
width: 100%;;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
backdrop-filter: blur(8px);
|
||||
@@ -193,7 +186,6 @@ function handleShowForm(show = false) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.time-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -214,7 +206,7 @@ function handleShowForm(show = false) {
|
||||
align-items: center;
|
||||
font-size: 220px;
|
||||
}
|
||||
|
||||
|
||||
.minute-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -240,7 +232,6 @@ function handleShowForm(show = false) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
.entry-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -313,11 +304,11 @@ function handleShowForm(show = false) {
|
||||
width: 100%;
|
||||
color: #d1d5db;
|
||||
text-align: center;
|
||||
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
@@ -326,7 +317,7 @@ function handleShowForm(show = false) {
|
||||
.time-display {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
|
||||
.meridiem-display {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<div v-if="settingStore.showLangSelect" class="navbar-actions__item">
|
||||
<LangSelect />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- 通知 -->
|
||||
@@ -67,7 +66,7 @@
|
||||
<el-icon><Lock /></el-icon>
|
||||
{{ t("navbar.lock") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<el-dropdown-item @click="logout">
|
||||
<el-icon><SwitchButton /></el-icon>
|
||||
{{ t("navbar.logout") }}
|
||||
</el-dropdown-item>
|
||||
@@ -76,7 +75,7 @@
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 引导 -->
|
||||
<Guide v-if="guideVisible" v-model="guideVisible" @skip="handleGuideExit" />
|
||||
|
||||
@@ -89,8 +88,7 @@
|
||||
</teleport>
|
||||
|
||||
<!-- 配置中心抽屉 -->
|
||||
<ConfigInfoDrawer v-model="drawerVisible"/>
|
||||
|
||||
<ConfigInfoDrawer v-model="drawerVisible" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -108,11 +106,10 @@ import Fullscreen from "@/components/Fullscreen/index.vue";
|
||||
import SizeSelect from "@/components/SizeSelect/index.vue";
|
||||
import LangSelect from "@/components/LangSelect/index.vue";
|
||||
import Notification from "@/components/Notification/index.vue";
|
||||
import LockDialog from './LockDialog.vue'
|
||||
import LockPage from './LockPage.vue'
|
||||
import Guide from '@/components/Guide/index.vue'
|
||||
import ConfigInfoDrawer from "@/views/module_system/param/components/ConfigInfoDrawer.vue"
|
||||
|
||||
import LockDialog from "./LockDialog.vue";
|
||||
import LockPage from "./LockPage.vue";
|
||||
import Guide from "@/components/Guide/index.vue";
|
||||
import ConfigInfoDrawer from "@/views/module_system/param/components/ConfigInfoDrawer.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
@@ -131,7 +128,6 @@ function handleProfileClick() {
|
||||
router.push({ name: "Profile" });
|
||||
}
|
||||
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
/**
|
||||
* 打开配置中心页面
|
||||
@@ -140,29 +136,28 @@ function handleConfigClick() {
|
||||
drawerVisible.value = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 项目文档
|
||||
*/
|
||||
function handleDocumentClick() {
|
||||
window.open('https://service.fastapiadmin.com', '_blank');
|
||||
window.open("https://service.fastapiadmin.com", "_blank");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gitee 项目地址
|
||||
*/
|
||||
function handleGiteeClick() {
|
||||
window.open('https://gitee.com/tao__tao/FastapiAdmin')
|
||||
window.open("https://gitee.com/tao__tao/FastapiAdmin");
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目引导
|
||||
* 项目引导
|
||||
*/
|
||||
// 使用ref而不是computed,以便可以修改引导可见性
|
||||
// 使用 computed 实现双向绑定,减少 watch 的使用
|
||||
const guideVisible = computed({
|
||||
get: () => appStore.guideVisible,
|
||||
set: (newValue) => appStore.showGuide(newValue)
|
||||
set: (newValue) => appStore.showGuide(newValue),
|
||||
});
|
||||
|
||||
function handleTourClick() {
|
||||
@@ -177,27 +172,29 @@ function handleTourClick() {
|
||||
// 引导结束(点击跳过或最后一步完成关闭)后,自动关闭下次登录的自动展示
|
||||
function handleGuideExit() {
|
||||
// 关闭自动展示开关,确保下次登录不再自动开启
|
||||
settingStore.updateSetting('showGuide', false);
|
||||
settingStore.updateSetting("showGuide", false);
|
||||
}
|
||||
|
||||
// 监听引导关闭(从 true -> false),也同步关闭自动展示开关
|
||||
watch(() => guideVisible.value, (val, oldVal) => {
|
||||
if (oldVal && !val) {
|
||||
settingStore.updateSetting('showGuide', false);
|
||||
watch(
|
||||
() => guideVisible.value,
|
||||
(val, oldVal) => {
|
||||
if (oldVal && !val) {
|
||||
settingStore.updateSetting("showGuide", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
/**
|
||||
* 锁屏
|
||||
*/
|
||||
const lockStore = useLockStore()
|
||||
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const lockStore = useLockStore();
|
||||
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false);
|
||||
const dialogVisible = ref<boolean>(false);
|
||||
// 锁定屏幕
|
||||
const handlelockScreen = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 根据主题和侧边栏配色方案选择样式类
|
||||
const navbarActionsClass = computed(() => {
|
||||
@@ -234,15 +231,16 @@ function logout() {
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
lockScroll: false,
|
||||
}).then(() => {
|
||||
userStore.logout().then(() => {
|
||||
router.push(`/login`);
|
||||
})
|
||||
.then(() => {
|
||||
userStore.logout().then(() => {
|
||||
router.push(`/login`);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessageBox.close();
|
||||
});
|
||||
}).catch(() => {
|
||||
ElMessageBox.close();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -80,12 +80,9 @@
|
||||
<div class="color-options">
|
||||
<!-- 预设颜色选项 -->
|
||||
<div
|
||||
v-for="(color) in displayColorPresets"
|
||||
v-for="color in displayColorPresets"
|
||||
:key="color"
|
||||
:class="[
|
||||
'color-option',
|
||||
{ 'is-active': selectedThemeColor === color }
|
||||
]"
|
||||
:class="['color-option', { 'is-active': selectedThemeColor === color }]"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="handleColorSelect(color)"
|
||||
>
|
||||
@@ -108,9 +105,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- 导航主题 -->
|
||||
<section v-if="!isDark" class="config-section ">
|
||||
<section v-if="!isDark" class="config-section">
|
||||
<el-divider>{{ t("settings.navigation") }}</el-divider>
|
||||
<div class="config-item flex-x-between">
|
||||
<span class="text-xs">{{ t("settings.sidebarColorScheme") }}</span>
|
||||
@@ -171,7 +167,7 @@
|
||||
<span class="text-xs">{{ t("settings.showNotification") }}</span>
|
||||
<el-switch v-model="settingsStore.showNotification" />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 修改此部分为引导开关,关闭以后登录以后不再启动引导 -->
|
||||
<div class="flex-x-between">
|
||||
<span class="text-xs">{{ t("settings.showGuide") }}</span>
|
||||
@@ -216,8 +212,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -312,7 +306,6 @@ const handleColorSelect = (color: string) => {
|
||||
selectedThemeColor.value = color;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 复制当前配置
|
||||
*/
|
||||
@@ -742,21 +735,21 @@ const handleCloseDrawer = () => {
|
||||
}
|
||||
|
||||
.color-picker-wrapper {
|
||||
.custom-color-picker {
|
||||
:deep(.el-color-picker__trigger) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s var(--el-transition-duration);
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
.custom-color-picker {
|
||||
:deep(.el-color-picker__trigger) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s var(--el-transition-duration);
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
border-color: var(--el-color-primary-light-3);
|
||||
}
|
||||
&:hover {
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
box-shadow: var(--el-box-shadow);
|
||||
border-color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-color-picker__color) {
|
||||
border: none;
|
||||
@@ -774,7 +767,6 @@ const handleCloseDrawer = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 深色模式适配 */
|
||||
.dark {
|
||||
.theme-color-selector {
|
||||
@@ -804,7 +796,6 @@ const handleCloseDrawer = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 复制配置对话框样式 */
|
||||
:deep(.copy-config-dialog) {
|
||||
.el-message-box__content {
|
||||
@@ -813,4 +804,3 @@ const handleCloseDrawer = () => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -10,16 +10,20 @@
|
||||
<el-scrollbar ref="scrollbarRef" class="scroll-container" @wheel="handleScroll">
|
||||
<VueDraggable v-model="visitedViews" :animation="150">
|
||||
<router-link
|
||||
v-for="tag in displayedViews" :key="tag.fullPath"
|
||||
:class="['tags-item', { active: tagsViewStore.isActive(tag) }]" :to="{ path: tag.path, query: tag.query }"
|
||||
v-for="tag in displayedViews"
|
||||
:key="tag.fullPath"
|
||||
:class="['tags-item', { active: tagsViewStore.isActive(tag) }]"
|
||||
:to="{ path: tag.path, query: tag.query }"
|
||||
@click="tagSwitchSource = 'tab'"
|
||||
@click.middle="handleMiddleClick(tag)">
|
||||
@click.middle="handleMiddleClick(tag)"
|
||||
>
|
||||
<!-- 为所有标签添加右键菜单 -->
|
||||
<el-dropdown
|
||||
<el-dropdown
|
||||
v-if="tagsViewStore.isActive(tag)"
|
||||
trigger="contextmenu"
|
||||
@visible-change="(visible) => onContextMenuVisibleChange(visible, tag)"
|
||||
@click.stop>
|
||||
@click.stop
|
||||
>
|
||||
<span class="tag-text">{{ translateRouteTitle(tag.title) }}</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
@@ -32,7 +36,8 @@
|
||||
|
||||
<el-dropdown-item
|
||||
:disabled="tag.affix || visitedViews.length <= 1"
|
||||
@click="closeSelectedTag(tag)">
|
||||
@click="closeSelectedTag(tag)"
|
||||
>
|
||||
<el-icon>
|
||||
<Close />
|
||||
</el-icon>
|
||||
@@ -41,7 +46,8 @@
|
||||
|
||||
<el-dropdown-item
|
||||
:disabled="isFirstView || !tagsViewStore.isActive(tag)"
|
||||
@click="closeLeftTags">
|
||||
@click="closeLeftTags"
|
||||
>
|
||||
<el-icon>
|
||||
<Back />
|
||||
</el-icon>
|
||||
@@ -50,7 +56,8 @@
|
||||
|
||||
<el-dropdown-item
|
||||
:disabled="isLastView || !tagsViewStore.isActive(tag)"
|
||||
@click="closeRightTags">
|
||||
@click="closeRightTags"
|
||||
>
|
||||
<el-icon>
|
||||
<Right />
|
||||
</el-icon>
|
||||
@@ -59,16 +66,15 @@
|
||||
|
||||
<el-dropdown-item
|
||||
:disabled="visitedViews.length <= 1 || !tagsViewStore.isActive(tag)"
|
||||
@click="closeOtherTags">
|
||||
@click="closeOtherTags"
|
||||
>
|
||||
<el-icon>
|
||||
<Remove />
|
||||
</el-icon>
|
||||
{{ t("navbar.closeOther") }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
:disabled="visitedViews.length <= 1"
|
||||
@click="closeAllTags(tag)">
|
||||
<el-dropdown-item :disabled="visitedViews.length <= 1" @click="closeAllTags(tag)">
|
||||
<el-icon>
|
||||
<Minus />
|
||||
</el-icon>
|
||||
@@ -81,21 +87,24 @@
|
||||
<Star v-if="!isQuickLinkExists(tag)" />
|
||||
<StarFilled v-else />
|
||||
</el-icon>
|
||||
{{ isQuickLinkExists(tag) ? '取消收藏' : '收藏' }}
|
||||
{{ isQuickLinkExists(tag) ? "取消收藏" : "收藏" }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span v-else class="tag-text">{{ translateRouteTitle(tag.title) }}</span>
|
||||
|
||||
<span v-if="!tag.affix" class="tag-close-btn" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
|
||||
<span
|
||||
v-if="!tag.affix"
|
||||
class="tag-close-btn"
|
||||
@click.prevent.stop="closeSelectedTag(tag)"
|
||||
>
|
||||
<el-icon>
|
||||
<Close />
|
||||
</el-icon>
|
||||
</span>
|
||||
</router-link>
|
||||
</VueDraggable>
|
||||
|
||||
</el-scrollbar>
|
||||
</nav>
|
||||
|
||||
@@ -146,7 +155,10 @@
|
||||
{{ t("navbar.closeRight") }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item :disabled="visitedViews.length <= 1" @click="handleAction('closeOther')">
|
||||
<el-dropdown-item
|
||||
:disabled="visitedViews.length <= 1"
|
||||
@click="handleAction('closeOther')"
|
||||
>
|
||||
<el-icon>
|
||||
<Remove />
|
||||
</el-icon>
|
||||
@@ -172,7 +184,7 @@ import { translateRouteTitle } from "@/utils/i18n";
|
||||
import { refreshAppCaches, usePermissionStore, useTagsViewStore } from "@/store";
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import { quickStartManager } from "@/utils/quickStartManager";
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -183,12 +195,11 @@ const route = useRoute();
|
||||
const permissionStore = usePermissionStore();
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
|
||||
|
||||
const { visitedViews } = storeToRefs(tagsViewStore);
|
||||
|
||||
// 简单的标签显示逻辑
|
||||
const displayedViews = computed(() => {
|
||||
return visitedViews.value
|
||||
return visitedViews.value;
|
||||
});
|
||||
|
||||
// 当前选中的标签
|
||||
@@ -203,7 +214,7 @@ const activeTag = computed(() => {
|
||||
const scrollbarRef = ref();
|
||||
|
||||
// 标签切换来源跟踪
|
||||
const tagSwitchSource = ref<'menu' | 'tab' | null>(null);
|
||||
const tagSwitchSource = ref<"menu" | "tab" | null>(null);
|
||||
|
||||
// 路由映射缓存,提升查找性能
|
||||
const routePathMap = computed(() => {
|
||||
@@ -282,14 +293,14 @@ const addCurrentTag = () => {
|
||||
if (!route.meta?.title) return;
|
||||
|
||||
// 检查标签是否已存在
|
||||
const existingTag = visitedViews.value.find(tag => tag.path === route.path);
|
||||
const existingTag = visitedViews.value.find((tag) => tag.path === route.path);
|
||||
|
||||
if (existingTag) {
|
||||
// 如果标签已存在,根据来源决定是否移动位置
|
||||
if (!existingTag.affix) {
|
||||
if (tagSwitchSource.value === 'menu') {
|
||||
if (tagSwitchSource.value === "menu") {
|
||||
// 通过菜单点击:移动到最新位置
|
||||
const index = visitedViews.value.findIndex(tag => tag.path === route.path);
|
||||
const index = visitedViews.value.findIndex((tag) => tag.path === route.path);
|
||||
if (index !== -1) {
|
||||
const tag = visitedViews.value.splice(index, 1)[0];
|
||||
visitedViews.value.push(tag);
|
||||
@@ -312,7 +323,7 @@ const addCurrentTag = () => {
|
||||
}
|
||||
|
||||
// 根据来源决定是否滚动
|
||||
if (tagSwitchSource.value === 'menu') {
|
||||
if (tagSwitchSource.value === "menu") {
|
||||
// 通过菜单点击:滚动到最新标签
|
||||
nextTick(() => {
|
||||
autoScrollToLatestTag();
|
||||
@@ -359,7 +370,6 @@ const handleMiddleClick = (tag: TagView) => {
|
||||
* 处理滚轮事件(优化后)
|
||||
*/
|
||||
const handleScroll = (event: WheelEvent) => {
|
||||
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef;
|
||||
if (!scrollWrapper) return;
|
||||
|
||||
@@ -373,8 +383,20 @@ const handleScroll = (event: WheelEvent) => {
|
||||
const deltaX = event.deltaX || 0;
|
||||
|
||||
// 计算新的滚动位置
|
||||
const newScrollLeft = Math.max(0, Math.min(scrollWrapper.scrollWidth - scrollWrapper.clientWidth, scrollWrapper.scrollLeft + deltaX));
|
||||
const newScrollTop = Math.max(0, Math.min(scrollWrapper.scrollHeight - scrollWrapper.clientHeight, scrollWrapper.scrollTop + deltaY));
|
||||
const newScrollLeft = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
scrollWrapper.scrollWidth - scrollWrapper.clientWidth,
|
||||
scrollWrapper.scrollLeft + deltaX
|
||||
)
|
||||
);
|
||||
const newScrollTop = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
scrollWrapper.scrollHeight - scrollWrapper.clientHeight,
|
||||
scrollWrapper.scrollTop + deltaY
|
||||
)
|
||||
);
|
||||
|
||||
scrollbarRef.value.setScrollLeft(newScrollLeft);
|
||||
scrollbarRef.value.setScrollTop(newScrollTop); // 新增垂直滚动支持
|
||||
@@ -493,35 +515,35 @@ const handleAction = async (action: string) => {
|
||||
if (!currentTag) return;
|
||||
|
||||
switch (action) {
|
||||
case 'refresh':
|
||||
case "refresh":
|
||||
refreshSelectedTag(currentTag);
|
||||
break;
|
||||
case 'close':
|
||||
case "close":
|
||||
closeSelectedTag(currentTag);
|
||||
break;
|
||||
case 'closeRight':
|
||||
case "closeRight":
|
||||
closeRightTags();
|
||||
break;
|
||||
case 'closeLeft':
|
||||
case "closeLeft":
|
||||
closeLeftTags();
|
||||
break;
|
||||
case 'closeOther':
|
||||
case "closeOther":
|
||||
closeOtherTags();
|
||||
break;
|
||||
case 'closeAll':
|
||||
case "closeAll":
|
||||
closeAllTags(currentTag);
|
||||
break;
|
||||
case 'refreshCache':
|
||||
case "refreshCache":
|
||||
try {
|
||||
// 1) 刷新服务端缓存(用户/权限/配置/公告/按需字典)并重建路由
|
||||
await refreshAppCaches();
|
||||
// 2) 软刷新当前页面,重新加载组件
|
||||
refreshSelectedTag(currentTag);
|
||||
// 3) 提示成功
|
||||
ElMessage.success(t('navbar.refreshCache') + '完成');
|
||||
ElMessage.success(t("navbar.refreshCache") + "完成");
|
||||
} catch (error) {
|
||||
console.error('刷新缓存失败:', error);
|
||||
ElMessage.error('刷新失败');
|
||||
console.error("刷新缓存失败:", error);
|
||||
ElMessage.error("刷新失败");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -553,7 +575,7 @@ const toggleQuickStart = (tag: TagView) => {
|
||||
if (isExists) {
|
||||
// 取消收藏:找到对应的链接并删除
|
||||
const links = quickStartManager.getQuickLinks();
|
||||
const targetLink = links.find(link => link.href === href);
|
||||
const targetLink = links.find((link) => link.href === href);
|
||||
if (targetLink?.id) {
|
||||
quickStartManager.removeQuickLink(targetLink.id);
|
||||
ElMessage.success(`已取消收藏:${tag.title}`);
|
||||
@@ -565,8 +587,8 @@ const toggleQuickStart = (tag: TagView) => {
|
||||
ElMessage.success(`已收藏:${tag.title}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle quick start:', error);
|
||||
ElMessage.error('操作失败');
|
||||
console.error("Failed to toggle quick start:", error);
|
||||
ElMessage.error("操作失败");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -581,102 +603,105 @@ const isQuickLinkExists = (tag: TagView): boolean => {
|
||||
* 向左滚动标签页
|
||||
*/
|
||||
const scrollLeft = () => {
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef
|
||||
if (!scrollWrapper) return
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef;
|
||||
if (!scrollWrapper) return;
|
||||
|
||||
const newScrollLeft = Math.max(0, scrollWrapper.scrollLeft - 200)
|
||||
scrollbarRef.value.setScrollLeft(newScrollLeft)
|
||||
}
|
||||
const newScrollLeft = Math.max(0, scrollWrapper.scrollLeft - 200);
|
||||
scrollbarRef.value.setScrollLeft(newScrollLeft);
|
||||
};
|
||||
|
||||
/**
|
||||
* 向右滚动标签页
|
||||
*/
|
||||
const scrollRight = () => {
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef
|
||||
if (!scrollWrapper) return
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef;
|
||||
if (!scrollWrapper) return;
|
||||
|
||||
const maxScrollLeft = scrollWrapper.scrollWidth - scrollWrapper.clientWidth
|
||||
const newScrollLeft = Math.min(maxScrollLeft, scrollWrapper.scrollLeft + 200)
|
||||
scrollbarRef.value.setScrollLeft(newScrollLeft)
|
||||
const maxScrollLeft = scrollWrapper.scrollWidth - scrollWrapper.clientWidth;
|
||||
const newScrollLeft = Math.min(maxScrollLeft, scrollWrapper.scrollLeft + 200);
|
||||
scrollbarRef.value.setScrollLeft(newScrollLeft);
|
||||
|
||||
// 如果滚动到最右边,重置滚动状态以允许下次自动滚动
|
||||
if (newScrollLeft >= maxScrollLeft - 1) {
|
||||
scrollState.value.hasScrolledToLatest = false
|
||||
scrollState.value.hasScrolledToLatest = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置滚动状态
|
||||
*/
|
||||
const resetScrollState = () => {
|
||||
scrollState.value.hasScrolledToLatest = false
|
||||
scrollState.value.isContainerFull = false
|
||||
}
|
||||
scrollState.value.hasScrolledToLatest = false;
|
||||
scrollState.value.isContainerFull = false;
|
||||
};
|
||||
|
||||
// 滚动状态跟踪
|
||||
const scrollState = ref({
|
||||
hasScrolledToLatest: false, // 是否已经滚动到最新标签
|
||||
isContainerFull: false // 容器是否已满
|
||||
})
|
||||
isContainerFull: false, // 容器是否已满
|
||||
});
|
||||
|
||||
/**
|
||||
* 自动滚动到最新标签或确保当前激活标签可见
|
||||
*/
|
||||
const autoScrollToLatestTag = () => {
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef
|
||||
if (!scrollWrapper) return
|
||||
const scrollWrapper = scrollbarRef.value?.wrapRef;
|
||||
if (!scrollWrapper) return;
|
||||
|
||||
// 获取容器宽度和内容宽度
|
||||
const containerWidth = scrollWrapper.clientWidth
|
||||
const contentWidth = scrollWrapper.scrollWidth
|
||||
const containerWidth = scrollWrapper.clientWidth;
|
||||
const contentWidth = scrollWrapper.scrollWidth;
|
||||
|
||||
// 判断容器是否已满(内容宽度是否超过容器宽度)
|
||||
const isContainerFull = contentWidth > containerWidth
|
||||
const isContainerFull = contentWidth > containerWidth;
|
||||
|
||||
// 查找当前激活的标签元素
|
||||
const activeTagElement = document.querySelector('.tags-item.active')
|
||||
|
||||
const activeTagElement = document.querySelector(".tags-item.active");
|
||||
|
||||
if (activeTagElement) {
|
||||
// 将Element类型断言为HTMLElement类型以访问offsetWidth属性
|
||||
const activeHtmlElement = activeTagElement as HTMLElement;
|
||||
// 计算激活标签的位置信息
|
||||
const activeTagRect = activeHtmlElement.getBoundingClientRect()
|
||||
const containerRect = scrollWrapper.getBoundingClientRect()
|
||||
|
||||
const activeTagRect = activeHtmlElement.getBoundingClientRect();
|
||||
const containerRect = scrollWrapper.getBoundingClientRect();
|
||||
|
||||
// 计算标签相对于容器的位置
|
||||
const tagLeft = activeTagRect.left - containerRect.left + scrollWrapper.scrollLeft
|
||||
const tagRight = tagLeft + activeHtmlElement.offsetWidth
|
||||
|
||||
const tagLeft = activeTagRect.left - containerRect.left + scrollWrapper.scrollLeft;
|
||||
const tagRight = tagLeft + activeHtmlElement.offsetWidth;
|
||||
|
||||
// 检查标签是否完全在可见区域内
|
||||
if (tagLeft < scrollWrapper.scrollLeft || tagRight > scrollWrapper.scrollLeft + containerWidth) {
|
||||
if (
|
||||
tagLeft < scrollWrapper.scrollLeft ||
|
||||
tagRight > scrollWrapper.scrollLeft + containerWidth
|
||||
) {
|
||||
// 如果标签不在可见区域内,滚动到使标签居中的位置
|
||||
const targetScrollLeft = tagLeft - (containerWidth - activeHtmlElement.offsetWidth) / 2
|
||||
const maxScrollLeft = contentWidth - containerWidth
|
||||
const minScrollLeft = 0
|
||||
const clampedScrollLeft = Math.max(minScrollLeft, Math.min(maxScrollLeft, targetScrollLeft))
|
||||
|
||||
scrollbarRef.value.setScrollLeft(clampedScrollLeft)
|
||||
scrollState.value.hasScrolledToLatest = true
|
||||
scrollState.value.isContainerFull = isContainerFull
|
||||
return
|
||||
const targetScrollLeft = tagLeft - (containerWidth - activeHtmlElement.offsetWidth) / 2;
|
||||
const maxScrollLeft = contentWidth - containerWidth;
|
||||
const minScrollLeft = 0;
|
||||
const clampedScrollLeft = Math.max(minScrollLeft, Math.min(maxScrollLeft, targetScrollLeft));
|
||||
|
||||
scrollbarRef.value.setScrollLeft(clampedScrollLeft);
|
||||
scrollState.value.hasScrolledToLatest = true;
|
||||
scrollState.value.isContainerFull = isContainerFull;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到激活标签或激活标签已经在可见区域内,则使用原来的逻辑
|
||||
if (isContainerFull && !scrollState.value.hasScrolledToLatest) {
|
||||
// 计算需要滚动到的位置,确保最新标签在右侧可见
|
||||
const maxScrollLeft = contentWidth - containerWidth
|
||||
scrollbarRef.value.setScrollLeft(maxScrollLeft)
|
||||
scrollState.value.hasScrolledToLatest = true
|
||||
scrollState.value.isContainerFull = true
|
||||
const maxScrollLeft = contentWidth - containerWidth;
|
||||
scrollbarRef.value.setScrollLeft(maxScrollLeft);
|
||||
scrollState.value.hasScrolledToLatest = true;
|
||||
scrollState.value.isContainerFull = true;
|
||||
} else if (!isContainerFull) {
|
||||
// 如果内容宽度不超过容器宽度,滚动到最左边
|
||||
scrollbarRef.value.setScrollLeft(0)
|
||||
scrollbarRef.value.setScrollLeft(0);
|
||||
// 重置滚动状态
|
||||
scrollState.value.hasScrolledToLatest = false
|
||||
scrollState.value.isContainerFull = false
|
||||
scrollState.value.hasScrolledToLatest = false;
|
||||
scrollState.value.isContainerFull = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听路由变化
|
||||
watch(
|
||||
@@ -684,7 +709,7 @@ watch(
|
||||
() => {
|
||||
// 如果没有设置来源,则默认为菜单点击
|
||||
if (tagSwitchSource.value === null) {
|
||||
tagSwitchSource.value = 'menu';
|
||||
tagSwitchSource.value = "menu";
|
||||
}
|
||||
addCurrentTag();
|
||||
updateCurrentTag();
|
||||
@@ -700,7 +725,7 @@ watch(
|
||||
() => visitedViews.value.length,
|
||||
() => {
|
||||
// 只有在通过菜单添加新标签时才滚动
|
||||
if (tagSwitchSource.value === 'menu') {
|
||||
if (tagSwitchSource.value === "menu") {
|
||||
nextTick(() => {
|
||||
autoScrollToLatestTag();
|
||||
});
|
||||
@@ -721,9 +746,9 @@ watch(
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
initAffixTags();
|
||||
|
||||
|
||||
// 监听容器大小变化
|
||||
const tagsContainer = document.querySelector('.tags-container');
|
||||
const tagsContainer = document.querySelector(".tags-container");
|
||||
if (tagsContainer && window.ResizeObserver) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
// 强制重新计算 displayedViews
|
||||
@@ -741,7 +766,6 @@ onUnmounted(() => {
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -764,7 +788,6 @@ onUnmounted(() => {
|
||||
height: $tags-view-height;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-light);
|
||||
@@ -860,7 +883,6 @@ onUnmounted(() => {
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-white);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<!-- 顶部菜单栏 -->
|
||||
<div class="layout__header">
|
||||
<div class="layout__header-content">
|
||||
|
||||
<!-- 顶部菜单区域 -->
|
||||
<div class="layout__header-menu">
|
||||
<!-- Logo区域 -->
|
||||
@@ -119,7 +118,8 @@ watch(
|
||||
() => route.path,
|
||||
(newPath) => {
|
||||
// 获取顶级路径
|
||||
const topMenuPath = newPath.split("/").filter(Boolean).length > 1 ? newPath.match(/^\/[^/]+/)?.[0] || "/" : "/";
|
||||
const topMenuPath =
|
||||
newPath.split("/").filter(Boolean).length > 1 ? newPath.match(/^\/[^/]+/)?.[0] || "/" : "/";
|
||||
|
||||
// 如果当前路径属于当前激活的顶部菜单
|
||||
if (newPath.startsWith(activeTopMenuPath.value)) {
|
||||
|
||||
Reference in New Issue
Block a user