注册功能
This commit is contained in:
@@ -8,5 +8,6 @@
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
28
src/api/register.js
Normal file
28
src/api/register.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// 创建独立的 axios 实例,避免被全局拦截器影响
|
||||
const registerAxios = axios.create({
|
||||
timeout: 30000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
// 注册接口使用 tkNewAdmin 后端
|
||||
const REGISTER_BASE_URL = process.env.NODE_ENV === 'development'
|
||||
? 'http://192.168.2.22:48080'
|
||||
: 'https://backstageapi.yolozs.com'
|
||||
|
||||
/**
|
||||
* 租户注册
|
||||
* 调用 tkNewAdmin 后端的 /admin-api/system/tenant/register 接口
|
||||
*/
|
||||
export function tenantRegister(data) {
|
||||
return registerAxios.post(`${REGISTER_BASE_URL}/admin-api/system/tenant/register`, data)
|
||||
.then(res => {
|
||||
console.log('注册返回', res)
|
||||
if (res.data && res.data.code === 0) {
|
||||
return res.data.data
|
||||
}
|
||||
const msg = (res.data && res.data.msg) || (res.data && res.data.message) || '注册失败'
|
||||
return Promise.reject(new Error(msg))
|
||||
})
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
```
|
||||
<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
|
||||
@@ -40,95 +34,261 @@
|
||||
<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>
|
||||
<!-- ==================== 登录表单 ==================== -->
|
||||
<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>
|
||||
|
||||
<!-- 账号 -->
|
||||
<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>
|
||||
<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>
|
||||
登录中
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
登 录
|
||||
</template>
|
||||
</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>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<div class="mt-8 text-center">
|
||||
<!-- ==================== 注册表单 ==================== -->
|
||||
<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>
|
||||
@@ -156,10 +316,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
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.png'
|
||||
|
||||
@@ -167,31 +329,38 @@ 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 STORAGE_KEY = 'login_credentials' // Deprecated in favor of getUserPass
|
||||
// const USER_KEY = 'user_data' // Deprecated in favor of setUser
|
||||
// 切换登录/注册
|
||||
const switchMode = (target) => {
|
||||
mode.value = target
|
||||
error.value = ''
|
||||
}
|
||||
|
||||
// ==================== 通用状态 ====================
|
||||
const isLoading = ref(false)
|
||||
const error = ref('')
|
||||
const version = ref('')
|
||||
|
||||
// ==================== 登录相关 ====================
|
||||
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) {
|
||||
@@ -214,7 +383,6 @@ const handleSubmit = async () => {
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
// 保存凭据 (Using compatible storage helper)
|
||||
setUserPass(credentials.value)
|
||||
|
||||
console.log('[LoginPage] 开始登录...', credentials.value)
|
||||
@@ -229,11 +397,9 @@ const handleSubmit = async () => {
|
||||
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);
|
||||
|
||||
// 保存权限信息
|
||||
setPermissions({
|
||||
bigBrother: result.user.bigBrother,
|
||||
crawl: result.user.crawl,
|
||||
@@ -250,6 +416,156 @@ const handleSubmit = async () => {
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user