Compare commits

...

26 commits
v1.0.8 ... dev

Author SHA1 Message Date
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
89801e787f Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-05-29 13:55:22 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
a0f0443a55 refactor: downloadBlobFile with direct blob download logic 2026-05-29 13:55:05 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
d022ae189c Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m17s
2026-05-29 11:36:06 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
650de029f3 feat(leave): DownloadFile form leave 2026-05-29 11:35:50 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
423b9f5fe3 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m31s
2026-05-29 10:55:56 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
54c7855b61 feat(leave): display leaveCountApproveCount 2026-05-29 10:55:41 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
2a27dadff7 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-05-25 13:25:11 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
0092133ba6 fix: await API fileByFile 2026-05-25 13:24:55 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
b845cdaeab Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m30s
2026-05-11 09:24:58 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
9bee07eb7c refactor:(genreport) : import downloadBlobFile 2026-05-11 09:24:23 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
0a68951571 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m16s
2026-05-08 17:57:05 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
ebd514a33a fix:downloadFileName 2026-05-08 17:56:45 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
58c6150ef8 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-05-08 17:39:28 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
f07cf25489 refactor(registry): downloadBlobFile 2026-05-08 17:39:01 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
814c17d4c9 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m21s
2026-05-07 09:36:46 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
9eaa28711d refactor(convertDateToAPI): parsedDate new Date 2026-05-07 09:36:31 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
d00d4ac692 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m21s
2026-05-06 17:46:26 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
780a815a24 refactor(date): function convertDateToAPI delete utcToZonedTime Asia/Bangkok 2026-05-06 17:46:04 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
6ad798a2a2 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-05-01 11:24:41 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
e54d936b19 fix: test load file iOS Safari 2026-05-01 11:24:22 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
b403054348 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m31s
2026-05-01 09:43:32 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
e5d9800294 refactor(file): replace window.open with blob download pattern in registry 2026-05-01 09:43:00 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
1cb83bd0c5 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m23s
2026-04-30 15:18:05 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
14195e972f refactor(file): replace window.open with blob download pattern in ChangeName
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 15:17:49 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
e67f064421 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m21s
2026-04-30 15:05:05 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
ab63afc56d refactor(file): replace window.open with blob download pattern
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 15:04:45 +07:00
20 changed files with 382 additions and 115 deletions

View file

