feat: 迁移自定义功能至 1.5.9.9 版本
包含音乐源限制、CI 防 OOM 优化、数据库连接池优化、管理端权限及 UI 细节定制等功能
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
<div class="backup-manager">
|
||||
<!-- 标题 -->
|
||||
<div class="header">
|
||||
<h3>{{ locale.title }}</h3>
|
||||
<p class="description">{{ locale.desc }}</p>
|
||||
<h3>数据库备份</h3>
|
||||
<p class="description">导出和导入数据库备份</p>
|
||||
</div>
|
||||
|
||||
<!-- 主要功能区 -->
|
||||
@@ -17,15 +17,15 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h4>{{ locale.exportTitle }}</h4>
|
||||
<p>{{ locale.exportDesc }}</p>
|
||||
<h4>导出备份</h4>
|
||||
<p>创建数据库备份文件</p>
|
||||
<button
|
||||
:disabled="createLoading"
|
||||
class="action-btn primary"
|
||||
@click="showCreateModal = true"
|
||||
>
|
||||
<span v-if="createLoading">{{ locale.exporting }}</span>
|
||||
<span v-else>{{ locale.startExport }}</span>
|
||||
<span v-if="createLoading">导出中...</span>
|
||||
<span v-else>开始导出</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,25 +39,25 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h4>{{ locale.importTitle }}</h4>
|
||||
<p>{{ locale.importDesc }}</p>
|
||||
<h4>导入备份</h4>
|
||||
<p>从备份文件恢复数据</p>
|
||||
<button
|
||||
:disabled="uploadLoading"
|
||||
class="action-btn secondary"
|
||||
@click="showUploadModal = true"
|
||||
>
|
||||
<span v-if="uploadLoading">{{ locale.importing }}</span>
|
||||
<span v-else>{{ locale.chooseFile }}</span>
|
||||
<span v-if="uploadLoading">导入中...</span>
|
||||
<span v-else>选择文件</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建备份模态框 -->
|
||||
<div v-if="showCreateModal" class="backupmanager-modal-overlay" @click="showCreateModal = false">
|
||||
<div v-if="showCreateModal" class="modal-overlay" @click="showCreateModal = false">
|
||||
<div class="modal" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>{{ locale.createTitle }}</h3>
|
||||
<h3>创建数据库备份</h3>
|
||||
<button class="close-btn" @click="showCreateModal = false">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<line x1="18" x2="6" y1="6" y2="18" />
|
||||
@@ -67,43 +67,43 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>{{ locale.backupType }}</label>
|
||||
<label>备份类型</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-option">
|
||||
<input v-model="createForm.tables" type="radio" value="all" >
|
||||
<span>{{ locale.fullBackup }}</span>
|
||||
<small>{{ locale.fullBackupDesc }}</small>
|
||||
<span>完整备份(推荐)</span>
|
||||
<small>备份所有数据表</small>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input v-model="createForm.tables" type="radio" value="users" >
|
||||
<span>{{ locale.userDataOnly }}</span>
|
||||
<small>{{ locale.userDataOnlyDesc }}</small>
|
||||
<span>仅用户数据</span>
|
||||
<small>只备份用户相关数据</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-option">
|
||||
<input v-model="createForm.includeSystemData" type="checkbox" >
|
||||
<span>{{ locale.includeSystemSettings }}</span>
|
||||
<small>{{ locale.includeSystemSettingsDesc }}</small>
|
||||
<span>包含系统设置</span>
|
||||
<small>包含系统配置和设置数据</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="action-btn secondary" @click="showCreateModal = false">{{ locale.cancel }}</button>
|
||||
<button class="action-btn secondary" @click="showCreateModal = false">取消</button>
|
||||
<button :disabled="createLoading" class="action-btn primary" @click="createBackup">
|
||||
<span v-if="createLoading">{{ locale.creating }}</span>
|
||||
<span v-else>{{ locale.createBackup }}</span>
|
||||
<span v-if="createLoading">创建中...</span>
|
||||
<span v-else>创建备份</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导入备份模态框 -->
|
||||
<div v-if="showUploadModal" class="backupmanager-modal-overlay" @click="showUploadModal = false">
|
||||
<div v-if="showUploadModal" class="modal-overlay" @click="showUploadModal = false">
|
||||
<div class="modal" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>{{ locale.importFileTitle }}</h3>
|
||||
<h3>导入备份文件</h3>
|
||||
<button class="close-btn" @click="showUploadModal = false">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<line x1="18" x2="6" y1="6" y2="18" />
|
||||
@@ -126,8 +126,8 @@
|
||||
<polyline points="17,8 12,3 7,8" />
|
||||
<line x1="12" x2="12" y1="3" y2="15" />
|
||||
</svg>
|
||||
<h4>{{ locale.uploadTitle }}</h4>
|
||||
<p>{{ locale.uploadDesc }}</p>
|
||||
<h4>选择或拖拽备份文件</h4>
|
||||
<p>支持 .json 格式的备份文件,最大 100MB</p>
|
||||
<input
|
||||
ref="fileInput"
|
||||
accept=".json,application/json"
|
||||
@@ -161,17 +161,17 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ locale.restoreMode }}</label>
|
||||
<label>恢复模式</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-option">
|
||||
<input v-model="restoreForm.mode" type="radio" value="merge" >
|
||||
<span>{{ locale.mergeMode }}</span>
|
||||
<small>{{ locale.mergeModeDesc }}</small>
|
||||
<span>合并模式(推荐)</span>
|
||||
<small>更新现有记录,添加新记录</small>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input v-model="restoreForm.mode" type="radio" value="replace" >
|
||||
<span>{{ locale.replaceMode }}</span>
|
||||
<small>{{ locale.replaceModeDesc }}</small>
|
||||
<span>替换模式</span>
|
||||
<small>先清空数据,然后导入备份</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,8 +179,8 @@
|
||||
<div v-if="restoreForm.mode === 'replace'" class="form-group">
|
||||
<label class="checkbox-option danger">
|
||||
<input v-model="restoreForm.clearExisting" type="checkbox" >
|
||||
<span>{{ locale.confirmClear }}</span>
|
||||
<small>{{ locale.confirmClearDesc }}</small>
|
||||
<span>我确认要清空现有数据</span>
|
||||
<small>此操作不可逆,请谨慎操作</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -190,8 +190,8 @@
|
||||
>
|
||||
<label class="checkbox-option">
|
||||
<input v-model="restoreForm.overwriteSuperAdmin" type="checkbox" >
|
||||
<span>{{ locale.overwriteSuperAdmin }}</span>
|
||||
<small>{{ locale.overwriteSuperAdminDesc }}</small>
|
||||
<span>覆盖备份中的超级管理员账号数据</span>
|
||||
<small>关闭时将保留当前超级管理员及其第三方绑定、2FA相关数据</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -204,13 +204,13 @@
|
||||
<line x1="12" x2="12.01" y1="17" y2="17" />
|
||||
</svg>
|
||||
<div>
|
||||
<h4>{{ locale.warningTitle }}</h4>
|
||||
<p>{{ locale.warningDesc }}</p>
|
||||
<h4>注意</h4>
|
||||
<p>导入备份将会影响现有数据,请确保您了解操作的后果。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="action-btn secondary" @click="showUploadModal = false">{{ locale.cancel }}</button>
|
||||
<button class="action-btn secondary" @click="showUploadModal = false">取消</button>
|
||||
<button
|
||||
:disabled="
|
||||
!selectedFile ||
|
||||
@@ -220,8 +220,8 @@
|
||||
class="action-btn primary"
|
||||
@click="uploadFile"
|
||||
>
|
||||
<span v-if="uploadLoading">{{ locale.importing }}</span>
|
||||
<span v-else>{{ locale.startImport }}</span>
|
||||
<span v-if="uploadLoading">导入中...</span>
|
||||
<span v-else>开始导入</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -230,13 +230,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { useAuth } from '~/composables/useAuth'
|
||||
import { useLocale } from '~/utils/locale'
|
||||
|
||||
const { admin } = useLocale()
|
||||
const locale = computed(() => admin.value?.backupManager || {})
|
||||
const { msg: getLocaleText } = useLocaleText(locale)
|
||||
|
||||
// 响应式数据
|
||||
const createLoading = ref(false)
|
||||
@@ -310,14 +305,14 @@ const createBackup = async () => {
|
||||
// 显示成功消息
|
||||
const sizeText = backup.size ? ` (${formatFileSize(backup.size)})` : ''
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('downloaded', backup.filename, sizeText), 'success')
|
||||
window.$showNotification(`备份文件已下载: ${backup.filename}${sizeText}`, 'success')
|
||||
}
|
||||
|
||||
console.log('✅ 文件下载成功:', backup.filename)
|
||||
} catch (downloadError) {
|
||||
console.error('下载失败:', downloadError)
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('fileDownloadFailed', downloadError.message), 'error')
|
||||
window.$showNotification('文件下载失败: ' + downloadError.message, 'error')
|
||||
}
|
||||
}
|
||||
} else if (backup.downloadMode === 'file' && backup.filename) {
|
||||
@@ -353,20 +348,20 @@ const createBackup = async () => {
|
||||
// 显示成功消息
|
||||
const sizeText = backup.size ? ` (${formatFileSize(backup.size)})` : ''
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('downloaded', backup.filename, sizeText), 'success')
|
||||
window.$showNotification(`备份文件已下载: ${backup.filename}${sizeText}`, 'success')
|
||||
}
|
||||
|
||||
console.log('✅ 文件下载成功:', backup.filename)
|
||||
} catch (downloadError) {
|
||||
console.error('文件下载失败:', downloadError)
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('fileDownloadFailed', downloadError.message), 'error')
|
||||
window.$showNotification('文件下载失败: ' + downloadError.message, 'error')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('无效的下载模式或缺少数据')
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(locale.value?.invalidResponse || '服务器返回了无效响应', 'error')
|
||||
window.$showNotification('备份创建失败:无效的响应格式', 'error')
|
||||
}
|
||||
showCreateModal.value = false
|
||||
return
|
||||
@@ -374,16 +369,16 @@ const createBackup = async () => {
|
||||
} else {
|
||||
console.error('服务器响应格式错误:', response)
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(locale.value?.serverResponseInvalid || '服务器响应格式无效', 'error')
|
||||
window.$showNotification('备份创建失败:服务器响应格式错误', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
showCreateModal.value = false
|
||||
} catch (error) {
|
||||
console.error('创建备份失败:', error)
|
||||
const errorMessage = error.data?.message || error.message || locale.value?.unknownError || '未知错误'
|
||||
const errorMessage = error.data?.message || error.message || '未知错误'
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('createFailed', errorMessage), 'error')
|
||||
window.$showNotification('创建备份失败: ' + errorMessage, 'error')
|
||||
}
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
@@ -404,7 +399,7 @@ const parseBackupSuperAdmin = async (file) => {
|
||||
hasSuperAdminInBackup.value = false
|
||||
restoreForm.value.overwriteSuperAdmin = false
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(locale.value?.parseFailed || '备份文件解析失败', 'error')
|
||||
window.$showNotification('无法解析备份文件,请检查文件格式是否正确。', 'error')
|
||||
}
|
||||
console.error('解析备份文件失败:', error)
|
||||
}
|
||||
@@ -463,7 +458,7 @@ const uploadFile = async () => {
|
||||
// 显示成功通知
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(
|
||||
getLocaleText('restoreSuccess', response.details?.tablesProcessed || 0, response.details?.recordsRestored || 0),
|
||||
`备份恢复成功!处理了 ${response.details?.tablesProcessed || 0} 个表,恢复了 ${response.details?.recordsRestored || 0} 条记录`,
|
||||
'success'
|
||||
)
|
||||
|
||||
@@ -471,7 +466,7 @@ const uploadFile = async () => {
|
||||
if (response.details?.errors && response.details.errors.length > 0) {
|
||||
setTimeout(() => {
|
||||
window.$showNotification(
|
||||
getLocaleText('restoreWarning', response.details.errors.length),
|
||||
`恢复过程中发生了 ${response.details.errors.length} 个错误`,
|
||||
'warning'
|
||||
)
|
||||
}, 1000)
|
||||
@@ -479,7 +474,7 @@ const uploadFile = async () => {
|
||||
|
||||
// 显示即将重定向的通知
|
||||
setTimeout(() => {
|
||||
window.$showNotification(locale.value?.restoreCompleteRedirect || '恢复完成,即将刷新页面', 'info')
|
||||
window.$showNotification('数据库恢复完成,3秒后将返回首页重新登录', 'info')
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
@@ -498,13 +493,13 @@ const uploadFile = async () => {
|
||||
}, 5000)
|
||||
} else {
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('importFailed', response.message || locale.value.unknownError), 'error')
|
||||
window.$showNotification('备份导入失败: ' + (response.message || '未知错误'), 'error')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导入备份失败:', error)
|
||||
if (window.$showNotification) {
|
||||
window.$showNotification(getLocaleText('importFailed', error.data?.message || error.message), 'error')
|
||||
window.$showNotification('导入备份失败: ' + (error.data?.message || error.message), 'error')
|
||||
}
|
||||
} finally {
|
||||
uploadLoading.value = false
|
||||
@@ -525,8 +520,8 @@ const formatFileSize = (bytes) => {
|
||||
/* 主容器 */
|
||||
.backup-manager {
|
||||
min-height: 100vh;
|
||||
background: var(--panel-bg-deep);
|
||||
color: var(--panel-border-light);
|
||||
background: var(--bg-primary, #1a1a1a);
|
||||
color: var(--text-primary, #e5e5e5);
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
@@ -540,13 +535,13 @@ const formatFileSize = (bytes) => {
|
||||
.header h3 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.header .description {
|
||||
font-size: 1.125rem;
|
||||
color: var(--text-muted-dark);
|
||||
color: var(--text-secondary, #a0a0a0);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -561,8 +556,8 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
/* 操作卡片 */
|
||||
.action-card {
|
||||
background: var(--panel-border);
|
||||
border: 1px solid var(--panel-border-dark);
|
||||
background: var(--bg-tertiary, #2a2a2a);
|
||||
border: 1px solid var(--border-secondary, #404040);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
@@ -570,8 +565,8 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
border-color: var(--panel-border-dark);
|
||||
background: var(--panel-border-subtle);
|
||||
border-color: #606060;
|
||||
background: var(--bg-tertiary, #2f2f2f);
|
||||
}
|
||||
|
||||
.action-card .card-content {
|
||||
@@ -587,12 +582,12 @@ const formatFileSize = (bytes) => {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
margin: 0 auto 1.5rem;
|
||||
background: var(--panel-border-dark);
|
||||
background: #404040;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.action-icon svg {
|
||||
@@ -604,12 +599,12 @@ const formatFileSize = (bytes) => {
|
||||
.action-card h4 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 0 0 0.75rem 0;
|
||||
}
|
||||
|
||||
.action-card p {
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
font-size: 1rem;
|
||||
margin: 0 0 2rem 0;
|
||||
line-height: 1.6;
|
||||
@@ -634,24 +629,24 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: var(--color-accent);
|
||||
background: #4a90e2;
|
||||
}
|
||||
|
||||
.action-btn.primary:hover {
|
||||
background: var(--color-accent);
|
||||
background: #357abd;
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background: var(--panel-border-dark);
|
||||
background: #606060;
|
||||
}
|
||||
|
||||
.action-btn.secondary:hover {
|
||||
background: var(--panel-border-dark);
|
||||
background: #505050;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
background: var(--panel-border-dark) !important;
|
||||
color: var(--text-muted) !important;
|
||||
background: #404040 !important;
|
||||
color: #808080 !important;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
@@ -662,13 +657,13 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
/* 模态框样式 */
|
||||
.backupmanager-modal-overlay {
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--modal-overlay);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -677,8 +672,8 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: var(--panel-border);
|
||||
border: 1px solid var(--panel-border-dark);
|
||||
background: var(--bg-tertiary, #2a2a2a);
|
||||
border: 1px solid var(--border-secondary, #404040);
|
||||
border-radius: 12px;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
@@ -696,19 +691,19 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
.current-status h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: var(--color-accent);
|
||||
color: #4a90e2;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.progress-stats p {
|
||||
margin: 5px 0;
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--panel-border-dark);
|
||||
background: #404040;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
overflow: hidden;
|
||||
@@ -716,7 +711,7 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--color-accent), var(--color-accent));
|
||||
background: linear-gradient(90deg, #4a90e2, #357abd);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
@@ -729,7 +724,7 @@ const formatFileSize = (bytes) => {
|
||||
.progress-details h5,
|
||||
.progress-errors h5 {
|
||||
margin: 0 0 10px 0;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -738,17 +733,17 @@ const formatFileSize = (bytes) => {
|
||||
.errors-list {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--panel-border-dark);
|
||||
border: 1px solid #505050;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
background: var(--text-primary);
|
||||
background: var(--bg-quaternary, #333333);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
padding: 4px 0;
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid var(--panel-border-dark);
|
||||
border-bottom: 1px solid var(--border-secondary, #404040);
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
@@ -757,9 +752,9 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
.error-item {
|
||||
padding: 4px 0;
|
||||
color: var(--color-error-light);
|
||||
color: #ff6b6b;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid var(--panel-bg-darker);
|
||||
border-bottom: 1px solid var(--border-secondary, #4a2a2a);
|
||||
}
|
||||
|
||||
.error-item:last-child {
|
||||
@@ -777,14 +772,14 @@ const formatFileSize = (bytes) => {
|
||||
.modal-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: var(--panel-border-dark);
|
||||
background: #404040;
|
||||
border: none;
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
cursor: pointer;
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
@@ -792,8 +787,8 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: var(--panel-border-dark);
|
||||
color: var(--text-primary);
|
||||
background: #505050;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.close-btn svg {
|
||||
@@ -807,11 +802,11 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
.modal-footer {
|
||||
padding: 1.5rem 2rem;
|
||||
border-top: 1px solid var(--panel-border-dark);
|
||||
border-top: 1px solid var(--border-secondary, #404040);
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
background: var(--panel-border-subtle);
|
||||
background: var(--bg-tertiary, #252525);
|
||||
}
|
||||
|
||||
.modal-footer button {
|
||||
@@ -827,7 +822,7 @@ const formatFileSize = (bytes) => {
|
||||
display: block;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -843,8 +838,8 @@ const formatFileSize = (bytes) => {
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem;
|
||||
background: var(--text-primary);
|
||||
border: 1px solid var(--panel-border-dark);
|
||||
background: var(--bg-quaternary, #333333);
|
||||
border: 1px solid #505050;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
@@ -852,8 +847,8 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
.radio-option:hover,
|
||||
.checkbox-option:hover {
|
||||
border-color: var(--panel-border-dark);
|
||||
background: var(--panel-border-dark);
|
||||
border-color: #606060;
|
||||
background: #383838;
|
||||
}
|
||||
|
||||
.radio-option input[type='radio'],
|
||||
@@ -861,7 +856,7 @@ const formatFileSize = (bytes) => {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin: 0;
|
||||
accent-color: var(--color-accent);
|
||||
accent-color: #4a90e2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -875,7 +870,7 @@ const formatFileSize = (bytes) => {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
@@ -883,21 +878,21 @@ const formatFileSize = (bytes) => {
|
||||
.checkbox-option small {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.checkbox-option.danger {
|
||||
border-color: var(--color-error-hover);
|
||||
background: var(--backup-manager-danger-bg);
|
||||
border-color: #d32f2f;
|
||||
background: rgba(211, 47, 47, 0.1);
|
||||
}
|
||||
|
||||
.checkbox-option.danger span {
|
||||
color: var(--color-error-light);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.checkbox-option.danger small {
|
||||
color: var(--color-error-light);
|
||||
color: #ff8a80;
|
||||
}
|
||||
|
||||
/* 文件上传区域 */
|
||||
@@ -906,37 +901,37 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed var(--panel-border-dark);
|
||||
border: 2px dashed #505050;
|
||||
border-radius: 8px;
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--text-primary);
|
||||
background: var(--bg-quaternary, #333333);
|
||||
}
|
||||
|
||||
.upload-area:hover,
|
||||
.upload-area.drag-over {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--panel-border-dark);
|
||||
border-color: var(--border-secondary, #4a90e2);
|
||||
background: #383838;
|
||||
}
|
||||
|
||||
.upload-area svg {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
color: var(--color-accent);
|
||||
color: #4a90e2;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.upload-area h4 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.upload-area p {
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -946,8 +941,8 @@ const formatFileSize = (bytes) => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.25rem;
|
||||
background: var(--text-primary);
|
||||
border: 1px solid var(--panel-border-dark);
|
||||
background: var(--bg-quaternary, #333333);
|
||||
border: 1px solid #505050;
|
||||
border-radius: 8px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
@@ -962,7 +957,7 @@ const formatFileSize = (bytes) => {
|
||||
.file-info svg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--color-accent);
|
||||
color: #4a90e2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -974,28 +969,28 @@ const formatFileSize = (bytes) => {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted-dark);
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
.remove-file-btn {
|
||||
background: var(--backup-manager-file-remove-bg);
|
||||
background: rgba(211, 47, 47, 0.2);
|
||||
border: none;
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: var(--color-error-light);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.remove-file-btn:hover {
|
||||
background: var(--backup-manager-file-remove-bg-hover);
|
||||
background: rgba(211, 47, 47, 0.3);
|
||||
}
|
||||
|
||||
.remove-file-btn svg {
|
||||
@@ -1005,8 +1000,8 @@ const formatFileSize = (bytes) => {
|
||||
|
||||
/* 警告框 */
|
||||
.warning-box {
|
||||
background: var(--backup-manager-warning-box-bg);
|
||||
border: 1px solid var(--color-orange);
|
||||
background: rgba(255, 152, 0, 0.1);
|
||||
border: 1px solid #ff9800;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
@@ -1018,7 +1013,7 @@ const formatFileSize = (bytes) => {
|
||||
.warning-box svg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--color-orange);
|
||||
color: #ff9800;
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
@@ -1028,14 +1023,14 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.warning-box h4 {
|
||||
color: var(--color-orange);
|
||||
color: #ff9800;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.warning-box p {
|
||||
color: var(--color-orange);
|
||||
color: #ffb74d;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
@@ -1050,7 +1045,7 @@ const formatFileSize = (bytes) => {
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 12px var(--mask-30);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1001;
|
||||
animation: slideInRight 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
@@ -1061,23 +1056,23 @@ const formatFileSize = (bytes) => {
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background: var(--color-success);
|
||||
border: 1px solid var(--color-success);
|
||||
background: var(--bg-tertiary, #2e7d32);
|
||||
border: 1px solid var(--border-secondary, #4caf50);
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background: var(--color-error-hover);
|
||||
border: 1px solid var(--color-error);
|
||||
background: #d32f2f;
|
||||
border: 1px solid #f44336;
|
||||
}
|
||||
|
||||
.notification.warning {
|
||||
background: var(--color-orange);
|
||||
border: 1px solid var(--color-orange);
|
||||
background: #f57c00;
|
||||
border: 1px solid #ff9800;
|
||||
}
|
||||
|
||||
.notification.info {
|
||||
background: var(--color-accent-hover);
|
||||
border: 1px solid var(--color-accent);
|
||||
background: var(--bg-secondary, #1976d2);
|
||||
border: 1px solid var(--border-secondary, #2196f3);
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
|
||||
Reference in New Issue
Block a user