總疏文
This commit is contained in:
@@ -0,0 +1,558 @@
|
||||
<template>
|
||||
<div id="control-panel">
|
||||
|
||||
<div class="control-group">
|
||||
<label>紙張大小</label><select id="paper-size" onchange="updateLayout()">
|
||||
<option value="A4" data-w="210" data-h="297" selected>A4</option>
|
||||
<option value="A3" data-w="297" data-h="420">A3</option>
|
||||
<option value="B5" data-w="176" data-h="250">B5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>紙張方向</label><select id="paper-orientation" onchange="updateLayout()">
|
||||
<option value="portrait" selected>直向</option>
|
||||
<option value="landscape">橫向</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>一次修改全部行距: <span id="global-margin-lbl">10</span>px</label>
|
||||
<div class="adjust-row">
|
||||
<button onclick="adjustAllMargins(-1)">
|
||||
-
|
||||
</button><input type="range"
|
||||
id="global-margin-slide"
|
||||
min="0"
|
||||
max="80"
|
||||
value="10"
|
||||
oninput="changeAllMargins(this.value)" /><button onclick="adjustAllMargins(1)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<p style="
|
||||
font-size: 11px;
|
||||
color: #c0392b;
|
||||
font-weight: 700;
|
||||
background: #fde8e7;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
">
|
||||
💡 提示:在右側文字上【按滑鼠右鍵】觸發設定,面板固定顯示在左下角!
|
||||
</p>
|
||||
<hr />
|
||||
<button class="action-btn"
|
||||
style="background: #28a745"
|
||||
onclick="saveData()">
|
||||
1. 儲存至資料庫
|
||||
</button>
|
||||
<button class="action-btn" onclick="window.print()">
|
||||
2. 產生 PDF / 列印
|
||||
</button>
|
||||
</div>
|
||||
<div id="preview-container" onclick="hideFloatingMenu(event)">
|
||||
<div id="paper-preview">
|
||||
<div id="editor"
|
||||
contenteditable="true"
|
||||
oncontextmenu="rightClickLineTrack(event)">
|
||||
<div style="
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
margin-left: 25px;
|
||||
">
|
||||
中元普渡祈福文疏
|
||||
</div>
|
||||
<div></div>
|
||||
<div style="font-size: 16px; letter-spacing: 4px; margin-left: 12px">
|
||||
今逢佳節齋供養,花果盡是素佳餚。
|
||||
</div>
|
||||
<div style="font-size: 16px; margin-left: 12px">
|
||||
誠心祈求風調雨順、國泰民安。
|
||||
</div>
|
||||
<div></div>
|
||||
<div style="font-size: 14px; align-items: flex-end; margin-left: 30px">
|
||||
歲次天運民國一一五[YEAR]年七[MONTH]月十五[DAY]日。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="line-floating-menu" onclick="event.stopPropagation()">
|
||||
<h4 id="menu-header">✥ 拖拉此處移動面板</h4>
|
||||
<div class="control-group">
|
||||
<label>字型大小: <span id="f-size-lbl">16</span>px</label>
|
||||
<div class="adjust-row">
|
||||
<button onclick="
|
||||
stepSingleLine(
|
||||
'fontSize',
|
||||
'f-size-slide',
|
||||
'f-size-lbl',
|
||||
-1,
|
||||
'px',
|
||||
)
|
||||
">
|
||||
-
|
||||
</button><input type="range"
|
||||
id="f-size-slide"
|
||||
min="12"
|
||||
max="60"
|
||||
value="16"
|
||||
oninput="
|
||||
adjustLineStyle(
|
||||
'fontSize',
|
||||
this.value + 'px',
|
||||
'f-size-lbl',
|
||||
this.value,
|
||||
)
|
||||
" /><button onclick="
|
||||
stepSingleLine(
|
||||
'fontSize',
|
||||
'f-size-slide',
|
||||
'f-size-lbl',
|
||||
1,
|
||||
'px',
|
||||
)
|
||||
">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>字距: <span id="f-space-lbl">2</span>px</label>
|
||||
<div class="adjust-row">
|
||||
<button onclick="
|
||||
stepSingleLine(
|
||||
'letterSpacing',
|
||||
'f-space-slide',
|
||||
'f-space-lbl',
|
||||
-1,
|
||||
'px',
|
||||
)
|
||||
">
|
||||
-
|
||||
</button><input type="range"
|
||||
id="f-space-slide"
|
||||
min="0"
|
||||
max="25"
|
||||
value="2"
|
||||
oninput="
|
||||
adjustLineStyle(
|
||||
'letterSpacing',
|
||||
this.value + 'px',
|
||||
'f-space-lbl',
|
||||
this.value,
|
||||
)
|
||||
" /><button onclick="
|
||||
stepSingleLine(
|
||||
'letterSpacing',
|
||||
'f-space-slide',
|
||||
'f-space-lbl',
|
||||
1,
|
||||
'px',
|
||||
)
|
||||
">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>行距(與左側): <span id="f-margin-lbl">10</span>px</label>
|
||||
<div class="adjust-row">
|
||||
<button onclick="
|
||||
stepSingleLine(
|
||||
'marginLeft',
|
||||
'f-margin-slide',
|
||||
'f-margin-lbl',
|
||||
-1,
|
||||
'px',
|
||||
)
|
||||
">
|
||||
-
|
||||
</button><input type="range"
|
||||
id="f-margin-slide"
|
||||
min="0"
|
||||
max="80"
|
||||
value="10"
|
||||
oninput="
|
||||
adjustLineStyle(
|
||||
'marginLeft',
|
||||
this.value + 'px',
|
||||
'f-margin-lbl',
|
||||
this.value,
|
||||
)
|
||||
" /><button onclick="
|
||||
stepSingleLine(
|
||||
'marginLeft',
|
||||
'f-margin-slide',
|
||||
'f-margin-lbl',
|
||||
1,
|
||||
'px',
|
||||
)
|
||||
">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>垂直位置對齊</label>
|
||||
<div class="menu-btn-group">
|
||||
<button id="f-align-top"
|
||||
onclick="adjustLineAlign('flex-start', this)">
|
||||
上
|
||||
</button><button id="f-align-center"
|
||||
onclick="adjustLineAlign('center', this)">
|
||||
中
|
||||
</button><button id="f-align-bottom"
|
||||
onclick="adjustLineAlign('flex-end', this)">
|
||||
下
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
let currentTargetLine = null;
|
||||
function updateLayout() {
|
||||
const e = document.getElementById("paper-size"),
|
||||
t = document.getElementById("paper-orientation"),
|
||||
n = document.getElementById("paper-preview"),
|
||||
a = document.getElementById("print-style"),
|
||||
o = e.options[e.selectedIndex],
|
||||
i = parseFloat(o.getAttribute("data-w")),
|
||||
r = parseFloat(o.getAttribute("data-h")),
|
||||
d = e.value,
|
||||
l = t.value;
|
||||
let s = "portrait" === l ? i : r,
|
||||
c = "portrait" === l ? r : i;
|
||||
((n.style.width = s + "mm"),
|
||||
(n.style.height = c + "mm"),
|
||||
(a.innerHTML = `@media print { @page { size: ${d} ${l}; margin: 0mm; } }`),
|
||||
hideFloatingMenu());
|
||||
}
|
||||
function rightClickLineTrack(e) {
|
||||
s
|
||||
let t = e.target;
|
||||
const n = document.getElementById("editor");
|
||||
while (t && t.parentNode !== n && t !== n) {
|
||||
t = t.parentNode;
|
||||
}
|
||||
if (!t || t === n || "DIV" !== t.tagName) return;
|
||||
(e.preventDefault(),
|
||||
currentTargetLine &&
|
||||
currentTargetLine.classList.remove("selected-line"),
|
||||
(currentTargetLine = t),
|
||||
currentTargetLine.classList.add("selected-line"));
|
||||
const a = window.getComputedStyle(currentTargetLine),
|
||||
o = parseInt(a.fontSize) || 16;
|
||||
((document.getElementById("f-size-slide").value = o),
|
||||
(document.getElementById("f-size-lbl").innerText = o));
|
||||
const i = "normal" === a.letterSpacing ? 0 : parseInt(a.letterSpacing);
|
||||
((document.getElementById("f-space-slide").value = i),
|
||||
(document.getElementById("f-space-lbl").innerText = i));
|
||||
const r = parseInt(a.marginLeft) || 10;
|
||||
((document.getElementById("f-margin-slide").value = r),
|
||||
(document.getElementById("f-margin-lbl").innerText = r),
|
||||
document
|
||||
.querySelectorAll(".menu-btn-group button")
|
||||
.forEach((e) => e.classList.remove("active")),
|
||||
"flex-start" === a.alignItems &&
|
||||
document.getElementById("f-align-top").classList.add("active"),
|
||||
"center" === a.alignItems &&
|
||||
document.getElementById("f-align-center").classList.add("active"),
|
||||
"flex-end" === a.alignItems &&
|
||||
document.getElementById("f-align-bottom").classList.add("active"),
|
||||
(document.getElementById("line-floating-menu").style.display =
|
||||
"flex"));
|
||||
}
|
||||
function hideFloatingMenu(e) {
|
||||
if (
|
||||
e &&
|
||||
("preview-container" === e.target.id ||
|
||||
"paper-preview" === e.target.id)
|
||||
) {
|
||||
((document.getElementById("line-floating-menu").style.display =
|
||||
"none"),
|
||||
currentTargetLine &&
|
||||
currentTargetLine.classList.remove("selected-line"),
|
||||
(currentTargetLine = null));
|
||||
}
|
||||
}
|
||||
function adjustLineStyle(e, t, n, a) {
|
||||
currentTargetLine &&
|
||||
((currentTargetLine.style[e] = t),
|
||||
(document.getElementById(n).innerText = a));
|
||||
}
|
||||
function stepSingleLine(e, t, n, a, o) {
|
||||
if (currentTargetLine) {
|
||||
const i = document.getElementById(t);
|
||||
let r = parseInt(i.value) + a;
|
||||
(r < parseInt(i.min) && (r = parseInt(i.min)),
|
||||
r > parseInt(i.max) && (r = parseInt(i.max)),
|
||||
(i.value = r),
|
||||
adjustLineStyle(e, r + o, n, r));
|
||||
}
|
||||
}
|
||||
function changeAllMargins(e) {
|
||||
((document.getElementById("global-margin-lbl").innerText = e),
|
||||
document.querySelectorAll("#editor > div").forEach((t) => {
|
||||
t.style.marginLeft = e + "px";
|
||||
}));
|
||||
}
|
||||
function adjustAllMargins(e) {
|
||||
const t = document.getElementById("global-margin-slide");
|
||||
let n = parseInt(t.value) + e;
|
||||
(n < parseInt(t.min) && (n = parseInt(t.min)),
|
||||
n > parseInt(t.max) && (n = parseInt(t.max)),
|
||||
(t.value = n),
|
||||
changeAllMargins(n));
|
||||
}
|
||||
function adjustLineAlign(e, t) {
|
||||
currentTargetLine &&
|
||||
((currentTargetLine.style.alignItems = e),
|
||||
document
|
||||
.querySelectorAll(".menu-btn-group button")
|
||||
.forEach((e) => e.classList.remove("active")),
|
||||
t.classList.add("active"));
|
||||
}
|
||||
function saveData() {
|
||||
((document.getElementById("line-floating-menu").style.display = "none"),
|
||||
currentTargetLine &&
|
||||
currentTargetLine.classList.remove("selected-line"),
|
||||
console.log(document.getElementById("editor").innerHTML),
|
||||
alert("已成功擷取 HTML 結構至 Console!"));
|
||||
}
|
||||
window.onload = function () {
|
||||
updateLayout();
|
||||
};
|
||||
(function () {
|
||||
const e = document.getElementById("line-floating-menu"),
|
||||
t = document.getElementById("menu-header");
|
||||
let n = !1,
|
||||
a,
|
||||
o;
|
||||
(t.addEventListener("mousedown", (r) => {
|
||||
const i = e.getBoundingClientRect();
|
||||
((a = r.clientX - i.left),
|
||||
(o = r.clientY - i.top),
|
||||
r.target === t && ((n = !0), (e.style.position = "fixed")));
|
||||
}),
|
||||
document.addEventListener("mousemove", (t) => {
|
||||
if (n) {
|
||||
t.preventDefault();
|
||||
let i = t.clientX - a,
|
||||
r = t.clientY - o;
|
||||
((e.style.left = i + "px"),
|
||||
(e.style.top = r + "px"),
|
||||
(e.style.bottom = "auto"));
|
||||
}
|
||||
}),
|
||||
document.addEventListener("mouseup", () => (n = !1)));
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
@media print {
|
||||
@page {
|
||||
size: A4 portrait;
|
||||
margin: 0mm;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Microsoft JhengHei", sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#control-panel {
|
||||
width: 240px;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
select,
|
||||
input[type="range"] {
|
||||
padding: 6px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button.action-btn {
|
||||
padding: 12px;
|
||||
background: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#preview-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
background: #ddd;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#paper-preview {
|
||||
background: #fff;
|
||||
padding: 20mm;
|
||||
box-sizing: border-box;
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
}
|
||||
|
||||
#editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
writing-mode: vertical-rl;
|
||||
text-orient: upright;
|
||||
}
|
||||
|
||||
#editor > div {
|
||||
min-height: 1em;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-left: 10px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
#editor > div:hover {
|
||||
background: rgba(0, 123, 255, 0.05);
|
||||
}
|
||||
|
||||
#editor > div.selected-line {
|
||||
background: rgba(0, 123, 255, 0.1) !important;
|
||||
box-shadow: 0 0 0 1px #ff7675;
|
||||
}
|
||||
|
||||
#line-floating-menu {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 280px;
|
||||
background: #fff0f1;
|
||||
border: 2px solid #ff7675;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
z-index: 99999;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
#line-floating-menu h4 {
|
||||
margin: 0;
|
||||
color: #d63031;
|
||||
font-size: 14px;
|
||||
cursor: move;
|
||||
background: #ffe4e6;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.menu-btn-group {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.menu-btn-group button {
|
||||
flex: 1;
|
||||
padding: 6px;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.menu-btn-group button.active {
|
||||
background: #28a745;
|
||||
color: #fff;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.adjust-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.adjust-row button {
|
||||
padding: 4px 10px;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.adjust-row button:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.adjust-row input[type="range"] {
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#control-panel,
|
||||
#line-floating-menu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body,
|
||||
#preview-container {
|
||||
background: #fff;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#paper-preview {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
box-shadow: none;
|
||||
/* 1. 移除原本的 flex 與置中設定,改為一般的區塊顯示 */
|
||||
display: block !important;
|
||||
/* 2. 透過 padding 設定您需要的上邊界與右邊界 (數值可自由調整) */
|
||||
padding-top: 30mm !important; /* 控制上方留白 */
|
||||
padding-right: 30mm !important; /* 控制右側留白 */
|
||||
padding-bottom: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
/* padding: 0;
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important; */
|
||||
}
|
||||
|
||||
#editor {
|
||||
height: auto !important;
|
||||
/* width: auto !important; 加入這行,讓寬度貼合內容 */
|
||||
max-height: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user