fix: Optimize UI and fix bugs
This commit is contained in:
1 parent
1171adb41b
commit
2e33924342
3 files changed
+250
-79
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/* 右侧表单 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user