重构: Settings拆4子组件(Provider/Connection/General/Knowledge·1042行瘦身零行为变化)
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<!-- ═══ 连接管理 ═══ -->
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<h2>{{ $t('settings.panelConnection') }}</h2>
|
||||
<button class="btn btn-ghost btn-sm" @click="openConnForm()">{{ $t('settings.addConnection') }}</button>
|
||||
</div>
|
||||
<div class="connection-list" v-if="connections.length > 0">
|
||||
<div class="connection-card" v-for="conn in connections" :key="conn.id">
|
||||
<div class="conn-left">
|
||||
<span class="conn-icon">{{ typeIcon(conn.type) }}</span>
|
||||
<div class="conn-info">
|
||||
<div class="conn-name-row">
|
||||
<span class="conn-name">{{ conn.name }}</span>
|
||||
<span class="conn-type" :class="'type-' + conn.type">{{ typeLabel(conn.type) }}</span>
|
||||
</div>
|
||||
<span class="conn-host">{{ conn.host }}{{ conn.port ? ':' + conn.port : '' }}</span>
|
||||
<span class="conn-user" v-if="conn.user">{{ conn.user }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conn-actions">
|
||||
<button class="btn-link" @click="openConnForm(conn)">{{ $t('common.edit') }}</button>
|
||||
<button class="btn-link btn-link-danger" @click="removeConn(conn.id)">{{ $t('common.delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="empty-hint" v-else>{{ $t('settings.emptyConnection') }}</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 连接表单 ═══ -->
|
||||
<section class="panel" v-if="connForm.visible">
|
||||
<div class="panel-header">
|
||||
<h2>{{ connForm.editId ? $t('settings.editConnectionTitle') : $t('settings.addConnectionTitle') }}</h2>
|
||||
<button class="btn btn-ghost btn-sm" @click="connForm.visible = false">{{ $t('common.cancel') }}</button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-field">
|
||||
<label class="form-label">{{ $t('settings.labelName') }}</label>
|
||||
<input v-model="connForm.name" class="setting-input" :placeholder="$t('settings.phConnName')" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">{{ $t('settings.labelType') }}</label>
|
||||
<select v-model="connForm.type" class="setting-select">
|
||||
<option value="mysql">MySQL</option>
|
||||
<option value="ssh">SSH</option>
|
||||
<option value="redis">Redis</option>
|
||||
<option value="mongo">MongoDB</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">{{ $t('settings.labelHost') }}</label>
|
||||
<input v-model="connForm.host" class="setting-input" :placeholder="$t('settings.phHost')" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">{{ $t('settings.labelPort') }}</label>
|
||||
<input v-model.number="connForm.port" class="setting-input" type="number" :placeholder="$t('settings.phPort')" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">{{ $t('settings.labelUser') }}</label>
|
||||
<input v-model="connForm.user" class="setting-input" :placeholder="$t('settings.phUser')" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" @click="saveConn">{{ $t('common.save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
|
||||
// ============================================================
|
||||
// 连接管理域 — localStorage CRUD
|
||||
// ============================================================
|
||||
const { t } = useI18n()
|
||||
const appSettings = useAppSettingsStore()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'toast', msg: string, type: 'error' | 'warning' | 'info'): void
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
confirmDialog: (msg: string) => Promise<boolean>
|
||||
}>()
|
||||
|
||||
interface ConnRecord {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
}
|
||||
|
||||
const connections = ref<ConnRecord[]>([])
|
||||
|
||||
const connForm = reactive({
|
||||
visible: false,
|
||||
editId: '' as string,
|
||||
name: '',
|
||||
type: 'mysql',
|
||||
host: '',
|
||||
port: 3306,
|
||||
user: '',
|
||||
})
|
||||
|
||||
const CONN_STORAGE_KEY = 'df-connections'
|
||||
|
||||
function loadConnections() {
|
||||
const stored = appSettings.get<ConnRecord[] | null>(CONN_STORAGE_KEY, null)
|
||||
if (stored) connections.value = stored
|
||||
}
|
||||
|
||||
function persistConnections() {
|
||||
void appSettings.set(CONN_STORAGE_KEY, connections.value)
|
||||
}
|
||||
|
||||
function typeIcon(type: string) {
|
||||
const map: Record<string, string> = { mysql: '🗄️', ssh: '🔐', redis: '⚡', mongo: '🍃' }
|
||||
return map[type] || '🔗'
|
||||
}
|
||||
|
||||
function typeLabel(type: string) {
|
||||
const map: Record<string, string> = { mysql: 'MySQL', ssh: 'SSH', redis: 'Redis', mongo: 'MongoDB' }
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
function openConnForm(conn?: ConnRecord) {
|
||||
if (conn) {
|
||||
connForm.editId = conn.id
|
||||
connForm.name = conn.name
|
||||
connForm.type = conn.type
|
||||
connForm.host = conn.host
|
||||
connForm.port = conn.port
|
||||
connForm.user = conn.user
|
||||
} else {
|
||||
connForm.editId = ''
|
||||
connForm.name = ''
|
||||
connForm.type = 'mysql'
|
||||
connForm.host = ''
|
||||
connForm.port = 3306
|
||||
connForm.user = ''
|
||||
}
|
||||
connForm.visible = true
|
||||
}
|
||||
|
||||
function saveConn() {
|
||||
if (!connForm.name || !connForm.host) {
|
||||
emit('toast', t('settings.toastConnIncomplete'), 'warning')
|
||||
return
|
||||
}
|
||||
const record: ConnRecord = {
|
||||
id: connForm.editId || Date.now().toString(36),
|
||||
name: connForm.name,
|
||||
type: connForm.type,
|
||||
host: connForm.host,
|
||||
port: connForm.port,
|
||||
user: connForm.user,
|
||||
}
|
||||
if (connForm.editId) {
|
||||
const idx = connections.value.findIndex(c => c.id === connForm.editId)
|
||||
if (idx >= 0) connections.value[idx] = record
|
||||
} else {
|
||||
connections.value.push(record)
|
||||
}
|
||||
persistConnections()
|
||||
connForm.visible = false
|
||||
}
|
||||
|
||||
async function removeConn(id: string) {
|
||||
const c = connections.value.find(x => x.id === id)
|
||||
if (!await props.confirmDialog(t('settings.confirmDeleteConn', { name: c?.name || id }))) return
|
||||
connections.value = connections.value.filter(c => c.id !== id)
|
||||
persistConnections()
|
||||
emit('toast', t('settings.toastDeleted'), 'info')
|
||||
}
|
||||
|
||||
/** 暴露给 shell onMounted 调用 */
|
||||
defineExpose({ loadConnections })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ===== 面板容器(原 Settings.vue 抽出,各子组件自持 scoped 副本) ===== */
|
||||
.panel {
|
||||
background: var(--df-bg-card);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-lg);
|
||||
padding: var(--df-pad-panel);
|
||||
}
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--df-gap-head);
|
||||
}
|
||||
.panel-header h2 { font-size: 15px; font-weight: 500; }
|
||||
.empty-hint { font-size: 13px; color: var(--df-text-dim); padding: 12px 0; }
|
||||
|
||||
/* ===== 按钮 ===== */
|
||||
.btn { padding: 8px 16px; border: none; border-radius: var(--df-radius-sm); font-size: 13px; cursor: pointer; transition: all 0.15s; }
|
||||
.btn-primary { background: var(--df-accent); color: #fff; }
|
||||
.btn-primary:hover { background: var(--df-accent-hover); }
|
||||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn-ghost { background: transparent; color: var(--df-text-secondary); border: 0.5px solid var(--df-border); }
|
||||
.btn-ghost:hover { background: var(--df-bg-card); color: var(--df-text); }
|
||||
.btn-sm { padding: 4px 12px; font-size: 12px; }
|
||||
.btn-link { background: none; border: none; color: var(--df-accent); font-size: 12px; cursor: pointer; padding: 2px 6px; }
|
||||
.btn-link:hover { text-decoration: underline; }
|
||||
.btn-link-danger { color: var(--df-danger); }
|
||||
|
||||
/* ===== 连接卡片 ===== */
|
||||
.connection-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.connection-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
background: var(--df-bg);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius);
|
||||
}
|
||||
.conn-left { display: flex; align-items: center; gap: 10px; }
|
||||
.conn-icon { font-size: 18px; }
|
||||
.conn-info { display: flex; flex-direction: column; gap: 2px; }
|
||||
.conn-name-row { display: flex; align-items: center; gap: 8px; }
|
||||
.conn-name { font-size: 14px; font-weight: 500; color: var(--df-text); }
|
||||
.conn-type {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--df-radius-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
.type-mysql { background: rgba(100,181,246,0.15); color: var(--df-info); }
|
||||
.type-ssh { background: rgba(108,99,255,0.15); color: var(--df-accent); }
|
||||
.type-redis { background: rgba(255,107,107,0.15); color: var(--df-danger); }
|
||||
.type-mongo { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
|
||||
.conn-host, .conn-user {
|
||||
font-size: 12px;
|
||||
color: var(--df-text-dim);
|
||||
font-family: var(--df-font-mono);
|
||||
}
|
||||
.conn-user {
|
||||
background: rgba(90,99,128,0.2);
|
||||
padding: 1px 6px;
|
||||
border-radius: var(--df-radius-sm);
|
||||
}
|
||||
.conn-actions { display: flex; gap: 4px; }
|
||||
|
||||
/* 表单 */
|
||||
.form-grid { display: flex; flex-direction: column; gap: 14px; }
|
||||
.form-field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.form-label { font-size: 12px; font-weight: 500; color: var(--df-text-secondary); }
|
||||
.form-actions { display: flex; justify-content: flex-end; padding-top: 4px; }
|
||||
|
||||
.setting-input { padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; min-width: 260px; }
|
||||
.setting-input:focus { border-color: var(--df-accent); }
|
||||
.setting-select { padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; cursor: pointer; min-width: 160px; }
|
||||
.setting-select:focus { border-color: var(--df-accent); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user