984 lines
25 KiB
Vue
984 lines
25 KiB
Vue
<template>
|
||
<div class="print-preview-app">
|
||
<!-- Vue 2 動態注入列印樣式 -->
|
||
<component :is="'style'" id="print-style">{{ printPageStyle }}</component>
|
||
|
||
<!-- 左側面板容器 (加入收合狀態 class) -->
|
||
<div class="left-panel-container no-print" :class="{ 'is-collapsed': isPanelCollapsed }">
|
||
|
||
<!-- 控制面板主體 -->
|
||
<div id="control-panel">
|
||
<!-- panel-inner 用於固定內容寬度,避免收合時文字因為寬度變小而折行 -->
|
||
<div class="panel-inner">
|
||
<div class="control-group">
|
||
<label>疏文樣板</label>
|
||
<select v-model="defaultTemplate" @change="changeTemplate">
|
||
<option v-for="item in shuwenList" :key="item.num" :value="item.num">{{ item.name }}</option>
|
||
</select>
|
||
</div>
|
||
<div class="control-group">
|
||
<label>紙張大小</label>
|
||
<select v-model="paperSize" @change="updateLayout">
|
||
<option value="A4">A4</option>
|
||
<option value="A3">A3</option>
|
||
<option value="B5">B5</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label>紙張方向</label>
|
||
<select v-model="paperOrientation" @change="updateLayout">
|
||
<option value="portrait">直向</option>
|
||
<option value="landscape">橫向</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label>一次修改全部行距: <span>{{ globalMargin }}</span>px</label>
|
||
<div class="adjust-row">
|
||
<button @click.prevent="adjustAllMargins(-1)">-</button>
|
||
<input type="range"
|
||
min="0"
|
||
max="80"
|
||
v-model.number="globalMargin"
|
||
@input="applyGlobalMargin" />
|
||
<button @click.event="adjustAllMargins(1)">+</button>
|
||
</div>
|
||
</div>
|
||
|
||
<hr />
|
||
<p class="hint-text">
|
||
💡 提示:在右側文字上【按滑鼠右鍵】觸發設定,面板固定顯示在左下角
|
||
</p>
|
||
<hr />
|
||
|
||
<button class="action-btn save-btn" @click.prevent="saveData">
|
||
1. 儲存至資料庫
|
||
</button>
|
||
<button class="action-btn print-btn" @click.prevent="triggerPrint">
|
||
2. 產生 PDF / 列印
|
||
</button>
|
||
<button class="action-btn print-btn" @click.prevent="clearContent">
|
||
3. 清除內容
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 收合/展開 按鈕 -->
|
||
<button class="collapse-btn" @click.prevent="isPanelCollapsed = !isPanelCollapsed" :title="isPanelCollapsed ? '展開面板' : '收合面板'">
|
||
<svg class="arrow-icon" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="15 18 9 12 15 6"></polyline>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 預覽區塊 -->
|
||
<div id="preview-container" @click.prevent="hideFloatingMenu">
|
||
<div id="paper-preview" :style="paperStyle">
|
||
<div
|
||
id="editor"
|
||
contenteditable="true"
|
||
ref="editorRef"
|
||
@contextmenu.prevent="rightClickLineTrack"
|
||
>
|
||
<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"
|
||
v-show="isMenuVisible"
|
||
:style="menuStyle"
|
||
@click.stop
|
||
class="no-print"
|
||
>
|
||
<h4 id="menu-header" @mousedown="startDrag">✥ 拖拉此處移動面板</h4>
|
||
|
||
<div class="control-group">
|
||
<label>字型大小: <span>{{ currentLineState.fontSize }}</span>px</label>
|
||
<div class="adjust-row">
|
||
<button @click.prevent="stepLineStyle('fontSize', -1)">-</button>
|
||
<input
|
||
type="range"
|
||
min="12"
|
||
max="60"
|
||
v-model.number="currentLineState.fontSize"
|
||
@input="applyLineStyle('fontSize', currentLineState.fontSize + 'px')"
|
||
/>
|
||
<button @click.prevent="stepLineStyle('fontSize', 1)">+</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label>字距: <span>{{ currentLineState.letterSpacing }}</span>px</label>
|
||
<div class="adjust-row">
|
||
<button @click.prevent="stepLineStyle('letterSpacing', -1)">-</button>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="25"
|
||
v-model.number="currentLineState.letterSpacing"
|
||
@input="applyLineStyle('letterSpacing', currentLineState.letterSpacing + 'px')"
|
||
/>
|
||
<button @click.prevent="stepLineStyle('letterSpacing', 1)">+</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label>行距(與左側): <span>{{ currentLineState.marginLeft }}</span>px</label>
|
||
<div class="adjust-row">
|
||
<button @click.prevent="stepLineStyle('marginLeft', -1)">-</button>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="80"
|
||
v-model.number="currentLineState.marginLeft"
|
||
@input="applyLineStyle('marginLeft', currentLineState.marginLeft + 'px')"
|
||
/>
|
||
<button @click.prevent="stepLineStyle('marginLeft', 1)">+</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label>垂直位置對齊</label>
|
||
<div class="menu-btn-group">
|
||
<button
|
||
:class="{ active: currentLineState.alignItems === 'flex-start' }"
|
||
@click.prevent="applyLineStyle('alignItems', 'flex-start')"
|
||
>上</button>
|
||
<button
|
||
:class="{ active: currentLineState.alignItems === 'center' }"
|
||
@click.prevent="applyLineStyle('alignItems', 'center')"
|
||
>中</button>
|
||
<button
|
||
:class="{ active: currentLineState.alignItems === 'flex-end' }"
|
||
@click.prevent="applyLineStyle('alignItems', 'flex-end')"
|
||
>下</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
module.exports = {
|
||
name: 'Shuwen02',
|
||
|
||
data() {
|
||
return {
|
||
isPanelCollapsed: false, // 控制面板收合狀態
|
||
paperSize: 'A4',
|
||
paperOrientation: 'portrait',
|
||
globalMargin: 10,
|
||
paperStyle: {
|
||
width: '210mm',
|
||
height: '297mm'
|
||
},
|
||
isMenuVisible: false,
|
||
menuStyle: {
|
||
left: '320px', // 微調初始位置,避免擋住按鈕
|
||
top: '',
|
||
bottom: '20px'
|
||
},
|
||
currentLineState: {
|
||
fontSize: 16,
|
||
letterSpacing: 2,
|
||
marginLeft: 10,
|
||
alignItems: 'flex-start'
|
||
},
|
||
shuwenList: [],
|
||
defaultTemplate: {}
|
||
};
|
||
},
|
||
|
||
computed: {
|
||
printPageStyle() {
|
||
return `@media print { @page { size: ${this.paperSize} ${this.paperOrientation}; margin: 0mm; } }`;
|
||
}
|
||
},
|
||
|
||
created() {
|
||
this.currentTargetLine = null;
|
||
this.isDragging = false;
|
||
this.startX = 0;
|
||
this.startY = 0;
|
||
},
|
||
|
||
methods: {
|
||
updateLayout() {
|
||
const sizes = {
|
||
'A4': { w: 210, h: 297 },
|
||
'A3': { w: 297, h: 420 },
|
||
'B5': { w: 176, h: 250 }
|
||
};
|
||
|
||
const w = sizes[this.paperSize].w;
|
||
const h = sizes[this.paperSize].h;
|
||
|
||
const finalW = this.paperOrientation === 'portrait' ? w : h;
|
||
const finalH = this.paperOrientation === 'portrait' ? h : w;
|
||
|
||
this.paperStyle.width = `${finalW}mm`;
|
||
this.paperStyle.height = `${finalH}mm`;
|
||
|
||
this.hideFloatingMenu();
|
||
this.initData();
|
||
},
|
||
changeTemplate() {
|
||
// console.log("changeTemplate:", this.defaultTemplate)
|
||
let temp = this.shuwenList.find((x) => x.num === this.defaultTemplate);
|
||
if (temp) {
|
||
document.getElementById("editor").innerHTML = temp.contents;
|
||
}
|
||
},
|
||
rightClickLineTrack(e) {
|
||
let target = e.target;
|
||
const editor = this.$refs.editorRef;
|
||
|
||
while (target && target.parentNode !== editor && target !== editor) {
|
||
target = target.parentNode;
|
||
}
|
||
|
||
if (!target || target === editor || target.tagName !== 'DIV') return;
|
||
|
||
if (this.currentTargetLine) {
|
||
this.currentTargetLine.classList.remove('selected-line');
|
||
}
|
||
|
||
this.currentTargetLine = target;
|
||
this.currentTargetLine.classList.add('selected-line');
|
||
|
||
const computed = window.getComputedStyle(this.currentTargetLine);
|
||
|
||
this.currentLineState.fontSize = parseInt(computed.fontSize) || 16;
|
||
this.currentLineState.letterSpacing = computed.letterSpacing === 'normal' ? 0 : (parseInt(computed.letterSpacing) || 0);
|
||
this.currentLineState.marginLeft = parseInt(computed.marginLeft) || 10;
|
||
this.currentLineState.alignItems = computed.alignItems || 'flex-start';
|
||
|
||
this.isMenuVisible = true;
|
||
},
|
||
|
||
hideFloatingMenu(e) {
|
||
if (!e || e.target.id === 'preview-container' || e.target.id === 'paper-preview') {
|
||
this.isMenuVisible = false;
|
||
if (this.currentTargetLine) {
|
||
this.currentTargetLine.classList.remove('selected-line');
|
||
this.currentTargetLine = null;
|
||
}
|
||
}
|
||
},
|
||
|
||
applyLineStyle(property, value) {
|
||
if (this.currentTargetLine) {
|
||
this.currentTargetLine.style[property] = value;
|
||
if (property === 'alignItems') {
|
||
this.currentLineState.alignItems = value;
|
||
}
|
||
}
|
||
},
|
||
|
||
stepLineStyle(property, step) {
|
||
let currentValue = this.currentLineState[property];
|
||
let min = 0, max = 100;
|
||
|
||
if (property === 'fontSize') { min = 12; max = 60; }
|
||
if (property === 'letterSpacing') { min = 0; max = 25; }
|
||
if (property === 'marginLeft') { min = 0; max = 80; }
|
||
|
||
let newValue = currentValue + step;
|
||
if (newValue < min) newValue = min;
|
||
if (newValue > max) newValue = max;
|
||
|
||
this.currentLineState[property] = newValue;
|
||
this.applyLineStyle(property, `${newValue}px`);
|
||
},
|
||
|
||
applyGlobalMargin() {
|
||
const editor = this.$refs.editorRef;
|
||
if (editor) {
|
||
const lines = editor.querySelectorAll('div');
|
||
lines.forEach(line => {
|
||
line.style.marginLeft = `${this.globalMargin}px`;
|
||
});
|
||
}
|
||
},
|
||
|
||
adjustAllMargins(step) {
|
||
let newVal = this.globalMargin + step;
|
||
if (newVal < 0) newVal = 0;
|
||
if (newVal > 80) newVal = 80;
|
||
this.globalMargin = newVal;
|
||
this.applyGlobalMargin();
|
||
},
|
||
|
||
saveData() {
|
||
this.isMenuVisible = false;
|
||
if (this.currentTargetLine) {
|
||
this.currentTargetLine.classList.remove('selected-line');
|
||
}
|
||
const editor = this.$refs.editorRef;
|
||
if (editor) {
|
||
console.log(editor.innerHTML);
|
||
alert('已成功擷取 HTML 結構至 Console!');
|
||
}
|
||
},
|
||
|
||
triggerPrint() {
|
||
//window.print();
|
||
|
||
this.print();
|
||
},
|
||
|
||
startDrag(e) {
|
||
this.isDragging = true;
|
||
const el = document.getElementById('line-floating-menu');
|
||
const rect = el.getBoundingClientRect();
|
||
this.startX = e.clientX - rect.left;
|
||
this.startY = e.clientY - rect.top;
|
||
},
|
||
|
||
handleDrag(e) {
|
||
if (this.isDragging) {
|
||
e.preventDefault();
|
||
this.menuStyle.left = `${e.clientX - this.startX}px`;
|
||
this.menuStyle.top = `${e.clientY - this.startY}px`;
|
||
this.menuStyle.bottom = 'auto';
|
||
}
|
||
},
|
||
|
||
stopDrag() {
|
||
this.isDragging = false;
|
||
},
|
||
|
||
bindEvents() {
|
||
document.addEventListener('mousemove', this.handleDrag);
|
||
document.addEventListener('mouseup', this.stopDrag);
|
||
},
|
||
|
||
unbindEvents() {
|
||
document.removeEventListener('mousemove', this.handleDrag);
|
||
document.removeEventListener('mouseup', this.stopDrag);
|
||
},
|
||
initData() {
|
||
axios
|
||
|
||
.post(HTTP_HOST + 'api/shuwens/ListShuwenTemplate')
|
||
.then(response => {
|
||
console.log(response)
|
||
this.shuwenList = response.data.data
|
||
|
||
})
|
||
.catch(error => console.log(error))
|
||
},
|
||
clearContent() {
|
||
document.getElementById("editor").innerHTML = `
|
||
<div style="font-size: 28px; font-weight: bold; align-items: flex-start; margin-left: 5px; letter-spacing: 16px;" class="">
|
||
</div>
|
||
`;
|
||
},
|
||
print() {
|
||
let w = window.open('', '_blank');
|
||
if (!w) {
|
||
alert("請允許瀏覽器開啟彈出式視窗!");
|
||
return;
|
||
}
|
||
|
||
|
||
this.pageStyle = `<style id="pageStyle">
|
||
@media print {
|
||
@page {
|
||
size: ${this.paperSize} ${this.paperOrientation}; margin: 0mm; } };};
|
||
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: auto;
|
||
height: auto;
|
||
}
|
||
#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>
|
||
`
|
||
let canvasHtml = document.getElementById("preview-container").outerHTML; //$("#preview-container").html();
|
||
console.log(canvasHtml);
|
||
|
||
let htmlContent = `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title></title>
|
||
<meta charset="utf-8">
|
||
${this.pageStyle}
|
||
</head>
|
||
<body>
|
||
${canvasHtml}
|
||
</body>
|
||
</html>`;
|
||
|
||
w.document.write(htmlContent);
|
||
w.document.close();
|
||
w.onload = function () {
|
||
// 5. 將等待時間稍微拉長至 1.2 秒,確保大張的 SVG 底圖與外部字體(如標楷體)下載完畢
|
||
setTimeout(() => {
|
||
w.focus();
|
||
w.print();
|
||
}, 1200);
|
||
}
|
||
},
|
||
},
|
||
|
||
mounted() {
|
||
this.updateLayout();
|
||
this.bindEvents();
|
||
},
|
||
|
||
beforeDestroy() {
|
||
this.unbindEvents();
|
||
},
|
||
|
||
activated() {
|
||
this.bindEvents();
|
||
},
|
||
|
||
deactivated() {
|
||
this.unbindEvents();
|
||
this.isDragging = false;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 基礎排版與重設 */
|
||
.print-preview-app {
|
||
font-family: "Microsoft JhengHei", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
margin: 0;
|
||
background: #f8f9fa;
|
||
display: flex;
|
||
gap: 24px;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
padding: 24px;
|
||
}
|
||
|
||
/* 左側面板容器 (控制收合動畫) */
|
||
.left-panel-container {
|
||
position: relative;
|
||
width: 260px;
|
||
height: fit-content;
|
||
flex-shrink: 0;
|
||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
/* 展開/收合 按鈕 */
|
||
.collapse-btn {
|
||
position: absolute;
|
||
right: -14px; /* 讓按鈕跨在面板邊界上 */
|
||
top: 32px;
|
||
width: 28px;
|
||
height: 28px;
|
||
background: #fff;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
color: #4a5568;
|
||
z-index: 10;
|
||
transition: all 0.2s ease;
|
||
padding: 0;
|
||
}
|
||
|
||
.collapse-btn:hover {
|
||
background: #f1f5f9;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.arrow-icon {
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
/* == 收合狀態處理 == */
|
||
.left-panel-container.is-collapsed {
|
||
width: 0px;
|
||
}
|
||
|
||
.left-panel-container.is-collapsed #control-panel {
|
||
padding-left: 0;
|
||
padding-right: 0;
|
||
border-width: 0;
|
||
opacity: 0;
|
||
visibility: hidden; /* 防止收合時裡面元素還能被點擊 */
|
||
}
|
||
|
||
/* 收合時箭頭反轉,並把按鈕往右推出避免被隱藏 */
|
||
.left-panel-container.is-collapsed .arrow-icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
.left-panel-container.is-collapsed .collapse-btn {
|
||
right: -36px;
|
||
}
|
||
|
||
/* 控制面板外框 */
|
||
#control-panel {
|
||
width: 100%;
|
||
background: #ffffff;
|
||
padding: 24px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||
border: 1px solid #edf2f7;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
transition: padding 0.3s, opacity 0.3s, border-width 0.3s;
|
||
}
|
||
|
||
/* 固定內部內容寬度,避免收合過程中文字擠壓換行 */
|
||
.panel-inner {
|
||
width: 210px; /* 260px - 24px*2(padding) */
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.control-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #4a5568;
|
||
}
|
||
|
||
select, input[type="range"] {
|
||
padding: 8px 12px;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 6px;
|
||
background-color: #f8fafc;
|
||
color: #1a202c;
|
||
outline: none;
|
||
transition: all 0.2s;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
select:focus {
|
||
border-color: #cbd5e1;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
hr {
|
||
border: 0;
|
||
height: 1px;
|
||
background: #edf2f7;
|
||
width: 100%;
|
||
margin: 4px 0;
|
||
}
|
||
|
||
.hint-text {
|
||
font-size: 12px;
|
||
color: #718096;
|
||
background: #f7fafc;
|
||
padding: 10px;
|
||
border-radius: 6px;
|
||
margin: 0;
|
||
line-height: 1.5;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
|
||
/* 按鈕樣式 */
|
||
button.action-btn {
|
||
padding: 12px;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
transition: opacity 0.2s, transform 0.1s;
|
||
width: 100%;
|
||
}
|
||
|
||
button.action-btn:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
.save-btn {
|
||
background: #2d3748;
|
||
}
|
||
|
||
.save-btn:hover {
|
||
background: #4a5568;
|
||
}
|
||
|
||
.print-btn {
|
||
background: #0f172a;
|
||
}
|
||
|
||
.print-btn:hover {
|
||
background: #334155;
|
||
}
|
||
|
||
/* 預覽容器與紙張 */
|
||
#preview-container {
|
||
display: flex;
|
||
flex-grow: 1;
|
||
background: #e2e8f0;
|
||
padding: 32px;
|
||
overflow: auto;
|
||
position: relative;
|
||
border-radius: 12px;
|
||
justify-content: center;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
#paper-preview {
|
||
background: #fff;
|
||
padding: 20mm;
|
||
box-sizing: border-box;
|
||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||
transition: width 0.3s, height 0.3s;
|
||
}
|
||
|
||
#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;
|
||
border-radius: 4px;
|
||
transition: background 0.1s;
|
||
}
|
||
|
||
#editor > div:hover {
|
||
background: #f1f5f9;
|
||
}
|
||
|
||
#editor > div.selected-line {
|
||
background: #e2e8f0 !important;
|
||
box-shadow: 0 0 0 1px #94a3b8;
|
||
}
|
||
|
||
/* 浮動選單 */
|
||
#line-floating-menu {
|
||
position: fixed;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(8px);
|
||
border: 1px solid #e2e8f0;
|
||
padding: 16px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||
z-index: 99999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
width: 220px;
|
||
}
|
||
|
||
#line-floating-menu h4 {
|
||
margin: 0;
|
||
color: #4a5568;
|
||
font-size: 12px;
|
||
cursor: move;
|
||
background: #f8fafc;
|
||
padding: 8px;
|
||
border-radius: 6px;
|
||
text-align: center;
|
||
user-select: none;
|
||
border: 1px solid #edf2f7;
|
||
}
|
||
|
||
.menu-btn-group {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.menu-btn-group button {
|
||
flex: 1;
|
||
padding: 6px;
|
||
border: 1px solid #e2e8f0;
|
||
background: #fff;
|
||
color: #4a5568;
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
font-weight: 600;
|
||
font-size: 12px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.menu-btn-group button.active {
|
||
background: #2d3748;
|
||
color: #fff;
|
||
border-color: #2d3748;
|
||
}
|
||
|
||
.adjust-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.adjust-row button {
|
||
padding: 4px 12px;
|
||
border: 1px solid #e2e8f0;
|
||
background: #fff;
|
||
color: #4a5568;
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
font-weight: 600;
|
||
user-select: none;
|
||
}
|
||
|
||
.adjust-row button:hover {
|
||
background: #f1f5f9;
|
||
}
|
||
|
||
.adjust-row input[type="range"] {
|
||
flex-grow: 1;
|
||
padding: 0;
|
||
}
|
||
|
||
/* 列印專屬設定 */
|
||
@media print {
|
||
|
||
|
||
.print-preview-app {
|
||
padding: 0;
|
||
height: auto;
|
||
overflow: visible;
|
||
}
|
||
|
||
.left-panel-container,
|
||
#line-floating-menu {
|
||
display: none !important;
|
||
}
|
||
|
||
#preview-container, #preview-container * {
|
||
visibility: visible;
|
||
}
|
||
|
||
#preview-container {
|
||
background: #fff;
|
||
padding: 0;
|
||
margin: 0;
|
||
position: absolute;
|
||
left:0;
|
||
top:0;
|
||
}
|
||
|
||
#paper-preview {
|
||
width: 100% !important;
|
||
height: 100% !important;
|
||
box-shadow: none;
|
||
display: block !important;
|
||
padding-top: 30mm !important;
|
||
padding-right: 30mm !important;
|
||
padding-bottom: 0 !important;
|
||
padding-left: 0 !important;
|
||
border: none;
|
||
}
|
||
|
||
#editor {
|
||
height: auto !important;
|
||
max-height: 100% !important;
|
||
}
|
||
|
||
#editor > div.selected-line {
|
||
background: transparent !important;
|
||
box-shadow: none !important;
|
||
}
|
||
.no-print {
|
||
display:none !important;
|
||
}
|
||
}
|
||
</style> |