修正租屋契約 PDF 匯出問題

This commit is contained in:
2026-05-14 19:31:30 +08:00
parent f7e6cc124a
commit 3a0bae068b

View File

@@ -3,46 +3,39 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>租屋契約工具</title>
<title>租屋契約 PDF 工具</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
:root {
--bg: #f3f4f6;
--panel: rgba(255, 255, 255, 0.94);
--panel-strong: #ffffff;
--text: #2b2118;
--muted: #746657;
--line: #d9dde3;
--accent: #a34b2a;
--accent-strong: #7f3519;
--shadow: 0 18px 45px rgba(82, 51, 28, 0.12);
--bg: #eee4d6;
--paper: #ffffff;
--ink: #222222;
--muted: #6c6258;
--line: #d4c7b8;
--accent: #8a4a27;
--accent-2: #6f3517;
--panel: rgba(255, 250, 244, 0.9);
--shadow: 0 18px 45px rgba(67, 43, 24, 0.12);
}
* {
box-sizing: border-box;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: "Segoe UI", "Noto Sans TC", sans-serif;
color: var(--text);
color: var(--ink);
font-family: "PMingLiU", "MingLiU", "Noto Serif TC", serif;
background:
radial-gradient(circle at top left, rgba(163, 75, 42, 0.06), transparent 30%),
linear-gradient(135deg, #fafbfc 0%, #eef1f4 100%);
radial-gradient(circle at top left, rgba(138, 74, 39, 0.1), transparent 26%),
linear-gradient(135deg, #f7f1e8, var(--bg));
}
.shell {
width: min(1180px, calc(100% - 32px));
.page {
width: min(1380px, calc(100% - 32px));
margin: 24px auto;
padding: 24px;
border: 1px solid rgba(217, 221, 227, 0.8);
border: 1px solid rgba(212, 199, 184, 0.9);
border-radius: 24px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(12px);
background: rgba(255, 247, 239, 0.78);
backdrop-filter: blur(10px);
box-shadow: var(--shadow);
}
.hero {
display: flex;
justify-content: space-between;
@@ -50,414 +43,381 @@
gap: 16px;
margin-bottom: 20px;
}
h1 {
margin: 0;
font-size: clamp(28px, 4vw, 42px);
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
font-size: clamp(28px, 4vw, 40px);
line-height: 1.05;
letter-spacing: 0.02em;
letter-spacing: 0.04em;
}
.subtitle {
margin: 8px 0 0;
color: var(--muted);
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
font-size: 15px;
}
.meta {
color: var(--muted);
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
font-size: 13px;
white-space: nowrap;
}
.layout {
display: grid;
grid-template-columns: 360px 1fr;
gap: 20px;
grid-template-columns: 330px 1fr;
gap: 22px;
align-items: start;
}
.panel {
background: var(--panel);
border: 1px solid rgba(217, 221, 227, 0.9);
border-radius: 20px;
.controls {
position: sticky;
top: 20px;
padding: 18px;
border: 1px solid var(--line);
border-radius: 20px;
background: var(--panel);
box-shadow: var(--shadow);
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
}
.panel h2 {
margin: 0 0 12px;
.controls h2 {
margin: 0 0 10px;
font-size: 18px;
}
.field-list {
display: grid;
gap: 12px;
}
.field-card {
padding: 12px;
border: 1px solid var(--line);
border-radius: 16px;
background: rgba(248, 250, 252, 0.96);
}
label {
display: block;
font-size: 13px;
.controls p {
margin: 0 0 16px;
color: var(--muted);
margin-bottom: 6px;
font-size: 14px;
line-height: 1.6;
}
input {
.field { margin-bottom: 14px; }
.field label {
display: block;
margin-bottom: 6px;
font-size: 14px;
color: var(--muted);
}
.field input {
width: 100%;
padding: 10px 12px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 14px;
background: var(--panel-strong);
color: var(--text);
background: #fffdfa;
font: inherit;
font-size: 16px;
color: var(--ink);
}
.toolbar {
display: flex;
flex-wrap: wrap;
display: grid;
gap: 10px;
margin-bottom: 12px;
margin-top: 18px;
}
button {
border: 0;
border-radius: 999px;
padding: 11px 16px;
padding: 12px 16px;
font: inherit;
cursor: pointer;
transition: transform 0.18s ease;
transition: transform 0.18s ease, opacity 0.18s ease;
}
button:hover {
transform: translateY(-1px);
}
button:hover { transform: translateY(-1px); }
.primary {
color: #fff9f3;
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
color: #fffaf5;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.secondary {
color: var(--text);
background: #eceff3;
color: var(--ink);
background: #eadcc9;
}
.output {
white-space: pre-wrap;
min-height: 640px;
padding: 16px 16px 32px;
border-radius: 16px;
border: 1px dashed var(--line);
.preview-wrap {
display: flex;
justify-content: center;
}
.paper {
width: 210mm;
min-height: 297mm;
padding: 22mm 18mm;
border: 1px solid #d7d1c8;
background: var(--paper);
box-shadow: 0 24px 60px rgba(38, 25, 14, 0.12);
color: #111111;
line-height: 1.72;
font-size: 18px;
}
.paper.pdf-export-mode {
width: 174mm;
min-height: 0;
padding: 0;
border: 0;
box-shadow: none;
background: #ffffff;
line-height: 1.75;
}
.contract-title {
margin: 0 0 14px;
text-align: center;
font-size: 24px;
letter-spacing: 0.2em;
font-weight: 700;
}
.contract-title,
.contract-line,
.signature-row {
break-inside: avoid;
page-break-inside: avoid;
}
.contract-line {
white-space: pre-wrap;
min-height: 1.75em;
}
.indent {
padding-left: 2em;
text-indent: -2em;
}
.signature-block { margin-top: 18px; }
.signature-row { white-space: pre-wrap; }
.fill {
display: inline-block;
min-width: 3em;
padding: 0 0.15em;
}
.hint {
margin-top: 12px;
margin-top: 14px;
color: var(--muted);
font-size: 13px;
line-height: 1.6;
}
.status {
min-height: 20px;
min-height: 22px;
margin-top: 10px;
color: var(--accent-strong);
color: var(--accent-2);
font-size: 13px;
}
.pdf-export {
width: 794px;
padding: 16px 16px 32px;
border: 1px dashed var(--line);
border-radius: 16px;
background: #ffffff;
color: var(--text);
font-family: "Segoe UI", "Noto Sans TC", sans-serif;
font-size: 16px;
line-height: 1.75;
white-space: pre-wrap;
@media (max-width: 1100px) {
.layout { grid-template-columns: 1fr; }
.controls { position: static; }
.preview-wrap { overflow: auto; justify-content: start; }
}
@page {
size: A4;
margin: 22mm 18mm;
}
@media print {
body {
background: #fff;
}
.shell {
body { background: #ffffff; }
.page {
width: auto;
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
background: #fff;
background: #ffffff;
box-shadow: none;
}
.hero,
.panel:first-child,
.toolbar,
.hint,
.status {
display: none !important;
}
.layout {
display: block;
}
.panel:last-child {
border: 0;
padding: 0;
background: #fff;
}
.panel:last-child h2 {
display: none;
}
.output {
.hero, .controls { display: none !important; }
.layout { display: block; }
.preview-wrap { display: block; }
.paper {
width: auto;
min-height: auto;
padding: 16px 16px 32px;
border: 1px dashed var(--line);
border-radius: 16px;
background: #fff;
}
}
@media (max-width: 900px) {
.layout {
grid-template-columns: 1fr;
}
.hero {
flex-direction: column;
align-items: start;
margin: 0;
padding: 0;
border: 0;
box-shadow: none;
}
}
</style>
</head>
<body>
<div class="shell">
<div class="page">
<div class="hero">
<div>
<h1>租屋契約工具</h1>
<p class="subtitle">直接填 3 個欄位,右邊會自動產生完整契約內容</p>
<h1>租屋契約 PDF 工具</h1>
<p class="subtitle">只改 `每月租金`、`繳款日期`、`保證金`,右邊維持接近原始 `.doc` 的契約排版,並可匯出或分享 PDF</p>
</div>
<div class="meta" id="timeLabel">台北時間:載入中</div>
</div>
<div class="layout">
<section class="panel">
<h2>填寫資料</h2>
<div class="field-list" id="fields"></div>
</section>
<section class="panel">
<h2>帶入後內容</h2>
<aside class="controls">
<h2>填寫欄位</h2>
<p>這版固定只改 3 個地方,其他條文、地址、設備、租期都照原本文件排版顯示。</p>
<div class="field">
<label for="rentInput">每月租金</label>
<input id="rentInput" type="text" placeholder="例如6500">
</div>
<div class="field">
<label for="payDayInput">繳款日期</label>
<input id="payDayInput" type="text" placeholder="例如5">
</div>
<div class="field">
<label for="depositInput">保證金</label>
<input id="depositInput" type="text" placeholder="例如12000">
</div>
<div class="toolbar">
<button class="primary" id="exportPdf">匯出 PDF</button>
<button class="secondary" id="sharePdf">分享 PDF</button>
<button class="secondary" id="printDoc">列印 / 另存 PDF</button>
<button class="primary" id="exportPdfBtn">匯出 PDF</button>
<button class="secondary" id="sharePdfBtn">分享 PDF</button>
<button class="secondary" id="printBtn">列印 / 另存 PDF</button>
<button class="secondary" id="copyTextBtn">複製契約文字</button>
</div>
<div class="output" id="output"></div>
<div class="hint">提示PDF 輸出目前跟第二版一樣,走簡單文字框樣式。</div>
<div class="hint">如果瀏覽器不支援直接分享檔案,還是可以先按「匯出 PDF」存檔再用 Line、Email 或其他方式傳出去。</div>
<div class="status" id="status"></div>
</aside>
<section class="preview-wrap">
<article class="paper" id="contractPaper">
<h2 class="contract-title">房屋租賃契約</h2>
<div class="contract-line"> 立契約書人 出租人 廖雅惠 (以下簡稱為甲方)</div>
<div class="contract-line"> 承租人 (以下簡稱為乙方)</div>
<div class="contract-line">因房屋租賃事件,訂立本契約,雙方同意之條件如左:</div>
<div class="contract-line">房屋所在地及使用範圍:台中市西屯區西平里文華路 217-5 號 2 樓 A 室</div>
<div class="contract-line">第二條 租賃期限:自民國 114 年 6 月 18 日至 115 年 6 月 17 日止計 1 年。</div>
<div class="contract-line">第三條 租金1. 每月租金新台幣 <span class="fill" id="rentValue"></span> 元,每月 <span class="fill" id="payDayValue"></span> 日以前繳納。</div>
<div class="contract-line"> 2. 保證金新台幣 <span class="fill" id="depositValue"></span> 元,於租賃期滿交還</div>
<div class="contract-line"> 3. 包 (管理費 網路 安博盒子 )</div>
<div class="contract-line"> 4. 附( 電視 冷氣 冰箱 洗衣機 雙人床 書桌 衣櫃 椅子 )</div>
<div class="contract-line">第四條 使用租賃物之限制:</div>
<div class="contract-line indent">1. 本房屋係供住家之用</div>
<div class="contract-line indent">2. 未經甲方同意,乙方不得將房屋全部或一部轉租、出借、頂讓,或以其他變相方法由他人使用房屋。</div>
<div class="contract-line indent">3. 乙方於租賃期滿應即將房屋遷讓交還,不得向甲方請求遷移費或任何費用。</div>
<div class="contract-line indent">4. 房屋不得供非法使用,或存放危險物品影響公共安全。</div>
<div class="contract-line indent">5. 房屋有改裝設施之必要,乙方取得甲方之同意後得自行裝設,但不得損害原有建築,乙方於交還房屋時應負責回復原狀不可以在牆上張貼任何物品如海報公佈欄</div>
<div class="contract-line">第五條 危險負擔:乙方應以善良管理人之注意使用房屋,除因天災地變等不可抗拒之情形外,因乙方之過失致房屋毀損,應負損害賠償之責。房屋因自然之損壞有修繕必要時,由甲方負責修理。</div>
<div class="contract-line">第六條 違約處罰:</div>
<div class="contract-line indent">1. 乙方違反約定方法使用房屋,或拖欠租金,經甲方催告限期繳納仍不支付時,甲方得終止租約。</div>
<div class="contract-line indent">2. 乙方於終止租約或租賃期滿不交還房屋,自終止租約或租賃期滿之歷日起,乙方應支付按房租壹倍計算之違約金。</div>
<div class="contract-line">第七條 其他特約事項:</div>
<div class="contract-line indent">1. 乙方遷出時,如遺留傢俱雜物不搬者,視為放棄,應由甲方處理及留存最後一期水電單以便結水電及退押金。</div>
<div class="contract-line indent">2. 本契約租賃期限未滿,住滿半年需扣一個月押金但需提前一個月告知及配合看屋。及一個月房租沒付房東可請房客遷出。</div>
<div class="contract-line indent">3. 退租時必須將房屋打掃乾淨,否收 800 元清潔費,不得養寵物 房間禁菸被發現強制退租及扣 2 個月押金。</div>
<div class="contract-line indent">4. 合約到期前一個月如不續租,須告知房東,並配合給其他房客看屋</div>
<div class="contract-line indent">5. 牆壁上不可張貼紙張或釘任何物品例如公佈欄</div>
<div class="contract-line indent">6. 如惡意輕生必須賠償甲方當初購屋款及裝潢費用新台幣 150 萬元整.</div>
<div class="contract-line indent">7. 電費 1 度 5 元,每月與房租計算</div>
<div class="contract-line">第八條 應受強制執行之事項:承租人給付租金或期限屆滿交還租賃物出租人返還保證金,如不履行應逕送強制執行。</div>
<div class="signature-block">
<div class="signature-row"> 出租人: 廖雅惠 簽章:</div>
<div class="signature-row">地 址:台中市西屯區西平里文華路 217-5 號 2 樓 A 室</div>
<div class="signature-row">電 話0918298185</div>
<div class="signature-row">承租人:           簽章:</div>
<div class="signature-row">身份證號: 緊急聯絡人:</div>
<div class="signature-row">地 址:</div>
<div class="signature-row">電 話:</div>
<div class="signature-row">匯款帳號:</div>
<div class="signature-row">中  華  民  國 年 月 日</div>
</div>
</article>
</section>
</div>
</div>
<script>
const defaultTemplate = `房屋租賃契約
立契約書人 出租人 廖雅惠 (以下簡稱為甲方)
承租人 (以下簡稱為乙方)
因房屋租賃事件,訂立本契約,雙方同意之條件如左:
房屋所在地及使用範圍:台中市西屯區西平里文華路 217-5 號 2 樓 A 室
第二條 租賃期限:自民國 114 年 6 月 18 日至 115 年 6 月 17 日止計 1 年。
第三條 租金:
1. 每月租金新台幣 {{每月租金}} 元,每月 {{繳款日期}} 日以前繳納。
2. 保證金新台幣 {{保證金}} 元,於租賃期滿交還。
3. 包(管理費、網路、安博盒子)
4. 附(電視、冷氣、冰箱、洗衣機、雙人床、書桌、衣櫃、椅子)
第四條 使用租賃物之限制:
1. 本房屋係供住家之用。
2. 未經甲方同意,乙方不得將房屋全部或一部轉租、出借、頂讓,或以其他變相方法由他人使用房屋。
3. 乙方於租賃期滿應即將房屋遷讓交還,不得向甲方請求遷移費或任何費用。
4. 房屋不得供非法使用,或存放危險物品影響公共安全。
5. 房屋有改裝設施之必要,乙方取得甲方之同意後得自行裝設,但不得損害原有建築,乙方於交還房屋時應負責回復原狀,不可以在牆上張貼任何物品如海報、公佈欄。
第五條 危險負擔:乙方應以善良管理人之注意使用房屋,除因天災地變等不可抗拒之情形外,因乙方之過失致房屋毀損,應負損害賠償之責。房屋因自然之損壞有修繕必要時,由甲方負責修理。
第六條 違約處罰:
1. 乙方違反約定方法使用房屋,或拖欠租金,經甲方催告限期繳納仍不支付時,甲方得終止租約。
2. 乙方於終止租約或租賃期滿不交還房屋,自終止租約或租賃期滿之歷日起,乙方應支付按房租壹倍計算之違約金。
第七條 其他特約事項:
1. 乙方遷出時,如遺留傢俱雜物不搬者,視為放棄,應由甲方處理及留存最後一期水電單以便結水電及退押金。
2. 本契約租賃期限未滿,住滿半年需扣一個月押金,但需提前一個月告知及配合看屋。及一個月房租沒付房東可請房客遷出。
3. 退租時必須將房屋打掃乾淨,否則收 800 元清潔費,不得養寵物,房間禁菸,被發現強制退租及扣 2 個月押金。
4. 合約到期前一個月如不續租,須告知房東,並配合給其他房客看屋。
5. 牆壁上不可張貼紙張或釘任何物品例如公佈欄。
6. 如惡意輕生必須賠償甲方當初購屋款及裝潢費用新台幣 150 萬元整。
7. 電費 1 度 5 元,每月與房租計算。
第八條 應受強制執行之事項:承租人給付租金或期限屆滿交還租賃物,出租人返還保證金,如不履行應逕送強制執行。
出租人:廖雅惠
地址:台中市西屯區西平里文華路 217-5 號 2 樓 A 室
電話0918298185
承租人:
簽章:
身份證號:
緊急聯絡人:
地址:
電話:
匯款帳號:
中華民國 年 月 日`;
const fieldsContainer = document.getElementById("fields");
const output = document.getElementById("output");
const timeLabel = document.getElementById("timeLabel");
const status = document.getElementById("status");
const values = {
"每月租金": "",
"繳款日期": "",
"保證金": ""
};
const allowedFields = ["每月租金", "繳款日期", "保證金"];
const rentInput = document.getElementById("rentInput");
const payDayInput = document.getElementById("payDayInput");
const depositInput = document.getElementById("depositInput");
const rentValue = document.getElementById("rentValue");
const payDayValue = document.getElementById("payDayValue");
const depositValue = document.getElementById("depositValue");
const contractPaper = document.getElementById("contractPaper");
const pdfPageMargin = 22;
const pdfSideMargin = 18;
function updateTaipeiTime() {
const formatter = new Intl.DateTimeFormat("zh-TW", {
timeZone: "Asia/Taipei",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
timeZone: "Asia/Taipei", year: "numeric", month: "2-digit", day: "2-digit",
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false
});
timeLabel.textContent = `台北時間:${formatter.format(new Date())}`;
}
function setStatus(message) {
status.textContent = message;
function setStatus(message) { status.textContent = message; }
function syncValues() {
rentValue.textContent = rentInput.value || "    ";
payDayValue.textContent = payDayInput.value || "  ";
depositValue.textContent = depositInput.value || "    ";
}
function compiledText() {
return defaultTemplate.replace(/{{\s*([^{}]+)\s*}}/g, (_, key) => {
const name = key.trim();
return values[name] ?? "";
});
}
function renderFields() {
fieldsContainer.innerHTML = "";
allowedFields.forEach(name => {
const card = document.createElement("div");
card.className = "field-card";
const label = document.createElement("label");
label.setAttribute("for", `field-${name}`);
label.textContent = name;
const input = document.createElement("input");
input.id = `field-${name}`;
input.type = "text";
input.value = values[name] || "";
input.placeholder = `請輸入 ${name}`;
input.addEventListener("input", () => {
values[name] = input.value;
renderOutput();
});
card.append(label, input);
fieldsContainer.appendChild(card);
});
}
function renderOutput() {
output.textContent = compiledText();
}
function buildFileName() {
const rent = (values["每月租金"] || "未填租金").replace(/[\\/:*?"<>|]/g, "_");
const day = (values["繳款日期"] || "未填日期").replace(/[\\/:*?"<>|]/g, "_");
return `租屋契約_租金${rent}_日${day}.pdf`;
const rent = (rentInput.value || "未填租金").replace(/[\\/:*?"<>|]/g, "_");
const payDay = (payDayInput.value || "未填日期").replace(/[\\/:*?"<>|]/g, "_");
return `租屋契約_逢甲A_租金${rent}_繳款${payDay}.pdf`;
}
function createPdfElement() {
const div = document.createElement("div");
div.className = "pdf-export";
div.textContent = output.textContent;
return div;
}
async function exportPdfBlob() {
const pdfElement = createPdfElement();
return window.html2pdf().set({
margin: 10,
function buildPdfOptions() {
return {
margin: [pdfPageMargin, pdfSideMargin, pdfPageMargin, pdfSideMargin],
filename: buildFileName(),
image: { type: "jpeg", quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, backgroundColor: "#ffffff" },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }
}).from(pdfElement).outputPdf("blob");
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
pagebreak: {
mode: ["css", "legacy"],
avoid: [".contract-title", ".contract-line", ".signature-row"]
}
};
}
document.getElementById("printDoc").addEventListener("click", () => {
setStatus("已開啟列印視窗,可另存為 PDF。");
async function withPdfExportMode(callback) {
contractPaper.classList.add("pdf-export-mode");
try {
await callback();
} finally {
contractPaper.classList.remove("pdf-export-mode");
}
}
async function exportPdfFile() {
await withPdfExportMode(() => {
return window.html2pdf().set(buildPdfOptions()).from(contractPaper).save();
});
}
async function createPdfBlob() {
let blob;
await withPdfExportMode(async () => {
const worker = window.html2pdf().set(buildPdfOptions()).from(contractPaper).toPdf();
const pdf = await worker.get("pdf");
blob = pdf.output("blob");
});
return blob;
}
function buildContractText() {
return [
"房屋租賃契約",
" 立契約書人 出租人 廖雅惠 (以下簡稱為甲方)",
"承租人 (以下簡稱為乙方)",
"因房屋租賃事件,訂立本契約,雙方同意之條件如左:",
"房屋所在地及使用範圍:台中市西屯區西平里文華路 217-5 號 2 樓 A 室",
"第二條 租賃期限:自民國 114 年 6 月 18 日至 115 年 6 月 17 日止計 1 年。",
`第三條 租金1. 每月租金新台幣 ${rentInput.value || ""} 元,每月 ${payDayInput.value || ""} 日以前繳納。`,
` 2. 保證金新台幣 ${depositInput.value || ""} 元,於租賃期滿交還`,
" 3. 包 (管理費 網路 安博盒子 )",
" 4. 附( 電視 冷氣 冰箱 洗衣機 雙人床 書桌 衣櫃 椅子 )"
].join("\n");
}
rentInput.addEventListener("input", syncValues);
payDayInput.addEventListener("input", syncValues);
depositInput.addEventListener("input", syncValues);
document.getElementById("copyTextBtn").addEventListener("click", async () => {
try { await navigator.clipboard.writeText(buildContractText()); setStatus("已複製契約文字。"); }
catch { setStatus("複製失敗,請檢查瀏覽器剪貼簿權限。"); }
});
document.getElementById("printBtn").addEventListener("click", () => {
setStatus("已開啟列印視窗,可選擇另存為 PDF。");
window.print();
});
document.getElementById("exportPdf").addEventListener("click", async () => {
try {
setStatus("正在匯出 PDF...");
const pdfElement = createPdfElement();
await window.html2pdf().set({
margin: 10,
filename: buildFileName(),
image: { type: "jpeg", quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, backgroundColor: "#ffffff" },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }
}).from(pdfElement).save();
setStatus("PDF 已匯出。");
} catch (error) {
setStatus("匯出失敗,請改用列印 / 另存 PDF。");
}
document.getElementById("exportPdfBtn").addEventListener("click", async () => {
try { setStatus("正在匯出 PDF..."); await exportPdfFile(); setStatus(`PDF 已匯出:${buildFileName()}`); }
catch { setStatus("匯出失敗,改用列印另存 PDF 也可以。"); }
});
document.getElementById("sharePdf").addEventListener("click", async () => {
document.getElementById("sharePdfBtn").addEventListener("click", async () => {
try {
if (!navigator.share || !navigator.canShare) {
throw new Error("not-supported");
}
if (!navigator.share || !navigator.canShare) throw new Error("目前瀏覽器不支援檔案分享");
setStatus("正在產生分享用 PDF...");
const blob = await exportPdfBlob();
const blob = await createPdfBlob();
const file = new File([blob], buildFileName(), { type: "application/pdf" });
if (!navigator.canShare({ files: [file] })) {
throw new Error("cannot-share-file");
}
await navigator.share({
title: "租屋契約 PDF",
text: "這是帶入後的租屋契約。",
files: [file]
});
setStatus("已開啟分享視窗。");
} catch (error) {
setStatus("分享失敗或瀏覽器不支援,請先匯出 PDF。");
if (!navigator.canShare({ files: [file] })) throw new Error("這個裝置不支援分享 PDF 檔");
await navigator.share({ title: "租屋契約 PDF", text: "這是已帶入租金資料的租屋契約。", files: [file] });
setStatus("PDF 已開啟分享視窗。");
} catch {
setStatus("分享失敗或瀏覽器不支援,請先匯出 PDF 後再傳送。");
}
});
renderFields();
renderOutput();
updateTaipeiTime();
syncValues();
setInterval(updateTaipeiTime, 1000);
</script>
</body>