2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 11:27:17 +08:00

feat: 前端路由固定

This commit is contained in:
耗子
2024-10-19 20:18:23 +08:00
parent b94664ed87
commit c75cfe9eb6
11 changed files with 104 additions and 49 deletions

View File

@@ -1,11 +1,17 @@
<script lang="ts" setup>
import { useAppStore } from '@/store'
import { useTabStore } from '@/store'
const appStore = useAppStore()
const tabStore = useTabStore()
const keepAliveNames = computed(() => {
return tabStore.tabs.filter((item) => item.keepAlive).map((item) => item.name)
})
</script>
<template>
<router-view v-slot="{ Component, route }">
<component :is="Component" v-if="appStore.reloadFlag" :key="route.path" />
<keep-alive :include="keepAliveNames">
<component :is="Component" v-if="!tabStore.reloading" :key="route.path" />
</keep-alive>
</router-view>
</template>

View File

@@ -1,10 +1,10 @@
<script lang="ts" setup>
import { useAppStore } from '@/store'
import { useTabStore } from '@/store'
const appStore = useAppStore()
const tabStore = useTabStore()
const handleReloadPage = () => {
appStore.reloadPage()
tabStore.reloadTab(tabStore.active)
}
</script>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { useAppStore, usePermissionStore, useThemeStore } from '@/store'
import { usePermissionStore, useTabStore, useThemeStore } from '@/store'
import { isUrl, renderIcon } from '@/utils'
import type { MenuInst, MenuOption } from 'naive-ui'
import type { VNodeChild } from 'vue'
@@ -11,7 +11,7 @@ const router = useRouter()
const currentRoute = useRoute()
const permissionStore = usePermissionStore()
const themeStore = useThemeStore()
const appStore = useAppStore()
const tabStore = useTabStore()
const menu = ref<MenuInst>()
watch(currentRoute, async () => {
@@ -93,7 +93,8 @@ function handleMenuSelect(key: string, item: MenuOption) {
window.open(menuItem.path)
return
}
if (menuItem.path === currentRoute.path && !currentRoute.meta?.keepAlive) appStore.reloadPage()
if (menuItem.path === currentRoute.path && !currentRoute.meta?.keepAlive)
tabStore.reloadTab(currentRoute.path)
else router.push(menuItem.path)
// 手机端自动收起菜单

View File

@@ -8,6 +8,7 @@ const tabStore = useTabStore()
interface ContextMenuOption {
show: boolean
keepAlive: boolean
x: number
y: number
currentPath: string
@@ -15,6 +16,7 @@ interface ContextMenuOption {
const contextMenuOption = reactive<ContextMenuOption>({
show: false,
keepAlive: false,
x: 0,
y: 0,
currentPath: ''
@@ -33,15 +35,15 @@ function hideContextMenu() {
contextMenuOption.show = false
}
function setContextMenu(x: number, y: number, currentPath: string) {
Object.assign(contextMenuOption, { x, y, currentPath })
function setContextMenu(x: number, y: number, keepAlive: boolean, currentPath: string) {
Object.assign(contextMenuOption, { x, y, keepAlive, currentPath })
}
// 右击菜单
async function handleContextMenu(e: MouseEvent, tabItem: TabItem) {
const { clientX, clientY } = e
hideContextMenu()
setContextMenu(clientX, clientY, tabItem.path)
setContextMenu(clientX, clientY, tabItem.keepAlive, tabItem.path)
await nextTick()
showContextMenu()
}
@@ -68,6 +70,7 @@ async function handleContextMenu(e: MouseEvent, tabItem: TabItem) {
<ContextMenu
v-model:show="contextMenuOption.show"
:current-path="contextMenuOption.currentPath"
:keep-alive="contextMenuOption.keepAlive"
:x="contextMenuOption.x"
:y="contextMenuOption.y"
/>

View File

@@ -1,9 +1,10 @@
<script lang="ts" setup>
import { useAppStore, useTabStore } from '@/store'
import { useTabStore } from '@/store'
import { renderIcon } from '@/utils'
interface Props {
show?: boolean
keepAlive?: boolean
currentPath?: string
x: number
y: number
@@ -11,27 +12,39 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
show: false,
keepAlive: false,
currentPath: ''
})
const emit = defineEmits(['update:show'])
const tabStore = useTabStore()
const appStore = useAppStore()
const options = computed(() => [
{
label: '重新加载',
key: 'reload',
disabled: props.currentPath !== tabStore.active,
icon: renderIcon('mdi:refresh', { size: 14 })
},
{
label: '关闭',
key: 'close',
disabled: tabStore.tabs.length <= 1,
icon: renderIcon('mdi:close', { size: 14 })
},
{
label: '重载',
key: 'reload',
disabled: props.currentPath !== tabStore.active,
icon: renderIcon('mdi:refresh', { size: 14 })
},
{
label: '固定',
key: 'pin',
disabled: props.keepAlive,
icon: renderIcon('mdi:pin', { size: 14 })
},
{
label: '取消固定',
key: 'unpin',
disabled: !props.keepAlive,
icon: renderIcon('mdi:pin-off', { size: 14 })
},
{
label: '关闭其他',
key: 'close-other',
@@ -64,18 +77,30 @@ const dropdownShow = computed({
})
const actionMap = new Map([
[
'reload',
() => {
appStore.reloadPage()
}
],
[
'close',
() => {
tabStore.removeTab(props.currentPath)
}
],
[
'reload',
() => {
tabStore.reloadTab(props.currentPath)
}
],
[
'pin',
() => {
tabStore.pinTab(props.currentPath)
}
],
[
'unpin',
() => {
tabStore.unpinTab(props.currentPath)
}
],
[
'close-other',
() => {

View File

@@ -12,7 +12,8 @@ export function createTabGuard(router: Router) {
tabStore.addTab({
name: String(name),
path,
title
title,
keepAlive: false
})
})
}

View File

@@ -2,21 +2,7 @@ import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state() {
return {
reloadFlag: <boolean>true
}
return {}
},
actions: {
async reloadPage() {
window.$loadingBar?.start()
this.reloadFlag = false
await nextTick()
this.reloadFlag = true
setTimeout(() => {
document.documentElement.scrollTo({ left: 0, top: 0 })
window.$loadingBar?.finish()
}, 100)
}
}
actions: {}
})

View File

@@ -1,4 +1,3 @@
export * from './app'
export * from './permission'
export * from './tab'
export * from './theme'

View File

@@ -5,6 +5,7 @@ export const WITHOUT_TAB_PATHS = ['/404', '/login']
export interface Tab {
active: string
reloading: boolean
tabs: Array<TabItem>
}
@@ -12,17 +13,20 @@ export interface TabItem {
name: string
path: string
title: string
keepAlive: boolean
}
export const useTabStore = defineStore('tab', {
state: (): Tab => {
return {
active: '',
reloading: false,
tabs: []
}
},
actions: {
setActiveTab(path: string) {
async setActiveTab(path: string) {
await nextTick()
this.active = path
},
setTabs(tabs: Array<TabItem>) {
@@ -34,6 +38,30 @@ export const useTabStore = defineStore('tab', {
return
this.setTabs([...this.tabs, tab])
},
async reloadTab(path: string) {
const findItem = this.tabs.find((item) => item.path === path)
if (!findItem) return
const keepLive = findItem.keepAlive
findItem.keepAlive = false // 取消keepAlive
window.$loadingBar.start()
this.reloading = true
await nextTick()
this.reloading = false
await nextTick()
findItem.keepAlive = keepLive // 恢复keepAlive原状态
setTimeout(() => {
document.documentElement.scrollTo({ left: 0, top: 0 })
window.$loadingBar.finish()
}, 100)
},
pinTab(path: string) {
const findItem = this.tabs.find((item) => item.path === path)
if (findItem) findItem.keepAlive = true
},
unpinTab(path: string) {
const findItem = this.tabs.find((item) => item.path === path)
if (findItem) findItem.keepAlive = false
},
removeTab(path: string) {
if (path === this.active) {
const activeIndex = this.tabs.findIndex((item) => item.path === path)
@@ -65,5 +93,7 @@ export const useTabStore = defineStore('tab', {
this.setActiveTab('')
}
},
persist: true
persist: {
storage: sessionStorage
}
})

View File

@@ -14,7 +14,7 @@ import { useI18n } from 'vue-i18n'
import dashboard from '@/api/panel/dashboard'
import { router } from '@/router'
import { useAppStore } from '@/store'
import { useTabStore } from '@/store'
import { formatDateTime, formatDuration, toTimestamp } from '@/utils/common'
import { formatBytes, formatPercent } from '@/utils/file'
import VChart from 'vue-echarts'
@@ -31,7 +31,7 @@ use([
])
const { locale } = useI18n()
const appStore = useAppStore()
const tabStore = useTabStore()
const realtime = ref<Realtime | null>(null)
const systemInfo = ref<SystemInfo | null>(null)
const homeApps = ref<HomeApp[] | null>(null)
@@ -310,7 +310,7 @@ const handleRestartPanel = () => {
dashboard.restart().then(() => {
window.$message.success('面板重启成功')
setTimeout(() => {
appStore.reloadPage()
tabStore.reloadTab(tabStore.active)
}, 3000)
})
}

View File

@@ -1,4 +1,8 @@
<script setup lang="ts">
defineOptions({
name: 'website-edit'
})
import Editor from '@guolao/vue-monaco-editor'
import { NButton } from 'naive-ui'