Files
wordpress-theme-aksrt/assets/admin/src/pages/tools/ToolsBackup.vue
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

279 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="jio-page-container">
<PageHeader
title="数据备份"
desc="管理设置快照、创建手动备份,并执行恢复或删除。"
>
<template #actions>
<n-space>
<n-button quaternary @click="go('/tools/import-export')">导入导出</n-button>
</n-space>
</template>
</PageHeader>
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
<n-card class="jio-section-card">
<div class="jio-section-header">
<h3>备份策略</h3>
</div>
<n-space vertical size="small" style="width: 100%;">
<n-space>
<n-tag round :type="settings.backup_auto ? 'success' : 'default'">
自动备份:{{ settings.backup_auto ? '已开启' : '未开启' }}
</n-tag>
<n-tag round type="info">频率:{{ frequencyLabel }}</n-tag>
<n-tag round type="info">保留:{{ settings.backup_retain }} 份</n-tag>
<n-tag round :type="backups.length ? 'success' : 'default'">现有快照:{{ backups.length }} 份</n-tag>
</n-space>
<n-space align="center" wrap>
<span class="jio-backup__label">自动备份</span>
<n-switch v-model:value="settings.backup_auto" />
<span class="jio-backup__label">频率</span>
<n-select v-model:value="settings.backup_frequency" :options="frequencyOptions" style="width: 140px;" />
<span class="jio-backup__label">保留</span>
<n-input-number v-model:value="settings.backup_retain" :min="3" :max="20" style="width: 140px;" />
<n-button type="primary" :loading="savingPolicy" @click="handleSavePolicy">保存策略</n-button>
</n-space>
</n-space>
</n-card>
<n-card class="jio-section-card">
<div class="jio-section-header">
<h3>手动创建快照</h3>
</div>
<n-space vertical size="small" style="width: 100%;">
<n-input v-model:value="backupLabel" placeholder="可选:输入本次快照备注,如“升级前备份”" />
<n-space>
<n-button type="primary" :loading="creating" @click="handleCreateBackup">立即创建快照</n-button>
<n-button :loading="loading" @click="load">刷新列表</n-button>
</n-space>
</n-space>
</n-card>
<n-card class="jio-section-card">
<div class="jio-section-header">
<h3>快照列表</h3>
</div>
<n-empty v-if="!backups.length" description="暂无设置快照" />
<n-data-table
v-else
:columns="columns"
:data="backups"
:bordered="true"
size="small"
:single-line="false"
:scroll-x="900"
/>
<p class="jio-backup__hint">
恢复非当前网站 / 非当前版本的快照可能导致配置异常,恢复前请确认站点环境一致。
</p>
</n-card>
</n-spin>
</div>
</template>
<script setup>
import { computed, h, onMounted, ref } from 'vue'
import { NButton, NSpace, NTag } from 'naive-ui'
import { useRouter } from 'vue-router'
import { useMessage } from 'naive-ui'
import api from '@/api'
import PageHeader from '@/components/PageHeader.vue'
const router = useRouter()
const message = useMessage()
const loading = ref(true)
const creating = ref(false)
const savingPolicy = ref(false)
const backupLabel = ref('')
const backups = ref([])
const settings = ref({
backup_auto: false,
backup_frequency: 'weekly',
backup_retain: 7,
})
const frequencyLabelMap = {
daily: '每天',
weekly: '每周',
monthly: '每月',
}
const frequencyOptions = [
{ label: '每天', value: 'daily' },
{ label: '每周', value: 'weekly' },
{ label: '每月', value: 'monthly' },
]
const frequencyLabel = computed(() => frequencyLabelMap[settings.value.backup_frequency] || '每周')
const columns = [
{
title: 'ID',
key: 'id',
width: 100,
},
{
title: '类型',
key: 'type',
width: 120,
render: (row) => h(NTag, { size: 'small', round: true, type: tagType(row.type) }, () => typeLabel(row.type)),
},
{
title: '名称',
key: 'label',
minWidth: 220,
},
{
title: '创建时间',
key: 'created_at',
width: 180,
},
{
title: '操作',
key: 'actions',
width: 180,
render: (row) => h(NSpace, { size: 'small' }, () => [
h(NButton, {
size: 'tiny',
type: 'primary',
quaternary: true,
onClick: () => handleRestore(row),
}, () => '恢复'),
h(NButton, {
size: 'tiny',
type: 'error',
quaternary: true,
onClick: () => handleDelete(row),
}, () => '删除'),
]),
},
]
onMounted(load)
function go(path) {
router.push(path)
}
function typeLabel(type) {
return {
manual: '手动备份',
scheduled: '定期备份',
reset_all: '重置全部',
reset_domain: '重置分区',
update_theme: '主题更新',
}[type] || type
}
function tagType(type) {
return {
manual: 'info',
scheduled: 'success',
reset_all: 'warning',
reset_domain: 'warning',
update_theme: 'error',
}[type] || 'default'
}
async function load() {
loading.value = true
try {
const [schemaRes, backupRes] = await Promise.all([
api.getSchemaPage('tools', 'backup'),
api.listSettingBackups(),
])
const schemaData = schemaRes?.data || {}
settings.value = {
backup_auto: !!schemaData.backup_auto,
backup_frequency: schemaData.backup_frequency || 'weekly',
backup_retain: Number(schemaData.backup_retain || 7),
}
backups.value = backupRes?.data?.items || []
} catch (error) {
message.error(error?.message || '加载备份列表失败')
} finally {
loading.value = false
}
}
async function handleCreateBackup() {
creating.value = true
try {
await api.createSettingBackup({
type: 'manual',
label: backupLabel.value,
})
backupLabel.value = ''
message.success('设置快照已创建')
await load()
} catch (error) {
message.error(error?.message || '创建快照失败')
} finally {
creating.value = false
}
}
async function handleSavePolicy() {
savingPolicy.value = true
try {
await api.saveSchemaPage('tools', 'backup', {
backup_auto: !!settings.value.backup_auto,
backup_frequency: settings.value.backup_frequency || 'weekly',
backup_retain: Math.min(20, Math.max(3, Number(settings.value.backup_retain || 7))),
})
message.success('备份策略已保存')
await load()
} catch (error) {
message.error(error?.message || '保存备份策略失败')
} finally {
savingPolicy.value = false
}
}
async function handleRestore(row) {
if (!window.confirm(`确认恢复快照「${row.label}」吗?此操作会覆盖当前设置。`)) {
return
}
try {
await api.restoreSettingBackup(row.id)
message.success('设置已恢复')
await load()
} catch (error) {
message.error(error?.message || '恢复快照失败')
}
}
async function handleDelete(row) {
if (!window.confirm(`确认删除快照「${row.label}」吗?`)) {
return
}
try {
await api.deleteSettingBackup(row.id)
message.success('快照已删除')
await load()
} catch (error) {
message.error(error?.message || '删除快照失败')
}
}
</script>
<style scoped>
.jio-backup__hint {
margin: 12px 0 0;
color: #667085;
font-size: 12px;
line-height: 1.7;
}
.jio-backup__label {
color: #344054;
font-size: 12px;
}
</style>