feat(est-test): 표지·갑지 상세편집, 산출 줄 글자모양, 수량산출서 인쇄, 엑셀형 셀 테두리·음영·수량붙여넣기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 18:53:58 +09:00
co-authored by Claude Opus 5.5
parent 3e7c477631
commit 67cb82d8d2
6 changed files with 118 additions and 3 deletions
+51 -1
View File
@@ -13,6 +13,9 @@ const EstQuantityTable = (() => {
p.align && `text-align:${p.align}`,
p.indent && `padding-left:${p.indent * 8}px`,
row.title && "font-weight:bold;font-size:20px;color:#800000",
p.bold && "font-weight:bold",
p.size && `font-size:${p.size}px`,
p.color && `color:${p.color}`,
]
.filter(Boolean)
.join(";");
@@ -155,6 +158,7 @@ const EstQuantityTable = (() => {
sheet.tableEdited = true;
}, false);
} else if (name === "qt-mul-load" || name === "qt-mul-take") mulLoad(name, state, node);
else if (name === "qt-print") print(state);
else return false;
return true;
}
@@ -175,7 +179,23 @@ const EstQuantityTable = (() => {
throw Error("정렬은 left·center·right 입니다.");
const indent = Number(prompt("들여쓰기(칸)", String(sheet.rows[index].para?.indent ?? 0)));
if (!(indent >= 0 && indent <= 40)) throw Error("들여쓰기는 0~40 칸입니다.");
change(() => (sheet.rows[index].para = { align, indent }));
// 글자모양(mnuFormatFont): 크기·굵게·색
const size =
Number(prompt("글자 크기(px, 0 = 기본)", String(sheet.rows[index].para?.size ?? 0))) || 0,
bold = confirm("굵게 할까요?"),
color =
prompt("글자색(#RRGGBB, 비우면 기본)", sheet.rows[index].para?.color || "")?.trim() || "";
if (color && !/^#[0-9a-f]{6}$/i.test(color)) throw Error("글자색은 #RRGGBB 꼴입니다.");
change(
() =>
(sheet.rows[index].para = {
align,
indent,
...(size && { size }),
...(bold && { bold }),
...(color && { color }),
}),
);
} else if (name === "qt-image") {
const picker = document.createElement("input");
picker.type = "file";
@@ -233,6 +253,36 @@ const EstQuantityTable = (() => {
state.notice = `「${src.Name}」 산출서를 불러왔습니다.`;
}
// 수량산출서 인쇄(btnPrint → TfrmMulPrintRange): 현재 산출서 또는 모두(산출서마다 간지 고르기), 산출 줄·표 그대로
function print(state) {
const sheets = state.project.quantitySheets || [];
if (!sheets.length) throw Error("수량산출서가 없습니다.");
const all = confirm("모든 수량산출서를 인쇄할까요? (취소 = 현재 산출서만)"),
gangi = all && confirm("산출서마다 간지를 넣을까요?"),
list = all ? sheets : [sheetOf(state)].filter(Boolean),
{ esc } = EstApp;
const body = list
.map((sheet) => {
const rows = sheet.rows
.map((r) =>
r.image
? `<img src="${esc(r.image)}" style="max-width:100%">`
: r.table
? `<table>${r.table.map((tr) => `<tr>${tr.map((c) => (c.vmerge ? "" : `<td${c.vtop ? ` rowspan="${c.vtop}"` : ""}>${esc(c.text)}</td>`)).join("")}</tr>`).join("")}</table>`
: `<div style="white-space:pre-wrap;${paraStyle(r)}">${esc(EstText.clean(r.LineData || ""))}</div>`,
)
.join("");
return `${gangi ? `<section class="gangi"><div>${esc(sheet.Name)}</div></section>` : ""}<section class="sheet"><h2>${esc(sheet.Name)} ${esc(sheet.Size || "")}</h2>${rows}</section>`;
})
.join("");
const win = open("", "_blank");
if (!win) throw Error("새 창을 열 수 없습니다. 팝업 차단을 확인하세요.");
win.document.write(
`<!doctype html><meta charset="utf-8"><title>수량산출서</title><style>body{font:10pt "Malgun Gothic",sans-serif}table{border-collapse:collapse}td{border:1px solid #777;padding:2px 4px}.sheet{break-after:page}.gangi{break-after:page;height:250mm;display:flex;align-items:center;justify-content:center;font-size:28pt;font-weight:bold}</style>${body}<script>print()<\/script>`,
);
win.document.close();
}
function install() {
document.addEventListener("click", (event) => {
const td = event.target.closest("[data-qt]");