From 33da60ec026ced73776f13b72c5749713efe277a Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 12 Mar 2026 00:51:22 +0700 Subject: [PATCH 01/10] =?UTF-8?q?fix=20disable=20=E0=B8=9B=E0=B8=B8?= =?UTF-8?q?=E0=B9=88=E0=B8=A1=E0=B8=A5=E0=B8=87=E0=B9=80=E0=B8=A7=E0=B8=A5?= =?UTF-8?q?=E0=B8=B2=E0=B8=81=E0=B8=A3=E0=B8=93=E0=B8=B5=E0=B9=80=E0=B8=84?= =?UTF-8?q?=E0=B8=A3=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=87=E0=B8=8A=E0=B9=89?= =?UTF-8?q?=E0=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/HomeView.vue | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 43c14c0..7021e65 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -449,6 +449,8 @@ const objectRef: FormRef = { /** function ตรวจสอบค่าว่างของ input*/ async function validateForm() { + disabledBtn.value = true + const hasError = [] for (const key in objectRef) { if (Object.prototype.hasOwnProperty.call(objectRef, key)) { @@ -480,11 +482,15 @@ async function validateForm() { model.value === 'อื่นๆ' ? useLocation.value : model.value })` } คุณต้องการยืนยันการลงเวลาเข้างาน?`, - () => {}, + () => { + disabledBtn.value = false + }, 'red', 'ยืนยัน' ) } + } else { + disabledBtn.value = false } } @@ -495,10 +501,12 @@ const timeChickin = ref('') //เวลาเข้างาน,เว async function confirm() { // เช็คสิทธิ์ privacy ก่อนใช้งานแผนที่และกล้อง if (!checkPrivacyAccepted()) { + disabledBtn.value = false return } if (!formLocation.POI || !formLocation.lat || !formLocation.lng) { + disabledBtn.value = false mapRef.value?.requestLocationPermission() return } @@ -508,7 +516,6 @@ async function confirm() { return } - disabledBtn.value = true showLoader() const isLocation = workplace.value === 'in-place' //*true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง const locationName = workplace.value === 'in-place' ? '' : useLocation.value @@ -551,6 +558,7 @@ async function confirm() { async function getCheck() { if (!formLocation.POI || !formLocation.lat || !formLocation.lng) { + disabledBtn.value = false mapRef.value?.requestLocationPermission() return } @@ -583,7 +591,9 @@ async function getCheck() { () => confirm(), 'ยืนยันการลงเวลาออกงาน', `เวลาออกจากงานของคุณคือ ${endTimeAfternoonVal} แต่ขณะนี้เป็นเวลา ${timeVal} น. หากคุณออกจากงานในเวลานี้สถานะการลงเวลาจะเป็น "${res.data.result.statusText}" คุณแน่ใจว่าจะลงเวลาออกงานในตอนนี้ใช่หรือไม่?`, - () => {}, + () => { + disabledBtn.value = false + }, 'red', 'ยืนยัน' ) @@ -592,6 +602,7 @@ async function getCheck() { } }) .catch((e) => { + disabledBtn.value = false messageError($q, e) }) .finally(() => { From e2f22cc9c056c5eacabbdc7427fb0888d9ed4b35 Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Wed, 25 Mar 2026 11:06:00 +0700 Subject: [PATCH 02/10] fix --- src/components/DialogDebug.vue | 5 ++++- src/components/FooterContact.vue | 13 +++++++++++++ src/views/MainView.vue | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/components/FooterContact.vue diff --git a/src/components/DialogDebug.vue b/src/components/DialogDebug.vue index 5fa1f3f..ab55a0e 100644 --- a/src/components/DialogDebug.vue +++ b/src/components/DialogDebug.vue @@ -11,6 +11,7 @@ import http from '@/plugins/http' import config from '@/app.config' import DialogHeader from '@/components/DialogHeader.vue' +import FooterContact from '@/components/FooterContact.vue' const $q = useQuasar() const store = usePositionKeycloakStore() @@ -343,7 +344,9 @@ function onClose() { - + + + +
+ + + พบปัญหาการใช้งานกรุณาติดต่อผู้ดูแลระบบ + 088-264-9800 + +
+ + + + + diff --git a/src/views/MainView.vue b/src/views/MainView.vue index eda5edc..e34ab53 100644 --- a/src/views/MainView.vue +++ b/src/views/MainView.vue @@ -17,6 +17,7 @@ import { usePositionKeycloakStore } from '@/stores/positionKeycloak' import DialogHeader from '@/components/DialogHeader.vue' import PopupPrivacy from '@/components/PopupPrivacy.vue' import DialogDebug from '@/components/DialogDebug.vue' +import FooterContact from '@/components/FooterContact.vue' const mixin = useCounterMixin() const privacyStore = usePrivacyStore() @@ -561,6 +562,11 @@ onMounted(async () => { + + + + + From 66a48f38302946708ea52af51dcd4f2da33dd864 Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Wed, 25 Mar 2026 11:58:46 +0700 Subject: [PATCH 03/10] fix:tel --- src/components/FooterContact.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/FooterContact.vue b/src/components/FooterContact.vue index fe21234..025aed3 100644 --- a/src/components/FooterContact.vue +++ b/src/components/FooterContact.vue @@ -3,7 +3,9 @@ พบปัญหาการใช้งานกรุณาติดต่อผู้ดูแลระบบ - 088-264-9800 + 088-264-9800 From fa855b30c84b55f797725f8e3861554f1ef8c07f Mon Sep 17 00:00:00 2001 From: "DESKTOP-1R2VSQH\\Lenovo ThinkPad E490" Date: Wed, 25 Mar 2026 12:12:40 +0700 Subject: [PATCH 04/10] fix:style --- src/components/FooterContact.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/FooterContact.vue b/src/components/FooterContact.vue index 025aed3..4df5d53 100644 --- a/src/components/FooterContact.vue +++ b/src/components/FooterContact.vue @@ -4,7 +4,9 @@ พบปัญหาการใช้งานกรุณาติดต่อผู้ดูแลระบบ 088-264-9800088-264-9800 From f53327e2d9b8d71373783d4f5844082c81763d4d Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 27 Mar 2026 20:57:24 +0700 Subject: [PATCH 05/10] fix on mobile hidden contact --- src/views/MainView.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/views/MainView.vue b/src/views/MainView.vue index e34ab53..c8bc1d8 100644 --- a/src/views/MainView.vue +++ b/src/views/MainView.vue @@ -564,7 +564,10 @@ onMounted(async () => { - + @@ -720,4 +723,8 @@ onMounted(async () => { background-color: #016987; color: #fff; } + +.hidden { + display: none !important; +} From 7edfaa4e4fb50dc12428855d7c329e7b6d9053d9 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 30 Mar 2026 10:47:36 +0700 Subject: [PATCH 06/10] fix remove code check fake location --- src/components/AscGISMap.vue | 354 ++++++++++------------- src/components/DialogDebug.vue | 2 +- src/composables/useLocationValidation.ts | 255 ---------------- src/views/HomeView.vue | 125 +------- 4 files changed, 157 insertions(+), 579 deletions(-) delete mode 100644 src/composables/useLocationValidation.ts diff --git a/src/components/AscGISMap.vue b/src/components/AscGISMap.vue index 7ed48a7..7651b7c 100644 --- a/src/components/AscGISMap.vue +++ b/src/components/AscGISMap.vue @@ -5,51 +5,20 @@ import axios from 'axios' import { useCounterMixin } from '@/stores/mixin' import { useQuasar } from 'quasar' import { usePrivacyStore } from '@/stores/privacy' -import { useLocationValidation } from '@/composables/useLocationValidation' const mixin = useCounterMixin() const { messageError } = mixin const privacyStore = usePrivacyStore() -// import type { LocationObject } from '@/interface/index/Main' -const mapElement = ref(null) -const emit = defineEmits(['update:location', 'locationStatus', 'mockDetected']) +const emit = defineEmits(['update:location']) const $q = useQuasar() -const { validateLocation } = useLocationValidation() -const MOCK_CHECK_DELAY_MS = 800 - -function wait(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -function getCurrentPositionAsync(options?: PositionOptions) { - return new Promise((resolve, reject) => { - navigator.geolocation.getCurrentPosition(resolve, reject, options) - }) -} - -async function getPositionForValidation(fallback: GeolocationPosition) { - await wait(MOCK_CHECK_DELAY_MS) - try { - return await getCurrentPositionAsync({ - enableHighAccuracy: true, - timeout: 10000, - maximumAge: 0, - }) - } catch (error) { - // Keep the original reading if the second sampling fails. - return fallback - } -} - function updateLocation(latitude: number, longitude: number, namePOI: string) { // ส่ง event ไปยัง parent component เพื่ออัพเดทค่า props emit('update:location', latitude, longitude, namePOI) } const poiPlaceName = ref('') // ชื่อพื้นที่ใกล้เคียง -const isMapReady = ref(false) // Replace ArcGIS api key const apiKey = ref( @@ -58,7 +27,7 @@ const apiKey = ref( ) const zoomMap = ref(18) -async function initializeMap(position: GeolocationPosition) { +async function initializeMap() { try { // Load modules of ArcGIS loadModules([ @@ -69,149 +38,159 @@ async function initializeMap(position: GeolocationPosition) { 'esri/Graphic', 'esri/layers/TileLayer', ]).then(async ([esriConfig, Map, MapView, Point, Graphic, TileLayer]) => { + // Set apiKey + // esriConfig.apiKey = + // 'AAPK4f700a4324d04e9f8a1a134e0771ac45FXWawdCl-OotFfr52gz9XKxTDJTpDzw_YYcwbmKDDyAJswf14FoPyw0qBkN64DvP' + + // Create a FeatureLayer using a custom server URL + // const hillshadeLayer = new TileLayer({ + // url: `https://bmagis.bangkok.go.th/arcgis/rest/services/cache/BMA_3D_2D_Cache/MapServer`, + // }) + const map = new Map({ basemap: 'streets', + // basemap: 'arcgis-topographic', + // layers: [hillshadeLayer], }) - const { latitude, longitude } = position.coords + 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 + 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) - }, + 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 - }, - }) - isMapReady.value = true - - // ตำแหน่งของผู้ใช้ - const userPoint = new Point({ longitude, latitude }) - const userSymbol = { - type: 'picture-marker', - url: 'http://maps.google.com/mapfiles/ms/icons/red.png', - width: '32px', - height: '32px', - } - const userGraphic = new Graphic({ - geometry: userPoint, - symbol: userSymbol, - }) - mapView.graphics.add(userGraphic) - // Get POI place ยิงไปขอที่ server ของกทม.ก่อน - await axios - .get( - 'https://bmagis.bangkok.go.th/portal/sharing/servers/e4732c3a9fe549ab8bc697573b468f68/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.location) - poiPlaceName.value = response.data.address - ? response.data.address.PlaceName === '' - ? response.data.address.ShortLabel - : response.data.address.PlaceName - : 'ไม่พบข้อมูล' - const poiPoint = new Point({ - longitude: response.data.location.x, - latitude: response.data.location.y, - }) - const poiSymbol = { - type: 'picture-marker', - url: 'http://maps.google.com/mapfiles/ms/icons/blue.png', - width: '32px', - height: '32px', - } - const poiGraphic = new Graphic({ - geometry: poiPoint, - symbol: poiSymbol, - }) - mapView.graphics.add(poiGraphic) - // อัปเดตการแสดงผลให้แสดงทั้งตำแหน่งของผู้ใช้และ POI - mapView.goTo({ - target: [userPoint, poiPoint], - zoom: zoomMap.value, - }) - - updateLocation(latitude, longitude, poiPlaceName.value) + ui: { + components: [], // Empty array to remove all default UI components + }, }) - .catch(async (error) => { - // console.error('Error fetching points of interest:', error) - // Get POI place ยิงไปขอที่ server arcgis ไม่ต้องใช้ token - await 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, - }, + + // ตำแหน่งของผู้ใช้ + const userPoint = new Point({ longitude, latitude }) + const userSymbol = { + type: 'picture-marker', + url: 'http://maps.google.com/mapfiles/ms/icons/red.png', + width: '32px', + height: '32px', + } + const userGraphic = new Graphic({ + geometry: userPoint, + symbol: userSymbol, + }) + mapView.graphics.add(userGraphic) + // Get POI place ยิงไปขอที่ server ของกทม.ก่อน + await axios + .get( + 'https://bmagis.bangkok.go.th/portal/sharing/servers/e4732c3a9fe549ab8bc697573b468f68/rest/services/World/GeocodeServer/reverseGeocode/', + { + params: { + f: 'json', // Format JSON response + distance: 2000, + category: 'POI', + location: { + spatialReference: { wkid: 4326 }, + x: longitude, + y: latitude, }, - } - ) - .then((response) => { - // console.log('poi', response.data.location) - poiPlaceName.value = response.data.address - ? response.data.address.PlaceName === '' - ? response.data.address.ShortLabel - : response.data.address.PlaceName - : 'ไม่พบข้อมูล' - const poiPoint = new Point({ - longitude: response.data.location.x, - latitude: response.data.location.y, - }) - const poiSymbol = { - type: 'picture-marker', - url: 'http://maps.google.com/mapfiles/ms/icons/blue.png', - width: '32px', - height: '32px', - } - const poiGraphic = new Graphic({ - geometry: poiPoint, - symbol: poiSymbol, - }) - mapView.graphics.add(poiGraphic) - // อัปเดตการแสดงผลให้แสดงทั้งตำแหน่งของผู้ใช้และ POI - mapView.goTo({ - target: [userPoint, poiPoint], - zoom: zoomMap.value, - }) + token: apiKey.value, + }, + } + ) + .then((response) => { + // console.log('poi', response.data.location) + poiPlaceName.value = response.data.address + ? response.data.address.PlaceName === '' + ? response.data.address.ShortLabel + : response.data.address.PlaceName + : 'ไม่พบข้อมูล' + const poiPoint = new Point({ + longitude: response.data.location.x, + latitude: response.data.location.y, + }) + const poiSymbol = { + type: 'picture-marker', + url: 'http://maps.google.com/mapfiles/ms/icons/blue.png', + width: '32px', + height: '32px', + } + const poiGraphic = new Graphic({ + geometry: poiPoint, + symbol: poiSymbol, + }) + mapView.graphics.add(poiGraphic) + // อัปเดตการแสดงผลให้แสดงทั้งตำแหน่งของผู้ใช้และ POI + mapView.goTo({ + target: [userPoint, poiPoint], + zoom: zoomMap.value, + }) - updateLocation(latitude, longitude, poiPlaceName.value) - }) - .catch((error) => { - // Keep map visible even when POI lookup fails. - poiPlaceName.value = 'ไม่พบข้อมูล' - updateLocation(latitude, longitude, poiPlaceName.value) - }) - }) + updateLocation(latitude, longitude, poiPlaceName.value) + }) + .catch(async (error) => { + // console.error('Error fetching points of interest:', error) + // Get POI place ยิงไปขอที่ server arcgis ไม่ต้องใช้ token + await 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, + }, + }, + } + ) + .then((response) => { + // console.log('poi', response.data.location) + poiPlaceName.value = response.data.address + ? response.data.address.PlaceName === '' + ? response.data.address.ShortLabel + : response.data.address.PlaceName + : 'ไม่พบข้อมูล' + const poiPoint = new Point({ + longitude: response.data.location.x, + latitude: response.data.location.y, + }) + const poiSymbol = { + type: 'picture-marker', + url: 'http://maps.google.com/mapfiles/ms/icons/blue.png', + width: '32px', + height: '32px', + } + const poiGraphic = new Graphic({ + geometry: poiPoint, + symbol: poiSymbol, + }) + mapView.graphics.add(poiGraphic) + // อัปเดตการแสดงผลให้แสดงทั้งตำแหน่งของผู้ใช้และ POI + mapView.goTo({ + target: [userPoint, poiPoint], + zoom: zoomMap.value, + }) + + updateLocation(latitude, longitude, poiPlaceName.value) + }) + .catch((error) => { + // console.error('Error fetching points of interest:', error) + }) + }) + }) }) } catch (error) { console.error('Error loading the map', error) @@ -242,35 +221,10 @@ const requestLocationPermission = () => { navigator.geolocation.getCurrentPosition( async (position) => { - const sampledPosition = await getPositionForValidation(position) - - // Validate location first - const validationResult = validateLocation(sampledPosition) - - // Do not block map preview on initial mock detection. - // The hard-stop warning is enforced at submit time in HomeView. - if (validationResult.isMockDetected) { - locationGranted.value = true - emit('locationStatus', true) - } - - // Check for critical errors (invalid coordinates) that prevent showing location - const hasCriticalErrors = validationResult.errors.some((error) => - error.includes('พิกัดตำแหน่งไม่ถูกต้อง') - ) - - if (hasCriticalErrors) { - locationGranted.value = false - emit('locationStatus', false) - messageError($q, '', validationResult.errors[0]) - return - } - - // Permission granted for map preview state. + // Permission granted locationGranted.value = true - emit('locationStatus', true) - const { latitude, longitude } = sampledPosition.coords + const { latitude, longitude } = position.coords // console.log('Current position:', latitude, longitude) if (!latitude || !longitude) { @@ -280,13 +234,12 @@ const requestLocationPermission = () => { // Center map on user's location if map is initialized if (privacyStore.isAccepted) { - await initializeMap(sampledPosition) + await initializeMap() } }, (error) => { // Permission denied locationGranted.value = false - emit('locationStatus', false) switch (error.code) { case error.PERMISSION_DENIED: @@ -311,19 +264,18 @@ const requestLocationPermission = () => { break } }, - { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } + { enableHighAccuracy: true } ) } defineExpose({ requestLocationPermission, - locationGranted, }) diff --git a/src/components/DialogDebug.vue b/src/components/DialogDebug.vue index ab55a0e..fb151e3 100644 --- a/src/components/DialogDebug.vue +++ b/src/components/DialogDebug.vue @@ -176,7 +176,7 @@ function onClose() {