feat(frontend): align custom channels table layout and styling with events tab

This commit is contained in:
ryan
2026-06-15 15:15:05 +08:00
parent 2a3b9f8a5f
commit 1425fd1dbb
@@ -15,7 +15,6 @@ import {Label} from "@/components/ui/label"
import {Textarea} from "@/components/ui/textarea" import {Textarea} from "@/components/ui/textarea"
import {Switch} from "@/components/ui/switch" import {Switch} from "@/components/ui/switch"
import {Badge} from "@/components/ui/badge" import {Badge} from "@/components/ui/badge"
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow,} from "@/components/ui/table" import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow,} from "@/components/ui/table"
import { import {
Dialog, Dialog,
@@ -254,120 +253,122 @@ export function SettingsTab() {
return ( return (
<div className="pt-4 space-y-6"> <div className="pt-4 space-y-4">
<Card className="h-full flex flex-col"> <div className="flex items-center justify-between">
<CardHeader className="pb-3 border-b bg-muted/10"> <div>
<div className="flex items-center justify-between"> <h2 className="text-sm font-semibold">自定义推送通道</h2>
<div> <p className="text-[11px] text-muted-foreground mt-0.5">
<CardTitle className="text-base font-semibold">自定义推送通道</CardTitle> 添加、配置及管理用于第三方 Webhook 对接的自定义数据推送通道
<p className="text-xs text-muted-foreground mt-0.5"> </p>
添加、配置及管理用于第三方 Webhook 对接的自定义数据推送通道 </div>
</p> <Button size="sm" onClick={handleCreateChannelClick} className="text-xs">
</div> <Plus className="size-3.5 mr-1" />
<Button size="sm" onClick={handleCreateChannelClick} className="text-xs"> 新建消息通道
<Plus className="size-3.5 mr-1" /> </Button>
新建消息通道 </div>
</Button>
</div> {channelsQuery.isLoading ? (
</CardHeader> <LoadingStateWithBorder icon={Settings} description="加载消息通道中..." />
<CardContent className="p-0 flex-1 overflow-x-auto"> ) : channelsQuery.isError ? (
{channelsQuery.isLoading ? ( <div className="p-8 border border-dashed rounded-xl bg-card">
<div className="p-8"> <ErrorInline error={channelsQuery.error} onRetry={() => channelsQuery.refetch()} className="justify-center" />
<LoadingStateWithBorder icon={Settings} description="加载消息通道中..." className="border-0 shadow-none animate-pulse" /> </div>
</div> ) : (channelsQuery.data ?? []).length === 0 ? (
) : channelsQuery.isError ? ( <div className="py-12 border border-dashed rounded-lg flex flex-col items-center justify-center text-muted-foreground">
<div className="p-8"> <Settings className="size-8 mb-2 opacity-30 animate-spin" style={{ animationDuration: '3s' }} />
<ErrorInline error={channelsQuery.error} onRetry={() => channelsQuery.refetch()} className="justify-center" /> <span className="text-xs font-medium">暂无自定义通道配置,请点击右上角新建</span>
</div> </div>
) : (channelsQuery.data ?? []).length === 0 ? ( ) : (
<div className="py-12 flex flex-col items-center justify-center text-muted-foreground"> <div className="border border-dashed shadow-none rounded-lg overflow-hidden">
<Settings className="size-8 mb-2 opacity-30 animate-spin" style={{ animationDuration: '3s' }} /> <Table className="w-full caption-bottom text-sm min-w-full">
<span className="text-xs font-medium">暂无自定义通道配置,请点击右上角新建</span> <TableHeader className="sticky top-0 z-20 bg-background">
</div> <TableRow className="border-b border-dashed hover:bg-transparent">
) : ( <TableHead className="w-[120px] whitespace-nowrap py-2 h-8">名称</TableHead>
<Table> <TableHead className="w-[100px] whitespace-nowrap py-2 h-8">类型</TableHead>
<TableHeader> <TableHead className="whitespace-nowrap py-2 h-8">备注</TableHead>
<TableRow className="bg-muted/30"> <TableHead className="w-[80px] text-center whitespace-nowrap py-2 h-8">状态</TableHead>
<TableHead className="text-xs font-semibold">名称</TableHead> <TableHead className="sticky right-0 text-center bg-background z-10 w-[180px] py-2 h-8">操作</TableHead>
<TableHead className="text-xs font-semibold">类型</TableHead> </TableRow>
<TableHead className="text-xs font-semibold">备注</TableHead> </TableHeader>
<TableHead className="text-xs font-semibold w-[80px] text-center">状态</TableHead> <TableBody>
<TableHead className="text-xs font-semibold w-[180px] text-right">操作</TableHead> {(channelsQuery.data ?? []).map(ch => (
</TableRow> <TableRow
</TableHeader> key={ch.id}
<TableBody> className="border-dashed hover:bg-muted/30 cursor-pointer group"
{(channelsQuery.data ?? []).map(ch => ( onClick={() => handleEditChannelClick(ch)}
<TableRow key={ch.id} className="hover:bg-muted/5"> >
<TableCell className="text-xs font-mono font-bold"> <TableCell className="text-xs font-mono font-bold py-1">
{ch.name} {ch.name}
</TableCell> </TableCell>
<TableCell className="text-xs"> <TableCell className="py-1">
<Badge variant="outline" className="text-[10px] whitespace-nowrap"> <Badge variant="outline" className="text-[10px] py-0 px-1.5 h-4.5 whitespace-nowrap">
{(definitionsQuery.data ?? []).find(d => d.type === ch.type)?.name ?? ch.type} {(definitionsQuery.data ?? []).find(d => d.type === ch.type)?.name ?? ch.type}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="text-xs text-muted-foreground max-w-[200px] truncate"> <TableCell className="text-xs text-muted-foreground max-w-[200px] truncate py-1">
{ch.description || <span className="italic">无备注</span>} {ch.description || <span className="italic">无备注</span>}
</TableCell> </TableCell>
<TableCell className="text-center"> <TableCell className="text-center py-1" onClick={(e) => e.stopPropagation()}>
<Switch <Switch
checked={ch.enabled} checked={ch.enabled}
onCheckedChange={checked => { onCheckedChange={checked => {
updateChannelMutation.mutate({ updateChannelMutation.mutate({
id: ch.id, id: ch.id,
data: { data: {
description: ch.description, description: ch.description,
type: ch.type, type: ch.type,
token: ch.token, token: ch.token,
url: ch.url, url: ch.url,
other: ch.other, other: ch.other,
enabled: checked, enabled: checked,
} }
}) })
}} }}
/> className="scale-75 data-[state=checked]:bg-green-600 h-4 w-7"
</TableCell> />
<TableCell className="text-right space-x-1 whitespace-nowrap"> </TableCell>
<Button <TableCell className="sticky right-0 text-center bg-background z-10 py-1" onClick={(e) => e.stopPropagation()}>
variant="ghost" <div className="flex items-center justify-center gap-1">
size="sm" <Button
onClick={() => handleTestChannelClick(ch.name)} variant="ghost"
className="h-7 text-[11px] text-primary hover:text-primary hover:bg-primary/10" size="sm"
> onClick={() => handleTestChannelClick(ch.name)}
<Play className="size-2.5 mr-1" /> className="h-6 px-2 text-[10px] text-primary hover:text-primary hover:bg-primary/10"
测试 >
</Button> <Play className="size-2.5 mr-1" />
<Button 测试
variant="ghost" </Button>
size="sm" <Button
onClick={() => handleEditChannelClick(ch)} variant="ghost"
className="h-7 text-[11px]" size="sm"
> onClick={() => handleEditChannelClick(ch)}
<Edit2 className="size-2.5 mr-1" /> className="h-6 px-2 text-[10px] text-muted-foreground hover:text-foreground"
编辑 >
</Button> <Edit2 className="size-2.5 mr-1" />
<Button 编辑
variant="ghost" </Button>
size="sm" <Button
disabled={deleteChannelMutation.isPending} variant="ghost"
onClick={() => { size="sm"
if (confirm(`确定要删除通道 "${ch.name}" 吗?`)) { disabled={deleteChannelMutation.isPending}
deleteChannelMutation.mutate(ch.id) onClick={() => {
} if (confirm(`确定要删除通道 "${ch.name}" 吗?`)) {
}} deleteChannelMutation.mutate(ch.id)
className="h-7 text-[11px] text-destructive hover:text-destructive hover:bg-destructive/10" }
> }}
<Trash2 className="size-2.5 mr-1" /> className="h-6 px-2 text-[10px] text-destructive hover:text-destructive hover:bg-destructive/10"
删除 >
</Button> <Trash2 className="size-2.5 mr-1" />
</TableCell> 删除
</TableRow> </Button>
))} </div>
</TableBody> </TableCell>
</Table> </TableRow>
)} ))}
</CardContent> </TableBody>
</Card> </Table>
</div>
)}
{/* ==================== 对话框:新增/编辑消息通道 ==================== */} {/* ==================== 对话框:新增/编辑消息通道 ==================== */}
<Dialog open={channelDialogOpen} onOpenChange={setChannelDialogOpen}> <Dialog open={channelDialogOpen} onOpenChange={setChannelDialogOpen}>