Browse Source

add:优化聊天功能整体UI。新增删除聊天记录功能

master
qiutianhong 7 months ago
parent
commit
98441c800f
  1. 5
      frontend/components.d.ts
  2. 1
      frontend/package.json
  3. 950
      frontend/pnpm-lock.yaml
  4. BIN
      frontend/src/assets/icons/group.png
  5. 0
      frontend/src/assets/icons/group_avatar.svg
  6. 11
      frontend/src/components/chat/Chat.vue
  7. 164
      frontend/src/components/chat/ChatBox.vue
  8. 230
      frontend/src/components/chat/ChatMenu.vue
  9. 102
      frontend/src/components/chat/ChatMessage.vue
  10. 39
      frontend/src/components/chat/ChatMsgList.vue
  11. 25
      frontend/src/components/chat/ChatUserList.vue
  12. 66
      frontend/src/components/chat/chatGroupMember.vue
  13. 255
      frontend/src/components/chat/chatUserInfo.vue
  14. 4
      frontend/src/components/window/WindowTemplate.vue
  15. 21
      frontend/src/main.ts
  16. 73
      frontend/src/stores/chat.ts
  17. 5
      frontend/src/stores/db.ts
  18. 2
      frontend/src/system/member.ts

5
frontend/components.d.ts

