using System; using System.Collections.Generic; using System.Windows.Forms; namespace 成套报价软件 { /// /// 新建项目 的交互逻辑(事件处理)。 /// UI 布局定义见 新建项目.ui.cs。 /// 本窗体作为 MainForm 的对话框使用,故不含独立的 Main 入口点。 /// public partial class 新建项目 : Form { /// 项目数据库文件相对路径(与 MainForm 保持一致)。 private readonly string _dbPath; /// 项目数据库表名。 private readonly string _tableName; /// 构造函数:初始化 UI 并绑定事件。 public 新建项目() : this(null, null) { // 无参构造保留,以便独立测试时仍可创建。 } /// /// 带数据库路径的构造函数(主窗口调用此版本)。 /// 传入数据库后,对话框会在打开时自动填入“项目日期”和“项目编号”。 /// /// SQLite 数据库绝对路径;为 null/不存在则不自动生成编号。 /// 项目数据表名。 public 新建项目(string dbPath, string tableName) { InitializeComponent(); _dbPath = dbPath; _tableName = tableName; // 预置价格格式化下拉框选项(小数位 + 取整方式) InitPriceFormatCombos(); // 加载完成后(已显示)自动填充编号和日期。 // 用 Load 事件确保控件都已创建好。 this.Load += 新建项目_Load; } /// /// 预置“单台价格/项目总价/单台取整/项目取整”4 个下拉框的“候选选项列表”。 /// 注意:只填充 Items,不改动 SelectedIndex/Text —— 默认选中项由设计器 XML 里的 /// Text 属性决定,不要在代码里覆盖。 /// /// 控件布局对应(UI 里 label 文字决定语义): /// 单台价格行 → cmb单台价格(单台层小数位) /// 项目总价行 → cmb项目总价(项目层小数位) /// 单台取整行 → comboBox1 (单台层取整方式) /// 项目取整行 → comboBox2 (项目层取整方式) /// private void InitPriceFormatCombos() { // 小数位选项:cmb单台价格(单台层) + cmb项目总价(项目层) this.cmb单台价格.Items.Clear(); this.cmb单台价格.Items.AddRange(价格格式化.小数位选项); this.cmb项目总价.Items.Clear(); this.cmb项目总价.Items.AddRange(价格格式化.小数位选项); // 取整方式选项:comboBox1(单台层) + comboBox2(项目层) this.comboBox1.Items.Clear(); this.comboBox1.Items.AddRange(价格格式化.取整方式选项); this.comboBox2.Items.Clear(); this.comboBox2.Items.AddRange(价格格式化.取整方式选项); } /// 对话框首次加载:自动填项目日期 + 项目编号。 private void 新建项目_Load(object sender, EventArgs e) { FillAutoFields(); } /// /// 自动填充“项目日期”为当天,并按 yyyyMMdd+3位序号 /// 规则生成“项目编号”(序号=数据库当天最大序号+1)。 /// 同时把 txt项目编号 设为只读,用户不能修改。 /// private void FillAutoFields() { // 1) 项目日期 = 当天 yyyy-MM-dd DateTime today = DateTime.Today; this.txt项目日期.Text = today.ToString("yyyy-MM-dd"); // 2) 项目编号 = yyyyMMdd + 3 位序号(001 起,查库取当天最大序号 +1) string datePart = today.ToString("yyyyMMdd"); int seq = GetNextSeqForDate(datePart); // 找不到就返回 1 this.txt项目编号.Text = datePart + seq.ToString("000"); // 3) 项目编号只读,用户不能改 this.txt项目编号.ReadOnly = true; this.txt项目编号.TabStop = false; // 给只读框一个浅灰背景,视觉上提示“不可编辑” this.txt项目编号.BackColor = System.Drawing.SystemColors.Control; // 4) 成套厂信息(名称/地址/联系人/联系电话)从「企业信息」配置自动带入, // 用户可在对话框中临时改写;改写后的值随项目一起保存到项目库。 // 若企业信息未配置,则留空由用户手填。 try { var qiye = 企业信息.LoadSettings(); if (qiye != null) { // 仅在文本框为空时填入,避免覆盖已有内容(理论上 Load 阶段都是空) if (string.IsNullOrEmpty(this.txt成套厂名称.Text)) this.txt成套厂名称.Text = qiye["企业名称"]; if (string.IsNullOrEmpty(this.txt成套厂地址.Text)) this.txt成套厂地址.Text = qiye["地址"]; if (string.IsNullOrEmpty(this.txt成套厂联系人.Text)) this.txt成套厂联系人.Text = qiye["联系人"]; if (string.IsNullOrEmpty(this.txt成套厂联系电话.Text)) this.txt成套厂联系电话.Text = qiye["电话"]; } } catch { // 读不到企业信息不阻断新建项目流程 } } /// /// 在数据库中查找“指定日期前缀(yyyyMMdd)”的最大序号, /// 返回“最大序号 + 1”;若无数据或出错则返回 1。 /// 例:库中已有 20260721001、20260721005,返回 6。 /// private int GetNextSeqForDate(string datePart) { if (string.IsNullOrEmpty(datePart) || string.IsNullOrEmpty(_dbPath) || string.IsNullOrEmpty(_tableName) || !System.IO.File.Exists(_dbPath)) { return 1; } try { string connStr = "Data Source=" + _dbPath + ";Version=3;"; using (var conn = new System.Data.SQLite.SQLiteConnection(connStr)) { conn.Open(); // 取“项目编号”这一列中、前 8 位等于 datePart 的最大值。 // SUBSTR 兼容 SQLite,从 1 开始计数,截取前 8 位。 string sql = "SELECT MAX(CAST(SUBSTR(项目编号, 9) AS INTEGER)) " + "FROM " + _tableName + " " + "WHERE SUBSTR(项目编号, 1, 8) = '" + datePart + "';"; using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn)) { object result = cmd.ExecuteScalar(); if (result == null || result == DBNull.Value) { return 1; } int maxSeq; // ExecuteScalar 返回的可能是 long/int if (int.TryParse(result.ToString(), out maxSeq)) { return maxSeq + 1; } return 1; } } } catch { // 查询失败时不阻断流程,退化为从 001 开始 return 1; } } /// /// 收集对话框中已填写的字段,以“去掉 txt 前缀的字段名”为键返回。 /// 仅包含实际存在文本框且文本非空的字段。 /// 调用方只需按列名(Name/DataPropertyName)匹配,匹配不上的字段自动忽略。 /// public Dictionary GetInputValues() { var values = new Dictionary(); // (字段名, 对应文本框)。字段名与MainForm advancedDataGridView6 列名一致。 AddIfNotEmpty(values, "项目编号", txt项目编号.Text); AddIfNotEmpty(values, "项目日期", txt项目日期.Text); AddIfNotEmpty(values, "项目名称", txt项目名称.Text); AddIfNotEmpty(values, "客户名称", txt客户名称.Text); AddIfNotEmpty(values, "客户地址", txt客户地址.Text); AddIfNotEmpty(values, "联系人", txt联系人.Text); AddIfNotEmpty(values, "联系电话", txt联系电话.Text); AddIfNotEmpty(values, "备注", txt备注.Text); // 成套厂信息(4 个字段):新建项目时从企业信息带入,用户可改写; // 写入项目库,供项目窗口后续调用。 AddIfNotEmpty(values, "成套厂名称", txt成套厂名称.Text); AddIfNotEmpty(values, "成套厂地址", txt成套厂地址.Text); AddIfNotEmpty(values, "成套厂联系人", txt成套厂联系人.Text); AddIfNotEmpty(values, "成套厂联系电话", txt成套厂联系电话.Text); // 价格显示格式化(4 个字段):控制报价页价格单元格的“小数位 + 取整方式”。 // 仅影响显示,不改变原始计算值;随项目一起保存到「项目信息」表。 // 控件对应:cmb单台价格=单台小数位 / cmb项目总价=项目小数位 / // comboBox1=单台取整方式 / comboBox2=项目取整方式 values["单台小数位"] = (cmb单台价格.SelectedItem ?? cmb单台价格.Text ?? "").ToString(); values["项目小数位"] = (cmb项目总价.SelectedItem ?? cmb项目总价.Text ?? "").ToString(); values["单台取整方式"] = (comboBox1.SelectedItem ?? comboBox1.Text ?? "").ToString(); values["项目取整方式"] = (comboBox2.SelectedItem ?? comboBox2.Text ?? "").ToString(); return values; } private static void AddIfNotEmpty(Dictionary dict, string key, string value) { if (!string.IsNullOrWhiteSpace(value)) { dict[key] = value.Trim(); } } // 提示:在设计器的属性窗口(事件页)双击事件名, // 会自动在此生成事件处理方法骨架。 } }