266 lines
8.8 KiB
Vue
266 lines
8.8 KiB
Vue
<template>
|
||
<view class="container">
|
||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||
<!-- #ifdef MP-TOUTIAO -->
|
||
<view class="login-form">
|
||
<view class="input-row">
|
||
<text class="label">账号:</text>
|
||
<input
|
||
type="text"
|
||
v-model="account"
|
||
class="input-field"
|
||
placeholder="请输入账号"
|
||
/>
|
||
</view>
|
||
|
||
<view class="input-row">
|
||
<text class="label">密码:</text>
|
||
<input
|
||
type="password"
|
||
v-model="pwd"
|
||
class="input-field"
|
||
placeholder="请输入密码"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 记住账号密码 -->
|
||
<view class="remember-row">
|
||
<checkbox :value="remember" @change="handleRememberChange" />
|
||
<text class="remember-text">记住账号密码</text>
|
||
</view>
|
||
<!-- 按钮区域 -->
|
||
<view class="button-group">
|
||
<button class="btn login-btn" @click="handleLogin">登录</button>
|
||
</view>
|
||
<!-- 注册链接 -->
|
||
<view class="register-link">
|
||
<text class="register-text" @click="goToRegister">还没有账号?点击注册</text>
|
||
</view>
|
||
|
||
|
||
|
||
</view>
|
||
<!-- #endif -->
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<view class="title">微信登录</view>
|
||
|
||
|
||
<button class="btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">授权手机号快速登录</button>
|
||
<!-- #endif -->
|
||
<view class="agreements">
|
||
<text>注册或登录即表示您已阅读并同意</text>
|
||
<text class="link" @tap="toUserAgreement">《用户协议》</text>
|
||
<text>和</text>
|
||
<text class="link" @tap="toPurchaseAgreement">《购买协议》</text>
|
||
</view>
|
||
<view v-if="needBindPhone" class="tip">登录成功,请绑定手机号以完成登录</view>
|
||
<view v-if="error" class="error">{{ error }}</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed,onMounted } from 'vue'
|
||
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
|
||
|
||
const loading = ref(false)
|
||
const error = ref('')
|
||
const needBindPhone = ref(false)
|
||
const account =ref("")
|
||
const pwd = ref ("")
|
||
const remember=ref(false)
|
||
const loggedIn = computed(() => !!uni.getStorageSync('token'))
|
||
|
||
onMounted(() => {
|
||
try {
|
||
const saved = uni.getStorageSync('loginInfo')
|
||
if (saved && saved.account && saved.pwd) {
|
||
account.value = saved.account
|
||
pwd.value = saved.pwd
|
||
remember.value = true
|
||
}
|
||
} catch (e) {
|
||
console.error('读取本地登录信息失败', e)
|
||
}
|
||
})
|
||
|
||
// 切换“记住密码”
|
||
const handleRememberChange = (e) => {
|
||
remember.value = e.detail.value.length > 0
|
||
}
|
||
|
||
|
||
|
||
|
||
function goToRegister() { uni.navigateTo({ url: '/pages/register/register' }) }
|
||
function onLogin() {}
|
||
|
||
function toUserAgreement() { uni.navigateTo({ url: '/pages/agreement/user' }) }
|
||
function toPurchaseAgreement() { uni.navigateTo({ url: '/pages/agreement/purchase' }) }
|
||
|
||
function onGetPhoneNumber(e) {
|
||
const phoneCode = e.detail.code
|
||
console.log('login_flow start getPhoneNumber, codeExists:', !!phoneCode)
|
||
if (!phoneCode) {
|
||
uni.showToast({ title: '未授权手机号', icon: 'none' })
|
||
console.error('login_flow error: missing phoneCode')
|
||
return
|
||
}
|
||
loading.value = true
|
||
error.value = ''
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: async (res) => {
|
||
try {
|
||
const loginCode = res.code
|
||
console.log('login_flow uni.login success, loginCode exists:', !!loginCode)
|
||
const inviterCode = uni.getStorageSync('inviter_code')
|
||
console.log('login_flow using inviter_code:', inviterCode)
|
||
const data = await wechatLogin(loginCode, inviterCode)
|
||
console.log('login_flow wechatLogin response user_id:', data && data.user_id)
|
||
const token = data && data.token
|
||
const user_id = data && data.user_id
|
||
const avatar = data && data.avatar
|
||
const nickname = data && data.nickname
|
||
const invite_code = data && data.invite_code
|
||
const openid = data && data.openid
|
||
uni.setStorageSync('user_info', data || {})
|
||
if (token) {
|
||
uni.setStorageSync('token', token)
|
||
console.log('login_flow token stored')
|
||
}
|
||
if (user_id) {
|
||
uni.setStorageSync('user_id', user_id)
|
||
console.log('login_flow user_id stored:', user_id)
|
||
}
|
||
if (avatar) {
|
||
uni.setStorageSync('avatar', avatar)
|
||
}
|
||
if (nickname) {
|
||
uni.setStorageSync('nickname', nickname)
|
||
}
|
||
if (invite_code) {
|
||
uni.setStorageSync('invite_code', invite_code)
|
||
}
|
||
if (openid) {
|
||
uni.setStorageSync('openid', openid)
|
||
}
|
||
console.log('login_flow bindPhone start')
|
||
try {
|
||
// 首次绑定前短暂延迟,确保服务端 token 生效
|
||
await new Promise(r => setTimeout(r, 600))
|
||
const bindRes = await bindPhone(user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||
const phoneNumber = (bindRes && (bindRes.phone || bindRes.phone_number || bindRes.mobile)) || ''
|
||
if (phoneNumber) uni.setStorageSync('phone_number', phoneNumber)
|
||
} catch (bindErr) {
|
||
if (bindErr && bindErr.statusCode === 401) {
|
||
console.warn('login_flow bindPhone 401, try re-login and retry')
|
||
// 重新获取登录 code
|
||
const relogin = await new Promise((resolve, reject) => {
|
||
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
||
})
|
||
const data2 = await wechatLogin(relogin.code, inviterCode)
|
||
const token2 = data2 && data2.token
|
||
const user2 = data2 && data2.user_id
|
||
if (token2) uni.setStorageSync('token', token2)
|
||
if (user2) uni.setStorageSync('user_id', user2)
|
||
// 再次延迟后重试绑定
|
||
await new Promise(r => setTimeout(r, 600))
|
||
const bindRes2 = await bindPhone(user2 || user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||
const phoneNumber2 = (bindRes2 && (bindRes2.phone || bindRes2.phone_number || bindRes2.mobile)) || ''
|
||
if (phoneNumber2) uni.setStorageSync('phone_number', phoneNumber2)
|
||
} else {
|
||
throw bindErr
|
||
}
|
||
}
|
||
uni.setStorageSync('phone_bound', true)
|
||
console.log('login_flow bindPhone success, phone_bound stored')
|
||
try {
|
||
const stats = await getUserStats(user_id)
|
||
console.log('login_flow getUserStats success')
|
||
const balance = await getPointsBalance(user_id)
|
||
console.log('login_flow getPointsBalance success')
|
||
uni.setStorageSync('user_stats', stats)
|
||
const b = balance && balance.balance !== undefined ? balance.balance : balance
|
||
uni.setStorageSync('points_balance', b)
|
||
} catch (e) {
|
||
console.error('login_flow fetch stats/points error:', e && (e.message || e.errMsg))
|
||
}
|
||
uni.showToast({ title: '登录并绑定成功', icon: 'success' })
|
||
console.log('login_flow navigate to index')
|
||
uni.reLaunch({ url: '/pages/index/index' })
|
||
} catch (err) {
|
||
console.error('login_flow error:', err && (err.message || err.errMsg), 'status:', err && err.statusCode)
|
||
error.value = err.message || '登录或绑定失败'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
},
|
||
fail: (e) => {
|
||
console.error('login_flow uni.login fail:', e && e.errMsg)
|
||
error.value = '微信登录失败'
|
||
loading.value = false
|
||
}
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.login-form {
|
||
padding: 20px;
|
||
}
|
||
|
||
.input-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
.remember-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 60rpx;
|
||
}
|
||
|
||
.remember-text {
|
||
margin-left: 16rpx;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
.register-link {
|
||
text-align: center;
|
||
}
|
||
|
||
.register-text {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
text-decoration: underline; /* 可选:加下划线 */
|
||
}
|
||
|
||
.register-text:active {
|
||
color: #ff6700; /* 点击时变色 */
|
||
}
|
||
.label {
|
||
width: 60px; /* 固定宽度,保证对齐 */
|
||
font-size: 16px;
|
||
color: #333;
|
||
}
|
||
|
||
.input-field {
|
||
flex: 1;
|
||
height: 40px;
|
||
border: 2px solid #ccc; /* 边框加粗 */
|
||
border-radius: 4px;
|
||
padding: 0 10px;
|
||
font-size: 16px;
|
||
/* 去除 iOS 默认样式 */
|
||
-webkit-appearance: none;
|
||
outline: none;
|
||
}
|
||
.container { padding: 40rpx; display: flex; flex-direction: column; align-items: center }
|
||
.logo { width: 200rpx; margin-top: 100rpx; margin-bottom: 40rpx }
|
||
.title { font-size: 36rpx; margin-bottom: 20rpx }
|
||
.btn { width: 80%; margin-top: 20rpx }
|
||
.agreements { margin-top: 16rpx; font-size: 24rpx; color: #666; display: flex; flex-wrap: wrap; justify-content: center }
|
||
.link { color: #007AFF; margin: 0 6rpx }
|
||
.tip { color: #666; margin-top: 12rpx }
|
||
.error { color: #e43; margin-top: 20rpx }
|
||
</style> |