增加了tabs
This commit is contained in:
+212
-231
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user