基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)

This commit is contained in:
dqb-dev
2026-10-01 22:15:22 +08:00
commit 1e65792b09
499 changed files with 204579 additions and 0 deletions
+247
View File
@@ -0,0 +1,247 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;
namespace 成套报价软件
{
/// <summary>
/// 费用模板 的交互逻辑(2026-10-01 G1-1):
/// 维护"费用模板"列表(名称/计算方式/数值/计入),点「应用到当前项目」把启用模板
/// 换算成"费用行"写入当前项目每个箱号的元件表(类型='费用',参与箱号/图号/项目聚合,
/// 与普通元件走同一套报价公式,重算稳定)。
/// 计算方式:固定金额(每柜)=数值;元件采购价×%=该箱号非费用元件采购价合计×数值%;
/// 元件报出价×%=非费用元件报出价合计×数值%。
/// 计入:采购+报出(采购系数=1)/仅报出(采购系数=0,成本不含此费用)。
/// 模板存储:exe\sjk\费用模板.xml(全局,与项目无关)。
/// 布局在 费用模板.Designer.cs。
/// </summary>
public partial class 费用模板 : Form
{
private readonly MainForm _主窗;
/// <summary>一条费用模板。
/// ★ 计入维度已删(2026-10-01 修复):费用固定"采购+报出"双向计入——公式链
/// 报出单价=采购单价×报出系数 决定了"仅报出"(采购系数=0)会把报出价也清零,
/// 数学上不成立;辅料/人工/成套费本就应进成本,"只抬报价"请用「调系数」。</summary>
public class 模板行
{
public bool 启用 = true;
public string 名称 = "";
public string 方式 = "固定金额(每柜)"; // 固定金额(每柜)/元件采购价×%/元件报出价×%
public decimal 数值 = 0m;
}
public 费用模板(MainForm 主窗)
{
InitializeComponent();
_主窗 = 主窗;
Load模板();
刷列表();
// ★ 事件挂接在 Designer.cs(SD 设计器可见可编辑,铁律:Designer.cs 纯布局+事件,
// 逻辑与处理器方法在本文件)
cbo方式.SelectedIndex = 0;
}
// ===== 模板存取(sjk\费用模板.xml) =====
private static string 模板路径()
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sjk", "费用模板.xml");
}
private List<模板行> _模板s = new List<模板行>();
private void Load模板()
{
try
{
_模板s = new List<模板行>();
string f = 模板路径();
if (!File.Exists(f)) return;
string xml = File.ReadAllText(f, Encoding.UTF8);
int p = 0;
while (true)
{
int s = xml.IndexOf("<模板>", p, StringComparison.Ordinal);
if (s < 0) break;
int e = xml.IndexOf("</模板>", s, StringComparison.Ordinal);
if (e < 0) break;
string seg = xml.Substring(s + 4, e - s - 4);
_模板s.Add(new 模板行
{
启用 = 取段(seg, "启用") == "1",
名称 = 取段(seg, "名称"),
方式 = 取段(seg, "方式"),
数值 = TryParseDec(取段(seg, "数值"))
// 旧 XML 的 <计入> 段忽略(维度已删)
});
p = e;
}
}
catch { }
}
private static string 取段(string xml, string 标签)
{
string 开 = "<" + 标签 + ">", 关 = "</" + 标签 + ">";
int i = xml.IndexOf(开, StringComparison.Ordinal);
if (i < 0) return "";
int j = xml.IndexOf(关, i + 开.Length, StringComparison.Ordinal);
if (j < 0) return "";
return xml.Substring(i + 开.Length, j - i - 开.Length).Trim();
}
private void Save模板()
{
try
{
var sb = new StringBuilder();
sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
sb.AppendLine("<!-- 费用模板(全局):应用到当前项目时按方式换算成费用行写入各箱号 -->");
sb.AppendLine("<费用模板列表>");
foreach (var t in _模板s)
{
sb.AppendLine(" <模板>");
sb.AppendLine(" <启用>" + (t.启用 ? "1" : "0") + "</启用>");
sb.AppendLine(" <名称>" + (t.名称 ?? "") + "</名称>");
sb.AppendLine(" <方式>" + (t.方式 ?? "") + "</方式>");
sb.AppendLine(" <数值>" + t.数值.ToString("0.####", CultureInfo.InvariantCulture) + "</数值>");
sb.AppendLine(" </模板>");
}
sb.AppendLine("</费用模板列表>");
string f = 模板路径();
string dir = Path.GetDirectoryName(f);
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
File.WriteAllText(f, sb.ToString(), new UTF8Encoding(true));
}
catch (Exception ex) { MessageBox.Show(this, "保存模板失败: " + ex.Message, "费用模板"); }
}
private static decimal TryParseDec(string s)
{
decimal d;
if (decimal.TryParse((s ?? "").Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
decimal.TryParse((s ?? "").Trim(), out d);
return d;
}
// ===== 界面 =====
/// <summary>合法的方式取值(与 Designer 列 Items、编辑区 ComboBox Items 保持一致)。</summary>
private static readonly string[] 合法方式 = { "固定金额(每柜)", "元件采购价×%", "元件报出价×%" };
private static string 归一化方式(string s)
{
foreach (string v in 合法方式) if (v == s) return v;
return 合法方式[0];
}
private void 刷列表()
{
dgv模板.Rows.Clear();
foreach (var t in _模板s)
dgv模板.Rows.Add(t.启用, t.名称, 归一化方式(t.方式), t.数值.ToString("0.####"));
}
/// <summary>★ 修复(2026-10-01):ComboBox 列值不在 Items 时吞掉 DataError 默认弹框
/// (写值已归一化,此为兜底;异常记日志便于排查)。</summary>
private void dgv模板_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
e.ThrowException = false;
try
{
System.Diagnostics.Debug.WriteLine("[费用模板] DataError 行" + e.RowIndex + " 列" + e.ColumnIndex + ": " + e.Exception.Message);
}
catch { }
}
private void dgv模板_SelectionChanged(object sender, EventArgs e)
{
if (dgv模板.CurrentRow == null) return;
int i = dgv模板.CurrentRow.Index;
if (i < 0 || i >= _模板s.Count) return;
var t = _模板s[i];
txt名称.Text = t.名称;
cbo方式.SelectedItem = 归一化方式(t.方式);
txt数值.Text = t.数值.ToString("0.####");
}
private 模板行 从编辑区读()
{
var t = new 模板行();
t.名称 = (txt名称.Text ?? "").Trim();
t.方式 = cbo方式.SelectedItem == null ? "固定金额(每柜)" : cbo方式.SelectedItem.ToString();
t.数值 = TryParseDec(txt数值.Text);
return t;
}
private void btn新增_Click(object sender, EventArgs e)
{
var t = 从编辑区读();
if (t.名称.Length == 0) { MessageBox.Show(this, "请填写费用名称。", "费用模板"); return; }
if (t.数值 <= 0m && t.方式 != "固定金额(每柜)") { MessageBox.Show(this, "百分比方式的数值必须大于 0。", "费用模板"); return; }
_模板s.Add(t);
Save模板(); 刷列表();
dgv模板.CurrentCell = dgv模板.Rows[dgv模板.Rows.Count - 1].Cells[1];
}
private void btn修改_Click(object sender, EventArgs e)
{
if (dgv模板.CurrentRow == null) { MessageBox.Show(this, "请先在列表选中要修改的模板。", "费用模板"); return; }
int i = dgv模板.CurrentRow.Index;
if (i < 0 || i >= _模板s.Count) return;
var t = 从编辑区读();
if (t.名称.Length == 0) { MessageBox.Show(this, "请填写费用名称。", "费用模板"); return; }
_模板s[i] = t;
Save模板(); 刷列表();
}
private void btn删除_Click(object sender, EventArgs e)
{
if (dgv模板.CurrentRow == null) { MessageBox.Show(this, "请先在列表选中要删除的模板。", "费用模板"); return; }
int i = dgv模板.CurrentRow.Index;
if (i < 0 || i >= _模板s.Count) return;
_模板s.RemoveAt(i);
Save模板(); 刷列表();
}
private void btn应用_Click(object sender, EventArgs e)
{
var 启用的 = _模板s.FindAll(t => t.启用);
if (启用的.Count == 0) { MessageBox.Show(this, "没有勾选「启用」的模板,先勾选或新增。", "费用模板"); return; }
if (MessageBox.Show(this,
"将把 " + 启用的.Count + " 条启用模板换算成费用行,写入当前项目【每个箱号】的元件表:\n" +
"· 之前应用过的同名费用行会被替换(可反复应用)\n" +
"· 各箱号成本价/报出价、图号总价、项目汇总会自动重算\n\n确认应用?",
"应用到当前项目", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
try
{
string 结果 = _主窗.应用费用模板到当前项目(启用的);
MessageBox.Show(this, 结果, "应用完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, "应用失败:\n" + ex.Message, "费用模板", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btn清空费用_Click(object sender, EventArgs e)
{
if (MessageBox.Show(this,
"删除当前项目所有箱号元件表里「类型=费用」的行?\n(普通元件行不受影响,价格自动重算)",
"清空项目费用行", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
try
{
string 结果 = _主窗.清空项目费用行();
MessageBox.Show(this, 结果, "清空完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, "清空失败:\n" + ex.Message, "费用模板", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}