start project
This commit is contained in:
commit
ca3c00b6a1
42 changed files with 1310 additions and 0 deletions
7
.env.example
Normal file
7
.env.example
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
KC_URL="https://keycloak-server.com"
|
||||||
|
KC_REALMS="your-realm"
|
||||||
|
|
||||||
|
VITE_API_URI_CONFIG="https://api-server/api/v1"
|
||||||
|
VITE_CLIENTID_KEYCLOAK="your-client-id"
|
||||||
|
|
||||||
|
SSO_COOKIE_NAME="sso"
|
||||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
30
README.md
Normal file
30
README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# HRMS SSO
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format and fixes files
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run format
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
8
cypress.config.ts
Normal file
8
cypress.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
|
||||||
|
baseUrl: 'http://localhost:4173',
|
||||||
|
},
|
||||||
|
})
|
||||||
19
docker/Dockerfile
Normal file
19
docker/Dockerfile
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# docker buildx build --platform=linux/amd64 -f docker/Dockerfile . -t hrms-git.chin.in.th/bma-hrms/hrms-checkin:0.1
|
||||||
|
|
||||||
|
# Build Stage
|
||||||
|
FROM node:20-alpine as build-stage
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY ./ .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production Stage
|
||||||
|
FROM nginx:stable-alpine AS production-stage
|
||||||
|
RUN mkdir /app
|
||||||
|
COPY --from=build-stage /app/dist /app
|
||||||
|
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
RUN chmod u+x /usr/local/bin/entrypoint.sh
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
18
docker/entrypoint.sh
Normal file
18
docker/entrypoint.sh
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ROOT_DIR=/app
|
||||||
|
|
||||||
|
# Replace env vars in JavaScript files
|
||||||
|
echo "Replacing env constants in JS"
|
||||||
|
for file in $ROOT_DIR/assets/app.*.js* $ROOT_DIR/js/app.*.js* $ROOT_DIR/index.html $ROOT_DIR/precache-manifest*.js $ROOT_DIR/assets/index*.js*;
|
||||||
|
do
|
||||||
|
echo "Processing $file ...";
|
||||||
|
|
||||||
|
sed -i 's|VITE_API_URI_CONFIG|'${VITE_API_URI_CONFIG}'|g' $file
|
||||||
|
sed -i 's|VITE_URL_SSO|'${VITE_URL_SSO}'|g' $file
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Starting Nginx"
|
||||||
|
nginx -g 'daemon off;'
|
||||||
|
|
||||||
30
docker/nginx.conf
Normal file
30
docker/nginx.conf
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
user nginx;
|
||||||
|
worker_processes 1;
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
location / {
|
||||||
|
root /app;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
env.d.ts
vendored
Normal file
1
env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
34
index.html
Normal file
34
index.html
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>HRMS SSO</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#viewDiv {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
package.json
Normal file
53
package.json
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"name": "hrms-sso",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p build-only",
|
||||||
|
"preview": "vite preview --port 3002",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier ./src --write"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@arcgis/core": "^4.28.10",
|
||||||
|
"@quasar/extras": "^1.15.8",
|
||||||
|
"@vuepic/vue-datepicker": "^5.2.1",
|
||||||
|
"keycloak-js": "^22.0.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"pinia": "^2.1.4",
|
||||||
|
"quasar": "^2.17.5",
|
||||||
|
"register-service-worker": "^1.7.2",
|
||||||
|
"simple-vue-camera": "^1.1.3",
|
||||||
|
"vite-plugin-pwa": "^0.16.7",
|
||||||
|
"vue": "^3.4.15",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@quasar/vite-plugin": "^1.3.0",
|
||||||
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
|
"@types/jsdom": "^20.0.1",
|
||||||
|
"@types/node": "^18.18.10",
|
||||||
|
"@types/vue-router": "^2.0.0",
|
||||||
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||||
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
|
"@vue/test-utils": "^2.2.6",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"cypress": "^12.0.2",
|
||||||
|
"eslint": "^8.22.0",
|
||||||
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
|
"jsdom": "^20.0.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"sass": "^1.32.12",
|
||||||
|
"start-server-and-test": "^1.15.2",
|
||||||
|
"typescript": "~4.7.4",
|
||||||
|
"vite": "^4.0.0",
|
||||||
|
"vitest": "^0.25.6",
|
||||||
|
"vue-tsc": "^1.0.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
26
src/App.vue
Normal file
26
src/App.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#app {
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a.router-link-exact-active {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
src/api/api.org.ts
Normal file
7
src/api/api.org.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import env from "./index";
|
||||||
|
const org = `${env.API_URI}/org`;
|
||||||
|
// const log = `${env.API_URI}/log`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
org,
|
||||||
|
};
|
||||||
29
src/api/index.ts
Normal file
29
src/api/index.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**config api */
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const env = ref<string>(process.env.NODE_ENV || 'development')
|
||||||
|
export const apiUrlConfig = import.meta.env.VITE_API_URI_CONFIG
|
||||||
|
// if (process.env.VUE_APP_TEST) {
|
||||||
|
// env = "test";
|
||||||
|
// }
|
||||||
|
|
||||||
|
const config = ref<any>({
|
||||||
|
development: {
|
||||||
|
// API_URI: "https://localhost:7260/api",
|
||||||
|
API_URI: 'https://bma-ehr.frappet.synology.me/api/v1',
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
API_URI: 'http://localhost:5010/api/v1',
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
API_URI: apiUrlConfig,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const API_URI = ref<string>(config.value[env.value].API_URI)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
env: env.value,
|
||||||
|
config: config.value,
|
||||||
|
API_URI: API_URI.value,
|
||||||
|
}
|
||||||
13
src/app.config.ts
Normal file
13
src/app.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**ใช้รวมไฟล์ย่อยๆ ของ api แต่ละไฟล์ */
|
||||||
|
|
||||||
|
/** API ระบบลงเวลา */
|
||||||
|
import org from "@/api/api.org";
|
||||||
|
|
||||||
|
const API = {
|
||||||
|
/**message */
|
||||||
|
...org,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
API: API,
|
||||||
|
};
|
||||||
51
src/assets/key/BMA
Normal file
51
src/assets/key/BMA
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
1
src/assets/key/BMA.pub
Normal file
1
src/assets/key/BMA.pub
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQClVi9NES4zetZ5pvbr40/VWDu3xjv2298+EySGqFG/YTh9a6pyzIaxrNHPuZvQH2p79B2xE2HmuAqTLFQhRhUiFKBaXa4TONXNcF6++by7sCSDWr5aTtI8gR/gaLHEwh81ZDB5ukVeGd2DtZxBHPzoBBQvKQuEkUP1qJsNRBBNYuSRq6dXEvyv2IK4RaPomWEdflqjV+6BA+A6GtOq3dc9sRSrLFpMaVZMY38fZPEOM8fBJRZdHfv7ktW1H6klEqKt8kOtjXCvjRKlNUn6HX3X+V6NYu/cJdZLFj2hWdgE/c/mP7gqrRnsS3WC8HL7eY6wFOu8PxPQicF/R44sPz3pDizv2mRT3Tl7G3bgVd8OlLxk3SuLY5WguZz7LzvP9N+lOBl29sHOt2wZeeZit+t7j/8N+KC1i7J2gplGn0CobekjSy8Y1VzCWZN+EKXstxLpe7ajxamw2BC4nc+CbsAD4U6sgJ7qiu4xfKjgNo2YYrG3ySVN+qMYDzt0P5P+Z5XeFDfwohcPvnQkRtsNXNKT7JEiDrqZGDx/qg4ePmqrXYi/kJ/ufclA0HjMSy8+kFZSrezquK7Fa2slADSh895sQS5W6D95LLYs4PkeFXNyq0L2FuJ2DyAF4Cc+ojSguksFQmdE46Fu0Qut2EgsHESV7CHaXi3bG8Yb2GkC+M+nTQ== waruneeta@AUYs-MacBook-Pro.local
|
||||||
14
src/assets/key/BMA.pub.pem
Normal file
14
src/assets/key/BMA.pub.pem
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApVYvTREuM3rWeab26+NP
|
||||||
|
1Vg7t8Y79tvfPhMkhqhRv2E4fWuqcsyGsazRz7mb0B9qe/QdsRNh5rgKkyxUIUYV
|
||||||
|
IhSgWl2uEzjVzXBevvm8u7Akg1q+Wk7SPIEf4GixxMIfNWQwebpFXhndg7WcQRz8
|
||||||
|
6AQULykLhJFD9aibDUQQTWLkkaunVxL8r9iCuEWj6JlhHX5ao1fugQPgOhrTqt3X
|
||||||
|
PbEUqyxaTGlWTGN/H2TxDjPHwSUWXR37+5LVtR+pJRKirfJDrY1wr40SpTVJ+h19
|
||||||
|
1/lejWLv3CXWSxY9oVnYBP3P5j+4Kq0Z7Et1gvBy+3mOsBTrvD8T0InBf0eOLD89
|
||||||
|
6Q4s79pkU905ext24FXfDpS8ZN0ri2OVoLmc+y87z/TfpTgZdvbBzrdsGXnmYrfr
|
||||||
|
e4//DfigtYuydoKZRp9AqG3pI0svGNVcwlmTfhCl7LcS6Xu2o8WpsNgQuJ3Pgm7A
|
||||||
|
A+FOrICe6oruMXyo4DaNmGKxt8klTfqjGA87dD+T/meV3hQ38KIXD750JEbbDVzS
|
||||||
|
k+yRIg66mRg8f6oOHj5qq12Iv5Cf7n3JQNB4zEsvPpBWUq3s6riuxWtrJQA0ofPe
|
||||||
|
bEEuVug/eSy2LOD5HhVzcqtC9hbidg8gBeAnPqI0oLpLBUJnROOhbtELrdhILBxE
|
||||||
|
lewh2l4t2xvGG9hpAvjPp00CAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
BIN
src/assets/line.png
Normal file
BIN
src/assets/line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
BIN
src/assets/sso.png
Normal file
BIN
src/assets/sso.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
57
src/interface/index/Main.ts
Normal file
57
src/interface/index/Main.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
interface DataOption {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataDateMonthObject {
|
||||||
|
month: number
|
||||||
|
year: number
|
||||||
|
}
|
||||||
|
interface FormRef {
|
||||||
|
date: object | null
|
||||||
|
reason: object | null
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
interface notiType {
|
||||||
|
id: string
|
||||||
|
sender: string
|
||||||
|
body: string
|
||||||
|
timereceive: Date
|
||||||
|
isOpen: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LocationObject {
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Pagination {
|
||||||
|
sortBy: string | null
|
||||||
|
descending: boolean
|
||||||
|
page: number
|
||||||
|
rowsPerPage: number | undefined
|
||||||
|
}
|
||||||
|
interface DataCheckIn {
|
||||||
|
checkInDate: string
|
||||||
|
checkInDateTime: string
|
||||||
|
checkInId: string
|
||||||
|
checkInLocation: string
|
||||||
|
checkInStatus: string
|
||||||
|
checkInTime: string
|
||||||
|
checkOutLocation: string
|
||||||
|
checkOutStatus: string
|
||||||
|
checkOutTime: string
|
||||||
|
editReason: string
|
||||||
|
editStatus: string
|
||||||
|
isEdit: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type {
|
||||||
|
DataOption,
|
||||||
|
FormRef,
|
||||||
|
notiType,
|
||||||
|
DataDateMonthObject,
|
||||||
|
LocationObject,
|
||||||
|
Pagination,
|
||||||
|
DataCheckIn,
|
||||||
|
}
|
||||||
13
src/interface/response/Main.ts
Normal file
13
src/interface/response/Main.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
interface Noti {
|
||||||
|
id: string
|
||||||
|
body: string
|
||||||
|
receiverUserId: string
|
||||||
|
type: string
|
||||||
|
payload: null
|
||||||
|
isOpen: false
|
||||||
|
receiveDate: Date
|
||||||
|
openDate: null
|
||||||
|
createdFullName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { Noti }
|
||||||
38
src/main.ts
Normal file
38
src/main.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { createApp, defineAsyncComponent } from 'vue'
|
||||||
|
import App from '@/App.vue'
|
||||||
|
import '@/registerServiceWorker'
|
||||||
|
import router from '@/router'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import { Quasar, Dialog, Notify, Loading } from 'quasar'
|
||||||
|
import '@vuepic/vue-datepicker/dist/main.css'
|
||||||
|
import quasarUserOptions from '@/quasar-user-options'
|
||||||
|
|
||||||
|
import 'quasar/src/css/index.sass'
|
||||||
|
import th from 'quasar/lang/th'
|
||||||
|
|
||||||
|
import http from '@/plugins/http'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
app.use(Quasar, {
|
||||||
|
...quasarUserOptions,
|
||||||
|
plugins: {
|
||||||
|
Notify,
|
||||||
|
Dialog,
|
||||||
|
Loading,
|
||||||
|
},
|
||||||
|
lang: th,
|
||||||
|
})
|
||||||
|
|
||||||
|
app.component(
|
||||||
|
'datepicker',
|
||||||
|
defineAsyncComponent(() => import('@vuepic/vue-datepicker'))
|
||||||
|
)
|
||||||
|
|
||||||
|
app.config.globalProperties.$http = http
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
24
src/plugins/axios.ts
Normal file
24
src/plugins/axios.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
// import { dotnetPath } from "../path/axiosPath";
|
||||||
|
// import { getToken } from "@baloise/vue-keycloak";
|
||||||
|
import { getToken } from './auth'
|
||||||
|
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
withCredentials: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// axiosInstance.defaults.baseURL = dotnetPath;
|
||||||
|
axiosInstance.interceptors.request.use(
|
||||||
|
async (config) => {
|
||||||
|
const token = await getToken()
|
||||||
|
config.headers = {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default axiosInstance
|
||||||
20
src/plugins/filters.ts
Normal file
20
src/plugins/filters.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* GLOABL Filters
|
||||||
|
* - ไฟล์นี้จะไว้เก็บฟังก์ชันง่าย ๆ พวก Helper Functions ทั้งหลาย
|
||||||
|
*/
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
/**
|
||||||
|
* ฟังก์ชัน compactNumber ใช้แปลงตัวเลขยาว ๆ ให้กลายเป็นเลขสั้น ๆ แบบที่พวก Social Media ชอบใช้กัน เช่น 1,000 แปลงเป็น 1K หรือ 1,000,000 แปลงเป็น 1M
|
||||||
|
* วิธีใช้ : {{ $filters.compactNumber(value) }}
|
||||||
|
*
|
||||||
|
* @param val รับค่าพารามิเตอร์เป็นตัวแปรชนิดตัวเลข
|
||||||
|
* @returns คืนค่าเป็นตัวเลขที่แปลงค่าแล้ว
|
||||||
|
*/
|
||||||
|
compactNumber(val: number) {
|
||||||
|
const formatter = Intl.NumberFormat('en', { notation: 'compact' })
|
||||||
|
return formatter.format(val)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default filters
|
||||||
39
src/plugins/http.ts
Normal file
39
src/plugins/http.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import Axios, { type AxiosRequestConfig, type AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
const http = Axios.create({
|
||||||
|
timeout: 1000000000, // เพิ่มค่า timeout
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
http.interceptors.request.use(
|
||||||
|
async function (config: AxiosRequestConfig<any>) {
|
||||||
|
config.headers = config.headers ?? {};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
function (error: any) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
http.interceptors.response.use(
|
||||||
|
function (response: AxiosResponse<any, any>) {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
function (error: any) {
|
||||||
|
if (typeof error !== undefined) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (error.hasOwnProperty("response")) {
|
||||||
|
if (error.response.status === 401 || error.response.status === 403) {
|
||||||
|
// Store.commit("SET_ERROR_MESSAGE", error.response.data.message);
|
||||||
|
// Store.commit("REMOVE_ACCESS_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default http;
|
||||||
70
src/plugins/keycloak.ts
Normal file
70
src/plugins/keycloak.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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,
|
||||||
|
// }
|
||||||
11
src/quasar-user-options.ts
Normal file
11
src/quasar-user-options.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// import "./styles/quasar.scss"
|
||||||
|
import '@quasar/extras/material-icons/material-icons.css'
|
||||||
|
import '@quasar/extras/material-icons-outlined/material-icons-outlined.css'
|
||||||
|
import '@quasar/extras/fontawesome-v5/fontawesome-v5.css'
|
||||||
|
import '@quasar/extras/mdi-v4/mdi-v4.css'
|
||||||
|
|
||||||
|
// To be used on app.use(Quasar, { ... })
|
||||||
|
export default {
|
||||||
|
config: {},
|
||||||
|
plugins: {},
|
||||||
|
}
|
||||||
34
src/registerServiceWorker.ts
Normal file
34
src/registerServiceWorker.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
import { register } from 'register-service-worker'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
register('registerSW.js', {
|
||||||
|
ready() {
|
||||||
|
console.log(
|
||||||
|
'App is being served from cache by a service worker.\n' +
|
||||||
|
'For more details, visit https://goo.gl/AFskqB'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
registered() {
|
||||||
|
console.log('Service worker has been registered.')
|
||||||
|
},
|
||||||
|
cached() {
|
||||||
|
console.log('Content has been cached for offline use.')
|
||||||
|
},
|
||||||
|
updatefound() {
|
||||||
|
console.log('New content is downloading.')
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
console.log('New content is available; please refresh.')
|
||||||
|
},
|
||||||
|
offline() {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
error(error) {
|
||||||
|
console.error('Error during service worker registration:', error)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
29
src/router/index.ts
Normal file
29
src/router/index.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
const loginView = () => import("@/views/login.vue");
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "loginMain",
|
||||||
|
component: loginView,
|
||||||
|
children: [
|
||||||
|
/**
|
||||||
|
* 404 Not Found
|
||||||
|
* ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
path: "/:pathMatch(.*)*",
|
||||||
|
name: "NotFound",
|
||||||
|
component: () => import("@/views/ErrorNotFoundPage.vue"),
|
||||||
|
meta: {
|
||||||
|
Auth: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
1
src/shims-vue.d.ts
vendored
Normal file
1
src/shims-vue.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
declare module '*.vue'
|
||||||
13
src/stores/main.ts
Normal file
13
src/stores/main.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useCounterMixin } from '@/stores/mixin'
|
||||||
|
|
||||||
|
const mixin = useCounterMixin()
|
||||||
|
|
||||||
|
/** store for checkin history*/
|
||||||
|
export const useSsoHrms = defineStore('hrmssso', () => {
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
91
src/stores/mixin.ts
Normal file
91
src/stores/mixin.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { Loading, QSpinnerCube } from "quasar";
|
||||||
|
|
||||||
|
export const useCounterMixin = defineStore("mixin", () => {
|
||||||
|
function date2Thai(srcDate: Date, isFullMonth = false, isTime = false) {
|
||||||
|
if (srcDate == null) {
|
||||||
|
return null;
|
||||||
|
`
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
const date = new Date(srcDate);
|
||||||
|
const isValidDate = Boolean(+date);
|
||||||
|
if (!isValidDate) return srcDate.toString();
|
||||||
|
if (isValidDate && date.getFullYear() < 1000) return srcDate.toString();
|
||||||
|
const fullMonthThai = [
|
||||||
|
"มกราคม",
|
||||||
|
"กุมภาพันธ์",
|
||||||
|
"มีนาคม",
|
||||||
|
"เมษายน",
|
||||||
|
"พฤษภาคม",
|
||||||
|
"มิถุนายน",
|
||||||
|
"กรกฎาคม",
|
||||||
|
"สิงหาคม",
|
||||||
|
"กันยายน",
|
||||||
|
"ตุลาคม",
|
||||||
|
"พฤศจิกายน",
|
||||||
|
"ธันวาคม",
|
||||||
|
];
|
||||||
|
const abbrMonthThai = [
|
||||||
|
"ม.ค.",
|
||||||
|
"ก.พ.",
|
||||||
|
"มี.ค.",
|
||||||
|
"เม.ย.",
|
||||||
|
"พ.ค.",
|
||||||
|
"มิ.ย.",
|
||||||
|
"ก.ค.",
|
||||||
|
"ส.ค.",
|
||||||
|
"ก.ย.",
|
||||||
|
"ต.ค.",
|
||||||
|
"พ.ย.",
|
||||||
|
"ธ.ค.",
|
||||||
|
];
|
||||||
|
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()];
|
||||||
|
}
|
||||||
|
let dstTime = "";
|
||||||
|
if (isTime) {
|
||||||
|
const H = date.getHours().toString().padStart(2, "0");
|
||||||
|
const M = date.getMinutes().toString().padStart(2, "0");
|
||||||
|
// const S = date.getSeconds().toString().padStart(2, "0")
|
||||||
|
// dstTime = " " + H + ":" + M + ":" + S + " น."
|
||||||
|
dstTime = " " + H + ":" + M + " น.";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
date.getDate().toString().padStart(2, "0") +
|
||||||
|
" " +
|
||||||
|
dstMonth +
|
||||||
|
" " +
|
||||||
|
dstYear +
|
||||||
|
dstTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const showLoader = () => {
|
||||||
|
Loading.show({
|
||||||
|
spinner: QSpinnerCube,
|
||||||
|
spinnerSize: 140,
|
||||||
|
spinnerColor: "primary",
|
||||||
|
backgroundColor: "white",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideLoader = () => {
|
||||||
|
Loading.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
date2Thai,
|
||||||
|
showLoader,
|
||||||
|
hideLoader,
|
||||||
|
};
|
||||||
|
});
|
||||||
139
src/style/quasar-variables.sass
Normal file
139
src/style/quasar-variables.sass
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
// FILE (create it): src/quasar-variables.sass
|
||||||
|
|
||||||
|
$primary: #02A998
|
||||||
|
$secondary: #016987
|
||||||
|
$accent: #9C27B0
|
||||||
|
|
||||||
|
// $dark: #1D1D1D
|
||||||
|
$dark: #35473C
|
||||||
|
|
||||||
|
$positive: #21BA45
|
||||||
|
$negative: #C10015
|
||||||
|
$info: #31CCEC
|
||||||
|
$warning: #F2C037
|
||||||
|
|
||||||
|
$add: #00aa86
|
||||||
|
.text-add
|
||||||
|
color: $add !important
|
||||||
|
.bg-add
|
||||||
|
background: $add !important
|
||||||
|
|
||||||
|
$edit: #019fc4
|
||||||
|
.text-edit
|
||||||
|
color: $edit !important
|
||||||
|
.bg-edit
|
||||||
|
background: $edit !important
|
||||||
|
|
||||||
|
$public: #016987
|
||||||
|
.text-public
|
||||||
|
color: $public !important
|
||||||
|
.bg-public
|
||||||
|
background: $public !important
|
||||||
|
|
||||||
|
$save: #4154b3
|
||||||
|
.text-save
|
||||||
|
color: $save !important
|
||||||
|
.bg-save
|
||||||
|
background: $save !important
|
||||||
|
|
||||||
|
$nativetab: #c8d3db
|
||||||
|
.text-nativetab
|
||||||
|
color: $nativetab !important
|
||||||
|
.bg-nativetab
|
||||||
|
background: $nativetab !important
|
||||||
|
|
||||||
|
$activetab: #4a5568
|
||||||
|
.text-activetab
|
||||||
|
color: $activetab !important
|
||||||
|
.bg-activetab
|
||||||
|
background: $activetab !important
|
||||||
|
|
||||||
|
.inputgreen .q-field__prefix,
|
||||||
|
.inputgreen .q-field__suffix,
|
||||||
|
.inputgreen .q-field__input,
|
||||||
|
.inputgreen .q-field__native
|
||||||
|
|
||||||
|
color: rgb(6, 136, 77)
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@100;200;300;400;500;600;700;800;900&display=swap')
|
||||||
|
|
||||||
|
$noto-thai: 'Noto Sans Thai', sans-serif
|
||||||
|
|
||||||
|
#azay-app,
|
||||||
|
div
|
||||||
|
font-family: $noto-thai !important
|
||||||
|
text-rendering: optimizeLegibility
|
||||||
|
-webkit-font-smoothing: antialiased
|
||||||
|
-moz-osx-font-smoothing: grayscale
|
||||||
|
|
||||||
|
$separator-color: #EDEDED !default
|
||||||
|
|
||||||
|
.bg-teal-1
|
||||||
|
background: #e0f2f1a6 !important
|
||||||
|
|
||||||
|
.table_ellipsis
|
||||||
|
max-width: 200px
|
||||||
|
white-space: nowrap
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
|
||||||
|
.table_ellipsis:hover
|
||||||
|
word-wrap: break-word
|
||||||
|
overflow: visible
|
||||||
|
white-space: normal
|
||||||
|
|
||||||
|
.table_ellipsis2
|
||||||
|
max-width: 25vw
|
||||||
|
white-space: nowrap
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
|
||||||
|
.table_ellipsis2:hover
|
||||||
|
word-wrap: break-word
|
||||||
|
overflow: visible
|
||||||
|
white-space: normal
|
||||||
|
transition: width 2s
|
||||||
|
|
||||||
|
$muti-tab: #87d4cc
|
||||||
|
.text-muti-tab
|
||||||
|
color: $muti-tab !important
|
||||||
|
.bg-muti-tab
|
||||||
|
background: $muti-tab !important
|
||||||
|
|
||||||
|
|
||||||
|
/* editor */
|
||||||
|
|
||||||
|
.q-editor
|
||||||
|
font-size: 1rem
|
||||||
|
line-height: 1.5rem
|
||||||
|
font-weight: 400
|
||||||
|
|
||||||
|
.q-editor h1, .q-menu h1
|
||||||
|
font-size: 1.5rem
|
||||||
|
line-height: 2rem
|
||||||
|
font-weight: 400
|
||||||
|
margin-block-start: 0em
|
||||||
|
margin-block-end: 0em
|
||||||
|
|
||||||
|
.q-editor h2, .q-menu h2
|
||||||
|
font-size: 1.25rem
|
||||||
|
line-height: 1.5rem
|
||||||
|
font-weight: 400
|
||||||
|
margin-block-start: 0em
|
||||||
|
margin-block-end: 0em
|
||||||
|
|
||||||
|
|
||||||
|
.q-editor h3, .q-menu h3
|
||||||
|
font-size: 1.1rem
|
||||||
|
line-height: 1.5rem
|
||||||
|
font-weight: 400
|
||||||
|
margin-block-start: 0em
|
||||||
|
margin-block-end: 0em
|
||||||
|
|
||||||
|
.q-editor p, .q-menu p
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
/* q-tree */
|
||||||
|
|
||||||
|
.q-tree
|
||||||
|
color: #c8d3db
|
||||||
27
src/views/ErrorNotFoundPage.vue
Normal file
27
src/views/ErrorNotFoundPage.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Error404NotFound',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="fullscreen bg-secondary text-white text-center q-pa-md flex flex-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="text-h1">ไม่พบหน้าที่ต้องการ</div>
|
||||||
|
<div class="text-h2">(404 Page Not Found)</div>
|
||||||
|
<q-btn
|
||||||
|
class="q-mt-xl"
|
||||||
|
color="white"
|
||||||
|
text-color="secondary"
|
||||||
|
unelevated
|
||||||
|
to="/"
|
||||||
|
label="กลับไปหน้าหลัก"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
210
src/views/login.vue
Normal file
210
src/views/login.vue
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!-- authen with keycloak client -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useCounterMixin } from "@/stores/mixin";
|
||||||
|
import config from "@/app.config";
|
||||||
|
|
||||||
|
const mixin = useCounterMixin();
|
||||||
|
|
||||||
|
const { showLoader, hideLoader } = mixin;
|
||||||
|
|
||||||
|
const msgError = ref<string>("");
|
||||||
|
const username = ref<string>("");
|
||||||
|
const password = ref<string>("");
|
||||||
|
async function onSubmit() {
|
||||||
|
showLoader();
|
||||||
|
const formdata = new URLSearchParams();
|
||||||
|
formdata.append("username", username.value);
|
||||||
|
formdata.append("password", password.value);
|
||||||
|
|
||||||
|
await axios
|
||||||
|
.post(`${config.API.org}/login`, formdata, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
// setAuthen(res.data.result);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// $q.dialog({
|
||||||
|
// component: CustomComponent,
|
||||||
|
// componentProps: {
|
||||||
|
// title: `ข้อความแจ้งเตือน`,
|
||||||
|
// message: `${err.response.data.message}`,
|
||||||
|
// icon: "warning",
|
||||||
|
// color: "red",
|
||||||
|
// onlycancel: true,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hideLoader();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// const checkAuthen = await authenticated();
|
||||||
|
// if (checkAuthen) {
|
||||||
|
// router.push("/");
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="vh-100" style="overflow: hidden">
|
||||||
|
<div class="container-fluid h-custom">
|
||||||
|
<div class="row d-flex justify-content-center align-items-center h-100">
|
||||||
|
<!-- 1 sec -->
|
||||||
|
<div
|
||||||
|
class="col-md-12 col-lg-6 bg-img d-none d-lg-block position-relative"
|
||||||
|
>
|
||||||
|
<img src="@/assets/line.png" class="img_absolute_line" />
|
||||||
|
<div
|
||||||
|
class="d-flex flex-column justify-content-center align-items-center vh-100"
|
||||||
|
>
|
||||||
|
<div class="text-white position-relative">
|
||||||
|
<img src="@/assets/sso.png" class="img_absolute" />
|
||||||
|
<h4>ระบบบริหารจัดการการใช้งาน</h4>
|
||||||
|
<h4 class="pb-2">ระบบสารสนเทศสนับสนุนการเชื่อมโยง</h4>
|
||||||
|
<p class="mb-0 txt_detail">Bangkok Metropolitan Administration</p>
|
||||||
|
<p class="mb-0 txt_detail">Single Sign-On</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 2 sec -->
|
||||||
|
|
||||||
|
<div class="col-10 col-md-8 col-lg-5 offset-lg-1">
|
||||||
|
<form class="bg_md" id="contact-form">
|
||||||
|
<div class="d_over">
|
||||||
|
<div class="text-center">
|
||||||
|
<img src="@/assets/sso.png" />
|
||||||
|
</div>
|
||||||
|
<div class="my-3 text-center" style="color: #00aa86">
|
||||||
|
<h5>ระบบบริหารจัดการการใช้งาน</h5>
|
||||||
|
<h5 class="pb-1">ระบบสารสนเทศสนับสนุนการเชื่อมโยง</h5>
|
||||||
|
<p class="mb-0 txt_detail">
|
||||||
|
Bangkok Metropolitan Administration
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 txt_detail">Single Sign-On</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="d-flex flex-row align-items-center justify-content-center justify-content-lg-start"
|
||||||
|
>
|
||||||
|
<p class="lead fw-normal mb-0 me-3 mb-2">
|
||||||
|
<strong>เข้าใช้งานระบบ</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-12 col-lg-9">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
value=""
|
||||||
|
class="form-control form-control-lg"
|
||||||
|
placeholder="ชื่อผู้ใช้"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12 col-lg-9">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
value=""
|
||||||
|
class="form-control form-control-lg"
|
||||||
|
placeholder="รหัสผ่าน"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12 col-lg-9 d-grid">
|
||||||
|
<button type="submit" class="btn_custom">เข้าระบบ</button>
|
||||||
|
<div id="response-message">{{ msgError }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.img_absolute_line {
|
||||||
|
position: absolute;
|
||||||
|
height: auto;
|
||||||
|
width: 50%;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.txt_detail {
|
||||||
|
color: #c0c0c0;
|
||||||
|
}
|
||||||
|
.img_absolute {
|
||||||
|
position: absolute;
|
||||||
|
top: -170px;
|
||||||
|
}
|
||||||
|
.d_over {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.btn_custom {
|
||||||
|
background-color: #00aa86;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 0;
|
||||||
|
padding: 8px 0 8px 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.btn_custom:hover {
|
||||||
|
background-color: #00ca9f;
|
||||||
|
}
|
||||||
|
.bg-img {
|
||||||
|
background: rgb(30, 50, 49);
|
||||||
|
background: linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(30, 50, 49, 1) 0%,
|
||||||
|
rgba(20, 120, 99, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: "Noto Sans Thai", sans-serif !important;
|
||||||
|
}
|
||||||
|
.h-custom {
|
||||||
|
height: calc(100% - 73px);
|
||||||
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
.h-custom {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
/* Up to medium screens */
|
||||||
|
section.vh-100 {
|
||||||
|
background: rgb(30, 50, 49);
|
||||||
|
background: linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(30, 50, 49, 1) 0%,
|
||||||
|
rgba(20, 120, 99, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.bg_md {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
.d_over {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.txt_detail {
|
||||||
|
color: #949494;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#response-message {
|
||||||
|
color: red;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
tsconfig.app.json
Normal file
16
tsconfig.app.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"ignoreDeprecations": "5.0",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"lib": ["dom", "es2015", "es2018", "es2018.promise"]
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tsconfig.config.json
Normal file
16
tsconfig.config.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"types": ["node"],
|
||||||
|
"ignoreDeprecations": "5.0",
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"module": "esnext"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.config.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
tsconfig.vitest.json
Normal file
9
tsconfig.vitest.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
||||||
66
vite.config.js
Normal file
66
vite.config.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue({
|
||||||
|
template: { transformAssetUrls },
|
||||||
|
}),
|
||||||
|
quasar({
|
||||||
|
sassVariables: 'src/style/quasar-variables.sass',
|
||||||
|
}),
|
||||||
|
vueJsx(),
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'autoUpdate',
|
||||||
|
injectRegister: 'auto',
|
||||||
|
workbox: {
|
||||||
|
cleanupOutdatedCaches: true,
|
||||||
|
globPatterns: ['**/*.*'],
|
||||||
|
},
|
||||||
|
includeAssets: ['icons/safari-pinned-tab.svg'],
|
||||||
|
manifest: {
|
||||||
|
name: 'BMA-Checkin',
|
||||||
|
short_name: 'EHR Checkin',
|
||||||
|
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'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3002,
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['esri-loader'],
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue