qiuyuan e92f7e00da 1
2026-03-03 16:22:37 +08:00

1071 lines
37 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="profile-page">
<!-- 用户信息区域 -->
<view class="user-info-card" @click="handleUserInfoClick">
<view class="user-info">
<view class="avatar-section">
<view class="avatar-wrapper">
<image class="avatar" :src="userInfo.avatarUrl || '/static/imgs/index/nav.png'"
mode="aspectFill"></image>
<view class="edit-icon" v-if="isLogin" @click.stop="handleEditClick">
<u-icon name="edit-pen" color="#fff" size="24"></u-icon>
</view>
</view>
</view>
<view class="name-section">
<view class="name">{{userInfo.nickName || '点击登录/注册'}}</view>
<view class="phone">{{userInfo.phone || '登录后体验更多功能'}}</view>
</view>
<view class="description-card">
<text class="title">个人简介</text>
<text class="content">{{userInfo.bio || (isLogin ? '这家伙很懒,什么都没有写~' : '登录后可查看完整信息')}}</text>
</view>
</view>
</view>
<!-- 功能按钮区域 -->
<view class="function-card">
<view class="button-item" v-for="(item, index) in choseList" :key="index" @click="goPage(item)">
<view class="button-content">
<image class="icon" :src='item.url' mode="aspectFit"></image>
<text class="label">{{item.name}}</text>
</view>
<u-icon name="arrow-right" color="#c8c9cc" size="28"></u-icon>
</view>
</view>
<!-- 登录弹窗 - 仅在用户主动点击后显示 -->
<u-modal :show="showLoginModal" title="手机号快捷登录" :show-confirm-button="false" :show-cancel-button="true"
@cancel="showLoginModal = false" cancel-text="暂不登录" :closeOnClickOverlay="true">
<view class="modal-content">
<view class="auth-section">
<view class="auth-title">请先阅读并同意相关协议</view>
<view class="auth-agreement">
<checkbox-group @change="handleAgreementChange">
<label>
<checkbox :checked="agreementChecked" color="#2979ff" style="transform:scale(0.7)"/>
我已阅读并同意
<text class="agreement-link" @click.stop="goToAgreement('userAgreement')">《用户协议》</text>
<text class="agreement-link" @click.stop="goToAgreement('agreement')">《隐私政策》</text>
</label>
</checkbox-group>
</view>
<button
class="auth-button"
:class="{ 'disabled': !agreementChecked }"
:disabled="!agreementChecked"
open-type="getUserInfo"
@getuserinfo="onGetUserInfo">
<image class="auth-icon" src="/static/imgs/phone.png"></image>
微信用户一键登录
</button>
<view class="auth-tip">
登录表示您同意我们的用户协议和隐私政策
</view>
</view>
</view>
</u-modal>
<!-- 手机号授权弹窗 -->
<u-modal :show="showPhoneModal" title="绑定手机号" :show-confirm-button="false" :show-cancel-button="true"
@cancel="showPhoneModal = false" cancel-text="暂不绑定" :closeOnClickOverlay="true">
<view class="modal-content">
<view class="auth-section">
<view class="auth-title">为了提供更好的服务,请绑定手机号</view>
<view class="auth-agreement">
<checkbox-group @change="handleAgreementChange">
<label>
<checkbox :checked="agreementChecked" color="#2979ff" style="transform:scale(0.7)"/>
我已阅读并同意
<text class="agreement-link" @click.stop="goToAgreement('userAgreement')">《用户协议》</text>
<text class="agreement-link" @click.stop="goToAgreement('agreement')">《隐私政策》</text>
</label>
</checkbox-group>
</view>
<button
class="auth-button phone-button"
:class="{ 'disabled': !agreementChecked }"
:disabled="!agreementChecked"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber">
<image class="phone-icon" src="/static/imgs/phone.png"></image>
授权获取手机号
</button>
</view>
</view>
</u-modal>
<!-- 修改用户信息弹窗 -->
<u-modal :show="showEditModal" title="修改用户信息" :show-confirm-button="true"
:show-cancel-button="true" @confirm="handleSubmit" @cancel="showEditModal = false" confirm-color="#2979ff"
cancel-color="#606266">
<view class="modal-content">
<view class="edit-section">
<view class="form-item">
<text class="form-label">头像:</text>
<view class="avatar-upload-container" @click="onAvatarClick">
<view class="avatar-preview">
<image
v-if="avatarList.length > 0"
:src="avatarList[0].url"
mode="aspectFill"
class="avatar-img">
</image>
<view v-else class="avatar-placeholder">
<u-icon name="plus" color="#c0c4cc" size="28"></u-icon>
<text class="placeholder-text">点击上传</text>
</view>
<view v-if="avatarList.length > 0" class="avatar-delete" @click.stop="handleAvatarDelete">
<u-icon name="close" color="#fff" size="26"></u-icon>
</view>
</view>
</view>
<view class="upload-tip">点击上传,支持拍照或从相册选择</view>
</view>
<view class="form-item">
<text class="form-label">姓名:</text>
<u-input v-model="formData.name" placeholder="请输入姓名" border="bottom" clearable></u-input>
</view>
<view class="form-item">
<text class="form-label">简介:</text>
<u-input v-model="formData.bio" type="textarea" placeholder="请输入简介" :maxlength="200"
height="120" border="bottom">
</u-input>
<view class="word-count">{{formData.bio.length}}/200</view>
</view>
</view>
</view>
</u-modal>
<u-modal
:show="showPhoneDialog"
title="联系社区"
confirm-text="拨打"
cancel-text="取消"
@confirm="callCommunityPhone"
@cancel="showPhoneDialog = false"
:closeOnClickOverlay="true">
<view class="phone-dialog-content">
<text>社区电话</text>
<text class="phone-number">{{communityPhone}}</text>
</view>
</u-modal>
<Copyright/>
<Footer></Footer>
</view>
</template>
<script>
import Footer from '@/components/footer_common.vue';
import Copyright from '@/components/gx-copyright.vue';
import {
get,
post
} from '@/utils/request';
import {
IMAGE_BASE_URL,
BASE_URL
} from '@/utils/config';
export default {
components: {
Footer,Copyright
},
data() {
return {
isLogin: false,
showLoginModal: false, // 登录弹窗
showPhoneModal: false, // 手机号绑定弹窗
showEditModal: false, // 编辑信息弹窗
showPhoneDialog: false, // 联系社区弹窗
agreementChecked: false, // 协议勾选状态
avatarList: [],
formData: {
name: "",
bio: "",
},
userInfo: {
nickName: "",
avatarUrl: "",
bio: "",
phone: ""
},
uploading: false,
displayAvatar: '/static/imgs/index/nav.png',
choseList: [
{
key: 4,
url: "/static/imgs/service/service_friend.png",
name: '会客厅预约',
pageUrl: 'meetingList',
needLogin: true // 标记需要登录的功能
},
{
key: 3,
url: "/static/imgs/service/service_service.png",
name: '周边服务',
pageUrl: 'neighbor',
needLogin: true
},
{
key: 5,
url: "/static/imgs/service/service_notice.png",
name: '设置',
needLogin: true
},
{
key: 6,
url: "/static/imgs/service/service_phone.png",
name: '联系社区',
needLogin: false
},
{
key: 7,
url: "/static/imgs/service/service_xieyi.png",
name: '用户协议',
pageUrl: 'userAgreement',
needLogin: false
},
{
key: 8,
url: "/static/imgs/service/service_zhengce.png",
name: '隐私政策',
pageUrl: 'agreement',
needLogin: false
}
],
communityPhone: '0513-59000051',
wxCode: '' // 存储微信登录code
}
},
onShow() {
this.initData();
},
methods: {
// 初始化数据
async initData() {
const token = uni.getStorageSync('token');
if (token) {
try {
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
this.isLogin = true;
} else {
this.clearUserData();
}
} catch (err) {
console.error('获取用户信息失败:', err);
this.clearUserData();
}
} else {
this.clearUserData();
}
},
// 点击用户信息区域
handleUserInfoClick() {
if (!this.isLogin) {
this.showLoginModal = true;
this.agreementChecked = false; // 重置协议勾选
}
},
// 处理编辑点击
handleEditClick() {
if (!this.isLogin) {
this.showLoginModal = true;
this.agreementChecked = false;
return;
}
this.formData = {
name: this.userInfo.nickName,
bio: this.userInfo.bio
};
if (this.userInfo.avatarUrl) {
this.avatarList = [{
url: this.userInfo.avatarUrl
}];
} else {
this.avatarList = [];
}
this.showEditModal = true;
},
// 协议勾选状态变化
handleAgreementChange(e) {
this.agreementChecked = e.detail.value.length > 0;
},
// 跳转到协议页面
goToAgreement(type) {
uni.navigateTo({
url: `/pages/${type}/index`
});
},
// 获取用户信息
onGetUserInfo(e) {
if (!this.agreementChecked) {
uni.showToast({
title: '请先阅读并同意协议',
icon: 'none'
});
return;
}
if (e.detail.userInfo) {
this.userInfo.nickName = e.detail.userInfo.nickName;
this.userInfo.avatarUrl = e.detail.userInfo.avatarUrl;
this.wxLogin();
} else {
uni.showToast({
title: '您拒绝了授权',
icon: 'none'
});
}
},
// 微信登录
wxLogin() {
uni.showLoading({
title: '登录中...'
});
this.showLoginModal = false;
uni.login({
provider: 'weixin',
success: (loginRes) => {
this.wxCode = loginRes.code;
this.sendLoginRequest(loginRes.code);
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
title: '登录失败',
icon: 'none'
});
console.error('登录失败:', err);
}
});
},
// 发送登录请求
async sendLoginRequest(code) {
try {
const res = await post('/api/v1/apps/login/weixins', {
code: code
});
if (res.success) {
uni.setStorageSync('token', res.data.access_token);
// 检查是否需要绑定手机号
if (!res.data.phone) {
this.showPhoneModal = true;
this.agreementChecked = false;
uni.hideLoading();
} else {
await this.loginComplete(res.data);
}
} else {
throw new Error(res.message || '登录失败');
}
} catch (err) {
uni.hideLoading();
uni.showToast({
title: '登录失败,请稍后再试',
icon: 'none'
});
console.error('登录接口错误:', err);
}
},
// 获取手机号
async getPhoneNumber(e) {
if (!this.agreementChecked) {
uni.showToast({
title: '请先阅读并同意协议',
icon: 'none'
});
return;
}
if (e.detail.errMsg !== "getPhoneNumber:ok") {
uni.showToast({
title: '获取手机号失败',
icon: 'none'
});
return;
}
uni.showLoading({
title: '获取手机号中...'
});
try {
const res = await post('/api/v1/app_auth/weixin/bind_phone', {
code: e.detail.code
});
if (res.success) {
await this.loginComplete(res.data);
this.showPhoneModal = false;
} else {
throw new Error(res.message || '绑定手机号失败');
}
} catch (err) {
uni.hideLoading();
uni.showToast({
title: '绑定手机号失败',
icon: 'none'
});
console.error('绑定手机号失败:', err);
}
},
// 登录完成处理
async loginComplete(data) {
try {
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
this.isLogin = true;
uni.hideLoading();
uni.showToast({
title: '登录成功'
});
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
uni.hideLoading();
uni.showToast({
title: '登录失败: ' + err.message,
icon: 'none'
});
console.error('登录完成处理失败:', err);
}
},
// 更新用户信息
updateUserInfo(data) {
const avatarUrl = data.avatar ? `${IMAGE_BASE_URL}${data.avatar}` : '/static/imgs/index/nav.png';
this.userInfo = {
nickName: data.name || data.nickName || '未命名用户',
avatarUrl: avatarUrl,
bio: data.introduce || data.bio || '这家伙很懒,什么都没有写~',
phone: data.phone || ''
};
this.displayAvatar = this.userInfo.avatarUrl;
this.formData = {
name: this.userInfo.nickName,
bio: this.userInfo.bio
};
if (this.userInfo.avatarUrl) {
this.avatarList = [{
url: this.userInfo.avatarUrl
}];
}
},
// 清除用户数据
clearUserData() {
this.isLogin = false;
this.userInfo = {
nickName: '点击登录/注册',
avatarUrl: '/static/imgs/index/nav.png',
bio: '登录后可查看完整信息',
phone: ''
};
this.formData = {
name: '',
bio: ''
};
this.avatarList = [];
},
// 点击头像上传
onAvatarClick() {
if (!this.isLogin) {
this.showLoginModal = true;
this.agreementChecked = false;
return;
}
if (this.uploading) return;
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePaths = res.tempFilePaths;
if (tempFilePaths && tempFilePaths.length > 0) {
const file = {
url: tempFilePaths[0]
};
this.handleAvatarUpload({ file });
}
},
fail: (err) => {
console.error('选择图片失败', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// 上传头像
async handleAvatarUpload(event) {
const { file } = event;
if (!file || !file.url) return;
this.uploading = true;
try {
const fileInfo = await new Promise((resolve, reject) => {
uni.getFileInfo({
filePath: file.url,
success: resolve,
fail: reject
});
});
if (fileInfo.size > 3 * 1024 * 1024) {
throw new Error('图片大小不能超过3MB');
}
const avatarUrl = await this.uploadAvatar(file.url);
await this.updateUserAvatar(avatarUrl);
uni.showToast({
title: '头像上传成功'
});
} catch (error) {
console.error('头像上传失败:', error);
uni.showToast({
title: '头像上传失败: ' + error.message,
icon: 'none'
});
} finally {
this.uploading = false;
}
},
// 上传头像到服务器
uploadAvatar(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${IMAGE_BASE_URL}/api/v1/upload`,
filePath: filePath,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`
},
success: (uploadRes) => {
try {
const res = JSON.parse(uploadRes.data);
if (res && res.success) {
resolve(res.data);
} else {
reject(new Error(res.message || '上传失败'));
}
} catch (e) {
reject(new Error('解析响应失败'));
}
},
fail: (err) => {
reject(new Error('上传失败: ' + JSON.stringify(err)));
}
});
});
},
// 更新用户头像
async updateUserAvatar(avatarUrl) {
try {
await post('/api/v1/app_auth/bind', {
avatar: avatarUrl,
introduce: this.formData.bio,
name: this.formData.name
});
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
console.error('更新头像失败:', err);
throw err;
}
},
// 删除头像
handleAvatarDelete() {
uni.showModal({
title: '提示',
content: '确定要删除当前头像吗?',
confirmText: '删除',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.avatarList = [];
this.displayAvatar = '/static/imgs/index/nav.png';
this.userInfo.avatarUrl = '';
uni.showToast({
title: '已删除',
icon: 'none'
});
}
}
});
},
// 提交表单
async handleSubmit() {
if (!this.isLogin) return;
const { name, bio } = this.formData;
let avatarUrl = this.avatarList.length > 0 ? this.avatarList[0].url : "";
if (avatarUrl && avatarUrl.includes(IMAGE_BASE_URL)) {
avatarUrl = avatarUrl.replace(IMAGE_BASE_URL, '');
}
if (!name) {
uni.showToast({
title: '请输入姓名',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中...'
});
try {
await post('/api/v1/app_auth/bind', {
avatar: avatarUrl,
introduce: bio,
name: name
});
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
this.showEditModal = false;
uni.hideLoading();
uni.showToast({
title: '修改成功'
});
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
uni.hideLoading();
uni.showToast({
title: '修改失败: ' + (err.message || '未知错误'),
icon: 'none'
});
console.error('修改失败:', err);
}
},
// 跳转页面
goPage(item) {
// 联系社区
if (item.key === 6) {
this.handleContactCommunity();
return;
}
// 需要登录的功能
if (item.needLogin && !this.isLogin) {
this.showLoginModal = true;
this.agreementChecked = false;
return;
}
// 设置按钮
if (item.key === 5) {
this.handleEditClick();
return;
}
// 协议页面(不需要登录)
if (item.pageUrl) {
uni.navigateTo({
url: `/pages/${item.pageUrl}/index`
});
}
},
// 处理联系社区
handleContactCommunity() {
if (!this.isLogin) {
this.showLoginModal = true;
this.agreementChecked = false;
return;
}
uni.showModal({
title: '联系社区',
content: '是否要拨打社区电话:' + this.communityPhone,
confirmText: '拨打',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.callCommunityPhone();
}
}
});
},
// 拨打社区电话
callCommunityPhone() {
uni.makePhoneCall({
phoneNumber: this.communityPhone,
success: () => {
console.log('拨打电话成功');
},
fail: (err) => {
uni.showToast({
title: '拨打电话失败',
icon: 'none'
});
console.error('拨打电话失败:', err);
}
});
this.showPhoneDialog = false;
}
}
}
</script>
<style lang="scss" scoped>
.profile-page {
background-color: #f5f7fa;
padding: 20rpx;
min-height: 100vh;
box-sizing: border-box;
padding-bottom: calc(100rpx + 50rpx);
}
.user-info-card {
background: #fff;
border-radius: 16rpx;
padding: 40rpx 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
.user-info {
display: flex;
flex-direction: column;
align-items: center;
}
}
.avatar-section {
margin-bottom: 20rpx;
.avatar-wrapper {
position: relative;
width: 140rpx;
height: 140rpx;
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
border: 4rpx solid #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.edit-icon {
position: absolute;
right: 0;
bottom: 0;
background: #2979ff;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
}
}
.name-section {
width: 100%;
align-items: center;
text-align: center;
margin-bottom: 30rpx;
.name {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.phone {
color: #999;
font-size: 24rpx;
}
}
.description-card {
background: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
width: 100%;
text-align: center;
.title {
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
display: block;
}
.content {
font-size: 26rpx;
color: #999;
line-height: 1.5;
}
}
.function-card {
background: #fff;
border-radius: 16rpx;
padding: 0 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
.button-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 0;
border-bottom: 1rpx solid #f2f3f5;
&:active {
background-color: #f8f8f8;
}
&:last-child {
border-bottom: none;
}
.button-content {
display: flex;
align-items: center;
flex: 1;
}
.icon {
width: 48rpx;
height: 48rpx;
margin-right: 24rpx;
}
.label {
font-size: 30rpx;
color: #333;
flex: 1;
}
}
}
.modal-content {
padding: 0 30rpx;
width: 90%;
margin: 0 auto;
max-height: 70vh;
overflow-y: auto;
}
// 授权登录区域
.auth-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
.auth-title {
font-size: 32rpx;
color: #333;
margin-bottom: 30rpx;
text-align: center;
font-weight: 500;
}
.auth-agreement {
margin-bottom: 30rpx;
font-size: 24rpx;
color: #666;
.agreement-link {
color: #2979ff;
display: inline-block;
}
}
.auth-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 90rpx;
background: #2979ff;
color: #fff;
font-size: 30rpx;
border-radius: 45rpx;
margin-bottom: 20rpx;
padding: 0 30rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
&.disabled {
opacity: 0.5;
pointer-events: none;
}
&::after {
border: none;
}
.auth-icon,
.phone-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
}
.phone-button {
background: #07C160;
}
.auth-tip {
font-size: 24rpx;
color: #999;
text-align: center;
margin-top: 20rpx;
}
}
// 编辑表单区域
.edit-section {
padding: 20rpx 0;
.form-item {
margin-bottom: 40rpx;
.form-label {
display: block;
font-size: 28rpx;
color: #606266;
margin-bottom: 16rpx;
font-weight: 500;
}
.upload-tip {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
text-align: center;
}
.word-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
}
}
// 头像上传容器
.avatar-upload-container {
width: 160rpx;
height: 160rpx;
margin: 0 auto;
position: relative;
border-radius: 8rpx;
overflow: hidden;
}
// 头像预览区域
.avatar-preview {
width: 100%;
height: 100%;
position: relative;
border-radius: 8rpx;
overflow: hidden;
background-color: #f5f7fa;
display: flex;
align-items: center;
justify-content: center;
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #c0c4cc;
.placeholder-text {
font-size: 24rpx;
margin-top: 10rpx;
}
}
.avatar-delete {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background-color: #f56c6c;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
}
}
.phone-dialog-content {
padding: 40rpx;
text-align: center;
font-size: 32rpx;
.phone-number {
font-weight: bold;
color: #2979ff;
margin-left: 10rpx;
}
}
</style>