@ -0,0 +1,100 @@ | |||||
<template> | |||||
<view class="waterfall-container"> | |||||
<view class="waterfall-columns"> | |||||
<!-- 左列 --> | |||||
<view class="waterfall-column"> | |||||
<waterfallItem | |||||
v-for="(item, index) in leftColumnData" | |||||
:key="'left_' + index" | |||||
:item="item" | |||||
@click="handleItemClick" | |||||
@like="handleItemLike" | |||||
/> | |||||
</view> | |||||
<!-- 右列 --> | |||||
<view class="waterfall-column"> | |||||
<waterfallItem | |||||
v-for="(item, index) in rightColumnData" | |||||
:key="'right_' + index" | |||||
:item="item" | |||||
@click="handleItemClick" | |||||
@like="handleItemLike" | |||||
/> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
import waterfallItem from './waterfallItem.vue' | |||||
export default { | |||||
components: { | |||||
waterfallItem | |||||
}, | |||||
props: { | |||||
list: { | |||||
type: Array, | |||||
default: () => [] | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
leftColumnData: [], | |||||
rightColumnData: [] | |||||
} | |||||
}, | |||||
watch: { | |||||
list: { | |||||
handler(newList) { | |||||
this.distributeItems(newList) | |||||
}, | |||||
immediate: true | |||||
} | |||||
}, | |||||
methods: { | |||||
// 将数据分配到左右两列 | |||||
distributeItems(items) { | |||||
this.leftColumnData = [] | |||||
this.rightColumnData = [] | |||||
items.forEach((item, index) => { | |||||
// 简单的奇偶分配,也可以根据内容高度智能分配 | |||||
if (index % 2 === 0) { | |||||
this.leftColumnData.push(item) | |||||
} else { | |||||
this.rightColumnData.push(item) | |||||
} | |||||
}) | |||||
}, | |||||
// 处理点击事件 | |||||
handleItemClick(item) { | |||||
this.$emit('item-click', item) | |||||
}, | |||||
// 处理点赞事件 | |||||
handleItemLike(item) { | |||||
this.$emit('item-like', item) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.waterfall-container { | |||||
padding: 20rpx; | |||||
.waterfall-columns { | |||||
display: flex; | |||||
gap: 20rpx; | |||||
.waterfall-column { | |||||
flex: 1; | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,297 @@ | |||||
<template> | |||||
<view class="waterfall-item" @click="handleClick"> | |||||
<!-- 主图片 --> | |||||
<view class="main-image" v-if="mainImage"> | |||||
<image :src="mainImage" mode="aspectFill" @click.stop="previewImage([mainImage])"></image> | |||||
<!-- 分类标签 --> | |||||
<view class="category-tag" v-if="item.classId_dictText"> | |||||
#{{ item.classId_dictText }} | |||||
</view> | |||||
</view> | |||||
<!-- 内容区域 --> | |||||
<view class="content"> | |||||
<!-- 标题/内容 --> | |||||
<view class="title" v-if="item.title" v-html="formatContent(item.title)"></view> | |||||
<!-- 地址信息 --> | |||||
<view class="address" v-if="item.address"> | |||||
<uv-icon name="map-pin" size="24rpx" color="#999"></uv-icon> | |||||
<text>{{ item.address }}</text> | |||||
</view> | |||||
<!-- 用户信息 --> | |||||
<view class="user-info"> | |||||
<view class="user-avatar"> | |||||
<image :src="item.userImage" mode="aspectFill" @click.stop="previewImage([item.userImage])"></image> | |||||
</view> | |||||
<view class="user-details"> | |||||
<view class="username">{{ item.userName }}</view> | |||||
<view class="user-tags"> | |||||
<text class="tag" v-if="item.sex" :style="{'background-color': sexColors[item.sex] || '#999'}">{{ item.sex }}</text> | |||||
<text class="tag" v-if="item.yearDate">{{ item.yearDate }}</text> | |||||
<text class="auth-tag" v-if="item.isContent">{{ item.isContent }}</text> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
<!-- 互动数据 --> | |||||
<view class="interaction"> | |||||
<view class="interaction-item"> | |||||
<uv-icon name="eye" size="24rpx" color="#999"></uv-icon> | |||||
<text>{{ item.isBrowse || 0 }}</text> | |||||
</view> | |||||
<view class="interaction-item"> | |||||
<uv-icon name="chat" size="24rpx" color="#999"></uv-icon> | |||||
<text>{{ item.isComment || 0 }}</text> | |||||
</view> | |||||
<view class="interaction-item" @click.stop="handleLike"> | |||||
<uv-icon name="thumb-up" size="24rpx" :color="isLiked ? '#ff4757' : '#999'"></uv-icon> | |||||
<text :style="{color: isLiked ? '#ff4757' : '#999'}">{{ item.isUp || 0 }}</text> | |||||
</view> | |||||
<!-- 发布时间 --> | |||||
<view class="publish-time"> | |||||
{{ formatTime(item.createTime) }} | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: { | |||||
item: { | |||||
type: Object, | |||||
default: () => ({}) | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
isLiked: false, | |||||
sexColors: { | |||||
'男': '#4A90E2', | |||||
'女': '#FF69B4', | |||||
'其他': '#999' | |||||
} | |||||
} | |||||
}, | |||||
computed: { | |||||
// 主图片 - 优先显示微信图片,然后是第一张图片 | |||||
mainImage() { | |||||
if (this.item.wxImage) { | |||||
return this.item.wxImage | |||||
} | |||||
if (this.item.image) { | |||||
const images = this.item.image.split(',').filter(img => img.trim()) | |||||
return images.length > 0 ? images[0] : null | |||||
} | |||||
return null | |||||
} | |||||
}, | |||||
methods: { | |||||
// 处理点击事件 | |||||
handleClick() { | |||||
this.$emit('click', this.item) | |||||
}, | |||||
// 处理点赞事件 | |||||
handleLike() { | |||||
this.isLiked = !this.isLiked | |||||
this.$emit('like', this.item) | |||||
}, | |||||
// 预览图片 | |||||
previewImage(urls, current = 0) { | |||||
if (!urls || urls.length === 0) return | |||||
uni.previewImage({ | |||||
urls: urls, | |||||
current: current | |||||
}) | |||||
}, | |||||
// 格式化内容 | |||||
formatContent(content) { | |||||
if (!content) return '' | |||||
// 尝试使用全局utils,如果不存在则返回原内容 | |||||
if (this.$utils && this.$utils.stringFormatHtml) { | |||||
return this.$utils.stringFormatHtml(content) | |||||
} | |||||
// 简单的HTML处理 | |||||
return content.replace(/\n/g, '<br/>') | |||||
}, | |||||
// 格式化时间 | |||||
formatTime(timeStr) { | |||||
if (!timeStr) return '' | |||||
// 如果已经包含"发布",直接返回 | |||||
if (timeStr.includes('发布')) { | |||||
return timeStr | |||||
} | |||||
// 简单的时间格式处理 | |||||
const now = new Date() | |||||
const time = new Date(timeStr) | |||||
const diff = now - time | |||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24)) | |||||
if (days === 0) { | |||||
return '今天' | |||||
} else if (days === 1) { | |||||
return '昨天' | |||||
} else if (days < 7) { | |||||
return `${days}天前` | |||||
} else { | |||||
// 返回月-日格式 | |||||
const month = time.getMonth() + 1 | |||||
const day = time.getDate() | |||||
return `${month}-${day}` | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.waterfall-item { | |||||
background-color: #fff; | |||||
border-radius: 16rpx; | |||||
overflow: hidden; | |||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); | |||||
margin-bottom: 20rpx; | |||||
.main-image { | |||||
position: relative; | |||||
width: 100%; | |||||
image { | |||||
width: 100%; | |||||
height: 400rpx; | |||||
object-fit: cover; | |||||
} | |||||
.category-tag { | |||||
position: absolute; | |||||
top: 16rpx; | |||||
right: 16rpx; | |||||
background: rgba(255, 215, 0, 0.9); | |||||
color: #333; | |||||
padding: 8rpx 16rpx; | |||||
border-radius: 20rpx; | |||||
font-size: 22rpx; | |||||
font-weight: 500; | |||||
} | |||||
} | |||||
.content { | |||||
padding: 24rpx; | |||||
.title { | |||||
font-size: 28rpx; | |||||
line-height: 1.4; | |||||
color: #333; | |||||
margin-bottom: 16rpx; | |||||
display: -webkit-box; | |||||
-webkit-box-orient: vertical; | |||||
-webkit-line-clamp: 3; | |||||
overflow: hidden; | |||||
} | |||||
.address { | |||||
display: flex; | |||||
align-items: center; | |||||
margin-bottom: 20rpx; | |||||
text { | |||||
font-size: 24rpx; | |||||
color: #666; | |||||
margin-left: 8rpx; | |||||
} | |||||
} | |||||
.user-info { | |||||
display: flex; | |||||
align-items: center; | |||||
margin-bottom: 20rpx; | |||||
.user-avatar { | |||||
width: 60rpx; | |||||
height: 60rpx; | |||||
border-radius: 30rpx; | |||||
overflow: hidden; | |||||
margin-right: 16rpx; | |||||
image { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | |||||
.user-details { | |||||
flex: 1; | |||||
.username { | |||||
font-size: 26rpx; | |||||
color: #333; | |||||
font-weight: 500; | |||||
margin-bottom: 6rpx; | |||||
} | |||||
.user-tags { | |||||
display: flex; | |||||
align-items: center; | |||||
flex-wrap: wrap; | |||||
.tag { | |||||
font-size: 20rpx; | |||||
color: white; | |||||
background-color: #999; | |||||
padding: 4rpx 12rpx; | |||||
border-radius: 12rpx; | |||||
margin-right: 8rpx; | |||||
margin-bottom: 4rpx; | |||||
} | |||||
.auth-tag { | |||||
font-size: 20rpx; | |||||
color: white; | |||||
background-color: #ffd036; | |||||
padding: 4rpx 12rpx; | |||||
border-radius: 12rpx; | |||||
margin-right: 8rpx; | |||||
margin-bottom: 4rpx; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.interaction { | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
.interaction-item { | |||||
display: flex; | |||||
align-items: center; | |||||
text { | |||||
font-size: 24rpx; | |||||
color: #999; | |||||
margin-left: 6rpx; | |||||
} | |||||
} | |||||
.publish-time { | |||||
font-size: 24rpx; | |||||
color: #999; | |||||
margin-left: auto; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,371 @@ | |||||
<template> | |||||
<view class="square-page"> | |||||
<navbar title="广场"/> | |||||
<!-- 一级分类:关注/发现 --> | |||||
<view class="primary-tabs"> | |||||
<view | |||||
class="primary-tab-item" | |||||
:class="{active: currentPrimaryTab === 0}" | |||||
@click="switchPrimaryTab(0)" | |||||
> | |||||
关注 | |||||
</view> | |||||
<view | |||||
class="primary-tab-item" | |||||
:class="{active: currentPrimaryTab === 1}" | |||||
@click="switchPrimaryTab(1)" | |||||
> | |||||
发现 | |||||
</view> | |||||
</view> | |||||
<!-- 二级分类:城市列表 --> | |||||
<!-- <view class="secondary-tabs"> | |||||
<scroll-view scroll-x="true" class="city-scroll"> | |||||
<view class="city-tabs"> | |||||
<view | |||||
class="city-tab-item" | |||||
:class="{active: currentCityIndex === -1}" | |||||
@click="switchCity(-1, null)" | |||||
> | |||||
全部 | |||||
</view> | |||||
<view | |||||
v-for="(city, index) in cityList" | |||||
:key="city.id || index" | |||||
class="city-tab-item" | |||||
:class="{active: currentCityIndex === index}" | |||||
@click="switchCity(index, city)" | |||||
> | |||||
{{ city.name || city.cityName }} | |||||
</view> | |||||
</view> | |||||
</scroll-view> | |||||
</view> --> | |||||
<!-- 使用uv-tabs组件的城市分类 --> | |||||
<view class="city-tabs-container"> | |||||
<uv-tabs | |||||
:list="cityTabsList" | |||||
:current="currentCityTabIndex" | |||||
:activeStyle="{color: '#5baaff', fontWeight: 600}" | |||||
lineColor="#5baaff" | |||||
lineHeight="6rpx" | |||||
lineWidth="40rpx" | |||||
keyName="name" | |||||
@click="onCityTabClick" | |||||
/> | |||||
</view> | |||||
<!-- 瀑布流列表 --> | |||||
<view class="content-container"> | |||||
<waterfallContainer | |||||
:list="List" | |||||
@item-click="onItemClick" | |||||
@item-like="onItemLike" | |||||
/> | |||||
<!-- 加载更多提示 --> | |||||
<view v-if="loadmore && List.length > 0" class="load-more"> | |||||
<uv-loading-icon size="28"></uv-loading-icon> | |||||
<text class="load-more-text">加载更多...</text> | |||||
</view> | |||||
<!-- 没有更多数据提示 --> | |||||
<view v-if="!loadmore && List.length > 0" class="no-more"> | |||||
<text>— 没有更多了 —</text> | |||||
</view> | |||||
</view> | |||||
<!-- 空状态 --> | |||||
<view v-if="!loading && List.length === 0" class="empty-state"> | |||||
<uv-empty | |||||
text="暂无动态" | |||||
textColor="#999" | |||||
icon="list" | |||||
iconColor="#ddd" | |||||
iconSize="120" | |||||
></uv-empty> | |||||
</view> | |||||
<!-- 加载状态 --> | |||||
<view v-if="loading && List.length === 0" class="loading-state"> | |||||
<uv-loading-icon size="40"></uv-loading-icon> | |||||
<text class="loading-text">加载中...</text> | |||||
</view> | |||||
<tabber select="1" /> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
import navbar from '@/components/base/navbar.vue' | |||||
import tabber from '@/components/base/tabbar.vue' | |||||
import waterfallContainer from '@/components/list/square/waterfallContainer.vue' | |||||
import mixinsList from '@/mixins/loadList.js' | |||||
import { mapState } from 'vuex' | |||||
export default { | |||||
mixins: [mixinsList], | |||||
components: { | |||||
navbar, | |||||
tabber, | |||||
waterfallContainer | |||||
}, | |||||
data() { | |||||
return { | |||||
mixinsListApi: 'getPostPage', // 使用与首页相同的API | |||||
currentPrimaryTab: 1, // 默认显示发现 | |||||
currentCityIndex: -1, // 默认全部城市 | |||||
currentCity: null, | |||||
currentCityTabIndex: 0 // uv-tabs当前选中的城市索引 | |||||
} | |||||
}, | |||||
onLoad() { | |||||
// 获取城市列表 | |||||
this.$store.commit('getCityList') | |||||
// 初始化查询参数 | |||||
this.initQueryParams() | |||||
}, | |||||
onShow() { | |||||
this.refreshList() | |||||
}, | |||||
onPullDownRefresh() { | |||||
this.refreshList() | |||||
}, | |||||
onReachBottom() { | |||||
this.loadMore() | |||||
}, | |||||
computed: { | |||||
...mapState(['cityList']), | |||||
// 城市标签页列表(包含"全部"选项) | |||||
cityTabsList() { | |||||
const allTab = { id: null, name: '全部' } | |||||
const cityTabs = this.cityList.map(city => ({ | |||||
id: city.id || city.cityId, | |||||
name: city.name || city.cityName || city.title | |||||
})) | |||||
return [allTab, ...cityTabs] | |||||
} | |||||
}, | |||||
methods: { | |||||
// 初始化查询参数 | |||||
initQueryParams() { | |||||
// 设置默认查询参数 | |||||
this.queryParams = { | |||||
...this.queryParams, | |||||
type: this.currentPrimaryTab // 0: 关注, 1: 发现 | |||||
} | |||||
// 如果有选择城市,添加城市参数 | |||||
if (this.currentCity) { | |||||
this.queryParams.cityId = this.currentCity.id || this.currentCity.cityId | |||||
} | |||||
}, | |||||
// 切换一级分类 | |||||
switchPrimaryTab(index) { | |||||
if (this.currentPrimaryTab === index) return | |||||
this.currentPrimaryTab = index | |||||
this.queryParams.type = index | |||||
this.refreshList() | |||||
}, | |||||
// 切换城市(已注释,改用uv-tabs的onCityTabClick方法) | |||||
// switchCity(index, city) { | |||||
// if (this.currentCityIndex === index) return | |||||
// | |||||
// this.currentCityIndex = index | |||||
// this.currentCity = city | |||||
// | |||||
// // 更新查询参数 | |||||
// if (city) { | |||||
// this.queryParams.cityId = city.id || city.cityId | |||||
// } else { | |||||
// delete this.queryParams.cityId | |||||
// } | |||||
// | |||||
// this.refreshList() | |||||
// }, | |||||
// 重写刷新方法 | |||||
onRefresh() { | |||||
this.refreshList() | |||||
}, | |||||
// 点击动态项 | |||||
onItemClick(item) { | |||||
this.$utils.navigateTo('/pages_order/post/postDetail?id=' + item.id) | |||||
}, | |||||
// 点赞动态 | |||||
onItemLike(item) { | |||||
console.log('点赞动态:', item.id) | |||||
// 这里可以添加点赞API调用 | |||||
}, | |||||
// uv-tabs城市切换事件 | |||||
onCityTabClick(item) { | |||||
this.currentCityTabIndex = item.index | |||||
// 如果是第一个(全部),清除城市筛选 | |||||
if (item.index === 0) { | |||||
this.currentCity = null | |||||
this.currentCityIndex = -1 | |||||
delete this.queryParams.cityId | |||||
} else { | |||||
// 获取对应的城市数据 | |||||
const cityData = this.cityList[item.index - 1] // 减1是因为第一个是"全部" | |||||
this.currentCity = cityData | |||||
this.currentCityIndex = item.index - 1 | |||||
this.queryParams.cityId = cityData.id || cityData.cityId | |||||
} | |||||
this.refreshList() | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.square-page { | |||||
background-color: #f5f5f5; | |||||
min-height: 100vh; | |||||
} | |||||
.primary-tabs { | |||||
background-color: #fff; | |||||
display: flex; | |||||
padding: 0 30rpx; | |||||
border-bottom: 1rpx solid #eee; | |||||
.primary-tab-item { | |||||
flex: 1; | |||||
text-align: center; | |||||
padding: 30rpx 0; | |||||
font-size: 32rpx; | |||||
color: #666; | |||||
position: relative; | |||||
&.active { | |||||
color: #5baaff; | |||||
font-weight: bold; | |||||
&::after { | |||||
content: ''; | |||||
position: absolute; | |||||
bottom: 0; | |||||
left: 50%; | |||||
transform: translateX(-50%); | |||||
width: 60rpx; | |||||
height: 6rpx; | |||||
background-color: #5baaff; | |||||
border-radius: 3rpx; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// 原有的二级分类样式(已注释) | |||||
// .secondary-tabs { | |||||
// background-color: #fff; | |||||
// border-bottom: 1rpx solid #eee; | |||||
// | |||||
// .city-scroll { | |||||
// padding: 20rpx 0; | |||||
// | |||||
// .city-tabs { | |||||
// display: flex; | |||||
// white-space: nowrap; | |||||
// padding: 0 30rpx; | |||||
// | |||||
// .city-tab-item { | |||||
// flex-shrink: 0; | |||||
// padding: 16rpx 32rpx; | |||||
// margin-right: 20rpx; | |||||
// background-color: #f8f9fa; | |||||
// border-radius: 30rpx; | |||||
// font-size: 28rpx; | |||||
// color: #666; | |||||
// border: 1rpx solid transparent; | |||||
// | |||||
// &.active { | |||||
// background-color: #5baaff; | |||||
// color: #fff; | |||||
// } | |||||
// | |||||
// &:last-child { | |||||
// margin-right: 30rpx; | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// uv-tabs城市分类容器 | |||||
.city-tabs-container { | |||||
background-color: #fff; | |||||
border-bottom: 1rpx solid #eee; | |||||
padding: 0 20rpx; | |||||
} | |||||
.content-container { | |||||
padding: 0; | |||||
} | |||||
.empty-state { | |||||
padding: 100rpx 0; | |||||
text-align: center; | |||||
} | |||||
.loading-state { | |||||
padding: 100rpx 0; | |||||
text-align: center; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
.loading-text { | |||||
margin-top: 20rpx; | |||||
font-size: 28rpx; | |||||
color: #999; | |||||
} | |||||
} | |||||
.load-more { | |||||
padding: 30rpx 0; | |||||
text-align: center; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
.load-more-text { | |||||
margin-top: 15rpx; | |||||
font-size: 24rpx; | |||||
color: #999; | |||||
} | |||||
} | |||||
.no-more { | |||||
padding: 30rpx 0; | |||||
text-align: center; | |||||
text { | |||||
font-size: 24rpx; | |||||
color: #ccc; | |||||
} | |||||
} | |||||
// 原有的城市滚动条隐藏样式(已注释,uv-tabs自带滚动处理) | |||||
// /deep/ .city-scroll::-webkit-scrollbar { | |||||
// display: none; | |||||
// } | |||||
</style> |
@ -0,0 +1,476 @@ | |||||
<template> | |||||
<view class="fans-list-page"> | |||||
<navbar title="粉丝列表" leftClick @leftClick="$utils.navigateBack" /> | |||||
<!-- 标签页切换 --> | |||||
<view class="tabs-container"> | |||||
<uv-tabs | |||||
:list="tabsList" | |||||
:current="currentTab" | |||||
:activeStyle="{color: '#333', fontWeight: 600}" | |||||
lineColor="#5baaff" | |||||
lineHeight="6rpx" | |||||
lineWidth="40rpx" | |||||
keyName="name" | |||||
@click="onTabClick" | |||||
/> | |||||
</view> | |||||
<!-- 用户列表 --> | |||||
<view class="user-list"> | |||||
<view | |||||
class="user-item" | |||||
v-for="(user, index) in userList" | |||||
:key="index" | |||||
@click="goToUserProfile(user)" | |||||
> | |||||
<view class="user-avatar"> | |||||
<image :src="user.headImage" mode="aspectFill"></image> | |||||
</view> | |||||
<view class="user-info"> | |||||
<view class="user-name">{{ user.nickName }}</view> | |||||
<view class="user-desc"> | |||||
<view class="user-tags"> | |||||
<text class="tag" v-if="user.sex">{{ user.sex }}</text> | |||||
<text class="tag" v-if="user.address">{{ user.address }}</text> | |||||
<text class="auth-tag" v-if="user.idCardOpen">{{ getAuthText(user.idCardOpen) }}</text> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
<view class="user-action" v-if="!isCurrentUserPage"> | |||||
<button | |||||
class="follow-btn" | |||||
:class="{followed: user.isFollowed}" | |||||
@click.stop="toggleFollow(user, index)" | |||||
> | |||||
{{ user.isFollowed ? '已关注' : '关注' }} | |||||
</button> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
<!-- 空状态 --> | |||||
<view v-if="!loading && userList.length === 0" class="empty-state"> | |||||
<uv-empty | |||||
:text="emptyText" | |||||
icon="account" | |||||
iconSize="120" | |||||
></uv-empty> | |||||
</view> | |||||
<!-- 加载状态 --> | |||||
<view v-if="loading" class="loading-state"> | |||||
<uv-loading-icon size="40"></uv-loading-icon> | |||||
<text>加载中...</text> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
import { mapState } from 'vuex' | |||||
export default { | |||||
data() { | |||||
return { | |||||
userId: '', // 用户ID | |||||
type: 'fans', // fans: 粉丝, following: 关注 | |||||
currentTab: 0, | |||||
tabsList: [ | |||||
{ name: '粉丝', type: 'fans' }, | |||||
{ name: '关注', type: 'following' } | |||||
], | |||||
userList: [], // 用户列表 | |||||
loading: false, | |||||
// 模拟数据 | |||||
mockFansData: [ | |||||
{ | |||||
id: '1', | |||||
nickName: '小美同学', | |||||
headImage: 'https://picsum.photos/100/100?random=1', | |||||
sex: '女', | |||||
address: '北京', | |||||
idCardOpen: 1, | |||||
isFollowed: false | |||||
}, | |||||
{ | |||||
id: '2', | |||||
nickName: '程序员小王', | |||||
headImage: 'https://picsum.photos/100/100?random=2', | |||||
sex: '男', | |||||
address: '上海', | |||||
idCardOpen: 2, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '3', | |||||
nickName: '设计师小李', | |||||
headImage: 'https://picsum.photos/100/100?random=3', | |||||
sex: '女', | |||||
address: '深圳', | |||||
idCardOpen: 0, | |||||
isFollowed: false | |||||
}, | |||||
{ | |||||
id: '4', | |||||
nickName: '产品经理老张', | |||||
headImage: 'https://picsum.photos/100/100?random=4', | |||||
sex: '男', | |||||
address: '广州', | |||||
idCardOpen: 1, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '5', | |||||
nickName: '运营小姐姐', | |||||
headImage: 'https://picsum.photos/100/100?random=5', | |||||
sex: '女', | |||||
address: '杭州', | |||||
idCardOpen: 2, | |||||
isFollowed: false | |||||
}, | |||||
{ | |||||
id: '6', | |||||
nickName: '前端工程师', | |||||
headImage: 'https://picsum.photos/100/100?random=6', | |||||
sex: '男', | |||||
address: '成都', | |||||
idCardOpen: 1, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '7', | |||||
nickName: 'UI设计师', | |||||
headImage: 'https://picsum.photos/100/100?random=7', | |||||
sex: '女', | |||||
address: '武汉', | |||||
idCardOpen: 0, | |||||
isFollowed: false | |||||
}, | |||||
{ | |||||
id: '8', | |||||
nickName: '后端大神', | |||||
headImage: 'https://picsum.photos/100/100?random=8', | |||||
sex: '男', | |||||
address: '西安', | |||||
idCardOpen: 2, | |||||
isFollowed: true | |||||
} | |||||
], | |||||
mockFollowingData: [ | |||||
{ | |||||
id: '9', | |||||
nickName: '技术大牛', | |||||
headImage: 'https://picsum.photos/100/100?random=9', | |||||
sex: '男', | |||||
address: '北京', | |||||
idCardOpen: 2, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '10', | |||||
nickName: '设计总监', | |||||
headImage: 'https://picsum.photos/100/100?random=10', | |||||
sex: '女', | |||||
address: '上海', | |||||
idCardOpen: 2, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '11', | |||||
nickName: '产品总监', | |||||
headImage: 'https://picsum.photos/100/100?random=11', | |||||
sex: '男', | |||||
address: '深圳', | |||||
idCardOpen: 1, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '12', | |||||
nickName: '运营总监', | |||||
headImage: 'https://picsum.photos/100/100?random=12', | |||||
sex: '女', | |||||
address: '广州', | |||||
idCardOpen: 2, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '13', | |||||
nickName: '架构师', | |||||
headImage: 'https://picsum.photos/100/100?random=13', | |||||
sex: '男', | |||||
address: '杭州', | |||||
idCardOpen: 1, | |||||
isFollowed: true | |||||
}, | |||||
{ | |||||
id: '14', | |||||
nickName: '资深设计师', | |||||
headImage: 'https://picsum.photos/100/100?random=14', | |||||
sex: '女', | |||||
address: '成都', | |||||
idCardOpen: 2, | |||||
isFollowed: true | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
computed: { | |||||
...mapState(['userInfo']), | |||||
// 页面标题 | |||||
pageTitle() { | |||||
return this.type === 'fans' ? '粉丝列表' : '关注列表' | |||||
}, | |||||
// 空状态文本 | |||||
emptyText() { | |||||
return this.type === 'fans' ? '暂无粉丝' : '暂无关注' | |||||
}, | |||||
// 是否是当前用户的页面 | |||||
isCurrentUserPage() { | |||||
return this.userId === this.userInfo.id | |||||
} | |||||
}, | |||||
onLoad(options) { | |||||
this.userId = options.userId || this.userInfo.id | |||||
this.type = options.type || 'fans' | |||||
// 设置初始tab | |||||
this.currentTab = this.type === 'fans' ? 0 : 1 | |||||
this.loadUserList() | |||||
}, | |||||
onPullDownRefresh() { | |||||
this.loadUserList() | |||||
}, | |||||
methods: { | |||||
// 返回上一页 | |||||
goBack() { | |||||
uni.navigateBack() | |||||
}, | |||||
// 标签页切换 | |||||
onTabClick(item) { | |||||
this.currentTab = item.index | |||||
this.type = item.type | |||||
this.loadUserList() | |||||
}, | |||||
// 获取认证文本 | |||||
getAuthText(status) { | |||||
const authTexts = ['审核中', '个人认证', '店铺认证'] | |||||
return authTexts[status] || '未认证' | |||||
}, | |||||
// 跳转到用户主页 | |||||
goToUserProfile(user) { | |||||
uni.navigateTo({ | |||||
url: `/pages_order/profile/userProfile?userId=${user.id}` | |||||
}) | |||||
}, | |||||
// 切换关注状态 | |||||
toggleFollow(user, index) { | |||||
if (!uni.getStorageSync('token')) { | |||||
uni.showToast({ | |||||
title: '请先登录', | |||||
icon: 'none' | |||||
}) | |||||
return | |||||
} | |||||
const isFollowed = !user.isFollowed | |||||
// 模拟API调用 | |||||
setTimeout(() => { | |||||
// 更新本地数据 | |||||
this.userList[index].isFollowed = isFollowed | |||||
// 同时更新模拟数据源 | |||||
if (this.type === 'fans') { | |||||
const mockIndex = this.mockFansData.findIndex(item => item.id === user.id) | |||||
if (mockIndex !== -1) { | |||||
this.mockFansData[mockIndex].isFollowed = isFollowed | |||||
} | |||||
} else { | |||||
const mockIndex = this.mockFollowingData.findIndex(item => item.id === user.id) | |||||
if (mockIndex !== -1) { | |||||
this.mockFollowingData[mockIndex].isFollowed = isFollowed | |||||
} | |||||
} | |||||
uni.showToast({ | |||||
title: isFollowed ? '关注成功' : '取消关注', | |||||
icon: 'success' | |||||
}) | |||||
}, 500) | |||||
}, | |||||
// 加载用户列表 | |||||
loadUserList() { | |||||
this.loading = true | |||||
// 模拟网络延迟 | |||||
setTimeout(() => { | |||||
this.loading = false | |||||
uni.stopPullDownRefresh() | |||||
// 使用模拟数据 | |||||
if (this.type === 'fans') { | |||||
this.userList = [...this.mockFansData] | |||||
} else { | |||||
this.userList = [...this.mockFollowingData] | |||||
} | |||||
// 模拟API调用成功 | |||||
console.log(`加载${this.type === 'fans' ? '粉丝' : '关注'}列表成功`) | |||||
}, 1000) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.fans-list-page { | |||||
background-color: #f5f5f5; | |||||
min-height: 100vh; | |||||
} | |||||
.navbar { | |||||
position: fixed; | |||||
top: 0; | |||||
left: 0; | |||||
right: 0; | |||||
z-index: 100; | |||||
height: 88rpx; | |||||
background: #fff; | |||||
border-bottom: 1rpx solid #eee; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
padding: 0 30rpx; | |||||
padding-top: var(--status-bar-height, 44rpx); | |||||
.nav-title { | |||||
color: #333; | |||||
font-size: 32rpx; | |||||
font-weight: 600; | |||||
} | |||||
.nav-left, .nav-right { | |||||
width: 60rpx; | |||||
height: 60rpx; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
} | |||||
} | |||||
.tabs-container { | |||||
margin-top: 88rpx; | |||||
padding-top: var(--status-bar-height, 44rpx); | |||||
background: #fff; | |||||
border-bottom: 1rpx solid #eee; | |||||
padding-left: 20rpx; | |||||
padding-right: 20rpx; | |||||
} | |||||
.user-list { | |||||
padding: 20rpx; | |||||
.user-item { | |||||
display: flex; | |||||
align-items: center; | |||||
background: #fff; | |||||
padding: 30rpx; | |||||
margin-bottom: 20rpx; | |||||
border-radius: 16rpx; | |||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); | |||||
.user-avatar { | |||||
width: 100rpx; | |||||
height: 100rpx; | |||||
border-radius: 50rpx; | |||||
overflow: hidden; | |||||
margin-right: 30rpx; | |||||
image { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | |||||
.user-info { | |||||
flex: 1; | |||||
.user-name { | |||||
font-size: 32rpx; | |||||
font-weight: 600; | |||||
color: #333; | |||||
margin-bottom: 12rpx; | |||||
} | |||||
.user-desc { | |||||
.user-tags { | |||||
display: flex; | |||||
flex-wrap: wrap; | |||||
gap: 8rpx; | |||||
.tag { | |||||
background: #f0f0f0; | |||||
color: #666; | |||||
padding: 4rpx 12rpx; | |||||
border-radius: 12rpx; | |||||
font-size: 20rpx; | |||||
} | |||||
.auth-tag { | |||||
background: #52c41a; | |||||
color: #fff; | |||||
padding: 4rpx 12rpx; | |||||
border-radius: 12rpx; | |||||
font-size: 20rpx; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.user-action { | |||||
.follow-btn { | |||||
background: #5baaff; | |||||
color: #fff; | |||||
border: none; | |||||
padding: 16rpx 32rpx; | |||||
border-radius: 30rpx; | |||||
font-size: 24rpx; | |||||
&.followed { | |||||
background: #f0f0f0; | |||||
color: #666; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.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; | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,797 @@ | |||||
<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> |