基线:全量源码首提(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
+447
View File
@@ -0,0 +1,447 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
namespace 成套报价软件
{
/// <summary>
/// 元件排序 的交互逻辑(事件处理)。
/// UI 布局定义见 元件排序.ui.cs。
///
/// 用法:MainForm button33「元件排序」传入(项目编号,当前图号,当前箱号)弹出。
/// 左侧点箱号 → 右侧加载该箱号元件;选中行用 置顶/上移/下移/置底 调整;
/// 点「确定」把右侧列表顺序重排序号写回数据库;「取消」不保存。
/// </summary>
public partial class 元件排序
{
// ===== 主窗口传入的上下文 =====
/// <summary>当前项目编号。</summary>
private string _projectId = "";
/// <summary>打开时的当前图号(用于默认选中箱号)。</summary>
private string _initTuhao = "";
/// <summary>打开时的当前箱号(用于默认选中)。</summary>
private string _initXianghao = "";
// ===== 左侧箱号列表数据 =====
/// <summary>箱号列表数据:Key="图号内部ID\n箱号内部ID", Value="图号 / 箱号"(显示)。</summary>
private List<KeyValuePair<string, string>> _boxList = new List<KeyValuePair<string, string>>();
// ===== 右侧元件列表数据 =====
/// <summary>当前箱号的元件行(独立 DataTable,不动主窗口内存缓存)。</summary>
private List<DataRow> _rows = new List<DataRow>();
/// <summary>按元件名称分组后的组列表:每组=同名称的连续行(保持库内相对顺序)。
/// 右侧列表只显示每组一个名称(带数量),移动按钮按组整体移动。</summary>
private List<List<DataRow>> _groups = new List<List<DataRow>>();
/// <summary>_rows 所属的 DataTable(TableName=真实元件表名 yj_N,保存时用)。</summary>
private DataTable _table = null;
/// <summary>构造函数:初始化 UI 并绑定事件。</summary>
public 元件排序()
{
InitializeComponent();
BindEvents();
}
/// <summary>主窗口调用:传入项目上下文并默认选中当前箱号。</summary>
public 元件排序(string projectId, string currentTuhao, string currentXianghao)
: this()
{
_projectId = projectId ?? "";
_initTuhao = currentTuhao ?? "";
_initXianghao = currentXianghao ?? "";
}
/// <summary>绑定事件 + 初始状态。</summary>
private void BindEvents()
{
// 左侧箱号 → 加载元件
this.listBoxXianghao.SelectedIndexChanged += listBoxXianghao_SelectedIndexChanged;
// 移动按钮
this.btnMoveTop.Click += (s, e) => MoveSelected(-int.MaxValue);
this.btnMoveUp.Click += (s, e) => MoveSelected(-1);
this.btnMoveDown.Click += (s, e) => MoveSelected(1);
this.btnMoveBottom.Click += (s, e) => MoveSelected(int.MaxValue);
// 确定按钮:DialogResult=OK 会直接关窗,在 Closing 前拦截保存
this.btnOK.Click += btnOK_Click;
}
/// <summary>窗体加载:装载箱号列表并默认选中当前箱号。</summary>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
try
{
LoadBoxList();
// 默认选中当前箱号(显示名含 "图号 / 箱号",按箱号内部ID 匹配)
int sel = -1;
if (!string.IsNullOrEmpty(_initXianghao))
{
for (int i = 0; i < _boxList.Count; i++)
{
if (_boxList[i].Key.EndsWith("\n" + _initXianghao))
{
sel = i;
break;
}
}
}
if (sel < 0 && _boxList.Count > 0) sel = 0;
if (sel >= 0) this.listBoxXianghao.SelectedIndex = sel;
}
catch (Exception ex)
{
MessageBox.Show(this, "加载箱号失败:\n" + ex.Message, "元件排序",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// ============================================================
// 数据装载
// ============================================================
/// <summary>报价库路径(与MainForm GetQuoteDbPath 同规则)。</summary>
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;"; }
/// <summary>读取当前项目所有图号下的箱号,填充左侧列表。</summary>
private void LoadBoxList()
{
_boxList.Clear();
this.listBoxXianghao.Items.Clear();
string dbPath = QuoteDbPath;
if (!System.IO.File.Exists(dbPath)) return;
using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(dbPath)))
{
conn.Open();
// 图号表:内部ID + 图号
var tuhaos = new List<KeyValuePair<string, string>>();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 内部ID, 图号 FROM advancedDataGridView9Table ORDER BY rowid;", conn))
using (var rd = cmd.ExecuteReader())
{
while (rd.Read())
{
string id = rd.IsDBNull(0) ? "" : rd.GetString(0);
string nm = rd.IsDBNull(1) ? "" : rd.GetString(1);
if (!string.IsNullOrEmpty(id))
tuhaos.Add(new KeyValuePair<string, string>(id, nm));
}
}
// 每个图号 → 查表索引拿箱号表 → 读箱号
foreach (var th in tuhaos)
{
string xhTbl = GetXianghaoTable(conn, th.Key);
if (string.IsNullOrEmpty(xhTbl)) continue;
try
{
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 内部ID, 箱号 FROM \"" + xhTbl + "\" ORDER BY rowid;", conn))
using (var rd = cmd.ExecuteReader())
{
while (rd.Read())
{
string xid = rd.IsDBNull(0) ? "" : rd.GetString(0);
string xnm = rd.IsDBNull(1) ? "" : rd.GetString(1);
if (string.IsNullOrEmpty(xid)) continue;
_boxList.Add(new KeyValuePair<string, string>(
th.Key + "\n" + xid, th.Value + " / " + xnm));
}
}
}
catch { }
}
}
foreach (var kv in _boxList) this.listBoxXianghao.Items.Add(kv.Value);
}
/// <summary>查图号的箱号真实表名(xh_N),无则返回空。</summary>
private static string GetXianghaoTable(System.Data.SQLite.SQLiteConnection conn, string tuhaoId)
{
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 实际表名 FROM 表索引 WHERE 类型='箱号' AND 图号=@th LIMIT 1;", conn))
{
cmd.Parameters.AddWithValue("@th", tuhaoId);
object o = cmd.ExecuteScalar();
return (o == null || o == DBNull.Value) ? "" : o.ToString();
}
}
/// <summary>查箱号的元件真实表名(yj_N),无则返回空。</summary>
private static string GetYuanjianTable(System.Data.SQLite.SQLiteConnection conn,
string tuhaoId, string xianghaoId)
{
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 实际表名 FROM 表索引 WHERE 类型='元件' AND 图号=@th AND 箱号=@xh LIMIT 1;", conn))
{
cmd.Parameters.AddWithValue("@th", tuhaoId);
cmd.Parameters.AddWithValue("@xh", xianghaoId);
object o = cmd.ExecuteScalar();
return (o == null || o == DBNull.Value) ? "" : o.ToString();
}
}
// ============================================================
// 左侧箱号 → 右侧元件
// ============================================================
/// <summary>点左侧箱号:加载该箱号的元件到右侧列表。</summary>
private void listBoxXianghao_SelectedIndexChanged(object sender, EventArgs e)
{
int idx = this.listBoxXianghao.SelectedIndex;
if (idx < 0 || idx >= _boxList.Count) return;
// 切箱号前,如果当前箱号有未保存的顺序改动 → 先保存
if (_table != null && _dirty) SaveCurrentOrder(false);
string[] parts = _boxList[idx].Key.Split('\n');
if (parts.Length != 2) return;
string yjTbl;
using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(QuoteDbPath)))
{
conn.Open();
yjTbl = GetYuanjianTable(conn, parts[0], parts[1]);
}
_rows.Clear();
_groups.Clear();
_table = null;
this.listBoxYuanjian.Items.Clear();
if (string.IsNullOrEmpty(yjTbl))
{
// 该箱号还没有元件表
this.listBoxYuanjian.Items.Add("(此箱号暂无元件)");
return;
}
// 读成独立 DataTable(按 rowid = 原始顺序)
var t = new DataTable();
t.TableName = yjTbl;
using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(QuoteDbPath)))
{
conn.Open();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT * FROM \"" + yjTbl + "\" ORDER BY rowid;", conn))
using (var adp = new System.Data.SQLite.SQLiteDataAdapter(cmd))
{
adp.Fill(t);
}
}
_table = t;
foreach (DataRow r in t.Rows) _rows.Add(r);
BuildGroups();
RefreshYuanjianList(-1);
}
/// <summary>右侧列表是否有未保存的顺序改动。</summary>
private bool _dirty = false;
/// <summary>
/// 把 _rows 按「元件名称」分组到 _groups:
/// 同名称的行归为一组(组内保持库内相对顺序),右侧列表每组只显示一行。
/// </summary>
private void BuildGroups()
{
_groups.Clear();
if (_rows.Count == 0) return;
// 名称 → 组
var map = new Dictionary<string, List<DataRow>>();
var order = new List<string>(); // 首次出现顺序
foreach (DataRow r in _rows)
{
string name = _table.Columns.Contains("元件名称") ? (r["元件名称"] ?? "").ToString() : "";
if (string.IsNullOrWhiteSpace(name)) name = "(未命名)";
List<DataRow> g;
if (!map.TryGetValue(name, out g))
{
g = new List<DataRow>();
map[name] = g;
order.Add(name);
}
g.Add(r);
}
foreach (string nm in order) _groups.Add(map[nm]);
}
/// <summary>刷新右侧元件列表:每组一行,只显示元件名称(同名多行带 ×N)。</summary>
private void RefreshYuanjianList(int keepIndex)
{
this.listBoxYuanjian.Items.Clear();
for (int i = 0; i < _groups.Count; i++)
{
string name = _table.Columns.Contains("元件名称")
? (_groups[i][0]["元件名称"] ?? "").ToString() : "";
if (string.IsNullOrWhiteSpace(name)) name = "(未命名)";
string text = (i + 1).ToString() + ". " + name;
if (_groups[i].Count > 1) text += " ×" + _groups[i].Count;
this.listBoxYuanjian.Items.Add(text);
}
if (this.listBoxYuanjian.Items.Count > 0)
{
int sel = keepIndex >= 0 && keepIndex < this.listBoxYuanjian.Items.Count ? keepIndex : 0;
this.listBoxYuanjian.SelectedIndex = sel;
}
}
// ============================================================
// 上移/下移/置顶/置底(按组整体移动)
// ============================================================
/// <summary>移动右侧选中的组:delta=-1上移/+1下移/±int.MaxValue置顶置底。</summary>
private void MoveSelected(int delta)
{
if (_groups.Count < 2) return;
int idx = this.listBoxYuanjian.SelectedIndex;
if (idx < 0) return;
int target;
if (delta == -int.MaxValue) target = 0;
else if (delta == int.MaxValue) target = _groups.Count - 1;
else target = idx + delta;
if (target < 0 || target >= _groups.Count || target == idx) return;
List<DataRow> moved = _groups[idx];
_groups.RemoveAt(idx);
_groups.Insert(target, moved);
_dirty = true;
RefreshYuanjianList(target);
}
// ============================================================
// 保存
// ============================================================
/// <summary>确定:把当前箱号顺序写库,然后关窗。</summary>
private void btnOK_Click(object sender, EventArgs e)
{
try
{
if (_table != null && _dirty)
{
SaveCurrentOrder(true);
}
// DialogResult 已由按钮属性设为 OK,直接关窗
}
catch (Exception ex)
{
// 保存失败则不关窗,让用户决定
MessageBox.Show(this, "保存排序失败:\n" + ex.Message, "元件排序",
MessageBoxButtons.OK, MessageBoxIcon.Error);
this.DialogResult = DialogResult.None;
}
}
/// <summary>
/// 按 _groups 顺序展开成新 _rows,重排序号并整表写回库。
/// 组内保持库内原相对顺序;主窗口关闭窗体后会清缓存重载,报价页自动看到新顺序。
/// </summary>
private void SaveCurrentOrder(bool showMessage)
{
if (_table == null || _groups.Count == 0) return;
// 按组顺序展开成新的行顺序
_rows.Clear();
foreach (List<DataRow> g in _groups) _rows.AddRange(g);
// 重排序号 1..N
if (_table.Columns.Contains("序号"))
{
for (int i = 0; i < _rows.Count; i++)
_rows[i]["序号"] = (i + 1).ToString();
}
// 按新顺序重建表
var ordered = _table.Clone();
ordered.TableName = _table.TableName;
foreach (DataRow r in _rows) ordered.ImportRow(r);
string dbPath = QuoteDbPath;
using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(dbPath)))
{
conn.Open();
using (var tx = conn.BeginTransaction())
{
try
{
// 删旧 + 按新顺序插回
using (var del = new System.Data.SQLite.SQLiteCommand(
"DELETE FROM \"" + _table.TableName + "\";", conn, tx))
{
del.ExecuteNonQuery();
}
// 用参数化 INSERT(列以表实际列为准)
var cols = new List<string>();
foreach (DataColumn c in ordered.Columns) cols.Add(c.ColumnName);
var colParts = new List<string>();
var valParts = new List<string>();
for (int ci = 0; ci < cols.Count; ci++)
{
colParts.Add("\"" + cols[ci] + "\"");
valParts.Add("@p" + ci);
}
string sql = "INSERT INTO \"" + _table.TableName + "\" (" +
string.Join(",", colParts.ToArray()) +
") VALUES (" + string.Join(",", valParts.ToArray()) + ");";
using (var ins = new System.Data.SQLite.SQLiteCommand(sql, conn, tx))
{
for (int ci = 0; ci < cols.Count; ci++)
ins.Parameters.Add("@p" + ci, System.Data.DbType.String);
foreach (DataRow r in ordered.Rows)
{
for (int i = 0; i < ordered.Columns.Count; i++)
ins.Parameters[i].Value = (r[i] == null || r[i] == DBNull.Value)
? (object)"" : r[i].ToString();
ins.ExecuteNonQuery();
}
}
tx.Commit();
}
catch
{
try { tx.Rollback(); } catch { }
throw;
}
}
}
_dirty = false;
if (showMessage)
{
MessageBox.Show(this, "排序已保存!\n\n共 " + ordered.Rows.Count + " 行元件已按新顺序写入。",
"元件排序", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}