5 changed files with 509 additions and 14 deletions
@ -0,0 +1,457 @@ |
|||
<template> |
|||
<view class="my-campus-page"> |
|||
<view class="header" :style="{ paddingTop: statusBarTop + 'px' }"> |
|||
<view class="back" @tap="back"><uni-icons type="left" size="22" color="#173b32"></uni-icons></view> |
|||
<view class="title">我的圈子</view> |
|||
<view class="tabs"> |
|||
<view |
|||
class="tab" |
|||
:class="{ active: activeTab === item.key }" |
|||
v-for="item in tabs" |
|||
:key="item.key" |
|||
@tap="switchTab(item.key)" |
|||
> |
|||
<text>{{item.name}}</text> |
|||
<view class="tab-badge" v-if="item.key === 'messages' && commentUnread > 0">{{formatBadgeCount(commentUnread)}}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="search-bar"> |
|||
<uni-icons type="search" size="18" color="#8d9693"></uni-icons> |
|||
<input |
|||
v-model.trim="keyword" |
|||
class="search-input" |
|||
confirm-type="search" |
|||
:placeholder="searchPlaceholder" |
|||
@confirm="handleSearch" |
|||
/> |
|||
<view class="search-clear" v-if="keyword" @tap="clearSearch">清空</view> |
|||
<view class="search-btn" @tap="handleSearch">搜索</view> |
|||
</view> |
|||
|
|||
<view class="swipe-tip" v-if="activeTab === 'posts'"> |
|||
<uni-icons type="info" size="15" color="#24b979"></uni-icons> |
|||
<text>左滑卡片可编辑、上下架或删除</text> |
|||
</view> |
|||
<view class="swipe-tip" v-else-if="activeTab === 'messages'"> |
|||
<uni-icons type="info" size="15" color="#24b979"></uni-icons> |
|||
<text>有人评论我的发布会显示角标,点开详情后自动已读</text> |
|||
</view> |
|||
|
|||
<scroll-view scroll-y class="list" :class="{ 'list--with-tip': activeTab !== 'comments' }" @scrolltolower="loadMore"> |
|||
<u-swipe-action v-if="activeTab === 'posts'"> |
|||
<u-swipe-action-item |
|||
v-for="(item,index) in list" |
|||
:key="item.id" |
|||
:name="index" |
|||
:options="swipeOptions(item)" |
|||
@click="handleSwipeAction" |
|||
> |
|||
<view class="card" @tap="openPost(item)"> |
|||
<image class="cover" :src="coverText(item)" mode="aspectFill"></image> |
|||
<view class="main"> |
|||
<view class="name">{{item.title || '校园圈发布'}}</view> |
|||
<view class="desc">{{descText(item)}}</view> |
|||
<view class="meta"> |
|||
<text>{{typeText(item)}}</text> |
|||
<text v-if="item.categoryName">{{item.categoryName}}</text> |
|||
<text>{{item.createTime || ''}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="status" :class="{ 'status--off': isOffShelf(item) }">{{statusText(item)}}</view> |
|||
</view> |
|||
</u-swipe-action-item> |
|||
</u-swipe-action> |
|||
|
|||
<block v-else-if="activeTab === 'messages'"> |
|||
<view class="card message-card" v-for="item in list" :key="item.id" @tap="openMessagePost(item)"> |
|||
<image class="cover" :src="coverText(item)" mode="aspectFill"></image> |
|||
<view class="main"> |
|||
<view class="name-row"> |
|||
<view class="name">{{item.title || '校园圈发布'}}</view> |
|||
<view class="card-badge" v-if="unreadCount(item) > 0">{{formatBadgeCount(unreadCount(item))}}</view> |
|||
</view> |
|||
<view class="desc">{{messageDesc(item)}}</view> |
|||
<view class="meta"> |
|||
<text>{{typeText(item)}}</text> |
|||
<text>{{item.commentCount || 0}} 条评论</text> |
|||
<text>{{item.lastActiveTime || item.createTime || ''}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="status status--action">查看</view> |
|||
</view> |
|||
</block> |
|||
|
|||
<block v-else> |
|||
<view class="card" v-for="item in list" :key="item.id" @tap="openComment(item)"> |
|||
<image class="cover" :src="commentCoverText(item)" mode="aspectFill"></image> |
|||
<view class="main"> |
|||
<view class="name">{{item.displayTitle || '校园圈发布'}}</view> |
|||
<view class="desc">{{commentDesc(item)}}</view> |
|||
<view class="meta"> |
|||
<text>我的评论</text> |
|||
<text>{{item.createTime || ''}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="status status--danger" @tap.stop="deleteCommentItem(item)">删除</view> |
|||
</view> |
|||
</block> |
|||
|
|||
<view class="empty" v-if="!list.length && !loading">{{emptyText}}</view> |
|||
<view class="load-more">{{loading ? '加载中...' : (hasMore ? '上滑加载更多' : (list.length ? '已经到底了' : ''))}}</view> |
|||
<view class="bottom-space"></view> |
|||
</scroll-view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
pageMyCampusPosts, |
|||
pageMyCampusComments, |
|||
updateMyCampusPostStatus, |
|||
getCampusUnreadSummary, |
|||
deleteCampusComment |
|||
} from '@/common/campusApi.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
statusBarTop: 24, |
|||
activeTab: 'posts', |
|||
keyword: '', |
|||
list: [], |
|||
pageNum: 1, |
|||
pageSize: 10, |
|||
totalPages: 1, |
|||
loading: false, |
|||
loadedOnce: false, |
|||
chatUnread: 0, |
|||
commentUnread: 0, |
|||
defaultCover: '/static/images/img/songshu.png', |
|||
tabs: [ |
|||
{ key: 'posts', name: '我的发布' }, |
|||
{ key: 'messages', name: '我的消息' }, |
|||
{ key: 'comments', name: '我的评论' } |
|||
] |
|||
} |
|||
}, |
|||
computed: { |
|||
hasMore() { |
|||
return this.pageNum < this.totalPages |
|||
}, |
|||
searchPlaceholder() { |
|||
if (this.activeTab === 'comments') return '按评论内容搜索' |
|||
return '按标题或内容搜索' |
|||
}, |
|||
emptyText() { |
|||
if (this.activeTab === 'messages') return '暂无评论消息' |
|||
if (this.activeTab === 'comments') return '暂无我的评论' |
|||
return '暂无发布内容' |
|||
} |
|||
}, |
|||
onLoad(options = {}) { |
|||
if (options.tab && this.tabs.some(item => item.key === options.tab)) this.activeTab = options.tab |
|||
if (uni.getMenuButtonBoundingClientRect) { |
|||
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop |
|||
} |
|||
this.load() |
|||
}, |
|||
onShow() { |
|||
if (this.loadedOnce) this.reload() |
|||
}, |
|||
methods: { |
|||
back() { |
|||
uni.navigateBack() |
|||
}, |
|||
getArea() { |
|||
const area = uni.getStorageSync('area') |
|||
if (!area) return {} |
|||
if (typeof area === 'string') { |
|||
try { return JSON.parse(area) } catch (e) { return {} } |
|||
} |
|||
return area |
|||
}, |
|||
switchTab(key) { |
|||
if (this.activeTab === key) return |
|||
this.activeTab = key |
|||
this.keyword = '' |
|||
this.reload() |
|||
}, |
|||
swipeOptions(item) { |
|||
const offShelf = this.isOffShelf(item) |
|||
return [{ |
|||
text: '编辑', |
|||
style: { |
|||
backgroundColor: '#24b979', |
|||
color: '#fff', |
|||
fontSize: '26rpx' |
|||
} |
|||
}, { |
|||
text: offShelf ? '上架' : '下架', |
|||
style: { |
|||
backgroundColor: offShelf ? '#2f8cff' : '#ff9f2d', |
|||
color: '#fff', |
|||
fontSize: '26rpx' |
|||
} |
|||
}, { |
|||
text: '删除', |
|||
style: { |
|||
backgroundColor: '#ef4c3f', |
|||
color: '#fff', |
|||
fontSize: '26rpx' |
|||
} |
|||
}] |
|||
}, |
|||
handleSwipeAction(e) { |
|||
const item = this.list[Number(e.name)] |
|||
if (!item) return |
|||
if (e.index == 0) { |
|||
this.editItem(item) |
|||
return |
|||
} |
|||
if (e.index == 1) { |
|||
this.toggleShelf(item) |
|||
return |
|||
} |
|||
this.deleteItem(item) |
|||
}, |
|||
handleSearch() { |
|||
uni.hideKeyboard() |
|||
this.reload() |
|||
}, |
|||
clearSearch() { |
|||
this.keyword = '' |
|||
this.reload() |
|||
}, |
|||
reload() { |
|||
this.pageNum = 1 |
|||
this.totalPages = 1 |
|||
this.list = [] |
|||
this.load() |
|||
}, |
|||
loadMore() { |
|||
if (this.loading || !this.hasMore) return |
|||
this.pageNum += 1 |
|||
this.load() |
|||
}, |
|||
load() { |
|||
if (this.loading) return |
|||
this.loading = true |
|||
this.loadUnreadSummary() |
|||
const area = this.getArea() |
|||
const query = { |
|||
pageNum: this.pageNum, |
|||
pageSize: this.pageSize, |
|||
regionId: area.id || '', |
|||
keyword: this.keyword, |
|||
sortType: 'latest' |
|||
} |
|||
const request = this.activeTab === 'comments' ? pageMyCampusComments(query) : pageMyCampusPosts(query) |
|||
request.then(page => { |
|||
const records = page && (page.records || page.content) ? (page.records || page.content) : [] |
|||
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1 |
|||
const normalized = this.activeTab === 'messages' ? this.sortMessages(records) : records |
|||
this.list = this.pageNum === 1 ? normalized : this.list.concat(normalized) |
|||
}).finally(() => { |
|||
this.loading = false |
|||
this.loadedOnce = true |
|||
}) |
|||
}, |
|||
loadUnreadSummary() { |
|||
getCampusUnreadSummary().then(summary => { |
|||
this.chatUnread = 0 |
|||
this.commentUnread = Number(summary && summary.campusCommentUnread) || 0 |
|||
uni.$emit('campusUnreadChange', { |
|||
...summary, |
|||
campusChatUnread: 0, |
|||
myCircleUnread: this.commentUnread, |
|||
campusUnread: this.commentUnread |
|||
}) |
|||
}).catch(() => {}) |
|||
}, |
|||
sortMessages(records) { |
|||
return (Array.isArray(records) ? records : []).slice().sort((a, b) => { |
|||
const unreadDiff = this.unreadCount(b) - this.unreadCount(a) |
|||
if (unreadDiff) return unreadDiff |
|||
return String(b.lastActiveTime || b.createTime || '').localeCompare(String(a.lastActiveTime || a.createTime || '')) |
|||
}) |
|||
}, |
|||
openPost(item) { |
|||
if (!item || !item.id) return |
|||
uni.navigateTo({ url: '/package1/post/postDetail?id=' + item.id + '&source=campus' }) |
|||
}, |
|||
openMessagePost(item) { |
|||
if (!item || !item.id) return |
|||
const count = this.unreadCount(item) |
|||
uni.navigateTo({ |
|||
url: '/package1/post/postDetail?id=' + item.id + '&source=campus', |
|||
success: () => { |
|||
if (count <= 0) return |
|||
this.$set(item, 'unreadCommentCount', 0) |
|||
this.commentUnread = Math.max(0, this.commentUnread - count) |
|||
uni.$emit('campusUnreadChange', { |
|||
campusChatUnread: 0, |
|||
campusCommentUnread: this.commentUnread, |
|||
myCircleUnread: this.commentUnread, |
|||
campusUnread: this.commentUnread |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
openComment(item) { |
|||
const postId = item && item.postId |
|||
if (!postId) return |
|||
uni.navigateTo({ url: '/package1/post/postDetail?id=' + postId + '&source=campus' }) |
|||
}, |
|||
editItem(item) { |
|||
if (!item || !item.id) return |
|||
const type = item.postType === 'partner' ? (item.partnerType || 'daily') : 'wall' |
|||
uni.navigateTo({ url: `/package1/campus/campusRelease?mode=edit&type=${type}&id=${item.id}` }) |
|||
}, |
|||
isOffShelf(item) { |
|||
return Number(item.status) === 2 || Number(item.offShelf) === 1 |
|||
}, |
|||
toggleShelf(item) { |
|||
const nextStatus = this.isOffShelf(item) ? 1 : 2 |
|||
this.updateItemStatus(item, nextStatus) |
|||
}, |
|||
deleteItem(item) { |
|||
uni.showModal({ |
|||
title: '删除确认', |
|||
content: '删除后不可恢复,确定删除这条发布吗?', |
|||
confirmText: '删除', |
|||
confirmColor: '#ef4c3f', |
|||
success: (res) => { |
|||
if (!res.confirm) return |
|||
this.updateItemStatus(item, 3) |
|||
} |
|||
}) |
|||
}, |
|||
deleteCommentItem(item) { |
|||
if (!item || !item.id) return |
|||
uni.showModal({ |
|||
title: '删除评论', |
|||
content: '确定删除这条评论吗?', |
|||
confirmText: '删除', |
|||
confirmColor: '#ef4c3f', |
|||
success: (res) => { |
|||
if (!res.confirm) return |
|||
deleteCampusComment(item.id).then(() => { |
|||
uni.showToast({ title: '已删除', icon: 'none' }) |
|||
this.reload() |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
updateItemStatus(item, status) { |
|||
return updateMyCampusPostStatus({ |
|||
postId: item.id, |
|||
status |
|||
}).then(() => { |
|||
uni.showToast({ title: status === 3 ? '已删除' : (status === 2 ? '已下架' : '已上架'), icon: 'none' }) |
|||
uni.$emit('campusPostPublished') |
|||
this.reload() |
|||
}) |
|||
}, |
|||
statusText(item) { |
|||
if (Number(item.status) === 2) return '已下架' |
|||
if (Number(item.status) === 3) return '已删除' |
|||
if (Number(item.auditStatus) === 2) return '未通过' |
|||
if (Number(item.auditStatus) === 0) return '审核中' |
|||
return '展示中' |
|||
}, |
|||
unreadCount(item) { |
|||
return Number(item && (item.unreadCommentCount || item.unreadCount)) || 0 |
|||
}, |
|||
messageDesc(item) { |
|||
const count = this.unreadCount(item) |
|||
if (count > 0) return count + ' 条新评论,点击查看并标记已读' |
|||
return '暂无新评论,点击查看评论区' |
|||
}, |
|||
typeText(item) { |
|||
if (item.postType === 'wall') return '校园圈' |
|||
const map = { game: '游戏搭子', daily: '日常搭子', sport: '运动搭子' } |
|||
return map[item.partnerType] || '校园搭子' |
|||
}, |
|||
descText(item) { |
|||
return this.plainText(item.contentText || item.content || '') |
|||
}, |
|||
commentDesc(item) { |
|||
const prefix = item.replyToUserName ? '回复 ' + item.replyToUserName + ':' : '' |
|||
return prefix + (item.content || '') |
|||
}, |
|||
coverText(item) { |
|||
return this.firstImage(item.imageList) |
|||
|| this.firstImage(item.images) |
|||
|| item.coverImage |
|||
|| this.defaultCover |
|||
}, |
|||
commentCoverText(item) { |
|||
return item.displayCover || this.defaultCover |
|||
}, |
|||
firstImage(value) { |
|||
if (!value) return '' |
|||
if (Array.isArray(value)) return value[0] || '' |
|||
try { |
|||
const parsed = JSON.parse(value) |
|||
return Array.isArray(parsed) ? (parsed[0] || '') : '' |
|||
} catch (e) { |
|||
return '' |
|||
} |
|||
}, |
|||
plainText(value) { |
|||
return String(value || '') |
|||
.replace(/<img[^>]*>/gi, '[图片]') |
|||
.replace(/<[^>]+>/g, '') |
|||
.replace(/ /g, ' ') |
|||
.replace(/</g, '<') |
|||
.replace(/>/g, '>') |
|||
.replace(/&/g, '&') |
|||
.trim() |
|||
}, |
|||
formatBadgeCount(count) { |
|||
const value = Number(count) || 0 |
|||
return value > 99 ? '99+' : value |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
page { background: #f7f9f7; } |
|||
.my-campus-page { min-height: 100vh; background: #f7f9f7; color: #17362f; } |
|||
.header { padding: 0 26rpx 18rpx; background: linear-gradient(180deg, #e5fbf3, #f7f9f7); box-sizing: border-box; } |
|||
.back { width: 68rpx; height: 68rpx; display: flex; align-items: center; } |
|||
.title { margin-top: 8rpx; font-size: 40rpx; font-weight: 900; color: #17362f; } |
|||
.tabs { margin-top: 20rpx; display: flex; align-items: center; gap: 12rpx; } |
|||
.tab { position: relative; flex-shrink: 0; min-width: 132rpx; height: 58rpx; padding: 0 20rpx; display: flex; align-items: center; justify-content: center; border-radius: 30rpx; background: #fff; color: #758782; font-size: 23rpx; box-shadow: 0 8rpx 20rpx rgba(20, 60, 48, 0.045); box-sizing: border-box; } |
|||
.tab.active { background: linear-gradient(135deg, #38c897, #19af7e); color: #fff; font-weight: 900; } |
|||
.tab-badge, .card-badge { min-width: 30rpx; height: 30rpx; padding: 0 8rpx; border-radius: 18rpx; background: #ff4d5f; color: #fff; font-size: 18rpx; font-weight: 900; line-height: 30rpx; text-align: center; box-sizing: border-box; } |
|||
.tab-badge { margin-left: 8rpx; } |
|||
.card-badge { margin-left: 8rpx; flex-shrink: 0; } |
|||
.search-bar { height: 70rpx; margin: 0 24rpx 16rpx; padding: 0 12rpx 0 22rpx; display: flex; align-items: center; border-radius: 38rpx; background: #fff; box-shadow: 0 8rpx 22rpx rgba(20, 60, 48, 0.045); box-sizing: border-box; } |
|||
.search-input { flex: 1; height: 68rpx; margin-left: 10rpx; color: #293632; font-size: 24rpx; line-height: 68rpx; } |
|||
.search-clear { padding: 0 12rpx; color: #9aa8a4; font-size: 22rpx; } |
|||
.search-btn { width: 86rpx; height: 54rpx; border-radius: 28rpx; background: linear-gradient(135deg, #38c897, #19af7e); color: #fff; font-size: 23rpx; font-weight: 800; line-height: 54rpx; text-align: center; } |
|||
.swipe-tip { height: 52rpx; margin: 0 24rpx 16rpx; padding: 0 20rpx; display: flex; align-items: center; border-radius: 26rpx; background: #eafaf3; color: #4f7368; font-size: 22rpx; box-sizing: border-box; } |
|||
.swipe-tip text { margin-left: 8rpx; } |
|||
.list { height: calc(100vh - 286rpx); } |
|||
.list--with-tip { height: calc(100vh - 354rpx); } |
|||
.card { position: relative; margin: 18rpx 24rpx 0; padding: 18rpx; display: flex; align-items: center; border-radius: 24rpx; background: #fff; box-shadow: 0 8rpx 22rpx rgba(20, 60, 48, 0.05); } |
|||
.message-card { border: 2rpx solid rgba(36, 185, 121, .10); } |
|||
.cover { width: 126rpx; height: 126rpx; border-radius: 18rpx; background: #edf3ef; flex-shrink: 0; } |
|||
.main { flex: 1; min-width: 0; margin-left: 18rpx; } |
|||
.name-row { display: flex; align-items: center; min-width: 0; } |
|||
.name { overflow: hidden; font-size: 28rpx; font-weight: 900; text-overflow: ellipsis; white-space: nowrap; } |
|||
.name-row .name { flex: 1; } |
|||
.desc { margin-top: 8rpx; overflow: hidden; color: #71817c; font-size: 23rpx; text-overflow: ellipsis; white-space: nowrap; } |
|||
.meta { margin-top: 8rpx; display: flex; gap: 14rpx; color: #a0aaa6; font-size: 20rpx; } |
|||
.meta text:first-child { color: #22a879; font-weight: 900; } |
|||
.status { margin-left: 12rpx; color: #22a879; font-size: 22rpx; font-weight: 800; white-space: nowrap; } |
|||
.status--off { color: #9aa8a4; } |
|||
.status--action { padding: 8rpx 14rpx; border-radius: 18rpx; background: #eafaf3; color: #18a878; font-size: 20rpx; } |
|||
.status--danger { padding: 8rpx 14rpx; border-radius: 18rpx; background: #fff2f2; color: #ef4c3f; font-size: 20rpx; } |
|||
.empty, .load-more { padding: 70rpx 0; color: #9aa8a4; text-align: center; font-size: 24rpx; } |
|||
.bottom-space { height: 120rpx; } |
|||
</style> |
|||
Loading…
Reference in new issue