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 高亮 // ============================================================