ai-clone/frontend-ai/pages/login/login.vue
2026-03-05 14:29:21 +08:00

505 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="login-container">
<view class="login-content">
<!-- Logo区域 -->
<view class="logo-section">
<view class="logo-circle">
<text class="logo-icon">🕊</text>
</view>
<text class="app-name">时光意境</text>
<text class="app-slogan">用科技温暖记忆</text>
</view>
<!-- 登录表单 -->
<view class="form-section">
<!-- #ifndef MP-WEIXIN -->
<view class="input-group">
<view class="input-icon">📱</view>
<input
class="input-field"
v-model="phone"
type="number"
maxlength="11"
placeholder="请输入手机号"
/>
</view>
<view class="input-group">
<view class="input-icon">🔐</view>
<input
class="input-field"
v-model="password"
:password="!showPassword"
placeholder="请输入密码"
/>
<view class="eye-icon" @click="togglePassword">
{{ showPassword ? '👁️' : '👁️‍🗨️' }}
</view>
</view>
<view class="forgot-password" @click="forgotPassword">
忘记密码?
</view>
<!-- #endif -->
<!-- 隐私政策勾选 -->
<view class="privacy-checkbox">
<checkbox-group @change="onPrivacyChange">
<label class="checkbox-label">
<checkbox :checked="agreedPrivacy" value="privacy" color="#8B7355" />
<text class="checkbox-text">
我已阅读并同意
<text class="privacy-link" @click.stop="viewPrivacy">《隐私政策》</text>
</text>
</label>
</checkbox-group>
</view>
<!-- #ifndef MP-WEIXIN -->
<button class="login-btn" @click="handleLogin" :disabled="loading || !agreedPrivacy">
<text v-if="loading">登录中...</text>
<text v-else>登 录</text>
</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button class="wechat-login-btn" @click="handleWechatLogin" :disabled="loading || !agreedPrivacy">
<text>微信一键登录</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="register-section">
<text class="register-text">还没有账号?</text>
<text class="register-link" @click="goToRegister">立即注册</text>
</view>
<!-- #endif -->
</view>
</view>
</view>
</template>
<script>
import { API_BASE } from '@/config/api.js';
export default {
data() {
return {
phone: '',
password: '',
showPassword: false,
loading: false,
agreedPrivacy: false
};
},
methods: {
// 切换密码显示
togglePassword() {
this.showPassword = !this.showPassword;
},
// #ifdef MP-WEIXIN
async handleWechatLogin() {
if (!this.agreedPrivacy) {
uni.showToast({
title: '请先同意隐私政策',
icon: 'none'
});
return;
}
this.loading = true;
try {
const loginRes = await new Promise((resolve, reject) => {
wx.login({
timeout: 10000,
success: resolve,
fail: reject
});
});
const code = loginRes && loginRes.code;
if (!code) {
throw new Error('获取微信登录code失败');
}
const res = await uni.request({
url: `${API_BASE}/api/users/wechat-login`,
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: { code }
});
const { statusCode, data } = res[1] || res;
if (statusCode === 200 && data && data.success) {
const userData = data.user || {};
uni.setStorageSync('token', data.token || '');
uni.setStorageSync('wx_openid', data.openid || '');
uni.setStorageSync('userId', String(userData.id || ''));
uni.setStorageSync('userPhone', userData.phone || '');
uni.setStorageSync('userNickname', userData.nickname || '');
uni.setStorageSync('username', userData.username || '');
uni.setStorageSync('userInfo', {
id: userData.id,
phone: userData.phone,
nickname: userData.nickname,
username: userData.username,
avatarUrl: userData.avatarUrl
});
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 1500
});
setTimeout(() => {
// 与密码登录保持一致:若从“我的”页跳来则返回并触发 onShow 刷新
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
if (prevPage && prevPage.route === 'pages/history/history') {
uni.navigateBack();
} else {
uni.switchTab({
url: '/pages/history/history'
});
}
}, 1500);
} else {
const errorMsg = data?.message || '登录失败';
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 2000
});
}
} catch (err) {
console.error('[Login] 微信登录失败:', err);
uni.showToast({
title: err.message || '微信登录失败',
icon: 'none',
duration: 2000
});
} finally {
this.loading = false;
}
},
// #endif
// 隐私政策勾选变化
onPrivacyChange(e) {
this.agreedPrivacy = e.detail.value.includes('privacy');
},
// 查看隐私政策
viewPrivacy() {
uni.navigateTo({
url: '/pages/settings/privacy-policy'
});
},
// 登录处理
async handleLogin() {
if (!this.phone) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
uni.showToast({
title: '手机号格式不正确',
icon: 'none'
});
return;
}
if (!this.password) {
uni.showToast({
title: '请输入密码',
icon: 'none'
});
return;
}
if (!this.agreedPrivacy) {
uni.showToast({
title: '请先同意隐私政策',
icon: 'none'
});
return;
}
this.loading = true;
try {
const res = await uni.request({
url: `${API_BASE}/api/users/login`,
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: {
phone: this.phone,
password: this.password
}
});
const { statusCode, data } = res[1] || res; // 兼容不同端返回格式
if (statusCode === 200 && data && data.success) {
const userData = data.user || {};
// 保存用户信息到本地存储(确保 userId 是字符串)
uni.setStorageSync('token', data.token || '');
uni.setStorageSync('userId', String(userData.id || ''));
uni.setStorageSync('userPhone', userData.phone || '');
uni.setStorageSync('userNickname', userData.nickname || '');
uni.setStorageSync('username', userData.username || '');
uni.setStorageSync('userInfo', {
id: userData.id,
phone: userData.phone,
nickname: userData.nickname,
username: userData.username,
avatarUrl: userData.avatarUrl
});
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 1500
});
setTimeout(() => {
// 检查是否从"我的"页面跳转过来
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
if (prevPage && prevPage.route === 'pages/history/history') {
// 如果是从"我的"页面来的,返回并刷新
uni.navigateBack();
} else {
// 否则跳转到"我的"页面
uni.switchTab({
url: '/pages/history/history'
});
}
}, 1500);
} else {
// 处理所有错误情况包括401等
const errorMsg = data?.message || '登录失败';
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 2000
});
}
} catch (err) {
console.error('[Login] 请求失败:', err);
uni.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 2000
});
} finally {
this.loading = false;
}
},
// 忘记密码
forgotPassword() {
uni.navigateTo({
url: '/pages/forgot-password/forgot-password'
});
},
// 注册
goToRegister() {
uni.navigateTo({
url: '/pages/register/register'
});
}
}
};
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #8B7355 0%, #6D8B8B 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 40upx;
padding-top: calc(40upx + env(safe-area-inset-top));
padding-bottom: calc(40upx + env(safe-area-inset-bottom));
}
.login-content {
width: 100%;
max-width: 600upx;
}
/* Logo区域 */
.logo-section {
text-align: center;
margin-bottom: 80upx;
.logo-circle {
width: 160upx;
height: 160upx;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 30upx;
backdrop-filter: blur(10upx);
box-shadow: 0 16upx 60upx rgba(0, 0, 0, 0.2);
.logo-icon {
font-size: 80upx;
}
}
.app-name {
display: block;
font-size: 56upx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16upx;
letter-spacing: 4upx;
}
.app-slogan {
display: block;
font-size: 28upx;
color: rgba(255, 255, 255, 0.8);
}
}
/* 表单区域 */
.form-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 40upx;
padding: 60upx 40upx;
box-shadow: 0 16upx 60upx rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20upx);
}
.input-group {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 30upx;
padding: 0 30upx;
margin-bottom: 30upx;
height: 100upx;
.input-icon {
font-size: 40upx;
margin-right: 20upx;
}
.input-field {
flex: 1;
font-size: 30upx;
color: #333;
}
.eye-icon {
font-size: 40upx;
padding: 10upx;
}
}
.forgot-password {
text-align: right;
font-size: 26upx;
color: #8B7355;
margin-bottom: 40upx;
}
.login-btn {
width: 100%;
height: 100upx;
background: linear-gradient(135deg, #8B7355 0%, #6D8B8B 100%);
color: #ffffff;
border-radius: 30upx;
font-size: 34upx;
font-weight: bold;
border: none;
margin-top: 20upx;
box-shadow: 0 12upx 40upx rgba(139, 115, 85, 0.3);
}
.login-btn[disabled] {
opacity: 0.6;
transform: none !important;
}
/* #ifdef MP-WEIXIN */
.wechat-login-btn {
width: 100%;
height: 100upx;
margin-top: 22upx;
background: linear-gradient(135deg, #07C160 0%, #10B981 100%);
color: #ffffff;
border-radius: 30upx;
font-size: 34upx;
font-weight: bold;
border: none;
box-shadow: 0 12upx 40upx rgba(7, 193, 96, 0.25);
}
.wechat-login-btn[disabled] {
opacity: 0.6;
transform: none !important;
}
/* #endif */
.privacy-checkbox {
margin-bottom: 30upx;
.checkbox-label {
display: flex;
align-items: center;
font-size: 24upx;
color: #666;
.checkbox-text {
margin-left: 10upx;
line-height: 1.5;
}
.privacy-link {
color: #8B7355;
text-decoration: underline;
}
}
}
.register-section {
text-align: center;
margin-top: 30upx;
.register-text {
font-size: 26upx;
color: #999;
}
.register-link {
font-size: 26upx;
color: #8B7355;
font-weight: bold;
margin-left: 10upx;
}
}
</style>