Browse Source

add:完成消息接受消息发送功能

master
qiutianhong 7 months ago
parent
commit
3f2853f394
  1. 3
      frontend/auto-imports.d.ts
  2. 1
      frontend/components.d.ts
  3. 106
      frontend/src/components/chat/Chat.vue
  4. 62
      frontend/src/components/chat/ChatBox.vue
  5. 127
      frontend/src/components/chat/ChatMessage.vue
  6. 14
      frontend/src/components/chat/ChatMsgList.vue
  7. 23
      frontend/src/components/chat/ChatUserList.vue
  8. 4
      frontend/src/components/chat/chatUserInfo.vue
  9. 419
      frontend/src/stores/chat.ts
  10. 23
      frontend/src/stores/db.ts
  11. 18
      frontend/src/stores/upgrade.ts
  12. 5
      frontend/src/system/index.ts

3
frontend/auto-imports.d.ts

@ -3,6 +3,7 @@
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
@ -70,6 +71,6 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

1
frontend/components.d.ts

@ -51,7 +51,6 @@ declare module 'vue' {
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElBu: typeof import('element-plus/es')['ElBu']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']

106
frontend/src/components/chat/Chat.vue

@ -7,21 +7,37 @@
onMounted(() => {
store.initChat();
});
const userList = ref([
{
id: 2,
nickname: "朋友2",
avatar: "/logo.png",
previewTimeFormat: "昨天",
previewType: 1,
previewMessage: "测试消息",
},
{
id: 3,
nickname: "朋友3",
avatar: "/logo.png",
previewTimeFormat: "昨天",
previewType: 1,
previewMessage: "测试消息",
},
]);
// el-transfer
const generateData = () => {
const data = [];
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: ` ${i}`,
disabled: i % 4 === 0,
});
}
return data;
return userList.value.map((user) => ({
key: user.id,
label: user.nickname,
avatar: user.avatar, //
}));
};
const data = generateData();
const value = ref([]);
const data = ref(generateData());
const users = ref([]);
</script>
<template>
<el-container class="container">
@ -47,7 +63,7 @@
<!-- 邀请群聊 -->
<button
class="inviteGroupChats"
@click="store.setGroupChatDialogVisible(true)"
@click="store.setGroupChatInvitedDialogVisible(true)"
>
<el-icon><Plus /></el-icon>
</button>
@ -57,7 +73,6 @@
<el-scrollbar>
<chat-msg-list v-if="store.currentNavId == 0" />
<chat-user-list v-if="store.currentNavId == 1" />
<!-- <chat-work-list v-if="store.currentNavId == 2" /> -->
</el-scrollbar>
</el-main>
</el-container>
@ -81,29 +96,44 @@
<ChatUserSetting />
</el-container>
</el-container>
<!-- 群聊弹窗 -->
<!-- 邀请群聊弹窗 -->
<el-dialog
v-model="store.groupChatDialogVisible"
v-model="store.groupChatInvitedDialogVisible"
title="发起群聊"
width="600px"
width="80%"
>
<div class="transfer">
<el-transfer class="transfer-box" v-model="value" :data="data" />
</div>
<div class="dialog-body">
<el-transfer
v-model="users"
:data="data"
>
<!-- 义穿梭框 -->
<template #default="{ option }">
<div class="user-item">
<el-avatar
:size="20"
:src="option.avatar"
class="avatar"
/>
<span>{{ option.label }}</span>
</div>
</template>
</el-transfer>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="store.groupChatDialogVisible = false"
<el-button @click="store.groupChatInvitedDialogVisible = false"
>取消</el-button
>
<el-button
type="primary"
@click="createGroupChat"
<el-button @click="store.setGroupInfoDrawerVisible(true)"
>确定</el-button
>
</span>
</template>
</el-dialog>
</template>
<style scoped>
.container {
display: flex;
@ -149,13 +179,12 @@
background-color: #f0f0f0;
}
.transfer-box {
height: 220px;
display: flex;
justify-content: center;
align-items: center;
}
.user-item {
width: 100%;
height: 30px;
display: flex;
align-items: center;
}
.search-input {
width: calc(100% - 20px);
@ -174,6 +203,21 @@
overflow-y: hidden;
overflow-x: hidden;
}
.dialog-body {
width: 100%;
}
.el-transfer {
display: flex;
align-items: center;
justify-content: center;
}
/* .el-transfer >>> .el-transfer-panel >>> .el-transfer__buttons {
width: 50px;
} */
/* .el-transfer__button {
width: 20px;;
} */
.chat-box {
flex: 3;

62
frontend/src/components/chat/ChatBox.vue

@ -4,23 +4,29 @@
</script>
<template>
<div
class="chatbox-main"
v-if="store.targetUserId > 0"
>
<!--聊天顶部区-->
<el-header class="chat-header">
<div class="header-title">{{ store.targetUserInfo.username }}</div>
</el-header>
<!--聊天主体区-->
<el-main class="msg-main">
<el-scrollbar ref="store.scrollbarRef">
<div ref="store.innerRef">
<ChatMessage />
</div>
</el-scrollbar>
</el-main>
<el-footer class="msg-footer">
<div
class="chatbox-main"
v-if="store.targetUserId > 0"
>
<!--聊天顶部区-->
<el-header class="chat-header">
<div class="header-title" >
{{ store.targetUserInfo.nickname }}
</div>
</el-header>
<!--聊天主体区-->
<el-main class="msg-main">
<el-scrollbar ref="store.scrollbarRef">
<div ref="store.innerRef">
<ChatMessage />
</div>
</el-scrollbar>
</el-main>
<!--聊天输入区和发送按钮等-->
<el-footer class="msg-footer">
<!--聊天输入选项-->
<div class="input-option">
<el-icon
@ -67,20 +73,16 @@
</el-icon>
</el-tooltip>
</el-footer>
</div>
<div
class="no-message-container"
v-else
>
<el-icon
:size="180"
color="#0078d7"
>
<ChatDotSquare />
</el-icon>
<p>欢迎使用GodoOS</p>
</div>
</div>
<div class="no-message-container" v-else>
<el-icon :size="180" color="#0078d7">
<ChatDotSquare />
</el-icon>
<p>欢迎使用GodoOS</p>
</div>
</template>
<style scoped>
.chatbox-main {
width: 100%;

127
frontend/src/components/chat/ChatMessage.vue

@ -1,19 +1,11 @@
<script setup lang="ts">
import { useChatStore } from "@/stores/chat";
const chatHistory = computed(() => store.chatHistory as any);
const store = useChatStore();
</script>
<template>
<div
v-for="item in chatHistory"
:key="item.id"
v-if="store.chatHistory.length > 0"
v-for="item in store.chatHistory"
:key="item.time"
>
<div
v-if="!item.isme"
v-if="item.userId == store.userInfo.id"
class="chat-item"
>
<el-row>
@ -22,20 +14,22 @@
<el-row>
<el-col :span="24">
<div class="chat-name-me">
{{ item.userInfo.username }}
{{ item.userInfo.nickname }}
</div>
</el-col>
</el-row>
<div
class="bubble-me"
@contextmenu.prevent="
store.showContextMenu($event, item.id)
store.showContextMenu($event, item.userId)
"
>
<div class="chat-font">
{{ item.content }}
{{ item.message }}
</div>
</div>
<!-- 时间显示在消息框外 -->
<div class="chat-time">{{ formatTime(item.time) }}</div>
</el-col>
<el-col :span="2">
<div class="chat-avatar">
@ -70,31 +64,24 @@
<el-row>
<el-col :span="24">
<div class="chat-name-other">
{{ item.userInfo.username }}
{{ item.userInfo.nickname }}
</div>
</el-col>
</el-row>
<div class="bubble-other">
<div class="chat-font">
{{ item.content }}
{{ item.message }}
</div>
</div>
<!-- 时间显示在消息框外 -->
<div class="chat-time">{{ formatTime(item.time) }}</div>
</el-col>
<el-col :span="8" />
</el-row>
</div>
<div
v-if="item.type === 1"
class="withdraw"
>
{{
item.userInfo.id === store.targetUserId
? "你"
: item.userInfo.username
}}撤回了一条消息
</div>
</div>
<!--悬浮菜单-->
<!-- 悬浮菜单 -->
<div
class="context-menu"
v-if="store.contextMenu.visible"
@ -118,6 +105,21 @@
</div>
</template>
<script setup lang="ts">
import { useChatStore } from "@/stores/chat";
const store = useChatStore();
const formatTime = (timestamp: number) => {
const date = new Date(timestamp);
const options: Intl.DateTimeFormatOptions = {
hour: "numeric",
minute: "numeric",
hour12: false,
};
return date.toLocaleString("default", options);
};
</script>
<style scoped>
.bubble-me {
background-color: #95ec69;
@ -125,19 +127,7 @@
border-radius: 4px;
margin-right: 5px;
margin-top: 5px;
}
.bubble-me:hover {
background-color: #89d961;
}
.chat-name-me {
font-size: 14px;
font-family: Arial, sans-serif;
line-height: 1.5;
color: #b2b2b2;
float: right;
margin-right: 5px;
padding: 5px;
}
.bubble-other {
@ -146,19 +136,15 @@
border-radius: 4px;
margin-left: 5px;
margin-top: 5px;
padding: 5px;
}
.bubble-other:hover {
background-color: #ebebeb;
}
.chat-name-me,
.chat-name-other {
font-size: 14px;
font-family: Arial, sans-serif;
line-height: 1.5;
color: #b2b2b2;
float: left;
margin-left: 5px;
}
.chat-font {
@ -168,43 +154,14 @@
line-height: 1.5;
}
.chat-avatar {
margin: 5px;
}
.chat-item {
margin: 5px;
}
.withdraw {
text-align: center;
font-size: 13px;
font-family: Arial, sans-serif;
color: #999999;
line-height: 3.2;
}
.context-menu {
position: fixed;
background-color: white;
z-index: 9999;
border: 1px solid #cccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.context-menu-item {
width: 80px;
height: 30px;
}
.context-menu-item:hover {
background-color: #e2e2e2;
}
.context-menu-item-font {
font-size: 14px;
text-align: center;
font-family: Arial, sans-serif;
line-height: 2.2;
.chat-time {
font-size: 12px;
color: #999;
height: 50px;
display: flex;
align-items: self-end;
justify-content: start;
padding-left: 10px;
text-align: left;
}
</style>

14
frontend/src/components/chat/ChatMsgList.vue

@ -2,9 +2,9 @@
<div v-if="store.chatList.length > 0" v-for="item in store.chatList" :key="item.id">
<div
class="list-item"
@click="store.changeChatList(item.id)"
@click="store.changeChatList(item.userId)"
:style="{
backgroundColor: item.id === store.targetUserId ? '#bae7ff' : '',
backgroundColor: item.userId == store.targetUserId ? '#bae7ff' : '',
}"
>
<el-row>
@ -21,14 +21,8 @@
<el-col :span="18" class="preview-left">
<div class="previewName">{{ item.nickname }}</div>
<div class="previewChat">
<span v-if="item.previewType === 0">{{ item.previewMessage }}</span>
<span v-if="item.previewType === 1">
{{
item.targetUserId === id
? "你"
: '"' + item.nickname + '"'
}}撤回了一条消息
</span>
<span>{{ item.previewMessage }}</span>
</div>
</el-col>
<el-col :span="6" class="preview-right">

23
frontend/src/components/chat/ChatUserList.vue

@ -122,29 +122,6 @@
{{ group.name }}
</div>
</el-col>
<el-col :span="6">
<div class="previewTime">
{{ group.previewTimeFormat }}
</div>
</el-col>
</el-row>
<el-row>
<div
v-if="group.previewType === 0"
class="previewChat"
>
{{ group.previewMessage }}
</div>
<div
v-if="group.previewType === 1"
class="previewChat"
>
{{
group.userId === id
? "你"
: '"' + group.name + '"'
}}撤回了一条消息
</div>
</el-row>
</el-col>
</el-row>

4
frontend/src/components/chat/chatUserInfo.vue

@ -37,9 +37,9 @@
const store = useChatStore();
const sendMessage = (id:number) => {
const sendMessage = (userId:number) => {
store.currentNavId = 0;
store.updateConversationList(id)
store.changeChatListAndGetChatHistory(userId)
};
</script>

419
frontend/src/stores/chat.ts

@ -3,71 +3,64 @@ import { fetchPost, getSystemConfig } from '@/system/config';
import { notifyError } from "@/util/msg";
import { defineStore } from 'pinia';
import { db } from "./db";
import { useMessageStore } from "./message";
interface ChatMessage {
type: string;
createdAt: number;
content: any;
targetUserId: any;
previewType: 0 | 1; // 消息类型,0表示正常消息,1表示撤回消息
previewMessage: any;
isMe: boolean;
isRead: boolean;
userInfo: {
id: any;
username: any;
avatar: any;
};
}
// 发起群聊对话框显示
const groupChatDialogVisible = ref(false);
// 设置发起群聊对话框状态
const setGroupChatDialogVisible = (visible: boolean) => {
groupChatDialogVisible.value = visible;
};
export const useChatStore = defineStore('chatStore', () => {
// 用户列表
const userList: any = ref([]);
// 定义聊天列表项的接口
interface ChatListItem {
interface ChatMessage {
type: string; // 消息类型,0表示文字消息,1表示图片消息,2表示文件消息
time: number; // 消息发送时间
message: any; // 消息内容
userId: any; // 发送者id
toUserId: any; // 接收者id
// receiver: any; // 消息接收者
// to_groupid: any; // 群组id
userInfo: { // 发送者信息
}
};
// 发起群聊对话框显示
const groupChatInvitedDialogVisible = ref(false);
// 群信息设置抽屉
const groupInfoSettingDrawerVisible = ref(false);
// 设置群聊邀请对话框状态
const setGroupChatInvitedDialogVisible = (visible: boolean) => {
groupChatInvitedDialogVisible.value = visible;
};
// 设置群信息抽屉状态
const setGroupInfoDrawerVisible = (visible: boolean) => {
groupInfoSettingDrawerVisible.value = visible
}
// 定义用户类型
type User = {
id: number;
nickname: string;
ip: string;
isOnline: boolean;
avatar: string;
previewTimeFormat: string;
previewType: 0 | 1; // 消息类型,0表示正常消息,1表示撤回消息
previewMessage: string;
}
nickname: string;
username: string;
updatedAt?: number;
};
// 模拟数据 - 聊天列表
const chatList = ref<ChatListItem[]>([
{
id: 2,
nickname: '朋友2',
avatar: '/logo.png',
previewTimeFormat: "昨天",
previewType: 1,
previewMessage: "测试消息",
},
{
id: 3,
nickname: '朋友2',
avatar: '/logo.png',
previewTimeFormat: "昨天",
previewType: 1,
previewMessage: "测试消息",
},
// 将 userList 的类型设置为 User[]
const userList = ref<User[]>([]);
]);
// 聊天列表
const chatList: any = ref([]);
// 聊天消息记录列表
const chatHistory: any = ref([]);
const targetNickname: any = ref('');
// 模拟数据 - 聊天消息列表
const chatHistory = ref<ChatMessage[]>([]);
// 群组数据
const groupList = ref([
const groupList: any = ref([
{
id: 1,
name: '群组1',
@ -107,8 +100,6 @@ export const useChatStore = defineStore('chatStore', () => {
const targetUserInfo: any = ref({});
const targetUserId = ref();
const search = ref('');
const messageStore = useMessageStore();
const apiUrl = "http://192.168.1.10:8816";
const contextMenu = ref({
visible: false,
@ -127,35 +118,40 @@ export const useChatStore = defineStore('chatStore', () => {
}
userInfo.value = config.userInfo;
getUserList()
initUserList()
initOnlineUserList()
// initUserList()
initChatList()
// initOnlineUserList()
console.log(userList.value);
};
// 初始化用户列表
const initChatList = async () => {
chatList.value = await db.getAll('conversationList');
console.log(chatList.value);
};
const setCurrentNavId = (id: number) => {
currentNavId.value = id;
};
const sendMessage = async () => {
const chatSendUrl = apiUrl + '/chat/send';
const chatSendUrl = config.userInfo.url + '/chat/send';
// 封装成消息历史记录
const messageHistory: ChatMessage = {
type: 'text',
createdAt: Date.now(),
content: message.value,
targetUserId: targetUserId.value,
previewType: 0, // 消息类型,0表示正常消息,1表示撤回消息
previewMessage: message.value,
isMe: true,
isRead: false,
type: 'user',
time: Date.now(),
message: message.value,
userId: userInfo.value.userId,
toUserId: targetUserId.value,
// receiver: targetUserId.value,
userInfo: {
id: config.userInfo.id,
username: config.userInfo.username,
avatar: config.userInfo.avatar,
},
};
// 发送消息
const res = await fetchPost(chatSendUrl, messageHistory);
console.log(res);
if (res.ok) {
// 本地存储一份聊天记录
await db.addOne('chatRecord', messageHistory);
@ -163,8 +159,10 @@ export const useChatStore = defineStore('chatStore', () => {
// 更新聊天历史
chatHistory.value.push(messageHistory);
console.log(chatHistory.value);
// 更新 chatList 和 conversationList
await updateConversationList(targetUserId.value);
// await changeChatListAndGetChatHistory(targetUserId.value, messageHistory);
// 清空输入框
clearMessage();
@ -176,57 +174,97 @@ export const useChatStore = defineStore('chatStore', () => {
notifyError("消息发送失败");
};
const updateConversationList = async (id: number) => {
// 先判断是否已经存在该会话
const res = await db.getRow('conversationList', 'id', id);
// 更新聊天和聊天记录
const changeChatListAndChatHistory = async (data: any) => {
try {
// 从 conversationList 数据库中查找是否存在对应的会话
const conversation = await db.getByField('conversationList', 'userId', data.userId);
if (res) {
// 更新现有会话
// 准备会话更新数据
const updatedConversation = {
...res,
previewMessage: message.value,
previewTimeFormat: formatTime(Date.now()),
previewType: 0,
userId: data.userId,
avatar: data.userInfo.avatar || "logo.png", // 如果没有头像使用默认图片
toUserId: data.toUserId,
messages: data.message,
nickname: data.userInfo.nickname,
time: data.time || Date.now(),
previewMessage: data.message,
previewTimeFormat: formatTime(Date.now()), // 时间格式化函数
createdAt: Date.now()
};
await db.update('conversationList', id, updatedConversation);
// 更新 chatList
const existingConversationIndex = chatList.value.findIndex(conversation => conversation.id === id);
if (existingConversationIndex !== -1) {
chatList.value[existingConversationIndex] = updatedConversation;
} else {
if (conversation.length === 0) {
// 如果会话不存在,则添加到数据库和 chatList
await db.addOne('conversationList', updatedConversation);
chatList.value.push(updatedConversation);
} else {
// 如果会话存在,则更新数据库和 chatList
// 只更新变化的字段,而非全部覆盖,以减少写入数据的量
await db.update('conversationList', conversation[0].id, {
avatar: data.userInfo.avatar || "logo.png",
nickname: data.userInfo.nickname,
previewMessage: data.message,
time: data.time || Date.now(),
previewTimeFormat: formatTime(Date.now())
});
// 更新 chatList 中的对应项
const existingConversationIndex = chatList.value.findIndex(
(conv: any) => conv.userId === data.userId
);
if (existingConversationIndex !== -1) {
chatList.value[existingConversationIndex] = updatedConversation;
} else {
chatList.value.push(updatedConversation);
}
}
} else {
const targetUser = await db.getOne('workbenchusers', id);
const lastMessage: any = messageStore;
} catch (error) {
console.error("更新聊天和聊天记录时出错:", error);
}
};
const targetUserInfo = {
id: targetUser.id,
nickname: targetUser.nickname,
avatar: targetUser.avatar,
};
// 计算时间差
const now = new Date();
const createdAt = new Date(lastMessage.createdAt);
const diffTime = Math.abs(now.getTime() - createdAt.getTime());
console.log(diffTime);
// 根据时间差格式化时间
const previewTimeFormat = formatTime(Date.now());
const newConversation = {
...targetUserInfo,
previewTimeFormat,
previewMessage: lastMessage.content,
previewType: lastMessage.type,
};
const changeChatListAndGetChatHistory = async (userId: number) => {
// 将当前的chatlist存到map中id为key,如果userId能在map中找到就不添加。
// 添加到 conversationList
await db.addOne('conversationList', newConversation);
// 将当前的 chatList 存到 map 中,id 为 key
const chatListMap = new Map<number, any>();
// value可设置为空
chatList.value.forEach((chat: { userId: number; }) => {
chatListMap.set(chat.userId, "");
});
// 检查 userId 是否在 map 中
if (chatListMap.has(userId)) {
// 如果存在获取聊天记录添加到historyList
const data = await db.getByField("chatRecord", "userId", userId)
chatHistory.value = data
return;
} else {
// 如果不在chatlist中表示没有聊天记录。那么去用户表中获取该用户的基本信息
const user = await db.getOne("workbenchusers", userId)
chatList.value.push({
id: user.id,
nickname: user.nickname,
avatar: user.avatar,
previewTimeFormat: formatTime(Date.now()),
previewMessage: "",
})
// 持久化
await db.addOne("conversationList", {
userId: user.id,
username: user.username,
nickname: user.nickname,
avatar: user.avatar,
toUserId: userInfo.value.id,
time: Date.now(),
previewMessage: "",
createdAt: Date.now()
})
// 添加到 chatList
chatList.value.push(newConversation);
}
};
@ -255,6 +293,66 @@ export const useChatStore = defineStore('chatStore', () => {
};
// 创建群聊
const createGroupChat = async (groupName?: string, userIds?: number[]) => {
try {
const data = {
name: groupName,
user_ids: userIds
}
const url = config.userInfo.url + "/chat/group";
const res = await fetchPost(url, JSON.stringify(data));
if (!res.ok) {
return false;
}
console.log(res)
const groupData = await res.json();
console.log(groupData)
} catch (error) {
console.log(error);
}
};
// 处理用户消息
const userChatMessage = async (data: any) => {
// 先判断数据库是否有该用户
// 更新聊天记录表
// 更新会话列表数据库
// 更新chatlist
const isPresence = await db.getByField('workbenchusers', 'id', data.userId)
if (isPresence[0].id !== data.userId) {
return
}
// 添加消息记录
const addMessageHistory = {
type: data.type,
time: data.time,
userId: data.userId,
message: data.message,
toUserId: data.toUserId,
createdAt: Date.now(),
// 用户信息
userInfo: {
id: data.userId,
nickname: data.userInfo.nickname || "未知用户",
avatar: data.userInfo.avatar || "logo.png", // 使用默认头像。
email: data.userInfo.email,
phone: data.userInfo.phone,
remark: data.userInfo.remark,
role_id: data.userInfo.role_id,
}
}
await db.addOne('chatRecord', addMessageHistory)
// 更新 chatList 和 conversationList表
changeChatListAndChatHistory(data)
};
const setScrollToBottom = async () => {
// await nextTick(); // 确保 DOM 已经更新完毕
@ -274,11 +372,8 @@ export const useChatStore = defineStore('chatStore', () => {
};
const handleUserData = async (data: any[]) => {
;
// 创建一个用户数组,将所有在线的用户提取出来
const users: any[] = [];
// 遍历每个数据项
data.forEach((item: any) => {
if (item.id && item.login_ip) {
@ -292,7 +387,6 @@ export const useChatStore = defineStore('chatStore', () => {
}
});
console.log(users);
// 将提取到的用户数据传递给 setUserList
if (users.length > 0) {
@ -301,6 +395,7 @@ export const useChatStore = defineStore('chatStore', () => {
};
const setUserList = async (data: any[]) => {
if (data.length < 1) {
return;
@ -308,8 +403,8 @@ export const useChatStore = defineStore('chatStore', () => {
// 从当前用户列表中获取已有用户的 IP 和完整用户映射
const existingIps = new Set(userList.value.map((d: any) => d.ip));
const userMap = new Map(
userList.value.map((user: any) => [user.ip, user])
const userMap = new Map<string, User>(
userList.value.map((user: User) => [user.ip, user])
);
const updates: any[] = [];
@ -324,8 +419,9 @@ export const useChatStore = defineStore('chatStore', () => {
key: existingUser.id,
changes: {
isOnline: true,
avatar: d.avatar,
nickname: d.nickname,
username: d.usernmae,
username: d.username,
updatedAt: Date.now()
}
});
@ -335,6 +431,7 @@ export const useChatStore = defineStore('chatStore', () => {
id: d.id,
ip: d.ip,
isOnline: true,
avatar: d.avatar,
nickname: d.nickname,
username: d.usernmae,
createdAt: Date.now(),
@ -343,8 +440,6 @@ export const useChatStore = defineStore('chatStore', () => {
}
});
console.log(updates);
console.log(newEntries);
// 批量更新和添加用户数据
if (updates.length > 0) {
@ -363,21 +458,20 @@ export const useChatStore = defineStore('chatStore', () => {
try {
// 从数据库中获取所有用户信息
const list = await db.getAll("workbenchusers");
console.log(list);
// 创建一个 Map,用于存储每个用户的唯一 ID 地址
let uniqueIdMap = new Map<string, any>();
// 遍历用户列表,将每个用户添加到 Map 中(基于 ID 去重)
list.forEach((item: any) => {
uniqueIdMap.set(item.id, item); // 使用 ID 作为键,用户对象作为值
uniqueIdMap.set(item.userId, item); // 使用 ID 作为键,用户对象作为值
});
// 将 Map 的值转换为数组(去重后的用户列表)
const uniqueIdList = Array.from(uniqueIdMap.values());
// 按照 updatedAt 时间进行升序排序
uniqueIdList.sort((a: any, b: any) => a.updatedAt - b.updatedAt);
uniqueIdList.sort((a: any, b: any) => a.time - b.time);
// 更新用户列表
userList.value = uniqueIdList;
} catch (error) {
@ -387,25 +481,29 @@ export const useChatStore = defineStore('chatStore', () => {
// 初始化统一用户列表状态
const initUserList = async () => {
// 检查用户列表是否为空
if (userList.value.length > 0) {
// 收集需要更新的用户数据
const updates = userList.value
.filter((d: any) => d.isOnline) // 过滤出在线的用户
.map((d: any) => ({
key: d.id,
changes: {
isOnline: false
}
}));
// 批量更新用户状态
if (updates.length > 0) {
await db.table('workbenchusers').bulkUpdate(updates);
console.log("初始化用户列表状态");
console.log(userList.value);
if (!userList.value.length) return;
// 获取需要更新的用户数据(只选取在线的用户并设为离线状态)
const updates = userList.value.reduce((acc: any[], user: any) => {
console.log(user);
if (user.isOnline) {
console.log(user);
acc.push({
key: user.id,
changes: { isOnline: false }
});
}
return acc;
}, []);
// 如果有需要更新的用户,批量更新数据库状态
if (updates.length) {
await db.table('workbenchusers').bulkUpdate(updates);
}
};
const initOnlineUserList = async () => {
const msgAll = await db.getAll('workbenchusers');
@ -438,20 +536,37 @@ export const useChatStore = defineStore('chatStore', () => {
};
const changeChatList = async (id: number) => {
const changeChatList = async (userId: number) => {
// 设置 targetUserId
targetUserId.value = id;
targetUserId.value = userId;
// 获取当前用户和目标用户的聊天记录
const messagesList = await db.getByField('chatRecord', 'targetUserId', id);
const messagesList = await db.getByField('chatRecord', 'userId', userId);
// // 表示与该用户没有聊天记录,但是也需要初始化一些信息
// if (messagesList.length == 0) {
// const userData:any = await db.getOne("workbenchusers", userId)
// console.log(userData)
// const data: any = {
// userId: userData.id,
// userInfo: {
// id: userData.id,
// avatar: userData.avatar,
// nickname: userData.nickname,
// }
// }
// chatHistory.value.push(data)
// } else {
chatHistory.value = messagesList;
// }
// 设置目标用户的信息
setTargetUserInfo(id);
await setTargetUserInfo(userId);
};
const setTargetUserInfo = async (id: number) => {
targetUserInfo.value = await db.getOne('workbenchusers', id);
};
@ -487,16 +602,22 @@ export const useChatStore = defineStore('chatStore', () => {
message,
contextMenu,
activeNames,
groupChatDialogVisible,
groupChatInvitedDialogVisible,
groupInfoSettingDrawerVisible,
targetNickname,
initChat,
showContextMenu,
setCurrentNavId,
sendMessage,
changeChatList,
handleContextMenu,
updateConversationList,
changeChatListAndGetChatHistory,
handleUserData,
initUserList,
setGroupChatDialogVisible
setGroupChatInvitedDialogVisible,
setGroupInfoDrawerVisible,
createGroupChat,
userChatMessage,
initOnlineUserList
};
});

23
frontend/src/stores/db.ts

@ -1,22 +1,31 @@
import Dexie from 'dexie';
export type ChatTable = 'chatuser' | 'chatmsg' | 'chatmessage' | 'groupmessage' | 'chatRecord' | 'workbenchusers' | 'conversationList';
export type ChatTable = 'chatuser' | 'chatmsg' | 'chatmessage' | 'groupmessage' | 'chatRecord' | 'workbenchusers' | 'conversationList' | 'group';
export const dbInit: any = new Dexie('GodoOSDatabase');
dbInit.version(1).stores({
// 用户列表
workbenchusers: '++id,ip,userName,avatar,mobile,nickName,isOnline,updatedAt,createdAt',
// 聊天记录
chatRecord: '++id,userId,targetUserId,senderInfo,previewType,previewMessage,isMe,isRead,content,type,readAt,createdAt',
chatRecord: '++id,toUserId,messages,time,createdAt,userInfo',
// 会话列表
conversationList: '++id,userId,targetUserId,targetIp,senderInfo,previewMessage,previewType,isMe,isRead,content,type,createdAt',
conversationList: '++id,avatar,username,nickname,userId,toUserId,previewMessage,messages,time,createdAt',
chatuser: '++id,ip,hostname,userName,avatar,mobile,nickName,isOnline,updatedAt,createdAt',
// chatmsg: '++id,targetUserId,targetIp,senderInfo,reciperInfo,previewMessage,previewType,content,type,status,isRead,isMe,readAt,createdAt',
chatmessage: '++id,userId,targetUserId,senderInfo,isMe,isRead,content,type,readAt,createdAt',
// chatmsg: '++id,toUserId,targetIp,senderInfo,reciperInfo,previewMessage,content,type,status,isRead,isMe,readAt,createdAt',
chatmessage: '++id,userId,toUserId,senderInfo,isMe,isRead,content,type,readAt,createdAt',
groupmessage: '++id,userId,groupId,senderInfo,isMe,isRead,content,type,readAt,createdAt',
}).upgrade((tx: { chatRecord: { addIndex: (arg0: string, arg1: (obj: { targetUserId: any; }) => any) => void; }; }) => {
// 群组表
group: '++id,name,description,owner_id,avatar_url,created_at,updated_at,member_count,max_members,is_public,join_policy,group_type,status'
}).upgrade((tx: {
conversationList: any;
group: any;
chatRecord: { addIndex: (arg0: string, arg1: (obj: { toUserId: any; }) => any) => void; };
}) => {
// 手动添加索引
tx.chatRecord.addIndex('targetUserId', (obj: { targetUserId: any; }) => obj.targetUserId);
tx.conversationList.addIndex('userId', (obj: { userId: any; }) => obj.userId);
tx.chatRecord.addIndex('toUserId', (obj: { toUserId: any; }) => obj.toUserId);
tx.group.addIndex('owner_id', (obj: { owner_id: any }) => obj.owner_id); // 添加索引: 群主 ID
});
export const db = {

18
frontend/src/stores/upgrade.ts

@ -67,6 +67,20 @@ export const useUpgradeStore = defineStore('upgradeStore', () => {
};
}
// 获取用户聊天消息
function userChatMessage() {
const url = getUrl('/chat/message', false)
const source = new EventSource(url);
source.onmessage = function (event) {
const data = JSON.parse(event.data);
handleMessage(data);
};
source.onerror = function (event) {
console.error('EventSource error:', event);
};
}
async function handleMessage(message: any) {
switch (message.type) {
@ -79,6 +93,9 @@ export const useUpgradeStore = defineStore('upgradeStore', () => {
case 'online':
chatChatStore.handleUserData(message.data)
break;
case 'user':
chatChatStore.userChatMessage
break
default:
console.warn('Unknown message type:', message.type);
}
@ -180,6 +197,7 @@ export const useUpgradeStore = defineStore('upgradeStore', () => {
checkUpdate,
systemMessage,
onlineMessage,
userChatMessage,
update
}
})

5
frontend/src/system/index.ts

@ -14,7 +14,6 @@ import {
} from './type/type';
import { BrowserWindow, BrowserWindowOption } from './window/BrowserWindow';
import { useMessageStore } from '@/stores/message';
import { useUpgradeStore } from '@/stores/upgrade';
import { RestartApp } from '@/util/goutil';
import { notifyError } from '@/util/msg';
@ -435,7 +434,7 @@ export class System {
const fileContent = await this.fs.readShareFile(path)
// console.log('阅读:', fileContent);
if (fileContent !== false) {
const fileName = extname(arr[arr.length-1] || '') || 'link'
const fileName = extname(arr[arr.length - 1] || '') || 'link'
this._flieOpenerMap
.get(fileName)
?.func.call(this, path, fileContent || '');
@ -461,7 +460,7 @@ export class System {
?.func.call(this, path, fileContent || '');
}
}
}
// 插件系统
use(func: OsPlugin): void {

Loading…
Cancel
Save