191 lines
5.5 KiB
Vue
191 lines
5.5 KiB
Vue
<template>
|
||
<div class="aksrt-page-container">
|
||
<PageHeader
|
||
title="导入导出"
|
||
desc="导出当前设置快照、执行 dry-run 导入校验,并支持按域重置到 schema 默认值。"
|
||
>
|
||
<template #actions>
|
||
<n-space>
|
||
<n-button quaternary @click="go('/tools/backup')">数据备份</n-button>
|
||
<n-button quaternary @click="go('/tools/health')">健康检查</n-button>
|
||
</n-space>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>导出与导入</h3>
|
||
</div>
|
||
<n-space style="margin-bottom: 16px;">
|
||
<n-button :loading="exporting" @click="handleExport">导出当前配置</n-button>
|
||
<n-button :loading="importing" @click="handleDryRun">Dry Run 校验</n-button>
|
||
<n-button type="primary" :loading="importing" @click="handleImport">执行导入</n-button>
|
||
</n-space>
|
||
<n-input
|
||
v-model:value="payloadText"
|
||
type="textarea"
|
||
:autosize="{ minRows: 12, maxRows: 20 }"
|
||
placeholder="导出的 JSON 会出现在这里,也可以直接粘贴导入。"
|
||
/>
|
||
<p class="aksrt-tools-hint">
|
||
正式导入前会自动创建一份“导入前自动快照”,与“数据备份”页的快照体系共用,便于回滚。
|
||
</p>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>按域重置</h3>
|
||
</div>
|
||
<n-space align="center" wrap>
|
||
<n-select v-model:value="resetDomain" :options="domainOptions" style="width: 220px;" />
|
||
<n-button :loading="resetting" @click="handleReset">重置所选域</n-button>
|
||
<n-button type="error" tertiary :loading="resettingAll" @click="handleResetAll">重置全部</n-button>
|
||
</n-space>
|
||
<p class="aksrt-tools-hint">仅支持重置 schema 托管的业务域。健康检查和迁移状态属于运维面板,不参与按域回滚。</p>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>最近结果</h3>
|
||
</div>
|
||
<pre class="aksrt-tools-output">{{ resultText || '尚未执行操作。' }}</pre>
|
||
</n-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue'
|
||
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 exporting = ref(false)
|
||
const importing = ref(false)
|
||
const resetting = ref(false)
|
||
const resettingAll = ref(false)
|
||
const payloadText = ref('')
|
||
const resultText = ref('')
|
||
const resetDomain = ref('global')
|
||
|
||
const domainOptions = [
|
||
{ label: '全局&功能', value: 'global' },
|
||
{ label: '页面&显示', value: 'page_display' },
|
||
{ label: '文章&列表', value: 'post_list' },
|
||
{ label: '用户&互动', value: 'users' },
|
||
{ label: '支付&付费', value: 'payment' },
|
||
]
|
||
|
||
function go(path) {
|
||
router.push(path)
|
||
}
|
||
|
||
function extractData(response) {
|
||
return response?.data ?? response ?? {}
|
||
}
|
||
|
||
function parsePayload() {
|
||
try {
|
||
return JSON.parse(payloadText.value || '{}')
|
||
} catch (error) {
|
||
throw new Error('导入 JSON 格式无效')
|
||
}
|
||
}
|
||
|
||
async function handleExport() {
|
||
exporting.value = true
|
||
try {
|
||
const data = extractData(await api.exportSettings())
|
||
payloadText.value = JSON.stringify(data, null, 2)
|
||
resultText.value = `导出完成\n版本: ${data.version || 'unknown'}\n时间: ${data.exported || 'unknown'}`
|
||
message.success('导出成功')
|
||
} catch (error) {
|
||
message.error(error.message || '导出失败')
|
||
} finally {
|
||
exporting.value = false
|
||
}
|
||
}
|
||
|
||
async function handleDryRun() {
|
||
importing.value = true
|
||
try {
|
||
const payload = parsePayload()
|
||
const result = await api.importSettings({ ...payload, dry_run: true })
|
||
resultText.value = JSON.stringify(result, null, 2)
|
||
message.success('Dry Run 校验完成')
|
||
} catch (error) {
|
||
message.error(error.message || '校验失败')
|
||
} finally {
|
||
importing.value = false
|
||
}
|
||
}
|
||
|
||
async function handleImport() {
|
||
importing.value = true
|
||
try {
|
||
const payload = parsePayload()
|
||
const result = await api.importSettings(payload)
|
||
resultText.value = JSON.stringify(result, null, 2)
|
||
message.success('导入完成')
|
||
} catch (error) {
|
||
message.error(error.message || '导入失败')
|
||
} finally {
|
||
importing.value = false
|
||
}
|
||
}
|
||
|
||
async function handleReset() {
|
||
resetting.value = true
|
||
try {
|
||
const result = await api.resetDomain(resetDomain.value)
|
||
resultText.value = JSON.stringify(result, null, 2)
|
||
message.success('域设置已重置')
|
||
} catch (error) {
|
||
message.error(error.message || '重置失败')
|
||
} finally {
|
||
resetting.value = false
|
||
}
|
||
}
|
||
|
||
async function handleResetAll() {
|
||
if (!window.confirm('确认重置全部 schema 托管设置吗?系统会先自动创建一份“重置全部前快照”。')) {
|
||
return
|
||
}
|
||
|
||
resettingAll.value = true
|
||
try {
|
||
const result = await api.resetAllSettings()
|
||
resultText.value = JSON.stringify(result, null, 2)
|
||
message.success('全部设置已重置')
|
||
} catch (error) {
|
||
message.error(error.message || '重置全部失败')
|
||
} finally {
|
||
resettingAll.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.aksrt-tools-output {
|
||
margin: 0;
|
||
padding: 14px;
|
||
border-radius: 12px;
|
||
background: #101828;
|
||
color: #d0d7e2;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.aksrt-tools-hint {
|
||
margin: 12px 0 0;
|
||
color: #667085;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
</style>
|