Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
V
Vue-Dashboard
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
高源
Vue-Dashboard
Commits
d506034a
Commit
d506034a
authored
Mar 06, 2025
by
高源
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
优化tab关闭后组件销毁
parent
6b244022
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
200 additions
and
43 deletions
+200
-43
index.html
dist/index.html
+2
-2
index.vue
src/layouts/modules/global-tab/index.vue
+13
-3
[url].vue
src/views/_builtin/iframe-page/[url].vue
+81
-19
extJs.vue
src/views/_builtin/iframe-page/extJs.vue
+33
-13
vueComponent.vue
src/views/_builtin/iframe-page/vueComponent.vue
+40
-1
webview.vue
src/views/_builtin/iframe-page/webview.vue
+31
-5
No files found.
dist/index.html
View file @
d506034a
<!doctype html>
<html
lang=
"zh-cmn-Hans"
>
<head>
<meta
name=
"buildTime"
content=
"2025-0
2-20 16:06:48
"
>
<meta
name=
"buildTime"
content=
"2025-0
3-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>
...
...
src/layouts/modules/global-tab/index.vue
View file @
d506034a
<
script
setup
lang=
"ts"
>
import
{
nextTick
,
reactive
,
ref
,
watch
,
h
}
from
'vue'
;
import
{
h
,
nextTick
,
reactive
,
ref
,
watc
h
}
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
()
{
...
...
src/views/_builtin/iframe-page/[url].vue
View file @
d506034a
<!-- 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
=
`
${
o
rigin
}
/
${
url
}
`
;
selectTag
.
value
=
`
${
backendO
rigin
}
/
${
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
=
`
${
o
rigin
}${
handler
}
`
;
selectTag
.
value
=
`
${
backendO
rigin
}${
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
>
...
...
src/views/_builtin/iframe-page/extJs.vue
View file @
d506034a
...
...
@@ -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 的变化
...
...
src/views/_builtin/iframe-page/vueComponent.vue
View file @
d506034a
<
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
>
...
...
src/views/_builtin/iframe-page/webview.vue
View file @
d506034a
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment