From ddc90e4b0d4aad50507eef077e35122c488e8205 Mon Sep 17 00:00:00 2001 From: hefanyang Date: Sat, 13 Jun 2026 07:47:49 +0800 Subject: [PATCH] 1.2 --- frontend/src/composables/useWebSocket.ts | 21 +- frontend/src/layouts/ChatLayout.vue | 203 ------------- frontend/src/layouts/MainLayout.vue | 38 --- frontend/src/layouts/UnifiedLayout.vue | 34 ++- frontend/src/views/chat/ChatListView.vue | 168 ++++++++++- frontend/src/views/chat/ChatRoomView.vue | 270 ++++++++++-------- .../src/views/contacts/ContactsSidebar.vue | 25 +- .../src/views/contacts/FriendProfileCard.vue | 127 ++++++++ .../src/views/moments/MomentsFeedView.vue | 58 +++- frontend/src/views/profile/ProfileView.vue | 70 ----- 提示词.md | 4 +- 11 files changed, 562 insertions(+), 456 deletions(-) delete mode 100644 frontend/src/layouts/ChatLayout.vue delete mode 100644 frontend/src/layouts/MainLayout.vue create mode 100644 frontend/src/views/contacts/FriendProfileCard.vue delete mode 100644 frontend/src/views/profile/ProfileView.vue diff --git a/frontend/src/composables/useWebSocket.ts b/frontend/src/composables/useWebSocket.ts index 1ccecc4..b191234 100644 --- a/frontend/src/composables/useWebSocket.ts +++ b/frontend/src/composables/useWebSocket.ts @@ -8,8 +8,11 @@ let ws: WebSocket | null = null let reconnectAttempts = 0 const maxReconnectAttempts = 10 +// 共享状态 +const connected = ref(false) +const onlineUsers = ref(new Map()) + export function useWebSocket() { - const connected = ref(false) function connect() { const auth = useAuthStore() @@ -36,7 +39,6 @@ export function useWebSocket() { ws.onclose = () => { connected.value = false console.log('🌿 WebSocket 已断开') - // 自动重连 if (reconnectAttempts < maxReconnectAttempts) { const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000) reconnectAttempts++ @@ -71,12 +73,17 @@ export function useWebSocket() { chatStore.addMessage(event.data) break case 'presence.online': + onlineUsers.value.set(event.data.user_id, true) + console.log(`用户 ${event.data.user_id} 上线`) + break case 'presence.offline': - // 可以在这里更新联系人在线状态 - console.log(`用户 ${event.data.user_id} ${event.type === 'presence.online' ? '上线' : '下线'}`) + onlineUsers.value.set(event.data.user_id, false) + console.log(`用户 ${event.data.user_id} 下线`) break case 'friend.request': console.log('收到好友请求:', event.data) + // 通过自定义事件通知全局 + window.dispatchEvent(new CustomEvent('qingye:friend-request', { detail: event.data })) break case 'error': console.error('服务端错误:', event.data.message) @@ -84,5 +91,9 @@ export function useWebSocket() { } } - return { connected, connect, disconnect, send } + function isUserOnline(userId: string): boolean { + return onlineUsers.value.get(userId) === true + } + + return { connected, onlineUsers, connect, disconnect, send, isUserOnline } } diff --git a/frontend/src/layouts/ChatLayout.vue b/frontend/src/layouts/ChatLayout.vue deleted file mode 100644 index 4a331b3..0000000 --- a/frontend/src/layouts/ChatLayout.vue +++ /dev/null @@ -1,203 +0,0 @@ - - - - - diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue deleted file mode 100644 index 426ac10..0000000 --- a/frontend/src/layouts/MainLayout.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/frontend/src/layouts/UnifiedLayout.vue b/frontend/src/layouts/UnifiedLayout.vue index 5a63a6c..02a87fc 100644 --- a/frontend/src/layouts/UnifiedLayout.vue +++ b/frontend/src/layouts/UnifiedLayout.vue @@ -1,5 +1,10 @@ @@ -111,6 +122,17 @@ import { ref } from 'vue' background: var(--color-bg); } +.reconnect-bar { + position: fixed; top: 0; left: 0; right: 0; z-index: 200; + background: #FFF3E0; color: #E65100; font-size: 13px; text-align: center; + padding: 6px; display: flex; align-items: center; justify-content: center; gap: 6px; +} +.reconnect-dot { + width: 8px; height: 8px; border-radius: 50%; background: #FF9800; + animation: blink 1s infinite; +} +@keyframes blink { 50% { opacity: 0.3; } } + /* 左侧图标栏 */ .icon-rail { width: 64px; diff --git a/frontend/src/views/chat/ChatListView.vue b/frontend/src/views/chat/ChatListView.vue index 61292ad..7f2e69f 100644 --- a/frontend/src/views/chat/ChatListView.vue +++ b/frontend/src/views/chat/ChatListView.vue @@ -1,14 +1,166 @@ - diff --git a/frontend/src/views/chat/ChatRoomView.vue b/frontend/src/views/chat/ChatRoomView.vue index 23dc4fe..536c9a3 100644 --- a/frontend/src/views/chat/ChatRoomView.vue +++ b/frontend/src/views/chat/ChatRoomView.vue @@ -3,118 +3,115 @@
-
+
{{ (conversationName || '群')[0] }}
{{ conversationName }} - - {{ convDetail.members?.length || 0 }} 位成员 - - 在线 + {{ convDetail.members?.length || 0 }} 位成员 + {{ isOtherOnline ? '在线' : '离线' }}
- - - 群信息 ▾ - + 群信息 ▾
- -
+
🌿

