feat: Initial commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { ExternalLink } from 'lucide-vue-next'
|
||||
import { api, type AboutInfo } from '@/api'
|
||||
|
||||
const aboutInfo = ref<AboutInfo | null>(null)
|
||||
|
||||
const techStack = ['Golang', 'Vue 3', 'TypeScript', 'Vite', 'Tailwind CSS', 'Shadcn/ui']
|
||||
const features = ['脚本管理', '定时任务', '在线终端', '执行日志', '环境变量', 'Docker部署']
|
||||
|
||||
async function loadAbout() {
|
||||
try {
|
||||
aboutInfo.value = await api.settings.getAbout()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(loadAbout)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 站点关于 -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-semibold mb-2">白虎面板</h3>
|
||||
<p class="text-muted-foreground">一个轻量级的定时任务管理系统,支持脚本管理、定时执行和日志追踪。</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-x-16 gap-y-8">
|
||||
<!-- 左侧:技术栈和功能特性 -->
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium mb-4">技术栈</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge v-for="tech in techStack" :key="tech" variant="secondary">{{ tech }}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium mb-4">功能特性</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge v-for="feature in features" :key="feature" variant="outline">{{ feature }}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:系统信息 -->
|
||||
<div>
|
||||
<h4 class="text-sm font-medium mb-4">系统信息</h4>
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-muted-foreground text-sm">系统版本:</span>
|
||||
<Badge variant="outline" class="font-mono">{{ aboutInfo?.version || 'dev' }}</Badge>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-muted-foreground text-sm">构建时间:</span>
|
||||
<span class="text-sm">{{ aboutInfo?.build_time || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-muted-foreground text-sm">内存使用:</span>
|
||||
<span class="text-sm">{{ aboutInfo?.mem_usage || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-muted-foreground text-sm">运行时间:</span>
|
||||
<span class="text-sm">{{ aboutInfo?.uptime || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部:版权和链接 -->
|
||||
<div class="mt-10 pt-6 border-t flex items-center justify-center gap-2 text-sm text-muted-foreground">
|
||||
<span>© 2025 保留所有权利。</span>
|
||||
<a href="https://github.com" target="_blank" class="inline-flex items-center gap-1 text-primary hover:underline">
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
GitHub 仓库
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { api } from '@/api'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
const cleanDays = ref(30)
|
||||
const cleanResult = ref<number | null>(null)
|
||||
|
||||
async function cleanLogs() {
|
||||
if (cleanDays.value < 1) {
|
||||
toast.error('天数必须大于0')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await api.settings.cleanLogs(cleanDays.value)
|
||||
cleanResult.value = res.deleted
|
||||
toast.success(`已清理 ${res.deleted} 条日志`)
|
||||
} catch {
|
||||
toast.error('清理失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>清理多少天前的日志</Label>
|
||||
<Input v-model.number="cleanDays" type="number" class="w-32" min="1" />
|
||||
</div>
|
||||
<Button variant="destructive" @click="cleanLogs">清理日志</Button>
|
||||
<p v-if="cleanResult !== null" class="text-sm text-muted-foreground">
|
||||
上次清理了 {{ cleanResult }} 条日志
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { api } from '@/api'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
const oldPassword = ref('')
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
|
||||
async function changePassword() {
|
||||
if (!oldPassword.value || !newPassword.value) {
|
||||
toast.error('请填写完整')
|
||||
return
|
||||
}
|
||||
if (newPassword.value.length < 6) {
|
||||
toast.error('新密码至少6位')
|
||||
return
|
||||
}
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
toast.error('两次密码不一致')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await api.settings.changePassword({ old_password: oldPassword.value, new_password: newPassword.value })
|
||||
toast.success('密码修改成功')
|
||||
oldPassword.value = ''
|
||||
newPassword.value = ''
|
||||
confirmPassword.value = ''
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || '修改失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>原密码</Label>
|
||||
<Input v-model="oldPassword" type="password" placeholder="请输入原密码" class="max-w-sm" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>新密码</Label>
|
||||
<Input v-model="newPassword" type="password" placeholder="请输入新密码(至少6位)" class="max-w-sm" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>确认密码</Label>
|
||||
<Input v-model="confirmPassword" type="password" placeholder="请再次输入新密码" class="max-w-sm" />
|
||||
</div>
|
||||
<Button @click="changePassword">修改密码</Button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import PasswordSettings from './PasswordSettings.vue'
|
||||
import LogsSettings from './LogsSettings.vue'
|
||||
import SiteSettings from './SiteSettings.vue'
|
||||
import AboutSettings from './AboutSettings.vue'
|
||||
|
||||
const activeTab = ref('password')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">系统设置</h2>
|
||||
<p class="text-muted-foreground">管理系统配置和账户安全</p>
|
||||
</div>
|
||||
|
||||
<Tabs v-model="activeTab" class="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="password">密码修改</TabsTrigger>
|
||||
<TabsTrigger value="logs">日志清理</TabsTrigger>
|
||||
<TabsTrigger value="site">站点设置</TabsTrigger>
|
||||
<TabsTrigger value="about">关于</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="password" class="mt-6">
|
||||
<Card class="max-w-lg">
|
||||
<CardHeader>
|
||||
<CardTitle>修改密码</CardTitle>
|
||||
<CardDescription>更新您的账户密码</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PasswordSettings />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="logs" class="mt-6">
|
||||
<Card class="max-w-lg">
|
||||
<CardHeader>
|
||||
<CardTitle>日志清理</CardTitle>
|
||||
<CardDescription>清理历史执行日志以释放存储空间</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<LogsSettings />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="site" class="mt-6">
|
||||
<Card class="max-w-lg">
|
||||
<CardHeader>
|
||||
<CardTitle>站点设置</CardTitle>
|
||||
<CardDescription>查看当前站点配置信息</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SiteSettings />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="about" class="mt-6">
|
||||
<Card class="max-w-3xl">
|
||||
<CardContent class="pt-6">
|
||||
<AboutSettings />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { api } from '@/api'
|
||||
|
||||
const siteName = ref('')
|
||||
const sitePort = ref(0)
|
||||
|
||||
async function loadSiteSettings() {
|
||||
try {
|
||||
const res = await api.settings.getSite()
|
||||
siteName.value = res.site_name || '白虎面板'
|
||||
sitePort.value = res.port
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(loadSiteSettings)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>站点名称</Label>
|
||||
<Input v-model="siteName" disabled class="max-w-sm" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>端口</Label>
|
||||
<Input v-model.number="sitePort" type="number" class="w-32" disabled />
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">站点设置需要修改配置文件后重启服务</p>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user