Browse Source

fix:test

master
qiutianhong 7 months ago
parent
commit
e34e6fe1c9
  1. 16
      frontend/components.d.ts
  2. 197
      frontend/src/components/chat/Chat.vue
  3. 195
      frontend/src/components/chat/ChatBox.vue
  4. 61
      frontend/src/components/chat/ChatMessage.vue
  5. 96
      frontend/src/components/chat/ChatMsgList.vue
  6. 19
      frontend/src/components/chat/ChatUserList.vue
  7. 192
      frontend/src/stores/chat.ts

16
frontend/components.d.ts

@ -50,31 +50,45 @@ 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']
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']
ElCarousel: typeof import('element-plus/es')['ElCarousel'] ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElFooter: typeof import('element-plus/es')['ElFooter'] ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
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']
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']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
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']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
Error: typeof import('./src/components/taskbar/Error.vue')['default'] Error: typeof import('./src/components/taskbar/Error.vue')['default']
FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default'] FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default']
FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default'] FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default']

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

@ -8,7 +8,6 @@
store.initChat(); store.initChat();
}); });
// el-transfer
const generateData = () => { const generateData = () => {
return store.allUserList.map((user: any) => ({ return store.allUserList.map((user: any) => ({
key: user.id, key: user.id,
@ -27,18 +26,21 @@
} }
}); });
function toggleSelectItem(item: any) {
const index = users.value.indexOf(item.key); // searchList
if (index === -1) { watch(
users.value.push(item.key); () => store.searchInput,
(newSearchInput) => {
if (newSearchInput === "") {
store.searchList = []; //
} else { } else {
users.value.splice(index, 1); store.searchList = store.chatList.filter(
} (user: { displayName: string | string[] }) =>
user.displayName.includes(newSearchInput)
);
} }
function removeItem(userId: string) {
users.value = users.value.filter((user) => user !== userId);
} }
);
</script> </script>
<template> <template>
<el-container class="container"> <el-container class="container">
@ -59,7 +61,7 @@
placeholder="搜索" placeholder="搜索"
:prefix-icon="Search" :prefix-icon="Search"
class="search-input" class="search-input"
v-model="store.search" v-model="store.searchInput"
/> />
<!-- 邀请群聊 --> <!-- 邀请群聊 -->
<button <button
@ -103,86 +105,54 @@
v-model="store.groupChatInvitedDialogVisible" v-model="store.groupChatInvitedDialogVisible"
title="发起群聊" title="发起群聊"
width="80%" width="80%"
style="height: 550px"
align-center align-center
> >
<div class="dialog-body"> <div class="dialog-body">
<!-- 添加输入部门名的输入框 --> <!-- 添加输入部门名的输入框 -->
<el-form <div>
label-position="top" <el-form label-position="top">
>
<el-form-item label="群聊名称:"> <el-form-item label="群聊名称:">
<el-input <el-input
style="width: 240px" style="width: 240px; height: 30px"
class="department-name" class="department-name"
v-model="store.departmentName" v-model="store.departmentName"
placeholder="请输入群聊名称" placeholder="请输入群聊名称"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-upload>
</el-upload>
</el-form> </el-form>
<div class="transfer-container"> </div>
<!-- 自定义穿梭框组件 -->
<div class="transfer-box"> <!-- 使用 Element el-transfer 组件替换自定义穿梭框 -->
<div class="list-box"> <el-transfer
<h3>可选项</h3> v-model="users"
<ul> :data="data"
<li :titles="['可选项', '已选项']"
v-for="item in data" filterable
:key="item.key" :props="{ key: 'key', label: 'label', avatar: 'avatar' }"
@click="toggleSelectItem(item)" class="transfer-container"
:class="{ selected: users.includes(item.key) }"
> >
<!-- 自定义穿梭框列表项模板 -->
<template #default="{ option }">
<el-avatar <el-avatar
:size="10" :src="option.avatar"
:src="item.avatar" size="small"
class="avatar" style="margin-right: 5px"
/> />
<input <span>{{ option.label }}</span>
type="checkbox" </template>
:checked="users.includes(item.key)" </el-transfer>
/>
<span>{{ item.label }}</span>
<span
v-if="users.includes(item.key)"
class="remove-icon"
@click.stop="removeItem(item.key)"
></span
>
</li>
</ul>
</div>
<div class="list-box">
<h3>已选项</h3>
<ul>
<li
v-for="user in users"
:key="user"
>
<span>{{
data.find((item: any) => item.key === user)
?.label
}}</span>
<span
class="remove-icon"
@click="removeItem(user)"
></span
>
</li>
</ul>
</div>
</div>
</div>
</div> </div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="store.groupChatInvitedDialogVisible = false" <el-button
style="background-color: #0078d4; color: #fff"
@click="store.groupChatInvitedDialogVisible = false"
>取消</el-button >取消</el-button
> >
<el-button <el-button
type="primary" style="background-color: #0078d4; color: #fff"
@click="store.createGroupChat(users)" @click="store.createGroupChat(users)"
>确定</el-button >确定</el-button
> >
@ -192,6 +162,34 @@
</template> </template>
<style scoped> <style scoped>
.el-transfer {
display: flex;
flex-direction: row; /* 将布局方向设置为横向 */
width: 550px; /* 让穿梭框占满宽度 */
}
.el-transfer-panel {
width: 300px !important;
height: 530px !important;
}
.el-transfer-panel__body {
height: 450px !important;
}
.el-checkbox__label {
margin-left: 10px !important;
}
.el-transfer-panel
.el-transfer-panel__header
.el-checkbox
.el-checkbox__label
span {
left: 150px;
right: 0px;
}
.container { .container {
display: flex; display: flex;
height: 100%; height: 100%;
@ -213,7 +211,7 @@
flex: 1; flex: 1;
/* 占据剩余宽度 */ /* 占据剩余宽度 */
max-height: max-content; max-height: max-content;
border-right: 1px solid #edebeb; border-right: 1px solid #e8e8e8;
overflow-y: hidden; overflow-y: hidden;
overflow-x: hidden; overflow-x: hidden;
} }
@ -264,21 +262,11 @@
overflow-y: hidden; overflow-y: hidden;
overflow-x: hidden; overflow-x: hidden;
border-left: 1px solid #e8e8e8; border-left: 1px solid #e8e8e8;
/* border-right: 1px solid #E8E8E8; */
} }
.dialog-body { .dialog-body {
width: 100%; width: 100%;
} }
.transfer-container >>> .el-transfer-panel {
width: 300px;
}
.el-transfer {
display: flex;
align-items: center;
justify-content: center;
}
.chat-box { .chat-box {
flex: 3; flex: 3;
/* 占据剩余宽度的三倍 */ /* 占据剩余宽度的三倍 */
@ -307,53 +295,6 @@
justify-content: center; justify-content: center;
} }
.transfer-box {
display: flex;
gap: 0px;
}
.list-box {
width: 50%;
border-top: 1px solid #ccc;
margin-top: 10px;
}
.list-box ul {
list-style: none;
padding: 0;
margin: 0;
}
.list-box li {
display: flex;
align-items: center;
padding: 5px 0;
cursor: pointer;
transition: background-color 0.2s;
}
.list-box li:hover {
background-color: #f0f8ff;
}
.list-box li.selected {
background-color: #e6f7ff;
}
input[type="checkbox"] {
margin-right: 8px;
}
.remove-icon {
margin-left: auto;
color: #d32f2f;
cursor: pointer;
font-size: 14px;
}
.remove-icon:hover {
color: #ff5a5a;
}
.department-name { .department-name {
margin: 10px 0; margin: 10px 0;
} }

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

