302 lines
6.1 KiB
Vue
302 lines
6.1 KiB
Vue
<script setup>
|
|
import { ref } from "vue";
|
|
import { login, register } from "@/api/auth";
|
|
import { useUserStore } from "@/store/user";
|
|
|
|
const userStore = useUserStore();
|
|
|
|
const isRegister = ref(false);
|
|
const activeTab = ref("phone");
|
|
const form = ref({
|
|
phone: "",
|
|
email: "",
|
|
password: "",
|
|
nickname: "",
|
|
});
|
|
const loading = ref(false);
|
|
|
|
const validate = () => {
|
|
const account =
|
|
activeTab.value === "phone" ? form.value.phone : form.value.email;
|
|
if (!account) {
|
|
uni.showToast({
|
|
title: activeTab.value === "phone" ? "请输入手机号" : "请输入邮箱",
|
|
icon: "none",
|
|
});
|
|
return false;
|
|
}
|
|
if (!form.value.password) {
|
|
uni.showToast({ title: "请输入密码", icon: "none" });
|
|
return false;
|
|
}
|
|
if (form.value.password.length < 6) {
|
|
uni.showToast({ title: "密码至少6位", icon: "none" });
|
|
return false;
|
|
}
|
|
if (isRegister.value && !form.value.nickname) {
|
|
uni.showToast({ title: "请输入昵称", icon: "none" });
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!validate()) return;
|
|
loading.value = true;
|
|
|
|
try {
|
|
let res;
|
|
if (isRegister.value) {
|
|
res = await register({
|
|
password: form.value.password,
|
|
nickname: form.value.nickname,
|
|
...(activeTab.value === "phone"
|
|
? { phone: form.value.phone }
|
|
: { email: form.value.email }),
|
|
});
|
|
} else {
|
|
const account =
|
|
activeTab.value === "phone" ? form.value.phone : form.value.email;
|
|
res = await login({ account, password: form.value.password });
|
|
}
|
|
|
|
userStore.setTokens(res.access_token, res.refresh_token);
|
|
await userStore.fetchUserInfo();
|
|
uni.showToast({
|
|
title: isRegister.value ? "注册成功" : "登录成功",
|
|
icon: "success",
|
|
});
|
|
setTimeout(() => {
|
|
uni.switchTab({ url: "/pages/index/index" });
|
|
}, 500);
|
|
} catch (e) {
|
|
console.error(e);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const toggleMode = () => {
|
|
isRegister.value = !isRegister.value;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<view class="login-page">
|
|
<view class="header">
|
|
<view class="logo-circle">
|
|
<text class="logo-text">取</text>
|
|
</view>
|
|
<text class="app-title">次元取景器</text>
|
|
<text class="app-subtitle">发现最美二次元取景地</text>
|
|
</view>
|
|
|
|
<view class="card">
|
|
<view class="tabs">
|
|
<view
|
|
class="tab-item"
|
|
:class="{ active: activeTab === 'phone' }"
|
|
@tap="activeTab = 'phone'"
|
|
>
|
|
手机号登录
|
|
</view>
|
|
<view
|
|
class="tab-item"
|
|
:class="{ active: activeTab === 'email' }"
|
|
@tap="activeTab = 'email'"
|
|
>
|
|
邮箱登录
|
|
</view>
|
|
</view>
|
|
|
|
<view class="form">
|
|
<view v-if="isRegister" class="field">
|
|
<input
|
|
v-model="form.nickname"
|
|
class="input"
|
|
placeholder="请输入昵称"
|
|
placeholder-class="placeholder"
|
|
/>
|
|
</view>
|
|
|
|
<view v-if="activeTab === 'phone'" class="field">
|
|
<input
|
|
v-model="form.phone"
|
|
class="input"
|
|
type="number"
|
|
maxlength="11"
|
|
placeholder="请输入手机号"
|
|
placeholder-class="placeholder"
|
|
/>
|
|
</view>
|
|
<view v-else class="field">
|
|
<input
|
|
v-model="form.email"
|
|
class="input"
|
|
placeholder="请输入邮箱"
|
|
placeholder-class="placeholder"
|
|
/>
|
|
</view>
|
|
|
|
<view class="field">
|
|
<input
|
|
v-model="form.password"
|
|
class="input"
|
|
password
|
|
placeholder="请输入密码"
|
|
placeholder-class="placeholder"
|
|
/>
|
|
</view>
|
|
|
|
<button class="submit-btn" :loading="loading" @tap="handleSubmit">
|
|
{{ isRegister ? "注册" : "登录" }}
|
|
</button>
|
|
</view>
|
|
|
|
<view class="toggle" @tap="toggleMode">
|
|
<text class="toggle-text">
|
|
{{ isRegister ? "已有账号?立即登录" : "没有账号?立即注册" }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.login-page {
|
|
min-height: 100vh;
|
|
background: linear-gradient(135deg, #6366f1 0%, #a78bfa 100%);
|
|
padding: 0 40rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding-top: 160rpx;
|
|
padding-bottom: 60rpx;
|
|
}
|
|
|
|
.logo-circle {
|
|
width: 140rpx;
|
|
height: 140rpx;
|
|
border-radius: 70rpx;
|
|
background: rgba(255, 255, 255, 0.25);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 64rpx;
|
|
color: #ffffff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.app-title {
|
|
font-size: 48rpx;
|
|
color: #ffffff;
|
|
font-weight: bold;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.app-subtitle {
|
|
font-size: 28rpx;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.card {
|
|
width: 100%;
|
|
background: #ffffff;
|
|
border-radius: 24rpx;
|
|
padding: 48rpx 40rpx;
|
|
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
margin-bottom: 48rpx;
|
|
border-bottom: 2rpx solid #e2e8f0;
|
|
}
|
|
|
|
.tab-item {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding-bottom: 20rpx;
|
|
font-size: 30rpx;
|
|
color: #64748b;
|
|
position: relative;
|
|
}
|
|
|
|
.tab-item.active {
|
|
color: #6366f1;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tab-item.active::after {
|
|
content: "";
|
|
position: absolute;
|
|
bottom: -2rpx;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 80rpx;
|
|
height: 4rpx;
|
|
background: #6366f1;
|
|
border-radius: 2rpx;
|
|
}
|
|
|
|
.form {
|
|
margin-bottom: 32rpx;
|
|
}
|
|
|
|
.field {
|
|
margin-bottom: 28rpx;
|
|
}
|
|
|
|
.input {
|
|
width: 100%;
|
|
height: 88rpx;
|
|
background: #f5f6fa;
|
|
border-radius: 16rpx;
|
|
padding: 0 28rpx;
|
|
font-size: 30rpx;
|
|
color: #1e293b;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.placeholder {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.submit-btn {
|
|
width: 100%;
|
|
height: 88rpx;
|
|
line-height: 88rpx;
|
|
background: #6366f1;
|
|
color: #ffffff;
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
border-radius: 16rpx;
|
|
border: none;
|
|
margin-top: 12rpx;
|
|
}
|
|
|
|
.submit-btn::after {
|
|
border: none;
|
|
}
|
|
|
|
.toggle {
|
|
text-align: center;
|
|
padding-top: 8rpx;
|
|
}
|
|
|
|
.toggle-text {
|
|
font-size: 26rpx;
|
|
color: #6366f1;
|
|
}
|
|
</style>
|