From eaa0d7101f47ba53c5d64ce70602d6da6b09cab2 Mon Sep 17 00:00:00 2001 From: cheng <545895878@qq.com> Date: Tue, 21 Jul 2026 21:27:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E6=B7=BB=E5=8A=A0=E7=A7=81?= =?UTF-8?q?=E4=BF=A1=E5=8A=9F=E8=83=BD=E5=92=8CWebSocket=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E9=80=9A=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 集成WebSocket管理器,支持心跳、自动重连、消息队列 - 实现私信相关API接口,包括会话列表、历史消息、已读标记等 - 添加聊天页面和会话列表页面,支持实时消息收发 - 配置地图权限和腾讯地图SDK用于酒局定位功能 - 修改应用名称为"喝酒了么"并更新应用描述 - 在App.vue中初始化WebSocket连接和私信功能 - 添加详细的私信模块接口文档和Mock数据 - 优化单图片动态高度计算和预加载逻辑 --- App.vue | 10 + common/api.js | 34 +- common/mock-data.js | 52 +++ common/websocket.js | 253 ++++++++++++ docs/私信模块接口文档.md | 465 +++++++++++++++++++++ manifest.json | 22 +- pages.json | 14 + pages/card/card.vue | 18 +- pages/chat-list/chat-list.vue | 294 ++++++++++++++ pages/chat/chat.vue | 601 ++++++++++++++++++++++++++++ pages/circle/circle.vue | 86 +++- pages/event-create/event-create.vue | 142 ++++++- pages/event-detail/event-detail.vue | 75 ++++ pages/games/games.vue | 84 +++- 14 files changed, 2112 insertions(+), 38 deletions(-) create mode 100644 common/websocket.js create mode 100644 docs/私信模块接口文档.md create mode 100644 pages/chat-list/chat-list.vue create mode 100644 pages/chat/chat.vue diff --git a/App.vue b/App.vue index e7af936..726d2ea 100644 --- a/App.vue +++ b/App.vue @@ -1,6 +1,7 @@ + + diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue new file mode 100644 index 0000000..3a449c1 --- /dev/null +++ b/pages/chat/chat.vue @@ -0,0 +1,601 @@ + + + + + diff --git a/pages/circle/circle.vue b/pages/circle/circle.vue index 597d6ab..684a5d4 100644 --- a/pages/circle/circle.vue +++ b/pages/circle/circle.vue @@ -2,7 +2,15 @@ - 酒友圈 + + 酒友圈 + + 💬 + + {{ unreadTotal > 99 ? '99+' : unreadTotal }} + + + conv_001) + const convId = 'conv_' + friend.id.replace('f', '') + uni.navigateTo({ + url: `/pages/chat/chat?friendId=${friend.id}&nickname=${encodeURIComponent(friend.nickname)}&conversationId=${convId}` + }) + }, + goChatList() { + uni.navigateTo({ url: '/pages/chat-list/chat-list' }) + }, + async loadUnread() { + try { + const res = await GetUnreadCount() + this.unreadTotal = res.data.total + } catch (e) { /* ignore */ } + }, // === 酒局 === async loadEvents() { try { @@ -256,12 +285,57 @@ export default { z-index: 10; } +.circle-title-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: $sp-lg; +} + .circle-title { - display: block; font-size: $fs-2xl; font-weight: $fw-black; color: $text-primary; - margin-bottom: $sp-lg; +} + +.msg-entry { + position: relative; + width: 72rpx; + height: 72rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: $bg-card; + + &:active { + background: $bg-card-alt; + } +} + +.msg-entry-icon { + font-size: 36rpx; +} + +.msg-badge { + position: absolute; + top: -4rpx; + right: -4rpx; + min-width: 32rpx; + height: 32rpx; + padding: 0 8rpx; + border-radius: 16rpx; + background: $coral; + display: flex; + align-items: center; + justify-content: center; +} + +.msg-badge-text { + font-size: 18rpx; + color: #fff; + font-weight: $fw-bold; + line-height: 1; } .circle-tabs { diff --git a/pages/event-create/event-create.vue b/pages/event-create/event-create.vue index 2c565dc..3a958ba 100644 --- a/pages/event-create/event-create.vue +++ b/pages/event-create/event-create.vue @@ -45,13 +45,31 @@ 地点 - + + + + + {{ form.location }} + {{ form.address || '' }} + + + 重新选择 + + + + + 📍 + 点击选择地点 + + @@ -104,6 +122,9 @@ export default { date: '', time: '', location: '', + address: '', + latitude: null, + longitude: null, maxPeople: 6, note: '' }, @@ -113,6 +134,17 @@ export default { computed: { canSubmit() { return this.form.title.trim() && this.form.date && this.form.time && this.form.location.trim() && !this.submitting + }, + locationMarkers() { + if (!this.form.latitude) return [] + return [{ + id: 1, + latitude: this.form.latitude, + longitude: this.form.longitude, + title: this.form.location, + width: 28, + height: 38 + }] } }, methods: { @@ -125,6 +157,19 @@ export default { onTimeChange(e) { this.form.time = e.detail.value }, + chooseLocation() { + uni.chooseLocation({ + success: (res) => { + this.form.location = res.name || res.address || '' + this.form.address = res.address || '' + this.form.latitude = res.latitude + this.form.longitude = res.longitude + }, + fail: () => { + // 用户取消或无权限,不做处理 + } + }) + }, changePeople(delta) { const val = this.form.maxPeople + delta if (val >= 2 && val <= 50) { @@ -139,6 +184,9 @@ export default { title: this.form.title, time: `${this.form.date} ${this.form.time}`, location: this.form.location, + address: this.form.address, + latitude: this.form.latitude, + longitude: this.form.longitude, maxPeople: this.form.maxPeople, note: this.form.note }) @@ -279,6 +327,86 @@ export default { box-sizing: border-box; } +/* 地点选择器 */ +.location-picker { + display: flex; + align-items: center; + gap: $sp-md; + height: 96rpx; + padding: 0 $sp-lg; + background: $bg-card; + border-radius: $radius-lg; + border: 2rpx dashed var(--border-dashed, rgba(255,255,255,0.12)); + + &:active { + background: $bg-card-alt; + } +} + +.location-picker-icon { + font-size: $fs-lg; +} + +.location-picker-text { + flex: 1; + font-size: $fs-base; + color: $text-tertiary; +} + +.location-picker-arrow { + font-size: $fs-xl; + color: $text-tertiary; +} + +/* 地图预览 */ +.location-preview { + border-radius: $radius-lg; + overflow: hidden; + border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08)); +} + +.location-map { + width: 100%; + height: 280rpx; +} + +.location-info { + display: flex; + flex-direction: column; + gap: 6rpx; + padding: $sp-md $sp-lg; + background: $bg-card; +} + +.location-name { + font-size: $fs-base; + font-weight: $fw-bold; + color: $text-primary; +} + +.location-address { + font-size: $fs-xs; + color: $text-tertiary; +} + +.location-change { + display: flex; + align-items: center; + justify-content: center; + padding: $sp-sm; + background: $bg-card-alt; + + &:active { + background: $bg-elevated; + } +} + +.location-change-text { + font-size: $fs-sm; + color: $amber; + font-weight: $fw-medium; +} + /* 人数步进器 */ .form-stepper { display: flex; diff --git a/pages/event-detail/event-detail.vue b/pages/event-detail/event-detail.vue index ec13fb3..7314abc 100644 --- a/pages/event-detail/event-detail.vue +++ b/pages/event-detail/event-detail.vue @@ -53,6 +53,22 @@ + + + + + 🧭 + 导航到这里 + + + @@ -131,6 +147,17 @@ export default { if (!this.event) return '' const map = { open: '报名中', ongoing: '进行中', ended: '已结束' } return map[this.event.status] || '报名中' + }, + eventMarkers() { + if (!this.event || !this.event.latitude) return [] + return [{ + id: 1, + latitude: this.event.latitude, + longitude: this.event.longitude, + title: this.event.location, + width: 28, + height: 38 + }] } }, onLoad(options) { @@ -186,6 +213,18 @@ export default { } catch (e) { uni.showToast({ title: '签到失败', icon: 'none' }) } + }, + openNavigation() { + if (!this.event || !this.event.latitude) return + uni.openLocation({ + latitude: this.event.latitude, + longitude: this.event.longitude, + name: this.event.location, + address: this.event.address || this.event.location, + fail: () => { + uni.showToast({ title: '无法打开地图', icon: 'none' }) + } + }) } } } @@ -324,6 +363,42 @@ export default { font-weight: $fw-medium; } +/* 地图区域 */ +.evt-map-section { + margin-bottom: $sp-xl; + border-radius: $radius-xl; + overflow: hidden; + border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08)); +} + +.evt-map { + width: 100%; + height: 320rpx; +} + +.evt-map-nav-btn { + display: flex; + align-items: center; + justify-content: center; + gap: $sp-sm; + padding: $sp-md; + background: $bg-card; + + &:active { + background: $bg-card-alt; + } +} + +.evt-map-nav-icon { + font-size: $fs-base; +} + +.evt-map-nav-text { + font-size: $fs-base; + color: $amber; + font-weight: $fw-bold; +} + /* 发起人 & 参与者 */ .evt-section-label { display: block; diff --git a/pages/games/games.vue b/pages/games/games.vue index 1aaf537..b6d8eda 100644 --- a/pages/games/games.vue +++ b/pages/games/games.vue @@ -166,10 +166,18 @@ - {{ diceShaking ? '?' : diceFaces[diceA] }} + + + + + - {{ diceShaking ? '?' : diceFaces[diceB] }} + + + + + {{ diceA + diceB }} @@ -213,7 +221,9 @@ :key="i" :class="{ 'slot-item-active': !wheelSpinning && wheelResult && i === slotTarget }" > - {{ s.emoji }} + + {{ s.emoji }} + {{ s.label }} @@ -299,7 +309,6 @@ export default { rpsLastPick: '', rpsTimer: null, // --- 骰子 --- - diceFaces: ['', '⚀', '⚁', '⚂', '⚃', '⚄', '⚅'], diceA: 1, diceB: 1, diceShaking: false, @@ -452,6 +461,18 @@ export default { }, /* ========== 骰子大话 ========== */ + // 骰子点数对应的 pip 位置(3x3 九宫格,1~9 从左到右、从上到下) + dicePips(val) { + const map = { + 1: [5], + 2: [1, 9], + 3: [1, 5, 9], + 4: [1, 3, 7, 9], + 5: [1, 3, 5, 7, 9], + 6: [1, 3, 4, 6, 7, 9] + } + return map[val] || [] + }, diceRoll() { if (this.diceShaking) return this.diceShaking = true @@ -716,14 +737,14 @@ export default { .rule-title { display: block; - font-size: $fs-sm; + font-size: 30rpx; font-weight: $fw-bold; color: $text-secondary; margin-bottom: $sp-sm; } .rule-text { - font-size: $fs-xs; + font-size: 30rpx; color: $text-tertiary; line-height: $lh-loose; } @@ -784,7 +805,7 @@ export default { background: linear-gradient(135deg, var(--amber, #E8A838), var(--amber-deep, #C47F17)); box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25)); transition: transform $duration-fast $ease-out, opacity $duration-fast; - text { color: var(--text-on-amber, #0B0B14); font-size: $fs-base; font-weight: $fw-bold; } + text { color: var(--text-on-amber, #0B0B14); font-size:40rpx; font-weight: $fw-bold; } &:active { transform: scale(0.94); } &.btn-disabled { opacity: 0.5; pointer-events: none; } } @@ -1069,11 +1090,12 @@ export default { } .die { - width: 120rpx; height: 120rpx; - display: flex; align-items: center; justify-content: center; + width: 132rpx; height: 132rpx; + display: flex; + align-items: center; justify-content: center; border-radius: $radius-md; - background: $bg-elevated; - border: 2rpx solid var(--border-subtle, rgba(255,255,255,0.10)); + background: linear-gradient(160deg, #FBF7EF, #EAE2D3); + box-shadow: inset 0 4rpx 8rpx rgba(255,255,255,0.7), inset 0 -6rpx 12rpx rgba(0,0,0,0.12), $shadow-sm; transition: transform $duration-base $ease-bounce; &.die-rolling { animation: die-spin 0.3s linear infinite; } @@ -1085,7 +1107,28 @@ export default { 100% { transform: rotate(360deg) scale(1); } } -.die-face { font-size: 72rpx; color: $text-primary; } +.die-grid { + display: flex; + flex-wrap: wrap; + width: 96rpx; + height: 96rpx; +} + +.die-cell { + width: 32rpx; + height: 32rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.die-pip { + width: 18rpx; + height: 18rpx; + border-radius: 50%; + background: #241B10; + box-shadow: inset 0 2rpx 3rpx rgba(0,0,0,0.5); +} .dice-sum { font-size: $fs-2xl; @@ -1199,6 +1242,7 @@ export default { position: absolute; left: 0; right: 0; top: 0; + width: 100%; will-change: transform; } @@ -1206,7 +1250,7 @@ export default { display: flex; align-items: center; justify-content: center; - gap: $sp-md; + gap: $sp-sm; height: 120rpx; border-bottom: 1rpx solid var(--border-micro, rgba(255,255,255,0.05)); transition: background $duration-base; @@ -1216,8 +1260,18 @@ export default { } } -.slot-item-emoji { font-size: 44rpx; } -.slot-item-label { font-size: $fs-base; font-weight: $fw-bold; color: $text-primary; } +.slot-item-emoji-box { + width: 52rpx; + height: 52rpx; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + flex-shrink: 0; +} + +.slot-item-emoji { font-size: 40rpx; line-height: 1; } +.slot-item-label { font-size: $fs-base; font-weight: $fw-bold; color: $text-primary; letter-spacing: 2rpx; } .slot-mask-top, .slot-mask-bottom { position: absolute;