upload new version
24
README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# maptest
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
babel.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
40
package.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "maptest",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@googlemaps/js-api-loader": "^1.16.2",
|
||||
"@quasar/cli": "^2.3.0",
|
||||
"@quasar/extras": "^1.0.0",
|
||||
"@vuepic/vue-datepicker": "^7.2.1",
|
||||
"core-js": "^3.8.3",
|
||||
"moment": "^2.29.4",
|
||||
"pinia": "^2.1.7",
|
||||
"quasar": "^2.0.0",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.0.3",
|
||||
"vue3-google-map": "^0.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-pwa": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/eslint-config-typescript": "^9.1.0",
|
||||
"eslint": "^8.53.0",
|
||||
"sass": "1.32.12",
|
||||
"sass-loader": "^12.0.0",
|
||||
"typescript": "~4.5.5",
|
||||
"vue-cli-plugin-quasar": "~5.0.2"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
public/img/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
public/img/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/img/icons/android-chrome-maskable-192x192.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
public/img/icons/android-chrome-maskable-512x512.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/img/icons/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
public/img/icons/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
public/img/icons/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/img/icons/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/img/icons/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/img/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/img/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 799 B |
BIN
public/img/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/img/icons/msapplication-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
3
public/img/icons/safari-pinned-tab.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 215 B |
23
public/index.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
|
||||
properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBzPSF5NxUZ1G8DKBnJvJPTqCR0Ct2xf58&libraries=places"></script>
|
||||
</div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
2
public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow:
|
||||
30
src/App.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<!-- <nav>
|
||||
<router-link to="/">Home</router-link> |
|
||||
<router-link to="/about">About</router-link>
|
||||
</nav> -->
|
||||
<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>
|
||||
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
15
src/assets/logo.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356 360">
|
||||
<path
|
||||
d="M43.4 303.4c0 3.8-2.3 6.3-7.1 6.3h-15v-22h14.4c4.3 0 6.2 2.2 6.2 5.2 0 2.6-1.5 4.4-3.4 5 2.8.4 4.9 2.5 4.9 5.5zm-8-13H24.1v6.9H35c2.1 0 4-1.3 4-3.8 0-2.2-1.3-3.1-3.7-3.1zm5.1 12.6c0-2.3-1.8-3.7-4-3.7H24.2v7.7h11.7c3.4 0 4.6-1.8 4.6-4zm36.3 4v2.7H56v-22h20.6v2.7H58.9v6.8h14.6v2.3H58.9v7.5h17.9zm23-5.8v8.5H97v-8.5l-11-13.4h3.4l8.9 11 8.8-11h3.4l-10.8 13.4zm19.1-1.8V298c0-7.9 5.2-10.7 12.7-10.7 7.5 0 13 2.8 13 10.7v1.4c0 7.9-5.5 10.8-13 10.8s-12.7-3-12.7-10.8zm22.7 0V298c0-5.7-3.9-8-10-8-6 0-9.8 2.3-9.8 8v1.4c0 5.8 3.8 8.1 9.8 8.1 6 0 10-2.3 10-8.1zm37.2-11.6v21.9h-2.9l-15.8-17.9v17.9h-2.8v-22h3l15.6 18v-18h2.9zm37.9 10.2v1.3c0 7.8-5.2 10.4-12.4 10.4H193v-22h11.2c7.2 0 12.4 2.8 12.4 10.3zm-3 0c0-5.3-3.3-7.6-9.4-7.6h-8.4V307h8.4c6 0 9.5-2 9.5-7.7V298zm50.8-7.6h-9.7v19.3h-3v-19.3h-9.7v-2.6h22.4v2.6zm34.4-2.6v21.9h-3v-10.1h-16.8v10h-2.8v-21.8h2.8v9.2H296v-9.2h2.9zm34.9 19.2v2.7h-20.7v-22h20.6v2.7H316v6.8h14.5v2.3H316v7.5h17.8zM24 340.2v7.3h13.9v2.4h-14v9.6H21v-22h20v2.7H24zm41.5 11.4h-9.8v7.9H53v-22h13.3c5.1 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6H66c3.1 0 5.3-1.5 5.3-4.7 0-3.3-2.2-4.1-5.3-4.1H55.7v8.8zm47.9 6.2H89l-2 4.3h-3.2l10.7-22.2H98l10.7 22.2h-3.2l-2-4.3zm-1-2.3l-6.3-13-6 13h12.2zm46.3-15.3v21.9H146v-17.2L135.7 358h-2.1l-10.2-15.6v17h-2.8v-21.8h3l11 16.9 11.3-17h3zm35 19.3v2.6h-20.7v-22h20.6v2.7H166v6.8h14.5v2.3H166v7.6h17.8zm47-19.3l-8.3 22h-3l-7.1-18.6-7 18.6h-3l-8.2-22h3.3L204 356l6.8-18.5h3.4L221 356l6.6-18.5h3.3zm10 11.6v-1.4c0-7.8 5.2-10.7 12.7-10.7 7.6 0 13 2.9 13 10.7v1.4c0 7.9-5.4 10.8-13 10.8-7.5 0-12.7-3-12.7-10.8zm22.8 0v-1.4c0-5.7-4-8-10-8s-9.9 2.3-9.9 8v1.4c0 5.8 3.8 8.2 9.8 8.2 6.1 0 10-2.4 10-8.2zm28.3 2.4h-9.8v7.9h-2.8v-22h13.2c5.2 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6h10.2c3 0 5.2-1.5 5.2-4.7 0-3.3-2.1-4.1-5.2-4.1h-10.2v8.8zm40.3-1.5l-6.8 5.6v6.4h-2.9v-22h2.9v12.3l15.2-12.2h3.7l-9.9 8.1 10.3 13.8h-3.6l-8.9-12z" />
|
||||
<path fill="#050A14"
|
||||
d="M188.4 71.7a10.4 10.4 0 01-20.8 0 10.4 10.4 0 1120.8 0zM224.2 45c-2.2-3.9-5-7.5-8.2-10.7l-12 7c-3.7-3.2-8-5.7-12.6-7.3a49.4 49.4 0 00-9.7 13.9 59 59 0 0140.1 14l7.6-4.4a57 57 0 00-5.2-12.5zM178 125.1c4.5 0 9-.6 13.4-1.7v-14a40 40 0 0012.5-7.2 47.7 47.7 0 00-7.1-15.3 59 59 0 01-32.2 27.7v8.7c4.4 1.2 8.9 1.8 13.4 1.8zM131.8 45c-2.3 4-4 8.1-5.2 12.5l12 7a40 40 0 000 14.4c5.7 1.5 11.3 2 16.9 1.5a59 59 0 01-8-41.7l-7.5-4.3c-3.2 3.2-6 6.7-8.2 10.6z" />
|
||||
<path fill="#00B4FF"
|
||||
d="M224.2 98.4c2.3-3.9 4-8 5.2-12.4l-12-7a40 40 0 000-14.5c-5.7-1.5-11.3-2-16.9-1.5a59 59 0 018 41.7l7.5 4.4c3.2-3.2 6-6.8 8.2-10.7zm-92.4 0c2.2 4 5 7.5 8.2 10.7l12-7a40 40 0 0012.6 7.3c4-4.1 7.3-8.8 9.7-13.8a59 59 0 01-40-14l-7.7 4.4c1.2 4.3 3 8.5 5.2 12.4zm46.2-80c-4.5 0-9 .5-13.4 1.7V34a40 40 0 00-12.5 7.2c1.5 5.7 4 10.8 7.1 15.4a59 59 0 0132.2-27.7V20a53.3 53.3 0 00-13.4-1.8z" />
|
||||
<path fill="#00B4FF"
|
||||
d="M178 9.2a62.6 62.6 0 11-.1 125.2A62.6 62.6 0 01178 9.2m0-9.2a71.7 71.7 0 100 143.5A71.7 71.7 0 00178 0z" />
|
||||
<path fill="#050A14"
|
||||
d="M96.6 212v4.3c-9.2-.8-15.4-5.8-15.4-17.8V180h4.6v18.4c0 8.6 4 12.6 10.8 13.5zm16-31.9v18.4c0 8.9-4.3 12.8-10.9 13.5v4.4c9.2-.7 15.5-5.6 15.5-18v-18.3h-4.7zM62.2 199v-2.2c0-12.7-8.8-17.4-21-17.4-12.1 0-20.7 4.7-20.7 17.4v2.2c0 12.8 8.6 17.6 20.7 17.6 1.5 0 3-.1 4.4-.3l11.8 6.2 2-3.3-8.2-4-6.4-3.1a32 32 0 01-3.6.2c-9.8 0-16-3.9-16-13.3v-2.2c0-9.3 6.2-13.1 16-13.1 9.9 0 16.3 3.8 16.3 13.1v2.2c0 5.3-2.1 8.7-5.6 10.8l4.8 2.4c3.4-2.8 5.5-7 5.5-13.2zM168 215.6h5.1L156 179.7h-4.8l17 36zM143 205l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.8-3.7H143zm133.7 10.7h5.2l-17.3-35.9h-4.8l17 36zm-25-10.7l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.7-3.7h-14.8zm73.8-2.5c6-1.2 9-5.4 9-11.4 0-8-4.5-10.9-12.9-10.9h-21.4v35.5h4.6v-31.3h16.5c5 0 8.5 1.4 8.5 6.7 0 5.2-3.5 7.7-8.5 7.7h-11.4v4.1h10.7l9.3 12.8h5.5l-9.9-13.2zm-117.4 9.9c-9.7 0-14.7-2.5-18.6-6.3l-2.2 3.8c5.1 5 11 6.7 21 6.7 1.6 0 3.1-.1 4.6-.3l-1.9-4h-3zm18.4-7c0-6.4-4.7-8.6-13.8-9.4l-10.1-1c-6.7-.7-9.3-2.2-9.3-5.6 0-2.5 1.4-4 4.6-5l-1.8-3.8c-4.7 1.4-7.5 4.2-7.5 8.9 0 5.2 3.4 8.7 13 9.6l11.3 1.2c6.4.6 8.9 2 8.9 5.4 0 2.7-2.1 4.7-6 5.8l1.8 3.9c5.3-1.6 8.9-4.7 8.9-10zm-20.3-21.9c7.9 0 13.3 1.8 18.1 5.7l1.8-3.9a30 30 0 00-19.6-5.9c-2 0-4 .1-5.7.3l1.9 4 3.5-.2z" />
|
||||
<path fill="#00B4FF"
|
||||
d="M.5 251.9c29.6-.5 59.2-.8 88.8-1l88.7-.3 88.7.3 44.4.4 44.4.6-44.4.6-44.4.4-88.7.3-88.7-.3a7981 7981 0 01-88.8-1z" />
|
||||
<path fill="none" d="M-565.2 324H-252v15.8h-313.2z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/assets/map1.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
146
src/components/FormTime.vue
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, defineProps } from "vue";
|
||||
import { useCounterMixin } from "@/stores/mixin";
|
||||
import moment, { Moment } from "moment";
|
||||
|
||||
const mixin = useCounterMixin();
|
||||
const { date2Thai } = mixin;
|
||||
|
||||
const props = defineProps({
|
||||
dataById: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const dataById = ref<any>([]);
|
||||
const date = ref<Date | null>(null);
|
||||
const checkboxIn = ref<boolean>(false);
|
||||
const checkboxOut = ref<boolean>(false);
|
||||
const reason = ref<string>("");
|
||||
const statusAction = ref<boolean>(false);
|
||||
|
||||
onMounted(() => {
|
||||
updateClock();
|
||||
dataById.value = props.dataById;
|
||||
if (dataById.value == null) {
|
||||
statusAction.value = true;
|
||||
}
|
||||
});
|
||||
const dateNow = ref<Date>(new Date());
|
||||
const timeNoew = ref<string>("");
|
||||
|
||||
function updateClock() {
|
||||
const date = Date.now();
|
||||
let hh = moment(date).format("HH");
|
||||
let mm = moment(date).format("mm");
|
||||
timeNoew.value = `${hh}:${mm} น.`;
|
||||
}
|
||||
|
||||
function onCkickSave() {
|
||||
console.log("save");
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<!-- <q-card style="width: 350px; max-width: 80vw; higth: 100%"> -->
|
||||
<div class="row q-pa-md q-col-q-gutter-sm">
|
||||
<q-card
|
||||
flat
|
||||
bordered
|
||||
:class="$q.screen.gt.xs ? 'col-12 bg-grey-1' : 'col-12 '"
|
||||
>
|
||||
<q-card-section class="bg-primary text-white" v-if="!$q.screen.gt.xs">
|
||||
<div class="text-center">เวลาปัจจุบัน</div>
|
||||
</q-card-section>
|
||||
<div v-else class="q-pa-sm text-primary">เวลาปัจจุบัน</div>
|
||||
<q-card-section class="text-center">
|
||||
<div class="row q-gutter-md">
|
||||
<!-- <div class="col">{{ data.date }}</div>
|
||||
<div class="col">{{ data.in }} น.</div> -->
|
||||
<div class="col">{{ date2Thai(dateNow) }}</div>
|
||||
<div class="col">{{ timeNoew }}</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card flat bordered class="q-pa-sm col-12 q-mt-sm" v-if="statusAction">
|
||||
<VueDatePicker
|
||||
v-model="date"
|
||||
:locale="'th'"
|
||||
autoApply
|
||||
borderless
|
||||
:enableTimePicker="false"
|
||||
week-start="0"
|
||||
>
|
||||
<template #year="{ year }">
|
||||
{{ year + 543 }}
|
||||
</template>
|
||||
<template #year-overlay-value="{ value }">
|
||||
{{ parseInt(value + 543) }}
|
||||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
borderless
|
||||
dense
|
||||
:model-value="date !== null ? date2Thai(date) : null"
|
||||
:label="`${'กรอกวันที่'}`"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon
|
||||
name="event"
|
||||
class="cursor-pointer"
|
||||
style="color: var(--q-primary)"
|
||||
>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input> </template
|
||||
></VueDatePicker>
|
||||
</q-card>
|
||||
|
||||
<q-card flat bordered class="q-pa-sm col-12 bg-grey-1 q-mt-sm" v-else>
|
||||
<div class="row q-gutter-md text-grey-5">
|
||||
<div class="col-1">
|
||||
<q-icon color="grey-5" name="calendar_today" />
|
||||
</div>
|
||||
<div class="col">31 ต.ค. 56</div>
|
||||
</div>
|
||||
</q-card>
|
||||
<q-card flat bordered class="q-pa-sm col-12 q-mt-sm">
|
||||
<div class="row q-gutter-xs">
|
||||
<div class="col-12">
|
||||
<q-checkbox
|
||||
keep-color
|
||||
color="primary"
|
||||
v-model="checkboxIn"
|
||||
label="ขอแก้ไขเวลาเข้างาน"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<q-checkbox
|
||||
keep-color
|
||||
v-model="checkboxOut"
|
||||
color="primary"
|
||||
label="ขอแก้ไขเวลาออกงาน"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
<q-card flat bordered class="q-pa-sm col-12 q-mt-sm">
|
||||
<q-input
|
||||
borderless
|
||||
v-model="reason"
|
||||
label="เหตุผล"
|
||||
type="textarea"
|
||||
rows="1"
|
||||
label-color="grey-5"
|
||||
/>
|
||||
</q-card>
|
||||
</div>
|
||||
<q-separator />
|
||||
<q-card-actions align="right">
|
||||
<q-btn dense color="secondary" label="บันทึก" @click="onCkickSave" />
|
||||
</q-card-actions>
|
||||
<!-- </q-card> -->
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
35
src/components/HeaderPopup.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
clickClose: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
function clickClosePopup() {
|
||||
props.clickClose();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-toolbar>
|
||||
<q-toolbar-title class="text-subtitle2 text-bold">{{
|
||||
props.title
|
||||
}}</q-toolbar-title>
|
||||
<q-btn
|
||||
icon="close"
|
||||
unelevated
|
||||
round
|
||||
dense
|
||||
@click="clickClosePopup"
|
||||
style="color: #ff8080; background-color: #ffdede"
|
||||
/>
|
||||
</q-toolbar>
|
||||
<q-separator />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
94
src/components/MapCheckin.vue
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { GoogleMap, Marker } from "vue3-google-map";
|
||||
|
||||
declare var google: any;
|
||||
const center = ref<any>();
|
||||
const location = ref<string>("สำนักงาน");
|
||||
|
||||
// hook
|
||||
onMounted(() => {
|
||||
findNearestPlace();
|
||||
});
|
||||
|
||||
// หาตำแหน่ง
|
||||
function findNearestPlace() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
const userLocation = {
|
||||
lat: position.coords.latitude,
|
||||
lng: position.coords.longitude,
|
||||
};
|
||||
|
||||
// ส่ง userLocation ไปยัง API เพื่อหาสถานที่ที่ใกล้ที่สุด
|
||||
|
||||
findNearestPlaceFromAPI(userLocation);
|
||||
center.value = userLocation;
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
console.log("erroe");
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.error("เบราว์เซอร์ไม่รองรับการระบุตำแหน่ง");
|
||||
}
|
||||
}
|
||||
function findNearestPlaceFromAPI(userLocation: any) {
|
||||
const placesService = new google.maps.places.PlacesService(
|
||||
document.createElement("div")
|
||||
);
|
||||
|
||||
const request = {
|
||||
location: userLocation,
|
||||
radius: 500, // รัศมีในเมตร
|
||||
types: [
|
||||
"hospital",
|
||||
"school",
|
||||
"stadium",
|
||||
"cafe",
|
||||
"gym",
|
||||
"store",
|
||||
"accounting",
|
||||
"amusement_park",
|
||||
], // ประเภทของสถานที่ที่คุณต้องการค้นหา
|
||||
};
|
||||
|
||||
placesService.nearbySearch(request, (results: any, status: any) => {
|
||||
if (status === google.maps.places.PlacesServiceStatus.OK) {
|
||||
console.log("Nearby places:", results);
|
||||
} else {
|
||||
console.error("Error fetching nearby places:", status);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-card
|
||||
bordered
|
||||
flat
|
||||
class="col-12 bg-grey-2 shadow-0"
|
||||
:style="$q.screen.gt.xs ? 'height: 350px;' : 'height: 220px;'"
|
||||
>
|
||||
<div style="width: 100%; height: 90%">
|
||||
<GoogleMap
|
||||
api-key="AIzaSyBzPSF5NxUZ1G8DKBnJvJPTqCR0Ct2xf58"
|
||||
style="width: 100%; height: 100%"
|
||||
:center="center"
|
||||
:zoom="15"
|
||||
>
|
||||
<Marker :options="{ position: center }" />
|
||||
</GoogleMap>
|
||||
</div>
|
||||
|
||||
<div class="q-pa-md text-weight-medium text-grey-8">
|
||||
พื้นที่ใกล้เคียง
|
||||
<span class="q-px-sm">:</span>
|
||||
{{ location }}
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
52
src/components/PopUp.vue
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, defineProps, watch } from "vue";
|
||||
import HeaderPopup from "@/components/HeaderPopup.vue";
|
||||
import FormTime from "@/components/FormTime.vue";
|
||||
|
||||
const props = defineProps({
|
||||
modal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
clickClose: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
dataById: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
// -ข้อมูล
|
||||
const data = ref<any>();
|
||||
function clickClosePopup() {
|
||||
props.clickClose();
|
||||
}
|
||||
watch(props, () => {
|
||||
if (props.modal === true) {
|
||||
data.value = props.dataById;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<!-- <q-dialog v-model="props.modal" full-height>
|
||||
<q-card class="column full-height" style="width: 300px">
|
||||
<HeaderPopup :title="props.title" :clickClose="clickClosePopup" />
|
||||
<FormTime :dataById="data" />
|
||||
</q-card>
|
||||
</q-dialog> -->
|
||||
|
||||
<q-dialog v-model="props.modal" full-height>
|
||||
<q-card class="column full-height" style="width: 350px">
|
||||
<HeaderPopup :title="props.title" :clickClose="clickClosePopup" />
|
||||
<FormTime :dataById="data" />
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
267
src/components/TableHistory.vue
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
<script setup lang="ts">
|
||||
import type { QTableProps } from "quasar";
|
||||
import { ref } from "vue";
|
||||
|
||||
// importStores
|
||||
import { useChekIn } from "@/stores/chekin";
|
||||
|
||||
// importComponent
|
||||
import Popup from "@/components/PopUp.vue";
|
||||
|
||||
const stores = useChekIn();
|
||||
|
||||
const columns = ref<QTableProps["columns"]>([
|
||||
{
|
||||
name: "date",
|
||||
align: "left",
|
||||
label: "วัน/เดือน/ปี",
|
||||
sortable: true,
|
||||
field: "date",
|
||||
headerStyle: "font-size: 14px",
|
||||
style: "font-size: 14px; width:15%;",
|
||||
},
|
||||
{
|
||||
name: "in",
|
||||
align: "left",
|
||||
label: "เวลาเข้างาน",
|
||||
sortable: true,
|
||||
field: "in",
|
||||
headerStyle: "font-size: 14px",
|
||||
style: "font-size: 14px; width:15%;",
|
||||
},
|
||||
{
|
||||
name: "out",
|
||||
align: "left",
|
||||
label: "เวลาออกงาน",
|
||||
sortable: true,
|
||||
field: "out",
|
||||
headerStyle: "font-size: 14px",
|
||||
style: "font-size: 14px; width:15%;",
|
||||
},
|
||||
{
|
||||
name: "Morningstatus",
|
||||
align: "left",
|
||||
label: "สถานะช่วงเช้า",
|
||||
sortable: true,
|
||||
field: "Morningstatus",
|
||||
headerStyle: "font-size: 14px",
|
||||
style: "font-size: 14px; width:15%;",
|
||||
},
|
||||
{
|
||||
name: "AfternoonStatus",
|
||||
align: "left",
|
||||
label: "สถานะช่วงบ่าย",
|
||||
sortable: true,
|
||||
field: "AfternoonStatus",
|
||||
headerStyle: "font-size: 14px",
|
||||
style: "font-size: 14px; width:15%;",
|
||||
},
|
||||
// {
|
||||
// name: "loIn",
|
||||
// align: "left",
|
||||
// label: "พิกัด",
|
||||
// sortable: true,
|
||||
// field: "loIn",
|
||||
// headerStyle: "font-size: 14px",
|
||||
// style: "font-size: 14px",
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: "loOut",
|
||||
// align: "left",
|
||||
// label: "พิกัด",
|
||||
// sortable: true,
|
||||
// field: "loOut",
|
||||
// headerStyle: "font-size: 14px",
|
||||
// style: "font-size: 14px",
|
||||
// },
|
||||
// {
|
||||
// name: "status",
|
||||
// align: "left",
|
||||
// label: "สถานะ",
|
||||
// sortable: true,
|
||||
// field: "status",
|
||||
// headerStyle: "font-size: 14px",
|
||||
// style: "font-size: 14px; width:10%;",
|
||||
// },
|
||||
]);
|
||||
// popup
|
||||
const modalPopup = ref<boolean>(false);
|
||||
const titlePopup = ref<string>("");
|
||||
const dataRow = ref<any>();
|
||||
function openPopup(data: any) {
|
||||
const title = "แก้ไขลงเวลา";
|
||||
modalPopup.value = true;
|
||||
titlePopup.value = title;
|
||||
dataRow.value = data;
|
||||
}
|
||||
function closePopup() {
|
||||
modalPopup.value = false;
|
||||
}
|
||||
function classStatus(status: string) {
|
||||
switch (status) {
|
||||
case "ขาดราชการ":
|
||||
return "text-red";
|
||||
case "ปกติ":
|
||||
return "text-blue";
|
||||
case "สาย":
|
||||
return "text-yellow-8";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-table
|
||||
flat
|
||||
bordered
|
||||
class="custom-table2"
|
||||
:columns="columns"
|
||||
:rows="stores.rows"
|
||||
virtual-scroll
|
||||
:virtual-scroll-sticky-size-start="48"
|
||||
:style="$q.screen.gt.xs ? 'max-height: 64vh' : ''"
|
||||
:grid="$q.screen.gt.xs ? false : true"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span class="text-weight-medium">{{ col.label }}</span>
|
||||
</q-th>
|
||||
<q-th auto-width> </q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props" class="cursor-pointer">
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<div
|
||||
v-if="col.name === 'Morningstatus'"
|
||||
:class="classStatus(props.row.Morningstatus)"
|
||||
>
|
||||
{{ props.row.Morningstatus }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="col.name === 'AfternoonStatus'"
|
||||
:class="classStatus(props.row.AfternoonStatus)"
|
||||
>
|
||||
{{ props.row.AfternoonStatus }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ col.value }}
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-btn
|
||||
outline
|
||||
rounded
|
||||
icon="edit"
|
||||
label="ขอแก้ไข"
|
||||
color="info"
|
||||
@click="openPopup(props.row)"
|
||||
/>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template #item="props">
|
||||
<div
|
||||
class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3 grid-style-transition"
|
||||
>
|
||||
<q-card bordered flat class="q-py-sm shadow-0">
|
||||
<q-list dense>
|
||||
<q-item v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<q-item-section>
|
||||
<q-item-label caption>{{ col.label }}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-item-label>
|
||||
<span
|
||||
v-if="col.name == 'Morningstatus'"
|
||||
:class="classStatus(props.row.Morningstatus)"
|
||||
>{{ col.value }}</span
|
||||
>
|
||||
<span
|
||||
v-else-if="col.name == 'AfternoonStatus'"
|
||||
:class="classStatus(props.row.AfternoonStatus)"
|
||||
>{{ col.value }}</span
|
||||
>
|
||||
<span v-else class="text-black">{{ col.value }} </span>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-separator />
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-card-section class="q-card-section-last">
|
||||
<q-btn
|
||||
outline
|
||||
rounded
|
||||
icon="edit"
|
||||
label="ขอแก้ไข"
|
||||
color="info"
|
||||
size="sm"
|
||||
@click="openPopup(props.row)"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<Popup
|
||||
:modal="modalPopup"
|
||||
:clickClose="closePopup"
|
||||
:title="titlePopup"
|
||||
:dataById="dataRow"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon-color {
|
||||
color: #4154b3;
|
||||
}
|
||||
|
||||
.custom-table2 {
|
||||
.q-table tr:nth-child(odd) td {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.q-table tr:nth-child(even) td {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.q-table thead tr {
|
||||
background: #ecebeb;
|
||||
}
|
||||
|
||||
.q-table thead tr th {
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.q-table td:nth-of-type(2) {
|
||||
z-index: 3 !important;
|
||||
}
|
||||
|
||||
.q-table th:nth-of-type(2),
|
||||
.q-table td:nth-of-type(2) {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* this will be the loading indicator */
|
||||
.q-table thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
.q-table thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
.text-caption {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.q-card-section-last {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
49
src/components/ToolBar.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import type { DataOption } from "@/interface/index/Main";
|
||||
import Popup from "@/components/PopUp.vue";
|
||||
// import HeaderPopup from "@/components/HeaderPopup.vue";
|
||||
// import FormTime from "@/components/FormTime.vue";
|
||||
|
||||
const filterYear = ref<string>("");
|
||||
const yearOption = ref<DataOption[]>([{ id: "2566", name: "2566" }]);
|
||||
const titleName = ref<string>("เพิ่มรายการลงเวลากรณีพิเศษ");
|
||||
|
||||
const modalPopup = ref<boolean>(false);
|
||||
function onClickopen() {
|
||||
modalPopup.value = true;
|
||||
}
|
||||
function onClickClose() {
|
||||
modalPopup.value = false;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="q-pb-sm row">
|
||||
<div class="items-center col-12 row q-gutter-sm">
|
||||
<q-select
|
||||
dense
|
||||
outlined
|
||||
style="width: 180px"
|
||||
label="ปีประวัติลงเวลา"
|
||||
v-model="filterYear"
|
||||
:options="yearOption"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
<q-space />
|
||||
<q-btn
|
||||
unelevated
|
||||
outline
|
||||
icon="add"
|
||||
color="secondary"
|
||||
:class="$q.screen.gt.xs ? 'q-px-sm' : ''"
|
||||
:label="$q.screen.gt.xs ? 'เพิ่มรายการลงเวลากรณีพิเศษ' : ''"
|
||||
@click="onClickopen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Popup :modal="modalPopup" :title="titleName" :clickClose="onClickClose" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
5
src/interface/index/Main.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
interface DataOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
export type { DataOption };
|
||||
7
src/interface/response/checkin.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface FormRef {
|
||||
model: object | null;
|
||||
useLocation: object | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type { FormRef };
|
||||
20
src/main.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import router from "./router";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
import { Quasar } from "quasar";
|
||||
import quasarUserOptions from "./quasar-user-options";
|
||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||
import "@vuepic/vue-datepicker/dist/main.css";
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
|
||||
app.use(router);
|
||||
app.use(Quasar, quasarUserOptions);
|
||||
app.use(pinia);
|
||||
app.component("VueDatePicker", VueDatePicker);
|
||||
|
||||
app.mount("#app");
|
||||
9
src/quasar-user-options.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import './styles/quasar.sass'
|
||||
// import "quasar/dist/quasar.css";
|
||||
import "@quasar/extras/material-icons/material-icons.css";
|
||||
|
||||
// To be used on app.use(Quasar, { ... })
|
||||
export default {
|
||||
config: {},
|
||||
plugins: {},
|
||||
};
|
||||
32
src/registerServiceWorker.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from 'register-service-worker'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
32
src/router/index.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import HistoryView from "@/views/HistoryView.vue";
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "about",
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/history",
|
||||
name: "history",
|
||||
component: HistoryView,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
6
src/shims-vue.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
40
src/stores/chekin.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useChekIn = defineStore("checkin", () => {
|
||||
const rows = ref<any>();
|
||||
|
||||
function fetchHistoryList(data: any) {
|
||||
console.log(data);
|
||||
const datalist = data.map((e: any) => ({
|
||||
no: e.no,
|
||||
date: e.date,
|
||||
in: e.in,
|
||||
loIn: e.loIn,
|
||||
out: e.out,
|
||||
loOut: e.loOut,
|
||||
status: e.status,
|
||||
Morningstatus: convertStatus(e.Morningstatus),
|
||||
AfternoonStatus: convertStatus(e.AfternoonStatus),
|
||||
}));
|
||||
rows.value = datalist;
|
||||
}
|
||||
|
||||
function convertStatus(status: string) {
|
||||
switch (status) {
|
||||
case "1":
|
||||
return "ขาดราชการ";
|
||||
case "2":
|
||||
return "ปกติ";
|
||||
case "3":
|
||||
return "สาย";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rows,
|
||||
fetchHistoryList,
|
||||
};
|
||||
});
|
||||
75
src/stores/mixin.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
date2Thai,
|
||||
};
|
||||
});
|
||||
3
src/styles/quasar.sass
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@import './quasar.variables.sass'
|
||||
@import '~quasar-styl'
|
||||
// @import '~quasar-addon-styl'
|
||||
139
src/styles/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
|
||||
15
src/views/AboutView.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-btn color="white" text-color="black" label="Standard" />
|
||||
<q-btn color="primary" label="Primary" />
|
||||
<q-btn color="secondary" label="Secondary" />
|
||||
<q-btn color="amber" glossy label="Amber" />
|
||||
<q-btn color="brown-5" label="Brown 5" />
|
||||
<q-btn color="deep-orange" glossy label="Deep Orange" />
|
||||
<q-btn color="purple" label="Purple" />
|
||||
<q-btn color="black" label="Black" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
177
src/views/HistoryView.vue
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<script setup lang="ts">
|
||||
import type { QTableProps } from "quasar";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import Table from "@/components/TableHistory.vue";
|
||||
import ToolBar from "@/components/ToolBar.vue";
|
||||
|
||||
// importStores
|
||||
import { useChekIn } from "@/stores/chekin";
|
||||
|
||||
const router = useRouter();
|
||||
const stores = useChekIn();
|
||||
|
||||
onMounted(() => {
|
||||
fetchlist();
|
||||
});
|
||||
|
||||
function fetchlist() {
|
||||
const listData = [
|
||||
{
|
||||
no: "1",
|
||||
date: "13/08/66",
|
||||
in: "11:20",
|
||||
loIn: "สำนักงาน ก.ก ",
|
||||
out: "",
|
||||
loOut: "",
|
||||
status: "",
|
||||
Morningstatus: "1",
|
||||
AfternoonStatus: "1",
|
||||
},
|
||||
{
|
||||
no: "2",
|
||||
date: "12/08/66",
|
||||
in: "08:04",
|
||||
loIn: "สำนักงาน ก.ก ",
|
||||
out: "17:01",
|
||||
loOut: "สำนักงาน ก.ก ",
|
||||
status: "ลงเวลาเรียบร้อย",
|
||||
Morningstatus: "2",
|
||||
AfternoonStatus: "2",
|
||||
},
|
||||
{
|
||||
no: "3",
|
||||
date: "11/08/66",
|
||||
in: "08:34",
|
||||
loIn: "สำนักงาน ก.ก ",
|
||||
out: "17:36",
|
||||
loOut: "สำนักงาน ก.ก ",
|
||||
status: "สาย ทำงานครบ",
|
||||
Morningstatus: "3",
|
||||
AfternoonStatus: "2",
|
||||
},
|
||||
{
|
||||
no: "4",
|
||||
date: "10/08/66",
|
||||
in: "08:48",
|
||||
loIn: "สำนักงาน ก.ก ",
|
||||
out: "17:00",
|
||||
loOut: "สำนักงาน ก.ก ",
|
||||
status: "สาย ทำงานไม่ครบ",
|
||||
Morningstatus: "3",
|
||||
AfternoonStatus: "3",
|
||||
},
|
||||
];
|
||||
stores.fetchHistoryList(listData);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="col-12 row justify-center">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<q-card flat class="row col-12 cardNone">
|
||||
<div
|
||||
class="bg-secondary text-white col-12 row items-center q-px-md q-py-sm"
|
||||
>
|
||||
<div class="col-2">
|
||||
<q-btn
|
||||
icon="arrow_backt"
|
||||
unelevated
|
||||
round
|
||||
dense
|
||||
flat
|
||||
color="white"
|
||||
@click="router.go(-1)"
|
||||
/>
|
||||
</div>
|
||||
<q-space />
|
||||
<span class="text-body1 text-weight-bold col-8 text-center"
|
||||
>ประวัติการลงเวลา</span
|
||||
>
|
||||
<div class="col-2"></div>
|
||||
</div>
|
||||
<div class="col-12 q-pa-md text-grey-9">
|
||||
<ToolBar />
|
||||
<Table />
|
||||
<!-- <Table
|
||||
:style="$q.screen.gt.xs ? 'max-height: 64vh' : ''"
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
:filter="filter"
|
||||
:visible-columns="visibleColumns"
|
||||
v-model:inputfilter="filter"
|
||||
v-model:inputvisible="visibleColumns"
|
||||
:pagination="initialPagination"
|
||||
:inputShow="false"
|
||||
:grid="$q.screen.gt.xs ? false : true"
|
||||
>
|
||||
<template #columns="props">
|
||||
<q-tr :props="props">
|
||||
<q-td key="no" :props="props">
|
||||
{{ props.rowIndex + 1 }}
|
||||
</q-td>
|
||||
<q-td key="date" :props="props">
|
||||
{{ props.row.date }}
|
||||
</q-td>
|
||||
<q-td key="in" :props="props">
|
||||
{{ props.row.in }}
|
||||
</q-td>
|
||||
<q-td key="loIn" :props="props">
|
||||
{{ props.row.loIn }}
|
||||
</q-td>
|
||||
<q-td key="out" :props="props">
|
||||
{{ props.row.out }}
|
||||
</q-td>
|
||||
<q-td key="loOut" :props="props">
|
||||
{{ props.row.loOut }}
|
||||
</q-td>
|
||||
<q-td key="status" :props="props">
|
||||
<span
|
||||
:class="
|
||||
props.row.status == 'ลงเวลาเรียบร้อย'
|
||||
? 'text-blue'
|
||||
: 'text-orange'
|
||||
"
|
||||
>{{ props.row.status }}</span
|
||||
>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template #item="props">
|
||||
<div
|
||||
class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3 grid-style-transition"
|
||||
>
|
||||
<q-card bordered flat class="q-py-sm shadow-0">
|
||||
<q-list dense>
|
||||
<q-item
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label caption>{{ col.label }}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-item-label>
|
||||
<span
|
||||
v-if="col.name == 'status'"
|
||||
:class="
|
||||
props.row.status == 'ลงเวลาเรียบร้อย'
|
||||
? 'text-blue'
|
||||
: 'text-orange'
|
||||
"
|
||||
>{{ col.value }}</span
|
||||
>
|
||||
<span v-else class="text-black">{{ col.value }}</span>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
</Table> -->
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
494
src/views/HomeView.vue
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import moment, { Moment } from "moment";
|
||||
|
||||
// import Type
|
||||
import type { FormRef } from "@/interface/response/checkin";
|
||||
// import components
|
||||
import MapCheck from "@/components/MapCheckin.vue";
|
||||
// import Stores
|
||||
import { useCounterMixin } from "@/stores/mixin";
|
||||
|
||||
const mixin = useCounterMixin();
|
||||
const { date2Thai } = mixin;
|
||||
const router = useRouter();
|
||||
|
||||
const stetusCheckin = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
updateClock();
|
||||
});
|
||||
|
||||
//time
|
||||
const dateNow = ref<Date>(new Date());
|
||||
const Thai = ref<Date>(dateNow.value);
|
||||
const formattedS = ref();
|
||||
const formattedM = ref();
|
||||
const formattedH = ref();
|
||||
|
||||
function updateClock() {
|
||||
const date = Date.now();
|
||||
let hh = moment(date).format("HH");
|
||||
let mm = moment(date).format("mm");
|
||||
let ss = moment(date).format("ss");
|
||||
formattedS.value = ss;
|
||||
formattedM.value = mm;
|
||||
formattedH.value = hh;
|
||||
}
|
||||
setInterval(updateClock, 1000);
|
||||
|
||||
//location
|
||||
const location = ref<string>("สำนักงาน ก.ก");
|
||||
const coordinates = ref<string>("13° 43’ 45” N 100° 31’ 26” E");
|
||||
const workplace = ref<string>("in-place");
|
||||
const useLocation = ref<string | null>("");
|
||||
const model = ref<string | null>("");
|
||||
const options = ref<string[]>([
|
||||
"ปฏิบัติงานที่บ้าน",
|
||||
"ลืมลงเวลาปฏิบัติงาน",
|
||||
"ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่",
|
||||
"ขออนุญาตออกนอกสถานที่",
|
||||
"อื่นๆ",
|
||||
]);
|
||||
function selectLocation() {
|
||||
if (model.value === "อื่นๆ") {
|
||||
useLocation.value = "";
|
||||
} else {
|
||||
useLocation.value = model.value;
|
||||
}
|
||||
}
|
||||
//camera
|
||||
const camera = ref(false);
|
||||
const hasPhoto = ref<boolean>(true);
|
||||
const mediaStream = ref<MediaStream | null>(null);
|
||||
const video = ref<HTMLVideoElement | null>(null);
|
||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||
const img = ref<any>(null);
|
||||
|
||||
const openCamera = () => {
|
||||
camera.value = true;
|
||||
camera.value && setupCamera();
|
||||
};
|
||||
const setupCamera = async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
if (video.value) {
|
||||
video.value.srcObject = stream;
|
||||
}
|
||||
mediaStream.value = stream;
|
||||
} catch (error) {
|
||||
console.error("Error accessing camera:", error);
|
||||
}
|
||||
};
|
||||
function capturePhoto() {
|
||||
const videoElement = video.value;
|
||||
const canvasElement = canvas.value;
|
||||
if (!videoElement || !canvasElement) {
|
||||
console.error("Video or Canvas element not available");
|
||||
return;
|
||||
}
|
||||
const context = canvasElement.getContext("2d");
|
||||
if (!context) {
|
||||
console.error("Canvas context not available");
|
||||
return;
|
||||
}
|
||||
const desiredWidth = 150;
|
||||
const desiredHeight = 200;
|
||||
const zoomFactor = 10;
|
||||
|
||||
const videoAspectRatio = videoElement.videoWidth / videoElement.videoHeight;
|
||||
const canvasAspectRatio = desiredWidth / desiredHeight;
|
||||
|
||||
let drawWidth, drawHeight;
|
||||
|
||||
if (videoAspectRatio > canvasAspectRatio) {
|
||||
drawWidth = desiredWidth * zoomFactor;
|
||||
drawHeight = (desiredWidth * zoomFactor) / videoAspectRatio;
|
||||
} else {
|
||||
drawWidth = desiredHeight * zoomFactor * videoAspectRatio;
|
||||
drawHeight = desiredHeight * zoomFactor;
|
||||
}
|
||||
|
||||
canvasElement.width = drawWidth;
|
||||
canvasElement.height = drawHeight;
|
||||
if (context) {
|
||||
context.imageSmoothingEnabled = true;
|
||||
context.imageSmoothingQuality = "low";
|
||||
}
|
||||
|
||||
// context.drawImage(
|
||||
// videoElement,
|
||||
// 0,
|
||||
// 0,
|
||||
// canvasElement.width,
|
||||
// canvasElement.height
|
||||
// );
|
||||
context.drawImage(
|
||||
videoElement,
|
||||
0,
|
||||
0,
|
||||
videoElement.videoWidth,
|
||||
videoElement.videoHeight,
|
||||
0,
|
||||
0,
|
||||
drawWidth,
|
||||
drawHeight
|
||||
);
|
||||
|
||||
//ไฟล์รูป
|
||||
const dataURL = canvasElement.toDataURL(".image/.png");
|
||||
img.value = dataURL;
|
||||
console.log(img.value);
|
||||
|
||||
if (mediaStream.value) {
|
||||
mediaStream.value.getTracks().forEach((track) => track.stop());
|
||||
videoElement.srcObject = null;
|
||||
hasPhoto.value = false;
|
||||
}
|
||||
}
|
||||
function refreshPhoto() {
|
||||
hasPhoto.value = true;
|
||||
img.value = "";
|
||||
}
|
||||
// validate
|
||||
const useLocationRef = ref<object | null>(null);
|
||||
const modelRef = ref<object | null>(null);
|
||||
const objectRef: FormRef = {
|
||||
model: modelRef,
|
||||
useLocation: useLocationRef,
|
||||
};
|
||||
function validateForm() {
|
||||
const hasError = [];
|
||||
for (const key in objectRef) {
|
||||
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
||||
const property = objectRef[key];
|
||||
if (property.value && typeof property.value.validate === "function") {
|
||||
const isValid = property.value.validate();
|
||||
hasError.push(isValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasError.every((result) => result === true)) {
|
||||
confirm();
|
||||
} else {
|
||||
console.log("ไม่ผ่าน ");
|
||||
}
|
||||
}
|
||||
// ยืนยันการลงเวลา
|
||||
const dialogTime = ref<boolean>(false);
|
||||
const confirm = () => {
|
||||
dialogTime.value = true;
|
||||
};
|
||||
|
||||
// class
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
"bg-primary text-white col-12 row items-center q-px-md q-py-sm": val,
|
||||
"bg-red-9 text-white col-12 row items-center q-px-md q-py-sm": !val,
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-12 row justify-center">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<q-card flat class="row col-12 cardNone">
|
||||
<div :class="getClass(stetusCheckin)">
|
||||
<div class="col-2">
|
||||
<!-- <q-btn
|
||||
icon="mdi-arrow-left"
|
||||
unelevated
|
||||
round
|
||||
dense
|
||||
flat
|
||||
color="white"
|
||||
@click="router.go(-1)"
|
||||
/> -->
|
||||
</div>
|
||||
<span class="text-body1 text-weight-bold col-8 text-center">
|
||||
<span v-if="stetusCheckin">ลงเวลาเข้างาน</span>
|
||||
<span v-else>ลงเวลาออกงาน</span>
|
||||
</span>
|
||||
<div class="col-2 text-right">
|
||||
<q-btn
|
||||
icon="history"
|
||||
unelevated
|
||||
rounded
|
||||
dense
|
||||
flat
|
||||
color="white"
|
||||
:label="$q.screen.gt.xs ? 'ประวัติการลงเวลา' : ''"
|
||||
:class="$q.screen.gt.xs ? 'q-px-md' : ''"
|
||||
@click="router.push('/history')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 q-pa-md text-grey-9">
|
||||
<div class="col-12 row justify-center">
|
||||
<div class="col-12 row q-py-sm justify-center">
|
||||
<div
|
||||
class="col-xs-12 col-sm-10 text-h6 text-center text-weight-bold"
|
||||
>
|
||||
{{ date2Thai(Thai) }}
|
||||
</div>
|
||||
<div class="row col-12 justify-center q-py-sm">
|
||||
<div class="colunm">
|
||||
<div class="text-h3 text-weight-bold">
|
||||
{{ formattedH }}<span class="q-ma-md">:</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colunm">
|
||||
<div class="text-h3 text-weight-bold">
|
||||
{{ formattedM }}<span class="q-ma-md">:</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colunm">
|
||||
<div class="text-h3 text-weight-bold">{{ formattedS }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-11 row q-col-gutter-md">
|
||||
<div class="col-12 col-sm-8">
|
||||
<MapCheck />
|
||||
<!-- <q-card
|
||||
bordered
|
||||
flat
|
||||
class="col-12 bg-grey-2 shadow-0"
|
||||
:style="$q.screen.gt.xs ? 'height: 350px;' : 'height: 220px;'"
|
||||
>
|
||||
<MapCheck />
|
||||
<q-img
|
||||
src="@/assets/map1.png"
|
||||
:style="
|
||||
$q.screen.gt.xs ? 'height: 300px;' : 'height: 168px;'
|
||||
"
|
||||
></q-img>
|
||||
<div class="q-pa-md text-weight-medium text-grey-8">
|
||||
พื้นที่ใกล้เคียง
|
||||
<span class="q-px-sm">:</span>
|
||||
{{ location }}
|
||||
</div>
|
||||
</q-card> -->
|
||||
</div>
|
||||
<div class="col-12 col-sm-4">
|
||||
<q-card
|
||||
flat
|
||||
bordered
|
||||
class="card-container"
|
||||
@click="openCamera()"
|
||||
>
|
||||
<div v-if="!camera" class="preview-placeholder">
|
||||
<div class="text-center">
|
||||
<q-icon
|
||||
name="photo_camera"
|
||||
color="blue-grey-3"
|
||||
size="100px"
|
||||
class="center-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="hasPhoto" class="video-container">
|
||||
<video ref="video" autoplay class="video-element"></video>
|
||||
<canvas ref="canvas" class="canvas-element"></canvas>
|
||||
</div>
|
||||
<div v-else class="image-container">
|
||||
<q-img :src="img" class="image-element"></q-img>
|
||||
<canvas ref="canvas" class="canvas-element"></canvas>
|
||||
</div>
|
||||
<div class="absolute-bottom-right q-ma-md">
|
||||
<q-btn
|
||||
v-if="hasPhoto"
|
||||
round
|
||||
push
|
||||
icon="photo_camera"
|
||||
size="md"
|
||||
color="positive"
|
||||
@click="capturePhoto"
|
||||
/>
|
||||
<q-btn
|
||||
v-else
|
||||
round
|
||||
push
|
||||
icon="refresh"
|
||||
size="md"
|
||||
color="negative"
|
||||
@click="refreshPhoto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
<div class="col-12 q-mb-md">
|
||||
<q-card
|
||||
bordered
|
||||
flat
|
||||
:class="
|
||||
$q.screen.gt.xs
|
||||
? 'q-px-md q-py-sm row items-center shadow-0'
|
||||
: 'q-pa-md row items-center shadow-0'
|
||||
"
|
||||
>
|
||||
<div class="text-weight-bold">สถานที่ทำงาน</div>
|
||||
<div
|
||||
:class="
|
||||
$q.screen.gt.xs
|
||||
? 'row q-gutter-md q-pl-md col-sm-6 col-md-3'
|
||||
: 'column col-12'
|
||||
"
|
||||
>
|
||||
<q-radio
|
||||
v-model="workplace"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="in-place"
|
||||
label="ในสถานที่"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="workplace"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="off-site"
|
||||
label="นอกสถานที่"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col-xs-12 col-sm-6 col-md-4"
|
||||
v-if="workplace == 'off-site'"
|
||||
>
|
||||
<q-select
|
||||
ref="modelRef"
|
||||
dense
|
||||
class="q-ml-md"
|
||||
outlined
|
||||
v-model="model"
|
||||
:options="options"
|
||||
prefix="ระบุสถานที่ :"
|
||||
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
|
||||
lazy-rules
|
||||
@update:model-value="selectLocation()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col-xs-12 col-sm-6 col-md-4"
|
||||
v-if="model == 'อื่นๆ'"
|
||||
>
|
||||
<q-input
|
||||
ref="useLocationRef"
|
||||
dense
|
||||
class="q-ml-md"
|
||||
outlined
|
||||
v-model="useLocation"
|
||||
label="ระบุสถานที่"
|
||||
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
|
||||
lazy-rules
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-right" v-if="camera && img">
|
||||
<div class="col-12">
|
||||
<q-separator />
|
||||
</div>
|
||||
<div class="col-12 q-pa-md">
|
||||
<p class="text-red text-caption">
|
||||
*หมายเหตุ คลิกลงเวลาเข้างานแล้วระบบจะลงเวลาทันที
|
||||
</p>
|
||||
<q-btn
|
||||
:label="stetusCheckin == true ? 'ลงเวลาเข้างาน' : 'ลงเวลาออกงาน'"
|
||||
:color="stetusCheckin == true ? 'primary' : 'red-9'"
|
||||
push
|
||||
size="14px"
|
||||
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width'"
|
||||
@click="validateForm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="dialogTime">
|
||||
<q-card class="full-width cardNone">
|
||||
<div :class="getClass(stetusCheckin)">
|
||||
<div class="text-body1 text-center col-12 text-weight-bold">
|
||||
<span v-if="stetusCheckin">ลงเวลาเข้างานของคุณ</span>
|
||||
<span v-else>ลงเวลาออกงานของคุณ</span>
|
||||
</div>
|
||||
</div>
|
||||
<q-card-section class="row col-12 justify-center">
|
||||
<div class="bg-grey-2 rounded-borders q-pa-md col-11">
|
||||
<div class="col-12 text-subtitle1 text-center text-weight-medium">
|
||||
{{ Thai }}
|
||||
</div>
|
||||
<div class="row col-12 justify-center q-pt-sm">
|
||||
<div class="text-h3 text-weight-bold">
|
||||
{{ formattedH }}<span class="q-ma-md">:</span>
|
||||
</div>
|
||||
<div class="text-h3 text-weight-bold">{{ formattedM }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-center row q-pt-md">
|
||||
<div class="col-12 text-subtitle1 text-weight-medium text-secondary">
|
||||
{{ location }}
|
||||
</div>
|
||||
<div class="col-12 text-grey-7">{{ coordinates }}</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="center" class="q-mb-md row">
|
||||
<q-btn
|
||||
class="col-xs-11 col-sm-6"
|
||||
push
|
||||
label="ตกลง"
|
||||
color="secondary"
|
||||
v-close-popup
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
<style scoped>
|
||||
.q-card.cardImg:hover {
|
||||
border: 1px solid #02a998 !important;
|
||||
}
|
||||
|
||||
.center-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.card-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 350px; /* Adjust as needed */
|
||||
background: #f6f5f5;
|
||||
}
|
||||
|
||||
.video-container,
|
||||
.image-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-element,
|
||||
.image-element {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 5px; /* Adjust as needed */
|
||||
}
|
||||
|
||||
.canvas-element {
|
||||
display: none; /* Adjust as needed */
|
||||
}
|
||||
</style>
|
||||
29
tsconfig.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useDefineForClassFields": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["webpack-env"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
13
vue.config.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: [
|
||||
'quasar'
|
||||
],
|
||||
|
||||
pluginOptions: {
|
||||
quasar: {
|
||||
importStrategy: 'kebab',
|
||||
rtlSupport: false
|
||||
}
|
||||
}
|
||||
})
|
||||