Files
xingrin/frontend/components/tools/commands/commands-columns.tsx
2025-12-22 11:14:46 +08:00

246 lines
6.3 KiB
TypeScript

"use client"
import React from "react"
import { ColumnDef } from "@tanstack/react-table"
import { Command } from "@/types/command.types"
import { Checkbox } from "@/components/ui/checkbox"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { MoreHorizontal, Eye, Trash2, ChevronsUpDown, ChevronUp, ChevronDown, Copy } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { formatDate } from "@/lib/utils"
import { toast } from "sonner"
/**
* 数据表格列头组件
*/
function DataTableColumnHeader({
column,
title,
}: {
column: { getCanSort: () => boolean; getIsSorted: () => false | "asc" | "desc"; toggleSorting: (desc?: boolean) => void }
title: string
}) {
if (!column.getCanSort()) {
return <div className="-ml-3 font-medium">{title}</div>
}
const isSorted = column.getIsSorted()
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="-ml-3 h-8 data-[state=open]:bg-accent hover:bg-muted"
>
{title}
{isSorted === "asc" ? (
<ChevronUp />
) : isSorted === "desc" ? (
<ChevronDown />
) : (
<ChevronsUpDown />
)}
</Button>
)
}
/**
* 命令表格列定义
*/
export const commandColumns: ColumnDef<Command>[] = [
// 选择列
{
id: "select",
size: 40,
minSize: 40,
maxSize: 40,
enableResizing: false,
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
// 名称列
{
accessorKey: "displayName",
size: 200,
minSize: 150,
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
cell: ({ row }) => {
const displayName = row.getValue("displayName") as string
const name = row.original.name
return (
<div className="flex flex-col flex-1 min-w-0">
<span className="text-sm font-medium break-all leading-relaxed whitespace-normal">
{displayName || name}
</span>
{displayName && name && displayName !== name && (
<span className="text-xs text-muted-foreground font-mono break-all leading-relaxed whitespace-normal">
{name}
</span>
)}
</div>
)
},
},
// 所属工具列
{
accessorKey: "tool",
size: 120,
minSize: 80,
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Tool" />
),
cell: ({ row }) => {
const tool = row.original.tool
return (
<div className="flex items-center gap-2">
{tool ? (
<Badge variant="outline">{tool.name}</Badge>
) : (
<span className="text-muted-foreground text-sm">-</span>
)}
</div>
)
},
},
// 命令模板列
{
accessorKey: "commandTemplate",
size: 350,
minSize: 250,
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Command Template" />
),
cell: ({ row }) => {
const template = row.getValue("commandTemplate") as string
if (!template) return <span className="text-muted-foreground text-sm">-</span>
return (
<div className="flex-1 min-w-0">
<span className="text-sm font-mono break-all leading-relaxed whitespace-normal">
{template}
</span>
</div>
)
},
},
// 描述列
{
accessorKey: "description",
size: 250,
minSize: 150,
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Description" />
),
cell: ({ row }) => {
const description = row.getValue("description") as string
if (!description) return <span className="text-muted-foreground text-sm">-</span>
return (
<div className="flex-1 min-w-0">
<span className="text-sm text-muted-foreground break-all leading-relaxed whitespace-normal">
{description}
</span>
</div>
)
},
},
// 更新时间列
{
accessorKey: "updatedAt",
size: 150,
minSize: 120,
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Updated At" />
),
cell: ({ row }) => (
<div className="text-sm text-muted-foreground">
{formatDate(row.getValue("updatedAt"))}
</div>
),
},
// 操作列
{
id: "actions",
size: 60,
minSize: 60,
maxSize: 60,
enableResizing: false,
cell: ({ row }) => {
const command = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<MoreHorizontal />
<span className="sr-only"></span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={async () => {
try {
await navigator.clipboard.writeText(command.commandTemplate)
toast.success('已复制命令模板')
} catch {
toast.error('复制失败')
}
}}
>
<Copy />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Eye />
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive focus:text-destructive">
<Trash2 />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
enableSorting: false,
enableHiding: false,
},
]