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

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

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

@ -1,7 +1,7 @@
<template> <template>
<div class="layout-header"> <div class="layout-header">
<div class="header-left"> <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 /> <LayoutBreakcrumb />
</div> </div>
<div class="header-right"> <div class="header-right">
@ -15,6 +15,11 @@
import LayoutBreakcrumb from './breakcrumb.vue'; import LayoutBreakcrumb from './breakcrumb.vue';
import LayoutProfile from './profile.vue'; import LayoutProfile from './profile.vue';
import LayoutOperation from './operation.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> </script>
<style lang="less" scoped> <style lang="less" scoped>

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

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

@ -1,22 +1,28 @@
<template> <template>
<div class="layout-tabs"> <div class="layout-tabs">
<el-tabs v-model="activeTab" type="card" class="demo-tabs" @tab-click="handleClick"> <el-tabs :modelValue="activeTab" type="card" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane v-for="(item, index) in tabs" :key="index" :name="item.name"> <el-tab-pane v-for="(item, index) in tabList" :key="index" :name="item.name">
<template #label> <template #label>
<XIcon :name="item.meta.icon" /> <XIcon class="tab-icon" :name="item.meta.icon" />
<span>{{ item.meta.title }}</span> <span>{{ item.meta.title }}</span>
<XIcon
v-if="tabList.length > 1"
class="tab-close"
name="close-fill"
@click.stop="handleCloseTab(index)"
/>
</template> </template>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<div class="operation"> <div class="operation">
<el-dropdown trigger="hover"> <el-dropdown trigger="hover" @command="handleCommand">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<XIcon name="apps-fill" /> <XIcon name="apps-fill" />
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item>关闭全部</el-dropdown-item> <el-dropdown-item command="all">关闭全部</el-dropdown-item>
<el-dropdown-item>关闭其他</el-dropdown-item> <el-dropdown-item command="other">关闭其他</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -25,16 +31,40 @@
</template> </template>
<script setup> <script setup>
const activeTab = ref('home'); const router = useRouter();
const tabs = computed(() => [ const store = useStore();
{ path: '/', name: 'home', meta: { title: '首页', icon: 'home-fill' } }, const activeTab = computed(() => store.state.layout.activeTab);
{ path: '/', name: 'route1', meta: { title: '菜单1', icon: 'home-fill' } }, const tabList = computed(() => store.state.layout.tabList);
{ path: '/', name: 'route2', meta: { title: '菜单2', icon: 'home-fill' } }, watch(
{ path: '/', name: 'route3', meta: { title: '菜单3', icon: 'home-fill' } }, () => unref(tabList),
{ path: '/', name: 'route4', meta: { title: '菜单4', icon: 'home-fill' } }, (value) => {
{ path: '/', name: 'route5', meta: { title: '菜单5', icon: 'home-fill' } }, if (!unref(activeTab) || value.findIndex((item) => item.name === unref(activeTab)) === -1) {
]); router.push({ name: value[0].name });
const handleClick = () => {}; }
},
{ 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> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -70,11 +100,12 @@
height: 100%; height: 100%;
border: none; border: none;
padding: 0 @layout-space-large; padding: 0 @layout-space-large;
display: flex;
.el-tabs__item { .el-tabs__item {
height: 100%; height: 100%;
min-width: 100px; min-width: 100px;
margin-right: -8px; margin-right: -8px;
display: inline-flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: none; border: none;
@ -86,13 +117,29 @@
-webkit-mask-size: 100% 100%; -webkit-mask-size: 100% 100%;
&:hover { &:hover {
background-color: @color-white-dark; background-color: @color-white-dark;
.tab-close {
width: 20px;
}
} }
&.is-active { &.is-active {
background-color: @color-primary-white; background-color: @color-primary-white;
.tab-close {
width: 20px;
}
} }
.x-icon { .tab-icon {
margin-right: @layout-space-small; 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) => { router.onError((error, to) => {
console.info('[router] error', error, to); console.info('[router] error', error, to);
}); });
import debug from '@/utils/debug';
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
if (!from.matched.length) { if (!from.matched.length) {
store.loadCache(); store.loadCache();
@ -80,8 +81,9 @@ router.beforeEach(async (to, from, next) => {
// await store.dispatch('auth/getPermission'); // await store.dispatch('auth/getPermission');
// } // }
if (to.matched.length) { 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/setActiveMenu', to.name);
store.commit('layout/setActiveTab', to.name);
console.info(`[router] from ${from.name} to ${to.name}`); console.info(`[router] from ${from.name} to ${to.name}`);
if (to.meta.global) { if (to.meta.global) {
next(); next();
@ -95,6 +97,7 @@ router.beforeEach(async (to, from, next) => {
next({ name: childName }); next({ name: childName });
} else { } else {
next(); next();
store.commit('layout/addTab', to);
} }
} }
} }

@ -1,7 +1,11 @@
import { routes } from '@/router'; import router, { routes } from '@/router';
const state = () => ({ const state = () => ({
activeAside: null, activeAside: null,
collapseAside: false,
activeMenu: null, activeMenu: null,
menuList: [],
tabList: [],
activeTab: null,
}); });
const getters = { const getters = {
asideList: () => { asideList: () => {
@ -9,11 +13,39 @@ const getters = {
arr.flatMap((item) => (item.meta?.layout ? deep(item.children || []) : item.meta?.global ? [] : item)); arr.flatMap((item) => (item.meta?.layout ? deep(item.children || []) : item.meta?.global ? [] : item));
return deep(routes); 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 = { const mutations = {
setCollapseAside: (state, data) => (state.collapseAside = data),
setActiveAside: (state, data) => (state.activeAside = data), setActiveAside: (state, data) => (state.activeAside = data),
setMenuList: (state, data) => (state.menuList = data),
setActiveMenu: (state, data) => (state.activeMenu = 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 = {}; const actions = {};
export default { 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