Files
CosScene/clients/pages/login/index.vue
T
2026-05-09 16:40:29 +08:00

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>