开始聊天吧

发送第一条消息

-
- -
- {{ formatTimeDivider(msg.created_at) }} -
- +
+
{{ formatTimeDivider(msg.created_at) }}
- - + +
+
📋 复制
+
🗑️ 删除
+
+ +
- 😊 - 🖼️ + 😊 + 🖼️ + +
+ +
+
{{ e }}
- - 发送 - + placeholder="输入消息..." @keydown.enter.exact.prevent="sendMessage" @focus="showEmoji = false" /> + 发送
diff --git a/frontend/src/views/contacts/FriendProfileCard.vue b/frontend/src/views/contacts/FriendProfileCard.vue new file mode 100644 index 0000000..3aa5418 --- /dev/null +++ b/frontend/src/views/contacts/FriendProfileCard.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/frontend/src/views/moments/MomentsFeedView.vue b/frontend/src/views/moments/MomentsFeedView.vue index d5d0c15..09bd628 100644 --- a/frontend/src/views/moments/MomentsFeedView.vue +++ b/frontend/src/views/moments/MomentsFeedView.vue @@ -49,6 +49,20 @@
+ +
+
+
+ + +
+
+ + + 图片 +
+
+ +
可见范围: @@ -73,6 +87,7 @@ import { ref, onMounted } from 'vue' import { useMomentsStore } from '@/stores/moments' import { useMessage } from 'naive-ui' +import api from '@/api/client' import MomentCard from './MomentCard.vue' const momentsStore = useMomentsStore() @@ -82,18 +97,41 @@ const showCompose = ref(false) const composeText = ref('') const composeVisibility = ref('friends') const publishing = ref(false) +const composeImages = ref([]) +const momentImageInput = ref() onMounted(() => { momentsStore.fetchFeed(true) }) +function triggerMomentImage() { + momentImageInput.value?.click() +} + +async function handleMomentImages(event: Event) { + const target = event.target as HTMLInputElement + const files = target.files + if (!files) return + for (let i = 0; i < files.length && composeImages.value.length < 9; i++) { + try { + const formData = new FormData() + formData.append('file', files[i]) + const { data } = await api.post('/uploads/file', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) + composeImages.value.push(data.url) + } catch {} + } + target.value = '' +} + async function publishMoment() { if (!composeText.value.trim()) return publishing.value = true try { - await momentsStore.createMoment(composeText.value.trim(), undefined, composeVisibility.value) + const images = composeImages.value.length > 0 ? composeImages.value : undefined + await momentsStore.createMoment(composeText.value.trim(), images, composeVisibility.value) message.success('动态发布成功') composeText.value = '' + composeImages.value = [] showCompose.value = false } catch { message.error('发布失败') @@ -159,6 +197,24 @@ async function handleComment(momentId: string, content: string) { .close-btn:hover { color: var(--color-text-primary); } .compose-body { padding: 16px 24px; } .compose-options { margin-top: 12px; } + +/* 图片上传 */ +.image-upload-area { margin-top: 10px; } +.image-previews { display: flex; gap: 8px; flex-wrap: wrap; } +.image-preview-item { width: 64px; height: 64px; position: relative; border-radius: 8px; overflow: hidden; } +.image-preview-item img { width: 100%; height: 100%; object-fit: cover; } +.remove-img { + position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; + background: rgba(0,0,0,0.5); color: white; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + font-size: 10px; cursor: pointer; +} +.add-image-btn { + width: 64px; height: 64px; border: 2px dashed var(--color-border); border-radius: 8px; + display: flex; flex-direction: column; align-items: center; justify-content: center; + cursor: pointer; color: var(--color-text-hint); font-size: 24px; transition: border-color 0.2s; +} +.add-image-btn:hover { border-color: var(--color-primary-lighter); color: var(--color-primary); } .visibility-select { display: flex; align-items: center; gap: 8px; } .option-label { font-size: 13px; color: var(--color-text-secondary); } .compose-footer { diff --git a/frontend/src/views/profile/ProfileView.vue b/frontend/src/views/profile/ProfileView.vue deleted file mode 100644 index 088dc76..0000000 --- a/frontend/src/views/profile/ProfileView.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - - - diff --git a/提示词.md b/提示词.md index 8b60819..addadfb 100644 --- a/提示词.md +++ b/提示词.md @@ -12,4 +12,6 @@ 账号绑定邮箱 -不要列表卡片瀑布流,功能丰富一点,不要太单调,界面也丰富一点 \ No newline at end of file +不要列表卡片瀑布流,功能丰富一点,不要太单调,界面也丰富一点 + +要多功能,界面丰富不单调。 \ No newline at end of file