no message

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2023-11-01 11:03:56 +07:00
parent d4ed0f9ebc
commit b770d6f989
24 changed files with 1384 additions and 356 deletions

View file

@ -9,11 +9,29 @@ module.exports = {
"@vue/typescript/recommended",
"plugin:prettier/recommended",
],
overrides: [
{
files: ["*.vue"],
rules: {
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
},
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/ban-types": [
"error",
{
types: {
Object: false,
Number: false,
},
},
],
},
};

14
.prettierrc Normal file
View file

@ -0,0 +1,14 @@
{
"extends": ["prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
],
"arrow-body-style": "off",
"prefer-arrow-callback": "off"
}
}

View file

@ -9,7 +9,7 @@ npm install
### Compiles and hot-reloads for development
```
npm run serve
npm run dev
```
### Compiles and minifies for production

659
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,18 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "eslint --fix ",
"format": "prettier --write ."
},
"dependencies": {
"@mdi/font": "^7.3.67",
"@quasar/extras": "^1.0.0",
"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-class-component": "^8.0.0-0",
@ -24,11 +30,13 @@
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"prettier": "^2.4.1",
"typescript": "~4.5.5"
"sass": "1.32.12",
"sass-loader": "^12.0.0",
"typescript": "~4.5.5",
"vue-cli-plugin-quasar": "~5.0.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before After
Before After

View file

@ -1,15 +1,19 @@
<!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">
<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>
<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"></div>
<!-- built files will be auto injected -->

View file

@ -1,8 +1,8 @@
<template>
<nav>
<!-- <nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
</nav> -->
<router-view />
</template>

15
src/assets/logo.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -1,142 +1,372 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa"
target="_blank"
rel="noopener"
>pwa</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
target="_blank"
rel="noopener"
>router</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
target="_blank"
rel="noopener"
>typescript</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
<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(checkIn)">
<!-- <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-12 text-center">
<span v-if="checkIn">ลงเวลาเข้างาน</span>
<span v-else>ลงเวลาออกงาน</span>
</span>
<!-- <div class="col-2 text-right">
<q-btn
icon="mdi-history"
unelevated
rounded
dense
flat
color="white"
:label="$q.screen.gt.xs ? 'ประวัติการลงเวลา' : ''"
:class="$q.screen.gt.xs ? 'q-px-sm' : ''"
@click="router.push('/check-in/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">
<q-card
bordered
flat
class="col-12 bg-grey-2 shadow-0"
:style="$q.screen.gt.xs ? 'height: 350px;' : 'height: 220px;'"
>
<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="cardImg col-12 bg-grey-2 items-center row cursor-pointer shadow-0"
@click="photo()"
:style="$q.screen.gt.xs ? 'height: 350px;' : 'height: 220px;'"
>
<div class="column col-12" v-if="!camera">
<div class="text-center">
<q-icon
name="photo_camera"
color="blue-grey-3"
size="100px"
/>
</div>
</div>
<div v-else>
<div v-if="hasPhoto">
<video
ref="video"
autoplay
:style="{
width: videoWidth + 'px',
height: videoHeight + 'px',
}"
></video>
<canvas
ref="canvas"
:width="videoWidth"
:height="videoHeight"
></canvas>
</div>
<div v-else class="q-mt-sm">
<q-img
:src="img"
:style="{
width: videoWidth + 'px',
height: videoHeight + 'px',
}"
class="q-mt-lg"
></q-img>
<canvas
ref="canvas"
:width="canvasWidth"
:height="canvasHeight"
:style="{
width: videoWidth + 'px',
height: videoHeight + 'px',
}"
></canvas>
</div>
<div class="absolute-bottom-right q-ma-md">
<q-btn
round
push
icon="photo_camera"
size="md"
color="positive"
@click="capturePhoto"
/>
</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">
<q-select
v-if="workplace == 'off-site'"
dense
class="q-ml-md"
outlined
v-model="model"
:options="options"
prefix="ระบุสถานที่ :"
/>
</div>
</q-card>
</div>
</div>
</div>
</div>
<div class="col-12 text-right" v-if="camera">
<div class="col-12">
<q-separator />
</div>
<div class="col-12 q-pa-md">
<q-btn
:label="checkIn == true ? 'ลงเวลาเข้างาน' : 'ลงเวลาออกงาน'"
:color="checkIn == true ? 'primary' : 'red-9'"
push
size="14px"
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width'"
@click="confirm"
/>
</div>
</div>
</q-card>
</div>
</div>
<q-dialog v-model="dialogTime">
<q-card class="full-width cardNone">
<div :class="getClass(checkIn)">
<div class="text-body1 text-center col-12 text-weight-bold">
<span v-if="checkIn">ลงเวลาเข้างานของคุณ</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>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useCounterMixin } from "@/stores/mixin";
// import { useRouter } from "vue-router";
import moment, { Moment } from "moment";
const mixin = useCounterMixin();
const { date2Thai } = mixin;
@Options({
props: {
msg: String,
},
})
export default class HelloWorld extends Vue {
msg!: string;
// const router = useRouter();
const dateNow = ref(new Date());
const Thai = ref(dateNow.value);
const checkIn = ref(true); // () true 2 ()
const location = ref("สำนักงาน ก.ก");
const coordinates = ref("13° 43 45” N 100° 31 26” E");
const workplace = ref("in-place");
const model = ref(null);
const options = ref([
"ปฏิบัติงานที่บ้าน",
"ลืมลงเวลาปฏิบัติงาน",
"ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่",
"ขออนุญาตออกนอกสถานที่",
]);
const camera = ref(false);
const dialogTime = ref(false);
const mediaStream = ref<MediaStream | null>(null);
const video = ref<HTMLVideoElement | null>(null);
const canvas = ref<HTMLCanvasElement | null>(null);
const hasPhoto = ref<boolean>(true);
const img = ref<any>();
const videoWidth = ref<number>(335);
const videoHeight = ref<number>(350);
const canvasWidth = ref<number>(335);
const canvasHeight = ref<number>(350);
const photo = () => {
camera.value = true;
camera.value && setupCamera();
};
const confirm = () => {
dialogTime.value = true;
};
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;
}
context.drawImage(videoElement, 0, 0, 335, 270);
//
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;
}
}
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);
}
};
// const time = new Date().toLocaleTimeString();
const formattedS = ref();
const formattedM = ref();
const formattedH = ref();
onMounted(() => {
updateClock();
});
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);
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>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
.q-card.cardImg:hover {
border: 1px solid #02a998 !important;
}
</style>

