總疏文

This commit is contained in:
2026-08-17 18:05:22 +08:00
parent b1c05c82cb
commit c03bcfea64
5 changed files with 1190 additions and 2 deletions
+7
View File
@@ -612,6 +612,9 @@
<button class="nav-link" id="sec2-tab3" data-bs-toggle="tab" data-bs-target="#sec2-page3"
type="button" role="tab" aria-controls="bom" aria-selected="false">
功德項目</button>
<%--<button class="nav-link" id="sec2-tab4" data-bs-toggle="tab" data-bs-target="#sec2-page4"
type="button" role="tab" aria-controls="bom" aria-selected="false">
總疏文維護</button>--%>
</div>
</nav>
</div>
@@ -884,6 +887,10 @@
</div>
</asp:Panel>
</div>
<%-- <div class="tab-pane fade giftItems label-sm-right" id="sec2-page4" role="tabpanel" aria-labelledby="sec2-tab4">
總疏文維護
</div>--%>
</div>
</div>
</div>
+20 -2
View File
@@ -2,6 +2,7 @@
<%@ Register Src="~/admin/_uc/alert.ascx" TagPrefix="uc1" TagName="alert" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
<script src="/js/httpVueLoader.js"></script>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="footer_script" runat="Server">
<script>
@@ -286,7 +287,8 @@
items: [
{ order_no: "D", member_name: "S", order_date: "2025-12-07" }
],
},
},
currentView: 'shuwen-component',
}
},
@@ -317,6 +319,9 @@
deep: true,
},
},
components: {
'shuwen-component': httpVueLoader('./shuwen02.vue'),
},
methods: {
search_show(curr) {
//console.log("btn_click:", curr, curr.api_url);
@@ -1061,6 +1066,10 @@
type="button" role="tab" aria-controls="profile" aria-selected="false" @click="loadUnfilledList">
未填寫名單
</button>
<button v-if="this_id!=''" class="nav-link" id="sec2-tab5" data-bs-toggle="tab" data-bs-target="#sec2-page5"
type="button" role="tab" aria-controls="profile" aria-selected="false">
總疏文維護
</button>
</div>
</nav>
</div>
@@ -1349,7 +1358,16 @@
</v-data-table>
</v-card>
</div>
</div>
<div class="tab-pane fade" id="sec2-page5" role="tabpanel" aria-labelledby="sec2-tab5" v-if="this_id!=''">
<%-- <v-card class="mx-auto" outlined >
<keep-alive>
<component :is="currentView">
</component>
</keep-alive>
</v-card>--%>
</div>
</div>
</asp:Panel>
</div>
+558
View File
@@ -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>
+558
View File
@@ -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>