update
This commit is contained in:
@@ -92,7 +92,6 @@ watch(searchTerm, (newValue) => {
|
||||
<!-- 搜索控制按钮 -->
|
||||
<div class="search-controls">
|
||||
<button @click="toggleSearch" class="icon-button" :title="isSearching ? t('commandInputBar.closeSearch') : t('commandInputBar.openSearch')">
|
||||
<!-- 使用 Font Awesome 图标 -->
|
||||
<i v-if="!isSearching" class="fas fa-search"></i>
|
||||
<i v-else class="fas fa-times"></i>
|
||||
</button>
|
||||
@@ -104,11 +103,11 @@ watch(searchTerm, (newValue) => {
|
||||
<button @click="findNext" class="icon-button" :title="t('commandInputBar.findNext')">
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
</button>
|
||||
<!-- 搜索结果显示已移除 -->
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Removed hidden span -->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -178,14 +178,14 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 遍历分组内的连接 -->
|
||||
|
||||
<tr v-for="conn in groupConnections" :key="conn.id">
|
||||
<td>{{ conn.name }}</td>
|
||||
<td>{{ conn.host }}</td>
|
||||
<td>{{ conn.port }}</td>
|
||||
<td>{{ conn.username }}</td>
|
||||
<td>{{ conn.auth_method }}</td>
|
||||
<td> <!-- 显示标签 -->
|
||||
<td>
|
||||
<span v-if="getConnectionTagNames(conn).length > 0" class="tag-list">
|
||||
<span v-for="tagName in getConnectionTagNames(conn)" :key="tagName" class="tag-item">
|
||||
{{ tagName }}
|
||||
|
||||
@@ -217,7 +217,7 @@ const handlePaneResize = (eventData: { panes: Array<{ size: number; [key: string
|
||||
<!-- 如果是容器节点 -->
|
||||
<template v-if="layoutNode.type === 'container' && layoutNode.children && layoutNode.children.length > 0">
|
||||
<splitpanes
|
||||
:horizontal="layoutNode.direction === 'vertical'"
|
||||
:horizontal="layoutNode.direction === 'vertical'"
|
||||
class="default-theme"
|
||||
style="height: 100%; width: 100%;"
|
||||
@resized="handlePaneResize"
|
||||
|
||||
@@ -48,29 +48,29 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
|
||||
searchAddon.value = addon; // *** 存储 searchAddon 实例 ***
|
||||
|
||||
// *** 监听搜索结果变化 ***
|
||||
if (searchAddon.value) {
|
||||
// *** 移除错误的类型注解,让 TS 推断 ***
|
||||
searchAddon.value.onDidChangeResults((results) => {
|
||||
// *** 添加更详细的日志 ***
|
||||
console.log(`[会话 ${sessionId}][SearchAddon] onDidChangeResults 事件触发! results:`, JSON.stringify(results)); // 使用 JSON.stringify 查看完整结构
|
||||
if (results && typeof results.resultIndex === 'number' && typeof results.resultCount === 'number') {
|
||||
// 确认 results 包含预期的数字属性
|
||||
searchResultCount.value = results.resultCount;
|
||||
currentSearchResultIndex.value = results.resultIndex; // xterm 的索引是从 0 开始的
|
||||
console.log(`[会话 ${sessionId}][SearchAddon] 状态已更新: index=${currentSearchResultIndex.value}, count=${searchResultCount.value}`);
|
||||
} else {
|
||||
// 没有结果、搜索被清除或 results 结构不符合预期
|
||||
console.log(`[会话 ${sessionId}][SearchAddon] 清除搜索状态或结果无效。 results:`, JSON.stringify(results)); // 使用 JSON.stringify 查看完整结构
|
||||
searchResultCount.value = 0;
|
||||
currentSearchResultIndex.value = -1;
|
||||
// console.log(`[会话 ${sessionId}][SearchAddon] 搜索结果清除或无匹配。`); // 这行日志有点重复,可以注释掉
|
||||
}
|
||||
});
|
||||
// *** 添加确认日志 ***
|
||||
console.log(`[会话 ${sessionId}][SearchAddon] onDidChangeResults 监听器已附加。`);
|
||||
} else {
|
||||
console.warn(`[会话 ${sessionId}][SearchAddon] 无法附加 onDidChangeResults 监听器,searchAddon 实例为空。`);
|
||||
}
|
||||
// if (searchAddon.value) {
|
||||
// // *** 移除错误的类型注解,让 TS 推断 ***
|
||||
// searchAddon.value.onDidChangeResults((results) => {
|
||||
// // *** 添加更详细的日志 ***
|
||||
// console.log(`[会话 ${sessionId}][SearchAddon] onDidChangeResults 事件触发! results:`, JSON.stringify(results)); // 使用 JSON.stringify 查看完整结构
|
||||
// if (results && typeof results.resultIndex === 'number' && typeof results.resultCount === 'number') {
|
||||
// // 确认 results 包含预期的数字属性
|
||||
// searchResultCount.value = results.resultCount;
|
||||
// currentSearchResultIndex.value = results.resultIndex; // xterm 的索引是从 0 开始的
|
||||
// console.log(`[会话 ${sessionId}][SearchAddon] 状态已更新: index=${currentSearchResultIndex.value}, count=${searchResultCount.value}`);
|
||||
// } else {
|
||||
// // 没有结果、搜索被清除或 results 结构不符合预期
|
||||
// console.log(`[会话 ${sessionId}][SearchAddon] 清除搜索状态或结果无效。 results:`, JSON.stringify(results)); // 使用 JSON.stringify 查看完整结构
|
||||
// searchResultCount.value = 0;
|
||||
// currentSearchResultIndex.value = -1;
|
||||
// // console.log(`[会话 ${sessionId}][SearchAddon] 搜索结果清除或无匹配。`); // 这行日志有点重复,可以注释掉
|
||||
// }
|
||||
// });
|
||||
// // *** 添加确认日志 ***
|
||||
// console.log(`[会话 ${sessionId}][SearchAddon] onDidChangeResults 监听器已附加。`);
|
||||
// } else {
|
||||
// console.warn(`[会话 ${sessionId}][SearchAddon] 无法附加 onDidChangeResults 监听器,searchAddon 实例为空。`);
|
||||
// }
|
||||
|
||||
// --- 添加日志:检查缓冲区处理 ---
|
||||
console.log(`[会话 ${sessionId}][SSH前端] handleTerminalReady: 准备处理缓冲区,缓冲区长度: ${terminalOutputBuffer.value.length}`);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import { createI18n, Composer } from 'vue-i18n';
|
||||
|
||||
// 导入语言文件
|
||||
import enMessages from './locales/en.json';
|
||||
@@ -41,8 +41,9 @@ const i18n = createI18n<[MessageSchema], 'en' | 'zh'>({
|
||||
* @param lang 要设置的语言代码 ('en', 'zh', etc.)
|
||||
*/
|
||||
export const setLocale = (lang: 'en' | 'zh') => {
|
||||
if (i18n.global.availableLocales.includes(lang)) {
|
||||
i18n.global.locale = lang; // 直接赋值
|
||||
const globalComposer = i18n.global as unknown as Composer; // 强制类型断言
|
||||
if (globalComposer.availableLocales.includes(lang)) {
|
||||
globalComposer.locale.value = lang; // 访问 .value 属性
|
||||
try {
|
||||
localStorage.setItem(localStorageKey, lang); // 持久化到 localStorage
|
||||
console.log(`[i18n] Locale set to "${lang}" and saved to localStorage.`); // 添加日志
|
||||
|
||||
@@ -92,7 +92,8 @@
|
||||
"loggingIn": "Logging in...",
|
||||
"error": "Login failed. Please check your username and password.",
|
||||
"twoFactorPrompt": "Enter your two-factor authentication code:",
|
||||
"verifyButton": "Verify"
|
||||
"verifyButton": "Verify",
|
||||
"rememberMe": "Remember Me (7 days)"
|
||||
},
|
||||
"connections": {
|
||||
"title": "Connection Management",
|
||||
@@ -243,7 +244,9 @@
|
||||
"noPassword": "Connection config is missing password.",
|
||||
"shellError": "Failed to open shell: {message}",
|
||||
"alreadyConnected": "An active SSH connection already exists.",
|
||||
"unknown": "Unknown status"
|
||||
"unknown": "Unknown status",
|
||||
"wsClosedWillRetry": "WebSocket connection closed, will attempt reconnect {attempt} in {seconds} seconds...",
|
||||
"reconnecting": "Attempting to reconnect..."
|
||||
},
|
||||
"terminal": {
|
||||
"infoPrefix": "[INFO]",
|
||||
@@ -269,6 +272,7 @@
|
||||
"newFolder": "New Folder",
|
||||
"rename": "Rename",
|
||||
"changePermissions": "Change Permissions",
|
||||
"newFile": "New File",
|
||||
"delete": "Delete",
|
||||
"deleteMultiple": "Delete {count} items",
|
||||
"download": "Download",
|
||||
@@ -306,7 +310,8 @@
|
||||
"readFileFailed": "Failed to read file",
|
||||
"fileDecodeError": "File decoding failed (likely not UTF-8)",
|
||||
"saveFailed": "Failed to save file",
|
||||
"saveTimeout": "Save timed out"
|
||||
"saveTimeout": "Save timed out",
|
||||
"fileExists": "File \"{name}\" already exists."
|
||||
},
|
||||
"prompts": {
|
||||
"enterFolderName": "Enter the name for the new folder:",
|
||||
@@ -315,7 +320,8 @@
|
||||
"confirmDeleteFolder": "Are you sure you want to delete the directory \"{name}\" and all its contents? This cannot be undone.",
|
||||
"confirmDeleteFile": "Are you sure you want to delete the file \"{name}\"? This cannot be undone.",
|
||||
"enterNewName": "Enter the new name for \"{oldName}\":",
|
||||
"enterNewPermissions": "Enter new permissions for \"{name}\" (octal, e.g., 755):"
|
||||
"enterNewPermissions": "Enter new permissions for \"{name}\" (octal, e.g., 755):",
|
||||
"enterFileName": "Enter the name for the new file:"
|
||||
},
|
||||
"editingFile": "Editing",
|
||||
"loadingFile": "Loading file...",
|
||||
|
||||
@@ -330,6 +330,7 @@
|
||||
"saveError": "保存出错",
|
||||
"editPathTooltip": "点击路径进行编辑",
|
||||
"noActiveSession": "无活动会话",
|
||||
"loadDirectoryFailed": "加载目录失败",
|
||||
"noOpenFile": "未打开文件",
|
||||
"selectFileToEdit": "请从文件管理器中选择文件以开始编辑。",
|
||||
"searchPlaceholder": "搜索文件..."
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useTagsStore, TagInfo } from '../stores/tags.store';
|
||||
|
||||
|
||||
const { t } = useI18n();
|
||||
const tagsStore = useTagsStore();
|
||||
|
||||
const showAddTagForm = ref(false);
|
||||
const tagToEdit = ref<TagInfo | null>(null);
|
||||
|
||||
// 组件挂载时获取标签列表
|
||||
onMounted(() => {
|
||||
tagsStore.fetchTags();
|
||||
});
|
||||
|
||||
// 打开添加表单
|
||||
const openAddForm = () => {
|
||||
tagToEdit.value = null; // 确保不是编辑模式
|
||||
showAddTagForm.value = true;
|
||||
};
|
||||
|
||||
// 打开编辑表单
|
||||
const openEditForm = (tag: TagInfo) => {
|
||||
tagToEdit.value = tag;
|
||||
showAddTagForm.value = true;
|
||||
};
|
||||
|
||||
// 关闭表单
|
||||
const closeForm = () => {
|
||||
showAddTagForm.value = false;
|
||||
tagToEdit.value = null;
|
||||
};
|
||||
|
||||
// 处理标签添加/更新成功事件
|
||||
const onTagSaved = () => {
|
||||
closeForm();
|
||||
// Store 内部会自动刷新列表,这里无需额外操作
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tags-view">
|
||||
<h2>{{ t('tags.title') }}</h2>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button @click="openAddForm">{{ t('tags.addTag') }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="tagsStore.isLoading" class="loading-message">
|
||||
{{ t('tags.loading') }}
|
||||
</div>
|
||||
<div v-else-if="tagsStore.error" class="error-message">
|
||||
{{ t('tags.error', { error: tagsStore.error }) }}
|
||||
</div>
|
||||
<div v-else-if="tagsStore.tags.length === 0" class="no-data-message">
|
||||
{{ t('tags.noTags') }}
|
||||
</div>
|
||||
<TagList v-else :tags="tagsStore.tags" @edit-tag="openEditForm" />
|
||||
|
||||
<!-- 添加/编辑标签表单 (模态框) -->
|
||||
<AddTagForm
|
||||
v-if="showAddTagForm"
|
||||
:tag-to-edit="tagToEdit"
|
||||
@close="closeForm"
|
||||
@tag-saved="onTagSaved"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tags-view {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.actions-bar button {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading-message,
|
||||
.error-message,
|
||||
.no-data-message {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@@ -309,7 +309,7 @@ const handleCloseEditorTab = (tabId: string) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="workspace-view"> <!-- Root element -->
|
||||
<div class="workspace-view">
|
||||
<TerminalTabBar
|
||||
:sessions="sessionTabsWithStatus"
|
||||
:active-session-id="activeSessionId"
|
||||
@@ -327,7 +327,6 @@ const handleCloseEditorTab = (tabId: string) => {
|
||||
class="layout-renderer-wrapper"
|
||||
:editor-tabs="editorTabs"
|
||||
:active-editor-tab-id="activeEditorTabId"
|
||||
<!-- Removed terminalManager prop -->
|
||||
@send-command="handleSendCommand"
|
||||
@terminal-input="handleTerminalInput"
|
||||
@terminal-resize="handleTerminalResize"
|
||||
|
||||
Reference in New Issue
Block a user