feat: adapt mobile phone style

This commit is contained in:
engigu
2025-12-23 23:22:25 +08:00
parent 95242410d5
commit ca46573c9b
16 changed files with 174 additions and 148 deletions
+37 -11
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RouterLink, RouterView, useRoute } from 'vue-router'
import { LayoutDashboard, ListTodo, FileCode, Settings, LogOut, ScrollText, Terminal, Variable, KeyRound, Package } from 'lucide-vue-next'
import { LayoutDashboard, ListTodo, FileCode, Settings, LogOut, ScrollText, Terminal, Variable, KeyRound, Package, Menu, X } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import ThemeToggle from '@/components/ThemeToggle.vue'
import { api } from '@/api'
@@ -10,6 +10,7 @@ import { useSiteSettings } from '@/composables/useSiteSettings'
const route = useRoute()
const sentence = ref('欢迎使用白虎面板')
const { siteSettings, loadSettings } = useSiteSettings()
const mobileMenuOpen = ref(false)
const navItems = [
{ to: '/', icon: LayoutDashboard, label: '数据仪表', exact: true },
@@ -30,6 +31,11 @@ function isItemActive(item: (typeof navItems)[0]) {
return route.path.startsWith(item.to)
}
function handleNavClick(navigate: () => void) {
navigate()
mobileMenuOpen.value = false
}
async function logout() {
try {
await api.auth.logout()
@@ -56,12 +62,27 @@ onMounted(() => {
<template>
<div class="flex h-screen bg-muted/40">
<!-- Mobile Menu Overlay -->
<div
v-if="mobileMenuOpen"
class="fixed inset-0 bg-black/50 z-40 lg:hidden"
@click="mobileMenuOpen = false"
/>
<!-- 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 }}
<aside
:class="[
'fixed lg:static inset-y-0 left-0 z-50 w-56 border-r bg-background flex flex-col transform transition-transform duration-200 ease-in-out lg:transform-none',
mobileMenuOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
]"
>
<div class="h-14 flex items-center justify-between px-4 lg:px-10 font-semibold text-lg border-b">
<span>{{ siteSettings.title }}</span>
<Button variant="ghost" size="icon" class="h-8 w-8 lg:hidden" @click="mobileMenuOpen = false">
<X class="h-4 w-4" />
</Button>
</div>
<nav class="flex-1 px-6 py-9 space-y-1">
<nav class="flex-1 px-3 lg:px-6 py-6 lg:py-9 space-y-1">
<RouterLink
v-for="item in navItems"
:key="item.to"
@@ -71,8 +92,8 @@ onMounted(() => {
>
<Button
variant="ghost"
:class="['w-[80%] justify-start gap-3 h-9 px-3', isItemActive(item) && 'bg-accent text-accent-foreground']"
@click="navigate"
:class="['w-full lg:w-[80%] justify-start gap-3 h-9 px-3', isItemActive(item) && 'bg-accent text-accent-foreground']"
@click="handleNavClick(navigate)"
>
<component :is="item.icon" class="h-4 w-4" />
{{ item.label }}
@@ -88,12 +109,17 @@ onMounted(() => {
</aside>
<!-- Main Content -->
<main class="flex-1 overflow-auto">
<div class="h-14 border-b bg-background flex items-center justify-between px-6">
<span class="text-sm text-muted-foreground">{{ sentence }}</span>
<main class="flex-1 overflow-auto w-full">
<div class="h-14 border-b bg-background flex items-center justify-between px-4 lg:px-6">
<div class="flex items-center gap-3">
<Button variant="ghost" size="icon" class="h-8 w-8 lg:hidden" @click="mobileMenuOpen = true">
<Menu class="h-5 w-5" />
</Button>
<span class="text-sm text-muted-foreground hidden sm:block">{{ sentence }}</span>
</div>
<ThemeToggle />
</div>
<div class="p-6">
<div class="p-4 lg:p-6">
<RouterView />
</div>
</main>
+11 -11
View File
@@ -219,26 +219,26 @@ onMounted(async () => {
<p class="text-muted-foreground">查看系统运行状态和统计数据</p>
</div>
<div class="grid gap-4 md:grid-cols-3 lg:grid-cols-6">
<div class="grid gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-6">
<Card v-for="item in statItems" :key="item.key" class="cursor-pointer hover:bg-accent/50 transition-colors" @click="navigateTo(item.route)">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">{{ item.label }}</CardTitle>
<CardTitle class="text-xs sm:text-sm font-medium">{{ item.label }}</CardTitle>
<component :is="item.icon" class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">{{ stats[item.key as keyof Stats] }}</div>
<div class="text-xl sm:text-2xl font-bold">{{ stats[item.key as keyof Stats] }}</div>
</CardContent>
</Card>
</div>
<div class="grid gap-4 lg:grid-cols-10">
<Card class="lg:col-span-7 h-[340px]">
<Card class="lg:col-span-7 h-[300px] sm:h-[340px]">
<CardHeader class="pb-2">
<CardTitle>执行统计</CardTitle>
<CardDescription>最近30天任务执行情况</CardDescription>
<CardTitle class="text-base sm:text-lg">执行统计</CardTitle>
<CardDescription class="text-xs sm:text-sm">最近30天任务执行情况</CardDescription>
</CardHeader>
<CardContent>
<div id="stats-chart" class="w-full h-[240px]">
<div id="stats-chart" class="w-full h-[200px] sm:h-[240px]">
<div v-if="!chartsLoaded" class="h-full flex items-center justify-center text-muted-foreground text-sm">
加载中...
</div>
@@ -246,13 +246,13 @@ onMounted(async () => {
</CardContent>
</Card>
<Card class="lg:col-span-3 h-[340px]">
<Card class="lg:col-span-3 h-[300px] sm:h-[340px]">
<CardHeader class="pb-2">
<CardTitle>任务占比</CardTitle>
<CardDescription>最近30天任务执行分布</CardDescription>
<CardTitle class="text-base sm:text-lg">任务占比</CardTitle>
<CardDescription class="text-xs sm:text-sm">最近30天任务执行分布</CardDescription>
</CardHeader>
<CardContent>
<div id="pie-chart" class="w-full h-[240px]">
<div id="pie-chart" class="w-full h-[200px] sm:h-[240px]">
<div v-if="!chartsLoaded" class="h-full flex items-center justify-center text-muted-foreground text-sm">
加载中...
</div>
+14 -14
View File
@@ -116,10 +116,10 @@ onMounted(loadDeps)
<template>
<div class="space-y-4">
<div class="flex items-center justify-between">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-2xl font-bold tracking-tight">依赖管理</h2>
<p class="text-muted-foreground">管理 Python Node.js 依赖包</p>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">依赖管理</h2>
<p class="text-muted-foreground text-sm">管理 Python Node.js 依赖包</p>
</div>
</div>
@@ -130,36 +130,36 @@ onMounted(loadDeps)
</TabsList>
<TabsContent :value="activeTab" class="mt-4">
<div class="rounded-lg border bg-card">
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 工具栏 -->
<div class="flex items-center justify-between px-4 py-3 border-b bg-muted/30">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 px-4 py-3 border-b bg-muted/30">
<div class="flex items-center gap-2">
<Badge variant="secondary">{{ filteredDeps.length }} 个包</Badge>
</div>
<div class="flex items-center gap-2">
<div class="relative">
<div class="relative flex-1 sm:flex-none">
<Search class="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input v-model="searchQuery" placeholder="搜索包名..." class="h-9 pl-8 w-48 text-sm" />
<Input v-model="searchQuery" placeholder="搜索包名..." class="h-9 pl-8 w-full sm:w-48 text-sm" />
</div>
<Button variant="outline" size="icon" class="h-9 w-9" @click="loadDeps" :disabled="loading">
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadDeps" :disabled="loading">
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
</Button>
<Button size="sm" class="h-9" @click="openInstallDialog">
<Download class="h-4 w-4 mr-1.5" /> 安装
<Button size="sm" class="h-9 shrink-0" @click="openInstallDialog">
<Download class="h-4 w-4 sm:mr-1.5" /> <span class="hidden sm:inline">安装</span>
</Button>
</div>
</div>
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium">
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium min-w-[400px]">
<span class="flex-1">包名</span>
<span class="w-32">版本</span>
<span class="w-48">备注</span>
<span class="w-48 hidden md:block">备注</span>
<span class="w-20 text-center">操作</span>
</div>
<!-- 列表 -->
<div class="divide-y max-h-[480px] overflow-y-auto">
<div class="divide-y max-h-[480px] overflow-y-auto min-w-[400px]">
<div v-if="loading" class="text-center py-8 text-muted-foreground">
<Loader2 class="h-5 w-5 animate-spin mx-auto mb-2" />
加载中...
@@ -176,7 +176,7 @@ onMounted(loadDeps)
>
<span class="flex-1 font-mono text-sm">{{ dep.name }}</span>
<span class="w-32 text-sm text-muted-foreground">{{ dep.version || '-' }}</span>
<span class="w-48 text-sm text-muted-foreground truncate" :title="dep.remark">{{ dep.remark || '-' }}</span>
<span class="w-48 text-sm text-muted-foreground truncate hidden md:block" :title="dep.remark">{{ dep.remark || '-' }}</span>
<span class="w-20 flex justify-center gap-1">
<Button v-if="dep.log" variant="ghost" size="icon" class="h-7 w-7" @click="showLog(dep)">
<FileText class="h-4 w-4" />
+12 -12
View File
@@ -103,32 +103,32 @@ onMounted(loadEnvVars)
<template>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-2xl font-bold tracking-tight">环境变量</h2>
<p class="text-muted-foreground">管理脚本执行时的环境变量</p>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">环境变量</h2>
<p class="text-muted-foreground text-sm">管理脚本执行时的环境变量</p>
</div>
<div class="flex items-center gap-2">
<div class="relative">
<div class="relative flex-1 sm:flex-none">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input v-model="filterName" placeholder="搜索变量..." class="h-9 pl-9 w-56 text-sm" @input="handleSearch" />
<Input v-model="filterName" placeholder="搜索变量..." class="h-9 pl-9 w-full sm:w-56 text-sm" @input="handleSearch" />
</div>
<Button @click="openCreate">
<Plus class="h-4 w-4 mr-2" /> 新建变量
<Button @click="openCreate" class="shrink-0">
<Plus class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">新建变量</span>
</Button>
</div>
</div>
<div class="rounded-lg border bg-card">
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium">
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium min-w-[500px]">
<span class="w-48 shrink-0">变量名</span>
<span class="flex-1"></span>
<span class="w-48 shrink-0">备注</span>
<span class="w-48 shrink-0 hidden md:block">备注</span>
<span class="w-24 shrink-0 text-center">操作</span>
</div>
<!-- 列表 -->
<div class="divide-y">
<div class="divide-y min-w-[500px]">
<div v-if="envVars.length === 0" class="text-sm text-muted-foreground text-center py-8">
暂无环境变量
</div>
@@ -141,7 +141,7 @@ onMounted(loadEnvVars)
<span class="flex-1 font-mono text-muted-foreground truncate text-xs">
{{ showValues[env.id] ? env.value : maskValue(env.value) }}
</span>
<span class="w-48 shrink-0 text-muted-foreground truncate text-sm">{{ env.remark || '-' }}</span>
<span class="w-48 shrink-0 text-muted-foreground truncate text-sm hidden md:block">{{ env.remark || '-' }}</span>
<span class="w-24 shrink-0 flex justify-center gap-1">
<Button variant="ghost" size="icon" class="h-7 w-7" @click="toggleShow(env.id)" :title="showValues[env.id] ? '隐藏' : '显示'">
<Eye v-if="!showValues[env.id]" class="h-3.5 w-3.5" />
+13 -13
View File
@@ -119,33 +119,33 @@ watch(() => route.query.task_id, (newTaskId) => {
<template>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-2xl font-bold tracking-tight">执行历史</h2>
<p class="text-muted-foreground">查看任务执行记录和日志</p>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">执行历史</h2>
<p class="text-muted-foreground text-sm">查看任务执行记录和日志</p>
</div>
<div class="flex items-center gap-2">
<div class="relative">
<div class="relative flex-1 sm:flex-none">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input v-model="filterKeyword" placeholder="搜索任务..." class="h-9 pl-9 w-56 text-sm" @input="handleSearch" />
<Input v-model="filterKeyword" placeholder="搜索任务..." class="h-9 pl-9 w-full sm:w-56 text-sm" @input="handleSearch" />
</div>
<Button variant="outline" size="icon" class="h-9 w-9" @click="loadLogs">
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadLogs">
<RefreshCw class="h-4 w-4" />
</Button>
</div>
</div>
<div class="flex gap-4">
<div class="flex flex-col lg:flex-row gap-4">
<!-- 日志列表 -->
<div class="flex-1 min-w-0 rounded-lg border bg-card overflow-hidden">
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium">
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium overflow-x-auto">
<span class="w-12 shrink-0">ID</span>
<span class="w-32 shrink-0">任务名称</span>
<span :class="selectedLog ? 'w-40 shrink-0' : 'flex-1'">命令</span>
<span :class="selectedLog ? 'w-40 shrink-0 hidden sm:block' : 'flex-1'">命令</span>
<span class="w-12 shrink-0 text-center">状态</span>
<span class="w-20 text-right shrink-0">耗时</span>
<span v-if="!selectedLog" class="w-40 text-right shrink-0">执行时间</span>
<span v-if="!selectedLog" class="w-40 text-right shrink-0 hidden md:block">执行时间</span>
</div>
<!-- 列表 -->
<div class="divide-y">
@@ -163,12 +163,12 @@ watch(() => route.query.task_id, (newTaskId) => {
>
<span class="w-12 shrink-0 text-muted-foreground text-sm">#{{ log.id }}</span>
<span class="w-32 font-medium truncate shrink-0 text-sm">{{ log.task_name }}</span>
<code :class="['text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded', selectedLog ? 'w-40 shrink-0' : 'flex-1']">{{ log.command }}</code>
<code :class="['text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded', selectedLog ? 'w-40 shrink-0 hidden sm:block' : 'flex-1']">{{ log.command }}</code>
<span class="w-12 flex justify-center shrink-0">
<span :class="['w-2 h-2 rounded-full', log.status === 'success' ? 'bg-green-500' : log.status === 'failed' ? 'bg-red-500' : 'bg-yellow-500']" />
</span>
<span class="w-20 text-right shrink-0 text-muted-foreground text-xs">{{ formatDuration(log.duration) }}</span>
<span v-if="!selectedLog" class="w-40 text-right shrink-0 text-muted-foreground text-xs">{{ log.created_at }}</span>
<span v-if="!selectedLog" class="w-40 text-right shrink-0 text-muted-foreground text-xs hidden md:block">{{ log.created_at }}</span>
</div>
</div>
<!-- 分页 -->
@@ -178,7 +178,7 @@ watch(() => route.query.task_id, (newTaskId) => {
<!-- 日志详情侧边栏 -->
<div
v-if="selectedLog"
class="w-[480px] rounded-lg border bg-card flex flex-col overflow-hidden shrink-0 max-h-[calc(100vh-180px)]"
class="w-full lg:w-[480px] rounded-lg border bg-card flex flex-col overflow-hidden shrink-0 max-h-[60vh] lg:max-h-[calc(100vh-180px)]"
>
<div class="flex items-center justify-between px-4 py-3 border-b">
<span class="text-sm font-medium">日志详情</span>
+6 -6
View File
@@ -65,16 +65,16 @@ onMounted(loadSiteSettings)
<ThemeToggle />
</div>
<div class="border rounded-lg bg-background shadow-sm overflow-hidden">
<div class="border rounded-lg bg-background shadow-sm overflow-hidden w-full max-w-[400px] lg:max-w-none lg:w-auto">
<div class="flex">
<!-- 左侧登录表单 -->
<div class="w-96 p-10">
<div class="space-y-8">
<div class="w-full lg:w-96 p-6 sm:p-10">
<div class="space-y-6 sm:space-y-8">
<div class="space-y-2">
<h1 class="text-2xl font-bold tracking-tight">{{ siteTitle }}</h1>
<p class="text-muted-foreground">{{ siteSubtitle }}</p>
<h1 class="text-xl sm:text-2xl font-bold tracking-tight">{{ siteTitle }}</h1>
<p class="text-muted-foreground text-sm sm:text-base">{{ siteSubtitle }}</p>
</div>
<form @submit.prevent="handleLogin" class="space-y-5">
<form @submit.prevent="handleLogin" class="space-y-4 sm:space-y-5">
<div class="space-y-2">
<Label>用户名</Label>
<Input v-model="username" placeholder="请输入用户名" class="h-10 text-base" />
+11 -11
View File
@@ -62,38 +62,38 @@ onMounted(loadLogs)
<template>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-2xl font-bold tracking-tight">登录日志</h2>
<p class="text-muted-foreground">查看系统登录记录</p>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">登录日志</h2>
<p class="text-muted-foreground text-sm">查看系统登录记录</p>
</div>
<div class="flex items-center gap-2">
<div class="relative">
<div class="relative flex-1 sm:flex-none">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
v-model="filterUsername"
placeholder="搜索用户名..."
class="h-9 pl-9 w-56 text-sm"
class="h-9 pl-9 w-full sm:w-56 text-sm"
@input="handleSearch"
/>
</div>
<Button variant="outline" size="icon" class="h-9 w-9" @click="loadLogs" :disabled="loading">
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadLogs" :disabled="loading">
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
</Button>
</div>
</div>
<div class="rounded-lg border bg-card">
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium">
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium min-w-[500px]">
<span class="w-24 shrink-0">用户名</span>
<span class="w-32 shrink-0">IP 地址</span>
<span class="w-16 shrink-0 text-center">状态</span>
<span class="flex-1">User Agent</span>
<span class="flex-1 hidden md:block">User Agent</span>
<span class="w-40 shrink-0 text-right">时间</span>
</div>
<!-- 列表 -->
<div class="divide-y">
<div class="divide-y min-w-[500px]">
<div v-if="logs.length === 0" class="text-sm text-muted-foreground text-center py-8">
暂无登录日志
</div>
@@ -107,7 +107,7 @@ onMounted(loadLogs)
<span class="w-16 shrink-0 flex justify-center">
<span :class="['h-2 w-2 rounded-full', log.status === 'success' ? 'bg-green-500' : 'bg-red-500']"></span>
</span>
<span class="flex-1 text-xs text-muted-foreground truncate" :title="log.user_agent">{{ log.user_agent || '-' }}</span>
<span class="flex-1 text-xs text-muted-foreground truncate hidden md:block" :title="log.user_agent">{{ log.user_agent || '-' }}</span>
<span class="w-40 shrink-0 text-right text-xs text-muted-foreground">{{ log.created_at }}</span>
</div>
</div>
+8 -8
View File
@@ -242,9 +242,9 @@ onMounted(loadTree)
</script>
<template>
<div class="flex h-[calc(100vh-2rem)] gap-3">
<div class="flex flex-col lg:flex-row h-[calc(100vh-2rem)] gap-3">
<!-- File Tree -->
<div class="w-56 flex-shrink-0 border rounded-lg bg-card flex flex-col">
<div class="w-full lg:w-56 flex-shrink-0 border rounded-lg bg-card flex flex-col max-h-[200px] lg:max-h-none">
<div class="p-2 border-b flex items-center justify-between">
<span class="text-xs font-medium">脚本文件</span>
<div class="flex gap-0.5">
@@ -277,21 +277,21 @@ onMounted(loadTree)
</div>
<!-- Editor -->
<div class="flex-1 border rounded-lg bg-card flex flex-col overflow-hidden">
<div class="flex-1 border rounded-lg bg-card flex flex-col overflow-hidden min-h-[300px]">
<div class="p-2 border-b flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-xs font-medium">{{ selectedFile || '未选择文件' }}</span>
<span v-if="hasChanges" class="text-xs text-orange-500"> 未保存</span>
<div class="flex items-center gap-2 min-w-0">
<span class="text-xs font-medium truncate">{{ selectedFile || '未选择文件' }}</span>
<span v-if="hasChanges" class="text-xs text-orange-500 shrink-0"> 未保存</span>
</div>
<Button
v-if="selectedFile"
size="sm"
class="h-6 text-xs gap-1"
class="h-6 text-xs gap-1 shrink-0"
:disabled="!hasChanges || saving"
@click="saveFile"
>
<Save class="h-3 w-3" />
{{ saving ? '保存中...' : '保存' }}
<span class="hidden sm:inline">{{ saving ? '保存中...' : '保存' }}</span>
</Button>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@ onMounted(loadAbout)
<p class="text-sm text-muted-foreground">一个轻量级的定时任务管理系统支持脚本管理定时执行和日志追踪</p>
</div>
<div class="grid md:grid-cols-2 gap-x-8 gap-y-5">
<div class="grid sm:grid-cols-2 gap-x-8 gap-y-5">
<!-- 左侧技术栈和功能特性 -->
<div class="space-y-5">
<div>
+3 -3
View File
@@ -90,12 +90,12 @@ onMounted(checkBackupStatus)
<template>
<div class="space-y-4">
<div class="flex items-center gap-4">
<Button @click="createBackup" :disabled="backupLoading" variant="outline">
<div class="flex flex-wrap items-center gap-3">
<Button @click="createBackup" :disabled="backupLoading" variant="outline" class="shrink-0">
<Archive class="w-4 h-4 mr-2" />
{{ backupLoading ? '备份中...' : '创建备份' }}
</Button>
<Button v-if="hasBackup" @click="downloadBackup" variant="outline">
<Button v-if="hasBackup" @click="downloadBackup" variant="outline" class="shrink-0">
<Download class="w-4 h-4 mr-2" />
下载备份
</Button>
+9 -9
View File
@@ -37,17 +37,17 @@ async function changePassword() {
<template>
<div class="space-y-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">原密码</Label>
<Input v-model="oldPassword" type="password" placeholder="请输入原密码" class="col-span-3" />
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">原密码</Label>
<Input v-model="oldPassword" type="password" placeholder="请输入原密码" class="sm:col-span-3" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">新密码</Label>
<Input v-model="newPassword" type="password" placeholder="请输入新密码(至少6位)" class="col-span-3" />
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">新密码</Label>
<Input v-model="newPassword" type="password" placeholder="请输入新密码(至少6位)" class="sm:col-span-3" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">确认密码</Label>
<Input v-model="confirmPassword" type="password" placeholder="请再次输入新密码" class="col-span-3" />
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">确认密码</Label>
<Input v-model="confirmPassword" type="password" placeholder="请再次输入新密码" class="sm:col-span-3" />
</div>
<div class="flex justify-end pt-2">
<Button @click="changePassword">修改密码</Button>
+12 -12
View File
@@ -63,25 +63,25 @@ onMounted(loadSettings)
<template>
<div class="space-y-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">Worker 数量</Label>
<div class="col-span-3 flex items-center gap-2">
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-2 sm:gap-4">
<Label class="sm:text-right pt-2">Worker数</Label>
<div class="sm:col-span-3 space-y-1">
<Input v-model="form.worker_count" type="number" class="w-24" />
<span class="text-xs text-muted-foreground">并发执行任务的 worker 数量</span>
<span class="text-xs text-muted-foreground block">并发执行任务的 worker 数量</span>
</div>
</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">
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-2 sm:gap-4">
<Label class="sm:text-right pt-2">队列大小</Label>
<div class="sm:col-span-3 space-y-1">
<Input v-model="form.queue_size" type="number" class="w-24" />
<span class="text-xs text-muted-foreground">任务队列缓冲区大小</span>
<span class="text-xs text-muted-foreground block">任务队列缓冲区大小</span>
</div>
</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">
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-2 sm:gap-4">
<Label class="sm:text-right pt-2">速率间隔</Label>
<div class="sm:col-span-3 space-y-1">
<Input v-model="form.rate_interval" type="number" class="w-24" />
<span class="text-xs text-muted-foreground">ms任务启动间隔200ms = 每秒最多5个</span>
<span class="text-xs text-muted-foreground block">ms任务启动间隔200ms = 每秒最多5个</span>
</div>
</div>
<div class="flex justify-end pt-2">
+8 -8
View File
@@ -14,17 +14,17 @@ const activeTab = ref('password')
<template>
<div class="space-y-6">
<div>
<h2 class="text-2xl font-bold tracking-tight">系统设置</h2>
<p class="text-muted-foreground">管理系统配置和账户安全</p>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">系统设置</h2>
<p class="text-muted-foreground text-sm">管理系统配置和账户安全</p>
</div>
<Tabs v-model="activeTab" class="w-full">
<TabsList>
<TabsTrigger value="password">密码修改</TabsTrigger>
<TabsTrigger value="site">站点设置</TabsTrigger>
<TabsTrigger value="scheduler">调度设置</TabsTrigger>
<TabsTrigger value="backup">备份恢复</TabsTrigger>
<TabsTrigger value="about">关于</TabsTrigger>
<TabsList class="flex-wrap h-auto gap-1">
<TabsTrigger value="password" class="text-xs sm:text-sm">密码修改</TabsTrigger>
<TabsTrigger value="site" class="text-xs sm:text-sm">站点设置</TabsTrigger>
<TabsTrigger value="scheduler" class="text-xs sm:text-sm">调度设置</TabsTrigger>
<TabsTrigger value="backup" class="text-xs sm:text-sm">备份恢复</TabsTrigger>
<TabsTrigger value="about" class="text-xs sm:text-sm">关于</TabsTrigger>
</TabsList>
<TabsContent value="password" class="mt-6">
+12 -12
View File
@@ -56,24 +56,24 @@ onMounted(loadSettings)
<template>
<div class="space-y-4">
<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 class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">站点标题</Label>
<Input v-model="form.title" placeholder="白虎面板" class="sm:col-span-3" />
</div>
<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 class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">站点标语</Label>
<Input v-model="form.subtitle" placeholder="轻量级定时任务管理系统" class="sm: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">
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">站点图标</Label>
<div class="sm: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-white dark:bg-white 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="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
<Label class="sm:text-right">分页/Cookie</Label>
<div class="sm:col-span-3 flex flex-wrap 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>
+16 -16
View File
@@ -210,36 +210,36 @@ onMounted(() => {
<template>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-2xl font-bold tracking-tight">定时任务</h2>
<p class="text-muted-foreground">管理和调度自动化任务</p>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">定时任务</h2>
<p class="text-muted-foreground text-sm">管理和调度自动化任务</p>
</div>
<div class="flex items-center gap-2">
<div class="relative">
<div class="relative flex-1 sm:flex-none">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input v-model="filterName" placeholder="搜索任务..." class="h-9 pl-9 w-56 text-sm" @input="handleSearch" />
<Input v-model="filterName" placeholder="搜索任务..." class="h-9 pl-9 w-full sm:w-56 text-sm" @input="handleSearch" />
</div>
<Button @click="openCreate">
<Plus class="h-4 w-4 mr-2" /> 新建任务
<Button @click="openCreate" class="shrink-0">
<Plus class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">新建任务</span>
</Button>
</div>
</div>
<div class="rounded-lg border bg-card">
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium">
<div class="flex items-center gap-4 px-4 py-2 border-b bg-muted/50 text-sm text-muted-foreground font-medium min-w-[700px]">
<span class="w-12 shrink-0">ID</span>
<span class="w-40 shrink-0">名称</span>
<span class="flex-1">命令</span>
<span class="w-32 shrink-0">定时规则</span>
<span class="w-40 shrink-0">上次执行</span>
<span class="w-40 shrink-0">下次执行</span>
<span class="w-32 shrink-0 hidden md:block">定时规则</span>
<span class="w-40 shrink-0 hidden lg:block">上次执行</span>
<span class="w-40 shrink-0 hidden lg:block">下次执行</span>
<span class="w-12 shrink-0 text-center">状态</span>
<span class="w-36 shrink-0 text-center">操作</span>
</div>
<!-- 列表 -->
<div class="divide-y">
<div class="divide-y min-w-[700px]">
<div v-if="tasks.length === 0" class="text-sm text-muted-foreground text-center py-8">
暂无任务
</div>
@@ -251,9 +251,9 @@ onMounted(() => {
<span class="w-12 shrink-0 text-muted-foreground text-sm">#{{ task.id }}</span>
<span class="w-40 font-medium truncate shrink-0 text-sm">{{ task.name }}</span>
<code class="flex-1 text-muted-foreground truncate text-xs bg-muted px-2 py-1 rounded">{{ task.command }}</code>
<code class="w-36 shrink-0 text-muted-foreground text-xs bg-muted px-2 py-1 rounded">{{ task.schedule }}</code>
<span class="w-40 shrink-0 text-muted-foreground text-xs">{{ task.last_run || '-' }}</span>
<span class="w-40 shrink-0 text-muted-foreground text-xs">{{ task.next_run || '-' }}</span>
<code class="w-36 shrink-0 text-muted-foreground text-xs bg-muted px-2 py-1 rounded hidden md:block">{{ task.schedule }}</code>
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.last_run || '-' }}</span>
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.next_run || '-' }}</span>
<span class="w-12 flex justify-center shrink-0 cursor-pointer" @click="toggleTask(task, !task.enabled)" :title="task.enabled ? '点击禁用' : '点击启用'">
<span :class="['w-2 h-2 rounded-full', task.enabled ? 'bg-green-500' : 'bg-gray-400']" />
</span>
+1 -1
View File
@@ -172,7 +172,7 @@ onUnmounted(() => {
</script>
<template>
<div class="flex flex-col h-[calc(100vh-100px)]">
<div class="flex flex-col h-[calc(100vh-120px)] sm:h-[calc(100vh-100px)]">
<div class="flex items-center justify-between p-2 border rounded-t-md bg-[#252526]">
<span class="text-xs font-medium text-gray-300">终端</span>
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white" @click="reconnect" title="重新连接">