chore: opt mobile style

This commit is contained in:
duorameng
2026-04-02 20:31:20 +08:00
parent 08715e0172
commit ad2dbd8523
9 changed files with 465 additions and 165 deletions
+3 -1
View File
@@ -275,7 +275,9 @@ export const api = {
listTokens: () => request<AgentToken[]>('/agents/tokens'),
createToken: (data: { remark?: string; max_uses?: number; expires_at?: string }) =>
request<AgentToken>('/agents/tokens', { method: 'POST', body: JSON.stringify(data) }),
deleteToken: (id: string) => request('/agents/tokens/' + id, { method: 'DELETE' })
deleteToken: (id: string) => request('/agents/tokens/' + id, { method: 'DELETE' }),
updateToken: (id: string, data: { remark?: string; max_uses?: number; expires_at?: string }) =>
request<AgentToken>('/agents/tokens/' + id, { method: 'PUT', body: JSON.stringify(data) })
},
mise: {
list: () => request<MiseLanguage[]>('/mise/ls'),
+104 -25
View File
@@ -29,9 +29,12 @@ const showEditDialog = ref(false)
const showDeleteDialog = ref(false)
const showDownloadDialog = ref(false)
const showTokenDialog = ref(false)
const showEditTokenDialog = ref(false)
const showDetailDialog = ref(false)
const formData = ref({ name: '', description: '' })
const tokenForm = ref({ remark: '', max_uses: 0, expires_at: '' })
const editingToken = ref<AgentToken | null>(null)
const editTokenForm = ref({ remark: '', max_uses: 0, expires_at: '' })
const editingAgent = ref<Agent | null>(null)
const deletingAgent = ref<Agent | null>(null)
const viewingAgent = ref<Agent | null>(null)
@@ -71,11 +74,13 @@ async function loadAgents() {
}
function viewDetail(agent: Agent) {
;(document.activeElement as HTMLElement)?.blur()
viewingAgent.value = agent
showDetailDialog.value = true
}
function openEditDialog(agent: Agent) {
;(document.activeElement as HTMLElement)?.blur()
editingAgent.value = agent
formData.value = { name: agent.name, description: agent.description }
showEditDialog.value = true
@@ -105,10 +110,21 @@ async function toggleEnabled(agent: Agent) {
}
function confirmDelete(agent: Agent) {
;(document.activeElement as HTMLElement)?.blur()
deletingAgent.value = agent
showDeleteDialog.value = true
}
function openDownloadDialog() {
;(document.activeElement as HTMLElement)?.blur()
showDownloadDialog.value = true
}
function openTokenDialog() {
;(document.activeElement as HTMLElement)?.blur()
showTokenDialog.value = true
}
async function deleteAgent() {
if (!deletingAgent.value) return
try {
@@ -170,6 +186,35 @@ async function deleteToken(id: string) {
}
}
function openEditToken(token: AgentToken) {
;(document.activeElement as HTMLElement)?.blur()
editingToken.value = token
// expires_at 后端格式 "2006-01-02 15:04:05",转回 datetime-local 格式
const rawExpires = token.expires_at?.replace(' ', 'T').slice(0, 16) || ''
editTokenForm.value = { remark: token.remark || '', max_uses: token.max_uses, expires_at: rawExpires }
showEditTokenDialog.value = true
}
async function updateToken() {
if (!editingToken.value) return
try {
let expiresAt = editTokenForm.value.expires_at
if (expiresAt) {
expiresAt = expiresAt.replace('T', ' ') + ':00'
}
await api.agents.updateToken(editingToken.value.id, {
remark: editTokenForm.value.remark,
max_uses: editTokenForm.value.max_uses,
expires_at: expiresAt || undefined
})
showEditTokenDialog.value = false
await loadAgents()
toast.success('更新成功')
} catch (e: unknown) {
toast.error((e as Error).message || '更新失败')
}
}
function isTokenExpired(token: AgentToken) {
if (!token.expires_at) return false
// 将 "YYYY-MM-DD HH:mm:ss" 转换为 ISO 格式 "YYYY-MM-DDTHH:mm:ss" 以提高浏览器兼容性
@@ -214,7 +259,7 @@ onUnmounted(() => {
<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-full sm:w-40 md:w-48 text-sm" />
</div>
<Button variant="outline" size="sm" class="h-9 shrink-0" @click="showDownloadDialog = true">
<Button variant="outline" size="sm" class="h-9 shrink-0" @click="openDownloadDialog">
<Download class="h-4 w-4 sm:mr-1.5" /> <span class="hidden sm:inline">下载</span>
</Button>
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadAgents" :disabled="loading">
@@ -224,7 +269,7 @@ onUnmounted(() => {
</div>
<Tabs v-model="activeTab">
<TabsList>
<TabsList class="w-full grid grid-cols-2">
<TabsTrigger value="agents">Agent 列表</TabsTrigger>
<TabsTrigger value="regcodes">
<Ticket class="h-4 w-4 mr-1" />令牌
@@ -380,28 +425,29 @@ onUnmounted(() => {
</TabsContent>
<TabsContent value="regcodes" class="mt-4">
<div class="rounded-lg border bg-card overflow-x-auto hide-scrollbar">
<div class="min-w-full w-max">
<div
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 border-b bg-muted/50 text-xs sm:text-sm text-muted-foreground font-medium">
<span class="w-6 shrink-0"></span>
<span class="flex-1 min-w-[200px]">令牌</span>
<span class="w-24 sm:w-32 shrink-0">备注</span>
<span class="w-16 sm:w-20 shrink-0 text-center">使用次数</span>
<span class="w-28 sm:w-36 shrink-0 hidden sm:block">过期时间</span>
<span class="w-20 shrink-0 flex justify-center">
<Button size="sm" class="h-7" @click="showTokenDialog = true">
<Plus class="h-3.5 w-3.5 mr-1" />生成
<div class="rounded-lg border bg-card">
<!-- 表头 -->
<div class="flex items-center gap-2 px-3 py-2 border-b bg-muted/50 text-xs text-muted-foreground font-medium">
<span class="w-5 shrink-0"></span>
<span class="flex-1 min-w-0">令牌</span>
<span class="w-20 shrink-0 hidden sm:block">备注</span>
<span class="w-14 shrink-0 text-center hidden sm:block">次数</span>
<span class="w-28 shrink-0 hidden md:block">过期时间</span>
<span class="w-16 shrink-0 flex justify-end">
<Button size="sm" class="h-7 px-2" @click="openTokenDialog">
<Plus class="h-3.5 w-3.5 sm:mr-1" /><span class="hidden sm:inline">生成</span>
</Button>
</span>
</div>
<!-- 数据行 -->
<div class="divide-y">
<div v-if="tokens.length === 0" class="text-center py-8 text-muted-foreground">
<Ticket class="h-8 w-8 mx-auto mb-2 opacity-50" />暂无令牌
</div>
<div v-for="token in tokens" :key="token.id"
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 hover:bg-muted/50 transition-colors">
<span class="w-6 shrink-0 flex justify-center">
class="flex items-center gap-2 px-3 py-2 hover:bg-muted/50 transition-colors">
<!-- 状态 -->
<span class="w-5 shrink-0 flex justify-center">
<div v-if="!isTokenExpired(token) && !isTokenExhausted(token)"
class="h-5 w-5 rounded-full bg-green-500/10 flex items-center justify-center">
<Check class="h-3 w-3 text-green-500 stroke-[3]" />
@@ -410,28 +456,33 @@ onUnmounted(() => {
<X class="h-3 w-3 text-red-500 stroke-[3]" />
</div>
</span>
<!-- Token截断省略号撑满剩余空间 -->
<code
class="flex-1 min-w-[200px] font-mono text-xs bg-muted px-2 py-0.5 rounded truncate">{{ token.token }}</code>
<span class="w-24 sm:w-32 shrink-0 text-xs sm:text-sm text-muted-foreground truncate">{{ token.remark ||
'-' }}</span>
<span class="w-16 sm:w-20 shrink-0 text-xs sm:text-sm text-muted-foreground text-center">
class="flex-1 min-w-0 font-mono text-xs bg-muted px-2 py-0.5 rounded truncate">{{ token.token }}</code>
<!-- 备注小屏隐藏 -->
<span class="w-20 shrink-0 text-xs text-muted-foreground truncate hidden sm:block">{{ token.remark || '-' }}</span>
<!-- 使用次数小屏隐藏 -->
<span class="w-14 shrink-0 text-xs text-muted-foreground text-center hidden sm:block">
{{ token.used_count }}/{{ token.max_uses === 0 ? '∞' : token.max_uses }}
</span>
<span class="w-28 sm:w-36 shrink-0 text-xs sm:text-sm text-muted-foreground hidden sm:block truncate">
<!-- 过期时间md 以上才显示 -->
<span class="w-28 shrink-0 text-xs text-muted-foreground truncate hidden md:block">
{{ token.expires_at || '永不过期' }}
</span>
<span class="w-20 shrink-0 flex justify-center gap-1">
<!-- 操作 -->
<span class="w-24 shrink-0 flex justify-end gap-1">
<Button variant="ghost" size="icon" class="h-7 w-7" @click="copyToken(token.token)" title="复制">
<Copy class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive" @click="deleteToken(token.id)"
title="删除">
<Button variant="ghost" size="icon" class="h-7 w-7" @click="openEditToken(token)" title="编辑">
<Edit class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive" @click="deleteToken(token.id)" title="删除">
<Trash2 class="h-3.5 w-3.5" />
</Button>
</span>
</div>
</div>
</div>
</div>
</TabsContent>
</Tabs>
@@ -629,5 +680,33 @@ onUnmounted(() => {
</DialogFooter>
</DialogContent>
</Dialog>
<!-- 编辑令牌对话框 -->
<Dialog v-model:open="showEditTokenDialog">
<DialogContent @openAutoFocus.prevent>
<DialogHeader>
<DialogTitle>编辑令牌</DialogTitle>
<DialogDescription class="sr-only">修改令牌的备注使用次数和过期时间</DialogDescription>
</DialogHeader>
<div class="space-y-4">
<div>
<Label>备注</Label>
<Input v-model="editTokenForm.remark" placeholder="备注信息(可选)" />
</div>
<div>
<Label>最大使用次数</Label>
<Input v-model.number="editTokenForm.max_uses" type="number" placeholder="0 表示无限制" />
</div>
<div>
<Label>过期时间</Label>
<Input v-model="editTokenForm.expires_at" type="datetime-local" />
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="showEditTokenDialog = false">取消</Button>
<Button @click="updateToken">保存</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</template>
+68 -12
View File
@@ -287,31 +287,87 @@ onBeforeUnmount(() => {
</p>
</div>
<div v-else class="rounded-lg border bg-card overflow-x-auto mt-4">
<!-- 表头 -->
<div v-else class="rounded-lg border bg-card mt-4 overflow-hidden">
<!-- 大屏表头 -->
<div
class="flex items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium min-w-[500px]">
class="hidden sm:flex items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
<span class="w-12 shrink-0 pl-1">序号</span>
<span class="w-32 sm:w-48 shrink-0">名称</span>
<span class="w-24 sm:flex-1 shrink-0 sm:shrink"></span>
<span class="flex-1 min-w-0"></span>
<span class="w-32 sm:w-48 shrink-0 hidden md:block">备注说明</span>
<span class="w-28 sm:w-36 shrink-0 text-center">操作</span>
<span class="w-32 shrink-0 text-center">操作</span>
</div>
<!-- 列表 -->
<div class="divide-y min-w-[500px]">
<div class="divide-y">
<div v-if="envVars.length === 0" class="text-sm text-muted-foreground text-center py-8">
{{ activeTab === ENV_TYPE.SECRET ? '暂无机密' : '暂无环境变量' }}
</div>
<div v-for="env in envVars" :key="env.id"
class="flex items-center gap-4 px-4 py-2 hover:bg-muted/30 transition-colors">
<!-- 小屏卡片布局 -->
<div v-for="(env, index) in envVars" :key="`mobile-${env.id}`"
class="sm:hidden p-3 hover:bg-muted/50 transition-colors">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-2 flex-1 min-w-0 pr-2">
<span class="text-xs text-muted-foreground shrink-0 tabular-nums">#{{ total - (currentPage - 1) * pageSize - index }}</span>
<code class="font-bold text-xs bg-muted/60 px-2 py-0.5 rounded break-all truncate">{{ env.name }}</code>
</div>
<span class="cursor-pointer group shrink-0"
@click="toggleEnabled(env)" :title="env.enabled ? '已启用' : '已禁用'">
<div v-if="env.enabled"
class="h-6 w-6 rounded-md bg-green-500/10 flex items-center justify-center group-hover:bg-green-500/20 transition-colors">
<Zap class="h-3.5 w-3.5 text-green-500 fill-green-500" />
</div>
<div v-else
class="h-6 w-6 rounded-md bg-muted flex items-center justify-center group-hover:bg-muted/80 transition-colors">
<ZapOff class="h-3.5 w-3.5 text-muted-foreground" />
</div>
</span>
</div>
<!-- 详情信息列表 -->
<div class="space-y-1.5 text-xs text-muted-foreground mb-3 px-1">
<div class="flex items-start gap-3">
<span class="w-8 shrink-0 font-medium mt-0.5 opacity-70">内容:</span>
<div class="flex-1 min-w-0 text-foreground break-all leading-relaxed">
<TextOverflow :text="showValues[env.id] ? env.value : maskValue(env.value)" title="查看值" />
</div>
</div>
<div v-if="env.remark" class="flex items-start gap-3">
<span class="w-8 shrink-0 font-medium mt-0.5 opacity-70">备注:</span>
<span class="flex-1 text-[11px] leading-relaxed">{{ env.remark }}</span>
</div>
</div>
<div class="flex items-center justify-end gap-1 pt-2 border-t">
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="toggleShow(env.id)">
<Eye v-if="!showValues[env.id]" class="h-3 w-3 mr-1" />
<EyeOff v-else class="h-3 w-3 mr-1" />
{{ showValues[env.id] ? '隐藏' : '显示' }}
</Button>
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="openEdit(env)">
<Pencil class="h-3 w-3 mr-1" />编辑
</Button>
<Button variant="ghost" size="sm" class="h-7 px-2 text-xs text-destructive" @click="confirmDelete(env.id)">
<Trash2 class="h-3 w-3 mr-1" />删除
</Button>
</div>
</div>
<!-- 大屏行布局 -->
<div v-for="(env, index) in envVars" :key="`desktop-${env.id}`"
class="hidden sm:flex items-center gap-4 px-4 py-2 hover:bg-muted/30 transition-colors">
<div class="w-12 shrink-0 pl-1">
<span class="text-muted-foreground text-xs tabular-nums">#{{ total - (currentPage - 1) * pageSize - index }}</span>
</div>
<code
class="w-32 sm:w-48 font-medium truncate shrink-0 text-xs bg-muted/40 px-2 py-1 rounded">{{ env.name }}</code>
<span class="w-24 sm:flex-1 shrink-0 sm:shrink font-mono text-muted-foreground truncate text-xs">
<span class="flex-1 min-w-0 text-muted-foreground truncate text-xs px-1">
<TextOverflow :text="showValues[env.id] ? env.value : maskValue(env.value)" title="查看值" />
</span>
<span class="w-32 sm:w-48 shrink-0 text-muted-foreground truncate text-sm hidden md:block">
<TextOverflow :text="env.remark || '-'" title="备注描述" />
</span>
<span class="w-28 sm:w-36 shrink-0 flex justify-center gap-1">
<div class="w-32 shrink-0 flex justify-center gap-1">
<Button variant="ghost" size="icon" class="h-7 w-7 rounded-md transition-all"
:class="env.enabled ? 'text-green-500 bg-green-500/10 hover:bg-green-500/20' : 'text-muted-foreground bg-muted/50 hover:bg-muted'"
@click="toggleEnabled(env)" :title="env.enabled ? '已启用(点击禁用)' : '已禁用(点击启用)'">
@@ -330,7 +386,7 @@ onBeforeUnmount(() => {
title="删除">
<Trash2 class="h-3.5 w-3.5" />
</Button>
</span>
</div>
</div>
</div>
<!-- 分页 -->
+1 -1
View File
@@ -335,7 +335,7 @@ onMounted(() => {
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">语言依赖</h2>
<p class="text-muted-foreground text-sm mt-0.5">管理系统环境中的编程语言运行时及相关包依赖 (Mise)</p>
<p class="text-muted-foreground text-sm mt-0.5">管理编程语言及相关包依赖(Mise)</p>
</div>
<div class="flex flex-col sm:flex-row items-center gap-3 shrink-0 w-full md:w-auto">
+52 -20
View File
@@ -124,37 +124,69 @@ onMounted(loadLogs)
</div>
</div>
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 表头 -->
<div class="rounded-lg border bg-card overflow-hidden">
<!-- 大屏表头 -->
<div
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 border-b bg-muted/50 text-xs sm:text-sm text-muted-foreground font-medium sm:min-w-[500px]">
<span class="w-16 sm:w-24 shrink-0">用户名</span>
<span class="w-20 sm:w-32 shrink-0">IP 地址</span>
<span class="w-10 sm:w-16 shrink-0 text-center">状态</span>
<span class="hidden sm:flex sm:flex-1">User Agent</span>
<span class="shrink-0 sm:w-40 sm:text-right">时间</span>
class="hidden sm:flex items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
<span class="w-16 shrink-0 pl-1">序号</span>
<span class="w-24 sm:w-32 shrink-0">用户名</span>
<span class="w-32 sm:w-40 shrink-0">IP 地址</span>
<span class="w-16 shrink-0 text-center">状态</span>
<span class="flex-1 min-w-0">User Agent</span>
<span class="w-40 shrink-0 text-right">登录时间</span>
</div>
<!-- 列表 -->
<div class="divide-y sm:min-w-[500px]">
<div class="divide-y">
<div v-if="logs.length === 0" class="text-sm text-muted-foreground text-center py-8">
暂无登录日志
</div>
<div v-for="log in logs" :key="log.id"
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 hover:bg-muted/50 transition-colors">
<span class="w-16 sm:w-24 shrink-0 font-medium text-xs sm:text-sm truncate">{{ log.username
}}</span>
<code
class="w-20 sm:w-32 shrink-0 text-xs text-muted-foreground bg-muted px-1 sm:px-2 py-0.5 sm:py-1 rounded truncate cursor-pointer hover:bg-muted/80 transition-colors"
@click="showIpInfo(log.ip)">{{ log.ip }}</code>
<span class="w-10 sm:w-16 shrink-0 flex justify-center">
<!-- 小屏卡片布局 -->
<div v-for="(log, index) in logs" :key="`mobile-${log.id}`"
class="sm:hidden p-3 hover:bg-muted/50 transition-colors">
<div class="flex items-start justify-between mb-3 border-b border-border/40 pb-2">
<div class="flex items-center gap-2 flex-1 min-w-0 mr-2">
<span class="text-xs text-muted-foreground shrink-0">#{{ total - (currentPage - 1) * pageSize - index }}</span>
<span class="font-bold text-sm truncate">{{ log.username }}</span>
</div>
<span
:class="['h-2 w-2 mt-1.5 rounded-full shrink-0', log.status === 'success' ? 'bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.4)]' : 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.4)]']"></span>
</div>
<!-- 详情信息列表 -->
<div class="space-y-1.5 text-xs text-muted-foreground mb-1 px-1">
<div class="flex items-center gap-3">
<span class="w-8 shrink-0 font-medium opacity-70">IP:</span>
<span
class="text-foreground bg-muted/40 px-1.5 py-0.5 rounded cursor-pointer hover:bg-muted/80 transition-colors"
@click="showIpInfo(log.ip)">{{ log.ip }}</span>
</div>
<div class="flex items-center gap-3">
<span class="w-8 shrink-0 font-medium opacity-70">时间:</span>
<span class="text-muted-foreground">{{ log.created_at }}</span>
</div>
</div>
</div>
<!-- 大屏行布局 -->
<div v-for="(log, index) in logs" :key="`desktop-${log.id}`"
class="hidden sm:flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors">
<span class="w-16 shrink-0 text-muted-foreground text-sm pl-1">#{{ total - (currentPage - 1) * pageSize - index }}</span>
<span class="w-24 sm:w-32 shrink-0 font-medium text-sm truncate">{{ log.username }}</span>
<div class="w-32 sm:w-40 shrink-0">
<code
class="text-xs text-muted-foreground bg-muted px-2 py-1 rounded truncate cursor-pointer hover:bg-muted/80 transition-colors inline-block"
@click="showIpInfo(log.ip)">{{ log.ip }}</code>
</div>
<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="hidden sm:flex sm:flex-1 text-xs text-muted-foreground truncate">
<span class="flex-1 min-w-0 text-xs text-muted-foreground truncate">
<TextOverflow :text="log.user_agent || '-'" title="User Agent" />
</span>
<span class="shrink-0 sm:w-40 sm:text-right text-xs text-muted-foreground">{{ log.created_at
}}</span>
<span class="w-40 shrink-0 text-right text-xs text-muted-foreground">{{ log.created_at }}</span>
</div>
</div>
<!-- 分页 -->
+50 -21
View File
@@ -127,11 +127,11 @@ const selectedLog = computed(() => logs.value.find((l: AppLog) => l.id === selec
function getLevelBadgeClass(level: string) {
switch (level) {
case LOG_LEVEL.INFO:
return 'bg-blue-500/10 text-blue-700 border-blue-200/50'
return 'bg-blue-500/15 text-blue-500 border-blue-500/30'
case LOG_LEVEL.WARNING:
return 'bg-yellow-500/10 text-yellow-700 border-yellow-200/50'
return 'bg-amber-500/15 text-amber-500 border-amber-500/30'
case LOG_LEVEL.ERROR:
return 'bg-red-500/10 text-red-700 border-red-200/50'
return 'bg-red-500/15 text-red-500 border-red-500/30'
default:
return 'bg-secondary text-secondary-foreground border-transparent'
}
@@ -217,34 +217,63 @@ function onDialogClose(open: boolean) {
</AlertDialog>
</div>
<div class="rounded-lg border bg-card overflow-x-auto">
<div class="rounded-lg border bg-card overflow-hidden">
<!-- 大屏表头 -->
<div
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 border-b bg-muted/50 text-xs sm:text-sm text-muted-foreground font-medium sm:min-w-[700px]">
<span class="w-12 sm:w-16 shrink-0">级别</span>
class="hidden sm:flex items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
<span class="w-16 shrink-0 pl-1">序号</span>
<span class="w-12 sm:w-16 shrink-0 text-center">级别</span>
<span class="w-40 sm:w-56 shrink-0">事件标题</span>
<span class="hidden sm:flex sm:flex-1">详情内容</span>
<span class="shrink-0 w-24 sm:w-40 sm:text-right">发生时间</span>
<span class="flex-1 min-w-0">详情内容</span>
<span class="w-40 shrink-0 text-right">发生时间</span>
</div>
<div class="divide-y sm:min-w-[700px]">
<!-- 列表 -->
<div class="divide-y">
<div v-if="logs.length === 0 && !loading" class="text-sm text-muted-foreground text-center py-8">
暂无系统事件
</div>
<div v-for="log in logs" :key="log.id"
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 hover:bg-muted/50 transition-colors cursor-pointer group"
<!-- 小屏卡片布局 -->
<div v-for="(log, index) in logs" :key="`mobile-${log.id}`"
class="sm:hidden p-3 hover:bg-muted/50 transition-colors cursor-pointer group"
:class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-2 flex-1 min-w-0 mr-2">
<span class="text-xs text-muted-foreground shrink-0 tabular-nums">#{{ total - (filters.page - 1) * pageSize - index }}</span>
<component :is="getLevelIcon(log.level)" :class="['h-4 w-4 shrink-0',
log.level === LOG_LEVEL.INFO ? 'text-blue-500' :
log.level === LOG_LEVEL.WARNING ? 'text-yellow-500' : 'text-red-500']" />
<span class="font-medium text-sm truncate" :title="log.title">{{ log.title }}</span>
</div>
</div>
<div class="bg-muted/30 rounded px-2 py-1.5 mb-2">
<div class="text-muted-foreground text-xs truncate">
{{ log.content || '-' }}
</div>
</div>
<div class="text-[10px] text-muted-foreground text-right tabular-nums">
{{ formatDate(log.created_at) }}
</div>
</div>
<!-- 大屏行布局 -->
<div v-for="(log, index) in logs" :key="`desktop-${log.id}`"
class="hidden sm:flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors cursor-pointer group"
:class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)">
<span class="w-16 shrink-0 text-muted-foreground text-sm tabular-nums pl-1">#{{ total - (filters.page - 1) * pageSize - index }}</span>
<span class="w-12 sm:w-16 shrink-0 flex justify-center">
<component :is="getLevelIcon(log.level)" :class="['h-4 w-4',
log.level === LOG_LEVEL.INFO ? 'text-blue-500' :
log.level === LOG_LEVEL.WARNING ? 'text-yellow-500' : 'text-red-500']" />
</span>
<span class="w-40 sm:w-56 shrink-0 font-medium text-xs sm:text-sm truncate" :title="log.title">{{
<span class="w-40 sm:w-56 shrink-0 font-medium text-sm truncate" :title="log.title">{{
log.title }}</span>
<span class="hidden sm:flex sm:flex-1 text-xs sm:text-sm text-muted-foreground truncate"
<span class="flex-1 min-w-0 text-sm text-muted-foreground truncate"
:title="log.content">
{{ log.content || '-' }}
</span>
<span class="shrink-0 w-24 sm:w-40 sm:text-right text-xs text-muted-foreground">
<span class="w-40 shrink-0 text-right text-xs text-muted-foreground tabular-nums">
{{ formatDate(log.created_at) }}
</span>
</div>
@@ -259,10 +288,10 @@ function onDialogClose(open: boolean) {
<div class="flex items-center justify-between pr-8">
<DialogTitle>事件详情</DialogTitle>
<Badge variant="outline" :class="[
'px-2 py-0.5 text-[10px] font-bold rounded-full border shadow-sm',
'px-2 py-0.5 text-[10px] font-bold rounded-md border shadow-sm',
selectedLog ? getLevelBadgeClass(selectedLog.level) : ''
]">
<div class="flex items-center gap-1.5 uppercase tracking-wider">
<div class="flex items-center gap-1 uppercase tracking-tighter">
<component :is="getLevelIcon(selectedLog?.level || 'info')" class="h-3 w-3" />
<span>{{ selectedLog?.level || 'INFO' }}</span>
</div>
@@ -273,11 +302,11 @@ function onDialogClose(open: boolean) {
<div class="flex-1 overflow-y-auto">
<div class="px-6 py-4 border-b space-y-3 bg-card">
<div class="flex justify-between items-center text-sm">
<span class="text-muted-foreground">标题</span>
<span class="font-medium text-foreground">{{ detailDialogProps.title }}</span>
<span class="text-muted-foreground font-medium">标题</span>
<span class="font-bold text-foreground">{{ detailDialogProps.title }}</span>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-muted-foreground">发生时间</span>
<span class="text-muted-foreground font-medium">发生时间</span>
<span class="font-mono text-xs text-muted-foreground">{{ selectedLog ?
formatDate(selectedLog.created_at) : '-' }}</span>
</div>
@@ -285,7 +314,7 @@ function onDialogClose(open: boolean) {
<div class="flex flex-col min-h-0 bg-muted/5">
<div
class="px-6 py-2.5 text-xs font-semibold text-muted-foreground border-b bg-muted/10 uppercase tracking-wider">
class="px-6 py-2.5 text-[10px] font-bold text-muted-foreground border-b bg-muted/10 uppercase tracking-widest">
内容详情
</div>
<div class="p-6">
@@ -298,7 +327,7 @@ function onDialogClose(open: boolean) {
<template v-if="detailDialogProps.error">
<div
class="px-6 py-2.5 text-xs font-semibold uppercase tracking-wider border-y bg-muted/10 text-muted-foreground border-border/60">
class="px-6 py-2.5 text-[10px] font-bold uppercase tracking-widest border-y bg-muted/10 text-muted-foreground border-border/60">
错误信息
</div>
<div class="p-6">
+54 -20
View File
@@ -129,9 +129,9 @@ const selectedLog = computed(() => logs.value.find((l: AppLog) => l.id === selec
function getStatusBadgeClass(status: string) {
switch (status) {
case LOG_STATUS.SUCCESS:
return 'bg-green-500/10 text-green-700 border-green-200/50 dark:bg-green-500/20 dark:text-green-400 dark:border-green-900/50'
return 'bg-green-500/15 text-green-500 border-green-500/30'
case LOG_STATUS.FAILED:
return 'bg-red-500/10 text-red-700 border-red-200/50 dark:bg-red-500/20 dark:text-red-400 dark:border-red-900/50'
return 'bg-red-500/15 text-red-500 border-red-500/30'
default:
return 'bg-secondary text-secondary-foreground border-transparent'
}
@@ -212,38 +212,72 @@ function onDialogClose(open: boolean) {
</AlertDialog>
</div>
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 表头 -->
<div class="rounded-lg border bg-card overflow-hidden">
<!-- 大屏表头 -->
<div
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 border-b bg-muted/50 text-xs sm:text-sm text-muted-foreground font-medium sm:min-w-[700px]">
<span class="w-12 sm:w-16 shrink-0">序号</span>
<span class="w-40 sm:w-56 shrink-0">标题及渠道</span>
<span class="hidden sm:flex sm:flex-1">内容详情</span>
<span class="w-10 sm:w-16 shrink-0 text-center">状态</span>
<span class="shrink-0 w-24 sm:w-40 sm:text-right">发送时间</span>
class="hidden sm:flex items-center gap-4 px-4 py-2 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
<span class="w-16 shrink-0 pl-1">序号</span>
<span class="w-56 shrink-0">标题及渠道</span>
<span class="flex-1 min-w-0">内容详情</span>
<span class="w-16 shrink-0 text-center">状态</span>
<span class="w-40 shrink-0 text-right">发送时间</span>
</div>
<!-- 列表 -->
<div class="divide-y sm:min-w-[700px]">
<div class="divide-y">
<div v-if="logs.length === 0 && !loading" class="text-sm text-muted-foreground text-center py-8">
暂无推送记录
</div>
<div v-for="(log, index) in logs" :key="log.id"
class="flex items-center gap-2 sm:gap-4 px-3 sm:px-4 py-2 hover:bg-muted/50 transition-colors cursor-pointer group"
<!-- 小屏卡片布局 -->
<div v-for="(log, index) in logs" :key="`mobile-${log.id}`"
class="sm:hidden p-3 hover:bg-muted/50 transition-colors cursor-pointer group" @click="showDetail(log)"
:class="[selectedLogId === log.id && 'bg-accent/50']">
<div class="flex items-start justify-between mb-3 border-b border-border/40 pb-2">
<div class="flex items-center gap-2 flex-1 min-w-0 mr-2">
<span class="text-xs text-muted-foreground shrink-0">#{{ getLogIndex(index) }}</span>
<span class="font-bold text-sm truncate">{{ log.title }}</span>
</div>
<span
:class="['h-2 w-2 mt-1.5 rounded-full shrink-0', log.status === LOG_STATUS.SUCCESS ? 'bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.4)]' : 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.4)]']"></span>
</div>
<!-- 详情信息列表 -->
<div class="space-y-1.5 text-xs text-muted-foreground mb-1 px-1">
<div v-if="log.channel_name" class="flex items-center gap-3">
<span class="w-8 shrink-0 font-medium opacity-70">渠道:</span>
<span class="text-foreground bg-muted/40 px-1.5 py-0.5 rounded text-[10px]">{{ log.channel_name }}</span>
</div>
<div class="flex items-start gap-3">
<span class="w-8 shrink-0 font-medium mt-0.5 opacity-70">内容:</span>
<div class="flex-1 min-w-0 text-foreground break-all leading-relaxed line-clamp-2">
{{ log.content || '-' }}
</div>
</div>
<div class="flex items-center gap-3">
<span class="w-8 shrink-0 font-medium opacity-70">时间:</span>
<span class="text-[10px] text-muted-foreground">{{ formatDate(log.created_at) }}</span>
</div>
</div>
</div>
<!-- 大屏行布局 -->
<div v-for="(log, index) in logs" :key="`desktop-${log.id}`"
class="hidden sm:flex items-center gap-4 px-4 py-2 hover:bg-muted/50 transition-colors cursor-pointer group"
:class="[selectedLogId === log.id && 'bg-accent/50']" @click="showDetail(log)">
<span class="w-12 sm:w-16 shrink-0 text-muted-foreground text-xs sm:text-sm">#{{ getLogIndex(index) }}</span>
<span class="w-40 sm:w-56 shrink-0 font-medium text-xs sm:text-sm truncate" :title="log.title">
<span class="w-16 shrink-0 text-muted-foreground text-sm pl-1">#{{ getLogIndex(index) }}</span>
<span class="w-56 shrink-0 font-medium text-sm truncate" :title="log.title">
<span v-if="log.channel_name" class="mr-1 text-muted-foreground">[{{ log.channel_name }}]</span>{{ log.title
}}
</span>
<span class="hidden sm:flex sm:flex-1 text-xs sm:text-sm text-muted-foreground truncate" :title="log.content">
<span class="flex-1 min-w-0 text-sm text-muted-foreground truncate" :title="log.content">
{{ log.content || '-' }}
</span>
<span class="w-10 sm:w-16 shrink-0 flex justify-center">
<span class="w-16 shrink-0 flex justify-center">
<span
:class="['h-2 w-2 rounded-full', log.status === LOG_STATUS.SUCCESS ? 'bg-green-500' : 'bg-red-500']"></span>
</span>
<span class="shrink-0 w-24 sm:w-40 sm:text-right text-xs text-muted-foreground">
<span class="w-40 shrink-0 text-right text-xs text-muted-foreground">
{{ formatDate(log.created_at) }}
</span>
</div>
@@ -259,10 +293,10 @@ function onDialogClose(open: boolean) {
<div class="flex items-center justify-between pr-8">
<DialogTitle>日志详情</DialogTitle>
<Badge variant="outline" :class="[
'px-2 py-0.5 text-[10px] font-bold rounded-full border shadow-sm transition-all duration-300',
'px-2 py-0.5 text-[10px] font-bold rounded-md border shadow-sm transition-all duration-300',
selectedLog ? getStatusBadgeClass(selectedLog.status) : ''
]">
<div class="flex items-center gap-1.5 uppercase tracking-wider">
<div class="flex items-center gap-1 uppercase tracking-tighter">
<Check v-if="selectedLog?.status === LOG_STATUS.SUCCESS" class="h-3 w-3" />
<X v-else class="h-3 w-3" />
<span>{{ selectedLog?.status === LOG_STATUS.SUCCESS ? 'Success' : 'Failed' }}</span>
+132 -64
View File
@@ -400,56 +400,129 @@ watch(() => route.query.agent_id, (newVal: any) => {
</div>
</div>
<div class="rounded-lg border bg-card overflow-x-auto">
<!-- 表头 -->
<div class="rounded-lg border bg-card">
<!-- 大屏表头 -->
<div
class="flex flex-wrap sm:flex-nowrap items-center gap-x-2 gap-y-2 sm:gap-4 px-3 sm:px-4 py-2 sm:py-1.5 border-b bg-muted/20 text-xs sm:text-sm text-muted-foreground font-medium min-w-0 sm:min-w-[1000px]">
<div class="w-10 sm:w-12 shrink-0 flex items-center gap-2 max-sm:order-1 pl-1">
<span class="text-xs sm:text-sm">序号</span>
</div>
<span class="w-8 shrink-0 text-center max-sm:order-2">类型</span>
<span class="flex-1 min-w-0 sm:flex-none sm:w-40 md:w-48 lg:w-56 shrink-0 max-sm:order-3">名称</span>
<span class="w-24 sm:w-32 shrink-0 hidden md:block">执行位置</span>
<span class="w-8 shrink-0 text-center max-sm:order-4 max-sm:ml-auto">状态</span>
<div class="w-full hidden max-sm:block max-sm:order-5 mt-1 border-t border-muted/10 opacity-50"></div>
<span class="flex-1 min-w-[120px] max-sm:order-6 block sm:block max-sm:mt-1 flex items-center gap-1.5">
<Terminal class="h-3.5 w-3.5 sm:hidden opacity-50" />命令/地址
class="hidden sm:flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
<span class="w-12 shrink-0 pl-1">序号</span>
<span class="w-8 shrink-0 text-center">类型</span>
<span class="w-40 md:w-48 lg:w-56 shrink-0">名称</span>
<span class="w-32 shrink-0 hidden md:block">执行位置</span>
<span class="w-8 shrink-0 text-center">状态</span>
<span class="flex-1 min-w-0 flex items-center gap-1.5">
<Terminal class="h-3.5 w-3.5 opacity-50" />命令/地址
</span>
<span class="w-28 shrink-0 hidden md:block">定时规则</span>
<span class="w-40 shrink-0 hidden lg:block">执行时间</span>
<span class="w-28 sm:w-32 shrink-0 text-right sm:text-center max-sm:order-7 max-sm:mt-1">操作</span>
<span class="w-36 shrink-0 text-center">操作</span>
</div>
<!-- 列表 -->
<div class="divide-y min-w-0 sm:min-w-[1000px]">
<div class="divide-y">
<div v-if="tasks.length === 0" class="text-sm text-muted-foreground text-center py-8">
暂无任务
</div>
<div v-for="(task, index) in tasks" :key="task.id"
class="flex flex-wrap sm:flex-nowrap items-center gap-x-2 gap-y-2 sm:gap-4 px-3 sm:px-4 py-2.5 sm:py-1.5 hover:bg-muted/30 transition-colors">
<div class="w-10 sm:w-12 shrink-0 flex items-center gap-2 max-sm:order-1 pl-1">
<span class="text-muted-foreground text-xs sm:text-sm">#{{ total -
(currentPage - 1) * pageSize - index }}</span>
<!-- 小屏卡片布局 -->
<div v-for="(task, index) in tasks" :key="`mobile-${task.id}`"
class="sm:hidden p-3 hover:bg-muted/50 transition-colors">
<!-- 标题行 -->
<div class="flex items-start justify-between mb-3 border-b border-border/40 pb-2">
<div class="flex items-center gap-2 flex-1 min-w-0 pr-2">
<span class="text-xs text-muted-foreground tabular-nums flex-shrink-0">#{{ total - (currentPage - 1) * pageSize - index }}</span>
<span class="shrink-0" :title="getTaskTypeTitle(task.type || 'task')">
<GitBranch v-if="task.type === TASK_TYPE.REPO" class="h-3.5 w-3.5 text-primary" />
<Terminal v-else class="h-3.5 w-3.5 text-primary" />
</span>
<span class="font-bold text-sm truncate" :title="task.name">{{ task.name }}</span>
</div>
<div class="flex items-center gap-2 shrink-0">
<span class="cursor-pointer group"
@click="toggleTask(task, !task.enabled)"
:title="task.enabled ? '点击禁用' : '点击启用'">
<div v-if="task.enabled"
class="h-6 w-6 rounded-md bg-green-500/10 flex items-center justify-center group-hover:bg-green-500/20 transition-colors">
<Zap class="h-3.5 w-3.5 text-green-500 fill-green-500" />
</div>
<div v-else
class="h-6 w-6 rounded-md bg-muted flex items-center justify-center group-hover:bg-muted/80 transition-colors">
<ZapOff class="h-3.5 w-3.5 text-muted-foreground" />
</div>
</span>
</div>
</div>
<span class="w-8 shrink-0 flex justify-center max-sm:order-2" :title="getTaskTypeTitle(task.type || 'task')">
<GitBranch v-if="task.type === TASK_TYPE.REPO" class="h-3.5 w-3.5 sm:h-4 sm:w-4 text-primary" />
<Terminal v-else class="h-3.5 w-3.5 sm:h-4 sm:w-4 text-primary" />
<!-- 详情信息列表 -->
<div class="space-y-1.5 text-xs text-muted-foreground mb-3 px-1">
<div class="flex items-center gap-3">
<span class="w-10 shrink-0 font-medium opacity-70">定时:</span>
<span v-if="task.trigger_type === TRIGGER_TYPE.BAIHU_STARTUP"
class="text-[10px] leading-tight bg-blue-500/10 text-blue-500 px-1.5 py-0.5 rounded font-medium">服务启动时</span>
<span v-else-if="task.schedule" class="text-xs text-foreground bg-muted/40 px-1.5 py-0.5 rounded">{{ task.schedule }}</span>
<span v-else class="text-muted-foreground opacity-50">-</span>
</div>
<div class="flex items-start gap-3">
<span class="w-10 shrink-0 font-medium mt-0.5 opacity-70">命令:</span>
<div class="flex-1 min-w-0 overflow-hidden text-foreground">
<TextOverflow :text="task.command" :title="task.type === TASK_TYPE.REPO ? '同步地址' : '执行命令'" class="truncate opacity-80" />
</div>
</div>
<div v-if="task.tags" class="flex items-start gap-3">
<span class="w-10 shrink-0 font-medium mt-0.5 opacity-70">标签:</span>
<div class="flex flex-wrap gap-1 flex-1">
<span v-for="tag in task.tags.split(',').filter(Boolean)" :key="tag"
class="text-[9px] leading-none px-1.5 py-0.5 bg-primary/10 text-primary/80 rounded-full border border-primary/20 font-semibold">
{{ tag }}
</span>
</div>
</div>
<div v-if="task.remark" class="flex items-start gap-3">
<span class="w-10 shrink-0 font-medium mt-0.5 opacity-70">备注:</span>
<span class="flex-1 text-[11px] truncate">{{ task.remark }}</span>
</div>
</div>
<!-- 操作按钮行 -->
<div class="flex items-center justify-end gap-0.5 pt-2 border-t border-border/40">
<Button variant="ghost" class="h-7 px-1.5 text-[10px] gap-1" @click="runTask(task.id)" title="执行" :disabled="executingTaskId === task.id">
<Loader2 v-if="executingTaskId === task.id" class="h-3 w-3 animate-spin" />
<Play v-else class="h-3 w-3" />执行
</Button>
<Button variant="ghost" class="h-7 px-1.5 text-[10px] gap-1" @click="viewLogs(task.id)" title="日志">
<ScrollText class="h-3 w-3" />日志
</Button>
<Button variant="ghost" class="h-7 px-1.5 text-[10px] gap-1" @click="openEdit(task)" title="编辑">
<Pencil class="h-3 w-3" />编辑
</Button>
<Button variant="ghost" class="h-7 px-1.5 text-[10px] gap-1" @click="duplicateTask(task)" title="克隆">
<Copy class="h-3 w-3" />克隆
</Button>
<Button variant="ghost" class="h-7 px-1.5 text-[10px] gap-1 text-destructive" @click="confirmDelete(task.id)" title="删除">
<Trash2 class="h-3 w-3" />删除
</Button>
</div>
</div>
<!-- 大屏行布局 -->
<div v-for="(task, index) in tasks" :key="`desktop-${task.id}`"
class="hidden sm:flex items-center gap-4 px-4 py-1.5 hover:bg-muted/30 transition-colors">
<div class="w-12 shrink-0 pl-1">
<span class="text-muted-foreground text-sm">#{{ total - (currentPage - 1) * pageSize - index }}</span>
</div>
<span class="w-8 shrink-0 flex justify-center" :title="getTaskTypeTitle(task.type || 'task')">
<GitBranch v-if="task.type === TASK_TYPE.REPO" class="h-4 w-4 text-primary" />
<Terminal v-else class="h-4 w-4 text-primary" />
</span>
<div
class="flex-1 min-w-0 sm:flex-none sm:w-40 md:w-48 lg:w-56 shrink-0 flex flex-col justify-center gap-0.5 overflow-hidden max-sm:order-3">
<span class="font-medium truncate text-xs sm:text-sm cursor-help block w-full" :title="task.name">{{
task.name }}</span>
<div class="w-40 md:w-48 lg:w-56 shrink-0 flex flex-col justify-center gap-0.5 overflow-hidden">
<span class="font-medium truncate text-sm cursor-help" :title="task.name">{{ task.name }}</span>
<div v-if="task.tags" class="flex items-center gap-1 overflow-hidden" :title="task.tags">
<span v-for="tag in task.tags.split(',').filter(Boolean).slice(0, 3)" :key="tag"
class="truncate text-[10px] leading-none px-1 py-0.5 bg-secondary text-secondary-foreground rounded border">
{{ tag }}
</span>
<span v-if="task.tags.split(',').filter(Boolean).length > 3"
class="text-[10px] text-muted-foreground">...</span>
<span v-if="task.tags.split(',').filter(Boolean).length > 3" class="text-[10px] text-muted-foreground">...</span>
</div>
</div>
<span class="w-24 sm:w-32 shrink-0 hidden md:flex items-center gap-1 text-xs" :title="getExecutorName(task)">
<span class="w-32 shrink-0 hidden md:flex items-center gap-1 text-xs" :title="getExecutorName(task)">
<Monitor v-if="!task.agent_id" class="h-3 w-3 text-muted-foreground" />
<template v-else>
<Wifi v-if="getExecutorStatus(task) === 'online'" class="h-3 w-3 text-green-500" />
@@ -457,11 +530,20 @@ watch(() => route.query.agent_id, (newVal: any) => {
</template>
<span class="truncate">{{ getExecutorName(task) }}</span>
</span>
<code
class="flex-1 min-w-[120px] text-muted-foreground truncate text-xs bg-muted/40 px-2 py-1 rounded block sm:block max-sm:order-6 overflow-hidden max-sm:mt-1">
<TextOverflow :text="task.command" :title="task.type === TASK_TYPE.REPO ? '同步地址' : '执行命令'" class="truncate" />
</code>
<span class="w-8 flex justify-center shrink-0 cursor-pointer group"
@click="toggleTask(task, !task.enabled)" :title="task.enabled ? '点击禁用' : '点击启用'">
<div v-if="task.enabled"
class="h-6 w-6 rounded-md bg-green-500/10 flex items-center justify-center group-hover:bg-green-500/20 transition-colors">
<Zap class="h-3.5 w-3.5 text-green-500 fill-green-500" />
</div>
<div v-else
class="h-6 w-6 rounded-md bg-muted flex items-center justify-center group-hover:bg-muted/80 transition-colors">
<ZapOff class="h-3.5 w-3.5 text-muted-foreground" />
</div>
</span>
<code class="flex-1 min-w-0 text-muted-foreground truncate text-xs bg-muted/40 px-2 py-1 rounded overflow-hidden">
<TextOverflow :text="task.command" :title="task.type === TASK_TYPE.REPO ? '同步地址' : '执行命令'" class="truncate" />
</code>
<div class="w-28 shrink-0 hidden md:flex flex-col items-start justify-center gap-1 overflow-hidden">
<span v-if="task.trigger_type === TRIGGER_TYPE.BAIHU_STARTUP"
class="text-[10px] leading-tight bg-blue-500/10 text-blue-500 px-2 py-1 rounded-md whitespace-nowrap font-medium">服务启动时</span>
@@ -477,38 +559,24 @@ watch(() => route.query.agent_id, (newVal: any) => {
下: {{ task.next_run || '-' }}
</span>
</div>
<span class="w-8 flex justify-center shrink-0 cursor-pointer group max-sm:order-4 max-sm:ml-auto"
@click="toggleTask(task, !task.enabled)" :title="task.enabled ? '点击禁用' : '点击启用'">
<div v-if="task.enabled"
class="h-6 w-6 rounded-md bg-green-500/10 flex items-center justify-center group-hover:bg-green-500/20 transition-colors">
<Zap class="h-3.5 w-3.5 text-green-500 fill-green-500" />
</div>
<div v-else
class="h-6 w-6 rounded-md bg-muted flex items-center justify-center group-hover:bg-muted/80 transition-colors">
<ZapOff class="h-3.5 w-3.5 text-muted-foreground" />
</div>
</span>
<div class="w-full hidden max-sm:block max-sm:order-5 -my-0.5"></div>
<span class="w-auto sm:w-32 shrink-0 flex justify-end sm:justify-center gap-1 max-sm:order-7 max-sm:mt-1">
<Button variant="ghost" size="icon" class="h-6 w-6 sm:h-7 sm:w-7" @click="runTask(task.id)" title="执行"
<span class="w-36 shrink-0 flex justify-center gap-1">
<Button variant="ghost" size="icon" class="h-7 w-7" @click="runTask(task.id)" title="执行"
:disabled="executingTaskId === task.id">
<Loader2 v-if="executingTaskId === task.id" class="h-3 w-3 sm:h-3.5 sm:w-3.5 animate-spin" />
<Play v-else class="h-3 w-3 sm:h-3.5 sm:w-3.5" />
<Loader2 v-if="executingTaskId === task.id" class="h-3.5 w-3.5 animate-spin" />
<Play v-else class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-6 w-6 sm:h-7 sm:w-7" @click="viewLogs(task.id)" title="日志">
<ScrollText class="h-3 w-3 sm:h-3.5 sm:w-3.5" />
<Button variant="ghost" size="icon" class="h-7 w-7" @click="viewLogs(task.id)" title="日志">
<ScrollText class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-6 w-6 sm:h-7 sm:w-7" @click="openEdit(task)" title="编辑">
<Pencil class="h-3 w-3 sm:h-3.5 sm:w-3.5" />
<Button variant="ghost" size="icon" class="h-7 w-7" @click="openEdit(task)" title="编辑">
<Pencil class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-6 w-6 sm:h-7 sm:w-7" @click="duplicateTask(task)" title="克隆">
<Copy class="h-3 w-3 sm:h-3.5 sm:w-3.5" />
<Button variant="ghost" size="icon" class="h-7 w-7" @click="duplicateTask(task)" title="克隆">
<Copy class="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" class="h-6 w-6 sm:h-7 sm:w-7 text-destructive"
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive"
@click="confirmDelete(task.id)" title="删除">
<Trash2 class="h-3 w-3 sm:h-3.5 sm:w-3.5" />
<Trash2 class="h-3.5 w-3.5" />
</Button>
</span>
</div>