ai-clone/frontend-ai/pages/login/login.vue

541 lines
12 KiB
Vue
Raw Normal View History

2026-03-05 14:29:21 +08:00
<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 -->
<!-- 新增暂不登录按钮 -->
<button class="skip-login-btn" @click="skipLogin">
<text>暂不登录先去体验</text>
</button>
2026-03-05 14:29:21 +08:00
<!-- #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'
});
},
// 暂不登录:以游客身份进入首页体验
skipLogin() {
uni.showToast({
title: '可体验部分功能,登录后可解锁更多',
icon: 'none',
duration: 2000
});
setTimeout(() => {
// 跳转到首页(白名单页面,未登录也可访问)
uni.switchTab({
url: '/pages/index/index'
});
}, 1500);
2026-03-05 14:29:21 +08:00
}
}
};
</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;
}
/* 暂不登录按钮 */
.skip-login-btn {
width: 100%;
height: 90upx;
margin-top: 24upx;
background: transparent;
color: #666;
border-radius: 30upx;
font-size: 28upx;
border: 1rpx solid #ddd;
}
.skip-login-btn:active {
background: rgba(0, 0, 0, 0.05);
}
2026-03-05 14:29:21 +08:00
/* #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>