feat: add settings pages
This commit is contained in:
+14
-2
@@ -101,14 +101,18 @@ export const api = {
|
||||
detail: (id: number) => request<LogDetail>(`/logs/${id}`)
|
||||
},
|
||||
dashboard: {
|
||||
stats: () => request<Stats>('/stats')
|
||||
stats: () => request<Stats>('/stats'),
|
||||
sentence: () => request<{ sentence: string }>('/sentence')
|
||||
},
|
||||
settings: {
|
||||
changePassword: (data: { old_password: string; new_password: string }) =>
|
||||
request('/settings/password', { method: 'POST', body: JSON.stringify(data) }),
|
||||
cleanLogs: (days: number) =>
|
||||
request<{ deleted: number }>('/settings/clean-logs', { method: 'POST', body: JSON.stringify({ days }) }),
|
||||
getSite: () => request<{ site_name: string; port: number }>('/settings/site'),
|
||||
getSite: () => request<SiteSettings>('/settings/site'),
|
||||
getPublicSite: () => request<{ title: string; subtitle: string; icon: string }>('/settings/public'),
|
||||
updateSite: (data: SiteSettings) =>
|
||||
request('/settings/site', { method: 'PUT', body: JSON.stringify(data) }),
|
||||
getAbout: () => request<AboutInfo>('/settings/about')
|
||||
},
|
||||
files: {
|
||||
@@ -252,3 +256,11 @@ export interface AboutInfo {
|
||||
log_count: number
|
||||
env_count: number
|
||||
}
|
||||
|
||||
export interface SiteSettings {
|
||||
title: string
|
||||
subtitle: string
|
||||
icon: string
|
||||
page_size: string
|
||||
cookie_days: string
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, "modelValue", emits, {
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-7 w-full min-w-0 rounded-md border bg-transparent px-2 py-1 text-xs shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ref } from 'vue'
|
||||
import { api, type SiteSettings } from '@/api'
|
||||
|
||||
const siteSettings = ref<SiteSettings>({
|
||||
title: '白虎面板',
|
||||
subtitle: '轻量级定时任务管理系统',
|
||||
icon: "<svg t=\"1766107903919\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"1942\" width=\"200\" height=\"200\"><path d=\"M884.992 273.05984c4.10624 0 5.0688-2.36544 2.10944-5.25312 0 0-64.28672-65.55648-111.7696-75.55072-47.48288-9.984-45.47584-59.37152-80.56832-75.02848-72.0896-32.16384-158.6176-34.3552-158.6176-34.3552s-91.37152-4.92544-138.752-9.89184c-19.46624-2.03776-54.46656-9.58464-54.46656-9.58464-4.0448-0.84992-10.63936-1.19808-14.66368-0.44032 0 0-30.63808-0.07168-44.30848 43.35616-8.8576 28.11904 1.792 104.79616 1.792 104.79616 1.46432 12.27776-3.42016 30.21824-10.72128 40.20224 0 0-36.77184 46.03904-58.9312 100.34176-22.15936 54.30272 118.15936 145.05984 208.98816 205.27104C507.82208 613.89824 502.03648 743.424 502.03648 743.424s-74.19904-96.75776-194.00704-156.9792C188.2112 526.22336 150.30272 442.23488 150.30272 442.23488c-2.89792-5.45792-5.94944-4.94592-6.71744 1.21856 0 0-15.1552 91.61728 16.25088 147.8144 70.92224 126.88384 141.74208 112.88576 197.03808 183.27552s54.272 164.9152 54.272 164.9152-97.62816-141.29152-235.66336-205.55776c-91.53536-61.27616-74.7008-125.91104-85.49376-101.85728-10.79296 24.05376 26.73664 192.41984 65.40288 222.2592 80.57856 62.18752 94.16704 101.98016 94.16704 101.98016h175.53408S544.512 814.85824 572.928 725.73952c44.41088-139.30496 40.20224-191.26272 40.20224-191.26272 0.08192-8.22272 6.81984-14.19264 14.98112-13.29152 0 0 46.45888 4.64896 66.64192 9.68704 23.53152 5.86752 55.35744 26.20416 55.35744 26.20416 3.49184 2.14016 7.39328 0.68608 8.69376-3.1744l34.4576-102.77888c1.30048-3.8912-0.63488-5.51936-4.352-3.75808 0 0-45.37344 25.09824-88.17664 12.1856-20.15232-6.08256-59.60704-14.82752-74.69056-32.6656-16.95744-20.03968-15.59552-71.3728 26.66496-79.21664 48.0256-8.9088 33.13664 15.14496 65.91488 24.64768 27.42272 7.95648 22.29248-1.69984 26.69568 5.21216 1.67936 2.63168 0.38912 32.65536 0.38912 32.65536-0.21504 6.144 3.39968 7.84384 8.0384 3.79904l41.89184-36.46464c17.37728-11.24352 30.86336-0.57344 48.24064-11.81696 14.73536-9.53344 29.58336-43.66336 29.58336-43.66336 4.48512-9.24672 0.60416-20.41856-8.63232-24.92416l-42.58816-20.80768c-3.69664-1.80224-3.38944-3.26656 0.74752-3.26656h62.0032zM422.54336 123.87328s-49.88928 50.7392-74.5472 50.66752c-24.65792-0.07168-33.24928-56.12544-3.92192-56.12544 18.00192 0 76.32896 0.21504 76.32896 0.21504 4.13696 0.02048 5.0688 2.36544 2.14016 5.24288z m123.09504 249.64096s-3.31776-25.53856-33.16736-40.05888c-29.8496-14.52032-52.5312-41.13408-59.648-68.95616-12.1856-54.8864 48.29184-104.192 48.29184-104.192s-30.1056 73.6768 3.5328 106.60864c54.272 53.12512 40.99072 106.5984 40.99072 106.5984z m155.56608-164.46464c-7.94624 10.32192-9.60512 37.92896-59.2384 20.736-49.63328-17.2032-71.00416-54.5792-71.00416-54.5792-2.27328-3.40992-0.79872-6.49216 3.328-6.8096 0 0 43.55072-5.03808 80.06656 6.44096 24.91392 7.82336 54.79424 23.88992 46.848 34.21184z\" fill=\"#272636\" p-id=\"1943\"></path><path d=\"M366.30528 259.26656c1.05472-1.76128 1.51552-1.57696 1.19808 0.43008 0 0-7.55712 19.0464 22.8864 87.63392 27.0848 61.02016 87.49056 68.7104 118.66112 98.23232 55.808 52.82816 53.52448 123.45344 53.52448 123.45344s-25.82528-49.85856-85.98528-78.83776-92.6208-50.52416-127.7952-85.77024c-45.02528-45.12768 17.5104-145.14176 17.5104-145.14176zM500.0704 961.44384h134.49216s95.8464-138.07616 46.68416-291.25632c-16.19968-50.46272-43.45856-80.00512-43.45856-80.00512-5.18144-6.38976-8.89856-4.88448-8.448 3.34848 0 0 9.15456 99.80928-19.44576 194.00704-28.60032 94.208-109.824 173.90592-109.824 173.90592zM681.61536 956.8768h105.24672s22.38464-73.5232 16.61952-130.21184c-9.30816-91.57632-48.31232-132.72064-48.31232-132.72064-3.80928-4.77184-6.0928-3.67616-5.2224 2.38592 0 0 16.75264 89.1904-14.4896 150.1184-30.9248 60.30336-53.84192 110.42816-53.84192 110.42816zM869.30432 811.35616c-2.88768-2.9184-4.80256-1.95584-4.38272 2.10944 0 0 6.79936 44.99456-7.76192 81.22368-10.12736 25.1904-28.91776 58.60352-28.91776 58.60352h107.17184s5.34528-50.31936-19.89632-83.97824c-11.56096-23.99232-46.21312-57.9584-46.21312-57.9584z\" fill=\"#272636\" p-id=\"1944\"></path></svg>",
|
||||
|
||||
page_size: '10',
|
||||
cookie_days: '7'
|
||||
})
|
||||
|
||||
let loaded = false
|
||||
|
||||
export function useSiteSettings() {
|
||||
async function loadSettings() {
|
||||
if (loaded) return
|
||||
try {
|
||||
const res = await api.settings.getSite()
|
||||
siteSettings.value = res
|
||||
document.title = res.title || '白虎面板'
|
||||
loaded = true
|
||||
} catch {
|
||||
// 使用默认值
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSettings() {
|
||||
loaded = false
|
||||
return loadSettings()
|
||||
}
|
||||
|
||||
return {
|
||||
siteSettings,
|
||||
loadSettings,
|
||||
refreshSettings
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@ import { LayoutDashboard, ListTodo, FileCode, Settings, LogOut, ScrollText, Term
|
||||
import { Button } from '@/components/ui/button'
|
||||
import ThemeToggle from '@/components/ThemeToggle.vue'
|
||||
import { api } from '@/api'
|
||||
import * as jinrishici from 'jinrishici'
|
||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||
|
||||
const route = useRoute()
|
||||
const sentence = ref('欢迎使用白虎面板')
|
||||
const { siteSettings, loadSettings } = useSiteSettings()
|
||||
|
||||
const navItems = [
|
||||
{ to: '/', icon: LayoutDashboard, label: '数据仪表', exact: true },
|
||||
@@ -20,7 +21,7 @@ const navItems = [
|
||||
{ to: '/settings', icon: Settings, label: '系统设置', exact: true },
|
||||
]
|
||||
|
||||
function isItemActive(item: typeof navItems[0]) {
|
||||
function isItemActive(item: (typeof navItems)[0]) {
|
||||
if (item.exact) {
|
||||
return route.path === item.to
|
||||
}
|
||||
@@ -36,18 +37,19 @@ async function logout() {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
|
||||
function loadSentence() {
|
||||
jinrishici.load(
|
||||
(result: { data: { content: string } }) => {
|
||||
sentence.value = result.data.content
|
||||
},
|
||||
() => {
|
||||
// 加载失败保持默认
|
||||
}
|
||||
)
|
||||
async function loadSentence() {
|
||||
try {
|
||||
const res = await api.dashboard.sentence()
|
||||
sentence.value = res.sentence
|
||||
} catch {
|
||||
// 加载失败保持默认
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSentence)
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
loadSentence()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,7 +57,7 @@ onMounted(loadSentence)
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-56 border-r bg-background flex flex-col">
|
||||
<div class="h-14 flex items-center px-10 font-semibold text-lg border-b">
|
||||
白虎面板
|
||||
{{ siteSettings.title }}
|
||||
</div>
|
||||
<nav class="flex-1 px-6 py-9 space-y-1">
|
||||
<RouterLink
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -13,6 +13,20 @@ const username = ref('')
|
||||
const password = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const siteTitle = ref('白虎面板')
|
||||
const siteSubtitle = ref('轻量级定时任务管理系统')
|
||||
|
||||
async function loadSiteSettings() {
|
||||
try {
|
||||
const res = await api.settings.getPublicSite()
|
||||
siteTitle.value = res.title || '白虎面板'
|
||||
siteSubtitle.value = res.subtitle || '轻量级定时任务管理系统'
|
||||
document.title = siteTitle.value
|
||||
} catch {
|
||||
// 使用默认值
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -25,6 +39,8 @@ async function handleLogin() {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSiteSettings)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -40,8 +56,8 @@ async function handleLogin() {
|
||||
<div class="w-96 p-10">
|
||||
<div class="space-y-8">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-2xl font-bold tracking-tight">白虎面板</h1>
|
||||
<p class="text-muted-foreground">轻量级定时任务管理系统</p>
|
||||
<h1 class="text-2xl font-bold tracking-tight">{{ siteTitle }}</h1>
|
||||
<p class="text-muted-foreground">{{ siteSubtitle }}</p>
|
||||
</div>
|
||||
<form @submit.prevent="handleLogin" class="space-y-5">
|
||||
<div class="space-y-2">
|
||||
|
||||
@@ -21,36 +21,36 @@ onMounted(loadAbout)
|
||||
<template>
|
||||
<div>
|
||||
<!-- 站点关于 -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-semibold mb-2">白虎面板</h3>
|
||||
<p class="text-muted-foreground">一个轻量级的定时任务管理系统,支持脚本管理、定时执行和日志追踪。</p>
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold mb-1">白虎面板</h3>
|
||||
<p class="text-sm text-muted-foreground">一个轻量级的定时任务管理系统,支持脚本管理、定时执行和日志追踪。</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-x-16 gap-y-8">
|
||||
<div class="grid md:grid-cols-2 gap-x-8 gap-y-5">
|
||||
<!-- 左侧:技术栈和功能特性 -->
|
||||
<div class="space-y-8">
|
||||
<div class="space-y-5">
|
||||
<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>
|
||||
<h4 class="text-sm font-medium mb-2">技术栈</h4>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<Badge v-for="tech in techStack" :key="tech" class="text-xs bg-primary/15 text-primary border-0">{{ 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>
|
||||
<h4 class="text-sm font-medium mb-2">功能特性</h4>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<Badge v-for="feature in features" :key="feature" class="text-xs bg-accent text-accent-foreground">{{ feature }}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:系统信息 -->
|
||||
<div>
|
||||
<h4 class="text-sm font-medium mb-4">系统信息</h4>
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-sm font-medium mb-2">系统信息</h4>
|
||||
<div class="space-y-2">
|
||||
<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>
|
||||
<Badge variant="outline" class="font-mono text-xs">{{ aboutInfo?.version || 'dev' }}</Badge>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-muted-foreground text-sm">构建时间:</span>
|
||||
@@ -69,9 +69,9 @@ onMounted(loadAbout)
|
||||
</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">
|
||||
<div class="mt-6 pt-4 border-t flex items-center justify-center gap-2 text-sm text-muted-foreground">
|
||||
<span>© {{ new Date().getFullYear() }} 保留所有权利。</span>
|
||||
<a href="https://github.com/engigu/BaiHu/" target="_blank" class="inline-flex items-center gap-1 text-primary hover:underline">
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
GitHub 仓库
|
||||
</a>
|
||||
|
||||
@@ -50,10 +50,10 @@ const activeTab = ref('password')
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="site" class="mt-6">
|
||||
<Card class="max-w-lg">
|
||||
<Card class="max-w-xl">
|
||||
<CardHeader>
|
||||
<CardTitle>站点设置</CardTitle>
|
||||
<CardDescription>查看当前站点配置信息</CardDescription>
|
||||
<CardDescription>配置站点标题、图标和系统参数</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SiteSettings />
|
||||
@@ -62,7 +62,7 @@ const activeTab = ref('password')
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="about" class="mt-6">
|
||||
<Card class="max-w-3xl">
|
||||
<Card class="max-w-xl">
|
||||
<CardContent class="pt-6">
|
||||
<AboutSettings />
|
||||
</CardContent>
|
||||
|
||||
@@ -1,33 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { api } from '@/api'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { api, type SiteSettings } from '@/api'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||
|
||||
const siteName = ref('')
|
||||
const sitePort = ref(0)
|
||||
const { refreshSettings } = useSiteSettings()
|
||||
|
||||
async function loadSiteSettings() {
|
||||
const form = ref<SiteSettings>({
|
||||
title: '',
|
||||
subtitle: '',
|
||||
icon: '',
|
||||
page_size: '10',
|
||||
cookie_days: '7'
|
||||
})
|
||||
const loading = ref(false)
|
||||
|
||||
const iconPreview = computed(() => {
|
||||
if (!form.value.icon) return ''
|
||||
// 简单验证是否是 SVG
|
||||
if (form.value.icon.trim().startsWith('<svg')) {
|
||||
return form.value.icon
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const res = await api.settings.getSite()
|
||||
siteName.value = res.site_name || '白虎面板'
|
||||
sitePort.value = res.port
|
||||
form.value = res
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(loadSiteSettings)
|
||||
async function saveSettings() {
|
||||
loading.value = true
|
||||
try {
|
||||
await api.settings.updateSite(form.value)
|
||||
await refreshSettings()
|
||||
toast.success('保存成功')
|
||||
} catch {
|
||||
toast.error('保存失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSettings)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>站点名称</Label>
|
||||
<Input v-model="siteName" disabled class="max-w-sm" />
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">站点标题</Label>
|
||||
<Input v-model="form.title" placeholder="白虎面板" class="col-span-3" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>端口</Label>
|
||||
<Input v-model.number="sitePort" type="number" class="w-32" disabled />
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">站点标语</Label>
|
||||
<Input v-model="form.subtitle" placeholder="轻量级定时任务管理系统" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">站点图标</Label>
|
||||
<div class="col-span-3 flex items-center gap-2">
|
||||
<Input v-model="form.icon" placeholder="<svg>...</svg>" class="flex-1 font-mono text-xs" />
|
||||
<div v-if="iconPreview" class="p-1.5 border rounded bg-muted/50 w-8 h-8 flex items-center justify-center shrink-0 [&>svg]:w-5 [&>svg]:h-5" v-html="iconPreview" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">分页/Cookie</Label>
|
||||
<div class="col-span-3 flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Input v-model="form.page_size" type="number" class="w-20" />
|
||||
<span class="text-sm text-muted-foreground">条/页</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input v-model="form.cookie_days" type="number" class="w-20" />
|
||||
<span class="text-sm text-muted-foreground">天过期</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end pt-2">
|
||||
<Button @click="saveSettings" :disabled="loading">
|
||||
{{ loading ? '保存中...' : '保存设置' }}
|
||||
</Button>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">站点设置需要修改配置文件后重启服务</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,16 @@ const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const cronPresets = [
|
||||
{ label: '每分钟', value: '* * * * *' },
|
||||
{ label: '每5分钟', value: '*/5 * * * *' },
|
||||
{ label: '每小时', value: '0 * * * *' },
|
||||
{ label: '每天0点', value: '0 0 * * *' },
|
||||
{ label: '每天8点', value: '0 8 * * *' },
|
||||
{ label: '每周一', value: '0 0 * * 1' },
|
||||
{ label: '每月1号', value: '0 0 1 * *' },
|
||||
]
|
||||
|
||||
async function loadTasks() {
|
||||
try {
|
||||
const res = await api.tasks.list({ page: currentPage.value, page_size: pageSize.value, name: filterName.value || undefined })
|
||||
@@ -168,26 +178,39 @@ onMounted(loadTasks)
|
||||
</div>
|
||||
|
||||
<Dialog v-model:open="showDialog">
|
||||
<DialogContent class="max-w-md">
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ isEdit ? '编辑任务' : '新建任务' }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="space-y-4 py-2">
|
||||
<div class="space-y-2">
|
||||
<Label>任务名称</Label>
|
||||
<Input v-model="editingTask.name" placeholder="任务名称" />
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">任务名称</Label>
|
||||
<Input v-model="editingTask.name" placeholder="我的任务" class="col-span-3" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>执行命令</Label>
|
||||
<Input v-model="editingTask.command" class="font-mono" placeholder="node script.js" />
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">执行命令</Label>
|
||||
<Input v-model="editingTask.command" placeholder="node script.js" class="col-span-3 font-mono" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>定时规则 (Cron)</Label>
|
||||
<Input v-model="editingTask.schedule" class="font-mono" placeholder="0 * * * *" />
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">定时规则</Label>
|
||||
<Input v-model="editingTask.schedule" placeholder="0 * * * *" class="col-span-3 font-mono" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>超时时间 (分钟)</Label>
|
||||
<Input v-model.number="editingTask.timeout" type="number" placeholder="30" />
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<span></span>
|
||||
<div class="col-span-3 flex flex-wrap gap-1.5">
|
||||
<span
|
||||
v-for="preset in cronPresets"
|
||||
:key="preset.value"
|
||||
class="px-2 py-0.5 text-xs rounded-md bg-muted hover:bg-accent cursor-pointer transition-colors"
|
||||
@click="editingTask.schedule = preset.value"
|
||||
>
|
||||
{{ preset.label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">超时(分钟)</Label>
|
||||
<Input v-model.number="editingTask.timeout" type="number" placeholder="30" class="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user