增加了tabs

This commit is contained in:
2025-10-30 17:34:32 +08:00
parent ddf90424ba
commit 8430eb509c
12 changed files with 522 additions and 271 deletions
+212 -231
View File
@@ -46,194 +46,202 @@
</el-aside>
</template>
<script>
import { ref, computed, onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useAllDataStore } from "@/stores";
import { getAllMenus } from "@/api/menu";
<script setup>
import { ref, computed, onMounted, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAllDataStore } from '@/stores';
import { getAllMenus } from '@/api/menu';
export default {
name: "CommonAside",
setup() {
const router = useRouter();
const route = useRoute();
const list = ref([]);
const loading = ref(false);
const emit = defineEmits(['menu-click']);
const store = useAllDataStore();
const isCollapse = computed(() => store.state.isCollapse);
const width = computed(() => store.state.isCollapse ? '64px' : '180px');
const router = useRouter();
const route = useRoute();
const list = ref([]);
const loading = ref(false);
const store = useAllDataStore();
const isCollapse = computed(() => store.state.isCollapse);
const width = computed(() => store.state.isCollapse ? '64px' : '180px');
const asideBgColor = ref('#0081ff');
const asideTextColor = ref('#ffffff');
// 更新主题颜色
const updateThemeColors = () => {
try {
const root = document.documentElement;
const bgColor = getComputedStyle(root).getPropertyValue('--aside-bg-color').trim();
const textColor = getComputedStyle(root).getPropertyValue('--aside-text-color').trim();
// 主题颜色(用于 Element Plus 组件,需要响应式)
// 初始值设为可见的默认值,避免初始化时不可见
const asideBgColor = ref('#0081ff');
const asideTextColor = ref('#ffffff');
// 只有当获取到有效值时才更新
if (bgColor) {
asideBgColor.value = bgColor;
}
if (textColor) {
asideTextColor.value = textColor;
}
} catch (e) {
console.warn('更新主题颜色失败:', e);
// 出错时保持默认值
}
};
// 将后端数据转换为前端需要的格式
const transformMenuData = (menus) => {
// 首先映射字段格式
const mappedMenus = menus.map(menu => ({
id: menu.id,
path: menu.path,
icon: menu.icon || 'fa-circle',
label: menu.name,
route: menu.path,
parentId: menu.parentId || 0,
order: menu.order || 0,
children: []
}));
// 构建树形结构
const menuMap = new Map();
const rootMenus = [];
// 先创建所有菜单的映射
mappedMenus.forEach(menu => {
menuMap.set(menu.id, menu);
});
// 构建树形结构
mappedMenus.forEach(menu => {
if (menu.parentId === 0) {
rootMenus.push(menu);
} else {
const parent = menuMap.get(menu.parentId);
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(menu);
} else {
// 如果找不到父节点,作为根节点处理
rootMenus.push(menu);
}
}
});
// 按 order 排序(确保排序正确)
const sortMenus = (menus) => {
if (!menus || menus.length === 0) return;
// 更新主题颜色
const updateThemeColors = () => {
try {
const root = document.documentElement;
const bgColor = getComputedStyle(root).getPropertyValue('--aside-bg-color').trim();
const textColor = getComputedStyle(root).getPropertyValue('--aside-text-color').trim();
// 只有当获取到有效值时才更新
if (bgColor) {
asideBgColor.value = bgColor;
}
if (textColor) {
asideTextColor.value = textColor;
}
} catch (e) {
console.warn('更新主题颜色失败:', e);
// 出错时保持默认值
// 对当前层级的菜单进行排序
menus.sort((a, b) => {
const orderA = Number(a.order) ?? 999999; // 没有order的排在最后
const orderB = Number(b.order) ?? 999999;
const diff = orderA - orderB;
// 如果 order 相同,按 id 排序(保证稳定性)
if (diff === 0) {
return (a.id || 0) - (b.id || 0);
}
};
// 将后端数据转换为前端需要的格式
const transformMenuData = (menus) => {
// 首先映射字段格式
const mappedMenus = menus.map(menu => ({
id: menu.id,
path: menu.path,
icon: menu.icon || 'fa-circle',
label: menu.name,
route: menu.path,
parentId: menu.parentId || 0,
order: menu.order || 0,
children: []
}));
// 构建树形结构
const menuMap = new Map();
const rootMenus = [];
// 先创建所有菜单的映射
mappedMenus.forEach(menu => {
menuMap.set(menu.id, menu);
});
// 构建树形结构
mappedMenus.forEach(menu => {
if (menu.parentId === 0) {
rootMenus.push(menu);
} else {
const parent = menuMap.get(menu.parentId);
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(menu);
} else {
// 如果找不到父节点,作为根节点处理
rootMenus.push(menu);
}
}
});
// 按 order 排序(确保排序正确)
const sortMenus = (menus) => {
if (!menus || menus.length === 0) return;
// 对当前层级的菜单进行排序
menus.sort((a, b) => {
const orderA = Number(a.order) ?? 999999; // 没有order的排在最后
const orderB = Number(b.order) ?? 999999;
const diff = orderA - orderB;
// 如果 order 相同,按 id 排序(保证稳定性)
if (diff === 0) {
return (a.id || 0) - (b.id || 0);
}
return diff;
});
// 递归排序子菜单
menus.forEach(menu => {
if (menu.children && menu.children.length > 0) {
sortMenus(menu.children);
}
});
};
// 先排序根菜单
sortMenus(rootMenus);
// console.log('排序后的根菜单:', rootMenus.map(m => ({ name: m.label, order: m.order })));
return rootMenus;
};
// 获取菜单数据
const fetchMenus = async () => {
loading.value = true;
try {
// 优先从 localStorage 读取
const cachedMenus = localStorage.getItem('menuData');
let menuData = null;
if (cachedMenus) {
try {
menuData = JSON.parse(cachedMenus);
// console.log('从缓存读取菜单数据');
} catch (e) {
console.warn('缓存菜单数据解析失败:', e);
}
}
// 如果缓存中没有或解析失败,从接口获取
if (!menuData) {
const res = await getAllMenus();
if (res && res.success && res.data) {
menuData = res.data;
// 保存到缓存
localStorage.setItem('menuData', JSON.stringify(menuData));
} else {
console.error('获取菜单失败:', res?.message || '未知错误');
list.value = [];
loading.value = false;
return;
}
}
// 转换并排序菜单数据
const transformedMenus = transformMenuData(menuData);
// console.log('转换后的菜单数据:', transformedMenus);
// console.log('菜单顺序(按 order 排序):', transformedMenus.map(m => ({ name: m.label, order: m.order, id: m.id })));
list.value = transformedMenus;
} catch (error) {
console.error('获取菜单异常:', error);
list.value = [];
} finally {
loading.value = false;
}
};
// 组件挂载时获取菜单
onMounted(() => {
// 先更新主题颜色,确保组件可见
updateThemeColors();
// 延迟一点获取菜单,确保主题已初始化
setTimeout(() => {
fetchMenus();
}, 100);
// 监听主题变化事件
window.addEventListener('theme-change', updateThemeColors);
// 监听 CSS 变量变化(MutationObserver
const observer = new MutationObserver(updateThemeColors);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
return diff;
});
// 递归排序子菜单
menus.forEach(menu => {
if (menu.children && menu.children.length > 0) {
sortMenus(menu.children);
}
});
};
// 计算属性:统一排序所有菜单项(不再区分有无子菜单)
const sortedMenuList = computed(() => {
// 创建副本并排序,确保按 order 排序
const sorted = [...list.value].sort((a, b) => {
// 先排序根菜单
sortMenus(rootMenus);
// console.log('排序后的根菜单:', rootMenus.map(m => ({ name: m.label, order: m.order })));
return rootMenus;
};
// 获取菜单数据
const fetchMenus = async () => {
loading.value = true;
try {
// 优先从 localStorage 读取
const cachedMenus = localStorage.getItem('menuData');
let menuData = null;
if (cachedMenus) {
try {
menuData = JSON.parse(cachedMenus);
// console.log('从缓存读取菜单数据');
} catch (e) {
console.warn('缓存菜单数据解析失败:', e);
}
}
// 如果缓存中没有或解析失败,从接口获取
if (!menuData) {
const res = await getAllMenus();
if (res && res.success && res.data) {
menuData = res.data;
// 保存到缓存
localStorage.setItem('menuData', JSON.stringify(menuData));
} else {
console.error('获取菜单失败:', res?.message || '未知错误');
list.value = [];
loading.value = false;
return;
}
}
// 转换并排序菜单数据
const transformedMenus = transformMenuData(menuData);
// console.log('转换后的菜单数据:', transformedMenus);
// console.log('菜单顺序(按 order 排序):', transformedMenus.map(m => ({ name: m.label, order: m.order, id: m.id })));
list.value = transformedMenus;
} catch (error) {
console.error('获取菜单异常:', error);
list.value = [];
} finally {
loading.value = false;
}
};
// 组件挂载时获取菜单
onMounted(() => {
// 先更新主题颜色,确保组件可见
updateThemeColors();
// 延迟一点获取菜单,确保主题已初始化
setTimeout(() => {
fetchMenus();
}, 100);
// 监听主题变化事件
window.addEventListener('theme-change', updateThemeColors);
// 监听 CSS 变量变化(MutationObserver
const observer = new MutationObserver(updateThemeColors);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
});
// 计算属性:统一排序所有菜单项(不再区分有无子菜单)
const sortedMenuList = computed(() => {
// 创建副本并排序,确保按 order 排序
const sorted = [...list.value].sort((a, b) => {
const orderA = Number(a.order) ?? 999999;
const orderB = Number(b.order) ?? 999999;
if (orderA === orderB) {
return (a.id || 0) - (b.id || 0);
}
return orderA - orderB;
});
// 确保子菜单也排序
sorted.forEach(menu => {
if (menu.children && menu.children.length > 0) {
menu.children.sort((a, b) => {
const orderA = Number(a.order) ?? 999999;
const orderB = Number(b.order) ?? 999999;
if (orderA === orderB) {
@@ -241,60 +249,33 @@ export default {
}
return orderA - orderB;
});
// 确保子菜单也排序
sorted.forEach(menu => {
if (menu.children && menu.children.length > 0) {
menu.children.sort((a, b) => {
const orderA = Number(a.order) ?? 999999;
const orderB = Number(b.order) ?? 999999;
if (orderA === orderB) {
return (a.id || 0) - (b.id || 0);
}
return orderA - orderB;
});
}
});
return sorted;
});
}
});
return sorted;
});
// 菜单点击事件处理
const handleMenuSelect = (index) => {
const menuItem = findMenuItemByPath(list.value, index);
if (menuItem && menuItem.route) {
router.push(menuItem.route);
}
};
// 递归查找菜单项
const findMenuItemByPath = (menus, path) => {
for (const menu of menus) {
if (menu.path === path) {
return menu;
}
if (menu.children && menu.children.length > 0) {
const found = findMenuItemByPath(menu.children, path);
if (found) return found;
}
}
return null;
};
return {
list,
sortedMenuList,
store,
isCollapse,
width,
loading,
handleMenuSelect,
route,
asideBgColor,
asideTextColor
};
// 菜单点击事件emit 通知父组件
const handleMenuSelect = (index) => {
const menuItem = findMenuItemByPath(list.value, index);
if (menuItem && menuItem.route) {
emit('menu-click', menuItem);
}
};
// 递归查找菜单项
const findMenuItemByPath = (menus, path) => {
for (const menu of menus) {
if (menu.path === path) {
return menu;
}
if (menu.children && menu.children.length > 0) {
const found = findMenuItemByPath(menu.children, path);
if (found) return found;
}
}
return null;
};
</script>
<style scoped lang="less">