feat: 优化AdminLayout布局组件,简化代码结构,调整部分元素顺序,并修复HeaderNav组件属性传递方式

This commit is contained in:
FalingCliff 2025-06-15 01:06:57 +08:00
parent b7a012967e
commit 1b9fb6f26d
5 changed files with 118 additions and 117 deletions

View File

@ -20,7 +20,11 @@ export default defineConfigWithVueTs(
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
{
...pluginVitest.configs.recommended,
files: ['src/**/__tests__/*'],

View File

@ -10,7 +10,7 @@
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
const route = useRoute()

View File

@ -1,19 +1,14 @@
<template>
<a-layout class="admin-layout">
<!-- 侧边栏 -->
<a-layout-sider
v-model:collapsed="collapsed"
:trigger="null"
collapsible
class="admin-sider"
>
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" class="admin-sider" collapsible>
<div class="logo">
<img src="@/assets/logo.svg" alt="Logo" />
<img alt="Logo" src="@/assets/logo.svg" />
<h1 v-show="!collapsed">Admin System</h1>
</div>
<a-menu
v-model:selectedKeys="selectedKeys"
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
mode="inline"
theme="dark"
>
@ -58,18 +53,16 @@
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
<menu-fold-outlined
v-else
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
<menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
<breadcrumb />
</div>
<div class="header-right">
<a-dropdown>
<a class="user-dropdown" @click.prevent>
<a-avatar>
<template #icon><user-outlined /></template>
<template #icon>
<user-outlined />
</template>
</a-avatar>
<span class="username">管理员</span>
</a>
@ -98,16 +91,16 @@
<div class="admin-tabs">
<a-tabs
v-model:activeKey="activeTab"
type="editable-card"
:hide-add="true"
type="editable-card"
@edit="onTabEdit"
@tabClick="onTabClick"
>
<a-tab-pane
v-for="tab in tabs"
:key="tab.path"
:tab="tab.title"
:closable="tabs.length > 1"
:tab="tab.title"
>
<template #closeIcon>
<a-dropdown :trigger="['hover']">
@ -130,7 +123,7 @@
<!-- 内容 -->
<a-layout-content class="admin-content">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<transition mode="out-in" name="fade">
<component :is="Component" />
</transition>
</router-view>
@ -147,19 +140,19 @@
</a-layout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
<script lang="ts" setup>
import { onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
DashboardOutlined,
SettingOutlined,
UserOutlined,
TeamOutlined,
MenuOutlined,
LogoutOutlined,
CloseOutlined,
DashboardOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuOutlined,
MenuUnfoldOutlined,
SettingOutlined,
TeamOutlined,
UserOutlined,
} from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import Breadcrumb from '@/components/common/Breadcrumb.vue'
@ -175,23 +168,23 @@ const loading = ref(false)
//
interface TabItem {
title: string;
path: string;
name: string;
query?: Record<string, any>;
params?: Record<string, any>;
title: string
path: string
name: string
query?: Record<string, any>
params?: Record<string, any>
}
const tabs = ref<TabItem[]>([]);
const activeTab = ref('');
const tabs = ref<TabItem[]>([])
const activeTab = ref('')
//
const addTab = (route: any) => {
const { path, meta, name, query, params } = route;
const title = meta?.title || '未命名页面';
const { path, meta, name, query, params } = route
const title = meta?.title || '未命名页面'
//
const isExist = tabs.value.some(tab => tab.path === path);
const isExist = tabs.value.some((tab) => tab.path === path)
if (!isExist) {
tabs.value.push({
@ -199,171 +192,175 @@ const addTab = (route: any) => {
path,
name,
query,
params
});
params,
})
}
activeTab.value = path;
};
activeTab.value = path
}
//
const closeTab = (targetPath: string) => {
const targetIndex = tabs.value.findIndex(tab => tab.path === targetPath);
const targetIndex = tabs.value.findIndex((tab) => tab.path === targetPath)
if (targetIndex === -1) return;
if (targetIndex === -1) return
//
if (activeTab.value === targetPath) {
//
if (targetIndex < tabs.value.length - 1) {
activeTab.value = tabs.value[targetIndex + 1].path;
router.push(tabs.value[targetIndex + 1]);
activeTab.value = tabs.value[targetIndex + 1].path
router.push(tabs.value[targetIndex + 1])
} else if (targetIndex > 0) {
activeTab.value = tabs.value[targetIndex - 1].path;
router.push(tabs.value[targetIndex - 1]);
activeTab.value = tabs.value[targetIndex - 1].path
router.push(tabs.value[targetIndex - 1])
}
}
tabs.value.splice(targetIndex, 1);
saveTabsToStorage();
};
tabs.value.splice(targetIndex, 1)
saveTabsToStorage()
}
//
const onTabEdit = (targetKey: string, action: 'add' | 'remove') => {
if (action === 'remove') {
closeTab(targetKey);
closeTab(targetKey)
}
}
};
//
const onTabClick = (key: string) => {
//
const tab = tabs.value.find(item => item.path === key);
const tab = tabs.value.find((item) => item.path === key)
if (tab) {
//
router.push({
path: tab.path,
query: tab.query,
params: tab.params
});
params: tab.params,
})
}
}
};
//
const handleTabAction = (action: string, tab: TabItem) => {
const currentIndex = tabs.value.findIndex(item => item.path === tab.path);
const currentIndex = tabs.value.findIndex((item) => item.path === tab.path)
switch (action) {
case 'current':
closeTab(tab.path);
break;
closeTab(tab.path)
break
case 'others':
tabs.value = tabs.value.filter(item => item.path === tab.path);
break;
tabs.value = tabs.value.filter((item) => item.path === tab.path)
break
case 'left':
if (currentIndex > 0) {
tabs.value = tabs.value.filter((item, index) =>
index >= currentIndex || item.path === '/dashboard'
);
tabs.value = tabs.value.filter(
(item, index) => index >= currentIndex || item.path === '/dashboard',
)
}
break;
break
case 'right':
if (currentIndex < tabs.value.length - 1) {
tabs.value = tabs.value.filter((item, index) =>
index <= currentIndex || item.path === '/dashboard'
);
tabs.value = tabs.value.filter(
(item, index) => index <= currentIndex || item.path === '/dashboard',
)
}
break;
break
case 'all':
//
const homePage = tabs.value.find(item => item.path === '/dashboard');
tabs.value = homePage ? [homePage] : [];
const homePage = tabs.value.find((item) => item.path === '/dashboard')
tabs.value = homePage ? [homePage] : []
if (homePage) {
activeTab.value = homePage.path;
router.push(homePage);
activeTab.value = homePage.path
router.push(homePage)
}
break
}
break;
}
};
//
router.beforeEach((to, from, next) => {
loading.value = true;
loading.value = true
// meta.title
if (to.meta?.title) {
addTab(to);
addTab(to)
}
next();
});
next()
})
router.afterEach(() => {
//
setTimeout(() => {
loading.value = false;
}, 300);
});
loading.value = false
}, 300)
})
//
const saveTabsToStorage = () => {
localStorage.setItem('admin-tabs', JSON.stringify(tabs.value));
localStorage.setItem('admin-active-tab', activeTab.value);
};
localStorage.setItem('admin-tabs', JSON.stringify(tabs.value))
localStorage.setItem('admin-active-tab', activeTab.value)
}
//
const loadTabsFromStorage = () => {
const savedTabs = localStorage.getItem('admin-tabs');
const savedActiveTab = localStorage.getItem('admin-active-tab');
const savedTabs = localStorage.getItem('admin-tabs')
const savedActiveTab = localStorage.getItem('admin-active-tab')
if (savedTabs) {
try {
const parsedTabs = JSON.parse(savedTabs);
tabs.value = parsedTabs;
const parsedTabs = JSON.parse(savedTabs)
tabs.value = parsedTabs
//
if (savedActiveTab) {
activeTab.value = savedActiveTab;
activeTab.value = savedActiveTab
//
const activeTabData = tabs.value.find(tab => tab.path === savedActiveTab);
const activeTabData = tabs.value.find((tab) => tab.path === savedActiveTab)
if (activeTabData && activeTabData.path !== route.path) {
router.push({
path: activeTabData.path,
query: activeTabData.query,
params: activeTabData.params
});
return true; //
params: activeTabData.params,
})
return true //
}
}
} catch (e) {
console.error('Failed to parse saved tabs:', e);
console.error('Failed to parse saved tabs:', e)
}
}
return false; //
};
return false //
}
//
watch(tabs, () => {
saveTabsToStorage();
}, { deep: true });
watch(
tabs,
() => {
saveTabsToStorage()
},
{ deep: true },
)
watch(activeTab, () => {
localStorage.setItem('admin-active-tab', activeTab.value);
});
localStorage.setItem('admin-active-tab', activeTab.value)
})
//
onMounted(() => {
//
const restored = loadTabsFromStorage();
const restored = loadTabsFromStorage()
//
if (!restored || tabs.value.length === 0) {
if (route.meta?.title) {
addTab(route);
addTab(route)
}
}
});
})
//
const selectedKeys = ref<string[]>([])
@ -436,7 +433,7 @@ watch(
() => route.path,
() => {
updateSelectedMenu()
}
},
)
onMounted(() => {

View File

@ -14,8 +14,6 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const currentYear = ref(new Date().getFullYear())
</script>

View File

@ -5,12 +5,12 @@
<menu-unfold-outlined
v-if="collapsed"
class="trigger"
@click="() => (collapsed = !collapsed)"
@click="toggleCollapsed"
/>
<menu-fold-outlined
v-else
class="trigger"
@click="() => (collapsed = !collapsed)"
@click="toggleCollapsed"
/>
<span class="logo">Admin Template</span>
</div>
@ -59,11 +59,13 @@ import {
LogoutOutlined,
} from '@ant-design/icons-vue'
defineProps<{
collapsed: boolean
}>()
const props = defineProps<{ collapsed: boolean }>()
defineEmits(['update:collapsed'])
const emit = defineEmits(['update:collapsed'])
function toggleCollapsed() {
emit('update:collapsed', !props.collapsed)
}
</script>
<style scoped>