ascgis map
This commit is contained in:
parent
357a49e706
commit
f81f138c63
4 changed files with 818 additions and 1 deletions
144
src/components/AscGISMap.vue
Normal file
144
src/components/AscGISMap.vue
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { loadModules } from 'esri-loader'
|
||||
import axios from 'axios'
|
||||
import type { LocationObject } from '@/interface/index/Main'
|
||||
|
||||
const currentPosition = ref<LocationObject>()
|
||||
const poiPlaceName = ref<string>('')
|
||||
|
||||
// Replace ArcGIS api key
|
||||
const apiKey = ref<string>(
|
||||
'AAPK4f700a4324d04e9f8a1a134e0771ac45FXWawdCl-OotFfr52gz9XKxTDJTpDzw_YYcwbmKDDyAJswf14FoPyw0qBkN64DvP'
|
||||
)
|
||||
const zoomMap = ref<number>(16)
|
||||
|
||||
async function initializeMap() {
|
||||
try {
|
||||
// Load modules of ArcGIS
|
||||
const [esriConfig, Map, MapView, Point, Graphic] = await loadModules([
|
||||
'esri/config',
|
||||
'esri/Map',
|
||||
'esri/views/MapView',
|
||||
'esri/geometry/Point',
|
||||
'esri/Graphic',
|
||||
])
|
||||
|
||||
// Set apiKey
|
||||
esriConfig.apiKey = apiKey.value
|
||||
|
||||
const map = new Map({
|
||||
basemap: 'arcgis-topographic',
|
||||
// basemap: 'streets-navigation-vector',
|
||||
})
|
||||
|
||||
navigator.geolocation.getCurrentPosition(async (position) => {
|
||||
const { latitude, longitude } = position.coords
|
||||
|
||||
const mapView = new MapView({
|
||||
container: 'mapViewDisplay',
|
||||
map: map,
|
||||
center: {
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
}, // Set the initial map center current position
|
||||
zoom: zoomMap.value,
|
||||
constraints: {
|
||||
snapToZoom: false, // Disables snapping to the zoom level
|
||||
minZoom: zoomMap.value, // Set minimum zoom level
|
||||
maxZoom: zoomMap.value, // Set maximum zoom level (same as minZoom for fixed zoom)
|
||||
},
|
||||
ui: {
|
||||
components: [], // Empty array to remove all default UI components
|
||||
},
|
||||
})
|
||||
|
||||
// Update the state with the current position
|
||||
currentPosition.value = await {
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
}
|
||||
|
||||
// Create a Point object with the coordinates
|
||||
const point = new Point({
|
||||
longitude,
|
||||
latitude,
|
||||
})
|
||||
|
||||
// Pin icon
|
||||
const pinSymbol = {
|
||||
type: 'picture-marker',
|
||||
url: 'http://maps.google.com/mapfiles/ms/icons/red.png',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
}
|
||||
|
||||
// Create a graphic using the point and symbol
|
||||
const pointGraphic = new Graphic({
|
||||
geometry: point,
|
||||
symbol: pinSymbol,
|
||||
})
|
||||
mapView.graphics.add(pointGraphic)
|
||||
|
||||
// Get POI place
|
||||
axios
|
||||
.get(
|
||||
'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode/',
|
||||
{
|
||||
params: {
|
||||
f: 'json', // Format JSON response
|
||||
distance: 2000,
|
||||
category: 'POI',
|
||||
location: {
|
||||
spatialReference: { wkid: 4326 },
|
||||
x: longitude,
|
||||
y: latitude,
|
||||
},
|
||||
token: apiKey.value,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
// console.log('poi', response.data.address)
|
||||
poiPlaceName.value = response.data.address
|
||||
? response.data.address.PlaceName
|
||||
: 'ไม่พบข้อมูล'
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching points of interest:', error)
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error loading the map', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
initializeMap()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Loading skeleton -->
|
||||
<div v-if="!poiPlaceName" class="q-pa-md">
|
||||
<div class="q-gutter-md">
|
||||
<q-skeleton height="35vh" width="100%" class="bg-grey-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-card v-show="poiPlaceName" bordered flat class="col-12 bg-grey-2 shadow-0">
|
||||
<div id="mapViewDisplay" style="height: 33vh"></div>
|
||||
|
||||
<div
|
||||
:class="
|
||||
$q.screen.gt.xs
|
||||
? 'q-pa-xs text-weight-medium text-grey-8'
|
||||
: ' text-weight-medium text-grey-8'
|
||||
"
|
||||
>
|
||||
พื้นที่ใกล้เคียง
|
||||
<span class="q-px-sm">:</span>
|
||||
{{ poiPlaceName }}
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
|
|
@ -19,4 +19,9 @@ interface notiType {
|
|||
timereceive: Date
|
||||
}
|
||||
|
||||
export type { DataOption, FormRef, notiType, DataDateMonthObject }
|
||||
interface LocationObject {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
|
||||
export type { DataOption, FormRef, notiType, DataDateMonthObject, LocationObject}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '@/views/HomeView.vue'
|
||||
import MapView from '@/views/MapView.vue'
|
||||
|
||||
import keycloak from '@/plugins/keycloak'
|
||||
|
||||
|
|
@ -14,6 +15,14 @@ const router = createRouter({
|
|||
Auth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/map',
|
||||
name: 'map',
|
||||
component: MapView,
|
||||
meta: {
|
||||
Auth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
|
|
|
|||
659
src/views/MapView.vue
Normal file
659
src/views/MapView.vue
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useQuasar } from 'quasar'
|
||||
import moment from 'moment'
|
||||
import Camera from 'simple-vue-camera'
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
|
||||
// import Type
|
||||
import type { FormRef } from '@/interface/response/checkin'
|
||||
import type { notiType } from '@/interface/index/Main'
|
||||
|
||||
// import components
|
||||
import MapCheck from '@/components/AscGISMap.vue'
|
||||
|
||||
// import Stores
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
|
||||
const mixin = useCounterMixin()
|
||||
const { date2Thai, showLoader, hideLoader, messageError, dialogRemove } = mixin
|
||||
const router = useRouter()
|
||||
const $q = useQuasar()
|
||||
|
||||
const dialogTime = ref<boolean>(false)
|
||||
const stetusCheckin = ref<boolean>(true)
|
||||
|
||||
/** function เช็คเวลาต้องลงเวลาเข้าหรือออกงาน */
|
||||
async function fetchCheckTime() {
|
||||
showLoader()
|
||||
await http
|
||||
.get(config.API.checkTime())
|
||||
.then((res) => {
|
||||
const data = res.data.result
|
||||
stetusCheckin.value = data.checkInId ? false : true
|
||||
checkInId.value = data.checkInId ? data.checkInId : ''
|
||||
})
|
||||
.catch((err) => {
|
||||
messageError($q, err)
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoader()
|
||||
})
|
||||
}
|
||||
|
||||
const notiTrigger = ref<boolean>(false)
|
||||
const notiList = ref<notiType[]>([])
|
||||
/** function เรียกข้อมุลแจ้งเตือน */
|
||||
async function fetchNotifications() {
|
||||
showLoader()
|
||||
await http
|
||||
.get(config.API.msgNotificate)
|
||||
.then((res) => {
|
||||
const response = res.data.result
|
||||
const list: notiType[] = []
|
||||
response.map((e: any) => {
|
||||
list.push({
|
||||
id: e.id,
|
||||
sender:
|
||||
e.createdFullName == '' || e.createdFullName == null
|
||||
? 'เจ้าหน้าที่'[0]
|
||||
: e.createdFullName[0],
|
||||
body: e.body ?? '',
|
||||
timereceive: new Date(e.receiveDate),
|
||||
})
|
||||
})
|
||||
notiList.value = list
|
||||
})
|
||||
.catch((err) => {
|
||||
messageError($q, err)
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoader()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* function ลบรายการแจ้งเตือน
|
||||
* @param id noti
|
||||
*/
|
||||
async function onClickDelete(id: string) {
|
||||
dialogRemove($q, async () => {
|
||||
await http
|
||||
.delete(config.API.msgId(id))
|
||||
.then(() => {
|
||||
success($q, 'ลบข้อมูลสำเร็จ')
|
||||
})
|
||||
.catch((e) => {
|
||||
messageError($q, e)
|
||||
})
|
||||
.finally(async () => {
|
||||
await fetchNotifications()
|
||||
hideLoader()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** ref อัพเดทเวลา*/
|
||||
const dateNow = ref<Date>(new Date())
|
||||
const Thai = ref<Date>(dateNow.value)
|
||||
const formattedS = ref()
|
||||
const formattedM = ref()
|
||||
const formattedH = ref()
|
||||
/** function อัพเดทเวลา*/
|
||||
function updateClock() {
|
||||
const date = Date.now()
|
||||
const hh = moment(date).format('HH')
|
||||
const mm = moment(date).format('mm')
|
||||
const ss = moment(date).format('ss')
|
||||
formattedS.value = ss
|
||||
formattedM.value = mm
|
||||
formattedH.value = hh
|
||||
}
|
||||
setInterval(updateClock, 1000)
|
||||
|
||||
/** form-data */
|
||||
const formLocation = reactive({
|
||||
lat: 0, //พิกัดละติจูด
|
||||
lng: 0, //พิกัดลองจิจูด
|
||||
POI: '', //ชื่อสถานที่
|
||||
})
|
||||
const workplace = ref<string>('in-place') //ณ สถานที่ตั้ง, นอกสถานที่ตั้ง
|
||||
const useLocation = ref<string>('') //กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่
|
||||
const fileImg = ref<any>() //รูปถ่ายสถานที่
|
||||
const remark = ref<string>('') //ข้อความหมายเหตุที่ต้องการระบุเพิ่ม
|
||||
const checkInId = ref<string>('') //Id ลงเวลา check-in ล่าสุดที่ยังไม่ลงเวลาออก
|
||||
|
||||
/**
|
||||
* funciton เรียกพิกัดละติจูด พิกัดลองติจูด
|
||||
* @param location พิกัดละติจูด พิกัดลองติจูด
|
||||
* @param namePOI ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์
|
||||
*/
|
||||
async function updateLocation(location: any, namePOI: string) {
|
||||
formLocation.lat = location.lat
|
||||
formLocation.lng = location.lng
|
||||
formLocation.POI = namePOI
|
||||
}
|
||||
|
||||
//location
|
||||
const location = ref<string>('')
|
||||
// const coordinates = ref<string>('')
|
||||
const model = ref<string>('')
|
||||
const options = ref<string[]>([
|
||||
'ปฏิบัติงานที่บ้าน',
|
||||
'ลืมลงเวลาปฏิบัติงาน',
|
||||
'ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่',
|
||||
'ขออนุญาตออกนอกสถานที่',
|
||||
'อื่นๆ',
|
||||
])
|
||||
|
||||
/** function เลือกสถานที่*/
|
||||
function selectLocation() {
|
||||
if (model.value === 'อื่นๆ') {
|
||||
useLocation.value = ''
|
||||
} else {
|
||||
useLocation.value = model.value
|
||||
}
|
||||
}
|
||||
|
||||
/** Camera */
|
||||
const camera = ref<InstanceType<typeof Camera>>()
|
||||
const cameraIsOn = ref<boolean>(false)
|
||||
const img = ref<any>(undefined)
|
||||
const photoWidth = ref<number>(350)
|
||||
const photoHeight = ref<number>(350)
|
||||
|
||||
/** function เปิดกล้อง */
|
||||
async function openCamera() {
|
||||
// change camera device
|
||||
if (cameraIsOn.value) {
|
||||
camera.value?.stop()
|
||||
} else {
|
||||
await camera.value?.start()
|
||||
changeCamera()
|
||||
}
|
||||
cameraIsOn.value = !cameraIsOn.value
|
||||
}
|
||||
|
||||
/** change camera device */
|
||||
async function changeCamera() {
|
||||
const devices: any = await camera.value?.devices(['videoinput'])
|
||||
const device = await devices[0]
|
||||
camera.value?.changeCamera(device.deviceId)
|
||||
}
|
||||
|
||||
/** function ถ่ายรูป*/
|
||||
async function capturePhoto() {
|
||||
const imageBlob: any = await camera.value?.snapshot(
|
||||
{ width: photoWidth.value, height: photoHeight.value },
|
||||
'image/png',
|
||||
0.5
|
||||
)
|
||||
const fileName = 'photo.png'
|
||||
//ไฟล์รูป
|
||||
const file = new File([imageBlob], fileName, { type: 'image/png' })
|
||||
fileImg.value = file
|
||||
//แสดงรูป
|
||||
camera.value?.stop()
|
||||
const url = URL.createObjectURL(imageBlob)
|
||||
img.value = url
|
||||
}
|
||||
|
||||
/** function เปลี่ยนรูปภาพ*/
|
||||
function refreshPhoto() {
|
||||
img.value = undefined
|
||||
camera.value?.start()
|
||||
}
|
||||
|
||||
/** ref validate*/
|
||||
const useLocationRef = ref<object | null>(null)
|
||||
const modelRef = ref<object | null>(null)
|
||||
const objectRef: FormRef = {
|
||||
model: modelRef,
|
||||
useLocation: useLocationRef,
|
||||
}
|
||||
|
||||
/** function ตรวจสอบค่าว่างของ input*/
|
||||
function validateForm() {
|
||||
const hasError = []
|
||||
for (const key in objectRef) {
|
||||
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
||||
const property = objectRef[key]
|
||||
if (property.value && typeof property.value.validate === 'function') {
|
||||
const isValid = property.value.validate()
|
||||
hasError.push(isValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasError.every((result) => result === true)) {
|
||||
confirm()
|
||||
} else {
|
||||
console.log('ไม่ผ่าน ')
|
||||
}
|
||||
}
|
||||
|
||||
const timeChickin = ref<string>()
|
||||
/** function ยืนยันการลงเวลาเข้า - ออก*/
|
||||
async function confirm() {
|
||||
const isLocation = workplace.value === 'in-place' //*true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง
|
||||
const formdata = new FormData()
|
||||
formdata.append('lat', formLocation.lat.toString())
|
||||
formdata.append('lon', formLocation.lng.toString())
|
||||
formdata.append('POI', formLocation.POI)
|
||||
formdata.append('isLocation', isLocation.toString())
|
||||
formdata.append('locationName', useLocation.value)
|
||||
formdata.append('img', fileImg.value)
|
||||
formdata.append('remark', remark.value)
|
||||
formdata.append('checkInId', checkInId.value)
|
||||
await http
|
||||
.post(config.API.checkin(), formdata)
|
||||
.then(async (res) => {
|
||||
const data = res.data.result
|
||||
const dateObject = new Date(data.date)
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}
|
||||
const timeString = new Intl.DateTimeFormat('en-US', options).format(
|
||||
dateObject
|
||||
)
|
||||
timeChickin.value = timeString
|
||||
dialogTime.value = true
|
||||
// await fetchCheckTime()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
messageError($q, err)
|
||||
})
|
||||
}
|
||||
|
||||
async function onClickConfirm() {
|
||||
await fetchCheckTime()
|
||||
cameraIsOn.value = false
|
||||
img.value = undefined
|
||||
dialogTime.value = false
|
||||
}
|
||||
|
||||
// class
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'bg-primary text-white col-12 row items-center q-px-md q-py-sm': val,
|
||||
'bg-red-9 text-white col-12 row items-center q-px-md q-py-sm': !val,
|
||||
}
|
||||
}
|
||||
/** Hook*/
|
||||
onMounted(async () => {
|
||||
await fetchCheckTime()
|
||||
await fetchNotifications()
|
||||
|
||||
updateClock()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-12 row justify-center">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<q-card flat class="row col-12 cardNone">
|
||||
<div :class="getClass(stetusCheckin)">
|
||||
<div class="col-2">
|
||||
<q-btn
|
||||
icon="history"
|
||||
unelevated
|
||||
rounded
|
||||
dense
|
||||
flat
|
||||
color="white"
|
||||
:label="$q.screen.gt.xs ? 'ประวัติการลงเวลา' : ''"
|
||||
:class="$q.screen.gt.xs ? 'q-px-md' : ''"
|
||||
@click="router.push('/history')"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-body1 text-weight-bold col-8 text-center">
|
||||
<span v-if="stetusCheckin">ลงเวลาเข้างาน</span>
|
||||
<span v-else>ลงเวลาออกงาน</span>
|
||||
</span>
|
||||
<div class="col-2 text-right">
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
size="13px"
|
||||
class="q-mx-md"
|
||||
:disable="notiList.length === 0"
|
||||
>
|
||||
<q-icon name="notifications" size="24px" color="white" />
|
||||
|
||||
<q-badge
|
||||
rounded
|
||||
v-show="notiList.length > 0"
|
||||
color="negative"
|
||||
text-color="white"
|
||||
floating
|
||||
>{{ notiList.length }}</q-badge
|
||||
>
|
||||
<q-menu v-model="notiTrigger" max-width="480px" :offset="[0, 10]">
|
||||
<div class="q-px-md q-py-sm row col-12 items-center">
|
||||
<div class="text-subtitle1 text-weight-medium">
|
||||
การแจ้งเตือน
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-list
|
||||
style="min-width: 300px"
|
||||
v-for="n in notiList"
|
||||
:key="n.id"
|
||||
>
|
||||
<q-item v-ripple class="mytry q-py-xs" dense>
|
||||
<q-item-section avatar top style="min-width: 40px">
|
||||
<q-avatar color="primary" size="22px" text-color="white">
|
||||
<span class="text-weight-medium text-uppercase">{{
|
||||
n.sender
|
||||
}}</span>
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label caption class="text-black">{{
|
||||
n.body
|
||||
}}</q-item-label>
|
||||
<q-item-label
|
||||
caption
|
||||
class="row items-center text-grey-7"
|
||||
>{{ date2Thai(n.timereceive) }}</q-item-label
|
||||
>
|
||||
</q-item-section>
|
||||
<q-btn
|
||||
size="sm"
|
||||
unelevated
|
||||
dense
|
||||
icon="mdi-close"
|
||||
class="mybtn q-mx-xs"
|
||||
@click="onClickDelete(n.id)"
|
||||
></q-btn>
|
||||
</q-item>
|
||||
<q-separator color="grey-2" />
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 q-pa-md text-grey-9">
|
||||
<div class="col-12 row justify-center">
|
||||
<div class="col-12 row q-py-sm justify-center">
|
||||
<div
|
||||
class="col-xs-12 col-sm-10 text-h6 text-center text-weight-bold"
|
||||
>
|
||||
{{ date2Thai(Thai) }}
|
||||
</div>
|
||||
<div class="row col-12 justify-center q-py-sm">
|
||||
<div class="colunm">
|
||||
<div class="text-h3 text-weight-bold">
|
||||
{{ formattedH }}<span class="q-ma-md">:</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colunm">
|
||||
<div class="text-h3 text-weight-bold">
|
||||
{{ formattedM }}<span class="q-ma-md">:</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colunm">
|
||||
<div class="text-h3 text-weight-bold">{{ formattedS }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-11 row q-col-gutter-md">
|
||||
<div class="col-12 col-sm-8">
|
||||
<MapCheck @update:location="updateLocation" />
|
||||
<!-- <AscMaps /> -->
|
||||
</div>
|
||||
<div class="col-12 col-sm-4">
|
||||
<q-card flat bordered class="card-container">
|
||||
<div
|
||||
v-if="!cameraIsOn && img == null"
|
||||
class="preview-placeholder"
|
||||
@click="openCamera()"
|
||||
>
|
||||
<div class="text-center">
|
||||
<q-icon
|
||||
name="photo_camera"
|
||||
color="blue-grey-3"
|
||||
size="100px"
|
||||
class="center-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- แสดงกล้องตอนกดถ่ายภาพ -->
|
||||
<Camera
|
||||
:resolution="{ width: photoWidth, height: photoHeight }"
|
||||
ref="camera"
|
||||
: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>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="cameraIsOn"
|
||||
class="absolute-bottom-right q-ma-md"
|
||||
>
|
||||
<q-btn
|
||||
v-if="img == null"
|
||||
round
|
||||
push
|
||||
icon="photo_camera"
|
||||
size="md"
|
||||
color="positive"
|
||||
@click="capturePhoto"
|
||||
/>
|
||||
<q-btn
|
||||
v-else
|
||||
round
|
||||
push
|
||||
icon="refresh"
|
||||
size="md"
|
||||
color="negative"
|
||||
@click="refreshPhoto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<div class="col-12 q-mb-md">
|
||||
<q-card
|
||||
bordered
|
||||
flat
|
||||
:class="
|
||||
$q.screen.gt.xs
|
||||
? 'q-px-md q-py-sm row items-center shadow-0'
|
||||
: 'q-pa-md row items-center shadow-0'
|
||||
"
|
||||
>
|
||||
<div class="text-weight-bold">สถานที่ทำงาน</div>
|
||||
<div
|
||||
:class="
|
||||
$q.screen.gt.xs
|
||||
? 'row q-gutter-md q-pl-md col-sm-6 col-md-3'
|
||||
: 'column col-12'
|
||||
"
|
||||
>
|
||||
<q-radio
|
||||
v-model="workplace"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="in-place"
|
||||
label="ในสถานที่"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="workplace"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="off-site"
|
||||
label="นอกสถานที่"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col-xs-12 col-sm-6 col-md-4"
|
||||
v-if="workplace == 'off-site'"
|
||||
>
|
||||
<q-select
|
||||
ref="modelRef"
|
||||
dense
|
||||
class="q-ml-md"
|
||||
outlined
|
||||
v-model="model"
|
||||
:options="options"
|
||||
prefix="ระบุสถานที่ :"
|
||||
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
|
||||
lazy-rules
|
||||
@update:model-value="selectLocation()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="col-xs-12 col-sm-6 col-md-4"
|
||||
v-if="model == 'อื่นๆ' && workplace === 'off-site'"
|
||||
>
|
||||
<q-input
|
||||
ref="useLocationRef"
|
||||
dense
|
||||
class="q-ml-md"
|
||||
outlined
|
||||
v-model="useLocation"
|
||||
label="ระบุสถานที่"
|
||||
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
|
||||
lazy-rules
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
<q-card bordered flat class="q-pa-sm">
|
||||
<div class="col-12">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="remark"
|
||||
label="หมายเหตุ"
|
||||
lazy-rules
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
<div class="col-12 text-right">
|
||||
<q-separator />
|
||||
<div class="col-12 q-pa-md">
|
||||
<p
|
||||
:class="
|
||||
$q.screen.gt.xs
|
||||
? 'text-red text-caption '
|
||||
: 'text-red text-caption text-center'
|
||||
"
|
||||
>
|
||||
*หมายเหตุ คลิกลงเวลาเข้างานแล้วระบบจะลงเวลาทันที
|
||||
</p>
|
||||
<q-btn
|
||||
:label="
|
||||
stetusCheckin == true ? 'ลงเวลาเข้างาน' : 'ลงเวลาออกงาน'
|
||||
"
|
||||
:color="img == null ? 'grey-6' : 'primary'"
|
||||
push
|
||||
size="14px"
|
||||
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width'"
|
||||
:disable="camera && img ? false : true"
|
||||
@click="validateForm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="dialogTime" persistent>
|
||||
<q-card class="full-width cardNone">
|
||||
<div :class="getClass(stetusCheckin)">
|
||||
<div class="text-body1 text-center col-12 text-weight-bold">
|
||||
<span v-if="stetusCheckin">ลงเวลาเข้างานของคุณ</span>
|
||||
<span v-else>ลงเวลาออกงานของคุณ</span>
|
||||
</div>
|
||||
</div>
|
||||
<q-card-section class="row col-12 justify-center">
|
||||
<div class="bg-grey-2 rounded-borders q-pa-md col-11">
|
||||
<div class="col-12 text-subtitle1 text-center text-weight-medium">
|
||||
{{ date2Thai(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">{{ timeChickin }}</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">
|
||||
{{ formLocation.lat }} , {{ formLocation.lng }}
|
||||
</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
|
||||
@click="onClickConfirm"
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
<style scoped>
|
||||
.q-card.cardImg:hover {
|
||||
border: 1px solid #02a998 !important;
|
||||
}
|
||||
|
||||
.center-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.card-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 350px; /* Adjust as needed */
|
||||
background: #f6f5f5;
|
||||
}
|
||||
.image-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image-element {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 5px; /* Adjust as needed */
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue