updated
10
index.html
|
|
@ -5,16 +5,6 @@
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>HRMS - Landing Page</title>
|
<title>HRMS - Landing Page</title>
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#viewDiv {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
||||||
18
package.json
|
|
@ -11,26 +11,31 @@
|
||||||
"format": "prettier ./src --write"
|
"format": "prettier ./src --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arcgis/core": "^4.28.10",
|
"@fullcalendar/core": "^6.1.8",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.8",
|
||||||
|
"@fullcalendar/interaction": "^6.1.8",
|
||||||
|
"@fullcalendar/list": "^6.1.8",
|
||||||
|
"@fullcalendar/timegrid": "^6.1.8",
|
||||||
|
"@fullcalendar/vue3": "^6.1.8",
|
||||||
|
"@googlemaps/js-api-loader": "^1.16.2",
|
||||||
"@quasar/extras": "^1.15.8",
|
"@quasar/extras": "^1.15.8",
|
||||||
"@vuepic/vue-datepicker": "^5.2.1",
|
"@vuepic/vue-datepicker": "^5.2.1",
|
||||||
"keycloak-js": "^22.0.2",
|
"keycloak-js": "^22.0.2",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
"quasar": "^2.17.5",
|
"quasar": "^2.11.1",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"sass": "^1.83.0",
|
|
||||||
"simple-vue-camera": "^1.1.3",
|
"simple-vue-camera": "^1.1.3",
|
||||||
"vite-plugin-pwa": "^0.16.7",
|
"vite-plugin-pwa": "^0.16.7",
|
||||||
"vue": "^3.4.15",
|
"vue": "^3.4.15",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.1.6",
|
||||||
|
"vue3-google-map": "^0.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/vite-plugin": "^1.3.0",
|
"@quasar/vite-plugin": "^1.3.0",
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
"@types/jsdom": "^20.0.1",
|
"@types/jsdom": "^20.0.1",
|
||||||
"@types/node": "^18.18.10",
|
"@types/node": "^18.18.10",
|
||||||
"@types/vue-router": "^2.0.0",
|
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
|
|
@ -44,10 +49,13 @@
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
"sass": "^1.83.0",
|
||||||
|
"sass-loader": "^16.0.4",
|
||||||
"start-server-and-test": "^1.15.2",
|
"start-server-and-test": "^1.15.2",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "~4.7.4",
|
||||||
"vite": "^4.0.0",
|
"vite": "^4.0.0",
|
||||||
"vitest": "^0.25.6",
|
"vitest": "^0.25.6",
|
||||||
|
"vue-loader": "^17.4.2",
|
||||||
"vue-tsc": "^1.0.12"
|
"vue-tsc": "^1.0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1 MiB |
BIN
src/assets/3135715.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
src/assets/bg.png
Normal file
|
After Width: | Height: | Size: 391 KiB |
BIN
src/assets/line2.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/screen1.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
src/assets/screen2.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
src/assets/screen3.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
src/assets/screen4.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
90
src/components/CustomDialog.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent>
|
||||||
|
<q-card class="q-pa-sm">
|
||||||
|
<q-card-section class="row">
|
||||||
|
<div class="q-pr-md">
|
||||||
|
<q-avatar
|
||||||
|
:icon="icon"
|
||||||
|
size="lg"
|
||||||
|
font-size="25px"
|
||||||
|
color="blue-1"
|
||||||
|
:text-color="color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col text-dark">
|
||||||
|
<span class="text-bold">{{ title }}</span>
|
||||||
|
<br />
|
||||||
|
<span>{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions
|
||||||
|
align="right"
|
||||||
|
class="bg-white text-teal"
|
||||||
|
v-if="onlycancel"
|
||||||
|
>
|
||||||
|
<q-btn label="ตกลง" flat color="grey-8" @click="onDialogCancel" />
|
||||||
|
<!-- <q-btn :label="textOk" :color="color" @click="onOKClick" /> -->
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-actions align="right" class="bg-white text-teal" v-else>
|
||||||
|
<q-btn label="ยกเลิก" flat color="grey-8" @click="onDialogCancel" />
|
||||||
|
<q-btn :label="textOk" :color="color" @click="onOKClick" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useDialogPluginComponent } from "quasar";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "primary",
|
||||||
|
},
|
||||||
|
textOk: {
|
||||||
|
type: String,
|
||||||
|
default: "ตกลง",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "หัวข้อ?",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: "ข้อความ",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: "question_mark",
|
||||||
|
},
|
||||||
|
onlycancel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits([
|
||||||
|
// REQUIRED; need to specify some events that your
|
||||||
|
// component will emit through useDialogPluginComponent()
|
||||||
|
...useDialogPluginComponent.emits,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
||||||
|
useDialogPluginComponent();
|
||||||
|
// dialogRef - Vue ref to be applied to QDialog
|
||||||
|
// onDialogHide - Function to be used as handler for @hide on QDialog
|
||||||
|
// onDialogOK - Function to call to settle dialog with "ok" outcome
|
||||||
|
// example: onDialogOK() - no payload
|
||||||
|
// example: onDialogOK({ /*...*/ }) - with payload
|
||||||
|
// onDialogCancel - Function to call to settle dialog with "cancel" outcome
|
||||||
|
|
||||||
|
// this is part of our example (so not required)
|
||||||
|
function onOKClick() {
|
||||||
|
// on OK, it is REQUIRED to
|
||||||
|
// call onDialogOK (with optional payload)
|
||||||
|
onDialogOK();
|
||||||
|
// or with payload: onDialogOK({ ... })
|
||||||
|
// ...and it will also hide the dialog automatically
|
||||||
|
}
|
||||||
|
</script>
|
||||||
27
src/plugins/cookie.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// authen with keycloak client
|
||||||
|
function setCookie(name: string, value: string, days: number) {
|
||||||
|
let expires = "";
|
||||||
|
if (days) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name: string) {
|
||||||
|
const nameEQ = name + "=";
|
||||||
|
const ca = document.cookie.split(";");
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == " ") c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCookie(name: string) {
|
||||||
|
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { setCookie, getCookie, deleteCookie };
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// authen with keycloak client
|
|
||||||
import Keycloak from "keycloak-js";
|
|
||||||
|
|
||||||
// const ACCESS_TOKEN = 'BMAHRIS_KEYCLOAK_IDENTITY'
|
|
||||||
// const REFRESH_TOKEN = 'BMAHRIS_KEYCLOAK_REFRESH'
|
|
||||||
// const keycloakConfig = {
|
|
||||||
// url: import.meta.env.VITE_URL_KEYCLOAK,
|
|
||||||
// realm: import.meta.env.VITE_REALM_KEYCLOAK,
|
|
||||||
// clientId: import.meta.env.VITE_CLIENTID_KEYCLOAK,
|
|
||||||
// clientSecret: import.meta.env.VITE_CLIENTSECRET_KEYCLOAK,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const keycloak = new Keycloak(keycloakConfig)
|
|
||||||
|
|
||||||
// async function kcAuthen(access_token: string, refresh_token: string) {
|
|
||||||
// await setCookie(ACCESS_TOKEN, access_token, 1)
|
|
||||||
// await setCookie(REFRESH_TOKEN, refresh_token, 1)
|
|
||||||
// window.location.href = '/'
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function kcLogout() {
|
|
||||||
// await deleteCookie(ACCESS_TOKEN)
|
|
||||||
// await deleteCookie(REFRESH_TOKEN)
|
|
||||||
// if (keycloak.authenticated !== undefined) {
|
|
||||||
// keycloak.logout()
|
|
||||||
// }
|
|
||||||
// window.location.href = '/login'
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function getToken() {
|
|
||||||
// return {
|
|
||||||
// token: getCookie(ACCESS_TOKEN),
|
|
||||||
// refresh_token: getCookie(REFRESH_TOKEN),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function setCookie(name: string, value: any, days: number) {
|
|
||||||
// let expires = ''
|
|
||||||
// if (days) {
|
|
||||||
// const date = new Date()
|
|
||||||
// date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000)
|
|
||||||
// expires = '; expires=' + date.toUTCString()
|
|
||||||
// }
|
|
||||||
// document.cookie = name + '=' + (value || '') + expires + '; path=/'
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function getCookie(name: string) {
|
|
||||||
// const nameEQ = name + '='
|
|
||||||
// const ca = document.cookie.split(';')
|
|
||||||
// for (let i = 0; i < ca.length; i++) {
|
|
||||||
// let c = ca[i]
|
|
||||||
// while (c.charAt(0) == ' ') c = c.substring(1, c.length)
|
|
||||||
// if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length)
|
|
||||||
// }
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function deleteCookie(name: string) {
|
|
||||||
// document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export default keycloak
|
|
||||||
// export {
|
|
||||||
// keycloakConfig,
|
|
||||||
// getToken,
|
|
||||||
// kcAuthen,
|
|
||||||
// kcLogout,
|
|
||||||
// ACCESS_TOKEN,
|
|
||||||
// REFRESH_TOKEN,
|
|
||||||
// }
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
const homeView = () => import("@/views/home.vue");
|
const homeView = () => import("@/views/home.vue");
|
||||||
|
const ssoPage = () => import("@/views/sso.vue");
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
|
@ -23,6 +24,14 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/sso",
|
||||||
|
name: "sso-page",
|
||||||
|
component: ssoPage,
|
||||||
|
meta: {
|
||||||
|
Auth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,173 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
import { deleteCookie, getCookie, setCookie } from "@/plugins/cookie";
|
||||||
|
import router from "@/router";
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { useQuasar } from "quasar";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import CustomComponent from "@/components/CustomDialog.vue";
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
const urlAdmin = import.meta.env.VITE_URL_ADMIN ?? "";
|
||||||
|
const urlUser = import.meta.env.VITE_URL_USER ?? "";
|
||||||
|
const urlMgt = import.meta.env.VITE_URL_MGT ?? "";
|
||||||
|
const urlCheckin = import.meta.env.VITE_URL_CHECKIN ?? "";
|
||||||
|
|
||||||
|
const token = ref<any>("");
|
||||||
|
const refreshToken = ref<any>("");
|
||||||
|
const fullname = computed(() => {
|
||||||
|
if (token.value) {
|
||||||
|
const base64Url = token.value.split(".")[1];
|
||||||
|
|
||||||
|
// แปลงจาก Base64 URL-safe เป็น Base64 ปกติ
|
||||||
|
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
|
||||||
|
// ถอดรหัส Base64
|
||||||
|
const decoded = atob(base64);
|
||||||
|
const decodedData = JSON.parse(decoded);
|
||||||
|
|
||||||
|
// ดึงชื่อผู้ใช้
|
||||||
|
return decodeURIComponent(escape(decodedData.name));
|
||||||
|
} else return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
async function goPage(sys: string, url: string) {
|
||||||
|
// แยกส่วน Payload ของ JWT (ส่วนที่ 2)
|
||||||
|
const base64Url = token.value.split(".")[1];
|
||||||
|
|
||||||
|
// แปลงจาก Base64 URL-safe เป็น Base64 ปกติ
|
||||||
|
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
|
||||||
|
// ถอดรหัส Base64
|
||||||
|
const decoded = atob(base64);
|
||||||
|
|
||||||
|
// กำหนด requiredRole ตามค่าของ sys
|
||||||
|
let requiredRole: string[] = [];
|
||||||
|
|
||||||
|
if (sys === "user" || sys === "checkin") {
|
||||||
|
requiredRole = ["USER"];
|
||||||
|
} else if (sys === "mgt") {
|
||||||
|
requiredRole = ["STAFF"]; // ถ้า sys เป็นค่าว่าง ให้ใช้ "ADMIN"
|
||||||
|
} else if (sys === "admin") {
|
||||||
|
requiredRole = ["ADMIN", "SUPER_ADMIN"];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("requiredRole===>", requiredRole);
|
||||||
|
console.log("decoded===>", JSON.parse(decoded).realm_access.roles);
|
||||||
|
|
||||||
|
// ตรวจสอบว่า payload.role มีค่าหรือไม่ และว่ามี role ที่ต้องการหรือไม่
|
||||||
|
if (
|
||||||
|
requiredRole.some((role) =>
|
||||||
|
JSON.parse(decoded).realm_access.roles.includes(role)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
window.location.href = `${url}/auth?token=${token.value}&accessToken=${refreshToken.value}`;
|
||||||
|
} else {
|
||||||
|
// alert("คุณไม่มีสิทธิ์เข้าใช้งานระบบนี้");
|
||||||
|
$q.dialog({
|
||||||
|
component: CustomComponent,
|
||||||
|
componentProps: {
|
||||||
|
title: `แจ้งเตือน`,
|
||||||
|
message: "คุณไม่มีสิทธิ์เข้าใช้งานระบบนี้",
|
||||||
|
icon: "warning",
|
||||||
|
color: "red",
|
||||||
|
onlycancel: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
await deleteCookie("BMAHRIS_KEYCLOAK_IDENTITY");
|
||||||
|
await deleteCookie("BMAHRIS_KEYCLOAK_REFRESH");
|
||||||
|
|
||||||
|
router.push("/sso");
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// const checkAuthen = await authenticated();
|
token.value = await getCookie("BMAHRIS_KEYCLOAK_IDENTITY");
|
||||||
// if (checkAuthen) {
|
refreshToken.value = await getCookie("BMAHRIS_KEYCLOAK_REFRESH");
|
||||||
// router.push("/");
|
|
||||||
// }
|
deleteCookie("BMAHRISADM_KEYCLOAK_IDENTITY");
|
||||||
|
deleteCookie("BMAHRISCKI_KEYCLOAK_IDENTITY");
|
||||||
|
deleteCookie("BMAHRISUSER_KEYCLOAK_IDENTITY");
|
||||||
|
|
||||||
|
const checkToken = (await token.value) ?? null;
|
||||||
|
|
||||||
|
if (!checkToken && !token.value) {
|
||||||
|
await axios
|
||||||
|
.post(
|
||||||
|
`${import.meta.env.VITE_SSO_URL}/kcauth`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
withCredentials: true, // Include cookies with the request
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
console.log("res===>", res);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
setCookie("BMAHRIS_KEYCLOAK_IDENTITY", res.data.access_token, 1);
|
||||||
|
setCookie("BMAHRIS_KEYCLOAK_REFRESH", res.data.refresh_token, 1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
router.push("/sso");
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1>Lanading page</h1>
|
<div class="q-ma-md">
|
||||||
|
<div class="row">
|
||||||
|
{{ fullname }}
|
||||||
|
<q-btn @click="logout()" class="btn_logout">
|
||||||
|
ออกจากระบบ <i class="mdi mdi-logout"></i>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
<img src="@/assets/screen1.png" style="width: 100%" />
|
||||||
|
<div class="h-100 py-3">
|
||||||
|
<a @click="goPage('user', urlUser)" class="link">
|
||||||
|
ระบบบริการเจ้าของข้อมูล
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4">
|
||||||
|
<img src="@/assets/screen2.png" style="width: 100%" />
|
||||||
|
<div class="h-100 py-3">
|
||||||
|
<a @click="goPage('checkin', urlCheckin)" class="link">
|
||||||
|
ระบบลงเวลาปฏิบัติราชการ
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4">
|
||||||
|
<img src="@/assets/screen3.png" style="width: 100%" />
|
||||||
|
<div class="h-100 py-3">
|
||||||
|
<a @click="goPage('mgt', urlMgt)" class="link"> ระบบบริหารจัดการ </a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4">
|
||||||
|
<img src="@/assets/screen4.png" style="width: 100%" />
|
||||||
|
<div class="h-100 py-3">
|
||||||
|
<a @click="goPage('admin', urlAdmin)" class="link"> ระบบแอดมิน </a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
298
src/views/sso.vue
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
<!-- authen with keycloak client -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useQuasar } from "quasar";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useCounterMixin } from "@/stores/mixin";
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
const router = useRouter();
|
||||||
|
const mixin = useCounterMixin();
|
||||||
|
|
||||||
|
const { showLoader, hideLoader } = mixin;
|
||||||
|
|
||||||
|
const username = ref<string>(""); // ผู้ใช้
|
||||||
|
const password = ref<string>(""); // รหัสผ่าน
|
||||||
|
const msgError = ref<string>(""); // ข้อความแสดงข้อผิดพลาด
|
||||||
|
/**
|
||||||
|
* ฟังก์ชั่นเข้าสู่ระบบ
|
||||||
|
*/
|
||||||
|
async function onSubmit() {
|
||||||
|
showLoader();
|
||||||
|
msgError.value = "";
|
||||||
|
// const formdata = new URLSearchParams();
|
||||||
|
// formdata.append("username", username.value);
|
||||||
|
// formdata.append("password", password.value);
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.post(
|
||||||
|
`${import.meta.env.VITE_SSO_URL}/signin`,
|
||||||
|
{
|
||||||
|
username: username.value,
|
||||||
|
password: password.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
withCredentials: true, // Include cookies with the request
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.data === "OK") {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
console.log("res===>", res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
msgError.value = "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง";
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hideLoader();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ทำงานเมื่อ components ถูกเรียกใช้งาน
|
||||||
|
*/
|
||||||
|
onMounted(async () => {});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-image">
|
||||||
|
<div class="shadow-top"></div>
|
||||||
|
<div class="shadow-bottom"></div>
|
||||||
|
<div class="row fit items-center">
|
||||||
|
<div class="row full-width justify-evenly">
|
||||||
|
<div class="">
|
||||||
|
<div id="myimage"></div>
|
||||||
|
|
||||||
|
<div class="text-logo">
|
||||||
|
ระบบบริหารจัดการการใช้งาน <br />ระบบสารสนเทศสนับสนุนการเชื่อมโยง
|
||||||
|
</div>
|
||||||
|
<div class="subtext-logo">
|
||||||
|
Bangkok Metropolitan Administration Single Sign-On
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img src="@/assets/line.png" class="img_absolute_line" />
|
||||||
|
|
||||||
|
<div class="card-pf">
|
||||||
|
<div class="row q-gutter-sm items-center q-mb-sm">
|
||||||
|
<div class="column">
|
||||||
|
<div class="login-pf-header">
|
||||||
|
<h1 id="kc-page-title">เข้าใช้งานระบบ</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-form
|
||||||
|
greedy
|
||||||
|
@submit.prevent
|
||||||
|
@validation-success="onSubmit"
|
||||||
|
style="max-width: 100%; min-width: 30%"
|
||||||
|
>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 form-group">
|
||||||
|
<label
|
||||||
|
for="username"
|
||||||
|
class="pf-c-form__label pf-c-form__label-text"
|
||||||
|
>ชื่อผู้ใช้งาน</label
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
v-model="username"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
lazy-rules
|
||||||
|
hide-bottom-space
|
||||||
|
:rules="[(val:string) => !!val || `${'กรุณากรอกชื่อผู้ใช้งาน'}`,]"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 form-group">
|
||||||
|
<label
|
||||||
|
for="username"
|
||||||
|
class="pf-c-form__label pf-c-form__label-text"
|
||||||
|
>รหัสผ่าน</label
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
v-model="password"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
lazy-rules
|
||||||
|
hide-bottom-space
|
||||||
|
type="password"
|
||||||
|
:rules="[(val:string) => !!val || `${'กรุณากรอกรหัสผ่าน'}`,]"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 text-red text-center">{{ msgError }}</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
class="full-width"
|
||||||
|
label="เข้าระบบ"
|
||||||
|
style="font-size: 16px; border-radius: 8px"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.shadow-top {
|
||||||
|
position: absolute;
|
||||||
|
height: 4rem;
|
||||||
|
width: 4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $primary;
|
||||||
|
top: -4rem;
|
||||||
|
right: -4rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
box-shadow: 0px 0px 300px 320px $primary;
|
||||||
|
backdrop-filter: blur(550px);
|
||||||
|
}
|
||||||
|
.shadow-bottom {
|
||||||
|
position: absolute;
|
||||||
|
height: 4rem;
|
||||||
|
width: 4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $primary;
|
||||||
|
bottom: -4rem;
|
||||||
|
left: -4rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
box-shadow: 0px 0px 300px 320px $primary;
|
||||||
|
backdrop-filter: blur(550px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-icon {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #cc0004;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: #ff0004;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.checkbox,
|
||||||
|
.radio {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
margin: 1px 3px 0 0;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
.checkbox label,
|
||||||
|
.radio label {
|
||||||
|
min-height: 20px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
#kc-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#kc-content-wrapper {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
#kc-form-options .checkbox {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #72767b;
|
||||||
|
}
|
||||||
|
.bg-image {
|
||||||
|
font-family: "Noto Sans Thai", sans-serif;
|
||||||
|
font-family: "Noto Sans Thai", sans-serif;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background-size: cover;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgb(30, 50, 49);
|
||||||
|
background: linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(30, 50, 49, 1) 0%,
|
||||||
|
rgba(20, 120, 99, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#myimage {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
object-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% !important;
|
||||||
|
background: url(@/assets/logo.png) no-repeat center center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.text-logo {
|
||||||
|
text-align: left;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtext-logo {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-pf {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 30%;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10 !important;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-pf-page .login-pf-header {
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-pf-page .login-pf-header h1 {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1#kc-page-title {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-pf-page .login-pf-header h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.img_absolute_line {
|
||||||
|
position: absolute;
|
||||||
|
height: auto;
|
||||||
|
width: 400px;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -28,24 +28,6 @@ export default defineConfig({
|
||||||
name: 'BMA-Checkin',
|
name: 'BMA-Checkin',
|
||||||
short_name: 'EHR Checkin',
|
short_name: 'EHR Checkin',
|
||||||
theme_color: '#ffffff',
|
theme_color: '#ffffff',
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: 'icons/android-chrome-192x192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'icons/android-chrome-512x512.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'icons/android-chrome-512x512.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png',
|
|
||||||
purpose: ['any', 'maskable'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
||||||