Browse Source

feat: 优化协议日志表格功能 - 添加右键菜单颜色选择器,优化深色主题显示效果 - 修复表格高度适配性问题,使用响应式高度设置 - 增强颜色选择器的可见性和交互体验

feature/x1-web-request
root 3 days ago
parent
commit
dd0b272990
  1. 192
      src/X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx
  2. 6
      src/X1.WebUI/src/pages/online-protocol-logs/OnlineProtocolLogsView.tsx
  3. 6
      src/X1.WebUI/src/pages/protocol-logs/HistoryProtocolLogsView.tsx
  4. 111
      src/modify.md

192
src/X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx

@ -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>
)}
</>
);
}

6
src/X1.WebUI/src/pages/online-protocol-logs/OnlineProtocolLogsView.tsx

@ -15,14 +15,14 @@ import { Badge } from '@/components/ui/badge';
import { ChevronDown, X } from 'lucide-react';
const defaultColumns = [
{ key: 'layerType', title: 'Layer Type', visible: true },
{ key: 'time', title: 'Time', visible: true },
{ key: 'plmn', title: 'PLMN', visible: true },
{ key: 'info', title: 'Info', visible: true },
{ key: 'ueid', title: 'UEID', visible: true },
{ key: 'imsi', title: 'IMSI', visible: true },
{ key: 'ueid', title: 'UEID', visible: true },
{ key: 'cellID', title: 'CellID', visible: true },
{ key: 'direction', title: 'Direction', visible: true },
{ key: 'info', title: 'Info', visible: true },
{ key: 'layerType', title: 'Layer', visible: true },
{ key: 'message', title: 'Message', visible: true },
{ key: 'MessageDetailJson', title: 'Detail', visible: true },
];

6
src/X1.WebUI/src/pages/protocol-logs/HistoryProtocolLogsView.tsx

@ -15,14 +15,14 @@ import { Badge } from '@/components/ui/badge';
import { ChevronDown, X } from 'lucide-react';
const defaultColumns = [
{ key: 'layerType', title: 'Layer Type', visible: true },
{ key: 'time', title: 'Time', visible: true },
{ key: 'plmn', title: 'PLMN', visible: true },
{ key: 'info', title: 'Info', visible: true },
{ key: 'ueid', title: 'UEID', visible: true },
{ key: 'imsi', title: 'IMSI', visible: true },
{ key: 'ueid', title: 'UEID', visible: true },
{ key: 'cellID', title: 'CellID', visible: true },
{ key: 'direction', title: 'Direction', visible: true },
{ key: 'info', title: 'Info', visible: true },
{ key: 'layerType', title: 'Layer', visible: true },
{ key: 'message', title: 'Message', visible: true },
{ key: 'MessageDetailJson', title: 'Detail', visible: true },
];

111
src/modify.md

@ -2,6 +2,117 @@
## 2024年修改记录
### 协议日志表格右键菜单功能
#### 修改文件:
`X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx`
#### 修改内容:
1. **功能描述**
- 为协议日志表格添加了右键菜单功能
- 支持选择背景颜色和字体颜色
- 提供清空样式功能
2. **新增功能**
- **右键菜单触发**:在表格行上右键点击可打开菜单
- **背景颜色选择**:提供7种浅色背景选项(浅红、浅橙、浅黄、浅绿、浅蓝、浅紫、浅灰)
- **字体颜色选择**:提供8种字体颜色选项(黑、红、橙、黄、绿、蓝、紫、灰)
- **清空样式**:一键清除当前行的所有自定义样式
3. **技术实现**
- 添加了 `RowStyle` 接口定义行样式结构
- 使用 `useState` 管理行样式状态 `rowStyles`
- 实现了右键菜单状态管理 `contextMenu`
- 添加了颜色选项配置数组
- 实现了样式设置和清空的相关函数
4. **UI设计**
- 使用网格布局展示颜色选择器
- 背景颜色显示为彩色方块
- 字体颜色显示为带字母A的彩色方块
- 添加了悬停效果和过渡动画
- 使用图标区分不同功能区域
5. **交互体验**
- 右键菜单在鼠标位置显示
- 点击外部自动关闭菜单
- 颜色选择器支持悬停预览
- 样式变更即时生效
6. **代码结构**
```tsx
// 行样式接口
interface RowStyle {
backgroundColor?: string;
color?: string;
}
// 右键菜单状态
const [contextMenu, setContextMenu] = useState<{
visible: boolean;
x: number;
y: number;
rowId: string;
}>();
// 行样式状态
const [rowStyles, setRowStyles] = useState<Record<string, RowStyle>>({});
```
#### 修改时间:
2024年
#### 修改原因:
增强协议日志表格的交互功能,允许用户通过右键菜单自定义行的背景颜色和字体颜色,提升数据可视化和用户体验。
---
### 协议日志表格高度适配性优化
#### 修改文件:
`X1.WebUI/src/components/protocol-logs/ProtocolLogsTable.tsx`
#### 修改内容:
1. **问题描述**
- 原代码使用固定的 `max-h-[600px]` 高度设置
- 导致在不同屏幕尺寸下无法良好适配
- 在小屏幕设备上可能出现显示问题
2. **解决方案**
- 将固定高度 `max-h-[600px]` 替换为响应式高度设置
- 使用 `calc()` 函数结合视口高度(vh)进行动态计算
- 添加最小高度限制确保在小屏幕上的可用性
3. **具体修改**
```tsx
// 修改前
<div className="max-h-[600px] overflow-auto">
// 修改后
<div className="h-[calc(100vh-400px)] min-h-[300px] max-h-[calc(100vh-200px)] overflow-auto">
```
4. **新高度设置说明**
- `h-[calc(100vh-400px)]`:设置表格高度为视口高度减去400px(为页面其他元素预留空间)
- `min-h-[300px]`:设置最小高度为300px,确保在小屏幕上仍有足够的显示空间
- `max-h-[calc(100vh-200px)]`:设置最大高度为视口高度减去200px,防止在大屏幕上占用过多空间
- `overflow-auto`:保持原有的滚动功能
5. **优势**
- **响应式设计**:能够根据不同的屏幕尺寸自动调整
- **更好的用户体验**:在各种设备上都能提供合适的显示效果
- **保持功能完整性**:滚动功能和其他交互特性保持不变
#### 修改时间:
2024年
#### 修改原因:
解决协议日志表格在不同屏幕尺寸下的适配性问题,提升用户体验。
---
### 协议日志仓储修改
#### 修改文件:

Loading…
Cancel
Save