Files

451 lines
11 KiB
Plaintext

<template>
<view class="page">
<!-- 顶部导航栏 -->
<view class="page-nav">
<view class="page-nav-left">
<scroll-view class="tabsbox" :show-scrollbar="false" direction="horizontal">
<view v-for="(tab, index) in tabs" :key="index" class="tabsbox-item"
:class="{ 'tabsbox-active': activeTab === index }" @click="switchTab(index)">
<text class="tabsbox-text" :class="{'tabsbox-active-txt': activeTab == index }">{{ tab.name }}</text>
<!-- <text v-if="tab.badge" class="badge">{{ tab.badge }}</text> -->
</view>
</scroll-view>
</view>
<view class="page-nav-right">
<view class="icon-btn" @click="onSearchClick">
<uni-icons type="search" size="24" color="#333"></uni-icons>
</view>
<view class="icon-btn" @click="onPublishClick">
<uni-icons type="plusempty" size="24" color="#333"></uni-icons>
</view>
</view>
</view>
<view class="page-body" style="">
<scroll-view class="moment-list" :show-scrollbar="false" direction="vertical" :refresher-enabled="true" :refresher-triggered="refreshing"
@refresherrefresh="onRefresh" @scrolltolower="onLoadMore" style="padding-bottom: 96rpx;">
<!-- 置顶动态 -->
<view v-if="pinnedMoments.length > 0" class="pinned-section">
<text class="section-title">置顶动态</text>
<view v-for="moment in pinnedMoments" :key="'pinned-' + moment.id" class="moment-item pinned">
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
@more="onMore" />
</view>
</view>
<!-- 推荐动态 -->
<view class="recommend-section" v-if="activeTab == 0">
<text class="section-title">推荐动态</text>
<view v-for="moment in recommendMoments" :key="'recommend-' + moment.id" class="moment-item">
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
@more="onMore" />
</view>
</view>
<!-- 关注动态 -->
<view class="follow-section" v-if="activeTab == 1">
<view v-if="followMoments.length > 0">
<view v-for="moment in followMoments" :key="'follow-' + moment.id" class="moment-item">
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
@more="onMore" />
</view>
</view>
<view v-else class="empty-state">
<uni-icons type="person" size="60" color="#ccc"></uni-icons>
<text class="empty-text">还没有关注的好友动态</text>
<button class="empty-btn" @click="findFriends">去发现好友</button>
</view>
</view>
<!-- 附近动态 -->
<view class="nearby-section" v-if="activeTab == 2">
<view class="section-header">
<text class="section-title">附近动态</text>
<text class="section-subtitle">{{ nearbyCount }}条动态</text>
</view>
<view v-for="moment in nearbyMoments" :key="'nearby-' + moment.id" class="moment-item">
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
@more="onMore" />
</view>
</view>
<!-- 热门动态 -->
<view class="hot-section" v-if="activeTab == 3">
<view class="section-header">
<text class="section-title">热门动态</text>
<view class="hot-tags">
<view v-for="(tag, index) in hotTags" :key="index" class="hot-tag"
:class="{ active: activeHotTag == index }" @click="switchHotTag(index)">
<text class="tag-text">{{ tag }}</text>
</view>
</view>
</view>
<view v-for="moment in hotMoments" :key="'hot-' + moment.id" class="moment-item">
<moment-card :moment="moment" @like="onLike" @comment="onComment" @share="onShare"
@more="onMore" />
</view>
</view>
<!-- 加载状态 -->
<!-- <view class="load-status">
<uni-load-more :status="loadStatus" />
</view> -->
</scroll-view>
<!-- 发布按钮 -->
<view class="publish-fab" @click="onPublishClick">
<uni-icons type="plus" size="30" color="#fff"></uni-icons>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed, onMounted } from 'vue'
import MomentCard from './components/MomentCard.uvue'
import {IMoment,ITab} from '@/types/dynamicType.uts'
import Api from '@/common/api-service.uts'
// 响应式数据
const activeTab = ref(0)
const activeHotTag = ref(0)
const refreshing = ref(false)
const loading = ref(false)
const noMore = ref(false)
const page = ref(1)
const pageSize = 10
const moments = reactive<IMoment[]>([])
const scopes = ['recommend', 'follow', 'nearby', 'hot']
const nearbyCount = computed(() => moments.length)
// 标签页数据
const tabs = reactive<ITab[]>([
{ name: '推荐', badge: 0 },
{ name: '关注', badge: 3 },
{ name: '附近', badge: 12 },
{ name: '热门', badge: 0 }
])
// 热门标签
const hotTags = reactive(['全部', '生活', '情感', '旅行', '美食', '游戏'])
// 计算属性
const pinnedMoments = computed<IMoment[]>(() => moments.filter(m => m.isPinned))
const recommendMoments = computed<IMoment[]>(() => moments)
const followMoments = computed<IMoment[]>(() => moments)
const nearbyMoments = computed<IMoment[]>(() => moments)
const hotMoments = computed<IMoment[]>(() => moments)
const loadStatus = computed(() => {
if (loading.value) return 'loading'
if (noMore.value) return 'noMore'
return 'more'
})
// 加载数据
const loadData = (reset : boolean = false) => {
if (loading.value) return
loading.value = true
const p = reset ? 1 : page.value
Api.moment.list({ scope: scopes[activeTab.value], page: p, page_size: pageSize })
.then((res : UTSJSONObject) => {
const list = (res.get('list') as Array<IMoment>) ?? []
if (reset) {
moments.splice(0, moments.length)
page.value = 1
}
list.forEach((m : IMoment) => moments.push(m))
page.value = p + 1
noMore.value = list.length < pageSize
})
.catch(() => {})
.finally(() => { loading.value = false })
}
// 页面加载
onMounted(() => { loadData(true) })
onShow(() => { loadData(true) })
// 切换标签页
const switchTab = (index : number) => {
activeTab.value = index
page.value = 1
noMore.value = false
loadData(true)
}
// 切换热门标签
const switchHotTag = (index : number) => {
activeHotTag.value = index
console.log('切换热门标签:', hotTags[index])
// 这里应该根据标签筛选热门动态
}
// 点击搜索
const onSearchClick = () => {
console.log('点击搜索')
uni.navigateTo({
url: '/pages/search/search'
})
}
// 点击发布
const onPublishClick = () => {
console.log('点击发布')
uni.navigateTo({
url: '/pages/moment/publish'
})
}
// 点赞动态
const onLike = (momentId : number) => {
const m = moments.find(x => x.id === momentId)
if (! m) return
const willLike = ! m.isLiked
m.isLiked = willLike
m.likes += willLike ? 1 : -1
Api.moment.like({ dynamic_id: momentId })
.catch(() => { m.isLiked = !willLike; m.likes += willLike ? -1 : 1 })
}
// 评论动态
const onComment = (momentId : number) => {
uni.navigateTo({ url: `/pages/moment/details?id=${momentId}` })
}
// 分享动态
const onShare = (momentId : number) => {
console.log('分享动态:', momentId)
uni.showActionSheet({
itemList: ['分享给好友', '分享到朋友圈', '复制链接', '保存图片'],
success: (res) => {
console.log('选择了分享方式:', res.tapIndex)
uni.showToast({
title: '分享成功',
icon: 'success',
duration: 2000
})
}
})
}
// 更多操作
const onMore = (momentId : number) => {
console.log('更多操作:', momentId)
uni.showActionSheet({
itemList: ['举报', '不感兴趣', '屏蔽用户', '保存到相册'],
success: (res) => {
const actions = ['report', 'notInterested', 'block', 'save']
const action = actions[res.tapIndex]
console.log('选择了:', action)
if (action === 'report') {
uni.navigateTo({
url: '/pages/report/report?momentId=' + momentId
})
} else {
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 1500
})
}
}
})
}
// 下拉刷新
const onRefresh = () => {
refreshing.value = true
page.value = 1
loadData(true)
setTimeout(() => { refreshing.value = false }, 600)
}
// 上拉加载更多
const onLoadMore = () => {
if (loading.value || noMore.value) return
loadData(false)
}
// 发现好友
const findFriends = () => {
console.log('去发现好友')
uni.switchTab({
url: '/pages/home/home'
})
}
</script>
<style>
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.header-right {
display: flex;
flex-flow: row nowrap;
}
.icon-btn {
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background-color: #f8f8f8;
}
/* 分类标签 */
.category-tabs {
display: flex;
flex-flow: row nowrap;
padding: 20rpx 0;
background-color: #fff;
border-bottom: 1rpx solid #eee;
}
.tab-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
margin: 0 30rpx;
padding: 10rpx 0;
}
.tab-text {
font-size: 30rpx;
color: #666;
}
.tabactive {
color: #FF6B6B;
font-weight: bold;
}
.badge {
position: absolute;
top: 0;
right: 0;
height: 32rpx;
text-align: center;
background-color: #FF6B6B;
color: #fff;
font-size: 20rpx;
border-radius: 16rpx;
transform: translate(50%, -50%);
}
/* 动态列表 */
.moment-list {
flex: 1;
padding: 0 20rpx 20rpx;
}
.section-title {
font-size: 28rpx;
color: #999;
padding: 20rpx 0 10rpx 20rpx;
}
.pinned-section .section-title {
color: #FF9500;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
background-color: #fff;
border-radius: 20rpx;
margin: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin: 20rpx 0 30rpx;
}
.empty-btn {
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
color: #fff;
padding: 20rpx 50rpx;
border-radius: 40rpx;
font-size: 28rpx;
border: none;
}
.empty-btn::after {
border: none;
}
/* 附近动态头部 */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0 10rpx 20rpx;
}
.section-subtitle {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
}
/* 热门标签 */
.hot-tags {
display: flex;
flex-flow: row nowrap;
padding: 10rpx;
}
.hot-tag {
padding: 10rpx;
background-color: #f8f8f8;
border-radius: 30rpx;
font-size: 24rpx;
color: #666;
transition: all 0.3s ease;
}
.hot-tag.active {
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
color: #fff;
}
/* 加载状态 */
.load-status {
padding: 30rpx 0;
display: flex;
justify-content: center;
}
/* 发布按钮 */
.publish-fab {
position: fixed;
right: 40rpx;
bottom: 140rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(to bottom right , #FF6B6B , #FF8E53);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.3);
z-index: 100;
}
</style>