@ -22,6 +22,7 @@ declare module 'vue' {
ChatDomain: typeof import('./src/components/localchat/ChatDomain.vue')['default'] ChatDomain: typeof import('./src/components/localchat/ChatDomain.vue')['default']
ChatEditor: typeof import('./src/components/localchat/ChatEditor.vue')['default'] ChatEditor: typeof import('./src/components/localchat/ChatEditor.vue')['default']
ChatFoot: typeof import('./src/components/localchat/ChatFoot.vue')['default'] ChatFoot: typeof import('./src/components/localchat/ChatFoot.vue')['default']
ChatGroupMember: typeof import('./src/components/chat/chatGroupMember.vue')['default']
ChatMenu: typeof import('./src/components/chat/ChatMenu.vue')['default'] ChatMenu: typeof import('./src/components/chat/ChatMenu.vue')['default']
ChatMessage: typeof import('./src/components/chat/ChatMessage.vue')['default'] ChatMessage: typeof import('./src/components/chat/ChatMessage.vue')['default']
ChatMsgList: typeof import('./src/components/chat/ChatMsgList.vue')['default'] ChatMsgList: typeof import('./src/components/chat/ChatMsgList.vue')['default']
@ -50,6 +51,7 @@ declare module 'vue' {
EditType: typeof import('./src/components/builtin/EditType.vue')['default'] EditType: typeof import('./src/components/builtin/EditType.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside'] ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElAvimg: typeof import('element-plus/es')['ElAvimg']
ElBadge: typeof import('element-plus/es')['ElBadge'] ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
@ -71,6 +73,7 @@ declare module 'vue' {
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage'] ElImage: typeof import('element-plus/es')['ElImage']
ElImg: typeof import('element-plus/es')['ElImg']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
@ -80,6 +83,7 @@ declare module 'vue' {
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElS: typeof import('element-plus/es')['ElS']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
@ -147,6 +151,7 @@ declare module 'vue' {
StartOption: typeof import('./src/components/taskbar/StartOption.vue')['default'] StartOption: typeof import('./src/components/taskbar/StartOption.vue')['default']
StateIcon: typeof import('./src/components/taskbar/StateIcon.vue')['default'] StateIcon: typeof import('./src/components/taskbar/StateIcon.vue')['default']
Store: typeof import('./src/components/store/Store.vue')['default'] Store: typeof import('./src/components/store/Store.vue')['default']
Svg: typeof import('./src/components/svg/index.vue')['default']
Taskbar: typeof import('./src/components/taskbar/Taskbar.vue')['default'] Taskbar: typeof import('./src/components/taskbar/Taskbar.vue')['default']
Upgrade: typeof import('./src/components/desktop/Upgrade.vue')['default'] Upgrade: typeof import('./src/components/desktop/Upgrade.vue')['default']
UpPopover: typeof import('./src/components/computer/UpPopover.vue')['default'] UpPopover: typeof import('./src/components/computer/UpPopover.vue')['default']

1
frontend/package.json

@ -21,6 +21,7 @@
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persist": "^1.0.0", "pinia-plugin-persist": "^1.0.0",
"vditor": "^3.10.4", "vditor": "^3.10.4",
"vite-plugin-svg-icons": "^2.0.1",
"vue": "^3.4.31", "vue": "^3.4.31",
"vue-i18n": "^9.13.1", "vue-i18n": "^9.13.1",
"vue-router": "^4.4.0", "vue-router": "^4.4.0",

950
frontend/pnpm-lock.yaml

File diff suppressed because it is too large

BIN
frontend/src/assets/icons/group.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

0
frontend/src/assets/icon/group_avatar.svg → frontend/src/assets/icons/group_avatar.svg

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

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

@ -2,7 +2,7 @@
import { useChatStore } from "@/stores/chat"; import { useChatStore } from "@/stores/chat";
import { getWorkflowUrl } from "@/system/config"; import { getWorkflowUrl } from "@/system/config";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
const store = useChatStore(); const store = useChatStore()
const workUrl = getWorkflowUrl(); const workUrl = getWorkflowUrl();
onMounted(() => { onMounted(() => {
store.initChat(); store.initChat();
@ -223,8 +223,9 @@
.side { .side {
flex: 1; flex: 1;
/* 占据剩余宽度 */ /* 占据剩余宽度 */
max-width: 200px; max-width: 200px;
min-width: 200px; min-width: 200px;
min-height: 650px;
max-height: max-content; max-height: max-content;
border-right: 1px solid #e8e8e8; border-right: 1px solid #e8e8e8;
overflow-y: hidden; overflow-y: hidden;
@ -282,8 +283,10 @@
.chat-box { .chat-box {
flex: 3; flex: 3;
width: 100%;
height: 100%;
max-height: max-content; max-height: max-content;
background-color: #f5f5f5; background-color: #ffffff;
} }
.chat-setting { .chat-setting {

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

@ -99,79 +99,11 @@
" "
>退出群聊</el-dropdown-item >退出群聊</el-dropdown-item
> >
<el-dropdown-item>清空记录</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<el-icon
style="
cursor: pointer;
color: black;
font-size: 15px;
"
@click="openDrawer()"
><Tools
/></el-icon>
</div> </div>
<!-- 抽屉 -->
<el-drawer
v-model="store.drawerVisible"
direction="ltr"
title="群成员"
:with-header="true"
>
<div
style="
display: flex;
height: 100%;
padding-top: 40px;
flex-direction: column;
justify-content: space-between;
"
>
<div
class="group-member-container"
style="
display: flex;
flex-wrap: wrap;
max-width: 248px; /* 每行4个用户,每个60px + 2px间距 */
"
>
<div
class="group-member"
v-for="member in store.groupMembers"
:key="member.id"
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50px;
height: 70px;
"
>
<el-avatar
size="40"
src=""
/>
<div
style="
max-width: 46px;
height: 20px;
font-size: 10px;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
line-height: 20px;
"
>
{{ member.nickname }}
</div>
</div>
</div>
</div>
</el-drawer>
</div> </div>
<div <div
@ -190,11 +122,13 @@
color: black; color: black;
font-size: 15px; font-size: 15px;
" "
><Tools ><More
/></el-icon> /></el-icon>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item>删除好友</el-dropdown-item> <el-dropdown-item @click="store.clearChatRecord()"
>清空记录</el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -204,28 +138,29 @@
<!--聊天主体区--> <!--聊天主体区-->
<el-main class="msg-main"> <el-main class="msg-main">
<el-scrollbar ref="scrollbarRef"> <!-- 聊天消息滚动区域 -->
<div ref="innerRef"> <div class="msg-container">
<ChatMessage /> <el-scrollbar ref="scrollbarRef">
</div> <div ref="innerRef">
</el-scrollbar> <ChatMessage />
<!-- <ChatGroupMember /> -->
</div>
</el-scrollbar>
</div>
<!-- 群成员滚动区域 -->
<div class="member-container">
<el-scrollbar ref="memberScrollbarRef">
<div ref="memberInnerRef">
<ChatGroupMember />
</div>
</el-scrollbar>
</div>
</el-main> </el-main>
<!--聊天输入区和发送按钮等--> <!--聊天输入区和发送按钮等-->
<el-footer <el-footer class="msg-footer">
class="msg-footer"
style="
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
"
>
<!-- 输入主体 --> <!-- 输入主体 -->
<div <div class="input-main">
class="input-main"
style="width: 100%; height: 40px; background-color: #ffffff"
>
<el-input <el-input
size="large" size="large"
style="width: 100%; height: 100%" style="width: 100%; height: 100%"
@ -248,12 +183,6 @@
> >
<Link /> <Link />
</el-icon> </el-icon>
<el-icon
:size="20"
class="input-option-icon"
>
<Delete />
</el-icon>
<el-icon <el-icon
@click="store.sendMessage('text')" @click="store.sendMessage('text')"
:size="22" :size="22"
@ -300,6 +229,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useChatStore } from "@/stores/chat"; import { useChatStore } from "@/stores/chat";
import { useChooseStore } from "@/stores/choose"; import { useChooseStore } from "@/stores/choose";
const store: any = useChatStore(); const store: any = useChatStore();
const choose = useChooseStore(); const choose = useChooseStore();
const imgExt = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"]; const imgExt = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"];
@ -364,7 +294,7 @@
function openDrawer() { function openDrawer() {
store.drawerVisible = true; store.drawerVisible = true;
store.getGroupMemberList(store.targetGroupInfo.group_id); // store.getGroupMemberList(store.targetGroupInfo.group_id);
} }
// store.drawerVisible // store.drawerVisible
@ -405,6 +335,35 @@
</script> </script>
<style scoped> <style scoped>
.msg-main {
width: 100%;
/* 占据整个宽度 */
height: 75%;
padding: 0;
border-top: 1px solid #edebeb;
display: flex;
justify-content: space-between;
}
/* 聊天消息滚动区域 */
.msg-container {
width: calc(100% - 150px);
height: 100%;
}
/* 群成员滚动区域 */
.member-container {
border-left: 1px solid #edebeb;
width: 140px;
height: 100%;
}
.input-main {
width: 100%;
height: 40px;
background-color: #ffffff;
}
.invite-group-button { .invite-group-button {
background-color: #0078d4; background-color: #0078d4;
color: #fff; color: #fff;
@ -549,20 +508,15 @@
font-size: 20px; font-size: 20px;
} }
.msg-main {
background-color: re;
/* width: 100%; */
/* 占据整个宽度 */
height: 75%;
padding: 0;
border-top: 1px solid #edebeb;
}
.msg-footer { .msg-footer {
width: 100%; width: 100%;
/* 占据整个宽度 */ /* 占据整个宽度 */
height: calc(100% - 75% - 49px); height: calc(100% - 75% - 49px);
border: none; border: none;
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
} }
.input-option { .input-option {

230
frontend/src/components/chat/ChatMenu.vue

@ -1,115 +1,137 @@
<script setup> <script setup>
import { useChatStore } from "@/stores/chat"; import { useChatStore } from "@/stores/chat";
import { Setting as SettingIcon } from "@element-plus/icons-vue"; import { Setting as SettingIcon } from "@element-plus/icons-vue";
const store = useChatStore(); const store = useChatStore();
</script> </script>
<template> <template>
<el-row> <!-- 头像 -->
<el-avatar <el-row>
shape="square" <el-avatar
:size="40" shape="square"
class="userAvatar" :size="40"
:src="store.userInfo.avatar" class="userAvatar"
/> :src="store.userInfo.avatar"
</el-row> />
<el-row @click="store.setCurrentNavId(0)"> </el-row>
<div class="menu-icon-box">
<el-icon <!-- 菜单按钮 -->
v-if="store.currentNavId === 0" <el-row @click="store.setCurrentNavId(0)">
class="menu-icon-on" <div class="menu-icon-box">
> <el-icon
<ChatLineRound /> v-if="store.currentNavId === 0"
</el-icon> class="menu-icon-on"
<el-icon >
v-else <Comment />
class="menu-icon" </el-icon>
> <el-icon
<ChatRound /> v-else
</el-icon> class="menu-icon"
</div> >
</el-row> <Comment />
<el-row @click="store.setCurrentNavId(1)"> </el-icon>
<div class="menu-icon-box"> </div>
<el-icon </el-row>
v-if="store.currentNavId === 1"
class="menu-icon-on" <el-row @click="store.setCurrentNavId(1)">
> <div class="menu-icon-box">
<UserFilled /> <el-icon
</el-icon> v-if="store.currentNavId === 1"
<el-icon class="menu-icon-on"
v-else >
class="menu-icon" <UserFilled />
> </el-icon>
<User /> <el-icon
</el-icon> v-else
</div> class="menu-icon"
</el-row> >
<el-row @click="store.setCurrentNavId(2)"> <User />
<div class="menu-icon-box"> </el-icon>
<el-icon </div>
v-if="store.currentNavId === 2" </el-row>
class="menu-icon-on"
> <el-row @click="store.setCurrentNavId(2)">
<Platform /> <div class="menu-icon-box">
</el-icon> <el-icon
<el-icon v-if="store.currentNavId === 2"
v-else class="menu-icon-on"
class="menu-icon" >
> <Platform />
<Monitor /> </el-icon>
</el-icon> <el-icon
</div> v-else
</el-row> class="menu-icon"
<el-row @click="store.setCurrentNavId(5)"> >
<div class="menu-icon-box"> <Monitor />
<el-icon </el-icon>
v-if="store.currentNavId === 5" </div>
class="menu-icon-on" </el-row>
>
<SettingIcon /> <!-- 设置按钮固定在底部 -->
</el-icon> <el-row
<el-icon class="settings-icon-row"
v-else @click="store.setCurrentNavId(5)"
class="menu-icon" >
> <div class="menu-icon-box">
<SettingIcon /> <el-icon
</el-icon> v-if="store.currentNavId === 5"
</div> class="menu-icon-on"
</el-row> >
<Tools />
</el-icon>
<el-icon
v-else
class="menu-icon"
>
<Tools />
</el-icon>
</div>
</el-row>
</template> </template>
<style scoped> <style scoped>
.userAvatar { .chat-user-info {
margin: 6px auto; position: relative;
-webkit-app-region: no-drag; height: 100%;
} }
.userAvatar {
margin: 6px auto;
-webkit-app-region: no-drag;
}
.menu-icon-box {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin: 10px auto;
transition: all 0.5s;
}
.menu-icon-box { .menu-icon {
display: flex; margin: 0 auto;
align-items: center; font-size: 25px;
justify-content: center; cursor: pointer;
width: 40px; color: #86909C;
height: 40px; }
margin: 10px auto;
/* border-radius: 15%; */
transition: all 0.5s;
}
/* .menu-icon-box:hover {
background-color: #d9d9d9;
} */
.menu-icon { .menu-icon-on {
margin: 0px auto; margin: 0 auto;
font-size: 25px; font-size: 25px;
cursor: pointer; color: #0d42d2;
} cursor: pointer;
}
.menu-icon-on { /* 设置按钮独立在底部 */
margin: 0px auto; .settings-icon-row {
font-size: 25px; position: absolute;
color: #2A6BF2; bottom: 20px;
cursor: pointer; left: 0;
} right: 0;
</style> width: 55px;
height: 55px;
}
</style>

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

@ -82,6 +82,7 @@
<el-col :span="2"> <el-col :span="2">
<div class="chat-avatar"> <div class="chat-avatar">
<el-avatar <el-avatar
@click="showUserInfo(item.userId)"
shape="square" shape="square"
style="margin: 0; float: left" style="margin: 0; float: left"
:size="32" :size="32"
@ -100,6 +101,7 @@
<el-col :span="2"> <el-col :span="2">
<div class="chat-avatar"> <div class="chat-avatar">
<el-avatar <el-avatar
@click="showUserInfo(item.userId)"
shape="square" shape="square"
style="margin: 0; float: right" style="margin: 0; float: right"
:size="32" :size="32"
@ -188,16 +190,114 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 用户信息弹窗 -->
<el-dialog
style="border-radius: 10px"
v-model="showUserInfoDialog"
title="用户信息"
width="300"
>
<!-- 用户信息 -->
<div class="user-content">
<div class="user-details">
<h3>{{ targetUserInfo.displayName }}</h3>
<p>工号{{ targetUserInfo.jobNumber }}</p>
<p>岗位{{ targetUserInfo.desc }}</p>
<p>邮箱{{ targetUserInfo.email }}</p>
<p>电话{{ targetUserInfo.phone }}</p>
<p>入职日期{{ targetUserInfo.hiredDate }}</p>
</div>
</div>
<!-- 发送消息按钮 -->
<el-button
@click="sendMessage"
class="send-button-container"
>发送消息</el-button
>
</el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useChatStore } from "@/stores/chat"; import { useChatStore } from "@/stores/chat";
import { System } from "@/system"; import { System } from "@/system";
import { notifyInfo } from "@/util/msg";
const store = useChatStore(); const store = useChatStore();
const sys: any = inject<System>("system"); const sys: any = inject<System>("system");
const showUserInfoDialog = ref(false);
var targetUserInfo: any = {};
const currUserId = ref();
const showUserInfo = (chatId: string) => {
console.log(chatId);
currUserId.value = chatId;
//
const userInfo = store.groupMembers.find(
(member: any) => member.id === chatId
);
//
targetUserInfo = {
displayName: userInfo.nickname,
jobNumber: 12345678,
desc: "测试岗位",
email: "12345678@qq.com",
phone: "12345678910",
hiredDate: "2024-01-01",
};
showUserInfoDialog.value = true;
};
const sendMessage = () => {
if (currUserId.value == store.userInfo.id) {
notifyInfo("不能给自己发送消息");
return;
}
store.getSessionInfo(currUserId.value, "user");
showUserInfoDialog.value = false;
};
</script> </script>
<style scoped> <style scoped>
.send-button-container {
width: 230px;
display: flex;
justify-content: center;
margin-top: 20px;
background-color: #0d42d2;
color: #fff;
}
.send-button-container:hover {
background-color: #4080ff;
color: #fff;
}
.user-content {
display: flex;
align-items: center;
justify-content: center;
}
.user-details {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.user-details h3 {
margin: 0;
font-size: 1.2rem;
}
.user-details p {
margin: 5px 0;
color: #666;
}
.system-message { .system-message {
background-color: #e8e8e8; /* 设置背景颜色 */ background-color: #e8e8e8; /* 设置背景颜色 */
color: #333; /* 设置文字颜色 */ color: #333; /* 设置文字颜色 */

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

@ -14,7 +14,7 @@
@click="store.getSessionInfo(item.chatId, item.type)" @click="store.getSessionInfo(item.chatId, item.type)"
:style="{ :style="{
backgroundColor: backgroundColor:
item.chatId == store.targetChatId ? '#F5F5F5' : '', item.chatId == store.targetChatId ? '#E8F3FF' : '',
}" }"
> >
<el-row> <el-row>
@ -27,18 +27,27 @@
:src="item.avatar" :src="item.avatar"
></el-avatar> ></el-avatar>
<!-- <el-avatar <el-avatar
v-else="item.type == 'group'" v-else-if="item.type == 'group'"
shape="square" shape="square"
:size="40" :size="40"
class="avatar" class="avatar"
:src="item.avatar" style="
></el-avatar background-color: #165DFF;
> --> display: flex;
<!-- 引入icon --> align-items: center;
<el-icon :size="40"> justify-content: center;
"
</el-icon> >
<div
style="
width: 25px;
height: 25px;
background-image: url('./src/assets/icons/group.png');
background-size: cover;
"
></div>
</el-avatar>
</el-col> </el-col>
<!-- 在线状态 --> <!-- 在线状态 -->
@ -143,10 +152,11 @@
margin-bottom: 5px; margin-bottom: 5px;
overflow: hidden; overflow: hidden;
margin-top: 5px; margin-top: 5px;
background-color: #FFF;
} }
.list-item:hover { .list-item:hover {
background-color: #f5f5f5; background-color: #E8F3FF;
} }
.avatar { .avatar {
@ -179,6 +189,7 @@
justify-content: space-between; justify-content: space-between;
} }
.previewName { .previewName {
font-weight: 400;
font-size: 14px; font-size: 14px;
color: #000000; color: #000000;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
@ -190,7 +201,7 @@
.previewTime { .previewTime {
font-size: 12px; font-size: 12px;
color: #999999; color: #86909C;
} }
.preview-left { .preview-left {
@ -205,9 +216,9 @@
width: 100px; width: 100px;
height: 20px; height: 20px;
margin-left: 10px; margin-left: 10px;
font-size: 10px; font-size: 12px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
color: #999999; color: #86909C;
overflow: hidden; /* 隐藏超出部分 */ overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 显示为省略号 */ text-overflow: ellipsis; /* 显示为省略号 */
white-space: nowrap; /* 不换行 */ white-space: nowrap; /* 不换行 */

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

@ -208,10 +208,11 @@
border-radius: 4px; border-radius: 4px;
transition: all 0.5s; transition: all 0.5s;
margin-bottom: 5px; margin-bottom: 5px;
background-color: #fff;
} }
.list-item:hover { .list-item:hover {
background-color: #f5f5f5; background-color: #e8f3ff;
} }
.avatar { .avatar {
@ -219,10 +220,12 @@
} }
.previewName { .previewName {
font-weight: 400;
margin-left: 10px; margin-left: 10px;
font-size: 14px; font-size: 14px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
color: #000000;
overflow: hidden; /* 隐藏超出部分 */ overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 显示为省略号 */ text-overflow: ellipsis; /* 显示为省略号 */
white-space: nowrap; /* 不换行 */ white-space: nowrap; /* 不换行 */
@ -252,26 +255,6 @@
margin-left: 5px; /* 与用户名之间的间距 */ margin-left: 5px; /* 与用户名之间的间距 */
} }
.previewChat {
margin-left: 10px;
font-size: 12px; /* 调整字体大小 */
font-family: Arial, sans-serif;
color: #999999;
overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 显示为省略号 */
white-space: nowrap; /* 不换行 */
max-width: 100%; /* 最大宽度 */
}
.previewTime {
float: right;
margin-top: 10px;
margin-right: 10px;
font-size: 12px;
font-family: Arial, sans-serif;
color: #999999;
}
.no-data { .no-data {
text-align: center; text-align: center;
color: #999999; color: #999999;

66
frontend/src/components/chat/chatGroupMember.vue

@ -0,0 +1,66 @@
<template>
<div class="group-member-container">
<!-- 群成员列表 -->
<div
class="member-list"
v-for="item in groupMembers"
:key="item.userId"
>
<!-- 头像和昵称 -->
<div class="member-item">
<div class="avatar-container">
<el-avatar
style="width: 100%; height: 100%;"
:src="item.avatar"
/>
</div>
<span>{{ item.nickname }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useChatStore } from "@/stores/chat";
const store = useChatStore();
// 使
// 使40
const groupMembers = ref(
Array.from({ length: 40 }, (v, i) => ({
userId: `${i + 1}`,
avatar: `path/to/avatar${i + 1}.jpg`,
nickname: `用户1111111111111111${i + 1}`,
}))
);
</script>
<style scoped>
.group-member-container {
margin: 10px 0;
width: 100%;
height: 100%;
}
.member-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.avatar-container {
width: 25px;
height: 25px;
margin-right: 5px;
}
.member-item span {
width: 90px;
margin-left: 10px;
font-size: 12px;
color: #86909C;
overflow: hidden; /* 防止内容溢出 */
white-space: nowrap; /* 保持文本在一行 */
text-overflow: ellipsis; /* 文本溢出显示省略号 */
max-width: 150px; /* 最大宽度,根据需要调整 */
}
</style>

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

@ -1,173 +1,172 @@
<template> <template>
<div <div
v-if="store.targetChatId == null" v-if="store.targetChatId"
style="height: 280px; width: 100%" class="chat-user-info"
class="no-message-container"
> >
<el-icon <!-- 用户信息区域 -->
:size="180" <div
color="#0078d7" v-if="
store.targetUserInfo &&
Object.keys(store.targetUserInfo).length > 0
"
class="user-content"
> >
<ChatDotSquare /> <div class="user-details">
</el-icon> <h2>{{ store.targetUserInfo.displayName }}</h2>
<p class="welcome-text">欢迎使用GodoOS</p> <p>工号{{ targetUserInfo.jobNumber }}</p>
</div> <p>岗位{{ targetUserInfo.desc }}</p>
<p>邮箱{{ targetUserInfo.email }}</p>
<div <p>电话{{ targetUserInfo.phone }}</p>
v-else-if=" <p>入职日期{{ targetUserInfo.hiredDate }}</p>
store.targetUserInfo && Object.keys(store.targetUserInfo).length > 0 </div>
" <div class="avatar">
class="user-info-container" <el-avatar
> style="width: 80px; height: 80px"
<el-avatar :src="targetUserInfo.avatar"
class="user-avatar" alt="avatar"
:size="80" />
:src="store.targetUserInfo.avatarUrl || ''" </div>
/>
<h2 class="user-name">{{ store.targetUserInfo.displayName }}</h2>
<div class="user-details">
<p><strong>邮箱:</strong> {{ store.targetUserInfo.email }}</p>
<p><strong>电话:</strong> {{ store.targetUserInfo.phone }}</p>
<p><strong>描述:</strong> {{ store.targetUserInfo.desc }}</p>
<p><strong>工号:</strong> {{ store.targetUserInfo.jobNumber }}</p>
<p>
<strong>工作地点:</strong> {{ store.targetUserInfo.workPlace }}
</p>
<p>
<strong>入职日期:</strong> {{ store.targetUserInfo.hiredDate }}
</p>
</div> </div>
<el-button
class="send-message-btn"
@click="sendMessage(store.targetChatId, 'user')"
>
发送消息
</el-button>
</div>
<!-- 群聊信息 -->
<div
v-else
class="group-info-container"
>
<el-avatar
class="group-avatar"
:size="80"
><span style="font-size: 40px;"></span></el-avatar>
<h2 class="group-name">{{ store.targetGroupInfo.displayName }}</h2>
<el-button <!-- 群信息区域 -->
class="send-message-btn" <div
@click="sendMessage(store.targetChatId, 'group')" v-else-if="
store.targetGroupInfo &&
Object.keys(store.targetGroupInfo).length > 0
"
class="group-content"
> >
发送消息 <div class="group-details">
</el-button> <h2>{{ store.targetGroupInfo.displayName }}</h2>
<p>群ID{{ store.targetGroupInfo.chatId }}</p>
<p>群人数{{ targetGroupInfo.memberCount }}</p>
<p>创建时间{{ targetGroupInfo.createdAt }}</p>
</div>
<div class="group-avatar">
<div class="avatar-container">
<el-avatar
style="width: 80px; height: 80px"
:src="targetGroupInfo.avatar"
alt="group-avatar"
/>
</div>
</div>
</div>
<!-- 分割线 -->
<div class="divider"></div>
<!-- 发送按钮 -->
<div class="send-button-container">
<el-button
type="primary"
@click="
sendMessage(
store.targetGroupInfo?.chatId ||
store.targetUserInfo.chatId,
store.targetGroupInfo
? 'group'
: store.targetUserInfo.type
)
"
>
发送消息
</el-button>
</div>
</div> </div>
<div v-else>1</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useChatStore } from "@/stores/chat"; import { useChatStore } from "@/stores/chat";
const store = useChatStore(); const store = useChatStore();
//
const targetUserInfo = {
avatar: "./logo.png",
jobNumber: 12345678,
desc: "测试岗位",
email: "12345678@qq.com",
phone: "12345678910",
hiredDate: "2024-01-01",
};
//
const targetGroupInfo = {
avatar: "./logo.png",
chatId: "1234567890",
memberCount: 100,
createdAt: "2024-01-01",
};
const sendMessage = (chatId: string, type: string) => { const sendMessage = (chatId: string, type: string) => {
store.currentNavId = 0; store.currentNavId = 0;
console.log(chatId, type); store.getGroupMemberList(chatId);
store.addChatListAndGetChatHistory(chatId, type); store.addChatListAndGetChatHistory(chatId, type);
}; };
</script> </script>
<style scoped> <style scoped>
/* 无消息提示样式 */ .chat-user-info {
.no-message-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; width: 100%;
justify-content: center; padding: 20px;
text-align: center;
color: #4a4a4a;
} }
.welcome-text { .user-content,
font-size: 18px; .group-content {
font-weight: 600; display: flex;
margin-top: 10px; align-items: center;
color: #333; justify-content: space-between;
} }
/* 用户信息容器 */ .user-details,
.user-info-container { .group-details {
flex: 1;
display: flex; display: flex;
width: 100%;
flex-direction: column; flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 20px;
text-align: center;
} }
/* 用户头像 */ .user-details h2,
.user-avatar { .group-details h2 {
margin-bottom: 15px; margin: 0;
border-radius: 50%; font-size: 1.5rem;
} }
/* 用户名标题 */ .user-details p,
.user-name { .group-details p {
font-size: 22px; margin: 5px 0;
font-weight: 700; color: #666;
color: #333;
margin-bottom: 15px;
} }
/* 用户详细信息 */ .avatar,
.user-details { .group-avatar {
text-align: left; width: 80px;
background-color: #f8f9fa; height: 100%;
padding: 20px; object-fit: cover;
border-radius: 10px; margin-left: 20px;
box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.1);
max-width: 200px;
width: 100%;
margin-bottom: 30px;
} }
.user-details p { .divider {
margin: 8px 0; width: 100%;
color: #333; height: 1px;
background-color: #e0e0e0;
margin: 20px 0;
} }
.user-details p strong { .send-button-container {
color: #333; display: flex;
justify-content: center;
margin-top: 20px;
} }
/* 发送消息按钮 */ .el-button {
.send-message-btn { background-color: #0d42d2;
width: 100%;
max-width: 240px;
font-size: 16px;
background-color: #0078d4;
color: #fff; color: #fff;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 120, 212, 0.3);
font-weight: 600;
transition: background-color 0.3s;
} }
.el-button:hover {
.send-message-btn:hover { background-color: #4080ff;
background-color: #005a9e; color: #fff;
}
/* 群聊信息容器 */
.group-info-container {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 20px;
text-align: center;
} }
</style> </style>

4
frontend/src/components/window/WindowTemplate.vue

@ -191,10 +191,10 @@
margin: 0; margin: 0;
// left: 0; // left: 0;
// top: 0; // top: 0;
min-width: 950px; // min-width: 900px;
width: max-content; width: max-content;
height: max-content; height: max-content;
min-height: 650px; // min-height: 650px;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
background-color: #fff; background-color: #fff;

21
frontend/src/main.ts

@ -1,21 +1,24 @@
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
//svg插件需要配置代码
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import { createApp } from "vue"; import { createApp } from "vue";
import screenShort from "vue-web-screen-shot";
import App from "./App.vue"; import App from "./App.vue";
import pinia from './stores/index.ts' import './assets/windows10.scss';
import ElementPlus from 'element-plus' import { i18n } from './i18n/index.ts';
import 'element-plus/dist/index.css' import pinia from './stores/index.ts';
import './assets/windows10.scss' import router from './system/router';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import router from './system/router'
import screenShort from "vue-web-screen-shot"
import {i18n} from './i18n/index.ts'
const app = createApp(App) const app = createApp(App)
app.use(router) app.use(router)
app.use(ElementPlus) app.use(ElementPlus)
app.use(pinia) app.use(pinia)
app.use(i18n) app.use(i18n)
app.use(screenShort, { enableWebRtc: true }) app.use(screenShort, { enableWebRtc: true })
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component) app.component(key, component)
} }
app.mount("#app"); app.mount("#app");

73
frontend/src/stores/chat.ts

@ -80,7 +80,6 @@ export const useChatStore = defineStore('chatStore', () => {
// 群组l列表 // 群组l列表
const groupList: any = ref([]); const groupList: any = ref([]);
const drawerVisible = ref(false)
const targetGroupInfo: any = ref({}) const targetGroupInfo: any = ref({})
const activeNames = ref([]); const activeNames = ref([]);
const userInfo: any = ref({}); const userInfo: any = ref({});
@ -411,7 +410,7 @@ export const useChatStore = defineStore('chatStore', () => {
previewMessage: message.value, previewMessage: message.value,
previewTimeFormat: formatTime(Date.now()), previewTimeFormat: formatTime(Date.now()),
displayName: userInfo.value.nickname, displayName: userInfo.value.nickname,
chatId: targetUserInfo.value.chatId.toString(), chatId: targetUserInfo.value.chatId,
avatar: userInfo.value.avatar, avatar: userInfo.value.avatar,
createdAt: Date.now() createdAt: Date.now()
} }
@ -426,7 +425,7 @@ export const useChatStore = defineStore('chatStore', () => {
previewTimeFormat: formatTime(Date.now()), previewTimeFormat: formatTime(Date.now()),
displayName: targetGroupInfo.value.name, displayName: targetGroupInfo.value.name,
isMe: true, isMe: true,
chatId: targetGroupInfo.value.chatId.toString(), chatId: targetGroupInfo.value.chatId,
createdAt: Date.now() createdAt: Date.now()
} }
@ -483,10 +482,6 @@ export const useChatStore = defineStore('chatStore', () => {
// 从 conversationList 数据库中查找是否存在对应的会话 // 从 conversationList 数据库中查找是否存在对应的会话
const conversation = await db.getByField("workbenchSessionList", 'chatId', data.userId); const conversation = await db.getByField("workbenchSessionList", 'chatId', data.userId);
console.log("更新的文字", message)
// 准备会话更新数据 // 准备会话更新数据
const updatedConversation = { const updatedConversation = {
userId: data.userId, userId: data.userId,
@ -903,8 +898,6 @@ export const useChatStore = defineStore('chatStore', () => {
const updateOrAddUsers = async (users: OnlineUserInfoType[]) => { const updateOrAddUsers = async (users: OnlineUserInfoType[]) => {
// 从数据库中获取所有用户信息 // 从数据库中获取所有用户信息
const allUsers = await db.getAll("workbenchChatUser"); const allUsers = await db.getAll("workbenchChatUser");
console.log(users)
console.log(allUsers)
// 添加或更新在线用户 // 添加或更新在线用户
for (const user of users) { for (const user of users) {
const existingUser = allUsers.find((u: { id: string; }) => u.id === user.id); const existingUser = allUsers.find((u: { id: string; }) => u.id === user.id);
@ -986,12 +979,10 @@ export const useChatStore = defineStore('chatStore', () => {
chatHistory.value = [] chatHistory.value = []
targetChatId.value = chatId targetChatId.value = chatId
console.log("------")
if (type === 'user') { if (type === 'user') {
console.log("user") console.log("user")
// 获取当前用户和目标用户的聊天记录 // 获取当前用户和目标用户的聊天记录
const history = await getHistory(userInfo.value.id, chatId, type) const history = await getHistory(userInfo.value.id, chatId, type)
console.log(history)
chatHistory.value = [...history]; chatHistory.value = [...history];
// 设置目标用户的信息 // 设置目标用户的信息
await setTargetUserInfo(chatId); await setTargetUserInfo(chatId);
@ -1030,12 +1021,18 @@ export const useChatStore = defineStore('chatStore', () => {
// 设置目标用户的信息 // 设置目标用户的信息
const setTargetUserInfo = async (id: string) => { const setTargetUserInfo = async (id: string) => {
const userInfoArray = await db.getByField("workbenchChatUser", "chatId", id); var userInfoArray = await db.getByField("workbenchChatUser", "chatId", id);
// 封装用户信息 // 封装用户信息
const userInfo = { const userInfo = {
type: "user", type: "user",
avatar: userInfoArray[0].avatar || "", avatar: userInfoArray[0].avatar || "",
displayName: userInfoArray[0].nickname || "", displayName: userInfoArray[0].nickname || "",
jobNumber: userInfoArray[0].jobNumber || "",
desc: userInfoArray[0].desc || "",
email: userInfoArray[0].email || "",
phone: userInfoArray[0].phone || "",
hiredDate: userInfoArray[0].hiredDate || "",
toUserId: config.userInfo.id, toUserId: config.userInfo.id,
chatId: userInfoArray[0].chatId chatId: userInfoArray[0].chatId
} }
@ -1045,8 +1042,16 @@ export const useChatStore = defineStore('chatStore', () => {
// 设置目标群信息 // 设置目标群信息
const setTargetGrouprInfo = async (id: string) => { const setTargetGrouprInfo = async (id: string) => {
for (const group of groupList.value) { for (var group of groupList.value) {
if (group.group_id === id) { if (group.group_id === id) {
// 模拟群信息
// group = {
// chatId: id,
// displayName: "湖南果度科技有限公司",
// avatar: "./src/assets/icons/group.png",
// memberCount: 150, // 假设成员数
// createdAt: "2023-01-01" // 假设创建日期
// }
targetGroupInfo.value = group; targetGroupInfo.value = group;
targetUserInfo.value = {}; targetUserInfo.value = {};
break; break;
@ -1149,7 +1154,6 @@ export const useChatStore = defineStore('chatStore', () => {
initChatList() initChatList()
targetGroupInfo.value = {} targetGroupInfo.value = {}
targetChatId.value = '' targetChatId.value = ''
drawerVisible.value = false
notifySuccess("退出群聊成功") notifySuccess("退出群聊成功")
} }
@ -1256,6 +1260,43 @@ export const useChatStore = defineStore('chatStore', () => {
}; };
// 清空聊天记录
// 清空聊天记录
const clearChatRecord = async () => {
// 删除我发送给对方的记录
const whereObjSent = {
toUserId: targetChatId.value,
userId: userInfo.value.id
};
const resSent = await db.deleteByWhere("workbenchChatRecord", whereObjSent);
// 删除对方发送给我的记录
const whereObjReceived = {
userId: targetChatId.value,
toUserId: userInfo.value.id
};
const resReceived = await db.deleteByWhere("workbenchChatRecord", whereObjReceived);
// 检查两个删除操作是否都成功
if (resSent == 1 && resReceived == 1) {
// 更新chatHistory,移除相关记录
chatHistory.value = chatHistory.value.filter((item: any) =>
!(item.toUserId === targetChatId.value && item.userId === userInfo.value.id) &&
!(item.userId === targetChatId.value && item.toUserId === userInfo.value.id)
);
// 更新chatList中的预览消息
chatList.value.forEach((item: any) => {
if (item.chatId === targetChatId.value) {
item.previewMessage = "快开始打招呼吧!";
}
});
notifySuccess("清空成功");
return;
}
notifyError("清空失败");
}
return { return {
emojiList, emojiList,
groupSystemMessage, groupSystemMessage,
@ -1288,7 +1329,6 @@ export const useChatStore = defineStore('chatStore', () => {
groupMemberList, groupMemberList,
groups, groups,
inviteFriendDialogVisible, inviteFriendDialogVisible,
drawerVisible,
addMemberDialogVisible, addMemberDialogVisible,
inviteUserList, inviteUserList,
messageSendStatus, messageSendStatus,
@ -1317,6 +1357,7 @@ export const useChatStore = defineStore('chatStore', () => {
groupInviteMessage, groupInviteMessage,
setScrollToBottom, setScrollToBottom,
getGroupMemberList, getGroupMemberList,
getInviteUserList getInviteUserList,
clearChatRecord,
}; };
}); });

5
frontend/src/stores/db.ts

@ -142,6 +142,11 @@ export const db = {
async deleteByField(tableName: ChatTable, fieldName: string, val: any) { async deleteByField(tableName: ChatTable, fieldName: string, val: any) {
return dbInit[tableName].where(fieldName).equals(val).delete() return dbInit[tableName].where(fieldName).equals(val).delete()
}, },
// 根据whereObj条件删除
async deleteByWhere(tableName: ChatTable, whereObj: any) {
return dbInit[tableName].where(whereObj).delete()
},
// 获取创建时间最近的记录 // 获取创建时间最近的记录
async getLatest(tableName: ChatTable, fieldName: string, val: any) { async getLatest(tableName: ChatTable, fieldName: string, val: any) {
return dbInit[tableName].where(fieldName).equals(val).reverse().first() return dbInit[tableName].where(fieldName).equals(val).reverse().first()

2
frontend/src/system/member.ts

@ -4,7 +4,7 @@ export const memberList = [
appIcon: "chat", appIcon: "chat",
content:"Chat", content:"Chat",
frame: true, frame: true,
width: 1050, width: 1000,
height: 650, height: 650,
center: true, center: true,
resizable: true, resizable: true,

Loading…
Cancel
Save