Commit d506034a by 高源

优化tab关闭后组件销毁

parent 6b244022
<!doctype html>
<html lang="zh-cmn-Hans">
<head>
<meta name="buildTime" content="2025-02-20 16:06:48">
<meta name="buildTime" content="2025-03-06 10:17:29">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<title>VueDashboard</title>
<script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-BTK4dlWY.js"></script>
<script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-ClligkOZ.js"></script>
<link rel="stylesheet" crossorigin href="/Content/VueDashboardUi/VueDashboard1/assets/index-9k_B1ZU8.css">
</head>
<body>
......
<script setup lang="ts">
import { nextTick, reactive, ref, watch, h } from 'vue';
import { h, nextTick, reactive, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useElementBounding } from '@vueuse/core';
import { useElementBounding, useEventBus } from '@vueuse/core';
import { PageTab } from '@sa/materials';
import BetterScroll from '@/components/custom/better-scroll.vue';
import { useAppStore } from '@/store/modules/app';
......@@ -31,6 +31,9 @@ type TabNamedNodeMap = NamedNodeMap & {
[TAB_DATA_ID]: Attr;
};
// 创建事件总线
const tabCloseEventBus = useEventBus('tab-close');
async function scrollToActiveTab() {
await nextTick();
if (!tabRef.value) return;
......@@ -81,6 +84,13 @@ function getContextMenuDisabledKeys(tabId: string) {
}
async function handleCloseTab(tab: App.Global.Tab) {
// 发送关闭事件,传递要关闭的标签ID
tabCloseEventBus.emit(tab.id);
// 等待一小段时间,确保清理操作有时间执行
await new Promise(resolve => setTimeout(resolve, 50));
// 然后移除标签和缓存
await tabStore.removeTab(tab.id);
await routeStore.reCacheRoutesByKey(tab.routeKey);
}
......@@ -139,7 +149,7 @@ async function handleContextMenu(e: MouseEvent, tabId: string) {
function init() {
tabStore.initTabStore(route);
console.log(tabStore)
console.log(tabStore);
}
function removeFocus() {
......
<!-- eslint-disable no-console -->
<!-- eslint-disable @typescript-eslint/no-shadow -->
<script setup lang="ts">
import { nextTick, onActivated, onMounted, ref, shallowRef, watch } from 'vue';
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { useEventBus } from '@vueuse/core'; // 添加事件总线
import { loadModule } from 'vue3-sfc-loader';
import { getSelectMenu } from '@/service/api';
import NotFound from '@/views/_builtin/404/index.vue'; // 引入 404 组件
......@@ -9,6 +10,9 @@ import ExtJsComponent from './extJs.vue';
import WebviewComponent from './webview.vue';
import VueComponent from './vueComponent.vue'; // 添加新组件导入
// 添加组件选项
defineOptions({ name: 'IframePage' });
interface Props {
url: string;
kvid: string;
......@@ -21,6 +25,65 @@ const selectTag = ref('');
const extTag = ref('');
const hasError = ref(false);
// 组件引用
const webviewRef = ref<any>(null);
const extJsRef = ref<any>(null);
const vueComponentRef = ref<any>(null);
// 监听标签关闭事件
const tabCloseEventBus = useEventBus('tab-close');
// 清理所有资源的函数
const cleanupResources = () => {
console.log('清理iframe-page资源');
// 清理webview (iframe)
if (webviewRef.value && typeof webviewRef.value.cleanup === 'function') {
webviewRef.value.cleanup();
}
// 清理ExtJS组件
if (extJsRef.value && typeof extJsRef.value.cleanup === 'function') {
extJsRef.value.cleanup();
}
// 清理Vue组件
if (vueComponentRef.value && typeof vueComponentRef.value.cleanup === 'function') {
vueComponentRef.value.cleanup();
}
// 重置状态
asyncComponent.value = null;
selectTag.value = '';
extTag.value = '';
hasError.value = false;
};
// 监听标签关闭事件
tabCloseEventBus.on(() => {
cleanupResources();
});
// 组件卸载前清理
onBeforeUnmount(() => {
cleanupResources();
// 移除事件监听
tabCloseEventBus.off();
});
// 修复console[type]的类型错误
const safeConsoleLog = (type: string, ...args: any[]) => {
if (type === 'error') {
console.error(...args);
} else if (type === 'warn') {
console.warn(...args);
} else if (type === 'info') {
console.info(...args);
} else {
console.log(...args);
}
};
// 定义加载外部组件的函数
const loadExternalComponent = async (url: string) => {
const options = {
......@@ -41,7 +104,7 @@ const loadExternalComponent = async (url: string) => {
document.head.appendChild(style);
},
log(type: string, ...args: any[]) {
console[type](...args);
safeConsoleLog(type, ...args);
}
};
......@@ -53,6 +116,7 @@ const loadExternalComponent = async (url: string) => {
})
.catch(error => {
console.error('Error loading component:', error);
hasError.value = true;
});
};
......@@ -65,32 +129,27 @@ const handleMenuAccess = async () => {
console.log('Processing:', { url, kvid, type });
const origin = window.location.origin;
// 创建后端地址 - 假设后端在80端口
const backendOrigin = origin.replace(':8080', ':80');
if (type === 'System') {
console.log(url);
if (url.startsWith('App')) {
extTag.value = url;
} else if (url.endsWith('.vue')) {
// 添加.vue文件判断
// selectTag.value = `${origin}/${url}`;
// asyncComponent.value = `${origin}/${url}`; // 存储vue文件的URL
loadExternalComponent(`${origin}/${url}`);
// 使用后端地址加载Vue文件
loadExternalComponent(`${backendOrigin}/${url}`);
} else {
selectTag.value = `${origin}/${url}`;
selectTag.value = `${backendOrigin}/${url}`;
}
console.log('url1:', url);
console.log('kvid1:', kvid);
console.log('type1:', type);
} else {
console.log('url2:', url);
console.log('kvid2:', kvid);
console.log('type2:', type);
try {
const { data: selectMenu } = await getSelectMenu(
`/Restful/Kivii.Basic.Entities.Function/Access.json?MenuKvids=${kvid}`
);
if (selectMenu?.Results?.length > 0) {
// 修复类型错误
if (selectMenu?.Results && selectMenu.Results.length > 0) {
const handler = selectMenu.Results[0].Handler;
// 确保在设置 extTag 之前 DOM 已准备好
if (!handler.startsWith('/') && !handler.endsWith('.vue')) {
......@@ -100,11 +159,10 @@ const handleMenuAccess = async () => {
// 先判断是否以/开头
if (handler.endsWith('.vue')) {
// 如果以.vue结尾
// asyncComponent.value = `${origin}${handler}`;
loadExternalComponent(`${origin}${handler}`);
loadExternalComponent(`${backendOrigin}${handler}`);
} else {
// 如果不以.vue结尾
selectTag.value = `${origin}${handler}`;
selectTag.value = `${backendOrigin}${handler}`;
}
} else {
extTag.value = handler;
......@@ -123,6 +181,9 @@ const handleMenuAccess = async () => {
watch(
[() => type, () => kvid, () => url],
() => {
// 先清理旧资源
cleanupResources();
// 然后加载新内容
handleMenuAccess();
},
{ immediate: true }
......@@ -147,11 +208,12 @@ onMounted(() => {
<WebviewComponent
v-if="selectTag && !hasError && !asyncComponent"
id="iframePage"
ref="webviewRef"
class="size-full"
:url="selectTag"
></WebviewComponent>
<ExtJsComponent v-else-if="extTag && !hasError" :key="extTag" :url="extTag"></ExtJsComponent>
<VueComponent v-else-if="asyncComponent && !hasError" :url="asyncComponent"></VueComponent>
<ExtJsComponent v-else-if="extTag && !hasError" ref="extJsRef" :key="extTag" :url="extTag"></ExtJsComponent>
<VueComponent v-else-if="asyncComponent && !hasError" ref="vueComponentRef" :url="asyncComponent"></VueComponent>
<NotFound v-else />
</div>
</template>
......
......@@ -2,6 +2,8 @@
<script setup lang="ts">
import { nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
defineOptions({ name: 'ExtJsComponent' });
// 接收一个名为 url 的 prop
const props = defineProps<{
url: string;
......@@ -11,6 +13,7 @@ let extComponent: any = null;
// 控制组件显示状态的变量
const isActive = ref(true);
const extjsContainerId = `extjs-${Math.random().toString(36).substr(2, 9)}`;
function initializeExtComponent() {
extComponent = Ext.create('Ext.panel.Panel', {
renderTo: extjsContainerId,
......@@ -25,9 +28,6 @@ function initializeExtComponent() {
function updateExtComponent(newUrl: string) {
if (extComponent) {
// 更新 ExtJS 组件的内容,而不销毁整个组件
// 这里假设您可以通过某种方式更新组件的内容
// 例如,替换面板中的子组件
// 获取当前的子组件
const oldItem = extComponent.items.getAt(0);
......@@ -42,6 +42,34 @@ function updateExtComponent(newUrl: string) {
}
}
// 清理函数 - 完全销毁ExtJS组件
function cleanup() {
console.log('清理ExtJS资源');
if (extComponent) {
try {
// 销毁ExtJS组件
extComponent.destroy();
// 清空容器
const container = document.getElementById(extjsContainerId);
if (container) {
container.innerHTML = '';
}
} catch (e) {
console.error('清理ExtJS组件时出错:', e);
}
// 清空引用
extComponent = null;
}
}
// 暴露清理方法给父组件
defineExpose({
cleanup
});
onMounted(() => {
nextTick(() => {
if (!extComponent) {
......@@ -54,24 +82,16 @@ onMounted(() => {
});
onActivated(() => {
// if (extComponent) {
// extComponent.show();
isActive.value = true;
// }
});
onDeactivated(() => {
// if (extComponent) {
// extComponent.hide();
isActive.value = false;
// }
});
onBeforeUnmount(() => {
if (extComponent) {
extComponent.destroy();
extComponent = null;
}
// 组件卸载前清理
cleanup();
});
// 监听 props.url 的变化
......
<script setup lang="ts">
import { computed, getCurrentInstance, onActivated, onDeactivated, onMounted, ref, shallowRef } from 'vue';
import {
computed,
getCurrentInstance,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted,
ref,
shallowRef
} from 'vue';
import { NConfigProvider, darkTheme } from 'naive-ui';
import * as naive from 'naive-ui';
import axios from 'axios';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({ name: 'VueComponent' });
const props = defineProps<{
url: string;
}>();
......@@ -36,6 +47,29 @@ declare global {
// 全局注入axios
(window as any).$axios = axios;
// 清理函数 - 完全销毁Vue组件
function cleanup() {
console.log('清理Vue组件资源');
try {
// 清空组件引用
asyncComponent.value = null;
// 清空容器
const container = document.getElementById(containerId);
if (container) {
container.innerHTML = '';
}
} catch (e) {
console.error('清理Vue组件时出错:', e);
}
}
// 暴露清理方法给父组件
defineExpose({
cleanup
});
onMounted(async () => {
try {
asyncComponent.value = props.url;
......@@ -55,6 +89,11 @@ onActivated(() => {
onDeactivated(() => {
isActive.value = false;
});
onBeforeUnmount(() => {
// 组件卸载前清理
cleanup();
});
</script>
<template>
......
......@@ -2,6 +2,8 @@
<script setup lang="ts">
import { nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
defineOptions({ name: 'WebviewComponent' });
// 接收一个名为 url 的 prop
const props = defineProps<{
url: string;
......@@ -36,6 +38,34 @@ function updateExtComponent(newUrl: string) {
}
}
// 清理函数 - 完全销毁iframe
function cleanup() {
console.log('清理webview资源');
if (iframeEl) {
try {
// 先将src设为空白页,停止所有活动
iframeEl.src = 'about:blank';
// 从DOM中移除
const container = document.getElementById(extjsContainerId);
if (container && container.contains(iframeEl)) {
container.removeChild(iframeEl);
}
} catch (e) {
console.error('清理iframe时出错:', e);
}
// 清空引用
iframeEl = null;
}
}
// 暴露清理方法给父组件
defineExpose({
cleanup
});
onMounted(() => {
nextTick(() => {
// 初次挂载时创建 iframe
......@@ -57,11 +87,7 @@ onDeactivated(() => {
onBeforeUnmount(() => {
// 组件卸载前清理
const container = document.getElementById(extjsContainerId);
if (container && iframeEl) {
container.removeChild(iframeEl);
iframeEl = null;
}
cleanup();
});
// 监听 props.url 的变化,当地址变化时更新 iframe 的 src
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment