优化备份后台布局

This commit is contained in:
yml2213
2026-08-17 10:36:15 +08:00
parent 50b458e5c2
commit 79ced2d121
@@ -17,8 +17,15 @@ const loading = ref(false)
const saving = ref(false) const saving = ref(false)
const status = ref<BackupStatus | null>(null) const status = ref<BackupStatus | null>(null)
const statusError = ref('') const statusError = ref('')
const canUpdateSchedule = computed(() => adminSession.isSuperAdmin || adminSession.hasPermission('backup:schedule:update')) const canUpdateSchedule = computed(
const schedule = reactive({ enabled: true, full_hour: 4, full_minute: 30, binlog_interval_minutes: 1 }) () => adminSession.isSuperAdmin || adminSession.hasPermission('backup:schedule:update')
)
const schedule = reactive({
enabled: true,
full_hour: 4,
full_minute: 30,
binlog_interval_minutes: 1,
})
const healthLabel = computed(() => (status.value?.health === 'healthy' ? '链路正常' : '需要检查')) const healthLabel = computed(() => (status.value?.health === 'healthy' ? '链路正常' : '需要检查'))
const healthType = computed(() => (status.value?.health === 'healthy' ? 'success' : 'warning')) const healthType = computed(() => (status.value?.health === 'healthy' ? 'success' : 'warning'))
@@ -27,7 +34,10 @@ async function load() {
loading.value = true loading.value = true
statusError.value = '' statusError.value = ''
try { try {
const [nextStatus, nextSchedule] = await Promise.all([fetchBackupStatus(), fetchBackupSchedule()]) const [nextStatus, nextSchedule] = await Promise.all([
fetchBackupStatus(),
fetchBackupSchedule(),
])
status.value = nextStatus status.value = nextStatus
Object.assign(schedule, nextSchedule) Object.assign(schedule, nextSchedule)
} catch (error) { } catch (error) {
@@ -64,7 +74,7 @@ onMounted(load)
<el-alert v-if="statusError" type="warning" :title="statusError" :closable="false" show-icon /> <el-alert v-if="statusError" type="warning" :title="statusError" :closable="false" show-icon />
<div v-else class="status-grid"> <div v-else class="status-grid">
<el-card shadow="never"> <el-card shadow="never" class="status-card health-card">
<template #header><span>备份健康状态</span></template> <template #header><span>备份健康状态</span></template>
<el-tag :type="healthType" effect="light">{{ healthLabel }}</el-tag> <el-tag :type="healthType" effect="light">{{ healthLabel }}</el-tag>
<dl> <dl>
@@ -77,7 +87,7 @@ onMounted(load)
</dl> </dl>
</el-card> </el-card>
<el-card shadow="never"> <el-card shadow="never" class="status-card full-card">
<template #header><span>最新全量备份</span></template> <template #header><span>最新全量备份</span></template>
<dl> <dl>
<dt>本地完成标识</dt> <dt>本地完成标识</dt>
@@ -87,7 +97,7 @@ onMounted(load)
</dl> </dl>
</el-card> </el-card>
<el-card shadow="never"> <el-card shadow="never" class="status-card binlog-card">
<template #header><span>最新 Binlog 归档</span></template> <template #header><span>最新 Binlog 归档</span></template>
<dl> <dl>
<dt>Binlog 文件</dt> <dt>Binlog 文件</dt>
@@ -101,41 +111,176 @@ onMounted(load)
<el-card shadow="never" class="schedule-card"> <el-card shadow="never" class="schedule-card">
<template #header><span>定时配置</span></template> <template #header><span>定时配置</span></template>
<el-form label-position="top" class="schedule-form"> <el-form label-position="top" class="schedule-form">
<div class="schedule-fields">
<el-form-item label="启用服务器定时备份"> <el-form-item label="启用服务器定时备份">
<el-switch v-model="schedule.enabled" :disabled="!canUpdateSchedule" /> <el-switch v-model="schedule.enabled" :disabled="!canUpdateSchedule" />
</el-form-item> </el-form-item>
<el-form-item label="每日全量备份时间(Asia/Shanghai"> <el-form-item label="每日全量备份时间(Asia/Shanghai">
<div class="time-inputs"> <div class="time-inputs">
<el-input-number v-model="schedule.full_hour" :min="0" :max="23" :disabled="!canUpdateSchedule" controls-position="right" /> <el-input-number
v-model="schedule.full_hour"
:min="0"
:max="23"
:disabled="!canUpdateSchedule"
controls-position="right"
/>
<span>:</span> <span>:</span>
<el-input-number v-model="schedule.full_minute" :min="0" :max="59" :disabled="!canUpdateSchedule" controls-position="right" /> <el-input-number
v-model="schedule.full_minute"
:min="0"
:max="59"
:disabled="!canUpdateSchedule"
controls-position="right"
/>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="Binlog 归档间隔"> <el-form-item label="Binlog 归档间隔">
<el-select v-model="schedule.binlog_interval_minutes" :disabled="!canUpdateSchedule"> <el-select
v-model="schedule.binlog_interval_minutes"
class="binlog-interval-select"
:disabled="!canUpdateSchedule"
>
<el-option :value="1" label="每 1 分钟" /> <el-option :value="1" label="每 1 分钟" />
<el-option :value="2" label="每 2 分钟" /> <el-option :value="2" label="每 2 分钟" />
<el-option :value="3" label="每 3 分钟" /> <el-option :value="3" label="每 3 分钟" />
<el-option :value="5" label="每 5 分钟" /> <el-option :value="5" label="每 5 分钟" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-button v-if="canUpdateSchedule" type="primary" :loading="saving" @click="saveSchedule">保存定时配置</el-button> </div>
<div v-if="canUpdateSchedule" class="schedule-actions">
<el-button type="primary" :loading="saving" @click="saveSchedule">保存定时配置</el-button>
</div>
</el-form> </el-form>
</el-card> </el-card>
</section> </section>
</template> </template>
<style scoped> <style scoped>
.backup-page { padding: 20px; } .backup-page {
.page-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; } padding: 20px;
.page-header h1 { margin: 0; font-size: 22px; font-weight: 600; } }
.status-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
dl { margin: 16px 0 0; display: grid; gap: 8px; } .page-header {
dt { color: var(--el-text-color-secondary); font-size: 13px; } display: flex;
dd { margin: 0; overflow-wrap: anywhere; } align-items: flex-start;
.path { color: var(--el-text-color-regular); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; } justify-content: space-between;
.schedule-card { margin-top: 16px; } margin-bottom: 18px;
.schedule-form { display: grid; grid-template-columns: repeat(3, minmax(180px, 260px)); gap: 0 18px; align-items: end; } }
.time-inputs { display: flex; align-items: center; gap: 8px; }
@media (max-width: 900px) { .status-grid, .schedule-form { grid-template-columns: 1fr; } } .page-header h1 {
margin: 0;
font-size: 22px;
font-weight: 600;
}
.status-grid {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 16px;
}
.health-card {
grid-column: span 4;
}
.full-card {
grid-column: span 8;
}
.binlog-card {
grid-column: 1 / -1;
}
dl {
display: grid;
gap: 8px;
margin: 16px 0 0;
}
dt {
color: var(--el-text-color-secondary);
font-size: 13px;
}
dd {
min-width: 0;
margin: 0;
overflow-wrap: anywhere;
}
.path {
color: var(--el-text-color-regular);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
line-height: 1.5;
}
.schedule-card {
margin-top: 16px;
}
.schedule-form {
display: grid;
gap: 16px;
}
.schedule-fields {
display: grid;
grid-template-columns: 250px 280px 180px;
gap: 20px;
align-items: end;
}
.schedule-fields :deep(.el-form-item) {
margin-bottom: 0;
}
.time-inputs {
display: flex;
align-items: center;
gap: 8px;
}
.time-inputs :deep(.el-input-number) {
width: 112px;
}
.binlog-interval-select {
width: 180px;
}
.schedule-actions {
display: flex;
}
@media (max-width: 1100px) {
.health-card {
grid-column: span 5;
}
.full-card {
grid-column: span 7;
}
.schedule-fields {
grid-template-columns: repeat(2, minmax(220px, 1fr));
}
}
@media (max-width: 700px) {
.backup-page {
padding: 16px;
}
.health-card,
.full-card,
.binlog-card {
grid-column: 1 / -1;
}
.schedule-fields {
grid-template-columns: 1fr;
gap: 16px;
}
}
</style> </style>