hrms-user/src/modules/12_organization/views/main.vue
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 fbe3b62a95 load orgchart
2025-07-18 15:06:41 +07:00

342 lines
9.6 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, nextTick, watch } from "vue";
import { useQuasar } from "quasar";
import { useRouter } from "vue-router";
import http from "@/plugins/http";
import config from "@/app.config";
import { StructChart } from "structure-chart";
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();
const mixin = useCounterMixin();
const { showLoader, hideLoader, messageError } = mixin;
const chartRef = ref(); // อ้างอิงไปที่ตัว chart
const theBreadcrumb = ref();
const rootOrgID = ref(); // org id ของ root ตัวปัจจุบันที่เลือกอยู่
const dataSource = ref(); // ข้อมูล Chart
const idActive = ref<string>("");
const dataSourceLock = ref(); // ข้อมูลตั้งต้นของ Chart ใช้ให้กดกลับไปที่ home
const scrollContainer = ref<HTMLElement | null>(null);
/**
* สร้าง Path Breadcrumbs
*/
function breadcrumbsGen() {
if (rootOrgID.value !== 0) {
theBreadcrumb.value = [];
const newPath = findPath(rootOrgID.value);
theBreadcrumb.value = newPath;
}
}
/**
* === กระบวนการสร้าง Path ===
*/
/**
* Recursive เพื่อหา Path ที่ผู้ใช้คลิก
* @param id OrgID ของหน่วยงานที่ต้องการหา
* @param chart Array ของ Object ของหน่วยงานย่อยใน Tree นั้น ๆ
*/
function chartTraverse(id: any, chart: any): any {
let _returnPath = [];
for (const child of chart) {
if (child.deptID === id) {
_returnPath.push({
label: child.departmentName,
id: child.deptID,
type: child.type,
});
return _returnPath;
} else {
if (
typeof child.children !== "undefined" &&
child.children !== null &&
child.children.length > 0
) {
let _result = chartTraverse(id, child.children);
if (typeof _result !== "undefined" && _result !== null) {
_returnPath.push({
label: child.departmentName,
id: child.deptID,
type: child.type,
});
return [..._returnPath, ...(_result ?? [])];
}
}
}
}
}
function findPath(id: any) {
let _path = [];
_path.push({
label: dataSourceLock.value.departmentName,
id: dataSourceLock.value.deptID,
type: dataSourceLock.value.type,
});
if (dataSourceLock.value.deptID === id) return _path;
if (dataSourceLock.value.children.length > 0) {
let _returnPath = chartTraverse(id, dataSourceLock.value.children);
return [..._path, ...(_returnPath ?? [])];
}
}
const isLoadBtn = ref(false);
/** function ดาวน์โหลดไฟล์โครงสร้าง PNG*/
async function savePNG() {
showLoadingSpinner();
isLoadBtn.value = true;
setTimeout(async () => {
try {
// export แบบเต็มๆ
scrollContainer.value && (await exportChartToPNG(scrollContainer.value));
} finally {
isLoadBtn.value = false;
}
}, 500);
}
/** function ดาวน์โหลดไฟล์โครงสร้าง PDF*/
async function savePDF() {
showLoadingSpinner();
isLoadBtn.value = true;
setTimeout(async () => {
try {
// export แบบเต็มๆ
scrollContainer.value && (await exportChartToPDF(scrollContainer.value));
} finally {
isLoadBtn.value = false;
}
}, 500);
}
/** ฟังก์ชันเลื่อน scroll ไปที่กึ่งกลาง*/
async function scrollToCenter() {
const container = scrollContainer.value;
if (container) {
container.scrollLeft = (container.scrollWidth - container.clientWidth) / 2;
}
}
/**
* เมื่อมีการคลิกที่ Chart ให้อ่าน ID ของหน่วยงานที่ถูกคลิก แล้วดึงข้อมูล Chart ของหน่วยงานนั้น ๆ จาก API
* @param data
*/
async function refreshChart(data: any, type: number) {
if (isLoadBtn.value) return; // ถ้าโหลดอยู่ไม่ให้ทำอะไร
if (data.value === undefined) {
fetchStructChart(data, type.toString());
rootOrgID.value = data;
} else {
searchAndReplace(dataSource.value, data.value);
}
}
async function fetchStructChart(
id: string,
type: string,
status: boolean = false
) {
showLoader();
await http
.get(config.API.orgStructChart(id, type))
.then((res) => {
if (res.data.result.length > 0) {
const data = res.data.result[0];
const struct = [];
struct.push({ ...data, officer: [], heads: [] });
dataSource.value = struct[0];
if (dataSourceLock.value === undefined || status)
dataSourceLock.value = dataSource.value;
breadcrumbsGen();
}
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
}
function searchAndReplace(data: any, id: string) {
if (data.deptID === id) {
fetchStructChart(data.deptID, data.type.toString());
rootOrgID.value = data.deptID;
} else if (data.children) {
for (const child of data.children) {
searchAndReplace(child, id);
}
}
}
async function getActive() {
http
.get(config.API.orgActive)
.then(async (res) => {
const data = await res.data.result;
idActive.value = data.activeId;
fetchStructChart(data.activeId, "0");
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {});
}
// Watch เมื่อข้อมูลใน dataSource เปลี่ยนแปลง
watch(
dataSource,
() => {
// ใช้ nextTick เพื่อให้ DOM อัปเดตเสร็จก่อนค่อยเลื่อน scroll
nextTick(() => {
scrollToCenter();
});
},
{ deep: true } // ตรวจจับการเปลี่ยนแปลงภายในข้อมูล
);
/** hook lifecycle*/
onMounted(async () => {
await getActive();
});
</script>
<template>
<div class="col-12 row justify-center">
<div class="col-xs-12 col-sm-12 col-md-11">
<div class="toptitle text-white col-12 row items-center">
<q-btn
icon="mdi-arrow-left"
unelevated
round
dense
flat
color="primary"
class="q-mr-sm"
@click="router.push('/')"
/>
<div>แผนผงองคกร</div>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-11 row q-col-gutter-md">
<div class="col-12 row">
<q-card bordered class="col-12 row caedNone">
<div class="q-pa-sm row wrap items-center">
<q-btn
flat
:diasble="isLoadBtn"
round
:color="!isLoadBtn ? 'primary' : 'grey-7'"
@click="savePNG()"
icon="mdi-image"
>
<q-tooltip> ดาวนโหลด PNG </q-tooltip>
</q-btn>
<q-btn
flat
round
:color="!isLoadBtn ? 'red-7' : 'grey-7'"
:diasble="isLoadBtn"
@click="savePDF()"
icon="mdi-file-pdf-box"
>
<q-tooltip> ดาวนโหลด PDF </q-tooltip>
</q-btn>
<div class="bg-grey-2 q-py-xs q-px-sm rounded-borders">
<q-breadcrumbs>
<template v-slot:separator>
<q-icon size="1.5em" name="chevron_right" color="primary" />
</template>
<template v-for="link in theBreadcrumb" :key="link.id">
<q-breadcrumbs-el
:label="link.label"
@click="refreshChart(link.id, link.type)"
class="breadcrumbs-link"
/>
</template>
</q-breadcrumbs>
</div>
</div>
<div class="col-12">
<q-separator />
</div>
<div
id="structChart"
ref="scrollContainer"
style="
overflow-x: auto;
overflow-y: auto;
height: 70vh;
position: relative;
"
class="q-pt-md"
>
<StructChart
style="height: 70vh"
ref="chartRef"
class="struct"
:dataSource="dataSource"
@onElementClick="refreshChart"
/>
</div>
</q-card>
</div>
</div>
</div>
</template>
<style scoped>
.breadcrumbs-link {
cursor: pointer;
}
.struct .section-primary .header,
.struct .section-secondary .header,
.struct .section-tertiary .header {
font-size: 1rem !important;
}
.struct .section-primary .column-side .side-button,
.struct .section-secondary .column-side .side-button,
.struct .section-list .column-side .side-button {
background-color: #d9d9d96b !important;
color: #545459 !important;
width: 25px !important;
height: 25px !important;
font-size: 11px !important;
padding: 0px;
}
.struct .section-primary,
.section-secondary,
.struct .section-list {
padding: 6px 15px 6px 15px;
}
.struct .section-primary .header {
font-weight: 600 !important;
}
.struct .section-secondary .header {
font-weight: 400 !important;
}
.struct .subchild-more {
background-position: center !important;
background-size: 14px !important;
}
</style>