add mock location
This commit is contained in:
parent
386ab6480f
commit
9c68349983
4 changed files with 325 additions and 95 deletions
|
|
@ -5,6 +5,7 @@ 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
|
||||
|
|
@ -12,9 +13,11 @@ const privacyStore = usePrivacyStore()
|
|||
|
||||
// import type { LocationObject } from '@/interface/index/Main'
|
||||
const mapElement = ref<HTMLElement | null>(null)
|
||||
const emit = defineEmits(['update:location'])
|
||||
const emit = defineEmits(['update:location', 'locationStatus', 'mockDetected'])
|
||||
const $q = useQuasar()
|
||||
|
||||
const { validateLocation, showMockWarning } = useLocationValidation()
|
||||
|
||||
function updateLocation(latitude: number, longitude: number, namePOI: string) {
|
||||
// ส่ง event ไปยัง parent component เพื่ออัพเดทค่า props
|
||||
emit('update:location', latitude, longitude, namePOI)
|
||||
|
|
@ -199,7 +202,7 @@ async function initializeMap() {
|
|||
}
|
||||
}
|
||||
|
||||
const locationGranted = ref(false)
|
||||
const locationGranted = ref<boolean>(false)
|
||||
// Function to request location permission
|
||||
const requestLocationPermission = () => {
|
||||
// เช็คสิทธิ์ privacy ก่อนเข้าถึงแผนที่
|
||||
|
|
@ -223,8 +226,30 @@ const requestLocationPermission = () => {
|
|||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
async (position) => {
|
||||
// Permission granted
|
||||
locationGranted.value = true
|
||||
// Validate location first
|
||||
const validationResult = validateLocation(position)
|
||||
|
||||
// Always emit mockDetected event (regardless of result)
|
||||
if (validationResult.isMockDetected) {
|
||||
showMockWarning(validationResult)
|
||||
emit('mockDetected', validationResult)
|
||||
}
|
||||
|
||||
// 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 based on mock detection
|
||||
locationGranted.value = !validationResult.isMockDetected
|
||||
emit('locationStatus', !validationResult.isMockDetected)
|
||||
|
||||
const { latitude, longitude } = position.coords
|
||||
// console.log('Current position:', latitude, longitude)
|
||||
|
|
@ -242,6 +267,7 @@ const requestLocationPermission = () => {
|
|||
(error) => {
|
||||
// Permission denied
|
||||
locationGranted.value = false
|
||||
emit('locationStatus', false)
|
||||
|
||||
switch (error.code) {
|
||||
case error.PERMISSION_DENIED:
|
||||
|
|
@ -272,6 +298,7 @@ const requestLocationPermission = () => {
|
|||
|
||||
defineExpose({
|
||||
requestLocationPermission,
|
||||
locationGranted,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
169
src/composables/useLocationValidation.ts
Normal file
169
src/composables/useLocationValidation.ts
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import { ref } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
|
||||
export interface LocationValidationResult {
|
||||
isValid: boolean
|
||||
isMockDetected: boolean
|
||||
confidence: 'low' | 'medium' | 'high'
|
||||
warnings: string[]
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
export interface PositionSnapshot {
|
||||
latitude: number
|
||||
longitude: number
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
// Configuration constants - exported for documentation and testing purposes
|
||||
export const VALIDATION_CONFIG = {
|
||||
MAX_TIMESTAMP_AGE_MS: 60_000, // 60 seconds - maximum acceptable age of location data
|
||||
MAX_ACCURACY_METERS: 100, // 100 meters - maximum acceptable GPS accuracy
|
||||
MAX_SPEED_MS: 100, // 100 m/s (~360 km/h) - maximum plausible movement speed
|
||||
POSITION_HISTORY_SIZE: 5, // number of positions to keep for pattern detection
|
||||
MOCK_INDICATOR_THRESHOLD: 3, // threshold for mock detection (indicators >= 3 = mock)
|
||||
} as const
|
||||
|
||||
export function useLocationValidation() {
|
||||
const $q = useQuasar()
|
||||
const { messageError } = useCounterMixin()
|
||||
|
||||
// Thai error messages - exported for i18n consistency
|
||||
const errorMessages = {
|
||||
MOCK_DETECTED: 'ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง กรุณาปิดแอปจำลองตำแหน่งและลองใหม่',
|
||||
INVALID_COORDINATES: 'พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่',
|
||||
STALE_TIMESTAMP: 'ข้อมูลตำแหน่งเก่าเกินไป กรุณารับสัญญาณ GPS ใหม่',
|
||||
POOR_ACCURACY: 'ความแม่นยำตำแหน่งต่ำเกินไป กรุณาตรวจสอบการรับสัญญาณ GPS',
|
||||
IMPOSSIBLE_SPEED: 'ตรวจพบการเคลื่อนที่ด้วยความเร็วผิดปกติ อาจเป็นการจำลองตำแหน่ง',
|
||||
}
|
||||
|
||||
const previousPositions = ref<PositionSnapshot[]>([])
|
||||
|
||||
// คำนวณระยะห่างระหว่าง 2 จุด (Haversine formula)
|
||||
const haversineDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {
|
||||
const R = 6371e3 // Earth's radius in meters
|
||||
const φ1 = (lat1 * Math.PI) / 180
|
||||
const φ2 = (lat2 * Math.PI) / 180
|
||||
const Δφ = ((lat2 - lat1) * Math.PI) / 180
|
||||
const Δλ = ((lon2 - lon1) * Math.PI) / 180
|
||||
|
||||
const a =
|
||||
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||||
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||
|
||||
return R * c
|
||||
}
|
||||
|
||||
// ตรวจสอบพิกัดถูกต้อง
|
||||
const validateCoordinates = (lat: number, lon: number): boolean => {
|
||||
return (
|
||||
lat >= -90 && lat <= 90 &&
|
||||
lon >= -180 && lon <= 180 &&
|
||||
!isNaN(lat) && !isNaN(lon) &&
|
||||
!(lat === 0 && lon === 0) // Mock มักใช้ 0,0
|
||||
)
|
||||
}
|
||||
|
||||
// ตรวจสอบความแม่นยำ
|
||||
const validateAccuracy = (accuracy: number | null): boolean => {
|
||||
if (accuracy === null) return true
|
||||
return accuracy <= VALIDATION_CONFIG.MAX_ACCURACY_METERS
|
||||
}
|
||||
|
||||
// ตรวจสอบ Timestamp
|
||||
const validateTimestamp = (timestamp: number): boolean => {
|
||||
const now = Date.now()
|
||||
const age = Math.abs(now - timestamp)
|
||||
return age <= VALIDATION_CONFIG.MAX_TIMESTAMP_AGE_MS
|
||||
}
|
||||
|
||||
// คำนวณความเร็ว
|
||||
const calculateSpeed = (pos1: PositionSnapshot, pos2: PositionSnapshot): number => {
|
||||
const distance = haversineDistance(pos1.latitude, pos1.longitude, pos2.latitude, pos2.longitude)
|
||||
const timeDiff = Math.abs(pos2.timestamp - pos1.timestamp) / 1000 // seconds
|
||||
return timeDiff > 0 ? distance / timeDiff : 0
|
||||
}
|
||||
|
||||
// ตรวจสอบความเร็วปกติ
|
||||
const validateSpeed = (current: PositionSnapshot, previous: PositionSnapshot): boolean => {
|
||||
const speed = calculateSpeed(previous, current)
|
||||
return speed <= VALIDATION_CONFIG.MAX_SPEED_MS
|
||||
}
|
||||
|
||||
// Main validation function
|
||||
const validateLocation = (position: GeolocationPosition): LocationValidationResult => {
|
||||
const warnings: string[] = []
|
||||
const errors: string[] = []
|
||||
let mockIndicators = 0
|
||||
|
||||
const { latitude, longitude, accuracy } = position.coords
|
||||
const { timestamp } = position
|
||||
|
||||
// 1. Coordinate validation
|
||||
if (!validateCoordinates(latitude, longitude)) {
|
||||
errors.push(errorMessages.INVALID_COORDINATES)
|
||||
mockIndicators += 3
|
||||
}
|
||||
|
||||
// 2. Timestamp validation
|
||||
if (!validateTimestamp(timestamp)) {
|
||||
errors.push(errorMessages.STALE_TIMESTAMP)
|
||||
mockIndicators += 2
|
||||
}
|
||||
|
||||
// 3. Accuracy validation
|
||||
if (!validateAccuracy(accuracy)) {
|
||||
warnings.push(errorMessages.POOR_ACCURACY)
|
||||
mockIndicators += 1
|
||||
}
|
||||
|
||||
// 4. Compare with previous positions
|
||||
if (previousPositions.value.length > 0) {
|
||||
const previous = previousPositions.value[previousPositions.value.length - 1]
|
||||
|
||||
if (!validateSpeed({ latitude, longitude, timestamp }, previous)) {
|
||||
errors.push(errorMessages.IMPOSSIBLE_SPEED)
|
||||
mockIndicators += 3
|
||||
}
|
||||
}
|
||||
|
||||
// Store current position
|
||||
previousPositions.value.push({ latitude, longitude, timestamp })
|
||||
if (previousPositions.value.length > VALIDATION_CONFIG.POSITION_HISTORY_SIZE) {
|
||||
previousPositions.value.shift()
|
||||
}
|
||||
|
||||
// Determine result
|
||||
const isMockDetected = mockIndicators >= VALIDATION_CONFIG.MOCK_INDICATOR_THRESHOLD
|
||||
const isValid = errors.length === 0
|
||||
|
||||
let confidence: 'low' | 'medium' | 'high' = 'low'
|
||||
if (mockIndicators >= 5) confidence = 'high'
|
||||
else if (mockIndicators >= 3) confidence = 'medium'
|
||||
|
||||
return {
|
||||
isValid,
|
||||
isMockDetected,
|
||||
confidence,
|
||||
warnings,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
const showMockWarning = (result: LocationValidationResult) => {
|
||||
if (!result.isMockDetected) return
|
||||
messageError($q, null, errorMessages.MOCK_DETECTED)
|
||||
}
|
||||
|
||||
const resetValidation = () => {
|
||||
previousPositions.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
validateLocation,
|
||||
showMockWarning,
|
||||
resetValidation,
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,8 @@ const endTimeAfternoon = ref<string>('12:00:00') //เวลาเช็คเ
|
|||
|
||||
const isLoadingCheckTime = ref<boolean>(false) // ตัวแปรสำหรับการโหลด
|
||||
const disabledBtn = ref<boolean>(false)
|
||||
const locationGranted = ref<boolean>(false)
|
||||
const isMockLocationDetected = ref<boolean>(false)
|
||||
|
||||
/**
|
||||
* fetch เช็คเวลาต้องลงเวลาเข้าหรือออกงาน
|
||||
|
|
@ -113,6 +115,21 @@ async function updateLocation(
|
|||
formLocation.POI = namePOI
|
||||
}
|
||||
|
||||
/**
|
||||
* รับค่าสถานะ location จาก AscGISMap
|
||||
*/
|
||||
function onLocationStatus(status: boolean) {
|
||||
locationGranted.value = status
|
||||
}
|
||||
|
||||
/**
|
||||
* รับค่า mock location detection จาก AscGISMap
|
||||
*/
|
||||
function onMockDetected(result: any) {
|
||||
isMockLocationDetected.value = true
|
||||
disabledBtn.value = true
|
||||
}
|
||||
|
||||
const location = ref<string>('') // พื้นที่ใกล้เคียง
|
||||
const model = ref<string>('') // สถานที่ทำงาน
|
||||
// ตัวเลือกสถานที่ทำงาน
|
||||
|
|
@ -595,6 +612,16 @@ watch(
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => locationGranted.value,
|
||||
(newVal) => {
|
||||
// Removed auto-reset of isMockLocationDetected to prevent
|
||||
// clearing mock detection state when permission is granted.
|
||||
// Mock detection state should only be reset after explicit user action
|
||||
// or after a successful validation without mock indicators.
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -667,6 +694,8 @@ watch(
|
|||
v-if="$q.screen.gt.xs"
|
||||
ref="mapRef"
|
||||
@update:location="updateLocation"
|
||||
@location-status="onLocationStatus"
|
||||
@mock-detected="onMockDetected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -833,7 +862,12 @@ watch(
|
|||
</div>
|
||||
|
||||
<div class="col-12" v-if="$q.screen.xs">
|
||||
<MapCheck ref="mapRef" @update:location="updateLocation" />
|
||||
<MapCheck
|
||||
ref="mapRef"
|
||||
@update:location="updateLocation"
|
||||
@location-status="onLocationStatus"
|
||||
@mock-detected="onMockDetected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- กรอกข้อมูล หน้ามือถือ -->
|
||||
|
|
@ -982,7 +1016,7 @@ watch(
|
|||
push
|
||||
size="18px"
|
||||
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width q-pa-sm'"
|
||||
:disable="disabledBtn ? true : camera && img ? false : true"
|
||||
:disable="disabledBtn || !locationGranted || isMockLocationDetected ? true : camera && img ? false : true"
|
||||
@click="validateForm"
|
||||
:loading="inQueue"
|
||||
/>
|
||||
|
|
@ -1097,7 +1131,7 @@ watch(
|
|||
push
|
||||
size="18px"
|
||||
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width q-pa-sm'"
|
||||
:disable="disabledBtn ? true : camera && img ? false : true"
|
||||
:disable="disabledBtn || !locationGranted || isMockLocationDetected ? true : camera && img ? false : true"
|
||||
@click="validateForm"
|
||||
:loading="inQueue"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
|
|
@ -11,8 +11,8 @@ import { useCounterMixin } from '@/stores/mixin'
|
|||
import { usePrivacyStore } from '@/stores/privacy'
|
||||
import { usePositionKeycloakStore } from '@/stores/positionKeycloak'
|
||||
|
||||
import type { notiType } from '@/interface/index/Main'
|
||||
import type { Noti } from '@/interface/response/Main'
|
||||
// import type { notiType } from '@/interface/index/Main'
|
||||
// import type { Noti } from '@/interface/response/Main'
|
||||
|
||||
import DialogHeader from '@/components/DialogHeader.vue'
|
||||
import PopupPrivacy from '@/components/PopupPrivacy.vue'
|
||||
|
|
@ -22,10 +22,10 @@ const mixin = useCounterMixin()
|
|||
const privacyStore = usePrivacyStore()
|
||||
const positionKeycloakStore = usePositionKeycloakStore()
|
||||
const {
|
||||
date2Thai,
|
||||
// date2Thai,
|
||||
hideLoader,
|
||||
messageError,
|
||||
dialogRemove,
|
||||
// dialogRemove,
|
||||
success,
|
||||
showLoader,
|
||||
} = mixin
|
||||
|
|
@ -40,17 +40,17 @@ const configParam = {
|
|||
|
||||
const modalReset = ref<boolean>(false) //ตัวแปร popup reset password
|
||||
const fullName = ref<string>('') //ชื่อผู้ใช้งาน
|
||||
const notiTrigger = ref<boolean>(false) // เปิด,ปิดรายการ
|
||||
const notiList = ref<notiType[]>([]) // รายการแจ้งเตือน
|
||||
// const notiTrigger = ref<boolean>(false) // เปิด,ปิดรายการ
|
||||
// const notiList = ref<notiType[]>([]) // รายการแจ้งเตือน
|
||||
// const totalNotiList = ref<number>(0) //จำนวนการรายการแจ้งเตือน
|
||||
// const totalNoti = ref<number>(0) //จำนวนการแจ้งเตือน
|
||||
const statusLoad = ref<boolean>(false) // สถานะการโหลด
|
||||
// const statusLoad = ref<boolean>(false) // สถานะการโหลด
|
||||
const modalDebug = ref<boolean>(false) // ตัวแปร popup debug
|
||||
//รูปแบบการแสดงผลของวันที่และเวลา
|
||||
const thaiOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}
|
||||
// const thaiOptions: Intl.DateTimeFormatOptions = {
|
||||
// hour: '2-digit',
|
||||
// minute: '2-digit',
|
||||
// }
|
||||
|
||||
const oldPassWord = ref<string>('')
|
||||
|
||||
|
|
@ -76,63 +76,63 @@ const isPwdReNewOld = ref<boolean>(true)
|
|||
// })
|
||||
// }
|
||||
|
||||
/**
|
||||
* ฟังก์ชั่นดึงข้อมูลรายการแจ้งเตือน
|
||||
* @param index page ที่ต้องการโหลดข้อมูล
|
||||
* @param type DEL คือโหลดหลังลบข้อความ, NOMAL คือกรณีโหลดข้อมูลปกติ
|
||||
*/
|
||||
async function fetchNotifications(index: number, type: string) {
|
||||
await http
|
||||
.get(config.API.msgNotificate + `?page=${index}&pageSize=${20}`)
|
||||
.then((res) => {
|
||||
const response = res.data.result.data
|
||||
totalNotiList.value = res.data.result.total
|
||||
const list: notiType[] = []
|
||||
if (type === 'DEL') {
|
||||
notiList.value = []
|
||||
}
|
||||
response.map((e: Noti) => {
|
||||
list.push({
|
||||
id: e.id,
|
||||
sender:
|
||||
e.createdFullName == '' || e.createdFullName == null
|
||||
? 'เจ้าหน้าที่'[0]
|
||||
: e.createdFullName[0],
|
||||
body: e.body ?? '',
|
||||
timereceive: e.receiveDate,
|
||||
isOpen: e.isOpen,
|
||||
})
|
||||
})
|
||||
notiList.value.push(...list)
|
||||
statusLoad.value = totalNotiList.value === 0 ? true : false
|
||||
})
|
||||
.catch((err) => {
|
||||
messageError($q, err)
|
||||
})
|
||||
}
|
||||
// /**
|
||||
// * ฟังก์ชั่นดึงข้อมูลรายการแจ้งเตือน
|
||||
// * @param index page ที่ต้องการโหลดข้อมูล
|
||||
// * @param type DEL คือโหลดหลังลบข้อความ, NOMAL คือกรณีโหลดข้อมูลปกติ
|
||||
// */
|
||||
// async function fetchNotifications(index: number, type: string) {
|
||||
// await http
|
||||
// .get(config.API.msgNotificate + `?page=${index}&pageSize=${20}`)
|
||||
// .then((res) => {
|
||||
// const response = res.data.result.data
|
||||
// totalNotiList.value = res.data.result.total
|
||||
// const list: notiType[] = []
|
||||
// if (type === 'DEL') {
|
||||
// notiList.value = []
|
||||
// }
|
||||
// response.map((e: Noti) => {
|
||||
// list.push({
|
||||
// id: e.id,
|
||||
// sender:
|
||||
// e.createdFullName == '' || e.createdFullName == null
|
||||
// ? 'เจ้าหน้าที่'[0]
|
||||
// : e.createdFullName[0],
|
||||
// body: e.body ?? '',
|
||||
// timereceive: e.receiveDate,
|
||||
// isOpen: e.isOpen,
|
||||
// })
|
||||
// })
|
||||
// notiList.value.push(...list)
|
||||
// statusLoad.value = totalNotiList.value === 0 ? true : false
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// messageError($q, err)
|
||||
// })
|
||||
// }
|
||||
|
||||
/**
|
||||
* function ลบรายการแจ้งเตือน
|
||||
* @param id noti
|
||||
*/
|
||||
async function onClickDelete(id: string, index: number) {
|
||||
dialogRemove($q, async () => {
|
||||
await http
|
||||
.delete(config.API.msgId(id))
|
||||
.then(async () => {
|
||||
notiList.value.splice(index, 1)
|
||||
totalNotiList.value--
|
||||
notiList.value.length === 14 && (await fetchNotifications(1, 'DEL'))
|
||||
success($q, 'ลบข้อมูลสำเร็จ')
|
||||
})
|
||||
.catch((e) => {
|
||||
messageError($q, e)
|
||||
})
|
||||
.finally(async () => {
|
||||
hideLoader()
|
||||
})
|
||||
})
|
||||
}
|
||||
// /**
|
||||
// * function ลบรายการแจ้งเตือน
|
||||
// * @param id noti
|
||||
// */
|
||||
// async function onClickDelete(id: string, index: number) {
|
||||
// dialogRemove($q, async () => {
|
||||
// await http
|
||||
// .delete(config.API.msgId(id))
|
||||
// .then(async () => {
|
||||
// notiList.value.splice(index, 1)
|
||||
// totalNotiList.value--
|
||||
// notiList.value.length === 14 && (await fetchNotifications(1, 'DEL'))
|
||||
// success($q, 'ลบข้อมูลสำเร็จ')
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// messageError($q, e)
|
||||
// })
|
||||
// .finally(async () => {
|
||||
// hideLoader()
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
/*** function logout */
|
||||
function onClickLogout() {
|
||||
|
|
@ -148,25 +148,25 @@ function onClickLogout() {
|
|||
})
|
||||
}
|
||||
|
||||
const page = ref<number>(0)
|
||||
// const page = ref<number>(0)
|
||||
|
||||
/**
|
||||
* โหลดรายการแจ้งเตือนเพิ่มเมื่อ scroll
|
||||
* @param index
|
||||
* @param done
|
||||
*/
|
||||
function onLoad(index: number, done: Function) {
|
||||
if (
|
||||
notiList.value.length < totalNotiList.value ||
|
||||
(notiList.value.length == 0 && totalNotiList.value === 0)
|
||||
) {
|
||||
page.value++
|
||||
setTimeout(async () => {
|
||||
await fetchNotifications(page.value, 'NOMAL')
|
||||
await done()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * โหลดรายการแจ้งเตือนเพิ่มเมื่อ scroll
|
||||
// * @param index
|
||||
// * @param done
|
||||
// */
|
||||
// function onLoad(index: number, done: Function) {
|
||||
// if (
|
||||
// notiList.value.length < totalNotiList.value ||
|
||||
// (notiList.value.length == 0 && totalNotiList.value === 0)
|
||||
// ) {
|
||||
// page.value++
|
||||
// setTimeout(async () => {
|
||||
// await fetchNotifications(page.value, 'NOMAL')
|
||||
// await done()
|
||||
// }, 1500)
|
||||
// }
|
||||
// }
|
||||
|
||||
// landing page redirect
|
||||
const landingPageUrl = ref<string>(configParam.landingPageUrl)
|
||||
|
|
@ -247,7 +247,7 @@ async function onSubmit() {
|
|||
})
|
||||
}
|
||||
|
||||
function ruleNewPassWord(val: string) {
|
||||
function ruleNewPassWord(val: string): Promise<string | boolean> {
|
||||
return new Promise((resolve) => {
|
||||
if (!val) {
|
||||
return resolve('กรุณากรอกรหัสผ่านใหม่')
|
||||
|
|
@ -268,7 +268,7 @@ function ruleNewPassWord(val: string) {
|
|||
})
|
||||
}
|
||||
|
||||
function ruleReNewPassWord(val: string) {
|
||||
function ruleReNewPassWord(val: string): Promise<string | boolean> {
|
||||
return new Promise((resolve) => {
|
||||
if (!val) {
|
||||
return resolve('กรุณายืนยันรหัสผ่านใหม่')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue