fix: Optimize UI and fix bugs

This commit is contained in:
dalaolala committed 2026-03-05 11:26:09 +08:00
1 parent 1171adb41b
commit 2e33924342
3 files changed
+250 -79

No files matched your search

+133 -34
View File
@@ -53,6 +53,15 @@
</el-button>
</div>
<div class="server-search">
<el-input
v-model="searchQuery"
placeholder="搜索服务器..."
clearable
:prefix-icon="Search"
/>
</div>
<div class="server-list">
<el-tree
:data="serverTree"
@@ -60,6 +69,7 @@
:default-expanded-keys="expandedKeys"
:expand-on-click-node="false"
@node-click="handleServerClick"
@node-dblclick="handleServerDblClick"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
>
@@ -178,7 +188,7 @@
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Folder, Monitor, Connection, Lock, More, Edit, Delete, User, SwitchButton, UserFilled } from '@element-plus/icons-vue'
import { Plus, Folder, Monitor, Connection, Lock, More, Edit, Delete, User, SwitchButton, UserFilled, Search } from '@element-plus/icons-vue'
import { useServersStore } from '@/stores/servers'
import { useAuthStore } from '@/stores/auth'
import ServerForm from '@/components/ServerForm.vue'
@@ -191,6 +201,7 @@ const showAddServerDialog = ref(false)
const showEditServerDialog = ref(false)
const editingServerId = ref(null)
const editingServerName = ref('')
const searchQuery = ref('')
// 分组展开状态持久化 (sessionStorage)
const EXPANDED_KEYS_STORAGE_KEY = 'webssh_dashboard_expanded_keys'
@@ -216,8 +227,18 @@ const handleNodeCollapse = (data) => {
const serverTree = computed(() => {
const groups = {}
let filteredServers = serversStore.servers
if (searchQuery.value) {
const q = searchQuery.value.toLowerCase()
filteredServers = filteredServers.filter(server => {
return server.name?.toLowerCase().includes(q) ||
server.host?.toLowerCase().includes(q) ||
server.group_name?.toLowerCase().includes(q)
})
}
// 按分组组织服务器
serversStore.servers.forEach(server => {
filteredServers.forEach(server => {
const groupName = server.group_name || 'Default'
if (!groups[groupName]) {
groups[groupName] = []
@@ -243,9 +264,18 @@ const serverTree = computed(() => {
})
// 处理服务器点击
const handleServerClick = (data) => {
const handleServerClick = (data, node) => {
if (data.type === 'server') {
router.push(`/terminal/${data.serverId}`)
} else if (data.type === 'group' && node) {
node.expanded = !node.expanded
}
}
// 处理服务器双击
const handleServerDblClick = (data, node) => {
if (data.type === 'group' && node) {
node.expanded = !node.expanded
}
}
@@ -393,9 +423,11 @@ onMounted(async () => {
}
.sidebar {
background: white;
border-right: 1px solid #e4e7ed;
padding: 20px;
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-right: 1px solid rgba(0, 0, 0, 0.1);
padding: 24px 20px;
display: flex;
flex-direction: column;
}
@@ -404,13 +436,39 @@ onMounted(async () => {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
margin-bottom: 24px;
}
.sidebar-header h2 {
margin: 0;
color: #333;
color: #1c1c1e;
font-size: 18px;
font-weight: 600;
letter-spacing: -0.5px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.server-search {
margin-bottom: 20px;
}
.server-search :deep(.el-input__wrapper) {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 8px;
box-shadow: none !important;
border: 1px solid rgba(0, 0, 0, 0.08);
transition: all 0.2s;
}
.server-search :deep(.el-input__wrapper.is-focus) {
background-color: rgba(255, 255, 255, 1);
border-color: #007AFF;
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2) !important;
}
.server-search :deep(.el-input__inner) {
color: #1c1c1e;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.server-list {
@@ -420,11 +478,48 @@ onMounted(async () => {
}
/* 树形组件样式调整 */
:deep(.el-tree) {
background: transparent;
}
:deep(.el-tree-node__content) {
height: 56px !important;
padding: 0 16px !important;
margin-bottom: 0 !important;
transition: all 0.3s ease !important;
height: auto !important;
padding: 4px 8px !important;
margin-bottom: 2px !important;
border-radius: 8px;
transition: all 0.2s ease !important;
}
:deep(.el-tree-node__content:hover) {
background-color: rgba(0, 0, 0, 0.06) !important;
}
:deep(.el-tree-node.is-current > .el-tree-node__content:has(.server-node)) {
background-color: #007AFF !important;
color: white;
}
:deep(.el-tree-node.is-current > .el-tree-node__content:has(.group-node)) {
background-color: transparent !important;
}
:deep(.el-tree-node.is-current > .el-tree-node__content .server-node .server-name),
:deep(.el-tree-node.is-current > .el-tree-node__content .server-node .server-address) {
color: white !important;
}
:deep(.el-tree-node.is-current > .el-tree-node__content .server-node .server-name:hover) {
color: white !important;
}
:deep(.el-tree-node.is-current > .el-tree-node__content .server-actions) {
background-color: transparent;
border-color: rgba(255, 255, 255, 0.4);
color: white;
}
:deep(.el-tree-node.is-current > .el-tree-node__content .server-actions:hover) {
background-color: rgba(255, 255, 255, 0.2);
}
:deep(.el-tree-node) {
@@ -435,16 +530,20 @@ onMounted(async () => {
display: flex;
align-items: center;
width: 100%;
padding: 6px 0;
padding: 2px 0;
min-height: 48px;
box-sizing: border-box;
}
.group-node {
font-weight: 600;
color: #333;
padding: 8px 0;
min-height: 40px;
color: #1c1c1e;
padding: 8px 4px;
min-height: auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
display: flex;
align-items: center;
gap: 8px;
}
.server-node {
@@ -452,55 +551,55 @@ onMounted(async () => {
justify-content: space-between;
align-items: center;
width: 100%;
padding: 8px;
border-radius: 4px;
transition: all 0.3s;
min-height: 56px;
padding: 6px;
border-radius: 6px;
min-height: auto;
box-sizing: border-box;
}
.server-node:hover {
background-color: #f5f7fa;
background-color: transparent; /* rely on node content hover */
}
.server-info-wrapper {
flex: 1;
margin: 0 8px;
margin: 0 10px;
min-width: 0;
}
.server-name {
font-weight: 500;
font-size: 14px;
color: #333;
color: #1c1c1e;
cursor: pointer;
margin-bottom: 2px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.server-name:hover {
color: #409eff;
color: #007AFF;
}
.server-address {
font-size: 11px;
color: #999;
opacity: 0.8;
color: rgba(28, 28, 30, 0.6);
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}
.server-actions {
padding: 4px 8px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background-color: white;
color: #606266;
transition: all 0.3s;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.8);
color: #1c1c1e;
transition: all 0.2s;
white-space: nowrap;
}
.server-actions:hover {
border-color: #409eff;
color: #409eff;
background-color: #f0f7ff;
border-color: #007AFF;
color: #007AFF;
background-color: rgba(0, 122, 255, 0.1);
}
.action-text {
+69 -41
View File
@@ -681,52 +681,56 @@ onMounted(() => {
display: flex; /* Ensure the children (aside and main) flow correctly */
}
/* 左侧栏样式 */
.history-aside {
background: white;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
}
.aside-header {
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
align-items: center;
}
.history-search {
padding: 10px 20px;
border-bottom: 1px solid #ebeef5;
}
.aside-title {
}
/* 左侧历史树 */
.history-aside {
background: white;
border-right: 1px solid #e4e7ed;
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-right: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
padding: 8px 12px;
}
.aside-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 16px 12px;
border-bottom: 1px solid #f0f0f0;
padding: 12px 6px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
flex-shrink: 0;
}
.history-search {
padding: 12px 4px 8px;
}
.history-search :deep(.el-input__wrapper) {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 8px;
box-shadow: none !important;
border: 1px solid rgba(0, 0, 0, 0.08);
transition: all 0.2s;
}
.history-search :deep(.el-input__wrapper.is-focus) {
background-color: rgba(255, 255, 255, 1);
border-color: #007AFF;
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2) !important;
}
.history-search :deep(.el-input__inner) {
color: #1c1c1e;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.aside-title {
font-size: 15px;
font-size: 16px;
font-weight: 600;
color: #303133;
color: #1c1c1e;
letter-spacing: -0.5px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.aside-actions {
@@ -758,20 +762,21 @@ onMounted(() => {
.tree-node {
display: flex;
align-items: center;
gap: 6px;
gap: 8px;
width: 100%;
padding: 2px 0;
padding: 4px 6px;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.tree-folder-icon {
color: #e6a23c;
color: #1c1c1e;
font-size: 16px;
flex-shrink: 0;
}
.tree-server-icon {
color: #409eff;
color: #007AFF;
font-size: 14px;
flex-shrink: 0;
}
@@ -781,11 +786,13 @@ onMounted(() => {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #303133;
color: #1c1c1e;
font-weight: 500;
}
.tree-node.is-leaf .tree-node-label {
color: #606266;
color: #1c1c1e;
font-weight: normal;
}
.tree-node-actions {
@@ -808,20 +815,41 @@ onMounted(() => {
.tree-delete-btn {
width: 20px !important;
height: 20px !important;
background-color: transparent;
border-color: rgba(255, 255, 255, 0.4);
}
/* Element Tree 深度覆盖 */
:deep(.el-tree) {
background: transparent;
}
:deep(.el-tree-node__content) {
height: 36px;
padding-right: 8px !important;
height: auto !important;
padding: 4px 6px !important;
margin-bottom: 2px !important;
border-radius: 8px;
transition: all 0.2s ease !important;
}
:deep(.el-tree-node__content:hover) {
background-color: #f5f7fa;
background-color: rgba(0, 0, 0, 0.06) !important;
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: #ecf5ff;
/* Tree Selection Highlights */
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content:has(.is-leaf)) {
background-color: #007AFF !important;
color: white;
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content:has(.is-leaf) .tree-node-label),
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content:has(.is-leaf) .tree-server-icon) {
color: white !important;
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content:has(.is-leaf) .tree-delete-btn:hover) {
background-color: rgba(255, 255, 255, 0.2);
color: white;
}
/* 右侧表单 */
+48 -4
View File
@@ -15,6 +15,16 @@
</el-button>
</div>
<!-- 搜索框 -->
<div class="server-search">
<el-input
v-model="searchQuery"
placeholder="搜索服务器..."
clearable
:prefix-icon="Search"
/>
</div>
<!-- 服务器树型结构 -->
<div class="server-tree">
<el-tree
@@ -221,7 +231,8 @@ import {
Monitor,
Folder,
Back,
Tickets
Tickets,
Search
} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useTerminalStore } from '@/stores/terminal'
@@ -241,6 +252,9 @@ const tabs = ref([])
const activeTabId = ref(null)
const contextMenu = ref({ visible: false, x: 0, y: 0, tabId: null })
// 搜索文本
const searchQuery = ref('')
// 常用命令显示控制
const showCommands = ref(false)
@@ -253,7 +267,18 @@ const activeTab = computed(() => {
const serverTreeData = computed(() => {
const groups = {}
serversStore.servers.forEach(server => {
// 过滤服务器列表
let filteredServers = serversStore.servers
if (searchQuery.value) {
const q = searchQuery.value.toLowerCase()
filteredServers = filteredServers.filter(server => {
return server.name?.toLowerCase().includes(q) ||
server.host?.toLowerCase().includes(q) ||
server.group_name?.toLowerCase().includes(q)
})
}
filteredServers.forEach(server => {
const groupName = server.group_name || '默认分组'
if (!groups[groupName]) {
groups[groupName] = []
@@ -536,24 +561,28 @@ const hideHelperTextarea = (tabId) => {
}
// 处理树节点单击(直接创建新页签)
const handleTreeNodeClick = (data) => {
const handleTreeNodeClick = (data, node) => {
if (data.type === 'server') {
const server = serversStore.servers.find(s => s.id === data.serverId)
if (server) {
// 单击时直接创建新页签
createNewTabForServer(server)
}
} else if (data.type === 'group' && node) {
node.expanded = !node.expanded
}
}
// 处理树节点双击(功能与单击相同,提供一致性)
const handleTreeNodeDblClick = (data) => {
const handleTreeNodeDblClick = (data, node) => {
if (data.type === 'server') {
const server = serversStore.servers.find(s => s.id === data.serverId)
if (server) {
// 双击时也创建新页签
createNewTabForServer(server)
}
} else if (data.type === 'group' && node) {
node.expanded = !node.expanded
}
}
@@ -985,6 +1014,21 @@ onUnmounted(() => {
letter-spacing: 0.5px;
}
.server-search {
padding: 12px 16px;
border-bottom: 1px solid rgba(62, 62, 66, 0.6);
background: rgba(30, 30, 30, 0.4);
}
.server-search :deep(.el-input__wrapper) {
background-color: rgba(0, 0, 0, 0.2);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
}
.server-search :deep(.el-input__inner) {
color: #e0e0e0;
}
/* 树型结构样式 */
.server-tree {
flex: 1;