1030 lines
32 KiB
Vue
1030 lines
32 KiB
Vue
<template>
|
||
<div class="print-preview-app">
|
||
|
||
|
||
<div id="preview-container" @click="hideFloatingMenu">
|
||
<div id="paper-preview" :style="paperStyle">
|
||
<div class="dual-editor-wrapper">
|
||
<!-- 右側編輯區 (直書的第一區) -->
|
||
<div id="editor-right"
|
||
class="editor"
|
||
:style="editorSize"
|
||
ref="editorRightRef"
|
||
@contextmenu="rightClickLineTrack"
|
||
@keydown.enter.prevent="newLine">
|
||
<!--contenteditable="true"-->
|
||
</div>
|
||
|
||
<!-- 左側編輯區 (直書的第二區) -->
|
||
<div id="editor-left"
|
||
class="editor"
|
||
:style="editorSize"
|
||
ref="editorLeftRef"
|
||
>
|
||
|
||
<div class="canvas-area flex-grow-1 overflow-auto d-flex flex-column align-items-center position-relative" id="canvas">
|
||
<div class="tablet-paper" :style="tabletPaper">
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 浮動設定面板 -->
|
||
<div id="line-floating-menu"
|
||
:style="menuStyle"
|
||
@click.stop
|
||
class="no-print">
|
||
<h4 id="menu-header" @mousedown="startDrag">✥ 拖拉此處移動面板</h4>
|
||
<div class="control-group">
|
||
<label>版型</label>
|
||
<div class="adjust-row">
|
||
<select v-model="selectedStyle">
|
||
<option value="">請選擇</option>
|
||
<option v-for="item in tabletStyles" :key="item.styleID" :value="item.styleID">{{ item.name }}</option>
|
||
</select>
|
||
</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="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>
|
||
</template>
|
||
|
||
<script>
|
||
module.exports = {
|
||
name: 'Shuwen02',
|
||
props: {
|
||
num: { type: String }
|
||
},
|
||
data() {
|
||
|
||
return {
|
||
isPanelCollapsed: false, // 控制面板收合狀態
|
||
paperSize: 'A4',
|
||
paperOrientation: 'portrait',
|
||
globalMargin: 10,
|
||
paperStyle: {
|
||
width: '210mm',
|
||
height: '297mm'
|
||
},
|
||
editorSize: {
|
||
width: '105mm',
|
||
height: '297mm',
|
||
//backgroundImage: 'url(../print/html/tablet-1_new.svg)'
|
||
},
|
||
tabletPaper: {
|
||
|
||
},
|
||
papers: [],
|
||
elements: [],
|
||
tabletStyles: [],
|
||
selectedStyle: {},
|
||
isMenuVisible: true,
|
||
menuStyle: {
|
||
left: '300px', // 微調初始位置,避免擋住按鈕
|
||
top: '300px',
|
||
height: '80vh',
|
||
width: '30vw'
|
||
//bottom: '20px'
|
||
},
|
||
currentLineState: {
|
||
fontSize: 16,
|
||
letterSpacing: 2,
|
||
marginLeft: 10,
|
||
alignItems: 'flex-start'
|
||
},
|
||
shuwenList: [],
|
||
defaultTemplate: {},
|
||
shuwenType: "",
|
||
bg: [
|
||
{ name: "超薦1", path: "../print/html/tablet-1_new.svg" },
|
||
{ name: "超薦2", path: "../print/html/tablet-1B_new.svg" },
|
||
{ name: "消災1", path: "../print/html/tablet-2.svg" },
|
||
{ name: "消災2", path: "../print/html/tablet-2B.svg" },
|
||
{ name: "消災3", path: "../print/html/tablet-2C.svg" },
|
||
{ name: "消災4", path: "../print/html/tablet-2D.svg" }
|
||
],
|
||
};
|
||
},
|
||
|
||
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;
|
||
},
|
||
watch: {
|
||
selectedStyle: {
|
||
handler() {
|
||
this.changeStyle();
|
||
},
|
||
deep: true
|
||
}
|
||
},
|
||
methods: {
|
||
getTabletStyle() {
|
||
const params = {
|
||
sortBy: "", sortDesc: false,
|
||
page: '1', pageSize: '1000'
|
||
};
|
||
axios.post('/api/tablet/GetTabletStyleList', {})
|
||
.then(response => {
|
||
//console.log("getTabletStyle:", response, response.status, response.data)
|
||
if (response.status == 200) {
|
||
//Object.assign(this.tabletStyles, response.data);
|
||
//console.log("yes", this.tabletStyles);
|
||
this.tabletStyles = response.data.list;
|
||
this.papers = response.data.papers;
|
||
this.elements = response.data.elements;
|
||
}
|
||
|
||
})
|
||
.catch(error => console.log(error))
|
||
},
|
||
changeStyle() {
|
||
const s = this.tabletStyles.find(y => y.styleID === this.selectedStyle);
|
||
const printsize = this.papers.find(x => x.paperID === s.printSize);
|
||
const papersize = this.papers.find(x => x.paperID === s.paperSize);
|
||
console.log("changeStyle:", this.selectedStyle, s, printsize, papersize);
|
||
this.paperStyle.width = Number(printsize.width) * 2 + 'mm'
|
||
this.paperStyle.height = Number(printsize.height) + 'mm'
|
||
this.papersize = {
|
||
width: `${Number(printsize.width) * 2}mm !important`,
|
||
height : `${ Number(printsize.height) }mm !important`
|
||
}
|
||
const bkg = this.bg.find(n => n.name === s.backendImg)
|
||
this.tabletPaper = {
|
||
width: `${Number(papersize.width)}mm !important`,
|
||
height: `${Number(papersize.height)}mm !important`,
|
||
backgroundPosition: `center top`,
|
||
backgroundImage: `url(${bkg.path})`
|
||
}
|
||
this.editorSize.width = Number(papersize.width) + 'mm'
|
||
this.editorSize.height = Number(papersize.height) + 'mm'
|
||
|
||
//this.editorSize.backgroundImage =`url(${bkg.path})`
|
||
},
|
||
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() {
|
||
if (this.defaultTemplate === "") {
|
||
this.clearContent();
|
||
return
|
||
}
|
||
let temp = this.shuwenList.find((x) => x.num === this.defaultTemplate);
|
||
if (temp) {
|
||
// 預設將樣板內容放入右側
|
||
document.getElementById("editor-right").innerHTML = temp.contents;
|
||
}
|
||
},
|
||
rightClickLineTrack(e) {
|
||
let target = e.target;
|
||
// 往上尋找,確認點擊目標是否在任一個 .editor 裡面
|
||
const editor = target.closest('.editor');
|
||
|
||
// 如果不是在編輯區內點擊,則不做處理
|
||
if (!editor) return;
|
||
|
||
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`);
|
||
},
|
||
|
||
// 例:針對所有 .editor 進行全域邊界設定
|
||
applyGlobalMargin() {
|
||
// 同時抓取左右兩個編輯區
|
||
const editors = document.querySelectorAll('.editor');
|
||
editors.forEach(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 rightHTML = document.getElementById('editor-right').innerHTML;
|
||
const leftHTML = document.getElementById('editor-left').innerHTML;
|
||
|
||
console.log("右側內容:", rightHTML);
|
||
console.log("左側內容:", leftHTML);
|
||
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() {
|
||
const emptyContent = `<div class="selected-line"> </div>`;
|
||
// 左右兩邊一起清空
|
||
document.getElementById("editor-right").innerHTML = emptyContent;
|
||
document.getElementById("editor-left").innerHTML = emptyContent;
|
||
},
|
||
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: 100% !important;
|
||
height: 100% !important;
|
||
flex-grow: 1;
|
||
}
|
||
#editor {
|
||
width: 100%;
|
||
height: 100%;
|
||
outline: none;
|
||
writing-mode: vertical-rl;
|
||
text-orient: upright;
|
||
/* 新增多欄設定,分為左右兩區塊 */
|
||
column-count: 2; /* 分成 2 欄 */
|
||
column-gap: 40px; /* 兩欄之間的間距,可依需求微調 */
|
||
column-rule: 1px dashed #ccc; /* (選用) 在兩欄中間加一條虛線分隔 */
|
||
}
|
||
#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.getTabletStyle();
|
||
this.updateLayout();
|
||
this.bindEvents();
|
||
document.execCommand("defaultParagraphSeparator", false, 'div');
|
||
this.$nextTick(() => { });
|
||
},
|
||
|
||
beforeDestroy() {
|
||
this.unbindEvents();
|
||
},
|
||
|
||
activated() {
|
||
this.bindEvents();
|
||
},
|
||
|
||
deactivated() {
|
||
this.unbindEvents();
|
||
this.isDragging = false;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
/* 基礎排版與重設 */
|
||
.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 填滿並最大化 */
|
||
#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;
|
||
width: 100% ;
|
||
height: 100% ;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 左右兩個編輯區的排版容器 */
|
||
.dual-editor-wrapper {
|
||
display: flex;
|
||
flex-direction: row-reverse; /* 關鍵:直書由右至左,讓第一個 div 顯示在右側 */
|
||
width: 100%;
|
||
height: 100%;
|
||
gap: 40px; /* 左右編輯區的間距 */
|
||
justify-content: space-between; /* 確保內容向兩側分開 */
|
||
}
|
||
|
||
/* 兩個獨立編輯區 */
|
||
.editor {
|
||
/* 關鍵:強制指定寬度為一半,並扣掉間隔 (40px 的一半是 20px) */
|
||
/* width: calc(50% - 20px);
|
||
height: 100%;*/
|
||
display: flex;
|
||
outline: none;
|
||
writing-mode: vertical-rl;
|
||
text-orient: upright;
|
||
box-sizing: border-box;
|
||
/* 建議先加上這行虛線外框,讓您一眼看清楚兩個編輯區是否有被成功切開 */
|
||
border: 1px dashed #cbd5e1;
|
||
/* 避免內容過多撐破版面導致重疊 */
|
||
overflow: hidden;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 下方原本 #editor 的子元素樣式,全數改為 .editor */
|
||
.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> |