Browse Source

add chating

master
godo 9 months ago
parent
commit
de990d0712
  1. 21
      frontend/components.d.ts
  2. 105
      frontend/src/components/chat/Chat.vue
  3. 170
      frontend/src/components/chat/ChatBox.vue
  4. 72
      frontend/src/components/chat/ChatMenu.vue
  5. 166
      frontend/src/components/chat/ChatMessage.vue
  6. 71
      frontend/src/components/chat/ChatMsgList.vue
  7. 71
      frontend/src/components/chat/ChatUserList.vue
  8. 23
      frontend/src/components/chat/ChatUserSetting.vue
  9. 10
      frontend/src/components/chat/ChatWorkList.vue
  10. 0
      frontend/src/components/localchat/LocalChat.vue
  11. 86
      frontend/src/stores/chat.ts
  12. 5
      frontend/src/stores/db.ts
  13. 3
      frontend/src/system/applist.ts

21
frontend/components.d.ts

@ -16,17 +16,28 @@ declare module 'vue' {
BatteryPop: typeof import('./src/components/taskbar/BatteryPop.vue')['default'] BatteryPop: typeof import('./src/components/taskbar/BatteryPop.vue')['default']
Browser: typeof import('./src/components/builtin/Browser.vue')['default'] Browser: typeof import('./src/components/builtin/Browser.vue')['default']
Calendar: typeof import('./src/components/builtin/Calendar.vue')['default'] Calendar: typeof import('./src/components/builtin/Calendar.vue')['default']
Chat: typeof import('./src/components/localchat/Chat.vue')['default'] Chat: typeof import('./src/components/chat/Chat.vue')['default']
ChatBox: typeof import('./src/components/chat/ChatBox.vue')['default']
ChatContent: typeof import('./src/components/localchat/ChatContent.vue')['default'] ChatContent: typeof import('./src/components/localchat/ChatContent.vue')['default']
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']
ChatHeader: typeof import('./src/components/chat/ChatHeader.vue')['default']
ChatHistory: typeof import('./src/components/chat/ChatHistory.vue')['default']
ChatList: typeof import('./src/components/chat/ChatList.vue')['default']
ChatMenu: typeof import('./src/components/chat/ChatMenu.vue')['default']
ChatMessage: typeof import('./src/components/chat/ChatMessage.vue')['default']
ChatMsgList: typeof import('./src/components/chat/ChatMsgList.vue')['default']
ChatNav: typeof import('./src/components/localchat/ChatNav.vue')['default'] ChatNav: typeof import('./src/components/localchat/ChatNav.vue')['default']
ChatUserList: typeof import('./src/components/chat/ChatUserList.vue')['default']
ChatUserSetting: typeof import('./src/components/chat/ChatUserSetting.vue')['default']
ChatWorkList: typeof import('./src/components/chat/ChatWorkList.vue')['default']
CloseButton: typeof import('./src/components/ui/CloseButton.vue')['default'] CloseButton: typeof import('./src/components/ui/CloseButton.vue')['default']
CloseDesktop: typeof import('./src/components/desktop/CloseDesktop.vue')['default'] CloseDesktop: typeof import('./src/components/desktop/CloseDesktop.vue')['default']
ColorPicker: typeof import('./src/components/setting/ColorPicker.vue')['default'] ColorPicker: typeof import('./src/components/setting/ColorPicker.vue')['default']
Computer: typeof import('./src/components/computer/Computer.vue')['default'] Computer: typeof import('./src/components/computer/Computer.vue')['default']
ComputerNavBar: typeof import('./src/components/computer/ComputerNavBar.vue')['default'] ComputerNavBar: typeof import('./src/components/computer/ComputerNavBar.vue')['default']
Conservation: typeof import('./src/components/chat/Conservation.vue')['default']
ContextMenu: typeof import('./src/components/builtin/ContextMenu.vue')['default'] ContextMenu: typeof import('./src/components/builtin/ContextMenu.vue')['default']
CreateUrl: typeof import('./src/components/builtin/CreateUrl.vue')['default'] CreateUrl: typeof import('./src/components/builtin/CreateUrl.vue')['default']
CustomIcon: typeof import('./src/components/taskbar/CustomIcon.vue')['default'] CustomIcon: typeof import('./src/components/taskbar/CustomIcon.vue')['default']
@ -41,6 +52,7 @@ declare module 'vue' {
EditFileName: typeof import('./src/components/builtin/EditFileName.vue')['default'] EditFileName: typeof import('./src/components/builtin/EditFileName.vue')['default']
EditType: typeof import('./src/components/builtin/EditType.vue')['default'] EditType: typeof import('./src/components/builtin/EditType.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert'] ElAlert: typeof import('element-plus/es')['ElAlert']
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'] ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
@ -51,16 +63,20 @@ declare module 'vue' {
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
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']
ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElEmpty: typeof import('element-plus/es')['ElEmpty']
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']
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']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElOption: typeof import('element-plus/es')['ElOption'] 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'] ElPopover: typeof import('element-plus/es')['ElPopover']
@ -84,15 +100,18 @@ declare module 'vue' {
FileProps: typeof import('./src/components/builtin/FileProps.vue')['default'] FileProps: typeof import('./src/components/builtin/FileProps.vue')['default']
FileTree: typeof import('./src/components/builtin/FileTree.vue')['default'] FileTree: typeof import('./src/components/builtin/FileTree.vue')['default']
FileViewer: typeof import('./src/components/builtin/FileViewer.vue')['default'] FileViewer: typeof import('./src/components/builtin/FileViewer.vue')['default']
Home: typeof import('./src/components/chat/Home.vue')['default']
IframeFile: typeof import('./src/components/window/IframeFile.vue')['default'] IframeFile: typeof import('./src/components/window/IframeFile.vue')['default']
ImageViewer: typeof import('./src/components/builtin/ImageViewer.vue')['default'] ImageViewer: typeof import('./src/components/builtin/ImageViewer.vue')['default']
Install: typeof import('./src/components/install/Install.vue')['default'] Install: typeof import('./src/components/install/Install.vue')['default']
InstallCompony: typeof import('./src/components/install/InstallCompony.vue')['default'] InstallCompony: typeof import('./src/components/install/InstallCompony.vue')['default']
InstallMember: typeof import('./src/components/install/InstallMember.vue')['default'] InstallMember: typeof import('./src/components/install/InstallMember.vue')['default']
InstallPerson: typeof import('./src/components/install/InstallPerson.vue')['default'] InstallPerson: typeof import('./src/components/install/InstallPerson.vue')['default']
LocalChat: typeof import('./src/components/localchat/LocalChat.vue')['default']
LockDesktop: typeof import('./src/components/desktop/LockDesktop.vue')['default'] LockDesktop: typeof import('./src/components/desktop/LockDesktop.vue')['default']
Magnet: typeof import('./src/components/taskbar/Magnet.vue')['default'] Magnet: typeof import('./src/components/taskbar/Magnet.vue')['default']
MarkDown: typeof import('./src/components/builtin/MarkDown.vue')['default'] MarkDown: typeof import('./src/components/builtin/MarkDown.vue')['default']
Menu: typeof import('./src/components/chat/Menu.vue')['default']
MenuBar: typeof import('./src/components/window/MenuBar.vue')['default'] MenuBar: typeof import('./src/components/window/MenuBar.vue')['default']
MenuList: typeof import('./src/components/taskbar/MenuList.vue')['default'] MenuList: typeof import('./src/components/taskbar/MenuList.vue')['default']
MessageCenterPop: typeof import('./src/components/taskbar/MessageCenterPop.vue')['default'] MessageCenterPop: typeof import('./src/components/taskbar/MessageCenterPop.vue')['default']

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

@ -0,0 +1,105 @@
<script setup lang="ts">
import { useChatStore } from '@/stores/chat';
import { Search } from '@element-plus/icons-vue';
const store = useChatStore()
onMounted(() => {
store.initChat()
})
</script>
<template>
<el-container class="container">
<!--菜单-->
<el-aside class="menu">
<chat-menu />
</el-aside>
<el-container class="side" v-if="store.currentNavId < 3">
<!--搜索栏-->
<el-header class="search" v-if="store.currentNavId < 2">
<el-input placeholder="搜索" :prefix-icon="Search" class="search-input" v-model="store.search" />
</el-header>
<!--好友列表-->
<el-main class="list">
<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>
<el-container class="chat-box">
<chat-box v-if="store.currentNavId < 2" />
</el-container>
<el-container class="chat-setting" v-if="store.currentNavId == 5">
<ChatUserSetting />
</el-container>
</el-container>
</template>
<style scoped>
.container {
display: flex;
height: 100%;
width: 100%;
overflow-y: hidden;
overflow-x: hidden;
}
.menu {
width: 65px;
background-color: #2E2E2E;
overflow-y: hidden;
overflow-x: hidden;
-webkit-app-region: drag;
}
.side {
flex: 1;
/* 占据剩余宽度 */
max-height: max-content;
border-right: 1px solid #edebeb;
overflow-y: hidden;
overflow-x: hidden;
background-color: #F7F7F7;
}
.search {
width: 100%;
/* 占据整个宽度 */
height: 50px;
padding: 0;
-webkit-app-region: drag;
}
.search-input {
width: calc(100% - 20px);
/* 减去左右边距 */
margin: 10px;
-webkit-app-region: no-drag;
--el-input-placeholder-color: #818181 !important;
--el-input-icon-color: #5D5D5D !important;
}
.list {
width: 100%;
/* 占据整个宽度 */
padding: 0;
overflow-y: hidden;
overflow-x: hidden;
}
.chat-box {
flex: 3;
/* 占据剩余宽度的三倍 */
max-height: max-content;
background-color: #F5F5F5;
}
.chat-setting {
width: calc(100% - 65px);
/* 占据整个宽度 */
height: 100%;
overflow: hidden;
}
</style>

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

@ -0,0 +1,170 @@
<script setup lang="ts">
import { useChatStore } from '@/stores/chat';
const store = useChatStore()
</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="input-option">
<el-icon :size="20" class="input-option-icon">
<Picture />
</el-icon>
<el-icon :size="20" class="input-option-icon">
<Link />
</el-icon>
<el-icon :size="20" class="input-option-icon">
<Delete />
</el-icon>
</div>
<!--聊天输入区-->
<div class="input-textarea">
<el-input type="textarea" maxlength="1000" resize="none" class="textarea"
@keyup.enter.exact="store.sendMessage" v-model="store.message" />
</div>
<!--聊天发送按钮-->
<el-tooltip placement="top" content="按enter键发送,按ctrl+enter键换行">
<el-icon :size="22" class="input-button">
<Promotion />
</el-icon>
</el-tooltip>
</el-footer>
</div>
<div class="no-message-container" v-else>
<el-icon :size="180" color="#0078d7">
<ChatDotRound />
</el-icon>
</div>
</template>
<style scoped>
.chatbox-main {
width: 100%;
height: 100%;
}
.chat-header {
width: 100%;
/* 占据整个宽度 */
height: 50px;
line-height: 50px;
padding: 0;
-webkit-app-region: drag;
}
header-title {
font-size: 20px;
text-align: left;
margin-left: 15px;
}
.msg-main {
width: 100%;
/* 占据整个宽度 */
height: calc(70% - 50px);
padding: 0;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-width: 0;
border-right-width: 0;
border-color: #D6D6D6;
border-style: solid;
}
.msg-footer {
width: 100%;
/* 占据整个宽度 */
height: 30%;
padding: 0;
}
.input-option {
height: 20px;
padding: 5px;
}
.input-option-icon {
color: #494949;
margin-left: 20px;
margin-top: 5px;
cursor: pointer;
}
.input-textarea {
height: calc(100% - 50px);
width: calc(100% - 20px);
/* 减去左右边距 */
margin: 10px;
}
.textarea {
font-size: 16px;
font-family: Arial, sans-serif;
line-height: 1.5;
width: 100%;
height: 100%;
overflow-y: hidden;
overflow-x: hidden;
--el-input-border-radius: 0;
--el-input-border-color: transparent;
--el-input-hover-border-color: transparent;
--el-input-clear-hover-color: transparent;
--el-input-focus-border-color: transparent;
}
.input-button {
position: absolute;
bottom: 12px;
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 {
background-color: #D1E4FF;
/* 悬浮时颜色略深,保持浅色调 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
/* 稍微增强阴影 */
}
.input-button:active {
background-color: #B3D4FC;
/* 按下时颜色更深,但依然保持清新 */
box-shadow: 0 1px 2px rgba(0, 0, 0.1);
/* 回复初始阴影 */
transform: translateY(1px);
/* 微小下移,模拟按下 */
}
.no-message-container {
height: 100%;
margin: 120px auto;
text-align: center;
justify-content: center;
}
</style>

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

@ -0,0 +1,72 @@
<script setup>
import {ref} from "vue";
import { useChatStore } from "@/stores/chat";
import {Setting as SettingIcon} from "@element-plus/icons-vue";
const store = useChatStore()
</script>
<template>
<el-row>
<el-avatar shape="square" :size="40" class="userAvatar" :src="store.userInfo.avatar"/>
</el-row>
<el-row @click="store.setCurrentNavId(0)">
<el-icon v-if="store.currentNavId === 0" class="menu-icon-on">
<ChatLineRound />
</el-icon>
<el-icon v-else class="menu-icon">
<ChatRound />
</el-icon>
</el-row>
<el-row @click="store.setCurrentNavId(1)">
<el-icon v-if="store.currentNavId === 1" class="menu-icon-on">
<UserFilled />
</el-icon>
<el-icon v-else class="menu-icon">
<User />
</el-icon>
</el-row>
<el-row @click="store.setCurrentNavId(2)">
<el-icon v-if="store.currentNavId === 2" class="menu-icon-on">
<Platform />
</el-icon>
<el-icon v-else class="menu-icon">
<Monitor />
</el-icon>
</el-row>
<el-row @click="store.setCurrentNavId(5)">
<el-icon v-if="store.currentNavId === 5" class="menu-icon-on">
<SettingIcon />
</el-icon>
<el-icon v-else class="menu-icon">
<SettingIcon />
</el-icon>
</el-row>
</template>
<style scoped>
.userAvatar {
margin: 30px auto 10px;
-webkit-app-region: no-drag
}
.menu-icon {
margin: 20px auto;
font-size: 25px;
color: #8F8F8F;
cursor: pointer;
}
.menu-icon-on {
margin: 20px auto;
font-size: 25px;
color: #07C160;
cursor: pointer;
}
/* 新增样式 */
.floating-button {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
}
</style>

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

@ -0,0 +1,166 @@
<script setup lang="ts">
import { useChatStore } from '@/stores/chat';
const store = useChatStore()
</script>
<template>
<div v-for="item in store.msgList">
<!--聊天记录-用户-->
<div v-if="item.userInfo.id === store.targetUserId">
<div class="chat-item">
<el-row>
<el-col :span="8" />
<el-col :span="14">
<el-row>
<el-col :span="24">
<div class="chat-name-me">{{ item.userInfo.username }}</div>
</el-col>
</el-row>
<div class="bubble-me" @contextmenu.prevent="store.showContextMenu($event, item.id)">
<div class="chat-font">
{{ item.content }}
</div>
</div>
</el-col>
<el-col :span="2">
<div class="chat-avatar">
<el-avatar shape="square" style="margin: 0;float: left" :size="32" class="userAvatar"
:src="item.userInfo.avatar" />
</div>
</el-col>
</el-row>
</div>
</div>
<!--聊天记录-好友-->
<div v-else>
<div class="chat-item">
<el-row>
<el-col :span="2">
<div class="chat-avatar">
<el-avatar shape="square" style="margin: 0;float: right" :size="32" class="userAvatar"
:src="item.userInfo.avatar" />
</div>
</el-col>
<el-col :span="14">
<el-row>
<el-col :span="24">
<div class="chat-name-other">{{ item.userInfo.username }}</div>
</el-col>
</el-row>
<div class="bubble-other">
<div class="chat-font">
{{ item.content }}
</div>
</div>
</el-col>
<el-col :span="8" />
</el-row>
</div>
</div>
<div v-if="item.type === 1">
<div class="withdraw">
{{ item.userInfo.id === store.targetUserId ? "你" : item.userInfo.username }}撤回了一条消息
</div>
</div>
</div>
<!--悬浮菜单-->
<div class="context-menu" v-if="store.contextMenu.visible"
:style="{ top: `${store.contextMenu.y}px`, left: `${store.contextMenu.x}px` }">
<div v-for="item in store.contextMenu.list" class="context-menu-item">
<div class="context-menu-item-font" @click="store.handleContextMenu(item.id)">
{{ item.label }}
</div>
</div>
</div>
</template>
<style scoped>
.bubble-me {
background-color: #95EC69;
float: right;
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;
}
.bubble-other {
background-color: #FFFFFF;
float: left;
border-radius: 4px;
margin-left: 5px;
margin-top: 5px;
}
.bubble-other:hover {
background-color: #EBEBEB;
}
.chat-name-other {
font-size: 14px;
font-family: Arial, sans-serif;
line-height: 1.5;
color: #B2B2B2;
float: left;
margin-left: 5px;
}
.chat-font {
margin: 8px;
font-size: 15px;
font-family: Arial, sans-serif;
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;
}
</style>

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

@ -0,0 +1,71 @@
<script setup>
import { ref } from "vue";
import { useChatStore } from "@/stores/chat";
const store = useChatStore()
</script>
<template>
<div v-for="item in store.chatList">
<div class="list-item" @click="store.changeChatList(item)"
:style="{ backgroundColor: item.id === store.targetUserId ? '#C4C4C4' : '' }">
<el-row>
<el-col :span="6">
<el-avatar shape="square" :size="40" class="avatar" :src="item.avatar" />
</el-col>
<el-col :span="18">
<el-row>
<el-col :span="18">
<div class="previewName">{{ item.name }}</div>
</el-col>
<el-col :span="6">
<div class="previewTime">{{ item.previewTimeFormat }}</div>
</el-col>
</el-row>
<el-row>
<div v-if="item.previewType === 0" class="previewChat">{{ item.previewMessage }}</div>
<div v-if="item.previewType === 1" class="previewChat">{{ item.userId === id ? "" : "\"" + item.name +
"\""}}撤回了一条消息</div>
</el-row>
</el-col>
</el-row>
</div>
</div>
</template>
<style scoped>
.list-item {
width: 100%;
height: 60px;
}
.list-item:hover {
background-color: #D0D0D0;
}
.avatar {
margin: 10px
}
.previewName {
margin-top: 10px;
font-size: 15px;
font-family: Arial, sans-serif;
line-height: 1.5;
}
.previewChat {
font-size: 14px;
font-family: Arial, sans-serif;
color: #999999;
}
.previewTime {
float: right;
margin-top: 10px;
margin-right: 10px;
font-size: 12px;
font-family: Arial, sans-serif;
color: #999999;
}
</style>

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

@ -0,0 +1,71 @@
<script setup>
import { ref } from "vue";
import { useChatStore } from "@/stores/chat";
const store = useChatStore()
</script>
<template>
<div v-for="item in store.chatList">
<div class="list-item" @click="store.changeChatList(item)"
:style="{ backgroundColor: item.id === store.targetUserId ? '#C4C4C4' : '' }">
<el-row>
<el-col :span="6">
<el-avatar shape="square" :size="40" class="avatar" :src="item.avatar" />
</el-col>
<el-col :span="18">
<el-row>
<el-col :span="18">
<div class="previewName">{{ item.name }}</div>
</el-col>
<el-col :span="6">
<div class="previewTime">{{ item.previewTimeFormat }}</div>
</el-col>
</el-row>
<el-row>
<div v-if="item.previewType === 0" class="previewChat">{{ item.previewMessage }}</div>
<div v-if="item.previewType === 1" class="previewChat">{{ item.userId === id ? "" : "\"" + item.name +
"\""}}撤回了一条消息</div>
</el-row>
</el-col>
</el-row>
</div>
</div>
</template>
<style scoped>
.list-item {
width: 240px;
height: 60px;
}
.list-item:hover {
background-color: #D0D0D0;
}
.avatar {
margin: 10px
}
.previewName {
margin-top: 10px;
font-size: 15px;
font-family: Arial, sans-serif;
line-height: 1.5;
}
.previewChat {
font-size: 14px;
font-family: Arial, sans-serif;
color: #999999;
}
.previewTime {
float: right;
margin-top: 10px;
margin-right: 10px;
font-size: 12px;
font-family: Arial, sans-serif;
color: #999999;
}
</style>

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

@ -0,0 +1,23 @@
<template>
<el-form :model="form" label-width="auto" style="padding: 30px;">
<el-form-item label="昵称">
<el-input v-model="form.nickname" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
// do not use same name with ref
const form = reactive({
nickname: '',
})
const onSubmit = () => {
console.log('submit!')
}
</script>

10
frontend/src/components/chat/ChatWorkList.vue

@ -0,0 +1,10 @@
<script setup>
import { ref } from "vue";
import { useChatStore } from "@/stores/chat";
const store = useChatStore()
</script>
<template>
</template>

0
frontend/src/components/localchat/Chat.vue → frontend/src/components/localchat/LocalChat.vue

86
frontend/src/stores/chat.ts

@ -0,0 +1,86 @@
import { defineStore } from 'pinia'
import emojiList from "@/assets/emoji.json"
import { getSystemConfig } from '@/system/config'
export const useChatStore = defineStore('chatStore', () => {
const userList: any = ref([]) // 用户列表
const chatList: any = ref([]) // 消息列表
const msgList: any = ref([]) //聊天消息列表
const userInfo: any = ref({})
const showChooseFile = ref(false)
const currentPage = ref(1)
const pageSize = ref(50)
const scrollbarRef = ref()
const innerRef = ref()
const config = getSystemConfig()
const currentNavId = ref(0)
const message: any = ref()
const targetUserInfo:any = ref({})
const targetUserId = ref(0)
const search = ref('')
const contextMenu = ref({
visible: false,
chatMessageId: 0,
list: [
{
id: 2,
label: '撤回',
}
],
x: 0,
y: 0
})
const initChat = () => {
if(config.userInfo.avatar == ''){
config.userInfo.avatar = '/logo.png'
}
userInfo.value = config.userInfo
}
const setCurrentNavId = (id: number) => {
currentNavId.value = id
}
const sendMessage = async () => {
await setScrollToBottom()
}
const setScrollToBottom = async () => {
await nextTick()
const max = innerRef.value.clientHeight
scrollbarRef.value.setScrollTop(max)
}
const changeChatList = async (item:any) => {
// const res = await getChatList(id)
// chatList.value = res.data
}
const handleContextMenu = async (id: number) => {
contextMenu.value.visible = false;
}
const showContextMenu = (event: any, id: number) => {
contextMenu.value.visible = true;
contextMenu.value.chatMessageId = id;
contextMenu.value.x = event.x;
contextMenu.value.y = event.y;
}
return {
emojiList,
userList,
chatList,
msgList,
userInfo,
search,
showChooseFile,
currentPage,
pageSize,
scrollbarRef,
innerRef,
currentNavId,
targetUserInfo,
targetUserId,
message,
contextMenu,
initChat,
setCurrentNavId,
sendMessage,
changeChatList,
handleContextMenu,
showContextMenu
}
})

5
frontend/src/stores/db.ts

@ -1,12 +1,13 @@
import Dexie from 'dexie' import Dexie from 'dexie'
export type ChatTable = 'chatuser' | 'chatmsg' | 'chatfile' export type ChatTable = 'chatuser' | 'chatmsg' | 'chatmessage' | 'groupmessage'
export const dbInit:any = new Dexie('GodoOSDatabase'); export const dbInit:any = new Dexie('GodoOSDatabase');
dbInit.version(1).stores({ dbInit.version(1).stores({
chatuser:'++id,ip,hostname,userName,avatar,mobile,nickName,isOnline,updatedAt,createdAt', chatuser:'++id,ip,hostname,userName,avatar,mobile,nickName,isOnline,updatedAt,createdAt',
chatmsg:'++id,targetId,targetIp,senderInfo,reciperInfo,content,type,status,isRead,isMe,readAt,createdAt', chatmsg:'++id,targetId,targetIp,senderInfo,reciperInfo,content,type,status,isRead,isMe,readAt,createdAt',
chatfile:'++id,msgId,fileName,fileSize,fileType,fileUrl,filePath,fileExt' chatmessage:'++id,userId,toUserId,senderInfo,isMe,isRead,content,type,readAt,createdAt',
groupmessage:'++id,userId,groupId,senderInfo,isMe,isRead,content,type,readAt,createdAt'
}); });
export const db = { export const db = {

3
frontend/src/system/applist.ts

@ -189,7 +189,8 @@ export const appList = [
{ {
name: 'localchat', name: 'localchat',
appIcon: "chat", appIcon: "chat",
content: "Chat", //content: "LocalChat",
content:"Chat",
frame: true, frame: true,
width: 800, width: 800,
height: 600, height: 600,

Loading…
Cancel
Save