Files
17168ERP/web/admin/item/TabletDesigner.aspx
T
2026-03-12 11:22:28 +08:00

941 lines
45 KiB
Plaintext

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TabletDesigner.aspx.cs" Inherits="admin_item_TabletDesigner" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" %>
<%@ Register Src="~/admin/_uc/alert.ascx" TagPrefix="uc1" TagName="alert" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<div class="d-flex overflow-hidden">
<div class="p-2 bg-dark text-white floting-box" style="width: 250px; left: 20px; top: 80px;">
<h6 class="border-bottom pb-2">設計工具箱</h6>
<div class="input-group mb-3">
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
版型</button>
<ul class="dropdown-menu style-menu">
<li><span class="dropdown-item" data-value="">選擇版型</span></li>
</ul>
<input type="text" id="styleName" class="form-control" aria-label="版型名稱">
</div>
<div class="form-floating mb-3">
<select class="form-select form-select-sm mb-2 " onchange="Designer.changePaper()" id="paperSize">
<option value="">請選擇</option>
</select>
<label for="paperSize" style="color: black">尺寸</label>
</div>
<div id="sizeDiv" style="display: none;">
<div class="form-floating mb-3">
<input type="text" class="form-control form-select-sm mb-2 " id="paperName" />
<label for="paperName" style="color: black">名稱</label>
</div>
<div class="form-floating mb-3">
<input type="text" class="form-control form-select-sm mb-2 " id="paperWidth" />
<label for="paperWidth" style="color: black">寬度</label>
</div>
<div class="form-floating mb-3">
<input type="text" class="form-control form-select-sm mb-2 " id="paperHeight" />
<label for="paperHeight" style="color: black">高度</label>
</div>
<div class="row">
<span class="btn btn-info" onclick="Designer.savePaperSize()">儲存</span>
</div>
</div>
<div id="paperOrientation" onclick="Designer.changeOrientation()" class="mb-2">
<span></span>
</div>
<div>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">顯示物件
</a>
<ul class="dropdown-menu">
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('address')">地址欄</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('title1')">牌位正名</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('lefttitle')">左正名</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('righttitle')">右正名</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('titletriangle')">品字名單</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('combined')">雙姓合併置中</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('tricombined')">三姓合併置中</span></li>
<li><span class="btn btn-sm btn-outline-light w-100 mb-2 dropdown-item" onclick="Designer.displayItem('alive')">陽上報恩</span></li>
</ul>
</li>
</ul>
</div>
<div class="form-floating mb-3">
<%-- <span class="btn btn-sm btn-outline-light w-100 mb-2" onclick="Designer.clickBackend()">
<i class="bi bi-upload me-1"></i>上傳自訂底圖 (PNG/JPG)
</span>--%>
<select class="form-select form-select-sm mb-2 " onchange="Designer.changeBg()" id="backendInp">
<option value="">請選擇</option>
</select>
<label for="backendInp" style="color: black">選用底圖</label>
<%-- <input id="backendInp" type="file" accept="image/png, image/jpeg,image/svg+xml" style="display: none">--%>
</div>
<hr>
<div class="form-floating mb-3">
<select class="form-select form-select-sm mb-2 " onchange="Designer.changePrintSize()" id="printSize">
<option value="">請選擇</option>
</select>
<label for="printSize" style="color: black">列印尺寸</label>
</div>
<div class="form-floating mb-3">
<select class="form-select form-select-sm mb-2 " onchange="Designer.changePrintMode()" id="printMode">
<option value="">請選擇</option>
<option value="combine">合併</option>
<option value="origin">原尺寸</option>
</select>
<label for="printMode" style="color: black">列印模式</label>
</div>
<div class="form-floating mb-3">
<input type="number" id="perpage" class="form-control form-control-sm mb-2">
<label class="small" for="perpage" style="color: black;">每頁列印筆數</label>
</div>
<div class="row">
<div class="col-6">
<span class="btn btn-sm btn-outline-info w-100 mb-2" onclick="Designer.saveStyle()">存檔</span>
</div>
<div class="col-6">
<span class="btn btn-sm btn-outline-info w-100 mb-2" onclick="Designer.print()">預覽列印</span>
</div>
</div>
</div>
<div id="printArea" style="width: 100vw; height: 100vh">
<div class="canvas-area flex-grow-1 overflow-auto d-flex flex-column align-items-center position-relative" id="canvas">
<div class="tablet-paper">
<%--<div class="tablet-element vertical-text">嘿嘿</div>--%>
</div>
</div>
</div>
<div id="attr-box" class="d-none p-2 bg-dark text-whit floting-box" style="top: 80px; right: 20px">
<div class="form-floating mb-3">
<textarea id="inp-text" class="form-control form-control-sm mb-2" rows="5"></textarea>
<label class="small" for="inp-text">內容文字</label>
</div>
<div class="form-floating mb-3">
<input type="number" id="inp-size" class="form-control form-control-sm mb-2">
<label class="small" for="inp-size">字體大小 (pt)</label>
</div>
<div class="row ">
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="2offset" class="form-control form-control-sm mb-2">
<label class="small" for="2offset">二行偏移量</label>
</div>
</div>
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="3offset" class="form-control form-control-sm mb-2">
<label class="small" for="3offset">三行偏移量</label>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="4offset" class="form-control form-control-sm mb-2">
<label class="small" for="4offset">四行偏移量</label>
</div>
</div>
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="breakLen" class="form-control form-control-sm mb-2">
<label class="small" for="breakLen">斷行字數</label>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="xPosition" class="form-control form-control-sm mb-2">
<label class="small" for="xPosition">X</label>
</div>
</div>
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="yPosition" class="form-control form-control-sm mb-2">
<label class="small" for="yPosition">Y</label>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="width" class="form-control form-control-sm mb-2">
<label class="small" for="width">寬度</label>
</div>
</div>
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="height" class="form-control form-control-sm mb-2">
<label class="small" for="height">高度</label>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="textWidth" class="form-control form-control-sm mb-2">
<label class="small" for="textWidth">文字寬度</label>
</div>
</div>
<div class="col-6">
<div class="form-floating mb-3">
<input type="number" id="textHeight" class="form-control form-control-sm mb-2">
<label class="small" for="textHeight">文字高度</label>
</div>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="footer_script" runat="Server">
<script>
const Designer = {
elements: [],
activeId: null,
styleID: null,
paper: { width: "100", height: "272" },
rosterLimit: 8,
allSize: [
],
bg: [
{ name: "黃1", path: "../../admin/print/html/tablet-1.svg" },
{ name: "黃2", path: "../../admin/print/html/tablet-1B.svg" },
{ name: "紅1", path: "../../admin/print/html/tablet-2.svg" },
{ name: "紅2", path: "../../admin/print/html/tablet-2B.svg" }
],
allStyle: [
],
tabletElements: [
],
orientation: "portrait",
init() {
//$(".tablet-element").draggable({});
this.getPaperSize();
this.getElements();
this.getStyles();
this.bindEvents();
this.bindBackend();
this.bg.forEach(x => {
$("#backendInp").append("<option value='" + x.name + "'>" + x.name + "</option>")
});
$("#paperOrientation").html("<span>直向</span>")
},
print() {
let s = this.allStyle.find(x => x.styleID == this.styleID)
let size = this.allSize.find(x => x.id = s.paperSize)
let w = window.open('', '_blank');
if (!w) {
alert("請允許瀏覽器開啟彈出式視窗!");
return;
}
// 1. 抓取您原本網頁定義的 HTTP_HOST (您的全域變數),如果沒有則抓取當前網址
let hostUrl = typeof HTTP_HOST !== 'undefined' ? HTTP_HOST : (window.location.protocol + "//" + window.location.host);
if (!hostUrl.endsWith('/')) hostUrl += '/';
// 2. 抓出畫布完整的 HTML
let canvasHtml = $("#printArea").html();
// 3. 【關鍵修復】把所有的 "../../" 替換成完整的網址,解決底圖破圖問題!
canvasHtml = canvasHtml.replace(/\.\.\/\.\.\//g, hostUrl);
// 4. 組合列印視窗的 HTML
let htmlContent = `
<!DOCTYPE html>
<html>
<head>
<title>預覽列印 - 牌位設計</title>
<meta charset="utf-8">
<style>
/* 名單金字塔佈局容器 */
.roster-container {
width: 100%; height: 100%;
writing-mode: vertical-rl; /* 直書 */
display: flex;
/*flex-direction: column;*/ /* 雖然是直書,但物理上我們是將「上層區」和「下層區」垂直堆疊 */
flex-direction: row; /* 上下分層 (Top / Bottom) */
align-items: center; /* 左右置中對齊 */
justify-content: center;
gap: 20px; /* 這是「上層」跟「下層」之間的距離,可以設大一點 */
}
.roster-row {
display: flex;
flex-direction: row-reverse; /* 直書由右至左,所以 Row 要反向或依需求調整 */
justify-content: center;
gap: 8px; /* 名字之間的間距 */
margin-left: 10px; /* 上下排之間的間距 (因為是直書,margin-left 是物理上的左邊/下方) */
}
.name-group {
display: flex;
flex-direction: column; /* ★★★ 關鍵:這讓名字左右並排 ★★★ */
justify-content: center;
align-items: center;
/* gap 由 HTML 動態綁定 */
}
.roster-name {
text-orientation: upright;
/*font-weight: bold;*/
white-space: nowrap;
line-height: 1.2;
font-family: 'Kaiti', serif;
/* 確保名字本身不會佔據過多寬度導致間距看起來很大 */
width: fit-content;
}
/* =======================================
暴力防護層:避免外部 CSS 載入失敗或被干擾
======================================= */
/* 【修復直書變橫書】強制寫入直書屬性 */
.vertical-text {
writing-mode: vertical-rl !important;
-webkit-writing-mode: vertical-rl !important;
text-orientation: mixed !important;
}
/* 【修復底圖沒出現】強制印出背景設定 */
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
color-adjust: exact !important;
}
/* 【修復排版沒照設定】覆寫原本 HTML 裡的 fixed,並防止 Bootstrap 洗掉 absolute */
.tablet-paper {
position: relative !important;
margin: 0 auto !important;
page-break-after: always !important;
box-shadow: none !important;
width: var(--page-w);
height: var(--page-h);
}
.tablet-element {
position: absolute !important; /* 絕對不能被 Bootstrap 覆蓋為 static */
color: black !important; /* 確保文字是黑色的 */
}
/* 列印時的紙張大小與頁面歸零 */
@media print {
@page {
/* 動態套用您設定的紙張寬高 */
size: ${size.width}mm ${size.height}mm portrait;
margin: 0;
}
body, html {
margin: 0 !important;
padding: 0 !important;
}
body{
--page-w: ${size.width}mm;
--page-h: ${size.height}mm;
--font-max: 22pt;
--bg-padding: 0;
}
}
</style>
</head>
<body>
${canvasHtml}
</body>
</html>
`;
w.document.write(htmlContent);
w.document.close();
// 5. 將等待時間稍微拉長至 1.2 秒,確保大張的 SVG 底圖與外部字體(如標楷體)下載完畢
setTimeout(() => {
w.focus();
w.print();
}, 1200);
},
changeOrientation() {
this.orientation = (this.orientation === "portrait" ? "land" : "portrait");
$("#paperOrientation").empty();
let ori = this.orientation == "portrait" ? "直向" : "橫向";
$("#paperOrientation").html("<span>" + ori + "</span>");
let paperSize = $("#paperSize").val()
let size = this.allSize.find(x => x.name == paperSize)
this.paper.width = this.orientation == "portrait" ? size.width : size.height;
this.paper.height = this.orientation == "portrait" ? size.height : size.width;
$(".tablet-paper").css({
"background-color": "white",
width: this.paper.width + "mm",
height: this.paper.height + "mm",
"background-repeat": "no-repeat!important",
});
},
async getPaperSize() {
let self = this
await axios
.post(HTTP_HOST + 'api/tablet/GetPaperSize', {})
.then(response => {
//if (response.result=="Y") {
if (response.status == "200") {
let data = response.data;
if (data.result == "Y") {
data.data.forEach(x => {
self.allSize.push({ name: x.paperName, id: x.paperID, width: x.width, height: x.height })
});
}
}
});
$("#paperSize").append("<option value='newsize' >新增尺寸</option>")
this.allSize.forEach(x => {
$("#paperSize").append("<option value='" + x.id + "'>" + x.name + "(" + x.width + "*" + x.height + ")</option>")
$("#printSize").append("<option value='" + x.id + "'>" + x.name + "(" + x.width + "*" + x.height + ")</option>");
})
},
async getElements() {
let self = this
await axios
.post(HTTP_HOST + 'api/tablet/GetTabletElement', {})
.then(response => {
//if (response.result=="Y") {
if (response.status == "200") {
let data = response.data;
if (data.result == "Y") {
data.data.forEach(x => {
self.tabletElements.push(x);
});
}
}
});
},
async getStyles() {
await axios
.post(HTTP_HOST + 'api/tablet/GetStyleData', {})
.then(response => {
//if (response.result=="Y") {
if (response.status == "200") {
let data = response.data;
if (data.result == "Y") {
data.data.forEach(x => {
this.allStyle.push(x);
$(".style-menu").append("<li><span class='dropdown-item style-item' data-value='" + x.styleID + "'>" + x.name + "</span></li>");
});
this.bindDropdown();
}
}
//}
});
},
bindDropdown() {
let self = this;
$(".style-menu li").on("click", function (e) {
$(".style-menu li").removeClass('active');
$(this).addClass('active');
$("#styleName").val(e.target.textContent);
self.styleID = e.target.getAttribute("data-value")
self.changeStyle(e.target.getAttribute("data-value"));
let style = self.allStyle.find(x => x.styleID == e.target.getAttribute("data-value"));
if (style) {
let size = self.allSize.find(x => x.name == style.paperSize);
if (size) {
self.paper.width = size.width;
self.paper.height = size.height;
} else {
self.paper.width = 100;
self.paper.height = 272;
}
} else {
self.paper.width = 100;
self.paper.height = 272;
}
});
},
async changeStyle(id) {//切換版型,抓回明細
let self = this;
this.elements.length = 0;
let obj = this.elements
let s = this.allStyle.find(x => x.styleID == id)
let size = this.allSize.find(x => x.id == s.paperSize)
this.paper.width = size.width
this.paper.height = size.height
$("#paperSize").val(s.paperSize);
$("#backendInp").val(s.backendImg);
$("#printSize").val(s.printSize);
$("#printMode").val(s.printMode);
$("#perPage").val(s.printPageCount);
let img = this.bg.find(x => x.name == s.backendImg);
$(".tablet-paper").css({
'background-image': 'url(' + (img ? img.path : "") + ')', 'background-size': '100% 100%',
width: (size ? size.width : self.paper.width) + "mm",
height: (size ? size.height : self.paper.height) + "mm",
})
await axios
.post(HTTP_HOST + 'api/tablet/GetStyleDetailData', { styleID: id })
.then(response => {
if (response.status == "200") {
let data = response.data;
if (data.result == "Y") {
let details = data.data;
$(".tablet-paper").empty()
details.forEach(el => {
let te = self.tabletElements.find(x => {
return x.elementID == el.elementID
});
let config = {
id: el.elementID,
type: te.elementType,
text: te.sampleContent.replaceAll("\\n", "\n"),
x: el.startX, y: el.startY,
style: {
fontSize: el.fontSize,
fontFamily: "Kaiti",
isVertical: true,
letterSpacing: 5, lineHeight: 1.5,
visibility: el.isActive,
},
width: el.width,
height: el.height,
textWidth: el.textWidth,
textHeight: el.textHeight,
breakLen: el.breakLen,
backendInp: el.backendImg
};
obj.push(config)
})
this.render();
}
}
});
},
changeBg() {
let path = this.bg.find(el => el.name == $("#backendInp").val())
$(".tablet-paper").css({ 'background-image': 'url(' + path.path + ')', 'background-size': '100% 100%' })
},
changePrintSize() {
},
changePaper() {
let paperSize = $("#paperSize").val()
let self = this;
if (paperSize == "newsize") {
$("#sizeDiv").attr("style", "display:");
} else {
console.log("paperSize:", paperSize);
let size = this.allSize.find(x => x.id == paperSize)
$(".tablet-paper").css({
"background-color": "white",
width: (size ? size.width : self.paper.width) + "mm",
height: (size ? size.height : self.paper.height) + "mm",
});
}
},
async savePaperSize() {
let ps = {
paperName: $("#paperName").val(),
width: $("#paperWidth").val(),
height: $("#paperHeight").val()
}
console.log(ps);
await axios
.post(HTTP_HOST + 'api/tablet/SavePaperSize', ps)
.then(response => {
if (response.status == "200") {
}
});
},
changePrintMode() {
},
bindEvents() {
// 文字變更連動渲染
$('#inp-text').on('input', (e) => this.updateActive('text', $(e.target).val()));
$('#inp-size').on('input', (e) => this.updateActive('fontSize', $(e.target).val()));
$('#width').on('input', (e) => this.updateActive('width', $(e.target).val()))
$('#2offset').on('input', (e) => this.updateActive('2offset', $(e.target).val()))
$('#3offset').on('input', (e) => this.updateActive('3offset', $(e.target).val()))
$('#4offset').on('input', (e) => this.updateActive('4offset', $(e.target).val()))
$('#breakLen').on('input', (e) => this.updateActive('breakLen', $(e.target).val()))
$('#width').on('input', (e) => this.updateActive('width', $(e.target).val()))
$('#height').on('input', (e) => this.updateActive('height', $(e.target).val()))
$('#textWidth').on('input', (e) => this.updateActive('textWidth', $(e.target).val()))
$('#textHeight').on('input', (e) => this.updateActive('textHeight', $(e.target).val()))
$(document).on('mousedown', '.tablet-element', (e) => {
e.stopPropagation();
this.select($(e.currentTarget).attr('id'));
});
},
loadConfig() {
$.ajax({
type: "POST", url: "TabletDesigner.aspx/GetConfig", contentType: "application/json",
success: (res) => {
const data = JSON.parse(res.d);
this.elements = data.elements;
this.render();
}
});
},
// 處理分頁渲染邏輯
render() {
$(".tablet-paper").empty();
//const $canvas = $('#canvas').empty();
const roster = this.elements.find(e => e.type === 'roster');
const names = roster ? roster.text.split('\n').filter(s => s.trim()) : [];
const pages = Math.max(1, Math.ceil(names.length / this.rosterLimit));
let style = this.allStyle.find(x => x.styleID == this.styleID);
let size = this.allSize.find(x => x.id == style.paperSize);
console.log(this.paper, size);
//for (let p = 0; p < pages; p++) {
// const $paper = $('<div class="tablet-paper"></div>')
// .css({ "background-color": "red", width: this.paper.width + 'mm', height: this.paper.height + 'mm' });
let $paper = $(".tablet-paper").css({
"background-color": "white",
width: (size ? size.width : this.paper.width) + 'mm',
height: (size ? size.height : this.paper.height) + 'mm',
position: "absolute"
});
//const slice = names.slice(p * this.rosterLimit, (p + 1) * this.rosterLimit);
const slice = names.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit);
this.elements.forEach(el => {
console.log("shit:", el);
$paper.append(this.createEl(el, slice));
});
//$canvas.append($paper);
//}
//let s = this.allStyle.find(x => x.styleID == this.styleID);
//let d = this.elements.filter(x => x.styleID == this.styleID);
//console.log(s,d);
this.makeDraggable();
},
createEl(el, slice) {
let html = '';
// 1. 智慧名單渲染
if (el.type === 'roster') {
html = this.renderRoster(slice, el);
}
// 2. 正名合併置中
else if (el.type === 'combined-center') {
const parts = el.text.split('\n');
html = `<div class="ancestor-wrapper" >
<span class="main-name" >${parts[0] || ''}</span>
<span class="sub-text">${parts[1] || ''}</span>
</div>`;
}
// 3. 地址數字轉橫排
else if (el.id === 'address') {
html = el.text.replace(/(\d+)/g, '<span class="tate-chu-yoko">$1</span>');
}
else if (el.id === 'title1') {
html = this.renderNameList(slice, el);
console.log(html);
}
else {
html = el.text;
}
console.log("QQ:", el);
return $(`<div class="tablet-element vertical-text ${this.activeId === el.id ? 'selected' : ''}" id="${el.id}"></div>`)
.css({
position: "absolute", left: el.x + "mm", top: el.y + "mm", fontSize: el.style.fontSize + 'pt', fontFamily: el.style.fontFamily, "z-index": 9999, visibility: el.style.visibility
//position: "absolute", left: el.startX + "mm", top: el.startY + "mm", fontSize: el.fontSize + 'pt', fontFamily: el.fontFamily, "z-index": 9999, visibility: el.isActive
})
.html(html);
},
renderNameList(names, el) {
let $namelist = $(`<div class='nameList'></div>`).css({
"writing-mode": "vertical rl",
display: "flex",
"flex-direction": "row",
"flex-wrap": "wrap",
margin: "auto",
width: `${el.width}px`,
height: `${el.height}px`,
border: "0px solid #ccc",
padding: "1px",
"font-family": "BiauKai",
"letter-spacing": "0.1em",
"column-gap": "1px",
"row-gap": "1px",
"align-items": "center",
});
console.log("nameList:", $namelist)
let self = this;
names.forEach(n => {
$namelist.append(self.renderNameSpan(n, el))
})
return $namelist;
},
renderNameSpan(name, el) {
return $(`<span>${name}</span>`).css({
display: "block",
"min-height": `${el.textHeight}px`,
"max-height": `${el.height}px`,
width: `${el.textWidth}px`,
"text-align": "justify",
"text-align-last": "justify",
"margin-bottom": "40px",
"margin-left": "5px",
"text-justify": "inter-character",
});
},
// 品字佈局邏輯:一上二下
renderRoster(names, el) {
if (!names.length) return '';
const mid = names.length === 1 ? 1 : Math.floor(names.length / 2);
const top = names.slice(0, mid);
const bot = names.slice(mid);
const size = this.autoScale(names, el.fontSize);
let h = $(`<div class="roster-container"></div>`).css(
{
width: "100%", height: "600px",
"writing-mode": "vertical-rl", /* 直書 */
display: "flex",
"flex-direction": "row", /* 上下分層 (Top / Bottom) */
"align-items": "center", /* 左右置中對齊 */
"justify-content": "center",
gap: "20px",
});
h.append(
$(`<div class="name-group">${top.map(n => `<div class="roster-name" style="font-size:${size}pt">${n}</div>`).join('')}</div>`)
)
if (bot.length) h.append(`<div class="name-group">${bot.map(n => `<div class="roster-name" style="font-size:${size}pt">${n}</div>`).join('')}</div>`)
//h += `<div class="name-group">${top.map(n => `<div class="roster-name" style="font-size:${size}pt">${n}</div>`).join('')}</div>`;
///if (bot.length) h += `<div class="name-group">${bot.map(n => `<div class="roster-name" style="font-size:${size}pt">${n}</div>`).join('')}</div>`;
//return h + `</div>`;
return h.html()
},
autoScale(names, base) {
const max = Math.max(...names.map(n => n.length), 0);
return max > 5 ? Math.max(base * (5 / max), base * 0.6) : base;
},
getPosInMm(px) {
return parseFloat((px * 25.4 / 96).toFixed(2));
},
// 拖動後同步所有頁面的位置
makeDraggable() {
const self = this;
$(".tablet-element").draggable({
start(e, ui) {
console.log("gogogogo");
},
stop(e, ui) {
const id = $(this).attr('id');
const el = self.elements.find(x => x.id === id);
el.x = self.getPosInMm(ui.position.left);
el.y = self.getPosInMm(ui.position.top);
//console.log(id,ui.position.left, ui.position.top,ui.offset.left,ui.offset.top);
$(`.tablet-element[id="${id}"]`).css({ left: el.startX + "mm", top: el.startY + "mm" });
}
});
},
select(id) {
this.activeId = id;
const el = this.elements.find(x => x.id === id);
$('#attr-box').removeClass('d-none');
$('#inp-text').val(el.text);
$('#inp-size').val(el.style.fontSize);
$('#xPosition').val(el.x);
$("#yPosition").val(el.y);
$('#2offset').val(el.twoOffset);
$("#3offset").val(el.threeOffset);
$("#4offset").val(el.fourOffset);
$("#breakLen").val(el.breakLen);
$("#width").val(el.width);
$("#height").val(el.height);
$("#textWidth").val(el.textWidth);
$("#textHeight").val(el.textHeight);
//this.render();
},
updateActive(key, val) {
const el = this.elements.find(x => x.id === this.activeId);
if (key === 'fontSize') el.fontSize = parseFloat(val);
else if (key === 'width') el.width = parseFloat(val);
else if (key === 'breakLen') el.breakLen = parseInt(val);
else if (key === '2offset') el.twoOffset = parseFloat(val);
else if (key === '3offset') el.threeOffset = parseFloat(val);
else if (key === '4offset') el.fourOffset = parseFloat(val);
else el[key] = val;
if (el.type == "roster") {
const names = el.text.split('\n').filter(s => s.trim());
$(`.tablet-element[id="${this.activeId}"]`).css({
position: "absolute", left: el.startX + "mm", top: el.startY + "mm", fontSize: el.fontSize + 'pt', fontFamily: el.fontFamily, "z-index": 9999, visibility: el.style.isActive
}).html(this.renderRoster(names.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit), el));
} else if (el.type == "combined-center") {
const parts = el.text.split('\n');
html = `<div class="ancestor-wrapper" >
<span class="main-name" >${parts[0] || ''}</span>
<span class="sub-text">${parts[1] || ''}</span>
</div>`;
$(`.tablet-element[id="${this.activeId}"]`).css({
position: "absolute", left: el.startX + "mm", top: el.startY + "mm", fontSize: el.fontSize + 'pt', fontFamily: el.fontFamily, "z-index": 9999, visibility: el.style.isActive
}).html(html);
} else if (this.activeId === "title1") {
let slice = el.text.split('\n').filter(s => s.trim());
//html = "<div class='name-list'>";
//console.log(slice);
//slice.forEach(x => {
// html = html + "<span>" + x + "</span>"
//});
//html = html + "</div>";
html = this.renderNameList(slice, el);
$(`.tablet-element[id="${this.activeId}"]`).css({
position: "absolute", left: el.x + "mm", top: el.y + "mm", fontSize: el.style.fontSize + 'pt', fontFamily: el.style.fontFamily, "z-index": 9999, visibility: el.style.visibility
}).html(html);
} else {
$(`.tablet-element[id="${this.activeId}"]`).css({
position: "absolute", left: el.x + "mm", top: el.y + "mm", fontSize: el.style.fontSize + 'pt', fontFamily: el.style.fontFamily, "z-index": 9999, visibility: el.style.visibility
}).text(el.text);
}
//this.render();
},
displayItem(id) {
let el = this.elements.find(x => x.id === id);
el.style.visibility = el.style.visibility === "hidden" ? "" : "hidden";
$("#" + id).css({
position: "absolute", left: el.x + "mm", top: el.y + "mm", fontSize: el.style.fontSize + 'pt', fontFamily: el.style.fontFamily, "z-index": 9999, visibility: el.style.visibility
})
},
addMulti(prefix, count) {
for (let i = 1; i <= count; i++) {
const id = `${prefix}-${Date.now()}-${i}`;
this.elements.push({
id, type: 'title', text: `正名${i}`, x: 100 - (i * 30), y: 80,
style: { fontSize: 24, fontFamily: 'Kaiti', isVertical: true }
});
}
//this.render();
},
addCombined() {
this.stopPropagation();
this.elements.push({
id: 'combined-' + Date.now(), type: 'combined-center', text: '林張\n氏歷代祖先', x: 130, y: 80,
style: { fontSize: 24, fontFamily: 'Kaiti', isVertical: true }
});
//this.render();
},
addElement() {
//console.log("QQQ");
},
wrapImageInSvg(imageBase64) {
// 建立一個簡單的 SVG 字串,將圖片鋪滿
// preserveAspectRatio="none" 確保圖片會拉伸填滿 SVG 容器
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" preserveAspectRatio="none">
<image href="${imageBase64}" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" />
</svg>
`.trim();
// 轉成 Data URI 格式
// 注意:SVG 內容若包含特殊字元需編碼,但 base64 圖片通常是安全的
// 為了保險,我們使用 encodeURIComponent 對 svgString 編碼
return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
},
clickBackend() {
$("#backendInp").click();
},
bindBackend() {
$("#backendInp").on('change', function (e) {
const input = e.target;
if (input.files && input.files[0]) {
const file = input.files[0];
// 1. 先讀取檔案轉成 Base64
const reader = new FileReader();
reader.onload = (e) => {
const base64Image = e.target.result; // 這會是 data:image/png;base64,xxxx...
// 2. 將 Base64 PNG 包裝成 SVG
//const svgDataUri = this.wrapImageInSvg(base64Image);
// 3. 設定到底圖
//this.service.setBackground(svgDataUri);
$(".tablet-paper").css({ 'background-image': 'url(' + base64Image + ')', 'background-size': '100% 100%' })
};
reader.readAsDataURL(file);
}
})
},
async saveStyle() {
let detail = [
];
this.elements.forEach((el) => {
detail.push({
elementID: el.id, startX: el.x.toString(), startY: el.y.toString(), fontSize: el.style.fontSize, fontFamily: el.style.fontFamily,
breakLen: el.breakLen, twoOffset: el.twoOffset, threeOffset: el.threeOffset, fourOffset: el.fourOffset,
isActive: el.style.visibility, width: el.width, height: el.height, textWidth: el.textWidth, textHeight: el.textHeight
});
});
let sID = this.styleID
if (sID == "20260310100234") {
this.styleID = ""
}
let master = {
styleID: this.styleID, styleName: $("#styleName").val(), paperSize: $("#paperSize").val(),
backendImg: $("#backendInp").val(), printSize: $("#printSize").val(), printMode: $("#printMode").val(),
orientation: $("#paperOrientation").val(), printPageCount: $("#perpage").val(),
detail: detail
}
console.log(master);
let path = "SavDegignerData";
if (this.styleID != null && this.styleID != "") {
path = "UpdateDegignerData"
}
await axios
.post(HTTP_HOST + `api/tablet/${path}`, master)
.then(response => {
console.log(response);
});
}
};
$(() => Designer.init());
</script>
</asp:Content>