554 lines
16 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="container">
<!-- 登录后需要填写昵称头像 -->
<view v-if="isPersonal === true" class="personal">
<!-- 页面头部 -->
<view class="header">
<view class="title">
<text>获取您的昵称头像</text>
</view>
<view class="sub-title">
<text>填写您的微信头像和昵称以便获得更好的体验</text>
</view>
</view>
<!-- 表单组件 -->
<view class="login-form">
<view class="form-item">
<view class="form-item--label">头像</view>
<button class="btn-normal" open-type="chooseAvatar" @click="onClickAvatar()" @chooseavatar="onChooseAvatar">
<avatar-image :url="avatarUrl" :width="100" />
</button>
</view>
<view class="form-item">
<view class="form-item--label">昵称</view>
<input class="form-item--input" type="nickname" v-model="form.nickName" maxlength="30" placeholder="请输入微信昵称"
@input="onInputNickName" @blur="onInputNickName" />
</view>
</view>
<!-- 操作按钮 -->
<view class="login-btn">
<view class="button" :class="{ disabled }" @click="handleSubmit()">确认</view>
</view>
<view class="no-login-btn">
<view class="button" @click="handleCancel()">暂不登录</view>
</view>
</view>
<view v-if="isPersonal === false" class="authorize">
<view class="store-info">
<view class="header">
<image class="image" src="https://heyuimage.ihzhy.com/prd/202511/697446e044869c29.jpg??access_token=e01c8565446ad0448dfb6d9f69f3d562"></image>
</view>
</view>
<view class="auth-title">申请获取以下权限</view>
<view class="auth-subtitle">获得你的公开信息昵称头像等</view>
<!-- #ifdef MP-WEIXIN -->
<view class="agreement-box">
<view class="agreement-checkbox" @click="toggleAgreement">
<view class="checkbox-icon" :class="{ checked: agreedToTerms }">
<u-icon v-if="agreedToTerms" name="checkbox-mark" color="#fff" size="28"></u-icon>
</view>
<text class="agreement-text">
我已同意
<text class="agreement-link" @click.stop="openAgreement">相关协议</text>
</text>
</view>
</view>
<view class="login-btn">
<view class="button" :class="{ disabled: disabled || !agreedToTerms }" @click="handleLogin()">一键登录</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="agreement-box">
<view class="agreement-checkbox" @click="toggleAgreement">
<view class="checkbox-icon" :class="{ checked: agreedToTerms }">
<u-icon v-if="agreedToTerms" name="checkbox-mark" color="#fff" size="28"></u-icon>
</view>
<text class="agreement-text">
我已同意
<text class="agreement-link" @click.stop="openAgreement">相关协议</text>
</text>
</view>
</view>
<view class="login-btn">
<view class="button" :class="{ disabled: disabled || !agreedToTerms }" @click="handleLogin()">一键登录</view>
</view>
<!-- #endif -->
<view class="no-login-btn">
<view class="button" @click="handleCancel()">暂不登录</view>
</view>
</view>
</view>
</template>
<script>
import store from '@/store'
import AvatarImage from '@/components/avatar-image'
import * as Verify from '@/utils/verify'
import * as OrderApi from '@/api/order'
import * as UserApi from '@/api/user'
export default {
components: {
AvatarImage
},
data() {
return {
// 是否需要填写用户昵称和头像
isPersonal: false,
// 按钮禁用
disabled: false,
// 是否同意协议
agreedToTerms: false,
// 头像路径 (用于显示)
avatarUrl: '',
// 临时图片 (用于上传)
tempFile: undefined,
// 表单数据
form: {
nickName: ''
},
// 微信小程序登录凭证 (code)
// 提交到后端用于换取openid
code: ''
}
},
methods: {
// 一键登录按钮点击事件
async handleLogin() {
if (!this.agreedToTerms) {
this.$toast('请先同意相关协议')
return
}
// 获取微信登录code
if (!this.code) {
this.code = await this.getCode()
}
this.onAuthSuccess()
},
// 授权成功事件
// 这里分为两个逻辑:
// 1.将code和userInfo提交到后端如果存在该用户 则实现自动登录
// 2.如果不存在该用户, 则显示注册页面
// 3.如果后端报错了, 则显示错误信息
async onAuthSuccess() {
const app = this
// 如果还没有获取code则获取code
if (!app.code) {
app.code = await app.getCode()
}
// 按钮禁用
app.disabled = true
store.dispatch('LoginMpWx', { code: app.code })
.then(result => {
app.disabled = false
if (!result.data.buyer.avatar) {
app.isPersonal = true
} else {
store.dispatch('UpdateUserInfo', res.data.buyer)
app.$toast('登录成功')
uni.$emit('syncRefresh', true)
setTimeout(() => app.onNavigateBack(), 2000)
}
})
.catch(err => {
const resultData = err.result?.data || {}
// 判断还需绑定手机号
if (resultData.isBindMobile) {
app.onEmitSuccess(userInfo)
} else {
// 其他错误,恢复按钮状态
app.disabled = false
app.code = '' // 重置code下次重新获取
}
})
},
// 获取code
// https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
getCode() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success({ code }) {
resolve(code)
},
fail: reject
})
})
},
// 绑定昵称输入框 (用于微信小程序端快速填写昵称能力)
onInputNickName({ detail }) {
console.log(detail)
if (detail.value) {
this.form.nickName = detail.value
}
},
// 点击头像按钮事件
onClickAvatar() {
// #ifdef MP-WEIXIN
return
// #endif
this.chooseImage()
},
// 选择头像事件 - 仅限微信小程序
onChooseAvatar({ detail }) {
// #ifdef MP-WEIXIN
const app = this
console.log('onChooseAvatar', detail)
// chooseAvatar 返回的临时文件路径在开发者工具中可能无法直接访问
// 需要将临时文件保存到小程序文件系统中
const tempFilePath = detail.avatarUrl
// 先显示临时路径(用于预览)
app.avatarUrl = tempFilePath
// 将临时文件保存到小程序文件系统
// uni.saveFile 会将临时文件保存到小程序的文件系统中,返回一个可用的文件路径
uni.saveFile({
tempFilePath: tempFilePath,
success: (res) => {
// 保存成功,使用保存后的文件路径
const savedFilePath = res.savedFilePath
console.log('头像文件保存成功', savedFilePath)
app.avatarUrl = savedFilePath
app.tempFile = { path: savedFilePath }
},
fail: (err) => {
console.error('头像文件保存失败', err)
// 如果保存失败,尝试使用文件系统管理器复制文件
// 在开发者工具中,临时文件可能无法访问,但真机上通常可以直接使用
try {
const fs = wx.getFileSystemManager()
// 使用小程序的用户数据目录(如果可用)
const userDataPath = wx.env && wx.env.USER_DATA_PATH
if (userDataPath) {
const timestamp = Date.now()
const random = Math.random().toString(36).substring(2, 8)
const destPath = `${userDataPath}/avatar_${timestamp}_${random}.jpg`
fs.copyFile({
srcPath: tempFilePath,
destPath: destPath,
success: (copyRes) => {
console.log('头像文件复制成功', destPath)
app.avatarUrl = destPath
app.tempFile = { path: destPath }
},
fail: (copyErr) => {
console.error('头像文件复制也失败', copyErr)
// 最后尝试直接使用临时路径(真机上通常可用)
app.tempFile = { path: tempFilePath }
}
})
} else {
// 如果无法获取文件目录,直接使用临时路径(真机上通常可用)
app.tempFile = { path: tempFilePath }
}
} catch (e) {
console.error('文件处理异常', e)
// 最后尝试直接使用临时路径(真机上通常可用)
app.tempFile = { path: tempFilePath }
}
}
})
// #endif
},
// 选择图片
chooseImage() {
const app = this
// 选择图片
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success({ tempFiles }) {
app.tempFile = tempFiles[0]
app.avatarUrl = app.tempFile.path
}
});
},
// 上传图片
uploadFile() {
const app = this
return OrderApi.apiUploadFile([app.tempFile])
.then(res => {
app.form.avatarUrl = res[0].data[0].url
app.tempFile = null
return true
})
.catch(() => {
return false
})
},
// 确认提交头像昵称
async handleSubmit() {
const app = this
console.log('handleSubmit', app.form, app.tempFile)
if (app.disabled === true) {
return
}
if (app.tempFile === undefined || Verify.isEmpty(app.form.nickName)) {
app.$toast('请填写头像和昵称~')
return
}
// 按钮禁用
app.disabled = true
// 先上传头像图片
if (!await app.uploadFile()) {
app.$toast('很抱歉,头像上传失败')
app.disabled = false
return
}
store.dispatch('UpdateUserInfo', { buyerName: app.form.nickName, avatar: app.form.avatarUrl })
},
// 将oauth提交给父级
// 这里要重新获取code, 因为上一次获取的code不能复用(会报错)
async onEmitSuccess(userInfo) {
const app = this
app.$emit('success', {
oauth: 'MP-WEIXIN', // 第三方登录类型: MP-WEIXIN
code: await app.getCode(), // 微信登录的code, 用于换取openid
userInfo // 微信用户信息
})
},
/**
* 暂不登录
*/
handleCancel() {
// 跳转回原页面
this.onNavigateBack()
},
/**
* 登录成功-跳转回原页面
*/
onNavigateBack(delta = 1) {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: Number(delta || 1)
})
} else {
this.$navTo('pages/index/index')
}
},
/**
* 切换协议同意状态
*/
toggleAgreement() {
this.agreedToTerms = !this.agreedToTerms
},
/**
* 打开协议页面
*/
openAgreement() {
this.$navTo('pages/agreement/index')
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 60rpx;
font-size: 32rpx;
background: #fff;
min-height: 100vh;
}
.personal {
// 页面头部
.header {
padding-top: 120rpx;
margin-bottom: 60rpx;
.title {
margin-bottom: 20rpx;
color: #191919;
font-size: 44rpx;
}
.sub-title {
margin-bottom: 70rpx;
color: #b3b3b3;
font-size: 28rpx;
}
}
.login-form {
margin-bottom: 90rpx;
}
// 输入框元素
.form-item {
display: flex;
align-items: center;
padding: 18rpx;
border-bottom: 1rpx solid #f3f1f2;
margin-bottom: 30rpx;
min-height: 96rpx;
&--label {
min-width: 150rpx;
font-size: 28rpx;
line-height: 50rpx;
}
&--input {
font-size: 28rpx;
letter-spacing: 1rpx;
flex: 1;
height: 100%;
}
&--parts {
min-width: 100rpx;
}
}
}
.authorize {
.store-info {
padding: 80rpx 0 48rpx;
border-bottom: 1rpx solid #e3e3e3;
margin-bottom: 72rpx;
text-align: center;
.header {
width: 190rpx;
height: 190rpx;
border: 4rpx solid #fff;
margin: 0 auto 0;
.image {
display: block;
width: 100%;
height: 100%;
}
}
}
.auth-title {
color: #585858;
font-size: 34rpx;
margin-bottom: 40rpx;
}
.auth-subtitle {
color: #888;
margin-bottom: 88rpx;
font-size: 28rpx;
}
}
.login-btn {
margin-bottom: 20rpx;
.button {
width: 100%;
height: 86rpx;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
font-size: 30rpx;
border-radius: 80rpx;
letter-spacing: 5rpx;
display: flex;
justify-content: center;
align-items: center;
border: none;
padding: 0;
line-height: 86rpx;
// 禁用按钮
&.disabled {
opacity: 0.6;
}
// 微信小程序button组件样式重置
&::after {
border: none;
}
}
}
.no-login-btn {
.button {
height: 86rpx;
background: #dfdfdf;
color: #fff;
font-size: 30rpx;
border-radius: 80rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.agreement-box {
margin-bottom: 30rpx;
padding: 0 10rpx;
.agreement-checkbox {
display: flex;
align-items: flex-start;
font-size: 24rpx;
color: #666;
line-height: 1.6;
.checkbox-icon {
width: 32rpx;
height: 32rpx;
border: 2rpx solid #ddd;
border-radius: 4rpx;
margin-right: 12rpx;
margin-top: 2rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.3s;
&.checked {
background: $main-bg;
border-color: $main-bg;
}
.icon-check2 {
color: #fff;
font-size: 20rpx;
font-weight: bold;
}
}
.agreement-text {
flex: 1;
line-height: 1.6;
}
.agreement-link {
color: $main-bg;
text-decoration: underline;
}
}
}
</style>