585 lines
30 KiB
Vue
585 lines
30 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">
|
||
<div class="absolute top-8 right-8 flex gap-4 z-20">
|
||
<!-- 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 v-show="mode === 'login'" class="flex justify-center">
|
||
<img :src="logo" alt="Logo" class="w-[200px] h-auto" />
|
||
</div>
|
||
|
||
<!-- ==================== 登录表单 ==================== -->
|
||
<template v-if="mode === 'login'">
|
||
<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-6 text-center">
|
||
<span class="text-gray-500 text-sm">还没有账号?</span>
|
||
<button @click="switchMode('register')"
|
||
class="text-[#4F81E6] text-sm font-medium hover:text-blue-700 ml-1 transition-colors">
|
||
立即注册
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ==================== 注册表单 ==================== -->
|
||
<template v-else>
|
||
<div class="mb-6">
|
||
<h1 class="text-2xl font-bold text-gray-800 mb-2">注册新账号</h1>
|
||
<p class="text-gray-500 text-sm">填写以下信息创建您的租户账号</p>
|
||
</div>
|
||
|
||
<form @submit.prevent="handleRegister" class="space-y-4">
|
||
<!-- 租户名称 -->
|
||
<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="registerForm.name" placeholder="2-20个字符"
|
||
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-2.5 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="registerForm.contactName" placeholder="请输入联系人姓名"
|
||
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-2.5 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 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||
</svg>
|
||
</div>
|
||
<input type="tel" v-model="registerForm.contactMobile" placeholder="请输入手机号码"
|
||
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-2.5 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="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||
</svg>
|
||
</div>
|
||
<input type="text" v-model="registerForm.username" placeholder="4-30个字符"
|
||
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-2.5 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="registerForm.password" placeholder="5-20个字符"
|
||
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-2.5 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="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||
</svg>
|
||
</div>
|
||
<input type="password" v-model="registerForm.confirmPassword" placeholder="请再次输入密码"
|
||
class="focus:ring-[#4F81E6] focus:border-[#4F81E6] block w-full pl-10 sm:text-sm border-gray-300 rounded-lg py-2.5 transition-colors bg-gray-50 focus:bg-white" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Turnstile 人机验证 -->
|
||
<div class="flex justify-center">
|
||
<div id="turnstile-container"></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-1">
|
||
<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-4 text-center">
|
||
<span class="text-gray-500 text-sm">已有账号?</span>
|
||
<button @click="switchMode('login')"
|
||
class="text-[#4F81E6] text-sm font-medium hover:text-blue-700 ml-1 transition-colors">
|
||
返回登录
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="mt-6 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, watch, nextTick } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { ElMessage } from 'element-plus'
|
||
import { isElectron, getAppVersion } from '../utils/electronBridge'
|
||
import { setUser, setToken, setUserPass, getUserPass, setPermissions } from '@/utils/storage'
|
||
import { tenantRegister } from '@/api/register'
|
||
import logo from '../assets/logo.png'
|
||
import illustration from '../assets/illustration.webp'
|
||
|
||
const emit = defineEmits(['loginSuccess'])
|
||
|
||
const { locale } = useI18n()
|
||
|
||
// 当前模式:login / register
|
||
const mode = ref('login')
|
||
|
||
// Language Switcher
|
||
const switchLanguage = (lang) => {
|
||
locale.value = lang
|
||
localStorage.setItem('lang', lang)
|
||
}
|
||
|
||
// 切换登录/注册
|
||
const switchMode = (target) => {
|
||
mode.value = target
|
||
error.value = ''
|
||
}
|
||
|
||
// ==================== 通用状态 ====================
|
||
const isLoading = ref(false)
|
||
const error = ref('')
|
||
const version = ref('')
|
||
|
||
// ==================== 登录相关 ====================
|
||
const credentials = ref({
|
||
tenantName: '',
|
||
username: '',
|
||
password: '',
|
||
})
|
||
|
||
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 {
|
||
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) {
|
||
setToken(result.user.tokenValue);
|
||
setUser(result.user);
|
||
|
||
setPermissions({
|
||
bigBrother: result.user.bigBrother,
|
||
crawl: result.user.crawl,
|
||
webAi: result.user.webAi,
|
||
});
|
||
|
||
emit('loginSuccess')
|
||
} else {
|
||
error.value = result.error || '登录失败'
|
||
}
|
||
} catch (err) {
|
||
error.value = err.message || '登录失败'
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// ==================== 注册相关 ====================
|
||
const registerForm = ref({
|
||
name: '',
|
||
contactName: '',
|
||
contactMobile: '',
|
||
username: '',
|
||
password: '',
|
||
confirmPassword: '',
|
||
})
|
||
|
||
// Turnstile 人机验证
|
||
const TURNSTILE_SITE_KEY = '0x4AAAAAACYSAf0bQMQ347Pz'
|
||
const turnstileToken = ref('')
|
||
const turnstileWidgetId = ref('')
|
||
|
||
const initTurnstile = () => {
|
||
const waitForTurnstile = (retries = 0) => {
|
||
if (retries > 50) {
|
||
console.error('[Turnstile] SDK 加载超时')
|
||
return
|
||
}
|
||
if (window.turnstile && !turnstileWidgetId.value) {
|
||
const container = document.getElementById('turnstile-container')
|
||
if (!container) return
|
||
|
||
try {
|
||
turnstileWidgetId.value = window.turnstile.render('#turnstile-container', {
|
||
sitekey: TURNSTILE_SITE_KEY,
|
||
theme: 'light',
|
||
callback: (token) => {
|
||
turnstileToken.value = token
|
||
},
|
||
'error-callback': () => {
|
||
turnstileToken.value = ''
|
||
},
|
||
'expired-callback': () => {
|
||
turnstileToken.value = ''
|
||
},
|
||
})
|
||
} catch (err) {
|
||
console.error('[Turnstile] 渲染错误:', err)
|
||
}
|
||
} else if (!window.turnstile) {
|
||
setTimeout(() => waitForTurnstile(retries + 1), 100)
|
||
}
|
||
}
|
||
waitForTurnstile()
|
||
}
|
||
|
||
const resetTurnstile = () => {
|
||
if (window.turnstile && turnstileWidgetId.value) {
|
||
window.turnstile.reset(turnstileWidgetId.value)
|
||
turnstileToken.value = ''
|
||
}
|
||
}
|
||
|
||
const destroyTurnstile = () => {
|
||
if (window.turnstile && turnstileWidgetId.value) {
|
||
window.turnstile.remove(turnstileWidgetId.value)
|
||
turnstileWidgetId.value = ''
|
||
turnstileToken.value = ''
|
||
}
|
||
}
|
||
|
||
// 切换到注册页时初始化 Turnstile,切回登录时销毁
|
||
watch(mode, (newMode) => {
|
||
if (newMode === 'register') {
|
||
nextTick(() => initTurnstile())
|
||
} else {
|
||
destroyTurnstile()
|
||
}
|
||
})
|
||
|
||
const handleRegister = async () => {
|
||
const form = registerForm.value
|
||
|
||
// 表单校验
|
||
if (!form.name || !form.contactName || !form.contactMobile || !form.username || !form.password || !form.confirmPassword) {
|
||
error.value = '请填写所有字段'
|
||
return
|
||
}
|
||
if (form.name.length < 2 || form.name.length > 20) {
|
||
error.value = '租户名称长度必须介于 2 和 20 之间'
|
||
return
|
||
}
|
||
if (!/^1[3-9]\d{9}$/.test(form.contactMobile)) {
|
||
error.value = '请输入正确的手机号码'
|
||
return
|
||
}
|
||
if (form.username.length < 4 || form.username.length > 30) {
|
||
error.value = '账号长度必须介于 4 和 30 之间'
|
||
return
|
||
}
|
||
if (form.password.length < 5 || form.password.length > 20) {
|
||
error.value = '密码长度必须介于 5 和 20 之间'
|
||
return
|
||
}
|
||
if (/[<>"'|\\]/.test(form.password)) {
|
||
error.value = '密码不能包含非法字符:< > " \' \\ |'
|
||
return
|
||
}
|
||
if (form.password !== form.confirmPassword) {
|
||
error.value = '两次输入的密码不一致'
|
||
return
|
||
}
|
||
if (!turnstileToken.value) {
|
||
error.value = '请完成人机验证'
|
||
return
|
||
}
|
||
|
||
isLoading.value = true
|
||
error.value = ''
|
||
|
||
try {
|
||
await tenantRegister({
|
||
name: form.name,
|
||
contactName: form.contactName,
|
||
contactMobile: form.contactMobile,
|
||
username: form.username,
|
||
password: form.password,
|
||
turnstileToken: turnstileToken.value,
|
||
})
|
||
|
||
ElMessage.success('注册成功,请登录')
|
||
|
||
// 将租户名和账号回填到登录表单
|
||
credentials.value.tenantName = form.name
|
||
credentials.value.username = form.username
|
||
credentials.value.password = ''
|
||
|
||
// 清空注册表单
|
||
registerForm.value = {
|
||
name: '',
|
||
contactName: '',
|
||
contactMobile: '',
|
||
username: '',
|
||
password: '',
|
||
confirmPassword: '',
|
||
}
|
||
|
||
// 切换回登录
|
||
mode.value = 'login'
|
||
} catch (err) {
|
||
error.value = err.message || '注册失败,请稍后重试'
|
||
resetTurnstile()
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
@keyframes float {
|
||
0% {
|
||
transform: translateY(0px);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-15px);
|
||
}
|
||
|
||
100% {
|
||
transform: translateY(0px);
|
||
}
|
||
}
|
||
</style>
|