Files
autoAiWorkSys/app/views/LoginPage.vue
张成 e17d5610f5 1
2025-12-22 16:26:59 +08:00

362 lines
8.4 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="page-login">
<div class="login-container">
<div class="login-header">
<h1>用户登录</h1>
<p class="login-subtitle">请输入您的邮箱和密码进行登录</p>
</div>
<div class="login-form">
<Message
v-if="errorMessage"
severity="error"
:closable="false"
class="error-message"
>
{{ errorMessage }}
</Message>
<div class="form-group">
<label class="form-label">邮箱</label>
<InputText
v-model="email"
type="email"
placeholder="请输入邮箱地址"
autocomplete="email"
class="form-input"
@keyup.enter="handleLogin"
/>
</div>
<div class="form-group">
<label class="form-label">密码</label>
<InputText
v-model="password"
type="password"
placeholder="请输入密码"
autocomplete="current-password"
class="form-input"
@keyup.enter="handleLogin"
/>
</div>
<div class="form-options">
<div class="remember-me">
<Checkbox v-model="rememberMe" inputId="remember" binary />
<label for="remember" class="remember-label">记住登录信息</label>
</div>
</div>
<div class="form-actions">
<Button
label="登录"
@click="handleLogin"
:disabled="isLoading || !email || !password"
:loading="isLoading"
class="btn-block"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import { InputText, Password, Button, Checkbox, Message } from '../components/PrimeVue';
export default {
name: 'LoginPage',
components: {
InputText,
Password,
Button,
Checkbox,
Message
},
data() {
return {
email: '',
password: '',
rememberMe: true,
isLoading: false,
errorMessage: ''
};
},
mounted() {
// 从 store 加载保存的邮箱
if (this.$store) {
const savedEmail = this.$store.state.config.email || this.$store.state.auth.email;
if (savedEmail) {
this.email = savedEmail;
}
const rememberMe = this.$store.state.config.rememberMe;
if (rememberMe !== undefined) {
this.rememberMe = rememberMe;
}
}
},
methods: {
/**
* 生成设备ID基于浏览器特征
* @returns {string} 设备ID
*/
generateDeviceId() {
// 尝试从 store 获取已保存的设备ID
if (this.$store) {
let deviceId = this.$store.state.config.deviceId || this.$store.state.auth.deviceId;
if (deviceId) {
return deviceId;
}
}
// 基于浏览器特征生成设备ID
try {
const navigatorInfo = [
navigator.userAgent,
navigator.language,
navigator.platform,
screen.width,
screen.height,
screen.colorDepth,
new Date().getTimezoneOffset()
].join('|');
// 使用简单的哈希算法生成ID
let hash = 0;
for (let i = 0; i < navigatorInfo.length; i++) {
const char = navigatorInfo.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
const deviceId = 'web_' + Math.abs(hash).toString(16).substring(0, 32);
// 保存生成的 device_id 到 store
if (this.$store) {
this.$store.dispatch('config/setDeviceId', deviceId);
this.$store.commit('auth/SET_DEVICE_ID', deviceId);
}
return deviceId;
} catch (error) {
console.error('生成设备ID失败:', error);
// 如果生成失败,使用随机字符串作为后备方案
const fallbackId = 'web_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 15);
if (this.$store) {
this.$store.dispatch('config/setDeviceId', fallbackId);
this.$store.commit('auth/SET_DEVICE_ID', fallbackId);
}
return fallbackId;
}
},
async handleLogin() {
if (!this.email) {
this.errorMessage = '请输入邮箱地址';
return;
}
if (!this.password) {
this.errorMessage = '请输入密码';
return;
}
// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.email)) {
this.errorMessage = '邮箱格式不正确,请输入有效的邮箱地址';
return;
}
this.isLoading = true;
this.errorMessage = '';
try {
// 生成或获取设备ID
const deviceId = this.generateDeviceId();
// 调用 Vuex store 的 login action会自动调用 HTTP 接口)
const result = await this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
deviceId: deviceId
});
if (result.success) {
// 保存记住登录选项到 store
if (this.$store) {
this.$store.dispatch('config/setRememberMe', this.rememberMe);
if (this.rememberMe) {
this.$store.dispatch('config/setEmail', this.email);
}
}
// 等待 store 状态更新完成后再跳转
await this.$nextTick();
// 跳转到控制台App.vue 的 watch 会自动连接 MQTT
this.$router.push('/console');
} else {
this.errorMessage = result.error || '登录失败,请检查邮箱和密码';
this.isLoading = false;
}
} catch (error) {
this.errorMessage = `登录失败: ${error.message}`;
this.isLoading = false;
}
},
setError(message) {
this.errorMessage = message;
this.isLoading = false;
},
reset() {
this.password = '';
this.errorMessage = '';
this.isLoading = false;
}
}
};
</script>
<style lang="less" scoped>
.page-login {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
overflow: auto;
}
.login-container {
background: #fff;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
width: 100%;
max-width: 420px;
padding: 40px;
box-sizing: border-box;
}
.login-header {
text-align: center;
margin-bottom: 30px;
h1 {
font-size: 28px;
font-weight: 600;
color: #333;
margin: 0 0 10px 0;
}
.login-subtitle {
font-size: 14px;
color: #666;
margin: 0;
}
}
.login-form {
.form-group {
margin-bottom: 20px;
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #333;
font-weight: 500;
}
// PrimeVue InputText 样式覆盖
:deep(.p-inputtext) {
width: 100%;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
&:enabled:hover {
border-color: #667eea;
}
&:enabled:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
&::placeholder {
color: #999;
}
}
}
.form-options {
margin-bottom: 24px;
.remember-me {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #666;
.remember-label {
cursor: pointer;
user-select: none;
}
}
}
.form-actions {
:deep(.p-button) {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 500;
transition: transform 0.2s, box-shadow 0.3s;
&:enabled:hover {
background: linear-gradient(135deg, #5568d3 0%, #653a8b 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
&:enabled:active {
transform: translateY(0);
}
}
}
.error-message {
margin-bottom: 20px;
}
}
@media (max-width: 480px) {
.page-login {
padding: 15px;
}
.login-container {
padding: 30px 20px;
}
.login-header h1 {
font-size: 24px;
}
}
</style>