Files
17168ERP/web/admin/print/tablet_edit/editorNewOne.html
T
2026-09-09 16:51:48 +08:00

1512 lines
86 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="../../../js/bootstrap5/bootstrap.min.css" rel="stylesheet" />
<link href="../../../js/fontawesome6/css/all.min.css" rel="stylesheet" />
<link href="../../item/jquery-ui/jquery-ui.min.css" rel="stylesheet" />
<link href="../../../js/mdi-font/css/materialdesignicons.min.css" rel="stylesheet" />
<script src="../../../js/axios.min.js"></script>
<script src="../../../js/bootstrap5/js/bootstrap.bundle.min.js"></script>
<script src="../../../js/jquery-4.0.0.min.js"></script>
<script src="../../item/jquery-ui/jquery-ui.min.js"></script>
<style>
.div_border {
border: 1px solid black;
}
.work_space {
min-height: 40vh;
min-width: 70vw;
max-width: 90vw;
max-height: 50vh;
}
body {
width: 90vw;
height: 90vh
}
li {
font-size: 16px;
list-style: none;
margin-top: 1rem;
}
</style>
</head>
<body>
<div class="d-flex full-home no-print" style="height: 100vh;width:100%;">
<div id="designArea" class="designArea" style="width: 100%; height: auto">
<div class="row mt-2">
<div class="col-3">
版型設定:
<select id="styleSel" onchange="designer.changeStyle()" class="form-select">
<option value="">請選擇版型</option>
</select>
</div>
<div class="col-3">
引用範本:
<select id="templateSel" onchange="designer.changeTemplate()" class="form-select">
<option value="">請選擇範本</option>
</select>
</div>
<div class="col-2">
<button class="btn btn-primary" onclick="designer.save(event)">儲存牌位</button>
<button class="btn btn-primary" onclick="designer.saveTemplate(event)">存成範本</button>
</div>
<div class="col-4">
<div style="display: flex; align-items: center; padding: 8px; background-color: #f5f5f5; border-radius: 4px; margin-bottom: 12px; font-family: sans-serif;">
<span style="margin-right: 8px; font-size: 14px; cursor: pointer;" onclick="designer.minusZoomScalee()">➖</span>
<input type="range" min="0.0" max="1.0" step="0.05" style="flex-grow: 1; cursor: pointer;" id="scaleSlider">
<span style="margin-left: 8px; font-size: 14px; cursor: pointer;" onclick="designer.plusZoomScalee()">➕</span>
<span id="scaleSize" style="margin-left: 12px; min-width: 45px; font-weight: bold; font-size: 14px; text-align: right;"></span>
</div>
</div>
</div>
<div class="row work_space">
<div class="col-7 div_border printArea" style="height:80vh;overflow-y:auto;"></div>
<div class="col-5 div_border" style="font-size:12px;">
<div class="row" style="height: 50%; overflow-y: auto;">
<div style="display:inline-block;width:100%;height:30px;background-color:darkgray;">
<div class="row">
<div class="col-12"><h3>已選</h3></div>
<div class="col-10">
<div class="form-floating mb-3">
<input class="form-control" type="text" id="custom_txt" placeholder="請輸入名稱" />
<label for="custom_txt">請輸入名稱</label>
</div>
</div>
<div class="col-2">
<button type="button" class="custom_add" onclick="designer.addNewItem()" style="width:20px;height:20px;font-size:25px">+</button>
</div>
</div>
<ul class="selectedItem" style="margin-left:1rem;margin-right:1rem;"></ul>
</div>
</div>
<div class="row" style="height:50%;overflow-y:auto;">
<div style="display:inline-block;width:100%;height:30px;background-color:darkgray;">
<div class="row">
<div class="col-12"><h3>可選</h3></div>
<div class="col-10">
<div class="form-floating mb-3">
<input class="form-control" type="text" id="filter_txt" onkeyup="designer.filterToSel()" placeholder="篩選條件" />
<label for="filter_txt">篩選條件</label>
</div>
</div>
<div class="col-2">
<button type="button" data-bs-toggle="modal" data-bs-target="#familyModal" title="新增親友">
<i class="mdi mdi-account-multiple-plus" style="font-size:30px;"></i>
</button>
</div>
</div>
<ul class="canselectItem" style="margin-left: 1rem;margin-right:1rem;"></ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade hide" id="familyModal" tabindex="-1" aria-labelledby="familyModalLabel" aria-hidden="true" style="z-index:10000 !important;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">新增親友</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12">
<div class="form-floating mb-3">
<input class="form-control" type="text" id="family_txt" placeholder="姓名" />
<label for="family_txt">姓名</label>
</div>
</div>
<div class="col-12">
<div class="form-floating mb-3">
<select class="form-select" id="deceased_sel" placeholder="存歿">
<option value="False">存</option>
<option value="True">歿</option>
</select>
<label for="deceased_sel">存歿</label>
</div>
</div>
<div class="col-12">
<div class="form-floating mb-3">
<input class="form-control" type="text" id="title_txt" placeholder="稱謂" />
<label for="title_txt">稱謂</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">關閉</button>
<button type="button" class="btn btn-primary" onclick="designer.saveFamily()">存檔</button>
</div>
</div>
</div>
</div>
<div class="position-fixed top-50 start-50 p-3" style="z-index: 10001">
<div id="liveToast" class="toast hide " role="alert" aria-live="assertive" data-bs-delay="3000" aria-atomic="true" style="color:white;background-color:dimgray;opacity:1;font-size:24px;">
<div class="toast-header">
<strong class="me-auto">提示訊息</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body" id="toast_body"></div>
</div>
</div>
<div class="modal fade hide" id="templateModal" tabindex="-1" aria-labelledby="templateModalLabel" aria-hidden="true" style="z-index:10000 !important;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">範本名稱</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12">
<div class="form-floating mb-3">
<input class="form-control" type="text" id="templateName" placeholder="名稱" />
<label for="family_txt">名稱</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">關閉</button>
<button type="button" class="btn btn-primary" onclick="designer.saveTemplateReal()">存檔</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
const designer = {
zoomScale: 0.5,
selected: { style: "" },
tabletTempNameList: [],
selectedTemplate: {},
actitem: "",
pageSize: "",
defaultStyle: "",
familyMembers: [],
tabletItem: {},
family_deceased_Y_selected: [],
family_deceased_N_selected: [],
family_left_title: [],
family_right_title: [],
family_address: "",
allStyle: [],
allStyleDetails: [],
allSize: [],
tabletElement: [],
http_host: "",
printData: [],
selectedElement: {},
selectStyle: {},
properTitle: [],
leftProperTitle: [],
rightProperTitle: [],
address: "",
alive: [],
tabletType: "A",
follower_id: "",
toast: null,
modal: null,
templateModal:null,
bg: [
{ name: "超薦1", path: "../../../admin/print/html/tablet-1_new.svg" },
{ name: "超薦2", path: "../../../admin/print/html/tablet-1B_new.svg" },
{ name: "消災1", path: "../../../admin/print/html/tablet-2.svg" },
{ name: "消災2", path: "../../../admin/print/html/tablet-2B.svg" },
{ name: "消災3", path: "../../../admin/print/html/tablet-2C.svg" },
{ name: "消災4", path: "../../../admin/print/html/tablet-2D.svg" }
],
async init() {
this.toast = new bootstrap.Toast($('#liveToast'));
this.modal = new bootstrap.Modal($('#familyModal'));
this.templateModal = new bootstrap.Modal($('#templateModal'));
$("#scaleSize").html((this.zoomScale * 100) + "%");
$("#scaleSlider").val(this.zoomScale).on("input change", function () {
designer.zoomScale = parseFloat($(this).val());
$("#scaleSize").html(Math.floor(designer.zoomScale * 100) + "%");
$(".tablet-paper").css("transform", `scale(${designer.zoomScale})`);
});
},
async getTemplateNameList() {
$("#templateSel").empty().append('<option value="">請選擇範本</option>');
this.tabletTempNameList.length = 0;
if (!this.follower_id) return;
await axios.post('/api/follower/GetTabletTemplateList', { num: this.follower_id }).then(res => {
if (res.status == 200 && res.data.list) {
this.tabletTempNameList = res.data.list;
this.tabletTempNameList.forEach(x => {
$('#templateSel').append(`<option value="${x.num}">${x.name}</option>`);
});
}
});
},
async changeTemplate() {
let template_num = $("#templateSel").val();
if (!template_num) return;
this.selectedTemplate = this.tabletTempNameList.find(x => x.num == template_num);
if (!this.selectedTemplate) return;
if (this.selectedTemplate.tablet) {
let tablet = JSON.parse(this.selectedTemplate.tablet);
this.properTitle.length = 0; this.leftProperTitle.length = 0; this.rightProperTitle.length = 0; this.alive.length = 0;
this.family_deceased_Y_selected.length = 0; this.family_deceased_N_selected.length = 0;
this.family_left_title.length = 0; this.family_right_title.length = 0; this.family_address = "";
let mid_items = tablet.mid_items || [];
let left_items = tablet.left_items || [];
let mid_left = tablet.mid_left || [];
let mid_right = tablet.mid_right || [];
let address = tablet.addr_items || "";
Object.assign(this.family_deceased_N_selected, mid_items);
Object.assign(this.family_deceased_Y_selected, left_items);
Object.assign(this.family_left_title, mid_left);
Object.assign(this.family_right_title, mid_right);
this.family_address = address;
mid_items.forEach(y => this.properTitle.push(y.fam_name));
left_items.forEach(y => this.alive.push(y.fam_name));
mid_left.forEach(y => this.leftProperTitle.push(y.fam_name));
mid_right.forEach(y => this.rightProperTitle.push(y.fam_name));
this.address = address;
}
if (this.selectedTemplate.style_id) {
this.selectStyle.style = this.selectedTemplate.style_id;
this.selected.style = this.selectedTemplate.style_id;
$("#styleSel").val(this.selectedTemplate.style_id);
}
this.render();
},
async minusZoomScalee() {
this.zoomScale = Math.max(0, this.zoomScale - 0.05);
$("#scaleSize").html(Math.floor(this.zoomScale * 100) + "%");
$("#scaleSlider").val(this.zoomScale);
$(".tablet-paper").css("transform", `scale(${this.zoomScale})`);
},
async plusZoomScalee() {
this.zoomScale = Math.min(1.0, this.zoomScale + 0.05);
$("#scaleSize").html(Math.floor(this.zoomScale * 100) + "%");
$("#scaleSlider").val(this.zoomScale);
$(".tablet-paper").css("transform", `scale(${this.zoomScale})`);
},
async getActItem() {
await axios.post('/api/tablet/GetActItem', { itemNum: this.actitem }).then(response => {
if (response.data.result == "Y" && response.data.data) {
this.pageSize = response.data.data.pageSize;
this.defaultStyle = response.data.data.defaultStyle;
this.selected.style = response.data.data.defaultStyle;
this.selectStyle.style = response.data.data.defaultStyle;
$("#styleSel").val(this.defaultStyle);
}
});
},
async getAllStyleDetails() {
this.allStyleDetails.length = 0;
await axios.post('/api/tablet/GetStyleDetailData', {}).then(response => {
if (response.status == "200" && response.data.result == "Y") {
this.allStyleDetails = response.data.data;
}
});
},
async getPaperSize() {
this.allSize.length = 0;
await axios.post('/api/tablet/GetPaperSize', {}).then(response => {
if (response.status == "200") {
this.allSize = response.data.data;
}
});
},
async getTabletElement() {
this.tabletElement.length = 0;
await axios.post('/api/tablet/GetTabletElement', {}).then(response => {
if (response.status == "200") {
this.tabletElement = response.data.data;
}
});
},
async getTabletStyles() {
this.allStyle.length = 0;
await axios.post('/api/tablet/GetStyleData', {}).then(response => {
if (response.status == "200" && response.data.data) {
response.data.data.forEach(x => {
if (x.styleID !== "000001") {
this.allStyle.push(x);
$("#styleSel").append(`<option value="${x.styleID}">${x.name}</option>`);
}
});
}
});
},
async getData() {
this.printData.length = 0;
this.properTitle.length = 0; this.leftProperTitle.length = 0; this.rightProperTitle.length = 0; this.alive.length = 0;
let param = [{ order_no: this.tabletItem.order_no, num: this.tabletItem.num }];
await axios.post('/api/orderdetail/GetDetailToPrint', { param: param }).then(response => {
if (response.status == 200) {
this.printData = response.data;
this.printData.forEach(x => {
if (x.style != null && x.style != undefined) {
this.selectStyle.style = x.style;
this.selected.style = x.style;
this.defaultStyle = x.style;
$("#styleSel").val(x.style);
}
if (x.f_num_tablet != "") {
let tablet = JSON.parse(x.f_num_tablet);
let mid_items = tablet?.mid_items;
let left_items = tablet?.left_items;
let mid_left = tablet?.mid_left;
let mid_right = tablet?.mid_right;
let address = tablet?.addr_items === undefined ? "" : tablet?.addr_items;
if (address === "" && x.address != "") {
address = x.address;
}
this.family_deceased_Y_selected.length = 0;
this.family_deceased_N_selected.length = 0;
this.family_left_title.length = 0;
this.family_right_title.length = 0;
Object.assign(this.family_deceased_N_selected, mid_items);
Object.assign(this.family_deceased_Y_selected, left_items);
Object.assign(this.family_left_title, mid_left);
Object.assign(this.family_right_title, mid_right);
this.family_address = address;
if (mid_items) mid_items.forEach(y => this.properTitle.push(y.fam_name));
if (mid_left) mid_left.forEach(y => this.leftProperTitle.push(y.fam_name));
if (mid_right) mid_right.forEach(y => this.rightProperTitle.push(y.fam_name));
if (address) this.address = address;
if (left_items) left_items.forEach(y => this.alive.push(y.fam_name));
}
});
this.render();
}
});
},
async render() {
$(".printArea").empty();
$(".selectedItem").empty();
$(".canselectItem").empty();
this.tabletType = "A";
let canvas = $(`<div class="canvas-area flex-grow-1 overflow-auto d-flex flex-row align-items-center position-relative"></div>`);
let tabletpaper;
let mid = [...this.properTitle];
let left = [...this.alive];
let ltitle = [...this.leftProperTitle];
let rtitle = [...this.rightProperTitle];
let myAddress = this.address != "" ? [this.address] : [];
let x = this.selectStyle;
let style = this.allStyle.find(y => y.styleID == (x.style ? x.style : $("#styleSel").val()));
if (!x.style) {
this.selectStyle.style = style.styleID;
this.selected.style = style.styleID;
}
let size = this.allSize.find(y => y.paperID == style.paperSize);
let pageSize = this.allSize.find(y => y.paperID == style.paperSize);
let printSize = this.allSize.find(y => y.paperID == style.printSize);
tabletpaper = $(`<div class="tablet-paper" style="transform-origin:top left;transform:scale(${this.zoomScale})"></div>`);
let img = this.bg.find(y => y.name == style.backendImg);
let details = this.allStyleDetails.filter(y => y.styleID == style.styleID);
let ancestorFontSize = 16;
details.forEach(d => {
if (d.isActive != "hidden") {
let element = this.tabletElement.find(y => y.elementID == d.elementID);
let newFontSize = this.scaleFontSize(mid, d.fontSize, d.width);
let html = "";
if (d.elementID === 'title1') {
if (mid.length == 0) mid.push("請選擇");
let th = Number(d.textHeight) || 0;
let span = Number(d.textSpan) || 0;
let namesPerCol = Math.max(1, Math.floor((Number(d.height) + span) / (th + span)));
let cols = Math.max(1, Math.ceil(mid.length / namesPerCol));
let colWidth = Math.floor(Number(d.width) / cols);
html = this.renderNameList(mid, d, newFontSize, style, colWidth);
} else if (d.elementID === "alive") {
this.tabletType = "B";
if (left.length == 0) left.push("請選擇");
newFontSize = d.fontSize;
html = this.renderLiveList(left, d);
} else if (d.elementID === "titletriangle") {
if (mid.length == 0) mid.push("請選擇");
html = this.renderRoster(mid, d);
} else if (d.elementID === "combined" || d.elementID === "tricombined") {
ancestorFontSize = d.fontSize;
if (mid.length == 0) mid.push("請選擇");
html = this.renderCombined(mid, d);
} else if (d.elementID === "lefttitle") {
if (ltitle.length == 0) ltitle.push("請選擇");
html = this.renderNormalWithSpace(ltitle, d);
} else if (d.elementID === "righttitle") {
if (rtitle.length == 0) rtitle.push("請選擇");
html = this.renderNormalWithSpace(rtitle, d);
} else if (d.elementID == "address") {
if (myAddress.length == 0) myAddress.push("請選擇");
html = this.renderNormal(myAddress.join(""), d);
}
if (newFontSize < 10) newFontSize = 10;
let content = $(`<div class="tablet-element vertical-text ">${element.sampleContent}</div>`).css({
position: "absolute", left: d.startX + "mm", top: d.startY + "mm", fontSize: newFontSize + 'px',
fontFamily: d.fontFamily, "font-weight": "bold", "z-index": 9999, visibility: d.isActive, "align-items": "center"
}).html(html);
tabletpaper.append(content);
}
});
let pageStyle = `<style id="pageStyle">
* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; color-adjust: exact !important; }
.vertical-text { writing-mode: vertical-rl !important; -webkit-writing-mode: vertical-rl !important; text-orientation: mixed !important; }
body { overflow: visible !important; }
@media print {
:host { width:auto !important; height:auto !important; }
@page { size:${printSize.width}mm ${printSize.height}mm !important; margin:0; }
html, body, form, .full-home, .printArea, .canvas-area { height: auto !important; width: auto !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; display: block !important; }
.no-print, #customMenu { display: none !important; }
.canvas-area { display:flex !important; flex-direction: row !important; justify-content:center !important; align-items:center !important; }
.tablet-paper { position: relative !important; display: block !important; break-inline:avoid; page-break-inline:avoid; break-after: page !important; page-break-after: always !important; margin: 0 !important; border: none !important; background-repeat: no-repeat; background-size: 100% 100%; }
.tablet-element { position: absolute !important; white-space: normal; color: black !important; }
}
.roster-container { width: 100%; height: 100%; writing-mode: vertical-rl; display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 20px; }
.roster-row { display: flex; flex-direction: row-reverse; justify-content: center; gap: 8px; margin-left: 10px; }
.name-group { display: flex; flex-direction: column; justify-content: center; align-items: center; }
.roster-name { text-orientation: upright; white-space: normal; line-height: 1.2; font-family: DFKai-sb; width: fit-content; }
.ancestor-wrapper { writing-mode: horizontal-tb; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; width: fit-content; }
.main-name { line-height: 1.2; }
.sub-text { font-size: ${ancestorFontSize}px; line-height: 1.2; margin-top: 4px; white-space: normal; writing-mode:vertical-rl; }
.canvas-area { width: ${pageSize.width}mm; height: ${pageSize.height}mm; break-after: page; page-break-after: always; display:flex !important; justify-content:center; }
.tablet-paper { background-color: white; display: inline-block; width: ${size.width}mm !important; height: ${size.height}mm !important; position: relative; margin-bottom: 0; background-image: url(${img.path}); background-size: 100% 100%; break-inline:avoid; page-break-inline:avoid; }
.tablet-element { cursor: move; padding: 4px; color: black !important; border: 1px solid transparent; transition: writing-mode 0.3s, transform 0.2s; }
.tablet-element:hover { border-color: rgba(13, 110, 253, 0.3); }
.tablet-paper:last-child { page-break-after: auto; }
.tablet-element span { z-index: 999; }
.selectedItem .canselectItem { font-size: 18px; }
.selectedItem, .rounded-pill .canselectItem, .rounded-pill { font-size: 12px; }
</style>`;
$("#pageStyle").remove();
$("head").append(pageStyle);
$(canvas).append(tabletpaper);
$(".printArea").append(canvas);
$(".tablet-element").on("click", (e) => {
e.preventDefault();
this.selectedElement = $(e.currentTarget);
let element = $(e.currentTarget).children().first();
this.renderItem(element);
});
},
async renderItem(element) {
$(".selectedItem").empty();
$(".canselectItem").empty();
if (element.hasClass("liveList")) {
this.alive.forEach(x => {
let f = this.familyMembers.find(z => z.fam_name == x);
$(".selectedItem").append(`<li><span style="opacity:0.5">卍</span>
<span class="selected">${x}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class="itemRemove" style="float:right;margin-left:2rem;cursor:pointer;">&minus;</span>
<span class="itemUp" style="float:right;margin-left:2rem;cursor:pointer;">&#8593;</span>
<span class="itemDown" style="float:right;margin-left:2rem;cursor:pointer;">&#8595;</span></li>`);
});
let items = this.familyMembers.filter(x => x.deceased == false).map(x => x.fam_name);
let finalItem = items.filter(y => !this.alive.includes(y));
finalItem.forEach(yy => {
let f = this.familyMembers.find(z => z.fam_name == yy);
$(".canselectItem").append(`<li><span style="opacity:0.5">卍</span> <span class="unselect">${yy}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class='itemAppend' style="float:right;cursor:pointer;">&#43;</span></li>`);
});
} else if (element.hasClass("address")) {
$(".selectedItem").append(`<li><span style="opacity:0.5">卍</span>
<span class="selected">${this.address == undefined ? "" : this.address}</span>
<span class="itemRemove" style="float:right;margin-left:2rem;cursor:pointer;">&minus;</span>`);
} else if (element.hasClass("lefttitle")) {
this.leftProperTitle.forEach(x => {
let f = this.familyMembers.find(z => z.fam_name == x);
$(".selectedItem").append(`<li><span style="opacity:0.5">卍</span><span class="selected">${x}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class="itemRemove" style="float:right;margin-left:2rem;cursor:pointer;">&minus;</span>`);
});
let items = this.familyMembers.filter(x => (this.tabletType == "A" && x.deceased == false) || (this.tabletType == "B" && x.deceased == true)).map(x => x.fam_name);
let finalItem = items.filter(y => !this.leftProperTitle.includes(y));
finalItem.forEach(yy => {
let f = this.familyMembers.find(z => z.fam_name == yy);
$(".canselectItem").append(`<li><span style="opacity:0.5">卍</span> <span class="unselect">${yy}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class='itemAppend' style="float:right;cursor:pointer;">&#43;</span></li>`);
});
} else if (element.hasClass("righttitle")) {
this.rightProperTitle.forEach(x => {
let f = this.familyMembers.find(z => z.fam_name == x);
$(".selectedItem").append(`<li><span style="opacity:0.5">卍</span><span class="selected">${x}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class="itemRemove" style="float:right;margin-left:2rem;cursor:pointer;">&minus;</span>`);
});
let items = this.familyMembers.filter(x => (this.tabletType == "A" && x.deceased == false) || (this.tabletType == "B" && x.deceased == true)).map(x => x.fam_name);
let finalItem = items.filter(y => !this.rightProperTitle.includes(y));
finalItem.forEach(yy => {
let f = this.familyMembers.find(z => z.fam_name == yy);
$(".canselectItem").append(`<li><span style="opacity:0.5">卍</span> <span class="unselect">${yy}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class='itemAppend' style="float:right;cursor:pointer;">&#43;</span></li>`);
});
} else {
let hasControls = !element.hasClass("ancestor-wrapper");
this.properTitle.forEach(y => {
let f = this.familyMembers.find(z => z.fam_name == y);
let controlHtml = hasControls ? `<span class="itemUp" style="float:right;margin-left:2rem;cursor:pointer;">&#8593;</span><span class="itemDown" style="float:right;margin-left:2rem;cursor:pointer;">&#8595;</span>` : '';
$(".selectedItem").append(`<li><span style="opacity:0.5">卍</span><span class="selected">${y}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class="itemRemove" style="float:right;margin-left:2rem;cursor:pointer;">&minus;</span>${controlHtml}</li>`);
});
if (hasControls) {
let items = this.familyMembers.filter(x => (this.tabletType == "A" && x.deceased == false) || (this.tabletType == "B" && x.deceased == true)).map(x => x.fam_name);
let finalItem = items.filter(y => !this.properTitle.includes(y));
finalItem.forEach(yy => {
let f = this.familyMembers.find(z => z.fam_name == yy);
$(".canselectItem").append(`<li><span style="opacity:0.5">卍</span> <span class="unselect">${yy}</span>
<span><i class="rounded-pill rounded-3 ${(f && f.fam_title) ? 'p-1' : ''}" style="background-color:lightgray">${(f && f.fam_title) ? f.fam_title : ""}</i></span>
<span class='itemAppend' style="float:right;cursor:pointer;">&#43;</span></li>`);
});
}
}
this.bindEvent();
},
async addNewItem() {
let customVal = $("#custom_txt").val();
if (customVal == "") {
alert("名稱不可空白");
return false;
}
let target = $(this.selectedElement).children().first();
let error = "";
if (target.hasClass("liveList")) {
if (this.alive.includes(customVal)) error = "名稱已存在";
if (error) return alert(error), false;
this.alive.push(customVal);
this.family_deceased_Y_selected.push({ IsShuWen: false, deceased: false, fam_gender: "", fam_name: customVal, fam_file: "", nospace: true, num: 0, option_break: false });
} else if (target.hasClass("nameList") || target.hasClass("rosterList")) {
if (this.properTitle.includes(customVal)) error = "名稱已存在";
if (error) return alert(error), false;
this.properTitle.push(customVal);
this.family_deceased_N_selected.push({ IsShuWen: false, deceased: false, fam_gender: "", fam_name: customVal, fam_file: "", nospace: true, num: 0, option_break: false });
} else if (target.hasClass("ancestor-wrapper")) {
if (this.properTitle.includes(customVal)) error = "名稱已存在";
if (error) return alert(error), false;
if (this.properTitle.length >= 2) {
alert("多姓氏合併必須第一個名稱為姓,第二個名稱為氏歷代祖先之類,且最多兩個名稱");
} else {
this.properTitle.push(customVal);
this.family_deceased_N_selected.push({ IsShuWen: false, deceased: false, fam_gender: "", fam_name: customVal, fam_file: "", nospace: true, num: 0, option_break: false });
}
} else if (target.hasClass("lefttitle")) {
if (this.leftProperTitle.includes(customVal)) error = "名稱已存在";
if (error) return alert(error), false;
this.leftProperTitle.length = 0;
this.family_left_title.length = 0;
this.leftProperTitle.push(customVal);
this.family_left_title.push({ IsShuWen: false, deceased: false, fam_gender: "", fam_name: customVal, fam_file: "", nospace: true, num: -1, option_break: false });
} else if (target.hasClass("righttitle")) {
if (this.rightProperTitle.includes(customVal)) error = "名稱已存在";
if (error) return alert(error), false;
this.rightProperTitle.length = 0;
this.family_right_title.length = 0;
this.rightProperTitle.push(customVal);
this.family_right_title.push({ IsShuWen: false, deceased: false, fam_gender: "", fam_name: customVal, fam_file: "", nospace: true, num: 0, option_break: false });
} else if (target.hasClass("address")) {
this.address = customVal;
this.family_address = this.address;
}
$("#custom_txt").val("");
this.renderItem($(this.selectedElement).children().first());
this.rerenderPart();
},
async bindEvent() {
let self = this;
$(".itemAppend, .itemRemove, .itemUp, .itemDown").off("click");
$(".itemAppend").on("click", function (e) {
let curr = $(e.currentTarget.parentElement).find(".unselect").first();
let target = self.familyMembers.find(x => x.fam_name == curr.text());
if (target) {
let elClass = $(self.selectedElement).children().first();
let obj = { IsShuWen: false, deceased: false, fam_gender: "", fam_name: target.fam_name, fam_file: "", nospace: true, num: target.num, option_break: false };
if (elClass.hasClass("liveList")) {
if (self.tabletItem.has_yang_limit && self.tabletItem.yang_limit_count > 0 && self.alive.length + 1 > self.tabletItem.yang_limit_count) {
$("#toast_body").html(`陽上人數不得超過${self.tabletItem.yang_limit_count}人`); self.toast.show(); return;
}
self.alive.push(target.fam_name);
self.family_deceased_Y_selected.push(obj);
} else if (elClass.hasClass("nameList") || elClass.hasClass("rosterList")) {
if (self.tabletItem.has_chao_limit && self.tabletItem.chao_limit_count > 0 && self.properTitle.length + 1 > self.tabletItem.chao_limit_count) {
$("#toast_body").html(`祈福或超渡人數不得超過${self.tabletItem.chao_limit_count}人`); self.toast.show(); return;
}
self.properTitle.push(target.fam_name);
self.family_deceased_N_selected.push(obj);
} else if (elClass.hasClass("lefttitle")) {
self.leftProperTitle.push(target.fam_name);
self.family_left_title.push(obj);
} else if (elClass.hasClass("righttitle")) {
self.rightProperTitle.push(target.fam_name);
self.family_right_title.push(obj);
}
}
self.renderItem($(self.selectedElement).children().first());
self.rerenderPart();
});
$(".itemRemove").on("click", function (e) {
let currName = $(e.currentTarget.parentElement).find(".selected").first().text();
let elClass = $(self.selectedElement).children().first();
if (elClass.hasClass("liveList")) {
self.alive = self.alive.filter(x => x !== currName);
self.family_deceased_Y_selected = self.family_deceased_Y_selected.filter(x => x.fam_name != currName);
} else if (elClass.hasClass("nameList") || elClass.hasClass("rosterList") || elClass.hasClass("ancestor-wrapper")) {
self.properTitle = self.properTitle.filter(x => x !== currName);
self.family_deceased_N_selected = self.family_deceased_N_selected.filter(x => x.fam_name != currName);
} else if (elClass.hasClass("address")) {
self.address = currName; self.family_address = currName;
} else if (elClass.hasClass("lefttitle")) {
self.leftProperTitle = self.leftProperTitle.filter(x => x !== currName);
self.family_left_title = self.family_left_title.filter(x => x.fam_name !== currName);
} else if (elClass.hasClass("righttitle")) {
self.rightProperTitle = self.rightProperTitle.filter(x => x !== currName);
self.family_right_title = self.family_right_title.filter(x => x.fam_name !== currName);
}
self.renderItem($(self.selectedElement).children().first());
self.rerenderPart();
});
$(".itemUp").on("click", function (e) {
let currName = $(e.currentTarget.parentElement).find(".selected").first().text();
let elClass = $(self.selectedElement).children().first();
const swap = (arr, i) => { if (i > 0) [arr[i - 1], arr[i]] = [arr[i], arr[i - 1]]; };
if (elClass.hasClass("liveList")) {
let idx = self.alive.indexOf(currName);
swap(self.alive, idx); swap(self.family_deceased_Y_selected, idx);
} else if (elClass.hasClass("nameList") || elClass.hasClass("rosterList") || elClass.hasClass("ancestor-wrapper")) {
let idx = self.properTitle.indexOf(currName);
swap(self.properTitle, idx); swap(self.family_deceased_N_selected, idx);
} else if (elClass.hasClass("lefttitle")) {
let idx = self.leftProperTitle.indexOf(currName);
swap(self.leftProperTitle, idx); swap(self.family_left_title, idx);
} else if (elClass.hasClass("righttitle")) {
let idx = self.rightProperTitle.indexOf(currName);
swap(self.rightProperTitle, idx); swap(self.family_right_title, idx);
}
self.renderItem($(self.selectedElement).children().first());
self.rerenderPart();
});
$(".itemDown").on("click", function (e) {
let currName = $(e.currentTarget.parentElement).find(".selected").first().text();
let elClass = $(self.selectedElement).children().first();
const swap = (arr, i) => { if (i > -1 && i < arr.length - 1) [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; };
if (elClass.hasClass("liveList")) {
let idx = self.alive.indexOf(currName);
swap(self.alive, idx); swap(self.family_deceased_Y_selected, idx);
} else if (elClass.hasClass("nameList") || elClass.hasClass("rosterList") || elClass.hasClass("ancestor-wrapper")) {
let idx = self.properTitle.indexOf(currName);
swap(self.properTitle, idx); swap(self.family_deceased_N_selected, idx);
} else if (elClass.hasClass("lefttitle")) {
let idx = self.leftProperTitle.indexOf(currName);
swap(self.leftProperTitle, idx); swap(self.family_left_title, idx);
} else if (elClass.hasClass("righttitle")) {
let idx = self.rightProperTitle.indexOf(currName);
swap(self.rightProperTitle, idx); swap(self.family_right_title, idx);
}
self.renderItem($(self.selectedElement).children().first());
self.rerenderPart();
});
},
async rerenderPart() {
let x = this.selectStyle;
let style = this.allStyle.find(y => y.styleID == x.style);
let details = this.allStyleDetails.filter(y => y.styleID == x.style);
let elClass = $(this.selectedElement).children().first();
$(this.selectedElement).empty();
if (elClass.hasClass("liveList")) {
let d = details.find(y => y.elementID == "alive");
$(this.selectedElement).append(this.renderLiveList([...this.alive], d));
} else if (elClass.hasClass("lefttitle")) {
let d = details.find(y => y.elementID == "lefttitle");
$(this.selectedElement).append(this.renderNormalWithSpace([...this.leftProperTitle], d));
} else if (elClass.hasClass("righttitle")) {
let d = details.find(y => y.elementID == "righttitle");
$(this.selectedElement).append(this.renderNormalWithSpace([...this.rightProperTitle], d));
} else if (elClass.hasClass("address")) {
let d = details.find(y => y.elementID == "address");
$(this.selectedElement).append(this.renderNormal([this.address], d));
} else {
let mid = [...this.properTitle];
if (elClass.hasClass("nameList")) {
let d = details.find(y => y.elementID == "title1");
let newfontSize = this.scaleFontSize(mid, d.fontSize, d.textWidth);
this.selectedElement.css("font-size", newfontSize + "px");
$(this.selectedElement).append(this.renderNameList(mid, d, style.fontSize, style, 0));
} else if (elClass.hasClass("rosterList")) {
let d = details.find(y => y.elementID == "titletriangle");
let newfontSize = Math.max(10, this.scaleFontSize(mid, d.fontSize, d.textWidth));
this.selectedElement.css("font-size", newfontSize + "px");
$(this.selectedElement).append(this.renderRoster(mid, d));
} else if (elClass.hasClass("ancestor-wrapper")) {
let d = details.find(y => y.elementID == "combined");
let newfontSize = Math.max(10, this.scaleFontSize(mid, d.fontSize, d.textWidth));
this.selectedElement.css("font-size", newfontSize + "px");
$(this.selectedElement).append(this.renderCombined(mid, d));
}
}
},
countFontSize(name, el, fontSize, rows) {
if (Number(name.length) * 2 * Number(fontSize) > el.height) {
fontSize = Number(fontSize) - 2;
let rowWidth = Math.floor(Number(el.width) / (rows + 1));
fontSize = Math.floor(rowWidth / 2);
let finalSize = this.countFontSize(name, el, fontSize, rows + 1);
return finalSize === 0 ? fontSize : finalSize;
}
return 0;
},
calcIndividualFontSize(name, el, maxW) {
let s = Number(el.style ? el.style.fontSize : (el.fontSize ? el.fontSize : 18));
let maxH = Number(el.textHeight);
let l = Number(name.length);
while (s >= 12) {
let s_px = s * 1.333;
let charsPerCol = Math.max(1, Math.floor(maxH / s_px));
let cols = Math.ceil(l / charsPerCol);
let neededWidth = (cols === 1) ? s_px : cols * (s_px * 1.1);
if (neededWidth <= maxW && (cols > 1 || l * s_px <= maxH)) {
return s;
}
s -= 1;
}
return 12;
},
changeStyle() {
this.selected.style = $("#styleSel").val();
this.selectStyle.style = $("#styleSel").val();
this.render();
},
renderNormalWithSpace(mid, d) {
let txt = $(`<div class='${d.elementID}'></div>`).css({
"z-index": 9999, visibility: d.visibility, width: d.width, height: d.height, padding: "1px",
"writing-mode": "vertical-rl", display: "flex", "flex-direction": "row", "flex-wrap": "wrap", margin: "auto",
"column-gap": `${d.textSpan}px`, "align-items": "flex-start", "justify-content": "start"
});
mid.forEach(x => {
$(txt).append($(`<div>${x}</div>`).css({ "margin-bottom": "10px", "margin-left": "5px", "text-justify": "inter-character", "white-space": "pre-line" }));
});
return txt;
},
renderNormal(mid, d) {
return $(`<div class='${d.elementID}'>${mid}</div>`).css({ "z-index": 9999, visibility: d.visibility, width: d.width, height: d.height });
},
/*renderNameList(names, d, newFontSize, style, colWidth) {
let h = parseFloat(d.height), w = parseFloat(d.width), th = parseFloat(d.textHeight) || h;
let v_gap = parseFloat(d.textSpan) || 0, h_gap = 5;
let shrinkLimit = h < 300 ? 12 : (h < 500 ? 8 : 6);
let long_s = parseFloat(d.fontSize), short_s = parseFloat(d.fontSize);
let testNames = [...names];
if (h<500&&testNames.length == 1 && testNames[0].includes("暨無始劫以來累世冤親債主")) testNames[0] = testNames[0].split("暨")[0] + "暨";
let items = testNames.map(name => ({ name: name, l: name.length, isShort: (testNames.length > 1) && ((name.length * 40) <= (th + (v_gap / 2))), isMulti: false, size: 0 }));
let sizes = [], shrink_turn = 'long';
while (true) {
let cols_width = [], current_col_h = 0, current_col_w = 0, any_wrapped = false;
for (let item of items) {
let s = item.isShort ? short_s : long_s;
if (item.isShort) s = Math.min(s, Math.floor(th / (item.l * 1.333)));
item.size = s;
let size_px = s * 1.333;
if (item.isShort) {
let item_h = Math.max(th, item.l * size_px);
if (current_col_h === 0) { current_col_h = item_h; current_col_w = size_px; }
else if ((current_col_h + v_gap + item_h) <= h) { current_col_h += (v_gap + item_h); current_col_w = Math.max(current_col_w, size_px); }
else { cols_width.push(current_col_w); current_col_h = item_h; current_col_w = size_px; }
} else {
if (current_col_h > 0) { cols_width.push(current_col_w); current_col_h = 0; current_col_w = 0; }
let item_w = size_px;
if ((item.l * size_px) > (h - 10)) {
item.isMulti = true; any_wrapped = true;
item_w = Math.ceil(item.l / Math.max(1, Math.floor((h - 10) / size_px))) * (size_px * 1.1);
} else { item.isMulti = false; }
cols_width.push(item_w);
}
}
if (current_col_h > 0) cols_width.push(current_col_w);
if (cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0) <= w && (!any_wrapped || long_s === shrinkLimit)) {
sizes = items.map(x => ({ name: x.name, size: x.isShort ? Math.min(x.size, Math.floor(th / (x.l * 1.333))) : x.size, isMulti: x.isMulti, isShort: x.isShort }));
break;
}
if (any_wrapped && long_s > shrinkLimit) long_s--;
else {
let hasLong = items.some(x => !x.isShort), hasShort = items.some(x => x.isShort);
if (hasLong && hasShort) {
if (shrink_turn === 'long') { if (long_s > shrinkLimit) { long_s--; shrink_turn = 'short'; } else if (short_s > shrinkLimit) { short_s--; shrink_turn = 'long'; } else break; }
else { if (short_s > shrinkLimit) { short_s--; shrink_turn = 'long'; } else if (long_s > shrinkLimit) { long_s--; shrink_turn = 'short'; } else break; }
} else if (hasLong) { if (long_s > shrinkLimit) long_s--; else break; }
else { if (short_s > shrinkLimit) short_s--; else break; }
}
}
if (sizes.length === 0) sizes = names.map(name => ({ name: name, size: shrinkLimit, isMulti: false, isShort: true }));
let $namelist = $(`<div class='nameList'></div>`).css({ "writing-mode": "vertical-rl", display: "flex", "flex-direction": "row", "flex-wrap": "wrap", "align-content": "center", "align-items": "center", margin: "auto", width: `${w}px`, height: `${h}px`, border: "0px solid transparent", padding: "1px", "column-gap": `${v_gap}px`, "font-family": d.fontFamily });
sizes.forEach(item => {
let alignStyle = item.isMulti || (!item.isMulti && !item.isShort) ? "start" : "justify";
$namelist.append($(`<span style="font-size: ${item.size}pt;">${item.name}</span>`).css({ display: "block", "min-height": item.isShort ? `${th}px` : `${h}px`, "max-height": `${h}px`, width: "auto", "line-height": "1.1", "text-align": alignStyle, "text-align-last": alignStyle, "margin-left": "5px", "text-justify": "inter-character", "word-break": "break-all" }));
});
return $namelist;
},*/
renderNameList(names, d, newFontSize, style, colWidth) {
let h = Number(d.height);
let w = Number(d.width);
let th = Number(d.textHeight) || h;
let v_gap = Number(d.textSpan) || 0;
let h_gap = 5;
let shrinkLimit = 6;
if (h < 500) {
shrinkLimit = 8;
} else if (h < 300) {
shrinkLimit = 12;
}
let long_s = Number(d.fontSize);
let short_s = Number(d.fontSize);
let renderNames = [...names];
if (h < 500 && renderNames.length == 1 && renderNames[0].includes("暨無始劫以來累世冤親債主")) {
renderNames = renderNames[0].split("暨");
renderNames[0] = renderNames[0] + "暨";
}
const init_fontSize = Number(d.fontSize) > 40 ? 40 : Number(d.fontSize);
let items = renderNames.map(name => {
let l = name.length;
let isShort = (l * init_fontSize) <= (th + (v_gap / 2));
return { name: name, l: l, isShort: isShort, isMulti: false, size: 0 };
});
let maxShortLen = Math.max(...items.filter(x => x.isShort).map(x => x.l), 1);
let allShort = items.every(x => x.isShort);
let fitsInHalf = items.every(x => (x.l * short_s * 1.333) <= (h / 2));
let isSpecial1 = allShort && fitsInHalf && items.length === 1;
let isSpecial2 = allShort && fitsInHalf && items.length === 2;
let isSpecial4 = allShort && fitsInHalf && (items.length === 4 || items.length === 6 || items.length === 8);
let isSpecial = isSpecial1 || isSpecial2 || isSpecial4;
let sizes = [];
let shrink_turn = 'long';
while (true) {
let totalWidth = 0;
let current_col_h = 0;
let current_col_w = 0;
let cols_width = [];
let any_wrapped = false;
for (let i = 0; i < items.length; i++) {
let item = items[i];
let s = item.isShort ? short_s : long_s;
item.size = s;
let size_px = s * 1.333;
if (item.isShort) {
let item_h = Math.max(th, maxShortLen * size_px);
if (isSpecial) {
if (isSpecial1 || isSpecial2) item_h = h / 2;
if (isSpecial4) item_h = (h / 2) - (v_gap / 2);
} else {
let natural_h = maxShortLen * size_px;
if (item_h > natural_h * 1.5) {
item_h = natural_h * 1.5;
}
}
if (current_col_h === 0) {
current_col_h = item_h;
current_col_w = size_px;
} else if ((current_col_h + v_gap + item_h) <= h) {
current_col_h += (v_gap + item_h);
if (size_px > current_col_w) current_col_w = size_px;
} else {
cols_width.push(current_col_w);
current_col_h = item_h;
current_col_w = size_px;
}
} else {
if (current_col_h > 0) {
cols_width.push(current_col_w);
current_col_h = 0;
current_col_w = 0;
}
let item_w = size_px;
if ((item.l * size_px) > (h - 10)) {
item.isMulti = true;
any_wrapped = true;
let chars_per_col = Math.floor((h - 10) / size_px);
if (chars_per_col < 1) chars_per_col = 1;
let item_cols = Math.ceil(item.l / chars_per_col);
item_w = item_cols * (size_px * 1.1);
} else {
item.isMulti = false;
}
cols_width.push(item_w);
}
}
if (current_col_h > 0) cols_width.push(current_col_w);
totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0);
if (totalWidth <= w && (!any_wrapped || long_s === shrinkLimit)) {
sizes = items.map(x => {
let final_s = x.size;
if (x.isShort) {
//let max_s_for_th = Math.floor(th / (maxShortLen * 1.333));
//if (final_s > max_s_for_th) {
// final_s = max_s_for_th;
//}
let available_h = th;
if (isSpecial) {
if (isSpecial1 || isSpecial2) available_h = h / 2;
if (isSpecial4) available_h = (h / 2) - v_gap - 2;
}
let max_s_for_th = Math.floor(available_h / (maxShortLen * 1.333));
if (final_s > max_s_for_th) {
final_s = max_s_for_th;
}
}
return { name: x.name, size: final_s, isMulti: x.isMulti, isShort: x.isShort };
});
break;
}
let hasLong = items.some(x => !x.isShort);
let hasShort = items.some(x => x.isShort);
if (hasLong && hasShort) {
if (shrink_turn === 'long') {
if (long_s > shrinkLimit) {
long_s--;
shrink_turn = 'short';
} else if (short_s > shrinkLimit) {
short_s--;
shrink_turn = 'long';
} else {
break;
}
} else {
if (short_s > shrinkLimit) {
short_s--;
shrink_turn = 'long';
} else if (long_s > shrinkLimit) {
long_s--;
shrink_turn = 'short';
} else {
break;
}
}
} else if (hasLong) {
if (long_s > shrinkLimit) {
long_s--;
} else {
break;
}
} else {
if (short_s > shrinkLimit) {
short_s--;
} else {
break;
}
}
}
if (sizes.length === 0) {
sizes = renderNames.map(name => {
return { name: name, size: shrinkLimit, isMulti: false, isShort: true };
});
}
let flexDir = isSpecial2 ? "column" : "row";
let flexJustify = isSpecial ? "center" : "flex-start";
let flexAlign = "center";
//if (isSpecial4) {
// v_gap = Number(h) / 4;
//}
let $namelist = $(`<div class='nameList'></div>`).css({
"writing-mode": "vertical-rl",
display: "flex",
"flex-direction": flexDir,
"flex-wrap": "wrap",
"align-content": "center",
"align-items": flexAlign,
"justify-content": flexJustify,
margin: "auto",
width: `${w}px`,
height: `${h}px`,
border: "0px solid transparent",
padding: "1px",
"column-gap": `${v_gap}px`,
"font-family": d.fontFamily
});
sizes.forEach(item => {
let size_px = item.size * 1.333;
let natural_max_h = maxShortLen * size_px;
let boxH_num = item.isShort ? Math.max(th, natural_max_h) : h;
if (isSpecial) {
if (isSpecial1 || isSpecial2) boxH_num = h / 2;
if (isSpecial4) boxH_num = (h / 2) - (v_gap) - 2;
} else if (item.isShort) {
if (boxH_num > natural_max_h * 1.5) {
boxH_num = natural_max_h * 1.5;
}
}
let minH = `${boxH_num}px`;
let alignStyle = item.isMulti || (!item.isMulti && !item.isShort) ? "start" : "justify";
let spanDisplay = "block";
let spanJustifyContent = "flex-start";
let spanMarginTop = "0px";
if (isSpecial1 || isSpecial2) {
spanMarginTop = `-${h / 4}px`;
}
if (isSpecial1 || isSpecial2 || isSpecial4) {
alignStyle = "justify";
spanJustifyContent = "space-between";
}
let nameLen = item.name.length;
let contentH = nameLen * size_px;
let emptySpace = boxH_num - contentH;
let gapsCount = nameLen > 1 ? (nameLen - 1) : 1;
let avgGap = emptySpace / gapsCount;
if (!isSpecial && alignStyle === "justify" && avgGap > (size_px * 1.5)) {
alignStyle = "justify";
spanDisplay = "block";
spanJustifyContent = "start";
}
let $span = $(`<span style="font-size: ${item.size}pt;">${item.name}</span>`).css({
"display": spanDisplay,
"justify-content": spanJustifyContent,
"align-items": "center",
"min-height": minH,
"max-height": `${h}px`,
"width": "auto",
"line-height": "1.1",
"text-align": alignStyle,
"text-align-last": alignStyle,
"margin-left": "5px",
"margin-top": spanMarginTop,
"text-justify": "inter-character",
"word-break": "break-all"
});
$namelist.append($span);
});
return $namelist;
},
/*renderLiveList(names, el) {
let h = parseFloat(el.height), w = parseFloat(el.width), th = parseFloat(el.textHeight) || h;
let v_gap = parseFloat(el.textSpan) || 0, h_gap = 5;
let shrinkLimit = h < 300 ? 8 : (h < 500 ? 6 : 4);
let s = parseFloat(el.fontSize);
while (s >= shrinkLimit) {
let size_px = s * 1.333, current_col_h = 0, current_col_w = 0, cols_width = [], any_wrapped = false;
for (let name of names) {
let item_h = Math.max(th, name.length * size_px);
if (item_h > (h - 5)) {
any_wrapped = true;
if (current_col_h > 0) { cols_width.push(current_col_w); current_col_h = 0; current_col_w = 0; }
cols_width.push(Math.ceil(name.length / Math.max(1, Math.floor((h - 5) / size_px))) * (size_px * 1.1));
} else {
if (current_col_h === 0) { current_col_h = item_h; current_col_w = size_px; }
else if (current_col_h + v_gap + item_h <= h) { current_col_h += v_gap + item_h; current_col_w = Math.max(current_col_w, size_px); }
else { cols_width.push(current_col_w); current_col_h = item_h; current_col_w = size_px; }
}
}
if (current_col_h > 0) cols_width.push(current_col_w);
if (cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0) <= w && (!any_wrapped || s === shrinkLimit)) break;
s -= 1;
}
s = Math.max(s, shrinkLimit);
let $namelist = $(`<div class='liveList'></div>`).css({ "writing-mode": "vertical-rl", "display": "flex", "flex-flow": "wrap", "align-content": "center", "align-items": "center", "margin": "auto", "width": `${w}px`, "height": `${h}px`, "border": "0px solid transparent", "padding": "0px", "font-family": el.fontFamily, "column-gap": `${v_gap}px` });
names.forEach(name => $namelist.append(this.renderLiveSpan(name, el, s)));
return $namelist;
},
renderLiveSpan(name, el, fontSize) {
return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({ "display": "block", "min-height": `${parseFloat(el.textHeight) || parseFloat(el.height)}px`, "max-height": `${parseFloat(el.height)}px`, "width": "auto", "text-align": "justify", "text-align-last": "justify", "margin-left": "0px", "text-justify": "inter-character", "word-break": "break-all", "white-space": "pre-line" });
},*/
renderLiveList(names, el) {
let h = parseFloat(el.height);
let w = parseFloat(el.width);
let th = parseFloat(el.textHeight) || h;
let v_gap = parseFloat(el.textSpan) || 0;
let h_gap = 5;
let shrinkLimit = 4;
if (Number(el.height) < 300) {
shrinkLimit = 8;
} else if (Number(el.height) < 500) {
shrinkLimit = 6;
}
let s = parseFloat(el.fontSize);
if (names.length == 2) {
h = th + v_gap;
}
let maxLen = Math.max(...names.map(x => x.length), 1);
while (s >= shrinkLimit) {
let size_px = s * 1.333;
let current_col_h = 0;
let current_col_w = 0;
let cols_width = [];
let any_wrapped = false;
for (let i = 0; i < names.length; i++) {
let l = names[i].length;
let natural_max_h = maxLen * size_px;
let item_h = Math.max(th, natural_max_h);
if (item_h > natural_max_h * 1.5) {
item_h = natural_max_h * 1.5;
}
if (item_h > (h - 5)) {
any_wrapped = true;
if (current_col_h > 0) {
cols_width.push(current_col_w);
current_col_h = 0;
current_col_w = 0;
}
let chars_per_col = Math.floor((h - 5) / size_px);
if (chars_per_col < 1) chars_per_col = 1;
let item_cols = Math.ceil(l / chars_per_col);
cols_width.push(item_cols * (size_px * 1.1));
} else {
if (current_col_h === 0) {
current_col_h = item_h;
current_col_w = size_px;
} else if (current_col_h + v_gap + item_h <= h) {
current_col_h += v_gap + item_h;
if (size_px > current_col_w) current_col_w = size_px;
} else {
cols_width.push(current_col_w);
current_col_h = item_h;
current_col_w = size_px;
}
}
}
if (current_col_h > 0) cols_width.push(current_col_w);
let totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0);
if (totalWidth <= w && (!any_wrapped || s === shrinkLimit)) {
break;
}
s -= 1;
}
if (s < shrinkLimit) s = shrinkLimit;
let $namelist = $(`<div class='liveList'></div>`).css({
"writing-mode": "vertical-rl",
"display": "flex",
"flex-flow": "wrap",
"align-content": "center",
"align-items": "center",
"margin": "auto",
"width": `${w}px`,
"height": `${h}px`,
"border": "0px solid transparent",
"padding": "0px",
"font-family": el.fontFamily,
"column-gap": `${v_gap}px`
});
names.forEach(name => {
$namelist.append(this.renderLiveSpan(name, el, s, maxLen));
});
return $namelist;
},
renderLiveSpan(name, el, fontSize, maxLen = 1) {
let h = parseFloat(el.height);
let th = parseFloat(el.textHeight) || h;
let size_px = fontSize * 1.333;
let l = name.length;
let natural_max_h = maxLen * size_px;
let boxH_num = Math.max(th, natural_max_h);
if (boxH_num > natural_max_h * 1.5) {
boxH_num = natural_max_h * 1.5;
}
let minH = `${boxH_num}px`;
let alignStyle = "justify";
let spanDisplay = "block";
let spanJustifyContent = "flex-start";
let contentH = l * size_px;
let emptySpace = boxH_num - contentH;
let gapsCount = l > 1 ? (l - 1) : 1;
let avgGap = emptySpace / gapsCount;
if (alignStyle === "justify" && avgGap > (size_px * 1.5)) {
alignStyle = "justify";
spanDisplay = "block";
spanJustifyContent = "space-between";
}
return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({
"display": spanDisplay,
"justify-content": spanJustifyContent,
"align-items": "center",
"min-height": minH,
"max-height": `${h}px`,
"width": "auto",
"text-align": alignStyle,
"text-align-last": alignStyle,
"margin-left": "0px",
"margin-top": "0px",
"text-justify": "inter-character",
"word-break": "break-all",
"white-space": "pre-line"
});
},
renderRoster(names, el) {
if (!names.length) return '';
const mid = names.length === 1 ? 1 : Math.floor(names.length / 2);
let h = $(`<div class="rosterList"></div>`).css({ width: el.width, height: el.height, "writing-mode": "vertical-rl", display: "flex", "flex-direction": "row", "align-items": "center", "justify-content": "start", gap: "20px" });
h.append(this.renderNameGroups(names.slice(0, mid), el));
if (names.slice(mid).length) h.append(this.renderNameGroups(names.slice(mid), el));
return h;
},
renderCombined(mid, d) {
const parts = mid.join("\n").split('\n');
return `<div class="ancestor-wrapper" style="width:${d.width}px !important;height:${d.height}px !important;" >
<span class="main-name" >${parts[0] || ''}</span>
<span class="sub-text">${parts[1] || ''}</span>
</div>`;
},
renderNameGroups(items, el) {
let g = $(`<div class="rosterspan"></div>`).css({ "display": "flex", "flex-direction": "column", "justify-content": "center", "height": el.textHeight, "white-space": "pre-line" });
items.forEach(x => g.append($(`<div style="letter-spacing: 10px;text-align: justify; text-align-last: justify;">${x}</div>`)));
return g;
},
async 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;
},
scaleFontSize(txtContent, fontSize, elementWidth) {
let font = this.getTextSize("講", parseInt(fontSize));
if ((Math.ceil(txtContent.length / 2) * font.width) * 2 > parseFloat(elementWidth)) {
let newfontSize = parseInt(fontSize) - 1;
return newfontSize <= 10 ? newfontSize : this.scaleFontSize(txtContent, newfontSize, elementWidth);
}
return fontSize;
},
getTextSize(txt, fontsize) {
let span = document.createElement("span");
span.style.cssText = `visibility: hidden; position: absolute; font-size: ${fontsize}px; font-family: "";`;
span.innerText = txt;
document.body.appendChild(span);
let result = { width: span.offsetWidth, height: span.offsetHeight };
document.body.removeChild(span);
return result;
},
filterToSel() {
document.querySelectorAll(".canselectItem li").forEach(x => {
let curr = $(x).children().eq(1);
if (curr && curr.text().includes($("#filter_txt").val())) {
$(x).css("display", "");
} else {
$(x).css("display", "none");
}
});
},
async saveFamily() {
if (!this.follower_id) return false;
let detail = { follower_num: this.follower_id, fam_name: $("#family_txt").val(), fam_title: $("#title_txt").val(), deceased: $("#deceased_sel").val() !== "False", num: 0 };
this.modal.toggle();
await axios.post('/api/tablet/saveFamily', detail).then(response => {
if (response.status == 200 && response.data.result == "Y") {
this.familyMembers.push(response.data.data);
this.modal.toggle();
if (this.selectedElement) this.renderItem($(this.selectedElement).children().first());
$("#toast_body").html("新增成功"); $("#family_txt").val(""); $("#title_txt").val(""); $("#deceased_sel").val("False"); this.toast.show();
}
});
},
async saveTemplate(event) {
//event.preventDefault();
this.templateModal.show();
},
async saveTemplateReal(event) {
//event.preventDefault();
let tablet = { mid_items: this.family_deceased_N_selected, left_items: this.family_deceased_Y_selected, style: this.selectStyle.style, mid_left: this.family_left_title, mid_right: this.family_right_title, addr_items: this.family_address };
let data = { num: "0", name: $("#templateName").val(), follower_num: this.follower_id, style_id: this.selectStyle.style, tablet: JSON.stringify(tablet), shuwen:"" }
await axios.post('/api/follower/SaveTabletTemplate', data).then(res => {
if (res.status == 200) {
this.templateModal.hide();
$("#toast_body").html("存檔成功");
}
});
},
async save(event) {
event.preventDefault();
let tablet = { mid_items: this.family_deceased_N_selected, left_items: this.family_deceased_Y_selected, style: this.selectStyle.style, mid_left: this.family_left_title, mid_right: this.family_right_title, addr_items: this.family_address };
let detail = {
num: this.tabletItem.num <= 0 ? null : this.tabletItem.num, order_no: this.tabletItem.order_no, parent_num: this.tabletItem.parent_num, actItem_num: this.tabletItem.actitem_num_selected?.val,
f_num: this.tabletItem.f_num_selected?.val, f_num_tablet: JSON.stringify(tablet), print_id: this.tabletItem.print_id, address: this.tabletItem.address,
from_id: this.tabletItem.from_id_selected?.val, from_id_tablet: this.tabletItem.from_id_tablet, start_date: this.tabletItem.category == 1 ? this.tabletItem.start_date : null,
due_date: this.tabletItem.category == 1 ? this.tabletItem.due_date : null, extend_date: this.tabletItem.category == 1 ? this.tabletItem.extend_date : null,
price: this.tabletItem.price, qty: this.tabletItem.qty, keyin1: this.tabletItem.keyin1_selected?.val, demo: this.tabletItem.demo, pay: this.tabletItem.pay,
pay_date: this.tabletItem.pay_date, customize_data: this.tabletItem.customize_data, style: this.selectStyle.style
};
await axios.post('/api/order/SaveDetailData', detail).then(() => {
window.parent.postMessage({ source: 'editorNew.btn.click', data: { tabletItem: this.tabletItem, tablet_data: tablet, style: this.selectStyle.style } }, '/');
});
},
async resetScreen() {
$(".printArea").empty(); $(".selectItem").empty(); $(".canselectItem").empty();
}
};
$(() => $(window).on("message", function (event) {
let e = event.originalEvent;
if (e.origin === window.parent.location.origin) {
const data = e.data;
designer.family_deceased_Y_selected = []; designer.family_deceased_N_selected = []; designer.family_left_title = []; designer.family_right_title = [];
designer.http_host = data.host; designer.follower_id = data.follower_id;
if (data.tabletItem) {
designer.tabletItem = data.tabletItem;
if (data.familyMembers) designer.familyMembers = data.familyMembers;
designer.selected.style = data.tabletItem.style || "";
designer.actitem = data.tabletItem.actitem_num_selected?.val || "";
if (designer.tabletItem.f_num_tablet) {
try {
const parsedData = JSON.parse(designer.tabletItem.f_num_tablet);
if (designer.item_type === 'B') {
designer.family_deceased_Y_selected = parsedData.mid_items || [];
designer.family_deceased_N_selected = parsedData.left_items || [];
} else {
designer.family_deceased_Y_selected = [];
designer.family_deceased_N_selected = parsedData.mid_items || [];
}
} catch (err) { console.error('解析牌位資料時發生錯誤:', err); }
}
designer.resetScreen();
designer.getActItem().then(() => new Promise(res => setTimeout(res, 300))).then(() => {
designer.getTemplateNameList();
designer.getData();
});
}
}
}));
$(async () => {
await designer.init();
await Promise.all([
designer.getTabletElement(),
designer.getPaperSize(),
designer.getTabletStyles()
]);
await designer.getAllStyleDetails();
});
</script>
</body>
</html>