2025-07-28 10:40:48 +08:00

899 lines
20 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">
<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" @click="handleEditClick">
<u-icon name="edit-pen" color="#fff" size="24"></u-icon>
</view>
<u-loading-icon v-if="uploading" mode="circle" color="#2979ff" size="28"></u-loading-icon>
</view>
</view>
<view class="name-section" @click="handleEditClick">
<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 || '这家伙很懒,什么都没有写~'}}</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="showEditModal" :title="isLogin ? '修改用户信息' : '微信授权登录'" :show-confirm-button="isLogin"
:show-cancel-button="isLogin" @confirm="handleSubmit" @cancel="handleCancel" confirm-color="#2979ff"
cancel-color="#606266">
<view class="modal-content">
<!-- 未登录时显示授权按钮 -->
<view v-if="!isLogin" class="auth-section">
<view class="auth-title">请先登录以使用完整功能</view>
<button v-if="!showPhoneButton" class="auth-button" open-type="getUserInfo"
@getuserinfo="onGetUserInfo">
<image class="wechat-icon" src="/static/imgs/wechat.png"></image>
微信一键登录
</button>
<button v-if="showPhoneButton" type="default" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber" class="auth-button phone-button">
<image class="phone-icon" src="/static/imgs/phone.png"></image>
授权获取手机号
</button>
<view class="auth-tip">登录即表示同意用户协议隐私政策</view>
</view>
<!-- 已登录时显示编辑表单 -->
<view v-else class="edit-section">
<view class="form-item">
<text class="form-label">头像</text>
<u-upload :file-list="avatarList" :max-count="1" @afterRead="handleAvatarUpload"
@delete="handleAvatarDelete" width="160" height="160" preview-full-image>
<view slot="delete" class="custom-delete">
<u-icon name="close" color="#fff" size="24"></u-icon>
</view>
</u-upload>
<view class="upload-tip">点击上传建议尺寸1:1</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">
<view class="phone-dialog-content">
<text>社区电话</text>
<text class="phone-number">{{communityPhone}}</text>
</view>
</u-modal>
<Footer></Footer>
</view>
</template>
<script>
import Footer from '@/components/footer_common.vue';
import {
get,
post
} from '@/utils/request';
import {
IMAGE_BASE_URL,
BASE_URL
} from '@/utils/config';
export default {
components: {
Footer
},
data() {
return {
isLogin: false,
showEditModal: false,
showPhoneButton: false,
avatarList: [],
formData: {
name: "",
bio: "",
},
userInfo: {
nickName: "",
avatarUrl: "",
bio: "",
phone: ""
},
uploading: false,
displayAvatar: '/static/imgs/index/nav.png',
choseList: [{
key: 1,
url: "/static/imgs/service/service_list.png",
name: '全部工单',
pageUrl: 'myTickets'
},
{
key: 2,
url: "/static/imgs/service/service_help.png",
name: '我的求助',
pageUrl: 'mySeekHelp'
},
{
key: 3,
url: "/static/imgs/service/service_service.png",
name: '周边服务',
pageUrl: 'neighbor'
},
{
key: 4,
url: "/static/imgs/service/service_friend.png",
name: '共享空间预约',
pageUrl: 'meetingList'
},
{
key: 5,
url: "/static/imgs/service/service_notice.png",
name: '设置',
pageUrl: 'serviceNoticeList'
},
{
key: 6,
url: "/static/imgs/service/service_phone.png",
name: '联系社区',
},
],
communityPhone: '0513-59000051',
showPhoneDialog: false
}
},
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();
}
},
// 更新用户信息
updateUserInfo(data) {
this.userInfo = {
nickName: data.name || data.nickName || '未命名用户',
avatarUrl: data.avatar || data.avatarUrl || '/static/imgs/index/nav.png',
bio: data.introduce || data.bio || '这家伙很懒,什么都没有写~',
phone: data.phone || ''
};
// 更新显示头像
this.displayAvatar = this.userInfo.avatarUrl || '/static/imgs/index/nav.png';
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: '这家伙很懒,什么都没有写~'
};
this.formData = {
name: '',
bio: ''
};
this.avatarList = [];
uni.removeStorageSync('token');
uni.removeStorageSync('userInfo');
},
// 处理编辑点击
handleEditClick() {
if (!this.isLogin) {
this.showEditModal = true;
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;
},
// 获取用户信息
onGetUserInfo(e) {
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'
});
this.showEditModal = false;
}
},
// 微信登录获取code
wxLogin() {
uni.showLoading({
title: '登录中...'
});
uni.login({
provider: 'weixin',
success: (loginRes) => {
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.showPhoneButton = true;
uni.showToast({
title: '请授权获取手机号',
icon: 'none'
});
} 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 (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);
} 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;
this.showEditModal = false;
this.showPhoneButton = false;
uni.showToast({
title: '登录成功'
});
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
uni.showToast({
title: '登录失败: ' + err.message,
icon: 'none'
});
console.error('登录完成处理失败:', err);
}
},
// 上传头像
async handleAvatarUpload(event) {
if (!this.isLogin) {
this.showEditModal = true;
return;
}
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 > 2 * 1024 * 1024) {
throw new Error('图片大小不能超过2MB');
}
// 上传到服务器
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: `${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(`${IMAGE_BASE_URL}${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 {
// 1. 提交修改请求
await post('/api/v1/app_auth/bind', {
avatar: avatarUrl,
introduce: this.formData.bio,
name: this.formData.name
});
// 2. 获取最新用户信息
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
// 3. 更新本地存储和页面数据
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
console.error('更新头像失败:', err);
throw err;
}
},
// 删除头像
handleAvatarDelete() {
this.avatarList = [];
this.displayAvatar = '/static/imgs/index/nav.png';
this.userInfo.avatarUrl = '';
},
// 提交表单
async handleSubmit() {
if (!this.isLogin) return;
const {
name,
bio
} = this.formData;
const avatarUrl = this.avatarList.length > 0 ? this.avatarList[0].url : "";
if (!name) {
uni.showToast({
title: '请输入姓名',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中...'
});
try {
// 1. 提交修改请求
await post('/api/v1/app_auth/bind', {
avatar: avatarUrl,
introduce: bio,
name: name
});
// 2. 获取最新用户信息
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
// 3. 更新本地存储和页面数据
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
uni.hideLoading();
uni.showToast({
title: '修改成功'
});
this.showEditModal = false;
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
uni.hideLoading();
uni.showToast({
title: '修改失败: ' + (err.message || '未知错误'),
icon: 'none'
});
console.error('修改失败:', err);
}
},
// 取消修改
handleCancel() {
this.showEditModal = false;
},
// 跳转页面
goPage(item) {
if (item.key === 6) { // 联系社区的特殊处理
this.handleContactCommunity();
return;
}
if (!this.checkLogin()) {
this.showEditModal = true;
return;
}
if (item.pageUrl) {
uni.navigateTo({
url: `/pages/${item.pageUrl}/index`
});
}
},
// 处理联系社区点击
handleContactCommunity() {
if (!this.checkLogin()) {
this.showEditModal = true;
return;
}
this.showPhoneDialog = true;
},
// 拨打社区电话
callCommunityPhone() {
uni.makePhoneCall({
phoneNumber: this.communityPhone,
success: () => {
console.log('拨打电话成功');
},
fail: (err) => {
uni.showToast({
title: '拨打电话失败',
icon: 'none'
});
console.error('拨打电话失败:', err);
}
});
this.showPhoneDialog = false;
},
// 检查登录状态
checkLogin() {
const token = uni.getStorageSync('token');
if (!token) {
uni.showToast({
title: '请先登录',
icon: 'none'
});
return false;
}
return true;
}
}
}
</script>
<style lang="scss" scoped>
.profile-page {
background-color: #f5f7fa;
padding: 20rpx;
min-height: 100vh;
box-sizing: border-box;
padding-bottom: 120rpx;
}
.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);
}
.u-loading-icon {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 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;
/* 编辑表单区域 */
.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;
}
}
/* 自定义删除按钮样式 */
.custom-delete {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background: #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);
}
}
/* 授权登录区域 */
.auth-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
.auth-title {
font-size: 32rpx;
color: #333;
margin-bottom: 40rpx;
text-align: center;
font-weight: 500;
}
.auth-button {
width: 100%;
height: 90rpx;
line-height: 90rpx;
background: #07C160;
color: #fff;
font-size: 30rpx;
border-radius: 45rpx;
margin-bottom: 30rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
&::after {
border: none;
}
&.phone-button {
background: #2979ff;
box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
}
&:active {
opacity: 0.9;
}
.wechat-icon,
.phone-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
}
.auth-tip {
font-size: 24rpx;
color: #999;
margin-top: 20rpx;
text-align: center;
}
}
.phone-dialog-content {
padding: 40rpx;
text-align: center;
font-size: 32rpx;
.phone-number {
font-weight: bold;
color: #2979ff;
margin-left: 10rpx;
}
}
}
</style>