466 lines
11 KiB
Vue
466 lines
11 KiB
Vue
<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>
|