| @ -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> | |||