feat(layout): 添加路由视图刷新功能

- 在 BasicLayout 中添加 viewKey 响应式变量用于控制 router-view 的重新挂载
- 监听自定义 'app:refresh-view' 事件实现页面刷新功能
- 修改 router-view 组件 key 值为 viewKey + '-' + r.fullPath 实现条件渲染

feat(tabs-nav): 优化标签页操作功能

- 添加 handleRefresh 方法通过 dispatchEvent 触发页面刷新事件
- 实现 handleCloseOthers、handleCloseRight、handleCloseAll 方法
- 优化 handleTabClick 避免重复点击当前路由
- 移除无用的激活状态小圆点和右键菜单注释代码

refactor(mock): 调整 mock 数据导入逻辑

- 将 import.meta.env.DEV 条件判断改为直接导入,简化代码结构
```
This commit is contained in:
Yuhang Wu 2026-06-02 14:33:30 +08:00
parent 1701bdb800
commit 3dd146f871
3 changed files with 36 additions and 21 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount } from 'vue'
import { computed, onMounted, onBeforeUnmount, ref, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
Expand,
@ -24,6 +24,16 @@ const userStore = useUserStore()
const collapsed = computed(() => appStore.sidebarCollapsed)
/**
* 路由视图刷新控制监听 TabsNav 派发的 'app:refresh-view' 事件
* 通过 v-if 短暂卸载再挂载 router-view达到刷新当前页面的效果
*/
const viewKey = ref(0)
function onRefreshView() {
viewKey.value++
}
function toggleSidebar() {
appStore.toggleSidebar()
}
@ -37,9 +47,11 @@ function onResize() {
onMounted(() => {
onResize()
window.addEventListener('resize', onResize)
window.addEventListener('app:refresh-view', onRefreshView)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', onResize)
window.removeEventListener('app:refresh-view', onRefreshView)
})
async function handleCommand(cmd: string) {
@ -122,7 +134,7 @@ async function handleCommand(cmd: string) {
<router-view v-slot="{ Component, route: r }">
<transition name="slide-up" mode="out-in">
<keep-alive>
<component :is="Component" :key="r.fullPath" />
<component :is="Component" :key="viewKey + '-' + r.fullPath" />
</keep-alive>
</transition>
</router-view>

View File

@ -18,6 +18,7 @@ watch(
)
function handleTabClick(path: string) {
if (path === route.path) return
tabsStore.setActive(path)
router.push(path)
}
@ -30,15 +31,26 @@ function handleClose(e: MouseEvent, path: string) {
}
}
/**
* 刷新当前页 通过自定义事件通知 BasicLayout 重新挂载 <router-view>
* 不使用 router.replace({ path: '/redirect' + ... })因为我们没有注册 /redirect/* 路由
* 会触发通配符跳到 /404
*/
function handleRefresh() {
//
router.replace({ path: '/redirect' + route.path })
window.dispatchEvent(new CustomEvent('app:refresh-view'))
}
//
function showContextMenu(e: MouseEvent, path: string) {
e.preventDefault()
// el-dropdown ref
function handleCloseOthers() {
tabsStore.closeOthers(route.path)
}
function handleCloseRight() {
tabsStore.closeRight(route.path)
}
function handleCloseAll() {
tabsStore.closeAll()
router.push('/dashboard')
}
</script>
@ -51,10 +63,8 @@ function showContextMenu(e: MouseEvent, path: string) {
:class="['tab-item', { active: tab.path === tabsStore.activeTab }]"
@click="handleTabClick(tab.path)"
>
<!-- 激活状态的小圆点 -->
<span v-if="tab.path === tabsStore.activeTab" class="tab-dot" />
<span class="tab-title">{{ tab.title }}</span>
<!-- 非首页才显示关闭按钮 -->
<el-icon
v-if="tab.path !== '/dashboard'"
class="tab-close"
@ -65,7 +75,6 @@ function showContextMenu(e: MouseEvent, path: string) {
</div>
</div>
<!-- 右侧操作按钮 -->
<div class="tabs-actions">
<el-tooltip content="刷新当前页" placement="bottom">
<el-button text size="small" :icon="RefreshRight" @click="handleRefresh" />
@ -78,15 +87,9 @@ function showContextMenu(e: MouseEvent, path: string) {
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="tabsStore.closeOthers(route.path)">
关闭其他
</el-dropdown-item>
<el-dropdown-item @click="tabsStore.closeRight(route.path)">
关闭右侧
</el-dropdown-item>
<el-dropdown-item divided @click="tabsStore.closeAll(); router.push('/dashboard')">
关闭所有
</el-dropdown-item>
<el-dropdown-item @click="handleCloseOthers">关闭其他</el-dropdown-item>
<el-dropdown-item @click="handleCloseRight">关闭右侧</el-dropdown-item>
<el-dropdown-item divided @click="handleCloseAll">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>

View File

@ -23,7 +23,7 @@ import './assets/styles/main.scss'
import './permission'
// Mock 拦截(开发阶段用本地假数据,启动后端后可注释此行)
import.meta.env.DEV && import('./mock')
import './mock'
const app = createApp(App)