基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
// ============================================================
|
||||
// 停靠窗口:CAD 查看器(中央文档区)
|
||||
// WebView2 承载网页版 CAD-Viewer(localhost:5173),并挂到 CadBridge。
|
||||
// ★ 网页服务自启动:安装布局 {app}\cadbt\node\node.exe + {app}\cadbt\CAD-Viewer\dist\server.cjs
|
||||
// (静态产物零依赖,客户机无需 Node 环境/pnpm dev;主程序同款端口探测模式)
|
||||
// ============================================================
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
|
||||
namespace 成套报价软件
|
||||
{
|
||||
/// <summary>停靠窗口:网页 CAD 查看器(DockState.Document)。</summary>
|
||||
public partial class CadViewerWindow : WeifenLuo.WinFormsUI.Docking.DockContent
|
||||
{
|
||||
private WebView2 _webView;
|
||||
private Label _hint;
|
||||
|
||||
/// <summary>静态服务进程句柄(退出时杀进程树)。</summary>
|
||||
private static Process _serverProcess;
|
||||
|
||||
public CadViewerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.BackColor = System.Drawing.Color.White;
|
||||
Text = "CAD 查看器";
|
||||
TabText = "CAD 查看器";
|
||||
FormClosed += (s, e) => 停止网页服务();
|
||||
BuildToolbar();
|
||||
挂加载状态();
|
||||
LoadViewer();
|
||||
}
|
||||
|
||||
/// <summary>CAD 网页服务端口(与 server.cjs 一致)。</summary>
|
||||
private const int 服务端口 = 5173;
|
||||
|
||||
/// <summary>顶部工具栏:显示蒙版按钮(蒙版存档就位后手动加载——
|
||||
/// 打开图纸统一走网页自带的上传界面,实测渲染无问题)。
|
||||
/// 右侧状态标签实时显示加载渲染进度(大图 40~60 秒,期间黑屏是正常加载态)。</summary>
|
||||
private ToolStripLabel _statusLabel;
|
||||
|
||||
private void BuildToolbar()
|
||||
{
|
||||
var ts = new ToolStrip();
|
||||
ts.Dock = DockStyle.Top;
|
||||
ts.GripStyle = ToolStripGripStyle.Hidden;
|
||||
var btnMask = new ToolStripButton("显示蒙版");
|
||||
btnMask.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F);
|
||||
btnMask.Click += (s, e) => 成套报价软件.CadBridge.ReloadMasks();
|
||||
ts.Items.Add(btnMask);
|
||||
_statusLabel = new ToolStripLabel("等待打开图纸(用网页里的选择文件按钮)");
|
||||
_statusLabel.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F);
|
||||
_statusLabel.ForeColor = System.Drawing.Color.DimGray;
|
||||
ts.Items.Add(new ToolStripSeparator());
|
||||
ts.Items.Add(_statusLabel);
|
||||
Controls.Add(ts);
|
||||
}
|
||||
|
||||
/// <summary>订阅网页加载进度,实时刷到工具栏状态标签(完成后显示总耗时)。</summary>
|
||||
private void 挂加载状态()
|
||||
{
|
||||
成套报价软件.CadBridge.加载状态 += On加载状态;
|
||||
FormClosed += (s, e) => 成套报价软件.CadBridge.加载状态 -= On加载状态;
|
||||
}
|
||||
|
||||
private void On加载状态(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_statusLabel == null || IsDisposed) return;
|
||||
if (InvokeRequired)
|
||||
BeginInvoke((MethodInvoker)delegate { 刷状态(text); });
|
||||
else
|
||||
刷状态(text);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void 刷状态(string text)
|
||||
{
|
||||
if (_statusLabel == null) return;
|
||||
_statusLabel.Text = text;
|
||||
// 完成显示绿,失败红,进行中灰
|
||||
if (text.IndexOf("渲染完成") >= 0) _statusLabel.ForeColor = System.Drawing.Color.Green;
|
||||
else if (text.IndexOf("失败") >= 0) _statusLabel.ForeColor = System.Drawing.Color.Red;
|
||||
else _statusLabel.ForeColor = System.Drawing.Color.DimGray;
|
||||
}
|
||||
|
||||
private async void LoadViewer()
|
||||
{
|
||||
try
|
||||
{
|
||||
_webView = new WebView2();
|
||||
_webView.Dock = DockStyle.Fill;
|
||||
Controls.Add(_webView);
|
||||
// ★ WinForms 停靠按控件集合逆序排版:Fill 控件必须处在最前(最低索引),
|
||||
// 最后分剩余空间;不加这句,先加的工具栏(Top)会叠在 WebView 上面盖住 CAD 画面
|
||||
_webView.BringToFront();
|
||||
await _webView.EnsureCoreWebView2Async(null);
|
||||
CadBridge.Attach(_webView);
|
||||
|
||||
// ★ 禁用浏览器快捷键(F5/Ctrl+R/Ctrl+F 等):刷新会丢掉所有扒图蒙版
|
||||
_webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;
|
||||
|
||||
// ★ 先确保静态服务在线(复刻主程序 StartPriceWebView 的探测+拉起模式)
|
||||
启动网页服务();
|
||||
|
||||
// 服务不可达时给用户明确提示
|
||||
_webView.CoreWebView2.NavigationCompleted += (s, e) =>
|
||||
{
|
||||
if (!e.IsSuccess && _hint == null)
|
||||
ShowLoadHint("无法连接网页 CAD 服务(端口 " + 服务端口 + ")。\r\n请确认 cadbt\\node\\node.exe 与 cadbt\\CAD-Viewer\\dist 目录完整,然后点击\"重新加载\"。");
|
||||
};
|
||||
// ★ URL 带时间戳:dist 网页热更新后强制 WebView2 拉新 index.html
|
||||
// (旧 node 进程无 no-store 头时会启发式缓存,网页端跑旧 bundle
|
||||
// 导致宿主新消息无人处理——组框/高亮全失效)
|
||||
_webView.CoreWebView2.Navigate("http://localhost:" + 服务端口 + "/?v=" + DateTime.Now.Ticks);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowLoadHint("WebView2 初始化失败: " + ex.Message + "\r\n请确认系统已安装 Microsoft Edge WebView2 运行时。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>启动 CAD 网页静态服务(端口已通=复用;不通=node server.cjs 后台拉起)。</summary>
|
||||
private void 启动网页服务()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (端口已通()) return; // 上次实例或手动 dev server 已在服务
|
||||
|
||||
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
// 安装布局:{app}\cadbt\node\node.exe + {app}\cadbt\CAD-Viewer\dist\server.cjs
|
||||
// (make_package 已展平 packages 层,与独立 cadbt.exe 共用同一套文件)
|
||||
string nodeExe = Path.Combine(baseDir, "cadbt", "node", "node.exe");
|
||||
string serverScript = Path.Combine(baseDir, "cadbt", "CAD-Viewer", "dist", "server.cjs");
|
||||
// ★ 开发机回退:仓库根在 bin\Debug 上三级(仓库根\成套报价软件\bin\Debug)。
|
||||
// 旧代码只回退两级,算出 ...\成套报价软件\成套报价软件\cadbt\...(多一层项目目录),
|
||||
// 开发版一直起不了服务——此前全靠装机目录实例顶着 5173 端口才没暴露
|
||||
// (2026-09-24 cad服务.log 实证:缺少 node.exe 或 server.cjs 路径带双层项目名)。
|
||||
// 两级三级都探测,哪个存在用哪个(防未来目录层级再变)。
|
||||
if (!File.Exists(nodeExe))
|
||||
{
|
||||
string 三级 = Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\cadbt\cadbt\bin\Debug\node\node.exe"));
|
||||
string 二级 = Path.GetFullPath(Path.Combine(baseDir, @"..\..\cadbt\cadbt\bin\Debug\node\node.exe"));
|
||||
nodeExe = File.Exists(三级) ? 三级 : (File.Exists(二级) ? 二级 : 三级);
|
||||
}
|
||||
if (!File.Exists(serverScript))
|
||||
{
|
||||
string 三级 = Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\cadbt\cadbt\CAD-Viewer\packages\cad-viewer-example\dist\server.cjs"));
|
||||
string 二级 = Path.GetFullPath(Path.Combine(baseDir, @"..\..\cadbt\cadbt\CAD-Viewer\packages\cad-viewer-example\dist\server.cjs"));
|
||||
serverScript = File.Exists(三级) ? 三级 : (File.Exists(二级) ? 二级 : 三级);
|
||||
}
|
||||
|
||||
if (!File.Exists(nodeExe) || !File.Exists(serverScript))
|
||||
{
|
||||
WriteDiag("[CAD服务] 缺少 node.exe 或 server.cjs: node=" + nodeExe + " script=" + serverScript);
|
||||
return;
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = nodeExe,
|
||||
Arguments = "\"" + Path.GetFullPath(serverScript) + "\"",
|
||||
WorkingDirectory = Path.GetDirectoryName(Path.GetFullPath(serverScript)),
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
};
|
||||
_serverProcess = Process.Start(psi);
|
||||
WriteDiag("[CAD服务] 已启动 node PID=" + (_serverProcess == null ? "?" : _serverProcess.Id.ToString()));
|
||||
|
||||
// 等端口就绪(最多 8 秒)
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
System.Threading.Thread.Sleep(200);
|
||||
if (端口已通()) { WriteDiag("[CAD服务] 端口就绪, 耗时 " + (i + 1) * 200 + "ms"); return; }
|
||||
}
|
||||
WriteDiag("[CAD服务] 端口 8 秒未就绪(继续导航,由 NavigationCompleted 提示)");
|
||||
}
|
||||
catch (Exception ex) { WriteDiag("[CAD服务] 启动异常: " + ex.Message); }
|
||||
}
|
||||
|
||||
/// <summary>快速探测本机端口。</summary>
|
||||
private static bool 端口已通()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
var ar = client.BeginConnect("127.0.0.1", 服务端口, null, null);
|
||||
bool ok = ar.AsyncWaitHandle.WaitOne(400);
|
||||
if (ok && client.Connected) { client.EndConnect(ar); return true; }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>退出清理:杀 node 静态服务进程树(taskkill /T 覆盖可能的子进程)。</summary>
|
||||
private void 停止网页服务()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serverProcess != null && !_serverProcess.HasExited)
|
||||
{
|
||||
int pid = _serverProcess.Id;
|
||||
Process.Start(new ProcessStartInfo("taskkill", "/PID " + pid + " /T /F")
|
||||
{
|
||||
CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden
|
||||
});
|
||||
WriteDiag("[CAD服务] 已停止 PID=" + pid);
|
||||
}
|
||||
_serverProcess = null;
|
||||
}
|
||||
catch (Exception ex) { WriteDiag("[CAD服务] 停止异常: " + ex.Message); }
|
||||
}
|
||||
|
||||
/// <summary>简单诊断日志(logs\cad服务.log)。</summary>
|
||||
private static void WriteDiag(string msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
|
||||
Directory.CreateDirectory(dir);
|
||||
File.AppendAllText(Path.Combine(dir, "cad服务.log"),
|
||||
DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg + Environment.NewLine);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void ShowLoadHint(string text)
|
||||
{
|
||||
if (_hint != null) return;
|
||||
_hint = new Label();
|
||||
_hint.Dock = DockStyle.Fill;
|
||||
_hint.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
_hint.Text = text;
|
||||
var reload = new Button();
|
||||
reload.Text = "重新加载";
|
||||
reload.Dock = DockStyle.Bottom;
|
||||
reload.Click += (s, e) =>
|
||||
{
|
||||
Controls.Remove(_hint);
|
||||
_hint.Dispose();
|
||||
_hint = null;
|
||||
启动网页服务();
|
||||
if (_webView != null && _webView.CoreWebView2 != null)
|
||||
_webView.CoreWebView2.Navigate("http://localhost:" + 服务端口 + "/");
|
||||
};
|
||||
Controls.Add(reload);
|
||||
Controls.Add(_hint);
|
||||
_hint.BringToFront();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user