using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D; // Helix 窗口内 3D (ModelVisual3D 等)
using System.Windows.Controls.Primitives; // DataGridColumnHeader (参数表按列筛选)
using Microsoft.Win32;
using Ounibo.Catalog.Core;
using Ounibo.Catalog.Viewer3D;
namespace Ounibo.Catalog.App
{
/// 简单双语: zh/en 文案切换。
public class Lang
{
public string Code = "zh";
public string T(string zh, string en) { return Code == "en" ? en : zh; }
public void Toggle() { Code = (Code == "zh") ? "en" : "zh"; }
}
public class ComboItem
{
public object Value;
public string Text;
public ComboItem(object v, string t) { Value = v; Text = t; }
public override string ToString() { return Text; }
}
///
/// 选项块 (DESTACO 风, 2026-09-15): 替代下拉菜单的单值可选卡片。
/// 三态: 可选 / 已选 / 不可选 (置灰划线但保留占位, 让用户看到选项存在)。
///
public class OptionTile : Border
{
public object Value;
public ParameterDef Param;
readonly TextBlock _code;
readonly TextBlock _desc;
bool _usable = true, _on;
static readonly Brush BorderIdle = new SolidColorBrush(Color.FromRgb(0xE5, 0xE7, 0xEB));
static readonly Brush BorderHover = new SolidColorBrush(Color.FromRgb(0x2D, 0xD4, 0xBF));
static readonly Brush BorderOn = new SolidColorBrush(Color.FromRgb(0x14, 0xB8, 0xA6));
static readonly Brush BgIdle = Brushes.White;
static readonly Brush BgHover = new SolidColorBrush(Color.FromRgb(0xF0, 0xFD, 0xFA));
static readonly Brush BgOn = new SolidColorBrush(Color.FromRgb(0xF0, 0xFD, 0xFA));
static readonly Brush BgOff = new SolidColorBrush(Color.FromRgb(0xF9, 0xFA, 0xFB));
static readonly Brush FgCodeIdle = new SolidColorBrush(Color.FromRgb(0x11, 0x18, 0x27));
static readonly Brush FgCodeOn = new SolidColorBrush(Color.FromRgb(0x0F, 0x76, 0x6E));
public OptionTile(ParameterDef p, object value, string code, string desc, Action onClick)
{
Param = p; Value = value;
BorderThickness = new Thickness(1.5);
CornerRadius = new CornerRadius(10);
Padding = new Thickness(9, 7, 9, 7);
Margin = new Thickness(0, 0, 6, 6);
MinWidth = 58;
Cursor = Cursors.Hand;
Background = BgIdle;
BorderBrush = BorderIdle;
var sp = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center };
_code = new TextBlock
{
Text = code,
FontFamily = new FontFamily("Consolas, monospace"),
FontWeight = FontWeights.Bold,
FontSize = 13,
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Center
};
sp.Children.Add(_code);
if (!string.IsNullOrEmpty(desc) && desc != code)
{
_desc = new TextBlock
{
Text = desc,
FontSize = 10,
Margin = new Thickness(0, 2, 0, 0),
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Center,
TextWrapping = TextWrapping.Wrap,
MaxWidth = 120,
Foreground = new SolidColorBrush(Color.FromRgb(0x9C, 0xA3, 0xAF))
};
sp.Children.Add(_desc);
}
Child = sp;
MouseEnter += (s, e) => { if (_usable && !_on) { BorderBrush = BorderHover; Background = BgHover; } };
MouseLeave += (s, e) => { if (_usable && !_on) { BorderBrush = BorderIdle; Background = BgIdle; } };
MouseLeftButtonUp += (s, e) => { if (_usable) onClick(this); };
}
/// 设置可用性 (被联动规则排除时置灰但不移除)。
public void SetUsable(bool usable)
{
_usable = usable;
Cursor = usable ? Cursors.Hand : Cursors.Arrow;
if (!usable)
{
Background = BgOff; BorderBrush = BorderIdle;
BorderThickness = new Thickness(1.5);
_code.Foreground = new SolidColorBrush(Color.FromRgb(0x9C, 0xA3, 0xAF));
_code.TextDecorations = TextDecorations.Strikethrough;
Opacity = 0.4;
if (_desc != null) _desc.Opacity = 0.7;
}
else
{
Opacity = 1;
_code.TextDecorations = null;
if (_desc != null) _desc.Opacity = 1;
ApplyState();
}
}
/// 设置选中态。
public void SetOn(bool on)
{
_on = on;
if (_usable) ApplyState();
}
void ApplyState()
{
if (_on)
{
Background = BgOn; BorderBrush = BorderOn;
_code.Foreground = FgCodeOn;
}
else
{
Background = BgIdle; BorderBrush = BorderIdle;
_code.Foreground = FgCodeIdle;
}
}
}
///
/// 主窗口: 目录树 + 参数选型 + 3D 预览 + 尺寸图/参数表/数据表 + 搜索 + STEP 下载/批量下载。
///
public class MainWindow : Window
{
Lang _lang = new Lang();
OpcPackage _pkg;
Configurator _cfg;
SearchEngine _search;
Series _series;
readonly Dictionary _sel = new Dictionary();
readonly Dictionary _boxes = new Dictionary();
// 选项块 (DESTACO 风, 2026-09-15): 每个参数一组可点选项块, 替代下拉菜单
readonly Dictionary> _tiles = new Dictionary>();
readonly Dictionary _tilePanels = new Dictionary();
TextBlock _skuCode, _skuHint; // 零件号实时条
Border _skuBar;
ProgressBar _skuProg;
readonly Dictionary _ranges = new Dictionary();
readonly Dictionary _rangeBoxes = new Dictionary();
List _searchResult;
bool _refreshing;
#if !CUSTOMER_BUILD
bool _admin;
#endif
TreeView _tree;
StackPanel _paramPanel, _rangePanel;
ViewerControl _viewer;
Image _dimImage;
StackPanel _dimPanel, _manualPanel;
DataGrid _grid, _searchGrid, _crossGrid;
ComboBox _crossBrand;
TabControl _tabs;
TextBlock _modelTitle, _status, _pkgInfo;
Button _btnDownload, _btnDownloadPdf, _btnAddCart, _btnOpen, _btnLang, _btnCart, _btnSearch, _btnExport, _btnRangeApply, _btnRangeReset;
string _curModelCode; // 当前已选型号 (平替对照按此过滤)
DataTable _gridDt; // 参数表数据 (RowFilter 按列筛选)
Dictionary _colFilter = new Dictionary(); // 参数表按列筛选 {参数code: 值}
List _cart = new List(); // 购物车 (淘宝式清单, 与选型状态解耦)
Variant _curVariant; // 当前已解析型号 (加入清单用)
ParamResult _curParam; // 当前参数化结果 (非标档位时非空, 与 _curVariant 互斥; 一期 2026-08-25)
Border _viewerHint; // 3D 视口顶部非标档位提示条
string _genApi = ""; // NX 按需生成服务地址 (catalog.json genApi; 空=非标下载置灰)
ComboBox _btnGenFmt; // 生成格式选择 (非标档位在线生成, 二期)
CartWindow _cartWin; // 清单窗口 (非模态, 数据在主窗口)
StackPanel _fallbackPanel; // 真实数模 (桌面解析器不支持) → 浏览器预览回退面板
Button _btnBrowser3D;
LocalViewerServer _previewSrv; // 本机 3D 预览微型服务器 (惰性启动)
string _curStepName; // 当前变体 STEP 文件名 (浏览器预览用)
byte[] _curStepBytes;
HelixToolkit.Wpf.HelixViewport3D _helixView; // 开源 Helix Toolkit (MIT): 真实数模网格 (STL) 窗口内直渲
Microsoft.Web.WebView2.Wpf.WebView2 _webView2; // 方案 B: 窗口内嵌新迪查看器, 真实 STEP 直显 (免弹浏览器)
bool _webView2Ready; // WebView2 运行时可用标记 (不可用自动降级)
#if !CUSTOMER_BUILD
Button _btnAdmin;
#endif
TextBox _searchBox;
public OpcPackage Package { get { return _pkg; } }
public MainWindow(OpcPackage pkg, bool admin)
{
_pkg = pkg;
_genApi = string.IsNullOrEmpty(pkg.Catalog.genApi) ? "" : pkg.Catalog.genApi.TrimEnd('/'); // NX 按需生成服务 (二期)
#if !CUSTOMER_BUILD
_admin = admin;
#endif
Width = 1280; Height = 840; MinWidth = 900; MinHeight = 560;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
var logo = AppResources.LoadPng("OneBot-logo.png");
if (logo != null) Icon = logo; // 窗口图标 (OneBot logo)
BuildUi();
ApplyLang();
if (_pkg != null) LoadPackageIntoUi();
Closed += (s, e) => { if (_previewSrv != null) { try { _previewSrv.Dispose(); } catch { } } };
}
void BuildUi()
{
var root = new DockPanel();
// ---- 顶栏 ----
var top = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(8, 6, 8, 4) };
var logoImg = AppResources.LoadPng("OneBot-logo.png");
if (logoImg != null)
top.Children.Add(new Image { Source = logoImg, Height = 26, Stretch = Stretch.Uniform, Margin = new Thickness(0, 0, 10, 0), VerticalAlignment = VerticalAlignment.Center });
_btnOpen = new Button { Width = 110, Margin = new Thickness(0, 0, 6, 0) };
_btnOpen.Click += (s, e) => OpenPackage();
_searchBox = new TextBox { Width = 200, Margin = new Thickness(10, 0, 4, 0) };
_searchBox.KeyDown += (s, e) => { if (e.Key == Key.Enter) DoSearch(); };
_btnSearch = new Button { Width = 60, Margin = new Thickness(0, 0, 6, 0) };
_btnSearch.Click += (s, e) => DoSearch();
_btnLang = new Button { Width = 60, Margin = new Thickness(0, 0, 6, 0) };
_btnLang.Click += (s, e) => { _lang.Toggle(); ApplyLang(); };
// 清单角标按钮 (常驻顶栏, 2026-08-24)
_btnCart = new Button { Width = 90, Margin = new Thickness(6, 0, 0, 0) };
_btnCart.Click += (s, e) => OpenCart();
#if !CUSTOMER_BUILD
_btnAdmin = new Button { Width = 100, Margin = new Thickness(0, 0, 6, 0), Visibility = _admin ? Visibility.Visible : Visibility.Collapsed };
_btnAdmin.Click += (s, e) => { var w = new AdminWindow(this); w.ShowDialog(); };
#endif
_pkgInfo = new TextBlock { VerticalAlignment = VerticalAlignment.Center };
top.Children.Add(_btnOpen);
top.Children.Add(_searchBox); top.Children.Add(_btnSearch);
top.Children.Add(_btnLang);
top.Children.Add(_btnCart);
#if !CUSTOMER_BUILD
top.Children.Add(_btnAdmin);
#endif
top.Children.Add(_pkgInfo);
DockPanel.SetDock(top, Dock.Top);
root.Children.Add(top);
// ---- 底部状态栏 ----
_status = new TextBlock { Margin = new Thickness(8, 4, 8, 6) };
DockPanel.SetDock(_status, Dock.Bottom);
root.Children.Add(_status);
// ---- 中部: 目录树 | 参数面板 | 标签页 (分隔条可拖拽调整宽度) ----
var center = new Grid();
center.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(250), MinWidth = 150, MaxWidth = 420 });
center.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
center.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(320), MinWidth = 220, MaxWidth = 520 });
center.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
center.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
_tree = new TreeView();
Grid.SetColumn(_tree, 0);
center.Children.Add(_tree);
center.Children.Add(MakeSplitter(1));
var left = new StackPanel { Margin = new Thickness(8) };
_modelTitle = new TextBlock { FontSize = 16, FontWeight = FontWeights.Bold, Margin = new Thickness(0, 0, 0, 8), TextWrapping = TextWrapping.Wrap };
left.Children.Add(_modelTitle);
// 零件号实时条 (DESTACO 风, 2026-09-15): 边选边拼, 已确定片段高亮 / 待定虚化
_skuBar = new Border
{
CornerRadius = new CornerRadius(12),
BorderThickness = new Thickness(1),
BorderBrush = new SolidColorBrush(Color.FromRgb(0x99, 0xF6, 0xE4)),
Background = new LinearGradientBrush(
Color.FromRgb(0xF0, 0xFD, 0xFA), Colors.White, 45),
Padding = new Thickness(12, 10, 12, 10),
Margin = new Thickness(0, 0, 0, 10)
};
var skuInner = new StackPanel();
skuInner.Children.Add(new TextBlock
{
Text = _lang.T("零件号 Part Number", "Part Number"),
FontSize = 10,
Foreground = new SolidColorBrush(Color.FromRgb(0x9C, 0xA3, 0xAF)),
Margin = new Thickness(0, 0, 0, 4)
});
_skuCode = new TextBlock
{
FontFamily = new FontFamily("Consolas, monospace"),
FontSize = 17, FontWeight = FontWeights.Bold,
TextWrapping = TextWrapping.Wrap,
MinHeight = 22
};
skuInner.Children.Add(_skuCode);
_skuProg = new ProgressBar
{
Height = 3, Margin = new Thickness(0, 7, 0, 0),
Minimum = 0, Maximum = 1, Value = 0,
Foreground = new SolidColorBrush(Color.FromRgb(0x14, 0xB8, 0xA6)),
Background = new SolidColorBrush(Color.FromRgb(0xCC, 0xFB, 0xF1)),
BorderThickness = new Thickness(0)
};
skuInner.Children.Add(_skuProg);
_skuHint = new TextBlock
{
FontSize = 11, Margin = new Thickness(0, 6, 0, 0),
TextWrapping = TextWrapping.Wrap,
Foreground = new SolidColorBrush(Color.FromRgb(0x6B, 0x72, 0x80))
};
skuInner.Children.Add(_skuHint);
_skuBar.Child = skuInner;
left.Children.Add(_skuBar);
_paramPanel = new StackPanel();
left.Children.Add(_paramPanel); // 参数最多 7 行, 直接平铺不滚动 (避免下方参数被遮挡)
// 下载操作区 (2026-08-24, 与网页版一致: 参数下方并排 下载 STEP / 下载 PDF, 同款配色)
var actions = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 10, 0, 0) };
_btnDownload = new Button
{
Padding = new Thickness(14, 6, 14, 6),
FontSize = 13,
Margin = new Thickness(0, 0, 8, 0),
Visibility = Visibility.Collapsed,
Background = new SolidColorBrush(Color.FromRgb(0x1B, 0x4F, 0x8C)),
Foreground = Brushes.White,
BorderThickness = new Thickness(0),
Cursor = Cursors.Hand
};
_btnDownload.Click += (s, e) =>
{
// 非标档位 + 生成服务可用 → 在线生成 (二期); 否则原 STEP 下载
if (_curParam != null && !string.IsNullOrEmpty(_genApi)) GenSingle(_curParam);
else DownloadStep();
};
// 生成格式选择 (非标档位在线生成用; 生成输出留存全格式, 格式只影响下载)
_btnGenFmt = new ComboBox
{
Width = 108,
Margin = new Thickness(0, 0, 8, 0),
Visibility = Visibility.Collapsed,
ItemsSource = new[] {
new ComboItem("zip", "全格式 zip"),
new ComboItem("step", "STEP"),
new ComboItem("parasolid", "Parasolid"),
new ComboItem("iges", "IGES"),
new ComboItem("stl", "STL")
},
SelectedIndex = 0
};
_btnDownloadPdf = new Button
{
Padding = new Thickness(14, 6, 14, 6),
FontSize = 13,
Visibility = Visibility.Collapsed,
Background = new SolidColorBrush(Color.FromRgb(0xE9, 0xEC, 0xEF)),
Foreground = new SolidColorBrush(Color.FromRgb(0x33, 0x33, 0x33)),
BorderBrush = new SolidColorBrush(Color.FromRgb(0xCC, 0xCC, 0xCC)),
BorderThickness = new Thickness(1),
Cursor = Cursors.Hand
};
_btnDownloadPdf.Click += (s, e) => DownloadDatasheet();
// 加入清单 (淘宝式购物车, 2026-08-24)
_btnAddCart = new Button
{
Padding = new Thickness(14, 6, 14, 6),
FontSize = 13,
Margin = new Thickness(0, 0, 8, 0),
Visibility = Visibility.Collapsed,
Background = new SolidColorBrush(Color.FromRgb(0xE9, 0xEC, 0xEF)),
Foreground = new SolidColorBrush(Color.FromRgb(0x33, 0x33, 0x33)),
BorderBrush = new SolidColorBrush(Color.FromRgb(0xCC, 0xCC, 0xCC)),
BorderThickness = new Thickness(1),
Cursor = Cursors.Hand
};
_btnAddCart.Click += (s, e) =>
{
if (_curVariant != null) AddToCart(_curVariant);
else if (_curParam != null) AddToCart(_curParam);
};
actions.Children.Add(_btnDownload); actions.Children.Add(_btnGenFmt); actions.Children.Add(_btnDownloadPdf); actions.Children.Add(_btnAddCart);
left.Children.Add(actions);
Grid.SetColumn(left, 2);
center.Children.Add(left);
center.Children.Add(MakeSplitter(3));
_tabs = new TabControl();
var tab3d = new TabItem();
// 3D/尺寸图同页 (2026-08-24, 与网页版布局一致): 左 3D 视口 | 可拖拽分隔条 | 右 尺寸图+手册页
var grid3d = new Grid();
grid3d.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 });
grid3d.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid3d.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(320), MinWidth = 180, MaxWidth = 640 });
var view3d = new Grid();
_viewer = new ViewerControl();
view3d.Children.Add(_viewer);
// 窗口内 3D (开源 Helix Toolkit, MIT): 渲染 NX 导出的 STL 网格, 免浏览器
_helixView = new HelixToolkit.Wpf.HelixViewport3D
{
Visibility = Visibility.Collapsed,
Background = new LinearGradientBrush(
Color.FromRgb(0xF2, 0xF4, 0xF7), Color.FromRgb(0xD8, 0xDE, 0xE6), 90.0),
ShowCoordinateSystem = false,
ShowViewCube = false
};
_helixView.Children.Add(new HelixToolkit.Wpf.DefaultLights());
view3d.Children.Add(_helixView);
// 窗口内嵌 3D (方案 B, WebView2): 真实 STEP 直接渲染, 不弹外部浏览器
_webView2 = new Microsoft.Web.WebView2.Wpf.WebView2 { Visibility = Visibility.Collapsed };
view3d.Children.Add(_webView2);
// 真实数模回退面板: 桌面内置解析器只支持简化占位几何, 真实 B-rep 走本机浏览器+新迪查看器
_fallbackPanel = new StackPanel
{
Visibility = Visibility.Collapsed,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
MaxWidth = 520
};
_fallbackPanel.Children.Add(new TextBlock
{
Text = "该型号为真实 CAD 数模,已自动在浏览器中打开 3D 预览(本机运行,完全离线)。\n若浏览器窗口被关闭,可点下方按钮重新打开。",
TextWrapping = TextWrapping.Wrap,
TextAlignment = TextAlignment.Center,
Margin = new Thickness(20, 0, 20, 14)
});
_btnBrowser3D = new Button { Width = 240, Height = 42, FontSize = 15 };
_btnBrowser3D.Click += (s, e) => OpenBrowser3D();
_fallbackPanel.Children.Add(_btnBrowser3D);
view3d.Children.Add(_fallbackPanel);
// 非标档位提示条 (参数化引擎: 3D 展示最近标准档位近似几何时显示; 与网页版一致) — 最后添加保证置顶
_viewerHint = new Border
{
Background = new SolidColorBrush(Color.FromArgb(0xE6, 0xFF, 0xF7, 0xD6)),
BorderBrush = new SolidColorBrush(Color.FromRgb(0xD9, 0xB6, 0x3C)),
BorderThickness = new Thickness(1),
Padding = new Thickness(8, 4, 8, 4),
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(6),
Visibility = Visibility.Collapsed
};
_viewerHint.Child = new TextBlock { TextWrapping = TextWrapping.Wrap, FontSize = 12 };
view3d.Children.Add(_viewerHint);
Grid.SetColumn(view3d, 0);
grid3d.Children.Add(view3d);
grid3d.Children.Add(MakeSplitter(1));
_dimPanel = new StackPanel();
_dimPanel.Children.Add(_dimImage = new Image { Stretch = Stretch.Uniform, Margin = new Thickness(8), HorizontalAlignment = HorizontalAlignment.Left });
_manualPanel = new StackPanel();
_dimPanel.Children.Add(_manualPanel);
var dimScroll = new ScrollViewer { Content = _dimPanel, VerticalScrollBarVisibility = ScrollBarVisibility.Auto };
Grid.SetColumn(dimScroll, 2);
grid3d.Children.Add(dimScroll);
tab3d.Content = grid3d;
var tabGrid = new TabItem(); tabGrid.Content = BuildGridTab();
var tabSearch = new TabItem(); tabSearch.Content = BuildSearchTab();
var tabCross = new TabItem(); tabCross.Content = BuildCrossRefTab();
_tabs.Items.Add(tab3d); _tabs.Items.Add(tabGrid); _tabs.Items.Add(tabSearch); _tabs.Items.Add(tabCross);
Grid.SetColumn(_tabs, 4);
center.Children.Add(_tabs);
root.Children.Add(center);
Content = root;
Loaded += (s, e) => InitWebView2Async(); // WebView2 初始化须在窗口加载后
Closed += (s, e) => { if (_webView2 != null) _webView2.Dispose(); };
}
// WebView2 运行时初始化 (窗口内嵌 3D 方案 B): 失败则静默降级到浏览器预览
async void InitWebView2Async()
{
try
{
var userData = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"OnebotCatalog", "WebView2");
var env = await Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync(null, userData);
await _webView2.EnsureCoreWebView2Async(env);
_webView2Ready = true;
}
catch
{
_webView2Ready = false;
_status.Text = _lang.T(
"未检测到 WebView2 运行时, 真实数模用浏览器预览 (Win11 自带; Win10 装 Edge 即可)",
"WebView2 runtime not found - real models open in browser (built into Win11; install Edge on Win10)");
}
}
// 列分隔条 (鼠标拖拽调宽)
GridSplitter MakeSplitter(int col)
{
var sp = new GridSplitter { Width = 6, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
Grid.SetColumn(sp, col);
return sp;
}
static T FindAncestor(DependencyObject d) where T : DependencyObject
{
while (d != null)
{
if (d is T) return (T)d;
d = VisualTreeHelper.GetParent(d);
}
return null;
}
// ---- 参数表 Tab (范围筛选 + 批量下载) ----
DockPanel BuildGridTab()
{
var panel = new DockPanel();
var top = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(4) };
_rangePanel = new StackPanel { Orientation = Orientation.Horizontal };
top.Children.Add(_rangePanel);
_btnRangeApply = new Button { Width = 60, Margin = new Thickness(6, 0, 4, 0) };
_btnRangeApply.Click += (s, e) => ApplyRange();
_btnRangeReset = new Button { Width = 60, Margin = new Thickness(0, 0, 10, 0) };
_btnRangeReset.Click += (s, e) => ResetRange();
_btnExport = new Button { Width = 110 };
_btnExport.Click += (s, e) => ExportSelected();
top.Children.Add(_btnRangeApply); top.Children.Add(_btnRangeReset); top.Children.Add(_btnExport);
DockPanel.SetDock(top, Dock.Top);
panel.Children.Add(top);
_grid = new DataGrid { AutoGenerateColumns = false, IsReadOnly = true, SelectionMode = DataGridSelectionMode.Extended, SelectionUnit = DataGridSelectionUnit.FullRow, Margin = new Thickness(4) };
// 双击行 → 参数带入选型 (与网页版一致; 单击/框选仍用于批量下载)
_grid.MouseDoubleClick += (s, e) =>
{
var dr = _grid.SelectedItem as DataRowView;
if (dr != null) NavigateToModel(dr["modelCode"].ToString());
};
// 右键列头 → 按列筛选 (2026-08-24, 与网页版列筛选一致)
_grid.PreviewMouseRightButtonUp += (s, e) =>
{
var header = FindAncestor(e.OriginalSource as DependencyObject);
if (header == null || header.Column == null) return;
var col = header.Column as DataGridTextColumn;
if (col == null || !(col.Binding is System.Windows.Data.Binding)) return;
var path = ((System.Windows.Data.Binding)col.Binding).Path.Path;
if (path == "modelCode") return;
var menu = new ContextMenu();
var all = new MenuItem { Header = _lang.T("全部 (清除本列筛选)", "All (clear filter)") };
all.Click += (s2, e2) => { _colFilter.Remove(path); BuildGrid(); };
menu.Items.Add(all);
var vals = new List();
foreach (DataRowView r in _grid.Items) { var sv = Convert.ToString(r[path]); if (!vals.Contains(sv)) vals.Add(sv); }
foreach (var sv in vals)
{
var mi = new MenuItem
{
Header = sv.Length == 0 ? _lang.T("(空值)", "(empty)") : sv,
IsChecked = _colFilter.ContainsKey(path) && _colFilter[path] == sv
};
mi.Click += (s2, e2) => { _colFilter[path] = sv; BuildGrid(); };
menu.Items.Add(mi);
}
menu.PlacementTarget = header;
menu.IsOpen = true;
e.Handled = true;
};
var gridHint = new TextBlock
{
Text = "双击行 → 参数带入选型 | 右键列头 → 按列筛选 | 单击/框选多行用于批量下载",
FontSize = 12,
Foreground = new SolidColorBrush(Color.FromRgb(0x88, 0x88, 0x88)),
Margin = new Thickness(6, 2, 0, 4)
};
DockPanel.SetDock(gridHint, Dock.Bottom);
panel.Children.Add(gridHint);
panel.Children.Add(_grid);
return panel;
}
// ---- 搜索 Tab ----
DockPanel BuildSearchTab()
{
var panel = new DockPanel { Margin = new Thickness(4) };
var hint = new TextBlock
{
Text = "双击结果定位到该型号。支持: 型号编码 / 中文名 / 英文名 / 关键词(拼音) / 竞品型号(平替, 支持模糊)。",
TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 4)
};
DockPanel.SetDock(hint, Dock.Top);
panel.Children.Add(hint);
_searchGrid = new DataGrid { AutoGenerateColumns = false, IsReadOnly = true, SelectionMode = DataGridSelectionMode.Single };
_searchGrid.Columns.Add(new DataGridTextColumn { Header = "型号 Model", Binding = new System.Windows.Data.Binding("ModelCode"), Width = 180 });
_searchGrid.Columns.Add(new DataGridTextColumn { Header = "系列 Series", Binding = new System.Windows.Data.Binding("SeriesName") });
_searchGrid.Columns.Add(new DataGridTextColumn { Header = "STEP", Binding = new System.Windows.Data.Binding("Step") });
_searchGrid.MouseDoubleClick += (s, e) => JumpToSelected();
panel.Children.Add(_searchGrid);
return panel;
}
// ---- 数据包加载 ----
void OpenPackage()
{
var dlg = new OpenFileDialog { Filter = "目录数据包 (*.opc)|*.opc", Title = "打开 .opc 数据包" };
if (dlg.ShowDialog() == true)
ReplacePackage(dlg.FileName);
}
public bool ReplacePackage(string path)
{
try
{
var np = OpcPackage.Open(path);
if (_pkg != null) _pkg.Dispose();
_pkg = np;
LoadPackageIntoUi();
return true;
}
catch (Exception ex)
{
MessageBox.Show("数据包打开失败: " + ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
}
void LoadPackageIntoUi()
{
_search = new SearchEngine(_pkg.Catalog);
_tree.Items.Clear();
foreach (var cat in _pkg.Catalog.categories)
{
var catNode = new TreeViewItem { Header = cat.nameZh, Tag = cat };
foreach (var sc in cat.series)
{
var s = _pkg.Catalog.series.FirstOrDefault(x => x.code == sc);
if (s == null) continue;
var node = new TreeViewItem { Header = s.nameZh, Tag = s };
catNode.Items.Add(node);
}
catNode.IsExpanded = true;
_tree.Items.Add(catNode);
}
_tree.SelectedItemChanged += TreeSelected;
_pkgInfo.Text = string.Format("{0} v{1}", _pkg.Catalog.catalogName, _pkg.Catalog.catalogVersion);
RefreshCrossRef();
_status.Text = _lang.T("请选择产品系列", "Select a series");
_series = null; _cfg = null;
_paramPanel.Children.Clear();
_boxes.Clear(); _sel.Clear();
_tiles.Clear(); _tilePanels.Clear();
if (_skuBar != null) _skuBar.Visibility = Visibility.Collapsed;
}
void TreeSelected(object sender, RoutedPropertyChangedEventArgs