575 lines
13 KiB
Vue
575 lines
13 KiB
Vue
<template>
|
||
<view class="login-container">
|
||
<view class="header-row">
|
||
<view class="back-button" @click="back">
|
||
<image src="/static/ic_back.png" class="back-icon"></image>
|
||
</view>
|
||
<text class="title">{{ $t('login.title') }}</text>
|
||
<view class="back-button" @click="markAllRead">
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Logo Section -->
|
||
<view class="logo-section">
|
||
<view class="logo-box">
|
||
<image v-if="appLogo" :src="appLogo" class="logo-image" mode="aspectFit"></image>
|
||
<text v-else class="logo-text">LOGO</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Main Content -->
|
||
<view class="main-content">
|
||
<!-- One-Click Login Button -->
|
||
<button class="login-button" @click="handleWechatLogin" :loading="isLoading">
|
||
{{ $t('login.oneClickLogin') }}
|
||
</button>
|
||
|
||
<!-- Agreement Checkbox -->
|
||
<view class="agreement-section">
|
||
<checkbox-group @change="handleAgreementChange">
|
||
<label class="agreement-label">
|
||
<checkbox value="agree" :checked="agreeToTerms" />
|
||
<text class="agreement-text">
|
||
{{ $t('login.agreeToTerms') }}
|
||
<text class="link-text" @click="showUserAgreement">{{ $t('login.userAgreement') }}</text>
|
||
{{ $t('login.and') }}
|
||
<text class="link-text" @click="showPrivacyPolicy">{{ $t('login.privacyPolicy') }}</text>
|
||
</text>
|
||
</label>
|
||
</checkbox-group>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- User Agreement Modal -->
|
||
<view v-if="showAgreementModal" class="modal-overlay" @click="closeUserAgreement">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ $t('login.userAgreement') }}</text>
|
||
<text class="modal-close" @click="closeUserAgreement">×</text>
|
||
</view>
|
||
<scroll-view class="modal-body" scroll-y>
|
||
<text class="modal-text">{{ userAgreementContent }}</text>
|
||
</scroll-view>
|
||
<view class="modal-footer">
|
||
<button class="modal-button" @click="closeUserAgreement">{{ $t('login.agree') }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Privacy Policy Modal -->
|
||
<view v-if="showPrivacyModal" class="modal-overlay" @click="closePrivacyPolicy">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ $t('login.privacyPolicy') }}</text>
|
||
<text class="modal-close" @click="closePrivacyPolicy">×</text>
|
||
</view>
|
||
<scroll-view class="modal-body" scroll-y>
|
||
<text class="modal-text">{{ privacyPolicyContent }}</text>
|
||
</scroll-view>
|
||
<view class="modal-footer">
|
||
<button class="modal-button" @click="closePrivacyPolicy">{{ $t('login.agree') }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
AppServer
|
||
} from '../../modules/api/AppServer';
|
||
import {
|
||
saveAuthData
|
||
} from '@/utils/auth.js';
|
||
import Config from '@/modules/Config.js';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
isLoading: false,
|
||
agreeToTerms: false,
|
||
showAgreementModal: false,
|
||
showPrivacyModal: false,
|
||
userAgreementContent: this.getUserAgreementContent(),
|
||
privacyPolicyContent: this.getPrivacyPolicyContent(),
|
||
statusBarHeight: 0,
|
||
appLogo: ''
|
||
}
|
||
},
|
||
onLoad() {
|
||
// Get status bar height
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
||
// 加载配置
|
||
this.loadConfig()
|
||
},
|
||
methods: {
|
||
back() {
|
||
uni.navigateBack({
|
||
delta: 1
|
||
});
|
||
},
|
||
// Handle WeChat login
|
||
async handleWechatLogin() {
|
||
// 防止重复点击
|
||
if (this.isLoading) {
|
||
return;
|
||
}
|
||
|
||
// 检查是否勾选协议
|
||
if (!this.agreeToTerms) {
|
||
uni.showToast({
|
||
title: this.$t('login.mustAgreeToTerms'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
this.isLoading = true
|
||
|
||
try {
|
||
// 获取微信登录code
|
||
const loginRes = await this.getWechatLoginCode();
|
||
|
||
console.log('微信登录 code:', loginRes);
|
||
|
||
if (!loginRes || !loginRes.code) {
|
||
uni.showToast({
|
||
title: this.$t('login.wechatLoginFailed'),
|
||
icon: 'none'
|
||
})
|
||
this.isLoading = false
|
||
return
|
||
}
|
||
|
||
// 调用后端登录接口
|
||
const appserver = new AppServer();
|
||
const data = await appserver.WechatLogin(loginRes.code);
|
||
|
||
console.log('登录接口返回:', data);
|
||
|
||
// 检查API响应
|
||
if (!data) {
|
||
console.error('登录接口无响应');
|
||
uni.showToast({
|
||
title: this.$t('login.loginFailed'),
|
||
icon: 'none'
|
||
})
|
||
this.isLoading = false
|
||
return
|
||
}
|
||
|
||
// 检查返回码(0表示成功)
|
||
if (data.code !== 0) {
|
||
console.error('登录失败,code:', data.code, 'message:', data.message);
|
||
uni.showToast({
|
||
title: data.message || this.$t('login.loginFailed'),
|
||
icon: 'none'
|
||
})
|
||
this.isLoading = false
|
||
return
|
||
}
|
||
|
||
// 检查token数据
|
||
if (!data.data || !data.data.token) {
|
||
console.error('Token 数据缺失:', data.data);
|
||
uni.showToast({
|
||
title: this.$t('login.loginFailed'),
|
||
icon: 'none'
|
||
})
|
||
this.isLoading = false
|
||
return
|
||
}
|
||
|
||
console.log('登录成功,保存认证信息');
|
||
|
||
// 保存认证信息
|
||
const token = "Bearer " + data.data.token
|
||
saveAuthData(token, data.data.refreshToken, data.data.user)
|
||
|
||
// 登录成功
|
||
uni.showToast({
|
||
title: this.$t('login.loginSuccess') || '登录成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
|
||
// 延迟跳转,让用户看到成功提示
|
||
setTimeout(() => {
|
||
this.redirectToHome();
|
||
}, 500);
|
||
|
||
} catch (error) {
|
||
console.error('登录异常:', error);
|
||
console.error('错误堆栈:', error.stack);
|
||
uni.showToast({
|
||
title: this.$t('login.loginError') + ': ' + (error.message || ''),
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
} finally {
|
||
this.isLoading = false
|
||
}
|
||
},
|
||
|
||
// 获取微信登录code(Promise封装)
|
||
getWechatLoginCode() {
|
||
return new Promise((resolve, reject) => {
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: (res) => {
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
console.error('uni.login 失败:', err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
// 登录成功后跳转到首页
|
||
redirectToHome() {
|
||
// 设置全局标志,标记登录成功
|
||
const app = getApp();
|
||
if (app.globalData) {
|
||
app.globalData.shouldRefresh = true;
|
||
app.globalData.loginTime = Date.now();
|
||
}
|
||
|
||
// 跳转到首页(tabBar页面)
|
||
uni.switchTab({
|
||
url: '/pages/index/index',
|
||
success: () => {
|
||
console.log('登录成功,已跳转到首页');
|
||
},
|
||
fail: (err) => {
|
||
console.error('跳转首页失败:', err);
|
||
// 如果 switchTab 失败,尝试使用 reLaunch
|
||
uni.reLaunch({
|
||
url: '/pages/index/index'
|
||
});
|
||
}
|
||
});
|
||
},
|
||
|
||
// Handle agreement checkbox change
|
||
handleAgreementChange(e) {
|
||
this.agreeToTerms = e.detail.value.includes('agree')
|
||
},
|
||
|
||
// Show user agreement
|
||
showUserAgreement() {
|
||
this.showAgreementModal = true
|
||
},
|
||
|
||
// Close user agreement
|
||
closeUserAgreement() {
|
||
this.showAgreementModal = false
|
||
},
|
||
|
||
// Show privacy policy
|
||
showPrivacyPolicy() {
|
||
this.showPrivacyModal = true
|
||
},
|
||
|
||
// Close privacy policy
|
||
closePrivacyPolicy() {
|
||
this.showPrivacyModal = false
|
||
},
|
||
|
||
// Get user agreement content
|
||
getUserAgreementContent() {
|
||
return this.$t('login.userAgreementContent') || `
|
||
用户协议
|
||
|
||
欢迎使用本应用。本协议规定了您使用本应用的条款和条件。
|
||
|
||
1. 服务条款
|
||
用户同意遵守本协议的所有条款和条件。
|
||
|
||
2. 用户责任
|
||
用户对其账户的安全负责,并同意不与他人共享登录凭证。
|
||
|
||
3. 禁止行为
|
||
用户不得进行任何非法或有害的活动。
|
||
|
||
4. 免责声明
|
||
本应用按"现状"提供,不提供任何明示或暗示的保证。
|
||
|
||
5. 修改权利
|
||
我们保留随时修改本协议的权利。
|
||
`
|
||
},
|
||
|
||
// Get privacy policy content
|
||
getPrivacyPolicyContent() {
|
||
return this.$t('login.privacyPolicyContent') || `
|
||
隐私政策
|
||
|
||
我们重视您的隐私。本政策说明我们如何收集、使用和保护您的信息。
|
||
|
||
1. 信息收集
|
||
我们收集您在使用本应用时提供的信息,包括账户信息和使用数据。
|
||
|
||
2. 信息使用
|
||
我们使用收集的信息来改进服务、进行分析和提供个性化体验。
|
||
|
||
3. 信息保护
|
||
我们采取适当的安全措施来保护您的个人信息。
|
||
|
||
4. 第三方共享
|
||
我们不会将您的个人信息出售给第三方。
|
||
|
||
5. 联系我们
|
||
如有隐私问题,请通过应用内的联系方式与我们联系。
|
||
`
|
||
},
|
||
|
||
// 加载配置
|
||
async loadConfig() {
|
||
try {
|
||
const config = await Config.getPublicConfig()
|
||
if (config.app_logo) {
|
||
this.appLogo = Config.getImageUrl(config.app_logo)
|
||
}
|
||
} catch (error) {
|
||
console.error('加载配置失败:', error)
|
||
}
|
||
},
|
||
|
||
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.login-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.header-row {
|
||
width: 100%;
|
||
margin-top: 100rpx;
|
||
padding-bottom: 20rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.back-button {
|
||
width: 80rpx;
|
||
height: 50rpx;
|
||
margin-left: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.back-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
}
|
||
|
||
// Logo Section
|
||
.logo-section {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 30px 20px 20px;
|
||
flex: 0 0 auto;
|
||
|
||
.logo-box {
|
||
width: 140px;
|
||
height: 140px;
|
||
border: 2px solid #17a2b8;
|
||
border-radius: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #fff;
|
||
box-shadow: 0 2px 8px rgba(23, 162, 184, 0.1);
|
||
overflow: hidden;
|
||
|
||
.logo-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.logo-text {
|
||
font-size: 36px;
|
||
color: #17a2b8;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Main Content
|
||
.main-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
padding: 0 20px 40px;
|
||
}
|
||
|
||
// Login Button
|
||
.login-button {
|
||
width: 100%;
|
||
height: 50px;
|
||
background-color: #17a2b8;
|
||
color: #fff;
|
||
border-radius: 10px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
border: none;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 3px 10px rgba(23, 162, 184, 0.2);
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
line-height: 50px;
|
||
padding: 0;
|
||
text-align: center;
|
||
|
||
&:active {
|
||
background-color: #138496;
|
||
box-shadow: 0 2px 6px rgba(23, 162, 184, 0.15);
|
||
}
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
}
|
||
|
||
// Agreement Section
|
||
.agreement-section {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
|
||
checkbox-group {
|
||
width: 100%;
|
||
}
|
||
|
||
.agreement-label {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
font-size: 13px;
|
||
color: #666;
|
||
line-height: 1.8;
|
||
|
||
checkbox {
|
||
margin-right: 8px;
|
||
margin-top: 3px;
|
||
flex-shrink: 0;
|
||
transform: scale(0.9);
|
||
}
|
||
|
||
.agreement-text {
|
||
flex: 1;
|
||
padding-top: 1px;
|
||
}
|
||
|
||
.link-text {
|
||
color: #17a2b8;
|
||
text-decoration: underline;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Modal Overlay
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 999;
|
||
padding: 20px;
|
||
}
|
||
|
||
// Modal Styles
|
||
.modal-content {
|
||
width: 100%;
|
||
max-width: 480px;
|
||
max-height: 85vh;
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 18px 20px;
|
||
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
||
border-bottom: none;
|
||
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
}
|
||
|
||
.modal-close {
|
||
font-size: 32px;
|
||
color: #fff;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
|
||
.modal-body {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
|
||
.modal-text {
|
||
font-size: 14px;
|
||
color: #555;
|
||
line-height: 1.8;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
}
|
||
|
||
.modal-footer {
|
||
padding: 16px 20px;
|
||
border-top: 1px solid #f0f0f0;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
background-color: #fafafa;
|
||
|
||
.modal-button {
|
||
padding: 10px 28px;
|
||
background-color: #17a2b8;
|
||
color: #fff;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
border: none;
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
background-color: #138496;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |