feat: adapt mobile phone style
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="重新连接">
|
||||
|
||||
Reference in New Issue
Block a user