feat(circle): 添加酒友圈社交模块

- 新增酒友圈、圈子详情、发布动态、好友列表、活动创建等页面配置
- 添加酒友圈相关API接口,包括动态、评论、好友、活动等功能
- 实现模拟数据用于前端开发和测试
- 创建评论项、空状态、活动卡片、动态卡片、好友项等组件
- 编写酒友圈模块详细的接口文档,定义数据结构和业务规则
This commit is contained in:
cg
2026-07-20 22:53:04 +08:00
parent 9f5c69e1e5
commit fabbb167cf
18 changed files with 3992 additions and 18 deletions
+267
View File
@@ -0,0 +1,267 @@
<template>
<view :class="themeClass" class="page-container detail-page">
<!-- 顶部导航 -->
<view class="detail-nav" :style="{ paddingTop: navPaddingTop }">
<view class="detail-nav-inner">
<view class="detail-back" @click="goBack">
<text class="detail-back-icon"></text>
</view>
<text class="detail-nav-title">动态详情</text>
<view class="detail-nav-ph"></view>
</view>
</view>
<!-- 动态内容 -->
<scroll-view class="detail-scroll" scroll-y>
<view class="detail-body">
<FeedCard
v-if="feed"
:feed="feed"
@like="handleLike"
/>
<!-- 评论区 -->
<view class="comments-section">
<view class="comments-header">
<text class="comments-title">评论</text>
<text class="comments-count">{{ comments.length }}</text>
</view>
<CommentItem
v-for="c in comments"
:key="c.id"
:comment="c"
/>
<EmptyState
v-if="!comments.length"
icon="💬"
title="暂无评论"
desc="来说点什么吧"
/>
</view>
</view>
</scroll-view>
<!-- 底部评论输入 -->
<view class="comment-bar">
<input
class="comment-input"
v-model="commentText"
placeholder="写评论..."
placeholder-class="comment-placeholder"
confirm-type="send"
@confirm="submitComment"
/>
<view class="comment-send" :class="{ disabled: !commentText.trim() }" @click="submitComment">
<text class="comment-send-text">发送</text>
</view>
</view>
</view>
</template>
<script>
import FeedCard from '../../components/FeedCard.vue'
import CommentItem from '../../components/CommentItem.vue'
import EmptyState from '../../components/EmptyState.vue'
import { GetCircleFeeds, GetFeedComments, AddComment, LikeFeed, UnlikeFeed } from '../../common/api'
import { MOCK_FEEDS } from '../../common/mock-data'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
components: { FeedCard, CommentItem, EmptyState },
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
return {
navPaddingTop: menuBtn.top + 'px',
feedId: '',
feed: null,
comments: [],
commentText: ''
}
},
onLoad(options) {
this.feedId = options.id || ''
this.loadFeed()
this.loadComments()
},
methods: {
goBack() {
uni.navigateBack()
},
async loadFeed() {
// 从 Mock 数据中查找对应动态
this.feed = MOCK_FEEDS.find(f => f.id === this.feedId) || MOCK_FEEDS[0]
},
async loadComments() {
try {
const res = await GetFeedComments({ feedId: this.feedId })
this.comments = res.data.list
} catch (e) {
console.warn('加载评论失败', e)
}
},
async handleLike(feed) {
feed.liked = !feed.liked
feed.likes += feed.liked ? 1 : -1
try {
if (feed.liked) await LikeFeed({ feedId: feed.id })
else await UnlikeFeed({ feedId: feed.id })
} catch (e) { /* ignore */ }
},
async submitComment() {
const text = this.commentText.trim()
if (!text) return
try {
await AddComment({ feedId: this.feedId, content: text })
this.comments.push({
id: 'c_' + Date.now(),
userId: 'me',
nickname: '我',
avatar: '',
content: text,
time: '刚刚'
})
this.commentText = ''
if (this.feed) this.feed.comments = (this.feed.comments || 0) + 1
} catch (e) {
uni.showToast({ title: '发送失败', icon: 'none' })
}
}
}
}
</script>
<style lang="scss" scoped>
.detail-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.detail-nav {
background: $bg-base;
padding-left: $sp-lg;
padding-right: $sp-lg;
position: relative;
z-index: 10;
}
.detail-nav-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
}
.detail-back {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: $bg-card;
&:active {
background: $bg-card-alt;
}
}
.detail-back-icon {
font-size: $fs-xl;
color: $text-primary;
font-weight: $fw-bold;
}
.detail-nav-title {
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
}
.detail-nav-ph {
width: 64rpx;
}
.detail-scroll {
flex: 1;
height: 0;
}
.detail-body {
padding: $sp-md $sp-lg;
padding-bottom: $sp-xl;
}
.comments-section {
margin-top: $sp-lg;
background: $bg-card;
border-radius: $radius-xl;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
padding: $sp-xl;
}
.comments-header {
display: flex;
align-items: center;
gap: $sp-sm;
margin-bottom: $sp-md;
}
.comments-title {
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
}
.comments-count {
font-size: $fs-sm;
color: $text-tertiary;
}
/* 底部评论栏 */
.comment-bar {
display: flex;
align-items: center;
gap: $sp-md;
padding: $sp-md $sp-lg;
padding-bottom: calc(env(safe-area-inset-bottom) + #{$sp-md});
background: $bg-card;
border-top: 1rpx solid var(--divider-color, rgba(255,255,255,0.08));
}
.comment-input {
flex: 1;
height: 72rpx;
padding: 0 $sp-lg;
background: $bg-elevated;
border-radius: $radius-full;
font-size: $fs-base;
color: $text-primary;
}
.comment-placeholder {
color: $text-tertiary;
}
.comment-send {
padding: $sp-sm $sp-lg;
background: linear-gradient(135deg, $amber, $amber-deep);
border-radius: $radius-full;
&.disabled {
opacity: 0.4;
}
&:active {
transform: scale(0.95);
}
}
.comment-send-text {
font-size: $fs-sm;
font-weight: $fw-bold;
color: $text-on-amber;
}
</style>