using System; using System.Collections.Generic; using System.Data; using System.Windows.Forms; using Smart.CustomComponents; namespace 成套报价软件 { /// /// 调母排 的交互逻辑(事件处理)。 /// UI 布局定义见 调母排.ui.cs。 /// /// 用法:主窗口报价页 button15「调母排」传入主窗口引用弹出。 /// 功能: /// ① 打开时从主窗口母排页带出 当前铜价/当前铝价/铜加工费/铝加工费 /// ② advancedDataGridView1 汇总当前项目所有图号→箱柜→元件里的 铜排/铝排 /// (按规格识别材质:LMY 开头=铝排,其余(TMY 等)=铜排;类型=母排 的行也算) /// ③ 「计算」:按 新表价 = (铜价或铝价)×每米重量 + 对应加工费 重算表格的 /// "表价(更改后)"列;铜排铝排各自用对应价格与加工费 /// ④ 「更新到项目」:把"表价(更改后)"写回所有元件表(表价+派生列 SQL 重算), /// 并通知主窗口刷新报价页 /// public partial class 调母排 { // ===== 主窗口引用与上下文 ===== /// 主窗口引用(取母排页价格/加工费,更新后刷新报价页)。 private MainForm _main = null; /// 当前项目编号。 private string _projectId = ""; /// 汇总表内存数据(元件名称/规格/单位/表价更改前/表价更改后)。 private DataTable _table = null; /// 每行对应元件表名+行序号,更新到项目时定位用。 private List _rowKeys = new List(); /// 构造函数(独立运行测试用)。 public 调母排() { InitializeComponent(); // 事件绑定(运行时绑,不动 ui.cs) this.button1.Click += button1_Click; // 计算 this.button2.Click += button2_Click; // 更新到项目 this.button3.Click += button3_Click; // 退出 // ★ 加工费联动(与 tabPage12 母排页 Recalc铜价PerKg 同规则): // 价格框显示 = (金属价/吨 + 加工费/吨) / 1000 → 元/kg // 改加工费时自动把价格框重算成"金属价+加工费"合价 this.textBox1加工费1.TextChanged += (s, e) => Recalc铜价框(false); this.textBox1加工费2.TextChanged += (s, e) => Recalc铜价框(true); } /// 带出价格时记录的"纯金属价(元/kg)"(未含加工费),联动重算用。 private decimal _纯铜价Kg = 0m; private decimal _纯铝价Kg = 0m; /// /// 加工费变化 → 重算对应价格框 = 纯金属价(kg) + 加工费(元/吨)/1000。 /// (加工费单位与价格一致:元/吨,除以 1000 变元/kg) /// 与 tabPage12 的 Recalc铜价PerKg 公式相同。 /// private void Recalc铜价框(bool 铝) { decimal 纯价 = 铝 ? _纯铝价Kg : _纯铜价Kg; if (纯价 <= 0m) return; // 还没带出过价格,不动 decimal 费吨 = ParseDec(铝 ? this.textBox1加工费2.Text : this.textBox1加工费1.Text); decimal 合价 = 纯价 + 费吨 / 1000m; if (铝) this.textBox2当前铝价.Text = Math.Round(合价, 2).ToString("0.##"); else this.textBox1当前铜价.Text = Math.Round(合价, 2).ToString("0.##"); } /// 主窗口调用:传主窗口引用,打开时自动带价格并汇总。 public 调母排(MainForm main) : this() { _main = main; } /// 窗体加载:先落盘+汇总母排,再自动抓取铜价/铝价填入。 protected override void OnLoad(EventArgs e) { base.OnLoad(e); try { // ① 主窗口内存里未保存的元件编辑先落盘(本窗口直接读库,必须看到最新数据) if (_main != null) _main.FlushQuoteTablesToDb(); // ② 加工费带出 this.textBox1加工费1.Text = _main != null ? _main.Get母排加工费(false) : ""; this.textBox1加工费2.Text = _main != null ? _main.Get母排加工费(true) : ""; // ③ 汇总整个项目的铜排/铝排 LoadSummary(); // ④ 自动抓取铜价+铝价(异步,抓到自动填入文本框) FetchMetalPrices(); } catch (Exception ex) { MessageBox.Show(this, "初始化失败:\n" + ex.Message, "调母排", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // ============================================================ // 数据路径与工具 // ============================================================ /// 主窗口实例上的当前项目编号(通过公共方式取,不依赖私有字段)。 private string ProjectId { get { if (!string.IsNullOrEmpty(_projectId)) return _projectId; if (_main != null) _projectId = _main.GetCurrentProjectId(); return _projectId ?? ""; } } /// 当前项目报价库路径(与主窗口同规则)。 private string QuoteDbPath { get { string safe = ProjectId; foreach (char c in System.IO.Path.GetInvalidFileNameChars()) safe = safe.Replace(c, '_'); return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"sjk\bjb\项目" + safe + ".db"); } } private static string ConnStr(string dbPath) { return "Data Source=" + dbPath + ";Version=3;"; } private static decimal ParseDec(string s) { decimal d; if (decimal.TryParse((s ?? "").Trim(), out d)) return d; return 0m; } // ============================================================ // ① 价格来源:读配置文件缓存(主窗口母排页抓价时写入),不发网络请求 // ============================================================ /// /// 从 shezhi\铜排参数.config.xml / 铝排参数.config.xml 读缓存的铜价/铝价(元/吨), /// 加上加工费后换算成 元/kg 填入文本框(与 tabPage12 母排页口径一致: /// 价格框显示的就是"金属价+加工费"的合价)。 /// ★ 缓存缺失的品种自动联网补抓(调母排自己抓,不依赖主窗口切换材质刷新过)。 /// private void FetchMetalPrices() { string[] 铜缓存 = _main != null ? _main.Get母排价格缓存(false) : new string[] { "", "" }; string[] 铝缓存 = _main != null ? _main.Get母排价格缓存(true) : new string[] { "", "" }; decimal 铜吨 = ParseDec(铜缓存[0]); decimal 铝吨 = ParseDec(铝缓存[0]); // 缓存缺哪个,退回主窗口当前文本框值(材质对得上才用) if (铜吨 <= 0m && _main != null && !_main.Get母排是铝排()) 铜吨 = ParseDec(_main.Get母排当前价格文本()); if (铝吨 <= 0m && _main != null && _main.Get母排是铝排()) 铝吨 = ParseDec(_main.Get母排当前价格文本()); // ★ 还有缺的 → 后台联网补抓(如铝价:主窗口一直在铜排模式,缓存里就没有铝价) if (铜吨 <= 0m || 铝吨 <= 0m) { FetchMetalPricesOnline(铜吨 <= 0m, 铝吨 <= 0m); } // ★ 价格框 = (金属价/吨 + 加工费/吨) / 1000 = 金属价kg + 加工费kg decimal 铜费吨 = ParseDec(this.textBox1加工费1.Text); decimal 铝费吨 = ParseDec(this.textBox1加工费2.Text); if (铜吨 > 0m) { _纯铜价Kg = Math.Round(铜吨 / 1000m, 2); this.textBox1当前铜价.Text = Math.Round(_纯铜价Kg + 铜费吨 / 1000m, 2).ToString("0.##"); } if (铝吨 > 0m) { _纯铝价Kg = Math.Round(铝吨 / 1000m, 2); this.textBox2当前铝价.Text = Math.Round(_纯铝价Kg + 铝费吨 / 1000m, 2).ToString("0.##"); } // 标题栏标注价格日期(铜/铝各自缓存的) string 铜日 = 铜缓存[1]; string 铝日 = 铝缓存[1]; List parts = new List(); if (铜吨 > 0m) parts.Add("铜价日期:" + (string.IsNullOrEmpty(铜日) ? "?" : 铜日)); if (铝吨 > 0m) parts.Add("铝价日期:" + (string.IsNullOrEmpty(铝日) ? "?" : 铝日)); this.Text = "调母排" + (parts.Count > 0 ? "(" + string.Join(" ", parts.ToArray()) + ")" : "(获取价格中…)"); } /// 长江有色数据接口(与主窗口母排页一致)。 private const string 价格接口 = "https://www.ccmn.cn/shop/historyData/getCjysAndCjysw"; /// 接口 AES 密钥(与主窗口一致)。 private const string AES密钥 = "ccmnCjysw1455881"; /// /// 后台联网抓缺失的金属价(need铜/need铝 指明缺哪个,至少一个为 true), /// 抓到后:①写入配置缓存(下次不用再抓) ②回 UI 线程填入价格框(含加工费合价)。 /// 失败则标题栏提示手填。 /// private void FetchMetalPricesOnline(bool need铜, bool need铝) { System.Threading.Tasks.Task.Factory.StartNew(() => { string 铜吨 = "", 铝吨 = "", 日期 = ""; try { System.Net.ServicePointManager.SecurityProtocol = (System.Net.SecurityProtocolType)3072; // Tls12 string json; using (var wc = new System.Net.WebClient()) { wc.Encoding = System.Text.Encoding.UTF8; wc.Headers[System.Net.HttpRequestHeader.UserAgent] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; wc.Headers[System.Net.HttpRequestHeader.Referer] = "https://www.ccmn.cn/"; json = wc.DownloadString(价格接口); } string showPriceList = 取JSON字段(json, "showPriceList"); if (!string.IsNullOrEmpty(showPriceList)) { string plain = AES解密(showPriceList); foreach (System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(plain, @"\{[^}]*\}")) { string obj = m.Value; string 名称 = 取JSON字段(obj, "productSortName"); var 均价m = System.Text.RegularExpressions.Regex.Match(obj, @"""avgPrice""\s*:\s*([0-9]+\.?[0-9]*)"); if (!均价m.Success) continue; if (名称 == "1#铜" && need铜) { 铜吨 = 均价m.Groups[1].Value; 日期 = 取JSON字段(obj, "publishDate"); } else if (名称 == "A00铝" && need铝) { 铝吨 = 均价m.Groups[1].Value; } } } } catch { } // 回 UI 线程填入 if (this.IsHandleCreated && !this.IsDisposed) { try { this.BeginInvoke(new System.Action(() => { decimal 铜费吨 = ParseDec(this.textBox1加工费1.Text); decimal 铝费吨 = ParseDec(this.textBox1加工费2.Text); decimal d铜 = ParseDec(铜吨); if (d铜 > 0m) { // 写入配置缓存(下次打开直接读,不再联网) if (_main != null) _main.Save母排价格缓存Pub(false, 铜吨, 日期); _纯铜价Kg = Math.Round(d铜 / 1000m, 2); this.textBox1当前铜价.Text = Math.Round(_纯铜价Kg + 铜费吨 / 1000m, 2).ToString("0.##"); } decimal d铝 = ParseDec(铝吨); if (d铝 > 0m) { if (_main != null) _main.Save母排价格缓存Pub(true, 铝吨, 日期); _纯铝价Kg = Math.Round(d铝 / 1000m, 2); this.textBox2当前铝价.Text = Math.Round(_纯铝价Kg + 铝费吨 / 1000m, 2).ToString("0.##"); } List parts = new List(); if (ParseDec(this.textBox1当前铜价.Text) > 0m) parts.Add("铜价OK"); if (ParseDec(this.textBox2当前铝价.Text) > 0m) parts.Add("铝价OK"); this.Text = "调母排" + (parts.Count == 2 ? "(价格已就绪)" : parts.Count > 0 ? "(" + string.Join(" ", parts.ToArray()) + " 另一价请手填)" : "(价格获取失败,请手填)"); })); } catch { } } }); } /// AES-ECB(PKCS7) 解密接口 Base64 密文(与主窗口铜价AES解密一致)。 private static string AES解密(string 加密Base64) { byte[] 密钥 = System.Text.Encoding.UTF8.GetBytes(AES密钥); byte[] 密文 = System.Convert.FromBase64String(加密Base64); using (var aes = System.Security.Cryptography.Aes.Create()) { aes.Mode = System.Security.Cryptography.CipherMode.ECB; aes.Padding = System.Security.Cryptography.PaddingMode.PKCS7; aes.Key = 密钥; using (var 解密器 = aes.CreateDecryptor()) { byte[] 明文 = 解密器.TransformFinalBlock(密文, 0, 密文.Length); return System.Text.Encoding.UTF8.GetString(明文); } } } /// 从简单 JSON 取字符串字段(与MainForm 取JSON字段 一致)。 private static string 取JSON字段(string json, string key) { if (string.IsNullOrEmpty(json)) return ""; var m = System.Text.RegularExpressions.Regex.Match(json, "\"" + System.Text.RegularExpressions.Regex.Escape(key) + "\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\""); if (!m.Success) return ""; return m.Groups[1].Value.Replace("\\\"", "\"").Replace("\\\\", "\\").Replace("\\/", "/"); } // ============================================================ // ② 汇总当前项目所有铜排/铝排元件 // ============================================================ /// 汇总表列定义。 private static readonly string[] SummaryCols = new string[] { "元件名称", "规格", "单位", "表价(更改前)", "表价(更改后)" }; /// 按规格判断材质:true=铝排,false=铜排。LMY 前缀=铝,其余(TMY/裸规格)=铜。 private static bool Is铝排(string 规格) { string s = (规格 ?? "").Trim().ToUpperInvariant(); return s.StartsWith("LMY") || s.StartsWith("L") && s.IndexOf('-') > 0 && s.Substring(0, s.IndexOf('-')) == "LMY"; } /// /// 解析规格得到 层数/宽度/厚度(与MainForm TryParse铜排规格 同规则): /// 去掉 "TMY-"/"LMY-" 前缀;"2(60×6)"→2/60/6;"60×6"→1/60/6。 /// private static bool TryParse规格(string 规格, out int 层数, out decimal 宽, out decimal 厚) { 层数 = 1; 宽 = 0m; 厚 = 0m; if (string.IsNullOrWhiteSpace(规格)) return false; string s = 规格.Trim(); int dash = s.IndexOf('-'); if (dash >= 0 && dash < s.Length - 1) s = s.Substring(dash + 1).Trim(); var m = System.Text.RegularExpressions.Regex.Match(s, @"^(\d+)\s*\(\s*(\d+(?:\.\d+)?)\s*[×xX\*]\s*(\d+(?:\.\d+)?)\s*\)"); if (m.Success) { if (!int.TryParse(m.Groups[1].Value, out 层数)) 层数 = 1; 宽 = ParseDec(m.Groups[2].Value); 厚 = ParseDec(m.Groups[3].Value); return 宽 > 0 && 厚 > 0; } m = System.Text.RegularExpressions.Regex.Match(s, @"^(\d+(?:\.\d+)?)\s*[×xX\*]\s*(\d+(?:\.\d+)?)"); if (m.Success) { 层数 = 1; 宽 = ParseDec(m.Groups[1].Value); 厚 = ParseDec(m.Groups[2].Value); return 宽 > 0 && 厚 > 0; } return false; } /// 汇总当前项目所有元件表里的铜排/铝排行到表格(按 名称+规格 聚合数量列不做,逐行显示)。 private void LoadSummary() { _rowKeys.Clear(); _table = new DataTable(); foreach (string c in SummaryCols) _table.Columns.Add(c, typeof(string)); _table.Columns.Add("__表名", typeof(string)); // 内部:元件真实表名 yj_N _table.Columns.Add("__序号", typeof(string)); // 内部:行序号 _table.Columns.Add("__铝排", typeof(bool)); // 内部:是否铝排 string dbPath = QuoteDbPath; if (!System.IO.File.Exists(dbPath)) { MessageBox.Show(this, "当前项目报价库不存在。", "调母排", MessageBoxButtons.OK, MessageBoxIcon.Warning); BindSummary(); return; } using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(dbPath))) { conn.Open(); // 所有元件表(表索引 类型=元件) var yjTables = new List(); using (var cmd = new System.Data.SQLite.SQLiteCommand( "SELECT DISTINCT 实际表名 FROM 表索引 WHERE 类型='元件';", conn)) using (var rd = cmd.ExecuteReader()) { while (rd.Read()) { string t = rd.IsDBNull(0) ? "" : rd.GetString(0); if (!string.IsNullOrEmpty(t)) yjTables.Add(t); } } _表数 = yjTables.Count; foreach (string tbl in yjTables) { try { using (var cmd = new System.Data.SQLite.SQLiteCommand( "SELECT 序号, 元件名称, 规格, 单位, 表价, 类型 FROM \"" + tbl + "\";", conn)) using (var rd = cmd.ExecuteReader()) { while (rd.Read()) { // ★ 注意:表价列在库里是 REAL 类型(浮点),不能用 GetString, // 否则抛 InvalidCastException 被 catch 吞掉 → 一行都读不出来! // 统一用 GetValue+ToString,TEXT/REAL 通吃。 string 序号 = rd.IsDBNull(0) ? "" : rd.GetValue(0).ToString(); string 名称 = rd.IsDBNull(1) ? "" : rd.GetValue(1).ToString(); string 规格 = rd.IsDBNull(2) ? "" : rd.GetValue(2).ToString(); string 单位 = rd.IsDBNull(3) ? "" : rd.GetValue(3).ToString(); string 表价 = rd.IsDBNull(4) ? "" : rd.GetValue(4).ToString(); string 类型 = rd.IsDBNull(5) ? "" : rd.GetValue(5).ToString(); // ★ 唯一识别条件:类型字段 = 母排(严格按库里数据,不做任何猜测) if ((类型 ?? "").Trim() != "母排") continue; // 材质按规格前缀:LMY=铝排,其余(TMY等)=铜排 bool 铝 = Is铝排(规格); var row = _table.NewRow(); row["元件名称"] = 名称; row["规格"] = 规格; row["单位"] = 单位; row["表价(更改前)"] = 表价; row["表价(更改后)"] = 表价; // 默认等于更改前,等「计算」刷新 row["__表名"] = tbl; row["__序号"] = 序号; row["__铝排"] = 铝; _table.Rows.Add(row); _rowKeys.Add(tbl + "#" + 序号); } } } catch (Exception exTbl) { // 单表读失败不中断其他表,但要留日志(之前静默吞掉表价REAL类型异常吃过亏) try { System.IO.File.AppendAllText( System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"logs\调母排.log"), "[" + DateTime.Now.ToString("HH:mm:ss.fff") + "] 读表失败 " + tbl + ": " + exTbl.Message + "\r\n"); } catch { } } } } BindSummary(); // 诊断日志:确认读到了几个表/几行(排查"打开窗口没数据") try { System.IO.File.AppendAllText( System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"logs\调母排.log"), "[" + DateTime.Now.ToString("HH:mm:ss.fff") + "] 项目=" + ProjectId + " 元件表数=" + _表数 + " 母排行数=" + _table.Rows.Count + "\r\n"); } catch { } } /// 本次汇总扫过的元件表数量(诊断用)。 private int _表数 = 0; /// 把内存表绑定到表格(只显示 5 个业务列)。 private void BindSummary() { var view = new DataTable(); foreach (string c in SummaryCols) view.Columns.Add(c, typeof(string)); foreach (DataRow r in _table.Rows) { var nr = view.NewRow(); foreach (string c in SummaryCols) nr[c] = r[c] ?? ""; view.Rows.Add(nr); } // ★ 设计器手工加的 5 列(Name=Column1..5)没设 DataPropertyName, // 直接绑 DataSource 匹配不上会显示不出数据——按表头顺序接上数据列。 var grid = this.advancedDataGridView1; grid.AutoGenerateColumns = false; if (grid.Columns.Count >= SummaryCols.Length) { for (int i = 0; i < SummaryCols.Length; i++) { grid.Columns[i].DataPropertyName = SummaryCols[i]; } } grid.DataSource = view; } // ============================================================ // ③ 计算:按当前价格+加工费重算"表价(更改后)" // ============================================================ /// /// button1「计算」(与 tabPage12 母排页同规则): /// 价格框已是"金属价+加工费"合价(元/kg) /// 新表价 = round( 合价(元/kg) × 每米重量(kg/m), 0 ) /// 每米重量 = 层数 × 宽 × 厚 × 密度 / 1000 (铜 8.9 / 铝 2.7) /// —— 即母排页 Recalc单价PerMeter 的批量版,加工费不重复加。 /// 复选框可只算一种材质。 /// private void button1_Click(object sender, EventArgs e) { if (_table == null || _table.Rows.Count == 0) { MessageBox.Show(this, "没有可计算的铜排/铝排数据。", "调母排", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } decimal 铜价 = ParseDec(this.textBox1当前铜价.Text); decimal 铝价 = ParseDec(this.textBox2当前铝价.Text); int 算铜 = 0, 算铝 = 0, 跳过 = 0; foreach (DataRow r in _table.Rows) { bool 铝 = (bool)r["__铝排"]; // 复选框:只算勾选的材质(两个都没勾=都算) if (this.checkBox1.Checked && !this.checkBox2.Checked && 铝) { 跳过++; continue; } if (this.checkBox2.Checked && !this.checkBox1.Checked && !铝) { 跳过++; continue; } decimal 价 = 铝 ? 铝价 : 铜价; // 已含加工费的合价 if (价 <= 0m) { 跳过++; continue; } // 价格没填 int 层; decimal 宽, 厚; if (!TryParse规格((r["规格"] ?? "").ToString(), out 层, out 宽, out 厚)) { 跳过++; continue; } decimal 密度 = 铝 ? 2.7m : 8.9m; decimal 每米重量 = 层 * 宽 * 厚 * 密度 / 1000m; decimal 新表价 = Math.Round(价 * 每米重量, 0, MidpointRounding.AwayFromZero); r["表价(更改后)"] = 新表价.ToString("0"); if (铝) 算铝++; else 算铜++; } BindSummary(); MessageBox.Show(this, "计算完成!\n\n铜排 " + 算铜 + " 行,铝排 " + 算铝 + " 行" + (跳过 > 0 ? "\n跳过 " + 跳过 + " 行(价格未填/规格无法解析/材质未勾选)" : ""), "调母排", MessageBoxButtons.OK, MessageBoxIcon.Information); } // ============================================================ // ④ 更新到项目 // ============================================================ /// /// button2「更新到项目」:把"表价(更改后)"写回所有元件表, /// 派生列(采购单价/采购价/报出单价/报出价)用 SQL 表达式按每行自己的数量与系数重算, /// 然后通知主窗口清缓存刷新报价页并全量重算。 /// private void button2_Click(object sender, EventArgs e) { if (_table == null || _table.Rows.Count == 0) { MessageBox.Show(this, "没有可更新的数据。", "调母排", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 统计有变化的行 int 有变化 = 0, 相同 = 0; foreach (DataRow r in _table.Rows) { string 前 = (r["表价(更改前)"] ?? "").ToString().Trim(); string 后 = (r["表价(更改后)"] ?? "").ToString().Trim(); decimal d前, d后; decimal.TryParse(前, out d前); decimal.TryParse(后, out d后); if (d后 != d前) 有变化++; else 相同++; } if (有变化 == 0) { MessageBox.Show(this, "没有价格变化(表价更改后=更改前),无需更新。\n请先点「计算」。", "调母排", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (MessageBox.Show(this, "将把 " + 有变化 + " 条铜排/铝排的新表价更新到整个项目,\n" + "并按各行的数量/系数自动重算派生列与汇总。\n\n确定更新吗?", "更新到项目", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) != DialogResult.Yes) return; string dbPath = QuoteDbPath; int 更新行 = 0; try { using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(dbPath))) { conn.Open(); using (var tx = conn.BeginTransaction()) { try { foreach (DataRow r in _table.Rows) { string 前 = (r["表价(更改前)"] ?? "").ToString().Trim(); string 后 = (r["表价(更改后)"] ?? "").ToString().Trim(); decimal d前, d后; decimal.TryParse(前, out d前); decimal.TryParse(后, out d后); if (d后 == d前) continue; // 无变化不写 string tbl = (string)r["__表名"]; string 序号 = (string)r["__序号"]; // 表价 + 派生列一起更新(与 RecalcYuanjianRow 公式一致): // 采购单价 = 表价 × 采购系数 // 采购价 = 采购单价 × 数量 // 报出单价 = 采购单价 × 报出系数 // 报出价 = 报出单价 × 数量 string sql = "UPDATE \"" + tbl.Replace("\"", "") + "\" SET " + "表价 = @bj, " + "采购单价 = @bj * CAST(采购系数 AS REAL), " + "采购价 = (@bj * CAST(采购系数 AS REAL)) * CAST(数量 AS REAL), " + "报出单价 = (@bj * CAST(采购系数 AS REAL)) * CAST(报出系数 AS REAL), " + "报出价 = ((@bj * CAST(采购系数 AS REAL)) * CAST(报出系数 AS REAL)) * CAST(数量 AS REAL) " + "WHERE 序号 = @xh;"; using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx)) { cmd.Parameters.AddWithValue("@bj", d后); cmd.Parameters.AddWithValue("@xh", 序号); 更新行 += cmd.ExecuteNonQuery(); } } tx.Commit(); } catch { try { tx.Rollback(); } catch { } throw; } } } // 同步"更改前"=新值(可再次计算而不重复更新) foreach (DataRow r in _table.Rows) r["表价(更改前)"] = r["表价(更改后)"]; BindSummary(); // 通知主窗口:清缓存 + 全量重算 + 刷新报价页 if (_main != null) _main.RefreshAfter母排调价(); MessageBox.Show(this, "更新完成!\n\n共更新 " + 更新行 + " 条元件表价,\n项目汇总已自动重算。", "调母排", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show(this, "更新失败:\n" + ex.Message, "调母排", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// button3「退出」:关闭窗口。 private void button3_Click(object sender, EventArgs e) { this.Close(); } } }