基线:全量源码首提(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
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# 诊断:无明细箱号导出问题(临时)
import sqlite3
db = r'C:\成套报价软件\sjk\bjb\项目20260920001.db'
c = sqlite3.connect(db)
print('=== xh_1 箱号行(箱号/箱柜类型/数量/单台成本/成本价/单台报出/报出价):')
try:
cols = [r[1] for r in c.execute('PRAGMA table_info(xh_1)')]
print('列:', cols)
for r in c.execute('SELECT 箱号,箱柜类型,数量,单台成本,成本价,单台报出,报出价 FROM xh_1'):
print(' ', r)
except Exception as e:
print('读失败:', e)
print('=== 表索引(元件):')
for r in c.execute("SELECT 图号,箱号,实际表名 FROM 表索引 WHERE 类型='元件'"):
print(' ', r)
print('=== 图号表箱号内部ID:')
for r in c.execute('SELECT 内部ID,箱号 FROM xh_1'):
print(' ', r)
# 无明细箱号的元件表内容
for (xh_id, nm) in c.execute('SELECT 内部ID,箱号 FROM xh_1').fetchall():
tp = c.execute('SELECT 箱柜类型 FROM xh_1 WHERE 内部ID=?', (xh_id,)).fetchone()
if tp and tp[0] in ('无明细', '明细'):
t = c.execute("SELECT 实际表名 FROM 表索引 WHERE 类型='元件' AND 箱号=?", (xh_id,)).fetchone()
print('=== 无明细箱号[%s] 内部ID=%s 元件表=%s' % (nm, xh_id, t))
if t:
for r in c.execute('SELECT 序号,元件名称,表价,报出价 FROM "%s"' % t[0]):
print(' ', r)
+6
View File
@@ -0,0 +1,6 @@
$names = 'System.Memory','System.Numerics.Vectors','System.Runtime.CompilerServices.Unsafe','System.Resources.Extensions','Smart.CustomComponents','System.Data.SQLite','WeifenLuo.WinFormsUI.Docking','WeifenLuo.WinFormsUI.Docking.ThemeVS2015'
foreach ($n in $names) {
$p = Join-Path 'C:\Program Files (x86)\SharpDevelop\5.2\bin' ($n + '.dll')
$an = [System.Reflection.AssemblyName]::GetAssemblyName($p)
Write-Output ($an.FullName)
}
+34
View File
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# 对比源 ku 与 release ku 的文件清单,确认差异仅为 __pycache__/*.pyc 排除
import os
SRC = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\ku"
DST = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\release\成套报价软件\ku"
def scan(root):
result = set()
for dirpath, dirnames, filenames in os.walk(root):
rel = os.path.relpath(dirpath, root)
for f in filenames:
p = os.path.normpath(os.path.join(rel, f))
result.add(p)
return result
src = scan(SRC)
dst = scan(DST)
only_src = sorted(src - dst)
only_dst = sorted(dst - src)
pycache_related = [p for p in only_src if "__pycache__" in p or p.endswith(".pyc")]
other_src = [p for p in only_src if p not in pycache_related]
print("源文件数:", len(src), " release文件数:", len(dst))
print("仅源有(共%d): __pycache__/.pyc 类 %d 个, 其他 %d 个" % (len(only_src), len(pycache_related), len(other_src)))
for p in other_src[:20]:
print(" [缺失!]", p)
print("仅release有(共%d):" % len(only_dst))
for p in only_dst[:20]:
print(" [多余!]", p)
if not other_src and not only_dst:
print("\n结论: 差异全部是 __pycache__/*.pyc 排除(make_package.py 刻意设计), .venv 完整")
+213
View File
@@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
"""
精简 Designer.cs:只保留设计器需要的最小属性集,
去掉旧工具写入的全量属性垃圾(几十行默认值/无效值),
解决 SharpDevelop 设计时 ArgumentOutOfRangeException。
"""
import io
import re
import os
import glob
BASE = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
# 允许保留的属性行前缀模式
KEEP_PROPS = [
# ★ 控件创建语句(new) — 绝对不能删
r"this\.\w+\s*=\s*new",
r"\w+\s*=\s*new",
r"new\s+System\.Windows\.Forms\.",
r"new\s+Smart\.CustomComponents\.",
r"new\s+Zuby\.ADGV\.",
r"new\s+CJZZ\.Controls\.",
r"new\s+System\.Drawing\.",
r"new\s+System\.ComponentModel\.",
# 列字段创建
r"dataGridViewTextBoxColumn\d+\s*=",
r"dataGridViewCheckBoxColumn\d+\s*=",
r"dataGridViewComboBoxColumn\d+\s*=",
r"System\.Windows\.Forms\.DataGridView\w+Column",
r"this\.\w+\.Name\s*=",
r"this\.\w+\.Size\s*=",
r"this\.\w+\.Location\s*=",
r"this\.\w+\.Text\s*=",
r"this\.\w+\.TabIndex\s*=",
r"this\.\w+\.Dock\s*=",
r"this\.\w+\.Anchor\s*=",
r"this\.\w+\.Font\s*=",
r"this\.\w+\.BackColor\s*=",
r"this\.\w+\.ForeColor\s*=",
r"this\.\w+\.Enabled\s*=",
r"this\.\w+\.Visible\s*=",
r"this\.\w+\.ReadOnly\s*=",
r"this\.\w+\.Multiline\s*=",
r"this\.\w+\.WordWrap\s*=",
r"this\.\w+\.ScrollBars\s*=",
r"this\.\w+\.SelectedIndex\s*=\s*[0-9]", # 只保留>=0的
r"this\.\w+\.Items\.Add",
r"this\.\w+\.Items\.AddRange",
r"this\.\w+\.Items\.Clear",
r"this\.\w+\.Columns\.Add",
r"this\.\w+\.Columns\.AddRange",
r"this\.\w+\.Rows\.Add",
r"this\.\w+\.Controls\.Add",
r"this\.\w+\.Controls\.AddRange",
r"this\.\w+\.Controls\.Clear",
r"this\.\w+\.HeaderText\s*=",
r"this\.\w+\.Width\s*=",
r"this\.\w+\.DataPropertyName\s*=",
r"this\.\w+\.SortMode\s*=",
r"this\.\w+\.ReadOnly\s*=",
r"this\.\w+\.Frozen\s*=",
r"this\.\w+\.AutoSizeMode\s*=\s*System\.Windows\.Forms\.DataGridViewAutoSizeMode",
r"this\.\w+\.Visible\s*=",
r"this\.\w+\.DefaultCellStyle\.",
r"this\.\w+\.UseVisualStyleBackColor\s*=",
r"this\.\w+\.Checked\s*=",
r"this\.\w+\.CheckState\s*=",
r"this\.\w+\.CheckOnClick\s*=",
r"this\.\w+\.Style\s*=\s*Smart\.CustomComponents", # Smart 控件样式枚举
r"this\.\w+\.Radius\s*=",
r"this\.\w+\.PlaceholderText\s*=",
r"this\.\w+\.MultiSelect\s*=",
r"this\.\w+\.Searchable\s*=",
r"this\.\w+\.AllowCustomInput\s*=",
r"this\.\w+\.Flat\s*=",
r"this\.\w+\.AllowUserToAddRows\s*=",
r"this\.\w+\.AllowUserToDeleteRows\s*=",
r"this\.\w+\.AllowUserToOrderColumns\s*=",
r"this\.\w+\.AllowUserToResizeColumns\s*=",
r"this\.\w+\.AllowUserToResizeRows\s*=",
r"this\.\w+\.AutoSize\b",
r"this\.\w+\.AutoScroll\s*=",
r"this\.\w+\.RowHeadersVisible\s*=",
r"this\.\w+\.ColumnHeadersVisible\s*=",
r"this\.\w+\.SelectionMode\s*=",
r"this\.\w+\.EditMode\s*=",
r"this\.\w+\.BackgroundColor\s*=",
r"this\.\w+\.EnableHeadersVisualStyles\s*=",
r"this\.\w+\.EnableExcelStyleSelection\s*=",
r"this\.\w+\.RowHeadersWidth\s*=",
r"this\.\w+\.ColumnHeadersHeight\s*=",
r"this\.\w+\.RowHeadersWidthSizeMode\s*=",
r"this\.\w+\.ColumnHeadersHeightSizeMode\s*=",
r"this\.\w+\.BorderStyle\s*=",
r"this\.\w+\.CellBorderStyle\s*=",
r"this\.\w+\.GridColor\s*=",
r"this\.\w+\.ClipboardCopyMode\s*=",
r"this\.\w+\.MultiSelect\s*=",
r"this\.\w+\.StandardTab\s*=",
r"this\.\w+\.VirtualMode\s*=",
r"this\.\w+\.ImageKey\s*=",
r"this\.\w+\.ImageScaling\s*=",
r"this\.\w+\.DisplayStyle\s*=",
r"this\.\w+\.TextAlign\s*=\s*System\.Drawing", # 只留 ContentAlignment
r"this\.\w+\.TextDirection\s*=",
r"this\.\w+\.ToolTipText\s*=",
r"this\.\w+\.AutoSize\b.*=\s*true",
# 窗体自身属性
r"this\.AutoScaleDimensions\s*=",
r"this\.AutoScaleMode\s*=",
r"this\.ClientSize\s*=",
r"this\.Name\s*=",
r"this\.Text\s*=",
r"this\.FormBorderStyle\s*=",
r"this\.StartPosition\s*=",
r"this\.WindowState\s*=",
r"this\.MaximizeBox\s*=",
r"this\.MinimizeBox\s*=",
r"this\.ControlBox\s*=",
r"this\.ShowInTaskbar\s*=",
r"this\.TopMost\s*=",
r"this\.Opacity\s*=",
r"this\.ShowIcon\s*=",
r"this\.Icon\s*=",
r"this\.KeyPreview\s*=",
r"this\.AutoValidate\s*=",
r"this\.Padding\s*=\s*new\s+System\.Windows\.Forms\.Padding\([1-9]", # 只留非零Padding
r"this\.MainMenuStrip\s*=",
# 控件本身(非 this)的属性
r"\w+\.Name\s*=",
r"\w+\.HeaderText\s*=",
r"\w+\.Width\s*=",
r"\w+\.DataPropertyName\s*=",
r"\w+\.SortMode\s*=",
r"\w+\.ReadOnly\s*=",
r"\w+\.Frozen\s*=",
r"\w+\.Visible\s*=",
r"\w+\.AutoSizeMode\s*=",
r"\w+\.DefaultCellStyle\.",
]
# 编译为正则列表
KEEP_RE = [re.compile(p) for p in KEEP_PROPS]
# 绝对排除的(即使匹配 KEEP 也要删掉——已知导致设计器崩溃的)
HARD_KILL = [
r"this\.\w+\.Padding\s*=\s*new\s+System\.Windows\.Forms\.Padding\(0,\s*0,\s*0,\s*0\)",
r"this\.\w+\.SelectedIndex\s*=\s*-1",
r"this\.\w+\.DropDownWidth\s*=\s*0",
r"this\.\w+\.DropDownHeight\s*=\s*0",
r"this\.\w+\.MaximumSize\s*=\s*new\s+System\.Drawing\.Size\(0,\s*0\)",
r"this\.\w+\.MinimumSize\s*=\s*new\s+System\.Drawing\.Size\(0,\s*0\)",
r"this\.\w+\.Margin\s*=", # Margin 对设计器布局影响大,删掉
]
HARD_KILL_RE = [re.compile(p) for p in HARD_KILL]
def should_keep(line):
st = line.strip()
if not st or st.startswith("//") or st.startswith("#"):
return True # 注释空行保留
# 不是赋值语句的直接保留(如 new, SuspendLayout, Controls.Add 等)
if "=" not in st and ".Add" not in st and ".Clear" not in st:
return True
# 硬排除
for kr in HARD_KILL_RE:
if kr.search(st):
return False
# 白名单
for kr in KEEP_RE:
if kr.search(st):
return True
# 其他赋值语句 → 删掉
if re.match(r"this\.\w+\.\w+\s*=", st) or re.match(r"\w+\.\w+\s*=", st):
return False
return True # 非赋值行保留
def clean_file(path):
s = io.open(path, encoding="utf-8-sig").read()
lines = s.split("\n")
out = []
removed = 0
for line in lines:
if should_keep(line):
out.append(line)
else:
removed += 1
# 去掉连续空行
result = []
prev_blank = False
for l in out:
blank = l.strip() == ""
if blank and prev_blank:
continue
result.append(l)
prev_blank = blank
s2 = "\n".join(result)
if s2 != s:
io.open(path, "w", encoding="utf-8-sig", newline="\r\n").write(s2)
return removed
if __name__ == "__main__":
total = 0
for pattern in ["xiangmu/*.Designer.cs", "shezhi/*.Designer.cs",
"ku/*.Designer.cs", "GCS母排计算/*.Designer.cs"]:
for f in glob.glob(os.path.join(BASE, pattern)):
n = clean_file(f)
total += n
if n > 0:
print("%s: 删除 %d 行冗余属性" % (os.path.basename(f), n))
print("\n共删除 %d 行" % total)
+54
View File
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
"""同一张渲染 PNG 分别喂给 两个 Python 后端 的 ocr_worker,对比识别结果。"""
import base64
import json
import subprocess
import sys
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\pdfbt"
IMG = ROOT + r"\bin\Debug\realtest\selftest_render.png"
WORKER = ROOT + r"\bin\Debug\ocr_worker.py"
PYENV = ROOT + r"\pyenv\python.exe"
PYDEV = r"F:\PythonDev\python\python\python.exe"
def run_ocr(python_exe, label):
with open(IMG, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
p = subprocess.Popen([python_exe, "-u", WORKER],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True, encoding="utf-8")
# 等 warm+ready 两条就绪消息
line1 = p.stdout.readline()
line2 = p.stdout.readline()
if 'ready' not in line2:
# 兼容只发一条的情况
if 'ready' not in line1 and 'warm' not in line1:
print(label, '未就绪:', line1[:120], line2[:120]); p.kill(); return None
req = json.dumps({"id": 1, "image": b64}, ensure_ascii=False)
p.stdin.write(req + "\n")
p.stdin.flush()
resp = p.stdout.readline()
p.kill()
try:
obj = json.loads(resp)
except Exception as e:
print(label, "解析失败:", e, "| 原始:", resp[:200])
return None
items = obj.get("items") or []
texts = [it.get("text", "") for it in items]
print("%s: %d 条, 耗时 %s ms" % (label, len(items), obj.get("ms")))
return texts
t_pyenv = run_ocr(PYENV, "pyenv(CPU)")
t_dev = run_ocr(PYDEV, "开发机(DML)")
if t_pyenv is not None and t_dev is not None:
s1, s2 = set(t_pyenv), set(t_dev)
only_env = [t for t in t_pyenv if t not in s2]
only_dev = [t for t in t_dev if t not in s1]
print("\n仅 pyenv 识别到(%d):" % len(only_env))
for t in only_env[:10]:
print(" +", t)
print("仅 开发机 识别到(%d):" % len(only_dev))
for t in only_dev[:10]:
print(" -", t)
+99
View File
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
"""6 个 Fak* handler 数据源切换:fak.db → 方案窗口当前打开的方案文件。
替换各 handler 内的路径定义为 方案数据源路径(),并把"方案库不存在"提示改为"请先打开方案"。
另:恢复三组右键菜单挂接(完整功能),并加 方案数据源路径() helper。幂等。"""
import io
import re
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
s = io.open(P, encoding="utf-8-sig").read()
n = 0
# 0) helper 方法(挂在 ReloadFakData 后)
helper = ''' /// <summary>方案窗口当前打开的方案文件路径(未打开返回 null)。</summary>
private string 方案数据源路径()
{
return (this.w8 != null) ? this.w8.当前方案文件 : null;
}
'''
if "方案数据源路径()" not in s:
anchor = " /// <summary>读方案库\"项目信息\"表第一行为字典(无表/无行返回空字典)。</summary>"
assert anchor in s, "helper 锚点缺失"
s = s.replace(anchor, helper + anchor, 1)
n += 1
# 1) 6 个 handler 的路径定义+紧邻的存在校验/提示 → 方案数据源路径()+打开提示
# 模式:string xxx = GetDbFullPath(@"sjk\fak.db");\n if (!File.Exists(xxx)) { 提示 return; }
pat = re.compile(
r"([ \t]+)string (fakDbPath|srcDbPath) = GetDbFullPath\(@\"sjk\\\\fak\.db\"\);\r?\n"
r"[ \t]+if \(!System\.IO\.File\.Exists\(\2\)\)\r?\n"
r"[ \t]+\{\r?\n"
r"[ \t]+MessageBox\.Show\(this, \"([^\"]*)\",\r?\n"
r"[ \t]+\"([^\"]*)\", MessageBoxButtons\.OK, MessageBoxIcon\.(Warning|Information)\);\r?\n"
r"[ \t]+return;\r?\n"
r"[ \t]+\}")
def sub_fn(m):
global n
n += 1
ind, var, oldmsg, title, icon = m.group(1), m.group(2), m.group(3), m.group(4), m.group(5)
return (
ind + "string " + var + " = 方案数据源路径();\n" +
ind + "if (string.IsNullOrEmpty(" + var + ") || !System.IO.File.Exists(" + var + "))\n" +
ind + "{\n" +
ind + " MessageBox.Show(this, \"请先在方案窗口的项目页双击打开一个方案,再进行此操作。\",\n" +
ind + " \"" + title + "\", MessageBoxButtons.OK, MessageBoxIcon.Information);\n" +
ind + " return;\n" +
ind + "}"
)
s, c = pat.subn(sub_fn, s)
print("路径切换", c, "处")
n += c
# 2) 恢复三组右键菜单挂接(若已被删)
anchor2 = ''' // ---- 方案窗口(advancedDataGridView图号/箱号/元件)右键菜单已由 DockWindow8 自治挂接
// (操作对象为当前打开的方案文件,不再是 fak.db)----'''
restore = ''' // ---- 方案窗口 三表右键菜单(操作当前打开的方案文件)----
_menuFakTuhao = new ContextMenuStrip();
_menuFakTuhao.Items.Add("删除图号", null, FakTuhao_Delete_Click);
_menuFakTuhao.Items.Add(MakeUndoMenuItem());
_menuFakTuhao.Items.Add(new ToolStripSeparator());
_menuFakTuhao.Items.Add("插入图号到当前项目", null, FakTuhao_Insert_Click);
this.advancedDataGridView图号.ContextMenuStrip = _menuFakTuhao;
Log(" 方案图号菜单已挂到 advancedDataGridView图号(删除/插入到当前项目)");
_menuFakXianghao = new ContextMenuStrip();
_menuFakXianghao.Items.Add("删除箱号", null, FakXianghao_Delete_Click);
_menuFakXianghao.Items.Add(MakeUndoMenuItem());
_menuFakXianghao.Items.Add(new ToolStripSeparator());
_menuFakXianghao.Items.Add("插入箱号到当前项目", null, FakXianghao_Insert_Click);
this.advancedDataGridView箱号.ContextMenuStrip = _menuFakXianghao;
Log(" 方案箱号菜单已挂到 advancedDataGridView箱号(删除/插入到当前项目)");
_menuFakYuanjian = new ContextMenuStrip();
_menuFakYuanjian.Items.Add("删除元件", null, FakYuanjian_Delete_Click);
_menuFakYuanjian.Items.Add(MakeUndoMenuItem());
_menuFakYuanjian.Items.Add(new ToolStripSeparator());
_menuFakYuanjian.Items.Add("插入元件到当前项目", null, FakYuanjian_Insert_Click);
this.advancedDataGridView元件.ContextMenuStrip = _menuFakYuanjian;
Log(" 方案元件菜单已挂到 advancedDataGridView元件(删除/插入到当前项目)");
// ---- 方案窗口(advancedDataGridView图号/箱号/元件)右键菜单已由 DockWindow8 自治挂接
// (操作对象为当前打开的方案文件,不再是 fak.db)----'''
if anchor2 in s:
s = s.replace(anchor2, restore, 1)
n += 1
print("菜单挂接已恢复")
else:
print("警告: 未找到方案窗口右键挂接注释锚点(可能此前未删除)")
# 恢复挂接直接追加在 元件菜单挂接 之后
anchor3 = 'Log(" 元件菜单已挂到 advancedDataGridView5(含「加入用户库」「加入柜体库」+ 行移动)");'
if anchor3 in s and "_menuFakTuhao = new ContextMenuStrip();" not in s:
s = s.replace(anchor3, anchor3 + "\n" + restore.split("\n", 2)[2], 1)
n += 1
print("挂接已追加到元件菜单后")
io.open(P, "w", encoding="utf-8-sig").write(s)
print("合计", n, "处")
+55
View File
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
"""①全部 Fak* handler 的数据源 fak.db → 方案窗口当前打开的方案文件(统一语义)
②ReloadFakData 改为通知方案窗口重新加载当前方案
③DockWindow8 加 重新加载当前方案() 方法。幂等。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
s = io.open(P, encoding="utf-8-sig").read()
n = 0
# 1) 路径定义统一替换(handler 数据源 → 当前方案文件)
pairs = [
('string fakDbPath = GetDbFullPath(@"sjk\\fak.db");', 'string fakDbPath = 方案数据源路径();'),
('string srcDbPath = GetDbFullPath(@"sjk\\fak.db");', 'string srcDbPath = 方案数据源路径();'),
('"方案库不存在:\\n" + fakDbPath', '"请先在方案窗口项目页双击打开一个方案"'),
('"方案库不存在:\\n" + srcDbPath', '"请先在方案窗口项目页双击打开一个方案"'),
]
for old, new in pairs:
c = s.count(old)
s = s.replace(old, new)
n += c
print("替换", c, "处:", old[:50])
# 2) ReloadFakData 改为通知方案窗口重载当前方案
old_rf = ''' public void ReloadFakData()
{
try
{
if (this.w8 != null) this.w8.刷新项目页();
Log("[方案刷新] 已通知方案窗口刷新方案列表");
}
catch (Exception ex)
{
Log("[方案刷新] 失败: " + ex.Message);
}
}'''
new_rf = ''' public void ReloadFakData()
{
try
{
if (this.w8 != null) this.w8.重新加载当前方案();
Log("[方案刷新] 已通知方案窗口重载当前方案");
}
catch (Exception ex)
{
Log("[方案刷新] 失败: " + ex.Message);
}
}'''
if old_rf in s:
s = s.replace(old_rf, new_rf, 1)
n += 1
print("ReloadFakData 已改")
io.open(P, "w", encoding="utf-8-sig").write(s)
print("合计", n, "处")
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
"""修 paste_optimize.py 中图号行触发器的锚点(适配现签名),并加开关条件。"""
import io
p = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\tools\paste_optimize.py"
s = io.open(p, encoding="utf-8").read()
old = (
'\' "_u AFTER UPDATE OF \\\\"" + 图号输入列[0] + "\\\\"" ON \\\\"" + 图号表名 + "\\\\"" FOR EACH ROW WHEN NEW.\\\\\"内部ID\\\\\"=\'" +\n'
"\t\t\t\t图号ID.Replace(\"'\", \"''\") + \"' BEGIN UPDATE \\\\\"\" + 图号表名 + \"\\\\\" SET \" + thSetNew +"
)
# 直接用朴素文本定位(避免转义嵌套):找 'BEGIN UPDATE' 紧邻 thSetNew 的那一行对
lines = s.split("\n")
done = False
for i, l in enumerate(lines):
if "FOR EACH ROW WHEN NEW." in l and "图号输入列[0]" in l and i + 2 < len(lines):
if "' BEGIN UPDATE" in lines[i + 2] and "thSetNew" in lines[i + 2]:
lines[i + 2] = lines[i + 2].replace("' BEGIN UPDATE", "' AND \" + 开关条件 + \" BEGIN UPDATE", 1)
done = True
print("锚点行已改:", lines[i + 2].strip()[:80])
break
if done:
io.open(p, "w", encoding="utf-8").write("\n".join(lines))
print("done", done)
+206
View File
@@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
"""
把旧工具的 Columns.Add + Columns[i].X 写法转换为 SharpDevelop 设计器认的
"列字段 + AddRange" 标准 VS 写法,让设计器里能显示列头。
"""
import io
import re
import os
import glob
BASE = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
def convert_columns(designer_text, class_name):
"""把一个 Designer.cs 里的所有 DataGridView 列定义从旧格式转换为 VS 标准格式"""
lines = designer_text.split("\n")
out = []
all_column_fields = [] # 所有列字段声明
all_column_news = [] # 所有列 new 语句
all_column_blocks = [] # 所有列属性块
i = 0
while i < len(lines):
line = lines[i].strip()
# 检测: this.xxx.Columns.Add(new System.Windows.Forms.DataGridViewTextBoxColumn());
m_add = re.match(r'this\.(\w+)\.Columns\.Add\(new System\.Windows\.Forms\.(\w+Column)\(\)\);', line)
if not m_add:
out.append(lines[i])
i += 1
continue
grid_name = m_add.group(1)
col_type = m_add.group(2)
# 收集后续的 Columns[i].X = ... 行
col_idx = 0
col_props = []
j = i + 1
while j < len(lines):
st = lines[j].strip()
m_prop = re.match(r'this\.' + grid_name + r'\.Columns\[(\d+)\]\.(\w+(?:\.\w+)*)\s*=\s*(.+?);', st)
if not m_prop:
break
idx = int(m_prop.group(1))
if idx != col_idx:
break
col_props.append((m_prop.group(2), m_prop.group(3)))
j += 1
if not col_props:
out.append(lines[i])
i += 1
continue
# 生成 VS 标准写法
# 列名从 HeaderText 取
header = ""
for prop, val in col_props:
if prop == "HeaderText":
header = val.strip('"')
break
field_name = "dataGridViewColumn_%s_%d" % (grid_name, col_idx)
# 收集属性
prop_lines = []
for prop, val in col_props:
if prop == "Name" and val == '""':
continue # 跳过空 Name
prop_lines.append("\t\t\tthis.%s.%s = %s;" % (field_name, prop, val))
all_column_news.append("\t\t\tthis.%s = new System.Windows.Forms.%s();" % (field_name, col_type))
all_column_blocks.append("\t\t\t// \n\t\t\t// %s\n\t\t\t// " % field_name)
all_column_blocks.extend(prop_lines)
all_column_fields.append("\t\tprivate System.Windows.Forms.%s %s;" % (col_type, field_name))
# 记住这个列属于哪个 grid
if not hasattr(convert_columns, "_grid_cols"):
convert_columns._grid_cols = {}
convert_columns._grid_cols.setdefault(grid_name, []).append(field_name)
col_idx += 1
# 继续找下一个 Columns.Add(同一 grid 的下一列)
while j < len(lines):
st2 = lines[j].strip()
m_next = re.match(r'this\.' + grid_name + r'\.Columns\.Add\(new System\.Windows\.Forms\.(\w+Column)\(\)\);', st2)
if m_next:
col_type2 = m_next.group(1)
j += 1
col_props2 = []
while j < len(lines):
st3 = lines[j].strip()
m_prop2 = re.match(r'this\.' + grid_name + r'\.Columns\[(\d+)\]\.(\w+(?:\.\w+)*)\s*=\s*(.+?);', st3)
if not m_prop2:
break
col_props2.append((m_prop2.group(2), m_prop2.group(3)))
j += 1
field_name2 = "dataGridViewColumn_%s_%d" % (grid_name, col_idx)
for prop, val in col_props2:
if prop == "HeaderText":
header2 = val.strip('"')
break
prop_lines2 = []
for prop, val in col_props2:
if prop == "Name" and val == '""':
continue
prop_lines2.append("\t\t\tthis.%s.%s = %s;" % (field_name2, prop, val))
all_column_news.append("\t\t\tthis.%s = new System.Windows.Forms.%s();" % (field_name2, col_type2))
all_column_blocks.append("\t\t\t// \n\t\t\t// %s\n\t\t\t// " % field_name2)
all_column_blocks.extend(prop_lines2)
all_column_fields.append("\t\tprivate System.Windows.Forms.%s %s;" % (col_type2, field_name2))
convert_columns._grid_cols.setdefault(grid_name, []).append(field_name2)
col_idx += 1
else:
break
# 跳过所有已消费的行
i = j
# 在 SuspendLayout 之前插入列的 new 语句
result = []
inserted_news = False
for line in out:
if not inserted_news and "this.SuspendLayout()" in line:
result.extend(all_column_news)
inserted_news = True
result.append(line)
# 在最后一个控件属性块之后、Controls.Add 之前插入列属性块和 AddRange
final = []
inserted_blocks = False
for k, line in enumerate(result):
# 找 Controls.Add 或 AddRange(第一个容器层级的地方)
if not inserted_blocks and re.search(r'this\.\w+\.Controls\.Add', line.strip()):
# 插入列属性块
final.extend(all_column_blocks)
# 插入每个 grid 的 Columns.AddRange
for grid_name, fields in convert_columns._grid_cols.items():
inner = ",\n\t\t\t\t".join("this.%s" % f for f in fields)
final.append("\t\t\tthis.%s.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {" % grid_name)
final.append("\t\t\t\t" + inner + "});")
final.append("")
inserted_blocks = True
final.append(line)
if not inserted_blocks:
# 没找到 Controls.Add,直接在 ResumeLayout 前插
final2 = []
for line in final:
if "this.ResumeLayout" in line and not inserted_blocks:
final2.extend(all_column_blocks)
for grid_name, fields in convert_columns._grid_cols.items():
inner = ",\n\t\t\t\t".join("this.%s" % f for f in fields)
final2.append("\t\t\tthis.%s.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {" % grid_name)
final2.append("\t\t\t\t" + inner + "});")
final2.append("")
inserted_blocks = True
final2.append(line)
final = final2
# 在最后一个 private 字段声明之前插入列字段
final3 = []
inserted_fields = False
for line in final:
if not inserted_fields and re.match(r'\t\tprivate ', line) and "Column" not in line:
final3.extend(all_column_fields)
final3.append("")
inserted_fields = True
final3.append(line)
if not inserted_fields:
final3.extend(all_column_fields)
# 清理类级变量
convert_columns._grid_cols = {}
return "\n".join(final3), len(all_column_fields)
def process_file(path):
s = io.open(path, encoding="utf-8-sig").read()
# 提取类名
m = re.search(r'partial class (\w+)', s)
if not m:
return 0
class_name = m.group(1)
# 检查是否有旧格式的列定义
if "Columns.Add(new" not in s:
return 0
new_s, n_cols = convert_columns(s, class_name)
if n_cols > 0:
io.open(path, "w", encoding="utf-8-sig", newline="\r\n").write(new_s)
print("%s: 转换了 %d 个列字段" % (os.path.basename(path), n_cols))
return n_cols
if __name__ == "__main__":
total = 0
for pattern in ["xiangmu/*.Designer.cs", "shezhi/*.Designer.cs",
"ku/*.Designer.cs", "GCS母排计算/*.Designer.cs"]:
for f in glob.glob(os.path.join(BASE, pattern)):
total += process_file(f)
print("\n共转换 %d 个列" % total)
+12
View File
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
"""把 csproj 里 Smart.CustomComponents 的 HintPath 从安装目录改指仓库 bin(最新构建)。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\成套报价软件.csproj"
OLD = r"<HintPath>..\..\..\..\..\..\Program Files %28x86%29\SharpDevelop\5.2\bin\Smart.CustomComponents.dll</HintPath>"
NEW = r"<HintPath>F:\Visual Studio\1\33\SharpDevelop-master\bin\Smart.CustomComponents.dll</HintPath>"
s = io.open(P, encoding="utf-8-sig").read()
assert OLD in s, "旧 HintPath 未找到"
io.open(P, "w", encoding="utf-8-sig").write(s.replace(OLD, NEW))
print("HintPath 已指向仓库 bin(最新构建)")
+117
View File
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""项目右键"加入方案库"插入段修正版:在 复制图号到方案库 方法前插入 项目右键处理方法。幂等。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
s = io.open(P, encoding="utf-8-sig").read()
anchor = " /// <summary>复制一个图号(含箱号/元件子树)从项目库到方案库。供单图号加入与整项目加入共用。</summary>"
assert anchor in s, "插入锚缺失"
if "MenuProject_AddToFak_Click" in s:
print("已存在,跳过")
else:
proj_click = ''' /// <summary>项目表右键「加入方案库」:把整个项目(全部图号+箱号+元件+项目信息)复制到方案库。</summary>
private void MenuProject_AddToFak_Click(object sender, EventArgs e)
{
var row = GetFirstSelectedRow(this.advancedDataGridView6);
if (row == null)
{
MessageBox.Show(this, "请先在项目列表中选择要加入方案库的项目。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
string projectId = GetBoundValueAsString(row, "项目编号");
if (string.IsNullOrWhiteSpace(projectId))
{
MessageBox.Show(this, "选中行没有项目编号,无法加入方案库。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string srcDbPath = GetQuoteDbPath(projectId);
if (!System.IO.File.Exists(srcDbPath))
{
MessageBox.Show(this, "项目报价库不存在:\\n" + srcDbPath,
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string fakDbPath = GetDbFullPath(@"sjk\\fak.db");
var sw = System.Diagnostics.Stopwatch.StartNew();
Cursor = Cursors.WaitCursor;
try
{
var 图号们 = new System.Collections.Generic.List<string>();
using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnString(srcDbPath)))
{
conn.Open();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 内部ID FROM " + TuhaoTableName + " ORDER BY CAST(序号 AS INTEGER);", conn))
using (var r = cmd.ExecuteReader())
while (r.Read())
{
if (!r.IsDBNull(0))
{
string v = r.GetString(0);
if (!string.IsNullOrEmpty(v)) 图号们.Add(v);
}
}
}
if (图号们.Count == 0)
{
MessageBox.Show(this, "项目 [" + projectId + "] 没有图号数据,无法加入方案库。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
foreach (var thId in 图号们)
复制图号到方案库(projectId, thId, srcDbPath, fakDbPath);
string projTbl = ProjectInfoTableName;
using (var srcConn = new System.Data.SQLite.SQLiteConnection(GetConnString(srcDbPath)))
using (var dstConn = new System.Data.SQLite.SQLiteConnection(GetConnString(fakDbPath)))
{
srcConn.Open();
dstConn.Open();
var infoRow = new DataTable();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT * FROM " + projTbl + " WHERE 项目编号=@id LIMIT 1;", srcConn))
{
cmd.Parameters.AddWithValue("@id", projectId);
using (var ad = new System.Data.SQLite.SQLiteDataAdapter(cmd)) ad.Fill(infoRow);
}
var cols = new System.Collections.Generic.List<string>();
foreach (DataColumn c in infoRow.Columns) cols.Add(c.ColumnName);
using (var tx = dstConn.BeginTransaction())
{
Exec(dstConn, tx, "DELETE FROM " + projTbl + ";");
foreach (DataRow r in infoRow.Rows)
InsertRowByColumns(r, projTbl, cols, dstConn, tx);
tx.Commit();
}
}
sw.Stop();
Log("[加入方案库] 整项目完成: 项目=" + projectId + " 图号=" + 图号们.Count + " 耗时 " + sw.ElapsedMilliseconds + "ms");
ReloadFakData();
MessageBox.Show(this, "已把项目 [" + projectId + "] 加入方案库。\\n共 " + 图号们.Count + " 个图号,耗时 " +
sw.ElapsedMilliseconds + "ms。", "加入方案库",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception exAdd)
{
Log("[加入方案库] 整项目失败: " + exAdd);
MessageBox.Show(this, "加入方案库失败:\\n" + exAdd.Message, "加入方案库",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
Cursor = Cursors.Default;
}
}
'''
s = s.replace(anchor, proj_click + anchor, 1)
io.open(P, "w", encoding="utf-8-sig").write(s)
print("项目右键方法已插入")
+20
View File
@@ -0,0 +1,20 @@
$ErrorActionPreference = 'Stop'
$src = 'C:\Users\099978\Documents\SharpDevelop Projects\chengtao\pdfbt\libs\AdvancedDataGridView.dll'
$dst = 'C:\Program Files (x86)\SharpDevelop\5.2\bin\AdvancedDataGridView.dll'
if (Test-Path $dst) { Write-Output 'exists-skip'; exit 0 }
$dstDir = 'C:\Program Files (x86)\SharpDevelop\5.2\bin'
# 中文路径占位替换(写文件时避免编码问题): 用真实路径覆盖
$src = $src -replace 'chengtao', [char]0x6210 + [char]0x5957 + [char]0x62A5 + [char]0x4EF7
try {
Copy-Item -LiteralPath $src -Destination $dst -ErrorAction Stop
Write-Output 'copy-ok'
exit 0
} catch { Write-Output ('copy-fail: ' + $_.Exception.Message) }
try {
$me = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
icacls $dstDir /grant "${me}:(OI)(CI)M" | Out-Null
Copy-Item -LiteralPath $src -Destination $dst -ErrorAction Stop
Write-Output 'icacls-ok'
exit 0
} catch { Write-Output ('icacls-fail: ' + $_.Exception.Message) }
Write-Output 'ALL_FAILED'
+87
View File
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
"""
成套报价软件图标生成器 —— 与 app.svg 设计稿完全等价的 Pillow 绘制。
纯 Python + Pillow,零原生依赖(本机没有 cairo,SVG 无法直接栅格化)。
设计:深蓝→青蓝渐变圆角底 + 白色开关柜轮廓 + 三根铜排 + 绿色 ¥ 角标。
512 高分辨率绘制后缩放出多尺寸 app.ico,保证 16px 也清晰。
"""
import os
import math
from PIL import Image, ImageDraw
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
OUT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "成套报价软件"))
ICO_PATH = os.path.join(OUT_DIR, "app.ico")
PNG_PATH = os.path.join(OUT_DIR, "app_512.png")
SIZES = [16, 32, 48, 64, 128, 256]
RENDER = 512 # 与 SVG viewBox 一致,坐标 1:1
def lerp(c1, c2, t):
return (int(c1[0] + (c2[0] - c1[0]) * t),
int(c1[1] + (c2[1] - c1[1]) * t),
int(c1[2] + (c2[2] - c1[2]) * t))
def rounded_gradient_rect(img, box, c_top, c_bot, radius):
"""垂直渐变圆角矩形(逐行渐变 + 圆角 mask)。"""
x0, y0, x1, y1 = box
w, h = x1 - x0, y1 - y0
grad = Image.new("RGBA", (w, h), (0, 0, 0, 0))
gp = grad.load()
for y in range(h):
c = lerp(c_top, c_bot, y / max(h - 1, 1))
for x in range(w):
gp[x, y] = (c[0], c[1], c[2], 255)
mask = Image.new("L", (w, h), 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, w - 1, h - 1], radius=radius, fill=255)
img.paste(grad, (x0, y0), mask)
def line_round(draw, p1, p2, width, fill):
"""带圆头的粗线。"""
draw.line([p1, p2], fill=fill, width=width)
r = width / 2.0
for p in (p1, p2):
draw.ellipse([p[0] - r, p[1] - r, p[0] + r, p[1] + r], fill=fill)
def main():
img = Image.new("RGBA", (RENDER, RENDER), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
# 1) 渐变圆角背景 (16,16)-(496,496) r=100,#1d4ed8→#0e7490
rounded_gradient_rect(img, (16, 16, 496, 496), (29, 78, 216), (14, 116, 144), 100)
d = ImageDraw.Draw(img)
# 2) 开关柜轮廓 (118,88)-(394,392) r=30 白色描边 24
d.rounded_rectangle([118, 88, 394, 392], radius=30, outline=(255, 255, 255, 255), width=24)
# 3) 三根铜排 (160..352),高 34,r=9,铜色渐变近似为纯色+顶部亮色高光线
for y in (138, 218, 298):
d.rounded_rectangle([160, y, 352, y + 34], radius=9, fill=(234, 141, 25, 255)) # #ea8d19 主体
d.rounded_rectangle([160, y, 352, y + 14], radius=7, fill=(251, 191, 36, 255)) # #fbbf24 顶部亮
# 4) ¥ 角标:绿圆 (376,376) r=94 + 白描边 14
d.ellipse([376 - 94, 376 - 94, 376 + 94, 376 + 94],
fill=(16, 185, 129, 255), outline=(255, 255, 255, 255), width=14)
# ¥ 笔画(白,线宽 19,圆头)
W = (255, 255, 255, 255)
lw = 19
line_round(d, (338, 338), (378, 380), lw, W) # 撇
line_round(d, (416, 338), (376, 380), lw, W) # 捺
line_round(d, (340, 392), (412, 392), lw, W) # 上横
line_round(d, (340, 420), (412, 420), lw, W) # 下横
line_round(d, (377, 414), (377, 442), lw, W) # 竖
# 5) 导出:512 PNG 预览 + 多尺寸 ICO
img.save(PNG_PATH)
img.save(ICO_PATH, sizes=[(s, s) for s in SIZES])
print("已生成:", ICO_PATH)
print("已生成:", PNG_PATH)
if __name__ == "__main__":
main()
+187
View File
@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
"""组装发布目录 release\成套报价软件\(exe + 依赖 DLL + 图标 + 运行数据目录)。"""
import os
import shutil
ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
BIN = os.path.join(ROOT, "成套报价软件", "bin", "Debug")
REL = os.path.join(ROOT, "release", "成套报价软件")
DLLS = [
"AdvancedDataGridView.dll", "Smart.CustomComponents.dll",
"WeifenLuo.WinFormsUI.Docking.dll", "WeifenLuo.WinFormsUI.Docking.ThemeVS2015.dll",
"System.Data.SQLite.dll", "System.Resources.Extensions.dll", "System.Memory.dll",
"System.Numerics.Vectors.dll", "System.Runtime.CompilerServices.Unsafe.dll",
"Microsoft.Web.WebView2.Core.dll", "Microsoft.Web.WebView2.WinForms.dll", "WebView2Loader.dll",
"pdfium.dll",
"NPOI.dll", "NPOI.OOXML.dll", "NPOI.OpenXml4Net.dll", "NPOI.OpenXmlFormats.dll",
"DotNetZip.dll", "ICSharpCode.SharpZipLib.dll", "unvell.ReoGrid.dll", "CJZZ.Controls.dll",
]
DATA_DIRS = ["sjk", "GCS铜排壳体计算", "shezhi", "xiangmu"]
def main():
# 只清掉旧的同名文件,保留 setup.iss 和 Output 目录(安装脚本手工维护)
os.makedirs(REL, exist_ok=True)
for name in os.listdir(REL):
p = os.path.join(REL, name)
if name in ("setup.iss", "Output"):
continue
if os.path.isdir(p):
shutil.rmtree(p)
else:
os.remove(p)
# ★ WebView2 运行时引导程序(setup.iss [Files] 引用;新装机缺运行时时安装期静默补装)
wv2 = os.path.join(ROOT, "tools", "MicrosoftEdgeWebview2Setup.exe")
if os.path.exists(wv2):
shutil.copy2(wv2, os.path.join(REL, "MicrosoftEdgeWebview2Setup.exe"))
print("复制 MicrosoftEdgeWebview2Setup.exe(WebView2 运行时引导程序)")
else:
print("!! 缺少 tools/MicrosoftEdgeWebview2Setup.exe(新装机缺 WebView2 时无法自动补装)")
# exe + 配置 + 图标
for f in ["成套报价软件.exe", "成套报价软件.exe.config", "app.ico"]:
src = os.path.join(BIN, f)
if not os.path.exists(src):
src = os.path.join(ROOT, "成套报价软件", f) # app.ico 在项目根
shutil.copy2(src, REL)
print("复制", f)
# 依赖 DLL
missing = []
for dll in DLLS:
src = os.path.join(BIN, dll)
if os.path.exists(src):
shutil.copy2(src, REL)
else:
missing.append(dll)
print("DLL 复制完成,缺失:", missing if missing else "无")
# 功能区按钮图标目录(exe 旁 icons\,运行时按绝对路径加载)
icons_src = os.path.join(BIN, "icons")
if os.path.isdir(icons_src):
shutil.copytree(icons_src, os.path.join(REL, "icons"))
print("复制目录 icons(功能区按钮图标 %d 个)" % len(os.listdir(icons_src)))
# 开源许可证全文目录(exe 旁 licenses\,关于窗口「查看许可证全文」用)
lic_src = os.path.join(BIN, "licenses")
if os.path.isdir(lic_src):
shutil.copytree(lic_src, os.path.join(REL, "licenses"))
print("复制目录 licenses(开源许可证全文 %d 份)" % len(os.listdir(lic_src)))
# 元件库网页(node 静态服务:html+server.cjs+jquery;2026-09-13 页面已改公网 dx24test.dhub.com.cn,
# 本地 data 数据树/access.log/.mimosa/旧数据包 zip 一律不进包)
yj_src = os.path.join(BIN, "元件库网页")
if os.path.isdir(yj_src):
shutil.copytree(yj_src, os.path.join(REL, "元件库网页"),
ignore=shutil.ignore_patterns("__pycache__", "data", "access.log", ".mimosa", "*.zip"))
print("复制目录 元件库网页(%d 文件)" % sum(len(fs) for _, _, fs in os.walk(yj_src)))
else:
print("!! 缺少目录 元件库网页")
# 运行数据目录
for d in DATA_DIRS:
src = os.path.join(BIN, d)
if not os.path.isdir(src):
print("!! 缺少数据目录", d)
continue
dst = os.path.join(REL, d)
if d == "shezhi":
# 只带铜排/铝排参数;企业信息、收藏、最近项目等用户私有配置不带
os.makedirs(dst)
for f in ("铜排参数.config.xml", "铝排参数.config.xml"):
fp = os.path.join(src, f)
if os.path.exists(fp):
shutil.copy2(fp, dst)
print("复制 shezhi/" + f)
elif d == "sjk":
# 库数据照拷;排除项目列表库(程序自动建)和 bjb 各项目报价库(只留空目录)
shutil.copytree(src, dst, ignore=shutil.ignore_patterns("advancedDataGridView6Table.db"))
bjb = os.path.join(dst, "bjb")
if os.path.isdir(bjb):
shutil.rmtree(bjb)
os.makedirs(bjb)
# 方案库是用户私有方案文件(同 bjb 策略留空,程序自动建)
fangan = os.path.join(dst, "方案库")
if os.path.isdir(fangan):
shutil.rmtree(fangan)
os.makedirs(fangan)
print("复制目录 sjk(排除项目库,bjb/方案库 留空)")
else:
# GCS铜排壳体计算:只带运行文件,排除遗留源码/构建脚本(2026-09-06 用户要求源码不进包)
if d == "GCS铜排壳体计算":
shutil.copytree(src, dst,
ignore=shutil.ignore_patterns("*.cs", "*.xml", "*.bat", "*.rsp", "*.py", "build*"))
print("复制目录 GCS铜排壳体计算(排除源码/构建脚本)")
continue
shutil.copytree(src, dst,
ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
print("复制目录", d)
# ===== cadbt(CAD扒图独立程序,默认安装) =====
cadbt_src = os.path.join(ROOT, "cadbt", "cadbt", "bin", "Debug")
cadbt_dst = os.path.join(REL, "cadbt")
os.makedirs(cadbt_dst, exist_ok=True)
for f in os.listdir(cadbt_src):
if f.endswith((".exe", ".dll", ".config")):
shutil.copy2(os.path.join(cadbt_src, f), os.path.join(cadbt_dst, f))
os.makedirs(os.path.join(cadbt_dst, "node"), exist_ok=True)
shutil.copy2(os.path.join(cadbt_src, "node", "node.exe"), os.path.join(cadbt_dst, "node", "node.exe"))
dist_src = os.path.join(ROOT, "cadbt", "cadbt", "CAD-Viewer", "packages", "cad-viewer-example", "dist")
dist_dst = os.path.join(cadbt_dst, "CAD-Viewer", "dist")
shutil.copytree(dist_src, dist_dst)
# ★ vite build 只产出 assets/index.html/testdata;fonts(中文字体库)/templates/
# server.cjs 来自原厂预编译包,源码树没有——从部署机运行目录补齐
# (2026-09-20 补:此前安装包漏带这三样,装机机 CAD 文字渲染缺字体)
runtime_dist = r"C:\成套报价软件\cadbt\CAD-Viewer\dist"
for extra in ("fonts", "templates"):
p = os.path.join(runtime_dist, extra)
if os.path.isdir(p):
shutil.copytree(p, os.path.join(dist_dst, extra))
print("补齐 dist/%s(%d 文件)" % (extra, sum(len(fs) for _, _, fs in os.walk(p))))
else:
print("!! 运行目录缺少", extra, "(安装机 CAD 文字渲染会缺字体!)")
sc = os.path.join(runtime_dist, "server.cjs")
if os.path.exists(sc):
shutil.copy2(sc, os.path.join(dist_dst, "server.cjs"))
print("补齐 dist/server.cjs")
else:
print("!! 运行目录缺少 server.cjs(CAD 网页服务起不来!)")
os.makedirs(os.path.join(cadbt_dst, "sjk", "bjb"), exist_ok=True)
print("复制目录 cadbt(exe+DLL+node+dist 网页+fonts/templates/server.cjs)")
# ===== pdfbt(PDF扒图独立程序+OCR Python 环境,默认安装) =====
pdfbt_src = os.path.join(ROOT, "pdfbt", "bin", "Debug")
pdfbt_dst = os.path.join(REL, "pdfbt")
os.makedirs(pdfbt_dst, exist_ok=True)
for f in os.listdir(pdfbt_src):
if f.endswith((".exe", ".dll", ".config")):
shutil.copy2(os.path.join(pdfbt_src, f), os.path.join(pdfbt_dst, f))
os.makedirs(os.path.join(pdfbt_dst, "sjk", "bjb"), exist_ok=True)
print("复制目录 pdfbt(exe+pdfium,OCR=百度API无需本地环境)")
# ===== AI 助手引擎目录(2026-09-26 OpenClaw→ZCode 开源版 zai-org/ZCode) =====
# ai助手\ = zcode\(官方 build:zcode 发行包:bin/web/server/agent/node_modules + 捆绑 node)
# + zcode预置\(技能/provider模板/密钥/版本戳) + 启停 cmd,位于仓库根。
# 排除运行时产物:zcode数据\(装机从零初始化;ZCODE_DATA_BASE_DIR 隔离目录)与日志。
ai_src = os.path.join(ROOT, "ai助手")
ai_dst = os.path.join(REL, "ai助手")
if os.path.isdir(ai_src):
shutil.copytree(
ai_src, ai_dst,
# openclaw=旧引擎空壳目录(进程句柄占着删不掉,重启自清;零文件无实害,不进包)
ignore=shutil.ignore_patterns(".mimosa", "*.log", "zcode数据", "openclaw"),
)
print("复制目录 ai助手(ZCode 引擎 zcode 发行包+node+预置+启停cmd,排除 .mimosa/日志/zcode数据运行时目录)")
else:
print("跳过 ai助手(源目录不存在)")
# ===== 会员服务器:不进包(2026-09-17 用户要求停进包;2026-09-19 曾恢复,2026-09-22 用户
# 明确长期规则=不打包并已从 release 目录手动删除——客户端默认连 https://ss61.odn.cc
# 云端,部署套件单独交付,setup.iss 条目同步删除) =====
print("\n发布目录:", REL)
if __name__ == "__main__":
main()
+43
View File
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
"""PDF扒图图标(Pillow 手绘,白文档+红折角+PDF 红标签,64x64 4x 超采样)。"""
import os
from PIL import Image, ImageDraw, ImageFont
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
OUT = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons", "pdf-grab.png")
S = 4
RED = (214, 69, 65, 255) # PDF 红
RED_D = (192, 57, 43, 255) # 深红描边
INK = (61, 67, 74, 255)
img = Image.new("RGBA", (64 * S, 64 * S), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
def P(x, y, w, h, r, fill=None, outline=None, width=0):
d.rounded_rectangle([x * S, y * S, (x + w) * S, (y + h) * S], radius=r * S,
fill=fill, outline=outline, width=width * S)
def L(pts, color, w):
d.line([(x * S, y * S) for x, y in pts], fill=color, width=w * S, joint="curve")
# 文档(带折角路径)
doc = [(14, 6), (36, 6), (48, 18), (48, 58), (14, 58)]
d.polygon([(x * S, y * S) for x, y in doc], fill=(255, 255, 255, 255))
d.line([(x * S, y * S) for x, y in doc] + [(14 * S, 6 * S)], fill=RED_D, width=3 * S, joint="curve")
L([(36, 6), (36, 18), (48, 18)], RED_D, 3)
# 内容线
L([(20, 26), (42, 26)], (185, 190, 195, 255), 2)
L([(20, 33), (42, 33)], (185, 190, 195, 255), 2)
# PDF 红标签(左下,压住文档边缘)
P(6, 38, 36, 18, 3, fill=RED)
try:
font = ImageFont.truetype("msyhbd.ttc", 11 * S)
except Exception:
font = ImageFont.truetype("arialbd.ttf", 11 * S)
d.text((24 * S, 47 * S), "PDF", font=font, fill=(255, 255, 255, 255), anchor="mm")
img.resize((64, 64), Image.LANCZOS).save(OUT)
print("pdf-grab.png ->", OUT)
+266
View File
@@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
"""功能区 24 个按钮图标生成器。
SVG 设计稿(语义化,统一风格) → svglib 栅格化 64x64 透明 PNG → bin\Debug\icons\。
幂等可重跑;svg 定义即源档(同步存一份到 icons/svg 供后续编辑)。
风格规范:viewBox 0 0 64 64;主色 #4A4A4A;强调绿 #217346(=功能区 AccentColor);
删除红 #D83931;母排铜 #C8791E;线宽 4 圆角;不用白色填充(保透明底)。"""
import os
import io
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
SVG_DIR = os.path.join(ROOT, "成套报价软件", "icons", "svg")
PNG_DIR = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons")
G = "#4A4A4A" # 主体深灰
GR = "#217346" # 强调绿
RD = "#D83931" # 删除红
CU = "#C8791E" # 铜排橙
BL = "#2B579A" # 文档蓝(点缀)
W = 'stroke="%s" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"' % G
def svg(body):
return ('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">\n' + body + "\n</svg>\n")
# ---- 图标定义: name -> SVG body (64x64) ----
ICONS = {}
# 文档底形(多图标复用): 纸张
def doc(x=14, y=8, w=30, h=44, fold=True, color=G):
f = ('<path d="M%d %d h%d l%d %d v%d h-%d z" %s stroke="%s"/>' %
(x, y, w - 10, 10, 10, h - 10, w, W, color)) if fold else \
('<rect x="%d" y="%d" width="%d" height="%d" rx="3" %s stroke="%s"/>' % (x, y, w, h, W, color))
return f
# 1 button1 新建项目: 文档 + 绿色加号
ICONS["new-project"] = svg(
doc() +
'<line x1="18" y1="24" x2="34" y2="24" %s stroke="%s"/>' % (W, G) +
'<line x1="18" y1="32" x2="30" y2="32" %s stroke="%s"/>' % (W, G) +
'<circle cx="46" cy="44" r="13" fill="%s"/>' % GR +
'<line x1="46" y1="37" x2="46" y2="51" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round"/>' +
'<line x1="39" y1="44" x2="53" y2="44" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round"/>')
# 2 button2 修改项目: 文档 + 笔(斜置)
ICONS["edit-project"] = svg(
doc() +
'<line x1="18" y1="20" x2="28" y2="20" %s stroke="%s"/>' % (W, G) +
'<line x1="18" y1="28" x2="26" y2="28" %s stroke="%s"/>' % (W, G) +
'<path d="M36 46 L52 30 L58 36 L42 52 L34 54 Z" fill="%s" stroke="%s" stroke-width="2"/>' % (CU, "#9C5A10") +
'<line x1="49" y1="33" x2="55" y2="39" stroke="#9C5A10" stroke-width="2"/>')
# 3 button3 删除项目: 垃圾桶
ICONS["delete-project"] = svg(
'<path d="M16 20 h32 l-3 36 a4 4 0 0 1 -4 4 h-18 a4 4 0 0 1 -4 -4 z" %s stroke="%s"/>' % (W, RD) +
'<line x1="12" y1="20" x2="52" y2="20" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % RD +
'<line x1="26" y1="20" x2="26" y2="14" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % RD +
'<line x1="38" y1="20" x2="38" y2="14" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % RD +
'<line x1="26" y1="30" x2="27" y2="50" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % RD +
'<line x1="38" y1="30" x2="37" y2="50" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % RD)
# 4 button4 打开项目(项目页): 打开的文件夹
ICONS["open-project"] = svg(
'<path d="M8 16 h14 l5 6 h21 v8 h-40 z" %s stroke="%s"/>' % (W, G) +
'<path d="M10 30 h46 l-6 20 h-36 z" %s stroke="%s"/>' % (W, GR) +
'<line x1="18" y1="38" x2="44" y2="38" stroke="%s" stroke-width="3" stroke-linecap="round" opacity="0.55"/>' % GR)
# 5 button5 复制项目: 双层文档
ICONS["copy-project"] = svg(
'<rect x="22" y="8" width="28" height="38" rx="3" %s stroke="%s"/>' % (W, G) +
'<path d="M14 18 v36 h28" %s stroke="%s"/>' % (W, GR) +
'<line x1="29" y1="18" x2="43" y2="18" stroke="%s" stroke-width="3" stroke-linecap="round"/>' % G +
'<line x1="29" y1="26" x2="43" y2="26" stroke="%s" stroke-width="3" stroke-linecap="round"/>' % G +
'<line x1="29" y1="34" x2="38" y2="34" stroke="%s" stroke-width="3" stroke-linecap="round"/>' % G)
# 6 button8 导入项目: 容器+向下箭头(进箱)
ICONS["import-project"] = svg(
'<path d="M10 36 h44 v14 a6 6 0 0 1 -6 6 h-32 a6 6 0 0 1 -6 -6 z" %s stroke="%s"/>' % (W, GR) +
'<line x1="32" y1="6" x2="32" y2="26" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % GR +
'<path d="M24 20 l8 8 l8 -8" stroke="%s" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % GR)
# 7 button7 导出项目: 容器+向上箭头(出箱)
ICONS["export-project"] = svg(
'<path d="M10 36 h44 v14 a6 6 0 0 1 -6 6 h-32 a6 6 0 0 1 -6 -6 z" %s stroke="%s"/>' % (W, BL) +
'<line x1="32" y1="26" x2="32" y2="6" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % BL +
'<path d="M24 12 l8 -8 l8 8" stroke="%s" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % BL)
# 8 button9 打开项目(报价页): 文件夹+文档探出
ICONS["open-quote"] = svg(
'<path d="M8 14 h14 l5 6 h21 v8 h-40 z" %s stroke="%s"/>' % (W, G) +
'<rect x="26" y="10" width="18" height="24" rx="2" fill="%s" stroke="%s" stroke-width="3"/>' % ("#E8F2EC", GR) +
'<line x1="30" y1="17" x2="40" y2="17" stroke="%s" stroke-width="3" stroke-linecap="round"/>' % GR +
'<line x1="30" y1="24" x2="36" y2="24" stroke="%s" stroke-width="3" stroke-linecap="round"/>' % GR +
'<path d="M10 28 h46 l-6 20 h-36 z" %s stroke="%s"/>' % (W, G))
# 9 button10 修改项目(报价页): 文档+笔+绿点(区分项目页)
ICONS["edit-quote"] = svg(
doc() +
'<line x1="18" y1="20" x2="28" y2="20" %s stroke="%s"/>' % (W, G) +
'<circle cx="24" cy="46" r="11" fill="%s"/>' % GR +
'<path d="M34 42 L48 28 L54 34 L40 48 L33 50 Z" fill="%s" stroke="%s" stroke-width="2"/>' % (CU, "#9C5A10"))
# 10 button17 保存: 软盘(绿色)
ICONS["save"] = svg(
'<path d="M10 10 h36 l8 8 v36 h-44 z" %s stroke="%s"/>' % (W, GR) +
'<rect x="20" y="10" width="20" height="14" %s stroke="%s"/>' % (W, GR) +
'<rect x="20" y="36" width="24" height="18" %s stroke="%s"/>' % (W, GR) +
'<line x1="38" y1="12" x2="38" y2="20" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % GR)
# 11 button18 导入报价: 文档+向下箭头
ICONS["import-quote"] = svg(
'<rect x="10" y="8" width="30" height="44" rx="3" %s stroke="%s"/>' % (W, G) +
'<line x1="17" y1="18" x2="33" y2="18" %s stroke="%s"/>' % (W, G) +
'<line x1="17" y1="26" x2="29" y2="26" %s stroke="%s"/>' % (W, G) +
'<line x1="48" y1="30" x2="48" y2="54" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % GR +
'<path d="M40 48 l8 8 l8 -8" stroke="%s" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % GR)
# 12 button16 甲方报表: 表格+绿色对勾
ICONS["report"] = svg(
'<rect x="8" y="10" width="48" height="44" rx="3" %s stroke="%s"/>' % (W, G) +
'<line x1="8" y1="22" x2="56" y2="22" %s stroke="%s"/>' % (W, G) +
'<line x1="8" y1="34" x2="56" y2="34" %s stroke="%s"/>' % (W, G) +
'<line x1="24" y1="22" x2="24" y2="54" %s stroke="%s"/>' % (W, G) +
'<line x1="40" y1="22" x2="40" y2="54" %s stroke="%s"/>' % (W, G) +
'<circle cx="47" cy="45" r="12" fill="%s"/>' % GR +
'<path d="M41 45 l5 5 l8 -9" stroke="#FFFFFF" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>')
# 13 button15 调母排: 三根铜排+调节箭头
ICONS["busbar"] = svg(
'<rect x="8" y="14" width="48" height="8" rx="3" fill="%s"/>' % CU +
'<rect x="8" y="30" width="48" height="8" rx="3" fill="%s" opacity="0.85"/>' % CU +
'<rect x="8" y="46" width="34" height="8" rx="3" fill="%s" opacity="0.7"/>' % CU +
'<path d="M46 56 l6 -6 l6 6" stroke="%s" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % G)
# 14 button13 调系数: 双滑块调节
ICONS["factor"] = svg(
'<line x1="12" y1="22" x2="52" y2="22" %s stroke="%s"/>' % (W, G) +
'<line x1="12" y1="42" x2="52" y2="42" %s stroke="%s"/>' % (W, G) +
'<circle cx="40" cy="22" r="7" fill="%s" stroke="%s" stroke-width="3"/>' % (GR, GR) +
'<circle cx="24" cy="42" r="7" fill="%s" stroke="%s" stroke-width="3"/>' % (BL, BL))
# 15 button33 元件排序: 列表+上下箭头
ICONS["sort-components"] = svg(
'<circle cx="16" cy="16" r="4" fill="%s"/>' % G +
'<circle cx="16" cy="32" r="4" fill="%s"/>' % G +
'<circle cx="16" cy="48" r="4" fill="%s"/>' % G +
'<line x1="26" y1="16" x2="44" y2="16" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % G +
'<line x1="26" y1="32" x2="40" y2="32" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % G +
'<line x1="26" y1="48" x2="36" y2="48" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % G +
'<path d="M50 20 l6 -6 l6 6" stroke="%s" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % GR +
'<path d="M50 44 l6 6 l6 -6" stroke="%s" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % GR)
# 16 button29 模板费用: 模板网格+¥
ICONS["template-cost"] = svg(
'<rect x="8" y="8" width="48" height="48" rx="4" %s stroke="%s"/>' % (W, G) +
'<line x1="8" y1="24" x2="56" y2="24" %s stroke="%s"/>' % (W, G) +
'<line x1="8" y1="40" x2="36" y2="40" %s stroke="%s"/>' % (W, G) +
'<line x1="30" y1="24" x2="30" y2="56" %s stroke="%s"/>' % (W, G) +
'<circle cx="47" cy="45" r="13" fill="%s"/>' % GR +
'<text x="47" y="53" font-family="Segoe UI, Arial" font-size="20" font-weight="bold" fill="#FFFFFF" text-anchor="middle">¥</text>')
# 17 button11 CAD扒图: 齿轮+放大镜
ICONS["cad-grab"] = svg(
'<circle cx="26" cy="26" r="9" %s stroke="%s"/>' % (W, G) +
'<path d="M26 8 v6 M26 38 v6 M8 26 h6 M38 26 h6 M13 13 l4.5 4.5 M34.5 34.5 L39 39 M39 13 l-4.5 4.5 M17.5 34.5 L13 39" '
'stroke="%s" stroke-width="5" stroke-linecap="round"/>' % G +
'<circle cx="44" cy="44" r="10" %s stroke="%s"/>' % (W, GR) +
'<line x1="51" y1="51" x2="58" y2="58" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % GR)
# 18 btn登录: 人形+门框
ICONS["login"] = svg(
'<path d="M40 8 h14 v48 h-14" %s stroke="%s"/>' % (W, G) +
'<circle cx="22" cy="24" r="8" %s stroke="%s"/>' % (W, GR) +
'<path d="M8 52 a14 14 0 0 1 28 0" %s stroke="%s"/>' % (W, GR) +
'<line x1="48" y1="32" x2="48" y2="36" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % G)
# 19 btn修改密码: 锁+钥匙孔
ICONS["password"] = svg(
'<rect x="14" y="28" width="36" height="28" rx="4" %s stroke="%s"/>' % (W, GR) +
'<path d="M22 28 v-8 a10 10 0 0 1 20 0 v8" %s stroke="%s"/>' % (W, GR) +
'<circle cx="32" cy="40" r="4" fill="%s"/>' % GR +
'<line x1="32" y1="44" x2="32" y2="50" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % GR)
# 20 btn会员信息: 人像卡片
ICONS["member"] = svg(
'<rect x="8" y="14" width="48" height="36" rx="4" %s stroke="%s"/>' % (W, G) +
'<circle cx="24" cy="30" r="6" %s stroke="%s"/>' % (W, GR) +
'<path d="M14 46 a10 10 0 0 1 20 0" %s stroke="%s"/>' % (W, GR) +
'<line x1="40" y1="26" x2="50" y2="26" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % G +
'<line x1="40" y1="36" x2="50" y2="36" stroke="%s" stroke-width="4" stroke-linecap="round"/>' % G)
# 21 btn企业信息: 办公楼
ICONS["company"] = svg(
'<rect x="12" y="10" width="28" height="46" %s stroke="%s"/>' % (W, G) +
'<rect x="40" y="26" width="14" height="30" %s stroke="%s"/>' % (W, GR) +
'<line x1="19" y1="20" x2="25" y2="20" stroke="%s" stroke-width="3.5" stroke-linecap="round"/>' % G +
'<line x1="31" y1="20" x2="37" y2="20" stroke="%s" stroke-width="3.5" stroke-linecap="round"/>' % G +
'<line x1="19" y1="30" x2="25" y2="30" stroke="%s" stroke-width="3.5" stroke-linecap="round"/>' % G +
'<line x1="31" y1="30" x2="37" y2="30" stroke="%s" stroke-width="3.5" stroke-linecap="round"/>' % G +
'<line x1="19" y1="40" x2="25" y2="40" stroke="%s" stroke-width="3.5" stroke-linecap="round"/>' % G +
'<line x1="31" y1="40" x2="37" y2="40" stroke="%s" stroke-width="3.5" stroke-linecap="round"/>' % G +
'<rect x="22" y="48" width="8" height="8" %s stroke="%s"/>' % (W, G))
# 22 btn保存布局: 网格布局+绿色对勾
ICONS["save-layout"] = svg(
'<rect x="8" y="8" width="48" height="48" rx="4" %s stroke="%s"/>' % (W, G) +
'<line x1="8" y1="26" x2="56" y2="26" %s stroke="%s"/>' % (W, G) +
'<line x1="30" y1="26" x2="30" y2="56" %s stroke="%s"/>' % (W, G) +
'<rect x="12" y="12" width="14" height="10" fill="%s" opacity="0.85"/>' % GR +
'<circle cx="45" cy="43" r="12" fill="%s"/>' % GR +
'<path d="M39 43 l5 5 l8 -9" stroke="#FFFFFF" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>')
# 23 btn备份: 数据库+向右箭头
ICONS["backup"] = svg(
'<ellipse cx="24" cy="16" rx="14" ry="6" %s stroke="%s"/>' % (W, G) +
'<path d="M10 16 v30 a14 6 0 0 0 28 0 v-30" %s stroke="%s"/>' % (W, G) +
'<path d="M10 31 a14 6 0 0 0 28 0" %s stroke="%s"/>' % (W, G) +
'<line x1="40" y1="40" x2="56" y2="40" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % GR +
'<path d="M50 34 l8 6 l-8 6" stroke="%s" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % GR)
# 24 btn恢复: 数据库+向左回箭头
ICONS["restore"] = svg(
'<ellipse cx="40" cy="16" rx="14" ry="6" %s stroke="%s"/>' % (W, GR) +
'<path d="M26 16 v30 a14 6 0 0 0 28 0 v-30" %s stroke="%s"/>' % (W, GR) +
'<path d="M26 31 a14 6 0 0 0 28 0" %s stroke="%s"/>' % (W, GR) +
'<line x1="24" y1="40" x2="8" y2="40" stroke="%s" stroke-width="5" stroke-linecap="round"/>' % G +
'<path d="M14 34 l-8 6 l8 6" stroke="%s" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>' % G)
# 按钮 Name → 图标文件映射(与 Designer.cs 集合一致)
BIND = {
"button1": "new-project", "button2": "edit-project", "button3": "delete-project",
"button4": "open-project", "button5": "copy-project", "button7": "export-project",
"button8": "import-project", "button9": "open-quote", "button10": "edit-quote",
"button17": "save", "button18": "import-quote", "button16": "report",
"button15": "busbar", "button13": "factor", "button33": "sort-components",
"button29": "template-cost", "button11": "cad-grab",
"btn登录": "login", "btn修改密码": "password", "btn会员信息": "member",
"btn企业信息": "company", "btn保存布局": "save-layout",
"btn备份": "backup", "btn恢复": "restore",
}
def render_all():
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
os.makedirs(SVG_DIR, exist_ok=True)
os.makedirs(PNG_DIR, exist_ok=True)
ok, fail = 0, []
for name, body in ICONS.items():
sp = os.path.join(SVG_DIR, name + ".svg")
with io.open(sp, "w", encoding="utf-8") as f:
f.write(svg(body) if not body.startswith("<svg") else body)
try:
d = svg2rlg(sp)
# 64x64 输出(svglib 默认按 72dpi 走 1:1,viewBox 64 → 64px)
pp = os.path.join(PNG_DIR, name + ".png")
renderPM.drawToFile(d, pp, fmt="PNG", dpi=72, bg=0x00000000)
ok += 1
except Exception as ex:
fail.append((name, str(ex)[:120]))
print("PNG 生成: %d 成功 / %d 失败, 输出 %s" % (ok, len(fail), PNG_DIR))
for n, e in fail:
print(" [失败]", n, e)
if __name__ == "__main__":
render_all()
+118
View File
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
"""功能区 24 图标最终版:Lucide 开源图标库(ISC 可商用,专业设计)。
管线:jsdelivr 拉取 lucide-static SVG → 替换 currentColor 为语义色 → Edge 无头渲染 256px → 缩 64px。
文件名不变(bin\\Debug\\icons\\*.png),Designer.cs 的 FromFile 路径不用改。
404 的图标名会打印出来供换名重跑。"""
import io
import os
import re
import shutil
import subprocess
import tempfile
from PIL import Image
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
PNG_DIR = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons")
SVG_DIR = os.path.join(ROOT, "成套报价软件", "icons", "svg", "lucide")
EDGE = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
VER = "0.525.0"
URL = "https://cdn.jsdelivr.net/npm/lucide-static@%s/icons/%%s.svg" % VER
INK = "#3D434A" # 墨青灰(默认)
GRN = "#217346" # 强调绿(=功能区 AccentColor)
COP = "#C8791E" # 铜橙
RED = "#D64541" # 警示红
BLU = "#2B579A" # 点缀蓝
# 按钮 → (lucide 图标名, 颜色)
BIND = {
"new-project": ("file-plus-2", INK),
"edit-project": ("file-pen", INK),
"delete-project": ("trash-2", RED),
"open-project": ("folder-open", INK),
"copy-project": ("files", INK),
"import-project": ("import", GRN),
"export-project": ("export", BLU),
"open-quote": ("folder-open", INK), # button9 同款
"edit-quote": ("file-pen", INK), # button10 同款
"save": ("save", GRN),
"import-quote": ("file-down", GRN),
"report": ("table-cells", INK),
"busbar": ("align-justify", COP), # 三横线=三根铜排
"factor": ("sliders-horizontal", INK),
"sort-components": ("arrow-up-narrow-wide", INK),
"template-cost": ("badge-japanese-yen", GRN),
"cad-grab": ("drafting-compass", INK),
"login": ("log-in", GRN),
"password": ("key-round", INK),
"member": ("id-card", INK),
"company": ("building-2", INK),
"save-layout": ("layout-template", INK),
"backup": ("database-backup", GRN),
"restore": ("archive-restore", INK),
}
def fetch(name):
import urllib.request
req = urllib.request.Request(URL % name, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=30) as r:
return r.read().decode("utf-8")
def process(svg_text, color):
# 去注释/class,currentColor→语义色,尺寸改 256(viewBox 24 不动)
svg_text = re.sub(r"<!--.*?-->\s*", "", svg_text, flags=re.S)
svg_text = svg_text.replace('class="lucide lucide-%s"' % "", "")
svg_text = re.sub(r'\s+class="[^"]*"', "", svg_text)
svg_text = svg_text.replace('stroke="currentColor"', 'stroke="%s"' % color)
svg_text = svg_text.replace('width="24"', 'width="256"').replace('height="24"', 'height="256"')
return svg_text
def render_edge(name, svg_text):
sp = os.path.join(SVG_DIR, name + ".svg")
with io.open(sp, "w", encoding="utf-8") as f:
f.write(svg_text)
tmp = tempfile.mkdtemp(prefix="lucide_")
try:
tmp_svg = os.path.join(tmp, "i.svg")
tmp_png = os.path.join(tmp, "i.png")
shutil.copy(sp, tmp_svg)
subprocess.run(
[EDGE, "--headless", "--disable-gpu", "--no-sandbox",
"--user-data-dir=" + os.path.join(tmp, "p"),
"--screenshot=" + tmp_png, "--window-size=256,256",
"--default-background-color=00000000",
"file:///" + tmp_svg.replace("\\", "/")],
capture_output=True, timeout=40)
if not os.path.exists(tmp_png):
raise RuntimeError("Edge 渲染失败")
Image.open(tmp_png).convert("RGBA").resize((64, 64), Image.LANCZOS)\
.save(os.path.join(PNG_DIR, name + ".png"))
finally:
shutil.rmtree(tmp, ignore_errors=True)
def main():
os.makedirs(SVG_DIR, exist_ok=True)
os.makedirs(PNG_DIR, exist_ok=True)
ok, missing = 0, []
for name, (icon, color) in BIND.items():
try:
raw = fetch(icon)
if "<svg" not in raw:
raise RuntimeError("非 SVG 响应")
render_edge(name, process(raw, color))
ok += 1
print(" ✓ %-18s <- %s" % (name, icon))
except Exception as ex:
missing.append((name, icon, str(ex)[:80]))
print("\n完成 %d/%d" % (ok, len(BIND)))
for n, i, e in missing:
print(" [失败] %s <- %s: %s" % (n, i, e))
if __name__ == "__main__":
main()
+269
View File
@@ -0,0 +1,269 @@
# -*- coding: utf-8 -*-
"""功能区 24 个按钮图标:Pillow 等价绘制(本机无 cairo,SVG 无法栅格化——
icons/svg/*.svg 为设计稿存档,本脚本按同一几何画 256x256 超采样 → 64x64 透明 PNG)。
输出 bin\\Debug\\icons\\*.png,文件名与 make_ribbon_icons.py 的 BIND 映射一致。"""
import os
from PIL import Image, ImageDraw, ImageFont
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
OUT = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons")
SVG_DIR = os.path.join(ROOT, "成套报价软件", "icons", "svg")
S = 4 # 256 = 64*4 超采样
G = (74, 74, 74, 255) # 主体深灰
GR = (33, 115, 70, 255) # 强调绿
RD = (216, 57, 49, 255) # 删除红
CU = (200, 121, 30, 255) # 铜排橙
BL = (43, 87, 154, 255) # 点缀蓝
WH = (255, 255, 255, 255)
PGL = (232, 242, 236, 255) # 淡绿底
def L(v): # 64 坐标 → 256(取整,Pillow 不收浮点)
return int(round(v * S))
def line(d, pts, color, w=4):
d.line([(L(x), L(y)) for x, y in pts], fill=color, width=L(w), joint="curve")
def rrect(d, x, y, w2, h2, r, color, width=4, fill=None):
d.rounded_rectangle([L(x), L(y), L(x + w2), L(y + h2)], radius=L(r),
outline=color, width=L(width), fill=fill)
def rect(d, x, y, w2, h2, color, width=4, fill=None):
d.rectangle([L(x), L(y), L(x + w2), L(y + h2)], outline=color, width=L(width), fill=fill)
def circle(d, cx, cy, r, color=None, width=4, fill=None):
d.ellipse([L(cx - r), L(cy - r), L(cx + r), L(cy + r)],
outline=color, width=L(width) if color else 0, fill=fill)
def ell(d, cx, cy, rx, ry, color, width=4, fill=None):
d.ellipse([L(cx - rx), L(cy - ry), L(cx + rx), L(cy + ry)], outline=color,
width=L(width), fill=fill)
def arc(d, cx, cy, r, a1, a2, color, width=4):
d.arc([L(cx - r), L(cy - r), L(cx + r), L(cy + r)], start=a1, end=a2,
fill=color, width=L(width))
def doc_outline(d, x=14, y=8, w2=30, h2=44, color=G):
"""折角文档轮廓"""
line(d, [(x, y), (x + w2 - 10, y), (x + w2, y + 10), (x + w2, y + h2),
(x, y + h2), (x, y)], color)
def poly(d, pts, color, width=4, fill=None, close=True):
p = [(L(x), L(y)) for x, y in pts]
if fill:
d.polygon(p, fill=fill)
if close:
p = p + [p[0]]
d.line(p, fill=color, width=L(width), joint="curve")
# ============ 24 个图标(与 icons/svg 设计稿同几何) ============
def i_new_project(d):
doc_outline(d)
line(d, [(18, 24), (34, 24)], G)
line(d, [(18, 32), (30, 32)], G)
circle(d, 46, 44, 13, fill=GR)
line(d, [(46, 37), (46, 51)], WH)
line(d, [(39, 44), (53, 44)], WH)
def i_edit_project(d):
doc_outline(d)
line(d, [(18, 20), (28, 20)], G)
line(d, [(18, 28), (26, 28)], G)
poly(d, [(36, 46), (52, 30), (58, 36), (42, 52), (34, 54)], (156, 90, 16, 255), width=2, fill=CU)
line(d, [(49, 33), (55, 39)], (156, 90, 16, 255), w=2)
def i_delete_project(d):
poly(d, [(16, 20), (48, 20), (45, 56), (23, 56)], RD, width=4)
line(d, [(12, 20), (52, 20)], RD, w=5)
line(d, [(26, 20), (26, 14)], RD)
line(d, [(38, 20), (38, 14)], RD)
line(d, [(26, 30), (27, 50)], RD)
line(d, [(38, 30), (37, 50)], RD)
def i_open_project(d):
poly(d, [(8, 16), (22, 16), (27, 22), (48, 22), (48, 30), (8, 30)], G, width=4)
poly(d, [(10, 30), (56, 30), (50, 50), (14, 50)], GR, width=4)
line(d, [(18, 38), (44, 38)], (150, 190, 168, 255), w=3)
def i_copy_project(d):
rrect(d, 22, 8, 28, 38, 3, G)
poly(d, [(14, 18), (14, 54), (42, 54)], GR, width=4, close=False)
line(d, [(29, 18), (43, 18)], G, w=3)
line(d, [(29, 26), (43, 26)], G, w=3)
line(d, [(29, 34), (38, 34)], G, w=3)
def _box_u(d, color):
"""开口容器:U 形(左、底、右边,顶部开放)"""
line(d, [(10, 36), (10, 50), (16, 56), (48, 56), (54, 50), (54, 36)], color, w=4)
def i_import_project(d):
_box_u(d, GR)
line(d, [(32, 6), (32, 26)], GR, w=5)
poly(d, [(24, 20), (32, 28), (40, 20)], GR, width=5, close=False)
def i_export_project(d):
_box_u(d, BL)
line(d, [(32, 26), (32, 6)], BL, w=5)
poly(d, [(24, 12), (32, 4), (40, 12)], BL, width=5, close=False)
def i_open_quote(d):
poly(d, [(8, 14), (22, 14), (27, 20), (48, 20), (48, 28), (8, 28)], G, width=4)
rrect(d, 26, 10, 18, 24, 2, GR, width=3, fill=PGL)
line(d, [(30, 17), (40, 17)], GR, w=3)
line(d, [(30, 24), (36, 24)], GR, w=3)
poly(d, [(10, 28), (56, 28), (50, 48), (14, 48)], G, width=4)
def i_edit_quote(d):
doc_outline(d)
line(d, [(18, 20), (28, 20)], G)
circle(d, 24, 46, 11, fill=GR)
poly(d, [(34, 42), (48, 28), (54, 34), (40, 48), (33, 50)], (156, 90, 16, 255), width=2, fill=CU)
def i_save(d):
poly(d, [(10, 10), (46, 10), (54, 18), (54, 54), (10, 54)], GR, width=4)
rect(d, 20, 10, 20, 14, GR, width=4)
rect(d, 20, 36, 24, 18, GR, width=4)
line(d, [(38, 12), (38, 20)], GR)
def i_import_quote(d):
rrect(d, 10, 8, 30, 44, 3, G)
line(d, [(17, 18), (33, 18)], G)
line(d, [(17, 26), (29, 26)], G)
line(d, [(48, 30), (48, 54)], GR, w=5)
poly(d, [(40, 48), (48, 56), (56, 48)], GR, width=5, close=False)
def i_report(d):
rrect(d, 8, 10, 48, 44, 3, G)
line(d, [(8, 22), (56, 22)], G)
line(d, [(8, 34), (56, 34)], G)
line(d, [(24, 22), (24, 54)], G)
line(d, [(40, 22), (40, 54)], G)
circle(d, 47, 45, 12, fill=GR)
poly(d, [(41, 45), (46, 50), (54, 41)], WH, width=4, close=False)
def i_busbar(d):
# 三根铜排(fill 直接画,深浅表现层次)+右下调节箭头
d.rounded_rectangle([L(8), L(14), L(56), L(22)], radius=L(3), fill=CU)
d.rounded_rectangle([L(8), L(30), L(56), L(38)], radius=L(3), fill=(214, 149, 74, 255))
d.rounded_rectangle([L(8), L(46), L(42), L(54)], radius=L(3), fill=(227, 177, 118, 255))
poly(d, [(48, 58), (54, 52), (60, 58)], G, width=4, close=False)
def i_factor(d):
line(d, [(12, 22), (52, 22)], G)
line(d, [(12, 42), (52, 42)], G)
circle(d, 40, 22, 7, GR, width=3, fill=GR)
circle(d, 24, 42, 7, BL, width=3, fill=BL)
def i_sort_components(d):
circle(d, 16, 16, 4, fill=G)
circle(d, 16, 32, 4, fill=G)
circle(d, 16, 48, 4, fill=G)
line(d, [(26, 16), (44, 16)], G)
line(d, [(26, 32), (40, 32)], G)
line(d, [(26, 48), (36, 48)], G)
poly(d, [(50, 20), (56, 14), (62, 20)], GR, width=4, close=False)
poly(d, [(50, 44), (56, 50), (62, 44)], GR, width=4, close=False)
def i_template_cost(d):
rrect(d, 8, 8, 48, 48, 4, G)
line(d, [(8, 24), (56, 24)], G)
line(d, [(8, 40), (36, 40)], G)
line(d, [(30, 24), (30, 56)], G)
circle(d, 47, 45, 13, fill=GR)
try:
font = ImageFont.truetype("msyhbd.ttc", L(22))
except Exception:
font = ImageFont.truetype("arialbd.ttf", L(22))
d.text((L(47), L(45)), "¥", font=font, fill=WH, anchor="mm")
def i_cad_grab(d):
circle(d, 26, 26, 9, G)
for pts in [[(26, 8), (26, 14)], [(26, 38), (26, 44)], [(8, 26), (14, 26)],
[(38, 26), (44, 26)], [(13, 13), (17.5, 17.5)], [(34.5, 34.5), (39, 39)],
[(39, 13), (34.5, 17.5)], [(17.5, 34.5), (13, 39)]]:
line(d, pts, G, w=5)
circle(d, 44, 44, 10, GR)
line(d, [(51, 51), (58, 58)], GR, w=5)
def i_login(d):
poly(d, [(40, 8), (54, 8), (54, 56), (40, 56)], G, width=4, close=False)
circle(d, 22, 24, 8, GR)
arc(d, 22, 48, 14, 180, 360, GR)
line(d, [(48, 32), (48, 36)], G)
def i_password(d):
rrect(d, 14, 28, 36, 28, 4, GR)
arc(d, 32, 28, 10, 180, 360, GR)
line(d, [(22, 28), (22, 20)], GR)
line(d, [(42, 28), (42, 20)], GR)
circle(d, 32, 40, 4, fill=GR)
line(d, [(32, 44), (32, 50)], GR)
def i_member(d):
rrect(d, 8, 14, 48, 36, 4, G)
circle(d, 24, 30, 6, GR)
arc(d, 24, 43, 10, 180, 360, GR)
line(d, [(40, 26), (50, 26)], G)
line(d, [(40, 36), (50, 36)], G)
def i_company(d):
rect(d, 12, 10, 28, 46, G)
rect(d, 40, 26, 14, 30, GR)
for y in (20, 30, 40):
line(d, [(19, y), (25, y)], G, w=3.5)
line(d, [(31, y), (37, y)], G, w=3.5)
rect(d, 22, 48, 8, 8, G)
def i_save_layout(d):
rrect(d, 8, 8, 48, 48, 4, G)
line(d, [(8, 26), (56, 26)], G)
line(d, [(30, 26), (30, 56)], G)
d.rectangle([L(12), L(12), L(26), L(22)], fill=(150, 190, 168, 255))
circle(d, 45, 43, 12, fill=GR)
poly(d, [(39, 43), (44, 48), (52, 39)], WH, width=4, close=False)
def _db(d, cx, color):
ell(d, cx, 16, 14, 6, color)
line(d, [(cx - 14, 16), (cx - 14, 46)], color)
line(d, [(cx + 14, 16), (cx + 14, 46)], color)
arc(d, cx, 40, 14, 0, 180, color)
arc(d, cx, 25, 14, 0, 180, color)
def i_backup(d):
_db(d, 24, G)
line(d, [(40, 40), (56, 40)], GR, w=5)
poly(d, [(50, 34), (58, 40), (50, 46)], GR, width=5, close=False)
def i_restore(d):
_db(d, 40, GR)
line(d, [(24, 40), (8, 40)], G, w=5)
poly(d, [(14, 34), (6, 40), (14, 46)], G, width=5, close=False)
ICONS = {
"new-project": i_new_project, "edit-project": i_edit_project,
"delete-project": i_delete_project, "open-project": i_open_project,
"copy-project": i_copy_project, "export-project": i_export_project,
"import-project": i_import_project, "open-quote": i_open_quote,
"edit-quote": i_edit_quote, "save": i_save, "import-quote": i_import_quote,
"report": i_report, "busbar": i_busbar, "factor": i_factor,
"sort-components": i_sort_components, "template-cost": i_template_cost,
"cad-grab": i_cad_grab, "login": i_login, "password": i_password,
"member": i_member, "company": i_company, "save-layout": i_save_layout,
"backup": i_backup, "restore": i_restore,
}
def main():
os.makedirs(OUT, exist_ok=True)
ok = 0
for name, fn in ICONS.items():
img = Image.new("RGBA", (64 * S, 64 * S), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
fn(d)
img = img.resize((64, 64), Image.LANCZOS)
img.save(os.path.join(OUT, name + ".png"))
ok += 1
print("Pillow 图标生成 %d 个 → %s" % (ok, OUT))
if __name__ == "__main__":
main()
+290
View File
@@ -0,0 +1,290 @@
# -*- coding: utf-8 -*-
"""功能区 24 图标(SVG 正式版):手写 SVG 设计稿 → Edge 无头渲染 256px 透明 PNG → 缩 64px。
设计原则(frontend-design 技能):视觉词汇取自电气成套行业(图纸/柜体/铜排/元件符号);
主描边 3px 圆角端点;每个图标 = 墨青灰线性主形 + 1 个实色焦点(绿/铜/红/蓝)。
调色:墨青灰#3D434A 强调绿#217346(=功能区AccentColor) 铜橙#C8791E 警示红#D64541 点缀蓝#2B579A
输出: bin\\Debug\\icons\\*.png(文件名不变,Designer.cs FromFile 路径不用改) + icons\\svg\\*.svg 设计稿。"""
import os
import io
import subprocess
import tempfile
import shutil
from PIL import Image
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
PNG_DIR = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons")
SVG_DIR = os.path.join(ROOT, "成套报价软件", "icons", "svg")
EDGE = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
INK = "#3D434A" # 墨青灰
GRN = "#217346" # 强调绿
COP = "#C8791E" # 铜橙
RED = "#D64541" # 警示红
BLU = "#2B579A" # 点缀蓝
PGR = "#E5F0EA" # 淡绿底
LGR = "#9DBFAA" # 灰绿(弱线)
def S(body, stroke=INK, w=3):
"""外层 svg: 256 输出 / 64 网格"""
return ('<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 64 64">\n'
+ body + "\n</svg>")
def L(pts, c=INK, w=3):
p = " ".join("%g %g" % (x, y) for x, y in pts)
return '<polyline points="%s" fill="none" stroke="%s" stroke-width="%g" stroke-linecap="round" stroke-linejoin="round"/>' % (p, c, w)
def R(x, y, w2, h2, rx, c=INK, sw=3, fill="none"):
f = ' fill="%s"' % fill
s = ' stroke="%s" stroke-width="%g"' % (c, sw) if sw else ""
return '<rect x="%g" y="%g" width="%g" height="%g" rx="%g"%s%s/>' % (x, y, w2, h2, rx, f, s)
def C(cx, cy, r, c=INK, sw=3, fill="none"):
f = ' fill="%s"' % fill
s = ' stroke="%s" stroke-width="%g"' % (c, sw) if sw else ""
return '<circle cx="%g" cy="%g" r="%g"%s%s/>' % (cx, cy, r, f, s)
def E(cx, cy, rx, ry, c=INK, sw=3, fill="none"):
return '<ellipse cx="%g" cy="%g" rx="%g" ry="%g" fill="%s" stroke="%s" stroke-width="%g"/>' % (cx, cy, rx, ry, fill, c, sw)
def P(d, c=INK, w=3, fill="none"):
s = ' stroke="%s" stroke-width="%g"' % (c, w) if w else ""
f = ' fill="%s"' % fill
return '<path d="%s"%s%s stroke-linecap="round" stroke-linejoin="round"/>' % (d, f, s)
def T(x, y, txt, size, c="#FFFFFF", weight="bold"):
return ('<text x="%g" y="%g" font-family="Segoe UI, Microsoft YaHei, Arial" font-size="%g" '
'font-weight="%s" fill="%s" text-anchor="middle" dominant-baseline="central">%s</text>'
% (x, y, size, weight, c, txt))
# ---- 图标定义(64 网格) ----
ICONS = {}
# 1 新建项目:完整折角图纸 + 右下绿加号徽标(白色隔离圈防穿插)
ICONS["new-project"] = S(
P("M12 6 H34 L46 18 V36 H12 Z") +
L([(18, 14), (28, 14)], INK, 2.25) + L([(18, 21), (34, 21)], INK, 2.25) +
L([(18, 28), (31, 28)], INK, 2.25) +
C(46, 46, 15, "#FFFFFF", 0, fill="#FFFFFF") +
C(46, 46, 12.5, GRN, 0, fill=GRN) +
L([(46, 40.5), (46, 51.5)], "#FFFFFF", 3) + L([(40.5, 46), (51.5, 46)], "#FFFFFF", 3))
# 2 修改项目:完整图纸 + 45° 铅笔(白色隔离层分离,规整几何)
def _edit_body():
return (
P("M10 6 H32 L42 16 V32 H10 Z") +
L([(16, 14), (26, 14)], INK, 2.25) + L([(16, 21), (32, 21)], INK, 2.25) +
L([(16, 28), (30, 28)], INK, 2.25) +
# 铅笔:白色 gap 层(同形放大) → 彩色层。rotate(45) 后笔尖朝左下。
'<g transform="translate(45,45) rotate(45)">'
+ '<g stroke="#FFFFFF" stroke-width="9" stroke-linejoin="round" fill="#FFFFFF">'
'<rect x="-5" y="-17" width="12" height="24" rx="2"/>'
'<path d="M-5 7 L1 16.5 L7 7 Z"/></g>'
+ '<rect x="-5" y="-17" width="12" height="24" rx="2" fill="#C8791E" stroke="#8A4F0B" stroke-width="2"/>'
+ '<path d="M-5 7 L1 16.5 L7 7 Z" fill="#F6D7AC" stroke="#8A4F0B" stroke-width="2"/>'
+ '<rect x="-5" y="-17" width="12" height="6" rx="2" fill="#E58E3A" stroke="#8A4F0B" stroke-width="2"/>'
+ '</g>')
ICONS["edit-project"] = S(_edit_body())
# 3 删除项目:红色垃圾桶
ICONS["delete-project"] = S(
P("M17 20 H47 L44.3 52 A4 4 0 0 1 40.3 56 H23.7 A4 4 0 0 1 19.7 52 Z", RED, 3) +
L([(13, 20), (51, 20)], RED, 3.5) +
L([(26, 20), (26, 15)], RED, 3) + L([(38, 20), (38, 15)], RED, 3) +
L([(26.5, 28), (27.5, 48)], RED, 2.5) + L([(37.5, 28), (36.5, 48)], RED, 2.5))
# 4 打开项目:标准两层文件夹(后板墨青线,前板圆角底+淡绿填充,白色 gap 分离)
def _open_body():
return (
P("M8 13 H23 L27.5 18.5 H51 A3.5 3.5 0 0 1 54.5 22 V27 H8 Z") +
P("M6.5 31 H57.5 L52.9 46.2 A5.2 5.2 0 0 1 47.9 50.2 H11.1 A5.2 5.2 0 0 1 6.1 45.2 Z",
"#FFFFFF", 8, fill="#FFFFFF") +
P("M6.5 31 H57.5 L52.9 46.2 A5.2 5.2 0 0 1 47.9 50.2 H11.1 A5.2 5.2 0 0 1 6.1 45.2 Z",
GRN, 3, fill=PGR))
ICONS["open-project"] = S(_open_body())
# 5 复制项目:双层图纸,后层绿
ICONS["copy-project"] = S(
P("M22 8 H44 V44 H22 Z") +
P("M14 16 V54 H36", GRN, 3) +
L([(28, 16), (38, 16)], INK, 2.25) + L([(28, 24), (38, 24)], INK, 2.25) +
L([(28, 32), (34, 32)], INK, 2.25))
# 6 导入项目:托盘 + 绿下箭头
ICONS["import-project"] = S(
P("M10 34 V48 A6 6 0 0 0 16 54 H48 A6 6 0 0 0 54 48 V34", GRN, 3.5) +
L([(32, 6), (32, 26)], GRN, 4) +
P("M23 19 L32 28 L41 19", GRN, 4))
# 7 导出项目:托盘 + 蓝上箭头
ICONS["export-project"] = S(
P("M10 34 V48 A6 6 0 0 0 16 54 H48 A6 6 0 0 0 54 48 V34", BLU, 3.5) +
L([(32, 26), (32, 6)], BLU, 4) +
P("M23 13 L32 4 L41 13", BLU, 4))
# 8 打开项目(报价页):同语义,与项目页"打开项目"同款图形
ICONS["open-quote"] = S(_open_body())
# 9 修改项目(报价页):同语义,与项目页"修改项目"同款图形
ICONS["edit-quote"] = S(_edit_body())
# 10 保存:绿色软盘
ICONS["save"] = S(
P("M10 10 H44 L54 20 V54 H10 Z", GRN, 3.5) +
R(22, 10, 18, 14, 1, GRN, 2.5) +
L([(35, 12), (35, 20)], GRN, 2.5) +
R(18, 36, 28, 18, 1, GRN, 2.5))
# 11 导入报价:报价单 + 绿下箭头
ICONS["import-quote"] = S(
R(10, 8, 30, 44, 2) +
L([(17, 18), (33, 18)], INK, 2.25) + L([(17, 25), (29, 25)], INK, 2.25) +
T(24, 37, "¥", 12, LGR, "600") +
L([(48, 30), (48, 53)], GRN, 4) +
P("M40 47 L48 55 L56 47", GRN, 4))
# 12 甲方报表:表格 + 绿勾角标
ICONS["report"] = S(
R(8, 10, 48, 44, 3) +
L([(8, 22), (56, 22)], INK, 2.5) + L([(8, 33), (56, 33)], INK, 2.5) +
L([(24, 22), (24, 54)], INK, 2.5) + L([(40, 22), (40, 54)], INK, 2.5) +
C(47, 46, 11.5, fill=GRN) +
P("M41.5 46 L45.5 50 L53 41.5", "#FFFFFF", 3.5))
# 13 调母排:三根铜排(深浅层次) + 调节箭头
ICONS["busbar"] = S(
R(7, 12, 50, 9, 3, sw=0, fill=COP) +
R(7, 28, 50, 9, 3, sw=0, fill="#D69449") +
R(7, 44, 38, 9, 3, sw=0, fill="#E5B176") +
P("M46 56 L52 50 L58 56", INK, 3))
# 14 调系数:双滑杆,绿+蓝滑钮
ICONS["factor"] = S(
L([(10, 21), (54, 21)], INK, 3) + L([(10, 43), (54, 43)], INK, 3) +
C(41, 21, 7.5, GRN, 0, fill=GRN) +
C(23, 43, 7.5, BLU, 0, fill=BLU))
# 15 元件排序:三行元件符号(电阻矩形,电气符号) + 上箭头
ICONS["sort-components"] = S(
L([(9, 15), (19, 15)], INK, 2.5) + R(19, 10.5, 13, 9, 1.5, INK, 2.5) + L([(32, 15), (40, 15)], INK, 2.5) +
L([(9, 32), (19, 32)], INK, 2.5) + R(19, 27.5, 13, 9, 1.5, INK, 2.5) + L([(32, 32), (40, 32)], INK, 2.5) +
L([(9, 49), (19, 49)], INK, 2.5) + R(19, 44.5, 13, 9, 1.5, INK, 2.5) + L([(32, 49), (40, 49)], INK, 2.5) +
P("M50 24 L56 17 L62 24", GRN, 3) +
P("M50 44 L56 51 L62 44", GRN, 3))
# 16 模板费用:模板网格 + ¥ 绿徽
ICONS["template-cost"] = S(
R(7, 7, 50, 50, 4) +
L([(7, 23), (57, 23)], INK, 2.5) + L([(7, 38), (36, 38)], INK, 2.5) +
L([(28, 23), (28, 57)], INK, 2.5) +
C(47, 46, 12.5, fill=GRN) +
T(47, 46.5, "¥", 20))
# 17 CAD扒图:齿轮 + 绿放大镜
ICONS["cad-grab"] = S(
C(25, 25, 9.5, INK, 3) +
P("M25 7 V13 M25 37 V43 M7 25 H13 M37 25 H43 M12.4 12.4 L16.6 16.6 M33.4 33.4 L37.6 37.6 M37.6 12.4 L33.4 16.6 M16.6 33.4 L12.4 37.6", INK, 4) +
C(45, 45, 9.5, GRN, 3.5) +
L([(51.5, 51.5), (58, 58)], GRN, 4))
# 18 登录:门框 + 绿人形
ICONS["login"] = S(
P("M38 8 H54 V56 H38", INK, 3.5) +
C(21, 23, 8, GRN, 3) +
P("M7 52 A14 14 0 0 1 35 52", GRN, 3) +
C(47, 33, 1.8, INK, 0, fill=INK))
# 19 修改密码:绿挂锁
ICONS["password"] = S(
R(14, 28, 36, 27, 4, GRN, 3.5) +
P("M22 28 V19 A10 10 0 0 1 42 19 V28", GRN, 3.5) +
C(32, 39, 4, GRN, 0, fill=GRN) +
L([(32, 43), (32, 49)], GRN, 3))
# 20 会员信息:证件卡(人像+信息行)
ICONS["member"] = S(
R(7, 13, 50, 38, 4) +
C(23, 29, 6, GRN, 2.75) +
P("M13 45 A10 10 0 0 1 33 45", GRN, 2.75) +
L([(40, 26), (51, 26)], INK, 2.75) + L([(40, 35), (51, 35)], INK, 2.75) +
L([(16, 51), (48, 51)], LGR, 0)) # 占位(卡内底线省略)
# 21 企业信息:办公楼群(主楼+绿副楼)
ICONS["company"] = S(
R(11, 9, 27, 47, 1) +
L([(18, 19), (24, 19)], INK, 2.5) + L([(30, 19), (36, 19)], INK, 2.5) +
L([(18, 28), (24, 28)], INK, 2.5) + L([(30, 28), (36, 28)], INK, 2.5) +
L([(18, 37), (24, 37)], INK, 2.5) + L([(30, 37), (36, 37)], INK, 2.5) +
R(43, 26, 13, 30, 1, GRN, 3) +
L([(48, 34), (52, 34)], GRN, 2.25) + L([(48, 42), (52, 42)], GRN, 2.25) +
R(21, 47, 8, 9, 1, INK, 2.5))
# 22 保存布局:停靠面板示意 + 绿勾
ICONS["save-layout"] = S(
R(7, 7, 50, 50, 4) +
L([(7, 24), (57, 24)], INK, 2.5) + L([(28, 24), (28, 57)], INK, 2.5) +
R(11, 11, 13, 9, 1.5, sw=0, fill=LGR) +
C(46, 44, 11.5, fill=GRN) +
P("M40.5 44 L44.5 48 L52 39.5", "#FFFFFF", 3.5))
# 23 备份:数据库 + 绿右箭头
ICONS["backup"] = S(
E(24, 15, 13, 5.5) +
P("M11 15 V45 A13 5.5 0 0 0 37 45 V15") +
P("M11 30 A13 5.5 0 0 0 37 30", INK, 2.5) +
L([(40, 38), (55, 38)], GRN, 3.5) +
P("M50 32.5 L56.5 38 L50 43.5", GRN, 3.5))
# 24 恢复:数据库 + 墨青回转箭头
ICONS["restore"] = S(
E(40, 15, 13, 5.5, GRN) +
P("M27 15 V45 A13 5.5 0 0 0 53 45 V15", GRN) +
P("M27 30 A13 5.5 0 0 0 53 30", GRN, 2.5) +
L([(24, 38), (9, 38)], INK, 3.5) +
P("M14 32.5 L7.5 38 L14 43.5", INK, 3.5))
def render(name, body):
"""单个 SVG → Edge 截图 → 64px PNG"""
sp = os.path.join(SVG_DIR, name + ".svg")
with io.open(sp, "w", encoding="utf-8") as f:
f.write(body)
tmp = tempfile.mkdtemp(prefix="ribbon_icon_")
try:
tmp_svg = os.path.join(tmp, "i.svg")
tmp_png = os.path.join(tmp, "i.png")
shutil.copy(sp, tmp_svg)
w = subprocess.run(
[EDGE, "--headless", "--disable-gpu", "--no-sandbox",
"--user-data-dir=" + os.path.join(tmp, "profile"),
"--screenshot=" + tmp_png, "--window-size=256,256",
"--default-background-color=00000000",
"file:///" + tmp_svg.replace("\\", "/")],
capture_output=True, timeout=40)
if not os.path.exists(tmp_png):
raise RuntimeError("Edge 未产出 PNG: " + w.stderr.decode("utf-8", "ignore")[-150:])
img = Image.open(tmp_png).convert("RGBA").resize((64, 64), Image.LANCZOS)
img.save(os.path.join(PNG_DIR, name + ".png"))
finally:
shutil.rmtree(tmp, ignore_errors=True)
def main():
os.makedirs(SVG_DIR, exist_ok=True)
os.makedirs(PNG_DIR, exist_ok=True)
ok, fail = 0, []
for name, body in ICONS.items():
try:
render(name, body)
ok += 1
except Exception as ex:
fail.append((name, str(ex)[:160]))
print("SVG→PNG: %d 成功 / %d 失败" % (ok, len(fail)))
for n, e in fail:
print(" [失败]", n, e)
if __name__ == "__main__":
main()
+75
View File
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""组装更新包_20261001: 成套报价软件.exe + 使用说明.txt → 目录 + zip"""
import os
import shutil
import zipfile
ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
REL = os.path.join(ROOT, "release", "成套报价软件")
OUT = os.path.join(REL, "Output")
PKG = os.path.join(OUT, "更新包_20261001_2")
说明 = """成套报价软件 更新包 2026-10-01 第2期(覆盖式更新)
本期更新:
一、PDF 扒图缩放卡顿修复(重点)
原来滚轮缩放每档都在界面线程整页重绘,大幅面图纸一滚就卡死;
现在改为后台渲染+旧图即时拉伸过渡——缩放跟手流畅,
连续滚动只渲染最后一档,不再反复卡顿。
主程序内置查看器与 pdfbt 独立程序已同步修复。
二、批量插入修复
修复批量插入时报"列'框Handle'不属于表"异常的问题。
三、数据落库保障
1. 编辑直写队列强制落库改造:关窗/切换项目时数据完整写入数据库;
2. 全量重算改为单事务提交,重算期间数据一致性更好。
四、查询提速
项目库与方案库补齐"内部ID"索引,图号/箱号/元件查询明显提速。
五、CAD 扒图窗口修复(与 PDF 扒图同步)
1. 右键点击已选中的行不再丢失多选集合;
2. F1 合并增加防抖,不再误弹提示。
六、稳定性
后台异常不再被静默吞掉,关键失败写入日志(安装目录 logs),便于远程排查。
使用方法:
1. 完全关闭成套报价软件(含CAD/PDF扒图也退出);
2. 把本包里「成套报价软件.exe」复制到安装目录(默认 C:\\成套报价软件)覆盖;
3. 把本包里「pdfbt.exe」复制到安装目录的 pdfbt 子文件夹
(默认 C:\\成套报价软件\\pdfbt)覆盖;
4. 重新打开软件即可。
(完整安装包 成套报价软件_Setup.exe 已同步构建,全新机器请用安装包。)
"""
def main():
if os.path.isdir(PKG):
shutil.rmtree(PKG)
os.makedirs(PKG)
# 主程序
shutil.copy2(os.path.join(REL, "成套报价软件.exe"), PKG)
# PDF 扒图独立程序(2026-10-01 第2期:缩放卡顿同步修复)
shutil.copy2(os.path.join(REL, "pdfbt", "pdfbt.exe"), PKG)
# 使用说明(GBK,记事本直接打开不乱码)
with open(os.path.join(PKG, "使用说明.txt"), "w", encoding="gbk", newline="\r\n") as f:
f.write(说明)
# zip(文件夹+zip两种形态,与历史更新包一致)
zip_path = PKG + ".zip"
if os.path.exists(zip_path):
os.remove(zip_path)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
for name in os.listdir(PKG):
z.write(os.path.join(PKG, name), name)
print("更新包目录:", PKG)
print("更新包zip:", zip_path)
for name in os.listdir(PKG):
print(" -", name, os.path.getsize(os.path.join(PKG, name)), "字节")
print("zip大小:", os.path.getsize(zip_path), "字节")
if __name__ == "__main__":
main()
+322
View File
@@ -0,0 +1,322 @@
# -*- coding: utf-8 -*-
"""
事件/逻辑代码迁移:旧项目 主窗口*.cs + 对话框 → 新项目(停靠面板版)。
策略:能复制就复制,只做三类最小改动:
1) namespace MyApp → 成套报价软件;partial class 主窗口 → MainForm
2) 旧构造函数拆到 MainForm.cs(加 CreateDockWindows/Load 接线)
3) 停靠窗口控件在 MainForm 上生成公开别名属性(this.xxx 继续可用)
旧 ui.cs 尾部的 SQLite 加载方法一并带过来。
"""
import io
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import migrate_ui as mu
SRC_DIR = os.path.dirname(mu.SRC)
DST = mu.DST_PROJECT
def read(p):
return io.open(p, encoding="utf-8-sig").read()
def write(p, s):
io.open(p, "w", encoding="utf-8-sig", newline="\r\n").write(s)
def rename_common(s):
s = s.replace("namespace MyApp", "namespace 成套报价软件")
s = s.replace("public partial class 主窗口", "public partial class MainForm")
return s
# ---------------------------------------------------------------- 1) 别名文件
def gen_aliases():
text = read(mu.SRC)
decls, begin_inits, blocks, top, edges, ts_edges = mu.parse(text)
ctrl_blocks, form_block = mu.classify(blocks)
ch = mu.build_children(edges)
decl_types = dict(decls)
# MainForm 自身已有成员(避免别名冲突)
taken = set(["tabControl1", "tabPage1", "tabPage2", "tabPage3", "tabPage4", "tabPage5",
"tabPage6", "statusStrip1", "dockSurface1", "components",
"InitializeComponent", "CreateDockWindows_dockSurface1", "DockSurface1ActiveContentChanged"])
for n, _t in decls:
if n in mu.subtree("tabControl1", ch) or n in mu.subtree("statusStrip1", ch):
taken.add(n)
wfields = []
props = []
for idx, (cls, tab, title) in enumerate(mu.DOCK_MAP, 1):
members = mu.subtree(tab, ch)
members.remove(tab)
mset = set(members)
ordered = [n for n, _t in decls if n in mset]
for parent, kids in ts_edges:
if parent in mset:
ordered += [k for k in kids if k not in ordered]
wfields.append("\t\tinternal %s w%d;" % (cls, idx))
for n in ordered:
if n in taken:
print("!! 别名冲突跳过:", n)
continue
props.append("\t\tpublic %s %s { get { return w%d.%s; } }" % (decl_types[n], n, idx, n))
taken.add(n)
L = []
a = L.append
a("// 旧 主窗口 逻辑代码通过 this.<控件名> 访问控件;")
a("// 控件已迁入 9 个停靠窗口,这里用别名属性转发,逻辑代码无需改动。")
a("// 停靠窗口实例字段由 MainForm.dockSurface1.DockWindows.cs 在创建后赋值。")
a("namespace 成套报价软件")
a("{")
a("\tpublic partial class MainForm")
a("\t{")
L.extend(wfields)
a("")
L.extend(props)
a("\t}")
a("}")
write(os.path.join(DST, "MainForm.控件别名.cs"), "\n".join(L))
print("生成 MainForm.控件别名.cs:实例字段 %d 个,别名属性 %d 个" % (len(wfields), len(props)))
# ---------------------------------------------------------------- 2) 停靠窗口字段改 public
def publify_fields():
pat = re.compile(r"\t\tprivate (?!System\.ComponentModel\.IContainer)([\w.]+) ([\w\u4e00-\u9fff]+);")
for cls, _tab, _title in mu.DOCK_MAP:
p = os.path.join(DST, "DockWindows", cls, cls + ".Designer.cs")
s = read(p)
s2, n = pat.subn(r"\t\tpublic \1 \2;", s)
write(p, s2)
print("%s: %d 个字段改为 public" % (cls, n))
# ---------------------------------------------------------------- 3) 记录停靠窗口实例
def patch_dockwindows_cs():
p = os.path.join(DST, "DockWindows", "MainForm.dockSurface1.DockWindows.cs")
s = read(p)
n = 0
for idx, (cls, _tab, _title) in enumerate(mu.DOCK_MAP, 1):
old = "var w%d = new %s();" % (idx, cls)
new = old + "\n\t\t\tthis.w%d = w%d;" % (idx, idx)
if old in s and new not in s:
s = s.replace(old, new)
n += 1
write(p, s)
print("MainForm.dockSurface1.DockWindows.cs: 接线 %d 处" % n)
# ---------------------------------------------------------------- 4) MainForm.cs + 主窗口.cs
def split_main():
src = read(os.path.join(SRC_DIR, "主窗口.cs"))
lines = src.split("\n")
ctor_start = None
for i, l in enumerate(lines):
if re.match(r"\s*public 主窗口\(\)", l):
ctor_start = i
break
assert ctor_start is not None, "找不到旧构造函数"
# ctor 结束:InitSelectionPage(); 后的第一个 ' }'
ctor_end = None
for i in range(ctor_start, len(lines)):
if "InitSelectionPage();" in lines[i]:
for j in range(i, min(i + 5, len(lines))):
if lines[j].strip() == "}":
ctor_end = j
break
break
assert ctor_end is not None, "找不到构造函数结束"
usings = [l for l in lines[:ctor_start] if l.startswith("using ")]
ctor_body = lines[ctor_start + 3:ctor_end] # 跳过签名/{/InitializeComponent();
# 新 MainForm.cs
L = []
a = L.append
a("/*")
a(" * 由SharpDevelop创建。")
a(" * 用户: 099978")
a(" * 日期: 2026-08-25")
a(" *")
a(" * 主窗体构造函数(由旧项目 主窗口.cs 迁移)。")
a(" */")
L.extend(usings)
a("")
a("namespace 成套报价软件")
a("{")
a("\t/// <summary>")
a("\t/// 主窗体:构造函数迁移自旧项目 主窗口.cs(事件挂接全部保留)。")
a("\t/// </summary>")
a("\tpublic partial class MainForm : Form")
a("\t{")
a("\t\tpublic MainForm()")
a("\t\t{")
a("\t\t\tInitializeComponent();")
a("\t\t\tCreateDockWindows_dockSurface1();")
a("")
a("\t\t\t// 旧 ui.cs 设计器注入的 SQLite 数据加载(Load 事件)")
a("\t\t\tthis.Load += (s, e) => LoadAllSqliteData();")
a("")
L.extend(ctor_body)
a("")
a("\t\tvoid DockSurface1ActiveContentChanged(object sender, EventArgs e)")
a("\t\t{")
a("")
a("\t\t}")
a("\t}")
a("}")
write(os.path.join(DST, "MainForm.cs"), "\n".join(L))
# 主窗口.cs(去掉构造函数的其余逻辑)
rest = lines[:ctor_start] + [" // 构造函数已迁至 MainForm.cs", ""] + lines[ctor_end + 1:]
write(os.path.join(DST, "主窗口.cs"), rename_common("\n".join(rest)))
print("生成 MainForm.cs 与 主窗口.cs(构造函数 %d-%d 行)" % (ctor_start + 1, ctor_end + 1))
# ---------------------------------------------------------------- 5) 其余 partial 与工具类
def copy_partials():
files = ["主窗口.菜单与库.cs", "主窗口.公式重算.cs", "主窗口.母排与备份.cs", "主窗口.项目导入导出.cs", "价格格式化.cs"]
for f in files:
write(os.path.join(DST, f), rename_common(read(os.path.join(SRC_DIR, f))))
print("复制", f)
# ---------------------------------------------------------------- 6) ui.cs 尾部 SQLite 加载
def port_sqlite_tail():
lines = read(mu.SRC).split("\n")
start = None
for i, l in enumerate(lines):
if "加载所有 SQLite 设计数据绑定" in l:
start = i
break
assert start is not None
tail = lines[start:]
while tail and tail[-1].strip() in ("}", ""):
tail.pop()
L = ["// 旧 主窗口.ui.cs 尾部的 SQLite 设计数据加载方法(设计器注入,逻辑不变)。"]
L.append("namespace 成套报价软件")
L.append("{")
L.append("\tpublic partial class MainForm")
L.append("\t{")
L.extend(tail)
L.append("\t}")
L.append("}")
write(os.path.join(DST, "主窗口.SQLite加载.cs"), "\n".join(L))
print("生成 主窗口.SQLite加载.cs(%d 行)" % len(tail))
# ---------------------------------------------------------------- 7) 对话框
DIALOGS = [
("xiangmu", ["新建项目", "修改项目", "甲方报表", "调系数", "导入报价", "元件排序", "调母排"]),
("shezhi", ["企业信息"]),
("ku", ["用户库元件编辑"]),
("GCS母排计算", ["GCS母排计算窗口", "GCS推荐铜排窗口"]),
]
def copy_dialogs():
for sub, names in DIALOGS:
for name in names:
for ext in (".cs", ".ui.cs"):
s = read(os.path.join(SRC_DIR, sub, name + ext))
s = rename_common(s)
# 类型引用 主窗口 → MainForm(注释里的中文连写不受影响)
s = re.sub(r"主窗口(?=[\s\.\)\[_,;])", "MainForm", s)
dst_sub = os.path.join(DST, sub)
if not os.path.isdir(dst_sub):
os.makedirs(dst_sub)
write(os.path.join(dst_sub, name + ext), s)
print("复制", sub + "\\" + name + ext)
# ---------------------------------------------------------------- 8) csproj
CSPROJ_COMPILE = [
"主窗口.cs", "主窗口.菜单与库.cs", "主窗口.公式重算.cs", "主窗口.母排与备份.cs",
"主窗口.项目导入导出.cs", "主窗口.SQLite加载.cs", "MainForm.控件别名.cs", "价格格式化.cs",
]
for sub, names in DIALOGS:
for name in names:
CSPROJ_COMPILE.append(sub + "\\" + name + ".cs")
CSPROJ_COMPILE.append(sub + "\\" + name + ".ui.cs")
LIB_REFS = [
"System.Data.SQLite", "NPOI", "NPOI.OOXML", "NPOI.OpenXml4Net", "NPOI.OpenXmlFormats",
"DotNetZip", "ICSharpCode.SharpZipLib", "unvell.ReoGrid", "CJZZ.Controls",
]
def patch_csproj():
p = os.path.join(DST, "成套报价软件.csproj")
s = read(p)
s = s.replace("<PlatformTarget>x86</PlatformTarget>", "<PlatformTarget>x64</PlatformTarget>")
# System.Data.SQLite 从 SD bin(x86) 改为 lib\x64 版
s = re.sub(
r"<Reference Include=\"System\.Data\.SQLite\">\s*<HintPath>.*?</HintPath>\s*</Reference>",
lambda m: '<Reference Include="System.Data.SQLite">\n <HintPath>lib\\System.Data.SQLite.dll</HintPath>\n <Private>True</Private>\n </Reference>',
s, flags=re.S)
# 追加 lib 引用(插到 WeifenLuo.ThemeVS2015 引用块之后)
ref_xml = ""
for name in LIB_REFS:
ref_xml += ' <Reference Include="%s">\n <HintPath>lib\\%s.dll</HintPath>\n <Private>True</Private>\n </Reference>\n' % (name, name)
ref_xml += ' <Reference Include="System.IO.Compression" />\n <Reference Include="System.IO.Compression.FileSystem" />\n'
anchor = "</Reference>\n </ItemGroup>"
idx = s.rfind(anchor)
s = s[:idx + len("</Reference>\n")] + ref_xml + " </ItemGroup>" + s[idx + len(anchor):]
# 追加 Compile 条目
comp_xml = ""
for f in CSPROJ_COMPILE:
comp_xml += ' <Compile Include="%s" />\n' % f
anchor2 = ' <Compile Include="Program.cs" />'
s = s.replace(anchor2, comp_xml + anchor2)
write(p, s)
print("csproj 已更新(x64 + %d 引用 + %d 编译项)" % (len(LIB_REFS) + 2, len(CSPROJ_COMPILE)))
# ---------------------------------------------------------------- 9) verify_build.rsp
def gen_rsp():
L = ["# csc 编译验证(UTF-8)", "/target:winexe", "/platform:x64", "/utf8output", "/nologo", "/warn:0",
"/out:bin\\Debug\\成套报价软件.exe"]
for fw in ["System.dll", "System.Core.dll", "System.Data.dll", "System.Drawing.dll",
"System.Windows.Forms.dll", "System.Xml.dll", "System.IO.Compression.dll",
"System.IO.Compression.FileSystem.dll"]:
L.append("/r:" + fw)
for name in ["Smart.CustomComponents", "WeifenLuo.WinFormsUI.Docking", "WeifenLuo.WinFormsUI.Docking.ThemeVS2015"]:
L.append('/r:"C:\\Program Files (x86)\\SharpDevelop\\5.2\\bin\\%s.dll"' % name)
for name in ["AdvancedDataGridView", "Microsoft.Web.WebView2.Core", "Microsoft.Web.WebView2.WinForms"] + LIB_REFS:
L.append('/r:"lib\\%s.dll"' % name)
for f in ["MainForm.cs", "MainForm.Designer.cs", "Program.cs", "Properties\\AssemblyInfo.cs"] + CSPROJ_COMPILE:
L.append(f)
for cls, _t, _ti in mu.DOCK_MAP:
L.append("DockWindows\\%s\\%s.cs" % (cls, cls))
L.append("DockWindows\\%s\\%s.Designer.cs" % (cls, cls))
L.append("DockWindows\\MainForm.dockSurface1.DockWindows.cs")
write(os.path.join(HERE, "verify_build.rsp"), "\n".join(L) + "\n")
print("verify_build.rsp 已重新生成")
if __name__ == "__main__":
gen_aliases()
publify_fields()
patch_dockwindows_cs()
split_main()
copy_partials()
port_sqlite_tail()
copy_dialogs()
patch_csproj()
gen_rsp()
print("完成")
+152
View File
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
"""一次性迁移:把 python 代理的 proxy_cache 展开成静态目录树 → bin\\Debug\\元件库网页\\
数据平移,不带旧服务逻辑。规则:
1. price_standalone.html / jquery.min.js 直接拷到根
2. 关键资源按【页面引用清单 override】精确落位(解决键名下划线歧义)
3. electricData/privateJson/electricPic/privateImg 各取最大缓存 → 固定路径(查询忽略)
4. webGetPrice 族(dx2_pcprice..._hash) → dx2_webGetPrice\\ 原键保存(server.cjs 按 infoJson hash 查)
5. 其余键:下划线全展开为目录树,同类多版本保留最新
"""
import os, re, shutil, sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = os.path.join(ROOT, "旧服务-python代理", "src")
CACHE = os.path.join(SRC, "proxy_cache")
PAGE_DIR = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "元件库网页")
OUT = os.path.join(PAGE_DIR, "data") # 迁移只重建 data\;server.cjs/页面 在外层不动
# 键名 → 精确目标路径(页面/SDK 实际请求;键不带 .data 后缀)
OVERRIDE = {
"dian_open_api_css_jprice_public.min.css": "dian/open/api/css/jprice_public.min.css",
"open_api_css_jprice_public.min.css": "open/api/css/jprice_public.min.css",
"open_api_css_jprice.min.css": "open/api/css/jprice.min.css",
"dian_open_api_js_jprice-2.0.0.js": "dian/open/api/js/jprice-2.0.0.js",
"viewer2_preview_ztree_zTreeStyle.css": "viewer2/preview/ztree/zTreeStyle.css",
"viewer2_preview_ztree_preview.css": "viewer2/preview/ztree/preview.css",
"open_api_images_zTreeStandard.png": "open/api/images/zTreeStandard.png",
"images_nopic.png": "images/nopic.png",
}
def main():
if not os.path.isdir(CACHE):
print("!! 找不到 proxy_cache:", CACHE); sys.exit(1)
if os.path.isdir(OUT):
shutil.rmtree(OUT)
os.makedirs(OUT)
# 1. 页面与 jquery(放外层,与 server.cjs 同级)
for f in ("price_standalone.html", "jquery.min.js"):
shutil.copy2(os.path.join(SRC, f), os.path.join(PAGE_DIR, f))
print("OK 页面+jquery")
# 收集缓存键(去 .meta/.data,同名保留最新 mtime 的 .data)
entries = {} # 键 -> (data路径, mtime, size)
for name in os.listdir(CACHE):
if not name.endswith(".data"):
continue
key = name[:-5]
p = os.path.join(CACHE, name)
st = os.stat(p)
old = entries.get(key)
if old is None or st.st_mtime > old[1]:
entries[key] = (p, st.st_mtime, st.st_size)
print("缓存键总数:", len(entries))
# 2. webGetPrice 族 → 独立目录原键保存
n_wgp = 0
os.makedirs(os.path.join(OUT, "dx2_webGetPrice"), exist_ok=True)
for key, (p, _, _) in list(entries.items()):
if "webGetPrice" in key:
shutil.copy2(p, os.path.join(OUT, "dx2_webGetPrice", key + ".data"))
entries.pop(key); n_wgp += 1
print("OK webGetPrice:", n_wgp)
# 3. electricData 族:全部 ZIP 按内部文件名平移到 electricData_zips\(server.cjs 按 v 参数路由)
import zipfile
n_zip = 0
os.makedirs(os.path.join(OUT, "dian", "electricData_zips"), exist_ok=True)
for key in list(entries.keys()):
if "electricData" not in key:
continue
p = entries[key][0]
try:
z = zipfile.ZipFile(p)
inner = z.namelist()[0] if z.namelist() else ""
except Exception:
inner = ""
if not inner:
continue
base = inner[:-3] if inner.endswith(".js") else (inner[:-2] if inner.endswith("js") else inner)
base = base.rstrip(".")
dst = os.path.join(OUT, "dian", "electricData_zips", base + ".zip")
if not os.path.exists(dst):
shutil.copy2(p, dst)
n_zip += 1
entries.pop(key)
print("OK electricData ZIPs:", n_zip)
# electricPic 等取最大一份 → 固定路径
for 词, 目标 in [("privateJson", ("dian", "privateJson")),
("electricPic", ("dian", "electricPic")),
("privateImg", ("dian", "privateImg"))]:
best_key, best = None, None
for key, (p, mt, sz) in entries.items():
base = key.rsplit("/", 1)[-1]
名 = base.split("_")[0] if "_" in base else base
# 键形如 dian_electricData_xxxxxxxx / electricData
parts = key.split("_")
if 词 in parts or key == 词 or (parts and parts[0] == "dian" and len(parts) > 1 and parts[1].startswith(词)):
if best is None or sz > best[2]:
best_key, best = key, (p, mt, sz)
if best:
dst = os.path.join(OUT, *目标)
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(best[0], dst)
entries.pop(best_key)
print("OK %s <- %s (%d B)" % (词, best_key, best[2]))
else:
print("!! %s 无缓存" % 词)
# 4. override 精确落位
n_ov = 0
for key, rel in OVERRIDE.items():
if key in entries:
dst = os.path.join(OUT, *rel.split("/"))
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(entries[key][0], dst)
entries.pop(key); n_ov += 1
else:
# 有参数hash变体:找 键_hash
cand = [k for k in entries if k == key or k.startswith(key + "_")]
if cand:
cand.sort(key=lambda k: -entries[k][2])
k0 = cand[0]
dst = os.path.join(OUT, *rel.split("/"))
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(entries[k0][0], dst)
entries.pop(k0); n_ov += 1
else:
print("!! override 缺缓存:", key)
print("OK override:", n_ov)
# 5. 其余全展开(下划线→斜杠);去掉 _参数hash 尾巴后仍带 8 位 hex 的整段保留
n_tree = 0
for key, (p, _, _) in entries.items():
rel = key.replace("_", "/")
dst = os.path.join(OUT, *rel.split("/"))
try:
os.makedirs(os.path.dirname(dst), exist_ok=True)
if os.path.exists(dst):
os.remove(dst)
shutil.copy2(p, dst)
n_tree += 1
except OSError:
pass # 非法文件名等跳过
print("OK 目录树:", n_tree)
total = sum(len(fs) for _, _, fs in os.walk(OUT))
size = sum(os.path.getsize(os.path.join(r, f)) for r, _, fs in os.walk(OUT) for f in fs)
print("完成: %d 文件, %.1f MB → %s" % (total, size / 1048576.0, OUT))
if __name__ == "__main__":
main()
+777
View File
@@ -0,0 +1,777 @@
# -*- coding: utf-8 -*-
"""
把旧项目 主窗口.ui.cs 的 UI 迁移到 SharpDevelop 停靠面板项目。
映射:旧 tabControl2/tabControl3 的 9 个页签 → 9 个 DockWindow;
旧 tabControl1(顶部 项目/报价/设置)+ statusStrip1 → MainForm。
仅迁移 UI(控件、属性、布局),事件与逻辑下一阶段迁移。
"""
import io
import re
import os
import sys
SRC = r"F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\scr\成套报价软件\主窗口.ui.cs"
DST_PROJECT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
DOCK_MAP = [
("DockWindow", "tabPage3", "项目"),
("DockWindow2", "tabPage4", "报价"),
("DockWindow3", "tabPage10", "选型"),
("DockWindow4", "tabPage5", "元件库"),
("DockWindow5", "tabPage6", "用户库"),
("DockWindow6", "tabPage11", "柜体库"),
("DockWindow7", "tabPage12", "母排"),
("DockWindow8", "tabPage13", "方案"),
("DockWindow9", "tabPage14", "箱体计算"),
]
NAME = r"[A-Za-z0-9_\u4e00-\u9fff]+"
# ---------------------------------------------------------------- 解析
def parse(text):
lines = text.split("\n")
decls = [] # [(name, fulltype)] 按旧文件声明顺序
decls_seen = set()
begin_inits = set()
top_statements = [] # 声明区/尾部区语句(无块头上下文)
blocks = [] # [(header_name, [statement strings])] 属性块
edges = [] # [(parent_key, [child])] Controls.AddRange
ts_edges = [] # [(toolstrip, [items])] ToolStrip Items.AddRange
cur_name = None
cur_stmt = []
cur_lines = []
prev_blank_comment = False
def flush_stmt():
nonlocal cur_stmt
if not cur_stmt:
return
stmt = "\n".join(cur_stmt).strip()
cur_stmt = []
if not stmt:
return
m = re.match(r"this\.(" + NAME + r"(?:\.Panel[12])?)\.Controls\.AddRange\(new System\.Windows\.Forms\.Control\[\] \{(.*?)\}\);", stmt, re.S)
if m:
kids = re.findall(r"this\.(" + NAME + r")(?=[,\s}])", m.group(2))
edges.append((m.group(1), kids))
return
m = re.match(r"this\.Controls\.AddRange\(new System\.Windows\.Forms\.Control\[\] \{(.*?)\}\);", stmt, re.S)
if m:
kids = re.findall(r"this\.(" + NAME + r")(?=[,\s}])", m.group(1))
edges.append(("", kids))
return
m = re.match(r"this\.(" + NAME + r")\.Items\.AddRange\(new System\.Windows\.Forms\.ToolStripItem\[\] \{(.*?)\}\);", stmt, re.S)
if m:
kids = re.findall(r"this\.(" + NAME + r")(?=[,\s}])", m.group(2))
ts_edges.append((m.group(1), kids))
return
m = re.match(r"this\.(" + NAME + r") = new ([\w.]+)\(\);$", stmt)
if m:
if m.group(1) == "components":
return
if m.group(1) not in decls_seen:
decls.append((m.group(1), m.group(2)))
decls_seen.add(m.group(1))
return
if re.match(r"\(\(System\.ComponentModel\.ISupportInitialize\)\(this\.(" + NAME + r")\)\)\.BeginInit\(\);", stmt):
begin_inits.add(re.match(r"\(\(System\.ComponentModel\.ISupportInitialize\)\(this\.(" + NAME + r")\)\)\.BeginInit\(\);", stmt).group(1))
return
if cur_name is not None:
cur_lines.append(stmt)
else:
top_statements.append(stmt)
field_re = re.compile(r"^private [\w.]+ " + NAME + r";$")
for raw in lines:
line = raw.rstrip("\r")
s = line.strip()
if field_re.match(s):
continue
m = re.match(r"^ {12}// (" + NAME + r")$", line)
if m and prev_blank_comment:
flush_stmt()
if cur_name is not None and cur_lines:
blocks.append((cur_name, cur_lines))
cur_name, cur_lines = m.group(1), []
prev_blank_comment = False
continue
prev_blank_comment = (s == "//")
if s.startswith("this.") or s.startswith("((System") or cur_stmt:
cur_stmt.append(s)
if s.endswith(";"):
flush_stmt()
flush_stmt()
if cur_name is not None and cur_lines:
blocks.append((cur_name, cur_lines))
return decls, begin_inits, blocks, top_statements, edges, ts_edges
def classify(blocks):
ctrl_blocks = {}
form_block = []
for name, stmts in blocks:
if name == "主窗口":
form_block = stmts
else:
ctrl_blocks[name] = stmts
return ctrl_blocks, form_block
# ---------------------------------------------------------------- 默认值过滤
DROP_GLOBAL = [
r"\.Enabled = true;$",
r"\.Visible = true;$",
r"\.TabStop = true;$",
r"\.Font = new System\.Drawing\.Font\(\"宋体\", 9F, System\.Drawing\.FontStyle\.Regular, System\.Drawing\.GraphicsUnit\.Point(, 1)?\);$",
r"\.ForeColor = System\.Drawing\.SystemColors\.ControlText;$",
r"\.Margin = new System\.Windows\.Forms\.Padding\((3, 3, 3, 3|0, 0, 0, 0)\);$",
r"\.MaximumSize = new System\.Drawing\.Size\(0, 0\);$",
r"\.MinimumSize = new System\.Drawing\.Size\(0, 0\);$",
r"\.Padding = new System\.Windows\.Forms\.Padding\(0, 0, 0, 0\);$",
r"\.AutoScroll = false;$",
r"\.AutoSize = false;$",
r"\.AutoSizeMode = System\.Windows\.Forms\.AutoSizeMode\.GrowOnly;$",
r"\.AutoValidate = System\.Windows\.Forms\.AutoValidate\.\w+;$",
r"\.AllowDrop = false;$",
r"\.UseWaitCursor = false;$",
r"\.BackgroundImageLayout = System\.Windows\.Forms\.ImageLayout\.Tile;$",
r"\.AutoEllipsis = false;$",
r"\.DialogResult = System\.Windows\.Forms\.DialogResult\.None;$",
r"\.FlatStyle = System\.Windows\.Forms\.FlatStyle\.Standard;$",
r"\.ImageAlign = System\.Drawing\.ContentAlignment\.MiddleCenter;$",
r"\.TextImageRelation = System\.Windows\.Forms\.TextImageRelation\.Overlay;$",
r"\.UseMnemonic = true;$",
r"\.UseCompatibleTextRendering = false;$",
r"\.Cursor = System\.Windows\.Forms\.Cursors\.Default;$",
r"\.ImeMode = System\.Windows\.Forms\.ImeMode\.\w+;$",
r"\.RightToLeft = System\.Windows\.Forms\.RightToLeft\.No;$",
r"\.CauseValidation = true;$",
r"\.Dock = System\.Windows\.Forms\.DockStyle\.None;$",
r"\.GridColor = System\.Drawing\.SystemColors\.ControlDark;$",
r"\.ColumnHeadersVisible = true;$",
r"\.RowHeadersVisible = true;$",
r"\.ShowCellErrors = true;$",
r"\.ShowCellToolTips = true;$",
r"\.ShowEditingIcon = true;$",
r"\.ShowRowErrors = true;$",
r"\.StandardTab = false;$",
r"\.VirtualMode = false;$",
r"\.MultiSelect = true;$",
r"\.ScrollBars = System\.Windows\.Forms\.ScrollBars\.Both;$",
r"\.AllowUserToResizeColumns = true;$",
r"\.AutoSizeColumnsMode = System\.Windows\.Forms\.DataGridViewAutoSizeColumnsMode\.None;$",
r"\.AutoSizeRowsMode = System\.Windows\.Forms\.DataGridViewAutoSizeRowsMode\.None;$",
r"\.ClipboardCopyMode = System\.Windows\.Forms\.DataGridViewClipboardCopyMode\.EnableWithAutoHeaderText;$",
r"\.ColumnHeadersBorderStyle = System\.Windows\.Forms\.DataGridViewHeaderBorderStyle\.Raised;$",
r"\.RowHeadersBorderStyle = System\.Windows\.Forms\.DataGridViewHeaderBorderStyle\.Raised;$",
r"\.RowHeadersWidthSizeMode = System\.Windows\.Forms\.DataGridViewRowHeadersWidthSizeMode\.EnableResizing;$",
r"\.ColumnHeadersHeightSizeMode = System\.Windows\.Forms.DataGridViewColumnHeadersHeightSizeMode\.EnableResizing;$",
r"\.EnableCellPaste = false;$",
r"\.DefaultCellStyle\.BackColor = System\.Drawing\.SystemColors\.Window;$",
r"\.DefaultCellStyle\.ForeColor = System\.Drawing\.SystemColors\.ControlText;$",
r"\.DefaultCellStyle\.SelectionBackColor = System\.Drawing\.SystemColors\.Highlight;$",
r"\.DefaultCellStyle\.SelectionForeColor = System\.Drawing\.SystemColors\.HighlightText;$",
r"\.DefaultCellStyle\.WrapMode = System\.Windows\.Forms\.DataGridViewTriState\.False;$",
r"\.DefaultCellStyle\.Alignment = System\.Windows\.Forms\.DataGridViewContentAlignment\.MiddleLeft;$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle|DefaultCellStyle)\.Font = new System\.Drawing\.Font\(\"宋体\", 9F.*\);$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle)\.BackColor = System\.Drawing\.SystemColors\.Control;$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle)\.ForeColor = System\.Drawing\.SystemColors\.WindowText;$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle)\.SelectionBackColor = System\.Drawing\.SystemColors\.Highlight;$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle)\.SelectionForeColor = System\.Drawing\.SystemColors\.HighlightText;$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle)\.WrapMode = System\.Windows\.Forms\.DataGridViewTriState\.True;$",
r"\.(ColumnHeadersDefaultCellStyle|RowHeadersDefaultCellStyle)\.Alignment = System\.Windows\.Forms\.DataGridViewContentAlignment\.MiddleLeft;$",
# TextBox / ComboBox 默认值
r"\.AcceptsReturn = false;$",
r"\.AutoCompleteMode = System\.Windows\.Forms\.AutoCompleteMode\.None;$",
r"\.AutoCompleteSource = System\.Windows\.Forms\.AutoCompleteSource\.None;$",
r"\.CharacterCasing = System\.Windows\.Forms\.CharacterCasing\.Normal;$",
r"\.Multiline = false;$",
r"\.UseSystemPasswordChar = false;$",
r"\.AcceptsTab = false;$",
r"\.ShortcutsEnabled = true;$",
r"\.HideSelection = true;$",
r"\.WordWrap = true;$",
r"\.MaxLength = 32767;$",
r"\.MaxLength = 0;$",
r"\.ReadOnly = false;$",
r"\.ScrollBars = System\.Windows\.Forms\.ScrollBars\.None;$",
r"\.TextAlign = System\.Windows\.Forms\.HorizontalAlignment\.Left;$",
r"\.BorderStyle = System\.Windows\.Forms\.BorderStyle\.Fixed3D;$",
r"\.DrawMode = System\.Windows\.Forms\.DrawMode\.Normal;$",
r"\.IntegralHeight = true;$",
r"\.MaxDropDownItems = 8;$",
r"\.Sorted = false;$",
r"\.DropDownStyle = System\.Windows\.Forms\.ComboBoxStyle\.DropDown;$",
r"\.CanOverflow = true;$",
r"\.AllowItemReorder = false;$",
r"\.AllowMerge = true;$",
r"\.GripMargin = new System\.Windows\.Forms\.Padding\(2, 2, 2, 2\);$",
r"\.ImageScalingSize = new System\.Drawing\.Size\(16, 16\);$",
r"\.LayoutStyle = System\.Windows\.Forms\.ToolStripLayoutStyle\.HorizontalStackWithOverflow;$",
r"\.Stretch = false;$",
r"\.RenderMode = System\.Windows\.Forms\.ToolStripRenderMode\.ManagerRenderMode;$",
r"\.ShowItemToolTips = true;$",
r"\.TextDirection = System\.Windows\.Forms\.ToolStripTextDirection\.Horizontal;$",
r"\.Padding = new System\.Windows\.Forms\.Padding\(0, 0, 1, 0\);$",
]
DROP_BY_TYPE = {
"Panel": [r"\.BorderStyle = System\.Windows\.Forms\.BorderStyle\.None;$",
r"\.BackColor = System\.Drawing\.SystemColors\.Control;$"],
"SplitContainer": [
r"\.BorderStyle = System\.Windows\.Forms\.BorderStyle\.None;$",
r"\.FixedPanel = System\.Windows\.Forms\.FixedPanel\.None;$",
r"\.IsSplitterFixed = false;$",
r"\.Orientation = System\.Windows\.Forms\.Orientation\.Vertical;$",
r"\.Panel1Collapsed = false;$",
r"\.Panel2Collapsed = false;$",
r"\.Panel1MinSize = 25;$",
r"\.Panel2MinSize = 25;$",
r"\.SplitterIncrement = 1;$",
r"\.SplitterWidth = 4;$",
r"\.BackColor = System\.Drawing\.SystemColors\.Control;$",
],
"TabControl": [
r"\.Alignment = System\.Windows\.Forms\.TabAlignment\.Top;$",
r"\.Appearance = System\.Windows\.Forms\.TabAppearance\.Normal;$",
r"\.DrawMode = System\.Windows\.Forms\.TabDrawMode\.Normal;$",
r"\.HotTrack = false;$",
r"\.Multiline = false;$",
r"\.RightToLeftLayout = false;$",
r"\.ShowToolTips = false;$",
r"\.BackColor = System\.Drawing\.SystemColors\.Control;$",
],
"TabPage": [
r"\.BorderStyle = System\.Windows\.Forms\.BorderStyle\.None;$",
r"\.AutoScrollMargin = new System\.Drawing\.Size\(0, 0\);$",
r"\.AutoScrollMinSize = new System\.Drawing\.Size\(0, 0\);$",
],
"Button": [
r"\.BackColor = System\.Drawing\.SystemColors\.Control;$",
r"\.TextAlign = System\.Drawing\.ContentAlignment\.MiddleCenter;$",
],
"Label": [
r"\.BackColor = System\.Drawing\.SystemColors\.Control;$",
],
}
DROP_GLOBAL_C = [re.compile(p) for p in DROP_GLOBAL]
DROP_BY_TYPE_C = {k: [re.compile(p) for p in v] for k, v in DROP_BY_TYPE.items()}
def short_type(t):
return t.split(".")[-1]
def should_drop(stmt, stype):
for c in DROP_GLOBAL_C:
if c.search(stmt):
return True
for c in DROP_BY_TYPE_C.get(stype, []):
if c.search(stmt):
return True
return False
# ---------------------------------------------------------------- 列转换
def extract_columns(stmts):
cols = []
rest = []
for st in stmts:
m = re.match(r"this\.(" + NAME + r")\.Columns\.Add\(new System\.Windows\.Forms\.(\w+)\(\)\);$", st, re.S)
if m:
cols.append({"type": m.group(2), "props": []})
continue
m = re.match(r"this\.(" + NAME + r")\.Columns\[(\d+)\]\.([\w.]+) = (.*);$", st, re.S)
if m:
idx = int(m.group(2))
while len(cols) <= idx:
cols.append({"type": "DataGridViewTextBoxColumn", "props": []})
cols[idx]["props"].append((m.group(3), m.group(4)))
continue
rest.append(st)
return rest, cols
COL_DROP_VALUE = {
"Visible": "true",
"Frozen": "false",
"AutoSizeMode": "System.Windows.Forms.DataGridViewAutoSizeColumnMode.NotSet",
"Width": "100",
"Resizable": "System.Windows.Forms.DataGridViewTriState.True",
}
def column_lines(col):
out = []
for prop, val in col["props"]:
if prop in COL_DROP_VALUE and val == COL_DROP_VALUE[prop]:
continue
if prop == "Name" and val == '""':
continue
out.append((prop, val))
return out
# ---------------------------------------------------------------- 子树工具
def build_children(edges):
ch = {}
for parent, kids in edges:
ch.setdefault(parent, []).extend(kids)
return ch
def subtree(root, ch):
out = []
stack = [root]
while stack:
n = stack.pop(0)
out.append(n)
stack = ch.get(n, []) + ch.get(n + ".Panel1", []) + ch.get(n + ".Panel2", []) + stack
return out
# ---------------------------------------------------------------- 通用块生成
def plan_columns(ordered, ctrl_blocks):
"""预规划列字段:返回 {grid: [(fname, col)]},字段按 ordered 顺序编号"""
plan = {}
n = 0
for g in ordered:
_rest, cols = extract_columns(ctrl_blocks.get(g, []))
if cols:
flist = []
for col in cols:
n += 1
flist.append(("dataGridViewTextBoxColumn%d" % n, col))
plan[g] = flist
return plan
def emit_control_blocks(ordered, decl_types, ctrl_blocks, ts_edges, col_plan, a):
"""生成属性块"""
for n in ordered:
stype = short_type(decl_types[n])
stmts = ctrl_blocks.get(n, [])
rest, cols = extract_columns(stmts)
# 工具栏项
ts_items = None
for parent, kk in ts_edges:
if parent == n:
ts_items = kk
if not rest and not cols and ts_items is None:
continue
if n in col_plan:
for fname, col in col_plan[n]:
a("\t\t\t// ")
a("\t\t\t// %s" % fname)
a("\t\t\t// ")
for prop, val in column_lines(col):
a("\t\t\tthis.%s.%s = %s;" % (fname, prop, val))
a("\t\t\t// ")
a("\t\t\t// %s" % n)
a("\t\t\t// ")
for st in rest:
if should_drop(st, stype):
continue
a("\t\t\t" + st.replace("\n", "\n\t\t\t"))
if cols:
inner = ",\n\t\t\t\t".join("this.%s" % fname for fname, _c in col_plan[n])
a("\t\t\tthis.%s.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {" % n)
a("\t\t\t\t" + inner + "});")
if ts_items is not None:
inner = ",\n\t\t\t\t".join("this.%s" % k for k in ts_items)
a("\t\t\tthis.%s.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {" % n)
a("\t\t\t\t" + inner + "});")
TEMPLATE = """namespace 成套报价软件
{{
partial class {cls}
{{
/// <summary>
/// Designer variable used to keep track of non-visual components.
/// </summary>
private System.ComponentModel.IContainer components = null;
{fields}
/// <summary>
/// Disposes resources used by the form.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{{
if (disposing && (components != null)) {{
components.Dispose();
}}
base.Dispose(disposing);
}}
#region Designer generated code
/// <summary>
/// This method is required for Windows Forms designer support.
/// Do not change the method contents inside the source code editor. The Forms designer might
/// not be able to load this method if it was changed manually.
/// </summary>
private void InitializeComponent()
{{
{body}
}}
#endregion
}}
}}
"""
def emit_decls(ordered, decl_types, begin_inits, a):
for n in ordered:
a("\t\t\tthis.%s = new %s();" % (n, decl_types[n]))
if n in begin_inits:
a("\t\t\t((System.ComponentModel.ISupportInitialize)(this.%s)).BeginInit();" % n)
def emit_hierarchy(ordered_set, edges, ch, a, root=None):
"""输出容器层级;root 的孩子挂 this"""
for parent, kids in edges:
if parent == "":
continue
if parent == root:
target = "this"
else:
base = parent.split(".")[0]
if base not in ordered_set:
continue
target = "this." + parent
if not kids:
continue
inner = ",\n\t\t\t\t".join("this.%s" % k for k in kids)
a("\t\t\t%s.Controls.AddRange(new System.Windows.Forms.Control[] {" % target)
a("\t\t\t\t" + inner + "});")
def gen_dockwindow(cls, tab, title, decls, decl_types, begin_inits, ctrl_blocks, ch, edges, ts_edges, splitter_map):
members = subtree(tab, ch)
members.remove(tab)
members_set = set(members)
ordered = [n for n, _t in decls if n in members_set]
# 工具栏项不在 Controls 层级里,补进来
for parent, kids in ts_edges:
if parent in members_set:
for k in kids:
if k not in ordered:
ordered.append(k)
tab_size = (800, 600)
for st in ctrl_blocks.get(tab, []):
m = re.match(r"this\.(" + NAME + r")\.Size = new System\.Drawing\.Size\((\d+), (\d+)\);$", st)
if m:
tab_size = (int(m.group(2)), int(m.group(3)))
L = []
a = L.append
a("\t\t\tthis.components = new System.ComponentModel.Container();")
emit_decls(ordered, decl_types, begin_inits, a)
col_plan = plan_columns(ordered, ctrl_blocks)
for flist in col_plan.values():
for fname, _c in flist:
a("\t\t\tthis.%s = new System.Windows.Forms.DataGridViewTextBoxColumn();" % fname)
a("\t\t\tthis.SuspendLayout();")
emit_control_blocks(ordered, decl_types, ctrl_blocks, ts_edges, col_plan, a)
# 层级
emit_hierarchy(members_set, edges, ch, a, root=tab)
a("\t\t\t// ")
a("\t\t\t// %s" % cls)
a("\t\t\t// ")
a("\t\t\tthis.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);")
a("\t\t\tthis.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;")
a("\t\t\tthis.ClientSize = new System.Drawing.Size(%d, %d);" % tab_size)
a("\t\t\tthis.Name = \"%s\";" % cls)
a("\t\t\tthis.Text = \"%s\";" % title)
for n in ordered:
if n in begin_inits:
a("\t\t\t((System.ComponentModel.ISupportInitialize)(this.%s)).EndInit();" % n)
a("\t\t\tthis.ResumeLayout(false);")
a("\t\t\tthis.PerformLayout();")
for n in ordered:
if n in splitter_map:
a("\t\t\tthis.%s.SplitterDistance = %d;" % (n, splitter_map[n]))
fields = ["\t\tprivate %s %s;" % (decl_types[n], n) for n in ordered]
for flist in col_plan.values():
for fname, _c in flist:
fields.append("\t\tprivate System.Windows.Forms.DataGridViewTextBoxColumn %s;" % fname)
return TEMPLATE.format(cls=cls, body="\n".join(L), fields="\n".join(fields))
# ---------------------------------------------------------------- MainForm
def gen_mainform(decls, decl_types, begin_inits, ctrl_blocks, ch, edges, ts_edges, splitter_map):
# MainForm 使用的旧控件:tabControl1 三页签的子树 + statusStrip1
main_roots = ["tabPage1", "tabPage2", "tabPage8"]
members = set()
for r in main_roots:
members.update(subtree(r, ch))
members.remove("tabPage1"); members.remove("tabPage2"); members.remove("tabPage8")
members.add("statusStrip1")
ordered = [n for n, _t in decls if n in members]
for parent, kids in ts_edges:
if parent in members:
for k in kids:
if k not in ordered:
ordered.append(k)
L = []
a = L.append
for i in range(1, 10):
a("\t\t\tSmart.CustomComponents.DockWindows.DockWindowItem dockWindowItem%d = new Smart.CustomComponents.DockWindows.DockWindowItem();" % i)
a("\t\t\tthis.tabControl1 = new System.Windows.Forms.TabControl();")
a("\t\t\tthis.tabPage1 = new System.Windows.Forms.TabPage();")
a("\t\t\tthis.tabPage2 = new System.Windows.Forms.TabPage();")
a("\t\t\tthis.tabPage3 = new System.Windows.Forms.TabPage();")
for n in ordered:
a("\t\t\tthis.%s = new %s();" % (n, decl_types[n]))
a("\t\t\tthis.dockSurface1 = new Smart.CustomComponents.DockSurface();")
col_plan = plan_columns(ordered, ctrl_blocks)
for flist in col_plan.values():
for fname, _c in flist:
a("\t\t\tthis.%s = new System.Windows.Forms.DataGridViewTextBoxColumn();" % fname)
a("\t\t\tthis.tabControl1.SuspendLayout();")
a("\t\t\tthis.SuspendLayout();")
emit_control_blocks(ordered, decl_types, ctrl_blocks, ts_edges, col_plan, a)
# tabControl1 属性(旧值 + 新尺寸)
a("\t\t\t// ")
a("\t\t\t// tabControl1")
a("\t\t\t// ")
tc_props = []
for st in ctrl_blocks.get("tabControl1", []):
if should_drop(st, "TabControl"):
continue
if re.search(r"\.(Size|Location|Name|TabIndex|Dock) = ", st):
continue
tc_props.append(st)
for st in tc_props:
a("\t\t\t" + st)
a("\t\t\tthis.tabControl1.Dock = System.Windows.Forms.DockStyle.Top;")
a("\t\t\tthis.tabControl1.Location = new System.Drawing.Point(0, 0);")
a("\t\t\tthis.tabControl1.Name = \"tabControl1\";")
a("\t\t\tthis.tabControl1.SelectedIndex = 0;")
a("\t\t\tthis.tabControl1.Size = new System.Drawing.Size(1884, 111);")
a("\t\t\tthis.tabControl1.TabIndex = 0;")
# 三个页签
for old, new, text in [("tabPage1", "tabPage1", "项目"), ("tabPage2", "tabPage2", "报价"), ("tabPage8", "tabPage3", "设置")]:
a("\t\t\t// ")
a("\t\t\t// %s" % new)
a("\t\t\t// ")
kids = ch.get(old, [])
if kids:
inner = ",\n\t\t\t\t".join("this.%s" % k for k in kids)
a("\t\t\tthis.%s.Controls.AddRange(new System.Windows.Forms.Control[] {" % new)
a("\t\t\t\t" + inner + "});")
for st in ctrl_blocks.get(old, []):
if should_drop(st, "TabPage"):
continue
if re.search(r"\.(Size|Location|Name|TabIndex|Padding|Text|BackColor) = ", st):
continue
a("\t\t\t" + re.sub(r"^this\.%s\b" % old, "this.%s" % new, st))
a("\t\t\tthis.%s.Location = new System.Drawing.Point(4, 22);" % new)
a("\t\t\tthis.%s.Name = \"%s\";" % (new, new))
a("\t\t\tthis.%s.Padding = new System.Windows.Forms.Padding(3, 3, 3, 3);" % new)
a("\t\t\tthis.%s.Size = new System.Drawing.Size(1876, 82);" % new)
a("\t\t\tthis.%s.TabIndex = %d;" % (new, ["tabPage1", "tabPage2", "tabPage3"].index(new)))
a("\t\t\tthis.%s.Text = \"%s\";" % (new, text))
a("\t\t\tthis.%s.UseVisualStyleBackColor = true;" % new)
# statusStrip1
a("\t\t\t// ")
a("\t\t\t// statusStrip1")
a("\t\t\t// ")
for st in ctrl_blocks.get("statusStrip1", []):
if should_drop(st, "StatusStrip"):
continue
if re.search(r"\.(Location|Size|Name|TabIndex) = ", st):
continue
a("\t\t\t" + st)
a("\t\t\tthis.statusStrip1.Dock = System.Windows.Forms.DockStyle.Bottom;")
a("\t\t\tthis.statusStrip1.Location = new System.Drawing.Point(0, 932);")
a("\t\t\tthis.statusStrip1.Name = \"statusStrip1\";")
a("\t\t\tthis.statusStrip1.Size = new System.Drawing.Size(1884, 22);")
a("\t\t\tthis.statusStrip1.TabIndex = 2;")
# dockSurface1(保持现有内容)
a("\t\t\t// ")
a("\t\t\t// dockSurface1")
a("\t\t\t// ")
a("\t\t\tthis.dockSurface1.Dock = System.Windows.Forms.DockStyle.Fill;")
a("\t\t\tthis.dockSurface1.DockBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(238)))), ((int)(((byte)(238)))), ((int)(((byte)(242)))));")
a("\t\t\tdockWindowItem1.DockState = WeifenLuo.WinFormsUI.Docking.DockState.Document;")
a("\t\t\tdockWindowItem1.Title = \"项目\";")
a("\t\t\tdockWindowItem2.ClassName = \"DockWindow2\";")
a("\t\t\tdockWindowItem2.DockState = WeifenLuo.WinFormsUI.Docking.DockState.Document;")
a("\t\t\tdockWindowItem2.Title = \"报价\";")
a("\t\t\tdockWindowItem3.ClassName = \"DockWindow3\";")
a("\t\t\tdockWindowItem3.DockState = WeifenLuo.WinFormsUI.Docking.DockState.Document;")
a("\t\t\tdockWindowItem3.Title = \"选型\";")
a("\t\t\tdockWindowItem4.ClassName = \"DockWindow4\";")
a("\t\t\tdockWindowItem4.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockRight;")
a("\t\t\tdockWindowItem4.Title = \"元件库\";")
a("\t\t\tdockWindowItem5.ClassName = \"DockWindow5\";")
a("\t\t\tdockWindowItem5.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockRight;")
a("\t\t\tdockWindowItem5.Title = \"用户库\";")
a("\t\t\tdockWindowItem6.ClassName = \"DockWindow6\";")
a("\t\t\tdockWindowItem6.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockRight;")
a("\t\t\tdockWindowItem6.Title = \"柜体库\";")
a("\t\t\tdockWindowItem7.ClassName = \"DockWindow7\";")
a("\t\t\tdockWindowItem7.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockRight;")
a("\t\t\tdockWindowItem7.Title = \"母排\";")
a("\t\t\tdockWindowItem8.ClassName = \"DockWindow8\";")
a("\t\t\tdockWindowItem8.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockRight;")
a("\t\t\tdockWindowItem8.Title = \"方案\";")
a("\t\t\tdockWindowItem9.ClassName = \"DockWindow9\";")
a("\t\t\tdockWindowItem9.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockRight;")
a("\t\t\tdockWindowItem9.Title = \"箱体计算\";")
for i in range(1, 10):
a("\t\t\tthis.dockSurface1.DockWindows.Add(dockWindowItem%d);" % i)
a("\t\t\tthis.dockSurface1.Location = new System.Drawing.Point(0, 111);")
a("\t\t\tthis.dockSurface1.Name = \"dockSurface1\";")
a("\t\t\tthis.dockSurface1.Padding = new System.Windows.Forms.Padding(6);")
a("\t\t\tthis.dockSurface1.ShowAutoHideContentOnHover = false;")
a("\t\t\tthis.dockSurface1.Size = new System.Drawing.Size(1884, 821);")
a("\t\t\tthis.dockSurface1.TabIndex = 1;")
# 窗体
a("\t\t\t// ")
a("\t\t\t// MainForm")
a("\t\t\t// ")
a("\t\t\tthis.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);")
a("\t\t\tthis.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;")
a("\t\t\tthis.ClientSize = new System.Drawing.Size(1884, 954);")
a("\t\t\tthis.Controls.Add(this.dockSurface1);")
a("\t\t\tthis.Controls.Add(this.statusStrip1);")
a("\t\t\tthis.Controls.Add(this.tabControl1);")
a("\t\t\tthis.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);")
a("\t\t\tthis.Name = \"MainForm\";")
a("\t\t\tthis.Text = \"成套报价软件\";")
a("\t\t\tthis.tabControl1.ResumeLayout(false);")
a("\t\t\tthis.ResumeLayout(false);")
a("\t\t\tthis.PerformLayout();")
fields = []
fields.append("\t\tprivate System.Windows.Forms.TabControl tabControl1;")
fields.append("\t\tprivate System.Windows.Forms.TabPage tabPage1;")
fields.append("\t\tprivate System.Windows.Forms.TabPage tabPage2;")
fields.append("\t\tprivate System.Windows.Forms.TabPage tabPage3;")
for n in ordered:
fields.append("\t\tprivate %s %s;" % (decl_types[n], n))
fields.append("\t\tprivate Smart.CustomComponents.DockSurface dockSurface1;")
for flist in col_plan.values():
for fname, _c in flist:
fields.append("\t\tprivate System.Windows.Forms.DataGridViewTextBoxColumn %s;" % fname)
header = """/*
* 由SharpDevelop创建。
* 用户: 099978
* 日期: 2026-08-25
* 时间: 20:59
*
* 要改变这种模板请点击 工具|选项|代码编写|编辑标准头文件
*/
namespace 成套报价软件
{
partial class MainForm
{
/// <summary>
/// Designer variable used to keep track of non-visual components.
/// </summary>
private System.ComponentModel.IContainer components = null;
%s
/// <summary>
/// Disposes resources used by the form.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing) {
if (components != null) {
components.Dispose();
}
}
base.Dispose(disposing);
}
/// <summary>
/// This method is required for Windows Forms designer support.
/// Do not change the method contents inside the source code editor. The Forms designer might
/// not be able to load this method if it was changed manually.
/// </summary>
private void InitializeComponent()
{
%s
}
}
}
""" % ("\n".join(fields), "\n".join(L))
return header
# ---------------------------------------------------------------- main
def main():
text = io.open(SRC, encoding="utf-8-sig").read()
decls, begin_inits, blocks, top_statements, edges, ts_edges = parse(text)
ctrl_blocks, form_block = classify(blocks)
ch = build_children(edges)
decl_types = dict(decls)
splitter_map = {}
for st in form_block + top_statements:
m = re.match(r"this\.(" + NAME + r")\.SplitterDistance = (\d+);$", st)
if m:
splitter_map[m.group(1)] = int(m.group(2))
missing = [t for _c, t, _ti in DOCK_MAP if t not in decl_types]
if missing:
print("缺少页签:", missing)
sys.exit(1)
for cls, tab, title in DOCK_MAP:
out = gen_dockwindow(cls, tab, title, decls, decl_types, begin_inits, ctrl_blocks, ch, edges, ts_edges, splitter_map)
path = os.path.join(DST_PROJECT, "DockWindows", cls, cls + ".Designer.cs")
io.open(path, "w", encoding="utf-8-sig", newline="\r\n").write(out)
n_ctrl = len(subtree(tab, ch)) - 1
print("生成 %-60s ← %-9s (%s) 控件%d个" % (path, tab, title, n_ctrl))
mf = gen_mainform(decls, decl_types, begin_inits, ctrl_blocks, ch, edges, ts_edges, splitter_map)
path = os.path.join(DST_PROJECT, "MainForm.Designer.cs")
io.open(path, "w", encoding="utf-8-sig", newline="\r\n").write(mf)
print("生成", path)
# 覆盖检查
covered = set()
for _c, tab, _t in DOCK_MAP:
covered.update(subtree(tab, ch))
main_used = set(["tabControl1", "tabControl2", "tabControl3", "splitContainer1", "statusStrip1"])
for r in ["tabControl1", "statusStrip1"]:
main_used.update(subtree(r, ch))
leftover = [n for n, _ in decls if n not in covered and n not in main_used]
print("未覆盖控件:", leftover)
# 边完整性检查
all_kids = [k for _p, kk in edges for k in kk]
no_parent = [k for k in all_kids if k not in decl_types]
print("边中未声明的子控件:", no_parent)
if __name__ == "__main__":
main()
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""触发器创建挪到数据写完后统一建。幂等(基于标记检查)。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
n = 0
s = io.open(P, encoding="utf-8-sig").read()
def rep(old, new, tag):
global s, n
if old not in s:
if new in s:
print(tag, "已有(跳过)")
return
raise AssertionError(tag + " 锚点缺失: " + old[:70])
s = s.replace(old, new, 1)
n += 1
print(tag, "ok")
# 1) 字段(编辑直写库.cs, tab 缩进)
p2 = ROOT + r"\编辑直写库.cs"
s2 = io.open(p2, encoding="utf-8-sig").read()
old_f = "\t\t/// <summary>当前 Reload 耗时测量(进 Reload 方法时重启)。</summary>"
new_f = ("\t\t/// <summary>待建触发器清单 [yj表, 箱号表, 箱号ID, 图号ID](数据写完后统一建,避免事务内 DDL 拖慢批量 INSERT)。</summary>\n"
"\t\tprivate readonly System.Collections.Generic.List<string[]> _待建yj触发器 = new System.Collections.Generic.List<string[]>();\n"
"\t\t/// <summary>待建箱号表触发器 [箱号表, 图号ID]。</summary>\n"
"\t\tprivate readonly System.Collections.Generic.List<string[]> _待建xh触发器 = new System.Collections.Generic.List<string[]>();\n\n"
"\t\t/// <summary>当前 Reload 耗时测量(进 Reload 方法时重启)。</summary>")
if old_f in s2 and "_待建yj触发器" not in s2:
s2 = s2.replace(old_f, new_f, 1)
io.open(p2, "w", encoding="utf-8-sig").write(s2)
n += 1
print("字段 ok")
else:
print("字段 已有(跳过)")
# 2) 图号管线循环内:建元件表触发器 → 收集
rep(''' // ★ 建元件表触发器:INSERT/UPDATE/DELETE 自动算派生价+刷新箱号/图号聚合
计算引擎.建元件表触发器(conn, tx, yjTbl, xhTbl, nx, nt, TuhaoTableName,
_公式_元件, _公式_箱号, _公式_图号);''',
''' // ★ 登记待建触发器(数据全部写完后统一建,避免事务内 DDL 拖慢 INSERT)
_待建yj触发器.Add(new[] { yjTbl, xhTbl, nx, nt });''', "图号管线yj收集")
# 3) 图号管线:建箱号表触发器 → 收集
rep(''' // ★ 建箱号表触发器:INSERT/UPDATE/DELETE 自动算行内公式+刷新图号聚合
计算引擎.建箱号表触发器(conn, tx, xhTbl, nt, TuhaoTableName, new List<string[]>(), _公式_箱号, _公式_图号);''',
''' // 箱号表触发器管尾统一建(行级依赖各行的元件表名)
if (!_待建xh触发器.Exists(x => x[0] == xhTbl)) _待建xh触发器.Add(new[] { xhTbl, nt });''', "图号管线xh收集")
# 4) 图号管线管尾:统一建(开关=0 前)
rep(''' // ★ 恢复触发器(编辑自动计算)
计算引擎.开关设(conn, tx, 0);''',
''' // ★ 数据全部写完,统一建触发器(此后无批量 DML)
foreach (var t in _待建yj触发器)
计算引擎.建元件表触发器(conn, tx, t[0], t[1], t[2], t[3], TuhaoTableName,
_公式_元件, _公式_箱号, _公式_图号);
foreach (var t in _待建xh触发器)
{
var rows = new System.Collections.Generic.List<string[]>();
foreach (var y in _待建yj触发器)
if (y[1] == t[0]) rows.Add(new[] { y[2], y[0] });
计算引擎.建箱号表触发器(conn, tx, t[0], t[1], TuhaoTableName, rows, _公式_箱号, _公式_图号);
}
_待建yj触发器.Clear();
_待建xh触发器.Clear();
// ★ 恢复触发器(编辑自动计算)
计算引擎.开关设(conn, tx, 0);''', "图号管线管尾")
# 5) PasteYuanjianRowsToDb 建新表:触发器 → 收集
rep(''' // ★ 新元件表:建公式触发器(INSERT/UPDATE/DELETE 自动算派生价+刷聚合)
string _xhTblReal = LookupTableByIndex("箱号", tuhao, "", conn);
if (!string.IsNullOrEmpty(_xhTblReal))
{
using (var fakConn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(
System.IO.Path.Combine(System.IO.Path.GetDirectoryName(GetQuoteDbPath(projectId)), "fak.db"))))
{
fakConn.Open();
计算引擎.EnsureFormulaTable(fakConn);
计算引擎.建元件表触发器(conn, tx, tblName, _xhTblReal, xhId, tuhao, TuhaoTableName,
计算引擎.加载公式(fakConn, "元件"), 计算引擎.加载公式(fakConn, "箱号"),
计算引擎.加载公式(fakConn, "图号"));
}
}''',
''' // ★ 登记待建触发器(管尾统一建;行内派生值已随行写入,无需即时触发)
string _xhTblReal = LookupTableByIndex("箱号", tuhao, "", conn);
if (!string.IsNullOrEmpty(_xhTblReal))
{
bool exists = false;
foreach (var t in _待建yj触发器) if (t[2] == xhId && t[3] == tuhao) { exists = true; break; }
if (!exists) _待建yj触发器.Add(new[] { tblName, _xhTblReal, xhId, tuhao });
if (!_待建xh触发器.Exists(x => x[0] == _xhTblReal && x[1] == tuhao)) _待建xh触发器.Add(new[] { _xhTblReal, tuhao });
}''', "PasteYuanjian收集")
# 6) 箱号管线管尾:统一建(开关=0 前) —— 锚点用 刷新图号行聚合 调用行
rep(''' // 箱号行聚合抄源行已正确;仅图号行聚合(全箱号表 SUM)需刷新一次
计算引擎.刷新图号行聚合(conn, tx, dbPath, th, TuhaoTableName);''',
''' // 箱号行聚合抄源行已正确;统一建触发器 + 刷一次图号行聚合
foreach (var t in _待建yj触发器)
计算引擎.建元件表触发器(conn, tx, t[0], t[1], t[2], t[3], TuhaoTableName,
_公式_元件, _公式_箱号, _公式_图号);
foreach (var t in _待建xh触发器)
{
var rows = new System.Collections.Generic.List<string[]>();
foreach (var y in _待建yj触发器)
if (y[1] == t[0] && y[3] == t[1]) rows.Add(new[] { y[2], y[0] });
计算引擎.建箱号表触发器(conn, tx, t[0], t[1], TuhaoTableName, rows, _公式_箱号, _公式_图号);
}
_待建yj触发器.Clear();
_待建xh触发器.Clear();
计算引擎.刷新图号行聚合(conn, tx, dbPath, th, TuhaoTableName);''', "箱号管线管尾")
io.open(P, "w", encoding="utf-8-sig").write(s)
print("合计", n, "处")
+253
View File
@@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-
"""按用户洞察修正:粘贴/删除时数据本身正确,关掉触发器直接写;只有编辑(改输入)才触发计算。
①计算引擎:开关表+触发器 WHEN 开关=0+刷新图号行聚合方法
②图号粘贴:箱号行/图号行 INSERT 直接抄源行的聚合值(不再写 0);管线开关=1 包裹(零重算)
③箱号粘贴:开关=1 包裹,管尾刷一次图号行聚合
④删除管线:开关=1 包裹,管尾刷一次图号行聚合"""
import io
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
n = 0
def edit(path, pairs, tag):
global n
s = io.open(path, encoding="utf-8-sig").read()
for old, new in pairs:
if old not in s:
if new in s:
print(tag, "已有(跳过):", old[:40].replace("\n", " "))
continue
raise AssertionError(tag + " 锚点缺失: " + old[:80])
s = s.replace(old, new, 1)
n += 1
io.open(path, "w", encoding="utf-8-sig").write(s)
print(tag, "ok", n, "累计")
# ============ ① 计算引擎 ============
p = ROOT + r"\计算引擎.cs"
edit(p, [
# 开关条件常量
(''' /// <summary>触发器名前缀(重建时按前缀全清)。</summary>
public const string 触发器前缀 = "trgjs_";''',
''' /// <summary>触发器名前缀(重建时按前缀全清)。</summary>
public const string 触发器前缀 = "trgjs_";
/// <summary>触发器总开关条件:粘贴/删除批量写时置 开关=1(数据自带正确值,无需触发器计算)。</summary>
public const string 开关条件 = "(SELECT 开关 FROM _触发器开关 WHERE id=1)=0";'''),
# 触发器加 WHEN 开关=0(元件 u/i/d)
(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_u AFTER UPDATE OF " + of + " ON \\"" +
元件表 + "\\" FOR EACH ROW BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_u AFTER UPDATE OF " + of + " ON \\"" +
元件表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +'''),
(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_i AFTER INSERT ON \\"" +
元件表 + "\\" FOR EACH ROW BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_i AFTER INSERT ON \\"" +
元件表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +'''),
(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_d AFTER DELETE ON \\"" +
元件表 + "\\" FOR EACH ROW BEGIN " + 刷箱号 + 刷图号 + "END;");''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_d AFTER DELETE ON \\"" +
元件表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN " + 刷箱号 + 刷图号 + "END;");'''),
# 箱号行级 + i/d
(''' "_u AFTER UPDATE OF " + of + " ON \\"" + 箱号表 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
箱号ID.Replace("'", "''") + "' BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetNew +''',
''' "_u AFTER UPDATE OF " + of + " ON \\"" + 箱号表 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
箱号ID.Replace("'", "''") + "' AND " + 开关条件 + " BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetNew +'''),
(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_i AFTER INSERT ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_d AFTER DELETE ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_i AFTER INSERT ON \\"" +
箱号表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_d AFTER DELETE ON \\"" +
箱号表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN " + 刷图号 + "END;");'''),
# 确保开关表(重建时)+开关设+刷新图号行聚合
(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"CREATE TABLE IF NOT EXISTS _触发器开关 (id INTEGER PRIMARY KEY, 开关 INTEGER DEFAULT 0);", conn, tx))
cmd.ExecuteNonQuery();
Exec(conn, tx, "INSERT OR IGNORE INTO _触发器开关 (id, 开关) VALUES (1, 0);");
var old = new List<string>();''',
''' var old = new List<string>();'''),
(''' /// <summary>新表(管线内建表后)即时补触发器——只处理该表自身。</summary>''',
''' /// <summary>确保开关表存在(项目库;老库首次自动建)。</summary>
public static void 确保开关表(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx)
{
Exec(conn, tx, "CREATE TABLE IF NOT EXISTS _触发器开关 (id INTEGER PRIMARY KEY, 开关 INTEGER DEFAULT 0);");
Exec(conn, tx, "INSERT OR IGNORE INTO _触发器开关 (id, 开关) VALUES (1, 0);");
}
/// <summary>开关设值(1=批量写跳过触发器;0=编辑自动计算)。</summary>
public static void 开关设(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, int 值)
{
确保开关表(conn, tx);
Exec(conn, tx, "UPDATE _触发器开关 SET 开关=" + 值 + " WHERE id=1;");
}
/// <summary>刷新图号行聚合(箱号增删后调用;箱号表名按表索引查;一条 UPDATE,公式来自公式表)。</summary>
public static void 刷新图号行聚合(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
string 项目库路径, string 图号ID, string 图号表名)
{
string xhTbl = null;
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 实际表名 FROM 表索引 WHERE 类型='箱号' AND 图号=@th LIMIT 1;", conn, tx))
{
cmd.Parameters.AddWithValue("@th", 图号ID);
object o = cmd.ExecuteScalar();
xhTbl = o == null || o == DBNull.Value ? null : o.ToString();
}
if (string.IsNullOrEmpty(xhTbl)) return;
using (var fakConn = new System.Data.SQLite.SQLiteConnection(
"Data Source=" + System.IO.Path.Combine(System.IO.Path.GetDirectoryName(项目库路径), "..", "fak.db") + ";Version=3;"))
{
fakConn.Open();
var 图号公式 = 加载公式(fakConn, "图号");
string thSet = 生成Set片段(图号公式, "ROW", xhTbl);
Exec(conn, tx, "UPDATE \\"" + 图号表名 + "\\" SET " + thSet + " WHERE \\"内部ID\\"='" + 图号ID.Replace("'", "''") + "';");
}
}
/// <summary>新表(管线内建表后)即时补触发器——只处理该表自身。</summary>'''),
], "计算引擎.cs")
# ============ ② 图号粘贴管线:抄源聚合值 + 开关包裹 ============
p = ROOT + r"\主窗口.菜单与库.cs"
edit(p, [
# tx 后设开关=1(锚点:图号管线快照日志)
(''' Log("[粘贴图号] 快照: 事务内图号数=" + 快照图号数 + " xhMax=" + xhMax + " yjMax=" + yjMax);''',
''' Log("[粘贴图号] 快照: 事务内图号数=" + 快照图号数 + " xhMax=" + xhMax + " yjMax=" + yjMax);
// ★ 批量模式:粘贴数据自带正确计算结果(源行快照),无需触发器逐行重算
计算引擎.开关设(conn, tx, 1);'''),
# 图号行 INSERT 抄源聚合值(原来是 0,0,0,0,0,0)
(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"INSERT INTO " + TuhaoTableName +
" (序号, 图号, 台数, 箱柜数量, 单价, 总价, 成本总价, 毛利, 毛利率, 内部ID) " +
"VALUES (@s, @th, @ts, 0, 0, 0, 0, 0, 0, @iid);", conn, tx))
{
cmd.Parameters.AddWithValue("@s", thSeqMax.ToString());
cmd.Parameters.AddWithValue("@th", nt);
cmd.Parameters.AddWithValue("@ts", GetSrcField(clip.TuhaoRow.Count > 0 ? clip.TuhaoRow[0] : null, "台数", "1"));
cmd.Parameters.AddWithValue("@iid", nt);
cmd.ExecuteNonQuery();
}''',
''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"INSERT INTO " + TuhaoTableName +
" (序号, 图号, 台数, 箱柜数量, 单价, 总价, 成本总价, 毛利, 毛利率, 内部ID) " +
"VALUES (@s, @th, @ts, @sl, @dj, @zj, @cbzj, @ml, @mll, @iid);", conn, tx))
{
// ★ 副本=源:聚合值直接抄源图号行(箱号/元件表是完整副本,聚合天然相等)
var srcTh = clip.TuhaoRow.Count > 0 ? clip.TuhaoRow[0] : null;
cmd.Parameters.AddWithValue("@s", thSeqMax.ToString());
cmd.Parameters.AddWithValue("@th", nt);
cmd.Parameters.AddWithValue("@ts", GetSrcField(srcTh, "台数", "1"));
cmd.Parameters.AddWithValue("@sl", GetSrcFieldDecimal(srcTh, "箱柜数量", 0));
cmd.Parameters.AddWithValue("@dj", GetSrcFieldDecimal(srcTh, "单价", 0));
cmd.Parameters.AddWithValue("@zj", GetSrcFieldDecimal(srcTh, "总价", 0));
cmd.Parameters.AddWithValue("@cbzj", GetSrcFieldDecimal(srcTh, "成本总价", 0));
cmd.Parameters.AddWithValue("@ml", GetSrcFieldDecimal(srcTh, "毛利", 0));
cmd.Parameters.AddWithValue("@mll", GetSrcFieldDecimal(srcTh, "毛利率", 0));
cmd.Parameters.AddWithValue("@iid", nt);
cmd.ExecuteNonQuery();
}'''),
# 箱号行 INSERT 抄源聚合值(原 7 个 0)
(''' xhSeq++;
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"INSERT INTO \\"" + xhTbl + "\\" VALUES (@s, @xh, @xm, @model, @spec, @unit, @count," +
" 0, 0, 0, 0, 0, 0, 0, @type, @remark, @drawing, @iid);", conn, tx))
{
cmd.Parameters.AddWithValue("@s", xhSeq.ToString());
cmd.Parameters.AddWithValue("@xh", nx);
cmd.Parameters.AddWithValue("@xm", GetSrcField(xhRow, "箱名", ""));
cmd.Parameters.AddWithValue("@model", GetSrcField(xhRow, "型号", ""));
cmd.Parameters.AddWithValue("@spec", GetSrcField(xhRow, "规格", ""));
cmd.Parameters.AddWithValue("@unit", GetSrcField(xhRow, "单位", "台"));
cmd.Parameters.AddWithValue("@count", GetSrcField(xhRow, "数量", "1"));
cmd.Parameters.AddWithValue("@type", GetSrcField(xhRow, "箱柜类型", ""));
cmd.Parameters.AddWithValue("@remark", GetSrcField(xhRow, "备注", ""));
cmd.Parameters.AddWithValue("@drawing", GetSrcField(xhRow, "图纸", ""));
cmd.Parameters.AddWithValue("@iid", nx);
cmd.ExecuteNonQuery();
}''',
''' xhSeq++;
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"INSERT INTO \\"" + xhTbl + "\\" VALUES (@s, @xh, @xm, @model, @spec, @unit, @count," +
" @dtcb, @cbj, @dtbc, @bcj, @lr, @lrl, @mgl, @type, @remark, @drawing, @iid);", conn, tx))
{
// ★ 副本=源:箱号行聚合值直接抄源行(单台成本/成本价/单台报出/报出价/利润/利润率/毛利率)
cmd.Parameters.AddWithValue("@s", xhSeq.ToString());
cmd.Parameters.AddWithValue("@xh", nx);
cmd.Parameters.AddWithValue("@xm", GetSrcField(xhRow, "箱名", ""));
cmd.Parameters.AddWithValue("@model", GetSrcField(xhRow, "型号", ""));
cmd.Parameters.AddWithValue("@spec", GetSrcField(xhRow, "规格", ""));
cmd.Parameters.AddWithValue("@unit", GetSrcField(xhRow, "单位", "台"));
cmd.Parameters.AddWithValue("@count", GetSrcField(xhRow, "数量", "1"));
cmd.Parameters.AddWithValue("@dtcb", GetSrcFieldDecimal(xhRow, "单台成本", 0));
cmd.Parameters.AddWithValue("@cbj", GetSrcFieldDecimal(xhRow, "成本价", 0));
cmd.Parameters.AddWithValue("@dtbc", GetSrcFieldDecimal(xhRow, "单台报出", 0));
cmd.Parameters.AddWithValue("@bcj", GetSrcFieldDecimal(xhRow, "报出价", 0));
cmd.Parameters.AddWithValue("@lr", GetSrcFieldDecimal(xhRow, "利润", 0));
cmd.Parameters.AddWithValue("@lrl", GetSrcFieldDecimal(xhRow, "利润率", 0));
cmd.Parameters.AddWithValue("@mgl", GetSrcFieldDecimal(xhRow, "毛利率", 0));
cmd.Parameters.AddWithValue("@type", GetSrcField(xhRow, "箱柜类型", ""));
cmd.Parameters.AddWithValue("@remark", GetSrcField(xhRow, "备注", ""));
cmd.Parameters.AddWithValue("@drawing", GetSrcField(xhRow, "图纸", ""));
cmd.Parameters.AddWithValue("@iid", nx);
cmd.ExecuteNonQuery();
}'''),
# 管尾:开关=0(Commit 前)
(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT COUNT(*) FROM " + TuhaoTableName + ";", conn, tx))
Log("[粘贴图号] Commit前(事务内): 图号数=" + cmd.ExecuteScalar());''',
''' // ★ 恢复触发器(编辑自动计算)
计算引擎.开关设(conn, tx, 0);
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT COUNT(*) FROM " + TuhaoTableName + ";", conn, tx))
Log("[粘贴图号] Commit前(事务内): 图号数=" + cmd.ExecuteScalar());'''),
# 箱号粘贴管线:开关包裹+管尾刷图号行
(''' using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(dbPath)))
{
conn.Open();
using (var tx = conn.BeginTransaction())
{
foreach (var clip in clips)
{
string baseName = !string.IsNullOrWhiteSpace(clip.SrcXianghaoName) ? clip.SrcXianghaoName : "箱号";
string newXhId = MakeUniqueName(baseName,
candidate => NameExistsXhInConn2(conn, tx, th, candidate));
PasteXianghaoRowToDb(pid, th, newXhId, clip.XianghaoRow, conn, tx);
PasteYuanjianRowsToDb(pid, th, newXhId, clip.YuanjianRows, conn, tx);
pastedCount++;
lastXhId = newXhId;
}
tx.Commit();
}
// ★ 触发器已自动算好箱号/图号聚合;只补跨库项目汇总
计算引擎.刷新项目汇总(dbPath, pid, TuhaoTableName);''',
''' using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(dbPath)))
{
conn.Open();
using (var tx = conn.BeginTransaction())
{
// ★ 批量模式:粘贴行自带源计算结果;关触发器,提交前刷一次图号行聚合
计算引擎.开关设(conn, tx, 1);
foreach (var clip in clips)
{
string baseName = !string.IsNullOrWhiteSpace(clip.SrcXianghaoName) ? clip.SrcXianghaoName : "箱号";
string newXhId = MakeUniqueName(baseName,
candidate => NameExistsXhInConn2(conn, tx, th, candidate));
PasteXianghaoRowToDb(pid, th, newXhId, clip.XianghaoRow, conn, tx);
PasteYuanjianRowsToDb(pid, th, newXhId, clip.YuanjianRows, conn, tx);
pastedCount++;
lastXhId = newXhId;
}
// 箱号行聚合抄源行已正确;仅图号行聚合(全箱号表 SUM)需刷新一次
计算引擎.刷新图号行聚合(conn, tx, dbPath, th, TuhaoTableName);
计算引擎.开关设(conn, tx, 0);
tx.Commit();
}
// 只补跨库项目汇总
计算引擎.刷新项目汇总(dbPath, pid, TuhaoTableName);'''),
], "主窗口.菜单与库.cs")
print("合计", n, "处")
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<path d="M14 6 H38 L50 18 V58 H14 Z" fill="#FFFFFF" stroke="#C0392B" stroke-width="3" stroke-linejoin="round"/>
<path d="M38 6 V18 H50" fill="none" stroke="#C0392B" stroke-width="3" stroke-linejoin="round"/>
<rect x="10" y="34" width="34" height="18" rx="3" fill="#D64541"/>
<text x="27" y="48" font-family="Segoe UI, Arial" font-size="13" font-weight="bold" fill="#FFFFFF" text-anchor="middle" dominant-baseline="central">PDF</text>
</svg>

After

Width:  |  Height:  |  Size: 510 B

+142
View File
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""加载卡顿优化:①计算引擎加公式指纹(PRAGMA user_version 比对,一致跳过重建)
②EnsureFormulaTriggersQuiet 改后台+指纹跳过 ③三表 Reload 加 Stopwatch 耗时日志。幂等。"""
import io
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
n = 0
def edit(path, pairs, tag):
global n
s = io.open(path, encoding="utf-8-sig").read()
for old, new in pairs:
if old not in s:
if new in s:
print(tag, "已有(跳过)")
continue
raise AssertionError(tag + " 锚点缺失: " + old[:70])
s = s.replace(old, new, 1)
n += 1
io.open(path, "w", encoding="utf-8-sig").write(s)
print(tag, "ok")
# 1) 计算引擎:公式指纹
edit(ROOT + r"\计算引擎.cs", [
(''' /// <summary>加载某层级启用中的公式(按排序)。加载失败(表缺/异常)回退默认公式。</summary>''',
''' /// <summary>公式表内容指纹(全部公式行串联的稳定 hash)。用于判断触发器是否需要重建。</summary>
public static int 公式指纹(System.Data.SQLite.SQLiteConnection fakConn)
{
try
{
var sb = new StringBuilder();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 层级,目标列,表达式,排序,启用 FROM \\"" + 公式表名 + "\\" ORDER BY 排序;", fakConn))
using (var r = cmd.ExecuteReader())
while (r.Read())
sb.Append(r.GetValue(0)).Append('|').Append(r.GetValue(1)).Append('|')
.Append(r.GetValue(2)).Append('|').Append(r.GetValue(3)).Append('|')
.Append(r.GetValue(4)).Append(';');
return sb.ToString().GetHashCode();
}
catch { return 0; }
}
/// <summary>读取项目库 user_version(存的是上次重建时的公式指纹)。</summary>
public static int 读触发器版本(System.Data.SQLite.SQLiteConnection conn)
{
using (var cmd = new System.Data.SQLite.SQLiteCommand("PRAGMA user_version;", conn))
return Convert.ToInt32(cmd.ExecuteScalar());
}
/// <summary>写入项目库 user_version。</summary>
public static void 写触发器版本(System.Data.SQLite.SQLiteConnection conn, int 版本)
{
using (var cmd = new System.Data.SQLite.SQLiteCommand("PRAGMA user_version=" + 版本 + ";", conn))
cmd.ExecuteNonQuery();
}
/// <summary>加载某层级启用中的公式(按排序)。加载失败(表缺/异常)回退默认公式。</summary>'''),
], "计算引擎.cs")
# 2) 编辑直写库.cs:指纹跳过+后台化
edit(ROOT + r"\编辑直写库.cs", [
(''' /// <summary>进项目/建表/撤销删除后:按公式表重建项目库全部触发器(幂等,安静模式只记日志)。</summary>
private void EnsureFormulaTriggersQuiet(string projectId)
{
try
{
if (string.IsNullOrWhiteSpace(projectId)) return;
string dbPath = GetQuoteDbPath(projectId);
if (!System.IO.File.Exists(dbPath)) return;
string fak = GetDbFullPath(@"sjk\\fak.db");
using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(dbPath)))
using (var fakConn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(fak)))
{
conn.Open();
fakConn.Open();
计算引擎.EnsureFormulaTable(fakConn);
计算引擎.重建项目全部触发器(conn, null, TuhaoTableName, fakConn);
}
Log("[公式触发器] 已按公式表重建 [" + projectId + "] 全部触发器");
}
catch (Exception ex) { Log("[公式触发器] 重建失败(不影响编辑,但派生列不会自动算): " + ex.Message); }
}''',
''' /// <summary>进项目/建表/撤销删除后:按公式表重建项目库全部触发器。
/// ★ 性能:公式指纹与项目库 user_version 一致时直接跳过(毫秒级);需要重建时放后台线程不卡 UI。</summary>
private void EnsureFormulaTriggersQuiet(string projectId)
{
System.Threading.Tasks.Task.Run(delegate { EnsureFormulaTriggersCore(projectId); });
}
private void EnsureFormulaTriggersCore(string projectId)
{
try
{
if (string.IsNullOrWhiteSpace(projectId)) return;
string dbPath = GetQuoteDbPath(projectId);
if (!System.IO.File.Exists(dbPath)) return;
string fak = GetDbFullPath(@"sjk\\fak.db");
var sw = System.Diagnostics.Stopwatch.StartNew();
using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(dbPath)))
using (var fakConn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(fak)))
{
conn.Open();
fakConn.Open();
计算引擎.EnsureFormulaTable(fakConn);
int 指纹 = 计算引擎.公式指纹(fakConn);
int 已存 = 计算引擎.读触发器版本(conn);
if (指纹 != 0 && 指纹 == 已存)
{
Log("[公式触发器] 公式未变化,跳过重建 (" + projectId + ")");
return;
}
计算引擎.重建项目全部触发器(conn, null, TuhaoTableName, fakConn);
计算引擎.写触发器版本(conn, 指纹);
}
sw.Stop();
Log("[公式触发器] 已按公式表重建 [" + projectId + "] 全部触发器, 耗时 " + sw.ElapsedMilliseconds + "ms");
}
catch (Exception ex) { Log("[公式触发器] 重建失败(不影响编辑,但派生列不会自动算): " + ex.Message); }
}'''),
], "编辑直写库.cs")
# 3) 三表 Reload 耗时日志
p3 = ROOT + r"\主窗口.cs"
edit(p3, [
(''' _tuhaoTable = table; // 记下当前表引用,供关闭/切换时回写
this.advancedDataGridView9.RowCount = table.Rows.Count;
this.advancedDataGridView9.Invalidate();''',
''' _tuhaoTable = table; // 记下当前表引用,供关闭/切换时回写
this.advancedDataGridView9.RowCount = table.Rows.Count;
this.advancedDataGridView9.Invalidate();
Log("[性能] ReloadTuhaoByProject 耗时 " + _swReload.ElapsedMilliseconds + "ms (含取缓存/查库/转换)");'''),
(''' _xianghaoTable = table; // 记下当前表引用,供切换/关闭时回写
this.advancedDataGridView4.RowCount = table.Rows.Count;
this.advancedDataGridView4.Invalidate();''',
''' _xianghaoTable = table; // 记下当前表引用,供切换/关闭时回写
this.advancedDataGridView4.RowCount = table.Rows.Count;
this.advancedDataGridView4.Invalidate();
Log("[性能] ReloadXianghao 耗时 " + _swReload.ElapsedMilliseconds + "ms (含取缓存/查库/转换)");'''),
], "主窗口.cs")
print("合计", n, "处")
+221
View File
@@ -0,0 +1,221 @@
# -*- coding: utf-8 -*-
"""全量价格在线预热:遍历全树所有系列的所有规格组合,经本地旧 python 代理(8080,在线模式)
逐个请求 webGetPrice 存入 proxy_cache;完成后重跑 migrate_price_cache.py 平移到新服务。
用法:先双击 启动旧代理预热数据.bat(联网),确认 http://localhost:8080/standalone 能开,
然后: python tools/preheat_prices.py (全量,约 3-6 万请求,20-60 分钟)
python tools/preheat_prices.py 1122 1506 (只预热指定系列,调试用)
组合策略:每系列"全默认组合 + 每组每个选项单独变化"——覆盖换任一规格的真实查询主路径;
可选 ALL=1 环境变量改全笛卡尔积(每系列上限 300)。
(请求走 http.client 直连 127.0.0.1:8080 本地代理,不访问任何外部地址。)
"""
import base64
import http.client
import json
import os
import sys
import time
import zipfile
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ZIPS = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "元件库网页", "data", "dian", "electricData_zips")
ONLY = sys.argv[1:]
全组合 = os.environ.get("ALL") == "1"
HOST, PORT, PATH = "127.0.0.1", 8080, "/dx2/pcprice/default/dx2/interface/webGetPrice"
def 全树系列():
z = zipfile.ZipFile(os.path.join(ZIPS, "s.zip"))
d = json.loads(z.read("s.js"))
return [(x["a"], x["b"]) for x in d if x.get("c") == 1]
def 在线拉ZIP(v逻辑名):
"""经本地旧代理在线拉 electricData 包(自动缓存);返回 bytes(ZIP)。"""
v = base64.b64encode(v逻辑名.encode()).decode().rstrip("=")
try:
conn = http.client.HTTPConnection(HOST, PORT, timeout=25)
conn.request("GET", "/electricData?v=" + v + "&t=" + str(int(time.time() * 1000)))
r = conn.getresponse()
body = r.read()
conn.close()
return body if r.status == 200 and len(body) > 100 else None
except Exception:
return None
def 树系列(body):
"""从树 ZIP bytes 解析叶子系列号(s 字段)列表。"""
import io as _io
try:
z = zipfile.ZipFile(_io.BytesIO(body))
d = json.loads(z.read(z.namelist()[0]))
return [x["s"] for x in d if isinstance(x, dict) and x.get("s")]
except Exception:
return []
def 全部系列():
"""f.zip=厂家列表(o=代表系列号=树id)→拉每厂树→汇总全部叶子系列号。"""
z = zipfile.ZipFile(os.path.join(ZIPS, "f.zip"))
f = json.loads(z.read(z.namelist()[0]))
# ★ 树 id = f 字段(正泰=1 德力西=2,与 1.zip/2.zip 吻合);o 是代表系列号不是树 id
厂家 = sorted(set(x["f"] for x in f
if isinstance(x, dict) and x.get("f") not in (0, "0", None, "", False)),
key=str)
print("厂家树数:", len(厂家), flush=True)
系列 = []
拉树成功 = 0
for i, o in enumerate(厂家):
# 本地已有树的直接读
zp = os.path.join(ZIPS, str(o) + ".zip")
leaves = []
if os.path.exists(zp):
try:
zz = zipfile.ZipFile(zp)
leaves = 树系列(zz.read(zz.namelist()[0]))
except Exception:
pass
if not leaves:
body = 在线拉ZIP("z_seri/%s.jss" % o)
if body:
leaves = 树系列(body)
time.sleep(0.1)
if leaves:
拉树成功 += 1
系列.extend(leaves)
if (i + 1) % 50 == 0:
print(" 厂家进度 %d/%d, 已拉树 %d, 系列累计 %d" % (i + 1, len(厂家), 拉树成功, len(系列)), flush=True)
去重 = sorted(set(str(x) for x in 系列))
print("树拉取 %d/%d, 系列总数(去重) %d" % (拉树成功, len(厂家), len(去重)), flush=True)
return [int(x) for x in 去重]
def 在线拉系列ZIP(系列id):
"""经本地旧代理在线拉系列规格包(z_seri/N.jss);返回解析出的 b 数组。"""
body = 在线拉ZIP("z_seri/%s.jss" % 系列id)
if body is None:
return None
import io as _io
try:
z = zipfile.ZipFile(_io.BytesIO(body))
data = json.loads(z.read(z.namelist()[0]))
return data.get("b", []) if isinstance(data, dict) else []
except Exception:
return None
def 规格组合(系列id):
"""从系列 ZIP 的 b 数组解析规格组:ad=组号,ab=选项id。
返回组合列表(选项连串,如 "337-350-15-9-29-30-37")。"""
zp = os.path.join(ZIPS, str(系列id) + ".zip")
b = None
if os.path.exists(zp):
z = zipfile.ZipFile(zp)
inner = z.namelist()[0]
data = json.loads(z.read(inner))
data = None
zp = os.path.join(ZIPS, str(系列id) + ".zip")
import io as _io
if os.path.exists(zp):
try:
data = json.loads(zipfile.ZipFile(zp).read(zipfile.ZipFile(zp).namelist()[0]))
except Exception:
data = None
if data is None:
body = 在线拉ZIP("z_seri/%s.jss" % 系列id)
if body is None:
return []
try:
data = json.loads(zipfile.ZipFile(_io.BytesIO(body)).read(
zipfile.ZipFile(_io.BytesIO(body)).namelist()[0]))
except Exception:
return []
# ★ 规格组=顶层 c 键里 bc==0 的项(组名 aj),选项=ar[].as;b 数组是附件树不是规格
groups = {}
c = data.get("c") if isinstance(data, dict) else None
if not c:
return []
for 组 in c:
if not isinstance(组, dict) or 组.get("bc") != 0:
continue
p_ = 组.get("p")
ar = 组.get("ar") or []
选项 = [o["as"] for o in ar if isinstance(o, dict) and o.get("as") not in (None, 0)]
if p_ is not None and 选项:
groups.setdefault(p_, [])
for a in 选项:
if a not in groups[p_]:
groups[p_].append(a)
if not groups:
return []
ads = sorted(groups)
默认 = [str(groups[ad][0]) for ad in ads]
组合 = ["-".join(默认)]
for i, ad in enumerate(ads):
for ab in groups[ad][1:]:
c = list(默认)
c[i] = str(ab)
组合.append("-".join(c))
if 全组合:
import itertools
allc = ["-".join(p) for p in itertools.product(*[[str(a) for a in groups[ad]] for ad in ads])]
组合 = allc[:300]
return 组合
def 查价(系列id, 组合):
参数 = {"s": str(系列id), "options": [组合], "time": int(time.time() * 1000), "from": "dx2jsapi"}
info = base64.b64encode(json.dumps(参数, ensure_ascii=False).encode("utf-8")).decode()
try:
conn = http.client.HTTPConnection(HOST, PORT, timeout=15)
conn.request("GET", PATH + "?infoJson=" + info)
r = conn.getresponse()
body = r.read()
conn.close()
ok = b'"code": 0' in body or b'"code":0' in body
return len(body), ok
except Exception as e:
return -1, str(e)[:50]
def main():
if ONLY:
系列 = [int(o) for o in ONLY]
print("指定系列数:", len(系列))
else:
系列 = 全部系列() # f.zip 厂家→每厂树→全部叶子系列号
import threading
lock = threading.Lock()
stat = [0, 0, 0] # 序列idx, 总, 成功
def 处理(sid):
组合 = 规格组合(sid)
局部总 = 局部成 = 0
for c in 组合:
n, ok = 查价(sid, c)
局部总 += 1
if ok:
局部成 += 1
time.sleep(0.01)
with lock:
stat[0] += 1
stat[1] += 局部总
stat[2] += 局部成
if stat[0] % 100 == 0:
速率 = stat[1] / max(1, time.time() - t0)
print("[%d/%d] 系列%s: %d组合 累计%d 成功%d (%.1f/s)" %
(stat[0], len(系列), sid, len(组合), stat[1], stat[2], 速率), flush=True)
t0 = time.time()
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=4) as pool:
list(pool.map(处理, 系列))
总, 成功 = stat[1], stat[2]
print()
print("完成: 请求 %d, 成功 %d, 用时 %.0f 分钟" % (总, 成功, (time.time() - t0) / 60))
print("下一步: python tools/migrate_price_cache.py 平移到新服务")
if __name__ == "__main__":
main()
+252
View File
@@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
"""①把 MenuTuhao_AddToFak_Click 的复制逻辑抽成 复制图号到方案库(可复用)
②Click 改为调用 ③新增项目表右键"加入方案库"(整项目) ④挂右键菜单。幂等。"""
import io
import re
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
s = io.open(P, encoding="utf-8-sig").read()
n = 0
def rep(old, new, tag):
global s, n
if old not in s:
if new in s:
print(tag, "跳过")
return
raise AssertionError(tag + " 缺失: " + old[:70])
s = s.replace(old, new, 1)
n += 1
print(tag, "ok")
# ---- 定位 MenuTuhao_AddToFak_Click 方法体 ----
start_anchor = " private void MenuTuhao_AddToFak_Click(object sender, EventArgs e)"
i0 = s.find(start_anchor)
assert i0 > 0, "AddToFak 方法头缺失"
# 方法结束:其后第一个 \n }\n (8空格闭括号=方法级)
m_end = re.compile(r"\n \}\r?\n").search(s, i0)
end = m_end.end()
body = s[i0:end]
print("AddToFak 方法体长度:", len(body))
# ---- 1) 生成 复制图号到方案库 方法:参数化 _currentTuhao→图号内部ID ----
core = body
# 换方法名与签名
core = core.replace(
" private void MenuTuhao_AddToFak_Click(object sender, EventArgs e)\n {",
" /// <summary>复制一个图号(含箱号/元件子树)从项目库到方案库。供单图号加入与整项目加入共用。</summary>\n"
" private void 复制图号到方案库(string projectId, string 图号内部ID, string srcDbPath, string fakDbPath)\n"
" {", 1)
# 参数化 _currentTuhao
core = core.replace("_currentTuhao", "图号内部ID")
# 删 Click 专属校验段(从开头到 fakDbPath 定义前)
m_fak = re.search(r"(\n\s*string fakDbPath = GetDbFullPath\(@\"sjk\\\\fak\.db\"\);)", core)
if not m_fak:
m_fak = re.search(r"(\n\s*string fakDbPath = GetDbFullPath\(.+fak\.db\"\);)", core)
assert m_fak, "fakDbPath 行未找到"
# ★ 保留新方法签名(否则切割会把它连同校验段一起丢掉)
new_sig = (" /// <summary>复制一个图号(含箱号/元件子树)从项目库到方案库。供单图号加入与整项目加入共用。</summary>\n"
" private void 复制图号到方案库(string projectId, string 图号内部ID, string srcDbPath, string fakDbPath)\n"
" {")
core = new_sig + "\n" + core[m_fak.start(1):] # 签名 + 从 fakDbPath 定义开始保留
# 删尾部 ReloadFakData + MessageBox(调用方负责)
core = re.sub(r"\n\s*// 刷新方案页面的图号表\r?\n\s*ReloadFakData\(\);\r?\n", "\n", core)
core = re.sub(r"\n\s*string msg = \"已把图号[^\r\n]*\r?\n(?:\s+[^\r\n]*\r?\n)*?\s*MessageBox\.Show\(this, msg, \"加入方案库\",[^;]*;\r?\n", "\n", core)
# 内部 xhCount/yjCount 声明保留;统计行保留
new_method = core
# ---- 2) 原 Click 改为调方法 ----
new_click = ''' private void MenuTuhao_AddToFak_Click(object sender, EventArgs e)
{
Log("点击[加入方案库], _currentProjectId=[" + _currentProjectId +
"] _currentTuhao=[" + _currentTuhao + "]");
// 1) 校验当前选中状态
if (string.IsNullOrWhiteSpace(_currentProjectId))
{
MessageBox.Show(this, "请先在项目表中双击选择一个项目,再加入方案库。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrWhiteSpace(_currentTuhao))
{
MessageBox.Show(this, "请先在图号表中选择要加入方案库的图号。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 2) 先保存当前三个表的未提交编辑,确保复制到方案库的是最新数据
try { SaveAllQuoteTablesIfDirty(false); }
catch (Exception exSave) { Log("[加入方案库] 预保存失败(继续): " + exSave.Message); }
string srcDbPath = GetQuoteDbPath(_currentProjectId);
if (!System.IO.File.Exists(srcDbPath))
{
MessageBox.Show(this, "项目报价库不存在:\\n" + srcDbPath,
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string fakDbPath = GetDbFullPath(@"sjk\\fak.db");
try
{
复制图号到方案库(_currentProjectId, _currentTuhao, srcDbPath, fakDbPath);
Log("[加入方案库] 成功");
ReloadFakData();
MessageBox.Show(this, "已把图号 [" + _currentTuhao + "] 加入方案库。", "加入方案库",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception exAdd)
{
Log("[加入方案库] 失败: " + exAdd);
MessageBox.Show(this, "加入方案库失败:\\n" + exAdd.Message, "加入方案库",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}'''
s = s[:i0] + new_click + "\n" + new_method + s[end:]
n += 1
print("AddToFak 抽方法重构 ok")
# ---- 3) 新增 项目表右键"加入方案库"(整项目) ----
proj_click = '''
/// <summary>项目表右键「加入方案库」:把整个项目(全部图号+箱号+元件+项目信息)复制到方案库。</summary>
private void MenuProject_AddToFak_Click(object sender, EventArgs e)
{
// 取当前选中项目(项目表当前行)
var row = GetFirstSelectedRow(this.advancedDataGridView6);
if (row == null)
{
MessageBox.Show(this, "请先在项目列表中选择要加入方案库的项目。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
string projectId = GetBoundValueAsString(row, "项目编号");
if (string.IsNullOrWhiteSpace(projectId))
{
MessageBox.Show(this, "选中行没有项目编号,无法加入方案库。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string srcDbPath = GetQuoteDbPath(projectId);
if (!System.IO.File.Exists(srcDbPath))
{
MessageBox.Show(this, "项目报价库不存在:\\n" + srcDbPath,
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string fakDbPath = GetDbFullPath(@"sjk\\fak.db");
var sw = System.Diagnostics.Stopwatch.StartNew();
Cursor = Cursors.WaitCursor;
try
{
// 1) 该项目的全部图号(内部ID,按序号顺序)
var 图号们 = new System.Collections.Generic.List<string>();
using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnString(srcDbPath)))
{
conn.Open();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 内部ID FROM " + TuhaoTableName + " ORDER BY CAST(序号 AS INTEGER);", conn))
using (var r = cmd.ExecuteReader())
while (r.Read())
{
if (!r.IsDBNull(0))
{
string v = r.GetString(0);
if (!string.IsNullOrEmpty(v)) 图号们.Add(v);
}
}
}
if (图号们.Count == 0)
{
MessageBox.Show(this, "项目 [" + projectId + "] 没有图号数据,无法加入方案库。",
"加入方案库", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 2) 逐图号复制到方案库
foreach (var thId in 图号们)
复制图号到方案库(projectId, thId, srcDbPath, fakDbPath);
// 3) 项目信息:该项目库的项目信息行覆盖 fak 的项目信息(项目页客户列显示用)
string projTbl = ProjectInfoTableName;
using (var srcConn = new System.Data.SQLite.SQLiteConnection(GetConnString(srcDbPath)))
using (var dstConn = new System.Data.SQLite.SQLiteConnection(GetConnString(fakDbPath)))
{
srcConn.Open();
dstConn.Open();
var infoRow = new DataTable();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT * FROM " + projTbl + " WHERE 项目编号=@id LIMIT 1;", srcConn))
{
cmd.Parameters.AddWithValue("@id", projectId);
using (var ad = new System.Data.SQLite.SQLiteDataAdapter(cmd)) ad.Fill(infoRow);
}
var cols = new System.Collections.Generic.List<string>();
foreach (DataColumn c in infoRow.Columns) cols.Add(c.ColumnName);
using (var tx = dstConn.BeginTransaction())
{
Exec(dstConn, tx, "DELETE FROM " + projTbl + ";");
foreach (DataRow r in infoRow.Rows)
InsertRowByColumns(r, projTbl, cols, dstConn, tx);
tx.Commit();
}
}
sw.Stop();
Log("[加入方案库] 整项目完成: 项目=" + projectId + " 图号=" + 图号们.Count + " 耗时 " + sw.ElapsedMilliseconds + "ms");
ReloadFakData();
MessageBox.Show(this, "已把项目 [" + projectId + "] 加入方案库。\\n共 " + 图号们.Count + " 个图号,耗时 " +
sw.ElapsedMilliseconds + "ms。", "加入方案库",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception exAdd)
{
Log("[加入方案库] 整项目失败: " + exAdd);
MessageBox.Show(this, "加入方案库失败:\\n" + exAdd.Message, "加入方案库",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
Cursor = Cursors.Default;
}
}
/// <summary>Exec 简写(删除表数据用)。</summary>
private static void Exec(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, string sql)
{
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx))
cmd.ExecuteNonQuery();
}
'''
# ---- 插入 proj_click 到 菜单与库.cs(在 复制图号到方案库 方法之前) ----
anchor = " /// <summary>复制一个图号(含箱号/元件子树)从项目库到方案库。供单图号加入与整项目加入共用。</summary>"
print("anchor in s:", anchor in s, "| find:", s.find(anchor)); pos = s.find(anchor)
assert pos > 0, "插入锚缺失"
s = s[:pos] + proj_click + "\n" + s[pos:]
n += 1
print("项目右键方法插入 ok")
# ---- 4) 项目表右键菜单注册(MainForm.cs 挂方案菜单附近)——项目表无右键菜单,新建
p_main = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\MainForm.cs"
s4 = io.open(p_main, encoding="utf-8-sig").read()
old4 = ''' // ===== 方案页面(tabPage13)三个表格的联动 ====='''
new4 = ''' // ===== 项目表右键:加入方案库(整项目) =====
var _menuProject = new ContextMenuStrip();
_menuProject.Items.Add("加入方案库", null, MenuProject_AddToFak_Click);
this.advancedDataGridView6.ContextMenuStrip = _menuProject;
Log(" 项目表菜单已挂到 advancedDataGridView6(加入方案库)");
// ===== 方案页面(tabPage13)三个表格的联动 ====='''
assert old4 in s4
io.open(p_main, "w", encoding="utf-8-sig").write(s4.replace(old4, new4, 1))
n += 1
print("右键菜单注册 ok")
io.open(P, "w", encoding="utf-8-sig").write(s)
print("合计", n, "处")
+127
View File
@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
"""pdfbt Form3 列定义重建为标准设计器格式(57 字段+new+属性块+AddRange),设计器可见中文列头。
列属性数据从 cadbt Form3.ui.cs 索引段提取(权威参照);行内容定位插入(缩进无关)。"""
import io
import re
CADBT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\cadbt\cadbt\Plugin\Form3.ui.cs"
PDFBT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\pdfbt\Plugin\Form3.Designer.cs"
TABLES = ["advancedDataGridView6", "advancedDataGridView9", "advancedDataGridView4", "advancedDataGridView5"]
cs = io.open(CADBT, encoding="utf-8-sig").read()
ps = io.open(PDFBT, encoding="utf-8-sig").read()
# ---------- 1) 从 cadbt 解析每表每列属性 ----------
pat_idx = re.compile(r"this\.(\w+)\.Columns\[(\d+)\]\.(\w+) = (.*?);")
cols = {t: {} for t in TABLES}
pat_add = re.compile(r"this\.(\w+)\.Columns\.Add\(new System\.Windows\.Forms\.DataGridViewTextBoxColumn\(\)\);")
for l in cs.split("\n"):
t = l.strip()
m = pat_add.match(t)
if m and m.group(1) in TABLES:
continue
m = pat_idx.match(t)
if m and m.group(1) in cols:
tbl, idx, prop, val = m.group(1), int(m.group(2)), m.group(3), m.group(4)
cols[tbl].setdefault(idx, {})[prop] = val
for t in TABLES:
n = len(cols[t])
assert n > 0 and cols[t].get(0, {}).get("HeaderText"), t + " 解析不完整"
print(t, "解析列数", n)
# ---------- 2) 生成标准设计器代码 ----------
gnum = 0
fields, news, blocks = [], [], []
addRanges = {}
for tbl in TABLES:
ids = []
for idx in sorted(cols[tbl].keys()):
gnum += 1
fname = "dataGridViewTextBoxColumn%d" % gnum
ids.append(fname)
fields.append("\t\tprivate System.Windows.Forms.DataGridViewTextBoxColumn %s;" % fname)
news.append("\t\t\tthis.%s = new System.Windows.Forms.DataGridViewTextBoxColumn();" % fname)
props = dict(cols[tbl][idx])
defaults = {"Width": "100", "Visible": "true", "ReadOnly": "true", "Frozen": "false",
"AutoSizeMode": "System.Windows.Forms.DataGridViewAutoSizeColumnMode.NotSet",
"SortMode": "System.Windows.Forms.DataGridViewColumnSortMode.NotSortable"}
for k, v in defaults.items():
props.setdefault(k, v)
blocks.append("\t\t\t// ")
blocks.append("\t\t\t// %s" % fname)
blocks.append("\t\t\t// ")
blocks.append('\t\t\tthis.%s.Name = "%s";' % (fname, fname))
for k in ["HeaderText", "Width", "Visible", "ReadOnly", "Frozen", "AutoSizeMode",
"DataPropertyName", "SortMode"]:
if k in props:
blocks.append("\t\t\tthis.%s.%s = %s;" % (fname, k, props[k]))
inner = ",\n".join("\t\t\t\tthis." + i for i in ids)
addRanges[tbl] = ("\t\t\tthis.%s.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {\n" % tbl
+ inner + "\n\t\t\t});")
# ---------- 3) 重写 pdfbt ----------
def lines_of(text):
return text.split("\n")
def to_text(lines):
return "\n".join(lines)
# 3a) 删索引式段
for tbl in TABLES:
pat_a = re.compile(r"this\." + tbl + r"\.Columns\.Add\(")
pat_i = re.compile(r"this\." + tbl + r"\.Columns\[\d+\]\.")
out, in_seg, seen = [], False, False
for l in lines_of(ps):
t = l.strip()
if pat_a.search(t):
in_seg, seen = True, True
continue
if in_seg:
if pat_i.search(t) or t == "":
continue
in_seg = False
out.append(l)
assert seen, tbl + " 索引段未找到"
ps = to_text(out)
print(tbl, "索引段已删")
# 3b) 每表 ColumnHeadersDefaultCellStyle 行后插 AddRange
for tbl in TABLES:
ls = lines_of(ps)
key = "this.%s.ColumnHeadersDefaultCellStyle" % tbl
done = False
for i, l in enumerate(ls):
if key in l:
ls.insert(i + 1, addRanges[tbl])
done = True
break
assert done, tbl + " 样式行未找到"
ps = to_text(ls)
# 3c) new 行插在 InitializeComponent 里第一个 cellStyle 声明行前
ls = lines_of(ps)
done = False
for i, l in enumerate(ls):
if "System.Windows.Forms.DataGridViewCellStyle" in l and "= new" in l:
ls[i:i] = news
done = True
break
assert done, "cellStyle 首行未找到"
ps = to_text(ls)
# 3d) 字段声明插在 components = null 行后
ls = lines_of(ps)
done = False
for i, l in enumerate(ls):
if "private System.ComponentModel.IContainer components = null;" in l:
ls[i + 1:i + 1] = fields
done = True
break
assert done, "components 声明未找到"
ps = to_text(ls)
io.open(PDFBT, "w", encoding="utf-8-sig").write(ps)
print("重建完成: 字段%d new%d HeaderText%d" % (len(fields), len(news), len(re.findall("HeaderText", ps))))
+169
View File
@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
"""触发器生成器重构:①去掉聚合用列引用模式 ②箱号表数量触发器按箱号行生成(WHEN 行ID,真 SUM)
③图号行触发器传箱号表 ④重建/新表入口同步。幂等。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\计算引擎.cs"
s = io.open(P, encoding="utf-8-sig").read()
n = 0
def rep(old, new):
global s, n
if new in s and old not in s:
print("已改(跳过):", old[:50].replace("\n", " "))
return
assert old in s, "锚点缺失: " + old[:80]
s = s.replace(old, new, 1)
n += 1
# 1) 子翻译 透传清理
rep("p.引用模式 = 引用模式; p.下层表 = 下层表; p.层内公式 = 层内公式; p.展开栈 = 展开栈; p.聚合用列引用 = 聚合用列引用;",
"p.引用模式 = 引用模式; p.下层表 = 下层表; p.层内公式 = 层内公式; p.展开栈 = 展开栈;")
# 2) 解析器删字段
rep(''' public string 引用模式 = "NEW"; // NEW=触发器 NEW."列" / ROW=上级目标行 "列"
public bool 聚合用列引用 = false; // true=SUM('列') 直接引用本行列(该聚合由下级触发器维护)''',
''' public string 引用模式 = "NEW"; // NEW=触发器 NEW."列" / ROW=上级目标行 "列"''')
# 3) Sum子查询 删分支
rep(''' /// <summary>SUM('列') → 对下层表整表求和;或(自触发场景)直接引用本行列(由下级触发器维护)。</summary>
private string Sum子查询(string col)
{
string c = "\\"" + col.Replace("\\"", "\\"\\"") + "\\"";
if (聚合用列引用)
return "COALESCE(CAST(" + (引用模式 == "NEW" ? "NEW." : "") + c + " AS REAL),0)";
return "COALESCE((SELECT CAST(SUM(CAST(" + c + " AS REAL)) AS REAL) FROM \\"" + 下层表.Replace("\\"", "\\"\\"") + "\\"),0)";
}''',
''' /// <summary>SUM('列') → 对下层表整表求和(每箱号一张元件表/每图号一张箱号表,无条件)。</summary>
private string Sum子查询(string col)
{
return "COALESCE((SELECT CAST(SUM(CAST(\\"" + col.Replace("\\"", "\\"\\"") +
"\\" AS REAL)) AS REAL) FROM \\"" + 下层表.Replace("\\"", "\\"\\"") + "\\"),0)";
}''')
# 4) 翻译公式 删参数
rep(''' private static string 翻译公式(string 表达式, Dictionary<string, 公式条目> 层内公式,
string 引用模式, string 下层表, out string 错误, bool 聚合用列引用 = false)''',
''' private static string 翻译公式(string 表达式, Dictionary<string, 公式条目> 层内公式,
string 引用模式, string 下层表, out string 错误)''')
rep(''' { 引用模式 = 引用模式, 下层表 = 下层表, 层内公式 = 层内公式, 展开栈 = new HashSet<string>(), 聚合用列引用 = 聚合用列引用 };''',
''' { 引用模式 = 引用模式, 下层表 = 下层表, 层内公式 = 层内公式, 展开栈 = new HashSet<string>() };''')
# 5) 生成Set片段 删参数
rep("private static string 生成Set片段(List<公式条目> 公式, string 引用模式, string 下层表, bool 聚合用列引用 = false)",
"private static string 生成Set片段(List<公式条目> 公式, string 引用模式, string 下层表)")
rep("string sql = 翻译公式(f.表达式, map, 引用模式, 下层表, out err, 聚合用列引用);",
"string sql = 翻译公式(f.表达式, map, 引用模式, 下层表, out err);")
rep("{ string e2; sql = 翻译公式(d[2], map, 引用模式, 下层表, out e2, 聚合用列引用); break; }",
"{ string e2; sql = 翻译公式(d[2], map, 引用模式, 下层表, out e2); break; }")
# 6) 建箱号表触发器:按行生成
rep(''' /// <summary>为一张箱号表生成触发器(u/i/d):行内公式 + 所属图号行聚合。</summary>
public static void 建箱号表触发器(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
string 箱号表, string 图号ID, string 图号表名,
List<公式条目> 箱号公式, List<公式条目> 图号公式)
{
Drop触发器(conn, tx, 箱号表);
string of = "";
foreach (var c in 箱号输入列) of += "\\"" + c + "\\",";
of = of.TrimEnd(',');
string xhSetNew = 生成Set片段(箱号公式, "NEW", null, true); // 本行(数量变);单台成本等由元件触发器维护
string xhSetRow = 生成Set片段(箱号公式, "ROW", 箱号表, true); // 同上
string thSet = 生成Set片段(图号公式, "ROW", 箱号表);
string 刷图号 = "UPDATE \\"" + 图号表名 + "\\" SET " + thSet + " WHERE \\"内部ID\\"='" + 图号ID.Replace("'", "''") + "';";
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_u AFTER UPDATE OF " + of + " ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetNew +
" WHERE rowid=NEW.rowid; " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_i AFTER INSERT ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetRow +
" WHERE rowid=NEW.rowid; " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_d AFTER DELETE ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");
}''',
''' /// <summary>
/// 为一张箱号表生成触发器。箱号聚合依赖"每行各自的元件表",数量更新触发器按行生成
/// (WHEN NEW."内部ID"=行ID,体内 SUM 子查询 FROM 该行的元件表);INSERT/DELETE 表级只刷图号聚合。
/// 行列表:每项 [箱号ID, 该行元件表名]。
/// </summary>
public static void 建箱号表触发器(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
string 箱号表, string 图号ID, string 图号表名, List<string[]> 行列表,
List<公式条目> 箱号公式, List<公式条目> 图号公式)
{
Drop触发器(conn, tx, 箱号表);
string of = "";
foreach (var c in 箱号输入列) of += "\\"" + c + "\\",";
of = of.TrimEnd(',');
string thSet = 生成Set片段(图号公式, "ROW", 箱号表);
string 刷图号 = "UPDATE \\"" + 图号表名 + "\\" SET " + thSet + " WHERE \\"内部ID\\"='" + 图号ID.Replace("'", "''") + "';";
foreach (var 行 in 行列表)
{
string 行ID = 行[0], yj表 = 行[1];
if (string.IsNullOrEmpty(yj表)) continue;
string xhSetNew = 生成Set片段(箱号公式, "NEW", yj表);
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_r_" + Safe(行ID) +
"_u AFTER UPDATE OF " + of + " ON \\"" + 箱号表 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
行ID.Replace("'", "''") + "' BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetNew +
" WHERE rowid=NEW.rowid; " + 刷图号 + "END;");
}
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_i AFTER INSERT ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_d AFTER DELETE ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");
}''')
# 7) 建图号行触发器:真 SUM
rep(''' public static void 建图号行触发器(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
string 图号表名, string 图号ID, List<公式条目> 图号公式)
{
string thSetNew = 生成Set片段(图号公式, "NEW", null, true);''',
''' public static void 建图号行触发器(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
string 图号表名, string 图号ID, string 箱号表, List<公式条目> 图号公式)
{
string thSetNew = 生成Set片段(图号公式, "NEW", 箱号表);''')
# 8) 重建项目全部触发器:组装行列表
rep(''' // 先建箱号表映射后建元件触发器(元件触发器引用箱号表名,顺序无碍;图号行触发器每图号一次)
var 已建图号 = new HashSet<string>();
foreach (var m in 箱号行列表)
建箱号表触发器(conn, tx, m[0], m[1], 图号表名, 箱号公式, 图号公式);
foreach (var m in 元件表列表)
建元件表触发器(conn, tx, m[0], m[1], m[2], m[3], 图号表名, 元件公式, 箱号公式, 图号公式);
foreach (var kv in 箱号表Of图号)
if (已建图号.Add(kv.Key))
建图号行触发器(conn, tx, 图号表名, kv.Key, 图号公式);''',
''' // 按箱号表分组元件表:[箱号表] → [箱号ID, 元件表] 列表
var 行按表 = new Dictionary<string, List<string[]>>();
foreach (var m in 元件表列表)
{
List<string[]> rows;
if (!行按表.TryGetValue(m[1], out rows)) { rows = new List<string[]>(); 行按表[m[1]] = rows; }
rows.Add(new[] { m[2], m[0] });
}
var 已建图号 = new HashSet<string>();
foreach (var m in 箱号行列表)
{
List<string[]> rows;
if (!行按表.TryGetValue(m[0], out rows)) rows = new List<string[]>();
建箱号表触发器(conn, tx, m[0], m[1], 图号表名, rows, 箱号公式, 图号公式);
}
foreach (var m in 元件表列表)
建元件表触发器(conn, tx, m[0], m[1], m[2], m[3], 图号表名, 元件公式, 箱号公式, 图号公式);
foreach (var kv in 箱号表Of图号)
if (已建图号.Add(kv.Key))
建图号行触发器(conn, tx, 图号表名, kv.Key, kv.Value, 图号公式);''')
# 9) 建新表触发器:箱号分支传空行列表
rep(''' else if (新表类型 == "箱号")
建箱号表触发器(conn, tx, 箱号表, 图号ID, 图号表名, 箱号公式, 图号公式);''',
''' else if (新表类型 == "箱号")
{
// 新箱号表暂无行级元件表映射;行级触发器由"建元件表触发器时同批重建/进项目自检"补齐
建箱号表触发器(conn, tx, 箱号表, 图号ID, 图号表名, new List<string[]>(), 箱号公式, 图号公式);
}''')
io.open(P, "w", encoding="utf-8-sig").write(s)
print("重构完成", n, "处")
+73
View File
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
"""把 xiangmu 窗体里的 CJZZ.Controls 第三方控件替换为标准 WinForms 控件,
让 SharpDevelop 设计器能正常打开编辑。"""
import io
import re
import os
BASE = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\xiangmu"
# 控件类型映射
TYPE_MAP = {
"CJZZ.Controls.ResizableComboBox": "System.Windows.Forms.ComboBox",
"CJZZ.Controls.ResizableTextBox": "System.Windows.Forms.TextBox",
"CJZZ.Controls.RoundedButton": "System.Windows.Forms.Button",
"CJZZ.Controls.MultiSelectComboBox": "System.Windows.Forms.ComboBox",
}
# 需要删除的 CJZZ 特有属性行(按前缀匹配)
CJZZ_PROPS = [
"BorderColor", "HoverBorderColor", "FocusBorderColor", "PlaceholderColor",
"PlaceholderText", "Flat", "Radius", "Items.PerLine", "Watermark",
"ShowClearButton", "ArrowColor", "ArrowHoverColor",
]
def fix_file(path):
s = io.open(path, encoding="utf-8-sig").read()
orig = s
n_changed = 0
# 1) 替换类型声明
for old_t, new_t in TYPE_MAP.items():
old_decl = "private " + old_t + " "
new_decl = "private " + new_t + " "
if old_decl in s:
s = s.replace(old_decl, new_decl)
n_changed += s.count(new_decl) - orig.count(new_decl)
old_new = "new " + old_t + "()"
new_new = "new " + new_t + "()"
if old_new in s:
s = s.replace(old_new, new_new)
n_changed += 1
# 2) 删除 CJZZ 特有属性行
lines = s.split("\n")
out = []
removed = 0
for line in lines:
stripped = line.strip()
skip = False
for prop in CJZZ_PROPS:
# 形如 this.xxx.PropName = ...;
m = re.match(r"this\.\w+\.(" + prop + r")\s*=", stripped)
if m:
skip = True
removed += 1
break
if not skip:
out.append(line)
s = "\n".join(out)
if s != orig:
io.open(path, "w", encoding="utf-8-sig", newline="\r\n").write(s)
print("%s: 类型替换=%d, 删除属性行=%d" % (os.path.basename(path), n_changed, removed))
else:
print("%s: 无变化" % os.path.basename(path))
if __name__ == "__main__":
for name in os.listdir(BASE):
if name.endswith(".ui.cs"):
fix_file(os.path.join(BASE, name))
print("\n完成。")
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""恢复 pdfbt Form3 四表列定义:照 cadbt Form3.ui.cs 的索引式列(Columns.Add+Columns[N]属性)
替换 pdfbt 的空壳字段+AddRange,并清理 57 个无用列字段。幂等(重复跑前检查)。"""
import io
import re
CADBT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\cadbt\cadbt\Plugin\Form3.ui.cs"
PDFBT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\pdfbt\Plugin\Form3.Designer.cs"
cs = io.open(CADBT, encoding="utf-8-sig").read()
ps = io.open(PDFBT, encoding="utf-8-sig").read()
TABLES = ["advancedDataGridView6", "advancedDataGridView9", "advancedDataGridView4", "advancedDataGridView5"]
def 提取cadbt段(tbl):
"""从 cadbt 提取该表的 Columns.Add...Columns[N] 连续段(含首尾)。"""
i = cs.find("this." + tbl + ".Columns.Add(")
assert i > 0, tbl + " Add 未找到"
lines = cs[i:].split("\n")
out = []
for l in lines:
t = l.strip()
if ("Columns.Add(" in t) or re.match(re.escape(tbl) + r"\.Columns\[\d+\]\.", t):
out.append(l)
elif out and t == "":
# 允许段内不换段(索引段中间无空行),遇其他语句即止
continue
elif out:
break
assert out, tbl + " 段为空"
return "\n".join(out)
def 替换pdfbt段(tbl, new_seg):
"""把 pdfbt 该表的 Columns.AddRange(...) 块替换为 cadbt 索引段。"""
global ps
m = re.search(
r"this\." + tbl + r"\.Columns\.AddRange\(new System\.Windows\.Forms\.DataGridViewColumn\[\] \{.*?\}\);",
ps, re.S)
if not m:
# 已经替换过(幂等)
if "this." + tbl + ".Columns.Add(" in ps:
return False
raise AssertionError(tbl + " AddRange 未找到")
ps = ps[:m.start()] + new_seg + ps[m.end():]
return True
for tbl in TABLES:
seg = 提取cadbt段(tbl)
n = len([l for l in seg.split("\n") if "Columns.Add(" in l])
changed = 替换pdfbt段(tbl, seg)
print(tbl, "列数", n, "替换" if changed else "跳过(已改)")
# 删 57 个字段声明 + new 行(带 \t 或空格缩进两种都匹配)
ps = re.sub(r"\n\s*private System\.Windows\.Forms\.DataGridViewTextBoxColumn dataGridViewTextBoxColumn\d+;", "", ps)
ps = re.sub(r"\n\s*this\.dataGridViewTextBoxColumn\d+ = new System\.Windows\.Forms\.DataGridViewTextBoxColumn\(\);", "", ps)
io.open(PDFBT, "w", encoding="utf-8-sig").write(ps)
print("字段清理完成; HeaderText 数:", len(re.findall(r"HeaderText", ps)))
+37
View File
@@ -0,0 +1,37 @@
// resx2res: 把 .resx(XML) 编译成 .resources(二进制),等价 SDK 的 resgen。
// 背景:本机无 Windows SDK 无 resgen.exe;csc 的 /resource 只会原样嵌入文件,
// 直接嵌 resx 运行时报"流不是有效的资源文件"。msbuild(SD F5)会自动转换,
// 命令行 csc 编译前需先用本工具生成 .resources。
// 用法: resx2res <输入.resx> <输出.resources>
using System;
using System.Collections;
using System.Resources;
class Resx2Res
{
static int Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("用法: resx2res <in.resx> <out.resources>");
return 1;
}
using (var reader = new ResXResourceReader(args[0]))
{
reader.UseResXDataNodes = true;
using (var writer = new ResourceWriter(args[1]))
{
foreach (DictionaryEntry e in reader)
{
var node = e.Value as ResXDataNode;
object val = node != null
? node.GetValue((System.ComponentModel.Design.ITypeResolutionService)null)
: e.Value;
writer.AddResource((string)e.Key, val);
}
}
}
Console.WriteLine("OK: " + args[1]);
return 0;
}
}
+335
View File
@@ -0,0 +1,335 @@
# -*- coding: utf-8 -*-
"""方案窗口编辑区替换为主窗口完整保存链(虚拟模式版)。heredoc 太长,用文件执行。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\DockWindows\DockWindow8\DockWindow8.cs"
s = io.open(P, encoding="utf-8-sig").read()
start = s.find("\t\t// ============ 方案三表编辑直写")
end_anchor = "\t\tprivate string 查方案表2(System.Data.SQLite.SQLiteConnection conn, string 类型, string 图号, string 箱号)"
end = s.find(end_anchor)
assert start > 0 and end > start, "编辑区定位失败"
NEW = '''\t\t// ============================================================
\t\t// 方案三表实时保存 —— 主窗口报价页保存链的完整复制
\t\t// (虚拟模式:CellValueNeeded 供数 / CellValuePushed 写回 /
\t\t// 内存重算链乐观显示 / 300ms 防抖队列 / 后台单事务写库+聚合)
\t\t// ============================================================
\t\t/// <summary>虚拟模式供数(主窗口 Dgv_CellValueNeeded 同款)。</summary>
\t\tprivate void 方案表_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
\t\t{
\t\t\tvar dgv = sender as DataGridView;
\t\t\tvar table = 取方案数据表(dgv);
\t\t\tif (table == null || e.RowIndex < 0 || e.RowIndex >= table.Rows.Count) { e.Value = ""; return; }
\t\t\tif (e.ColumnIndex < 0 || e.ColumnIndex >= dgv.Columns.Count) { e.Value = ""; return; }
\t\t\tstring colName = dgv.Columns[e.ColumnIndex].DataPropertyName;
\t\t\tif (string.IsNullOrEmpty(colName) || !table.Columns.Contains(colName)) { e.Value = ""; return; }
\t\t\tobject v = table.Rows[e.RowIndex][colName];
\t\t\te.Value = (v == null || v == DBNull.Value) ? "" : v;
\t\t}
\t\t/// <summary>编辑立即提交(主窗口 Grid_DirtyStateChanged 同款)。</summary>
\t\tprivate void 方案表_DirtyStateChanged(object sender, EventArgs e)
\t\t{
\t\t\tvar dgv = sender as DataGridView;
\t\t\tif (dgv != null && dgv.IsCurrentCellDirty)
\t\t\t\tdgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
\t\t}
\t\t/// <summary>虚拟模式写回:写内存表 + 内存重算链(乐观显示) + 入队直写方案文件。</summary>
\t\tprivate void 方案表_CellValuePushed(object sender, DataGridViewCellValueEventArgs e)
\t\t{
\t\t\tvar dgv = sender as DataGridView;
\t\t\tvar table = 取方案数据表(dgv);
\t\t\tif (table == null || e.RowIndex < 0 || e.RowIndex >= table.Rows.Count) return;
\t\t\tif (e.ColumnIndex < 0 || e.ColumnIndex >= dgv.Columns.Count) return;
\t\t\tstring colName = dgv.Columns[e.ColumnIndex].DataPropertyName;
\t\t\tif (string.IsNullOrEmpty(colName) || colName == "_rowid_" || !table.Columns.Contains(colName)) return;
\t\t\ttable.Rows[e.RowIndex][colName] = (e.Value == null || e.Value == DBNull.Value) ? DBNull.Value : e.Value;
\t\t\t// ★ 内存重算链(乐观显示):元件→箱号→图号
\t\t\tif (_联动层 == 0 && !_方案派生列.Contains(colName))
\t\t\t{
\t\t\t\t_联动层++;
\t\t\t\ttry
\t\t\t\t{
\t\t\t\t\tif (dgv == advancedDataGridView元件) 方案重算元件行(e.RowIndex);
\t\t\t\t\telse if (dgv == advancedDataGridView箱号) 方案重算箱号行(e.RowIndex);
\t\t\t\t\telse if (dgv == advancedDataGridView图号) 方案重算图号行(e.RowIndex);
\t\t\t\t}
\t\t\t\tfinally { _联动层--; }
\t\t\t\tdgv.Invalidate();
\t\t\t\tif (dgv == advancedDataGridView元件)
\t\t\t\t{
\t\t\t\t\tadvancedDataGridView箱号.Invalidate();
\t\t\t\t\tadvancedDataGridView图号.Invalidate();
\t\t\t\t}
\t\t\t\telse if (dgv == advancedDataGridView箱号) advancedDataGridView图号.Invalidate();
\t\t\t}
\t\t\t// ★ 入队直写(主窗口 入队直写库 同款:300ms 防抖聚合)
\t\t\tif (!_方案派生列.Contains(colName))
\t\t\t\t入队方案直写(dgv, e.RowIndex, colName, e.Value);
\t\t}
\t\t// ---- 内存重算链(主窗口 RecalcYuanjianRow/RecalcXianghaoRowFromYuanjian/RecalcTuhaoRowFromXianghao 的方案版) ----
\t\tprivate static decimal 方案数值(object v)
\t\t{
\t\t\tdecimal d;
\t\t\treturn v == null || v == DBNull.Value ? 0 :
\t\t\t\t(decimal.TryParse(Convert.ToString(v).Trim(), System.Globalization.NumberStyles.Float,
\t\t\t\tSystem.Globalization.CultureInfo.InvariantCulture, out d) ? d : 0);
\t\t}
\t\tprivate void 方案重算元件行(int rowIndex)
\t\t{
\t\t\tvar t = _方案元件表;
\t\t\tif (t == null || rowIndex < 0 || rowIndex >= t.Rows.Count) return;
\t\t\tvar r = t.Rows[rowIndex];
\t\t\tdecimal 表价 = 方案数值(r["表价"]), 采购系数 = 方案数值(r["采购系数"]),
\t\t\t\t报出系数 = 方案数值(r["报出系数"]), 数量 = 方案数值(r["数量"]);
\t\t\tdecimal 采购单价 = 表价 * 采购系数;
\t\t\tif (t.Columns.Contains("采购单价")) r["采购单价"] = 采购单价;
\t\t\tif (t.Columns.Contains("采购价")) r["采购价"] = 采购单价 * 数量;
\t\t\tif (t.Columns.Contains("报出单价")) r["报出单价"] = 采购单价 * 报出系数;
\t\t\tif (t.Columns.Contains("报出价")) r["报出价"] = 采购单价 * 报出系数 * 数量;
\t\t\t方案重算箱号行全部(); // 所属箱号聚合 → 图号聚合
\t\t}
\t\tprivate void 方案重算箱号行(int rowIndex)
\t\t{
\t\t\t// 箱号行数量变:按内存元件表 SUM 重算本行 + 图号聚合
\t\t\t方案重算箱号行全部();
\t\t}
\t\tprivate void 方案重算箱号行全部()
\t\t{
\t\t\tvar xh = _方案箱号表;
\t\t\tvar yj = _方案元件表;
\t\t\tif (xh == null) return;
\t\t\tdecimal 采购合计 = 0, 报出合计 = 0;
\t\t\tif (yj != null)
\t\t\t{
\t\t\t\tforeach (DataRow r in yj.Rows)
\t\t\t\t{
\t\t\t\t\t采购合计 += 方案数值(r["采购价"]);
\t\t\t\t\t报出合计 += 方案数值(r["报出价"]);
\t\t\t\t}
\t\t\t}
\t\t\t// 写到当前选中箱号行(方案库每箱号一张元件表,当前元件表即当前箱号)
\t\t\tif (advancedDataGridView箱号.CurrentRow != null)
\t\t\t{
\t\t\t\tint idx = advancedDataGridView箱号.CurrentRow.Index;
\t\t\t\tif (idx >= 0 && idx < xh.Rows.Count)
\t\t\t\t{
\t\t\t\t\tvar row = xh.Rows[idx];
\t\t\t\t\tdecimal 数量 = 方案数值(row["数量"]);
\t\t\t\t\tif (xh.Columns.Contains("单台成本")) row["单台成本"] = 采购合计;
\t\t\t\t\tif (xh.Columns.Contains("成本价")) row["成本价"] = 采购合计 * 数量;
\t\t\t\t\tif (xh.Columns.Contains("单台报出")) row["单台报出"] = 报出合计;
\t\t\t\t\tif (xh.Columns.Contains("报出价")) row["报出价"] = 报出合计 * 数量;
\t\t\t\t\tdecimal 成本价 = 采购合计 * 数量, 报出价 = 报出合计 * 数量;
\t\t\t\t\tif (xh.Columns.Contains("利润")) row["利润"] = 报出价 - 成本价;
\t\t\t\t\tif (xh.Columns.Contains("利润率")) row["利润率"] = 成本价 == 0 ? 0 : (报出价 - 成本价) / 成本价;
\t\t\t\t\tif (xh.Columns.Contains("毛利率")) row["毛利率"] = 报出价 == 0 ? 0 : (报出价 - 成本价) / 报出价;
\t\t\t\t}
\t\t\t}
\t\t\t方案重算图号行全部();
\t\t}
\t\tprivate void 方案重算图号行(int rowIndex)
\t\t{
\t\t\t方案重算图号行全部();
\t\t}
\t\tprivate void 方案重算图号行全部()
\t\t{
\t\t\tvar th = _方案图号表;
\t\t\tvar xh = _方案箱号表;
\t\t\tif (th == null || xh == null) return;
\t\t\tdecimal 箱柜数量 = 0, 报出合计 = 0, 成本合计 = 0;
\t\t\tforeach (DataRow r in xh.Rows)
\t\t\t{
\t\t\t\t箱柜数量 += 方案数值(r["数量"]);
\t\t\t\t报出合计 += 方案数值(r["报出价"]);
\t\t\t\t成本合计 += 方案数值(r["成本价"]);
\t\t\t}
\t\t\tif (advancedDataGridView图号.CurrentRow != null)
\t\t\t{
\t\t\t\tint idx = advancedDataGridView图号.CurrentRow.Index;
\t\t\t\tif (idx >= 0 && idx < th.Rows.Count)
\t\t\t\t{
\t\t\t\t\tvar row = th.Rows[idx];
\t\t\t\t\tdecimal 台数 = 方案数值(row["台数"]);
\t\t\t\t\tdecimal 总价 = 报出合计 * 台数, 成本总价 = 成本合计 * 台数;
\t\t\t\t\tif (th.Columns.Contains("箱柜数量")) row["箱柜数量"] = 箱柜数量;
\t\t\t\t\tif (th.Columns.Contains("单价")) row["单价"] = 报出合计;
\t\t\t\t\tif (th.Columns.Contains("总价")) row["总价"] = 总价;
\t\t\t\t\tif (th.Columns.Contains("成本总价")) row["成本总价"] = 成本总价;
\t\t\t\t\tif (th.Columns.Contains("毛利")) row["毛利"] = 总价 - 成本总价;
\t\t\t\t\tif (th.Columns.Contains("毛利率")) row["毛利率"] = 总价 == 0 ? 0 : (总价 - 成本总价) / 总价;
\t\t\t\t}
\t\t\t}
\t\t}
\t\t// ---- 防抖队列 + 后台事务(主窗口 编辑直写库.cs 的方案版) ----
\t\t/// <summary>派生列集合(由计算维护;用户编辑这些列不触发直写)。</summary>
\t\tprivate static readonly System.Collections.Generic.HashSet<string> _方案派生列 =
\t\t\tnew System.Collections.Generic.HashSet<string>(
\t\t\t\tnew[] { "采购单价", "采购价", "报出单价", "报出价", "单台成本", "成本价", "单台报出",
\t\t\t\t\t"利润", "利润率", "毛利率", "箱柜数量", "单价", "总价", "成本总价", "毛利" });
\t\t/// <summary>待写库项:表名|rowid|列 → 值(后写覆盖先写)。主窗口 _待写项 同款。</summary>
\t\tprivate readonly System.Collections.Generic.Dictionary<string, string[]> _方案待写项 =
\t\t\tnew System.Collections.Generic.Dictionary<string, string[]>();
\t\t/// <summary>防抖定时器(300ms)。主窗口 _直写Timer 同款。</summary>
\t\tprivate Timer _方案直写Timer;
\t\tprivate void 入队方案直写(DataGridView dgv, int rowIndex, string colName, object value)
\t\t{
\t\t\tvar table = 取方案数据表(dgv);
\t\t\tif (table == null || !table.Columns.Contains("_rowid_")) return;
\t\t\tif (rowIndex < 0 || rowIndex >= table.Rows.Count) return;
\t\t\tlong rowid = table.Rows[rowIndex]["_rowid_"] is long ? Convert.ToInt64(table.Rows[rowIndex]["_rowid_"]) : 0;
\t\t\tif (rowid <= 0) return;
\t\t\tlock (_方案待写项)
\t\t\t{
\t\t\t\t_方案待写项[table.TableName + "|" + rowid + "|" + colName] =
\t\t\t\t\tnew[] { table.TableName, rowid.ToString(), colName, value == null ? "" : value.ToString() };
\t\t\t}
\t\t\tif (_方案直写Timer == null)
\t\t\t{
\t\t\t\t_方案直写Timer = new Timer();
\t\t\t\t_方案直写Timer.Interval = 300;
\t\t\t\t_方案直写Timer.Tick += 方案直写到期;
\t\t\t}
\t\t\t_方案直写Timer.Stop();
\t\t\t_方案直写Timer.Start();
\t\t}
\t\t/// <summary>防抖到期:批量直写方案文件(后台单事务)+按公式刷聚合。</summary>
\t\tprivate void 方案直写到期(object sender, EventArgs e)
\t\t{
\t\t\t_方案直写Timer.Stop();
\t\t\tif (_方案待写项.Count == 0 || string.IsNullOrEmpty(_当前方案文件)) return;
\t\t\tSystem.Collections.Generic.List<string[]> 批;
\t\t\tlock (_方案待写项)
\t\t\t{
\t\t\t\t批 = new System.Collections.Generic.List<string[]>(_方案待写项.Values);
\t\t\t\t_方案待写项.Clear();
\t\t\t}
\t\t\tstring dbPath = _当前方案文件;
\t\t\tSystem.Threading.Tasks.Task.Run(delegate
\t\t\t{
\t\t\t\ttry
\t\t\t\t{
\t\t\t\t\tusing (var conn = new System.Data.SQLite.SQLiteConnection(连接串(dbPath)))
\t\t\t\t\t{
\t\t\t\t\t\tconn.Open();
\t\t\t\t\t\tusing (var tx = conn.BeginTransaction())
\t\t\t\t\t\t{
\t\t\t\t\t\t\tforeach (var item in 批)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand(
\t\t\t\t\t\t\t\t\t"UPDATE \\"" + item[0].Replace("\\"", "") + "\\" SET \\"" + item[2].Replace("\\"", "") +
\t\t\t\t\t\t\t\t\t"\\"=@v WHERE rowid=@r;", conn, tx))
\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@v", item[3]);
\t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@r", long.Parse(item[1]));
\t\t\t\t\t\t\t\t\tcmd.ExecuteNonQuery();
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\t// 元件行:重算本行派生价
\t\t\t\t\t\t\t\tif (item[0].StartsWith("yj_"))
\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\tstring yjSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "元件"), "ROW", null);
\t\t\t\t\t\t\t\t\tif (yjSet.Length > 0)
\t\t\t\t\t\t\t\t\t\tExec(conn, tx, "UPDATE \\"" + item[0] + "\\" SET " + yjSet + " WHERE rowid=" + item[1] + ";");
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 聚合链:反查 图号/箱号 → 箱号行聚合 + 图号行聚合
\t\t\t\t\t\t\tstring 图号 = null, 箱号 = null;
\t\t\t\t\t\t\tforeach (var item in 批)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tif (item[0].StartsWith("yj_"))
\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand(
\t\t\t\t\t\t\t\t\t\t"SELECT 图号,箱号 FROM 表索引 WHERE 类型='元件' AND 实际表名=@t LIMIT 1;", conn, tx))
\t\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@t", item[0]);
\t\t\t\t\t\t\t\t\t\tusing (var r = cmd.ExecuteReader())
\t\t\t\t\t\t\t\t\t\t\tif (r.Read()) { 图号 = r.IsDBNull(0) ? null : r.GetString(0); 箱号 = r.IsDBNull(1) ? null : r.GetString(1); }
\t\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\telse if (item[0].StartsWith("xh_"))
\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand(
\t\t\t\t\t\t\t\t\t\t"SELECT 图号 FROM 表索引 WHERE 类型='箱号' AND 实际表名=@t LIMIT 1;", conn, tx))
\t\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@t", item[0]);
\t\t\t\t\t\t\t\t\t\tusing (var r = cmd.ExecuteReader())
\t\t\t\t\t\t\t\t\t\t\tif (r.Read()) { 图号 = r.IsDBNull(0) ? null : r.GetString(0); 箱号 = null; }
\t\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(图号))
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tstring xhTbl = 查方案表2(conn, tx, "箱号", 图号, null);
\t\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(xhTbl))
\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(箱号))
\t\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\t\tstring yjTbl = 查方案表2(conn, tx, "元件", 图号, 箱号);
\t\t\t\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(yjTbl))
\t\t\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\t\t\tstring xhSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "箱号"), "ROW", yjTbl);
\t\t\t\t\t\t\t\t\t\t\t\tif (xhSet.Length > 0)
\t\t\t\t\t\t\t\t\t\t\t\t\tExec(conn, tx, "UPDATE \\"" + xhTbl + "\\" SET " + xhSet + " WHERE \\"内部ID\\"='" + 箱号.Replace("'", "''") + "';");
\t\t\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\t\tstring thSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "图号"), "ROW", xhTbl);
\t\t\t\t\t\t\t\t\tif (thSet.Length > 0)
\t\t\t\t\t\t\t\t\t\tExec(conn, tx, "UPDATE \\"" + 图号表名 + "\\" SET " + thSet + " WHERE \\"内部ID\\"='" + 图号.Replace("'", "''") + "';");
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\ttx.Commit();
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t\t写联动日志("方案直写完成: " + 批.Count + " 项");
\t\t\t\t}
\t\t\t\tcatch (Exception ex)
\t\t\t\t{
\t\t\t\t\t写联动日志("方案直写异常: " + ex.Message);
\t\t\t\t}
\t\t\t});
\t\t}
\t\t/// <summary>简单 SQL 执行。</summary>
\t\tprivate static void Exec(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, string sql)
\t\t{
\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx))
\t\t\t\tcmd.ExecuteNonQuery();
\t\t}
\t\t/// <summary>方案窗口自己的 fak.db 连接(公式表来源;已打开)。</summary>
\t\tprivate System.Data.SQLite.SQLiteConnection GetFak连接()
\t\t{
\t\t\tvar conn = new System.Data.SQLite.SQLiteConnection("Data Source=" +
\t\t\t\tPath.Combine(AppDomain.CurrentDomain.BaseDirectory, "sjk", "fak.db") + ";Version=3;");
\t\t\tconn.Open();
\t\t\treturn conn;
\t\t}
'''
s = s[:start] + NEW + s[end:]
io.open(P, "w", encoding="utf-8-sig").write(s)
print("编辑直写区已替换为主窗口完整链路")
+215
View File
@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
"""甲方报表 FillDetailSheet 重写:DOM 组装(上万行节点树+OuterXml 双份字符串→32位OOM)
改为字符串模板替换法:提取模板行字符串→逐行换号/替换单元格→拼接输出。零 DOM、单份字符串。"""
import io
import re
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\xiangmu\甲方报表.cs"
s = io.open(P, encoding="utf-8-sig").read()
start_anchor = " private void FillDetailSheet(Dictionary<string, byte[]> entries, string sheetKey,"
end_anchor = " entries[sheetKey] = System.Text.Encoding.UTF8.GetBytes(doc.OuterXml);\n }"
i0 = s.find(start_anchor)
assert i0 > 0, "起点未找到"
i1 = s.find(end_anchor, i0)
assert i1 > 0, "终点未找到"
i1 += len(end_anchor)
NEW = ''' // ============ 明细表:字符串模板替换法(零 DOM——上万像素级 DOM+OuterXml 双份字符串会 OOM) ============
/// <summary>行字符串:更新行号与全部单元格引用(r="列行号")。</summary>
private static string 行换号(string rowXml, int rowNum)
{
rowXml = System.Text.RegularExpressions.Regex.Replace(rowXml, "<row r=\\"\\\\d+\\"", "<row r=\\"" + rowNum + "\\"");
return System.Text.RegularExpressions.Regex.Replace(rowXml, "r=\\"([A-Z]+)\\\\d+\\"", "r=\\"$1" + rowNum + "\\"");
}
/// <summary>行字符串:设置某列单元格内容(innerXml=单元格内部 XML;tAttr=类型属性,null=移除 t)。</summary>
private static string 行设单元格(string rowXml, string col, int rowNum, string innerXml, string tAttr)
{
string r = col + rowNum;
var m = System.Text.RegularExpressions.Regex.Match(rowXml, "<c r=\\"" + r + "\\"([^>]*?)(?:/>|>.*?</c>)");
if (!m.Success) return rowXml;
string attrs = m.Groups[1].Value;
attrs = System.Text.RegularExpressions.Regex.Replace(attrs, "\\\\s*t=\\"[^\\"]*\\"", "");
if (!string.IsNullOrEmpty(tAttr)) attrs += " t=\\"" + tAttr + "\\"";
return rowXml.Substring(0, m.Index) + "<c r=\\"" + r + "\\"" + attrs + ">" + innerXml + "</c>" + rowXml.Substring(m.Index + m.Length);
}
private static string 行设文本(string rowXml, string col, int rowNum, string text)
{ return 行设单元格(rowXml, col, rowNum, "<v>" + XmlEscape(text) + "</v>", "str"); }
private static string 行设数值(string rowXml, string col, int rowNum, decimal v)
{ return 行设单元格(rowXml, col, rowNum, "<v>" + v.ToString(System.Globalization.CultureInfo.InvariantCulture) + "</v>", null); }
private static string 行设公式(string rowXml, string col, int rowNum, string formula)
{ return 行设单元格(rowXml, col, rowNum, "<f>" + XmlEscape(formula) + "</f>", null); }
private void FillDetailSheet(Dictionary<string, byte[]> entries, string sheetKey,
List<TuhaoData> tuhaoList, List<CabinetRowInfo> cabRowInfos, System.Text.StringBuilder dbgLog,
bool useFormula)
{
string xml = System.Text.Encoding.UTF8.GetString(entries[sheetKey]);
// 提取全部行块,定位模板行 11~16 与头/尾(零 DOM)
var 行匹配 = System.Text.RegularExpressions.Regex.Matches(
xml, "<row r=\\"(\\\\d+)\\"[^>]*?(?:/>|>.*?</row>)");
if (行匹配.Count == 0) return;
string[] tpl = new string[6];
int bodyStart = -1, bodyEnd = -1;
foreach (System.Text.RegularExpressions.Match m in 行匹配)
{
int rn = int.Parse(m.Groups[1].Value);
if (rn >= 11 && rn <= 16)
{
tpl[rn - 11] = m.Value;
if (bodyStart < 0) bodyStart = m.Index;
bodyEnd = m.Index + m.Length;
}
}
string tplPaperRow = tpl[0], tplCabRow = tpl[1], tplHeaderRow = tpl[2],
tplElemRow = tpl[3], tplSingleRow = tpl[4], tplGrandRow = tpl[5];
if (tplCabRow == null || tplElemRow == null) return;
string 头 = xml.Substring(0, bodyStart); // 行1~10
string 尾 = xml.Substring(bodyEnd); // 行17(签名)及之后
var sb = new System.Text.StringBuilder();
int rowNum = 11;
decimal grandQty = 0, grandTotal = 0;
int tuhaoIdx = 0;
foreach (var tuhao in tuhaoList)
{
string tuhaoName = !string.IsNullOrEmpty(tuhao.TuMing) ? tuhao.TuMing : tuhao.Tuhao;
// 图名行
if (tplPaperRow != null)
{
string row = 行换号(tplPaperRow, rowNum);
row = 行设文本(row, "A", rowNum, tuhaoName);
sb.Append(row);
rowNum++;
}
// 每个箱号: 柜号行 + 表头 + 元件行*N + 单台合计 + 总计
int cabOrder = 0;
foreach (var cab in tuhao.Cabinets)
{
cabOrder++;
int cabRowNum = rowNum;
// 柜号行
{
string row = 行换号(tplCabRow, rowNum);
row = 行设文本(row, "A", rowNum, (tuhaoIdx + 1) + "-" + cabOrder);
row = 行设文本(row, "B", rowNum, "柜号:" + cab.CabinetNo);
row = 行设文本(row, "D", rowNum, cab.Model);
// ★ G列"名称:"= 箱名(进线柜/PT柜等);库里没有时退回图名(与旧版行为一致)
string 名称值 = !string.IsNullOrEmpty(cab.BoxName) ? cab.BoxName : tuhaoName;
row = 行设文本(row, "G", rowNum, 名称值);
row = 行设文本(row, "I", rowNum, cab.SpecName);
sb.Append(row);
rowNum++;
}
// 表头行
if (tplHeaderRow != null)
{
sb.Append(行换号(tplHeaderRow, rowNum));
rowNum++;
}
// 元件行(G列用 =E*F 公式)
int elemStartRow = rowNum;
if (cab.Elements.Count == 0)
{
sb.Append(行换号(tplElemRow, rowNum));
rowNum++;
}
else
{
int elemOrder = 0;
foreach (var elem in cab.Elements)
{
elemOrder++;
string row = 行换号(tplElemRow, rowNum);
row = 行设文本(row, "A", rowNum, elemOrder.ToString());
row = 行设文本(row, "B", rowNum, elem.Name);
row = 行设文本(row, "C", rowNum, elem.ShowSpecName);
row = 行设文本(row, "D", rowNum, elem.Unit);
row = 行设数值(row, "E", rowNum, SafeDec(elem.Count));
row = 行设数值(row, "F", rowNum, elem.OfferUnitPrice);
// G列: 公式 =E{行}*F{行} 或直接值
if (useFormula)
row = 行设公式(row, "G", rowNum, "E" + rowNum + "*F" + rowNum);
else
row = 行设数值(row, "G", rowNum, SafeDec(elem.Count) * elem.OfferUnitPrice);
row = 行设文本(row, "H", rowNum, elem.FactName);
row = 行设文本(row, "I", rowNum, elem.Remark);
sb.Append(row);
rowNum++;
}
}
int elemEndRow = rowNum - 1;
// 单台合计行(G列用 =ROUND(SUM(G{起}:G{止}),0) 公式)
int singleRowNum = rowNum;
if (tplSingleRow != null)
{
string row = 行换号(tplSingleRow, rowNum);
row = 行设文本(row, "B", rowNum, "单台合计");
if (useFormula)
row = 行设公式(row, "G", rowNum, "ROUND(SUM(G" + elemStartRow + ":G" + elemEndRow + "),0)");
else
{
decimal elemSum = 0;
foreach (var elem in cab.Elements) elemSum += SafeDec(elem.Count) * elem.OfferUnitPrice;
row = 行设数值(row, "G", rowNum, elemSum);
}
sb.Append(row);
rowNum++;
}
// 总计行(G列用 =G{单台合计}*E{总计} 公式)
int grandRowNum = rowNum;
if (tplGrandRow != null)
{
string row = 行换号(tplGrandRow, rowNum);
row = 行设文本(row, "B", rowNum, "总计");
row = 行设文本(row, "D", rowNum, "台");
row = 行设数值(row, "E", rowNum, SafeDec(cab.Count));
if (useFormula)
row = 行设公式(row, "G", rowNum, "G" + singleRowNum + "*E" + rowNum);
else
{
decimal elemSum = 0;
foreach (var elem in cab.Elements) elemSum += SafeDec(elem.Count) * elem.OfferUnitPrice;
row = 行设数值(row, "G", rowNum, elemSum * SafeDec(cab.Count));
}
sb.Append(row);
rowNum++;
}
// 记录行号供汇总表引用
cabRowInfos.Add(new CabinetRowInfo
{
CabRow = cabRowNum,
SingleTotalRow = singleRowNum,
GrandRow = grandRowNum
});
grandQty += SafeDec(cab.Count);
grandTotal += cab.OfferTotalPrice;
}
tuhaoIdx++;
}
entries[sheetKey] = System.Text.Encoding.UTF8.GetBytes(头 + sb.ToString() + 尾);
}'''
s = s[:i0] + NEW + s[i1:]
io.open(P, "w", encoding="utf-8-sig").write(s)
print("FillDetailSheet 已重写为字符串模板法")
+86
View File
@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""MenuProject_AddToFak_Click 重写为:复制项目库文件为方案(一个方案一个库文件)。幂等(按方法名定位替换)。"""
import io
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
s = io.open(P, encoding="utf-8-sig").read()
si = None
for i, l in enumerate(s.split("\n")):
if l.startswith(" private void MenuProject_AddToFak_Click"):
si = i
break
assert si is not None, "MenuProject 方法未找到"
lines = s.split("\n")
ei = None
for i in range(si + 1, len(lines)):
if lines[i] == " }":
ei = i
break
assert ei is not None, "方法结束未找到"
NEW = r''' private void MenuProject_AddToFak_Click(object sender, EventArgs e)
{
var row = GetFirstSelectedRow(this.advancedDataGridView6);
if (row == null)
{
MessageBox.Show(this, "请先在项目列表中选择要存为方案的项目。",
"存为方案", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
string projectId = GetBoundValueAsString(row, "项目编号");
string projName = GetBoundValueAsString(row, "项目名称");
if (string.IsNullOrWhiteSpace(projectId))
{
MessageBox.Show(this, "选中行没有项目编号,无法存为方案。",
"存为方案", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string srcDbPath = GetQuoteDbPath(projectId);
if (!System.IO.File.Exists(srcDbPath))
{
MessageBox.Show(this, "项目报价库不存在:\n" + srcDbPath,
"存为方案", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 方案名默认 = 项目名称(空则项目编号);重名自动加序号
string 方案名 = !string.IsNullOrWhiteSpace(projName) ? projName : projectId;
string dstDir = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sjk", "方案库");
System.IO.Directory.CreateDirectory(dstDir);
string dstPath = System.IO.Path.Combine(dstDir, 方案名 + ".db");
int seq = 1;
while (System.IO.File.Exists(dstPath))
{
seq++;
dstPath = System.IO.Path.Combine(dstDir, 方案名 + "(" + seq + ").db");
}
var sw = System.Diagnostics.Stopwatch.StartNew();
Cursor = Cursors.WaitCursor;
try
{
// ★ 一个方案 = 一份独立项目库副本(含全部图号/箱号/元件/项目信息)
BackupSQLiteDb(srcDbPath, dstPath);
sw.Stop();
Log("[存为方案] 项目=" + projectId + " → " + dstPath + " 耗时 " + sw.ElapsedMilliseconds + "ms");
MessageBox.Show(this, "已把项目 [" + projectId + "] 存为方案。\n方案文件: " + dstPath +
"\n耗时 " + sw.ElapsedMilliseconds + "ms。", "存为方案",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception exSave)
{
MessageBox.Show(this, "存为方案失败:\n" + exSave.Message, "存为方案",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
Cursor = Cursors.Default;
}
}'''
lines[si:ei + 1] = NEW.split("\n")
io.open(P, "w", encoding="utf-8-sig").write("\n".join(lines))
print("MenuProject 重写完成")
+46
View File
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""把 24 个按钮的 Image 行(FromFile 形态)写进 MainForm.Designer.cs——设计器里直接显示图标。
锚定 .Name = "原字段名" 行(SD 重序列化后变量名是 ribbonButtonN,不能依赖变量名),Image 紧随其后。
路径用本机绝对路径(设计器进程 CWD 不可控);FromFile 找不到文件返回 null 不抛异常,
换机后设计器只是不画图,运行时由 MainForm.cs 用 BaseDirectory 兜底。幂等。"""
import io
import os
import re
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件"
DSG = os.path.join(ROOT, "成套报价软件", "MainForm.Designer.cs")
ICON_DIR = os.path.join(ROOT, "成套报价软件", "bin", "Debug", "icons")
BIND = {
"button1": "new-project", "button2": "edit-project", "button3": "delete-project",
"button4": "open-project", "button5": "copy-project", "button7": "export-project",
"button8": "import-project", "button9": "open-quote", "button10": "edit-quote",
"button17": "save", "button18": "import-quote", "button16": "report",
"button15": "busbar", "button13": "factor", "button33": "sort-components",
"button29": "template-cost", "button11": "cad-grab",
"btn登录": "login", "btn修改密码": "password", "btn会员信息": "member",
"btn企业信息": "company", "btn保存布局": "save-layout",
"btn备份": "backup", "btn恢复": "restore",
}
s = io.open(DSG, encoding="utf-8-sig").read()
if ".Image = Smart.CustomComponents.RibbonButton.FromFile(" in s:
print("Image 行已存在,跳过")
else:
lines = s.split("\n")
out = []
n = 0
name_re = re.compile(r'^\t\t\t([A-Za-z0-9_]+)\.Name = "([^"]+)";')
for line in lines:
out.append(line)
m = name_re.match(line)
if m:
var, btn = m.group(1), m.group(2)
if btn in BIND:
png = os.path.join(ICON_DIR, BIND[btn] + ".png")
out.append('\t\t\t%s.Image = Smart.CustomComponents.RibbonButton.FromFile("%s");'
% (var, png.replace("\\", "\\\\")))
n += 1
assert n == 24, "只锚定 %d 个按钮(期望 24)" % n
io.open(DSG, "w", encoding="utf-8-sig").write("\n".join(out))
print("Designer.cs 已插入 %d 行 Image(FromFile)" % n)
+31
View File
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""修 Designer.cs 集合块的局部变量重名:三个页签的 rtab1/rgrpA.. 按页加唯一前缀。"""
import io
import re
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\MainForm.Designer.cs"
s = io.open(P, encoding="utf-8-sig").read()
start = s.find("\t\t\tSmart.CustomComponents.RibbonTab rtab1 = new")
endmark = "\t\t\tthis.ribbonStrip1.TabIndex = 3;"
end = s.find(endmark)
assert start > 0 and end > start, "找不到集合块"
block = s[start:end]
# 按页签轮次分段:每轮以 new RibbonTab 开头
parts = re.split(r"(?=\t\t\tSmart\.CustomComponents\.RibbonTab rtab1 = new)", block)
parts = [p for p in parts if p.strip()]
assert len(parts) == 3, "期望 3 个页签块,实际 %d" % len(parts)
prefixes = ["项目_", "报价_", "设置_"]
out = []
for p, pre in zip(parts, prefixes):
p = p.replace("rtab1", "rtab" + pre)
# 组变量 rgrpA..D 每轮加页前缀
for g in ["A", "B", "C", "D"]:
p = p.replace("rgrp" + g, "rgrp" + pre + g)
out.append(p)
s = s[:start] + "".join(out) + s[end:]
io.open(P, "w", encoding="utf-8-sig").write(s)
print("集合块变量改名完成: rtab项目_/rtab报价_/rtab设置_")
+424
View File
@@ -0,0 +1,424 @@
# -*- coding: utf-8 -*-
"""把主窗口顶部 tabControl1(项目/报价/设置 三页签×24按钮)替换为 RibbonStrip 功能区(主功能区子类)。
改写 MainForm.Designer.cs / MainForm.cs / 主窗口.cs / MainForm.旧页签兼容.cs。
幂等:重复运行前先检查关键标记是否已存在。"""
import io
import re
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
# 24 个按钮: (原字段名, Text)
BTN_PROJECT = [("button1", "新建项目"), ("button2", "修改项目"), ("button3", "删除项目"),
("button4", "打开项目"), ("button5", "复制项目")]
BTN_PROJECT2 = [("button8", "导入项目"), ("button7", "导出项目")]
BTN_QUOTE1 = [("button9", "打开项目"), ("button10", "修改项目"), ("button17", "保存")]
BTN_QUOTE2 = [("button18", "导入报价"), ("button16", "甲方报表")]
BTN_QUOTE3 = [("button15", "调母排"), ("button13", "调系数"), ("button33", "元件排序"), ("button29", "模板费用")]
BTN_QUOTE4 = [("button11", "CAD扒图")]
BTN_SET1 = [("btn登录", "登录"), ("btn修改密码", "修改密码"), ("btn会员信息", "会员信息")]
BTN_SET2 = [("btn企业信息", "企业信息"), ("btn保存布局", "保存布局")]
BTN_SET3 = [("btn备份", "备份"), ("btn恢复", "恢复")]
ALL_BTNS = [b for grp in (BTN_PROJECT, BTN_PROJECT2, BTN_QUOTE1, BTN_QUOTE2, BTN_QUOTE3,
BTN_QUOTE4, BTN_SET1, BTN_SET2, BTN_SET3) for b in grp]
TAB_PAGES = ["tabPage项目", "tabPage报价", "tabPage设置"]
BTN_NAMES = set(n for n, _ in ALL_BTNS)
def read(p):
with io.open(p, "r", encoding="utf-8-sig") as f:
return f.read()
def write(p, s):
with io.open(p, "w", encoding="utf-8-sig") as f:
f.write(s)
def must_remove(line):
"""Designer.cs 里要删的行:24 按钮 + tabControl1 + 3 tabPage 的字段/new/suspend/resume/属性行。"""
s = line.strip()
# 按钮字段/new
m = re.match(r"private System\.Windows\.Forms\.Button (button\d+|btn[^;]+);$", s)
if m and m.group(1).strip() in BTN_NAMES:
return True
m = re.match(r"this\.(button\d+|btn[^ =]+) = new System\.Windows\.Forms\.Button\(\);$", s)
if m and m.group(1) in BTN_NAMES:
return True
# 按钮属性行(this.buttonN.xxx = ...)
m = re.match(r"this\.([A-Za-z0-9_]+)\.[A-Za-z0-9_]+ = ", s)
if m and m.group(1) in BTN_NAMES:
return True
# tabControl1/tabPage
if re.match(r"private System\.Windows\.Forms\.TabControl tabControl1;$", s):
return True
if s == "this.tabControl1 = new System.Windows.Forms.TabControl();":
return True
if s.startswith("this.tabControl1.") or s == "this.tabControl1.ResumeLayout(false);":
return True
for tp in TAB_PAGES:
if re.match(r"private System\.Windows\.Forms\.TabPage %s;$" % tp, s):
return True
if s == "this.%s = new System.Windows.Forms.TabPage();" % tp:
return True
if s.startswith("this.%s." % tp) or s == "this.%s.ResumeLayout(false);" % tp:
return True
return False
def ribbon_block():
"""生成 ribbonStrip1 的 Designer 区块(tab 缩进)。"""
t = "\t\t\t"
L = []
L.append(t + "// ")
L.append(t + "// ribbonStrip1")
L.append(t + "// ")
def tab(var, text):
L.append(t + "Smart.CustomComponents.RibbonTab %s = new Smart.CustomComponents.RibbonTab();" % var)
L.append(t + "%s.Text = \"%s\";" % (var, text))
def group(var, text):
L.append(t + "Smart.CustomComponents.RibbonGroup %s = new Smart.CustomComponents.RibbonGroup();" % var)
L.append(t + "%s.Text = \"%s\";" % (var, text))
def btn(field, text):
var = "rbtn_" + field
L.append(t + "Smart.CustomComponents.RibbonButton %s = new Smart.CustomComponents.RibbonButton();" % var)
L.append(t + "%s.Name = \"%s\";" % (var, field))
L.append(t + "%s.Text = \"%s\";" % (var, text))
def add_group(var):
L.append(t + "rtab1.Groups.Add(%s);" % var)
def add_tab():
L.append(t + "this.ribbonStrip1.Tabs.Add(rtab1);")
# ---- 页签1 项目 ----
tab("rtab1", "项目")
group("rgrpA", "项目文件")
for f, x in BTN_PROJECT:
btn(f, x)
L.append(t + "rgrpA.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpA")
group("rgrpB", "数据")
for f, x in BTN_PROJECT2:
btn(f, x)
L.append(t + "rgrpB.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpB")
add_tab()
# ---- 页签2 报价 ----
tab("rtab1", "报价")
group("rgrpA", "项目")
for f, x in BTN_QUOTE1:
btn(f, x)
L.append(t + "rgrpA.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpA")
group("rgrpB", "报价")
for f, x in BTN_QUOTE2:
btn(f, x)
L.append(t + "rgrpB.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpB")
group("rgrpC", "计算")
for f, x in BTN_QUOTE3:
btn(f, x)
L.append(t + "rgrpC.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpC")
group("rgrpD", "工具")
for f, x in BTN_QUOTE4:
btn(f, x)
L.append(t + "rgrpD.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpD")
add_tab()
# ---- 页签3 设置 ----
tab("rtab1", "设置")
group("rgrpA", "账号")
for f, x in BTN_SET1:
btn(f, x)
L.append(t + "rgrpA.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpA")
group("rgrpB", "系统")
for f, x in BTN_SET2:
btn(f, x)
L.append(t + "rgrpB.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpB")
group("rgrpC", "数据")
for f, x in BTN_SET3:
btn(f, x)
L.append(t + "rgrpC.Buttons.Add(rbtn_%s);" % f)
add_group("rgrpC")
add_tab()
L.append(t + "this.ribbonStrip1.Dock = System.Windows.Forms.DockStyle.Top;")
L.append(t + "this.ribbonStrip1.Location = new System.Drawing.Point(0, 0);")
L.append(t + "this.ribbonStrip1.Name = \"ribbonStrip1\";")
L.append(t + "this.ribbonStrip1.Size = new System.Drawing.Size(1884, 110);")
L.append(t + "this.ribbonStrip1.TabIndex = 3;")
return "\n".join(L)
def replace_designer(src):
lines = src.split("\n")
out = []
i = 0
n = len(lines)
while i < n:
line = lines[i]
s = line.strip()
# 1) 按钮区块整体跳过:遇到 "// buttonN" 或 "// btnXxx" 标题行,连标题三行一起跳到区块尾
m = re.match(r"// (button\d+|btn[^ ]+)$", s)
if m and m.group(1) in BTN_NAMES:
i += 3 # 跳过 // 、// buttonN、// 三行
while i < n and lines[i].strip().startswith("this." + m.group(1) + "."):
i += 1
continue
# 2) tabControl1 区块跳过(注释头三行 + this.tabControl1.* 行),替换为 ribbonStrip1 区块
if s == "// tabControl1":
i += 3
while i < n and lines[i].strip().startswith("this.tabControl1"):
i += 1
out.append("\t\t\t// ")
out.append("\t\t\t// ribbonStrip1")
out.append("\t\t\t// ")
out.append(ribbon_block())
# 跳过后续 3 个 tabPage 区块
for tp in TAB_PAGES:
while i < n and lines[i].strip() != "// " + tp:
i += 1
if i < n:
i += 3
while i < n and lines[i].strip().startswith("this." + tp + "."):
i += 1
continue
# 3) 其余单行删除(字段/new/suspend/resume/Controls.Add(tabControl1))
if must_remove(line):
i += 1
continue
if s == "this.Controls.Add(this.tabControl1);":
out.append(line.replace("tabControl1", "ribbonStrip1"))
i += 1
continue
out.append(line)
i += 1
txt = "\n".join(out)
# new 行与字段行插入
txt = txt.replace(
"\t\t\tthis.tabControl1 = new System.Windows.Forms.TabControl();",
"\t\t\tthis.ribbonStrip1 = new 主功能区();")
txt = txt.replace(
"\t\tprivate System.Windows.Forms.TabControl tabControl1;",
"\t\tprivate 主功能区 ribbonStrip1;")
return txt
def replace_mainform_cs(src):
# 1) 删 24 行 Click 挂接(button 系 12 行 + button15 lambda 段 + btn 系 5 行)
pat_click = re.compile(
r"[ \t]*this\.(button1|button3|button2|button5|button10|button16|button13|button18|button7|button8|button33|btn企业信息|btn备份|btn恢复|button11)\.Click \+= [^\n]*\n")
src = pat_click.sub("", src)
# button17 保存
src = re.sub(r"[ \t]*this\.button17\.Click \+= \(s, e\) => SaveAllToDatabase\(\);[^\n]*\n", "", src)
# button15 lambda 整段(从注释行到闭括号行)
src = re.sub(
r"[ \t]*// ===== 调母排\(button15\):普通非模态弹窗 =====\n"
r"[ \t]*// 不自动居中、不贴靠右侧、不跟随主窗口移动、不限制大小,直接弹出。\n"
r"[ \t]*调母排 _tiaomuForm = null;\n"
r"[ \t]*\n"
r"[ \t]*this\.button15\.Click \+= \(s, e\) =>\n"
r"[ \t]*\{.*?\n"
r"[ \t]*\};\n",
"", src, flags=re.S)
# 2) tabControl1.SelectedIndexChanged 联动段替换为功能区事件
src = re.sub(
r"[ \t]*// ===== 顶部选项卡:点击\"项目\"/\"报价\"时激活对应停靠文档窗口 =====\n"
r"[ \t]*this\.tabControl1\.SelectedIndexChanged \+= \(s, e\) =>\n"
r"[ \t]*\{.*?\n"
r"[ \t]*\};\n",
(" // ===== 功能区(ribbonStrip1):统一按钮点击路由 + 页签切换联动 =====\n"
" this.ribbonStrip1.ButtonClick += ribbonStrip1_ButtonClick;\n"
" this.ribbonStrip1.活动页已切换 += (s, e) =>\n"
" {\n"
" string tabName = (this.ribbonStrip1.ActiveTab != null) ? this.ribbonStrip1.ActiveTab.Text : \"\";\n"
" Log(\"[TabSync] 功能区页签切换: [\" + tabName + \"]\");\n"
" if (tabName == \"项目\" && this.w1 != null)\n"
" {\n"
" this.w1.Show(this.dockSurface1);\n"
" this.w1.Activate();\n"
" }\n"
" else if (tabName == \"报价\" && this.w2 != null)\n"
" {\n"
" this.w2.Show(this.dockSurface1);\n"
" this.w2.Activate();\n"
" }\n"
" // \"设置\"页只显示按钮面板,无停靠窗口\n"
" };\n\n"),
src, flags=re.S)
return src
def add_handlers(src):
"""在 MainForm 类尾部(canvas:找类的最后一个方法后)插入处理器与辅助方法。找 'private void Dgv_CellValuePushed' 所在文件不适用——直接加在构造器后第一个方法前。"""
handlers = '''
// ============================================================
// 功能区(ribbonStrip1)按钮路由与辅助
// ============================================================
/// <summary>功能区统一按钮点击:按 RibbonButton.Name(保留原按钮字段名)分发到原处理器。</summary>
private void ribbonStrip1_ButtonClick(object sender, Smart.CustomComponents.RibbonButtonEventArgs e)
{
if (e.Button == null || string.IsNullOrEmpty(e.Button.Name)) return;
switch (e.Button.Name)
{
case "button1": button1_Click(sender, e); break; // 新建项目
case "button2": button2_Click(sender, e); break; // 修改项目(项目页)
case "button3": button3_Click(sender, e); break; // 删除项目
case "button4": break; // 打开项目(项目页,原无处理器)
case "button5": button5_Click(sender, e); break; // 复制项目
case "button7": button7_Click(sender, e); break; // 导出项目
case "button8": button8_Click(sender, e); break; // 导入项目
case "button9": break; // 打开项目(报价页,原无处理器)
case "button10": button10_Click(sender, e); break; // 修改项目(报价页)
case "button11": button11_Click(sender, e); break; // CAD扒图
case "button13": button13_Click(sender, e); break; // 调系数
case "button15": 打开调母排窗口(); break; // 调母排
case "button16": button16_Click(sender, e); break; // 甲方报表
case "button17": SaveAllToDatabase(); break; // 保存
case "button18": button18_Click(sender, e); break; // 导入报价
case "button29": break; // 模板费用(原无处理器)
case "button33": button33_Click(sender, e); break; // 元件排序
case "btn登录": break; // 登录(原无处理器)
case "btn修改密码": break; // 修改密码(原无处理器)
case "btn企业信息": btn企业信息_Click(sender, e); break; // 企业信息
case "btn会员信息": break; // 会员信息(原无处理器)
case "btn保存布局": btn保存布局_Click(sender, e); break; // 保存布局
case "btn备份": btn备份_Click(sender, e); break; // 备份
case "btn恢复": btn恢复_Click(sender, e); break; // 恢复
}
}
/// <summary>按 Name 在功能区三层集合里找 RibbonButton(找不到返回 null)。</summary>
private Smart.CustomComponents.RibbonButton FindRibbonButton(string name)
{
if (this.ribbonStrip1 == null) return null;
foreach (Smart.CustomComponents.RibbonTab tab in this.ribbonStrip1.Tabs)
foreach (Smart.CustomComponents.RibbonGroup grp in tab.Groups)
foreach (Smart.CustomComponents.RibbonButton btn in grp.Buttons)
if (btn.Name == name) return btn;
return null;
}
/// <summary>调母排(button15):普通非模态弹窗(原构造器内 lambda 抽出)。
/// 不自动居中、不贴靠右侧、不跟随主窗口移动、不限制大小,直接弹出。</summary>
private 调母排 _调母排窗;
private void 打开调母排窗口()
{
if (_调母排窗 != null && !_调母排窗.IsDisposed)
{
_调母排窗.Activate();
return;
}
if (string.IsNullOrWhiteSpace(_currentProjectId))
{
MessageBox.Show(this, "请先在项目列表双击打开一个项目。", "调母排",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
_调母排窗 = new 调母排(this);
// 不设置 StartPosition/Location/Size/MaximumSize/MinimumSize,使用窗体自身默认值
_调母排窗.Show(this);
_调母排窗.FormClosed += (s2, e2) => { _调母排窗 = null; };
}
/// <summary>项目菜单页"修改项目"按钮(项目页 button2):无条件可用,补路由。</summary>
private void button2_Click(object sender, EventArgs e)
{
修改项目入口();
}
'''
# button2_Click 原本存在?若存在会重复定义——先检查
if "private void button2_Click" in src and "ribbonStrip1_ButtonClick" not in src:
# 已有 button2_Click,去掉补丁里的重复定义
handlers = handlers.replace('''
/// <summary>项目菜单页"修改项目"按钮(项目页 button2):无条件可用,补路由。</summary>
private void button2_Click(object sender, EventArgs e)
{
修改项目入口();
}
''', "")
# 插入点:构造器结束后(锚点在构造器内,往后找第一个方法级结束括号 \t\t})
anchor = " // ===== 监听主区域 tabControl2 切换"
idx = src.find(anchor)
if idx < 0:
raise SystemExit("MainForm.cs 找不到插入锚点")
m = re.search(r"\n\t\t\}\n", src[idx:])
if not m:
raise SystemExit("MainForm.cs 找不到构造器结束位置")
pos = idx + m.end()
return src[:pos] + handlers + "\n" + src[pos:]
def replace_zhuwindow(src):
# UpdateButton10State: button10 → FindRibbonButton
src = src.replace(
" this.button10.Enabled = onQuotePage && hasProject;",
" var rb10 = FindRibbonButton(\"button10\");\n"
" if (rb10 != null) rb10.Enabled = onQuotePage && hasProject;")
# EnterProjectCore: tabControl1.SelectedTab = tabPage2
src = src.replace(
" this.tabControl1.SelectedTab = this.tabPage2;\n"
" Log(\"[EnterProjectCore] 已设 tabControl1.SelectedTab = tabPage2\");",
" int 报价页idx = this.ribbonStrip1.页索引(\"报价\");\n"
" if (报价页idx >= 0) this.ribbonStrip1.设活动页(报价页idx);\n"
" Log(\"[EnterProjectCore] 已设功能区活动页=报价\");")
return src
def replace_compat(src):
# 删 tabPage1/2/8 三个兼容属性
src = re.sub(
r"[ \t]*// ---- 旧页签名 → 新控件别名(旧代码 this\.tabPageN 继续可用)----\n"
r".*?public DockContent tabPage3 \{ get \{ return this\.w1; \} \}",
" // ---- 旧页签名 → 新控件别名(旧代码 this.tabPageN 继续可用)----\n\n"
" /// <summary>旧左侧\"项目\"页 → 停靠窗口\"项目\"。</summary>\n"
" public DockContent tabPage3 { get { return this.w1; } }",
src, flags=re.S)
return src
def main():
import os
# 幂等保护
d = read(os.path.join(ROOT, "MainForm.Designer.cs"))
if "ribbonStrip1" in d:
print("Designer.cs 已含 ribbonStrip1,跳过")
else:
write(os.path.join(ROOT, "MainForm.Designer.cs"), replace_designer(d))
print("Designer.cs 改写完成")
m = read(os.path.join(ROOT, "MainForm.cs"))
if "ribbonStrip1_ButtonClick(object sender" not in m:
m2 = replace_mainform_cs(m)
m2 = add_handlers(m2)
write(os.path.join(ROOT, "MainForm.cs"), m2)
print("MainForm.cs 改写完成")
else:
print("MainForm.cs 已改,跳过")
z = read(os.path.join(ROOT, "主窗口.cs"))
if "FindRibbonButton(\"button10\")" not in z:
write(os.path.join(ROOT, "主窗口.cs"), replace_zhuwindow(z))
print("主窗口.cs 改写完成")
else:
print("主窗口.cs 已改,跳过")
c = read(os.path.join(ROOT, "MainForm.旧页签兼容.cs"))
if "tabPage2 { get" in c:
write(os.path.join(ROOT, "MainForm.旧页签兼容.cs"), replace_compat(c))
print("旧页签兼容.cs 改写完成")
else:
print("旧页签兼容.cs 已改,跳过")
if __name__ == "__main__":
main()
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# 验证 清理代理与WebView进程 的 PowerShell 脚本(C# 里用 Base64 UTF-16LE 传递)
# 此处以完全相同的方式构建并执行,确认语法正确、能正常运行并输出计数
import base64
import subprocess
ps = (
"$n=0; "
"Get-CimInstance Win32_Process -Filter \"Name='python.exe' OR Name='pythonw.exe' OR Name='msedgewebview2.exe'\" | "
"Where-Object { $_.ProcessId -ne $pid -and ( "
"$_.CommandLine -like '*proxy_server.py*' -or "
"($_.Name -eq 'msedgewebview2.exe' -and $_.CommandLine -like '*成套报价软件.exe.WebView2*') ) } | "
"ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue; $n++ } catch {} }; "
"Write-Output $n"
)
enc = base64.b64encode(ps.encode("utf-16-le")).decode()
r = subprocess.run(
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-EncodedCommand", enc],
capture_output=True, text=True, timeout=30,
)
print("退出码:", r.returncode)
print("输出(清理数):", r.stdout.strip())
print("错误:", r.stderr.strip()[:300] if r.stderr.strip() else "无")
+29
View File
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""用 C# 同款拆分规则验证正泰格式的型号规格拆分(拆规格与名称)。"""
import re
def 拆(原始):
# 找最长连续中文段(>=2字)
best = None
for m in re.finditer(r'[\u4e00-\u9fa5]{2,}', 原始):
if best is None or len(m.group()) > len(best.group()):
best = m
if best:
名称 = best.group()
规格 = (原始[:best.start()] + ' ' + 原始[best.end():]).strip()
规格 = re.sub(r'\s+', ' ', 规格).strip(' -/')
return 规格, 名称
return 原始.strip(), ''
cases = [
"CA1-100/4300B/40A 断路器",
"CH1-63C16/1P",
"DJMB2 220/36 0.3kVA",
"RT12-63/4P 40A",
"CM3-63M/33002/50A 塑壳断路器",
"LAM-C40/4P",
"CH1L-32C20/1N 剩余电流断路器",
]
for c in cases:
规格, 名称 = 拆(c)
print(repr(c), '→ 规格:', repr(规格), ' 名称:', repr(名称))
+193
View File
@@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
"""触发器总开关+批量刷新:
①计算引擎:触发器统一加 WHEN 开关=0 条件;新增 开关表/开关设/批量刷新方法;重建时确保开关表存在
②管线(图号/箱号粘贴、删除):开关=1 → 批量写 → 刷新图号全部计算 → 开关=0 (全在原事务内)
幂等。"""
import io
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
n = 0
def edit(path, pairs, tag):
global s, n
for old, new in pairs:
if old not in s:
if new in s:
print(tag, "已有(跳过)")
continue
raise AssertionError(tag + " 锚点缺失: " + old[:70])
s = s.replace(old, new, 1)
n += 1
io.open(path, "w", encoding="utf-8-sig").write(s)
print(tag, "ok")
p = ROOT + r"\计算引擎.cs"
s = io.open(p, encoding="utf-8-sig").read()
# 1) 开关常量与开关表
@@SKIP@@
public const string 触发器前缀 = "trgjs_";''',
''' /// <summary>触发器名前缀(重建时按前缀全清)。</summary>
public const string 触发器前缀 = "trgjs_";
/// <summary>触发器总开关条件(批量操作时设 开关=1 跳过触发器体,提交前批量刷新+设回 0)。</summary>
public const string 开关条件 = "(SELECT 开关 FROM _触发器开关 WHERE id=1)=0";''')
# 上面的辅助不适用,直接顺序替换
def rep2(old, new):
global s, n
if old not in s:
if new in s:
print("跳过(已改)")
return
raise AssertionError("锚点缺失: " + old[:70])
s = s.replace(old, new, 1)
n += 1
rep2(''' /// <summary>触发器名前缀(重建时按前缀全清)。</summary>
public const string 触发器前缀 = "trgjs_";''',
''' /// <summary>触发器名前缀(重建时按前缀全清)。</summary>
public const string 触发器前缀 = "trgjs_";
/// <summary>触发器总开关条件:批量操作时置 开关=1,触发器整体短路,提交前批量刷新后置回 0。</summary>
public const string 开关条件 = "(SELECT 开关 FROM _触发器开关 WHERE id=1)=0";''')
# 2) EnsureFormulaTable 里顺带建开关表
rep2(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"CREATE TABLE IF NOT EXISTS \\"" + 公式表名 + "\\" (" +
"层级 TEXT, 目标列 TEXT, 表达式 TEXT, 排序 INTEGER, 启用 INTEGER DEFAULT 1);", conn))
cmd.ExecuteNonQuery();''',
''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"CREATE TABLE IF NOT EXISTS \\"" + 公式表名 + "\\" (" +
"层级 TEXT, 目标列 TEXT, 表达式 TEXT, 排序 INTEGER, 启用 INTEGER DEFAULT 1);", conn))
cmd.ExecuteNonQuery();
// 开关表放项目库(触发器同库),由 重建项目全部触发器 保证存在''')
# 3) 所有触发器 CREATE 加 WHEN 开关条件
rep2(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_u AFTER UPDATE OF " + of + " ON \\"" +
元件表 + "\\" FOR EACH ROW BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +
" WHERE rowid=NEW.rowid; " + 刷箱号 + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_i AFTER INSERT ON \\"" +
元件表 + "\\" FOR EACH ROW BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +
" WHERE rowid=NEW.rowid; " + 刷箱号 + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_d AFTER DELETE ON \\"" +
元件表 + "\\" FOR EACH ROW BEGIN " + 刷箱号 + 刷图号 + "END;");''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_u AFTER UPDATE OF " + of + " ON \\"" +
元件表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +
" WHERE rowid=NEW.rowid; " + 刷箱号 + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_i AFTER INSERT ON \\"" +
元件表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN UPDATE \\"" + 元件表 + "\\" SET " + selfSet +
" WHERE rowid=NEW.rowid; " + 刷箱号 + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(元件表) + "_d AFTER DELETE ON \\"" +
元件表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN " + 刷箱号 + 刷图号 + "END;");''')
rep2(''' Exec(conn, tx, "DROP TRIGGER IF EXISTS " + 触发器前缀 + Safe(箱号表) + "_r_" + Safe(箱号ID) + "_u;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_r_" + Safe(箱号ID) +
"_u AFTER UPDATE OF " + of + " ON \\"" + 箱号表 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
箱号ID.Replace("'", "''") + "' BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetNew +
" WHERE rowid=NEW.rowid; UPDATE \\"" + 图号表名 + "\\" SET " + thSet +
" WHERE \\"内部ID\\"='" + 图号ID.Replace("'", "''") + "'; END;");''',
''' Exec(conn, tx, "DROP TRIGGER IF EXISTS " + 触发器前缀 + Safe(箱号表) + "_r_" + Safe(箱号ID) + "_u;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_r_" + Safe(箱号ID) +
"_u AFTER UPDATE OF " + of + " ON \\"" + 箱号表 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
箱号ID.Replace("'", "''") + "' AND " + 开关条件 + " BEGIN UPDATE \\"" + 箱号表 + "\\" SET " + xhSetNew +
" WHERE rowid=NEW.rowid; UPDATE \\"" + 图号表名 + "\\" SET " + thSet +
" WHERE \\"内部ID\\"='" + 图号ID.Replace("'", "''") + "'; END;");''')
rep2(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_i AFTER INSERT ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_d AFTER DELETE ON \\"" +
箱号表 + "\\" FOR EACH ROW BEGIN " + 刷图号 + "END;");''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_i AFTER INSERT ON \\"" +
箱号表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN " + 刷图号 + "END;");
Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + Safe(箱号表) + "_d AFTER DELETE ON \\"" +
箱号表 + "\\" FOR EACH ROW WHEN " + 开关条件 + " BEGIN " + 刷图号 + "END;");''')
rep2(''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + "th_" + suffix + "_u AFTER UPDATE OF \\"" +
图号输入列[0] + "\\" ON \\"" + 图号表名 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
图号ID.Replace("'", "''") + "' BEGIN UPDATE \\"" + 图号表名 + "\\" SET " + thSetNew +
" WHERE rowid=NEW.rowid; END;");''',
''' Exec(conn, tx, "CREATE TRIGGER " + 触发器前缀 + "th_" + suffix + "_u AFTER UPDATE OF \\"" +
图号输入列[0] + "\\" ON \\"" + 图号表名 + "\\" FOR EACH ROW WHEN NEW.\\"内部ID\\"='" +
图号ID.Replace("'", "''") + "' AND " + 开关条件 + " BEGIN UPDATE \\"" + 图号表名 + "\\" SET " + thSetNew +
" WHERE rowid=NEW.rowid; END;");''')
# 4) 重建项目全部触发器:确保开关表+默认行
rep2(''' var old = new List<string>();''',
''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"CREATE TABLE IF NOT EXISTS _触发器开关 (id INTEGER PRIMARY KEY, 开关 INTEGER DEFAULT 0);", conn, tx))
cmd.ExecuteNonQuery();
Exec(conn, tx, "INSERT OR IGNORE INTO _触发器开关 (id, 开关) VALUES (1, 0);");
var old = new List<string>();''')
# 5) 批量刷新方法(管线提交前调用):按表索引刷新该图号全部计算
rep2(''' /// <summary>新表(管线内建表后)即时补触发器——只处理该表自身。</summary>''',
''' /// <summary>
/// 批量刷新某图号的全部计算(触发器关闭期间用):元件表派生列→箱号行聚合→图号行聚合。
/// 全部为集合 SQL,每表一条 UPDATE;开关由调用方控制。
/// </summary>
public static void 批量刷新图号计算(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
string 图号ID, string 图号表名, System.Data.SQLite.SQLiteConnection fakConn)
{
var 元件公式 = 加载公式(fakConn, "元件");
var 箱号公式 = 加载公式(fakConn, "箱号");
var 图号公式 = 加载公式(fakConn, "图号");
string xhTbl = null;
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 实际表名 FROM 表索引 WHERE 类型='箱号' AND 图号=@th LIMIT 1;", conn, tx))
{
cmd.Parameters.AddWithValue("@th", 图号ID);
object o = cmd.ExecuteScalar();
xhTbl = o == null || o == DBNull.Value ? null : o.ToString();
}
if (string.IsNullOrEmpty(xhTbl)) return;
// 1) 每张元件表:派生列集合 UPDATE
var yj表们 = new List<string>();
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 实际表名 FROM 表索引 WHERE 类型='元件' AND 图号=@th;", conn, tx))
{
cmd.Parameters.AddWithValue("@th", 图号ID);
using (var r = cmd.ExecuteReader())
while (r.Read()) yj表们.Add(r.GetString(0));
}
string yjSet = 生成Set片段(元件公式, "ROW", null);
foreach (var yj in yj表们)
Exec(conn, tx, "UPDATE \\"" + yj + "\\" SET " + yjSet + ";");
// 2) 箱号每行:聚合(行自己的元件表 SUM)——行级一条 UPDATE
string xhSet = 生成Set片段(箱号公式, "ROW", null); // 由下方逐行替换 SUM
// 逐行:SUM 需要按行的元件表,取 表索引(元件) 列表
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"SELECT 箱号, 实际表名 FROM 表索引 WHERE 类型='元件' AND 图号=@th;", conn, tx))
{
cmd.Parameters.AddWithValue("@th", 图号ID);
using (var r = cmd.ExecuteReader())
{
var pairs = new List<string[]>();
while (r.Read()) pairs.Add(new[] { r.IsDBNull(0) ? "" : r.GetString(0), r.IsDBNull(1) ? "" : r.GetString(1) });
foreach (var pr in pairs)
{
string 行Set = 生成Set片段(箱号公式, "ROW", pr[1]);
Exec(conn, tx, "UPDATE \\"" + xhTbl + "\\" SET " + 行Set + " WHERE \\"内部ID\\"='" + pr[0].Replace("'", "''") + "';");
}
}
}
// 3) 图号行:聚合(FROM 箱号表)
string thSet = 生成Set片段(图号公式, "ROW", xhTbl);
Exec(conn, tx, "UPDATE \\"" + 图号表名 + "\\" SET " + thSet + " WHERE \\"内部ID\\"='" + 图号ID.Replace("'", "''") + "';");
}
/// <summary>开关设值(1=批量模式跳过触发器,0=恢复自动触发)。</summary>
public static void 开关设(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, int 值)
{
Exec(conn, tx, "UPDATE _触发器开关 SET 开关=" + 值 + " WHERE id=1;");
}
/// <summary>新表(管线内建表后)即时补触发器——只处理该表自身。</summary>''')
io.open(p, "w", encoding="utf-8-sig").write(s)
print("计算引擎 完成", n, "处")
+147
View File
@@ -0,0 +1,147 @@
# csc 编译验证(UTF-8)
/target:winexe
/platform:x86
/utf8output
/nologo
/warn:0
/win32icon:app.ico
/out:bin\Debug\成套报价软件.exe
/r:System.dll
/r:System.Core.dll
/r:System.Data.dll
/r:System.Drawing.dll
/r:System.Windows.Forms.dll
/r:System.Xml.dll
/r:System.IO.Compression.dll
/r:System.IO.Compression.FileSystem.dll
/r:"C:\Program Files (x86)\SharpDevelop\5.2\bin\Smart.CustomComponents.dll"
/r:"C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.dll"
/r:"C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.ThemeVS2015.dll"
/r:"lib\AdvancedDataGridView.dll"
/r:"lib\Microsoft.Web.WebView2.Core.dll"
/r:"lib\Microsoft.Web.WebView2.WinForms.dll"
/r:"C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Data.SQLite.dll"
/r:"lib\NPOI.dll"
/r:"lib\NPOI.OOXML.dll"
/r:"lib\NPOI.OpenXml4Net.dll"
/r:"lib\NPOI.OpenXmlFormats.dll"
/r:"lib\DotNetZip.dll"
/r:"lib\ICSharpCode.SharpZipLib.dll"
/r:"lib\unvell.ReoGrid.dll"
/r:"lib\CJZZ.Controls.dll"
/r:System.Web.Extensions.dll
/r:System.Configuration.dll
MainForm.cs
计算引擎.cs
编辑直写库.cs
MainForm.Designer.cs
Program.cs
Properties\AssemblyInfo.cs
主窗口.cs
主窗口.菜单与库.cs
主窗口.公式重算.cs
主窗口.母排与备份.cs
备份目标选择.cs
会员安全客户端.cs
主窗口.状态栏.cs
主窗口.项目导入导出.cs
主窗口.SQLite加载.cs
MainForm.控件别名.cs
MainForm.旧页签兼容.cs
MainForm.停靠布局.cs
价格格式化.cs
xiangmu\新建项目.cs
xiangmu\新建项目.Designer.cs
xiangmu\修改项目.cs
xiangmu\修改项目.Designer.cs
xiangmu\甲方报表.cs
xiangmu\甲方报表.Designer.cs
xiangmu\项目云.cs
xiangmu\调系数.cs
xiangmu\调系数.Designer.cs
xiangmu\导入报价.cs
xiangmu\导入报价.Designer.cs
xiangmu\元件排序.cs
xiangmu\元件排序.Designer.cs
xiangmu\调母排.cs
xiangmu\调母排.Designer.cs
xiangmu\批量插入询问.cs
xiangmu\批量插入询问.Designer.cs
xiangmu\费用模板.cs
xiangmu\费用模板.Designer.cs
shezhi\企业信息.cs
shezhi\企业信息.Designer.cs
shezhi\关于软件.cs
shezhi\关于软件.Designer.cs
shezhi\许可证文本.cs
shezhi\许可证文本.Designer.cs
登录.cs
登录.Designer.cs
会员服务Host.cs
OpenClawHost.cs
AI助手接口.cs
AI助手设置.cs
AI助手设置.Designer.cs
扒图\PaTypes.cs
扒图\CadBridge.cs
扒图\CadViewerWindow.cs
扒图\CadViewerWindow.Designer.cs
扒图\PdfBridge.cs
扒图\PdfViewerWindow.cs
扒图\PdfiumNative.cs
扒图\PdfDocument.cs
扒图\OcrClient.cs
扒图\GlmOcrClient.cs
扒图\QianfanOcrClient.cs
扒图\UapisOcrClient.cs
扒图\UmiOcrClient.cs
扒图\UmiOcrHost.cs
扒图\UmiOcrLauncher.cs
扒图\OcrSettingsForm.cs
扒图\OcrSettingsForm.Designer.cs
扒图\Cad扒图窗口.cs
扒图\Pdf扒图窗口.cs
扒图\CadQuoteWindow.cs
扒图\CadQuoteWindow.Designer.cs
扒图\PdfQuoteWindow.cs
扒图\PdfQuoteWindow.Designer.cs
扒图\Cad\Form3.cs
扒图\Cad\Form3.ui.cs
扒图\Cad\新建项目.cs
扒图\Cad\新建项目.ui.cs
扒图\Pdf\Form3.cs
扒图\Pdf\Form3.Designer.cs
扒图\Pdf\新建项目.cs
扒图\Pdf\新建项目.Designer.cs
会员信息.cs
会员信息.Designer.cs
修改密码.cs
修改密码.Designer.cs
ku\用户库元件编辑.cs
ku\用户库元件编辑.Designer.cs
GCS母排计算\GCS母排计算窗口.cs
GCS母排计算\GCS母排计算窗口.Designer.cs
GCS母排计算\GCS推荐铜排窗口.cs
GCS母排计算\GCS推荐铜排窗口.Designer.cs
DockWindows\DockWindow\DockWindow.cs
DockWindows\DockWindow\DockWindow.Designer.cs
DockWindows\DockWindow2\DockWindow2.cs
DockWindows\DockWindow2\DockWindow2.Designer.cs
DockWindows\DockWindow3\DockWindow3.cs
DockWindows\DockWindow3\DockWindow3.Designer.cs
DockWindows\DockWindow4\DockWindow4.cs
DockWindows\DockWindow4\DockWindow4.Designer.cs
DockWindows\DockWindow5\DockWindow5.cs
DockWindows\DockWindow5\DockWindow5.Designer.cs
DockWindows\DockWindow6\DockWindow6.cs
DockWindows\DockWindow6\DockWindow6.Designer.cs
DockWindows\DockWindow7\DockWindow7.cs
DockWindows\DockWindow7\DockWindow7.Designer.cs
DockWindows\DockWindow8\DockWindow8.cs
DockWindows\DockWindow8\DockWindow8.Designer.cs
DockWindows\DockWindow9\DockWindow9.cs
DockWindows\DockWindow9\DockWindow9.Designer.cs
DockWindows\DockWindow10\DockWindow10.cs
DockWindows\DockWindow10\DockWindow10.Designer.cs
DockWindows\MainForm.dockSurface1.DockWindows.cs
报价公式.cs
+72
View File
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
"""管线接入触发器:①图号粘贴管线建 xh/yj 表后即建触发器 ②管尾 RecalcAllFromDatabase
→ 计算引擎.刷新项目汇总 ③管线开头加载公式。幂等(带标记检查)。"""
import io
import re
P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\主窗口.菜单与库.cs"
s = io.open(P, encoding="utf-8-sig").read()
n = 0
# 0) 管线开头加载公式(conn.Open 后、journal_mode 日志后):图号粘贴专属段
anchor = 'Log("[粘贴图号] 库=" + dbPath + " journal_mode=" + dcmd.ExecuteScalar());'
add = anchor + '''
// ★ 公式加载(Excel 式公式表):触发器生成与管线内联计算共用
using (var fakConn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(
System.IO.Path.Combine(System.IO.Path.GetDirectoryName(dbPath), "fak.db"))))
{
fakConn.Open();
计算引擎.EnsureFormulaTable(fakConn);
_公式_元件 = 计算引擎.加载公式(fakConn, "元件");
_公式_箱号 = 计算引擎.加载公式(fakConn, "箱号");
_公式_图号 = 计算引擎.加载公式(fakConn, "图号");
}'''
if "_公式_元件 = 计算引擎.加载公式" not in s:
assert anchor in s, "管线开头锚点缺失"
s = s.replace(anchor, add, 1)
n += 1
# 1) CREATE xh + 表索引登记后 → 建箱号表触发器
a1 = '''INSERT INTO 表索引 (类型, 图号, 箱号, 实际表名) VALUES ('箱号', @th, '', @real);", conn, tx))
{
cmd.Parameters.AddWithValue("@th", nt);
cmd.Parameters.AddWithValue("@real", xhTbl);
cmd.ExecuteNonQuery();
}'''
add1 = a1 + '''
// ★ 建箱号表触发器:INSERT/UPDATE/DELETE 自动算行内公式+刷新图号聚合
计算引擎.建箱号表触发器(conn, tx, xhTbl, nt, TuhaoTableName, _公式_箱号, _公式_图号);'''
if "计算引擎.建箱号表触发器(conn, tx, xhTbl" not in s:
assert a1 in s, "xh 锚点缺失"
s = s.replace(a1, add1, 1)
n += 1
# 2) 图号管线 CREATE yj + 表索引登记后 → 建元件表触发器
a2 = '''INSERT INTO 表索引 (类型, 图号, 箱号, 实际表名) VALUES ('元件', @th, @xh, @real);", conn, tx))
{
cmd.Parameters.AddWithValue("@th", nt);
cmd.Parameters.AddWithValue("@xh", nx);
cmd.Parameters.AddWithValue("@real", yjTbl);
cmd.ExecuteNonQuery();
}'''
add2 = a2 + '''
// ★ 建元件表触发器:INSERT/UPDATE/DELETE 自动算派生价+刷新箱号/图号聚合
计算引擎.建元件表触发器(conn, tx, yjTbl, xhTbl, nx, nt, TuhaoTableName,
_公式_元件, _公式_箱号, _公式_图号);'''
if "计算引擎.建元件表触发器(conn, tx, yjTbl" not in s:
assert a2 in s, "yj 锚点缺失"
s = s.replace(a2, add2, 1)
n += 1
# 3) 管尾 RecalcAllFromDatabase(pid) (图号粘贴 657) → 刷新项目汇总
old3 = ''' // ★ 全量重算:箱号聚合元件价格、图号聚合箱号、项目聚合图号
// (管线 INSERT 的公式列是 0,必须重算;纯数据库操作,后台线程安全)
RecalcAllFromDatabase(pid);'''
new3 = ''' // ★ 触发器已在 INSERT 时自动算好派生列与箱号/图号聚合;只补跨库项目汇总
计算引擎.刷新项目汇总(dbPath, pid, TuhaoTableName);'''
if old3 in s:
s = s.replace(old3, new3, 1)
n += 1
io.open(P, "w", encoding="utf-8-sig").write(s)
print("图号管线接入完成", n, "处")
+138
View File
@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
"""第二批接线:①箱号粘贴/方案库管尾 RecalcAllFromDatabase → 刷新项目汇总
②PasteYuanjianRowsToDb 建新表后建触发器 ③RecalcAllAfterReload 落库段退役
④SaveAllQuoteTablesIfDirty 置空(保留 EndEdit) ⑤SaveAllToDatabaseSilent 置空
⑥EnterProjectCore 挂触发器重建 ⑦保存按钮改实时保存提示"""
import io
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
n = 0
def edit(path, pairs, tag):
global n
s = io.open(path, encoding="utf-8-sig").read()
for old, new in pairs:
if new in s and old not in s:
continue
assert old in s, tag + " 锚点缺失: " + old[:60]
s = s.replace(old, new, 1)
n += 1
io.open(path, "w", encoding="utf-8-sig").write(s)
print(tag, "ok")
# ①②③ 菜单与库.cs
p1 = ROOT + r"\主窗口.菜单与库.cs"
edit(p1, [
# 箱号粘贴管尾
(''' // ★ 全量重算:箱号聚合元件价格、图号聚合箱号(管线 INSERT 公式列是 0)
RecalcAllFromDatabase(pid);''',
''' // ★ 触发器已自动算好箱号/图号聚合;只补跨库项目汇总
计算引擎.刷新项目汇总(dbPath, pid, TuhaoTableName);'''),
# 方案库两处
(''' RecalcAllFromDatabase(_currentProjectId); // ★ 重算箱号/图号/项目汇总价''',
''' 计算引擎.刷新项目汇总(GetQuoteDbPath(_currentProjectId), _currentProjectId, TuhaoTableName); // 触发器已算明细,补跨库项目汇总'''),
# PasteYuanjianRowsToDb 建新表后建触发器
(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"INSERT INTO 表索引 (类型, 图号, 箱号, 实际表名) VALUES ('元件', @th, @xh, @real);", conn, tx))
{
cmd.Parameters.AddWithValue("@th", tuhao);
cmd.Parameters.AddWithValue("@xh", xhId);
cmd.Parameters.AddWithValue("@real", tblName);
cmd.ExecuteNonQuery();
}''',
''' using (var cmd = new System.Data.SQLite.SQLiteCommand(
"INSERT INTO 表索引 (类型, 图号, 箱号, 实际表名) VALUES ('元件', @th, @xh, @real);", conn, tx))
{
cmd.Parameters.AddWithValue("@th", tuhao);
cmd.Parameters.AddWithValue("@xh", xhId);
cmd.Parameters.AddWithValue("@real", tblName);
cmd.ExecuteNonQuery();
}
// ★ 新元件表:建公式触发器(INSERT/UPDATE/DELETE 自动算派生价+刷聚合)
string _xhTblReal = LookupTableByIndex("箱号", tuhao, "", conn);
if (!string.IsNullOrEmpty(_xhTblReal))
{
using (var fakConn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(
System.IO.Path.Combine(System.IO.Path.GetDirectoryName(GetQuoteDbPath(projectId)), "fak.db"))))
{
fakConn.Open();
计算引擎.EnsureFormulaTable(fakConn);
计算引擎.建元件表触发器(conn, tx, tblName, _xhTblReal, xhId, tuhao, TuhaoTableName,
计算引擎.加载公式(fakConn, "元件"), 计算引擎.加载公式(fakConn, "箱号"),
计算引擎.加载公式(fakConn, "图号"));
}
}'''),
], "菜单与库.cs")
# ④⑤⑥ 公式重算.cs + 主窗口.cs
p2 = ROOT + r"\主窗口.公式重算.cs"
edit(p2, [
# RecalcAllAfterReload 尾部落库退役(直写库架构:内存只读,不回写)
(''' SaveYuanjianTableIfDirty();
SaveXianghaoTableIfDirty();
SaveTuhaoTableIfDirty();
if (_yuanjianTable != null) _yuanjianTable.AcceptChanges();''',
''' // ★ 直写库架构:派生值由数据库触发器算好并经"读回"刷新缓存,内存不再回写库
if (_yuanjianTable != null) _yuanjianTable.AcceptChanges();'''),
], "公式重算.cs")
p3 = ROOT + r"\主窗口.cs"
edit(p3, [
# 选型页回报价页:触发器已算好聚合,去掉全量重算
(''' // ★ 步骤①:先调 RecalcAllFromDatabase 重算数据库里的箱号/图号/项目汇总
// (此时数据库里的元件派生列已是新值,Recalc 会按新元件数据聚合)
// 必须先重算再 Reload,否则 Reload 把旧聚合值读到内存,显示还是旧的
RecalcAllFromDatabase(_currentProjectId);''',
''' // ★ 直写库+触发器:元件写库时聚合已自动更新,这里只补跨库项目汇总
计算引擎.刷新项目汇总(GetQuoteDbPath(_currentProjectId), _currentProjectId, TuhaoTableName);'''),
# SaveAllQuoteTablesIfDirty 置空(保留 EndEdit——把 UI 编辑提交进直写队列)
(''' private void SaveAllQuoteTablesIfDirty(bool force = true)
{
if (string.IsNullOrWhiteSpace(_currentProjectId)) return;
try
{
// 先提交所有正在编辑的单元格,否则最后一笔编辑没进 DataTable
if (this.advancedDataGridView5 != null) this.advancedDataGridView5.EndEdit();
if (this.advancedDataGridView4 != null) this.advancedDataGridView4.EndEdit();
if (this.advancedDataGridView9 != null) this.advancedDataGridView9.EndEdit();''',
''' private void SaveAllQuoteTablesIfDirty(bool force = true)
{
// ★ 直写库架构退役:编辑已实时 UPDATE 库(触发器自动计算),无"未保存"数据。
// 仅保留 EndEdit——把正在编辑的单元格提交进 CellValuePushed(排队直写库)。
if (string.IsNullOrWhiteSpace(_currentProjectId)) return;
try
{
if (this.advancedDataGridView5 != null) this.advancedDataGridView5.EndEdit();
if (this.advancedDataGridView4 != null) this.advancedDataGridView4.EndEdit();
if (this.advancedDataGridView9 != null) this.advancedDataGridView9.EndEdit();
return;'''),
# SaveAllToDatabaseSilent 主体置空(实时架构无保存动作;保留项目汇总刷新+项目列表刷新)
(''' private void SaveAllToDatabaseSilent()
{
if (string.IsNullOrWhiteSpace(_currentProjectId)) return;
// 先提交所有正在编辑的单元格到内存 DataTable
if (this.advancedDataGridView5 != null) this.advancedDataGridView5.EndEdit();
if (this.advancedDataGridView4 != null) this.advancedDataGridView4.EndEdit();
if (this.advancedDataGridView9 != null) this.advancedDataGridView9.EndEdit();''',
''' private void SaveAllToDatabaseSilent()
{
// ★ 直写库架构退役:数据已实时在库(触发器自动计算),保存动作不复存在。
// 只提交编辑队列 + 刷新跨库项目汇总(供项目列表页显示)。
if (string.IsNullOrWhiteSpace(_currentProjectId)) return;
if (this.advancedDataGridView5 != null) this.advancedDataGridView5.EndEdit();
if (this.advancedDataGridView4 != null) this.advancedDataGridView4.EndEdit();
if (this.advancedDataGridView9 != null) this.advancedDataGridView9.EndEdit();
try
{
计算引擎.刷新项目汇总(GetQuoteDbPath(_currentProjectId), _currentProjectId, TuhaoTableName);
}
catch (Exception exSummary) { Log("刷新项目汇总异常: " + exSummary.Message); }
return;
if (this.advancedDataGridView5 != null) this.advancedDataGridView5.EndEdit();
if (this.advancedDataGridView4 != null) this.advancedDataGridView4.EndEdit();
if (this.advancedDataGridView9 != null) this.advancedDataGridView9.EndEdit();'''),
], "主窗口.cs")
print("合计", n, "处")
+152
View File
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
"""第三批接线:①CreateXianghaoTable/CreateYuanjianTable 尾部重建全部触发器(兜底所有建表路径)
②EnterProjectCore 进项目时公式触发器自检 ③红绿"未保存"退役 ④保存按钮改实时保存提示
⑤撤销删除(项目库)后重建触发器 ⑥EnsureFormulaTriggers/保存提示 方法体"""
import io
ROOT = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件"
n = 0
def edit(path, pairs, tag):
global n
s = io.open(path, encoding="utf-8-sig").read()
for old, new in pairs:
if old not in s:
if new in s:
print(tag, "已有(跳过):", old[:40].replace("\n", " "))
continue
raise AssertionError(tag + " 锚点缺失: " + old[:80])
s = s.replace(old, new, 1)
n += 1
io.open(path, "w", encoding="utf-8-sig").write(s)
print(tag, "ok")
# ① 两个建表函数尾部:重建全部触发器(幂等兜底,覆盖一切建表路径)
p1 = ROOT + r"\主窗口.菜单与库.cs"
edit(p1, [
(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
{
cmd.ExecuteNonQuery();
}
}
}
catch { }
}
/// <summary>按真实ID表名建一张空元件表。</summary>''',
''' using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
{
cmd.ExecuteNonQuery();
}
}
// ★ 公式触发器兜底:重建项目库全部触发器(幂等,新表即刻具备自动计算能力)
EnsureFormulaTriggersQuiet(projectId);
}
catch { }
}
/// <summary>按真实ID表名建一张空元件表。</summary>'''),
(''' using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
{
cmd.ExecuteNonQuery();
}
}
}
catch { }
}
private void DeleteXianghao(string projectId, string tuhao, string xianghao)''',
''' using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
{
cmd.ExecuteNonQuery();
}
}
// ★ 公式触发器兜底:重建项目库全部触发器(幂等)
EnsureFormulaTriggersQuiet(projectId);
}
catch { }
}
private void DeleteXianghao(string projectId, string tuhao, string xianghao)'''),
# ⑤ 撤销删除(项目库快照还原)后重建触发器(快照库里的触发器可能是旧公式)
(''' // 快照 → 目标库
BackupSQLiteDb(snapPath, _undoTargetDbPath);''',
''' // 快照 → 目标库
BackupSQLiteDb(snapPath, _undoTargetDbPath);
// ★ 快照库的触发器可能是旧公式版本,还原后按当前公式表重建
if (!wasFak) EnsureFormulaTriggersQuiet(_undoProjectId);'''),
], "菜单与库.cs")
# ②⑥ 编辑直写库.cs:EnsureFormulaTriggers + 保存提示方法
p2 = ROOT + r"\编辑直写库.cs"
edit(p2, [
(''' /// <summary>公式表缓存(管线建触发器时用;图号粘贴管线开头加载)。</summary>
private List<公式条目> _公式_元件, _公式_箱号, _公式_图号;''',
''' /// <summary>公式表缓存(管线建触发器时用;图号粘贴管线开头加载)。</summary>
private List<公式条目> _公式_元件, _公式_箱号, _公式_图号;
/// <summary>进项目/建表/撤销删除后:按公式表重建项目库全部触发器(幂等,安静模式只记日志)。</summary>
private void EnsureFormulaTriggersQuiet(string projectId)
{
try
{
if (string.IsNullOrWhiteSpace(projectId)) return;
string dbPath = GetQuoteDbPath(projectId);
if (!System.IO.File.Exists(dbPath)) return;
string fak = System.IO.Path.Combine(
System.IO.Path.GetDirectoryName(dbPath), "fak.db");
using (var conn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(dbPath)))
using (var fakConn = new System.Data.SQLite.SQLiteConnection(GetConnStringStatic(fak)))
{
conn.Open();
fakConn.Open();
计算引擎.EnsureFormulaTable(fakConn);
计算引擎.重建项目全部触发器(conn, null, TuhaoTableName, fakConn);
}
Log("[公式触发器] 已按公式表重建 [" + projectId + "] 全部触发器");
}
catch (Exception ex) { Log("[公式触发器] 重建失败(不影响编辑,但派生列不会自动算): " + ex.Message); }
}
/// <summary>保存按钮(功能区"保存"):实时架构下数据已在库,只刷跨库项目汇总并提示。</summary>
private void 保存按钮_实时保存提示()
{
try { 计算引擎.刷新项目汇总(GetQuoteDbPath(_currentProjectId), _currentProjectId, TuhaoTableName); }
catch (Exception ex) { Log("[保存] 刷新项目汇总异常: " + ex.Message); }
SetStatus("数据已实时保存在数据库");
MessageBox.Show(this, "数据已实时保存在数据库:\\n编辑即写库,价格由数据库触发器自动计算,无需手动保存。",
"保存", MessageBoxButtons.OK, MessageBoxIcon.Information);
}'''),
], "编辑直写库.cs")
# ②③④ 主窗口.cs + 公式重算.cs
p3 = ROOT + r"\主窗口.cs"
edit(p3, [
# EnterProjectCore:进项目自检(幂等重建)
(''' private void EnterProjectCore(string pid)
{
Log("[EnterProjectCore] 开始进入项目 [" + pid + "], _isTabSyncing=" + _isTabSyncing);''',
''' private void EnterProjectCore(string pid)
{
Log("[EnterProjectCore] 开始进入项目 [" + pid + "], _isTabSyncing=" + _isTabSyncing);
// ★ 公式触发器自检:按公式表重建该项目库全部触发器(幂等;老库补装/公式变更生效)
EnsureFormulaTriggersQuiet(pid);'''),
], "主窗口.cs")
p4 = ROOT + r"\主窗口.公式重算.cs"
edit(p4, [
# 红绿"未保存"退役(实时架构库里永远最新)
(''' dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
标记未保存(); // 状态栏:保存状态置"未保存"''',
''' dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
// ★ 直写库架构:编辑即写库,不再有"未保存"状态'''),
], "公式重算.cs")
p5 = ROOT + r"\MainForm.cs"
edit(p5, [
# 保存按钮 → 实时保存提示
(''' case "button17": SaveAllToDatabase(); break; // 保存''',
''' case "button17": 保存按钮_实时保存提示(); break; // 保存(实时架构:提示+刷项目汇总)'''),
], "MainForm.cs")
print("合计", n, "处")
+61
View File
@@ -0,0 +1,61 @@
// 直写批复现:用与 直写Timer 完全相同的连接串/事务/UPDATE 序列,复现 SQL logic error
using System;
using System.Collections.Generic;
class 复现直写
{
[STAThread]
static void Main()
{
string dbPath = @"C:\成套报价软件\sjk\bjb\项目20260908001.db";
string cs = "Data Source=" + dbPath + ";Version=3;Journal Mode=WAL;Synchronous=Normal;Pooling=True;";
var 批 = new List<string[]>
{
new[]{"yj_1","1","报出系数","1.5"},
new[]{"yj_1","1","采购单价","1078.875"},
new[]{"yj_1","1","采购价","1078.875"},
new[]{"yj_1","1","报出单价","1078.875"},
new[]{"yj_1","1","报出价","1078.875"},
new[]{"xh_1","1","单台成本","1078.875"},
new[]{"xh_1","1","成本价","1078.875"},
new[]{"xh_1","1","单台报出","1078.875"},
new[]{"xh_1","1","报出价","1078.875"},
new[]{"xh_1","1","利润","0"},
new[]{"xh_1","1","利润率","0"},
new[]{"xh_1","1","毛利率","0"},
new[]{"advancedDataGridView9Table","1","箱柜数量","2"},
new[]{"advancedDataGridView9Table","1","单价","7552.13"},
new[]{"advancedDataGridView9Table","1","总价","7552.13"},
new[]{"advancedDataGridView9Table","1","成本总价","7552.13"},
new[]{"advancedDataGridView9Table","1","毛利","0"},
new[]{"advancedDataGridView9Table","1","毛利率","0"},
};
try
{
using (var conn = new System.Data.SQLite.SQLiteConnection(cs))
{
conn.Open();
using (var tx = conn.BeginTransaction())
{
foreach (var item in 批)
{
using (var cmd = new System.Data.SQLite.SQLiteCommand(
"UPDATE \"" + item[0].Replace("\"", "") + "\" SET \"" + item[2].Replace("\"", "") +
"\"=@v WHERE rowid=@r;", conn, tx))
{
cmd.Parameters.AddWithValue("@v", item[3]);
cmd.Parameters.AddWithValue("@r", long.Parse(item[1]));
cmd.ExecuteNonQuery();
}
}
tx.Commit();
}
}
Console.WriteLine("全部 OK —— 未复现");
}
catch (Exception ex)
{
Console.WriteLine("复现: " + ex.GetType().Name + " " + ex.Message);
}
}
}
+1
View File
@@ -0,0 +1 @@
109
+71
View File
@@ -0,0 +1,71 @@
# ============================================================
# 扒图同步检查.ps1 —— Cad/Pdf 扒图窗口 Form3.cs 漂移护栏
# ============================================================
# 背景: 扒图\Cad\Form3.cs 与 扒图\Pdf\Form3.cs 是约 98% 重复的双胞胎文件
# (物理合并被 SD 设计器"项目内自定义类型"死结阻挡,2026-10-01 决策维持双文件)。
# 历史上 Pdf 侧修复漏同步回 Cad(AdgvMsgHook 右键多选保持 / F1 合并防抖),
# 本脚本防止再次漂移。
# 原理: 两文件做字面映射归一化(CadBridge→PdfBridge / cadbt→pdfbt / CAD→PDF /
# namespace MyApp→MyAppPdf)后 git diff,差异行数不超过基线即 PASS。
# 用法: powershell -ExecutionPolicy Bypass -File tools\扒图同步检查.ps1
# 加 -UpdateBaseline 把当前差异量定为新基线(确认属合法演进后使用)。
# ============================================================
param([switch]$UpdateBaseline)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$cadPath = Join-Path $root '成套报价软件\扒图\Cad\Form3.cs'
$pdfPath = Join-Path $root '成套报价软件\扒图\Pdf\Form3.cs'
$baseline = Join-Path $PSScriptRoot '扒图同步基线.txt'
foreach ($f in @($cadPath, $pdfPath)) {
if (-not (Test-Path $f)) { Write-Host "[FAIL] 缺少文件: $f" -ForegroundColor Red; exit 2 }
}
function Normalize([string]$path) {
(Get-Content $path -Encoding UTF8) | ForEach-Object {
$_ = $_ -creplace '^namespace MyApp\s*$', 'namespace MyAppPdf'
$_ = $_ -creplace 'CadBridge', 'PdfBridge'
$_ = $_ -creplace 'cadbt', 'pdfbt'
$_ = $_ -creplace 'CAD', 'PDF'
$_
}
}
$tmpCad = Join-Path $env:TEMP 'baotu_cad_norm.cs'
$tmpPdf = Join-Path $env:TEMP 'baotu_pdf_norm.cs'
Normalize $cadPath | Set-Content $tmpCad -Encoding UTF8
Normalize $pdfPath | Set-Content $tmpPdf -Encoding UTF8
$diffFile = Join-Path $env:TEMP 'baotu_drift.diff'
$null = git diff --no-index --unified=1 $tmpCad $tmpPdf 2>$null | Out-File $diffFile -Encoding UTF8
$numstat = git diff --no-index --numstat $tmpCad $tmpPdf 2>$null
$added = 0; $deleted = 0
if ($numstat) {
$parts = (@($numstat)[0]) -split "`t"
if ($parts.Count -ge 2) { $added = [int]$parts[0]; $deleted = [int]$parts[1] }
}
$drift = $added + $deleted
if ($UpdateBaseline) {
Set-Content $baseline -Value $drift -Encoding ASCII
Write-Host "[OK] 基线已更新为 $drift 行差异 (完整差异: $diffFile)"
exit 0
}
if (-not (Test-Path $baseline)) {
Write-Host "[FAIL] 缺少基线文件 $baseline ,请先跑一次 -UpdateBaseline 生成" -ForegroundColor Red
exit 2
}
$limit = [int](Get-Content $baseline -TotalCount 1)
Write-Host ("扒图 Cad/Pdf 归一化差异: {0} 行 (基线上限 {1} 行)" -f $drift, $limit)
if ($drift -gt $limit) {
Write-Host "[FAIL] 差异超过基线 —— 两文件已漂移!请排查:" -ForegroundColor Red
Write-Host " ① 是否一侧修了 bug 忘了同步另一侧?(最常见原因)"
Write-Host " ② 完整差异见: $diffFile"
Write-Host " ③ 确属合法演进后,用 -UpdateBaseline 刷新基线"
exit 1
}
Write-Host "[PASS] 同步状态正常"