2025-08-12 16:47:10 +08:00

944 lines
34 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" @click="onAvatarClick"></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>
<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,
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: '设置'
},
{
key: 6,
url: "/static/imgs/service/service_phone.png",
name: '联系社区',
},
],
communityPhone: '0513-59000051',
showPhoneDialog: false
}
},
onShow() {
this.initData();
},
methods: {
// 新增:点击头像触发上传
onAvatarClick() {
if (!this.isLogin) {
this.showEditModal = true;
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) {
// 构造一个类似file对象的结构用于handleAvatarUpload
const file = {
url: tempFilePaths[0]
};
// 调用上传方法
this.handleAvatarUpload({ file });
}
},
fail: (err) => {
console.error('选择图片失败', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// 其他方法保持不变...
handleCloseModal() {
this.showPhoneDialog = false;
},
// 初始化数据
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) {
// 显示时拼接IMAGE_BASE_URL
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: '这家伙很懒,什么都没有写~'
};
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: '登录中...'
});
this.showEditModal = false;
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 {
// 获取最新用户信息
this.showEditModal = false;
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.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 > 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 {
// 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() {
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 {
// 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);
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);
}
},
// 取消修改
handleCancel() {
this.showEditModal = false;
},
// 跳转页面
goPage(item) {
if (item.key === 6) { // 联系社区的特殊处理
this.handleContactCommunity();
return;
}
if (item.key === 5) { // 设置的特殊处理
this.handleEditClick(); // 调用编辑用户信息的方法
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;
}
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;
},
// 检查登录状态
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: 190rpx;
}
.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;
}
}
}
/* 授权登录区域 */
.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;
}
}
}
// 头像上传容器
.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);
}
}
</style>