|
|
@ -1,10 +1,11 @@ |
|
|
|
import React, { useState } from 'react'; |
|
|
|
import React, { useState, useRef, useEffect } from 'react'; |
|
|
|
import { ProtocolLogDto, protocolLogsService } from '@/services/protocolLogsService'; |
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; |
|
|
|
import { Badge } from '@/components/ui/badge'; |
|
|
|
import { Button } from '@/components/ui/button'; |
|
|
|
import { Drawer, DrawerContent, DrawerHeader } from '@/components/ui/drawer'; |
|
|
|
import { Eye, X, Loader2 } from 'lucide-react'; |
|
|
|
|
|
|
|
import { Eye, X, Loader2, Palette, Type, RotateCcw } from 'lucide-react'; |
|
|
|
import { cn } from '@/lib/utils'; |
|
|
|
import { DensityType } from '@/components/ui/TableToolbar'; |
|
|
|
import { getProtocolLayerLabel } from '@/constants/protocolLayerOptions'; |
|
|
@ -18,6 +19,12 @@ interface ProtocolLogsTableProps { |
|
|
|
density: DensityType; |
|
|
|
} |
|
|
|
|
|
|
|
// 行样式接口
|
|
|
|
interface RowStyle { |
|
|
|
backgroundColor?: string; |
|
|
|
color?: string; |
|
|
|
} |
|
|
|
|
|
|
|
export default function ProtocolLogsTable({ |
|
|
|
protocolLogs, |
|
|
|
loading, |
|
|
@ -29,6 +36,100 @@ export default function ProtocolLogsTable({ |
|
|
|
const [loadingMessageDetail, setLoadingMessageDetail] = useState(false); |
|
|
|
const { toast } = useToast(); |
|
|
|
|
|
|
|
// 右键菜单状态
|
|
|
|
const [contextMenu, setContextMenu] = useState<{ |
|
|
|
visible: boolean; |
|
|
|
x: number; |
|
|
|
y: number; |
|
|
|
rowId: string; |
|
|
|
}>({ |
|
|
|
visible: false, |
|
|
|
x: 0, |
|
|
|
y: 0, |
|
|
|
rowId: '', |
|
|
|
}); |
|
|
|
|
|
|
|
// 行样式状态
|
|
|
|
const [rowStyles, setRowStyles] = useState<Record<string, RowStyle>>({}); |
|
|
|
|
|
|
|
// 颜色选项 - 优化深色主题下的可见性
|
|
|
|
const backgroundColorOptions = [ |
|
|
|
{ name: '浅红色', value: '#fef2f2', borderColor: '#fecaca' }, |
|
|
|
{ name: '浅橙色', value: '#fff7ed', borderColor: '#fed7aa' }, |
|
|
|
{ name: '浅黄色', value: '#fefce8', borderColor: '#fde68a' }, |
|
|
|
{ name: '浅绿色', value: '#f0fdf4', borderColor: '#bbf7d0' }, |
|
|
|
{ name: '浅蓝色', value: '#eff6ff', borderColor: '#bfdbfe' }, |
|
|
|
{ name: '浅紫色', value: '#faf5ff', borderColor: '#ddd6fe' }, |
|
|
|
{ name: '浅灰色', value: '#f9fafb', borderColor: '#d1d5db' }, |
|
|
|
]; |
|
|
|
|
|
|
|
const textColorOptions = [ |
|
|
|
{ name: '黑色', value: '#000000' }, |
|
|
|
{ name: '红色', value: '#dc2626' }, |
|
|
|
{ name: '橙色', value: '#ea580c' }, |
|
|
|
{ name: '黄色', value: '#ca8a04' }, |
|
|
|
{ name: '绿色', value: '#16a34a' }, |
|
|
|
{ name: '蓝色', value: '#2563eb' }, |
|
|
|
{ name: '紫色', value: '#7c3aed' }, |
|
|
|
{ name: '灰色', value: '#6b7280' }, |
|
|
|
]; |
|
|
|
|
|
|
|
// 处理右键菜单
|
|
|
|
const handleContextMenu = (event: React.MouseEvent, rowId: string) => { |
|
|
|
event.preventDefault(); |
|
|
|
setContextMenu({ |
|
|
|
visible: true, |
|
|
|
x: event.clientX, |
|
|
|
y: event.clientY, |
|
|
|
rowId, |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
// 关闭右键菜单
|
|
|
|
const closeContextMenu = () => { |
|
|
|
setContextMenu(prev => ({ ...prev, visible: false })); |
|
|
|
}; |
|
|
|
|
|
|
|
// 设置背景颜色
|
|
|
|
const setBackgroundColor = (rowId: string, color: string) => { |
|
|
|
setRowStyles(prev => ({ |
|
|
|
...prev, |
|
|
|
[rowId]: { ...prev[rowId], backgroundColor: color } |
|
|
|
})); |
|
|
|
closeContextMenu(); |
|
|
|
}; |
|
|
|
|
|
|
|
// 设置字体颜色
|
|
|
|
const setTextColor = (rowId: string, color: string) => { |
|
|
|
setRowStyles(prev => ({ |
|
|
|
...prev, |
|
|
|
[rowId]: { ...prev[rowId], color: color } |
|
|
|
})); |
|
|
|
closeContextMenu(); |
|
|
|
}; |
|
|
|
|
|
|
|
// 清空行样式
|
|
|
|
const clearRowStyle = (rowId: string) => { |
|
|
|
setRowStyles(prev => { |
|
|
|
const newStyles = { ...prev }; |
|
|
|
delete newStyles[rowId]; |
|
|
|
return newStyles; |
|
|
|
}); |
|
|
|
closeContextMenu(); |
|
|
|
}; |
|
|
|
|
|
|
|
// 点击外部关闭右键菜单
|
|
|
|
useEffect(() => { |
|
|
|
const handleClickOutside = () => { |
|
|
|
closeContextMenu(); |
|
|
|
}; |
|
|
|
|
|
|
|
if (contextMenu.visible) { |
|
|
|
document.addEventListener('click', handleClickOutside); |
|
|
|
return () => document.removeEventListener('click', handleClickOutside); |
|
|
|
} |
|
|
|
}, [contextMenu.visible]); |
|
|
|
|
|
|
|
// 密度样式映射 - 更紧凑的样式
|
|
|
|
const densityStyles = { |
|
|
|
relaxed: 'py-2 px-2', |
|
|
@ -280,7 +381,7 @@ export default function ProtocolLogsTable({ |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* 可滚动的表体 */} |
|
|
|
<div className="max-h-[600px] overflow-auto"> |
|
|
|
<div className="h-[calc(100vh-400px)] min-h-[300px] max-h-[calc(100vh-200px)] overflow-auto"> |
|
|
|
<Table className="w-full"> |
|
|
|
<TableBody> |
|
|
|
{loading ? ( |
|
|
@ -302,7 +403,12 @@ export default function ProtocolLogsTable({ |
|
|
|
</TableRow> |
|
|
|
) : ( |
|
|
|
protocolLogs.map((log, index) => ( |
|
|
|
<TableRow key={`${log.id}-${index}`} className="hover:bg-muted/50"> |
|
|
|
<TableRow |
|
|
|
key={`${log.id}-${index}`} |
|
|
|
className="hover:bg-muted/50" |
|
|
|
style={rowStyles[log.id] || {}} |
|
|
|
onContextMenu={(event) => handleContextMenu(event, log.id)} |
|
|
|
> |
|
|
|
{visibleColumns.map((column) => ( |
|
|
|
<TableCell |
|
|
|
key={column.key} |
|
|
@ -352,6 +458,84 @@ export default function ProtocolLogsTable({ |
|
|
|
</DrawerContent> |
|
|
|
</div> |
|
|
|
</Drawer> |
|
|
|
|
|
|
|
{/* 右键菜单 */} |
|
|
|
{contextMenu.visible && ( |
|
|
|
<div |
|
|
|
className="fixed z-50 bg-popover border-2 border-border rounded-md shadow-2xl p-3 min-w-[220px] backdrop-blur-sm" |
|
|
|
style={{ |
|
|
|
top: contextMenu.y, |
|
|
|
left: contextMenu.x, |
|
|
|
}} |
|
|
|
> |
|
|
|
{/* 背景颜色选择 */} |
|
|
|
<div className="mb-3"> |
|
|
|
<div className="text-xs font-medium text-foreground mb-2 flex items-center"> |
|
|
|
<Palette className="mr-2 h-4 w-4" /> |
|
|
|
背景颜色 |
|
|
|
</div> |
|
|
|
<div className="grid grid-cols-4 gap-2"> |
|
|
|
{backgroundColorOptions.map((color) => ( |
|
|
|
<button |
|
|
|
key={color.value} |
|
|
|
className="w-8 h-8 rounded-lg border-2 hover:scale-110 transition-all duration-200 shadow-lg hover:shadow-xl relative group" |
|
|
|
style={{ |
|
|
|
backgroundColor: color.value, |
|
|
|
borderColor: color.borderColor, |
|
|
|
boxShadow: `0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.1)` |
|
|
|
}} |
|
|
|
onClick={() => setBackgroundColor(contextMenu.rowId, color.value)} |
|
|
|
title={color.name} |
|
|
|
> |
|
|
|
{/* 悬停时的光晕效果 */} |
|
|
|
<div className="absolute inset-0 rounded-lg bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-200" /> |
|
|
|
</button> |
|
|
|
))} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* 字体颜色选择 */} |
|
|
|
<div className="mb-3"> |
|
|
|
<div className="text-xs font-medium text-foreground mb-2 flex items-center"> |
|
|
|
<Type className="mr-2 h-4 w-4" /> |
|
|
|
字体颜色 |
|
|
|
</div> |
|
|
|
<div className="grid grid-cols-4 gap-2"> |
|
|
|
{textColorOptions.map((color) => ( |
|
|
|
<button |
|
|
|
key={color.value} |
|
|
|
className="w-8 h-8 rounded-lg border-2 hover:scale-110 transition-all duration-200 shadow-lg hover:shadow-xl flex items-center justify-center text-sm font-bold relative group" |
|
|
|
style={{ |
|
|
|
backgroundColor: color.value, |
|
|
|
color: color.value === '#000000' ? '#ffffff' : '#000000', |
|
|
|
textShadow: color.value === '#000000' ? '0 0 3px rgba(0,0,0,0.8)' : '0 0 3px rgba(255,255,255,0.8)', |
|
|
|
borderColor: color.value === '#000000' ? '#374151' : color.value, |
|
|
|
boxShadow: `0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.1)` |
|
|
|
}} |
|
|
|
onClick={() => setTextColor(contextMenu.rowId, color.value)} |
|
|
|
title={color.name} |
|
|
|
> |
|
|
|
<span className="relative z-10">A</span> |
|
|
|
{/* 悬停时的光晕效果 */} |
|
|
|
<div className="absolute inset-0 rounded-lg bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-200" /> |
|
|
|
</button> |
|
|
|
))} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* 分隔线 */} |
|
|
|
<div className="border-t border-border my-3" /> |
|
|
|
|
|
|
|
{/* 清空样式 */} |
|
|
|
<button |
|
|
|
className="w-full text-left px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground rounded-md flex items-center transition-colors duration-200" |
|
|
|
onClick={() => clearRowStyle(contextMenu.rowId)} |
|
|
|
> |
|
|
|
<RotateCcw className="mr-2 h-4 w-4" /> |
|
|
|
清空样式 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
</> |
|
|
|
); |
|
|
|
} |