瑶都万能墙
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

797 lines
18 KiB

<template>
<view class="profile-page">
<!-- 顶部导航 -->
<!-- <navbar
:title="userProfile.nickName || '用户主页'"
:leftClick="true"
:moreClick="showMoreOptions"
bgColor="transparent"
color="#fff"
@leftClick="$utils.navigateBack"
/> -->
<!-- 用户信息头部 -->
<view class="profile-header">
<!-- 背景装饰 -->
<view class="header-bg"></view>
<!-- 返回按钮 -->
<view class="back-btn" @click="$utils.navigateBack">
<uv-icon name="arrow-left" size="30rpx" color="#fff"></uv-icon>
</view>
<!-- 用户基本信息 -->
<view class="user-info">
<view class="user-avatar">
<image :src="userProfile.headImage" mode="aspectFill" @click="previewAvatar"></image>
<!-- VIP标识 -->
<view class="vip-badge" v-if="userProfile.isPay">
<text>{{ getVipLevel(userProfile.isPay) }}</text>
</view>
</view>
<view class="user-details">
<view class="username">{{ userProfile.nickName }}</view>
<view class="user-tags">
<!-- 性别年龄 -->
<view class="tag gender-tag" v-if="userProfile.sex">
<uv-icon :name="sexIcons[userProfile.sex]" size="24rpx" :color="sexColors[userProfile.sex]"></uv-icon>
<text>{{ userProfile.sex }}</text>
<text v-if="userProfile.yearDate">{{ getAge() }}</text>
</view>
<!-- 地址 -->
<view class="tag location-tag" v-if="userProfile.address">
<uv-icon name="map-pin" size="20rpx" color="#666"></uv-icon>
<text>{{ userProfile.address }}</text>
</view>
<!-- 认证状态 -->
<view class="tag auth-tag" v-if="userProfile.idCardOpen">
<uv-icon name="checkmark-circle-fill" size="20rpx" color="#52c41a"></uv-icon>
<text>{{ getAuthText(userProfile.idCardOpen) }}</text>
</view>
</view>
<!-- 学校信息 -->
<view class="school-info" v-if="userProfile.czSchool || userProfile.gzSchool">
<text v-if="userProfile.czSchool">🎓 {{ userProfile.czSchool }}</text>
<text v-if="userProfile.gzSchool">🏫 {{ userProfile.gzSchool }}</text>
</view>
</view>
</view>
<!-- 数据统计 -->
<view class="stats-row">
<view class="stat-item" @click="showFans">
<view class="stat-number">{{ userProfile.intentionNum || 0 }}</view>
<view class="stat-label">粉丝</view>
</view>
<view class="stat-item" @click="showFollowing">
<view class="stat-number">{{ userProfile.followNum || 0 }}</view>
<view class="stat-label">关注</view>
</view>
<view class="stat-item" @click="showLikes">
<view class="stat-number">{{ userProfile.likeNum || 0 }}</view>
<view class="stat-label">获赞</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-buttons" v-if="!isCurrentUser">
<button class="follow-btn" :class="{followed: isFollowed}" @click="toggleFollow">
<uv-icon :name="isFollowed ? 'checkmark' : 'plus'" size="24rpx"></uv-icon>
<text>{{ isFollowed ? '已关注' : '关注' }}</text>
</button>
<button class="message-btn" @click="sendMessage">
<uv-icon name="chat" size="24rpx"></uv-icon>
<text>私信</text>
</button>
</view>
</view>
<!-- 内容标签页 -->
<view class="content-tabs">
<uv-tabs
:list="contentTabs"
:current="currentTabIndex"
:activeStyle="{color: '#333', fontWeight: 600}"
lineColor="#5baaff"
lineHeight="6rpx"
lineWidth="40rpx"
keyName="name"
@click="onTabClick"
/>
</view>
<!-- 内容区域 -->
<view class="content-container">
<!-- 帖子 - 瀑布流展示 -->
<view v-if="currentTabIndex === 0" class="posts-content">
<waterfallContainer
v-if="postsList.length > 0"
:list="postsList"
@item-click="onPostClick"
@item-like="onPostLike"
/>
</view>
<!-- 租房信息 -->
<view v-else-if="currentTabIndex === 1" class="renting-content">
<rentingItem
v-for="(item, index) in rentingList"
:key="index"
:item="item"
@click="onRentingClick(item)"
/>
</view>
<!-- 招聘信息 -->
<view v-else-if="currentTabIndex === 2" class="work-content">
<workItem
v-for="(item, index) in workList"
:key="index"
:item="item"
@click="onWorkClick(item)"
/>
</view>
<!-- 店铺信息 -->
<view v-else-if="currentTabIndex === 3" class="shop-content">
<gourmetItem
v-for="(item, index) in shopList"
:key="index"
:item="item"
@click="onShopClick(item)"
/>
</view>
<!-- 加载更多提示 -->
<view v-if="showLoadMore" class="load-more-state">
<uv-loading-icon size="32"></uv-loading-icon>
<text>加载更多...</text>
</view>
<!-- 没有更多数据提示 -->
<view v-if="showNoMore" class="no-more-state">
<text>— 没有更多了 —</text>
</view>
</view>
<!-- 空状态 -->
<view v-if="showEmptyState" class="empty-state">
<uv-empty
:text="getEmptyText()"
:icon="getEmptyIcon()"
iconSize="120"
></uv-empty>
</view>
<!-- 初始加载状态 -->
<view v-if="loading && currentList.length === 0" class="loading-state">
<uv-loading-icon size="40"></uv-loading-icon>
<text>加载中...</text>
</view>
</view>
</template>
<script>
import navbar from '@/components/base/navbar.vue'
import waterfallContainer from '@/components/list/square/waterfallContainer.vue'
import rentingItem from '@/components/list/renting/rentingItem.vue'
import workItem from '@/components/list/work/workItem.vue'
import gourmetItem from '@/components/list/gourmet/gourmetItem.vue'
import { mapState } from 'vuex'
export default {
components: {
navbar,
waterfallContainer,
rentingItem,
workItem,
gourmetItem
},
data() {
return {
userId: '', // 要查看的用户ID
userProfile: {}, // 用户资料
currentTabIndex: 0, // 当前标签页索引
contentTabs: [
{ name: '帖子', icon: 'grid' },
{ name: '租房', icon: 'home' },
{ name: '招聘', icon: 'search' },
{ name: '店铺', icon: 'shop' }
],
postsList: [], // 帖子列表
rentingList: [], // 租房列表
workList: [], // 招聘列表
shopList: [], // 店铺列表
loading: false, // 初始加载状态
loadingMore: false, // 加载更多状态
isFollowed: false, // 是否已关注
// 分页参数
pageParams: {
postsList: { pageNum: 1, hasMore: true },
rentingList: { pageNum: 1, hasMore: true },
workList: { pageNum: 1, hasMore: true },
shopList: { pageNum: 1, hasMore: true }
},
sexIcons: {
'男': 'mars',
'女': 'venus',
'其他': 'transgender'
},
sexColors: {
'男': '#4A90E2',
'女': '#FF69B4',
'其他': '#999'
}
}
},
computed: {
...mapState(['userInfo']),
// 是否是当前登录用户
isCurrentUser() {
return this.userInfo.id === this.userId
},
// 当前显示的列表
currentList() {
const listMap = ['postsList', 'rentingList', 'workList', 'shopList']
return this[listMap[this.currentTabIndex]] || []
},
// 当前列表的分页参数
currentPageParams() {
const listMap = ['postsList', 'rentingList', 'workList', 'shopList']
return this.pageParams[listMap[this.currentTabIndex]]
},
// 是否显示空状态
showEmptyState() {
return !this.loading && this.currentList.length === 0
},
// 是否显示加载更多
showLoadMore() {
return this.currentList.length > 0 && this.currentPageParams.hasMore && this.loadingMore
},
// 是否显示没有更多
showNoMore() {
return this.currentList.length > 0 && !this.currentPageParams.hasMore
}
},
onLoad(options) {
this.userId = options.userId || this.userInfo.id
this.loadUserProfile()
this.loadUserContent(false)
},
onPullDownRefresh() {
this.loadUserProfile()
this.refreshUserContent()
},
onReachBottom() {
this.loadMoreContent()
},
methods: {
// 显示更多选项
showMoreOptions() {
uni.showActionSheet({
itemList: ['举报用户', '拉黑用户'],
success: (res) => {
if (res.tapIndex === 0) {
this.reportUser()
} else if (res.tapIndex === 1) {
this.blockUser()
}
}
})
},
// 预览头像
previewAvatar() {
if (this.userProfile.headImage) {
uni.previewImage({
urls: [this.userProfile.headImage]
})
}
},
// 获取VIP等级
getVipLevel(level) {
const levels = ['', 'VIP', 'SVIP']
return levels[level] || ''
},
// 获取年龄
getAge() {
if (!this.userProfile.yearDate) return ''
const birthYear = parseInt(this.userProfile.yearDate)
const currentYear = new Date().getFullYear()
return currentYear - birthYear
},
// 获取认证文本
getAuthText(status) {
const authTexts = ['审核中', '个人认证', '店铺认证']
return authTexts[status] || '未认证'
},
// 标签页切换
onTabClick(item) {
this.currentTabIndex = item.index
this.refreshUserContent()
},
// 切换关注状态
toggleFollow() {
if (!uni.getStorageSync('token')) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
return
}
this.isFollowed = !this.isFollowed
// 这里调用关注/取消关注API
this.$api(this.isFollowed ? 'followUser' : 'unfollowUser', {
userId: this.userId
}, res => {
if (res.code === 200) {
uni.showToast({
title: this.isFollowed ? '关注成功' : '取消关注',
icon: 'success'
})
}
})
},
// 发送私信
sendMessage() {
if (!uni.getStorageSync('token')) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
return
}
uni.navigateTo({
url: `/pages_order/chat/chatDetail?userId=${this.userId}`
})
},
// 显示粉丝列表
showFans() {
uni.navigateTo({
url: `/pages_order/profile/fansList?userId=${this.userId}&type=fans`
})
},
// 显示关注列表
showFollowing() {
uni.navigateTo({
url: `/pages_order/profile/fansList?userId=${this.userId}&type=following`
})
},
// 显示点赞列表
showLikes() {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
},
// 加载用户资料
loadUserProfile() {
this.loading = true
this.$api('getUserProfile', { userId: this.userId }, res => {
this.loading = false
uni.stopPullDownRefresh()
if (res.code === 200) {
this.userProfile = res.result
// 检查是否已关注(如果不是当前用户)
if (!this.isCurrentUser) {
this.checkFollowStatus()
}
}
})
},
// 检查关注状态
checkFollowStatus() {
this.$api('checkFollowStatus', { userId: this.userId }, res => {
if (res.code === 200) {
this.isFollowed = res.result.isFollowed
}
})
},
// 刷新用户内容(重置分页)
refreshUserContent() {
const listMap = ['postsList', 'rentingList', 'workList', 'shopList']
const currentListKey = listMap[this.currentTabIndex]
// 重置分页参数
this.pageParams[currentListKey].pageNum = 1
this.pageParams[currentListKey].hasMore = true
// 清空当前列表
this[currentListKey] = []
// 加载第一页数据
this.loadUserContent(false)
},
// 加载用户内容
loadUserContent(isLoadMore = false) {
const apiMap = [
'getUserPosts', // 帖子
'getUserRenting', // 租房
'getUserWork', // 招聘
'getUserShop' // 店铺
]
const listMap = ['postsList', 'rentingList', 'workList', 'shopList']
const currentListKey = listMap[this.currentTabIndex]
const currentPageParams = this.pageParams[currentListKey]
// 设置加载状态
if (isLoadMore) {
this.loadingMore = true
} else {
this.loading = true
}
this.$api(apiMap[this.currentTabIndex], {
userId: this.userId,
pageNum: currentPageParams.pageNum,
pageSize: 20
}, res => {
// 清除加载状态
this.loading = false
this.loadingMore = false
uni.stopPullDownRefresh()
if (res.code === 200) {
const newData = res.result.records || res.result || []
if (isLoadMore) {
// 加载更多:追加数据
this[currentListKey] = [...this[currentListKey], ...newData]
} else {
// 首次加载:替换数据
this[currentListKey] = newData
}
// 更新分页状态
if (newData.length < 20) {
// 数据不足一页,说明没有更多了
this.pageParams[currentListKey].hasMore = false
} else {
// 还有更多数据,页码+1
this.pageParams[currentListKey].pageNum += 1
}
} else {
// 请求失败,恢复分页参数
if (isLoadMore && currentPageParams.pageNum > 1) {
this.pageParams[currentListKey].pageNum -= 1
}
}
})
},
// 加载更多内容
loadMoreContent() {
// 检查是否有更多数据和是否正在加载
if (!this.currentPageParams.hasMore || this.loadingMore || this.loading) {
return
}
// 检查当前列表是否有数据
if (this.currentList.length === 0) {
return
}
this.loadUserContent(true)
},
// 点击帖子
onPostClick(item) {
this.$utils.navigateTo(`/pages_order/post/postDetail?id=${item.id}`)
},
// 点赞帖子
onPostLike(item) {
console.log('点赞帖子:', item.id)
},
// 点击租房
onRentingClick(item) {
this.$utils.navigateTo(`/pages_order/renting/rentingDetail?id=${item.id}`)
},
// 点击招聘
onWorkClick(item) {
this.$utils.navigateTo(`/pages_order/work/workDetail?id=${item.id}`)
},
// 点击店铺
onShopClick(item) {
this.$utils.navigateTo(`/pages_order/gourmet/gourmetDetail?id=${item.id}`)
},
// 举报用户
reportUser() {
uni.showToast({
title: '举报功能开发中',
icon: 'none'
})
},
// 拉黑用户
blockUser() {
uni.showModal({
title: '确认拉黑该用户?',
success: (res) => {
if (res.confirm) {
// 调用拉黑API
uni.showToast({
title: '拉黑功能开发中',
icon: 'none'
})
}
}
})
},
// 获取空状态文本
getEmptyText() {
const emptyTexts = ['暂无帖子', '暂无租房信息', '暂无招聘信息', '暂无店铺信息']
return emptyTexts[this.currentTabIndex] || '暂无数据'
},
// 获取空状态图标
getEmptyIcon() {
const emptyIcons = ['list', 'home', 'search', 'shop']
return emptyIcons[this.currentTabIndex] || 'list'
}
}
}
</script>
<style scoped lang="scss">
.profile-page {
background-color: #f5f5f5;
min-height: 100vh;
}
.profile-header {
position: relative;
padding: 40rpx 30rpx 40rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding-top: 180rpx;
.header-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.9) 0%, rgba(118, 75, 162, 0.9) 100%);
}
.back-btn {
position: absolute;
top: 80rpx;
left: 30rpx;
z-index: 3;
width: 70rpx;
height: 70rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
backdrop-filter: blur(10rpx);
}
.user-info {
position: relative;
z-index: 2;
display: flex;
margin-bottom: 40rpx;
.user-avatar {
position: relative;
margin-right: 30rpx;
image {
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
border: 6rpx solid rgba(255, 255, 255, 0.3);
}
.vip-badge {
position: absolute;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(45deg, #FFD700, #FFA500);
color: #333;
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 20rpx;
font-weight: 600;
}
}
.user-details {
flex: 1;
color: #fff;
.username {
font-size: 36rpx;
font-weight: 600;
margin-bottom: 16rpx;
}
.user-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 16rpx;
.tag {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.2);
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
text {
margin-left: 6rpx;
}
}
}
.school-info {
font-size: 24rpx;
opacity: 0.9;
text {
display: block;
margin-bottom: 6rpx;
}
}
}
}
.stats-row {
position: relative;
z-index: 2;
display: flex;
justify-content: space-around;
margin-bottom: 40rpx;
.stat-item {
text-align: center;
color: #fff;
.stat-number {
font-size: 40rpx;
font-weight: 600;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
opacity: 0.8;
}
}
}
.action-buttons {
position: relative;
z-index: 2;
display: flex;
gap: 20rpx;
button {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
border-radius: 50rpx;
font-size: 28rpx;
border: none;
text {
margin-left: 8rpx;
}
}
.follow-btn {
background: #fff;
color: #333;
&.followed {
background: rgba(255, 255, 255, 0.3);
color: #fff;
}
}
.message-btn {
background: rgba(255, 255, 255, 0.3);
color: #fff;
}
}
}
.content-tabs {
background: #fff;
border-bottom: 1rpx solid #eee;
padding: 0 20rpx;
}
.content-container {
min-height: 400rpx;
.posts-content,
.renting-content,
.work-content,
.shop-content {
padding: 20rpx 0;
}
}
.empty-state {
padding: 100rpx 0;
text-align: center;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 0;
text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
}
.load-more-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
text {
margin-top: 15rpx;
font-size: 24rpx;
color: #666;
}
}
.no-more-state {
padding: 30rpx 0;
text-align: center;
text {
font-size: 24rpx;
color: #ccc;
}
}
</style>