大哥 主播 即时消息 三合一
This commit is contained in:
262
src/pages/LoginPage.vue
Normal file
262
src/pages/LoginPage.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
```
|
||||
<template>
|
||||
<div
|
||||
class="min-h-screen bg-[#F0F4F8] flex items-center justify-center font-sans antialiased relative overflow-hidden transition-colors duration-300">
|
||||
<div class="absolute top-8 right-8 flex gap-4 z-20">
|
||||
<!-- Network Settings (Placeholder/Mock) -->
|
||||
<!-- <div class="bg-white/95 border border-slate-200 rounded-2xl px-3 py-2 shadow-lg cursor-pointer hover:-translate-y-px transition-all flex items-center gap-2">
|
||||
<span class="text-sm font-semibold text-slate-700">{{ $t('login.network') }}</span>
|
||||
</div> -->
|
||||
|
||||
<!-- Language Selector -->
|
||||
<el-dropdown>
|
||||
<div
|
||||
class="bg-white/95 border border-slate-200 rounded-2xl px-4 py-2 shadow-lg cursor-pointer hover:-translate-y-px transition-all flex items-center gap-2">
|
||||
<span class="text-sm font-semibold text-slate-700">{{ locale === 'zh' ? '中文' : 'English' }}</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="switchLanguage('zh')">中文</el-dropdown-item>
|
||||
<el-dropdown-item @click="switchLanguage('en')">English</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- 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 { useI18n } from 'vue-i18n'
|
||||
import { isElectron, getAppVersion } from '../utils/electronBridge'
|
||||
import { setUser, setToken, setUserPass, getUserPass } from '@/utils/storage'
|
||||
import logo from '../assets/logo.png'
|
||||
import illustration from '../assets/illustration.png'
|
||||
|
||||
const emit = defineEmits(['loginSuccess'])
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
// Language Switcher
|
||||
const switchLanguage = (lang) => {
|
||||
locale.value = lang
|
||||
localStorage.setItem('lang', lang)
|
||||
}
|
||||
|
||||
// const STORAGE_KEY = 'login_credentials' // Deprecated in favor of getUserPass
|
||||
// const USER_KEY = 'user_data' // Deprecated in favor of setUser
|
||||
|
||||
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 = getUserPass()
|
||||
if (saved) {
|
||||
credentials.value = {
|
||||
tenantName: saved.tenantName || '',
|
||||
username: saved.username || saved.userId || '',
|
||||
password: saved.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 {
|
||||
// 保存凭据 (Using compatible storage helper)
|
||||
setUserPass(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) {
|
||||
// Save token and user info to localStorage using legacy keys to support ported views
|
||||
setToken(result.user.tokenValue);
|
||||
setUser(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>
|
||||
Reference in New Issue
Block a user