Files
FileCollect/pages/admin-center/system-settings/data-backup.vue
T
2025-10-22 08:19:00 +08:00

492 lines
16 KiB
Vue

<template>
<view class="data-backup">
<!-- 顶部导航栏 -->
<view class="nav-bar">
<view class="nav-back" @click="navigateBack">
<u-icon name="arrow-left" size="24" color="#606266"></u-icon>
</view>
<view class="nav-title">数据备份</view>
<view class="nav-right">
<u-button type="primary" size="small" @click="createBackup" :disabled="isBackingUp">
{{ isBackingUp ? '备份中...' : '立即备份' }}
</u-button>
</view>
</view>
<!-- 备份设置 -->
<view class="backup-settings">
<u-cell-group>
<u-cell
title="自动备份频率"
:right-text="`${autoBackupFrequency}小时`"
is-link
@click="showFrequencyModal = true"
></u-cell>
<u-cell
title="保留备份数量"
:right-text="`${maxBackups}个`"
is-link
@click="showMaxBackupsModal = true"
></u-cell>
<u-cell
title="备份存储位置"
:right-text="backupStorageLocation"
is-link
@click="selectStorageLocation"
></u-cell>
</u-cell-group>
</view>
<!-- 备份列表 -->
<view class="backup-list">
<view class="section-header">
<text class="section-title">历史备份</text>
<u-button type="text" @click="cleanOldBackups">清理旧备份</u-button>
</view>
<u-list v-if="backups.length > 0" :load-more="loadMore" @loadmore="onLoadMore">
<u-list-item v-for="backup in paginatedBackups" :key="backup.id" :border="true">
<view class="backup-item">
<view class="backup-header">
<view class="backup-name">{{ backup.name }}</view>
<view class="backup-time">{{ formatTime(backup.timestamp) }}</view>
</view>
<view class="backup-info">
<text class="backup-size">大小: {{ backup.size }}</text>
<text class="backup-type" :class="backup.type">类型: {{ backup.type === 'auto' ? '自动' : '手动' }}</text>
</view>
<view class="backup-actions">
<u-button size="mini" type="primary" @click="downloadBackup(backup.id)">下载</u-button>
<u-button size="mini" type="success" @click="restoreBackup(backup.id)">恢复</u-button>
<u-button size="mini" type="error" @click="deleteBackup(backup.id)">删除</u-button>
</view>
</view>
</u-list-item>
</u-list>
<u-empty v-else text="暂无备份记录"></u-empty>
</view>
<!-- 自动备份频率选择弹窗 -->
<u-popup v-model="showFrequencyModal" mode="bottom" :round="true" :closeable="true">
<view class="modal-content">
<view class="modal-title">选择自动备份频率</view>
<view class="picker-options">
<u-button
v-for="option in frequencyOptions"
:key="option.value"
:type="autoBackupFrequency === option.value ? 'primary' : 'default'"
style="margin-bottom: 20rpx;"
@click="selectFrequency(option.value)"
>
{{ option.label }}
</u-button>
</view>
<view class="modal-buttons">
<u-button @click="showFrequencyModal = false">取消</u-button>
</view>
</view>
</u-popup>
<!-- 保留备份数量选择弹窗 -->
<u-popup v-model="showMaxBackupsModal" mode="bottom" :round="true" :closeable="true">
<view class="modal-content">
<view class="modal-title">选择保留备份数量</view>
<view class="picker-options">
<u-button
v-for="option in maxBackupsOptions"
:key="option.value"
:type="maxBackups === option.value ? 'primary' : 'default'"
style="margin-bottom: 20rpx;"
@click="selectMaxBackups(option.value)"
>
{{ option.label }}
</u-button>
</view>
<view class="modal-buttons">
<u-button @click="showMaxBackupsModal = false">取消</u-button>
</view>
</view>
</u-popup>
<!-- 备份进度弹窗 -->
<u-popup v-model="showProgressModal" mode="center" :round="true" :closeable="false" :mask-close-able="false">
<view class="progress-modal-content">
<view class="progress-title">正在备份数据</view>
<u-progress :percentage="backupProgress" :show-text="true" bar-height="10" active></u-progress>
<view class="progress-text">{{ backupProgressText }}</view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
isBackingUp: false,
backupProgress: 0,
backupProgressText: '准备备份...',
showProgressModal: false,
showFrequencyModal: false,
showMaxBackupsModal: false,
autoBackupFrequency: 24, // 默认24小时
maxBackups: 10, // 默认保留10个备份
backupStorageLocation: '本地存储',
currentPage: 1,
pageSize: 10,
loadMore: {
loading: false,
finished: false,
error: false
},
frequencyOptions: [
{ label: '6小时', value: 6 },
{ label: '12小时', value: 12 },
{ label: '24小时', value: 24 },
{ label: '48小时', value: 48 },
{ label: '72小时', value: 72 },
{ label: '关闭自动备份', value: 0 }
],
maxBackupsOptions: [
{ label: '5个', value: 5 },
{ label: '10个', value: 10 },
{ label: '20个', value: 20 },
{ label: '50个', value: 50 }
],
backups: [
{ id: 1, name: 'backup_20230801_120000', timestamp: 1690843200000, size: '12.5 MB', type: 'auto' },
{ id: 2, name: 'backup_20230802_120000', timestamp: 1690929600000, size: '13.2 MB', type: 'auto' },
{ id: 3, name: 'backup_20230803_103000', timestamp: 1691005800000, size: '14.1 MB', type: 'manual' },
{ id: 4, name: 'backup_20230804_120000', timestamp: 1691092200000, size: '14.3 MB', type: 'auto' },
{ id: 5, name: 'backup_20230805_120000', timestamp: 1691178600000, size: '14.8 MB', type: 'auto' },
{ id: 6, name: 'backup_20230806_120000', timestamp: 1691265000000, size: '15.2 MB', type: 'auto' },
{ id: 7, name: 'backup_20230807_091500', timestamp: 1691336100000, size: '15.5 MB', type: 'manual' },
{ id: 8, name: 'backup_20230808_120000', timestamp: 1691434200000, size: '16.1 MB', type: 'auto' },
{ id: 9, name: 'backup_20230809_120000', timestamp: 1691520600000, size: '16.5 MB', type: 'auto' },
{ id: 10, name: 'backup_20230810_120000', timestamp: 1691607000000, size: '17.0 MB', type: 'auto' },
{ id: 11, name: 'backup_20230811_120000', timestamp: 1691693400000, size: '17.3 MB', type: 'auto' },
{ id: 12, name: 'backup_20230812_153000', timestamp: 1691779800000, size: '17.8 MB', type: 'manual' }
]
}
},
computed: {
paginatedBackups() {
const start = (this.currentPage - 1) * this.pageSize
const end = start + this.pageSize
return this.backups.sort((a, b) => b.timestamp - a.timestamp).slice(start, end)
}
},
methods: {
navigateBack() {
uni.navigateBack()
},
formatTime(timestamp) {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hour = date.getHours().toString().padStart(2, '0')
const minute = date.getMinutes().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
},
createBackup() {
this.isBackingUp = true
this.backupProgress = 0
this.backupProgressText = '准备备份...'
this.showProgressModal = true
// 模拟备份进度
let progress = 0
const interval = setInterval(() => {
progress += 5
this.backupProgress = progress
if (progress <= 30) {
this.backupProgressText = '正在收集数据...'
} else if (progress <= 60) {
this.backupProgressText = '正在压缩数据...'
} else if (progress <= 90) {
this.backupProgressText = '正在保存备份...'
} else {
this.backupProgressText = '备份完成!'
}
if (progress >= 100) {
clearInterval(interval)
setTimeout(() => {
this.isBackingUp = false
this.showProgressModal = false
// 添加新备份到列表
const now = new Date()
const newBackup = {
id: this.backups.length + 1,
name: `backup_${now.getFullYear()}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}_${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}${now.getSeconds().toString().padStart(2, '0')}`,
timestamp: now.getTime(),
size: `${(Math.random() * 5 + 15).toFixed(1)} MB`,
type: 'manual'
}
this.backups.unshift(newBackup)
this.currentPage = 1
this.updateLoadMoreStatus()
uni.showToast({ title: '备份成功', icon: 'success' })
}, 1000)
}
}, 200)
},
downloadBackup(id) {
uni.showLoading({ title: '正在下载...' })
// 模拟下载
setTimeout(() => {
uni.hideLoading()
const backup = this.backups.find(b => b.id === id)
uni.showToast({
title: `备份文件 ${backup.name} 已下载`,
icon: 'success'
})
}, 1500)
},
restoreBackup(id) {
uni.showModal({
title: '确认恢复',
content: '恢复数据将覆盖当前系统数据,是否继续?',
success: (res) => {
if (res.confirm) {
uni.showLoading({ title: '正在恢复...' })
// 模拟恢复
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '数据恢复成功',
icon: 'success'
})
}, 2000)
}
}
})
},
deleteBackup(id) {
uni.showModal({
title: '确认删除',
content: '确定要删除此备份文件吗?',
success: (res) => {
if (res.confirm) {
this.backups = this.backups.filter(backup => backup.id !== id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
this.updateLoadMoreStatus()
}
}
})
},
cleanOldBackups() {
uni.showModal({
title: '清理旧备份',
content: `系统将只保留最近 ${this.maxBackups} 个备份文件,是否继续?`,
success: (res) => {
if (res.confirm) {
// 模拟清理
setTimeout(() => {
const sortedBackups = this.backups.sort((a, b) => b.timestamp - a.timestamp)
this.backups = sortedBackups.slice(0, this.maxBackups)
uni.showToast({
title: '清理完成',
icon: 'success'
})
this.currentPage = 1
this.updateLoadMoreStatus()
}, 1000)
}
}
})
},
selectFrequency(value) {
this.autoBackupFrequency = value
this.showFrequencyModal = false
uni.showToast({
title: `自动备份频率已设置为${value === 0 ? '关闭' : value + '小时'}`,
icon: 'success'
})
},
selectMaxBackups(value) {
this.maxBackups = value
this.showMaxBackupsModal = false
uni.showToast({
title: `保留备份数量已设置为${value}个`,
icon: 'success'
})
},
selectStorageLocation() {
// 模拟选择存储位置
uni.showActionSheet({
itemList: ['本地存储', '云存储'],
success: (res) => {
this.backupStorageLocation = res.tapIndex === 0 ? '本地存储' : '云存储'
uni.showToast({
title: `存储位置已设置为${this.backupStorageLocation}`,
icon: 'success'
})
}
})
},
onLoadMore() {
if (this.loadMore.loading) return
this.loadMore.loading = true
// 模拟加载更多数据
setTimeout(() => {
this.currentPage++
this.loadMore.loading = false
this.updateLoadMoreStatus()
}, 1000)
},
updateLoadMoreStatus() {
const hasMore = this.paginatedBackups.length < this.backups.length
this.loadMore.finished = !hasMore
}
}
}
</script>
<style lang="scss">
.data-backup {
padding-bottom: 20rpx;
background-color: #f5f5f5;
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 30rpx;
background-color: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.nav-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
}
.backup-settings {
margin-top: 20rpx;
}
.backup-list {
margin-top: 20rpx;
background-color: #fff;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1px solid #f0f0f0;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.backup-item {
padding: 20rpx;
.backup-header {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
.backup-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.backup-time {
font-size: 28rpx;
color: #999;
}
}
.backup-info {
display: flex;
gap: 30rpx;
margin-bottom: 20rpx;
.backup-size {
font-size: 28rpx;
color: #666;
}
.backup-type {
font-size: 28rpx;
}
.backup-type.auto {
color: #409eff;
}
.backup-type.manual {
color: #67c23a;
}
}
.backup-actions {
display: flex;
justify-content: flex-end;
gap: 10rpx;
}
}
}
.modal-content {
padding: 30rpx;
.modal-title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
color: #333;
}
.picker-options {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
margin-bottom: 30rpx;
}
.modal-buttons {
margin-top: 20rpx;
}
}
.progress-modal-content {
padding: 40rpx;
text-align: center;
.progress-title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 30rpx;
color: #333;
}
.progress-text {
margin-top: 20rpx;
font-size: 30rpx;
color: #666;
}
}
}
</style>