From ef22e494917ebfb2580cb51e5202c5fe843234c2 Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Tue, 7 Jul 2026 20:00:08 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=AB=E5=90=8D=E6=A0=91=E5=AD=90?=
=?UTF-8?q?=E6=A0=91=E5=88=AB=E5=90=8D=E5=AF=BC=E5=87=BA/=E5=AF=BC?=
=?UTF-8?q?=E5=85=A5/=E5=A4=8D=E5=88=B6/=E7=B2=98=E8=B4=B4=E5=8A=9F?=
=?UTF-8?q?=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/UI/WPF/Views/AliasTreeControl.xaml | 18 ++
src/UI/WPF/Views/AliasTreeControl.xaml.cs | 257 ++++++++++++++++++++++
2 files changed, 275 insertions(+)
diff --git a/src/UI/WPF/Views/AliasTreeControl.xaml b/src/UI/WPF/Views/AliasTreeControl.xaml
index 20e2a88..b125bb3 100644
--- a/src/UI/WPF/Views/AliasTreeControl.xaml
+++ b/src/UI/WPF/Views/AliasTreeControl.xaml
@@ -224,6 +224,24 @@
+
+
+
+
+
+
diff --git a/src/UI/WPF/Views/AliasTreeControl.xaml.cs b/src/UI/WPF/Views/AliasTreeControl.xaml.cs
index 999ac38..17e0a61 100644
--- a/src/UI/WPF/Views/AliasTreeControl.xaml.cs
+++ b/src/UI/WPF/Views/AliasTreeControl.xaml.cs
@@ -25,6 +25,7 @@ namespace NavisworksTransport.UI.WPF.Views
// ============================================================
private AliasDataStore _dataStore;
+ private string _copiedAliasTreeJson;
/// 当控件检测到模型就绪但数据存储未连接时触发
internal event Action OnDatabaseNeeded;
@@ -1191,6 +1192,262 @@ namespace NavisworksTransport.UI.WPF.Views
catch (Exception ex) { LogManager.Debug($"[别名树] Clipboard异常: {ex.Message}"); }
}
+ // ============================================================
+ // 子树别名导出/导入 / 复制/粘贴
+ // ============================================================
+
+ ///
+ /// 解析带 #N 后缀的段名 ("Room101#1" → "Room101", 1)
+ ///
+ private static void ParseSuffixedSegment(string segment, out string baseName, out int index)
+ {
+ int hashIdx = segment.LastIndexOf('#');
+ if (hashIdx > 0 && int.TryParse(segment.Substring(hashIdx + 1), out index))
+ {
+ baseName = segment.Substring(0, hashIdx);
+ }
+ else
+ {
+ baseName = segment;
+ index = 0;
+ }
+ }
+
+ ///
+ /// 收集源节点子树下所有别名,返回相对路径→别名 字典(不含源节点自身)
+ ///
+ private Dictionary CollectSubtreeAliases(AliasNodeIdentity sourceIdentity)
+ {
+ var result = new Dictionary();
+ var allAliases = _dataStore.LoadAll();
+ if (allAliases == null || allAliases.Count == 0) return result;
+
+ string prefix = sourceIdentity.DisplayPath + "/";
+
+ foreach (var kvp in allAliases)
+ {
+ if (!AliasNodeIdentity.TryParseKey(kvp.Key, out var id)) continue;
+
+ if (id.DisplayPath.StartsWith(prefix, StringComparison.Ordinal))
+ {
+ string relativePath = id.DisplayPath.Substring(prefix.Length);
+ result[relativePath] = kvp.Value;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// 将相对路径→别名 字典应用到目标节点子树。
+ /// 逐段 Children 寻径,任一缺失则返回错误信息取消全部。
+ ///
+ private string TryApplySubtreeAliases(AliasNodeViewModel targetNode, Dictionary relativeAliases)
+ {
+ if (relativeAliases == null || relativeAliases.Count == 0) return "没有可导入的别名数据";
+
+ EnsureFullNodeKey(targetNode);
+ if (!AliasNodeIdentity.TryParseKey(targetNode.NodeKey, out var targetIdentity))
+ return "无法解析目标节点标识";
+
+ var targetItem = FindModelItemByNodeKey(targetNode.NodeKey);
+ if (targetItem == null) return "无法定位目标模型节点";
+
+ var batch = new Dictionary();
+
+ foreach (var kvp in relativeAliases)
+ {
+ Autodesk.Navisworks.Api.ModelItem current = targetItem;
+ string[] segments = kvp.Key.Split('/');
+ bool found = true;
+
+ foreach (var segment in segments)
+ {
+ ParseSuffixedSegment(segment, out var baseName, out var index);
+ int matchCount = 0;
+ Autodesk.Navisworks.Api.ModelItem matched = null;
+
+ foreach (var child in current.Children)
+ {
+ if (string.Equals(child.DisplayName, baseName, StringComparison.Ordinal))
+ {
+ if (matchCount == index) { matched = child; break; }
+ matchCount++;
+ }
+ }
+
+ if (matched == null) { found = false; break; }
+ current = matched;
+ }
+
+ if (!found) return $"目标子树缺少路径:{kvp.Key},已取消";
+
+ var descIdentity = AliasNodeIdentity.FromModelItem(current);
+ batch[descIdentity.ToKey()] = kvp.Value;
+ }
+
+ if (batch.Count > 0)
+ {
+ _dataStore.ImportBatch(batch);
+ RefreshAliasesFromStore();
+ SetStatus($"已导入 {batch.Count} 条别名到目标子树");
+ }
+
+ return null;
+ }
+
+ private void OnCtxExportSubtreeClick(object sender, RoutedEventArgs e)
+ {
+ var node = GetSelectedNode();
+ if (node == null || _dataStore == null) return;
+
+ EnsureFullNodeKey(node);
+ if (!AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity))
+ { SetStatus("无法解析节点标识"); return; }
+
+ var entries = CollectSubtreeAliases(identity);
+ if (entries.Count == 0) { SetStatus("该节点下没有别名数据"); return; }
+
+ var defaultName = string.Join("_", identity.DisplayPath.Split(System.IO.Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries)) + ".alias-subtree.json";
+ var dlg = new Microsoft.Win32.SaveFileDialog
+ {
+ Filter = "JSON 文件|*.alias-subtree.json",
+ Title = "导出子树别名",
+ FileName = defaultName
+ };
+ if (dlg.ShowDialog() != true) return;
+
+ try
+ {
+ var data = new
+ {
+ version = 1,
+ type = "alias_subtree",
+ exportedAt = DateTime.Now.ToString("s"),
+ sourceDisplayPath = identity.DisplayPath,
+ aliases = entries
+ };
+
+ var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
+ File.WriteAllText(dlg.FileName, serializer.Serialize(data), System.Text.Encoding.UTF8);
+ SetStatus($"已导出 {entries.Count} 条子树别名");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[别名树] 子树导出失败: {ex.Message}");
+ SetStatus("导出失败,见日志");
+ }
+ }
+
+ private void OnCtxImportSubtreeClick(object sender, RoutedEventArgs e)
+ {
+ var node = GetSelectedNode();
+ if (node == null || _dataStore == null) return;
+
+ var dlg = new Microsoft.Win32.OpenFileDialog
+ {
+ Filter = "JSON 文件|*.alias-subtree.json;*.json",
+ Title = "导入子树别名"
+ };
+ if (dlg.ShowDialog() != true) return;
+
+ try
+ {
+ var json = File.ReadAllText(dlg.FileName, System.Text.Encoding.UTF8);
+ var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
+ var data = serializer.Deserialize>(json);
+
+ if (!(data.TryGetValue("type", out var typeObj) && (typeObj as string) == "alias_subtree"))
+ { SetStatus("无效的子树别名文件"); return; }
+
+ if (!(data.TryGetValue("aliases", out var aliasesObj) && aliasesObj is Dictionary rawAliases))
+ { SetStatus("别名数据格式无效"); return; }
+
+ var entries = new Dictionary();
+ foreach (var kvp in rawAliases)
+ {
+ var alias = kvp.Value as string;
+ if (!string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(alias))
+ entries[kvp.Key] = alias;
+ }
+
+ if (entries.Count == 0) { SetStatus("文件不包含有效别名"); return; }
+
+ var error = TryApplySubtreeAliases(node, entries);
+ if (error != null) SetStatus(error);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"[别名树] 子树导入失败: {ex.Message}");
+ SetStatus("导入失败,见日志");
+ }
+ }
+
+ private void OnCtxCopyAliasTreeClick(object sender, RoutedEventArgs e)
+ {
+ var node = GetSelectedNode();
+ if (node == null || _dataStore == null) return;
+
+ EnsureFullNodeKey(node);
+ if (!AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity))
+ { SetStatus("无法解析节点标识"); return; }
+
+ var entries = CollectSubtreeAliases(identity);
+ if (entries.Count == 0) { SetStatus("该节点下没有别名数据"); return; }
+
+ var data = new
+ {
+ version = 1,
+ type = "alias_subtree",
+ exportedAt = DateTime.Now.ToString("s"),
+ sourceDisplayPath = identity.DisplayPath,
+ aliases = entries
+ };
+
+ var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
+ _copiedAliasTreeJson = serializer.Serialize(data);
+ SetStatus($"已复制 {entries.Count} 条别名树");
+ }
+
+ private void OnCtxPasteAliasTreeClick(object sender, RoutedEventArgs e)
+ {
+ var node = GetSelectedNode();
+ if (node == null || _dataStore == null) return;
+
+ if (string.IsNullOrEmpty(_copiedAliasTreeJson))
+ { SetStatus("没有已复制的别名树数据"); return; }
+
+ try
+ {
+ var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
+ var data = serializer.Deserialize>(_copiedAliasTreeJson);
+
+ if (!(data.TryGetValue("type", out var typeObj) && (typeObj as string) == "alias_subtree"))
+ { SetStatus("内存数据无效"); return; }
+
+ if (!(data.TryGetValue("aliases", out var aliasesObj) && aliasesObj is Dictionary rawAliases))
+ { SetStatus("别名数据格式无效"); return; }
+
+ var entries = new Dictionary();
+ foreach (var kvp in rawAliases)
+ {
+ var alias = kvp.Value as string;
+ if (!string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(alias))
+ entries[kvp.Key] = alias;
+ }
+
+ if (entries.Count == 0) { SetStatus("别名数据为空"); return; }
+
+ var error = TryApplySubtreeAliases(node, entries);
+ if (error != null) SetStatus(error);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Debug($"[别名树] 粘贴别名树异常: {ex.Message}");
+ SetStatus("别名树数据格式无效");
+ }
+ }
+
// ============================================================
// 3D 高亮
// ============================================================