登陆页
登录页
vue
<!--
* @Author: jsopy
* @Date: 2025-01-11 09:10:41
* @LastEditTime: 2025-01-18 07:19:19
* @FilePath: /adminvue/src/pages/login/index.vue
* @Description:
*
-->
<template>
<div>
<div class="login-container">
<el-form
class="login-form"
ref="loginFromRef"
:model="loginForm"
:rules="loginRules"
>
<div class="title-container">
<div class="title">用户登陆</div>
<div class="langselecticon">
<I18n></I18n>
</div>
</div>
<el-form-item prop="username">
<span class="svg-container">
<SvgIcon icon="user" size="16" />
</span>
<el-input
placeholder="username"
name="username"
type="text"
v-model="loginForm.username"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<SvgIcon icon="password" size="16" />
</span>
<el-input
placeholder="password"
name="password"
:type="passwordType"
v-model="loginForm.password"
/>
<span class="show-pwd">
<SvgIcon
:icon="passwordType === 'password' ? 'eye' : 'eye-open'"
size="14"
@click="onChangePwdType"
/>
</span>
</el-form-item>
<el-button
type="primary"
style="width: 100%; margin-bottom: 30px; background-color: cadetblue"
:loading="loading"
@click="handleLogin"
>登陆</el-button
>
<div class="tips">
<p>测试权限账号:</p>
<p>提供三种权限账号:</p>
<p>1. 超级管理员账号: super-admin</p>
<p>2. 管理员账号:admin</p>
<p>3. 测试可配置账号:test</p>
<p>密码统一为:123456</p>
<p>
测试中英文切换
<span> {{ $t("message.hello") }}</span>
</p>
<!--
<p>
<span>{{ test_login_title }}</span>
<el-button @click="switchLanguage('cn')">切换语言</el-button>
</p> -->
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import useLogin from "@/hooks/login/useLogin";
// import usei18n from '@/hooks/login/usei18n';
let {
loginForm,
loginRules,
passwordType,
loading,
loginFromRef,
handleLogin,
onChangePwdType,
} = useLogin();
// let { test_login_title, switchLanguage } = usei18n();
</script>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
$cursor: #fff;
.login-container {
min-height: 100vh;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
:deep(.el-form-item) {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
margin-bottom: 50px;
}
:deep(.el-form-item__error) {
font-size: 14px;
left: 0px;
top: 62px;
}
:deep(.el-input__wrapper) {
background-color: rgba(0, 0, 0, 0);
box-shadow: none;
}
:deep(.el-input) {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
border-radius: 0px;
padding: 5px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
}
}
}
.tips {
font-size: 16px;
line-height: 28px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
display: inline-block;
}
.title-container {
position: relative;
width: 100%;
height: 40px;
margin-bottom: 15px;
.title {
font-size: 26px;
color: $light_gray;
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 40px;
text-align: center;
font-weight: bold;
}
.langselecticon {
position: absolute;
right: 0px;
top: 0px;
width: 48px;
height: 48px;
z-index: 2;
}
:deep(.lang-select) {
position: absolute;
top: 4px;
right: 0;
background-color: white;
font-size: 22px;
padding: 4px;
border-radius: 4px;
cursor: pointer;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>hooks 之 uselogin
封装 rules
hook/login/rules.js
js
export const validatePassword = () => {
return (rule, value, callback) => {
if (value.length < 6) {
callback("密码长度不能小于6位");
} else {
callback();
}
};
};封装 uselogin
hook/login/uselogin.js
js
import { validatePassword } from "./rules";
import { useLoginStore } from "@/stores/login";
const LoginStore = useLoginStore();
const useLogin = () => {
// 数据源
const loginForm = ref({
username: "super-admin",
password: "123456",
});
// 自定义规则
const loginRules = ref({
username: [
{
required: true,
trigger: "blur",
message: "请输入用户名",
},
],
password: [
{
required: true,
trigger: "blur",
validator: validatePassword(),
},
],
});
// 处理密码框文本显示状态
const passwordType = ref("password");
const onChangePwdType = () => {
if (passwordType.value === "password") {
passwordType.value = "text";
} else {
passwordType.value = "password";
}
};
// 登录动作处理
const loading = ref(false);
const loginFromRef = ref(null);
// 登录
const handleLogin = () => {
loginFromRef.value.validate((valid) => {
if (!valid) return;
loading.value = true;
LoginStore.login(loginForm.value);
});
};
return {
loginForm,
loginRules,
passwordType,
loading,
loginFromRef,
handleLogin,
onChangePwdType,
};
};
export default useLogin;封装 store/login
js
import { defineStore } from "pinia";
import { setItem, getItem, removeAllItem } from "@/utils/storage";
import { loginApi, getUserInfoApi } from "@/api/login/login";
import { useRouter } from "vue-router";
import { TOKEN } from "@/constant";
export const useLoginStore = defineStore("login", () => {
const router = useRouter();
// token
const token = ref(getItem(TOKEN) || "");
// 用户信息
const userInfo = ref({});
// 判断用户信息有没有
let hasUserInfo = ref(false);
watch(
userInfo,
(newval, oldval) => {
if (Object.keys(newval).length > 0) {
hasUserInfo.value = true;
} else {
hasUserInfo.value = false;
}
},
{ deep: true, immediate: true }
);
// 设置token
const setToken = (value) => {
token.value = value;
setItem(TOKEN, value);
};
// 设置用户信息
const setUserInfo = (value) => {
userInfo.value = value;
};
// 异步登陆
const login = async (userInfo) => {
const { username, password } = userInfo;
let result = await loginApi({ username, password });
setToken(result.token);
router.push("/profile");
};
// 获取个人信息
const getUserInfo = async () => {
const res = await getUserInfoApi();
setUserInfo(res);
};
// 退出登录
const logout = async () => {
console.log("退出登录");
setToken("");
setUserInfo({});
removeAllItem();
router.push("/login");
};
return {
token,
userInfo,
hasUserInfo,
setToken,
setUserInfo,
login,
getUserInfo,
logout,
};
});封装 utils/storage.js
js
/**
* 存储数据
*/
export const setItem = (key, value) => {
// 将数组、对象类型的数据转化为 JSON 字符串进行存储
if (typeof value === "object") {
value = JSON.stringify(value);
}
window.localStorage.setItem(key, value);
};
/**
* 获取数据
*/
export const getItem = (key) => {
const data = window.localStorage.getItem(key);
try {
return JSON.parse(data);
} catch (err) {
return data;
}
};
/**
* 删除数据
*/
export const removeItem = (key) => {
window.localStorage.removeItem(key);
};
/**
* 删除所有数据
*/
export const removeAllItem = (key) => {
window.localStorage.clear();
};封装 constant 下面的 index.js
- src/constant/index.js
js
// token
export const TOKEN = "token";
// token 时间戳
export const TIME_STAMP = "timeStamp";
// 超时时长(毫秒) 180天
export const TOKEN_TIMEOUT_VALUE = 24 * 3600 * 1000 * 180;
// 国际化
export const LANG = "language";
// 主题色保存的 key
export const MAIN_COLOR = "mainColor";
// 默认色值
export const DEFAULT_COLOR = "#409eff";
// tags
export const TAGS_VIEW = "tagsView";
export const USERINFO = "userInfo";
export const ISCOLLAPSE = "isCollapse";
export const BGCOLORINDEX = "bgcolorIndex";
export const ACTIVETEXTCOLORINDEX = "activeTextColorindex";
export const DEFAULTTEXTCOLORINDEX = "defaultTextColorindex";
export const BGCOLOR = "bgcolor";
export const TEXTCOLOR = "textcolor";
export const ACTIVETEXTCOLOR = "activeTextcolor";