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>
279 lines
7.5 KiB
Vue
279 lines
7.5 KiB
Vue
<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>
|