167 lines
4.7 KiB
JavaScript
167 lines
4.7 KiB
JavaScript
|
|
const form = document.querySelector('#contractForm');
|
||
|
|
const templateSelect = document.querySelector('#template');
|
||
|
|
const submitButton = document.querySelector('#submitButton');
|
||
|
|
const downloadButton = document.querySelector('#downloadButton');
|
||
|
|
const shareButton = document.querySelector('#shareButton');
|
||
|
|
const message = document.querySelector('#message');
|
||
|
|
const resultTitle = document.querySelector('#resultTitle');
|
||
|
|
const connectionStatus = document.querySelector('#connectionStatus');
|
||
|
|
|
||
|
|
let currentPdfBlob = null;
|
||
|
|
let currentPdfFileName = '租屋契約.pdf';
|
||
|
|
|
||
|
|
loadTemplates();
|
||
|
|
|
||
|
|
form.addEventListener('submit', async (event) => {
|
||
|
|
event.preventDefault();
|
||
|
|
setBusy(true);
|
||
|
|
resetPdf();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const formData = new FormData(form);
|
||
|
|
const response = await fetch('/api/contracts/pdf', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
template: formData.get('template'),
|
||
|
|
monthlyRent: formData.get('monthlyRent'),
|
||
|
|
paymentDay: formData.get('paymentDay'),
|
||
|
|
deposit: formData.get('deposit'),
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorBody = await response.json().catch(() => ({}));
|
||
|
|
throw new Error(errorBody.error || 'PDF 產生失敗。');
|
||
|
|
}
|
||
|
|
|
||
|
|
currentPdfBlob = await response.blob();
|
||
|
|
currentPdfFileName = getFileNameFromDisposition(response.headers.get('Content-Disposition'))
|
||
|
|
|| buildDefaultFileName(formData.get('template'));
|
||
|
|
|
||
|
|
resultTitle.textContent = 'PDF 已產生';
|
||
|
|
message.textContent = currentPdfFileName;
|
||
|
|
downloadButton.disabled = false;
|
||
|
|
shareButton.disabled = !canShareCurrentPdf();
|
||
|
|
} catch (error) {
|
||
|
|
resultTitle.textContent = '產生失敗';
|
||
|
|
message.textContent = error.message;
|
||
|
|
} finally {
|
||
|
|
setBusy(false);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
downloadButton.addEventListener('click', () => {
|
||
|
|
if (!currentPdfBlob) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
downloadPdf();
|
||
|
|
});
|
||
|
|
|
||
|
|
shareButton.addEventListener('click', async () => {
|
||
|
|
if (!currentPdfBlob) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const file = new File([currentPdfBlob], currentPdfFileName, { type: 'application/pdf' });
|
||
|
|
if (!navigator.canShare || !navigator.canShare({ files: [file] })) {
|
||
|
|
downloadPdf();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await navigator.share({
|
||
|
|
title: '租屋契約 PDF',
|
||
|
|
files: [file],
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
if (error.name !== 'AbortError') {
|
||
|
|
message.textContent = '分享失敗,已保留下載按鈕。';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
async function loadTemplates() {
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/templates');
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error('無法讀取範本。');
|
||
|
|
}
|
||
|
|
|
||
|
|
const body = await response.json();
|
||
|
|
templateSelect.replaceChildren(
|
||
|
|
...body.templates.map((name) => new Option(name, name)),
|
||
|
|
);
|
||
|
|
|
||
|
|
if (body.templates.length === 0) {
|
||
|
|
templateSelect.append(new Option('templates 資料夾沒有 .doc 範本', ''));
|
||
|
|
templateSelect.disabled = true;
|
||
|
|
submitButton.disabled = true;
|
||
|
|
connectionStatus.textContent = '缺少範本';
|
||
|
|
message.textContent = '請先將 .doc 範本放進 templates 資料夾。';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
connectionStatus.textContent = '可使用';
|
||
|
|
} catch (error) {
|
||
|
|
templateSelect.append(new Option('讀取失敗', ''));
|
||
|
|
templateSelect.disabled = true;
|
||
|
|
submitButton.disabled = true;
|
||
|
|
connectionStatus.textContent = '離線';
|
||
|
|
message.textContent = error.message;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function setBusy(isBusy) {
|
||
|
|
submitButton.disabled = isBusy || templateSelect.disabled;
|
||
|
|
submitButton.textContent = isBusy ? '產生中' : '產生 PDF';
|
||
|
|
}
|
||
|
|
|
||
|
|
function resetPdf() {
|
||
|
|
currentPdfBlob = null;
|
||
|
|
downloadButton.disabled = true;
|
||
|
|
shareButton.disabled = true;
|
||
|
|
resultTitle.textContent = '產生中';
|
||
|
|
message.textContent = '正在建立 PDF。';
|
||
|
|
}
|
||
|
|
|
||
|
|
function canShareCurrentPdf() {
|
||
|
|
if (!currentPdfBlob || !navigator.canShare) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const file = new File([currentPdfBlob], currentPdfFileName, { type: 'application/pdf' });
|
||
|
|
return navigator.canShare({ files: [file] });
|
||
|
|
}
|
||
|
|
|
||
|
|
function downloadPdf() {
|
||
|
|
const url = URL.createObjectURL(currentPdfBlob);
|
||
|
|
const link = document.createElement('a');
|
||
|
|
link.href = url;
|
||
|
|
link.download = currentPdfFileName;
|
||
|
|
document.body.append(link);
|
||
|
|
link.click();
|
||
|
|
link.remove();
|
||
|
|
URL.revokeObjectURL(url);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getFileNameFromDisposition(disposition) {
|
||
|
|
if (!disposition) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
const utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i);
|
||
|
|
if (utf8Match) {
|
||
|
|
return decodeURIComponent(utf8Match[1]);
|
||
|
|
}
|
||
|
|
|
||
|
|
const asciiMatch = disposition.match(/filename="([^"]+)"/i);
|
||
|
|
return asciiMatch ? asciiMatch[1] : '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildDefaultFileName(templateName) {
|
||
|
|
const baseName = String(templateName || '租屋契約').replace(/\.[^.]+$/, '');
|
||
|
|
return `${baseName}.pdf`;
|
||
|
|
}
|