mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-18 22:22:59 +08:00
parent
a3312331d2
commit
5a8d6db43e
@ -19,6 +19,7 @@ type SettingInfo struct {
|
||||
BindAddress string `json:"bindAddress"`
|
||||
PanelName string `json:"panelName"`
|
||||
Theme string `json:"theme"`
|
||||
MenuTabs string `json:"menuTabs"`
|
||||
Language string `json:"language"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
LastCleanTime string `json:"lastCleanTime"`
|
||||
|
@ -80,6 +80,7 @@ func Init() {
|
||||
migrations.NewMonitorDB,
|
||||
migrations.AddNoAuthSetting,
|
||||
migrations.UpdateXpackHideMenu,
|
||||
migrations.AddMenuTabsSetting,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -128,3 +128,13 @@ var UpdateXpackHideMenu = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMenuTabsSetting = &gormigrate.Migration{
|
||||
ID: "20240415-add-menu-tabs-setting",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "MenuTabs", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export namespace Setting {
|
||||
|
||||
panelName: string;
|
||||
theme: string;
|
||||
menuTabs: string;
|
||||
language: string;
|
||||
defaultNetwork: string;
|
||||
lastCleanTime: string;
|
||||
|
@ -352,6 +352,9 @@ const message = {
|
||||
tabs: {
|
||||
more: 'More',
|
||||
hide: 'Hide',
|
||||
close: 'Close',
|
||||
closeLeft: 'Close left',
|
||||
closeRight: 'Close right',
|
||||
closeCurrent: 'Close current',
|
||||
closeOther: 'Close other',
|
||||
closeAll: 'Close All',
|
||||
@ -1228,6 +1231,7 @@ const message = {
|
||||
portChange: 'Port change',
|
||||
portChangeHelper: 'Modify the service port and restart the service. Do you want to continue?',
|
||||
theme: 'Theme',
|
||||
menuTabs: 'Menu tabs',
|
||||
dark: 'Dark',
|
||||
light: 'Light',
|
||||
auto: 'Follow System',
|
||||
|
@ -348,6 +348,9 @@ const message = {
|
||||
tabs: {
|
||||
more: '更多',
|
||||
hide: '收起',
|
||||
close: '關閉',
|
||||
closeLeft: '關閉左側',
|
||||
closeRight: '關閉右側',
|
||||
closeCurrent: '關閉當前',
|
||||
closeOther: '關閉其它',
|
||||
closeAll: '關閉所有',
|
||||
@ -1165,6 +1168,7 @@ const message = {
|
||||
portChange: '端口修改',
|
||||
portChangeHelper: '服務端口修改需要重啟服務,是否繼續?',
|
||||
theme: '主題顏色',
|
||||
menuTabs: '菜單標簽頁',
|
||||
componentSize: '組件大小',
|
||||
dark: '暗色',
|
||||
light: '亮色',
|
||||
|
@ -348,6 +348,9 @@ const message = {
|
||||
tabs: {
|
||||
more: '更多',
|
||||
hide: '收起',
|
||||
close: '关闭',
|
||||
closeLeft: '关闭左侧',
|
||||
closeRight: '关闭右侧',
|
||||
closeCurrent: '关闭当前',
|
||||
closeOther: '关闭其它',
|
||||
closeAll: '关闭所有',
|
||||
@ -1166,6 +1169,7 @@ const message = {
|
||||
portChange: '端口修改',
|
||||
portChangeHelper: '服务端口修改需要重启服务,是否继续?',
|
||||
theme: '主题颜色',
|
||||
menuTabs: '菜单标签页',
|
||||
componentSize: '组件大小',
|
||||
dark: '暗色',
|
||||
light: '亮色',
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<router-view v-slot="{ Component, route }" :key="key">
|
||||
<transition appear name="fade-transform" mode="out-in">
|
||||
<keep-alive :include="cacheRouter">
|
||||
<keep-alive :include="include">
|
||||
<component :is="Component" :key="route.path"></component>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
@ -15,4 +15,13 @@ import { computed } from 'vue';
|
||||
const key = computed(() => {
|
||||
return Math.random();
|
||||
});
|
||||
const include = computed(() => {
|
||||
return props.keepAlive || cacheRouter;
|
||||
});
|
||||
const props = defineProps({
|
||||
keepAlive: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -10,10 +10,11 @@
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
:router="true"
|
||||
:router="menuRouter"
|
||||
:collapse="isCollapse"
|
||||
:collapse-transition="false"
|
||||
:unique-opened="true"
|
||||
@select="handleMenuClick"
|
||||
>
|
||||
<SubItem :menuList="routerMenus" />
|
||||
<el-menu-item :index="''">
|
||||
@ -31,7 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted, defineEmits } from 'vue';
|
||||
import { RouteRecordRaw, useRoute } from 'vue-router';
|
||||
import { loadingSvg } from '@/utils/svg';
|
||||
import Logo from './components/Logo.vue';
|
||||
@ -49,6 +50,13 @@ import { getSettingInfo } from '@/api/modules/setting';
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
const globalStore = GlobalStore();
|
||||
defineProps({
|
||||
menuRouter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route;
|
||||
return isString(meta.activeMenu) ? meta.activeMenu : path;
|
||||
@ -79,7 +87,10 @@ const listeningWindow = () => {
|
||||
};
|
||||
};
|
||||
listeningWindow();
|
||||
|
||||
const emit = defineEmits(['menuClick']);
|
||||
const handleMenuClick = (path) => {
|
||||
emit('menuClick', path);
|
||||
};
|
||||
const logout = () => {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.sureLogOut'), i18n.global.t('commons.msg.infoTitle'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
|
97
frontend/src/layout/components/Tabs/components/TabItem.vue
Normal file
97
frontend/src/layout/components/Tabs/components/TabItem.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-tab-pane :name="tabItem.path">
|
||||
<template #label>
|
||||
<el-dropdown
|
||||
size="small"
|
||||
:id="tabItem.path"
|
||||
ref="dropdownRef"
|
||||
trigger="contextmenu"
|
||||
@visible-change="$emit('dropdownVisibleChange', $event, tabItem.path)"
|
||||
>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon v-if="tabsStore.isShowTabIcon && menuIcon">
|
||||
<el-icon>
|
||||
<SvgIcon :iconName="menuIcon" />
|
||||
</el-icon>
|
||||
</el-icon>
|
||||
<span>{{ menuName }}</span>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-if="tabsStore.hasCloseDropdown(tabItem.path, 'close')"
|
||||
@click="$emit('closeTab', tabItem.path)"
|
||||
>
|
||||
<el-icon><Close /></el-icon>
|
||||
{{ $t('tabs.close') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="tabsStore.hasCloseDropdown(tabItem.path, 'left')"
|
||||
@click="$emit('closeTabs', tabItem.path, 'left')"
|
||||
>
|
||||
<el-icon><DArrowLeft /></el-icon>
|
||||
{{ $t('tabs.closeLeft') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="tabsStore.hasCloseDropdown(tabItem.path, 'right')"
|
||||
@click="$emit('closeTabs', tabItem.path, 'right')"
|
||||
>
|
||||
<el-icon><DArrowRight /></el-icon>
|
||||
{{ $t('tabs.closeRight') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="tabsStore.hasCloseDropdown(tabItem.path, 'other')"
|
||||
@click="$emit('closeOtherTabs', tabItem.path)"
|
||||
>
|
||||
<el-icon><More /></el-icon>
|
||||
{{ $t('tabs.closeOther') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { TabsStore } from '@/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Close, DArrowLeft, DArrowRight, More } from '@element-plus/icons-vue';
|
||||
import SvgIcon from '@/components/svg-icon/svg-icon.vue';
|
||||
|
||||
const i18n = useI18n();
|
||||
const tabsStore = TabsStore();
|
||||
|
||||
const props = defineProps({
|
||||
tabItem: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['closeTab', 'closeOtherTabs', 'closeTabs', 'dropdownVisibleChange']);
|
||||
|
||||
const menuName = computed(() => {
|
||||
return i18n.t(props.tabItem.meta.title);
|
||||
});
|
||||
|
||||
const menuIcon = computed(() => {
|
||||
return props.tabItem.meta.icon;
|
||||
});
|
||||
const dropdownRef = ref();
|
||||
|
||||
defineExpose({
|
||||
dropdownRef,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.common-tabs .custom-tabs-label .el-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.common-tabs .custom-tabs-label span {
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
83
frontend/src/layout/components/Tabs/index.vue
Normal file
83
frontend/src/layout/components/Tabs/index.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<el-tabs
|
||||
v-bind="$attrs"
|
||||
v-model="tabsStore.activeTabPath"
|
||||
class="common-tabs"
|
||||
type="card"
|
||||
:closable="tabsStore.openedTabs.length > 1"
|
||||
@tab-change="tabChange"
|
||||
@tab-remove="closeTab"
|
||||
>
|
||||
<tabs-view-item
|
||||
v-for="item in tabsStore.openedTabs"
|
||||
ref="tabItems"
|
||||
:key="item.path"
|
||||
:tab-item="item"
|
||||
@close-tab="closeTab"
|
||||
@close-other-tabs="closeOtherTabs"
|
||||
@close-tabs="closeTabs"
|
||||
@dropdown-visible-change="dropdownVisibleChange"
|
||||
/>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { TabsStore } from '@/store';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import TabsViewItem from './components/TabItem.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const tabsStore = TabsStore();
|
||||
const tabItems = ref();
|
||||
|
||||
onMounted(() => {
|
||||
if (!tabsStore.openedTabs.length) {
|
||||
tabsStore.addTab(route);
|
||||
}
|
||||
tabsStore.activeTabPath = route.path;
|
||||
});
|
||||
|
||||
const tabChange = (tabPath) => {
|
||||
const tab = tabsStore.findTab(tabPath);
|
||||
if (tab) {
|
||||
router.push(tab);
|
||||
tabsStore.activeTabPath = tab.path;
|
||||
}
|
||||
};
|
||||
|
||||
const closeTab = (tabPath) => {
|
||||
const lastTabPath = tabsStore.removeTab(tabPath);
|
||||
if (lastTabPath) {
|
||||
tabChange(lastTabPath);
|
||||
}
|
||||
};
|
||||
|
||||
const closeOtherTabs = (tabPath) => {
|
||||
tabsStore.removeOtherTabs(tabPath);
|
||||
tabChange(tabPath);
|
||||
};
|
||||
|
||||
const closeTabs = (tabPath, type) => {
|
||||
tabsStore.removeTabs(tabPath, type);
|
||||
tabChange(tabPath);
|
||||
};
|
||||
|
||||
const dropdownVisibleChange = (visible, tabPath) => {
|
||||
if (visible) {
|
||||
// 关闭其他下拉菜单
|
||||
tabItems.value.forEach(({ dropdownRef }) => {
|
||||
if (dropdownRef.id !== tabPath) {
|
||||
dropdownRef.handleClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
@ -2,3 +2,4 @@ export { default as Sidebar } from './Sidebar/index.vue';
|
||||
export { default as Footer } from './AppFooter.vue';
|
||||
export { default as AppMain } from './AppMain.vue';
|
||||
export { default as MobileHeader } from './MobileHeader.vue';
|
||||
export { default as Tabs } from '@/layout/components/Tabs/index.vue';
|
||||
|
@ -2,13 +2,13 @@
|
||||
<div :class="classObj" class="app-wrapper" v-loading="loading" :element-loading-text="loadingText" fullscreen>
|
||||
<div v-if="classObj.mobile && classObj.openSidebar" class="drawer-bg" @click="handleClickOutside" />
|
||||
<div class="app-sidebar" v-if="!globalStore.isFullScreen">
|
||||
<Sidebar />
|
||||
<Sidebar @menu-click="handleMenuClick" :menu-router="!classObj.openMenuTabs" />
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
<mobile-header v-if="classObj.mobile" />
|
||||
<app-main class="app-main" />
|
||||
|
||||
<Tabs v-if="classObj.openMenuTabs" />
|
||||
<app-main :keep-alive="classObj.openMenuTabs ? tabsStore.cachedTabs : null" class="app-main" />
|
||||
<Footer class="app-footer" v-if="!globalStore.isFullScreen" />
|
||||
</div>
|
||||
</div>
|
||||
@ -16,17 +16,21 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, computed, ref, watch, onBeforeUnmount } from 'vue';
|
||||
import { Sidebar, Footer, AppMain, MobileHeader } from './components';
|
||||
import { Sidebar, Footer, AppMain, MobileHeader, Tabs } from './components';
|
||||
import useResize from './hooks/useResize';
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { GlobalStore, MenuStore, TabsStore } from '@/store';
|
||||
import { DeviceType } from '@/enums/app';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { getLicense, getSettingInfo, getSystemAvailable } from '@/api/modules/setting';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
useResize();
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
const globalStore = GlobalStore();
|
||||
const tabsStore = TabsStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
const loading = ref(false);
|
||||
@ -36,12 +40,18 @@ const { switchDark } = useTheme();
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
if (!tabsStore.activeTabPath) {
|
||||
handleMenuClick('/');
|
||||
}
|
||||
});
|
||||
const classObj = computed(() => {
|
||||
return {
|
||||
fullScreen: globalStore.isFullScreen,
|
||||
hideSidebar: menuStore.isCollapse,
|
||||
openSidebar: !menuStore.isCollapse,
|
||||
mobile: globalStore.device === DeviceType.Mobile,
|
||||
openMenuTabs: globalStore.openMenuTabs,
|
||||
withoutAnimation: menuStore.withoutAnimation,
|
||||
};
|
||||
});
|
||||
@ -59,6 +69,11 @@ watch(
|
||||
}
|
||||
},
|
||||
);
|
||||
const handleMenuClick = async (path) => {
|
||||
await router.push({ path: path });
|
||||
tabsStore.addTab(route);
|
||||
tabsStore.activeTabPath = route.path;
|
||||
};
|
||||
|
||||
const loadDataFromDB = async () => {
|
||||
const res = await getSettingInfo();
|
||||
@ -66,6 +81,7 @@ const loadDataFromDB = async () => {
|
||||
i18n.locale.value = res.data.language;
|
||||
i18n.warnHtmlMessage = false;
|
||||
globalStore.entrance = res.data.securityEntrance;
|
||||
globalStore.setOpenMenuTabs(res.data.menuTabs === 'enable');
|
||||
globalStore.updateLanguage(res.data.language);
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: res.data.theme });
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, panelName: res.data.panelName });
|
||||
|
@ -2,10 +2,11 @@ import { createPinia } from 'pinia';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||
import GlobalStore from './modules/global';
|
||||
import MenuStore from './modules/menu';
|
||||
import TabsStore from './modules/tabs';
|
||||
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
export { GlobalStore, MenuStore };
|
||||
export { GlobalStore, MenuStore, TabsStore };
|
||||
|
||||
export default pinia;
|
||||
|
@ -20,6 +20,7 @@ export interface GlobalState {
|
||||
language: string; // zh | en | tw
|
||||
themeConfig: ThemeConfigProp;
|
||||
isFullScreen: boolean;
|
||||
openMenuTabs: boolean;
|
||||
isOnRestart: boolean;
|
||||
agreeLicense: boolean;
|
||||
hasNewVersion: boolean;
|
||||
|
@ -23,6 +23,7 @@ const GlobalStore = defineStore({
|
||||
logoWithText: '',
|
||||
favicon: '',
|
||||
},
|
||||
openMenuTabs: false,
|
||||
isFullScreen: false,
|
||||
isOnRestart: false,
|
||||
agreeLicense: false,
|
||||
@ -39,6 +40,9 @@ const GlobalStore = defineStore({
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
setOpenMenuTabs(openMenuTabs: boolean) {
|
||||
this.openMenuTabs = openMenuTabs;
|
||||
},
|
||||
setScreenFull() {
|
||||
this.isFullScreen = !this.isFullScreen;
|
||||
},
|
||||
|
142
frontend/src/store/modules/tabs.ts
Normal file
142
frontend/src/store/modules/tabs.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const TabsStore = defineStore(
|
||||
'TabsStore',
|
||||
() => {
|
||||
const isShowTabIcon = ref(true);
|
||||
// 缓存的KEY,直接给keepalive使用
|
||||
const cachedTabs = ref([]);
|
||||
const openedTabs = ref([]);
|
||||
const activeTabPath = ref('');
|
||||
|
||||
const getActivePath = (path) => {
|
||||
let firstSlashIndex = path.indexOf('/');
|
||||
let lastSlashIndex = path.lastIndexOf('/');
|
||||
if (firstSlashIndex === -1 || firstSlashIndex === lastSlashIndex) {
|
||||
return path;
|
||||
}
|
||||
return path.substring(firstSlashIndex, lastSlashIndex);
|
||||
};
|
||||
|
||||
const getTabIdxByPath = (path) => {
|
||||
return openedTabs.value.findIndex((v) => v.path === path);
|
||||
};
|
||||
|
||||
const removeAllTabs = () => {
|
||||
openedTabs.value = [];
|
||||
cachedTabs.value = [];
|
||||
};
|
||||
|
||||
const removeUnActiveTabs = () => {
|
||||
if (openedTabs.value.length) {
|
||||
let idx = getTabIdxByPath(activeTabPath.value);
|
||||
idx = idx > -1 ? idx : 0;
|
||||
const tab = openedTabs.value[idx];
|
||||
removeOtherTabs(tab);
|
||||
}
|
||||
};
|
||||
|
||||
const findTab = (path) => {
|
||||
const idx = getTabIdxByPath(path);
|
||||
if (idx > -1) {
|
||||
return openedTabs.value[idx];
|
||||
}
|
||||
};
|
||||
|
||||
const addTab = (tab) => {
|
||||
const idx = getTabIdxByPath(tab.path);
|
||||
if (idx < 0) {
|
||||
openedTabs.value.push(Object.assign({}, tab));
|
||||
addCachedTab(tab.name);
|
||||
}
|
||||
};
|
||||
|
||||
const removeTab = (path) => {
|
||||
if (openedTabs.value.length > 1) {
|
||||
const idx = getTabIdxByPath(path);
|
||||
if (idx > -1) {
|
||||
removeCachedTab(openedTabs.value[idx].name);
|
||||
openedTabs.value.splice(idx, 1);
|
||||
}
|
||||
return openedTabs.value[openedTabs.value.length - 1].path;
|
||||
}
|
||||
};
|
||||
|
||||
const removeOtherTabs = (path) => {
|
||||
const idx = getTabIdxByPath(path);
|
||||
if (idx > -1) {
|
||||
const tab = openedTabs.value[idx];
|
||||
openedTabs.value = [tab];
|
||||
cachedTabs.value = [];
|
||||
cachedTabs.value = [tab.name];
|
||||
}
|
||||
};
|
||||
|
||||
const removeTabs = (path, type) => {
|
||||
if (path) {
|
||||
const idx = getTabIdxByPath(path);
|
||||
let removeTabs = [];
|
||||
if (type === 'right') {
|
||||
removeTabs = openedTabs.value.splice(idx + 1);
|
||||
} else if (type === 'left') {
|
||||
removeTabs = openedTabs.value.splice(0, idx);
|
||||
}
|
||||
if (removeTabs.length) {
|
||||
removeTabs.forEach((e) => removeCachedTab(e.name));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addCachedTab = (name) => {
|
||||
if (name && !cachedTabs.value.includes(name)) {
|
||||
cachedTabs.value.push(name);
|
||||
}
|
||||
};
|
||||
|
||||
const removeCachedTab = (name) => {
|
||||
if (name) {
|
||||
const idx = cachedTabs.value.findIndex((v) => v === name);
|
||||
if (idx > -1) {
|
||||
cachedTabs.value.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const hasCloseDropdown = (path, type) => {
|
||||
const idx = getTabIdxByPath(path);
|
||||
switch (type) {
|
||||
case 'close':
|
||||
case 'other':
|
||||
return openedTabs.value.length > 1;
|
||||
case 'left':
|
||||
return idx !== 0;
|
||||
case 'right':
|
||||
return idx !== openedTabs.value.length - 1;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isShowTabIcon,
|
||||
activeTabPath,
|
||||
openedTabs,
|
||||
cachedTabs,
|
||||
addTab,
|
||||
findTab,
|
||||
addCachedTab,
|
||||
removeCachedTab,
|
||||
removeTab,
|
||||
removeTabs,
|
||||
removeOtherTabs,
|
||||
removeAllTabs,
|
||||
removeUnActiveTabs,
|
||||
hasCloseDropdown,
|
||||
getActivePath,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: true,
|
||||
},
|
||||
);
|
||||
|
||||
export default TabsStore;
|
@ -202,6 +202,9 @@ const loadDetail = (log: string) => {
|
||||
if (log.indexOf('[Theme]') !== -1) {
|
||||
return log.replace('[Theme]', '[' + i18n.global.t('setting.theme') + ']');
|
||||
}
|
||||
if (log.indexOf('[MenuTabs]') !== -1) {
|
||||
return log.replace('[MenuTabs]', '[' + i18n.global.t('setting.menuTabs') + ']');
|
||||
}
|
||||
if (log.indexOf('[SessionTimeout]') !== -1) {
|
||||
return log.replace('[SessionTimeout]', '[' + i18n.global.t('setting.sessionTimeout') + ']');
|
||||
}
|
||||
|
@ -153,13 +153,14 @@ import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { ElForm } from 'element-plus';
|
||||
import { loginApi, getCaptcha, mfaLoginApi, checkIsDemo, getLanguage } from '@/api/modules/auth';
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { GlobalStore, MenuStore, TabsStore } from '@/store';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const menuStore = MenuStore();
|
||||
const tabsStore = TabsStore();
|
||||
const usei18n = useI18n();
|
||||
|
||||
const errAuthInfo = ref(false);
|
||||
@ -270,6 +271,7 @@ const login = (formEl: FormInstance | undefined) => {
|
||||
globalStore.setLogStatus(true);
|
||||
globalStore.setAgreeLicense(true);
|
||||
menuStore.setMenuList([]);
|
||||
tabsStore.removeAllTabs();
|
||||
MsgSuccess(i18n.global.t('commons.msg.loginSuccess'));
|
||||
router.push({ name: 'home' });
|
||||
} catch (error) {
|
||||
@ -295,6 +297,7 @@ const mfaLogin = async (auto: boolean) => {
|
||||
}
|
||||
globalStore.setLogStatus(true);
|
||||
menuStore.setMenuList([]);
|
||||
tabsStore.removeAllTabs();
|
||||
MsgSuccess(i18n.global.t('commons.msg.loginSuccess'));
|
||||
router.push({ name: 'home' });
|
||||
}
|
||||
|
@ -40,6 +40,17 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('setting.menuTabs')" prop="menuTabs">
|
||||
<el-radio-group @change="onSave('MenuTabs', form.menuTabs)" v-model="form.menuTabs">
|
||||
<el-radio-button value="enable">
|
||||
<span>{{ $t('commons.button.enable') }}</span>
|
||||
</el-radio-button>
|
||||
<el-radio-button value="disable">
|
||||
<span>{{ $t('commons.button.disable') }}</span>
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('setting.title')" prop="panelName">
|
||||
<el-input disabled v-model="form.panelName">
|
||||
<template #append>
|
||||
@ -160,6 +171,7 @@ const form = reactive({
|
||||
panelName: '',
|
||||
systemIP: '',
|
||||
theme: '',
|
||||
menuTabs: '',
|
||||
language: '',
|
||||
complexityVerification: '',
|
||||
defaultNetwork: '',
|
||||
@ -200,6 +212,7 @@ const search = async () => {
|
||||
form.panelName = res.data.panelName;
|
||||
form.systemIP = res.data.systemIP;
|
||||
form.theme = res.data.theme;
|
||||
form.menuTabs = res.data.menuTabs;
|
||||
form.language = res.data.language;
|
||||
form.complexityVerification = res.data.complexityVerification;
|
||||
form.defaultNetwork = res.data.defaultNetwork;
|
||||
@ -270,6 +283,9 @@ const onSave = async (key: string, val: any) => {
|
||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: val });
|
||||
switchDark();
|
||||
}
|
||||
if (key === 'MenuTabs') {
|
||||
globalStore.setOpenMenuTabs(val === 'enable');
|
||||
}
|
||||
let param = {
|
||||
key: key,
|
||||
value: val + '',
|
||||
|
Loading…
Reference in New Issue
Block a user