Files
Nilai_Clock_Client/src/views/LoginView.vue
T
sudomarcma 8ecbd44948 feat(安全): 实现反欺诈检测和强制打卡功能
- 添加反欺诈服务,定期检查设备上的黑名单应用
- 检测到黑名单应用时自动强制用户打卡下班
- 在登录后启动反欺诈检查,每5分钟执行一次
- 更新后端API支持强制打卡事件
- 调整Android最低SDK版本至24
- 添加全局事件监听处理强制打卡事件
2025-07-09 18:31:21 +08:00

148 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="flex justify-center items-center mobile-viewport bg-gray-100 safe-top safe-bottom">
<div class="w-full max-w-sm p-8 space-y-3 bg-white rounded-2xl shadow-lg">
<!-- App Logo -->
<div class="flex justify-center">
<ArrowRightOnRectangleIcon class="w-16 h-16 text-blue-600" />
</div>
<!-- Title -->
<h2 class="text-3xl font-extrabold text-center text-gray-900">
{{ t('login') }}
</h2>
<form @submit.prevent="handleLogin" class="space-y-6">
<!-- Username -->
<div>
<label for="username" class="sr-only">{{ t('username') }}</label>
<input type="text" id="username" v-model="username" autocomplete="username"
class="w-full px-4 py-3 text-gray-700 bg-gray-50 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:placeholder="t('username')"
required />
</div>
<!-- Password -->
<div>
<label for="password" class="sr-only">{{ t('password') }}</label>
<input type="password" id="password" v-model="password" autocomplete="current-password"
class="w-full px-4 py-3 text-gray-700 bg-gray-50 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:placeholder="t('password')"
required />
</div>
<!-- Remember Me Checkbox -->
<div class="flex items-center justify-between">
<div class="flex items-center">
<input type="checkbox" id="rememberMe" v-model="rememberMe"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500" />
<label for="rememberMe" class="ml-2 block text-sm text-gray-900">
{{ t('rememberMe') }}
</label>
</div>
</div>
<!-- Submit Button -->
<button type="submit"
class="w-full py-3 text-lg font-semibold text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105 disabled:opacity-60 disabled:cursor-not-allowed shadow-md"
:disabled="loading">
{{ loading ? t('loggingIn') : t('login') }}
</button>
<!-- Error -->
<p v-if="error" class="text-sm text-center text-red-600 mt-4">
{{ t(error) !== error ? t(error) : error }}
</p>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ArrowRightOnRectangleIcon } from '@heroicons/vue/24/outline'
import { authService } from '@/services/authService.js'
import { nativeServicesManager } from '@/services/nativeServicesManager.js'
import { antiSpoofingService } from '@/services/antiSpoofingService.js'
const { t } = useI18n()
const router = useRouter()
const username = ref('')
const password = ref('')
const rememberMe = ref(false)
const error = ref('')
const loading = ref(false)
const handleLogin = async () => {
loading.value = true
error.value = ''
try {
const loginResult = await authService.login(
username.value,
password.value,
rememberMe.value
)
if (loginResult.success) {
console.log('✅ LOGIN SUCCESS:', loginResult)
sessionStorage.setItem('userId', loginResult.userId.toString())
sessionStorage.setItem('userRole', loginResult.userRole)
sessionStorage.setItem('token', loginResult.token)
console.log('✅ SESSION STORAGE SET:', {
userId: sessionStorage.getItem('userId'),
userRole: sessionStorage.getItem('userRole')
})
if (loginResult.userRole === 'worker') {
console.log('🔄 WORKER LOGIN - Starting native services...')
try {
await nativeServicesManager.onUserLogin()
console.log('✅ NATIVE SERVICES STARTED')
// Start anti-spoofing checks after native services are ready
antiSpoofingService.startSecurityChecks();
} catch (serviceError) {
console.error('❌ NATIVE SERVICES FAILED:', serviceError)
}
console.log('🔄 NAVIGATING TO WORKER DASHBOARD...')
try {
await router.push('/worker/dashboard')
console.log('✅ NAVIGATION COMPLETED')
} catch (navError) {
console.error('❌ NAVIGATION FAILED:', navError)
}
} else {
console.log('️ NON-WORKER LOGIN - Worker client app only')
error.value = 'This application is designed for workers only.'
await authService.logout()
}
} else {
if (loginResult.error.includes('Device not authorized')) {
error.value = 'deviceNotAuthorized'
} else if (loginResult.error.includes('Invalid credentials')) {
error.value = 'invalidCredentials'
} else if (loginResult.error.includes('Network error')) {
error.value = 'failedConnection'
} else {
error.value = loginResult.error
}
}
} catch (err) {
console.error('Login error:', err)
error.value = 'failedConnection'
} finally {
loading.value = false
}
}
</script>
<style scoped>
/* All styles are now handled by Tailwind CSS classes in the template. */
</style>