271 lines
9.0 KiB
Vue
271 lines
9.0 KiB
Vue
<template>
|
|
<div class="aksrt-page-container">
|
|
<PageHeader title="系统工具" desc="额外运维工具与扩展能力。" />
|
|
|
|
<!-- Health Overview -->
|
|
<n-card title="系统健康" size="small" class="aksrt-tools__section">
|
|
<n-grid :cols="4" :x-gap="12" :y-gap="12">
|
|
<n-grid-item v-for="item in healthItems" :key="item.key">
|
|
<div class="aksrt-tools__health-card" :class="'aksrt-tools__health-card--' + item.status">
|
|
<FaIcon :icon="item.icon" :size="18" :color="item.color" />
|
|
<div>
|
|
<strong>{{ item.value }}</strong>
|
|
<p>{{ item.label }}</p>
|
|
</div>
|
|
</div>
|
|
</n-grid-item>
|
|
</n-grid>
|
|
</n-card>
|
|
|
|
<!-- Tool Categories -->
|
|
<n-card title="工具集合" size="small" class="aksrt-tools__section">
|
|
<n-grid :cols="3" :x-gap="12" :y-gap="12">
|
|
<n-grid-item v-for="tool in tools" :key="tool.key">
|
|
<div class="aksrt-tools__tool-card" @click="navigateTo(tool.route)">
|
|
<div class="aksrt-tools__tool-icon" :style="{ background: tool.bg }">
|
|
<FaIcon :icon="tool.icon" :size="18" color="#fff" />
|
|
</div>
|
|
<div class="aksrt-tools__tool-body">
|
|
<strong>{{ tool.title }}</strong>
|
|
<p>{{ tool.desc }}</p>
|
|
</div>
|
|
<FaIcon icon="fas fa-chevron-right" :size="12" color="#c0c4cc" />
|
|
</div>
|
|
</n-grid-item>
|
|
</n-grid>
|
|
</n-card>
|
|
|
|
<!-- Service Status -->
|
|
<n-card title="服务状态" size="small" class="aksrt-tools__section">
|
|
<n-list bordered size="small">
|
|
<n-list-item v-for="svc in services" :key="svc.key">
|
|
<n-thing :title="svc.name" :description="svc.description">
|
|
<template #avatar>
|
|
<FaIcon :icon="svc.icon" :size="16" :color="svc.color" />
|
|
</template>
|
|
<template #header-extra>
|
|
<n-tag :type="svc.available ? 'success' : 'warning'" size="small">
|
|
{{ svc.available ? '可用' : '未配置' }}
|
|
</n-tag>
|
|
</template>
|
|
</n-thing>
|
|
</n-list-item>
|
|
</n-list>
|
|
</n-card>
|
|
|
|
<!-- Quick Maintenance -->
|
|
<n-card title="手动维护" size="small" class="aksrt-tools__section">
|
|
<n-space>
|
|
<n-button @click="handleFlushCache">
|
|
<template #icon><FaIcon icon="fas fa-broom" :size="12" /></template>
|
|
清除缓存
|
|
</n-button>
|
|
<n-button @click="handleOptimizeDb">
|
|
<template #icon><FaIcon icon="fas fa-database" :size="12" /></template>
|
|
优化数据表
|
|
</n-button>
|
|
<n-button @click="handleRebuildIndex">
|
|
<template #icon><FaIcon icon="fas fa-sync-alt" :size="12" /></template>
|
|
重建索引
|
|
</n-button>
|
|
</n-space>
|
|
</n-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useMessage } from 'naive-ui'
|
|
import PageHeader from '@/components/PageHeader.vue'
|
|
import FaIcon from '@/components/form/FaIcon.vue'
|
|
import api from '@/api/index.js'
|
|
|
|
const router = useRouter()
|
|
const message = useMessage()
|
|
|
|
const healthItems = ref([
|
|
{ key: 'php', icon: 'fas fa-code', color: '#1890ff', value: '—', label: 'PHP 版本', status: 'ok' },
|
|
{ key: 'wp', icon: 'fab fa-wordpress', color: '#722ed1', value: '—', label: 'WordPress', status: 'ok' },
|
|
{ key: 'db', icon: 'fas fa-database', color: '#52c41a', value: '—', label: '数据库', status: 'ok' },
|
|
{ key: 'uploads', icon: 'fas fa-folder-open', color: '#fa8c16', value: '—', label: '上传目录', status: 'ok' },
|
|
])
|
|
|
|
const tools = ref([
|
|
{ key: 'import-export', icon: 'fas fa-exchange-alt', bg: '#1890ff', title: '导入导出', desc: '设置导入、导出和重置', route: '/tools/import-export' },
|
|
{ key: 'health', icon: 'fas fa-heartbeat', bg: '#52c41a', title: '健康检查', desc: '系统状态检测和诊断', route: '/tools/health' },
|
|
{ key: 'email-rollback', icon: 'fas fa-undo', bg: '#fa8c16', title: '邮箱回滚', desc: '邮箱变更 14 天内回滚', route: '/tools/email-rollback' },
|
|
])
|
|
|
|
const services = ref([
|
|
{ key: 'sms', icon: 'fas fa-sms', color: '#1890ff', name: '短信服务', description: '验证码和通知短信', available: false },
|
|
{ key: 'wechat', icon: 'fab fa-weixin', color: '#52c41a', name: '微信通知', description: '微信模板消息和订阅通知', available: false },
|
|
{ key: 'captcha', icon: 'fas fa-shield-alt', color: '#722ed1', name: '验证码服务', description: '人机验证(极验/腾讯/阿里云)', available: false },
|
|
{ key: 'push', icon: 'fas fa-paper-plane', color: '#fa8c16', name: '搜索引擎推送', description: '百度/Google 主动推送', available: false },
|
|
{ key: 'audit', icon: 'fas fa-shield-virus', color: '#f5222d', name: '内容审核', description: '第三方内容安全审核 API', available: false },
|
|
])
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const res = await api.request('/tools/health')
|
|
if (res?.data) {
|
|
const d = res.data
|
|
if (d.php_version) healthItems.value[0].value = d.php_version
|
|
if (d.wp_version) healthItems.value[1].value = d.wp_version
|
|
if (d.db_status) {
|
|
healthItems.value[2].value = d.db_status
|
|
healthItems.value[2].status = d.db_ok ? 'ok' : 'warn'
|
|
}
|
|
if (d.upload_dir) {
|
|
healthItems.value[3].value = d.upload_dir
|
|
healthItems.value[3].status = d.upload_ok ? 'ok' : 'warn'
|
|
}
|
|
}
|
|
} catch {
|
|
// Endpoint may not exist yet
|
|
}
|
|
|
|
// Check service availability from structured status
|
|
try {
|
|
const settingsRes = await api.request('/settings/extensions')
|
|
if (settingsRes?.data) {
|
|
const s = settingsRes.data
|
|
// Prefer structured services if available
|
|
const svc = settingsRes.services || {}
|
|
services.value[0].available = !!(svc.sms?.available ?? s.sms_enabled)
|
|
services.value[0].status = svc.sms?.provider || (s.sms_enabled ? '已启用' : '未配置')
|
|
services.value[1].available = !!(svc.wechat?.available ?? s.wechat_enabled)
|
|
services.value[1].status = svc.wechat?.provider || (s.wechat_enabled ? '已启用' : '未配置')
|
|
services.value[2].available = !!(svc.captcha?.available ?? svc.captcha?.configured)
|
|
services.value[2].status = svc.captcha?.provider || (svc.captcha?.enabled ? '已启用' : '未配置')
|
|
services.value[3].available = !!(svc.push?.available ?? s.baidu_push_enabled)
|
|
services.value[3].status = svc.push?.provider || (s.baidu_push_enabled ? '已启用' : '未配置')
|
|
services.value[4].available = !!(svc.audit?.available ?? s.content_audit_enabled)
|
|
services.value[4].status = svc.audit?.provider || (s.content_audit_enabled ? '已启用' : '未配置')
|
|
}
|
|
} catch {
|
|
// Use defaults
|
|
}
|
|
})
|
|
|
|
function navigateTo(route) {
|
|
router.push(route)
|
|
}
|
|
|
|
async function handleFlushCache() {
|
|
message.info('正在清除缓存...')
|
|
try {
|
|
const res = await api.request('/tools/maintenance', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action: 'flush_cache' }),
|
|
})
|
|
message.success(res?.message || '缓存已清除')
|
|
} catch (err) {
|
|
message.error(err?.message || '清除缓存失败')
|
|
}
|
|
}
|
|
|
|
async function handleOptimizeDb() {
|
|
message.info('正在优化数据表...')
|
|
try {
|
|
const res = await api.request('/tools/maintenance', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action: 'optimize_db' }),
|
|
})
|
|
message.success(res?.message || '数据表已优化')
|
|
} catch (err) {
|
|
message.error(err?.message || '优化数据表失败')
|
|
}
|
|
}
|
|
|
|
async function handleRebuildIndex() {
|
|
message.info('正在重建索引...')
|
|
try {
|
|
const res = await api.request('/tools/maintenance', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action: 'rebuild_index' }),
|
|
})
|
|
message.success(res?.message || '索引重建完成')
|
|
} catch (err) {
|
|
message.error(err?.message || '重建索引失败')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.aksrt-tools__section {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.aksrt-tools__health-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 14px;
|
|
border-radius: 8px;
|
|
background: #fafafa;
|
|
border: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.aksrt-tools__health-card strong {
|
|
display: block;
|
|
font-size: 14px;
|
|
color: #1a1a2e;
|
|
}
|
|
|
|
.aksrt-tools__health-card p {
|
|
margin: 2px 0 0;
|
|
font-size: 12px;
|
|
color: #8a909c;
|
|
}
|
|
|
|
.aksrt-tools__health-card--warn {
|
|
border-color: #faad14;
|
|
background: #fffbe6;
|
|
}
|
|
|
|
.aksrt-tools__tool-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 14px;
|
|
border-radius: 8px;
|
|
background: #fafafa;
|
|
border: 1px solid #f0f0f0;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.aksrt-tools__tool-card:hover {
|
|
border-color: #1890ff;
|
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
|
|
}
|
|
|
|
.aksrt-tools__tool-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.aksrt-tools__tool-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.aksrt-tools__tool-body strong {
|
|
display: block;
|
|
font-size: 14px;
|
|
color: #1a1a2e;
|
|
}
|
|
|
|
.aksrt-tools__tool-body p {
|
|
margin: 2px 0 0;
|
|
font-size: 12px;
|
|
color: #8a909c;
|
|
}
|
|
</style>
|