Align management UI with web design guide

This commit is contained in:
opsx committed 2026-10-22 21:46:28 +08:00
1 parent 373a0904be
commit 347799cb1e
18 files changed
+367 -165

No files matched your search

+25 -11
View File
@@ -1,27 +1,31 @@
<template>
<el-card class="page-card" shadow="never">
<template #header>
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<el-input v-model="keyword" :placeholder="$t('tunnels.searchPlaceholder')" clearable style="width:200px" @change="load" />
<el-select v-model="protocolFilter" :placeholder="$t('tunnels.allProtocols')" clearable style="width:160px;margin-left:10px" @change="load">
<div class="tunnel-toolbar">
<div class="tunnel-filters">
<el-input v-model="keyword" :placeholder="$t('tunnels.searchPlaceholder')" clearable class="tunnel-search" @change="load" />
<el-select v-model="protocolFilter" :placeholder="$t('tunnels.allProtocols')" clearable class="tunnel-filter" @change="load">
<el-option v-for="p in protocolOptions" :key="p" :label="$t(protocolDisplay(p))" :value="p" />
</el-select>
<el-select v-model="stateFilter" :placeholder="$t('tunnels.allStates')" clearable style="width:140px;margin-left:10px" @change="load">
<el-select v-model="stateFilter" :placeholder="$t('tunnels.allStates')" clearable class="tunnel-filter" @change="load">
<el-option :label="$t('tunnels.stateRunning')" value="running" />
<el-option :label="$t('tunnels.stateStopped')" value="stopped" />
<el-option :label="$t('tunnels.stateError')" value="error" />
</el-select>
</div>
<div style="display:flex;align-items:center;gap:8px">
<div class="tunnel-actions">
<el-tooltip :content="$t('common.refresh')" placement="top">
<el-button :icon="Refresh" circle :loading="loading" @click="load" />
<el-button :icon="Refresh" circle :loading="loading" :aria-label="$t('common.refresh')" @click="load" />
</el-tooltip>
<el-button type="primary" :icon="Plus" @click="$router.push('/tunnels/create')">{{ $t('tunnels.createTunnel') }}</el-button>
</div>
</div>
</template>
<el-alert v-if="loadError" type="error" :closable="false" :title="loadError" class="tunnel-error">
<el-button size="small" @click="load">{{ $t('common.refresh') }}</el-button>
</el-alert>
<!-- 批量操作栏 -->
<div v-if="selectedRows.length > 0" class="batch-bar">
<span class="batch-info">{{ $t('tunnels.selected', { count: selectedRows.length }) }}</span>
@@ -30,7 +34,7 @@
<el-button type="danger" size="small" @click="batchAction('delete')">{{ $t('tunnels.batchDelete') }}</el-button>
</div>
<el-table :data="list" v-loading="loading" size="large" @selection-change="handleSelectionChange">
<el-table :data="list" v-loading="loading" size="default" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="45" />
<el-table-column prop="name" :label="$t('common.name')" min-width="140">
<template #default="{ row }">
@@ -62,7 +66,7 @@
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="$t('common.traffic')" width="140">
<el-table-column :label="$t('common.traffic')" width="160" align="right" header-align="right">
<template #default="{ row }">
<span style="font-size:12px">{{ fmtBytes(row.rx_bytes) }} / {{ fmtBytes(row.tx_bytes) }}</span>
</template>
@@ -251,6 +255,7 @@ const { t } = useI18n()
const router = useRouter()
const list = ref([])
const loading = ref(false)
const loadError = ref('')
const changingIds = ref(new Set())
let loadRevision = 0
const keyword = ref('')
@@ -340,7 +345,9 @@ async function load() {
if (stateFilter.value) {
result = result.filter(r => stateOf(r) === stateFilter.value)
}
if (revision === loadRevision) list.value = result
if (revision === loadRevision) { list.value = result; loadError.value = '' }
} catch (e) {
if (revision === loadRevision) loadError.value = e.response?.data?.error || t('common.loadFailed')
} finally { if (revision === loadRevision) loading.value = false }
}
@@ -403,7 +410,7 @@ async function resumeLogStream() {
}
}
} catch (e) {
if (e.name !== 'AbortError') console.error('log stream error', e)
if (e.name !== 'AbortError') logContent.value += `\n${t('common.loadFailed')}\n`
}
logStreaming.value = false
}
@@ -627,6 +634,13 @@ onUnmounted(() => clearInterval(timer))
</script>
<style scoped>
.tunnel-toolbar, .tunnel-filters, .tunnel-actions { display: flex; align-items: center; gap: var(--space-2); }
.tunnel-toolbar { justify-content: space-between; flex-wrap: wrap; }
.tunnel-filters { flex: 1; min-width: 0; flex-wrap: wrap; }
.tunnel-search { width: 200px; }
.tunnel-filter { width: 150px; }
.tunnel-error { margin-bottom: var(--space-4); }
@media (max-width: 640px) { .tunnel-search { flex: 1 1 100%; width: 100%; } .tunnel-filter { flex: 1 1 135px; width: auto; } .tunnel-actions { width: 100%; justify-content: flex-end; } }
.remark { font-size: 12px; color: var(--text-secondary); }
.proto-cell { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; }
.conf-box { background: var(--bg-inset); border: 1px solid var(--border-base); border-radius: var(--radius-sm); padding: 12px; max-height: 260px; overflow: auto; }