|
|
@@ -134,6 +134,53 @@ const nodeConfigs = [
|
|
|
},
|
|
|
];
|
|
|
|
|
|
+// 使用 import.meta.glob 动态导入所有图标(用于节点显示)
|
|
|
+const iconModulesForNode = import.meta.glob('../assets/节点图标/**/*.png', { eager: true, as: 'url' });
|
|
|
+
|
|
|
+// 根据文件名获取图标路径(用于节点显示)
|
|
|
+const getIconPathByFileName = (fileName: string | undefined): string | null => {
|
|
|
+ if (!fileName) return null;
|
|
|
+
|
|
|
+ // 如果已经是完整路径,尝试提取文件名
|
|
|
+ let actualFileName = fileName;
|
|
|
+ if (fileName.includes('/') || fileName.includes('\\')) {
|
|
|
+ // 从路径中提取文件名
|
|
|
+ const pathParts = fileName.replace(/\\/g, '/').split('/');
|
|
|
+ actualFileName = pathParts[pathParts.length - 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从文件名中提取数字和分类
|
|
|
+ const match = actualFileName.match(/^(\d+)\.png$/);
|
|
|
+ if (!match) return null;
|
|
|
+
|
|
|
+ const iconNumber = parseInt(match[1], 10);
|
|
|
+
|
|
|
+ // 根据数字范围判断分类
|
|
|
+ let category = '';
|
|
|
+ if (iconNumber >= 1000 && iconNumber <= 1011) category = '审核';
|
|
|
+ else if (iconNumber >= 2000 && iconNumber <= 2016) category = '开始';
|
|
|
+ else if (iconNumber >= 3000 && iconNumber <= 3028) category = '录入';
|
|
|
+ else if (iconNumber >= 4000 && iconNumber <= 4024) category = '确认';
|
|
|
+ else if (iconNumber >= 5000 && iconNumber <= 5018) category = '结束';
|
|
|
+ else if (iconNumber >= 6000 && iconNumber <= 6027) category = '能量隔离';
|
|
|
+ else if (iconNumber >= 7000 && iconNumber <= 7021) category = '解除隔离';
|
|
|
+
|
|
|
+ if (!category) return null;
|
|
|
+
|
|
|
+ // 查找对应的路径
|
|
|
+ const allKeys = Object.keys(iconModulesForNode);
|
|
|
+ const matchingKey = allKeys.find(k => {
|
|
|
+ const normalizedKey = k.replace(/\\/g, '/').toLowerCase();
|
|
|
+ return normalizedKey.includes(category.toLowerCase()) && normalizedKey.endsWith(actualFileName.toLowerCase());
|
|
|
+ });
|
|
|
+
|
|
|
+ if (matchingKey) {
|
|
|
+ return iconModulesForNode[matchingKey] as string;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+};
|
|
|
+
|
|
|
// 自定义节点组件
|
|
|
function CustomNode({ data, selected, id }: any) {
|
|
|
const config = nodeConfigs.find(c => c.type === data.type);
|
|
|
@@ -141,6 +188,25 @@ function CustomNode({ data, selected, id }: any) {
|
|
|
const nodeId = data.nodeId || (id ? String(parseInt(id.split('-').pop() || '0') % 1000).padStart(3, '0') : '001');
|
|
|
const isCompleted = data.completed || false;
|
|
|
|
|
|
+ // 检查是否是图片文件名(如 "1000.png")
|
|
|
+ let iconImagePath: string | null = null;
|
|
|
+ // 支持多种格式:纯文件名(如 "1000.png")或完整路径
|
|
|
+ if (data.icon) {
|
|
|
+ // 如果是纯文件名格式
|
|
|
+ if (/^\d+\.png$/.test(data.icon)) {
|
|
|
+ iconImagePath = getIconPathByFileName(data.icon);
|
|
|
+ }
|
|
|
+ // 如果已经是完整路径(兼容旧数据)
|
|
|
+ else if (data.icon.includes('/') || data.icon.includes('\\')) {
|
|
|
+ // 尝试从路径中提取文件名
|
|
|
+ const pathParts = data.icon.replace(/\\/g, '/').split('/');
|
|
|
+ const fileName = pathParts[pathParts.length - 1];
|
|
|
+ if (/^\d+\.png$/.test(fileName)) {
|
|
|
+ iconImagePath = getIconPathByFileName(fileName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 确定边框颜色:选中 > 已完成 > 默认
|
|
|
let borderClass = 'border-gray-200 hover:border-gray-300';
|
|
|
let borderStyle: React.CSSProperties = { borderWidth: '2px' };
|
|
|
@@ -228,10 +294,23 @@ function CustomNode({ data, selected, id }: any) {
|
|
|
|
|
|
<div className="flex flex-col items-center justify-between gap-3 h-full">
|
|
|
<div className={`${config?.bgColor || 'bg-gray-50'} ${config?.borderColor || 'border-gray-100'} border p-3 rounded-xl shadow-sm flex items-center justify-center flex-shrink-0`} style={{ borderRadius: '12px' }}>
|
|
|
- <Icon
|
|
|
- className={`${config?.iconColor || 'text-gray-600'} text-2xl`}
|
|
|
- style={{ color: config?.iconColorCustom || undefined }}
|
|
|
- />
|
|
|
+ {iconImagePath ? (
|
|
|
+ <img
|
|
|
+ src={iconImagePath}
|
|
|
+ alt={data.icon || '节点图标'}
|
|
|
+ className="w-6 h-6 object-contain"
|
|
|
+ onError={(e) => {
|
|
|
+ // 如果图片加载失败,回退到默认图标
|
|
|
+ console.error('节点图标加载失败:', iconImagePath);
|
|
|
+ (e.target as HTMLImageElement).style.display = 'none';
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <Icon
|
|
|
+ className={`${config?.iconColor || 'text-gray-600'} text-2xl`}
|
|
|
+ style={{ color: config?.iconColorCustom || undefined }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</div>
|
|
|
<div className="font-semibold text-sm text-gray-900 leading-tight text-center break-words w-full flex-1 flex items-center justify-center px-1">
|
|
|
{data.label || config?.label}
|
|
|
@@ -994,14 +1073,18 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
const nodeData = node.data || {};
|
|
|
const nodeId = node.id || node.uuid;
|
|
|
|
|
|
+ // 获取图标:优先使用 nodeIcon(顶层),其次使用 data.icon
|
|
|
+ const iconValue = node.nodeIcon || nodeData.icon || '';
|
|
|
+
|
|
|
return {
|
|
|
id: nodeId,
|
|
|
type: node.type || 'createJob',
|
|
|
position: node.position || { x: 0, y: 0 },
|
|
|
data: {
|
|
|
...nodeData,
|
|
|
- label: nodeData.label || node.label || nodeConfigs.find(c => c.type === (node.type || nodeData.type))?.label || '节点',
|
|
|
+ label: nodeData.label || node.label || node.nodeName || nodeConfigs.find(c => c.type === (node.type || nodeData.type))?.label || '节点',
|
|
|
type: node.type || nodeData.type || 'createJob',
|
|
|
+ icon: iconValue, // 确保icon字段被正确传递
|
|
|
},
|
|
|
};
|
|
|
});
|
|
|
@@ -1136,6 +1219,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
data: {
|
|
|
label: node.label,
|
|
|
type: node.type,
|
|
|
+ icon: node.icon || node.nodeIcon || '', // 确保icon字段被传递
|
|
|
},
|
|
|
}));
|
|
|
|