feat: 登录流程完成

main
donghao 3 months ago
parent 5cb82722ad
commit fda2cbcb31

@ -137,7 +137,7 @@ html, body, #app {
/* 大屏页面特殊处理 */ /* 大屏页面特殊处理 */
.router-view-container { .router-view-container {
background: #08104C; /* 深色背景 */ background-color: #08104C; /* 深色背景 */
color: #fff; color: #fff;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@ -21,7 +21,7 @@ const router = createRouter({
meta: { meta: {
keepAlive: false, keepAlive: false,
requiresAuth: false, requiresAuth: false,
isDashboard: false, isDashboard: true,
}, },
}, },
{ {

@ -1,14 +1,22 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 17:57:05
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-10 13:48:21
* @FilePath: \5G-Loading-Bay-Web\src\stores\user.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
/* /*
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 13:51:46 * @Date: 2025-03-06 13:51:46
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-06 17:45:26 * @LastEditTime: 2025-03-10 13:40:39
* @FilePath: \vite-ai\data-dashboard\src\stores\user.ts * @FilePath: \vite-ai\data-dashboard\src\stores\user.ts
* @Description: * @Description:
*/ */
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import router, { resetRouter } from "@/router"; import router, { resetRouter } from "@/router";
import { setLocal, removeLocal} from "@/utils/local";
interface UserState { interface UserState {
token: string | null; token: string | null;
} }
@ -18,9 +26,15 @@ export const useUserStore = defineStore("user", {
token: localStorage.getItem("token"), token: localStorage.getItem("token"),
}), }),
actions: { actions: {
login(token: string) { login(
token: string,
form: { remember: boolean; username: string; password: string }
) {
this.token = token; this.token = token;
localStorage.setItem("token", token); setLocal("token", token)
form.remember
? setLocal("userLoginInfo", form)
: removeLocal("userLoginInfo");
}, },
logout() { logout() {
this.token = null; this.token = null;

@ -0,0 +1,36 @@
/**
* get localStorage
* @param { String } key
*/
export function getLocal(key: string) {
if (!key) throw new Error("key is empty");
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : null;
}
/**
* set localStorage
* @param { String } key
* @param value
*/
export function setLocal(key: string, value: unknown) {
if (!key) throw new Error("key is empty");
if (!value) return;
return localStorage.setItem(key, JSON.stringify(value));
}
/**
* remove localStorage
* @param { String } key
*/
export function removeLocal(key: string) {
if (!key) throw new Error("key is empty");
return localStorage.removeItem(key);
}
/**
* clear localStorage
*/
export function clearLocal() {
return localStorage.clear();
}

@ -0,0 +1,162 @@
.login-container {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
// background-image: url('https://via.placeholder.com/1920x1080?text=铁路货车背景图');
background-image: url("@/assets/login/big_bg.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
.bg-login-title {
width: 100%;
height: 45px;
background-image: url("@/assets/login/title.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
margin: 0 auto 72px;
}
.login-box {
background-image: url("@/assets/login/login_bg.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
width: 630px;
height: 455px;
position: relative;
padding: 20px 0;
.title {
width: 424px;
height: 70px;
color: #87ceeb;
text-align: center;
font-family: PingFang SC, PingFang SC;
font-weight: bold;
font-size: 28px;
letter-spacing: 2px;
color: #e9f6ff;
position: relative;
background-image: url("@/assets/login/login_title.png");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
display: flex;
gap: 20px;
margin: 0 auto 48px;
.left-arrow,
.right-arrow {
width: 31px;
height: 15px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
.left-arrow {
left: 120px;
background-image: url("@/assets/login/right_arrow.png");
}
.right-arrow {
right: -30px;
background-image: url("@/assets/login/left_arrow.png");
}
}
.login-form {
width: 400px;
margin: 0 auto;
.el-form-item--large {
margin-bottom: 20px;
}
.input-group {
display: flex;
align-items: center;
overflow: hidden;
width: 100%;
background: #03366b;
box-shadow: inset 2px 2px 4px 0px #105890,
inset -2px -2px 4px 0px #105890;
border-radius: 4px;
border: 1px solid #105890;
.el-input__wrapper {
background-color: transparent;
border: none;
box-shadow: none;
padding: 0;
height: 48px;
font-size: 14px;
}
.input-icon {
margin: 0 12px;
width: 20px;
height: 20px;
background: rgba(58, 145, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.custom-input {
.el-input__inner {
background: transparent;
border: none;
color: white;
height: 48px;
&::placeholder {
color: #7b96b2;
}
}
}
}
.remember-item {
margin: 0 0 40px;
.el-form-item--large .el-form-item__content {
line-height: 22px;
}
.el-checkbox.el-checkbox--large {
height: 22px;
}
.el-checkbox__label {
color: #ffffff;
line-height: 22px;
}
.el-checkbox.el-checkbox--large .el-checkbox__inner {
height: 16px;
width: 16px;
}
.el-checkbox__inner:after {
left: 5px;
top: 2px;
}
}
.login-btn {
width: 400px;
height: 48px;
background: linear-gradient(180deg, #2589ff 0%, #46a9ed 100%);
border-radius: 4px;
font-size: 18px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #ffffff;
line-height: 25px;
letter-spacing: 2px;
border-radius: 4px;
&:hover {
background: linear-gradient(135deg, #1976d2, #42a5f5);
}
}
}
}
}

@ -2,24 +2,33 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 13:56:27 * @Date: 2025-03-06 13:56:27
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-07 15:12:33 * @LastEditTime: 2025-03-10 13:50:05
* @FilePath: \vite-ai\data-dashboard\src\views\login\login.vue * @FilePath: \vite-ai\data-dashboard\src\views\login\login.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
--> -->
<template> <template>
<div class="login-container"> <div class="login-container">
<div class="bg-login-title">
</div>
<div class="login-box"> <div class="login-box">
<div class="title"> <div class="flex items-center justify-center title">
欢迎登录 <span class="left-arrow"></span>
<span>欢迎登录</span>
<span class="right-arrow"></span>
</div> </div>
<el-form :model="form" :rules="rules" ref="formRef" label-width="0" class="login-form"> <el-form :model="form" :rules="rules" ref="formRef" label-width="0" class="login-form">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="form.username" placeholder="请输入您的账号" prefix-icon="el-icon-user" <div class="input-group">
class="input-item" /> <img :src="accountIconUrl" alt="账号图标" class="input-icon">
<el-input v-model="form.username" placeholder="请输入您的账号" class="custom-input" />
</div>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input v-model="form.password" placeholder="请输入登录密码" prefix-icon="el-icon-lock" type="password" <div class="input-group">
class="input-item" /> <img :src="passwordIconUrl" alt="密码图标" class="input-icon">
<el-input v-model="form.password" placeholder="请输入登录密码" type="password" class="custom-input" />
</div>
</el-form-item> </el-form-item>
<el-form-item class="remember-item"> <el-form-item class="remember-item">
<el-checkbox v-model="form.remember"></el-checkbox> <el-checkbox v-model="form.remember"></el-checkbox>
@ -38,13 +47,17 @@
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import accountIconUrl from '@/assets/login/account_icon.png';
import passwordIconUrl from '@/assets/login/password_icon.png';
import { getLocal} from "@/utils/local";
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const form = reactive({ const form = reactive({
username: 'admin', username: '',
password: 'admin123' password: '',
remember: false
}) })
const handleLogin = async () => { const handleLogin = async () => {
@ -59,102 +72,24 @@ const handleLogin = async () => {
body: JSON.stringify(form) body: JSON.stringify(form)
}) })
const data = await res.json() const data = await res.json()
if (data.code === 200) { if (data.code === 200) {
userStore.login(data.data.token) userStore.login(data.data.token, form)
router.push('/dashboard') router.push('/dashboard')
} }
} catch (err) { } catch (err) {
ElMessage.error('登录失败') ElMessage.error('登录失败')
} }
} }
</script>
<style scoped lang="scss">
.login-container {
height: 100vh;
// background: linear-gradient(45deg, #0a224a, #001233);
display: flex;
justify-content: center;
align-items: center;
// background-image: url('https://via.placeholder.com/1920x1080?text=');
background-size: cover;
background-position: center;
.login-box {
background: rgba(10, 34, 74, 0.9);
padding: 40px 60px;
border-radius: 20px;
box-shadow: 0 0 30px rgba(58, 145, 255, 0.6);
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 2px solid rgba(58, 145, 255, 0.3);
border-radius: 20px;
}
.title {
color: #87ceeb;
text-align: center;
font-size: 20px;
margin-bottom: 30px;
position: relative;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 2px;
background: #87ceeb;
}
&::before {
left: -30px;
}
&::after {
right: -30px;
}
}
.login-form {
.input-item {
margin-bottom: 20px;
.el-input__inner {
background: rgba(58, 145, 255, 0.1);
border: 1px solid rgba(58, 145, 255, 0.3);
color: white;
&::placeholder {
color: rgba(255, 255, 255, 0.6);
}
}
}
.remember-item {
margin: 15px 0;
}
.login-btn {
width: 100%;
background: linear-gradient(135deg, #42a5f5, #1976d2);
border: none;
box-shadow: 0 4px 12px rgba(58, 145, 255, 0.4);
&:hover { onMounted(() => {
background: linear-gradient(135deg, #1976d2, #42a5f5); const loginInfoCache = getLocal('userLoginInfo')
} if(loginInfoCache) {
} form.username = loginInfoCache.username
form.remember = loginInfoCache.remember
form.password = loginInfoCache.password
} }
} })
} </script>
<style lang="scss">
@import url('./Login.scss');
</style> </style>

@ -1,89 +1,123 @@
<template> <template>
<div class="login-container"> <div class="login-container">
<div class="login-box"> <div class="login-box">
<div class="title">
>> 欢迎登录 <<
</div>
<el-form <el-form
:model="form" :model="form"
:rules="rules" :rules="rules"
ref="formRef" ref="formRef"
label-width="0"
class="login-form" class="login-form"
> >
<el-form-item prop="username"> <el-form-item prop="username">
<div class="input-group">
<img :src="accountIcon" alt="账号图标" class="input-icon">
<el-input <el-input
v-model="form.username" v-model="form.username"
placeholder="请输入您的账号" placeholder="请输入您的账号"
prefix-icon="el-icon-user" class="custom-input"
class="input-item"
/> />
</div>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<div class="input-group">
<img :src="passwordIcon" alt="密码图标" class="input-icon">
<el-input <el-input
v-model="form.password" v-model="form.password"
placeholder="请输入登录密码" placeholder="请输入登录密码"
prefix-icon="el-icon-lock"
type="password" type="password"
class="input-item" class="custom-input"
/> />
</div>
</el-form-item> </el-form-item>
<el-form-item class="remember-item"> <el-form-item>
<el-checkbox v-model="form.remember"></el-checkbox> <el-checkbox v-model="form.remember"></el-checkbox>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button type="primary" @click="handleLogin" class="login-btn">
type="primary"
@click="handleLogin"
class="login-btn"
>
登录 登录
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import accountIconUrl from '@/assets/icons/account-icon.png';
import passwordIconUrl from '@/assets/icons/password-icon.png';
const router = useRouter(); //
const formRef = ref<any>(); const form = ref({
const form = ref({
username: '', username: '',
password: '', password: '',
remember: false remember: false
});
//
const rules = ref({
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 30, message: '长度在 6 到 30 个字符', trigger: 'blur' }
]
});
//
const accountIcon = ref(accountIconUrl);
const passwordIcon = ref(passwordIconUrl);
// mock
const mockLogin = (params: { username: string; password: string }) => {
return new Promise<{ success: boolean; message?: string }>((resolve) => {
// admin
if (params.username === 'admin' && params.password === 'admin') {
resolve({ success: true });
} else {
resolve({
success: false,
message: '账号或密码错误'
}); });
}
const rules = ref({
username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}); });
};
const handleLogin = () => { const handleLogin = async () => {
formRef.value?.validate((valid) => { const formRef = getCurrentInstance()?.$refs.formRef as any;
await formRef.validate(async (valid) => {
if (valid) { if (valid) {
// try {
router.push('/data-display'); const response = await mockLogin(form.value);
if (response.success) {
console.log('登录成功');
//
} else {
ElMessage.error(response.message || '登录失败');
}
} catch (error) {
ElMessage.error('登录请求异常');
}
} }
}); });
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.login-container { /* 保留之前的样式代码,此处省略重复样式 */
.login-container {
height: 100vh; height: 100vh;
background: linear-gradient(45deg, #0a224a, #001233); background: #002a5c;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-image: url('https://via.placeholder.com/1920x1080?text=铁路货车背景图'); background-image: url('https://via.placeholder.com/1920x1080?text=铁路货车背景图');
background-size: cover; background-size: cover;
background-position: center; background-position: center;
}
.login-box { .login-box {
background: rgba(10, 34, 74, 0.9); background: rgba(10, 34, 74, 0.9);
padding: 40px 60px; padding: 40px 60px;
border-radius: 20px; border-radius: 20px;
@ -100,50 +134,37 @@
border: 2px solid rgba(58, 145, 255, 0.3); border: 2px solid rgba(58, 145, 255, 0.3);
border-radius: 20px; border-radius: 20px;
} }
}
.title { .login-form {
color: #87ceeb; .input-group {
text-align: center; display: flex;
font-size: 20px; align-items: center;
margin-bottom: 30px; margin-bottom: 20px;
position: relative; border: 1px solid rgba(58, 145, 255, 0.3);
border-radius: 10px;
&::before, overflow: hidden;
&::after {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 2px;
background: #87ceeb;
}
&::before {
left: -30px;
}
&::after { .input-icon {
right: -30px; width: 30px;
} height: 30px;
background: rgba(58, 145, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
} }
.login-form { .custom-input {
.input-item {
margin-bottom: 20px;
.el-input__inner { .el-input__inner {
background: rgba(58, 145, 255, 0.1); background: transparent;
border: 1px solid rgba(58, 145, 255, 0.3); border: none;
color: white; color: white;
height: 50px;
&::placeholder { &::placeholder {
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
} }
} }
.remember-item {
margin: 15px 0;
} }
.login-btn { .login-btn {
@ -151,12 +172,25 @@
background: linear-gradient(135deg, #42a5f5, #1976d2); background: linear-gradient(135deg, #42a5f5, #1976d2);
border: none; border: none;
box-shadow: 0 4px 12px rgba(58, 145, 255, 0.4); box-shadow: 0 4px 12px rgba(58, 145, 255, 0.4);
color: white;
height: 50px;
border-radius: 10px;
font-size: 18px;
&:hover { &:hover {
background: linear-gradient(135deg, #1976d2, #42a5f5); background: linear-gradient(135deg, #1976d2, #42a5f5);
} }
} }
.el-checkbox {
margin: 15px 0;
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #42a5f5;
border-color: #42a5f5;
} }
.el-checkbox__label {
color: white;
font-size: 16px;
} }
} }
</style> }
</style>
Loading…
Cancel
Save