mcwl-pc/app/components/LoginModal.vue

261 lines
6.8 KiB
Vue

<script setup lang="ts">
import phoneImg from "@/assets/img/phone.png";
import wechatImg from "@/assets/img/wechat.png";
import type { FormInst, FormRules } from "naive-ui";
import { createDiscreteApi } from 'naive-ui';
import { ref } from "vue";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
const { message } = createDiscreteApi(['message'])
// 暴露方法给父组件
defineExpose({
showModal,
});
const rules: FormRules = {
phone: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ pattern: /^1[3-9]\d{9}$/, message: "手机号格式不正确", trigger: "blur" },
],
code: [
{ required: true, message: "请输入验证码", trigger: "blur" },
{ len: 4, message: "验证码长度为4位", trigger: "blur" },
],
};
// 响应式状态
const isVisible = ref(false);
const formRef = ref<FormInst | null>(null)
type LoginMode = "wechat" | "phone";
const currentLoginMode = ref<LoginMode>("phone");
const codeButtonText = ref("获取验证码");
const codeButtonDisabled = ref(false);
const timerCode: Ref<ReturnType<typeof setInterval> | null> = ref(null);
interface UserData {
code:number,
phone:number,
}
interface ApiResponse<T> {
code: number;
msg: string;
token: string;
}
const formData = ref({
});
interface LoginModeDescription {
title: string;
des: string;
src: string;
}
type LoginModeDescriptions = Record<LoginMode, LoginModeDescription>;
// 登录模式配置
const loginModeDescriptions: LoginModeDescriptions = {
phone: {
title: "登录",
des: "如未注册,验证后自动登录",
src: wechatImg,
},
wechat: {
title: "微信一键登录",
des: "关注后自动登录",
src: phoneImg,
},
};
function showModal() {
if (isVisible.value) return;
isVisible.value = true;
}
function onCloseLogin() {
formData.value = {}
isVisible.value = false;
}
//切换登录方式
function toggleMode() {
return currentLoginMode.value === "phone" ? "wechat" : "phone";
}
function setCurrentLoginMode() {
currentLoginMode.value = toggleMode();
}
interface UserToken{
token:string
}
// 登录
async function handleValidateClick(e: MouseEvent) {
e.preventDefault()
formRef.value?.validate(async(errors:any) => {
if (!errors) {
const res = await request.post<ApiResponse<UserData>>('/phoneLogin', {
...formData.value
})
userStore.setToken(res.token)
const res1 = await request.get<ApiResponse<UserToken>>('/getInfo', {
token:res.token
})
userStore.setUserInfo(res1.user)
onCloseLogin()
window.location.href = '/'
}
else {
console.log(errors)
}
})
}
interface codeInterface{
phone:number
}
// 获取验证码
async function onGetCode() {
try {
const response = await request.get<codeInterface>("/getCode", {
params: {
phone:formData.value.phone
}
});
if (response.code === 200) {
codeButtonDisabled.value = true;
let countdown = 60;
codeButtonText.value = `${countdown}秒后重试`;
if (timerCode.value) clearInterval(timerCode.value);
timerCode.value = setInterval(() => {
countdown--;
if (countdown <= 0) {
clearInterval(timerCode.value);
codeButtonDisabled.value = false;
codeButtonText.value = "获取验证码";
} else {
codeButtonText.value = `${countdown}秒后重试`;
}
}, 1000);
} else {
message.error(response.message || "获取验证码失败!");
}
} catch (err) {
console.error(err);
}
}
const onCloseModel = () =>{
formData.value = {}
if (timerCode.value) {
clearInterval(timerCode.value);
}
}
// 组件销毁时清理定时器
onUnmounted(() => {
formData.value = {}
if (timerCode.value) {
clearInterval(timerCode.value);
}
});
</script>
<template>
<NModal
:on-after-leave="onCloseModel"
v-model:show="isVisible"
preset="card"
style="width: 400px"
:maskClosable="false"
>
<div>
<!-- 关闭按钮 -->
<!-- <div
class="center fixed right-5 top-5 z-999 h-5 w-5 cursor-pointer b-white" @click="onCloseLogin(false)"
>
<NIcon size="30" class="text-white">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"><g><g><polygon
points="405,136.798 375.202,107 256,226.202 136.798,107 107,136.798 226.202,256 107,375.202 136.798,405 256,285.798
375.202,405 405,375.202 285.798,256"
/></g></g></svg>
</NIcon>
</div> -->
<!-- 登录表单区域 -->
<div>
<h2 class="text-center text-xl text-gray-800 font-bold">
{{ loginModeDescriptions[currentLoginMode].title }}
</h2>
<p class="text-center text-sm text-gray-500">
{{ loginModeDescriptions[currentLoginMode].des }}
</p>
<!-- 手机号登录表单 -->
<n-form
v-if="currentLoginMode === 'phone'"
ref="formRef"
:model="formData"
:rules="rules"
label-placement="left"
label-width="70px"
size="large"
>
<n-form-item label="手机号" path="phone">
<n-input v-model:value="formData.phone" placeholder="输入手机号" />
</n-form-item>
<n-form-item label="密码" path="code">
<n-input-group>
<n-input v-model:value="formData.code" placeholder="输入密码" />
<n-button
type="primary"
:disabled="codeButtonDisabled"
ghost
@click="onGetCode"
>
{{ codeButtonText }}
</n-button>
</n-input-group>
</n-form-item>
<n-form-item>
<n-button
type="primary"
block
attr-type="button"
@click="handleValidateClick"
>
登录
</n-button>
</n-form-item>
</n-form>
<!-- 微信登录区域 -->
<div v-else>
<div class="flex justify-center">
<WechatLoginQr />
</div>
</div>
</div>
<!-- 其他登录方式 -->
<div class="text-center">
<div class="text-sm text-gray-400">其他登录方式</div>
<div class="mt-2 flex items-center justify-center gap-6">
<div
class="h-10 w-10 cursor-pointer rounded-full p-2 transition-all hover:bg-gray-100"
@click="setCurrentLoginMode"
>
<img
:src="loginModeDescriptions[currentLoginMode].src"
alt=""
class="h-10 w-10"
/>
</div>
</div>
</div>
</div>
</NModal>
</template>
<style scoped></style>