v0.3.7: 恢复前端Vue源码 + 修复AdminDashboard 401根源
This commit is contained in:
355
source_clean/frontend-src/src/pages/admin/Cleanup.vue
Normal file
355
source_clean/frontend-src/src/pages/admin/Cleanup.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<div class="cleanup-section">
|
||||
<el-card class="config-card">
|
||||
<template #header><span>🧹 存储清理</span></template>
|
||||
<div class="cleanup-grid">
|
||||
<!-- 列1: ⏱ 基础清理策略 -->
|
||||
<div class="cleanup-group">
|
||||
<div class="cleanup-group-label">⏱ 基础清理策略</div>
|
||||
<el-form label-width="120px" label-position="left" size="small">
|
||||
<el-form-item label="自动清理">
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||
<el-switch v-model="cleanupEnabled" size="small" />
|
||||
<span class="cleanup-hint">每天自动检查一次,删除过期日志、移入回收站文件</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="清空回收站">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<el-switch v-model="cleanupEmptyTrash" size="small" />
|
||||
<span class="cleanup-hint">清理时一并清空各网盘回收站</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="白名单目录">
|
||||
<div style="width: 100%;">
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px;" v-if="whitelistDirs.length">
|
||||
<el-tag v-for="(dir, i) in whitelistDirs" :key="i" closable size="small" @close="removeWhitelistDir(i)">{{ dir }}</el-tag>
|
||||
</div>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<el-input v-model="newWhitelistDir" placeholder="输入目录名" size="small" style="width: 160px" @keyup.enter="addWhitelistDir" />
|
||||
<el-button type="primary" size="small" @click="addWhitelistDir">添加</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 列2: 📦 保留设置 -->
|
||||
<div class="cleanup-group">
|
||||
<div class="cleanup-group-label">📦 保留设置</div>
|
||||
<el-form label-width="120px" label-position="left" size="small">
|
||||
<el-form-item label="文件保留">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-input-number v-model="cleanupFileRetentionDays" :min="1" :max="365" style="width: 100px" size="small" />
|
||||
<span>天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="日志保留">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-input-number v-model="cleanupLogRetentionDays" :min="1" :max="365" style="width: 100px" size="small" />
|
||||
<span>天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="Cookie检测">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-input-number v-model="verifyIntervalMinutes" :min="5" :max="1440" :step="5" style="width: 100px" size="small" />
|
||||
<span>分钟</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="空间校准">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-input-number v-model="storageRefreshIntervalMinutes" :min="5" :max="1440" :step="5" style="width: 100px" size="small" />
|
||||
<span>分钟</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 列3: 📊 空间阈值自动清理 -->
|
||||
<div class="cleanup-group">
|
||||
<div class="cleanup-group-label">📊 空间阈值自动清理</div>
|
||||
<el-form label-width="120px" label-position="left" size="small">
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="cleanupSpaceThresholdEnabled" size="small" />
|
||||
<span class="cleanup-hint" style="margin-left: 8px;">已用空间超过阈值时,按比例删除最旧的转存文件</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="cleanupSpaceThresholdEnabled" label="使用阈值">
|
||||
<el-slider v-model="cleanupSpaceThresholdPercent" :min="50" :max="99" style="width: 140px" show-input size="small" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="cleanupSpaceThresholdEnabled" label="删除比例">
|
||||
<el-slider v-model="cleanupSpaceThresholdDeletePercent" :min="5" :max="50" :step="5" style="width: 140px" show-input size="small" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 列4: 🔗 分享链接复用 -->
|
||||
<div class="cleanup-group">
|
||||
<div class="cleanup-group-label">🔗 分享链接复用</div>
|
||||
<el-form label-width="120px" label-position="left" size="small">
|
||||
<el-form-item label="复用">
|
||||
<el-switch v-model="saveReuseEnabled" size="small" />
|
||||
<span class="cleanup-hint" style="margin-left: 8px;">相同原始链接不再重复转存,复用已有分享链接(会自动验证原链接有效性;60秒内重复请求直接返回已有链接)</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部:手动操作(跨列全宽) -->
|
||||
<div class="cleanup-actions">
|
||||
<div class="cleanup-actions-buttons">
|
||||
<el-button type="primary" :loading="cleanupSaving" @click="handleSaveCleanupConfigs">💾 保存清理配置</el-button>
|
||||
<el-button type="danger" :loading="cleanupRunning" @click="handleRunCleanup">{{ cleanupRunning ? '清理中...' : '🗑️ 立即清理' }}</el-button>
|
||||
<el-button type="warning" :loading="emptyTrashRunning" @click="handleEmptyTrash">{{ emptyTrashRunning ? '清空中...' : '🧹 清空回收站' }}</el-button>
|
||||
</div>
|
||||
<div v-if="lastCleanupTime" class="cleanup-info">
|
||||
⏰ 上次清理:{{ lastCleanupTime }}
|
||||
<span v-if="lastCleanupStats" style="margin-left: 12px;">📊 {{ lastCleanupStats }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getSystemConfigs, updateSystemConfigs, runCleanup, emptyAllTrash } from '../../api'
|
||||
|
||||
interface ConfigMap {
|
||||
[key: string]: string | number
|
||||
}
|
||||
const sysConfigs = reactive<ConfigMap>({})
|
||||
|
||||
const cleanupRunning = ref(false)
|
||||
const emptyTrashRunning = ref(false)
|
||||
const cleanupSaving = ref(false)
|
||||
|
||||
const lastCleanupTime = computed(() => String(sysConfigs.cleanup_last_run || ''))
|
||||
const lastCleanupStats = computed(() => {
|
||||
const raw = String(sysConfigs.cleanup_last_stats || '')
|
||||
if (!raw) return ''
|
||||
try {
|
||||
const s = JSON.parse(raw)
|
||||
const parts = []
|
||||
if (s.filesTrashed > 0) parts.push(`移入回收站 ${s.filesTrashed} 个文件夹`)
|
||||
if (s.logsDeleted > 0) parts.push(`删除 ${s.logsDeleted} 条日志`)
|
||||
if (s.trashEmptied) parts.push(`已清空回收站`)
|
||||
if (s.errors > 0) parts.push(`⚠️ ${s.errors} 个错误`)
|
||||
return parts.join(' / ') || '无操作'
|
||||
} catch { return '' }
|
||||
})
|
||||
|
||||
const cleanupEnabled = computed({
|
||||
get: () => String(sysConfigs.cleanup_enabled) === 'true',
|
||||
set: (val: boolean) => { sysConfigs.cleanup_enabled = val ? 'true' : 'false' },
|
||||
})
|
||||
const cleanupEmptyTrash = computed({
|
||||
get: () => String(sysConfigs.cleanup_empty_trash) !== 'false',
|
||||
set: (val: boolean) => { sysConfigs.cleanup_empty_trash = val ? 'true' : 'false' },
|
||||
})
|
||||
const cleanupFileRetentionDays = computed({
|
||||
get: () => Number(sysConfigs.cleanup_file_retention_days ?? 7),
|
||||
set: (val: number) => { sysConfigs.cleanup_file_retention_days = val },
|
||||
})
|
||||
const cleanupLogRetentionDays = computed({
|
||||
get: () => Number(sysConfigs.cleanup_log_retention_days ?? 30),
|
||||
set: (val: number) => { sysConfigs.cleanup_log_retention_days = val },
|
||||
})
|
||||
const cleanupSpaceThresholdEnabled = computed({
|
||||
get: () => String(sysConfigs.cleanup_space_threshold_enabled) === 'true',
|
||||
set: (val: boolean) => { sysConfigs.cleanup_space_threshold_enabled = val ? 'true' : 'false' },
|
||||
})
|
||||
const cleanupSpaceThresholdPercent = computed({
|
||||
get: () => Number(sysConfigs.cleanup_space_threshold_percent ?? 90),
|
||||
set: (val: number) => { sysConfigs.cleanup_space_threshold_percent = val },
|
||||
})
|
||||
const cleanupSpaceThresholdDeletePercent = computed({
|
||||
get: () => Number(sysConfigs.cleanup_space_threshold_delete_percent ?? 10),
|
||||
set: (val: number) => { sysConfigs.cleanup_space_threshold_delete_percent = val },
|
||||
})
|
||||
const saveReuseEnabled = computed({
|
||||
get: () => String(sysConfigs.save_reuse_enabled) !== 'false',
|
||||
set: (val: boolean) => { sysConfigs.save_reuse_enabled = val ? 'true' : 'false' },
|
||||
})
|
||||
|
||||
// 白名单
|
||||
const whitelistDirs = ref<string[]>([])
|
||||
const newWhitelistDir = ref('')
|
||||
|
||||
function loadWhitelistDirs() {
|
||||
try {
|
||||
const raw = String(sysConfigs.cleanup_whitelist_dirs || '[]')
|
||||
whitelistDirs.value = JSON.parse(raw)
|
||||
} catch {
|
||||
whitelistDirs.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function addWhitelistDir() {
|
||||
const name = newWhitelistDir.value.trim()
|
||||
if (!name) return
|
||||
if (whitelistDirs.value.includes(name)) {
|
||||
ElMessage.warning('该目录已在白名单中')
|
||||
return
|
||||
}
|
||||
whitelistDirs.value.push(name)
|
||||
newWhitelistDir.value = ''
|
||||
}
|
||||
|
||||
function removeWhitelistDir(index: number) {
|
||||
whitelistDirs.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// Cookie检测间隔 + 空间校准间隔
|
||||
const verifyIntervalMinutes = computed({
|
||||
get: () => Number(sysConfigs.cleanup_verify_interval ?? 30),
|
||||
set: (val: number) => { sysConfigs.cleanup_verify_interval = val },
|
||||
})
|
||||
const storageRefreshIntervalMinutes = computed({
|
||||
get: () => Number(sysConfigs.storage_refresh_interval ?? 180),
|
||||
set: (val: number) => { sysConfigs.storage_refresh_interval = val },
|
||||
})
|
||||
|
||||
async function loadCleanupConfigs() {
|
||||
try {
|
||||
const raw = await getSystemConfigs()
|
||||
for (const cfg of raw) {
|
||||
sysConfigs[cfg.key] = cfg.value
|
||||
}
|
||||
loadWhitelistDirs()
|
||||
} catch (e) {
|
||||
console.error('加载清理配置失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveCleanupConfigs() {
|
||||
cleanupSaving.value = true
|
||||
try {
|
||||
const keys = [
|
||||
'cleanup_enabled', 'cleanup_file_retention_days', 'cleanup_log_retention_days',
|
||||
'cleanup_empty_trash',
|
||||
'cleanup_space_threshold_enabled', 'cleanup_space_threshold_percent', 'cleanup_space_threshold_delete_percent',
|
||||
'save_reuse_enabled',
|
||||
'cleanup_verify_interval', 'storage_refresh_interval',
|
||||
]
|
||||
const entries = keys.map(key => ({ key, value: String(sysConfigs[key] ?? '') }))
|
||||
entries.push({ key: 'cleanup_whitelist_dirs', value: JSON.stringify(whitelistDirs.value) })
|
||||
await updateSystemConfigs(entries)
|
||||
ElMessage.success('清理配置已保存')
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '保存失败')
|
||||
} finally {
|
||||
cleanupSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRunCleanup() {
|
||||
cleanupRunning.value = true
|
||||
try {
|
||||
const result = await runCleanup()
|
||||
if (result.success) {
|
||||
ElMessage.success(result.message)
|
||||
} else {
|
||||
ElMessage.warning(result.message)
|
||||
}
|
||||
await loadCleanupConfigs()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '清理失败')
|
||||
} finally {
|
||||
cleanupRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEmptyTrash() {
|
||||
emptyTrashRunning.value = true
|
||||
try {
|
||||
const result = await emptyAllTrash()
|
||||
if (result.success) {
|
||||
ElMessage.success(result.message)
|
||||
} else {
|
||||
ElMessage.warning(result.message)
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '清空回收站失败')
|
||||
} finally {
|
||||
emptyTrashRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadCleanupConfigs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cleanup-section .config-card {
|
||||
/* 全宽展示,没有max-width限制 */
|
||||
}
|
||||
.cleanup-section :deep(.el-card__header) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ── 2列网格布局 ── */
|
||||
.cleanup-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.cleanup-group {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 16px 18px;
|
||||
background: var(--bg-card);
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.cleanup-group:hover {
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.cleanup-group-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed var(--border-light);
|
||||
}
|
||||
.cleanup-hint {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ── 底部操作栏(跨列全宽) ── */
|
||||
.cleanup-actions {
|
||||
margin-top: 20px;
|
||||
padding: 16px 18px;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.cleanup-actions-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.cleanup-info {
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* ── 响应式:窄屏时改为单列 ── */
|
||||
@media (max-width: 900px) {
|
||||
.cleanup-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user