This commit is contained in:
张成
2025-12-22 16:26:59 +08:00
parent aa2d03ee30
commit e17d5610f5
54 changed files with 11735 additions and 3 deletions

361
app/views/LoginPage.vue Normal file
View File

@@ -0,0 +1,361 @@
<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>