refactor: 清除前端冗余代码

This commit is contained in:
2026-04-06 15:39:28 +08:00
parent cb8f7f6cbe
commit 7dd25066ad
9 changed files with 11 additions and 254 deletions
+2 -6
View File
@@ -9,7 +9,6 @@ interface GraphViewProps {
data: GraphData;
layout?: GraphLayoutConfig;
onNodeClick?: (node: Node | null) => void;
onEdgeClick?: (edge: Edge) => void;
height?: number;
}
@@ -21,7 +20,6 @@ export const GraphView = ({
data,
layout = FORCE_LAYOUT_CONFIG,
onNodeClick,
onEdgeClick,
height = GRAPH_DEFAULT_HEIGHT,
}: GraphViewProps) => {
const containerRef = useRef<HTMLDivElement>(null);
@@ -31,12 +29,10 @@ export const GraphView = ({
const renderPromiseRef = useRef<Promise<void> | null>(null);
const onNodeClickRef = useRef(onNodeClick);
const onEdgeClickRef = useRef(onEdgeClick);
useEffect(() => {
onNodeClickRef.current = onNodeClick;
onEdgeClickRef.current = onEdgeClick;
}, [onNodeClick, onEdgeClick]);
}, [onNodeClick]);
useEffect(() => {
if (!containerRef.current) return;
@@ -219,7 +215,7 @@ export const GraphView = ({
graphRef.current = null;
}
};
}, [height, layout.type, data.id, onNodeClickRef, onEdgeClickRef]);
}, [height, layout.type, data.id, onNodeClickRef]);
// 窗口大小调整响应
useEffect(() => {
@@ -1,102 +0,0 @@
import { useState, useEffect, useMemo } from 'react';
import type { Node } from '../../types/graph';
interface NodeTooltipProps {
node: Node | null;
}
export const NodeTooltip = ({ node }: NodeTooltipProps) => {
const [isExpanded, setIsExpanded] = useState(false);
useEffect(() => {
setIsExpanded(false);
}, [node?.id]);
const propertiesList = useMemo(() => {
if (!node?.properties) return [];
return Object.entries(node.properties);
}, [node?.properties]);
const hasProperties = propertiesList.length > 0;
if (!node) return null;
const handleExpand = () => setIsExpanded(true);
const handleCollapse = () => setIsExpanded(false);
return (
<div className="absolute bottom-4 right-1/2 translate-x-1/2 bg-white/95 backdrop-blur-sm rounded-lg shadow-lg p-4 max-w-md w-80 z-20">
{!isExpanded ? (
<div className="space-y-2">
<div className="flex justify-between items-start">
<div className="font-semibold text-base text-gray-800">{node.label}</div>
</div>
{node.type && (
<div className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded inline-block">
{node.type}
</div>
)}
{hasProperties && (
<div className="space-y-1 text-xs">
{propertiesList.slice(0, 2).map(([key, value]) => (
<div key={key} className="flex justify-between gap-2">
<span className="text-gray-500 shrink-0">{key}:</span>
<span className="text-gray-800 text-right truncate max-w-[70%]">
{typeof value === 'object' ? JSON.stringify(value) : String(value)}
</span>
</div>
))}
{propertiesList.length > 2 && (
<div className="text-gray-400 text-center pt-1">还有 {propertiesList.length - 2} 项属性...</div>
)}
</div>
)}
{hasProperties && (
<button
onClick={handleExpand}
className="w-full mt-2 py-1.5 text-xs font-medium text-blue-600 hover:text-blue-700 hover:bg-blue-50 rounded transition-colors"
>
展开查看全部 ▼
</button>
)}
</div>
) : (
<div className="space-y-3">
<div className="flex justify-between items-start">
<div className="font-semibold text-base text-gray-800">{node.label}</div>
<button
onClick={handleCollapse}
className="text-xs text-gray-500 hover:text-gray-700 px-2 py-0.5 hover:bg-gray-100 rounded transition-colors"
>
收起 ▲
</button>
</div>
{node.type && (
<div className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded inline-block">
{node.type}
</div>
)}
{hasProperties && (
<div>
<div className="text-xs text-gray-500 uppercase tracking-wide mb-2">
属性 ({propertiesList.length})
</div>
<div className="space-y-1.5 text-xs max-h-40 overflow-y-auto">
{propertiesList.map(([key, value]) => (
<div key={key} className="flex justify-between gap-2 py-0.5">
<span className="text-gray-600 shrink-0">{key}:</span>
<span className="text-gray-800 text-right break-all max-w-[65%]">
{typeof value === 'object' ? JSON.stringify(value) : String(value)}
</span>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
);
};
export default NodeTooltip;
-17
View File
@@ -3,24 +3,7 @@ export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localho
export const API_TIMEOUT = 10000;
// 图谱渲染配置
export const GRAPH_CONTAINER_MIN_WIDTH = 600;
export const GRAPH_DEFAULT_HEIGHT = 600;
// 搜索相关常量
export const SEARCH_DEBOUNCE_MS = 300;
export const SEARCH_RESULT_MAX_HEIGHT = 256; // 16rem
// UI 相关常量
export const NODE_PANEL_WIDTH = 320; // 20rem
export const HOVER_PANEL_MAX_WIDTH = 320; // 20rem
// 性能优化常量
export const RESIZE_DEBOUNCE_MS = 200;
export const MAX_NODES_FOR_ANIMATION = 500;
// 错误消息
export const ERROR_MESSAGES = {
LOAD_GRAPH_FAILED: '加载图谱数据失败',
SEARCH_FAILED: '搜索节点失败',
NETWORK_ERROR: '网络连接错误',
} as const;
-3
View File
@@ -1,8 +1,5 @@
import type { GraphLayoutConfig } from '../types/graph';
// 支持的布局类型(保留 d3-force 力导向布局)
export const SUPPORTED_LAYOUTS = ['d3-force'] as const;
// 力导向布局默认配置
export const FORCE_LAYOUT_CONFIG = {
type: 'd3-force' as const,
-8
View File
@@ -11,7 +11,6 @@ import { resetColorAssignment } from '../config/colors';
export const useGraphData = () => {
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], edges: [] });
const [selectedNode, setSelectedNode] = useState<Node | null>(null);
const [hoveredNode, setHoveredNode] = useState<Node | null>(null);
const [searchResults, setSearchResults] = useState<SearchNode[]>([]);
const [loading, setLoading] = useState(true);
@@ -78,11 +77,6 @@ export const useGraphData = () => {
console.log('[useGraphData] setSelectedNode called');
}, []);
// 悬停节点(用于图谱悬停)
const handleNodeHover = useCallback((node: Node | null) => {
setHoveredNode(node);
}, []);
// 监听 selectedNode 变化,用于调试
useEffect(() => {
console.log('[useGraphData] selectedNode state changed:', selectedNode);
@@ -97,13 +91,11 @@ export const useGraphData = () => {
return {
graphData,
selectedNode,
hoveredNode,
searchResults,
loading,
setSelectedNode,
handleSearch,
handleNodeSelectById,
handleNodeSelect,
handleNodeHover,
};
};
+6 -12
View File
@@ -1,21 +1,15 @@
import { useState, useCallback } from 'react';
import type { GraphLayoutConfig } from '../types/graph';
import { DEFAULT_LAYOUT } from '../config/layout';
/**
* 知识图谱布局管理 Hook
* 负责布局状态的管理和切换
* 返回固定的默认布局配置
*/
export const useGraphLayout = () => {
const [layout, setLayout] = useState<GraphLayoutConfig>(DEFAULT_LAYOUT);
// 切换布局类型(目前只支持 force 力导向布局)
const handleLayoutChange = useCallback((newLayoutType: GraphLayoutConfig['type']) => {
setLayout({ type: newLayoutType });
}, []);
return {
layout,
handleLayoutChange,
layout: {
type: 'd3-force' as const,
collide: { radius: 60 } as const,
link: { distance: 150 } as const,
} as GraphLayoutConfig,
};
};
+1 -31
View File
@@ -1,5 +1,5 @@
import axios from 'axios';
import type { GraphData, Node, Edge } from '../types/graph';
import type { GraphData, Node } from '../types/graph';
import { API_BASE_URL, API_TIMEOUT } from '../config/constants';
/**
@@ -33,21 +33,6 @@ export const graphApi = {
}
},
/**
* 搜索节点(后端搜索)
* @param query 搜索关键词
* @returns 匹配的节点列表
*/
async searchNodes(query: string): Promise<Node[]> {
try {
const response = await apiClient.get<Node[]>(`/search?q=${encodeURIComponent(query)}`);
return response.data;
} catch (error) {
console.error('Failed to search nodes:', error);
return [];
}
},
/**
* 根据 ID 获取节点详情
* @param id 节点 ID
@@ -62,21 +47,6 @@ export const graphApi = {
return null;
}
},
/**
* 获取节点的邻居节点和连接边
* @param nodeId 节点 ID
* @returns 包含邻居节点和边的对象
*/
async getNeighbors(nodeId: string): Promise<{ nodes: Node[]; edges: Edge[] }> {
try {
const response = await apiClient.get(`/nodes/${nodeId}/neighbors`);
return response.data;
} catch (error) {
console.error('Failed to fetch neighbors:', error);
return { nodes: [], edges: [] };
}
},
};
export default apiClient;
+2 -5
View File
@@ -51,13 +51,10 @@ export interface GraphData {
/**
* 图谱布局配置
* 支持 force 力导向布局和 d3-force 力导向布局
* 支持 d3-force 力导向布局
*/
export interface GraphLayoutConfig {
type: 'force' | 'd3-force';
nodeSize?: number;
linkDistance?: number;
center?: [number, number];
type: 'd3-force';
collide?: { radius: number; strength?: number };
}
-70
View File
@@ -1,70 +0,0 @@
/**
* 通用工具函数
* 虽然当前未被使用,但保留以备未来需求
*/
/**
* 防抖函数
* 延迟执行函数,如果在延迟时间内再次调用则重置计时器
* @param func 要执行的函数
* @param wait 延迟时间(毫秒)
* @returns 防抖后的函数
*/
export const debounce = <T extends (...args: unknown[]) => unknown>(
func: T,
wait: number
): ((...args: Parameters<T>) => void) => {
let timeout: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => func(...args), wait);
};
};
/**
* 节流函数
* 限制函数执行频率,在指定时间内只执行一次
* @param func 要执行的函数
* @param limit 时间间隔(毫秒)
* @returns 节流后的函数
*/
export const throttle = <T extends (...args: unknown[]) => unknown>(
func: T,
limit: number
): ((...args: Parameters<T>) => void) => {
let inThrottle: boolean = false;
return (...args: Parameters<T>) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
};
/**
* 生成 UUID
* @returns UUID 字符串
*/
export const generateUUID = (): string => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
/**
* 根据重要性计算节点大小
* @param importance 重要性系数(默认为 1)
* @returns 节点大小
*/
export const calculateNodeSize = (importance: number = 1): number => {
const baseSize = 40;
const multiplier = importance * 20;
return baseSize + multiplier;
};