feat: 登录流程完成

main
donghao 3 months ago
parent 5cb82722ad
commit fda2cbcb31

@ -137,7 +137,7 @@ html, body, #app {
/* 大屏页面特殊处理 */
.router-view-container {
background: #08104C; /* 深色背景 */
background-color: #08104C; /* 深色背景 */
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: {
keepAlive: 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
* @Date: 2025-03-06 13:51:46
* @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
* @Description:
*/
import { defineStore } from "pinia";
import router, { resetRouter } from "@/router";
import { setLocal, removeLocal} from "@/utils/local";
interface UserState {
token: string | null;
}
@ -18,9 +26,15 @@ export const useUserStore = defineStore("user", {
token: localStorage.getItem("token"),
}),
actions: {
login(token: string) {
login(
token: string,
form: { remember: boolean; username: string; password: string }
) {
this.token = token;
localStorage.setItem("token", token);
setLocal("token", token)
form.remember
? setLocal("userLoginInfo", form)
: removeLocal("userLoginInfo");
},
logout() {
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
* @Date: 2025-03-06 13:56:27
* @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
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="login-container">
<div class="bg-login-title">
</div>
<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>
<el-form :model="form" :rules="rules" ref="formRef" label-width="0" class="login-form">
<el-form-item prop="username">
<el-input v-model="form.username" placeholder="请输入您的账号" prefix-icon="el-icon-user"
class="input-item" />
<div class="input-group">
<img :src="accountIconUrl" alt="账号图标" class="input-icon">
<el-input v-model="form.username" placeholder="请输入您的账号" class="custom-input" />
</div>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" placeholder="请输入登录密码" prefix-icon="el-icon-lock" type="password"
class="input-item" />
<div class="input-group">
<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 class="remember-item">
<el-checkbox v-model="form.remember"></el-checkbox>
@ -38,13 +47,17 @@
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
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 userStore = useUserStore()
const form = reactive({
username: 'admin',
password: 'admin123'
username: '',
password: '',
remember: false
})
const handleLogin = async () => {
@ -59,102 +72,24 @@ const handleLogin = async () => {
body: JSON.stringify(form)
})
const data = await res.json()
if (data.code === 200) {
userStore.login(data.data.token)
userStore.login(data.data.token, form)
router.push('/dashboard')
}
} catch (err) {
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 {
background: linear-gradient(135deg, #1976d2, #42a5f5);
}
}
}
onMounted(() => {
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>

@ -1,162 +1,196 @@
<template>
<div class="login-container">
<div class="login-box">
<div class="title">
>> 欢迎登录 <<
</div>
<el-form
:model="form"
:rules="rules"
ref="formRef"
label-width="0"
class="login-form"
>
<el-form-item prop="username">
<div class="login-container">
<div class="login-box">
<el-form
:model="form"
:rules="rules"
ref="formRef"
class="login-form"
>
<el-form-item prop="username">
<div class="input-group">
<img :src="accountIcon" alt="账号图标" class="input-icon">
<el-input
v-model="form.username"
placeholder="请输入您的账号"
prefix-icon="el-icon-user"
class="input-item"
class="custom-input"
/>
</el-form-item>
<el-form-item prop="password">
</div>
</el-form-item>
<el-form-item prop="password">
<div class="input-group">
<img :src="passwordIcon" alt="密码图标" class="input-icon">
<el-input
v-model="form.password"
placeholder="请输入登录密码"
prefix-icon="el-icon-lock"
type="password"
class="input-item"
type="password"
class="custom-input"
/>
</el-form-item>
<el-form-item class="remember-item">
<el-checkbox v-model="form.remember"></el-checkbox>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="handleLogin"
class="login-btn"
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.remember"></el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin" class="login-btn">
登录
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const formRef = ref<any>();
const form = ref({
username: '',
password: '',
remember: false
});
const rules = ref({
username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import accountIconUrl from '@/assets/icons/account-icon.png';
import passwordIconUrl from '@/assets/icons/password-icon.png';
//
const form = ref({
username: '',
password: '',
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 handleLogin = () => {
formRef.value?.validate((valid) => {
if (valid) {
//
router.push('/data-display');
};
const handleLogin = async () => {
const formRef = getCurrentInstance()?.$refs.formRef as any;
await formRef.validate(async (valid) => {
if (valid) {
try {
const response = await mockLogin(form.value);
if (response.success) {
console.log('登录成功');
//
} else {
ElMessage.error(response.message || '登录失败');
}
} catch (error) {
ElMessage.error('登录请求异常');
}
});
};
</script>
<style scoped lang="scss">
.login-container {
height: 100vh;
background: linear-gradient(45deg, #0a224a, #001233);
}
});
};
</script>
<style scoped lang="scss">
/* 保留之前的样式代码,此处省略重复样式 */
.login-container {
height: 100vh;
background: #002a5c;
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;
}
}
.login-form {
.input-group {
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 {
background: linear-gradient(135deg, #1976d2, #42a5f5);
}
margin-bottom: 20px;
border: 1px solid rgba(58, 145, 255, 0.3);
border-radius: 10px;
overflow: hidden;
.input-icon {
width: 30px;
height: 30px;
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: 50px;
&::placeholder {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
</style>
.login-btn {
width: 100%;
background: linear-gradient(135deg, #42a5f5, #1976d2);
border: none;
box-shadow: 0 4px 12px rgba(58, 145, 255, 0.4);
color: white;
height: 50px;
border-radius: 10px;
font-size: 18px;
&:hover {
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>
Loading…
Cancel
Save