199 lines
6.2 KiB
Vue
199 lines
6.2 KiB
Vue
<template>
|
||
<div class="aksrt-page-container">
|
||
<PageHeader
|
||
title="更新中心"
|
||
desc="查看版本状态、检测更新并浏览更新日志与教程入口。"
|
||
>
|
||
<template #actions>
|
||
<n-space>
|
||
<n-button quaternary :loading="checking" @click="handleCheck">检测更新</n-button>
|
||
<n-button quaternary @click="go('/docs')">文档中心</n-button>
|
||
</n-space>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-spin :show="loading" style="width: 100%; min-height: 220px;">
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>版本状态</h3>
|
||
</div>
|
||
<n-space>
|
||
<n-tag round type="info">当前版本:{{ updateInfo.current_version || '0.0.0' }}</n-tag>
|
||
<n-tag round :type="updateInfo.update_available ? 'warning' : 'success'">
|
||
{{ updateInfo.update_available ? `发现新版本 ${updateInfo.remote_version}` : '已是最新版本' }}
|
||
</n-tag>
|
||
<n-tag round type="default">上次检测:{{ formatDate(updateInfo.last_check) }}</n-tag>
|
||
</n-space>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card" v-if="updateInfo.update_available">
|
||
<div class="aksrt-section-header">
|
||
<h3>可用更新</h3>
|
||
</div>
|
||
<n-alert type="warning" style="margin-bottom: 12px;">
|
||
检测到新版本 {{ updateInfo.remote_version }},当前版本为 {{ updateInfo.current_version }}。
|
||
</n-alert>
|
||
<n-space>
|
||
<n-button v-if="updateInfo.download_url" type="primary" tag="a" :href="updateInfo.download_url" target="_blank">
|
||
下载更新
|
||
</n-button>
|
||
<n-button @click="handleDismiss" :loading="dismissing">忽略此版本</n-button>
|
||
</n-space>
|
||
<div v-if="updateInfo.changelog" class="aksrt-update__changelog">
|
||
<h4>更新日志</h4>
|
||
<pre>{{ updateInfo.changelog }}</pre>
|
||
</div>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>常用教程入口</h3>
|
||
</div>
|
||
<div class="aksrt-update__grid">
|
||
<a v-for="item in guides" :key="item.key" class="aksrt-update__guide" :href="item.url" target="_blank" rel="noreferrer">
|
||
<strong>{{ item.title }}</strong>
|
||
<span>{{ item.desc }}</span>
|
||
</a>
|
||
</div>
|
||
</n-card>
|
||
</n-spin>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onMounted, 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 loading = ref(true)
|
||
const checking = ref(false)
|
||
const dismissing = ref(false)
|
||
const updateInfo = ref({
|
||
current_version: '0.0.0',
|
||
remote_version: '',
|
||
update_available: false,
|
||
download_url: '',
|
||
changelog: '',
|
||
last_check: 0,
|
||
})
|
||
|
||
const guides = [
|
||
{ key: 'rewrite', title: '伪静态配置', desc: '检查固定链接与重写规则', url: '/wp-admin/options-permalink.php' },
|
||
{ key: 'oauth', title: '第三方登录', desc: '检查 GitHub / Gitee / QQ / Google 登录', url: '#/users/oauth' },
|
||
{ key: 'backup', title: '设置快照', desc: '创建与恢复站点配置快照', url: '#/tools/backup' },
|
||
{ key: 'health', title: '健康检查', desc: '查看 PHP / WP / 数据表状态', url: '#/tools/health' },
|
||
{ key: 'meilisearch', title: 'Meilisearch', desc: '配置搜索索引与同步策略', url: '#/global/search' },
|
||
{ key: 'i18n', title: '多语言翻译', desc: '检查语言切换与术语配置', url: '#/global/i18n' },
|
||
{ key: 'cdn', title: '静态资源与 CDN', desc: '核对前端资源地址与缓存策略', url: '#/global/common' },
|
||
{ key: 'ip-geo', title: 'IP 归属地', desc: '验证 IP 查询服务与密钥配置', url: '#/extensions/ip' },
|
||
{ key: 'payment', title: '支付与会员', desc: '核对网关、会员与充值配置', url: '#/payment/gateway' },
|
||
{ key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与推广奖励', url: '#/users/credits' },
|
||
{ key: 'badge', title: '等级与徽章', desc: '维护等级、认证与徽章体系', url: '#/users/badge' },
|
||
]
|
||
|
||
onMounted(load)
|
||
|
||
function go(path) {
|
||
router.push(path)
|
||
}
|
||
|
||
function formatDate(value) {
|
||
if (!value) return '未检测'
|
||
const timestamp = Number(value)
|
||
if (!Number.isNaN(timestamp) && timestamp > 0) {
|
||
return new Date(timestamp * 1000).toLocaleString()
|
||
}
|
||
return String(value)
|
||
}
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const res = await api.getUpdateInfo()
|
||
updateInfo.value = res?.data || {}
|
||
} catch (error) {
|
||
message.error(error?.message || '加载更新状态失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleCheck() {
|
||
checking.value = true
|
||
try {
|
||
const res = await api.detectUpdate()
|
||
updateInfo.value = res?.data || {}
|
||
message.success('更新检测完成')
|
||
} catch (error) {
|
||
message.error(error?.message || '检测更新失败')
|
||
} finally {
|
||
checking.value = false
|
||
}
|
||
}
|
||
|
||
async function handleDismiss() {
|
||
if (!updateInfo.value.remote_version) return
|
||
dismissing.value = true
|
||
try {
|
||
await api.dismissUpdate(updateInfo.value.remote_version)
|
||
message.success('已忽略此版本')
|
||
await load()
|
||
} catch (error) {
|
||
message.error(error?.message || '忽略版本失败')
|
||
} finally {
|
||
dismissing.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.aksrt-update__changelog {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.aksrt-update__changelog pre {
|
||
margin: 8px 0 0;
|
||
padding: 12px 14px;
|
||
border-radius: 12px;
|
||
background: #101828;
|
||
color: #d0d7e2;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.aksrt-update__grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.aksrt-update__guide {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 14px;
|
||
border: 1px solid var(--aksrt-color-border);
|
||
border-radius: 12px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
background: #fafafa;
|
||
}
|
||
|
||
.aksrt-update__guide:hover {
|
||
border-color: var(--aksrt-color-primary);
|
||
background: #f5faff;
|
||
}
|
||
|
||
.aksrt-update__guide span {
|
||
font-size: 12px;
|
||
color: #667085;
|
||
}
|
||
</style>
|