main
向文可 2 years ago
parent 30cd6b483d
commit c3c98de87f

@ -54,6 +54,8 @@
color: v-bind(color);
line-height: 1;
vertical-align: middle;
position: relative;
top: 1px;
}
svg.x-icon {
width: v-bind(size);

@ -1,12 +1,7 @@
<template>
<el-scrollbar class="layout-aside">
<el-menu collapse :default-active="activeAside" router>
<el-menu-item
v-for="(item, index) in asideList"
:key="index"
:index="item.name"
:route="{ name: item.name }"
>
<el-menu collapse :default-active="activeAside">
<el-menu-item v-for="(item, index) in asideList" :key="index" :index="item.name" @click="handleClick(item)">
<XIcon :name="item.meta.icon" size="30" />
<p>{{ item.meta.title }}</p>
</el-menu-item>
@ -20,6 +15,20 @@
const asideList = computed(() => store.getters['layout/asideList']);
const activeAside = computed(() => store.state.layout.activeAside);
watch(
() => activeAside,
(value) => {
store.commit(
'layout/setMenuList',
unref(asideList).find((item) => item.name === unref(value))?.children || []
);
},
{ immediate: true, deep: true }
);
const handleClick = (item) => {
store.commit('layout/setActiveAside', item.name);
};
</script>
<style lang="less" scoped>

@ -1,7 +1,7 @@
<template>
<div class="layout-header">
<div class="header-left">
<XIcon name="menu-fold-fill" size="16" />
<XIcon :name="collapseAside ? 'menu-unfold-fill' : 'menu-fold-fill'" size="16" @click="handleCollapse" />
<LayoutBreakcrumb />
</div>
<div class="header-right">
@ -15,6 +15,11 @@
import LayoutBreakcrumb from './breakcrumb.vue';
import LayoutProfile from './profile.vue';
import LayoutOperation from './operation.vue';
const store = useStore();
const collapseAside = computed(() => store.getters['layout/collapseAside']);
const handleCollapse = () => {
store.commit('layout/setCollapseAside', !unref(collapseAside));
};
</script>
<style lang="less" scoped>

@ -45,8 +45,8 @@
border-radius: @layout-border-radius;
&.is-opened {
background-color: @color-ghost-black;
:deep(.el-sub-menu__title) {
background-color: @layout-menu-active-bgc;
> :deep(.el-sub-menu__title) {
background-color: @layout-menu-hover-bgc;
}
}
:deep(.el-sub-menu__title) {

@ -1,8 +1,8 @@
<template>
<el-scrollbar class="layout-menu">
<el-scrollbar class="layout-menu" :class="{ collapse: collapseAside }">
<div class="title">马士兵严选后台管理平台</div>
<el-divider>test</el-divider>
<el-menu router :default-active="activeMenu">
<el-divider>{{ activeAsideName }}</el-divider>
<el-menu unique-opened router :default-active="activeMenu">
<MenuItem v-for="(item, index) in menuList" :key="index" :menu-item="item" />
</el-menu>
</el-scrollbar>
@ -11,9 +11,10 @@
<script setup>
import MenuItem from './menu-item.vue';
const store = useStore();
const menuList = computed(() => store.getters['layout/menuList']);
const activeAsideName = computed(() => store.getters['layout/activeAsideName']);
const menuList = computed(() => store.state.layout.menuList);
const activeMenu = computed(() => store.state.layout.activeMenu);
const handleClick = () => {};
const collapseAside = computed(() => store.getters['layout/collapseAside']);
</script>
<style lang="less" scoped>
@ -24,6 +25,16 @@
background-color: @layout-menu-bgc;
box-shadow: @layout-shadow;
padding: 0 @layout-space;
transition: width 0.3s;
&.collapse {
width: 0;
padding: 0;
overflow: hidden;
:deep(*) {
opacity: 0;
transition: opacity 0.3s;
}
}
.title {
width: 100%;
height: @layout-header-height;

@ -1,22 +1,28 @@
<template>
<div class="layout-tabs">
<el-tabs v-model="activeTab" type="card" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane v-for="(item, index) in tabs" :key="index" :name="item.name">
<el-tabs :modelValue="activeTab" type="card" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane v-for="(item, index) in tabList" :key="index" :name="item.name">
<template #label>
<XIcon :name="item.meta.icon" />
<XIcon class="tab-icon" :name="item.meta.icon" />
<span>{{ item.meta.title }}</span>
<XIcon
v-if="tabList.length > 1"
class="tab-close"
name="close-fill"
@click.stop="handleCloseTab(index)"
/>
</template>
</el-tab-pane>
</el-tabs>
<div class="operation">
<el-dropdown trigger="hover">
<el-dropdown trigger="hover" @command="handleCommand">
<span class="el-dropdown-link">
<XIcon name="apps-fill" />
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>关闭全部</el-dropdown-item>
<el-dropdown-item>关闭其他</el-dropdown-item>
<el-dropdown-item command="all">关闭全部</el-dropdown-item>
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -25,16 +31,40 @@
</template>
<script setup>
const activeTab = ref('home');
const tabs = computed(() => [
{ path: '/', name: 'home', meta: { title: '首页', icon: 'home-fill' } },
{ path: '/', name: 'route1', meta: { title: '菜单1', icon: 'home-fill' } },
{ path: '/', name: 'route2', meta: { title: '菜单2', icon: 'home-fill' } },
{ path: '/', name: 'route3', meta: { title: '菜单3', icon: 'home-fill' } },
{ path: '/', name: 'route4', meta: { title: '菜单4', icon: 'home-fill' } },
{ path: '/', name: 'route5', meta: { title: '菜单5', icon: 'home-fill' } },
]);
const handleClick = () => {};
const router = useRouter();
const store = useStore();
const activeTab = computed(() => store.state.layout.activeTab);
const tabList = computed(() => store.state.layout.tabList);
watch(
() => unref(tabList),
(value) => {
if (!unref(activeTab) || value.findIndex((item) => item.name === unref(activeTab)) === -1) {
router.push({ name: value[0].name });
}
},
{ immediate: true, deep: true }
);
const handleCloseTab = (index) => {
store.commit('layout/closeTab', {
index,
});
};
const handleClick = (tab) => {
router.push(unref(tabList)[tab.index].fullPath);
};
const handleCommand = (command) => {
switch (command) {
case 'all':
store.commit('layout/closeTab', {});
break;
case 'other':
store.commit('layout/closeTab', {
index: unref(tabList).findIndex((item) => item.name === unref(activeTab)),
reverse: true,
});
break;
}
};
</script>
<style lang="less" scoped>
@ -70,11 +100,12 @@
height: 100%;
border: none;
padding: 0 @layout-space-large;
display: flex;
.el-tabs__item {
height: 100%;
min-width: 100px;
margin-right: -8px;
display: inline-flex;
display: flex;
align-items: center;
justify-content: center;
border: none;
@ -86,13 +117,29 @@
-webkit-mask-size: 100% 100%;
&:hover {
background-color: @color-white-dark;
.tab-close {
width: 20px;
}
}
&.is-active {
background-color: @color-primary-white;
.tab-close {
width: 20px;
}
}
.x-icon {
.tab-icon {
margin-right: @layout-space-small;
margin-top: 1px;
}
.tab-close {
margin-left: @layout-space-small;
border-radius: 50px;
display: inline-block;
width: 0;
overflow: hidden;
transition: width 0.3s;
&:hover {
color: var(--el-color-danger);
}
}
}
}

@ -69,6 +69,7 @@ import store from '@/store';
router.onError((error, to) => {
console.info('[router] error', error, to);
});
import debug from '@/utils/debug';
router.beforeEach(async (to, from, next) => {
if (!from.matched.length) {
store.loadCache();
@ -80,8 +81,9 @@ router.beforeEach(async (to, from, next) => {
// await store.dispatch('auth/getPermission');
// }
if (to.matched.length) {
store.commit('layout/setActiveAside', to.matched[0].name);
store.commit('layout/setActiveAside', to.matched.find((item) => !item.meta?.layout).name);
store.commit('layout/setActiveMenu', to.name);
store.commit('layout/setActiveTab', to.name);
console.info(`[router] from ${from.name} to ${to.name}`);
if (to.meta.global) {
next();
@ -95,6 +97,7 @@ router.beforeEach(async (to, from, next) => {
next({ name: childName });
} else {
next();
store.commit('layout/addTab', to);
}
}
}

@ -1,7 +1,11 @@
import { routes } from '@/router';
import router, { routes } from '@/router';
const state = () => ({
activeAside: null,
collapseAside: false,
activeMenu: null,
menuList: [],
tabList: [],
activeTab: null,
});
const getters = {
asideList: () => {
@ -9,11 +13,39 @@ const getters = {
arr.flatMap((item) => (item.meta?.layout ? deep(item.children || []) : item.meta?.global ? [] : item));
return deep(routes);
},
menuList: (state, getters) => getters.asideList.find((item) => item.name === state.activeAside)?.children || [],
collapseAside: (state) => state.collapseAside || state.menuList.length < 2,
activeAsideName: (state, getters) => getters.asideList.find((item) => item.name === state.activeAside)?.meta.title,
};
const mutations = {
setCollapseAside: (state, data) => (state.collapseAside = data),
setActiveAside: (state, data) => (state.activeAside = data),
setMenuList: (state, data) => (state.menuList = data),
setActiveMenu: (state, data) => (state.activeMenu = data),
addTab: (state, data) => {
let oldIndex = state.tabList.findIndex((item) => item.name === data.name);
let tab = null;
if (oldIndex !== -1) {
tab.fullPath = data.fullPath;
} else {
tab = _.cloneDeep(data);
state.tabList.push(tab);
}
},
closeTab: (state, { index, reverse }) => {
if (typeof index === 'undefined') {
state.tabList.splice(1);
} else {
if (reverse) {
let tab = state.tabList[index];
router.push({ name: tab.name });
state.tabList = [tab];
} else if (state.tabList.length > 1) {
state.tabList.splice(index, 1);
router.push({ name: state.tabList[Math.min(index, state.tabList.length - 1)].name });
}
}
},
setActiveTab: (state, data) => (state.activeTab = data),
};
const actions = {};
export default {

@ -0,0 +1,17 @@
const during = (func, name = 'func') => {
let time = Date.now(),
error = null;
try {
func();
} catch (e) {
error = e;
}
const msg = `[debug] exec ${name} during ${Date.now() - time}ms`;
if (error) {
msg += ' with error';
}
console.info(msg, error);
};
export default {
during,
};
Loading…
Cancel
Save