- run by vite
- change camera
17
.eslintrc.js
|
|
@ -1,20 +1,19 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
node: true
|
node: true,
|
||||||
|
es2022: true,
|
||||||
},
|
},
|
||||||
'extends': [
|
extends: [
|
||||||
'plugin:vue/vue3-essential',
|
'plugin:vue/vue3-essential',
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'@vue/typescript/recommended'
|
'@vue/typescript/recommended',
|
||||||
|
|
||||||
],
|
],
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2020
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
// 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
// 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
// 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
// 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||||
'vue/no-mutating-props': 'off'
|
'vue/no-mutating-props': 'off',
|
||||||
}
|
// '@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'vue/multi-word-component-names': 'off'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
.prettierrc.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
12
README.md
|
|
@ -1,24 +1,30 @@
|
||||||
# maptest
|
# BMA ระบบลงเวลาเข้า-ออกงาน
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
### Compiles and hot-reloads for development
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and minifies for production
|
### Compiles and minifies for production
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run build
|
npm run build
|
||||||
|
npm run preview
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lints and fixes files
|
### Format and fixes files
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run lint
|
npm run format
|
||||||
```
|
```
|
||||||
|
|
||||||
### Customize configuration
|
### Customize configuration
|
||||||
|
|
||||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
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',
|
||||||
|
},
|
||||||
|
})
|
||||||
1
env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
13
index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!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>BMA ระบบลงเวลาเข้า-ออกงาน</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
73
package.json
|
|
@ -3,39 +3,60 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vue-cli-service serve",
|
"dev": "vite",
|
||||||
"build": "vue-cli-service build",
|
"build": "run-p type-check build-only",
|
||||||
"lint": "vue-cli-service lint"
|
"preview": "vite preview --port 3008",
|
||||||
|
"test:unit": "vitest --environment jsdom --root src/",
|
||||||
|
"test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'",
|
||||||
|
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier . --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fullcalendar/core": "^6.1.8",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.8",
|
||||||
|
"@fullcalendar/interaction": "^6.1.8",
|
||||||
|
"@fullcalendar/list": "^6.1.8",
|
||||||
|
"@fullcalendar/timegrid": "^6.1.8",
|
||||||
|
"@fullcalendar/vue3": "^6.1.8",
|
||||||
"@googlemaps/js-api-loader": "^1.16.2",
|
"@googlemaps/js-api-loader": "^1.16.2",
|
||||||
"@quasar/cli": "^2.3.0",
|
"@quasar/extras": "^1.15.8",
|
||||||
"@quasar/extras": "^1.0.0",
|
"@vuepic/vue-datepicker": "^5.2.1",
|
||||||
"@vuepic/vue-datepicker": "^7.2.1",
|
|
||||||
"core-js": "^3.8.3",
|
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.4",
|
||||||
"quasar": "^2.0.0",
|
"quasar": "^2.11.1",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"vue": "^3.2.13",
|
"simple-vue-camera": "^1.1.3",
|
||||||
"vue-router": "^4.0.3",
|
"vite-plugin-pwa": "^0.16.7",
|
||||||
|
"vue": "^3.2.45",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
"vue3-google-map": "^0.18.0"
|
"vue3-google-map": "^0.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
"@quasar/vite-plugin": "^1.3.0",
|
||||||
"@typescript-eslint/parser": "^5.4.0",
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
"@vue/cli-plugin-babel": "~5.0.0",
|
"@types/jsdom": "^20.0.1",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
"@types/node": "^18.11.12",
|
||||||
"@vue/cli-plugin-pwa": "~5.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vue/cli-plugin-router": "~5.0.0",
|
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
"@vue/eslint-config-typescript": "^9.1.0",
|
"@vue/test-utils": "^2.2.6",
|
||||||
"eslint": "^8.53.0",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"cypress": "^12.0.2",
|
||||||
"sass": "1.32.12",
|
"eslint": "^8.22.0",
|
||||||
"sass-loader": "^12.0.0",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"typescript": "~4.5.5",
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
"vue-cli-plugin-quasar": "~5.0.2"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
public/icons/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 731 B After Width: | Height: | Size: 731 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
|
@ -1,23 +0,0 @@
|
||||||
<!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>
|
|
||||||
|
|
@ -35,44 +35,43 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineEmits, defineProps } from "vue";
|
import { useDialogPluginComponent } from 'quasar'
|
||||||
import { useDialogPluginComponent } from "quasar";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "primary",
|
default: 'primary',
|
||||||
},
|
},
|
||||||
textOk: {
|
textOk: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "ตกลง",
|
default: 'ตกลง',
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "หัวข้อ?",
|
default: 'หัวข้อ?',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "ข้อความ",
|
default: 'ข้อความ',
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "question_mark",
|
default: 'question_mark',
|
||||||
},
|
},
|
||||||
onlycancel: {
|
onlycancel: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
defineEmits([
|
defineEmits([
|
||||||
// REQUIRED; need to specify some events that your
|
// REQUIRED; need to specify some events that your
|
||||||
// component will emit through useDialogPluginComponent()
|
// component will emit through useDialogPluginComponent()
|
||||||
...useDialogPluginComponent.emits,
|
...useDialogPluginComponent.emits,
|
||||||
]);
|
])
|
||||||
|
|
||||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
||||||
useDialogPluginComponent();
|
useDialogPluginComponent()
|
||||||
// dialogRef - Vue ref to be applied to QDialog
|
// dialogRef - Vue ref to be applied to QDialog
|
||||||
// onDialogHide - Function to be used as handler for @hide on QDialog
|
// onDialogHide - Function to be used as handler for @hide on QDialog
|
||||||
// onDialogOK - Function to call to settle dialog with "ok" outcome
|
// onDialogOK - Function to call to settle dialog with "ok" outcome
|
||||||
|
|
@ -84,7 +83,7 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
||||||
function onOKClick() {
|
function onOKClick() {
|
||||||
// on OK, it is REQUIRED to
|
// on OK, it is REQUIRED to
|
||||||
// call onDialogOK (with optional payload)
|
// call onDialogOK (with optional payload)
|
||||||
onDialogOK();
|
onDialogOK()
|
||||||
// or with payload: onDialogOK({ ... })
|
// or with payload: onDialogOK({ ... })
|
||||||
// ...and it will also hide the dialog automatically
|
// ...and it will also hide the dialog automatically
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, onMounted, defineProps } from "vue";
|
import { ref, watch, onMounted } from 'vue'
|
||||||
import { useQuasar } from "quasar";
|
import { useQuasar } from 'quasar'
|
||||||
import moment, { Moment } from "moment";
|
import moment from 'moment'
|
||||||
|
|
||||||
// importStores
|
// importStores
|
||||||
import { useCounterMixin } from "@/stores/mixin";
|
import { useCounterMixin } from '@/stores/mixin'
|
||||||
// importType
|
// importType
|
||||||
import type { FormRef } from "@/interface/index/Main";
|
import type { FormRef } from '@/interface/index/Main'
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar()
|
||||||
const mixin = useCounterMixin();
|
const mixin = useCounterMixin()
|
||||||
const { date2Thai, covertDateObject, dialogConfirm } = mixin;
|
const { date2Thai, covertDateObject, dialogConfirm } = mixin
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
dataById: {
|
dataById: {
|
||||||
|
|
@ -21,53 +21,53 @@ const props = defineProps({
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const dataById = ref<any>([]);
|
const dataById = ref<any>([])
|
||||||
const date = ref<Date | null>(null);
|
const date = ref<Date | null>(null)
|
||||||
const checkboxIn = ref<boolean>(false);
|
const checkboxIn = ref<boolean>(false)
|
||||||
const checkboxOut = ref<boolean>(false);
|
const checkboxOut = ref<boolean>(false)
|
||||||
const reason = ref<string>("");
|
const reason = ref<string>('')
|
||||||
const statusAction = ref<boolean>(false);
|
const statusAction = ref<boolean>(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateClock();
|
updateClock()
|
||||||
dataById.value = props.dataById;
|
dataById.value = props.dataById
|
||||||
if (dataById.value == null) {
|
if (dataById.value == null) {
|
||||||
statusAction.value = true;
|
statusAction.value = true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
const dateNow = ref<Date>(new Date());
|
const dateNow = ref<Date>(new Date())
|
||||||
const timeNoew = ref<string>("");
|
const timeNoew = ref<string>('')
|
||||||
|
|
||||||
function updateClock() {
|
function updateClock() {
|
||||||
const date = Date.now();
|
const date = Date.now()
|
||||||
let hh = moment(date).format("HH");
|
const hh = moment(date).format('HH')
|
||||||
let mm = moment(date).format("mm");
|
const mm = moment(date).format('mm')
|
||||||
timeNoew.value = `${hh}:${mm} น.`;
|
timeNoew.value = `${hh}:${mm} น.`
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateRef = ref<object | null>(null);
|
const dateRef = ref<object | null>(null)
|
||||||
const reasonRef = ref<object | null>(null);
|
const reasonRef = ref<object | null>(null)
|
||||||
|
|
||||||
const objectRef: FormRef = {
|
const objectRef: FormRef = {
|
||||||
date: dateRef,
|
date: dateRef,
|
||||||
reason: reasonRef,
|
reason: reasonRef,
|
||||||
};
|
}
|
||||||
const checkstatusBox = ref<boolean>(false);
|
const checkstatusBox = ref<boolean>(false)
|
||||||
function onCkickSave() {
|
function onCkickSave() {
|
||||||
const hasError = [];
|
const hasError = []
|
||||||
for (const key in objectRef) {
|
for (const key in objectRef) {
|
||||||
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
||||||
const property = objectRef[key];
|
const property = objectRef[key]
|
||||||
if (property.value && typeof property.value.validate === "function") {
|
if (property.value && typeof property.value.validate === 'function') {
|
||||||
const isValid = property.value.validate();
|
const isValid = property.value.validate()
|
||||||
hasError.push(isValid);
|
hasError.push(isValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (checkboxIn.value === false && checkboxOut.value === false) {
|
if (checkboxIn.value === false && checkboxOut.value === false) {
|
||||||
checkstatusBox.value = true;
|
checkstatusBox.value = true
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
hasError.every(
|
hasError.every(
|
||||||
|
|
@ -75,11 +75,11 @@ function onCkickSave() {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
dialogConfirm($q, async () => {
|
dialogConfirm($q, async () => {
|
||||||
console.log("save");
|
console.log('save')
|
||||||
props.closePopup();
|
props.closePopup()
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log("ไม่ผ่าน ");
|
console.log('ไม่ผ่าน ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,11 +88,11 @@ watch(
|
||||||
([newCheckboxIn, newCheckboxOut]) => {
|
([newCheckboxIn, newCheckboxOut]) => {
|
||||||
if (checkstatusBox.value) {
|
if (checkstatusBox.value) {
|
||||||
if (newCheckboxIn || newCheckboxOut) {
|
if (newCheckboxIn || newCheckboxOut) {
|
||||||
checkstatusBox.value = false;
|
checkstatusBox.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-card-section class="col q-pt-none">
|
<q-card-section class="col q-pt-none">
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps } from "vue";
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: '',
|
||||||
},
|
},
|
||||||
clickClose: {
|
clickClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
function clickClosePopup() {
|
function clickClosePopup() {
|
||||||
props.clickClose();
|
props.clickClose()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from 'vue'
|
||||||
import { GoogleMap, Marker } from "vue3-google-map";
|
import { GoogleMap, Marker } from 'vue3-google-map'
|
||||||
|
|
||||||
declare var google: any;
|
declare var google: any
|
||||||
const center = ref<any>();
|
const center = ref<any>()
|
||||||
const location = ref<string>("");
|
const location = ref<string>('')
|
||||||
const test = ref();
|
const test = ref()
|
||||||
|
|
||||||
// hook
|
// hook
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
findNearestPlace();
|
findNearestPlace()
|
||||||
});
|
})
|
||||||
|
|
||||||
// หาตำแหน่ง
|
// หาตำแหน่ง
|
||||||
function findNearestPlace() {
|
function findNearestPlace() {
|
||||||
|
|
@ -20,43 +20,43 @@ function findNearestPlace() {
|
||||||
const userLocation = {
|
const userLocation = {
|
||||||
lat: position.coords.latitude,
|
lat: position.coords.latitude,
|
||||||
lng: position.coords.longitude,
|
lng: position.coords.longitude,
|
||||||
};
|
}
|
||||||
|
|
||||||
// ส่ง Location ไปยัง API เพื่อหาสถานที่ที่ใกล้ที่สุด
|
// ส่ง Location ไปยัง API เพื่อหาสถานที่ที่ใกล้ที่สุด
|
||||||
findNearestPlaceFromAPI(userLocation);
|
findNearestPlaceFromAPI(userLocation)
|
||||||
center.value = userLocation;
|
center.value = userLocation
|
||||||
console.log(center.value);
|
console.log(center.value)
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error(error);
|
console.error(error)
|
||||||
console.log("erroe");
|
console.log('erroe')
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
console.error("เบราว์เซอร์ไม่รองรับการระบุตำแหน่ง");
|
console.error('เบราว์เซอร์ไม่รองรับการระบุตำแหน่ง')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function findNearestPlaceFromAPI(userLocation: any) {
|
function findNearestPlaceFromAPI(userLocation: any) {
|
||||||
const placesService = new google.maps.places.PlacesService(
|
const placesService = new google.maps.places.PlacesService(
|
||||||
document.createElement("div")
|
document.createElement('div')
|
||||||
);
|
)
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
location: userLocation,
|
location: userLocation,
|
||||||
radius: 1000, // รัศมีในเมตร
|
radius: 1000, // รัศมีในเมตร
|
||||||
types: ["point_of_interest"], // ประเภทของสถานที่ที่คุณต้องการค้นหา
|
types: ['point_of_interest'], // ประเภทของสถานที่ที่คุณต้องการค้นหา
|
||||||
};
|
}
|
||||||
|
|
||||||
placesService.nearbySearch(request, (results: any, status: any) => {
|
placesService.nearbySearch(request, (results: any, status: any) => {
|
||||||
if (status === google.maps.places.PlacesServiceStatus.OK) {
|
if (status === google.maps.places.PlacesServiceStatus.OK) {
|
||||||
console.log("Nearby places:", results[0]);
|
console.log('Nearby places:', results[0])
|
||||||
const place = results[0];
|
const place = results[0]
|
||||||
location.value = place.name;
|
location.value = place.name
|
||||||
test.value = place.geometry.location;
|
test.value = place.geometry.location
|
||||||
} else {
|
} else {
|
||||||
console.error("Error fetching nearby places:", status);
|
console.error('Error fetching nearby places:', status)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineProps, watch } from "vue";
|
import { ref, watch } from 'vue'
|
||||||
import HeaderPopup from "@/components/HeaderPopup.vue";
|
import HeaderPopup from '@/components/HeaderPopup.vue'
|
||||||
import FormTime from "@/components/FormTime.vue";
|
import FormTime from '@/components/FormTime.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modal: {
|
modal: {
|
||||||
|
|
@ -10,7 +10,7 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: '',
|
||||||
},
|
},
|
||||||
clickClose: {
|
clickClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
@ -20,18 +20,18 @@ const props = defineProps({
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
// -ข้อมูล
|
// -ข้อมูล
|
||||||
const data = ref<any>();
|
const data = ref<any>()
|
||||||
function clickClosePopup() {
|
function clickClosePopup() {
|
||||||
props.clickClose();
|
props.clickClose()
|
||||||
}
|
}
|
||||||
watch(props, () => {
|
watch(props, () => {
|
||||||
if (props.modal === true) {
|
if (props.modal === true) {
|
||||||
data.value = props.dataById;
|
data.value = props.dataById
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-dialog v-model="props.modal" full-height>
|
<q-dialog v-model="props.modal" full-height>
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,60 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { QTableProps } from "quasar";
|
import type { QTableProps } from 'quasar'
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
|
|
||||||
// importStores
|
// importStores
|
||||||
import { useChekIn } from "@/stores/chekin";
|
import { useChekIn } from '@/stores/chekin'
|
||||||
|
|
||||||
// importComponent
|
// importComponent
|
||||||
import Popup from "@/components/PopUp.vue";
|
import Popup from '@/components/PopUp.vue'
|
||||||
|
|
||||||
const stores = useChekIn();
|
const stores = useChekIn()
|
||||||
|
|
||||||
const columns = ref<QTableProps["columns"]>([
|
const columns = ref<QTableProps['columns']>([
|
||||||
{
|
{
|
||||||
name: "date",
|
name: 'date',
|
||||||
align: "left",
|
align: 'left',
|
||||||
label: "วัน/เดือน/ปี",
|
label: 'วัน/เดือน/ปี',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: "date",
|
field: 'date',
|
||||||
headerStyle: "font-size: 14px",
|
headerStyle: 'font-size: 14px',
|
||||||
style: "font-size: 14px; width:15%;",
|
style: 'font-size: 14px; width:15%;',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "in",
|
name: 'in',
|
||||||
align: "left",
|
align: 'left',
|
||||||
label: "เวลาเข้างาน",
|
label: 'เวลาเข้างาน',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: "in",
|
field: 'in',
|
||||||
headerStyle: "font-size: 14px",
|
headerStyle: 'font-size: 14px',
|
||||||
style: "font-size: 14px; width:15%;",
|
style: 'font-size: 14px; width:15%;',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "out",
|
name: 'out',
|
||||||
align: "left",
|
align: 'left',
|
||||||
label: "เวลาออกงาน",
|
label: 'เวลาออกงาน',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: "out",
|
field: 'out',
|
||||||
headerStyle: "font-size: 14px",
|
headerStyle: 'font-size: 14px',
|
||||||
style: "font-size: 14px; width:15%;",
|
style: 'font-size: 14px; width:15%;',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Morningstatus",
|
name: 'Morningstatus',
|
||||||
align: "left",
|
align: 'left',
|
||||||
label: "สถานะช่วงเช้า",
|
label: 'สถานะช่วงเช้า',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: "Morningstatus",
|
field: 'Morningstatus',
|
||||||
headerStyle: "font-size: 14px",
|
headerStyle: 'font-size: 14px',
|
||||||
style: "font-size: 14px; width:15%;",
|
style: 'font-size: 14px; width:15%;',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AfternoonStatus",
|
name: 'AfternoonStatus',
|
||||||
align: "left",
|
align: 'left',
|
||||||
label: "สถานะช่วงบ่าย",
|
label: 'สถานะช่วงบ่าย',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
field: "AfternoonStatus",
|
field: 'AfternoonStatus',
|
||||||
headerStyle: "font-size: 14px",
|
headerStyle: 'font-size: 14px',
|
||||||
style: "font-size: 14px; width:15%;",
|
style: 'font-size: 14px; width:15%;',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: "loIn",
|
// name: "loIn",
|
||||||
|
|
@ -84,36 +84,36 @@ const columns = ref<QTableProps["columns"]>([
|
||||||
// headerStyle: "font-size: 14px",
|
// headerStyle: "font-size: 14px",
|
||||||
// style: "font-size: 14px; width:10%;",
|
// style: "font-size: 14px; width:10%;",
|
||||||
// },
|
// },
|
||||||
]);
|
])
|
||||||
// popup
|
// popup
|
||||||
const modalPopup = ref<boolean>(false);
|
const modalPopup = ref<boolean>(false)
|
||||||
const titlePopup = ref<string>("");
|
const titlePopup = ref<string>('')
|
||||||
const dataRow = ref<any>();
|
const dataRow = ref<any>()
|
||||||
function openPopup(data: any) {
|
function openPopup(data: any) {
|
||||||
const title = "แก้ไขลงเวลา";
|
const title = 'แก้ไขลงเวลา'
|
||||||
modalPopup.value = true;
|
modalPopup.value = true
|
||||||
titlePopup.value = title;
|
titlePopup.value = title
|
||||||
dataRow.value = data;
|
dataRow.value = data
|
||||||
}
|
}
|
||||||
function closePopup() {
|
function closePopup() {
|
||||||
modalPopup.value = false;
|
modalPopup.value = false
|
||||||
}
|
}
|
||||||
function classStatus(status: string) {
|
function classStatus(status: string) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "ขาดราชการ":
|
case 'ขาดราชการ':
|
||||||
return "text-red";
|
return 'text-red'
|
||||||
case "ปกติ":
|
case 'ปกติ':
|
||||||
return "text-blue";
|
return 'text-blue'
|
||||||
case "สาย":
|
case 'สาย':
|
||||||
return "text-yellow-8";
|
return 'text-yellow-8'
|
||||||
default:
|
default:
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
page: 1,
|
page: 1,
|
||||||
rowsPerPage: 10,
|
rowsPerPage: 10,
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-table
|
<q-table
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
import type { DataOption } from "@/interface/index/Main";
|
import type { DataOption } from '@/interface/index/Main'
|
||||||
import Popup from "@/components/PopUp.vue";
|
import Popup from '@/components/PopUp.vue'
|
||||||
// import HeaderPopup from "@/components/HeaderPopup.vue";
|
// import HeaderPopup from "@/components/HeaderPopup.vue";
|
||||||
// import FormTime from "@/components/FormTime.vue";
|
// import FormTime from "@/components/FormTime.vue";
|
||||||
|
|
||||||
const filterYear = ref<string>("");
|
const filterYear = ref<string>('')
|
||||||
const yearOption = ref<DataOption[]>([{ id: "2566", name: "2566" }]);
|
const yearOption = ref<DataOption[]>([{ id: '2566', name: '2566' }])
|
||||||
const titleName = ref<string>("เพิ่มรายการลงเวลากรณีพิเศษ");
|
const titleName = ref<string>('เพิ่มรายการลงเวลากรณีพิเศษ')
|
||||||
|
|
||||||
const modalPopup = ref<boolean>(false);
|
const modalPopup = ref<boolean>(false)
|
||||||
function onClickopen() {
|
function onClickopen() {
|
||||||
modalPopup.value = true;
|
modalPopup.value = true
|
||||||
}
|
}
|
||||||
function onClickClose() {
|
function onClickClose() {
|
||||||
modalPopup.value = false;
|
modalPopup.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
interface DataOption {
|
interface DataOption {
|
||||||
id: string;
|
id: string
|
||||||
name: string;
|
name: string
|
||||||
}
|
}
|
||||||
interface FormRef {
|
interface FormRef {
|
||||||
date: object | null;
|
date: object | null
|
||||||
reason: object | null;
|
reason: object | null
|
||||||
[key: string]: any;
|
[key: string]: any
|
||||||
}
|
}
|
||||||
export type { DataOption, FormRef };
|
export type { DataOption, FormRef }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
interface FormRef {
|
interface FormRef {
|
||||||
model: object | null;
|
model: object | null
|
||||||
useLocation: object | null;
|
useLocation: object | null
|
||||||
[key: string]: any;
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { FormRef };
|
export type { FormRef }
|
||||||
|
|
|
||||||
45
src/main.ts
|
|
@ -1,29 +1,36 @@
|
||||||
import { createApp } from "vue";
|
import { createApp, defineAsyncComponent } from 'vue'
|
||||||
import App from "./App.vue";
|
import App from './App.vue'
|
||||||
import "./registerServiceWorker";
|
import './registerServiceWorker'
|
||||||
import router from "./router";
|
import router from './router'
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
import { Quasar, Dialog, Notify, Loading } from "quasar";
|
import { Quasar, Dialog, Notify, Loading } from 'quasar'
|
||||||
import '@quasar/extras/material-icons/material-icons.css';
|
import '@vuepic/vue-datepicker/dist/main.css'
|
||||||
import './styles/quasar.sass';
|
import quasarUserOptions from './quasar-user-options'
|
||||||
import quasarUserOptions from "./quasar-user-options";
|
|
||||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
|
||||||
import "@vuepic/vue-datepicker/dist/main.css";
|
|
||||||
|
|
||||||
const app = createApp(App);
|
import 'quasar/src/css/index.sass'
|
||||||
const pinia = createPinia();
|
import th from "quasar/lang/th";
|
||||||
|
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
app.use(router);
|
|
||||||
app.use(Quasar, quasarUserOptions);
|
|
||||||
app.use(Quasar, {
|
app.use(Quasar, {
|
||||||
|
...quasarUserOptions,
|
||||||
plugins: {
|
plugins: {
|
||||||
Notify,
|
Notify,
|
||||||
Dialog,
|
Dialog,
|
||||||
Loading,
|
Loading,
|
||||||
},
|
},
|
||||||
});
|
lang: th,
|
||||||
app.use(pinia);
|
})
|
||||||
app.component("VueDatePicker", VueDatePicker);
|
|
||||||
|
|
||||||
app.mount("#app");
|
app.component(
|
||||||
|
'datepicker',
|
||||||
|
defineAsyncComponent(() => import('@vuepic/vue-datepicker'))
|
||||||
|
)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import "./styles/quasar.sass";
|
// import "./styles/quasar.scss"
|
||||||
// import "quasar/dist/quasar.css";
|
import '@quasar/extras/material-icons/material-icons.css'
|
||||||
import "@quasar/extras/material-icons/material-icons.css";
|
import '@quasar/extras/material-icons-outlined/material-icons-outlined.css'
|
||||||
import { Dialog, Notify } from "quasar";
|
import '@quasar/extras/fontawesome-v5/fontawesome-v5.css'
|
||||||
|
import '@quasar/extras/mdi-v4/mdi-v4.css'
|
||||||
|
|
||||||
// To be used on app.use(Quasar, { ... })
|
// To be used on app.use(Quasar, { ... })
|
||||||
export default {
|
export default {
|
||||||
config: {},
|
config: {},
|
||||||
plugins: { Dialog, Notify },
|
plugins: {},
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { register } from 'register-service-worker'
|
import { register } from 'register-service-worker'
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
register('registerSW.js', {
|
||||||
ready() {
|
ready() {
|
||||||
console.log(
|
console.log(
|
||||||
'App is being served from cache by a service worker.\n' +
|
'App is being served from cache by a service worker.\n' +
|
||||||
|
|
@ -23,10 +23,12 @@ if (process.env.NODE_ENV === 'production') {
|
||||||
console.log('New content is available; please refresh.')
|
console.log('New content is available; please refresh.')
|
||||||
},
|
},
|
||||||
offline() {
|
offline() {
|
||||||
console.log('No internet connection found. App is running in offline mode.')
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
error(error) {
|
error(error) {
|
||||||
console.error('Error during service worker registration:', error)
|
console.error('Error during service worker registration:', error)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,42 @@
|
||||||
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from "../views/HomeView.vue";
|
import HomeView from '@/views/HomeView.vue'
|
||||||
import HistoryView from "@/views/HistoryView.vue";
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: '/',
|
||||||
name: "home",
|
name: 'home',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/about",
|
path: '/about',
|
||||||
name: "about",
|
name: 'about',
|
||||||
// route level code-splitting
|
// route level code-splitting
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () =>
|
component: () => import('@/views/AboutView.vue'),
|
||||||
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/history",
|
path: '/history',
|
||||||
name: "history",
|
name: 'history',
|
||||||
component: HistoryView,
|
component: () => import('@/views/HistoryView.vue'),
|
||||||
},
|
},
|
||||||
];
|
// {
|
||||||
|
// path: '/camera',
|
||||||
|
// name: 'camera',
|
||||||
|
// component: () => import('@/views/SampleCamera.vue'),
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* 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'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
const router = createRouter({
|
export default router
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
|
||||||
routes,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
|
|
|
||||||
2
src/shims-vue.d.ts
vendored
|
|
@ -1 +1 @@
|
||||||
declare module '*.vue';
|
declare module '*.vue'
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export const useChekIn = defineStore("checkin", () => {
|
export const useChekIn = defineStore('checkin', () => {
|
||||||
const rows = ref<any>();
|
const rows = ref<any>()
|
||||||
|
|
||||||
function fetchHistoryList(data: any) {
|
function fetchHistoryList(data: any) {
|
||||||
console.log(data);
|
console.log(data)
|
||||||
const datalist = data.map((e: any) => ({
|
const datalist = data.map((e: any) => ({
|
||||||
no: e.no,
|
no: e.no,
|
||||||
date: e.date,
|
date: e.date,
|
||||||
|
|
@ -18,38 +18,38 @@ export const useChekIn = defineStore("checkin", () => {
|
||||||
AfternoonStatus: convertStatus(e.AfternoonStatus),
|
AfternoonStatus: convertStatus(e.AfternoonStatus),
|
||||||
statusEdit: e.statusEdit,
|
statusEdit: e.statusEdit,
|
||||||
statusEditName: convertStatusEdit(e.statusEdit),
|
statusEditName: convertStatusEdit(e.statusEdit),
|
||||||
}));
|
}))
|
||||||
rows.value = datalist;
|
rows.value = datalist
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertStatus(status: string) {
|
function convertStatus(status: string) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "1":
|
case '1':
|
||||||
return "ขาดราชการ";
|
return 'ขาดราชการ'
|
||||||
case "2":
|
case '2':
|
||||||
return "ปกติ";
|
return 'ปกติ'
|
||||||
case "3":
|
case '3':
|
||||||
return "สาย";
|
return 'สาย'
|
||||||
default:
|
default:
|
||||||
return "";
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function convertStatusEdit(val: string) {
|
function convertStatusEdit(val: string) {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case "edit":
|
case 'edit':
|
||||||
return "ขอแก้ไข";
|
return 'ขอแก้ไข'
|
||||||
case "wait":
|
case 'wait':
|
||||||
return "รออนุมัติ";
|
return 'รออนุมัติ'
|
||||||
case "approve":
|
case 'approve':
|
||||||
return "อนุมัติ";
|
return 'อนุมัติ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function classColorStatus(val: string) {
|
function classColorStatus(val: string) {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case "wait":
|
case 'wait':
|
||||||
return "orange";
|
return 'orange'
|
||||||
case "approve":
|
case 'approve':
|
||||||
return "green";
|
return 'green'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,5 +57,5 @@ export const useChekIn = defineStore("checkin", () => {
|
||||||
rows,
|
rows,
|
||||||
fetchHistoryList,
|
fetchHistoryList,
|
||||||
classColorStatus,
|
classColorStatus,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,122 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from 'pinia'
|
||||||
import CustomComponent from "@/components/CustomDialog.vue";
|
import CustomComponent from '@/components/CustomDialog.vue'
|
||||||
import { useQuasar } from "quasar";
|
|
||||||
const $q = useQuasar();
|
|
||||||
|
|
||||||
export const useCounterMixin = defineStore("mixin", () => {
|
export const useCounterMixin = defineStore('mixin', () => {
|
||||||
function date2Thai(srcDate: Date, isFullMonth = false, isTime = false) {
|
function date2Thai(srcDate: Date, isFullMonth = false, isTime = false) {
|
||||||
if (srcDate == null) {
|
if (srcDate == null) {
|
||||||
return null;
|
return null
|
||||||
|
;`
|
||||||
`
|
`
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
const date = new Date(srcDate);
|
const date = new Date(srcDate)
|
||||||
const isValidDate = Boolean(+date);
|
const isValidDate = Boolean(+date)
|
||||||
if (!isValidDate) return srcDate.toString();
|
if (!isValidDate) return srcDate.toString()
|
||||||
if (isValidDate && date.getFullYear() < 1000) return srcDate.toString();
|
if (isValidDate && date.getFullYear() < 1000) return srcDate.toString()
|
||||||
const fullMonthThai = [
|
const fullMonthThai = [
|
||||||
"มกราคม",
|
'มกราคม',
|
||||||
"กุมภาพันธ์",
|
'กุมภาพันธ์',
|
||||||
"มีนาคม",
|
'มีนาคม',
|
||||||
"เมษายน",
|
'เมษายน',
|
||||||
"พฤษภาคม",
|
'พฤษภาคม',
|
||||||
"มิถุนายน",
|
'มิถุนายน',
|
||||||
"กรกฎาคม",
|
'กรกฎาคม',
|
||||||
"สิงหาคม",
|
'สิงหาคม',
|
||||||
"กันยายน",
|
'กันยายน',
|
||||||
"ตุลาคม",
|
'ตุลาคม',
|
||||||
"พฤศจิกายน",
|
'พฤศจิกายน',
|
||||||
"ธันวาคม",
|
'ธันวาคม',
|
||||||
];
|
]
|
||||||
const abbrMonthThai = [
|
const abbrMonthThai = [
|
||||||
"ม.ค.",
|
'ม.ค.',
|
||||||
"ก.พ.",
|
'ก.พ.',
|
||||||
"มี.ค.",
|
'มี.ค.',
|
||||||
"เม.ย.",
|
'เม.ย.',
|
||||||
"พ.ค.",
|
'พ.ค.',
|
||||||
"มิ.ย.",
|
'มิ.ย.',
|
||||||
"ก.ค.",
|
'ก.ค.',
|
||||||
"ส.ค.",
|
'ส.ค.',
|
||||||
"ก.ย.",
|
'ก.ย.',
|
||||||
"ต.ค.",
|
'ต.ค.',
|
||||||
"พ.ย.",
|
'พ.ย.',
|
||||||
"ธ.ค.",
|
'ธ.ค.',
|
||||||
];
|
]
|
||||||
let dstYear = 0;
|
let dstYear = 0
|
||||||
if (date.getFullYear() > 2500) {
|
if (date.getFullYear() > 2500) {
|
||||||
dstYear = date.getFullYear();
|
dstYear = date.getFullYear()
|
||||||
} else {
|
} else {
|
||||||
dstYear = date.getFullYear() + 543;
|
dstYear = date.getFullYear() + 543
|
||||||
}
|
}
|
||||||
let dstMonth = "";
|
let dstMonth = ''
|
||||||
if (isFullMonth) {
|
if (isFullMonth) {
|
||||||
dstMonth = fullMonthThai[date.getMonth()];
|
dstMonth = fullMonthThai[date.getMonth()]
|
||||||
} else {
|
} else {
|
||||||
dstMonth = abbrMonthThai[date.getMonth()];
|
dstMonth = abbrMonthThai[date.getMonth()]
|
||||||
}
|
}
|
||||||
let dstTime = "";
|
let dstTime = ''
|
||||||
if (isTime) {
|
if (isTime) {
|
||||||
const H = date.getHours().toString().padStart(2, "0");
|
const H = date.getHours().toString().padStart(2, '0')
|
||||||
const M = date.getMinutes().toString().padStart(2, "0");
|
const M = date.getMinutes().toString().padStart(2, '0')
|
||||||
// const S = date.getSeconds().toString().padStart(2, "0")
|
// const S = date.getSeconds().toString().padStart(2, "0")
|
||||||
// dstTime = " " + H + ":" + M + ":" + S + " น."
|
// dstTime = " " + H + ":" + M + ":" + S + " น."
|
||||||
dstTime = " " + H + ":" + M + " น.";
|
dstTime = ' ' + H + ':' + M + ' น.'
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
date.getDate().toString().padStart(2, "0") +
|
date.getDate().toString().padStart(2, '0') +
|
||||||
" " +
|
' ' +
|
||||||
dstMonth +
|
dstMonth +
|
||||||
" " +
|
' ' +
|
||||||
dstYear +
|
dstYear +
|
||||||
dstTime
|
dstTime
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function covertDateObject(date: string) {
|
function covertDateObject(date: string) {
|
||||||
if (date) {
|
if (date) {
|
||||||
const dateParts = date.split("/");
|
const dateParts = date.split('/')
|
||||||
// ประกาศตัวแปรเพื่อเก็บค่าวันที่, เดือน, และ ปี
|
// ประกาศตัวแปรเพื่อเก็บค่าวันที่, เดือน, และ ปี
|
||||||
const day = parseInt(dateParts[0], 10);
|
const day = parseInt(dateParts[0], 10)
|
||||||
const month = parseInt(dateParts[1], 10) - 1;
|
const month = parseInt(dateParts[1], 10) - 1
|
||||||
const year = parseInt(dateParts[2], 10) + 2500;
|
const year = parseInt(dateParts[2], 10) + 2500
|
||||||
// สร้างอ็อบเจ็กต์ Date ด้วยค่าที่ได้
|
// สร้างอ็อบเจ็กต์ Date ด้วยค่าที่ได้
|
||||||
const dateObject = new Date(year, month, day);
|
const dateObject = new Date(year, month, day)
|
||||||
return date2Thai(dateObject);
|
return date2Thai(dateObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type OkCallback = () => void;
|
|
||||||
type CancelCallback = () => void;
|
type OkCallback = () => void
|
||||||
const dialogConfirm = (
|
type CancelCallback = () => void
|
||||||
|
function dialogConfirm(
|
||||||
q: any,
|
q: any,
|
||||||
ok?: OkCallback,
|
ok?: OkCallback,
|
||||||
title?: string, // ถ้ามี cancel action ใส่เป็น null
|
title?: string, // ถ้ามี cancel action ใส่เป็น null
|
||||||
desc?: string, // ถ้ามี cancel action ใส่เป็น null
|
desc?: string, // ถ้ามี cancel action ใส่เป็น null
|
||||||
cancel?: CancelCallback
|
cancel?: CancelCallback
|
||||||
) => {
|
) {
|
||||||
q.dialog({
|
q.dialog({
|
||||||
component: CustomComponent,
|
component: CustomComponent,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
title: title && title != null ? title : "ยืนยันการบันทึก",
|
title: title && title != null ? title : 'ยืนยันการบันทึก',
|
||||||
message:
|
message:
|
||||||
desc && desc != null
|
desc && desc != null
|
||||||
? desc
|
? desc
|
||||||
: "ต้องการยืนยันการบันทึกข้อมูลนี้ใช่หรือไม่?",
|
: 'ต้องการยืนยันการบันทึกข้อมูลนี้ใช่หรือไม่?',
|
||||||
icon: "info",
|
icon: 'info',
|
||||||
color: "public",
|
color: 'public',
|
||||||
textOk: "ตกลง",
|
textOk: 'ตกลง',
|
||||||
onlycancel: false,
|
onlycancel: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.onOk(() => {
|
.onOk(() => {
|
||||||
if (ok) ok();
|
if (ok) ok()
|
||||||
})
|
})
|
||||||
.onCancel(() => {
|
.onCancel(() => {
|
||||||
if (cancel) cancel();
|
if (cancel) cancel()
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date2Thai,
|
date2Thai,
|
||||||
covertDateObject,
|
covertDateObject,
|
||||||
dialogConfirm,
|
dialogConfirm,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
@import './quasar.variables.sass'
|
|
||||||
@import '~quasar-styl'
|
|
||||||
// @import '~quasar-addon-styl'
|
|
||||||
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>
|
||||||
|
|
@ -1,72 +1,72 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { QTableProps } from "quasar";
|
import type { QTableProps } from 'quasar'
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from 'vue-router'
|
||||||
import Table from "@/components/TableHistory.vue";
|
import Table from '@/components/TableHistory.vue'
|
||||||
import ToolBar from "@/components/ToolBar.vue";
|
import ToolBar from '@/components/ToolBar.vue'
|
||||||
|
|
||||||
// importStores
|
// importStores
|
||||||
import { useChekIn } from "@/stores/chekin";
|
import { useChekIn } from '@/stores/chekin'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const stores = useChekIn();
|
const stores = useChekIn()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchlist();
|
fetchlist()
|
||||||
});
|
})
|
||||||
|
|
||||||
function fetchlist() {
|
function fetchlist() {
|
||||||
const listData = [
|
const listData = [
|
||||||
{
|
{
|
||||||
no: "1",
|
no: '1',
|
||||||
date: "13/08/66",
|
date: '13/08/66',
|
||||||
in: "11:20",
|
in: '11:20',
|
||||||
loIn: "สำนักงาน ก.ก ",
|
loIn: 'สำนักงาน ก.ก ',
|
||||||
out: "",
|
out: '',
|
||||||
loOut: "",
|
loOut: '',
|
||||||
status: "",
|
status: '',
|
||||||
Morningstatus: "1",
|
Morningstatus: '1',
|
||||||
AfternoonStatus: "1",
|
AfternoonStatus: '1',
|
||||||
statusEdit: "wait",
|
statusEdit: 'wait',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
no: "2",
|
no: '2',
|
||||||
date: "12/08/66",
|
date: '12/08/66',
|
||||||
in: "08:04",
|
in: '08:04',
|
||||||
loIn: "สำนักงาน ก.ก ",
|
loIn: 'สำนักงาน ก.ก ',
|
||||||
out: "17:01",
|
out: '17:01',
|
||||||
loOut: "สำนักงาน ก.ก ",
|
loOut: 'สำนักงาน ก.ก ',
|
||||||
status: "ลงเวลาเรียบร้อย",
|
status: 'ลงเวลาเรียบร้อย',
|
||||||
Morningstatus: "2",
|
Morningstatus: '2',
|
||||||
AfternoonStatus: "2",
|
AfternoonStatus: '2',
|
||||||
statusEdit: "edit",
|
statusEdit: 'edit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
no: "3",
|
no: '3',
|
||||||
date: "11/08/66",
|
date: '11/08/66',
|
||||||
in: "08:34",
|
in: '08:34',
|
||||||
loIn: "สำนักงาน ก.ก ",
|
loIn: 'สำนักงาน ก.ก ',
|
||||||
out: "17:36",
|
out: '17:36',
|
||||||
loOut: "สำนักงาน ก.ก ",
|
loOut: 'สำนักงาน ก.ก ',
|
||||||
status: "สาย ทำงานครบ",
|
status: 'สาย ทำงานครบ',
|
||||||
Morningstatus: "3",
|
Morningstatus: '3',
|
||||||
AfternoonStatus: "2",
|
AfternoonStatus: '2',
|
||||||
statusEdit: "edit",
|
statusEdit: 'edit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
no: "4",
|
no: '4',
|
||||||
date: "10/08/66",
|
date: '10/08/66',
|
||||||
in: "08:48",
|
in: '08:48',
|
||||||
loIn: "สำนักงาน ก.ก ",
|
loIn: 'สำนักงาน ก.ก ',
|
||||||
out: "17:00",
|
out: '17:00',
|
||||||
loOut: "สำนักงาน ก.ก ",
|
loOut: 'สำนักงาน ก.ก ',
|
||||||
status: "สาย ทำงานไม่ครบ",
|
status: 'สาย ทำงานไม่ครบ',
|
||||||
Morningstatus: "3",
|
Morningstatus: '3',
|
||||||
AfternoonStatus: "3",
|
AfternoonStatus: '3',
|
||||||
statusEdit: "approve",
|
statusEdit: 'approve',
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
stores.fetchHistoryList(listData);
|
stores.fetchHistoryList(listData)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,202 +1,134 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from 'vue-router'
|
||||||
import { useQuasar } from "quasar";
|
import { useQuasar } from 'quasar'
|
||||||
import moment, { Moment } from "moment";
|
import moment from 'moment'
|
||||||
|
import Camera from 'simple-vue-camera'
|
||||||
|
|
||||||
// import Type
|
// import Type
|
||||||
import type { FormRef } from "@/interface/response/checkin";
|
import type { FormRef } from '@/interface/response/checkin'
|
||||||
// import components
|
// import components
|
||||||
import MapCheck from "@/components/MapCheckin.vue";
|
import MapCheck from '@/components/MapCheckin.vue'
|
||||||
// import Stores
|
// import Stores
|
||||||
import { useCounterMixin } from "@/stores/mixin";
|
import { useCounterMixin } from '@/stores/mixin'
|
||||||
|
|
||||||
const mixin = useCounterMixin();
|
const mixin = useCounterMixin()
|
||||||
const { date2Thai, dialogConfirm } = mixin;
|
const { date2Thai, dialogConfirm } = mixin
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const $q = useQuasar();
|
const $q = useQuasar()
|
||||||
|
|
||||||
const stetusCheckin = ref(true);
|
const stetusCheckin = ref(true)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateClock();
|
updateClock()
|
||||||
});
|
})
|
||||||
|
|
||||||
//time
|
//time
|
||||||
const dateNow = ref<Date>(new Date());
|
const dateNow = ref<Date>(new Date())
|
||||||
const Thai = ref<Date>(dateNow.value);
|
const Thai = ref<Date>(dateNow.value)
|
||||||
const formattedS = ref();
|
const formattedS = ref()
|
||||||
const formattedM = ref();
|
const formattedM = ref()
|
||||||
const formattedH = ref();
|
const formattedH = ref()
|
||||||
|
|
||||||
function updateClock() {
|
function updateClock() {
|
||||||
const date = Date.now();
|
const date = Date.now()
|
||||||
let hh = moment(date).format("HH");
|
const hh = moment(date).format('HH')
|
||||||
let mm = moment(date).format("mm");
|
const mm = moment(date).format('mm')
|
||||||
let ss = moment(date).format("ss");
|
const ss = moment(date).format('ss')
|
||||||
formattedS.value = ss;
|
formattedS.value = ss
|
||||||
formattedM.value = mm;
|
formattedM.value = mm
|
||||||
formattedH.value = hh;
|
formattedH.value = hh
|
||||||
}
|
}
|
||||||
setInterval(updateClock, 1000);
|
setInterval(updateClock, 1000)
|
||||||
|
|
||||||
//location
|
//location
|
||||||
const location = ref<string>("");
|
const location = ref<string>('')
|
||||||
const coordinates = ref<string>("13° 43’ 45” N 100° 31’ 26” E");
|
const coordinates = ref<string>('13° 43’ 45” N 100° 31’ 26” E')
|
||||||
const workplace = ref<string>("in-place");
|
const workplace = ref<string>('in-place')
|
||||||
const useLocation = ref<string | null>("");
|
const useLocation = ref<string | null>('')
|
||||||
const model = ref<string | null>("");
|
const model = ref<string | null>('')
|
||||||
const options = ref<string[]>([
|
const options = ref<string[]>([
|
||||||
"ปฏิบัติงานที่บ้าน",
|
'ปฏิบัติงานที่บ้าน',
|
||||||
"ลืมลงเวลาปฏิบัติงาน",
|
'ลืมลงเวลาปฏิบัติงาน',
|
||||||
"ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่",
|
'ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่',
|
||||||
"ขออนุญาตออกนอกสถานที่",
|
'ขออนุญาตออกนอกสถานที่',
|
||||||
"อื่นๆ",
|
'อื่นๆ',
|
||||||
]);
|
])
|
||||||
function selectLocation() {
|
function selectLocation() {
|
||||||
if (model.value === "อื่นๆ") {
|
if (model.value === 'อื่นๆ') {
|
||||||
useLocation.value = "";
|
useLocation.value = ''
|
||||||
} else {
|
} else {
|
||||||
useLocation.value = model.value;
|
useLocation.value = model.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//camera
|
//camera
|
||||||
const camera = ref(false);
|
const camera = ref<InstanceType<typeof Camera>>()
|
||||||
const hasPhoto = ref<boolean>(true);
|
const cameraIsOn = ref<boolean>(false)
|
||||||
const mediaStream = ref<MediaStream | null>(null);
|
const img = ref<any>(undefined)
|
||||||
const video = ref<HTMLVideoElement | null>(null);
|
const photoWidth = ref<number>(350)
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
const photoHeight = ref<number>(350)
|
||||||
const img = ref<any>(null);
|
|
||||||
|
|
||||||
const openCamera = () => {
|
const openCamera = () => {
|
||||||
camera.value = true;
|
cameraIsOn.value ? camera.value?.stop() : camera.value?.start()
|
||||||
camera.value && setupCamera();
|
cameraIsOn.value = !cameraIsOn.value
|
||||||
};
|
|
||||||
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;
|
async function capturePhoto() {
|
||||||
canvasElement.height = drawHeight;
|
const imageBlob: any = await camera.value?.snapshot(
|
||||||
if (context) {
|
{ width: photoWidth.value, height: photoHeight.value },
|
||||||
context.imageSmoothingEnabled = true;
|
'image/png',
|
||||||
context.imageSmoothingQuality = "low";
|
0.5
|
||||||
}
|
)
|
||||||
|
|
||||||
// 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");
|
camera.value?.stop()
|
||||||
img.value = dataURL;
|
const url = URL.createObjectURL(imageBlob)
|
||||||
console.log(img.value);
|
img.value = url
|
||||||
|
}
|
||||||
|
|
||||||
if (mediaStream.value) {
|
|
||||||
mediaStream.value.getTracks().forEach((track) => track.stop());
|
|
||||||
videoElement.srcObject = null;
|
|
||||||
hasPhoto.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function refreshPhoto() {
|
function refreshPhoto() {
|
||||||
hasPhoto.value = true;
|
img.value = undefined
|
||||||
img.value = "";
|
camera.value?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate
|
// validate
|
||||||
const useLocationRef = ref<object | null>(null);
|
const useLocationRef = ref<object | null>(null)
|
||||||
const modelRef = ref<object | null>(null);
|
const modelRef = ref<object | null>(null)
|
||||||
const objectRef: FormRef = {
|
const objectRef: FormRef = {
|
||||||
model: modelRef,
|
model: modelRef,
|
||||||
useLocation: useLocationRef,
|
useLocation: useLocationRef,
|
||||||
};
|
}
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
const hasError = [];
|
const hasError = []
|
||||||
for (const key in objectRef) {
|
for (const key in objectRef) {
|
||||||
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
||||||
const property = objectRef[key];
|
const property = objectRef[key]
|
||||||
if (property.value && typeof property.value.validate === "function") {
|
if (property.value && typeof property.value.validate === 'function') {
|
||||||
const isValid = property.value.validate();
|
const isValid = property.value.validate()
|
||||||
hasError.push(isValid);
|
hasError.push(isValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasError.every((result) => result === true)) {
|
if (hasError.every((result) => result === true)) {
|
||||||
confirm();
|
confirm()
|
||||||
} else {
|
} else {
|
||||||
console.log("ไม่ผ่าน ");
|
console.log('ไม่ผ่าน ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ยืนยันการลงเวลา
|
// ยืนยันการลงเวลา
|
||||||
const dialogTime = ref<boolean>(false);
|
const dialogTime = ref<boolean>(false)
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
dialogConfirm(
|
// ยิงไปที่ api แล้วแสดง popup
|
||||||
$q,
|
dialogTime.value = true
|
||||||
async () => {
|
}
|
||||||
dialogTime.value = true;
|
|
||||||
},
|
|
||||||
"ยืนยันการบันทึกเวลา ?",
|
|
||||||
"ต้องการยืนยันการบันทึกการลงเวลานี้ใช่หรือไม่"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// class
|
// class
|
||||||
const getClass = (val: boolean) => {
|
const getClass = (val: boolean) => {
|
||||||
return {
|
return {
|
||||||
"bg-primary text-white col-12 row items-center q-px-md q-py-sm": val,
|
'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,
|
'bg-red-9 text-white col-12 row items-center q-px-md q-py-sm': !val,
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -263,13 +195,12 @@ const getClass = (val: boolean) => {
|
||||||
<MapCheck />
|
<MapCheck />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-4">
|
<div class="col-12 col-sm-4">
|
||||||
<q-card
|
<q-card flat bordered class="card-container">
|
||||||
flat
|
<div
|
||||||
bordered
|
v-if="!cameraIsOn && img == null"
|
||||||
class="card-container"
|
class="preview-placeholder"
|
||||||
@click="openCamera()"
|
@click="openCamera()"
|
||||||
>
|
>
|
||||||
<div v-if="!camera" class="preview-placeholder">
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<q-icon
|
<q-icon
|
||||||
name="photo_camera"
|
name="photo_camera"
|
||||||
|
|
@ -279,18 +210,26 @@ const getClass = (val: boolean) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div>
|
||||||
<div v-if="hasPhoto" class="video-container">
|
<!-- แสดงกล้องตอนกดถ่ายภาพ -->
|
||||||
<video ref="video" autoplay class="video-element"></video>
|
<Camera
|
||||||
<canvas ref="canvas" class="canvas-element"></canvas>
|
:resolution="{ width: photoWidth, height: photoHeight }"
|
||||||
</div>
|
ref="camera"
|
||||||
<div v-else class="image-container">
|
:autoplay="false"
|
||||||
|
:style="!img ? 'display: block' : 'display: none'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- แสดงรูปเมื่อกด capture -->
|
||||||
|
<div v-if="img" class="image-container">
|
||||||
<q-img :src="img" class="image-element"></q-img>
|
<q-img :src="img" class="image-element"></q-img>
|
||||||
<canvas ref="canvas" class="canvas-element"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute-bottom-right q-ma-md">
|
|
||||||
|
<div
|
||||||
|
v-if="cameraIsOn"
|
||||||
|
class="absolute-bottom-right q-ma-md"
|
||||||
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="hasPhoto"
|
v-if="img == null"
|
||||||
round
|
round
|
||||||
push
|
push
|
||||||
icon="photo_camera"
|
icon="photo_camera"
|
||||||
|
|
@ -311,6 +250,7 @@ const getClass = (val: boolean) => {
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 q-mb-md">
|
<div class="col-12 q-mb-md">
|
||||||
<q-card
|
<q-card
|
||||||
bordered
|
bordered
|
||||||
|
|
@ -377,6 +317,7 @@ const getClass = (val: boolean) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
<div class="col-12 text-right">
|
<div class="col-12 text-right">
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<div class="col-12 q-pa-md">
|
<div class="col-12 q-pa-md">
|
||||||
|
|
@ -471,8 +412,6 @@ const getClass = (val: boolean) => {
|
||||||
height: 350px; /* Adjust as needed */
|
height: 350px; /* Adjust as needed */
|
||||||
background: #f6f5f5;
|
background: #f6f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container,
|
|
||||||
.image-container {
|
.image-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -481,7 +420,6 @@ const getClass = (val: boolean) => {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-element,
|
|
||||||
.image-element {
|
.image-element {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -489,7 +427,8 @@ const getClass = (val: boolean) => {
|
||||||
border-radius: 5px; /* Adjust as needed */
|
border-radius: 5px; /* Adjust as needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-element {
|
.preview-placeholder {
|
||||||
display: none; /* Adjust as needed */
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
42
src/views/SampleCamera.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Camera from 'simple-vue-camera'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const camera = ref<InstanceType<typeof Camera>>();
|
||||||
|
|
||||||
|
const cameraIsOn = ref<boolean>(false)
|
||||||
|
|
||||||
|
const cameraOff = () => {
|
||||||
|
cameraIsOn.value ? camera.value?.stop() : camera.value?.start()
|
||||||
|
cameraIsOn.value = !cameraIsOn.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use camera reference to call functions
|
||||||
|
const takePicture = async () => {
|
||||||
|
const imageBlob = await camera.value?.snapshot(
|
||||||
|
{ width: 640, height: 480 },
|
||||||
|
'image/png',
|
||||||
|
0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('imageBlob', imageBlob)
|
||||||
|
camera.value?.stop()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="row col-12">
|
||||||
|
<div class="col-3">
|
||||||
|
<Camera
|
||||||
|
:resolution="{ width: 500, height: 500 }"
|
||||||
|
ref="camera"
|
||||||
|
:autoplay="false"
|
||||||
|
>
|
||||||
|
<q-btn color="primary" @click="cameraOff"
|
||||||
|
>I'm on top of the video</q-btn
|
||||||
|
>
|
||||||
|
<q-btn color="primary" @click="takePicture">Take Picture</q-btn>
|
||||||
|
</Camera>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tsconfig.config.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"ignoreDeprecations": "5.0",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,14 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"files": [],
|
||||||
"target": "es5",
|
"references": [
|
||||||
"module": "esnext",
|
{
|
||||||
"strict": true,
|
"path": "./tsconfig.config.json"
|
||||||
"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"]
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
},
|
},
|
||||||
"include": [
|
{
|
||||||
"src/**/*.ts",
|
"path": "./tsconfig.vitest.json"
|
||||||
"src/**/*.tsx",
|
}
|
||||||
"src/**/*.vue",
|
]
|
||||||
"tests/**/*.ts",
|
|
||||||
"tests/**/*.tsx"
|
|
||||||
],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
tsconfig.vitest.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
||||||
60
vite.config.js
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
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)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3008,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
const { defineConfig } = require('@vue/cli-service')
|
|
||||||
module.exports = defineConfig({
|
|
||||||
transpileDependencies: [
|
|
||||||
'quasar'
|
|
||||||
],
|
|
||||||
|
|
||||||
pluginOptions: {
|
|
||||||
quasar: {
|
|
||||||
importStrategy: 'kebab',
|
|
||||||
rtlSupport: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||