update
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ConnectionInfo } from '../stores/connections.store';
|
import type { ConnectionInfo } from '../stores/connections.store';
|
||||||
import { computed, defineAsyncComponent, type PropType, type Component, ref, watch, onMounted } from 'vue';
|
import { computed, defineAsyncComponent, type PropType, type Component, ref, watch, onMounted, nextTick, type CSSProperties } from 'vue'; // Added nextTick and CSSProperties
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import '@fortawesome/fontawesome-free/css/all.min.css';
|
import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
import { Splitpanes, Pane } from 'splitpanes';
|
import { Splitpanes, Pane } from 'splitpanes';
|
||||||
import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout.store';
|
import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout.store';
|
||||||
import { useSessionStore } from '../stores/session.store';
|
import { useSessionStore } from '../stores/session.store';
|
||||||
import { useFileEditorStore } from '../stores/fileEditor.store';
|
import { useFileEditorStore } from '../stores/fileEditor.store';
|
||||||
import { useSettingsStore } from '../stores/settings.store';
|
import { useSettingsStore } from '../stores/settings.store';
|
||||||
import { useSidebarResize } from '../composables/useSidebarResize';
|
import { useAppearanceStore } from '../stores/appearance.store'; // +++ Import appearance store +++
|
||||||
|
import { useSidebarResize } from '../composables/useSidebarResize';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +55,16 @@ const sessionStore = useSessionStore();
|
|||||||
const fileEditorStore = useFileEditorStore(); // <-- Initialize FileEditorStore
|
const fileEditorStore = useFileEditorStore(); // <-- Initialize FileEditorStore
|
||||||
const settingsStore = useSettingsStore(); // +++ Initialize SettingsStore +++
|
const settingsStore = useSettingsStore(); // +++ Initialize SettingsStore +++
|
||||||
const { t } = useI18n(); // <-- Get translation function
|
const { t } = useI18n(); // <-- Get translation function
|
||||||
|
|
||||||
|
// +++ Appearance Store Refs +++
|
||||||
|
const appearanceStore = useAppearanceStore();
|
||||||
|
const {
|
||||||
|
terminalBackgroundImage,
|
||||||
|
isTerminalBackgroundEnabled,
|
||||||
|
currentTerminalBackgroundOverlayOpacity,
|
||||||
|
terminalCustomHTML,
|
||||||
|
} = storeToRefs(appearanceStore);
|
||||||
|
|
||||||
const { activeSession } = storeToRefs(sessionStore);
|
const { activeSession } = storeToRefs(sessionStore);
|
||||||
const { workspaceSidebarPersistentBoolean, getSidebarPaneWidth } = storeToRefs(settingsStore);
|
const { workspaceSidebarPersistentBoolean, getSidebarPaneWidth } = storeToRefs(settingsStore);
|
||||||
const { sidebarPanes } = storeToRefs(layoutStore);
|
const { sidebarPanes } = storeToRefs(layoutStore);
|
||||||
@@ -66,6 +77,7 @@ const leftSidebarPanelRef = ref<HTMLElement | null>(null); // +++ Ref for left p
|
|||||||
const rightSidebarPanelRef = ref<HTMLElement | null>(null); // +++ Ref for right panel +++
|
const rightSidebarPanelRef = ref<HTMLElement | null>(null); // +++ Ref for right panel +++
|
||||||
const leftResizeHandleRef = ref<HTMLElement | null>(null); // +++ Ref for left handle +++
|
const leftResizeHandleRef = ref<HTMLElement | null>(null); // +++ Ref for left handle +++
|
||||||
const rightResizeHandleRef = ref<HTMLElement | null>(null); // +++ Ref for right handle +++
|
const rightResizeHandleRef = ref<HTMLElement | null>(null); // +++ Ref for right handle +++
|
||||||
|
const customHtmlLayerRef = ref<HTMLElement | null>(null); // +++ Ref for custom HTML layer +++
|
||||||
|
|
||||||
// --- Component Mapping ---
|
// --- Component Mapping ---
|
||||||
// 使用 defineAsyncComponent 优化加载,并映射 PaneName 到实际组件
|
// 使用 defineAsyncComponent 优化加载,并映射 PaneName 到实际组件
|
||||||
@@ -385,6 +397,74 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// +++ Background Image Style +++
|
||||||
|
const terminalBackgroundImageStyle = computed((): CSSProperties => {
|
||||||
|
if (isTerminalBackgroundEnabled.value && terminalBackgroundImage.value && props.layoutNode.component === 'terminal') {
|
||||||
|
const backendUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||||
|
const imagePath = terminalBackgroundImage.value;
|
||||||
|
const fullImageUrl = `${backendUrl}${imagePath}`;
|
||||||
|
return {
|
||||||
|
backgroundImage: `url(${fullImageUrl})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: 0, // Base layer for background
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
backgroundImage: 'none',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// +++ Function to execute scripts (migrated from Terminal.vue) +++
|
||||||
|
const executeScriptsInElement = (container: HTMLElement) => {
|
||||||
|
if (!container) return;
|
||||||
|
const scripts = Array.from(container.getElementsByTagName('script'));
|
||||||
|
scripts.forEach((oldScript) => {
|
||||||
|
const newScript = document.createElement('script');
|
||||||
|
Array.from(oldScript.attributes).forEach(attr => {
|
||||||
|
newScript.setAttribute(attr.name, attr.value);
|
||||||
|
});
|
||||||
|
if (oldScript.textContent) {
|
||||||
|
newScript.textContent = oldScript.textContent;
|
||||||
|
}
|
||||||
|
if (oldScript.parentNode) {
|
||||||
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||||
|
} else {
|
||||||
|
container.appendChild(newScript); // Fallback, though less likely if script was in container
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// +++ Watch for changes in terminalCustomHTML and execute scripts (migrated) +++
|
||||||
|
watch(terminalCustomHTML, (newHtmlContent, oldHtmlContent) => {
|
||||||
|
if (props.layoutNode.component !== 'terminal') return; // Only for terminal panes
|
||||||
|
|
||||||
|
if (newHtmlContent === oldHtmlContent && oldHtmlContent !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
const container = customHtmlLayerRef.value;
|
||||||
|
if (container) {
|
||||||
|
if (newHtmlContent) {
|
||||||
|
executeScriptsInElement(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -438,24 +518,60 @@ onMounted(() => {
|
|||||||
<template v-else-if="layoutNode.type === 'pane'">
|
<template v-else-if="layoutNode.type === 'pane'">
|
||||||
<!-- Terminal Pane: Render ALL SSH sessions, show only the active one -->
|
<!-- Terminal Pane: Render ALL SSH sessions, show only the active one -->
|
||||||
<template v-if="layoutNode.component === 'terminal'">
|
<template v-if="layoutNode.component === 'terminal'">
|
||||||
<div class="terminal-pane-container relative flex-grow overflow-hidden bg-background"> <!-- Add bg-background -->
|
<div
|
||||||
|
class="terminal-pane-container relative flex-grow overflow-hidden"
|
||||||
|
:class="{ 'has-global-terminal-background': isTerminalBackgroundEnabled, 'bg-background': !isTerminalBackgroundEnabled }"
|
||||||
|
>
|
||||||
|
<!-- Shared Background Layers -->
|
||||||
|
<div
|
||||||
|
v-if="isTerminalBackgroundEnabled"
|
||||||
|
class="shared-terminal-background-layers"
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0;"
|
||||||
|
>
|
||||||
|
<!-- Background Image -->
|
||||||
|
<div
|
||||||
|
class="terminal-background-image-layer"
|
||||||
|
:style="terminalBackgroundImageStyle"
|
||||||
|
></div>
|
||||||
|
<!-- Color Overlay -->
|
||||||
|
<div
|
||||||
|
class="terminal-background-overlay-layer"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute', top: '0', left: '0', width: '100%', height: '100%',
|
||||||
|
backgroundColor: `rgba(0, 0, 0, ${currentTerminalBackgroundOverlayOpacity})`,
|
||||||
|
zIndex: 1, pointerEvents: 'none'
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
<!-- Custom HTML -->
|
||||||
|
<div
|
||||||
|
ref="customHtmlLayerRef"
|
||||||
|
v-if="terminalCustomHTML"
|
||||||
|
class="terminal-custom-html-layer"
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2;"
|
||||||
|
v-html="terminalCustomHTML"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Terminal Instances -->
|
||||||
<template v-for="[sessionId, sessionState] in sessionStore.sessions" :key="sessionId">
|
<template v-for="[sessionId, sessionState] in sessionStore.sessions" :key="sessionId">
|
||||||
<!-- Only render terminals if terminalManager exists (indicates SSH) -->
|
|
||||||
<template v-if="sessionState.terminalManager">
|
<template v-if="sessionState.terminalManager">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component
|
<component
|
||||||
:is="componentMap.terminal"
|
:is="componentMap.terminal"
|
||||||
v-show="sessionId === activeSessionId"
|
v-show="sessionId === activeSessionId"
|
||||||
:session-id="sessionId"
|
:session-id="sessionId"
|
||||||
:is-active="sessionId === activeSessionId"
|
:is-active="sessionId === activeSessionId"
|
||||||
class="absolute inset-0 w-full h-full"
|
:class="['terminal-instance-wrapper absolute inset-0 w-full h-full', { 'terminal-transparent': isTerminalBackgroundEnabled }]"
|
||||||
|
:style="{ zIndex: 3 }"
|
||||||
:options="{}"
|
:options="{}"
|
||||||
/>
|
/>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<!-- Placeholder if no session is active or no SSH sessions exist -->
|
<!-- Placeholder -->
|
||||||
<div v-if="!activeSessionId || !hasSshSessions" class="absolute inset-0 flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4"> <!-- Use absolute positioning for placeholder too -->
|
<div v-if="!activeSessionId || !hasSshSessions"
|
||||||
|
class="absolute inset-0 flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4"
|
||||||
|
:style="{ zIndex: 4 }">
|
||||||
<div class="flex flex-col items-center justify-center p-8 w-full h-full">
|
<div class="flex flex-col items-center justify-center p-8 w-full h-full">
|
||||||
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
|
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
|
||||||
<span class="text-lg font-medium text-text-secondary mb-2">{{ activeSessionId ? t('layout.noSshSessionActive.title', '无活动的 SSH 会话') : t('layout.noActiveSession.title') }}</span>
|
<span class="text-lg font-medium text-text-secondary mb-2">{{ activeSessionId ? t('layout.noSshSessionActive.title', '无活动的 SSH 会话') : t('layout.noActiveSession.title') }}</span>
|
||||||
@@ -672,5 +788,15 @@ onMounted(() => {
|
|||||||
background-color: var(--border-color) !important; /* Override hover effect */
|
background-color: var(--border-color) !important; /* Override hover effect */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.terminal-pane-container.has-global-terminal-background .terminal-outer-wrapper.terminal-transparent {
|
||||||
|
background-color: transparent !important; /* 使 Terminal.vue 的最外层容器背景透明 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-pane-container.has-global-terminal-background .terminal-outer-wrapper.terminal-transparent .terminal-inner-container .xterm-viewport,
|
||||||
|
.terminal-pane-container.has-global-terminal-background .terminal-outer-wrapper.terminal-transparent .terminal-inner-container .xterm-screen {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const terminalOuterWrapperRef = ref<HTMLElement | null>(null); // 最外层容
|
|||||||
let terminal: Terminal | null = null;
|
let terminal: Terminal | null = null;
|
||||||
let fitAddon: FitAddon | null = null;
|
let fitAddon: FitAddon | null = null;
|
||||||
let searchAddon: SearchAddon | null = null; // *** 添加 searchAddon 变量 ***
|
let searchAddon: SearchAddon | null = null; // *** 添加 searchAddon 变量 ***
|
||||||
const customHtmlLayerRef = ref<HTMLElement | null>(null); // Ref for the custom HTML layer
|
|
||||||
let resizeObserver: ResizeObserver | null = null;
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
let observedElement: HTMLElement | null = null; // +++ Store the observed element +++
|
let observedElement: HTMLElement | null = null; // +++ Store the observed element +++
|
||||||
let debounceTimer: number | null = null; // 用于防抖的计时器 ID
|
let debounceTimer: number | null = null; // 用于防抖的计时器 ID
|
||||||
@@ -49,11 +48,7 @@ const appearanceStore = useAppearanceStore();
|
|||||||
const {
|
const {
|
||||||
effectiveTerminalTheme,
|
effectiveTerminalTheme,
|
||||||
currentTerminalFontFamily,
|
currentTerminalFontFamily,
|
||||||
terminalBackgroundImage,
|
|
||||||
currentTerminalFontSize,
|
currentTerminalFontSize,
|
||||||
isTerminalBackgroundEnabled,
|
|
||||||
currentTerminalBackgroundOverlayOpacity, // 获取蒙版透明度
|
|
||||||
terminalCustomHTML, // 用于自定义终端背景 HTML
|
|
||||||
// --- 文字描边和阴影状态 ---
|
// --- 文字描边和阴影状态 ---
|
||||||
terminalTextStrokeEnabled,
|
terminalTextStrokeEnabled,
|
||||||
terminalTextStrokeWidth,
|
terminalTextStrokeWidth,
|
||||||
@@ -122,17 +117,6 @@ const fitAndEmitResizeNow = (term: Terminal) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- 稳定 customHtmlLayerRef 的尺寸 (确保在 DOM 更新后) ---
|
|
||||||
nextTick(() => {
|
|
||||||
if (customHtmlLayerRef.value && terminalRef.value) {
|
|
||||||
const newWidth = terminalRef.value.offsetWidth;
|
|
||||||
const newHeight = terminalRef.value.offsetHeight;
|
|
||||||
customHtmlLayerRef.value.style.width = `${newWidth}px`;
|
|
||||||
customHtmlLayerRef.value.style.height = `${newHeight}px`;
|
|
||||||
console.log(`[Terminal ${props.sessionId}] customHtmlLayerRef stabilized (in nextTick) after immediate fit to: ${newWidth}x${newHeight}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 使用 nextTick 确保 fit() 的效果已反映,再触发 resize
|
// 使用 nextTick 确保 fit() 的效果已反映,再触发 resize
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 再次检查终端实例是否仍然存在
|
// 再次检查终端实例是否仍然存在
|
||||||
@@ -332,16 +316,6 @@ onMounted(() => {
|
|||||||
fitAddon?.fit();
|
fitAddon?.fit();
|
||||||
// console.log(`[TerminalResizeObserver sessionId=${props.sessionId}] After fitAddon.fit(). New xterm_cols: ${terminal.cols}, xterm_rows: ${terminal.rows}`);
|
// console.log(`[TerminalResizeObserver sessionId=${props.sessionId}] After fitAddon.fit(). New xterm_cols: ${terminal.cols}, xterm_rows: ${terminal.rows}`);
|
||||||
|
|
||||||
// --- 稳定 customHtmlLayerRef 的尺寸 (确保在DOM更新后) ---
|
|
||||||
nextTick(() => {
|
|
||||||
if (customHtmlLayerRef.value) {
|
|
||||||
customHtmlLayerRef.value.style.width = `${roundedWidth}px`;
|
|
||||||
customHtmlLayerRef.value.style.height = `${roundedHeight}px`;
|
|
||||||
console.log(`[TerminalResizeObserver sessionId=${props.sessionId}] customHtmlLayerRef stabilized (in nextTick) after ResizeObserver fit to: ${roundedWidth}x${roundedHeight}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// --- 稳定 customHtmlLayerRef 尺寸结束 ---
|
|
||||||
|
|
||||||
debouncedEmitResize(terminal); // This will log the cols/rows after debouncing
|
debouncedEmitResize(terminal); // This will log the cols/rows after debouncing
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(`[TerminalResizeObserver sessionId=${props.sessionId}] Fit addon or debouncedEmitResize failed:`, e);
|
console.warn(`[TerminalResizeObserver sessionId=${props.sessionId}] Fit addon or debouncedEmitResize failed:`, e);
|
||||||
@@ -500,14 +474,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听背景图片和启用状态的变化
|
|
||||||
watch([terminalBackgroundImage, isTerminalBackgroundEnabled], () => {
|
|
||||||
console.log(`[Terminal Watcher] Background image or enabled status changed. Image: ${terminalBackgroundImage.value}, Enabled: ${isTerminalBackgroundEnabled.value}`);
|
|
||||||
applyTerminalBackground();
|
|
||||||
}, { immediate: true }); // 强制立即执行一次
|
|
||||||
// 移除 onMounted 中的 applyTerminalBackground 调用,完全依赖 watch
|
|
||||||
// applyTerminalBackground(); // 初始应用一次
|
|
||||||
|
|
||||||
// 聚焦终端 (添加 null check)
|
// 聚焦终端 (添加 null check)
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
terminal.focus();
|
terminal.focus();
|
||||||
@@ -747,120 +713,10 @@ watchEffect(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 应用终端背景 ---
|
|
||||||
const applyTerminalBackground = () => {
|
|
||||||
// 背景应用到 terminalOuterWrapperRef
|
|
||||||
if (terminalOuterWrapperRef.value) {
|
|
||||||
if (isTerminalBackgroundEnabled.value) {
|
|
||||||
// 只要启用了背景功能,就应该让 xterm 透明以显示下方内容
|
|
||||||
nextTick(() => {
|
|
||||||
if (terminalOuterWrapperRef.value) {
|
|
||||||
terminalOuterWrapperRef.value.classList.add('has-terminal-background');
|
|
||||||
if (terminalBackgroundImage.value) {
|
|
||||||
const backendUrl = import.meta.env.VITE_API_BASE_URL || '';
|
|
||||||
const imagePath = terminalBackgroundImage.value;
|
|
||||||
const fullImageUrl = `${backendUrl}${imagePath}`;
|
|
||||||
terminalOuterWrapperRef.value.style.backgroundImage = `url(${fullImageUrl})`;
|
|
||||||
terminalOuterWrapperRef.value.style.backgroundSize = 'cover';
|
|
||||||
terminalOuterWrapperRef.value.style.backgroundPosition = 'center';
|
|
||||||
terminalOuterWrapperRef.value.style.backgroundRepeat = 'no-repeat';
|
|
||||||
console.log(`[Terminal ${props.sessionId}] 应用终端背景图片: ${terminalBackgroundImage.value}`);
|
|
||||||
} else {
|
|
||||||
terminalOuterWrapperRef.value.style.backgroundImage = 'none';
|
|
||||||
console.log(`[Terminal ${props.sessionId}] 终端背景功能已启用,但无背景图片,xterm 应透明。`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 背景功能禁用
|
|
||||||
nextTick(() => {
|
|
||||||
if (terminalOuterWrapperRef.value) {
|
|
||||||
terminalOuterWrapperRef.value.style.backgroundImage = 'none';
|
|
||||||
terminalOuterWrapperRef.value.classList.remove('has-terminal-background');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(`[Terminal ${props.sessionId}] 终端背景已禁用,移除背景。`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to execute scripts within an element
|
|
||||||
const executeScriptsInElement = (container: HTMLElement) => {
|
|
||||||
if (!container) return;
|
|
||||||
console.log('[Terminal] Attempting to execute scripts in custom HTML container:', container);
|
|
||||||
|
|
||||||
const scripts = Array.from(container.getElementsByTagName('script'));
|
|
||||||
console.log(`[Terminal] Found ${scripts.length} script(s) in custom HTML.`);
|
|
||||||
|
|
||||||
scripts.forEach((oldScript, index) => {
|
|
||||||
console.log(`[Terminal] Processing script #${index + 1}:`, oldScript.outerHTML.substring(0, 100) + '...');
|
|
||||||
const newScript = document.createElement('script');
|
|
||||||
|
|
||||||
// Copy attributes (type, src, async, defer, etc.)
|
|
||||||
Array.from(oldScript.attributes).forEach(attr => {
|
|
||||||
newScript.setAttribute(attr.name, attr.value);
|
|
||||||
console.log(`[Terminal] Script #${index + 1}: Copied attribute ${attr.name}="${attr.value}"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copy content for inline scripts
|
|
||||||
if (oldScript.textContent) {
|
|
||||||
newScript.textContent = oldScript.textContent;
|
|
||||||
console.log(`[Terminal] Script #${index + 1}: Copied inline content.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldScript.parentNode) {
|
|
||||||
oldScript.parentNode.insertBefore(newScript, oldScript.nextSibling); // Insert new after old
|
|
||||||
oldScript.parentNode.removeChild(oldScript); // Then remove old
|
|
||||||
console.log('[Terminal] Script #${index + 1} re-inserted and old one removed.');
|
|
||||||
} else {
|
|
||||||
container.appendChild(newScript);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch for changes in terminalCustomHTML and execute scripts
|
|
||||||
watch(terminalCustomHTML, (newHtmlContent, oldHtmlContent) => {
|
|
||||||
console.log(`[TerminalCustomHTML Watch sessionId=${props.sessionId}] Triggered. New HTML defined: ${!!newHtmlContent}, Old HTML defined: ${!!oldHtmlContent}. New === Old: ${newHtmlContent === oldHtmlContent}`);
|
|
||||||
// 仅当实际 HTML 字符串内容发生变化时才继续。
|
|
||||||
if (newHtmlContent === oldHtmlContent && oldHtmlContent !== undefined) { // Ensure initial undefined oldHtmlContent still proceeds if newHtmlContent exists
|
|
||||||
console.log(`[TerminalCustomHTML Watch sessionId=${props.sessionId}] HTML content is same as old and old was defined. Skipping script execution.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 始终在 nextTick 中操作,以确保 v-html 已更新 DOM
|
|
||||||
nextTick(() => {
|
|
||||||
const container = customHtmlLayerRef.value;
|
|
||||||
if (container) {
|
|
||||||
if (newHtmlContent) {
|
|
||||||
// console.log('[Terminal] terminalCustomHTML 内容已更改,正在为脚本处理新的 HTML 内容。'); // 保留原始的简单日志(注释掉)
|
|
||||||
executeScriptsInElement(container);
|
|
||||||
} else {
|
|
||||||
// console.log('[Terminal] terminalCustomHTML 内容已清除,脚本将不被处理。'); // 保留原始的简单日志(注释掉)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, { immediate: true });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="terminalOuterWrapperRef" class="terminal-outer-wrapper">
|
<div ref="terminalOuterWrapperRef" class="terminal-outer-wrapper">
|
||||||
<!-- 蒙版层 -->
|
|
||||||
<div
|
|
||||||
v-if="isTerminalBackgroundEnabled"
|
|
||||||
class="terminal-background-overlay"
|
|
||||||
:style="{ backgroundColor: `rgba(0, 0, 0, ${currentTerminalBackgroundOverlayOpacity})` }"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
ref="customHtmlLayerRef"
|
|
||||||
v-if="isTerminalBackgroundEnabled && terminalCustomHTML"
|
|
||||||
class="terminal-custom-html-layer"
|
|
||||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none;"
|
|
||||||
v-html="terminalCustomHTML"
|
|
||||||
></div>
|
|
||||||
<!-- xterm 实际挂载点 -->
|
<!-- xterm 实际挂载点 -->
|
||||||
<div ref="terminalRef" class="terminal-inner-container"></div>
|
<div ref="terminalRef" class="terminal-inner-container"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -874,21 +730,11 @@ watch(terminalCustomHTML, (newHtmlContent, oldHtmlContent) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-background-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none; /* 允许鼠标事件穿透 */
|
|
||||||
z-index: 1; /* 在背景图之上 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-inner-container {
|
.terminal-inner-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative; /* 使 z-index 生效 */
|
/* position: relative; 移除了 position relative */
|
||||||
z-index: 2; /* 在蒙版之上 */
|
/* z-index 调整或移除,因为背景层不再在此组件内 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 文字描边和阴影样式 */
|
/* 文字描边和阴影样式 */
|
||||||
@@ -910,10 +756,9 @@ watch(terminalCustomHTML, (newHtmlContent, oldHtmlContent) => {
|
|||||||
text-shadow: var(--terminal-shadow);
|
text-shadow: var(--terminal-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当最外层容器有背景图时,强制内部 xterm 视口和屏幕背景透明 */
|
/*
|
||||||
.terminal-outer-wrapper.has-terminal-background .terminal-inner-container :deep(.xterm-viewport),
|
移除以下样式,因为它依赖于本组件内部管理的 .has-terminal-background 类,
|
||||||
.terminal-outer-wrapper.has-terminal-background .terminal-inner-container :deep(.xterm-screen) {
|
该逻辑已移至 LayoutRenderer.vue
|
||||||
background-color: transparent !important;
|
*/
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user