@ -6,7 +6,7 @@ import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useLeaveStore } from "@/modules/05_leave/store";
import { useDataStore } from "@/stores/data";
import genReport from "@/plugins/genreport";
/** import type*/
import type {
@ -16,8 +16,6 @@ import type {
FromCancelDetail,
} from "@/modules/05_leave/interface/response/leave";
import DialogHeader from "@/components/DialogHeader.vue";
import Workflow from "@/components/Workflow/Main.vue";
import FormLeave from "@/modules/05_leave/components/formDetail/01_SickForm.vue";
import FormChildbirth from "@/modules/05_leave/components/formDetail/04_HelpWifeBirthForm.vue";
import FormHoliday from "@/modules/05_leave/components/formDetail/05_VacationForm.vue";
@ -33,7 +31,6 @@ import FormCancel from "@/modules/05_leave/components/formDetail/formCancel.vue"
const $q = useQuasar();
const dataStore = useLeaveStore();
const mainStore = useDataStore();
const { convertStatud } = dataStore;
const mixin = useCounterMixin();
const {
@ -411,6 +408,28 @@ async function onSubmit() {
});
}
/**
* งกนดาวนโหลดไฟล
* @param id รหสการลา
* @param fileName อไฟล
* @param type ประเภทไฟล
*/
async function onClickDownloadFile(id: string, fileName: string, type: string) {
showLoader();
await http
.get(config.API.leaveReport(id))
.then(async (res) => {
const data = res.data.result;
await genReport(data, fileName, type);
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
}
/**** ตรวจสอบว่ามีการส่งข้อมูลเข้ามาแล้วเปิด modal */
watch(
() => props.modal,
@ -434,10 +453,62 @@ watch(
v-if="props.leaveStatus != 'DELETE'"
style="width: 900px; max-width: 80vw"
>
<DialogHeader
:tittle="`${titleMain} ${titleName}`"
:close="props.onClickClose"
/>
<q-toolbar>
<q-toolbar-title class="text-subtitle2 text-bold">
{{ ` ${titleMain} ${titleName}` }}
<q-btn class="q-mr-sm" icon="mdi-download" round color="primary" flat>
<q-tooltip>ดาวนโหลดไฟล</q-tooltip>
<q-menu>
<q-list style="min-width: 100px">
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'docx',
)
"
>
<q-item-section avatar>
<q-icon color="blue" name="mdi-file-word" />
</q-item-section>
<q-item-section>ไฟล .DOCX</q-item-section>
</q-item>
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'pdf',
)
"
>
<q-item-section avatar>
<q-icon color="red" name="mdi-file-pdf" />
</q-item-section>
<q-item-section>ไฟล .pdf</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-toolbar-title>
<q-btn
icon="close"
unelevated
round
dense
@click="props.onClickClose?.()"
style="color: #ff8080; background-color: #ffdede"
/>
</q-toolbar>
<q-separator />
<q-card-section v-if="isLoading">
@ -580,10 +651,62 @@ watch(
</q-card>
<q-card v-if="props.leaveStatus === 'DELETE'" style="min-width: 40vw">
<DialogHeader
:tittle="`${titleMainCancle} ${titleName}`"
:close="props.onClickClose"
/>
<q-toolbar>
<q-toolbar-title class="text-subtitle2 text-bold">
{{ ` ${titleMainCancle} ${titleName}` }}
<q-btn class="q-mr-sm" icon="mdi-download" round color="primary" flat>
<q-tooltip>ดาวนโหลดไฟล</q-tooltip>
<q-menu>
<q-list style="min-width: 100px">
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'docx',
)
"
>
<q-item-section avatar>
<q-icon color="blue" name="mdi-file-word" />
</q-item-section>
<q-item-section>ไฟล .DOCX</q-item-section>
</q-item>
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'pdf',
)
"
>
<q-item-section avatar>
<q-icon color="red" name="mdi-file-pdf" />
</q-item-section>
<q-item-section>ไฟล .pdf</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-toolbar-title>
<q-btn
icon="close"
unelevated
round
dense
@click="props.onClickClose?.()"
style="color: #ff8080; background-color: #ffdede"
/>
</q-toolbar>
<q-separator />
<q-card-section v-if="isLoading">

View file

@ -43,6 +43,7 @@ interface LeaveItem {
all: number;
use: number;
remain: number;
leaveCountApproveCount: number;
}
interface MainList {

View file

@ -141,12 +141,14 @@ async function fetchStatsTable() {
value:
el.leaveLimit > 0
? Math.round(
(Number(el.leaveCountApprove) / Number(el.leaveLimit)) * 100
(Number(el.leaveCountApprove) / Number(el.leaveLimit)) *
100,
)
: 0,
all: Number(el.leaveLimit),
use: el.leaveCountApprove,
remain: Number(el.leaveLimit) - Number(el.leaveCountApprove),
leaveCountApproveCount: el.leaveCountApproveCount,
}));
});
stat.forEach((item) => itemPie.value.push(...item));
@ -254,7 +256,7 @@ onMounted(async () => {
</q-knob>
</div>
<div class="col-12 text-center text-weight-medium">
ลาพกผอน
{{ item.text }}
</div>
</div>
<div class="row gt-xs"><q-separator vertical /></div>
@ -262,17 +264,19 @@ onMounted(async () => {
<div class="col-12 row text-dark text-body2 items-center">
<div class="col-12 row q-pa-xs q-px-md row">
<span class="text-grey-7 col-6">ได</span>
<span class="text-weight-bold">{{ item.all }}</span>
<span class="text-weight-bold">{{ item.all }} </span>
</div>
<div class="col-12"><q-separator /></div>
<div class="col-12 row q-pa-xs q-px-md">
<span class="text-grey-7 col-6">ใชไป</span>
<span class="text-weight-bold">{{ item.use }}</span>
<span class="text-weight-bold">{{ item.use }} </span>
</div>
<div class="col-12"><q-separator /></div>
<div class="col-12 row q-pa-xs q-px-md">
<span class="text-grey-7 col-6">คงเหล</span>
<span class="text-weight-bold">{{ item.remain }}</span>
<span class="text-weight-bold"
>{{ item.remain }} </span
>
</div>
</div>
</div>
@ -294,11 +298,11 @@ onMounted(async () => {
flat
class="shadow-0 col-12 fit row items-center q-px-lg"
>
<div class="text-subtitle2 col-4">ลาปวย</div>
<div class="text-subtitle2 col-4">{{ item.text }}</div>
<div class="text-subtitle2 col-8">
<span class="text-grey-7 q-pr-md">ใชไป</span>
<span class="text-weight-bold">{{ item.use }}</span>
<!-- <span class="text-grey-7 q-pl-md">ลา</span> -->
<span class="text-weight-bold">{{ item.use }} </span>
({{ item.leaveCountApproveCount }} คร)
</div>
</q-card>
</div>
@ -317,11 +321,11 @@ onMounted(async () => {
flat
class="shadow-0 col-12 fit row items-center q-px-lg"
>
<div class="text-subtitle2 col-4">ลากจสวนต</div>
<div class="text-subtitle2 col-4">{{ item.text }}</div>
<div class="text-subtitle2 col-8">
<span class="text-grey-7 q-pr-md">ใชไป</span>
<span class="text-weight-bold">{{ item.use }}</span>
<!-- <span class="text-grey-7 q-pl-md">ลา</span> -->
<span class="text-weight-bold">{{ item.use }} </span>
({{ item.leaveCountApproveCount }} คร)
</div>
</q-card>
</div>

View file

@ -9,6 +9,7 @@ import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useKpiDataStore } from "@/modules/08_KPI/store";
import avatar from "@/assets/avatar_user.jpg";
import type { FormProfile } from "@/modules/08_KPI/interface/request/index";
import type {
@ -107,11 +108,13 @@ async function getAvatar(id: string) {
.then(async (res) => {
const data = await res.data.result;
if (data.avatarName) {
await fetchProfile(id, data.avatarName);
fetchProfile(id, data.avatarName);
}
})
.catch((e) => {
messageError($q, e);
imgProfile.value = avatar;
store.dataEvaluation.avartar = avatar;
})
.finally(() => {
isLoadAvatar.value = false;
@ -119,12 +122,16 @@ async function getAvatar(id: string) {
}
/** ดึงข้อมูล เพื่อเก็บรูปโปรไฟล์ */
async function fetchProfile(id: string, avatarName: string) {
await http
function fetchProfile(id: string, avatarName: string) {
http
.get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, avatarName))
.then(async (res) => {
.then((res) => {
store.dataEvaluation.avartar = res.data.downloadUrl;
imgProfile.value = res.data.downloadUrl;
})
.catch(() => {
imgProfile.value = avatar;
store.dataEvaluation.avartar = avatar;
});
}
@ -208,7 +215,7 @@ async function getOrgOp() {
name: `${i.prefix}${i.firstName} ${i.lastName}`,
}))
.find(
(i: EvaOptionType) => i.id == store.dataEvaluation.commanderHighId
(i: EvaOptionType) => i.id == store.dataEvaluation.commanderHighId,
);
isLoadCommander.value = false;
})
@ -225,21 +232,21 @@ function filterOption(val: string, update: Function, refData: string) {
case "evaluatorIdOp":
update(() => {
evaluatorIdOp.value = evaluatorIdMainOp.value.filter(
(v: DataOptions) => v.name.indexOf(val) > -1
(v: DataOptions) => v.name.indexOf(val) > -1,
);
});
break;
case "commanderIdOp":
update(() => {
commanderIdOp.value = commanderIdMainOp.value.filter(
(v: DataOptions) => v.name.indexOf(val) > -1
(v: DataOptions) => v.name.indexOf(val) > -1,
);
});
break;
case "commanderHighOp":
update(() => {
commanderHighOp.value = commanderHighMainOp.value.filter(
(v: DataOptions) => v.name.indexOf(val) > -1
(v: DataOptions) => v.name.indexOf(val) > -1,
);
});
break;
@ -278,7 +285,7 @@ function sendToEvaluatore() {
}
},
"ยืนยันการส่งข้อตกลงให้ผู้ประเมินอนุมัติ",
"ต้องการยืนยันส่งข้อตกลงนี้ให้ผู้ประเมินอนุมัติใช่หรือไม่?"
"ต้องการยืนยันส่งข้อตกลงนี้ให้ผู้ประเมินอนุมัติใช่หรือไม่?",
);
}
@ -308,7 +315,7 @@ function sendToEvaluateEvaluatore() {
}
},
"ยืนยันการส่งให้ผู้ประเมินรายงานผลสำเร็จของงาน",
"ต้องการยืนยันส่งให้ผู้ประเมินรายงานผลสำเร็จของงานใช่หรือไม่?"
"ต้องการยืนยันส่งให้ผู้ประเมินรายงานผลสำเร็จของงานใช่หรือไม่?",
);
}
@ -338,7 +345,7 @@ function requireEdit() {
}
},
"ยืนยันการขอแก้ไขข้อตกลง",
"ต้องการยืนยันการขอแก้ไขข้อตกลงนี้ใช่หรือไม่?"
"ต้องการยืนยันการขอแก้ไขข้อตกลงนี้ใช่หรือไม่?",
);
}
@ -411,7 +418,7 @@ async function goToSummary() {
store.excusiveIndicator2ScoreVal +
store.competencyScoreVal
).toFixed(2),
}
},
)
.then((res) => {});
@ -427,7 +434,7 @@ async function goToSummary() {
});
},
"ยืนยันการส่งไปสรุปผลการประเมิน",
"ต้องการยืนยันส่งไปสรุปผลการประเมินใช่หรือไม่?"
"ต้องการยืนยันส่งไปสรุปผลการประเมินใช่หรือไม่?",
);
}
@ -517,7 +524,7 @@ async function downloadReport() {
store.dataEvaluation.prefix +
store.dataEvaluation.firstName +
" " +
store.dataEvaluation.lastName
store.dataEvaluation.lastName,
);
})
.catch((e) => {
@ -556,7 +563,7 @@ async function clickUpload(file: any) {
const foundKey: string | undefined = Object.keys(res.data).find(
(key) =>
res.data[key]?.fileName !== undefined &&
res.data[key]?.fileName !== ""
res.data[key]?.fileName !== "",
);
foundKey &&
uploadFileDoc(res.data[foundKey]?.uploadUrl, fileUpload.value);
@ -566,7 +573,7 @@ async function clickUpload(file: any) {
});
},
"ยืนยันการอัปโหลดไฟล์",
"ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่ ?"
"ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่ ?",
);
}
@ -623,7 +630,7 @@ function deleteFile(fileName: string) {
showLoader();
http
.delete(
config.API.file("แบบกำหนดข้อตกลง", "KPI", id.value) + `/${fileName}`
config.API.file("แบบกำหนดข้อตกลง", "KPI", id.value) + `/${fileName}`,
)
.catch((e) => {
messageError($q, e);

View file

@ -3,6 +3,7 @@ import { ref, onMounted } from "vue";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import http from "@/plugins/http";
import config from "@/app.config";
@ -114,7 +115,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}
@ -132,12 +133,15 @@ async function onDownloadFile(id: string, profileId: string) {
"ประวัติการเปลี่ยนชื่อ-นามสกุล",
profileId,
id,
"เอกสารหลักฐาน"
)
"เอกสารหลักฐาน",
),
)
.then(async (res) => {
const data = res.data.downloadUrl;
window.open(data, "_blank");
const downloadUrl = res.data.downloadUrl;
await downloadBlobFile({
downloadUrl: downloadUrl,
fileName: `ประวัติการเปลี่ยนชื่อ-นามสกุล`,
});
})
.catch((err) => {
messageError($q, err);

View file

@ -6,6 +6,7 @@ import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
/** import type */
import type { AbilityRows } from "@/modules/10_registry/interface/index/Main";
@ -240,7 +241,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}
@ -258,12 +259,15 @@ async function onDownloadFile(id: string, profileId: string) {
"เอกสารความสามารถพิเศษ",
profileId,
id,
"เอกสารหลักฐาน"
)
"เอกสารหลักฐาน",
),
)
.then(async (res) => {
const data = res.data;
window.open(data.downloadUrl, "_blank");
const downloadUrl = res.data.downloadUrl;
await downloadBlobFile({
downloadUrl: downloadUrl,
fileName: `เอกสารความสามารถพิเศษ`,
});
})
.catch((err) => {
messageError($q, err);

View file

@ -7,6 +7,7 @@ import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { useRegistryDataStore } from "@/modules/10_registry/store/Main";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { DisciplineDetail } from "@/modules/10_registry/interface/index/Main";
@ -194,8 +195,11 @@ async function getHistory() {
async function onDownloadFile(id: string, profileId: string) {
showLoader();
try {
const data = await getPathUploadFlie(fileGroup.value, profileId, id);
window.open(data.downloadUrl, "_blank");
const res = await getPathUploadFlie(fileGroup.value, profileId, id);
await downloadBlobFile({
downloadUrl: res.downloadUrl,
fileName: `เอกสารวินัย`,
});
} catch (e) {
messageError($q, e);
} finally {
@ -208,7 +212,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}

View file

@ -7,6 +7,7 @@ import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { useRegistryDataStore } from "@/modules/10_registry/store/Main";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { DutyFormType } from "@/modules/10_registry/interface/index/Main";
@ -297,8 +298,11 @@ async function getHistory() {
async function onDownloadFile(id: string, profileId: string) {
showLoader();
try {
const data = await getPathUploadFlie(fileGroup.value, profileId, id);
window.open(data.downloadUrl, "_blank");
const res = await getPathUploadFlie(fileGroup.value, profileId, id);
await downloadBlobFile({
downloadUrl: res.downloadUrl,
fileName: `เอกสารปฏิบัติราชการพิเศษ`,
});
} catch (e) {
messageError($q, e);
} finally {
@ -311,7 +315,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}

View file

@ -6,6 +6,7 @@ import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { DutyFormType } from "@/modules/10_registry/interface/index/Main";
@ -302,12 +303,15 @@ async function onDownloadFile(id: string, profileId: string) {
"ช่วยราชการ",
profileId,
id,
"เอกสารหลักฐาน"
)
"เอกสารหลักฐาน",
),
)
.then(async (res) => {
const data = res.data;
window.open(data.downloadUrl, "_blank");
const downloadUrl = res.data.downloadUrl;
await downloadBlobFile({
downloadUrl: downloadUrl,
fileName: `เอกสารช่วยราชการ`,
});
})
.catch((err) => {
messageError($q, err);
@ -322,7 +326,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}

View file

@ -7,6 +7,7 @@ import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { useRegistryDataStore } from "@/modules/10_registry/store/Main";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { NopaidFormType } from "@/modules/10_registry/interface/index/Main";
@ -255,7 +256,7 @@ async function getHistory() {
rowsHistoryData.value = [];
await http
.get(
config.API.dataUserSalaryNopaidHistoryByType(link.value, idByRow.value)
config.API.dataUserSalaryNopaidHistoryByType(link.value, idByRow.value),
)
.then((res) => {
const data = res.data.result;
@ -278,8 +279,12 @@ async function getHistory() {
async function onDownloadFile(id: string, profileId: string) {
showLoader();
try {
const data = await getPathUploadFlie(fileGroup.value, profileId, id);
window.open(data.downloadUrl, "_blank");
const res = await getPathUploadFlie(fileGroup.value, profileId, id);
const downloadUrl = res.downloadUrl;
await downloadBlobFile({
downloadUrl: downloadUrl,
fileName: `บันทึกวันที่ไม่ได้รับ${salaryText.value}`,
});
} catch (e) {
messageError($q, e);
} finally {
@ -292,7 +297,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}

View file

@ -7,6 +7,7 @@ import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { useRegistryDataStore } from "@/modules/10_registry/store/Main";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { CertificateDetail } from "@/modules/10_registry/interface/index/Main";
@ -253,8 +254,8 @@ async function getHistory() {
config.API.dataUserCertificateHistoryByType(
link.value,
"certificate",
idByRow.value
)
idByRow.value,
),
)
.then((res) => {
const data = res.data.result;
@ -278,7 +279,10 @@ async function onDownloadFile(id: string, profileId: string) {
showLoader();
try {
const data = await getPathUploadFlie(fileGroup.value, profileId, id);
window.open(data.downloadUrl, "_blank");
await downloadBlobFile({
downloadUrl: data.downloadUrl,
fileName: `เอกสารใบอนุญาตประกอบวิชาชีพ`,
});
} catch (error) {
messageError($q, error);
} finally {
@ -291,7 +295,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}

View file

@ -7,6 +7,7 @@ import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { useRegistryDataStore } from "@/modules/10_registry/store/Main";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { InsigniaFormType } from "@/modules/10_registry/interface/index/Main";
@ -469,8 +470,8 @@ async function getHistory() {
config.API.dataUserCertificateHistoryByType(
link.value,
"insignia",
idByRow.value
)
idByRow.value,
),
)
.then((res) => {
const data = res.data.result;
@ -494,7 +495,10 @@ async function onDownloadFile(id: string, profileId: string) {
showLoader();
try {
const data = await getPathUploadFlie(fileGroup.value, profileId, id);
window.open(data.downloadUrl, "_blank");
await downloadBlobFile({
downloadUrl: data.downloadUrl,
fileName: `เอกสารเครื่องราชอิสริยาภรณ์`,
});
} catch (error) {
messageError($q, error);
} finally {
@ -507,7 +511,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}
@ -647,7 +651,6 @@ onMounted(async () => {
dense
round
size="14px"
class="absolute_button"
@click="onHistory(props.row.id)"
>
<q-tooltip>ประวแกไขเครองราชอสรยาภรณ</q-tooltip>

View file

@ -7,6 +7,7 @@ import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/stores/data";
import { useRegistryDataStore } from "@/modules/10_registry/store/Main";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { HonorFormData } from "@/modules/10_registry/interface/index/Main";
@ -278,7 +279,10 @@ async function onDownloadFile(id: string, profileId: string) {
showLoader();
try {
const data = await getPathUploadFlie(fileGroup.value, profileId, id);
window.open(data.downloadUrl, "_blank");
await downloadBlobFile({
downloadUrl: data.downloadUrl,
fileName: `เอกสารประกาศเกียรติคุณ`,
});
} catch (error) {
messageError($q, error);
} finally {
@ -291,7 +295,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}
@ -429,7 +433,6 @@ onMounted(async () => {
dense
round
size="14px"
class="absolute_button"
@click="onHistory(props.row.id)"
>
<q-tooltip>ประวแกไขประกาศเกยรต</q-tooltip>

View file

@ -1,11 +1,12 @@
<script setup lang="ts">
import { useCounterMixin } from "@/stores/mixin";
import { is, useQuasar } from "quasar";
import { ref, onMounted } from "vue";
import { useQuasar } from "quasar";
import { ref, onMounted, watch } from "vue";
import http from "@/plugins/http";
import config from "@/app.config";
import { useRegistryInFormationStore } from "@/modules/10_registry/store/registry";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { FileFormType } from "@/modules/10_registry/interface/index/Main";
@ -18,14 +19,15 @@ const fileList = ref<FileFormType[]>([]);
/** ฟังก์ชันดึงข้อมูลไฟล์ */
async function getData() {
if (!store.profileId) return;
isLoading.value = true;
await http
.get(
config.API.fileByFileUser(
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐาน",
store.profileId
)
store.profileId,
),
)
.then((res) => {
const data = res.data;
@ -51,12 +53,15 @@ async function downloadFile(fileName: string) {
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐาน",
store.profileId,
fileName
)
fileName,
),
)
.then((res) => {
const data = res.data.downloadUrl;
window.open(data, "_blank");
.then(async (res) => {
const downloadUrl = res.data.downloadUrl;
await downloadBlobFile({
downloadUrl: downloadUrl,
fileName: fileName,
});
})
.catch((e) => {
messageError($q, e);
@ -69,6 +74,15 @@ async function downloadFile(fileName: string) {
onMounted(() => {
getData();
});
watch(
() => store.profileId,
(newVal) => {
if (newVal) {
getData();
}
},
);
</script>
<template>

View file

@ -1,11 +1,12 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ref, onMounted, watch } from "vue";
import { useCounterMixin } from "@/stores/mixin";
import { useQuasar } from "quasar";
import http from "@/plugins/http";
import config from "@/app.config";
import { useRegistryInFormationStore } from "@/modules/10_registry/store/registry";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
import type { FileFormType } from "@/modules/10_registry/interface/index/Main";
@ -18,14 +19,15 @@ const fileList = ref<FileFormType[]>([]);
/** ฟังก์ชันดึงข้อมูลไฟล์ */
async function getData() {
if (!store.citizenId) return;
isLoading.value = true;
await http
.get(
config.API.fileByFileUser(
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐานเพิ่มเติม",
store.citizenId
)
store.citizenId,
),
)
.then((res) => {
const data = res.data;
@ -51,12 +53,15 @@ async function downloadFile(fileName: string) {
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐานเพิ่มเติม",
store.citizenId,
fileName
)
fileName,
),
)
.then((res) => {
const data = res.data.downloadUrl;
window.open(data, "_blank");
.then(async (res) => {
const downloadUrl = res.data.downloadUrl;
await downloadBlobFile({
downloadUrl: downloadUrl,
fileName: fileName,
});
})
.catch((e) => {
messageError($q, e);
@ -69,6 +74,15 @@ async function downloadFile(fileName: string) {
onMounted(() => {
getData();
});
watch(
() => store.citizenId,
(newVal) => {
if (newVal) {
getData();
}
},
);
</script>
<template>

View file

@ -0,0 +1,60 @@
export interface DownloadFileOptions {
downloadUrl: string;
fileName: string;
}
const isMobile =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent,
);
export async function downloadBlobFile({
downloadUrl,
fileName,
}: DownloadFileOptions): Promise<void> {
// Use window.open for desktop, blob download for mobile
if (!isMobile) {
window.open(downloadUrl, "_blank");
return;
}
const response = await fetch(downloadUrl);
const blob = await response.blob();
const contentType: string | null = response.headers.get("Content-Type");
const extensionMap: Record<string, string> = {
"application/pdf": "pdf",
"image/jpeg": "jpg",
"image/png": "png",
"image/gif": "gif",
"application/zip": "zip",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
"docx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
};
let extension = contentType ? extensionMap[contentType] : undefined;
if (!extension) {
const urlWithoutQuery = downloadUrl.split("?")[0];
extension = urlWithoutQuery.includes(".")
? urlWithoutQuery.split(".").pop()
: "pdf";
}
const blobForDownload = new Blob([blob], {
type: "application/octet-stream",
});
const url = URL.createObjectURL(blobForDownload);
const link = document.createElement("a");
link.href = url;
const downloadFileName = fileName.includes(".") ? fileName : `${fileName}.${extension}`;
link.download = downloadFileName;
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
}

View file

@ -151,6 +151,7 @@ async function getAvatar(id: string) {
})
.catch((e) => {
messageError($q, e);
profileImg.value = avatar;
});
}
@ -168,14 +169,15 @@ function getList(id: string) {
});
}
async function getImg(id: string, pathName: string) {
await http
function getImg(id: string, pathName: string) {
http
.get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, pathName))
.then((res) => {
profileImg.value = res.data.downloadUrl;
})
.catch((e) => {
messageError($q, e);
profileImg.value = avatar;
// messageError($q, e);
hideLoader();
});
}
@ -188,7 +190,7 @@ function onSearch() {
rows.value = onSearchDataTable(
filter.value,
rowsData.value,
columns.value ? columns.value : []
columns.value ? columns.value : [],
);
}

View file

@ -19,7 +19,7 @@ async function genReport(data: any, fileName: string, type: string = "docx") {
},
responseType: "arraybuffer",
})
.then((res) => {
.then(async (res) => {
const responseData = res.data;
if (responseData) {
const mimeType =
@ -32,13 +32,11 @@ async function genReport(data: any, fileName: string, type: string = "docx") {
const baseName = fileName.trim();
const extension = type === "docx" ? "docx" : "pdf";
const link = document.createElement("a");
link.href = url;
link.download = `${baseName}.${extension}`;
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);

View file

@ -14,7 +14,7 @@ export const useCounterMixin = defineStore("mixin", () => {
const calAge = (
srcDate: Date,
birthCal: Date = new Date(),
eng: boolean = false
eng: boolean = false,
) => {
const year = eng ? "years" : "ปี";
const month = eng ? "months" : "เดือน";
@ -82,7 +82,7 @@ export const useCounterMixin = defineStore("mixin", () => {
function date2Thai(
srcDate: Date | null,
isFullMonth: boolean = false,
isTime: boolean = false
isTime: boolean = false,
) {
if (srcDate == null || !moment(srcDate).isValid()) return "";
@ -98,7 +98,7 @@ export const useCounterMixin = defineStore("mixin", () => {
function dateThai(
srcDate: Date,
isFullMonth: boolean = true,
isTime: boolean = false
isTime: boolean = false,
) {
if (srcDate == null) {
return null;
@ -490,7 +490,7 @@ export const useCounterMixin = defineStore("mixin", () => {
color: string | undefined,
ok?: Function | undefined,
cancel?: Function | undefined,
onlycancel: Boolean = false
onlycancel: Boolean = false,
) => {
q.dialog({
component: CustomComponent,
@ -529,7 +529,7 @@ export const useCounterMixin = defineStore("mixin", () => {
title: string,
message: string,
ok: Function,
cancel?: Function
cancel?: Function,
) {
q.dialog({
title: `<span class="text-red">${title}</span>`,
@ -559,7 +559,7 @@ export const useCounterMixin = defineStore("mixin", () => {
title: string,
message: string,
ok: Function,
cancel?: Function
cancel?: Function,
) {
q.dialog({
title: `<span class="text-primary">${title}</span>`,
@ -795,7 +795,7 @@ export const useCounterMixin = defineStore("mixin", () => {
ok?: Function,
title?: string, // ถ้ามี cancel action ใส่เป็น null
desc?: string, // ถ้ามี cancel action ใส่เป็น null
cancel?: Function
cancel?: Function,
) => {
q.dialog({
component: CustomComponent,
@ -824,7 +824,7 @@ export const useCounterMixin = defineStore("mixin", () => {
ok?: Function,
title?: string, // ถ้ามี cancel action ใส่เป็น null
desc?: string, // ถ้ามี cancel action ใส่เป็น null
cancel?: Function
cancel?: Function,
) => {
q.dialog({
component: CustomComponent,
@ -851,7 +851,7 @@ export const useCounterMixin = defineStore("mixin", () => {
const dialogMessageNotify = (
q: any,
desc?: string, // ถ้ามี cancel action ใส่เป็น null
cancel?: Function
cancel?: Function,
) => {
q.dialog({
component: CustomComponent,
@ -973,7 +973,7 @@ export const useCounterMixin = defineStore("mixin", () => {
* @returns 1 10 5
*/
function calculateDurationYmd(startDate: any, endDate: any) {
if (!startDate || !endDate) return "";
if (!startDate || !endDate) return "";
let start = moment(startDate).startOf("day");
let end = moment(endDate).startOf("day").add(1, "day");
@ -1252,9 +1252,15 @@ export const useCounterMixin = defineStore("mixin", () => {
// กรณีมีเฉพาะ date
function convertDateToAPI(date: Date | null) {
return date
? format(utcToZonedTime(date, "Asia/Bangkok"), "yyyy-MM-dd")
: null;
if (!date) return null;
const parsedDate = new Date(date);
if (parsedDate) {
return format(parsedDate, "yyyy-MM-dd");
} else {
return null;
}
}
// กรณี datetime

View file

@ -74,7 +74,7 @@ async function checkUser() {
await dataStore.getProFileType();
kpiDataStore.dataProfile = data; // Set dataProfile in kpiDataStore
if (data.avatarName) {
await getImg(data.profileId, data.avatarName);
getImg(data.profileId, data.avatarName);
} else {
dataStore.profileImg = avatar;
}
@ -106,6 +106,9 @@ function getImg(id: string, pathName: string) {
.get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, pathName))
.then((res) => {
dataStore.profileImg = res.data.downloadUrl;
})
.catch(() => {
dataStore.profileImg = avatar;
});
}