feat: add login system backend
This commit is contained in:
@@ -1 +1 @@
|
|||||||
VITE_SERVER=http://localhost:8080
|
VITE_SERVER=http://localhost:48080
|
||||||
28
src/apis/system/oauth.ts
Normal file
28
src/apis/system/oauth.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { httpWithCustomReturn } from "@/utils/http";
|
||||||
|
|
||||||
|
export const loginByEmailPasswordWithOauthApi = ({ email, password }) => {
|
||||||
|
return httpWithCustomReturn<{
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: {
|
||||||
|
scope: string;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
token_type: string;
|
||||||
|
expires_in: number;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
url: "/admin-api/system/oauth2/token",
|
||||||
|
method: "POST",
|
||||||
|
params: {
|
||||||
|
grant_type: "password",
|
||||||
|
username: email,
|
||||||
|
password,
|
||||||
|
client_id: "rc",
|
||||||
|
client_secret: "rc",
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
"tenant-id": 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
26
src/apis/system/user.ts
Normal file
26
src/apis/system/user.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { httpWithCustomReturn } from "@/utils/http";
|
||||||
|
|
||||||
|
export const getUserInfoApi = () => {
|
||||||
|
return httpWithCustomReturn<{
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
nickname: string;
|
||||||
|
email: string;
|
||||||
|
mobile: string;
|
||||||
|
sex: number;
|
||||||
|
avatar: string;
|
||||||
|
loginIp: string;
|
||||||
|
loginDate: null;
|
||||||
|
createTime: number;
|
||||||
|
roles: object[];
|
||||||
|
dept: null;
|
||||||
|
posts: null;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
url: "/admin-api/system/user/profile/get",
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
17
src/apis/temp/auth-qrcode.ts
Normal file
17
src/apis/temp/auth-qrcode.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { httpWithCustomReturn } from "@/utils/http";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns 直接返回图片二进制数据
|
||||||
|
*/
|
||||||
|
export const getAuthQrcodeApi = () => {
|
||||||
|
return httpWithCustomReturn<Blob>({
|
||||||
|
url: "https://api.qrtool.cn",
|
||||||
|
method: "get",
|
||||||
|
responseType: "blob",
|
||||||
|
params: {
|
||||||
|
text: "测试",
|
||||||
|
size: 600,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import http from "../utils/http";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an example, please remove if not needed
|
|
||||||
*/
|
|
||||||
export const getUserList = () => {
|
|
||||||
return http({
|
|
||||||
url: "/user/list",
|
|
||||||
method: "get",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const insertUser = (data: { account: string; passowrd: string }) => {
|
|
||||||
return http({
|
|
||||||
url: "/user/insert",
|
|
||||||
method: "post",
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { renderAuthDialog } from "@/hooks/dialog/renderAuthDialog";
|
import { renderAuthDialog } from "@/hooks/dialog/renderAuthDialog";
|
||||||
|
import { useUserStore } from "@/stores/UserStore";
|
||||||
|
|
||||||
|
// 用户 store
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const dropdownVisible = ref(true);
|
const dropdownVisible = ref(true);
|
||||||
|
|
||||||
@@ -9,19 +13,39 @@ const closeDropdown = () => {
|
|||||||
dropdownVisible.value = true;
|
dropdownVisible.value = true;
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = () => {
|
||||||
|
userStore.clearUserInfo();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown dropdown-hover dropdown-end">
|
<div class="dropdown dropdown-hover dropdown-end">
|
||||||
<div
|
<div
|
||||||
tabindex="0"
|
v-if="!userStore.checkUserLogin()"
|
||||||
role="button"
|
role="button"
|
||||||
class="btn btn-circle btn-sm btn-ghost px-3 w-16"
|
class="btn btn-circle btn-sm btn-ghost px-3 w-16"
|
||||||
|
@click="
|
||||||
|
renderAuthDialog('login');
|
||||||
|
closeDropdown();
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span class="text-sm text-base-content/70">{{ $t("nav.login") }}</span>
|
<span class="text-sm text-base-content/70">
|
||||||
|
{{ $t("nav.login") }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
role="button"
|
||||||
|
class="text-sm text-base-content/70 px-3 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ userStore.userInfo?.nickname || userStore.userInfo?.username }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 未登录用户下拉框 -->
|
||||||
<ul
|
<ul
|
||||||
v-show="dropdownVisible"
|
v-if="dropdownVisible && !userStore.checkUserLogin()"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class="dropdown-content z-1 w-100 p-2"
|
class="dropdown-content z-1 w-100 p-2"
|
||||||
>
|
>
|
||||||
@@ -53,6 +77,25 @@ const closeDropdown = () => {
|
|||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- 已登录用户下拉框 -->
|
||||||
|
<ul
|
||||||
|
v-if="dropdownVisible && userStore.checkUserLogin()"
|
||||||
|
tabindex="-1"
|
||||||
|
class="dropdown-content z-1 w-100 p-2"
|
||||||
|
>
|
||||||
|
<div class="card bg-base-200 w-96 mt-4 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">退出登录</h2>
|
||||||
|
<p>切换您的账号</p>
|
||||||
|
<div class="card-actions justify-end">
|
||||||
|
<button class="btn btn-primary btn-sm" @click="handleLogout">
|
||||||
|
{{ $t("nav.logout") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<!-- 登录弹窗 -->
|
<!-- 登录弹窗 -->
|
||||||
<dialog id="my_modal" class="modal">
|
<dialog id="my_modal" class="modal">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { loginByEmailPasswordWithOauthApi } from "@/apis/system/oauth";
|
||||||
|
import { getUserInfoApi } from "@/apis/system/user";
|
||||||
|
import { getAuthQrcodeApi } from "@/apis/temp/auth-qrcode";
|
||||||
import { closeAuthDialog } from "@/hooks/dialog/renderAuthDialog";
|
import { closeAuthDialog } from "@/hooks/dialog/renderAuthDialog";
|
||||||
|
import { useUserStore } from "@/stores/UserStore";
|
||||||
import { AnimatePresence, motion } from "motion-v";
|
import { AnimatePresence, motion } from "motion-v";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
@@ -12,10 +16,12 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const authFormType = ref<"login" | "register">(props.defaultAuthFormType);
|
const authFormType = ref<"login" | "register" | "transition">(
|
||||||
|
props.defaultAuthFormType,
|
||||||
|
);
|
||||||
|
|
||||||
const changeAuthFormType = (type: "login" | "register") => {
|
const changeAuthFormType = (type: "login" | "register") => {
|
||||||
authFormType.value = "";
|
authFormType.value = "transition";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
authFormType.value = type;
|
authFormType.value = type;
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -28,6 +34,49 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
closeAuthDialog();
|
closeAuthDialog();
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 登录方式
|
||||||
|
const loginType = ref<"password" | "phone">("password");
|
||||||
|
|
||||||
|
// 登录二维码
|
||||||
|
const authQrcode = ref("");
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const qrImg = await getAuthQrcodeApi();
|
||||||
|
authQrcode.value = URL.createObjectURL(qrImg);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 控制登录表单宽度
|
||||||
|
const loginFormWidth = computed(() => {
|
||||||
|
return authFormType.value === "login" ? "900px" : "550px";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 登录表单数据绑定
|
||||||
|
const loginForm = reactive({
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 登录按钮点击事件
|
||||||
|
const clickLoginButtonEvent = async () => {
|
||||||
|
const { email, password } = loginForm;
|
||||||
|
if (!email || !password) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await loginByEmailPasswordWithOauthApi({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
if (res.code === 0) {
|
||||||
|
const store = useUserStore();
|
||||||
|
store.setAccessToken(res.data.access_token);
|
||||||
|
store.setRefreshToken(res.data.refresh_token);
|
||||||
|
// 进一步获取用户信息
|
||||||
|
const userInfoRes = await getUserInfoApi();
|
||||||
|
store.setUserInfo(userInfoRes.data);
|
||||||
|
closeAuthDialogWaitAnim();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -38,7 +87,7 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
v-show="isNotClose"
|
v-show="isNotClose"
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
:initial="{ opacity: 0, scale: 0 }"
|
:initial="{ opacity: 0, scale: 0 }"
|
||||||
:animate="{ opacity: 1, scale: 1 }"
|
:animate="{ opacity: 1, scale: 1, width: loginFormWidth }"
|
||||||
:transition="{
|
:transition="{
|
||||||
duration: 0.4,
|
duration: 0.4,
|
||||||
scale: { type: 'spring', visualDuration: 0.4, bounce: 0.5 },
|
scale: { type: 'spring', visualDuration: 0.4, bounce: 0.5 },
|
||||||
@@ -77,50 +126,106 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
opacity: 0,
|
opacity: 0,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<h2>
|
<div class="flex items-center gap-16">
|
||||||
{{ t("auth.login") }}
|
<!-- 二维码登录区域 -->
|
||||||
</h2>
|
<div class="flex flex-col items-center gap-4 w-[200px]">
|
||||||
<form action="#">
|
<div class="text-2xl select-none">扫描二维码登录</div>
|
||||||
<div class="input-box">
|
<img
|
||||||
<span class="icon">
|
v-if="authQrcode"
|
||||||
<span class="icon-[material-symbols--mail-outline]" />
|
:src="authQrcode"
|
||||||
</span>
|
alt="登录二维码"
|
||||||
<input type="text" required />
|
class="h-48 w-48"
|
||||||
<label>{{ t("auth.email") }}</label>
|
/>
|
||||||
</div>
|
<div v-if="!authQrcode" class="skeleton h-48 w-48" />
|
||||||
<div class="input-box">
|
<div class="text-sm text-gray-500 select-none">
|
||||||
<span class="icon">
|
请打开手机微信 APP 扫码登录
|
||||||
<span class="icon-[material-symbols--lock-outline]" />
|
</div>
|
||||||
</span>
|
|
||||||
<input type="password" required />
|
|
||||||
<label>{{ t("auth.password") }}</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="remember-forgot">
|
<div class="h-48 w-[1px] bg-gray-300/70" />
|
||||||
<label class="label">
|
|
||||||
<input type="checkbox" class="checkbox checkbox-xs" />
|
<!-- 账号登录区域 -->
|
||||||
{{ t("auth.remember_me") }}
|
<div>
|
||||||
</label>
|
<div class="flex justify-center items-center gap-3">
|
||||||
<a class="label" href="#">{{ t("auth.forget_password") }}</a>
|
<div
|
||||||
|
class="text-2xl cursor-pointer transition duration-300"
|
||||||
|
:class="{ 'text-gray-400': loginType === 'phone' }"
|
||||||
|
:transition="{
|
||||||
|
duration: 0.3,
|
||||||
|
}"
|
||||||
|
@click="loginType = 'password'"
|
||||||
|
>
|
||||||
|
{{ t("auth.login_password") }}
|
||||||
|
</div>
|
||||||
|
<div class="h-8 w-[1px] bg-gray-300/70" />
|
||||||
|
|
||||||
|
<!-- 注册暂未开放 -->
|
||||||
|
<div class="tooltip">
|
||||||
|
<div class="tooltip-content">
|
||||||
|
<div class="font-black">暂未开放</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-2xl cursor-pointer transition duration-300"
|
||||||
|
:class="{ 'text-gray-400': loginType === 'password' }"
|
||||||
|
:transition="{
|
||||||
|
duration: 0.3,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ t("auth.login_phone") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表单区域 -->
|
||||||
|
<form action="#">
|
||||||
|
<div class="input-box">
|
||||||
|
<span class="icon">
|
||||||
|
<span class="icon-[material-symbols--mail-outline]" />
|
||||||
|
</span>
|
||||||
|
<input v-model="loginForm.email" type="text" required />
|
||||||
|
<label>{{ t("auth.email") }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<span class="icon">
|
||||||
|
<span class="icon-[material-symbols--lock-outline]" />
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="loginForm.password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label>{{ t("auth.password") }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="remember-forgot">
|
||||||
|
<label class="label">
|
||||||
|
<input type="checkbox" class="checkbox checkbox-xs" />
|
||||||
|
{{ t("auth.remember_me") }}
|
||||||
|
</label>
|
||||||
|
<a class="label" href="#">{{
|
||||||
|
t("auth.forget_password")
|
||||||
|
}}</a>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn bg-primary text-primary-content"
|
||||||
|
@click="clickLoginButtonEvent"
|
||||||
|
>
|
||||||
|
{{ t("auth.login") }}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="login-register"
|
||||||
|
@click="changeAuthFormType('register')"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{{ t("auth.no_account") }}
|
||||||
|
<a class="register-link cursor-pointer">
|
||||||
|
{{ t("auth.register") }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</div>
|
||||||
type="submit"
|
|
||||||
class="btn bg-primary text-primary-content"
|
|
||||||
>
|
|
||||||
{{ t("auth.login") }}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
class="login-register"
|
|
||||||
@click="changeAuthFormType('register')"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{{ t("auth.no_account") }}
|
|
||||||
<a class="register-link cursor-pointer">
|
|
||||||
{{ t("auth.register") }}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
@@ -146,7 +251,11 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
opacity: 0,
|
opacity: 0,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<h2>{{ t("auth.register") }}</h2>
|
<div class="flex justify-center items-center gap-3">
|
||||||
|
<div class="text-2xl cursor-pointer">
|
||||||
|
{{ t("auth.register") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<form action="#">
|
<form action="#">
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
@@ -197,19 +306,9 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap");
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
|
height: 430px;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 400px;
|
|
||||||
height: 440px;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -222,8 +321,7 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wrapper .form-box {
|
.wrapper .form-box {
|
||||||
width: 100%;
|
padding: 40px 80px;
|
||||||
padding: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper .icon-close {
|
.wrapper .icon-close {
|
||||||
@@ -241,14 +339,9 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-box h2 {
|
|
||||||
font-size: 2em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box {
|
.input-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 350px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-bottom: 2px solid;
|
border-bottom: 2px solid;
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
@@ -314,8 +407,6 @@ const closeAuthDialogWaitAnim = () => {
|
|||||||
outline: none;
|
outline: none;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-register {
|
.login-register {
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ export const en_USMessages: messagesInterface = {
|
|||||||
locale: "Locale",
|
locale: "Locale",
|
||||||
login: "Login",
|
login: "Login",
|
||||||
register: "Register",
|
register: "Register",
|
||||||
|
logout: "Logout",
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: "Login",
|
login: "Login",
|
||||||
|
login_password: "Password Login",
|
||||||
|
login_phone: "Phone Login",
|
||||||
register: "Register",
|
register: "Register",
|
||||||
email: "Email",
|
email: "Email",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
@@ -19,5 +22,6 @@ export const en_USMessages: messagesInterface = {
|
|||||||
no_account: "Don't have an account?",
|
no_account: "Don't have an account?",
|
||||||
agree_terms: "Agree to the Terms & Conditions",
|
agree_terms: "Agree to the Terms & Conditions",
|
||||||
have_account: "Already have an account?",
|
have_account: "Already have an account?",
|
||||||
|
logout: "Logout",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ export interface messagesInterface {
|
|||||||
locale: string;
|
locale: string;
|
||||||
login: string;
|
login: string;
|
||||||
register: string;
|
register: string;
|
||||||
|
logout: string;
|
||||||
};
|
};
|
||||||
auth: {
|
auth: {
|
||||||
login: string;
|
login: string;
|
||||||
|
login_password: string;
|
||||||
|
login_phone: string;
|
||||||
register: string;
|
register: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
@@ -21,6 +24,7 @@ export interface messagesInterface {
|
|||||||
no_account: string;
|
no_account: string;
|
||||||
agree_terms: string;
|
agree_terms: string;
|
||||||
have_account: string;
|
have_account: string;
|
||||||
|
logout: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,20 @@ export const zh_CNMessages: messagesInterface = {
|
|||||||
locale: "语言",
|
locale: "语言",
|
||||||
login: "登录",
|
login: "登录",
|
||||||
register: "注册",
|
register: "注册",
|
||||||
|
logout: "退出登录",
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: "登录",
|
login: "登录",
|
||||||
|
login_password: "密码登录",
|
||||||
|
login_phone: "短信登录",
|
||||||
register: "注册",
|
register: "注册",
|
||||||
email: "邮箱",
|
email: "邮箱",
|
||||||
password: "密码",
|
password: "密码",
|
||||||
remember_me: "记住我",
|
remember_me: "记住我",
|
||||||
forget_password: "忘记密码",
|
forget_password: "忘记密码",
|
||||||
no_account: "没有账号?",
|
no_account: "没有账号?",
|
||||||
agree_terms: "同意条款和条件",
|
agree_terms: "我同意《用户协议》相关条款和条件",
|
||||||
have_account: "已经有账号?",
|
have_account: "已经有账号?",
|
||||||
|
logout: "退出登录",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,89 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
nickname: string;
|
||||||
|
email: string;
|
||||||
|
mobile: string;
|
||||||
|
sex: number;
|
||||||
|
avatar: string;
|
||||||
|
loginIp: string;
|
||||||
|
loginDate: null;
|
||||||
|
createTime: number;
|
||||||
|
roles: object[];
|
||||||
|
dept: null;
|
||||||
|
posts: null;
|
||||||
|
}
|
||||||
|
|
||||||
export const useUserStore = defineStore(
|
export const useUserStore = defineStore(
|
||||||
"user",
|
"user",
|
||||||
() => {
|
() => {
|
||||||
const token = ref("");
|
const accessToken = ref("");
|
||||||
|
const refreshToken = ref("");
|
||||||
|
|
||||||
const setToken = (newToken: string) => {
|
const setAccessToken = (newToken: string) => {
|
||||||
token.value = newToken;
|
accessToken.value = newToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setRefreshToken = (newToken: string) => {
|
||||||
|
refreshToken.value = newToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
const userInfo = ref<UserInfo>({
|
||||||
|
id: 0,
|
||||||
|
username: "",
|
||||||
|
nickname: "",
|
||||||
|
email: "",
|
||||||
|
mobile: "",
|
||||||
|
sex: 0,
|
||||||
|
avatar: "",
|
||||||
|
loginIp: "",
|
||||||
|
loginDate: null,
|
||||||
|
createTime: 0,
|
||||||
|
roles: [],
|
||||||
|
dept: null,
|
||||||
|
posts: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setUserInfo = (newInfo: UserInfo) => {
|
||||||
|
userInfo.value = newInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkUserLogin = () => {
|
||||||
|
return accessToken.value !== "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearUserInfo = () => {
|
||||||
|
accessToken.value = "";
|
||||||
|
refreshToken.value = "";
|
||||||
|
userInfo.value = {
|
||||||
|
id: 0,
|
||||||
|
username: "",
|
||||||
|
nickname: "",
|
||||||
|
email: "",
|
||||||
|
mobile: "",
|
||||||
|
sex: 0,
|
||||||
|
avatar: "",
|
||||||
|
loginIp: "",
|
||||||
|
loginDate: null,
|
||||||
|
createTime: 0,
|
||||||
|
roles: [],
|
||||||
|
dept: null,
|
||||||
|
posts: null,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
accessToken,
|
||||||
setToken,
|
setAccessToken,
|
||||||
|
refreshToken,
|
||||||
|
setRefreshToken,
|
||||||
|
userInfo,
|
||||||
|
setUserInfo,
|
||||||
|
checkUserLogin,
|
||||||
|
clearUserInfo,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
themes: all;
|
themes: all;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "light";
|
||||||
|
--color-primary: blue;
|
||||||
|
--color-secondary: teal;
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom Scrollbar Styles */
|
/* Custom Scrollbar Styles */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|||||||
@@ -5,22 +5,30 @@ const instance = axios.create({
|
|||||||
baseURL: import.meta.env.VITE_SERVER,
|
baseURL: import.meta.env.VITE_SERVER,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 所有请求自动加入 token
|
||||||
instance.interceptors.request.use((config) => {
|
instance.interceptors.request.use((config) => {
|
||||||
const store = useUserStore();
|
const store = useUserStore();
|
||||||
if (!store.token) {
|
if (!store.accessToken) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
const token = store.token;
|
const token = store.accessToken;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
config.headers["token"] = token;
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
const http = async <T>(config: AxiosRequestConfig): Promise<Result<T>> => {
|
export const http = async <T>(
|
||||||
|
config: AxiosRequestConfig,
|
||||||
|
): Promise<Result<T>> => {
|
||||||
const { data } = await instance.request<Result<T>>(config);
|
const { data } = await instance.request<Result<T>>(config);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default http;
|
export const httpWithCustomReturn = async <T>(
|
||||||
|
config: AxiosRequestConfig,
|
||||||
|
): Promise<T> => {
|
||||||
|
const { data } = await instance.request<T>(config);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user