Merge branch 'develop' into devTee

This commit is contained in:
setthawutttty 2025-07-21 13:21:18 +07:00
commit 6f330cfd83
9 changed files with 267 additions and 36 deletions

View file

@ -176,6 +176,11 @@ const baseColumns = ref<QTableColumn[]>([
label: "ตำแหน่งทางการบริหาร",
sortable: true,
field: "positionExecutive",
format(val, row) {
return !row.positionExecutiveField
? val
: `${val} (${row.positionExecutiveField})`;
},
headerStyle: "font-size: 14px",
style: "font-size: 14px",
sort: (a: string, b: string) =>
@ -189,7 +194,9 @@ const baseColumns = ref<QTableColumn[]>([
field: "commandNo",
format(val, row) {
return row.commandNo && row.commandYear
? `${row.commandNo}/${Number(row.commandYear) + 543}`
? row.commandType !== "C-PM-47"
? `${row.commandNo}/${Number(row.commandYear) + 543}`
: `${row.commandNo}`
: "";
},
headerStyle: "font-size: 14px",

View file

@ -160,6 +160,11 @@ const baseColumns = ref<QTableColumn[]>([
label: "ตำแหน่งทางการบริหาร",
sortable: true,
field: "positionExecutive",
format(val, row) {
return !row.positionExecutiveField
? val
: `${val} (${row.positionExecutiveField})`;
},
headerStyle: "font-size: 14px",
style: "font-size: 14px",
sort: (a: string, b: string) =>
@ -173,7 +178,9 @@ const baseColumns = ref<QTableColumn[]>([
field: "commandNo",
format(val, row) {
return row.commandNo && row.commandYear
? `${row.commandNo}/${Number(row.commandYear) + 543}`
? row.commandType !== "C-PM-47"
? `${row.commandNo}/${Number(row.commandYear) + 543}`
: `${row.commandNo}`
: "";
},
headerStyle: "font-size: 14px",

View file

@ -196,6 +196,7 @@ interface SalaryFormType {
refCommandNo: string | null;
templateDoc: string;
order: number;
commandType: string;
}
interface NopaidFormType {

View file

@ -10,6 +10,12 @@ import { useCounterMixin } from "@/stores/mixin";
import "structure-chart/structure-chart.css";
import {
exportChartToPNG,
exportChartToPDF,
showLoadingSpinner,
} from "@/plugins/exportChart";
/** use*/
const $q = useQuasar();
const router = useRouter();
@ -88,30 +94,33 @@ function findPath(id: any) {
}
}
const isLoadBtn = ref(false);
/** function ดาวน์โหลดไฟล์โครงสร้าง PNG*/
async function savePNG() {
try {
showLoader();
await scrollToCenter();
await chartRef.value.savePNG();
} catch {
messageError($q);
} finally {
hideLoader();
}
showLoadingSpinner();
isLoadBtn.value = true;
setTimeout(async () => {
try {
// export
scrollContainer.value && (await exportChartToPNG(scrollContainer.value));
} finally {
isLoadBtn.value = false;
}
}, 500);
}
/** function ดาวน์โหลดไฟล์โครงสร้าง PDF*/
async function savePDF() {
try {
showLoader();
await scrollToCenter();
await chartRef.value.savePDF();
} catch {
messageError($q);
} finally {
hideLoader();
}
showLoadingSpinner();
isLoadBtn.value = true;
setTimeout(async () => {
try {
// export
scrollContainer.value && (await exportChartToPDF(scrollContainer.value));
} finally {
isLoadBtn.value = false;
}
}, 500);
}
/** ฟังก์ชันเลื่อน scroll ไปที่กึ่งกลาง*/
@ -127,6 +136,7 @@ async function scrollToCenter() {
* @param data
*/
async function refreshChart(data: any, type: number) {
if (isLoadBtn.value) return; //
if (data.value === undefined) {
fetchStructChart(data, type.toString());
rootOrgID.value = data;
@ -229,8 +239,9 @@ onMounted(async () => {
<div class="q-pa-sm row wrap items-center">
<q-btn
flat
:diasble="isLoadBtn"
round
color="primary"
:color="!isLoadBtn ? 'primary' : 'grey-7'"
@click="savePNG()"
icon="mdi-image"
>
@ -239,7 +250,8 @@ onMounted(async () => {
<q-btn
flat
round
color="red-7"
:color="!isLoadBtn ? 'red-7' : 'grey-7'"
:diasble="isLoadBtn"
@click="savePDF()"
icon="mdi-file-pdf-box"
>
@ -263,7 +275,9 @@ onMounted(async () => {
<div class="col-12">
<q-separator />
</div>
<div
id="structChart"
ref="scrollContainer"
style="
overflow-x: auto;

115
src/plugins/exportChart.ts Normal file
View file

@ -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 = `
<div style="display:flex;flex-direction:column;align-items:center;">
<div style="
border: 8px solid #f3f3f3;
border-top: 8px solid #3498db;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
"></div>
</div>
<style>
@keyframes spin {
0% { transform: rotate(0deg);}
100% { transform: rotate(360deg);}
}
</style>
`;
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<void> {
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<void> {
// ใช้ 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();
}
}

View file

@ -225,7 +225,13 @@ export const useCounterMixin = defineStore("mixin", () => {
}
function monthYear2Thai(month: number, year: number, isFullMonth = false) {
const date = new Date(`${year}-${month + 1}-1`);
if (
month < 0 ||
month > 11 ||
!Number.isFinite(month) ||
!Number.isFinite(year)
)
return "";
const fullMonthThai = [
"มกราคม",
"กุมภาพันธ์",
@ -254,19 +260,12 @@ export const useCounterMixin = defineStore("mixin", () => {
"พ.ย.",
"ธ.ค.",
];
let dstYear = 0;
if (date.getFullYear() > 2500) {
dstYear = date.getFullYear();
} else {
dstYear = date.getFullYear() + 543;
}
let dstMonth = "";
if (isFullMonth) {
dstMonth = fullMonthThai[date.getMonth()];
} else {
dstMonth = abbrMonthThai[date.getMonth()];
}
return dstMonth + " " + dstYear;
// assume year is in BE if > 2500
let dstYear = year > 2500 ? year : year + 543;
// month is already 0-based
let dstMonth = isFullMonth ? fullMonthThai[month] : abbrMonthThai[month];
return `${dstMonth} ${dstYear}`;
}
function dateToISO(date: Date) {

36
src/types/dom-to-image-more.d.ts vendored Normal file
View file

@ -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<string>;
export function toJpeg(node: HTMLElement, options?: Options): Promise<string>;
export function toSvg(node: HTMLElement, options?: Options): Promise<string>;
export function toPixelData(
node: HTMLElement,
options?: Options
): Promise<Uint8ClampedArray>;
export function toCanvas(
node: HTMLElement,
options?: Options
): Promise<HTMLCanvasElement>;
export function toBlob(node: HTMLElement, options?: Options): Promise<Blob>;
const domtoimage: {
toPng: typeof toPng;
toJpeg: typeof toJpeg;
toSvg: typeof toSvg;
toPixelData: typeof toPixelData;
toCanvas: typeof toCanvas;
toBlob: typeof toBlob;
};
export default domtoimage;
}