Align management UI with web design guide
This commit is contained in:
1 parent
373a0904be
commit
347799cb1e
18 files changed
+367
-165
No files matched your search
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user