@ -28,7 +28,8 @@
<el-icon><More /></el-icon> <el-icon><More /></el-icon>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="store.inviteFriend" <el-dropdown-item
@click="store.inviteFriendDialogVisible = true"
>邀请好友</el-dropdown-item >邀请好友</el-dropdown-item
> >
<el-dropdown-item <el-dropdown-item
@ -42,6 +43,36 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<!-- 邀请好友对话框 -->
<el-dialog
v-model="store.inviteFriendDialogVisible"
title="邀请好友"
width="80%"
style="height: 550px"
align-center
>
<div>
<el-transfer
v-model="value"
:data="data"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button
style="background-color: #0078d4; color: #fff"
@click="store.inviteFriendDialogVisible = false"
>取消</el-button
>
<el-button
style="background-color: #0078d4; color: #fff"
>确定</el-button
>
</span>
</template>
</el-dialog>
</div> </div>
</el-header> </el-header>
@ -55,9 +86,28 @@
</el-main> </el-main>
<!--聊天输入区和发送按钮等--> <!--聊天输入区和发送按钮等-->
<el-footer class="msg-footer"> <el-footer
<!--聊天输入选项--> class="msg-footer"
<div class="input-option"> style="
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
"
>
<!-- 输入主体 -->
<div
class="input-main"
style="width: 100%; height: 40px; background-color: #ffffff"
>
<el-input
size="large"
style="width: 100%; height: 100%"
placeholder="请输入内容"
@keyup.enter.exact="store.sendMessage('text')"
v-model="store.message"
>
<template #suffix>
<el-icon <el-icon
:size="20" :size="20"
class="input-option-icon" class="input-option-icon"
@ -78,23 +128,6 @@
> >
<Delete /> <Delete />
</el-icon> </el-icon>
</div>
<!--聊天输入区-->
<div class="input-textarea">
<el-input
type="textarea"
maxlength="1000"
resize="none"
class="textarea"
@keyup.enter.exact="store.sendMessage('text')"
v-model="store.message"
/>
</div>
<!--聊天发送按钮-->
<el-tooltip
placement="top"
content="按enter键发送,按ctrl+enter键换行"
>
<el-icon <el-icon
@click="store.sendMessage('text')" @click="store.sendMessage('text')"
:size="22" :size="22"
@ -102,7 +135,9 @@
> >
<Promotion /> <Promotion />
</el-icon> </el-icon>
</el-tooltip> </template>
</el-input>
</div>
</el-footer> </el-footer>
</div> </div>
@ -128,9 +163,20 @@
const imgExt = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"]; const imgExt = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"];
const choosetype = ref(""); const choosetype = ref("");
const generateData = () => {
return store.allUserList.map((user: any) => ({
key: user.id,
label: user.nickname,
avatar: user.avatar, //
}));
};
const data = ref(generateData());
const value = ref([]);
function selectImg() { function selectImg() {
choosetype.value = "image"; choosetype.value = "image";
console.log(choosetype.value) console.log(choosetype.value);
choose.select("选择图片", imgExt); choose.select("选择图片", imgExt);
} }
function selectFile() { function selectFile() {
@ -156,6 +202,57 @@
</script> </script>
<style scoped> <style scoped>
.el-transfer {
display: flex;
flex-direction: row; /* 将布局方向设置为横向 */
width: 550px; /* 让穿梭框占满宽度 */
}
.el-transfer-panel {
width: 300px !important;
height: 530px !important;
}
.el-transfer-panel__body {
height: 450px !important;
}
.el-checkbox__label {
margin-left: 10px !important;
}
.el-transfer-panel
.el-transfer-panel__header
.el-checkbox
.el-checkbox__label
span {
left: 150px;
right: 0px;
}
.el-transfer__buttons {
display: flex;
flex-direction: column;
align-items: center !important;
/* 水平居中对齐 */
justify-content: center !important;
gap: 10px;
/* 子元素之间的间距 */
padding: 0 15px;
}
.el-transfer__buttons .el-button {
min-width: 35px !important;
text-align: center;
margin-left: 0 !important;
}
.el-transfer__buttons .el-transfer__button {
width: 35px;
height: 35px;
border-radius: 50%;
}
.chatbox-main { .chatbox-main {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -164,17 +261,14 @@
.chat-header { .chat-header {
width: 100%; width: 100%;
/* 占据整个宽度 */ /* 占据整个宽度 */
height: 50px; height: 49px;
line-height: 50px; line-height: 50px;
padding: 0;
-webkit-app-region: drag;
} }
.header-title { .header-title {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin: 0px 10px;
} }
.header-title-name { .header-title-name {
@ -186,22 +280,16 @@
.msg-main { .msg-main {
width: 100%; width: 100%;
/* 占据整个宽度 */ /* 占据整个宽度 */
height: calc(70% - 50px); height: 75%;
padding: 0; padding: 0;
border-top-width: 1px; border-top: 1px solid #edebeb;
border-bottom-width: 1px;
border-left-width: 0;
border-right-width: 0;
border-color: #d6d6d6;
border-style: solid;
} }
.msg-footer { .msg-footer {
width: 100%; width: 100%;
/* 占据整个宽度 */ /* 占据整个宽度 */
height: 30%; height: calc(100% - 75% - 49px);
padding: 0; border: none;
} }
.input-option { .input-option {
@ -210,9 +298,8 @@
} }
.input-option-icon { .input-option-icon {
color: #494949; margin-left: 10px;
margin-left: 20px; color: #656a72;
margin-top: 5px;
cursor: pointer; cursor: pointer;
} }
@ -239,26 +326,8 @@
} }
.input-button { .input-button {
position: absolute; margin-left: 10px;
bottom: 12px; color: #2a6bf2;
right: 12px;
width: 30px;
/* 缩小宽度 */
height: 30px;
/* 减小高度 */
border-radius: 50%;
/* 较小的圆角 */
background-color: #e8f0fe;
/* 浅蓝色,符合Win11的轻量风格 */
color: #0078d4;
/* 使用Win11的强调色作为文字颜色 */
font-weight: bold;
border: 1px solid #b3d4fc;
/* 添加边框,保持简洁风格 */
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
/* 轻微阴影 */
transition: all 0.2s ease;
/* 快速过渡效果 */
} }
.input-button:hover { .input-button:hover {
@ -276,10 +345,4 @@
transform: translateY(1px); transform: translateY(1px);
/* 微小下移,模拟按下 */ /* 微小下移,模拟按下 */
} }
.no-message-container {
margin: 120px auto;
text-align: center;
justify-content: center;
}
</style> </style>

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

@ -2,6 +2,7 @@
<div <div
v-if="store.chatHistory && store.chatHistory.length > 0" v-if="store.chatHistory && store.chatHistory.length > 0"
v-for="item in store.chatHistory" v-for="item in store.chatHistory"
style="margin-top: 10px"
:key="item.chatId" :key="item.chatId"
> >
<div <div
@ -37,7 +38,6 @@
v-else-if="item.content_type == 'file'" v-else-if="item.content_type == 'file'"
:class="['chat-item-file', 'file-me']" :class="['chat-item-file', 'file-me']"
> >
<!-- 文件图标 -->
<div class="chat-item-file-icon"> <div class="chat-item-file-icon">
<el-icon <el-icon
size="30" size="30"
@ -45,7 +45,6 @@
><Document ><Document
/></el-icon> /></el-icon>
</div> </div>
<!-- 文件名 -->
<div class="chat-item-file-name"> <div class="chat-item-file-name">
{{ item.message }} {{ item.message }}
</div> </div>
@ -63,6 +62,10 @@
/> />
</div> </div>
</div> </div>
<!-- 发送时间展示在消息框外部的下方 -->
<div class="chat-time-me-outer">
{{ item.previewTimeFormat }}
</div>
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<div class="chat-avatar"> <div class="chat-avatar">
@ -115,7 +118,6 @@
v-else-if="item.content_type == 'file'" v-else-if="item.content_type == 'file'"
:class="['chat-item-file', 'file-other']" :class="['chat-item-file', 'file-other']"
> >
<!-- 文件图标 -->
<div class="chat-item-file-icon"> <div class="chat-item-file-icon">
<el-icon <el-icon
size="30" size="30"
@ -123,7 +125,6 @@
><Document ><Document
/></el-icon> /></el-icon>
</div> </div>
<!-- 文件名 -->
<div class="chat-item-file-name"> <div class="chat-item-file-name">
{{ item.message }} {{ item.message }}
</div> </div>
@ -141,6 +142,10 @@
/> />
</div> </div>
</div> </div>
<!-- 发送时间展示在消息框外部的下方 -->
<div class="chat-time-other-outer">
{{ item.previewTimeFormat }}
</div>
</el-col> </el-col>
<el-col :span="8" /> <el-col :span="8" />
</el-row> </el-row>
@ -180,34 +185,43 @@
.bubble-me, .bubble-me,
.bubble-other { .bubble-other {
display: flex; display: flex;
align-items: center; flex-direction: column;
background-color: #ffffff; background-color: #ffffff;
float: left; float: left;
border-radius: 4px;
margin-left: 5px; margin-left: 5px;
margin-bottom: 20px; margin-bottom: 10px;
} }
.bubble-me { .bubble-me {
background-color: #95ec69; background-color: #d6e4f6;
float: right; float: right;
border-radius: 12px 0px 12px 12px;
margin-right: 5px; margin-right: 5px;
margin-left: 0; margin-left: 0;
} }
.bubble-other {
border-radius: 0 12px 12px 12px;
background-color: #e8eaed;
}
.chat-name-me, .chat-name-me,
.chat-name-other { .chat-name-other {
font-size: 14px; font-size: 14px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 1.5;
color: #b2b2b2; color: #b2b2b2;
margin-bottom: 2px; margin-bottom: 2px;
margin-left: 2px; margin-left: 2px;
} }
.chat-name-me { .chat-name-me {
text-align: right; /* 右对齐昵称 */ text-align: right;
margin-right: 2px; margin-right: 5px;
}
.chat-name-other {
text-align: left;
margin-left: 5px;
} }
.chat-font { .chat-font {
@ -226,11 +240,11 @@
} }
.file-me { .file-me {
flex-direction: row; /* 图标在左边 */ flex-direction: row;
} }
.file-other { .file-other {
flex-direction: row-reverse; /* 图标在右边 */ flex-direction: row-reverse;
} }
.chat-item-file-icon { .chat-item-file-icon {
@ -243,9 +257,30 @@
line-height: 1.5; line-height: 1.5;
color: #409eff; color: #409eff;
} }
.chat-item-image { .chat-item-image {
width: 180px; width: 180px;
height: 180px; height: 180px;
margin: 10px; margin: 10px;
} }
.chat-time-me-outer,
.chat-time-other-outer {
font-size: 12px;
color: #999999;
margin-top: 25px;
text-align: center;
}
.chat-time-me-outer {
display: flex;
padding-right: 5px;
justify-content: end;
}
.chat-time-other-outer {
display: flex;
padding-left: 5px;
justify-content: start;
}
</style> </style>

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

@ -1,7 +1,12 @@
<template> <template>
<div <div
v-if="store.chatList.length > 0" v-if="
v-for="item in store.chatList" (store.searchList.length > 0 ? store.searchList : store.chatList)
.length > 0
"
v-for="item in store.searchList.length > 0
? store.searchList
: store.chatList"
:key="item.id" :key="item.id"
> >
<div <div
@ -9,17 +14,27 @@
@click="store.getSessionInfo(item.chatId, item.type)" @click="store.getSessionInfo(item.chatId, item.type)"
:style="{ :style="{
backgroundColor: backgroundColor:
item.chatId == store.targetChatId ? '#EAF3FC' : '', item.chatId == store.targetChatId ? '#F5F5F5' : '',
}" }"
> >
<el-row> <el-row>
<el-col :span="6"> <el-col :span="6">
<el-avatar <el-avatar
v-if="item.type == 'user'"
shape="square" shape="square"
:size="40" :size="40"
class="avatar" class="avatar"
:src="item.avatar" :src="item.avatar"
/> ></el-avatar>
<el-avatar
v-else="item.type == 'group'"
shape="square"
:size="40"
class="avatar"
:src="item.avatar"
></el-avatar
>
</el-col> </el-col>
<el-col <el-col
:span="18" :span="18"
@ -30,9 +45,15 @@
:span="18" :span="18"
class="preview-left" class="preview-left"
> >
<div class="preview-top-content">
<div class="previewName"> <div class="previewName">
{{ item.displayName }} {{ item.displayName }}
</div> </div>
<div class="previewTime">
{{ item.previewTimeFormat }}
</div>
</div>
<div class="previewChat"> <div class="previewChat">
<span>{{ item.previewMessage }}</span> <span>{{ item.previewMessage }}</span>
</div> </div>
@ -41,15 +62,13 @@
:span="6" :span="6"
class="preview-right" class="preview-right"
> >
<div class="previewTime">
{{ item.previewTimeFormat }}
</div>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</div> </div>
<div <div
v-else v-else
class="emptyChat" class="emptyChat"
@ -70,20 +89,34 @@
const store = useChatStore(); const store = useChatStore();
const id = ref("1"); const id = ref("1");
// store
const filteredMessages = computed(() => {
return store.search
? store.messages.filter((message) =>
message.content
.toLowerCase()
.includes(store.search.toLowerCase())
)
: store.messages;
});
</script> </script>
<style scoped> <style scoped>
.list-item { .list-item {
width: 100%; width: 95%;
height: 60px; height: 60px;
display: flex; display: flex;
margin: 0 auto;
transition: all 0.5s; transition: all 0.5s;
margin: 0 auto;
border-radius: 4px;
margin-bottom: 5px;
overflow: hidden; overflow: hidden;
margin-top: 5px;
} }
.list-item:hover { .list-item:hover {
background-color: #eaf3fc; background-color: #f5f5f5;
} }
.avatar { .avatar {
@ -103,28 +136,43 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
margin-left: 5px;
} }
.preview-left { .preview-top-content {
height: 20px;
display: flex; display: flex;
flex-direction: column; min-width: 110px;
justify-content: center; max-width: 200px;
height: 100%; align-items: center;
margin-left: 10px;
justify-content: space-between;
} }
.previewName { .previewName {
margin-left: 10px; font-size: 16px;
font-size: 12px; color: #000000;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
overflow: hidden; /* 隐藏超出部分 */ overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 显示为省略号 */ text-overflow: ellipsis; /* 显示为省略号 */
white-space: nowrap; /* 不换行 */ white-space: nowrap; /* 不换行 */
min-width: 100px; /* 最小宽度 */ }
max-width: 100%; /* 最大宽度 */
.previewTime {
font-size: 12px;
color: #999999;
}
.preview-left {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
} }
.previewChat { .previewChat {
line-height: 20px;
width: 100px;
height: 20px; height: 20px;
margin-left: 10px; margin-left: 10px;
font-size: 10px; font-size: 10px;
@ -133,8 +181,6 @@
overflow: hidden; /* 隐藏超出部分 */ overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 显示为省略号 */ text-overflow: ellipsis; /* 显示为省略号 */
white-space: nowrap; /* 不换行 */ white-space: nowrap; /* 不换行 */
min-width: 90px; /* 最小宽度 */
max-width: 100%; /* 最大宽度 */
} }
.preview-right { .preview-right {
@ -144,14 +190,6 @@
height: 100%; height: 100%;
} }
.previewTime {
font-size: 12px;
font-family: Arial, sans-serif;
color: #999999;
min-width: 30px; /* 最小宽度 */
max-width: 100%; /* 最大宽度 */
}
.emptyChat { .emptyChat {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

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

@ -69,7 +69,7 @@
@click="store.getSessionInfo(item.chatId, 'user')" @click="store.getSessionInfo(item.chatId, 'user')"
:style="{ :style="{
backgroundColor: backgroundColor:
item.id === store.targetChatId ? '#bae7ff' : '', item.id === store.targetChatId ? '#F5F5F5' : '',
}" }"
> >
<el-row> <el-row>
@ -82,8 +82,8 @@
/> />
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<el-row> <el-row style="display: flex; align-items: center;">
<el-col :span="18"> <el-col>
<div class="previewName"> <div class="previewName">
{{ item.nickname }} {{ item.nickname }}
<el-icon <el-icon
@ -100,7 +100,7 @@
</el-row> </el-row>
<el-row> <el-row>
<div class="previewIP"> <div class="previewIP">
{{ item.ip }} {{ item.login_ip}}
</div> </div>
</el-row> </el-row>
</el-col> </el-col>
@ -139,7 +139,6 @@
@node-click="handleNodeClick" @node-click="handleNodeClick"
:default-expand-all="false" :default-expand-all="false"
> >
</el-tree> </el-tree>
</div> </div>
@ -171,7 +170,7 @@
:style="{ :style="{
backgroundColor: backgroundColor:
group.id === store.targetChatId group.id === store.targetChatId
? '#bae7ff' ? '#F5F5F5'
: '', : '',
}" }"
> >
@ -181,8 +180,8 @@
shape="square" shape="square"
:size="40" :size="40"
class="avatar" class="avatar"
:src="group.avatar" ></el-avatar
/> >
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<div class="previewName">{{ group.name }}</div> <div class="previewName">{{ group.name }}</div>
@ -203,7 +202,7 @@
padding-left: 10px; padding-left: 10px;
} }
.list-item { .list-item {
width: 94%; width: 95%;
height: 60px; height: 60px;
margin: 0 auto; margin: 0 auto;
border-radius: 4px; border-radius: 4px;
@ -212,7 +211,7 @@
} }
.list-item:hover { .list-item:hover {
background-color: #bae7ff; background-color: #F5F5F5;
} }
.avatar { .avatar {

192
frontend/src/stores/chat.ts

@ -88,8 +88,9 @@ export const useChatStore = defineStore('chatStore', () => {
const message: any = ref(''); const message: any = ref('');
const targetUserInfo: any = ref({}); const targetUserInfo: any = ref({});
const targetChatId = ref(); const targetChatId = ref();
const search = ref(''); const searchList = ref([]);
const groups: any = ref([]) const groups: any = ref([])
const searchInput = ref('');
// 群成员列表 // 群成员列表
const groupMemberList = ref([]) const groupMemberList = ref([])
@ -98,10 +99,10 @@ export const useChatStore = defineStore('chatStore', () => {
const allUserList = ref([]) const allUserList = ref([])
// 部门列表 // 部门列表
const departmentList = ref([ const departmentList = ref([])
])
// 邀请好友
const inviteFriendDialogVisible = ref(false)
const contextMenu = ref({ const contextMenu = ref({
visible: false, visible: false,
chatMessageId: 0, chatMessageId: 0,
@ -159,7 +160,6 @@ export const useChatStore = defineStore('chatStore', () => {
}; };
const sendMessage = async (messageType: string) => { const sendMessage = async (messageType: string) => {
console.log("sendMessage:", messageType)
if (messageType == 'text') { if (messageType == 'text') {
await sendTextMessage() await sendTextMessage()
} }
@ -203,19 +203,19 @@ export const useChatStore = defineStore('chatStore', () => {
fileSendActive.value = false; fileSendActive.value = false;
return; return;
} }
const data = await res.json();
console.log(data);
// 封装成消息历史记录 // 封装成消息历史记录
const messageHistory = { const messageHistory = {
...Message, ...Message,
isMe: true, isMe: true,
previewTimeFormat: formatTime(Date.now()),
displayName: userInfo.value.nickname,
chatId: Message.type === 'group' ? targetGroupInfo.value.chatId : targetUserInfo.value.chatId, chatId: Message.type === 'group' ? targetGroupInfo.value.chatId : targetUserInfo.value.chatId,
avatar: Message.type === 'group' ? targetUserInfo.value.avatar : '', avatar: Message.type === 'group' ? targetUserInfo.value.avatar : '',
createdAt: Date.now() createdAt: Date.now()
}; };
console.log("messageHistory------", messageHistory)
// 添加到聊天记录 // 添加到聊天记录
if (Message.type === 'group') { if (Message.type === 'group') {
@ -255,7 +255,6 @@ export const useChatStore = defineStore('chatStore', () => {
Message.to_groupid = targetGroupInfo.value?.group_id || ''; Message.to_groupid = targetGroupInfo.value?.group_id || '';
Message.userInfo = {}; Message.userInfo = {};
} }
console.log(Message)
// 发送文件消息 // 发送文件消息
console.log("token", config.userInfo.token) console.log("token", config.userInfo.token)
@ -266,20 +265,18 @@ export const useChatStore = defineStore('chatStore', () => {
fileSendActive.value = false; fileSendActive.value = false;
return; return;
} }
const data = await res.json();
console.log("", data);
// 封装成消息历史记录 // 封装成消息历史记录
const messageHistory = { const messageHistory = {
...Message, ...Message,
isMe: true, isMe: true,
previewTimeFormat: formatTime(Date.now()),
displayName: userInfo.value.nickname,
chatId: Message.type === 'group' ? targetGroupInfo.value.chatId : targetUserInfo.value.chatId, chatId: Message.type === 'group' ? targetGroupInfo.value.chatId : targetUserInfo.value.chatId,
avatar: Message.type === 'group' ? targetUserInfo.value.avatar : '', avatar: Message.type === 'group' ? targetUserInfo.value.avatar : '',
createdAt: Date.now() createdAt: Date.now()
}; };
console.log("messageHistory------", messageHistory)
// 添加到聊天记录 // 添加到聊天记录
if (Message.type === 'group') { if (Message.type === 'group') {
await db.addOne("workbenchGroupChatRecord", messageHistory); await db.addOne("workbenchGroupChatRecord", messageHistory);
@ -333,6 +330,7 @@ export const useChatStore = defineStore('chatStore', () => {
messageHistory = { messageHistory = {
...Message, ...Message,
isMe: true, isMe: true,
previewTimeFormat: formatTime(Date.now()),
displayName: userInfo.value.nickname, displayName: userInfo.value.nickname,
chatId: targetUserInfo.value.chatId.toString(), chatId: targetUserInfo.value.chatId.toString(),
avatar: userInfo.value.avatar, avatar: userInfo.value.avatar,
@ -343,6 +341,8 @@ export const useChatStore = defineStore('chatStore', () => {
} else if (Message.type === 'group') { } else if (Message.type === 'group') {
messageHistory = { messageHistory = {
...Message, ...Message,
previewTimeFormat: formatTime(Date.now()),
displayName: targetGroupInfo.value.name,
isMe: true, isMe: true,
chatId: targetGroupInfo.value.chatId.toString(), chatId: targetGroupInfo.value.chatId.toString(),
createdAt: Date.now() createdAt: Date.now()
@ -379,9 +379,9 @@ export const useChatStore = defineStore('chatStore', () => {
displayName: data.userInfo.nickname, displayName: data.userInfo.nickname,
nickname: data.userInfo.nickname, nickname: data.userInfo.nickname,
time: Date.now().toString, time: Date.now().toString,
// previewMessage: data.message, previewMessage: data.message,
// previewTimeFormat: formatTime(Date.now()), // 时间格式化函数 previewTimeFormat: formatTime(Date.now()), // 时间格式化函数
createdAt: Date.now() createdAt: Date.now(),
}; };
if (conversation.length === 0) { if (conversation.length === 0) {
@ -396,9 +396,9 @@ export const useChatStore = defineStore('chatStore', () => {
avatar: data.userInfo.avatar, avatar: data.userInfo.avatar,
nickname: data.userInfo.nickname, nickname: data.userInfo.nickname,
displayName: data.userInfo.nickname, displayName: data.userInfo.nickname,
// previewMessage: data.message, previewMessage: data.message,
time: data.time || Date.now().toString, time: data.time || Date.now().toString,
// previewTimeFormat: formatTime(Date.now()) previewTimeFormat: formatTime(Date.now())
}); });
@ -435,7 +435,8 @@ export const useChatStore = defineStore('chatStore', () => {
nickname: user.nickname, nickname: user.nickname,
avatar: user.avatar, avatar: user.avatar,
toUserId: userInfo.value.id, toUserId: userInfo.value.id,
time: Date.now(), previewMessage: message.value || "快开始打招呼吧!",
previewTimeFormat: formatTime(Date.now()),
displayName: user.nickname, displayName: user.nickname,
}; };
chatList.value.push(newChat); chatList.value.push(newChat);
@ -449,7 +450,8 @@ export const useChatStore = defineStore('chatStore', () => {
nickname: user.nickname, nickname: user.nickname,
avatar: user.avatar, avatar: user.avatar,
toUserId: userInfo.value.id, toUserId: userInfo.value.id,
time: Date.now(), previewMessage: message.value || "快开始打招呼吧!",
previewTimeFormat: formatTime(Date.now()),
createdAt: Date.now(), createdAt: Date.now(),
}); });
@ -518,8 +520,6 @@ export const useChatStore = defineStore('chatStore', () => {
return false; return false;
} }
// getGroupList()
// initChatList()
const groupData = await res.json(); const groupData = await res.json();
console.log(groupData) console.log(groupData)
// 构建数据入库 // 构建数据入库
@ -534,7 +534,7 @@ export const useChatStore = defineStore('chatStore', () => {
chatId: group_id, chatId: group_id,
type: "group", type: "group",
displayName: departmentName.value, displayName: departmentName.value,
previewMessage: "", previewMessage: "快来和大家一起交流吧!",
previewTimeFormat: formatTime(Date.now()), previewTimeFormat: formatTime(Date.now()),
createdAt: new Date() createdAt: new Date()
} }
@ -542,17 +542,17 @@ export const useChatStore = defineStore('chatStore', () => {
initChatList() initChatList()
// 关闭对话弹窗 // 关闭对话弹窗
setGroupChatInvitedDialogVisible(false) setGroupChatInvitedDialogVisible(false)
notifySuccess('创建群聊成功')
await getAllList() await getAllList()
}; };
// 处���用户消息 // 处用户消息
const userChatMessage = async (data: any) => { const userChatMessage = async (data: any) => {
// 先判断数据库是否有该用户 // 先判断数据库是否有该用户
// 更新聊天记录表 // 更新聊天记录表
// 更新会话列表数据库 // 更新会话列表数据库
// 更新chatlist // 更新chatlist
// 更新聊天记录 // 更新聊天记录
console.log("userChatMessage:", data)
const isPresence = await db.getByField('workbenchChatUser', 'chatId', data.userId) const isPresence = await db.getByField('workbenchChatUser', 'chatId', data.userId)
if (isPresence[0].id !== data.userId) { if (isPresence[0].id !== data.userId) {
return return
@ -569,6 +569,8 @@ export const useChatStore = defineStore('chatStore', () => {
isMe: false, isMe: false,
content_type: data.content_type, content_type: data.content_type,
to_groupid: data.to_groupid, to_groupid: data.to_groupid,
previewMessage: data.message,
previewTimeFormat: formatTime(Date.now()),
displayName: data.userInfo.nickname, displayName: data.userInfo.nickname,
avatar: data.userInfo.avatar, avatar: data.userInfo.avatar,
createdAt: Date.now(), createdAt: Date.now(),
@ -645,9 +647,10 @@ export const useChatStore = defineStore('chatStore', () => {
}; };
const onlineUserData = async (data: OnlineUserInfoType[]) => { const onlineUserData = async (data: OnlineUserInfoType[]) => {
// 创建一个用户数组,将所有在线的用户提取出来 // 创建一个新的用户数组,用于更新在线用户列表
const onlineUsers: OnlineUserInfoType[] = data.map(item => ({ const updatedOnlineUsers = data.map(item => ({
id: item.id, id: item.id,
login_ip: item.login_ip, login_ip: item.login_ip,
type: "user", type: "user",
@ -658,16 +661,21 @@ export const useChatStore = defineStore('chatStore', () => {
nickname: item.nickname nickname: item.nickname
})).filter(item => item.id && item.login_ip); // 确保所有项都有有效的id和ip })).filter(item => item.id && item.login_ip); // 确保所有项都有有效的id和ip
// 更新在线用户列表 // 更新在线用户列表,只添加不存在的用户
if (onlineUsers.length > 0) { updatedOnlineUsers.forEach(newUser => {
await setOnlineUserList(onlineUsers); if (!onlineUserList.value.some(existingUser => existingUser.id === newUser.id)) {
onlineUserList.value.push(newUser);
} }
});
// 更新数据库中的用户信息
await updateOrAddUsers(updatedOnlineUsers);
}; };
const setOnlineUserList = async (data: OnlineUserInfoType[]) => { const updateOrAddUsers = async (users: OnlineUserInfoType[]) => {
// 从数据库中获取所有用户信息 // 从数据库中获取所有用户信息
const allUsers = await db.getAll("workbenchChatUser"); const allUsers = await db.getAll("workbenchChatUser");
const onlineUserIds = new Set(data.map(user => user.id)); const onlineUserIds = new Set(users.map(user => user.id));
// 筛选出需要删除的用户(即不在在线列表中的用户) // 筛选出需要删除的用户(即不在在线列表中的用户)
const usersToDelete = allUsers.filter((user: { id: string; }) => !onlineUserIds.has(user.id)); const usersToDelete = allUsers.filter((user: { id: string; }) => !onlineUserIds.has(user.id));
@ -679,7 +687,7 @@ export const useChatStore = defineStore('chatStore', () => {
} }
// 添加或更新在线用户 // 添加或更新在线用户
for (const user of data) { 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);
if (existingUser) { if (existingUser) {
// 更新现有用户 // 更新现有用户
@ -707,115 +715,13 @@ export const useChatStore = defineStore('chatStore', () => {
}); });
} }
} }
// 重新初始化在线用户列表,确保所有用户都被显示
await initOnlineUserList();
}; };
const initOnlineUserList = async () => { // 邀请好友
// 从数据库中获取所有用户信息 const inviteFriend = async () => {
const list = await db.getAll("workbenchChatUser"); console.log("邀请好友")
// 更新用户列表 }
onlineUserList.value = list;
};
// const onlineUserData = async (data: OnlineUserInfoType[]) => {
// // 创建一个用户数组,将所有在线的用户提取出来
// const onlineUsers: OnlineUserInfoType[] = data.map(item => ({
// id: item.id,
// login_ip: item.login_ip,
// type: "user",
// chatId: item.id,
// online: true,
// avatar: item.avatar || '/default_avatar.png', // 使用默认头像如果没有提供
// username: item.username,
// nickname: item.nickname
// })).filter(item => item.id && item.login_ip); // 确保所有项都有有效的id和ip
// // 更新在线用户列表
// if (onlineUsers.length > 0) {
// await setOnlineUserList(onlineUsers);
// }
// };
// const setOnlineUserList = async (data: OnlineUserInfoType[]) => {
// // 从当前用户列表中获取已有用户的 IP 和完整用户映射
// const existingIps = new Set(onlineUserList.value.map((d: any) => d.ip));
// const userMap = new Map<string, OnlineUserInfoType>(
// onlineUserList.value.map((user: OnlineUserInfoType) => [user.login_ip, user])
// );
// const updates: any[] = [];
// const newEntries: any[] = [];
// // 遍历传入的 data,每个用户根据是否存在来更新或添加
// data.forEach((d: any) => {
// const existingUser = userMap.get(d.ip);
// if (existingUser && existingIps.has(d.ip)) {
// // 若用户已存在,添加到更新列表
// updates.push({
// key: existingUser.id,
// changes: {
// isOnline: true,
// avatar: d.avatar,
// nickname: d.nickname,
// username: d.username,
// updatedAt: Date.now()
// }
// });
// } else {
// // 若用户不存在,添加到新条目列表
// newEntries.push({
// id: d.id,
// ip: d.ip,
// type: "user",
// chatId: d.id,
// isOnline: true,
// avatar: d.avatar,
// nickname: d.nickname,
// username: d.username,
// createdAt: Date.now(),
// updatedAt: Date.now()
// });
// }
// });
// // 批量更新和添加用户数据
// if (updates.length > 0) {
// await db.table("workbenchChatUser").bulkUpdate(updates);
// }
// if (newEntries.length > 0) {
// await db.table("workbenchChatUser").bulkPut(newEntries);
// }
// console.log(updates)
// console.log(newEntries)
// // 重新初始化在线用户列表,确保所有用户都被显示
// await initOnlineUserList();
// };
// const initOnlineUserList = async () => {
// // 从数据库中获取所有用户信息
// const list = await db.getAll("workbenchChatUser");
// // 创建一个 Map,用于存储每个用户的唯一 ID 地址
// let uniqueIdMap = new Map<string, any>();
// // 遍历用户列表,将每个用户添加到 Map 中(基于 ID 去重)
// list.forEach((item: any) => {
// uniqueIdMap.set(item.userId, item); // 使用 ID 作为键,用户对象作为值
// });
// // 将 Map 的值转换为数组(去重后的用户列表)
// const uniqueIdList = Array.from(uniqueIdMap.values());
// // 按照 updatedAt 时间进行升序排序
// uniqueIdList.sort((a: any, b: any) => a.time - b.time);
// // 更新用户列表
// onlineUserList.value = list;
// };
// 初始化统一用户列表状态
const initUserList = async () => { const initUserList = async () => {
if (!onlineUserList.value.length) return; if (!onlineUserList.value.length) return;
// 获取需要更新的用户数据(只选取在线的用户并设为离线状态) // 获取需要更新的用户数据(只选取在线的用户并设为离线状态)
@ -915,8 +821,6 @@ export const useChatStore = defineStore('chatStore', () => {
}; };
const groupChatMessage = async (data: any) => { const groupChatMessage = async (data: any) => {
console.log("收到群消息了!!!!!")
// 创建消息记录
const messageRecord = { const messageRecord = {
userId: data.userId, userId: data.userId,
groupId: data.to_groupid, groupId: data.to_groupid,
@ -926,6 +830,7 @@ export const useChatStore = defineStore('chatStore', () => {
type: data.type, type: data.type,
chatId: data.to_groupid, chatId: data.to_groupid,
isMe: false, isMe: false,
previewTimeFormat: formatTime(Date.now()),
displayName: data.userInfo.nickname, displayName: data.userInfo.nickname,
avatar: data.userInfo.avatar, avatar: data.userInfo.avatar,
role_id: data.userInfo.role_id, role_id: data.userInfo.role_id,
@ -985,7 +890,7 @@ export const useChatStore = defineStore('chatStore', () => {
groupList, groupList,
chatHistory, chatHistory,
userInfo, userInfo,
search, searchList,
showChooseFile, showChooseFile,
currentPage, currentPage,
pageSize, pageSize,
@ -996,6 +901,7 @@ export const useChatStore = defineStore('chatStore', () => {
targetChatId, targetChatId,
message, message,
contextMenu, contextMenu,
searchInput,
activeNames, activeNames,
groupChatInvitedDialogVisible, groupChatInvitedDialogVisible,
groupInfoSettingDrawerVisible, groupInfoSettingDrawerVisible,
@ -1007,6 +913,7 @@ export const useChatStore = defineStore('chatStore', () => {
fileSendActive, fileSendActive,
groupMemberList, groupMemberList,
groups, groups,
inviteFriendDialogVisible,
initChat, initChat,
showContextMenu, showContextMenu,
setCurrentNavId, setCurrentNavId,
@ -1024,5 +931,6 @@ export const useChatStore = defineStore('chatStore', () => {
getAllList, getAllList,
getAllUser, getAllUser,
quitGroup, quitGroup,
inviteFriend,
}; };
}); });
Loading…
Cancel
Save