View file

@ -1,6 +1,16 @@
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
import "./registerServiceWorker";
import router from "./router";
import { Quasar } from "quasar";
import quasarUserOptions from "./quasar-user-options";
createApp(App).use(router).mount("#app");
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.use(Quasar, quasarUserOptions);
app.mount("#app");

View file

@ -0,0 +1,100 @@
<template>
<div>
<div id="map"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import Map from "ol/Map";
import View from "ol/View";
import { fromLonLat } from "ol/proj";
import { Tile as TileLayer } from "ol/layer";
import { OSM } from "ol/source";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import Circle from "ol/geom/Circle";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import { Style, Icon, Fill, Stroke } from "ol/style";
onMounted(() => {
const map = new Map({
target: "map",
layers: [
new TileLayer({
source: new OSM(),
}),
],
view: new View({
center: fromLonLat([98.981716, 18.7883]),
zoom: 12,
}),
});
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
console.log(position);
const lon = position.coords.longitude;
const lat = position.coords.latitude;
const coordinates = fromLonLat([lon, lat]);
currentLocationFeature.setGeometry(new Point(coordinates));
map.getView().setCenter(coordinates);
});
}
// feature
const currentLocationFeature = new Feature({
geometry: new Point(fromLonLat([98.981716, 18.7883])),
});
currentLocationFeature.setStyle(
new Style({
image: new Icon({
src: "https://assets.untappd.com/site/beer_logos_hd/beer-4817317_108bb_hd.jpeg",
scale: 0.02,
}),
})
);
const currentLocationLayer = new VectorLayer({
source: new VectorSource({
features: [currentLocationFeature],
}),
});
map.addLayer(currentLocationLayer);
//
const circleFeature = new Feature({
geometry: new Circle(fromLonLat([98.981716, 18.7883]), 1000),
});
circleFeature.setStyle(
new Style({
fill: new Fill({
color: "rgba(2, 169, 152, 0.2)",
}),
stroke: new Stroke({
color: "rgb(2, 169, 152)",
width: 2,
}),
})
);
const circleLayer = new VectorLayer({
source: new VectorSource({
features: [circleFeature],
}),
});
map.addLayer(circleLayer);
});
</script>
<style scoped>
#map {
width: 100%;
height: 400px;
}
</style>

View file

