714 lines
17 KiB
Vue
714 lines
17 KiB
Vue
<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>
|
||
</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.pageUrl)">
|
||
<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">
|
||
<button v-if="!showPhoneButton"
|
||
class="auth-button"
|
||
open-type="getUserInfo"
|
||
@getuserinfo="onGetUserInfo">
|
||
微信一键登录
|
||
</button>
|
||
<button v-if="showPhoneButton"
|
||
type="default"
|
||
open-type="getPhoneNumber"
|
||
@getphonenumber="getPhoneNumber"
|
||
class="auth-button phone-button">
|
||
授权获取手机号
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 已登录时显示编辑表单 -->
|
||
<view v-else>
|
||
<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>
|
||
</u-upload>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<text class="form-label">姓名:</text>
|
||
<u-input v-model="formData.name" placeholder="请输入姓名" border="bottom"></u-input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<text class="form-label">简介:</text>
|
||
<u-input
|
||
v-model="formData.bio"
|
||
type="textarea"
|
||
placeholder="请输入简介"
|
||
:maxlength="200"
|
||
height="320">
|
||
</u-input>
|
||
</view>
|
||
</view>
|
||
</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: ""
|
||
},
|
||
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: '联系社区',
|
||
},
|
||
],
|
||
}
|
||
},
|
||
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.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;
|
||
|
||
try {
|
||
uni.showLoading({ title: '上传中...' });
|
||
|
||
// 检查文件大小
|
||
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);
|
||
this.avatarList = [{ url: avatarUrl }];
|
||
this.userInfo.avatarUrl = avatarUrl;
|
||
|
||
uni.hideLoading();
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '头像上传失败: ' + error.message,
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
|
||
// 上传头像到服务器
|
||
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)));
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
// 删除头像
|
||
handleAvatarDelete() {
|
||
this.avatarList = [];
|
||
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(page) {
|
||
if (!this.checkLogin()) {
|
||
this.showEditModal = true;
|
||
return;
|
||
}
|
||
|
||
if (page) {
|
||
uni.navigateTo({
|
||
url: `/pages/${page}/index`
|
||
});
|
||
}
|
||
},
|
||
|
||
// 检查登录状态
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
.name-section {
|
||
// display: flex;
|
||
width: 100%;
|
||
align-items: center;
|
||
text-align: center;
|
||
margin-bottom: 30rpx;
|
||
|
||
.name {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
// margin-right: 12rpx;
|
||
}
|
||
.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;
|
||
|
||
.form-item {
|
||
margin-bottom: 40rpx;
|
||
|
||
.form-label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #606266;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
}
|
||
|
||
.auth-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 40rpx 0;
|
||
|
||
.auth-button {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
background: #07C160;
|
||
color: #fff;
|
||
font-size: 26rpx;
|
||
border-radius: 40rpx;
|
||
margin-bottom: 20rpx;
|
||
text-align: center;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&.phone-button {
|
||
background: #2979ff;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |