466 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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="page-login">
<!-- 全局背景装饰 -->
<view class="bg-decoration"></view>
<view class="content-wrap">
<view class="login-card glass-card anim-fade-in-up">
<!-- 品牌标识项 -->
<view class="brand-section">
<view class="logo-wrapper anim-float">
<image class="logo-img" src="/static/logo.png" mode="aspectFit"></image>
</view>
<text class="brand-title">获取手机号</text>
<text class="brand-desc">
为保障您的权益并提供精准的发货服务我们需要获取您的手机号码作为登录标识
</text>
</view>
<!-- 登录方式区域 -->
<view class="auth-section">
<!-- 微信一键登录 (微信小程序环境) -->
<!-- #ifdef MP-WEIXIN -->
<view class="auth-btn-row">
<button class="btn-refuse" @tap="handleRefuse">拒绝</button>
<button
class="btn-allow anim-scale"
open-type="getPhoneNumber"
:disabled="loading"
@getphonenumber="onGetPhoneNumber"
>
{{ loading ? '处理中...' : '允许' }}
<view class="btn-shine"></view>
</button>
</view>
<!-- #endif -->
<!-- 其他登录 (其他环境或手机号验证码) -->
<!-- #ifndef MP-WEIXIN -->
<view class="form-container">
<view class="u-input-wrap">
<input type="text" v-model="account" placeholder="请输入手机号/账号" class="u-input" />
</view>
<view class="u-input-wrap">
<input type="password" v-model="pwd" placeholder="请输入密码" class="u-input" />
</view>
<button class="btn-primary-large" @tap="handleLogin">立即登录</button>
</view>
<!-- #endif -->
</view>
<!-- 协议勾选 -->
<view class="agreement-wrap">
<view class="checkbox-container" @tap="toggleAgreement">
<view class="custom-checkbox" :class="{ 'is-checked': agreementChecked }">
<text class="check-icon" v-if="agreementChecked"></text>
</view>
<view class="agreement-content">
我已阅读并同意 <text class="link-text" @tap.stop="toUserAgreement">用户协议</text> <text class="link-text" @tap.stop="toPurchaseAgreement">隐私政策</text>
</view>
</view>
</view>
</view>
<!-- 页脚品牌标识 -->
<view class="page-footer">
<text class="footer-copy">Copyright © 2025 柯大鸭潮玩科技</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
const loading = ref(false)
const error = ref('')
const account = ref("")
const pwd = ref("")
const agreementChecked = ref(false)
const process = { env: { NODE_ENV: 'development' } } // Polyfill for template if needed
onMounted(() => {
try {
const saved = uni.getStorageSync('loginInfo')
if (saved && saved.account && saved.pwd) {
account.value = saved.account
pwd.value = saved.pwd
}
} catch (e) {}
})
function toggleAgreement() {
agreementChecked.value = !agreementChecked.value
}
function handleRefuse() {
uni.showToast({ title: '需要授权手机号才能体验完整功能', icon: 'none' })
}
function handleLogin() {
if (!agreementChecked.value) {
uni.showToast({ title: '请先阅读并同意相关协议', icon: 'none' })
return
}
uni.showToast({ title: '密码登录正在维护中', icon: 'none' })
}
function handleTestLogin() {
if (!agreementChecked.value) {
uni.showToast({ title: '请先阅读并同意相关协议', icon: 'none' })
return
}
account.value = 'demo'
pwd.value = '123456'
uni.showLoading({ title: '测试登录中' })
setTimeout(() => {
uni.hideLoading()
uni.setStorageSync('token', 'mock_token_dev')
uni.setStorageSync('user_id', 999)
uni.reLaunch({ url: '/pages/mine/index' })
}, 1000)
}
function toUserAgreement() { uni.navigateTo({ url: '/pages-user/agreement/user' }) }
function toPurchaseAgreement() { uni.navigateTo({ url: '/pages-user/agreement/purchase' }) }
function onGetPhoneNumber(e) {
if (!agreementChecked.value) {
uni.showToast({ title: '请先阅读并同意相关协议', icon: 'none' })
uni.vibrateShort()
return
}
const phoneCode = e.detail.code
if (!phoneCode) {
uni.showToast({ title: '需要授权手机号以完成注册', icon: 'none' })
return
}
loading.value = true
uni.login({
provider: 'weixin',
success: async (res) => {
try {
const loginCode = res.code
const inviterCode = uni.getStorageSync('inviter_code')
const data = await wechatLogin(loginCode, inviterCode)
const token = data && data.token
const user_id = data && data.user_id
const user_info = data || {}
// 保存用户信息及 Token
uni.setStorageSync('user_info', user_info)
if (token) uni.setStorageSync('token', token)
if (user_id) uni.setStorageSync('user_id', user_id)
if (user_info.avatar) uni.setStorageSync('avatar', user_info.avatar)
if (user_info.nickname) uni.setStorageSync('nickname', user_info.nickname)
if (user_info.invite_code) uni.setStorageSync('invite_code', user_info.invite_code)
// 保存 openid 用于支付等场景
const openid = data && (data.openid || data.open_id)
if (openid) uni.setStorageSync('openid', openid)
// 若还没绑定手机号,则进行绑定;已绑定则跳过,大幅提升登录速度
const isBound = user_info.phone || user_info.phone_number || user_info.mobile
if (!isBound) {
try {
await bindPhone(user_id, phoneCode)
uni.setStorageSync('phone_bound', true)
} catch (e) {
console.warn('Bind phone failed', e)
}
} else {
uni.setStorageSync('phone_bound', true)
}
// 性能优化:后台静默获取非关键数据,不阻塞页面跳转
Promise.all([
getUserStats(user_id).then(stats => {
if (stats) uni.setStorageSync('user_stats', stats)
}).catch(()=>{}),
getPointsBalance(user_id).then(balance => {
const b = balance && balance.balance !== undefined ? balance.balance : balance
uni.setStorageSync('points_balance', b)
}).catch(()=>{})
])
uni.showToast({ title: '欧气加持,登录成功!', icon: 'none', duration: 1000 })
// 缩短延迟,极致响应
setTimeout(() => {
uni.reLaunch({ url: '/pages/mine/index' })
}, 500)
} catch (err) {
uni.showToast({ title: err.message || '登录异常', icon: 'none' })
} finally {
loading.value = false
}
},
fail: () => {
loading.value = false
}
})
}
</script>
<style lang="scss" scoped>
.page-login {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
box-sizing: border-box;
}
.content-wrap {
width: 100%;
position: relative;
z-index: 10;
}
.login-card {
padding: 80rpx 40rpx;
text-align: center;
}
/* 品牌区 */
.brand-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 80rpx;
}
.logo-wrapper {
width: 180rpx;
height: 180rpx;
background: #fff;
border-radius: 46rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-warm;
margin-bottom: 40rpx;
padding: 24rpx;
box-sizing: border-box;
overflow: hidden;
border: 4rpx solid rgba($brand-primary, 0.05);
.logo-img {
width: 100%;
height: 100%;
}
}
.brand-info {
text-align: center;
}
.brand-title {
font-size: 36rpx;
font-weight: 800;
color: $text-main;
margin-bottom: 24rpx;
display: block;
}
.brand-desc {
font-size: 26rpx;
color: $text-sub;
line-height: 1.6;
text-align: center;
padding: 0 20rpx;
}
/* 登录操作区 */
.auth-section {
margin-top: 100rpx;
margin-bottom: 60rpx;
}
.auth-btn-row {
display: flex;
gap: 30rpx;
margin-bottom: 0;
}
.btn-refuse {
flex: 1;
height: 100rpx;
border-radius: 50rpx;
background: #f1f3f5;
color: $text-sub;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
border: none;
&::after { border: none; }
}
.btn-allow {
flex: 1.2;
height: 100rpx;
border-radius: 50rpx;
background: $gradient-brand;
color: #fff;
font-size: 32rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-warm;
position: relative;
overflow: hidden;
border: none;
&::after { border: none; }
}
.dev-login {
margin-top: 40rpx;
font-size: 24rpx;
color: $text-sub;
text-decoration: underline;
opacity: 0.5;
}
/* 协议区 */
.agreement-wrap {
margin-top: 40rpx;
}
.checkbox-container {
display: flex;
align-items: flex-start;
justify-content: center;
}
.custom-checkbox {
width: 32rpx;
height: 32rpx;
border: 2rpx solid $text-tertiary;
border-radius: 50%;
margin-right: 16rpx;
margin-top: 4rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
&.is-checked {
background: $brand-primary;
border-color: $brand-primary;
}
.check-icon {
color: #fff;
font-size: 20rpx;
font-weight: bold;
}
}
.agreement-content {
font-size: 24rpx;
color: $text-sub;
line-height: 1.5;
text-align: left;
max-width: 500rpx;
.link-text {
color: $brand-primary;
font-weight: 600;
}
}
/* 页脚 */
.page-footer {
margin-top: 100rpx;
text-align: center;
.footer-copy {
font-size: 20rpx;
color: rgba($text-tertiary, 0.6);
letter-spacing: 1rpx;
}
}
/* 动画增强 */
.anim-fade-in-up {
animation: fadeInUp 0.8s $ease-out both;
}
.anim-float {
animation: float 4s ease-in-out infinite;
}
.anim-scale {
transition: transform 0.2s $ease-out;
&:active {
transform: scale(0.96);
}
}
.btn-shine {
position: absolute;
top: 0; left: -100%;
width: 50%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transform: skewX(-25deg);
animation: shine 4s infinite;
}
@keyframes shine {
0% { left: -150%; }
30% { left: 150%; }
100% { left: 150%; }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15rpx); }
}
/* 其他表单 */
.form-container {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.u-input-wrap {
background: #f8f8fa;
border-radius: 50rpx;
height: 90rpx;
padding: 0 40rpx;
display: flex;
align-items: center;
border: 1px solid transparent;
&:focus-within {
border-color: rgba($brand-primary, 0.3);
background: #fff;
}
}
.u-input {
flex: 1;
font-size: 28rpx;
}
.btn-primary-large {
background: $gradient-brand;
color: #fff;
height: 90rpx;
border-radius: 45rpx;
border: none;
font-weight: 700;
box-shadow: $shadow-warm;
}
</style>