261 lines
6.8 KiB
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>
|