wzclm f22ce91e1a Enhance application structure with new modules and login improvements
- Add new menu sections for Activity Management and User Feedback
- Update router configuration with new route paths
- Refactor login API and user store for better error handling
- Modify login view to improve navigation and error logging
- Update patrol tasks view with more detailed table columns
- Fix import paths for SCSS variables in patrol views
2025-02-22 14:57:43 +08:00

385 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.

<script setup>
import { ref, reactive } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { useUserStore } from "../../stores/user";
import { useSystemLogStore } from "../../stores/systemLog";
import { useRoute } from "vue-router";
import { User, Lock } from "@element-plus/icons-vue";
const router = useRouter();
const userStore = useUserStore();
const systemLogStore = useSystemLogStore();
const route = useRoute();
const loginForm = reactive({
username: "",
password: "",
});
// 表单验证规则
const rules = {
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
};
const loading = ref(false);
const formRef = ref();
const shake = ref(false);
// 获取当前时间
const getCurrentTime = () => {
return new Date()
.toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
.replace(/\//g, "-");
};
const handleLogin = async (formEl) => {
if (!formEl) return;
loading.value = true;
try {
await formEl.validate();
const success = await userStore.login(loginForm.username, loginForm.password);
if (success) {
// 记录登录日志
systemLogStore.addLog({
type: "用户操作",
user: loginForm.username,
action: "登录系统",
ip: "192.168.1.100",
status: "成功",
detail: "用户登录成功",
});
// 显示登录成功提示
ElMessage.success({
message: '登录成功,欢迎回来',
duration: 2000
});
// 等待一下确保状态更新完成
await new Promise(resolve => setTimeout(resolve, 100));
// 获取重定向地址并等待导航完成
const redirect = route.query.redirect;
try {
// 使用replace而不是push这样不会留下历史记录
await router.replace(redirect || "/dashboard");
} catch (navigationError) {
console.error('导航错误:', navigationError);
// 如果导航失败,尝试强制刷新页面
window.location.replace(redirect || "/dashboard");
}
} else {
// 记录失败日志
systemLogStore.addLog({
type: "用户操作",
user: loginForm.username,
action: "登录系统",
ip: "192.168.1.100",
status: "失败",
detail: "用户名或密码错误",
});
ElMessage.error("用户名或密码错误");
}
} catch (error) {
shake.value = true;
setTimeout(() => {
shake.value = false;
}, 500);
} finally {
loading.value = false;
}
};
// 添加输入框获得焦点时的处理函数
const handleFocus = (prop) => {
if (formRef.value) {
// 清除对应字段的验证错误
formRef.value.clearValidate(prop);
}
};
</script>
<template>
<div class="login-container">
<div class="login-bg">
<div class="bg-overlay"></div>
</div>
<div class="login-wrapper">
<div class="login-content">
<div class="login-header">
<h1>智慧湿地管理平台</h1>
<p class="subtitle">科技赋能生态保护 智慧守护绿色家园</p>
</div>
<el-card class="login-card" :class="{ shake }">
<h2>账号登录</h2>
<el-form
ref="formRef"
:model="loginForm"
:rules="rules"
label-width="0"
:validate-on-rule-change="false"
:hide-required-asterisk="true"
>
<el-form-item prop="username" class="form-item">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名"
:prefix-icon="User"
@focus="handleFocus('username')"
/>
</el-form-item>
<el-form-item prop="password" class="form-item">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
:prefix-icon="Lock"
show-password
@focus="handleFocus('password')"
@keyup.enter="handleLogin(formRef)"
/>
</el-form-item>
<el-form-item class="form-item">
<el-button
type="primary"
:loading="loading"
class="login-button"
@click="handleLogin(formRef)"
>
登录
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<div class="login-footer">
<p>Copyright © 2025 智慧湿地管理平台 All Rights Reserved.</p>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@use "../../styles/variables.scss" as *;
.login-container {
height: 100vh;
position: relative;
overflow: hidden;
}
.login-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url("@/assets/images/login/bg.jpg");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
.bg-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.3) 100%);
backdrop-filter: blur(3px);
}
}
.login-wrapper {
position: relative;
z-index: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.login-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
margin-bottom: 60px;
}
.login-header {
text-align: center;
margin-bottom: 40px;
color: white;
h1 {
font-size: 36px;
font-weight: 600;
margin: 0 0 20px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
letter-spacing: 2px;
}
.subtitle {
font-size: 16px;
opacity: 0.9;
margin: 0;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
letter-spacing: 1px;
}
}
.login-card {
width: 400px;
padding: 30px;
border: none;
border-radius: 16px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s;
&:hover {
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.15);
}
h2 {
text-align: center;
margin-bottom: 30px;
color: $text-primary;
font-weight: 500;
font-size: 24px;
}
:deep(.form-item) {
margin-bottom: 24px;
.el-form-item__error {
padding-top: 4px;
font-size: 12px;
opacity: 0;
transition: opacity 0.3s;
}
&.is-error .el-form-item__error {
opacity: 1;
}
&:last-child {
margin-bottom: 0;
}
.el-input__wrapper {
padding: 8px 16px;
border-radius: 8px 8px 4px 4px;
background: #f0f2f5;
box-shadow: none !important;
border: 2px solid transparent;
border-bottom: 2px solid #a3d0ff;
transition: all 0.3s;
&:hover {
background: #e8f1ff;
border-bottom-color: #409eff;
}
&.is-focus {
background: #ffffff;
border-bottom-color: #409eff;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.15) !important;
}
.el-input__prefix {
margin-right: 8px;
color: #606266;
}
.el-input__inner {
color: #303133;
&::placeholder {
color: #606266;
}
}
}
}
.login-button {
width: 100%;
height: 44px;
font-size: 16px;
border-radius: 8px;
font-weight: 500;
letter-spacing: 1px;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
}
&.shake {
animation: shake 0.5s ease-in-out;
}
}
.login-footer {
position: absolute;
left: 0;
right: 0;
bottom: 35px;
padding: 12px;
text-align: center;
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
}
// 响应式设计
@media (max-width: 768px) {
.login-header {
h1 {
font-size: 28px;
}
.subtitle {
font-size: 14px;
}
}
.login-card {
width: 90%;
max-width: 400px;
}
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-4px);
}
20%,
40%,
60%,
80% {
transform: translateX(4px);
}
}
</style>