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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ +