229 lines
11 KiB
Vue
229 lines
11 KiB
Vue
<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>
|