Files
17168ERP/web/admin/item/TabletDesigner.aspx
2026-03-02 17:52:21 +08:00

346 lines
16 KiB
Plaintext
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.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TabletDesigner.aspx.cs" Inherits="admin_item_TabletDesigner" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" %>
<%@ Register Src="~/admin/_uc/alert.ascx" TagPrefix="uc1" TagName="alert" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
<%-- <style type="text/css">
@import "css/styles.css";
@import "css/tablet-design.css";
@import "css/floating.css";
</style>--%>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<div class="d-flex overflow-hidden">
<div class="p-2 bg-dark text-white floting-box" style="width: 250px; left: 20px; top: 80px;">
<h6 class="border-bottom pb-2">設計工具箱</h6>
<div class="form-floating mb-3">
<select class="form-select form-select-sm mb-2 " onchange="Designer.changePaper()" id="paperSize">
<option value="">請選擇</option>
</select>
<label for="paperSize">尺寸</label>
</div>
<div id="paperOrientation" onclick="Designer.changeOrientation()" class="mb-2">
<span></span>
</div>
<span class="btn btn-sm btn-outline-light w-100 mb-2" onclick="Designer.displayItem('address')">地址欄</span>
<span class="btn btn-sm btn-outline-light w-100 mb-2" onclick="Designer.displayItem('title1')">牌位正名</span>
<span class="btn btn-sm btn-outline-light w-100 mb-2" onclick="Designer.displayItem('titletriangle')">品字名單</span>
<span class="btn btn-sm btn-outline-info w-100 mb-2" onclick="Designer.displayItem('combined')">正名合併置中</span>
<span class="btn btn-sm btn-outline-info w-100 mb-2" onclick="Designer.displayItem('alive')">陽上報恩</span>
<div>
<span class="btn btn-sm btn-outline-secondary" onclick="Designer.clickBackend()">
<i class="bi bi-upload me-1"></i> 上傳自訂底圖 (PNG/JPG)
</span>
<input id="backendInp" type="file" accept="image/png, image/jpeg" style="display: none"
>
</div>
<hr>
</div>
<div class="canvas-area flex-grow-1 overflow-auto d-flex flex-column align-items-center position-relative" id="canvas">
<div class="tablet-paper" >
<%--<div class="tablet-element vertical-text">嘿嘿</div>--%>
</div>
</div>
<div id="attr-box" class="d-none p-2 bg-dark text-whit floting-box" style="top: 80px; right: 20px">
<div class="form-floating mb-3">
<textarea id="inp-text" class="form-control form-control-sm mb-2" rows="5"></textarea>
<label class="small" for="inp-text">內容文字</label>
</div>
<div class="form-floating mb-3">
<input type="number" id="inp-size" class="form-control form-control-sm mb-2">
<label class="small" for="inp-size">字體大小 (pt)</label>
</div>
<div class="form-floating mb-3">
<input type="number" id="2offset" class="form-control form-control-sm mb-2">
<label class="small" for="2offset">二行偏移量</label>
</div>
<div class="form-floating mb-3">
<input type="number" id="3offset" class="form-control form-control-sm mb-2">
<label class="small" for="3offset">三行偏移量</label>
</div>
<div class="form-floating mb-3">
<input type="number" id="4offset" class="form-control form-control-sm mb-2">
<label class="small" for="4offset">四行偏移量</label>
</div>
<div class="form-floating mb-3">
<div class="row">
<div class="col-6">
<input type="number" id="xPosition" class="form-control form-control-sm mb-2">
<label class="small" for="xPosition">X</label>
</div>
<div class="col-6">
<input type="number" id="yPosition" class="form-control form-control-sm mb-2">
<label class="small" for="yPosition">Y</label>
</div>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="footer_script" runat="Server">
<%-- <script src="../../js/jquery-4.0.0.min.js"></script>
<script src="jquery-ui/jquery-ui.js"></script>--%>
<script>
const Designer = {
elements: [],
activeId: null,
paper: { width: 100, height: 272 },
rosterLimit: 8,
allSize: [
{ name: "A3", width: 297, height: 420, selected :""},
{ name: "A4", width: 210, height: 297, selected :""},
{ name: "B4", width: 257, height: 364, selected :""},
{ name: "red", width: 100, height: 272,selected:"selected" },
],
orientation: "portrait",
init() {
//$(".tablet-element").draggable({});
this.bindEvents();
this.loadConfig();
this.bindBackend();
this.allSize.forEach(x => {
$("#paperSize").append("<option value='" + x.name + "' " + x.selected + ">" + x.width + "*" + x.height + "</option>")
})
$("#paperOrientation").html("<span>直向</span>")
},
changeOrientation() {
this.orientation = (this.orientation === "portrait" ? "land" : "portrait");
console.log(this.orientation);
$("#paperOrientation").empty();
let ori = this.orientation == "portrait" ? "直向" : "橫向";
console.log(this.orientation,ori)
$("#paperOrientation").html("<span>" +ori + "</span>");
let paperSize = $("#paperSize").val()
let size = this.allSize.find(x => x.name == paperSize)
console.log(paperSize, size);
this.paper.width = this.orientation == "portrait" ? size.width : size.height;
this.paper.height = this.orientation == "portrait" ? size.height : size.width;
$(".tablet-paper").css({
"background-color": "red",
width: this.paper.width + "mm",
height: this.paper.height + "mm",
});
},
changePaper() {
let paperSize = $("#paperSize").val()
let size = this.allSize.find(x => x.name == paperSize)
$(".tablet-paper").css({
"background-color": "red",
width: size.width + "mm",
height: size.height + "mm",
});
},
bindEvents() {
// 文字變更連動渲染
$('#inp-text').on('input', (e) => this.updateActive('text', $(e.target).val()));
$('#inp-size').on('input', (e) => this.updateActive('fontSize', $(e.target).val()));
$(document).on('mousedown', '.tablet-element', (e) => {
e.stopPropagation();
this.select($(e.currentTarget).attr('id'));
});
},
loadConfig() {
$.ajax({
type: "POST", url: "TabletDesigner.aspx/GetConfig", contentType: "application/json",
success: (res) => {
const data = JSON.parse(res.d);
this.elements = data.elements;
this.render();
}
});
},
// 處理分頁渲染邏輯
render() {
//const $canvas = $('#canvas').empty();
const roster = this.elements.find(e => e.type === 'roster');
const names = roster ? roster.text.split('\n').filter(s => s.trim()) : [];
const pages = Math.max(1, Math.ceil(names.length / this.rosterLimit));
//for (let p = 0; p < pages; p++) {
// const $paper = $('<div class="tablet-paper"></div>')
// .css({ "background-color": "red", width: this.paper.width + 'mm', height: this.paper.height + 'mm' });
let $paper = $(".tablet-paper").css({ "background-color": "red", width: this.paper.width + 'mm', height: this.paper.height + 'mm' });
//const slice = names.slice(p * this.rosterLimit, (p + 1) * this.rosterLimit);
const slice = names.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit);
this.elements.forEach(el => {
$paper.append(this.createEl(el, slice));
});
//$canvas.append($paper);
//}
this.makeDraggable();
},
createEl(el, slice) {
let html = '';
// 1. 智慧名單渲染
if (el.type === 'roster') {
html = this.renderRoster(slice, el);
}
// 2. 正名合併置中
else if (el.type === 'combined-center') {
const parts = el.text.split('\n');
html = `<div class="ancestor-wrapper" >
<span class="main-name" >${parts[0] || ''}</span>
<span class="sub-text">${parts[1] || ''}</span>
</div>`;
}
// 3. 地址數字轉橫排
else if (el.id === 'address') {
html = el.text.replace(/(\d+)/g, '<span class="tate-chu-yoko">$1</span>');
}
else {
html = el.text;
}
return $(`<div class="tablet-element vertical-text ${this.activeId === el.id ? 'selected' : ''}" id="${el.id}"></div>`)
.css({
left: el.x, top: el.y, fontSize: el.style.fontSize + 'pt', fontFamily: el.style.fontFamily, "z-index": 9999, visibility: el.style.visibility
})
.html(html);
},
// 品字佈局邏輯:一上二下
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.style.fontSize);
let h = `<div class="roster-container" style="gap:${el.style.itemSpacing || 20}px">`;
h += `<div class="name-group">${top.map(n => `<div class="roster-name" style="font-size:${size}pt">${n}</div>`).join('')}</div>`;
if (bot.length) h += `<div class="name-group">${bot.map(n => `<div class="roster-name" style="font-size:${size}pt">${n}</div>`).join('')}</div>`;
return h + `</div>`;
},
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;
},
// 拖動後同步所有頁面的位置
makeDraggable() {
const self = this;
$(".tablet-element").draggable({
start(e, ui) {
console.log("gogogogo");
},
stop(e, ui) {
const id = $(this).attr('id');
const el = self.elements.find(x => x.id === id);
el.x = ui.position.left;
el.y = ui.position.top;
$(`.tablet-element[id="${id}"]`).css({ left: el.x, top: el.y });
}
});
},
select(id) {
this.activeId = id;
const el = this.elements.find(x => x.id === id);
$('#attr-box').removeClass('d-none');
$('#inp-text').val(el.text);
$('#inp-size').val(el.style.fontSize);
$('#xPosition').val(el.x);
$("#yPosition").val(el.y);
//this.render();
},
updateActive(key, val) {
const el = this.elements.find(x => x.id === this.activeId);
if (key === 'fontSize') el.style.fontSize = parseFloat(val);
else el[key] = val;
//this.render();
},
displayItem(id) {
console.log(id);
let el = this.elements.find(x => x.id === id);
console.log("before:",el);
el.style.visibility = el.style.visibility == "hidden" ? "" : "hidden";
console.log("after:", el);
$("#" + id).css({
left: el.x, top: el.y, fontSize: el.style.fontSize + 'pt', fontFamily: el.style.fontFamily, "z-index": 9999, visibility: el.style.visibility
})
},
addMulti(prefix, count) {
for (let i = 1; i <= count; i++) {
const id = `${prefix}-${Date.now()}-${i}`;
this.elements.push({
id, type: 'title', text: `正名${i}`, x: 100 - (i * 30), y: 80,
style: { fontSize: 24, fontFamily: 'Kaiti', isVertical: true }
});
}
//this.render();
},
addCombined() {
this.stopPropagation();
this.elements.push({
id: 'combined-' + Date.now(), type: 'combined-center', text: '林張\n氏歷代祖先', x: 130, y: 80,
style: { fontSize: 24, fontFamily: 'Kaiti', isVertical: true }
});
//this.render();
},
addElement() {
console.log("QQQ");
},
wrapImageInSvg(imageBase64) {
// 建立一個簡單的 SVG 字串,將圖片鋪滿
// preserveAspectRatio="none" 確保圖片會拉伸填滿 SVG 容器
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" preserveAspectRatio="none">
<image href="${imageBase64}" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" />
</svg>
`.trim();
// 轉成 Data URI 格式
// 注意SVG 內容若包含特殊字元需編碼,但 base64 圖片通常是安全的
// 為了保險,我們使用 encodeURIComponent 對 svgString 編碼
return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
},
clickBackend() {
$("#backendInp").click();
},
bindBackend() {
$("#backendInp").on('change', function (e) {
console.log($("#backendInp"))
const input = e.target;
if (input.files && input.files[0]) {
console.log("file:", input.files[0]);
const file = input.files[0];
// 1. 先讀取檔案轉成 Base64
const reader = new FileReader();
reader.onload = (e) => {
const base64Image = e.target.result; // 這會是 data:image/png;base64,xxxx...
// 2. 將 Base64 PNG 包裝成 SVG
//const svgDataUri = this.wrapImageInSvg(base64Image);
console.log(base64Image)
// 3. 設定到底圖
//this.service.setBackground(svgDataUri);
$(".tablet-paper").css({ 'background-image': 'url(' + base64Image + ')','background-size':'100% 100%' })
};
reader.readAsDataURL(file);
}
})
}
};
$(() => Designer.init());
</script>
</asp:Content>