504 lines
11 KiB
Vue
504 lines
11 KiB
Vue
<script lang='ts' setup>
|
|
import { toastController } from "@ionic/vue";
|
|
import { safeClient } from "@/api";
|
|
import { authClient } from "@/auth";
|
|
import { LoginSchema } from "./schema";
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const form = ref({
|
|
phoneNumber: "",
|
|
password: "",
|
|
});
|
|
const agreed = ref(false);
|
|
const showPassword = ref(false);
|
|
const isLoading = ref(false);
|
|
|
|
async function showToast(message: string, color: "success" | "danger" | "warning" = "danger") {
|
|
const toast = await toastController.create({
|
|
message,
|
|
duration: 2000,
|
|
position: "top",
|
|
color,
|
|
});
|
|
await toast.present();
|
|
}
|
|
async function handleLogin() {
|
|
if (!agreed.value) {
|
|
await showToast("请先阅读并同意服务条款和隐私政策", "warning");
|
|
return;
|
|
}
|
|
const result = LoginSchema.safeParse({ ...form.value });
|
|
if (!result.success) {
|
|
const first = result.error.issues[0];
|
|
await showToast(first.message);
|
|
return;
|
|
}
|
|
|
|
isLoading.value = true;
|
|
try {
|
|
const { data } = await safeClient(authClient.signIn.username({
|
|
username: form.value.phoneNumber,
|
|
password: form.value.password,
|
|
}));
|
|
if (!data.value?.token) {
|
|
toastController.create({
|
|
message: "登录失败,请检查手机号或密码",
|
|
duration: 2000,
|
|
color: "danger",
|
|
}).then(toast => toast.present());
|
|
}
|
|
else {
|
|
const userStore = useUserStore();
|
|
userStore.setToken(data.value.token);
|
|
await userStore.updateProfile();
|
|
await showToast("登录成功!", "success");
|
|
router.push(route.query.redirect as string || "/");
|
|
}
|
|
}
|
|
finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
function handleSignup() {
|
|
router.push(`/auth/signup?redirect=${encodeURIComponent(route.query.redirect as string || "/")}`);
|
|
}
|
|
|
|
function goToTerms(type: "service" | "privacy") {
|
|
router.push(`/auth/term?type=${type}`);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ion-page>
|
|
<ion-content :fullscreen="true" class="login-page">
|
|
<!-- 渐变背景 -->
|
|
<div class="gradient-bg" />
|
|
|
|
<!-- 装饰元素 - 顶部飞鸟 -->
|
|
<div class="decoration-top">
|
|
<div class="bird bird-1" />
|
|
<div class="bird bird-2" />
|
|
<div class="bird bird-3" />
|
|
</div>
|
|
|
|
<!-- 主要内容区域 -->
|
|
<div class="relative z-2 h-full flex flex-col p-10 px-6 pb-5">
|
|
<!-- 顶部Logo和欢迎语 -->
|
|
<div class="text-center mb-10">
|
|
<div class="flex justify-center mb-5">
|
|
<div class="emblem-circle">
|
|
<div class="emblem-inner">
|
|
<div class="star">
|
|
★
|
|
</div>
|
|
<div class="emblem-text">
|
|
中华人民共和国
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<h1 class="text-4xl font-bold text-[#2c3e50] mb-2 tracking-wider">
|
|
你好
|
|
</h1>
|
|
<h2 class="text-2xl font-semibold text-[#2c3e50] tracking-wide">
|
|
欢迎登录深化改革
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- 登录表单 -->
|
|
<div class="flex-1 flex flex-col gap-4 max-w-[400px] w-full mx-auto">
|
|
<!-- 手机号输入 -->
|
|
<ion-item lines="none" class="input-item">
|
|
<ion-input
|
|
v-model="form.phoneNumber"
|
|
type="tel"
|
|
placeholder="请输入手机号"
|
|
class="custom-input"
|
|
:maxlength="11"
|
|
/>
|
|
</ion-item>
|
|
|
|
<!-- 密码输入 -->
|
|
<ion-item lines="none" class="input-item">
|
|
<ion-input
|
|
v-model="form.password"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
placeholder="请输入密码"
|
|
class="custom-input"
|
|
/>
|
|
<ion-icon
|
|
slot="end"
|
|
:icon="showPassword ? 'eye-outline' : 'eye-off-outline'"
|
|
class="password-toggle"
|
|
@click="showPassword = !showPassword"
|
|
/>
|
|
</ion-item>
|
|
|
|
<!-- 协议同意 -->
|
|
<div class="flex items-center gap-2 px-1">
|
|
<ion-checkbox v-model="agreed" class="agreement-checkbox" />
|
|
<span class="text-xs text-[#666] leading-snug">
|
|
我已阅读并同意
|
|
<a class="text-[#c41e3a] no-underline cursor-pointer font-medium hover:underline" @click="goToTerms('service')">《服务条款》</a>
|
|
和
|
|
<a class="text-[#c41e3a] no-underline cursor-pointer font-medium hover:underline" @click="goToTerms('privacy')">《隐私政策》</a>
|
|
</span>
|
|
</div>
|
|
|
|
<!-- 登录按钮 -->
|
|
<ion-button
|
|
expand="block"
|
|
class="login-button mt-2"
|
|
:disabled="isLoading"
|
|
@click="handleLogin"
|
|
>
|
|
<ion-spinner v-if="isLoading" name="crescent" class="mr-2" />
|
|
{{ isLoading ? '登录中...' : '登录' }}
|
|
</ion-button>
|
|
|
|
<!-- 注册按钮 -->
|
|
<ion-button
|
|
expand="block"
|
|
fill="outline"
|
|
class="signup-button"
|
|
@click="handleSignup"
|
|
>
|
|
注册
|
|
</ion-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 底部装饰 - 天安门和飞鸟 -->
|
|
<div class="decoration-bottom">
|
|
<div class="building-wrapper">
|
|
<div class="building" />
|
|
<div class="birds-bottom">
|
|
<div class="bird-small bird-b-1" />
|
|
<div class="bird-small bird-b-2" />
|
|
<div class="bird-small bird-b-3" />
|
|
<div class="bird-small bird-b-4" />
|
|
</div>
|
|
</div>
|
|
<div class="wave-red" />
|
|
</div>
|
|
</ion-content>
|
|
</ion-page>
|
|
</template>
|
|
|
|
<style lang='css' scoped>
|
|
.login-page {
|
|
--background: transparent;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* 渐变背景 */
|
|
.gradient-bg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: linear-gradient(
|
|
180deg,
|
|
#c41e3a 0%,
|
|
#e8756d 15%,
|
|
#f5d5c8 35%,
|
|
#fef5f1 50%,
|
|
#fef5f1 65%,
|
|
#f5d5c8 85%,
|
|
#e8756d 100%
|
|
);
|
|
z-index: 0;
|
|
}
|
|
|
|
/* 顶部装饰飞鸟 */
|
|
.decoration-top {
|
|
position: absolute;
|
|
top: 80px;
|
|
left: 20px;
|
|
z-index: 1;
|
|
}
|
|
|
|
.bird {
|
|
width: 16px;
|
|
height: 16px;
|
|
background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
|
position: absolute;
|
|
clip-path: polygon(50% 0%, 0% 40%, 20% 50%, 0% 60%, 50% 100%, 100% 60%, 80% 50%, 100% 40%);
|
|
opacity: 0.8;
|
|
animation: float 3s ease-in-out infinite;
|
|
}
|
|
|
|
.bird-1 {
|
|
left: 0;
|
|
top: 0;
|
|
animation-delay: 0s;
|
|
}
|
|
|
|
.bird-2 {
|
|
left: 25px;
|
|
top: 10px;
|
|
width: 14px;
|
|
height: 14px;
|
|
animation-delay: 0.5s;
|
|
}
|
|
|
|
.bird-3 {
|
|
left: 15px;
|
|
top: 30px;
|
|
width: 12px;
|
|
height: 12px;
|
|
animation-delay: 1s;
|
|
}
|
|
|
|
@keyframes float {
|
|
0%,
|
|
100% {
|
|
transform: translateY(0) rotate(0deg);
|
|
}
|
|
50% {
|
|
transform: translateY(-10px) rotate(5deg);
|
|
}
|
|
}
|
|
|
|
/* 国徽 */
|
|
|
|
.emblem-circle {
|
|
width: 70px;
|
|
height: 70px;
|
|
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 4px 12px rgba(196, 30, 58, 0.3);
|
|
border: 3px solid #c41e3a;
|
|
}
|
|
|
|
.emblem-inner {
|
|
width: 56px;
|
|
height: 56px;
|
|
background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
}
|
|
|
|
.star {
|
|
font-size: 24px;
|
|
color: #ffd700;
|
|
font-weight: bold;
|
|
margin-bottom: -4px;
|
|
}
|
|
|
|
.emblem-text {
|
|
font-size: 6px;
|
|
color: #ffd700;
|
|
font-weight: bold;
|
|
letter-spacing: 0.5px;
|
|
text-align: center;
|
|
line-height: 1.2;
|
|
width: 40px;
|
|
}
|
|
|
|
/* 输入框 */
|
|
.input-item {
|
|
--background: rgba(255, 255, 255, 0.9);
|
|
--border-radius: 12px;
|
|
--padding-start: 16px;
|
|
--padding-end: 16px;
|
|
--inner-padding-end: 0;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
margin: 0;
|
|
}
|
|
|
|
.custom-input {
|
|
--padding-start: 0;
|
|
--padding-end: 0;
|
|
font-size: 16px;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.password-toggle {
|
|
color: #8b949e;
|
|
font-size: 22px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.agreement-checkbox {
|
|
--size: 20px;
|
|
--border-radius: 4px;
|
|
--background-checked: #c41e3a;
|
|
--border-color-checked: #c41e3a;
|
|
margin: 0;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* 登录按钮 */
|
|
.login-button {
|
|
--background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
|
--background-hover: linear-gradient(135deg, #a01830 0%, #6b1424 100%);
|
|
--border-radius: 25px;
|
|
--box-shadow: 0 4px 12px rgba(196, 30, 58, 0.3);
|
|
height: 50px;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
letter-spacing: 2px;
|
|
margin-top: 8px;
|
|
text-transform: none;
|
|
}
|
|
|
|
/* 注册按钮 */
|
|
.signup-button {
|
|
--border-color: #8b1a2e;
|
|
--border-width: 2px;
|
|
--border-radius: 25px;
|
|
--color: #8b1a2e;
|
|
--background: transparent;
|
|
--background-hover: rgba(196, 30, 58, 0.05);
|
|
height: 50px;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
letter-spacing: 2px;
|
|
text-transform: none;
|
|
}
|
|
|
|
/* 底部装饰 */
|
|
.decoration-bottom {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.building-wrapper {
|
|
position: relative;
|
|
height: 180px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.building {
|
|
position: absolute;
|
|
bottom: 40px;
|
|
left: 0;
|
|
right: 0;
|
|
height: 140px;
|
|
background: linear-gradient(
|
|
to top,
|
|
rgba(196, 30, 58, 0.9) 0%,
|
|
rgba(196, 30, 58, 0.8) 20%,
|
|
rgba(232, 117, 109, 0.7) 40%,
|
|
rgba(232, 117, 109, 0.5) 60%,
|
|
rgba(232, 117, 109, 0.3) 80%,
|
|
transparent 100%
|
|
);
|
|
clip-path: polygon(
|
|
0% 100%,
|
|
0% 60%,
|
|
10% 55%,
|
|
15% 50%,
|
|
15% 45%,
|
|
18% 45%,
|
|
18% 40%,
|
|
22% 40%,
|
|
22% 35%,
|
|
28% 35%,
|
|
28% 30%,
|
|
35% 30%,
|
|
35% 25%,
|
|
42% 25%,
|
|
45% 22%,
|
|
50% 20%,
|
|
55% 22%,
|
|
58% 25%,
|
|
65% 25%,
|
|
65% 30%,
|
|
72% 30%,
|
|
72% 35%,
|
|
78% 35%,
|
|
78% 40%,
|
|
82% 40%,
|
|
82% 45%,
|
|
85% 45%,
|
|
85% 50%,
|
|
90% 55%,
|
|
100% 60%,
|
|
100% 100%
|
|
);
|
|
}
|
|
|
|
.birds-bottom {
|
|
position: absolute;
|
|
bottom: 80px;
|
|
right: 20px;
|
|
}
|
|
|
|
.bird-small {
|
|
width: 12px;
|
|
height: 12px;
|
|
background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
|
position: absolute;
|
|
clip-path: polygon(50% 0%, 0% 40%, 20% 50%, 0% 60%, 50% 100%, 100% 60%, 80% 50%, 100% 40%);
|
|
opacity: 0.9;
|
|
animation: float-bottom 2.5s ease-in-out infinite;
|
|
}
|
|
|
|
.bird-b-1 {
|
|
right: 0;
|
|
bottom: 0;
|
|
animation-delay: 0s;
|
|
}
|
|
|
|
.bird-b-2 {
|
|
right: 20px;
|
|
bottom: 15px;
|
|
width: 14px;
|
|
height: 14px;
|
|
animation-delay: 0.3s;
|
|
}
|
|
|
|
.bird-b-3 {
|
|
right: 45px;
|
|
bottom: 8px;
|
|
animation-delay: 0.6s;
|
|
}
|
|
|
|
.bird-b-4 {
|
|
right: 70px;
|
|
bottom: 20px;
|
|
width: 10px;
|
|
height: 10px;
|
|
animation-delay: 0.9s;
|
|
}
|
|
|
|
@keyframes float-bottom {
|
|
0%,
|
|
100% {
|
|
transform: translate(0, 0) rotate(0deg);
|
|
}
|
|
50% {
|
|
transform: translate(-5px, -8px) rotate(-5deg);
|
|
}
|
|
}
|
|
|
|
/* 红色波浪 */
|
|
.wave-red {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 40px;
|
|
background: linear-gradient(180deg, transparent 0%, #c41e3a 100%);
|
|
}
|
|
</style>
|