From fabbb167cf662bcbcd74f61713121b395ab3d271 Mon Sep 17 00:00:00 2001
From: cheng <545895878@qq.com>
Date: Mon, 20 Jul 2026 22:53:04 +0800
Subject: [PATCH] =?UTF-8?q?feat(circle):=20=E6=B7=BB=E5=8A=A0=E9=85=92?=
=?UTF-8?q?=E5=8F=8B=E5=9C=88=E7=A4=BE=E4=BA=A4=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增酒友圈、圈子详情、发布动态、好友列表、活动创建等页面配置
- 添加酒友圈相关API接口,包括动态、评论、好友、活动等功能
- 实现模拟数据用于前端开发和测试
- 创建评论项、空状态、活动卡片、动态卡片、好友项等组件
- 编写酒友圈模块详细的接口文档,定义数据结构和业务规则
---
common/api.js | 104 ++++
common/mock-data.js | 181 +++++++
components/CommentItem.vue | 90 ++++
components/EmptyState.vue | 70 +++
components/EventCard.vue | 179 +++++++
components/FeedCard.vue | 239 +++++++++
components/FriendItem.vue | 99 ++++
docs/酒友圈接口文档.md | 675 ++++++++++++++++++++++++
pages.json | 48 ++
pages/circle-detail/circle-detail.vue | 267 ++++++++++
pages/circle-publish/circle-publish.vue | 430 +++++++++++++++
pages/circle/circle.vue | 378 +++++++++++++
pages/event-create/event-create.vue | 340 ++++++++++++
pages/event-detail/event-detail.vue | 450 ++++++++++++++++
pages/friends/friends.vue | 378 +++++++++++++
pages/profile/profile.vue | 82 ++-
static/tab-circle-active.png | Bin 0 -> 1686 bytes
static/tab-circle.png | Bin 0 -> 1672 bytes
18 files changed, 3992 insertions(+), 18 deletions(-)
create mode 100644 components/CommentItem.vue
create mode 100644 components/EmptyState.vue
create mode 100644 components/EventCard.vue
create mode 100644 components/FeedCard.vue
create mode 100644 components/FriendItem.vue
create mode 100644 docs/酒友圈接口文档.md
create mode 100644 pages/circle-detail/circle-detail.vue
create mode 100644 pages/circle-publish/circle-publish.vue
create mode 100644 pages/circle/circle.vue
create mode 100644 pages/event-create/event-create.vue
create mode 100644 pages/event-detail/event-detail.vue
create mode 100644 pages/friends/friends.vue
create mode 100644 static/tab-circle-active.png
create mode 100644 static/tab-circle.png
diff --git a/common/api.js b/common/api.js
index 552152c..a9c8bea 100644
--- a/common/api.js
+++ b/common/api.js
@@ -154,3 +154,107 @@ export async function refreshAuthToken() {
return false
}
}
+
+// ==========================================
+// 酒友圈 API(当前返回 Mock 数据,后端就绪后切换为 SDK 调用)
+// ==========================================
+import { MOCK_FEEDS, MOCK_COMMENTS, MOCK_FRIENDS, MOCK_FRIEND_REQUESTS, MOCK_EVENTS } from './mock-data'
+
+/** 模拟网络延迟 */
+function mockDelay(data, ms = 300) {
+ return new Promise(resolve => setTimeout(() => resolve({ data }), ms))
+}
+
+/** 获取动态信息流 */
+export function GetCircleFeeds({ page = 1, pageSize = 10 } = {}) {
+ // TODO: 后端就绪后替换为 client.GetCircleFeeds(...)
+ const start = (page - 1) * pageSize
+ const list = MOCK_FEEDS.slice(start, start + pageSize)
+ return mockDelay({ list, total: MOCK_FEEDS.length, hasMore: start + pageSize < MOCK_FEEDS.length })
+}
+
+/** 点赞动态 */
+export function LikeFeed({ feedId }) {
+ return mockDelay({ success: true })
+}
+
+/** 取消点赞 */
+export function UnlikeFeed({ feedId }) {
+ return mockDelay({ success: true })
+}
+
+/** 获取动态评论 */
+export function GetFeedComments({ feedId }) {
+ const list = MOCK_COMMENTS[feedId] || []
+ return mockDelay({ list })
+}
+
+/** 发表评论 */
+export function AddComment({ feedId, content }) {
+ return mockDelay({ success: true, comment: { id: 'c_' + Date.now(), content, time: '刚刚' } })
+}
+
+/** 发布动态 */
+export function PublishFeed({ text, images, recordId, visibility }) {
+ return mockDelay({ success: true, feedId: 'feed_' + Date.now() })
+}
+
+/** 获取酒友列表 */
+export function GetFriends({ keyword = '' } = {}) {
+ const list = keyword
+ ? MOCK_FRIENDS.filter(f => f.nickname.includes(keyword))
+ : MOCK_FRIENDS
+ return mockDelay({ list })
+}
+
+/** 获取好友请求 */
+export function GetFriendRequests() {
+ return mockDelay({ list: MOCK_FRIEND_REQUESTS })
+}
+
+/** 发送好友请求 */
+export function SendFriendRequest({ userId }) {
+ return mockDelay({ success: true })
+}
+
+/** 接受好友请求 */
+export function AcceptFriendRequest({ requestId }) {
+ return mockDelay({ success: true })
+}
+
+/** 删除酒友 */
+export function RemoveFriend({ userId }) {
+ return mockDelay({ success: true })
+}
+
+/** 获取酒局列表 */
+export function GetEvents({ status = '' } = {}) {
+ const list = status ? MOCK_EVENTS.filter(e => e.status === status) : MOCK_EVENTS
+ return mockDelay({ list })
+}
+
+/** 获取酒局详情 */
+export function GetEventDetail({ eventId }) {
+ const evt = MOCK_EVENTS.find(e => e.id === eventId) || MOCK_EVENTS[0]
+ return mockDelay(evt)
+}
+
+/** 发起酒局 */
+export function CreateEvent({ title, time, location, maxPeople, note }) {
+ return mockDelay({ success: true, eventId: 'evt_' + Date.now() })
+}
+
+/** 报名酒局 */
+export function JoinEvent({ eventId }) {
+ return mockDelay({ success: true })
+}
+
+/** 取消报名 */
+export function QuitEvent({ eventId }) {
+ return mockDelay({ success: true })
+}
+
+/** 酒局签到 */
+export function CheckInEvent({ eventId }) {
+ return mockDelay({ success: true })
+}
diff --git a/common/mock-data.js b/common/mock-data.js
index 6bb4e80..581894f 100644
--- a/common/mock-data.js
+++ b/common/mock-data.js
@@ -148,3 +148,184 @@ export function getUserInfo() {
}
return MOCK_USER
}
+
+// ==========================================
+// 酒友圈 Mock 数据
+// ==========================================
+
+// 模拟酒友列表
+export const MOCK_FRIENDS = [
+ { id: 'f001', nickname: '老张', avatar: '', lastDrink: '昨晚喝了飞天茅台', lastDrinkTime: '2小时前', online: true },
+ { id: 'f002', nickname: '酒仙李白', avatar: '', lastDrink: '青岛啤酒×3', lastDrinkTime: '5小时前', online: true },
+ { id: 'f003', nickname: '小红', avatar: '', lastDrink: '梅见青梅酒', lastDrinkTime: '昨天', online: false },
+ { id: 'f004', nickname: '阿伟', avatar: '', lastDrink: '百威经典×2', lastDrinkTime: '昨天', online: false },
+ { id: 'f005', nickname: '品酒师Tony', avatar: '', lastDrink: '麦卡伦12年', lastDrinkTime: '3天前', online: true },
+ { id: 'f006', nickname: '醉翁之意', avatar: '', lastDrink: '奔富Bin389', lastDrinkTime: '3天前', online: false }
+]
+
+// 模拟好友请求
+export const MOCK_FRIEND_REQUESTS = [
+ { id: 'req001', userId: 'u101', nickname: '夜猫子', avatar: '', message: '一起喝过酒,加个好友', time: '1小时前' },
+ { id: 'req002', userId: 'u102', nickname: '杯中物', avatar: '', message: '酒友圈看到你的动态', time: '3小时前' }
+]
+
+// 模拟动态信息流
+export const MOCK_FEEDS = [
+ {
+ id: 'feed001',
+ userId: 'f001',
+ nickname: '老张',
+ avatar: '',
+ time: '2小时前',
+ text: '今晚和几个老兄弟整了瓶飞天,配重庆火锅,巴适得很!',
+ drinks: [{ category: 'baijiu', name: '飞天茅台53度', amount: '100ml' }],
+ feeling: 'tipsy',
+ images: [],
+ likes: 12,
+ liked: false,
+ comments: 3
+ },
+ {
+ id: 'feed002',
+ userId: 'f002',
+ nickname: '酒仙李白',
+ avatar: '',
+ time: '5小时前',
+ text: '夏天就是要冰啤酒配小龙虾,三瓶百威下肚,人生圆满。',
+ drinks: [{ category: 'beer', name: '百威经典', amount: '3瓶' }],
+ feeling: 'buzzed',
+ images: [],
+ likes: 8,
+ liked: true,
+ comments: 5
+ },
+ {
+ id: 'feed003',
+ userId: 'f003',
+ nickname: '小红',
+ avatar: '',
+ time: '昨天 21:30',
+ text: '第一次尝试青梅酒,酸酸甜甜的,适合女生微醺~',
+ drinks: [{ category: 'fruit', name: '梅见青梅酒', amount: '200ml' }],
+ feeling: 'tipsy',
+ images: [],
+ likes: 15,
+ liked: false,
+ comments: 7
+ },
+ {
+ id: 'feed004',
+ userId: 'f005',
+ nickname: '品酒师Tony',
+ avatar: '',
+ time: '昨天 19:00',
+ text: '麦卡伦12年雪莉桶,闻香有太妃糖和干果的气息,入口丝滑。推荐搭配黑巧克力。',
+ drinks: [{ category: 'whisky', name: '麦卡伦12年', amount: '3 shot' }],
+ feeling: 'tipsy',
+ images: [],
+ likes: 22,
+ liked: false,
+ comments: 4
+ },
+ {
+ id: 'feed005',
+ userId: 'f004',
+ nickname: '阿伟',
+ avatar: '',
+ time: '前天 20:15',
+ text: '公司团建,五粮液配水煮鱼,喝到位了。明天又要搬砖...',
+ drinks: [{ category: 'baijiu', name: '五粮液普五52度', amount: '150ml' }, { category: 'beer', name: '雪花勇闯天涯', amount: '1瓶' }],
+ feeling: 'drunk',
+ images: [],
+ likes: 6,
+ liked: false,
+ comments: 2
+ }
+]
+
+// 模拟评论数据
+export const MOCK_COMMENTS = {
+ feed001: [
+ { id: 'c001', userId: 'f002', nickname: '酒仙李白', avatar: '', content: '茅台配火锅,土豪啊老张!', time: '1小时前' },
+ { id: 'c002', userId: 'f003', nickname: '小红', avatar: '', content: '看着就馋了,下次带上我', time: '45分钟前' },
+ { id: 'c003', userId: 'f005', nickname: '品酒师Tony', avatar: '', content: '飞天53度确实是经典,好酒!', time: '30分钟前' }
+ ],
+ feed002: [
+ { id: 'c004', userId: 'f001', nickname: '老张', avatar: '', content: '夏天标配!', time: '4小时前' },
+ { id: 'c005', userId: 'f004', nickname: '阿伟', avatar: '', content: '小龙虾哪买的?看着不错', time: '3小时前' }
+ ],
+ feed003: [
+ { id: 'c006', userId: 'f001', nickname: '老张', avatar: '', content: '梅见确实适合入门', time: '昨天' },
+ { id: 'c007', userId: 'f002', nickname: '酒仙李白', avatar: '', content: '下次试试蜜桃味', time: '昨天' }
+ ]
+}
+
+// 模拟酒局活动
+export const MOCK_EVENTS = [
+ {
+ id: 'evt001',
+ title: '周五夜·老地方火锅局',
+ organizer: { id: 'f001', nickname: '老张', avatar: '' },
+ time: '2026-07-25 19:00',
+ location: '渝味晓宇火锅(解放碑店)',
+ maxPeople: 8,
+ joined: 5,
+ participants: [
+ { id: 'f001', nickname: '老张', avatar: '' },
+ { id: 'f002', nickname: '酒仙李白', avatar: '' },
+ { id: 'f003', nickname: '小红', avatar: '' },
+ { id: 'f004', nickname: '阿伟', avatar: '' },
+ { id: 'f005', nickname: '品酒师Tony', avatar: '' }
+ ],
+ status: 'open',
+ note: '自带酒水,AA制餐费',
+ isJoined: false,
+ isOrganizer: false
+ },
+ {
+ id: 'evt002',
+ title: '威士忌品鉴之夜',
+ organizer: { id: 'f005', nickname: '品酒师Tony', avatar: '' },
+ time: '2026-07-27 20:00',
+ location: 'Malt Bar(国贸店)',
+ maxPeople: 6,
+ joined: 3,
+ participants: [
+ { id: 'f005', nickname: '品酒师Tony', avatar: '' },
+ { id: 'f001', nickname: '老张', avatar: '' },
+ { id: 'f006', nickname: '醉翁之意', avatar: '' }
+ ],
+ status: 'open',
+ note: '品鉴3款单一麦芽,费用AA',
+ isJoined: true,
+ isOrganizer: false
+ },
+ {
+ id: 'evt003',
+ title: '啤酒花园烧烤趴',
+ organizer: { id: 'f002', nickname: '酒仙李白', avatar: '' },
+ time: '2026-07-20 18:00',
+ location: '望京啤酒花园',
+ maxPeople: 12,
+ joined: 10,
+ participants: [],
+ status: 'ongoing',
+ note: '畅饮模式,不醉不归',
+ isJoined: false,
+ isOrganizer: false
+ },
+ {
+ id: 'evt004',
+ title: '红酒配牛排·优雅之夜',
+ organizer: { id: 'f003', nickname: '小红', avatar: '' },
+ time: '2026-07-15 19:30',
+ location: 'The Grill(三里屯)',
+ maxPeople: 4,
+ joined: 4,
+ participants: [],
+ status: 'ended',
+ note: '已圆满结束,下次再约',
+ isJoined: false,
+ isOrganizer: false
+ }
+]
diff --git a/components/CommentItem.vue b/components/CommentItem.vue
new file mode 100644
index 0000000..0524f32
--- /dev/null
+++ b/components/CommentItem.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
diff --git a/components/EmptyState.vue b/components/EmptyState.vue
new file mode 100644
index 0000000..addc3f8
--- /dev/null
+++ b/components/EmptyState.vue
@@ -0,0 +1,70 @@
+
+
+ {{ icon }}
+ {{ title }}
+ {{ desc }}
+
+
+
+
+
+
+
diff --git a/components/EventCard.vue b/components/EventCard.vue
new file mode 100644
index 0000000..059b855
--- /dev/null
+++ b/components/EventCard.vue
@@ -0,0 +1,179 @@
+
+
+
+
+ {{ statusLabel }}
+
+
+
+ {{ event.title }}
+
+
+
+
+ 🕐
+ {{ event.time }}
+
+
+ 📍
+ {{ event.location }}
+
+
+ 👥
+ {{ event.joined }}/{{ event.maxPeople }} 人已报名
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/FeedCard.vue b/components/FeedCard.vue
new file mode 100644
index 0000000..2a6f70e
--- /dev/null
+++ b/components/FeedCard.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+ {{ feed.text }}
+
+
+
+
+ {{ categoryEmoji(d.category) }}
+ {{ d.name }} {{ d.amount }}
+
+
+
+
+
+ {{ feelingLabel(feed.feeling) }}
+
+
+
+
+
+
+
+
+
+
+ {{ feed.liked ? '❤️' : '🤍' }}
+ {{ feed.likes || '' }}
+
+
+ 💬
+ {{ feed.comments || '' }}
+
+
+ ↗
+
+
+
+
+
+
+
+
diff --git a/components/FriendItem.vue b/components/FriendItem.vue
new file mode 100644
index 0000000..71e982e
--- /dev/null
+++ b/components/FriendItem.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+ {{ friend.nickname ? friend.nickname[0] : '酒' }}
+
+
+
+
+ {{ friend.nickname }}
+ {{ friend.lastDrink }} · {{ friend.lastDrinkTime }}
+
+
+
+
+
+
+
+
diff --git a/docs/酒友圈接口文档.md b/docs/酒友圈接口文档.md
new file mode 100644
index 0000000..07af754
--- /dev/null
+++ b/docs/酒友圈接口文档.md
@@ -0,0 +1,675 @@
+# 干杯日记 - 酒友圈模块接口文档
+
+> 版本: v1.0
+> 日期: 2026-07-20
+> 模块: 酒友圈(社交模块)
+> 小程序: 干杯日记(uni-app 微信小程序)
+> 基础URL: `https://your-domain.com/api/v1`
+
+---
+
+## 1. 模块概述
+
+酒友圈是干杯日记的核心社交模块,包含三大子功能:
+
+| 子功能 | 说明 |
+|--------|------|
+| 动态信息流 | 酒友发布饮酒动态,支持点赞、评论 |
+| 酒友管理 | 好友请求、酒友列表、搜索、删除 |
+| 约酒活动 | 发起酒局、报名、取消、签到 |
+
+**前端当前状态:** 所有接口已在前端 `common/api.js` 中预留调用方法,暂用 Mock 数据。后端实现后前端仅需切换调用方式,无需改动页面逻辑。**请严格按照本文档的字段名和数据结构实现**,确保前端零改动对接。
+
+---
+
+## 2. 通用约定
+
+### 2.1 鉴权
+- 请求头:`Authorization: Bearer {token}`
+- 所有接口均需登录态
+
+### 2.2 统一响应格式
+```json
+{
+ "code": 0,
+ "message": "success",
+ "data": {}
+}
+```
+
+### 2.3 错误码
+| code | 含义 |
+|------|------|
+| 0 | 成功 |
+| 400 | 参数错误 |
+| 401 | 未登录/token过期 |
+| 403 | 无权限(如:非酒友关系、非发起人) |
+| 404 | 资源不存在 |
+| 409 | 冲突(如:重复报名、已是酒友) |
+| 500 | 服务器错误 |
+
+### 2.4 时间格式
+- 所有时间字段使用 ISO 8601:`2026-07-20T22:30:00.000Z`
+- 前端会自行转换为相对时间("2小时前"等)
+
+---
+
+## 3. 动态信息流
+
+### 3.1 获取动态列表
+```
+GET /circle/feeds
+```
+
+**查询参数:**
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| page | int | 否 | 页码,默认1 |
+| pageSize | int | 否 | 每页数量,默认10 |
+
+**响应 data:**
+```json
+{
+ "list": [
+ {
+ "id": "feed001",
+ "userId": "user_002",
+ "nickname": "老张",
+ "avatar": "https://xxx/avatar.jpg",
+ "time": "2026-07-20T20:30:00.000Z",
+ "text": "今晚和几个老兄弟整了瓶飞天,配重庆火锅,巴适得很!",
+ "drinks": [
+ { "category": "baijiu", "name": "飞天茅台53度", "amount": "100ml" }
+ ],
+ "feeling": "tipsy",
+ "images": ["https://xxx/img1.jpg"],
+ "likes": 12,
+ "liked": false,
+ "comments": 3
+ }
+ ],
+ "total": 50,
+ "hasMore": true
+}
+```
+
+**Feed 字段说明:**
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 动态ID |
+| userId | string | 发布者用户ID |
+| nickname | string | 发布者昵称 |
+| avatar | string | 发布者头像URL(可为空字符串) |
+| time | string | 发布时间 ISO 8601 |
+| text | string | 动态文字内容 |
+| drinks | DrinkTag[] | 关联酒水标签(见下方结构) |
+| feeling | string/null | 感受ID:sober/buzzed/tipsy/drunk |
+| images | string[] | 配图URL数组(最多9张) |
+| likes | int | 点赞数 |
+| liked | boolean | 当前用户是否已点赞 |
+| comments | int | 评论数 |
+
+**DrinkTag 结构:**
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| category | string | 酒类ID(baijiu/beer/wine/whisky/huangjiu/sake/fruit/cocktail) |
+| name | string | 酒名(如"飞天茅台53度") |
+| amount | string | 用量展示文本(如"100ml"、"3瓶") |
+
+**业务规则:**
+- 仅返回当前用户**酒友**发布的动态(visibility=friends)+ 公开动态(visibility=public)
+- 按发布时间倒序
+- 自己的动态也包含在内
+
+---
+
+### 3.2 发布动态
+```
+POST /circle/feeds
+```
+
+**请求参数:**
+```json
+{
+ "text": "今晚小酌一杯",
+ "images": ["https://xxx/img1.jpg"],
+ "recordId": "record_1689234567890",
+ "visibility": "friends"
+}
+```
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| text | string | 否 | 文字内容(最长500字),text和images至少一项非空 |
+| images | string[] | 否 | 图片URL数组(最多9张,先调上传接口获取URL) |
+| recordId | string/null | 否 | 关联的饮酒记录ID,传入时自动提取酒水信息生成drinks标签 |
+| visibility | string | 否 | 可见范围:friends(默认)/private |
+
+**响应 data:**
+```json
+{
+ "success": true,
+ "feedId": "feed_1689234567890"
+}
+```
+
+**业务规则:**
+- 传入 recordId 时,后端从记录中提取 drinks 信息填充到动态的 drinks 字段
+- UGC 内容(text)需接入微信内容安全审核(msgSecCheck)
+
+---
+
+### 3.3 点赞动态
+```
+POST /circle/feeds/:id/like
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+### 3.4 取消点赞
+```
+POST /circle/feeds/:id/unlike
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+> 重复点赞/取消应幂等处理,不报错。
+
+---
+
+### 3.5 获取动态评论
+```
+GET /circle/feeds/:id/comments
+```
+
+**响应 data:**
+```json
+{
+ "list": [
+ {
+ "id": "c001",
+ "userId": "user_003",
+ "nickname": "酒仙李白",
+ "avatar": "https://xxx/avatar3.jpg",
+ "content": "茅台配火锅,土豪啊老张!",
+ "time": "2026-07-20T21:00:00.000Z"
+ }
+ ]
+}
+```
+
+**Comment 字段说明:**
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 评论ID |
+| userId | string | 评论者ID |
+| nickname | string | 评论者昵称 |
+| avatar | string | 评论者头像 |
+| content | string | 评论内容 |
+| time | string | 评论时间 ISO 8601 |
+
+> 按时间正序返回(最早在前)。
+
+---
+
+### 3.6 发表评论
+```
+POST /circle/feeds/:id/comments
+```
+
+**请求参数:**
+```json
+{ "content": "看着就馋了,下次带上我" }
+```
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| content | string | 是 | 评论内容(最长200字) |
+
+**响应 data:**
+```json
+{
+ "success": true,
+ "comment": {
+ "id": "c_1689234567890",
+ "content": "看着就馋了,下次带上我",
+ "time": "2026-07-20T22:00:00.000Z"
+ }
+}
+```
+
+**业务规则:**
+- 评论需接入内容安全审核
+- 评论成功后,动态的 comments 计数 +1
+
+---
+
+## 4. 酒友管理
+
+### 4.1 获取酒友列表
+```
+GET /circle/friends
+```
+
+**查询参数:**
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| keyword | string | 否 | 按昵称模糊搜索 |
+
+**响应 data:**
+```json
+{
+ "list": [
+ {
+ "id": "user_002",
+ "nickname": "老张",
+ "avatar": "https://xxx/avatar.jpg",
+ "lastDrink": "昨晚喝了飞天茅台",
+ "lastDrinkTime": "2026-07-20T20:30:00.000Z",
+ "online": true
+ }
+ ]
+}
+```
+
+**Friend 字段说明:**
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 酒友用户ID |
+| nickname | string | 昵称 |
+| avatar | string | 头像URL |
+| lastDrink | string | 最近一次饮酒摘要文本(如"昨晚喝了飞天茅台"),无记录则为空 |
+| lastDrinkTime | string | 最近饮酒时间 ISO 8601 |
+| online | boolean | 是否在线(24小时内活跃视为在线) |
+
+---
+
+### 4.2 获取好友请求列表
+```
+GET /circle/friend-requests
+```
+
+**响应 data:**
+```json
+{
+ "list": [
+ {
+ "id": "req001",
+ "userId": "user_101",
+ "nickname": "夜猫子",
+ "avatar": "https://xxx/avatar101.jpg",
+ "message": "一起喝过酒,加个好友",
+ "time": "2026-07-20T21:00:00.000Z"
+ }
+ ]
+}
+```
+
+> 仅返回**待处理**的请求(status=pending),按时间倒序。
+
+---
+
+### 4.3 发送好友请求
+```
+POST /circle/friend-requests
+```
+
+**请求参数:**
+```json
+{
+ "userId": "user_101",
+ "message": "一起喝过酒,加个好友"
+}
+```
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| userId | string | 是 | 目标用户ID |
+| message | string | 否 | 验证消息(最长50字) |
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+**业务规则:**
+- 已是酒友返回 409
+- 已有待处理请求则幂等返回成功
+
+---
+
+### 4.4 接受好友请求
+```
+POST /circle/friend-requests/:id/accept
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+**业务规则:**
+- 接受后双方建立酒友关系
+- 请求状态更新为 accepted
+
+---
+
+### 4.5 删除酒友
+```
+DELETE /circle/friends/:userId
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+**业务规则:**
+- 双向解除酒友关系
+- 删除后对方动态不再出现在自己的信息流中
+
+---
+
+## 5. 约酒活动(酒局)
+
+### 5.1 获取酒局列表
+```
+GET /circle/events
+```
+
+**查询参数:**
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| status | string | 否 | 状态筛选:open/ongoing/ended,不传返回全部 |
+
+**响应 data:**
+```json
+{
+ "list": [
+ {
+ "id": "evt001",
+ "title": "周五夜·老地方火锅局",
+ "organizer": {
+ "id": "user_002",
+ "nickname": "老张",
+ "avatar": "https://xxx/avatar.jpg"
+ },
+ "time": "2026-07-25T19:00:00.000Z",
+ "location": "渝味晓宇火锅(解放碑店)",
+ "maxPeople": 8,
+ "joined": 5,
+ "participants": [
+ { "id": "user_002", "nickname": "老张", "avatar": "https://xxx/avatar.jpg" }
+ ],
+ "status": "open",
+ "note": "自带酒水,AA制餐费",
+ "isJoined": false,
+ "isOrganizer": false
+ }
+ ]
+}
+```
+
+**Event 字段说明:**
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 酒局ID |
+| title | string | 酒局名称(最长30字) |
+| organizer | UserInfo | 发起人信息 |
+| time | string | 开始时间 ISO 8601 |
+| location | string | 地点文本 |
+| maxPeople | int | 人数上限(2-50) |
+| joined | int | 已报名人数(含发起人) |
+| participants | UserInfo[] | 参与者列表 |
+| status | string | 状态:open(报名中)/ongoing(进行中)/ended(已结束) |
+| note | string | 备注 |
+| isJoined | boolean | 当前用户是否已报名 |
+| isOrganizer | boolean | 当前用户是否为发起人 |
+
+**状态流转规则(后端自动维护):**
+- `open`:当前时间 < 开始时间
+- `ongoing`:开始时间 <= 当前时间 < 开始时间+6小时
+- `ended`:当前时间 >= 开始时间+6小时
+
+**可见范围:** 仅酒友可见(同动态信息流规则)
+
+---
+
+### 5.2 获取酒局详情
+```
+GET /circle/events/:id
+```
+
+**响应 data:** 同 5.1 中单个 Event 结构
+
+---
+
+### 5.3 发起酒局
+```
+POST /circle/events
+```
+
+**请求参数:**
+```json
+{
+ "title": "周五夜·老地方火锅局",
+ "time": "2026-07-25T19:00:00.000Z",
+ "location": "渝味晓宇火锅(解放碑店)",
+ "maxPeople": 8,
+ "note": "自带酒水,AA制餐费"
+}
+```
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| title | string | 是 | 酒局名称(最长30字) |
+| time | string | 是 | 开始时间(必须为未来时间) |
+| location | string | 是 | 地点(最长50字) |
+| maxPeople | int | 是 | 人数上限(2-50) |
+| note | string | 否 | 备注(最长200字) |
+
+**响应 data:**
+```json
+{
+ "success": true,
+ "eventId": "evt_1689234567890"
+}
+```
+
+**业务规则:**
+- 发起人自动算作已报名(joined 初始为1)
+- 发起人 isOrganizer = true
+
+---
+
+### 5.4 报名酒局
+```
+POST /circle/events/:id/join
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+**业务规则:**
+- 人数已满返回 409
+- 酒局非 open 状态返回 403
+- 重复报名幂等处理
+
+---
+
+### 5.5 取消报名
+```
+POST /circle/events/:id/quit
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+**业务规则:**
+- 发起人不可取消报名(返回 403)
+- 未报名状态幂等处理
+
+---
+
+### 5.6 酒局签到
+```
+POST /circle/events/:id/checkin
+```
+
+**响应 data:**
+```json
+{ "success": true }
+```
+
+**业务规则:**
+- 仅 ongoing 状态可签到(返回 403)
+- 仅已报名用户可签到
+- 签到后可在参与者信息中标记 checkedIn(可选扩展)
+
+---
+
+## 6. 数据库表设计参考
+
+### 6.1 酒友关系表 (friendships)
+```sql
+CREATE TABLE friendships (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ user_id VARCHAR(32) NOT NULL,
+ friend_id VARCHAR(32) NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE INDEX idx_user_friend (user_id, friend_id),
+ INDEX idx_friend (friend_id)
+);
+```
+> 双向关系存两条记录(A->B 和 B->A),便于查询。
+
+### 6.2 好友请求表 (friend_requests)
+```sql
+CREATE TABLE friend_requests (
+ id VARCHAR(32) PRIMARY KEY,
+ from_user_id VARCHAR(32) NOT NULL,
+ to_user_id VARCHAR(32) NOT NULL,
+ message VARCHAR(64),
+ status ENUM('pending', 'accepted', 'rejected') DEFAULT 'pending',
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_to_status (to_user_id, status),
+ INDEX idx_from_to (from_user_id, to_user_id)
+);
+```
+
+### 6.3 动态表 (feeds)
+```sql
+CREATE TABLE feeds (
+ id VARCHAR(32) PRIMARY KEY,
+ user_id VARCHAR(32) NOT NULL,
+ text VARCHAR(512),
+ drinks JSON,
+ feeling VARCHAR(16),
+ images JSON,
+ record_id VARCHAR(32),
+ visibility ENUM('friends', 'private') DEFAULT 'friends',
+ like_count INT DEFAULT 0,
+ comment_count INT DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_user_time (user_id, created_at),
+ INDEX idx_created (created_at)
+);
+```
+
+### 6.4 评论表 (comments)
+```sql
+CREATE TABLE comments (
+ id VARCHAR(32) PRIMARY KEY,
+ feed_id VARCHAR(32) NOT NULL,
+ user_id VARCHAR(32) NOT NULL,
+ content VARCHAR(256) NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_feed (feed_id, created_at)
+);
+```
+
+### 6.5 酒局表 (events)
+```sql
+CREATE TABLE events (
+ id VARCHAR(32) PRIMARY KEY,
+ organizer_id VARCHAR(32) NOT NULL,
+ title VARCHAR(64) NOT NULL,
+ start_time DATETIME NOT NULL,
+ location VARCHAR(128) NOT NULL,
+ max_people INT NOT NULL DEFAULT 6,
+ joined_count INT NOT NULL DEFAULT 1,
+ note VARCHAR(256),
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_organizer (organizer_id),
+ INDEX idx_start_time (start_time)
+);
+```
+
+### 6.6 酒局报名表 (event_participants)
+```sql
+CREATE TABLE event_participants (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ event_id VARCHAR(32) NOT NULL,
+ user_id VARCHAR(32) NOT NULL,
+ checked_in TINYINT(1) DEFAULT 0,
+ joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE INDEX idx_event_user (event_id, user_id),
+ INDEX idx_user (user_id)
+);
+```
+
+---
+
+## 7. 前端对接说明
+
+### 7.1 前端调用位置
+所有接口调用集中在 `common/api.js`,当前为 Mock 实现,方法签名如下:
+
+```js
+// 动态
+GetCircleFeeds({ page, pageSize }) // GET /circle/feeds
+PublishFeed({ text, images, recordId, visibility }) // POST /circle/feeds
+LikeFeed({ feedId }) // POST /circle/feeds/:id/like
+UnlikeFeed({ feedId }) // POST /circle/feeds/:id/unlike
+GetFeedComments({ feedId }) // GET /circle/feeds/:id/comments
+AddComment({ feedId, content }) // POST /circle/feeds/:id/comments
+
+// 酒友
+GetFriends({ keyword }) // GET /circle/friends
+GetFriendRequests() // GET /circle/friend-requests
+SendFriendRequest({ userId }) // POST /circle/friend-requests
+AcceptFriendRequest({ requestId }) // POST /circle/friend-requests/:id/accept
+RemoveFriend({ userId }) // DELETE /circle/friends/:userId
+
+// 酒局
+GetEvents({ status }) // GET /circle/events
+GetEventDetail({ eventId }) // GET /circle/events/:id
+CreateEvent({ title, time, location, maxPeople, note }) // POST /circle/events
+JoinEvent({ eventId }) // POST /circle/events/:id/join
+QuitEvent({ eventId }) // POST /circle/events/:id/quit
+CheckInEvent({ eventId }) // POST /circle/events/:id/checkin
+```
+
+### 7.2 响应解包约定
+前端 SDK 会自动解包 `data.data`,因此接口实际返回给前端的结构为:
+```json
+{ "code": 0, "data": { ... } }
+```
+前端拿到的就是 `data` 内的对象。
+
+### 7.3 注意事项
+1. **字段名严格一致**:前端已按本文档字段名渲染,请勿更改命名(如 `likes` 不能改为 `likeCount`)
+2. **头像可为空**:avatar 字段允许空字符串,前端有默认占位头像
+3. **时间格式**:统一 ISO 8601,前端自行格式化
+4. **内容安全**:text、content、title、note 等 UGC 字段需接入微信 msgSecCheck 审核
+5. **幂等性**:点赞/取消、报名/取消等操作需幂等,避免前端重试导致计数错误
diff --git a/pages.json b/pages.json
index c6342d5..200a098 100644
--- a/pages.json
+++ b/pages.json
@@ -48,6 +48,48 @@
"navigationStyle": "custom",
"navigationBarTitleText": ""
}
+ },
+ {
+ "path": "pages/circle/circle",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": ""
+ }
+ },
+ {
+ "path": "pages/circle-detail/circle-detail",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": ""
+ }
+ },
+ {
+ "path": "pages/circle-publish/circle-publish",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": ""
+ }
+ },
+ {
+ "path": "pages/friends/friends",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": ""
+ }
+ },
+ {
+ "path": "pages/event-create/event-create",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": ""
+ }
+ },
+ {
+ "path": "pages/event-detail/event-detail",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": ""
+ }
}
],
"globalStyle": {
@@ -70,6 +112,12 @@
"iconPath": "static/tab-home.png",
"selectedIconPath": "static/tab-home-active.png"
},
+ {
+ "pagePath": "pages/circle/circle",
+ "text": "酒友圈",
+ "iconPath": "static/tab-circle.png",
+ "selectedIconPath": "static/tab-circle-active.png"
+ },
{
"pagePath": "pages/profile/profile",
"text": "我的",
diff --git a/pages/circle-detail/circle-detail.vue b/pages/circle-detail/circle-detail.vue
new file mode 100644
index 0000000..1351647
--- /dev/null
+++ b/pages/circle-detail/circle-detail.vue
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+ ‹
+
+ 动态详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/circle-publish/circle-publish.vue b/pages/circle-publish/circle-publish.vue
new file mode 100644
index 0000000..95ed100
--- /dev/null
+++ b/pages/circle-publish/circle-publish.vue
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+ ‹
+
+ 发布动态
+
+ 发布
+
+
+
+
+
+
+
+
+
+ 🍻
+
+
+ 关联最近饮酒记录
+ {{ recordSummary }}
+
+
+ {{ linkRecord ? '✓' : '' }}
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+ +
+ {{ images.length }}/9
+
+
+
+
+
+ 谁可以看
+
+
+ 所有酒友
+
+
+ 仅自己
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/circle/circle.vue b/pages/circle/circle.vue
new file mode 100644
index 0000000..597d6ab
--- /dev/null
+++ b/pages/circle/circle.vue
@@ -0,0 +1,378 @@
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+ — 没有更多了 —
+
+
+
+
+
+
+
+
+
+
+ 🔔
+ {{ friendRequests.length }} 条新的好友请求
+ ›
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentTab === 2 ? '🎉' : '✏️' }}
+
+
+
+
+
+
+
diff --git a/pages/event-create/event-create.vue b/pages/event-create/event-create.vue
new file mode 100644
index 0000000..2c565dc
--- /dev/null
+++ b/pages/event-create/event-create.vue
@@ -0,0 +1,340 @@
+
+
+
+
+
+
+ ‹
+
+ 发起酒局
+
+
+
+
+
+
+
+
+ 酒局名称
+
+
+
+
+
+ 时间
+
+
+ {{ form.date || '选择日期' }}
+ ›
+
+
+
+
+ {{ form.time || '选择时间' }}
+ ›
+
+
+
+
+
+
+ 地点
+
+
+
+
+
+ 人数上限
+
+
+ −
+
+ {{ form.maxPeople }}
+
+ +
+
+
+
+
+
+
+ 备注(选填)
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/event-detail/event-detail.vue b/pages/event-detail/event-detail.vue
new file mode 100644
index 0000000..ec13fb3
--- /dev/null
+++ b/pages/event-detail/event-detail.vue
@@ -0,0 +1,450 @@
+
+
+
+
+
+
+ ‹
+
+ 酒局详情
+
+
+
+
+
+
+
+
+ {{ statusLabel }}
+
+
+
+ {{ event.title }}
+
+
+
+
+ 🕐
+
+ 时间
+ {{ event.time }}
+
+
+
+ 📍
+
+ 地点
+ {{ event.location }}
+
+
+
+ 👥
+
+ 人数
+ {{ event.joined }}/{{ event.maxPeople }} 人已报名
+
+
+
+ 📝
+
+ 备注
+ {{ event.note }}
+
+
+
+
+
+
+ 发起人
+
+
+ {{ event.organizer ? event.organizer.nickname[0] : '酒' }}
+
+ {{ event.organizer ? event.organizer.nickname : '' }}
+
+
+
+
+
+ 已报名 ({{ event.participants.length }})
+
+
+
+ {{ p.nickname[0] }}
+
+ {{ p.nickname }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 酒局已结束
+
+
+ 你是发起人
+
+
+
+
+
+
+
+
diff --git a/pages/friends/friends.vue b/pages/friends/friends.vue
new file mode 100644
index 0000000..0495a25
--- /dev/null
+++ b/pages/friends/friends.vue
@@ -0,0 +1,378 @@
+
+
+
+
+
+
+ ‹
+
+ 我的酒友
+
+
+
+
+
+
+
+ 🔍
+
+
+ ×
+
+
+
+
+
+
+
+
+ 新的好友请求
+
+
+ {{ req.nickname[0] }}
+
+
+ {{ req.nickname }}
+ {{ req.message }}
+
+
+ 接受
+
+
+
+
+
+
+ 全部酒友 ({{ friends.length }})
+ 搜索结果 ({{ friends.length }})
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/profile/profile.vue b/pages/profile/profile.vue
index 28315ae..f27b275 100644
--- a/pages/profile/profile.vue
+++ b/pages/profile/profile.vue
@@ -5,6 +5,7 @@
+ 🥃