Files
web-fusion/src/pages/LoginPage.vue
2026-02-05 18:50:40 +08:00

229 lines
11 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="min-h-screen bg-[#F0F4F8] flex items-center justify-center font-sans antialiased relative overflow-hidden transition-colors duration-300">
<!-- Background Shapes -->
<div class="absolute top-[-200px] right-[-200px] w-[800px] h-[800px] rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-pulse"
style="background: radial-gradient(circle, rgba(79, 129, 230, 0.2) 0%, rgba(79, 129, 230, 0) 70%)" />
<div class="absolute bottom-[-100px] left-[-100px] w-[600px] h-[600px] rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-pulse"
style="background: radial-gradient(circle, rgba(236, 72, 153, 0.15) 0%, rgba(236, 72, 153, 0) 70%); animation-duration: 4s" />
<div class="container mx-auto px-4 z-10 relative flex justify-center items-center h-full">
<div
class="bg-white/70 backdrop-blur-xl w-full max-w-5xl rounded-[2rem] overflow-hidden flex flex-col md:flex-row shadow-2xl border border-white/20">
<!-- Left Side: Form -->
<div class="w-full md:w-1/2 p-8 md:p-12 lg:p-16 flex flex-col justify-center">
<!-- Header / Logo -->
<div class="flex justify-center">
<img :src="logo" alt="Logo" class="w-[200px] h-auto" />
</div>
<div class="mb-8">
<h1 class="text-2xl font-bold text-gray-800 mb-2">欢迎回来</h1>
<p class="text-gray-500 text-sm">请输入您的账户信息以登录平台</p>
</div>
<form @submit.prevent="handleSubmit" class="space-y-5">
<!-- 租户号 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">租户号</label>
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<input type="text" v-model="credentials.tenantName" placeholder="请输入租户号"
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-3 transition-colors bg-gray-50 focus:bg-white" />
</div>
</div>
<!-- 账号 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">账号</label>
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<input type="text" v-model="credentials.username" placeholder="请输入账号"
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-3 transition-colors bg-gray-50 focus:bg-white" />
</div>
</div>
<!-- 密码 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">密码</label>
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<input type="password" v-model="credentials.password" placeholder="请输入密码"
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-3 transition-colors bg-gray-50 focus:bg-white" />
</div>
</div>
<!-- 错误提示 -->
<div v-if="error"
class="flex items-center gap-2 text-red-600 text-xs bg-red-50 p-2.5 rounded-lg">
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ error }}
</div>
<!-- 登录按钮 -->
<div class="pt-2">
<button type="submit" :disabled="isLoading"
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-blue-500 to-[#4F81E6] hover:from-blue-600 hover:to-[#3A6BC7] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#4F81E6] transition-all transform hover:scale-[1.01] disabled:opacity-70 disabled:cursor-not-allowed">
<template v-if="isLoading">
<span class="flex items-center gap-2">
<svg class="w-4 h-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
登录中
</span>
</template>
<template v-else>
</template>
</button>
</div>
</form>
<div class="mt-8 text-center">
<span class="text-gray-300 text-xs font-mono">v{{ version }}</span>
</div>
</div>
<!-- Right Side: Illustration -->
<div
class="hidden md:flex w-1/2 bg-blue-50/50 relative items-center justify-center p-12 overflow-hidden">
<!-- Decorative Circle matches login.html style -->
<div
class="absolute w-96 h-96 bg-blue-100 rounded-full blur-3xl opacity-30 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
</div>
<div class="relative z-10 w-full max-w-sm">
<img :src="illustration" alt="Illustration" class="w-full h-auto drop-shadow-xl animate-float"
style="animation: float 6s ease-in-out infinite" />
<div class="text-center mt-8">
<h3 class="text-xl font-bold text-gray-800 mb-2">连接全球创意</h3>
<p class="text-gray-500 text-sm">高效管理您的TikTok矩阵释放无限潜能</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { isElectron, getAppVersion } from '../utils/electronBridge'
import logo from '../assets/logo.png'
import illustration from '../assets/illustration.png'
const emit = defineEmits(['loginSuccess'])
const STORAGE_KEY = 'login_credentials'
const USER_KEY = 'user_data'
const credentials = ref({
tenantName: '',
username: '',
password: '',
})
const isLoading = ref(false)
const error = ref('')
const version = ref('')
onMounted(() => {
// 获取应用版本
getAppVersion().then(v => {
version.value = v
})
// 加载保存的凭据
try {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
const data = JSON.parse(saved)
credentials.value = {
tenantName: data.tenantName || '',
username: data.username || data.userId || '',
password: data.password || '',
}
}
} catch { } // eslint-disable-line no-empty
})
const handleSubmit = async () => {
if (!credentials.value.tenantName || !credentials.value.username || !credentials.value.password) {
error.value = '请填写所有字段'
return
}
isLoading.value = true
error.value = ''
try {
// 保存凭据
localStorage.setItem(STORAGE_KEY, JSON.stringify(credentials.value))
console.log('[LoginPage] 开始登录...', credentials.value)
if (!isElectron()) {
error.value = '非 Electron 环境,无法进行真实登录'
isLoading.value = false
return
}
const result = await window.electronAPI.login({ ...credentials.value })
console.log('[LoginPage] 登录结果:', result)
if (result.success && result.user) {
localStorage.setItem(USER_KEY, JSON.stringify(result.user))
emit('loginSuccess')
} else {
error.value = result.error || '登录失败'
}
} catch (err) {
error.value = err.message || '登录失败'
} finally {
isLoading.value = false
}
}
</script>
<style>
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-15px);
}
100% {
transform: translateY(0px);
}
}
</style>