2180 lines
115 KiB
HTML
2180 lines
115 KiB
HTML
<!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/vuetify.css" rel="stylesheet" />
|
||
<link href="../../../js/mdi-font/css/materialdesignicons.min.css" rel="stylesheet" />
|
||
<script src="../../../js/vue.min.js"></script>
|
||
<script src="../../../js/vuetify.min.js"></script>
|
||
<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-2">
|
||
<button class="btn btn-primary" onclick="designer.save(event)">儲存牌位</button>
|
||
</div>
|
||
<div class="col-5">
|
||
<!-- 原生縮放橫軸容器 -->
|
||
<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;" onclick="designer.minusZoomScalee()">➖</span>
|
||
|
||
<!-- 使用 input type="range",綁定 zoomScale -->
|
||
<!-- min="0.2" (20%), max="1.5" (150%), step="0.05" (每次滑動5%) -->
|
||
<input type="range"
|
||
v-model="designer.zoomScale"
|
||
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;" 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">
|
||
<!--<img src="..." class="rounded me-2" alt="...">
|
||
<strong class="me-auto">Bootstrap</strong>
|
||
<small>11 mins ago</small>-->
|
||
<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>
|
||
<script type="text/javascript">
|
||
const designer = {
|
||
zoomScale:1,
|
||
code: {
|
||
style: []
|
||
},
|
||
selected: {
|
||
style: ""
|
||
},
|
||
actitem: "",
|
||
pageSize: "",
|
||
defaultStyle: "",
|
||
isAllSelected: false,
|
||
dialog: false,
|
||
addDialog: false, // 新增項目對話框
|
||
addDialogType: '', // 'Y' 或 'N'
|
||
newItemText: '', // 新增項目的文字
|
||
noSpace: true, // 不加空格 checkbox(預設勾選)
|
||
deleteDialog: false, // 確認刪除對話框
|
||
deleteIndex: -1, // 要刪除的 index
|
||
deleteType: '', // 'Y' 或 'N'
|
||
deleteMemberName: '', // 要刪除的名稱(顯示用)
|
||
phrases: [], // 常用片語
|
||
familyMembers: [], // 親友名單
|
||
tabletItem: {}, // 傳入的資料(信眾/牌位資訊)
|
||
item: {},
|
||
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",
|
||
http_host: "",
|
||
follower_id: "",
|
||
toast: null,
|
||
modal: 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() {
|
||
// 載入常用片語
|
||
fetch('phrases.json')
|
||
.then(res => res.json())
|
||
.then(data => { this.phrases = data; })
|
||
.catch(err => console.error('載入 phrases.json 失敗:', err));
|
||
this.toast = new bootstrap.Toast($('#liveToast'));
|
||
this.modal = new bootstrap.Modal($('#familyModal'));
|
||
//this.toast.show();
|
||
$("#scaleSize").html((this.zoomScale * 100) + "%")
|
||
$("#scaleSlider").val(this.zoomScale)
|
||
$("#scaleSlider").on("change", function () {
|
||
this.zoomScale = $(this).val()
|
||
$("#scaleSize").html(Math.floor(Number($(this).val()) * 100) + "%");
|
||
$(".tablet-paper").css("transform",`scale(${Number($(this).val())})`);
|
||
})
|
||
},
|
||
async minusZoomScalee() {
|
||
this.zoomScale = this.zoomScale - 0.05
|
||
$("#scaleSize").html(Math.floor(this.zoomScale * 100) + "%")
|
||
$("#scaleSlider").val(this.zoomScale)
|
||
$(".tablet-paper").css("transform", `scale(${Number(this.zoomScale)})`);
|
||
},
|
||
async plusZoomScalee() {
|
||
this.zoomScale = this.zoomScale + 0.05
|
||
$("#scaleSize").html(Math.floor(this.zoomScale * 100) + "%")
|
||
$("#scaleSlider").val(this.zoomScale)
|
||
$(".tablet-paper").css("transform", `scale(${Number(this.zoomScale)})`);
|
||
}, //加撈activity_relating的資料,用來判斷是否有人數上限 activity_num actItem_num
|
||
async getActItem() {
|
||
//console.log(this.tabletItem.has_chao_limit, this.tabletItem.has_yang_limit, this.tabletItem.chao_limit_count, this.tabletItem.yang_limit_count)
|
||
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;
|
||
//console.log("GetActItem:",this.defaultStyle)
|
||
$("#styleSel").val(this.defaultStyle);
|
||
}
|
||
}
|
||
);
|
||
|
||
},
|
||
async getAllStyleDetails() {
|
||
this.allStyleDetails.length = 0;
|
||
await axios
|
||
.post('/api/tablet/GetStyleDetailData', {})
|
||
.then(response => {
|
||
if (response.status == "200") {
|
||
let data = response.data;
|
||
if (data.result == "Y") {
|
||
let details = data.data;
|
||
this.allStyleDetails = details;
|
||
//console.log(data.data);
|
||
}
|
||
}
|
||
}
|
||
);
|
||
},
|
||
async getPaperSize() {
|
||
this.allSize.length = 0;
|
||
let self = this
|
||
await axios
|
||
.post('/api/tablet/GetPaperSize', {})
|
||
.then(response => {
|
||
//if (response.result=="Y") {
|
||
if (response.status == "200") {
|
||
let data = response.data;
|
||
this.allSize = data.data;
|
||
}
|
||
//$("#paperSizeSel").val("0003");
|
||
})
|
||
},
|
||
async getTabletElement() {
|
||
this.tabletElement.length = 0;
|
||
await axios
|
||
.post('/api/tablet/GetTabletElement', {})
|
||
.then(response => {
|
||
//if (response.result=="Y") {
|
||
if (response.status == "200") {
|
||
let data = response.data;
|
||
this.tabletElement = data.data;
|
||
}
|
||
});
|
||
},
|
||
async getTabletStyles() {
|
||
this.allStyle.length = 0;
|
||
await axios
|
||
.post('/api/tablet/GetStyleData', {})
|
||
.then(response => {
|
||
if (response.status == "200") {
|
||
let data = response.data;
|
||
if (data.data) {
|
||
data.data.forEach(x => {
|
||
if (x.styleID !== "000001") {
|
||
this.allStyle.push(x);
|
||
$("#styleSel").append(`<option value="${x.styleID}">${x.name}</option`);
|
||
}
|
||
})
|
||
}
|
||
}
|
||
});
|
||
},
|
||
async getData() {
|
||
//console.log("getData:", this.tabletItem)
|
||
this.printData.length = 0;
|
||
this.properTitle.length = 0
|
||
this.leftProperTitle.length = 0;
|
||
this.rightProperTitle.length = 0;
|
||
this.alive.length = 0;
|
||
//let list = localStorage.getItem("list")
|
||
let param = [];
|
||
let self = this;
|
||
param.push({ order_no: this.tabletItem.order_no, num: this.tabletItem.num })
|
||
|
||
|
||
await axios
|
||
.post('/api/orderdetail/GetDetailToPrint', { param: param })
|
||
.then(response => {
|
||
|
||
if (response.status == 200) {
|
||
self.printData = response.data;
|
||
console.log("GetDetailToPrint:",response);
|
||
self.printData.forEach(x => {
|
||
//console.log("GetDetailToPrint:",x.style);
|
||
if (x.style != null && x.style != undefined) {
|
||
self.selectStyle.style = x.style;
|
||
self.selected.style = x.style;
|
||
self.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 === "") {
|
||
if (x.address!="") {
|
||
address = x.address
|
||
}
|
||
}
|
||
self.family_deceased_Y_selected.length = 0;
|
||
self.family_deceased_N_selected.length = 0;
|
||
self.family_address = address;
|
||
self.family_left_title.length = 0;
|
||
self.family_right_title.length = 0;
|
||
Object.assign(self.family_deceased_N_selected, mid_items);
|
||
Object.assign(self.family_deceased_Y_selected, left_items);
|
||
Object.assign(self.family_left_title, mid_left);
|
||
Object.assign(self.family_right_title, mid_right);
|
||
self.family_address = address;//tablet.addr_items;
|
||
//let mid = [];
|
||
//family_deceased_Y_selected family_deceased_N_selected
|
||
if (mid_items) {
|
||
mid_items.forEach(y => {
|
||
self.properTitle.push(y.fam_name);
|
||
|
||
});
|
||
}
|
||
if (mid_left) {
|
||
mid_left.forEach(y => {
|
||
self.leftProperTitle.push(y.fam_name);
|
||
})
|
||
}
|
||
if (mid_right) {
|
||
mid_right.forEach(y => {
|
||
self.rightProperTitle.push(y.fam_name);
|
||
})
|
||
}
|
||
if (address) {
|
||
self.address = address;
|
||
|
||
}
|
||
//let left = []
|
||
if (left_items) {
|
||
left_items.forEach(y => {
|
||
self.alive.push(y.fam_name);
|
||
});
|
||
}
|
||
}
|
||
});
|
||
this.render();
|
||
}
|
||
});
|
||
},
|
||
async render() {
|
||
|
||
$(".printArea").empty();
|
||
$(".selectedItem").empty();
|
||
$(".canselectItem").empty();
|
||
let self = this;
|
||
self.tabletType = "A";
|
||
let canvas = $(` <div class="canvas-area flex-grow-1 overflow-auto d-flex flex-row align-items-center position-relative" ></div>`)
|
||
let nowPageWidth = 0;
|
||
let tabletpaper;
|
||
//列印紙張的尺寸
|
||
|
||
let mid = [];
|
||
Object.assign(mid, self.properTitle);
|
||
let left = []
|
||
Object.assign(left, self.alive);
|
||
|
||
let ltitle = [];
|
||
let rtitle = [];
|
||
let myAddress = [];
|
||
Object.assign(ltitle, self.leftProperTitle);
|
||
Object.assign(rtitle, self.rightProperTitle);
|
||
if (self.address != "") {
|
||
myAddress.push(self.address)
|
||
}
|
||
|
||
//this.printData.forEach(x => {
|
||
let x = self.selectStyle;
|
||
let style = self.allStyle.find(y => y.styleID == (x.style ? x.style : $("#styleSel").val()));
|
||
//console.log("style:",style)
|
||
if (x.style) {
|
||
|
||
} else {
|
||
self.selectStyle.style = style.styleID;
|
||
self.selected.style = style.styleID;
|
||
}
|
||
|
||
//用來印內容的尺寸
|
||
let size = self.allSize.find(y => y.paperID == style.paperSize);
|
||
let pageSize = self.allSize.find(y => y.paperID == style.paperSize);
|
||
//包含空白用來折的尺寸
|
||
let printSize = self.allSize.find(y => y.paperID == style.printSize);
|
||
//let scaleSize = 1
|
||
//let documentHeight = document.documentElement.scrollHeight;
|
||
//if ((Number(pageSize.height) / 0.265) >= (Number(documentHeight) * 0.8)) {
|
||
// scaleSize = (Number(documentHeight) * 0.8) / (Number(pageSize.height) / 0.265)
|
||
//}
|
||
//$("#scaleSize").html((scaleSize * 100) + "%")
|
||
tabletpaper = $(` <div class="tablet-paper" style="transform-origin:top left;transform:scale(${this.zoomScale})"></div>`)
|
||
let img = self.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 = self.tabletElement.find(y => y.elementID == d.elementID);
|
||
|
||
let newFontSize = self.scaleFontSize(mid, d.fontSize, d.width);
|
||
let html = "";
|
||
let alive = "";
|
||
let yang = "";
|
||
let bi = "";
|
||
|
||
if (d.elementID === 'title1') {
|
||
if (mid.length == 0) {
|
||
mid.push("請選擇")
|
||
}
|
||
// 1. 依據 textHeight 與 textSpan 計算一行(垂直列)最多能放幾個名字
|
||
|
||
let th = Number(d.textHeight) || 0;
|
||
let span = Number(d.textSpan) || 0;
|
||
let h = Number(d.height);
|
||
let w = Number(d.width);
|
||
|
||
let namesPerCol = Math.floor((h + span) / (th + span));
|
||
if (namesPerCol < 1) namesPerCol = 1;
|
||
|
||
// 2. 計算總共會產生幾列 (cols),並推算出每個名字能分配到的最大欄寬
|
||
let cols = Math.ceil(mid.length / namesPerCol);
|
||
if (cols < 1) cols = 1;
|
||
let colWidth = Math.floor(w / cols);
|
||
//console.log(perRow, rows, rowWidth, newFontSize)
|
||
html = self.renderNameList(mid, d, newFontSize, style, colWidth);
|
||
} else if (d.elementID === "alive") {
|
||
self.tabletType = "B";
|
||
if (left.length == 0) {
|
||
left.push("請選擇");
|
||
}
|
||
newFontSize =d.fontSize //self.scaleFontSize(left, d.fontSize, d.Width);
|
||
|
||
//html = self.renderLiveList(left, d, rowWidth);
|
||
html = this.renderLiveList(left, d);
|
||
|
||
} else if (d.elementID === "titletriangle") {
|
||
if (mid.length == 0) {
|
||
mid.push("請選擇")
|
||
}
|
||
html = self.renderRoster(mid, d)
|
||
} else if (d.elementID === "combined" || d.elementID === "tricombined") {
|
||
ancestorFontSize = d.fontSize;
|
||
if (mid.length == 0) {
|
||
mid.push("請選擇")
|
||
}
|
||
html = self.renderCombined(mid, d);
|
||
|
||
} else if (d.elementID === "lefttitle") {
|
||
if (ltitle.length == 0) {
|
||
ltitle.push("請選擇")
|
||
}
|
||
html = self.renderNormalWithSpace(ltitle, d)
|
||
} else if (d.elementID === "righttitle") {
|
||
if (rtitle.length == 0) {
|
||
rtitle.push("請選擇")
|
||
}
|
||
html = self.renderNormalWithSpace(rtitle, d)
|
||
} else if (d.elementID == "address") {
|
||
if (myAddress.length == 0) {
|
||
myAddress.push("請選擇")
|
||
}
|
||
html = self.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);
|
||
html = "";
|
||
alive = "";
|
||
yang = "";
|
||
bi = "";
|
||
|
||
|
||
}
|
||
});
|
||
self.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; /*必須為 visible */
|
||
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: 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: normal;
|
||
line-height: 1.2;
|
||
font-family: DFKai-sb;
|
||
/* 確保名字本身不會佔據過多寬度導致間距看起來很大 */
|
||
width: fit-content;
|
||
}
|
||
|
||
/* 【修復底圖沒出現】強制印出背景設定 */
|
||
* {
|
||
-webkit-print-color-adjust: exact !important;
|
||
print-color-adjust: exact !important;
|
||
color-adjust: exact !important;
|
||
}
|
||
.ancestor-wrapper {
|
||
/* 重置為橫向流,這樣 column 才會是真正的上下堆疊 */
|
||
writing-mode: horizontal-tb;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center; /* 水平置中 */
|
||
justify-content: felx-start;
|
||
width: fit-content;
|
||
}
|
||
|
||
.main-name {
|
||
line-height: 1.2;
|
||
/* 保持大字 */
|
||
}
|
||
|
||
.sub-text {
|
||
font-size: ${ancestorFontSize}px ;/*0.8em;*/ /* 縮小字體 */
|
||
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(self.pageStyle);
|
||
$(canvas).append(tabletpaper);
|
||
$(".printArea").append(canvas);
|
||
|
||
$(".tablet-element").on("click", function (e) {
|
||
e.preventDefault();
|
||
self.selectedElement = $(this);
|
||
let element = $(this).children().first();
|
||
self.renderItem(element);
|
||
});
|
||
},
|
||
async renderItem(element) {
|
||
let self = this;
|
||
$(".selectedItem").empty();
|
||
$(".canselectItem").empty();
|
||
|
||
if (element.hasClass("liveList")) {//陽上
|
||
//抓未往生的人出來
|
||
//self.familyMembers;
|
||
self.alive.forEach(x => {
|
||
let f = self.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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class="itemRemove" style="float:right ;margin-left:2rem;cursor:pointer;" >−</span>
|
||
<span class="itemUp" style="float:right ;margin-left:2rem;cursor:pointer;">↑</span>
|
||
<span class="itemDown" style="float:right ;margin-left:2rem;cursor:pointer;">↓</span></li>`);
|
||
});
|
||
let items = []
|
||
self.familyMembers.forEach(x => {
|
||
if (x.deceased == false)
|
||
items.push(x.fam_name);
|
||
});
|
||
let finalItem = items.filter(y => !self.alive.includes(y))
|
||
finalItem.forEach(yy => {
|
||
let f = self.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 && f.fam_title != ''&&f.fam_title!=null) ? 'p-1':''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class='itemAppend' style="float:right;cursor:pointer;">+<span></li>`);
|
||
});
|
||
|
||
} else if (element.hasClass("address")) {//地址
|
||
//console.log(self.address);
|
||
$(".selectedItem").append(`<li><span style="opacity:0.5">卍</span>
|
||
<span class="selected">${self.address==undefined?"":self.address}</span>
|
||
<span class="itemRemove" style="float:right ;margin-left:2rem;cursor:pointer;" >−</span>`);
|
||
} else if (element.hasClass("lefttitle")) {//左正名
|
||
self.leftProperTitle.forEach(x => {
|
||
let f = self.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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class="itemRemove" style="float:right ;margin-left:2rem;cursor:pointer;" >−</span>`);
|
||
})
|
||
let items = [];
|
||
self.familyMembers.forEach(x => {
|
||
if (self.tabletType == "A" && x.deceased == false) {
|
||
items.push(x.fam_name);
|
||
} else if (self.tabletType == "B" && x.deceased == true) {
|
||
items.push(x.fam_name);
|
||
}
|
||
});
|
||
let finalItem = items.filter(y => !self.leftProperTitle.includes(y))
|
||
finalItem.forEach(yy => {
|
||
let f = self.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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class='itemAppend' style="float:right;cursor:pointer;">+<span></li>`);
|
||
});
|
||
} else if (element.hasClass("righttitle")) {//右正名
|
||
self.rightProperTitle.forEach(x => {
|
||
let f = self.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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class="itemRemove" style="float:right ;margin-left:2rem;cursor:pointer;" >−</span>`);
|
||
})
|
||
let items = [];
|
||
self.familyMembers.forEach(x => {
|
||
if (self.tabletType == "A" && x.deceased == false) {
|
||
items.push(x.fam_name);
|
||
} else if (self.tabletType == "B" && x.deceased == true) {
|
||
items.push(x.fam_name);
|
||
}
|
||
});
|
||
let finalItem = items.filter(y => !self.rightProperTitle.includes(y))
|
||
finalItem.forEach(yy => {
|
||
let f=self.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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class='itemAppend' style="float:right;cursor:pointer;">+<span></li>`);
|
||
});
|
||
} else {//其他都是正名
|
||
|
||
if (element.hasClass("ancestor-wrapper")) {//多姓氏合併,只允許用打的
|
||
self.properTitle.forEach(y => {
|
||
let f = self.familyMembers.find(z => z.fam_name == y);
|
||
$(".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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class="itemRemove" style="float:right ;margin-left:2rem;cursor:pointer;" >−</span>
|
||
<span class="itemUp" style="float:right ;margin-left:2rem;cursor:pointer;">↑</span>
|
||
<span class="itemDown" style="float:right ;margin-left:2rem;cursor:pointer;">↓</span></li>`);
|
||
});
|
||
} else if (element.hasClass("nameList") || element.hasClass("rosterList")) {//
|
||
|
||
self.properTitle.forEach(y => {
|
||
let f = self.familyMembers.find(z => z.fam_name == y);
|
||
$(".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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class="itemRemove" style="float:right ;margin-left:2rem;cursor:pointer;" >−</span>
|
||
<span class="itemUp" style="float:right ;margin-left:2rem;cursor:pointer;">↑</span>
|
||
<span class="itemDown" style="float:right ;margin-left:2rem;cursor:pointer;">↓</span></li>`);
|
||
})
|
||
let items = []
|
||
self.familyMembers.forEach(x => {
|
||
if (self.tabletType == "A" && x.deceased == false) {
|
||
items.push(x.fam_name);
|
||
} else if (self.tabletType == "B" && x.deceased == true) {
|
||
items.push(x.fam_name);
|
||
}
|
||
});
|
||
let finalItem = items.filter(y => !self.properTitle.includes(y))
|
||
finalItem.forEach(yy => {
|
||
let f = self.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 && f.fam_title != '' && f.fam_title != null) ? 'p-1' : ''}" style="background-color:lightgray">
|
||
${(f && f.fam_title != null) ? f.fam_title : ""}</i>
|
||
</span>
|
||
<span class='itemAppend' style="float:right;cursor:pointer;">+<span></li>`);
|
||
});
|
||
}
|
||
}
|
||
|
||
self.bindEvent();
|
||
},
|
||
async addNewItem() {//已選輸入框按加號
|
||
//console.log("addNewItem:",$("#custom_txt").val())
|
||
if ($("#custom_txt").val()=="") {
|
||
|
||
alert("名稱不可空白")
|
||
return false
|
||
}
|
||
|
||
|
||
let self = this;
|
||
let target = $(self.selectedElement).children().first();
|
||
let error=""
|
||
//let fam = self.familyMembers.find(x => x.fam_name == target.text());
|
||
if (target.hasClass("liveList")) {
|
||
self.alive.forEach(x => {
|
||
if (x == $("#custom_txt").val()) {
|
||
error = "名稱已存在"
|
||
}
|
||
})
|
||
if (error != "") {
|
||
alert(error)
|
||
return false
|
||
}
|
||
self.alive.push($("#custom_txt").val());
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: $("#custom_txt").val(),
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: 0,
|
||
option_break: false
|
||
}
|
||
self.family_deceased_Y_selected.push(obj);
|
||
} else if (target.hasClass("nameList") || target.hasClass("rosterList")) {
|
||
self.properTitle.forEach(x => {
|
||
if (x == $("#custom_txt").val()) {
|
||
error = "名稱已存在"
|
||
}
|
||
})
|
||
if (error != "") {
|
||
alert(error)
|
||
return false
|
||
}
|
||
self.properTitle.push($("#custom_txt").val());
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: $("#custom_txt").val(),
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: 0,
|
||
option_break: false
|
||
}
|
||
self.family_deceased_N_selected.push(obj);
|
||
} else if (target.hasClass("ancestor-wrapper")) {
|
||
//再來判斷是否已有這個名稱
|
||
self.properTitle.forEach(x => {
|
||
if (x == $("#custom_txt").val()) {
|
||
error = "名稱已存在"
|
||
}
|
||
})
|
||
if (error != "") {
|
||
alert(error)
|
||
return false
|
||
}
|
||
if (self.properTitle.length >= 2) {
|
||
alert("多姓氏合併必須第一個名稱為姓,第二個名稱為氏歷代祖先之類,且最多兩個名稱");
|
||
} else {
|
||
self.properTitle.push($("#custom_txt").val());
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: $("#custom_txt").val(),
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: 0,
|
||
option_break: false
|
||
}
|
||
self.family_deceased_N_selected.push(obj);
|
||
}
|
||
} else if (target.hasClass("lefttitle")) {
|
||
self.leftProperTitle.forEach(x => {
|
||
if (x == $("#custom_txt").val()) {
|
||
error = "名稱已存在"
|
||
}
|
||
})
|
||
if (error != "") {
|
||
alert(error)
|
||
return false
|
||
}
|
||
self.leftProperTitle.length = 0;
|
||
self.family_left_title.length = 0;
|
||
self.leftProperTitle.push($("#custom_txt").val());
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: $("#custom_txt").val(),
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: -1,
|
||
option_break: false
|
||
}
|
||
self.family_left_title.push(obj);
|
||
//self.family_deceased_N_selected.push(obj);
|
||
//}
|
||
} else if (target.hasClass("righttitle")) {
|
||
self.rightProperTitle.forEach(x => {
|
||
if (x == $("#custom_txt").val()) {
|
||
error = "名稱已存在"
|
||
}
|
||
})
|
||
if (error != "") {
|
||
alert(error)
|
||
return false
|
||
}
|
||
self.rightProperTitle.length = 0;
|
||
self.family_right_title.length = 0;
|
||
self.rightProperTitle.push($("#custom_txt").val());
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: $("#custom_txt").val(),
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: 0,
|
||
option_break: false
|
||
}
|
||
self.family_right_title.push(obj);
|
||
//self.family_deceased_N_selected.push(obj);
|
||
//}
|
||
} else if (target.hasClass("address")) {
|
||
|
||
self.address = ($("#custom_txt").val());
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: $("#custom_txt").val(),
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: 0,
|
||
option_break: false
|
||
}
|
||
self.family_address = self.address
|
||
//self.family_deceased_N_selected.push(obj);
|
||
|
||
}
|
||
$("#custom_txt").val("");
|
||
self.renderItem($(self.selectedElement).children().first());
|
||
self.rerenderPart();
|
||
},
|
||
async bindEvent() {
|
||
let self = this;
|
||
$(".itemAppend").off("click");
|
||
$(".itemRemove").off("click");
|
||
$(".itemUp").off("click");
|
||
$(".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) {//liveList nameList rosterList 要判斷yang_limit_count chao_limit_count
|
||
if ($(self.selectedElement).children().first().hasClass("liveList")) {
|
||
if (self.tabletItem.has_yang_limit && self.tabletItem.yang_limit_count > 0) {
|
||
if (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);
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: target.fam_name,
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: target.num,
|
||
option_break: false
|
||
}
|
||
self.family_deceased_Y_selected.push(obj);
|
||
} else if ($(self.selectedElement).children().first().hasClass("nameList") ||
|
||
$(self.selectedElement).children().first().hasClass("rosterList")) {
|
||
|
||
if (self.tabletItem.has_chao_limit && self.tabletItem.chao_limit_count > 0) {
|
||
if (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);
|
||
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: target.fam_name,
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: target.num,
|
||
option_break: false
|
||
}
|
||
self.family_deceased_N_selected.push(obj);
|
||
} else if ($(self.selectedElement).children().first().hasClass("lefttitle")) {
|
||
self.leftProperTitle.push(target.fam_name);
|
||
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: target.fam_name,
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: target.num,
|
||
option_break: false
|
||
}
|
||
self.family_left_title.push(obj);
|
||
} else if ($(self.selectedElement).children().first().hasClass("righttitle")) {
|
||
self.rightProperTitle.push(target.fam_name);
|
||
|
||
let obj = {
|
||
IsShuWen: false,
|
||
deceased: false,
|
||
fam_gender: "",
|
||
fam_name: target.fam_name,
|
||
fam_file: "",
|
||
nospace: true,
|
||
num: target.num,
|
||
option_break: false
|
||
}
|
||
self.family_right_title.push(obj);
|
||
}
|
||
}
|
||
self.renderItem($(self.selectedElement).children().first())
|
||
self.rerenderPart();
|
||
});
|
||
|
||
$(".itemRemove").on("click", function (e) {
|
||
let curr = $(e.currentTarget.parentElement).find(".selected").first();
|
||
if ($(self.selectedElement).children().first().hasClass("liveList")) {
|
||
self.alive = self.alive.filter(x => x !== curr.text());
|
||
self.family_deceased_Y_selected = self.family_deceased_Y_selected.filter(x => x.fam_name != curr.text());
|
||
} else if ($(self.selectedElement).children().first().hasClass("nameList") ||
|
||
$(self.selectedElement).children().first().hasClass("rosterList")) {
|
||
self.properTitle = self.properTitle.filter(x => x !== curr.text());
|
||
self.family_deceased_N_selected = self.family_deceased_N_selected.filter(x => x.fam_name != curr.text());
|
||
} else if ($(self.selectedElement).children().first().hasClass("ancestor-wrapper")) {//
|
||
self.properTitle = self.properTitle.filter(x => x !== curr.text());
|
||
self.family_deceased_N_selected = self.family_deceased_N_selected.filter(x => x.fam_name != curr.text());
|
||
} else if ($(self.selectedElement).children().first().hasClass("address")) {
|
||
self.address = curr.text();
|
||
self.family_address = curr.text()
|
||
} else if ($(self.selectedElement).children().first().hasClass("lefttitle")) {
|
||
self.leftProperTitle = self.leftProperTitle.filter(x => x !== curr.text());
|
||
self.family_left_title = self.family_left_title = (x => x.fam_name !== curr.text());
|
||
} else if ($(self.selectedElement).children().first().hasClass("righttitle")) {
|
||
self.rightProperTitle = self.rightProperTitle.filter(x => x !== curr.text());
|
||
self.family_right_title = self.family_right_title = (x => x.fam_name !== curr.text());
|
||
}
|
||
self.renderItem($(self.selectedElement).children().first());
|
||
self.rerenderPart();
|
||
});
|
||
|
||
$(".itemUp").on("click", function (e) {
|
||
let curr = $(e.currentTarget.parentElement).find(".selected").first();
|
||
if ($(self.selectedElement).children().first().hasClass("liveList")) {
|
||
let index = self.alive.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.alive[index - 1], self.alive[index]] = [self.alive[index], self.alive[index - 1]];
|
||
[self.family_deceased_Y_selected[index - 1], self.family_deceased_Y_selected[index]] =
|
||
[self.family_deceased_Y_selected[index], self.family_deceased_Y_selected[index - 1]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("nameList") ||
|
||
$(self.selectedElement).children().first().hasClass("rosterList")) {
|
||
let index = self.properTitle.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.properTitle[index - 1], self.properTitle[index]] = [self.properTitle[index], self.properTitle[index - 1]];
|
||
[self.family_deceased_N_selected[index - 1], self.family_deceased_N_selected[index]] =
|
||
[self.family_deceased_N_selected[index], self.family_deceased_N_selected[index - 1]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("ancestor-wrapper")) {
|
||
let index = self.properTitle.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.properTitle[index - 1], self.properTitle[index]] = [self.properTitle[index], self.properTitle[index - 1]];
|
||
[self.family_deceased_N_selected[index - 1], self.family_deceased_N_selected[index]] =
|
||
[self.family_deceased_N_selected[index], self.family_deceased_N_selected[index - 1]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("address")) {
|
||
//self.address = curr.text();
|
||
} else if ($(self.selectedElement).children().first().hasClass("lefttitle")) {
|
||
let index = self.leftProperTitle.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.leftProperTitle[index - 1], self.leftProperTitle[index]] = [self.leftProperTitle[index], self.properTitle[index - 1]];
|
||
[self.family_left_title[index - 1], self.family_left_title[index]] =
|
||
[self.family_left_title[index], self.family_left_title[index - 1]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("righttitle")) {
|
||
let index = self.rightProperTitle.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.rightProperTitle[index - 1], self.rightProperTitle[index]] = [self.rightProperTitle[index], self.properTitle[index - 1]];
|
||
[self.family_right_title[index - 1], self.family_right_title[index]] =
|
||
[self.family_right_title[index], self.family_right_title[index - 1]];
|
||
};
|
||
}
|
||
self.renderItem($(self.selectedElement).children().first());
|
||
self.rerenderPart();
|
||
});
|
||
$(".itemDown").on("click", function (e) {
|
||
let curr = $(e.currentTarget.parentElement).find(".selected").first();
|
||
if ($(self.selectedElement).children().first().hasClass("liveList")) {
|
||
let index = self.alive.indexOf(curr.text());
|
||
if (index == self.alive.length - 1) {
|
||
|
||
} else {
|
||
[self.alive[index], self.alive[index + 1]] = [self.alive[index + 1], self.alive[index]];
|
||
[self.family_deceased_Y_selected[index], self.family_deceased_Y_selected[index + 1]] =
|
||
[self.family_deceased_Y_selected[index + 1], self.family_deceased_Y_selected[index]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("nameList") ||
|
||
$(self.selectedElement).children().first().hasClass("rosterList")) {
|
||
let index = self.properTitle.indexOf(curr.text());
|
||
if (index == self.properTitle.length - 1) {
|
||
|
||
} else {
|
||
[self.properTitle[index], self.properTitle[index + 1]] = [self.properTitle[index + 1], self.properTitle[index]];
|
||
[self.family_deceased_N_selected[index], self.family_deceased_N_selected[index + 1]] =
|
||
[self.family_deceased_N_selected[index + 1], self.family_deceased_N_selected[index]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("ancestor-wrapper")) {
|
||
let index = self.properTitle.indexOf(curr.text());
|
||
if (index == self.properTitle.length - 1) {
|
||
|
||
} else {
|
||
[self.properTitle[index], self.properTitle[index + 1]] = [self.properTitle[index + 1], self.properTitle[index]];
|
||
[self.family_deceased_N_selected[index], self.family_deceased_N_selected[index + 1]] =
|
||
[self.family_deceased_N_selected[index + 1], self.family_deceased_N_selected[index]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("address")) {
|
||
//self.address = curr.text();
|
||
} else if ($(self.selectedElement).children().first().hasClass("lefttitle")) {
|
||
let index = self.leftProperTitle.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.leftProperTitle[index], self.leftProperTitle[index + 1]] = [self.leftProperTitle[index + 1], self.properTitle[index]];
|
||
[self.family_left_title[index], self.family_left_title[index + 1]] =
|
||
[self.family_left_title[index + 1], self.family_left_title[index]];
|
||
}
|
||
} else if ($(self.selectedElement).children().first().hasClass("righttitle")) {
|
||
let index = self.rightProperTitle.indexOf(curr.text());
|
||
if (index == 0) {
|
||
|
||
} else {
|
||
[self.rightProperTitle[index], self.rightProperTitle[index + 1]] = [self.rightProperTitle[index + 1], self.properTitle[index]];
|
||
[self.family_right_title[index], self.family_right_title[index + 1]] =
|
||
[self.family_right_title[index + 1], self.family_right_title[index]];
|
||
};
|
||
}
|
||
|
||
self.renderItem($(self.selectedElement).children().first());
|
||
self.rerenderPart();
|
||
});
|
||
},
|
||
async rerenderPart() {
|
||
let self = this;
|
||
let x = self.selectStyle;
|
||
let style = self.allStyle.find(y => y.styleID == x.style);
|
||
//用來印內容的尺寸
|
||
let size = self.allSize.find(y => y.paperID == style.paperSize);
|
||
let pageSize = self.allSize.find(y => y.paperID == style.printSize);
|
||
//包含空白用來折的尺寸
|
||
let printSize = self.allSize.find(y => y.paperID == style.printSize);
|
||
let details = self.allStyleDetails.filter(y => y.styleID == x.style);
|
||
if ($(self.selectedElement).children().first().hasClass("liveList")) {
|
||
$(self.selectedElement).empty();
|
||
//要重新render陽上
|
||
let left = []
|
||
Object.assign(left, self.alive);
|
||
//this.printData.forEach(x => {
|
||
let d = details.find(y => y.elementID == "alive");
|
||
|
||
let html = this.renderLiveList(left, d);
|
||
//self.selectedElement.css("font-size", fontSize + "px");
|
||
|
||
//let html = self.renderLiveList(left, d, rowWidth);
|
||
$(self.selectedElement).append(html);
|
||
} else if ($(self.selectedElement).children().first().hasClass("lefttitle") ||
|
||
$(self.selectedElement).children().first().hasClass("righttitle") ||
|
||
$(self.selectedElement).children().first().hasClass("address")) {
|
||
|
||
let data = []
|
||
if ($(self.selectedElement).children().first().hasClass("lefttitle")) {
|
||
$(self.selectedElement).empty();
|
||
Object.assign(data, self.leftProperTitle);
|
||
let d = details.find(y => y.elementID == "lefttitle");
|
||
let html = self.renderNormalWithSpace(data, d);
|
||
//console.log("html:",html);
|
||
$(self.selectedElement).append(html);
|
||
} else if ($(self.selectedElement).children().first().hasClass("righttitle")) {
|
||
$(self.selectedElement).empty();
|
||
Object.assign(data, self.rightProperTitle);
|
||
let d = details.find(y => y.elementID == "righttitle");
|
||
let html = self.renderNormalWithSpace(data, d);
|
||
$(self.selectedElement).append(html);
|
||
} else if ($(self.selectedElement).children().first().hasClass("address")) {
|
||
$(self.selectedElement).empty();
|
||
data.push(self.address);
|
||
let d = details.find(y => y.elementID == "address");
|
||
let html = self.renderNormal(data, d);
|
||
$(self.selectedElement).append(html);
|
||
}
|
||
//this.printData.forEach(x => {
|
||
} else {
|
||
let mid = []
|
||
Object.assign(mid, self.properTitle);
|
||
//要重新render陽上
|
||
if ($(self.selectedElement).children().first().hasClass("nameList")) {
|
||
$(self.selectedElement).empty();
|
||
let d = details.find(y => y.elementID == "title1");
|
||
let newfontSize = self.scaleFontSize(mid, d.fontSize, d.textWidth)
|
||
// 1. 依據 textHeight 與 textSpan 計算一行(垂直列)最多能放幾個名字
|
||
|
||
//let th = Number(d.textHeight) || 0;
|
||
//let span = Number(d.textSpan) || 0;
|
||
//let h = Number(d.height);
|
||
//let w = Number(d.width);
|
||
|
||
//let namesPerCol = Math.floor((h + span) / (th + span));
|
||
//if (namesPerCol < 1) namesPerCol = 1;
|
||
|
||
//// 2. 計算總共會產生幾列 (cols),並推算出每個名字能分配到的最大欄寬
|
||
//let cols = Math.ceil(mid.length / namesPerCol);
|
||
//if (cols < 1) cols = 1;
|
||
//let colWidth = Math.floor(w / cols);
|
||
//console.log(self.selectedElement,perRow, rows, rowWidth, newfontSize)
|
||
|
||
self.selectedElement.css("font-size", newfontSize+"px");
|
||
let html = self.renderNameList(mid, d, style.fontSize, style,0);
|
||
$(self.selectedElement).append(html);
|
||
} else if ($(self.selectedElement).children().first().hasClass("rosterList")) {
|
||
$(self.selectedElement).empty();
|
||
let d = details.find(y => y.elementID == "titletriangle");
|
||
let newfontSize = self.scaleFontSize(mid, d.fontSize, d.textWidth)
|
||
if (newfontSize < 10) {
|
||
newfontSize = 10;
|
||
}
|
||
self.selectedElement.css("font-size", newfontSize+"px");
|
||
let html = self.renderRoster(mid, d);
|
||
$(self.selectedElement).append(html);
|
||
} else if ($(self.selectedElement).children().first().hasClass("ancestor-wrapper")) {
|
||
$(self.selectedElement).empty();
|
||
let d = details.find(y => y.elementID == "combined");
|
||
let newfontSize = self.scaleFontSize(mid, d.fontSize, d.textWidth)
|
||
if (newfontSize < 10) {
|
||
newfontSize = 10;
|
||
}
|
||
self.selectedElement.css("font-size", newfontSize+"px");
|
||
let html = self.renderCombined(mid, d);
|
||
$(self.selectedElement).append(html);
|
||
}
|
||
}
|
||
},
|
||
countFontSize(name, el, fontSize, rows) {
|
||
if (Number(name.length) * 2 * Number(fontSize) > el.height) {
|
||
fontSize = Number(fontSize) - 2;
|
||
rows = rows + 1;
|
||
let rowWidth = Math.floor(Number(el.width) / rows);
|
||
fontSize = Math.floor(rowWidth / 2);
|
||
|
||
// 取得遞迴後最終適合的字體大小
|
||
let finalSize = this.countFontSize(name, el, fontSize, rows);
|
||
|
||
// 若遞迴底層回傳 0 (代表沒再超過高度),則回傳當下計算出的 fontSize
|
||
return finalSize === 0 ? fontSize : finalSize;
|
||
}
|
||
return 0; // 若原本就沒超過高度,回傳 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; // 將 pt 轉換為 px
|
||
|
||
|
||
// 如果單行高度夠放,直接回傳當下大小 (不會過度縮小)
|
||
//if (l * s_px <= maxH) {
|
||
// return s;
|
||
//}
|
||
|
||
// 單行放不下,模擬多行折疊後的總寬度
|
||
let charsPerCol = Math.floor(maxH / s_px);
|
||
if (charsPerCol < 1) charsPerCol = 1;
|
||
|
||
let cols = Math.ceil(l / charsPerCol);
|
||
let neededWidth = (cols === 1) ? s_px : cols * (s_px * 1.1); // 1.1 為緊密行高
|
||
|
||
// 如果折疊後的總寬度不超過分配到的橫向欄寬,就是最佳字體
|
||
if (neededWidth <= maxW && (cols > 1 || l * s_px <= maxH)) {
|
||
return s;
|
||
}
|
||
s -= 1; // 否則繼續縮小 1pt
|
||
}
|
||
return 12; // 極限最小字體
|
||
},
|
||
async itemAppend(e) {
|
||
//console.log(e);
|
||
},
|
||
changeStyle() {
|
||
let self = this;
|
||
this.selected.style = $("#styleSel").val();
|
||
this.selectStyle.style = $("#styleSel").val();
|
||
//console.log("changeStyle:",this.selectStyle);
|
||
this.render();
|
||
},
|
||
fix2(val) {
|
||
return Number.parseFloat(val).toFixed(2);
|
||
},
|
||
renderNormalWithSpace(mid, d) {
|
||
//console.log("renderNormal:", d)
|
||
let self = this;
|
||
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",
|
||
//"letter-spacing": "0.1em",
|
||
"column-gap": `${d.textSpan}px`,
|
||
//"row-gap": "1px",
|
||
"align-items": "flex-start",
|
||
"justify-content": "start",
|
||
});
|
||
//console.log("吼優:",mid);
|
||
mid.forEach(x => {
|
||
let dd = $(`<div>${x}</div>`).css({
|
||
"margin-bottom": "10px",
|
||
"margin-left": "5px",
|
||
"text-justify": "inter-character",
|
||
"white-space": "pre-line",
|
||
});
|
||
$(txt).append(dd);
|
||
});
|
||
return txt;
|
||
},
|
||
renderNormal(mid, d) {
|
||
//console.log("renderNormal:", d)
|
||
let self = this;
|
||
let txt = $(`<div class='${d.elementID}'>${mid}</div>`).css({
|
||
"z-index": 9999, visibility: d.visibility,
|
||
width: d.width, height: d.height
|
||
});
|
||
return txt;
|
||
},
|
||
renderNameList(names, d, newFontSize, style, colWidth) {
|
||
let self = this;
|
||
let h = Number(d.height);
|
||
let w = Number(d.width);
|
||
let th = Number(d.textHeight) || h;
|
||
let gap = 5; // 對應 span 的 margin-left
|
||
|
||
let s = Number(d.fontSize);
|
||
let sizes = [];
|
||
|
||
// 1. 模擬排版:找出能讓全體塞入的最佳字體組合
|
||
while (s >= 12) {
|
||
let short_s_px = s * 1.333;
|
||
let hasShort = names.some(n => (n.length * short_s_px) <= th);
|
||
|
||
let long_s = hasShort? Math.floor(s * 0.75):s; // 長字體最多縮小 25%
|
||
if (long_s < 12) long_s = 12;
|
||
let long_s_px = long_s * 1.333;
|
||
|
||
let currentColHeight = 0;
|
||
let totalWidth = 0;
|
||
let tempSizes = [];
|
||
let anyWrapped = false;
|
||
for (let i = 0; i < names.length; i++) {
|
||
let name = names[i];
|
||
let l = name.length;
|
||
//let isMulti = (l * short_s_px) > th;
|
||
let isShort = (l * short_s_px) <= th;
|
||
let size = isShort ? s : long_s;//isMulti ? long_s : s;
|
||
let size_px = size * 1.333; //isMulti ? long_s_px : short_s_px;
|
||
|
||
let isMulti = !isShort && (l * size_px >= h);
|
||
if (isMulti) anyWrapped = true;
|
||
|
||
tempSizes.push({ name: name, size: size, isMulti: isMulti, isShort: isShort });
|
||
|
||
if (isMulti) {
|
||
// 多行長名
|
||
let charsPerCol = Math.floor(h / size_px);
|
||
if (charsPerCol < 1) charsPerCol = 1;
|
||
let lines = Math.ceil(l / charsPerCol);
|
||
let nameW = lines * (size_px * 1.1); // 1.1倍行距
|
||
|
||
if (currentColHeight > 0) currentColHeight = 0;
|
||
totalWidth += nameW + gap;
|
||
} else {
|
||
if (isShort) {
|
||
// 單行短名
|
||
if (currentColHeight > 0 && (currentColHeight + th + gap) <= h) {
|
||
currentColHeight += th + gap;
|
||
} else {
|
||
currentColHeight = th;
|
||
totalWidth += short_s_px + gap;
|
||
}
|
||
} else {
|
||
currentColHeight = 0;
|
||
totalWidth += size_px + gap;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (totalWidth > 0) totalWidth -= gap;
|
||
|
||
// 總寬度未爆框,確定最佳組合
|
||
if (totalWidth <= w) {
|
||
if (hasShort) {
|
||
sizes = tempSizes;
|
||
break;
|
||
} else {
|
||
if (!anyWrapped || s === 12) {
|
||
sizes = tempSizes;
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
s -= 1;
|
||
}
|
||
|
||
// 處理極端情況
|
||
if (sizes.length === 0) {
|
||
sizes = names.map(name => {
|
||
return { name: name, size: 12, isMulti: (name.length * 12 * 1.333) > th };
|
||
});
|
||
}
|
||
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: `${d.width}px`,
|
||
height: `${d.height}px`,
|
||
border: "0px solid #ccc",
|
||
padding: "1px",
|
||
"column-gap": `${d.textSpan}px`,
|
||
"font-family": d.fontFamily
|
||
});
|
||
|
||
sizes.forEach(item => {
|
||
let th = Number(d.textHeight) || Number(d.height)
|
||
//let minH = item.isMulti ? `${d.height}px` : `${d.textHeight}px`;
|
||
let minH = item.isShort ? `${th}px` : `${d.height}px`
|
||
;
|
||
let alignStyle = item.isMulti ? "start" : "justify";
|
||
|
||
let $span= $(`<span style="font-size: ${item.size}pt;">${item.name}</span>`).css({
|
||
display: "block",
|
||
"min-height": minH, // 強制多行長名撐滿高度,避免其他名字墊底
|
||
"max-height": `${d.height}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"
|
||
});
|
||
$namelist.append($span);
|
||
})
|
||
//let $namelist = $(`<div class='nameList'></div>`).css({
|
||
// "writing-mode": "vertical rl",
|
||
// display: "flex",
|
||
// "flex-direction": "row",
|
||
// "flex-wrap": "wrap",
|
||
// margin: "auto",
|
||
// width: `${d.width}px`,
|
||
// height: `${d.height}px`,
|
||
// border: "0px solid #ccc",
|
||
// padding: "1px",
|
||
// "font-family": style.fontFamily,
|
||
// //"letter-spacing": "0.1em",
|
||
// "column-gap": `${d.textSpan}px`,
|
||
// //"row-gap": "1px",
|
||
// "align-items": "center",
|
||
// "justify-content": "start",
|
||
//});
|
||
|
||
//mid.forEach(z => {
|
||
// //let info = self.getTextInfo(z, newFontSize);
|
||
// //let spanWidth = mid.length > 1 ? newFontSize : d.width
|
||
// //let height = d.textHeight;
|
||
// //if (info.width >= d.textHeight) {
|
||
// // height = d.height;
|
||
// //}
|
||
// //let $span = $(`<span class="nameSpan">${z}</span>`).css({
|
||
// // display: "block",
|
||
// // "min-height": `${height}px`,
|
||
// // "max-height": `${d.height}px`,
|
||
// // "max-width": `${rowWidth}px`,
|
||
// // //width: `${newFontSize}px`,
|
||
// // "text-align": "justify",
|
||
// // "text-align-last": "justify",
|
||
// // //"margin-bottom": "20px",
|
||
// // "margin-left": "5px",
|
||
// // "text-justify": "inter-character",
|
||
// // "white-space": "pre-line",
|
||
// // //"justify-content":"center"
|
||
// //});
|
||
// // 為「這個名字」獨立計算出專屬的最完美字體大小
|
||
// let indSize = this.calcIndividualFontSize(z, d, colWidth);
|
||
// // 2. 判斷是否發生了折行 (名字長度 * 單字像素高度 > 分配到的最大高度)
|
||
// let s_px = indSize * 1.333;
|
||
// let isMultiLine = (z.length * s_px) > Number(d.textHeight);
|
||
|
||
// // 3. 根據是否折行動態決定對齊方式
|
||
// // 單行:分散對齊 (justify) / 多行:置中對齊 (center) 或是改為 靠上對齊 (start)
|
||
// let alignStyle = isMultiLine ? "start" : "justify";
|
||
// let $span = $(`<span style="font-size: ${indSize}pt;">${z}</span>`).css({
|
||
// display: "block",
|
||
// "min-height": `${d.textHeight}px`,
|
||
// "max-height": `${d.height}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"
|
||
// });
|
||
// $namelist.append($span);
|
||
//});
|
||
return $namelist;
|
||
},
|
||
renderLiveList(names, el) {
|
||
let h = Number(el.height);
|
||
let w = Number(el.width);
|
||
let th = Number(el.textHeight) || h;
|
||
|
||
// 1. 單純計算總共有幾行 (列)
|
||
let namesPerCol = Math.floor(h / th);
|
||
if (namesPerCol < 1) namesPerCol = 1;
|
||
|
||
let cols = Math.ceil(names.length / namesPerCol); // 總行數
|
||
|
||
// 2. 根據行數,縮小字體直到總寬度放得下
|
||
let s = Number(el.fontSize);
|
||
while (s >= 4) {
|
||
let s_px = s * (4 / 3); // pt 轉 px
|
||
let lh = 1.1; // 行高
|
||
let gap = 0; // 對應 span 的 margin-left
|
||
|
||
// 總寬度 = 總行數 * (單行像素寬度 + 間距)
|
||
let totalWidth = cols * (s_px * lh + gap);
|
||
|
||
// 放得下就停止縮小
|
||
if (totalWidth <= w) {
|
||
break;
|
||
}
|
||
s -= 1;
|
||
}
|
||
|
||
if (s < 4) s = 4;
|
||
|
||
// 3. 建立外層 Flexbox 容器
|
||
let $namelist = $(`<div class='liveList'></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 #ccc",
|
||
padding: "0px",
|
||
"font-family": el.fontFamily,
|
||
"column-gap": `${el.textSpan}px`,
|
||
});
|
||
|
||
let self = this;
|
||
names.forEach(name => {
|
||
$namelist.append(self.renderLiveSpan(name, el, s));
|
||
});
|
||
//let $namelist = $(`<div class='liveList'></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": el.fontFamily,
|
||
// //"letter-spacing": "0.1em",
|
||
// "column-gap": `${el.textSpan}px`,
|
||
// //"row-gap": "1px",
|
||
// "align-items": "center",
|
||
//});
|
||
//let self = this;
|
||
//names.forEach(n => {
|
||
// $namelist.append(self.renderLiveSpan(n, el,rowWidth))
|
||
//})
|
||
return $namelist;
|
||
},
|
||
renderLiveSpan(name, el, fontSize) {
|
||
let th = Number(el.textHeight) || Number(el.height);
|
||
|
||
return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({
|
||
display: "block",
|
||
"min-height": `${th}px`, // ★ 固定分配高度,交給 Flexbox 自動排版
|
||
"max-height": `${el.height}px`,
|
||
width: `${fontSize * 1.33}`,
|
||
//"line-height": "1.1",
|
||
"text-align": "justify", // 統一分散對齊
|
||
"text-align-last": "justify",
|
||
//"margin-left": "5px",
|
||
"text-justify": "inter-character",
|
||
"word-break": "break-all",
|
||
"white-space": "pre-line"
|
||
});
|
||
//return $(`<span class="liveSpan">${name}</span>`).css({
|
||
// display: "block",
|
||
// "min-height": `${el.textHeight}px`,
|
||
// "max-height": `${el.height}px`,
|
||
// width: `${colWidth}px`,
|
||
// height: `${el.textHeight}px`,
|
||
// "text-align": "justify",
|
||
// "text-align-last": "justify",
|
||
// //"margin-bottom": "10px",
|
||
// "text-justify": "inter-character",
|
||
// "white-space": "pre-line"
|
||
//});
|
||
},
|
||
// 品字佈局邏輯:一上二下
|
||
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="rosterList"></div>`).css(
|
||
{
|
||
width: el.width, height: el.height,
|
||
"writing-mode": "vertical-rl", /* 直書 */
|
||
display: "flex",
|
||
"flex-direction": "row", /* 上下分層 (Top / Bottom) */
|
||
"align-items": "center", /* 左右置中對齊 */
|
||
"justify-content": "start",
|
||
gap: "20px",
|
||
});
|
||
h.append(this.renderNameGroups(top, el));
|
||
if (bot.length) h.append(this.renderNameGroups(bot, el));
|
||
return h
|
||
},
|
||
renderCombined(mid, d) {
|
||
ancestorFontSize = d.fontSize;
|
||
newFontSize = d.fontSize;
|
||
const parts = mid.join("\n").split('\n');
|
||
html = `<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>`;
|
||
return html
|
||
},
|
||
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 class="" style="letter-spacing: 10px;text-align: justify; text-align-last: justify;">${x}</div>`))//font-size:${el.fontSize}pt;
|
||
})
|
||
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 (dStyle.elementID === 'title1') {//表示是一般正名,也就是牌位正中間的一般格式,排法是上下上下
|
||
//console.log("shit:", txtContent.join(","), (Math.ceil(txtContent.length / 2) * font.width) * 2, parseFloat(elementWidth))
|
||
if ((Math.ceil(txtContent.length / 2) * font.width) * 2 > parseFloat(elementWidth)) {
|
||
let newfontSize = parseInt(fontSize) - 1;
|
||
if (newfontSize <= 10) {
|
||
return newfontSize;
|
||
}
|
||
//console.log("newfontSize:",newfontSize)
|
||
return this.scaleFontSize(txtContent, newfontSize, elementWidth);
|
||
}
|
||
return fontSize;
|
||
},
|
||
getTextInfo(text, font) {
|
||
const canvas = document.createElement("canvas");
|
||
const context = canvas.getContext("2d");
|
||
context.font = font;
|
||
const metrics = context.measureText(text);
|
||
return metrics
|
||
},
|
||
getTextWidth(text, font) {
|
||
const canvas = document.createElement("canvas");
|
||
const context = canvas.getContext("2d");
|
||
context.font = font;
|
||
const metrics = context.measureText(text);
|
||
|
||
return metrics;
|
||
},
|
||
getTextSize(txt, fontsize) {
|
||
let span = document.createElement("span");
|
||
span.style.visibility = "hidden";
|
||
span.style.position = "absolute";
|
||
span.style.fontSize = fontsize + "px";
|
||
span.style.fontFamily = "";
|
||
span.innerText = txt;
|
||
document.body.appendChild(span);
|
||
let result = {
|
||
width: span.offsetWidth,
|
||
height: span.offsetHeight
|
||
}
|
||
document.body.removeChild(span);
|
||
return result;
|
||
},
|
||
filterToSel() {
|
||
let allList = document.querySelector(".canselectItem").querySelectorAll("li"); //$(".canselectItem").querySelectorAll("li");
|
||
allList.forEach(x => {
|
||
let curr = $(x).children().eq(1);
|
||
x.setAttribute("style", "display:none")
|
||
$(x).css("style", "display:none")
|
||
if (curr && curr.text().includes($("#filter_txt").val())) {
|
||
x.setAttribute("style", "display:")
|
||
} else {
|
||
|
||
}
|
||
});
|
||
},
|
||
async saveFamily() {
|
||
let self = this;
|
||
if (this.follower_id == "") {
|
||
//console.log("follower_id:", this.follower_id);
|
||
return false
|
||
}
|
||
|
||
let pro_order_detail =
|
||
{
|
||
// num: this.editedItem.num,//原先載入時的家人num
|
||
//f_num: this.editedItem.f_num_selected.val, //儲存時的家人num
|
||
//leader: this.follower_id,
|
||
//appellation_id: this.editedItem.appellation_id_selected.val, //儲存時的稱謂
|
||
follower_num: this.follower_id,
|
||
fam_name: $("#family_txt").val(),
|
||
fam_title:$("#title_txt").val(),
|
||
deceased: $("#deceased_sel").val() == "False" ? false : true,
|
||
num: 0
|
||
|
||
}
|
||
this.modal.toggle();
|
||
//$(this.modal).toggle();
|
||
//$(this.modal).addClass("hide");
|
||
//console.log(pro_order_detail);
|
||
//console.log(this.modal);
|
||
//$(this.modal).hide();
|
||
axios
|
||
.post('/api/tablet/saveFamily', pro_order_detail)
|
||
.then(response => {
|
||
//console.log(response);
|
||
if (response.status == 200 && response.data.result == "Y") {
|
||
let data = response.data;
|
||
self.familyMembers.push(data.data);
|
||
//console.log(self.familyMembers);
|
||
//alert("新增成功");
|
||
self.modal.toggle();
|
||
|
||
if (self.selectedElement) {
|
||
let element = $(self.selectedElement).children().first();
|
||
self.renderItem(element);
|
||
}
|
||
$("#toast_body").html(`${"新增成功"}`)
|
||
$("#family_txt").val("");
|
||
$("#title_txt").val("");
|
||
$("#deceased_sel").val("False")
|
||
self.toast.show();
|
||
} else {
|
||
|
||
}
|
||
});
|
||
},
|
||
async save(event) {
|
||
event.preventDefault()
|
||
//console.log("tabletItem:", designer.tabletItem);
|
||
//拚牌位的內容
|
||
//console.log("AA")
|
||
let self = this;
|
||
let tablet = {
|
||
mid_items: designer.family_deceased_N_selected,
|
||
left_items: designer.family_deceased_Y_selected,
|
||
style: designer.selectStyle.style,
|
||
mid_left: designer.family_left_title,
|
||
mid_right: designer.family_right_title,
|
||
addr_items: designer.family_address
|
||
}
|
||
//console.log("BB")
|
||
var pro_order_detail =
|
||
{
|
||
num: designer.tabletItem.num <= 0 ? null : designer.tabletItem.num,
|
||
order_no: designer.tabletItem.order_no,
|
||
parent_num: designer.tabletItem.parent_num,
|
||
actItem_num: designer.tabletItem.actitem_num_selected.val,
|
||
f_num: designer.tabletItem.f_num_selected.val,
|
||
f_num_tablet: JSON.stringify(tablet), //designer.tabletItem.f_num_tablet,//牌位內容
|
||
print_id: designer.tabletItem.print_id,
|
||
address: designer.tabletItem.address,
|
||
from_id: designer.tabletItem.from_id_selected.val,
|
||
from_id_tablet: designer.tabletItem.from_id_tablet,
|
||
start_date: designer.tabletItem.category == 1 ? designer.tabletItem.start_date : null,
|
||
due_date: designer.tabletItem.category == 1 ? designer.tabletItem.due_date : null,
|
||
extend_date: designer.tabletItem.category == 1 ? designer.tabletItem.extend_date : null,
|
||
price: designer.tabletItem.price,
|
||
qty: designer.tabletItem.qty,
|
||
//total: this.editedItem.total,
|
||
keyin1: designer.tabletItem.keyin1_selected.val,
|
||
demo: designer.tabletItem.demo,
|
||
pay: designer.tabletItem.pay,
|
||
pay_date: designer.tabletItem.pay_date,
|
||
customize_data: designer.tabletItem.customize_data,
|
||
style: designer.selectStyle.style
|
||
}
|
||
//console.log("CC")
|
||
await axios
|
||
.post('/api/order/SaveDetailData', pro_order_detail)
|
||
.then(response => {
|
||
|
||
let tablet_data = {
|
||
mid_items: self.family_deceased_N_selected,
|
||
left_items: self.family_deceased_Y_selected,
|
||
style: self.selectStyle.style,
|
||
mid_left: self.family_left_title,
|
||
mid_right: self.family_right_title,
|
||
addr_items: self.family_address
|
||
}
|
||
const ret = {
|
||
source: 'editorNew.btn.click',
|
||
data: {
|
||
tabletItem: this.tabletItem,
|
||
tablet_data: tablet_data,
|
||
style: self.selectStyle.style
|
||
}
|
||
};
|
||
window.parent.postMessage(ret, '/');
|
||
//$.toast({
|
||
// heading: 'Success',
|
||
// text: 'Your action was completed successfully!',
|
||
// showHideTransition: 'slide', // fade, slide, plain
|
||
// icon: 'success', // info, warning, error, success
|
||
// position: 'top-left',
|
||
// loaderBg: '#4CAF50', // loader background color
|
||
// hideAfter: 3000 // auto close after 3 seconds
|
||
//});
|
||
//$(".toast-body").empty();
|
||
//$(".toast-body").html("ok");
|
||
//this.toast.show();
|
||
});
|
||
|
||
//console.log("pro_order_detail:",pro_order_detail);
|
||
},
|
||
reset() {
|
||
designer.code = { style: [] };
|
||
designer.selected = {
|
||
style: ""
|
||
};
|
||
designer.actitem = "";
|
||
designer.pageSize = "";
|
||
designer.defaultStyle = "";
|
||
designer.isAllSelected = false;
|
||
designer.dialog = false;
|
||
designer.addDialog = false; // 新增項目對話框
|
||
designer.addDialogType = ''; // 'Y' 或 'N'
|
||
designer.newItemText = ''; // 新增項目的文字
|
||
designer.noSpace = true; // 不加空格 checkbox(預設勾選)
|
||
designer.deleteDialog = false; // 確認刪除對話框
|
||
designer.deleteIndex = -1; // 要刪除的 index
|
||
designer.deleteType = ''; // 'Y' 或 'N'
|
||
designer.deleteMemberName = ''; // 要刪除的名稱(顯示用)
|
||
designer.phrases = []; // 常用片語
|
||
designer.familyMembers = []; // 親友名單
|
||
designer.tabletItem = {}; // 傳入的資料(信眾/牌位資訊)
|
||
designer.item = {};
|
||
designer.family_deceased_Y_selected = []; // 超渡/超薦/超冤名單
|
||
designer.family_deceased_N_selected = []; // 消災/陽上名單
|
||
designer.family_left_title = [];
|
||
designer.family_right_title = [];
|
||
designer.allStyle = [];
|
||
designer.allStyleDetails = [];
|
||
designer.allSize = [];
|
||
designer.tabletElement = [];
|
||
designer.http_host = "";
|
||
designer.printData = [];
|
||
designer.selectedElement = {};
|
||
designer.selectStyle = {};
|
||
designer.properTitle = [];//正名
|
||
designer.leftProperTitle = [];//左正名
|
||
designer.rightProperTitle = [];//右正名
|
||
designer.address = "";//地址
|
||
designer.alive = [];//陽上
|
||
designer.tabletType = "A";
|
||
designer.follower_id = "";
|
||
$(".printArea").empty();
|
||
$(".selectItem").empty();
|
||
$(".canselectItem").empty();
|
||
},
|
||
async resetScreen() {
|
||
$(".printArea").empty();
|
||
$(".selectItem").empty();
|
||
$(".canselectItem").empty();
|
||
}
|
||
|
||
}
|
||
|
||
$(() => $(window).on("message", function (event) {
|
||
//designer.toast.show();
|
||
//designer.reset();
|
||
let e = event.originalEvent;
|
||
//console.log("QOO");
|
||
//console.log("傳過來了");
|
||
if (e.origin === window.parent.location.origin) {
|
||
//console.log('editor.html - origin check passed');
|
||
//console.log('editor.html - received data:', event.data);
|
||
const receivedData = e.data;
|
||
// console.log("receivedData:", receivedData)
|
||
designer.family_deceased_Y_selected = [];
|
||
designer.family_deceased_N_selected = [];
|
||
designer.family_left_title = [];
|
||
designer.family_right_title = [];
|
||
designer.http_host = receivedData.host;
|
||
designer.follower_id = receivedData.follower_id;
|
||
//console.log("三小:", receivedData.tabletItem)
|
||
if (receivedData.tabletItem) {
|
||
//console.log('editor.html - updating tabletItem:', receivedData.tabletItem);
|
||
designer.tabletItem = receivedData.tabletItem;
|
||
if (receivedData.familyMembers) {
|
||
designer.familyMembers = receivedData.familyMembers; // 更新 familyMembers
|
||
//console.log("app mounted, window message: ", this.familyMembers, receivedData);
|
||
}
|
||
//if (receivedData.tabletItem) {
|
||
// designer.tabletItem = receivedData.tabletItem;
|
||
//}
|
||
//console.log("actitem:", receivedData.tabletItem.actitem_num_selected.val);
|
||
|
||
if (receivedData.tabletItem.style) {
|
||
designer.selected.style = receivedData.tabletItem.style;
|
||
} else {
|
||
designer.selected.style = "";
|
||
}
|
||
|
||
designer.actitem = receivedData.tabletItem.actitem_num_selected.val;
|
||
|
||
//console.log("actitem:",receivedData.tabletItem.actitem_num_selected.val);
|
||
// 處理 f_num_tablet 資料
|
||
if (designer.tabletItem.f_num_tablet) {
|
||
try {
|
||
const data = JSON.parse(designer.tabletItem.f_num_tablet);
|
||
|
||
if (designer.item_type === 'B') {
|
||
// B類型:超渡、超薦等
|
||
designer.family_deceased_Y_selected = data.mid_items || [];
|
||
designer.family_deceased_N_selected = data.left_items || [];
|
||
|
||
designer.family_deceased_Y_selected.forEach(item => {
|
||
if (item.IsShuWen === undefined) {
|
||
//Vue.set(item, 'IsShuWen', false);
|
||
}
|
||
});
|
||
if (designer.family_deceased_Y_selected.length > 0) {
|
||
designer.isAllSelected = this.family_deceased_Y_selected.every(member => member.IsShuWen === true);
|
||
} else {
|
||
designer.isAllSelected = false;
|
||
}
|
||
|
||
} else {
|
||
// A類型:消災、陽上等
|
||
designer.family_deceased_Y_selected = [];
|
||
designer.family_deceased_N_selected = data.mid_items || [];
|
||
|
||
designer.family_deceased_N_selected.forEach(item => {
|
||
if (item.IsShuWen === undefined) {
|
||
//Vue.set(item, 'IsShuWen', false);
|
||
}
|
||
});
|
||
if (designer.family_deceased_Y_selected.length > 0) {
|
||
designer.isAllSelected = designer.family_deceased_N_selected.every(member => member.IsShuWen === true);
|
||
|
||
} else {
|
||
designer.isAllSelected = false;
|
||
}
|
||
//console.log(this.family_deceased_N_selected)
|
||
}
|
||
} catch (e) {
|
||
console.error('解析牌位資料時發生錯誤:', e);
|
||
}
|
||
}
|
||
designer.resetScreen();
|
||
designer.getActItem().then(() => delay(300)).then(() => designer.getData());
|
||
|
||
|
||
}
|
||
}
|
||
//console.log(e.origin, window.parent.location.origin)
|
||
})
|
||
);
|
||
function delay(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
$(() => designer.init().then(() => designer.getTabletElement().
|
||
then(() => designer.getPaperSize().
|
||
then(() => designer.getTabletStyles().
|
||
then(designer.getAllStyleDetails())))));
|
||
</script>
|
||
</body>
|
||
|
||
</html> |