451 lines
11 KiB
Plaintext
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> |