qiuyuan 7854188d95 1
2026-01-21 16:16:41 +08:00

1007 lines
36 KiB
Vue
Raw Permalink 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="auth-icon" src="/static/imgs/phone.png"></image>
手机号快捷登录
</button>
<button class="auth-button"
@click="showEditModal=false">
取消登录授权
</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-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>
</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',
agreementChecked: false,
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: '联系社区',
},
{
key: 7,
url: "/static/imgs/service/service_xieyi.png",
name: '用户协议',
pageUrl: 'userAgreement'
},
{
key: 8,
url: "/static/imgs/service/service_zhengce.png",
name: '隐私政策',
pageUrl: 'agreement'
}
],
communityPhone: '0513-59000051',
showPhoneDialog: false
}
},
onShow() {
this.initData();
},
methods: {
// 新增:协议勾选状态变化
handleAgreementChange(e) {
this.agreementChecked = e.detail.value.length > 0;
},
// 新增:跳转到协议页面
goToAgreement(type) {
if (type === 'user') {
uni.navigateTo({
url: '/pages/userAgreement/index'
});
} else {
uni.navigateTo({
url: '/pages/agreement/index'
});
}
},
// 点击头像触发上传
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) {
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) {
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 (!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'
});
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 (!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);
} 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 {
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);
}
},
// 取消修改
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: 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);
}
.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 {
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);
&::after {
border: none;
}
.auth-icon,
.phone-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
}
.phone-button {
background: #07C160; /* 微信绿色,保持原有成功色 */
}
.auth-agreement {
margin-top: 20rpx;
font-size: 24rpx;
color: #666;
.agreement-link {
color: #2979ff;
}
}
}
.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>