diff --git a/package.json b/package.json index bb26ccfd3..f6919af12 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,11 @@ "arcgis-js-api": "^4.28.10", "axios": "^1.6.7", "bma-org-chart": "^0.0.8", + "dom-to-image-more": "^3.6.0", "esri-loader": "^3.7.0", "keycloak-js": "^20.0.2", "moment": "^2.29.4", + "pdf-lib": "^1.17.1", "pinia": "^2.0.29", "quasar": "^2.11.1", "socket.io-client": "^4.7.4", diff --git a/src/modules/02_organization/components/StructureMain.vue b/src/modules/02_organization/components/StructureMain.vue index 9a007522e..a19beaadc 100644 --- a/src/modules/02_organization/components/StructureMain.vue +++ b/src/modules/02_organization/components/StructureMain.vue @@ -8,6 +8,11 @@ import http from "@/plugins/http"; import config from "@/app.config"; import { useCounterMixin } from "@/stores/mixin"; import { useOrganizational } from "@/modules/02_organization/store/organizational"; +import { + showLoadingSpinner, + exportChartToPDF, + exportChartToPNG, +} from "@/plugins/exportChart"; const $q = useQuasar(); const mixin = useCounterMixin(); @@ -23,28 +28,45 @@ const scrollContainer = ref(null); /** function ดาวน์โหลดไฟล์โครงสร้าง PNG*/ async function savePNG() { - try { - showLoader(); - await scrollToCenter(); - await chartRef.value.savePNG(); - } catch { - messageError($q); - } finally { - hideLoader(); - } + // try { + // showLoader(); + // await scrollToCenter(); + // await chartRef.value.savePNG(); + // } catch { + // messageError($q); + // } finally { + // hideLoader(); + // } + showLoadingSpinner(); + setTimeout(async () => { + try { + // export แบบเต็มๆ + scrollContainer.value && (await exportChartToPNG(scrollContainer.value)); + } finally { + } + }, 500); } /** function ดาวน์โหลดไฟล์โครงสร้าง PDF*/ async function savePDF() { - try { - showLoader(); - await scrollToCenter(); - await chartRef.value.savePDF(); - } catch { - messageError($q); - } finally { - hideLoader(); - } + // try { + // showLoader(); + // await scrollToCenter(); + // await chartRef.value.savePDF(); + // } catch { + // messageError($q); + // } finally { + // hideLoader(); + // } + + showLoadingSpinner(); + setTimeout(async () => { + try { + // export แบบเต็มๆ + scrollContainer.value && (await exportChartToPDF(scrollContainer.value)); + } finally { + } + }, 500); } /** diff --git a/src/plugins/exportChart.ts b/src/plugins/exportChart.ts new file mode 100644 index 000000000..31ea0ff26 --- /dev/null +++ b/src/plugins/exportChart.ts @@ -0,0 +1,115 @@ +import domtoimage from "dom-to-image-more"; +import { PDFDocument } from "pdf-lib"; + +/** ฟังก์ชันสำหรับแสดง loading spinner */ +export function showLoadingSpinner() { + const loading = document.createElement("div"); + loading.id = "loading-spinner"; + loading.style.position = "fixed"; + loading.style.top = "0"; + loading.style.left = "0"; + loading.style.width = "100vw"; + loading.style.height = "100vh"; + loading.style.background = "rgba(0,0,0,0.2)"; + loading.style.display = "flex"; + loading.style.alignItems = "center"; + loading.style.justifyContent = "center"; + loading.style.zIndex = "9999"; + loading.innerHTML = ` +
+
+ +
+ + `; + document.body.appendChild(loading); +} + +/** ฟังก์ชันสำหรับซ่อน loading spinner */ +function hideLoadingSpinner() { + const loading = document.getElementById("loading-spinner"); + if (loading) loading.remove(); +} + +/** + * ฟังก์ชันสำหรับ export โครงสร้างองค์กรเป็น PNG + * @param node HTMLElement ที่ต้องการ export เป็น PNG + */ +export async function exportChartToPNG(node: HTMLElement): Promise { + if (node) { + try { + // สร้าง PNG จาก DOM ขนาดเต็ม + const imageData = await domtoimage.toPng(node, { + bgcolor: "#fff", + quality: 1, + width: node.scrollWidth, + height: node.scrollHeight, + }); + + const link = document.createElement("a"); + link.download = "orgchart.png"; + link.href = imageData; + link.click(); + } catch (error: any) { + alert("Export ไม่สำเร็จ: " + error.message); + } finally { + hideLoadingSpinner(); + } + } +} + +/** + * ฟังก์ชันสำหรับ export โครงสร้างองค์กรเป็น PNG + * @param node HTMLElement ที่ต้องการ export เป็น PNG + */ +export async function exportChartToPDF(node: HTMLElement): Promise { + // ใช้ scrollWidth/scrollHeight เพื่อขนาดเต็ม + const width = node.scrollWidth; + const height = node.scrollHeight; + + try { + // สร้าง PNG จาก DOM ขนาดเต็ม + const imageData = await domtoimage.toPng(node, { + width, + height, + bgcolor: "#fff", + }); + // สร้าง PDF ด้วย pdf-lib + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage([width, height]); + const pngImage = await pdfDoc.embedPng(imageData); + page.drawImage(pngImage, { + x: 0, + y: 0, + width, + height, + }); + const pdfBytes = await pdfDoc.save(); + // ดาวน์โหลด PDF + const blob = new Blob([pdfBytes], { type: "application/pdf" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "orgchart.pdf"; + a.click(); + URL.revokeObjectURL(url); + } catch (err: any) { + alert("Export ไม่สำเร็จ: " + err.message); + + console.error(err); + } finally { + hideLoadingSpinner(); + } +} diff --git a/src/types/dom-to-image-more.d.ts b/src/types/dom-to-image-more.d.ts new file mode 100644 index 000000000..3e07abad6 --- /dev/null +++ b/src/types/dom-to-image-more.d.ts @@ -0,0 +1,36 @@ +declare module "dom-to-image-more" { + export interface Options { + filter?: (node: Node) => boolean; + bgcolor?: string; + width?: number; + height?: number; + style?: any; + quality?: number; + imagePlaceholder?: string; + cacheBust?: boolean; + } + + export function toPng(node: HTMLElement, options?: Options): Promise; + export function toJpeg(node: HTMLElement, options?: Options): Promise; + export function toSvg(node: HTMLElement, options?: Options): Promise; + export function toPixelData( + node: HTMLElement, + options?: Options + ): Promise; + export function toCanvas( + node: HTMLElement, + options?: Options + ): Promise; + export function toBlob(node: HTMLElement, options?: Options): Promise; + + const domtoimage: { + toPng: typeof toPng; + toJpeg: typeof toJpeg; + toSvg: typeof toSvg; + toPixelData: typeof toPixelData; + toCanvas: typeof toCanvas; + toBlob: typeof toBlob; + }; + + export default domtoimage; +}