@ -0,0 +1,200 @@
<template>
<div class="q-pb-sm row">
<div class="items-center col-12 row q-gutter-sm">
<!-- นหาขอความใน table -->
<datepicker
menu-class-name="modalfix"
v-model="yearly"
:locale="'th'"
autoApply
year-picker
:enableTimePicker="false"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
hide-bottom-space
outlined
dense
lazy-rules
:model-value="yearly + 543"
:rules="[(val) => !!val || `${'กรุณาเลือกปีงบประมาณ'}`]"
:label="`${'ปีงบประมาณ'}`"
style="width: 150px"
>
<template v-slot:prepend>
<q-icon name="event" class="cursor-pointer" color="primary">
</q-icon>
</template>
</q-input>
</template>
</datepicker>
<q-space />
<q-input
standout
dense
:model-value="inputfilter"
ref="filterRef"
@update:model-value="updateInput"
outlined
debounce="300"
placeholder="ค้นหา"
class="gt-xs"
style="max-width: 200px"
>
<template v-slot:append>
<q-icon v-if="inputfilter == ''" name="search" />
<q-icon
v-if="inputfilter !== ''"
name="clear"
class="cursor-pointer"
@click="resetFilter"
/>
</template>
</q-input>
<!-- แสดงคอลมนใน table -->
<q-select
:model-value="inputvisible"
@update:model-value="updateVisible"
:display-value="$q.lang.table.columns"
multiple
outlined
dense
:options="attrs.columns"
options-dense
option-value="name"
map-options
emit-value
style="min-width: 150px"
class="gt-xs"
>
<template> </template>
</q-select>
</div>
</div>
<div>
<q-table
ref="table"
flat
bordered
class="custom-table2"
v-bind="attrs"
virtual-scroll
:virtual-scroll-sticky-size-start="48"
dense
:pagination-label="paginationLabel"
:pagination="initialPagination"
:rows-per-page-options="[0]"
:grid="grid"
>
<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" v-html="col.label" />
</q-th>
<q-th auto-width v-if="inputShow" />
</q-tr>
</template>
<template #body="props">
<slot v-bind="props" name="columns"></slot>
</template>
<template #item="props">
<slot v-bind="props" name="item"></slot>
</template>
</q-table>
</div>
</template>
<script setup lang="ts">
import { ref, useAttrs } from "vue";
const attrs = ref<any>(useAttrs());
const table = ref<any>(null);
const filterRef = ref<any>(null);
const initialPagination = ref({
rowsPerPage: 0,
});
const yearly = ref<number>(new Date().getFullYear());
const props = defineProps({
count: Number,
pass: Number,
notpass: Number,
inputfilter: String,
name: String,
icon: String,
inputvisible: Array,
editvisible: Boolean,
grid: Boolean,
inputShow: Boolean,
});
const emit = defineEmits([
"update:inputfilter",
"update:inputvisible",
"update:editvisible",
]);
const updateInput = (value: string | number | null) => {
emit("update:inputfilter", value);
};
const updateVisible = (value: []) => {
emit("update:inputvisible", value);
};
const paginationLabel = (start: string, end: string, total: string) => {
return start + "-" + end + " ใน " + total;
};
const resetFilter = () => {
// reset X
emit("update:inputfilter", "");
filterRef.value.focus();
};
</script>
<style 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;
}
}
</style>

View file

@ -0,0 +1,38 @@
/**
* Router Checkin
*/
const Checkin = () => import("@/modules/04_checkin/views/CheckIn.vue");
const History = () => import("@/modules/04_checkin/views/History.vue");
/* const Checkout = () => import("@/modules/04_checkin/views/Checkout.vue");
*/
export default [
{
path: "/check-in",
name: "Checkin",
component: Checkin,
meta: {
Auth: true,
Key: [7],
},
},
{
path: "/check-in/history",
name: "History",
component: History,
meta: {
Auth: true,
Key: [7],
},
},
/* {
path: "/check-out",
name: "Checkout",
component: Checkout,
meta: {
Auth: true,
Key: [7],
},
}, */
];

View file

@ -0,0 +1,11 @@
import "./styles/quasar.scss";
import "@quasar/extras/material-icons/material-icons.css";
import "@quasar/extras/material-icons-outlined/material-icons-outlined.css";
import "@quasar/extras/material-icons-round/material-icons-round.css";
import "@quasar/extras/material-icons-sharp/material-icons-sharp.css";
// To be used on app.use(Quasar, { ... })
export default {
config: {},
plugins: {},
};

View file

@ -1,7 +1,6 @@
/* 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() {

8
src/shims-vue.d.ts vendored
View file

@ -1,6 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

78
src/stores/mixin.ts Normal file
View file

@ -0,0 +1,78 @@
import { defineStore } from "pinia";
export const useCounterMixin = defineStore("mixin", () => {
function date2Thai(
srcDate: Date,
isFullMonth: boolean = false,
isTime: boolean = 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.scss Normal file
View file

@ -0,0 +1,3 @@
@import "./quasar.variables.scss";
@import "~quasar-styl";
// @import '~quasar-addon-styl';

View file

@ -0,0 +1,15 @@
// It's highly recommended to change the default colors
// to match your app's branding.
$primary: #02a998;
$secondary: #016987;
$accent: #9c27b0;
$dark: #35473c;
$positive: #21ba45;
$negative: #c10015;
$info: #31ccec;
$warning: #f2c037;
@import "~quasar-variables-styl";

View file

@ -1,18 +1,10 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
<script setup lang="ts">
// import { Options, Vue } from "vue-class-component";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
@Options({
components: {
HelloWorld,
},
})
export default class HomeView extends Vue {}
</script>

View file

@ -13,20 +13,11 @@
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"types": ["webpack-env"],
"paths": {
"@/*": [
"src/*"
]
"@/*": ["src/*"]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},
"include": [
"src/**/*.ts",
@ -35,7 +26,5 @@
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
"exclude": ["node_modules"]
}

View file

@ -1,4 +1,11 @@
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
transpileDependencies: ["quasar"],
pluginOptions: {
quasar: {
importStrategy: "kebab",
rtlSupport: false,
},
},
});