style: 优化连接弹窗 - 分区折叠 + 视觉层次

- 基本连接信息始终显示, 带卡片背景和图标标题
- TLS/Sentinel/Cluster/SSH 改为可折叠面板, 默认折叠
- 折叠标题显示已启用标签 (Enabled tag)
- 内联样式替换为 CSS 类 (.file-input-row, .form-input)
- 弹窗宽度 520px -> 580px
- 深色/浅色主题适配
This commit is contained in:
2026-07-11 14:55:39 +08:00
parent 6be62e0fe6
commit f6b85106a2
@@ -40,9 +40,11 @@ const form = reactive({
const saving = ref(false)
const testing = ref(false)
const isEdit = ref(false)
const activeCollapses = ref<string[]>([])
watch(() => props.visible, async (v) => {
if (v) {
activeCollapses.value = []
if (props.connection) {
isEdit.value = true
const decryptedAuth = await connStore.decryptPassword(props.connection.auth || '')
@@ -219,128 +221,183 @@ async function handleSave() {
<el-dialog
:model-value="visible"
:title="isEdit ? t('connection.edit') : t('connection.new')"
width="520px"
width="580px"
:close-on-click-modal="false"
@update:model-value="close"
class="connection-dialog"
>
<el-form label-position="top" size="small">
<el-form-item :label="t('connection.name')" required>
<el-input v-model="form.name" placeholder="My Redis" />
</el-form-item>
<el-row :gutter="12">
<el-col :span="16">
<el-form-item :label="t('connection.host')" required>
<el-input v-model="form.host" placeholder="127.0.0.1" />
<el-form label-position="top" size="small" class="connection-form">
<!-- Basic Connection Info -->
<div class="section basic-section">
<div class="section-header">
<div class="section-icon">🔌</div>
<span class="section-title">Basic Connection</span>
</div>
<div class="section-content">
<el-form-item :label="t('connection.name')" required>
<el-input v-model="form.name" placeholder="My Redis" class="form-input" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="t('connection.port')">
<el-input-number v-model="form.port" :min="1" :max="65535" controls-position="right" style="width:100%" />
<el-row :gutter="12">
<el-col :span="16">
<el-form-item :label="t('connection.host')" required>
<el-input v-model="form.host" placeholder="127.0.0.1" class="form-input" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="t('connection.port')">
<el-input-number v-model="form.port" :min="1" :max="65535" controls-position="right" class="form-input" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="t('connection.password')">
<el-input v-model="form.auth" type="password" show-password :placeholder="t('connection.password')" class="form-input" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="t('connection.password')">
<el-input v-model="form.auth" type="password" show-password :placeholder="t('connection.password')" />
</el-form-item>
<el-form-item :label="t('connection.username')">
<el-input v-model="form.username" placeholder="default" />
</el-form-item>
<el-form-item label="Key Separator">
<el-input v-model="form.separator" placeholder=":" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.tls">{{ t('connection.tls') }}</el-checkbox>
</el-form-item>
<template v-if="form.tls">
<el-form-item :label="t('connection.tlsCaPath')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.tlsCaPath" placeholder="/path/to/ca.pem" />
<el-button @click="selectTlsCaFile">{{ t('connection.selectFile') }}</el-button>
<el-form-item :label="t('connection.username')">
<el-input v-model="form.username" placeholder="default" class="form-input" />
</el-form-item>
<el-form-item label="Key Separator">
<el-input v-model="form.separator" placeholder=":" class="form-input" />
</el-form-item>
</div>
</div>
<!-- Advanced Options -->
<el-collapse v-model="activeCollapses" class="advanced-collapse">
<!-- TLS Section -->
<el-collapse-item name="tls">
<template #title>
<div class="collapse-title">
<span class="collapse-icon">🔒</span>
<span class="collapse-text">{{ t('connection.tls') }}</span>
<el-tag v-if="form.tls" size="small" type="success" effect="dark">Enabled</el-tag>
</div>
</template>
<div class="collapse-content">
<el-form-item>
<el-checkbox v-model="form.tls">{{ t('connection.tls') }}</el-checkbox>
</el-form-item>
<template v-if="form.tls">
<el-form-item :label="t('connection.tlsCaPath')">
<div class="file-input-row">
<el-input v-model="form.tlsCaPath" placeholder="/path/to/ca.pem" />
<el-button @click="selectTlsCaFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('connection.tlsCertPath')">
<div class="file-input-row">
<el-input v-model="form.tlsCertPath" placeholder="/path/to/client.pem" />
<el-button @click="selectTlsCertFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('connection.tlsKeyPath')">
<div class="file-input-row">
<el-input v-model="form.tlsKeyPath" placeholder="/path/to/client-key.pem" />
<el-button @click="selectTlsKeyFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.tlsRejectUnauthorized">{{ t('connection.tlsRejectUnauthorized') }}</el-checkbox>
</el-form-item>
</template>
</div>
</el-form-item>
<el-form-item :label="t('connection.tlsCertPath')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.tlsCertPath" placeholder="/path/to/client.pem" />
<el-button @click="selectTlsCertFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('connection.tlsKeyPath')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.tlsKeyPath" placeholder="/path/to/client-key.pem" />
<el-button @click="selectTlsKeyFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.tlsRejectUnauthorized">{{ t('connection.tlsRejectUnauthorized') }}</el-checkbox>
</el-form-item>
</template>
</el-collapse-item>
<!-- Sentinel -->
<el-divider />
<el-form-item>
<el-checkbox v-model="form.sentinelEnabled">{{ t('connection.sentinel') }}</el-checkbox>
</el-form-item>
<template v-if="form.sentinelEnabled">
<el-row :gutter="12">
<el-col :span="16">
<el-form-item :label="t('connection.sentinelHost')">
<el-input v-model="form.sentinelHost" placeholder="192.168.1.1" />
<!-- Sentinel Section -->
<el-collapse-item name="sentinel">
<template #title>
<div class="collapse-title">
<span class="collapse-icon">🏰</span>
<span class="collapse-text">{{ t('connection.sentinel') }}</span>
<el-tag v-if="form.sentinelEnabled" size="small" type="success" effect="dark">Enabled</el-tag>
</div>
</template>
<div class="collapse-content">
<el-form-item>
<el-checkbox v-model="form.sentinelEnabled">{{ t('connection.sentinel') }}</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="t('connection.sentinelPort')">
<el-input-number v-model="form.sentinelPort" :min="1" :max="65535" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="t('connection.sentinelMasterName')">
<el-input v-model="form.sentinelMasterName" placeholder="mymaster" />
</el-form-item>
<el-form-item :label="t('connection.sentinelNodePassword')">
<el-input v-model="form.sentinelNodePassword" type="password" show-password />
</el-form-item>
</template>
<!-- Cluster -->
<el-divider />
<el-form-item>
<el-checkbox v-model="form.cluster">{{ t('connection.cluster') }}</el-checkbox>
</el-form-item>
<!-- SSH Tunnel -->
<el-divider />
<el-form-item>
<el-checkbox v-model="form.sshEnabled">{{ t('connection.ssh') }}</el-checkbox>
</el-form-item>
<template v-if="form.sshEnabled">
<el-row :gutter="12">
<el-col :span="16">
<el-form-item :label="t('connection.sshHost')">
<el-input v-model="form.sshHost" placeholder="192.168.1.1" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="t('connection.sshPort')">
<el-input-number v-model="form.sshPort" :min="1" :max="65535" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="t('connection.sshUsername')">
<el-input v-model="form.sshUsername" placeholder="root" />
</el-form-item>
<el-form-item :label="t('connection.sshPassword')">
<el-input v-model="form.sshPassword" type="password" show-password :placeholder="t('connection.sshPassword')" />
</el-form-item>
<el-form-item :label="t('connection.sshPrivateKey')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.sshPrivateKeyPath" placeholder="~/.ssh/id_rsa" />
<el-button @click="selectFile">{{ t('connection.selectFile') }}</el-button>
<template v-if="form.sentinelEnabled">
<el-row :gutter="12">
<el-col :span="16">
<el-form-item :label="t('connection.sentinelHost')">
<el-input v-model="form.sentinelHost" placeholder="192.168.1.1" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="t('connection.sentinelPort')">
<el-input-number v-model="form.sentinelPort" :min="1" :max="65535" controls-position="right" class="form-input" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="t('connection.sentinelMasterName')">
<el-input v-model="form.sentinelMasterName" placeholder="mymaster" />
</el-form-item>
<el-form-item :label="t('connection.sentinelNodePassword')">
<el-input v-model="form.sentinelNodePassword" type="password" show-password />
</el-form-item>
</template>
</div>
</el-form-item>
<el-form-item :label="t('connection.sshPassphrase')">
<el-input v-model="form.sshPassphrase" type="password" show-password :placeholder="t('connection.sshPassphrase')" />
</el-form-item>
</template>
</el-collapse-item>
<!-- Cluster Section -->
<el-collapse-item name="cluster">
<template #title>
<div class="collapse-title">
<span class="collapse-icon">🌐</span>
<span class="collapse-text">{{ t('connection.cluster') }}</span>
<el-tag v-if="form.cluster" size="small" type="success" effect="dark">Enabled</el-tag>
</div>
</template>
<div class="collapse-content">
<el-form-item>
<el-checkbox v-model="form.cluster">{{ t('connection.cluster') }}</el-checkbox>
</el-form-item>
</div>
</el-collapse-item>
<!-- SSH Tunnel Section -->
<el-collapse-item name="ssh">
<template #title>
<div class="collapse-title">
<span class="collapse-icon">🔑</span>
<span class="collapse-text">{{ t('connection.ssh') }}</span>
<el-tag v-if="form.sshEnabled" size="small" type="success" effect="dark">Enabled</el-tag>
</div>
</template>
<div class="collapse-content">
<el-form-item>
<el-checkbox v-model="form.sshEnabled">{{ t('connection.ssh') }}</el-checkbox>
</el-form-item>
<template v-if="form.sshEnabled">
<el-row :gutter="12">
<el-col :span="16">
<el-form-item :label="t('connection.sshHost')">
<el-input v-model="form.sshHost" placeholder="192.168.1.1" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="t('connection.sshPort')">
<el-input-number v-model="form.sshPort" :min="1" :max="65535" controls-position="right" class="form-input" />
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="t('connection.sshUsername')">
<el-input v-model="form.sshUsername" placeholder="root" />
</el-form-item>
<el-form-item :label="t('connection.sshPassword')">
<el-input v-model="form.sshPassword" type="password" show-password :placeholder="t('connection.sshPassword')" />
</el-form-item>
<el-form-item :label="t('connection.sshPrivateKey')">
<div class="file-input-row">
<el-input v-model="form.sshPrivateKeyPath" placeholder="~/.ssh/id_rsa" />
<el-button @click="selectFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('connection.sshPassphrase')">
<el-input v-model="form.sshPassphrase" type="password" show-password :placeholder="t('connection.sshPassphrase')" />
</el-form-item>
</template>
</div>
</el-collapse-item>
</el-collapse>
</el-form>
<template #footer>
<div class="dialog-footer">
@@ -357,10 +414,104 @@ async function handleSave() {
</template>
<style scoped>
.connection-form {
max-height: 70vh;
overflow-y: auto;
padding-right: 8px;
}
/* Section styling */
.section {
margin-bottom: 16px;
}
.basic-section {
background: var(--bg-card);
border-radius: var(--radius-md);
padding: 16px;
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.section-icon {
font-size: 20px;
}
.section-title {
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
}
/* Form inputs */
.form-input {
width: 100%;
}
/* File input row */
.file-input-row {
display: flex;
gap: 8px;
width: 100%;
}
.file-input-row .el-input {
flex: 1;
}
/* Advanced collapse styling */
.advanced-collapse :deep(.el-collapse-item__header) {
padding: 12px 0;
background: transparent;
border: none;
}
.advanced-collapse :deep(.el-collapse-item__wrap) {
border: none;
background: var(--bg-card);
border-radius: var(--radius-sm);
padding: 12px 16px;
margin-top: 4px;
}
.advanced-collapse :deep(.el-collapse-item__content) {
padding: 0;
}
.advanced-collapse :deep(.el-collapse-item) {
border: none;
margin-bottom: 8px;
}
.collapse-title {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.collapse-icon {
font-size: 18px;
}
.collapse-text {
font-weight: 500;
color: var(--text-primary);
}
/* Footer */
.dialog-footer {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.footer-right {